ンンンパ

ふとしです

移転しました

Ruby on Rails + webpack-dev-server で Development.

Rails で JavaScript が必要なプライベートプロジェクトでは、ながらく Npm + Watchify + Gulp + Rails を手動起動などしながらすすめていましたが、最近、意を決して Yarn + Webpack + Rails に変更しました。そこで、Rails と JavaScript の接合点も Railsway にのった設定に変えようと思いました。(今までは public/css を直接参照していた)

ざっと検索したところ、webpack-dev-server 経由で JavaScript を参照する場合、あらたな helper を用意する方式しか見つけられなかったので、assets の path を変更する方式をメモります。

概要

webpack-dev-server を port 5001 で起動し、Rails の assets 用のタグがそれを参照するようにします。

Rails configuration

#config/environments/development.rb

Rails.application.configure do
  # snip
  config.action_controller.asset_host = 'http://localhost:5001'
end

Webpack configuration

Files place

JavaScript ファイルとして app1.js, app2.js, app3.js が書きだされる想定です。

- app # 他 Rails のディレクトリ
- public
- lib
  - sass
    - common.sass
  - src
    - app1
      - index.js
    - app2
      - index.js
    - app3
      - index.js
  - package.json

package.json

style-loader を噛ませるととても遅いので、sass は node-sass ダイレクトで書きだします。書きだし先のディレクトリを参照できるように、webpack-dev-server を --content-base でルートを public に合わせておきます。

  "scripts": {
    "watch": "webpack-dev-server --progress --colors --config ./webpack/dev.config.js --port 5001 --content-base ../public",
    "watch-sass": "node-sass --watch ./sass/common.sass ../public/stylesheets/common.css"
  },

webpack.config.js

publicPath を javascripts という、Rails ネーミングに合わせておきます。

sass も webpack-dev-server を通す場合、こちらにも publicPath が必要ですが、こちらは ExtractTextPlugin 側に設定しなければならない点に注意します。

const entryPoints =  require('glob').sync('./src/*/index.js').reduce((a, v)=> {
  a[v.split('/')[2]] = v
  return a
}, {})

const jsConfiguration = {
  entry: entryPoints,
  output: {
    publicPath: 'javascripts',
    filename: "[name].js"
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
      }
    ]
  }
}

