CloudWatchメトリクス AWS CLI サンプル集

この記事はCloudWatchメトリクスをAWS CLIで扱うサンプルをまとめた記事です。

# CloudWatchとは
CloudWatchはAWSが提供するモニタリングのためのマネージドサービスです。
この記事で主に取り扱うメトリクスのほかに様々なサービスがあります。
- CloudWatch Metrics: メトリクス
- CloudWatch Alarms: アラーム（通知）
- CloudWatch Logs: ログ
- CloudWatch Events (現在は Amazon EventBridge): イベント契機の通知や処理
- CloudWatch Dashboards: ダッシュボード、コンソール画面
- CloudWatch Synthetics: 外形監視
- CloudWatch ServiceLens: X-Rayと連携したアプリケーション監視・分散トレーシング
- CloudWatch Contributor Insights: パフォーマンスに最も影響を及ぼしている要因の分析
- CloudWatch Container Insights: ECSとEKSのメトリクス・ログ収集
- CloudWatch Application Insights for .NET and SQL Server
- CloudWatch (unified) agent: 任意のサーバーで実行できるメトリクス・ログ収集エージェント
- CloudWatch SDK metrics for Enterprise Support: AWS APIをクライアントからコールした回数等の状況をSDKレベルでメトリクス化

# 基本操作
メトリクスストレージとしての(狭義の)CloudWatchメトリクスはPut/List/Getの3つの機能(だけ)をもちます。
EC2やRDS、Lambdaなどの各種AWSサービスはそれぞれメトリクスを収集して、CloudWatchに送信しています。
CloudWatchメトリクスはそのメトリクスを保管・参照する機能を提供しているわけです。

CloudWatchはメトリクスを特定するために、`Namespace`(文字列)、`Metric Name`(文字列)、そして`Dimensions`(Key/Valueの配列)の3つの情報をユニーク識別子として使用します。
Put/Getのときにはこの識別子を指定してメトリクスを操作することになります。

まずは基本のPut/List/GetからAWS CLIで実行してみましょう。

## Put
CloudWatchではメトリクスのPutに`PutMetricData`というAPIを使用します。
- PutMetricData API https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_PutMetricData.html

### put-metric-data
以下の`put-metric-data`コマンドでは、`Namespace=JawsCLI`、`Metric Name=TestMetrics`、`Dimensions=[TestKey=TestValue]`という識別値に対して、`100`という値をPutしています。

```sh
$ aws cloudwatch put-metric-data --namespace JawsCLI --metric-name TestMetric --dimensions TestKey=TestValue --value 100
```

ちなみに、ここでは`timestamp`と`unit`を指定していません。
CloudWatchはこの場合、`timestamp=(現在日時)`、`unit=None`を自動で使用します。

## List
CloudWatchではメトリクスのListに`ListMetrics`というAPIを使用します。
- ListMetrics API https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_ListMetrics.html

### list-metrics
以下の`list-metrics`コマンドでは、`Namespace=JawsCLI`配下のメトリクスをすべて表示しています。

```sh
$ aws cloudwatch list-metrics --namespace JawsCLI
{
    "Metrics": [
        {
            "Namespace": "JawsCLI",
            "MetricName": "TestMetric",
            "Dimensions": [
                {
                    "Name": "TestKey",
                    "Value": "TestValue"
                }
            ]
        }
    ]
}
```

さきほどPutしたTestMetricsが確認できます。

なお、新しいメトリクスをPutしてからListできるようになるまでには、最大15分かかることがAPIドキュメントでも説明されています。ただ、ほとんどの場合、数十秒程度でListできますし、Getできるようになるまではもっと短い時間ですみます。
> After you create a metric, allow up to fifteen minutes before the metric appears. Statistics about the metric, however, are available sooner using GetMetricData or GetMetricStatistics. 

## Get
CloudWatchではメトリクスのGetに`GetMetricStatistics`と`GetMetricData`というAPIを使用します。
- GetMetricStatistics API https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_GetMetricStatistics.html
- GetMetricData API https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_GetMetricData.html

