きょこみのーと

技術に関係ないほうのブログ

gormのCallbackをつかってexplain結果をログ出力する

はじめに

explainは基本的に実装時に自分で叩いてると思いますが、dev環境とかそこそこデータとか利用頻度が高い環境で雑に垂れ流したいなーと思ってgormのDebug実装を眺めていたらCallbackという仕組みがあったので、それを使ってみました。

作ったもの

github.com

使い方

gormのexampleをにちょこっと追加してみました。

package main

import (
    "fmt"

    _ "github.com/go-sql-driver/mysql" // mysqlを使う
    "github.com/jinzhu/gorm"
    explain "github.com/kyokomi/gorm-explain"
)

type options struct {
    user     string
    password string
    host     string
    port     int
    dbName   string
    location string
}

func buildDataSourceName(opts options) string {
    const format = "%s:%s@tcp(%s:%d)/%s?parseTime=True&loc=%s"
    return fmt.Sprintf(format, opts.user, opts.password, opts.host, opts.port, opts.dbName, opts.location)
}

// Product product table struct
type Product struct {
    gorm.Model
    Code  string
    Price uint
}

func main() {
    opts := options{
        user:     "test-user",
        password: "test-user",
        host:     "127.0.0.1",
        port:     3306,
        dbName:   "test_db",
        location: "UTC",
    }
    db, err := gorm.Open("mysql", buildDataSourceName(opts))
    if err != nil {
        panic("failed to connect database")
    }
    defer db.Close()
    db = db.Debug() // query trace
    db.Callback().Query().Register("explain", explain.Callback) // add explain callback

    // Migrate the schema
    db.AutoMigrate(&Product{})

    // Create
    db.Create(&Product{Code: "L1212", Price: 1000})

    // Read
    var product Product
    db.First(&product, 1)                   // find product with id 1
    db.First(&product, "code = ?", "L1212") // find product with code l1212

    // Update - update product's price to 2000
    db.Model(&product).Update("Price", 2000)

    // Delete - delete product
    db.Delete(&product)
}

実行した結果

(あと、gormのDebug機能をつかうとqueryがログに吐かれるようになるのでこれを合わせて使うと便利です)

(/Users/kyokomi/workspace/go/src/github.com/kyokomi/gorm-explain/example/main.go:53)
[2017-05-05 18:16:38]  [1.08ms]  INSERT INTO `products` (`created_at`,`updated_at`,`deleted_at`,`code`,`price`) VALUES ('2017-05-05 18:16:38','2017-05-05 18:16:38',NULL,'L1212','1000')

(/Users/kyokomi/workspace/go/src/github.com/kyokomi/gorm-explain/example/main.go:57)
[2017-05-05 18:16:38]  [0.95ms]  SELECT * FROM `products`  WHERE `products`.`deleted_at` IS NULL AND ((`products`.`id` = '1')) ORDER BY `products`.`id` ASC LIMIT 1
+-------+----------------+----------+---------------+---------+------------------+--------+------------+--------+---------+-------------+--------------------------------------------------------+
|    id |    select_type |    table |    partitions |    type |    possible_keys |    key |    key_len |    ref |    rows |    filtered |                                                  Extra |
+=======+================+==========+===============+=========+==================+========+============+========+=========+=============+========================================================+
|     1 |         SIMPLE |          |               |         |                  |        |            |        |         |             |    Impossible WHERE noticed after reading const tables |
+-------+----------------+----------+---------------+---------+------------------+--------+------------+--------+---------+-------------+--------------------------------------------------------+



  1 Fix example to add query trace

