CloudWatch から S3 のストレージ使用量推移を取得する（シェルスクリプト版 + Python版）

## はじめに

S3 の容量推移を取得する方法は色々あります

- Cost Explorer で取得する

    Web　コンソールの請求ダッシュボードから Cost Explorer を表示し、フィルタを以下のように指定すれば
    S3 の全バケットの使用量と、それによるコストの推移が表示されます

    - サービス=S3
    - 使用タイプグループ=Storage - Standard

    ![cost-explorer.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/7aa127b8-24a5-8c38-b2ab-c9cc83f611ef.png)

- CloudWatch で取得する

    Web コンソールの CloudWatch から「すべてのメトリクス」を表示し、
    S3 のバケット、 StandardStorage を選択すればバケット毎の使用量推移が表示されます

    ![cloudwatch.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/2f4f561c-af5f-6a46-52d3-d3397edd2789.png)

- S3 Storage Lens で取得する

    S3 の統計情報を手軽に取得できる S3 Lens を使用すれば、より詳細なデータを取得できます
    バケット毎のストレージ容量などは無料分に含まれており、デフォルトで収集されています
    プレフィックスなどで集計する場合は有償で個別の設定を行います（設定後、48時間経過すると参照できるようになる）

    Web コンソールの S3 から「Storage Lens」の「ダッシュボード」を選択します
    デフォルトで「default-account-dashboard」が作られているので、これを選択します
    ※ただし、ルートユーザーでは参照できないようになっているので、IAMユーザーでサインインしてください

    ![s3-lens.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/b8201bfc-1019-8918-1ec9-c1e6b0089d5a.png)


ただし、これらは Web コンソールから表示する方法であり、 
Web コンソール用の認証情報を持っていない場合や、
プログラムからアクセスしたい場合には使用できません

というわけで、コマンドラインから使用できる S3 使用量推移取得ツールを実装しました

実行すると、以下のような CSV を出力します

```csvs
Date,Bytes
"2022/01/30",4878460855189
"2022/01/31",4851200439116
"2022/02/01",4925325370518
"2022/02/02",5060814016833
"2022/02/03",5079663776337
```

AWS CLI と jq を利用したシェルスクリプト版と、
boto3 と pandas を利用した Python 版の2種類を作っています

コードの全量は以下のリポジトリを参照してください

https://github.com/RyoWakabayashi/amazon-s3-storage-history

## 仕組み

CloudWatch の API からデータを取得します

CloudWatch には S3 のストレージ容量を監視するためのメトリクスが自動的に作られており、
ここにバケット毎にストレージ容量、オブジェクト数のリクエスト量などが格納されています

## シェルスクリプト版

以下のコマンドでバケット毎の指定期間のストレージ容量が1日毎に取得できます

```bash
$ aws cloudwatch get-metric-statistics \
    --namespace AWS/S3 \
    --metric-name BucketSizeBytes \
    --dimensions \
        Name=StorageType,Value=StandardStorage \
        Name=BucketName,Value="$backet_name" \
    --statistics Maximum \
    --start-time "$start_date"T00:00:00Z \
    --end-time "$end_date"T00:00:00Z \
    --period 86400 \
    --unit Bytes
```

- backet_name: バケット名
- start_date: 開始日（ YYYY-MM-DD ）
- end_date: 終了日（ YYYY-MM-DD ）

period の 86400 は 60 * 60 * 24 秒 = 1日で集計の単位を表しています

結果は以下のような JSON になります

```json
{
    "Label": "BucketSizeBytes",
    "Datapoints": [
        {
            "Timestamp": "2022-01-31T00:00:00Z",
            "Maximum": 889248651.0,
            "Unit": "Bytes"
        },
        {
            "Timestamp": "2022-01-30T00:00:00Z",
            "Maximum": 889248651.0,
            "Unit": "Bytes"
        }
    ]
}
```

あとはこれを整形すれば OK です

