# OpenAI-compatible Usage – Grafana Query Guide

> **Purpose**: 이 문서는 운영자가 Prometheus metric을 Grafana 패널로 조회해 사용자별 OpenAI-compatible token 사용량, 사용 출처, cloud-equivalent cost, avoided-cost ROI 판단 기준을 볼 수 있도록 한다. Control Plane/Client dashboard, request-level ledger, live cloud pricing sync, billing/chargeback, 사용자별 제한 enforcement는 만들지 않는다.

---

## Metric 목록

### 1. `iop_openai_usage_tokens_total` (Counter)

Provider-reported OpenAI-compatible token usage by token type.

| Label | Value 예시 | 설명 |
|-------|-----------|------|
| `edge_id` | `edge-1` | Edge instance identity |
| `principal_ref` | `usr-abc123` | Principal foreign-key-like reference |
| `principal_alias` | `john@acme` | Human-readable alias |
| `token_ref` | `tok-xyz789` | Token identity (not a raw secret) |
| `model_group` | `gpt-4o` | Model group identifier |
| `endpoint` | `chat.completions`, `responses` | OpenAI-compatible route |
| `response_mode` | `passthrough`, `normalized` | Internal execution label; callers cannot set it via OpenAI metadata |
| `token_type` | `input`, `output`, `reasoning`, `cached_input` | Token type |

### 2. `iop_openai_requests_total` (Counter)

OpenAI-compatible requests processed by terminal status and usage source.

| Label | Value 예시 | 설명 |
|-------|-----------|------|
| `edge_id` | `edge-1` | Edge instance identity |
| `principal_ref` | `usr-abc123` | Principal reference |
| `principal_alias` | `john@acme` | Human-readable alias |
| `token_ref` | `tok-xyz789` | Token identity |
| `model_group` | `gpt-4o` | Model group identifier |
| `endpoint` | `chat.completions`, `responses` | OpenAI-compatible route |
| `response_mode` | `passthrough`, `normalized` | Internal execution label; callers cannot set it via OpenAI metadata |
| `status` | `success`, `error`, `cancel` | Terminal request status |
| `usage_source` | `provider_reported`, `unavailable` | Whether at least one token type was reported |

### 3. `iop_openai_reasoning_observed_total` (Counter)

Requests where reasoning text was observed but the provider did not report reasoning token usage.
Same labels as `iop_openai_requests_total` excluding `status` and `usage_source`: `edge_id`, `principal_ref`, `principal_alias`, `token_ref`, `model_group`, `endpoint`, `response_mode`.

### 4. `iop_openai_reasoning_chars_total` (Counter)

Observed reasoning text characters for requests without provider-reported reasoning tokens. The token estimate is emitted separately via `iop_openai_reasoning_estimated_tokens_total`; provider-reported `token_type="reasoning"` remains authoritative.

### 5. `iop_openai_reasoning_estimated_tokens_total` (Counter)

Estimated reasoning tokens for requests with observed reasoning text but without provider-reported reasoning token usage. Calculated as `ceil(chars / 4)` with `estimation_method="chars_div_4"`. This is an operator-grade observation estimate only, never billing-grade or chargeback-confirmed.

---

## Label Allowlist & Forbidden Labels

### 허용 label

```
edge_id, principal_ref, principal_alias, token_ref,
model_group, endpoint, response_mode, status,
token_type, usage_source, estimation_method
```

`estimation_method`는 `iop_openai_reasoning_estimated_tokens_total` 전용 보조 라벨이며, 그 외 counter에는 사용되지 않습니다.

### 금지 label

- `request_id`, `session_id` — request-level 상세는 후속 ledger/Loki 축에서 다룬다.
- Bearer token, provider API key, raw payload, raw prompt/response text — secret 누출 방지 (SDD S08).
- 그 외 임의 label — cardinality 폭발을 막는다.

---

## Principal / Token Attribution 전제

- `principal_ref`는 사용자/테넌트/외부 운영 시스템의 안정 참조값이다.
- `principal_alias`는 Grafana legend와 table에 보여줄 낮은 cardinality 별칭이다.
- `token_ref`는 raw bearer token이 아니라 앱/통합/용도별 token 참조값이다.
- 같은 `principal_ref` 아래 여러 `token_ref`를 둘 수 있다. 이 경우 `principal_ref` 기준 query는 사용자 합산, `token_ref` 기준 query는 앱/통합별 breakdown으로 본다.
- 운영 token 발급은 raw token을 tracked 파일에 남기지 않고 hash/reference만 기록하는 절차를 따른다.

