diff --git a/docs/design.md b/docs/design.md index 17ff38af6..f13f1c8b7 100644 --- a/docs/design.md +++ b/docs/design.md @@ -1,10 +1,107 @@ # Datadog C++ Tracer Design -The primary purpose of this guide is to describe salient features of the Datadog C++ Tracer's -design. +This guide describes salient features of the Datadog C++ Tracer's design. ## Architecture +### Overview + +```mermaid +--- +title: Datadog C++ Tracer Architecture Overview +--- +classDiagram + class Span { + create_child() Span + inject(writer) + } + Span "0..n" o-- TraceSegment + Span "1" o-- "1" SpanData + + class SpanData + + class TraceSegment { + mutex_ 🔒 + } + TraceSegment *-- "0..n" SpanData + TraceSegment o-- Collector + TraceSegment o-- SpanSampler + TraceSegment o-- ConfigManager + TraceSegment o-- TraceSampler + + class Tracer { + create_span(config) Span + extract_span(reader) Span + } + Tracer "1..n" o-- Collector + Tracer "1" o-- "1" SpanSampler + Tracer "1" o-- "1" ConfigManager + Tracer -- FinalizedTracerConfig + + class FinalizedTracerConfig { + finalize_config(TracerConfig) FinalizedTracerConfig + } + + class SpanSampler { + mutex_ 🔒 + } + + class Collector { + send(spans) + } + + class DatadogAgent { + mutex_ 🔒 + flush() + } + DatadogAgent ..|> Collector + DatadogAgent o-- HTTPClient + DatadogAgent o-- EventScheduler + + class ConfigManager { + mutex_ 🔒 + } + ConfigManager o-- TraceSampler + + class TraceSampler { + mutex_ 🔒 + } + + class HTTPClient { + post() + } + + class Curl + Curl ..|> HTTPClient + + class EventScheduler + + class ThreadedEventScheduler { + mutex_ 🔒 + } + ThreadedEventScheduler ..|> EventScheduler + + class Logger { + log_startup() + log_error() + } + + class NullLogger + NullLogger ..|> Logger + + class CerrLogger + CerrLogger ..|> Logger +``` + +Intended usage is: + +1. Create a `TracerConfig`. +2. Use the `TracerConfig` to create a `Tracer`. +3. Use the `Tracer` to create and/or extract local root `Span`s. +4. Use `Span` to create children and/or inject context. +5. Use a `Span`'s `TraceSegment` to perform trace-wide operations. +6. When all `Span`s in a `TraceSegment` are finished, the segment is sent to the `Collector`. + ### Span [Span](../include/datadog/span.h) is the component with which users will interact the most. Each @@ -259,38 +356,7 @@ uses a different implementation, [AgentHTTPClient](https://github.com/envoyproxy/envoy/blob/main/source/extensions/tracers/datadog/agent_http_client.h), which uses Envoy's built-in HTTP facilities. libcurl is not involved at all. -### Logical Component Relationships - -- Vertices are components. -- Edges are ownership relationships between components. Each edge is labeled by the kind of pointer - that is used to implement the relationship. -- Components with a padlock are protected by a mutex. - -```mermaid ---- -title: Components Relationships -config: - layout: elk ---- -graph LR; - Tracer(Tracer) & TraceSegment("TraceSegment 🔒")-- shared -->Collector("Collector 🔒") & SpanSampler("SpanSampler 🔒") - Tracer & TraceSegment-- shared -->ConfigManager("ConfigManager 🔒") - ConfigManager & TraceSegment-- shared -->TraceSampler("TraceSampler 🔒") - TraceSegment-- "`**unique**`" -->SpanData(SpanData) - Span(Span)-- shared -->TraceSegment - Span-- "`**raw**`" -->SpanData -``` - -Intended usage is: - -1. Create a `TracerConfig`. -2. Use the `TracerConfig` to create a `Tracer`. -3. Use the `Tracer` to create and/or extract local root `Span`s. -4. Use `Span` to create children and/or inject context. -5. Use a `Span`'s `TraceSegment` to perform trace-wide operations. -6. When all `Span`s in a `TraceSegment` are finished, the segment is sent to the `Collector`. - -## EventScheduler +### EventScheduler `DatadogAgent` uses an `EventScheduler` to schedule its recurring work, at fixed intervals. @@ -312,7 +378,9 @@ also uses a different implementation, [EventScheduler](https://github.com/envoyproxy/envoy/blob/main/source/extensions/tracers/datadog/event_scheduler.h), which uses Envoy's built-in event dispatch facilities. -## Configuration +## Operational Aspects + +### Configuration This library encodes configuration validation into the type system (see ["Parse, don't validate" by Alexis King](https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate)). Invalid states @@ -346,7 +414,7 @@ This static validation happens once, at construction. `ConfigManager`, which is separately allows some configuration to change afterward, via a Remote Configuration update, rather than through `finalize_config()`. This path is also validated, by a different parser. -## Error Handling +### Error Handling Most error scenarios within this library are enumerated by `enum Error::Code`, defined in [error.h](../include/datadog/error.h). The integer values of the enumerated `Error::Code`s are @@ -413,7 +481,7 @@ value when it succeeds. It behaves in the same way as `Expected`, except that `operator*()` are not defined. `Expected` is implemented in terms of `std::optional`, but inverts the value of `explicit operator bool`. -## Logging +### Logging This library has a logging interface alongside its `Expected`/`Error` reporting, because the default `HTTPClient`/`EventScheduler` implementations do work on background threads where errors can occur diff --git a/src/datadog/limiter.h b/src/datadog/limiter.h index ddddb5efb..b0cbb95a4 100644 --- a/src/datadog/limiter.h +++ b/src/datadog/limiter.h @@ -1,12 +1,10 @@ #pragma once -// This component provides a `class`, `Limiter`, that is an implementation of -// the [token bucket][1] rate limiter. +// The `Limiter` class is an implementation of the [token +// bucket](https://en.wikipedia.org/wiki/Token_bucket) rate limiter. // // `Limiter` is used by the `TraceSampler` and the `SpanSampler` to enforce // their respective `max_per_second` configuration parameters. -// -// [1]: https://en.wikipedia.org/wiki/Token_bucket #include #include diff --git a/src/datadog/random.cpp b/src/datadog/random.cpp index 78f52e65f..6d52df538 100644 --- a/src/datadog/random.cpp +++ b/src/datadog/random.cpp @@ -22,7 +22,7 @@ class Uint64Generator { // If a process links to this library and then calls `fork`, the // `generator_` in the parent and child processes will produce the exact // same sequence of values, which is bad. - // A subsequent call to `exec` would remedy this, but nginx in particular + // A subsequent call to `exec` would remedy this, but Nginx in particular // does not call `exec` after forking its worker processes. // So, we use `at_fork_in_child` to re-seed `generator_` in the child // process after `fork`. diff --git a/src/datadog/remote_config/remote_config.h b/src/datadog/remote_config/remote_config.h index b63a428be..380dda8dd 100644 --- a/src/datadog/remote_config/remote_config.h +++ b/src/datadog/remote_config/remote_config.h @@ -1,11 +1,11 @@ #pragma once // Remote Configuration is a Datadog capability that allows a user to remotely -// configure and change the behaviour of the tracing library. +// configure and change the behavior of the tracing library. // The current implementation is restricted to Application Performance // Monitoring features. // -// The `RemoteConfigurationManager` class implement the protocol to query, +// The `RemoteConfigurationManager` class implements the protocol to query, // process and verify configuration from a remote source. It is also // responsible for handling configuration updates received from a remote source // and maintains the state of applied configuration. diff --git a/src/datadog/telemetry/telemetry_impl.h b/src/datadog/telemetry/telemetry_impl.h index 59503565e..f65f92f0a 100644 --- a/src/datadog/telemetry/telemetry_impl.h +++ b/src/datadog/telemetry/telemetry_impl.h @@ -21,14 +21,13 @@ namespace datadog::telemetry { using MetricSnapshot = std::vector>; -/// The telemetry class is responsible for handling internal telemetry data to -/// track Datadog product usage. It _can_ collect and report logs and metrics. -/// -/// NOTE(@dmehala): The current implementation can lead a significant amount -/// of overhead if the mutext is highly disputed. Unless this is proven to be -/// indeed a bottleneck, I'll embrace KISS principle. However, in a future -/// iteration we could use multiple producer single consumer queue or -/// lock-free queue. +// The telemetry class is responsible for handling internal telemetry data to +// track Datadog product usage. It _can_ collect and report logs and metrics. +// +// The current implementation can lead a significant amount of overhead if the +// mutext is highly disputed. Unless this is proven to be a bottleneck, we keep +// this simple approach. However, in a future iteration we could use multiple +// producers single consumer queue or lock-free queue. class Telemetry final : public std::enable_shared_from_this { /// Configuration object containing the validated settings for telemetry FinalizedConfiguration config_;