CloudWatch Metricsの細かすぎて誰にも伝わらない話

*本記事は個人の見解であり、所属組織の立場、意見を代表するものではありません.*

CloudWatch Metrics ってご存知ですか？
そう、アレです。AWS 使ってると CPU とか Network とかメトリクスが溜まってってグラフで見れるアレ。
今回は OpsJAWS Advent Calendar 12/12 の記事ということで、CloudWatch Metrics の細かすぎて誰にも伝わらない話をしたいと思います。

# CloudWatch Metrics って何？
まずは何はともあれ公式ドキュメントを読みましょう。
http://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/monitoring/cloudwatch_concepts.html#Metric

・・・だと話が終わってしまうのでいろいろ付け足します。
そもそも、狭義の CloudWatch Metrics とは時系列データの **入れ物** です。
そこに AWS サービスの各サービスがメトリクスを送信したり、ユーザが自分でほしいと思ったカスタムメトリクスを送信することで、メトリクス監視システムとなっているわけです。
ここで大事なのは CloudWatch Metrics 自体にメトリクス収集の機能はないということです。入れ物ですからね。
これを踏まえて考えると Metrics の API や Alarm の動作がなぜこのようになっているのか、イメージしやすくなるでしょう。

# CloudWatch Metrics API
さて、実は CloudWatch Metrics は 3つしか API をもっていません。
- GetMetricStatistics
- ListMetrics
- PutMetricData

とってくるやつ (GetMetricStatistics) と、ならべるやつ (ListMetrics) と、入れるやつ (PutMetricData) ですね。
データベースみたいですよね。

PutMetricData API とはカスタムメトリクスを CloudWatch Metrics の入れ物の中に送信して登録する API です。
例えば、EC2 インスタンス上でデフォルトのメトリクスとしては存在しないメモリ使用率を cron で定期的に free コマンド叩いて送信したり、あるいはアプリケーションのエラーが発生した回数を送信したり、株価をリアルタイムに収集して送信したり、基本的な数値データの時系列の集合であれば何にでも使えます。

試しに AWS CLI で PutMetricData API を呼ぶとこんな感じになります。

```Bash
$ aws cloudwatch put-metric-data --namespace OpsJAWS --metric-name AdventCalendar2017 --dimensions Date=20171212 --value 100
```

ちゃんと登録されてるか ListMetrics で確認してみましょう。

```Bash
$ aws cloudwatch list-metrics --namespace OpsJAWS
{
    "Metrics": [
        {
            "Namespace": "OpsJAWS",
            "Dimensions": [
                {
                    "Name": "Date",
                    "Value": "20171212"
                }
            ],
            "MetricName": "AdventCalendar2017"
        }
    ]
}
```

いい感じですね。値も GetMetricStatistics でとってみましょう。

```Bash
$ aws cloudwatch get-metric-statistics --namespace OpsJAWS --metric-name AdventCalendar2017 --dimensions Name=Date,Value=20171212 --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")
{
    "Datapoints": [
        {
            "Timestamp": "2017-12-12T05:44:00Z",
            "Average": 100.0,
            "Unit": "None"
        }
    ],
    "Label": "AdventCalendar2017"
}
```

ちゃんとメトリクスの値をとることができました。

# PutMetricData API の細かすぎるかもしれない話
前置きが長くなりましたが、ここから PutMetricData API の細かすぎて伝わらない話をしましょう。

この PutMetricData API ですが、実は 1回の API コールで複数のメトリクスデータポイントをまとめて登録することができるようになっています。
このときは --value ではなく --metric-data でメトリクスデータを記述した JSON を指定します。

```
$ cat metrics.json
[
  {
    "MetricName": "AdventCalendar2017",
    "Dimensions": [
      {
        "Name": "Date",
        "Value": "20171212"
      }
    ],
    "Value": 100
  },
  {
    "MetricName": "AdventCalendar2017",
    "Dimensions": [
      {
        "Name": "Date",
        "Value": "20171212"
      },
      {
        "Name": "Category",
        "Value": "CloudWatch"
      }
    ],
    "Value": 200
  },
  {
    "MetricName": "AdventCalendar2017-PageView",
    "Dimensions": [
      {
        "Name": "Date",
        "Value": "20171212"
      }
    ],
    "Value": 100000
  }
]

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

```

このテストデータのポイントは以下の 2点です。
- 異なる Metric Name がある
- 異なる Dimension がある

それではちゃんと登録されたか見てみましょう。

