Skip to content

Commit

Permalink
[APM] Mock synthetic source for metric documents (elastic#139643)
Browse files Browse the repository at this point in the history
  • Loading branch information
dgieselaar authored and Mpdreamz committed Sep 6, 2022
1 parent a1560e1 commit f0eb40c
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { fakeSyntheticSource } from './fake_synthetic_source';

describe('fakeSyntheticSource', () => {
it('flattens and unflattens objects', () => {
const obj = {
service: {
name: 'my-service',
},
transaction: {
type: 'request',
name: 'GET /api/my-transaction',
},
};

expect(fakeSyntheticSource(obj)).toEqual(obj);
});

it('drops null and undefined values', () => {
const obj = {
service: {
name: null,
},
transaction: {
type: 'request',
name: 'GET /api/my-transaction',
},
};

expect(fakeSyntheticSource(obj)).toEqual({
transaction: {
type: 'request',
name: 'GET /api/my-transaction',
},
});
});

it('only wraps multiple values in an array', () => {
const obj = {
span: {
links: [
{ trace: { id: '1' }, span: { id: '1' } },
{ trace: { id: '2' }, span: { id: '2' } },
],
},
};

expect(fakeSyntheticSource(obj)).toEqual({
span: {
links: {
trace: {
id: ['1', '2'],
},
span: {
id: ['1', '2'],
},
},
},
});
});

it('deduplicates and sorts array values', () => {
const obj = {
span: {
links: [
{ trace: { id: '3' }, span: { id: '1' } },
{ trace: { id: '1' }, span: { id: '1' } },
{ trace: { id: '1' }, span: { id: '4' } },
],
},
};

expect(fakeSyntheticSource(obj)).toEqual({
span: {
links: {
trace: {
id: ['1', '3'],
},
span: {
id: ['1', '4'],
},
},
},
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { isArray, isObjectLike, set, uniq } from 'lodash';

function flatten(
target: Record<string, any>,
source: Record<string, any>,
toArray: boolean,
prefix: string = ''
) {
for (const key in source) {
if (!Object.hasOwn(source, key)) {
continue;
}
const value = source[key];
if (value === undefined || value === null) {
continue;
}
const nextKey = `${prefix}${key}`;
if (isArray(value)) {
value.forEach((val) => {
flatten(target, val, true, `${nextKey}.`);
});
} else if (isObjectLike(value)) {
flatten(target, value, toArray, `${nextKey}.`);
} else if (toArray && Array.isArray(target[nextKey])) {
target[nextKey].push(value);
} else if (toArray) {
target[nextKey] = [value];
} else {
target[nextKey] = value;
}
}
return target;
}

export function fakeSyntheticSource(object: Record<string, any>) {
const flattened = flatten({}, object, false, '');

const unflattened = {};
for (const key in flattened) {
if (!Object.hasOwn(flattened, key)) {
continue;
}
let val = flattened[key];
if (Array.isArray(val)) {
val = uniq(val).sort();
}
set(unflattened, key, val);
}
return unflattened;
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
unpackProcessorEvents,
processorEventsToIndex,
} from './unpack_processor_events';
import { fakeSyntheticSource } from './fake_synthetic_source';

export type APMEventESSearchRequest = Omit<ESSearchRequest, 'index'> & {
apm: {
Expand Down Expand Up @@ -162,9 +163,48 @@ export class APMEventClient {

return this.callAsyncWithDebug({
cb: (opts) =>
this.esClient.search(searchParams, opts) as unknown as Promise<{
body: TypedSearchResponse<TParams>;
}>,
(
this.esClient.search(searchParams, opts) as unknown as Promise<{
body: TypedSearchResponse<TParams>;
}>
).then((response) => {
// ensure metric data is compatible with synthetic source
// enabled
const metricEventsOnly = params.apm.events.every(
(event) => event === ProcessorEvent.metric
);

if (!response.body?.hits?.hits) {
return response;
}

const hits = response.body.hits.hits.map((hit) => {
if (
metricEventsOnly ||
// take filter_path etc into account
(hit._source &&
'processor' in hit._source &&
hit._source.processor?.event === ProcessorEvent.metric)
) {
return {
...hit,
_source: fakeSyntheticSource(hit._source),
};
}
return hit;
});

return {
...response,
body: {
...response.body,
hits: {
...response.body.hits,
hits,
},
},
};
}),
operationName,
params: searchParams,
requestType: 'search',
Expand Down

0 comments on commit f0eb40c

Please sign in to comment.