### get-metric-statistics
以下の`get-metric-statistics`コマンドでは、`Namespace=JawsCLI`、`Metric Name=TestMetrics`、`Dimensions=[TestKey=TestValue]`という識別値にひもづくメトリクスの平均値を60秒粒度で、15分前から現在の分まで取得しています。

```sh
$ aws cloudwatch get-metric-statistics --namespace JawsCLI --metric-name TestMetric --dimensions Name=TestKey,Value=TestValue --period 60 --statistics Average --start-time $(date -u +"%Y-%m-%dT%H:%M:%SZ" -d "15 min ago") --end-time $(date -u +"%Y-%m-%dT%H:%M:%SZ")
{
    "Label": "TestMetric",
    "Datapoints": [
        {
            "Timestamp": "2020-03-02T07:27:00Z",
            "Average": 100.0,
            "Unit": "None"
        }
    ]
}
```
### get-metric-data
以下の`get-metric-data`コマンドでは、`Namespace=JawsCLI`、`Metric Name=TestMetrics`、`Dimensions=[TestKey=TestValue]`という識別値にひもづくメトリクスの平均値を60秒粒度で、15分前から現在の分まで取得しています。

なお、今回はCLIの引数にJSON文字列を含めていますが、`file://`プレフィックスによりJSONファイルから読み取らせることもできます。

```sh
$ aws cloudwatch get-metric-data --metric-data-queries '{"Id":"q1","MetricStat":{"Metric":{"Namespace":"JawsCLI","MetricName":"TestMetric","Dimensions":[{"Name":"TestKey","Value":"TestValue"}]},"Period":60,"Stat":"Average"}}' --start-time $(date -u +"%Y-%m-%dT%H:%M:%SZ" -d "15 min ago") --end-time $(date -u +"%Y-%m-%dT%H:%M:%SZ")
{
    "MetricDataResults": [
        {
            "Id": "q1",
            "Label": "TestMetric",
            "Timestamps": [
                "2020-03-02T07:56:00Z"
            ],
            "Values": [
                100.0
            ],
            "StatusCode": "Complete"
        }
    ],
    "Messages": []
}
```

上記のように、`GetMetricStatistics`と`GetMetricData`はいずれもメトリクスの取得に使用できます。
違いについては以下のドキュメントに記述があります。
基本的には何も考えずに`GetMetricData`を使えばOKです。

参考: CloudWatch メトリクスでは、GetMetricData または GetMetricStatistics を使用する必要がありますか?
https://aws.amazon.com/jp/premiumsupport/knowledge-center/cloudwatch-getmetricdata-api/


# 複数メトリクス同時処理
CloudWatchはAPIごとに秒間実行数の上限値をもちます。また、CloudWatchはAPIコールに課金するサービスです。
このため、APIコール数を減らすことが、上限値の影響の回避、さらにコスト削減につながります。

CloudWatchメトリクスはPut時もGet時も、いずれも複数のメトリクスをまとめて処理できます。

## Put
`put-metric-data`に`--metric-name`などの代わりに`--metric-data`を指定するのがポイントです。

```sh
$ cat metrics.json
[
  {
    "MetricName": "TestMetric1",
    "Dimensions": [
      {
        "Name": "TestKey",
        "Value": "TestValue"
      }
    ],
    "Value": 100
  },
  {
    "MetricName": "TestMetric2",
    "Dimensions": [
      {
        "Name": "TestKey1",
        "Value": "TestValue1"
      },
      {
        "Name": "TestKey2",
        "Value": "TestValue2"
      }
    ],
    "Value": 200
  }
]

$ aws cloudwatch put-metric-data --namespace JawsCLI --metric-data file://./metrics.json
```

なお、複数のメトリクスを同時にPutする場合、`Metric Name`や`Dimensions`は全く異なるものを同時に扱うことができますが、`Namespace`は同一である必要があります。

