1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
//! This example discusses things to consider when using and setting up dial9 in production
//!
//! It also provides an opinionated set of knobs and environment variables to configure your application.
//!
//! Many applications can run with dial9 enabled all the time. Some applications will have worse performance, especially applications that perform
//! a very small amount of "useful work" per poll. Applications that use an extremely large number of worker threads will experience higher memory usage.
//! For an average production service, Dial9 produces 50-100GB / day.
//!
//! Since dial9 is recording an event on each poll (~50ns), if your polls are very short then this fix cost of overhead will impact your application performance.
//!
//! ### Enabling and disabling
//!
//! - Attaching a runtime to a disabled recorder ([`recorder_disabled`](dial9::recorder_disabled)) in the config
//! function produces a pass-through: a plain, unmodified tokio runtime with zero dial9 overhead.
//! [`Dial9TokioHandle::current()`](dial9::Dial9TokioHandle::current) returns an inert
//! handle, and `handle.spawn` falls through to `tokio::spawn`, so application code does not need branches.
//! - Alternatively, you can install dial9 but leave recording disabled at runtime via the handle's
//! [`disable()`](dial9::Dial9Handle::disable). The runtime hooks are installed
//! but all event writes are no-ops behind a relaxed atomic read. This has slightly more overhead than
//! a disabled recorder but lets a background task flip
//! recording on from dynamic configuration later. It is a larger surface area of code, so it is higher risk.
//!
//! > Note! dial9 must be created _before_ your async runtime. dial9 relies on installing itself into the runtime
//! > telemetry hooks to produce Tokio events.
//!
//! ### The overhead of running dial9
//!
//! 1. Dial9 allocates a 1MB buffer for each thread that records events. If you are recording events from a huge number of threads, this can bloat memory.
//! 2. When Tokio telemetry is enabled, 2 dial9 events will be emitted by every poll. If your poll times are extremely short and your application is CPU bound then this overhead can be significant.
//!
//! Dial9 has many possible components you can enable. The more components, the more data you will produce and the more overhead your application will have.
//!
//! #### CPU Profiling
//! Dial9 has two types of CPU profiling available:
//! 1. CPU profiling (this is what you would normally consider CPU profiling): Dial9 is sampling stack traces that are running on the CPU.
//! 2. Schedule Profiling: dial9 subscribes to the sched-switch linux kernel event and can capture a stack trace when your application is descheduled by the kernel. In order
//! to subscribe to these events, dial9 must open one perf fd per worker thread.
//!
//! #### Tokio Telemetry
//! The basic Tokio telemetry will install a few hooks:
//! 1. `on_before_poll` and `on_after_poll` callbacks: This have a nanosecond level of overhead on each poll, just from the dynamic dispatch.
//! 2. `on_worker_park/unpark`: When these events happen, dial9 reads a few kernel APIs (~1us) to help to understand how Tokio is interacting with the OS.
//!
//! When you use dial9's spawn method, your futures are wrapped in a future that tracks `wake` events. This is done by instrumenting the waker. This is optional but
//! allows you to understand scheduling delay of the tasks you are running.
//!
//! #### Tracing
//! Dial9 can capture tracing spans through
//! `dial9_utils::tracing_layer::Dial9TracingLayer`. On the scale of tracing,
//! this is fairly low overhead, but deeply nested spans can produce a huge
//! amount of data. We recommend using a very fine-grained filter.
//!
//! ### Metrics
//!
//! Dial9 emits operational metrics about its own internals via a pluggable
//! [`metrique::writer::BoxEntrySink`]. These tell you how the trace pipeline
//! is performing (not application metrics). Wire up a sink with
//! [`RecorderBuilder::metrics_sink`](dial9::RecorderBuilder::metrics_sink) on the
//! recorder, before you build it. If no sink is provided, metrics are discarded.
//!
//! #### Metrics emitted
//!
//! Dial9 emits three metric entries:
//!
//! - **Flush**: each flush cycle (~30 s) and on shutdown.
//! `EventCount`, `DroppedBatches`, `CpuFlushDuration` (µs),
//! `FlushDuration` (µs), `LastFlush`.
//!
//! - **TlDrain**: each thread-local buffer drain (~30 s).
//! `BuffersFlushed`, `BuffersLocked`, `BuffersSkippedBusy`,
//! `EventsFlushed`, `DeadPruned`, `Duration` (µs).
//!
//! - **ProcessSegment**: per sealed segment processed by the background worker.
//! `TotalTime` (ms), `Success`/`Failure`, `SegmentIndex`,
//! `UncompressedSize`/`CompressedSize` (bytes), plus per-stage keys
//! like `Gzip.Time`, `S3Upload.Success`.
//!
//! #### Example: emit metrics to stderr
//!
//! ```rust,no_run
//! use metrique::local::{LocalFormat, OutputStyle};
//! use metrique::writer::format::FormatExt;
//! use metrique::writer::sink::FlushImmediatelyBuilder;
//!
//! let metrics_sink = FlushImmediatelyBuilder::new().build_boxed(
//! LocalFormat::new(OutputStyle::Pretty)
//! .output_to_makewriter(|| std::io::stderr().lock()),
//! );
//! ```
//!
//! Then set it on the recorder when you build it:
//!
//! ```rust,ignore
//! dial9::recorder(writer)
//! .metrics_sink(metrics_sink)
//! .build()
//! ```
//!
//! In a test or local-dev scenario you can use the test utilities from
//! `metrique::writer::test_util` to capture and inspect entries programmatically.
//!
//! The rest of this example shows an opinionated way to wire dial9
//! so it can be enabled and tuned with CLI flags or environment variables
//! (via [`clap`]). The same binary can then run in dev, staging, and prod
//! with different tracing behavior, and tooling (CDK, Docker, k8s, etc.)
//! can flip knobs without a rebuild.
//!
//! ### Segment metadata
//!
//! Static build and deployment context belongs in segment metadata so it is
//! available when any rotated segment is loaded independently. This example
//! records the compiled application's Cargo package version in every segment.
//!
//! ### Getting Useful Data
//!
//! To get the most use out of dial9, you need application-specific events in your traces to make sense of your data. The best way to do this is to emit some sort
//! of request id into your dial9 traces. There are a couple of ways to do this:
//!
//! 1. Use `dial9_utils::tracing_layer::Dial9TracingLayer`, which allows dial9
//! to capture tracing spans directly. Typically, you will select a narrow
//! set of top-level spans to track.
//! 2. Emit an event when your request starts and when your request stops. Because these should _normally_ always be on the same Tokio Task, we can
//! correlate post-hoc to figure exactly which polls belonged to which requests.
//!
//! # Configuration (CLI flags / environment variables)
//!
//! Every option can be set as a `--flag` or via its environment variable.
//! Run with `--help` for full usage.
//!
//! | Name | Default | Meaning |
//! | --------------------------------- | ------------------------------- | ------------------------------------------------------------- |
//! | `DIAL9_ENABLED` | `false` | Master switch. `true`/`false` only (Rust `bool::from_str`). |
//! | `DIAL9_TRACE_DIR` | `/tmp/dial9-traces` | Directory to write rotated trace segments into. |
//! | `DIAL9_ROTATION_SECS` | `60` | Rotation period in seconds, measured monotonically from writer start. |
//! | `DIAL9_MAX_DISK_USAGE_MB` | `1024` | Upper bound on total on-disk bytes (old files evicted). |
//! | `DIAL9_S3_BUCKET` | unset / empty | When set, sealed segments are gzip-uploaded to this bucket. |
//! | `DIAL9_SERVICE_NAME` | binary name | Service name used in the S3 key layout (required with S3). |
//! | `DIAL9_CPU_PROFILE_ENABLED` | `true` on Linux, `false` else | Enables `perf_event_open`-based CPU sampling. |
//! | `DIAL9_CPU_SAMPLE_HZ` | `99` | Sampling frequency for CPU profiling. |
//! | `DIAL9_SCHEDULE_PROFILE_ENABLED` | `true` on Linux, `false` else | Enables per-worker scheduler event capture (context switches).|
//!
//! # Invalid configuration
//!
//! Invalid operator input for the knobs above (unknown boolean, non-numeric
//! duration, etc.) is caught by `clap` and exits with a diagnostic, as it
//! would for any misconfigured CLI tool. Invalid _dial9_ configuration
//! (writer I/O failure, unwritable trace directory, etc.) is caught by the
//! config function, which logs the error and returns a disabled recorder
//! ([`recorder_disabled`](dial9::recorder_disabled)), attaching a plain
//! tokio runtime with telemetry disabled. A bad trace config must never take
//! down prod.
//!
//! # Running the example
//!
//! ```sh
//! # plain run: telemetry disabled
//! cargo run --example production_use
//!
//! # basic local tracing (env var)
//! DIAL9_ENABLED=true cargo run --example production_use
//!
//! # basic local tracing (CLI flag)
//! cargo run --example production_use -- --enabled
//!
//! # with CPU profiling + schedule events (Linux, requires feature flag)
//! DIAL9_ENABLED=true \
//! cargo run --features cpu-profiling --example production_use
//!
//! # with S3 upload (requires feature flag and AWS creds in env)
//! cargo run --features worker-s3 --example production_use -- \
//! --enabled --s3-bucket my-trace-bucket --service-name my-service
//! ```
use io;
use Duration;
use Parser;
use ;
use ;
use FormatExt;
use FlushImmediatelyBuilder;
const LINUX: bool = cfg!;
/// Opinionated configuration for a production dial9 deployment.
///
/// All fields can be set via environment variables (shown in help) or CLI flags.
/// Emit dial9 operational metrics (Flush, TlDrain, ProcessSegment) to stderr.
/// In production, you would typically pass the `ServiceMetrics` sink that
/// your application already uses.
/// Plug the perf `Source`s selected by the operator onto the recorder builder,
/// before it is built.
/// Build the [`Recorder`] [`my_config`] attaches a runtime to.
///
/// `--enabled false` and any writer I/O failure (unwritable `trace_dir`,
/// zero-sized budget, etc.) return a disabled recorder ([`recorder_disabled`]),
/// which attaches a plain tokio runtime. In both
/// cases `Dial9TokioHandle::current()` returns an inert handle and
/// `handle.spawn` delegates to `tokio::spawn`, so application code does not
/// need to branch on whether dial9 is running.
///
/// [`recorder_disabled`]: dial9::recorder_disabled
/// Complain at startup when the operator asked for something a feature flag
/// disables, so silent misconfiguration doesn't go unnoticed.
async
async