관련 기준:

- `agent-contract/outer/openai-compatible-api.md`: OpenAI-compatible principal token auth 계약
- `agent-contract/inner/edge-config-runtime-refresh.md`: `openai.principal_tokens[]` config 계약
- `agent-ops/skills/project/openai-usage-token-issue/SKILL.md`: 운영 token 발급 절차
- `apps/edge/internal/openai/identity_metering_test.go`: 같은 principal의 여러 token resolution 테스트
- `packages/go/config/config_test.go`: 같은 principal의 여러 `principal_tokens[]` config load 테스트

---

## PromQL Examples

### 기본: 사용자별 사용량 (principal_alias 기준)

```promql
# 입력 토큰 합계
sum by (principal_alias) (iop_openai_usage_tokens_total{token_type="input"})

# 출력 토큰 합계
sum by (principal_alias) (iop_openai_usage_tokens_total{token_type="output"})
```

### principal_ref별 usage breakdown

```promql
# principal_ref, token_type별 aggregation
sum by (principal_ref, token_type) (iop_openai_usage_tokens_total)
```

### token_ref별 사용량

```promql
# token_ref별 input/output 합계
sum by (token_ref, token_type) (iop_openai_usage_tokens_total)
```

### 사용자 합산 + 앱/통합별 breakdown

```promql
# 같은 principal_ref의 전체 사용량
sum by (principal_ref, principal_alias, token_type) (
  iop_openai_usage_tokens_total
)

# 같은 principal_ref 안에서 token_ref별 사용량
sum by (principal_ref, principal_alias, token_ref, token_type) (
  iop_openai_usage_tokens_total
)
```

### model_group별 usage

```promql
# model_group, token_type별 aggregation
sum by (model_group, token_type) (iop_openai_usage_tokens_total)
```

### endpoint별 요청 수

```promql
# endpoint, status별 요청 수
sum by (endpoint, status) (iop_openai_requests_total)
```

### response_mode별 분석

`response_mode`는 handler 실행 경로에서 파생되는 내부 라벨이다. provider 라우트의 raw tunnel 패스스루는 `passthrough`, 정규화된 RunEvent 경로는 `normalized`로 기록된다. caller가 OpenAI metadata로 설정할 수 있는 값이 아니며 API selector가 아니다.

```promql
# response_mode별 성공 요청 수
sum by (response_mode, status) (iop_openai_requests_total{status="success"})
```

### usage origin breakdown

```promql
# "어디서 얼만큼 사용했는지"를 보는 table용 breakdown
sum by (principal_alias, token_ref, model_group, endpoint, response_mode, token_type) (
  iop_openai_usage_tokens_total
)
```

### token_type별 사용량 전체

```promql
# 모든 token_type별 합계 (time range 그래프용)
sum by (token_type) (irate(iop_openai_usage_tokens_total[5m]))
```

### usage_source별 요청 coverage

```promql
# provider_reported vs unavailable 비율
sum by (usage_source) (iop_openai_requests_total)
```

### reasoning 보조 metric

provider가 reasoning token을 보고하지 않는 경우:

```promql
# reasoning text가 관찰된 요청 수 (provider token 미보고)
sum by (principal_alias) (iop_openai_reasoning_observed_total)

# reasoning text character 합계
sum by (principal_alias) (iop_openai_reasoning_chars_total)

# estimated reasoning tokens (provider가 token 수를 보고하지 않는 경우)
sum by (principal_alias, estimation_method) (iop_openai_reasoning_estimated_tokens_total)
```

> **주의**: `iop_openai_reasoning_observed_total` / `iop_openai_reasoning_chars_total` / `iop_openai_reasoning_estimated_tokens_total`는 provider-reported reasoning token이 있을 경우 emit되지 않는다. 즉, 이 metric들은 "reasoning text는 있으나 token count 보고를 안 하는 provider"만 커버한다. `iop_openai_reasoning_estimated_tokens_total`은 `estimation_method="chars_div_4"`로 ceil(chars/4) 추정을 제공하지만 billing-grade 정산값이 아니다.

---

## Dashboard Panel 후보

