App Engineのgo111用ディレクトリ構成

App Engine Standard EnvironmentでGo1.9を使う時は以下のように、リポジトリのルートをGOPATHとして設定し、appcfg.pyを使ってデプロイをしていました。

.
├── app.yaml
└── src
    └── app
        ├── glide.lock
        ├── glide.yaml
        ├── app.go
        ├── handler
        ├── ...
        └── vendor
  • app.go
package app

import (
        "net/http"

        "app/handler"
)

func init() {
        http.Handle("/", handler.New())
}

これがgo111のランタイムになると、main関数が必須になり、デプロイコマンドもgcloud app deployに変わります。*1

そのため、以下の条件を満たしつつ、どのようなディレクトリ構成にするのが良いのか考えてみました。

  • app.yamlのパスは変更しない
  • GOPATHリポジトリごとに変更しなくても良いようにする
    • src/apppkgに変更
    • app.gopkg.goに変更
  • 依存関係の管理にgo.modを使う

1) ./main.goを作成する

.
├── app.yaml
├── go.mod
├── go.sum
├── main.go
└── pkg
    ├── handler
    ├── ...
    └── pkg.go

2) ./cmd/app/main.goを作成し、app.yamlmainでパスを指定する

.
├── app.yaml
├── cmd
│  └── app
│      └── main.go
├── go.mod
├── go.sum
└── pkg
    ├── handler
    ├── ...
    └── pkg.go

この時、プライベートなパッケージ*2を使っているとCloud Buildで依存関係を解決できません。
対策としては、代わりにvendorを使う、もしくはgo.modreplaceディレクティブを使う、のどちらかです。

しかし、vendorを使う場合にはgo.mod.gcloudignoreに追加し、GO111MODULEoffにしないといけません。
また、go.modを使うにはGO111MODULEonにしないといけません。
GO111MODULEリポジトリGOPATHの配下に置くか、外に置かでautoの時の値が変わるため*3、組み合わせが非常に複雑です。

まとめると次のようになります。

依存管理 リポジトリの場所 GO111MODULE デプロイ可否
go.mod GOPATH配下 auto(=off) offだと使用不可
go.mod GOPATH配下 on OK
go.mod GOPATH外 auto(=on) OK
go.mod GOPATH外 off offにできない
vendor GOPATH配下 auto(=off) 1)はOK、2)はNG
vendor GOPATH配下 on NG
vendor GOPATH外 auto(=on) NG
vendor GOPATH外 off offにできない

※確認する際にCloud Buildで使われたイメージはgcr.io/gae-runtimes/go111_app_builder:go111_20190503_1_11_9_RC00です

正しくgo.modを使えば問題なくデプロイできるため、replace対象のパッケージが管理できるのであればこちらを使うのが良さそうです。
ただ、replace対象のパッケージはgitのsubmodulessubtreeで管理することになり、それが煩雑になってしまう可能性があります。

そういった場合、現段階だと1)をGOPATH配下に置くのが無難です。
app.yamlmainを指定することができませんが、今のプロジェクトでは必要なかったので以下のディレクトリ構成にすることにしました。

.
├── app.yaml
├── go.mod  // デプロイしない
├── go.sum  // デプロイしない
├── main.go
├── pkg
│  ├── handler
│  ├── ...
│  └── pkg.go
└── vendor  // `GO111MODULE=on go mod vendor` で作成

*1:https://cloud.google.com/appengine/docs/standard/go111/go-differences 参照

*2:GitHub Enterprise含む

*3:GOPATH配下: off、GOPATH外: on

MojaveでGDB 8.3を使う

1. インストール

brew install gdb --HEAD

2019/04/13時点でインストールされるバージョンは8.3.50。
2019/05/14以降は--HEAD不要。

echo "set startup-with-shell off" >> ~/.gdbinit

を実行するようにと表示されるが、ファイルの場所は$HOME/.config/gdb/initでも良い。

2. 証明書の作成

キーチェーンアクセスを起動し、メニューから

キーチェーンアクセス > 証明書アシスタント > 証明書を作成...

を選択。

以下を入力したら他はデフォルトのまま(期限は伸ばしておいても良い)ひたすら「続ける」を押していく。

