Skip to content

Commit 71fe630

Browse files
authored
Fix spans-per-trace slider not affecting storage calculations (#169)
The spans-per-trace slider had no effect on any storage or ingest metric because total spans was a direct input. Restructured the model so traces/month is the input and spans = traces * spans/trace, making every slider affect at least one output metric. Also fixed stale formulas in the How It Works section and docs page. Signed-off-by: Anirudha Jadhav <anirudha@nyu.edu>
1 parent b15dc0d commit 71fe630

3 files changed

Lines changed: 33 additions & 24 deletions

File tree

docs/src/components/APMUsageCalculator.test.tsx

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { render, screen } from '@testing-library/react';
33
import APMUsageCalculator, { calculateUsage } from './APMUsageCalculator';
44

55
const defaults = {
6-
spansPerMonth: 10_000_000,
6+
tracesPerMonth: 1_250_000,
77
avgSpanSizeKB: 0.5,
88
spansPerTrace: 8,
99
services: 10,
@@ -12,22 +12,30 @@ const defaults = {
1212
};
1313

1414
describe('calculateUsage', () => {
15-
it('computes traces from spans and spans-per-trace', () => {
15+
it('derives spans from traces * spans-per-trace', () => {
1616
const result = calculateUsage(defaults);
17-
expect(result.tracesPerMonth).toBe(10_000_000 / 8);
17+
expect(result.spansPerMonth).toBe(1_250_000 * 8);
1818
});
1919

2020
it('computes retained spans based on retention', () => {
2121
const result = calculateUsage(defaults);
22-
expect(result.retainedSpans).toBe(10_000_000 * (15 / 30));
22+
const spansPerMonth = 1_250_000 * 8;
23+
expect(result.retainedSpans).toBe(spansPerMonth * (15 / 30));
2324
});
2425

2526
it('computes span storage in bytes with index overhead', () => {
2627
const result = calculateUsage(defaults);
27-
const rawBytes = 10_000_000 * (15 / 30) * 0.5 * 1024;
28+
const spansPerMonth = 1_250_000 * 8;
29+
const rawBytes = spansPerMonth * (15 / 30) * 0.5 * 1024;
2830
expect(result.spanStorageBytes).toBe(rawBytes * 2.0);
2931
});
3032

33+
it('spans-per-trace slider affects storage', () => {
34+
const r8 = calculateUsage({ ...defaults, spansPerTrace: 8 });
35+
const r16 = calculateUsage({ ...defaults, spansPerTrace: 16 });
36+
expect(r16.spanStorageBytes).toBe(r8.spanStorageBytes * 2);
37+
});
38+
3139
it('computes directed service map edges as n*(n-1)', () => {
3240
const result = calculateUsage(defaults);
3341
expect(result.edges).toBe(10 * 9);
@@ -53,7 +61,7 @@ describe('calculateUsage', () => {
5361
it('scales storage linearly with retention', () => {
5462
const r15 = calculateUsage({ ...defaults, retentionDays: 15 });
5563
const r30 = calculateUsage({ ...defaults, retentionDays: 30 });
56-
expect(r30.spanStorageBytes).toBe(r15.spanStorageBytes * 2);
64+
expect(r30.spanStorageBytes).toBeCloseTo(r15.spanStorageBytes * 2, 0);
5765
});
5866

5967
it('ingest rate is positive', () => {
@@ -65,7 +73,7 @@ describe('calculateUsage', () => {
6573
describe('APMUsageCalculator component', () => {
6674
it('renders slider labels', () => {
6775
render(<APMUsageCalculator />);
68-
expect(screen.getByText('Spans ingested / month')).toBeTruthy();
76+
expect(screen.getByText('Traces (requests) / month')).toBeTruthy();
6977
expect(screen.getByText('Avg span payload size')).toBeTruthy();
7078
expect(screen.getByText('Number of services')).toBeTruthy();
7179
expect(screen.getByText('Retention period')).toBeTruthy();

docs/src/components/APMUsageCalculator.tsx

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,13 @@ function formatBytes(bytes: number): string {
3434
}
3535

3636
const sliders: Record<string, SliderConfig> = {
37-
spansPerMonth: {
38-
label: 'Spans ingested / month',
39-
min: 1_000_000,
40-
max: 10_000_000_000,
41-
step: 1_000_000,
42-
default: 10_000_000,
43-
unit: 'spans',
37+
tracesPerMonth: {
38+
label: 'Traces (requests) / month',
39+
min: 100_000,
40+
max: 1_000_000_000,
41+
step: 100_000,
42+
default: 1_250_000,
43+
unit: 'traces',
4444
format: formatCompact,
4545
logarithmic: true,
4646
},
@@ -272,16 +272,16 @@ function Section({
272272

273273
export function calculateUsage(values: Record<SliderKey, number>) {
274274
const {
275-
spansPerMonth,
275+
tracesPerMonth,
276276
avgSpanSizeKB,
277277
spansPerTrace,
278278
services,
279279
opsPerService,
280280
retentionDays,
281281
} = values;
282282

283-
// Span storage
284-
const tracesPerMonth = spansPerMonth / spansPerTrace;
283+
// Span storage — spans derived from traces * spans/trace
284+
const spansPerMonth = tracesPerMonth * spansPerTrace;
285285
const retainedSpans = spansPerMonth * (retentionDays / 30);
286286
const spanRawBytes = retainedSpans * avgSpanSizeKB * 1024;
287287
const spanStorageBytes = spanRawBytes * OPENSEARCH_INDEX_OVERHEAD;
@@ -311,6 +311,7 @@ export function calculateUsage(values: Record<SliderKey, number>) {
311311
const ingestRate = totalDocsPerDay / 86400;
312312

313313
return {
314+
spansPerMonth,
314315
tracesPerMonth,
315316
retainedSpans,
316317
spanStorageBytes,
@@ -381,7 +382,7 @@ function HowItWorks() {
381382
start/end timestamps, duration, status code, and dynamic attributes.
382383
</p>
383384
<div className="font-mono text-xs bg-slate-950 rounded p-3 space-y-1">
384-
<div><span className="text-slate-500">Traces/month</span> = spans_per_month / avg_spans_per_trace</div>
385+
<div><span className="text-slate-500">Spans/month</span> = traces_per_month * avg_spans_per_trace</div>
385386
<div><span className="text-slate-500">Retained spans</span> = spans_per_month * (retention_days / 30)</div>
386387
<div><span className="text-slate-500">Storage</span> = retained_spans * avg_span_size_kb * 1024 * 2.0x index overhead</div>
387388
</div>
@@ -501,14 +502,14 @@ export default function APMUsageCalculator() {
501502

502503
<Section title="Span Storage (OpenSearch)">
503504
<MetricRow
504-
label="Traces / month"
505-
value={formatCompact(usage.tracesPerMonth)}
506-
formula={`= ${formatCompact(values.spansPerMonth)} spans / ${values.spansPerTrace} spans-per-trace`}
505+
label="Spans / month"
506+
value={formatCompact(usage.spansPerMonth)}
507+
formula={`= ${formatCompact(values.tracesPerMonth)} traces * ${values.spansPerTrace} spans/trace`}
507508
/>
508509
<MetricRow
509510
label="Spans retained"
510511
value={formatCompact(usage.retainedSpans)}
511-
formula={`= ${formatCompact(values.spansPerMonth)} spans/mo * (${values.retentionDays} / 30) retention`}
512+
formula={`= ${formatCompact(usage.spansPerMonth)} spans/mo * (${values.retentionDays} / 30) retention`}
512513
/>
513514
<MetricRow
514515
label="Storage (with index overhead)"

docs/starlight-docs/src/content/docs/apm/sizing-calculator.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Spans are the raw building blocks of traces. Each span document is indexed into
1717

1818
| Output | Formula |
1919
|--------|---------|
20-
| **Traces / month** | `spans_per_month ÷ avg_spans_per_trace` |
20+
| **Spans / month** | `traces_per_month × avg_spans_per_trace` |
2121
| **Retained spans** | `spans_per_month × (retention_days ÷ 30)` |
2222
| **Storage** | `retained_spans × avg_span_size × 2.0` (the 2.0× factor accounts for OpenSearch index overhead — field mappings, inverted indices, doc values) |
2323
| **Ingest rate** | `spans_per_month ÷ 30 ÷ 86400` (spans/sec) |
@@ -50,7 +50,7 @@ The calculator sums OpenSearch storage (spans + service map) and Prometheus stor
5050

5151
| Parameter | Default | Range | Description |
5252
|-----------|---------|-------|-------------|
53-
| Spans ingested / month | 10M | 1M10B | Total span documents your applications generate per month |
53+
| Traces (requests) / month | 1.25M | 100K1B | Total traces (requests) your applications generate per month |
5454
| Avg span payload size | 0.5 KB | 0.1 – 50 KB | Average size of a single span document before indexing |
5555
| Avg spans per trace | 8 | 1 – 200 | How many spans make up one trace on average |
5656
| Number of services | 10 | 1 – 1000 | Distinct instrumented services in your environment |

0 commit comments

Comments
 (0)