# 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 세팅은 별도 세션에서 수행한다.