はじめに
lambdaでは通常CPU使用率を気にする必要はありませんが、パフォーマンスチューニングをして実行時間を短くしようとした際に、処理のボトルネックがIOなのかCPUなのか知りたいと思うケースがあります。
その際に簡単なデコレータを用意してCPU使用率を計測できるようにして、その機能をLambda Layerで使えるようにしてみました。
なおpython3の話です。
概要
- psutilを使えばCPU時間を取得できる
- 関数の実行前後でCPU時間を取得するデコレータ関数を作る
- その関数をLambda Layerとしてデプロイする
- Lambda関数でLayerをインポートしてのエントリポイントにデコテータをつける
詳細
psutilを使ったデコレータ関数を用意する
psutlを使えばCPU時間が取れます。
import psutil
psutil.cputimes()
scputimes(user=344.21, nice=2.71, system=38.98, idle=39275.96, iowait=5.9, irq=0.0, softirq=7.84, steal=13.8, guest=0.0, guest_nice=0.0)
psutil.cpu_times
を使って関数実行前後にCPU時間を取得し、その差分からCPU使用率を取得するデコレータ関数を作成します。
from functools import wraps
from psutil import cpu_times
def cpu_trace(func):
@wraps(func)
def wrapper(*args, **kwargs):
before = cpu_times()._asdict()
result = func(*args, **kwargs)
after = cpu_times()._asdict()
increment = {}
for key in after.keys():
increment[key] = after[key] - before[key]
total = sum(increment.values())
rate = dict( (key, value / total ) for key, value in increment.items())
print(rate)
return result
return wrapper
cpu_times
の戻り値はnamed_tuple
なので、そのままでは各要素の値を計算し辛いです。なので_asdict()
を読んでdict型で保存しています。
後はafterの各状態からbeforeの各状態を引いて差分を計算すれば関数実行中にどれだけCPU時間が増加したかわかります。それを元に各状態(idle, nice, user, ...)の比率を計算します。
作ったデコレータをLambda Layerとしてデプロイする
作ったデコレータをSAMでデプロイする設定を上のリポジトリにあげてます。
Lambda Layerで作るPython用パッケージはpython
という名前のディレクトリに配置されるようなディレクトリ構成にする必要があります。
workdir(ContentUri)
└── python
├── psutil
├── psutil-5.4.8.egg-info
└── traceutil
├── __init__.py
└── cpu_trace.py
Lambda LayerはCloud Formationのpackageとdeployでアップロード出来ます。
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Lambda Layers to measure cpu in Python3 applications.
Resources:
CpuTrace:
Type: AWS::Serverless::LayerVersion
Properties:
LayerName: AWSLambda-Python3-TraceUtil
Description: Lambda Layers to measure cpu in Python3 applications.
ContentUri: workdir
CompatibleRuntimes:
- python3.6
- python3.7
RetentionPolicy: Retain
aws cloudformation package \
--template-file sam.yml \
--s3-bucket $S3_BACKET \
--s3-prefix $S3_PREFIX \
--output-template-file .template.yml
aws cloudformation deploy \
--template-file .template.yml \
--stack-name $STACK_NAME
lambdaのエントリポイントにデコレータを追加する
あとはLambdaFunctionsのLayersに先程のLayerを追加した後にエントリポイントにデコレータを記述するだけです。
import json
from traceutil import cpu_trace
import time
@cpu_trace
def lambda_handler(event, context):
time.sleep(1)
# TODO implement
return {
'statusCode': 200,
'body': json.dumps('Hello from Lambda!')
}
結果の確認
結果はCloudwatch Logsで見れます
Function Logs:
START RequestId: eff78e17-e3ee-11e8-a1bb-97928c2e9342 Version: $LATEST
{'user': 0.0, 'nice': 0.0, 'system': 0.0, 'idle': 1.0, 'iowait': 0.0, 'irq': 0.0, 'softirq': 0.0, 'steal': 0.0, 'guest': 0.0, 'guest_nice': 0.0}
END RequestId: eff78e17-e3ee-11e8-a1bb-97928c2e9342
REPORT RequestId: eff78e17-e3ee-11e8-a1bb-97928c2e9342 Duration: 1001.90 ms Billed Duration: 1100 ms Memory Size: 128 MB Max Memory Used: 25 MB
終わりに
LambdaのCPU使用率を計算したかったのでデコレータを使って計測できるLambda Layerを作ってみました。
こういうユーティリティっぽいものにも使えてLambda Layer便利ですね。