profiling_stats 模块

目录

概述

mc::engine::profiling_stats 模块为 OpenUBMC 引擎提供轻量级性能追踪与调用统计能力,只记录跨组件的 Signal 与 RPC。同进程内部的自调用、仅本服务订阅的信号不计入。每个 session 只累加本组件作为发送方或接收方的次数,同进程其它组件的流量不会写入该 session。

  • Profiling 计数器(按 app_name / well-known 名绑定):sent_* 记在发起方 session,received_* 记在接收方 session。
  • RPC 明细统计(进程级单例):按服务名、对象路径、接口名、方法名聚合跨组件出站调用次数。

模块支持按需启动/停止单个 session,并可设置自动采集时长;停止时将对应 session 的计数写入 CSV。

主要特性

  • 按需激活:通过 start_profiling / stop_profiling 控制采集生命周期,避免常态性能损耗
  • 定时自动停止:支持指定采集分钟数,到期自动停止并落盘,适合短期巡检
  • 组件隔离:profiling_inc_* 只递增与该 service 匹配的 session,同进程多组件互不干扰
  • 跨组件 RPC:出站在 send* 用 target != this 计 sent;入站在 dispatch / dispatch_method 用 caller well-known 计 received。同服务短路不计。
  • 跨组件 Signal:无 backend 时 emit 看引擎 match table 是否有其它服务或远端 endpoint;service_backend::emit 看 shm 订阅树(其它 well-known 或非本进程 unique)。send() 点对点(destination 为其它服务)计 sent;广播(destination 空)与无 backend 的 emit 相同,查 match table。无 proto 或仅本服务自订阅不计。dispatch_event / shm 回调仅当发射方不是本服务时计 received。未 start_profiling 时上述判定与 stamp 均跳过。
  • Unique Name 解析:RPC 明细将 D-Bus unique name 解析为 well-known name
  • CSV 导出:停止时写入 /dev/shm/profiling.csv,按 app_name 合并更新
  • 线程安全:session 创建/停止/计数由 get_mutex() 保护;计数器为 std::atomic

基本用法

示例 1:基本采集流程

#include <mc/engine/profiling_stats.h>

using namespace mc::engine;

void run_profiling_example()
{
    // 启动 profiling,采集 5 分钟,父对象为当前服务对象(继承 executor)
    start_profiling("MyService", 5, this);

    // 计数由引擎路径按 service 归属自动完成,不要对所有 session 广播累加
    // profiling_inc_sent_signal(*this);

    // 定时器到期后自动调用 stop_profiling("MyService"),写入 /dev/shm/profiling.csv
    // 也可手动停止:stop_profiling("MyService");
}

示例 2:手动控制采集时长

#include <mc/engine/profiling_stats.h>

using namespace mc::engine;

void manual_profiling_example()
{
    // 启动,不自动停止
    start_profiling("MyService", 0);

    // ... 长时间运行 ...

    // 在适当时候手动停止并导出 CSV
    stop_profiling("MyService");
}

示例 3:导出 RPC 统计到日志

#include <mc/engine/profiling_stats.h>

using namespace mc::engine;

void dump_stats_example()
{
    // 获取当前 RPC 统计字符串(可用于日志),dump 后明细会被清空
    auto stats = dump_rpc_stats("MyService");
    ilog("RPC Stats:\n${stats}", ("stats", stats));
}

示例 4:Unique Name 解析

#include <mc/engine/profiling_stats.h>

using namespace mc::engine;

void unique_name_example()
{
    // 假设目标服务使用 unique name ":1.234"
    record_outgoing_rpc(":1.234", "/obj", "iface", "Method");
    // 内部自动解析为 well-known name(如果存在),否则保留 ":1.234"
}

API 详细说明

start_profiling

MC_API void start_profiling(mc::string_view app_name, uint32_t duration_min, mc::object* parent = nullptr);
参数 说明
app_name 应用/组件名称,作为 CSV 中的记录标识写入 /dev/shm/profiling.csv,同时出现于日志输出
duration_min 采集时长(分钟)。为 0 时不自动停止,需手动调 stop_profiling;大于 0 时创建 single_shot 定时器,到期自动停止
parent 定时器的父对象(通常为当前服务对象),用于继承 executor 上下文。为 nullptr 时定时器回调可能无法触发(取决于当前线程是否有 executor)

行为:

  1. 若当前 app_name 已有活跃 session,先停旧 session(不写 CSV)。
  2. 创建新 session,重置 profiling 计数器(原子赋零)。
  3. 设置 session 为活跃状态。
  4. 如果 duration_min > 0,创建 single_shot 定时器,到期后调用 stop_profiling(app_name)。

注意:

  • start_profiling 仅重置 profiling 计数器,不会清空 RPC 统计(rpc_stats_data);两者相互独立。
  • 若 profiling 已激活,重复调用同一 app_name 会重新初始化该 session(计数器归零、定时器重置)。
  • 定时器依赖 mc::object 的事件循环机制,若 parent 无效且当前线程无 executor,定时器不会触发。

