AWS CLIにオレオレwaitコマンドを追加する

aws
aws cliには API 呼び出し後、特定のステータスになるまでポーリングする wait コマンドがあります。
例えば ec2 インスタンスを起動する API を呼び出し後、インスタンスの起動が完了するまで待つには $ aws ec2 wait instance-running --instance-ids xxx のようにします。

この wait 機能が実装されるまでは

#!/bin/bash
instance_id=$(aws ec2 run-instances –image-id ami-12345 \
  --query Reservations[].Instances[].InstanceId \
  --output text)
instance_state=$(aws ec2 describe-instances –instance-ids $instance_id
  --query 'Reservations[].Instances[].State.Name')
while [ "$instance_state" != "running" ]
do
  sleep 1
  instance_state=$(aws ec2 describe-instances –instance-ids $instance_id \ --query 'Reservations[].Instances[].State.Name')
done

というように、ポーリング処理を自前で実装しなければいけ ませんでした(例外処理も真面目にやるとさらにごちゃごちゃする)。

この wait 系コマンドを独自追加する方法をメモ。

cloudformationでスタック構築完了を待つ

例として cloudformation でスタックの作成APIをたたいたあと
(crete-stack)、スタックの構築が完了するまでポーリングするコマンド $ aws cloudformation wait stack-completed を実装してみましょう。

AWS CLI で wait を使わず cloudformation のスタックを構築

wait を使わずに cloudformation のスタックを構築するには

  • $ aws cloudformation create-stack --stack [STACKNAME] でスタックの作製命令をし
  • $ aws cloudformation describe-stacks --stack-name [STACKNAME] で StackStatus が CREATE_COMPLETE になるかチェックすることになるかと思います。
$ aws cloudformation create-stack --stack-name SampleStack --template-body file://SNSToSQS.template \
  --parameters ParameterKey=MyPublishUserPassword,ParameterValue=password \
  ParameterKey=MyQueueUserPassword,ParameterValue=password \
  --capabilities CAPABILITY_IAM
{
    "StackId": "arn:aws:cloudformation:us-east-1:01234:stack/SampleStack/30811fd0-1215-11e5-aacf-50018ffe9e62"
}

$ aws cloudformation describe-stacks --stack-name SampleStack
{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:ap-northeast-1:01234:stack/SampleStack/db83f660-3753-11e5-a35a-50fa594fb836",
            "Description": "...",
            "Parameters": [
                {
                    "ParameterValue": "****",
                    "ParameterKey": "MyPublishUserPassword"
                },
                {
                    "ParameterValue": "****",
                    "ParameterKey": "MyQueueUserPassword"
                }
            ],
            "Tags": [],
            "CreationTime": "2015-08-01T07:15:00.948Z",
            "Capabilities": [
                "CAPABILITY_IAM"
            ],
            "StackName": "SampleStack",
            "NotificationARNs": [],
            "StackStatus": "CREATE_IN_PROGRESS",
            "DisableRollback": false
        }
    ]
}
$ aws cloudformation describe-stacks --stack-name SampleStack
{
    "Stacks": [
        {
            ...,
            "StackName": "SampleStack",
            "StackStatus": "CREATE_COMPLETE",
            ...,
        }
    ]
}

AWS CLI wait に落としこむ

では、これを AWS CLI の wait コマンドに落としこんでみましょう。

ポイント AWS CLI はデータドリブンであるということです。
API のリクエスト/レスポンスは JSON ファイル(データ)でモデル定義します。

wait コマンドも例外ではなく

  • wait のコマンド名
  • ポーリング時にステータスチェックするコマンド
  • ポーリング間隔

などを JSON で定義します。

天下り的になりますが、cloudformation の stack-completed wait 用の JSON モデルは次のようになります。

{
  "version": 2,
  "waiters": {
    "StackCompleted": {
      "operation": "DescribeStacks",
      "delay": 30,
      "maxAttempts": 30,
      "acceptors": [
        {
          "expected": 200,
          "argument": "Stacks[].StackStatus",
          "expected": "CREATE_COMPLETE",
          "state": "success",
          "matcher": "pathAll"
        }
      ]
    }
  }
}

重要な箇所をかいつまんで説明します。

StackCompleted が wait のコマンド名。
AWS Web API のメソッド名は CamelCase なので、モデルでもそのようにします。
AWS CLI から使うときは FooBar は foo_bar のように変換されるので、 StackCompletedstack_completed となります。

"operation": "DescribeStacks" はポーリング時に問い合わせる API です。

"delay": 30 はポーリング間隔です。単位は秒です。

"maxAttempts": 30 はポーリングのリトライ数です。
この数を超えても適切な状態に遷移しなかった場合、ステータスコード 255 で AWS CLI のプロセスが終了します。

"acceptors" のブロックは "operation"(DescribeStacks) のレスポンスに対する処理を記述します。
今回のケースでは StackStatus が CREATE_COMPLETE に遷移した時に wait 完了と判断します。
より具体的には API DescribeStacks に対するレスポンスボディー

{
    "Stacks": [
        {
            ...,
            "StackName": "SampleStack",
            "StackStatus": "CREATE_COMPLETE",
        }
    ]
}

に対して JMESPATHStacks[].StackStatus というようにステータス を抽出し、期待値 CREATE_COMPLETE と一致するか判定します。
JMESPATH の expressionargument で、期待値を expected で記述します。

"state": "success" により、条件が満たされた時は正常終了します。

"expected": 200 は HTTP レスポンスステータスです。

この JSON ファイルを botocore の cloudformation 用モデル定義ディレクトリ botocore/data/cloudformation/2010-05-15waiters-2.json という名前で新規作成します。
(ec2 などと異なり cloudformation には wait が未定義なので新規ファイルとして作成します)

結果的に、以下の様なファイル構成になります。

botocore/data/cloudformation/
└── 2010-05-15
    ├── paginators-1.json
    ├── service-2.json
    └── waiters-2.json # <- NEW!

botocore のディレクトリは SHELL から以下のコマンドで確認できます。

$ python -c 'import botocore;print (botocore.__file__)'
/Users/jsmith/venv/lib/python2.7/site-packages/botocore/__init__.pyc

オレオレ wait を使ってみる

それでは追加した wait コマンドを実際に使ってみましょう。

ヘルプメッセージの表示

まずはヘルプコマンドを確認

$ aws cloudformation wait help
WAIT()                                                                  WAIT()

NAME
       wait -

DESCRIPTION
       Wait until a particular condition is satisfied.

AVAILABLE COMMANDS
       o stack-completed

                                                                        WAIT()

$ aws cloudformation wait stack-completed help
STACK-COMPLETED()                                            STACK-COMPLETED()

NAME
       stack-completed -

DESCRIPTION
       Wait  until JMESPath query Stacks[].StackStatus returns CREATE_COMPLETE
       for all elements when polling with describe-stacks. It will poll  every
       30  seconds  until  a successful state has been reached. This will exit
       with a return code of 255 after 30 failed checks.

SYNOPSIS
            stack-completed
          [--stack-name <value>]
          [--cli-input-json <value>]
          [--starting-token <value>]
          [--max-items <value>]
          [--generate-cli-skeleton]

OPTIONS
       --stack-name (string)
          The name or the unique stack ID that is associated with  the  stack,
          which are not always interchangeable:
       ...

ヘルプは問題なさそうです。

stack-completed は内部的には action で定義したように describe-stacks を呼び出しているだけなので describe-stacks の引数(--stack-name)がそのまま使えます。

スタックの作製

次の実際にスタックを作成して、wait コマンドで構築完了を待ちます。
SNS/SQS を構築する次のテンプレートを利用します。

