From 032082f3e79727e771b96e710d5d424f59d56eff Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Wed, 30 Aug 2023 09:50:49 +0100 Subject: [PATCH 01/12] [Profiling] Initial changes profiling data access --- package.json | 1 + tsconfig.base.json | 2 + x-pack/plugins/profiling/kibana.jsonc | 3 +- .../profiling/server/routes/flamechart.ts | 42 +-- x-pack/plugins/profiling/server/types.ts | 6 + .../profiling_data_access/.i18nrc.json | 7 + .../profiling_data_access/common/base64.ts | 22 ++ .../profiling_data_access/common/callee.ts | 162 +++++++++ .../common/elasticsearch.ts | 90 +++++ .../common/flamegraph.ts | 153 ++++++++ .../common/frame_group.ts | 38 ++ .../profiling_data_access/common/hash.ts | 79 +++++ .../profiling_data_access/common/profiling.ts | 329 ++++++++++++++++++ .../common/stack_traces.ts | 188 ++++++++++ .../profiling_data_access/jest.config.js | 14 + .../profiling_data_access/kibana.jsonc | 14 + .../profiling_data_access/server/index.ts | 34 ++ .../profiling_data_access/server/plugin.ts | 47 +++ .../server/services/fetch_flamechart/index.ts | 55 +++ .../server/services/register_services.ts | 21 ++ .../services/search_stack_traces/index.ts | 57 +++ .../utils/create_profiling_es_client.ts | 94 +++++ .../server/utils/with_profiling_span.ts | 25 ++ .../profiling_data_access/tsconfig.json | 19 + yarn.lock | 4 + 25 files changed, 1470 insertions(+), 36 deletions(-) create mode 100644 x-pack/plugins/profiling_data_access/.i18nrc.json create mode 100644 x-pack/plugins/profiling_data_access/common/base64.ts create mode 100644 x-pack/plugins/profiling_data_access/common/callee.ts create mode 100644 x-pack/plugins/profiling_data_access/common/elasticsearch.ts create mode 100644 x-pack/plugins/profiling_data_access/common/flamegraph.ts create mode 100644 x-pack/plugins/profiling_data_access/common/frame_group.ts create mode 100644 x-pack/plugins/profiling_data_access/common/hash.ts create mode 100644 x-pack/plugins/profiling_data_access/common/profiling.ts create mode 100644 x-pack/plugins/profiling_data_access/common/stack_traces.ts create mode 100644 x-pack/plugins/profiling_data_access/jest.config.js create mode 100644 x-pack/plugins/profiling_data_access/kibana.jsonc create mode 100644 x-pack/plugins/profiling_data_access/server/index.ts create mode 100644 x-pack/plugins/profiling_data_access/server/plugin.ts create mode 100644 x-pack/plugins/profiling_data_access/server/services/fetch_flamechart/index.ts create mode 100644 x-pack/plugins/profiling_data_access/server/services/register_services.ts create mode 100644 x-pack/plugins/profiling_data_access/server/services/search_stack_traces/index.ts create mode 100644 x-pack/plugins/profiling_data_access/server/utils/create_profiling_es_client.ts create mode 100644 x-pack/plugins/profiling_data_access/server/utils/with_profiling_span.ts create mode 100644 x-pack/plugins/profiling_data_access/tsconfig.json diff --git a/package.json b/package.json index 29f34767ba409..180e1dfeee578 100644 --- a/package.json +++ b/package.json @@ -555,6 +555,7 @@ "@kbn/portable-dashboards-example": "link:examples/portable_dashboards_example", "@kbn/preboot-example-plugin": "link:examples/preboot_example", "@kbn/presentation-util-plugin": "link:src/plugins/presentation_util", + "@kbn/profiling-data-access-plugin": "link:x-pack/plugins/profiling_data_access", "@kbn/profiling-plugin": "link:x-pack/plugins/profiling", "@kbn/random-sampling": "link:x-pack/packages/kbn-random-sampling", "@kbn/react-field": "link:packages/kbn-react-field", diff --git a/tsconfig.base.json b/tsconfig.base.json index a347a249b68ca..c473c168c990f 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1082,6 +1082,8 @@ "@kbn/preboot-example-plugin/*": ["examples/preboot_example/*"], "@kbn/presentation-util-plugin": ["src/plugins/presentation_util"], "@kbn/presentation-util-plugin/*": ["src/plugins/presentation_util/*"], + "@kbn/profiling-data-access-plugin": ["x-pack/plugins/profiling_data_access"], + "@kbn/profiling-data-access-plugin/*": ["x-pack/plugins/profiling_data_access/*"], "@kbn/profiling-plugin": ["x-pack/plugins/profiling"], "@kbn/profiling-plugin/*": ["x-pack/plugins/profiling/*"], "@kbn/random-sampling": ["x-pack/packages/kbn-random-sampling"], diff --git a/x-pack/plugins/profiling/kibana.jsonc b/x-pack/plugins/profiling/kibana.jsonc index 2985456d4ec8b..121da578bb9bb 100644 --- a/x-pack/plugins/profiling/kibana.jsonc +++ b/x-pack/plugins/profiling/kibana.jsonc @@ -23,7 +23,8 @@ "observabilityShared", "observabilityAIAssistant", "unifiedSearch", - "share" + "share", + "profilingDataAccess" ], "requiredBundles": [ "kibanaReact", diff --git a/x-pack/plugins/profiling/server/routes/flamechart.ts b/x-pack/plugins/profiling/server/routes/flamechart.ts index 0f78ca0288d81..5dedcc15e0c78 100644 --- a/x-pack/plugins/profiling/server/routes/flamechart.ts +++ b/x-pack/plugins/profiling/server/routes/flamechart.ts @@ -6,21 +6,17 @@ */ import { schema } from '@kbn/config-schema'; - import { RouteRegisterParameters } from '.'; import { getRoutePaths } from '../../common'; -import { createCalleeTree } from '../../common/callee'; import { handleRouteHandlerError } from '../utils/handle_route_error_handler'; -import { createBaseFlameGraph } from '../../common/flamegraph'; -import { withProfilingSpan } from '../utils/with_profiling_span'; import { getClient } from './compat'; -import { createCommonFilter } from './query'; -import { searchStackTraces } from './search_stacktraces'; export function registerFlameChartSearchRoute({ router, logger, - services: { createProfilingEsClient }, + dependencies: { + start: { profilingDataAccess }, + }, }: RouteRegisterParameters) { const paths = getRoutePaths(); router.get( @@ -37,39 +33,15 @@ export function registerFlameChartSearchRoute({ }, async (context, request, response) => { const { timeFrom, timeTo, kuery } = request.query; - const targetSampleSize = 20000; // minimum number of samples to get statistically sound results try { const esClient = await getClient(context); - const profilingElasticsearchClient = createProfilingEsClient({ request, esClient }); - const filter = createCommonFilter({ - timeFrom, - timeTo, + const flamegraph = await profilingDataAccess.services.fetchFlamechartData({ + esClient, + rangeFrom: timeFrom, + rangeTo: timeTo, kuery, }); - const totalSeconds = timeTo - timeFrom; - - const { events, stackTraces, executables, stackFrames, totalFrames, samplingRate } = - await searchStackTraces({ - client: profilingElasticsearchClient, - filter, - sampleSize: targetSampleSize, - }); - - const flamegraph = await withProfilingSpan('create_flamegraph', async () => { - const tree = createCalleeTree( - events, - stackTraces, - stackFrames, - executables, - totalFrames, - samplingRate - ); - - const fg = createBaseFlameGraph(tree, samplingRate, totalSeconds); - - return fg; - }); return response.ok({ body: flamegraph }); } catch (error) { diff --git a/x-pack/plugins/profiling/server/types.ts b/x-pack/plugins/profiling/server/types.ts index e1542e6939896..6ee94e238effa 100644 --- a/x-pack/plugins/profiling/server/types.ts +++ b/x-pack/plugins/profiling/server/types.ts @@ -12,6 +12,10 @@ import { SpacesPluginStart, SpacesPluginSetup } from '@kbn/spaces-plugin/server' import { CloudSetup, CloudStart } from '@kbn/cloud-plugin/server'; import { FleetSetupContract, FleetStartContract } from '@kbn/fleet-plugin/server'; import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; +import { + ProfilingDataAccessPluginSetup, + ProfilingDataAccessPluginStart, +} from '@kbn/profiling-data-access-plugin/server'; export interface ProfilingPluginSetupDeps { observability: ObservabilityPluginSetup; @@ -20,6 +24,7 @@ export interface ProfilingPluginSetupDeps { fleet: FleetSetupContract; spaces?: SpacesPluginSetup; usageCollection?: UsageCollectionSetup; + profilingDataAccess: ProfilingDataAccessPluginSetup; } export interface ProfilingPluginStartDeps { @@ -28,6 +33,7 @@ export interface ProfilingPluginStartDeps { cloud: CloudStart; fleet: FleetStartContract; spaces?: SpacesPluginStart; + profilingDataAccess: ProfilingDataAccessPluginStart; } // eslint-disable-next-line @typescript-eslint/no-empty-interface diff --git a/x-pack/plugins/profiling_data_access/.i18nrc.json b/x-pack/plugins/profiling_data_access/.i18nrc.json new file mode 100644 index 0000000000000..de8ac3249413e --- /dev/null +++ b/x-pack/plugins/profiling_data_access/.i18nrc.json @@ -0,0 +1,7 @@ +{ + "prefix": "profiling", + "paths": { + "profiling": "." + }, + "translations": [] +} diff --git a/x-pack/plugins/profiling_data_access/common/base64.ts b/x-pack/plugins/profiling_data_access/common/base64.ts new file mode 100644 index 0000000000000..0d724c142271a --- /dev/null +++ b/x-pack/plugins/profiling_data_access/common/base64.ts @@ -0,0 +1,22 @@ +/* + * 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. + */ + +export const safeBase64Decoder = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 0, 0, 0, 0, 63, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, + 45, 46, 47, 48, 49, 50, 51, 0, 0, 0, 0, 0, +]; + +export const safeBase64Encoder = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234456789-_'; + +/* eslint no-bitwise: ["error", { "allow": ["&"] }] */ +export function charCodeAt(input: string, i: number): number { + return safeBase64Decoder[input.charCodeAt(i) & 0x7f]; +} diff --git a/x-pack/plugins/profiling_data_access/common/callee.ts b/x-pack/plugins/profiling_data_access/common/callee.ts new file mode 100644 index 0000000000000..68fa9170f44ec --- /dev/null +++ b/x-pack/plugins/profiling_data_access/common/callee.ts @@ -0,0 +1,162 @@ +/* + * 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 { createFrameGroupID, FrameGroupID } from './frame_group'; +import { + emptyExecutable, + emptyStackFrame, + emptyStackTrace, + Executable, + FileID, + StackFrame, + StackFrameID, + StackTrace, + StackTraceID, +} from './profiling'; + +type NodeID = number; + +export interface CalleeTree { + Size: number; + Edges: Array>; + + FileID: string[]; + FrameType: number[]; + Inline: boolean[]; + ExeFilename: string[]; + AddressOrLine: number[]; + FunctionName: string[]; + FunctionOffset: number[]; + SourceFilename: string[]; + SourceLine: number[]; + + CountInclusive: number[]; + CountExclusive: number[]; +} + +export function createCalleeTree( + events: Map, + stackTraces: Map, + stackFrames: Map, + executables: Map, + totalFrames: number, + samplingRate: number +): CalleeTree { + const tree: CalleeTree = { + Size: 1, + Edges: new Array(totalFrames), + FileID: new Array(totalFrames), + FrameType: new Array(totalFrames), + Inline: new Array(totalFrames), + ExeFilename: new Array(totalFrames), + AddressOrLine: new Array(totalFrames), + FunctionName: new Array(totalFrames), + FunctionOffset: new Array(totalFrames), + SourceFilename: new Array(totalFrames), + SourceLine: new Array(totalFrames), + + CountInclusive: new Array(totalFrames), + CountExclusive: new Array(totalFrames), + }; + + // The inverse of the sampling rate is the number with which to multiply the number of + // samples to get an estimate of the actual number of samples the backend received. + const scalingFactor = 1.0 / samplingRate; + tree.Edges[0] = new Map(); + + tree.FileID[0] = ''; + tree.FrameType[0] = 0; + tree.Inline[0] = false; + tree.ExeFilename[0] = ''; + tree.AddressOrLine[0] = 0; + tree.FunctionName[0] = ''; + tree.FunctionOffset[0] = 0; + tree.SourceFilename[0] = ''; + tree.SourceLine[0] = 0; + + tree.CountInclusive[0] = 0; + tree.CountExclusive[0] = 0; + + const sortedStackTraceIDs = new Array(); + for (const trace of stackTraces.keys()) { + sortedStackTraceIDs.push(trace); + } + sortedStackTraceIDs.sort((t1, t2) => { + return t1.localeCompare(t2); + }); + + // Walk through all traces. Increment the count of the root by the count of + // that trace. Walk "down" the trace (through the callees) and add the count + // of the trace to each callee. + + for (const stackTraceID of sortedStackTraceIDs) { + // The slice of frames is ordered so that the leaf function is at the + // highest index. + + // It is possible that we do not have a stacktrace for an event, + // e.g. when stopping the host agent or on network errors. + const stackTrace = stackTraces.get(stackTraceID) ?? emptyStackTrace; + const lenStackTrace = stackTrace.FrameIDs.length; + const samples = Math.floor((events.get(stackTraceID) ?? 0) * scalingFactor); + + let currentNode = 0; + + // Increment the count by the number of samples observed, multiplied with the inverse of the + // samplingrate (this essentially means scaling up the total samples). It would incur + tree.CountInclusive[currentNode] += samples; + tree.CountExclusive[currentNode] = 0; + + for (let i = 0; i < lenStackTrace; i++) { + const frameID = stackTrace.FrameIDs[i]; + const fileID = stackTrace.FileIDs[i]; + const addressOrLine = stackTrace.AddressOrLines[i]; + const frame = stackFrames.get(frameID) ?? emptyStackFrame; + const executable = executables.get(fileID) ?? emptyExecutable; + + const frameGroupID = createFrameGroupID( + fileID, + addressOrLine, + executable.FileName, + frame.FileName, + frame.FunctionName + ); + + let node = tree.Edges[currentNode].get(frameGroupID); + + if (node === undefined) { + node = tree.Size; + + tree.FileID[node] = fileID; + tree.FrameType[node] = stackTrace.Types[i]; + tree.ExeFilename[node] = executable.FileName; + tree.AddressOrLine[node] = addressOrLine; + tree.FunctionName[node] = frame.FunctionName; + tree.FunctionOffset[node] = frame.FunctionOffset; + tree.SourceLine[node] = frame.LineNumber; + tree.SourceFilename[node] = frame.FileName; + tree.Inline[node] = frame.Inline; + tree.CountInclusive[node] = samples; + tree.CountExclusive[node] = 0; + + tree.Edges[currentNode].set(frameGroupID, node); + tree.Edges[node] = new Map(); + + tree.Size++; + } else { + tree.CountInclusive[node] += samples; + } + + if (i === lenStackTrace - 1) { + // Leaf frame: sum up counts for exclusive CPU. + tree.CountExclusive[node] += samples; + } + currentNode = node; + } + } + + return tree; +} diff --git a/x-pack/plugins/profiling_data_access/common/elasticsearch.ts b/x-pack/plugins/profiling_data_access/common/elasticsearch.ts new file mode 100644 index 0000000000000..a47e4c018d581 --- /dev/null +++ b/x-pack/plugins/profiling_data_access/common/elasticsearch.ts @@ -0,0 +1,90 @@ +/* + * 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 { UnionToIntersection, ValuesType } from 'utility-types'; + +export enum ProfilingESField { + Timestamp = '@timestamp', + ContainerName = 'container.name', + ProcessThreadName = 'process.thread.name', + StacktraceCount = 'Stacktrace.count', + HostID = 'host.id', + HostName = 'host.name', + HostIP = 'host.ip', + OrchestratorResourceName = 'orchestrator.resource.name', + ServiceName = 'service.name', + StacktraceID = 'Stacktrace.id', + StacktraceFrameIDs = 'Stacktrace.frame.ids', + StacktraceFrameTypes = 'Stacktrace.frame.types', + StackframeFileName = 'Stackframe.file.name', + StackframeFunctionName = 'Stackframe.function.name', + StackframeLineNumber = 'Stackframe.line.number', + StackframeFunctionOffset = 'Stackframe.function.offset', + ExecutableBuildID = 'Executable.build.id', + ExecutableFileName = 'Executable.file.name', +} + +type DedotKey< + TKey extends string | number | symbol, + TValue +> = TKey extends `${infer THead}.${infer TTail}` + ? { + [key in THead]: DedotKey; + } + : { [key in TKey]: TValue }; + +export type DedotObject> = UnionToIntersection< + ValuesType<{ + [TKey in keyof TObject]: DedotKey; + }> +>; + +export type FlattenObject< + TObject extends Record, + TPrefix extends string = '' +> = UnionToIntersection< + ValuesType<{ + [TKey in keyof TObject & string]: TObject[TKey] extends Record + ? FlattenObject + : { [key in `${TPrefix}${TKey}`]: TObject[TKey] }; + }> +>; + +type FlattenedKeysOf> = keyof FlattenObject; + +export type PickFlattened< + TObject extends Record, + TPickKey extends FlattenedKeysOf +> = DedotObject, TPickKey>>; + +export type ProfilingESEvent = DedotObject<{ + [ProfilingESField.Timestamp]: string; + [ProfilingESField.ContainerName]: string; + [ProfilingESField.ProcessThreadName]: string; + [ProfilingESField.StacktraceCount]: number; + [ProfilingESField.HostID]: string; + [ProfilingESField.OrchestratorResourceName]: string; + [ProfilingESField.ServiceName]: string; + [ProfilingESField.StacktraceID]: string; +}>; + +export type ProfilingStackTrace = DedotObject<{ + [ProfilingESField.StacktraceFrameIDs]: string; + [ProfilingESField.StacktraceFrameTypes]: string; +}>; + +export type ProfilingStackFrame = DedotObject<{ + [ProfilingESField.StackframeFileName]: string; + [ProfilingESField.StackframeFunctionName]: string; + [ProfilingESField.StackframeLineNumber]: number; + [ProfilingESField.StackframeFunctionOffset]: number; +}>; + +export type ProfilingExecutable = DedotObject<{ + [ProfilingESField.ExecutableBuildID]: string; + [ProfilingESField.ExecutableFileName]: string; +}>; diff --git a/x-pack/plugins/profiling_data_access/common/flamegraph.ts b/x-pack/plugins/profiling_data_access/common/flamegraph.ts new file mode 100644 index 0000000000000..16fb8c1a396c5 --- /dev/null +++ b/x-pack/plugins/profiling_data_access/common/flamegraph.ts @@ -0,0 +1,153 @@ +/* + * 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 { CalleeTree } from './callee'; +import { createFrameGroupID } from './frame_group'; +import { fnv1a64 } from './hash'; +import { createStackFrameMetadata, getCalleeLabel } from './profiling'; + +export interface BaseFlameGraph { + Size: number; + Edges: number[][]; + + FileID: string[]; + FrameType: number[]; + Inline: boolean[]; + ExeFilename: string[]; + AddressOrLine: number[]; + FunctionName: string[]; + FunctionOffset: number[]; + SourceFilename: string[]; + SourceLine: number[]; + + CountInclusive: number[]; + CountExclusive: number[]; + + TotalSeconds: number; + SamplingRate: number; +} + +// createBaseFlameGraph encapsulates the tree representation into a serialized form. +export function createBaseFlameGraph( + tree: CalleeTree, + samplingRate: number, + totalSeconds: number +): BaseFlameGraph { + const graph: BaseFlameGraph = { + Size: tree.Size, + SamplingRate: samplingRate, + Edges: new Array(tree.Size), + + FileID: tree.FileID.slice(0, tree.Size), + FrameType: tree.FrameType.slice(0, tree.Size), + Inline: tree.Inline.slice(0, tree.Size), + ExeFilename: tree.ExeFilename.slice(0, tree.Size), + AddressOrLine: tree.AddressOrLine.slice(0, tree.Size), + FunctionName: tree.FunctionName.slice(0, tree.Size), + FunctionOffset: tree.FunctionOffset.slice(0, tree.Size), + SourceFilename: tree.SourceFilename.slice(0, tree.Size), + SourceLine: tree.SourceLine.slice(0, tree.Size), + + CountInclusive: tree.CountInclusive.slice(0, tree.Size), + CountExclusive: tree.CountExclusive.slice(0, tree.Size), + + TotalSeconds: totalSeconds, + }; + + for (let i = 0; i < tree.Size; i++) { + let j = 0; + const nodes = new Array(tree.Edges[i].size); + for (const [, n] of tree.Edges[i]) { + nodes[j] = n; + j++; + } + graph.Edges[i] = nodes; + } + + return graph; +} + +export interface ElasticFlameGraph extends BaseFlameGraph { + ID: string[]; + Label: string[]; +} + +// createFlameGraph combines the base flamegraph with CPU-intensive values. +// This allows us to create a flamegraph in two steps (e.g. first on the server +// and finally in the browser). +export function createFlameGraph(base: BaseFlameGraph): ElasticFlameGraph { + const graph: ElasticFlameGraph = { + Size: base.Size, + SamplingRate: base.SamplingRate, + Edges: base.Edges, + + FileID: base.FileID, + FrameType: base.FrameType, + Inline: base.Inline, + ExeFilename: base.ExeFilename, + AddressOrLine: base.AddressOrLine, + FunctionName: base.FunctionName, + FunctionOffset: base.FunctionOffset, + SourceFilename: base.SourceFilename, + SourceLine: base.SourceLine, + + CountInclusive: base.CountInclusive, + CountExclusive: base.CountExclusive, + + ID: new Array(base.Size), + Label: new Array(base.Size), + + TotalSeconds: base.TotalSeconds, + }; + + const rootFrameGroupID = createFrameGroupID( + graph.FileID[0], + graph.AddressOrLine[0], + graph.ExeFilename[0], + graph.SourceFilename[0], + graph.FunctionName[0] + ); + + graph.ID[0] = fnv1a64(new TextEncoder().encode(rootFrameGroupID)); + + const queue = [0]; + while (queue.length > 0) { + const parent = queue.pop()!; + for (const child of graph.Edges[parent]) { + const frameGroupID = createFrameGroupID( + graph.FileID[child], + graph.AddressOrLine[child], + graph.ExeFilename[child], + graph.SourceFilename[child], + graph.FunctionName[child] + ); + const bytes = new TextEncoder().encode(graph.ID[parent] + frameGroupID); + graph.ID[child] = fnv1a64(bytes); + queue.push(child); + } + } + + graph.Label[0] = 'root: Represents 100% of CPU time.'; + + for (let i = 1; i < graph.Size; i++) { + const metadata = createStackFrameMetadata({ + FileID: graph.FileID[i], + FrameType: graph.FrameType[i], + Inline: graph.Inline[i], + ExeFileName: graph.ExeFilename[i], + AddressOrLine: graph.AddressOrLine[i], + FunctionName: graph.FunctionName[i], + FunctionOffset: graph.FunctionOffset[i], + SourceFilename: graph.SourceFilename[i], + SourceLine: graph.SourceLine[i], + SamplingRate: graph.SamplingRate, + }); + graph.Label[i] = getCalleeLabel(metadata); + } + + return graph; +} diff --git a/x-pack/plugins/profiling_data_access/common/frame_group.ts b/x-pack/plugins/profiling_data_access/common/frame_group.ts new file mode 100644 index 0000000000000..6881b14ed98fe --- /dev/null +++ b/x-pack/plugins/profiling_data_access/common/frame_group.ts @@ -0,0 +1,38 @@ +/* + * 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 { takeRight } from 'lodash'; +import { StackFrameMetadata } from './profiling'; + +export type FrameGroupID = string; + +function stripLeadingSubdirs(sourceFileName: string) { + return takeRight(sourceFileName.split('/'), 2).join('/'); +} + +// createFrameGroupID is the "standard" way of grouping frames, by commonly +// shared group identifiers. +// +// For ELF-symbolized frames, group by FunctionName, ExeFileName and FileID. +// For non-symbolized frames, group by FileID and AddressOrLine. +// otherwise group by ExeFileName, SourceFilename and FunctionName. +export function createFrameGroupID( + fileID: StackFrameMetadata['FileID'], + addressOrLine: StackFrameMetadata['AddressOrLine'], + exeFilename: StackFrameMetadata['ExeFileName'], + sourceFilename: StackFrameMetadata['SourceFilename'], + functionName: StackFrameMetadata['FunctionName'] +): FrameGroupID { + if (functionName === '') { + return `empty;${fileID};${addressOrLine}`; + } + + if (sourceFilename === '') { + return `elf;${exeFilename};${functionName}`; + } + + return `full;${exeFilename};${functionName};${stripLeadingSubdirs(sourceFilename || '')}`; +} diff --git a/x-pack/plugins/profiling_data_access/common/hash.ts b/x-pack/plugins/profiling_data_access/common/hash.ts new file mode 100644 index 0000000000000..3eab4bde871e0 --- /dev/null +++ b/x-pack/plugins/profiling_data_access/common/hash.ts @@ -0,0 +1,79 @@ +/* + * 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. + */ + +// prettier-ignore +const lowerHex = [ + '00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '0a', '0b', '0c', '0d', '0e', '0f', + '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '1a', '1b', '1c', '1d', '1e', '1f', + '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '2a', '2b', '2c', '2d', '2e', '2f', + '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '3a', '3b', '3c', '3d', '3e', '3f', + '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '4a', '4b', '4c', '4d', '4e', '4f', + '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '5a', '5b', '5c', '5d', '5e', '5f', + '60', '61', '62', '63', '64', '65', '66', '67', '68', '69', '6a', '6b', '6c', '6d', '6e', '6f', + '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '7a', '7b', '7c', '7d', '7e', '7f', + '80', '81', '82', '83', '84', '85', '86', '87', '88', '89', '8a', '8b', '8c', '8d', '8e', '8f', + '90', '91', '92', '93', '94', '95', '96', '97', '98', '99', '9a', '9b', '9c', '9d', '9e', '9f', + 'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9', 'aa', 'ab', 'ac', 'ad', 'ae', 'af', + 'b0', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'b7', 'b8', 'b9', 'ba', 'bb', 'bc', 'bd', 'be', 'bf', + 'c0', 'c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8', 'c9', 'ca', 'cb', 'cc', 'cd', 'ce', 'cf', + 'd0', 'd1', 'd2', 'd3', 'd4', 'd5', 'd6', 'd7', 'd8', 'd9', 'da', 'db', 'dc', 'dd', 'de', 'df', + 'e0', 'e1', 'e2', 'e3', 'e4', 'e5', 'e6', 'e7', 'e8', 'e9', 'ea', 'eb', 'ec', 'ed', 'ee', 'ef', + 'f0', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'fa', 'fb', 'fc', 'fd', 'fe', 'ff', +]; + +// fnv1a64 computes a 64-bit hash of a byte array using the FNV-1a hash function [1]. +// +// Due to the lack of a native uint64 in JavaScript, we operate on 64-bit values using an array +// of 4 uint16s instead. This method follows Knuth's Algorithm M in section 4.3.1 [2] using a +// modified multiword multiplication implementation described in [3]. The modifications include: +// +// * rewrite default algorithm for the special case m = n = 4 +// * unroll loops +// * simplify expressions +// * create pre-computed lookup table for serialization to hexadecimal +// +// 1. https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function +// 2. Knuth, Donald E. The Art of Computer Programming, Volume 2, Third Edition: Seminumerical +// Algorithms. Addison-Wesley, 1998. +// 3. Warren, Henry S. Hacker's Delight. Upper Saddle River, NJ: Addison-Wesley, 2013. + +/* eslint no-bitwise: ["error", { "allow": ["^=", ">>", "&"] }] */ +export function fnv1a64(bytes: Uint8Array): string { + const n = bytes.length; + let [h0, h1, h2, h3] = [0x2325, 0x8422, 0x9ce4, 0xcbf2]; + let [t0, t1, t2, t3] = [0, 0, 0, 0]; + + for (let i = 0; i < n; i++) { + h0 ^= bytes[i]; + + t0 = h0 * 0x01b3; + t1 = h1 * 0x01b3; + t2 = h2 * 0x01b3; + t3 = h3 * 0x01b3; + + t1 += t0 >> 16; + t2 += t1 >> 16; + t2 += h0 * 0x0100; + t3 += h1 * 0x0100; + + h0 = t0 & 0xffff; + h1 = t1 & 0xffff; + h2 = t2 & 0xffff; + h3 = (t3 + (t2 >> 16)) & 0xffff; + } + + return ( + lowerHex[h3 >> 8] + + lowerHex[h3 & 0xff] + + lowerHex[h2 >> 8] + + lowerHex[h2 & 0xff] + + lowerHex[h1 >> 8] + + lowerHex[h1 & 0xff] + + lowerHex[h0 >> 8] + + lowerHex[h0 & 0xff] + ); +} diff --git a/x-pack/plugins/profiling_data_access/common/profiling.ts b/x-pack/plugins/profiling_data_access/common/profiling.ts new file mode 100644 index 0000000000000..22480a26f8ab2 --- /dev/null +++ b/x-pack/plugins/profiling_data_access/common/profiling.ts @@ -0,0 +1,329 @@ +/* + * 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 { charCodeAt, safeBase64Encoder } from './base64'; + +export type StackTraceID = string; +export type StackFrameID = string; +export type FileID = string; + +export function createStackFrameID(fileID: FileID, addressOrLine: number): StackFrameID { + const buf = Buffer.alloc(24); + Buffer.from(fileID, 'base64url').copy(buf); + buf.writeBigUInt64BE(BigInt(addressOrLine), 16); + return buf.toString('base64url'); +} + +/* eslint no-bitwise: ["error", { "allow": ["&"] }] */ +export function getFileIDFromStackFrameID(frameID: StackFrameID): FileID { + return frameID.slice(0, 21) + safeBase64Encoder[frameID.charCodeAt(21) & 0x30]; +} + +/* eslint no-bitwise: ["error", { "allow": ["<<=", "&"] }] */ +export function getAddressFromStackFrameID(frameID: StackFrameID): number { + let address = charCodeAt(frameID, 21) & 0xf; + address <<= 6; + address += charCodeAt(frameID, 22); + address <<= 6; + address += charCodeAt(frameID, 23); + address <<= 6; + address += charCodeAt(frameID, 24); + address <<= 6; + address += charCodeAt(frameID, 25); + address <<= 6; + address += charCodeAt(frameID, 26); + address <<= 6; + address += charCodeAt(frameID, 27); + address <<= 6; + address += charCodeAt(frameID, 28); + address <<= 6; + address += charCodeAt(frameID, 29); + address <<= 6; + address += charCodeAt(frameID, 30); + address <<= 6; + address += charCodeAt(frameID, 31); + return address; +} + +export enum FrameType { + Unsymbolized = 0, + Python, + PHP, + Native, + Kernel, + JVM, + Ruby, + Perl, + JavaScript, + PHPJIT, +} + +const frameTypeDescriptions = { + [FrameType.Unsymbolized]: '', + [FrameType.Python]: 'Python', + [FrameType.PHP]: 'PHP', + [FrameType.Native]: 'Native', + [FrameType.Kernel]: 'Kernel', + [FrameType.JVM]: 'JVM/Hotspot', + [FrameType.Ruby]: 'Ruby', + [FrameType.Perl]: 'Perl', + [FrameType.JavaScript]: 'JavaScript', + [FrameType.PHPJIT]: 'PHP JIT', +}; + +export function describeFrameType(ft: FrameType): string { + return frameTypeDescriptions[ft]; +} + +export interface StackTraceEvent { + StackTraceID: StackTraceID; + Count: number; +} + +export interface StackTrace { + FrameIDs: string[]; + FileIDs: string[]; + AddressOrLines: number[]; + Types: number[]; +} + +export const emptyStackTrace: StackTrace = { + FrameIDs: [], + FileIDs: [], + AddressOrLines: [], + Types: [], +}; + +export interface StackFrame { + FileName: string; + FunctionName: string; + FunctionOffset: number; + LineNumber: number; + Inline: boolean; +} + +export const emptyStackFrame: StackFrame = { + FileName: '', + FunctionName: '', + FunctionOffset: 0, + LineNumber: 0, + Inline: false, +}; + +export interface Executable { + FileName: string; +} + +export const emptyExecutable: Executable = { + FileName: '', +}; + +export interface StackFrameMetadata { + // StackTrace.FrameID + FrameID: string; + // StackTrace.FileID + FileID: FileID; + // StackTrace.Type + FrameType: FrameType; + // StackFrame.Inline + Inline: boolean; + + // StackTrace.AddressOrLine + AddressOrLine: number; + // StackFrame.FunctionName + FunctionName: string; + // StackFrame.FunctionOffset + FunctionOffset: number; + // should this be StackFrame.SourceID? + SourceID: FileID; + // StackFrame.Filename + SourceFilename: string; + // StackFrame.LineNumber + SourceLine: number; + // auto-generated - see createStackFrameMetadata + FunctionSourceLine: number; + + // Executable.FileName + ExeFileName: string; + + // unused atm due to lack of symbolization metadata + CommitHash: string; + // unused atm due to lack of symbolization metadata + SourceCodeURL: string; + // unused atm due to lack of symbolization metadata + SourcePackageHash: string; + // unused atm due to lack of symbolization metadata + SourcePackageURL: string; + // unused atm due to lack of symbolization metadata + + SamplingRate: number; +} + +export function createStackFrameMetadata( + options: Partial = {} +): StackFrameMetadata { + const metadata = {} as StackFrameMetadata; + + metadata.FrameID = options.FrameID ?? ''; + metadata.FileID = options.FileID ?? ''; + metadata.FrameType = options.FrameType ?? 0; + metadata.Inline = options.Inline ?? false; + metadata.AddressOrLine = options.AddressOrLine ?? 0; + metadata.FunctionName = options.FunctionName ?? ''; + metadata.FunctionOffset = options.FunctionOffset ?? 0; + metadata.SourceID = options.SourceID ?? ''; + metadata.SourceLine = options.SourceLine ?? 0; + metadata.ExeFileName = options.ExeFileName ?? ''; + metadata.CommitHash = options.CommitHash ?? ''; + metadata.SourceCodeURL = options.SourceCodeURL ?? ''; + metadata.SourceFilename = options.SourceFilename ?? ''; + metadata.SourcePackageHash = options.SourcePackageHash ?? ''; + metadata.SourcePackageURL = options.SourcePackageURL ?? ''; + metadata.SamplingRate = options.SamplingRate ?? 1.0; + + // Unknown/invalid offsets are currently set to 0. + // + // In this case we leave FunctionSourceLine=0 as a flag for the UI that the + // FunctionSourceLine should not be displayed. + // + // As FunctionOffset=0 could also be a legit value, this work-around needs + // a real fix. The idea for after GA is to change FunctionOffset=-1 to + // indicate unknown/invalid. + if (metadata.FunctionOffset > 0) { + metadata.FunctionSourceLine = metadata.SourceLine - metadata.FunctionOffset; + } else { + metadata.FunctionSourceLine = 0; + } + + return metadata; +} + +function checkIfStringHasParentheses(s: string) { + return /\(|\)/.test(s); +} + +function getFunctionName(metadata: StackFrameMetadata) { + return metadata.FunctionName !== '' && !checkIfStringHasParentheses(metadata.FunctionName) + ? `${metadata.FunctionName}()` + : metadata.FunctionName; +} + +function getExeFileName(metadata: StackFrameMetadata) { + if (metadata?.ExeFileName === undefined) { + return ''; + } + if (metadata.ExeFileName !== '') { + return metadata.ExeFileName; + } + return describeFrameType(metadata.FrameType); +} + +export function getCalleeLabel(metadata: StackFrameMetadata) { + if (metadata.FunctionName !== '') { + const sourceFilename = metadata.SourceFilename; + const sourceURL = sourceFilename ? sourceFilename.split('/').pop() : ''; + return `${getExeFileName(metadata)}: ${getFunctionName(metadata)} in ${sourceURL}#${ + metadata.SourceLine + }`; + } + return getExeFileName(metadata); +} + +export function getCalleeFunction(frame: StackFrameMetadata): string { + // In the best case scenario, we have the file names, source lines, + // and function names. However we need to deal with missing function or + // executable info. + const exeDisplayName = frame.ExeFileName ? frame.ExeFileName : describeFrameType(frame.FrameType); + + // When there is no function name, only use the executable name + return frame.FunctionName ? exeDisplayName + ': ' + frame.FunctionName : exeDisplayName; +} +export enum FrameSymbolStatus { + PARTIALLY_SYMBOLYZED = 'PARTIALLY_SYMBOLYZED', + NOT_SYMBOLIZED = 'NOT_SYMBOLIZED', + SYMBOLIZED = 'SYMBOLIZED', +} +export function getFrameSymbolStatus({ + sourceFilename, + sourceLine, + exeFileName, +}: { + sourceFilename: string; + sourceLine: number; + exeFileName?: string; +}) { + if (sourceFilename === '' && sourceLine === 0) { + if (exeFileName) { + return FrameSymbolStatus.PARTIALLY_SYMBOLYZED; + } + + return FrameSymbolStatus.NOT_SYMBOLIZED; + } + + return FrameSymbolStatus.SYMBOLIZED; +} + +const nativeLanguages = [FrameType.Native, FrameType.Kernel]; +export function getLanguageType({ frameType }: { frameType: FrameType }) { + return nativeLanguages.includes(frameType) ? 'NATIVE' : 'INTERPRETED'; +} + +export function getCalleeSource(frame: StackFrameMetadata): string { + const frameSymbolStatus = getFrameSymbolStatus({ + sourceFilename: frame.SourceFilename, + sourceLine: frame.SourceLine, + exeFileName: frame.ExeFileName, + }); + + switch (frameSymbolStatus) { + case FrameSymbolStatus.NOT_SYMBOLIZED: { + // If we don't have the executable filename, display + return ''; + } + case FrameSymbolStatus.PARTIALLY_SYMBOLYZED: { + // If no source line or filename available, display the executable offset + return frame.ExeFileName + '+0x' + frame.AddressOrLine.toString(16); + } + case FrameSymbolStatus.SYMBOLIZED: { + return frame.SourceFilename + (frame.SourceLine !== 0 ? `#${frame.SourceLine}` : ''); + } + } +} + +export function groupStackFrameMetadataByStackTrace( + stackTraces: Map, + stackFrames: Map, + executables: Map +): Record { + const stackTraceMap: Record = {}; + for (const [stackTraceID, trace] of stackTraces) { + const numFramesPerTrace = trace.FrameIDs.length; + const frameMetadata = new Array(numFramesPerTrace); + for (let i = 0; i < numFramesPerTrace; i++) { + const frameID = trace.FrameIDs[i]; + const fileID = trace.FileIDs[i]; + const addressOrLine = trace.AddressOrLines[i]; + const frame = stackFrames.get(frameID) ?? emptyStackFrame; + const executable = executables.get(fileID) ?? emptyExecutable; + + frameMetadata[i] = createStackFrameMetadata({ + FrameID: frameID, + FileID: fileID, + AddressOrLine: addressOrLine, + FrameType: trace.Types[i], + Inline: frame.Inline, + FunctionName: frame.FunctionName, + FunctionOffset: frame.FunctionOffset, + SourceLine: frame.LineNumber, + SourceFilename: frame.FileName, + ExeFileName: executable.FileName, + }); + } + stackTraceMap[stackTraceID] = frameMetadata; + } + return stackTraceMap; +} diff --git a/x-pack/plugins/profiling_data_access/common/stack_traces.ts b/x-pack/plugins/profiling_data_access/common/stack_traces.ts new file mode 100644 index 0000000000000..8266f32a19f1e --- /dev/null +++ b/x-pack/plugins/profiling_data_access/common/stack_traces.ts @@ -0,0 +1,188 @@ +/* + * 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 { ProfilingESField } from './elasticsearch'; +import { + Executable, + FileID, + StackFrame, + StackFrameID, + StackTrace, + StackTraceID, +} from './profiling'; + +export interface ProfilingStatusResponse { + profiling: { + enabled: boolean; + }; + resource_management: { + enabled: boolean; + }; + resources: { + created: boolean; + }; +} + +interface ProfilingEvents { + [key: string]: number; +} + +export interface ProfilingStackTrace { + ['file_ids']: string[]; + ['frame_ids']: string[]; + ['address_or_lines']: number[]; + ['type_ids']: number[]; +} + +interface ProfilingStackTraces { + [key: string]: ProfilingStackTrace; +} + +export interface ProfilingStackFrame { + ['file_name']: string[]; + ['function_name']: string[]; + ['function_offset']: number[]; + ['line_number']: number[]; +} + +interface ProfilingStackFrames { + [key: string]: ProfilingStackFrame; +} + +interface ProfilingExecutables { + [key: string]: string; +} + +export interface StackTraceResponse { + ['stack_trace_events']?: ProfilingEvents; + ['stack_traces']?: ProfilingStackTraces; + ['stack_frames']?: ProfilingStackFrames; + ['executables']?: ProfilingExecutables; + ['total_frames']: number; + ['sampling_rate']: number; +} + +export interface DecodedStackTraceResponse { + events: Map; + stackTraces: Map; + stackFrames: Map; + executables: Map; + totalFrames: number; + samplingRate: number; +} + +export const makeFrameID = (frameID: string, n: number): string => { + return n === 0 ? frameID : frameID + ';' + n.toString(); +}; + +// createInlineTrace builds a new StackTrace with inline frames. +const createInlineTrace = ( + trace: ProfilingStackTrace, + frames: Map +): StackTrace => { + // The arrays need to be extended with the inline frame information. + const frameIDs: string[] = []; + const fileIDs: string[] = []; + const addressOrLines: number[] = []; + const typeIDs: number[] = []; + + for (let i = 0; i < trace.frame_ids.length; i++) { + const frameID = trace.frame_ids[i]; + frameIDs.push(frameID); + fileIDs.push(trace.file_ids[i]); + addressOrLines.push(trace.address_or_lines[i]); + typeIDs.push(trace.type_ids[i]); + + for (let j = 1; ; j++) { + const inlineID = makeFrameID(frameID, j); + const frame = frames.get(inlineID); + if (!frame) { + break; + } + frameIDs.push(inlineID); + fileIDs.push(trace.file_ids[i]); + addressOrLines.push(trace.address_or_lines[i]); + typeIDs.push(trace.type_ids[i]); + } + } + + return { + FrameIDs: frameIDs, + FileIDs: fileIDs, + AddressOrLines: addressOrLines, + Types: typeIDs, + } as StackTrace; +}; + +export function decodeStackTraceResponse(response: StackTraceResponse): DecodedStackTraceResponse { + const stackTraceEvents: Map = new Map(); + for (const [key, value] of Object.entries(response.stack_trace_events ?? {})) { + stackTraceEvents.set(key, value); + } + + const stackFrames: Map = new Map(); + for (const [frameID, frame] of Object.entries(response.stack_frames ?? {})) { + // Each field in a stackframe is represented by an array. This is + // necessary to support inline frames. + // + // We store the inlined frames with a modified (and unique) ID. + // We can do so since we don't display the frame IDs. + for (let i = 0; i < frame.function_name.length; i++) { + stackFrames.set(makeFrameID(frameID, i), { + FileName: frame.file_name[i], + FunctionName: frame.function_name[i], + FunctionOffset: frame.function_offset[i], + LineNumber: frame.line_number[i], + Inline: i > 0, + } as StackFrame); + } + } + + const stackTraces: Map = new Map(); + for (const [traceID, trace] of Object.entries(response.stack_traces ?? {})) { + stackTraces.set(traceID, createInlineTrace(trace, stackFrames)); + } + + const executables: Map = new Map(); + for (const [key, value] of Object.entries(response.executables ?? {})) { + executables.set(key, { + FileName: value, + } as Executable); + } + + return { + events: stackTraceEvents, + stackTraces, + stackFrames, + executables, + totalFrames: response.total_frames, + samplingRate: response.sampling_rate, + }; +} + +export enum StackTracesDisplayOption { + StackTraces = 'stackTraces', + Percentage = 'percentage', +} + +export enum TopNType { + Containers = 'containers', + Deployments = 'deployments', + Threads = 'threads', + Hosts = 'hosts', + Traces = 'traces', +} + +export function getFieldNameForTopNType(type: TopNType): string { + return { + [TopNType.Containers]: ProfilingESField.ContainerName, + [TopNType.Deployments]: ProfilingESField.OrchestratorResourceName, + [TopNType.Threads]: ProfilingESField.ProcessThreadName, + [TopNType.Hosts]: ProfilingESField.HostID, + [TopNType.Traces]: ProfilingESField.StacktraceID, + }[type]; +} diff --git a/x-pack/plugins/profiling_data_access/jest.config.js b/x-pack/plugins/profiling_data_access/jest.config.js new file mode 100644 index 0000000000000..c87c047a5ea73 --- /dev/null +++ b/x-pack/plugins/profiling_data_access/jest.config.js @@ -0,0 +1,14 @@ +/* + * 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. + */ + +const path = require('path'); + +module.exports = { + preset: '@kbn/test', + rootDir: path.resolve(__dirname, '../../..'), + roots: ['/x-pack/plugins/profiling_data_access'], +}; diff --git a/x-pack/plugins/profiling_data_access/kibana.jsonc b/x-pack/plugins/profiling_data_access/kibana.jsonc new file mode 100644 index 0000000000000..3f0254ba92647 --- /dev/null +++ b/x-pack/plugins/profiling_data_access/kibana.jsonc @@ -0,0 +1,14 @@ +{ + "type": "plugin", + "id": "@kbn/profiling-data-access-plugin", + "owner": "@elastic/profiling-ui", + "plugin": { + "id": "profilingDataAccess", + "server": true, + "browser": false, + "configPath": ["xpack", "profiling"], + "requiredPlugins": ["data"], + "optionalPlugins": [], + "requiredBundles": [] + } +} diff --git a/x-pack/plugins/profiling_data_access/server/index.ts b/x-pack/plugins/profiling_data_access/server/index.ts new file mode 100644 index 0000000000000..73700f3704157 --- /dev/null +++ b/x-pack/plugins/profiling_data_access/server/index.ts @@ -0,0 +1,34 @@ +/* + * 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, TypeOf } from '@kbn/config-schema'; + +import type { PluginInitializerContext } from '@kbn/core/server'; +import { ProfilingDataAccessPlugin } from './plugin'; +import type { ProfilingDataAccessPluginSetup, ProfilingDataAccessPluginStart } from './plugin'; + +const configSchema = schema.object({ + elasticsearch: schema.conditional( + schema.contextRef('dist'), + schema.literal(true), + schema.never(), + schema.maybe( + schema.object({ + hosts: schema.string(), + username: schema.string(), + password: schema.string(), + }) + ) + ), +}); + +export type ProfilingConfig = TypeOf; + +export { ProfilingDataAccessPluginSetup, ProfilingDataAccessPluginStart }; + +export function plugin(initializerContext: PluginInitializerContext) { + return new ProfilingDataAccessPlugin(initializerContext); +} diff --git a/x-pack/plugins/profiling_data_access/server/plugin.ts b/x-pack/plugins/profiling_data_access/server/plugin.ts new file mode 100644 index 0000000000000..23d72f39caee6 --- /dev/null +++ b/x-pack/plugins/profiling_data_access/server/plugin.ts @@ -0,0 +1,47 @@ +/* + * 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 type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/server'; +import { ProfilingConfig } from '.'; +import { registerServices } from './services/register_services'; +import { createProfilingEsClient } from './utils/create_profiling_es_client'; + +export type ProfilingDataAccessPluginSetup = ReturnType; +export type ProfilingDataAccessPluginStart = ReturnType; + +export class ProfilingDataAccessPlugin implements Plugin { + constructor(private readonly initializerContext: PluginInitializerContext) {} + public setup(core: CoreSetup) {} + + public start(core: CoreStart) { + const config = this.initializerContext.config.get(); + + const profilingSpecificEsClient = config.elasticsearch + ? core.elasticsearch.createClient('profiling', { + hosts: [config.elasticsearch.hosts], + username: config.elasticsearch.username, + password: config.elasticsearch.password, + }) + : undefined; + + const services = registerServices({ + createProfilingEsClient: ({ esClient: defaultEsClient, useDefaultAuth = false }) => { + const esClient = + profilingSpecificEsClient && !useDefaultAuth + ? profilingSpecificEsClient.asInternalUser + : defaultEsClient; + + return createProfilingEsClient({ esClient }); + }, + }); + + // called after all plugins are set up + return { + services, + }; + } +} diff --git a/x-pack/plugins/profiling_data_access/server/services/fetch_flamechart/index.ts b/x-pack/plugins/profiling_data_access/server/services/fetch_flamechart/index.ts new file mode 100644 index 0000000000000..dd72a012c6343 --- /dev/null +++ b/x-pack/plugins/profiling_data_access/server/services/fetch_flamechart/index.ts @@ -0,0 +1,55 @@ +/* + * 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 { ElasticsearchClient } from '@kbn/core/server'; +import { RegisterServicesParams } from '../register_services'; +import { withProfilingSpan } from '../../utils/with_profiling_span'; +import { searchStackTraces } from '../search_stack_traces'; +import { createCalleeTree } from '../../../common/callee'; +import { createBaseFlameGraph } from '../../../common/flamegraph'; + +interface FetchFlamechartParams { + esClient: ElasticsearchClient; + rangeFrom: number; + rangeTo: number; + kuery: string; +} + +export function createFetchFlamechart({ createProfilingEsClient }: RegisterServicesParams) { + return async ({ esClient, rangeFrom, rangeTo, kuery }: FetchFlamechartParams) => { + const profilingEsClient = createProfilingEsClient({ esClient }); + const targetSampleSize = 20000; // minimum number of samples to get statistically sound results + + const totalSeconds = rangeTo - rangeFrom; + + const { events, stackTraces, executables, stackFrames, totalFrames, samplingRate } = + await searchStackTraces({ + client: profilingEsClient, + rangeFrom, + rangeTo, + kuery, + sampleSize: targetSampleSize, + }); + + const flamegraph = await withProfilingSpan('create_flamegraph', async () => { + const tree = createCalleeTree( + events, + stackTraces, + stackFrames, + executables, + totalFrames, + samplingRate + ); + + const fg = createBaseFlameGraph(tree, samplingRate, totalSeconds); + + return fg; + }); + + return flamegraph; + }; +} diff --git a/x-pack/plugins/profiling_data_access/server/services/register_services.ts b/x-pack/plugins/profiling_data_access/server/services/register_services.ts new file mode 100644 index 0000000000000..fdac4528f6b5d --- /dev/null +++ b/x-pack/plugins/profiling_data_access/server/services/register_services.ts @@ -0,0 +1,21 @@ +/* + * 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 { ElasticsearchClient } from '@kbn/core/server'; +import { createFetchFlamechart } from './fetch_flamechart'; +import { ProfilingESClient } from '../utils/create_profiling_es_client'; + +export interface RegisterServicesParams { + createProfilingEsClient: (params: { + esClient: ElasticsearchClient; + useDefaultAuth?: boolean; + }) => ProfilingESClient; +} + +export function registerServices(params: RegisterServicesParams) { + return { fetchFlamechartData: createFetchFlamechart(params) }; +} diff --git a/x-pack/plugins/profiling_data_access/server/services/search_stack_traces/index.ts b/x-pack/plugins/profiling_data_access/server/services/search_stack_traces/index.ts new file mode 100644 index 0000000000000..1c9e185fe4c05 --- /dev/null +++ b/x-pack/plugins/profiling_data_access/server/services/search_stack_traces/index.ts @@ -0,0 +1,57 @@ +/* + * 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 type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; +import { decodeStackTraceResponse } from '../../../common/stack_traces'; +import { ProfilingESClient } from '../../utils/create_profiling_es_client'; + +export async function searchStackTraces({ + client, + sampleSize, + rangeFrom, + rangeTo, + kuery, +}: { + client: ProfilingESClient; + sampleSize: number; + rangeFrom: number; + rangeTo: number; + kuery: string; +}) { + const response = await client.profilingStacktraces({ + query: { + bool: { + filter: [ + ...kqlQuery(kuery), + { + range: { + ['@timestamp']: { + gte: String(rangeFrom), + lt: String(rangeTo), + format: 'epoch_second', + boost: 1.0, + }, + }, + }, + ], + }, + }, + sampleSize, + }); + + return decodeStackTraceResponse(response); +} + +function kqlQuery(kql?: string): estypes.QueryDslQueryContainer[] { + if (!kql) { + return []; + } + + const ast = fromKueryExpression(kql); + return [toElasticsearchQuery(ast)]; +} diff --git a/x-pack/plugins/profiling_data_access/server/utils/create_profiling_es_client.ts b/x-pack/plugins/profiling_data_access/server/utils/create_profiling_es_client.ts new file mode 100644 index 0000000000000..0c5b85f42c8fb --- /dev/null +++ b/x-pack/plugins/profiling_data_access/server/utils/create_profiling_es_client.ts @@ -0,0 +1,94 @@ +/* + * 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 { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { ElasticsearchClient } from '@kbn/core/server'; +import type { ESSearchRequest, InferSearchResponseOf } from '@kbn/es-types'; +import { unwrapEsResponse } from '@kbn/observability-plugin/server'; +import { ProfilingStatusResponse, StackTraceResponse } from '../../common/stack_traces'; +import { withProfilingSpan } from './with_profiling_span'; + +export interface ProfilingESClient { + search( + operationName: string, + searchRequest: TSearchRequest + ): Promise>; + profilingStacktraces({}: { + query: QueryDslQueryContainer; + sampleSize: number; + }): Promise; + profilingStatus(): Promise; + getEsClient(): ElasticsearchClient; +} + +export function createProfilingEsClient({ + esClient, +}: { + esClient: ElasticsearchClient; +}): ProfilingESClient { + return { + search( + operationName: string, + searchRequest: TSearchRequest + ): Promise> { + const controller = new AbortController(); + + const promise = withProfilingSpan(operationName, () => { + return esClient.search(searchRequest, { + signal: controller.signal, + meta: true, + }) as unknown as Promise<{ + body: InferSearchResponseOf; + }>; + }); + + return unwrapEsResponse(promise); + }, + profilingStacktraces({ query, sampleSize }) { + const controller = new AbortController(); + const promise = withProfilingSpan('_profiling/stacktraces', () => { + return esClient.transport.request( + { + method: 'POST', + path: encodeURI('/_profiling/stacktraces'), + body: { + query, + sample_size: sampleSize, + }, + }, + { + signal: controller.signal, + meta: true, + } + ); + }); + + return unwrapEsResponse(promise) as Promise; + }, + profilingStatus() { + const controller = new AbortController(); + + const promise = withProfilingSpan('_profiling/status', () => { + return esClient.transport.request( + { + method: 'GET', + path: encodeURI('/_profiling/status'), + }, + { + signal: controller.signal, + meta: true, + } + ); + }); + + return unwrapEsResponse(promise) as Promise; + }, + getEsClient() { + return esClient; + }, + }; +} diff --git a/x-pack/plugins/profiling_data_access/server/utils/with_profiling_span.ts b/x-pack/plugins/profiling_data_access/server/utils/with_profiling_span.ts new file mode 100644 index 0000000000000..6d366799780e7 --- /dev/null +++ b/x-pack/plugins/profiling_data_access/server/utils/with_profiling_span.ts @@ -0,0 +1,25 @@ +/* + * 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 { withSpan, SpanOptions, parseSpanOptions } from '@kbn/apm-utils'; + +export function withProfilingSpan( + optionsOrName: SpanOptions | string, + cb: () => Promise +): Promise { + const options = parseSpanOptions(optionsOrName); + + const optionsWithDefaults = { + ...(options.intercept ? {} : { type: 'plugin:profiling' }), + ...options, + labels: { + plugin: 'profiling', + ...options.labels, + }, + }; + + return withSpan(optionsWithDefaults, cb); +} diff --git a/x-pack/plugins/profiling_data_access/tsconfig.json b/x-pack/plugins/profiling_data_access/tsconfig.json new file mode 100644 index 0000000000000..a9aff086cbbe0 --- /dev/null +++ b/x-pack/plugins/profiling_data_access/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types" + }, + "include": [ + "server/**/*", + "jest.config.js" + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/config-schema", + "@kbn/core", + "@kbn/i18n", + "@kbn/core-saved-objects-api-server" + ] +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 52e67729e7891..e422e291068d9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5089,6 +5089,10 @@ version "0.0.0" uid "" +"@kbn/profiling-data-access-plugin@link:x-pack/plugins/profiling_data_access": + version "0.0.0" + uid "" + "@kbn/profiling-plugin@link:x-pack/plugins/profiling": version "0.0.0" uid "" From 4444778bb1180aa76c05230ae0f42f364df2bc99 Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Wed, 30 Aug 2023 10:13:02 +0100 Subject: [PATCH 02/12] moving dependencies to data acces plugin --- x-pack/plugins/profiling/common/base64.ts | 22 -- x-pack/plugins/profiling/common/callee.ts | 162 --------- .../plugins/profiling/common/elasticsearch.ts | 90 ----- x-pack/plugins/profiling/common/flamegraph.ts | 153 -------- .../plugins/profiling/common/frame_group.ts | 38 -- x-pack/plugins/profiling/common/functions.ts | 7 +- x-pack/plugins/profiling/common/hash.ts | 79 ----- x-pack/plugins/profiling/common/profiling.ts | 329 ------------------ .../profiling/common/run_length_encoding.ts | 2 +- .../plugins/profiling/common/stack_traces.ts | 189 ---------- x-pack/plugins/profiling/common/topn.ts | 4 +- .../missing_symbols_callout.tsx | 2 +- .../components/stack_frame_summary/index.tsx | 6 +- .../profiling/public/components/subchart.tsx | 2 +- .../public/components/topn_functions/utils.ts | 2 +- .../profiling/public/routing/index.tsx | 5 +- x-pack/plugins/profiling/public/services.ts | 6 +- .../utils/get_flamegraph_model/index.ts | 4 +- .../get_stack_traces_tabs.ts | 2 +- .../public/views/stack_traces_view/index.tsx | 5 +- .../public/views/stack_traces_view/utils.ts | 5 +- .../public/views/storage_explorer/summary.tsx | 5 +- .../plugins/profiling/server/routes/query.ts | 2 +- .../server/routes/search_stacktraces.ts | 3 +- .../plugins/profiling/server/routes/setup.ts | 9 + .../profiling/server/routes/stacktrace.ts | 8 +- .../get_host_breakdown_size_timeseries.ts | 2 +- .../storage_explorer/get_host_details.ts | 2 +- .../get_profiling_hosts_details_by_id.ts | 2 +- .../profiling/server/routes/topn.test.ts | 2 +- .../plugins/profiling/server/routes/topn.ts | 11 +- .../utils/create_profiling_es_client.ts | 5 +- .../common/__fixtures__/README.md | 0 .../common/__fixtures__/stacktraces.ts | 0 .../__fixtures__/stacktraces_3600s_5x.json | 0 .../stacktraces_604800s_625x.json | 0 .../__fixtures__/stacktraces_60s_1x.json | 0 .../__fixtures__/stacktraces_86400s_125x.json | 0 .../common/callee.test.ts | 0 .../common/flamegraph.test.ts | 0 .../common/frame_group.test.ts | 0 .../common/hash.test.ts | 0 .../common/profiling.test.ts | 0 .../common/stack_traces.test.ts | 0 44 files changed, 71 insertions(+), 1094 deletions(-) delete mode 100644 x-pack/plugins/profiling/common/base64.ts delete mode 100644 x-pack/plugins/profiling/common/callee.ts delete mode 100644 x-pack/plugins/profiling/common/elasticsearch.ts delete mode 100644 x-pack/plugins/profiling/common/flamegraph.ts delete mode 100644 x-pack/plugins/profiling/common/frame_group.ts delete mode 100644 x-pack/plugins/profiling/common/hash.ts delete mode 100644 x-pack/plugins/profiling/common/profiling.ts delete mode 100644 x-pack/plugins/profiling/common/stack_traces.ts rename x-pack/plugins/{profiling => profiling_data_access}/common/__fixtures__/README.md (100%) rename x-pack/plugins/{profiling => profiling_data_access}/common/__fixtures__/stacktraces.ts (100%) rename x-pack/plugins/{profiling => profiling_data_access}/common/__fixtures__/stacktraces_3600s_5x.json (100%) rename x-pack/plugins/{profiling => profiling_data_access}/common/__fixtures__/stacktraces_604800s_625x.json (100%) rename x-pack/plugins/{profiling => profiling_data_access}/common/__fixtures__/stacktraces_60s_1x.json (100%) rename x-pack/plugins/{profiling => profiling_data_access}/common/__fixtures__/stacktraces_86400s_125x.json (100%) rename x-pack/plugins/{profiling => profiling_data_access}/common/callee.test.ts (100%) rename x-pack/plugins/{profiling => profiling_data_access}/common/flamegraph.test.ts (100%) rename x-pack/plugins/{profiling => profiling_data_access}/common/frame_group.test.ts (100%) rename x-pack/plugins/{profiling => profiling_data_access}/common/hash.test.ts (100%) rename x-pack/plugins/{profiling => profiling_data_access}/common/profiling.test.ts (100%) rename x-pack/plugins/{profiling => profiling_data_access}/common/stack_traces.test.ts (100%) diff --git a/x-pack/plugins/profiling/common/base64.ts b/x-pack/plugins/profiling/common/base64.ts deleted file mode 100644 index 0d724c142271a..0000000000000 --- a/x-pack/plugins/profiling/common/base64.ts +++ /dev/null @@ -1,22 +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. - */ - -export const safeBase64Decoder = [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, - 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, - 25, 0, 0, 0, 0, 63, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, - 45, 46, 47, 48, 49, 50, 51, 0, 0, 0, 0, 0, -]; - -export const safeBase64Encoder = - 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234456789-_'; - -/* eslint no-bitwise: ["error", { "allow": ["&"] }] */ -export function charCodeAt(input: string, i: number): number { - return safeBase64Decoder[input.charCodeAt(i) & 0x7f]; -} diff --git a/x-pack/plugins/profiling/common/callee.ts b/x-pack/plugins/profiling/common/callee.ts deleted file mode 100644 index 68fa9170f44ec..0000000000000 --- a/x-pack/plugins/profiling/common/callee.ts +++ /dev/null @@ -1,162 +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 { createFrameGroupID, FrameGroupID } from './frame_group'; -import { - emptyExecutable, - emptyStackFrame, - emptyStackTrace, - Executable, - FileID, - StackFrame, - StackFrameID, - StackTrace, - StackTraceID, -} from './profiling'; - -type NodeID = number; - -export interface CalleeTree { - Size: number; - Edges: Array>; - - FileID: string[]; - FrameType: number[]; - Inline: boolean[]; - ExeFilename: string[]; - AddressOrLine: number[]; - FunctionName: string[]; - FunctionOffset: number[]; - SourceFilename: string[]; - SourceLine: number[]; - - CountInclusive: number[]; - CountExclusive: number[]; -} - -export function createCalleeTree( - events: Map, - stackTraces: Map, - stackFrames: Map, - executables: Map, - totalFrames: number, - samplingRate: number -): CalleeTree { - const tree: CalleeTree = { - Size: 1, - Edges: new Array(totalFrames), - FileID: new Array(totalFrames), - FrameType: new Array(totalFrames), - Inline: new Array(totalFrames), - ExeFilename: new Array(totalFrames), - AddressOrLine: new Array(totalFrames), - FunctionName: new Array(totalFrames), - FunctionOffset: new Array(totalFrames), - SourceFilename: new Array(totalFrames), - SourceLine: new Array(totalFrames), - - CountInclusive: new Array(totalFrames), - CountExclusive: new Array(totalFrames), - }; - - // The inverse of the sampling rate is the number with which to multiply the number of - // samples to get an estimate of the actual number of samples the backend received. - const scalingFactor = 1.0 / samplingRate; - tree.Edges[0] = new Map(); - - tree.FileID[0] = ''; - tree.FrameType[0] = 0; - tree.Inline[0] = false; - tree.ExeFilename[0] = ''; - tree.AddressOrLine[0] = 0; - tree.FunctionName[0] = ''; - tree.FunctionOffset[0] = 0; - tree.SourceFilename[0] = ''; - tree.SourceLine[0] = 0; - - tree.CountInclusive[0] = 0; - tree.CountExclusive[0] = 0; - - const sortedStackTraceIDs = new Array(); - for (const trace of stackTraces.keys()) { - sortedStackTraceIDs.push(trace); - } - sortedStackTraceIDs.sort((t1, t2) => { - return t1.localeCompare(t2); - }); - - // Walk through all traces. Increment the count of the root by the count of - // that trace. Walk "down" the trace (through the callees) and add the count - // of the trace to each callee. - - for (const stackTraceID of sortedStackTraceIDs) { - // The slice of frames is ordered so that the leaf function is at the - // highest index. - - // It is possible that we do not have a stacktrace for an event, - // e.g. when stopping the host agent or on network errors. - const stackTrace = stackTraces.get(stackTraceID) ?? emptyStackTrace; - const lenStackTrace = stackTrace.FrameIDs.length; - const samples = Math.floor((events.get(stackTraceID) ?? 0) * scalingFactor); - - let currentNode = 0; - - // Increment the count by the number of samples observed, multiplied with the inverse of the - // samplingrate (this essentially means scaling up the total samples). It would incur - tree.CountInclusive[currentNode] += samples; - tree.CountExclusive[currentNode] = 0; - - for (let i = 0; i < lenStackTrace; i++) { - const frameID = stackTrace.FrameIDs[i]; - const fileID = stackTrace.FileIDs[i]; - const addressOrLine = stackTrace.AddressOrLines[i]; - const frame = stackFrames.get(frameID) ?? emptyStackFrame; - const executable = executables.get(fileID) ?? emptyExecutable; - - const frameGroupID = createFrameGroupID( - fileID, - addressOrLine, - executable.FileName, - frame.FileName, - frame.FunctionName - ); - - let node = tree.Edges[currentNode].get(frameGroupID); - - if (node === undefined) { - node = tree.Size; - - tree.FileID[node] = fileID; - tree.FrameType[node] = stackTrace.Types[i]; - tree.ExeFilename[node] = executable.FileName; - tree.AddressOrLine[node] = addressOrLine; - tree.FunctionName[node] = frame.FunctionName; - tree.FunctionOffset[node] = frame.FunctionOffset; - tree.SourceLine[node] = frame.LineNumber; - tree.SourceFilename[node] = frame.FileName; - tree.Inline[node] = frame.Inline; - tree.CountInclusive[node] = samples; - tree.CountExclusive[node] = 0; - - tree.Edges[currentNode].set(frameGroupID, node); - tree.Edges[node] = new Map(); - - tree.Size++; - } else { - tree.CountInclusive[node] += samples; - } - - if (i === lenStackTrace - 1) { - // Leaf frame: sum up counts for exclusive CPU. - tree.CountExclusive[node] += samples; - } - currentNode = node; - } - } - - return tree; -} diff --git a/x-pack/plugins/profiling/common/elasticsearch.ts b/x-pack/plugins/profiling/common/elasticsearch.ts deleted file mode 100644 index a47e4c018d581..0000000000000 --- a/x-pack/plugins/profiling/common/elasticsearch.ts +++ /dev/null @@ -1,90 +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 { UnionToIntersection, ValuesType } from 'utility-types'; - -export enum ProfilingESField { - Timestamp = '@timestamp', - ContainerName = 'container.name', - ProcessThreadName = 'process.thread.name', - StacktraceCount = 'Stacktrace.count', - HostID = 'host.id', - HostName = 'host.name', - HostIP = 'host.ip', - OrchestratorResourceName = 'orchestrator.resource.name', - ServiceName = 'service.name', - StacktraceID = 'Stacktrace.id', - StacktraceFrameIDs = 'Stacktrace.frame.ids', - StacktraceFrameTypes = 'Stacktrace.frame.types', - StackframeFileName = 'Stackframe.file.name', - StackframeFunctionName = 'Stackframe.function.name', - StackframeLineNumber = 'Stackframe.line.number', - StackframeFunctionOffset = 'Stackframe.function.offset', - ExecutableBuildID = 'Executable.build.id', - ExecutableFileName = 'Executable.file.name', -} - -type DedotKey< - TKey extends string | number | symbol, - TValue -> = TKey extends `${infer THead}.${infer TTail}` - ? { - [key in THead]: DedotKey; - } - : { [key in TKey]: TValue }; - -export type DedotObject> = UnionToIntersection< - ValuesType<{ - [TKey in keyof TObject]: DedotKey; - }> ->; - -export type FlattenObject< - TObject extends Record, - TPrefix extends string = '' -> = UnionToIntersection< - ValuesType<{ - [TKey in keyof TObject & string]: TObject[TKey] extends Record - ? FlattenObject - : { [key in `${TPrefix}${TKey}`]: TObject[TKey] }; - }> ->; - -type FlattenedKeysOf> = keyof FlattenObject; - -export type PickFlattened< - TObject extends Record, - TPickKey extends FlattenedKeysOf -> = DedotObject, TPickKey>>; - -export type ProfilingESEvent = DedotObject<{ - [ProfilingESField.Timestamp]: string; - [ProfilingESField.ContainerName]: string; - [ProfilingESField.ProcessThreadName]: string; - [ProfilingESField.StacktraceCount]: number; - [ProfilingESField.HostID]: string; - [ProfilingESField.OrchestratorResourceName]: string; - [ProfilingESField.ServiceName]: string; - [ProfilingESField.StacktraceID]: string; -}>; - -export type ProfilingStackTrace = DedotObject<{ - [ProfilingESField.StacktraceFrameIDs]: string; - [ProfilingESField.StacktraceFrameTypes]: string; -}>; - -export type ProfilingStackFrame = DedotObject<{ - [ProfilingESField.StackframeFileName]: string; - [ProfilingESField.StackframeFunctionName]: string; - [ProfilingESField.StackframeLineNumber]: number; - [ProfilingESField.StackframeFunctionOffset]: number; -}>; - -export type ProfilingExecutable = DedotObject<{ - [ProfilingESField.ExecutableBuildID]: string; - [ProfilingESField.ExecutableFileName]: string; -}>; diff --git a/x-pack/plugins/profiling/common/flamegraph.ts b/x-pack/plugins/profiling/common/flamegraph.ts deleted file mode 100644 index 16fb8c1a396c5..0000000000000 --- a/x-pack/plugins/profiling/common/flamegraph.ts +++ /dev/null @@ -1,153 +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 { CalleeTree } from './callee'; -import { createFrameGroupID } from './frame_group'; -import { fnv1a64 } from './hash'; -import { createStackFrameMetadata, getCalleeLabel } from './profiling'; - -export interface BaseFlameGraph { - Size: number; - Edges: number[][]; - - FileID: string[]; - FrameType: number[]; - Inline: boolean[]; - ExeFilename: string[]; - AddressOrLine: number[]; - FunctionName: string[]; - FunctionOffset: number[]; - SourceFilename: string[]; - SourceLine: number[]; - - CountInclusive: number[]; - CountExclusive: number[]; - - TotalSeconds: number; - SamplingRate: number; -} - -// createBaseFlameGraph encapsulates the tree representation into a serialized form. -export function createBaseFlameGraph( - tree: CalleeTree, - samplingRate: number, - totalSeconds: number -): BaseFlameGraph { - const graph: BaseFlameGraph = { - Size: tree.Size, - SamplingRate: samplingRate, - Edges: new Array(tree.Size), - - FileID: tree.FileID.slice(0, tree.Size), - FrameType: tree.FrameType.slice(0, tree.Size), - Inline: tree.Inline.slice(0, tree.Size), - ExeFilename: tree.ExeFilename.slice(0, tree.Size), - AddressOrLine: tree.AddressOrLine.slice(0, tree.Size), - FunctionName: tree.FunctionName.slice(0, tree.Size), - FunctionOffset: tree.FunctionOffset.slice(0, tree.Size), - SourceFilename: tree.SourceFilename.slice(0, tree.Size), - SourceLine: tree.SourceLine.slice(0, tree.Size), - - CountInclusive: tree.CountInclusive.slice(0, tree.Size), - CountExclusive: tree.CountExclusive.slice(0, tree.Size), - - TotalSeconds: totalSeconds, - }; - - for (let i = 0; i < tree.Size; i++) { - let j = 0; - const nodes = new Array(tree.Edges[i].size); - for (const [, n] of tree.Edges[i]) { - nodes[j] = n; - j++; - } - graph.Edges[i] = nodes; - } - - return graph; -} - -export interface ElasticFlameGraph extends BaseFlameGraph { - ID: string[]; - Label: string[]; -} - -// createFlameGraph combines the base flamegraph with CPU-intensive values. -// This allows us to create a flamegraph in two steps (e.g. first on the server -// and finally in the browser). -export function createFlameGraph(base: BaseFlameGraph): ElasticFlameGraph { - const graph: ElasticFlameGraph = { - Size: base.Size, - SamplingRate: base.SamplingRate, - Edges: base.Edges, - - FileID: base.FileID, - FrameType: base.FrameType, - Inline: base.Inline, - ExeFilename: base.ExeFilename, - AddressOrLine: base.AddressOrLine, - FunctionName: base.FunctionName, - FunctionOffset: base.FunctionOffset, - SourceFilename: base.SourceFilename, - SourceLine: base.SourceLine, - - CountInclusive: base.CountInclusive, - CountExclusive: base.CountExclusive, - - ID: new Array(base.Size), - Label: new Array(base.Size), - - TotalSeconds: base.TotalSeconds, - }; - - const rootFrameGroupID = createFrameGroupID( - graph.FileID[0], - graph.AddressOrLine[0], - graph.ExeFilename[0], - graph.SourceFilename[0], - graph.FunctionName[0] - ); - - graph.ID[0] = fnv1a64(new TextEncoder().encode(rootFrameGroupID)); - - const queue = [0]; - while (queue.length > 0) { - const parent = queue.pop()!; - for (const child of graph.Edges[parent]) { - const frameGroupID = createFrameGroupID( - graph.FileID[child], - graph.AddressOrLine[child], - graph.ExeFilename[child], - graph.SourceFilename[child], - graph.FunctionName[child] - ); - const bytes = new TextEncoder().encode(graph.ID[parent] + frameGroupID); - graph.ID[child] = fnv1a64(bytes); - queue.push(child); - } - } - - graph.Label[0] = 'root: Represents 100% of CPU time.'; - - for (let i = 1; i < graph.Size; i++) { - const metadata = createStackFrameMetadata({ - FileID: graph.FileID[i], - FrameType: graph.FrameType[i], - Inline: graph.Inline[i], - ExeFileName: graph.ExeFilename[i], - AddressOrLine: graph.AddressOrLine[i], - FunctionName: graph.FunctionName[i], - FunctionOffset: graph.FunctionOffset[i], - SourceFilename: graph.SourceFilename[i], - SourceLine: graph.SourceLine[i], - SamplingRate: graph.SamplingRate, - }); - graph.Label[i] = getCalleeLabel(metadata); - } - - return graph; -} diff --git a/x-pack/plugins/profiling/common/frame_group.ts b/x-pack/plugins/profiling/common/frame_group.ts deleted file mode 100644 index 6881b14ed98fe..0000000000000 --- a/x-pack/plugins/profiling/common/frame_group.ts +++ /dev/null @@ -1,38 +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 { takeRight } from 'lodash'; -import { StackFrameMetadata } from './profiling'; - -export type FrameGroupID = string; - -function stripLeadingSubdirs(sourceFileName: string) { - return takeRight(sourceFileName.split('/'), 2).join('/'); -} - -// createFrameGroupID is the "standard" way of grouping frames, by commonly -// shared group identifiers. -// -// For ELF-symbolized frames, group by FunctionName, ExeFileName and FileID. -// For non-symbolized frames, group by FileID and AddressOrLine. -// otherwise group by ExeFileName, SourceFilename and FunctionName. -export function createFrameGroupID( - fileID: StackFrameMetadata['FileID'], - addressOrLine: StackFrameMetadata['AddressOrLine'], - exeFilename: StackFrameMetadata['ExeFileName'], - sourceFilename: StackFrameMetadata['SourceFilename'], - functionName: StackFrameMetadata['FunctionName'] -): FrameGroupID { - if (functionName === '') { - return `empty;${fileID};${addressOrLine}`; - } - - if (sourceFilename === '') { - return `elf;${exeFilename};${functionName}`; - } - - return `full;${exeFilename};${functionName};${stripLeadingSubdirs(sourceFilename || '')}`; -} diff --git a/x-pack/plugins/profiling/common/functions.ts b/x-pack/plugins/profiling/common/functions.ts index 304c56b81e906..dac08c97abbf7 100644 --- a/x-pack/plugins/profiling/common/functions.ts +++ b/x-pack/plugins/profiling/common/functions.ts @@ -6,7 +6,10 @@ */ import * as t from 'io-ts'; import { sumBy } from 'lodash'; -import { createFrameGroupID, FrameGroupID } from './frame_group'; +import { + createFrameGroupID, + FrameGroupID, +} from '@kbn/profiling-data-access-plugin/common/frame_group'; import { createStackFrameMetadata, emptyExecutable, @@ -19,7 +22,7 @@ import { StackFrameMetadata, StackTrace, StackTraceID, -} from './profiling'; +} from '@kbn/profiling-data-access-plugin/common/profiling'; interface TopNFunctionAndFrameGroup { Frame: StackFrameMetadata; diff --git a/x-pack/plugins/profiling/common/hash.ts b/x-pack/plugins/profiling/common/hash.ts deleted file mode 100644 index 3eab4bde871e0..0000000000000 --- a/x-pack/plugins/profiling/common/hash.ts +++ /dev/null @@ -1,79 +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. - */ - -// prettier-ignore -const lowerHex = [ - '00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '0a', '0b', '0c', '0d', '0e', '0f', - '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '1a', '1b', '1c', '1d', '1e', '1f', - '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '2a', '2b', '2c', '2d', '2e', '2f', - '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '3a', '3b', '3c', '3d', '3e', '3f', - '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '4a', '4b', '4c', '4d', '4e', '4f', - '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '5a', '5b', '5c', '5d', '5e', '5f', - '60', '61', '62', '63', '64', '65', '66', '67', '68', '69', '6a', '6b', '6c', '6d', '6e', '6f', - '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '7a', '7b', '7c', '7d', '7e', '7f', - '80', '81', '82', '83', '84', '85', '86', '87', '88', '89', '8a', '8b', '8c', '8d', '8e', '8f', - '90', '91', '92', '93', '94', '95', '96', '97', '98', '99', '9a', '9b', '9c', '9d', '9e', '9f', - 'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9', 'aa', 'ab', 'ac', 'ad', 'ae', 'af', - 'b0', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'b7', 'b8', 'b9', 'ba', 'bb', 'bc', 'bd', 'be', 'bf', - 'c0', 'c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8', 'c9', 'ca', 'cb', 'cc', 'cd', 'ce', 'cf', - 'd0', 'd1', 'd2', 'd3', 'd4', 'd5', 'd6', 'd7', 'd8', 'd9', 'da', 'db', 'dc', 'dd', 'de', 'df', - 'e0', 'e1', 'e2', 'e3', 'e4', 'e5', 'e6', 'e7', 'e8', 'e9', 'ea', 'eb', 'ec', 'ed', 'ee', 'ef', - 'f0', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'fa', 'fb', 'fc', 'fd', 'fe', 'ff', -]; - -// fnv1a64 computes a 64-bit hash of a byte array using the FNV-1a hash function [1]. -// -// Due to the lack of a native uint64 in JavaScript, we operate on 64-bit values using an array -// of 4 uint16s instead. This method follows Knuth's Algorithm M in section 4.3.1 [2] using a -// modified multiword multiplication implementation described in [3]. The modifications include: -// -// * rewrite default algorithm for the special case m = n = 4 -// * unroll loops -// * simplify expressions -// * create pre-computed lookup table for serialization to hexadecimal -// -// 1. https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function -// 2. Knuth, Donald E. The Art of Computer Programming, Volume 2, Third Edition: Seminumerical -// Algorithms. Addison-Wesley, 1998. -// 3. Warren, Henry S. Hacker's Delight. Upper Saddle River, NJ: Addison-Wesley, 2013. - -/* eslint no-bitwise: ["error", { "allow": ["^=", ">>", "&"] }] */ -export function fnv1a64(bytes: Uint8Array): string { - const n = bytes.length; - let [h0, h1, h2, h3] = [0x2325, 0x8422, 0x9ce4, 0xcbf2]; - let [t0, t1, t2, t3] = [0, 0, 0, 0]; - - for (let i = 0; i < n; i++) { - h0 ^= bytes[i]; - - t0 = h0 * 0x01b3; - t1 = h1 * 0x01b3; - t2 = h2 * 0x01b3; - t3 = h3 * 0x01b3; - - t1 += t0 >> 16; - t2 += t1 >> 16; - t2 += h0 * 0x0100; - t3 += h1 * 0x0100; - - h0 = t0 & 0xffff; - h1 = t1 & 0xffff; - h2 = t2 & 0xffff; - h3 = (t3 + (t2 >> 16)) & 0xffff; - } - - return ( - lowerHex[h3 >> 8] + - lowerHex[h3 & 0xff] + - lowerHex[h2 >> 8] + - lowerHex[h2 & 0xff] + - lowerHex[h1 >> 8] + - lowerHex[h1 & 0xff] + - lowerHex[h0 >> 8] + - lowerHex[h0 & 0xff] - ); -} diff --git a/x-pack/plugins/profiling/common/profiling.ts b/x-pack/plugins/profiling/common/profiling.ts deleted file mode 100644 index 22480a26f8ab2..0000000000000 --- a/x-pack/plugins/profiling/common/profiling.ts +++ /dev/null @@ -1,329 +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 { charCodeAt, safeBase64Encoder } from './base64'; - -export type StackTraceID = string; -export type StackFrameID = string; -export type FileID = string; - -export function createStackFrameID(fileID: FileID, addressOrLine: number): StackFrameID { - const buf = Buffer.alloc(24); - Buffer.from(fileID, 'base64url').copy(buf); - buf.writeBigUInt64BE(BigInt(addressOrLine), 16); - return buf.toString('base64url'); -} - -/* eslint no-bitwise: ["error", { "allow": ["&"] }] */ -export function getFileIDFromStackFrameID(frameID: StackFrameID): FileID { - return frameID.slice(0, 21) + safeBase64Encoder[frameID.charCodeAt(21) & 0x30]; -} - -/* eslint no-bitwise: ["error", { "allow": ["<<=", "&"] }] */ -export function getAddressFromStackFrameID(frameID: StackFrameID): number { - let address = charCodeAt(frameID, 21) & 0xf; - address <<= 6; - address += charCodeAt(frameID, 22); - address <<= 6; - address += charCodeAt(frameID, 23); - address <<= 6; - address += charCodeAt(frameID, 24); - address <<= 6; - address += charCodeAt(frameID, 25); - address <<= 6; - address += charCodeAt(frameID, 26); - address <<= 6; - address += charCodeAt(frameID, 27); - address <<= 6; - address += charCodeAt(frameID, 28); - address <<= 6; - address += charCodeAt(frameID, 29); - address <<= 6; - address += charCodeAt(frameID, 30); - address <<= 6; - address += charCodeAt(frameID, 31); - return address; -} - -export enum FrameType { - Unsymbolized = 0, - Python, - PHP, - Native, - Kernel, - JVM, - Ruby, - Perl, - JavaScript, - PHPJIT, -} - -const frameTypeDescriptions = { - [FrameType.Unsymbolized]: '', - [FrameType.Python]: 'Python', - [FrameType.PHP]: 'PHP', - [FrameType.Native]: 'Native', - [FrameType.Kernel]: 'Kernel', - [FrameType.JVM]: 'JVM/Hotspot', - [FrameType.Ruby]: 'Ruby', - [FrameType.Perl]: 'Perl', - [FrameType.JavaScript]: 'JavaScript', - [FrameType.PHPJIT]: 'PHP JIT', -}; - -export function describeFrameType(ft: FrameType): string { - return frameTypeDescriptions[ft]; -} - -export interface StackTraceEvent { - StackTraceID: StackTraceID; - Count: number; -} - -export interface StackTrace { - FrameIDs: string[]; - FileIDs: string[]; - AddressOrLines: number[]; - Types: number[]; -} - -export const emptyStackTrace: StackTrace = { - FrameIDs: [], - FileIDs: [], - AddressOrLines: [], - Types: [], -}; - -export interface StackFrame { - FileName: string; - FunctionName: string; - FunctionOffset: number; - LineNumber: number; - Inline: boolean; -} - -export const emptyStackFrame: StackFrame = { - FileName: '', - FunctionName: '', - FunctionOffset: 0, - LineNumber: 0, - Inline: false, -}; - -export interface Executable { - FileName: string; -} - -export const emptyExecutable: Executable = { - FileName: '', -}; - -export interface StackFrameMetadata { - // StackTrace.FrameID - FrameID: string; - // StackTrace.FileID - FileID: FileID; - // StackTrace.Type - FrameType: FrameType; - // StackFrame.Inline - Inline: boolean; - - // StackTrace.AddressOrLine - AddressOrLine: number; - // StackFrame.FunctionName - FunctionName: string; - // StackFrame.FunctionOffset - FunctionOffset: number; - // should this be StackFrame.SourceID? - SourceID: FileID; - // StackFrame.Filename - SourceFilename: string; - // StackFrame.LineNumber - SourceLine: number; - // auto-generated - see createStackFrameMetadata - FunctionSourceLine: number; - - // Executable.FileName - ExeFileName: string; - - // unused atm due to lack of symbolization metadata - CommitHash: string; - // unused atm due to lack of symbolization metadata - SourceCodeURL: string; - // unused atm due to lack of symbolization metadata - SourcePackageHash: string; - // unused atm due to lack of symbolization metadata - SourcePackageURL: string; - // unused atm due to lack of symbolization metadata - - SamplingRate: number; -} - -export function createStackFrameMetadata( - options: Partial = {} -): StackFrameMetadata { - const metadata = {} as StackFrameMetadata; - - metadata.FrameID = options.FrameID ?? ''; - metadata.FileID = options.FileID ?? ''; - metadata.FrameType = options.FrameType ?? 0; - metadata.Inline = options.Inline ?? false; - metadata.AddressOrLine = options.AddressOrLine ?? 0; - metadata.FunctionName = options.FunctionName ?? ''; - metadata.FunctionOffset = options.FunctionOffset ?? 0; - metadata.SourceID = options.SourceID ?? ''; - metadata.SourceLine = options.SourceLine ?? 0; - metadata.ExeFileName = options.ExeFileName ?? ''; - metadata.CommitHash = options.CommitHash ?? ''; - metadata.SourceCodeURL = options.SourceCodeURL ?? ''; - metadata.SourceFilename = options.SourceFilename ?? ''; - metadata.SourcePackageHash = options.SourcePackageHash ?? ''; - metadata.SourcePackageURL = options.SourcePackageURL ?? ''; - metadata.SamplingRate = options.SamplingRate ?? 1.0; - - // Unknown/invalid offsets are currently set to 0. - // - // In this case we leave FunctionSourceLine=0 as a flag for the UI that the - // FunctionSourceLine should not be displayed. - // - // As FunctionOffset=0 could also be a legit value, this work-around needs - // a real fix. The idea for after GA is to change FunctionOffset=-1 to - // indicate unknown/invalid. - if (metadata.FunctionOffset > 0) { - metadata.FunctionSourceLine = metadata.SourceLine - metadata.FunctionOffset; - } else { - metadata.FunctionSourceLine = 0; - } - - return metadata; -} - -function checkIfStringHasParentheses(s: string) { - return /\(|\)/.test(s); -} - -function getFunctionName(metadata: StackFrameMetadata) { - return metadata.FunctionName !== '' && !checkIfStringHasParentheses(metadata.FunctionName) - ? `${metadata.FunctionName}()` - : metadata.FunctionName; -} - -function getExeFileName(metadata: StackFrameMetadata) { - if (metadata?.ExeFileName === undefined) { - return ''; - } - if (metadata.ExeFileName !== '') { - return metadata.ExeFileName; - } - return describeFrameType(metadata.FrameType); -} - -export function getCalleeLabel(metadata: StackFrameMetadata) { - if (metadata.FunctionName !== '') { - const sourceFilename = metadata.SourceFilename; - const sourceURL = sourceFilename ? sourceFilename.split('/').pop() : ''; - return `${getExeFileName(metadata)}: ${getFunctionName(metadata)} in ${sourceURL}#${ - metadata.SourceLine - }`; - } - return getExeFileName(metadata); -} - -export function getCalleeFunction(frame: StackFrameMetadata): string { - // In the best case scenario, we have the file names, source lines, - // and function names. However we need to deal with missing function or - // executable info. - const exeDisplayName = frame.ExeFileName ? frame.ExeFileName : describeFrameType(frame.FrameType); - - // When there is no function name, only use the executable name - return frame.FunctionName ? exeDisplayName + ': ' + frame.FunctionName : exeDisplayName; -} -export enum FrameSymbolStatus { - PARTIALLY_SYMBOLYZED = 'PARTIALLY_SYMBOLYZED', - NOT_SYMBOLIZED = 'NOT_SYMBOLIZED', - SYMBOLIZED = 'SYMBOLIZED', -} -export function getFrameSymbolStatus({ - sourceFilename, - sourceLine, - exeFileName, -}: { - sourceFilename: string; - sourceLine: number; - exeFileName?: string; -}) { - if (sourceFilename === '' && sourceLine === 0) { - if (exeFileName) { - return FrameSymbolStatus.PARTIALLY_SYMBOLYZED; - } - - return FrameSymbolStatus.NOT_SYMBOLIZED; - } - - return FrameSymbolStatus.SYMBOLIZED; -} - -const nativeLanguages = [FrameType.Native, FrameType.Kernel]; -export function getLanguageType({ frameType }: { frameType: FrameType }) { - return nativeLanguages.includes(frameType) ? 'NATIVE' : 'INTERPRETED'; -} - -export function getCalleeSource(frame: StackFrameMetadata): string { - const frameSymbolStatus = getFrameSymbolStatus({ - sourceFilename: frame.SourceFilename, - sourceLine: frame.SourceLine, - exeFileName: frame.ExeFileName, - }); - - switch (frameSymbolStatus) { - case FrameSymbolStatus.NOT_SYMBOLIZED: { - // If we don't have the executable filename, display - return ''; - } - case FrameSymbolStatus.PARTIALLY_SYMBOLYZED: { - // If no source line or filename available, display the executable offset - return frame.ExeFileName + '+0x' + frame.AddressOrLine.toString(16); - } - case FrameSymbolStatus.SYMBOLIZED: { - return frame.SourceFilename + (frame.SourceLine !== 0 ? `#${frame.SourceLine}` : ''); - } - } -} - -export function groupStackFrameMetadataByStackTrace( - stackTraces: Map, - stackFrames: Map, - executables: Map -): Record { - const stackTraceMap: Record = {}; - for (const [stackTraceID, trace] of stackTraces) { - const numFramesPerTrace = trace.FrameIDs.length; - const frameMetadata = new Array(numFramesPerTrace); - for (let i = 0; i < numFramesPerTrace; i++) { - const frameID = trace.FrameIDs[i]; - const fileID = trace.FileIDs[i]; - const addressOrLine = trace.AddressOrLines[i]; - const frame = stackFrames.get(frameID) ?? emptyStackFrame; - const executable = executables.get(fileID) ?? emptyExecutable; - - frameMetadata[i] = createStackFrameMetadata({ - FrameID: frameID, - FileID: fileID, - AddressOrLine: addressOrLine, - FrameType: trace.Types[i], - Inline: frame.Inline, - FunctionName: frame.FunctionName, - FunctionOffset: frame.FunctionOffset, - SourceLine: frame.LineNumber, - SourceFilename: frame.FileName, - ExeFileName: executable.FileName, - }); - } - stackTraceMap[stackTraceID] = frameMetadata; - } - return stackTraceMap; -} diff --git a/x-pack/plugins/profiling/common/run_length_encoding.ts b/x-pack/plugins/profiling/common/run_length_encoding.ts index 813c46eb09d91..80066ae70d72a 100644 --- a/x-pack/plugins/profiling/common/run_length_encoding.ts +++ b/x-pack/plugins/profiling/common/run_length_encoding.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { charCodeAt } from './base64'; +import { charCodeAt } from '@kbn/profiling-data-access-plugin/common/base64'; // runLengthEncode run-length encodes the input array. // diff --git a/x-pack/plugins/profiling/common/stack_traces.ts b/x-pack/plugins/profiling/common/stack_traces.ts deleted file mode 100644 index 97a18d09ed389..0000000000000 --- a/x-pack/plugins/profiling/common/stack_traces.ts +++ /dev/null @@ -1,189 +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 { ProfilingESField } from './elasticsearch'; -import { - Executable, - FileID, - StackFrame, - StackFrameID, - StackTrace, - StackTraceID, -} from './profiling'; - -export interface ProfilingStatusResponse { - profiling: { - enabled: boolean; - }; - resource_management: { - enabled: boolean; - }; - resources: { - created: boolean; - pre_8_9_1_data: boolean; - }; -} - -interface ProfilingEvents { - [key: string]: number; -} - -export interface ProfilingStackTrace { - ['file_ids']: string[]; - ['frame_ids']: string[]; - ['address_or_lines']: number[]; - ['type_ids']: number[]; -} - -interface ProfilingStackTraces { - [key: string]: ProfilingStackTrace; -} - -export interface ProfilingStackFrame { - ['file_name']: string[]; - ['function_name']: string[]; - ['function_offset']: number[]; - ['line_number']: number[]; -} - -interface ProfilingStackFrames { - [key: string]: ProfilingStackFrame; -} - -interface ProfilingExecutables { - [key: string]: string; -} - -export interface StackTraceResponse { - ['stack_trace_events']?: ProfilingEvents; - ['stack_traces']?: ProfilingStackTraces; - ['stack_frames']?: ProfilingStackFrames; - ['executables']?: ProfilingExecutables; - ['total_frames']: number; - ['sampling_rate']: number; -} - -export interface DecodedStackTraceResponse { - events: Map; - stackTraces: Map; - stackFrames: Map; - executables: Map; - totalFrames: number; - samplingRate: number; -} - -export const makeFrameID = (frameID: string, n: number): string => { - return n === 0 ? frameID : frameID + ';' + n.toString(); -}; - -// createInlineTrace builds a new StackTrace with inline frames. -const createInlineTrace = ( - trace: ProfilingStackTrace, - frames: Map -): StackTrace => { - // The arrays need to be extended with the inline frame information. - const frameIDs: string[] = []; - const fileIDs: string[] = []; - const addressOrLines: number[] = []; - const typeIDs: number[] = []; - - for (let i = 0; i < trace.frame_ids.length; i++) { - const frameID = trace.frame_ids[i]; - frameIDs.push(frameID); - fileIDs.push(trace.file_ids[i]); - addressOrLines.push(trace.address_or_lines[i]); - typeIDs.push(trace.type_ids[i]); - - for (let j = 1; ; j++) { - const inlineID = makeFrameID(frameID, j); - const frame = frames.get(inlineID); - if (!frame) { - break; - } - frameIDs.push(inlineID); - fileIDs.push(trace.file_ids[i]); - addressOrLines.push(trace.address_or_lines[i]); - typeIDs.push(trace.type_ids[i]); - } - } - - return { - FrameIDs: frameIDs, - FileIDs: fileIDs, - AddressOrLines: addressOrLines, - Types: typeIDs, - } as StackTrace; -}; - -export function decodeStackTraceResponse(response: StackTraceResponse): DecodedStackTraceResponse { - const stackTraceEvents: Map = new Map(); - for (const [key, value] of Object.entries(response.stack_trace_events ?? {})) { - stackTraceEvents.set(key, value); - } - - const stackFrames: Map = new Map(); - for (const [frameID, frame] of Object.entries(response.stack_frames ?? {})) { - // Each field in a stackframe is represented by an array. This is - // necessary to support inline frames. - // - // We store the inlined frames with a modified (and unique) ID. - // We can do so since we don't display the frame IDs. - for (let i = 0; i < frame.function_name.length; i++) { - stackFrames.set(makeFrameID(frameID, i), { - FileName: frame.file_name[i], - FunctionName: frame.function_name[i], - FunctionOffset: frame.function_offset[i], - LineNumber: frame.line_number[i], - Inline: i > 0, - } as StackFrame); - } - } - - const stackTraces: Map = new Map(); - for (const [traceID, trace] of Object.entries(response.stack_traces ?? {})) { - stackTraces.set(traceID, createInlineTrace(trace, stackFrames)); - } - - const executables: Map = new Map(); - for (const [key, value] of Object.entries(response.executables ?? {})) { - executables.set(key, { - FileName: value, - } as Executable); - } - - return { - events: stackTraceEvents, - stackTraces, - stackFrames, - executables, - totalFrames: response.total_frames, - samplingRate: response.sampling_rate, - }; -} - -export enum StackTracesDisplayOption { - StackTraces = 'stackTraces', - Percentage = 'percentage', -} - -export enum TopNType { - Containers = 'containers', - Deployments = 'deployments', - Threads = 'threads', - Hosts = 'hosts', - Traces = 'traces', -} - -export function getFieldNameForTopNType(type: TopNType): string { - return { - [TopNType.Containers]: ProfilingESField.ContainerName, - [TopNType.Deployments]: ProfilingESField.OrchestratorResourceName, - [TopNType.Threads]: ProfilingESField.ProcessThreadName, - [TopNType.Hosts]: ProfilingESField.HostID, - [TopNType.Traces]: ProfilingESField.StacktraceID, - }[type]; -} diff --git a/x-pack/plugins/profiling/common/topn.ts b/x-pack/plugins/profiling/common/topn.ts index 41d820729b48f..cd58e35c6442f 100644 --- a/x-pack/plugins/profiling/common/topn.ts +++ b/x-pack/plugins/profiling/common/topn.ts @@ -9,9 +9,9 @@ import { euiPaletteColorBlind } from '@elastic/eui'; import { InferSearchResponseOf } from '@kbn/es-types'; import { i18n } from '@kbn/i18n'; import { orderBy } from 'lodash'; -import { ProfilingESField } from './elasticsearch'; +import { ProfilingESField } from '@kbn/profiling-data-access-plugin/common/elasticsearch'; +import { StackFrameMetadata } from '@kbn/profiling-data-access-plugin/common/profiling'; import { createUniformBucketsForTimeRange } from './histogram'; -import { StackFrameMetadata } from './profiling'; export const OTHER_BUCKET_LABEL = i18n.translate('xpack.profiling.topn.otherBucketLabel', { defaultMessage: 'Other', diff --git a/x-pack/plugins/profiling/public/components/frame_information_window/missing_symbols_callout.tsx b/x-pack/plugins/profiling/public/components/frame_information_window/missing_symbols_callout.tsx index ff86ba5628c27..1f9d40d23a648 100644 --- a/x-pack/plugins/profiling/public/components/frame_information_window/missing_symbols_callout.tsx +++ b/x-pack/plugins/profiling/public/components/frame_information_window/missing_symbols_callout.tsx @@ -9,7 +9,7 @@ import { EuiButton, EuiCallOut, EuiLink } from '@elastic/eui'; import React from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; -import { FrameType, getLanguageType } from '../../../common/profiling'; +import { FrameType, getLanguageType } from '@kbn/profiling-data-access-plugin/common/profiling'; import { PROFILING_FEEDBACK_LINK } from '../profiling_app_page_template'; import { useProfilingDependencies } from '../contexts/profiling_dependencies/use_profiling_dependencies'; import { useProfilingRouter } from '../../hooks/use_profiling_router'; diff --git a/x-pack/plugins/profiling/public/components/stack_frame_summary/index.tsx b/x-pack/plugins/profiling/public/components/stack_frame_summary/index.tsx index a92934df79009..5533df8479d4e 100644 --- a/x-pack/plugins/profiling/public/components/stack_frame_summary/index.tsx +++ b/x-pack/plugins/profiling/public/components/stack_frame_summary/index.tsx @@ -6,7 +6,11 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiText } from '@elastic/eui'; import React from 'react'; -import { getCalleeFunction, getCalleeSource, StackFrameMetadata } from '../../../common/profiling'; +import { + getCalleeFunction, + getCalleeSource, + StackFrameMetadata, +} from '@kbn/profiling-data-access-plugin/common/profiling'; interface Props { frame: StackFrameMetadata; diff --git a/x-pack/plugins/profiling/public/components/subchart.tsx b/x-pack/plugins/profiling/public/components/subchart.tsx index 72c869e85c3a3..ee388b851d0ed 100644 --- a/x-pack/plugins/profiling/public/components/subchart.tsx +++ b/x-pack/plugins/profiling/public/components/subchart.tsx @@ -32,7 +32,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { StackFrameMetadata } from '../../common/profiling'; +import { StackFrameMetadata } from '@kbn/profiling-data-access-plugin/common/profiling'; import { CountPerTime, OTHER_BUCKET_LABEL, TopNSample } from '../../common/topn'; import { useKibanaTimeZoneSetting } from '../hooks/use_kibana_timezone_setting'; import { useProfilingChartsTheme } from '../hooks/use_profiling_charts_theme'; diff --git a/x-pack/plugins/profiling/public/components/topn_functions/utils.ts b/x-pack/plugins/profiling/public/components/topn_functions/utils.ts index f44454d255601..f4524425883d1 100644 --- a/x-pack/plugins/profiling/public/components/topn_functions/utils.ts +++ b/x-pack/plugins/profiling/public/components/topn_functions/utils.ts @@ -5,8 +5,8 @@ * 2.0. */ import { keyBy } from 'lodash'; +import { StackFrameMetadata } from '@kbn/profiling-data-access-plugin/common/profiling'; import { TopNFunctions } from '../../../common/functions'; -import { StackFrameMetadata } from '../../../common/profiling'; import { calculateImpactEstimates } from '../../../common/calculate_impact_estimates'; export function getColorLabel(percent: number) { diff --git a/x-pack/plugins/profiling/public/routing/index.tsx b/x-pack/plugins/profiling/public/routing/index.tsx index 9e08cfbc53fec..b6dcebc3ec18d 100644 --- a/x-pack/plugins/profiling/public/routing/index.tsx +++ b/x-pack/plugins/profiling/public/routing/index.tsx @@ -9,8 +9,11 @@ import { toNumberRt } from '@kbn/io-ts-utils'; import { createRouter, Outlet } from '@kbn/typed-react-router-config'; import * as t from 'io-ts'; import React from 'react'; +import { + StackTracesDisplayOption, + TopNType, +} from '@kbn/profiling-data-access-plugin/common/stack_traces'; import { TopNFunctionSortField, topNFunctionSortFieldRt } from '../../common/functions'; -import { StackTracesDisplayOption, TopNType } from '../../common/stack_traces'; import { indexLifecyclePhaseRt, IndexLifecyclePhaseSelectOption, diff --git a/x-pack/plugins/profiling/public/services.ts b/x-pack/plugins/profiling/public/services.ts index 1a2a684f96e15..6fbe64f57d025 100644 --- a/x-pack/plugins/profiling/public/services.ts +++ b/x-pack/plugins/profiling/public/services.ts @@ -5,8 +5,12 @@ * 2.0. */ import { HttpFetchQuery } from '@kbn/core/public'; +import { + BaseFlameGraph, + createFlameGraph, + ElasticFlameGraph, +} from '@kbn/profiling-data-access-plugin/common/flamegraph'; import { getRoutePaths } from '../common'; -import { BaseFlameGraph, createFlameGraph, ElasticFlameGraph } from '../common/flamegraph'; import { TopNFunctions } from '../common/functions'; import type { IndexLifecyclePhaseSelectOption, diff --git a/x-pack/plugins/profiling/public/utils/get_flamegraph_model/index.ts b/x-pack/plugins/profiling/public/utils/get_flamegraph_model/index.ts index 9e0ec087523ad..6ba179ac44d9c 100644 --- a/x-pack/plugins/profiling/public/utils/get_flamegraph_model/index.ts +++ b/x-pack/plugins/profiling/public/utils/get_flamegraph_model/index.ts @@ -8,10 +8,10 @@ import { ColumnarViewModel } from '@elastic/charts'; import { i18n } from '@kbn/i18n'; import d3 from 'd3'; import { compact, range, sum, uniqueId } from 'lodash'; +import { describeFrameType, FrameType } from '@kbn/profiling-data-access-plugin/common/profiling'; +import { ElasticFlameGraph } from '@kbn/profiling-data-access-plugin/common/flamegraph'; import { createColumnarViewModel } from '../../../common/columnar_view_model'; -import { ElasticFlameGraph } from '../../../common/flamegraph'; import { FRAME_TYPE_COLOR_MAP, rgbToRGBA } from '../../../common/frame_type_colors'; -import { describeFrameType, FrameType } from '../../../common/profiling'; import { ComparisonMode } from '../../components/normalization_menu'; import { getInterpolationValue } from './get_interpolation_value'; diff --git a/x-pack/plugins/profiling/public/views/stack_traces_view/get_stack_traces_tabs.ts b/x-pack/plugins/profiling/public/views/stack_traces_view/get_stack_traces_tabs.ts index 3b06074ba9db9..b1a70254348ab 100644 --- a/x-pack/plugins/profiling/public/views/stack_traces_view/get_stack_traces_tabs.ts +++ b/x-pack/plugins/profiling/public/views/stack_traces_view/get_stack_traces_tabs.ts @@ -8,7 +8,7 @@ import { EuiPageHeaderContentProps } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { TypeOf } from '@kbn/typed-react-router-config'; -import { TopNType } from '../../../common/stack_traces'; +import { TopNType } from '@kbn/profiling-data-access-plugin/common/stack_traces'; import { StatefulProfilingRouter } from '../../hooks/use_profiling_router'; import { ProfilingRoutes } from '../../routing'; diff --git a/x-pack/plugins/profiling/public/views/stack_traces_view/index.tsx b/x-pack/plugins/profiling/public/views/stack_traces_view/index.tsx index 638df8712e115..0df1f9ae855f9 100644 --- a/x-pack/plugins/profiling/public/views/stack_traces_view/index.tsx +++ b/x-pack/plugins/profiling/public/views/stack_traces_view/index.tsx @@ -7,7 +7,10 @@ import { EuiButton, EuiButtonGroup, EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { StackTracesDisplayOption, TopNType } from '../../../common/stack_traces'; +import { + StackTracesDisplayOption, + TopNType, +} from '@kbn/profiling-data-access-plugin/common/stack_traces'; import { groupSamplesByCategory, TopNResponse } from '../../../common/topn'; import { useProfilingParams } from '../../hooks/use_profiling_params'; import { useProfilingRouter } from '../../hooks/use_profiling_router'; diff --git a/x-pack/plugins/profiling/public/views/stack_traces_view/utils.ts b/x-pack/plugins/profiling/public/views/stack_traces_view/utils.ts index 74e13f249108c..7b6b2a0ead397 100644 --- a/x-pack/plugins/profiling/public/views/stack_traces_view/utils.ts +++ b/x-pack/plugins/profiling/public/views/stack_traces_view/utils.ts @@ -6,7 +6,10 @@ */ import { TypeOf } from '@kbn/typed-react-router-config'; -import { getFieldNameForTopNType, TopNType } from '../../../common/stack_traces'; +import { + getFieldNameForTopNType, + TopNType, +} from '@kbn/profiling-data-access-plugin/common/stack_traces'; import { ProfilingRoutes } from '../../routing'; export function getTracesViewRouteParams({ diff --git a/x-pack/plugins/profiling/public/views/storage_explorer/summary.tsx b/x-pack/plugins/profiling/public/views/storage_explorer/summary.tsx index 051765a8880a6..19a3e95809155 100644 --- a/x-pack/plugins/profiling/public/views/storage_explorer/summary.tsx +++ b/x-pack/plugins/profiling/public/views/storage_explorer/summary.tsx @@ -9,7 +9,10 @@ import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiPanel, EuiStat, EuiText } from ' import { i18n } from '@kbn/i18n'; import { asDynamicBytes } from '@kbn/observability-plugin/common'; import React from 'react'; -import { StackTracesDisplayOption, TopNType } from '../../../common/stack_traces'; +import { + StackTracesDisplayOption, + TopNType, +} from '@kbn/profiling-data-access-plugin/common/stack_traces'; import { StorageExplorerSummaryAPIResponse } from '../../../common/storage_explorer'; import { useProfilingDependencies } from '../../components/contexts/profiling_dependencies/use_profiling_dependencies'; import { LabelWithHint } from '../../components/label_with_hint'; diff --git a/x-pack/plugins/profiling/server/routes/query.ts b/x-pack/plugins/profiling/server/routes/query.ts index f8a776ee68ce7..eed6b272c800e 100644 --- a/x-pack/plugins/profiling/server/routes/query.ts +++ b/x-pack/plugins/profiling/server/routes/query.ts @@ -7,7 +7,7 @@ import { QueryDslBoolQuery } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { kqlQuery } from '@kbn/observability-plugin/server'; -import { ProfilingESField } from '../../common/elasticsearch'; +import { ProfilingESField } from '@kbn/profiling-data-access-plugin/common/elasticsearch'; export interface ProjectTimeQuery { bool: QueryDslBoolQuery; diff --git a/x-pack/plugins/profiling/server/routes/search_stacktraces.ts b/x-pack/plugins/profiling/server/routes/search_stacktraces.ts index b90cbaf78bbe3..1e42cd11265ee 100644 --- a/x-pack/plugins/profiling/server/routes/search_stacktraces.ts +++ b/x-pack/plugins/profiling/server/routes/search_stacktraces.ts @@ -4,8 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import { decodeStackTraceResponse } from '../../common/stack_traces'; +import { decodeStackTraceResponse } from '@kbn/profiling-data-access-plugin/common/stack_traces'; import { ProfilingESClient } from '../utils/create_profiling_es_client'; import { ProjectTimeQuery } from './query'; diff --git a/x-pack/plugins/profiling/server/routes/setup.ts b/x-pack/plugins/profiling/server/routes/setup.ts index 1ce7d8d056201..0b8ed360cb724 100644 --- a/x-pack/plugins/profiling/server/routes/setup.ts +++ b/x-pack/plugins/profiling/server/routes/setup.ts @@ -74,6 +74,15 @@ export function registerSetupRoute({ config: dependencies.config, }; + return response.ok({ + body: { + has_setup: true, + pre_8_9_1_data: false, + has_data: true, + unauthorized: false, + }, + }); + const state = createDefaultSetupState(); state.cloud.available = dependencies.setup.cloud.isCloudEnabled; diff --git a/x-pack/plugins/profiling/server/routes/stacktrace.ts b/x-pack/plugins/profiling/server/routes/stacktrace.ts index 0777d8c741161..96af265358078 100644 --- a/x-pack/plugins/profiling/server/routes/stacktrace.ts +++ b/x-pack/plugins/profiling/server/routes/stacktrace.ts @@ -4,13 +4,15 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import { DedotObject, ProfilingESField } from '../../common/elasticsearch'; +import { + DedotObject, + ProfilingESField, +} from '@kbn/profiling-data-access-plugin/common/elasticsearch'; import { getAddressFromStackFrameID, getFileIDFromStackFrameID, StackTrace, -} from '../../common/profiling'; +} from '@kbn/profiling-data-access-plugin/common/profiling'; import { runLengthDecodeBase64Url } from '../../common/run_length_encoding'; const BASE64_FRAME_ID_LENGTH = 32; diff --git a/x-pack/plugins/profiling/server/routes/storage_explorer/get_host_breakdown_size_timeseries.ts b/x-pack/plugins/profiling/server/routes/storage_explorer/get_host_breakdown_size_timeseries.ts index 57ec31e6bcfbd..529887f7f190d 100644 --- a/x-pack/plugins/profiling/server/routes/storage_explorer/get_host_breakdown_size_timeseries.ts +++ b/x-pack/plugins/profiling/server/routes/storage_explorer/get_host_breakdown_size_timeseries.ts @@ -6,7 +6,7 @@ */ import { kqlQuery, termQuery } from '@kbn/observability-plugin/server'; -import { ProfilingESField } from '../../../common/elasticsearch'; +import { ProfilingESField } from '@kbn/profiling-data-access-plugin/common/elasticsearch'; import { computeBucketWidthFromTimeRangeAndBucketCount } from '../../../common/histogram'; import { IndexLifecyclePhaseSelectOption, diff --git a/x-pack/plugins/profiling/server/routes/storage_explorer/get_host_details.ts b/x-pack/plugins/profiling/server/routes/storage_explorer/get_host_details.ts index 65a66071431f1..20f3f080597f5 100644 --- a/x-pack/plugins/profiling/server/routes/storage_explorer/get_host_details.ts +++ b/x-pack/plugins/profiling/server/routes/storage_explorer/get_host_details.ts @@ -6,7 +6,7 @@ */ import { kqlQuery, termQuery } from '@kbn/observability-plugin/server'; -import { ProfilingESField } from '../../../common/elasticsearch'; +import { ProfilingESField } from '@kbn/profiling-data-access-plugin/common/elasticsearch'; import { IndexLifecyclePhaseSelectOption, indexLifeCyclePhaseToDataTier, diff --git a/x-pack/plugins/profiling/server/routes/storage_explorer/get_profiling_hosts_details_by_id.ts b/x-pack/plugins/profiling/server/routes/storage_explorer/get_profiling_hosts_details_by_id.ts index 6e3216dc0fd43..bc3a0dd581903 100644 --- a/x-pack/plugins/profiling/server/routes/storage_explorer/get_profiling_hosts_details_by_id.ts +++ b/x-pack/plugins/profiling/server/routes/storage_explorer/get_profiling_hosts_details_by_id.ts @@ -6,7 +6,7 @@ */ import { kqlQuery } from '@kbn/observability-plugin/server'; import { keyBy } from 'lodash'; -import { ProfilingESField } from '../../../common/elasticsearch'; +import { ProfilingESField } from '@kbn/profiling-data-access-plugin/common/elasticsearch'; import { ProfilingESClient } from '../../utils/create_profiling_es_client'; interface HostDetails { diff --git a/x-pack/plugins/profiling/server/routes/topn.test.ts b/x-pack/plugins/profiling/server/routes/topn.test.ts index 1276c5dcc94dd..ab88c911ce97a 100644 --- a/x-pack/plugins/profiling/server/routes/topn.test.ts +++ b/x-pack/plugins/profiling/server/routes/topn.test.ts @@ -8,7 +8,7 @@ import { AggregationsAggregationContainer } from '@elastic/elasticsearch/lib/api/types'; import { coreMock } from '@kbn/core/server/mocks'; import { loggerMock } from '@kbn/logging-mocks'; -import { ProfilingESField } from '../../common/elasticsearch'; +import { ProfilingESField } from '@kbn/profiling-data-access-plugin/common/elasticsearch'; import { ProfilingESClient } from '../utils/create_profiling_es_client'; import { topNElasticSearchQuery } from './topn'; diff --git a/x-pack/plugins/profiling/server/routes/topn.ts b/x-pack/plugins/profiling/server/routes/topn.ts index ac4621cf0ed14..5e3f1cb16e8be 100644 --- a/x-pack/plugins/profiling/server/routes/topn.ts +++ b/x-pack/plugins/profiling/server/routes/topn.ts @@ -7,12 +7,15 @@ import { schema } from '@kbn/config-schema'; import type { Logger } from '@kbn/core/server'; -import { RouteRegisterParameters } from '.'; +import { ProfilingESField } from '@kbn/profiling-data-access-plugin/common/elasticsearch'; +import { groupStackFrameMetadataByStackTrace } from '@kbn/profiling-data-access-plugin/common/profiling'; +import { + getFieldNameForTopNType, + TopNType, +} from '@kbn/profiling-data-access-plugin/common/stack_traces'; import { getRoutePaths, INDEX_EVENTS } from '../../common'; -import { ProfilingESField } from '../../common/elasticsearch'; +import { RouteRegisterParameters } from '.'; import { computeBucketWidthFromTimeRangeAndBucketCount } from '../../common/histogram'; -import { groupStackFrameMetadataByStackTrace } from '../../common/profiling'; -import { getFieldNameForTopNType, TopNType } from '../../common/stack_traces'; import { createTopNSamples, getTopNAggregationRequest, TopNResponse } from '../../common/topn'; import { handleRouteHandlerError } from '../utils/handle_route_error_handler'; import { ProfilingESClient } from '../utils/create_profiling_es_client'; diff --git a/x-pack/plugins/profiling/server/utils/create_profiling_es_client.ts b/x-pack/plugins/profiling/server/utils/create_profiling_es_client.ts index 8fa67eb23ecfe..1379fe2a56bf2 100644 --- a/x-pack/plugins/profiling/server/utils/create_profiling_es_client.ts +++ b/x-pack/plugins/profiling/server/utils/create_profiling_es_client.ts @@ -10,8 +10,11 @@ import type { ESSearchRequest, InferSearchResponseOf } from '@kbn/es-types'; import type { KibanaRequest } from '@kbn/core/server'; import { unwrapEsResponse } from '@kbn/observability-plugin/server'; import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { + ProfilingStatusResponse, + StackTraceResponse, +} from '@kbn/profiling-data-access-plugin/common/stack_traces'; import { withProfilingSpan } from './with_profiling_span'; -import { ProfilingStatusResponse, StackTraceResponse } from '../../common/stack_traces'; export function cancelEsRequestOnAbort>( promise: T, diff --git a/x-pack/plugins/profiling/common/__fixtures__/README.md b/x-pack/plugins/profiling_data_access/common/__fixtures__/README.md similarity index 100% rename from x-pack/plugins/profiling/common/__fixtures__/README.md rename to x-pack/plugins/profiling_data_access/common/__fixtures__/README.md diff --git a/x-pack/plugins/profiling/common/__fixtures__/stacktraces.ts b/x-pack/plugins/profiling_data_access/common/__fixtures__/stacktraces.ts similarity index 100% rename from x-pack/plugins/profiling/common/__fixtures__/stacktraces.ts rename to x-pack/plugins/profiling_data_access/common/__fixtures__/stacktraces.ts diff --git a/x-pack/plugins/profiling/common/__fixtures__/stacktraces_3600s_5x.json b/x-pack/plugins/profiling_data_access/common/__fixtures__/stacktraces_3600s_5x.json similarity index 100% rename from x-pack/plugins/profiling/common/__fixtures__/stacktraces_3600s_5x.json rename to x-pack/plugins/profiling_data_access/common/__fixtures__/stacktraces_3600s_5x.json diff --git a/x-pack/plugins/profiling/common/__fixtures__/stacktraces_604800s_625x.json b/x-pack/plugins/profiling_data_access/common/__fixtures__/stacktraces_604800s_625x.json similarity index 100% rename from x-pack/plugins/profiling/common/__fixtures__/stacktraces_604800s_625x.json rename to x-pack/plugins/profiling_data_access/common/__fixtures__/stacktraces_604800s_625x.json diff --git a/x-pack/plugins/profiling/common/__fixtures__/stacktraces_60s_1x.json b/x-pack/plugins/profiling_data_access/common/__fixtures__/stacktraces_60s_1x.json similarity index 100% rename from x-pack/plugins/profiling/common/__fixtures__/stacktraces_60s_1x.json rename to x-pack/plugins/profiling_data_access/common/__fixtures__/stacktraces_60s_1x.json diff --git a/x-pack/plugins/profiling/common/__fixtures__/stacktraces_86400s_125x.json b/x-pack/plugins/profiling_data_access/common/__fixtures__/stacktraces_86400s_125x.json similarity index 100% rename from x-pack/plugins/profiling/common/__fixtures__/stacktraces_86400s_125x.json rename to x-pack/plugins/profiling_data_access/common/__fixtures__/stacktraces_86400s_125x.json diff --git a/x-pack/plugins/profiling/common/callee.test.ts b/x-pack/plugins/profiling_data_access/common/callee.test.ts similarity index 100% rename from x-pack/plugins/profiling/common/callee.test.ts rename to x-pack/plugins/profiling_data_access/common/callee.test.ts diff --git a/x-pack/plugins/profiling/common/flamegraph.test.ts b/x-pack/plugins/profiling_data_access/common/flamegraph.test.ts similarity index 100% rename from x-pack/plugins/profiling/common/flamegraph.test.ts rename to x-pack/plugins/profiling_data_access/common/flamegraph.test.ts diff --git a/x-pack/plugins/profiling/common/frame_group.test.ts b/x-pack/plugins/profiling_data_access/common/frame_group.test.ts similarity index 100% rename from x-pack/plugins/profiling/common/frame_group.test.ts rename to x-pack/plugins/profiling_data_access/common/frame_group.test.ts diff --git a/x-pack/plugins/profiling/common/hash.test.ts b/x-pack/plugins/profiling_data_access/common/hash.test.ts similarity index 100% rename from x-pack/plugins/profiling/common/hash.test.ts rename to x-pack/plugins/profiling_data_access/common/hash.test.ts diff --git a/x-pack/plugins/profiling/common/profiling.test.ts b/x-pack/plugins/profiling_data_access/common/profiling.test.ts similarity index 100% rename from x-pack/plugins/profiling/common/profiling.test.ts rename to x-pack/plugins/profiling_data_access/common/profiling.test.ts diff --git a/x-pack/plugins/profiling/common/stack_traces.test.ts b/x-pack/plugins/profiling_data_access/common/stack_traces.test.ts similarity index 100% rename from x-pack/plugins/profiling/common/stack_traces.test.ts rename to x-pack/plugins/profiling_data_access/common/stack_traces.test.ts From 3b7eb81714679a8948f150703efc9984e4f05ba5 Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Wed, 30 Aug 2023 10:25:20 +0100 Subject: [PATCH 03/12] adding plugin to tsconfig --- x-pack/plugins/profiling/tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/profiling/tsconfig.json b/x-pack/plugins/profiling/tsconfig.json index 980249959b215..ff93bada71703 100644 --- a/x-pack/plugins/profiling/tsconfig.json +++ b/x-pack/plugins/profiling/tsconfig.json @@ -47,7 +47,8 @@ "@kbn/licensing-plugin", "@kbn/utility-types", "@kbn/usage-collection-plugin", - "@kbn/observability-ai-assistant-plugin" + "@kbn/observability-ai-assistant-plugin", + "@kbn/profiling-data-access-plugin" // add references to other TypeScript projects the plugin depends on // requiredPlugins from ./kibana.json From f973c3f6ba30d836193450c11c84091323502309 Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Wed, 30 Aug 2023 10:46:55 +0100 Subject: [PATCH 04/12] fixing imports --- x-pack/plugins/profiling/common/frame_type_colors.ts | 2 +- x-pack/plugins/profiling/kibana.jsonc | 2 +- .../plugins/profiling/public/components/flamegraph/index.tsx | 2 +- .../frame_information_window/get_information_rows.ts | 2 +- .../public/components/frame_information_window/index.tsx | 5 ++++- .../missing_symbols_callout.stories.tsx | 2 +- 6 files changed, 9 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/profiling/common/frame_type_colors.ts b/x-pack/plugins/profiling/common/frame_type_colors.ts index 11c8cbaca9f30..59e8a6004c4e6 100644 --- a/x-pack/plugins/profiling/common/frame_type_colors.ts +++ b/x-pack/plugins/profiling/common/frame_type_colors.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FrameType } from './profiling'; +import { FrameType } from '@kbn/profiling-data-access-plugin/common/profiling'; /* * Helper to calculate the color of a given block to be drawn. The desirable outcomes of this are: diff --git a/x-pack/plugins/profiling/kibana.jsonc b/x-pack/plugins/profiling/kibana.jsonc index 121da578bb9bb..612e8d7936c25 100644 --- a/x-pack/plugins/profiling/kibana.jsonc +++ b/x-pack/plugins/profiling/kibana.jsonc @@ -29,7 +29,7 @@ "requiredBundles": [ "kibanaReact", "kibanaUtils", - "observabilityAIAssistant", + "observabilityAIAssistant" ] } } diff --git a/x-pack/plugins/profiling/public/components/flamegraph/index.tsx b/x-pack/plugins/profiling/public/components/flamegraph/index.tsx index 5f4bc8cfab9cc..7d02f4b480350 100644 --- a/x-pack/plugins/profiling/public/components/flamegraph/index.tsx +++ b/x-pack/plugins/profiling/public/components/flamegraph/index.tsx @@ -19,7 +19,7 @@ import { EuiFlexGroup, EuiFlexItem, useEuiTheme } from '@elastic/eui'; import { Maybe } from '@kbn/observability-plugin/common/typings'; import React, { useEffect, useMemo, useState } from 'react'; import { useUiTracker } from '@kbn/observability-shared-plugin/public'; -import { ElasticFlameGraph } from '../../../common/flamegraph'; +import { ElasticFlameGraph } from '@kbn/profiling-data-access-plugin/common/flamegraph'; import { getFlamegraphModel } from '../../utils/get_flamegraph_model'; import { FlameGraphLegend } from './flame_graph_legend'; import { FrameInformationWindow } from '../frame_information_window'; diff --git a/x-pack/plugins/profiling/public/components/frame_information_window/get_information_rows.ts b/x-pack/plugins/profiling/public/components/frame_information_window/get_information_rows.ts index 057f56f9c2f06..8c2fa0ebd0763 100644 --- a/x-pack/plugins/profiling/public/components/frame_information_window/get_information_rows.ts +++ b/x-pack/plugins/profiling/public/components/frame_information_window/get_information_rows.ts @@ -6,8 +6,8 @@ */ import { i18n } from '@kbn/i18n'; +import { describeFrameType } from '@kbn/profiling-data-access-plugin/common/profiling'; import { NOT_AVAILABLE_LABEL } from '../../../common'; -import { describeFrameType } from '../../../common/profiling'; export function getInformationRows({ fileID, diff --git a/x-pack/plugins/profiling/public/components/frame_information_window/index.tsx b/x-pack/plugins/profiling/public/components/frame_information_window/index.tsx index d0925897877fc..96879a59fc737 100644 --- a/x-pack/plugins/profiling/public/components/frame_information_window/index.tsx +++ b/x-pack/plugins/profiling/public/components/frame_information_window/index.tsx @@ -13,7 +13,10 @@ import { useObservabilityAIAssistant, } from '@kbn/observability-ai-assistant-plugin/public'; import React, { useMemo } from 'react'; -import { FrameSymbolStatus, getFrameSymbolStatus } from '../../../common/profiling'; +import { + FrameSymbolStatus, + getFrameSymbolStatus, +} from '@kbn/profiling-data-access-plugin/common/profiling'; import { FrameInformationPanel } from './frame_information_panel'; import { getImpactRows } from './get_impact_rows'; import { getInformationRows } from './get_information_rows'; diff --git a/x-pack/plugins/profiling/public/components/frame_information_window/missing_symbols_callout.stories.tsx b/x-pack/plugins/profiling/public/components/frame_information_window/missing_symbols_callout.stories.tsx index ba353d48c6995..aba91dcc4127a 100644 --- a/x-pack/plugins/profiling/public/components/frame_information_window/missing_symbols_callout.stories.tsx +++ b/x-pack/plugins/profiling/public/components/frame_information_window/missing_symbols_callout.stories.tsx @@ -8,7 +8,7 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { Meta } from '@storybook/react'; import React from 'react'; -import { FrameType } from '../../../common/profiling'; +import { FrameType } from '@kbn/profiling-data-access-plugin/common/profiling'; import { MockProfilingDependenciesStorybook } from '../contexts/profiling_dependencies/mock_profiling_dependencies_storybook'; import { MissingSymbolsCallout } from './missing_symbols_callout'; From 6eef0c68ff91ec9a192745de0043a1b5069a8f68 Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Wed, 30 Aug 2023 10:55:10 +0100 Subject: [PATCH 05/12] adding files to tsconfig --- x-pack/plugins/profiling_data_access/tsconfig.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x-pack/plugins/profiling_data_access/tsconfig.json b/x-pack/plugins/profiling_data_access/tsconfig.json index a9aff086cbbe0..5f45b3ce396d1 100644 --- a/x-pack/plugins/profiling_data_access/tsconfig.json +++ b/x-pack/plugins/profiling_data_access/tsconfig.json @@ -5,6 +5,8 @@ }, "include": [ "server/**/*", + "common/**/*.ts", + "common/**/*.json", "jest.config.js" ], "exclude": [ From 13cfd1f38b7580512788ce9292b215075f71101e Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 30 Aug 2023 10:00:57 +0000 Subject: [PATCH 06/12] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- x-pack/plugins/profiling_data_access/tsconfig.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/profiling_data_access/tsconfig.json b/x-pack/plugins/profiling_data_access/tsconfig.json index 5f45b3ce396d1..f8693cb83658e 100644 --- a/x-pack/plugins/profiling_data_access/tsconfig.json +++ b/x-pack/plugins/profiling_data_access/tsconfig.json @@ -15,7 +15,9 @@ "kbn_references": [ "@kbn/config-schema", "@kbn/core", - "@kbn/i18n", - "@kbn/core-saved-objects-api-server" + "@kbn/es-query", + "@kbn/es-types", + "@kbn/observability-plugin", + "@kbn/apm-utils" ] -} \ No newline at end of file +} From ebf92cea6fefb42abec339569f5575daae516562 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 30 Aug 2023 10:09:34 +0000 Subject: [PATCH 07/12] [CI] Auto-commit changed files from 'node scripts/generate codeowners' --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4f050e3bf422f..e917cdea1081e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -544,6 +544,7 @@ packages/kbn-plugin-helpers @elastic/kibana-operations examples/portable_dashboards_example @elastic/kibana-presentation examples/preboot_example @elastic/kibana-security @elastic/kibana-core src/plugins/presentation_util @elastic/kibana-presentation +x-pack/plugins/profiling_data_access @elastic/profiling-ui x-pack/plugins/profiling @elastic/profiling-ui x-pack/packages/kbn-random-sampling @elastic/kibana-visualizations packages/kbn-react-field @elastic/kibana-data-discovery From 5a614b497c871788e3321e5747808393e02cd770 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 30 Aug 2023 10:16:48 +0000 Subject: [PATCH 08/12] [CI] Auto-commit changed files from 'node scripts/build_plugin_list_docs' --- docs/developer/plugin-list.asciidoc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 632bce5883c28..fd2a6d9d4204a 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -689,6 +689,10 @@ Elastic. |Universal Profiling provides fleet-wide, whole-system, continuous profiling with zero instrumentation. Get a comprehensive understanding of what lines of code are consuming compute resources throughout your entire fleet by visualizing your data in Kibana using the flamegraph, stacktraces, and top functions views. +|{kib-repo}blob/{branch}/x-pack/plugins/profiling_data_access[profilingDataAccess] +|WARNING: Missing README. + + |{kib-repo}blob/{branch}/x-pack/plugins/remote_clusters/README.md[remoteClusters] |This plugin helps users manage their remote clusters, which enable cross-cluster search and cross-cluster replication. From 3052137691d886f41ee4e9161bc523019f405cc8 Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Wed, 30 Aug 2023 12:25:51 +0100 Subject: [PATCH 09/12] fixing lint --- .../profiling/common/columnar_view_model.test.ts | 12 +++++++----- .../plugins/profiling/common/columnar_view_model.ts | 3 +-- x-pack/plugins/profiling/common/functions.test.ts | 6 ++---- .../public/views/stack_traces_view/utils.test.ts | 5 ++++- .../profiling/server/routes/stacktrace.test.ts | 2 +- x-pack/plugins/profiling_data_access/server/index.ts | 2 +- 6 files changed, 16 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/profiling/common/columnar_view_model.test.ts b/x-pack/plugins/profiling/common/columnar_view_model.test.ts index c43a1aa86ba63..d967650c76c7f 100644 --- a/x-pack/plugins/profiling/common/columnar_view_model.test.ts +++ b/x-pack/plugins/profiling/common/columnar_view_model.test.ts @@ -7,12 +7,14 @@ import { sum } from 'lodash'; -import { createCalleeTree } from './callee'; +import { createCalleeTree } from '@kbn/profiling-data-access-plugin/common/callee'; import { createColumnarViewModel } from './columnar_view_model'; -import { createBaseFlameGraph, createFlameGraph } from './flamegraph'; -import { decodeStackTraceResponse } from './stack_traces'; - -import { stackTraceFixtures } from './__fixtures__/stacktraces'; +import { + createBaseFlameGraph, + createFlameGraph, +} from '@kbn/profiling-data-access-plugin/common/flamegraph'; +import { decodeStackTraceResponse } from '@kbn/profiling-data-access-plugin/common/stack_traces'; +import { stackTraceFixtures } from '@kbn/profiling-data-access-plugin/common/__fixtures__/stacktraces'; describe('Columnar view model operations', () => { stackTraceFixtures.forEach(({ response, seconds, upsampledBy }) => { diff --git a/x-pack/plugins/profiling/common/columnar_view_model.ts b/x-pack/plugins/profiling/common/columnar_view_model.ts index 20bf2b2761855..e4d2386a0bd74 100644 --- a/x-pack/plugins/profiling/common/columnar_view_model.ts +++ b/x-pack/plugins/profiling/common/columnar_view_model.ts @@ -6,8 +6,7 @@ */ import { ColumnarViewModel } from '@elastic/charts'; - -import { ElasticFlameGraph } from './flamegraph'; +import { ElasticFlameGraph } from '@kbn/profiling-data-access-plugin/common/flamegraph'; import { frameTypeToRGB, rgbToRGBA } from './frame_type_colors'; function normalize(n: number, lower: number, upper: number): number { diff --git a/x-pack/plugins/profiling/common/functions.test.ts b/x-pack/plugins/profiling/common/functions.test.ts index 1ba31d397a338..4c70cca80d7ba 100644 --- a/x-pack/plugins/profiling/common/functions.test.ts +++ b/x-pack/plugins/profiling/common/functions.test.ts @@ -6,11 +6,9 @@ */ import { sum } from 'lodash'; - import { createTopNFunctions } from './functions'; -import { decodeStackTraceResponse } from './stack_traces'; - -import { stackTraceFixtures } from './__fixtures__/stacktraces'; +import { decodeStackTraceResponse } from '@kbn/profiling-data-access-plugin/common/stack_traces'; +import { stackTraceFixtures } from '@kbn/profiling-data-access-plugin/common/__fixtures__/stacktraces'; describe('TopN function operations', () => { stackTraceFixtures.forEach(({ response, seconds, upsampledBy }) => { diff --git a/x-pack/plugins/profiling/public/views/stack_traces_view/utils.test.ts b/x-pack/plugins/profiling/public/views/stack_traces_view/utils.test.ts index 915b24c53429d..71272b7eda63c 100644 --- a/x-pack/plugins/profiling/public/views/stack_traces_view/utils.test.ts +++ b/x-pack/plugins/profiling/public/views/stack_traces_view/utils.test.ts @@ -4,7 +4,10 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { StackTracesDisplayOption, TopNType } from '../../../common/stack_traces'; +import { + StackTracesDisplayOption, + TopNType, +} from '@kbn/profiling-data-access-plugin/common/stack_traces'; import { getTracesViewRouteParams } from './utils'; describe('stack traces view utils', () => { diff --git a/x-pack/plugins/profiling/server/routes/stacktrace.test.ts b/x-pack/plugins/profiling/server/routes/stacktrace.test.ts index 91b55312928c3..48a2feda70e0f 100644 --- a/x-pack/plugins/profiling/server/routes/stacktrace.test.ts +++ b/x-pack/plugins/profiling/server/routes/stacktrace.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { createStackFrameID, StackTrace } from '../../common/profiling'; +import { createStackFrameID, StackTrace } from '@kbn/profiling-data-access-plugin/common/profiling'; import { runLengthEncode } from '../../common/run_length_encoding'; import { decodeStackTrace, EncodedStackTrace } from './stacktrace'; diff --git a/x-pack/plugins/profiling_data_access/server/index.ts b/x-pack/plugins/profiling_data_access/server/index.ts index 73700f3704157..d979d22f4a011 100644 --- a/x-pack/plugins/profiling_data_access/server/index.ts +++ b/x-pack/plugins/profiling_data_access/server/index.ts @@ -27,7 +27,7 @@ const configSchema = schema.object({ export type ProfilingConfig = TypeOf; -export { ProfilingDataAccessPluginSetup, ProfilingDataAccessPluginStart }; +export type { ProfilingDataAccessPluginSetup, ProfilingDataAccessPluginStart }; export function plugin(initializerContext: PluginInitializerContext) { return new ProfilingDataAccessPlugin(initializerContext); From 63d7641713186b1e5ef033c8ba62acd522904eac Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Wed, 30 Aug 2023 14:20:45 +0100 Subject: [PATCH 10/12] fixing setup --- x-pack/plugins/profiling/server/routes/setup.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/x-pack/plugins/profiling/server/routes/setup.ts b/x-pack/plugins/profiling/server/routes/setup.ts index 0b8ed360cb724..1ce7d8d056201 100644 --- a/x-pack/plugins/profiling/server/routes/setup.ts +++ b/x-pack/plugins/profiling/server/routes/setup.ts @@ -74,15 +74,6 @@ export function registerSetupRoute({ config: dependencies.config, }; - return response.ok({ - body: { - has_setup: true, - pre_8_9_1_data: false, - has_data: true, - unauthorized: false, - }, - }); - const state = createDefaultSetupState(); state.cloud.available = dependencies.setup.cloud.isCloudEnabled; From 969b0bbb9f9d3d81e5734e2193443b03ea0cbe37 Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Thu, 31 Aug 2023 09:08:47 +0100 Subject: [PATCH 11/12] adding pre89 data flag --- x-pack/plugins/profiling_data_access/common/stack_traces.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/profiling_data_access/common/stack_traces.ts b/x-pack/plugins/profiling_data_access/common/stack_traces.ts index 8266f32a19f1e..97a18d09ed389 100644 --- a/x-pack/plugins/profiling_data_access/common/stack_traces.ts +++ b/x-pack/plugins/profiling_data_access/common/stack_traces.ts @@ -24,6 +24,7 @@ export interface ProfilingStatusResponse { }; resources: { created: boolean; + pre_8_9_1_data: boolean; }; } From 46f7e3d51b1a6dc5b9ec0efb020e042b897c21c0 Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Thu, 31 Aug 2023 09:54:38 +0100 Subject: [PATCH 12/12] removing unused code --- x-pack/plugins/profiling/common/index.ts | 12 -- .../common/run_length_encoding.test.ts | 170 --------------- .../profiling/common/run_length_encoding.ts | 199 ------------------ .../server/routes/stacktrace.test.ts | 88 -------- .../profiling/server/routes/stacktrace.ts | 74 ------- .../profiling_data_access/common/base64.ts | 22 -- .../common/profiling.test.ts | 11 - .../profiling_data_access/common/profiling.ts | 40 ---- 8 files changed, 616 deletions(-) delete mode 100644 x-pack/plugins/profiling/common/run_length_encoding.test.ts delete mode 100644 x-pack/plugins/profiling/common/run_length_encoding.ts delete mode 100644 x-pack/plugins/profiling/server/routes/stacktrace.test.ts delete mode 100644 x-pack/plugins/profiling/server/routes/stacktrace.ts delete mode 100644 x-pack/plugins/profiling_data_access/common/base64.ts diff --git a/x-pack/plugins/profiling/common/index.ts b/x-pack/plugins/profiling/common/index.ts index 2713ed3f98a13..f6266b606ee5a 100644 --- a/x-pack/plugins/profiling/common/index.ts +++ b/x-pack/plugins/profiling/common/index.ts @@ -41,18 +41,6 @@ export function timeRangeFromRequest(request: any): [number, number] { return [timeFrom, timeTo]; } -// Converts from a Map object to a Record object since Map objects are not -// serializable to JSON by default -export function fromMapToRecord(m: Map): Record { - const output: Record = {}; - - for (const [key, value] of m) { - output[key] = value; - } - - return output; -} - export const NOT_AVAILABLE_LABEL = i18n.translate('xpack.profiling.notAvailableLabel', { defaultMessage: 'N/A', }); diff --git a/x-pack/plugins/profiling/common/run_length_encoding.test.ts b/x-pack/plugins/profiling/common/run_length_encoding.test.ts deleted file mode 100644 index 4831177075fc2..0000000000000 --- a/x-pack/plugins/profiling/common/run_length_encoding.test.ts +++ /dev/null @@ -1,170 +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 { runLengthDecode, runLengthDecodeBase64Url, runLengthEncode } from './run_length_encoding'; - -describe('Run-length encoding operations', () => { - test('run length is fully reversible', () => { - const tests: number[][] = [[], [0], [0, 1, 2, 3], [0, 1, 1, 2, 2, 2, 3, 3, 3, 3]]; - - for (const t of tests) { - expect(runLengthDecode(runLengthEncode(t))).toEqual(t); - } - }); - - test('runLengthDecode with optional parameter', () => { - const tests: Array<{ - bytes: Buffer; - expected: number[]; - }> = [ - { - bytes: Buffer.from([0x5, 0x0, 0x2, 0x2]), - expected: [0, 0, 0, 0, 0, 2, 2], - }, - { - bytes: Buffer.from([0x1, 0x8]), - expected: [8], - }, - ]; - - for (const t of tests) { - expect(runLengthDecode(t.bytes, t.expected.length)).toEqual(t.expected); - } - }); - - 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; - expected: number[]; - }> = [ - { - bytes: Buffer.from([0x5, 0x0, 0x2, 0x2]), - expected: [0, 0, 0, 0, 0, 2, 2], - }, - { - bytes: Buffer.from([0x1, 0x8]), - expected: [8], - }, - ]; - - for (const t of tests) { - expect(runLengthDecode(t.bytes)).toEqual(t.expected); - } - }); - - test('runLengthDecode works for very long runs', () => { - const tests: Array<{ - bytes: Buffer; - expected: number[]; - }> = [ - { - bytes: Buffer.from([0x5, 0x2, 0xff, 0x0]), - expected: [2, 2, 2, 2, 2].concat(Array(255).fill(0)), - }, - { - bytes: Buffer.from([0xff, 0x2, 0x1, 0x2]), - expected: Array(256).fill(2), - }, - ]; - - for (const t of tests) { - expect(runLengthDecode(t.bytes)).toEqual(t.expected); - } - }); - - test('runLengthEncode works for very long runs', () => { - const tests: Array<{ - numbers: number[]; - expected: Buffer; - }> = [ - { - numbers: [2, 2, 2, 2, 2].concat(Array(255).fill(0)), - expected: Buffer.from([0x5, 0x2, 0xff, 0x0]), - }, - { - numbers: Array(256).fill(2), - expected: Buffer.from([0xff, 0x2, 0x1, 0x2]), - }, - ]; - - for (const t of tests) { - expect(runLengthEncode(t.numbers)).toEqual(t.expected); - } - }); - - test('runLengthDecodeBase64Url', () => { - const tests: Array<{ - data: string; - expected: number[]; - }> = [ - { - data: 'CQM', - expected: [3, 3, 3, 3, 3, 3, 3, 3, 3], - }, - { - data: 'AQkBCAEHAQYBBQEEAQMBAgEBAQA', - expected: [9, 8, 7, 6, 5, 4, 3, 2, 1, 0], - }, - { - data: 'EgMHBA', - expected: Array(18).fill(3).concat(Array(7).fill(4)), - }, - { - data: 'CAMfBQIDEAQ', - expected: Array(8) - .fill(3) - .concat(Array(31).fill(5)) - .concat([3, 3]) - .concat(Array(16).fill(4)), - }, - ]; - - for (const t of tests) { - expect(runLengthDecodeBase64Url(t.data, t.data.length, t.expected.length)).toEqual( - t.expected - ); - } - }); - - test('runLengthDecodeBase64Url with larger output than available input', () => { - const data = Buffer.from([0x5, 0x0, 0x3, 0x2]).toString('base64url'); - const decoded = [0, 0, 0, 0, 0, 2, 2, 2]; - const expected = decoded.concat(Array(decoded.length).fill(0)); - - expect(runLengthDecodeBase64Url(data, data.length, expected.length)).toEqual(expected); - }); - - test('runLengthDecodeBase64Url works for very long runs', () => { - const tests: Array<{ - data: string; - expected: number[]; - }> = [ - { - data: Buffer.from([0x5, 0x2, 0xff, 0x0]).toString('base64url'), - expected: [2, 2, 2, 2, 2].concat(Array(255).fill(0)), - }, - { - data: Buffer.from([0xff, 0x2, 0x1, 0x2]).toString('base64url'), - expected: Array(256).fill(2), - }, - ]; - - for (const t of tests) { - expect(runLengthDecodeBase64Url(t.data, t.data.length, t.expected.length)).toEqual( - t.expected - ); - } - }); -}); diff --git a/x-pack/plugins/profiling/common/run_length_encoding.ts b/x-pack/plugins/profiling/common/run_length_encoding.ts deleted file mode 100644 index 80066ae70d72a..0000000000000 --- a/x-pack/plugins/profiling/common/run_length_encoding.ts +++ /dev/null @@ -1,199 +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 { charCodeAt } from '@kbn/profiling-data-access-plugin/common/base64'; - -// runLengthEncode run-length encodes the input array. -// -// The input is a list of uint8s. The output is a binary stream of -// 2-byte pairs (first byte is the length and the second byte is the -// binary representation of the object) in reverse order. -// -// E.g. uint8 array [0, 0, 0, 0, 0, 2, 2, 2] is converted into the byte -// array [5, 0, 3, 2]. -export function runLengthEncode(input: number[]): Buffer { - const output: number[] = []; - - if (input.length === 0) { - return Buffer.from(output); - } - - let count = 1; - let current = input[0]; - - for (let i = 1; i < input.length; i++) { - const next = input[i]; - - if (next === current && count < 255) { - count++; - continue; - } - - output.push(count, current); - - count = 1; - current = next; - } - - output.push(count, current); - - return Buffer.from(output); -} - -function copyNumber(target: number[], value: number, offset: number, end: number) { - for (let i = offset; i < end; i++) { - target[i] = value; - } -} - -// runLengthDecode decodes a run-length encoding for the input array. -// -// The input is a binary stream of 2-byte pairs (first byte is the length and the -// second byte is the binary representation of the object). The output is a list of -// uint8s. -// -// E.g. byte array [5, 0, 3, 2] is converted into an uint8 array like -// [0, 0, 0, 0, 0, 2, 2, 2]. -export function runLengthDecode(input: Buffer, outputSize?: number): number[] { - let size; - - if (typeof outputSize === 'undefined') { - size = 0; - for (let i = 0; i < input.length; i += 2) { - size += input[i]; - } - } else { - size = outputSize; - } - - const output: number[] = new Array(size); - - let idx = 0; - for (let i = 0; i < input.length; i += 2) { - for (let j = 0; j < input[i]; j++) { - output[idx] = input[i + 1]; - idx++; - } - } - - // 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. - copyNumber(output, 0, idx, size); - - return output; -} - -// runLengthDecodeBase64Url decodes a run-length encoding for the -// base64-encoded input string. -// -// The input is a base64-encoded string. The output is a list of uint8s. -// -// E.g. string 'BQADAg' is converted into an uint8 array like -// [0, 0, 0, 0, 0, 2, 2, 2]. -// -// The motivating intent for this method is to unpack a base64-encoded -// run-length encoding without using intermediate storage. -// -// This method relies on these assumptions and details: -// - array encoded using run-length and base64 always returns string of length -// 0, 3, or 6 (mod 8) -// - since original array is composed of uint8s, we ignore Unicode codepoints -// - JavaScript bitwise operators operate on 32-bits so decoding must be done -// in 32-bit chunks - -/* eslint no-bitwise: ["error", { "allow": ["<<", ">>", ">>=", "&", "|"] }] */ -export function runLengthDecodeBase64Url(input: string, size: number, capacity: number): number[] { - const output = new Array(capacity); - const multipleOf8 = Math.floor(size / 8); - const remainder = size % 8; - - let n = 0; - let count = 0; - let value = 0; - let i = 0; - let j = 0; - - for (i = 0; i < multipleOf8 * 8; i += 8) { - n = - (charCodeAt(input, i) << 26) | - (charCodeAt(input, i + 1) << 20) | - (charCodeAt(input, i + 2) << 14) | - (charCodeAt(input, i + 3) << 8) | - (charCodeAt(input, i + 4) << 2) | - (charCodeAt(input, i + 5) >> 4); - - count = (n >> 24) & 0xff; - value = (n >> 16) & 0xff; - - copyNumber(output, value, j, j + count); - j += count; - - count = (n >> 8) & 0xff; - value = n & 0xff; - - copyNumber(output, value, j, j + count); - j += count; - - n = - ((charCodeAt(input, i + 5) & 0xf) << 12) | - (charCodeAt(input, i + 6) << 6) | - charCodeAt(input, i + 7); - - count = (n >> 8) & 0xff; - value = n & 0xff; - - copyNumber(output, value, j, j + count); - j += count; - } - - if (remainder === 6) { - n = - (charCodeAt(input, i) << 26) | - (charCodeAt(input, i + 1) << 20) | - (charCodeAt(input, i + 2) << 14) | - (charCodeAt(input, i + 3) << 8) | - (charCodeAt(input, i + 4) << 2) | - (charCodeAt(input, i + 5) >> 4); - - count = (n >> 24) & 0xff; - value = (n >> 16) & 0xff; - - copyNumber(output, value, j, j + count); - j += count; - - count = (n >> 8) & 0xff; - value = n & 0xff; - - copyNumber(output, value, j, j + count); - j += count; - } else if (remainder === 3) { - n = (charCodeAt(input, i) << 12) | (charCodeAt(input, i + 1) << 6) | charCodeAt(input, i + 2); - n >>= 2; - - count = (n >> 8) & 0xff; - value = n & 0xff; - - copyNumber(output, value, j, j + count); - j += count; - } - - // 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. - copyNumber(output, 0, j, capacity); - - return output; -} diff --git a/x-pack/plugins/profiling/server/routes/stacktrace.test.ts b/x-pack/plugins/profiling/server/routes/stacktrace.test.ts deleted file mode 100644 index 48a2feda70e0f..0000000000000 --- a/x-pack/plugins/profiling/server/routes/stacktrace.test.ts +++ /dev/null @@ -1,88 +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 { createStackFrameID, StackTrace } from '@kbn/profiling-data-access-plugin/common/profiling'; -import { runLengthEncode } from '../../common/run_length_encoding'; -import { decodeStackTrace, EncodedStackTrace } from './stacktrace'; - -enum fileID { - A = 'aQpJmTLWydNvOapSFZOwKg', - B = 'hz_u-HGyrN6qeIk6UIJeCA', - C = 'AJ8qrcXSoJbl_haPhlc4og', - D = 'lHZiv7a58px6Gumcpo-6yA', - E = 'fkbxUTZgljnk71ZMnqJnyA', - F = 'gnEsgxvvEODj6iFYMQWYlA', -} - -enum addressOrLine { - A = 515512, - B = 26278522, - C = 6712518, - D = 105806025, - E = 111, - F = 106182663, - G = 100965370, -} - -const frameID: Record = { - A: createStackFrameID(fileID.A, addressOrLine.A), - B: createStackFrameID(fileID.B, addressOrLine.B), - C: createStackFrameID(fileID.C, addressOrLine.C), - D: createStackFrameID(fileID.D, addressOrLine.D), - E: createStackFrameID(fileID.E, addressOrLine.E), - F: createStackFrameID(fileID.F, addressOrLine.F), - G: createStackFrameID(fileID.F, addressOrLine.G), -}; - -const frameTypeA = [0, 0, 0]; -const frameTypeB = [8, 8, 8, 8]; - -describe('Stack trace operations', () => { - test('decodeStackTrace', () => { - const tests: Array<{ - original: EncodedStackTrace; - expected: StackTrace; - }> = [ - { - original: { - Stacktrace: { - frame: { - ids: frameID.A + frameID.B + frameID.C, - types: runLengthEncode(frameTypeA).toString('base64url'), - }, - }, - } as EncodedStackTrace, - expected: { - FrameIDs: [frameID.A, frameID.B, frameID.C], - FileIDs: [fileID.A, fileID.B, fileID.C], - AddressOrLines: [addressOrLine.A, addressOrLine.B, addressOrLine.C], - Types: frameTypeA, - } as StackTrace, - }, - { - original: { - Stacktrace: { - frame: { - ids: frameID.D + frameID.E + frameID.F + frameID.G, - types: runLengthEncode(frameTypeB).toString('base64url'), - }, - }, - } as EncodedStackTrace, - expected: { - FrameIDs: [frameID.D, frameID.E, frameID.F, frameID.G], - FileIDs: [fileID.D, fileID.E, fileID.F, fileID.F], - AddressOrLines: [addressOrLine.D, addressOrLine.E, addressOrLine.F, addressOrLine.G], - Types: frameTypeB, - } as StackTrace, - }, - ]; - - for (const t of tests) { - expect(decodeStackTrace(t.original)).toEqual(t.expected); - } - }); -}); diff --git a/x-pack/plugins/profiling/server/routes/stacktrace.ts b/x-pack/plugins/profiling/server/routes/stacktrace.ts deleted file mode 100644 index 96af265358078..0000000000000 --- a/x-pack/plugins/profiling/server/routes/stacktrace.ts +++ /dev/null @@ -1,74 +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 { - DedotObject, - ProfilingESField, -} from '@kbn/profiling-data-access-plugin/common/elasticsearch'; -import { - getAddressFromStackFrameID, - getFileIDFromStackFrameID, - StackTrace, -} from '@kbn/profiling-data-access-plugin/common/profiling'; -import { runLengthDecodeBase64Url } from '../../common/run_length_encoding'; - -const BASE64_FRAME_ID_LENGTH = 32; - -export type EncodedStackTrace = DedotObject<{ - // This field is a base64-encoded byte string. The string represents a - // serialized list of frame IDs in which the order of frames are - // reversed to allow for prefix compression (leaf frame last). Each - // frame ID is composed of two concatenated values: a 16-byte file ID - // and an 8-byte address or line number (depending on the context of - // the downstream reader). - // - // Frame ID #1 Frame ID #2 - // +----------------+--------+----------------+--------+---- - // | File ID | Addr | File ID | Addr | - // +----------------+--------+----------------+--------+---- - [ProfilingESField.StacktraceFrameIDs]: string; - - // This field is a run-length encoding of a list of uint8s. The order is - // reversed from the original input. - [ProfilingESField.StacktraceFrameTypes]: string; -}>; - -// decodeStackTrace unpacks an encoded stack trace from Elasticsearch -export function decodeStackTrace(input: EncodedStackTrace): StackTrace { - const inputFrameIDs = input.Stacktrace.frame.ids; - const inputFrameTypes = input.Stacktrace.frame.types; - const countsFrameIDs = inputFrameIDs.length / BASE64_FRAME_ID_LENGTH; - - const fileIDs: string[] = new Array(countsFrameIDs); - const frameIDs: string[] = new Array(countsFrameIDs); - const addressOrLines: number[] = new Array(countsFrameIDs); - - // Step 1: Convert the base64-encoded frameID list into two separate - // lists (frame IDs and file IDs), both of which are also base64-encoded. - // - // To get the frame ID, we grab the next 32 bytes. - // - // To get the file ID, we grab the first 22 bytes of the frame ID. - // However, since the file ID is base64-encoded using 21.33 bytes - // (16 * 4 / 3), then the 22 bytes have an extra 4 bits from the - // address (see diagram in definition of EncodedStackTrace). - for (let i = 0, pos = 0; i < countsFrameIDs; i++, pos += BASE64_FRAME_ID_LENGTH) { - const frameID = inputFrameIDs.slice(pos, pos + BASE64_FRAME_ID_LENGTH); - frameIDs[i] = frameID; - fileIDs[i] = getFileIDFromStackFrameID(frameID); - addressOrLines[i] = getAddressFromStackFrameID(frameID); - } - - // Step 2: Convert the run-length byte encoding into a list of uint8s. - const typeIDs = runLengthDecodeBase64Url(inputFrameTypes, inputFrameTypes.length, countsFrameIDs); - - return { - AddressOrLines: addressOrLines, - FileIDs: fileIDs, - FrameIDs: frameIDs, - Types: typeIDs, - } as StackTrace; -} diff --git a/x-pack/plugins/profiling_data_access/common/base64.ts b/x-pack/plugins/profiling_data_access/common/base64.ts deleted file mode 100644 index 0d724c142271a..0000000000000 --- a/x-pack/plugins/profiling_data_access/common/base64.ts +++ /dev/null @@ -1,22 +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. - */ - -export const safeBase64Decoder = [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, - 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, - 25, 0, 0, 0, 0, 63, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, - 45, 46, 47, 48, 49, 50, 51, 0, 0, 0, 0, 0, -]; - -export const safeBase64Encoder = - 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234456789-_'; - -/* eslint no-bitwise: ["error", { "allow": ["&"] }] */ -export function charCodeAt(input: string, i: number): number { - return safeBase64Decoder[input.charCodeAt(i) & 0x7f]; -} diff --git a/x-pack/plugins/profiling_data_access/common/profiling.test.ts b/x-pack/plugins/profiling_data_access/common/profiling.test.ts index 5115055ad3c94..24c898bf1cfbe 100644 --- a/x-pack/plugins/profiling_data_access/common/profiling.test.ts +++ b/x-pack/plugins/profiling_data_access/common/profiling.test.ts @@ -6,26 +6,15 @@ */ import { - createStackFrameID, createStackFrameMetadata, FrameSymbolStatus, FrameType, - getAddressFromStackFrameID, getCalleeFunction, getCalleeSource, - getFileIDFromStackFrameID, getFrameSymbolStatus, getLanguageType, } from './profiling'; -describe('Stack frame operations', () => { - test('decode stack frame ID', () => { - const frameID = createStackFrameID('ABCDEFGHIJKLMNOPQRSTUw', 123456789); - expect(getAddressFromStackFrameID(frameID)).toEqual(123456789); - expect(getFileIDFromStackFrameID(frameID)).toEqual('ABCDEFGHIJKLMNOPQRSTUw'); - }); -}); - describe('Stack frame metadata operations', () => { test('metadata has executable and function names', () => { const metadata = createStackFrameMetadata({ diff --git a/x-pack/plugins/profiling_data_access/common/profiling.ts b/x-pack/plugins/profiling_data_access/common/profiling.ts index 22480a26f8ab2..c6f72f20629d3 100644 --- a/x-pack/plugins/profiling_data_access/common/profiling.ts +++ b/x-pack/plugins/profiling_data_access/common/profiling.ts @@ -5,50 +5,10 @@ * 2.0. */ -import { charCodeAt, safeBase64Encoder } from './base64'; - export type StackTraceID = string; export type StackFrameID = string; export type FileID = string; -export function createStackFrameID(fileID: FileID, addressOrLine: number): StackFrameID { - const buf = Buffer.alloc(24); - Buffer.from(fileID, 'base64url').copy(buf); - buf.writeBigUInt64BE(BigInt(addressOrLine), 16); - return buf.toString('base64url'); -} - -/* eslint no-bitwise: ["error", { "allow": ["&"] }] */ -export function getFileIDFromStackFrameID(frameID: StackFrameID): FileID { - return frameID.slice(0, 21) + safeBase64Encoder[frameID.charCodeAt(21) & 0x30]; -} - -/* eslint no-bitwise: ["error", { "allow": ["<<=", "&"] }] */ -export function getAddressFromStackFrameID(frameID: StackFrameID): number { - let address = charCodeAt(frameID, 21) & 0xf; - address <<= 6; - address += charCodeAt(frameID, 22); - address <<= 6; - address += charCodeAt(frameID, 23); - address <<= 6; - address += charCodeAt(frameID, 24); - address <<= 6; - address += charCodeAt(frameID, 25); - address <<= 6; - address += charCodeAt(frameID, 26); - address <<= 6; - address += charCodeAt(frameID, 27); - address <<= 6; - address += charCodeAt(frameID, 28); - address <<= 6; - address += charCodeAt(frameID, 29); - address <<= 6; - address += charCodeAt(frameID, 30); - address <<= 6; - address += charCodeAt(frameID, 31); - return address; -} - export enum FrameType { Unsymbolized = 0, Python,