- 预扣:每请求按 body 估算成本上界计入冻结额(1e-8 USD 原子计数),402 判定 改为「余额 - 在途冻结 ≤ 0」,并发的长流式请求不再互相透支;OT_PROXY_RESERVE_MAX_TOKENS 控制估算基数(默认 4096,0=关闭),handler defer 释放,失败/中断/panic 均覆盖 - 调度:全部候选都在运行期冷却时,剩余冷却 ≤ OT_PROXY_COOLDOWN_GRACE 的最早恢复渠道 兜底重试一次而不是 503(429 Retry-After 语义);DB 层不可用的渠道不参与兜底 - 指标:openteam_proxy_requests_total / request_duration / channel_failures / usage_queue_depth / frozen_balance,/metrics 默认关闭(OT_METRICS_ENABLED 开启)
59 lines
1.7 KiB
Go
59 lines
1.7 KiB
Go
package metrics
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// /metrics 文本端点:注入计数/观测后可被抓取,指标名与 label 齐全。
|
|
func TestHandlerExposesInstruments(t *testing.T) {
|
|
RequestsTotal.WithLabelValues("c1", "m1", "success").Inc()
|
|
RequestsTotal.WithLabelValues("none", "m1", "error").Inc()
|
|
RequestDuration.WithLabelValues("c1").Observe(0.42)
|
|
ChannelFailures.WithLabelValues("c1", "rate_limited").Inc()
|
|
RecorderQueueDepth.Set(7)
|
|
FrozenBalance.Set(0.00246)
|
|
|
|
srv := httptest.NewServer(Handler())
|
|
defer srv.Close()
|
|
resp, err := http.Get(srv.URL)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Fatalf("status = %d", resp.StatusCode)
|
|
}
|
|
buf := make([]byte, 1<<16)
|
|
n, _ := resp.Body.Read(buf)
|
|
body := string(buf[:n])
|
|
for _, want := range []string{
|
|
"openteam_proxy_requests_total{",
|
|
`channel="c1"`,
|
|
`status="success"`,
|
|
"openteam_proxy_request_duration_seconds",
|
|
"openteam_channel_failures_total",
|
|
"openteam_usage_queue_depth 7",
|
|
"openteam_frozen_balance 0.00246",
|
|
"go_goroutines", // Go 运行时指标一并暴露
|
|
} {
|
|
if !strings.Contains(body, want) {
|
|
t.Errorf("metrics 输出缺少 %q", want)
|
|
}
|
|
}
|
|
// 直方图观测应产生 bucket 样本
|
|
if !strings.Contains(body, `openteam_proxy_request_duration_seconds_bucket`) && n < len(buf) {
|
|
// 一次 Read 可能读不完,重读剩余部分再断言
|
|
rest := make([]byte, 1<<16)
|
|
n2, _ := resp.Body.Read(rest)
|
|
body += string(rest[:n2])
|
|
if !strings.Contains(body, "openteam_proxy_request_duration_seconds_bucket") {
|
|
t.Errorf("metrics 输出缺少直方图 bucket 样本")
|
|
}
|
|
}
|
|
_ = time.Second
|
|
}
|