詩と創作・思索のひろば

ドキドキギュンギュンダイアリーです!!!

Fork me on GitHub

Electron で AdSense のレポートをメニューバーから確認できるアプリを書いた

ぼくはその日を善く生きたかどうかは AdSense の収益で決まると思っているので、以前にも AdSense のレポートを CLI で確認できるツール を書き、この記事にもあるように tmux のステータスバーに収益を表示していつでも確認できるようにカスタマイズしている。これは具合がよくてずっと使っていたのだけど、最近 tmux.conf をいじっていたらだんだん表示領域が手狭に感じられてきて、ターミナルから追い出したくなってしまった。くわえて、人にターミナルを見せると「ほうほう motemen さんの今月の収益は 9 円ですか……」といらぬ情報を人に与えてしまう問題もあり、ここはひとつ最近はやりの Electron を使ってメニューバーに表示してしまおうということしたのだ。

使用イメージはこちら。ここ7日分の収益を、日別に集計してメニューに表示している。アイコンの隣の数字はその合計(数字はサンプル)。

f:id:motemen:20151006193354p:plain

インストール方法

バイナリ配布をおこなっていないので、自前でビルドする必要がある。clone してきたリポジトリで

% npm install
% npm run setup-dev
% npm run compile
% npm run app

などとすればとりあえず起動できる。

64 ビット OSX 用の単独のアプリとして生成するには、npm run app のかわりに

% npm run dist

とする。それ以外のプラットフォームについても、package.json を編集すれば生成できるはず。

使用方法

起動するとメニューバー(やタスクバー)にアイコンが表示されるはず。アイコンをクリックして表示されるメニューから「Authorize…」を選択するとウィンドウが開き、また、普段つかっているブラウザでも Google の認証フローが開くはず。このフローの最後に表示されるコードを先ほどのウィンドウに入力すると認証完了。

スクリーンショットにもあるとおり、メニューからは期間、メトリック、ディメンションの3つが設定できる:

  • 期間: 今日、7日間、1ヶ月間
  • ディメンション: 日付、広告ユニット
  • メトリック: 収益、クリック数、ページビュー

これらを切り替えることで、メニューに表示されるレポートを使いやすいようにカスタマイズできる。どうぞご利用ください。

自分は今日の収益を広告ユニットごとに眺めるようにしている。今日も 0 JPY だ。


gulpfile.js を書きだすといつもそっちのほうに熱中してしまってぜんぜん本体が書き進まないので、今回は素の npm でさっさと実装してしまうことにした。Electron を使ったらほんとに一晩でネイティブの GUI アプリが作れてしまった。メニューバーのアイコンも標準パッケージでできた、すごい。高校生のころには無料の Delphi を使ってせっせと Windows アプリを作っていたことを思い出した。いい時代だというほかない。

今回アイコンは iconmonstr のものを使用した。

ISUCON5 予選通過したが若手に負けました #isucon

