Pebble Timeを買って最初にやったこと

この記事はピョッコリンアドベントカレンダーのために書かれたものです。

www.adventar.org

はじめに

Pebble Timeを買ったので、最初にやった設定などについてまとめます。

Pebble Timeとは

最近はやりのスマートウォッチというやつです。 Apple Watchのようにスペックは高くないけれど、 電池は長持ちするし、カラー表示もできるし、 何よりお財布にやさしいという特徴を持っています。

レビューはいろんな人が書いているので今更書きません。

smhn.info

【Pebble Time 】買ってよかった!と感じたシーン3つ
tobalog.com

どうして買ったか?

値段そこそこで電池長持ち、かつかわいらしいデザインだったからです。 あとはシンプルなところかな。 普段心置きなく使えるようなものが欲しかったんです。

どこで買えるか?

日本のAmazonでももちろん買えるけど、公式サイトだと割引していたので 公式サイトで買いました。12/20現在でも$150で買えるようでした。 日本への送料は無料でしたよ、確か。

www.pebble.com

買って何をやったか

PebbleのアップデートとWatchfaceの入れ替え

何はなくとも、まずは手持ちのAndroidとつなげてアップデートします。 Play StoreにPebble Time用アプリがあるので、それを入れましょう。 最初に接続したときにアップデートは勝手に始まります、確か。 その他、Pebble Timeアプリの指示に従って必要なものを入れればOKです。 僕のときは何でかわからないけどもAndroid Wearアプリを入れろと指示がありました。

Pebble Time - Google Play の Android アプリ

アップデートが終わったらまずは時計としての体裁を整えるため、 Watchfaceを入れ替えます。壁紙を設定するようなもんですね。

ぐぐったらこんなのが出てきたので、載せておきます。 shikarunochi.matrix.jp

日本語化

AndroidはPebbleに通知画面(例えばメールが来たとかLINEが来たとか)を飛ばして くれるわけですが、Pebbleはデフォルトで日本語表示できないので、 有志のみなさまが作ってくださっている日本語化パックを入れます。

いろいろあるみたいですが、僕はこれを入れました。 Pebble language pack: 日本語言語パック

以上でやったことはおしまいです。

アプリを入れる

最近のアップデートで睡眠時間と歩数をデフォルトでトラックしてくれるようになったので、 他にアプリをいれなくても充分かな、という感じです。

おわりに

まだ使って4日くらいですが、結構楽しく使っています。 通知が飛んで来るだけで結構充分な感。

秒速でRAM Diskを作成する

この記事はピョッコリンアドベントカレンダーのために書かれたものです。

サマリ

LIOでループバックのRAM Diskを作成する。

はじめに

書くネタがだいぶ苦しくなってきたので、特に意味もなくRAM Diskの作り方について書きます。

そもそもRAM Diskとは何か

DRAM(要はメモリ)を一部確保して、HDDやSSDのごとく使えるようにしたものです。 SSDがいくら速いと言っても、メモリにはもちろん勝てないです。 最近のマシンはメモリを山ほど積んでるので、一部切り取って、 何かのキャッシュ置き場として使うといいのでは感があります。

Windowsだとなんかわけわからんドライバやら入れないといけないのでは、というところですが、 Linuxだとまあ文字通りあっという間にできます。

想定環境

必要なもの

LIOがあれば万事OK。 ビルドは結構大変なので以下のコマンドで入れます。

$ sudo apt install targetd --install-recommends

作り方

targetdをインストールすると、targetcliというある種のシェル的なものが使えるようになります。 targetcliを使うとRAM Diskを作ることができます。

targetcliを起動してみます。何かまあプロンプトが出ますね。

$ sudo targetcli
targetcli GIT_VERSION (rtslib GIT_VERSION)
Copyright (c) 2011-2013 by Datera, Inc.
All rights reserved.
/> 

lsをたたいてみましょう。いろいろ出てきましたね。 LIOはフロントエンド(ユーザがI/OするためのI/F。/dev/sdXとかそういうの)と、 backstores以下のバックエンド(ユーザのI/Oを実際に受けるところ)に分かれています。 今回は、フロントエンドにloopback(自分のサーバ内でのみ有効な/dev/sd何とかに見せる)、 バックエンドにbackstores/rd_mcp(要はRAM Disk)を選びます。

/> ls
o- / ................................................................. [...]
  o- backstores ...................................................... [...]
  | o- fileio ........................................... [0 Storage Object]
  | o- iblock ........................................... [0 Storage Object]
  | o- pscsi ............................................ [0 Storage Object]
  | o- rd_dr ............................................ [0 Storage Object]
  | o- rd_mcp ........................................... [0 Storage Object]
  o- ib_srpt ................................................... [0 Targets]
  o- iscsi ..................................................... [0 Targets]
  o- loopback .................................................. [0 Targets]
  o- qla2xxx ................................................... [0 Targets]
  o- tcm_fc .................................................... [0 Targets]
/> 