$ aws cloudformation create-stack --stack-name SampleStack --template-body file://SNSToSQS.template \
  --parameters ParameterKey=MyPublishUserPassword,ParameterValue=password \
  ParameterKey=MyQueueUserPassword,ParameterValue=password \
  --capabilities CAPABILITY_IAM
$ aws cloudformation wait stack-completed --stack-name SampleStack --debug

wait 実行時に --debug オプションをつけると delay で指定した間隔で DescribeStacks を実行しているのがよく分かるかと思います。

エラー処理の強化

--stack-name に存在しないスタック名を指定すると、次のようにエラーが発生します。

$ aws cloudformation wait stack-completed --stack-name NXStackName

Waiter StackCompleted failed: Unexpected error encountered.

--debug オプションをつけてレスポンスを確認すると、

<ErrorResponse xmlns="http://cloudformation.amazonaws.com/doc/2010-05-15/">
  <Error>
    <Type>Sender</Type>
    <Code>ValidationError</Code>
    <Message>Stack with id SampleStack2 does not exist</Message>
  </Error>
  <RequestId>12345</RequestId>
</ErrorResponse>

というようなレスポンスが返ってきており、先ほど作成したモデルではこのようなレスポンスを考慮していません。

JSON ファイルの acceptorsValidationError 用のモデルを追加します。

{
  "version": 2,
  "waiters": {
    "StackCompleted": {
      "delay": 30,
      "operation": "DescribeStacks",
      "maxAttempts": 30,
      "acceptors": [
        {
          "expected": 200,
          "argument": "Stacks[].StackStatus",
          "state": "success",
          "expected": "CREATE_COMPLETE",
          "matcher": "pathAll"
        },
        {
          "matcher": "error",
          "expected": "ValidationError",
          "state": "success"
        }
      ]
    }
  }
}

"matcher": "error" でレスポンスの Error ブロックを抽出します。

"expected" : "ValidationError" でエラーコードを突き合わせます。

このように JSON を書き換えた上で再度存在しないスタック名を wait コマンドに食わせると、今度は正常に処理されました。

$ aws cloudformation wait stack-completed --stack-name NXStackName
$ echo $?
0

Stack のステータスはロールバック系など様々なステータスが存在するので、本運用で使うなら例外系処理を中心にもっとまじめに条件を書かないといけません。

boto3 から使ってみる

今回ハック下 botocore の JSONモデルは

でも利用されています。

boto3 は JSON モデルだけでなく botocore パッケージそのものが共有されているので、カスタマイズした botocore を boto3 からも呼び出してみましょう。

import boto3
import botocore

StackName = 'TEST'
Template = 'https://s3.amazonaws.com/cloudformation-templates-us-east-1/SNSToSQS.template'

client = boto3.client('cloudformation')
client.create_stack(
    StackName=StackName,
    TemplateURL=Template,
    Parameters=[
        {
            'ParameterKey': 'MyPublishUserPassword',
            'ParameterValue': 'password'
        },
        {
            'ParameterKey': 'MyQueueUserPassword',
            'ParameterValue': 'password'
        }
    ],
    Capabilities=['CAPABILITY_IAM'],
    )

waiter = client.get_waiter('stack_completed') # your own waiter
waiter.wait(StackName=StackName)

print 'do something'

まとめ

AWS CLI の wait 系コマンドはそれほど熱心にはコマンド追加されていないので、業務で必要なものは botocore をいじる(fork)ことで独自に拡張できることを理解いただけたかと思います。

References

Tagged with: , , ,
Posted in aws, python

bashでコマンドライン引数にファイルの中身を渡す

標準出力先としてファイル名を指定するのではなく、ファイルの中身(body)を引数として渡すにはどうすればよいのか?

実験用スクリプト

確認のため、次の簡易的なシェルスクリプトで各コマンドライン引数を表示させる。

#!/bin/bash
# test.sh — just dump arguments

for i; do
  echo $i;
done

入力ファイルとしては次のように改行やスペースを含んだ JSON ファイルを渡す

$ cat test.json
{
  "firstName": "John",
  "age": 25,
  "address": {
    "state": "NY",
    "postalCode": "10021-3100"
  }
}

実行コマンド

結果論としては、以下のように printfcat をコンボすればよい

$ ./test.sh foo $(printf '%s' $(cat test.json)) bar
foo
{"firstName":"John","age":25,"address":{"state":"NY","postalCode":"10021-3100"}}
bar

解説

ポイントとなるのは次の箇所

$(printf '%s' $(cat test.json)) 

ここを単純に $(cat test.json) や bash 方言のショートカットである $(< test.json) とすると、改行・スペースを引数の区切りとしてみなされてしまう

$ ./test.sh foo $(cat test.json) bar
foo
{
"firstName":
"John",
"age":
25,
"address":
{
"state":
"NY",
"postalCode":
"10021-3100"
}
}
bar

この問題を回避するために、ファイルの中身全体を1文字列として扱うために printf '%s' でラップさせるというわけ。

Reference

Tagged with: , , ,
Posted in linux

HTTP ベンチマークツール wrk についてメモ

モダンな HTTP ベンチマークツール wkr の簡単な使い方についてメモ。

wrk の特徴は以下。

  • C で書かれている
  • マルチコア CPU を 活かした高負荷をかけられる
  • スレッドと epoll/kqueue のイベントドリブンを活用して負荷をスケールさせる(NOTICE ファイルを読むと Redis Event Library(ae event loop) を拝借しているようです)
  • Lua スクリプトで HTTP クライアントの処理や実行結果のレポートをカスタマイズできる

Installing wrk in CentOS 6

まずはビルドに必要なパッケージをインストールします。

$ sudo yum install git
$ sudo yum groupinstall 'Development Tools'
$ sudo yum install openssl-devel

openssl-devel をインストールしていないと、make 時に以下のようなエラーが発生します。

In file included from src/wrk.c:3:
src/wrk.h:11:25: error: openssl/ssl.h: No such file or directory
src/wrk.h:12:25: error: openssl/err.h: No such file or directory

次にソースコードから wrk をビルドします。

$ git clone https://github.com/wg/wrk.git
Initialized empty Git repository in /root/wrk/.git/
remote: Counting objects: 792, done.
remote: Total 792 (delta 0), reused 0 (delta 0), pack-reused 792
Receiving objects: 100% (792/792), 1.12 MiB | 147 KiB/s, done.
Resolving deltas: 100% (430/430), done.
$ cd wrk/
$ make
...
$ ls -1
CHANGES
LICENSE
Makefile
NOTICE
README
SCRIPTING
deps
obj
scripts
src
wrk <- Compiled Binary

Usage

$ ./wrk --help
Usage: wrk<options> <url>
  Options:
    -c, --connections <N>  Connections to keep open
    -d, --duration    <T>  Duration of test
    -t, --threads     <N>  Number of threads to use

    -s, --script      <S>  Load Lua script file
    -H, --header      <H>  Add header to request
        --latency          Print latency statistics
        --timeout     <T>  Socket/request timeout
    -v, --version          Print version details

  Numeric arguments may include a SI unit (1k, 1M, 1G)
  Time arguments may include a time unit (2s, 2m, 2h)
$ ./wrk -t12 -c400 -d30s -H"User-Agent: MyBrowser" --latency --timeout 5 http://127.0.0.1:80/index.html
Running 30s test @ http://127.0.0.1:80/index.html
  12 threads and 400 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   218.81ms  474.65ms   4.64s    85.41%
    Req/Sec     1.20k     1.09k    7.99k    83.74%
  Latency Distribution
     50%  247.00us
     75%   12.20ms
     90%  999.45ms
     99%    1.81s
  249092 requests in 30.10s, 201.67MB read
  Socket errors: connect 0, read 28, write 0, timeout 25