stop_profiling

MC_API void stop_profiling(mc::string_view app_name);

行为:

  1. 清除对应 session 的活跃标志(后续计数函数不再为它递增)。
  2. 将 session 的 profiling 计数器写入日志(nlog)。
  3. 读取 /dev/shm/profiling.csv,以 app_name 为 key 合并更新后写回。
  4. 若指定 app_name 无活跃 session,此函数无任何效果。

说明: CSV 文件内容为 profiling 计数器(sent/received signals、跨组件 sent/received rpcs),不含 RPC 调用排行明细(后者由 dump_rpc_stats 独立导出)。

is_cross_component_rpc

MC_API bool is_cross_component_rpc(const service& self, mc::string_view peer);
方法 参数 返回值 异常 线程安全 说明
is_cross_component_rpc self:当前服务;peer:目标名(well-known 或 unique) true 跨组件;false 本服务或 peer 为空 不抛异常 是 主路径请用 service*;本函数是 find_service(peer) != &self 的测试/兜底

行为:

  1. peer 为空 → false。
  2. find_service(peer) 非空 → 返回该指针是否不等于 &self(与真正 dispatch 的目标一致)。
  3. 未注册 → peer != self.name()。

说明: 出站主路径用 service* 比较。入站用 k_profiling_caller_service well-known stamp,避免同进程共享 unique name 误判。

is_cross_component_inbound_rpc

MC_API bool is_cross_component_inbound_rpc(const service& self, const mc::dict& context, mc::string_view sender);

context 含 mc.profiling.caller_service 时与 self.name() 比较;无 stamp 的总线入站视为跨组件。

is_cross_component_inbound_signal

MC_API bool is_cross_component_inbound_signal(const service& self, const mc::dict& context);

context 含 mc.profiling.emitter_service 时与 self.name() 比较;无 stamp 视为跨组件。

is_profiling_self_emit

MC_API bool is_profiling_self_emit(const service& svc);
方法 参数 返回值 异常 线程安全 说明
is_profiling_self_emit svc:当前服务 true 本线程正由该 svc 同步 emit 不抛异常 线程局部 供 shm 回调跳过自发自收;与 scoped_profiling_emit 成对使用

行为: 比较线程局部发射方指针是否等于 &svc。无活跃 emit 栈或发射方不是 svc 时返回 false。

scoped_profiling_emit

class MC_API scoped_profiling_emit {
public:
    explicit scoped_profiling_emit(const service& svc);
    ~scoped_profiling_emit();
};
方法 参数 返回值 异常 线程安全 说明
构造 svc:正在 emit 的服务 无 不抛异常 改本线程 TLS 未 start_profiling 时不改 TLS
析构 无 无 不抛异常 改本线程 TLS 恢复构造前的发射方指针

使用场景: 仅 service::emit 内部使用。service_backend 的 shm 回调用 is_profiling_self_emit 识别同一次同步 emit 的回环。

record_outgoing_rpc

MC_API void record_outgoing_rpc(mc::string_view service_name, mc::string_view object_path,
                                mc::string_view interface_name, mc::string_view method_name);
参数 说明
service_name 目标服务名(well-known 或 unique name)
object_path 目标对象路径
interface_name 接口名
method_name 方法名

行为:

  1. 若 service_name 以 : 开头(D-Bus unique name),调用 mdb_query 尝试解析为 well-known name;解析失败则保留原值。
  2. 以四个字段构建 rpc_record_key,在 outgoing_rpc_counts 中递增计数(持有互斥锁)。

说明: RPC 统计独立于 profiling session,始终记录调用频次,不受 start_profiling/stop_profiling 状态影响。

dump_rpc_stats

MC_API std::string dump_rpc_stats(mc::string_view current_service_name);
参数 说明
current_service_name 当前服务名,用于输出末尾的分隔标题(便于多服务日志拼接)

返回值: 多行字符串。首行为 ----------RPC Performance Statistics----------,随后每条记录独立一行(格式 service_name object_path interface_name method_name count),记录间以空行分隔。末尾为 ----------{current_service_name} RPC Count Performance Statistics----------。

说明:

  • 该函数在持有 rpc_stats_data::mutex 时遍历 map,因此调用期间会阻塞 record_outgoing_rpc。
  • 输出内容不包含 profiling 计数器(profiling 计数器在 CSV 中体现)。
  • 导出后 RPC 明细 map 会被清空,避免长期运行内存无限增长。

clear_rpc_stats

MC_API void clear_rpc_stats();

行为: 清空 outgoing_rpc_counts。

get_profiling_data

MC_API profiling_data& get_profiling_data(mc::string_view app_name = {});

