GCP無料枠の消費状況を確認するDataStudioレポートを作った

みなさん、Gooble Cloud Platform (GCP)使ってますか？私は今年3月の無料枠（Always Free Products）強化のタイミングから便利に使わせて頂いております。といっても、これまでGoogleさんに支払った額は1円です。ろくでもないユーザーでごめんなさい。

有用かどうか若干あやしい[^1]記事「[GCEのf1-microインスタンスを真にタダで使う方法](https://qiita.com/hnw/items/a4e395d047b382e7dc5f)」についても多くの方に読んで頂いているようです。やはり皆さん無料という言葉には弱いようですね。

[^1]: sshdへのポートスキャンで課金が発生するかも？という指摘をしたのですが、現実には1円分も攻撃されないのではないかと想像しています。実際に課金されたよ！という方がいらしたら教えてください

そんなGCPを使ってみての感想ですが、「今のペースで使うと課金発生しそうか？」がわかりにくいように感じています[^kakin]。企業ユーザーであれば事前に「[Google Cloud Platform 料金計算ツール](https://cloud.google.com/products/calculator/)」で見積もっておくこともできるでしょうが、我々ド素人が試しに使うのに事前見積りが必須となると面倒すぎます。「このペースだと課金されますよ」「おっ、じゃあ停止するわ」くらいのカジュアルさで試したいというのが正直なところでしょう。

ところがそのような機能は公式に無さそうだったので、BigQuery+Google DataStudioで課金レポートを作ってみました、というのが本稿の内容です。

このレポートを自分の手元でも動かしたい人のための導入手順と、今回やり残したことについても紹介します。

[^kakin]: （2018/10追記）最近ダッシュボードに請求金額の予測機能が追加されました。もはやこのレポートも不要かもしれません。

## 作った課金レポートの紹介

私が作った課金レポートは下記URLをご確認ください。私の実際の利用状況が表示されます。

- [GCP無料枠レポート](https://datastudio.google.com/open/1biEGUETBohNPF8QPEUAg0MQExw1t1vo7)

1ページ目には今月分と先月分のリソースごとの利用量が表示されています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/46856/216dd434-6d39-636a-0048-eeb7538434db.png)

消化率というのは現時点で無料枠の何%を利用したかを表す数字です。当月の推定消化率は月末時点での消化率を推定した値で、単純にそれまでの1日平均消化率に当月の日数をかけ算したものです。

2ページ目から5ページ目まではプロダクトごとの利用状況をグラフ化して表示しています。下記はBigQueryのレポートです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/46856/bea1c6b5-21fb-20e4-1ba6-f7820ed82dfe.png)

これらの情報があれば月末に課金されそうなサービスの想像がつきますから、いったん特定プロジェクトを停止したり、料金計算ツールを使って課金額を見積もったりといったことができるはずです。

## レポートの導入方法

自分も同じレポートを使いたい！と思った方のために、導入手順を紹介します。

まず、GCPのWebコンソールから「お支払い」「課金データのエクスポート」「BigQueryエクスポート」からBigQUeryへの課金ログのエクスポート設定を行ってください。

BigQueryというと100万円単位でお金を溶かしそうなイメージがあって怖いかもしれませんが、実際には千円使うのさえ難しいので安心してください。特に今回使う課金ログはBigQueryで扱うには非常に小さいデータなので、無料枠（ストレージ10GB・検索1TB）の10%も使わないはずです。

エクスポート設定後にBigQueryにデータが格納されるまで多少タイムラグがあるので、数時間から半日程度待ってください。その後、先ほどのDataStudioレポートを開いて「このレポートのコピーを作成します」を選びます。すると、「新しいレポートに追加するデータソースを選択します」というダイアログが表示されますので、「新しいデータソースを作成する」「BigQuery」「カスタムクエリ」から次のように入力します。

```sql
WITH t2 AS (
  SELECT
    sku_id,
    EXTRACT(DAY FROM last_usage_time) AS days_in_curr_month,
    EXTRACT(DAY FROM DATE_SUB(DATE_ADD(DATE_TRUNC(DATE(last_usage_time), MONTH),INTERVAL 1 MONTH),INTERVAL 1 DAY)) / EXTRACT(DAY FROM last_usage_time) AS month_usage_estimate_ratio,
    CASE
    -- Compute Engine
    WHEN sku_id='5CE5-087B-6C3C' THEN 720.0 -- Micro instance with burstable CPU running in Americas (hours/month)
    WHEN sku_id='D973-5D65-BAB2' THEN 921600.0 -- Storage PD Capacity (MB-days/month)
    WHEN sku_id='F274-1692-F213' THEN 1024.0 -- Network Internet Egress from Americas to Americas (MB/month)
    WHEN sku_id='DFA5-B5C6-36D6' THEN 1024.0 -- Network Internet Egress from Americas to EMEA (MB/month)
    -- Cloud Storage
    WHEN sku_id='4DBF-185F-A415' THEN 5000.0 -- Class A Request Regional Storage (requests/month)
    WHEN sku_id='7870-010B-2763' THEN 50000.0 -- Class B Request Regional Storage (requests/month)
    WHEN sku_id='1F8B-71B0-3D1B' THEN 1024.0 -- Download APAC (MB/month)
    WHEN sku_id='22EB-AAE8-FBCD' THEN 1024.0 -- Download US EMEA (MB/month)
    WHEN sku_id='E5F0-6A5D-7BAD' THEN 153600.0 -- Regional Storage US (MB-day/month)
    -- BigQuery
    WHEN sku_id='947D-3B46-7781' THEN 10240.0 -- Active Storage (MB-day/month)
    WHEN sku_id='1DF5-1F98-1DD1' THEN 1048576.0 -- Analysis (MB/month)
    WHEN sku_id='993F-6B6B-DCC4' THEN 10240.0 -- Long Term Storage (MB-day/month)
    -- App Engine
    WHEN sku_id='E2EB-F679-D108' THEN 840.0 -- Frontend Instances (hours/month)
    WHEN sku_id='A5BD-69A4-8428' THEN 840.0 -- Frontend Instances Japan (hours/month)
    WHEN sku_id='EF56-1B38-498D' THEN 270.0 -- Backend Instances (hours/month)
    WHEN sku_id='C491-F798-D2C4' THEN 270.0 -- Backend Instances Japan (hours/month)
    WHEN sku_id='37F0-8E44-4CAA' THEN 30720.0 -- Out Bandwidth (MB/month)
    WHEN sku_id='93B0-477F-A22B' THEN 30720.0 -- Out Bandwidth Japan (MB/month)
    -- DataStore
    WHEN sku_id='EA09-CCF0-5B81' THEN 30720.0 -- Cloud Datastore Storage (MB-days/month)
    WHEN sku_id='B081-0F36-8FC9' THEN 30720.0 -- Cloud Datastore Storage Japan (MB-days/month)
    WHEN sku_id='000A-BC9A-4DEE' THEN 1500000.0 -- Cloud Datastore Read Ops (requests/month)
    WHEN sku_id='5AF1-F078-409A' THEN 1500000.0 -- Cloud Datastore Read Ops Japan (requests/month)
    ELSE 1E300 END AS free_tier_limit
  FROM ( SELECT sku.id as sku_id, TIMESTAMP_ADD(max(usage_start_time), INTERVAL 7 HOUR) as last_usage_time FROM `****.billing.gcp_billing_export_v1_******_******_******` GROUP BY sku_id )
)
SELECT
  t1.billing_account_id,
  t1.service.id AS service_id,
  t1.service.description AS service_description,
  t1.sku.id AS sku_id,
  t1.sku.description AS sku_description,
  t1.usage_start_time,
  t1.usage_end_time,
  TIMESTAMP_ADD(t1.usage_start_time, INTERVAL 7 HOUR) as billing_time,
  TIMESTAMP_ADD(TIMESTAMP_TRUNC(TIMESTAMP_ADD(t1.usage_end_time, INTERVAL 7 HOUR), MONTH), INTERVAL 7 HOUR) as first_day_month,
  TIMESTAMP_ADD(TIMESTAMP_TRUNC(TIMESTAMP_ADD(TIMESTAMP_TRUNC(TIMESTAMP_ADD(t1.usage_end_time, INTERVAL 7 HOUR), MONTH), INTERVAL 768 HOUR), MONTH), INTERVAL 7 HOUR) as first_day_next_month,
  EXTRACT(DAY FROM DATE_SUB(DATE_ADD(DATE_TRUNC(DATE(TIMESTAMP_ADD(t1.usage_start_time, INTERVAL 7 HOUR)), MONTH),INTERVAL 1 MONTH),INTERVAL 1 DAY)) as day_in_month,
  t1.export_time,
  TIMESTAMP_ADD(t1.export_time, INTERVAL -9 HOUR) as export_time_jst,
  t1.project.id AS project_id,
  t1.project.name AS project_name,
  t1.cost,
  t1.currency,
  t1.currency_conversion_rate,
  CASE WHEN t1.usage.unit = 'byte-seconds' THEN t1.usage.amount / 90596966400.0 WHEN t1.usage.unit = 'bytes' THEN t1.usage.amount / 1048576.0 WHEN t1.usage.unit = 'seconds' THEN t1.usage.amount / 3600.0 ELSE t1.usage.amount END AS usage_amount,
  CASE WHEN t1.usage.unit = 'byte-seconds' THEN 'MB-days' WHEN t1.usage.unit = 'bytes' THEN 'MB' WHEN t1.usage.unit = 'seconds' THEN 'hour' ELSE t1.usage.unit END AS usage_unit,
  (CASE WHEN t1.usage.unit = 'byte-seconds' THEN t1.usage.amount / 90596966400.0 WHEN t1.usage.unit = 'bytes' THEN t1.usage.amount / 1048576.0 WHEN t1.usage.unit = 'seconds' THEN t1.usage.amount / 3600.0 ELSE t1.usage.amount END) / t2.free_tier_limit AS usage_rate,
  (CASE WHEN t1.usage.unit = 'byte-seconds' THEN t1.usage.amount / 90596966400.0 WHEN t1.usage.unit = 'bytes' THEN t1.usage.amount / 1048576.0 WHEN t1.usage.unit = 'seconds' THEN t1.usage.amount / 3600.0 ELSE t1.usage.amount END) * t2.month_usage_estimate_ratio AS estimated_usage_amount,
  (CASE WHEN t1.usage.unit = 'byte-seconds' THEN t1.usage.amount / 90596966400.0 WHEN t1.usage.unit = 'bytes' THEN t1.usage.amount / 1048576.0 WHEN t1.usage.unit = 'seconds' THEN t1.usage.amount / 3600.0 ELSE t1.usage.amount END) * t2.month_usage_estimate_ratio / t2.free_tier_limit AS estimated_usage_rate,
  t2.free_tier_limit,
  t2.days_in_curr_month
FROM
  `****.billing.gcp_billing_export_v1_******_******_******` AS t1
  JOIN
  t2
  ON t1.sku.id = t2.sku_id
```

その際、FROM句のテーブル名はご自身の課金ログのテーブル名に合わせて書き換えてください（2カ所あります）。

これだけで基本的には動くかと思いますが、動かないようなら教えてください。

## やり残したこと

本当はやりたかったけどできていない点は下記の通りです。私が若干飽きてしまって自分ではこれ以上触らないと思いますので、気が向いた方はぜひ修正にチャレンジしてみてください。

### リソースごとの無料枠がSQL中に手書きしてあり、私が使っていないサービスに対応できていない

現状、リソースごとの無料枠のサイズがSKU IDごとにSQL中に手書きしてあります。これは下記URLから取得した値を元にしています。

- https://cloud.google.com/skus/

本来ならAPIから自動取得するなどした上で、別テーブルとして管理すべき情報でしょう。現状では私が必要な分を手打ちで書いただけなので、Cloud Pub/SubやCloud Functionsの利用状況が知りたい場合は別途追加が必要です。

### 月末の推定消化率でなく、推定課金額を表示すべき

現在のレポートだと無料枠を超過した場合に何円支払うことになるかがわかりませんが、このレポートが欲しい人が本当に欲しい情報はむしろ金額の方だと思うので、可能なら修正したいところです。

### レポートがイケてない

「[Public Billing Report Demo](https://datastudio.google.com/open/0B7GT7ZlyzUmCZHFhNDlKVENHYmc?hl=ja)」をはじめGoogleさんが配っているサンプルレポートはカッコいいんですが、私が作るとどうにもイケてないなーと思います。どんな情報をどう出すかはセンスが要求されますね…。


## 感想など

今回私はBigQueryとDataStudio両方とも初体験だったので、それらの所感も書き残しておきます。

まず、「BigQueryで大金を溶かすのは難しい」ことを実感しました。これはBigQueryに触ったことのある人なら共通の感想だと思いますが、私も改めて主張しておきたいと思います。

また、BigQueryにGCP課金ログを出しておくのはオススメです。上記のレポートを使わない場合でも、課金関連の情報をSQL文で調べられるのが便利だったりします。私はGCSへのファイルエクスポートと併用しています。

DataStudioについて言うと、表示するデータを加工せずに集計・可視化したい場合は非常に便利なツールだと思います。ただ、少しでもプログラマブルな要求が出てくるとSQLで頑張る必要があるので、かなりの試行錯誤が必要になってきます。今回のレポートではタイムゾーンの変換をSQLで頑張るのが面倒でした[^2]。

[^2]: DataStudioの日付の区切りはGMT+0であるのに対し、GCPの課金の日付区切りはPST（アメリカ西海岸）であるため。上記SQLだとPDT（夏時間）で計算してしまっているので今だと1時間ズレているバグがあることに今気づきました…。