Requests/sec:   8275.51
Transfer/sec:      6.70MB

上の例では

  • 12 スレッド(-t12)
  • 同時 400 コネクション(-c400)
  • 負荷は 30 秒かける(-d30s)
  • リクエストヘッダー “User-Agent: MyBrowser” を送信(-H"User-Agent: MyBrowser")
  • 通信は5秒でタイムアウト(--timeout 5)
  • ベンチマーク結果にレイテンシの統計情報も表示(--Latency)
  • リクエスト先は http://127.0.0.1:80/index.html

というようになっています。

wrk scripting

wrk は Lua スクリプトで

  • setup フェーズ(スレッド単位のカスタマイズ)
  • running フェーズ(リクエスト単位のカスタマイズ)
  • done フェーズ(レポート処理)

の各フェーズで処理やグローバル変数をカスタマイズできます。

実行環境はスレッドごとに独立しています

Lua スクリプトの詳細は次のファイルを参照

https://github.com/wg/wrk/blob/master/SCRIPTING

Lua スクリプト読み込み例

$ wrk -s /path/to/program.lua URL

というように -s オプションで指定します。
複数指定した場合、一番最後のスクリプトが利用される模様。

サンプルスクリプトの説明

ソースコードの wrk/scripts 以下にサンプルコードが転がっているので、各スクリプトを手短に解説します。
特に次の2つが参考になるかと思います。

  • post.lua
  • setup.lua

addr.lua

wrk.lookup(POSIX getaddrinfo に相当) を setup フェーズで実行し、接続先ホストの IP アドレスをランダムに決定する例
ホストに複数の IP が割り当てられていないと意味を成さない。

auth.lua

running フェーズで response 変数を使い、レスポンスヘッダーの特定のフィールドを次回以降のリクエストで活用する例。

counter.lua

running フェーズで request 関数を使い、リクエストごとにカウンターをインクレメントしてリクエストヘッダーに渡す例

delay.lua

running フェーズで delay 関数を使い、リクエスト前にランダムに遅延を発生させる例。

pipeline.lua

running フェーズで init 関数を使い、HTTP パイプラインする例。

https://en.wikipedia.org/wiki/HTTP_pipelining

post.lua

連想配列(Lua で言うところの Table) を書き換えて、HTTPリクエストのリクエストに含まれる

  • Method
  • Body
  • Request Header

を書き換える例。
コマンドラインからは GET メソッドで URL しか渡せないので、実試験では重宝します。

report.lua

done フェーズで done 関数を使い、実行結果をカスタマイズする例。

setup.lua

一番参考にすべき例。
setup/running/done のすべてのフェーズをカスタマイズ。
スレッドごとに番号を振り、running フェーズでリクエスト数をカウントし、done フェーズでスレッド単位の総リクエスト数を出力。

$ ./wrk -s ./scripts/setup.lua -c3 -d 3s -t 3 http://localhost:80/index.html
thread 1 created
thread 2 created
thread 3 created
Running 3s test @ http://localhost:80/index.html
  3 threads and 3 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     2.47ms    4.19ms  28.29ms   84.44%
    Req/Sec     2.94k   618.72     4.64k    65.56%
  26510 requests in 3.04s, 21.46MB read
Requests/sec:   8732.85
Transfer/sec:      7.07MB
thread 1 made 9130 requests and got 9128 responses
thread 2 made 8930 requests and got 8929 responses
thread 3 made 8454 requests and got 8453 responses

stop.lua

running フェーズで stop 関数を使い、リクエスト数がしきい値を超えたら終了させる例。

補足

負荷試験中の名前解決について

The setup phase begins after the target IP address has been resolved and all threads have been initialized but not yet started.
https://github.com/wg/wrk/blob/master/SCRIPTING

とあるように running フェーズでは名前の再解決をしない。
負荷とともに IP が変わるようなケースでは注意をすること。

listen(2) backlog

To handle the initial connection burst the server’s listen(2) backlog should be greater than the number of concurrent connections being tested.
https://github.com/wg/wrk/blob/master/README

TODO

References

Tagged with: , , ,
Posted in middleware

AWS Machine LearningのSchemaを自動生成する

aws

やりたいこと

AWS Machine Learning では学習・評価データのスキーマ定義が必要。

http://docs.aws.amazon.com/machine-learning/latest/dg/creating_datasources.html#creating-a-data-schema-for-amazon-ml

マネージメントコンソールからデータセットを指定する時は、サーバーサイドでスキーマを推測してくれるがクライアントからAPIと叩く時は、スキーマは自分で用意しないといけない。

このスキーマ作成の作業を簡略化するために、AWS 中の人が作成したスキーマ推測ツールを利用してみる。

スキーマファイルの定義

まずはスキーマファイルのサンプルから

{
  "version": "1.0",
  "dataFormat": "CSV",
  "rowId": "customerId",
  "dataFileContainsHeader": true,
  "attributes": [
    {
      "attributeName": "customerId",
      "attributeType": "NUMERIC"
    },
    ...
  ],
  "targetAttributeName": "loan"
}

重要な属性は以下

rowId

サロゲートキー
このデータは学習には利用されず、予測結果も含めてリファレンス目的で利用。

dataFileContainsHeader

元データの1行目にヘッダー行が含まれていると true

targetAttributeName

目的変数

attributeType

データの各カラムのデータ型
カラム名とそのデータ型(NUMERIC/CATEGORICAL/TEXT/BINARY)で構成される。

詳細は次のURLを参照

http://docs.aws.amazon.com/machine-learning/latest/dg/creating_datasources.html#creating-a-data-schema-for-amazon-ml

最終的にはこの JSON 形式のスキーマをいい感じで自動生成したい。

スキーマを自動生成してみる

github の次のレポジトリにある ml-tools-python ディレクトリにある guess_schema.py でスキーマを自動生成させる。

https://github.com/awslabs/machine-learning-samples

にある ml-tools-python ディレクトリの guess_schema.py を使います。

ソースコードのクローン

$ git clone https://github.com/awslabs/machine-learning-samples.git
$ cd machine-learning-samples/ml-tools-python

Python の AWS SDK である boto ライブラリが必要なので、インストールされていなければ $ pip install boto すること。

基本的な実行方法

$ ./guess_schema.py

Schema guessing utility for Amazon Machine Learning.
Looks at the beginning of a CSV file and makes a guess as to what
data type each column is.  Then produces a valid JSON schema that
can be passed to create_data_source_from_s3 method.

Usage:
    python guess_schema.py data_file.csv [target_variable_name] > data_file.csv.schema

If specified, target_variable_name should match one of the variables
in the file's header.
$ ./guess_schema.py ata_file.csv [target_variable_name] > data_file.csv.schema

数カラムであれば、JSON を手書きするのも手間ではないだろうが、カラムがたくさんあるときは、このようにザクっとスキーマ生成してくれるツールは重宝する。

ヘッダー行があるとき

1行目にヘッダー行があるシンプルなデータで実験

