AIのトークン・クォータ (利用枠) 残量をグラフ化する 〜AntigravityとZabbixの場合〜

AIのトークン・クォータ (利用枠) 残量をグラフ化する 〜AntigravityとZabbixの場合〜
Page content

クォータ残量の推移を知りたい

普段利用しているAIの、トークン・クォータ (利用枠) 残量が今どれくらいなのかを確認する行為は、趣味や仕事のペース配分を決めるために不可欠となった。欲を言えばこの残量確認は、ある瞬間の値ではなくて時系列での変化、つまりグラフの形で可視化して行いたい。なぜなら、“このままAIをぶん回していたらあと何時間でLimitに達する”、という先の見通しを立てたいからだ。

そのために、何らかの仕掛けを作ることにしよう。時系列データを取得してグラフ化することができたならば、次はその情報をスマホのホーム画面や通知領域に表示したり、電子ペーパー端末やスマートウォッチに表示するという応用もできるはず。そこで本記事では、最初の一歩として、自分が現在使っているプラン「Google AI Ultra 20x」のトークン・クォータ残量をZabbixに集約して記録に残し、推移をグラフ化してみることにした。

グラフ化する仕掛け

Step-1. agyコマンド「/usage」を使う

Antigravity CLIのコマンドagyに、次のような引数-p '/usage' --output-format jsonを付けて実行すると、「Weekly Limit Remaining」や「Five Hour Limit Remaining」という項目で、トークン・クォータ残量の数値などが得られる。

$ agy -p '/usage' --output-format json > ./agy_20261107-2001.json

実際のJSON出力は次の通りだ。わかりやすい項目分けがされている。

{
  "conversation_id": "",
  "status": "SUCCESS",
  "response": "Gemini Models\tWeekly Limit Remaining\t96%\t2026-11-14T02:40:30Z\nGemini Models\tFive Hour Limit Remaining\t95%\t2026-11-07T15:00:09Z\nClaude and GPT models\tWeekly Limit Remaining\t100%\t2026-11-14T11:01:57Z\nClaude and GPT models\tFive Hour Limit Remaining\t100%\t2026-11-07T16:01:57Z\n",
  "duration_seconds": 0,
  "num_turns": 0,
  "usage": {
    "input_tokens": 0,
    "output_tokens": 0,
    "thinking_tokens": 0,
    "cache_read_tokens": 0,
    "total_tokens": 0
  },
  "command": {
    "name": "usage",
    "data": {
      "description": "Within each group, models share a weekly limit and a 5-hour limit. Quota is consumed proportionally to the cost of the tokens. Thus, limits will last longer with shorter tasks or using more cost-effective models. The 5-hour limit smooths out aggregate demand to fairly distribute global capacity across all users, while your weekly limit is tied directly to your individual tier.",
      "groups": [
        {
          "name": "Gemini Models",
          "description": "Models within this group: Gemini Flash, Gemini Pro",
          "buckets": [
            {
              "id": "gemini-weekly",
              "name": "Weekly Limit Remaining",
              "description": "You have used some of your weekly limit, it will fully refresh in 6 days, 15 hours.",
              "window": "weekly",
              "remaining_fraction": 0.9619837999343872,
              "reset_time": "2026-11-14T02:40:30Z"
            },
            {
              "id": "gemini-5h",
              "name": "Five Hour Limit Remaining",
              "description": "You have used some of your 5-hour limit, it will fully refresh in 3 hours, 58 minutes.",
              "window": "5h",
              "remaining_fraction": 0.954495906829834,
              "reset_time": "2026-11-07T15:00:09Z"
            }
          ]
        },
        {
          "name": "Claude and GPT models",
          "description": "Models within this group: Claude Opus, Claude Sonnet, GPT-OSS",
          "buckets": [
            {
              "id": "3p-weekly",
              "name": "Weekly Limit Remaining",
              "window": "weekly",
              "remaining_fraction": 1,
              "reset_time": "2026-11-14T11:01:57Z"
            },
            {
              "id": "3p-5h",
              "name": "Five Hour Limit Remaining",
              "window": "5h",
              "remaining_fraction": 1,
              "reset_time": "2026-11-07T16:01:57Z"
            }
          ]
        }
      ]
    }
  }
}

