GitHub API で GitHub Actions の課金時間、平均課金時間を計算する

GitHub Actions で費用面のモニタリング、計測するための機能として、利用レポートを (CSV形式で) ダウンロードできます。

https://docs.github.com/ja/billing/managing-billing-for-github-actions/viewing-your-github-actions-usage

特定の周期、ファイル毎の利用量を把握するのには便利な一方、権限を持ったメンバーしかダウンロードできない、手作業 & メールを確認する必要がある、データが日毎の集計なので Workflow の実行回数などこれだと分からない情報がある、と不便な部分もあります。

GitHub API でも近い情報は取得でき、利用レポートを補完する手段として便利なのですが、結構ややこしい部分もあるので、どうやって取るか、計算するかをまとめます。

### GitHub Actions の料金計算について

GitHub Actions は利用時間に応じた従量課金で、 その料金計算に使うのが課金時間 (Job 毎に分単位で切り上げた利用時間) です。料金計算について詳しくは↓を見てみてください。

https://docs.github.com/ja/billing/managing-billing-for-github-actions/about-billing-for-github-actions

### 課金時間を取得したい → WorkflowRun 単位で課金時間を取得する API

取得するには、

https://docs.github.com/ja/rest/actions/workflow-runs?apiVersion=2022-11-28#get-workflow-run-usage

を使うとよいです。 Workflow 内の Job 毎の課金時間、全体の課金時間 (billable.UBUNTU.total_ms) を把握することができます。

```console
$ gh api /repos/{owner}/{repo}/actions/runs/RUN_ID/timing
{
  "billable": {
    "UBUNTU": {
      "total_ms": 180000,
      "jobs": 2,
      "job_runs": [
        {
          "job_id": 20059762313,
          "duration_ms": 120000
        },
        {
          "job_id": 20059770393,
          "duration_ms": 60000
        }
      ]
    }
  },
  "run_duration_ms": 96000
}

```

URL パラメータのうち、 WORKFLOW_RUN_ID はそれぞれの Workflow の実行 (※以後 WorkflowRun と呼びます) に振られている ID を指します。 (様々なページの URL にも含まれていて、例えば https://github.com/tomoasleep/ot-rb/actions/runs/5515721310 だと `5515721310` の部分です)

WORKFLOW_RUN_ID をプログラム的に取得するには、 WorkflowRun のリストを取得する API を利用したり、

https://docs.github.com/ja/rest/actions/workflow-runs?apiVersion=2022-11-28#list-workflow-runs-for-a-workflow

GraphQL API 上では `WorkflowRun` オブジェクトの `databaseId` が該当するので、これらを集めるのも良いでしょう。

https://docs.github.com/ja/graphql/reference/objects#workflowrun

(※ 課金時間は 2024/2/29 時点では GraphQL API から取得できないので REST API を使う必要があります)

ちなみに、課金時間から料金計算を行うには以下のドキュメントを参考にするとよいです。

https://docs.github.com/ja/billing/managing-billing-for-github-actions/about-billing-for-github-actions

### Workflow 毎の平均課金時間を知りたい → WorkflowRun の attempt 数を取得して合算し、算出する

料金分析を行うときに、課金時間の平均時間を見たいこともあると思います。 実行回数は様々な要因で変動することが多いので、改善したときの効果を測定したい、というときは平均のほうが比較しやすいです。

算出は実行回数で割ればいい話なのですが、注意点として Retry した場合の WorkflowRun の扱いがあります。
GitHub Actions には Retry 機能がありますが、 これは挙動として、

* ☓ 新しい WorkflowRun が作られる
* ○ 同じ WorkflowRun の新たな attempt として作られる

という挙動を取ります。 なので、 WorkflowRun の数だけ数えると、実際の実行回数とはずれてしまいます。

Retry を加味して実行回数を数えるには、 **各 WorkflowRun に含まれる `run_attempt` の個数を合算する** と良いです。

```console
$ gh api /repos/{owner}/{repo}/actions/runs/WORKFLOW_RUN_ID
{
  "name": "Deploy Next.js site to Pages",
  "path": ".github/workflows/nextjs.yml",
  "run_number": 11,
  "event": "push",
  "status": "completed",
  "conclusion": "success",
  // ...
  "run_attempt": 2, 
  // ...
}
```

(※ GraphQL API では 2024/2/29 時点では attempt の情報は取れなさそうです)

ここでいう、 attempt は (Retry した) Workflow の実行画面で確認できるものに相当し、 API 上でも同じ WorkflowRun が複数の attempt を持つ、という構造を取っています。 (`run_attempt` は attempt の個数です)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7048/8c8d6bdd-6f12-f124-9758-80699aed2859.png)

ちなみに、先程の 「WorkflowRun 単位で課金時間を取得する API」 は全ての attempt が合算された結果になるので、

```
「1 attempt あたりの平均課金時間」 = 「各 WorkflowRun の課金時間 (billable.UBUNTU.total_ms)」 の合算 / 「各 WorkflowRun の run_attempt」 の合算
```

ということになります。

## :warning: GitHub API の Rate Limit に気をつけよう :warning: 

こんな感じで API を叩いていくことで、 GitHub Actions の課金時間、平均課金時間を計算できるのですが、これらの情報は REST API しかないものも多く、 GraphQL API にそれらの情報がないということで、 長期間の集計をしようとすると Rate Limit に引っかかりやすいです。

https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28

個人的には、長期間の概要を把握するのに利用レポートを、範囲を絞って詳細な情報を得るのに API を、という使い分けをしていくのがおすすめです。
集計するにも、時間をおいてゆっくり REST API を叩き、取得データを貯めておく、万が一 Rate Limit を起こしても支障がないアカウントなどで試すのが良いと思います。