断捨離

1Password に登録しているログインの数が 70 を超えていて、さすがに多すぎと思ったのでログインの断捨離を決意した。

使ってないサービスやサブアカウントを削除すれば 3 分の 1 は減らせそう。欲を言えば 3 分の 2 減らしたいところだが、まだ必要なログインは思っていたよりも多いみたいだ。

ログインの削除には時間がかかっている。サービスの退会フォームを探す時間が大半だ。マイページから退会できるサービスが少ないこと少ないこと。退会して欲しくないのはわかるけど。

FAQ にリンクがあるのはまだいい方。退会フォームが存在しないサービスはどうしたもんかね。海外のサービスに多い。万が一サービスからパスワードが漏洩してしまったときの予防として、ツールで生成したパスワードに変更するぐらいしかないか。

だいぶ昔にも書いたな。ホント困る。

tnakamura.hatenablog.com

33回目の誕生日

また一つ歳をとってしまった。 33 歳。 プログラマーの定年まであと 2 歳か。 職場を見回すと、自分より年上の方々が皆バリバリコード書いているので、 今の職場にプログラマーの定年は無さそうだが。

さて今年は「毎日コードを書く」ことを目標にしていたけど、 ほとんど実践できていない。 2016年の半分が経過したというのに、この体たらく。

ただ、最近はペースがつかめてきたのか、 家事が終わって寝るまでの 30 分から 1 時間、 MacBook を開ける日も出てきた。 そこでマンガを読んでしまっているのがダメダメだな。 コードも少しずつ書いていかねば。

6/27 に .NET Core と ASP.NET Core が RTM になる予定なので、 RTM になったら本気出す。

『ガベージコレクションのアルゴリズムと実装』読んだ

本書はガベージコレクションを扱った、数少ない日本語の本。 アルゴリズム編と実装編に分かれている。

アルゴリズム編では

  • マークスイープGC
  • 参照カウント
  • コピーGC
  • マークコンパクトGC
  • 保守的GC
  • 世代別GC
  • インクリメンタルGC

を豊富な図と擬似コードで丁寧に解説している。

実装編では

GC アルゴリズムを、実際のソースコードを読み解きながら解説していて、

  • Python は参照カウントとマークスイープ GC
  • DalvikVM はマークスイープ GC
  • Rubinius は世代別 GC とマークスイープ GC とコピー GC
  • V8 は世代別 GC でマークスイープ GC とマークコンパクト GC とコピー GC

というように、本書で扱ったアルゴリズムが実際に使われていることを実感できる。

本書で扱っていない言語だと、自分の観測範囲では

  • デスクトップ向けやサーバー向けの.NET Frameworkは、マークコンパクト GC と3世代の世代別 GC*1
  • SwiftはARCなので参照カウント*2
  • Golang は Concurrent Mark & Sweep*3

てな感じで、やはりモダンな言語には GC が実装されている。 その多くが、本書で紹介されたアルゴリズムの改良や組み合わせで、 GC 登場から 50 年たっても基本は変わっていないんだなと感服。

最新技術を追うのも楽しいけど、普遍的な技術を身につけたいと思っていた。 GC はお世話になっているので、取っ掛かりとしては丁度良かった。 興味もあったし。 本書はカジュアルで読みやすかったのでオススメ。

ガベージコレクションのアルゴリズムと実装
中村 成洋, 相川 光, 竹内 郁雄(監修)
達人出版会
発行日: 2013-12-24
対応フォーマット: PDF, EPUB

Edy

Edy 機能付き楽天カードへの切り替えが無料(期間限定?)になっていたので切り替えてみた。

edy.rakuten.co.jp

買い物や公共料金の支払いはほとんどクレジットカードで済ませているので、ポイントがちょこちょこ貯まっている。 貯まったポイントの使い道に困っていたんだけど、ポイントを楽天 Edy に交換できるみたいなので、 コンビニでアイスやマンガ雑誌を買うのに使えるな。

実際 Edy で買い物してみた。電子マネー便利だ。 財布からお札や小銭を出したり、お釣りを入れたりする手間がないのは、結構快適。

動的に Kibana のダッシュボードを作成する

CloudWatch からダウンロードした RDS インスタンスのメトリクスを、 Elasticsearch に突っ込んで、 Kibana のダッシュボードで可視化するところまではできた。

tnakamura.hatenablog.com

RDS インスタンスは今後増えていく予定で、 Kibana のダッシュボードは RDS インスタンスごとに分けたい。 RDS インスタンスが増えるたびに、Kibana をぽちぽち操作してダッシュボードを作るのは手間だな。

なんとか自動化できないものか調べていたら、 Kibana の設定は Elasticsearch の .kibana インデックスに保存されているみたいだった。 ダッシュボードは type = dashboard に、 Visualization は type = visualization に保存されていた。

ということは、ダッシュボードの JSON を .kibana インデックスにスクリプトで登録すれば、 動的にダッシュボードを追加できそうだ。

一から JSON を書くのは面倒なので、すでに作成したダッシュボードや Visualization もろもろの JSON を Kibana からエクスポートし、加工してテンプレートにした。 かなり長いので、一部抜粋。