(/Users/kyokomi/workspace/go/src/github.com/kyokomi/gorm-explain/example/main.go:58)
[2017-05-05 18:16:38]  [1.11ms]  SELECT * FROM `products`  WHERE `products`.`deleted_at` IS NULL AND ((code = 'L1212')) ORDER BY `products`.`id` ASC LIMIT 1
+-------+----------------+-------------+---------------+---------+----------------------------+----------------------------+------------+----------+---------+-------------+---------------------------------------+
|    id |    select_type |       table |    partitions |    type |              possible_keys |                        key |    key_len |      ref |    rows |    filtered |                                 Extra |
+=======+================+=============+===============+=========+============================+============================+============+==========+=========+=============+=======================================+
|     1 |         SIMPLE |    products |               |     ref |    idx_products_deleted_at |    idx_products_deleted_at |          5 |    const |       2 |         100 |    Using index condition; Using where |
+-------+----------------+-------------+---------------+---------+----------------------------+----------------------------+------------+----------+---------+-------------+---------------------------------------+


(/Users/kyokomi/workspace/go/src/github.com/kyokomi/gorm-explain/example/main.go:61)
[2017-05-05 18:16:38]  [0.86ms]  UPDATE `products` SET `price` = '2000', `updated_at` = '2017-05-05 18:16:38'  WHERE `products`.`deleted_at` IS NULL AND `products`.`id` = '7'

(/Users/kyokomi/workspace/go/src/github.com/kyokomi/gorm-explain/example/main.go:64)
[2017-05-05 18:16:38]  [0.88ms]  UPDATE `products` SET `deleted_at`='2017-05-05 18:16:38'  WHERE `products`.`deleted_at` IS NULL AND `products`.`id` = '7'

注意点

さっと書いたので、fmtで出力してます。ちゃんと自前のロガーとかで出力したい場合は、このコード参考に自分で書いてください 🙏🏻 (もしくはPullRequestまってます 😇 )

おわり

explainの結果に filesortが含まれていたら〜みたいなHandlerを設定できるような感じに改修とかすると便利そうだな〜とか思ってます。

esaのAlfred WorkflowをGoで作りました

作ったもの

f:id:kyokomi:20191123150311p:plain

github.com

とりあえずtokenとチーム名を登録して記事を検索できるだけです。

注意点

ちなみに、esaAPIは利用制限があるので検索しすぎにご注意ください

https://docs.esa.io/posts/102#2-3-0

現時点では、ユーザ毎に15分間に75リクエストまで受け付けます。 とのこと

参考にしたもの

使ったライブラリ

github.com

filterで一覧表示するのがこんなに楽に実装できて感謝

github.com

esaのClientすでに作っている方がいてesaAPI仕様はあまり見ないでもサクッと作れました! 感謝

github.com

最初、直接 Open in Finder したフォルダ内にバイナリを放り込んで開発してたんですが、 普通に xxx.alfredworkflow をダブルクリックしてInstallして動作確認したかったので、 alfred-workflow-packager を使うようにしました。(まあ、色々replaceしてzipに固めるだけっぽいですが)

ただ、これはMacでAlfred3がインストールされている前提のコードがあるので、ちょっと書き直してCIで動くようにしようかなと思ってます。

GoでAlfredWorkflowを作る手順(雑メモ)

AlfredWorkflowつくるぞ〜と思ってから最初どうすればいいかわからない具合いが結構すごかったです。

とりあえず自分は、似てるworkflowをinstallして Alfred上で Open in Finder して中身を漁ってつくりを把握してつくってみました。 思ったより単純な構造で、基本的には以下の流れでつくればいいのかなという理解。

  • 普通にCLIツールをGoで作る
    • Script filter での出力は go-alfred 等を使ってxmlをPrintする
    • Run Script して Post Notification に表示する出力は普通に fmt.PrintでOK
  • Alfred上でkeywordsとかをとりあえず作成して ./<バイナリ名> search "{query}" みたいな感じでCLIツールの呼び出しする
  • Open URLとか適当なActionを紐付けて、とりあえず動くところまでいく
  • Open in Finder して icon.pnginfo.plist だけコピーしてGoのProjectにコピーする
  • あとは alfred-workflow-packager を使って xxx.alfredworkflowを作成して、ダブルクリックしてインストールして動作確認していく感じ

まとめ

