Skip to content

Profiler 工厂注册表和收集器管线

本页中的所有地址和偏移均适用于 libtpu-0.0.40-cp314 wheel 中的 libtpu.so(build-id 89edbbe81c5b328a958fe628a9f2207d)。其他版本会有所不同。

摘要

PJRT Profiler 扩展只是很薄的一层 C-ABI 外壳。真正执行工作的机器位于下一层:一个进程全局的工厂注册表,一个把单次生命周期调用扇出到每个已组装收集器的 tsl::profiler::ProfilerCollection,一组把主机和设备 trace 状态排入共享 XSpace具体 ProfilerInterface 实现,以及一个按每次运行来控制哪些 TPU 芯片实际参与的 TpuProfilerControlListener 单例。本页说明的就是这台机器。扩展结构体、8 槽 PLUGIN_Profiler_Api vtable,以及 PLUGIN_Profiler handle 位于 ../pjrt/ext-profiler.md;这里从 PLUGIN_Profiler_Create 调用的 tsl::profiler::CreateProfilers(opts) 接起,并沿着数据流追踪到线路输出。

其形态是标准的 TSL/xprof profiler 架构,也就是 TensorFlow 发布的同一套架构。收集器通过 RegisterProfilerFactory @ 0x1CF50780 注册一个工厂 std::function<unique_ptr<ProfilerInterface>(const ProfileOptions&)>,进入由 absl::Mutex 保护的单个全局 vector。CreateProfilers @ 0x1CF50860 在同一个 mutex 下遍历该 vector,调用每个工厂,并把每个非空结果包装进 ProfilerController(顺序/崩溃隔离保护器),然后收集到返回的 vector<unique_ptr<ProfilerInterface>> 中。ProfilerCollection 接收这个 vector,并通过迭代其内部 vector、调用每个 controller 上匹配的 vtable 槽,暴露相同的三方法 ProfilerInterface 表面:Start/Stop/CollectData。collection 自身也是 一个 ProfilerInterface,因此该设计是一个组合体:profiler 组成的 profiler。

具体收集器是异构的。xprof::tpu::TpuProfilerImpl::CollectData @ 0xEF34860 是设备排出路径:它在 XSpace arena 上分配一个 XprofResponse proto,通过内部收集器拉取每芯片 trace 数据,并通过 ConvertResponseToTpuXSpace 把结果折叠进设备 XPlanetsl::profiler::ThreadpoolProfilerInterface::CollectData @ 0xF3326C0 是一个近乎平凡的主机收集器,只在其 session 以非 OK 结束时追加一条诊断字符串。HostTracer 捕获主机 TraceMe 事件。它们都不是逐 trace event 触达的;它们只在 CollectData 时运行,排出由无锁 TraceMe 宏和硬件环形缓冲区累计的状态。与收集器路径正交的是,TpuProfilerControlListenerGetOrCreateTpuProfilerControlListener @ 0xF332800)让每个 TPU 芯片驱动在打开 trace buffer 之前或排出之后询问 CanStartProfiler/MustStopProfiler,因此 profiling 可以与执行并发运行,而 listener 逐芯片决定是否参与。

对重新实现而言,契约如下:

  • 注册表:一个惰性构造的 vector<std::function<...>>GetFactories()::factories),在 mu 下修改,永不清空。
  • 工厂遍历CreateProfilers整个遍历期间持有 mu,调用每个工厂,丢弃 null 结果,把其余结果包装进 ProfilerController
  • ProfilerController FSM:一个 phase 计数器和一个粘性的 last-status,用来强制 Start→Stop→CollectData 顺序,并在任何错误之后短路。
  • ProfilerCollection 扇出:32 字节对象,迭代内部 vector,调用 vtable +0x10/+0x18/+0x20;第一个非平凡 status 胜出的合并;CollectData 内部会破坏性地清空 vector。
  • 收集器语义:设备排出(TpuProfilerImpl)与主机追加(ThreadpoolProfilerInterfaceHostTracer)。
  • 运行门控TpuProfilerControlListener 委托给 xprof::tpu::Profiler::Register/UnregisterChipProfiler