$ cat sample.csv
customerId,jobId,education,housing,loan,campaign,duration
1,3,basic.4y,no,no,1,261,0
2,1,high.school,no,no,22,149,0
3,1,high.school,yes,no,65,226,1
4,2,basic.6y,no,no,1,151,0
$ ./guess_schema.py sample.csv duration
{
  "excludedAttributeNames": [],
  "version": "1.0",
  "dataFormat": "CSV",
  "rowId": null,
  "dataFileContainsHeader": true,
  "attributes": [
    {
      "attributeName": "customerId",
      "attributeType": "NUMERIC"
    },
    {
      "attributeName": "jobId",
      "attributeType": "NUMERIC"
    },
    {
      "attributeName": "education",
      "attributeType": "CATEGORICAL"
    },
    {
      "attributeName": "housing",
      "attributeType": "CATEGORICAL"
    },
    {
      "attributeName": "loan",
      "attributeType": "CATEGORICAL"
    },
    {
      "attributeName": "campaign",
      "attributeType": "NUMERIC"
    },
    {
      "attributeName": "duration",
      "attributeType": "NUMERIC"
    }
  ],
  "targetAttributeName": "duration"
}

attributeType がいい感じに guess されている。
dataFileContainsHeader は当然のごとく true となっている。
なお、存在しない attributeName を目的変数に指定するとエラーになる。

ヘッダー行がないとき

ヘッダー行がないと判断されると、 "dataFileContainsHeader": false にし、 attributeName には Var数字 が 自動的に設定される。
カラム数が多い時は Var01, …, Var25 というように必要な桁数を確保して0埋めされる。

※元のプログラムがビミョーにバグっているので PR の修正を適用して実行すること https://github.com/awslabs/machine-learning-samples/pull/8

$ cat sample_no_header.csv
1,3,basic.4y,no,1
2,1,high.school,yes,22
3,1,high.school,no,65
4,2,basic.6y,no,1
$ ./guess_schema.py sample_no_header.csv Var5
{
  "excludedAttributeNames": [],
  "version": "1.0",
  "dataFormat": "CSV",
  "rowId": null,
  "dataFileContainsHeader": false,
  "attributes": [
    {
      "attributeName": "Var1",
      "attributeType": "NUMERIC"
    },
    {
      "attributeName": "Var2",
      "attributeType": "NUMERIC"
    },
    {
      "attributeName": "Var3",
      "attributeType": "CATEGORICAL"
    },
    {
      "attributeName": "Var4",
      "attributeType": "CATEGORICAL"
    },
    {
      "attributeName": "Var5",
      "attributeType": "NUMERIC"
    }
  ],
  "targetAttributeName": "Var5"
}

痒いところに手が届いていない点

  • ヘッダー行の有り・無しは自動判定で、判定条件が超テキトー
  • rowId を指定するオプションが存在しない
  • 簡単にインストールできない

などなど。

Tagged with: ,
Posted in aws

Redisのレイテンシを計測

やりたいこと

Redis に TCP でコマンドを投げた時

DNS Lookup -> Initial Connection -> Request Sent -> Waiting(TTFB) -> Content Download

というような流れで処理される。
この一連の流れで

Redis の内部処理にかかった Waiting(TTFB) を除く通信の時間を計測する方法をメモ。

通信のレイテンシ

Redis には疎通確認向けのコマンド PING がある。
クライアントがこのコマンドを実行すると、サーバーは PONG を返すだけ。

このコマンドに対してサーバー(Redis)の処理は極めて軽微なため、PING を実行してから PONG がかえってくるまでの時間をレイテンシとみなして計測するのが次のコマンド。

$ redis-cli --latency -h HOST -p PORT # Ctrl-C で計測を止める
min: 0, max: 2, avg: 0.18 (103 samples)

上の例で言えば

  • 103 回 PING を実行し(samples)
  • レイテンシの 最短(min)/最長(max)/平均(avg) がそれぞれ 0ms/2ms/0.18ms

となる。

AWS ElastiCache では AZ をまたぐと同一 AZ に比べてレイテンシが10倍だったりするので、レイテンシを何としても切り詰めたいときは

  •  1コマンド1リクエストとせず Mass Insert を利用してリクエスト回数を減らす
  • マルチ AZ Redis クラスターを諦めて 同一 AZ の Redis に接続

などの対応が必要。

AWS の外から ElastiCache と通信すると、同一AZ/別AZ/AWS外のレイテンシーの違いがよくわかると思ったのだが、セキュリティー上、EC2 からしかアクセス出来ないようになっていたので計測できず。

Please note that IP-range based access control is currently not enabled for Cache Clusters. All clients to a Cache Cluster must be within the EC2 network, and authorized via security groups as described above
http://aws.amazon.com/elasticache/faqs/

参照

Redis の内部処理

コマンド実行単位では、今のところは処理時間を知るすべは提供されていないが、処理時間がしきい値を超えたコマンドをロギングする仕組みはある。

SLOWLOG コマンドを利用

SLOWLOG を使うと、処理に一定時間以上かかったコマンドの実行ログ詳細を確認できる。

http://redis.io/commands/slowlog

デフォルトでしきい値は 10000 ms に設定されている。

> config get slowlog-log-slower-than
1) "slowlog-log-slower-than"
2) "1000"

LATENCY コマンドを利用

Redis 2.8.13 から処理の遅かったコマンドの分布・傾向を知るための latency monitoring framework が追加されている。

デフォルトでは無効化されているので

CONFIG SET latency-monitor-threshold 100

などで有効化すること。

参照

Tagged with: ,
Posted in middleware

Amazon Machine LearningのチュートリアルをAWS CLIから実行してみる

Summary

“Amazon Machine Learning Developer Guide” には “Tutorial: Using Amazon ML to Predict Responses to a Marketing Offer” というこのサービスの初心者向けのチュートリアルが含まれている。

Tutorial: Using Amazon ML to Predict Responses to a Marketing Offer
http://docs.aws.amazon.com/machine-learning/latest/mlconcepts/mlconcepts.html

チュートリアルはマネージドコンソールから操作しているので、将来のスクリプト化を見据えて AWS CLI から操作してみる。

チュートリアルの流れ

University of California, Irvine の Machine Learning Repository にある Bank Marketing Data Set を利用して、顧客が定期預金を契約するかどうかの 2 値分類を行う。

作業の流れは以下

  • Step 1: Download, Edit, and Upload Data
  • Step 2: Create a Datasource
  • Step 3: Create an ML Model
  • Step 4: Review the ML Model’s Performance and Set a Score Threshold
  • Step 5: Use the ML Model to Generate Batch Predictions
  • Step 6: Clean Up

学習・評価用データとバッチ予測結果は S3 に置く。

Step 0: AWS CLI の設定

2015/06/01 時点では Amazon Marketing Learning(以下 ML) は US East(Northern Virginia)でしか利用できないため、デフォルトのリージョンを一時的に変更しておく。

$ cat ~/.aws/config
[default]
output=json
region=us-east-1

Step 1: Download, Edit, and Upload Data

新規に S3 バケットを用意し、学習データ(banking.csv)と評価データ(banking-batch.csv)をアップロードします。

$ BUCKET=b6b6
$ aws s3 mb s3://$BUCKET
make_bucket: s3://b6b6/
$ aws s3 cp banking.csv s3://$BUCKET/
upload: ./banking.csv to s3://b6b6/banking.csv
$ aws s3 cp banking-batch.csv s3://$BUCKET/
upload: ./banking-batch.csv to s3://b6b6/banking-batch.csv

AWS ML では以下の S3 操作を行います

  • ML から S3 にある学習データの GetObject
  • ML から S3 にある評価データの GetObject
  • ML S3 にバッチ予測結果データをバケットのルート直下に PutObject

このためのバケットポリシーを設定します

詳細は次の URL を参照 http://docs.aws.amazon.com/machine-learning/latest/dg/reference.html