Alfred便利なのでどんどん作っていくぞ!!!!!!

昔作ったgaego-initをglide対応した

github.com

gaego-initとは以下のように、GAE/Goの開発環境をコマンド一発でgenerateするやつです。

$ gaego-init -app hoge-app
$ tree hoge-app/
hoge-app/
├── Makefile
├── README.md
├── appengine
│   ├── app.yaml
│   └── init.go
└── circle.yml

1 directory, 5 files

以前作ったときは、vendoringの対応をどうするかべきかわからず、結局shellでgoapp getするという荒業をしていたのでそれを修正しました。

# Makefile                                                                                                                                                                                            22:26:56
APP_ROOT=appengine
VENDOR_GOPATH=$(shell pwd)/$(APP_ROOT)/gopath/vendor
APP_GOPATH=$(shell pwd)/$(APP_ROOT)/gopath
RUN_GOPATH=$(APP_GOPATH):$(VENDOR_GOPATH)

install:
    glide init
    glide install
    mkdir -p $(APP_ROOT)/gopath/vendor
    ln -s $(shell pwd)/vendor/ $(VENDOR_GOPATH)/src

run:
    GOPATH=$(RUN_GOPATH) goapp serve $(APP_ROOT)

clean-run:
    GOPATH=$(RUN_GOPATH) goapp serve --clear_datastore $(APP_ROOT)

deploy:
    GOPATH=$(RUN_GOPATH) goapp deploy $(APP_ROOT)

こちらの記事を参考にglideでのvendoringを行えるようにしました。

motemen.hatenablog.com

感謝 🙏 🙏 🙏 🙏 🙏

Arukasを使って無料でGo製のslackbotを運用する

はじめに

SlackbotをHerokuで一日中動かすと課金が発生するので色々ハックが必要となったりするので、なんとかしたくArukasに出会いました。

今回は、自分が作ったgo製のslackbotを元につくったbotgithubにpushしてWercker経由でdockerImageを作ってArukasへのDeployを行う方法を紹介します。

github.com

今回のコードとか設定をexampleとして公開してますので、よろしければ参考にしていただければと。

Arukasとは?

いまのところ無料でDockerをホスティングできるサービスです。

arukas.io

一応CLIツールも公開されていて、結構便利です。

github.com

※注意点としては、CMDで起動したプロセス監視とかは無いので自分でとかgo-server-starterとかを入れてプロセス死んだときに再起動する仕組みを入れる必要があるところです。

流れ

  • 以前作ったgo製のslackbotのdockerImageをWerckerで作成する kyokomi.hatenablog.com kyokomi.hatenablog.com
  • Arukasのコンソールでアプリケーションを作成し、dockerImageを指定する
  • WerckerのworkflowでArukasのrestartを設定する

slackbotのコード自体はDockerとか意識しない形になっているので、wercker.ymlとかを見ていただければと。

example-go-slackbot/wercker.yml at master · kyokomi/example-go-slackbot · GitHub

Werckerの設定

f:id:kyokomi:20170101221031p:plain

README.md に書いてある通り6つの環境変数の登録が必要です。

ポイントはdocker-pushのworkflowで github.com/lestrrat/go-server-starter/cmd/start_serverをinstallしてます。 まあ別にバイナリをdonwloadでもいいんですが面倒だったので...

docker-push:
  box: golang
  steps:
    - glide-install
    - setup-go-workspace
    - script:
        name: install deamontools
        code: |
          go get github.com/lestrrat/go-server-starter/cmd/start_server
    - script:
        name: install application
        code: |
          go install
    - internal/docker-push:
        username:   $DOCKER_HUB_USERNAME
        password:   $DOCKER_HUB_PASSWORD
        tag:        latest
        repository: $DOCKER_HUB_REPOSITORY
        registry:   https://registry.hub.docker.com

arukas-deployのworkflowでは、 arukasのバイナリをdownloadして適当にPATHを通してstop -> startしてます。 sleep 10 を挟んでいるのは、stopする前にstartするとエラーになるので仕方なくです。

