Skip to content

Latest commit

 

History

History
436 lines (301 loc) · 24.4 KB

File metadata and controls

436 lines (301 loc) · 24.4 KB

指标与观测参考

本文档描述 Pineapple 0.5.0 起稳定支持的可插拔观测模型:内建 /stats 原子统计始终可用,外部监控系统通过各运行时对等的 Provider 抽象接入。pine-go 是规范参考实现,其它运行时按字节级 parity 对齐。

权威文件

pine-go(规范参考实现)

  • pine-go/pkg/metrics/metrics.go
  • pine-go/pkg/metrics/nop.go
  • pine-go/pine.go
  • pine-go/internal/runtime/engine_metrics.go
  • pine-go/internal/runtime/scheduler.go
  • pine-go/internal/runtime/stats.go
  • pine-go/internal/types/operator.go
  • pine-go/operators/lua/lua.go
  • pine-go/operators/lua/pool.go
  • pine-go/pkg/server/server.go
  • pine-go/pkg/server/http_metrics.go
  • pine-go/pkg/metrics/collector.go — 资源级指标聚合 Collector/stats.resources 数据源)
  • pine-go/pkg/metrics/tee.go — fan-out Tee(Provider...),把同一指标写入多个下游 Provider

pine-cpp(C++ 对等实现)

  • pine-cpp/include/pine/metrics.hppProvider / Counter / Gauge / Histogram 接口
  • pine-cpp/src/runtime/metrics_nop.cppnop_provider() 单例
  • pine-cpp/include/pine/metrics_collector.hppmetrics::Collector + metrics::TeeProvider
  • pine-cpp/src/runtime/metrics_collector.cpp — Collector 聚合快照 + Tee fan-out 实现
  • pine-cpp/src/server/server.hppMiddlewareContext / Middleware / ServerConfig::middlewares
  • pine-cpp/src/server/http_metrics.cpphttp_metrics_middleware(provider) 工厂
  • pine-cpp/include/pine/pine.hppEngineOptions::metrics_providerEngine::peak_concurrency()

