Skip to content

Refactor: shard profiling collector accumulation#1312

Open
zmnobug wants to merge 1 commit into
hw-native-sys:mainfrom
zmnobug:full-buffer-optimization
Open

Refactor: shard profiling collector accumulation#1312
zmnobug wants to merge 1 commit into
hw-native-sys:mainfrom
zmnobug:full-buffer-optimization

Conversation

@zmnobug

@zmnobug zmnobug commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Extend PMU, DepGen, TensorDump, and ScopeStats host collectors to consume collector shard ids.
  • Accumulate hot-path collector output in shard-local state, then merge during stop/export to reduce shared lock contention.
  • Keep PMU CSV output bounded with shard-local temp files and add a ScopeStats regression test for shard merge before JSON export.

Testing

  • git diff --check upstream/main..HEAD
  • CCACHE_DIR=/tmp/ccache cmake --build tests/ut/cpp/build --target test_scope_stats_collector -j 8
  • ./tests/ut/cpp/build/test_scope_stats_collector

Refs #1281
Refs #1237

- Route PMU, DepGen, TensorDump, and ScopeStats callbacks through collector shard ids.

- Accumulate collector output in shard-local state and merge during stop/export to avoid hot-path shared locks.

- Add a ScopeStats unit test covering sharded collection before JSON export.
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 403944ed-0551-4d08-a5d5-e773d5eadd1c

📥 Commits

Reviewing files that changed from the base of the PR and between 1111660 and d79c947.

📒 Files selected for processing (11)
  • src/a2a3/platform/include/host/pmu_collector.h
  • src/a2a3/platform/shared/host/pmu_collector.cpp
  • src/a5/platform/include/host/pmu_collector.h
  • src/a5/platform/shared/host/pmu_collector.cpp
  • src/common/platform/include/host/dep_gen_collector.h
  • src/common/platform/include/host/scope_stats_collector.h
  • src/common/platform/include/host/tensor_dump_collector.h
  • src/common/platform/shared/host/dep_gen_collector.cpp
  • src/common/platform/shared/host/scope_stats_collector.cpp
  • src/common/platform/shared/host/tensor_dump_collector.cpp
  • tests/ut/cpp/common/test_scope_stats_collector.cpp

📝 Walkthrough

Walkthrough

Collector classes (PmuCollector in a2a3 and a5 platforms, DepGenCollector, ScopeStatsCollector, TensorDumpCollector) are refactored to support per-shard buffer accumulation. Each on_buffer_collected callback gains a collector_shard parameter; single mutex-guarded shared state (CSV files or record vectors) is replaced with per-shard containers/counters merged or flushed at reconcile/export/finalize time. A unit test validates sharded merge before JSONL export.

Changes

Sharded Collector Refactor

Layer / File(s) Summary
PmuCollector sharded CSV (a2a3)
src/a2a3/platform/include/host/pmu_collector.h, src/a2a3/platform/shared/host/pmu_collector.cpp
CSV writer switches from single mutex-guarded file to per-shard temp files with CollectorShardCounters, appended/flushed/merged into final CSV during reconcile and finalize.
PmuCollector sharded CSV (a5)
src/a5/platform/include/host/pmu_collector.h, src/a5/platform/shared/host/pmu_collector.cpp
Mirrors the a2a3 sharded CSV pipeline for the a5 platform, including shard reset/open/append/flush/cleanup helpers.
DepGenCollector sharded records
src/common/platform/include/host/dep_gen_collector.h, src/common/platform/shared/host/dep_gen_collector.cpp
In-memory records_ accumulation moves to per-collector shard vectors with counters, merged via merge_collector_shards() before reconciliation and finalize.
ScopeStatsCollector sharded records + tests
src/common/platform/include/host/scope_stats_collector.h, src/common/platform/shared/host/scope_stats_collector.cpp, tests/ut/cpp/common/test_scope_stats_collector.cpp
Record accumulation is sharded, merged before write_jsonl/reconcile_counters, mutex-guarded serialization removed, and a new test verifies merged sharded JSONL export.
TensorDumpCollector sharded records + atomics
src/common/platform/include/host/tensor_dump_collector.h, src/common/platform/shared/host/tensor_dump_collector.cpp
Collected metadata moves to per-shard containers merged into collected_, counters become atomics, writer-thread start is mutex-guarded, and progress tracking uses millisecond timestamps.

