【AWS】CloudWatchのメトリックデータをCSVにまとめたい【Python】

EC2のメトリックとかから障害予測とかできないかな～と思ったので、データを集めたくなりました。
ので、今回はCloudWatchのメトリックからデータを持ってきて、CSVにまとめてみたいと思います。

# 環境
python: 3.7.4

# 内容
今回実装するのは下記の動作をするものです。
1. CloudWatchから特定のデータを取得
2. 取得したデータから必要なものだけまとめる
3. まとめたデータをCSVにして出力

こんな感じです。ではやっていきましょう！

# 環境の準備
データの取得はpythonを用いて行おうと思います。
まずは`awscli`と`boto3`のモジュールを入れておきましょう。

```terminal
$ pip install awscli
$ pip install boto3
```

awscliの環境設定をしていない方はそちらも行いましょう。
それぞれのAccess KeyはIAMから発行できます。
利用するIAMユーザーの認証情報から、アクセスキーを発行して、それを使います。
今回のプログラムを動作させるなら、CloudWatchへのアクセスを許可していれば問題ないと思います。

```terminal
$ aws configure
AWS Access Key ID: [Your Key]
AWS Secret Access Key: [Your Secret Key]
Default region name: [Region](ap-northeast-1とか)
Default output format: [Format](jsonとか)
```

# CloudWatchから特定のデータを取得
ではデータを持ってきたいと思います。
データの取得には[get_metric_statistics](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/cloudwatch.html#CloudWatch.Client.get_metric_statistics)を利用します。

```python
response = client.get_metric_statistics(
    # 参照したいサービス(AWS/EC2とか)
    Namespace='string', 
    # とってきたいデータ(CPUUtilizationとか)
    MetricName='string', 
    # Name: 詳細の名称(InstanceIdとか), Value: 対応する名前ID(i-xxxxxxxとか)
    Dimensions=[
        {
            'Name': 'string', 
            'Value': 'string'
        },
    ],
    # 取得したい範囲の始まり。時分秒までいける。UTC形式。
    StartTime=datetime(2015, 1, 1), 
    # 取得したい範囲の終わり
    EndTime=datetime(2015, 1, 1), 
    # どのくらいの間隔でデータを取得するか。秒単位で指定。60の倍数である必要があるらしい。
    Period=123, 
    # 取得したいデータの種類。必要なのだけ入れればいい
    Statistics=[
        'SampleCount','Average','Sum','Minimum','Maximum', 
    ],
    # パーセンタイル統計というものらしい。p0.0~p100で指定するらしい。StatisticsかExtendedStatisticsのどちらかが必須項目。
    ExtendedStatistics=[
        'string', 
    ],
    # メトリックの単位。一致しない単位の時はnullが返ってくる、指定しなくても任意の形式で返ってくる 
    Unit='Seconds'|'Microseconds'|'Milliseconds'|'Bytes'|'Kilobytes'|'Megabytes'|'Gigabytes'|'Terabytes'|'Bits'|'Kilobits'|'Megabits'|'Gigabits'|'Terabits'|'Percent'|'Count'|'Bytes/Second'|'Kilobytes/Second'|'Megabytes/Second'|'Gigabytes/Second'|'Terabytes/Second'|'Bits/Second'|'Kilobits/Second'|'Megabits/Second'|'Gigabits/Second'|'Terabits/Second'|'Count/Second'|'None'
)
```

取得する機関によっては、取得間隔が一定以上に限られてくるので注意しましょう。

- 3時間から15日前までの開始時間-60秒（1分）の倍数を使用します。
- 15〜63日前の開始時間-300秒（5分）の倍数を使用します。
- 63日以上前の開始時間-3600秒（1時間）の倍数を使用します。

これを実行すると返ってくるのが下記

```json
{
    'Label': 'string',
    'Datapoints': [
        {
            'Timestamp': datetime(2015, 1, 1),
            'SampleCount': 123.0,
            'Average': 123.0,
            'Sum': 123.0,
            'Minimum': 123.0,
            'Maximum': 123.0,
            'Unit': 'Seconds'|'Microseconds'|'Milliseconds'|'Bytes'|'Kilobytes'|'Megabytes'|'Gigabytes'|'Terabytes'|'Bits'|'Kilobits'|'Megabits'|'Gigabits'|'Terabits'|'Percent'|'Count'|'Bytes/Second'|'Kilobytes/Second'|'Megabytes/Second'|'Gigabytes/Second'|'Terabytes/Second'|'Bits/Second'|'Kilobits/Second'|'Megabits/Second'|'Gigabits/Second'|'Terabits/Second'|'Count/Second'|'None',
            'ExtendedStatistics': {
                'string': 123.0
            }
        },
    ...
    ]
}
```

メトリクスの名前と、`Datapoints`の中に時間と、指定したデータが入ってくるみたい。
期間を指定してたら、`Datapoints`の中に複数データが格納される感じになる。


# 取得したデータから必要なものだけまとめる
データは取得できたので必要なデータにまとめちゃいます。
後々pandasでCSVにしたいので、時間とメトリックデータを項目ごとにlistでまとめたいと思います。

```python
# responseの中に上記のjson形式の結果がある
Metric = []
Timestamp = []
for res in response["Datapoints"]:
  Timestamp.append(res["Timestamp"])
  Metric.append(res["Maximum"])
```

これで必用なデータはまとまったのでcsvにしていきましょう。

# まとめたデータをCSVにして出力

```python
df = pd.DataFrame([Timestamp, Metric], columns = ["Timestamp", "CPUUtilization"])
df.to_csv("output/mt.csv", index=False)
```

これでCSVにできました！あとはこのデータを煮るなり焼くなりしていけばいいですね！

# まとめと全体のソースコード
今回はCloudWatchからデータを取得してCSVにまとめました。
データによって取得できる期間が一定しかないものもあるので、定期的にデータを取得して活用していきたいですね。

下記に全体のソースコードを載せておきます。
上記と異なり、複数のメトリクスをまとめているので多少異なるところに注意してください。

```python
import json
from datetime import datetime

import boto3
import pandas as pd

# cloudwatchのクライアント取得
client = boto3.client('cloudwatch')

# とってきたいデータ
Metrics = ["CPUUtilization", "NetworkIn", "NetworkOut", "NetworkPacketsIn", "NetworkPacketsOut"]

# 仮置きデータ(取得したデータの一時保存用)
output = []

# とりたいデータが複数あったので繰り返す
for Metric in Metrics:
    response = client.get_metric_statistics(
        Namespace = "AWS/EC2",
        MetricName = Metric,
        Dimensions=[{'Name': 'InstanceId', 'Value': 'i-xxxxxx'}],
        StartTime = datetime(2021, 10, 20, 5, 25),
        EndTime = datetime(2021, 10, 20, 6, 10),
        Period = 60,
        Statistics = ['Maximum'],
    )
    # それぞれをDataFrameに直して保存。indexをTimeStampにしておく
    output.append(pd.DataFrame([[v["Timestamp"], v["Maximum"]] for v in response["Datapoints"]], columns=["Timestamp", Metric]).set_index("Timestamp"))

# 取得した全部のデータを結合。TimeStampもデータに入れたいのでreset_indexでデータに入れるようにする
# 結合はTimeStampが同じものは同列になるような結合をする
df = pd.concat(output, axis=1).reset_index()

df.to_csv("output/mt.csv", index=False)
```