注册表修改器tsl::profiler::RegisterProfilerFactory @ 0x1CF50780
注册表存储GetFactories()::factories @ 0x2257C830vector<std::function<...>>,惰性 new
注册表 mutextsl::profiler::(anonymous)::mu @ 0x2257C828absl::Mutex
工厂遍历tsl::profiler::CreateProfilers @ 0x1CF50860
崩溃/顺序保护器tsl::profiler::ProfilerController ctor @ 0x1CF50CE0,32 字节
组合体tsl::profiler::ProfilerCollection,32 字节,vtable @ 0x217738A0
扇出Start @ 0xF6A1640Stop @ 0xF6A16C0CollectData @ 0xF6A1740
设备收集器xprof::tpu::TpuProfilerImpl::CollectData @ 0xEF34860
主机收集器ThreadpoolProfilerInterface::CollectData @ 0xF3326C0HostTracer(factory @ 0xF32F7C0
运行门控单例GetOrCreateTpuProfilerControlListener @ 0xF332800,16 字节,vtable symbol @ 0x2175C1A0(installed vptr 0x2175C1B0
输出一个 tensorflow.profiler.XSpace proto,主机 + 每设备 planes

工厂注册表

目的

profiler 是在链接时插入的,而不是在调用时插入的。每个后端,包括 host tracer、TPU device tracer、threadpool tracer、megascale RPC tracer,都会在 C++ 静态初始化期间贡献一个工厂函数。注册表就是这些工厂落入的单一全局列表,而 CreateProfilers 是唯一的读取者。这使得同一个后端既能从现代 PJRT 扩展触达,也能从旧版 TpuProfiler_* C-ABI 触达:两者都调用 CreateProfilers,而它遍历同一个共享列表。

入口点

text
<static init>  _GLOBAL__sub_I_*profiler*.cc
  └─ RegisterProfilerFactory(std::function<...>)   ── 0x1CF50780  (under mu)
        └─ GetFactories()::factories                ── 0x2257C830  (lazy `new(0x18)`)

PLUGIN_Profiler_Create / TpuProfiler_Create
  └─ CreateProfilers(opts)                          ── 0x1CF50860  (under mu)
```text

### 算法

`RegisterProfilerFactory` 是受保护的 append。`factories` vector 是一个 24 字节的 `{begin, end, cap_end}` header,在首次使用时于读取者也使用的同一个 mutex 下分配,然后对每个传入的 `std::function`(32 字节槽)执行 move-emplace。

```c
function RegisterProfilerFactory(fn /* std::function, 32 bytes */):   // 0x1CF50780
    mu.Lock();                                          // 0x2257C828
    if (!guard(factories)):                             // one-time init
        factories = operator new(0x18);                 // {0,0,0} vector header
        zero(factories);
    if (factories.size == factories.cap):               // grow
        emplace_back_slow_path(factories, fn);
    else:                                               // in-place move
        slot = factories.begin + 32 * factories.size;
        move_construct(slot, fn);                       // steals fn's callable; clears fn
        factories.size += 1;
    mu.Unlock();
    // No return value. The vector is never shrunk or cleared for the process lifetime.

注意 — 注册表没有注销路径,也没有 clear。在静态初始化期间注册的工厂会一直存在到进程退出。因此,CreateProfilers 对各个 handle 是幂等的:每个 PLUGIN_Profiler_Create(或 TpuProfiler_Create)看到的工厂集合完全相同。

函数映射

函数地址作用
RegisterProfilerFactory0x1CF50780mu 下追加一个工厂
GetFactories()::factories0x2257C830全局 vector<std::function<...>>
(anonymous)::mu0x2257C828保护该 vector 的 absl::Mutex

工厂遍历 — CreateProfilers

目的

CreateProfilers 是每 session 收集器集合的构造器。它在每次 PLUGIN_Profiler_Create 时调用,接收解析后的 tensorflow::ProfileOptions,并返回一个 vector<unique_ptr<ProfilerInterface>>,其中每个选择加入的工厂对应一个被 ProfilerController 包装的收集器。ProfileOptions 在这里不会被检查;它会原样传给每个工厂,由工厂自行决定是否参与(工厂返回 NULL 表示退出,例如 CPU-only device_type 上的设备 tracer)。

入口点

text
PLUGIN_Profiler_Create  (0xE6F0C60, see ../pjrt/ext-profiler.md)
  └─ CreateProfilers(opts)                       ── 0x1CF50860
        ├─ mu.Lock()                              ── held across the whole walk
        ├─ for each factory slot:
        │     ├─ result = factory(opts)           ── indirect call, slot+16
        │     └─ if result: ProfilerController(new(0x20), result)   ── 0x1CF50CE0
        └─ mu.Unlock()
  └─ new ProfilerCollection(move(result))          ── 0xF6A15E0
```text

### 算法

```c
function CreateProfilers(out /* vector<unique_ptr<ProfilerInterface>> */, opts):  // 0x1CF50860
    out = {begin:0, size:0, cap:0};
    mu.Lock();                                          // 0x2257C828
    lazy_init(factories);                               // same one-time path as the mutator
    for slot in factories:                              // 32 bytes per std::function
        raw = slot.invoke(opts);                        // *(slot+16)(slot, &opts) — the factory call
        if (raw != NULL):                               // factory opted in
            ctrl = operator new(0x20);                  // ProfilerController, 32 bytes
            ProfilerController(ctrl, raw);              // 0x1CF50CE0 — takes ownership of raw
            push_back(out, ctrl);                       // grows out (2x) as needed
    mu.Unlock();
    return out;

注意 — mu0x1CF50860 处会贯穿整个遍历过程被持有,包括间接的 factory(opts) 调用;mu.Unlock() 是返回前的最后一条语句。因此工厂体在持有 mu 的状态下运行,并且不得重入调用 RegisterProfilerFactoryCreateProfilersmu 是非递归的 absl::Mutex);这样做会自死锁。不同 PJRT handle 除了这个被串行化的遍历之外不共享可变状态。

怪癖 — 工厂返回 NULL 会被静默丢弃,并不算错误。结合“此处不检查”的规则,如果某个 ProfileOptionsdevice_type 没有匹配任何工厂,就会得到一个空的 ProfilerCollection。之后每个 Start/Stop/CollectData 都会对零个收集器成功,并生成一个空 XSpace。重新实现必须把空 collection 成功视为有效结果,而不是要拒绝的错误配置。

函数映射

函数地址作用
CreateProfilers0x1CF50860遍历注册表、包装、收集
ProfilerController::ProfilerController(unique_ptr)0x1CF50CE0包装一个收集器以进行隔离
ProfilerCollection::ProfilerCollection(vector)0xF6A15E0内联接收结果 vector

ProfilerController 隔离保护器

目的

工厂返回的每个收集器在进入 collection 之前都会被包装进 ProfilerController。controller 是围绕内部 ProfilerInterface 的有限状态保护器:它强制合法调用顺序(Start → Stop → CollectData),缓存最后一个 status,使失败的阶段短路所有后续阶段,并在任何违规时记录一个 absl::Status。这是崩溃隔离层:出错或被乱序调用的收集器无法污染 collection 中其余收集器,也无法污染 PJRT handle 上的生命周期状态机。

布局

c
/* tsl::profiler::ProfilerController — 32 bytes (operator new(0x20)). */
struct ProfilerController {
    /* +0x00 */ void**         vtable;       /* ProfilerInterface vtable                       */
    /* +0x08 */ uint32_t       phase;        /* FSM: 0=created, 1=started, 2=stopped, 3=collected */
    /* +0x0C */ uint32_t       _pad;
    /* +0x10 */ ProfilerInterface* inner;    /* the wrapped collector; owned                   */
    /* +0x18 */ absl::Status   last_status;  /* sticky; 1 == inline OkStatus                   */
};
```text

### 算法

`Start` 和 `CollectData` 主体(`0x1CF50DE0`、`0x1CF51060`)共享同一种形态:检查 phase,推进 phase,确认最后一个 status 为 OK,然后调用内部收集器匹配的 vtable 槽,并缓存新的 status。`Stop`(`0x1CF50F20`)在 phase 12 处遵循同样模式。

```c
function ProfilerController::Start(this):                 // 0x1CF50DE0
    if (this.phase != 0):                                 // wrong order
        return Log(MakeErrorImpl<10 ABORTED>("Start called in the wrong order"));   // profiler_controller.cc:51
    this.phase = 1;
    if (this.last_status != OK):                          // a prior phase already failed
        return Log("Previous call returned an error.");
    s = this.inner->Start();                              // vtable +0x10
    this.last_status = s;                                 // cache (ref-counted)
    if (s != OK): Log(s);
    return s;

function ProfilerController::CollectData(this, xspace):    // 0x1CF51060
    if (this.phase != 2):                                 // must follow a Stop
        return Log("CollectData called in the wrong order.");
    this.phase = 3;
    if (this.last_status != OK):
        return Log("Previous call returned an error.");
    s = this.inner->CollectData(xspace);                  // vtable +0x20
    this.last_status = s;
    if (s != OK): Log(s);
    return s;

陷阱 — 让顺序违规可存活的是 controller,而不是 ProfilerCollection。PJRT handle 的 ready 字节(见 ../pjrt/ext-profiler.md)是一个粗粒度、复用过度的门控,它不会检测例如 Stop 前的 CollectData。controller 在这里捕获这种情况,记录到 profiler_controller.cc:84,并返回错误 status,且不会调用内部收集器。因此,乱序调用无法驱动一个半初始化的 device tracer。省略 controller、直接分发到收集器的重新实现,会把这些 bug 暴露为崩溃而不是记录的 status。

函数映射

函数地址作用
ProfilerController::Start0x1CF50DE0phase 0→1,受保护的内部 Start
ProfilerController::Stop0x1CF50F20phase 1→2,受保护的内部 Stop
ProfilerController::CollectData0x1CF51060phase 2→3,受保护的内部 CollectData
ProfilerController::~ProfilerController (D2/D0)0x1CF50D20 / 0x1CF50DA0丢弃内部对象,unref status

ProfilerCollection 扇出

目的

ProfilerCollection 是组合体:它通过把每个调用转发给所有成员来实现 ProfilerInterface。PJRT handle 在 +0x70 处正好拥有一个这样的对象;五个 PJRT 生命周期方法会编组进入它的三个真实方法。因为它自身也是一个 ProfilerInterface,该设计可以干净地嵌套,尽管实践中 collection 持有的是 ProfilerController,而不是嵌套的 collection。

布局

c
/* tsl::profiler::ProfilerCollection — 32 bytes. vtable @ 0x217738A0. */
struct ProfilerCollection {
    /* +0x00 */ void**              vtable;   /* {top, RTTI, D2, D0, Start, Stop, CollectData} */
    /* +0x08 */ ProfilerController** begin;   /* inner vector data                              */
    /* +0x10 */ size_t              size;     /* element count                                  */
    /* +0x18 */ size_t              capacity;
};
```text

`0x217738A0` 处的 vtable 是 [PJRT 层](../pjrt/ext-profiler.md)所调用偏移的来源:`Start` 位于 `+0x10`(`0xF6A1640`),`Stop` 位于 `+0x18`(`0xF6A16C0`),`CollectData` 位于 `+0x20`(`0xF6A1740`),destroying dtors 位于 `+0x08`(`0xF6A1840` D2 / `0xF6A18E0` D0)。

### 算法

三个扇出方法都共享一种 status 合并惯用法:按顺序遍历内部 vector,调用成员匹配的 vtable 槽,在 `Unref` 每个后续非内联 status 的同时保留**第一个**非平凡 status。`CollectData` 在遍历之后还会增加一个破坏性的清理 pass。

```c
function ProfilerCollection::Start(this):                 // 0xF6A1640
    if (this.size == 0): return OkStatus;                 // empty collection — trivially OK
    merged = OkStatus;                                    // sentinel 1
    for ctrl in this[0 .. size):                          // forward order
        s = ctrl->Start();                                // vtable +0x10 (the controller's Start)
        if (merged == OkStatus): merged = s;              // first status wins
        else if (s is heap-rep):  Unref(s);               // discard later statuses
    return merged;

function ProfilerCollection::CollectData(this, xspace):    // 0xF6A1740
    if (this.size != 0):
        merged = OkStatus;
        for ctrl in this[0 .. size):
            s = ctrl->CollectData(xspace);                // vtable +0x20 — SAME xspace, members APPEND
            if (merged == OkStatus): merged = s; else if (s is heap-rep): Unref(s);
        for ctrl in this[size-1 .. 0]:                    // REVERSE destroy pass
            slot = take(ctrl); set slot = NULL;
            if (slot): slot->~()                          // vtable +0x08 — destroying dtor
        this.size = (begin_after - begin) >> 3;           // collapses to 0 — vector cleared
    else:
        merged = OkStatus;
    return merged;

Stop @ 0xF6A16C0 在结构上与 vtable 槽 +0x18 处的 Start 相同,并且没有 destroy pass。

注意 — status 合并是第一个 status 胜出:在 Start/Stop/CollectData 中,merged 初始化为内联 Ok sentinel,并且只在它仍等于该 sentinel 时才被覆盖,因此它捕获第一个返回 status(OK 或非 OK)的成员,并对所有后续 status 执行 Unref。由于空 collection 会提前返回 OK,第一个产生真实 status 的收集器决定合并结果。

陷阱 — 单个共享的 XSpace* 是 append 契约。每个成员都写入同一个 proto:TpuProfilerImpl 添加设备 XPlaneThreadpoolProfilerInterface 可能添加一个 errors 字符串,HostTracer 添加 host plane。因此成员顺序决定输出中的 plane 顺序。破坏性的反向销毁 pass 随后把 collection 重置为空,这正是 PJRT CollectData 依赖的一次性行为;第二次调用会在空 vector 上运行,并重新序列化缓存的字节。

函数映射

函数vtable 槽地址作用
ProfilerCollection::Start+0x100xF6A1640Start 扇出到所有成员
ProfilerCollection::Stop+0x180xF6A16C0Stop 扇出到所有成员
ProfilerCollection::CollectData+0x200xF6A1740扇出 CollectData,然后销毁成员
ProfilerCollection::~ (D2/D0)+0x080xF6A1840 / 0xF6A18E0丢弃剩余成员 + heap
ProfilerCollection::ProfilerCollection(vector)0xF6A15E0接收 CreateProfilers 结果

具体收集器

xprof::tpu::TpuProfilerImpl — 设备排出

这是唯一接触 TPU 硬件的收集器。它的 CollectData @ 0xEF34860 是从设备侧 trace transport(XprofResponse)到 XSpace 设备 planes 的桥梁。它在 XSpace 的 arena 上分配 response(因此 response 生命周期绑定到输出 proto),用 session 的 run_id 和第二个 metadata 字段为其打标,排出内部每芯片收集器,然后进行转换。

c
function TpuProfilerImpl::CollectData(this, xspace):       // 0xEF34860
    arena = arena_of(xspace);                              // xspace+8, untag low bit
    resp  = Arena::DefaultConstruct<XprofResponse>(arena);
    if (this.field_0x10 && this.field_0x18):               // session identifiers present
        resp.set_at(296, this.field_0x10);  resp.flags |= 0x100000;     // e.g. run_id
        resp.set_at(336, this.field_0x18);  resp.flags |= 0x8000000;
    inner = this.field_0x08;                               // per-chip collector
    if (inner):
        s = inner->vtable[+40](inner, resp);               // drain into resp
        this.field_0x08 = NULL; inner->~();                // release the inner collector
        if (s != OK):
            if (resp on heap): XprofResponse::SharedDtor(resp); free(resp);
            return s;
    wrapper = XprofResponseWrapper(resp, arena, {...});
    ConvertResponseToTpuXSpace(wrapper, xspace, &nptr, 0); // resp -> device XPlanes appended to xspace
    DropExcessBytes(xspace, xspace);                       // trim oversized event payloads
    return OkStatus;
```text

> **注意 —** `ConvertResponseToTpuXSpace` 是每芯片 `TraceEntry` blob 变成 `/device:TPU:N` planes 上 `XEvent`/`XEventMetadata` 的地方;`DropExcessBytes` 对 event metadata 强制执行大小上限。两者属于 [trace-entries coder](trace-entries-coder.md) 和 [XPlane/XStat/TraceMe](xplane-xstat-traceme.md) 页面;在这里它们是设备排出路径的不透明尾部。

### `tsl::profiler::ThreadpoolProfilerInterface` — 主机追加

一个近乎平凡的主机收集器。它的 `CollectData` @ `0xF3326C0` 在 collect 时不做 tracing 工作;它只通过把字符串化的 status 追加到 XSpace 的 `errors` repeated field(`XSpace.errors = 2`,结构偏移 `+40`)来报告失败的 session。

```c
function ThreadpoolProfilerInterface::CollectData(this, xspace):  // 0xF3326C0
    if (this.status != OK):                                // this+0x08
        msg = this.status.ToStringSlow();
        slot = xspace.errors.Add(arena_of(xspace));        // repeated string errors, +40
        xspace.has_bits |= 2;
        assign(slot, msg);
    return OkStatus;                                        // always OK

怪癖 — ThreadpoolProfilerInterface 不贡献任何 XPlane。它唯一可见的输出是在其 threadpool-tracing session 失败时写入 XSpace.errors 的条目。扫描 planes 以寻找“threadpool tracer 捕获了什么”的消费者不会找到任何东西;信号在 errors 中,而不在 planes 中。

xprof::cpu::HostTracer — 主机 plane

HostTracer 是主机侧 TraceMe 收集器,由 CreateHostTracer @ 0xF32F7C0 注册(它包装了 CreateHostTracer(HostTracerOptions) @ 0xF32F820)。它的 Start/Stop/CollectData 位于 0xF32FA40 / 0xF32FAC0 / 0xF32FB40。在 CollectData 时,它把每线程 TraceMe recorders flush 到 /host:0 XPlane。TraceMe 捕获路径本身位于 XPlane/XStat/TraceMe

收集器映射

收集器工厂CollectData输出到 XSpace
xprof::tpu::TpuProfilerImpl(旧版 TpuProfiler_Create 路径)0xEF34860通过 ConvertResponseToTpuXSpace 写入 /device:TPU:N planes
ThreadpoolProfilerInterface(static-init)0xF3326C0仅失败时写入 errors 字符串
xprof::cpu::HostTracer0xF32F7C00xF32FB40来自 TraceMe/host:0 plane

注意 — 完整的工厂清单尚未穷举。这三个由 symbol 和反编译主体确认;其他工厂(例如由 FLAGS_enable_megascale_profiler @ 0x2236E238 门控的 megascale RPC tracer)分布在多个 _GLOBAL__sub_I_*profiler*.cc static-init block 中,完整性为低置信度。


运行门控 — TpuProfilerControlListener

目的

收集(CollectData)是 session 结束时的一次性排出,但 tracing 会与 PJRT_LoadedExecutable_Execute 并发运行。两者之间的协调点是 TpuProfilerControlListener 单例,它在构造时安装到每个 TPU 芯片驱动中。芯片在打开其每核心 trace ring buffer 之前会询问 CanStartProfiler;为支持执行中途停止,它会轮询 MustStopProfiler。PJRT 收集器路径并不直接知道哪些芯片会参与;这完全由 listener 决定,并委托给全局 xprof::tpu::Profiler 单例。

布局和构造

c
/* xprof::tpu::TpuProfilerControlListener — 16 bytes, vtable symbol @ 0x2175C1A0 (installed vptr 0x2175C1B0). */
struct TpuProfilerControlListener {
    /* +0x00 */ void**             vtable;
    /* +0x08 */ xprof::tpu::Profiler* profiler;   /* the global profiler singleton */
};

function GetOrCreateTpuProfilerControlListener():          // 0xF332800
    if (guard(singleton)): return singleton;               // singleton @ 0x224C5D78, __cxa_guard @ 0x224C5D80
    p = operator new(0x10);
    p->profiler = Profiler::GetOrCreateProfilerSingleton(); // 0xF336640
    p->vtable   = off_2175C1B0;  // installed vptr = vtable symbol 0x2175C1A0 + 0x10
    singleton   = p;
    return singleton;
```text

### 算法

两个 gate 方法都是 `xprof::tpu::Profiler` 单例之上的薄适配器。`CanStartProfiler` 收集芯片身份和配置,并调用 `RegisterChipProfiler`;`MustStopProfiler` 调用 `UnregisterChipProfiler`。register/unregister 对才是真正的门控,返回给芯片驱动的 boolean 表示单例是否接受(start)或要求停止。

```c
function TpuProfilerControlListener::CanStartProfiler(this, chip_loc, chip_profiler, run_id):  // 0xF3328C0
    VLOG(1) "CanStartProfiler: chip_ordinal=" << chip_loc.index_on_host();  // deepsea_listeners.cc:33
    cfg   = chip_loc.chip->config;
    flags = TpuChipConfig::GetSpecialPurposeSyncFlags(cfg);
    // builds a request {chip_id, run_id, sync_flag_value, megacore} ...
    return Profiler::RegisterChipProfiler(this.profiler);  // delegate: accepts or vetoes the chip

function TpuProfilerControlListener::MustStopProfiler(this, chip_loc):       // 0xF332A00
    VLOG(1) "MustStopProfiler: chip_ordinal=" << chip_loc.index_on_host();   // deepsea_listeners.cc:56
    return Profiler::UnregisterChipProfiler(this.profiler, chip_loc.index_on_host());

注意 — listener 在 gate 附近还带有若干编译器侧注册方法:RegisterLloModule @ 0xF332BC0RegisterCompilerMetadata @ 0xF332AE0RegisterDebugMetadata @ 0xF332D20RegisterBarnaCoreSyncFlagMetadata @ 0xF333140,它们会在编译期间用 HLO source locations 丰富 device-plane XEventMetadata。它们是同一个单例的一部分,但属于设备排出路径的输入,而不是 start/stop gate;其细节属于 device-plane 页面。

函数映射

函数地址作用
GetOrCreateTpuProfilerControlListener0xF332800__cxa_guard 单例,包装 Profiler*
CanStartProfiler(chip_loc, profiler, run_id)0xF3328C0芯片进入门控:委托给 RegisterChipProfiler
MustStopProfiler(chip_loc)0xF332A00芯片退出轮询:委托给 UnregisterChipProfiler
Profiler::GetOrCreateProfilerSingleton0xF336640被包装的 xprof::tpu::Profiler

注意 — listener 的 vtable 槽顺序相对于其抽象基类尚未提取;四个注册方法和两个 gate 方法已在引用地址处确认,但每个方法在 vtable(symbol 0x2175C1A0,installed vptr 0x2175C1B0)中的位置为低置信度。


端到端数据流

text
JAX / PT-XLA ──Create──► PLUGIN_Profiler_Create  (0xE6F0C60)
                              └─ CreateProfilers(opts)  (0x1CF50860, under mu)
                                   walk factories ─► [HostTracer][TpuProfilerImpl][Threadpool]...
                                   each wrapped in ProfilerController (0x1CF50CE0)
                              └─ ProfilerCollection(move(vec))  (0xF6A15E0)  ─► handle+0x70

           ──Start──► collection->Start (0xF6A1640) ─► each ctrl->Start ─► inner->Start
                              (meanwhile, per chip:  TpuProfilerControlListener::CanStartProfiler)

           ──Stop───► collection->Stop  (0xF6A16C0) ─► each ctrl->Stop  ─► inner->Stop
                              (per chip:  MustStopProfiler poll)

           ──CollectData──► collection->CollectData(&xspace) (0xF6A1740)
                              ├─ each ctrl->CollectData(&xspace)  (APPEND to one XSpace)
                              │     TpuProfilerImpl  ─► /device:TPU:N planes  (0xEF34860)
                              │     HostTracer       ─► /host:0 plane
                              │     Threadpool       ─► errors[] on failure   (0xF3326C0)
                              └─ destroy all members; collection now empty (one-shot)
                              ─► XSpace serialized to caller buffer (PJRT layer)
```text

---

## 与相邻层的关系

| 层 | 拥有内容 | 关系 |
|---|---|---|
| [PJRT Profiler 扩展](../pjrt/ext-profiler.md) | extension struct、`PLUGIN_Profiler_Api` vtable、handle、5 方法生命周期 | 调用 `CreateProfilers` 和本文档说明的 `ProfilerCollection` 扇出 |
| [旧版 `TpuProfiler_*` ABI](tpu-profiler-abi.md) | 5 个 C exports + 120 字节 handle | 通过 `CreateProfilers` 触达*同一个*注册表/collection |
| [XPlane / XStat / TraceMe](xplane-xstat-traceme.md) | XSpace event hierarchy + host capture | 这些收集器追加写入的 proto |
| [Trace-entries coder](trace-entries-coder.md) | 每芯片 `TraceEntry` decode | `ConvertResponseToTpuXSpace` 折叠进来的设备 payload |

---

## 交叉引用

- [PJRT Profiler 扩展](../pjrt/ext-profiler.md) — 位于这个 factory/collection 机器*之上*的 extension struct、`PLUGIN_Profiler_Api` vtable、handle layout 和 5 方法生命周期;本页记录支配它的遍历期间持锁 mutex 作用域和第一个 status 胜出的合并
- [Profiling 概览](overview.md) — 这个收集器管线在 libtpu 整体 telemetry 架构中的位置
- [旧版 TpuProfiler C-ABI](tpu-profiler-abi.md) — 同一个 `CreateProfilers` 注册表的第二个消费者,并通过 handle size 和 error channel 进行对比
- [XPlane / XStat / TraceMe](xplane-xstat-traceme.md) — 收集器把 planes 扇入的 `XSpace` proto
- [Trace-entries Coder](trace-entries-coder.md) — `TpuProfilerImpl` 的 `ConvertResponseToTpuXSpace` 背后的每芯片 `TraceEntry` decode