[
  {
    "_id": "${instance}",
    "_type": "dashboard",
    "_source": {
      "title": "${instance}",
      "hits": 0,
      "description": "",
      "panelsJSON": "[{\"col\":1,\"id\":\"${instance}-CPUUtilization\",\"panelIndex\":1,\"row\":1,\"size_x\":3,\"size_y\":2,\"type\":\"visualization\"},{\"col\":4,\"id\":\"${instance}-DatabaseConnections\",\"panelIndex\":2,\"row\":1,\"size_x\":3,\"size_y\":2,\"type\":\"visualization\"},{\"col\":7,\"id\":\"${instance}-FreeableMemory\",\"panelIndex\":3,\"row\":1,\"size_x\":3,\"size_y\":2,\"type\":\"visualization\"},{\"col\":10,\"id\":\"${instance}-DiskQueueDepth\",\"panelIndex\":4,\"row\":1,\"size_x\":3,\"size_y\":2,\"type\":\"visualization\"},{\"col\":1,\"id\":\"${instance}-FreeStorageSpace\",\"panelIndex\":5,\"row\":3,\"size_x\":3,\"size_y\":2,\"type\":\"visualization\"},{\"col\":4,\"id\":\"${instance}-ReadIOPS\",\"panelIndex\":6,\"row\":3,\"size_x\":3,\"size_y\":2,\"type\":\"visualization\"},{\"col\":7,\"id\":\"${instance}-WriteIOPS\",\"panelIndex\":7,\"row\":3,\"size_x\":3,\"size_y\":2,\"type\":\"visualization\"},{\"id\":\"${instance}-NetworkReceiveThroughput\",\"type\":\"visualization\",\"panelIndex\":8,\"size_x\":3,\"size_y\":2,\"col\":10,\"row\":3},{\"id\":\"${instance}-NetworkTransmitThroughput\",\"type\":\"visualization\",\"panelIndex\":9,\"size_x\":3,\"size_y\":2,\"col\":1,\"row\":5},{\"id\":\"${instance}-ReadLatency\",\"type\":\"visualization\",\"panelIndex\":10,\"size_x\":3,\"size_y\":2,\"col\":4,\"row\":5},{\"id\":\"${instance}-WriteLatency\",\"type\":\"visualization\",\"panelIndex\":11,\"size_x\":3,\"size_y\":2,\"col\":7,\"row\":5}]",
      "optionsJSON": "{\"darkTheme\":true}",
      "uiStateJSON": "{\"P-1\":{\"vis\":{\"legendOpen\":false}},\"P-2\":{\"vis\":{\"legendOpen\":false}},\"P-3\":{\"vis\":{\"legendOpen\":false}},\"P-4\":{\"vis\":{\"legendOpen\":false}},\"P-5\":{\"vis\":{\"legendOpen\":false}},\"P-7\":{\"vis\":{\"legendOpen\":false},\"spy\":{\"mode\":{\"name\":null,\"fill\":false}}},\"P-8\":{\"vis\":{\"legendOpen\":false}},\"P-11\":{\"vis\":{\"legendOpen\":false}},\"P-10\":{\"vis\":{\"legendOpen\":false}},\"P-9\":{\"vis\":{\"legendOpen\":false}},\"P-6\":{\"vis\":{\"legendOpen\":false}}}",
      "version": 1,
      "timeRestore": false,
      "kibanaSavedObjectMeta": {
        "searchSourceJSON": "{\"filter\":[{\"query\":{\"query_string\":{\"analyze_wildcard\":true,\"query\":\"*\"}}}]}"
      }
    }
  },

  // search と visualization は省略
]

このテンプレートからダッシュボードの JSON を出力する Python スクリプトを書いた。

# -*- coding: utf-8 -*-
import os
import sys
import codecs
from string import Template


TEMPLATE_FILE_NAME = "dashboard_template.json"
JSON_FILE_SUFFIX = "_dashboard.json"


class DashboardGenerator(object):

    def _get_work_dir(self):
        dir_path = os.path.abspath(os.path.dirname(__file__))
        return dir_path

    def _get_template_path(self):
        dir_path = self._get_work_dir()
        template_path = os.path.join(dir_path, TEMPLATE_FILE_NAME)
        return template_path

    def _get_template(self):
        template_path = self._get_template_path()
        with open(template_path, "r") as f:
            template_text = f.read()
            template = Template(template_text)
            return template

    def _generate_dashboard_json(self, rds_instance_name):
        template = self._get_template()
        json = template.substitute(instance=rds_instance_name)
        return json

    def _save_dashboard_json(self, rds_instance_name, json):
        file_name = rds_instance_name + JSON_FILE_SUFFIX
        dir_path = self._get_work_dir()
        file_path = os.path.join(dir_path, file_name)
        with codecs.open(file_path, "w", "utf-8") as f:
            f.write(json)

    def generate(self, rds_instance_name):
        json_data = self._generate_dashboard_json(rds_instance_name)
        self._save_dashboard_json(rds_instance_name, json_data)