| 패널 | query | visualization |
|------|-------|---------------|
| 사용자별 token 사용량 (line) | `sum by (principal_alias, token_type) (irate(iop_openai_usage_tokens_total[5m]))` | Time series, legend=`{{principal_alias}} {{token_type}}` |
| 일별 usage origin (table) | `sum by (principal_alias, token_ref, model_group, endpoint, response_mode, token_type) (increase(iop_openai_usage_tokens_total[1d]))` | Table |
| model_group별 token 비율 (pie) | `sum by (model_group, token_type) (iop_openai_usage_tokens_total)` | Stat or bar chart |
| 요청 상태 분포 (table) | `sum by (endpoint, status, usage_source) (iop_openai_requests_total)` | Table |
| cloud-equivalent cost (stat/table) | token rollup query에 price scalar 또는 Grafana calculated field 적용 | Stat or Table |
| avoided-cost ROI summary (stat) | cloud-equivalent cost 합계, 필요 시 별도 infra cost field 차감 | Stat |
| reasoning 보조 지표 (singlestat) | `sum(iop_openai_reasoning_observed_total)` | Singlestat |
| estimated reasoning tokens (bar) | `sum by (estimation_method) (iop_openai_reasoning_estimated_tokens_total)` | Bar chart |
| estimated reasoning vs provider-reported (line) | `sum by (estimation_method) (iop_openai_reasoning_estimated_tokens_total)` vs `sum by (principal_alias) (iop_openai_usage_tokens_total{token_type="reasoning"})` | Time series |

> Grafana JSON dashboard import file은 본 scope에 포함되지 않는다. 위 PromQL을 바탕으로 패널을 수동 구성한다.

---

## daily / monthly Rollup

### 일일 토큰 합계

```promql
# 일일 token rollup (principal/token/model/endpoint/response_mode/token_type별)
sum by (principal_ref, principal_alias, token_ref, model_group, endpoint, response_mode, token_type) (
  increase(iop_openai_usage_tokens_total[1d])
)
```

> Grafana dashboard에서 `by time` aggregation으로 대체 가능:
> ```promql
> # Grafana 범용 aggregation (PromQL 함수 아님)
> # time range를 1일로 설정 후 sum by (principal_alias, token_type)
> iop_openai_usage_tokens_total
> ```

### 월간 토큰 합계

```promql
# 최근 30일 token rollup. 달력 월은 Grafana time range를 월 단위로 잡고 $__range를 사용한다.
sum by (principal_ref, principal_alias, token_ref, model_group, endpoint, response_mode, token_type) (
  increase(iop_openai_usage_tokens_total[30d])
)
```

> Grafana dashboard에서 범용 aggregation으로 대체 가능:
> ```promql
> # Grafana 범용 aggregation (PromQL 함수 아님)
> # time range를 30일로 설정 후 sum by (principal_alias, token_type)
> iop_openai_usage_tokens_total
> ```

### 일일/월간 rollup 활용 시나리오

- `principal_alias` 또는 `principal_ref`를 `by` clause에 추가해 사용자별 일일/월간 토큰 사용량을 산출한다.
- `token_ref`를 추가하면 같은 사용자 아래 앱/통합/용도별 사용량을 분해한다.
- `token_type`을 함께 group by하면 input/output/reasoning/cached_input을 구분한다.
- `model_group`, `endpoint`, `response_mode`를 추가하면 사용 위치와 응답 경로별 origin breakdown을 만든다.

---

## Cloud Price Baseline

가격은 런타임이 자동으로 가져오지 않는다. 운영자가 기준일과 기준 모델을 명시한 정적 baseline을 Grafana dashboard 변수, annotation, table panel, 또는 운영 문서에 둔다.

최소 필드:

| Field | 예시 | 설명 |
|-------|------|------|
| `cloud_provider` | `example-cloud` | 가격 기준 provider |
| `baseline_model` | `example-model` | 비교 기준 cloud model |
| `model_group` | `example-model` | IOP metric의 `model_group`과 매핑할 값 |
| `token_type` | `input`, `output`, `cached_input`, `reasoning` | 가격을 적용할 token type |
| `price_per_1m_tokens` | `1.00` | 1M tokens당 단가 |
| `currency` | `USD` | 통화 |
| `effective_date` | `2026-07-10` | 가격 기준일 |
| `source_note` | `operator baseline` | 운영자가 남기는 기준 설명 |

