mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Profiling] Move additional flamegraph calculations into UI (#142415)
* Remove total and sampled traces from API * Remove Samples array from flamegraph API These values are redundant with CountInclusive so could be removed without issue. * Remove totalCount and eventsIndex These values are no longer needed. * Remove samples from callee tree * Refactor columnar view model into separate file * Add more lazy-loaded flamegraph calculations * Fix spacing in frame label * Remove frame information API * Improve test coverage * Fix type error * Replace fnv-plus with custom 64-bit FNV1-a * Add exceptions for linting errors * Add workaround for frame type truncation bug * Replace prior workaround for truncation bug This fix supercedes the prior workaround and addresses the truncation at its source. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
276cd3d0ef
commit
c888aca9b4
22 changed files with 547 additions and 518 deletions
|
@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema';
|
|||
import { RouteRegisterParameters } from '.';
|
||||
import { getRoutePaths } from '../../common';
|
||||
import { createCalleeTree } from '../../common/callee';
|
||||
import { createFlameGraph } from '../../common/flamegraph';
|
||||
import { createBaseFlameGraph } from '../../common/flamegraph';
|
||||
import { createProfilingEsClient } from '../utils/create_profiling_es_client';
|
||||
import { withProfilingSpan } from '../utils/with_profiling_span';
|
||||
import { getClient } from './compat';
|
||||
|
@ -42,20 +42,13 @@ export function registerFlameChartSearchRoute({ router, logger }: RouteRegisterP
|
|||
});
|
||||
const totalSeconds = timeTo - timeFrom;
|
||||
|
||||
const {
|
||||
stackTraces,
|
||||
executables,
|
||||
stackFrames,
|
||||
eventsIndex,
|
||||
totalCount,
|
||||
totalFrames,
|
||||
stackTraceEvents,
|
||||
} = await getExecutablesAndStackTraces({
|
||||
logger,
|
||||
client: createProfilingEsClient({ request, esClient }),
|
||||
filter,
|
||||
sampleSize: targetSampleSize,
|
||||
});
|
||||
const { stackTraceEvents, stackTraces, executables, stackFrames, totalFrames } =
|
||||
await getExecutablesAndStackTraces({
|
||||
logger,
|
||||
client: createProfilingEsClient({ request, esClient }),
|
||||
filter,
|
||||
sampleSize: targetSampleSize,
|
||||
});
|
||||
|
||||
const flamegraph = await withProfilingSpan('create_flamegraph', async () => {
|
||||
const t0 = Date.now();
|
||||
|
@ -68,23 +61,8 @@ export function registerFlameChartSearchRoute({ router, logger }: RouteRegisterP
|
|||
);
|
||||
logger.info(`creating callee tree took ${Date.now() - t0} ms`);
|
||||
|
||||
// sampleRate is 1/5^N, with N being the downsampled index the events were fetched from.
|
||||
// N=0: full events table (sampleRate is 1)
|
||||
// N=1: downsampled by 5 (sampleRate is 0.2)
|
||||
// ...
|
||||
|
||||
// totalCount is the sum(Count) of all events in the filter range in the
|
||||
// downsampled index we were looking at.
|
||||
// To estimate how many events we have in the full events index: totalCount / sampleRate.
|
||||
// Do the same for single entries in the events array.
|
||||
|
||||
const t1 = Date.now();
|
||||
const fg = createFlameGraph(
|
||||
tree,
|
||||
totalSeconds,
|
||||
Math.floor(totalCount / eventsIndex.sampleRate),
|
||||
totalCount
|
||||
);
|
||||
const fg = createBaseFlameGraph(tree, totalSeconds);
|
||||
logger.info(`creating flamegraph took ${Date.now() - t1} ms`);
|
||||
|
||||
return fg;
|
||||
|
|
|
@ -1,102 +0,0 @@
|
|||
/*
|
||||
* 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 { schema } from '@kbn/config-schema';
|
||||
import { Logger } from '@kbn/logging';
|
||||
import { RouteRegisterParameters } from '.';
|
||||
import { getRoutePaths } from '../../common';
|
||||
import {
|
||||
createStackFrameMetadata,
|
||||
Executable,
|
||||
StackFrame,
|
||||
StackFrameMetadata,
|
||||
} from '../../common/profiling';
|
||||
import { createProfilingEsClient, ProfilingESClient } from '../utils/create_profiling_es_client';
|
||||
import { mgetStackFrames, mgetExecutables } from './stacktrace';
|
||||
|
||||
async function getFrameInformation({
|
||||
frameID,
|
||||
executableID,
|
||||
logger,
|
||||
client,
|
||||
}: {
|
||||
frameID: string;
|
||||
executableID: string;
|
||||
logger: Logger;
|
||||
client: ProfilingESClient;
|
||||
}): Promise<StackFrameMetadata | undefined> {
|
||||
const [stackFrames, executables] = await Promise.all([
|
||||
mgetStackFrames({
|
||||
logger,
|
||||
client,
|
||||
stackFrameIDs: new Set([frameID]),
|
||||
}),
|
||||
mgetExecutables({
|
||||
logger,
|
||||
client,
|
||||
executableIDs: new Set([executableID]),
|
||||
}),
|
||||
]);
|
||||
|
||||
const frame = Array.from(stackFrames.values())[0] as StackFrame | undefined;
|
||||
const executable = Array.from(executables.values())[0] as Executable | undefined;
|
||||
|
||||
if (frame) {
|
||||
return createStackFrameMetadata({
|
||||
FrameID: frameID,
|
||||
FileID: executableID,
|
||||
SourceFilename: frame.FileName,
|
||||
FunctionName: frame.FunctionName,
|
||||
ExeFileName: executable?.FileName,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function registerFrameInformationRoute(params: RouteRegisterParameters) {
|
||||
const { logger, router } = params;
|
||||
|
||||
const routePaths = getRoutePaths();
|
||||
|
||||
router.get(
|
||||
{
|
||||
path: routePaths.FrameInformation,
|
||||
validate: {
|
||||
query: schema.object({
|
||||
frameID: schema.string(),
|
||||
executableID: schema.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const { frameID, executableID } = request.query;
|
||||
|
||||
const client = createProfilingEsClient({
|
||||
request,
|
||||
esClient: (await context.core).elasticsearch.client.asCurrentUser,
|
||||
});
|
||||
|
||||
try {
|
||||
const frame = await getFrameInformation({
|
||||
frameID,
|
||||
executableID,
|
||||
logger,
|
||||
client,
|
||||
});
|
||||
|
||||
return response.ok({ body: frame });
|
||||
} catch (error: any) {
|
||||
logger.error(error);
|
||||
return response.custom({
|
||||
statusCode: error.statusCode ?? 500,
|
||||
body: {
|
||||
message: error.message ?? 'An internal server error occured',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
|
@ -13,7 +13,6 @@ import {
|
|||
} from '../types';
|
||||
|
||||
import { registerFlameChartSearchRoute } from './flamechart';
|
||||
import { registerFrameInformationRoute } from './frames';
|
||||
import { registerTopNFunctionsSearchRoute } from './functions';
|
||||
|
||||
import {
|
||||
|
@ -41,5 +40,4 @@ export function registerRoutes(params: RouteRegisterParameters) {
|
|||
registerTraceEventsTopNHostsSearchRoute(params);
|
||||
registerTraceEventsTopNStackTracesSearchRoute(params);
|
||||
registerTraceEventsTopNThreadsSearchRoute(params);
|
||||
registerFrameInformationRoute(params);
|
||||
}
|
||||
|
|
|
@ -118,6 +118,14 @@ describe('Stack trace operations', () => {
|
|||
}
|
||||
});
|
||||
|
||||
test('runLengthDecode with larger output than available input', () => {
|
||||
const bytes = Buffer.from([0x5, 0x0, 0x2, 0x2]);
|
||||
const decoded = [0, 0, 0, 0, 0, 2, 2];
|
||||
const expected = decoded.concat(Array(decoded.length).fill(0));
|
||||
|
||||
expect(runLengthDecode(bytes, expected.length)).toEqual(expected);
|
||||
});
|
||||
|
||||
test('runLengthDecode without optional parameter', () => {
|
||||
const tests: Array<{
|
||||
bytes: Buffer;
|
||||
|
|
|
@ -122,6 +122,17 @@ export function runLengthDecode(input: Buffer, outputSize?: number): number[] {
|
|||
}
|
||||
}
|
||||
|
||||
// Due to truncation of the frame types for stacktraces longer than 255,
|
||||
// the expected output size and the actual decoded size can be different.
|
||||
// Ordinarily, these two values should be the same.
|
||||
//
|
||||
// We have decided to fill in the remainder of the output array with zeroes
|
||||
// as a reasonable default. Without this step, the output array would have
|
||||
// undefined values.
|
||||
for (let i = idx; i < size; i++) {
|
||||
output[i] = 0;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue