mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-17 04:00:21 +00:00
Compare commits
32 Commits
copilot/re
...
pratik/ote
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
adeda255f0 | ||
|
|
26a85c764e | ||
|
|
dec68e3673 | ||
|
|
d2c7a00584 | ||
|
|
190b9470a4 | ||
|
|
4d5a71d327 | ||
|
|
34f41ea37f | ||
|
|
46dbc92b5f | ||
|
|
c3ccde3e39 | ||
|
|
d6450631bf | ||
|
|
c0272f1314 | ||
|
|
ede8a53a76 | ||
|
|
4f53291fe8 | ||
|
|
5598b0eac7 | ||
|
|
fe13359024 | ||
|
|
f5f13df5ff | ||
|
|
b46ee12a19 | ||
|
|
154d441ff2 | ||
|
|
e1163f7180 | ||
|
|
f3a095ab65 | ||
|
|
1fd971b78b | ||
|
|
d6c8dec451 | ||
|
|
30ecb32a6f | ||
|
|
a01b274352 | ||
|
|
193f5b39cb | ||
|
|
db8111ef7c | ||
|
|
913a4b794c | ||
|
|
accea17e9d | ||
|
|
c6fa00fbe3 | ||
|
|
bfb8f4f01a | ||
|
|
4b745a86b7 | ||
|
|
ddf894dcb0 |
@@ -65,6 +65,7 @@ words:
|
||||
- Btrfs
|
||||
- Buildx
|
||||
- canonicality
|
||||
- CGNAT
|
||||
- changespq
|
||||
- checkme
|
||||
- choco
|
||||
@@ -121,6 +122,8 @@ words:
|
||||
- fsanitize
|
||||
- funclets
|
||||
- Gamal
|
||||
- gantt
|
||||
- Gantt
|
||||
- gcov
|
||||
- gcovr
|
||||
- ghead
|
||||
@@ -167,12 +170,11 @@ words:
|
||||
- mathbunnyru
|
||||
- mcmodel
|
||||
- MEMORYSTATUSEX
|
||||
- MPTAMM
|
||||
- MPTDEX
|
||||
- Merkle
|
||||
- Metafuncton
|
||||
- misprediction
|
||||
- missingok
|
||||
- MPTAMM
|
||||
- mptbalance
|
||||
- MPTDEX
|
||||
- mptflags
|
||||
@@ -206,6 +208,7 @@ words:
|
||||
- nonxrp
|
||||
- noreplace
|
||||
- noripple
|
||||
- nostd
|
||||
- nostdinc
|
||||
- notifempty
|
||||
- nudb
|
||||
@@ -214,6 +217,7 @@ words:
|
||||
- Nyffenegger
|
||||
- onlatest
|
||||
- ostr
|
||||
- otelc
|
||||
- pargs
|
||||
- partitioner
|
||||
- paychan
|
||||
@@ -279,8 +283,6 @@ words:
|
||||
- sles
|
||||
- soci
|
||||
- socidb
|
||||
- sponsee
|
||||
- sponsees
|
||||
- SRPMS
|
||||
- sslws
|
||||
- statsd
|
||||
@@ -302,7 +304,7 @@ words:
|
||||
- takerpays
|
||||
- ters
|
||||
- TMEndpointv2
|
||||
- tparam
|
||||
- traceql
|
||||
- trixie
|
||||
- tx
|
||||
- txid
|
||||
@@ -310,6 +312,7 @@ words:
|
||||
- txjson
|
||||
- txn
|
||||
- txns
|
||||
- txqueue
|
||||
- txs
|
||||
- ubsan
|
||||
- UBSAN
|
||||
@@ -330,7 +333,6 @@ words:
|
||||
- unserviced
|
||||
- unshareable
|
||||
- unshares
|
||||
- unsponsored
|
||||
- unsquelch
|
||||
- unsquelched
|
||||
- unsquelching
|
||||
@@ -359,4 +361,5 @@ words:
|
||||
- xrplf
|
||||
- xxhash
|
||||
- xxhasher
|
||||
- CGNAT
|
||||
- xychart
|
||||
- zpages
|
||||
|
||||
@@ -43,11 +43,6 @@ repos:
|
||||
language: python
|
||||
entry: ./bin/pre-commit/fix_pragma_once.py
|
||||
files: \.(h|hpp)$
|
||||
- id: check-doxygen-style
|
||||
name: check Doxygen comment style
|
||||
entry: ./bin/pre-commit/check_doxygen_style.py
|
||||
language: python
|
||||
types_or: [c++, c]
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: dd18dad857d6133e90bbe478f4f2f22ec0030269 # frozen: v22.1.5
|
||||
|
||||
@@ -298,6 +298,46 @@ If you wish to automatically fix whatever clang-tidy finds _and_ is capable of f
|
||||
run-clang-tidy -p build -quiet -fix -allow-no-checks src tests
|
||||
```
|
||||
|
||||
## Telemetry span attribute naming
|
||||
|
||||
OpenTelemetry span attribute keys follow these rules so they stay consistent
|
||||
across the code, the OTel collector, Tempo, Grafana dashboards, and docs. The
|
||||
constants in the `*SpanNames.h` headers are the single source of truth; every
|
||||
other layer must match them. A CI check enforces this end to end.
|
||||
|
||||
1. Per-span unique attribute: bare field name — allowed when the field is
|
||||
recorded by a single span/workflow, so the span name already supplies the
|
||||
domain (e.g. `command`, `local`, `version` on `rpc.command` / `tx.process`).
|
||||
2. Shared attribute (same concept on more than one span): ONE key, reused
|
||||
verbatim on every span that records it — the span name tells the occurrences
|
||||
apart, so no per-emitter prefix is added. Pick the name by the field's
|
||||
meaning: a property of a domain object keeps that object's bare field name
|
||||
(`ledger_hash`, `ledger_seq`, `tx_hash`, `peer_id`, `full_validation`); a
|
||||
field already qualified by a sub-kind keeps that qualifier on every emitter
|
||||
(`proposal_trusted` on both `consensus.proposal.receive` and
|
||||
`peer.proposal.receive`; `validation_trusted` likewise). Define it once in
|
||||
the base `SpanNames.h` `namespace attr` block and re-export (`using`) it from
|
||||
each domain header, so all emitters share the exact string.
|
||||
3. Collision qualifier: `<domain>_<field>` — only when a bare name would collide
|
||||
with a DIFFERENT concept in the shared spanmetrics label space, or with the
|
||||
OTel-reserved `status` key (e.g. `rpc_status`, `grpc_status`,
|
||||
`consensus_phase`, `consensus_round`). This disambiguates distinct concepts
|
||||
that share a word; it is NOT used to tag the same concept with the workflow
|
||||
that emitted it — that is rule 2 (one shared name).
|
||||
4. Resource attribute: dotted `xrpl.<subsystem>.<field>` — reserved ONLY for
|
||||
process/network identity set once at startup (`xrpl.network.id`,
|
||||
`xrpl.network.type`). Never use the dotted `xrpl.` form for span attributes.
|
||||
5. Span names use `<subsystem>[.<component>]` (dotted). Only attribute _keys_
|
||||
follow rules 1–4.
|
||||
|
||||
Standard OpenTelemetry semantic-convention keys keep their canonical dotted
|
||||
form (e.g. `service.*` resource attributes, `http.*` span attributes); the
|
||||
"no dotted form" rule above applies to xrpl-custom keys, not to OTel-standard
|
||||
conventions.
|
||||
|
||||
Always reference the `*SpanNames.h` constants — never pass string literals as
|
||||
attribute keys or values to `setAttribute`/`addEvent`.
|
||||
|
||||
## Contracts and instrumentation
|
||||
|
||||
We are using [Antithesis](https://antithesis.com/) for continuous fuzzing,
|
||||
|
||||
565
OpenTelemetryPlan/00-tracing-fundamentals.md
Normal file
565
OpenTelemetryPlan/00-tracing-fundamentals.md
Normal file
@@ -0,0 +1,565 @@
|
||||
# Distributed Tracing Fundamentals
|
||||
|
||||
> **Parent Document**: [OpenTelemetryPlan.md](./OpenTelemetryPlan.md)
|
||||
> **Next**: [Architecture Analysis](./01-architecture-analysis.md)
|
||||
|
||||
---
|
||||
|
||||
## What is Distributed Tracing?
|
||||
|
||||
Distributed tracing is a method for tracking data objects as they flow through distributed systems. In a network like XRP Ledger, a single transaction touches multiple independent nodes—each with no shared memory or logging. Distributed tracing connects these dots.
|
||||
|
||||
**Without tracing:** You see isolated logs on each node with no way to correlate them.
|
||||
|
||||
**With tracing:** You see the complete journey of a transaction or an event across all nodes it touched.
|
||||
|
||||
---
|
||||
|
||||
## Actors and Actions at a Glance
|
||||
|
||||
### Actors
|
||||
|
||||
| Who (Plain English) | Technical Term |
|
||||
| ---------------------------------------------- | --------------- |
|
||||
| A single unit of work being tracked | Span |
|
||||
| The complete journey of a request | Trace |
|
||||
| Data that links spans across services | Trace Context |
|
||||
| Code that creates spans and propagates context | Instrumentation |
|
||||
| Service that receives and processes traces | Collector |
|
||||
| Storage and visualization system | Backend (Tempo) |
|
||||
| Decision logic for which traces to keep | Sampler |
|
||||
|
||||
### Actions
|
||||
|
||||
| What Happens (Plain English) | Technical Term |
|
||||
| --------------------------------------- | ----------------------- |
|
||||
| Start tracking a new operation | Create a Span |
|
||||
| Connect a child operation to its parent | Set `parent_span_id` |
|
||||
| Group all related operations together | Share a `trace_id` |
|
||||
| Pass tracking data between services | Context Propagation |
|
||||
| Decide whether to record a trace | Sampling (Head or Tail) |
|
||||
| Send completed traces to storage | Export (OTLP) |
|
||||
|
||||
---
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### 1. Trace
|
||||
|
||||
A **trace** represents the entire journey of a request through the system. It has a unique `trace_id` that stays constant across all nodes.
|
||||
|
||||
```
|
||||
Trace ID: abc123
|
||||
├── Node A: received transaction
|
||||
├── Node B: relayed transaction
|
||||
├── Node C: included in consensus
|
||||
└── Node D: applied to ledger
|
||||
```
|
||||
|
||||
### 2. Span
|
||||
|
||||
A **span** represents a single unit of work within a trace. Each span has:
|
||||
|
||||
| Attribute | Description | Example |
|
||||
| ---------------- | -------------------------------- | -------------------------- |
|
||||
| `trace_id` | Identifies the trace | `event123` |
|
||||
| `span_id` | Unique identifier | `span456` |
|
||||
| `parent_span_id` | Parent span (if any) | `p_span123` |
|
||||
| `name` | Operation name | `rpc.submit` |
|
||||
| `start_time` | When work began (local time) | `2024-01-15T10:30:00Z` |
|
||||
| `end_time` | When work completed (local time) | `2024-01-15T10:30:00.050Z` |
|
||||
| `attributes` | Key-value metadata | `tx_hash=ABC...` |
|
||||
| `status` | OK, ERROR MSG | `OK` |
|
||||
|
||||
### 3. Trace Context
|
||||
|
||||
**Trace context** is the data that propagates between services to link spans together. It contains:
|
||||
|
||||
- `trace_id` - The trace this span belongs to
|
||||
- `span_id` - The current span (becomes parent for child spans)
|
||||
- `trace_flags` - Sampling decisions
|
||||
|
||||
---
|
||||
|
||||
## How Spans Form a Trace
|
||||
|
||||
Spans have parent-child relationships forming a tree structure:
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph trace["Trace: abc123"]
|
||||
A["tx.submit<br/>span_id: 001<br/>50ms"] --> B["tx.validate<br/>span_id: 002<br/>5ms"]
|
||||
A --> C["tx.relay<br/>span_id: 003<br/>10ms"]
|
||||
A --> D["tx.apply<br/>span_id: 004<br/>30ms"]
|
||||
D --> E["ledger.update<br/>span_id: 005<br/>20ms"]
|
||||
end
|
||||
|
||||
style A fill:#0d47a1,stroke:#082f6a,color:#ffffff
|
||||
style B fill:#1b5e20,stroke:#0d3d14,color:#ffffff
|
||||
style C fill:#1b5e20,stroke:#0d3d14,color:#ffffff
|
||||
style D fill:#1b5e20,stroke:#0d3d14,color:#ffffff
|
||||
style E fill:#bf360c,stroke:#8c2809,color:#ffffff
|
||||
```
|
||||
|
||||
**Reading the diagram:**
|
||||
|
||||
- **tx.submit (blue, root)**: The top-level span representing the entire transaction submission; all other spans are its descendants.
|
||||
- **tx.validate, tx.relay, tx.apply (green)**: Direct children of tx.submit, representing the three main stages -- validation, relay to peers, and application to the ledger.
|
||||
- **ledger.update (red)**: A grandchild span nested under tx.apply, representing the actual ledger state mutation triggered by applying the transaction.
|
||||
- **Arrows (parent to child)**: Each arrow indicates a parent-child span relationship where the parent's completion depends on the child finishing.
|
||||
|
||||
The same trace visualized as a **timeline (Gantt chart)**:
|
||||
|
||||
```
|
||||
Time → 0ms 10ms 20ms 30ms 40ms 50ms
|
||||
├───────────────────────────────────────────┤
|
||||
tx.submit│▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│
|
||||
├─────┤
|
||||
tx.valid │▓▓▓▓▓│
|
||||
│ ├──────────┤
|
||||
tx.relay │ │▓▓▓▓▓▓▓▓▓▓│
|
||||
│ ├────────────────────────────┤
|
||||
tx.apply │ │▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│
|
||||
│ ├──────────────────┤
|
||||
ledger │ │▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Span Relationships
|
||||
|
||||
Spans don't always form simple parent-child trees. Distributed tracing defines several relationship types to capture different causal patterns:
|
||||
|
||||
### 1. Parent-Child (ChildOf)
|
||||
|
||||
The default relationship. The parent span **depends on** or **contains** the child span. The child runs within the scope of the parent.
|
||||
|
||||
```
|
||||
tx.submit (parent)
|
||||
├── tx.validate (child) ← parent waits for this
|
||||
├── tx.relay (child) ← parent waits for this
|
||||
└── tx.apply (child) ← parent waits for this
|
||||
```
|
||||
|
||||
**When to use:** Synchronous calls, nested operations, any case where the parent's completion depends on the child.
|
||||
|
||||
### 2. Follows-From
|
||||
|
||||
A causal relationship where the first span **triggers** the second, but does **not wait** for it. The originator fires and moves on.
|
||||
|
||||
```
|
||||
Time →
|
||||
|
||||
tx.receive [=======]
|
||||
↓ triggers (follows-from)
|
||||
tx.relay [===========] ← runs independently
|
||||
```
|
||||
|
||||
**When to use:** Asynchronous jobs, queued work, fire-and-forget patterns. For example, a node receives a transaction and queues it for relay — the relay span _follows from_ the receive span but the receiver doesn't wait for relaying to complete.
|
||||
|
||||
> **OpenTracing** defined `FollowsFrom` as a first-class reference type alongside `ChildOf`.
|
||||
> **OpenTelemetry** represents this using **Span Links** with descriptive attributes instead (see below).
|
||||
|
||||
### 3. Span Links (Cross-Trace and Non-Hierarchical)
|
||||
|
||||
Links connect spans that are **causally related but not in a parent-child hierarchy**. Unlike parent-child, links can cross trace boundaries.
|
||||
|
||||
```
|
||||
Trace A Trace B
|
||||
────── ──────
|
||||
batch.schedule batch.execute
|
||||
├─ item.enqueue (span X) ┌──► process.item
|
||||
├─ item.enqueue (span Y) ───┤ (links to X, Y, Z)
|
||||
├─ item.enqueue (span Z) └──►
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
|
||||
| Pattern | Description |
|
||||
| -------------------- | --------------------------------------------------------------------------- |
|
||||
| **Batch processing** | A batch span links back to all individual spans that contributed to it |
|
||||
| **Fan-in** | An aggregation span links to the multiple producer spans it merges |
|
||||
| **Fan-out** | Multiple downstream spans link back to the single span that triggered them |
|
||||
| **Async handoff** | A deferred job links back to the request that queued it (follows-from) |
|
||||
| **Cross-trace** | Correlating spans across independent traces (e.g., retries, related events) |
|
||||
|
||||
**Link structure:** Each link carries the target span's context plus optional attributes:
|
||||
|
||||
```
|
||||
Link {
|
||||
trace_id: <target trace>
|
||||
span_id: <target span>
|
||||
attributes: { "link.description": "triggered by batch scheduler" }
|
||||
}
|
||||
```
|
||||
|
||||
### Relationship Summary
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph parent_child["Parent-Child"]
|
||||
direction TB
|
||||
P["Parent"] --> C["Child"]
|
||||
end
|
||||
|
||||
subgraph follows_from["Follows-From"]
|
||||
direction TB
|
||||
A["Span A"] -.->|triggers| B["Span B"]
|
||||
end
|
||||
|
||||
subgraph links["Span Links"]
|
||||
direction TB
|
||||
X["Span X\n(Trace 1)"] -.-|link| Y["Span Y\n(Trace 2)"]
|
||||
end
|
||||
|
||||
parent_child ~~~ follows_from ~~~ links
|
||||
|
||||
style P fill:#0d47a1,stroke:#082f6a,color:#ffffff
|
||||
style C fill:#1b5e20,stroke:#0d3d14,color:#ffffff
|
||||
style A fill:#0d47a1,stroke:#082f6a,color:#ffffff
|
||||
style B fill:#bf360c,stroke:#8c2809,color:#ffffff
|
||||
style X fill:#4a148c,stroke:#38006b,color:#ffffff
|
||||
style Y fill:#4a148c,stroke:#38006b,color:#ffffff
|
||||
```
|
||||
|
||||
| Relationship | Same Trace? | Dependency? | OTel Mechanism |
|
||||
| ---------------- | ----------- | -------------------------- | ----------------- |
|
||||
| **Parent-Child** | Yes | Parent depends on child | `parent_span_id` |
|
||||
| **Follows-From** | Usually | Causal but no dependency | Link + attributes |
|
||||
| **Span Link** | Either | Correlation, no dependency | Link + attributes |
|
||||
|
||||
---
|
||||
|
||||
## Trace ID Generation
|
||||
|
||||
A `trace_id` is a 128-bit (16-byte) identifier that groups all spans belonging to one logical operation. How it's generated determines how easily you can find and correlate traces later.
|
||||
|
||||
### General Approaches
|
||||
|
||||
#### 1. Random (W3C Default)
|
||||
|
||||
Generate a random 128-bit ID when a trace starts. Standard approach for most services.
|
||||
|
||||
```
|
||||
trace_id = random_128_bits()
|
||||
```
|
||||
|
||||
| Pros | Cons |
|
||||
| --------------------------- | --------------------------------------------- |
|
||||
| Simple, standard | No natural correlation to domain events |
|
||||
| Guaranteed unique per trace | If propagation is lost, trace is broken |
|
||||
| Works with all OTel tooling | "Find trace for TX abc" requires index lookup |
|
||||
|
||||
#### 2. Deterministic (Derived from Domain Data)
|
||||
|
||||
Compute the trace_id from a hash of a natural identifier. Every node independently derives the **same** trace_id for the same event.
|
||||
|
||||
```
|
||||
trace_id = SHA-256(domain_identifier)[0:16] // truncate to 128 bits
|
||||
```
|
||||
|
||||
| Pros | Cons |
|
||||
| --------------------------------------------------- | ---------------------------------------------------------- |
|
||||
| Propagation-resilient — same ID computed everywhere | Same event processed twice (retry) shares trace_id |
|
||||
| Natural search — domain ID maps directly to trace | Non-standard (tooling assumes random) |
|
||||
| No coordination needed between nodes | 256→128 bit truncation (collision risk negligible at ~2⁶⁴) |
|
||||
|
||||
#### 3. Hybrid (Deterministic Prefix + Random Suffix)
|
||||
|
||||
First 8 bytes derived from domain data, last 8 bytes random.
|
||||
|
||||
```
|
||||
trace_id = SHA-256(domain_identifier)[0:8] || random_64_bits()
|
||||
```
|
||||
|
||||
| Pros | Cons |
|
||||
| ------------------------------------------- | ---------------------------------------- |
|
||||
| Prefix search: "find all traces for TX abc" | Must propagate to maintain full trace_id |
|
||||
| Unique per processing instance | More complex generation logic |
|
||||
| Retries get distinct trace_ids | Partial correlation only (prefix match) |
|
||||
|
||||
### XRPL Workflow Analysis
|
||||
|
||||
XRPL has a unique advantage: its core workflows produce **globally unique 256-bit hashes** that are known on every node. This makes deterministic trace_id generation practical in ways most systems can't achieve.
|
||||
|
||||
#### Natural Identifiers by Workflow
|
||||
|
||||
| Workflow | Natural Identifier | Size | Known at Start? | Same on All Nodes? |
|
||||
| ------------------- | --------------------------------- | ---------- | ----------------------------- | -------------------------------- |
|
||||
| **Transaction** | Transaction hash (`tid_`) | 256-bit | Yes — computed before signing | Yes — hash of canonical tx data |
|
||||
| **Consensus round** | Previous ledger hash + ledger seq | 256+32 bit | Yes — known when round opens | Yes — all validators agree |
|
||||
| **Validation** | Ledger hash being validated | 256-bit | Yes — from consensus result | Yes — same closed ledger |
|
||||
| **Ledger catch-up** | Target ledger hash | 256-bit | Yes — we know what to fetch | Yes — identifies ledger globally |
|
||||
|
||||
#### Where These Identifiers Live in Code
|
||||
|
||||
```
|
||||
Transaction: STTx::getTransactionID() → uint256 tid_
|
||||
TMTransaction::rawTransaction → recompute hash from bytes
|
||||
|
||||
Consensus: ConsensusProposal::prevLedger_ → uint256 (previous ledger hash)
|
||||
ConsensusProposal::position_ → uint256 (TxSet hash)
|
||||
LedgerHeader::seq → uint32_t (ledger sequence)
|
||||
|
||||
Validation: STValidation::getLedgerHash() → uint256
|
||||
STValidation::getNodeID() → NodeID (160-bit)
|
||||
|
||||
Ledger fetch: InboundLedger constructor → uint256 hash, uint32_t seq
|
||||
TMGetLedger::ledgerHash → bytes (uint256)
|
||||
```
|
||||
|
||||
### Recommended Strategy: Workflow-Scoped Deterministic
|
||||
|
||||
Each workflow type derives its trace_id from its natural domain identifier:
|
||||
|
||||
```
|
||||
Transaction trace: trace_id = SHA-256("tx" || tx_hash)[0:16]
|
||||
Consensus trace: trace_id = SHA-256("cons" || prev_ledger_hash || ledger_seq)[0:16]
|
||||
Ledger catch-up: trace_id = SHA-256("fetch" || target_ledger_hash)[0:16]
|
||||
```
|
||||
|
||||
The string prefix (`"tx"`, `"cons"`, `"fetch"`) prevents collisions between workflows that might share underlying hashes.
|
||||
|
||||
**Why this works for XRPL:**
|
||||
|
||||
1. **Propagation-resilient** — Even if a P2P message drops trace context, every node independently computes the same trace_id from the same tx_hash or ledger_hash. Spans still correlate.
|
||||
|
||||
2. **Zero-cost search** — "Show me the trace for transaction ABC" becomes a direct lookup: compute `SHA-256("tx" || ABC)[0:16]` and query. No secondary index needed.
|
||||
|
||||
3. **Cross-workflow linking via Span Links** — A consensus trace links to individual transaction traces. A validation span links to the consensus trace. This connects the full picture without forcing everything into one giant trace.
|
||||
|
||||
### Cross-Workflow Correlation
|
||||
|
||||
Each workflow gets its own trace. Span Links tie them together:
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph tx_trace["Transaction Trace"]
|
||||
direction LR
|
||||
Tn["trace_id = f(tx_hash)"]:::note --> T1["tx.receive"] --> T2["tx.validate"] --> T3["tx.relay"]
|
||||
end
|
||||
|
||||
subgraph cons_trace["Consensus Trace"]
|
||||
direction LR
|
||||
Cn["trace_id = f(prev_ledger, seq)"]:::note --> C1["cons.open"] --> C2["cons.propose"] --> C3["cons.accept"]
|
||||
end
|
||||
|
||||
subgraph val_trace["Validation"]
|
||||
direction LR
|
||||
Vn["spans within consensus trace"]:::note --> V1["val.create"] --> V2["val.broadcast"]
|
||||
end
|
||||
|
||||
subgraph fetch_trace["Catch-Up Trace"]
|
||||
direction LR
|
||||
Fn["trace_id = f(ledger_hash)"]:::note --> F1["fetch.request"] --> F2["fetch.receive"] --> F3["fetch.apply"]
|
||||
end
|
||||
|
||||
C1 -.-|"span link\n(tx traces)"| T3
|
||||
C3 --> V1
|
||||
F1 -.-|"span link\n(target ledger)"| C3
|
||||
|
||||
classDef note fill:none,stroke:#888,stroke-dasharray:5 5,color:#333,font-style:italic
|
||||
style T1 fill:#0d47a1,stroke:#082f6a,color:#ffffff
|
||||
style T2 fill:#0d47a1,stroke:#082f6a,color:#ffffff
|
||||
style T3 fill:#0d47a1,stroke:#082f6a,color:#ffffff
|
||||
style C1 fill:#1b5e20,stroke:#0d3d14,color:#ffffff
|
||||
style C2 fill:#1b5e20,stroke:#0d3d14,color:#ffffff
|
||||
style C3 fill:#1b5e20,stroke:#0d3d14,color:#ffffff
|
||||
style V1 fill:#bf360c,stroke:#8c2809,color:#ffffff
|
||||
style V2 fill:#bf360c,stroke:#8c2809,color:#ffffff
|
||||
style F1 fill:#4a148c,stroke:#38006b,color:#ffffff
|
||||
style F2 fill:#4a148c,stroke:#38006b,color:#ffffff
|
||||
style F3 fill:#4a148c,stroke:#38006b,color:#ffffff
|
||||
```
|
||||
|
||||
**Reading the diagram:**
|
||||
|
||||
- **Transaction Trace (blue)**: An independent trace whose `trace_id` is deterministically derived from the transaction hash. Contains receive, validate, and relay spans.
|
||||
- **Consensus Trace (green)**: An independent trace whose `trace_id` is derived from the previous ledger hash and sequence number. Covers the open, propose, and accept phases.
|
||||
- **Validation (red)**: Validation spans live within the consensus trace (not a separate trace). They are created after the accept phase completes.
|
||||
- **Catch-Up Trace (purple)**: An independent trace for ledger acquisition, derived from the target ledger hash. Used when a node is behind and fetching missing ledgers.
|
||||
- **Dotted arrows (span links)**: Cross-trace correlations. Consensus links to transaction traces it included; catch-up links to the consensus trace that produced the target ledger.
|
||||
- **Solid arrow (C3 to V1)**: A parent-child relationship -- validation spans are direct children of the consensus accept span within the same trace.
|
||||
|
||||
**How a query flows:**
|
||||
|
||||
```
|
||||
"Why was TX abc slow?"
|
||||
1. Compute trace_id = SHA-256("tx" || abc)[0:16]
|
||||
2. Find transaction trace → see it was included in consensus round N
|
||||
3. Follow span link → consensus trace for round N
|
||||
4. See which phase was slow (propose? accept?)
|
||||
5. If a node was catching up, follow link → catch-up trace
|
||||
```
|
||||
|
||||
### Trade-offs to Consider
|
||||
|
||||
| Concern | Mitigation |
|
||||
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Retries get same trace_id** | Add `attempt` attribute to root span; spans have unique span_ids and timestamps |
|
||||
| **256→128 bit truncation** | Birthday-bound collision at ~2⁶⁴ operations — negligible for XRPL's throughput |
|
||||
| **Non-standard generation** | OTel spec allows any 16-byte non-zero value; tooling works on the hex string |
|
||||
| **Hash computation cost** | SHA-256 is ~0.3μs per call; XRPL already computes these hashes for other purposes |
|
||||
| **Late-binding identifiers** | Ledger hash isn't known until after consensus — validation spans use ledger_seq as fallback, then link to the consensus trace |
|
||||
|
||||
---
|
||||
|
||||
## Distributed Traces Across Nodes
|
||||
|
||||
In distributed systems like xrpld, traces span **multiple independent nodes**. The trace context must be propagated in network messages:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant NodeA as Node A
|
||||
participant NodeB as Node B
|
||||
participant NodeC as Node C
|
||||
|
||||
Client->>NodeA: Submit TX<br/>(no trace context)
|
||||
|
||||
Note over NodeA: Creates new trace<br/>trace_id: abc123<br/>span: tx.receive
|
||||
|
||||
NodeA->>NodeB: Relay TX<br/>(trace_id: abc123, parent: 001)
|
||||
|
||||
Note over NodeB: Creates child span<br/>span: tx.relay<br/>parent_span_id: 001
|
||||
|
||||
NodeA->>NodeC: Relay TX<br/>(trace_id: abc123, parent: 001)
|
||||
|
||||
Note over NodeC: Creates child span<br/>span: tx.relay<br/>parent_span_id: 001
|
||||
|
||||
Note over NodeA,NodeC: All spans share trace_id: abc123<br/>enabling correlation across nodes
|
||||
```
|
||||
|
||||
**Reading the diagram:**
|
||||
|
||||
- **Client**: The external entity that submits a transaction. It does not carry trace context -- the trace originates at the first node.
|
||||
- **Node A**: The entry point that creates a new trace (trace_id: abc123) and the root span `tx.receive`. It relays the transaction to peers with trace context attached.
|
||||
- **Node B and Node C**: Peer nodes that receive the relayed transaction along with the propagated trace context. Each creates a child span under Node A's span, preserving the same `trace_id`.
|
||||
- **Arrows with trace context**: The relay messages carry `trace_id` and `parent_span_id`, allowing each downstream node to link its spans back to the originating span on Node A.
|
||||
|
||||
---
|
||||
|
||||
## Context Propagation
|
||||
|
||||
For traces to work across nodes, **trace context must be propagated** in messages.
|
||||
|
||||
### What's in the Context (~26 bytes)
|
||||
|
||||
| Field | Size | Description |
|
||||
| ------------- | -------- | ------------------------------------------------------- |
|
||||
| `trace_id` | 16 bytes | Identifies the entire trace (constant across all nodes) |
|
||||
| `span_id` | 8 bytes | The sender's current span (becomes parent on receiver) |
|
||||
| `trace_flags` | 1 byte | Sampling decision (bit 0 = sampled; bits 1-7 reserved) |
|
||||
| `trace_state` | variable | Optional vendor-specific data (typically omitted) |
|
||||
|
||||
### How span_id Changes at Each Hop
|
||||
|
||||
Only **one** `span_id` travels in the context - the sender's current span. Each node:
|
||||
|
||||
1. Extracts the received `span_id` and uses it as the `parent_span_id`
|
||||
2. Creates a **new** `span_id` for its own span
|
||||
3. Sends its own `span_id` as the parent when forwarding
|
||||
|
||||
```
|
||||
Node A Node B Node C
|
||||
────── ────── ──────
|
||||
|
||||
Span AAA Span BBB Span CCC
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
Context out: Context out: Context out:
|
||||
├─ trace_id: abc123 ├─ trace_id: abc123 ├─ trace_id: abc123
|
||||
├─ span_id: AAA ──────────► ├─ span_id: BBB ──────────► ├─ span_id: CCC ──────►
|
||||
└─ flags: 01 └─ flags: 01 └─ flags: 01
|
||||
│ │
|
||||
parent = AAA parent = BBB
|
||||
```
|
||||
|
||||
The `trace_id` stays constant, but `span_id` **changes at every hop** to maintain the parent-child chain.
|
||||
|
||||
### Propagation Formats
|
||||
|
||||
There are two patterns:
|
||||
|
||||
### HTTP/RPC Headers (W3C Trace Context)
|
||||
|
||||
```
|
||||
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
|
||||
│ │ │ │
|
||||
│ │ │ └── Flags (sampled)
|
||||
│ │ └── Parent span ID (16 hex)
|
||||
│ └── Trace ID (32 hex)
|
||||
└── Version
|
||||
```
|
||||
|
||||
### Protocol Buffers (xrpld P2P messages)
|
||||
|
||||
xrpld P2P messages such as `TMTransaction` carry the trace context in two added byte fields alongside the existing payload: `trace_parent` holds the W3C traceparent (`trace_id`, `span_id`, and `trace_flags`), and `trace_state` holds the optional W3C tracestate. Together they propagate the trace across the P2P boundary so a receiving node can attach its spans to the sender's span.
|
||||
|
||||
---
|
||||
|
||||
## Sampling
|
||||
|
||||
Not every trace needs to be recorded. **Sampling** reduces overhead:
|
||||
|
||||
### Head Sampling (at trace start)
|
||||
|
||||
```
|
||||
Request arrives → Random N% chance → Record or skip entire trace
|
||||
```
|
||||
|
||||
- ✅ Low overhead
|
||||
- ❌ May miss interesting traces
|
||||
|
||||
> **xrpld note**: xrpld intentionally fixes head sampling at 100% (sample
|
||||
> everything) and does not expose a configurable ratio. A per-node ratio
|
||||
> would let different nodes make divergent keep/drop decisions for the same
|
||||
> distributed trace, producing broken/partial traces. xrpld uses a
|
||||
> `ParentBased` sampler so spans with a remote parent honor the upstream
|
||||
> decision. Volume reduction is delegated to collector-side tail sampling.
|
||||
|
||||
### Tail Sampling (after trace completes)
|
||||
|
||||
```
|
||||
Trace completes → Collector evaluates:
|
||||
- Error? → KEEP
|
||||
- Slow? → KEEP
|
||||
- Normal? → Sample 10%
|
||||
```
|
||||
|
||||
- ✅ Never loses important traces
|
||||
- ❌ Higher memory usage at collector
|
||||
|
||||
---
|
||||
|
||||
## Key Benefits for xrpld
|
||||
|
||||
| Challenge | How Tracing Helps |
|
||||
| ---------------------------------- | ---------------------------------------- |
|
||||
| "Where is my transaction?" | Follow trace across all nodes it touched |
|
||||
| "Why was consensus slow?" | See timing breakdown of each phase |
|
||||
| "Which node is the bottleneck?" | Compare span durations across nodes |
|
||||
| "What happened during the outage?" | Correlate errors across the network |
|
||||
|
||||
---
|
||||
|
||||
## Glossary
|
||||
|
||||
| Term | Definition |
|
||||
| -------------------- | ------------------------------------------------------------------- |
|
||||
| **Trace** | Complete journey of a request, identified by `trace_id` |
|
||||
| **Span** | Single operation within a trace |
|
||||
| **Parent-Child** | Span relationship where the parent depends on the child |
|
||||
| **Follows-From** | Causal relationship where originator doesn't wait for the result |
|
||||
| **Span Link** | Non-hierarchical connection between spans, possibly across traces |
|
||||
| **Deterministic ID** | Trace ID derived from domain data (e.g., tx_hash) instead of random |
|
||||
| **Context** | Data propagated between services (`trace_id`, `span_id`, flags) |
|
||||
| **Instrumentation** | Code that creates spans and propagates context |
|
||||
| **Collector** | Service that receives, processes, and exports traces |
|
||||
| **Backend** | Storage/visualization system (Tempo) |
|
||||
| **Head Sampling** | Sampling decision at trace start |
|
||||
| **Tail Sampling** | Sampling decision after trace completes |
|
||||
|
||||
---
|
||||
|
||||
_Next: [Architecture Analysis](./01-architecture-analysis.md)_ | _Back to: [Overview](./OpenTelemetryPlan.md)_
|
||||
467
OpenTelemetryPlan/01-architecture-analysis.md
Normal file
467
OpenTelemetryPlan/01-architecture-analysis.md
Normal file
@@ -0,0 +1,467 @@
|
||||
# Architecture Analysis
|
||||
|
||||
> **Parent Document**: [OpenTelemetryPlan.md](./OpenTelemetryPlan.md)
|
||||
> **Related**: [Design Decisions](./02-design-decisions.md) | [Implementation Strategy](./03-implementation-strategy.md)
|
||||
|
||||
---
|
||||
|
||||
## 1.1 Current xrpld Architecture Overview
|
||||
|
||||
> **WS** = WebSocket | **UNL** = Unique Node List | **TxQ** = Transaction Queue | **StatsD** = Statistics Daemon
|
||||
|
||||
The xrpld node software consists of several interconnected components that need instrumentation for distributed tracing:
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph xrpld["xrpld Node"]
|
||||
subgraph services["Core Services"]
|
||||
RPC["RPC Server<br/>(HTTP/WS/gRPC)"]
|
||||
Overlay["Overlay<br/>(P2P Network)"]
|
||||
Consensus["Consensus<br/>(RCLConsensus)"]
|
||||
ValidatorList["ValidatorList<br/>(UNL Mgmt)"]
|
||||
end
|
||||
|
||||
JobQueue["JobQueue<br/>(Thread Pool)"]
|
||||
|
||||
subgraph processing["Processing Layer"]
|
||||
NetworkOPs["NetworkOPs<br/>(Tx Processing)"]
|
||||
LedgerMaster["LedgerMaster<br/>(Ledger Mgmt)"]
|
||||
NodeStore["NodeStore<br/>(Database)"]
|
||||
InboundLedgers["InboundLedgers<br/>(Ledger Sync)"]
|
||||
end
|
||||
|
||||
subgraph appservices["Application Services"]
|
||||
PathFind["PathFinding<br/>(Payment Paths)"]
|
||||
TxQ["TxQ<br/>(Fee Escalation)"]
|
||||
LoadMgr["LoadManager<br/>(Fee/Load)"]
|
||||
end
|
||||
|
||||
subgraph observability["Existing Observability"]
|
||||
PerfLog["PerfLog<br/>(JSON)"]
|
||||
Insight["Insight<br/>(StatsD)"]
|
||||
Logging["Logging<br/>(Journal)"]
|
||||
end
|
||||
|
||||
services --> JobQueue
|
||||
JobQueue --> processing
|
||||
JobQueue --> appservices
|
||||
end
|
||||
|
||||
style xrpld fill:#424242,stroke:#212121,color:#ffffff
|
||||
style services fill:#1565c0,stroke:#0d47a1,color:#ffffff
|
||||
style processing fill:#2e7d32,stroke:#1b5e20,color:#ffffff
|
||||
style appservices fill:#6a1b9a,stroke:#4a148c,color:#ffffff
|
||||
style observability fill:#e65100,stroke:#bf360c,color:#ffffff
|
||||
```
|
||||
|
||||
**Reading the diagram:**
|
||||
|
||||
- **Core Services (blue)**: The entry points into xrpld -- RPC Server handles client requests, Overlay manages peer-to-peer networking, Consensus drives agreement, and ValidatorList manages trusted validators.
|
||||
- **JobQueue (center)**: The asynchronous thread pool that decouples Core Services from the Processing and Application layers. All work flows through it.
|
||||
- **Processing Layer (green)**: Core business logic -- NetworkOPs processes transactions, LedgerMaster manages ledger state, NodeStore handles persistence, and InboundLedgers synchronizes missing data.
|
||||
- **Application Services (purple)**: Higher-level features -- PathFinding computes payment routes, TxQ manages fee-based queuing, and LoadManager tracks server load.
|
||||
- **Existing Observability (orange)**: The current monitoring stack (PerfLog, Insight, Journal logging) that OpenTelemetry will complement, not replace.
|
||||
- **Arrows (Services to JobQueue to layers)**: Work originates at Core Services, is enqueued onto the JobQueue, and dispatched to Processing or Application layers for execution.
|
||||
|
||||
---
|
||||
|
||||
## 1.1.1 Actors and Actions
|
||||
|
||||
### Actors
|
||||
|
||||
| Who (Plain English) | Technical Term |
|
||||
| ----------------------------------------- | -------------------------- |
|
||||
| Network node running XRPL software | xrpld node |
|
||||
| External client submitting requests | RPC Client |
|
||||
| Network neighbor sharing data | Peer (PeerImp) |
|
||||
| Request handler for client queries | RPC Server (ServerHandler) |
|
||||
| Command executor for specific RPC methods | RPCHandler |
|
||||
| Agreement process between nodes | Consensus (RCLConsensus) |
|
||||
| Transaction processing coordinator | NetworkOPs |
|
||||
| Background task scheduler | JobQueue |
|
||||
| Ledger state manager | LedgerMaster |
|
||||
| Payment route calculator | PathFinding (Pathfinder) |
|
||||
| Transaction waiting room | TxQ (Transaction Queue) |
|
||||
| Fee adjustment system | LoadManager |
|
||||
| Trusted validator list manager | ValidatorList |
|
||||
| Protocol upgrade tracker | AmendmentTable |
|
||||
| Ledger state hash tree | SHAMap |
|
||||
| Persistent key-value storage | NodeStore |
|
||||
|
||||
### Actions
|
||||
|
||||
| What Happens (Plain English) | Technical Term |
|
||||
| ---------------------------------------------- | ---------------------- |
|
||||
| Client sends a request to a node | `rpc.request` |
|
||||
| Node executes a specific RPC command | `rpc.command.*` |
|
||||
| Node receives a transaction from a peer | `tx.receive` |
|
||||
| Node checks if a transaction is valid | `tx.validate` |
|
||||
| Node forwards a transaction to neighbors | `tx.relay` |
|
||||
| Nodes agree on which transactions to include | `consensus.round` |
|
||||
| Consensus progresses through phases | `consensus.phase.*` |
|
||||
| Node builds a new confirmed ledger | `ledger.build` |
|
||||
| Node fetches missing ledger data from peers | `ledger.acquire` |
|
||||
| Node computes payment routes | `pathfind.compute` |
|
||||
| Node queues a transaction for later processing | `txq.enqueue` |
|
||||
| Node increases fees due to high load | `fee.escalate` |
|
||||
| Node fetches the latest trusted validator list | `validator.list.fetch` |
|
||||
| Node votes on a protocol amendment | `amendment.vote` |
|
||||
| Node synchronizes state tree data | `shamap.sync` |
|
||||
|
||||
---
|
||||
|
||||
## 1.2 Key Components for Instrumentation
|
||||
|
||||
> **TxQ** = Transaction Queue | **UNL** = Unique Node List
|
||||
|
||||
| Component | Location | Purpose | Trace Value |
|
||||
| ------------------ | ------------------------------------------ | ------------------------ | -------------------------------- |
|
||||
| **Overlay** | `src/xrpld/overlay/` | P2P communication | Message propagation timing |
|
||||
| **PeerImp** | `src/xrpld/overlay/detail/PeerImp.cpp` | Individual peer handling | Per-peer latency |
|
||||
| **RCLConsensus** | `src/xrpld/app/consensus/RCLConsensus.cpp` | Consensus algorithm | Round timing, phase analysis |
|
||||
| **NetworkOPs** | `src/xrpld/app/misc/NetworkOPs.cpp` | Transaction processing | Tx lifecycle tracking |
|
||||
| **ServerHandler** | `src/xrpld/rpc/detail/ServerHandler.cpp` | RPC entry point | Request latency |
|
||||
| **RPCHandler** | `src/xrpld/rpc/detail/RPCHandler.cpp` | Command execution | Per-command timing |
|
||||
| **JobQueue** | `src/xrpl/core/JobQueue.h` | Async task execution | Queue wait times |
|
||||
| **PathFinding** | `src/xrpld/app/paths/` | Payment path computation | Path latency, cache hits |
|
||||
| **TxQ** | `src/xrpld/app/misc/TxQ.cpp` | Transaction queue/fees | Queue depth, eviction rates |
|
||||
| **LoadManager** | `src/xrpld/app/main/LoadManager.cpp` | Fee escalation/load | Fee levels, load factors |
|
||||
| **InboundLedgers** | `src/xrpld/app/ledger/InboundLedgers.cpp` | Ledger acquisition | Sync time, peer reliability |
|
||||
| **ValidatorList** | `src/xrpld/app/misc/ValidatorList.cpp` | UNL management | List freshness, fetch failures |
|
||||
| **AmendmentTable** | `src/xrpld/app/misc/AmendmentTable.cpp` | Protocol amendments | Voting status, activation events |
|
||||
| **SHAMap** | `src/xrpld/shamap/` | State hash tree | Sync speed, missing nodes |
|
||||
|
||||
---
|
||||
|
||||
## 1.3 Transaction Flow Diagram
|
||||
|
||||
Transaction flow spans multiple nodes in the network. Each node creates linked spans to form a distributed trace:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant PeerA as Peer A (Receive)
|
||||
participant PeerB as Peer B (Relay)
|
||||
participant PeerC as Peer C (Validate)
|
||||
|
||||
Client->>PeerA: 1. Submit TX
|
||||
|
||||
rect rgb(230, 245, 255)
|
||||
Note over PeerA: tx.receive SPAN START
|
||||
PeerA->>PeerA: HashRouter Deduplication
|
||||
PeerA->>PeerA: tx.validate (child span)
|
||||
end
|
||||
|
||||
PeerA->>PeerB: 2. Relay TX (with trace ctx)
|
||||
|
||||
rect rgb(230, 245, 255)
|
||||
Note over PeerB: tx.receive (linked span)
|
||||
end
|
||||
|
||||
PeerB->>PeerC: 3. Relay TX
|
||||
|
||||
rect rgb(230, 245, 255)
|
||||
Note over PeerC: tx.receive (linked span)
|
||||
PeerC->>PeerC: tx.process
|
||||
end
|
||||
|
||||
Note over Client,PeerC: DISTRIBUTED TRACE (same trace_id: abc123)
|
||||
```
|
||||
|
||||
**Reading the diagram:**
|
||||
|
||||
- **Client**: The external entity that submits a transaction to Peer A. It has no trace context -- the trace starts at the first node.
|
||||
- **Peer A (Receive)**: The entry node that creates the root span `tx.receive`, runs HashRouter deduplication to avoid processing duplicates, and creates a child `tx.validate` span.
|
||||
- **Peer A to Peer B arrow**: The relay message carries trace context (trace_id + parent span_id), enabling Peer B to create a linked span under the same trace.
|
||||
- **Peer B (Relay)**: Receives the transaction and trace context, creates a `tx.receive` span linked to Peer A's trace, then relays onward.
|
||||
- **Peer C (Validate)**: Final hop in this example. Creates a linked `tx.receive` span and runs `tx.process` to fully process the transaction.
|
||||
- **Blue rectangles**: Highlight the span boundaries on each node, showing where instrumentation creates and closes spans.
|
||||
|
||||
### Trace Structure
|
||||
|
||||
```
|
||||
trace_id: abc123
|
||||
├── span: tx.receive (Peer A)
|
||||
│ ├── span: tx.validate
|
||||
│ └── span: tx.relay
|
||||
├── span: tx.receive (Peer B) [parent: Peer A]
|
||||
│ └── span: tx.relay
|
||||
└── span: tx.receive (Peer C) [parent: Peer B]
|
||||
└── span: tx.process
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1.4 Consensus Round Flow
|
||||
|
||||
Consensus rounds are multi-phase operations that benefit significantly from tracing:
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph round["consensus.round (root span)"]
|
||||
attrs["Attributes:<br/>ledger_seq = 12345678<br/>consensus_mode = proposing<br/>proposers = 35"]
|
||||
|
||||
subgraph open["consensus.phase.open"]
|
||||
open_desc["Duration: ~3s<br/>Waiting for transactions"]
|
||||
end
|
||||
|
||||
subgraph establish["consensus.phase.establish"]
|
||||
est_attrs["proposals_received = 28<br/>disputes_resolved = 3"]
|
||||
est_children["├── consensus.proposal.receive (×28)<br/>├── consensus.proposal.send (×1)<br/>└── consensus.dispute.resolve (×3)"]
|
||||
end
|
||||
|
||||
subgraph accept["consensus.phase.accept"]
|
||||
acc_attrs["transactions_applied = 150<br/>ledger_hash = DEF456..."]
|
||||
acc_children["├── ledger.build<br/>└── ledger.validate"]
|
||||
end
|
||||
|
||||
attrs --> open
|
||||
open --> establish
|
||||
establish --> accept
|
||||
end
|
||||
|
||||
style round fill:#f57f17,stroke:#e65100,color:#ffffff
|
||||
style open fill:#1565c0,stroke:#0d47a1,color:#ffffff
|
||||
style establish fill:#2e7d32,stroke:#1b5e20,color:#ffffff
|
||||
style accept fill:#c2185b,stroke:#880e4f,color:#ffffff
|
||||
```
|
||||
|
||||
**Reading the diagram:**
|
||||
|
||||
- **consensus.round (orange, root span)**: The top-level span encompassing the entire consensus round, with attributes like ledger sequence, mode, and proposer count.
|
||||
- **consensus.phase.open (blue)**: The first phase where the node waits (~3s) to collect incoming transactions before proposing.
|
||||
- **consensus.phase.establish (green)**: The negotiation phase where validators exchange proposals, resolve disputes, and converge on a transaction set. Child spans track each proposal received/sent and each dispute resolved.
|
||||
- **consensus.phase.accept (pink)**: The final phase where the agreed transaction set is applied, a new ledger is built, and the ledger is validated. Child spans cover `ledger.build` and `ledger.validate`.
|
||||
- **Arrows (open to establish to accept)**: The sequential flow through the three consensus phases. Each phase must complete before the next begins.
|
||||
|
||||
---
|
||||
|
||||
## 1.5 RPC Request Flow
|
||||
|
||||
> **WS** = WebSocket
|
||||
|
||||
RPC requests support W3C Trace Context headers for distributed tracing across services:
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph request["rpc.request (root span)"]
|
||||
http["HTTP Request — POST /<br/>traceparent:<br/>00-abc123...-def456...-01"]
|
||||
|
||||
attrs["Attributes:<br/>http.method = POST<br/>net.peer.ip = 192.168.1.100<br/>command = submit"]
|
||||
|
||||
subgraph enqueue["jobqueue.enqueue"]
|
||||
job_attr["job_type = jtCLIENT_RPC"]
|
||||
end
|
||||
|
||||
subgraph command["rpc.command.submit"]
|
||||
cmd_attrs["version = 2<br/>rpc_role = user"]
|
||||
cmd_children["├── tx.deserialize<br/>├── tx.validate_local<br/>└── tx.submit_to_network"]
|
||||
end
|
||||
|
||||
response["Response: 200 OK<br/>Duration: 45ms"]
|
||||
|
||||
http --> attrs
|
||||
attrs --> enqueue
|
||||
enqueue --> command
|
||||
command --> response
|
||||
end
|
||||
|
||||
style request fill:#2e7d32,stroke:#1b5e20,color:#ffffff
|
||||
style enqueue fill:#1565c0,stroke:#0d47a1,color:#ffffff
|
||||
style command fill:#e65100,stroke:#bf360c,color:#ffffff
|
||||
```
|
||||
|
||||
**Reading the diagram:**
|
||||
|
||||
- **rpc.request (green, root span)**: The outermost span representing the full RPC request lifecycle, from HTTP receipt to response. Carries the W3C `traceparent` header for distributed tracing.
|
||||
- **HTTP Request node**: Shows the incoming POST request with its `traceparent` header and extracted attributes (method, peer IP, command name).
|
||||
- **jobqueue.enqueue (blue)**: The span covering the asynchronous handoff from the RPC thread to the JobQueue worker thread. The trace context is preserved across this async boundary.
|
||||
- **rpc.command.submit (orange)**: The span for the actual command execution, with child spans for deserialization, local validation, and network submission.
|
||||
- **Response node**: The final output with HTTP status and total duration, marking the end of the root span.
|
||||
- **Arrows (top to bottom)**: The sequential processing pipeline -- receive request, extract attributes, enqueue job, execute command, return response.
|
||||
|
||||
---
|
||||
|
||||
## 1.6 Key Trace Points
|
||||
|
||||
> **TxQ** = Transaction Queue
|
||||
|
||||
The following table identifies priority instrumentation points across the codebase:
|
||||
|
||||
| Category | Span Name | File | Method | Priority |
|
||||
| --------------- | ---------------------- | ---------------------- | ----------------------- | -------- |
|
||||
| **Transaction** | `tx.receive` | `PeerImp.cpp` | `handleTransaction()` | High |
|
||||
| **Transaction** | `tx.validate` | `NetworkOPs.cpp` | `processTransaction()` | High |
|
||||
| **Transaction** | `tx.process` | `NetworkOPs.cpp` | `doTransactionSync()` | High |
|
||||
| **Transaction** | `tx.relay` | `OverlayImpl.cpp` | `relay()` | Medium |
|
||||
| **Consensus** | `consensus.round` | `RCLConsensus.cpp` | `startRound()` | High |
|
||||
| **Consensus** | `consensus.phase.*` | `Consensus.h` | `timerEntry()` | High |
|
||||
| **Consensus** | `consensus.proposal.*` | `RCLConsensus.cpp` | `peerProposal()` | Medium |
|
||||
| **RPC** | `rpc.request` | `ServerHandler.cpp` | `onRequest()` | High |
|
||||
| **RPC** | `rpc.command.*` | `RPCHandler.cpp` | `doCommand()` | High |
|
||||
| **Peer** | `peer.connect` | `OverlayImpl.cpp` | `onHandoff()` | Low |
|
||||
| **Peer** | `peer.message.*` | `PeerImp.cpp` | `onMessage()` | Low |
|
||||
| **Ledger** | `ledger.acquire` | `InboundLedgers.cpp` | `acquire()` | Medium |
|
||||
| **Ledger** | `ledger.build` | `RCLConsensus.cpp` | `buildLCL()` | High |
|
||||
| **PathFinding** | `pathfind.request` | `PathRequest.cpp` | `doUpdate()` | High |
|
||||
| **PathFinding** | `pathfind.compute` | `Pathfinder.cpp` | `findPaths()` | High |
|
||||
| **TxQ** | `txq.enqueue` | `TxQ.cpp` | `apply()` | High |
|
||||
| **TxQ** | `txq.apply` | `TxQ.cpp` | `processClosedLedger()` | High |
|
||||
| **Fee** | `fee.escalate` | `LoadManager.cpp` | `raiseLocalFee()` | Medium |
|
||||
| **Ledger** | `ledger.replay` | `LedgerReplayer.h` | `replay()` | Medium |
|
||||
| **Ledger** | `ledger.delta` | `LedgerDeltaAcquire.h` | `processData()` | Medium |
|
||||
| **Validator** | `validator.list.fetch` | `ValidatorList.cpp` | `verify()` | Medium |
|
||||
| **Validator** | `validator.manifest` | `Manifest.cpp` | `applyManifest()` | Low |
|
||||
| **Amendment** | `amendment.vote` | `AmendmentTable.cpp` | `doVoting()` | Low |
|
||||
| **SHAMap** | `shamap.sync` | `SHAMap.cpp` | `fetchRoot()` | Medium |
|
||||
|
||||
---
|
||||
|
||||
## 1.7 Instrumentation Priority
|
||||
|
||||
> **TxQ** = Transaction Queue
|
||||
|
||||
```mermaid
|
||||
quadrantChart
|
||||
title Instrumentation Priority Matrix
|
||||
x-axis Low Complexity --> High Complexity
|
||||
y-axis Low Value --> High Value
|
||||
quadrant-1 Implement First
|
||||
quadrant-2 Plan Carefully
|
||||
quadrant-3 Quick Wins
|
||||
quadrant-4 Consider Later
|
||||
|
||||
RPC Tracing: [0.2, 0.92]
|
||||
Transaction Tracing: [0.55, 0.88]
|
||||
Consensus Tracing: [0.78, 0.82]
|
||||
PathFinding: [0.38, 0.75]
|
||||
TxQ and Fees: [0.25, 0.65]
|
||||
Ledger Sync: [0.62, 0.58]
|
||||
Peer Message Tracing: [0.35, 0.25]
|
||||
JobQueue Tracing: [0.2, 0.48]
|
||||
Validator Mgmt: [0.48, 0.42]
|
||||
Amendment Tracking: [0.15, 0.32]
|
||||
SHAMap Operations: [0.72, 0.45]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1.8 Observable Outcomes
|
||||
|
||||
> **TxQ** = Transaction Queue | **UNL** = Unique Node List
|
||||
|
||||
After implementing OpenTelemetry, operators and developers will gain visibility into the following:
|
||||
|
||||
### 1.8.1 What You Will See: Traces
|
||||
|
||||
| Trace Type | Description | Example Query in Grafana/Tempo |
|
||||
| -------------------------- | ------------------------------------------------------------------------------------------- | ----------------------------------------------- |
|
||||
| **Transaction Lifecycle** | Full journey from RPC submission through validation, relay, consensus, and ledger inclusion | `{service.name="xrpld" && tx_hash="ABC123..."}` |
|
||||
| **Cross-Node Propagation** | Transaction path across multiple xrpld nodes with timing | `{relay_count > 0}` |
|
||||
| **Consensus Rounds** | Complete round with all phases (open, establish, accept) | `{span.name=~"consensus.round.*"}` |
|
||||
| **RPC Request Processing** | Individual command execution with timing breakdown | `{command="account_info"}` |
|
||||
| **Ledger Acquisition** | Peer-to-peer ledger data requests and responses | `{span.name="ledger.acquire"}` |
|
||||
| **PathFinding Latency** | Path computation time and cache effectiveness for payment RPCs | `{span.name="pathfind.compute"}` |
|
||||
| **TxQ Behavior** | Queue depth, eviction patterns, fee escalation during congestion | `{span.name=~"txq.*"}` |
|
||||
| **Ledger Sync** | Full acquisition timeline including delta and transaction fetches | `{span.name=~"ledger.acquire.*"}` |
|
||||
| **Validator Health** | UNL fetch success, manifest updates, stale list detection | `{span.name=~"validator.*"}` |
|
||||
|
||||
### 1.8.2 What You Will See: Metrics (Derived from Traces)
|
||||
|
||||
| Metric | Description | Dashboard Panel |
|
||||
| ----------------------------- | --------------------------------------- | --------------------------- |
|
||||
| **RPC Latency (p50/p95/p99)** | Response time distribution per command | Heatmap by command |
|
||||
| **Transaction Throughput** | Transactions processed per second | Time series graph |
|
||||
| **Consensus Round Duration** | Time to complete consensus phases | Histogram |
|
||||
| **Cross-Node Latency** | Time for transaction to reach N nodes | Line chart with percentiles |
|
||||
| **Error Rate** | Failed transactions/RPC calls by type | Stacked bar chart |
|
||||
| **PathFinding Latency** | Path computation time per currency pair | Heatmap by currency |
|
||||
| **TxQ Depth** | Queued transactions over time | Time series with thresholds |
|
||||
| **Fee Escalation Level** | Current fee multiplier | Gauge with alert thresholds |
|
||||
| **Ledger Sync Duration** | Time to acquire missing ledgers | Histogram |
|
||||
|
||||
### 1.8.3 Concrete Dashboard Examples
|
||||
|
||||
**Transaction Trace View (Tempo):**
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Trace: abc123... (Transaction Submission) Duration: 847ms │
|
||||
├────────────────────────────────────────────────────────────────────────────────┤
|
||||
│ ├── rpc.request [ServerHandler] ████░░░░░░ 45ms │
|
||||
│ │ └── rpc.command.submit [RPCHandler] ████░░░░░░ 42ms │
|
||||
│ │ └── tx.receive [NetworkOPs] ███░░░░░░░ 35ms │
|
||||
│ │ ├── tx.validate [TxQ] █░░░░░░░░░ 8ms │
|
||||
│ │ └── tx.relay [Overlay] ██░░░░░░░░ 15ms │
|
||||
│ │ ├── tx.receive [Node-B] █████░░░░░ 52ms │
|
||||
│ │ │ └── tx.relay [Node-B] ██░░░░░░░░ 18ms │
|
||||
│ │ └── tx.receive [Node-C] ██████░░░░ 65ms │
|
||||
│ └── consensus.round [RCLConsensus] ████████░░ 720ms │
|
||||
│ ├── consensus.phase.open ██░░░░░░░░ 180ms │
|
||||
│ ├── consensus.phase.establish █████░░░░░ 480ms │
|
||||
│ └── consensus.phase.accept █░░░░░░░░░ 60ms │
|
||||
└────────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**RPC Performance Dashboard Panel:**
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ RPC Command Latency (Last 1 Hour) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Command │ p50 │ p95 │ p99 │ Errors │ Rate │
|
||||
│──────────────────┼────────┼────────┼────────┼────────┼──────│
|
||||
│ account_info │ 12ms │ 45ms │ 89ms │ 0.1% │ 150/s│
|
||||
│ submit │ 35ms │ 120ms │ 250ms │ 2.3% │ 45/s│
|
||||
│ ledger │ 8ms │ 25ms │ 55ms │ 0.0% │ 80/s│
|
||||
│ tx │ 15ms │ 50ms │ 100ms │ 0.5% │ 60/s│
|
||||
│ server_info │ 5ms │ 12ms │ 20ms │ 0.0% │ 200/s│
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Consensus Health Dashboard Panel:**
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
xyChart:
|
||||
width: 1200
|
||||
height: 400
|
||||
plotReservedSpacePercent: 50
|
||||
chartOrientation: vertical
|
||||
themeVariables:
|
||||
xyChart:
|
||||
plotColorPalette: "#3498db"
|
||||
---
|
||||
xychart-beta
|
||||
title "Consensus Round Duration (Last 24 Hours)"
|
||||
x-axis "Time of Day (Hours)" [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24]
|
||||
y-axis "Duration (seconds)" 1 --> 5
|
||||
line [2.1, 2.4, 2.8, 3.2, 3.8, 4.3, 4.5, 5.0, 4.7, 4.0, 3.2, 2.6, 2.0]
|
||||
```
|
||||
|
||||
### 1.8.4 Operator Actionable Insights
|
||||
|
||||
| Scenario | What You'll See | Action |
|
||||
| ------------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------ |
|
||||
| **Slow RPC** | Span showing which phase is slow (parsing, execution, serialization) | Optimize specific code path |
|
||||
| **Transaction Stuck** | Trace stops at validation; error attribute shows reason | Fix transaction parameters |
|
||||
| **Consensus Delay** | Phase.establish taking too long; proposer attribute shows missing validators | Investigate network connectivity |
|
||||
| **Memory Spike** | Large batch of spans correlating with memory increase | Tune batch_size or sampling |
|
||||
| **Network Partition** | Traces missing cross-node links for specific peer | Check peer connectivity |
|
||||
| **Path Computation Slow** | pathfind.compute span shows high latency; cache miss rate in attributes | Warm the RippleLineCache, check order book depth |
|
||||
| **TxQ Full** | txq.enqueue spans show evictions; fee.escalate spans increasing | Monitor fee levels, alert operators |
|
||||
| **Ledger Sync Stalled** | ledger.acquire spans timing out; peer reliability attributes show issues | Check peer connectivity, add trusted peers |
|
||||
| **UNL Stale** | validator.list.fetch spans failing; last_update attribute aging | Verify validator site URLs, check DNS |
|
||||
|
||||
### 1.8.5 Developer Debugging Workflow
|
||||
|
||||
1. **Find Transaction**: Query by `tx_hash` to get full trace
|
||||
2. **Identify Bottleneck**: Look at span durations to find slowest component
|
||||
3. **Check Attributes**: Review `validity`, `rpc_status` for errors
|
||||
4. **Correlate Logs**: Use `trace_id` to find related PerfLog entries
|
||||
5. **Compare Nodes**: Filter by `service.instance.id` to compare behavior across nodes
|
||||
|
||||
---
|
||||
|
||||
_Next: [Design Decisions](./02-design-decisions.md)_ | _Back to: [Overview](./OpenTelemetryPlan.md)_
|
||||
568
OpenTelemetryPlan/02-design-decisions.md
Normal file
568
OpenTelemetryPlan/02-design-decisions.md
Normal file
@@ -0,0 +1,568 @@
|
||||
# Design Decisions
|
||||
|
||||
> **Parent Document**: [OpenTelemetryPlan.md](./OpenTelemetryPlan.md)
|
||||
> **Related**: [Architecture Analysis](./01-architecture-analysis.md)
|
||||
|
||||
---
|
||||
|
||||
## 2.1 OpenTelemetry Components
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol
|
||||
|
||||
### 2.1.1 SDK Selection
|
||||
|
||||
**Primary Choice**: OpenTelemetry C++ SDK (`opentelemetry-cpp`)
|
||||
|
||||
| Component | Purpose | Required |
|
||||
| --------------------------------------- | ---------------------- | ------------------------- |
|
||||
| `opentelemetry-cpp::api` | Tracing API headers | Yes |
|
||||
| `opentelemetry-cpp::sdk` | SDK implementation | Yes |
|
||||
| `opentelemetry-cpp::ext` | Extensions (exporters) | Yes |
|
||||
| `opentelemetry-cpp::otlp_http_exporter` | OTLP/HTTP export | Yes (shipped in Phase 1b) |
|
||||
| `opentelemetry-cpp::otlp_grpc_exporter` | OTLP/gRPC export | Future (not yet wired up) |
|
||||
|
||||
### 2.1.2 Instrumentation Strategy
|
||||
|
||||
**Manual Instrumentation** (recommended):
|
||||
|
||||
| Approach | Pros | Cons |
|
||||
| ---------- | --------------------------------------------------------------- | ------------------------------------------------------- |
|
||||
| **Manual** | Precise control, optimized placement, xrpld-specific attributes | More development effort |
|
||||
| **Auto** | Less code, automatic coverage | Less control, potential overhead, limited customization |
|
||||
|
||||
---
|
||||
|
||||
## 2.2 Exporter Configuration
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph nodes["xrpld Nodes"]
|
||||
node1["xrpld<br/>Node 1"]
|
||||
node2["xrpld<br/>Node 2"]
|
||||
node3["xrpld<br/>Node 3"]
|
||||
end
|
||||
|
||||
collector["OpenTelemetry<br/>Collector<br/>(sidecar or standalone)"]
|
||||
|
||||
subgraph backends["Observability Backends"]
|
||||
tempo["Tempo"]
|
||||
elastic["Elastic<br/>APM"]
|
||||
end
|
||||
|
||||
node1 -->|"OTLP/HTTP<br/>:4318"| collector
|
||||
node2 -->|"OTLP/HTTP<br/>:4318"| collector
|
||||
node3 -->|"OTLP/HTTP<br/>:4318"| collector
|
||||
|
||||
collector --> tempo
|
||||
collector --> elastic
|
||||
|
||||
style nodes fill:#0d47a1,stroke:#082f6a,color:#ffffff
|
||||
style backends fill:#1b5e20,stroke:#0d3d14,color:#ffffff
|
||||
style collector fill:#bf360c,stroke:#8c2809,color:#ffffff
|
||||
```
|
||||
|
||||
**Reading the diagram:**
|
||||
|
||||
- **xrpld Nodes (blue)**: The source of telemetry data. Each xrpld node exports spans via OTLP/HTTP on port 4318 (the only exporter shipped in Phase 1b).
|
||||
- **OpenTelemetry Collector (red)**: The central aggregation point that receives spans from all nodes. Can run as a sidecar (per-node) or standalone (shared). Handles batching, filtering, and routing.
|
||||
- **Observability Backends (green)**: The storage and visualization destinations. Tempo is the recommended backend for both development and production, and Elastic APM is an alternative. The Collector routes to one or more backends.
|
||||
- **Arrows (nodes to collector to backends)**: The data pipeline -- spans flow from nodes to the Collector over HTTP, then the Collector fans out to the configured backends.
|
||||
|
||||
### 2.2.1 OTLP/HTTP (Shipped in Phase 1b)
|
||||
|
||||
OTLP/HTTP is the only exporter wired up in Phase 1b. It is configured via
|
||||
`OtlpHttpExporterOptions` with the collector traces endpoint
|
||||
(`http://localhost:4318/v1/traces` by default) and a JSON content type
|
||||
(binary protobuf is also available).
|
||||
|
||||
### 2.2.2 OTLP/gRPC (Future Work — Planned Upgrade)
|
||||
|
||||
OTLP/gRPC is planned as a future upgrade from the HTTP exporter. The gRPC
|
||||
transport offers lower per-span overhead and tighter back-pressure semantics
|
||||
than HTTP/JSON, making it attractive for production deployments once the HTTP
|
||||
path is validated in earlier phases.
|
||||
|
||||
Required to land this upgrade:
|
||||
|
||||
1. Add `opentelemetry-cpp::otlp_grpc_exporter` to the Conan recipe (the
|
||||
dependency already exists but is not linked in Phase 1b builds).
|
||||
2. Extend `TelemetryConfig.cpp` to parse an `exporter` key (`otlp_http`
|
||||
default, `otlp_grpc` opt-in) and a gRPC endpoint override.
|
||||
3. In `Telemetry::start()` branch on the parsed exporter type and construct
|
||||
either `OtlpHttpExporterFactory::Create(httpOpts)` or
|
||||
`OtlpGrpcExporterFactory::Create(grpcOpts)` accordingly.
|
||||
4. Update the runbook and dashboards to document the alternate port and TLS
|
||||
settings.
|
||||
|
||||
When wired up, the gRPC path will use `OtlpGrpcExporterOptions` configured with
|
||||
the collector endpoint (host on port 4317), TLS credentials enabled, and a CA
|
||||
certificate path.
|
||||
|
||||
Until that work lands, `OtlpGrpcExporterOptions` is **not** used by any code
|
||||
path in Phase 1b through Phase 5.
|
||||
|
||||
---
|
||||
|
||||
## 2.3 Span Naming Conventions
|
||||
|
||||
> **TxQ** = Transaction Queue | **UNL** = Unique Node List | **WS** = WebSocket
|
||||
|
||||
### 2.3.1 Naming Schema
|
||||
|
||||
```
|
||||
<component>.<operation>[.<sub-operation>]
|
||||
```
|
||||
|
||||
**Examples**:
|
||||
|
||||
- `tx.receive` - Transaction received from peer
|
||||
- `consensus.phase.establish` - Consensus establish phase
|
||||
- `rpc.command.server_info` - server_info RPC command
|
||||
|
||||
### 2.3.2 Complete Span Catalog
|
||||
|
||||
| Span name | Description |
|
||||
| ------------------------------ | --------------------------------------- |
|
||||
| `tx.receive` | Transaction received from network |
|
||||
| `tx.validate` | Transaction signature/format validation |
|
||||
| `tx.process` | Full transaction processing |
|
||||
| `tx.relay` | Transaction relay to peers |
|
||||
| `tx.apply` | Apply transaction to ledger |
|
||||
| `consensus.round` | Complete consensus round |
|
||||
| `consensus.phase.open` | Open phase - collecting transactions |
|
||||
| `consensus.phase.establish` | Establish phase - reaching agreement |
|
||||
| `consensus.phase.accept` | Accept phase - applying consensus |
|
||||
| `consensus.proposal.receive` | Receive peer proposal |
|
||||
| `consensus.proposal.send` | Send our proposal |
|
||||
| `consensus.validation.receive` | Receive peer validation |
|
||||
| `consensus.validation.send` | Send our validation |
|
||||
| `rpc.request` | HTTP/WebSocket request handling |
|
||||
| `rpc.command.*` | Specific RPC command (dynamic) |
|
||||
| `peer.connect` | Peer connection establishment |
|
||||
| `peer.disconnect` | Peer disconnection |
|
||||
| `peer.message.send` | Send protocol message |
|
||||
| `peer.message.receive` | Receive protocol message |
|
||||
| `ledger.acquire` | Ledger acquisition from network |
|
||||
| `ledger.build` | Build new ledger |
|
||||
| `ledger.validate` | Ledger validation |
|
||||
| `ledger.close` | Close ledger |
|
||||
| `ledger.replay` | Ledger replay executed |
|
||||
| `ledger.delta` | Delta-based ledger acquired |
|
||||
| `pathfind.request` | Path request initiated |
|
||||
| `pathfind.compute` | Path computation executed |
|
||||
| `txq.enqueue` | Transaction queued |
|
||||
| `txq.apply` | Queued transaction applied |
|
||||
| `fee.escalate` | Fee escalation triggered |
|
||||
| `validator.list.fetch` | UNL list fetched |
|
||||
| `validator.manifest` | Manifest update processed |
|
||||
| `amendment.vote` | Amendment voting executed |
|
||||
| `shamap.sync` | State tree synchronization |
|
||||
| `job.enqueue` | Job added to queue |
|
||||
| `job.execute` | Job execution |
|
||||
|
||||
### 2.3.3 Attribute Naming Conventions
|
||||
|
||||
Span **names** follow §2.3.1 (dotted `<component>.<operation>`). Span
|
||||
**attribute keys** follow the rules below. The constants in the `*SpanNames.h`
|
||||
headers are the single source of truth; the collector, Tempo, the Grafana
|
||||
dashboards, and the runbook all consume these exact keys, so every layer must
|
||||
agree with the code. A CI check enforces this end to end.
|
||||
|
||||
1. **Per-span unique attribute** → bare field name, allowed when the field is
|
||||
recorded by a single span/workflow so the span name already supplies the
|
||||
domain (e.g. `command`, `version`, `local` on `rpc.command`).
|
||||
2. **Shared attribute (same concept on more than one span)** → ONE key, reused
|
||||
verbatim on every span that records it; the span name tells the occurrences
|
||||
apart, so no per-emitter prefix is added. Name it by the field's meaning: a
|
||||
property of a domain object keeps that object's bare field name (`ledger_hash`,
|
||||
`ledger_seq`, `tx_hash`, `peer_id`, `full_validation`); a field already
|
||||
qualified by a sub-kind keeps that qualifier on every emitter (`proposal_trusted`
|
||||
on both `consensus.proposal.receive` and `peer.proposal.receive`;
|
||||
`validation_trusted` likewise). Defined once in the base `SpanNames.h`
|
||||
`namespace attr` block and re-exported (`using`) by each domain header.
|
||||
3. **Collision qualifier** → `<domain>_<field>`, only when a bare name would
|
||||
collide with a DIFFERENT concept in the shared spanmetrics label space or with
|
||||
the OTel-reserved `status` key (e.g. `rpc_status`, `grpc_status`,
|
||||
`consensus_phase`, `consensus_round`, `consensus_mode`). This disambiguates
|
||||
distinct concepts that share a word; it is NOT used to tag the same concept
|
||||
with its emitting workflow — that is rule 2 (one shared name).
|
||||
4. **Resource attribute** → dotted `xrpl.<subsystem>.<field>`, reserved ONLY
|
||||
for process/network identity set once at startup (`xrpl.network.id`,
|
||||
`xrpl.network.type`). Span attributes are never dotted in the `xrpl.` form —
|
||||
it blurs the resource/span scope boundary and parses awkwardly in TraceQL.
|
||||
5. **Span names** use `<subsystem>[.<component>]` (dotted, per §2.3.1). Only
|
||||
attribute _keys_ follow rules 1–4.
|
||||
|
||||
Standard OpenTelemetry semantic-convention keys keep their canonical dotted
|
||||
form (e.g. `service.*` resource attributes, `http.*` span attributes); the
|
||||
"no dotted form" rule applies to xrpl-custom keys only.
|
||||
|
||||
The same rules are recorded in `CONTRIBUTING.md` (the permanent home, since
|
||||
`OpenTelemetryPlan/` is removed once the rollout completes). The attribute
|
||||
examples in §2.4 below follow these rules.
|
||||
|
||||
---
|
||||
|
||||
## 2.4 Attribute Schema
|
||||
|
||||
> **TxQ** = Transaction Queue | **UNL** = Unique Node List | **OTLP** = OpenTelemetry Protocol
|
||||
|
||||
### 2.4.1 Resource Attributes (Set Once at Startup)
|
||||
|
||||
Resource attributes identify the process and are set once at startup. They use
|
||||
the standard OpenTelemetry semantic conventions plus custom dotted `xrpl.*`
|
||||
keys (the dotted form is reserved for resource scope per §2.3.3).
|
||||
|
||||
| Key | Type / value | Description |
|
||||
| --------------------- | ------------------------------------------------------- | ------------------------------ |
|
||||
| `service.name` | `"xrpld"` | Standard `SERVICE_NAME` |
|
||||
| `service.version` | `BuildInfo::getVersionString()` | Standard `SERVICE_VERSION` |
|
||||
| `service.instance.id` | node public key (base58) | Standard `SERVICE_INSTANCE_ID` |
|
||||
| `xrpl.network.id` | network id (e.g. 0 for mainnet) | Network identifier |
|
||||
| `xrpl.network.type` | `"mainnet"` \| `"testnet"` \| `"devnet"` \| `"unknown"` | Network kind |
|
||||
| `xrpl.node.type` | `"validator"` \| `"stock"` \| `"reporting"` | Node role |
|
||||
| `xrpl.node.cluster` | cluster name | Cluster name, if clustered |
|
||||
|
||||
### 2.4.2 Span Attributes by Category
|
||||
|
||||
> Span attribute keys use the underscore form from §2.3.3 (shared/qualified
|
||||
> keys are `<domain>_<field>`; per-span unique keys are bare). The dotted form
|
||||
> is reserved for the resource attributes in §2.4.1 above. This catalog lists
|
||||
> the planned attribute set by category; the exact emitted key for each
|
||||
> implemented span is defined by the `*SpanNames.h` constants, which are the
|
||||
> single source of truth where the two differ.
|
||||
|
||||
#### Transaction Attributes
|
||||
|
||||
| Key | Type | Description |
|
||||
| -------------- | ------ | ------------------------------------- |
|
||||
| `tx_hash` | string | Transaction hash (hex) |
|
||||
| `tx_type` | string | `"Payment"`, `"OfferCreate"`, etc. |
|
||||
| `tx_account` | string | Source account (redacted in prod) |
|
||||
| `tx_sequence` | int64 | Account sequence number |
|
||||
| `tx_fee` | int64 | Fee in drops |
|
||||
| `tx_result` | string | `"tesSUCCESS"`, `"tecPATH_DRY"`, etc. |
|
||||
| `ledger_index` | int64 | Ledger containing transaction |
|
||||
| `relay_count` | int64 | Peers the transaction was relayed to |
|
||||
| `suppressed` | bool | `true` when HashRouter dropped a dup |
|
||||
|
||||
#### Consensus Attributes
|
||||
|
||||
| Key | Type | Description |
|
||||
| -------------------- | ------- | ----------------------------------- |
|
||||
| `consensus_round` | int64 | Round number |
|
||||
| `consensus_phase` | string | `"open"`, `"establish"`, `"accept"` |
|
||||
| `consensus_mode` | string | `"proposing"`, `"observing"`, etc. |
|
||||
| `proposers` | int64 | Number of proposers |
|
||||
| `prev_ledger_prefix` | string | Previous ledger hash prefix |
|
||||
| `ledger_seq` | int64 | Ledger sequence |
|
||||
| `tx_count` | int64 | Transactions in consensus set |
|
||||
| `round_time_ms` | float64 | Round duration |
|
||||
|
||||
#### RPC Attributes
|
||||
|
||||
| Key | Type | Description |
|
||||
| ------------- | ------- | ----------------------------------------------------------------------------- |
|
||||
| `command` | string | Command name (per-span unique on `rpc.command`) |
|
||||
| `version` | int64 | API version |
|
||||
| `rpc_role` | string | `"admin"` or `"user"` (qualified — `role` is generic) |
|
||||
| `params` | string | Sanitized parameters (optional) |
|
||||
| `rpc_status` | string | Response status: `success` \| `error` (qualified — `status` is OTel-reserved) |
|
||||
| `duration_ms` | float64 | Request duration in milliseconds |
|
||||
|
||||
#### Peer & Message Attributes
|
||||
|
||||
| Key | Type | Description |
|
||||
| -------------------- | ------- | -------------------------- |
|
||||
| `peer_id` | string | Peer public key (base58) |
|
||||
| `peer_address` | string | IP:port |
|
||||
| `peer_latency_ms` | float64 | Measured latency |
|
||||
| `peer_cluster` | string | Cluster name if clustered |
|
||||
| `message_type` | string | Protocol message type name |
|
||||
| `message_size_bytes` | int64 | Message size |
|
||||
| `message_compressed` | bool | Whether compressed |
|
||||
|
||||
#### Ledger & Job Attributes
|
||||
|
||||
| Key | Type | Description |
|
||||
| ----------------- | ------- | --------------------- |
|
||||
| `ledger_hash` | string | Ledger hash |
|
||||
| `ledger_index` | int64 | Ledger sequence/index |
|
||||
| `close_time` | int64 | Close time (epoch) |
|
||||
| `ledger_tx_count` | int64 | Transaction count |
|
||||
| `job_type` | string | Job type name |
|
||||
| `job_queue_ms` | float64 | Time spent in queue |
|
||||
| `job_worker` | int64 | Worker thread ID |
|
||||
|
||||
#### PathFinding Attributes
|
||||
|
||||
| Key | Type | Description |
|
||||
| -------------------------- | ------ | ------------------------- |
|
||||
| `pathfind_source_currency` | string | Source currency code |
|
||||
| `pathfind_dest_currency` | string | Destination currency code |
|
||||
| `pathfind_path_count` | int64 | Number of paths found |
|
||||
| `pathfind_cache_hit` | bool | RippleLineCache hit |
|
||||
|
||||
#### TxQ Attributes
|
||||
|
||||
| Key | Type | Description |
|
||||
| --------------------- | ------ | --------------------------- |
|
||||
| `txq_queue_depth` | int64 | Current queue depth |
|
||||
| `txq_fee_level` | int64 | Fee level of transaction |
|
||||
| `txq_eviction_reason` | string | Why transaction was evicted |
|
||||
|
||||
#### Fee Attributes
|
||||
|
||||
| Key | Type | Description |
|
||||
| ---------------------- | ----- | ------------------------- |
|
||||
| `fee_load_factor` | int64 | Current load factor |
|
||||
| `fee_escalation_level` | int64 | Fee escalation multiplier |
|
||||
|
||||
#### Validator Attributes
|
||||
|
||||
| Key | Type | Description |
|
||||
| ------------------------ | ----- | ------------------------- |
|
||||
| `validator_list_size` | int64 | UNL size |
|
||||
| `validator_list_age_sec` | int64 | Seconds since last update |
|
||||
|
||||
#### Amendment Attributes
|
||||
|
||||
| Key | Type | Description |
|
||||
| ------------------ | ------ | -------------------------------------- |
|
||||
| `amendment_name` | string | Amendment name |
|
||||
| `amendment_status` | string | `"enabled"`, `"vetoed"`, `"supported"` |
|
||||
|
||||
#### SHAMap Attributes
|
||||
|
||||
| Key | Type | Description |
|
||||
| ---------------------- | ------- | --------------------------------------------- |
|
||||
| `shamap_type` | string | `"transaction"`, `"state"`, `"account_state"` |
|
||||
| `shamap_missing_nodes` | int64 | Number of missing nodes during sync |
|
||||
| `shamap_duration_ms` | float64 | Sync duration |
|
||||
|
||||
### 2.4.3 Data Collection Summary
|
||||
|
||||
The following table summarizes what data is collected by category:
|
||||
|
||||
| Category | Attributes Collected | Purpose |
|
||||
| --------------- | ------------------------------------------------------------------------------------------------- | ---------------------------- |
|
||||
| **Transaction** | `tx_hash`, `tx_type`, `tx_result`, `tx_fee`, `ledger_index` | Trace transaction lifecycle |
|
||||
| **Consensus** | `consensus_round`, `consensus_phase`, `consensus_mode`, `proposers`, `round_time_ms` | Analyze consensus timing |
|
||||
| **RPC** | `command`, `version`, `rpc_status`, `duration_ms` | Monitor RPC performance |
|
||||
| **Peer** | `peer_id` (public key), `peer_latency_ms`, `message_type`, `message_size_bytes` | Network topology analysis |
|
||||
| **Ledger** | `ledger_hash`, `ledger_index`, `close_time`, `ledger_tx_count` | Ledger progression tracking |
|
||||
| **Job** | `job_type`, `job_queue_ms`, `job_worker` | JobQueue performance |
|
||||
| **PathFinding** | `pathfind_source_currency`, `pathfind_dest_currency`, `pathfind_path_count`, `pathfind_cache_hit` | Payment path analysis |
|
||||
| **TxQ** | `txq_queue_depth`, `txq_fee_level`, `txq_eviction_reason` | Queue depth and fee tracking |
|
||||
| **Fee** | `fee_load_factor`, `fee_escalation_level` | Fee escalation monitoring |
|
||||
| **Validator** | `validator_list_size`, `validator_list_age_sec` | UNL health monitoring |
|
||||
| **Amendment** | `amendment_name`, `amendment_status` | Protocol upgrade tracking |
|
||||
| **SHAMap** | `shamap_type`, `shamap_missing_nodes`, `shamap_duration_ms` | State tree sync performance |
|
||||
|
||||
### 2.4.4 Privacy & Sensitive Data Policy
|
||||
|
||||
> **PII** = Personally Identifiable Information
|
||||
|
||||
OpenTelemetry instrumentation is designed to collect **operational metadata only**, never sensitive content.
|
||||
|
||||
#### Data NOT Collected
|
||||
|
||||
The following data is explicitly **excluded** from telemetry collection:
|
||||
|
||||
| Excluded Data | Reason |
|
||||
| ----------------------- | ----------------------------------------- |
|
||||
| **Private Keys** | Never exposed; not relevant to tracing |
|
||||
| **Account Balances** | Financial data; privacy sensitive |
|
||||
| **Transaction Amounts** | Financial data; privacy sensitive |
|
||||
| **Raw TX Payloads** | May contain sensitive memo/data fields |
|
||||
| **Personal Data** | No PII collected |
|
||||
| **IP Addresses** | Configurable; excluded by default in prod |
|
||||
|
||||
#### Privacy Protection Mechanisms
|
||||
|
||||
| Mechanism | Description |
|
||||
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Account Hashing** | `tx_account` is hashed at collector level before storage |
|
||||
| **Configurable Redaction** | Sensitive fields can be excluded via `[telemetry]` config section |
|
||||
| **Collector Tail Sampling** | xrpld head sampling is fixed at 1.0 (every span emitted); the collector retains ~10% of non-error traces, reducing stored data exposure |
|
||||
| **Local Control** | Node operators have full control over what gets exported |
|
||||
| **No Raw Payloads** | Transaction content is never recorded, only metadata (hash, type, result) |
|
||||
| **Collector-Level Filtering** | Additional redaction/hashing can be configured at OTel Collector |
|
||||
|
||||
#### Collector-Level Data Protection
|
||||
|
||||
The OpenTelemetry Collector can be configured (via an `attributes` processor)
|
||||
to hash or redact sensitive attributes before export — for example, hashing
|
||||
`tx_account`, deleting `peer_address` to drop IP addresses, and deleting
|
||||
`params` to redact request parameters.
|
||||
|
||||
#### Configuration Options for Privacy
|
||||
|
||||
In `xrpld.cfg`, operators control data collection granularity through the
|
||||
`[telemetry]` section. Besides `enabled`, per-component toggles
|
||||
(`trace_transactions`, `trace_consensus`, `trace_rpc`, `trace_peer` — the last
|
||||
often disabled due to high volume) select which spans are emitted, and
|
||||
redaction flags (`redact_account` to hash account addresses, `redact_peer_address`
|
||||
to remove peer IP addresses) control SDK-level redaction before export.
|
||||
|
||||
> **Note**: The `redact_account` configuration in `xrpld.cfg` controls SDK-level redaction before export, while collector-level filtering (see [Collector-Level Data Protection](#collector-level-data-protection) above) provides an additional defense-in-depth layer. Both can operate independently.
|
||||
|
||||
> **Key Principle**: Telemetry collects **operational metadata** (timing, counts, hashes) — never **sensitive content** (keys, balances, amounts, raw payloads).
|
||||
|
||||
---
|
||||
|
||||
## 2.5 Context Propagation Design
|
||||
|
||||
> **WS** = WebSocket
|
||||
|
||||
### 2.5.1 Propagation Boundaries
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph http["HTTP/WebSocket (RPC)"]
|
||||
w3c["W3C Trace Context Headers:<br/>traceparent:<br/>00-trace_id-span_id-flags<br/>tracestate: xrpld=..."]
|
||||
end
|
||||
|
||||
subgraph protobuf["Protocol Buffers (P2P)"]
|
||||
proto["message TraceContext {<br/> bytes trace_id = 1; // 16 bytes<br/> bytes span_id = 2; // 8 bytes<br/> uint32 trace_flags = 3;<br/> string trace_state = 4;<br/>}"]
|
||||
end
|
||||
|
||||
subgraph jobqueue["JobQueue (Internal Async)"]
|
||||
job["Context captured at job creation,<br/>restored at execution<br/><br/>class Job {<br/> otel::context::Context<br/> traceContext_;<br/>};"]
|
||||
end
|
||||
|
||||
style http fill:#0d47a1,stroke:#082f6a,color:#ffffff
|
||||
style protobuf fill:#1b5e20,stroke:#0d3d14,color:#ffffff
|
||||
style jobqueue fill:#bf360c,stroke:#8c2809,color:#ffffff
|
||||
```
|
||||
|
||||
**Reading the diagram:**
|
||||
|
||||
- **HTTP/WebSocket - RPC (blue)**: For client-facing RPC requests, trace context is propagated using the W3C `traceparent` header. This is the standard approach and works with any OTel-compatible client.
|
||||
- **Protocol Buffers - P2P (green)**: For peer-to-peer messages between xrpld nodes, trace context is embedded as a protobuf `TraceContext` message carrying trace_id, span_id, flags, and optional trace_state.
|
||||
- **JobQueue - Internal Async (red)**: For asynchronous work within a single node, the OTel context is captured when a job is created and restored when the job executes on a worker thread. This bridges the async gap so spans remain linked.
|
||||
|
||||
---
|
||||
|
||||
## 2.6 Integration with Existing Observability
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol | **WS** = WebSocket
|
||||
|
||||
### 2.6.1 Existing Frameworks Comparison
|
||||
|
||||
xrpld already has two observability mechanisms. OpenTelemetry complements (not replaces) them:
|
||||
|
||||
| Aspect | PerfLog | Beast Insight (StatsD) | OpenTelemetry |
|
||||
| --------------------- | ----------------------------- | ---------------------------- | ------------------------- |
|
||||
| **Type** | Logging | Metrics | Distributed Tracing |
|
||||
| **Data** | JSON log entries | Counters, gauges, histograms | Spans with context |
|
||||
| **Scope** | Single node | Single node | **Cross-node** |
|
||||
| **Output** | `perf.log` file | StatsD server | OTLP Collector |
|
||||
| **Question answered** | "What happened on this node?" | "How many? How fast?" | "What was the journey?" |
|
||||
| **Correlation** | By timestamp | By metric name | By `trace_id` |
|
||||
| **Overhead** | Low (file I/O) | Low (UDP packets) | Low-Medium (configurable) |
|
||||
|
||||
### 2.6.2 What Each Framework Does Best
|
||||
|
||||
#### PerfLog
|
||||
|
||||
- **Purpose**: Detailed local event logging for RPC and job execution
|
||||
- **Strengths**:
|
||||
- Rich JSON output with timing data
|
||||
- Already integrated in RPC handlers
|
||||
- File-based, no external dependencies
|
||||
- **Limitations**:
|
||||
- Single-node only (no cross-node correlation)
|
||||
- No parent-child relationships between events
|
||||
- Manual log parsing required
|
||||
|
||||
A PerfLog entry is a JSON object with fields such as `time`, `method`,
|
||||
`duration_us`, and `result`.
|
||||
|
||||
#### Beast Insight (StatsD)
|
||||
|
||||
- **Purpose**: Real-time metrics for monitoring dashboards
|
||||
- **Strengths**:
|
||||
- Aggregated metrics (counters, gauges, histograms)
|
||||
- Low overhead (UDP, fire-and-forget)
|
||||
- Good for alerting thresholds
|
||||
- **Limitations**:
|
||||
- No request-level detail
|
||||
- No causal relationships
|
||||
- Single-node perspective
|
||||
|
||||
In xrpld, Beast Insight is used through `increment` (counters), `gauge`
|
||||
(point-in-time values), and `timing` (durations) calls.
|
||||
|
||||
#### OpenTelemetry (NEW)
|
||||
|
||||
- **Purpose**: Distributed request tracing across nodes
|
||||
- **Strengths**:
|
||||
- **Cross-node correlation** via `trace_id`
|
||||
- Parent-child span relationships
|
||||
- Rich attributes per span
|
||||
- Industry standard (CNCF)
|
||||
- **Limitations**:
|
||||
- Requires collector infrastructure
|
||||
- Higher complexity than logging
|
||||
|
||||
A span is created via `startSpan` (e.g. `"tx.relay"`), annotated with
|
||||
attributes such as `tx_hash` and `peer_id`, and is automatically linked to its
|
||||
parent through the active context.
|
||||
|
||||
### 2.6.3 When to Use Each
|
||||
|
||||
| Scenario | PerfLog | StatsD | OpenTelemetry |
|
||||
| --------------------------------------- | ---------- | ------ | ------------- |
|
||||
| "How many TXs per second?" | ❌ | ✅ | ✅ |
|
||||
| "What's the p99 RPC latency?" | ❌ | ✅ | ✅ |
|
||||
| "Why was this specific TX slow?" | ⚠️ partial | ❌ | ✅ |
|
||||
| "Which node delayed consensus?" | ❌ | ❌ | ✅ |
|
||||
| "What happened on node X at time T?" | ✅ | ❌ | ✅ |
|
||||
| "Show me the TX journey across 5 nodes" | ❌ | ❌ | ✅ |
|
||||
|
||||
### 2.6.4 Coexistence Strategy
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph xrpld["xrpld Process"]
|
||||
perflog["PerfLog<br/>(JSON to file)"]
|
||||
insight["Beast Insight<br/>(StatsD)"]
|
||||
otel["OpenTelemetry<br/>(Tracing)"]
|
||||
end
|
||||
|
||||
perflog --> perffile["perf.log"]
|
||||
insight --> statsd["StatsD Server"]
|
||||
otel --> collector["OTLP Collector"]
|
||||
|
||||
perffile --> grafana["Grafana<br/>(Unified UI)"]
|
||||
statsd --> grafana
|
||||
collector --> grafana
|
||||
|
||||
style xrpld fill:#212121,stroke:#0a0a0a,color:#ffffff
|
||||
style grafana fill:#bf360c,stroke:#8c2809,color:#ffffff
|
||||
```
|
||||
|
||||
**Reading the diagram:**
|
||||
|
||||
- **xrpld Process (dark gray)**: The single xrpld node running all three observability frameworks side by side. Each framework operates independently with no interference.
|
||||
- **PerfLog to perf.log**: PerfLog writes JSON-formatted event logs to a local file. Grafana can ingest these via Loki or a file-based datasource.
|
||||
- **Beast Insight to StatsD Server**: Insight sends aggregated metrics (counters, gauges) over UDP to a StatsD server. Grafana reads from StatsD-compatible backends like Graphite or Prometheus (via StatsD exporter).
|
||||
- **OpenTelemetry to OTLP Collector**: OTel exports spans over OTLP/HTTP to a Collector, which then forwards to a trace backend (Tempo). (OTLP/gRPC is future work — §2.2.2.)
|
||||
- **Grafana (red, unified UI)**: All three data streams converge in Grafana, enabling operators to correlate logs, metrics, and traces in a single dashboard.
|
||||
|
||||
### 2.6.5 Correlation with PerfLog
|
||||
|
||||
Trace IDs can be correlated with existing PerfLog entries for comprehensive
|
||||
debugging. The design is for `RPCHandler.cpp` to start an `rpc.command.<method>`
|
||||
span alongside the existing PerfLog `rpcStart`/`rpcFinish`/`rpcError` calls,
|
||||
extract the span's `trace_id` (when valid), and eventually stamp it onto the
|
||||
PerfLog entry (a planned `setTraceId` hook) so logs and traces share a key. The
|
||||
span status is set to OK on success or to error (recording the exception) on
|
||||
failure.
|
||||
|
||||
---
|
||||
|
||||
_Previous: [Architecture Analysis](./01-architecture-analysis.md)_ | _Next: [Implementation Strategy](./03-implementation-strategy.md)_ | _Back to: [Overview](./OpenTelemetryPlan.md)_
|
||||
488
OpenTelemetryPlan/03-implementation-strategy.md
Normal file
488
OpenTelemetryPlan/03-implementation-strategy.md
Normal file
@@ -0,0 +1,488 @@
|
||||
# Implementation Strategy
|
||||
|
||||
> **Parent Document**: [OpenTelemetryPlan.md](./OpenTelemetryPlan.md)
|
||||
> **Related**: [Configuration Reference](./05-configuration-reference.md)
|
||||
|
||||
---
|
||||
|
||||
## 3.1 Directory Structure
|
||||
|
||||
The telemetry implementation follows xrpld's existing code organization pattern:
|
||||
|
||||
```
|
||||
include/xrpl/
|
||||
├── telemetry/
|
||||
│ ├── Telemetry.h # Main telemetry interface
|
||||
│ ├── TelemetryConfig.h # Configuration structures
|
||||
│ ├── TraceContext.h # Context propagation utilities
|
||||
│ ├── SpanGuard.h # RAII span management
|
||||
│ └── SpanAttributes.h # Attribute helper functions
|
||||
|
||||
src/libxrpl/
|
||||
├── telemetry/
|
||||
│ ├── Telemetry.cpp # Implementation
|
||||
│ ├── TelemetryConfig.cpp # Config parsing
|
||||
│ ├── TraceContext.cpp # Context serialization
|
||||
│ └── NullTelemetry.cpp # No-op implementation
|
||||
|
||||
src/xrpld/
|
||||
├── telemetry/
|
||||
│ ├── TracingInstrumentation.h # Instrumentation macros
|
||||
│ └── TracingInstrumentation.cpp
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3.2 Implementation Approach
|
||||
|
||||
<div align="center">
|
||||
|
||||
```mermaid
|
||||
%%{init: {'flowchart': {'nodeSpacing': 20, 'rankSpacing': 30}}}%%
|
||||
flowchart TB
|
||||
subgraph phase1["Phase 1: Core"]
|
||||
direction LR
|
||||
sdk["SDK Integration"] ~~~ interface["Telemetry Interface"] ~~~ config["Configuration"]
|
||||
end
|
||||
|
||||
subgraph phase2["Phase 2: RPC"]
|
||||
direction LR
|
||||
http["HTTP Context"] ~~~ rpc["RPC Handlers"]
|
||||
end
|
||||
|
||||
subgraph phase3["Phase 3: P2P"]
|
||||
direction LR
|
||||
proto["Protobuf Context"] ~~~ tx["Transaction Relay"]
|
||||
end
|
||||
|
||||
subgraph phase4["Phase 4: Consensus"]
|
||||
direction LR
|
||||
consensus["Consensus Rounds"] ~~~ proposals["Proposals"]
|
||||
end
|
||||
|
||||
phase1 --> phase2 --> phase3 --> phase4
|
||||
|
||||
style phase1 fill:#1565c0,stroke:#0d47a1,color:#ffffff
|
||||
style phase2 fill:#2e7d32,stroke:#1b5e20,color:#ffffff
|
||||
style phase3 fill:#e65100,stroke:#bf360c,color:#ffffff
|
||||
style phase4 fill:#c2185b,stroke:#880e4f,color:#ffffff
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
### Key Principles
|
||||
|
||||
1. **Minimal Intrusion**: Instrumentation should not alter existing control flow
|
||||
2. **Zero-Cost When Disabled**: Use compile-time flags and no-op implementations
|
||||
3. **Backward Compatibility**: Protocol Buffer extensions use high field numbers
|
||||
4. **Graceful Degradation**: Tracing failures must not affect node operation
|
||||
|
||||
---
|
||||
|
||||
## 3.3 Performance Overhead Summary
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol
|
||||
|
||||
| Metric | Overhead | Notes |
|
||||
| ------------- | ---------- | ------------------------------------------------ |
|
||||
| CPU | 1-3% | Of per-transaction CPU cost (~200μs baseline) |
|
||||
| Memory | ~10 MB | SDK statics + batch buffer + worker thread stack |
|
||||
| Network | 10-50 KB/s | Compressed OTLP export to collector |
|
||||
| Latency (p99) | <2% | With proper sampling configuration |
|
||||
|
||||
---
|
||||
|
||||
## 3.4 Detailed CPU Overhead Analysis
|
||||
|
||||
### 3.4.1 Per-Operation Costs
|
||||
|
||||
> **Note on hardware assumptions**: The costs below are based on the official OTel C++ SDK CI benchmarks
|
||||
> (969 runs on GitHub Actions 2-core shared runners). On production server hardware (3+ GHz Xeon),
|
||||
> expect costs at the **lower end** of each range (~30-50% improvement over CI hardware).
|
||||
|
||||
| Operation | Time (ns) | Frequency | Impact |
|
||||
| --------------------- | --------- | ---------------------- | ---------- |
|
||||
| Span creation | 500-1000 | Every traced operation | Low |
|
||||
| Span end | 100-200 | Every traced operation | Low |
|
||||
| SetAttribute (string) | 80-120 | 3-5 per span | Low |
|
||||
| SetAttribute (int) | 40-60 | 2-3 per span | Negligible |
|
||||
| AddEvent | 100-200 | 0-2 per span | Low |
|
||||
| Context injection | 150-250 | Per outgoing message | Low |
|
||||
| Context extraction | 100-180 | Per incoming message | Low |
|
||||
| GetCurrent context | 10-20 | Thread-local access | Negligible |
|
||||
|
||||
**Source**: Span creation based on OTel C++ SDK `BM_SpanCreation` benchmark (AlwaysOnSampler +
|
||||
SimpleSpanProcessor + InMemoryExporter), median ~1,000 ns on CI hardware. AddEvent includes
|
||||
timestamp read + string copy + vector push + mutex acquisition. Context injection/extraction
|
||||
confirmed by `BM_SpanCreationWithScope` benchmark delta (~160 ns).
|
||||
|
||||
### 3.4.2 Transaction Processing Overhead
|
||||
|
||||
<div align="center">
|
||||
|
||||
```mermaid
|
||||
%%{init: {'pie': {'textPosition': 0.75}}}%%
|
||||
pie showData
|
||||
"tx.receive (1400ns)" : 1400
|
||||
"tx.validate (1200ns)" : 1200
|
||||
"tx.relay (1200ns)" : 1200
|
||||
"Context inject (200ns)" : 200
|
||||
```
|
||||
|
||||
**Transaction Tracing Overhead (~4.0μs total)**
|
||||
|
||||
</div>
|
||||
|
||||
**Overhead percentage**: 4.0 μs / 200 μs (avg tx processing) = **~2.0%**
|
||||
|
||||
> **Breakdown**: Each span (tx.receive, tx.validate, tx.relay) costs ~1,000 ns for creation plus
|
||||
> ~200-400 ns for 3-5 attribute sets. Context injection is ~200 ns (confirmed by benchmarks).
|
||||
> On production hardware, expect ~2.6 μs total (~1.3% overhead) due to faster span creation (~500-600 ns).
|
||||
|
||||
### 3.4.3 Consensus Round Overhead
|
||||
|
||||
| Operation | Count | Cost (ns) | Total |
|
||||
| ---------------------- | ----- | --------- | ---------- |
|
||||
| consensus.round span | 1 | ~1200 | ~1.2 μs |
|
||||
| consensus.phase spans | 3 | ~1100 | ~3.3 μs |
|
||||
| proposal.receive spans | ~20 | ~1100 | ~22 μs |
|
||||
| proposal.send spans | ~3 | ~1100 | ~3.3 μs |
|
||||
| Context operations | ~30 | ~200 | ~6 μs |
|
||||
| **TOTAL** | | | **~36 μs** |
|
||||
|
||||
> **Why higher**: Each span costs ~1,000 ns creation + ~100-200 ns for 1-2 attributes, totaling ~1,100-1,200 ns.
|
||||
> Context operations remain ~200 ns (confirmed by benchmarks). On production hardware, expect ~24 μs total.
|
||||
|
||||
**Overhead percentage**: 36 μs / 3s (typical round) = **~0.001%** (negligible)
|
||||
|
||||
### 3.4.4 RPC Request Overhead
|
||||
|
||||
| Operation | Cost (ns) |
|
||||
| ---------------- | ------------ |
|
||||
| rpc.request span | ~1200 |
|
||||
| rpc.command span | ~1100 |
|
||||
| Context extract | ~250 |
|
||||
| Context inject | ~200 |
|
||||
| **TOTAL** | **~2.75 μs** |
|
||||
|
||||
> **Why higher**: Each span costs ~1,000 ns creation + ~100-200 ns for attributes (command name,
|
||||
> version, role). Context extract/inject costs are confirmed by OTel C++ benchmarks.
|
||||
|
||||
- Fast RPC (1ms): 2.75 μs / 1ms = **~0.275%**
|
||||
- Slow RPC (100ms): 2.75 μs / 100ms = **~0.003%**
|
||||
|
||||
---
|
||||
|
||||
## 3.5 Memory Overhead Analysis
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol
|
||||
|
||||
### 3.5.1 Static Memory
|
||||
|
||||
| Component | Size | Allocated |
|
||||
| ------------------------------------ | ----------- | ---------- |
|
||||
| TracerProvider singleton | ~64 KB | At startup |
|
||||
| BatchSpanProcessor (circular buffer) | ~16 KB | At startup |
|
||||
| BatchSpanProcessor (worker thread) | ~8 MB | At startup |
|
||||
| OTLP/HTTP exporter (client init) | ~64 KB | At startup |
|
||||
| Propagator registry | ~8 KB | At startup |
|
||||
| **Total static** | **~8.1 MB** | |
|
||||
|
||||
> **Why higher than earlier estimate**: The BatchSpanProcessor's circular buffer itself is only ~16 KB
|
||||
> (2049 x 8-byte `AtomicUniquePtr` entries), but it spawns a dedicated worker thread whose default
|
||||
> stack size on Linux is ~8 MB. The OTLP/HTTP exporter allocates a small client and TLS
|
||||
> initialization buffer. The worker thread stack dominates the static footprint.
|
||||
|
||||
### 3.5.2 Dynamic Memory
|
||||
|
||||
| Component | Size per unit | Max units | Peak |
|
||||
| -------------------- | -------------- | ---------- | --------------- |
|
||||
| Active span | ~500-800 bytes | 1000 | ~500-800 KB |
|
||||
| Queued span (export) | ~500 bytes | 2048 | ~1 MB |
|
||||
| Attribute storage | ~80 bytes | 5 per span | Included |
|
||||
| Context storage | ~64 bytes | Per thread | ~6.4 KB |
|
||||
| **Total dynamic** | | | **~1.5-1.8 MB** |
|
||||
|
||||
> **Why active spans are larger**: An active `Span` object includes the wrapper (~88 bytes: shared_ptr,
|
||||
> mutex, unique_ptr to Recordable) plus `SpanData` (~250 bytes: SpanContext, timestamps, name, status,
|
||||
> empty containers) plus attribute storage (~200-500 bytes for 3-5 string attributes in a `std::map`).
|
||||
> Source: `sdk/src/trace/span.h` and `sdk/include/opentelemetry/sdk/trace/span_data.h`.
|
||||
> Queued spans release the wrapper, keeping only `SpanData` + attributes (~500 bytes).
|
||||
|
||||
### 3.5.3 Memory Growth Characteristics
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
xyChart:
|
||||
width: 700
|
||||
height: 400
|
||||
---
|
||||
xychart-beta
|
||||
title "Memory Usage vs Span Rate (bounded by queue limit)"
|
||||
x-axis "Spans/second" [0, 200, 400, 600, 800, 1000]
|
||||
y-axis "Memory (MB)" 0 --> 12
|
||||
line [8.5, 9.2, 9.6, 9.9, 10.0, 10.0]
|
||||
```
|
||||
|
||||
**Notes**:
|
||||
|
||||
- Memory increases with span rate but **plateaus at queue capacity** (default 2048 spans)
|
||||
- Batch export prevents unbounded growth
|
||||
- At queue limit, oldest spans are dropped (not blocked)
|
||||
- Maximum memory is bounded: ~8.3 MB static (dominated by worker thread stack) + 2048 queued spans x ~500 bytes (~1 MB) + active spans (~0.8 MB) ≈ **~10 MB ceiling**
|
||||
- The worker thread stack (~8 MB) is virtual memory; actual RSS depends on stack usage (typically much less)
|
||||
|
||||
> **Measured outcome**: A perf-iac comparison (telemetry compiled-in + enabled vs compiled-out,
|
||||
> 9 nodes — validators and client-handlers — under sustained payment load) recorded **no measurable
|
||||
> RSS increase over the telemetry-off baseline** (~15 GiB mean / ~18–19 GiB peak on both sides),
|
||||
> with no OOM, no swap, and no leak across the run. The ~10 MB ceiling above is therefore a
|
||||
> provisioning safety margin (dominated by virtual thread-stack address space), not an expected
|
||||
> resident-memory increase. Steady-state cost shows up as throughput (~3–4% at head sampling 1.0),
|
||||
> not memory.
|
||||
|
||||
### 3.5.4 Performance Data Sources
|
||||
|
||||
The overhead estimates in Sections 3.3-3.5 are derived from the following sources:
|
||||
|
||||
| Source | What it covers | URL |
|
||||
| ------------------------------------------------ | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| OTel C++ SDK CI benchmarks (969 runs) | Span creation, context activation, sampler overhead | [Benchmark Dashboard](https://open-telemetry.github.io/opentelemetry-cpp/benchmarks/) |
|
||||
| `api/test/trace/span_benchmark.cc` | API-level span creation (~22 ns no-op) | [Source](https://github.com/open-telemetry/opentelemetry-cpp/blob/main/api/test/trace/span_benchmark.cc) |
|
||||
| `sdk/test/trace/sampler_benchmark.cc` | SDK span creation with samplers (~1,000 ns AlwaysOn) | [Source](https://github.com/open-telemetry/opentelemetry-cpp/blob/main/sdk/test/trace/sampler_benchmark.cc) |
|
||||
| `sdk/include/.../span_data.h` | SpanData memory layout (~250 bytes base) | [Source](https://github.com/open-telemetry/opentelemetry-cpp/blob/main/sdk/include/opentelemetry/sdk/trace/span_data.h) |
|
||||
| `sdk/src/trace/span.h` | Span wrapper memory layout (~88 bytes) | [Source](https://github.com/open-telemetry/opentelemetry-cpp/blob/main/sdk/src/trace/span.h) |
|
||||
| `sdk/include/.../batch_span_processor_options.h` | Default queue size (2048), batch size (512) | [Source](https://github.com/open-telemetry/opentelemetry-cpp/blob/main/sdk/include/opentelemetry/sdk/trace/batch_span_processor_options.h) |
|
||||
| `sdk/include/.../circular_buffer.h` | CircularBuffer implementation (AtomicUniquePtr array) | [Source](https://github.com/open-telemetry/opentelemetry-cpp/blob/main/sdk/include/opentelemetry/sdk/common/circular_buffer.h) |
|
||||
| OTLP proto definition | Serialized span size estimation | [Proto](https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/trace/v1/trace.proto) |
|
||||
|
||||
---
|
||||
|
||||
## 3.6 Network Overhead Analysis
|
||||
|
||||
### 3.6.1 Export Bandwidth
|
||||
|
||||
> **Bytes per span**: Estimates use ~500 bytes/span (conservative upper bound). OTLP protobuf analysis
|
||||
> shows a typical span with 3-5 string attributes serializes to ~200-300 bytes raw; with gzip
|
||||
> compression (~60-70% of raw) and batching (amortized headers), ~350 bytes/span is more realistic.
|
||||
> The table uses the conservative estimate for capacity planning.
|
||||
|
||||
| Sampling Rate | Spans/sec | Bandwidth | Notes |
|
||||
| ------------- | --------- | --------- | ---------------- |
|
||||
| 100% | ~500 | ~250 KB/s | Development only |
|
||||
| 10% | ~50 | ~25 KB/s | Staging |
|
||||
| 1% | ~5 | ~2.5 KB/s | Production |
|
||||
| Error-only | ~1 | ~0.5 KB/s | Minimal overhead |
|
||||
|
||||
### 3.6.2 Trace Context Propagation
|
||||
|
||||
| Message Type | Context Size | Messages/sec | Overhead |
|
||||
| ---------------------- | ------------ | ------------ | ----------- |
|
||||
| TMTransaction | 25 bytes | ~100 | ~2.5 KB/s |
|
||||
| TMProposeSet | 25 bytes | ~10 | ~250 B/s |
|
||||
| TMValidation | 25 bytes | ~50 | ~1.25 KB/s |
|
||||
| **Total P2P overhead** | | | **~4 KB/s** |
|
||||
|
||||
---
|
||||
|
||||
## 3.7 Optimization Strategies
|
||||
|
||||
### 3.7.1 Sampling Strategies
|
||||
|
||||
#### Tail Sampling
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
trace["New Trace"]
|
||||
|
||||
trace --> errors{"Is Error?"}
|
||||
errors -->|Yes| sample["SAMPLE"]
|
||||
errors -->|No| consensus{"Is Consensus?"}
|
||||
|
||||
consensus -->|Yes| sample
|
||||
consensus -->|No| slow{"Is Slow?"}
|
||||
|
||||
slow -->|Yes| sample
|
||||
slow -->|No| prob{"Random < 10%?"}
|
||||
|
||||
prob -->|Yes| sample
|
||||
prob -->|No| drop["DROP"]
|
||||
|
||||
style sample fill:#4caf50,stroke:#388e3c,color:#fff
|
||||
style drop fill:#f44336,stroke:#c62828,color:#fff
|
||||
```
|
||||
|
||||
### 3.7.2 Batch Tuning Recommendations
|
||||
|
||||
| Environment | Batch Size | Batch Delay | Max Queue |
|
||||
| ------------------ | ---------- | ----------- | --------- |
|
||||
| Low-latency | 128 | 1000ms | 512 |
|
||||
| High-throughput | 1024 | 10000ms | 8192 |
|
||||
| Memory-constrained | 256 | 2000ms | 512 |
|
||||
|
||||
### 3.7.3 Conditional Instrumentation
|
||||
|
||||
Instrumentation is gated on two levels. A compile-time feature flag (`XRPL_ENABLE_TELEMETRY`) reduces the trace macros to no-ops when telemetry is built out, so disabled builds carry zero cost. At runtime, per-component guards (e.g. `shouldTracePeer()`) skip span creation for components whose tracing is turned off, incurring no overhead beyond a single boolean check.
|
||||
|
||||
---
|
||||
|
||||
## 3.8 Links to Detailed Documentation
|
||||
|
||||
- **[Configuration Reference](./05-configuration-reference.md)**: Configuration options and collector setup
|
||||
- **[Implementation Phases](./06-implementation-phases.md)**: Detailed timeline and milestones
|
||||
|
||||
---
|
||||
|
||||
## 3.9 Code Intrusiveness Assessment
|
||||
|
||||
> **TxQ** = Transaction Queue
|
||||
|
||||
This section provides a detailed assessment of how intrusive the OpenTelemetry integration is to the existing xrpld codebase.
|
||||
|
||||
### 3.9.1 Files Modified Summary
|
||||
|
||||
| Component | Files Modified | Lines Added | Lines Changed | Architectural Impact |
|
||||
| --------------------- | -------------- | ----------- | ------------- | -------------------- |
|
||||
| **Core Telemetry** | 11 new files | ~980 | 0 | None (new module) |
|
||||
| **Application Init** | 2 files | ~30 | ~5 | Minimal |
|
||||
| **RPC Layer** | 3 files | ~80 | ~20 | Minimal |
|
||||
| **Transaction Relay** | 4 files | ~120 | ~40 | Low |
|
||||
| **Consensus** | 3 files | ~100 | ~30 | Low-Medium |
|
||||
| **Protocol Buffers** | 1 file | ~25 | 0 | Low |
|
||||
| **CMake/Build** | 3 files | ~50 | ~10 | Minimal |
|
||||
| **PathFinding** | 2 | ~80 | ~5 | Minimal |
|
||||
| **TxQ/Fee** | 2 | ~60 | ~5 | Minimal |
|
||||
| **Validator/Amend** | 3 | ~40 | ~5 | Minimal |
|
||||
| **Total** | **~34 files** | **~1,670** | **~120** | **Low** |
|
||||
|
||||
### 3.9.2 Detailed File Impact
|
||||
|
||||
```mermaid
|
||||
pie title Code Changes by Component
|
||||
"New Telemetry Module" : 800
|
||||
"Transaction Relay" : 160
|
||||
"Consensus" : 130
|
||||
"RPC Layer" : 100
|
||||
"PathFinding" : 80
|
||||
"TxQ/Fee" : 60
|
||||
"Validator/Amendment" : 40
|
||||
"Application Init" : 35
|
||||
"Protocol Buffers" : 25
|
||||
"Build System" : 60
|
||||
```
|
||||
|
||||
#### New Files (No Impact on Existing Code)
|
||||
|
||||
| File | Lines | Purpose |
|
||||
| ------------------------------------------------ | ----- | ------------------------ |
|
||||
| `include/xrpl/telemetry/Telemetry.h` | ~160 | Main interface |
|
||||
| `include/xrpl/telemetry/TelemetryConfig.h` | ~80 | Configuration structures |
|
||||
| `include/xrpl/telemetry/TraceContext.h` | ~80 | Context propagation |
|
||||
| `include/xrpl/telemetry/SpanGuard.h` | ~120 | RAII wrapper |
|
||||
| `include/xrpl/telemetry/SpanAttributes.h` | ~60 | Attribute helpers |
|
||||
| `src/libxrpl/telemetry/Telemetry.cpp` | ~200 | Implementation |
|
||||
| `src/libxrpl/telemetry/TelemetryConfig.cpp` | ~60 | Config parsing |
|
||||
| `src/libxrpl/telemetry/TraceContext.cpp` | ~80 | Context serialization |
|
||||
| `src/libxrpl/telemetry/NullTelemetry.cpp` | ~40 | No-op implementation |
|
||||
| `src/xrpld/telemetry/TracingInstrumentation.h` | ~60 | Macros |
|
||||
| `src/xrpld/telemetry/TracingInstrumentation.cpp` | ~40 | Instrumentation impl |
|
||||
|
||||
#### Modified Files (Existing Xrpld Code)
|
||||
|
||||
| File | Lines Added | Lines Changed | Risk Level |
|
||||
| ------------------------------------------------- | ----------- | ------------- | ---------- |
|
||||
| `src/xrpld/app/main/Application.cpp` | ~15 | ~3 | Low |
|
||||
| `include/xrpl/core/ServiceRegistry.h` | ~5 | ~2 | Low |
|
||||
| `src/xrpld/rpc/detail/ServerHandler.cpp` | ~40 | ~10 | Low |
|
||||
| `src/xrpld/rpc/handlers/*.cpp` | ~30 | ~8 | Low |
|
||||
| `src/xrpld/overlay/detail/PeerImp.cpp` | ~60 | ~15 | Medium |
|
||||
| `src/xrpld/overlay/detail/OverlayImpl.cpp` | ~30 | ~10 | Medium |
|
||||
| `src/xrpld/app/consensus/RCLConsensus.cpp` | ~50 | ~15 | Medium |
|
||||
| `src/xrpld/app/consensus/RCLConsensusAdaptor.cpp` | ~40 | ~12 | Medium |
|
||||
| `src/xrpld/core/JobQueue.cpp` | ~20 | ~5 | Low |
|
||||
| `src/xrpld/app/paths/PathRequest.cpp` | ~40 | ~3 | Low |
|
||||
| `src/xrpld/app/paths/Pathfinder.cpp` | ~40 | ~2 | Low |
|
||||
| `src/xrpld/app/misc/TxQ.cpp` | ~40 | ~3 | Low |
|
||||
| `src/xrpld/app/main/LoadManager.cpp` | ~20 | ~2 | Low |
|
||||
| `src/xrpld/app/misc/ValidatorList.cpp` | ~20 | ~2 | Low |
|
||||
| `src/xrpld/app/misc/AmendmentTable.cpp` | ~10 | ~2 | Low |
|
||||
| `src/xrpld/app/misc/Manifest.cpp` | ~10 | ~1 | Low |
|
||||
| `src/xrpld/shamap/SHAMap.cpp` | ~20 | ~3 | Low |
|
||||
| `src/xrpld/overlay/detail/ripple.proto` | ~25 | 0 | Low |
|
||||
| `CMakeLists.txt` | ~40 | ~8 | Low |
|
||||
| `cmake/FindOpenTelemetry.cmake` | ~50 | 0 | None (new) |
|
||||
|
||||
### 3.9.3 Risk Assessment by Component
|
||||
|
||||
<div align="center">
|
||||
|
||||
**Do First** ↖ ↗ **Plan Carefully**
|
||||
|
||||
```mermaid
|
||||
quadrantChart
|
||||
title Code Intrusiveness Risk Matrix
|
||||
x-axis Low Risk --> High Risk
|
||||
y-axis Low Value --> High Value
|
||||
|
||||
RPC Tracing: [0.2, 0.55]
|
||||
Transaction Relay: [0.55, 0.85]
|
||||
Consensus Tracing: [0.75, 0.92]
|
||||
Peer Message Tracing: [0.85, 0.35]
|
||||
JobQueue Context: [0.3, 0.42]
|
||||
Ledger Acquisition: [0.48, 0.65]
|
||||
PathFinding: [0.38, 0.72]
|
||||
TxQ and Fees: [0.25, 0.62]
|
||||
Validator Mgmt: [0.15, 0.35]
|
||||
```
|
||||
|
||||
**Optional** ↙ ↘ **Avoid**
|
||||
|
||||
</div>
|
||||
|
||||
#### Risk Level Definitions
|
||||
|
||||
| Risk Level | Definition | Mitigation |
|
||||
| ---------- | ---------------------------------------------------------------- | ---------------------------------- |
|
||||
| **Low** | Additive changes only; no modification to existing logic | Standard code review |
|
||||
| **Medium** | Minor modifications to existing functions; clear boundaries | Comprehensive unit tests |
|
||||
| **High** | Changes to core logic or data structures; potential side effects | Integration tests + staged rollout |
|
||||
|
||||
### 3.9.4 Architectural Impact Assessment
|
||||
|
||||
| Aspect | Impact | Justification |
|
||||
| -------------------- | ------- | -------------------------------------------------------------------------------- |
|
||||
| **Data Flow** | Minimal | Read-only instrumentation; no modification to consensus or transaction data flow |
|
||||
| **Threading Model** | Minimal | Context propagation uses thread-local storage (standard OTel pattern) |
|
||||
| **Memory Model** | Low | Bounded queues prevent unbounded growth; RAII ensures cleanup |
|
||||
| **Network Protocol** | Low | Optional fields in protobuf (high field numbers); backward compatible |
|
||||
| **Configuration** | None | New config section; existing configs unaffected |
|
||||
| **Build System** | Low | Optional CMake flag; builds work without OpenTelemetry |
|
||||
| **Dependencies** | Low | OpenTelemetry SDK is optional; null implementation when disabled |
|
||||
|
||||
### 3.9.5 Backward Compatibility
|
||||
|
||||
| Compatibility | Status | Notes |
|
||||
| --------------- | ------- | ----------------------------------------------------- |
|
||||
| **Config File** | ✅ Full | New `[telemetry]` section is optional |
|
||||
| **Protocol** | ✅ Full | Optional protobuf fields with high field numbers |
|
||||
| **Build** | ✅ Full | `XRPL_ENABLE_TELEMETRY=OFF` produces identical binary |
|
||||
| **Runtime** | ✅ Full | `enabled=0` produces zero overhead |
|
||||
| **API** | ✅ Full | No changes to public RPC or P2P APIs |
|
||||
|
||||
### 3.9.6 Rollback Strategy
|
||||
|
||||
If issues are discovered after deployment:
|
||||
|
||||
1. **Immediate**: Set `enabled=0` in config and restart (zero code change)
|
||||
2. **Quick**: Rebuild with `XRPL_ENABLE_TELEMETRY=OFF`
|
||||
3. **Complete**: Revert telemetry commits (clean separation makes this easy)
|
||||
|
||||
### 3.9.7 Code Change Examples
|
||||
|
||||
**Minimal RPC Instrumentation (Low Intrusiveness):** Instrumenting an RPC handler adds roughly 3-4 lines: one macro to start the span and one or two `setAttribute` calls (command name, status). The span ends automatically via RAII, so the existing control flow — process the request, send the result — is untouched.
|
||||
|
||||
**Consensus Instrumentation (Medium Intrusiveness):** Consensus is slightly more intrusive because child spans in later phase transitions need the round's context. Beyond the span-start and attribute macros, this requires storing the active context in a new member variable (`currentRoundContext_`) at round start. The existing round logic itself remains unchanged.
|
||||
|
||||
---
|
||||
|
||||
_Previous: [Design Decisions](./02-design-decisions.md)_ | _Next: [Configuration Reference](./05-configuration-reference.md)_ | _Back to: [Overview](./OpenTelemetryPlan.md)_
|
||||
265
OpenTelemetryPlan/05-configuration-reference.md
Normal file
265
OpenTelemetryPlan/05-configuration-reference.md
Normal file
@@ -0,0 +1,265 @@
|
||||
# Configuration Reference
|
||||
|
||||
> **Parent Document**: [OpenTelemetryPlan.md](./OpenTelemetryPlan.md)
|
||||
> **Related**: [Implementation Phases](./06-implementation-phases.md)
|
||||
|
||||
---
|
||||
|
||||
## 5.1 xrpld Configuration
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol | **TxQ** = Transaction Queue
|
||||
|
||||
### 5.1.1 Configuration File Section
|
||||
|
||||
The authoritative `[telemetry]` example lives in `cfg/xrpld-example.cfg`. Telemetry is disabled by default (`enabled=0`); enabling it turns on distributed tracing for transaction flow, consensus, and RPC calls, with traces exported to an OpenTelemetry Collector over OTLP. Head sampling is intentionally fixed at 1.0 (sample everything) and is not configurable — per-node head-sampling would produce broken/partial distributed traces, so volume reduction is delegated to the collector's tail sampling (see Section 7.4.2). The full option reference follows.
|
||||
|
||||
### 5.1.2 Configuration Options Summary
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
| --------------------- | ------ | --------------------------------- | ---------------------------------------------------- |
|
||||
| `enabled` | bool | `false` | Enable/disable telemetry |
|
||||
| `endpoint` | string | `http://localhost:4318/v1/traces` | OTLP/HTTP collector endpoint |
|
||||
| `use_tls` | bool | `false` | Enable TLS for exporter connection |
|
||||
| `tls_ca_cert` | string | `""` | Path to CA certificate file |
|
||||
| `batch_size` | uint | `512` | Spans per export batch |
|
||||
| `batch_delay_ms` | uint | `5000` | Max delay before sending batch (ms) |
|
||||
| `max_queue_size` | uint | `2048` | Maximum queued spans |
|
||||
| `trace_transactions` | bool | `true` | Enable transaction tracing |
|
||||
| `trace_consensus` | bool | `true` | Enable consensus tracing |
|
||||
| `trace_rpc` | bool | `true` | Enable RPC tracing |
|
||||
| `trace_peer` | bool | `true` | Enable peer message tracing (high volume) |
|
||||
| `trace_ledger` | bool | `true` | Enable ledger tracing |
|
||||
| `service_name` | string | `"xrpld"` | Service name (`service.name`) for traces and metrics |
|
||||
| `service_instance_id` | string | `<node_pubkey>` | Instance identifier |
|
||||
|
||||
**Planned (not yet implemented)**: the following options appear in the design
|
||||
documents but are not parsed by `TelemetryConfig.cpp` in Phase 1b and later
|
||||
phases. They will be added as the corresponding subsystems are instrumented:
|
||||
|
||||
| Option | Planned Phase | Purpose |
|
||||
| -------------------------- | ------------- | ----------------------------------------------------------------------- |
|
||||
| `exporter` | Future | Select between OTLP/HTTP and OTLP/gRPC |
|
||||
| `trace_pathfind` | Phase 2 | Path computation tracing toggle |
|
||||
| `trace_txq` | Phase 3 | Transaction queue tracing toggle |
|
||||
| `trace_validator` | Future | Validator list / manifest update tracing |
|
||||
| `trace_amendment` | Future | Amendment voting tracing |
|
||||
| `consensus_trace_strategy` | Phase 4 | Trace ID strategy for consensus rounds (`deterministic` \| `attribute`) |
|
||||
|
||||
---
|
||||
|
||||
## 5.2 Configuration Parser
|
||||
|
||||
> **TxQ** = Transaction Queue
|
||||
|
||||
The parser `setup_Telemetry()` in `src/libxrpl/telemetry/TelemetryConfig.cpp` reads the `[telemetry]` `Section` and populates a `Telemetry::Setup` struct, applying the defaults listed in Section 5.1.2 via `section.value_or(...)`. It derives `serviceInstanceId` from the node public key when not overridden, selects the exporter endpoint default by exporter type, and leaves the sampling ratio at its fixed 1.0 default (not read from config — see Section 7.4.2).
|
||||
|
||||
---
|
||||
|
||||
## 5.3 Application Integration
|
||||
|
||||
### 5.3.1 ApplicationImp Changes
|
||||
|
||||
> **Deferred identity**: The node public key (`nodeIdentity_`) is not
|
||||
> available during `ApplicationImp`'s member initializer list — it is
|
||||
> resolved later in `setup()`. The `Telemetry` object is therefore
|
||||
> constructed with an empty `serviceInstanceId` and patched via
|
||||
> `setServiceInstanceId()` once `setup()` has called `getNodeIdentity()`.
|
||||
|
||||
`ApplicationImp` (in `src/xrpld/app/main/Application.cpp`) owns a `std::unique_ptr<telemetry::Telemetry> telemetry_`. It is built in the member initializer list via `make_Telemetry(setup_Telemetry(...))` with an empty `serviceInstanceId`, then patched in `setup()` by calling `setServiceInstanceId()` with the Base58 node public key (unless the user supplied a custom `service_instance_id`). `start()` and `run()` forward to `telemetry_->start()` / `telemetry_->stop()`, and `getTelemetry()` returns the owned instance.
|
||||
|
||||
### 5.3.2 ServiceRegistry Interface Addition
|
||||
|
||||
`include/xrpl/core/ServiceRegistry.h` gains a pure-virtual `telemetry::Telemetry& getTelemetry()` (with a forward declaration of `telemetry::Telemetry`), giving every component a uniform accessor for the tracing subsystem.
|
||||
|
||||
> **Note:** `Application` extends `ServiceRegistry`, so `getTelemetry()` is
|
||||
> available on both. Components that hold a `ServiceRegistry&` (e.g.
|
||||
> `NetworkOPsImp`) call `registry_.get().getTelemetry()`. Components that
|
||||
> still hold an `Application&` (e.g. `ServerHandler`, `PeerImp`,
|
||||
> `RCLConsensusAdaptor`) call `app_.getTelemetry()` directly.
|
||||
|
||||
---
|
||||
|
||||
## 5.4 CMake Integration
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol
|
||||
|
||||
### 5.4.1 Find OpenTelemetry Module
|
||||
|
||||
A `cmake/FindOpenTelemetry.cmake` module locates the OpenTelemetry C++ SDK. It first tries `find_package(opentelemetry-cpp CONFIG)`, aliasing the imported targets `OpenTelemetry::api`, `OpenTelemetry::sdk`, and `OpenTelemetry::otlp_grpc_exporter`, and falls back to `pkg-config` when no CMake config package is present.
|
||||
|
||||
### 5.4.2 CMakeLists.txt Changes
|
||||
|
||||
The top-level `CMakeLists.txt` adds an `XRPL_ENABLE_TELEMETRY` option (default `OFF`). When enabled, it runs `find_package(OpenTelemetry REQUIRED)`, defines the `XRPL_ENABLE_TELEMETRY` compile flag, and builds the `xrpl_telemetry` library from the real telemetry sources linked against the OpenTelemetry targets; when disabled, it builds the same target from a no-op `NullTelemetry.cpp` so call sites compile unchanged.
|
||||
|
||||
---
|
||||
|
||||
## 5.5 OpenTelemetry Collector Configuration
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol | **APM** = Application Performance Monitoring
|
||||
|
||||
The authoritative collector config lives in the repo at `docker/telemetry/otel-collector-config.yaml` (with Tempo backend config in `docker/telemetry/tempo.yaml`). The sections below summarize the development and production shapes of that pipeline.
|
||||
|
||||
### 5.5.1 Development Configuration
|
||||
|
||||
The development collector enables an OTLP receiver on both gRPC (`0.0.0.0:4317`) and HTTP (`0.0.0.0:4318`), a single `batch` processor (1s timeout, batch size 100), and two exporters: a `logging` exporter for console debugging and `otlp/tempo` (insecure) for trace visualization. The single `traces` pipeline wires receiver → batch → both exporters.
|
||||
|
||||
### 5.5.2 Production Configuration
|
||||
|
||||
The production collector adds TLS on the OTLP gRPC receiver and a richer processor chain: a `memory_limiter` (OOM guard), `batch` (5s timeout, size 512), `tail_sampling`, and an `attributes` processor that hashes sensitive fields (e.g. `tx_account`) and stamps `deployment.environment`. Tail sampling keeps all `ERROR` traces, slow consensus rounds (>5s) and slow RPC requests (>1s), and probabilistically samples the remainder at 10%. Exporters target Grafana Tempo (TLS) and Elastic APM; `health_check` and `zpages` extensions are enabled for operability.
|
||||
|
||||
---
|
||||
|
||||
## 5.6 Docker Compose Development Environment
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol
|
||||
|
||||
The authoritative development stack lives in the repo at `docker/telemetry/docker-compose.yml`. It brings up four services on a shared `xrpld-telemetry` network: an `otel-collector` (otel/opentelemetry-collector-contrib) exposing OTLP gRPC `4317`, OTLP HTTP `4318`, and health check `13133`; `tempo` for trace storage/visualization; `grafana` with provisioned datasources and dashboards (anonymous admin enabled); and an optional `prometheus` for metric correlation.
|
||||
|
||||
---
|
||||
|
||||
## 5.7 Configuration Architecture
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph config["Configuration Sources"]
|
||||
cfgFile["xrpld.cfg<br/>[telemetry] section"]
|
||||
cmake["CMake<br/>XRPL_ENABLE_TELEMETRY"]
|
||||
end
|
||||
|
||||
subgraph init["Initialization"]
|
||||
parse["setup_Telemetry()"]
|
||||
factory["make_Telemetry()"]
|
||||
end
|
||||
|
||||
subgraph runtime["Runtime Components"]
|
||||
tracer["TracerProvider"]
|
||||
exporter["OTLP Exporter"]
|
||||
processor["BatchProcessor"]
|
||||
end
|
||||
|
||||
subgraph collector["Collector Pipeline"]
|
||||
recv["Receivers"]
|
||||
proc["Processors"]
|
||||
exp["Exporters"]
|
||||
end
|
||||
|
||||
cfgFile --> parse
|
||||
cmake -->|"compile flag"| parse
|
||||
parse --> factory
|
||||
factory --> tracer
|
||||
tracer --> processor
|
||||
processor --> exporter
|
||||
exporter -->|"OTLP"| recv
|
||||
recv --> proc
|
||||
proc --> exp
|
||||
|
||||
style config fill:#e3f2fd,stroke:#1976d2
|
||||
style runtime fill:#e8f5e9,stroke:#388e3c
|
||||
style collector fill:#fff3e0,stroke:#ff9800
|
||||
```
|
||||
|
||||
**Reading the diagram:**
|
||||
|
||||
- **Configuration Sources**: `xrpld.cfg` provides runtime settings (endpoint, per-component trace toggles) while the CMake flag controls whether telemetry is compiled in at all. Head sampling is fixed at 1.0 and is not a config option; volume reduction happens via tail sampling in the collector.
|
||||
- **Initialization**: `setup_Telemetry()` parses config values, then `make_Telemetry()` constructs the provider, processor, and exporter objects.
|
||||
- **Runtime Components**: The `TracerProvider` creates spans, the `BatchProcessor` buffers them, and the `OTLP Exporter` serializes and sends them over the wire.
|
||||
- **OTLP arrow to Collector**: Trace data leaves the xrpld process via OTLP/HTTP and enters the external Collector pipeline. (OTLP/gRPC is future work — see design decisions §2.2.2.)
|
||||
- **Collector Pipeline**: `Receivers` ingest OTLP data, `Processors` apply sampling/filtering/enrichment, and `Exporters` forward traces to storage backends (Tempo, etc.).
|
||||
|
||||
---
|
||||
|
||||
## 5.8 Grafana Integration
|
||||
|
||||
> **APM** = Application Performance Monitoring
|
||||
|
||||
Step-by-step instructions for integrating xrpld traces with Grafana.
|
||||
|
||||
### 5.8.1 Data Source Configuration
|
||||
|
||||
#### Tempo (Recommended)
|
||||
|
||||
A Tempo datasource (`grafana/provisioning/datasources/tempo.yaml`, provisioned from `docker/telemetry/grafana/`) points at `http://tempo:3200` and enables `tracesToLogs` (linking to Loki on `service.name`/`tx_hash` and mapping `trace_id` → `traceID`), `serviceMap` against Prometheus, the node graph, and Loki search.
|
||||
|
||||
#### Elastic APM
|
||||
|
||||
Alternatively, an Elasticsearch datasource (`grafana/provisioning/datasources/elastic-apm.yaml`) of type `elasticsearch` points at `http://elasticsearch:9200` against the `apm-*` index, using `@timestamp` as the time field and mapping the log message/level fields.
|
||||
|
||||
### 5.8.2 Dashboard Provisioning
|
||||
|
||||
A dashboard provider (`grafana/provisioning/dashboards/dashboards.yaml`) loads the `xrpld` dashboard folder from disk (`/var/lib/grafana/dashboards/rippled`), polling for changes every 30s with deletion disabled.
|
||||
|
||||
### 5.8.3 Example Dashboard: RPC Performance
|
||||
|
||||
An example `xrpld RPC Performance` dashboard (uid `xrpld-rpc-performance`) sourced from Tempo via TraceQL provides four panels: RPC latency by command (heatmap), RPC error rate by command (timeseries), the top 10 slowest RPC commands by average duration (table), and a recent-traces table.
|
||||
|
||||
### 5.8.4 Example Dashboard: Transaction Tracing
|
||||
|
||||
An example `xrpld Transaction Tracing` dashboard (uid `xrpld-tx-tracing`) over Tempo provides three panels: transaction throughput (`tx.receive` rate, stat), cross-node relay count (average `span.relay_count` on `tx.relay`, timeseries), and a table of transaction validation errors (`tx.validate` with `status.code=error`).
|
||||
|
||||
### 5.8.5 TraceQL Query Examples
|
||||
|
||||
Common queries for xrpld traces:
|
||||
|
||||
```
|
||||
# Find all traces for a specific transaction hash
|
||||
{resource.service.name="xrpld" && span.tx_hash="ABC123..."}
|
||||
|
||||
# Find slow RPC commands (>100ms)
|
||||
{resource.service.name="xrpld" && name=~"rpc.command.*"} | duration > 100ms
|
||||
|
||||
# Find consensus rounds taking >5 seconds
|
||||
{resource.service.name="xrpld" && name="consensus.round"} | duration > 5s
|
||||
|
||||
# Find failed transactions with error details
|
||||
{resource.service.name="xrpld" && name="tx.validate" && status.code=error}
|
||||
|
||||
# Find transactions relayed to many peers
|
||||
{resource.service.name="xrpld" && name="tx.relay"} | span.relay_count > 10
|
||||
|
||||
# Compare latency across nodes
|
||||
{resource.service.name="xrpld" && name="rpc.command.account_info"} | avg(duration) by (resource.service.instance.id)
|
||||
```
|
||||
|
||||
### 5.8.6 Correlation with PerfLog
|
||||
|
||||
To correlate OpenTelemetry traces with existing PerfLog data:
|
||||
|
||||
**Step 1: Configure Loki to ingest PerfLog**
|
||||
|
||||
Configure a Promtail scrape job (`promtail-config.yaml`) that tails `/var/log/rippled/perf*.log`, parses each JSON line, and promotes `trace_id`, `ledger_seq`, and `tx_hash` to Loki labels.
|
||||
|
||||
**Step 2: Add trace_id to PerfLog entries**
|
||||
|
||||
Modify PerfLog so its JSON output includes a `trace_id` field whenever a valid span is active: fetch the current span from the OpenTelemetry runtime context, and if its context is valid, render the trace ID as a 32-character lowercase hex string into the log entry.
|
||||
|
||||
**Step 3: Configure Grafana trace-to-logs link**
|
||||
|
||||
In the Tempo datasource, set the `tracesToLogs` derived field to link to Loki on the `trace_id` and `tx_hash` tags, with `filterByTraceID: true`.
|
||||
|
||||
### 5.8.7 Correlation with Insight/StatsD Metrics
|
||||
|
||||
To correlate traces with existing Beast Insight metrics:
|
||||
|
||||
**Step 1: Export Insight metrics to Prometheus**
|
||||
|
||||
Add a Prometheus scrape job (`prometheus.yaml`) named `xrpld-statsd` targeting the StatsD exporter at `statsd-exporter:9102`.
|
||||
|
||||
**Step 2: Add exemplars to metrics**
|
||||
|
||||
The OpenTelemetry SDK automatically adds exemplars (trace IDs) to metrics when using the Prometheus exporter, linking metric spikes to specific traces.
|
||||
|
||||
**Step 3: Configure Grafana metric-to-trace link**
|
||||
|
||||
In the Prometheus datasource, set `exemplarTraceIdDestinations` to map the `trace_id` exemplar to the Tempo datasource.
|
||||
|
||||
**Step 4: Dashboard panel with exemplars**
|
||||
|
||||
Add a timeseries panel over Prometheus (e.g. `histogram_quantile(0.99, rate(xrpld_rpc_duration_seconds_bucket[5m]))`) with `exemplar: true` enabled.
|
||||
|
||||
This allows clicking on metric data points to jump directly to the related trace.
|
||||
|
||||
---
|
||||
|
||||
_Previous: [Implementation Strategy](./03-implementation-strategy.md)_ | _Next: [Implementation Phases](./06-implementation-phases.md)_ | _Back to: [Overview](./OpenTelemetryPlan.md)_
|
||||
575
OpenTelemetryPlan/06-implementation-phases.md
Normal file
575
OpenTelemetryPlan/06-implementation-phases.md
Normal file
@@ -0,0 +1,575 @@
|
||||
# Implementation Phases
|
||||
|
||||
> **Parent Document**: [OpenTelemetryPlan.md](./OpenTelemetryPlan.md)
|
||||
> **Related**: [Configuration Reference](./05-configuration-reference.md) | [Observability Backends](./07-observability-backends.md)
|
||||
|
||||
---
|
||||
|
||||
## 6.1 Phase Overview
|
||||
|
||||
> **TxQ** = Transaction Queue
|
||||
|
||||
```mermaid
|
||||
gantt
|
||||
title OpenTelemetry Implementation Timeline
|
||||
dateFormat YYYY-MM-DD
|
||||
axisFormat Week %W
|
||||
|
||||
section Phase 1
|
||||
Core Infrastructure :p1, 2024-01-01, 2w
|
||||
SDK Integration :p1a, 2024-01-01, 4d
|
||||
Telemetry Interface :p1b, after p1a, 3d
|
||||
Configuration & CMake :p1c, after p1b, 3d
|
||||
Unit Tests :p1d, after p1c, 2d
|
||||
Buffer & Integration :p1e, after p1d, 2d
|
||||
|
||||
section Phase 2
|
||||
RPC Tracing :p2, after p1, 2w
|
||||
HTTP Context Extraction :p2a, after p1, 2d
|
||||
RPC Handler Instrumentation :p2b, after p2a, 4d
|
||||
PathFinding Instrumentation :p2f, after p2b, 2d
|
||||
TxQ Instrumentation :p2g, after p2f, 2d
|
||||
WebSocket Support :p2c, after p2g, 2d
|
||||
Integration Tests :p2d, after p2c, 2d
|
||||
Buffer & Review :p2e, after p2d, 4d
|
||||
|
||||
section Phase 3
|
||||
Transaction Tracing :p3, after p2, 2w
|
||||
Protocol Buffer Extension :p3a, after p2, 2d
|
||||
PeerImp Instrumentation :p3b, after p3a, 3d
|
||||
Fee Escalation Instrumentation :p3f, after p3b, 2d
|
||||
Relay Context Propagation :p3c, after p3f, 3d
|
||||
Multi-node Tests :p3d, after p3c, 2d
|
||||
Buffer & Review :p3e, after p3d, 4d
|
||||
|
||||
section Phase 4
|
||||
Consensus Tracing :p4, after p3, 2w
|
||||
Consensus Round Spans :p4a, after p3, 3d
|
||||
Proposal Handling :p4b, after p4a, 3d
|
||||
Validator List & Manifest Tracing :p4f, after p4b, 2d
|
||||
Amendment Voting Tracing :p4g, after p4f, 2d
|
||||
SHAMap Sync Tracing :p4h, after p4g, 2d
|
||||
Validation Tests :p4c, after p4h, 4d
|
||||
Buffer & Review :p4e, after p4c, 4d
|
||||
|
||||
section Phase 5
|
||||
Documentation & Deploy :p5, after p4, 1w
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6.2 Phase 1: Core Infrastructure (Weeks 1-2)
|
||||
|
||||
**Objective**: Establish foundational telemetry infrastructure
|
||||
|
||||
### Tasks
|
||||
|
||||
| Task | Description |
|
||||
| ---- | ----------------------------------------------------- |
|
||||
| 1.1 | Add OpenTelemetry C++ SDK to Conan/CMake |
|
||||
| 1.2 | Implement `Telemetry` interface and factory |
|
||||
| 1.3 | Implement `SpanGuard` RAII wrapper |
|
||||
| 1.4 | Implement configuration parser |
|
||||
| 1.5 | Integrate into `ApplicationImp` |
|
||||
| 1.6 | Add conditional compilation (`XRPL_ENABLE_TELEMETRY`) |
|
||||
| 1.7 | Create `NullTelemetry` no-op implementation |
|
||||
| 1.8 | Unit tests for core infrastructure |
|
||||
|
||||
### Exit Criteria
|
||||
|
||||
- [ ] OpenTelemetry SDK compiles and links
|
||||
- [ ] Telemetry can be enabled/disabled via config
|
||||
- [ ] Basic span creation works
|
||||
- [ ] No performance regression when disabled
|
||||
- [ ] Unit tests passing
|
||||
|
||||
---
|
||||
|
||||
## 6.3 Phase 2: RPC Tracing (Weeks 3-4)
|
||||
|
||||
> **TxQ** = Transaction Queue
|
||||
|
||||
**Objective**: Complete tracing for all RPC operations
|
||||
|
||||
### Tasks
|
||||
|
||||
| Task | Description |
|
||||
| ---- | -------------------------------------------------------------------------- |
|
||||
| 2.1 | Implement W3C Trace Context HTTP header extraction |
|
||||
| 2.2 | Instrument `ServerHandler::onRequest()` |
|
||||
| 2.3 | Instrument `RPCHandler::doCommand()` |
|
||||
| 2.4 | Add RPC-specific attributes |
|
||||
| 2.5 | Instrument WebSocket handler |
|
||||
| 2.6 | PathFinding instrumentation (`pathfind.request`, `pathfind.compute` spans) |
|
||||
| 2.7 | TxQ instrumentation (`txq.enqueue`, `txq.apply` spans) |
|
||||
| 2.8 | Integration tests for RPC tracing |
|
||||
| 2.9 | Performance benchmarks |
|
||||
| 2.10 | Documentation |
|
||||
|
||||
### Exit Criteria
|
||||
|
||||
- [ ] All RPC commands traced
|
||||
- [ ] Trace context propagates from HTTP headers
|
||||
- [ ] WebSocket and HTTP both instrumented
|
||||
- [ ] <1ms overhead per RPC call
|
||||
- [ ] Integration tests passing
|
||||
|
||||
---
|
||||
|
||||
## 6.4 Phase 3: Transaction Tracing (Weeks 5-6)
|
||||
|
||||
**Objective**: Trace transaction lifecycle across network
|
||||
|
||||
### Tasks
|
||||
|
||||
| Task | Description |
|
||||
| ---- | ---------------------------------------------------- |
|
||||
| 3.1 | Define `TraceContext` Protocol Buffer message |
|
||||
| 3.2 | Implement protobuf context serialization |
|
||||
| 3.3 | Instrument `PeerImp::handleTransaction()` |
|
||||
| 3.4 | Instrument `NetworkOPs::submitTransaction()` |
|
||||
| 3.5 | Instrument HashRouter integration |
|
||||
| 3.6 | Fee escalation instrumentation (`fee.escalate` span) |
|
||||
| 3.7 | Implement relay context propagation |
|
||||
| 3.8 | Integration tests (multi-node) |
|
||||
| 3.9 | Performance benchmarks |
|
||||
|
||||
### Exit Criteria
|
||||
|
||||
- [ ] Transaction traces span across nodes
|
||||
- [ ] Trace context in Protocol Buffer messages
|
||||
- [ ] HashRouter deduplication visible in traces
|
||||
- [ ] Multi-node integration tests passing
|
||||
- [ ] <5% overhead on transaction throughput
|
||||
|
||||
---
|
||||
|
||||
## 6.5 Phase 4: Consensus Tracing (Weeks 7-8)
|
||||
|
||||
**Objective**: Full observability into consensus rounds
|
||||
|
||||
### Tasks
|
||||
|
||||
| Task | Description |
|
||||
| ---- | ---------------------------------------------- |
|
||||
| 4.1 | Instrument `RCLConsensusAdaptor::startRound()` |
|
||||
| 4.2 | Instrument phase transitions |
|
||||
| 4.3 | Instrument proposal handling |
|
||||
| 4.4 | Instrument validation handling |
|
||||
| 4.5 | Add consensus-specific attributes |
|
||||
| 4.6 | Correlate with transaction traces |
|
||||
| 4.7 | Validator list and manifest tracing |
|
||||
| 4.8 | Amendment voting tracing |
|
||||
| 4.9 | SHAMap sync tracing |
|
||||
| 4.10 | Multi-validator integration tests |
|
||||
| 4.11 | Performance validation |
|
||||
|
||||
### Exit Criteria
|
||||
|
||||
- [ ] Complete consensus round traces
|
||||
- [ ] Phase transitions visible
|
||||
- [ ] Proposals and validations traced
|
||||
- [ ] No impact on consensus timing
|
||||
- [ ] Multi-validator test network validated
|
||||
|
||||
### Implementation Status — Phase 4a Plan
|
||||
|
||||
Phase 4a (establish-phase gap fill & cross-node correlation) will add:
|
||||
|
||||
- **Deterministic trace ID** derived from `previousLedger.id()` so all validators
|
||||
in the same round share the same `trace_id` (switchable via
|
||||
`consensus_trace_strategy` config: `"deterministic"` or `"attribute"`).
|
||||
See [Configuration Reference](./05-configuration-reference.md) for full
|
||||
configuration options.
|
||||
- **Round lifecycle spans**: `consensus.round` with round-to-round span links.
|
||||
- **Establish phase**: `consensus.establish`, `consensus.update_positions` (with
|
||||
`dispute.resolve` events), `consensus.check` (with threshold tracking).
|
||||
- **Mode changes**: `consensus.mode_change` spans.
|
||||
- **Validation**: `consensus.validation.send` with span link to round span
|
||||
(thread-safe cross-thread access via `roundSpanContext_` snapshot).
|
||||
- **Separation of concerns**: telemetry extracted to private helpers
|
||||
(`startRoundTracing`, `createValidationSpan`, `startEstablishTracing`,
|
||||
`updateEstablishTracing`, `endEstablishTracing`).
|
||||
|
||||
The `Phase4_taskList.md` spec document is introduced in the Phase 2 PR (#6424)
|
||||
and will contain the full task breakdown and implementation notes.
|
||||
|
||||
---
|
||||
|
||||
## 6.6 Phase 5: Documentation & Deployment (Week 9)
|
||||
|
||||
**Objective**: Production readiness
|
||||
|
||||
### Tasks
|
||||
|
||||
| Task | Description |
|
||||
| ---- | ----------------------------- |
|
||||
| 5.1 | Operator runbook |
|
||||
| 5.2 | Grafana dashboards |
|
||||
| 5.3 | Alert definitions |
|
||||
| 5.4 | Collector deployment examples |
|
||||
| 5.5 | Developer documentation |
|
||||
| 5.6 | Training materials |
|
||||
| 5.7 | Final integration testing |
|
||||
|
||||
---
|
||||
|
||||
## 6.7 Risk Assessment
|
||||
|
||||
```mermaid
|
||||
quadrantChart
|
||||
title Risk Assessment Matrix
|
||||
x-axis Low Impact --> High Impact
|
||||
y-axis Low Likelihood --> High Likelihood
|
||||
quadrant-1 Mitigate Immediately
|
||||
quadrant-2 Plan Mitigation
|
||||
quadrant-3 Accept Risk
|
||||
quadrant-4 Monitor Closely
|
||||
|
||||
SDK Compat: [0.2, 0.18]
|
||||
Protocol Chg: [0.75, 0.72]
|
||||
Perf Overhead: [0.58, 0.42]
|
||||
Context Prop: [0.4, 0.55]
|
||||
Memory Leaks: [0.85, 0.25]
|
||||
```
|
||||
|
||||
### Risk Details
|
||||
|
||||
| Risk | Likelihood | Impact | Mitigation |
|
||||
| ------------------------------------ | ---------- | ------ | --------------------------------------- |
|
||||
| Protocol changes break compatibility | Medium | High | Use high field numbers, optional fields |
|
||||
| Performance overhead unacceptable | Medium | Medium | Sampling, conditional compilation |
|
||||
| Context propagation complexity | Medium | Medium | Phased rollout, extensive testing |
|
||||
| SDK compatibility issues | Low | Medium | Pin SDK version, fallback to no-op |
|
||||
| Memory leaks in long-running nodes | Low | High | Memory profiling, bounded queues |
|
||||
|
||||
---
|
||||
|
||||
## 6.8 Success Metrics
|
||||
|
||||
| Metric | Target | Measurement |
|
||||
| ------------------------ | -------------------------------------------------------------- | --------------------- |
|
||||
| Trace coverage | >95% of transaction code paths (independent of sampling ratio) | Sampling verification |
|
||||
| CPU overhead | <3% | Benchmark tests |
|
||||
| Memory overhead | <10 MB | Memory profiling |
|
||||
| Latency impact (p99) | <2% | Performance tests |
|
||||
| Trace completeness | >99% spans with required attrs | Validation script |
|
||||
| Cross-node trace linkage | >90% of multi-hop transactions | Integration tests |
|
||||
|
||||
---
|
||||
|
||||
## 6.9 Quick Wins and Crawl-Walk-Run Strategy
|
||||
|
||||
> **TxQ** = Transaction Queue
|
||||
|
||||
This section outlines a prioritized approach to maximize ROI with minimal initial investment.
|
||||
|
||||
### 6.9.1 Crawl-Walk-Run Overview
|
||||
|
||||
<div align="center">
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph crawl["🐢 CRAWL (Week 1-2)"]
|
||||
direction LR
|
||||
c1[Core SDK Setup] ~~~ c2[RPC Tracing Only] ~~~ c3[PathFinding + TxQ Tracing] ~~~ c4[Single Node]
|
||||
end
|
||||
|
||||
subgraph walk["🚶 WALK (Week 3-5)"]
|
||||
direction LR
|
||||
w1[Transaction Tracing] ~~~ w2[Fee Escalation Tracing] ~~~ w3[Cross-Node Context] ~~~ w4[Basic Dashboards]
|
||||
end
|
||||
|
||||
subgraph run["🏃 RUN (Week 6-9)"]
|
||||
direction LR
|
||||
r1[Consensus Tracing] ~~~ r2[Validator, Amendment,<br/>SHAMap Tracing] ~~~ r3[Full Correlation] ~~~ r4[Production Deploy]
|
||||
end
|
||||
|
||||
crawl --> walk --> run
|
||||
|
||||
style crawl fill:#1b5e20,stroke:#0d3d14,color:#fff
|
||||
style walk fill:#bf360c,stroke:#8c2809,color:#fff
|
||||
style run fill:#0d47a1,stroke:#082f6a,color:#fff
|
||||
style c1 fill:#1b5e20,stroke:#0d3d14,color:#fff
|
||||
style c2 fill:#1b5e20,stroke:#0d3d14,color:#fff
|
||||
style c3 fill:#1b5e20,stroke:#0d3d14,color:#fff
|
||||
style c4 fill:#1b5e20,stroke:#0d3d14,color:#fff
|
||||
style w1 fill:#ffe0b2,stroke:#ffcc80,color:#1e293b
|
||||
style w2 fill:#ffe0b2,stroke:#ffcc80,color:#1e293b
|
||||
style w3 fill:#ffe0b2,stroke:#ffcc80,color:#1e293b
|
||||
style w4 fill:#ffe0b2,stroke:#ffcc80,color:#1e293b
|
||||
style r1 fill:#0d47a1,stroke:#082f6a,color:#fff
|
||||
style r2 fill:#0d47a1,stroke:#082f6a,color:#fff
|
||||
style r3 fill:#0d47a1,stroke:#082f6a,color:#fff
|
||||
style r4 fill:#0d47a1,stroke:#082f6a,color:#fff
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
**Reading the diagram:**
|
||||
|
||||
- **CRAWL (Weeks 1-2)**: Minimal investment -- set up the SDK, instrument RPC and PathFinding/TxQ handlers, and verify on a single node. Delivers immediate latency visibility.
|
||||
- **WALK (Weeks 3-5)**: Expand to transaction lifecycle tracing, fee escalation, cross-node context propagation, and basic Grafana dashboards. This is where distributed tracing starts working.
|
||||
- **RUN (Weeks 6-9)**: Full consensus instrumentation, validator/amendment/SHAMap tracing, end-to-end correlation, and production deployment with sampling and alerting.
|
||||
- **Arrows (crawl → walk → run)**: Each phase builds on the prior one; you cannot skip ahead because later phases depend on infrastructure established earlier.
|
||||
|
||||
### 6.9.2 Quick Wins (Immediate Value)
|
||||
|
||||
| Quick Win | Value | When to Deploy |
|
||||
| ------------------------------ | ------ | -------------- |
|
||||
| **RPC Command Tracing** | High | Week 2 |
|
||||
| **RPC Latency Histograms** | High | Week 2 |
|
||||
| **Error Rate Dashboard** | Medium | Week 2 |
|
||||
| **Transaction Submit Tracing** | High | Week 3 |
|
||||
| **Consensus Round Duration** | Medium | Week 6 |
|
||||
|
||||
### 6.9.3 CRAWL Phase (Weeks 1-2)
|
||||
|
||||
**Goal**: Get basic tracing working with minimal code changes.
|
||||
|
||||
**What You Get**:
|
||||
|
||||
- RPC request/response traces for all commands
|
||||
- Latency breakdown per RPC command
|
||||
- PathFinding and TxQ tracing (directly impacts RPC latency)
|
||||
- Error visibility with stack traces
|
||||
- Basic Grafana dashboard
|
||||
|
||||
**Code Changes**: ~15 lines in `ServerHandler.cpp`, ~40 lines in new telemetry module
|
||||
|
||||
**Why Start Here**:
|
||||
|
||||
- RPC is the lowest-risk, highest-visibility component
|
||||
- PathFinding and TxQ are RPC-adjacent and directly affect latency
|
||||
- Immediate value for debugging client issues
|
||||
- No cross-node complexity
|
||||
- Single file modification to existing code
|
||||
|
||||
### 6.9.4 WALK Phase (Weeks 3-5)
|
||||
|
||||
**Goal**: Add transaction lifecycle tracing across nodes.
|
||||
|
||||
**What You Get**:
|
||||
|
||||
- End-to-end transaction traces from submit to relay
|
||||
- Fee escalation tracing within the transaction pipeline
|
||||
- Cross-node correlation (see transaction path)
|
||||
- HashRouter deduplication visibility
|
||||
- Relay latency metrics
|
||||
|
||||
**Code Changes**: ~120 lines across 4 files, plus protobuf extension
|
||||
|
||||
**Why Do This Second**:
|
||||
|
||||
- Builds on RPC tracing (transactions submitted via RPC)
|
||||
- Fee escalation is integral to the transaction processing pipeline
|
||||
- Moderate complexity (requires context propagation)
|
||||
- High value for debugging transaction issues
|
||||
|
||||
### 6.9.5 RUN Phase (Weeks 6-9)
|
||||
|
||||
**Goal**: Full observability including consensus.
|
||||
|
||||
**What You Get**:
|
||||
|
||||
- Complete consensus round visibility
|
||||
- Phase transition timing
|
||||
- Validator proposal tracking
|
||||
- Validator list and manifest tracing
|
||||
- Amendment voting tracing
|
||||
- SHAMap sync tracing
|
||||
- Full end-to-end traces (client → RPC → TX → consensus → ledger)
|
||||
|
||||
**Code Changes**: ~100 lines across 3 consensus files, plus validator/amendment/SHAMap modules
|
||||
|
||||
**Why Do This Last**:
|
||||
|
||||
- Highest complexity (consensus is critical path)
|
||||
- Validator, amendment, and SHAMap components are lower priority
|
||||
- Requires thorough testing
|
||||
- Lower relative value (consensus issues are rarer)
|
||||
|
||||
### 6.9.6 ROI Prioritization Matrix
|
||||
|
||||
```mermaid
|
||||
quadrantChart
|
||||
title Implementation ROI Matrix
|
||||
x-axis Low Effort --> High Effort
|
||||
y-axis Low Value --> High Value
|
||||
quadrant-1 Quick Wins - Do First
|
||||
quadrant-2 Major Projects - Plan Carefully
|
||||
quadrant-3 Nice to Have - Optional
|
||||
quadrant-4 Time Sinks - Avoid
|
||||
|
||||
RPC Tracing: [0.15, 0.92]
|
||||
TX Submit Trace: [0.3, 0.78]
|
||||
TX Relay Trace: [0.5, 0.88]
|
||||
Consensus Trace: [0.72, 0.72]
|
||||
Peer Msg Trace: [0.85, 0.3]
|
||||
Ledger Acquire: [0.55, 0.52]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6.10 Definition of Done
|
||||
|
||||
> **TxQ** = Transaction Queue | **HA** = High Availability
|
||||
|
||||
Clear, measurable criteria for each phase.
|
||||
|
||||
### 6.10.1 Phase 1: Core Infrastructure
|
||||
|
||||
| Criterion | Measurement | Target |
|
||||
| --------------- | ---------------------------------------------------------- | ---------------------------- |
|
||||
| SDK Integration | `cmake --build` succeeds with `-DXRPL_ENABLE_TELEMETRY=ON` | ✅ Compiles |
|
||||
| Runtime Toggle | `enabled=0` produces zero overhead | <0.1% CPU difference |
|
||||
| Span Creation | Unit test creates and exports span | Span appears in Tempo |
|
||||
| Configuration | All config options parsed correctly | Config validation tests pass |
|
||||
| Documentation | Developer guide exists | PR approved |
|
||||
|
||||
**Definition of Done**: All criteria met, PR merged, no regressions in CI.
|
||||
|
||||
### 6.10.2 Phase 2: RPC Tracing
|
||||
|
||||
| Criterion | Measurement | Target |
|
||||
| ------------------ | ---------------------------------- | -------------------------- |
|
||||
| Coverage | All RPC commands instrumented | 100% of commands |
|
||||
| Context Extraction | traceparent header propagates | Integration test passes |
|
||||
| Attributes | Command, status, duration recorded | Validation script confirms |
|
||||
| Performance | RPC latency overhead | <1ms p99 |
|
||||
| Dashboard | Grafana dashboard deployed | Screenshot in docs |
|
||||
|
||||
**Definition of Done**: RPC traces visible in Tempo for all commands, dashboard shows latency distribution.
|
||||
|
||||
### 6.10.3 Phase 3: Transaction Tracing
|
||||
|
||||
| Criterion | Measurement | Target |
|
||||
| ---------------- | ------------------------------- | ---------------------------------- |
|
||||
| Local Trace | Submit → validate → TxQ traced | Single-node test passes |
|
||||
| Cross-Node | Context propagates via protobuf | Multi-node test passes |
|
||||
| Relay Visibility | relay_count attribute correct | Spot check 100 txs |
|
||||
| HashRouter | Deduplication visible in trace | Duplicate txs show suppressed=true |
|
||||
| Performance | TX throughput overhead | <5% degradation |
|
||||
|
||||
**Definition of Done**: Transaction traces span 3+ nodes in test network, performance within bounds.
|
||||
|
||||
### 6.10.4 Phase 4: Consensus Tracing
|
||||
|
||||
| Criterion | Measurement | Target |
|
||||
| -------------------- | ----------------------------- | ------------------------- |
|
||||
| Round Tracing | startRound creates root span | Unit test passes |
|
||||
| Phase Visibility | All phases have child spans | Integration test confirms |
|
||||
| Proposer Attribution | Proposer ID in attributes | Spot check 50 rounds |
|
||||
| Timing Accuracy | Phase durations match PerfLog | <5% variance |
|
||||
| No Consensus Impact | Round timing unchanged | Performance test passes |
|
||||
|
||||
**Definition of Done**: Consensus rounds fully traceable, no impact on consensus timing.
|
||||
|
||||
### 6.10.5 Phase 5: Production Deployment
|
||||
|
||||
| Criterion | Measurement | Target |
|
||||
| ------------ | ---------------------------- | -------------------------- |
|
||||
| Collector HA | Multiple collectors deployed | No single point of failure |
|
||||
| Sampling | Tail sampling configured | 10% base + errors + slow |
|
||||
| Retention | Data retained per policy | 7 days hot, 30 days warm |
|
||||
| Alerting | Alerts configured | Error spike, high latency |
|
||||
| Runbook | Operator documentation | Approved by ops team |
|
||||
| Training | Team trained | Session completed |
|
||||
|
||||
**Definition of Done**: Telemetry running in production, operators trained, alerts active.
|
||||
|
||||
### 6.10.6 Success Metrics Summary
|
||||
|
||||
| Phase | Primary Metric | Secondary Metric | Deadline |
|
||||
| ------- | ---------------------- | --------------------------- | ------------- |
|
||||
| Phase 1 | SDK compiles and runs | Zero overhead when disabled | End of Week 2 |
|
||||
| Phase 2 | 100% RPC coverage | <1ms latency overhead | End of Week 4 |
|
||||
| Phase 3 | Cross-node traces work | <5% throughput impact | End of Week 6 |
|
||||
| Phase 4 | Consensus fully traced | No consensus timing impact | End of Week 8 |
|
||||
| Phase 5 | Production deployment | Operators trained | End of Week 9 |
|
||||
|
||||
---
|
||||
|
||||
## 6.11 Recommended Implementation Order
|
||||
|
||||
Based on ROI analysis, implement in this exact order:
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph week1["Week 1"]
|
||||
t1[1. OpenTelemetry SDK<br/>Conan/CMake integration]
|
||||
t2[2. Telemetry interface<br/>SpanGuard, config]
|
||||
end
|
||||
|
||||
subgraph week2["Week 2"]
|
||||
t3[3. RPC ServerHandler<br/>instrumentation]
|
||||
t4[4. Basic Tempo setup<br/>for testing]
|
||||
end
|
||||
|
||||
subgraph week3["Week 3"]
|
||||
t5[5. Transaction submit<br/>tracing]
|
||||
t6[6. Grafana dashboard<br/>v1]
|
||||
end
|
||||
|
||||
subgraph week4["Week 4"]
|
||||
t7[7. Protobuf context<br/>extension]
|
||||
t8[8. PeerImp tx.relay<br/>instrumentation]
|
||||
end
|
||||
|
||||
subgraph week5["Week 5"]
|
||||
t9[9. Multi-node<br/>integration tests]
|
||||
t10[10. Performance<br/>benchmarks]
|
||||
end
|
||||
|
||||
subgraph week6_8["Weeks 6-8"]
|
||||
t11[11. Consensus<br/>instrumentation]
|
||||
t12[12. Full integration<br/>testing]
|
||||
end
|
||||
|
||||
subgraph week9["Week 9"]
|
||||
t13[13. Production<br/>deployment]
|
||||
t14[14. Documentation<br/>& training]
|
||||
end
|
||||
|
||||
t1 --> t2 --> t3 --> t4
|
||||
t4 --> t5 --> t6
|
||||
t6 --> t7 --> t8
|
||||
t8 --> t9 --> t10
|
||||
t10 --> t11 --> t12
|
||||
t12 --> t13 --> t14
|
||||
|
||||
style week1 fill:#1b5e20,stroke:#0d3d14,color:#fff
|
||||
style week2 fill:#1b5e20,stroke:#0d3d14,color:#fff
|
||||
style week3 fill:#bf360c,stroke:#8c2809,color:#fff
|
||||
style week4 fill:#bf360c,stroke:#8c2809,color:#fff
|
||||
style week5 fill:#bf360c,stroke:#8c2809,color:#fff
|
||||
style week6_8 fill:#0d47a1,stroke:#082f6a,color:#fff
|
||||
style week9 fill:#4a148c,stroke:#2e0d57,color:#fff
|
||||
style t1 fill:#1b5e20,stroke:#0d3d14,color:#fff
|
||||
style t2 fill:#1b5e20,stroke:#0d3d14,color:#fff
|
||||
style t3 fill:#1b5e20,stroke:#0d3d14,color:#fff
|
||||
style t4 fill:#1b5e20,stroke:#0d3d14,color:#fff
|
||||
style t5 fill:#ffe0b2,stroke:#ffcc80,color:#1e293b
|
||||
style t6 fill:#ffe0b2,stroke:#ffcc80,color:#1e293b
|
||||
style t7 fill:#ffe0b2,stroke:#ffcc80,color:#1e293b
|
||||
style t8 fill:#ffe0b2,stroke:#ffcc80,color:#1e293b
|
||||
style t9 fill:#ffe0b2,stroke:#ffcc80,color:#1e293b
|
||||
style t10 fill:#ffe0b2,stroke:#ffcc80,color:#1e293b
|
||||
style t11 fill:#0d47a1,stroke:#082f6a,color:#fff
|
||||
style t12 fill:#0d47a1,stroke:#082f6a,color:#fff
|
||||
style t13 fill:#4a148c,stroke:#2e0d57,color:#fff
|
||||
style t14 fill:#4a148c,stroke:#2e0d57,color:#fff
|
||||
```
|
||||
|
||||
**Reading the diagram:**
|
||||
|
||||
- **Week 1 (tasks 1-2)**: Foundation work -- integrate the OpenTelemetry SDK via Conan/CMake and build the `Telemetry` interface with `SpanGuard` and config parsing.
|
||||
- **Week 2 (tasks 3-4)**: First observable output -- instrument `ServerHandler` for RPC tracing and stand up Tempo so developers can see traces immediately.
|
||||
- **Weeks 3-5 (tasks 5-10)**: Transaction lifecycle -- add submit tracing, build the first Grafana dashboard, extend protobuf for cross-node context, instrument `PeerImp` relay, then validate with multi-node integration tests and performance benchmarks.
|
||||
- **Weeks 6-8 (tasks 11-12)**: Consensus deep-dive -- instrument consensus rounds and phases, then run full integration testing across all instrumented paths.
|
||||
- **Week 9 (tasks 13-14)**: Go-live -- deploy to production with sampling/alerting configured, and deliver documentation and operator training.
|
||||
- **Arrow chain (t1 → ... → t14)**: Strict sequential dependency; each task's output is a prerequisite for the next.
|
||||
|
||||
---
|
||||
|
||||
_Previous: [Configuration Reference](./05-configuration-reference.md)_ | _Next: [Observability Backends](./07-observability-backends.md)_ | _Back to: [Overview](./OpenTelemetryPlan.md)_
|
||||
407
OpenTelemetryPlan/07-observability-backends.md
Normal file
407
OpenTelemetryPlan/07-observability-backends.md
Normal file
@@ -0,0 +1,407 @@
|
||||
# Observability Backend Recommendations
|
||||
|
||||
> **Parent Document**: [OpenTelemetryPlan.md](./OpenTelemetryPlan.md)
|
||||
> **Related**: [Implementation Phases](./06-implementation-phases.md) | [Appendix](./08-appendix.md)
|
||||
|
||||
---
|
||||
|
||||
## 7.1 Development/Testing Backends
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol
|
||||
|
||||
| Backend | Pros | Cons | Use Case |
|
||||
| ---------- | ----------------------------------- | ---------------------- | ------------------- |
|
||||
| **Tempo** | Cost-effective, Grafana integration | Requires Grafana stack | Local dev, CI, Prod |
|
||||
| **Zipkin** | Simple, lightweight | Basic features | Quick prototyping |
|
||||
|
||||
### Quick Start with Tempo
|
||||
|
||||
```bash
|
||||
# Start Tempo with OTLP support
|
||||
docker run -d --name tempo \
|
||||
-p 3200:3200 \
|
||||
-p 4317:4317 \
|
||||
-p 4318:4318 \
|
||||
grafana/tempo:2.6.1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7.2 Production Backends
|
||||
|
||||
> **APM** = Application Performance Monitoring
|
||||
|
||||
| Backend | Pros | Cons | Use Case |
|
||||
| ----------------- | ----------------------------------------- | ---------------------- | --------------------------- |
|
||||
| **Grafana Tempo** | Cost-effective, Grafana integration | Requires Grafana stack | Most production deployments |
|
||||
| **Elastic APM** | Full observability stack, log correlation | Resource intensive | Existing Elastic users |
|
||||
| **Honeycomb** | Excellent query, high cardinality | SaaS cost | Deep debugging needs |
|
||||
| **Datadog APM** | Full platform, easy setup | SaaS cost | Enterprise with budget |
|
||||
|
||||
### Backend Selection Flowchart
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
start[Select Backend] --> budget{Budget<br/>Constraints?}
|
||||
|
||||
budget -->|Yes| oss[Open Source]
|
||||
budget -->|No| saas{Prefer<br/>SaaS?}
|
||||
|
||||
oss --> existing{Existing<br/>Stack?}
|
||||
existing -->|Grafana| tempo[Grafana Tempo]
|
||||
existing -->|Elastic| elastic[Elastic APM]
|
||||
existing -->|None| tempo
|
||||
|
||||
saas -->|Yes| enterprise{Enterprise<br/>Support?}
|
||||
saas -->|No| oss
|
||||
|
||||
enterprise -->|Yes| datadog[Datadog APM]
|
||||
enterprise -->|No| honeycomb[Honeycomb]
|
||||
|
||||
tempo --> final[Configure Collector]
|
||||
elastic --> final
|
||||
honeycomb --> final
|
||||
datadog --> final
|
||||
|
||||
style start fill:#0f172a,stroke:#020617,color:#fff
|
||||
style budget fill:#334155,stroke:#1e293b,color:#fff
|
||||
style oss fill:#1e293b,stroke:#0f172a,color:#fff
|
||||
style existing fill:#334155,stroke:#1e293b,color:#fff
|
||||
style saas fill:#334155,stroke:#1e293b,color:#fff
|
||||
style enterprise fill:#334155,stroke:#1e293b,color:#fff
|
||||
style final fill:#0f172a,stroke:#020617,color:#fff
|
||||
style tempo fill:#1b5e20,stroke:#0d3d14,color:#fff
|
||||
style elastic fill:#bf360c,stroke:#8c2809,color:#fff
|
||||
style honeycomb fill:#0d47a1,stroke:#082f6a,color:#fff
|
||||
style datadog fill:#4a148c,stroke:#2e0d57,color:#fff
|
||||
```
|
||||
|
||||
**Reading the diagram:**
|
||||
|
||||
- **Budget Constraints? (Yes)**: Leads to open-source options. If you already run Grafana or Elastic, pick the matching backend; otherwise default to Grafana Tempo.
|
||||
- **Budget Constraints? (No) → Prefer SaaS?**: If you want a managed service, choose between Datadog (enterprise support) and Honeycomb (developer-focused). If not, fall back to open-source.
|
||||
- **Terminal nodes (Tempo / Elastic / Honeycomb / Datadog)**: Each represents a concrete backend choice, all of which feed into the same final step.
|
||||
- **Configure Collector**: Regardless of backend, you always finish by configuring the OTel Collector to export to your chosen destination.
|
||||
|
||||
---
|
||||
|
||||
## 7.3 Recommended Production Architecture
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol | **APM** = Application Performance Monitoring | **HA** = High Availability
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph validators["Validator Nodes"]
|
||||
v1[xrpld<br/>Validator 1]
|
||||
v2[xrpld<br/>Validator 2]
|
||||
end
|
||||
|
||||
subgraph stock["Stock Nodes"]
|
||||
s1[xrpld<br/>Stock 1]
|
||||
s2[xrpld<br/>Stock 2]
|
||||
end
|
||||
|
||||
subgraph collector["OTel Collector Cluster"]
|
||||
c1[Collector<br/>DC1]
|
||||
c2[Collector<br/>DC2]
|
||||
end
|
||||
|
||||
subgraph backends["Storage Backends"]
|
||||
tempo[(Grafana<br/>Tempo)]
|
||||
elastic[(Elastic<br/>APM)]
|
||||
archive[(S3/GCS<br/>Archive)]
|
||||
end
|
||||
|
||||
subgraph ui["Visualization"]
|
||||
grafana[Grafana<br/>Dashboards]
|
||||
end
|
||||
|
||||
v1 -->|OTLP| c1
|
||||
v2 -->|OTLP| c1
|
||||
s1 -->|OTLP| c2
|
||||
s2 -->|OTLP| c2
|
||||
|
||||
c1 --> tempo
|
||||
c1 --> elastic
|
||||
c2 --> tempo
|
||||
c2 --> archive
|
||||
|
||||
tempo --> grafana
|
||||
elastic --> grafana
|
||||
|
||||
%% Note: simplified single-collector-per-DC topology shown for clarity
|
||||
|
||||
style validators fill:#b71c1c,stroke:#7f1d1d,color:#ffffff
|
||||
style stock fill:#0d47a1,stroke:#082f6a,color:#ffffff
|
||||
style collector fill:#bf360c,stroke:#8c2809,color:#ffffff
|
||||
style backends fill:#1b5e20,stroke:#0d3d14,color:#ffffff
|
||||
style ui fill:#4a148c,stroke:#2e0d57,color:#ffffff
|
||||
```
|
||||
|
||||
**Reading the diagram:**
|
||||
|
||||
- **Validator / Stock Nodes**: All xrpld nodes emit trace data via OTLP. Validators and stock nodes are grouped separately because they may reside in different network zones.
|
||||
- **Collector Cluster (DC1, DC2)**: Regional collectors receive OTLP from nodes in their datacenter, apply processing (sampling, enrichment), and fan out to multiple backends. Enrichment includes deployment-tier tagging: each collector stamps `deployment.environment` and (as a fallback) `xrpl.network.type` so one Grafana stack can filter data from many collectors by tier.
|
||||
- **Storage Backends**: Tempo and Elastic provide queryable trace storage; S3/GCS Archive provides long-term cold storage for compliance or post-incident analysis.
|
||||
- **Grafana Dashboards**: The single visualization layer that queries both Tempo and Elastic, giving operators a unified view of all traces.
|
||||
- **Data flow direction**: Nodes → Collectors → Storage → Grafana. Each arrow represents a network hop; minimizing collector-to-backend hops reduces latency.
|
||||
|
||||
> **Note**: Production deployments should use multiple collector instances behind a load balancer for high availability. The diagram shows a simplified single-collector topology for clarity.
|
||||
|
||||
---
|
||||
|
||||
## 7.4 Architecture Considerations
|
||||
|
||||
### 7.4.1 Collector Placement
|
||||
|
||||
| Strategy | Description | Pros | Cons |
|
||||
| ------------- | -------------------- | ------------------------ | ----------------------- |
|
||||
| **Sidecar** | Collector per node | Isolation, simple config | Resource overhead |
|
||||
| **DaemonSet** | Collector per host | Shared resources | Complexity |
|
||||
| **Gateway** | Central collector(s) | Centralized processing | Single point of failure |
|
||||
|
||||
**Recommendation**: Use **Gateway** pattern with regional collectors for xrpld networks:
|
||||
|
||||
- One collector cluster per datacenter/region
|
||||
- Tail-based sampling at collector level
|
||||
- Multiple export destinations for redundancy
|
||||
|
||||
### 7.4.2 Sampling Strategy
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph head["Head Sampling (Node)"]
|
||||
hs[Node-level head sampling<br/>fixed at 100%<br/>not configurable]
|
||||
end
|
||||
|
||||
subgraph tail["Tail Sampling (Collector)"]
|
||||
ts1[Keep all errors]
|
||||
ts2[Keep slow >5s]
|
||||
ts3[Keep 10% rest]
|
||||
end
|
||||
|
||||
head --> tail
|
||||
|
||||
ts1 --> final[Final Traces]
|
||||
ts2 --> final
|
||||
ts3 --> final
|
||||
|
||||
style head fill:#0d47a1,stroke:#082f6a,color:#fff
|
||||
style tail fill:#1b5e20,stroke:#0d3d14,color:#fff
|
||||
style hs fill:#0d47a1,stroke:#082f6a,color:#fff
|
||||
style ts1 fill:#1b5e20,stroke:#0d3d14,color:#fff
|
||||
style ts2 fill:#1b5e20,stroke:#0d3d14,color:#fff
|
||||
style ts3 fill:#1b5e20,stroke:#0d3d14,color:#fff
|
||||
style final fill:#bf360c,stroke:#8c2809,color:#fff
|
||||
```
|
||||
|
||||
**Reading the diagram:**
|
||||
|
||||
- **Head Sampling (Node)**: xrpld pins head sampling at 100% (sample everything) and does not expose a configurable ratio. This is intentional: a per-node ratio would let different nodes make divergent keep/drop decisions for the same distributed trace, producing broken/partial traces. xrpld uses a `ParentBased` sampler so spans inheriting a remote parent honor the upstream decision. Volume reduction is delegated to the collector's tail sampling.
|
||||
- **Tail Sampling (Collector)**: The second filter -- the collector inspects completed traces and applies rules: keep all errors, keep anything slower than 5 seconds, and keep 10% of the remainder.
|
||||
- **Arrow head → tail**: All head-sampled traces flow to the collector, where tail sampling further reduces volume while preserving the most valuable data.
|
||||
- **Final Traces**: The output after both sampling stages; this is what gets stored and queried. The two-stage approach balances cost with debuggability.
|
||||
|
||||
### 7.4.3 Data Retention
|
||||
|
||||
| Environment | Hot Storage | Warm Storage | Cold Archive |
|
||||
| ----------- | ----------- | ------------ | ------------ |
|
||||
| Development | 24 hours | N/A | N/A |
|
||||
| Staging | 7 days | N/A | N/A |
|
||||
| Production | 7 days | 30 days | many years |
|
||||
|
||||
---
|
||||
|
||||
## 7.5 Integration Checklist
|
||||
|
||||
- [ ] Choose primary backend (Tempo recommended for cost/features)
|
||||
- [ ] Deploy collector cluster with high availability
|
||||
- [ ] Configure tail-based sampling for error/latency traces
|
||||
- [ ] Set up Grafana dashboards for trace visualization
|
||||
- [ ] Configure alerts for trace anomalies
|
||||
- [ ] Establish data retention policies
|
||||
- [ ] Test trace correlation with logs and metrics
|
||||
|
||||
---
|
||||
|
||||
## 7.6 Grafana Dashboard Examples
|
||||
|
||||
Pre-built dashboards for xrpld observability.
|
||||
|
||||
### 7.6.1 Consensus Health Dashboard
|
||||
|
||||
A Tempo-backed dashboard (uid `xrpld-consensus-health`) with four panels, all driven by TraceQL:
|
||||
|
||||
- **Consensus Round Duration** (timeseries, ms): average `consensus.round` span duration per node instance, with yellow/red thresholds at 4s/5s.
|
||||
- **Phase Duration Breakdown** (barchart): average duration of `consensus.phase.*` spans grouped by span name.
|
||||
- **Proposers per Round** (stat): average of the `span.proposers` attribute on `consensus.round` spans.
|
||||
- **Recent Slow Rounds (>5s)** (table): `consensus.round` spans filtered to `duration > 5s`.
|
||||
|
||||
Each panel's TraceQL query is described inline in its bullet above.
|
||||
|
||||
### 7.6.2 Node Overview Dashboard
|
||||
|
||||
A Tempo-backed dashboard (uid `xrpld-node-overview`) with four panels:
|
||||
|
||||
- **Active Nodes** (stat): count of distinct `resource.service.instance.id` values seen for the `xrpld` service.
|
||||
- **Total Transactions (1h)** (stat): count of `tx.receive` spans.
|
||||
- **Error Rate** (gauge, percent): ratio of `status.code=error` spans to all spans, with yellow/red thresholds at 1%/5%.
|
||||
- **Service Map** (nodeGraph): Tempo-generated service dependency graph.
|
||||
|
||||
### 7.6.3 Alert Rules
|
||||
|
||||
Grafana provisions three TraceQL-based alert rules (group `xrpld-tracing-alerts`, evaluated every 1m) against the Tempo datasource:
|
||||
|
||||
- **Consensus Round Slow** (warning, `for: 5m`): fires when average `consensus.round` duration exceeds 5s.
|
||||
|
||||
```
|
||||
{resource.service.name="xrpld" && name="consensus.round"} | avg(duration) > 5s
|
||||
```
|
||||
|
||||
- **RPC Error Rate Spike** (critical, `for: 2m`): fires when the error rate across `rpc.command.*` spans exceeds 5%. Error _rate_ is a ratio, so it must divide the error-span rate by the total-span rate — a single TraceQL `rate()` returns spans/second, not a percentage, and would fire on traffic volume alone. This uses span metrics emitted by the collector's `spanmetrics` connector (Prometheus datasource), not a TraceQL query:
|
||||
|
||||
```
|
||||
sum(rate(traces_spanmetrics_calls_total{service_name="xrpld", span_name=~"rpc.command.*", status_code="STATUS_CODE_ERROR"}[5m]))
|
||||
/
|
||||
sum(rate(traces_spanmetrics_calls_total{service_name="xrpld", span_name=~"rpc.command.*"}[5m]))
|
||||
> 0.05
|
||||
```
|
||||
|
||||
- **Transaction Throughput Drop** (warning, `for: 10m`): fires when the `tx.receive` span rate falls below 10/s.
|
||||
|
||||
```
|
||||
{resource.service.name="xrpld" && name="tx.receive"} | rate() < 10
|
||||
```
|
||||
|
||||
> **Note**: The Consensus Round Slow and Transaction Throughput Drop rules use TraceQL aggregates (`avg(duration)`, `rate()`), which require Tempo 2.3+ with TraceQL metrics enabled. Verify aggregate query support in your Tempo version before provisioning. The RPC Error Rate Spike rule instead queries Prometheus span metrics (collector `spanmetrics` connector), so it needs that connector enabled in the collector pipeline.
|
||||
|
||||
---
|
||||
|
||||
## 7.7 PerfLog and Insight Correlation
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol
|
||||
|
||||
How to correlate OpenTelemetry traces with existing xrpld observability.
|
||||
|
||||
### 7.7.1 Correlation Architecture
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph xrpld["xrpld Node"]
|
||||
otel[OpenTelemetry<br/>Spans]
|
||||
perflog[PerfLog<br/>JSON Logs]
|
||||
insight[Beast Insight<br/>StatsD Metrics]
|
||||
end
|
||||
|
||||
subgraph collectors["Data Collection"]
|
||||
otelc[OTel Collector]
|
||||
promtail[Promtail/Fluentd]
|
||||
statsd[StatsD Exporter]
|
||||
end
|
||||
|
||||
subgraph storage["Storage"]
|
||||
tempo[(Tempo)]
|
||||
loki[(Loki)]
|
||||
prom[(Prometheus)]
|
||||
end
|
||||
|
||||
subgraph grafana["Grafana"]
|
||||
traces[Trace View]
|
||||
logs[Log View]
|
||||
metrics[Metrics View]
|
||||
corr[Correlation<br/>Panel]
|
||||
end
|
||||
|
||||
otel -->|OTLP| otelc --> tempo
|
||||
perflog -->|JSON| promtail --> loki
|
||||
insight -->|StatsD| statsd --> prom
|
||||
|
||||
tempo --> traces
|
||||
loki --> logs
|
||||
prom --> metrics
|
||||
|
||||
traces --> corr
|
||||
logs --> corr
|
||||
metrics --> corr
|
||||
|
||||
style xrpld fill:#0d47a1,stroke:#082f6a,color:#fff
|
||||
style collectors fill:#bf360c,stroke:#8c2809,color:#fff
|
||||
style storage fill:#1b5e20,stroke:#0d3d14,color:#fff
|
||||
style grafana fill:#4a148c,stroke:#2e0d57,color:#fff
|
||||
style otel fill:#0d47a1,stroke:#082f6a,color:#fff
|
||||
style perflog fill:#0d47a1,stroke:#082f6a,color:#fff
|
||||
style insight fill:#0d47a1,stroke:#082f6a,color:#fff
|
||||
style otelc fill:#bf360c,stroke:#8c2809,color:#fff
|
||||
style promtail fill:#bf360c,stroke:#8c2809,color:#fff
|
||||
style statsd fill:#bf360c,stroke:#8c2809,color:#fff
|
||||
style tempo fill:#1b5e20,stroke:#0d3d14,color:#fff
|
||||
style loki fill:#1b5e20,stroke:#0d3d14,color:#fff
|
||||
style prom fill:#1b5e20,stroke:#0d3d14,color:#fff
|
||||
style traces fill:#4a148c,stroke:#2e0d57,color:#fff
|
||||
style logs fill:#4a148c,stroke:#2e0d57,color:#fff
|
||||
style metrics fill:#4a148c,stroke:#2e0d57,color:#fff
|
||||
style corr fill:#4a148c,stroke:#2e0d57,color:#fff
|
||||
```
|
||||
|
||||
**Reading the diagram:**
|
||||
|
||||
- **xrpld Node (three sources)**: A single node emits three independent data streams -- OpenTelemetry spans, PerfLog JSON logs, and Beast Insight StatsD metrics.
|
||||
- **Data Collection layer**: Each stream has its own collector -- OTel Collector for spans, Promtail/Fluentd for logs, and a StatsD exporter for metrics. They operate independently.
|
||||
- **Storage layer (Tempo, Loki, Prometheus)**: Each data type lands in a purpose-built store optimized for its query patterns (trace search, log grep, metric aggregation).
|
||||
- **Grafana Correlation Panel**: The key integration point -- Grafana queries all three stores and links them via shared fields (`trace_id`, `tx_hash`, `ledger_seq`), enabling a single-pane debugging experience.
|
||||
|
||||
### 7.7.2 Correlation Fields
|
||||
|
||||
| Source | Field | Link To | Purpose |
|
||||
| ----------- | ------------------- | ------------- | -------------------------- |
|
||||
| **Trace** | `trace_id` | Logs | Find log entries for trace |
|
||||
| **Trace** | `tx_hash` | Logs, Metrics | Find TX-related data |
|
||||
| **Trace** | `ledger_seq` | Logs | Find ledger-related logs |
|
||||
| **PerfLog** | `trace_id` (new) | Traces | Jump to trace from log |
|
||||
| **PerfLog** | `ledger_seq` | Traces | Find consensus trace |
|
||||
| **Insight** | `exemplar.trace_id` | Traces | Jump from metric spike |
|
||||
|
||||
### 7.7.3 Example: Debugging a Slow Transaction
|
||||
|
||||
**Step 1: Find the trace**
|
||||
|
||||
```
|
||||
# In Grafana Explore with Tempo
|
||||
{resource.service.name="xrpld" && span.tx_hash="ABC123..."}
|
||||
```
|
||||
|
||||
**Step 2: Get the trace_id from the trace view**
|
||||
|
||||
```
|
||||
Trace ID: 4bf92f3577b34da6a3ce929d0e0e4736
|
||||
```
|
||||
|
||||
**Step 3: Find related PerfLog entries**
|
||||
|
||||
```
|
||||
# In Grafana Explore with Loki
|
||||
{job="xrpld"} |= "4bf92f3577b34da6a3ce929d0e0e4736"
|
||||
```
|
||||
|
||||
**Step 4: Check Insight metrics for the time window**
|
||||
|
||||
```
|
||||
# In Grafana with Prometheus
|
||||
rate(xrpld_tx_applied_total[1m])
|
||||
@ timestamp_from_trace
|
||||
```
|
||||
|
||||
### 7.7.4 Unified Dashboard Example
|
||||
|
||||
A single dashboard (uid `xrpld-unified`) that ties traces, metrics, and logs together across the Tempo, Prometheus, and Loki datasources:
|
||||
|
||||
- **Transaction Latency (Traces)** (timeseries, Tempo): `histogram_over_time(duration)` of `tx.receive` spans.
|
||||
- **Transaction Rate (Metrics)** (timeseries, Prometheus): `rate(xrpld_tx_received_total[5m])` per instance, with a data link that opens the matching `tx.receive` traces in Tempo.
|
||||
- **Recent Logs** (logs, Loki): `{job="xrpld"} | json`.
|
||||
- **Trace Search** (table, Tempo): all `xrpld` traces, with per-row data links on `traceID` that jump to the trace in Tempo and to the correlated logs in Loki (`{job="xrpld"} |= "<traceID>"`).
|
||||
|
||||
The cross-datasource data links are what make this a single-pane debugging view; the correlation fields they rely on are listed in section 7.7.2.
|
||||
|
||||
---
|
||||
|
||||
_Previous: [Implementation Phases](./06-implementation-phases.md)_ | _Next: [Appendix](./08-appendix.md)_ | _Back to: [Overview](./OpenTelemetryPlan.md)_
|
||||
187
OpenTelemetryPlan/08-appendix.md
Normal file
187
OpenTelemetryPlan/08-appendix.md
Normal file
@@ -0,0 +1,187 @@
|
||||
# Appendix
|
||||
|
||||
> **Parent Document**: [OpenTelemetryPlan.md](./OpenTelemetryPlan.md)
|
||||
> **Related**: [Observability Backends](./07-observability-backends.md)
|
||||
|
||||
---
|
||||
|
||||
## 8.1 Glossary
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol | **TxQ** = Transaction Queue
|
||||
|
||||
| Term | Definition |
|
||||
| --------------------- | ---------------------------------------------------------- |
|
||||
| **Span** | A unit of work with start/end time, name, and attributes |
|
||||
| **Trace** | A collection of spans representing a complete request flow |
|
||||
| **Trace ID** | 128-bit unique identifier for a trace |
|
||||
| **Span ID** | 64-bit unique identifier for a span within a trace |
|
||||
| **Context** | Carrier for trace/span IDs across boundaries |
|
||||
| **Propagator** | Component that injects/extracts context |
|
||||
| **Sampler** | Decides which traces to record |
|
||||
| **Exporter** | Sends spans to backend |
|
||||
| **Collector** | Receives, processes, and forwards telemetry |
|
||||
| **OTLP** | OpenTelemetry Protocol (wire format) |
|
||||
| **W3C Trace Context** | Standard HTTP headers for trace propagation |
|
||||
| **Baggage** | Key-value pairs propagated across service boundaries |
|
||||
| **Resource** | Entity producing telemetry (service, host, etc.) |
|
||||
| **Instrumentation** | Code that creates telemetry data |
|
||||
|
||||
### xrpld-Specific Terms
|
||||
|
||||
| Term | Definition |
|
||||
| ----------------- | ------------------------------------------------------------- |
|
||||
| **Overlay** | P2P network layer managing peer connections |
|
||||
| **Consensus** | XRP Ledger consensus algorithm (RCL) |
|
||||
| **Proposal** | Validator's suggested transaction set for a ledger |
|
||||
| **Validation** | Validator's signature on a closed ledger |
|
||||
| **HashRouter** | Component for transaction deduplication |
|
||||
| **JobQueue** | Thread pool for asynchronous task execution |
|
||||
| **PerfLog** | Existing performance logging system in xrpld |
|
||||
| **Beast Insight** | Existing metrics framework in xrpld |
|
||||
| **PathFinding** | Payment path computation engine for cross-currency payments |
|
||||
| **TxQ** | Transaction queue managing fee-based prioritization |
|
||||
| **LoadManager** | Dynamic fee escalation based on network load |
|
||||
| **SHAMap** | SHA-256 hash-based map (Merkle trie variant) for ledger state |
|
||||
|
||||
---
|
||||
|
||||
## 8.2 Span Hierarchy Visualization
|
||||
|
||||
> **TxQ** = Transaction Queue
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph trace["Trace: Transaction Lifecycle"]
|
||||
rpc["rpc.request<br/>(entry point)"]
|
||||
validate["tx.validate"]
|
||||
relay["tx.relay<br/>(parent span)"]
|
||||
|
||||
subgraph peers["Peer Spans"]
|
||||
p1["peer.send<br/>Peer A"]
|
||||
p2["peer.send<br/>Peer B"]
|
||||
p3["peer.send<br/>Peer C"]
|
||||
end
|
||||
|
||||
subgraph pathfinding["PathFinding Spans"]
|
||||
pathfind["pathfind.request"]
|
||||
pathcomp["pathfind.compute"]
|
||||
end
|
||||
|
||||
consensus["consensus.round"]
|
||||
apply["tx.apply"]
|
||||
|
||||
subgraph txqueue["TxQ Spans"]
|
||||
txq["txq.enqueue"]
|
||||
txqApply["txq.apply"]
|
||||
end
|
||||
|
||||
feeCalc["fee.escalate"]
|
||||
end
|
||||
|
||||
subgraph validators["Validator Spans"]
|
||||
valFetch["validator.list.fetch"]
|
||||
valManifest["validator.manifest"]
|
||||
end
|
||||
|
||||
rpc --> validate
|
||||
rpc --> pathfind
|
||||
pathfind --> pathcomp
|
||||
validate --> relay
|
||||
relay --> p1
|
||||
relay --> p2
|
||||
relay --> p3
|
||||
p1 -.->|"context propagation"| consensus
|
||||
consensus --> apply
|
||||
apply --> txq
|
||||
txq --> txqApply
|
||||
txq --> feeCalc
|
||||
|
||||
style trace fill:#0f172a,stroke:#020617,color:#fff
|
||||
style peers fill:#1e3a8a,stroke:#172554,color:#fff
|
||||
style pathfinding fill:#134e4a,stroke:#0f766e,color:#fff
|
||||
style txqueue fill:#064e3b,stroke:#047857,color:#fff
|
||||
style validators fill:#4c1d95,stroke:#6d28d9,color:#fff
|
||||
style rpc fill:#1d4ed8,stroke:#1e40af,color:#fff
|
||||
style validate fill:#047857,stroke:#064e3b,color:#fff
|
||||
style relay fill:#047857,stroke:#064e3b,color:#fff
|
||||
style p1 fill:#0e7490,stroke:#155e75,color:#fff
|
||||
style p2 fill:#0e7490,stroke:#155e75,color:#fff
|
||||
style p3 fill:#0e7490,stroke:#155e75,color:#fff
|
||||
style consensus fill:#fef3c7,stroke:#fde68a,color:#1e293b
|
||||
style apply fill:#047857,stroke:#064e3b,color:#fff
|
||||
style pathfind fill:#0e7490,stroke:#155e75,color:#fff
|
||||
style pathcomp fill:#0e7490,stroke:#155e75,color:#fff
|
||||
style txq fill:#047857,stroke:#064e3b,color:#fff
|
||||
style txqApply fill:#047857,stroke:#064e3b,color:#fff
|
||||
style feeCalc fill:#047857,stroke:#064e3b,color:#fff
|
||||
style valFetch fill:#6d28d9,stroke:#4c1d95,color:#fff
|
||||
style valManifest fill:#6d28d9,stroke:#4c1d95,color:#fff
|
||||
```
|
||||
|
||||
**Reading the diagram:**
|
||||
|
||||
- **rpc.request (blue, top)**: The entry point — every traced transaction starts as an RPC call; this root span is the parent of all downstream work.
|
||||
- **tx.validate and pathfind.request (green/teal, first fork)**: The RPC request fans out into transaction validation and, for cross-currency payments, a PathFinding branch (`pathfind.request` -> `pathfind.compute`).
|
||||
- **tx.relay -> Peer Spans (teal, middle)**: After validation, the transaction is relayed to peers A, B, and C in parallel; each `peer.send` is a sibling child span showing fan-out across the network.
|
||||
- **context propagation (dashed arrow)**: The dotted line from `peer.send Peer A` to `consensus.round` represents the trace context crossing a node boundary — the receiving validator picks up the same `trace_id` and continues the trace.
|
||||
- **consensus.round -> tx.apply -> TxQ Spans (green, lower)**: Once consensus accepts the transaction, it is applied to the ledger; the TxQ spans (`txq.enqueue`, `txq.apply`, `fee.escalate`) capture queue depth and fee escalation behavior.
|
||||
- **Validator Spans (purple, detached)**: `validator.list.fetch` and `validator.manifest` are independent workflows for UNL management — they run on their own traces and are linked to consensus via Span Links, not parent-child relationships.
|
||||
|
||||
---
|
||||
|
||||
## 8.3 References
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol
|
||||
|
||||
### OpenTelemetry Resources
|
||||
|
||||
1. [OpenTelemetry C++ SDK](https://github.com/open-telemetry/opentelemetry-cpp)
|
||||
2. [OpenTelemetry Specification](https://opentelemetry.io/docs/specs/otel/)
|
||||
3. [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/)
|
||||
4. [OTLP Protocol Specification](https://opentelemetry.io/docs/specs/otlp/)
|
||||
|
||||
### Standards
|
||||
|
||||
5. [W3C Trace Context](https://www.w3.org/TR/trace-context/)
|
||||
6. [W3C Baggage](https://www.w3.org/TR/baggage/)
|
||||
7. [Protocol Buffers](https://protobuf.dev/)
|
||||
|
||||
### xrpld Resources
|
||||
|
||||
8. [xrpld Source Code](https://github.com/XRPLF/rippled)
|
||||
9. [XRP Ledger Documentation](https://xrpl.org/docs/)
|
||||
10. [xrpld Overlay README](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/README.md)
|
||||
11. [xrpld RPC README](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/README.md)
|
||||
12. [xrpld Consensus README](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/README.md)
|
||||
|
||||
---
|
||||
|
||||
## 8.4 Version History
|
||||
|
||||
| Version | Date | Author | Changes |
|
||||
| ------- | ---------- | ------ | -------------------------------------------------------------- |
|
||||
| 1.0 | 2026-02-12 | - | Initial implementation plan |
|
||||
| 1.1 | 2026-02-13 | - | Refactored into modular documents |
|
||||
| 1.2 | 2026-03-24 | - | Review fixes: accuracy corrections, cross-document consistency |
|
||||
|
||||
---
|
||||
|
||||
## 8.5 Document Index
|
||||
|
||||
### Plan Documents
|
||||
|
||||
| Document | Description |
|
||||
| ---------------------------------------------------------------- | -------------------------------------------- |
|
||||
| [OpenTelemetryPlan.md](./OpenTelemetryPlan.md) | Master overview and executive summary |
|
||||
| [00-tracing-fundamentals.md](./00-tracing-fundamentals.md) | Distributed tracing concepts and OTel primer |
|
||||
| [01-architecture-analysis.md](./01-architecture-analysis.md) | xrpld architecture and trace points |
|
||||
| [02-design-decisions.md](./02-design-decisions.md) | SDK selection, exporters, span conventions |
|
||||
| [03-implementation-strategy.md](./03-implementation-strategy.md) | Directory structure, performance analysis |
|
||||
| [05-configuration-reference.md](./05-configuration-reference.md) | xrpld config, CMake, Collector configs |
|
||||
| [06-implementation-phases.md](./06-implementation-phases.md) | Timeline, tasks, risks, success metrics |
|
||||
| [07-observability-backends.md](./07-observability-backends.md) | Backend selection and architecture |
|
||||
| [08-appendix.md](./08-appendix.md) | Glossary, references, version history |
|
||||
|
||||
---
|
||||
|
||||
_Previous: [Observability Backends](./07-observability-backends.md)_ | _Back to: [Overview](./OpenTelemetryPlan.md)_
|
||||
199
OpenTelemetryPlan/OpenTelemetryPlan.md
Normal file
199
OpenTelemetryPlan/OpenTelemetryPlan.md
Normal file
@@ -0,0 +1,199 @@
|
||||
# [OpenTelemetry](00-tracing-fundamentals.md) Distributed Tracing Implementation Plan for xrpld
|
||||
|
||||
## Executive Summary
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol
|
||||
|
||||
This document provides a comprehensive implementation plan for integrating OpenTelemetry distributed tracing into the xrpld XRP Ledger node software. The plan addresses the unique challenges of a decentralized peer-to-peer system where trace context must propagate across network boundaries between independent nodes.
|
||||
|
||||
### Key Benefits
|
||||
|
||||
- **End-to-end transaction visibility**: Track transactions from submission through consensus to ledger inclusion
|
||||
- **Consensus round analysis**: Understand timing and behavior of consensus phases across validators
|
||||
- **RPC performance insights**: Identify slow handlers and optimize response times
|
||||
- **Network topology understanding**: Visualize message propagation patterns between peers
|
||||
- **Incident debugging**: Correlate events across distributed nodes during issues
|
||||
|
||||
### Estimated Performance Overhead
|
||||
|
||||
| Metric | Overhead | Notes |
|
||||
| ------------- | ---------- | ------------------------------------------------ |
|
||||
| CPU | 1-3% | Span creation and attribute setting |
|
||||
| Memory | <10 MB | SDK statics + batch buffer + worker thread stack |
|
||||
| Network | 10-50 KB/s | Compressed OTLP export to collector |
|
||||
| Latency (p99) | <2% | With proper sampling configuration |
|
||||
|
||||
---
|
||||
|
||||
## Document Structure
|
||||
|
||||
This implementation plan is organized into modular documents for easier navigation:
|
||||
|
||||
<div align="center">
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
overview["📋 OpenTelemetryPlan.md<br/>(This Document)"]
|
||||
|
||||
subgraph fundamentals["Fundamentals"]
|
||||
fund["00-tracing-fundamentals.md"]
|
||||
end
|
||||
|
||||
subgraph analysis["Analysis & Design"]
|
||||
arch["01-architecture-analysis.md"]
|
||||
design["02-design-decisions.md"]
|
||||
end
|
||||
|
||||
subgraph impl["Implementation"]
|
||||
strategy["03-implementation-strategy.md"]
|
||||
config["05-configuration-reference.md"]
|
||||
end
|
||||
|
||||
subgraph deploy["Deployment & Planning"]
|
||||
phases["06-implementation-phases.md"]
|
||||
backends["07-observability-backends.md"]
|
||||
appendix["08-appendix.md"]
|
||||
end
|
||||
|
||||
overview --> fundamentals
|
||||
overview --> analysis
|
||||
overview --> impl
|
||||
overview --> deploy
|
||||
|
||||
fund --> arch
|
||||
arch --> design
|
||||
design --> strategy
|
||||
strategy --> config
|
||||
config --> phases
|
||||
phases --> backends
|
||||
backends --> appendix
|
||||
|
||||
style overview fill:#1b5e20,stroke:#0d3d14,color:#fff,stroke-width:2px
|
||||
style fundamentals fill:#00695c,stroke:#004d40,color:#fff
|
||||
style fund fill:#00695c,stroke:#004d40,color:#fff
|
||||
style analysis fill:#0d47a1,stroke:#082f6a,color:#fff
|
||||
style impl fill:#bf360c,stroke:#8c2809,color:#fff
|
||||
style deploy fill:#4a148c,stroke:#2e0d57,color:#fff
|
||||
style arch fill:#0d47a1,stroke:#082f6a,color:#fff
|
||||
style design fill:#0d47a1,stroke:#082f6a,color:#fff
|
||||
style strategy fill:#bf360c,stroke:#8c2809,color:#fff
|
||||
style config fill:#bf360c,stroke:#8c2809,color:#fff
|
||||
style phases fill:#4a148c,stroke:#2e0d57,color:#fff
|
||||
style backends fill:#4a148c,stroke:#2e0d57,color:#fff
|
||||
style appendix fill:#4a148c,stroke:#2e0d57,color:#fff
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
| Section | Document | Description |
|
||||
| ------- | ---------------------------------------------------------- | ---------------------------------------------------------------------- |
|
||||
| **0** | [Tracing Fundamentals](./00-tracing-fundamentals.md) | Distributed tracing concepts, span relationships, context propagation |
|
||||
| **1** | [Architecture Analysis](./01-architecture-analysis.md) | xrpld component analysis, trace points, instrumentation priorities |
|
||||
| **2** | [Design Decisions](./02-design-decisions.md) | SDK selection, exporters, span naming, attributes, context propagation |
|
||||
| **3** | [Implementation Strategy](./03-implementation-strategy.md) | Directory structure, key principles, performance optimization |
|
||||
| **5** | [Configuration Reference](./05-configuration-reference.md) | xrpld config, CMake integration, Collector configurations |
|
||||
| **6** | [Implementation Phases](./06-implementation-phases.md) | 5-phase timeline, tasks, risks, success metrics |
|
||||
| **7** | [Observability Backends](./07-observability-backends.md) | Backend selection guide and production architecture |
|
||||
| **8** | [Appendix](./08-appendix.md) | Glossary, references, version history |
|
||||
|
||||
---
|
||||
|
||||
## 0. Tracing Fundamentals
|
||||
|
||||
This document introduces distributed tracing concepts for readers unfamiliar with the domain. It covers what traces and spans are, how parent-child and follows-from relationships model causality, how context propagates across service boundaries, and how sampling controls data volume. It also maps these concepts to xrpld-specific scenarios like transaction relay and consensus.
|
||||
|
||||
➡️ **[Read Tracing Fundamentals](./00-tracing-fundamentals.md)**
|
||||
|
||||
---
|
||||
|
||||
## 1. Architecture Analysis
|
||||
|
||||
> **WS** = WebSocket | **TxQ** = Transaction Queue
|
||||
|
||||
The xrpld node consists of several key components that require instrumentation for comprehensive distributed tracing. The main areas include the RPC server (HTTP/WebSocket), Overlay P2P network, Consensus mechanism (RCLConsensus), JobQueue for async task execution, PathFinding, Transaction Queue (TxQ), fee escalation (LoadManager), ledger acquisition, validator management, and existing observability infrastructure (PerfLog, Insight/StatsD, Journal logging).
|
||||
|
||||
Key trace points span across transaction submission via RPC, peer-to-peer message propagation, consensus round execution, ledger building, path computation, transaction queue behavior, fee escalation, and validator health. The implementation prioritizes high-value, low-risk components first: RPC handlers provide immediate value with minimal risk, while consensus tracing requires careful implementation to avoid timing impacts.
|
||||
|
||||
➡️ **[Read full Architecture Analysis](./01-architecture-analysis.md)**
|
||||
|
||||
---
|
||||
|
||||
## 2. Design Decisions
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol | **CNCF** = Cloud Native Computing Foundation
|
||||
|
||||
The OpenTelemetry C++ SDK is selected for its CNCF backing, active development, and native performance characteristics. Traces are exported via OTLP/HTTP to an OpenTelemetry Collector, which provides flexible routing and sampling. OTLP/gRPC is planned future work (see design decisions §2.2.2).
|
||||
|
||||
Span naming follows a hierarchical `<component>.<operation>` convention (e.g., `rpc.submit`, `tx.relay`, `consensus.round`). Context propagation uses W3C Trace Context headers for HTTP and embedded Protocol Buffer fields for P2P messages. The implementation coexists with existing PerfLog and Insight observability systems through correlation IDs.
|
||||
|
||||
**Data Collection & Privacy**: Telemetry collects only operational metadata (timing, counts, hashes) — never sensitive content (private keys, balances, amounts, raw payloads). Privacy protection includes account hashing, configurable redaction, sampling, and collector-level filtering. Node operators retain full control over telemetry configuration.
|
||||
|
||||
➡️ **[Read full Design Decisions](./02-design-decisions.md)**
|
||||
|
||||
---
|
||||
|
||||
## 3. Implementation Strategy
|
||||
|
||||
The telemetry code is organized under `include/xrpl/telemetry/` for headers and `src/libxrpl/telemetry/` for implementation. Key principles include RAII-based span management via `SpanGuard`, conditional compilation with `XRPL_ENABLE_TELEMETRY`, and minimal runtime overhead through batch processing and efficient sampling.
|
||||
|
||||
Performance optimization strategies include head sampling fixed at 100% (intentionally not configurable, so trace keep/drop decisions stay coherent across nodes), tail-based sampling at the collector for errors and slow traces to reduce volume, batch export to reduce network overhead, and conditional instrumentation that compiles to no-ops when disabled.
|
||||
|
||||
➡️ **[Read full Implementation Strategy](./03-implementation-strategy.md)**
|
||||
|
||||
---
|
||||
|
||||
## 5. Configuration Reference
|
||||
|
||||
> **OTLP** = OpenTelemetry Protocol | **APM** = Application Performance Monitoring
|
||||
|
||||
Configuration is handled through the `[telemetry]` section in `xrpld.cfg` with options for enabling/disabling, exporter selection, endpoint configuration, and component-level filtering. Head sampling is fixed at 1.0 (not operator-configurable); volume reduction is done by tail sampling in the collector. CMake integration includes a `XRPL_ENABLE_TELEMETRY` option for compile-time control.
|
||||
|
||||
OpenTelemetry Collector configurations are provided for development and production (with tail-based sampling, Tempo, and Elastic APM). Docker Compose examples enable quick local development environment setup.
|
||||
|
||||
➡️ **[View full Configuration Reference](./05-configuration-reference.md)**
|
||||
|
||||
---
|
||||
|
||||
## 6. Implementation Phases
|
||||
|
||||
The implementation spans 9 weeks across 5 phases:
|
||||
|
||||
| Phase | Duration | Focus | Key Deliverables |
|
||||
| ----- | --------- | ------------------- | --------------------------------------------------- |
|
||||
| 1 | Weeks 1-2 | Core Infrastructure | SDK integration, Telemetry interface, Configuration |
|
||||
| 2 | Weeks 3-4 | RPC Tracing | HTTP context extraction, Handler instrumentation |
|
||||
| 3 | Weeks 5-6 | Transaction Tracing | Protocol Buffer context, Relay propagation |
|
||||
| 4 | Weeks 7-8 | Consensus Tracing | Round spans, Proposal/validation tracing |
|
||||
| 5 | Week 9 | Documentation | Runbook, Dashboards, Training |
|
||||
|
||||
**Total Effort**: 47 person-days (2 developers working in parallel)
|
||||
|
||||
➡️ **[View full Implementation Phases](./06-implementation-phases.md)**
|
||||
|
||||
---
|
||||
|
||||
## 7. Observability Backends
|
||||
|
||||
> **APM** = Application Performance Monitoring | **GCS** = Google Cloud Storage
|
||||
|
||||
Grafana Tempo is recommended for all environments due to its cost-effectiveness and Grafana integration, while Elastic APM is ideal for organizations with existing Elastic infrastructure.
|
||||
|
||||
The recommended production architecture uses a gateway collector pattern with regional collectors performing tail-based sampling, routing traces to multiple backends (Tempo for primary storage, Elastic for log correlation, S3/GCS for long-term archive).
|
||||
|
||||
➡️ **[View Observability Backend Recommendations](./07-observability-backends.md)**
|
||||
|
||||
---
|
||||
|
||||
## 8. Appendix
|
||||
|
||||
The appendix contains a glossary of OpenTelemetry and xrpld-specific terms, references to external documentation and specifications, version history for this implementation plan, and a complete document index.
|
||||
|
||||
➡️ **[View Appendix](./08-appendix.md)**
|
||||
|
||||
---
|
||||
|
||||
_This document provides a comprehensive implementation plan for integrating OpenTelemetry distributed tracing into the xrpld XRP Ledger node software. For detailed information on any section, follow the links to the corresponding sub-documents._
|
||||
@@ -1,440 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Check C++ Doxygen comment style.
|
||||
|
||||
Enforces the house convention for documentation comments:
|
||||
|
||||
* Use ``/** ... */`` blocks, not ``///``, ``//!`` or ``/*! ... */``; a plain
|
||||
``/* ... */`` that contains Doxygen commands is a doc comment missing its
|
||||
second star. Trailing member-after comments use ``///<`` (not ``//!<``,
|
||||
``/*!< ... */`` or ``/**< ... */`` -- the block forms get reflowed and
|
||||
mis-attached by clang-format on packed enum values, the line form does not).
|
||||
* ``/**`` sits alone on its line; the closing ``*/`` sits alone on its line.
|
||||
* Every content line is prefixed with `` * `` (no bare-indented continuation).
|
||||
* The first content line is flush (not over-indented).
|
||||
* Doxygen commands use the ``@cmd`` form, not ``\\cmd``.
|
||||
* Use ``@return`` / ``@throws`` rather than prose ``Returns:`` / ``Throws:``.
|
||||
* A plain ``//`` comment carrying a block-level ``@command`` (``@param``,
|
||||
``@return``, ``@see``, ...) is documentation and must be a ``/** ... */``
|
||||
block (Doxygen ignores ``//``).
|
||||
* Use canonical command spellings: ``@return`` (not ``@returns``),
|
||||
``@throws`` (not ``@throw``), ``@see`` (not ``@sa``).
|
||||
* Order block tags ``@tparam`` -> ``@param`` -> ``@return``. (Whether
|
||||
``@param`` order matches the signature is not checked here -- too fragile to
|
||||
parse; Doxygen's WARN_IF_DOC_ERROR covers name mismatches.)
|
||||
* One-liners are expanded to three lines, EXCEPT bare markers ``@{`` / ``@}``
|
||||
/ ``@cond [label]`` / ``@endcond`` / ``@file [name]`` which stay on one line.
|
||||
|
||||
Left intentionally alone (recognized, valid Doxygen that is not this style's
|
||||
concern):
|
||||
|
||||
* ``///<`` trailing "member-after" comments (the house form).
|
||||
* Divider lines made only of slashes (``//////////``).
|
||||
* Plain ``/* ... */`` (non-Doxygen) comments.
|
||||
|
||||
Usage:
|
||||
check_doxygen_style.py [FILE ...] # explicit files
|
||||
check_doxygen_style.py # default: src/ and include/ trees
|
||||
|
||||
Exit status is non-zero if any violation is found.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import sys
|
||||
from collections.abc import Iterable, Iterator
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class Category(Enum):
|
||||
"""A kind of style violation: a printed ``label`` and its ``description``.
|
||||
|
||||
The description is the default message; a few categories whose wording
|
||||
depends on the offending text (see ``Finding.detail``) override it.
|
||||
"""
|
||||
|
||||
def __init__(self, label: str, description: str) -> None:
|
||||
self.label = label
|
||||
self.description = description
|
||||
|
||||
BACKSLASH_COMMAND = ("backslash-command", "use the @cmd form, not \\cmd")
|
||||
WRONG_COMMAND = ("wrong-command", "use the canonical command spelling")
|
||||
TRIPLE_SLASH = ("triple-slash", "use a /** ... */ block instead of ///")
|
||||
QT_MEMBER = ("qt-member", "use ///< instead of //!<")
|
||||
QT_LINE = ("qt-line", "use a /** ... */ block instead of //!")
|
||||
BLOCK_MEMBER = ("block-member", "use ///< instead of /**<")
|
||||
QT_BLOCK_MEMBER = ("qt-block-member", "use ///< instead of /*!<")
|
||||
DOC_IN_LINE_COMMENT = (
|
||||
"doc-in-line-comment",
|
||||
"use a /** ... */ block for documentation, not //",
|
||||
)
|
||||
QT_COMMENT = ("qt-comment", "use /** instead of /*!")
|
||||
SINGLE_LINE_BLOCK = (
|
||||
"single-line-block",
|
||||
"expand one-line /** ... */ to a multi-line block "
|
||||
"(markers @{ @} @cond @endcond @file may stay)",
|
||||
)
|
||||
TEXT_ON_OPENER = ("text-on-opener", "move text off the /** opener line")
|
||||
BARE_CONTINUATION = ("bare-continuation", 'prefix continuation lines with " * "')
|
||||
OVER_INDENTED = ("over-indented", "first content line is over-indented")
|
||||
OVER_INDENTED_TAG = (
|
||||
"over-indented-tag",
|
||||
'Doxygen tag over-indented; use a single space after "*"',
|
||||
)
|
||||
COMBINED_MARKER = (
|
||||
"combined-marker",
|
||||
"scope marker @{ / @} should be its own single-line /** @{ */ block",
|
||||
)
|
||||
PROSE_LABEL = ("prose-label", "use a Doxygen tag instead of a prose label")
|
||||
CONTENT_ON_CLOSER = ("content-on-closer", "move content off the closing */ line")
|
||||
PLAIN_BLOCK_DOC = (
|
||||
"plain-block-doc",
|
||||
"documentation comment must open with /** not /*",
|
||||
)
|
||||
TAG_ORDER = (
|
||||
"tag-order",
|
||||
"block tags out of order; expected @tparam, then @param, then @return",
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Finding:
|
||||
"""A single style violation at a 1-based line number.
|
||||
|
||||
``detail`` overrides the category's default description when the message
|
||||
depends on the offending text (e.g. which command was misspelled).
|
||||
"""
|
||||
|
||||
line: int
|
||||
category: Category
|
||||
detail: str | None = None
|
||||
|
||||
@property
|
||||
def message(self) -> str:
|
||||
return self.detail if self.detail is not None else self.category.description
|
||||
|
||||
|
||||
DEFAULT_ROOTS = ("src", "include")
|
||||
EXTS = {".h", ".hpp", ".cpp", ".ipp", ".cxx", ".cc"}
|
||||
|
||||
# Every Doxygen command we recognize when written with a backslash (\cmd).
|
||||
_ALL_COMMANDS = (
|
||||
"brief|param|tparam|return|returns|retval|note|warning|pre|post|see|sa|ref|"
|
||||
"throw|throws|exception|deprecated|details|code|endcode|verbatim|endverbatim|"
|
||||
"li|arg|c|internal|since|todo|attention|remark|remarks|ingroup|defgroup"
|
||||
)
|
||||
# Block-level tags whose over-indentation we flag inside a block body.
|
||||
_BLOCK_TAGS = (
|
||||
"param|tparam|returns?|retval|brief|throws?|note|warning|"
|
||||
"pre|post|see|sa|details|deprecated"
|
||||
)
|
||||
# Tags that, appearing anywhere in a comment, mark it as documentation.
|
||||
_ANY_DOC_TAGS = (
|
||||
"param|tparam|returns?|retval|brief|throws?|note|warning|pre|post|see|sa"
|
||||
)
|
||||
# Tags that make a plain // comment a mis-styled doc comment.
|
||||
_LINE_DOC_TAGS = "brief|param|tparam|returns?|retval|throws?|note|see|pre|post"
|
||||
|
||||
# \cmd that should be @cmd.
|
||||
RE_BACKSLASH_CMD = re.compile(r"\\(" + _ALL_COMMANDS + r")\b")
|
||||
# Bare markers that may legitimately stay on a single line.
|
||||
RE_MARKER = re.compile(r"^@(\{|\}|cond(\s.*)?|endcond|file(\s.*)?)$")
|
||||
# Prose section labels that should be Doxygen tags.
|
||||
RE_PROSE_LABEL = re.compile(r"^\*\s(Returns|Throws|Exceptions):\s*$")
|
||||
# An over-indented block tag: "*" followed by 2+ spaces then the tag.
|
||||
RE_OVERINDENTED_TAG = re.compile(r"^\*\s{2,}@(" + _BLOCK_TAGS + r")\b")
|
||||
# Any documentation tag (used to spot a doc comment hiding in a plain /* */).
|
||||
RE_ANY_DOC_TAG = re.compile(r"@(" + _ANY_DOC_TAGS + r")\b")
|
||||
# A documentation tag inside a // comment.
|
||||
RE_LINE_DOC_TAG = re.compile(r"@(" + _LINE_DOC_TAGS + r")\b")
|
||||
# Order-relevant tags, for the @tparam -> @param -> @return ordering check.
|
||||
RE_ORDER_TAG = re.compile(r"^\*\s*@(param|tparam|returns?|retval)\b")
|
||||
# First content line indented by 2+ spaces after the "*".
|
||||
RE_FIRST_OVERINDENT = re.compile(r"^\s*\*\s{2,}\S")
|
||||
# A scope marker @{ / @} sharing a comment with other text.
|
||||
RE_COMBINED_MARKER = re.compile(r"^\*\s*@[{}]\s*$")
|
||||
|
||||
# Non-canonical command spellings -> the house spelling (bare command names).
|
||||
# Used both to flag a wrong @form and to suggest the right @form for a \wrong.
|
||||
CANONICAL_COMMAND = {"returns": "return", "throw": "throws", "sa": "see"}
|
||||
WRONG_SPELLINGS = [
|
||||
(re.compile(rf"@{wrong}\b"), f"@{right}")
|
||||
for wrong, right in CANONICAL_COMMAND.items()
|
||||
]
|
||||
|
||||
# Order block tags should appear in; a body out of this order is a violation.
|
||||
EXPECTED_TAG_ORDER = ("tparam", "param", "return")
|
||||
|
||||
|
||||
def is_doxy_open(stripped: str) -> bool:
|
||||
"""True for a line-start Doxygen block opener we should normalize."""
|
||||
if stripped.startswith("/*!"): # Qt-style Doxygen
|
||||
return not stripped.startswith("/*!<") # member-after, leave inline
|
||||
return (
|
||||
stripped.startswith("/**")
|
||||
and not stripped.startswith("/***")
|
||||
and not stripped.startswith("/**/")
|
||||
and not stripped.startswith("/**<")
|
||||
)
|
||||
|
||||
|
||||
def _flag_commands(raw_line: str, stripped: str, index: int) -> list[Finding]:
|
||||
"""Flag \\cmd and misspelled @cmd on a comment line (opener, body, or closer)."""
|
||||
if not stripped.startswith(("*", "//", "/*")):
|
||||
return []
|
||||
findings: list[Finding] = []
|
||||
backslash = RE_BACKSLASH_CMD.search(raw_line)
|
||||
if backslash:
|
||||
command = backslash.group(1)
|
||||
canonical = CANONICAL_COMMAND.get(command, command)
|
||||
findings.append(
|
||||
Finding(
|
||||
index + 1,
|
||||
Category.BACKSLASH_COMMAND,
|
||||
f"use @{canonical} instead of \\{command}",
|
||||
)
|
||||
)
|
||||
for pattern, replacement in WRONG_SPELLINGS:
|
||||
wrong = pattern.search(raw_line)
|
||||
if wrong:
|
||||
findings.append(
|
||||
Finding(
|
||||
index + 1,
|
||||
Category.WRONG_COMMAND,
|
||||
f"use {replacement} instead of {wrong.group(0)}",
|
||||
)
|
||||
)
|
||||
return findings
|
||||
|
||||
|
||||
def _flag_line_comment(raw_line: str, stripped: str, index: int) -> Finding | None:
|
||||
"""Return the finding for a single-line comment form (///, //!, /**<, ...), else None."""
|
||||
if stripped.startswith("///") and not stripped.startswith(("////", "///<")):
|
||||
return Finding(index + 1, Category.TRIPLE_SLASH)
|
||||
if "//!<" in raw_line:
|
||||
return Finding(index + 1, Category.QT_MEMBER)
|
||||
if stripped.startswith("//!"):
|
||||
return Finding(index + 1, Category.QT_LINE)
|
||||
if "/**<" in raw_line:
|
||||
return Finding(index + 1, Category.BLOCK_MEMBER)
|
||||
if "/*!<" in raw_line:
|
||||
return Finding(index + 1, Category.QT_BLOCK_MEMBER)
|
||||
if stripped.startswith("//") and RE_LINE_DOC_TAG.search(stripped):
|
||||
return Finding(index + 1, Category.DOC_IN_LINE_COMMENT)
|
||||
return None
|
||||
|
||||
|
||||
def _flag_single_line_block(stripped: str, line_no: int, is_qt: bool) -> list[Finding]:
|
||||
"""Findings for a whole /** ... */ or /*! ... */ block on one line."""
|
||||
inner = re.sub(r"^/\*[*!]", "", stripped)
|
||||
inner = re.sub(r"\*/\s*$", "", inner).strip()
|
||||
findings: list[Finding] = []
|
||||
if is_qt:
|
||||
findings.append(Finding(line_no, Category.QT_COMMENT))
|
||||
if inner and not RE_MARKER.match(inner):
|
||||
findings.append(Finding(line_no, Category.SINGLE_LINE_BLOCK))
|
||||
return findings
|
||||
|
||||
|
||||
def _canonical_order_tag(body: str) -> str | None:
|
||||
"""The order-relevant tag (tparam/param/return) a body line opens with, if any."""
|
||||
match = RE_ORDER_TAG.match(body)
|
||||
if match is None:
|
||||
return None
|
||||
command = match.group(1)
|
||||
return "return" if command in ("return", "returns", "retval") else command
|
||||
|
||||
|
||||
def _flag_body_line(
|
||||
body_line: str, line_no: int, is_first_content: bool
|
||||
) -> list[Finding]:
|
||||
"""Findings for one interior line of a multi-line block."""
|
||||
body = body_line.strip()
|
||||
findings: list[Finding] = []
|
||||
if body and not body.startswith("*"):
|
||||
findings.append(Finding(line_no, Category.BARE_CONTINUATION))
|
||||
if body.startswith("*"):
|
||||
if is_first_content and RE_FIRST_OVERINDENT.match(body_line):
|
||||
findings.append(Finding(line_no, Category.OVER_INDENTED))
|
||||
if RE_OVERINDENTED_TAG.match(body):
|
||||
findings.append(Finding(line_no, Category.OVER_INDENTED_TAG))
|
||||
if RE_COMBINED_MARKER.match(body):
|
||||
findings.append(Finding(line_no, Category.COMBINED_MARKER))
|
||||
label = RE_PROSE_LABEL.match(body)
|
||||
if label:
|
||||
suggested_tag = "@return" if label.group(1) == "Returns" else "@throws"
|
||||
findings.append(
|
||||
Finding(
|
||||
line_no,
|
||||
Category.PROSE_LABEL,
|
||||
f'use {suggested_tag} instead of prose "{label.group(1)}:"',
|
||||
)
|
||||
)
|
||||
return findings
|
||||
|
||||
|
||||
def _flag_closer(closer_line: str, line_no: int) -> list[Finding]:
|
||||
"""Findings for content sharing the closing */ line."""
|
||||
before = closer_line[: closer_line.index("*/")].strip()
|
||||
if before and before != "*":
|
||||
return [Finding(line_no, Category.CONTENT_ON_CLOSER)]
|
||||
return []
|
||||
|
||||
|
||||
def _flag_tag_order(first_tag_line: dict[str, int]) -> list[Finding]:
|
||||
"""One finding if the present block tags are not in EXPECTED_TAG_ORDER."""
|
||||
tag_lines = [
|
||||
first_tag_line[tag] for tag in EXPECTED_TAG_ORDER if tag in first_tag_line
|
||||
]
|
||||
if tag_lines != sorted(tag_lines):
|
||||
return [Finding(min(tag_lines), Category.TAG_ORDER)]
|
||||
return []
|
||||
|
||||
|
||||
def _flag_doxy_block(lines: list[str], start: int) -> tuple[int, list[Finding]]:
|
||||
"""Handle a /** or /*! block opening at ``start``; return (next index, findings)."""
|
||||
raw_line = lines[start]
|
||||
stripped = raw_line.lstrip()
|
||||
open_pos = raw_line.index("/*")
|
||||
is_qt = stripped.startswith("/*!")
|
||||
|
||||
# A whole block on one line: /** ... */.
|
||||
if "*/" in raw_line[open_pos + 2 :]:
|
||||
return start + 1, _flag_single_line_block(stripped, start + 1, is_qt)
|
||||
|
||||
# Multi-line block: opener, then scan the body to the closer.
|
||||
findings: list[Finding] = []
|
||||
if is_qt:
|
||||
findings.append(Finding(start + 1, Category.QT_COMMENT))
|
||||
if raw_line[open_pos + 3 :].strip():
|
||||
findings.append(Finding(start + 1, Category.TEXT_ON_OPENER))
|
||||
|
||||
line_count = len(lines)
|
||||
cursor = start + 1
|
||||
is_first_content = True
|
||||
first_tag_line: dict[str, int] = {} # canonical tag -> 1-based first line
|
||||
while cursor < line_count and "*/" not in lines[cursor]:
|
||||
body_line = lines[cursor]
|
||||
body = body_line.strip()
|
||||
findings.extend(_flag_commands(body_line, body, cursor))
|
||||
tag = _canonical_order_tag(body)
|
||||
if tag is not None:
|
||||
first_tag_line.setdefault(tag, cursor + 1)
|
||||
findings.extend(_flag_body_line(body_line, cursor + 1, is_first_content))
|
||||
if body.startswith("*"):
|
||||
is_first_content = False
|
||||
cursor += 1
|
||||
|
||||
if cursor < line_count:
|
||||
closer_line = lines[cursor]
|
||||
findings.extend(_flag_commands(closer_line, closer_line.strip(), cursor))
|
||||
findings.extend(_flag_closer(closer_line, cursor + 1))
|
||||
findings.extend(_flag_tag_order(first_tag_line))
|
||||
|
||||
return cursor + 1, findings
|
||||
|
||||
|
||||
def _flag_plain_block(lines: list[str], start: int) -> tuple[int, list[Finding]]:
|
||||
"""Handle a line-start plain /* ... */ block; return (next index, findings).
|
||||
|
||||
Only flagged when it hides a documentation command (a missing second star).
|
||||
"""
|
||||
line_count = len(lines)
|
||||
cursor = start
|
||||
while cursor < line_count and "*/" not in lines[cursor]:
|
||||
cursor += 1
|
||||
findings: list[Finding] = []
|
||||
# The opener (start) is command-checked by check_file; check the rest here.
|
||||
for i in range(start + 1, min(cursor + 1, line_count)):
|
||||
findings.extend(_flag_commands(lines[i], lines[i].strip(), i))
|
||||
block_text = "\n".join(
|
||||
lines[start : cursor + 1] if cursor < line_count else lines[start:]
|
||||
)
|
||||
if RE_ANY_DOC_TAG.search(block_text):
|
||||
findings.append(Finding(start + 1, Category.PLAIN_BLOCK_DOC))
|
||||
next_index = cursor + 1 if cursor < line_count else line_count
|
||||
return next_index, findings
|
||||
|
||||
|
||||
def check_source(text: str) -> list[Finding]:
|
||||
"""Return all style violations found in the given source text."""
|
||||
lines = text.split("\n")
|
||||
findings: list[Finding] = []
|
||||
line_count = len(lines)
|
||||
index = 0
|
||||
in_plain_block = False # inside a mid-line, non-Doxygen /* ... */
|
||||
while index < line_count:
|
||||
raw_line = lines[index]
|
||||
stripped = raw_line.lstrip()
|
||||
|
||||
# Skip the interior of a plain block opened on an earlier line.
|
||||
if in_plain_block:
|
||||
in_plain_block = "*/" not in raw_line
|
||||
index += 1
|
||||
continue
|
||||
|
||||
findings.extend(_flag_commands(raw_line, stripped, index))
|
||||
|
||||
line_finding = _flag_line_comment(raw_line, stripped, index)
|
||||
if line_finding is not None:
|
||||
findings.append(line_finding)
|
||||
index += 1
|
||||
elif is_doxy_open(stripped):
|
||||
index, block_findings = _flag_doxy_block(lines, index)
|
||||
findings.extend(block_findings)
|
||||
elif stripped.startswith("/*"):
|
||||
index, block_findings = _flag_plain_block(lines, index)
|
||||
findings.extend(block_findings)
|
||||
else:
|
||||
# A /* that opens mid-line without closing starts a plain block.
|
||||
if "/*" in raw_line and not stripped.startswith("//"):
|
||||
if "*/" not in raw_line[raw_line.index("/*") + 2 :]:
|
||||
in_plain_block = True
|
||||
index += 1
|
||||
return findings
|
||||
|
||||
|
||||
def check_file(path: Path) -> list[Finding]:
|
||||
"""Return all style violations found in one file."""
|
||||
return check_source(path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def iter_files(paths: Iterable[str]) -> Iterator[Path]:
|
||||
"""Yield every C++ source file among the given files and directories."""
|
||||
for raw_path in paths:
|
||||
path = Path(raw_path)
|
||||
if path.is_dir():
|
||||
for candidate in path.rglob("*"):
|
||||
if candidate.is_file() and candidate.suffix in EXTS:
|
||||
yield candidate
|
||||
elif path.suffix in EXTS:
|
||||
yield path
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Check Doxygen comment style.")
|
||||
parser.add_argument(
|
||||
"files", nargs="*", help="files or directories (default: src/ include/)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-q", "--quiet", action="store_true", help="only print the summary count"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
roots = args.files or [root for root in DEFAULT_ROOTS if Path(root).is_dir()]
|
||||
|
||||
total = 0
|
||||
for path in sorted(set(iter_files(roots)), key=str):
|
||||
for finding in check_file(path):
|
||||
total += 1
|
||||
if not args.quiet:
|
||||
print(
|
||||
f"{path}:{finding.line}: {finding.category.label}: {finding.message}"
|
||||
)
|
||||
print(f"\n{total} doxygen-style violation(s)", file=sys.stderr)
|
||||
return 1 if total else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -1,406 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Tests for check_doxygen_style.py.
|
||||
|
||||
Run directly (no test framework needed):
|
||||
./bin/pre-commit/test_check_doxygen_style.py
|
||||
or under pytest:
|
||||
pytest bin/pre-commit/test_check_doxygen_style.py
|
||||
"""
|
||||
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
from check_doxygen_style import Finding, check_source
|
||||
|
||||
|
||||
def findings_for(text: str) -> list[Finding]:
|
||||
"""Return the style violations for the given source text.
|
||||
|
||||
The text is dedented and its leading newline stripped, so fixtures can be
|
||||
written as indented triple-quoted here-docs while keeping honest 1-based
|
||||
line numbers.
|
||||
"""
|
||||
text = textwrap.dedent(text).lstrip("\n")
|
||||
return check_source(text)
|
||||
|
||||
|
||||
def labels_for(text: str) -> list[str]:
|
||||
return [f.category.label for f in findings_for(text)]
|
||||
|
||||
|
||||
def messages_for(text: str) -> list[str]:
|
||||
return [f.message for f in findings_for(text)]
|
||||
|
||||
|
||||
# --- well-formed input produces nothing -------------------------------------
|
||||
|
||||
|
||||
def test_clean_block_ok() -> None:
|
||||
code = """
|
||||
/**
|
||||
* Brief.
|
||||
*
|
||||
* @tparam T a type
|
||||
* @param x the x
|
||||
* @return the result
|
||||
*/
|
||||
"""
|
||||
assert findings_for(code) == []
|
||||
|
||||
|
||||
def test_blank_lines_inside_block_ok() -> None:
|
||||
code = """
|
||||
/**
|
||||
* a
|
||||
*
|
||||
* b
|
||||
*/
|
||||
"""
|
||||
assert findings_for(code) == []
|
||||
|
||||
|
||||
def test_member_and_divider_allowed() -> None:
|
||||
assert findings_for("int x; ///< ok member\n") == []
|
||||
assert findings_for("//////////\n") == []
|
||||
assert findings_for("//// text\n") == []
|
||||
|
||||
|
||||
# --- line-comment forms ------------------------------------------------------
|
||||
|
||||
|
||||
def test_triple_slash() -> None:
|
||||
code = "/// doc\n"
|
||||
assert labels_for(code) == ["triple-slash"]
|
||||
|
||||
|
||||
def test_qt_line() -> None:
|
||||
code = "//! doc\n"
|
||||
assert labels_for(code) == ["qt-line"]
|
||||
|
||||
|
||||
def test_qt_member() -> None:
|
||||
code = "int x; //!< doc\n"
|
||||
assert labels_for(code) == ["qt-member"]
|
||||
|
||||
|
||||
def test_block_member() -> None:
|
||||
code = "int x; /**< doc */\n"
|
||||
assert labels_for(code) == ["block-member"]
|
||||
|
||||
|
||||
def test_qt_block_member() -> None:
|
||||
code = "int x; /*!< doc */\n"
|
||||
assert labels_for(code) == ["qt-block-member"]
|
||||
|
||||
|
||||
def test_doc_in_line_comment() -> None:
|
||||
code = "// @param x\n"
|
||||
assert labels_for(code) == ["doc-in-line-comment"]
|
||||
|
||||
|
||||
# --- block forms -------------------------------------------------------------
|
||||
|
||||
|
||||
def test_qt_comment() -> None:
|
||||
code = """
|
||||
/*!
|
||||
* brief
|
||||
*/
|
||||
"""
|
||||
assert labels_for(code) == ["qt-comment"]
|
||||
|
||||
|
||||
def test_qt_comment_single_line() -> None:
|
||||
# /*! ... */ on one line -> qt-comment (plus single-line-block)
|
||||
code = "/*! brief */\n"
|
||||
assert labels_for(code) == ["qt-comment", "single-line-block"]
|
||||
|
||||
|
||||
def test_single_line_block() -> None:
|
||||
code = "/** brief */\n"
|
||||
assert labels_for(code) == ["single-line-block"]
|
||||
|
||||
|
||||
def test_single_line_markers_allowed() -> None:
|
||||
for marker in ("@{", "@}", "@cond LABEL", "@endcond", "@file foo.h"):
|
||||
code = f"/** {marker} */\n"
|
||||
assert findings_for(code) == [], marker
|
||||
|
||||
|
||||
def test_text_on_opener() -> None:
|
||||
code = """
|
||||
/** text here
|
||||
* more
|
||||
*/
|
||||
"""
|
||||
assert labels_for(code) == ["text-on-opener"]
|
||||
|
||||
|
||||
def test_bare_continuation() -> None:
|
||||
code = """
|
||||
/**
|
||||
* a
|
||||
bare line
|
||||
*/
|
||||
"""
|
||||
assert labels_for(code) == ["bare-continuation"]
|
||||
|
||||
|
||||
def test_over_indented_first_line() -> None:
|
||||
code = """
|
||||
/**
|
||||
* over
|
||||
*/
|
||||
"""
|
||||
assert labels_for(code) == ["over-indented"]
|
||||
|
||||
|
||||
def test_over_indented_tag() -> None:
|
||||
# a flush first line consumes "first content", isolating the tag check
|
||||
code = """
|
||||
/**
|
||||
* brief
|
||||
* @param x
|
||||
*/
|
||||
"""
|
||||
assert labels_for(code) == ["over-indented-tag"]
|
||||
|
||||
|
||||
def test_combined_marker() -> None:
|
||||
code = """
|
||||
/**
|
||||
* @{
|
||||
*/
|
||||
"""
|
||||
assert labels_for(code) == ["combined-marker"]
|
||||
|
||||
|
||||
def test_prose_label() -> None:
|
||||
for word in ("Returns", "Throws", "Exceptions"):
|
||||
code = f"""
|
||||
/**
|
||||
* {word}:
|
||||
*/
|
||||
"""
|
||||
assert labels_for(code) == ["prose-label"], word
|
||||
|
||||
|
||||
def test_content_on_closer() -> None:
|
||||
code = """
|
||||
/**
|
||||
* a
|
||||
* b */
|
||||
"""
|
||||
assert labels_for(code) == ["content-on-closer"]
|
||||
|
||||
|
||||
def test_plain_block_doc() -> None:
|
||||
assert labels_for("/* @param x */\n") == ["plain-block-doc"]
|
||||
assert findings_for("/* just an ordinary note */\n") == []
|
||||
|
||||
|
||||
def test_tag_order() -> None:
|
||||
out_of_order = """
|
||||
/**
|
||||
* @param x
|
||||
* @tparam T
|
||||
*/
|
||||
"""
|
||||
assert labels_for(out_of_order) == ["tag-order"]
|
||||
|
||||
correct = """
|
||||
/**
|
||||
* @tparam T
|
||||
* @param x
|
||||
* @return r
|
||||
*/
|
||||
"""
|
||||
assert findings_for(correct) == []
|
||||
|
||||
single = """
|
||||
/**
|
||||
* @param x
|
||||
*/
|
||||
"""
|
||||
assert findings_for(single) == [] # single tag: never out of order
|
||||
|
||||
|
||||
# --- command spelling (must work on body/closer lines, not just the opener) --
|
||||
|
||||
|
||||
def test_backslash_command_on_body_line() -> None:
|
||||
code = r"""
|
||||
/**
|
||||
* \brief x
|
||||
*/
|
||||
"""
|
||||
assert labels_for(code) == ["backslash-command"]
|
||||
|
||||
|
||||
def test_backslash_command_suggests_canonical_spelling() -> None:
|
||||
# a backslash + non-canonical spelling is fixed in one pass, not two:
|
||||
# \sa -> @see (not @sa), \returns -> @return (not @returns)
|
||||
sa = r"""
|
||||
/**
|
||||
* \sa other
|
||||
*/
|
||||
"""
|
||||
assert messages_for(sa) == [r"use @see instead of \sa"]
|
||||
|
||||
returns = r"""
|
||||
/**
|
||||
* \returns x
|
||||
*/
|
||||
"""
|
||||
assert messages_for(returns) == [r"use @return instead of \returns"]
|
||||
|
||||
|
||||
def test_wrong_command_on_body_line() -> None:
|
||||
code = """
|
||||
/**
|
||||
* @returns x
|
||||
*/
|
||||
"""
|
||||
assert labels_for(code) == ["wrong-command"]
|
||||
|
||||
|
||||
def test_body_line_commands_regression() -> None:
|
||||
# regression: these live on body lines of a multi-line block
|
||||
code = r"""
|
||||
/**
|
||||
* @returns bad
|
||||
* @throw ex
|
||||
* @sa other
|
||||
* \param y
|
||||
*/
|
||||
"""
|
||||
assert labels_for(code) == [
|
||||
"wrong-command",
|
||||
"wrong-command",
|
||||
"wrong-command",
|
||||
"backslash-command",
|
||||
]
|
||||
|
||||
|
||||
def test_command_on_closer_line() -> None:
|
||||
code = """
|
||||
/**
|
||||
* a
|
||||
* @sa b */
|
||||
"""
|
||||
assert labels_for(code) == ["wrong-command", "content-on-closer"]
|
||||
|
||||
|
||||
def test_no_double_count_across_opener_body_closer() -> None:
|
||||
code = """
|
||||
/** @returns opener
|
||||
* @throw body
|
||||
* @sa closer */
|
||||
"""
|
||||
assert labels_for(code).count("wrong-command") == 3
|
||||
|
||||
|
||||
def test_code_with_word_allowed() -> None:
|
||||
# @code{.cpp} is valid Doxygen and must not be flagged
|
||||
code = """
|
||||
/**
|
||||
* @code{.cpp}
|
||||
* int x;
|
||||
* @endcode
|
||||
*/
|
||||
"""
|
||||
assert findings_for(code) == []
|
||||
|
||||
|
||||
# --- rendered message text ---------------------------------------------------
|
||||
|
||||
|
||||
def test_message_uses_category_description() -> None:
|
||||
# a static category renders its default description
|
||||
code = "/// doc\n"
|
||||
assert messages_for(code) == ["use a /** ... */ block instead of ///"]
|
||||
|
||||
|
||||
def test_message_detail_overrides() -> None:
|
||||
# dynamic categories render the offending text via Finding.detail
|
||||
backslash = r"""
|
||||
/**
|
||||
* \param y
|
||||
*/
|
||||
"""
|
||||
assert messages_for(backslash) == [r"use @param instead of \param"]
|
||||
|
||||
wrong = """
|
||||
/**
|
||||
* @returns x
|
||||
*/
|
||||
"""
|
||||
assert messages_for(wrong) == ["use @return instead of @returns"]
|
||||
|
||||
prose = """
|
||||
/**
|
||||
* Throws:
|
||||
*/
|
||||
"""
|
||||
assert messages_for(prose) == ['use @throws instead of prose "Throws:"']
|
||||
|
||||
|
||||
# --- robustness --------------------------------------------------------------
|
||||
|
||||
|
||||
def test_empty_file_no_crash() -> None:
|
||||
assert findings_for("") == []
|
||||
|
||||
|
||||
def test_mid_line_plain_block_skipped() -> None:
|
||||
# a /* opened mid-line (after code) and spanning lines is skipped, so its
|
||||
# comment-like contents are not analyzed
|
||||
code = """
|
||||
int x = 0; /* note: @returns is not a real tag here
|
||||
* @param also not real
|
||||
*/
|
||||
int y = 0;
|
||||
"""
|
||||
assert findings_for(code) == []
|
||||
|
||||
|
||||
def test_unclosed_block_scanned_to_eof() -> None:
|
||||
# an unterminated /** block is still scanned to EOF (no crash, body checked)
|
||||
code = """
|
||||
/**
|
||||
* @returns x
|
||||
"""
|
||||
assert labels_for(code) == ["wrong-command"]
|
||||
|
||||
|
||||
def test_banner_and_empty_comment_not_flagged() -> None:
|
||||
code = """
|
||||
/***
|
||||
* banner
|
||||
***/
|
||||
"""
|
||||
assert findings_for(code) == []
|
||||
assert findings_for("/**/\n") == []
|
||||
|
||||
|
||||
def main() -> int:
|
||||
tests = sorted(
|
||||
(name, fn)
|
||||
for name, fn in globals().items()
|
||||
if name.startswith("test_") and callable(fn)
|
||||
)
|
||||
failed = 0
|
||||
for name, fn in tests:
|
||||
try:
|
||||
fn()
|
||||
print(f"PASS {name}")
|
||||
except AssertionError as exc:
|
||||
failed += 1
|
||||
print(f"FAIL {name}: {exc!r}")
|
||||
print(f"\n{len(tests) - failed}/{len(tests)} passed")
|
||||
return 1 if failed else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -177,9 +177,7 @@ ${field['typeData']['setter_type']} ${field['paramName']}${',' if i < len(requir
|
||||
object_ = *sle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Ledger entry-specific field setters
|
||||
*/
|
||||
/** @brief Ledger entry-specific field setters */
|
||||
% for field in fields:
|
||||
|
||||
/**
|
||||
|
||||
@@ -185,9 +185,7 @@ public:
|
||||
object_ = *tx;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Transaction-specific field setters
|
||||
*/
|
||||
/** @brief Transaction-specific field setters */
|
||||
% for field in fields:
|
||||
|
||||
/**
|
||||
|
||||
15
docs/build/environment.md
vendored
15
docs/build/environment.md
vendored
@@ -33,10 +33,9 @@ with a single command and without installing anything system-wide:
|
||||
nix --experimental-features 'nix-command flakes' develop
|
||||
```
|
||||
|
||||
On **Linux**, Nix also provides the compiler (GCC); on **macOS**, it provides
|
||||
Clang. If you instead opt to use your system-wide Apple Clang (via
|
||||
`nix develop .#apple-clang`), you need to manage its version yourself (see
|
||||
below).
|
||||
On **Linux**, Nix also provides the compiler (GCC). On **macOS**, the shell uses
|
||||
your **system-wide Apple Clang** as the compiler, so you still need to manage
|
||||
its version (see below).
|
||||
|
||||
See [Using the Nix development shell](./nix.md) for installation and usage
|
||||
details, including how to select a different compiler.
|
||||
@@ -49,10 +48,10 @@ details, including how to select a different compiler.
|
||||
|
||||
### macOS: managing the Apple Clang version
|
||||
|
||||
If you use your system-wide Apple Clang on macOS (via `nix develop .#apple-clang`),
|
||||
the compiler version is whatever your installed Xcode (or Command Line Tools)
|
||||
provides. The following command should return a version greater than or equal to
|
||||
the [minimum required](#tested-compiler-versions):
|
||||
Because the Nix shell uses the system-wide Apple Clang on macOS, the compiler
|
||||
version is whatever your installed Xcode (or Command Line Tools) provides. The
|
||||
following command should return a version greater than or equal to the
|
||||
[minimum required](#tested-compiler-versions):
|
||||
|
||||
```bash
|
||||
clang --version
|
||||
|
||||
40
docs/build/nix.md
vendored
40
docs/build/nix.md
vendored
@@ -9,7 +9,7 @@ This guide explains how to use Nix to set up a reproducible development environm
|
||||
- **Reproducible environment**: Everyone gets the same versions of tools and compilers
|
||||
- **Matches CI**: The Linux CI runs in Docker images built from this exact Nix environment
|
||||
- **No system pollution**: Dependencies are isolated and don't affect your system packages
|
||||
- **Consistent compilers**: The GCC and Clang shells use the same versions as CI
|
||||
- **Multiple compiler versions**: Easily switch between different GCC and Clang versions
|
||||
- **Quick setup**: Get started with a single command
|
||||
- **Works on Linux and macOS**: Consistent experience across platforms
|
||||
|
||||
@@ -31,8 +31,8 @@ This will:
|
||||
|
||||
- Download and set up all required development tools (CMake, Ninja, Conan, etc.)
|
||||
- Configure the appropriate compiler for your platform:
|
||||
- **Linux**: GCC (provided by Nix)
|
||||
- **macOS**: Clang (provided by Nix)
|
||||
- **Linux**: GCC 15.2 (provided by Nix)
|
||||
- **macOS**: Apple Clang (your system compiler)
|
||||
|
||||
The first time you run this command, it will take a few minutes to download and build the environment. Subsequent runs will be much faster.
|
||||
|
||||
@@ -40,12 +40,12 @@ The first time you run this command, it will take a few minutes to download and
|
||||
|
||||
- **Linux**: `nix develop` gives you a shell with all the tooling necessary to
|
||||
develop xrpld and with GCC 15.2 (also provided by Nix). There are no caveats.
|
||||
- **macOS**: `nix develop` gives you a full environment too, with Clang (and
|
||||
every other tool, including Conan) provided by Nix. To use your system-wide
|
||||
Apple Clang instead, enter `nix develop .#apple-clang`. Conan has no binary in
|
||||
the Nix cache for macOS, so it is built from source the first time you enter
|
||||
the shell, which makes the initial setup slower (this is handled
|
||||
automatically; see [`nix/devshell.nix`](../../nix/devshell.nix)).
|
||||
- **macOS**: `nix develop` gives you a full environment too. The compiler is
|
||||
your system-wide Apple Clang, while every other tool — including Conan — is
|
||||
provided by Nix. Conan has no binary in the Nix cache for macOS, so it is
|
||||
built from source the first time you enter the shell, which makes the initial
|
||||
setup slower (this is handled automatically; see
|
||||
[`nix/devshell.nix`](../../nix/devshell.nix)).
|
||||
|
||||
> [!TIP]
|
||||
> To avoid typing `--experimental-features 'nix-command flakes'` every time, you can permanently enable flakes by creating `~/.config/nix/nix.conf`:
|
||||
@@ -62,9 +62,7 @@ The first time you run this command, it will take a few minutes to download and
|
||||
|
||||
### Choosing a different compiler
|
||||
|
||||
A compiler can be chosen by providing its name with the `.#` prefix, e.g. `nix develop .#clang`.
|
||||
The `.#gcc` and `.#clang` shells provide the same GCC and Clang versions used in CI
|
||||
(pinned in [`nix/packages.nix`](../../nix/packages.nix)).
|
||||
A compiler can be chosen by providing its name with the `.#` prefix, e.g. `nix develop .#gcc15`.
|
||||
Use `nix flake show` to see all the available development shells.
|
||||
|
||||
Use `nix develop .#no-compiler` to use the compiler from your system.
|
||||
@@ -72,11 +70,11 @@ Use `nix develop .#no-compiler` to use the compiler from your system.
|
||||
### Example Usage
|
||||
|
||||
```bash
|
||||
# Use GCC (same version as CI)
|
||||
nix develop .#gcc
|
||||
# Use GCC 14
|
||||
nix develop .#gcc14
|
||||
|
||||
# Use Clang (same version as CI)
|
||||
nix develop .#clang
|
||||
# Use Clang 19
|
||||
nix develop .#clang19
|
||||
|
||||
# Use default for your platform
|
||||
nix develop
|
||||
@@ -114,15 +112,7 @@ Once inside the Nix development shell, follow the standard [build instructions](
|
||||
|
||||
[direnv](https://direnv.net/) or [nix-direnv](https://github.com/nix-community/nix-direnv) can automatically activate the Nix development shell when you enter the repository directory.
|
||||
|
||||
This is also the most robust way to use the environment from **any shell** (bash, zsh, fish, …): direnv stays in your current shell and loads the environment _after_ your shell's startup files have run, so the Nix-provided tools take precedence over anything your shell configuration adds to `$PATH`.
|
||||
|
||||
The repository already ships an `.envrc` at its root that activates the Nix flake development shell, so you don't need to create one. To use it:
|
||||
|
||||
1. [Install direnv](https://direnv.net/docs/installation.html) and [hook it into your shell](https://direnv.net/docs/hook.html) (bash, zsh, fish, …). Installing [nix-direnv](https://github.com/nix-community/nix-direnv) as well is recommended: it caches the shell so that activation is near-instant after the first run.
|
||||
2. Run `direnv allow` once in the repository root. direnv will then load (and reload) the Nix development shell automatically whenever you enter the directory.
|
||||
|
||||
> [!NOTE]
|
||||
> direnv only caches the `.direnv` directory (already listed in `.gitignore`); no other repository files are affected.
|
||||
This is also the most robust way to use the environment from **any shell** (bash, zsh, fish, …): direnv stays in your current shell and loads the environment _after_ your shell's startup files have run, so the Nix-provided tools take precedence over anything your shell configuration adds to `$PATH`. To use it, install direnv for your shell, then add an `.envrc` containing `use flake` at the repository root and run `direnv allow`.
|
||||
|
||||
## Conan and Prebuilt Packages
|
||||
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* Extract a tar archive compressed with lz4
|
||||
*
|
||||
* @param src the path of the archive to be extracted
|
||||
* @param dst the directory to extract to
|
||||
*
|
||||
* @throws runtime_error
|
||||
*/
|
||||
/** Extract a tar archive compressed with lz4
|
||||
|
||||
@param src the path of the archive to be extracted
|
||||
@param dst the directory to extract to
|
||||
|
||||
@throws runtime_error
|
||||
*/
|
||||
void
|
||||
extractTarLz4(std::filesystem::path const& src, std::filesystem::path const& dst);
|
||||
extractTarLz4(boost::filesystem::path const& src, boost::filesystem::path const& dst);
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* Storage for linear binary data.
|
||||
* Blocks of binary data appear often in various idioms and structures.
|
||||
*/
|
||||
/** Storage for linear binary data.
|
||||
Blocks of binary data appear often in various idioms and structures.
|
||||
*/
|
||||
using Blob = std::vector<unsigned char>;
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -10,10 +10,9 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* Like std::vector<char> but better.
|
||||
* Meets the requirements of BufferFactory.
|
||||
*/
|
||||
/** Like std::vector<char> but better.
|
||||
Meets the requirements of BufferFactory.
|
||||
*/
|
||||
class Buffer
|
||||
{
|
||||
private:
|
||||
@@ -25,37 +24,30 @@ public:
|
||||
|
||||
Buffer() = default;
|
||||
|
||||
/**
|
||||
* Create an uninitialized buffer with the given size.
|
||||
*/
|
||||
/** Create an uninitialized buffer with the given size. */
|
||||
explicit Buffer(std::size_t size)
|
||||
: p_((size != 0u) ? new std::uint8_t[size] : nullptr), size_(size)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a buffer as a copy of existing memory.
|
||||
*
|
||||
* @param data a pointer to the existing memory. If
|
||||
* size is non-zero, it must not be null.
|
||||
* @param size size of the existing memory block.
|
||||
*/
|
||||
/** Create a buffer as a copy of existing memory.
|
||||
|
||||
@param data a pointer to the existing memory. If
|
||||
size is non-zero, it must not be null.
|
||||
@param size size of the existing memory block.
|
||||
*/
|
||||
Buffer(void const* data, std::size_t size) : Buffer(size)
|
||||
{
|
||||
if (size != 0u)
|
||||
std::memcpy(p_.get(), data, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy-construct
|
||||
*/
|
||||
/** Copy-construct */
|
||||
Buffer(Buffer const& other) : Buffer(other.p_.get(), other.size_)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy assign
|
||||
*/
|
||||
/** Copy assign */
|
||||
Buffer&
|
||||
operator=(Buffer const& other)
|
||||
{
|
||||
@@ -67,19 +59,17 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move-construct.
|
||||
* The other buffer is reset.
|
||||
*/
|
||||
/** Move-construct.
|
||||
The other buffer is reset.
|
||||
*/
|
||||
Buffer(Buffer&& other) noexcept : p_(std::move(other.p_)), size_(other.size_)
|
||||
{
|
||||
other.size_ = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move-assign.
|
||||
* The other buffer is reset.
|
||||
*/
|
||||
/** Move-assign.
|
||||
The other buffer is reset.
|
||||
*/
|
||||
Buffer&
|
||||
operator=(Buffer&& other) noexcept
|
||||
{
|
||||
@@ -92,16 +82,12 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct from a slice
|
||||
*/
|
||||
/** Construct from a slice */
|
||||
explicit Buffer(Slice s) : Buffer(s.data(), s.size())
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign from slice
|
||||
*/
|
||||
/** Assign from slice */
|
||||
Buffer&
|
||||
operator=(Slice s)
|
||||
{
|
||||
@@ -115,9 +101,7 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of bytes in the buffer.
|
||||
*/
|
||||
/** Returns the number of bytes in the buffer. */
|
||||
[[nodiscard]] std::size_t
|
||||
size() const noexcept
|
||||
{
|
||||
@@ -137,11 +121,10 @@ public:
|
||||
return Slice{p_.get(), size_};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a pointer to beginning of the storage.
|
||||
* @note The return type is guaranteed to be a pointer
|
||||
* to a single byte, to facilitate pointer arithmetic.
|
||||
*/
|
||||
/** Return a pointer to beginning of the storage.
|
||||
@note The return type is guaranteed to be a pointer
|
||||
to a single byte, to facilitate pointer arithmetic.
|
||||
*/
|
||||
/** @{ */
|
||||
[[nodiscard]] std::uint8_t const*
|
||||
data() const noexcept
|
||||
@@ -156,10 +139,9 @@ public:
|
||||
}
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Reset the buffer.
|
||||
* All memory is deallocated. The resulting size is 0.
|
||||
*/
|
||||
/** Reset the buffer.
|
||||
All memory is deallocated. The resulting size is 0.
|
||||
*/
|
||||
void
|
||||
clear() noexcept
|
||||
{
|
||||
@@ -167,10 +149,9 @@ public:
|
||||
size_ = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reallocate the storage.
|
||||
* Existing data, if any, is discarded.
|
||||
*/
|
||||
/** Reallocate the storage.
|
||||
Existing data, if any, is discarded.
|
||||
*/
|
||||
std::uint8_t*
|
||||
alloc(std::size_t n)
|
||||
{
|
||||
|
||||
@@ -12,8 +12,7 @@
|
||||
|
||||
namespace xrpl::compression_algorithms {
|
||||
|
||||
/**
|
||||
* LZ4 block compression.
|
||||
/** LZ4 block compression.
|
||||
* @tparam BufferFactory Callable object or lambda.
|
||||
* Takes the requested buffer size and returns allocated buffer pointer.
|
||||
* @param in Data to compress
|
||||
@@ -81,8 +80,7 @@ lz4Decompress(
|
||||
return decompressedSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* LZ4 block decompression.
|
||||
/** LZ4 block decompression.
|
||||
* @tparam InputStream ZeroCopyInputStream
|
||||
* @param in Input source stream
|
||||
* @param inSize Size of compressed data
|
||||
|
||||
@@ -9,9 +9,7 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* Manages all counted object types.
|
||||
*/
|
||||
/** Manages all counted object types. */
|
||||
class CountedObjects
|
||||
{
|
||||
public:
|
||||
@@ -25,11 +23,10 @@ public:
|
||||
getCounts(int minimumThreshold) const;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Implementation for @ref CountedObject.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
/** Implementation for @ref CountedObject.
|
||||
|
||||
@internal
|
||||
*/
|
||||
class Counter
|
||||
{
|
||||
public:
|
||||
@@ -97,14 +94,13 @@ private:
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Tracks the number of instances of an object.
|
||||
*
|
||||
* Derived classes have their instances counted automatically. This is used
|
||||
* for reporting purposes.
|
||||
*
|
||||
* @ingroup basics
|
||||
*/
|
||||
/** Tracks the number of instances of an object.
|
||||
|
||||
Derived classes have their instances counted automatically. This is used
|
||||
for reporting purposes.
|
||||
|
||||
@ingroup basics
|
||||
*/
|
||||
template <class Object>
|
||||
class CountedObject
|
||||
{
|
||||
|
||||
@@ -6,10 +6,9 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* Sampling function using exponential decay to provide a continuous value.
|
||||
* @tparam The number of seconds in the decay window.
|
||||
*/
|
||||
/** Sampling function using exponential decay to provide a continuous value.
|
||||
@tparam The number of seconds in the decay window.
|
||||
*/
|
||||
template <int Window, typename Clock>
|
||||
class DecayingSample
|
||||
{
|
||||
@@ -20,16 +19,15 @@ public:
|
||||
DecayingSample() = delete;
|
||||
|
||||
/**
|
||||
* @param now Start time of DecayingSample.
|
||||
*/
|
||||
@param now Start time of DecayingSample.
|
||||
*/
|
||||
explicit DecayingSample(time_point now) : value_(value_type()), when_(now)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new sample.
|
||||
* The value is first aged according to the specified time.
|
||||
*/
|
||||
/** Add a new sample.
|
||||
The value is first aged according to the specified time.
|
||||
*/
|
||||
value_type
|
||||
add(value_type value, time_point now)
|
||||
{
|
||||
@@ -38,10 +36,9 @@ public:
|
||||
return value_ / Window;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current value in normalized units.
|
||||
* The samples are first aged according to the specified time.
|
||||
*/
|
||||
/** Retrieve the current value in normalized units.
|
||||
The samples are first aged according to the specified time.
|
||||
*/
|
||||
value_type
|
||||
value(time_point now)
|
||||
{
|
||||
@@ -90,10 +87,9 @@ private:
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Sampling function using exponential decay to provide a continuous value.
|
||||
* @tparam HalfLife The half life of a sample, in seconds.
|
||||
*/
|
||||
/** Sampling function using exponential decay to provide a continuous value.
|
||||
@tparam HalfLife The half life of a sample, in seconds.
|
||||
*/
|
||||
template <int HalfLife, class Clock>
|
||||
class DecayWindow
|
||||
{
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
std::string
|
||||
getFileContents(
|
||||
std::error_code& ec,
|
||||
std::filesystem::path const& sourcePath,
|
||||
boost::system::error_code& ec,
|
||||
boost::filesystem::path const& sourcePath,
|
||||
std::optional<std::size_t> maxSize = std::nullopt);
|
||||
|
||||
void
|
||||
writeFileContents(
|
||||
std::error_code& ec,
|
||||
std::filesystem::path const& destPath,
|
||||
boost::system::error_code& ec,
|
||||
boost::filesystem::path const& destPath,
|
||||
std::string const& contents);
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -10,37 +10,33 @@ namespace xrpl {
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Tag to create an intrusive pointer from another intrusive pointer by using a
|
||||
* static cast. This is useful to create an intrusive pointer to a derived
|
||||
* class from an intrusive pointer to a base class.
|
||||
*/
|
||||
/** Tag to create an intrusive pointer from another intrusive pointer by using a
|
||||
static cast. This is useful to create an intrusive pointer to a derived
|
||||
class from an intrusive pointer to a base class.
|
||||
*/
|
||||
struct StaticCastTagSharedIntrusive
|
||||
{
|
||||
};
|
||||
|
||||
/**
|
||||
* Tag to create an intrusive pointer from another intrusive pointer by using a
|
||||
* dynamic cast. This is useful to create an intrusive pointer to a derived
|
||||
* class from an intrusive pointer to a base class. If the cast fails an empty
|
||||
* (null) intrusive pointer is created.
|
||||
*/
|
||||
/** Tag to create an intrusive pointer from another intrusive pointer by using a
|
||||
dynamic cast. This is useful to create an intrusive pointer to a derived
|
||||
class from an intrusive pointer to a base class. If the cast fails an empty
|
||||
(null) intrusive pointer is created.
|
||||
*/
|
||||
struct DynamicCastTagSharedIntrusive
|
||||
{
|
||||
};
|
||||
|
||||
/**
|
||||
* When creating or adopting a raw pointer, controls whether the strong count
|
||||
* is incremented or not. Use this tag to increment the strong count.
|
||||
*/
|
||||
/** When creating or adopting a raw pointer, controls whether the strong count
|
||||
is incremented or not. Use this tag to increment the strong count.
|
||||
*/
|
||||
struct SharedIntrusiveAdoptIncrementStrongTag
|
||||
{
|
||||
};
|
||||
|
||||
/**
|
||||
* When creating or adopting a raw pointer, controls whether the strong count
|
||||
* is incremented or not. Use this tag to leave the strong count unchanged.
|
||||
*/
|
||||
/** When creating or adopting a raw pointer, controls whether the strong count
|
||||
is incremented or not. Use this tag to leave the strong count unchanged.
|
||||
*/
|
||||
struct SharedIntrusiveAdoptNoIncrementTag
|
||||
{
|
||||
};
|
||||
@@ -54,21 +50,20 @@ concept CAdoptTag = std::is_same_v<T, SharedIntrusiveAdoptIncrementStrongTag> ||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* A shared intrusive pointer class that supports weak pointers.
|
||||
*
|
||||
* This is meant to be used for SHAMapInnerNodes, but may be useful for other
|
||||
* cases. Since the reference counts are stored on the pointee, the pointee is
|
||||
* not destroyed until both the strong _and_ weak pointer counts go to zero.
|
||||
* When the strong pointer count goes to zero, the "partialDestructor" is
|
||||
* called. This can be used to destroy as much of the object as possible while
|
||||
* still retaining the reference counts. For example, for SHAMapInnerNodes the
|
||||
* children may be reset in that function. Note that std::shared_pointer WILL
|
||||
* run the destructor when the strong count reaches zero, but may not free the
|
||||
* memory used by the object until the weak count reaches zero. In xrpld, we
|
||||
* typically allocate shared pointers with the `make_shared` function. When
|
||||
* that is used, the memory is not reclaimed until the weak count reaches zero.
|
||||
*/
|
||||
/** A shared intrusive pointer class that supports weak pointers.
|
||||
|
||||
This is meant to be used for SHAMapInnerNodes, but may be useful for other
|
||||
cases. Since the reference counts are stored on the pointee, the pointee is
|
||||
not destroyed until both the strong _and_ weak pointer counts go to zero.
|
||||
When the strong pointer count goes to zero, the "partialDestructor" is
|
||||
called. This can be used to destroy as much of the object as possible while
|
||||
still retaining the reference counts. For example, for SHAMapInnerNodes the
|
||||
children may be reset in that function. Note that std::shared_pointer WILL
|
||||
run the destructor when the strong count reaches zero, but may not free the
|
||||
memory used by the object until the weak count reaches zero. In xrpld, we
|
||||
typically allocate shared pointers with the `make_shared` function. When
|
||||
that is used, the memory is not reclaimed until the weak count reaches zero.
|
||||
*/
|
||||
template <class T>
|
||||
class SharedIntrusive
|
||||
{
|
||||
@@ -116,9 +111,8 @@ public:
|
||||
operator=(
|
||||
SharedIntrusive<TT>&& rhs); // NOLINT(cppcoreguidelines-rvalue-reference-param-not-moved)
|
||||
|
||||
/**
|
||||
* Adopt the raw pointer. The strong reference may or may not be
|
||||
* incremented, depending on the TAdoptTag
|
||||
/** Adopt the raw pointer. The strong reference may or may not be
|
||||
incremented, depending on the TAdoptTag
|
||||
*/
|
||||
template <CAdoptTag TAdoptTag = SharedIntrusiveAdoptIncrementStrongTag>
|
||||
void
|
||||
@@ -126,31 +120,27 @@ public:
|
||||
|
||||
~SharedIntrusive();
|
||||
|
||||
/**
|
||||
* Create a new SharedIntrusive by statically casting the pointer
|
||||
* controlled by the rhs param.
|
||||
*/
|
||||
/** Create a new SharedIntrusive by statically casting the pointer
|
||||
controlled by the rhs param.
|
||||
*/
|
||||
template <class TT>
|
||||
SharedIntrusive(StaticCastTagSharedIntrusive, SharedIntrusive<TT> const& rhs);
|
||||
|
||||
/**
|
||||
* Create a new SharedIntrusive by statically casting the pointer
|
||||
* controlled by the rhs param.
|
||||
*/
|
||||
/** Create a new SharedIntrusive by statically casting the pointer
|
||||
controlled by the rhs param.
|
||||
*/
|
||||
template <class TT>
|
||||
SharedIntrusive(StaticCastTagSharedIntrusive, SharedIntrusive<TT>&& rhs);
|
||||
|
||||
/**
|
||||
* Create a new SharedIntrusive by dynamically casting the pointer
|
||||
* controlled by the rhs param.
|
||||
*/
|
||||
/** Create a new SharedIntrusive by dynamically casting the pointer
|
||||
controlled by the rhs param.
|
||||
*/
|
||||
template <class TT>
|
||||
SharedIntrusive(DynamicCastTagSharedIntrusive, SharedIntrusive<TT> const& rhs);
|
||||
|
||||
/**
|
||||
* Create a new SharedIntrusive by dynamically casting the pointer
|
||||
* controlled by the rhs param.
|
||||
*/
|
||||
/** Create a new SharedIntrusive by dynamically casting the pointer
|
||||
controlled by the rhs param.
|
||||
*/
|
||||
template <class TT>
|
||||
SharedIntrusive(DynamicCastTagSharedIntrusive, SharedIntrusive<TT>&& rhs);
|
||||
|
||||
@@ -163,22 +153,17 @@ public:
|
||||
explicit
|
||||
operator bool() const noexcept;
|
||||
|
||||
/**
|
||||
* Set the pointer to null, decrement the strong count, and run the
|
||||
* appropriate release action.
|
||||
*/
|
||||
/** Set the pointer to null, decrement the strong count, and run the
|
||||
appropriate release action.
|
||||
*/
|
||||
void
|
||||
reset();
|
||||
|
||||
/**
|
||||
* Get the raw pointer
|
||||
*/
|
||||
/** Get the raw pointer */
|
||||
[[nodiscard]] T*
|
||||
get() const;
|
||||
|
||||
/**
|
||||
* Return the strong count
|
||||
*/
|
||||
/** Return the strong count */
|
||||
[[nodiscard]] std::size_t
|
||||
useCount() const;
|
||||
|
||||
@@ -196,51 +181,43 @@ public:
|
||||
friend class WeakIntrusive;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Return the raw pointer held by this object.
|
||||
*/
|
||||
/** Return the raw pointer held by this object. */
|
||||
[[nodiscard]] T*
|
||||
unsafeGetRawPtr() const;
|
||||
|
||||
/**
|
||||
* Exchange the current raw pointer held by this object with the given
|
||||
* pointer. Decrement the strong count of the raw pointer previously held
|
||||
* by this object and run the appropriate release action.
|
||||
/** Exchange the current raw pointer held by this object with the given
|
||||
pointer. Decrement the strong count of the raw pointer previously held
|
||||
by this object and run the appropriate release action.
|
||||
*/
|
||||
void
|
||||
unsafeReleaseAndStore(T* next);
|
||||
|
||||
/**
|
||||
* Set the raw pointer directly. This is wrapped in a function so the class
|
||||
* can support both atomic and non-atomic pointers in a future patch.
|
||||
/** Set the raw pointer directly. This is wrapped in a function so the class
|
||||
can support both atomic and non-atomic pointers in a future patch.
|
||||
*/
|
||||
void
|
||||
unsafeSetRawPtr(T* p);
|
||||
|
||||
/**
|
||||
* Exchange the raw pointer directly.
|
||||
* This sets the raw pointer to the given value and returns the previous
|
||||
* value. This is wrapped in a function so the class can support both
|
||||
* atomic and non-atomic pointers in a future patch.
|
||||
/** Exchange the raw pointer directly.
|
||||
This sets the raw pointer to the given value and returns the previous
|
||||
value. This is wrapped in a function so the class can support both
|
||||
atomic and non-atomic pointers in a future patch.
|
||||
*/
|
||||
T*
|
||||
unsafeExchange(T* p);
|
||||
|
||||
/**
|
||||
* pointer to the type with an intrusive count
|
||||
*/
|
||||
/** pointer to the type with an intrusive count */
|
||||
T* ptr_{nullptr};
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* A weak intrusive pointer class for the SharedIntrusive pointer class.
|
||||
*
|
||||
* Note that this weak pointer class asks differently from normal weak pointer
|
||||
* classes. When the strong pointer count goes to zero, the "partialDestructor"
|
||||
* is called. See the comment on SharedIntrusive for a fuller explanation.
|
||||
*/
|
||||
/** A weak intrusive pointer class for the SharedIntrusive pointer class.
|
||||
|
||||
Note that this weak pointer class asks differently from normal weak pointer
|
||||
classes. When the strong pointer count goes to zero, the "partialDestructor"
|
||||
is called. See the comment on SharedIntrusive for a fuller explanation.
|
||||
*/
|
||||
template <class T>
|
||||
class WeakIntrusive
|
||||
{
|
||||
@@ -270,62 +247,54 @@ public:
|
||||
WeakIntrusive&
|
||||
operator=(SharedIntrusive<TT> const& rhs);
|
||||
|
||||
/**
|
||||
* Adopt the raw pointer and increment the weak count.
|
||||
*/
|
||||
/** Adopt the raw pointer and increment the weak count. */
|
||||
void
|
||||
adopt(T* ptr);
|
||||
|
||||
~WeakIntrusive();
|
||||
|
||||
/**
|
||||
* Get a strong pointer from the weak pointer, if possible. This will
|
||||
* only return a seated pointer if the strong count on the raw pointer
|
||||
* is non-zero before locking.
|
||||
/** Get a strong pointer from the weak pointer, if possible. This will
|
||||
only return a seated pointer if the strong count on the raw pointer
|
||||
is non-zero before locking.
|
||||
*/
|
||||
SharedIntrusive<T>
|
||||
lock() const;
|
||||
|
||||
/**
|
||||
* Return true if the strong count is zero.
|
||||
*/
|
||||
/** Return true if the strong count is zero. */
|
||||
[[nodiscard]] bool
|
||||
expired() const;
|
||||
|
||||
/**
|
||||
* Set the pointer to null and decrement the weak count.
|
||||
*
|
||||
* Note: This may run the destructor if the strong count is zero.
|
||||
*/
|
||||
/** Set the pointer to null and decrement the weak count.
|
||||
|
||||
Note: This may run the destructor if the strong count is zero.
|
||||
*/
|
||||
void
|
||||
reset();
|
||||
|
||||
private:
|
||||
T* ptr_ = nullptr;
|
||||
|
||||
/**
|
||||
* Decrement the weak count. This does _not_ set the raw pointer to
|
||||
* null.
|
||||
*
|
||||
* Note: This may run the destructor if the strong count is zero.
|
||||
*/
|
||||
/** Decrement the weak count. This does _not_ set the raw pointer to
|
||||
null.
|
||||
|
||||
Note: This may run the destructor if the strong count is zero.
|
||||
*/
|
||||
void
|
||||
unsafeReleaseNoStore();
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* A combination of a strong and a weak intrusive pointer stored in the
|
||||
* space of a single pointer.
|
||||
*
|
||||
* This class is similar to a `std::variant<SharedIntrusive,WeakIntrusive>`
|
||||
* with some optimizations. In particular, it uses a low-order bit to
|
||||
* determine if the raw pointer represents a strong pointer or a weak
|
||||
* pointer. It can also be quickly switched between its strong pointer and
|
||||
* weak pointer representations. This class is useful for storing intrusive
|
||||
* pointers in tagged caches.
|
||||
*/
|
||||
/** A combination of a strong and a weak intrusive pointer stored in the
|
||||
space of a single pointer.
|
||||
|
||||
This class is similar to a `std::variant<SharedIntrusive,WeakIntrusive>`
|
||||
with some optimizations. In particular, it uses a low-order bit to
|
||||
determine if the raw pointer represents a strong pointer or a weak
|
||||
pointer. It can also be quickly switched between its strong pointer and
|
||||
weak pointer representations. This class is useful for storing intrusive
|
||||
pointers in tagged caches.
|
||||
*/
|
||||
|
||||
template <class T>
|
||||
class SharedWeakUnion
|
||||
@@ -367,83 +336,69 @@ public:
|
||||
|
||||
~SharedWeakUnion();
|
||||
|
||||
/**
|
||||
* Return a strong pointer if this is already a strong pointer (i.e.
|
||||
* don't lock the weak pointer. Use the `lock` method if that's what's
|
||||
* needed)
|
||||
/** Return a strong pointer if this is already a strong pointer (i.e.
|
||||
don't lock the weak pointer. Use the `lock` method if that's what's
|
||||
needed)
|
||||
*/
|
||||
[[nodiscard]] SharedIntrusive<T>
|
||||
getStrong() const;
|
||||
|
||||
/**
|
||||
* Return true if this is a strong pointer and the strong pointer is
|
||||
* seated.
|
||||
/** Return true if this is a strong pointer and the strong pointer is
|
||||
seated.
|
||||
*/
|
||||
explicit
|
||||
operator bool() const noexcept;
|
||||
|
||||
/**
|
||||
* Set the pointer to null, decrement the appropriate ref count, and
|
||||
* run the appropriate release action.
|
||||
/** Set the pointer to null, decrement the appropriate ref count, and
|
||||
run the appropriate release action.
|
||||
*/
|
||||
void
|
||||
reset();
|
||||
|
||||
/**
|
||||
* If this is a strong pointer, return the raw pointer. Otherwise
|
||||
* return null.
|
||||
/** If this is a strong pointer, return the raw pointer. Otherwise
|
||||
return null.
|
||||
*/
|
||||
[[nodiscard]] T*
|
||||
get() const;
|
||||
|
||||
/**
|
||||
* If this is a strong pointer, return the strong count. Otherwise
|
||||
/** If this is a strong pointer, return the strong count. Otherwise
|
||||
* return 0
|
||||
*/
|
||||
[[nodiscard]] std::size_t
|
||||
useCount() const;
|
||||
|
||||
/**
|
||||
* Return true if there is a non-zero strong count.
|
||||
*/
|
||||
/** Return true if there is a non-zero strong count. */
|
||||
[[nodiscard]] bool
|
||||
expired() const;
|
||||
|
||||
/**
|
||||
* If this is a strong pointer, return the strong pointer. Otherwise
|
||||
* attempt to lock the weak pointer.
|
||||
/** If this is a strong pointer, return the strong pointer. Otherwise
|
||||
attempt to lock the weak pointer.
|
||||
*/
|
||||
[[nodiscard]] SharedIntrusive<T>
|
||||
lock() const;
|
||||
|
||||
/**
|
||||
* Return true is this represents a strong pointer.
|
||||
*/
|
||||
/** Return true is this represents a strong pointer. */
|
||||
[[nodiscard]] bool
|
||||
isStrong() const;
|
||||
|
||||
/**
|
||||
* Return true is this represents a weak pointer.
|
||||
*/
|
||||
/** Return true is this represents a weak pointer. */
|
||||
[[nodiscard]] bool
|
||||
isWeak() const;
|
||||
|
||||
/**
|
||||
* If this is a weak pointer, attempt to convert it to a strong
|
||||
* pointer.
|
||||
*
|
||||
* @return true if successfully converted to a strong pointer (or was
|
||||
* already a strong pointer). Otherwise false.
|
||||
*/
|
||||
/** If this is a weak pointer, attempt to convert it to a strong
|
||||
pointer.
|
||||
|
||||
@return true if successfully converted to a strong pointer (or was
|
||||
already a strong pointer). Otherwise false.
|
||||
*/
|
||||
bool
|
||||
convertToStrong();
|
||||
|
||||
/**
|
||||
* If this is a strong pointer, attempt to convert it to a weak
|
||||
* pointer.
|
||||
*
|
||||
* @return false if the pointer is null. Otherwise return true.
|
||||
*/
|
||||
/** If this is a strong pointer, attempt to convert it to a weak
|
||||
pointer.
|
||||
|
||||
@return false if the pointer is null. Otherwise return true.
|
||||
*/
|
||||
bool
|
||||
convertToWeak();
|
||||
|
||||
@@ -456,27 +411,23 @@ private:
|
||||
static constexpr std::uintptr_t kPtrMask = ~kTagMask;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Return the raw pointer held by this object.
|
||||
/** Return the raw pointer held by this object.
|
||||
*/
|
||||
[[nodiscard]] T*
|
||||
unsafeGetRawPtr() const;
|
||||
|
||||
enum class RefStrength { Strong, Weak };
|
||||
/**
|
||||
* Set the raw pointer and tag bit directly.
|
||||
/** Set the raw pointer and tag bit directly.
|
||||
*/
|
||||
void
|
||||
unsafeSetRawPtr(T* p, RefStrength rs);
|
||||
|
||||
/**
|
||||
* Set the raw pointer and tag bit to all zeros (strong null pointer).
|
||||
/** Set the raw pointer and tag bit to all zeros (strong null pointer).
|
||||
*/
|
||||
void unsafeSetRawPtr(std::nullptr_t);
|
||||
|
||||
/**
|
||||
* Decrement the appropriate ref count, and run the appropriate release
|
||||
* action. Note: this does _not_ set the raw pointer to null.
|
||||
/** Decrement the appropriate ref count, and run the appropriate release
|
||||
action. Note: this does _not_ set the raw pointer to null.
|
||||
*/
|
||||
void
|
||||
unsafeReleaseNoStore();
|
||||
@@ -484,13 +435,12 @@ private:
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Create a shared intrusive pointer.
|
||||
*
|
||||
* Note: unlike std::shared_ptr, where there is an advantage of allocating
|
||||
* the pointer and control block together, there is no benefit for intrusive
|
||||
* pointers.
|
||||
*/
|
||||
/** Create a shared intrusive pointer.
|
||||
|
||||
Note: unlike std::shared_ptr, where there is an advantage of allocating
|
||||
the pointer and control block together, there is no benefit for intrusive
|
||||
pointers.
|
||||
*/
|
||||
template <class TT, class... Args>
|
||||
SharedIntrusive<TT>
|
||||
makeSharedIntrusive(Args&&... args)
|
||||
|
||||
@@ -8,38 +8,35 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* Action to perform when releasing a strong pointer.
|
||||
*
|
||||
* noop: Do nothing. For example, a `noop` action will occur when a count is
|
||||
* decremented to a non-zero value.
|
||||
*
|
||||
* partialDestroy: Run the `partialDestructor`. This action will happen when a
|
||||
* strong count is decremented to zero and the weak count is non-zero.
|
||||
*
|
||||
* destroy: Run the destructor. This action will occur when either the strong
|
||||
* count or weak count is decremented and the other count is also zero.
|
||||
/** Action to perform when releasing a strong pointer.
|
||||
|
||||
noop: Do nothing. For example, a `noop` action will occur when a count is
|
||||
decremented to a non-zero value.
|
||||
|
||||
partialDestroy: Run the `partialDestructor`. This action will happen when a
|
||||
strong count is decremented to zero and the weak count is non-zero.
|
||||
|
||||
destroy: Run the destructor. This action will occur when either the strong
|
||||
count or weak count is decremented and the other count is also zero.
|
||||
*/
|
||||
enum class ReleaseStrongRefAction { NoOp, PartialDestroy, Destroy };
|
||||
|
||||
/**
|
||||
* Action to perform when releasing a weak pointer.
|
||||
*
|
||||
* noop: Do nothing. For example, a `noop` action will occur when a count is
|
||||
* decremented to a non-zero value.
|
||||
*
|
||||
* destroy: Run the destructor. This action will occur when either the strong
|
||||
* count or weak count is decremented and the other count is also zero.
|
||||
/** Action to perform when releasing a weak pointer.
|
||||
|
||||
noop: Do nothing. For example, a `noop` action will occur when a count is
|
||||
decremented to a non-zero value.
|
||||
|
||||
destroy: Run the destructor. This action will occur when either the strong
|
||||
count or weak count is decremented and the other count is also zero.
|
||||
*/
|
||||
enum class ReleaseWeakRefAction { NoOp, Destroy };
|
||||
|
||||
/**
|
||||
* Implement the strong count, weak count, and bit flags for an intrusive
|
||||
* pointer.
|
||||
*
|
||||
* A class can satisfy the requirements of an xrpl::IntrusivePointer by
|
||||
* inheriting from this class.
|
||||
*/
|
||||
/** Implement the strong count, weak count, and bit flags for an intrusive
|
||||
pointer.
|
||||
|
||||
A class can satisfy the requirements of an xrpl::IntrusivePointer by
|
||||
inheriting from this class.
|
||||
*/
|
||||
struct IntrusiveRefCounts
|
||||
{
|
||||
virtual ~IntrusiveRefCounts() noexcept;
|
||||
@@ -108,123 +105,109 @@ private:
|
||||
static constexpr size_t kFieldTypeBits = sizeof(FieldType) * 8;
|
||||
static constexpr FieldType kOne = 1;
|
||||
|
||||
/**
|
||||
* `refCounts` consists of four fields that are treated atomically:
|
||||
*
|
||||
* 1. Strong count. This is a count of the number of shared pointers that
|
||||
* hold a reference to this object. When the strong counts goes to zero,
|
||||
* if the weak count is zero, the destructor is run. If the weak count is
|
||||
* non-zero when the strong count goes to zero then the partialDestructor
|
||||
* is run.
|
||||
*
|
||||
* 2. Weak count. This is a count of the number of weak pointer that hold
|
||||
* a reference to this object. When the weak count goes to zero and the
|
||||
* strong count is also zero, then the destructor is run.
|
||||
*
|
||||
* 3. Partial destroy started bit. This bit is set if the
|
||||
* `partialDestructor` function has been started (or is about to be
|
||||
* started). This is used to prevent the destructor from running
|
||||
* concurrently with the partial destructor. This can easily happen when
|
||||
* the last strong pointer release its reference in one thread and starts
|
||||
* the partialDestructor, while in another thread the last weak pointer
|
||||
* goes out of scope and starts the destructor while the partialDestructor
|
||||
* is still running. Both a start and finished bit is needed to handle a
|
||||
* corner-case where the last strong pointer goes out of scope, then then
|
||||
* last `weakPointer` goes out of scope, but this happens before the
|
||||
* `partialDestructor` bit is set. It would be possible to use a single
|
||||
* bit if it could also be set atomically when the strong count goes to
|
||||
* zero and the weak count is non-zero, but that would add complexity (and
|
||||
* likely slow down common cases as well).
|
||||
*
|
||||
* 4. Partial destroy finished bit. This bit is set when the
|
||||
* `partialDestructor` has finished running. See (3) above for more
|
||||
* information.
|
||||
*/
|
||||
/** `refCounts` consists of four fields that are treated atomically:
|
||||
|
||||
1. Strong count. This is a count of the number of shared pointers that
|
||||
hold a reference to this object. When the strong counts goes to zero,
|
||||
if the weak count is zero, the destructor is run. If the weak count is
|
||||
non-zero when the strong count goes to zero then the partialDestructor
|
||||
is run.
|
||||
|
||||
2. Weak count. This is a count of the number of weak pointer that hold
|
||||
a reference to this object. When the weak count goes to zero and the
|
||||
strong count is also zero, then the destructor is run.
|
||||
|
||||
3. Partial destroy started bit. This bit is set if the
|
||||
`partialDestructor` function has been started (or is about to be
|
||||
started). This is used to prevent the destructor from running
|
||||
concurrently with the partial destructor. This can easily happen when
|
||||
the last strong pointer release its reference in one thread and starts
|
||||
the partialDestructor, while in another thread the last weak pointer
|
||||
goes out of scope and starts the destructor while the partialDestructor
|
||||
is still running. Both a start and finished bit is needed to handle a
|
||||
corner-case where the last strong pointer goes out of scope, then then
|
||||
last `weakPointer` goes out of scope, but this happens before the
|
||||
`partialDestructor` bit is set. It would be possible to use a single
|
||||
bit if it could also be set atomically when the strong count goes to
|
||||
zero and the weak count is non-zero, but that would add complexity (and
|
||||
likely slow down common cases as well).
|
||||
|
||||
4. Partial destroy finished bit. This bit is set when the
|
||||
`partialDestructor` has finished running. See (3) above for more
|
||||
information.
|
||||
|
||||
*/
|
||||
|
||||
mutable std::atomic<FieldType> refCounts_{kStrongDelta};
|
||||
|
||||
/**
|
||||
* Amount to change the strong count when adding or releasing a reference
|
||||
*
|
||||
* Note: The strong count is stored in the low `StrongCountNumBits` bits
|
||||
* of refCounts
|
||||
*/
|
||||
/** Amount to change the strong count when adding or releasing a reference
|
||||
|
||||
Note: The strong count is stored in the low `StrongCountNumBits` bits
|
||||
of refCounts
|
||||
*/
|
||||
static constexpr FieldType kStrongDelta = 1;
|
||||
|
||||
/**
|
||||
* Amount to change the weak count when adding or releasing a reference
|
||||
*
|
||||
* Note: The weak count is stored in the high `WeakCountNumBits` bits of
|
||||
* refCounts
|
||||
*/
|
||||
/** Amount to change the weak count when adding or releasing a reference
|
||||
|
||||
Note: The weak count is stored in the high `WeakCountNumBits` bits of
|
||||
refCounts
|
||||
*/
|
||||
static constexpr FieldType kWeakDelta = (kOne << kStrongCountNumBits);
|
||||
|
||||
/**
|
||||
* Flag that is set when the partialDestroy function has started running
|
||||
* (or is about to start running).
|
||||
*
|
||||
* See description of the `refCounts` field for a fuller description of
|
||||
* this field.
|
||||
*/
|
||||
/** Flag that is set when the partialDestroy function has started running
|
||||
(or is about to start running).
|
||||
|
||||
See description of the `refCounts` field for a fuller description of
|
||||
this field.
|
||||
*/
|
||||
static constexpr FieldType kPartialDestroyStartedMask = (kOne << (kFieldTypeBits - 1));
|
||||
|
||||
/**
|
||||
* Flag that is set when the partialDestroy function has finished running
|
||||
*
|
||||
* See description of the `refCounts` field for a fuller description of
|
||||
* this field.
|
||||
*/
|
||||
/** Flag that is set when the partialDestroy function has finished running
|
||||
|
||||
See description of the `refCounts` field for a fuller description of
|
||||
this field.
|
||||
*/
|
||||
static constexpr FieldType kPartialDestroyFinishedMask = (kOne << (kFieldTypeBits - 2));
|
||||
|
||||
/**
|
||||
* Mask that will zero out all the `count` bits and leave the tag bits
|
||||
* unchanged.
|
||||
*/
|
||||
/** Mask that will zero out all the `count` bits and leave the tag bits
|
||||
unchanged.
|
||||
*/
|
||||
static constexpr FieldType kTagMask = kPartialDestroyStartedMask | kPartialDestroyFinishedMask;
|
||||
|
||||
/**
|
||||
* Mask that will zero out the `tag` bits and leave the count bits
|
||||
* unchanged.
|
||||
*/
|
||||
/** Mask that will zero out the `tag` bits and leave the count bits
|
||||
unchanged.
|
||||
*/
|
||||
static constexpr FieldType kValueMask = ~kTagMask;
|
||||
|
||||
/**
|
||||
* Mask that will zero out everything except the strong count.
|
||||
/** Mask that will zero out everything except the strong count.
|
||||
*/
|
||||
static constexpr FieldType kStrongMask = ((kOne << kStrongCountNumBits) - 1) & kValueMask;
|
||||
|
||||
/**
|
||||
* Mask that will zero out everything except the weak count.
|
||||
/** Mask that will zero out everything except the weak count.
|
||||
*/
|
||||
static constexpr FieldType kWeakMask =
|
||||
(((kOne << kWeakCountNumBits) - 1) << kStrongCountNumBits) & kValueMask;
|
||||
|
||||
/**
|
||||
* Unpack the count and tag fields from the packed atomic integer form.
|
||||
*/
|
||||
/** Unpack the count and tag fields from the packed atomic integer form. */
|
||||
struct RefCountPair
|
||||
{
|
||||
CountType strong;
|
||||
CountType weak;
|
||||
/**
|
||||
* The `partialDestroyStartedBit` is set to on when the partial
|
||||
* destroy function is started. It is not a boolean; it is a uint32
|
||||
* with all bits zero with the possible exception of the
|
||||
* `partialDestroyStartedMask` bit. This is done so it can be directly
|
||||
* masked into the `combinedValue`.
|
||||
/** The `partialDestroyStartedBit` is set to on when the partial
|
||||
destroy function is started. It is not a boolean; it is a uint32
|
||||
with all bits zero with the possible exception of the
|
||||
`partialDestroyStartedMask` bit. This is done so it can be directly
|
||||
masked into the `combinedValue`.
|
||||
*/
|
||||
FieldType partialDestroyStartedBit{0};
|
||||
/**
|
||||
* The `partialDestroyFinishedBit` is set to on when the partial
|
||||
* destroy function has finished.
|
||||
/** The `partialDestroyFinishedBit` is set to on when the partial
|
||||
destroy function has finished.
|
||||
*/
|
||||
FieldType partialDestroyFinishedBit{0};
|
||||
RefCountPair(FieldType v) noexcept;
|
||||
RefCountPair(CountType s, CountType w) noexcept;
|
||||
|
||||
/**
|
||||
* Convert back to the packed integer form.
|
||||
*/
|
||||
/** Convert back to the packed integer form. */
|
||||
[[nodiscard]] FieldType
|
||||
combinedValue() const noexcept;
|
||||
|
||||
@@ -232,10 +215,9 @@ private:
|
||||
static_cast<CountType>((kOne << kStrongCountNumBits) - 1);
|
||||
static constexpr CountType kMaxWeakValue =
|
||||
static_cast<CountType>((kOne << kWeakCountNumBits) - 1);
|
||||
/**
|
||||
* Put an extra margin to detect when running up against limits.
|
||||
* This is only used in debug code, and is useful if we reduce the
|
||||
* number of bits in the strong and weak counts (to 16 and 14 bits).
|
||||
/** Put an extra margin to detect when running up against limits.
|
||||
This is only used in debug code, and is useful if we reduce the
|
||||
number of bits in the strong and weak counts (to 16 and 14 bits).
|
||||
*/
|
||||
static constexpr CountType kCheckStrongMaxValue = kMaxStrongValue - 32;
|
||||
static constexpr CountType kCheckWeakMaxValue = kMaxWeakValue - 32;
|
||||
|
||||
@@ -70,15 +70,11 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores instance of T specific to the calling coroutine or thread.
|
||||
*/
|
||||
/** Stores instance of T specific to the calling coroutine or thread. */
|
||||
T&
|
||||
operator*();
|
||||
|
||||
/**
|
||||
* Stores instance of T specific to the calling coroutine or thread.
|
||||
*/
|
||||
/** Stores instance of T specific to the calling coroutine or thread. */
|
||||
T*
|
||||
operator->()
|
||||
{
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
|
||||
#include <boost/beast/core/string.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
@@ -16,9 +16,7 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* Manages partitions for logging.
|
||||
*/
|
||||
/** Manages partitions for logging. */
|
||||
class Logs
|
||||
{
|
||||
private:
|
||||
@@ -42,81 +40,69 @@ private:
|
||||
writeAlways(beast::Severity level, std::string const& text) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Manages a system file containing logged output.
|
||||
* The system file remains open during program execution. Interfaces
|
||||
* are provided for interoperating with standard log management
|
||||
* tools like logrotate(8):
|
||||
* http://linuxcommand.org/man_pages/logrotate8.html
|
||||
* @note None of the listed interfaces are thread-safe.
|
||||
*/
|
||||
/** Manages a system file containing logged output.
|
||||
The system file remains open during program execution. Interfaces
|
||||
are provided for interoperating with standard log management
|
||||
tools like logrotate(8):
|
||||
http://linuxcommand.org/man_pages/logrotate8.html
|
||||
@note None of the listed interfaces are thread-safe.
|
||||
*/
|
||||
class File
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Construct with no associated system file.
|
||||
* A system file may be associated later with @ref open.
|
||||
* @see open
|
||||
*/
|
||||
/** Construct with no associated system file.
|
||||
A system file may be associated later with @ref open.
|
||||
@see open
|
||||
*/
|
||||
File();
|
||||
|
||||
/**
|
||||
* Destroy the object.
|
||||
* If a system file is associated, it will be flushed and closed.
|
||||
*/
|
||||
/** Destroy the object.
|
||||
If a system file is associated, it will be flushed and closed.
|
||||
*/
|
||||
~File() = default;
|
||||
|
||||
/**
|
||||
* Determine if a system file is associated with the log.
|
||||
* @return `true` if a system file is associated and opened for
|
||||
* writing.
|
||||
*/
|
||||
/** Determine if a system file is associated with the log.
|
||||
@return `true` if a system file is associated and opened for
|
||||
writing.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
isOpen() const noexcept;
|
||||
|
||||
/**
|
||||
* Associate a system file with the log.
|
||||
* If the file does not exist an attempt is made to create it
|
||||
* and open it for writing. If the file already exists an attempt is
|
||||
* made to open it for appending.
|
||||
* If a system file is already associated with the log, it is closed
|
||||
* first.
|
||||
* @return `true` if the file was opened.
|
||||
*/
|
||||
/** Associate a system file with the log.
|
||||
If the file does not exist an attempt is made to create it
|
||||
and open it for writing. If the file already exists an attempt is
|
||||
made to open it for appending.
|
||||
If a system file is already associated with the log, it is closed
|
||||
first.
|
||||
@return `true` if the file was opened.
|
||||
*/
|
||||
bool
|
||||
open(std::filesystem::path const& path);
|
||||
open(boost::filesystem::path const& path);
|
||||
|
||||
/**
|
||||
* Close and re-open the system file associated with the log
|
||||
* This assists in interoperating with external log management tools.
|
||||
* @return `true` if the file was opened.
|
||||
*/
|
||||
/** Close and re-open the system file associated with the log
|
||||
This assists in interoperating with external log management tools.
|
||||
@return `true` if the file was opened.
|
||||
*/
|
||||
bool
|
||||
closeAndReopen();
|
||||
|
||||
/**
|
||||
* Close the system file if it is open.
|
||||
*/
|
||||
/** Close the system file if it is open. */
|
||||
void
|
||||
close();
|
||||
|
||||
/**
|
||||
* write to the log file.
|
||||
* Does nothing if there is no associated system file.
|
||||
*/
|
||||
/** write to the log file.
|
||||
Does nothing if there is no associated system file.
|
||||
*/
|
||||
void
|
||||
write(char const* text);
|
||||
|
||||
/**
|
||||
* write to the log file and append an end of line marker.
|
||||
* Does nothing if there is no associated system file.
|
||||
*/
|
||||
/** write to the log file and append an end of line marker.
|
||||
Does nothing if there is no associated system file.
|
||||
*/
|
||||
void
|
||||
writeln(char const* text);
|
||||
|
||||
/**
|
||||
* Write to the log file using std::string.
|
||||
*/
|
||||
/** Write to the log file using std::string. */
|
||||
/** @{ */
|
||||
void
|
||||
write(std::string const& str)
|
||||
@@ -133,7 +119,7 @@ private:
|
||||
|
||||
private:
|
||||
std::unique_ptr<std::ofstream> stream_;
|
||||
std::filesystem::path path_;
|
||||
boost::filesystem::path path_;
|
||||
};
|
||||
|
||||
std::mutex mutable mutex_;
|
||||
@@ -152,7 +138,7 @@ public:
|
||||
virtual ~Logs() = default;
|
||||
|
||||
bool
|
||||
open(std::filesystem::path const& pathToLogFile);
|
||||
open(boost::filesystem::path const& pathToLogFile);
|
||||
|
||||
beast::Journal::Sink&
|
||||
get(std::string const& name);
|
||||
@@ -237,21 +223,19 @@ private:
|
||||
//------------------------------------------------------------------------------
|
||||
// Debug logging:
|
||||
|
||||
/**
|
||||
* Set the sink for the debug journal.
|
||||
*
|
||||
* @param sink unique_ptr to new debug Sink.
|
||||
* @return unique_ptr to the previous Sink. nullptr if there was no Sink.
|
||||
*/
|
||||
/** Set the sink for the debug journal.
|
||||
|
||||
@param sink unique_ptr to new debug Sink.
|
||||
@return unique_ptr to the previous Sink. nullptr if there was no Sink.
|
||||
*/
|
||||
std::unique_ptr<beast::Journal::Sink>
|
||||
setDebugLogSink(std::unique_ptr<beast::Journal::Sink> sink);
|
||||
|
||||
/**
|
||||
* Returns a debug journal.
|
||||
* The journal may drain to a null sink, so its output
|
||||
* may never be seen. Never use it for critical
|
||||
* information.
|
||||
*/
|
||||
/** Returns a debug journal.
|
||||
The journal may drain to a null sink, so its output
|
||||
may never be seen. Never use it for critical
|
||||
information.
|
||||
*/
|
||||
beast::Journal
|
||||
debugLog();
|
||||
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* Calculate one number divided by another number in percentage.
|
||||
/** Calculate one number divided by another number in percentage.
|
||||
* The result is rounded up to the next integer, and capped in the range [0,100]
|
||||
* E.g. calculatePercent(1, 100) = 1 because 1/100 = 0.010000
|
||||
* calculatePercent(1, 99) = 2 because 1/99 = 0.010101
|
||||
@@ -20,7 +19,7 @@ namespace xrpl {
|
||||
* @return the percentage, in [0, 100]
|
||||
*
|
||||
* @note total cannot be zero.
|
||||
*/
|
||||
* */
|
||||
constexpr std::size_t
|
||||
calculatePercent(std::size_t count, std::size_t total)
|
||||
{
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl {
|
||||
@@ -47,54 +48,46 @@ isPowerOfTen(T value)
|
||||
|
||||
namespace detail {
|
||||
|
||||
/**
|
||||
* Builds a table of the powers of 10
|
||||
/** Builds a table of the powers of 10
|
||||
*
|
||||
* This function is marked consteval, so it can only be run in
|
||||
* a constexpr context. This assures that it is and can only be run at
|
||||
* compile time. Doing it at runtime would be pretty wasteful and
|
||||
* inefficient.
|
||||
*/
|
||||
constexpr std::size_t kUint64Digits = 20;
|
||||
[[maybe_unused]] constexpr std::size_t kUint128Digits = 39;
|
||||
|
||||
template <typename T, std::size_t Digits>
|
||||
consteval std::array<T, Digits>
|
||||
constexpr std::size_t kInt64Digits = 20;
|
||||
consteval std::array<std::uint64_t, kInt64Digits>
|
||||
buildPowersOfTen()
|
||||
{
|
||||
std::array<T, Digits> result{};
|
||||
std::array<std::uint64_t, kInt64Digits> result{};
|
||||
|
||||
T power = 1;
|
||||
std::uint64_t power = 1;
|
||||
std::size_t exponent = 0;
|
||||
// end the loop early so it doesn't overflow;
|
||||
for (; exponent < result.size() - 1; ++exponent, power *= 10)
|
||||
{
|
||||
result[exponent] = power;
|
||||
if (power > std::numeric_limits<T>::max() / 10)
|
||||
if (power > std::numeric_limits<std::uint64_t>::max() / 10)
|
||||
throw std::logic_error("Power of 10 table is too big");
|
||||
}
|
||||
result[exponent] = power;
|
||||
if (power < std::numeric_limits<T>::max() / 10)
|
||||
throw std::logic_error("Power of 10 table is not big enough for the given type");
|
||||
if (power < std::numeric_limits<std::uint64_t>::max() / 10)
|
||||
throw std::logic_error("Power of 10 table is not big enough for the uint64_t type");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename T = std::uint64_t, std::size_t Digits = detail::kUint64Digits>
|
||||
constexpr std::array<T, Digits> kPowerOfTenImpl = detail::buildPowersOfTen<T, Digits>();
|
||||
|
||||
constexpr auto kPowerOfTen = kPowerOfTenImpl<std::uint64_t, detail::kUint64Digits>;
|
||||
constexpr std::array<std::uint64_t, detail::kInt64Digits> kPowerOfTen = detail::buildPowersOfTen();
|
||||
|
||||
static_assert(kPowerOfTen[0] == 1);
|
||||
static_assert(kPowerOfTen[1] == 10);
|
||||
static_assert(kPowerOfTen[10] == 10'000'000'000);
|
||||
static_assert(
|
||||
isPowerOfTen(kPowerOfTen.back()) && *logTen(kPowerOfTen.back()) == detail::kUint64Digits - 1);
|
||||
isPowerOfTen(kPowerOfTen.back()) && *logTen(kPowerOfTen.back()) == detail::kInt64Digits - 1);
|
||||
|
||||
/**
|
||||
* MantissaRange defines a range for the mantissa of a normalized Number.
|
||||
/** MantissaRange defines a range for the mantissa of a normalized Number.
|
||||
*
|
||||
* The mantissa is in the range [min, max], where
|
||||
* * min is a power of 10, and
|
||||
@@ -131,37 +124,17 @@ struct MantissaRange final
|
||||
{
|
||||
using rep = std::uint64_t;
|
||||
|
||||
// NOLINTBEGIN(readability-enum-initial-value)
|
||||
// The values don't matter, except for Large
|
||||
enum class MantissaScale {
|
||||
// Small can be removed when either featureSingleAssetVault or featureLendingProtocol are
|
||||
// retired
|
||||
Small,
|
||||
// LargeLegacy can be removed when fixCleanup3_2_0 is retired
|
||||
LargeLegacy,
|
||||
// Large320 can be removed when fixCleanup3_3_0 is retired
|
||||
Large320,
|
||||
// If Large330 is ever the only remaining "Large*" entry, it can be renamed to just "Large".
|
||||
Large330,
|
||||
// Large is a de-facto alias for "the latest", and is only here for backward compatibility
|
||||
// in the extremely unlikely case that a downstream project made use of it. Note that
|
||||
// because the behavior changed, this may still be a breaking change.
|
||||
Large = Large330,
|
||||
Large,
|
||||
};
|
||||
// NOLINTEND(readability-enum-initial-value)
|
||||
|
||||
// This entire enum can be removed when the last relevant amendment is retired
|
||||
enum class CuspRoundingFix : std::uint8_t {
|
||||
// Disabled can be removed when fixCleanup3_2_0 is retired
|
||||
Disabled = 0,
|
||||
// Enabled320 can be removed when fixCleanup3_3_0 is retired
|
||||
Enabled320 = 1,
|
||||
// If we ever get to the point that there's only one entry, remove the entire enum
|
||||
Enabled330 = 2,
|
||||
// Enabled is a de-facto alias for "the latest", and is only here for backward compatibility
|
||||
// in the extremely unlikely case that a downstream project made use of it. Note that
|
||||
// because the behavior changed, this may still be a breaking change.
|
||||
Enabled = Enabled330,
|
||||
// This entire enum can be removed when fixCleanup3_2_0 is retired
|
||||
enum class CuspRoundingFix : bool {
|
||||
Disabled = false,
|
||||
Enabled = true,
|
||||
};
|
||||
|
||||
explicit constexpr MantissaRange(MantissaScale sc) : scale(sc)
|
||||
@@ -172,27 +145,13 @@ struct MantissaRange final
|
||||
int const log{getExponent(scale)};
|
||||
rep const min{getMin(scale, log)};
|
||||
rep const max{(min * 10) - 1};
|
||||
CuspRoundingFix const cuspRoundingFix{isCuspFixEnabled(scale)};
|
||||
CuspRoundingFix const cuspRoundingFixEnabled{isCuspFixEnabled(scale)};
|
||||
|
||||
static MantissaRange const&
|
||||
getMantissaRange(MantissaScale scale);
|
||||
|
||||
static std::set<MantissaScale> const&
|
||||
getAllScales()
|
||||
{
|
||||
static std::set<MantissaRange::MantissaScale> const kScales = {
|
||||
MantissaRange::MantissaScale::Small,
|
||||
MantissaRange::MantissaScale::LargeLegacy,
|
||||
MantissaRange::MantissaScale::Large320,
|
||||
MantissaRange::MantissaScale::Large330,
|
||||
};
|
||||
return kScales;
|
||||
}
|
||||
|
||||
class Access
|
||||
{
|
||||
static constexpr MantissaRange const&
|
||||
mantissaRange(MantissaScale scale);
|
||||
|
||||
friend Number;
|
||||
};
|
||||
getAllScales();
|
||||
|
||||
private:
|
||||
static constexpr int
|
||||
@@ -203,8 +162,7 @@ private:
|
||||
case MantissaScale::Small:
|
||||
return 15;
|
||||
case MantissaScale::LargeLegacy:
|
||||
case MantissaScale::Large320:
|
||||
case MantissaScale::Large330:
|
||||
case MantissaScale::Large:
|
||||
return 18;
|
||||
// LCOV_EXCL_START
|
||||
default:
|
||||
@@ -233,24 +191,24 @@ private:
|
||||
case MantissaScale::Small:
|
||||
case MantissaScale::LargeLegacy:
|
||||
return CuspRoundingFix::Disabled;
|
||||
case MantissaScale::Large320:
|
||||
return CuspRoundingFix::Enabled320;
|
||||
case MantissaScale::Large330:
|
||||
return CuspRoundingFix::Enabled330;
|
||||
case MantissaScale::Large:
|
||||
return CuspRoundingFix::Enabled;
|
||||
default:
|
||||
// If called in a constexpr context, this throw assures that the build fails if an
|
||||
// invalid scale is used.
|
||||
throw std::runtime_error("Unknown mantissa scale"); // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
|
||||
static std::unordered_map<MantissaScale, MantissaRange> const&
|
||||
getRanges();
|
||||
};
|
||||
|
||||
// Like std::integral, but only 64-bit integral types.
|
||||
template <class T>
|
||||
concept Integral64 = std::is_same_v<T, std::int64_t> || std::is_same_v<T, std::uint64_t>;
|
||||
|
||||
/**
|
||||
* Number is a floating point type that can represent a wide range of values.
|
||||
/** Number is a floating point type that can represent a wide range of values.
|
||||
*
|
||||
* It can represent all values that can be represented by an STAmount -
|
||||
* regardless of asset type - XRPAmount, MPTAmount, and IOUAmount, with at least
|
||||
@@ -346,6 +304,7 @@ concept Integral64 = std::is_same_v<T, std::int64_t> || std::is_same_v<T, std::u
|
||||
* disable the amendments that control the mantissa range choice
|
||||
* (SingleAssetVault and LendingProtocol), and/or check if either of those
|
||||
* amendments are enabled to determine which result to expect.
|
||||
*
|
||||
*/
|
||||
class Number final
|
||||
{
|
||||
@@ -431,11 +390,10 @@ public:
|
||||
static Number
|
||||
lowest() noexcept;
|
||||
|
||||
/**
|
||||
* Conversions to Number are implicit and conversions away from Number
|
||||
* are explicit. This design encourages and facilitates the use of Number
|
||||
* as the preferred type for floating point arithmetic as it makes
|
||||
* "mixed mode" more convenient, e.g. MPTAmount + Number.
|
||||
/** Conversions to Number are implicit and conversions away from Number
|
||||
* are explicit. This design encourages and facilitates the use of Number
|
||||
* as the preferred type for floating point arithmetic as it makes
|
||||
* "mixed mode" more convenient, e.g. MPTAmount + Number.
|
||||
*/
|
||||
explicit
|
||||
operator rep() const; // round to nearest, even on tie
|
||||
@@ -490,9 +448,7 @@ public:
|
||||
return l.mantissa_ < r.mantissa_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the sign of the amount
|
||||
*/
|
||||
/** Return the sign of the amount */
|
||||
[[nodiscard]] constexpr int
|
||||
signum() const noexcept
|
||||
{
|
||||
@@ -546,16 +502,14 @@ public:
|
||||
static RoundingMode
|
||||
setround(RoundingMode inMode);
|
||||
|
||||
/**
|
||||
* Returns which mantissa scale is currently in use for normalization.
|
||||
/** Returns which mantissa scale is currently in use for normalization.
|
||||
*
|
||||
* If you think you need to call this outside of unit tests, no you don't.
|
||||
*/
|
||||
static MantissaRange::MantissaScale
|
||||
getMantissaScale();
|
||||
|
||||
/**
|
||||
* Changes which mantissa scale is used for normalization.
|
||||
/** Changes which mantissa scale is used for normalization.
|
||||
*
|
||||
* If you think you need to call this outside of unit tests, no you don't.
|
||||
*/
|
||||
@@ -600,17 +554,10 @@ private:
|
||||
// changing the values inside the range.
|
||||
static thread_local std::reference_wrapper<MantissaRange const> kRange;
|
||||
|
||||
class Guard;
|
||||
|
||||
void
|
||||
normalize(MantissaRange const& range);
|
||||
|
||||
// Guard has the fields that we need, as well as MantissaRange, so if we have a guard, use that
|
||||
void
|
||||
normalize(Guard const& guard);
|
||||
|
||||
/**
|
||||
* Normalize Number components to an arbitrary range.
|
||||
/** Normalize Number components to an arbitrary range.
|
||||
*
|
||||
* min/maxMantissa are parameters because this function is used by both
|
||||
* normalize(), which reads from kRange, and by normalizeToRange,
|
||||
@@ -624,7 +571,7 @@ private:
|
||||
int& exponent,
|
||||
internalrep const& minMantissa,
|
||||
internalrep const& maxMantissa,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFix);
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled);
|
||||
|
||||
template <class T>
|
||||
friend void
|
||||
@@ -634,7 +581,7 @@ private:
|
||||
int& exponent,
|
||||
MantissaRange::rep const& minMantissa,
|
||||
MantissaRange::rep const& maxMantissa,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFix,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled,
|
||||
bool dropped);
|
||||
|
||||
[[nodiscard]] bool
|
||||
@@ -652,6 +599,8 @@ private:
|
||||
// UB, and can vary across compilers.
|
||||
static internalrep
|
||||
externalToInternal(rep mantissa);
|
||||
|
||||
class Guard;
|
||||
};
|
||||
|
||||
constexpr Number::Number(bool negative, internalrep mantissa, int exponent, Unchecked) noexcept
|
||||
@@ -686,8 +635,7 @@ inline Number::Number(rep mantissa) : Number{mantissa, 0}
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mantissa of the external view of the Number.
|
||||
/** Returns the mantissa of the external view of the Number.
|
||||
*
|
||||
* Please see the "---- External Interface ----" section of the class
|
||||
* documentation for an explanation of why the internal value may be modified.
|
||||
@@ -708,8 +656,7 @@ Number::mantissa() const noexcept
|
||||
return sign * static_cast<Number::rep>(m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the exponent of the external view of the Number.
|
||||
/** Returns the exponent of the external view of the Number.
|
||||
*
|
||||
* Please see the "---- External Interface ----" section of the class
|
||||
* documentation for an explanation of why the internal value may be modified.
|
||||
@@ -915,11 +862,21 @@ squelch(Number const& x, Number const& limit) noexcept
|
||||
return x;
|
||||
}
|
||||
|
||||
std::string
|
||||
to_string(MantissaRange::MantissaScale const& scale);
|
||||
|
||||
std::string
|
||||
to_string(Number::RoundingMode const& round);
|
||||
inline std::string
|
||||
to_string(MantissaRange::MantissaScale const& scale)
|
||||
{
|
||||
switch (scale)
|
||||
{
|
||||
case MantissaRange::MantissaScale::Small:
|
||||
return "small";
|
||||
case MantissaRange::MantissaScale::LargeLegacy:
|
||||
return "largeLegacy";
|
||||
case MantissaRange::MantissaScale::Large:
|
||||
return "large";
|
||||
default:
|
||||
throw std::runtime_error("Bad scale");
|
||||
}
|
||||
}
|
||||
|
||||
class SaveNumberRoundMode
|
||||
{
|
||||
@@ -958,10 +915,10 @@ public:
|
||||
operator=(NumberRoundModeGuard const&) = delete;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the new scale and restores the old scale when it leaves scope.
|
||||
/** Sets the new scale and restores the old scale when it leaves scope.
|
||||
*
|
||||
* If you think you need to use this class outside of unit tests, no you don't.
|
||||
*
|
||||
*/
|
||||
class NumberMantissaScaleGuard
|
||||
{
|
||||
|
||||
@@ -13,25 +13,23 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* A closed interval over the domain T.
|
||||
*
|
||||
* For an instance ClosedInterval c, this represents the closed interval
|
||||
* (c.first(), c.last()). A single element interval has c.first() == c.last().
|
||||
*
|
||||
* This is simply a type-alias for boost interval container library interval
|
||||
* set, so users should consult that documentation for available supporting
|
||||
* member and free functions.
|
||||
*/
|
||||
/** A closed interval over the domain T.
|
||||
|
||||
For an instance ClosedInterval c, this represents the closed interval
|
||||
(c.first(), c.last()). A single element interval has c.first() == c.last().
|
||||
|
||||
This is simply a type-alias for boost interval container library interval
|
||||
set, so users should consult that documentation for available supporting
|
||||
member and free functions.
|
||||
*/
|
||||
template <class T>
|
||||
using ClosedInterval = boost::icl::closed_interval<T>;
|
||||
|
||||
/**
|
||||
* Create a closed range interval
|
||||
*
|
||||
* Helper function to create a closed range interval without having to qualify
|
||||
* the template argument.
|
||||
*/
|
||||
/** Create a closed range interval
|
||||
|
||||
Helper function to create a closed range interval without having to qualify
|
||||
the template argument.
|
||||
*/
|
||||
template <class T>
|
||||
ClosedInterval<T>
|
||||
range(T low, T high)
|
||||
@@ -39,30 +37,28 @@ range(T low, T high)
|
||||
return ClosedInterval<T>(low, high);
|
||||
}
|
||||
|
||||
/**
|
||||
* A set of closed intervals over the domain T.
|
||||
*
|
||||
* Represents a set of values of the domain T using the minimum number
|
||||
* of disjoint ClosedInterval<T>. This is useful to represent ranges of
|
||||
* T where a few instances are missing, e.g. the set 1-5,8-9,11-14.
|
||||
*
|
||||
* This is simply a type-alias for boost interval container library interval
|
||||
* set, so users should consult that documentation for available supporting
|
||||
* member and free functions.
|
||||
*/
|
||||
/** A set of closed intervals over the domain T.
|
||||
|
||||
Represents a set of values of the domain T using the minimum number
|
||||
of disjoint ClosedInterval<T>. This is useful to represent ranges of
|
||||
T where a few instances are missing, e.g. the set 1-5,8-9,11-14.
|
||||
|
||||
This is simply a type-alias for boost interval container library interval
|
||||
set, so users should consult that documentation for available supporting
|
||||
member and free functions.
|
||||
*/
|
||||
template <class T>
|
||||
using RangeSet = boost::icl::interval_set<T, std::less, ClosedInterval<T>>;
|
||||
|
||||
/**
|
||||
* Convert a ClosedInterval to a styled string
|
||||
*
|
||||
* The styled string is
|
||||
* "c.first()-c.last()" if c.first() != c.last()
|
||||
* "c.first()" if c.first() == c.last()
|
||||
*
|
||||
* @param ci The closed interval to convert
|
||||
* @return The style string
|
||||
*/
|
||||
/** Convert a ClosedInterval to a styled string
|
||||
|
||||
The styled string is
|
||||
"c.first()-c.last()" if c.first() != c.last()
|
||||
"c.first()" if c.first() == c.last()
|
||||
|
||||
@param ci The closed interval to convert
|
||||
@return The style string
|
||||
*/
|
||||
template <class T>
|
||||
std::string
|
||||
to_string(ClosedInterval<T> const& ci)
|
||||
@@ -72,15 +68,14 @@ to_string(ClosedInterval<T> const& ci)
|
||||
return std::to_string(ci.first()) + "-" + std::to_string(ci.last());
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given RangeSet to a styled string.
|
||||
*
|
||||
* The styled string representation is the set of disjoint intervals joined
|
||||
* by commas. The string "empty" is returned if the set is empty.
|
||||
*
|
||||
* @param rs The rangeset to convert
|
||||
* @return The styled string
|
||||
*/
|
||||
/** Convert the given RangeSet to a styled string.
|
||||
|
||||
The styled string representation is the set of disjoint intervals joined
|
||||
by commas. The string "empty" is returned if the set is empty.
|
||||
|
||||
@param rs The rangeset to convert
|
||||
@return The styled string
|
||||
*/
|
||||
template <class T>
|
||||
std::string
|
||||
to_string(RangeSet<T> const& rs)
|
||||
@@ -96,16 +91,15 @@ to_string(RangeSet<T> const& rs)
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given styled string to a RangeSet.
|
||||
*
|
||||
* The styled string representation is the set
|
||||
* of disjoint intervals joined by commas.
|
||||
*
|
||||
* @param rs The set to be populated
|
||||
* @param s The styled string to convert
|
||||
* @return True on successfully converting styled string
|
||||
*/
|
||||
/** Convert the given styled string to a RangeSet.
|
||||
|
||||
The styled string representation is the set
|
||||
of disjoint intervals joined by commas.
|
||||
|
||||
@param rs The set to be populated
|
||||
@param s The styled string to convert
|
||||
@return True on successfully converting styled string
|
||||
*/
|
||||
template <class T>
|
||||
[[nodiscard]] bool
|
||||
fromString(RangeSet<T>& rs, std::string const& s)
|
||||
@@ -167,15 +161,14 @@ fromString(RangeSet<T>& rs, std::string const& s)
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the largest value not in the set that is less than a given value.
|
||||
*
|
||||
* @param rs The set of interest
|
||||
* @param t The value that must be larger than the result
|
||||
* @param minVal (Default is 0) The smallest allowed value
|
||||
* @return The largest v such that minV <= v < t and !contains(rs, v) or
|
||||
* std::nullopt if no such v exists.
|
||||
*/
|
||||
/** Find the largest value not in the set that is less than a given value.
|
||||
|
||||
@param rs The set of interest
|
||||
@param t The value that must be larger than the result
|
||||
@param minVal (Default is 0) The smallest allowed value
|
||||
@return The largest v such that minV <= v < t and !contains(rs, v) or
|
||||
std::nullopt if no such v exists.
|
||||
*/
|
||||
template <class T>
|
||||
std::optional<T>
|
||||
prevMissing(RangeSet<T> const& rs, T t, T minVal = 0)
|
||||
|
||||
@@ -15,29 +15,22 @@ public:
|
||||
|
||||
virtual ~Resolver() = 0;
|
||||
|
||||
/**
|
||||
* Issue an asynchronous stop request.
|
||||
*/
|
||||
/** Issue an asynchronous stop request. */
|
||||
virtual void
|
||||
stopAsync() = 0;
|
||||
|
||||
/**
|
||||
* Issue a synchronous stop request.
|
||||
*/
|
||||
/** Issue a synchronous stop request. */
|
||||
virtual void
|
||||
stop() = 0;
|
||||
|
||||
/**
|
||||
* Issue a synchronous start request.
|
||||
*/
|
||||
/** Issue a synchronous start request. */
|
||||
virtual void
|
||||
start() = 0;
|
||||
|
||||
/**
|
||||
* resolve all hostnames on the list
|
||||
* @param names the names to be resolved
|
||||
* @param handler the handler to call
|
||||
*/
|
||||
/** resolve all hostnames on the list
|
||||
@param names the names to be resolved
|
||||
@param handler the handler to call
|
||||
*/
|
||||
/** @{ */
|
||||
template <class Handler>
|
||||
void
|
||||
|
||||
@@ -7,14 +7,13 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* A combination of a std::shared_ptr and a std::weak_pointer.
|
||||
*
|
||||
*
|
||||
* This class is a wrapper to a `std::variant<std::shared_ptr,std::weak_ptr>`
|
||||
* This class is useful for storing intrusive pointers in tagged caches using less
|
||||
* memory than storing both pointers directly.
|
||||
*/
|
||||
/** A combination of a std::shared_ptr and a std::weak_pointer.
|
||||
|
||||
|
||||
This class is a wrapper to a `std::variant<std::shared_ptr,std::weak_ptr>`
|
||||
This class is useful for storing intrusive pointers in tagged caches using less
|
||||
memory than storing both pointers directly.
|
||||
*/
|
||||
|
||||
template <class T>
|
||||
class SharedWeakCachePointer
|
||||
@@ -49,79 +48,65 @@ public:
|
||||
|
||||
~SharedWeakCachePointer();
|
||||
|
||||
/**
|
||||
* Return a strong pointer if this is already a strong pointer (i.e. don't
|
||||
* lock the weak pointer. Use the `lock` method if that's what's needed)
|
||||
/** Return a strong pointer if this is already a strong pointer (i.e. don't
|
||||
lock the weak pointer. Use the `lock` method if that's what's needed)
|
||||
*/
|
||||
[[nodiscard]] std::shared_ptr<T> const&
|
||||
getStrong() const;
|
||||
|
||||
/**
|
||||
* Return true if this is a strong pointer and the strong pointer is
|
||||
* seated.
|
||||
/** Return true if this is a strong pointer and the strong pointer is
|
||||
seated.
|
||||
*/
|
||||
explicit
|
||||
operator bool() const noexcept;
|
||||
|
||||
/**
|
||||
* Set the pointer to null, decrement the appropriate ref count, and run
|
||||
* the appropriate release action.
|
||||
/** Set the pointer to null, decrement the appropriate ref count, and run
|
||||
the appropriate release action.
|
||||
*/
|
||||
void
|
||||
reset();
|
||||
|
||||
/**
|
||||
* If this is a strong pointer, return the raw pointer. Otherwise return
|
||||
* null.
|
||||
/** If this is a strong pointer, return the raw pointer. Otherwise return
|
||||
null.
|
||||
*/
|
||||
[[nodiscard]] T*
|
||||
get() const;
|
||||
|
||||
/**
|
||||
* If this is a strong pointer, return the strong count. Otherwise return 0
|
||||
/** If this is a strong pointer, return the strong count. Otherwise return 0
|
||||
*/
|
||||
[[nodiscard]] std::size_t
|
||||
useCount() const;
|
||||
|
||||
/**
|
||||
* Return true if there is a non-zero strong count.
|
||||
*/
|
||||
/** Return true if there is a non-zero strong count. */
|
||||
[[nodiscard]] bool
|
||||
expired() const;
|
||||
|
||||
/**
|
||||
* If this is a strong pointer, return the strong pointer. Otherwise
|
||||
* attempt to lock the weak pointer.
|
||||
/** If this is a strong pointer, return the strong pointer. Otherwise
|
||||
attempt to lock the weak pointer.
|
||||
*/
|
||||
[[nodiscard]] std::shared_ptr<T>
|
||||
lock() const;
|
||||
|
||||
/**
|
||||
* Return true is this represents a strong pointer.
|
||||
*/
|
||||
/** Return true is this represents a strong pointer. */
|
||||
[[nodiscard]] bool
|
||||
isStrong() const;
|
||||
|
||||
/**
|
||||
* Return true is this represents a weak pointer.
|
||||
*/
|
||||
/** Return true is this represents a weak pointer. */
|
||||
[[nodiscard]] bool
|
||||
isWeak() const;
|
||||
|
||||
/**
|
||||
* If this is a weak pointer, attempt to convert it to a strong pointer.
|
||||
*
|
||||
* @return true if successfully converted to a strong pointer (or was
|
||||
* already a strong pointer). Otherwise false.
|
||||
*/
|
||||
/** If this is a weak pointer, attempt to convert it to a strong pointer.
|
||||
|
||||
@return true if successfully converted to a strong pointer (or was
|
||||
already a strong pointer). Otherwise false.
|
||||
*/
|
||||
bool
|
||||
convertToStrong();
|
||||
|
||||
/**
|
||||
* If this is a strong pointer, attempt to convert it to a weak pointer.
|
||||
*
|
||||
* @return false if the pointer is null. Otherwise return true.
|
||||
*/
|
||||
/** If this is a strong pointer, attempt to convert it to a weak pointer.
|
||||
|
||||
@return false if the pointer is null. Otherwise return true.
|
||||
*/
|
||||
bool
|
||||
convertToWeak();
|
||||
|
||||
|
||||
@@ -33,9 +33,7 @@ class SlabAllocator
|
||||
|
||||
static_assert(alignof(Type) == 8 || alignof(Type) == 4);
|
||||
|
||||
/**
|
||||
* A block of memory that is owned by a slab allocator
|
||||
*/
|
||||
/** A block of memory that is owned by a slab allocator */
|
||||
struct SlabBlock
|
||||
{
|
||||
// A mutex to protect the freelist for this block:
|
||||
@@ -82,9 +80,7 @@ class SlabAllocator
|
||||
SlabBlock&
|
||||
operator=(SlabBlock&& other) = delete;
|
||||
|
||||
/**
|
||||
* Determines whether the given pointer belongs to this allocator
|
||||
*/
|
||||
/** Determines whether the given pointer belongs to this allocator */
|
||||
bool
|
||||
own(std::uint8_t const* pIn) const noexcept
|
||||
{
|
||||
@@ -111,15 +107,14 @@ class SlabAllocator
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an item to this allocator's freelist.
|
||||
*
|
||||
* @param ptr The pointer to the chunk of memory being deallocated.
|
||||
*
|
||||
* @note This is a dangerous, private interface; the item being
|
||||
* returned should belong to this allocator. Debug builds
|
||||
* will check and assert if this is not the case. Release
|
||||
* builds will not.
|
||||
/** Return an item to this allocator's freelist.
|
||||
|
||||
@param ptr The pointer to the chunk of memory being deallocated.
|
||||
|
||||
@note This is a dangerous, private interface; the item being
|
||||
returned should belong to this allocator. Debug builds
|
||||
will check and assert if this is not the case. Release
|
||||
builds will not.
|
||||
*/
|
||||
void
|
||||
deallocate(std::uint8_t* ptr) noexcept
|
||||
@@ -150,14 +145,13 @@ private:
|
||||
std::size_t const slabSize_;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructs a slab allocator able to allocate objects of a fixed size
|
||||
*
|
||||
* @param count the number of items the slab allocator can allocate; note
|
||||
* that a count of 0 is valid and means that the allocator
|
||||
* is, effectively, disabled. This can be very useful in some
|
||||
* contexts (e.g. when minimal memory usage is needed) and
|
||||
* allows for graceful failure.
|
||||
/** Constructs a slab allocator able to allocate objects of a fixed size
|
||||
|
||||
@param count the number of items the slab allocator can allocate; note
|
||||
that a count of 0 is valid and means that the allocator
|
||||
is, effectively, disabled. This can be very useful in some
|
||||
contexts (e.g. when minimal memory usage is needed) and
|
||||
allows for graceful failure.
|
||||
*/
|
||||
constexpr explicit SlabAllocator(
|
||||
std::size_t extra,
|
||||
@@ -185,20 +179,17 @@ public:
|
||||
// shutdown process up could make this possible.
|
||||
~SlabAllocator() = default;
|
||||
|
||||
/**
|
||||
* Returns the size of the memory block this allocator returns.
|
||||
*/
|
||||
/** Returns the size of the memory block this allocator returns. */
|
||||
[[nodiscard]] constexpr std::size_t
|
||||
size() const noexcept
|
||||
{
|
||||
return itemSize_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a suitably aligned pointer, if one is available.
|
||||
*
|
||||
* @return a pointer to a block of memory from the allocator, or
|
||||
* nullptr if the allocator can't satisfy this request.
|
||||
/** Returns a suitably aligned pointer, if one is available.
|
||||
|
||||
@return a pointer to a block of memory from the allocator, or
|
||||
nullptr if the allocator can't satisfy this request.
|
||||
*/
|
||||
std::uint8_t*
|
||||
allocate() noexcept
|
||||
@@ -259,13 +250,12 @@ public:
|
||||
return slab->allocate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the memory block to the allocator.
|
||||
*
|
||||
* @param ptr A pointer to a memory block.
|
||||
* @param size If non-zero, a hint as to the size of the block.
|
||||
* @return true if this memory block belonged to the allocator and has
|
||||
* been released; false otherwise.
|
||||
/** Returns the memory block to the allocator.
|
||||
|
||||
@param ptr A pointer to a memory block.
|
||||
@param size If non-zero, a hint as to the size of the block.
|
||||
@return true if this memory block belonged to the allocator and has
|
||||
been released; false otherwise.
|
||||
*/
|
||||
bool
|
||||
deallocate(std::uint8_t* ptr) noexcept
|
||||
@@ -288,9 +278,7 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A collection of slab allocators of various sizes for a given type.
|
||||
*/
|
||||
/** A collection of slab allocators of various sizes for a given type. */
|
||||
template <typename Type>
|
||||
class SlabAllocatorSet
|
||||
{
|
||||
@@ -357,14 +345,13 @@ public:
|
||||
|
||||
~SlabAllocatorSet() = default;
|
||||
|
||||
/**
|
||||
* Returns a suitably aligned pointer, if one is available.
|
||||
*
|
||||
* @param extra The number of extra bytes, above and beyond the size of
|
||||
* the object, that should be returned by the allocator.
|
||||
*
|
||||
* @return a pointer to a block of memory, or nullptr if the allocator
|
||||
* can't satisfy this request.
|
||||
/** Returns a suitably aligned pointer, if one is available.
|
||||
|
||||
@param extra The number of extra bytes, above and beyond the size of
|
||||
the object, that should be returned by the allocator.
|
||||
|
||||
@return a pointer to a block of memory, or nullptr if the allocator
|
||||
can't satisfy this request.
|
||||
*/
|
||||
std::uint8_t*
|
||||
allocate(std::size_t extra) noexcept
|
||||
@@ -381,13 +368,12 @@ public:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the memory block to the allocator.
|
||||
*
|
||||
* @param ptr A pointer to a memory block.
|
||||
*
|
||||
* @return true if this memory block belonged to one of the allocators
|
||||
* in this set and has been released; false otherwise.
|
||||
/** Returns the memory block to the allocator.
|
||||
|
||||
@param ptr A pointer to a memory block.
|
||||
|
||||
@return true if this memory block belonged to one of the allocators
|
||||
in this set and has been released; false otherwise.
|
||||
*/
|
||||
bool
|
||||
deallocate(std::uint8_t* ptr) noexcept
|
||||
|
||||
@@ -16,13 +16,12 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* An immutable linear range of bytes.
|
||||
*
|
||||
* A fully constructed Slice is guaranteed to be in a valid state.
|
||||
* A Slice is lightweight and copyable, it retains no ownership
|
||||
* of the underlying memory.
|
||||
*/
|
||||
/** An immutable linear range of bytes.
|
||||
|
||||
A fully constructed Slice is guaranteed to be in a valid state.
|
||||
A Slice is lightweight and copyable, it retains no ownership
|
||||
of the underlying memory.
|
||||
*/
|
||||
class Slice
|
||||
{
|
||||
private:
|
||||
@@ -33,37 +32,30 @@ public:
|
||||
using value_type = std::uint8_t;
|
||||
using const_iterator = value_type const*;
|
||||
|
||||
/**
|
||||
* Default constructed Slice has length 0.
|
||||
*/
|
||||
/** Default constructed Slice has length 0. */
|
||||
Slice() noexcept = default;
|
||||
|
||||
Slice(Slice const&) noexcept = default;
|
||||
Slice&
|
||||
operator=(Slice const&) noexcept = default;
|
||||
|
||||
/**
|
||||
* Create a slice pointing to existing memory.
|
||||
*/
|
||||
/** Create a slice pointing to existing memory. */
|
||||
Slice(void const* data, std::size_t size) noexcept
|
||||
: data_(reinterpret_cast<std::uint8_t const*>(data)), size_(size)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Return `true` if the byte range is empty.
|
||||
*/
|
||||
/** Return `true` if the byte range is empty. */
|
||||
[[nodiscard]] bool
|
||||
empty() const noexcept
|
||||
{
|
||||
return size_ == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of bytes in the storage.
|
||||
*
|
||||
* This may be zero for an empty range.
|
||||
*/
|
||||
/** Returns the number of bytes in the storage.
|
||||
|
||||
This may be zero for an empty range.
|
||||
*/
|
||||
/** @{ */
|
||||
[[nodiscard]] std::size_t
|
||||
size() const noexcept
|
||||
@@ -78,20 +70,17 @@ public:
|
||||
}
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Return a pointer to beginning of the storage.
|
||||
* @note The return type is guaranteed to be a pointer
|
||||
* to a single byte, to facilitate pointer arithmetic.
|
||||
*/
|
||||
/** Return a pointer to beginning of the storage.
|
||||
@note The return type is guaranteed to be a pointer
|
||||
to a single byte, to facilitate pointer arithmetic.
|
||||
*/
|
||||
[[nodiscard]] std::uint8_t const*
|
||||
data() const noexcept
|
||||
{
|
||||
return data_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access raw bytes.
|
||||
*/
|
||||
/** Access raw bytes. */
|
||||
std::uint8_t
|
||||
operator[](std::size_t i) const noexcept
|
||||
{
|
||||
@@ -99,9 +88,7 @@ public:
|
||||
return data_[i];
|
||||
}
|
||||
|
||||
/**
|
||||
* Advance the buffer.
|
||||
*/
|
||||
/** Advance the buffer. */
|
||||
/** @{ */
|
||||
Slice&
|
||||
operator+=(std::size_t n)
|
||||
@@ -121,9 +108,7 @@ public:
|
||||
}
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Shrinks the slice by moving its start forward by n characters.
|
||||
*/
|
||||
/** Shrinks the slice by moving its start forward by n characters. */
|
||||
void
|
||||
removePrefix(std::size_t n)
|
||||
{
|
||||
@@ -131,9 +116,7 @@ public:
|
||||
size_ -= n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shrinks the slice by moving its end backward by n characters.
|
||||
*/
|
||||
/** Shrinks the slice by moving its end backward by n characters. */
|
||||
void
|
||||
removeSuffix(std::size_t n)
|
||||
{
|
||||
@@ -164,17 +147,16 @@ public:
|
||||
return data_ + size_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a "sub slice" of given length starting at the given position
|
||||
*
|
||||
* Note that the subslice encompasses the range [pos, pos + rCount),
|
||||
* where rCount is the smaller of count and size() - pos.
|
||||
*
|
||||
* @param pos position of the first character
|
||||
* @count requested length
|
||||
*
|
||||
* @return The requested subslice, if the request is valid.
|
||||
* @throws std::out_of_range if pos > size()
|
||||
/** Return a "sub slice" of given length starting at the given position
|
||||
|
||||
Note that the subslice encompasses the range [pos, pos + rCount),
|
||||
where rCount is the smaller of count and size() - pos.
|
||||
|
||||
@param pos position of the first character
|
||||
@count requested length
|
||||
|
||||
@returns The requested subslice, if the request is valid.
|
||||
@throws std::out_of_range if pos > size()
|
||||
*/
|
||||
[[nodiscard]] Slice
|
||||
substr(std::size_t pos, std::size_t count = std::numeric_limits<std::size_t>::max()) const
|
||||
|
||||
@@ -17,16 +17,15 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* Format arbitrary binary data as an SQLite "blob literal".
|
||||
*
|
||||
* In SQLite, blob literals must be encoded when used in a query. Per
|
||||
* https://sqlite.org/lang_expr.html#literal_values_constants_ they are
|
||||
* encoded as string literals containing hexadecimal data and preceded
|
||||
* by a single 'X' character.
|
||||
*
|
||||
* @param blob An arbitrary blob of binary data
|
||||
* @return The input, encoded as a blob literal.
|
||||
/** Format arbitrary binary data as an SQLite "blob literal".
|
||||
|
||||
In SQLite, blob literals must be encoded when used in a query. Per
|
||||
https://sqlite.org/lang_expr.html#literal_values_constants_ they are
|
||||
encoded as string literals containing hexadecimal data and preceded
|
||||
by a single 'X' character.
|
||||
|
||||
@param blob An arbitrary blob of binary data
|
||||
@return The input, encoded as a blob literal.
|
||||
*/
|
||||
std::string
|
||||
sqlBlobLiteral(Blob const& blob);
|
||||
@@ -131,12 +130,11 @@ trimWhitespace(std::string str);
|
||||
std::optional<std::uint64_t>
|
||||
toUInt64(std::string const& s);
|
||||
|
||||
/**
|
||||
* Determines if the given string looks like a TOML-file hosting domain.
|
||||
*
|
||||
* Do not use this function to determine if a particular string is a valid
|
||||
* domain, as this function may reject domains that are otherwise valid and
|
||||
* doesn't check whether the TLD is valid.
|
||||
/** Determines if the given string looks like a TOML-file hosting domain.
|
||||
|
||||
Do not use this function to determine if a particular string is a valid
|
||||
domain, as this function may reject domains that are otherwise valid and
|
||||
doesn't check whether the TLD is valid.
|
||||
*/
|
||||
bool
|
||||
isProperlyFormedTomlDomain(std::string_view domain);
|
||||
|
||||
@@ -41,19 +41,18 @@ struct ReplaceDynamically;
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* Map/cache combination.
|
||||
* This class implements a cache and a map. The cache keeps objects alive
|
||||
* in the map. The map allows multiple code paths that reference objects
|
||||
* with the same tag to get the same actual object.
|
||||
*
|
||||
* So long as data is in the cache, it will stay in memory.
|
||||
* If it stays in memory even after it is ejected from the cache,
|
||||
* the map will track it.
|
||||
*
|
||||
* @note Callers must not modify data objects that are stored in the cache
|
||||
* unless they hold their own lock over all cache operations.
|
||||
*/
|
||||
/** Map/cache combination.
|
||||
This class implements a cache and a map. The cache keeps objects alive
|
||||
in the map. The map allows multiple code paths that reference objects
|
||||
with the same tag to get the same actual object.
|
||||
|
||||
So long as data is in the cache, it will stay in memory.
|
||||
If it stays in memory even after it is ejected from the cache,
|
||||
the map will track it.
|
||||
|
||||
@note Callers must not modify data objects that are stored in the cache
|
||||
unless they hold their own lock over all cache operations.
|
||||
*/
|
||||
template <
|
||||
class Key,
|
||||
class T,
|
||||
@@ -83,15 +82,11 @@ public:
|
||||
beast::insight::Collector::ptr const& collector = beast::insight::NullCollector::make());
|
||||
|
||||
public:
|
||||
/**
|
||||
* Return the clock associated with the cache.
|
||||
*/
|
||||
/** Return the clock associated with the cache. */
|
||||
clock_type&
|
||||
clock();
|
||||
|
||||
/**
|
||||
* Returns the number of items in the container.
|
||||
*/
|
||||
/** Returns the number of items in the container. */
|
||||
std::size_t
|
||||
size() const;
|
||||
|
||||
@@ -110,10 +105,9 @@ public:
|
||||
void
|
||||
reset();
|
||||
|
||||
/**
|
||||
* Refresh the last access time on a key if present.
|
||||
* @return `true` If the key was found.
|
||||
*/
|
||||
/** Refresh the last access time on a key if present.
|
||||
@return `true` If the key was found.
|
||||
*/
|
||||
template <class KeyComparable>
|
||||
bool
|
||||
touchIfExists(KeyComparable const& key);
|
||||
@@ -136,15 +130,14 @@ private:
|
||||
SharedPointerType const&,
|
||||
SharedPointerType&>;
|
||||
|
||||
/**
|
||||
* Shared implementation of the canonicalize family.
|
||||
*
|
||||
* `policy` selects how a collision is resolved when `key` already exists:
|
||||
* detail::ReplaceCached, detail::ReplaceClient or
|
||||
* detail::ReplaceDynamically. For ReplaceDynamically `replaceCallback` is
|
||||
* invoked with the existing strong pointer and returns whether to replace
|
||||
* the cached value with `data`; for the tag policies it is unused.
|
||||
*/
|
||||
/** Shared implementation of the canonicalize family.
|
||||
|
||||
`policy` selects how a collision is resolved when `key` already exists:
|
||||
detail::ReplaceCached, detail::ReplaceClient or
|
||||
detail::ReplaceDynamically. For ReplaceDynamically `replaceCallback` is
|
||||
invoked with the existing strong pointer and returns whether to replace
|
||||
the cached value with `data`; for the tag policies it is unused.
|
||||
*/
|
||||
template <class Policy, class Callback = std::nullptr_t>
|
||||
bool
|
||||
canonicalizeImpl(
|
||||
@@ -154,73 +147,69 @@ private:
|
||||
Callback&& replaceCallback = nullptr);
|
||||
|
||||
public:
|
||||
/**
|
||||
* Replace aliased objects with originals.
|
||||
*
|
||||
* Due to concurrency it is possible for two separate objects with
|
||||
* the same content and referring to the same unique "thing" to exist.
|
||||
* This routine eliminates the duplicate and performs a replacement
|
||||
* on the callers shared pointer if needed.
|
||||
*
|
||||
* `replaceCallback` is a callable taking the existing strong pointer and
|
||||
* returning whether to replace the cached value with `data` (true) or to
|
||||
* keep the cached value and write it back into `data` (false). Because the
|
||||
* write-back case mutates `data`, `data` must be writable.
|
||||
*
|
||||
* @param key The key corresponding to the object
|
||||
* @param data A shared pointer to the data corresponding to the object.
|
||||
* @param replaceCallback A callable (existing strong pointer -> bool).
|
||||
*
|
||||
* @return `true` if an existing live entry was found and used; `false` if a new entry was
|
||||
* inserted or an expired tracked entry was re-cached.
|
||||
*/
|
||||
/** Replace aliased objects with originals.
|
||||
|
||||
Due to concurrency it is possible for two separate objects with
|
||||
the same content and referring to the same unique "thing" to exist.
|
||||
This routine eliminates the duplicate and performs a replacement
|
||||
on the callers shared pointer if needed.
|
||||
|
||||
`replaceCallback` is a callable taking the existing strong pointer and
|
||||
returning whether to replace the cached value with `data` (true) or to
|
||||
keep the cached value and write it back into `data` (false). Because the
|
||||
write-back case mutates `data`, `data` must be writable.
|
||||
|
||||
@param key The key corresponding to the object
|
||||
@param data A shared pointer to the data corresponding to the object.
|
||||
@param replaceCallback A callable (existing strong pointer -> bool).
|
||||
|
||||
@return `true` if an existing live entry was found and used; `false` if a new entry was
|
||||
inserted or an expired tracked entry was re-cached.
|
||||
**/
|
||||
template <class Callback>
|
||||
bool
|
||||
canonicalize(key_type const& key, SharedPointerType& data, Callback&& replaceCallback);
|
||||
|
||||
/**
|
||||
* Insert/update the canonical entry for `key`, always replacing the
|
||||
* cached value with `data`.
|
||||
*
|
||||
* If an entry already exists for `key`, the cached value is unconditionally
|
||||
* replaced with `data`; otherwise `data` is inserted. `data` is never
|
||||
* written back, so it may be const.
|
||||
*
|
||||
* @param key The key corresponding to the object.
|
||||
* @param data A shared pointer to the data corresponding to the object.
|
||||
*
|
||||
* @return `true` if an existing live entry was found and used; `false` if a new entry was
|
||||
* inserted or an expired tracked entry was re-cached.
|
||||
*/
|
||||
/** Insert/update the canonical entry for `key`, always replacing the
|
||||
cached value with `data`.
|
||||
|
||||
If an entry already exists for `key`, the cached value is unconditionally
|
||||
replaced with `data`; otherwise `data` is inserted. `data` is never
|
||||
written back, so it may be const.
|
||||
|
||||
@param key The key corresponding to the object.
|
||||
@param data A shared pointer to the data corresponding to the object.
|
||||
|
||||
@return `true` if an existing live entry was found and used; `false` if a new entry was
|
||||
inserted or an expired tracked entry was re-cached.
|
||||
**/
|
||||
bool
|
||||
canonicalizeReplaceCache(key_type const& key, SharedPointerType const& data);
|
||||
|
||||
/**
|
||||
* Insert the canonical entry for `key`, keeping any existing cached value.
|
||||
*
|
||||
* If an entry already exists for `key`, the cached value is kept and
|
||||
* written back into `data` so the caller ends up with the canonical
|
||||
* object; otherwise `data` is inserted. Because `data` may be overwritten
|
||||
* it must be writable.
|
||||
*
|
||||
* @param key The key corresponding to the object.
|
||||
* @param data A shared pointer to the data corresponding to the object;
|
||||
* updated to the canonical value when one already exists.
|
||||
*
|
||||
* @return `true` if an existing live entry was found and used; `false` if a new entry was
|
||||
* inserted or an expired tracked entry was re-cached.
|
||||
*/
|
||||
/** Insert the canonical entry for `key`, keeping any existing cached value.
|
||||
|
||||
If an entry already exists for `key`, the cached value is kept and
|
||||
written back into `data` so the caller ends up with the canonical
|
||||
object; otherwise `data` is inserted. Because `data` may be overwritten
|
||||
it must be writable.
|
||||
|
||||
@param key The key corresponding to the object.
|
||||
@param data A shared pointer to the data corresponding to the object;
|
||||
updated to the canonical value when one already exists.
|
||||
|
||||
@return `true` if an existing live entry was found and used; `false` if a new entry was
|
||||
inserted or an expired tracked entry was re-cached.
|
||||
**/
|
||||
bool
|
||||
canonicalizeReplaceClient(key_type const& key, SharedPointerType& data);
|
||||
|
||||
SharedPointerType
|
||||
fetch(key_type const& key);
|
||||
|
||||
/**
|
||||
* Insert the element into the container.
|
||||
* If the key already exists, nothing happens.
|
||||
* @return `true` If the element was inserted
|
||||
*/
|
||||
/** Insert the element into the container.
|
||||
If the key already exists, nothing happens.
|
||||
@return `true` If the element was inserted
|
||||
*/
|
||||
template <class ReturnType = bool>
|
||||
auto
|
||||
insert(key_type const& key, T const& value) -> ReturnType
|
||||
@@ -246,18 +235,15 @@ public:
|
||||
getKeys() const;
|
||||
|
||||
// CachedSLEs functions.
|
||||
/**
|
||||
* Returns the fraction of cache hits.
|
||||
*/
|
||||
/** Returns the fraction of cache hits. */
|
||||
double
|
||||
rate() const;
|
||||
|
||||
/**
|
||||
* Fetch an item from the cache.
|
||||
* If the digest was not found, Handler
|
||||
* will be called with this signature:
|
||||
* SLE::const_pointer(void)
|
||||
*/
|
||||
/** Fetch an item from the cache.
|
||||
If the digest was not found, Handler
|
||||
will be called with this signature:
|
||||
SLE::const_pointer(void)
|
||||
*/
|
||||
template <class Handler>
|
||||
SharedPointerType
|
||||
fetch(key_type const& digest, Handler const& h);
|
||||
|
||||
@@ -5,11 +5,10 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* to_string() generalizes std::to_string to handle bools, chars, and strings.
|
||||
*
|
||||
* It's also possible to provide implementation of to_string for a class
|
||||
* which needs a string implementation.
|
||||
/** to_string() generalizes std::to_string to handle bools, chars, and strings.
|
||||
|
||||
It's also possible to provide implementation of to_string for a class
|
||||
which needs a string implementation.
|
||||
*/
|
||||
|
||||
template <class T>
|
||||
|
||||
@@ -7,13 +7,12 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* Tracks program uptime to seconds precision.
|
||||
*
|
||||
* The timer caches the current time as a performance optimization.
|
||||
* This allows clients to query the current time thousands of times
|
||||
* per second.
|
||||
*/
|
||||
/** Tracks program uptime to seconds precision.
|
||||
|
||||
The timer caches the current time as a performance optimization.
|
||||
This allows clients to query the current time thousands of times
|
||||
per second.
|
||||
*/
|
||||
|
||||
class UptimeClock
|
||||
{
|
||||
|
||||
@@ -63,19 +63,18 @@ struct AlwaysFalseT : std::bool_constant<false>
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* Integers of any length that is a multiple of 32-bits
|
||||
*
|
||||
* @note This class stores its values internally in big-endian
|
||||
* form and that internal representation is part of the
|
||||
* binary protocol of the XRP Ledger and cannot be changed
|
||||
* arbitrarily without causing breakage.
|
||||
*
|
||||
* @tparam Bits The number of bits this integer should have; must
|
||||
* be at least 64 and a multiple of 32.
|
||||
* @tparam Tag An arbitrary type that functions as a tag and allows
|
||||
* the instantiation of "distinct" types that the same
|
||||
* number of bits.
|
||||
/** Integers of any length that is a multiple of 32-bits
|
||||
|
||||
@note This class stores its values internally in big-endian
|
||||
form and that internal representation is part of the
|
||||
binary protocol of the XRP Ledger and cannot be changed
|
||||
arbitrarily without causing breakage.
|
||||
|
||||
@tparam Bits The number of bits this integer should have; must
|
||||
be at least 64 and a multiple of 32.
|
||||
@tparam Tag An arbitrary type that functions as a tag and allows
|
||||
the instantiation of "distinct" types that the same
|
||||
number of bits.
|
||||
*/
|
||||
template <std::size_t Bits, class Tag = void>
|
||||
class BaseUInt
|
||||
@@ -155,23 +154,21 @@ public:
|
||||
return data() + kBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Value hashing function.
|
||||
* The seed prevents crafted inputs from causing degenerate parent
|
||||
* containers.
|
||||
*/
|
||||
/** Value hashing function.
|
||||
The seed prevents crafted inputs from causing degenerate parent
|
||||
containers.
|
||||
*/
|
||||
using hasher = HardenedHash<>;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
private:
|
||||
/**
|
||||
* Construct from a raw pointer.
|
||||
* The buffer pointed to by `data` must be at least Bits/8 bytes.
|
||||
*
|
||||
* @note the structure is used to disambiguate this from the std::uint64_t
|
||||
* constructor: something like base_uint(0) is ambiguous.
|
||||
*/
|
||||
/** Construct from a raw pointer.
|
||||
The buffer pointed to by `data` must be at least Bits/8 bytes.
|
||||
|
||||
@note the structure is used to disambiguate this from the std::uint64_t
|
||||
constructor: something like base_uint(0) is ambiguous.
|
||||
*/
|
||||
// NIKB TODO Remove the need for this constructor.
|
||||
struct VoidHelper
|
||||
{
|
||||
@@ -506,14 +503,13 @@ public:
|
||||
h(a.data_.data(), sizeof(a.data_));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a hex string into a base_uint
|
||||
*
|
||||
* The input must be precisely `2 * bytes` hexadecimal characters
|
||||
* long, with one exception: the value '0'.
|
||||
*
|
||||
* @param sv A null-terminated string of hexadecimal characters
|
||||
* @return true if the input was parsed properly; false otherwise.
|
||||
/** Parse a hex string into a base_uint
|
||||
|
||||
The input must be precisely `2 * bytes` hexadecimal characters
|
||||
long, with one exception: the value '0'.
|
||||
|
||||
@param sv A null-terminated string of hexadecimal characters
|
||||
@return true if the input was parsed properly; false otherwise.
|
||||
*/
|
||||
[[nodiscard]] constexpr bool
|
||||
parseHex(std::string_view sv)
|
||||
@@ -599,7 +595,7 @@ template <std::size_t Bits, typename Tag>
|
||||
[[nodiscard]] constexpr bool
|
||||
operator==(BaseUInt<Bits, Tag> const& lhs, BaseUInt<Bits, Tag> const& rhs)
|
||||
{
|
||||
return (lhs <=> rhs) == 0; // NOLINT(modernize-use-nullptr)
|
||||
return (lhs <=> rhs) == 0;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -21,16 +21,15 @@ using days =
|
||||
|
||||
using weeks = std::chrono::duration<int, std::ratio_multiply<days::period, std::ratio<7>>>;
|
||||
|
||||
/**
|
||||
* Clock for measuring the network time.
|
||||
*
|
||||
* The epoch is January 1, 2000
|
||||
*
|
||||
* epoch_offset
|
||||
* = date(2000-01-01) - date(1970-0-01)
|
||||
* = days(10957)
|
||||
* = seconds(946684800)
|
||||
*/
|
||||
/** Clock for measuring the network time.
|
||||
|
||||
The epoch is January 1, 2000
|
||||
|
||||
epoch_offset
|
||||
= date(2000-01-01) - date(1970-0-01)
|
||||
= days(10957)
|
||||
= seconds(946684800)
|
||||
*/
|
||||
|
||||
static constexpr std::chrono::seconds kEpochOffset =
|
||||
date::sys_days{date::year{2000} / 1 / 1} - date::sys_days{date::year{1970} / 1 / 1};
|
||||
@@ -82,21 +81,16 @@ toStringIso(NetClock::time_point tp)
|
||||
return toStringIso(date::sys_time<NetClock::duration>{tp.time_since_epoch() + kEpochOffset});
|
||||
}
|
||||
|
||||
/**
|
||||
* A clock for measuring elapsed time.
|
||||
*
|
||||
* The epoch is unspecified.
|
||||
*/
|
||||
/** A clock for measuring elapsed time.
|
||||
|
||||
The epoch is unspecified.
|
||||
*/
|
||||
using Stopwatch = beast::AbstractClock<std::chrono::steady_clock>;
|
||||
|
||||
/**
|
||||
* A manual Stopwatch for unit tests.
|
||||
*/
|
||||
/** A manual Stopwatch for unit tests. */
|
||||
using TestStopwatch = beast::ManualClock<std::chrono::steady_clock>;
|
||||
|
||||
/**
|
||||
* Returns an instance of a wall clock.
|
||||
*/
|
||||
/** Returns an instance of a wall clock. */
|
||||
inline Stopwatch&
|
||||
stopwatch()
|
||||
{
|
||||
|
||||
@@ -15,23 +15,20 @@ namespace xrpl {
|
||||
preconditions, postconditions, and invariants.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generates and logs a call stack
|
||||
*/
|
||||
/** Generates and logs a call stack */
|
||||
void
|
||||
logThrow(std::string const& title);
|
||||
|
||||
/**
|
||||
* Rethrow the exception currently being handled.
|
||||
*
|
||||
* When called from within a catch block, it will pass
|
||||
* control to the next matching exception handler, if any.
|
||||
* Otherwise, std::terminate will be called.
|
||||
*
|
||||
* ASAN can't handle sudden jumps in control flow very well. This
|
||||
* function is marked as XRPL_NO_SANITIZE_ADDRESS to prevent it from
|
||||
* triggering false positives, since it throws.
|
||||
*/
|
||||
/** Rethrow the exception currently being handled.
|
||||
|
||||
When called from within a catch block, it will pass
|
||||
control to the next matching exception handler, if any.
|
||||
Otherwise, std::terminate will be called.
|
||||
|
||||
ASAN can't handle sudden jumps in control flow very well. This
|
||||
function is marked as XRPL_NO_SANITIZE_ADDRESS to prevent it from
|
||||
triggering false positives, since it throws.
|
||||
*/
|
||||
[[noreturn]] XRPL_NO_SANITIZE_ADDRESS inline void
|
||||
rethrow()
|
||||
{
|
||||
@@ -59,9 +56,7 @@ Throw(Args&&... args)
|
||||
throw std::move(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when faulty logic causes a broken invariant.
|
||||
*/
|
||||
/** Called when faulty logic causes a broken invariant. */
|
||||
[[noreturn]] void
|
||||
logicError(std::string const& how) noexcept;
|
||||
|
||||
|
||||
@@ -39,33 +39,33 @@ makeSeedPair() noexcept
|
||||
|
||||
/**
|
||||
* Seed functor once per construction
|
||||
*
|
||||
* A std compatible hash adapter that resists adversarial inputs.
|
||||
* For this to work, T must implement in its own namespace:
|
||||
*
|
||||
* @code
|
||||
*
|
||||
* template <class Hasher>
|
||||
* void
|
||||
* hash_append (Hasher& h, T const& t) noexcept
|
||||
* {
|
||||
* // hash_append each base and member that should
|
||||
* // participate in forming the hash
|
||||
* using beast::hash_append;
|
||||
* hash_append (h, static_cast<T::base1 const&>(t));
|
||||
* hash_append (h, static_cast<T::base2 const&>(t));
|
||||
* // ...
|
||||
* hash_append (h, t.member1);
|
||||
* hash_append (h, t.member2);
|
||||
* // ...
|
||||
* }
|
||||
*
|
||||
* @endcode
|
||||
*
|
||||
* Do not use any version of Murmur or CityHash for the Hasher
|
||||
* template parameter (the hashing algorithm). For details
|
||||
* see https://131002.net/siphash/#at
|
||||
*/
|
||||
|
||||
A std compatible hash adapter that resists adversarial inputs.
|
||||
For this to work, T must implement in its own namespace:
|
||||
|
||||
@code
|
||||
|
||||
template <class Hasher>
|
||||
void
|
||||
hash_append (Hasher& h, T const& t) noexcept
|
||||
{
|
||||
// hash_append each base and member that should
|
||||
// participate in forming the hash
|
||||
using beast::hash_append;
|
||||
hash_append (h, static_cast<T::base1 const&>(t));
|
||||
hash_append (h, static_cast<T::base2 const&>(t));
|
||||
// ...
|
||||
hash_append (h, t.member1);
|
||||
hash_append (h, t.member2);
|
||||
// ...
|
||||
}
|
||||
|
||||
@endcode
|
||||
|
||||
Do not use any version of Murmur or CityHash for the Hasher
|
||||
template parameter (the hashing algorithm). For details
|
||||
see https://131002.net/siphash/#at
|
||||
*/
|
||||
|
||||
template <class HashAlgorithm = beast::Xxhasher>
|
||||
class HardenedHash
|
||||
|
||||
@@ -7,15 +7,11 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* Create a self-signed SSL context that allows anonymous Diffie Hellman.
|
||||
*/
|
||||
/** Create a self-signed SSL context that allows anonymous Diffie Hellman. */
|
||||
std::shared_ptr<boost::asio::ssl::context>
|
||||
makeSslContext(std::string const& cipherList);
|
||||
|
||||
/**
|
||||
* Create an authenticated SSL context using the specified files.
|
||||
*/
|
||||
/** Create an authenticated SSL context using the specified files. */
|
||||
std::shared_ptr<boost::asio::ssl::context>
|
||||
makeSslContextAuthed(
|
||||
std::string const& keyFile,
|
||||
|
||||
@@ -7,16 +7,16 @@
|
||||
namespace xrpl {
|
||||
constexpr auto kMuldivMax = std::numeric_limits<std::uint64_t>::max();
|
||||
|
||||
/**
|
||||
* Return value*mul/div accurately.
|
||||
*
|
||||
* Computes the result of the multiplication and division in
|
||||
* a single step, avoiding overflow and retaining precision.
|
||||
*
|
||||
* @throws None
|
||||
* @return `std::nullopt` if the calculation overflows. Otherwise,
|
||||
* `value * mul / div`.
|
||||
*/
|
||||
/** Return value*mul/div accurately.
|
||||
Computes the result of the multiplication and division in
|
||||
a single step, avoiding overflow and retaining precision.
|
||||
Throws:
|
||||
None
|
||||
Returns:
|
||||
`std::optional`:
|
||||
`std::nullopt` if the calculation overflows. Otherwise, `value * mul
|
||||
/ div`.
|
||||
*/
|
||||
std::optional<std::uint64_t>
|
||||
mulDiv(std::uint64_t value, std::uint64_t mul, std::uint64_t div);
|
||||
|
||||
|
||||
@@ -33,17 +33,16 @@ template <class Engine, class Result = typename Engine::result_type>
|
||||
using is_engine = std::is_invocable_r<Result, Engine>;
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* Return the default random engine.
|
||||
*
|
||||
* This engine is guaranteed to be deterministic, but by
|
||||
* default will be randomly seeded. It is NOT cryptographically
|
||||
* secure and MUST NOT be used to generate randomness that
|
||||
* will be used for keys, secure cookies, IVs, padding, etc.
|
||||
*
|
||||
* Each thread gets its own instance of the engine which
|
||||
* will be randomly seeded.
|
||||
*/
|
||||
/** Return the default random engine.
|
||||
|
||||
This engine is guaranteed to be deterministic, but by
|
||||
default will be randomly seeded. It is NOT cryptographically
|
||||
secure and MUST NOT be used to generate randomness that
|
||||
will be used for keys, secure cookies, IVs, padding, etc.
|
||||
|
||||
Each thread gets its own instance of the engine which
|
||||
will be randomly seeded.
|
||||
*/
|
||||
inline beast::xor_shift_engine&
|
||||
defaultPrng()
|
||||
{
|
||||
@@ -71,26 +70,25 @@ defaultPrng()
|
||||
return kEngine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a uniformly distributed random integer.
|
||||
*
|
||||
* @param min The smallest value to return. If not specified
|
||||
* the value defaults to 0.
|
||||
* @param max The largest value to return. If not specified
|
||||
* the value defaults to the largest value that
|
||||
* can be represented.
|
||||
*
|
||||
* The randomness is generated by the specified engine (or
|
||||
* the default engine if one is not specified). The result
|
||||
* is cryptographically secure only when the engine passed
|
||||
* into the function is cryptographically secure.
|
||||
*
|
||||
* @note The range is always a closed interval, so calling
|
||||
* rand_int(-5, 15) can return any integer in the
|
||||
* closed interval [-5, 15]; similarly, calling
|
||||
* rand_int(7) can return any integer in the closed
|
||||
* interval [0, 7].
|
||||
*/
|
||||
/** Return a uniformly distributed random integer.
|
||||
|
||||
@param min The smallest value to return. If not specified
|
||||
the value defaults to 0.
|
||||
@param max The largest value to return. If not specified
|
||||
the value defaults to the largest value that
|
||||
can be represented.
|
||||
|
||||
The randomness is generated by the specified engine (or
|
||||
the default engine if one is not specified). The result
|
||||
is cryptographically secure only when the engine passed
|
||||
into the function is cryptographically secure.
|
||||
|
||||
@note The range is always a closed interval, so calling
|
||||
rand_int(-5, 15) can return any integer in the
|
||||
closed interval [-5, 15]; similarly, calling
|
||||
rand_int(7) can return any integer in the closed
|
||||
interval [0, 7].
|
||||
*/
|
||||
/** @{ */
|
||||
template <class Engine, class Integral>
|
||||
Integral
|
||||
@@ -146,9 +144,7 @@ randInt()
|
||||
}
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Return a random byte
|
||||
*/
|
||||
/** Return a random byte */
|
||||
/** @{ */
|
||||
template <class Byte, class Engine>
|
||||
Byte
|
||||
@@ -170,9 +166,7 @@ randByte()
|
||||
}
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Return a random boolean value
|
||||
*/
|
||||
/** Return a random boolean value */
|
||||
/** @{ */
|
||||
template <class Engine>
|
||||
inline bool
|
||||
|
||||
@@ -156,41 +156,41 @@ template <class EF>
|
||||
ScopeSuccess(EF) -> ScopeSuccess<EF>;
|
||||
|
||||
/**
|
||||
* Automatically unlocks and re-locks a unique_lock object.
|
||||
*
|
||||
* This is the reverse of a std::unique_lock object - instead of locking the
|
||||
* mutex for the lifetime of this object, it unlocks it.
|
||||
*
|
||||
* Make sure you don't try to unlock mutexes that aren't actually locked!
|
||||
*
|
||||
* This is essentially a less-versatile boost::reverse_lock.
|
||||
*
|
||||
* e.g. @code
|
||||
*
|
||||
* std::mutex mut;
|
||||
*
|
||||
* for (;;)
|
||||
* {
|
||||
* std::unique_lock myScopedLock{mut};
|
||||
* // mut is now locked
|
||||
*
|
||||
* ... do some stuff with it locked ..
|
||||
*
|
||||
* while (xyz)
|
||||
* {
|
||||
* ... do some stuff with it locked ..
|
||||
*
|
||||
* scope_unlock unlocker{myScopedLock};
|
||||
*
|
||||
* // mut is now unlocked for the remainder of this block,
|
||||
* // and re-locked at the end.
|
||||
*
|
||||
* ...do some stuff with it unlocked ...
|
||||
* } // mut gets locked here.
|
||||
*
|
||||
* } // mut gets unlocked here
|
||||
* @endcode
|
||||
*/
|
||||
Automatically unlocks and re-locks a unique_lock object.
|
||||
|
||||
This is the reverse of a std::unique_lock object - instead of locking the
|
||||
mutex for the lifetime of this object, it unlocks it.
|
||||
|
||||
Make sure you don't try to unlock mutexes that aren't actually locked!
|
||||
|
||||
This is essentially a less-versatile boost::reverse_lock.
|
||||
|
||||
e.g. @code
|
||||
|
||||
std::mutex mut;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
std::unique_lock myScopedLock{mut};
|
||||
// mut is now locked
|
||||
|
||||
... do some stuff with it locked ..
|
||||
|
||||
while (xyz)
|
||||
{
|
||||
... do some stuff with it locked ..
|
||||
|
||||
scope_unlock unlocker{myScopedLock};
|
||||
|
||||
// mut is now unlocked for the remainder of this block,
|
||||
// and re-locked at the end.
|
||||
|
||||
...do some stuff with it unlocked ...
|
||||
} // mut gets locked here.
|
||||
|
||||
} // mut gets unlocked here
|
||||
@endcode
|
||||
*/
|
||||
|
||||
template <class Mutex>
|
||||
class ScopeUnlock
|
||||
|
||||
@@ -15,16 +15,15 @@
|
||||
namespace xrpl {
|
||||
|
||||
namespace detail {
|
||||
/**
|
||||
* Inform the processor that we are in a tight spin-wait loop.
|
||||
*
|
||||
* Spinlocks caught in tight loops can result in the processor's pipeline
|
||||
* filling up with comparison operations, resulting in a misprediction at
|
||||
* the time the lock is finally acquired, necessitating pipeline flushing
|
||||
* which is ridiculously expensive and results in very high latency.
|
||||
*
|
||||
* This function instructs the processor to "pause" for some architecture
|
||||
* specific amount of time, to prevent this.
|
||||
/** Inform the processor that we are in a tight spin-wait loop.
|
||||
|
||||
Spinlocks caught in tight loops can result in the processor's pipeline
|
||||
filling up with comparison operations, resulting in a misprediction at
|
||||
the time the lock is finally acquired, necessitating pipeline flushing
|
||||
which is ridiculously expensive and results in very high latency.
|
||||
|
||||
This function instructs the processor to "pause" for some architecture
|
||||
specific amount of time, to prevent this.
|
||||
*/
|
||||
inline void
|
||||
spinPause() noexcept
|
||||
@@ -39,39 +38,37 @@ spinPause() noexcept
|
||||
} // namespace detail
|
||||
|
||||
/** @{ */
|
||||
/**
|
||||
* Classes to handle arrays of spinlocks packed into a single atomic integer:
|
||||
*
|
||||
* Packed spinlocks allow for tremendously space-efficient lock-sharding
|
||||
* but they come at a cost.
|
||||
*
|
||||
* First, the implementation is necessarily low-level and uses advanced
|
||||
* features like memory ordering and highly platform-specific tricks to
|
||||
* maximize performance. This imposes a significant and ongoing cost to
|
||||
* developers.
|
||||
*
|
||||
* Second, and perhaps most important, is that the packing of multiple
|
||||
* locks into a single integer which, albeit space-efficient, also has
|
||||
* performance implications stemming from data dependencies, increased
|
||||
* cache-coherency traffic between processors and heavier loads on the
|
||||
* processor's load/store units.
|
||||
*
|
||||
* To be sure, these locks can have advantages but they are definitely
|
||||
* not general purpose locks and should not be thought of or used that
|
||||
* way. The use cases for them are likely few and far between; without
|
||||
* a compelling reason to use them, backed by profiling data, it might
|
||||
* be best to use one of the standard locking primitives instead. Note
|
||||
* that in most common platforms, `std::mutex` is so heavily optimized
|
||||
* that it can, usually, outperform spinlocks.
|
||||
*
|
||||
* @tparam T An unsigned integral type (e.g. std::uint16_t)
|
||||
/** Classes to handle arrays of spinlocks packed into a single atomic integer:
|
||||
|
||||
Packed spinlocks allow for tremendously space-efficient lock-sharding
|
||||
but they come at a cost.
|
||||
|
||||
First, the implementation is necessarily low-level and uses advanced
|
||||
features like memory ordering and highly platform-specific tricks to
|
||||
maximize performance. This imposes a significant and ongoing cost to
|
||||
developers.
|
||||
|
||||
Second, and perhaps most important, is that the packing of multiple
|
||||
locks into a single integer which, albeit space-efficient, also has
|
||||
performance implications stemming from data dependencies, increased
|
||||
cache-coherency traffic between processors and heavier loads on the
|
||||
processor's load/store units.
|
||||
|
||||
To be sure, these locks can have advantages but they are definitely
|
||||
not general purpose locks and should not be thought of or used that
|
||||
way. The use cases for them are likely few and far between; without
|
||||
a compelling reason to use them, backed by profiling data, it might
|
||||
be best to use one of the standard locking primitives instead. Note
|
||||
that in most common platforms, `std::mutex` is so heavily optimized
|
||||
that it can, usually, outperform spinlocks.
|
||||
|
||||
@tparam T An unsigned integral type (e.g. std::uint16_t)
|
||||
*/
|
||||
|
||||
/**
|
||||
* A class that grabs a single packed spinlock from an atomic integer.
|
||||
*
|
||||
* This class meets the requirements of Lockable:
|
||||
* https://en.cppreference.com/w/cpp/named_req/Lockable
|
||||
/** A class that grabs a single packed spinlock from an atomic integer.
|
||||
|
||||
This class meets the requirements of Lockable:
|
||||
https://en.cppreference.com/w/cpp/named_req/Lockable
|
||||
*/
|
||||
template <class T>
|
||||
class PackedSpinlock
|
||||
@@ -94,14 +91,13 @@ public:
|
||||
PackedSpinlock&
|
||||
operator=(PackedSpinlock const&) = delete;
|
||||
|
||||
/**
|
||||
* A single spinlock packed inside the specified atomic
|
||||
*
|
||||
* @param lock The atomic integer inside which the spinlock is packed.
|
||||
* @param index The index of the spinlock this object acquires.
|
||||
*
|
||||
* @note For performance reasons, you should strive to have `lock` be
|
||||
* on a cacheline by itself.
|
||||
/** A single spinlock packed inside the specified atomic
|
||||
|
||||
@param lock The atomic integer inside which the spinlock is packed.
|
||||
@param index The index of the spinlock this object acquires.
|
||||
|
||||
@note For performance reasons, you should strive to have `lock` be
|
||||
on a cacheline by itself.
|
||||
*/
|
||||
PackedSpinlock(std::atomic<T>& lock, int index) : bits_(lock), mask_(static_cast<T>(1) << index)
|
||||
{
|
||||
@@ -137,18 +133,17 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A spinlock implemented on top of an atomic integer.
|
||||
*
|
||||
* @note Using `packed_spinlock` and `spinlock` against the same underlying
|
||||
* atomic integer can result in `spinlock` not being able to actually
|
||||
* acquire the lock during periods of high contention, because of how
|
||||
* the two locks operate: `spinlock` will spin trying to grab all the
|
||||
* bits at once, whereas any given `packed_spinlock` will only try to
|
||||
* grab one bit at a time. Caveat emptor.
|
||||
*
|
||||
* This class meets the requirements of Lockable:
|
||||
* https://en.cppreference.com/w/cpp/named_req/Lockable
|
||||
/** A spinlock implemented on top of an atomic integer.
|
||||
|
||||
@note Using `packed_spinlock` and `spinlock` against the same underlying
|
||||
atomic integer can result in `spinlock` not being able to actually
|
||||
acquire the lock during periods of high contention, because of how
|
||||
the two locks operate: `spinlock` will spin trying to grab all the
|
||||
bits at once, whereas any given `packed_spinlock` will only try to
|
||||
grab one bit at a time. Caveat emptor.
|
||||
|
||||
This class meets the requirements of Lockable:
|
||||
https://en.cppreference.com/w/cpp/named_req/Lockable
|
||||
*/
|
||||
template <class T>
|
||||
class Spinlock
|
||||
@@ -164,13 +159,12 @@ public:
|
||||
Spinlock&
|
||||
operator=(Spinlock const&) = delete;
|
||||
|
||||
/**
|
||||
* Grabs the
|
||||
*
|
||||
* @param lock The atomic integer to spin against.
|
||||
*
|
||||
* @note For performance reasons, you should strive to have `lock` be
|
||||
* on a cacheline by itself.
|
||||
/** Grabs the
|
||||
|
||||
@param lock The atomic integer to spin against.
|
||||
|
||||
@note For performance reasons, you should strive to have `lock` be
|
||||
on a cacheline by itself.
|
||||
*/
|
||||
Spinlock(std::atomic<T>& lock) : lock_(lock)
|
||||
{
|
||||
|
||||
@@ -12,18 +12,17 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* A type-safe wrap around standard integral types
|
||||
*
|
||||
* The tag is used to implement type safety, catching mismatched types at
|
||||
* compile time. Multiple instantiations wrapping the same underlying integral
|
||||
* type are distinct types (distinguished by tag) and will not interoperate. A
|
||||
* tagged_integer supports all the usual assignment, arithmetic, comparison and
|
||||
* shifting operations defined for the underlying type
|
||||
*
|
||||
* The tag is not meant as a unit, which would require restricting the set of
|
||||
* allowed arithmetic operations.
|
||||
*/
|
||||
/** A type-safe wrap around standard integral types
|
||||
|
||||
The tag is used to implement type safety, catching mismatched types at
|
||||
compile time. Multiple instantiations wrapping the same underlying integral
|
||||
type are distinct types (distinguished by tag) and will not interoperate. A
|
||||
tagged_integer supports all the usual assignment, arithmetic, comparison and
|
||||
shifting operations defined for the underlying type
|
||||
|
||||
The tag is not meant as a unit, which would require restricting the set of
|
||||
allowed arithmetic operations.
|
||||
*/
|
||||
template <class Int, class Tag>
|
||||
class TaggedInteger : boost::totally_ordered<
|
||||
TaggedInteger<Int, Tag>,
|
||||
|
||||
@@ -14,9 +14,7 @@
|
||||
|
||||
namespace beast {
|
||||
|
||||
/**
|
||||
* Measures handler latency on an io_context queue.
|
||||
*/
|
||||
/** Measures handler latency on an io_context queue. */
|
||||
template <class Clock>
|
||||
class IOLatencyProbe
|
||||
{
|
||||
@@ -44,9 +42,7 @@ public:
|
||||
cancel(lock, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the io_context associated with the latency probe.
|
||||
*/
|
||||
/** Return the io_context associated with the latency probe. */
|
||||
/** @{ */
|
||||
boost::asio::io_context&
|
||||
getIoContext()
|
||||
@@ -61,10 +57,9 @@ public:
|
||||
}
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Cancel all pending i/o.
|
||||
* Any handlers which have already been queued will still be called.
|
||||
*/
|
||||
/** Cancel all pending i/o.
|
||||
Any handlers which have already been queued will still be called.
|
||||
*/
|
||||
/** @{ */
|
||||
void
|
||||
cancel()
|
||||
@@ -81,11 +76,10 @@ public:
|
||||
}
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Measure one sample of i/o latency.
|
||||
* Handler will be called with this signature:
|
||||
* void Handler (Duration d);
|
||||
*/
|
||||
/** Measure one sample of i/o latency.
|
||||
Handler will be called with this signature:
|
||||
void Handler (Duration d);
|
||||
*/
|
||||
template <class Handler>
|
||||
void
|
||||
sampleOne(Handler&& handler)
|
||||
@@ -97,11 +91,10 @@ public:
|
||||
ios_, SampleOp<Handler>(std::forward<Handler>(handler), Clock::now(), false, this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate continuous i/o latency sampling.
|
||||
* Handler will be called with this signature:
|
||||
* void Handler (std::chrono::milliseconds);
|
||||
*/
|
||||
/** Initiate continuous i/o latency sampling.
|
||||
Handler will be called with this signature:
|
||||
void Handler (std::chrono::milliseconds);
|
||||
*/
|
||||
template <class Handler>
|
||||
void
|
||||
sample(Handler&& handler)
|
||||
|
||||
@@ -2,35 +2,34 @@
|
||||
|
||||
namespace beast {
|
||||
|
||||
/**
|
||||
* Abstract interface to a clock.
|
||||
*
|
||||
* This makes now() a member function instead of a static member, so
|
||||
* an instance of the class can be dependency injected, facilitating
|
||||
* unit tests where time may be controlled.
|
||||
*
|
||||
* An abstract_clock inherits all the nested types of the Clock
|
||||
* template parameter.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* @code
|
||||
*
|
||||
* struct Implementation
|
||||
* {
|
||||
* using clock_type = abstract_clock <std::chrono::steady_clock>;
|
||||
* clock_type& clock_;
|
||||
* explicit Implementation (clock_type& clock)
|
||||
* : clock_(clock)
|
||||
* {
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Clock A type meeting these requirements:
|
||||
* http://en.cppreference.com/w/cpp/concept/Clock
|
||||
*/
|
||||
/** Abstract interface to a clock.
|
||||
|
||||
This makes now() a member function instead of a static member, so
|
||||
an instance of the class can be dependency injected, facilitating
|
||||
unit tests where time may be controlled.
|
||||
|
||||
An abstract_clock inherits all the nested types of the Clock
|
||||
template parameter.
|
||||
|
||||
Example:
|
||||
|
||||
@code
|
||||
|
||||
struct Implementation
|
||||
{
|
||||
using clock_type = abstract_clock <std::chrono::steady_clock>;
|
||||
clock_type& clock_;
|
||||
explicit Implementation (clock_type& clock)
|
||||
: clock_(clock)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
@endcode
|
||||
|
||||
@tparam Clock A type meeting these requirements:
|
||||
http://en.cppreference.com/w/cpp/concept/Clock
|
||||
*/
|
||||
template <class Clock>
|
||||
class AbstractClock
|
||||
{
|
||||
@@ -47,9 +46,7 @@ public:
|
||||
AbstractClock() = default;
|
||||
AbstractClock(AbstractClock const&) = default;
|
||||
|
||||
/**
|
||||
* Returns the current time.
|
||||
*/
|
||||
/** Returns the current time. */
|
||||
[[nodiscard]] virtual time_point
|
||||
now() const = 0;
|
||||
};
|
||||
@@ -77,12 +74,11 @@ struct AbstractClockWrapper : public AbstractClock<Facade>
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns a global instance of an abstract clock.
|
||||
* @tparam Facade A type meeting these requirements:
|
||||
* http://en.cppreference.com/w/cpp/concept/Clock
|
||||
* @tparam Clock The actual concrete clock to use.
|
||||
*/
|
||||
/** Returns a global instance of an abstract clock.
|
||||
@tparam Facade A type meeting these requirements:
|
||||
http://en.cppreference.com/w/cpp/concept/Clock
|
||||
@tparam Clock The actual concrete clock to use.
|
||||
*/
|
||||
template <class Facade, class Clock = Facade>
|
||||
AbstractClock<Facade>&
|
||||
getAbstractClock()
|
||||
|
||||
@@ -4,16 +4,15 @@
|
||||
|
||||
namespace beast {
|
||||
|
||||
/**
|
||||
* A clock whose minimum resolution is one second.
|
||||
*
|
||||
* The purpose of this class is to optimize the performance of the now()
|
||||
* member function call. It uses a dedicated thread that wakes up at least
|
||||
* once per second to sample the requested trivial clock.
|
||||
*
|
||||
* @tparam Clock A type meeting these requirements:
|
||||
* http://en.cppreference.com/w/cpp/concept/Clock
|
||||
*/
|
||||
/** A clock whose minimum resolution is one second.
|
||||
|
||||
The purpose of this class is to optimize the performance of the now()
|
||||
member function call. It uses a dedicated thread that wakes up at least
|
||||
once per second to sample the requested trivial clock.
|
||||
|
||||
@tparam Clock A type meeting these requirements:
|
||||
http://en.cppreference.com/w/cpp/concept/Clock
|
||||
*/
|
||||
class BasicSecondsClock
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -7,16 +7,15 @@
|
||||
|
||||
namespace beast {
|
||||
|
||||
/**
|
||||
* Manual clock implementation.
|
||||
*
|
||||
* This concrete class implements the @ref abstract_clock interface and
|
||||
* allows the time to be advanced manually, mainly for the purpose of
|
||||
* providing a clock in unit tests.
|
||||
*
|
||||
* @tparam Clock A type meeting these requirements:
|
||||
* http://en.cppreference.com/w/cpp/concept/Clock
|
||||
*/
|
||||
/** Manual clock implementation.
|
||||
|
||||
This concrete class implements the @ref abstract_clock interface and
|
||||
allows the time to be advanced manually, mainly for the purpose of
|
||||
providing a clock in unit tests.
|
||||
|
||||
@tparam Clock A type meeting these requirements:
|
||||
http://en.cppreference.com/w/cpp/concept/Clock
|
||||
*/
|
||||
template <class Clock>
|
||||
class ManualClock : public AbstractClock<Clock>
|
||||
{
|
||||
@@ -39,9 +38,7 @@ public:
|
||||
return now_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current time of the manual clock.
|
||||
*/
|
||||
/** Set the current time of the manual clock. */
|
||||
void
|
||||
set(time_point const& when)
|
||||
{
|
||||
@@ -51,9 +48,7 @@ public:
|
||||
now_ = when;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience for setting the time in seconds from epoch.
|
||||
*/
|
||||
/** Convenience for setting the time in seconds from epoch. */
|
||||
template <class Integer>
|
||||
void
|
||||
set(Integer secondsFromEpoch)
|
||||
@@ -61,9 +56,7 @@ public:
|
||||
set(time_point(duration(std::chrono::seconds(secondsFromEpoch))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Advance the clock by a duration.
|
||||
*/
|
||||
/** Advance the clock by a duration. */
|
||||
template <class Rep, class Period>
|
||||
void
|
||||
advance(std::chrono::duration<Rep, Period> const& elapsed)
|
||||
@@ -74,9 +67,7 @@ public:
|
||||
now_ += elapsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience for advancing the clock by one second.
|
||||
*/
|
||||
/** Convenience for advancing the clock by one second. */
|
||||
ManualClock&
|
||||
operator++()
|
||||
{
|
||||
|
||||
@@ -7,9 +7,7 @@
|
||||
|
||||
namespace beast {
|
||||
|
||||
/**
|
||||
* Expire aged container items past the specified age.
|
||||
*/
|
||||
/** Expire aged container items past the specified age. */
|
||||
template <class AgedContainer, class Rep, class Period>
|
||||
std::size_t
|
||||
expire(AgedContainer& c, std::chrono::duration<Rep, Period> const& age)
|
||||
|
||||
@@ -39,23 +39,22 @@ struct IsBoostReverseIterator<boost::intrusive::reverse_iterator<It>> : std::tru
|
||||
explicit IsBoostReverseIterator() = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Associative container where each element is also indexed by time.
|
||||
*
|
||||
* This container mirrors the interface of the standard library ordered
|
||||
* associative containers, with the addition that each element is associated
|
||||
* with a `when` `time_point` which is obtained from the value of the clock's
|
||||
* `now`. The function `touch` updates the time for an element to the current
|
||||
* time as reported by the clock.
|
||||
*
|
||||
* An extra set of iterator types and member functions are provided in the
|
||||
* `chronological` memberspace that allow traversal in temporal or reverse
|
||||
* temporal order. This container is useful as a building block for caches
|
||||
* whose items expire after a certain amount of time. The chronological
|
||||
* iterators allow for fully customizable expiration strategies.
|
||||
*
|
||||
* @see aged_set, aged_multiset, aged_map, aged_multimap
|
||||
*/
|
||||
/** Associative container where each element is also indexed by time.
|
||||
|
||||
This container mirrors the interface of the standard library ordered
|
||||
associative containers, with the addition that each element is associated
|
||||
with a `when` `time_point` which is obtained from the value of the clock's
|
||||
`now`. The function `touch` updates the time for an element to the current
|
||||
time as reported by the clock.
|
||||
|
||||
An extra set of iterator types and member functions are provided in the
|
||||
`chronological` memberspace that allow traversal in temporal or reverse
|
||||
temporal order. This container is useful as a building block for caches
|
||||
whose items expire after a certain amount of time. The chronological
|
||||
iterators allow for fully customizable expiration strategies.
|
||||
|
||||
@see aged_set, aged_multiset, aged_map, aged_multimap
|
||||
*/
|
||||
template <
|
||||
bool IsMulti,
|
||||
bool IsMap,
|
||||
@@ -1796,9 +1795,7 @@ swap(
|
||||
lhs.swap(rhs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expire aged container items past the specified age.
|
||||
*/
|
||||
/** Expire aged container items past the specified age. */
|
||||
template <
|
||||
bool IsMulti,
|
||||
bool IsMap,
|
||||
|
||||
@@ -43,24 +43,23 @@ TODO
|
||||
namespace beast {
|
||||
namespace detail {
|
||||
|
||||
/**
|
||||
* Associative container where each element is also indexed by time.
|
||||
*
|
||||
* This container mirrors the interface of the standard library unordered
|
||||
* associative containers, with the addition that each element is associated
|
||||
* with a `when` `time_point` which is obtained from the value of the clock's
|
||||
* `now`. The function `touch` updates the time for an element to the current
|
||||
* time as reported by the clock.
|
||||
*
|
||||
* An extra set of iterator types and member functions are provided in the
|
||||
* `chronological` memberspace that allow traversal in temporal or reverse
|
||||
* temporal order. This container is useful as a building block for caches
|
||||
* whose items expire after a certain amount of time. The chronological
|
||||
* iterators allow for fully customizable expiration strategies.
|
||||
*
|
||||
* @see aged_unordered_set, aged_unordered_multiset
|
||||
* @see aged_unordered_map, aged_unordered_multimap
|
||||
*/
|
||||
/** Associative container where each element is also indexed by time.
|
||||
|
||||
This container mirrors the interface of the standard library unordered
|
||||
associative containers, with the addition that each element is associated
|
||||
with a `when` `time_point` which is obtained from the value of the clock's
|
||||
`now`. The function `touch` updates the time for an element to the current
|
||||
time as reported by the clock.
|
||||
|
||||
An extra set of iterator types and member functions are provided in the
|
||||
`chronological` memberspace that allow traversal in temporal or reverse
|
||||
temporal order. This container is useful as a building block for caches
|
||||
whose items expire after a certain amount of time. The chronological
|
||||
iterators allow for fully customizable expiration strategies.
|
||||
|
||||
@see aged_unordered_set, aged_unordered_multiset
|
||||
@see aged_unordered_map, aged_unordered_multimap
|
||||
*/
|
||||
template <
|
||||
bool IsMulti,
|
||||
bool IsMap,
|
||||
@@ -2710,9 +2709,7 @@ swap(
|
||||
lhs.swap(rhs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expire aged container items past the specified age.
|
||||
*/
|
||||
/** Expire aged container items past the specified age. */
|
||||
template <
|
||||
bool IsMulti,
|
||||
bool IsMap,
|
||||
|
||||
@@ -12,10 +12,9 @@
|
||||
|
||||
namespace beast {
|
||||
|
||||
/**
|
||||
* Changes the name of the caller thread.
|
||||
* Different OSes may place different length or content limits on this name.
|
||||
*/
|
||||
/** Changes the name of the caller thread.
|
||||
Different OSes may place different length or content limits on this name.
|
||||
*/
|
||||
void
|
||||
setCurrentThreadName(std::string_view newThreadName);
|
||||
|
||||
@@ -25,14 +24,13 @@ setCurrentThreadName(std::string_view newThreadName);
|
||||
// Maximum number of characters is therefore 15.
|
||||
constexpr std::size_t kMaxThreadNameLength = 15;
|
||||
|
||||
/**
|
||||
* Sets the name of the caller thread with compile-time size checking.
|
||||
* @tparam N The size of the string literal including null terminator
|
||||
* @param newThreadName A string literal to set as the thread name
|
||||
*
|
||||
* This template overload enforces that thread names are at most 16 characters
|
||||
* (including null terminator) at compile time, matching Linux's limit.
|
||||
*/
|
||||
/** Sets the name of the caller thread with compile-time size checking.
|
||||
@tparam N The size of the string literal including null terminator
|
||||
@param newThreadName A string literal to set as the thread name
|
||||
|
||||
This template overload enforces that thread names are at most 16 characters
|
||||
(including null terminator) at compile time, matching Linux's limit.
|
||||
*/
|
||||
template <std::size_t N>
|
||||
void
|
||||
setCurrentThreadName(char const (&newThreadName)[N])
|
||||
@@ -43,15 +41,14 @@ setCurrentThreadName(char const (&newThreadName)[N])
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Returns the name of the caller thread.
|
||||
*
|
||||
* The name returned is the name as set by a call to setCurrentThreadName().
|
||||
* If the thread name is set by an external force, then that name change
|
||||
* will not be reported.
|
||||
*
|
||||
* If no name has ever been set, then the empty string is returned.
|
||||
*/
|
||||
/** Returns the name of the caller thread.
|
||||
|
||||
The name returned is the name as set by a call to setCurrentThreadName().
|
||||
If the thread name is set by an external force, then that name change
|
||||
will not be reported.
|
||||
|
||||
If no name has ever been set, then the empty string is returned.
|
||||
*/
|
||||
std::string
|
||||
getCurrentThreadName();
|
||||
|
||||
|
||||
@@ -163,19 +163,17 @@ struct LexicalCast<Out, char*>
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Thrown when a conversion is not possible with LexicalCast.
|
||||
* Only used in the throw variants of lexicalCast.
|
||||
*/
|
||||
/** Thrown when a conversion is not possible with LexicalCast.
|
||||
Only used in the throw variants of lexicalCast.
|
||||
*/
|
||||
struct BadLexicalCast : public std::bad_cast
|
||||
{
|
||||
explicit BadLexicalCast() = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Intelligently convert from one type to another.
|
||||
* @return `false` if there was a parsing or range error
|
||||
*/
|
||||
/** Intelligently convert from one type to another.
|
||||
@return `false` if there was a parsing or range error
|
||||
*/
|
||||
template <class Out, class In>
|
||||
bool
|
||||
lexicalCastChecked(Out& out, In in)
|
||||
@@ -183,13 +181,12 @@ lexicalCastChecked(Out& out, In in)
|
||||
return detail::LexicalCast<Out, In>()(out, in);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from one type to another, throw on error
|
||||
*
|
||||
* An exception of type BadLexicalCast is thrown if the conversion fails.
|
||||
*
|
||||
* @return The new type.
|
||||
*/
|
||||
/** Convert from one type to another, throw on error
|
||||
|
||||
An exception of type BadLexicalCast is thrown if the conversion fails.
|
||||
|
||||
@return The new type.
|
||||
*/
|
||||
template <class Out, class In>
|
||||
Out
|
||||
lexicalCastThrow(In in)
|
||||
@@ -200,12 +197,11 @@ lexicalCastThrow(In in)
|
||||
throw BadLexicalCast();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from one type to another.
|
||||
*
|
||||
* @param defaultValue The value returned if parsing fails
|
||||
* @return The new type.
|
||||
*/
|
||||
/** Convert from one type to another.
|
||||
|
||||
@param defaultValue The value returned if parsing fails
|
||||
@return The new type.
|
||||
*/
|
||||
template <class Out, class In>
|
||||
Out
|
||||
lexicalCast(In in, Out defaultValue = Out())
|
||||
|
||||
@@ -11,9 +11,7 @@ class List;
|
||||
|
||||
namespace detail {
|
||||
|
||||
/**
|
||||
* Copy `const` attribute from T to U if present.
|
||||
*/
|
||||
/** Copy `const` attribute from T to U if present. */
|
||||
/** @{ */
|
||||
template <typename T, typename U>
|
||||
struct CopyConst
|
||||
@@ -155,111 +153,110 @@ private:
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* Intrusive doubly linked list.
|
||||
*
|
||||
* This intrusive List is a container similar in operation to std::list in the
|
||||
* Standard Template Library (STL). Like all @ref intrusive containers, List
|
||||
* requires you to first derive your class from List<>::Node:
|
||||
*
|
||||
* @code
|
||||
*
|
||||
* struct Object : List <Object>::Node
|
||||
* {
|
||||
* explicit Object (int value) : value_ (value)
|
||||
* {
|
||||
* }
|
||||
*
|
||||
* int value_;
|
||||
* };
|
||||
*
|
||||
* @endcode
|
||||
*
|
||||
* Now we define the list, and add a couple of items.
|
||||
*
|
||||
* @code
|
||||
*
|
||||
* List <Object> list;
|
||||
*
|
||||
* list.push_back (* (new Object (1)));
|
||||
* list.push_back (* (new Object (2)));
|
||||
*
|
||||
* @endcode
|
||||
*
|
||||
* For compatibility with the standard containers, push_back() expects a
|
||||
* reference to the object. Unlike the standard container, however, push_back()
|
||||
* places the actual object in the list and not a copy-constructed duplicate.
|
||||
*
|
||||
* Iterating over the list follows the same idiom as the STL:
|
||||
*
|
||||
* @code
|
||||
*
|
||||
* for (List <Object>::iterator iter = list.begin(); iter != list.end; ++iter)
|
||||
* std::cout << iter->value_;
|
||||
*
|
||||
* @endcode
|
||||
*
|
||||
* You can even use BOOST_FOREACH, or range based for loops:
|
||||
*
|
||||
* @code
|
||||
*
|
||||
* BOOST_FOREACH (Object& object, list) // boost only
|
||||
* std::cout << object.value_;
|
||||
*
|
||||
* for (Object& object : list) // C++11 only
|
||||
* std::cout << object.value_;
|
||||
*
|
||||
* @endcode
|
||||
*
|
||||
* Because List is mostly STL compliant, it can be passed into STL algorithms:
|
||||
* e.g. `std::for_each()` or `std::find_first_of()`.
|
||||
*
|
||||
* In general, objects placed into a List should be dynamically allocated
|
||||
* although this cannot be enforced at compile time. Since the caller provides
|
||||
* the storage for the object, the caller is also responsible for deleting the
|
||||
* object. An object still exists after being removed from a List, until the
|
||||
* caller deletes it. This means an element can be moved from one List to
|
||||
* another with practically no overhead.
|
||||
*
|
||||
* Unlike the standard containers, an object may only exist in one list at a
|
||||
* time, unless special preparations are made. The Tag template parameter is
|
||||
* used to distinguish between different list types for the same object,
|
||||
* allowing the object to exist in more than one list simultaneously.
|
||||
*
|
||||
* For example, consider an actor system where a global list of actors is
|
||||
* maintained, so that they can each be periodically receive processing
|
||||
* time. We wish to also maintain a list of the subset of actors that require
|
||||
* a domain-dependent update. To achieve this, we declare two tags, the
|
||||
* associated list types, and the list element thusly:
|
||||
*
|
||||
* @code
|
||||
*
|
||||
* struct Actor; // Forward declaration required
|
||||
*
|
||||
* struct ProcessTag { };
|
||||
* struct UpdateTag { };
|
||||
*
|
||||
* using ProcessList = List <Actor, ProcessTag>;
|
||||
* using UpdateList = List <Actor, UpdateTag>;
|
||||
*
|
||||
* // Derive from both node types so we can be in each list at once.
|
||||
* //
|
||||
* struct Actor : ProcessList::Node, UpdateList::Node
|
||||
* {
|
||||
* bool process (); // returns true if we need an update
|
||||
* void update ();
|
||||
* };
|
||||
*
|
||||
* @endcode
|
||||
*
|
||||
* @tparam T The base type of element which the list will store
|
||||
* pointers to.
|
||||
*
|
||||
* @tparam Tag An optional unique type name used to distinguish lists and
|
||||
* nodes, when the object can exist in multiple lists simultaneously.
|
||||
*
|
||||
* @ingroup beast_core intrusive
|
||||
*/
|
||||
/** Intrusive doubly linked list.
|
||||
|
||||
This intrusive List is a container similar in operation to std::list in the
|
||||
Standard Template Library (STL). Like all @ref intrusive containers, List
|
||||
requires you to first derive your class from List<>::Node:
|
||||
|
||||
@code
|
||||
|
||||
struct Object : List <Object>::Node
|
||||
{
|
||||
explicit Object (int value) : value_ (value)
|
||||
{
|
||||
}
|
||||
|
||||
int value_;
|
||||
};
|
||||
|
||||
@endcode
|
||||
|
||||
Now we define the list, and add a couple of items.
|
||||
|
||||
@code
|
||||
|
||||
List <Object> list;
|
||||
|
||||
list.push_back (* (new Object (1)));
|
||||
list.push_back (* (new Object (2)));
|
||||
|
||||
@endcode
|
||||
|
||||
For compatibility with the standard containers, push_back() expects a
|
||||
reference to the object. Unlike the standard container, however, push_back()
|
||||
places the actual object in the list and not a copy-constructed duplicate.
|
||||
|
||||
Iterating over the list follows the same idiom as the STL:
|
||||
|
||||
@code
|
||||
|
||||
for (List <Object>::iterator iter = list.begin(); iter != list.end; ++iter)
|
||||
std::cout << iter->value_;
|
||||
|
||||
@endcode
|
||||
|
||||
You can even use BOOST_FOREACH, or range based for loops:
|
||||
|
||||
@code
|
||||
|
||||
BOOST_FOREACH (Object& object, list) // boost only
|
||||
std::cout << object.value_;
|
||||
|
||||
for (Object& object : list) // C++11 only
|
||||
std::cout << object.value_;
|
||||
|
||||
@endcode
|
||||
|
||||
Because List is mostly STL compliant, it can be passed into STL algorithms:
|
||||
e.g. `std::for_each()` or `std::find_first_of()`.
|
||||
|
||||
In general, objects placed into a List should be dynamically allocated
|
||||
although this cannot be enforced at compile time. Since the caller provides
|
||||
the storage for the object, the caller is also responsible for deleting the
|
||||
object. An object still exists after being removed from a List, until the
|
||||
caller deletes it. This means an element can be moved from one List to
|
||||
another with practically no overhead.
|
||||
|
||||
Unlike the standard containers, an object may only exist in one list at a
|
||||
time, unless special preparations are made. The Tag template parameter is
|
||||
used to distinguish between different list types for the same object,
|
||||
allowing the object to exist in more than one list simultaneously.
|
||||
|
||||
For example, consider an actor system where a global list of actors is
|
||||
maintained, so that they can each be periodically receive processing
|
||||
time. We wish to also maintain a list of the subset of actors that require
|
||||
a domain-dependent update. To achieve this, we declare two tags, the
|
||||
associated list types, and the list element thusly:
|
||||
|
||||
@code
|
||||
|
||||
struct Actor; // Forward declaration required
|
||||
|
||||
struct ProcessTag { };
|
||||
struct UpdateTag { };
|
||||
|
||||
using ProcessList = List <Actor, ProcessTag>;
|
||||
using UpdateList = List <Actor, UpdateTag>;
|
||||
|
||||
// Derive from both node types so we can be in each list at once.
|
||||
//
|
||||
struct Actor : ProcessList::Node, UpdateList::Node
|
||||
{
|
||||
bool process (); // returns true if we need an update
|
||||
void update ();
|
||||
};
|
||||
|
||||
@endcode
|
||||
|
||||
@tparam T The base type of element which the list will store
|
||||
pointers to.
|
||||
|
||||
@tparam Tag An optional unique type name used to distinguish lists and
|
||||
nodes, when the object can exist in multiple lists simultaneously.
|
||||
|
||||
@ingroup beast_core intrusive
|
||||
*/
|
||||
template <typename T, typename Tag = void>
|
||||
class List
|
||||
{
|
||||
@@ -277,9 +274,7 @@ public:
|
||||
using iterator = detail::ListIterator<Node>;
|
||||
using const_iterator = detail::ListIterator<Node const>;
|
||||
|
||||
/**
|
||||
* Create an empty list.
|
||||
*/
|
||||
/** Create an empty list. */
|
||||
List()
|
||||
{
|
||||
head_.prev_ = nullptr; // identifies the head
|
||||
@@ -291,133 +286,119 @@ public:
|
||||
List&
|
||||
operator=(List const&) = delete;
|
||||
|
||||
/**
|
||||
* Determine if the list is empty.
|
||||
* @return `true` if the list is empty.
|
||||
*/
|
||||
/** Determine if the list is empty.
|
||||
@return `true` if the list is empty.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
empty() const noexcept
|
||||
{
|
||||
return size() == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of elements in the list.
|
||||
*/
|
||||
/** Returns the number of elements in the list. */
|
||||
[[nodiscard]] size_type
|
||||
size() const noexcept
|
||||
{
|
||||
return size_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a reference to the first element.
|
||||
* @invariant The list may not be empty.
|
||||
* @return A reference to the first element.
|
||||
*/
|
||||
/** Obtain a reference to the first element.
|
||||
@invariant The list may not be empty.
|
||||
@return A reference to the first element.
|
||||
*/
|
||||
reference
|
||||
front() noexcept
|
||||
{
|
||||
return element_from(head_.next_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a const reference to the first element.
|
||||
* @invariant The list may not be empty.
|
||||
* @return A const reference to the first element.
|
||||
*/
|
||||
/** Obtain a const reference to the first element.
|
||||
@invariant The list may not be empty.
|
||||
@return A const reference to the first element.
|
||||
*/
|
||||
[[nodiscard]] const_reference
|
||||
front() const noexcept
|
||||
{
|
||||
return element_from(head_.next_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a reference to the last element.
|
||||
* @invariant The list may not be empty.
|
||||
* @return A reference to the last element.
|
||||
*/
|
||||
/** Obtain a reference to the last element.
|
||||
@invariant The list may not be empty.
|
||||
@return A reference to the last element.
|
||||
*/
|
||||
reference
|
||||
back() noexcept
|
||||
{
|
||||
return element_from(tail_.prev_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a const reference to the last element.
|
||||
* @invariant The list may not be empty.
|
||||
* @return A const reference to the last element.
|
||||
*/
|
||||
/** Obtain a const reference to the last element.
|
||||
@invariant The list may not be empty.
|
||||
@return A const reference to the last element.
|
||||
*/
|
||||
[[nodiscard]] const_reference
|
||||
back() const noexcept
|
||||
{
|
||||
return element_from(tail_.prev_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain an iterator to the beginning of the list.
|
||||
* @return An iterator pointing to the beginning of the list.
|
||||
*/
|
||||
/** Obtain an iterator to the beginning of the list.
|
||||
@return An iterator pointing to the beginning of the list.
|
||||
*/
|
||||
iterator
|
||||
begin() noexcept
|
||||
{
|
||||
return iterator(head_.next_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a const iterator to the beginning of the list.
|
||||
* @return A const iterator pointing to the beginning of the list.
|
||||
*/
|
||||
/** Obtain a const iterator to the beginning of the list.
|
||||
@return A const iterator pointing to the beginning of the list.
|
||||
*/
|
||||
[[nodiscard]] const_iterator
|
||||
begin() const noexcept
|
||||
{
|
||||
return const_iterator(head_.next_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a const iterator to the beginning of the list.
|
||||
* @return A const iterator pointing to the beginning of the list.
|
||||
*/
|
||||
/** Obtain a const iterator to the beginning of the list.
|
||||
@return A const iterator pointing to the beginning of the list.
|
||||
*/
|
||||
[[nodiscard]] const_iterator
|
||||
cbegin() const noexcept
|
||||
{
|
||||
return const_iterator(head_.next_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a iterator to the end of the list.
|
||||
* @return An iterator pointing to the end of the list.
|
||||
*/
|
||||
/** Obtain a iterator to the end of the list.
|
||||
@return An iterator pointing to the end of the list.
|
||||
*/
|
||||
iterator
|
||||
end() noexcept
|
||||
{
|
||||
return iterator(&tail_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a const iterator to the end of the list.
|
||||
* @return A constiterator pointing to the end of the list.
|
||||
*/
|
||||
/** Obtain a const iterator to the end of the list.
|
||||
@return A constiterator pointing to the end of the list.
|
||||
*/
|
||||
[[nodiscard]] const_iterator
|
||||
end() const noexcept
|
||||
{
|
||||
return const_iterator(&tail_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a const iterator to the end of the list
|
||||
* @return A constiterator pointing to the end of the list.
|
||||
*/
|
||||
/** Obtain a const iterator to the end of the list
|
||||
@return A constiterator pointing to the end of the list.
|
||||
*/
|
||||
[[nodiscard]] const_iterator
|
||||
cend() const noexcept
|
||||
{
|
||||
return const_iterator(&tail_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the list.
|
||||
* @note This does not free the elements.
|
||||
*/
|
||||
/** Clear the list.
|
||||
@note This does not free the elements.
|
||||
*/
|
||||
void
|
||||
clear() noexcept
|
||||
{
|
||||
@@ -426,13 +407,12 @@ public:
|
||||
size_ = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert an element.
|
||||
* @invariant The element must not already be in the list.
|
||||
* @param pos The location to insert after.
|
||||
* @param element The element to insert.
|
||||
* @return An iterator pointing to the newly inserted element.
|
||||
*/
|
||||
/** Insert an element.
|
||||
@invariant The element must not already be in the list.
|
||||
@param pos The location to insert after.
|
||||
@param element The element to insert.
|
||||
@return An iterator pointing to the newly inserted element.
|
||||
*/
|
||||
iterator
|
||||
insert(iterator pos, T& element) noexcept
|
||||
{
|
||||
@@ -445,12 +425,11 @@ public:
|
||||
return iterator(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert another list into this one.
|
||||
* The other list is cleared.
|
||||
* @param pos The location to insert after.
|
||||
* @param other The list to insert.
|
||||
*/
|
||||
/** Insert another list into this one.
|
||||
The other list is cleared.
|
||||
@param pos The location to insert after.
|
||||
@param other The list to insert.
|
||||
*/
|
||||
void
|
||||
insert(iterator pos, List& other) noexcept
|
||||
{
|
||||
@@ -466,12 +445,11 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an element.
|
||||
* @invariant The element must exist in the list.
|
||||
* @param pos An iterator pointing to the element to remove.
|
||||
* @return An iterator pointing to the next element after the one removed.
|
||||
*/
|
||||
/** Remove an element.
|
||||
@invariant The element must exist in the list.
|
||||
@param pos An iterator pointing to the element to remove.
|
||||
@return An iterator pointing to the next element after the one removed.
|
||||
*/
|
||||
iterator
|
||||
erase(iterator pos) noexcept
|
||||
{
|
||||
@@ -483,22 +461,20 @@ public:
|
||||
return pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert an element at the beginning of the list.
|
||||
* @invariant The element must not exist in the list.
|
||||
* @param element The element to insert.
|
||||
*/
|
||||
/** Insert an element at the beginning of the list.
|
||||
@invariant The element must not exist in the list.
|
||||
@param element The element to insert.
|
||||
*/
|
||||
iterator
|
||||
pushFront(T& element) noexcept
|
||||
{
|
||||
return insert(begin(), element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the element at the beginning of the list.
|
||||
* @invariant The list must not be empty.
|
||||
* @return A reference to the popped element.
|
||||
*/
|
||||
/** Remove the element at the beginning of the list.
|
||||
@invariant The list must not be empty.
|
||||
@return A reference to the popped element.
|
||||
*/
|
||||
T&
|
||||
popFront() noexcept
|
||||
{
|
||||
@@ -507,22 +483,20 @@ public:
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append an element at the end of the list.
|
||||
* @invariant The element must not exist in the list.
|
||||
* @param element The element to append.
|
||||
*/
|
||||
/** Append an element at the end of the list.
|
||||
@invariant The element must not exist in the list.
|
||||
@param element The element to append.
|
||||
*/
|
||||
iterator
|
||||
pushBack(T& element) noexcept
|
||||
{
|
||||
return insert(end(), element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the element at the end of the list.
|
||||
* @invariant The list must not be empty.
|
||||
* @return A reference to the popped element.
|
||||
*/
|
||||
/** Remove the element at the end of the list.
|
||||
@invariant The list must not be empty.
|
||||
@return A reference to the popped element.
|
||||
*/
|
||||
T&
|
||||
popBack() noexcept
|
||||
{
|
||||
@@ -531,9 +505,7 @@ public:
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Swap contents with another list.
|
||||
*/
|
||||
/** Swap contents with another list. */
|
||||
void
|
||||
swap(List& other) noexcept
|
||||
{
|
||||
@@ -543,46 +515,42 @@ public:
|
||||
append(temp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert another list at the beginning of this list.
|
||||
* The other list is cleared.
|
||||
* @param list The other list to insert.
|
||||
*/
|
||||
/** Insert another list at the beginning of this list.
|
||||
The other list is cleared.
|
||||
@param list The other list to insert.
|
||||
*/
|
||||
iterator
|
||||
prepend(List& list) noexcept
|
||||
{
|
||||
return insert(begin(), list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append another list at the end of this list.
|
||||
* The other list is cleared.
|
||||
* @param list the other list to append.
|
||||
*/
|
||||
/** Append another list at the end of this list.
|
||||
The other list is cleared.
|
||||
@param list the other list to append.
|
||||
*/
|
||||
iterator
|
||||
append(List& list) noexcept
|
||||
{
|
||||
return insert(end(), list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain an iterator from an element.
|
||||
* @invariant The element must exist in the list.
|
||||
* @param element The element to obtain an iterator for.
|
||||
* @return An iterator to the element.
|
||||
*/
|
||||
/** Obtain an iterator from an element.
|
||||
@invariant The element must exist in the list.
|
||||
@param element The element to obtain an iterator for.
|
||||
@return An iterator to the element.
|
||||
*/
|
||||
iterator
|
||||
iteratorTo(T& element) const noexcept
|
||||
{
|
||||
return iterator(static_cast<Node*>(&element));
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a const iterator from an element.
|
||||
* @invariant The element must exist in the list.
|
||||
* @param element The element to obtain an iterator for.
|
||||
* @return A const iterator to the element.
|
||||
*/
|
||||
/** Obtain a const iterator from an element.
|
||||
@invariant The element must exist in the list.
|
||||
@param element The element to obtain an iterator for.
|
||||
@return A const iterator to the element.
|
||||
*/
|
||||
[[nodiscard]] const_iterator
|
||||
constIteratorTo(T const& element) const noexcept
|
||||
{
|
||||
|
||||
@@ -103,19 +103,18 @@ operator!=(
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Multiple Producer, Multiple Consumer (MPMC) intrusive stack.
|
||||
*
|
||||
* This stack is implemented using the same intrusive interface as List.
|
||||
* All mutations are lock-free.
|
||||
*
|
||||
* The caller is responsible for preventing the "ABA" problem:
|
||||
* http://en.wikipedia.org/wiki/ABA_problem
|
||||
*
|
||||
* @param Tag A type name used to distinguish lists and nodes, for
|
||||
* putting objects in multiple lists. If this parameter is
|
||||
* omitted, the default tag is used.
|
||||
*/
|
||||
/** Multiple Producer, Multiple Consumer (MPMC) intrusive stack.
|
||||
|
||||
This stack is implemented using the same intrusive interface as List.
|
||||
All mutations are lock-free.
|
||||
|
||||
The caller is responsible for preventing the "ABA" problem:
|
||||
http://en.wikipedia.org/wiki/ABA_problem
|
||||
|
||||
@param Tag A type name used to distinguish lists and nodes, for
|
||||
putting objects in multiple lists. If this parameter is
|
||||
omitted, the default tag is used.
|
||||
*/
|
||||
template <class Element, class Tag = void>
|
||||
class LockFreeStack
|
||||
{
|
||||
@@ -163,27 +162,24 @@ public:
|
||||
LockFreeStack&
|
||||
operator=(LockFreeStack const&) = delete;
|
||||
|
||||
/**
|
||||
* Returns true if the stack is empty.
|
||||
*/
|
||||
/** Returns true if the stack is empty. */
|
||||
[[nodiscard]] bool
|
||||
empty() const
|
||||
{
|
||||
return head_.load() == &end_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a node onto the stack.
|
||||
* The caller is responsible for preventing the ABA problem.
|
||||
* This operation is lock-free.
|
||||
* Thread safety:
|
||||
* Safe to call from any thread.
|
||||
*
|
||||
* @param node The node to push.
|
||||
*
|
||||
* @return `true` if the stack was previously empty. If multiple threads
|
||||
* are attempting to push, only one will receive `true`.
|
||||
*/
|
||||
/** Push a node onto the stack.
|
||||
The caller is responsible for preventing the ABA problem.
|
||||
This operation is lock-free.
|
||||
Thread safety:
|
||||
Safe to call from any thread.
|
||||
|
||||
@param node The node to push.
|
||||
|
||||
@return `true` if the stack was previously empty. If multiple threads
|
||||
are attempting to push, only one will receive `true`.
|
||||
*/
|
||||
// VFALCO NOTE Fix this, shouldn't it be a reference like intrusive list?
|
||||
bool
|
||||
pushFront(Node* node)
|
||||
@@ -199,16 +195,15 @@ public:
|
||||
return first;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pop an element off the stack.
|
||||
* The caller is responsible for preventing the ABA problem.
|
||||
* This operation is lock-free.
|
||||
* Thread safety:
|
||||
* Safe to call from any thread.
|
||||
*
|
||||
* @return The element that was popped, or `nullptr` if the stack
|
||||
* was empty.
|
||||
*/
|
||||
/** Pop an element off the stack.
|
||||
The caller is responsible for preventing the ABA problem.
|
||||
This operation is lock-free.
|
||||
Thread safety:
|
||||
Safe to call from any thread.
|
||||
|
||||
@return The element that was popped, or `nullptr` if the stack
|
||||
was empty.
|
||||
*/
|
||||
Element*
|
||||
popFront()
|
||||
{
|
||||
@@ -224,13 +219,12 @@ public:
|
||||
return static_cast<Element*>(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a forward iterator to the beginning or end of the stack.
|
||||
* Undefined behavior results if push_front or pop_front is called
|
||||
* while an iteration is in progress.
|
||||
* Thread safety:
|
||||
* Caller is responsible for synchronization.
|
||||
*/
|
||||
/** Return a forward iterator to the beginning or end of the stack.
|
||||
Undefined behavior results if push_front or pop_front is called
|
||||
while an iteration is in progress.
|
||||
Thread safety:
|
||||
Caller is responsible for synchronization.
|
||||
*/
|
||||
/** @{ */
|
||||
iterator
|
||||
begin()
|
||||
|
||||
@@ -6,14 +6,13 @@
|
||||
|
||||
namespace beast {
|
||||
|
||||
/**
|
||||
* A Semantic Version number.
|
||||
*
|
||||
* Identifies the build of a particular version of software using
|
||||
* the Semantic Versioning Specification described here:
|
||||
*
|
||||
* http://semver.org/
|
||||
*/
|
||||
/** A Semantic Version number.
|
||||
|
||||
Identifies the build of a particular version of software using
|
||||
the Semantic Versioning Specification described here:
|
||||
|
||||
http://semver.org/
|
||||
*/
|
||||
class SemanticVersion
|
||||
{
|
||||
public:
|
||||
@@ -30,17 +29,14 @@ public:
|
||||
|
||||
SemanticVersion(std::string_view version);
|
||||
|
||||
/**
|
||||
* Parse a semantic version string.
|
||||
* The parsing is as strict as possible.
|
||||
* @return `true` if the string was parsed.
|
||||
*/
|
||||
/** Parse a semantic version string.
|
||||
The parsing is as strict as possible.
|
||||
@return `true` if the string was parsed.
|
||||
*/
|
||||
bool
|
||||
parse(std::string_view input);
|
||||
|
||||
/**
|
||||
* Produce a string from semantic version components.
|
||||
*/
|
||||
/** Produce a string from semantic version components. */
|
||||
[[nodiscard]] std::string
|
||||
print() const;
|
||||
|
||||
@@ -56,10 +52,9 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Compare two SemanticVersions against each other.
|
||||
* The comparison follows the rules as per the specification.
|
||||
*/
|
||||
/** Compare two SemanticVersions against each other.
|
||||
The comparison follows the rules as per the specification.
|
||||
*/
|
||||
int
|
||||
compare(SemanticVersion const& lhs, SemanticVersion const& rhs);
|
||||
|
||||
|
||||
@@ -135,20 +135,19 @@ struct IsUniquelyRepresented<std::array<T, N>>
|
||||
explicit IsUniquelyRepresented() = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Metafunction returning `true` if the type can be hashed in one call.
|
||||
*
|
||||
* For `IsContiguouslyHashable<T>::value` to be true, then for every
|
||||
* combination of possible values of `T` held in `x` and `y`,
|
||||
* if `x == y`, then it must be true that `memcmp(&x, &y, sizeof(T))`
|
||||
* return 0; i.e. that `x` and `y` are represented by the same bit pattern.
|
||||
*
|
||||
* For example: A two's complement `int` should be contiguously hashable.
|
||||
* Every bit pattern produces a unique value that does not compare equal to
|
||||
* any other bit pattern's value. A IEEE floating point should not be
|
||||
* contiguously hashable because -0. and 0. have different bit patterns,
|
||||
* though they compare equal.
|
||||
*/
|
||||
/** Metafunction returning `true` if the type can be hashed in one call.
|
||||
|
||||
For `IsContiguouslyHashable<T>::value` to be true, then for every
|
||||
combination of possible values of `T` held in `x` and `y`,
|
||||
if `x == y`, then it must be true that `memcmp(&x, &y, sizeof(T))`
|
||||
return 0; i.e. that `x` and `y` are represented by the same bit pattern.
|
||||
|
||||
For example: A two's complement `int` should be contiguously hashable.
|
||||
Every bit pattern produces a unique value that does not compare equal to
|
||||
any other bit pattern's value. A IEEE floating point should not be
|
||||
contiguously hashable because -0. and 0. have different bit patterns,
|
||||
though they compare equal.
|
||||
*/
|
||||
/** @{ */
|
||||
template <class T, class HashAlgorithm>
|
||||
struct IsContiguouslyHashable
|
||||
@@ -173,30 +172,29 @@ struct IsContiguouslyHashable<T[N], HashAlgorithm>
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Logically concatenate input data to a `Hasher`.
|
||||
*
|
||||
* Hasher requirements:
|
||||
*
|
||||
* `X` is the type `Hasher`
|
||||
* `h` is a value of type `x`
|
||||
* `p` is a value convertible to `void const*`
|
||||
* `n` is a value of type `std::size_t`, greater than zero
|
||||
*
|
||||
* Expression:
|
||||
* `h.append (p, n);`
|
||||
* Throws:
|
||||
* Never
|
||||
* Effect:
|
||||
* Adds the input data to the hasher state.
|
||||
*
|
||||
* Expression:
|
||||
* `static_cast<std::size_t>(j)`
|
||||
* Throws:
|
||||
* Never
|
||||
* Effect:
|
||||
* Returns the resulting hash of all the input data.
|
||||
*/
|
||||
/** Logically concatenate input data to a `Hasher`.
|
||||
|
||||
Hasher requirements:
|
||||
|
||||
`X` is the type `Hasher`
|
||||
`h` is a value of type `x`
|
||||
`p` is a value convertible to `void const*`
|
||||
`n` is a value of type `std::size_t`, greater than zero
|
||||
|
||||
Expression:
|
||||
`h.append (p, n);`
|
||||
Throws:
|
||||
Never
|
||||
Effect:
|
||||
Adds the input data to the hasher state.
|
||||
|
||||
Expression:
|
||||
`static_cast<std::size_t>(j)`
|
||||
Throws:
|
||||
Never
|
||||
Effect:
|
||||
Returns the resulting hash of all the input data.
|
||||
*/
|
||||
/** @{ */
|
||||
|
||||
// scalars
|
||||
|
||||
@@ -12,17 +12,16 @@
|
||||
|
||||
namespace beast::insight {
|
||||
|
||||
/**
|
||||
* Interface for a manager that allows collection of metrics.
|
||||
*
|
||||
* To export metrics from a class, pass and save a shared_ptr to this
|
||||
* interface in the class constructor. Create the metric objects
|
||||
* as desired (counters, events, gauges, meters, and an optional hook)
|
||||
* using the interface.
|
||||
*
|
||||
* @see Counter, Event, Gauge, Hook, Meter
|
||||
* @see NullCollector, StatsDCollector
|
||||
*/
|
||||
/** Interface for a manager that allows collection of metrics.
|
||||
|
||||
To export metrics from a class, pass and save a shared_ptr to this
|
||||
interface in the class constructor. Create the metric objects
|
||||
as desired (counters, events, gauges, meters, and an optional hook)
|
||||
using the interface.
|
||||
|
||||
@see Counter, Event, Gauge, Hook, Meter
|
||||
@see NullCollector, StatsDCollector
|
||||
*/
|
||||
class Collector
|
||||
{
|
||||
public:
|
||||
@@ -30,19 +29,18 @@ public:
|
||||
|
||||
virtual ~Collector() = 0;
|
||||
|
||||
/**
|
||||
* Create a hook.
|
||||
*
|
||||
* A hook is called at each collection interval, on an implementation
|
||||
* defined thread. This is a convenience facility for gathering metrics
|
||||
* in the polling style. The typical usage is to update all the metrics
|
||||
* of interest in the handler.
|
||||
*
|
||||
* Handler will be called with this signature:
|
||||
* void handler (void)
|
||||
*
|
||||
* @see Hook
|
||||
*/
|
||||
/** Create a hook.
|
||||
|
||||
A hook is called at each collection interval, on an implementation
|
||||
defined thread. This is a convenience facility for gathering metrics
|
||||
in the polling style. The typical usage is to update all the metrics
|
||||
of interest in the handler.
|
||||
|
||||
Handler will be called with this signature:
|
||||
void handler (void)
|
||||
|
||||
@see Hook
|
||||
*/
|
||||
/** @{ */
|
||||
template <class Handler>
|
||||
Hook
|
||||
@@ -55,10 +53,9 @@ public:
|
||||
makeHook(HookImpl::HandlerType const& handler) = 0;
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Create a counter with the specified name.
|
||||
* @see Counter
|
||||
*/
|
||||
/** Create a counter with the specified name.
|
||||
@see Counter
|
||||
*/
|
||||
/** @{ */
|
||||
virtual Counter
|
||||
makeCounter(std::string const& name) = 0;
|
||||
@@ -72,10 +69,9 @@ public:
|
||||
}
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Create an event with the specified name.
|
||||
* @see Event
|
||||
*/
|
||||
/** Create an event with the specified name.
|
||||
@see Event
|
||||
*/
|
||||
/** @{ */
|
||||
virtual Event
|
||||
makeEvent(std::string const& name) = 0;
|
||||
@@ -89,10 +85,9 @@ public:
|
||||
}
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Create a gauge with the specified name.
|
||||
* @see Gauge
|
||||
*/
|
||||
/** Create a gauge with the specified name.
|
||||
@see Gauge
|
||||
*/
|
||||
/** @{ */
|
||||
virtual Gauge
|
||||
makeGauge(std::string const& name) = 0;
|
||||
@@ -106,10 +101,9 @@ public:
|
||||
}
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Create a meter with the specified name.
|
||||
* @see Meter
|
||||
*/
|
||||
/** Create a meter with the specified name.
|
||||
@see Meter
|
||||
*/
|
||||
/** @{ */
|
||||
virtual Meter
|
||||
makeMeter(std::string const& name) = 0;
|
||||
|
||||
@@ -7,39 +7,34 @@
|
||||
|
||||
namespace beast::insight {
|
||||
|
||||
/**
|
||||
* A metric for measuring an integral value.
|
||||
*
|
||||
* A counter is a gauge calculated at the server. The owner of the counter
|
||||
* may increment and decrement the value by an amount.
|
||||
*
|
||||
* This is a lightweight reference wrapper which is cheap to copy and assign.
|
||||
* When the last reference goes away, the metric is no longer collected.
|
||||
*/
|
||||
/** A metric for measuring an integral value.
|
||||
|
||||
A counter is a gauge calculated at the server. The owner of the counter
|
||||
may increment and decrement the value by an amount.
|
||||
|
||||
This is a lightweight reference wrapper which is cheap to copy and assign.
|
||||
When the last reference goes away, the metric is no longer collected.
|
||||
*/
|
||||
class Counter final
|
||||
{
|
||||
public:
|
||||
using value_type = CounterImpl::value_type;
|
||||
|
||||
/**
|
||||
* Create a null metric.
|
||||
* A null metric reports no information.
|
||||
*/
|
||||
/** Create a null metric.
|
||||
A null metric reports no information.
|
||||
*/
|
||||
Counter() = default;
|
||||
|
||||
/**
|
||||
* Create the metric reference the specified implementation.
|
||||
* Normally this won't be called directly. Instead, call the appropriate
|
||||
* factory function in the Collector interface.
|
||||
* @see Collector.
|
||||
*/
|
||||
/** Create the metric reference the specified implementation.
|
||||
Normally this won't be called directly. Instead, call the appropriate
|
||||
factory function in the Collector interface.
|
||||
@see Collector.
|
||||
*/
|
||||
explicit Counter(std::shared_ptr<CounterImpl> impl) : impl_(std::move(impl))
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the counter.
|
||||
*/
|
||||
/** Increment the counter. */
|
||||
/** @{ */
|
||||
void
|
||||
increment(value_type amount) const
|
||||
|
||||
@@ -8,40 +8,35 @@
|
||||
|
||||
namespace beast::insight {
|
||||
|
||||
/**
|
||||
* A metric for reporting event timing.
|
||||
*
|
||||
* An event is an operation that has an associated millisecond time, or
|
||||
* other integral value. Because events happen at a specific moment, the
|
||||
* metric only supports a push-style interface.
|
||||
*
|
||||
* This is a lightweight reference wrapper which is cheap to copy and assign.
|
||||
* When the last reference goes away, the metric is no longer collected.
|
||||
*/
|
||||
/** A metric for reporting event timing.
|
||||
|
||||
An event is an operation that has an associated millisecond time, or
|
||||
other integral value. Because events happen at a specific moment, the
|
||||
metric only supports a push-style interface.
|
||||
|
||||
This is a lightweight reference wrapper which is cheap to copy and assign.
|
||||
When the last reference goes away, the metric is no longer collected.
|
||||
*/
|
||||
class Event final
|
||||
{
|
||||
public:
|
||||
using value_type = EventImpl::value_type;
|
||||
|
||||
/**
|
||||
* Create a null metric.
|
||||
* A null metric reports no information.
|
||||
*/
|
||||
/** Create a null metric.
|
||||
A null metric reports no information.
|
||||
*/
|
||||
Event() = default;
|
||||
|
||||
/**
|
||||
* Create the metric reference the specified implementation.
|
||||
* Normally this won't be called directly. Instead, call the appropriate
|
||||
* factory function in the Collector interface.
|
||||
* @see Collector.
|
||||
*/
|
||||
/** Create the metric reference the specified implementation.
|
||||
Normally this won't be called directly. Instead, call the appropriate
|
||||
factory function in the Collector interface.
|
||||
@see Collector.
|
||||
*/
|
||||
explicit Event(std::shared_ptr<EventImpl> impl) : impl_(std::move(impl))
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Push an event notification.
|
||||
*/
|
||||
/** Push an event notification. */
|
||||
template <class Rep, class Period>
|
||||
void
|
||||
notify(std::chrono::duration<Rep, Period> const& value) const
|
||||
|
||||
@@ -7,44 +7,40 @@
|
||||
|
||||
namespace beast::insight {
|
||||
|
||||
/**
|
||||
* A metric for measuring an integral value.
|
||||
*
|
||||
* A gauge is an instantaneous measurement of a value, like the gas gauge
|
||||
* in a car. The caller directly sets the value, or adjusts it by a
|
||||
* specified amount. The value is kept in the client rather than the collector.
|
||||
*
|
||||
* This is a lightweight reference wrapper which is cheap to copy and assign.
|
||||
* When the last reference goes away, the metric is no longer collected.
|
||||
*/
|
||||
/** A metric for measuring an integral value.
|
||||
|
||||
A gauge is an instantaneous measurement of a value, like the gas gauge
|
||||
in a car. The caller directly sets the value, or adjusts it by a
|
||||
specified amount. The value is kept in the client rather than the collector.
|
||||
|
||||
This is a lightweight reference wrapper which is cheap to copy and assign.
|
||||
When the last reference goes away, the metric is no longer collected.
|
||||
*/
|
||||
class Gauge final
|
||||
{
|
||||
public:
|
||||
using value_type = GaugeImpl::value_type;
|
||||
using difference_type = GaugeImpl::difference_type;
|
||||
|
||||
/**
|
||||
* Create a null metric.
|
||||
* A null metric reports no information.
|
||||
*/
|
||||
/** Create a null metric.
|
||||
A null metric reports no information.
|
||||
*/
|
||||
Gauge() = default;
|
||||
|
||||
/**
|
||||
* Create the metric reference the specified implementation.
|
||||
* Normally this won't be called directly. Instead, call the appropriate
|
||||
* factory function in the Collector interface.
|
||||
* @see Collector.
|
||||
*/
|
||||
/** Create the metric reference the specified implementation.
|
||||
Normally this won't be called directly. Instead, call the appropriate
|
||||
factory function in the Collector interface.
|
||||
@see Collector.
|
||||
*/
|
||||
explicit Gauge(std::shared_ptr<GaugeImpl> impl) : impl_(std::move(impl))
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value on the gauge.
|
||||
* A Collector implementation should combine multiple calls to value
|
||||
* changes into a single change if the calls occur within a single
|
||||
* collection interval.
|
||||
*/
|
||||
/** Set the value on the gauge.
|
||||
A Collector implementation should combine multiple calls to value
|
||||
changes into a single change if the calls occur within a single
|
||||
collection interval.
|
||||
*/
|
||||
/** @{ */
|
||||
void
|
||||
set(value_type value) const
|
||||
@@ -66,9 +62,7 @@ public:
|
||||
}
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Adjust the value of the gauge.
|
||||
*/
|
||||
/** Adjust the value of the gauge. */
|
||||
/** @{ */
|
||||
void
|
||||
increment(difference_type amount) const
|
||||
|
||||
@@ -7,17 +7,13 @@
|
||||
|
||||
namespace beast::insight {
|
||||
|
||||
/**
|
||||
* A collector front-end that manages a group of metrics.
|
||||
*/
|
||||
/** A collector front-end that manages a group of metrics. */
|
||||
class Group : public Collector
|
||||
{
|
||||
public:
|
||||
using ptr = std::shared_ptr<Group>;
|
||||
|
||||
/**
|
||||
* Returns the name of this group, for diagnostics.
|
||||
*/
|
||||
/** Returns the name of this group, for diagnostics. */
|
||||
[[nodiscard]] virtual std::string const&
|
||||
name() const = 0;
|
||||
};
|
||||
|
||||
@@ -8,17 +8,13 @@
|
||||
|
||||
namespace beast::insight {
|
||||
|
||||
/**
|
||||
* A container for managing a set of metric groups.
|
||||
*/
|
||||
/** A container for managing a set of metric groups. */
|
||||
class Groups
|
||||
{
|
||||
public:
|
||||
virtual ~Groups() = 0;
|
||||
|
||||
/**
|
||||
* Find or create a new collector with a given name.
|
||||
*/
|
||||
/** Find or create a new collector with a given name. */
|
||||
/** @{ */
|
||||
virtual Group::ptr const&
|
||||
get(std::string const& name) = 0;
|
||||
@@ -31,9 +27,7 @@ public:
|
||||
/** @} */
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a group container that uses the specified collector.
|
||||
*/
|
||||
/** Create a group container that uses the specified collector. */
|
||||
std::unique_ptr<Groups>
|
||||
makeGroups(Collector::ptr const& collector);
|
||||
|
||||
|
||||
@@ -7,24 +7,20 @@
|
||||
|
||||
namespace beast::insight {
|
||||
|
||||
/**
|
||||
* A reference to a handler for performing polled collection.
|
||||
*/
|
||||
/** A reference to a handler for performing polled collection. */
|
||||
class Hook final
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Create a null hook.
|
||||
* A null hook has no associated handler.
|
||||
*/
|
||||
/** Create a null hook.
|
||||
A null hook has no associated handler.
|
||||
*/
|
||||
Hook() = default;
|
||||
|
||||
/**
|
||||
* Create a hook referencing the specified implementation.
|
||||
* Normally this won't be called directly. Instead, call the appropriate
|
||||
* factory function in the Collector interface.
|
||||
* @see Collector.
|
||||
*/
|
||||
/** Create a hook referencing the specified implementation.
|
||||
Normally this won't be called directly. Instead, call the appropriate
|
||||
factory function in the Collector interface.
|
||||
@see Collector.
|
||||
*/
|
||||
explicit Hook(std::shared_ptr<HookImpl> impl) : impl_(std::move(impl))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -7,38 +7,33 @@
|
||||
|
||||
namespace beast::insight {
|
||||
|
||||
/**
|
||||
* A metric for measuring an integral value.
|
||||
*
|
||||
* A meter may be thought of as an increment-only counter.
|
||||
*
|
||||
* This is a lightweight reference wrapper which is cheap to copy and assign.
|
||||
* When the last reference goes away, the metric is no longer collected.
|
||||
*/
|
||||
/** A metric for measuring an integral value.
|
||||
|
||||
A meter may be thought of as an increment-only counter.
|
||||
|
||||
This is a lightweight reference wrapper which is cheap to copy and assign.
|
||||
When the last reference goes away, the metric is no longer collected.
|
||||
*/
|
||||
class Meter final
|
||||
{
|
||||
public:
|
||||
using value_type = MeterImpl::value_type;
|
||||
|
||||
/**
|
||||
* Create a null metric.
|
||||
* A null metric reports no information.
|
||||
*/
|
||||
/** Create a null metric.
|
||||
A null metric reports no information.
|
||||
*/
|
||||
Meter() = default;
|
||||
|
||||
/**
|
||||
* Create the metric reference the specified implementation.
|
||||
* Normally this won't be called directly. Instead, call the appropriate
|
||||
* factory function in the Collector interface.
|
||||
* @see Collector.
|
||||
*/
|
||||
/** Create the metric reference the specified implementation.
|
||||
Normally this won't be called directly. Instead, call the appropriate
|
||||
factory function in the Collector interface.
|
||||
@see Collector.
|
||||
*/
|
||||
explicit Meter(std::shared_ptr<MeterImpl> impl) : impl_(std::move(impl))
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the meter.
|
||||
*/
|
||||
/** Increment the meter. */
|
||||
/** @{ */
|
||||
void
|
||||
increment(value_type amount) const
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
|
||||
namespace beast::insight {
|
||||
|
||||
/**
|
||||
* A Collector which does not collect metrics.
|
||||
*/
|
||||
/** A Collector which does not collect metrics. */
|
||||
class NullCollector : public Collector
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -9,22 +9,20 @@
|
||||
|
||||
namespace beast::insight {
|
||||
|
||||
/**
|
||||
* A Collector that reports metrics to a StatsD server.
|
||||
* Reference:
|
||||
* https://github.com/b/statsd_spec
|
||||
*/
|
||||
/** A Collector that reports metrics to a StatsD server.
|
||||
Reference:
|
||||
https://github.com/b/statsd_spec
|
||||
*/
|
||||
class StatsDCollector : public Collector
|
||||
{
|
||||
public:
|
||||
explicit StatsDCollector() = default;
|
||||
|
||||
/**
|
||||
* Create a StatsD collector.
|
||||
* @param address The IP address and port of the StatsD server.
|
||||
* @param prefix A string pre-pended before each metric name.
|
||||
* @param journal Destination for logging output.
|
||||
*/
|
||||
/** Create a StatsD collector.
|
||||
@param address The IP address and port of the StatsD server.
|
||||
@param prefix A string pre-pended before each metric name.
|
||||
@param journal Destination for logging output.
|
||||
*/
|
||||
static std::shared_ptr<StatsDCollector>
|
||||
make(IP::Endpoint const& address, std::string const& prefix, Journal journal);
|
||||
};
|
||||
|
||||
@@ -19,54 +19,42 @@ namespace IP {
|
||||
|
||||
using Address = boost::asio::ip::address;
|
||||
|
||||
/**
|
||||
* Returns the address represented as a string.
|
||||
*/
|
||||
/** Returns the address represented as a string. */
|
||||
inline std::string
|
||||
to_string(Address const& addr)
|
||||
{
|
||||
return addr.to_string();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if this is a loopback address.
|
||||
*/
|
||||
/** Returns `true` if this is a loopback address. */
|
||||
inline bool
|
||||
isLoopback(Address const& addr)
|
||||
{
|
||||
return addr.is_loopback();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the address is unspecified.
|
||||
*/
|
||||
/** Returns `true` if the address is unspecified. */
|
||||
inline bool
|
||||
isUnspecified(Address const& addr)
|
||||
{
|
||||
return addr.is_unspecified();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the address is a multicast address.
|
||||
*/
|
||||
/** Returns `true` if the address is a multicast address. */
|
||||
inline bool
|
||||
isMulticast(Address const& addr)
|
||||
{
|
||||
return addr.is_multicast();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the address is a private unroutable address.
|
||||
*/
|
||||
/** Returns `true` if the address is a private unroutable address. */
|
||||
inline bool
|
||||
isPrivate(Address const& addr)
|
||||
{
|
||||
return (addr.is_v4()) ? isPrivate(addr.to_v4()) : isPrivate(addr.to_v6());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the address is a public routable address.
|
||||
*/
|
||||
/** Returns `true` if the address is a public routable address. */
|
||||
inline bool
|
||||
isPublic(Address const& addr)
|
||||
{
|
||||
|
||||
@@ -6,29 +6,23 @@
|
||||
|
||||
namespace beast::IP {
|
||||
|
||||
/**
|
||||
* Convert to Endpoint.
|
||||
* The port is set to zero.
|
||||
*/
|
||||
/** Convert to Endpoint.
|
||||
The port is set to zero.
|
||||
*/
|
||||
Endpoint
|
||||
fromAsio(boost::asio::ip::address const& address);
|
||||
|
||||
/**
|
||||
* Convert to Endpoint.
|
||||
*/
|
||||
/** Convert to Endpoint. */
|
||||
Endpoint
|
||||
fromAsio(boost::asio::ip::tcp::endpoint const& endpoint);
|
||||
|
||||
/**
|
||||
* Convert to asio::ip::address.
|
||||
* The port is ignored.
|
||||
*/
|
||||
/** Convert to asio::ip::address.
|
||||
The port is ignored.
|
||||
*/
|
||||
boost::asio::ip::address
|
||||
toAsioAddress(Endpoint const& endpoint);
|
||||
|
||||
/**
|
||||
* Convert to asio::ip::tcp::endpoint.
|
||||
*/
|
||||
/** Convert to asio::ip::tcp::endpoint. */
|
||||
boost::asio::ip::tcp::endpoint
|
||||
toAsioEndpoint(Endpoint const& endpoint);
|
||||
|
||||
|
||||
@@ -6,22 +6,17 @@ namespace beast::IP {
|
||||
|
||||
using AddressV4 = boost::asio::ip::address_v4;
|
||||
|
||||
/**
|
||||
* Returns `true` if the address is a private unroutable address.
|
||||
*/
|
||||
/** Returns `true` if the address is a private unroutable address. */
|
||||
bool
|
||||
isPrivate(AddressV4 const& addr);
|
||||
|
||||
/**
|
||||
* Returns `true` if the address is a public routable address.
|
||||
*/
|
||||
/** Returns `true` if the address is a public routable address. */
|
||||
bool
|
||||
isPublic(AddressV4 const& addr);
|
||||
|
||||
/**
|
||||
* Returns the address class for the given address.
|
||||
* @note Class 'D' represents multicast addresses (224.*.*.*).
|
||||
*/
|
||||
/** Returns the address class for the given address.
|
||||
@note Class 'D' represents multicast addresses (224.*.*.*).
|
||||
*/
|
||||
char
|
||||
getClass(AddressV4 const& address);
|
||||
|
||||
|
||||
@@ -6,15 +6,11 @@ namespace beast::IP {
|
||||
|
||||
using AddressV6 = boost::asio::ip::address_v6;
|
||||
|
||||
/**
|
||||
* Returns `true` if the address is a private unroutable address.
|
||||
*/
|
||||
/** Returns `true` if the address is a private unroutable address. */
|
||||
bool
|
||||
isPrivate(AddressV6 const& addr);
|
||||
|
||||
/**
|
||||
* Returns `true` if the address is a public routable address.
|
||||
*/
|
||||
/** Returns `true` if the address is a public routable address. */
|
||||
bool
|
||||
isPublic(AddressV6 const& addr);
|
||||
|
||||
|
||||
@@ -17,68 +17,51 @@ namespace beast::IP {
|
||||
|
||||
using Port = std::uint16_t;
|
||||
|
||||
/**
|
||||
* A version-independent IP address and port combination.
|
||||
*/
|
||||
/** A version-independent IP address and port combination. */
|
||||
class Endpoint
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Create an unspecified endpoint.
|
||||
*/
|
||||
/** Create an unspecified endpoint. */
|
||||
Endpoint();
|
||||
|
||||
/**
|
||||
* Create an endpoint from the address and optional port.
|
||||
*/
|
||||
/** Create an endpoint from the address and optional port. */
|
||||
explicit Endpoint(Address addr, Port port = 0);
|
||||
|
||||
/**
|
||||
* Create an Endpoint from a string.
|
||||
* If the port is omitted, the endpoint will have a zero port.
|
||||
* @return An optional endpoint; will be `std::nullopt` on failure
|
||||
*/
|
||||
/** Create an Endpoint from a string.
|
||||
If the port is omitted, the endpoint will have a zero port.
|
||||
@return An optional endpoint; will be `std::nullopt` on failure
|
||||
*/
|
||||
static std::optional<Endpoint>
|
||||
fromStringChecked(std::string const& s);
|
||||
static Endpoint
|
||||
fromString(std::string const& s);
|
||||
|
||||
/**
|
||||
* Returns a string representing the endpoint.
|
||||
*/
|
||||
/** Returns a string representing the endpoint. */
|
||||
[[nodiscard]] std::string
|
||||
toString() const;
|
||||
|
||||
/**
|
||||
* Returns the port number on the endpoint.
|
||||
*/
|
||||
/** Returns the port number on the endpoint. */
|
||||
[[nodiscard]] Port
|
||||
port() const
|
||||
{
|
||||
return port_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new Endpoint with a different port.
|
||||
*/
|
||||
/** Returns a new Endpoint with a different port. */
|
||||
[[nodiscard]] Endpoint
|
||||
atPort(Port port) const
|
||||
{
|
||||
return Endpoint(addr_, port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the address portion of this endpoint.
|
||||
*/
|
||||
/** Returns the address portion of this endpoint. */
|
||||
[[nodiscard]] Address const&
|
||||
address() const
|
||||
{
|
||||
return addr_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience accessors for the address part.
|
||||
*/
|
||||
/** Convenience accessors for the address part. */
|
||||
/** @{ */
|
||||
[[nodiscard]] bool
|
||||
isV4() const
|
||||
@@ -102,9 +85,7 @@ public:
|
||||
}
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Arithmetic comparison.
|
||||
*/
|
||||
/** Arithmetic comparison. */
|
||||
/** @{ */
|
||||
friend bool
|
||||
operator==(Endpoint const& lhs, Endpoint const& rhs);
|
||||
@@ -150,45 +131,35 @@ private:
|
||||
|
||||
// Properties
|
||||
|
||||
/**
|
||||
* Returns `true` if the endpoint is a loopback address.
|
||||
*/
|
||||
/** Returns `true` if the endpoint is a loopback address. */
|
||||
inline bool
|
||||
isLoopback(Endpoint const& endpoint)
|
||||
{
|
||||
return isLoopback(endpoint.address());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the endpoint is unspecified.
|
||||
*/
|
||||
/** Returns `true` if the endpoint is unspecified. */
|
||||
inline bool
|
||||
isUnspecified(Endpoint const& endpoint)
|
||||
{
|
||||
return isUnspecified(endpoint.address());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the endpoint is a multicast address.
|
||||
*/
|
||||
/** Returns `true` if the endpoint is a multicast address. */
|
||||
inline bool
|
||||
isMulticast(Endpoint const& endpoint)
|
||||
{
|
||||
return isMulticast(endpoint.address());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the endpoint is a private unroutable address.
|
||||
*/
|
||||
/** Returns `true` if the endpoint is a private unroutable address. */
|
||||
inline bool
|
||||
isPrivate(Endpoint const& endpoint)
|
||||
{
|
||||
return isPrivate(endpoint.address());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the endpoint is a public routable address.
|
||||
*/
|
||||
/** Returns `true` if the endpoint is a public routable address. */
|
||||
inline bool
|
||||
isPublic(Endpoint const& endpoint)
|
||||
{
|
||||
@@ -197,18 +168,14 @@ isPublic(Endpoint const& endpoint)
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns the endpoint represented as a string.
|
||||
*/
|
||||
/** Returns the endpoint represented as a string. */
|
||||
inline std::string
|
||||
to_string(Endpoint const& endpoint)
|
||||
{
|
||||
return endpoint.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Output stream conversion.
|
||||
*/
|
||||
/** Output stream conversion. */
|
||||
template <typename OutputStream>
|
||||
OutputStream&
|
||||
operator<<(OutputStream& os, Endpoint const& endpoint)
|
||||
@@ -217,9 +184,7 @@ operator<<(OutputStream& os, Endpoint const& endpoint)
|
||||
return os;
|
||||
}
|
||||
|
||||
/**
|
||||
* Input stream conversion.
|
||||
*/
|
||||
/** Input stream conversion. */
|
||||
std::istream&
|
||||
operator>>(std::istream& is, Endpoint& endpoint);
|
||||
|
||||
@@ -228,9 +193,7 @@ operator>>(std::istream& is, Endpoint& endpoint);
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace std {
|
||||
/**
|
||||
* std::hash support.
|
||||
*/
|
||||
/** std::hash support. */
|
||||
template <>
|
||||
struct hash<::beast::IP::Endpoint>
|
||||
{
|
||||
@@ -245,9 +208,7 @@ struct hash<::beast::IP::Endpoint>
|
||||
} // namespace std
|
||||
|
||||
namespace boost {
|
||||
/**
|
||||
* boost::hash support.
|
||||
*/
|
||||
/** boost::hash support. */
|
||||
template <>
|
||||
struct hash<::beast::IP::Endpoint>
|
||||
{
|
||||
|
||||
@@ -30,20 +30,17 @@ struct CiEqualPred
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns `true` if `c` is linear white space.
|
||||
*
|
||||
* This excludes the CRLF sequence allowed for line continuations.
|
||||
*/
|
||||
/** Returns `true` if `c` is linear white space.
|
||||
|
||||
This excludes the CRLF sequence allowed for line continuations.
|
||||
*/
|
||||
inline bool
|
||||
isLws(char c)
|
||||
{
|
||||
return c == ' ' || c == '\t';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if `c` is any whitespace character.
|
||||
*/
|
||||
/** Returns `true` if `c` is any whitespace character. */
|
||||
inline bool
|
||||
isWhite(char c)
|
||||
{
|
||||
@@ -90,15 +87,14 @@ trimRight(String const& s)
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* Parse a character sequence of values separated by commas.
|
||||
* Double quotes and escape sequences will be converted. Excess white
|
||||
* space, commas, double quotes, and empty elements are not copied.
|
||||
* Format:
|
||||
* #(token|quoted-string)
|
||||
* Reference:
|
||||
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2
|
||||
*/
|
||||
/** Parse a character sequence of values separated by commas.
|
||||
Double quotes and escape sequences will be converted. Excess white
|
||||
space, commas, double quotes, and empty elements are not copied.
|
||||
Format:
|
||||
#(token|quoted-string)
|
||||
Reference:
|
||||
http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2
|
||||
*/
|
||||
template <
|
||||
class FwdIt,
|
||||
class Result = std::vector<std::basic_string<typename std::iterator_traits<FwdIt>::value_type>>,
|
||||
@@ -193,15 +189,14 @@ splitCommas(boost::beast::string_view const& s)
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Iterates through a comma separated list.
|
||||
*
|
||||
* Meets the requirements of ForwardIterator.
|
||||
*
|
||||
* List defined in rfc2616 2.1.
|
||||
*
|
||||
* @note Values returned may contain backslash escapes.
|
||||
*/
|
||||
/** Iterates through a comma separated list.
|
||||
|
||||
Meets the requirements of ForwardIterator.
|
||||
|
||||
List defined in rfc2616 2.1.
|
||||
|
||||
@note Values returned may contain backslash escapes.
|
||||
*/
|
||||
class ListIterator
|
||||
{
|
||||
using iter_type = boost::string_ref::const_iterator;
|
||||
@@ -328,20 +323,17 @@ ListIterator::increment()
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns true if two strings are equal.
|
||||
*
|
||||
* A case-insensitive comparison is used.
|
||||
*/
|
||||
/** Returns true if two strings are equal.
|
||||
|
||||
A case-insensitive comparison is used.
|
||||
*/
|
||||
inline bool
|
||||
ciEqual(boost::string_ref s1, boost::string_ref s2)
|
||||
{
|
||||
return boost::range::equal(s1, s2, detail::CiEqualPred{});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a range representing the list.
|
||||
*/
|
||||
/** Returns a range representing the list. */
|
||||
inline boost::iterator_range<ListIterator>
|
||||
makeList(boost::string_ref const& field)
|
||||
{
|
||||
@@ -349,11 +341,10 @@ makeList(boost::string_ref const& field)
|
||||
ListIterator{field.begin(), field.end()}, ListIterator{field.end(), field.end()}};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified token exists in the list.
|
||||
*
|
||||
* A case-insensitive comparison is used.
|
||||
*/
|
||||
/** Returns true if the specified token exists in the list.
|
||||
|
||||
A case-insensitive comparison is used.
|
||||
*/
|
||||
template <class = void>
|
||||
bool
|
||||
tokenInList(boost::string_ref const& value, boost::string_ref const& token)
|
||||
|
||||
@@ -19,13 +19,12 @@
|
||||
|
||||
namespace beast::test {
|
||||
|
||||
/**
|
||||
* Mix-in to support tests using asio coroutines.
|
||||
*
|
||||
* Derive from this class and use yield_to to launch test
|
||||
* functions inside coroutines. This is handy for testing
|
||||
* asynchronous asio code.
|
||||
*/
|
||||
/** Mix-in to support tests using asio coroutines.
|
||||
|
||||
Derive from this class and use yield_to to launch test
|
||||
functions inside coroutines. This is handy for testing
|
||||
asynchronous asio code.
|
||||
*/
|
||||
class EnableYieldTo
|
||||
{
|
||||
protected:
|
||||
@@ -39,9 +38,7 @@ private:
|
||||
std::size_t running_ = 0;
|
||||
|
||||
public:
|
||||
/**
|
||||
* The type of yield context passed to functions.
|
||||
*/
|
||||
/// The type of yield context passed to functions.
|
||||
using yield_context = boost::asio::yield_context;
|
||||
|
||||
explicit EnableYieldTo(std::size_t concurrency = 1) : work_(boost::asio::make_work_guard(ios_))
|
||||
@@ -60,27 +57,24 @@ public:
|
||||
t.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the `io_context` associated with the object
|
||||
*/
|
||||
/// Return the `io_context` associated with the object
|
||||
boost::asio::io_context&
|
||||
getIoContext()
|
||||
{
|
||||
return ios_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run one or more functions, each in a coroutine.
|
||||
*
|
||||
* This call will block until all coroutines terminate.
|
||||
*
|
||||
* Each functions should have this signature:
|
||||
* @code
|
||||
* void f(yield_context);
|
||||
* @endcode
|
||||
*
|
||||
* @param fn... One or more functions to invoke.
|
||||
*/
|
||||
/** Run one or more functions, each in a coroutine.
|
||||
|
||||
This call will block until all coroutines terminate.
|
||||
|
||||
Each functions should have this signature:
|
||||
@code
|
||||
void f(yield_context);
|
||||
@endcode
|
||||
|
||||
@param fn... One or more functions to invoke.
|
||||
*/
|
||||
#if BEAST_DOXYGEN
|
||||
template <class... FN>
|
||||
void
|
||||
|
||||
@@ -10,9 +10,7 @@
|
||||
|
||||
namespace beast::unit_test {
|
||||
|
||||
/**
|
||||
* Utility for producing nicely composed output of amounts with units.
|
||||
*/
|
||||
/** Utility for producing nicely composed output of amounts with units. */
|
||||
class Amount
|
||||
{
|
||||
private:
|
||||
|
||||
@@ -6,11 +6,10 @@
|
||||
|
||||
namespace beast::unit_test::detail {
|
||||
|
||||
/**
|
||||
* Adapter to constrain a container interface.
|
||||
* The interface allows for limited read only operations. Derived classes
|
||||
* provide additional behavior.
|
||||
*/
|
||||
/** Adapter to constrain a container interface.
|
||||
The interface allows for limited read only operations. Derived classes
|
||||
provide additional behavior.
|
||||
*/
|
||||
template <class Container>
|
||||
class ConstContainer
|
||||
{
|
||||
@@ -39,27 +38,21 @@ public:
|
||||
using iterator = cont_type::const_iterator;
|
||||
using const_iterator = cont_type::const_iterator;
|
||||
|
||||
/**
|
||||
* Returns `true` if the container is empty.
|
||||
*/
|
||||
/** Returns `true` if the container is empty. */
|
||||
[[nodiscard]] bool
|
||||
empty() const
|
||||
{
|
||||
return cont_.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of items in the container.
|
||||
*/
|
||||
/** Returns the number of items in the container. */
|
||||
[[nodiscard]] size_type
|
||||
size() const
|
||||
{
|
||||
return cont_.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns forward iterators for traversal.
|
||||
*/
|
||||
/** Returns forward iterators for traversal. */
|
||||
/** @{ */
|
||||
[[nodiscard]] const_iterator
|
||||
begin() const
|
||||
|
||||
@@ -10,9 +10,7 @@ namespace beast::unit_test {
|
||||
|
||||
namespace detail {
|
||||
|
||||
/**
|
||||
* Holds test suites registered during static initialization.
|
||||
*/
|
||||
/// Holds test suites registered during static initialization.
|
||||
inline SuiteList&
|
||||
globalSuites()
|
||||
{
|
||||
@@ -36,9 +34,7 @@ struct InsertSuite
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* Holds test suites registered during static initialization.
|
||||
*/
|
||||
/// Holds test suites registered during static initialization.
|
||||
inline SuiteList const&
|
||||
globalSuites()
|
||||
{
|
||||
|
||||
@@ -121,48 +121,42 @@ Selector::operator()(SuiteInfo const& s)
|
||||
|
||||
// Utility functions for producing predicates to select suites.
|
||||
|
||||
/**
|
||||
* Returns a predicate that implements a smart matching rule.
|
||||
* The predicate checks the suite, module, and library fields of the
|
||||
* SuiteInfo in that order. When it finds a match, it changes modes
|
||||
* depending on what was found:
|
||||
*
|
||||
* If a suite is matched first, then only the suite is selected. The
|
||||
* suite may be marked manual.
|
||||
*
|
||||
* If a module is matched first, then only suites from that module
|
||||
* and library not marked manual are selected from then on.
|
||||
*
|
||||
* If a library is matched first, then only suites from that library
|
||||
* not marked manual are selected from then on.
|
||||
*/
|
||||
/** Returns a predicate that implements a smart matching rule.
|
||||
The predicate checks the suite, module, and library fields of the
|
||||
SuiteInfo in that order. When it finds a match, it changes modes
|
||||
depending on what was found:
|
||||
|
||||
If a suite is matched first, then only the suite is selected. The
|
||||
suite may be marked manual.
|
||||
|
||||
If a module is matched first, then only suites from that module
|
||||
and library not marked manual are selected from then on.
|
||||
|
||||
If a library is matched first, then only suites from that library
|
||||
not marked manual are selected from then on.
|
||||
|
||||
*/
|
||||
inline Selector
|
||||
matchAuto(std::string const& name)
|
||||
{
|
||||
return Selector(Selector::ModeT::Automatch, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a predicate that matches all suites not marked manual.
|
||||
*/
|
||||
/** Return a predicate that matches all suites not marked manual. */
|
||||
inline Selector
|
||||
matchAll()
|
||||
{
|
||||
return Selector(Selector::ModeT::All);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a predicate that matches a specific suite.
|
||||
*/
|
||||
/** Returns a predicate that matches a specific suite. */
|
||||
inline Selector
|
||||
matchSuite(std::string const& name)
|
||||
{
|
||||
return Selector(Selector::ModeT::Suite, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a predicate that matches all suites in a library.
|
||||
*/
|
||||
/** Returns a predicate that matches all suites in a library. */
|
||||
inline Selector
|
||||
matchLibrary(std::string const& name)
|
||||
{
|
||||
|
||||
@@ -13,9 +13,7 @@
|
||||
|
||||
namespace beast::unit_test {
|
||||
|
||||
/**
|
||||
* A test runner that stores the results.
|
||||
*/
|
||||
/** A test runner that stores the results. */
|
||||
class Recorder : public Runner
|
||||
{
|
||||
private:
|
||||
@@ -26,9 +24,7 @@ private:
|
||||
public:
|
||||
Recorder() = default;
|
||||
|
||||
/**
|
||||
* Returns a report with the results of all completed suites.
|
||||
*/
|
||||
/** Returns a report with the results of all completed suites. */
|
||||
[[nodiscard]] Results const&
|
||||
report() const
|
||||
{
|
||||
|
||||
@@ -25,10 +25,9 @@ namespace beast::unit_test {
|
||||
|
||||
namespace detail {
|
||||
|
||||
/**
|
||||
* A simple test runner that writes everything to a stream in real time.
|
||||
* The totals are output when the object is destroyed.
|
||||
*/
|
||||
/** A simple test runner that writes everything to a stream in real time.
|
||||
The totals are output when the object is destroyed.
|
||||
*/
|
||||
template <class = void>
|
||||
class Reporter : public Runner
|
||||
{
|
||||
|
||||
@@ -13,15 +13,11 @@
|
||||
|
||||
namespace beast::unit_test {
|
||||
|
||||
/**
|
||||
* Holds a set of test condition outcomes in a testcase.
|
||||
*/
|
||||
/** Holds a set of test condition outcomes in a testcase. */
|
||||
class CaseResults
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Holds the result of evaluating one test condition.
|
||||
*/
|
||||
/** Holds the result of evaluating one test condition. */
|
||||
struct Test
|
||||
{
|
||||
explicit Test(bool pass) : pass(pass)
|
||||
@@ -45,36 +41,28 @@ private:
|
||||
public:
|
||||
TestsT() = default;
|
||||
|
||||
/**
|
||||
* Returns the total number of test conditions.
|
||||
*/
|
||||
/** Returns the total number of test conditions. */
|
||||
[[nodiscard]] std::size_t
|
||||
total() const
|
||||
{
|
||||
return cont().size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of failed test conditions.
|
||||
*/
|
||||
/** Returns the number of failed test conditions. */
|
||||
[[nodiscard]] std::size_t
|
||||
failed() const
|
||||
{
|
||||
return failed_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a successful test condition.
|
||||
*/
|
||||
/** Register a successful test condition. */
|
||||
void
|
||||
pass()
|
||||
{
|
||||
cont().emplace_back(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a failed test condition.
|
||||
*/
|
||||
/** Register a failed test condition. */
|
||||
void
|
||||
fail(std::string const& reason = "")
|
||||
{
|
||||
@@ -86,9 +74,7 @@ private:
|
||||
class LogT : public detail::ConstContainer<std::vector<std::string>>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Insert a string into the log.
|
||||
*/
|
||||
/** Insert a string into the log. */
|
||||
void
|
||||
insert(std::string const& s)
|
||||
{
|
||||
@@ -103,31 +89,23 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of this testcase.
|
||||
*/
|
||||
/** Returns the name of this testcase. */
|
||||
[[nodiscard]] std::string const&
|
||||
name() const
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Memberspace for a container of test condition outcomes.
|
||||
*/
|
||||
/** Memberspace for a container of test condition outcomes. */
|
||||
TestsT tests;
|
||||
|
||||
/**
|
||||
* Memberspace for a container of testcase log messages.
|
||||
*/
|
||||
/** Memberspace for a container of testcase log messages. */
|
||||
LogT log;
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Holds the set of testcase results in a suite.
|
||||
*/
|
||||
/** Holds the set of testcase results in a suite. */
|
||||
class SuiteResults : public detail::ConstContainer<std::vector<CaseResults>>
|
||||
{
|
||||
private:
|
||||
@@ -140,36 +118,28 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of this suite.
|
||||
*/
|
||||
/** Returns the name of this suite. */
|
||||
[[nodiscard]] std::string const&
|
||||
name() const
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of test conditions.
|
||||
*/
|
||||
/** Returns the total number of test conditions. */
|
||||
[[nodiscard]] std::size_t
|
||||
total() const
|
||||
{
|
||||
return total_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of failures.
|
||||
*/
|
||||
/** Returns the number of failures. */
|
||||
[[nodiscard]] std::size_t
|
||||
failed() const
|
||||
{
|
||||
return failed_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a set of testcase results.
|
||||
*/
|
||||
/** Insert a set of testcase results. */
|
||||
/** @{ */
|
||||
void
|
||||
insert(CaseResults&& r)
|
||||
@@ -192,9 +162,7 @@ public:
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// VFALCO TODO Make this a template class using scoped allocators
|
||||
/**
|
||||
* Holds the results of running a set of testsuites.
|
||||
*/
|
||||
/** Holds the results of running a set of testsuites. */
|
||||
class Results : public detail::ConstContainer<std::vector<SuiteResults>>
|
||||
{
|
||||
private:
|
||||
@@ -205,36 +173,28 @@ private:
|
||||
public:
|
||||
Results() = default;
|
||||
|
||||
/**
|
||||
* Returns the total number of test cases.
|
||||
*/
|
||||
/** Returns the total number of test cases. */
|
||||
[[nodiscard]] std::size_t
|
||||
cases() const
|
||||
{
|
||||
return cases_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of test conditions.
|
||||
*/
|
||||
/** Returns the total number of test conditions. */
|
||||
[[nodiscard]] std::size_t
|
||||
total() const
|
||||
{
|
||||
return total_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of failures.
|
||||
*/
|
||||
/** Returns the number of failures. */
|
||||
[[nodiscard]] std::size_t
|
||||
failed() const
|
||||
{
|
||||
return failed_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a set of suite results.
|
||||
*/
|
||||
/** Insert a set of suite results. */
|
||||
/** @{ */
|
||||
void
|
||||
insert(SuiteResults&& r)
|
||||
|
||||
@@ -13,12 +13,11 @@
|
||||
|
||||
namespace beast::unit_test {
|
||||
|
||||
/**
|
||||
* Unit test runner interface.
|
||||
*
|
||||
* Derived classes can customize the reporting behavior. This interface is
|
||||
* injected into the unit_test class to receive the results of the tests.
|
||||
*/
|
||||
/** Unit test runner interface.
|
||||
|
||||
Derived classes can customize the reporting behavior. This interface is
|
||||
injected into the unit_test class to receive the results of the tests.
|
||||
*/
|
||||
class Runner
|
||||
{
|
||||
std::string arg_;
|
||||
@@ -34,132 +33,110 @@ public:
|
||||
Runner&
|
||||
operator=(Runner const&) = delete;
|
||||
|
||||
/**
|
||||
* Set the argument string.
|
||||
*
|
||||
* The argument string is available to suites and
|
||||
* allows for customization of the test. Each suite
|
||||
* defines its own syntax for the argument string.
|
||||
* The same argument is passed to all suites.
|
||||
*/
|
||||
/** Set the argument string.
|
||||
|
||||
The argument string is available to suites and
|
||||
allows for customization of the test. Each suite
|
||||
defines its own syntax for the argument string.
|
||||
The same argument is passed to all suites.
|
||||
*/
|
||||
void
|
||||
arg(std::string const& s)
|
||||
{
|
||||
arg_ = s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the argument string.
|
||||
*/
|
||||
/** Returns the argument string. */
|
||||
[[nodiscard]] std::string const&
|
||||
arg() const
|
||||
{
|
||||
return arg_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the specified suite.
|
||||
* @return `true` if any conditions failed.
|
||||
*/
|
||||
/** Run the specified suite.
|
||||
@return `true` if any conditions failed.
|
||||
*/
|
||||
template <class = void>
|
||||
bool
|
||||
run(SuiteInfo const& s);
|
||||
|
||||
/**
|
||||
* Run a sequence of suites.
|
||||
* The expression
|
||||
* `FwdIter::value_type`
|
||||
* must be convertible to `SuiteInfo`.
|
||||
* @return `true` if any conditions failed.
|
||||
*/
|
||||
/** Run a sequence of suites.
|
||||
The expression
|
||||
`FwdIter::value_type`
|
||||
must be convertible to `SuiteInfo`.
|
||||
@return `true` if any conditions failed.
|
||||
*/
|
||||
template <class FwdIter>
|
||||
bool
|
||||
run(FwdIter first, FwdIter last);
|
||||
|
||||
/**
|
||||
* Conditionally run a sequence of suites.
|
||||
* pred will be called as:
|
||||
* @code
|
||||
* bool pred(SuiteInfo const&);
|
||||
* @endcode
|
||||
* @return `true` if any conditions failed.
|
||||
*/
|
||||
/** Conditionally run a sequence of suites.
|
||||
pred will be called as:
|
||||
@code
|
||||
bool pred(SuiteInfo const&);
|
||||
@endcode
|
||||
@return `true` if any conditions failed.
|
||||
*/
|
||||
template <class FwdIter, class Pred>
|
||||
bool
|
||||
runIf(FwdIter first, FwdIter last, Pred pred = Pred{});
|
||||
|
||||
/**
|
||||
* Run all suites in a container.
|
||||
* @return `true` if any conditions failed.
|
||||
*/
|
||||
/** Run all suites in a container.
|
||||
@return `true` if any conditions failed.
|
||||
*/
|
||||
template <class SequenceContainer>
|
||||
bool
|
||||
runEach(SequenceContainer const& c);
|
||||
|
||||
/**
|
||||
* Conditionally run suites in a container.
|
||||
* pred will be called as:
|
||||
* @code
|
||||
* bool pred(SuiteInfo const&);
|
||||
* @endcode
|
||||
* @return `true` if any conditions failed.
|
||||
*/
|
||||
/** Conditionally run suites in a container.
|
||||
pred will be called as:
|
||||
@code
|
||||
bool pred(SuiteInfo const&);
|
||||
@endcode
|
||||
@return `true` if any conditions failed.
|
||||
*/
|
||||
template <class SequenceContainer, class Pred>
|
||||
bool
|
||||
runEachIf(SequenceContainer const& c, Pred pred = Pred{});
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Called when a new suite starts.
|
||||
*/
|
||||
/// Called when a new suite starts.
|
||||
virtual void
|
||||
onSuiteBegin(SuiteInfo const&)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a suite ends.
|
||||
*/
|
||||
/// Called when a suite ends.
|
||||
virtual void
|
||||
onSuiteEnd()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a new case starts.
|
||||
*/
|
||||
/// Called when a new case starts.
|
||||
virtual void
|
||||
onCaseBegin(std::string const&)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a new case ends.
|
||||
*/
|
||||
/// Called when a new case ends.
|
||||
virtual void
|
||||
onCaseEnd()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called for each passing condition.
|
||||
*/
|
||||
/// Called for each passing condition.
|
||||
virtual void
|
||||
onPass()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called for each failing condition.
|
||||
*/
|
||||
/// Called for each failing condition.
|
||||
virtual void
|
||||
onFail(std::string const&)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a test logs output.
|
||||
*/
|
||||
/// Called when a test logs output.
|
||||
virtual void
|
||||
onLog(std::string const&)
|
||||
{
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
|
||||
#include <xrpl/beast/unit_test/runner.h>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/throw_exception.hpp>
|
||||
|
||||
#include <exception>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
@@ -27,7 +27,7 @@ makeReason(String const& reason, char const* file, int line)
|
||||
std::string s(reason);
|
||||
if (!s.empty())
|
||||
s.append(": ");
|
||||
namespace fs = std::filesystem;
|
||||
namespace fs = boost::filesystem;
|
||||
s.append(fs::path{file}.filename().string());
|
||||
s.append("(");
|
||||
s.append(boost::lexical_cast<std::string>(line));
|
||||
@@ -41,14 +41,13 @@ class Thread;
|
||||
|
||||
enum class AbortT { NoAbortOnFail, AbortOnFail };
|
||||
|
||||
/**
|
||||
* A testsuite class.
|
||||
*
|
||||
* Derived classes execute a series of testcases, where each testcase is
|
||||
* a series of pass/fail tests. To provide a unit test using this class,
|
||||
* derive from it and use the BEAST_DEFINE_UNIT_TEST macro in a
|
||||
* translation unit.
|
||||
*/
|
||||
/** A testsuite class.
|
||||
|
||||
Derived classes execute a series of testcases, where each testcase is
|
||||
a series of pass/fail tests. To provide a unit test using this class,
|
||||
derive from it and use the BEAST_DEFINE_UNIT_TEST macro in a
|
||||
translation unit.
|
||||
*/
|
||||
class Suite
|
||||
{
|
||||
private:
|
||||
@@ -119,17 +118,16 @@ private:
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a new testcase.
|
||||
*
|
||||
* A testcase is a series of evaluated test conditions. A test
|
||||
* suite may have multiple test cases. A test is associated with
|
||||
* the last opened testcase. When the test first runs, a default
|
||||
* unnamed case is opened. Tests with only one case may omit the
|
||||
* call to testcase.
|
||||
*
|
||||
* @param abort Determines if suite continues running after a failure.
|
||||
*/
|
||||
/** Open a new testcase.
|
||||
|
||||
A testcase is a series of evaluated test conditions. A test
|
||||
suite may have multiple test cases. A test is associated with
|
||||
the last opened testcase. When the test first runs, a default
|
||||
unnamed case is opened. Tests with only one case may omit the
|
||||
call to testcase.
|
||||
|
||||
@param abort Determines if suite continues running after a failure.
|
||||
*/
|
||||
void
|
||||
operator()(std::string const& name, AbortT abort = AbortT::NoAbortOnFail);
|
||||
|
||||
@@ -142,23 +140,19 @@ private:
|
||||
};
|
||||
|
||||
public:
|
||||
/**
|
||||
* Logging output stream.
|
||||
*
|
||||
* Text sent to the log output stream will be forwarded to
|
||||
* the output stream associated with the runner.
|
||||
*/
|
||||
/** Logging output stream.
|
||||
|
||||
Text sent to the log output stream will be forwarded to
|
||||
the output stream associated with the runner.
|
||||
*/
|
||||
LogOs<char> log;
|
||||
|
||||
/**
|
||||
* Memberspace for declaring test cases.
|
||||
*/
|
||||
/** Memberspace for declaring test cases. */
|
||||
TestcaseT testcase;
|
||||
|
||||
/**
|
||||
* Returns the "current" running suite.
|
||||
* If no suite is running, nullptr is returned.
|
||||
*/
|
||||
/** Returns the "current" running suite.
|
||||
If no suite is running, nullptr is returned.
|
||||
*/
|
||||
static Suite*
|
||||
thisSuite()
|
||||
{
|
||||
@@ -174,34 +168,30 @@ public:
|
||||
Suite&
|
||||
operator=(Suite const&) = delete;
|
||||
|
||||
/**
|
||||
* Invokes the test using the specified runner.
|
||||
*
|
||||
* Data members are set up here instead of the constructor as a
|
||||
* convenience to writing the derived class to avoid repetition of
|
||||
* forwarded constructor arguments to the base.
|
||||
* Normally this is called by the framework for you.
|
||||
*/
|
||||
/** Invokes the test using the specified runner.
|
||||
|
||||
Data members are set up here instead of the constructor as a
|
||||
convenience to writing the derived class to avoid repetition of
|
||||
forwarded constructor arguments to the base.
|
||||
Normally this is called by the framework for you.
|
||||
*/
|
||||
template <class = void>
|
||||
void
|
||||
operator()(Runner& r);
|
||||
|
||||
/**
|
||||
* Record a successful test condition.
|
||||
*/
|
||||
/** Record a successful test condition. */
|
||||
template <class = void>
|
||||
void
|
||||
pass();
|
||||
|
||||
/**
|
||||
* Record a failure.
|
||||
*
|
||||
* @param reason Optional text added to the output on a failure.
|
||||
*
|
||||
* @param file The source code file where the test failed.
|
||||
*
|
||||
* @param line The source code line number where the test failed.
|
||||
*/
|
||||
/** Record a failure.
|
||||
|
||||
@param reason Optional text added to the output on a failure.
|
||||
|
||||
@param file The source code file where the test failed.
|
||||
|
||||
@param line The source code line number where the test failed.
|
||||
*/
|
||||
/** @{ */
|
||||
template <class String>
|
||||
void
|
||||
@@ -212,24 +202,23 @@ public:
|
||||
fail(std::string const& reason = "");
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Evaluate a test condition.
|
||||
*
|
||||
* This function provides improved logging by incorporating the
|
||||
* file name and line number into the reported output on failure,
|
||||
* as well as additional text specified by the caller.
|
||||
*
|
||||
* @param shouldBeTrue The condition to test. The condition
|
||||
* is evaluated in a boolean context.
|
||||
*
|
||||
* @param reason Optional added text to output on a failure.
|
||||
*
|
||||
* @param file The source code file where the test failed.
|
||||
*
|
||||
* @param line The source code line number where the test failed.
|
||||
*
|
||||
* @return `true` if the test condition indicates success.
|
||||
*/
|
||||
/** Evaluate a test condition.
|
||||
|
||||
This function provides improved logging by incorporating the
|
||||
file name and line number into the reported output on failure,
|
||||
as well as additional text specified by the caller.
|
||||
|
||||
@param shouldBeTrue The condition to test. The condition
|
||||
is evaluated in a boolean context.
|
||||
|
||||
@param reason Optional added text to output on a failure.
|
||||
|
||||
@param file The source code file where the test failed.
|
||||
|
||||
@param line The source code line number where the test failed.
|
||||
|
||||
@return `true` if the test condition indicates success.
|
||||
*/
|
||||
/** @{ */
|
||||
template <class Condition>
|
||||
bool
|
||||
@@ -286,19 +275,15 @@ public:
|
||||
return unexcept(f, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the argument associated with the runner.
|
||||
*/
|
||||
/** Return the argument associated with the runner. */
|
||||
std::string const&
|
||||
arg() const
|
||||
{
|
||||
return runner_->arg();
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED
|
||||
* @return `true` if the test condition indicates success(a false value)
|
||||
*/
|
||||
// DEPRECATED
|
||||
// @return `true` if the test condition indicates success(a false value)
|
||||
template <class Condition, class String>
|
||||
bool
|
||||
unexpected(Condition shouldBeFalse, String const& reason);
|
||||
@@ -320,9 +305,7 @@ private:
|
||||
return &kPTs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the suite.
|
||||
*/
|
||||
/** Runs the suite. */
|
||||
virtual void
|
||||
run() = 0;
|
||||
|
||||
@@ -575,20 +558,18 @@ Suite::run(Runner& r)
|
||||
}
|
||||
|
||||
#ifndef BEAST_EXPECT
|
||||
/**
|
||||
* Check a precondition.
|
||||
*
|
||||
* If the condition is false, the file and line number are reported.
|
||||
*/
|
||||
/** Check a precondition.
|
||||
|
||||
If the condition is false, the file and line number are reported.
|
||||
*/
|
||||
#define BEAST_EXPECT(cond) expect(cond, __FILE__, __LINE__)
|
||||
#endif
|
||||
|
||||
#ifndef BEAST_EXPECTS
|
||||
/**
|
||||
* Check a precondition.
|
||||
*
|
||||
* If the condition is false, the file and line number are reported.
|
||||
*/
|
||||
/** Check a precondition.
|
||||
|
||||
If the condition is false, the file and line number are reported.
|
||||
*/
|
||||
#define BEAST_EXPECTS(cond, reason) \
|
||||
((cond) ? (pass(), true) : (fail((reason), __FILE__, __LINE__), false))
|
||||
#endif
|
||||
@@ -612,43 +593,41 @@ Suite::run(Runner& r)
|
||||
//
|
||||
#ifndef BEAST_DEFINE_TESTSUITE
|
||||
|
||||
/**
|
||||
* Enables insertion of test suites into the global container.
|
||||
* The default is to insert all test suite definitions into the global
|
||||
* container. If BEAST_DEFINE_TESTSUITE is user defined, this macro
|
||||
* has no effect.
|
||||
*/
|
||||
/** Enables insertion of test suites into the global container.
|
||||
The default is to insert all test suite definitions into the global
|
||||
container. If BEAST_DEFINE_TESTSUITE is user defined, this macro
|
||||
has no effect.
|
||||
*/
|
||||
#ifndef BEAST_NO_UNIT_TEST_INLINE
|
||||
#define BEAST_NO_UNIT_TEST_INLINE 0
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Define a unit test suite.
|
||||
*
|
||||
* Class The type representing the class being tested.
|
||||
* Module Identifies the module.
|
||||
* Library Identifies the library.
|
||||
*
|
||||
* The declaration for the class implementing the test should be the same
|
||||
* as Class ## _test. For example, if Class is aged_ordered_container, the
|
||||
* test class must be declared as:
|
||||
*
|
||||
* @code
|
||||
*
|
||||
* struct aged_ordered_container_test : beast::unit_test::suite
|
||||
* {
|
||||
* //...
|
||||
* };
|
||||
*
|
||||
* @endcode
|
||||
*
|
||||
* The macro invocation must appear in the same namespace as the test class.
|
||||
*
|
||||
* Unit test priorities were introduced so parallel unit_test::suites would
|
||||
* execute faster. Suites with longer running times have higher priorities
|
||||
* than unit tests with shorter running times. Suites with no priorities
|
||||
* are assumed to run most quickly, so they run last.
|
||||
*/
|
||||
/** Define a unit test suite.
|
||||
|
||||
Class The type representing the class being tested.
|
||||
Module Identifies the module.
|
||||
Library Identifies the library.
|
||||
|
||||
The declaration for the class implementing the test should be the same
|
||||
as Class ## _test. For example, if Class is aged_ordered_container, the
|
||||
test class must be declared as:
|
||||
|
||||
@code
|
||||
|
||||
struct aged_ordered_container_test : beast::unit_test::suite
|
||||
{
|
||||
//...
|
||||
};
|
||||
|
||||
@endcode
|
||||
|
||||
The macro invocation must appear in the same namespace as the test class.
|
||||
|
||||
Unit test priorities were introduced so parallel unit_test::suites would
|
||||
execute faster. Suites with longer running times have higher priorities
|
||||
than unit tests with shorter running times. Suites with no priorities
|
||||
are assumed to run most quickly, so they run last.
|
||||
*/
|
||||
|
||||
#if BEAST_NO_UNIT_TEST_INLINE
|
||||
#define BEAST_DEFINE_TESTSUITE(Class, Module, Library)
|
||||
|
||||
@@ -13,9 +13,7 @@ namespace beast::unit_test {
|
||||
|
||||
class Runner;
|
||||
|
||||
/**
|
||||
* Associates a unit test type with metadata.
|
||||
*/
|
||||
/** Associates a unit test type with metadata. */
|
||||
class SuiteInfo
|
||||
{
|
||||
using run_type = std::function<void(Runner&)>;
|
||||
@@ -62,27 +60,21 @@ public:
|
||||
return library_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if this suite only runs manually.
|
||||
*/
|
||||
/// Returns `true` if this suite only runs manually.
|
||||
[[nodiscard]] bool
|
||||
manual() const
|
||||
{
|
||||
return manual_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the canonical suite name as a string.
|
||||
*/
|
||||
/// Return the canonical suite name as a string.
|
||||
[[nodiscard]] std::string
|
||||
fullName() const
|
||||
{
|
||||
return library_ + "." + module_ + "." + name_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a new instance of the associated test suite.
|
||||
*/
|
||||
/// Run a new instance of the associated test suite.
|
||||
void
|
||||
run(Runner& r) const
|
||||
{
|
||||
@@ -101,9 +93,7 @@ public:
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Convenience for producing SuiteInfo for a given test type.
|
||||
*/
|
||||
/// Convenience for producing SuiteInfo for a given test type.
|
||||
template <class Suite>
|
||||
SuiteInfo
|
||||
makeSuiteInfo(std::string name, std::string module, std::string library, bool manual, int priority)
|
||||
|
||||
@@ -16,9 +16,7 @@
|
||||
|
||||
namespace beast::unit_test {
|
||||
|
||||
/**
|
||||
* A container of test suites.
|
||||
*/
|
||||
/// A container of test suites.
|
||||
class SuiteList : public detail::ConstContainer<std::set<SuiteInfo>>
|
||||
{
|
||||
private:
|
||||
@@ -28,11 +26,10 @@ private:
|
||||
#endif
|
||||
|
||||
public:
|
||||
/**
|
||||
* Insert a suite into the set.
|
||||
*
|
||||
* The suite must not already exist.
|
||||
*/
|
||||
/** Insert a suite into the set.
|
||||
|
||||
The suite must not already exist.
|
||||
*/
|
||||
template <class Suite>
|
||||
void
|
||||
insert(char const* name, char const* module, char const* library, bool manual, int priority);
|
||||
|
||||
@@ -14,9 +14,7 @@
|
||||
|
||||
namespace beast::unit_test {
|
||||
|
||||
/**
|
||||
* Replacement for std::thread that handles exceptions in unit tests.
|
||||
*/
|
||||
/** Replacement for std::thread that handles exceptions in unit tests. */
|
||||
class Thread
|
||||
{
|
||||
private:
|
||||
|
||||
@@ -10,9 +10,7 @@
|
||||
|
||||
namespace beast {
|
||||
|
||||
/**
|
||||
* Severity level / threshold of a Journal message.
|
||||
*/
|
||||
/** Severity level / threshold of a Journal message. */
|
||||
enum class Severity : std::uint8_t {
|
||||
All = 0,
|
||||
|
||||
@@ -27,19 +25,18 @@ enum class Severity : std::uint8_t {
|
||||
None = Disabled
|
||||
};
|
||||
|
||||
/**
|
||||
* A generic endpoint for log messages.
|
||||
*
|
||||
* The Journal has a few simple goals:
|
||||
*
|
||||
* * To be light-weight and copied by value.
|
||||
* * To allow logging statements to be left in source code.
|
||||
* * The logging is controlled at run-time based on a logging threshold.
|
||||
*
|
||||
* It is advisable to check Journal::active(level) prior to formatting log
|
||||
* text. Doing so sidesteps expensive text formatting when the results
|
||||
* will not be sent to the log.
|
||||
*/
|
||||
/** A generic endpoint for log messages.
|
||||
|
||||
The Journal has a few simple goals:
|
||||
|
||||
* To be light-weight and copied by value.
|
||||
* To allow logging statements to be left in source code.
|
||||
* The logging is controlled at run-time based on a logging threshold.
|
||||
|
||||
It is advisable to check Journal::active(level) prior to formatting log
|
||||
text. Doing so sidesteps expensive text formatting when the results
|
||||
will not be sent to the log.
|
||||
*/
|
||||
class Journal
|
||||
{
|
||||
public:
|
||||
@@ -52,9 +49,7 @@ private:
|
||||
public:
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Abstraction for the underlying message destination.
|
||||
*/
|
||||
/** Abstraction for the underlying message destination. */
|
||||
class Sink
|
||||
{
|
||||
protected:
|
||||
@@ -68,47 +63,36 @@ public:
|
||||
Sink&
|
||||
operator=(Sink const& lhs) = delete;
|
||||
|
||||
/**
|
||||
* Returns `true` if text at the passed severity produces output.
|
||||
*/
|
||||
/** Returns `true` if text at the passed severity produces output. */
|
||||
[[nodiscard]] virtual bool
|
||||
active(Severity level) const;
|
||||
|
||||
/**
|
||||
* Returns `true` if a message is also written to the Output Window
|
||||
* (MSVC).
|
||||
*/
|
||||
/** Returns `true` if a message is also written to the Output Window
|
||||
* (MSVC). */
|
||||
[[nodiscard]] virtual bool
|
||||
console() const;
|
||||
|
||||
/**
|
||||
* Set whether messages are also written to the Output Window (MSVC).
|
||||
/** Set whether messages are also written to the Output Window (MSVC).
|
||||
*/
|
||||
virtual void
|
||||
console(bool output);
|
||||
|
||||
/**
|
||||
* Returns the minimum severity level this sink will report.
|
||||
*/
|
||||
/** Returns the minimum severity level this sink will report. */
|
||||
[[nodiscard]] virtual Severity
|
||||
threshold() const;
|
||||
|
||||
/**
|
||||
* Set the minimum severity this sink will report.
|
||||
*/
|
||||
/** Set the minimum severity this sink will report. */
|
||||
virtual void
|
||||
threshold(Severity thresh);
|
||||
|
||||
/**
|
||||
* Write text to the sink at the specified severity.
|
||||
* A conforming implementation will not write the text if the passed
|
||||
* level is below the current threshold().
|
||||
*/
|
||||
/** Write text to the sink at the specified severity.
|
||||
A conforming implementation will not write the text if the passed
|
||||
level is below the current threshold().
|
||||
*/
|
||||
virtual void
|
||||
write(Severity level, std::string const& text) = 0;
|
||||
|
||||
/**
|
||||
* Bypass filter and write text to the sink at the specified severity.
|
||||
/** Bypass filter and write text to the sink at the specified severity.
|
||||
* Always write the message, but maintain the same formatting as if
|
||||
* it passed through a level filter.
|
||||
*
|
||||
@@ -132,9 +116,7 @@ public:
|
||||
static_assert(std::is_nothrow_destructible_v<Sink>);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Returns a Sink which does nothing.
|
||||
*/
|
||||
/** Returns a Sink which does nothing. */
|
||||
static Sink&
|
||||
getNullSink();
|
||||
|
||||
@@ -192,33 +174,26 @@ public:
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
public:
|
||||
/**
|
||||
* Provide a light-weight way to check active() before string formatting
|
||||
*/
|
||||
/** Provide a light-weight way to check active() before string formatting */
|
||||
class Stream
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Create a stream which produces no output.
|
||||
*/
|
||||
/** Create a stream which produces no output. */
|
||||
explicit Stream() : sink_(getNullSink()), level_(Severity::Disabled)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a stream that writes at the given level.
|
||||
*
|
||||
* Constructor is inlined so checking active() very inexpensive.
|
||||
*/
|
||||
/** Create a stream that writes at the given level.
|
||||
|
||||
Constructor is inlined so checking active() very inexpensive.
|
||||
*/
|
||||
Stream(Sink& sink, Severity level) : sink_(sink), level_(level)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
level_ < Severity::Disabled, "beast::Journal::Stream::Stream : maximum level");
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct or copy another Stream.
|
||||
*/
|
||||
/** Construct or copy another Stream. */
|
||||
Stream(Stream const& other) : Stream(other.sink_, other.level_)
|
||||
{
|
||||
}
|
||||
@@ -226,27 +201,21 @@ public:
|
||||
Stream&
|
||||
operator=(Stream const& other) = delete;
|
||||
|
||||
/**
|
||||
* Returns the Sink that this Stream writes to.
|
||||
*/
|
||||
/** Returns the Sink that this Stream writes to. */
|
||||
[[nodiscard]] Sink&
|
||||
sink() const
|
||||
{
|
||||
return sink_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Severity level of messages this Stream reports.
|
||||
*/
|
||||
/** Returns the Severity level of messages this Stream reports. */
|
||||
[[nodiscard]] Severity
|
||||
level() const
|
||||
{
|
||||
return level_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if sink logs anything at this stream's level.
|
||||
*/
|
||||
/** Returns `true` if sink logs anything at this stream's level. */
|
||||
/** @{ */
|
||||
[[nodiscard]] bool
|
||||
active() const
|
||||
@@ -261,9 +230,7 @@ public:
|
||||
}
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Output stream support.
|
||||
*/
|
||||
/** Output stream support. */
|
||||
/** @{ */
|
||||
ScopedStream
|
||||
operator<<(std::ostream& manip(std::ostream&)) const;
|
||||
@@ -289,50 +256,39 @@ public:
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Journal has no default constructor.
|
||||
*/
|
||||
/** Journal has no default constructor. */
|
||||
Journal() = delete;
|
||||
|
||||
/**
|
||||
* Create a journal that writes to the specified sink.
|
||||
*/
|
||||
/** Create a journal that writes to the specified sink. */
|
||||
explicit Journal(Sink& sink) : sink_(&sink)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Sink associated with this Journal.
|
||||
*/
|
||||
/** Returns the Sink associated with this Journal. */
|
||||
[[nodiscard]] Sink&
|
||||
sink() const
|
||||
{
|
||||
return *sink_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a stream for this sink, with the specified severity level.
|
||||
*/
|
||||
/** Returns a stream for this sink, with the specified severity level. */
|
||||
[[nodiscard]] Stream
|
||||
stream(Severity level) const
|
||||
{
|
||||
return Stream(*sink_, level);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if any message would be logged at this severity level.
|
||||
* For a message to be logged, the severity must be at or above the
|
||||
* sink's severity threshold.
|
||||
*/
|
||||
/** Returns `true` if any message would be logged at this severity level.
|
||||
For a message to be logged, the severity must be at or above the
|
||||
sink's severity threshold.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
active(Severity level) const
|
||||
{
|
||||
return sink_->active(level);
|
||||
}
|
||||
|
||||
/**
|
||||
* Severity stream access functions.
|
||||
*/
|
||||
/** Severity stream access functions. */
|
||||
/** @{ */
|
||||
[[nodiscard]] Stream
|
||||
trace() const
|
||||
|
||||
@@ -12,9 +12,7 @@ namespace beast {
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Abstract stream with RAII containers that produce a property tree.
|
||||
*/
|
||||
/** Abstract stream with RAII containers that produce a property tree. */
|
||||
class PropertyStream
|
||||
{
|
||||
public:
|
||||
@@ -308,9 +306,7 @@ public:
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Subclasses can be called to write to a stream and have children.
|
||||
*/
|
||||
/** Subclasses can be called to write to a stream and have children. */
|
||||
class PropertyStream::Source
|
||||
{
|
||||
private:
|
||||
@@ -328,22 +324,17 @@ public:
|
||||
Source&
|
||||
operator=(Source const&) = delete;
|
||||
|
||||
/**
|
||||
* Returns the name of this source.
|
||||
*/
|
||||
/** Returns the name of this source. */
|
||||
[[nodiscard]] std::string const&
|
||||
name() const;
|
||||
|
||||
/**
|
||||
* Add a child source.
|
||||
*/
|
||||
/** Add a child source. */
|
||||
void
|
||||
add(Source& source);
|
||||
|
||||
/**
|
||||
* Add a child source by pointer.
|
||||
* The source pointer is returned so it can be used in ctor-initializers.
|
||||
*/
|
||||
/** Add a child source by pointer.
|
||||
The source pointer is returned so it can be used in ctor-initializers.
|
||||
*/
|
||||
template <class Derived>
|
||||
Derived*
|
||||
add(Derived* child)
|
||||
@@ -352,55 +343,45 @@ public:
|
||||
return child;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a child source from this Source.
|
||||
*/
|
||||
/** Remove a child source from this Source. */
|
||||
void
|
||||
remove(Source& child);
|
||||
|
||||
/**
|
||||
* Remove all child sources from this Source.
|
||||
*/
|
||||
/** Remove all child sources from this Source. */
|
||||
void
|
||||
removeAll();
|
||||
|
||||
/**
|
||||
* Write only this Source to the stream.
|
||||
*/
|
||||
/** Write only this Source to the stream. */
|
||||
void
|
||||
writeOne(PropertyStream& stream);
|
||||
|
||||
/**
|
||||
* write this source and all its children recursively to the stream.
|
||||
*/
|
||||
/** write this source and all its children recursively to the stream. */
|
||||
void
|
||||
write(PropertyStream& stream);
|
||||
|
||||
/**
|
||||
* Parse the path and write the corresponding Source and optional children.
|
||||
* If the source is found, it is written. If the wildcard character '*'
|
||||
* exists as the last character in the path, then all the children are
|
||||
* written recursively.
|
||||
*/
|
||||
/** Parse the path and write the corresponding Source and optional children.
|
||||
If the source is found, it is written. If the wildcard character '*'
|
||||
exists as the last character in the path, then all the children are
|
||||
written recursively.
|
||||
*/
|
||||
void
|
||||
write(PropertyStream& stream, std::string const& path);
|
||||
|
||||
/**
|
||||
* Parse the dot-delimited Source path and return the result.
|
||||
* The first value will be a pointer to the Source object corresponding
|
||||
* to the given path. If no Source object exists, then the first value
|
||||
* will be nullptr and the second value will be undefined.
|
||||
* The second value is a boolean indicating whether or not the path string
|
||||
* specifies the wildcard character '*' as the last character.
|
||||
*
|
||||
* print statement examples
|
||||
* "parent.child" prints child and all of its children
|
||||
* "parent.child." start at the parent and print down to child
|
||||
* "parent.grandchild" prints nothing- grandchild not direct descendent
|
||||
* "parent.grandchild." starts at the parent and prints down to grandchild
|
||||
* "parent.grandchild.*" starts at parent, print through grandchild
|
||||
* children
|
||||
*/
|
||||
/** Parse the dot-delimited Source path and return the result.
|
||||
The first value will be a pointer to the Source object corresponding
|
||||
to the given path. If no Source object exists, then the first value
|
||||
will be nullptr and the second value will be undefined.
|
||||
The second value is a boolean indicating whether or not the path string
|
||||
specifies the wildcard character '*' as the last character.
|
||||
|
||||
print statement examples
|
||||
"parent.child" prints child and all of its children
|
||||
"parent.child." start at the parent and print down to child
|
||||
"parent.grandchild" prints nothing- grandchild not direct descendent
|
||||
"parent.grandchild." starts at the parent and prints down to grandchild
|
||||
"parent.grandchild.*" starts at parent, print through grandchild
|
||||
children
|
||||
*/
|
||||
std::pair<Source*, bool>
|
||||
find(std::string path);
|
||||
|
||||
@@ -420,10 +401,9 @@ public:
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Subclass override.
|
||||
* The default version does nothing.
|
||||
*/
|
||||
/** Subclass override.
|
||||
The default version does nothing.
|
||||
*/
|
||||
virtual void
|
||||
onWrite(Map&);
|
||||
};
|
||||
|
||||
@@ -7,9 +7,7 @@
|
||||
|
||||
namespace beast {
|
||||
|
||||
/**
|
||||
* Wraps a Journal::Sink to prefix its output with a string.
|
||||
*/
|
||||
/** Wraps a Journal::Sink to prefix its output with a string. */
|
||||
|
||||
// A WrappedSink both is a Sink and has a Sink:
|
||||
// o It inherits from Sink so it has the correct interface.
|
||||
|
||||
@@ -4,23 +4,22 @@
|
||||
|
||||
namespace beast {
|
||||
|
||||
/**
|
||||
* Zero allows classes to offer efficient comparisons to zero.
|
||||
*
|
||||
* Zero is a struct to allow classes to efficiently compare with zero without
|
||||
* requiring an rvalue construction.
|
||||
*
|
||||
* It's often the case that we have classes which combine a number and a unit.
|
||||
* In such cases, comparisons like t > 0 or t != 0 make sense, but comparisons
|
||||
* like t > 1 or t != 1 do not.
|
||||
*
|
||||
* The class Zero allows such comparisons to be easily made.
|
||||
*
|
||||
* The comparing class T either needs to have a method called signum() which
|
||||
* returns a positive number, 0, or a negative; or there needs to be a signum
|
||||
* function which resolves in the namespace which takes an instance of T and
|
||||
* returns a positive, zero or negative number.
|
||||
*/
|
||||
/** Zero allows classes to offer efficient comparisons to zero.
|
||||
|
||||
Zero is a struct to allow classes to efficiently compare with zero without
|
||||
requiring an rvalue construction.
|
||||
|
||||
It's often the case that we have classes which combine a number and a unit.
|
||||
In such cases, comparisons like t > 0 or t != 0 make sense, but comparisons
|
||||
like t > 1 or t != 1 do not.
|
||||
|
||||
The class Zero allows such comparisons to be easily made.
|
||||
|
||||
The comparing class T either needs to have a method called signum() which
|
||||
returns a positive number, 0, or a negative; or there needs to be a signum
|
||||
function which resolves in the namespace which takes an instance of T and
|
||||
returns a positive, zero or negative number.
|
||||
*/
|
||||
|
||||
struct Zero
|
||||
{
|
||||
@@ -29,9 +28,7 @@ struct Zero
|
||||
|
||||
inline constexpr Zero kZero{};
|
||||
|
||||
/**
|
||||
* Default implementation of signum calls the method on the class.
|
||||
*/
|
||||
/** Default implementation of signum calls the method on the class. */
|
||||
template <typename T>
|
||||
auto
|
||||
signum(T const& t)
|
||||
|
||||
@@ -4,9 +4,7 @@
|
||||
|
||||
namespace beast {
|
||||
|
||||
/**
|
||||
* Makes T const or non const depending on a bool.
|
||||
*/
|
||||
/** Makes T const or non const depending on a bool. */
|
||||
template <bool IsConst, class T>
|
||||
struct MaybeConst
|
||||
{
|
||||
@@ -15,9 +13,7 @@ struct MaybeConst
|
||||
conditional_t<IsConst, typename std::remove_const<T>::type const, std::remove_const_t<T>>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Alias for omitting `typename`.
|
||||
*/
|
||||
/** Alias for omitting `typename`. */
|
||||
template <bool IsConst, class T>
|
||||
using maybe_const_t = MaybeConst<IsConst, T>::type;
|
||||
|
||||
|
||||
@@ -1,58 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <iomanip>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
|
||||
namespace beast {
|
||||
|
||||
/**
|
||||
* Generate a unique, non-existing path under @p base with an optional @p prefix
|
||||
* and a random hex suffix.
|
||||
*
|
||||
* Attempts up to @p maxAttempts paths. Throws `std::runtime_error` if a
|
||||
* unique path cannot be found or if the filesystem returns an error while
|
||||
* checking for existence.
|
||||
*/
|
||||
inline std::filesystem::path
|
||||
uniqueRandomPath(
|
||||
std::filesystem::path const& base,
|
||||
std::size_t maxAttempts = 100,
|
||||
std::string const& prefix = "")
|
||||
{
|
||||
std::random_device rd;
|
||||
for (std::size_t attempt = 0; attempt < maxAttempts; ++attempt)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << prefix << std::hex << std::setfill('0') << std::setw(8) << rd() << std::setw(8)
|
||||
<< rd();
|
||||
auto candidate = base / oss.str();
|
||||
std::error_code ec;
|
||||
bool const exists = std::filesystem::exists(candidate, ec);
|
||||
if (ec)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"Unable to check path '" + candidate.string() + "': " + ec.message());
|
||||
}
|
||||
if (!exists)
|
||||
return candidate;
|
||||
}
|
||||
throw std::runtime_error("Unable to generate a unique path under '" + base.string() + "'");
|
||||
}
|
||||
/** RAII temporary directory.
|
||||
|
||||
/**
|
||||
* RAII temporary directory.
|
||||
*
|
||||
* The directory and all its contents are deleted when
|
||||
* the instance of `TempDir` is destroyed.
|
||||
*/
|
||||
The directory and all its contents are deleted when
|
||||
the instance of `temp_dir` is destroyed.
|
||||
*/
|
||||
class TempDir
|
||||
{
|
||||
std::filesystem::path path_;
|
||||
boost::filesystem::path path_;
|
||||
|
||||
public:
|
||||
#if !GENERATING_DOCS
|
||||
@@ -61,40 +22,37 @@ public:
|
||||
operator=(TempDir const&) = delete;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Construct a temporary directory.
|
||||
*/
|
||||
/// Construct a temporary directory.
|
||||
TempDir()
|
||||
{
|
||||
path_ = uniqueRandomPath(std::filesystem::temp_directory_path());
|
||||
std::filesystem::create_directory(path_);
|
||||
auto const dir = boost::filesystem::temp_directory_path();
|
||||
do
|
||||
{
|
||||
path_ = dir / boost::filesystem::unique_path();
|
||||
} while (boost::filesystem::exists(path_));
|
||||
boost::filesystem::create_directory(path_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy a temporary directory.
|
||||
*/
|
||||
/// Destroy a temporary directory.
|
||||
~TempDir()
|
||||
{
|
||||
// use non-throwing calls in the destructor
|
||||
std::error_code ec;
|
||||
std::filesystem::remove_all(path_, ec);
|
||||
boost::system::error_code ec;
|
||||
boost::filesystem::remove_all(path_, ec);
|
||||
// TODO: warn/notify if ec set ?
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the native path for the temporary directory
|
||||
*/
|
||||
/// Get the native path for the temporary directory
|
||||
[[nodiscard]] std::string
|
||||
path() const
|
||||
{
|
||||
return path_.string();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the native path for the a file.
|
||||
*
|
||||
* The file does not need to exist.
|
||||
*/
|
||||
/** Get the native path for the a file.
|
||||
|
||||
The file does not need to exist.
|
||||
*/
|
||||
[[nodiscard]] std::string
|
||||
file(std::string const& name) const
|
||||
{
|
||||
|
||||
@@ -85,15 +85,14 @@ XorShiftEngine<Unused>::murmurhash3(result_type x) -> result_type
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* XOR-shift Generator.
|
||||
*
|
||||
* Meets the requirements of UniformRandomNumberGenerator.
|
||||
*
|
||||
* Simple and fast RNG based on:
|
||||
* http://xorshift.di.unimi.it/xorshift128plus.c
|
||||
* does not accept seed==0
|
||||
*/
|
||||
/** XOR-shift Generator.
|
||||
|
||||
Meets the requirements of UniformRandomNumberGenerator.
|
||||
|
||||
Simple and fast RNG based on:
|
||||
http://xorshift.di.unimi.it/xorshift128plus.c
|
||||
does not accept seed==0
|
||||
*/
|
||||
using xor_shift_engine = detail::XorShiftEngine<>;
|
||||
|
||||
} // namespace beast
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user