baseline 예시. 아래 숫자는 문서용 예시이며 최신 cloud 가격이 아니다.

| cloud_provider | baseline_model | model_group | token_type | price_per_1m_tokens | currency | effective_date |
|----------------|----------------|-------------|------------|----------------------|----------|----------------|
| example-cloud | example-model | example-model | input | 1.00 | USD | 2026-07-10 |
| example-cloud | example-model | example-model | output | 3.00 | USD | 2026-07-10 |
| example-cloud | example-model | example-model | cached_input | 0.50 | USD | 2026-07-10 |

`reasoning` token은 provider가 별도 단가를 제공하면 별도 baseline을 둔다. 별도 단가가 없으면 운영자가 `output`과 같은 단가로 볼지, report에서 `reasoning`을 제외할지 baseline에 명시한다.

> `iop_openai_reasoning_estimated_tokens_total`은 operator observation용 추정치이며, cloud-equivalent cost에 합산하거나 chargeback에 사용하지 않는다. billing-grade reasoning cost는 provider-reported `iop_openai_usage_tokens_total{token_type="reasoning"}`에 한한다.

---

## Cost Formula

기본 산식:

```text
cloud_equivalent_cost = (tokens / 1_000_000) * price_per_1m_tokens
daily_cloud_equivalent_cost = sum(cloud_equivalent_cost by day, principal/token/model/endpoint/token_type)
monthly_cloud_equivalent_cost = sum(cloud_equivalent_cost over selected month)
```

Prometheus metric 자체에는 price table이 없으므로 1차 표면에서는 다음 중 하나를 사용한다.

- Grafana table transformation으로 token rollup 결과에 static price column을 붙이고 calculated field를 만든다.
- model/token_type별 panel query에 운영자가 관리하는 scalar price를 곱한다.
- Prometheus recording rule을 운영자가 별도 관리한다. 이 repo는 recording rule 파일을 만들지 않는다.

PromQL scalar 예시:

```promql
# example-model input daily cloud-equivalent cost, USD
sum by (principal_alias, token_ref, model_group, endpoint) (
  increase(iop_openai_usage_tokens_total{model_group="example-model", token_type="input"}[1d])
) / 1000000 * 1.00

# example-model output daily cloud-equivalent cost, USD
sum by (principal_alias, token_ref, model_group, endpoint) (
  increase(iop_openai_usage_tokens_total{model_group="example-model", token_type="output"}[1d])
) / 1000000 * 3.00

# example-model cached input daily cloud-equivalent cost, USD
sum by (principal_alias, token_ref, model_group, endpoint) (
  increase(iop_openai_usage_tokens_total{model_group="example-model", token_type="cached_input"}[1d])
) / 1000000 * 0.50
```

여러 token type을 합산할 때는 token type별 cost query를 Grafana transformation으로 더한다. 단일 query에서 서로 다른 가격을 자동 join하려면 별도 recording rule 또는 external price table이 필요하다.

---

## ROI / Avoided-Cost Summary

이 MVP의 ROI는 billing-grade 정산이 아니라 cloud-equivalent avoided cost다.

```text
cloud_equivalent_cost = cloud baseline으로 환산한 사용량 비용
avoided_cost = IOP/local/provider에서 처리한 사용량의 cloud_equivalent_cost
net_avoided_cost = cloud_equivalent_cost - separately_known_local_infra_cost
```

- local infra cost가 별도로 계상되지 않으면 `avoided_cost`만 표시한다.
- local infra cost를 운영자가 별도 산정해 Grafana에 넣을 수 있으면 `net_avoided_cost`를 보조 field로 둔다.
- 이 값은 실제 결제, chargeback, 조직별 비용 배부, full infra cost accounting 근거가 아니다.
- 최소 summary table column은 `date`, `principal_alias`, `token_ref`, `model_group`, `endpoint`, `tokens`, `currency`, `cloud_equivalent_cost`, `avoided_cost`다.

Grafana table 구성 예:

1. Query A: daily token rollup by `principal_alias`, `token_ref`, `model_group`, `endpoint`, `token_type`.
2. Transformation: baseline price table 또는 panel-level scalar를 token type별로 적용한다.
3. Calculated field: `tokens / 1000000 * price_per_1m_tokens`.
4. Group by: `date`, `principal_alias`, `token_ref`, `model_group`, `endpoint`.
5. Reduce: `cloud_equivalent_cost` 합계와 token 합계를 보여준다.