```Bash
$ aws cloudwatch list-metrics --namespace OpsJAWS                               {
    "Metrics": [
        {
            "Namespace": "OpsJAWS",
            "Dimensions": [
                {
                    "Name": "Date",
                    "Value": "20171212"
                }
            ],
            "MetricName": "AdventCalendar2017"
        },
        {
            "Namespace": "OpsJAWS",
            "Dimensions": [
                {
                    "Name": "Category",
                    "Value": "CloudWatch"
                },
                {
                    "Name": "Date",
                    "Value": "20171212"
                }
            ],
            "MetricName": "AdventCalendar2017"
        },
        {
            "Namespace": "OpsJAWS",
            "Dimensions": [
                {
                    "Name": "Date",
                    "Value": "20171212"
                }
            ],
            "MetricName": "AdventCalendar2017-PageView"
        }
    ]
}
```

メタデータはちゃんと入ったみたいですね。
では、値を適当に選んで拾ってみましょう。

```Bash
$ aws cloudwatch get-metric-statistics --namespace OpsJAWS --metric-name AdventCalendar2017-PageView --dimensions Name=Date,Value=20171212 --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")
{
    "Datapoints": [
        {
            "Timestamp": "2017-12-12T06:06:00Z",
            "Average": 100000.0,
            "Unit": "None"
        }
    ],
    "Label": "AdventCalendar2017-PageView"
}
```

登録されてることが確認できました！

つまり、CloudWatch Metrics について以下のことがわかりました。
- PutMetricData API 1回で複数のメトリクスデータポイントを登録できること
- 同じ Namespace であれば異なる Metric Name, Dimension の組み合わせであっても 1回の PutMetricData に同梱できること

## 何が嬉しいの？

PutMetricData API には、他の多くの AWS API と同様、秒間の API 実行回数の上限があります。
また、CloudWatch Metrics は PutMetricData を含めたAPI コールに料金が発生します。
ですので、PutMetricData でできるだけたくさんのメトリクスデータポイントをまとめて登録すると、上限にあたることを回避したり、コストを節約できてオトクだったりするわけです。

# PutMetricData API のさらに細かすぎて誰にも伝わらない話
この PutMetricData API ですが、実はメトリクスデータポイントの timestamp を指定して入れることができるようになっています。
これまでの例では指定してなかったので、自動的に現在日時が入ってたわけです。
で、この timestamp ですが、2週間前から 2時間未来まで指定できたりします。

では早速では、未来の日時を入れてみましょうか。1時間未来くらいがいいかな。

```Bash
$ aws cloudwatch put-metric-data --namespace OpsJAWS --metric-name AdventCalendar2017 --dimensions Date=20171212 --timestamp $(date -u +"%Y-%m-%dT%H:%M:%SZ" -d "1 hour") --value 23456
```

取り出してみましょう。

```Bash
$ aws cloudwatch get-metric-statistics --namespace OpsJAWS --metric-name AdventCalendar2017 --dimensions Name=Date,Value=20171212 --period 300 --statistics Average --start-time $(date -u +"%Y-%m-%dT%H:%M:%SZ" -d "50 min") --end-time $(date -u +"%Y-%m-%dT%H:%M:%SZ" -d "2 hours")
{
    "Datapoints": [
        {
            "Timestamp": "2017-12-12T07:32:00Z",
            "Average": 23456.0,
            "Unit": "None"
        }
    ],
    "Label": "AdventCalendar2017"
}
```
とれました。

今度は過去ということで、10日前のデータでも入れてみましょうか。

```Bash
$ aws cloudwatch put-metric-data --namespace OpsJAWS --metric-name AdventCalendar2017 --dimensions Date=20171212 --timestamp $(date -u +"%Y-%m-%dT%H:%M:%SZ" -d "10 days ago") --value 12345
```

取り出してみましょう。

```Bash
$ aws cloudwatch get-metric-statistics --namespace OpsJAWS --metric-name AdventCalendar2017 --dimensions Name=Date,Value=20171212 --period 300 --statistics Average --start-time $(date -u +"%Y-%m-%dT%H:%M:%SZ" -d "11 days ago") --end-time $(date -u +"%Y-%m-%dT%H:%M:%SZ" -d "9 days ago")
{
    "Datapoints": [],
    "Label": "AdventCalendar2017"
}
```

あれっ・・・？空？

## どういうことなの？
実は AWS CLI のドキュメントに答えがあります。

http://docs.aws.amazon.com/cli/latest/reference/cloudwatch/put-metric-data.html
> Data points with time stamps from 24 hours ago or longer can take at least 48 hours to become available for get-metric-statistics from the time they are submitted.

つまり、古い時間帯を指定して登録したメトリクスデータはすぐに見れないよ、ということですね。
サンプルコマンドとかで古い日時が静的に指定されてたりすると、後から実行したときに「あれ？」となるので注意が必要なわけですね。

# おわりに

今回は OpsJAWS Advent Calendar 12/12 の記事ということで、CloudWatch Metrics の細かすぎて誰にも伝わらない(かもしれない)話をしました。
みなさんもぜひ CloudWatch Metrics をうまいこと活用して楽しいメトリクスライフ？を送りましょう。