$ cat ml_bucket_policy.json
{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Sid": "AmazonML_s3:ListBucket",
			"Effect": "Allow",
			"Principal": {
				"Service": "machinelearning.amazonaws.com"
			},
			"Action": "s3:ListBucket",
			"Resource": "arn:aws:s3:::b6b6",
			"Condition": {
				"StringLike": {
					"s3:prefix": [
						"*",
						"banking.csv*",
						"banking-batch.csv*"
					]
				}
			}
		},
		{
			"Sid": "AmazonML_s3:GetObject",
			"Effect": "Allow",
			"Principal": {
				"Service": "machinelearning.amazonaws.com"
			},
			"Action": "s3:GetObject",
			"Resource": [
				"arn:aws:s3:::b6b6/*",
				"arn:aws:s3:::b6b6/banking.csv*",
				"arn:aws:s3:::b6b6/banking-batch.csv*"
			]
		},
		{
			"Sid": "AmazonML_s3:PutObject",
			"Effect": "Allow",
			"Principal": {
				"Service": "machinelearning.amazonaws.com"
			},
			"Action": "s3:PutObject",
			"Resource": "arn:aws:s3:::b6b6/*"
		}
	]
}
$ aws s3api put-bucket-policy --bucket $BUCKET --policy file://ml_bucket_policy.json

Step 2: Create a Datasource

次に S3 にある学習データを ML データソースに設定します。

API は create-data-source-from-s3

API get-data-source でレスポンスの StatusMessage からステータス確認し、 COMPLETED になれば OK

$ DATA_SOURCE=ds-test
$ cat data-spec.json
{
  "DataLocationS3":"s3://b6b6/banking.csv",
  "DataSchema" : "{\"version\":\"1.0\",\"rowId\":null,\"rowWeight\":null,\"targetAttributeName\":\"y\",\"dataFormat\":\"CSV\",\"dataFileContainsHeader\":true,\"attributes\":[{\"attributeName\":\"age\",\"attributeType\":\"NUMERIC\"},{\"attributeName\":\"job\",\"attributeType\":\"CATEGORICAL\"},{\"attributeName\":\"marital\",\"attributeType\":\"CATEGORICAL\"},{\"attributeName\":\"education\",\"attributeType\":\"CATEGORICAL\"},{\"attributeName\":\"default\",\"attributeType\":\"CATEGORICAL\"},{\"attributeName\":\"housing\",\"attributeType\":\"CATEGORICAL\"},{\"attributeName\":\"loan\",\"attributeType\":\"CATEGORICAL\"},{\"attributeName\":\"contact\",\"attributeType\":\"CATEGORICAL\"},{\"attributeName\":\"month\",\"attributeType\":\"CATEGORICAL\"},{\"attributeName\":\"day_of_week\",\"attributeType\":\"CATEGORICAL\"},{\"attributeName\":\"duration\",\"attributeType\":\"NUMERIC\"},{\"attributeName\":\"campaign\",\"attributeType\":\"NUMERIC\"},{\"attributeName\":\"pdays\",\"attributeType\":\"NUMERIC\"},{\"attributeName\":\"previous\",\"attributeType\":\"NUMERIC\"},{\"attributeName\":\"poutcome\",\"attributeType\":\"CATEGORICAL\"},{\"attributeName\":\"emp_var_rate\",\"attributeType\":\"NUMERIC\"},{\"attributeName\":\"cons_price_idx\",\"attributeType\":\"NUMERIC\"},{\"attributeName\":\"cons_conf_idx\",\"attributeType\":\"NUMERIC\"},{\"attributeName\":\"euribor3m\",\"attributeType\":\"NUMERIC\"},{\"attributeName\":\"nr_employed\",\"attributeType\":\"NUMERIC\"},{\"attributeName\":\"y\",\"attributeType\":\"BINARY\"}],\"excludedAttributeNames\":[]}"
}
$ aws machinelearning create-data-source-from-s3 \
  --data-source-id $DATA_SOURCE \
  --data-source-name "Banking Data 1" \
  --data-spec file://data-spec.json \
  --compute-statistics
{
    "DataSourceId": "ds-test"
}

$ aws machinelearning get-data-source --data-source-id $DATA_SOURCE
{
    "Status": "INPROGRESS",
    "NumberOfFiles": 1,
    "Name": "Banking Data 1",
    "DataLocationS3": "s3://b6b6/banking.csv",
    "CreatedByIamUser": "arn:aws:iam::1234567890:user/awscli",
    "DataSizeInBytes": 4767561,
    "ComputeStatistics": true,
    "LastUpdatedAt": 1433031333.088,
    "DataSourceId": "ds-test",
    "Message": "Current Step: BASIC_STATISTICS (1/2) 50%",
    "CreatedAt": 1433031254.229
}
$ aws machinelearning get-data-source --data-source-id $DATA_SOURCE --verbose
{
    "Status": "COMPLETED",
    "NumberOfFiles": 1,
    "Name": "Banking Data 1",
    "DataLocationS3": "s3://b6b6/banking.csv",
    "CreatedByIamUser": "arn:aws:iam::1234567890:user/awscli",
    "DataSizeInBytes": 4767561,
    "ComputeStatistics": true,
    "LastUpdatedAt": 1433031580.255,
    "DataSourceId": "ds-test",
    "LogUri": "https://eml-prod-emr.s3.amazonaws.com/1234567890-pr-ml-test/userlog/...",
    "DataSourceSchema": "{\"version\":\"1.0\",\"rowId\":null,\"rowWeight\":null,\"targetAttributeName\":\"y\",\"dataFormat\":\"CSV\",\"dataFileContainsHeader\":true,\"attributes\":[{\"attributeName\":\"age\",\"attributeType\":\"NUMERIC\"},{\"attributeName\":\"job\",\"attributeType\":\"CATEGORICAL\"},{\"attributeName\":\"marital\",\"attributeType\":\"CATEGORICAL\"},{\"attributeName\":\"education\",\"attributeType\":\"CATEGORICAL\"},{\"attributeName\":\"default\",\"attributeType\":\"CATEGORICAL\"},{\"attributeName\":\"housing\",\"attributeType\":\"CATEGORICAL\"},{\"attributeName\":\"loan\",\"attributeType\":\"CATEGORICAL\"},{\"attributeName\":\"contact\",\"attributeType\":\"CATEGORICAL\"},{\"attributeName\":\"month\",\"attributeType\":\"CATEGORICAL\"},{\"attributeName\":\"day_of_week\",\"attributeType\":\"CATEGORICAL\"},{\"attributeName\":\"duration\",\"attributeType\":\"NUMERIC\"},{\"attributeName\":\"campaign\",\"attributeType\":\"NUMERIC\"},{\"attributeName\":\"pdays\",\"attributeType\":\"NUMERIC\"},{\"attributeName\":\"previous\",\"attributeType\":\"NUMERIC\"},{\"attributeName\":\"poutcome\",\"attributeType\":\"CATEGORICAL\"},{\"attributeName\":\"emp_var_rate\",\"attributeType\":\"NUMERIC\"},{\"attributeName\":\"cons_price_idx\",\"attributeType\":\"NUMERIC\"},{\"attributeName\":\"cons_conf_idx\",\"attributeType\":\"NUMERIC\"},{\"attributeName\":\"euribor3m\",\"attributeType\":\"NUMERIC\"},{\"attributeName\":\"nr_employed\",\"attributeType\":\"NUMERIC\"},{\"attributeName\":\"y\",\"attributeType\":\"BINARY\"}],\"excludedAttributeNames\":[]}",
    "CreatedAt": 1433031254.229
}

ログ(LogUri)から内部処理に Elastic MapReduce (EMR) を使っていることがわかる

データのフォーマット指定

API では --data-spec file://data-spec.json で指定するデータ・フォーマットの指定している。