まずはバックエンド部分を作ります。ってもコマンド1発です。 今回は、ramdiskという名前の1GBのRAM Diskを作りました。

/> /backstores/rd_mcp create name=ramdisk size=1G
Generating a wwn serial.
Created rd_mcp ramdisk ramdisk with size 1G.
/> ls
o- / ................................................................. [...]
  o- backstores ...................................................... [...]
  | o- fileio ........................................... [0 Storage Object]
  | o- iblock ........................................... [0 Storage Object]
  | o- pscsi ............................................ [0 Storage Object]
  | o- rd_dr ............................................ [0 Storage Object]
  | o- rd_mcp ........................................... [1 Storage Object]
  |   o- ramdisk ..................................... [ramdisk deactivated]
  o- ib_srpt ................................................... [0 Targets]
  o- iscsi ..................................................... [0 Targets]
  o- loopback .................................................. [0 Targets]
  o- qla2xxx ................................................... [0 Targets]
  o- tcm_fc .................................................... [0 Targets]
/> 

バックエンドを作っただけではアクセスできないので、ramdiskのステータスは dactivatedになっています。では、フロントエンドを作りましょう。 こちらも同じくコマンド1発・・・とはいかなくて、2発叩き込む必要があります。 タブキーで補完が効くので、あまり面倒ではないです。作業自体はこれで完了です。

/> /loopback create
Created target naa.60014053e8e7b899.
/> /loopback/naa.60014053e8e7b899/luns create /backstores/rd_mcp/ramdisk 
Selected LUN 0.
Successfully created LUN 0.
/> ls
o- / ................................................................. [...]
  o- backstores ...................................................... [...]
  | o- fileio ........................................... [0 Storage Object]
  | o- iblock ........................................... [0 Storage Object]
  | o- pscsi ............................................ [0 Storage Object]
  | o- rd_dr ............................................ [0 Storage Object]
  | o- rd_mcp ........................................... [1 Storage Object]
  |   o- ramdisk ....................................... [ramdisk activated]
  o- ib_srpt ................................................... [0 Targets]
  o- iscsi ..................................................... [0 Targets]
  o- loopback ................................................... [1 Target]
  | o- naa.60014053e8e7b899 ......................... [naa.6001405ec6fd7aef]
  |   o- luns ...................................................... [1 LUN]
  |     o- lun0 ................................. [rd_mcp/ramdisk (ramdisk)]
  o- qla2xxx ................................................... [0 Targets]
  o- tcm_fc .................................................... [0 Targets]
/> 

さて、lsscsiでチェックしてみましょう。僕の環境では/dev/sdcに見えていますね。 lsscsiがない人はaptで入れましょう。

$ lsscsi
()
[10:0:1:0]   disk    LIO-ORG  RAMDISK-MCP      4.0   /dev/sdc 

見えない場合は、以下コマンドでディスクをリスキャンしてみましょう。

#!/bin/bash