arukas-deploy:
  box: golang
  steps:
    - script:
        name: install tools
        code: |
          sudo apt-get update
          sudo apt-get -f install
          sudo apt-get install -y wget unzip curl tree
    - script:
        name: arukas install
        code: |
          mkdir -p $HOME/lib
          export PATH=$PATH:$HOME/lib
          cd $HOME/lib
          wget https://github.com/arukasio/cli/releases/download/v0.1.2/arukas_v0.1.2_linux_amd64.zip
          unzip arukas_v0.1.2_linux_amd64.zip
          rm arukas_v0.1.2_linux_amd64.zip
    - script:
        name: arukas restart
        code: |
          arukas stop ${ARUKAS_CONTAINER_ID}
          sleep 10
          arukas start ${ARUKAS_CONTAINER_ID}

Arukasの設定

f:id:kyokomi:20170102005223p:plain

おまけ

ちゃんとプロセス死んで再起動するかの動作確認ですが、昔ネタでいれたcommandを実行させるプラグインが役に立ちました。(危険)

f:id:kyokomi:20170102010051p:plain

何度でも蘇る。

2016年振り返り

主にTwitterの全ツイート履歴を見て振り返る。

振り返り

半分くらいは業務コードと思われる.

f:id:kyokomi:20170101122503p:plain

開発合宿とかPC持っていく旅行

結構同じメンバー何回も開発合宿行ってて、会社が別になっても気軽に集まれる関係なのがいいなと思いました。 来年も行きましょう!!!

各月のサマリー

2〜7月: ひたすらAndroidして退職

  • Droidkaigiに行った
  • 業務でAndroidエンジニアが居なくて困っていたので、Androidエンジニアにコンバートした
  • ひたすらAndroid書いてた。とにかく仕様バグとかクラッシュを削りながらな日々
  • twitter.com
  • 去っていた人のコードを当時いた人の気持ちになってリーディングするサイコメトリーをマスターする
  • twitter.com
  • twitter.com
  • Androidそこそこできるようになったので、Androidエンジニアの採用を頑張っていた
  • 制約と誓約を意識して、Goを書かないで全ステータスをAndroidに振っていた
  • 7月一杯でGunosyを退職

8月〜9月

10月〜12月

その他

麻雀

2016年は10回くらい打った気がする。役満は1回。 メンツは結構バラバラだったけど、トータル勝ち越した気がする。

twitter.com

関係者各位また定期的にやりましょう!!!

個人開発

  • GAE/Goでちょっとしたキュレーションシステム
  • Androidアプリ2つ(キュレーションのクライアントと、Twitterの画像をひたすら表示するやつ)
  • 放置ゲーのAPIサーバーをGAE/Go -> ECSに移行
  • 放置ゲーのクライアントをUnityでプロトタイプ作成

相変わらずエターなってるけど、作ったものを自分では使っているのでそこそこ満足している。

まとめ

  • 2016年は発表とかブログとかアウトプット少なめで本を読んだり技術検証したりでインプット多めだった気がする
  • 温泉行って開発合宿すると疲労を回復しながら、開発が捗るので2017年もやっていきたい
  • 2017年はリリースもあるので、Tipsとか開発フローとかについてとか発表したり、ブログでの情報発信とかもしっかりやっていくぞ

werckerでprivate repositoryを含むglide installする方法

手順

  1. glide.ymlでsshのURLを指定する
  2. wercker上でApplication Environmentか Organization settings Environmentで + Generate SSH Keys してssh keyを環境変数に登録する
  3. 「2.」で登録したssh keyをgithub上の Settingsの SSH keys で登録する
  4. wercker.ymlに add-ssh-keyadd-to-known_hosts を記載する

glide.ymlとwercker.yml

gist.github.com

fingerprintはこちらを参考に入力しましょう。

What are GitHub's SSH key fingerprints? - User Documentation