JSON ファイルでは

  • データの場所("DataLocationS3":"s3://b6b6/banking.csv")
  • 目的変数("targetAttributeName":"y")
  • CSV フォーマットであること("dataFormat":"CSV")
  • 各カラムのデータ型("attributeName":"age","attributeType":"NUMERIC"})

などを定義しており、このファイルの作成が面倒。

get-data-source API に --verbose オプションをつけると、データ定義も取得できる。最初は管理画面から設定して、細かいところは用途に応じてあとから修正するのが楽と思われる。

学習データのデータソースには--compute-statisticsフラグを有効にすること

CreateDataSoureFromS3 の API ドキュメントから

The compute statistics for a DataSource. The statistics are generated from the observation data referenced by a DataSource. Amazon ML uses the statistics internally during an MLModel training. This parameter must be set to true if the DataSource needs to be used for MLModel training

http://docs.aws.amazon.com/machine-learning/latest/APIReference/API_CreateDataSourceFromS3.html#API_CreateDataSourceFromS3_RequestSyntax

実際、このフラグを無効にしたデータソースを次のモデル作成のタイミングで指定すると、次の様なエラーが発生する

$ aws machinelearning create-ml-model ...
A client error (InvalidInputException) occurred when calling the CreateMLModel operation: FAILURES (1): Statistics were not requested for the training data source, 'dummy'. CreateMLModel() requests can only use data sources that were created with the 'ComputeStatistics' flag set.

Step 3: Create an ML Model

次に予測モデル設定します。

API は create-ml-model

二値分類なので --ml-model-type "BINARY" とする

API get-ml-model でレスポンスの Status からステータス確認し、StatusCOMPLETED になれば OK

$ ML_MODEL=ml-test
$ aws machinelearning create-ml-model \
  --ml-model-id $ML_MODEL \
  --ml-model-name "Subscription propensity model" \
  --ml-model-type "BINARY" \
  --training-data-source-id  $DATA_SOURCE
{
    "MLModelId": "ml-test"
}

# http://docs.aws.amazon.com/cli/latest/reference/machinelearning/get-ml-model.html
# http://docs.aws.amazon.com/machine-learning/latest/APIReference/API_GetMLModel.html
$ aws machinelearning get-ml-model --ml-model-id $ML_MODEL
{
    "Status": "INPROGRESS",
    "Name": "Subscription propensity model",
    "TrainingParameters": {
        "sgd.l2RegularizationAmount": "1E-6",
        "sgd.maxMLModelSizeInBytes": "100000000",
        "sgd.maxPasses": "10",
        "algorithm": "sgd",
        "sgd.l1RegularizationAmount": "0.0"
    },
    "MLModelType": "BINARY",
    "CreatedByIamUser": "arn:aws:iam::1234567890:user/awscli",
    "EndpointInfo": {
        "PeakRequestsPerSecond": 0,
        "EndpointStatus": "NONE"
    },
    "MLModelId": "ml-test",
    "InputDataLocationS3": "s3://b6b6/banking.csv",
    "LastUpdatedAt": 1433031823.1,
    "TrainingDataSourceId": "ds-test",
    "CreatedAt": 1433031820.012
}
$ aws machinelearning get-ml-model --ml-model-id $ML_MODEL
{
    "Status": "COMPLETED",
    "SizeInBytes": 429625,
    "Name": "Subscription propensity model",
    "TrainingParameters": {
        "sgd.l2RegularizationAmount": "1E-6",
        "sgd.maxMLModelSizeInBytes": "100000000",
        "sgd.maxPasses": "10",
        "algorithm": "sgd",
        "sgd.l1RegularizationAmount": "0.0"
    },
    "MLModelType": "BINARY",
    "CreatedByIamUser": "arn:aws:iam::1234567890:user/awscli",
    "EndpointInfo": {
        "PeakRequestsPerSecond": 0,
        "EndpointStatus": "NONE"
    },
    "MLModelId": "ml-test",
    "InputDataLocationS3": "s3://b6b6/banking.csv",
    "LastUpdatedAt": 1433032063.415,
    "TrainingDataSourceId": "ds-test",
    "LogUri": "https://eml-prod-emr.s3.amazonaws.com/1234567890-pr-ml-test/userlog/...",
    "CreatedAt": 1433031820.012
}

Step 4: Review the ML Model’s Performance and Set a Score Threshold

次に作成したモデルでデータを評価します。

API は create-evaluation

API get-evaluation でレスポンスの Status からステータス確認し、StatusCOMPLETED になれば OK

難しいので、対応する Developer Guide も目を通しておいたほうが良い http://docs.aws.amazon.com/machine-learning/latest/dg/evaluating_models.html

評価後のしきい値の調整は管理画面からで。

$ EVALUATION=ev-test
$ aws machinelearning create-evaluation \
  --evaluation-id $EVALUATION \
  --evaluation-name "Subscription propensity evaluation" \
  --ml-model-id $ML_MODEL \
  --evaluation-data-source-id $DATA_SOURCE
{
    "EvaluationId": "ev-test"
}
# http://docs.aws.amazon.com/cli/latest/reference/machinelearning/get-evaluation.html
# http://docs.aws.amazon.com/machine-learning/latest/APIReference/API_GetEvaluation.html
$ aws machinelearning get-evaluation --evaluation-id $EVALUATION
{
    "EvaluationDataSourceId": "ds-test",
    "Status": "INPROGRESS",
    "Name": "Subscription propensity evaluation",
    "InputDataLocationS3": "s3://b6b6/banking.csv",
    "EvaluationId": "ev-test",
    "CreatedByIamUser": "arn:aws:iam::1234567890:user/awscli",
    "MLModelId": "ml-test",
    "LastUpdatedAt": 1433032120.971,
    "PerformanceMetrics": {
        "Properties": {}
    },
    "CreatedAt": 1433032117.915
}

$ aws machinelearning get-evaluation --evaluation-id $EVALUATION
{
    "EvaluationDataSourceId": "ds-test",
    "Status": "COMPLETED",
    "Name": "Subscription propensity evaluation",
    "InputDataLocationS3": "s3://b6b6/banking.csv",
    "EvaluationId": "ev-test",
    "CreatedByIamUser": "arn:aws:iam::1234567890:user/awscli",
    "MLModelId": "ml-test",
    "LastUpdatedAt": 1433032365.509,
    "LogUri": "https://eml-prod-emr.s3.amazonaws.com/1234567890-pr-ml-test/userlog/...",
    "PerformanceMetrics": {
        "Properties": {
            "BinaryAUC": "0.9395119502741789"
        }
    },
    "CreatedAt": 1433032117.915
}

Step 5: Use the ML Model to Generate Batch Predictions

評価用データソースを指定

学習データではないので --no-compute-statistics とする。(デフォルトfalse/noなので、このオプションは不要)