## List
```sh
$ aws cloudwatch list-metrics --namespace JawsCLI
{
    "Metrics": [
        {
            "Namespace": "JawsCLI",
            "MetricName": "TestMetric",
            "Dimensions": [
                {
                    "Name": "TestKey",
                    "Value": "TestValue"
                }
            ]
        },
        {
            "Namespace": "JawsCLI",
            "MetricName": "TestMetric1",
            "Dimensions": [
                {
                    "Name": "TestKey",
                    "Value": "TestValue"
                }
            ]
        },
        {
            "Namespace": "JawsCLI",
            "MetricName": "TestMetric2",
            "Dimensions": [
                {
                    "Name": "TestKey2",
                    "Value": "TestValue2"
                },
                {
                    "Name": "TestKey1",
                    "Value": "TestValue1"
                }
            ]
        }
    ]
}
```
## Get
先述の通り、Getには`GetMetricStatistics`と`GetMetricData`のふたつのAPIがありますが、複数のメトリクスを同時に扱うには、`GetMetricData`を使う必要があります。

```sh
$ aws cloudwatch get-metric-data --metric-data-queries '[{"Id":"q1","MetricStat":{"Metric":{"Namespace":"JawsCLI","MetricName":"TestMetric1","Dimensions":[{"Name":"TestKey","Value":"TestValue"}]},"Period":60,"Stat":"Average"}},{"Id":"q2","MetricStat":{"Metric":{"Namespace":"JawsCLI","MetricName":"TestMetric2","Dimensions":[{"Name":"TestKey1","Value":"TestValue1"},{"Name":"TestKey2","Value":"TestValue2"}]},"Period":60,"Stat":"Average"}}]' --start-time $(date -u +"%Y-%m-%dT%H:%M:%SZ" -d "15 min ago") --end-time $(date -u +"%Y-%m-%dT%H:%M:%SZ")
{
    "MetricDataResults": [
        {
            "Id": "q1",
            "Label": "TestValue TestMetric1",
            "Timestamps": [
                "2020-03-02T08:19:00Z"
            ],
            "Values": [
                100.0
            ],
            "StatusCode": "Complete"
        },
        {
            "Id": "q2",
            "Label": "TestValue1 TestValue2 TestMetric2",
            "Timestamps": [
                "2020-03-02T08:19:00Z"
            ],
            "Values": [
                200.0
            ],
            "StatusCode": "Complete"
        }
    ],
    "Messages": []
}

```

Put時とは異なり、`GetMetricData`でメトリクスを複数同時に扱う場合は、`Namespace`、`Metric Name`、`Dimensions`のいずれも全く異なるものを同時に扱うことができます。

# メトリック計算

CloudWatchメトリクスには、複数のメトリクスをもとに算出した値を取得する機能があります。

## Put
ここでは、サンプルとして`Invocations`と`Errors`という2種類のメトリクスを用意して、(Lambda関数のような)エラーレートを算出してみましょう。

先述の他のサンプルのように、`put-metric-data`でまとめてメトリクスをPutしますが、今回は数が多いのでJSONファイルに外出しします。