for i in /sys/class/scsi_host/*
do
  echo "- - -" > $i/scan
done

さて、試しにinquiryを投げつけてみましょう。 inquiryとは、SCSIというDiskとやりとりするためのプロトコルで定義されているコマンドで、 Diskのベンダやら種類やらを知るためのものです。

何かいろいろ出てきましたが、 Product identificationがRAMDISK-MCPになっていて、確かにRAM Diskぽいということがわかりますね。 これでSSDより圧倒的に速いDiskを手に入れることができました。

$ sudo sg_inq /dev/sdc   
standard INQUIRY:
  PQual=0  Device_type=0  RMB=0  version=0x05  [SPC-3]
  [AERC=0]  [TrmTsk=0]  NormACA=0  HiSUP=0  Resp_data_format=2
  SCCS=1  ACC=0  TPGS=3  3PC=1  Protect=0  [BQue=0]
  EncServ=0  MultiP=0  [MChngr=0]  [ACKREQQ=0]  Addr16=0
  [RelAdr=0]  WBus16=0  Sync=0  Linked=0  [TranDis=0]  CmdQue=1
    length=36 (0x24)   Peripheral device type: disk
 Vendor identification: LIO-ORG 
 Product identification: RAMDISK-MCP     
 Product revision level: 4.0
 Unit serial number: 7539e091-7407-4637-8edc-f8d4a531c92b

使う分にはあまり関係ないですが、このRAM Disk、 SATAではなくてSASディスクとして見えているようですね。

$ sudo sg_inq -i /dev/sdc
VPD INQUIRY: Device Identification page
  Designation descriptor number 1, descriptor length: 20
    designator_type: NAA,  code_set: Binary
    associated with the addressed logical unit
      NAA 6, IEEE Company_id: 0x1405
      Vendor Specific Identifier: 0x7539e0917
      Vendor Specific Identifier Extension: 0x40746378edcf8d4a
      [0x60014057539e091740746378edcf8d4a]
  Designation descriptor number 2, descriptor length: 61
    designator_type: T10 vendor identification,  code_set: ASCII
    associated with the addressed logical unit
      vendor id: LIO-ORG
      vendor specific: RAMDISK-MCP:7539e091-7407-4637-8edc-f8d4a531c92b
  Designation descriptor number 3, descriptor length: 8
    transport: Serial Attached SCSI Protocol (SPL-2)
    designator_type: Relative target port,  code_set: Binary
    associated with the target port
      Relative target port: 0x1
  Designation descriptor number 4, descriptor length: 8
    transport: Serial Attached SCSI Protocol (SPL-2)
    designator_type: Target port group,  code_set: Binary
    associated with the target port
      Target port group: 0x0
  Designation descriptor number 5, descriptor length: 8
    designator_type: Logical unit group,  code_set: Binary
    associated with the addressed logical unit
      Logical unit group: 0x0
  Designation descriptor number 6, descriptor length: 36
    transport: Serial Attached SCSI Protocol (SPL-2)
    designator_type: SCSI name string,  code_set: UTF-8
    associated with the target port
      SCSI name string:

さいごに

LIOを使うとRAM Diskが簡単にできちゃうわけですが、 /dev/shm/以下もRAM Disk扱いになっているので、別に無理に作る必要はない。

Disk I/O性能測定するときに活躍する道具たち

この記事はピョッコリンアドベントカレンダーのために書かれたものです。

サマリ

Disk(HDD/SSD/仮想ディスク等)のI/O性能を測定するのに 活躍するツールについて、簡単に使い方を説明する。

はじめに

Diskに限定せずとも、性能測定と言えば、 おおまかには、スループット(Diskに関して言えば、1秒あたりどれくらいのI/Oを処理できるか?)と レイテンシ(Diskに関して言えば、1個のI/Oを処理するのにどれくらい時間がかかったか?) の2つについて見ることになる。 特にLinuxに関しては、このあたりをよく使うような気がする(主観)。

  • fio:Diskに対してI/Oを発行してくれて、スループットやレイテンシを算出してくれるもの。
  • iostat:Diskごとにスループットなどの情報をリアルタイムで見せてくれるもの
  • blktrace:LinuxがDiskに対してどんなI/Oを出していたかの履歴(=トレース)を見せてくれるもの。

fioはスループットもレイテンシも算出してくれるので、基本的にはfioだけで事足りるのだけど、 本当にfioに指定したとおりにLinuxがI/Oを出しているのか、に関してはちゃんと確認する意義があるので、 iostatとblktraceを追加した。

以後、以下の環境を前提とするので、必要があれば適宜読み替えてほしい。

fio

fioのインストール

fioはメジャーなツールなので、こんな感じでaptでインストールできる。

$ sudo aptitude install fio

ソースからビルドしたい原理主義者は、作者様のgithubから落としてくると幸せになれる。 (ビルド前にlibaioを入れておくことを忘れなければ、さらに幸せになれる) github.com

手順としてはこんな感じ。

$ sudo aptitude install libaio1 libaio-dev
$ git clone git@github.com:axboe/fio.git
$ ./configure
$ make
$ sudo make install

使い方

コマンドとしてはこんな感じ。起動時に、どんなI/Oをかけるのかを記述した 設定ファイルを指定する。

$ fio -f script.fio --output output_filename # 場合によってはsudoが必要

fioはコマンド引数でもろもろ指定ができるんだけど、 後々の再利用を考えると、設定ファイルを逐一書いたほうがよいと思う。

設定ファイルの書き方

最小限ではないが、これだけ書けばI/Oをかけることができる。 細かいところはman fioを見るのがいちばん。 英語で書いてあるけど、割と簡単に読めると思う。

fioの設定ファイルは、全体で共通の[global]以下の設定と、 個別の[job]に分けられる。[global]予約語だけど、 [job]に関しては好きな名前を指定できる。もちろん、[job]複数個書くことができる。

[global]                                
ioengine=libaio    # I/Oを発行するのにlibaioを使う。性能測るときは
                   # とりあえずlibaioを指定しておけばよいと思う。
blocksize=4KB      # 1回のI/Oの転送長
blockalign=4KB     # I/Oのアラインメント。
                   # I/O先のアドレスが4KB刻みになる。
kb_base=1024       # K=1000かK=1024かわからなくなるので、
                   # 書いておくと親切。defaultは1024。
filesize=1-1GB     # I/Oをかけるアドレス範囲の指定。
                   # この設定だと、各ファイルそれぞれについて、
                   # 1-1GBの範囲にI/Oをかける(1始まりなので注意)。
direct=1           # Disk I/O性能を測るので、Linuxのキャッシュを
                   # 素通りするようにする。
ioscheduler=noop   # Disk I/O性能を測るので、Linuxに何も考えず
                   # I/Oしてもらうようにする。

[job]
rw=randread        # I/Oの種別。[global]で指定したアドレス範囲から
                   # ランダムに選んでリードする。                                       
filename=/dev/sdc:/dev/sdd:/dev/sde 
                   # どのファイルにI/Oをかけるかを指定。
                   # : で複数指定できる。
                   # 見ての通り、別にファイルでなくともよい。
ramp_time=30       # I/Oかけはじめの、性能測定はしない時間。
                   # I/Oかけはじめは安定しないこともあるので、
                   # 少し時間を置くほうがよいこともある。
runtime=60         # I/Oをかけて、性能測定をする時間。
time_based=1       # filesize分I/Oかけても、runtimeが経過するまで
                   # I/Oをかけ続ける。
numjobs=6          # いくつのプロセスでI/Oするかを指定。
                   # thread=1とすると、forkではなく
                   # pthreadを使ってくれる。
group_reporting=1  # プロセスごとに結果を報告するのではなく、
                   # ジョブごとにまとめて報告する。
iodepth=1          # I/Oの多重度。概して多重で出したほうが性能が上がる。

得られる結果

上記をtest.fioとして保存して、RAM Disk相手にfioを実行してみる。 I/O中はこんな感じで表示される。

$ sudo fio -f test.fio --output test.result             
Jobs: 6 (f=18): [r(6)] [60.0% done] [1137MB/0KB/0KB /s] [291K/0/0 iops] [eta 00m:36s] 

test.resultの中身はこんな感じ。 注意すべきはレイテンシがslat、clat、latの3つあること。 それぞれは以下のとおり。

  • slat:fioがI/O発行して、Linuxがコマンド発行までにかかっている時間。
  • clat:コマンド発行から応答を受け取るまでにかかった時間(単にディスクのレイテンシを見るだけならここだけ見ればよいかも)。
  • lat:全体。
read_test: (g=0): rw=randread, bs=4K-4K/4K-4K/4K-4K, ioengine=libaio, iodepth=1
(略)
read_test: (groupid=0, jobs=6): err= 0: pid=8858: Sun Dec 13 22:27:14 2015                             
  read : io=67192MB, bw=1119.9MB/s, iops=286680, runt= 60001msec                                       
    slat (usec): min=4, max=11476, avg=16.24, stdev=13.23                                              
    clat (usec): min=0, max=10280, avg= 2.26, stdev= 8.96                                              
     lat (usec): min=11, max=10293, avg=18.94, stdev=14.98                                             
    clat percentiles (usec):                                                                           
     |  1.00th=[    0],  5.00th=[    0], 10.00th=[    0], 20.00th=[    1],                             
     | 30.00th=[    1], 40.00th=[    1], 50.00th=[    1], 60.00th=[    1],                             
     | 70.00th=[    1], 80.00th=[    1], 90.00th=[   12], 95.00th=[   13],                             
     | 99.00th=[   15], 99.50th=[   15], 99.90th=[   18], 99.95th=[   20],                             
     | 99.99th=[   47]                                                                                 
    bw (KB  /s): min=    0, max=228328, per=16.54%, avg=189615.16, stdev=19299.85                      
    lat (usec) : 2=88.03%, 4=0.33%, 10=0.02%, 20=11.56%, 50=0.05%                                      
    lat (usec) : 100=0.01%, 250=0.01%, 500=0.01%, 750=0.01%, 1000=0.01%                                
    lat (msec) : 2=0.01%, 4=0.01%, 10=0.01%, 20=0.01%                                                  
  cpu          : usr=12.58%, sys=37.56%, ctx=17215603, majf=0, minf=8                                  
  IO depths    : 1=150.9%, 2=0.0%, 4=0.0%, 8=0.0%, 16=0.0%, 32=0.0%, >=64=0.0%                         
     submit    : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%                        
     complete  : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%                        
     issued    : total=r=17201111/w=0/d=0, short=r=0/w=0/d=0, drop=r=0/w=0/d=0                         
     latency   : target=0, window=0, percentile=100.00%, depth=1                                       
                                                                                                       
Run status group 0 (all jobs):                                                                         
   READ: io=67192MB, aggrb=1119.9MB/s, minb=1119.9MB/s, maxb=1119.9MB/s, mint=60001msec, maxt=60001msec
                                                                                                       
Disk stats (read/write):                                                                               
  sdc: ios=8650516/0, merge=0/0, ticks=102172/0, in_queue=103080, util=61.34%                          
  sdd: ios=8650522/0, merge=0/0, ticks=104708/0, in_queue=103800, util=62.18%                          
  sde: ios=8650522/0, merge=0/0, ticks=101136/0, in_queue=101780, util=60.36%                          

iostat

fioはおおよそのスループットを表示してくれるわけだけど、いかんせんざっくりすぎるので、 iostatを使って、もう少し細かく見てみる。

iostatの入手

何も考えずにaptでinstallする。

$ sudo aptitude install sysstat

iostatの使い方

1秒間隔で表示させておけば困ることはないはず。

$ iostat -x 1 sdc sdd sde

得られる結果

こんな感じで毎秒表示してくれる。 よく使う前半部分に関しては、こんな感じ。

項目 意味
rrqm/s 1秒あたりにマージしたリードリクエストの回数
wrqm/s 1秒あたりにマージしたライトリクエストの回数
r/s 1秒あたりのリード回数
w/s 1秒あたりのライト回数
rkB/s 1秒あたりにリードしたデータ量
wkB/s 1秒あたりのライトしたデータ量
%util I/OリクエストにCPU時間をどれくらい使ったか?(100%=CPUが全力でI/Oを発行しまくっている)
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          12.31    0.00   64.45    0.00    0.00   23.24

Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sdc               0.00     0.00 96948.00    0.00 387796.00     0.00     8.00     1.08    0.01    0.01    0.00   0.01  60.00
sdd               0.00     0.00 96949.00    0.00 387796.00     0.00     8.00     1.15    0.01    0.01    0.00   0.01  64.40
sde               0.00     0.00 96949.00    0.00 387796.00     0.00     8.00     1.08    0.01    0.01    0.00   0.01  57.60

blktrace

測定をしたはよいが、芳しくない結果が得られたときに、一体何が起こっていたのかを 確認するためのトレース採取ツール。困ったときに覗くとよいと思う。

blktraceの入手

考える前にまずはaptを叩く。

$ sudo aptitude install blktrace

blktraceの使い方

I/O中に、デバイスを指定してトレースを取得する。 トレースはコア数分できる。

$ sudo blktrace -d /dev/sdc # Ctrl-Cでトレース採取終了
$ ls
sdc.blktrace.0  sdc.blktrace.2  sdc.blktrace.4  sdc.blktrace.6 
sdc.blktrace.1  sdc.blktrace.3  sdc.blktrace.5  sdc.blktrace.7  

得られる結果

得られたファイルはバイナリなので、blktraceとセットになっているblkparseというコマンドを使って、 読みやすい形に整形する。

$ blkparse -i sdc.blktrace.0 > sdc.blkparse.0

sdc.blkparse.0を覗いてみると、左から順に以下のようになっている(デフォルト設定なのでもちろん変えられる)。

  • デバイスのmajor番号, minor番号
  • CPUコア番号
  • シーケンス番号
  • トレース採取時からの経過時間(秒)
  • I/Oしていたプロセスのpid
  • コマンド状況を表すアルファベット(詳細はmanを参照のこと)
  • 処理を表すアルファベット(詳細はmanを参照のこと)
  • I/Oの先頭アドレス+オフセット
  • プロセス名
  8,32   0        1     0.000000000  9718  G   R 1839944 + 8 [fio]   
  8,32   0        2     0.000000383  9718  P   N [fio]               
  8,32   4        1     0.000000413  9717  G   R 1831472 + 8 [fio]   
  8,32   4        2     0.000000643  9717  P   N [fio]               
  8,32   0        3     0.000001059  9718  I   R 1839944 + 8 [fio]   
  8,32   0        4     0.000001355  9718  U   N [fio] 1             

CPUコア0だけ取り出したのが以下。 多重でI/Oかけていると、トレースが混迷を極めそうだ・・・

8,32   0        1     0.000000000  9718  G   R 1839944 + 8 [fio]
→ fioがI/Oの先頭アドレスを算出した
8,32   0        2     0.000000383  9718  P   N [fio]              
→ fioがカーネルに対してリードリクエストを発行するために
   コマンドをためておくキューに接続
8,32   0        3     0.000001059  9718  I   R 1839944 + 8 [fio]  
→ fioのリードリクエストがコマンドをためておくキューに、
  コマンドを追加
8,32   0        4     0.000001355  9718  U   N [fio] 1            
→ fioのリードリクエストがコマンドをためておくキューとの接続を解除
8,32   0        5     0.000001688  9718  D   R 1839944 + 8 [fio]
→ コマンド発行
8,32   0        6     0.000014064     3  C   R 1839944 + 8 [0]    
→ 応答が返ってきた

さいごに

以上、Disk I/Oの性能測定をするときに活躍する道具たちとその使い方でした。 使い方とか説明が間違っていたらごめんね!

the platinum searcher(pt)をビルドして使ってみよう

※この記事はピョッコリンアドベントカレンダーのために書かれたものです。

サマリ

Golang初心者だけどthe platinum searcherをビルドして使ってみた。ついでにpull requestもしてみた。

the platinum searcherとは何か

ひとことで言うと、プログラマ向けの速いgrep。 作者様のblogはこちら

世の中には改良版grepとして、以下のものがあるみたい。

ptとagの違いは、EUC-JPやShift_JISに対応しているか否か。 (agはUTF-8しか対応していなくて、EUC-JPやShift_JISのファイルが スキップされてしまうという罠がある)

the platinum searcherのビルドとインストール

README.mdにしたがっていけばOK。

$ go get -u github.com/monochromegane/the_platinum_searcher
$ go get -u github.com/jessevdk/go-flags        # なぜか入らなかったので別立て。普通はいらないはず。
$ go get -u github.com/monochromegane/conflag   # なぜか入らなかったので別立て。普通はいらないはず。
$ go get -u github.com/monochromegane/terminal  # なぜか入らなかったので別立て。普通はいらないはず。
$ cd $GOPATH/src/github.com/monochromegane/the_platinum_searcher
$ go build -o $GOPATH/bin/pt cmd/pt/main.go

おまけ:pull requestを投げてみた

pull requestした背景

githubを使ってはいるが、恥ずかしながら1度もpull requestというものを投げたことがなかったので、 pull requestを投げてみたかったんです・・・ というわけで、A Tour of Goをひととおりやった程度の僕でもなんとかできそうなissueを選んでpull requestを投げてみた。

以下のissueは、.gitconfigにexcludesfileが指定されていないときに、グローバルなgitignoreファイルを 読んでくれないよ、っていうもの。

github.com

問題箇所の特定

おおよそこんな感じだったと思う。gdbで実行するまでもなかったかもしれないけど、 gdbを使ってみたかったのもあって、gdbでステップ実行しながら特定した。

  1. gdbで追いかけられるように、まずはptをビルドし直す:go build -v -gcflags "-N -l" -o pt_debug cmd/pt/main.go

  2. ptなりgrepなりで"gitignore"をソースのどこで使っているか探す

  3. ソースコードでは、option.goとoption_test.goとignore.goがひっかかるので、中を覗く。ignore.goだけが関係あることがわかる。

  4. gdbでptを起動する。入り口と思しきignore.go内のglobalGitIgnore関数にbreakpointを作成する。"b globalGitIgnore"と打つと、gdbはそんな関数しらねとのたまうので、"b ignore.go:88"とファイルかつ行数指定でbreakpointを作成する。

  5. gdbでステップ実行していくと、globalGitIgnoreName関数内で、gitconfig内にexcludesfilesが定義されていないときに空文字列が返されることがわかるので、その部分を修正すればよいことがわかる。

追記

何か中途半端だったし、元のソースコードとコンフリクトしてたしでプルリクを引っ込めた。

プログラミング言語pyoko

※この記事はピョッコリンアドベントカレンダーのために書かれたものです

はじめに

みんなのアイドルピョッコリンさんのために、 プログラミング言語pyoko(以後、単にpyokoと呼びます)を開発しました!!

pyokoとは

pyokoは、言語のシンプルさとピョコ感を出すことを念頭に置いて設計された言語です。 字面の似ているpythonと同等の表現力を持ちながらも、圧倒的可読性・記述性を達成しました。 また、ピョコ感を演出することうけあいでしょう。

サンプルコード

お決まりのHello, Worldをみてみましょう。 ね、圧倒的可読性・記述性とピョコ感が伝わるでしょう?

ピョコピョコピョコピョコピョコピョコピョコピョコピョコピョリコピョッコ
ピョコピョコピョコピョコピョコピョコピョコピョコピョッコピョコピョコ
ピョコピョコピョコピョコピョコピョコピョコピョコピョコピョッコピョコ
ピョコピョコピョコピョコピョ?!ピョ?!ピョ?!ピョコリピョコ-ピョッコ
ピョッコリンピョッコピョコピョコピョッコリンピョコピョコピョコピョコピョコ
ピョコピョコピョッコリンピョッコリンピョコピョコピョコピョッコリンピョッコ
ピョコリピョッコリンピョコリピョコリピョコリピョコリピョコリピョコリピョコリ
ピョコリピョコリピョコリピョコリピョコリピョッコリンピョ?!ピョコピョコピョコ
ピョコピョコピョコピョコピョコピョッコリンピョコリピョコリピョコリピョコリ
ピョコリピョコリピョコリピョコリピョッコリンピョコピョコピョコピョッコリンピョコリ
ピョコリピョコリピョコリピョコリピョコリピョッコリンピョコリピョコリピョコリピョコリ
ピョコリピョコリピョコリピョコリピョッコリンピョッコピョコピョッコリン

上記テキストをhello.pyokoとして保存し、インタプリタを実行します。

$ pyoko hello.pyoko
Hello, world!

ね、Hello, worldでしょう?

ソースコード

pyokoのインタプリタpythonで実装されています。

#!/usr/bin/env python
#fileencoding: utf-8

from __future__ import print_function
import sys
import codecs

class pyokolang():
    plus   = "+"
    minus  = "-"
    gt     = ">"
    lt     = "<"
    lb     = "["
    rb     = "]"
    comma  = ","
    period = "."

    def __init__(self,filename):
        with codecs.open(filename, "r", "utf-8") as f:
            self.raw_code = ("".join(f.readlines())).strip()
        self.iptr = 0
        self.dptr = 0
        self.data = [0]*100
        self.loopmap = {} 
        self.insts = []
        self.__parser()

    def __parser(self):
        self.__tokenizer()

    def __get_token(self, pos):
        is_same = lambda raw_code, pos, x: raw_code[pos:pos+len(x)] == x
        quasi_plus   = u"ピョコ"
        quasi_minus  = u"ピョコリ"
        quasi_gt     = u"ピョッコ"
        quasi_lt     = u"ピョ?!"
        quasi_lb     = u"ピョリコ"
        quasi_rb     = u"ピョコ-"
        quasi_comma  = u"ピョコ?!"
        quasi_period = u"ピョッコリン"

        if is_same(self.raw_code, pos, quasi_minus):
            return quasi_minus, pyokolang.minus
        elif is_same(self.raw_code, pos, quasi_period):
            return quasi_period, pyokolang.period
        elif is_same(self.raw_code, pos, quasi_gt):
            return quasi_gt, pyokolang.gt
        elif is_same(self.raw_code, pos, quasi_lt):
            return quasi_lt, pyokolang.lt
        elif is_same(self.raw_code, pos, quasi_lb):
            return quasi_lb, pyokolang.lb
        elif is_same(self.raw_code, pos, quasi_rb):
            return quasi_rb, pyokolang.rb
        elif is_same(self.raw_code, pos, quasi_plus):
            return quasi_plus, pyokolang.plus
        elif self.raw_code[pos:pos+len(quasi_comma)] == quasi_comma:
            return quasi_comma, pyokolang.comma
        else:
            return False, False

    def __tokenizer(self):
        pos = 0
        while pos < len(self.raw_code):
            token, symbol = self.__get_token(pos)
            if token is False:
                pos += 1
                continue
            self.insts.append(symbol)
            pos += len(token)

    def __gen_loopmap(self):
        stack = []
        for i, val in enumerate(self.insts):
            if val == pyokolang.lb:
                stack.append(i)
            elif val == pyokolang.rb:
                if len(stack) == 0:
                    print("parse error: right brackets exists more than left brackets")
                    sys.exit()
                lb_index = stack.pop()
                self.loopmap[i] = lb_index
                self.loopmap[lb_index] = i

        if len(stack) != 0:
            print("parse error: left brackets exists more than right brackets")
            sys.exit()


    def executor(self):
        self.__gen_loopmap()
        while self.iptr < len(self.insts):
            if self.insts[self.iptr] == pyokolang.gt: # >
                self.dptr += 1
                if self.dptr >= len(self.data):
                    for i in range(len(self.data)):
                        self.data.append(0)
            elif self.insts[self.iptr] == pyokolang.lt: # <
                self.dptr -= 1
                if self.dptr < 0:
                    print("data pointer underflow", self.iptr, self.insts, self.dptr, self.data)
            elif self.insts[self.iptr] == pyokolang.plus: # +
                self.data[self.dptr] += 1
            elif self.insts[self.iptr] == pyokolang.minus: # -
                self.data[self.dptr] -= 1
            elif self.insts[self.iptr] == pyokolang.period: # .
                print(chr(self.data[self.dptr]), end="")
            elif self.insts[self.iptr] == pyokolang.comma:  # ,
                self.data[self.dptr] = sys.stdin.read(1)
            elif self.insts[self.iptr] == pyokolang.lb: # [
                if self.data[self.dptr] == 0:
                    self.iptr = self.loopmap[self.iptr]
            elif self.insts[self.iptr] == pyokolang.rb: # ]
                if self.data[self.dptr] != 0:
                    self.iptr = self.loopmap[self.iptr]
            self.iptr += 1

    def run(self):
        self.executor()

def usage():
    print("usage: python pyokolang.py <filename>")
    sys.exit()

def main():
    if len(sys.argv) != 2:
        usage()
    bp = pyokolang(sys.argv[1])
    bp.run()
    print()

if __name__ == "__main__":
    main()

実装に関して

ここまで来ればお気づきの方もいるかもしれませんね。 そう、pyokoは難読で有名なプログラミング言語brainf*ckを ベースに作られています。 実装方針は単純で、brainf*ckを構成する8個の実行命令(+/-/>/</[/]/,/.)を ピョコ感ある単語に置き換えるだけです。

処理の流れも単純で、以下の2ステップです。

  1. ピョコ感ある単語をbrainf*ckの8個の実行命令に置き換え(__tokenizer)

  2. brainf*ckインタプリタで実行(executor)

感想や反省など

  • 感想:意外に簡単にできた。
  • 反省:もっとhello.pyokoを複雑な文章にすべきだった。少々面白みに欠ける。

おまけ

インタプリタソースコードはここにあります。 github.com

easy_perfmonをjavascriptで書きなおした

シルバーウィークに作ったおもちゃをjavascriptで書きなおしました、というお話。

サマリ

こんな感じになった。

f:id:nbisco:20151029234055p:plain

Smoothie ChartからFlotに移行し、bottle.pyからexpressに移行した。 Flotはこまごましたところまでいじれるので楽しい。

ソースコードはこちら。 github.com

使ったツール

bottleには負けるがexpressも結構簡単に使えたと思う。 jadeの素晴らしさには感動した。あの面倒なhtmlファイルが簡単に書けちゃう。

以前
webフレームワーク bottle express
テンプレートエンジン なし jade、stylus
グラフ Smoothie Charts Flot

使い方

app.jsを起動するだけ。ポート3000番でお待ちしております。

$ node app.js  # => 3000番で待ち受け

はまったところ

Flotの凡例(legend)出すのにはまった。 最初にplot.pushするときには、ちゃんとラベルを指定していたんだけど、 アップデートのときにラベル指定が抜けてたせいで凡例が描画されなかった。 当たり前っちゃ当たり前なような気もするけど、全然気づかなかったなあ・・・

これが正しい例。

// 最初にpush
plots.push($.plot(placeholders[0],
  [{label:"usr",data:usr},{label:"sys",data:sys},{label:"idle", data:idle}], options[0]));

/* 略 */