$ DATA_SOURCE_BATCH=ds-test-batch
$ cat data-spec-batch.json
{
  "DataLocationS3":"s3://b6b6/banking-batch.csv",
  "DataSchema": "{\"version\":\"1.0\",\"rowId\":null,\"rowWeight\":null,\"targetAttributeName\":null,\"dataFormat\":\"CSV\",\"dataFileContainsHeader\":true,\"attributes\":[{\"attributeName\":\"age\",\"attributeType\":\"NUMERIC\"},{\"attributeName\":\"job\",\"attributeType\":\"CATEGORICAL\"},{\"attributeName\":\"marital\",\"attributeType\":\"CATEGORICAL\"},{\"attributeName\":\"education\",\"attributeType\":\"CATEGORICAL\"},{\"attributeName\":\"default\",\"attributeType\":\"CATEGORICAL\"},{\"attributeName\":\"housing\",\"attributeType\":\"CATEGORICAL\"},{\"attributeName\":\"loan\",\"attributeType\":\"CATEGORICAL\"},{\"attributeName\":\"contact\",\"attributeType\":\"CATEGORICAL\"},{\"attributeName\":\"month\",\"attributeType\":\"CATEGORICAL\"},{\"attributeName\":\"day_of_week\",\"attributeType\":\"CATEGORICAL\"},{\"attributeName\":\"duration\",\"attributeType\":\"NUMERIC\"},{\"attributeName\":\"campaign\",\"attributeType\":\"NUMERIC\"},{\"attributeName\":\"pdays\",\"attributeType\":\"NUMERIC\"},{\"attributeName\":\"previous\",\"attributeType\":\"NUMERIC\"},{\"attributeName\":\"poutcome\",\"attributeType\":\"CATEGORICAL\"},{\"attributeName\":\"emp_var_rate\",\"attributeType\":\"NUMERIC\"},{\"attributeName\":\"cons_price_idx\",\"attributeType\":\"NUMERIC\"},{\"attributeName\":\"cons_conf_idx\",\"attributeType\":\"NUMERIC\"},{\"attributeName\":\"euribor3m\",\"attributeType\":\"NUMERIC\"},{\"attributeName\":\"nr_employed\",\"attributeType\":\"NUMERIC\"}],\"excludedAttributeNames\":[]}"
}
$ aws machinelearning create-data-source-from-s3 \
    --data-source-id $DATA_SOURCE_BATCH \
    --data-source-name "Banking Data 2" \
    --data-spec file://data-spec-batch.json \
    --no-compute-statistics
{
    "DataSourceId": "ds-test-batch"
}
$ aws machinelearning get-data-source --data-source-id $DATA_SOURCE_BATCH
{
    "Status": "COMPLETED",
    "NumberOfFiles": 1,
    "Name": "Banking Data 2",
    "DataLocationS3": "s3://b6b6/banking-batch.csv",
    "CreatedByIamUser": "arn:aws:iam::1234567890:user/awscli",
    "DataSizeInBytes": 468846,
    "ComputeStatistics": false,
    "LastUpdatedAt": 1433032648.258,
    "DataSourceId": "ds-test-batch",
    "CreatedAt": 1433032648.258
}

データのフォーマット指定

API では --data-spec file://data-batch-spec.json で指定するデータ・フォーマットの指定している。

基本的なフォーマットは STEP2 で利用した学習データと同じだが、目的変数("targetAttributeName":null) となっていたり、 分類結果である {"attributeName":"y","attributeType":"BINARY"} の列が評価データにはない、といったこまかな違いは存在する。

get-data-source API に --verbose オプションをつけると、データ定義も取得できる。最初は管理画面から設定して、細かいところは用途に応じてあとから修正するのが楽と思われる。

バッチ予測を実行

メインとなるバッチ予測の実行。

API は create-batch-prediction

API get-batch-prediction でレスポンスの Status からステータス確認し、 COMPLETED になれば OK

$ BATCH_PREDICTION=bp-test
$ aws machinelearning create-batch-prediction \
    --batch-prediction-id $BATCH_PREDICTION \
    --batch-prediction-name "Subscription propensity Predictions" \
    --ml-model-id $ML_MODEL \
    --batch-prediction-data-source-id $DATA_SOURCE_BATCH \
    --output-uri s3://$BUCKET/
{
    "BatchPredictionId": "bp-test"
}
# http://docs.aws.amazon.com/machine-learning/latest/APIReference/API_GetBatchPrediction.html
# http://docs.aws.amazon.com/cli/latest/reference/machinelearning/get-batch-prediction.html
$ aws machinelearning get-batch-prediction --batch-prediction-id $BATCH_PREDICTION
{
    "Status": "INPROGRESS",
    "Name": "Subscription propensity Predictions",
    "InputDataLocationS3": "s3://b6b6/banking-batch.csv",
    "CreatedByIamUser": "arn:aws:iam::1234567890:user/awscli",
    "MLModelId": "ml-test",
    "LastUpdatedAt": 1433032861.346,
    "BatchPredictionDataSourceId": "ds-test-batch",
    "OutputUri": "s3://b6b6/",
    "BatchPredictionId": "bp-test",
    "Message": "Current Step: BATCH_PREDICTION (1/1) 0%",
    "CreatedAt": 1433032818.172
}
$ aws machinelearning get-batch-prediction --batch-prediction-id $BATCH_PREDICTION
{
    "Status": "COMPLETED",
    "Name": "Subscription propensity Predictions",
    "InputDataLocationS3": "s3://b6b6/banking-batch.csv",
    "CreatedByIamUser": "arn:aws:iam::1234567890:user/awscli",
    "MLModelId": "ml-test",
    "LastUpdatedAt": 1433033003.939,
    "BatchPredictionDataSourceId": "ds-test-batch",
    "OutputUri": "s3://b6b6/",
    "BatchPredictionId": "bp-test",
    "LogUri": "https://eml-prod-emr.s3.amazonaws.com/1234567890-pr-ml-test/userlog/...",
    "CreatedAt": 1433032818.172
}

バッチ予測結果を取得

バッチ予測結果は --output-uri で指定した S3 パスに result というディレクトリで作成されます。

$ aws s3 ls s3://$BUCKET/
                           PRE batch-prediction/
2015-05-31 09:40:20          0 .writePermissionCheck.tmp
2015-05-31 09:12:10     468846 banking-batch.csv
2015-05-31 09:12:06    4767561 banking.csv
$ aws s3 ls s3://$BUCKET/batch-prediction/
                           PRE result/
2015-05-31 09:42:02         96 bp-test.manifest
$ aws s3 sync s3://$BUCKET/batch-prediction batch-prediction
warning: Skipping file /Users/george/Dev/aws-machine-learning/batch-prediction/. File does not exist.
download: s3://b6b6/batch-prediction/bp-test.manifest to batch-prediction/bp-test.manifest
download: s3://b6b6/batch-prediction/result/bp-test-banking-batch.csv.gz to batch-prediction/result/bp-test-banking-batch.csv.gz
$ gzip -d batch-prediction/result/bp-test-banking-batch.csv.gz
$ head -n 20 batch-prediction/result/bp-test-banking-batch.csv
bestAnswer,score
0,8.652008E-2
0,1.342608E-2
0,1.971196E-2
0,1.625213E-3
0,2.575607E-3
0,9.214219E-2
0,4.216536E-1
0,1.793497E-3
0,2.575086E-3
0,4.006434E-3
0,4.669604E-2
0,1.50039E-2
0,2.411173E-3
0,8.510052E-3
0,4.313236E-1
0,2.866098E-1
0,2.3708E-2
0,1.281799E-3
0,1.571054E-3

これらで最低限のCLI操作はできるハズ。

Tagged with: ,
Posted in aws

[Linux]trickleを使ってプログラムごとにアドホックに帯域制御する

ネットワークの帯域制御は用途や手段に応じて様々な手段が存在する

今回は、Linux 環境下で trickle を使ってプログラム単位でアドホックに帯域制御する方法をメモ。

trickle とは

  • ユーザースペースで動作するトラフィックシェイパー
  • ダイナミックローダーのプリローダー(LD_PRELOAD)を利用して socket の送受信に割り込んでトラフィックを制御
  • プログラム単位で制御

trickle をインストール

RedHat Installation

epel レポジトリからインストールする