名前: gdb-cert
証明書のタイプ: コード署名
✅: デフォルトを無効化

「ログイン」に作成されたgdb-certを「システム」にドラッグ&ドロップし、
証明書の情報を開いて「信頼」の「コード署名」を「常に信頼」に変更。

3. コード署名

gdb-entitlement.xmlを作成する。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.security.cs.debugger</key>
    <true/>
</dict>
</plist>

署名する。

codesign --entitlements gdb-entitlement.xml -fs gdb-cert $(which gdb)

4. 設定を反映させる

sudo pkill taskgated

goonのクエリをチューニングした時のメモ

App Engine(Go 1.9)でDatastoreからデータを取得するのにgoonGoon.Runを使っていたが、プロパティを追加する改修*1をした後、 latencyが遅くなり、DatastoreでDeadline exceededが頻繁に発生するようになってしまった。

とりあえず該当するリクエストをStackdriver Traceで見てみるとdatastore_v3.Nextが7回ほど呼ばれていることがわかった。

まずはこの呼び出し回数を減らせば速くなるだろうと思い、Query.BatchSizeを設定してみたが、呼び出し回数に変化は無く、全く効果がなかった。
それも変だなと思い、試しにデバッグログを仕込んで1回の呼び出しでどれくらいのデータを取得できているのか見てみたところ、なんと90件くらいしか取得できていなかった。

ここまで改修前の状態を確認していなかったので念のため調べてみると、datastore_v3.Nextの呼び出し回数は5回、1回あたりのデータ取得件数は約120件だった。
このことから、原因はプロパティを追加したことでデータのサイズが増え、一度に取得できる件数が減ったことによるDatastoreへのアクセス増ということがわかった。

ということで次はMemcacheを使ってDatastoreへのアクセスを減らすようにしてみた。
方法としては今までのクエリにQuery.KeysOnlyを設定してキーだけを取得するようにし、本体はGoon.GetMultiで取ってくるというシンプルなもの。
しかし、これだと1000件のデータを取得しようとするとGoon.GetMultiがMemcacheにデータを格納する際に大量のメモリを消費してしまうため、エラーになったり、インスタンスが落ちるようになってしまった。
そこでGoon.GetMultiの呼び出しが100件ずつになるようにしてみたところ、エラーは解消されたように見えたが、どうやらMemcacheにデータが乗っておらず、常にDatastoreにアクセスしてしまっていた。
仕方なく50件まで減らしたら安定してキャッシュが効くようになり、ようやく速度が改善されるようになった。

そもそもデータ構造がイマイチっぽいので本来はそこから手を入れるのが良さそうだが、今からそれをやるのもなかなかに厳しい状況なので当分はこれでしのぐことになりそう。

*1:後で確認したらデータサイズが80%ほど増加していた...

第1回 ゴリラ.vimが開催されました

2/18(月)にゴリラ.vim #1が開催され、スタッフ兼発表者として参加してきました。

gorillavim.connpass.com

スタッフになったのはvim-jpのslackでゴリラさんこと@gorilla0513さんがVimの勉強会をやろうとしているのを見かけ、これは前々から思っていた渋谷でVim勉強会をするチャンスなのでは!?と会場提供を申し出たのがきっかけです。

そして当初は発表するネタが思い浮かばなかったのでスタッフに専念するつもりでいたのですが、少し経っても発表者枠が空いており、かつメンバーがVimConf 2018のスピーカーばかりという状態だったため、これは自分も何か話した方が良いんじゃないかと考え、ここ最近使ったgotypego-treeを基にVimと外部コマンドという発表をしてきました。

ちなみに発表・デモは@thincaさんのshowtimequickrunを使いました。

gorillavim.connpass.com

全体の発表内容としては、短い時間ながらも初心者向けから上級者向けまであり、とても充実したものとなっていました。
特に前半の発表は自分にもそういう時期があったんだよなぁ、と非常に懐かしい気持ちになりました。

その後、一通り発表が終わった後は懇親会となり、各々がVimトークでとても盛り上がっていました。

