Skip to content

Commit 400370d

Browse files
committed
Measure processor output backpressure
1 parent 77995b7 commit 400370d

4 files changed

Lines changed: 25 additions & 3 deletions

File tree

‎asap-precompute-rs/src/bin/asap_stream_bench.rs‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ struct Stats {
109109
completed_windows: u64,
110110
generator_paused: bool,
111111
otlp_payload_bytes_sent: u64,
112+
processor_wall_ns: u64,
113+
output_send_wall_ns: u64,
114+
output_messages: u64,
112115
window_latency_ms: BTreeMap<u64, u64>,
113116
}
114117
struct Shared {
@@ -228,6 +231,7 @@ impl processor::Processor<OtapPdata> for StreamProcessor {
228231
effects: &mut processor::EffectHandler<OtapPdata>,
229232
) -> Result<(), Error> {
230233
if let Message::PData(mut pdata) = message {
234+
let process_started = Instant::now();
231235
let outputs = if let Some(normalizer) = &mut self.normalizer {
232236
let count = pdata.num_items();
233237
{
@@ -252,9 +256,16 @@ impl processor::Processor<OtapPdata> for StreamProcessor {
252256
error,
253257
source_detail: String::new(),
254258
})?;
259+
let process_wall_ns = process_started.elapsed().as_nanos() as u64;
260+
let output_messages = outputs.len() as u64;
261+
let send_started = Instant::now();
255262
for output in outputs {
256263
effects.send_message_with_source_node(output).await?;
257264
}
265+
let mut stats = shared().stats.lock().unwrap();
266+
stats.processor_wall_ns += process_wall_ns;
267+
stats.output_send_wall_ns += send_started.elapsed().as_nanos() as u64;
268+
stats.output_messages += output_messages;
258269
}
259270
Ok(())
260271
}

‎benchmarks/PROFILING.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@ used by other threads cannot inflate a computation scope. Categories are:
3434
| `processor_bookkeeping` | Remaining synchronous processor work: benchmark labels/validation, window maps, object destruction, pass-through output allocation |
3535
| `outside_scopes` | Process CPU minus the above: native OTLP receiver/exporter, runtime, channels, native transport conversion, allocator work outside the processor, control listener and probe overhead |
3636

37+
Every run also records `processor_wall_ns`, `output_send_wall_ns`, and
38+
`output_messages` per role. These counters distinguish synchronous processor
39+
work from time awaiting capacity in the processor-to-exporter path. They are
40+
wall-clock diagnostics and must not be added to the exclusive CPU categories.
41+
If processor wall time plus output-send wall time approaches the observation
42+
interval while CPU remains below one core, output backpressure explains the
43+
gap rather than unused compute capacity.
44+
3745
**The residual is not a measurement of pure OTAP scheduling overhead.** Likewise,
3846
`pdata_codec` is not all OTAP engine time: it also includes the ASAP adapter's
3947
per-point allocation and labels. Use the call-stack profiles to inspect owners.

‎benchmarks/analyze-profile.py‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ def family(frames):
8585

8686
def analyze_stacks(root):
8787
rows = []
88-
for path in sorted(root.glob('*mbit/*/*/perf-stacks.txt')):
88+
for path in sorted(root.rglob('perf-stacks.txt')):
8989
parsed = list(samples(path))
9090
mapping = demangle_names({s for _, frames in parsed for s, _ in frames})
9191
roles = {int(v): k for k, v in json.loads((path.parent / 'profile-pids.json').read_text()).items()}
@@ -119,7 +119,7 @@ def main():
119119
parser.add_argument('--control-root', type=Path)
120120
args = parser.parse_args()
121121
reports = []
122-
for path in sorted(args.root.glob('*mbit/*/*/result.json')):
122+
for path in sorted(args.root.rglob('result.json')):
123123
report = json.loads(path.read_text())
124124
if 'cpu_profile' not in report:
125125
raise ValueError(f'{path}: requires --profile-cpu or --account-cpu')
@@ -170,7 +170,8 @@ def main():
170170
if args.control_root:
171171
comparison = []
172172
for row in summary:
173-
runs = [json.loads(p.read_text()) for p in args.control_root.glob(f'*mbit/{row["scenario"]}/*/result.json')]
173+
runs = [json.loads(p.read_text()) for p in args.control_root.rglob('result.json')
174+
if json.loads(p.read_text()).get('scenario') == row['scenario']]
174175
if not runs:
175176
continue
176177
inputs = sum(r['cpu_profile']['completed_input_signals'] for r in runs)

‎benchmarks/run-streaming.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,8 @@ def observe(self):
333333
"lifetime_peak_rss_mib": end["process_peak_rss_kib"] / 1024,
334334
"data_rx_bytes": rx, "data_tx_bytes": tx, "data_rx_mbit_s": rx * 8 / elapsed / 1e6,
335335
"data_tx_mbit_s": tx * 8 / elapsed / 1e6}
336+
for key in ("processor_wall_ns", "output_send_wall_ns", "output_messages"):
337+
resources[role][key] = end["stats"].get(key, 0) - start["stats"].get(key, 0)
336338
if self.args.profile_cpu:
337339
scopes = {key: (value - start["profile_cpu_ns"][key]) / 1e9 for key, value in end["profile_cpu_ns"].items()}
338340
resources[role]["scoped_cpu_seconds"] = scopes

0 commit comments

Comments
 (0)