// データ更新時に描画
plots[0].setData([{label:"usr",data:usr}, {label:"sys",data:sys}, {label:"idle",data:idle}]);

こっちが間違いの例。

// 最初にpush
plots.push($.plot(placeholders[0],
  [{label:"usr",data:usr},{label:"sys",data:sys},{label:"idle", data:idle}], options[0]));

/* 略 */

// データ更新時に描画 => labelがnullになるせいで、描画されない。
plots[0].setData([usr, sys, idle]);

Flot本体のコードを覗いてみると一目瞭然であった。さっさと読めという話ですね。

/* jquery.flot.js */

/* 略 */
function insertLegend() {
/* 略 */
     // Build a list of legend entries, with each having a label and a color

    for (var i = 0; i < series.length; ++i) {
        s = series[i];  // seriesには、setDataメソッドに渡したオブジェクトとグラフのoptionが入っている。
        if (s.label) {  // labelをつけてないと、このネストに入れない。
            label = lf ? lf(s.label, s) : s.label;
            if (label) {
                entries.push({
                    label: label,
                    color: s.color
               });
            }
        }
    }
/* 略 */
}

Smoothie ChartsとBottleを使ったリアルタイムパフォーマンスモニタ

今週はシルバーウィーク。 しばらく楽しくプログラムを書いてなかったし、 せっかくの長い休みなので、単にイカで遊び呆けるだけじゃなくて、 ちょっとしたものを工作してみようと思い立った。