Estimated code review effort: 4 (Complex) | ~75 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Profiler as ProfilerBase
  participant Collector as Collector (Pmu/DepGen/ScopeStats/TensorDump)
  participant Shard as Per-Shard Storage
  participant Final as Merged Output

  Profiler->>Collector: on_buffer_collected(info, collector_shard)
  Collector->>Collector: normalize_collector_shard(collector_shard)
  Collector->>Shard: append record/counter into shard
  Note over Collector,Shard: repeated per ready buffer
  Collector->>Collector: reconcile_counters() / export
  Collector->>Shard: merge_collector_shards() / flush_collector_shards_to_csv()
  Shard->>Final: consolidate into records_/collected_/final CSV
  Collector->>Final: finalize() clears shard state
Loading

Possibly related issues

Possibly related PRs

  • hw-native-sys/simpler#992: Both touch ScopeStatsCollector's write_jsonl() export path at the code level.
  • hw-native-sys/simpler#1054: Both modify ScopeStatsCollector's reconcile_counters/export flow around unflushed buffer recovery feeding into write_jsonl().
  • hw-native-sys/simpler#1273: Both implement/consume the shard-aware on_buffer_collected(..., int collector_shard) callback contract.

Poem

A rabbit hops through shards of code,
Each thread its own tidy abode. 🐇
No more mutex traffic jam—
Merge them all, ma'am, wham-bam!
CSV rows and JSONL too,
Sharded and merged, clean and true. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.54% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: sharded profiling collector accumulation.
Description check ✅ Passed The description matches the changeset by describing shard-aware collectors, shard-local accumulation, PMU CSV temp files, and the new regression test.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements sharded collection across several profiling collectors (PMU, DepGen, ScopeStats, and TensorDump) to eliminate mutex contention on the hot path by writing to shard-local files or vectors and merging them during stop-time reconciliation. However, the review highlights critical concurrency hazards across all modified collectors: on the hot path, there are non-thread-safe checks for empty shard containers or merged states that can trigger data races, memory corruption, or crashes when accessed concurrently by multiple threads. Since proper initialization is already performed during setup, these redundant and dangerous lazy-initialization checks should be removed.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +244 to +246
if (csv_shard_files_.empty()) {
reset_collector_shards();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Concurrency Hazard & Redundant Check

Multiple collector threads concurrently invoke on_buffer_collected (and thus append_buffer_to_csv_shard) on the hot path.

If csv_shard_files_ is empty, multiple threads will concurrently call reset_collector_shards(), which modifies shared vectors (csv_shard_paths_, csv_shard_files_, collector_counters_) without any synchronization. This leads to a severe data race, memory corruption, and potential crashes.

Since reset_collector_shards() is already called during init(), csv_shard_files_ is guaranteed to be initialized and sized correctly before any profiling starts. This lazy-initialization check is redundant and introduces a dangerous, non-thread-safe code path.

// csv_shard_files_ is guaranteed to be initialized by init()
References
  1. Avoid redundant scans or loops to re-verify state flags if the flag is already maintained by the primary data-collection path. Such redundant checks can be replaced with a comment noting where the flag is set.
  2. When a component is accessed by multiple threads, its operations should be protected by a mutex to prevent data races.

Comment on lines +283 to +285
if (csv_shard_files_.empty()) {
reset_collector_shards();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Concurrency Hazard & Redundant Check

Multiple collector threads concurrently invoke on_buffer_collected (and thus append_buffer_to_csv_shard) on the hot path.

If csv_shard_files_ is empty, multiple threads will concurrently call reset_collector_shards(), which modifies shared vectors (csv_shard_paths_, csv_shard_files_, collector_counters_) without any synchronization. This leads to a severe data race, memory corruption, and potential crashes.

Since reset_collector_shards() is already called during init(), csv_shard_files_ is guaranteed to be initialized and sized correctly before any profiling starts. This lazy-initialization check is redundant and introduces a dangerous, non-thread-safe code path.

// csv_shard_files_ is guaranteed to be initialized by init()
References
  1. Avoid redundant scans or loops to re-verify state flags if the flag is already maintained by the primary data-collection path. Such redundant checks can be replaced with a comment noting where the flag is set.
  2. When a component is accessed by multiple threads, its operations should be protected by a mutex to prevent data races.

Comment on lines +190 to +197
if (records_by_collector_.empty()) {
reset_collector_shards();
}
if (collector_shards_merged_) {
records_.clear();
total_collected_ = 0;
collector_shards_merged_ = false;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Concurrency Hazard & Redundant/Incorrect State Reset

Multiple collector threads concurrently invoke on_buffer_collected (and thus append_buffer_records) on the hot path.

  1. Lazy Initialization Race: If records_by_collector_ is empty, multiple threads will concurrently call reset_collector_shards(), which modifies shared vectors without synchronization, causing a severe data race and memory corruption.
  2. State Reset Race & Bug: If collector_shards_merged_ is true, multiple threads will concurrently clear records_ and reset collector_shards_merged_ to false. Furthermore, this reset is incomplete because it does not clear records_by_collector_, meaning old records would still be retained and duplicated upon the next merge.

Since reset_collector_shards() is already called during init(), these vectors are guaranteed to be initialized and sized correctly before any profiling starts. These checks are redundant, incorrect, and introduce dangerous non-thread-safe code paths.

// records_by_collector_ is guaranteed to be initialized by init()
References
  1. Do not unconditionally reset global synchronization or coordination states in a stale or null-state branch of a multi-threaded coordination protocol. Only release the thread's own election lock to prevent corrupting concurrently or subsequently initiated runs.
  2. Avoid redundant scans or loops to re-verify state flags if the flag is already maintained by the primary data-collection path. Such redundant checks can be replaced with a comment noting where the flag is set.
  3. When a component is accessed by multiple threads, its operations should be protected by a mutex to prevent data races.

Comment on lines +188 to +195
if (records_by_collector_.empty()) {
reset_collector_shards();
}
if (collector_shards_merged_) {
records_.clear();
total_collected_ = 0;
collector_shards_merged_ = false;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Concurrency Hazard & Redundant/Incorrect State Reset

Multiple collector threads concurrently invoke on_buffer_collected (and thus append_buffer_records) on the hot path.

  1. Lazy Initialization Race: If records_by_collector_ is empty, multiple threads will concurrently call reset_collector_shards(), which modifies shared vectors without synchronization, causing a severe data race and memory corruption.
  2. State Reset Race & Bug: If collector_shards_merged_ is true, multiple threads will concurrently clear records_ and reset collector_shards_merged_ to false. Furthermore, this reset is incomplete because it does not clear records_by_collector_, meaning old records would still be retained and duplicated upon the next merge.

Since reset_collector_shards() is already called during init(), these vectors are guaranteed to be initialized and sized correctly before any profiling starts. These checks are redundant, incorrect, and introduce dangerous non-thread-safe code paths.

// records_by_collector_ is guaranteed to be initialized by init()
References
  1. Do not unconditionally reset global synchronization or coordination states in a stale or null-state branch of a multi-threaded coordination protocol. Only release the thread's own election lock to prevent corrupting concurrently or subsequently initiated runs.
  2. Avoid redundant scans or loops to re-verify state flags if the flag is already maintained by the primary data-collection path. Such redundant checks can be replaced with a comment noting where the flag is set.
  3. When a component is accessed by multiple threads, its operations should be protected by a mutex to prevent data races.

Comment on lines +250 to +256
if (collected_by_collector_.empty()) {
reset_collector_shards();
}
if (collector_shards_merged_) {
collected_.clear();
collector_shards_merged_ = false;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Concurrency Hazard & Redundant/Incorrect State Reset

Multiple collector threads concurrently invoke on_buffer_collected (and thus process_dump_buffer) on the hot path.

  1. Lazy Initialization Race: If collected_by_collector_ is empty, multiple threads will concurrently call reset_collector_shards(), which modifies shared vectors without synchronization, causing a severe data race and memory corruption.
  2. State Reset Race & Bug: If collector_shards_merged_ is true, multiple threads will concurrently clear collected_ and reset collector_shards_merged_ to false. Furthermore, this reset is incomplete because it does not clear collected_by_collector_, meaning old records would still be retained and duplicated upon the next merge.

Since reset_collector_shards() is already called during initialize(), these vectors are guaranteed to be initialized and sized correctly before any profiling starts. These checks are redundant, incorrect, and introduce dangerous non-thread-safe code paths.

// collected_by_collector_ is guaranteed to be initialized by initialize()
References
  1. Do not unconditionally reset global synchronization or coordination states in a stale or null-state branch of a multi-threaded coordination protocol. Only release the thread's own election lock to prevent corrupting concurrently or subsequently initiated runs.
  2. Avoid redundant scans or loops to re-verify state flags if the flag is already maintained by the primary data-collection path. Such redundant checks can be replaced with a comment noting where the flag is set.
  3. When a component is accessed by multiple threads, its operations should be protected by a mutex to prevent data races.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant