Cloudflare GraphQL API からトラフィック量を過去1年に遡って集計する

# Cloudflare の Analytics データ

[Cloudflare Analytics](https://developers.cloudflare.com/analytics/) では、以下のようにダッシュボードで確認できるデータを提供しています。

そのデータは [GraphQL Analytics API](https://developers.cloudflare.com/analytics/graphql-api/) を使って取り出すことができます。

利用可能な [Datasets](https://developers.cloudflare.com/analytics/graphql-api/features/data-sets/) の中には、ダッシュボードで確認することができない（UI 未実装や Beta の）ものもあり、活用できるデータの宝庫になっています。

![image-20221014011807952](https://cdn.jsdelivr.net/gh/kyouheicf/image@master/uPic/image-20221014011807952.png)

# ログとの違い

では [Cloudflare Logs](https://developers.cloudflare.com/logs/) との違いはどこにあるのでしょうか。

Analytics データは

- [Sampling](https://developers.cloudflare.com/analytics/graphql-api/sampling/) される可能性があります。`Adaptive` と名の付くデータセットは動的にサンプリングレートが調整され、サンプルデータから全体を推測する倍率をかけたデータとなります。一方でログはユーザ自身でサンプリングを制御でき、すべてのデータにアクセスが可能です。
- 集計データが提供されます。`Groups` と名の付くデータセットは、すでに集計された形でデータ提供されるため、ログから集計する手間が省ける点で便利なことがあります。

# Analytics データ保持期間

「[Limits · Cloudflare Analytics docs](https://developers.cloudflare.com/analytics/graphql-api/limits/)」にある通り、データセットやプランによってデータ保持期間が変わってきます。

> [Cloudflare GraphQL データセットの保管期間を確認する - Qiita](https://qiita.com/khayama/items/4df78c2c8ce24f14d373)
> 以前はこのような保管期間がドキュメントで公開されていましたが、現時点ではドキュメントで確認できないため、自身で該当データを取得し、確認する必要があります。

その中でも、特に `httpRequests1dGroups` は過去1年に遡って確認できる集計データとして活用の余地があります。

また、データセットとして対応していても、ダッシュボードで遡れるデータ範囲も限られている（＋ UI であまり遡ると取得に時間がかかることもある）ため、GraphQL API を使ってデータを取得することで最大限の活用が可能です。

![image-20221014013347556](https://cdn.jsdelivr.net/gh/kyouheicf/image@master/uPic/image-20221014013347556.png)

# `httpRequests1dGroups` の取得例

以下の変数をセットし

```shell
export GQL_ENDPOINT='https://api.cloudflare.com/client/v4/graphql'
export ACCOUNT_ID='YOUR_ACCOUNT_ID'
export ACCOUNT_ID2='YOUR_ACCOUNT_ID2'
export ZONE_ID='YOUR_ZONE_ID'
```

GraphQL API をコールすることで Analytics データを取得できます。

スキーマの探索は「[Explore the Analytics schema with GraphiQL](https://developers.cloudflare.com/analytics/graphql-api/getting-started/explore-graphql-schema/)」が参考になります。

```shell:アカウントを対象とする場合
http -A creds $GQL_ENDPOINT query='query
{
  viewer {
    accounts(filter: {accountTag: "'${ACCOUNT_ID}'"}) {
      httpRequests1dGroups (limit:32,filter:{date_geq:"2022-09-01",date_lt:"2022-10-01"},orderBy:[date_ASC]) {
        sum {
          bytes,
          cachedBytes,
          cachedRequests,
          encryptedBytes,
          encryptedRequests,
          pageViews,
          requests,
          threats
        }
        dimensions {
          date
        }
      }
    }
  }
}
' | jq -r '.data.viewer.accounts[].httpRequests1dGroups[]'
```

```shell:ゾーンを対象とする場合
http -A creds $GQL_ENDPOINT query='query
{
  viewer {
    zones(filter: {zoneTag: "'${ZONE_ID}'"}) {
      httpRequests1dGroups (limit:32,filter:{date_geq:"2022-09-01",date_lt:"2022-10-01"},orderBy:[date_ASC]) {
        sum {
          bytes,
          cachedBytes,
          cachedRequests,
          encryptedBytes,
          encryptedRequests,
          pageViews,
          requests,
          threats
        }
        dimensions {
          date
        }
      }
    }
  }
}
' | jq -r '.data.viewer.zones[].httpRequests1dGroups[]'
```

2022 年 9 月を対象として、1日ごとに集計されたデータのリストが確認できます。

```json:result.json
{
  "dimensions": {
    "date": "2022-09-01"
  },
  "sum": {
    "bytes": 60514334,
    "cachedBytes": 60447958,
    "cachedRequests": 15161,
    "encryptedBytes": 60466295,
    "encryptedRequests": 15165,
    "pageViews": 12,
    "requests": 15205,
    "threats": 1
  }
}
{
  "dimensions": {
    "date": "2022-09-02"
  },
  "sum": {
    "bytes": 1622933,
    "cachedBytes": 987969,
    "cachedRequests": 447,
    "encryptedBytes": 1297520,
    "encryptedRequests": 568,
    "pageViews": 123,
    "requests": 827,
    "threats": 82
  }
}
...
{
  "dimensions": {
    "date": "2022-09-30"
  },
  "sum": {
    "bytes": 715675,
    "cachedBytes": 300188,
    "cachedRequests": 121,
    "encryptedBytes": 547365,
    "encryptedRequests": 146,
    "pageViews": 44,
    "requests": 247,
    "threats": 2
  }
}
```

# 前月（`N-1` 月）の集計

月間トラフィック量を把握するためにさらに整理して集計します。

```shell
% date -v -1m +"%Y-%m-01"
2022-09-01
% date +"%Y-%m-01"
2022-10-01
```

```shell:アカウントを対象とする場合
http -A creds $GQL_ENDPOINT query='query
{
  viewer {
    accounts(filter: {accountTag: "'${ACCOUNT_ID}'"}) {
      httpRequests1dGroups (limit:32,filter:{date_geq:"'$(date -v -1m +"%Y-%m-01")'",date_lt:"'$(date +"%Y-%m-01")'"},orderBy:[date_ASC]) {
        sum {
          bytes,
          cachedBytes,
          cachedRequests,
          encryptedBytes,
          encryptedRequests,
          pageViews,
          requests,
          threats
        }
        dimensions {
          date
        }
      }
    }
  }
}
' | jq -r '[.data.viewer.accounts[].httpRequests1dGroups[].sum | to_entries[] ] | group_by(.key) | map({key: .[0].key,value: map(.value) | add}) | from_entries'
```

```shell:複数アカウントまとめて対象とする場合
http -A creds $GQL_ENDPOINT query='query
{
  viewer {
    account1:accounts(filter: {accountTag: "'${ACCOUNT_ID}'"}) {
      httpRequests1dGroups (limit:32,filter:{date_geq:"'$(date -v -1m +"%Y-%m-01")'",date_lt:"'$(date +"%Y-%m-01")'"},orderBy:[date_ASC]) {
        sum {
          bytes,
          cachedBytes,
          cachedRequests,
          encryptedBytes,
          encryptedRequests,
          pageViews,
          requests,
          threats
        }
        dimensions {
          date
        }
      }
    },
    account2:accounts(filter: {accountTag: "'${ACCOUNT_ID2}'"}) {
      httpRequests1dGroups (limit:32,filter:{date_geq:"'$(date -v -1m +"%Y-%m-01")'",date_lt:"'$(date +"%Y-%m-01")'"},orderBy:[date_ASC]) {
        sum {
          bytes,
          cachedBytes,
          cachedRequests,
          encryptedBytes,
          encryptedRequests,
          pageViews,
          requests,
          threats
        }
        dimensions {
          date
        }
      }
    }
  }
}
' | jq -r '[.data.viewer[][].httpRequests1dGroups[].sum | to_entries[] ] | group_by(.key) | map({key: .[0].key,value: map(.value) | add}) | from_entries'
```

```shell:ゾーンを対象とする場合
http -A creds $GQL_ENDPOINT query='query
{
  viewer {
    zones(filter: {zoneTag: "'${ZONE_ID}'"}) {
      httpRequests1dGroups (limit:32,filter:{date_geq:"'$(date -v -1m +"%Y-%m-01")'",date_lt:"'$(date +"%Y-%m-01")'"},orderBy:[date_ASC]) {
        sum {
          bytes,
          cachedBytes,
          cachedRequests,
          encryptedBytes,
          encryptedRequests,
          pageViews,
          requests,
          threats
        }
        dimensions {
          date
        }
      }
    }
  }
}
' | jq -r '[.data.viewer.zones[].httpRequests1dGroups[].sum | to_entries[] ] | group_by(.key) | map({key: .[0].key,value: map(.value) | add}) | from_entries'
```

前月の月間トラフィック量は 138 MB とすぐに把握できます。

```json:result.json
{
  "bytes": 138328396, # 138 MB
  "cachedBytes": 94041494,
  "cachedRequests": 24703,
  "encryptedBytes": 97772563,
  "encryptedRequests": 23677,
  "pageViews": 1203,
  "requests": 56092,
  "threats": 335
}
```

# 過去11ヶ月分の月間トラフィック量（`bytes`）

過去11ヶ月分の月間トラフィック量について、データを取得します。

```shell:アカウントを対象とする場合
for ((i=0; i<11; i++)); do echo -n "$(date -v -$(expr ${i} + 1)m +"%Y年%m月") (MB) : " && http -A creds $GQL_ENDPOINT query='query
{
  viewer {
    accounts(filter: {accountTag: "'${ACCOUNT_ID}'"}) {
      httpRequests1dGroups (limit:32,filter:{date_geq:"'$(date -v -$(expr ${i} + 1)m +"%Y-%m-01")'",date_lt:"'$(date -v -${i}m +"%Y-%m-01")'"},orderBy:[date_ASC]) {
        sum {
          bytes,
          cachedBytes,
          cachedRequests,
          encryptedBytes,
          encryptedRequests,
          pageViews,
          requests,
          threats
        }
        dimensions {
          date
        }
      }
    }
  }
}
' | jq -r '[.data.viewer.accounts[].httpRequests1dGroups[].sum | to_entries[] ] | group_by(.key) | map({key: .[0].key,value: map(.value) | add}) | from_entries | (.bytes/1000000)' ; done
```

```shell:ゾーンを対象とする場合
for ((i=0; i<11; i++)); do echo -n "$(date -v -$(expr ${i} + 1)m +"%Y年%m月") (MB) : " && http -A creds $GQL_ENDPOINT query='query
{
  viewer {
    zones(filter: {zoneTag: "'${ZONE_ID}'"}) {
      httpRequests1dGroups (limit:32,filter:{date_geq:"'$(date -v -$(expr ${i} + 1)m +"%Y-%m-01")'",date_lt:"'$(date -v -${i}m +"%Y-%m-01")'"},orderBy:[date_ASC]) {
        sum {
          bytes,
          cachedBytes,
          cachedRequests,
          encryptedBytes,
          encryptedRequests,
          pageViews,
          requests,
          threats
        }
        dimensions {
          date
        }
      }
    }
  }
}
' | jq -r '[.data.viewer.zones[].httpRequests1dGroups[].sum | to_entries[] ] | group_by(.key) | map({key: .[0].key,value: map(.value) | add}) | from_entries | (.bytes/1000000)' ; done
```

年間の計画やレポートに活用できるかと思います。

```shell
2022年09月 (MB) : 138.328396
2022年08月 (MB) : 133.454489
2022年07月 (MB) : 19.204661
2022年06月 (MB) : 27.104583
2022年05月 (MB) : 463.646896
2022年04月 (MB) : 472.347435
2022年03月 (MB) : 339.184971
2022年02月 (MB) : 67.263986
2022年01月 (MB) : 469.10907
2021年12月 (MB) : 21.728387
2021年11月 (MB) : 15.443249
```

# 過去11ヶ月分の月間リクエスト数（`requests`）

```shell:アカウントを対象とする場合
for ((i=0; i<11; i++)); do echo -n "$(date -v -$(expr ${i} + 1)m +"%Y年%m月") (リクエスト数) : " && http -A creds $GQL_ENDPOINT query='query
{
  viewer {
    accounts(filter: {accountTag: "'${ACCOUNT_ID}'"}) {
      httpRequests1dGroups (limit:32,filter:{date_geq:"'$(date -v -$(expr ${i} + 1)m +"%Y-%m-01")'",date_lt:"'$(date -v -${i}m +"%Y-%m-01")'"},orderBy:[date_ASC]) {
        sum {
          bytes,
          cachedBytes,
          cachedRequests,
          encryptedBytes,
          encryptedRequests,
          pageViews,
          requests,
          threats
        }
        dimensions {
          date
        }
      }
    }
  }
}
' | jq -r '[.data.viewer.accounts[].httpRequests1dGroups[].sum | to_entries[] ] | group_by(.key) | map({key: .[0].key,value: map(.value) | add}) | from_entries | (.requests)' ; done 
```

```shell:ゾーンを対象とする場合
for ((i=0; i<11; i++)); do echo -n "$(date -v -$(expr ${i} + 1)m +"%Y年%m月") (リクエスト数) : " && http -A creds $GQL_ENDPOINT query='query
{
  viewer {
    zones(filter: {zoneTag: "'${ZONE_ID}'"}) {
      httpRequests1dGroups (limit:32,filter:{date_geq:"'$(date -v -$(expr ${i} + 1)m +"%Y-%m-01")'",date_lt:"'$(date -v -${i}m +"%Y-%m-01")'"},orderBy:[date_ASC]) {
        sum {
          bytes,
          cachedBytes,
          cachedRequests,
          encryptedBytes,
          encryptedRequests,
          pageViews,
          requests,
          threats
        }
        dimensions {
          date
        }
      }
    }
  }
}
' | jq -r '[.data.viewer.zones[].httpRequests1dGroups[].sum | to_entries[] ] | group_by(.key) | map({key: .[0].key,value: map(.value) | add}) | from_entries | (.requests)' ; done 
```

```shell
2022年09月 (リクエスト数) : 56092
2022年08月 (リクエスト数) : 26505
2022年07月 (リクエスト数) : 8530
2022年06月 (リクエスト数) : 12554
2022年05月 (リクエスト数) : 359617
2022年04月 (リクエスト数) : 25629
2022年03月 (リクエスト数) : 41212
2022年02月 (リクエスト数) : 12793
2022年01月 (リクエスト数) : 66101
2021年12月 (リクエスト数) : 9344
2021年11月 (リクエスト数) : 6159
```

# 過去 365 日間の集計

過去1年分について、データを取得します。

```shell
% date -v -365d +"%Y-%m-%d"
2021-10-14
% date +"%Y-%m-%d"
2022-10-14
```

```shell:アカウントを対象とする場合
http -A creds $GQL_ENDPOINT query='query
{
  viewer {
    accounts(filter: {accountTag: "'${ACCOUNT_ID}'"}) {
      httpRequests1dGroups (limit:365,filter:{date_geq:"'$(date -v -364d +"%Y-%m-%d")'",date_leq:"'$(date +"%Y-%m-%d")'"},orderBy:[date_ASC]) {
        sum {
          bytes,
          cachedBytes,
          cachedRequests,
          encryptedBytes,
          encryptedRequests,
          pageViews,
          requests,
          threats
        }
        dimensions {
          date
        }
      }
    }
  }
}
' | jq -r '[.data.viewer.accounts[].httpRequests1dGroups[].sum | to_entries[] ] | group_by(.key) | map({key: .[0].key,value: map(.value) | add}) | from_entries'
```

```shell:ゾーンを対象とする場合
http -A creds $GQL_ENDPOINT query='query
{
  viewer {
    zones(filter: {zoneTag: "'${ZONE_ID}'"}) {
      httpRequests1dGroups (limit:365,filter:{date_geq:"'$(date -v -364d +"%Y-%m-%d")'",date_leq:"'$(date +"%Y-%m-%d")'"},orderBy:[date_ASC]) {
        sum {
          bytes,
          cachedBytes,
          cachedRequests,
          encryptedBytes,
          encryptedRequests,
          pageViews,
          requests,
          threats
        }
        dimensions {
          date
        }
      }
    }
  }
}
' | jq -r '[.data.viewer.zones[].httpRequests1dGroups[].sum | to_entries[] ] | group_by(.key) | map({key: .[0].key,value: map(.value) | add}) | from_entries'
```

こちらも年間の計画やレポートに活用できるかと思います。

```json:result.json
{
  "bytes": 2289883831, # 2.2 GB
  "cachedBytes": 1531715556,
  "cachedRequests": 441301,
  "encryptedBytes": 2204593863,
  "encryptedRequests": 583508,
  "pageViews": 154492,
  "requests": 643433,
  "threats": 282210
}
```

# まとめ

GraphQL API で取得できるデータには、まだまだ活用の余地があると思います。

今回はその一部を確認しましたが、次はチャートを作って見ること等にトライしたいと思います。
↓
Grafana （無料アカウント）で可視化できたので、以下 GitHub リンク内の「HTTP-Requests.json」をご活用ください。
- 手順：[Cloudflare R2 の GraphQL Analytics を Grafana で可視化する - Qiita](https://qiita.com/khayama/items/305bbf005b99b8ab4d39)
- ダッシュボードファイル：https://github.com/kyouheicf/cloudflare-graphql-grafana
![httpRequests](https://user-images.githubusercontent.com/85217388/221065938-b9b3d3d2-a0de-48e2-8e44-f3440eea70e5.png)


# 参考

- [json - How do I sum the values in an array of maps in jq? - Stack Overflow](https://stackoverflow.com/questions/28484534/how-do-i-sum-the-values-in-an-array-of-maps-in-jq)
- [Macのdateコマンドで前日の日付を取得する - Qiita](https://qiita.com/___uhu/items/3c2312359da542cda163)
- [Cloudflare Free プランで Pro プランのような分析情報を得る - Qiita](https://qiita.com/KeioCF/items/2814da7b521951f34c5b)
- [HTTPie を Cloudflare API / GraphQL 用に設定する - Qiita](https://qiita.com/khayama/items/1738c3b98e9c4108a35d)