表題のとおり、ISUCON5 予選日曜の部にチーム「2nd party cookies」として参加し、17609点の総合10位で本選出場となりました(http://isucon.net/archives/45532743.html)。

それは良かったのだけど、実はうれしさも8割引きで、というのも同日に参加し3位で通過した「はむちゃん」、ここに勝つことが第一の目標だったからなのでした。はむちゃんははてな社内の若手エンジニアで組んだチームで、真っ先に ISUCON への参加表明をしていたので、それに触発されるかたちで自分も参加を決めた経緯があります。id:wtatsuru とともに、チーフエンジニア3人のチームでガチンコ勝負しようぜ! って言ってたら @Songmu さんがうらぎ先約のあったらしく、代わりというわけではないが、ISUCON はインフラ見られる人が2人はいるなという目論見の下、インフラもアプリもできる最高の人材 id:ichirin2501 に声をかけてチーム結成となりました。

やったこと

言語には Perl を使用。去年までの経験から、計測・計画せずに手を動かしはじめるのはキケンだと分かっていたので、開始1時間までにもろもろの環境を作ったら手を止めてアプリを見よう、と決めていて、ちゃんとその通りにできたのでえらい(小並感)。Devel::NYTProf を仕込む準備もしていたけれど、どうやら今回はクエリ改善が主だなということで出番はなく、終盤一度だけ使ったけど、収穫も特になし。予想していたよりアプリの規模が大きくて戸惑ったけど、その分やりがいがあって楽しかった。

方針は普通の改善をしていこう、ということであまり既存の実装から大きく変えることはせずに、pt-query-digest の結果とアクセス分布から重い所をひとつひとつ真面目につぶしていった。

役割分担は自分がアプリで、wtatsuru インフラ全般、ichirin2501 クエリ周りとアプリ、あと時々ぼくが適当な策を言うのをシラッとした目で見るというのをやってもらった。インフラはたつる先生に守られてるという安心感があり、いちりんちゃんは両方と話ができるのでつよい。チーム名は「もてめん」+「だぶるたつる」+「いちりんちゃん」で「もてるたつるちゃん」にしたかったけど却下されました。

  • comments に entries.user_id を持たせる
    • バッチを Perl で書いたらやたら時間かかってしまってよくなかった。要復習
  • users をメモリにキャッシュ
  • relations は one だけで引く
  • relations は一度に引く
  • トップのために 1000 件引いているのをキャッシュ
    • ここだけ memcached を使用
    • スコアは上がったが改善としては良くなかった

などなど。手を付けられなかったのが

  • footprints
    • どうしようかなー、と考えあぐねてたら手付かずになってしまった。終盤で初期データを覗いてみたら created_at が同じ日であるものが数件しかなかったので、うまいことクエリを直せそうだったが果たせず
  • UNIX ドメインソケットによる通信
    • 終盤、そろそろやるか、といってアプリと nginx、MySQL 間を UNIX ソケットに変更したがスコアが微減したので戻してしまった。ここはよく分かってないので復習

感想

予選から解きごたえのある問題で楽しかったし、ベンチマークツールが安定していて待ち時間もほぼなく、ストレスなく問題に取りかかれました。終盤のキューが捌ける速度がすごかった。運営のみなさんありがとうございました! 本選でもお世話になります!

それにしても若手に負けたのがショック。今回はアプリエンジニアの力量で負けたな、という感じなので気分はシオシオのパーです。今日は震えながら出社してます……。

Go のシンプルかつ明快な SQL クエリビルダ go-sqlf

Go でリレーショナルデータベースを利用したアプリケーションを書いているとき、動的に SQL を組み立てたい場合には、いくつかの方法が考えられます:

クエリビルダを使う。世の中にすでにいろいろ存在します。(そのためのライブラリなので)動的に生成するにはもってこいですが、この場合、それぞれのライブラリに合わせた書き方をしなければならないので読み手にもある程度負荷がある点、また、Go は言語として冗長に書くことをよしとする思想を持っているため、DSL 的な API との相性が悪いという欠点があります(map の組み立てが冗長、条件分岐する式が書けないなど)。また、一般にクエリビルダから生成される SQL がコードから想像しづらくなる問題もあります。

文字列連結や fmt.Sprintf を使う。発行される SQL は比較的分かりやすくなりますが、動的に組み立てると SQL プレースホルダとバインドされる値がソースコード中の離れた位置に登場するようになると、対応が分かりづらくなってしまいます。

自分も Go で RDB を使ったアプリケーションを書いていてこの点でかなり困ってしまったので、ひとつの解決案として、慣れ親しんだ fmt のインターフェースに乗っかったクエリビルダを書きました。

https://github.com/motemen/go-sqlf

使い方

sqlf.Printf(format string, values ...interface{}) sqlf.SQL がほぼ唯一の API です。返り値である sqlf.SQL が、発行したい(プレースホルダつきの)クエリと束縛する値の組を表します。さらにこの、BuildSQL() (string, []interface{}) メソッドで得られる queryargsdatabase/sql.DB.Query() などの API に渡すことで、望むクエリを実行できます。

query, args := sqlf.Printf(
    "SELECT %s FROM %s WHERE col1 = %_ AND col2 IN (%_)",
    "id",                   // SELECT %s
    "table",                // FROM %s
    "x",                    // col1 = %_
    []interface{}{1, 2, 3}, // col2 IN (%_)
).BuildSQL()

fmt.Println(query) // SELECT id FROM table WHERE col1 = ? AND col2 IN (?,?,?)
fmt.Println(args)  // [x 1 2 3]

見た目のとおり fmt のそのままのインターフェースですが、go-sqlf では特別に %_ というプレースホルダが定義されているのがポイントです。他と違って、これに対応する値はそのまま文字列には埋め込まれず、代わりに SQL のプレースホルダ(?)となります。値自身は BuildSQL した際の args のほうに出現します(上の例の "x")。

また特に値として []interface{} などスライスが与えられた場合、その個数に応じたプレースホルダが文字列に埋め込まれます(上の例の []interface{1,2,3})。WHERE ... IN (...) なクエリの生成に便利です。

BuildSQL() の返り値はそのまま database/sql の各種 API に渡すことを想定していて、これで意図したクエリを安全に実行できます。ショートカットとして、*sql.DB を引数にとって実行する Query といったメソッドを使うこともできます。

部分的な SQL の埋め込み

一般にクエリビルダを使う動機としては、例えば WHERE 節の一部の式を動的に変化させたいこともあります。go-sqlf ではこの場合、sqlf.Printf によって生成された sqlf.SQL を別の Printf の引数に渡すことで実現します。

wherePart := sqlf.Printf("col1 IN (%_)", []interface{}{"x", "y"})

query, args := sqlf.Printf(
    "SELECT id FROM table WHERE %_ AND col2 = %_",
    wherePart,
    "z",
).BuildSQL()

fmt.Println(query) // SELECT id FROM table WHERE col1 IN (?,?) AND col2 = ?
fmt.Println(args)  // [x y z]

wherePart のプレースホルダとその引数が最終的な結果にうまく利用されていることが分かると思います。

実装

fmt パッケージをそのまま利用しています(なので %s 以外にも例えば %d なんかもそのまま使えます)。fmt の API は引数が fmt.Formatter インターフェースを実装していればその実装を尊重して文字列展開するので、sqlf.Printf に渡された引数を適当な実装でくるんでやれば上記のような挙動も意外と簡単に実現できますし、元来の fmt では使われていない %_ も利用できるようです。

とはいえ、このへんはちゃんと実装や仕様を把握していないので、今後も有効かどうかは分からない。また、フォーマットが文字列の先頭から直列に処理されることに依存していますが、まあこの挙動は将来的にも変化ないんじゃないかな……。


以上、思いつきで書いたやつなので瑕疵もあるかと思いますが(MySQL のことしか考えられてないし)、けっこう便利そうなのでどうぞご利用ください。

はてなで一緒に働きませんか?