jq を使えばコマンドラインから JSON データの操作ができます

https://stedolan.github.io/jq/

今回は CSV にしたかったので、以下のように指定しました

```bash
jq -r '
    .Datapoints
    | sort_by(.Timestamp)
    | .[]
    | [(.Timestamp|strptime("%Y-%m-%dT%H:%M:%SZ")|strftime("%Y/%m/%d")), .Maximum]
    | @csv
'
```

- `.Datapoints`: Datapoints の値を取得する


- `sort_by(.Timestamp)`: Timestamp の昇順で並べ替える

- `.[]`: 配列の各要素を取り出す

- `[(.Timestamp|strptime("%Y-%m-%dT%H:%M:%SZ")|strftime("%Y/%m/%d")), .Maximum]`:

    Timestamp の形式を変換したものと、 Maximum の値を取り出す

    - `.Timestamp`: Timestamp の値を取り出す
    - `strptime("%Y-%m-%dT%H:%M:%SZ")`: `%Y-%m-%dT%H:%M:%SZ` 形式の文字列を日付型に変換する
    - `strftime("%Y/%m/%d")`: 日付を `%Y/%m/%d` 形式の文字列に変換する
    - `.Maximum`: Maximum の値を取り出す

- `@csv`: CSV 形式に整形する

最終的にこれを CSV ファイルに書き出すため、コマンドをくっつけると以下のようになります

```bash
echo "Date,Bytes" | tee "s3_storage.csv"

aws cloudwatch get-metric-statistics \
    --namespace AWS/S3 \
    --metric-name BucketSizeBytes \
    --dimensions \
        Name=StorageType,Value=StandardStorage \
        Name=BucketName,Value="$backet_name" \
    --statistics Maximum \
    --start-time "$start_date"T00:00:00Z \
    --end-time "$end_date"T00:00:00Z \
    --period 86400 \
    --unit Bytes \
    | jq -r '
        .Datapoints
        | sort_by(.Timestamp)
        | .[]
        | [(.Timestamp|strptime("%Y-%m-%dT%H:%M:%SZ")|strftime("%Y/%m/%d")), .Maximum]
        | @csv
    ' \
    | tee -a "s3_storage.csv"
```

※ `tee` を使っているのは、画面に表示しつつファイルにも書き出すため

## Python 版

boto3 では、以下のようにして CLoudWatch からデータを取得します

```python
client = boto3.client("cloudwatch")

dimensions = [
    {"Name": "StorageType", "Value": "StandardStorage"},
    {"Name": "BucketName", "Value": bucket_name},
]

statistics = client.get_metric_statistics(
    Namespace="AWS/S3",
    MetricName="BucketSizeBytes",
    Dimensions=dimensions,
    StartTime=start_time,
    EndTime=end_time,
    Period=86400,
    Statistics=["Maximum"],
)
```

シェルスクリプトのときと同じ形でデータが返ってくるので、これを加工します

pandas を使用するとデータの集計や加工が簡単に実装できます

https://pandas.pydata.org/

pandas で DataFrame に変換してから加工し、 CSV に出力します

```python
df = pd.DataFrame(statistics["Datapoints"])

# 並べ替え
df = df.sort_values("Timestamp").reset_index()
# 日付形式変更
df["Timestamp"] = df["Timestamp"].dt.strftime("%Y/%m/%d")
df["Maximum"] = df["Maximum"].astype(int)
# 不要な項目を消してヘッダの修正
df = df[["Timestamp", "Maximum"]]
df.columns = ["Date", "Bytes"]

print(df)

df.to_csv("s3_storage.csv", index=False)
```

## おわりに

シェルスクリプトなら jq 、 Python なら pandas がものすごく強力なので、
これらを使えばあらゆるデータ加工が可能そうです

CloudWatch には他にも多くのメトリクスがあるため、
同じ方法で色々な AWS データの集計ができそうですね