```sh
$ cat metric-data.json 
[
  {
    "MetricName": "Invocations",
    "Dimensions": [
      {
        "Name": "FunctionName",
        "Value": "MyFunc"
      }
    ],
    "Timestamp": "2020-03-02T08:00:00Z",
    "Value": 10
  },
  { 
    "MetricName": "Invocations",
    "Dimensions": [
      { 
        "Name": "FunctionName",
        "Value": "MyFunc"
      }
    ],
    "Timestamp": "2020-03-02T08:05:00Z",
    "Value": 10
  },
  { 
    "MetricName": "Invocations",
    "Dimensions": [
      { 
        "Name": "FunctionName",
        "Value": "MyFunc"
      }
    ],
    "Timestamp": "2020-03-02T08:10:00Z",
    "Value": 10
  },
  { 
    "MetricName": "Invocations",
    "Dimensions": [
      { 
        "Name": "FunctionName",
        "Value": "MyFunc"
      }
    ],
    "Timestamp": "2020-03-02T08:15:00Z",
    "Value": 10
  },
  { 
    "MetricName": "Invocations",
    "Dimensions": [
      { 
        "Name": "FunctionName",
        "Value": "MyFunc"
      }
    ],
    "Timestamp": "2020-03-02T08:20:00Z",
    "Value": 10
  },
  { 
    "MetricName": "Errors",
    "Dimensions": [
      { 
        "Name": "FunctionName",
        "Value": "MyFunc"
      }
    ],
    "Timestamp": "2020-03-02T08:00:00Z",
    "Value": 3
  },
  { 
    "MetricName": "Errors",
    "Dimensions": [
      { 
        "Name": "FunctionName",
        "Value": "MyFunc"
      }
    ],
    "Timestamp": "2020-03-02T08:05:00Z",
    "Value": 6
  },
  { 
    "MetricName": "Errors",
    "Dimensions": [
      { 
        "Name": "FunctionName",
        "Value": "MyFunc"
      }
    ],
    "Timestamp": "2020-03-02T08:10:00Z",
    "Value": 2
  },
  { 
    "MetricName": "Errors",
    "Dimensions": [
      { 
        "Name": "FunctionName",
        "Value": "MyFunc"
      }
    ],
    "Timestamp": "2020-03-02T08:15:00Z",
    "Value": 9
  },
  { 
    "MetricName": "Errors",
    "Dimensions": [
      { 
        "Name": "FunctionName",
        "Value": "MyFunc"
      }
    ],
    "Timestamp": "2020-03-02T08:20:00Z",
    "Value": 1
  }
]

$ aws cloudwatch put-metric-data --namespace MyApplication --metric-data file://./metric-data.json
```

## Get
では、メトリクス計算により算出された値を取得してみましょう。

まず、あらかじめ算出用のクエリを用意します。
ここでは、`m1`と`m2`に定義したメトリクスに`e1`で`m1/m2`という式を適用しています。

```sh
$ cat metric-data-queries.json 
[
    {
        "Id": "e1",
        "Expression": "m1 / m2",
        "Label": "ErrorRate"
    },
    {
        "Id": "m1",
        "MetricStat": {
            "Metric": {
                "Namespace": "MyApplication",
                "MetricName": "Errors",
                "Dimensions": [
                    {
                        "Name": "FunctionName",
                        "Value": "MyFunc"
                    }
                ]
            },
            "Period": 300,
            "Stat": "Sum"
        },
        "ReturnData": true
    },
    {
        "Id": "m2",
        "MetricStat": {
            "Metric": {
                "Namespace": "MyApplication",
                "MetricName": "Invocations",
                "Dimensions": [
                    {
                        "Name": "FunctionName",
                        "Value": "MyFunc"
                    }
                ]
            },
            "Period": 300,
            "Stat": "Sum"
        },
        "ReturnData": true
    }
]

```

メトリクス計算には`GetMetricData`を使用します。

```sh
$ aws cloudwatch get-metric-data --metric-data-queries file://./metric-data-queries.json --start-time 2020-03-02T08:00:00Z --end-time 2020-03-02T08:30:00Z
{
    "MetricDataResults": [
        {
            "Id": "m1",
            "Label": "Errors",
            "Timestamps": [
                "2020-03-02T08:20:00Z",
                "2020-03-02T08:15:00Z",
                "2020-03-02T08:10:00Z",
                "2020-03-02T08:05:00Z",
                "2020-03-02T08:00:00Z"
            ],
            "Values": [
                2.0,
                18.0,
                4.0,
                12.0,
                6.0
            ],
            "StatusCode": "Complete"
        },
        {
            "Id": "m2",
            "Label": "Invocations",
            "Timestamps": [
                "2020-03-02T08:20:00Z",
                "2020-03-02T08:15:00Z",
                "2020-03-02T08:10:00Z",
                "2020-03-02T08:05:00Z",
                "2020-03-02T08:00:00Z"
            ],
            "Values": [
                20.0,
                20.0,
                20.0,
                20.0,
                20.0
            ],
            "StatusCode": "Complete"
        },
        {
            "Id": "e1",
            "Label": "ErrorRate",
            "Timestamps": [
                "2020-03-02T08:20:00Z",
                "2020-03-02T08:15:00Z",
                "2020-03-02T08:10:00Z",
                "2020-03-02T08:05:00Z",
                "2020-03-02T08:00:00Z"
            ],
            "Values": [
                0.1,
                0.9,
                0.2,
                0.6,
                0.3
            ],
            "StatusCode": "Complete"
        }
    ],
    "Messages": []
}
```