開催前からこのイベントは盛り上がる...という予感はありましたが、予想以上に盛り上がり、スタッフとしても発表者としても参加者としても、嬉しい限りです。

ということで、今回ご参加いただいた皆様、ありがとうございました。
次回も是非よろしくお願いします。
(#2は少し枠が増えています!)

gorillavim.connpass.com

Vimの折畳に対応したtreeコマンド

golang.tokyo #21DevQuizはGoでtreeコマンドを作成するというものでした。

treeコマンドといえば、以前Vimでファイル一覧をツリー表示するためのtree.vimというプラグインを作る時に使いました。

github.com

このプラグインディレクトリにマーカーをつけることでサブディレクトリを階層ごとに折り畳んで表示します。
当時、マーカーを(Go製の)自作コマンドでつけることも検討しましたが、実装が非常に面倒くさそうだったのでVim scriptでtreeコマンドの結果をパースすることにしました。

そんな事情があったため、せっかくなのでDevQuizをやりつつ、追加機能としてVimの折畳に対応した出力形式も実装してしまおう!と思って作ったのがgo-treeです。*1

こちらはコマンドラインフラグに-Vをつけると以下のような出力になります。

$ go-tree -V github.com/daisuzu/tree.vim
github.com/daisuzu/tree.vim/../
github.com/daisuzu/tree.vim/./
github.com/daisuzu/tree.vim/README.md
github.com/daisuzu/tree.vim/autoload/{{{
  github.com/daisuzu/tree.vim/autoload/tree.vim}}}
github.com/daisuzu/tree.vim/ftplugin/{{{
  github.com/daisuzu/tree.vim/ftplugin/tree.vim}}}
github.com/daisuzu/tree.vim/plugin/{{{
  github.com/daisuzu/tree.vim/plugin/tree.vim}}}
github.com/daisuzu/tree.vim/syntax/{{{
  github.com/daisuzu/tree.vim/syntax/tree.vim}}}

コマンド自体はVimから実行しても良いし、

:r! go-tree -V [DIR]

シェルで実行した結果をVimに流し込んでもOKです。

# -cでfoldmethod(fdm)をmarkerにする
go-tree -V [DIR] | vim - -R -c 'setl fdm=marker'

# モードラインでfoldmethod(fdm)をmarkerにする
echo -e "vim: fdm=marker\n$(go-tree -V [DIR])" | vim - -R

そうするとgithub.com/daisuzu/tree.vimVimで以下のように表示されます。

github.com/daisuzu/tree.vim/../
github.com/daisuzu/tree.vim/./
github.com/daisuzu/tree.vim/README.md
+--  2 lines: github.com/daisuzu/tree.vim/autoload/-----------------------------
+--  2 lines: github.com/daisuzu/tree.vim/ftplugin/-----------------------------
+--  2 lines: github.com/daisuzu/tree.vim/plugin/-------------------------------
+--  2 lines: github.com/daisuzu/tree.vim/syntax/-------------------------------
~
~
~
~

さらに、コマンド単体で動作するのでjob機能を使った非同期実行も簡単です。

:vnew | setl fdm=marker | call job_start('go-tree -V', {'out_io': 'buffer', 'out_buf': bufnr('%')})

f:id:daisuzu:20190128202818g:plain

このように、github.com配下に大量のファイルがあるような場合でもVimをブロックすることなく操作を継続することができます。

現在はvimrcにAtreeコマンドを定義してgo-treeを常用しています。
filetypeをtree.vimのものにすることで、そのまま表示するよりも少し見栄えが良くなっています。

command! -nargs=? -complete=dir -count -bang -bar ATree
      \ call s:async_tree(<q-args>, <count>, <bang>0, <q-mods>)

function! s:async_tree(dir, depth, bang, mods) abort
  let cmd = 'go-tree -V ' . g:tree_options
  if a:bang
    " コマンドに!をつけたら隠しファイルも表示する
    let cmd .= ' -a'
  endif
  if a:depth > 0
    let cmd .= ' -L ' . a:depth
  endif
  " cd後もgfできるようにディレクトリ名を絶対パスにする
  let cmd .= ' ' . fnamemodify(a:dir != '' ? a:dir : '.', ':p:h')

  execute a:mods . ' new'
  setfiletype tree

  let bufnr = bufnr('%')
  call job_start(cmd, {
        \   'out_io': 'buffer',
        \   'out_buf': bufnr,
        \   'exit_cb': {channel, msg -> s:goto_first(bufnr)},
        \ })
endfunction

" オリジナル版はカーソル位置が先頭行になるので同じ挙動にする
function! s:goto_first(bufnr)
  if bufnr('%') != a:bufnr
    " バッファを移動していたら何もしない
    return
  endif
  if line('.') != line('$')
    " 行を移動していたら何もしない
    return
  endif
  normal gg
endfunction

予想通り実装は面倒でしたが、Vimをより快適に使えるようになりました!

*1:人が見ることはあまり考えてませんでした...

Vimのexecute()と組み合わせて便利なコマンド

この記事はVim Advent Calendar 2018の13日目の記事です。

先月に開催されたVimConf 2018で、Migrating plugins to standard featuresというタイトルで発表してコマンドの結果をバッファに表示する方法を紹介しました。
その中で解説しきれなかったテクニックとして、以下のようなコマンドを定義しておくとL <コマンド>*1のようにして簡単に任意のコマンドの結果をバッファに表示することが出来るようになります。

command! -bar ToScratch
      \ setlocal buftype=nofile bufhidden=hide noswapfile

command! -nargs=1 -complete=command L
      \ <mods> new | ToScratch |
      \ call setline(1, split(execute(<q-args>), '\n'))

当日はプラグイン一覧やMRUを表示する用途として、

  • :scriptnames
  • v:oldfiles(:oldfilesでも良い)

を紹介しましたが、簡単に使えるなら他にも便利なコマンドがあるんじゃないかと思って調べてみました。
なお、対象は:exusageから

  • 引数不要
  • ファイルタイプやカーソル位置、バッファの状態などに依存しない
  • 結果が複数行になる

コマンドのみとしています。(収拾ががつかなくなりそうなので...)

定義系

各種定義を一覧表示するのは何かと便利そうです。
年末にvimrcを掃除する際などにも大活躍するんじゃないでしょうか?

  • :augroup ... 自動コマンドのグループ
  • :autocmd ... 自動コマンド
  • :abbreviate ... 短縮入力
    • :noreabbrevや、先頭にc, iを付けても良い
  • :command ... コマンド
  • :function ... 関数
  • :highlight ... ハイライト
    • :runtime syntax/hitest.vimだと実際にハイライトされる
  • :let ... 変数
  • :map ... マップ
    • :noremapや、先頭にc, i, l, n, o, s, t, v, xを付けても良い
  • :menu ... メニュー
    • :noremenuや、先頭にa, c, i, n, o, s, tl, v, xを付けても良い

開いているもの

gfで開く用途には向きませんが、どちらも大量に開いている時はバッファに表示されると便利そうです。

  • :buffers ... バッファ
    • または:files, :ls
  • :tabs ... タブ

各種操作結果

こちらも大量に表示されるような状況では便利そうです。

  • :registers ... レジスタ
    • または:display
  • :marks ... マーク
  • :messages ... メッセージ
  • :history ... コマンド履歴

Vimの情報

Vim関連の様々な情報はバッファに表示して見たいというケースはあまりないかもしれません。
個人的には代替の手段の方が使い勝手が良いと思っています。

  • :compiler ... コンパイラ用設定
  • :digraphs ... ダイグラフ
    • :h digraph-tableの方が見やすいかもしれない
  • :set ... 既定値と異なるオプション
    • :setglobal:setlocalもある
    • :optionsでも良いかもしれない
  • :version ... バージョン番号やその他の情報

デバッグ向け

Vim script開発者向け。

QuickFix

切り替える際に表示するだけならわざわざバッファに表示する必要はないかもしれません。

  • :chistory ... grepやmakeなど、quickfixリストの履歴
    • locationリストの場合は:lhistory
  • :clist ... quickfixリストの内容
    • locationリストの場合は:llist

移動系

こちらも一時的に表示するだけならバッファに表示する必要はなさそうです。
※実際に移動できるマッピングなどがあれば...*2

  • :jumps ... ジャンプリスト
  • :tags ... タグスタック
  • :tselect ... タグ

その他

ということで最後はこちら。

  • :smile ... 「ついに見つけたんだね、セイバー!」
                            oooo$$$$$$$$$$$$oooo
                        oo$$$$$$$$$$$$$$$$$$$$$$$$o
                     oo$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o         o$   $$ o$
     o $ oo        o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o       $$ $$ $$o$
  oo $ $ "$      o$$$$$$$$$    $$$$$$$$$$$$$    $$$$$$$$$o       $$$o$$o$
  "$$$$$$o$     o$$$$$$$$$      $$$$$$$$$$$      $$$$$$$$$$o    $$$$$$$$
    $$$$$$$    $$$$$$$$$$$      $$$$$$$$$$$      $$$$$$$$$$$$$$$$$$$$$$$
    $$$$$$$$$$$$$$$$$$$$$$$    $$$$$$$$$$$$$    $$$$$$$$$$$$$$  """$$$
     "$$$""""$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$     "$$$
      $$$   o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$     "$$$o
     o$$"   $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$       $$$o
     $$$    $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" "$$$$$$ooooo$$$$o
    o$$$oooo$$$$$  $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$   o$$$$$$$$$$$$$$$$$
    $$$$$$$$"$$$$   $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$     $$$$""""""""
   """"       $$$$    "$$$$$$$$$$$$$$$$$$$$$$$$$$$$"      o$$$
              "$$$o     """$$$$$$$$$$$$$$$$$$"$$"         $$$
                $$$o          "$$""$$$$$$""""           o$$$
                 $$$$o                                o$$$"
                  "$$$$o      o$$$$$$o"$$$$o        o$$$$
                    "$$$$$oo     ""$$$$o$$$$$o   o$$$$""
                       ""$$$$$oooo  "$$$o$$$$$$$$$"""
                          ""$$$$$$$oo $$$$$$$$$$
                                  """"$$$$$$$$$$$
                                      $$$$$$$$$$$$
                                       $$$$$$$$$$"
                                        "$$$""""

*1:Listの略

*2:i.e. Unite

golang.tokyo 静的解析Dayでgolang.org/x/tools/go/analysisを使ってみた

golang.org/x/tools/go/analysis 面白そうだなー。 けど触る時間がなかなか取れないなー。 と思っていたらちょうどタイムリーなツイートがあり、

数時間後にこちらの開催が決まりました。

golangtokyo.connpass.com

ということでモクモクとgschonnef.co/go/tools/lintからgolang.org/x/tools/go/analysisへの置き換えをやってきました。

ちなみにgscgolang.tokyo #14のLT*1で紹介した自作のlinterです。

変更点をざっくりまとめると、

  • honnef.co/go/tools/lintではCheckerというinterfaceに実装をするが、golang.org/x/tools/go/analysisではAnalyzerというstructになる
  • honnef.co/go/tools/lintではJobを受け取ってast.Inspectで処理をしていくが、golang.org/x/tools/go/analysisではPassを受け取ってInspector.Preorderで処理をしていく
    • AnalyzerRequiresフィールドに必要な前処理を指定する
    • 次の処理に結果を渡すような作りにすることも可能
  • コマンド本体はlintutil. ProcessFlagSetからmultichecker.Mainになる
    • ただしmainでflagを扱えなくなってしまう!
      • ツール全体でExitCodeを0にするといった制御が不可に...
      • なのでPassReportフィールド(関数)を書き換えることで 無理矢理 実現した
  • テストはhonnef.co/go/tools/lint/testutilgolang.org/x/tools/go/analysis/analysistestになる
    • 使い方はだいたい同じ

といったところです。

統一的なインターフェースになって再利用しやすくなるのもメリットですが、処理速度も向上していて、 手元で3秒くらいかかっていたのが一瞬で終わるようになりました。

今日はまだgolang.org/x/tools/go/analysisを使いこなすところまではいけませんでしたが、とても有意義な時間を過ごせました。

便利なAnalyzerが増えるとみんな幸せになれるのでもっともっと使う人が増えていくと良いですね!