package.elで自動インストール

Emacsには便利なelispパッケージがあるが、最近ではpackage.elというのが付属していて、ネットワーク上からパッケージをインストール仕組みができている。以前はウェブ上から手作業でダウンロードして.emacs.dに下に保存していたが、package.elを使えば一覧から好きなものを選択するだけでインストールできる。

ということで、package.elの設定と自動インストールの設定をしてみた。

基本設定

デフォルトではelpa.gnu.orgから取得するようになっているが、ここにあるパッケージはあまり多くない。MELPA(Milkypostman’s Emacs Lisp Package Archive)というところに多く集まっているので、これを追加しよう。

MELPAを使う設定は公式サイトにあるが、Emacs-24なら以下で良い。

(require 'package)
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/"))
(add-to-list 'package-archives '("melpa-stable" . "https://stable.melpa.org/packages/") t)
(package-initialize)

ここではmelpaとmelpa-stalbeの2つの設定した。melpa-stableの方は安定版が登録されているので、安定性を重視するならこちらを使うのが良いだろう。ただ、melpa-stableにはないものもあるので両方設定する。

これで起動して、M-x package-list-packagesと打てばパッケージのリストを取得して表示してくれる。

f:id:wagavulin:20160704211100p:plain

同じ名前のパッケージが2つあるものがあるが、これはstableとそうでないものがあるからだ。あとは欲しいものを選んで"Install"を選べばインストールしてくれる。インストール先は.emacs.d/elpaの下になる。

また、package.elを使うときは必ずpackage-initializeを呼ぶようにしよう。単にインストールしただけでは、load-pathにそのインストール先が含まれていないため使うことができない。あるいは手動でload-pathに追加しなくてはならない。package-initializeを呼べばそのあたりを任せることができる。

Windowsでの設定

公式サイトに書いてあるが、Windows版のEmacsではHTTPSがうまく扱えないので、リポジトリのURLをhttps://ではなくhttp://にする必要がある。

プロキシ設定

プロキシを使う必要がある場合は以下のようにurl-proxy-servicesを設定する。

(require 'package)
(setq url-proxy-services 
      '(("http" . "proxy.example.com:8080")
        ("https" . "proxy.example.com:8080")))
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/"))
(add-to-list 'package-archives '("melpa-stable" . "https://stable.melpa.org/packages/") t)
(package-initialize)

自動インストール

これで手動でのインストールができたが、できれば自動化したいところだ。新しい環境に行く度に必要なパッケージを一つずつ選択するのは面倒だ。

動作仕様

やりたいことはざっと以下のような感じ。

  • リポジトリからパッケージの一覧を取得したことがない場合、パッケージ一覧を取ってくる。すでに一覧がある場合は取ってこない(結構時間がかかるので毎回やると煩わしい)。
  • あらかじめ必要なパッケージをリストアップしておき、そのパッケージがインストールされていなければインストールする。

以上を行うのが以下のスクリプト。my-favorite-package-listにインストールしたいパッケージの名前を入れておく。ここではweb-mode、projectile、auto-completeを入れてみた。

;;; MELPA
(defvar my-favorite-package-list
  '(web-mode
    projectile
    auto-complete)
  "packages to be installed")