const ExtractTextPlugin = require('extract-text-webpack-plugin')
const cssConfiguration = {
  entry: './sass/common.sass',
  output: {
    publicPath: 'stylesheets',
    filename: "common.css"
  },
  module: {
    loaders: [
      {
        test: /\.sass|\.scss/,
        loader: ExtractTextPlugin.extract({
          fallbackLoader: "style-loader",
          loader: "css-loader?minimize!sass-loader",
          publicPath: 'stylesheets',
        })
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin("common.css")
  ]
}

module.exports = [
  jsConfiguration,
  // cssConfiguration
]

できあがり

これで以下のようにコンパイルされ、webpack-dev-server は狙ったファイルを返すようになりました。

= javascript_include_tag('app1.js')
<script src="http://localhost:5001/javascripts/app1.js"></script>

なお Rails 起動時には webpack-dev-server も同時に起動することになりますが、Procfile.dev を用意して foreman で起動すると手間がかからなくて良いようです。

web: bundle exec rails server -p 5000
compile: cd lib/es && yarn run watch
compile: cd lib/es && yarn run watch-sass
foreman start -f Procfile.dev

今年の洗濯回数は 8 回でした。

一人暮らしももう 20 年に近くなりますが、洗濯機が家にあったことがありません。家に洗濯機がないので、コインランドリーを利用しています。利用料金は洗濯に 1,200 円、乾燥機に 700 円ほどです。

乾燥機は 25 kg対応の大型のものが 700 円で 56 分ほどまわります。本来はそこまでの容量のものを、そこまでの時間回す必要がないのですが、とにかくパリパリにするのが好きなのでまわします。

1 回目 2 月

2 回目 4 月

記録なし

3 回目 5 月

4 回目 7 月

5 回目 8 月

就職しました。

6 回目 10 月

7 回目 11 月

8 回目 12 月

まとめ

冬場はパーカーが洗濯物に加わりますので、少しペースが早くなりますね。

今年も少し T シャツとブリーフが増えましたが、同じぐらい捨てました。来年も同様のペースになることでしょう。よろしくお願いいたします。

top コマンドで得られる結果をどう見ていけばいいのかわからないので、とりあえず視覚化した

もともと一つの処理であるとか、一つのメソッドであるとかの処理時間には興味があって、測定などをしていました。ActiveRecord を使うにあたって発行クエリを抑えることにより、本当に早くなるのか確かめたりするのがすきです。

全体的なパフォーマンスを測定し、改善に繋げる知識がない

そういう局所的なものは何度もまわして処理時間を取って、という方法でなんとなく測定をできるのですが、Web アプリケーション全体の負荷の把握方法や、運用においてのスローダウンの原因究明などに使える情報などの取得については、ほとんど知識がありません。

ぐぐることにより、各種コマンドで負荷を数値化できることはすぐにわかりましたが、その数値が何を意味するかはよくわかりません (負荷が高い低いぐらいはわかるが、どういう経緯でそうなったかわからない)。さらに、これを蓄積し、それをそのまま眺めたところで、勘の悪いわたしが何かを把握できるとは思えなかったので、せめてわかりやすい形に変換しようと、最近取り組んでいたのが top のグラフ化です。

f:id:mmmpa:20161211200646g:plain

これは Chrome で 70 タブぐらい一気に開いたときの図です。CPU は大暴れ、メモリ消費もアゲアゲで大変きれいですね。

どこかにありそうなソフトですが、こういうラッパーのようなものをつくると、そのコマンドと仲良くなれるのでまるっきり無駄ということはないと信じています。

勉強

現段階では単にグラフ化しただけなので、ここからどうつなげていくかは要勉強というところですが、これだけでも Chrome の大量のタブは大量のプロセスを産んで、それぞれ個別にちょっとずつメモリを消費して大変なことになるので、個別のメモリに注目していてはその本当の負荷はわからないということがわかりました。

いまは、翔泳社 + kindle のポイント還元セールで入手した以下の本で、パフォーマンスのあれこれに関する勘所を把握しようとしています。各分野においての基礎知識を軽く説明ののちに、パフォーマンスなどの原因や解決法の話に入るので、まるでわからない状態で聞かされることがなく、いい本だと思います。

絵で見てわかるシステムパフォーマンスの仕組み

絵で見てわかるシステムパフォーマンスの仕組み

その他

プログラムのほうは、バックエンドは Go から SSH で対象サーバーに接続していますが goroutine の不適切使用により、同データへの同時アクセスが原因と思われる panic というのを経験して、非同期処理の経験値がアップしました。よかったですね。

Go 言語でつくったもののメモリとかをなんとなく見れるようにする

pprof を用いた詳細な情報を得る方法はさんざん紹介されており、しかしその詳細の情報のどこを見ればいいのかわからないので、とにかく簡単に見れるような施策を打ってみました。

f:id:mmmpa:20161202202126g:plain

これは社の Slack で動いてるボットの一つの強制停止画面ですが、ボットは継続して動いているので、メモリリークなどが気になるところです。ということで、グラフかつ、前後の増減がスッと把握できるようにしました。(これは短期すぎて役に立たなさそうですが)

グラフの描画は雑に C3.js で行っており雑な JavaScript なので特にあらためて何もないのですが、グラフを描画する用のデータ配列に関しては、今後もちゃんと把握して真面目に実装していきたいので、package 化して再利用できるようにしました。

github.com

go heaper.Run(1, 60)

http.HandleFunc("/heaps", func(w http.ResponseWriter, req *http.Request) {
    heaps, _ := json.Marshal(heaper.Read())
    w.Header().Set("Content-Type", "application/json")
    w.Write(heaps)
})

こんな感じで pprof.WriteHeapProfile で得られる情報を任意の間隔 (1 sec)、任意の量 (60) ためておき、見れるという感じで、今後、どこを見ればいいのかわかるようになれば、それなりに役に立ってくれるはずです、多分。ただ単に画面が動いてるのが好きなのでやってみたという気がしないでもない。

Go 言語で struct を url.Values に展開した

現在勉強用に作っているボットでは、Slack の API を使います。勉強用なので、ライブラリを使わず、独自に実装しています。API ではエンドポイント毎にさまざまな要求パラメーターがあって、その引数を自由な形式にすると間違いのもとになるので、専用の struct を用意して間違いが起こらないようにしています。

func (a *API) ChatPostMessages(p ChatPostMessageParameters) (ChatPostMessagesResponse, error) {
    var r ChatPostMessagesResponse
    err := a.postAndStruct(&r, "/chat.postMessage", p)

    return r, err
}

public にしているメソッドはそのようなキッチリ定義した struct を引数として要求する一方で、色々なエンドポイントを叩くときのメソッド (postAndStruct など) は共通化したかったため、private にしてあるその共通メソッドの引数では、どんな struct でも受けつけて url.Values に変換、POST や GET したいと思いました。

というわけで、あまり推奨されていないであろう、reflect で struct をなんでも展開という方法を使います。

func (a *API) buildParameters(parameters interface{}) url.Values {
    values := url.Values{}
    r := reflect.ValueOf(parameters)
    rt := r.Type()

    for i := 0; i < rt.NumField(); i++ {
        f := rt.Field(i)

        key := ""
        if to := f.Tag.Get("json"); to != "" {
            key = to
        } else {
            key = strings.ToLower(f.Name)
        }

        v := fmt.Sprint(r.Field(i))

        if v != "" {
            values.Add(key, v)
        }
    }

    values.Add("token", a.Token)

    return values
}

interfrace{} として受け取った値を reflect.ValueOf(parameters) するのがキモで、それ以外は流れ作業でした。どうやれば interface{} -> struct できるかというのは github.com/fatih/structs というライブラリを読んで知りました。ソースコードがスッと読める言語は、こういう時に便利ですね。

github.com

Go 言語でつくったボットを GitHub -> CircleCI -> Bluemix と自動デプロイできるようにした

Golang でのボット作成では、1 個目は自 PC で動かすことしか考えていませんでしたが、2 個目はきちんとどこかにデプロイすることを目標に作成していましたので、ついでに自動でやれるようにしました。

Heroku では 24 時間稼働のボット用途には不向きだと思われたので、IBM Bluemix にデプロイ先に選びました。Bluemix もデフォルトで Golang をサポートしていますが、ビルトインの buildpack だと Godeps のバージョンをきちんと読んでくれなかったり (これは気のせいかもしれない) したのでgithub にあるものを利用したりして色々とあれでしたが、うまく行きました。

circle.yml の様子。

machine:
  environment:
    REPO_ROOT: "${HOME}/.go_workspace/src/YOUR_DOMAIN/YOUR_PACKAGE_DIR"

dependencies:
  pre:
    - mkdir -p ${REPO_ROOT}
    - cp -rf ./* ${REPO_ROOT}
    - go get github.com/tools/godep
    - curl -v -L -o cf-cli_amd64.deb 'https://cli.run.pivotal.io/stable?release=debian64&source=github'
    - sudo dpkg -i cf-cli_amd64.deb
    - cf -v

test:
  pre:
    - go vet ./...
  override:
    - cd ${REPO_ROOT} && godep go test ./...
  post:
    - cf api https://api.au-syd.bluemix.net
    - cf login -u $BLUEMIX_USER -p $BLUEMIX_PASSWORD
    - cf target -o $BLUEMIX_ORG -s $BLUEMIX_SPACE
    - cf a

deployment:
  production:
    branch: master
    commands:
      - cf push APP_NAME -b https://github.com/cloudfoundry/go-buildpack.git

とにかくプッシュ、テスト、デプロイまでをやれるようにとツギハギしたので、まだ無駄があると思いますが、とりあえずこれでアップできます。

つまずきポイントとしては、

  • ${HOME}/.go_workspace/src/ へリポジトリを展開しないと、go get -t -d -v ./... でリモートからパッケージ fetch しようとして死んだ (YOUR_DOMAIN を github ではないところにしているため、みつからない)
  • cf api でエリア指定を間違えた (だいたいの例はアメリカ南部になっているが、シドニーを使っている)
  • cf のインターフェースが gem install cf のものとちょっとちがう

などがありました。

基本的にはテストが通ったら cf で Bluemix にデプロイするというだけなので、すでに cf でのデプロイを済ませている人は、設定の際にあらためて考えることはない感じですね。