Vim で起きているイベントを Machinist で可視化する

2019年12月09日 月曜日


【この記事を書いた人】
猪俣 亮

食べることが大好きなダイエット中プログラマ。 本業はバックエンド開発だが、ここ最近はフロントエンド開発にもどっぷり浸かっている。

「Vim で起きているイベントを Machinist で可視化する」のイメージ
IIJ Engineers blog読者プレゼントキャンペーン

Twitterフォロー&条件付きツイートで「バリーくんぬいぐるみ」を抽選で20名にプレゼント!
応募期間は2019/11/29~2019/12/31まで。詳細はこちらをご覧ください。
今すぐツイートするならこちら→ フォローもお忘れなく!

IIJ 2019 TECHアドベントカレンダー 12/9(月)の記事です】

昨年のアドベントカレンダーでも紹介した Machinist (マシニスト) は身の回りのあらゆるメトリクスを収集し、管理することを目的として開発されたサービスです。
今年7月に正式版リリースを済ませ、多数の方にご利用いただいています。

この記事では私が普段使っているテキストエディタである「Vim」を例に、コーディング中に発生しているイベントを収集し、Machinist で可視化する方法を紹介します。

コーディング中の細かいアクティビティを取得したい

コーディングに関するアクティビティは Git のような VCS/SCM のデータを分析することでも知ることができます。これまでは GitHub の Insights 機能でそれなりに満足していたのですが、最近はコーディング中のエディタ操作をトラッキングし、細かい癖なども把握できるといいなと考えるようになりました。

私が普段使っている Vim でデータを収集するにはどうすべきか考えてみたところ、 Vim 内で定義されているイベントを集める方法であれば手軽にできそうでした。そしてそのデータを Machinist に送りつければ、いつでもアクティビティを見返すことができるであろうということで実際にやってみました。

イベントを拾って送信するためのコードを Vim に仕込む

以下のコードを .vimrc に追加しました。

let g:machinist_api_key    = 'API_KEY'                               " Machinist で発行した API キー
let g:machinist_agent_name = 'macbook-pro'                           " 送信元を識別するための名前を指定する
let g:machinist_endpoint   = 'https://gw.machinist.iij.jp/endpoint'  " Machinist API の URL
let g:timewindow_interval  = 60                                      " データの集計と送信の間隔

function! s:calc_timewindow(timestamp, interval)
	return a:timestamp / a:interval * a:interval
endfunction

function! s:post_to_machinist(counters, timestamp)
		if (len(keys(a:counters)))
				let data = { 'agent': g:machinist_agent_name, 'metrics': [] }

				for event in keys(a:counters)
						let counter = a:counters[event]
						for buffer_name in keys(counter)
								let metric = {
										\ 'namespace': 'vim',
										\ 'name': event,
										\ 'tags': { 'buffer_name': buffer_name },
										\ 'data_point': { 'value': counter[buffer_name], 'timestamp': a:timestamp }
										\ }
								call add(data['metrics'], metric)
						endfor
				endfor

				let json    = json_encode(data)
				let headers = "-H 'Content-Type: application/json' -H 'Authorization: Bearer " . g:machinist_api_key . "'"
				let command = "curl " . headers . " '" . g:machinist_endpoint . "' -d '" . json . "' &"
				call system(command)
		endif
endfunction

function! s:count_event(event)
		let now                  = localtime()
		let l:current_timewindow = s:calc_timewindow(now, g:timewindow_interval)
		let buffer_name          = bufname()

		if (!exists('g:current_timewindow'))
				let g:current_timewindow = l:current_timewindow
		endif
		if (!exists('g:event_counters'))
				let g:event_counters = {}
		endif

		if (g:current_timewindow != l:current_timewindow)
				call s:post_to_machinist(g:event_counters, g:current_timewindow)
				let g:current_timewindow = l:current_timewindow
				let g:event_counters     = {}
		endif

		if (!has_key(g:event_counters, a:event))
				let g:event_counters[a:event] = {}
		endif

		if (has_key(g:event_counters[a:event], buffer_name))
				let g:event_counters[a:event][buffer_name] += 1
				let g:event_counters[a:event]['all']       += 1
		else
				let g:event_counters[a:event][buffer_name] = 1
				let g:event_counters[a:event]['all']       = 1
		endif
endfunction

augroup CountEvents
		autocmd!
		autocmd VimEnter     * call s:count_event('VimEnter')
		autocmd VimLeave     * call s:count_event('VimLeave')
		autocmd BufEnter     * call s:count_event('BufEnter')
		autocmd BufLeave     * call s:count_event('BufLeave')
		autocmd BufWrite     * call s:count_event('BufWrite')
		autocmd InsertEnter  * call s:count_event('InsertEnter')
		autocmd InsertLeave  * call s:count_event('InsertLeave')
		autocmd CursorHold   * call s:count_event('CursorHold')
		autocmd CursorHoldI  * call s:count_event('CursorHoldI')
		autocmd CursorMoved  * call s:count_event('CursorMoved')
		autocmd CursorMovedI * call s:count_event('CursorMovedI')
augroup END

上記コードのポイントは…

  •  autocmd でイベントを拾い、カウントするための関数に渡す
  • 1分間隔のタイムウインドウ毎に作業中のバッファ名とイベント名を区別したカウンタを作り、イベント発火時にインクリメントする
  • タイムウインドウが切り替わるタイミングで、旧タイムウインドウのカウンタの値を curl で Machinist へ POST する

といったところでしょうか。
拾うイベントは私の独断で選んでいますが、この他にも沢山のイベントが用意されているので、興味がある方は追加してみてください。

ここで少し筆を滑らせておくと、短時間に送信されたデータのタイムウインドウ集計を、Machinist 側で行えるように準備中です。この機能がリリースされれば、このコードももう少しすっきりするでしょう。

今後もう少し手を入れて、pluginに落とし込めるといいなと考えています。

貯まったデータを確認する

しばらくコーディングを続けると、データが貯まっていきます。

データが見やすくなるように、特定のファイルで発生したイベントをカスタムチャートで一つにまとめました。

CursorMoved や CursorHold の量の多さが目立ちますが、それ以外だと BufWrite が少し多めです。
やたらと無駄に保存する癖があるのは自分でも認知していましたが、それを改めて突き付けられてしまいました。
(余談ですが、このブログ記事のリビジョンも気付いたら130を超えてしまっていました。。。)

この時点ではまだデータが少ないですが、よりデータが貯まっていくにつれもっと面白い発見があるかもしれません。

最後に

Vim 内で起きているイベントを拾い、Machinist に送る方法を紹介しました。

Machinist は無料プランが用意されており、簡単に試せるようになっています。
センサーデータからテキストエディタのデータまで(?)幅広く対応できるように設計されているので、皆さんも身近にある気になるデータを見つけ、 Machinist での可視化を試してみてください。

関連リンク

猪俣 亮

2019年12月09日 月曜日

食べることが大好きなダイエット中プログラマ。 本業はバックエンド開発だが、ここ最近はフロントエンド開発にもどっぷり浸かっている。

Related
関連記事