Skip to content

Commit e1455d5

Browse files
bonniciglasser
andauthored
Always send large traces as stats (#6897)
Send large (>10mb) traces as stats always. Co-authored-by: David Glasser <[email protected]>
1 parent eb04917 commit e1455d5

5 files changed

Lines changed: 106 additions & 5 deletions

File tree

.changeset/yellow-actors-do.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@apollo/server': patch
3+
---
4+
5+
Usage reporting: always send traces over 10MB as stats.

packages/server/src/__tests__/plugin/usageReporting/stats.test.ts

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1-
import { Trace } from '@apollo/usage-reporting-protobuf';
1+
import {
2+
Trace,
3+
ReportHeader,
4+
ReferencedFieldsForType,
5+
} from '@apollo/usage-reporting-protobuf';
26
import { dateToProtoTimestamp } from '../../../plugin/traceTreeBuilder';
37
import {
48
OurContextualizedStats,
59
SizeEstimator,
10+
OurReport,
611
} from '../../../plugin/usageReporting/stats';
712
import { DurationHistogram } from '../../../plugin/usageReporting/durationHistogram';
813
import { describe, it, expect } from '@jest/globals';
@@ -473,3 +478,81 @@ describe('Check type stats', () => {
473478
expect(contextualizedStats).toMatchSnapshot();
474479
});
475480
});
481+
482+
describe('Add trace to report', () => {
483+
const defaultHeader = new ReportHeader({
484+
hostname: 'hostname',
485+
agentVersion: `@apollo/server`,
486+
runtimeVersion: `node latest`,
487+
uname: 'uname',
488+
executableSchemaId: 'schema',
489+
graphRef: 'graph',
490+
});
491+
const referencedFieldsByType = Object.create(null);
492+
referencedFieldsByType['type'] = new ReferencedFieldsForType({
493+
fieldNames: ['field1', 'field2'],
494+
isInterface: false,
495+
});
496+
497+
it('add as stats if asTrace is false', () => {
498+
const report = new OurReport(defaultHeader);
499+
report.addTrace({
500+
statsReportKey: 'key',
501+
trace: baseTrace,
502+
asTrace: false,
503+
referencedFieldsByType,
504+
});
505+
506+
expect(report.tracesPerQuery['key']?.trace?.length).toBe(0);
507+
expect(
508+
Object.keys(report.tracesPerQuery['key']?.statsWithContext?.map).length,
509+
).toBe(1);
510+
});
511+
512+
it('add as stats if asTrace is true but trace is too large', () => {
513+
const report = new OurReport(defaultHeader);
514+
report.addTrace({
515+
statsReportKey: 'key',
516+
trace: baseTrace,
517+
asTrace: true,
518+
referencedFieldsByType,
519+
maxTraceBytes: 10,
520+
});
521+
522+
expect(report.tracesPerQuery['key']?.trace?.length).toBe(0);
523+
expect(
524+
Object.keys(report.tracesPerQuery['key']?.statsWithContext?.map).length,
525+
).toBe(1);
526+
});
527+
528+
it('add as trace if asTrace is true and trace is not too large', () => {
529+
const report = new OurReport(defaultHeader);
530+
report.addTrace({
531+
statsReportKey: 'key',
532+
trace: baseTrace,
533+
asTrace: true,
534+
referencedFieldsByType,
535+
maxTraceBytes: 500 * 1024,
536+
});
537+
538+
expect(report.tracesPerQuery['key']?.trace?.length).toBe(1);
539+
expect(
540+
Object.keys(report.tracesPerQuery['key']?.statsWithContext?.map).length,
541+
).toBe(0);
542+
});
543+
544+
it('add as trace if asTrace is true and trace is not too large and max trace size is left as default', () => {
545+
const report = new OurReport(defaultHeader);
546+
report.addTrace({
547+
statsReportKey: 'key',
548+
trace: baseTrace,
549+
asTrace: true,
550+
referencedFieldsByType,
551+
});
552+
553+
expect(report.tracesPerQuery['key']?.trace?.length).toBe(1);
554+
expect(
555+
Object.keys(report.tracesPerQuery['key']?.statsWithContext?.map).length,
556+
).toBe(0);
557+
});
558+
});

packages/server/src/plugin/usageReporting/defaultSendOperationsAsTrace.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ export function defaultSendOperationsAsTrace() {
99
// operation, what minute the operation ended at, etc) to `true` if we've seen
1010
// it recently. We actually split this into one cache per minute so we can
1111
// throw away a full minute's worth of cache at once; we keep only the last
12-
// three minutes
12+
// three minutes.
13+
// Note that if a trace is over a certain size, we will always send it as
14+
// stats. We check this within the addTrace function of the OurReport class so
15+
// that we don't have to encode these large traces twice.
1316
const cache = new LRUCache<string, true>({
1417
// 3MiB limit, very much approximately since we can't be sure how V8 might
1518
// be storing these strings internally. Though this should be enough to

packages/server/src/plugin/usageReporting/plugin.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -680,7 +680,7 @@ export function ApolloServerPluginUsageReporting<TContext extends BaseContext>(
680680
trace,
681681
// We include the operation as a trace (rather than aggregated
682682
// into stats) only if the user didn't set `sendTraces: false`
683-
// *and8 we believe it's possible that our organization's plan
683+
// *and* we believe it's possible that our organization's plan
684684
// allows for viewing traces *and* we actually captured this as
685685
// a full trace *and* sendOperationAsTrace says so.
686686
//

packages/server/src/plugin/usageReporting/stats.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,20 +61,30 @@ export class OurReport implements Required<IReport> {
6161
trace,
6262
asTrace,
6363
referencedFieldsByType,
64+
// The max size a trace can be before it is sent as stats. Note that the
65+
// Apollo reporting ingress server will never store any traces over 10mb
66+
// anyway. They will still be converted to stats as we would do here.
67+
maxTraceBytes = 10 * 1024 * 1024,
6468
}: {
6569
statsReportKey: string;
6670
trace: Trace;
6771
asTrace: boolean;
6872
referencedFieldsByType: ReferencedFieldsByType;
73+
maxTraceBytes?: number;
6974
}) {
7075
const tracesAndStats = this.getTracesAndStats({
7176
statsReportKey,
7277
referencedFieldsByType,
7378
});
7479
if (asTrace) {
7580
const encodedTrace = Trace.encode(trace).finish();
76-
tracesAndStats.trace.push(encodedTrace);
77-
this.sizeEstimator.bytes += 2 + encodedTrace.length;
81+
82+
if (!isNaN(maxTraceBytes) && encodedTrace.length > maxTraceBytes) {
83+
tracesAndStats.statsWithContext.addTrace(trace, this.sizeEstimator);
84+
} else {
85+
tracesAndStats.trace.push(encodedTrace);
86+
this.sizeEstimator.bytes += 2 + encodedTrace.length;
87+
}
7888
} else {
7989
tracesAndStats.statsWithContext.addTrace(trace, this.sizeEstimator);
8090
}

0 commit comments

Comments
 (0)