iop/docs/openai-usage-grafana.md
toki 59c2997d47 refactor(openai): call metadata 기반 response_mode 라우터 선택을 제거한다
provider 라우트는 raw tunnel 패스스루, 그 외 라우트는 정규화된
RunEvent 경로로 응답을 결정한다. caller metadata는 임의 컨텍스트이며
응답 경로/shape를 선택하지 않는다 (SDD S01).

- chat_handler.go: response_mode parse/switch 로직 제거, 라우트 기반 분기로 단순화
- responses_handler.go: tunnelResponsesPassthroughSideband 함수 및 response_mode switch 제거
- stream.go: 관련 response_mode 라벨 사용 정리
- server_test.go: response_mode 관련 테스트 케이스 제거 및 정리
- usage_metrics_test.go: 사용되지 않는 테스트 대목 제거
- docs/openai-usage-grafana.md: response_mode 라벨 설명을 passthrough/normalized로 수정
2026-07-13 21:05:04 +09:00

18 KiB
Raw Blame History

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. Never converted to a token estimate.


Label Allowlist & Forbidden Labels

허용 label

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

금지 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 기준)

# 입력 토큰 합계
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

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

token_ref별 사용량

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

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

# 같은 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

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

endpoint별 요청 수

# 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가 아니다.

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

usage origin breakdown

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

token_type별 사용량 전체

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

usage_source별 요청 coverage

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

reasoning 보조 metric

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

# 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)

주의: iop_openai_reasoning_observed_total / iop_openai_reasoning_chars_total는 provider-reported reasoning token이 있을 경우 emit되지 않는다. 즉, 이 metric은 "reasoning text는 있으나 token count 보고를 안 하는 provider"만 커버한다. token 추정을 위해 chars를 사용할 수 있으나 이 문서는 추정 권유를 하지 않는다.


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

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


daily / monthly Rollup

일일 토큰 합계

# 일일 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으로 대체 가능:

# Grafana 범용 aggregation (PromQL 함수 아님)
# time range를 1일로 설정 후 sum by (principal_alias, token_type)
iop_openai_usage_tokens_total

월간 토큰 합계

# 최근 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으로 대체 가능:

# Grafana 범용 aggregation (PromQL 함수 아님)
# time range를 30일로 설정 후 sum by (principal_alias, token_type)
iop_openai_usage_tokens_total

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

  • principal_alias 또는 principal_refby 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에 명시한다.


Cost Formula

기본 산식:

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 예시:

# 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다.

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로 보조 확인

이 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

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 19102

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