返回值: 按 app_name(或 well-known / 短名)匹配的活跃 session 计数器;app_name 为空时返回首个活跃 session;若无匹配则返回静态 fallback。

说明: 持锁按名查找 session。计数函数按 service 匹配 session,不会给同进程其它组件加一。

get_rpc_stats_data

MC_API rpc_stats_data& get_rpc_stats_data();

返回值: 全局唯一的 rpc_stats_data 实例。

is_profiling_active

MC_API bool is_profiling_active();

返回值:

  • true:存在至少一个活跃 session(即曾调用 start_profiling 且尚未 stop_profiling)
  • false:无活跃 session

说明: 便捷计数函数内部先以 lock-free 原子变量快速判断,无活跃 session 时直接返回,避免不必要的锁开销。

便捷计数函数

头文件中声明了四个便捷计数函数,实现在 profiling_stats.cpp 中。只递增与 svc 绑定的 session。

MC_API void profiling_inc_sent_signal(const service& svc);
MC_API void profiling_inc_received_signal(const service& svc);
MC_API void profiling_inc_sent_rpc(const service& svc);
MC_API void profiling_inc_received_rpc(const service& svc);

实现: 无活跃 session 时立即返回;否则持锁查找 app_name / service_name 与 svc.name()(含短名)匹配的 session 并 fetch_add。

推荐: 只在引擎发送/接收路径上调用,业务代码不要手动广播计数。

CSV 输出格式

stop_profiling 将 session 数据写入 /dev/shm/profiling.csv,以 app_name 为 key 进行合并(读取→覆盖对应行→写回)。

app_name,start_time,duration_min,sent_signals,recv_signals,sent_rpcs,recv_rpcs
MyService,2026-07-27 10:25:00,5,1024,512,2048,1024
AnotherService,2026-07-27 10:26:00,1,256,128,512,256

字段说明:

列 含义
app_name 组件名称,来自 start_profiling 调用时的参数
start_time profiling 启动时间,格式 YYYY-MM-DD HH:mm:ss
duration_min 采集时长(分钟)
sent_signals 发出的 signal 计数
recv_signals 收到的 signal 计数
sent_rpcs 发出的 RPC 调用次数
recv_rpcs 收到的 RPC 调用次数

注意: CSV 仅包含 profiling 计数器汇总,RPC 调用排行明细由 dump_rpc_stats 独立输出到日志。

最佳实践

✅ 正例 1:传入 parent 确保定时器回调

// 在 service 对象中调用,传入 this
void my_service::start_profiling_wrapper(uint32_t duration)
{
    // ✅ 正确:传入 this,timer 继承 service 的 IO executor
    mc::engine::start_profiling("my_service", duration, this);
}

✅ 正例 2:发送前记录 RPC 明细

mc::future<message> my_service::call_remote(mc::string_view target, ...)
{
    // ✅ 正确:在实际发送前记录,准确反映调用频次
    mc::engine::record_outgoing_rpc(target, path, iface, method);
    return send_rpc(...);
}

❌ 反例 1:不传 parent 导致定时器失效

void bad_start_profiling()
{
    // ❌ 错误:parent 为 nullptr 且当前线程无 executor,
    // 定时器回调不会触发,profiling 永不停止
    mc::engine::start_profiling("my_service", 5);
}

❌ 反例 2:在 profiling 未激活时依赖计数

void bad_counting()
{
    // ❌ 错误:若未调用 start_profiling,计数函数静默跳过,
    // 导致统计结果为空
    profiling_inc_sent_rpc(*this);  // 可能无任何效果
    // 应先调用 start_profiling() 再计数
}

注意事项

  • 跨组件口径:组件身份是 well-known service*,不是 D-Bus unique name(同进程多服务常共享 unique name)。RPC sent 在调用方,received 在被调方;同服务 destination == this 不计。Signal:emit / 广播 send 仅当有其它服务或远端订阅者;点对点 send(destination 为其它服务)在实际经 proto 发出时计 sent。received 仅当 emitter well-known 不是本服务(无 stamp 的总线入站视为跨组件)。无 proto 的 send 不计。
  • 同进程隔离:两个 MicroComponent 同时 StartProfiling 时,各自 CSV/日志只含本组件作为端点的次数,不再出现几乎相同的四元组。
  • 性能影响:无活跃 session 时只读一个原子计数;有 session 时按名匹配,不再给所有 session +1。RPC 明细记录需要互斥锁,高并发诊断时再开启。
  • 定时器依赖:start_profiling 的自动停止依赖于 mc::object 的 executor。parent 无效且当前线程无 executor 时须手动 stop_profiling。
  • CSV 写入:停止时在调用线程写 /dev/shm/profiling.csv。
  • 数据一致性:RPC 明细 map 与 session 计数器独立。
  • Unique Name 解析:仅用于 record_outgoing_rpc 的可读输出,不作为是否跨组件的判定。