[countersyncd]: Optimize single-flight OTLP export and benchmark delivery - #4883
[countersyncd]: Optimize single-flight OTLP export and benchmark delivery#488350n1c-rnsft wants to merge 13 commits into
Conversation
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
/azp run |
Follow-up: real Go Collector concurrency and profilingPushed The single-flight target is still unmet. One client core with eight in-flight requests now exceeds it:
20-second Go CPU profile at default GC: request protobuf Unmarshal ~68.8% cumulative, mallocgc ~45.1% cumulative, gcDrain ~19.3% cumulative (overlapping call paths, not additive). Allocation profile attributed ~90% of allocated bytes to the metric decode tree. Tuned run still spends substantial CPU on per-point/attribute allocation. It used ~5 Collector CPU equivalents while delivering ~7M/s; final RSS snapshot ~351.5MiB, not a peak. This is not a protocol theoretical limit. The tuned single-client-core path is approaching saturation: ~130-135ns/point CPU cost gives a conditional current-implementation budget of ~7.4-7.7M/s, not a throughput guarantee. Further large gains require reducing client CPU/copies and receiver allocation, or scaling/sharding. pprof evidence and commands are in Validation: 21 module/delivery tests passed (manual calibration ignored); concurrent-path tail/timer/partial rejection tests passed with N=8 (single-flight retry test excluded because benchmark concurrent mode intentionally fails fast). Go tests verify all ACKs/count deltas, not per-value checksums. Production remains single-flight without the benchmark feature; concurrency retry/error/ordering semantics remain draft work. No InfluxDB persistence or physical NIC throughput claimed. |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
Hi, there are workflow run(s) waiting for approval, you may be first-time contributor. I will notify maintainers to help approve once PR is approved. Thanks! ---Powered by SONiC BuildBot
|
|
/azp run |
Configurable ordered worker pool — 582be6dAdded startup configuration to the daemon:
This is an ordered pool, not the prior unordered benchmark experiment. Concurrency is threads × independent series lanes; each lane uses the real actor's single-flight export/retry path. Every series hashes to one lane. Startup validation bounds threads/lanes/queues and validates CPU lists. Normal close drains tails/ACKs and joins worker threads; worker error/panic is detected even with idle upstream, and sibling workers are cancelled. Defaults remain one worker / one lane. No automatic live resizing: it would require a drain/ownership-migration protocol. Added Validation: 26 standalone Rust tests passed (one manual calibration ignored). New pool tests check exact decoded fields/duplicates/per-series order, retry ordering, per-lane/global concurrent request ceilings, overlapping lanes, bounds/CPU validation, empty shutdown, and idle-input errors. The four pool tests also passed on Linux with the unordered benchmark environment override set to 8, proving the ordered pool still uses single-flight lanes. Root Details and commands: |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
Canonical SAI names + refreshed matrix (7a28d92)Known type/stat pairs now export real SAI names: Metric.name= 52 standalone tests passed (one manual calibration ignored); Linux tests also passed before adding the final workload-schema assertion. Tests cover all four name families, unknown/extensions, fast encoder vs message conversion, and real RPC attribute/order/retry behavior. All benchmarks include real routing/clone/channel cost; router has one additional core. Refreshed input has 100 real port stat names across 500 object/stat series, not 500 distinct metric types. Fixed receiver four physical cores, 10K batch, native Rust/mimalloc, localhost/no TLS/no compression. Raw gRPC receive-only, two 40M-point trials, Mpoints/s ranges:
Long confirmation (200M/trial, two trials): 3x4 13.445/13.026M/s. Same-input old numeric baseline 17.139/16.710M/s, ~22% reduction. Payload ~91 -> ~141 bytes/point (~55% increase). Do not attribute this solely to name lookup; it is cached. Raw server verifies complete requests/bytes, not decoded values. Actual Go Collector core v0.123.0, four cores/GOGC=500, 40M/trial confirmation: 3x2 4.755/4.765M/s, 3x4 4.673/4.728M/s. Each pair accepted 80M points, zero refused. Full 9-combination Collector short-test matrix and reproduction are in Schema migration requires downstream measurement/label query updates. Historical performance docs are explicitly marked as old numeric schema. |
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
Shared metadata + unified iterator integration (b3b0aeb)Implemented real IPFIX output as shared immutable template metadata plus numeric values. Actual IPFIX performance did not regress materially in measured workloads. Same-input old/new runs on zegan-dev-vm, Rust 1.90/native/mimalloc, one parser/consumer core:
These measure the actual actor with parsing, full output iteration/checksum and destruction, using 16 pre-generated IPFIX payloads. They are not wire ingest or persistence rates. Small-record differences are within observed noise, not a formal no-regression proof. Actual IPFIX -> OTel -> raw gRPC integration: old 15.678/15.853/16.316M/s, new 20.393/20.660/21.155M/s (~30% aggregate gain), three 100M-point trials. Parser/router CPU0, three sender CPUs2/4/6, two lanes/worker, receiver CPUs8/10/12/14. Every run: 10,000 RPCs and 14,062,851,000 payload bytes. A decoded end-to-end test verifies real IPFIX/shared fan-out/OTel fields, values and order; raw performance sink itself only counts complete requests/bytes. Validation: 124 standalone tests pass, one manual calibration ignored, including actual IPFIX/StatsReporter tests, mixed-view projection/splitting, old-generation retention, slot eviction, real decoded fan-out delivery and retry/order tests. CounterDB iteration changed and its shared-input assertion added, but native SONiC/Redis tests were not run. Full native build remains a draft integration requirement. See SHARED_BATCHES.md for exact scope, baseline reproduction, failed early benchmark runs excluded due to premature template-channel close, and cache/memory review points. Numeric samples still copy during lane gathering and sample append; this is not the isolated C prototype's fully exclusive Vec transfer. No 50M/s, physical NIC or InfluxDB claim. |
Extract the IPFIX shared-template representation for independent review, retaining a lazy legacy slice adapter and migrating CounterDB/StatsReporter iteration. No OTel code changes. Include isolated real-actor tests and old/shared/legacy-adapter performance comparisons. 90 standalone tests pass on Linux and Windows. Borrowed wide-record output improves about 3.25-3.86x; legacy projection regresses about 29-32%, so migration of remaining hot consumers is a required rollout review item. Native SONiC/Redis integration remains untested. Signed-off-by: Ze Gan <zegan@microsoft.com>
…e setup Remove legacy slice materialization and the standalone benchmark package. Migrate all consumers and assertions to borrowed stat views; cache canonical SAI type/stat names in shared metadata and access names on demand. Keep existing IPFIX Criterion benchmarks and add metadata registration timing. Validate 98 module tests and 11 existing helper integration tests via a temporary external harness. Long IPFIX name-cache A/B observed ~1.7% aggregate throughput decrease with overlapping run ranges; metadata grows 24 to 56 bytes per field and template setup adds ~8-10us/500 fields or ~0.14ms/8000 fields. Native SONiC/Redis validation remains pending. Signed-off-by: Ze Gan <zegan@microsoft.com>
Remove unused stat view APIs and replace full metadata scans with constant-time consecutive-template reuse. Cover thousands of one-counter template Sets in a single message, mixed batch splitting, and scaling benchmarks. Signed-off-by: Ze Gan <zegan@microsoft.com>
Signed-off-by: Ze Gan <zegan@microsoft.com>
…enchmarks Cache series metadata and encode raw Gauge samples into standard OTLP protobuf buffers while retaining all attributes, timestamps and values. Reuse immutable payloads on retries, remove the already-drained shutdown delay, and validate partial responses. Add standalone real-actor/Tonic decode tests and reproducible local measurements. The 5M/s target (4M/s floor) is not met: system-allocator delivery is 0.54-0.72M/s, optional benchmark mimalloc reaches 1.04M/s, and receiver-only Prost decode/drop is 2.2-2.3M/s on the test Windows VM. Keep the PR draft pending target-platform profiling. Signed-off-by: Ze Gan <zegan@microsoft.com>
…unner Keep production single-flight; add feature-gated concurrency experiments with bounded tasks and final ACK drain. Document actual Linux Go Collector results: 5.665-5.781M points/s with eight in-flight requests. Include external endpoint support and pprof/count-verification runner; concurrent retries and ordering remain draft work. Signed-off-by: Ze Gan <zegan@microsoft.com>
Measure Linux client CPU within ACK timing intervals and capture Collector CPU/allocation profiles. Document decode/allocation hotspots, GOGC/core-affinity experiments, and two 200M-point runs at 6.908/6.993M points per second. Distinguish observed saturation from protocol limits and retain draft production-concurrency caveats. Signed-off-by: Ze Gan <zegan@microsoft.com>
Partition the same 500 series across pinned actor/runtime workers with independent channels and bounded per-worker concurrency. Measure total ACK completion and validate actual Go Collector counts. Two sender cores plus six Collector cores achieved 8.089/8.329M points/s in two 200M-point trials; document receiver saturation and exclude pre-sharding/input generation from throughput claims. Signed-off-by: Ze Gan <zegan@microsoft.com>
…anes Add a bounded worker pool with stable series routing, one awaited request per lane, optional CPU affinity, tail draining and worker failure cancellation. Expose startup thread/lane/queue/CPU parameters without live autoscaling. Include real routing in the new pool benchmark and tests for order, duplicates, retries, limits and idle error reporting. Standalone tests pass; Linux raw receive-only 200M-point runs reach 16.827/17.042M points/s with three sending threads plus routing. Actual Go Collector reaches 5.423/5.406M points/s with four receiving cores. Preserve measurement boundaries and include raw gRPC receiver POC and ordered pre-sharded experiments. Signed-off-by: Ze Gan <zegan@microsoft.com>
Use existing object/stat enum tables for metric names and sai_type/sai_stat attributes, cache encoded names per series, and retain collision-free numeric fallbacks for unknown pairs. Update message and wire equivalence tests and document the schema migration. Refresh ordered pool benchmarks with 100 known port stats across 500 series, thread/in-flight matrices, and same-input numeric baseline. Raw receive-only 3x4 long trials reach 13.026-13.445M/s; actual four-core Collector confirms 4.673-4.765M/s. Name expansion increases per-point payload about 55%. Standalone suite: 52 tests pass, one manual calibration ignored. Signed-off-by: Ze Gan <zegan@microsoft.com>
Remove the intermediate expanded request copy. Retain immutable timestamp/value samples and cached attributes through each unary RPC, preallocate the exact framed encoding size, and recover sample storage only when transport ownership is exclusive. Preserve schema, series routing and retry payloads. Include only direct encoding and required ownership/tests; omit template, codec-mode, sample-preallocation and identity-cache experiments. Standalone suite: 56 passed, one ignored. Final Linux 3-worker/2-lane raw receive-only trials: 16.507/16.587M points/s; actual Go Collector: 5.200/5.099M points/s. Signed-off-by: Ze Gan <zegan@microsoft.com>
Store immutable template identities once and append numeric samples. Add allocation-free borrowed record/stat views and a lazy legacy slice adapter; migrate hot consumers while preserving original timestamps, duplicate samples and template lifetimes. Cache per-generation OTel routes and local series slots without exposing stale handles. Add real IPFIX baseline/shared benchmark and decoded fan-out delivery tests. Wide-record parser/output throughput improves about 3.2-3.9x; two-counter workload is unchanged within observed variation. Real IPFIX-to-OTel raw-gRPC improves from 15.7-16.3M to 20.4-21.2M points/s. 124 standalone tests pass; native CounterDB/SONiC integration remains untested. Signed-off-by: Ze Gan <zegan@microsoft.com>
Signed-off-by: Ze Gan <zegan@microsoft.com>
b3b0aeb to
01348bc
Compare
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
Rebase and cross-host confirmation
Rebased onto #4884 at
76753cb; current integration commit01348bc. The PR base remains master until the dependency is merged, so shared IPFIX changes are still included in the diff.Rebased shared IPFIX pipeline, client on zegan-dev-vm and raw gRPC server on zegan-vm0, both unpinned with all CPUs available, 500 counters/record and 10K counters/export:
Each trial contains 200M counters; each three-trial group delivered 60,000 complete RPCs. The raw sink validates complete request counts, not per-point contents. Timing includes parsing, routing, encoding and final ACKs. A separate same-direction TCP check reached 11.99/11.90/11.91Gbps with 1/4/8 connections respectively, consistent with a roughly 12Gbps path limit despite the virtual NIC reporting 100Gbps. These cross-host results are network-limited and do not establish the sender's CPU ceiling. Experimental large-frame servers and encode-only diagnostic bypasses are not production changes.
Summary
Optimize OTLP Gauge export with direct protobuf encoding, a configurable ordered worker pool, and shared IPFIX template metadata.
(object_name, type_id, stat_id)series to one stable lane. Each lane sends one request at a time, including retries; independent lanes can overlap requests.object_name,sai_type, andsai_stat. Preserve individual timestamps, values, duplicate samples, and per-series input order.This remains a draft. The current measurements do not establish 50M points/s, physical-network capacity, or persistent-storage throughput.
Worker configuration
--otel-worker-threads--otel-in-flight-per-worker--otel-worker-cpus--otel-worker-queue-capacity--otel-max-counters-per-export--otel-flush-timeout-msThe router runs on the daemon runtime in addition to sender threads. Maximum concurrent requests are bounded by workers times lanes, with one request per lane. Defaults retain one worker and one lane; the entire pool is not globally single-flight when configured with multiple lanes. Pool size is fixed at startup.
Benchmark matrix (measured before the API rebase)
The following full worker/lane matrix was measured at
b3b0aeb, before rebasing onto #4884. It is retained as measured evidence, not presented as a fresh matrix for the rebased HEAD. Subsequent cross-host measurements of the rebased implementation are summarized below.Common setup and timing
otel-pool-benchpre-generates owned SAI batches outside timing. Timing includes live routing, hashing, cloning, channels, encoding, worker startup/shutdown, and all final ACKs. It does not include IPFIX parsing or exercise the shared-IPFIX input fast path; that path has a separate integration table below.Raw gRPC receive-only sink
The grpc-go sink counts complete requests and payload bytes without decoding protobuf or persisting data. Every configuration delivered 8,000 complete requests across its two trials. Sample semantics are checked separately by decoding delivery tests.
Actual Go OpenTelemetry Collector
Collector core v0.123.0, OTLP receiver, nop exporter,
GOMAXPROCS=4,GOGC=500. This path performs real protobuf decoding. Each configuration accepted exactly 80 million points across its two trials with zero refused points. Accepted counts do not validate every individual value and do not imply durable storage.The 3-worker/2-lane configuration delivered the highest aggregate throughput in both matrices: about 15.60M/s to the raw sink and 5.09M/s to the Collector. More lanes did not consistently improve throughput. The default 1-worker/1-lane configuration remains around 1.3M/s against this Collector, so a 5M/s single-flight target is not achieved. Receiver decode/allocation and scheduling costs matter; raw-sink throughput must not be presented as Collector throughput.
IPFIX and shared-input integration benchmarks
The following recorded A/B runs isolate the addition of shared metadata to the already optimized, named, direct-encoding pipeline. Their baseline is the pre-shared implementation inside this PR, not upstream before the entire PR. They are separate from the freshly rerun owned-input worker matrix above.
Actual IPFIX actor: parsing, traversal/checksum, and destruction
Producer, parser, and consumer on CPU 0; same compiler/allocator configuration; 16 pre-generated valid IPFIX payloads; template setup and readiness probe excluded. Three trials per version. Throughput in M counters/s.
Actual IPFIX -> ordered OTel pool -> raw gRPC
Parser/router on CPU 0, three sender workers on CPUs 2/4/6, two lanes each, receiver on CPUs 8/10/12/14; 500 counters/IPFIX record, 10K exports, 100 million points/trial. Parsing and live routing are included; input generation is excluded and final ACKs are included.
Each trial delivered 10,000 complete requests and 14,062,851,000 protobuf payload bytes. The approximately 30% improvement applies to this shared-input integration comparison. The raw performance sink does not decode values; a separate real IPFIX/shared fan-out/OTel/Tonic test checks decoded fields, timestamps, values, duplicates, and per-series order. An equivalent shared-IPFIX-to-Go-Collector throughput matrix has not been measured.
Correctness, compatibility, and review scope
SAIStatsBatch::iter()API from [countersyncd]: Share IPFIX output metadata with borrowed batch views #4884. Therecords()alias and lazy owned projection are removed. Shared names are cached at template construction and reused by the OTel encoder.sai_type_id/sai_stat_idto canonical SAI names andsai_type/sai_stat. Existing Port, Queue, BufferPool, and IngressPriorityGroup mappings are used; unknown IDs retain explicit ID-bearing fallback names. Downstream queries need migration.RUSTFLAGS=-Dwarnings:cargo check --locked -p countersyncd --bins --tests --benchesandcargo test --locked -p countersyncd. Results: 187 library tests, 198 binary tests, and 18 integration/helper tests passed; manual encoding calibration ignored in each unit target. The standalone suite passed 127 tests with one calibration ignored. Hardware HFT testing has not been established for this OTel branch.Reproduction and evidence
Build the current pool benchmark:
CARGO_TARGET_DIR=/tmp/otel-pool-target RUSTFLAGS='-C target-cpu=native' \ cargo +1.90.0 build --release --locked --features mimalloc \ --bin otel-pool-bench \ --manifest-path crates/countersyncd/benches/otel-standalone/Cargo.tomlRun the raw and Collector runners for each sender CPU list
2,2,4, and2,4,6, with lanes1 2 4:Use dedicated ports as configured by the runners. Current matrix JSON, request counts, Collector accepted/refused counters, and process metrics are retained on zegan-dev-vm under
/tmp/pr4883-description-head/results/.Repository references: worker configuration, shared batches and integration A/B, direct encoding, and schema migration. Performance tables in the older documents/comments describe their respective implementation stages; the current-head worker matrices are the ones above.