# yum info trickle
...
Installed Packages
Name        : trickle
Arch        : x86_64
Version     : 1.07
Release     : 19.el6
Size        : 89 k
Repo        : installed
From repo   : epel
Summary     : Portable lightweight userspace bandwidth shaper
URL         : http://monkey.org/~marius/pages/?page=trickle
License     : BSD with advertising
Description : trickle is a portable lightweight userspace bandwidth shaper.
            : It can run in collaborative mode or in stand alone mode.
            :
            : trickle works by taking advantage of the unix loader preloading.
            : Essentially it provides, to the application,
            : a new version of the functionality that is required
            : to send and receive data through sockets.
            : It then limits traffic based on delaying the sending
            : and receiving of data over a socket.
            : trickle runs entirely in userspace and does not require root privileges.
# yum install trickle --enablerepo=epel

Ubuntu Installation

何も考えずに apt-get でインストールできる

# apt-get install trickle

trickle を使う

  • standalone mode(単一プログラムでの制御)
  • collaborative mode(複数プログラム合算での制御)

の2モードがある。

standalone mode

プログラムごとにトラフィックを制御する
trickle が一番得意とすると思われる用途

実行例

$ trickle -s -d {DOWNLOAD_LIMIT(KB/s)} command
$ trickle -s -u {UPLOAD_LIMIT(KB/s)} command
$ trickle -s -u {UPLOAD_LIMIT(KB/s)} -d {DOWNLOAD_LIMIT(KB/s)} command

要は、コマンドの先頭に trickle {制御条件} を追加するだけと非常にお手軽。

-s は standalone mode を指定するオプション。

wget で実際の動きを確認してみる。(wget自体にも帯域を制御するオプションは存在するが、今回は使わない)

なにも制御しなかった時

$ wget http://stable.release.core-os.net/amd64-usr/current/coreos_production_iso_image.iso
--2015-05-13 12:09:32--  http://stable.release.core-os.net/amd64-usr/current/coreos_production_iso_image.iso
Resolving stable.release.core-os.net... 64.233.187.128
Connecting to stable.release.core-os.net|64.233.187.128|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 169869312 (162M) [application/x-iso9660-image]
Saving to: `coreos_production_iso_image.iso'
22% [=======>                               ] 38,317,350   719K/s  eta 1m 50s

ダウンロード速度は 719K/s となっている

下りを 100KB/s に制限した時

$ trickle -s -d 100 wget http://stable.release.core-os.net/amd64-usr/current/coreos_production_iso_image.iso
--2015-05-13 12:11:12--  http://stable.release.core-os.net/amd64-usr/current/coreos_production_iso_image.iso
Resolving stable.release.core-os.net... 64.233.187.128
Connecting to stable.release.core-os.net|64.233.187.128|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 169869312 (162M) [application/x-iso9660-image]
Saving to: `coreos_production_iso_image.iso.1'

 0% [                                       ] 1,196,032   99.3K/s  eta 27m 15s ^C

ダウンロード速度は 99.3K/s  に絞られている。
期待通り。

collaborative mode

次に collaborative mode の動作を確認

trickled デーモンを起動し、以降、非スタンドアローンモードで trickle コマンドを実行すると、デーモン起動時に指定した条件を満たしつつ各プログラムが帯域制御される。

デーモンの起動

$ trickled -d {DOWNLOAD_LIMIT(KB/s)}
$ trickled -u {UPLOAD_LIMIT(KB/s)}
$ trickled -u {UPLOAD_LIMIT(KB/s)} -d {DOWNLOAD_LIMIT(KB/s)}
$ trickled -d 10

trickle はスタンドアローン-sオプションを外して起動すれば OK

$ trickle curl -o a http://stable.release.core-os.net/amd64-usr/current/coreos_production_iso_image.iso

大きなファイルをダウンロード、細かいインタラクティブなやりとりが何度も発生、な
どプログラムによって traffic shape のかけ方を変えたい時がある。

次の様な設定ファイルを用意し、trickled 起動時に $ trickled -c /path/to/trickled.conf -u 10 -d 10 というように -c オプ ションで指定する。

[ssh]
Priority = 1
Time-Smoothing = 0.1
Length-Smoothing = 2
[wget]
Priority = 8
Time-Smoothing = 5
Length-Smoothing = 20
  • priority が低いプログラムほど優先される。
  • Time-Smoothing は traffic shape する塊を時間(秒)で指定。
  • Length-Smoothing は traffic shape する塊を転送量(KB)で指定。

備考

“trickle: Could not reach trickled, working independently: No such file or directory” エラー

trickled デーモンが起動していない状態で -s オプションなしに trickle を起動すると、上記警告メッセージが表示され、スタンドアローンモードで起動する。

明示的に trickle オプションを指定すれば、このメッセージは表示されなくなる。

LD_PRELOAD をちら見

trickle-overload.c がプリロードされる関数群のコア。
trickle_ld_reload

“Trickle: A Userland Bandwidth Shaper for Unix-like Systems” (USENIX 2005) の Figure 1 から。

trickle を起動し、環境変数 LD_PRELOAD が利用されていることを確認。

# strings /proc/1491/environ
...
_=/usr/bin/trickle
TRICKLE_DOWNLOAD_LIMIT=10
TRICKLE_UPLOAD_LIMIT=10
TRICKLE_VERBOSE=0
TRICKLE_WINDOW_SIZE=200
TRICKLE_ARGV=wget
TRICKLE_SOCKNAME=
TRICKLE_TSMOOTH=3.0
TRICKLE_LSMOOTH=20
LD_PRELOAD=/usr/lib64/trickle/trickle-overload.so <- HERE

PID を指定して、ライブラリの一覧を列挙

# lsof -p 1491 | awk '{print $9}' | grep '\.so'
/lib64/libnss_dns-2.12.so
/lib64/libnss_files-2.12.so
/lib64/libselinux.so.1
/lib64/libresolv-2.12.so
/lib64/libkeyutils.so.1.3
/lib64/libkrb5support.so.0.1
/lib64/libpthread-2.12.so
/lib64/libz.so.1.2.3
/lib64/libk5crypto.so.3.1
/lib64/libcom_err.so.2.1
/lib64/libkrb5.so.3.3
/lib64/libgssapi_krb5.so.2.2
/lib64/libnsl-2.12.so
/lib64/libc-2.12.so
/lib64/librt-2.12.so
/lib64/libdl-2.12.so
/usr/lib64/libcrypto.so.1.0.1e
/usr/lib64/libssl.so.1.0.1e
/usr/lib64/trickle/trickle-overload.so <- HERE
/lib64/ld-2.12.so

References

man

  • TRICKLE(1) : trickle – a lightweight userspace bandwidth shaper
  • TRICKLED(8) : trickled – userspace bandwidth manager daemon
  • TRICKLED.CONF(5) : trickled.conf – format of the configuration file used by trickled(8).
Tagged with: , ,
Posted in linux
Archives
  • RT @__apf__: How to write a research paper: a guide for software engineers & practitioners. docs.google.com/presentation/d… /cc @inwyrd 6 months ago
  • RT @HayatoChiba: 昔、自然と対話しながら数学に打ち込んだら何かを悟れるのではと思いたち、専門書1つだけ持ってパワースポットで名高い奈良の山奥に1週間籠ったことがある。しかし泊まった民宿にドカベンが全巻揃っていたため、水島新司と対話しただけで1週間過ぎた。 それ… 7 months ago
  • RT @googlecloud: Ever wonder what underwater fiber optic internet cables look like? Look no further than this deep dive w/ @NatAndLo: https… 7 months ago
  • @ijin UTC+01:00 な時間帯で生活しています、、、 1 year ago
  • RT @mattcutts: Google's world-class Site Reliability Engineering team wrote a new book: amazon.com/Site-Reliabili… It's about managing produc… 1 year ago