pine-java

  • pine-java: 参考 Registry / metrics/Provider.java 模块。指标名、Help 文案与 histogram 桶边界由 scripts/check-metrics-help-parity.py 守着(接 make lint,纯文本扫描全部三方声明点,桶按 metric 名比对)。 注意 scripts/cross-validate/13-metrics-parity.sh 覆盖这些:它读 /stats,由 runtime.Stats 供给, 与 metrics.Provider 是分离的两套机制,断言的是「算子从启动即可见且计数为零」这类 Stats 行为(issue #193)
  • metrics/MetricsCollector.java — 资源级指标聚合 Collector
  • metrics/TeeProvider.java — fan-out Provider

设计目标

Pineapple 把生产可观测性拆成两条稳定通道:

  1. 进程内原子统计

    • 零配置启用
    • /stats 提供数据
    • 不依赖外部监控后端
  2. 外部 Provider metrics

    • 由调用方按需注入
    • 面向 Prometheus 等采集系统
    • 不让 Pineapple core 直接依赖具体监控 SDK

该拆分允许 Pineapple 默认保持轻依赖,同时让接入方在自己的项目中实现约 80 行级别的 Prometheus adapter。

pine-go/pkg/metrics 抽象面

pine-go/pkg/metrics/metrics.go 定义的稳定接口只有三类 metric 和一个 provider:

  • Counter
  • Gauge
  • Histogram
  • Provider

共同约束:

  • metric handle 支持 With(labelValues ...string) 派生带标签实例
  • MetricOpts / HistogramOpts 描述名称、帮助文案、标签名和 histogram buckets
  • DurationSeconds(time.Duration) 把 Go duration 转成秒,供时长 histogram 使用

Pineapple 只依赖这些接口,不导入 prometheus/client_golang

默认 provider

metrics.Nop() 返回默认 no-op provider:

  • 丢弃全部观测
  • 不分配真实后端对象
  • 作为 pine.NewEngineserver.Config.Metrics 的默认值

因此未启用外部 metrics 时,Pineapple 仍保留 /stats 诊断能力,但不会把 Prometheus 等依赖强加给所有使用者。

引擎侧接入点

pine.NewEngine(jsonConfig, opts...)

引擎构建支持 option 注入,当前稳定项为:

  • pine.WithMetrics(provider)
  • pine.WithLogPrefix(prefix)

行为约束:

  • 未提供 provider 时自动回退到 metrics.Nop()
  • Engine 在构建期预创建 EngineMetrics
  • 引擎在构建期(NewEngine/Engine.create()/Engine() 初始化末尾)对所有已编译算子名调用 PreInitOperators,确保 Prometheus 等 metrics backend 从启动即暴露零值时间序列;同时 Stats 也预注册算子名,使 /stats 在首次请求前即可返回完整算子列表(各计数器初始为 0)
  • 同一 provider 会被传给所有实现 MetricsAware 的算子实例
  • 日志前缀可来自 JSON 根级 log_prefixpine.WithLogPrefix(...)
  • 当两者同时存在时,pine.WithLogPrefix(...) 优先
  • 前缀作用于引擎实例私有的 *log.LoggerLdate|Ltime|Lshortfile flags,输出含 file:line),经 LoggerAware 注入到算子与 scheduler 诊断路径;进程全局 log 包不受影响(issue #172)

可选接口注入顺序

对单个算子实例,引擎在构建期按以下固定顺序调用可选接口:

  1. MetadataAware
  2. DebugAware
  3. LoggerAware
  4. MetricsAware

(Go 实际路径;Java 在 Metrics 之后还有 ResourceAware,C++ 的 Metadata/Debug 经 init(op_cfg) 携带、随后同样是 Logger → Metrics → Resource。完整对照见 llmdoc/architecture/dag-engine.md 不变量 11。)该顺序是承载性的。像 pine-go/operators/lua/lua.go 这样的实现依赖 DebugHolder.OperatorName() 已先被注入,以便把 operator 实例名作为 metric label 使用。

双通道观测模型

通道一:内建原子统计

pine-go/internal/runtime/stats.go 始终维护:

  • 每算子执行次数、跳过次数、错误次数
  • 每算子总耗时、最大耗时、平均耗时
  • 调度器 run_count
  • 调度器 peak_concurrency

这些统计通过以下 API 暴露:

  • Engine.Stats()
  • Engine.SchedulerStats()
  • Engine.OperatorCustomStats()(聚合实现 StatsProvider 的算子)

通道二:外部 Provider metrics

pine-go/internal/runtime/engine_metrics.go 在引擎创建时预声明并缓存 metric handle。当前稳定指标名为:

算子级指标:

  • pine_scheduler_runs_total
  • pine_operator_active
  • pine_operator_exec_total{operator=...}
  • pine_operator_exec_duration_seconds{operator=...}
  • pine_operator_skip_total{operator=...}
  • pine_operator_error_total{operator=...}

DAG 级指标(0.6.6 起):

  • pine_dag_executions_total{status=success|error} — DAG 执行总次数,按成功/失败分标签
  • pine_dag_execution_duration_seconds — 单次 DAG 执行端到端耗时
  • pine_dag_operators_executed — 每次 DAG 执行中实际运行(非跳过、非取消)的算子数(histogram,桶边界以 pine-go/internal/runtime/engine_metrics.go 为准,并由 scripts/check-metrics-help-parity.py 跨运行时守护)

DAG 级指标在 scheduler.Run() 结束时统一记录:计时覆盖从调度开始到所有算子完成的完整区间;status 标签由是否有 fatal error 决定;operators_executed 只计入 !Skipped 的 trace 条目。

scheduler.go 在热路径中同时更新两类观测:

  • 调度开始时记录 scheduler run
  • 算子开始/结束时维护 active gauge 与 peak concurrency
  • 算子成功、跳过、失败时分别记录对应计数
  • 成功和失败都记录执行时长 histogram
  • DAG 执行结束时记录 DAG 级执行次数、耗时和实际执行算子数

这里的关键不是二选一,而是同一执行事实被写入两种消费面:

  • /stats 面向人类诊断与零配置自检
  • provider metrics 面向 Prometheus/Grafana 等系统

两个通道各自的能力边界(issue #193)

这两条通道不是互为备份,各有对方没有的东西,写清是为了避免读者以为其中一条冗余:

通道一 runtime.Stats 通道二 metrics Provider
服务对象 内建 /stats 外部后端
默认是否工作 是,开箱即用 否,默认 nop
跨运行时校验 有(13-metrics-parity.sh 有(check-metrics-help-parity.py,覆盖指标名、Help 与桶边界)
提供的信息 总耗时、最大耗时、平均耗时 count + sum(出厂);分位数(下游注入后)

两条硬边界,不写下来就会被误解:

  1. 出厂部署下通道二不留任何观测。 三方捆绑服务器都是「注入的 provider,否则 nop」 (pine-go/pkg/server/server.gopine-java/.../PineServer.javapine-cpp/src/server/server.cpp), 而 Collector / MetricsCollector 只交给 ResourceManager,engine metrics 被刻意排除在 /stats.resources 之外。所以不注入自定义 Provider 时,pine_operator_exec_duration_seconds 连一次观测都没有。
  2. 出厂 collector 只能给 count + sum,拿不到分位数。 三方 cell 结构一致(Go histCell、 Java HistCell、C++ HistCell 都只有 count 与 sum_ns),只够算平均值,没有直方图结构。 要 P50/P99 必须自己实现 Provider 并接真正的直方图后端HistogramOpts.buckets 是给那个 实现的建议值,出厂实现不消费它。

实现 Provider 前必须读的契约(并发、单位、桶的性质、label 生命周期)在 pine-go/pkg/metrics/metrics.go 的 package doc,那是唯一权威副本,pine-java / pine-cpp 的接口 注释与 design_doc/08_observability.md 都只留指针、不复述。

服务端观测

pine-go/pkg/server/server.go 支持通过 server.Config.Metrics 注入 provider,也支持通过 server.Config.Middlewares 在 HTTP 边界层包装整个 handler 链。

该 provider 会同时用于:

  • 初始 pine.NewEngine(configData, pine.WithMetrics(provider))
  • 后续 reloadConfig(...) 重建引擎时的 pine.WithMetrics(provider)
  • server 自身的 config reload 指标
  • 内置 HTTP 请求指标中间件

内置 HTTP 请求指标中间件(0.6.6 起)

pine-go/pkg/server/http_metrics.go 提供内置的 httpMetricsMiddleware,作为最内层中间件自动应用于所有 HTTP 路由,测量 handler 处理耗时(不含用户 middleware 开销)。

指标名:

  • pine_http_requests_total{method, path, status} — HTTP 请求总数
  • pine_http_request_duration_seconds{method, path} — HTTP 请求处理耗时

行为约定:

  • path 标签只输出已知路径(内置 /execute/health/stats/dag,加上 server.Config.Routes 注册的自定义路由路径),未知路径归一化为 _other,防止高基数标签爆炸。known-path 集合在启动时由 validateRoutes 一次性构建,运行期不增长
  • status 标签按 HTTP 状态码桶化为 2xx3xx4xx5xxother
  • 该中间件在用户 Middlewares 之内、http.ServeMux 路由之外包装,因此用户 middleware 的处理时间不会被计入 request_duration
  • server.Config.Metrics 为 nil 时,使用 metrics.Nop() 作为 provider,此时中间件仍执行但观测被丢弃,开销可忽略

C++ 端的等价中间件

C++ Server::run() 现在与 Go/Java 一致,无条件注入 http_metrics_middlewareNopProvider 兜底当 metrics_provider 为 nullptr),用户无需显式 push_back。middleware 同时写入外部 Provider 与内置 HttpStats 原子累加器(数据流入 /stats.http 子树)。MiddlewareContext::status 通过 send_response 内的 thread_local 指针写回。Provider* 由调用方持有,必须保证生命周期覆盖 server 运行期。

Middlewares 与 metrics 注入是正交能力:middleware 包装发生在内部路由注册完成之后、ListenAndServe 启动之前,对 /health/execute/stats/dag 一并生效,但不改变 /stats 数据来源、reload 计数逻辑或引擎内的 metrics provider 传递。

当前 server 级外部指标名:

  • pine_config_reload_total
  • pine_config_reload_errors_total
  • pine_config_reload_duration_seconds

同时,server 还维护原子 reload 统计:

  • reloadCount
  • reloadErrorCount
  • lastReloadDurationNs

/stats 响应契约

GET /stats 返回组合响应,字段稳定分为:

  • operators — 来自 Engine.Stats() 的每算子累计统计
  • scheduler — 来自 Engine.SchedulerStats() 的调度器统计
  • server — 配置热加载相关统计
  • operator_detail — 仅当至少一个算子实现 StatsProvider 时出现
  • http(0.6.7 起,三运行时一致):
    • requests_total: map<"<METHOD> <path> <status_bucket>", int64>
    • request_duration_seconds: map<"<METHOD> <path>", {count: int64, sum_ns: int64}>
    • key 字典序输出;duration bucket 字段顺序 count -> sum_ns
    • 由内置 http_metrics middleware 实时填充,与外部 Provider Counter/Histogram 平行,构成"双通道观测模型"
  • resources(三运行时一致):资源级指标的 metric-centric 快照 {指标名: {标签值组合: 值}}
    • counter/gauge 为标量,histogram 为 {count, sum_ns}(整数纳秒)
    • 每层键字典序排序,保证三运行时字节级一致
    • 该键恒存在(Collector 随 ResourceManager 一起无条件创建);无资源指标时为 {}
    • 数据源是 fan-out 路由中的专用 Collector,详见下文"资源级指标 fan-out 路由"

这意味着:

  • /stats 不要求 provider
  • /stats 与 Prometheus export 不是同一个接口层
  • 自定义算子若只想支持 /stats,实现 StatsProvider 即可
  • 自定义算子若还想接入外部指标,再额外实现 MetricsAware

算子扩展点

StatsProvider

定义于 pine-go/internal/types/operator.gopine-cpp/include/pine/operator.hpp,用于把算子内部累计统计暴露给 /stats

  • 方法:OperatorStats() map[string]int64(C++ 返回 std::map<std::string, int64_t>
  • 数据应是稳定、便于诊断的累计数值
  • 推荐用于 pool、cache、worker、重试等内部子系统状态

MetricsAware

定义于 pine-go/internal/types/operator.gopine-cpp/include/pine/operator.hpp,用于接收外部 provider:

  • 方法:SetMetricsProvider(p metrics.Provider)(C++ 为 set_metrics_provider(metrics::Provider*)
  • 适合在算子内部预创建或缓存自己的 metric handle
  • 实现必须接受 no-op provider 为正常情况

Lua 算子作为参考实现

Lua runtime 现在同时实现两种扩展接口:

  • StatsProvider
  • MetricsAware

/stats

pine-go/operators/lua/pool.go (与 pine-cpp/src/lua/lua_pool.cpp)通过原子计数维护:

  • borrow_count — 累计借用次数
  • return_count — 累计归还次数
  • create_count — 实际新建 state 的次数(pool miss)
  • reuse_count — 命中池内闲置 state 的次数(pool hit,0.9.7 起新增三运行时一致)
  • active_count — 当前借出未归还的 state 数

reuse_count + create_count == borrow_count 恒成立(borrow 要么命中已有 state 计入 reuse,要么走 newState 计入 create)。运维可由此直接计算命中率 reuse_count / borrow_count,无需额外采样。

pine-go/operators/lua/lua.goOperatorStats()(C++ 为 TransformByLuaOp::operator_stats())将这些计数暴露到 /stats.operator_detail[operatorName]

外部 metrics 侧

Lua pool 还会创建以下 provider metrics,并绑定 operator label:

  • pine_lua_pool_borrow_total
  • pine_lua_pool_return_total
  • pine_lua_pool_create_total
  • pine_lua_pool_active

label 值取自 DebugHolder.OperatorName(),所以 Lua 算子的 metrics 注入依赖固定顺序:MetadataAware → DebugAware →(LoggerAware →)MetricsAware,Debug 信息先于 Metrics 就位。

资源级指标 fan-out 路由

内置 redis_connection 资源在 metrics_name 非空时发出资源级指标(标签名 name 取该参数值):

连接池/探针指标(资源生命周期):

指标 类型 说明
pine_redis_pool_total_conns Gauge(name) 连接池总连接数(空闲 + 使用中)
pine_redis_pool_idle_conns Gauge(name) 连接池空闲连接数
pine_redis_ping_duration_seconds Histogram(name) 后台 PING 探针往返耗时
pine_redis_up Gauge(name) 最近一次 PING 成功为 1,失败为 0

Per-command 指标(每次业务命令执行,0.10.10 起):

指标 类型 说明
pine_redis_command_duration_seconds Histogram(name, command, status) 单次命令执行耗时(秒)
pine_redis_command_total Counter(name, command, status) 单次命令执行计数

metrics_name 为空(默认)时不发出任何指标,也不启动探针线程。探针在资源 Start() 时立即跑一次(probe-once-then-tick),随后固定 15s 一跳,三运行时一致——因此首个请求前指标即已就绪。

per-command 指标 status taxonomy(三运行时一致)

status 标签四态枚举,按"业务可用性 + 失败原因可区分性"切分:

status 含义
ok 命令成功(含 redis.Nil cache miss——业务路径上是合法成功)
timeout 命令在 read_timeout_ms / write_timeout_ms 超时(pine-go 走 context.DeadlineExceeded / go-redis timeout error)
pool_timeout pool 获取连接 pool_timeout_ms 内无空闲(pine-go 走 go-redis pool wait error)
error 其它错误(连接被拒、协议错误、未分类异常)

pine-cpp 已知缺口:当前 cpp Redis client 抛出单一 std::runtime_error 类型,没有 timeout / pool-timeout / generic 的类型分层;run_command<T>(...) 模板内只能落 error 桶,timeout 与 pool-timeout 计数被合并到 error。这是 known follow-up——cpp client 需要引入错误类型分层(拆 TimeoutError / PoolTimeoutError / generic)才能修齐 status taxonomy。

lifecycle 命令过滤(三运行时插桩位置非对称的等价契约)

per-command 指标只覆盖业务命令(GET / SET / DEL / EXPIRE / EXISTS / HGET / ...),不覆盖 lifecycle / probe 命令(HELLO / CLIENT / PING / AUTH / SELECT)。三运行时的实现路径不同但终态对齐:

  • pine-go:通过 redis.Hook 在客户端层自动包裹所有命令——hook 内对 HELLO / CLIENT / PING / AUTH / SELECT 做 name-based 过滤后才计数。这个名单是健康/握手命令,与业务命令分摊到同一指标会污染 status 分布
  • pine-java:通过 RedisConnectionResource.runCommand(name, action) facade 包裹业务命令;client 内部的 lifecycle 命令不经过 facade,自然不计入指标
  • pine-cpp:通过 Client::run_command<T>(name, fn) 模板包裹业务命令;client 内部 AUTH / SELECT / PING 同样不经过模板

cross-validate scripts/cross-validate/16-resource-metrics.sh 强断三运行时业务命令名集合一致;/stats.resources 子树中 pine_redis_command_* cells 因 facade 插桩位置(client 内 vs client 外)不同需在比较前剥离 cmd_* 标签——形状对齐由 section 16 维护,命名集合是契约的一部分。

fan-out(Tee)路由

资源级指标走 fan-out(Tee) 路由,与引擎指标解耦。bundled server 给 ResourceManager 注入的不是裸 Provider,而是 Tee(注入的Provider, 专用Collector)

  • 每条资源指标同时写入调用方注入的 Provider(如 Prometheus 适配器)和专用 Collector
  • 引擎指标仍直接走注入的 Provider,进入 Collector,因此 /stats.resources 子树天然只含资源级指标(当前 redis 连接池/探针 + per-command 两组),不掺入引擎/服务级指标——scope 隔离靠"谁通过 Tee 写入"实现,而非在 Collector 内按名字过滤;
  • /stats.resources 读取 Collector 的聚合快照,无需外部 Prometheus 后端即可观测;
  • 下游无需任何改动即可从 /stats.resources 读到新指标;已接 Prometheus 的下游照常经注入 Provider 导出,两条路径并存。

Collector 与 Tee 随 ResourceManager 在热重载时一同原子替换,且生命周期长于它(资源持有指向 Tee 的裸指针)。

Collector 契约

  • 聚合快照形状与 /stats.http 同构:{指标名: {标签值组合: 值}},counter/gauge 为标量、histogram 为 {count, sum_ns}(整数纳秒)。
  • histogram 纳秒取整:Go int64(math.Round(value*1e9))、Java Math.round(value*1e9)、C++ static_cast<int64_t>(std::llround(value*1e9))
  • 每层键字典序排序,保证三运行时字节级一致。
  • 即使指标被声明但从未 observe,也以空对象出现({"pine_redis_up": {}}),便于跨运行时形状断言。

三运行时实现(Go pkg/metrics Collector+Tee、Java MetricsCollector+TeeProvider、C++ metrics::Collector+metrics::TeeProvider)行为字节级对齐,由 cross-validate resource-metrics section(scripts/cross-validate/16-resource-metrics.sh)锁定。

Prometheus 适配边界

Pineapple 仓库内不提供 Prometheus 具体实现。推荐模式是:

  • 在业务项目里实现一个 pkg/metrics.Provider 适配器
  • 在适配器内部使用 prometheus/client_golang 注册 CounterVecGaugeVecHistogramVec
  • 应用启动时把适配器传给 pine.WithMetrics(...)server.Config.Metrics

这样可保持 Pineapple core 的后端无关性,也避免把注册表、命名冲突和 exporter 路由绑定到核心库中。

跨引擎 metrics parity 验证

cross-validate metrics-parity section(scripts/cross-validate/13-metrics-parity.sh)验证各运行时的 pre-init 行为和 /stats 数值一致性,覆盖:

  • zero-traffic pre-init:引擎启动后、无请求时 /stats 已暴露全部算子
  • operator names match:各运行时的算子名集合一致
  • exec_count match:执行计数一致
  • skip_count match:跳过计数一致
  • error_count match:错误计数一致
  • scheduler.run_count match:调度器运行计数一致
  • http.requests_total 中 POST /execute 2xx 计数三方一致
  • http.request_duration_seconds 中 POST /execute 的 count 字段三方一致(sum_ns 因 host load 差异不强制)
  • /stats.http 子树 schema shape:三方都有 requests_total + request_duration_seconds 两 key,每个 duration bucket 含 count + sum_ns 字段

C++ 端是否参与某次比对取决于该 section 中对 CPP_SERVER 的引用以及 scripts/cross-validate/_prebuild.sh 是否成功构建 cpp 二进制。

资源级指标 parity(section 16)

cross-validate resource-metrics section(scripts/cross-validate/16-resource-metrics.sh)锁定 /stats.resources 子树的形状与正确性,需要真实 redis-server(缺失时整段干净跳过):

  • 正例(metrics_name="cache"):
    • Go 正确性——连接池/探针指标齐全、pine_redis_up.cache == 1、ping count >= 1、histogram cell 含 sum_ns、per-command 业务命令名集合(GET / SET / DEL / EXPIRE / EXISTS / HGET / ...,参见脚本内业务命令名清单)
    • Go vs Java:指标名 + 标签 key 集合一致、pine_redis_up 数值一致、ping count 均 ≥1;pine_redis_command_* 命令名集合一致(cmd_* cells 因 facade 插桩位置不同在比较前剥离)
    • Go vs C++:同上形状 + 正确性 parity
  • 负例(无 metrics_name):三运行时 resources == {}(恒存在键的干净负断言)

版本边界

本文档描述的可插拔 metrics / observability 模型自 Pineapple 0.5.0 起稳定存在。0.6.6 新增内置 HTTP 请求指标中间件和 DAG 级执行指标。涉及的公开入口包括:

  • pine.WithMetrics
  • pine.WithLogPrefix
  • server.Config.Metrics
  • /statsscheduleroperator_detail 组合语义

检索指针

  • 接口定义:pine-go/pkg/metrics/metrics.gopine-cpp/include/pine/metrics.hpp
  • no-op 默认实现:pine-go/pkg/metrics/nop.gopine-cpp/src/runtime/metrics_nop.cpp
  • 引擎入口与 option:pine-go/pine.gopine-cpp/include/pine/pine.hppEngineOptions::metrics_provider / log_prefix
  • 引擎级 metric 预创建:pine-go/internal/runtime/engine_metrics.go
  • 调度热路径记录:pine-go/internal/runtime/scheduler.go
  • 原子统计:pine-go/internal/runtime/stats.go;C++ peak concurrency 与 operator_custom_stats:pine-cpp/src/runtime/engine.cpp 的 atomic CAS 路径 + Engine::peak_concurrency() / Engine::operator_custom_stats()
  • 算子扩展接口:pine-go/internal/types/operator.gopine-cpp/include/pine/operator.hpp
  • Lua 参考实现:pine-go/operators/lua/lua.gopine-go/operators/lua/pool.gopine-cpp/src/lua/lua_pool.hpp
  • HTTP /stats 与 reload 指标:pine-go/pkg/server/server.go
  • 内置 HTTP 请求指标中间件:pine-go/pkg/server/http_metrics.gopine-cpp/src/server/http_metrics.cpp
  • C++ middleware 注入:pine-cpp/src/server/server.hppMiddleware / MiddlewareContext / ServerConfig::middlewares