if __name__ == "__main__":
    generator = DashboardGenerator()
    generator.generate("rds-0001")

出力した JSON を elasticsearch クライアントを使って .kibana インデックスに登録すれば、 ダッシュボードを追加できる。これも Python スクリプトを書いた。

# -*- coding: utf-8 -*-
import os
import sys
import json
from elasticsearch import Elasticsearch


class KibanaSetup(object):
    def __init__(self):
        self.es = Elasticsearch("localhost:9200")

    def _get_work_dir(self):
        dir_path = os.path.abspath(os.path.dirname(__file__))
        return dir_path

    def _get_dashboard_json_path(self, rds_instance_name):
        work_dir = self._get_work_dir()
        file_path = os.path.join(work_dir, rds_instance_name + "_dashboard.json")
        return file_path

    def _load_dashboard_json(self, rds_instance_name):
        file_path = self._get_dashboard_json_path(rds_instance_name)
        with open(file_path) as f:
            json_data = json.load(f)
            return json_data

    def _post_object(self, obj):
        self.es.index(index=".kibana", doc_type=obj["_type"], id=obj["_id"], body=obj["_source"])

    def _create_dashboard(self, rds_instance_name):
        json_data = self._load_dashboard_json(rds_instance_name)
        for obj in json_data:
            self._post_object(obj)

    def setup(self, rds_instance_name):
        self._create_dashboard(rds_instance_name)


if __name__ == "__main__":
    app = KibanaSetup()
    app.setup("rds-0001")

これでやりたいことはだいたい出来た。 ダッシュボードに Visualization を追加するときはテンプレートをいじる必要があって、 そこはまだ手間だけど当面は増やす予定ないからいいことにしよう。

うどん和助 天神店

昼休みに天神3丁目の『うどん和助 天神店』に行ってみた。 店内はすでに満席で、少し並んだ。10分くらい。

最初はごぼう天うどん食べるつもりだったけど、 席に着いたとき既に昼休みの時間が残り半分だったので、 食べ終わるのは間に合わないと判断。 うどんセットを注文した。普通のうどんと、ミニカツ丼のセット。

うどんは麺がうっすら透き通っていて、ツルツルでもちもち。 大地のうどんに似てるなと思ったら、どちらも津田屋流豊前裏打会の加盟店だからみたい。

ミニカツ丼は丁度良いサイズ。 うどん屋やそば屋の丼ぶりは美味い。

豊前裏打会の麺はかなり好みなので、近いうちにまた食べたくなりそうだ。 博多にある大地のうどんよりは、天神にある和助の方が昼休みとかに行きやすいので、 度々お世話になるかもしれない。

関連ランキング:うどん | 天神駅西鉄福岡駅(天神)赤坂駅

OpenCover を使ってコードカバレッジを計測したメモ

アプリのコードがだいぶ増えてきて、それに伴いテストコードも増えてきた。 いい加減そろそろテストを書いていないメソッドの把握が難しくなってきたところだ。 コードカバレッジを計測する頃合かもしれない。

プライベートプロジェクトなので、開発は Visual Studio 2015 Community Edition を使っている。 Visual Studio のコードカバレッジ機能は Enterprise Edition じゃないと使えない。 OSS のツールを探さないといけないな。

.NET 用で使える OSS のコードカバレッジツールだと PartCover が良く検索に引っかかってくるが、 オリジナルは開発が止まっているみたいだった。 オリジナルからフォークした partcover.net4 と、別ツールの OpenCover の 2 つが紹介してあったので、 今回は OpenCover を使うことにする。

OpenCover が出力するコードカバレッジのレポートは XML ファイルなので人が読むのはツライ。 読みやすいレポートに変換してくれる ReportGenerator というツールと組み合わせるのが良い。

あとは下記のような内容のバッチファイルを作成(プロジェクト名は適当)。

REM OpenCoverのインストール先
SET OPENCOVER_HOME=C:\Applications\opencover

REM レポート生成ツールのインストール先
SET REPORTGEN_DIR=C:\Applications\ReportGenerator

REM VS2015のインストール先
SET VS2015_HOME=C:\Program Files\Microsoft Visual Studio 14.0

REM 実行するテストのアセンブリ
SET TARGET_TEST=MyWebApp.Tests.dll

REM 実行するテストのアセンブリの格納先
SET TARGET_DIR=C:\Projects\MyWebApp\MyWebApp.Tests\bin\Debug

REM カバレッジ計測対象の指定
SET FILTERS=+[MyWebApp*]*

REM パスの設定
SET PATH=%PATH%;%OPENCOVER_HOME%;%REPORTGEN_DIR%\bin;

REM OpenCoverの実行
OpenCover.Console -register:user -target:"%VS2015_HOME%\Common7\ide\mstest.exe" -targetargs:"/noisolation /testcontainer:

%TARGET_TEST%"  -targetdir:"%TARGET_DIR%" -filter:"%FILTERS%" -output:result.xml -mergebyhash

REM レポートの生成
ReportGenerator "result.xml" .\html

ダブルクリック一発で、MSTest のテストを実行しつつコードカバレッジを計測し、 HTML のレポートを出力できるようになった。