wercker Generate SSH Keys

Application Environment

f:id:kyokomi:20161218150924p:plain

Organization settings Environment

こっちで指定すると全部のアプリケーションで使いまわせます。

f:id:kyokomi:20161218150826p:plain

Webラジオのm3u8をdownloadしてffmpegでmp3に変換するツールをGoで書いた

2016/12/15 23:06 追記:

ちなみにffmpegでm3u8 -> mp3変換はできますが、実行時間が20分以上かかって待ってられなかったのでBulkDownloadが含まれてます。

あとm3u8のURLが推測難しく、いちいちブラウザ開いてplaylistをダウンロードして中身のmedia playlistのm3u8のURLを確認して〜がだるかったのでScrapingしてます。

Webラジオのm3u8をScrapingしてtsファイルをBulkDownloadしてffmpegでmp3に変換するツールをGoで書いた が正確なタイトルですかね。


今期は、ガーリッシュナンバーが結構お気に入りです。

www.tbs.co.jp

(ちーさまのダメ可愛さがなかなか癖になります)

f:id:kyokomi:20161215205231p:plain

(そして目玉焼きの服というセンス...圧倒的か...)

なんでこんな話から始まったかと言うと、面白いなーと思った作品のアニメのWebラジオとか結構聴くんですが、なんか専用のアプリがあって使いにくかったり、Webブラウザでしか再生できなかったりと中々不便なので結局ダウンロードすることにしてツールを作りました。

(たとえば、途中まで再生してブラウザ閉じたときに、続きから聴けない...とか)

sp.animatetimes.com


ここから本編

今回作ったツール

github.com

github.com

github.com

1つのツールにしても良かったんですが、用途考えると別々のほうが使いやすいかなと思って3つに分けました。

はじめに

今回必要なダウンロードの流れは以下になります。

流れ

  1. 指定したURLのWebページのCrawl
  2. CrawlしたHTMLから対象のURLをScrapingして抽出
  3. m3u8のPlaylistのURLを元にtsファイルをBulkDownload
  4. tsファイルをffmpegでつなぎ合わせてaacを作成
  5. aacffmpegを使ってmp3に変換

3〜5のあたり github.com/yyoshiki41/radigo こちらを参考にさせていただきました。

前提条件・事前準備

  • Macでしか試してません
  • 対象のWebページのURLを用意してださい
  • 対象のxpathを適当に調べておく(ChromeDeveloperToolとかで)
  • ffmpegをインストールしておく
  • Go環境用意しておく

インストールの仕方・それぞれの役割説明

インストール

go get github.com/kyokomi/xcrawl
go get github.com/kyokomi/m3u8go
go get github.com/kyokomi/ffmpego

役割

使い方

xcrawlのオプションで使えるconfig

スマートフォンサイトをCrawlしたいとかのときに使います。

headers:
  User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Mobile Safari/537.36

実行例

xargsでつなぐと一発で実行できて便利です。

$ xcrawl -c <configファイルのパス> \
         -i '<ラジオ再生ボタンがあるWebページのURL>' \
         -x '<master playlistのURLが抽出できるxpath>' \
         | xargs m3u8go -i \
         | xargs ffmpego -o <出力するファイル名> -i

デモ

冒頭にもリンク貼りましたが最近ハマっている、ガーリッシュナンバーの「クズらじ」で試してみます。

www.animatetimes.com

$ xcrawl -c config.yaml \
            -i 'http://sp.animatetimes.com/radio/details.php?id=gn&a=10&m=a' \ 
            -x '//*[@id="main-contents"]/ul[1]/li[3]/div/div[1]/div/a[1]/@href' \
            | xargs m3u8go -i \
            | xargs ffmpego -o kuzu3.mp3 -i

f:id:kyokomi:20161215203950p:plain

これでひとまず満足... ^q^

かなり雑に実装したので、もし自分以外に使う方がいましたら、バグとかあればお気軽に〜(対応するかはわからないが...)