---

## Grafana Report 절차

1. Grafana time range를 `Today`, `Yesterday`, 최근 7일, 또는 이번 달로 설정한다.
2. `daily / monthly Rollup` query로 token usage table을 만든다.
3. `principal_ref`/`principal_alias` 기준 panel과 `token_ref` 기준 panel을 나란히 둔다.
4. `model_group`, `endpoint`, `response_mode`, `token_type` column을 유지해 usage origin을 확인한다.
5. 운영 price baseline의 `effective_date`, `baseline_model`, `currency`를 dashboard text 또는 table에 함께 표시한다.
6. token type별 cost formula를 적용해 cloud-equivalent cost를 계산한다.
7. daily/monthly `avoided_cost` summary stat을 추가한다.
8. `usage_source="unavailable"` 요청 비율과 reasoning 보조 metric을 함께 확인해 cost report coverage 위험을 판단한다.

---

## Limit Follow-up: 경계 명시

### 후속 Milestone으로 남기는 항목

- 사용자별 daily/monthly token **warn** 또는 **reject** enforcement는 이 Milestone의 범위가 아니며 후속 Milestone으로 Planned된다.
- 실제 결제, chargeback, 조직별 비용 배부, full infra cost accounting은 후속 범위다.
- live cloud pricing API 연동 또는 최신 가격 자동 동기화는 후속 범위다.
- request-level ledger / audit log는 Loki/queriable log축 후속 작업의 대상이다.

### 현재 문서가 제공하는 것

- `principal_ref`, `principal_alias`, `token_ref`, `model_group`, `endpoint`, `response_mode`, `token_type` 라벨 기반 daily/monthly rollup PromQL
- 같은 principal의 여러 token/app usage를 합산과 breakdown으로 함께 보는 기준
- 운영자가 관리하는 cloud price baseline 필드
- token type별 cloud-equivalent cost 산식과 query/report 예시
- avoided-cost 수준의 ROI summary 기준
- provider reasoning report 누락 시 `reasoning_observed_total` / `reasoning_chars_total`로 보조 확인, `reasoning_estimated_tokens_total`로 chars/4 token 추정치 제공 (billing-grade 아님)

이 rollup 기준은 후속 enforce Milestone에서 Prometheus query를 그대로 재사용할 수 있도록 설계되었다.

---

## Quick Reference: Label Values

| Label | 허용 값 |
|-------|---------|
| `endpoint` | `chat.completions`, `responses` |
| `status` | `success`, `error`, `cancel` |
| `usage_source` | `provider_reported`, `unavailable` |
| `token_type` | `input`, `output`, `reasoning`, `cached_input` |
| `response_mode` | `passthrough`, `normalized` |
| `estimation_method` | `chars_div_4` (특히 `iop_openai_reasoning_estimated_tokens_total` 전용) |

---

## Appendix: Grafana Scrape Target

Compose 관측 스택에서는 Prometheus가 내부 Docker DNS로 `control-plane:9093`을 scrape하고, Grafana datasource는 `http://prometheus:9090`을 사용한다. dev profile은 기존 native Edge endpoint를 바꾸지 않기 위해 `host.docker.internal:19101`도 scrape한다.

Host publish 기본 포트:

- local/test: Prometheus `19110`, Grafana `19120`, Control Plane metrics `19100`, Edge metrics `19092`
- dev: Prometheus `19111`, Grafana `19121`, Control Plane metrics `19103`, Edge metrics `19101`
- dev-corp compose: Prometheus `19112`, Grafana `19122`, Control Plane metrics `19104`, Edge metrics `19105`

이 관측 스택은 기존 테스트 사용자-facing endpoint를 변경하지 않는다. dev 기준 기존 UI/Control Plane/Edge OpenAI-compatible 접속점은 유지하며, Grafana/Prometheus/metrics 포트만 운영자용으로 추가한다. dev Grafana/Prometheus는 기본적으로 원격 runner 내부 또는 SSH 터널 접근 기준이다. dev-corp는 현재 접근 가능한 runner가 없을 때 문서 정의만 갱신하고 실제 compose 세팅은 별도 세션에서 수행한다.