Step-2. データ送信

Step-2-1. jqコマンドのお試し

Step-1のJSON出力から欲しい値のみを抽出するjqコマンドを試してみると、たとえば次のようになる。

$ cat ./agy_20261107-2001.json | \
  jq -r '(.command.data.groups // [])[] | (.buckets // [])[] | "[\(.id)] \(.remaining_fraction)"'
[gemini-weekly] 0.9619837999343872
[gemini-5h] 0.954495906829834
[3p-weekly] 1
[3p-5h] 1

このようなコマンドを、Antigravity CLIを常駐 (agy remote-control serve) させて動かしているAIサーバ上で適切なタイミングにて実行し、値をZabbixサーバに送り込めば、値の可視化ができそうだと予想できる。

今回は環境構築で手抜きをして、「AIサーバからZabbixサーバへ公開鍵認証でのSSHログインだけは可能で、他は何もinstallしない」という条件で、仕掛け作りを進める。

Step-2-2. クォータ残量をStopフックで送信する

AIサーバに、~/.gemini/antigravity-cli/hook_export_quota.shとして次のbashスクリプトを保存する。Zabbixサーバに送り込むためのバイナリデータを、内部のPythonスクリプトで準備するという変態的な作り。動いたから良し。

#!/usr/bin/env bash

set -euo pipefail

# hook_export_quota.sh (Ver.20261107)

# 設定変数 (環境変数による上書き可)
readonly AGY_BIN="${AGY_BIN:-$(command -v agy 2>/dev/null || printf '%s/.local/bin/agy' "$HOME")}"
readonly OUTPUT_JSON="${OUTPUT_JSON:-/tmp/gemini_quota_hook.json}"
readonly LOCK_FILE="${LOCK_FILE:-/tmp/gemini_quota_hook.lock}"
readonly ZABBIX_SSH_TARGET="${ZABBIX_SSH_TARGET:-}"
readonly ZABBIX_HOST="${ZABBIX_HOST:-$(hostname -s 2>/dev/null || uname -n)}"

# 1. フックの入力を読み捨て、エージェントの停止を即座に許可する
if [[ ! -t 0 ]]; then
  cat > /dev/null || true
fi
printf '{"decision": "allow"}\n'

# 親子プロセスでの無限ループ防止ガード
if [[ "${GEMINI_QUOTA_HOOK_RUNNING:-0}" == "1" ]]; then
  exit 0
fi

# 実行コマンドの存在・実行権限チェック
if [[ ! -x "$AGY_BIN" ]]; then
  exit 0
fi

