fix(metrics): 补齐媒体结果存储读取与兼容响应指标

增加对象存储 GET、结果 URL 签名和同步 Base64 限流指标,使生产轮询可直接验证零对象下载,并区分签名失败、容量等待超时和 20MiB 超限。\n\n验证:API 全量 go test、go vet、gofmt 与 git diff --check。
This commit is contained in:
2026-08-05 18:55:41 +08:00
parent b13392ef50
commit 03c0873649
7 changed files with 113 additions and 7 deletions
@@ -109,11 +109,19 @@ type Metrics struct {
storageChannelFailovers atomic.Uint64
storageAllChannelsFailed atomic.Uint64
storageObjectifiedBytes atomic.Uint64
storageObjectReads atomic.Uint64
storageObjectReadFailures atomic.Uint64
storageObjectReadBytes atomic.Uint64
resultSourceUpstreamURL atomic.Uint64
resultSourceUploaded atomic.Uint64
resultPollResponses atomic.Uint64
resultPollResponseBytes atomic.Uint64
resultPollOversized atomic.Uint64
resultURLSignSuccess atomic.Uint64
resultURLSignFailure atomic.Uint64
syncBase64Success atomic.Uint64
syncBase64CapacityTimeout atomic.Uint64
syncBase64TooLarge atomic.Uint64
taskAdmissionWaitBuckets [11]atomic.Uint64
taskAdmissionWaitMicros atomic.Uint64
}
@@ -347,6 +355,13 @@ func (m *Metrics) ObserveObjectStorage(event string, provider string, bytes int6
m.storageChannelFailovers.Add(1)
case "all_failed":
m.storageAllChannelsFailed.Add(1)
case "read_success", "read_failure":
m.storageObjectReads.Add(1)
if event == "read_failure" {
m.storageObjectReadFailures.Add(1)
} else if bytes > 0 {
m.storageObjectReadBytes.Add(uint64(bytes))
}
}
}
@@ -371,6 +386,26 @@ func (m *Metrics) ObserveResultDelivery(event string, bytes int64) {
}
}
func (m *Metrics) ObserveResultURLSigning(outcome string) {
switch strings.ToLower(strings.TrimSpace(outcome)) {
case "success":
m.resultURLSignSuccess.Add(1)
case "failure":
m.resultURLSignFailure.Add(1)
}
}
func (m *Metrics) ObserveSynchronousBase64(outcome string) {
switch strings.ToLower(strings.TrimSpace(outcome)) {
case "success":
m.syncBase64Success.Add(1)
case "capacity_timeout":
m.syncBase64CapacityTimeout.Add(1)
case "too_large":
m.syncBase64TooLarge.Add(1)
}
}
func (m *Metrics) ObserveTaskAdmissionWait(wait time.Duration) {
if wait < 0 {
wait = 0
@@ -587,6 +622,9 @@ func (m *Metrics) Handler(provider MetricsSnapshotProvider, issuer, audience str
plainCounter(w, "easyai_gateway_storage_channel_failovers_total", "Switches to the next configured storage channel.", m.storageChannelFailovers.Load())
plainCounter(w, "easyai_gateway_storage_all_channels_failed_total", "Storage writes where every eligible channel failed.", m.storageAllChannelsFailed.Load())
plainCounter(w, "easyai_gateway_storage_objectified_bytes_total", "Binary bytes successfully written through storage channels.", m.storageObjectifiedBytes.Load())
plainCounter(w, "easyai_gateway_storage_object_get_attempts_total", "Object storage GET attempts, including synchronous compatibility hydration.", m.storageObjectReads.Load())
plainCounter(w, "easyai_gateway_storage_object_get_failures_total", "Failed object storage GET attempts.", m.storageObjectReadFailures.Load())
plainCounter(w, "easyai_gateway_storage_object_get_bytes_total", "Bytes read from object storage GET responses.", m.storageObjectReadBytes.Load())
outcomeCounters(w, "easyai_gateway_result_storage_total", "Generated media results by canonical storage source.", []outcomeValue{
{"upstream_url", m.resultSourceUpstreamURL.Load()},
{"uploaded", m.resultSourceUploaded.Load()},
@@ -594,6 +632,15 @@ func (m *Metrics) Handler(provider MetricsSnapshotProvider, issuer, audience str
plainCounter(w, "easyai_gateway_result_poll_responses_total", "Successful URL-only task result responses.", m.resultPollResponses.Load())
plainCounter(w, "easyai_gateway_result_poll_response_bytes_total", "Bytes written by successful URL-only task result responses.", m.resultPollResponseBytes.Load())
plainCounter(w, "easyai_gateway_result_poll_oversized_total", "Task result responses rejected by the URL-only size gate.", m.resultPollOversized.Load())
outcomeCounters(w, "easyai_gateway_result_url_signing_total", "Private result URL signing attempts by outcome.", []outcomeValue{
{"success", m.resultURLSignSuccess.Load()},
{"failure", m.resultURLSignFailure.Load()},
})
outcomeCounters(w, "easyai_gateway_sync_base64_responses_total", "Synchronous Base64 compatibility responses by bounded outcome.", []outcomeValue{
{"success", m.syncBase64Success.Load()},
{"capacity_timeout", m.syncBase64CapacityTimeout.Load()},
{"too_large", m.syncBase64TooLarge.Load()},
})
publicErrorCounters(w, publicerror.MetricSnapshot())
platformModelRateLimitUtilizationGauges(w, modelRateLimits)
plainGauge(w, "easyai_gateway_postgres_pool_max_connections", "Maximum PostgreSQL connections in this process pool.", int64(postgresPool.MaxConnections))
@@ -57,10 +57,17 @@ func TestMetricsExposeBoundedOutcomesAndState(t *testing.T) {
metrics.ObserveObjectStorage("retry", "s3", 0, 0)
metrics.ObserveObjectStorage("failover", "s3", 0, 0)
metrics.ObserveObjectStorage("all_failed", "", 0, 0)
metrics.ObserveObjectStorage("read_success", "aliyun_oss", 256, 15*time.Millisecond)
metrics.ObserveObjectStorage("read_failure", "aliyun_oss", 0, 5*time.Millisecond)
metrics.ObserveResultStorage("upstream_url")
metrics.ObserveResultStorage("uploaded")
metrics.ObserveResultDelivery("poll_success", 512)
metrics.ObserveResultDelivery("poll_oversized", 70<<10)
metrics.ObserveResultURLSigning("success")
metrics.ObserveResultURLSigning("failure")
metrics.ObserveSynchronousBase64("success")
metrics.ObserveSynchronousBase64("capacity_timeout")
metrics.ObserveSynchronousBase64("too_large")
metrics.ObserveAsyncWorkerResize("success")
metrics.ObserveConcurrencyLeaseRenewal("success")
metrics.ObserveConcurrencyLeaseRenewal("lost")
@@ -121,11 +128,19 @@ func TestMetricsExposeBoundedOutcomesAndState(t *testing.T) {
`easyai_gateway_storage_channel_failovers_total 1`,
`easyai_gateway_storage_all_channels_failed_total 1`,
`easyai_gateway_storage_objectified_bytes_total 128`,
`easyai_gateway_storage_object_get_attempts_total 2`,
`easyai_gateway_storage_object_get_failures_total 1`,
`easyai_gateway_storage_object_get_bytes_total 256`,
`easyai_gateway_result_storage_total{outcome="upstream_url"} 1`,
`easyai_gateway_result_storage_total{outcome="uploaded"} 1`,
`easyai_gateway_result_poll_responses_total 1`,
`easyai_gateway_result_poll_response_bytes_total 512`,
`easyai_gateway_result_poll_oversized_total 1`,
`easyai_gateway_result_url_signing_total{outcome="success"} 1`,
`easyai_gateway_result_url_signing_total{outcome="failure"} 1`,
`easyai_gateway_sync_base64_responses_total{outcome="success"} 1`,
`easyai_gateway_sync_base64_responses_total{outcome="capacity_timeout"} 1`,
`easyai_gateway_sync_base64_responses_total{outcome="too_large"} 1`,
`easyai_gateway_platform_model_rate_limit_utilization{platform_model_id="platform-model-1",metric="concurrent"} 0.750000`,
`easyai_gateway_async_worker_resizes_total{outcome="success"} 1`,
`easyai_gateway_concurrency_lease_renewals_total{outcome="success"} 1`,