Skip to content

Commit 4e3b12e

Browse files
authored
disallow duplicate dimensions sets (#21)
- disallow duplicate dimensions sets - fix integ tests - move helper methods to bottom of file for readability - change expected samples to 3 Closes #20
1 parent 6132122 commit 4e3b12e

File tree

3 files changed

+122
-47
lines changed

3 files changed

+122
-47
lines changed

src/logger/MetricsContext.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,34 @@ export class MetricsContext {
8888
*
8989
* @param dimensions
9090
*/
91-
public putDimensions(dimensions: Record<string, string>): void {
92-
this.dimensions.push(dimensions);
91+
public putDimensions(incomingDimensionSet: Record<string, string>): void {
92+
if (this.dimensions.length === 0) {
93+
this.dimensions.push(incomingDimensionSet);
94+
return;
95+
}
96+
97+
for (let i = 0; i < this.dimensions.length; i++) {
98+
const existingDimensionSet = this.dimensions[i];
99+
100+
// check for duplicate dimensions when putting
101+
// this is an O(n^2) operation, but since we never expect to have more than
102+
// 10 dimensions, this is acceptable for almost all cases.
103+
// This makes re-using loggers much easier.
104+
const existingDimensionSetKeys = Object.keys(existingDimensionSet);
105+
const incomingDimensionSetKeys = Object.keys(incomingDimensionSet);
106+
if (existingDimensionSetKeys.length !== incomingDimensionSetKeys.length) {
107+
this.dimensions.push(incomingDimensionSet);
108+
return;
109+
}
110+
111+
for (let j = 0; j < existingDimensionSetKeys.length; j++) {
112+
if (!incomingDimensionSetKeys.includes(existingDimensionSetKeys[j])) {
113+
// we're done now because we know that the dimensions keys are not identical
114+
this.dimensions.push(incomingDimensionSet);
115+
return;
116+
}
117+
}
118+
}
93119
}
94120

95121
/**

src/logger/__tests__/MetricsContext.test.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,53 @@ test('putDimension adds key to dimension and sets the dimension as a property',
2929
expect(context.getDimensions()[0]).toStrictEqual(expectedDimension);
3030
});
3131

32+
test('putDimension will not duplicate dimensions', () => {
33+
// arrange
34+
const context = MetricsContext.empty();
35+
const dimension = faker.random.word();
36+
const expectedDimension = { dimension };
37+
38+
// act
39+
context.putDimensions({ dimension });
40+
context.putDimensions({ dimension });
41+
42+
// assert
43+
expect(context.getDimensions().length).toBe(1);
44+
expect(context.getDimensions()[0]).toStrictEqual(expectedDimension);
45+
});
46+
47+
test('putDimension will not duplicate dimensions, multiple in different order', () => {
48+
// arrange
49+
const context = MetricsContext.empty();
50+
const dimension1 = faker.random.word();
51+
const dimension2 = faker.random.word();
52+
const expectedDimension = { dimension1, dimension2 };
53+
54+
// act
55+
context.putDimensions({ dimension1, dimension2 });
56+
context.putDimensions({ dimension2, dimension1 });
57+
58+
// assert
59+
expect(context.getDimensions().length).toBe(1);
60+
expect(context.getDimensions()[0]).toStrictEqual(expectedDimension);
61+
});
62+
63+
test('putDimension accepts multiple unique dimension sets', () => {
64+
// arrange
65+
const context = MetricsContext.empty();
66+
const expectedDimension1 = { d1: faker.random.word(), d2: faker.random.word() };
67+
const expectedDimension2 = { d2: faker.random.word(), d3: faker.random.word() };
68+
69+
// act
70+
context.putDimensions(expectedDimension1);
71+
context.putDimensions(expectedDimension2);
72+
73+
// assert
74+
expect(context.getDimensions().length).toBe(2);
75+
expect(context.getDimensions()[0]).toStrictEqual(expectedDimension1);
76+
expect(context.getDimensions()[1]).toStrictEqual(expectedDimension2);
77+
});
78+
3279
test('getDimensions returns default dimensions if custom dimensions not set', () => {
3380
// arrange
3481
const context = MetricsContext.empty();

test/integ/agent/end-to-end.integ.ts

Lines changed: 47 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -23,41 +23,6 @@ const dimensionValue = 'Integ-Test-Agent';
2323
const dimensions: Record<string, string> = {};
2424
dimensions[dimensionKey] = dimensionValue;
2525

26-
const metricExists = async (metricName: string, expectedSampleCount: number): Promise<boolean> => {
27-
const request = {
28-
Namespace: 'aws-embedded-metrics',
29-
MetricName: metricName,
30-
Dimensions: [
31-
{ Name: 'ServiceName', Value: serviceName },
32-
{ Name: 'ServiceType', Value: serviceType },
33-
{ Name: 'LogGroup', Value: logGroupName },
34-
{ Name: dimensionKey, Value: dimensionValue },
35-
],
36-
Period: 60,
37-
StartTime: new Date(startTime.getTime() - 5000),
38-
EndTime: new Date(now()),
39-
Statistics: ['SampleCount'],
40-
};
41-
42-
const result = await cwmClient.getMetricStatistics(request).promise();
43-
44-
if (result && result.Datapoints && result.Datapoints.length > 0) {
45-
const samples = result.Datapoints.map(dataPoint => dataPoint.SampleCount || 0).reduce((total, i) => total + i);
46-
console.log(`Received ${samples} samples.`);
47-
return samples === expectedSampleCount;
48-
}
49-
50-
return false;
51-
};
52-
53-
const waitForMetricExistence = async (metricName: string, expectedSampleCount: number): Promise<void> => {
54-
let attempts = 0;
55-
while (!(await metricExists(metricName, expectedSampleCount))) {
56-
console.log('No metrics yet. Sleeping before trying again. Attempt #', attempts++);
57-
await Sleep(2000);
58-
}
59-
};
60-
6126
test(
6227
'end to end integration test with agent over UDP',
6328
async () => {
@@ -86,23 +51,60 @@ test(
8651
// arrange
8752
const idleTimeout = 500;
8853
const metricName = 'TCP-MultipleFlushes';
89-
const expectedSamples = 2;
54+
const expectedSamples = 3;
9055

9156
Configuration.agentEndpoint = 'tcp://0.0.0.0:25888';
9257

93-
const doWork = metricScope(metrics => () => {
94-
metrics.putDimensions(dimensions);
95-
metrics.putMetric(metricName, 100, 'Milliseconds');
96-
metrics.setProperty('RequestId', '422b1569-16f6-4a03-b8f0-fe3fd9b100f8');
97-
});
58+
const doWork = metricScope(metrics => () => {
59+
metrics.putDimensions(dimensions);
60+
metrics.putMetric(metricName, 100, 'Milliseconds');
61+
metrics.setProperty('RequestId', '422b1569-16f6-4a03-b8f0-fe3fd9b100f8');
62+
});
9863

99-
// act
100-
doWork();
101-
await Sleep(idleTimeout);
102-
doWork();
64+
// act
65+
doWork();
66+
doWork();
67+
await Sleep(idleTimeout);
68+
doWork();
10369

10470
// assert
10571
await waitForMetricExistence(metricName, expectedSamples);
10672
},
10773
timeoutMillis,
10874
);
75+
76+
77+
const metricExists = async (metricName: string, expectedSampleCount: number): Promise<boolean> => {
78+
const request = {
79+
Namespace: 'aws-embedded-metrics',
80+
MetricName: metricName,
81+
Dimensions: [
82+
{ Name: 'ServiceName', Value: serviceName },
83+
{ Name: 'ServiceType', Value: serviceType },
84+
{ Name: 'LogGroup', Value: logGroupName },
85+
{ Name: dimensionKey, Value: dimensionValue },
86+
],
87+
Period: 60,
88+
StartTime: new Date(startTime.getTime() - 5000),
89+
EndTime: new Date(now()),
90+
Statistics: ['SampleCount'],
91+
};
92+
93+
const result = await cwmClient.getMetricStatistics(request).promise();
94+
95+
if (result && result.Datapoints && result.Datapoints.length > 0) {
96+
const samples = result.Datapoints.map(dataPoint => dataPoint.SampleCount || 0).reduce((total, i) => total + i);
97+
console.log(`Received ${samples} samples.`);
98+
return samples === expectedSampleCount;
99+
}
100+
101+
return false;
102+
};
103+
104+
const waitForMetricExistence = async (metricName: string, expectedSampleCount: number): Promise<void> => {
105+
let attempts = 0;
106+
while (!(await metricExists(metricName, expectedSampleCount))) {
107+
console.log('No metrics yet. Sleeping before trying again. Attempt #', attempts++);
108+
await Sleep(2000);
109+
}
110+
};

0 commit comments

Comments
 (0)