feat(server/otel): follow opentelemetry metrics sharded
This commit is contained in:
@@ -48,7 +48,7 @@
|
||||
"type": "stat",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(rate(http_server_request_duration_milliseconds_count{service_name=~\"$service\", deployment_environment=~\"$env\"}[$__rate_interval]))",
|
||||
"expr": "sum(rate(http_server_request_duration_seconds_count{service_name=~\"$service\", deployment_environment=~\"$env\"}[$__rate_interval]))",
|
||||
"legendFormat": "req/s"
|
||||
}
|
||||
]
|
||||
@@ -80,7 +80,7 @@
|
||||
"type": "stat",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(rate(http_server_request_duration_milliseconds_count{service_name=~\"$service\", deployment_environment=~\"$env\", http_status_code=~\"5..\"}[$__rate_interval])) / sum(rate(http_server_request_duration_milliseconds_count{service_name=~\"$service\", deployment_environment=~\"$env\"}[$__rate_interval]))",
|
||||
"expr": "sum(rate(http_server_request_duration_seconds_count{service_name=~\"$service\", deployment_environment=~\"$env\", http_status_code=~\"5..\"}[$__rate_interval])) / sum(rate(http_server_request_duration_seconds_count{service_name=~\"$service\", deployment_environment=~\"$env\"}[$__rate_interval]))",
|
||||
"legendFormat": "error rate"
|
||||
}
|
||||
]
|
||||
@@ -94,11 +94,11 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{ "color": "green", "value": null },
|
||||
{ "color": "yellow", "value": 200 },
|
||||
{ "color": "red", "value": 1000 }
|
||||
{ "color": "yellow", "value": 0.2 },
|
||||
{ "color": "red", "value": 1.0 }
|
||||
]
|
||||
},
|
||||
"unit": "ms"
|
||||
"unit": "s"
|
||||
}
|
||||
},
|
||||
"gridPos": { "h": 8, "w": 8, "x": 16, "y": 1 },
|
||||
@@ -108,11 +108,11 @@
|
||||
"colorMode": "value",
|
||||
"graphMode": "area"
|
||||
},
|
||||
"title": "P95 Latency",
|
||||
"title": "P95 Latency (excl. LLM)",
|
||||
"type": "stat",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "histogram_quantile(0.95, sum(rate(http_server_request_duration_milliseconds_bucket{service_name=~\"$service\", deployment_environment=~\"$env\"}[$__rate_interval])) by (le))",
|
||||
"expr": "histogram_quantile(0.95, sum(rate(http_server_request_duration_seconds_bucket{service_name=~\"$service\", deployment_environment=~\"$env\", http_route!~\"/api/v1/openai/.*\"}[$__rate_interval])) by (le))",
|
||||
"legendFormat": "p95"
|
||||
}
|
||||
]
|
||||
@@ -136,7 +136,7 @@
|
||||
"type": "timeseries",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum by (http_route) (rate(http_server_request_duration_milliseconds_count{service_name=~\"$service\", deployment_environment=~\"$env\"}[$__rate_interval]))",
|
||||
"expr": "sum by (http_route) (rate(http_server_request_duration_seconds_count{service_name=~\"$service\", deployment_environment=~\"$env\"}[$__rate_interval]))",
|
||||
"legendFormat": "{{http_route}}"
|
||||
}
|
||||
]
|
||||
@@ -150,7 +150,7 @@
|
||||
"lineInterpolation": "smooth",
|
||||
"fillOpacity": 20
|
||||
},
|
||||
"unit": "ms"
|
||||
"unit": "s"
|
||||
}
|
||||
},
|
||||
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 9 },
|
||||
@@ -159,7 +159,7 @@
|
||||
"type": "timeseries",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "histogram_quantile(0.95, sum by (le, http_route) (rate(http_server_request_duration_milliseconds_bucket{service_name=~\"$service\", deployment_environment=~\"$env\"}[$__rate_interval])))",
|
||||
"expr": "histogram_quantile(0.95, sum by (le, http_route) (rate(http_server_request_duration_seconds_bucket{service_name=~\"$service\", deployment_environment=~\"$env\"}[$__rate_interval])))",
|
||||
"legendFormat": "{{http_route}}"
|
||||
}
|
||||
]
|
||||
@@ -182,7 +182,7 @@
|
||||
"type": "timeseries",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum by (http_status_code) (rate(http_server_request_duration_milliseconds_count{service_name=~\"$service\", deployment_environment=~\"$env\"}[$__rate_interval]))",
|
||||
"expr": "sum by (http_status_code) (rate(http_server_request_duration_seconds_count{service_name=~\"$service\", deployment_environment=~\"$env\"}[$__rate_interval]))",
|
||||
"legendFormat": "{{http_status_code}}"
|
||||
}
|
||||
]
|
||||
@@ -318,7 +318,7 @@
|
||||
"lineInterpolation": "smooth",
|
||||
"fillOpacity": 20
|
||||
},
|
||||
"unit": "ms"
|
||||
"unit": "s"
|
||||
}
|
||||
},
|
||||
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 43 },
|
||||
@@ -327,7 +327,7 @@
|
||||
"type": "timeseries",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "histogram_quantile(0.95, sum by (le, db_operation) (rate(db_client_operation_duration_milliseconds_bucket{service_name=~\"$service\", deployment_environment=~\"$env\"}[$__rate_interval])))",
|
||||
"expr": "histogram_quantile(0.95, sum by (le, db_operation) (rate(db_client_operation_duration_seconds_bucket{service_name=~\"$service\", deployment_environment=~\"$env\"}[$__rate_interval])))",
|
||||
"legendFormat": "{{db_operation}}"
|
||||
}
|
||||
]
|
||||
@@ -350,7 +350,7 @@
|
||||
"type": "timeseries",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum by (db_operation) (rate(db_client_operation_duration_milliseconds_count{service_name=~\"$service\", deployment_environment=~\"$env\"}[$__rate_interval]))",
|
||||
"expr": "sum by (db_operation) (rate(db_client_operation_duration_seconds_count{service_name=~\"$service\", deployment_environment=~\"$env\"}[$__rate_interval]))",
|
||||
"legendFormat": "{{db_operation}}"
|
||||
}
|
||||
]
|
||||
@@ -402,7 +402,7 @@
|
||||
"lineInterpolation": "smooth",
|
||||
"fillOpacity": 20
|
||||
},
|
||||
"unit": "ms"
|
||||
"unit": "s"
|
||||
}
|
||||
},
|
||||
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 60 },
|
||||
@@ -411,7 +411,7 @@
|
||||
"type": "timeseries",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "histogram_quantile(0.95, sum by (le, db_operation) (rate(db_client_operation_duration_milliseconds_bucket{service_name=~\"$service\", deployment_environment=~\"$env\", db_system=\"redis\"}[$__rate_interval])))",
|
||||
"expr": "histogram_quantile(0.95, sum by (le, db_operation) (rate(db_client_operation_duration_seconds_bucket{service_name=~\"$service\", deployment_environment=~\"$env\", db_system=\"redis\"}[$__rate_interval])))",
|
||||
"legendFormat": "{{db_operation}}"
|
||||
}
|
||||
]
|
||||
@@ -434,7 +434,7 @@
|
||||
"type": "timeseries",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum by (db_operation) (rate(db_client_operation_duration_milliseconds_count{service_name=~\"$service\", deployment_environment=~\"$env\", db_system=\"redis\"}[$__rate_interval]))",
|
||||
"expr": "sum by (db_operation) (rate(db_client_operation_duration_seconds_count{service_name=~\"$service\", deployment_environment=~\"$env\", db_system=\"redis\"}[$__rate_interval]))",
|
||||
"legendFormat": "{{db_operation}}"
|
||||
}
|
||||
]
|
||||
@@ -473,7 +473,7 @@
|
||||
"type": "stat",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(rate(llm_request_count_total{service_name=~\"$service\", deployment_environment=~\"$env\"}[$__rate_interval]))",
|
||||
"expr": "sum(rate(gen_ai_client_operation_count_total{service_name=~\"$service\", deployment_environment=~\"$env\"}[$__rate_interval]))",
|
||||
"legendFormat": "req/s"
|
||||
}
|
||||
]
|
||||
@@ -487,11 +487,11 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{ "color": "green", "value": null },
|
||||
{ "color": "yellow", "value": 500 },
|
||||
{ "color": "red", "value": 2000 }
|
||||
{ "color": "yellow", "value": 0.5 },
|
||||
{ "color": "red", "value": 2.0 }
|
||||
]
|
||||
},
|
||||
"unit": "ms"
|
||||
"unit": "s"
|
||||
}
|
||||
},
|
||||
"gridPos": { "h": 8, "w": 8, "x": 8, "y": 69 },
|
||||
@@ -505,7 +505,7 @@
|
||||
"type": "stat",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "histogram_quantile(0.95, sum(rate(llm_request_duration_milliseconds_bucket{service_name=~\"$service\", deployment_environment=~\"$env\"}[$__rate_interval])) by (le))",
|
||||
"expr": "histogram_quantile(0.95, sum(rate(gen_ai_client_operation_duration_seconds_bucket{service_name=~\"$service\", deployment_environment=~\"$env\"}[$__rate_interval])) by (le))",
|
||||
"legendFormat": "p95"
|
||||
}
|
||||
]
|
||||
@@ -537,7 +537,7 @@
|
||||
"type": "stat",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(rate(flux_consumed_total{service_name=~\"$service\", deployment_environment=~\"$env\"}[$__rate_interval]))",
|
||||
"expr": "sum(rate(airi_billing_flux_consumed_total{service_name=~\"$service\", deployment_environment=~\"$env\"}[$__rate_interval]))",
|
||||
"legendFormat": "flux/s"
|
||||
}
|
||||
]
|
||||
@@ -561,7 +561,7 @@
|
||||
"type": "timeseries",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum by (gen_ai_request_model) (rate(llm_request_count_total{service_name=~\"$service\", deployment_environment=~\"$env\", gen_ai_request_model!=\"\"}[$__rate_interval]))",
|
||||
"expr": "sum by (gen_ai_request_model) (rate(gen_ai_client_operation_count_total{service_name=~\"$service\", deployment_environment=~\"$env\", gen_ai_request_model!=\"\"}[$__rate_interval]))",
|
||||
"legendFormat": "{{gen_ai_request_model}}"
|
||||
}
|
||||
]
|
||||
@@ -584,11 +584,11 @@
|
||||
"type": "timeseries",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum by (gen_ai_operation_name) (rate(llm_request_count_total{service_name=~\"$service\", deployment_environment=~\"$env\", gen_ai_operation_name!=\"\"}[$__rate_interval]))",
|
||||
"expr": "sum by (gen_ai_operation_name) (rate(gen_ai_client_operation_count_total{service_name=~\"$service\", deployment_environment=~\"$env\", gen_ai_operation_name!=\"\"}[$__rate_interval]))",
|
||||
"legendFormat": "{{gen_ai_operation_name}}"
|
||||
},
|
||||
{
|
||||
"expr": "sum by (airi_gen_ai_operation_kind) (rate(llm_request_count_total{service_name=~\"$service\", deployment_environment=~\"$env\", airi_gen_ai_operation_kind!=\"\"}[$__rate_interval]))",
|
||||
"expr": "sum by (airi_gen_ai_operation_kind) (rate(gen_ai_client_operation_count_total{service_name=~\"$service\", deployment_environment=~\"$env\", airi_gen_ai_operation_kind!=\"\"}[$__rate_interval]))",
|
||||
"legendFormat": "{{airi_gen_ai_operation_kind}}"
|
||||
}
|
||||
]
|
||||
@@ -602,7 +602,7 @@
|
||||
"lineInterpolation": "smooth",
|
||||
"fillOpacity": 20
|
||||
},
|
||||
"unit": "ms"
|
||||
"unit": "s"
|
||||
}
|
||||
},
|
||||
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 85 },
|
||||
@@ -611,7 +611,7 @@
|
||||
"type": "timeseries",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "histogram_quantile(0.95, sum by (le, gen_ai_request_model) (rate(llm_request_duration_milliseconds_bucket{service_name=~\"$service\", deployment_environment=~\"$env\", gen_ai_request_model!=\"\"}[$__rate_interval])))",
|
||||
"expr": "histogram_quantile(0.95, sum by (le, gen_ai_request_model) (rate(gen_ai_client_operation_duration_seconds_bucket{service_name=~\"$service\", deployment_environment=~\"$env\", gen_ai_request_model!=\"\"}[$__rate_interval])))",
|
||||
"legendFormat": "{{gen_ai_request_model}}"
|
||||
}
|
||||
]
|
||||
@@ -634,7 +634,7 @@
|
||||
"type": "timeseries",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum by (gen_ai_request_model) (rate(flux_consumed_total{service_name=~\"$service\", deployment_environment=~\"$env\", gen_ai_request_model!=\"\"}[$__rate_interval]))",
|
||||
"expr": "sum by (gen_ai_request_model) (rate(airi_billing_flux_consumed_total{service_name=~\"$service\", deployment_environment=~\"$env\", gen_ai_request_model!=\"\"}[$__rate_interval]))",
|
||||
"legendFormat": "{{gen_ai_request_model}}"
|
||||
}
|
||||
]
|
||||
@@ -657,7 +657,7 @@
|
||||
"type": "timeseries",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum by (gen_ai_request_model) (rate(llm_tokens_prompt_total{service_name=~\"$service\", deployment_environment=~\"$env\", gen_ai_request_model!=\"\"}[$__rate_interval]))",
|
||||
"expr": "sum by (gen_ai_request_model) (rate(gen_ai_client_token_usage_input_total{service_name=~\"$service\", deployment_environment=~\"$env\", gen_ai_request_model!=\"\"}[$__rate_interval]))",
|
||||
"legendFormat": "{{gen_ai_request_model}}"
|
||||
}
|
||||
]
|
||||
@@ -680,7 +680,7 @@
|
||||
"type": "timeseries",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum by (gen_ai_request_model) (rate(llm_tokens_completion_total{service_name=~\"$service\", deployment_environment=~\"$env\", gen_ai_request_model!=\"\"}[$__rate_interval]))",
|
||||
"expr": "sum by (gen_ai_request_model) (rate(gen_ai_client_token_usage_output_total{service_name=~\"$service\", deployment_environment=~\"$env\", gen_ai_request_model!=\"\"}[$__rate_interval]))",
|
||||
"legendFormat": "{{gen_ai_request_model}}"
|
||||
}
|
||||
]
|
||||
@@ -722,24 +722,24 @@
|
||||
{
|
||||
"current": { "selected": false, "text": "production", "value": "production" },
|
||||
"datasource": { "type": "prometheus", "uid": "grafanacloud-projairi-prom" },
|
||||
"definition": "label_values(http_server_request_duration_milliseconds_count, deployment_environment)",
|
||||
"definition": "label_values(http_server_request_duration_seconds_count, deployment_environment)",
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"multi": false,
|
||||
"name": "env",
|
||||
"query": "label_values(http_server_request_duration_milliseconds_count, deployment_environment)",
|
||||
"query": "label_values(http_server_request_duration_seconds_count, deployment_environment)",
|
||||
"refresh": 2,
|
||||
"type": "query"
|
||||
},
|
||||
{
|
||||
"current": { "selected": false, "text": "server", "value": "server" },
|
||||
"datasource": { "type": "prometheus", "uid": "grafanacloud-projairi-prom" },
|
||||
"definition": "label_values(http_server_request_duration_milliseconds_count{deployment_environment=~\"$env\"}, service_name)",
|
||||
"definition": "label_values(http_server_request_duration_seconds_count{deployment_environment=~\"$env\"}, service_name)",
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"multi": true,
|
||||
"name": "service",
|
||||
"query": "label_values(http_server_request_duration_milliseconds_count{deployment_environment=~\"$env\"}, service_name)",
|
||||
"query": "label_values(http_server_request_duration_seconds_count{deployment_environment=~\"$env\"}, service_name)",
|
||||
"refresh": 2,
|
||||
"type": "query"
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
"type": "stat",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(rate(airi_http_server_request_duration_milliseconds_count{service_name=~\"$service\"}[$__rate_interval]))",
|
||||
"expr": "sum(rate(airi_http_server_request_duration_seconds_count{service_name=~\"$service\"}[$__rate_interval]))",
|
||||
"legendFormat": "req/s"
|
||||
}
|
||||
]
|
||||
@@ -80,7 +80,7 @@
|
||||
"type": "stat",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(rate(airi_http_server_request_duration_milliseconds_count{service_name=~\"$service\", http_status_code=~\"5..\"}[$__rate_interval])) / sum(rate(airi_http_server_request_duration_milliseconds_count{service_name=~\"$service\"}[$__rate_interval]))",
|
||||
"expr": "sum(rate(airi_http_server_request_duration_seconds_count{service_name=~\"$service\", http_status_code=~\"5..\"}[$__rate_interval])) / sum(rate(airi_http_server_request_duration_seconds_count{service_name=~\"$service\"}[$__rate_interval]))",
|
||||
"legendFormat": "error rate"
|
||||
}
|
||||
]
|
||||
@@ -94,11 +94,11 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{ "color": "green", "value": null },
|
||||
{ "color": "yellow", "value": 200 },
|
||||
{ "color": "red", "value": 1000 }
|
||||
{ "color": "yellow", "value": 0.2 },
|
||||
{ "color": "red", "value": 1.0 }
|
||||
]
|
||||
},
|
||||
"unit": "ms"
|
||||
"unit": "s"
|
||||
}
|
||||
},
|
||||
"gridPos": { "h": 8, "w": 8, "x": 16, "y": 1 },
|
||||
@@ -108,11 +108,11 @@
|
||||
"colorMode": "value",
|
||||
"graphMode": "area"
|
||||
},
|
||||
"title": "P95 Latency",
|
||||
"title": "P95 Latency (excl. LLM)",
|
||||
"type": "stat",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "histogram_quantile(0.95, sum(rate(airi_http_server_request_duration_milliseconds_bucket{service_name=~\"$service\"}[$__rate_interval])) by (le))",
|
||||
"expr": "histogram_quantile(0.95, sum(rate(airi_http_server_request_duration_seconds_bucket{service_name=~\"$service\", http_route!~\"/api/v1/openai/.*\"}[$__rate_interval])) by (le))",
|
||||
"legendFormat": "p95"
|
||||
}
|
||||
]
|
||||
@@ -136,7 +136,7 @@
|
||||
"type": "timeseries",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum by (http_route) (rate(airi_http_server_request_duration_milliseconds_count{service_name=~\"$service\"}[$__rate_interval]))",
|
||||
"expr": "sum by (http_route) (rate(airi_http_server_request_duration_seconds_count{service_name=~\"$service\"}[$__rate_interval]))",
|
||||
"legendFormat": "{{http_route}}"
|
||||
}
|
||||
]
|
||||
@@ -150,7 +150,7 @@
|
||||
"lineInterpolation": "smooth",
|
||||
"fillOpacity": 20
|
||||
},
|
||||
"unit": "ms"
|
||||
"unit": "s"
|
||||
}
|
||||
},
|
||||
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 9 },
|
||||
@@ -159,7 +159,7 @@
|
||||
"type": "timeseries",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "histogram_quantile(0.95, sum by (le, http_route) (rate(airi_http_server_request_duration_milliseconds_bucket{service_name=~\"$service\"}[$__rate_interval])))",
|
||||
"expr": "histogram_quantile(0.95, sum by (le, http_route) (rate(airi_http_server_request_duration_seconds_bucket{service_name=~\"$service\"}[$__rate_interval])))",
|
||||
"legendFormat": "{{http_route}}"
|
||||
}
|
||||
]
|
||||
@@ -182,7 +182,7 @@
|
||||
"type": "timeseries",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum by (http_status_code) (rate(airi_http_server_request_duration_milliseconds_count{service_name=~\"$service\"}[$__rate_interval]))",
|
||||
"expr": "sum by (http_status_code) (rate(airi_http_server_request_duration_seconds_count{service_name=~\"$service\"}[$__rate_interval]))",
|
||||
"legendFormat": "{{http_status_code}}"
|
||||
}
|
||||
]
|
||||
@@ -318,7 +318,7 @@
|
||||
"lineInterpolation": "smooth",
|
||||
"fillOpacity": 20
|
||||
},
|
||||
"unit": "ms"
|
||||
"unit": "s"
|
||||
}
|
||||
},
|
||||
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 43 },
|
||||
@@ -327,7 +327,7 @@
|
||||
"type": "timeseries",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "histogram_quantile(0.95, sum by (le, db_operation) (rate(airi_db_client_operation_duration_milliseconds_bucket{service_name=~\"$service\"}[$__rate_interval])))",
|
||||
"expr": "histogram_quantile(0.95, sum by (le, db_operation) (rate(airi_db_client_operation_duration_seconds_bucket{service_name=~\"$service\"}[$__rate_interval])))",
|
||||
"legendFormat": "{{db_operation}}"
|
||||
}
|
||||
]
|
||||
@@ -350,7 +350,7 @@
|
||||
"type": "timeseries",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum by (db_operation) (rate(airi_db_client_operation_duration_milliseconds_count{service_name=~\"$service\"}[$__rate_interval]))",
|
||||
"expr": "sum by (db_operation) (rate(airi_db_client_operation_duration_seconds_count{service_name=~\"$service\"}[$__rate_interval]))",
|
||||
"legendFormat": "{{db_operation}}"
|
||||
}
|
||||
]
|
||||
@@ -402,7 +402,7 @@
|
||||
"lineInterpolation": "smooth",
|
||||
"fillOpacity": 20
|
||||
},
|
||||
"unit": "ms"
|
||||
"unit": "s"
|
||||
}
|
||||
},
|
||||
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 60 },
|
||||
@@ -411,7 +411,7 @@
|
||||
"type": "timeseries",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "histogram_quantile(0.95, sum by (le, db_operation) (rate(airi_db_client_operation_duration_milliseconds_bucket{service_name=~\"$service\", db_system=\"redis\"}[$__rate_interval])))",
|
||||
"expr": "histogram_quantile(0.95, sum by (le, db_operation) (rate(airi_db_client_operation_duration_seconds_bucket{service_name=~\"$service\", db_system=\"redis\"}[$__rate_interval])))",
|
||||
"legendFormat": "{{db_operation}}"
|
||||
}
|
||||
]
|
||||
@@ -434,7 +434,7 @@
|
||||
"type": "timeseries",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum by (db_operation) (rate(airi_db_client_operation_duration_milliseconds_count{service_name=~\"$service\", db_system=\"redis\"}[$__rate_interval]))",
|
||||
"expr": "sum by (db_operation) (rate(airi_db_client_operation_duration_seconds_count{service_name=~\"$service\", db_system=\"redis\"}[$__rate_interval]))",
|
||||
"legendFormat": "{{db_operation}}"
|
||||
}
|
||||
]
|
||||
@@ -486,12 +486,12 @@
|
||||
{
|
||||
"current": { "selected": false, "text": "airi-server", "value": "airi-server" },
|
||||
"datasource": { "type": "prometheus", "uid": "${datasource}" },
|
||||
"definition": "label_values(airi_http_server_request_duration_milliseconds_count, service_name)",
|
||||
"definition": "label_values(airi_http_server_request_duration_seconds_count, service_name)",
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"multi": true,
|
||||
"name": "service",
|
||||
"query": "label_values(airi_http_server_request_duration_milliseconds_count, service_name)",
|
||||
"query": "label_values(airi_http_server_request_duration_seconds_count, service_name)",
|
||||
"refresh": 2,
|
||||
"type": "query"
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ async function buildApp(deps: AppDeps) {
|
||||
/**
|
||||
* V1 routes for official provider.
|
||||
*/
|
||||
.route('/api/v1/openai', createV1CompletionsRoutes(deps.fluxService, deps.billingService, deps.configKV, deps.billingMq, deps.otel?.llm))
|
||||
.route('/api/v1/openai', createV1CompletionsRoutes(deps.fluxService, deps.billingService, deps.configKV, deps.billingMq, deps.otel?.genAi))
|
||||
|
||||
/**
|
||||
* Flux routes.
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { AuthMetrics } from './otel'
|
||||
|
||||
import { betterAuth } from 'better-auth'
|
||||
import { drizzleAdapter } from 'better-auth/adapters/drizzle'
|
||||
import { createAuthMiddleware } from 'better-auth/api'
|
||||
import { bearer } from 'better-auth/plugins'
|
||||
|
||||
import { getAuthTrustedOrigins } from '../utils/origin'
|
||||
@@ -53,6 +54,32 @@ export function createAuth(db: Database, env: Env, metrics?: AuthMetrics | null)
|
||||
},
|
||||
},
|
||||
|
||||
hooks: {
|
||||
before: createAuthMiddleware(async (ctx) => {
|
||||
const isAuthAttempt = ctx.path.includes('/sign-in') || ctx.path.includes('/sign-up')
|
||||
if (isAuthAttempt) {
|
||||
metrics?.attempts.add(1, { 'auth.method': ctx.path.split('/').pop() ?? 'unknown' })
|
||||
}
|
||||
}),
|
||||
after: createAuthMiddleware(async (ctx) => {
|
||||
// Track auth failures via otel
|
||||
const isAuthAttempt = ctx.path.includes('/sign-in') || ctx.path.includes('/sign-up')
|
||||
if (isAuthAttempt && ctx.context.returned && typeof ctx.context.returned === 'object' && 'error' in ctx.context.returned) {
|
||||
metrics?.failures.add(1, { 'auth.method': ctx.path.split('/').pop() ?? 'unknown' })
|
||||
}
|
||||
|
||||
// On OAuth callback errors, redirect back to the referer instead of returning API JSON
|
||||
if (ctx.path.startsWith('/callback') && ctx.context.returned && typeof ctx.context.returned === 'object' && 'error' in ctx.context.returned) {
|
||||
const referer = ctx.getHeader('referer')
|
||||
if (referer) {
|
||||
const url = new URL(referer)
|
||||
url.searchParams.set('error', 'auth_failed')
|
||||
throw ctx.redirect(url.toString())
|
||||
}
|
||||
}
|
||||
}),
|
||||
},
|
||||
|
||||
databaseHooks: {
|
||||
user: {
|
||||
create: {
|
||||
|
||||
@@ -23,6 +23,34 @@ import { NodeSDK } from '@opentelemetry/sdk-node'
|
||||
import { BatchSpanProcessor, ParentBasedSampler, TraceIdRatioBasedSampler } from '@opentelemetry/sdk-trace-node'
|
||||
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions'
|
||||
|
||||
import {
|
||||
METRIC_AUTH_ATTEMPTS,
|
||||
METRIC_AUTH_FAILURES,
|
||||
METRIC_CHARACTER_CREATED,
|
||||
METRIC_CHARACTER_DELETED,
|
||||
METRIC_CHARACTER_ENGAGEMENT,
|
||||
METRIC_CHAT_MESSAGES,
|
||||
METRIC_FLUX_CONSUMED,
|
||||
METRIC_FLUX_INSUFFICIENT_BALANCE,
|
||||
METRIC_GEN_AI_CLIENT_OPERATION_COUNT,
|
||||
METRIC_GEN_AI_CLIENT_OPERATION_DURATION,
|
||||
METRIC_GEN_AI_CLIENT_TOKEN_USAGE_INPUT,
|
||||
METRIC_GEN_AI_CLIENT_TOKEN_USAGE_OUTPUT,
|
||||
METRIC_HTTP_SERVER_ACTIVE_REQUESTS,
|
||||
METRIC_HTTP_SERVER_REQUEST_DURATION,
|
||||
METRIC_STRIPE_CHECKOUT_COMPLETED,
|
||||
METRIC_STRIPE_CHECKOUT_CREATED,
|
||||
METRIC_STRIPE_EVENTS,
|
||||
METRIC_STRIPE_PAYMENT_FAILED,
|
||||
METRIC_STRIPE_SUBSCRIPTION_EVENT,
|
||||
METRIC_USER_ACTIVE_SESSIONS,
|
||||
METRIC_USER_LOGIN,
|
||||
METRIC_USER_REGISTERED,
|
||||
METRIC_WS_CONNECTIONS_ACTIVE,
|
||||
METRIC_WS_MESSAGES_RECEIVED,
|
||||
METRIC_WS_MESSAGES_SENT,
|
||||
} from '../utils/observability'
|
||||
|
||||
const logger = useLogger('otel')
|
||||
|
||||
export interface HttpMetrics {
|
||||
@@ -57,18 +85,18 @@ export interface RevenueMetrics {
|
||||
fluxInsufficientBalance: Counter
|
||||
}
|
||||
|
||||
export interface LlmMetrics {
|
||||
requestDuration: Histogram
|
||||
requestCount: Counter
|
||||
tokensPrompt: Counter
|
||||
tokensCompletion: Counter
|
||||
export interface GenAiMetrics {
|
||||
operationDuration: Histogram
|
||||
operationCount: Counter
|
||||
tokenUsageInput: Counter
|
||||
tokenUsageOutput: Counter
|
||||
fluxConsumed: Counter
|
||||
}
|
||||
|
||||
export interface DbMetrics {
|
||||
queryDuration: Histogram
|
||||
redisCommandDuration: Histogram
|
||||
}
|
||||
// NOTICE: Database metrics (db.client.operation.duration, redis.client.command.duration) were
|
||||
// intentionally removed. PgInstrumentation and IORedisInstrumentation already generate spans
|
||||
// with timing for every query/command. To surface these as metrics in Grafana, configure the
|
||||
// OTel Collector's spanmetrics connector to derive metrics from those spans.
|
||||
|
||||
export interface OtelInstance {
|
||||
sdk: NodeSDK
|
||||
@@ -76,8 +104,7 @@ export interface OtelInstance {
|
||||
auth: AuthMetrics
|
||||
engagement: EngagementMetrics
|
||||
revenue: RevenueMetrics
|
||||
llm: LlmMetrics
|
||||
db: DbMetrics
|
||||
genAi: GenAiMetrics
|
||||
shutdown: () => Promise<void>
|
||||
}
|
||||
|
||||
@@ -168,115 +195,103 @@ export function initOtel(env: Env): OtelInstance | undefined {
|
||||
|
||||
const meter = metrics.getMeter(serviceName)
|
||||
|
||||
// HTTP metrics
|
||||
// HTTP metrics (semconv: unit MUST be seconds)
|
||||
const http: HttpMetrics = {
|
||||
requestDuration: meter.createHistogram('http.server.request.duration', {
|
||||
description: 'HTTP server request duration in milliseconds',
|
||||
unit: 'ms',
|
||||
requestDuration: meter.createHistogram(METRIC_HTTP_SERVER_REQUEST_DURATION, {
|
||||
description: 'HTTP server request duration',
|
||||
unit: 's',
|
||||
}),
|
||||
activeRequests: meter.createUpDownCounter('http.server.active_requests', {
|
||||
activeRequests: meter.createUpDownCounter(METRIC_HTTP_SERVER_ACTIVE_REQUESTS, {
|
||||
description: 'Number of active HTTP requests',
|
||||
}),
|
||||
}
|
||||
|
||||
// Auth & User metrics
|
||||
const auth: AuthMetrics = {
|
||||
attempts: meter.createCounter('auth.attempts', {
|
||||
attempts: meter.createCounter(METRIC_AUTH_ATTEMPTS, {
|
||||
description: 'Number of authentication attempts',
|
||||
}),
|
||||
failures: meter.createCounter('auth.failures', {
|
||||
failures: meter.createCounter(METRIC_AUTH_FAILURES, {
|
||||
description: 'Number of failed authentication attempts',
|
||||
}),
|
||||
userRegistered: meter.createCounter('user.registered', {
|
||||
userRegistered: meter.createCounter(METRIC_USER_REGISTERED, {
|
||||
description: 'Number of new user registrations',
|
||||
}),
|
||||
userLogin: meter.createCounter('user.login', {
|
||||
userLogin: meter.createCounter(METRIC_USER_LOGIN, {
|
||||
description: 'Number of user logins',
|
||||
}),
|
||||
activeSessions: meter.createUpDownCounter('user.active_sessions', {
|
||||
activeSessions: meter.createUpDownCounter(METRIC_USER_ACTIVE_SESSIONS, {
|
||||
description: 'Number of active user sessions',
|
||||
}),
|
||||
}
|
||||
|
||||
// Engagement metrics
|
||||
const engagement: EngagementMetrics = {
|
||||
chatMessages: meter.createCounter('chat.messages', {
|
||||
chatMessages: meter.createCounter(METRIC_CHAT_MESSAGES, {
|
||||
description: 'Number of chat messages written or pulled',
|
||||
}),
|
||||
characterCreated: meter.createCounter('character.created', {
|
||||
characterCreated: meter.createCounter(METRIC_CHARACTER_CREATED, {
|
||||
description: 'Number of characters created',
|
||||
}),
|
||||
characterDeleted: meter.createCounter('character.deleted', {
|
||||
characterDeleted: meter.createCounter(METRIC_CHARACTER_DELETED, {
|
||||
description: 'Number of characters deleted',
|
||||
}),
|
||||
characterEngagement: meter.createCounter('character.engagement', {
|
||||
characterEngagement: meter.createCounter(METRIC_CHARACTER_ENGAGEMENT, {
|
||||
description: 'Number of character engagement actions (like/bookmark)',
|
||||
}),
|
||||
wsConnectionsActive: meter.createUpDownCounter('ws.connections.active', {
|
||||
wsConnectionsActive: meter.createUpDownCounter(METRIC_WS_CONNECTIONS_ACTIVE, {
|
||||
description: 'Active WebSocket connections',
|
||||
}),
|
||||
wsMessagesSent: meter.createCounter('ws.messages.sent', {
|
||||
wsMessagesSent: meter.createCounter(METRIC_WS_MESSAGES_SENT, {
|
||||
description: 'Messages sent via WebSocket',
|
||||
}),
|
||||
wsMessagesReceived: meter.createCounter('ws.messages.received', {
|
||||
wsMessagesReceived: meter.createCounter(METRIC_WS_MESSAGES_RECEIVED, {
|
||||
description: 'Messages received via WebSocket',
|
||||
}),
|
||||
}
|
||||
|
||||
// Revenue metrics
|
||||
const revenue: RevenueMetrics = {
|
||||
stripeCheckoutCreated: meter.createCounter('stripe.checkout.created', {
|
||||
stripeCheckoutCreated: meter.createCounter(METRIC_STRIPE_CHECKOUT_CREATED, {
|
||||
description: 'Number of Stripe checkout sessions created',
|
||||
}),
|
||||
stripeCheckoutCompleted: meter.createCounter('stripe.checkout.completed', {
|
||||
stripeCheckoutCompleted: meter.createCounter(METRIC_STRIPE_CHECKOUT_COMPLETED, {
|
||||
description: 'Number of Stripe checkout sessions completed',
|
||||
}),
|
||||
stripePaymentFailed: meter.createCounter('stripe.payment.failed', {
|
||||
stripePaymentFailed: meter.createCounter(METRIC_STRIPE_PAYMENT_FAILED, {
|
||||
description: 'Number of failed Stripe payments',
|
||||
}),
|
||||
stripeSubscriptionEvent: meter.createCounter('stripe.subscription.event', {
|
||||
stripeSubscriptionEvent: meter.createCounter(METRIC_STRIPE_SUBSCRIPTION_EVENT, {
|
||||
description: 'Number of Stripe subscription lifecycle events',
|
||||
}),
|
||||
stripeEvents: meter.createCounter('stripe.events', {
|
||||
stripeEvents: meter.createCounter(METRIC_STRIPE_EVENTS, {
|
||||
description: 'Number of Stripe webhook events processed',
|
||||
}),
|
||||
fluxInsufficientBalance: meter.createCounter('flux.insufficient_balance', {
|
||||
fluxInsufficientBalance: meter.createCounter(METRIC_FLUX_INSUFFICIENT_BALANCE, {
|
||||
description: 'Number of insufficient flux balance errors',
|
||||
}),
|
||||
}
|
||||
|
||||
// LLM / Gateway metrics
|
||||
const llm: LlmMetrics = {
|
||||
requestDuration: meter.createHistogram('llm.request.duration', {
|
||||
description: 'LLM gateway request duration in milliseconds',
|
||||
unit: 'ms',
|
||||
// GenAI metrics (semconv: gen_ai.client.*)
|
||||
const genAi: GenAiMetrics = {
|
||||
operationDuration: meter.createHistogram(METRIC_GEN_AI_CLIENT_OPERATION_DURATION, {
|
||||
description: 'GenAI client operation duration',
|
||||
unit: 's',
|
||||
}),
|
||||
requestCount: meter.createCounter('llm.request.count', {
|
||||
description: 'Number of LLM gateway requests',
|
||||
operationCount: meter.createCounter(METRIC_GEN_AI_CLIENT_OPERATION_COUNT, {
|
||||
description: 'Number of GenAI client operations',
|
||||
}),
|
||||
tokensPrompt: meter.createCounter('llm.tokens.prompt', {
|
||||
description: 'Total prompt tokens consumed',
|
||||
tokenUsageInput: meter.createCounter(METRIC_GEN_AI_CLIENT_TOKEN_USAGE_INPUT, {
|
||||
description: 'Total input (prompt) tokens consumed',
|
||||
}),
|
||||
tokensCompletion: meter.createCounter('llm.tokens.completion', {
|
||||
description: 'Total completion tokens consumed',
|
||||
tokenUsageOutput: meter.createCounter(METRIC_GEN_AI_CLIENT_TOKEN_USAGE_OUTPUT, {
|
||||
description: 'Total output (completion) tokens consumed',
|
||||
}),
|
||||
fluxConsumed: meter.createCounter('flux.consumed', {
|
||||
fluxConsumed: meter.createCounter(METRIC_FLUX_CONSUMED, {
|
||||
description: 'Total flux consumed',
|
||||
}),
|
||||
}
|
||||
|
||||
// Database metrics
|
||||
const db: DbMetrics = {
|
||||
queryDuration: meter.createHistogram('db.client.operation.duration', {
|
||||
description: 'Database operation duration in milliseconds',
|
||||
unit: 'ms',
|
||||
}),
|
||||
redisCommandDuration: meter.createHistogram('redis.client.command.duration', {
|
||||
description: 'Redis command duration in milliseconds',
|
||||
unit: 'ms',
|
||||
}),
|
||||
}
|
||||
|
||||
// Graceful shutdown
|
||||
const shutdown = async () => {
|
||||
try {
|
||||
@@ -294,8 +309,7 @@ export function initOtel(env: Env): OtelInstance | undefined {
|
||||
auth,
|
||||
engagement,
|
||||
revenue,
|
||||
llm,
|
||||
db,
|
||||
genAi,
|
||||
shutdown,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ export function otelMiddleware(http: HttpMetrics): MiddlewareHandler<HonoEnv> {
|
||||
span.setStatus({ code: SpanStatusCode.ERROR, message: `HTTP ${status}` })
|
||||
}
|
||||
|
||||
http.requestDuration.record(performance.now() - startTime, {
|
||||
http.requestDuration.record((performance.now() - startTime) / 1000, {
|
||||
'http.request.method': method,
|
||||
'http.route': path,
|
||||
'http.response.status_code': status,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Context } from 'hono'
|
||||
|
||||
import type { MqService } from '../../../libs/mq'
|
||||
import type { LlmMetrics } from '../../../libs/otel'
|
||||
import type { GenAiMetrics } from '../../../libs/otel'
|
||||
import type { UsageInfo } from '../../../services/billing/billing'
|
||||
import type { BillingEvent } from '../../../services/billing/billing-events'
|
||||
import type { BillingService } from '../../../services/billing/billing-service'
|
||||
@@ -69,22 +69,22 @@ function getLlmMetricAttributes(opts: { model: string, type: string, status: num
|
||||
}
|
||||
}
|
||||
|
||||
export function createV1CompletionsRoutes(fluxService: FluxService, billingService: BillingService, configKV: ConfigKVService, billingMq: MqService<BillingEvent>, llm?: LlmMetrics | null) {
|
||||
export function createV1CompletionsRoutes(fluxService: FluxService, billingService: BillingService, configKV: ConfigKVService, billingMq: MqService<BillingEvent>, genAi?: GenAiMetrics | null) {
|
||||
const logger = useLogger('v1-completions').useGlobalConfig()
|
||||
// TODO: Extract this compat route into smaller facades/modules.
|
||||
// It currently mixes auth, rate limiting, proxying, billing, telemetry, and event publishing in one transport layer entrypoint.
|
||||
|
||||
function recordMetrics(opts: { model: string, status: number, type: string, durationMs: number, fluxConsumed: number, promptTokens?: number, completionTokens?: number }) {
|
||||
if (!llm)
|
||||
if (!genAi)
|
||||
return
|
||||
const attrs = getLlmMetricAttributes(opts)
|
||||
llm.requestCount.add(1, attrs)
|
||||
llm.requestDuration.record(opts.durationMs, attrs)
|
||||
llm.fluxConsumed.add(opts.fluxConsumed, attrs)
|
||||
genAi.operationCount.add(1, attrs)
|
||||
genAi.operationDuration.record(opts.durationMs / 1000, attrs)
|
||||
genAi.fluxConsumed.add(opts.fluxConsumed, attrs)
|
||||
if (opts.promptTokens != null)
|
||||
llm.tokensPrompt.add(opts.promptTokens, attrs)
|
||||
genAi.tokenUsageInput.add(opts.promptTokens, attrs)
|
||||
if (opts.completionTokens != null)
|
||||
llm.tokensCompletion.add(opts.completionTokens, attrs)
|
||||
genAi.tokenUsageOutput.add(opts.completionTokens, attrs)
|
||||
}
|
||||
|
||||
function publishRequestLog(entry: { userId: string, model: string, status: number, durationMs: number, fluxConsumed: number, promptTokens?: number, completionTokens?: number }) {
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
// ---------------------------------------------------------------------------
|
||||
// Span attribute constants (OTel semantic conventions + AIRI custom)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// GenAI semconv attributes — https://opentelemetry.io/docs/specs/semconv/gen-ai/
|
||||
export const GEN_AI_ATTR_OPERATION_NAME = 'gen_ai.operation.name'
|
||||
export const GEN_AI_ATTR_REQUEST_MODEL = 'gen_ai.request.model'
|
||||
export const GEN_AI_ATTR_USAGE_INPUT_TOKENS = 'gen_ai.usage.input_tokens'
|
||||
export const GEN_AI_ATTR_USAGE_OUTPUT_TOKENS = 'gen_ai.usage.output_tokens'
|
||||
|
||||
// AIRI custom span attributes
|
||||
export const AIRI_ATTR_BILLING_FLUX_CONSUMED = 'airi.billing.flux_consumed'
|
||||
export const AIRI_ATTR_GEN_AI_INPUT_MESSAGES = 'airi.gen_ai.input.messages'
|
||||
export const AIRI_ATTR_GEN_AI_INPUT_TEXT = 'airi.gen_ai.input.text'
|
||||
@@ -13,9 +19,53 @@ export const AIRI_ATTR_GEN_AI_OUTPUT_TEXT = 'airi.gen_ai.output.text'
|
||||
export const AIRI_ATTR_GEN_AI_STREAM = 'airi.gen_ai.stream'
|
||||
export const AIRI_ATTR_GEN_AI_STREAM_INTERRUPTED = 'airi.gen_ai.stream_interrupted'
|
||||
|
||||
// Server attributes
|
||||
export const SERVER_ATTR_ADDRESS = 'server.address'
|
||||
export const SERVER_ATTR_PORT = 'server.port'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metric name constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// HTTP — https://opentelemetry.io/docs/specs/semconv/http/http-metrics/
|
||||
export const METRIC_HTTP_SERVER_REQUEST_DURATION = 'http.server.request.duration'
|
||||
export const METRIC_HTTP_SERVER_ACTIVE_REQUESTS = 'http.server.active_requests'
|
||||
|
||||
// Auth & user (AIRI custom)
|
||||
export const METRIC_AUTH_ATTEMPTS = 'auth.attempts'
|
||||
export const METRIC_AUTH_FAILURES = 'auth.failures'
|
||||
export const METRIC_USER_REGISTERED = 'user.registered'
|
||||
export const METRIC_USER_LOGIN = 'user.login'
|
||||
export const METRIC_USER_ACTIVE_SESSIONS = 'user.active_sessions'
|
||||
|
||||
// Engagement (AIRI custom)
|
||||
export const METRIC_CHAT_MESSAGES = 'chat.messages'
|
||||
export const METRIC_CHARACTER_CREATED = 'character.created'
|
||||
export const METRIC_CHARACTER_DELETED = 'character.deleted'
|
||||
export const METRIC_CHARACTER_ENGAGEMENT = 'character.engagement'
|
||||
export const METRIC_WS_CONNECTIONS_ACTIVE = 'ws.connections.active'
|
||||
export const METRIC_WS_MESSAGES_SENT = 'ws.messages.sent'
|
||||
export const METRIC_WS_MESSAGES_RECEIVED = 'ws.messages.received'
|
||||
|
||||
// Revenue (AIRI custom)
|
||||
export const METRIC_STRIPE_CHECKOUT_CREATED = 'stripe.checkout.created'
|
||||
export const METRIC_STRIPE_CHECKOUT_COMPLETED = 'stripe.checkout.completed'
|
||||
export const METRIC_STRIPE_PAYMENT_FAILED = 'stripe.payment.failed'
|
||||
export const METRIC_STRIPE_SUBSCRIPTION_EVENT = 'stripe.subscription.event'
|
||||
export const METRIC_STRIPE_EVENTS = 'stripe.events'
|
||||
export const METRIC_FLUX_INSUFFICIENT_BALANCE = 'flux.insufficient_balance'
|
||||
|
||||
// GenAI — https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-metrics/
|
||||
export const METRIC_GEN_AI_CLIENT_OPERATION_DURATION = 'gen_ai.client.operation.duration'
|
||||
export const METRIC_GEN_AI_CLIENT_OPERATION_COUNT = 'gen_ai.client.operation.count'
|
||||
export const METRIC_GEN_AI_CLIENT_TOKEN_USAGE_INPUT = 'gen_ai.client.token.usage.input'
|
||||
export const METRIC_GEN_AI_CLIENT_TOKEN_USAGE_OUTPUT = 'gen_ai.client.token.usage.output'
|
||||
export const METRIC_FLUX_CONSUMED = 'airi.billing.flux.consumed'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function getServerConnectionAttributes(baseUrl: string): Record<string, string | number> {
|
||||
const url = new URL(baseUrl)
|
||||
const attributes: Record<string, string | number> = {
|
||||
|
||||
Reference in New Issue
Block a user