(require 'package)
(add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/"))
(add-to-list 'package-archives '("melpa-stable" . "http://stable.melpa.org/packages/") t)
(package-initialize)
(unless package-archive-contents (package-refresh-contents))
(dolist (pkg my-favorite-package-list)
  (unless (package-installed-p pkg)
    (package-install pkg)))

;;; web-mode
(require 'web-mode)
;; 以下web-modeの設定

これで起動するとパッケージ一覧の取得とインストールが行われる。下のバッファにエラーが出ていなければ成功だ(多少の警告はあるかもしれない)。また、最後に(require 'web-mode)としているので、これでweb-modeが使えるようになる。

f:id:wagavulin:20160704211059p:plain

stableと最新版の使い分け

上の設定だと、melpaとmelpa-stableの両方にパッケージがある場合、melpaの方が使われる。できれば安定したものが使いたいので、melpa-stableにあるものはmelpa-stableから、そうでなければmelpaから取ってくるようにしたい。

調べたところ、package-pinned-packagesという変数に、パッケージと取得するリポジトリを挙げておけば良いようだ。例えば、web-modeとauto-completeはmelpa-stableから取ってくるが、それ以外はmelpaから取ってくる、という場合、以下のようにする。

;;; MELPA
(defvar my-favorite-package-list
  '(web-mode
    auto-complete
    projectile)
  "packages to be installed")

(require 'package)
(add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/"))
(add-to-list 'package-archives '("melpa-stable" . "http://stable.melpa.org/packages/") t)
(package-initialize)
(setq package-pinned-packages
      '((web-mode . "melpa-stable")
        (auto-complete . "melpa-stable")))
(unless package-archive-contents (package-refresh-contents))
(dolist (pkg my-favorite-package-list)
  (unless (package-installed-p pkg)
    (package-install pkg)))

;;; web-mode
(require 'web-mode)
;; 以下web-modeの設定

パッケージ名を2箇所に書いているあたりがちょっとイケてないが、これでインストールを自動化できた。

Ubuntu 16.04 Unity設定

Ubuntu標準のインターフェースであるUnityだが、デフォルト状態では正直あまり使いやすくないし、見た目もイマイチだと思う。

f:id:wagavulin:20160703032008p:plain

MateやKDEを入れるという手もあるが、なんとかUnityを使ってやろうと思い、色々設定してみた。

設定ツールのインストール

システム設定から設定できる項目は少なく、痒いところに手が届かない。CompizConfig Settings ManagerとUnity Tweak Toolを入れよう。

 $ sudo apt-get install compizconfig-settings-manager unity-tweak-tool

Unityの設定は「システム設定」、「Unity Tweak Tool」、「Compiz Config Settings Manager」の3つを使うことになる。一部の項目はどれを使っても可能だったりするが、どれも守備範囲が異なるので全てを一つでまかなうことはできない。ここではUnity Tweak Tool→Compiz Config Settings Manager→システム設定という優先順位にする。

なお、設定された内容はちゃんと同期が取れているようなので、どのアプリで設定したかは覚えなくてよい。例えば、ワークスペースの有効化/無効化はUnity Tweak Toolでもシステム設定でもできるが、Unity Tweak Toolで有効化すれば次にシステム設定を開いたときにはそこでも有効になっている。ただし、テーマの設定については別で、システム設定にあるテーマ設定とUnity Tweak Toolのテーマ設定は別物のようだ。

Launcherの設定

自動的に隠す

Launcherというのは画面左にあるやつのこと。小型のノートPCを使っているような場合、これが結構場所を食う。そんなときはUnity Tweak Toolの「Launcher」→「自動的に隠す」をオンにしよう。

これでLauncherが隠れるようになる。Windowsのタスクバーの「自動的に隠す」設定と似たようなものだ。しかし、マウスカーソルを一番左に持っていってもLauncherが出てきてくれないことがある。Windowsならカーソルを画面下に持っていけばすぐに出てくれるのに。

どうもUnityの場合はカーソルを左端に置いただけではダメで、そこからさらに左に押し込むようにすることが必要だ。また、ある程度の速度があった方がよい。「表示感度」を最高(右端)にすれば出やすくなるが、それでもやはりカーソルを左端に置いただけでは出てこない。ある程度の速度とさらに押し込む動作が必要なことは覚えておこう。

f:id:wagavulin:20160703040815g:plain

また、自分が試した限りでは、VMwareのゲストとしてUbuntuを動かしているときはうまく出てこない。その場合はマウスを諦めてWindowsキーを押すようにするか、あるいは諦めよう。

位置を変える

「Position」を「bottom」にすればLauncherの表示位置を下にすることもできる。今のところ上や右にはできないようだ。個人的には下にあるのが好きだが、最近のPCのワイド液晶で下にLauncherを置くとますます縦が短くなるのが悩ましい。

f:id:wagavulin:20160703025427p:plain

アイコンサイズ

「自動的に隠す」を使わない場合、アイコンサイズを変えるだけでもある程度表示領域を増やすことができる。

f:id:wagavulin:20160703032009p:plain

ワークスペース(仮想デスクトップ)

Unix系OSのウィンドウマネージャにはたいてい仮想デスクトップの機能がある。Unityでは「ワークスペース」と呼ぶが、デフォルトでオフになっているのでオンにする。オンにすると自動的にワークスペーススイッチャーがLauncherに登録される。

f:id:wagavulin:20160703040834p:plain
f:id:wagavulin:20160703040828p:plain

ワークスペースの数は水平ワークスペースx垂直ワークスペースになる。2x2=4でいいだろう。

ワークスペースの変更

ワークスペースを変更するにはいくつかの方法がある。

  1. Launcherのワークスペーススイッチャーをクリックする。
  2. ワークスペーススイッチャをキーボード・ショートカットで呼び出す(Windows + s)。
  3. ワークスペースを上下左右に移動するキーボード・ショートカットを使う(Ctrl + Alt + カーソルキーの上下左右)。
  4. 指定した番号のワークスペースに直接移動するショートカットを使う。

一番よく使うのは4の方法だが、デフォルトでは割り当てられていないので追加する。

ワークスペース変更のショートカットキー

このショートカットキーの割り当てはCompiz Config Settings Managerからできる(システム設定でもできる)。「Viewport Switcher」→「Go to specific viewport」を選び、そこにある「Switch to Viewport 1」から順に4まで割り当てればよい。

f:id:wagavulin:20160703040826p:plain

割り当てるには、「無効」になっているボタンを選び、「キーの組み合わせをつかむ」をクリックし、そこで割り当てたいキーを押せば良い。

キーは何でも良いが、自分の場合はそれぞれAlt + 1, 2, 3, 4に割り当てている。昔使っていたウィンドウマネージャの何か(確かWindowMakerだったと思う)のデフォルトがこれだったので、それ以来手に染み付いているのだ。

Alt+1など、他のアプリのショートカットキーと被ってしまいそうだが、今のところ意外と不便を感じたことはない。

ワークスペース移動のアニメーションとプレビューの無効化

これでワークスペースの移動が手軽にできるようになったので、次に不要なUI効果を無効にする。デフォルトでは以下のようにワークスペースの移動時に画面全体が動くような感じのアニメーションがあり、また画面真ん中にプレビューが出る。

f:id:wagavulin:20160703040818g:plain

頻繁に移動するとどちらも目障りなので無効化する。移動のアニメーションはCompiz Config Settings Managerの「Desktop Wall」→「Viewport Switching」タブにある「Wall Sliding Duration」の値を0にすればよい。

f:id:wagavulin:20160703040822p:plain

また、プレビューは「Viewport Switch Preview」タブの「Show Viewport Switcher Preview」を外すると無効にできる。

f:id:wagavulin:20160703040821p:plain

これで余計な効果がなくなった。

f:id:wagavulin:20160703040819g:plain

Alt+Tabの挙動の変更

Windowsと同様、Alt+Tabでウィンドウを切り替えることができるが、Unityの場合は同じアプリのウィンドウが複数ある場合には、まずアプリを選択してその後にウィンドウを選ぶ、という2段階の操作になる。

f:id:wagavulin:20160703040813g:plain

個人的にはWindowsのように全ウィンドウを選択できる方が好き(というより、この挙動が嬉しい場面が思い当たらない)ので変更する。

プラグインの有効化とショートカットキーの設定

ウィンドウ切り替えのプラグインとして、「アプリケーション・スイッチャー」と「Static Application Switcher」というプラグインが用意されていて、これらを使えば1段階で選択できるような挙動に変えられる。どちらを使っても良いが、表示の仕方が少し違う。上がアプリケーションスイッチャー、下がStatic Application Switcherだ。

f:id:wagavulin:20160703040814g:plain
f:id:wagavulin:20160703040817g:plain

アプリケーション・スイッチャーの場合は、ウィンドウを選択するための四角い枠は固定された状態で、ウィンドウプレビューが動く。一方、Static Application Switcherの場合はWindowsと同様、ウィンドウプレビューは固定され、枠の方が動く。

自分はStatic Application Switcherの方が好きなのでこちらを使う。どちらもデフォルトでは無効になっている(チェックが外れている)ので、どちらかを有効にする。

f:id:wagavulin:20160703072107p:plain

このとき、「Static Application Swithcer プラグインの割り当てのいくつかは他のプラグインと干渉します。これらの干渉を解決しますか?」などというメッセージが出るだろう。"Alt+Tab"はデフォルトのSwitcher(アプリ→ウィンドウの2段階で選択するやつ)の「次のウィンドウ」に割り当てられている。一方、アプリケーション・スイッチャーやStatic Application Switcherもそれぞれの「次のウィンドウ」という動作に"Alt+Tab"が割り当てている。そのため、プラグインを有効にすると被ってしまうのだ。

ここで「干渉を無視」を選ぶと割り当てが特に変更されることなく有効化される。つまり、場合によって"Alt+Tab"が2つのコマンドに割り当てられることもある。そのような場合、"Alt+Tab"を押すと2つのスイッチャーが重なって表示される。「干渉を解決」を選べば、どちらを有効にするか聞かれるので選択する。

よく分からなくなった場合はそれぞれのキー割り当て設定を見直そう。いずれもCompiz Config Settings Managerで変更できる。デフォルトの"Alt+Tab"、"Shift+Alt+Tab"の設定は「Ubuntu Unity Plugin」の「Switcher」タブにある「Key to start the Switcher」と「Key to switch to the previous window in the Switcher」に割り当てられている。これらが無効になっていなければならない。

f:id:wagavulin:20160703040825p:plain

また、アプリケーション・スイッチャー/Static Application Swithcherの「次のウィンドウ」「前のウィンドウ」に"Alt+Tab"/"Shift+Alt+Tab"が割り当てられていなければならない。

f:id:wagavulin:20160703040824p:plain
f:id:wagavulin:20160703040820p:plain

背景色の設定

これでウィンドウ単位の切り替えができるようになった。しかし、プレビューウィンドウが周りに溶け込んでしまって見づらい。そういうときは背景色を設定する。その設定は、Static Application Switcherの「外観」タブの「Background」にある。ここの「Set background color」にチェックを入れ、「Background Color」に好きな色を入れよう。こだわりがなければ黒っぽい色にしてかつ少し透明にする、といった感じでいいと思う。自分の場合は色を黒にして不透明度を200くらいにした。

f:id:wagavulin:20160703040816g:plain

ウィンドウスナップの無効化

最近のWindowsと同様、Unityにもウィンドウスナップの機能があり、デフォルトでオンになっている。ウィンドウスナップというのは、ウィンドウを特定の場所に動かすとサイズを変えたりする機能だ。例えばウィンドウを一番上に持って行くと最大化し、一番右に持っていくと縦に最大化かつ横は画面半分の長さになり、また位置が画面一番右に固定される。

個人的にはあまり好きではないので無効にする。この設定はUnity Tweak Toolの「Window Snapping」にある。「ウィンドウスナップ」をオフにすれば無効化できる。あるいはオンにしたまま特定の機能だけ残すこともできる。

f:id:wagavulin:20160703040833p:plain

テーマの変更

機能とは無関係だが、Ubuntu標準の紫とオレンジを組み合わせた色使いがどうもあまり好きになれない。見た目を変えるにはテーマを変更すれば良いが、システム設定の「外観」タブにテーマでテーマを選べるが、正直あまりいけてないと思う。

そういう場合はテーマを別途インストールしよう。Ubuntu標準のパッケージにはUnity用のテーマはあまりないようなので、パッケージのリポジトリを追加することになる。検索すれば色々出てくるだろうが、Noobslabというところが提供しているテーマを使うことにした。

リポジトリを追加してパッケージをインストールするにはコマンドラインで以下のようにする。

 $ sudo add-apt-repository ppa:noobslab/themes
 $ audo apt-get update
 $ sudo apt-get install パッケージ名

ただ、これではパッケージ名があらかじめ分かっていなければならない。追加したリポジトリに含まれるパッケージの名前をコマンドラインから知る方法も多分あるだろうが、ちょっと調べただけでは見つからなかった。こういうのはGUIツールを使うのが良いと思う。

aptのGUIツールとしてはsynapticというのがある。デフォルトでは入っていないのでまずはこれを入れる。

 $ sudo apt-get install synaptic

synapticのメニューの「設定」→「リポジトリ」から「他のソフトウェア」タブを選ぶ。ここの「追加」ボタンを押し、そこにリポジトリの名前(ppa:noobslab/themes)と入れよう。

f:id:wagavulin:20160703040831p:plain

追加すると自動的にパッケージの再読み込みが行われる。あとは左下にある「配布元」からリポジトリを選べば、そのリポジトリにあるパッケージ一覧を見ることができる。とりあえずLP-PPA-noobslab-themes/nowとLP-PPA-noobslab-themes/xenialのパッケージを全部入れてしまおう。

f:id:wagavulin:20160703072108p:plain

これでUnity Tweak Toolの「テーマ」でテーマを変えることができる。なお、システム設定にあるテーマのところでは使えない。いくつか試した結果、今のところテーマはCrunchy-blue、アイコンはAdwaitaに落ち着いている。また、背景も変更した。

f:id:wagavulin:20160703040827p:plain

スクロールバー

Unityのデフォルトでは、スクロールバーが非常に細く表示される。また、ドラッグするにはマウスカーソルを近づけて、現れるやつ(以下を参照)をドラッグしなければならない。

f:id:wagavulin:20160703040829p:plain

少しでも表示領域を大きくしようということなのだろうが、はっきり言って使いにくいので普通のやつにしよう。この変更はコマンドラインで行う。

 # 一般的なスクロールバーにする
 $ gsettings set com.canonical.desktop.interface scrollbar-mode normal
 # 元に戻す
 $ gsettings reset com.canonical.desktop.interface scrollbar-mode

f:id:wagavulin:20160703040830p:plain

まとめ

以上、Unityの設定を色々変更してみた。他にも設定できるところはあるので試してみよう。

curlコマンドによるウェブアプリのパフォーマンス測定 (2) アップロード・ダウンロード速度測定パッチ

前回の続き。

前回色々試した結果、curlの-wオプションではPOSTリクエストのボディとレスポンスのBodyが両方とも大きい場合、アップロード・ダウンロード速度を上手く計れないことが分かりました。仕方ないのでcurlを改造することにしました。

今回追加したのは-wオプションで使える以下の変数です。

変数名 意味
time_startupload リクエスト送信開始時間
time_finishupload リクエスト送信終了時間
time_startdownload レスポンス受信開始時間
time_finishdownload レスポンス受信終了時間

前回のtest3.cgiで試すと以下のような感じになります.

$ curl -s -o /dev/null --data-binary @big.txt -w @format.txt http://localhost/~wagavulin/test3.cgi
http_code           200
size_upload         61931520
speed_upload        26589078.000
size_download       104857600
speed_download      45018544.000
time_pretransfer    0.001
time_starttransfer  0.002
time_startupload    0.002
time_finishupload   0.152
time_startdownload  2.173
time_finishdownload 2.329
time_total          2.329

概ね期待通りの値が出ています。ソースはgithubに置きました。ビルド方法はGIT-INFOに書いてありますが、

$ ./buildconf
$ ./configure
$ make

ビルドできます(Ubuntu-14.04で確認)。また、autoconf, automake, libtoolが必要です。

curlコマンドによるウェブアプリのパフォーマンス測定 (1) time_starttransferの仕様

curlの-wオプションを使うとHTTPリクエスト/レスポンスの様々なデータを表示することができます。が、ちょっと不可解な部分があったので調べてみました。

背景

ウェブアプリの簡単なテストのためcurlコマンドをよく使っていたのですが、-wオプションでちょっとしたパフォーマンス測定ができるらしいことを知りました。マニュアルを見ると、全体の処理時間(time_total)やTCPのコネクションにかかった時間(time_connect)などいくつかのデータを提供してくれますが、その中にtime_starttransferというのがあります。

time_starttransfer
The time, in seconds, it took from the start
until the first byte was just about to be trans-
ferred. This includes time_pretransfer and also
the time the server needed to calculate the
result.

ざっと訳すと、「開始から、最初のバイトがまさに転送されるときまでの秒数。time_pretransferと、サーバが結果の計算に必要だった時間も含む」となります。これならサーバのパフォーマンス測定に使えそうです。

しかし、POSTの場合はどうでしょうか?POSTのボディが大きい場合はリクエストの送信にも時間がかかります。その場合time_starttransferはリクエスト送信時間も含むのでしょうか?マニュアルの説明だけではよく分からず、ネット上にも詳細な説明はあまりないようなので調べてみることにしました。

-wオプションの使い方

その前にまずは-wオプションの使い方。curl -w <format>を使うと、各種データを標準出力に出すようになります。また、format中に%{変数名}を入れると、その部分が実際の値に置き換わります。例えば以下のようにすると、%{time_total}の部分が実際にかかったトータル時間に置き換わり、"total 0.003"などのように表示されます。なお、出力後に改行を入れてはくれないので、最後に\nを入れる方が良いでしょう。

 $ curl -w "total %{time_toal}\n" http://localhost

また、<format>の先頭を@にすると、@以降の文字列で指定したファイルをフォーマット文字列として使うようになります。文字列が長い場合はこれを使うと良いでしょう。今回はformat.txtというファイルを作り、内容を以下のようにしました。

http_code           %{http_code}\n
size_upload         %{size_upload}\n
speed_upload        %{speed_upload}\n
size_download       %{size_download}\n
speed_download      %{speed_download}\n
time_pretransfer    %{time_pretransfer}\n
time_starttransfer  %{time_starttransfer}\n
time_total          %{time_total}\n

なお、使える変数の一覧はcurlのmanページの-wオプションの項目にあります。

GETで試す

ということで早速試してみます。今回は時間の計測が目的であって実際にダウンロードされたコンテンツに興味はありません。-o /dev/nullとしてダウンロードしたデータは捨ててしまいましょう。また、-oを付けると自動的にアップロード/ダウンロード状況が表示されるようになりますが、これも邪魔なので-sを付けて抑制します。

$ curl -s -o /dev/null -w @format.txt http://localhost
http_code           200
size_upload         0
speed_upload        0.000
size_download       11510
speed_download      3304622.000
time_pretransfer    0.002
time_starttransfer  0.003
time_total          0.003

GETメソッドで静的なコンテンツをダウンロードしています。localhostなので非常に高速、かつサイズもそれほど大きくないのでほぼ一瞬で終わっています。

重い処理の場合

ではサーバの処理が重くなったらどうでしょうか。以下のようなCGIスクリプトを用意してみました。最初にsleep 2としている部分が"重い処理"にあたるもので。2秒後にレスポンスを返すようになっています。

test1.cgi

#!/usr/bin/ruby

sleep 2
print "Content-Type: text/plain\n\n"
puts "ok"
$ curl -s -o /dev/null -w @format.txt http://localhost/~wagavulin/test1.cgi
http_code           200
size_upload         0
speed_upload        0.000
size_download       3
speed_download      1.000
time_pretransfer    0.002
time_starttransfer  2.166
time_total          2.167

このように、starttransferが2秒ちょっとになっています。マニュアルにある「time_pretransferと、サーバが結果の計算に必要だった時間も含む」という説明に合っているようです。

POSTで試す(小さなデータ)

今度はPOSTを使ってみます。以下のようなスクリプトを作ってみました。recv_fileメソッドはリクエストのボディを受け取り、受け取ったバイト数を返します。最初にrecv_fileを呼び、その後2秒sleepしてからレスポンスを返します。

test2.cgi

#!/usr/bin/ruby

def recv_file
  bytes = 0
  while buf = $stdin.read(4096)
    bytes += buf.length
  end
  return bytes
end

bytes = recv_file
sleep 2
print "Content-Type: text/plain\n\n"

puts "received #{bytes.to_s}"

最初はsmall.txtという5バイトのファイルを使って試してみます。なお、--data-binaryなどといったデータ送信のためのオプションについては前回の記事を参考にして下さい。

$ curl -s -o /dev/null --data-binary @small.txt -w @format.txt http://localhost/~wagavulin/test2.cgi
http_code           200
size_upload         5
speed_upload        2.000
size_download       11
speed_download      5.000
time_pretransfer    0.009
time_starttransfer  2.155
time_total          2.155

size_uploadが5になっています。また、time_starttransferもやはり2秒ちょっとです。

POSTで試す(大きなデータ)

今度は大きなデータを送ってみます。big.txtという、約60MBのファイルを用意しました。

$ curl -s -o /dev/null --data-binary @big.txt -w @format.txt http://localhost/~wagavulin/test2.cgi
http_code           200
size_upload         61931520
speed_upload        24353647.000
size_download       18
speed_download      7.000
time_pretransfer    0.002
time_starttransfer  0.004
time_total          2.543

するとどういうわけか、time_starttransferが非常に小さな値になっています。2秒のsleepがあるので、レスポンスを返し始めるまでは最低2秒かかるはずです。なぜこんな小さな値になるのでしょうか?

ということで、原因調査のためまずは-vオプションで詳細な状況を見てみました。

$ curl -v -s -o /dev/null --data-binary @big.txt -w @format2.txt http://localhost/~wagavulin/test2.cgi
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 80 (#0)
> POST /~wagavulin/test2.cgi HTTP/1.1
> Host: localhost
> User-Agent: curl/7.45.0
> Accept: */*
> Content-Length: 61931520
> Content-Type: application/x-www-form-urlencoded
> Expect: 100-continue
> 
< HTTP/1.1 100 Continue
} [16384 bytes data]
* We are completely uploaded and fine
< HTTP/1.1 200 OK
以下略

すると、リクエストヘッダに"Expect: 100-continue"があり、サーバもまず最初に100を返しています。さらにその後200を返しているのが分かります。このExpectヘッダはサーバがPOSTを受け入れ可能かをチェックするために使われています。仮にサーバがそのURL+パラメータではPOSTを受けれ入れない場合、大量のデータをPOSTしてからようやくエラーが返ってくるのでは無駄というものです。そこで、クライアントはリクエストに"Expect 100-continue"を入れます。サーバは、もし受け入れ可能であれば100を返し、そうでなければエラー(417)を返します。クライアントは100が返ったのを確認してからリクエストボディの送信を始めるようです。

curlはこの"100 Continue"のレスポンスが来た時間を持ってstarttransferとしており、そのため小さな値になっているのかもしれません。

Expectヘッダを抑制する

この予想の確認のため、Expectヘッダを使わないようにして試してみます。curlでは-Hオプションを使ってヘッダを指定することができますが、-H "Expect:"のようにするとヘッダ出力を抑制することができます。

$ curl -v -H "Expect:" -s -o /dev/null --data-binary @big.txt -w @format.txt http://localhost/~wagavulin/test2.cgi
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 80 (#0)
> POST /~wagavulin/test2.cgi HTTP/1.1
> Host: localhost
> User-Agent: curl/7.45.0
> Accept: */*
> Content-Length: 61931520
> Content-Type: application/x-www-form-urlencoded
> 
} [16384 bytes data]
* We are completely uploaded and fine
< HTTP/1.1 200 OK
< Date: Sat, 21 Nov 2015 11:31:14 GMT
< Server: Apache/2.4.7 (Ubuntu)
< Content-Length: 18
< Content-Type: text/plain
< 
{ [18 bytes data]
* Connection #0 to host localhost left intact
http_code           200
size_upload         61931520
speed_upload        24889268.000
size_download       18
speed_download      7.000
time_pretransfer    0.001
time_starttransfer  0.001
time_total          2.488

確かにExpectヘッダと100のレスポンスはなくなりました。が、やはりstarttransferは小さな値のままです。予想は外れてしまいました。

curlのソースを覗く

starttransferについて色々検索もしたのですが、あまり有益な情報が見つかりませでした。仕方ないのでソースを見てみることにしました。

すると、starttransferの時間を記録する箇所(Curl_pgrsTime(data, STARTTRANSFER);と書かれた箇所)、がlib/transfer.cに2箇所ありました。それぞれreadwrite_upload()関数とreadwrite_data()関数です。この2つのいずれかが最初に呼ばれた段階でstarttransferの値が決まるようです。

また、readwrite_upload()関数のコメントに"Send data to upload to the server, when the socket is writable."と書かれていました。つまり、starttransferはレスポンスを受け取るときだけでなく、データをアップロードし始めるタイミングでも記録されるようです。

そして、このreadwrite_upload()はどうやらPOSTするファイルのサイズが1025バイト以上のときに呼ばれるらしいことが分かりました。

再び実験

ということで、アップロードするファイルのサイズを変えて実験してみます。1024.txt, 1025.txtというファイルを作りました。名前のとおり、サイズはそれぞれ1024, 1025バイトです。これを先ほどのtest2.cgiにPOSTします。すると思ったとおり、1024バイトか1025バイトかによってtime_starttransferの値が大きく変わりました。

$ curl -s -o /dev/null --data-binary @1024.txt -w @format.txt http://localhost/~wagavulin/test2.cgi
http_code           200
size_upload         1024
speed_upload        474.000
size_download       14
speed_download      6.000
time_pretransfer    0.005
time_starttransfer  2.159
time_total          2.160

$ curl -s -o /dev/null --data-binary @1025.txt -w @format.txt http://localhost/~wagavulin/test2.cgi
http_code           200
size_upload         1025
speed_upload        473.000
size_download       14
speed_download      6.000
time_pretransfer    0.002
time_starttransfer  0.004
time_total          2.165

time_starttransferの仕様

ということで結論です。curlのtime_starttransferの値は以下のようになっているようです。

  • GETのようにリクエストのボディがない => レスポンスを受け取り始めた時間
  • POSTのようにリクエストのボディがある
    • 指定したファイルのサイズが1024バイト以下 => レスポンスを受け取り始めた時間
    • 指定したファイルのサイズが1025バイト以上 => リクエストボディを送り始めた時間

なぜこのような仕様になっているのでしょうか?何か意図があるのかもしれませんが、個人的には分かりにくいし使いにくいと思います。POSTのサイズが大きい場合はstarttransferはほぼ0に近い値になるため、あまり意味がないように思います。

速度を使って計算してみる

とここで、speed_uploadとspeed_downloadというパラメータあるのを思い出しました。マニュアルによれば、アップロード/ダウンロードにかかった平均時間(bytes/second)のようです。アップロード/ダウンロードサイズも分かっているので、割り算をすれば時間も分かるはずです。

実験のため今度は以下のようなCGIスクリプトを用意しました。

test3.cgi

#!/usr/bin/ruby

def recv_file
  bytes = 0
  while buf = $stdin.read(4096)
    bytes += buf.length
  end
  return bytes
end

def send_data(kbytes)
  str_1k = 'a' * 1023 + "\n"
  for i in 1..kbytes
    print str_1k
  end
end

bytes = recv_file
sleep 2
print "Content-Type: text/plain\n\n"

send_data(1024 * 100)

send_data()は、指定したサイズ(KBytes)のデータを出力します(つまりレスポンスボディとして返します)。recv_fileでリクエストをすべて受け取り、その後2秒sleepした後、100MBのデータをレスポンスとして返します。

これと先ほどのbig.txtを使って実験です。

$ curl -s -o /dev/null --data-binary @big.txt -w @format.txt http://localhost/~wagavulin/test3.cgi
http_code           200
size_upload         61931520
speed_upload        20495351.000
size_download       104857600
speed_download      34701123.000
time_pretransfer    0.001
time_starttransfer  0.003
time_total          3.022

計算してみると以下のようになります。

  • アップロード時間: 61931520 / 20495351.000 = 3.0217 (秒)
  • ダウンロード時間: 104857600 / 34701123.000 = 3.0217 (秒)

どういうわけかどちらもほぼ同じ値で、かつtime_totalとほぼ同じです。なぜこうなるのか深く追ってはいませんが、ともかくこれではパフォーマンス測定には使えません。さて困りました。

ということで次回に続く。