サマリ

Smoothie ChartsとBottleを使って、こんな感じのものができた。 f:id:nbisco:20150923214841p:plain

ソースコードはここ。READMEはまだ。。。 github.com

使い方

以下の通り立ち上げて、ブラウザでアクセスすれば上記画像のような画面が現れる(はず)。 mpstat、iostat、vmstat、sarを使うので、sysstatをaptとかで入れておく。

$ sudo aptitude install sysstat
$ git clone https://github.com/bisco/easy_perfmon.git
$ cd easy_perfmon
## 引数なしだとPort 8080をListenして立ち上がる。引数あげるとそのPort番号をListenして立ち上がる。
$ python easy_perfmon.py  

経緯

Linuxサーバの負荷状況を見るツールはいろいろあるわけだけど、 CUIなのでいかんせん味気ない。グラフとして見えたほうがいいよね、ということで、 Linuxサーバの負荷状況を可視化することにした。

単に可視化するだけじゃなく、どうせならリアルタイムに負荷を見たい。 ということで、リアルタイムに負荷状況が見えるツールを作ることにした。

使ったライブラリ

Smoothie Charts

お手軽に可視化するにはブラウザ+Javascriptの組み合わせが一番(だと思う)。 なので、Javascriptで簡単にリアルタイムなグラフが描けるSmoothie Chartsを選んだ。 Smoothie Chartsは大変シンプルで、設定できる項目もあまりないんだけど、 その分簡単に使えるのがよいところ。 本家サイトに行けば、コードジェネレータ(!)があるので、 それを使いながら見た目の調整ができる。何とも楽ちん。

Bottle

BottlePythonで書かれたweb frameworkの1つで、 非常にシンプルで簡単に使えるのが特徴。

今回は、主にSmoothie Chartsにデータを渡すために使用。 mpstatやiostatなどの結果をJSONに変換して、httpでアクセスできるようにした。

bootstrap

おなじみbootstrap。見た目調整のために使用。

プログラムのおおよその構造

JavascriptでBottleから定期的にJSONを取得し、Smoothie Chartsに渡しているだけ。 定期取得の部分はjQueryAJAX関数を使って実装した。

はまったところ

AJAXで同期的に情報を取ろうとしてはまった。 AJAXは基本非同期動作なので、同期的に情報を取るときは、明示的に同期動作にしてやらないといけない。

$.ajaxSetup({ async: false }); 
$.getJSON(); 
$.ajaxSetup({ async: true }); 

まとめと感想

Smoothie ChartsとBottleでお手軽にLinuxの負荷状況を可視化するツールを作った。 グラフィカルに見えるものを作るのは大変でもあり、楽しくもあり、って感じでした。