🕌
AWS GlueのJobごとのコストを調べるスクリプト
コスト
コスト = 使用 DPU 数 × ジョブ実行時間(時間) × (1 DPU 時間あたりの単価)
スクリプト
aws sso loginした状態で、以下実行。
#!/bin/bash
# usage: ./glue.sh YYYY-MM
# 例: ./glue.sh 2025-01
if [ "$#" -ne 1 ]; then
echo "Usage: $0 YYYY-MM"
exit 1
fi
TARGET_MONTH="$1"
# 1 DPU 時間あたりの単価 (USD)
DPU_HOUR_RATE=0.44
echo "対象月: ${TARGET_MONTH}"
echo "1 DPU 時間あたりの単価: USD ${DPU_HOUR_RATE}"
echo "-----------------------------------------"
# サマリー用の配列 (bash 3.x でも利用可能な通常の配列)
job_names_summary=()
job_costs_summary=()
# 全ジョブ名を取得
job_names=$(aws glue list-jobs --query 'JobNames' --output text)
if [ -z "$job_names" ]; then
echo "Glue ジョブが見つかりません。"
exit 1
fi
for job in $job_names; do
total_cost=0
echo "ジョブ: ${job}"
next_token=""
while :; do
if [ -z "$next_token" ]; then
result=$(aws glue get-job-runs --job-name "$job" --output json)
else
result=$(aws glue get-job-runs --job-name "$job" --next-token "$next_token" --output json)
fi
# JSON出力が有効かチェック
if ! echo "$result" | jq . > /dev/null 2>&1; then
echo "Error: job '$job' の実行履歴で無効な JSON が返されました。"
break
fi
# JobRuns が存在しない場合はループ終了
runs=$(echo "$result" | jq -c '.JobRuns[]?')
if [ -z "$runs" ]; then
break
fi
for run in $runs; do
started=$(echo "$run" | jq -r '.StartedOn')
completed=$(echo "$run" | jq -r '.CompletedOn')
# 開始時刻・終了時刻が空または "null" ならスキップ
if [ -z "$started" ] || [ "$started" = "null" ] || [ -z "$completed" ] || [ "$completed" = "null" ]; then
echo " Skipping run: 開始または終了時刻が取得できません。"
continue
fi
# タイムスタンプの変換処理(fractional seconds やタイムゾーンの形式に対応)
if echo "$started" | grep -q "Z$"; then
clean_started="$started"
start_format="%Y-%m-%dT%H:%M:%SZ"
else
clean_started=$(echo "$started" | sed -E 's/\.[0-9]+//; s/([+-][0-9]{2}):([0-9]{2})/\1\2/')
start_format="%Y-%m-%dT%H:%M:%S%z"
fi
if echo "$completed" | grep -q "Z$"; then
clean_completed="$completed"
comp_format="%Y-%m-%dT%H:%M:%SZ"
else
clean_completed=$(echo "$completed" | sed -E 's/\.[0-9]+//; s/([+-][0-9]{2}):([0-9]{2})/\1\2/')
comp_format="%Y-%m-%dT%H:%M:%S%z"
fi
# 対象月のチェック: StartedOn の YYYY-MM 部分を抽出
run_month=$(date -j -f "$start_format" "$clean_started" "+%Y-%m" 2>/dev/null)
if [ -z "$run_month" ]; then
echo " Skipping run: 開始時刻のパースに失敗: $clean_started"
continue
fi
if [ "$run_month" != "$TARGET_MONTH" ]; then
continue
fi
# エポック秒へ変換
start_epoch=$(date -j -f "$start_format" "$clean_started" "+%s" 2>/dev/null)
end_epoch=$(date -j -f "$comp_format" "$clean_completed" "+%s" 2>/dev/null)
if [ -z "$start_epoch" ] || [ -z "$end_epoch" ]; then
echo " Skipping run: タイムスタンプ変換に失敗。"
continue
fi
runtime_seconds=$(( end_epoch - start_epoch ))
runtime_hours=$(echo "scale=4; $runtime_seconds / 3600" | bc)
# NumberOfWorkers を取得。無い場合はジョブ定義から取得。なければ 1 とする。
num_workers=$(echo "$run" | jq -r '.NumberOfWorkers')
if [ -z "$num_workers" ] || [ "$num_workers" = "null" ]; then
job_def=$(aws glue get-job --job-name "$job" --output json)
num_workers=$(echo "$job_def" | jq -r '.Job.DefaultArguments."--NumberOfWorkers"')
if [ -z "$num_workers" ] || [ "$num_workers" = "null" ]; then
num_workers=1
fi
fi
# WorkerType の取得。無ければ "G.1X" と仮定
worker_type=$(echo "$run" | jq -r '.WorkerType')
if [ -z "$worker_type" ] || [ "$worker_type" = "null" ]; then
worker_type="G.1X"
fi
if [ "$worker_type" = "G.2X" ]; then
dpu_multiplier=2
else
dpu_multiplier=1
fi
# コスト計算: cost = NumberOfWorkers * dpu_multiplier * runtime_hours * DPU_HOUR_RATE
run_cost=$(echo "scale=4; $num_workers * $dpu_multiplier * $runtime_hours * $DPU_HOUR_RATE" | bc)
total_cost=$(echo "scale=4; $total_cost + $run_cost" | bc)
echo " Run開始: $started / 終了: $completed / 時間: ${runtime_hours}h / Workers: ${num_workers} (${worker_type}) -> コスト: USD ${run_cost}"
done
next_token=$(echo "$result" | jq -r '.NextToken')
if [ -z "$next_token" ] || [ "$next_token" = "null" ]; then
break
fi
done
echo ">> ジョブ [${job}] の ${TARGET_MONTH} の合計コスト: USD ${total_cost}"
echo "-----------------------------------------"
# サマリー用に格納
job_names_summary+=("$job")
job_costs_summary+=("$total_cost")
done
# 全ジョブのサマリー出力
echo ""
echo "=== 全ジョブサマリー ==="
for i in "${!job_names_summary[@]}"; do
echo "Job: ${job_names_summary[$i]} -> 合計コスト: USD ${job_costs_summary[$i]}"
done
Discussion