# 2. バックグラウンドでクォータJSONを取得して保存、SSH経由でZabbixへ送信する
(
  export GEMINI_QUOTA_HOOK_RUNNING=1

  # 別プロセス間での多重起動防止ロック
  exec 9>"$LOCK_FILE"
  if command -v flock >/dev/null 2>&1; then
    flock -n 9 || exit 0
  fi

  output_dir="$(dirname "$OUTPUT_JSON")"
  mkdir -p "$output_dir"

  tmp_file="$(mktemp "${OUTPUT_JSON}.tmp.XXXXXX")"
  trap 'rm -f "$tmp_file"' EXIT

  if "$AGY_BIN" -p "/usage" --output-format json < /dev/null 9>&- > "$tmp_file" 2>/dev/null && [[ -s "$tmp_file" ]]; then
    chmod 0644 "$tmp_file"
    mv -f "$tmp_file" "$OUTPUT_JSON"

    # Zabbixサーバーへ各バケットの残量(%)を送信
    if [[ -n "$ZABBIX_SSH_TARGET" ]] && command -v jq >/dev/null 2>&1; then
      jq -c --arg host "$ZABBIX_HOST" '{
        request: "sender data",
        data: [
          select(.status == "SUCCESS")
          | (.command.data.groups // [])[]
          | (.buckets // [])[]
          | {
              host: $host,
              key: "gemini.quota.remaining[\(.id)]",
              value: (((.remaining_fraction // 0) * 100) | tostring)
            }
        ]
      }' "$OUTPUT_JSON" | ssh -o BatchMode=yes -o ConnectTimeout=5 "$ZABBIX_SSH_TARGET" 'python3 -c "
import sys, socket, struct
payload = sys.stdin.read().encode()
if payload:
    packet = b\"ZBXD\x01\" + struct.pack(\"<Q\", len(payload)) + payload
    with socket.create_connection((\"127.0.0.1\", 10051), timeout=5) as s:
        s.sendall(packet)
        s.recv(1024)
"' >/dev/null 2>&1 || true
    fi
  fi
) </dev/null >/dev/null 2>&1 &

exit 0

そして同じく、AIサーバに、~/.gemini/config/hooks.jsonとして次の内容を保存する (既に存在すれば追記する)。ZABBIX_SSH_TARGETに代入しているzabbix.example.comは、公開鍵認証でSSHログイン可能な、各々のZabbixサーバ名に置き換える。

{
  "quota-exporter": {
    "Stop": [
      {
        "type": "command",
        "command": "ZABBIX_SSH_TARGET=zabbix.example.com \"$HOME/.gemini/antigravity-cli/hook_export_quota.sh\"",
        "timeout": 15
      }
    ]
  }
}

Step-2-3. クォータ残量を定期送信する

AIのエージェントを操作していない時間帯も、クォータ残量を15分間隔でZabbixサーバへ送信するように、AIサーバの次のcrontabを設定する。この仕掛けにより、エージェント操作によらない、一定時間経過後の回復などの残量変化が、15分以内に捉えられる。

*/15 * * * * ZABBIX_SSH_TARGET=zabbix.example.com /home/USER/.gemini/antigravity-cli/hook_export_quota.sh < /dev/null >/dev/null 2>&1

Step-3. Zabbix側のItem/Graph設定

Zabbixサーバのウェブ管理画面にて、AIサーバのホスト名 (hostname -s) のホストを作成し、下記のアイテムを、Type = Zabbix trapper, Type of information = Numeric (float), Units = % として登録する。

NameKey対象モデル
Gemini Quota Remaining (5h)gemini.quota.remaining[gemini-5h]Gemini
Gemini Quota Remaining (Weekly)gemini.quota.remaining[gemini-weekly]Gemini
3P Quota Remaining (5h)gemini.quota.remaining[3p-5h]Claude, GPT
3P Quota Remaining (Weekly)gemini.quota.remaining[3p-weekly]Claude, GPT

あわせて、上記のアイテムで必要なもの (私の場合はGemini関係の2つにした) に対応するグラフを作成すると、クォータ残量の推移をZabbix上のグラフで把握できるようになる。

個人的まとめ

私は個人としてはGemini/Antigravityを利用していて、本格利用を始めた当初の契約プランは「Google AI Ultra 5x」だった。しかし、たとえば複数のエージェントに重めのタスクを投げて働かせたら、クォータの5時間リミットに達することは珍しくないし、1週間のリミットにも達して、2日ぐらい何もできないという事態にも至っていた。

AIのクォータ切れは、たとえばMonokuroCamをがりがり改良したい時などには大変つらい。そこで2026年10月末に、プランを「Google AI Ultra 20x」へ切り替えてみたのだ。下記のグラフの傾きを見ながら振り返ると、余裕が20xの四分の一しかない5xの場合、クォータ切れに達するのはまったく不思議ではない、自分がAIの使い方を変えない限りは、と納得させられた。