`e1`で返却されたJSONに、算出されたエラーレートが`0.1`, `0.9`等のように確認できます。

# 統計とパーセンタイル
CloudWatchメトリクスは、Putされたデータを生データのままもつわけではなく、すべて統計値として保管します。

これは、CloudWatchメトリクスがポーリング型のメトリクス収集サービスではなく、メトリクスの登録を待ち受けるかたちのサービスであるためです。

例えば、同じ時間帯(`2020-03-02T09:00:00Z`)に異なる値(`3,6,9`)をそれぞれ登録したとき、どうすべきでしょうか？後勝ちにして`9`で上書きしちゃう？上書き禁止にして`3`を採用しちゃう？

CloudWatchではこのような問題は実は起こりません。
なぜなら、すべてのデータを統計値として扱うためです。
この例では、`3,6,9`をすべて登録して統計値を計算し、`サンプル数=3, 平均=6, 最大=9, 最小=6, 合計=12`というデータとして扱う、ということになります。

標準の統計である`SampleCount`, `Maximum`, `Minimum`, `Average`, `Sum`で統計値が変わるのか、さらに異なるパーセンタイル値を指定するとどのような結果が得られるのかを見ていきましょう。

## Put
```sh
aws cloudwatch put-metric-data --namespace JawsCLI --metric-name Test --value 20
aws cloudwatch put-metric-data --namespace JawsCLI --metric-name Test --value 20
aws cloudwatch put-metric-data --namespace JawsCLI --metric-name Test --value 20
aws cloudwatch put-metric-data --namespace JawsCLI --metric-name Test --value 90
aws cloudwatch put-metric-data --namespace JawsCLI --metric-name Test --value 20
```

## Get
### 統計(標準)
ここでは`get-metric-statistics`コマンドの`--statistics`に異なる統計をそれぞれ指定した結果をみていきます。

```sh
$ aws cloudwatch get-metric-statistics --namespace JawsCLI --metric-name Test --start-time $(date -u +"%Y-%m-%dT%H:%M:%SZ" -d "15 min ago") --end-time $(date -u +"%Y-%m-%dT%H:%M:%SZ") --period 300 --statistics SampleCount
{
    "Label": "Test",
    "Datapoints": [
        {
            "Timestamp": "2020-03-02T08:55:00Z",
            "SampleCount": 5.0,
            "Unit": "None"
        }
    ]
}
$ aws cloudwatch get-metric-statistics --namespace JawsCLI --metric-name Test --start-time $(date -u +"%Y-%m-%dT%H:%M:%SZ" -d "15 min ago") --end-time $(date -u +"%Y-%m-%dT%H:%M:%SZ") --period 300 --statistics Maximum
{
    "Label": "Test",
    "Datapoints": [
        {
            "Timestamp": "2020-03-02T08:55:00Z",
            "Maximum": 90.0,
            "Unit": "None"
        }
    ]
}
$ aws cloudwatch get-metric-statistics --namespace JawsCLI --metric-name Test --start-time $(date -u +"%Y-%m-%dT%H:%M:%SZ" -d "15 min ago") --end-time $(date -u +"%Y-%m-%dT%H:%M:%SZ") --period 300 --statistics Minimum
{
    "Label": "Test",
    "Datapoints": [
        {
            "Timestamp": "2020-03-02T08:55:00Z",
            "Minimum": 20.0,
            "Unit": "None"
        }
    ]
}
$ aws cloudwatch get-metric-statistics --namespace JawsCLI --metric-name Test --start-time $(date -u +"%Y-%m-%dT%H:%M:%SZ" -d "15 min ago") --end-time $(date -u +"%Y-%m-%dT%H:%M:%SZ") --period 300 --statistics Average
{
    "Label": "Test",
    "Datapoints": [
        {
            "Timestamp": "2020-03-02T08:55:00Z",
            "Average": 34.0,
            "Unit": "None"
        }
    ]
}
$ aws cloudwatch get-metric-statistics --namespace JawsCLI --metric-name Test --start-time $(date -u +"%Y-%m-%dT%H:%M:%SZ" -d "15 min ago") --end-time $(date -u +"%Y-%m-%dT%H:%M:%SZ") --period 300 --statistics Sum
{
    "Label": "Test",
    "Datapoints": [
        {
            "Timestamp": "2020-03-02T08:55:00Z",
            "Sum": 170.0,
            "Unit": "None"
        }
    ]
}
```

