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) |
行为:
- 若当前 app_name 已有活跃 session,先停旧 session(不写 CSV)。
- 创建新 session,重置 profiling 计数器(原子赋零)。
- 设置 session 为活跃状态。
- 如果
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);
行为:
- 清除对应 session 的活跃标志(后续计数函数不再为它递增)。
- 将 session 的 profiling 计数器写入日志(
nlog)。 - 读取
/dev/shm/profiling.csv,以 app_name 为 key 合并更新后写回。 - 若指定 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 的测试/兜底 |
行为:
peer为空 →false。find_service(peer)非空 → 返回该指针是否不等于&self(与真正 dispatch 的目标一致)。- 未注册 →
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 |
方法名 |
行为:
- 若
service_name以:开头(D-Bus unique name),调用mdb_query尝试解析为 well-known name;解析失败则保留原值。 - 以四个字段构建
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的可读输出,不作为是否跨组件的判定。