### 統計(パーセンタイル)
ここでは`get-metric-statistics`コマンドの`--extended-statistics`に異なるパーセンタイル値をそれぞれ指定した結果をみていきます。

```sh
$ aws cloudwatch get-metric-statistics --namespace JawsCLI --metric-name Test --start-time $(date -u +"%Y-%m-%dT%H:%M:%SZ" -d "15 min ago") --end-time $(date -u +"%Y-%m-%dT%H:%M:%SZ") --period 300 --extended-statistics p95
{
    "Label": "Test",
    "Datapoints": [
        {
            "Timestamp": "2020-03-02T08:56:00Z",
            "Unit": "None",
            "ExtendedStatistics": {
                "p95": 89.54594678486895
            }
        }
    ]
}
$ aws cloudwatch get-metric-statistics --namespace JawsCLI --metric-name Test --start-time $(date -u +"%Y-%m-%dT%H:%M:%SZ" -d "15 min ago") --end-time $(date -u +"%Y-%m-%dT%H:%M:%SZ") --period 300 --extended-statistics p90
{
    "Label": "Test",
    "Datapoints": [
        {
            "Timestamp": "2020-03-02T08:57:00Z",
            "Unit": "None",
            "ExtendedStatistics": {
                "p90": 89.09418428442875
            }
        }
    ]
}
$ aws cloudwatch get-metric-statistics --namespace JawsCLI --metric-name Test --start-time $(date -u +"%Y-%m-%dT%H:%M:%SZ" -d "15 min ago") --end-time $(date -u +"%Y-%m-%dT%H:%M:%SZ") --period 300 --extended-statistics p50
{
    "Label": "Test",
    "Datapoints": [
        {
            "Timestamp": "2020-03-02T08:57:00Z",
            "Unit": "None",
            "ExtendedStatistics": {
                "p50": 20.689021529142646
            }
        }
    ]
}
$ aws cloudwatch get-metric-statistics --namespace JawsCLI --metric-name Test --start-time $(date -u +"%Y-%m-%dT%H:%M:%SZ" -d "15 min ago") --end-time $(date -u +"%Y-%m-%dT%H:%M:%SZ") --period 300 --extended-statistics p10
{
    "Label": "Test",
    "Datapoints": [
        {
            "Timestamp": "2020-03-02T08:57:00Z",
            "Unit": "None",
            "ExtendedStatistics": {
                "p10": 20.135943634260624
            }
        }
    ]
}

```

ここで、10パーセンタイル値として返却された`20.135943634260624`みたいな値は実際にPutしたデータではなく、算出されたパーセンタイル値であることが見て取れるかと思います。

# おわりに
今回は、JAWS-UG CLI専門支部への参加のお土産（？）として、CloudWatchの各機能をAWS CLIで扱うサンプルを書いてみました。
CloudWatchはもちろんサービスとしても便利ですが、数値をもつ時系列データベースのような使い方もできちゃう柔軟性の高いサービスですので、AWS CLIとの相性がとてもよいかと思います。
ぜひAWS CLIでCloudWatchメトリクスを使い倒してみていただけたら幸いです。