mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Profiling-APM] Service Profiling flamegraph (#165360)
- Move files from profiling-data-access-plugin to a new Kibana pkg @kbn/profiling-utils - Create a Profling flamegraph embeddable component in the Profiling plugin - Create a Profiling flamegraph embeddable client in the Observability-shared plugin - Create a Profiling tab in APM (it's only visible when kibana setting is enabled and Profiling has been initialized) - This PR has not yet removed the Profiling dependency from the APM plugin. For that, I need to refactor some parts on Profiling side and move some logic to the data access plugin. This will be done on another PR. **How plugins can use the Profiling Flamegraph** 1. Call [profilingDataAccessStart.services.fetchFlamechartData](https://github.com/elastic/kibana/blob/main/x-pack/plugins/profiling_data_access/server/services/fetch_flamechart/index.ts#L22), it returns an [ElasticFlameGraph](https://github.com/elastic/kibana/blob/main/x-pack/plugins/profiling_data_access/common/flamegraph.ts#L74). 2. Render the [EmbeddableFlamegraph](https://github.com/elastic/kibana/pull/165360/files#diff-fb9763ef775d15950acb682abf7447259c3feae74fab413d4e1a14fdcc401351R21) component passing the data received.2aa3d1b6
-3649-4e58-a088-11890a09feec --- <img width="885" alt="Screenshot 2023-09-05 at 09 41 11" src="dc65f870
-c4f6-4654-8cdd-8e2cd8e97b00"> --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Joseph Crail <joseph.crail@elastic.co>
This commit is contained in:
parent
6cb937a37a
commit
a9e882d18b
100 changed files with 1283 additions and 361 deletions
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
@ -553,6 +553,7 @@ 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
|
||||
packages/kbn-profiling-utils @elastic/profiling-ui
|
||||
x-pack/packages/kbn-random-sampling @elastic/kibana-visualizations
|
||||
packages/kbn-react-field @elastic/kibana-data-discovery
|
||||
packages/react/kibana_context/common @elastic/appex-sharedux
|
||||
|
|
|
@ -564,6 +564,7 @@
|
|||
"@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/profiling-utils": "link:packages/kbn-profiling-utils",
|
||||
"@kbn/random-sampling": "link:x-pack/packages/kbn-random-sampling",
|
||||
"@kbn/react-field": "link:packages/kbn-react-field",
|
||||
"@kbn/react-kibana-context-common": "link:packages/react/kibana_context/common",
|
||||
|
|
|
@ -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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { StackTraceResponse } from '../stack_traces';
|
||||
|
||||
import stackTraces1x from './stacktraces_60s_1x.json';
|
||||
import stackTraces5x from './stacktraces_3600s_5x.json';
|
||||
import stackTraces125x from './stacktraces_86400s_125x.json';
|
||||
import stackTraces625x from './stacktraces_604800s_625x.json';
|
||||
|
||||
export const stackTraceFixtures: Array<{
|
||||
response: StackTraceResponse;
|
||||
seconds: number;
|
||||
upsampledBy: number;
|
||||
}> = [
|
||||
{ response: stackTraces1x, seconds: 60, upsampledBy: 1 },
|
||||
{ response: stackTraces5x, seconds: 3600, upsampledBy: 5 },
|
||||
{ response: stackTraces125x, seconds: 86400, upsampledBy: 125 },
|
||||
{ response: stackTraces625x, seconds: 604800, upsampledBy: 625 },
|
||||
];
|
|
@ -1,15 +1,15 @@
|
|||
/*
|
||||
* 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.
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { sum } from 'lodash';
|
||||
|
||||
import { createCalleeTree } from './callee';
|
||||
import { decodeStackTraceResponse } from './stack_traces';
|
||||
|
||||
import { stackTraceFixtures } from './__fixtures__/stacktraces';
|
||||
|
||||
describe('Callee operations', () => {
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { createFrameGroupID, FrameGroupID } from './frame_group';
|
||||
|
@ -20,24 +21,48 @@ import {
|
|||
|
||||
type NodeID = number;
|
||||
|
||||
/**
|
||||
* Callee tree
|
||||
*/
|
||||
export interface CalleeTree {
|
||||
/** size */
|
||||
Size: number;
|
||||
/** edges */
|
||||
Edges: Array<Map<FrameGroupID, NodeID>>;
|
||||
|
||||
/** file ids */
|
||||
FileID: string[];
|
||||
/** frame types */
|
||||
FrameType: number[];
|
||||
/** inlines */
|
||||
Inline: boolean[];
|
||||
/** executable file names */
|
||||
ExeFilename: string[];
|
||||
/** address or lines */
|
||||
AddressOrLine: number[];
|
||||
/** function names */
|
||||
FunctionName: string[];
|
||||
/** function offsets */
|
||||
FunctionOffset: number[];
|
||||
/** source file names */
|
||||
SourceFilename: string[];
|
||||
/** source lines */
|
||||
SourceLine: number[];
|
||||
|
||||
/** total cpu */
|
||||
CountInclusive: number[];
|
||||
/** self cpu */
|
||||
CountExclusive: number[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a callee tree
|
||||
* @param events Map<StackTraceID, number>
|
||||
* @param stackTraces Map<StackTraceID, StackTrace>
|
||||
* @param stackFrames Map<StackFrameID, StackFrame>
|
||||
* @param executables Map<FileID, Executable>
|
||||
* @param totalFrames number
|
||||
* @param samplingRate number
|
||||
* @returns
|
||||
*/
|
||||
export function createCalleeTree(
|
||||
events: Map<StackTraceID, number>,
|
||||
stackTraces: Map<StackTraceID, StackTrace>,
|
|
@ -1,12 +1,16 @@
|
|||
/*
|
||||
* 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.
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { UnionToIntersection, ValuesType } from 'utility-types';
|
||||
|
||||
/**
|
||||
* Profiling Elasticsearch fields
|
||||
*/
|
||||
export enum ProfilingESField {
|
||||
Timestamp = '@timestamp',
|
||||
ContainerName = 'container.name',
|
|
@ -1,14 +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.
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { createCalleeTree } from './callee';
|
||||
import { createBaseFlameGraph, createFlameGraph } from './flamegraph';
|
||||
import { decodeStackTraceResponse } from './stack_traces';
|
||||
|
||||
import { stackTraceFixtures } from './__fixtures__/stacktraces';
|
||||
|
||||
describe('Flamegraph operations', () => {
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { CalleeTree } from './callee';
|
||||
|
@ -10,28 +11,49 @@ import { createFrameGroupID } from './frame_group';
|
|||
import { fnv1a64 } from './hash';
|
||||
import { createStackFrameMetadata, getCalleeLabel } from './profiling';
|
||||
|
||||
/**
|
||||
* Base Flamegraph
|
||||
*/
|
||||
export interface BaseFlameGraph {
|
||||
/** size */
|
||||
Size: number;
|
||||
/** edges */
|
||||
Edges: number[][];
|
||||
|
||||
/** file ids */
|
||||
FileID: string[];
|
||||
/** frame types */
|
||||
FrameType: number[];
|
||||
/** inlines */
|
||||
Inline: boolean[];
|
||||
/** executable file names */
|
||||
ExeFilename: string[];
|
||||
/** address or line */
|
||||
AddressOrLine: number[];
|
||||
/** function names */
|
||||
FunctionName: string[];
|
||||
/** function offsets */
|
||||
FunctionOffset: number[];
|
||||
/** source file names */
|
||||
SourceFilename: string[];
|
||||
/** source lines */
|
||||
SourceLine: number[];
|
||||
|
||||
/** total cpu */
|
||||
CountInclusive: number[];
|
||||
/** self cpu */
|
||||
CountExclusive: number[];
|
||||
|
||||
/** total seconds */
|
||||
TotalSeconds: number;
|
||||
/** sampling rate */
|
||||
SamplingRate: number;
|
||||
}
|
||||
|
||||
// createBaseFlameGraph encapsulates the tree representation into a serialized form.
|
||||
/**
|
||||
* createBaseFlameGraph encapsulates the tree representation into a serialized form.
|
||||
* @param tree CalleeTree
|
||||
* @param samplingRate number
|
||||
* @param totalSeconds number
|
||||
* @returns BaseFlameGraph
|
||||
*/
|
||||
export function createBaseFlameGraph(
|
||||
tree: CalleeTree,
|
||||
samplingRate: number,
|
||||
|
@ -71,14 +93,22 @@ export function createBaseFlameGraph(
|
|||
return graph;
|
||||
}
|
||||
|
||||
/** Elasticsearch flamegraph */
|
||||
export interface ElasticFlameGraph extends BaseFlameGraph {
|
||||
/** ID */
|
||||
ID: string[];
|
||||
/** Label */
|
||||
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).
|
||||
/**
|
||||
*
|
||||
* 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).
|
||||
* @param base BaseFlameGraph
|
||||
* @returns ElasticFlameGraph
|
||||
*/
|
||||
export function createFlameGraph(base: BaseFlameGraph): ElasticFlameGraph {
|
||||
const graph: ElasticFlameGraph = {
|
||||
Size: base.Size,
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { createFrameGroupID } from './frame_group';
|
|
@ -1,24 +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.
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { takeRight } from 'lodash';
|
||||
import { StackFrameMetadata } from './profiling';
|
||||
|
||||
/** Frame group ID */
|
||||
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.
|
||||
/**
|
||||
*
|
||||
* 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.
|
||||
* @param fileID string
|
||||
* @param addressOrLine string
|
||||
* @param exeFilename string
|
||||
* @param sourceFilename string
|
||||
* @param functionName string
|
||||
* @returns FrameGroupID
|
||||
*/
|
||||
export function createFrameGroupID(
|
||||
fileID: StackFrameMetadata['FileID'],
|
||||
addressOrLine: StackFrameMetadata['AddressOrLine'],
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { fnv1a64 } from './hash';
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
// prettier-ignore
|
||||
|
@ -25,23 +26,24 @@ const lowerHex = [
|
|||
'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": ["^=", ">>", "&"] }] */
|
||||
|
||||
/**
|
||||
* - 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.
|
||||
* @param bytes Uint8Array
|
||||
* @returns string
|
||||
*/
|
||||
export function fnv1a64(bytes: Uint8Array): string {
|
||||
const n = bytes.length;
|
||||
let [h0, h1, h2, h3] = [0x2325, 0x8422, 0x9ce4, 0xcbf2];
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import {
|
|
@ -1,14 +1,27 @@
|
|||
/*
|
||||
* 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.
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Stacktrace ID
|
||||
*/
|
||||
export type StackTraceID = string;
|
||||
/**
|
||||
* StackFrame ID
|
||||
*/
|
||||
export type StackFrameID = string;
|
||||
/**
|
||||
* File ID
|
||||
*/
|
||||
export type FileID = string;
|
||||
|
||||
/**
|
||||
* Frame type
|
||||
*/
|
||||
export enum FrameType {
|
||||
Unsymbolized = 0,
|
||||
Python,
|
||||
|
@ -35,94 +48,134 @@ const frameTypeDescriptions = {
|
|||
[FrameType.PHPJIT]: 'PHP JIT',
|
||||
};
|
||||
|
||||
/**
|
||||
* get frame type name
|
||||
* @param ft FrameType
|
||||
* @returns string
|
||||
*/
|
||||
export function describeFrameType(ft: FrameType): string {
|
||||
return frameTypeDescriptions[ft];
|
||||
}
|
||||
|
||||
export interface StackTraceEvent {
|
||||
/** stacktrace ID */
|
||||
StackTraceID: StackTraceID;
|
||||
/** count */
|
||||
Count: number;
|
||||
}
|
||||
|
||||
/** Stack trace */
|
||||
export interface StackTrace {
|
||||
/** frame ids */
|
||||
FrameIDs: string[];
|
||||
/** file ids */
|
||||
FileIDs: string[];
|
||||
/** address or lines */
|
||||
AddressOrLines: number[];
|
||||
/** types */
|
||||
Types: number[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty stack trace
|
||||
*/
|
||||
export const emptyStackTrace: StackTrace = {
|
||||
/** Frame IDs */
|
||||
FrameIDs: [],
|
||||
/** File IDs */
|
||||
FileIDs: [],
|
||||
/** Address or lines */
|
||||
AddressOrLines: [],
|
||||
/** Types */
|
||||
Types: [],
|
||||
};
|
||||
|
||||
/** Stack frame */
|
||||
export interface StackFrame {
|
||||
/** file name */
|
||||
FileName: string;
|
||||
/** function name */
|
||||
FunctionName: string;
|
||||
/** function offset */
|
||||
FunctionOffset: number;
|
||||
/** line number */
|
||||
LineNumber: number;
|
||||
/** inline */
|
||||
Inline: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty stack frame
|
||||
*/
|
||||
export const emptyStackFrame: StackFrame = {
|
||||
/** File name */
|
||||
FileName: '',
|
||||
/** Function name */
|
||||
FunctionName: '',
|
||||
/** Function offset */
|
||||
FunctionOffset: 0,
|
||||
/** Line number */
|
||||
LineNumber: 0,
|
||||
/** Inline */
|
||||
Inline: false,
|
||||
};
|
||||
|
||||
/** Executable */
|
||||
export interface Executable {
|
||||
/** file name */
|
||||
FileName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty exectutable
|
||||
*/
|
||||
export const emptyExecutable: Executable = {
|
||||
/** file name */
|
||||
FileName: '',
|
||||
};
|
||||
|
||||
/** Stack frame metadata */
|
||||
export interface StackFrameMetadata {
|
||||
// StackTrace.FrameID
|
||||
/** StackTrace.FrameID */
|
||||
FrameID: string;
|
||||
// StackTrace.FileID
|
||||
/** StackTrace.FileID */
|
||||
FileID: FileID;
|
||||
// StackTrace.Type
|
||||
/** StackTrace.Type */
|
||||
FrameType: FrameType;
|
||||
// StackFrame.Inline
|
||||
/** StackFrame.Inline */
|
||||
Inline: boolean;
|
||||
|
||||
// StackTrace.AddressOrLine
|
||||
/** StackTrace.AddressOrLine */
|
||||
AddressOrLine: number;
|
||||
// StackFrame.FunctionName
|
||||
/** StackFrame.FunctionName */
|
||||
FunctionName: string;
|
||||
// StackFrame.FunctionOffset
|
||||
/** StackFrame.FunctionOffset */
|
||||
FunctionOffset: number;
|
||||
// should this be StackFrame.SourceID?
|
||||
/** should this be StackFrame.SourceID? */
|
||||
SourceID: FileID;
|
||||
// StackFrame.Filename
|
||||
/** StackFrame.Filename */
|
||||
SourceFilename: string;
|
||||
// StackFrame.LineNumber
|
||||
/** StackFrame.LineNumber */
|
||||
SourceLine: number;
|
||||
// auto-generated - see createStackFrameMetadata
|
||||
/** auto-generated - see createStackFrameMetadata */
|
||||
FunctionSourceLine: number;
|
||||
|
||||
// Executable.FileName
|
||||
/** Executable.FileName */
|
||||
ExeFileName: string;
|
||||
|
||||
// unused atm due to lack of symbolization metadata
|
||||
/** unused atm due to lack of symbolization metadata */
|
||||
CommitHash: string;
|
||||
// unused atm due to lack of symbolization metadata
|
||||
/** unused atm due to lack of symbolization metadata */
|
||||
SourceCodeURL: string;
|
||||
// unused atm due to lack of symbolization metadata
|
||||
/** unused atm due to lack of symbolization metadata */
|
||||
SourcePackageHash: string;
|
||||
// unused atm due to lack of symbolization metadata
|
||||
/** unused atm due to lack of symbolization metadata */
|
||||
SourcePackageURL: string;
|
||||
// unused atm due to lack of symbolization metadata
|
||||
|
||||
/** unused atm due to lack of symbolization metadata */
|
||||
SamplingRate: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* create stackframe metadata
|
||||
* @param options Partial<StackFrameMetadata>
|
||||
* @returns StackFrameMetadata
|
||||
*/
|
||||
export function createStackFrameMetadata(
|
||||
options: Partial<StackFrameMetadata> = {}
|
||||
): StackFrameMetadata {
|
||||
|
@ -182,6 +235,11 @@ function getExeFileName(metadata: StackFrameMetadata) {
|
|||
return describeFrameType(metadata.FrameType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get callee label
|
||||
* @param metadata StackFrameMetadata
|
||||
* @returns string
|
||||
*/
|
||||
export function getCalleeLabel(metadata: StackFrameMetadata) {
|
||||
if (metadata.FunctionName !== '') {
|
||||
const sourceFilename = metadata.SourceFilename;
|
||||
|
@ -192,7 +250,11 @@ export function getCalleeLabel(metadata: StackFrameMetadata) {
|
|||
}
|
||||
return getExeFileName(metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get callee function name
|
||||
* @param frame StackFrameMetadata
|
||||
* @returns string
|
||||
*/
|
||||
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
|
||||
|
@ -202,20 +264,32 @@ export function getCalleeFunction(frame: StackFrameMetadata): string {
|
|||
// When there is no function name, only use the executable name
|
||||
return frame.FunctionName ? exeDisplayName + ': ' + frame.FunctionName : exeDisplayName;
|
||||
}
|
||||
/**
|
||||
* Frame symbol status
|
||||
*/
|
||||
export enum FrameSymbolStatus {
|
||||
PARTIALLY_SYMBOLYZED = 'PARTIALLY_SYMBOLYZED',
|
||||
NOT_SYMBOLIZED = 'NOT_SYMBOLIZED',
|
||||
SYMBOLIZED = 'SYMBOLIZED',
|
||||
}
|
||||
export function getFrameSymbolStatus({
|
||||
sourceFilename,
|
||||
sourceLine,
|
||||
exeFileName,
|
||||
}: {
|
||||
|
||||
/** Frame symbols status params */
|
||||
interface FrameSymbolStatusParams {
|
||||
/** source file name */
|
||||
sourceFilename: string;
|
||||
/** source file line */
|
||||
sourceLine: number;
|
||||
/** executable file name */
|
||||
exeFileName?: string;
|
||||
}) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get frame symbol status
|
||||
* @param param FrameSymbolStatusParams
|
||||
* @returns FrameSymbolStatus
|
||||
*/
|
||||
export function getFrameSymbolStatus(param: FrameSymbolStatusParams) {
|
||||
const { sourceFilename, sourceLine, exeFileName } = param;
|
||||
if (sourceFilename === '' && sourceLine === 0) {
|
||||
if (exeFileName) {
|
||||
return FrameSymbolStatus.PARTIALLY_SYMBOLYZED;
|
||||
|
@ -228,10 +302,28 @@ export function getFrameSymbolStatus({
|
|||
}
|
||||
|
||||
const nativeLanguages = [FrameType.Native, FrameType.Kernel];
|
||||
export function getLanguageType({ frameType }: { frameType: FrameType }) {
|
||||
return nativeLanguages.includes(frameType) ? 'NATIVE' : 'INTERPRETED';
|
||||
|
||||
interface LanguageTypeParams {
|
||||
/** frame type */
|
||||
frameType: FrameType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get language type
|
||||
* @param param LanguageTypeParams
|
||||
* @returns string
|
||||
*/
|
||||
export function getLanguageType(param: LanguageTypeParams) {
|
||||
return nativeLanguages.includes(param.frameType) ? 'NATIVE' : 'INTERPRETED';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get callee source information.
|
||||
* If we don't have the executable filename, display <unsymbolized>
|
||||
* If no source line or filename available, display the executable offset
|
||||
* @param frame StackFrameMetadata
|
||||
* @returns string
|
||||
*/
|
||||
export function getCalleeSource(frame: StackFrameMetadata): string {
|
||||
const frameSymbolStatus = getFrameSymbolStatus({
|
||||
sourceFilename: frame.SourceFilename,
|
||||
|
@ -254,6 +346,13 @@ export function getCalleeSource(frame: StackFrameMetadata): string {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Group stackframe by stack trace
|
||||
* @param stackTraces Map<StackTraceID, StackTrace>
|
||||
* @param stackFrames Map<StackFrameID, StackFrame>
|
||||
* @param executables Map<FileID, Executable>
|
||||
* @returns Record<string, StackFrameMetadata[]>
|
||||
*/
|
||||
export function groupStackFrameMetadataByStackTrace(
|
||||
stackTraces: Map<StackTraceID, StackTrace>,
|
||||
stackFrames: Map<StackFrameID, StackFrame>,
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import {
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ProfilingESField } from './elasticsearch';
|
||||
|
@ -15,13 +16,17 @@ import {
|
|||
StackTraceID,
|
||||
} from './profiling';
|
||||
|
||||
/** Profiling status response */
|
||||
export interface ProfilingStatusResponse {
|
||||
/** profiling enabled */
|
||||
profiling: {
|
||||
enabled: boolean;
|
||||
};
|
||||
/** resource management status*/
|
||||
resource_management: {
|
||||
enabled: boolean;
|
||||
};
|
||||
/** Indices creates / pre 8.9.1 data still available */
|
||||
resources: {
|
||||
created: boolean;
|
||||
pre_8_9_1_data: boolean;
|
||||
|
@ -58,24 +63,43 @@ interface ProfilingExecutables {
|
|||
[key: string]: string;
|
||||
}
|
||||
|
||||
/** Profiling stacktrace */
|
||||
export interface StackTraceResponse {
|
||||
/** stack trace events */
|
||||
['stack_trace_events']?: ProfilingEvents;
|
||||
/** stack traces */
|
||||
['stack_traces']?: ProfilingStackTraces;
|
||||
/** stack frames */
|
||||
['stack_frames']?: ProfilingStackFrames;
|
||||
/** executables */
|
||||
['executables']?: ProfilingExecutables;
|
||||
/** total frames */
|
||||
['total_frames']: number;
|
||||
/** sampling rate */
|
||||
['sampling_rate']: number;
|
||||
}
|
||||
|
||||
/** Decoded stack trace response */
|
||||
export interface DecodedStackTraceResponse {
|
||||
/** Map of Stacktrace ID and event */
|
||||
events: Map<StackTraceID, number>;
|
||||
/** Map of stacktrace ID and stacktrace */
|
||||
stackTraces: Map<StackTraceID, StackTrace>;
|
||||
/** Map of stackframe ID and stackframe */
|
||||
stackFrames: Map<StackFrameID, StackFrame>;
|
||||
/** Map of file ID and Executables */
|
||||
executables: Map<FileID, Executable>;
|
||||
/** Total number of frames */
|
||||
totalFrames: number;
|
||||
/** sampling rate */
|
||||
samplingRate: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Frame ID
|
||||
* @param frameID string
|
||||
* @param n number
|
||||
* @returns string
|
||||
*/
|
||||
export const makeFrameID = (frameID: string, n: number): string => {
|
||||
return n === 0 ? frameID : frameID + ';' + n.toString();
|
||||
};
|
||||
|
@ -119,6 +143,11 @@ const createInlineTrace = (
|
|||
} as StackTrace;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decodes stack trace response
|
||||
* @param response StackTraceResponse
|
||||
* @returns DecodedStackTraceResponse
|
||||
*/
|
||||
export function decodeStackTraceResponse(response: StackTraceResponse): DecodedStackTraceResponse {
|
||||
const stackTraceEvents: Map<StackTraceID, number> = new Map();
|
||||
for (const [key, value] of Object.entries(response.stack_trace_events ?? {})) {
|
||||
|
@ -165,11 +194,17 @@ export function decodeStackTraceResponse(response: StackTraceResponse): DecodedS
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Stacktraces options
|
||||
*/
|
||||
export enum StackTracesDisplayOption {
|
||||
StackTraces = 'stackTraces',
|
||||
Percentage = 'percentage',
|
||||
}
|
||||
|
||||
/**
|
||||
* Functions TopN types definition
|
||||
*/
|
||||
export enum TopNType {
|
||||
Containers = 'containers',
|
||||
Deployments = 'deployments',
|
||||
|
@ -178,6 +213,11 @@ export enum TopNType {
|
|||
Traces = 'traces',
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Profiling ES field based on TopN Type
|
||||
* @param type TopNType
|
||||
* @returns string
|
||||
*/
|
||||
export function getFieldNameForTopNType(type: TopNType): string {
|
||||
return {
|
||||
[TopNType.Containers]: ProfilingESField.ContainerName,
|
46
packages/kbn-profiling-utils/index.ts
Normal file
46
packages/kbn-profiling-utils/index.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { decodeStackTraceResponse } from './common/stack_traces';
|
||||
export { createBaseFlameGraph, createFlameGraph } from './common/flamegraph';
|
||||
export { createCalleeTree } from './common/callee';
|
||||
export { ProfilingESField } from './common/elasticsearch';
|
||||
export {
|
||||
groupStackFrameMetadataByStackTrace,
|
||||
describeFrameType,
|
||||
FrameType,
|
||||
getCalleeFunction,
|
||||
getCalleeSource,
|
||||
getLanguageType,
|
||||
FrameSymbolStatus,
|
||||
getFrameSymbolStatus,
|
||||
createStackFrameMetadata,
|
||||
emptyExecutable,
|
||||
emptyStackFrame,
|
||||
emptyStackTrace,
|
||||
} from './common/profiling';
|
||||
export { getFieldNameForTopNType, TopNType, StackTracesDisplayOption } from './common/stack_traces';
|
||||
export { createFrameGroupID } from './common/frame_group';
|
||||
|
||||
export type { CalleeTree } from './common/callee';
|
||||
export type {
|
||||
ProfilingStatusResponse,
|
||||
StackTraceResponse,
|
||||
DecodedStackTraceResponse,
|
||||
} from './common/stack_traces';
|
||||
export type { ElasticFlameGraph, BaseFlameGraph } from './common/flamegraph';
|
||||
export type { FrameGroupID } from './common/frame_group';
|
||||
export type {
|
||||
Executable,
|
||||
FileID,
|
||||
StackFrame,
|
||||
StackFrameID,
|
||||
StackFrameMetadata,
|
||||
StackTrace,
|
||||
StackTraceID,
|
||||
} from './common/profiling';
|
13
packages/kbn-profiling-utils/jest.config.js
Normal file
13
packages/kbn-profiling-utils/jest.config.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test',
|
||||
rootDir: '../..',
|
||||
roots: ['<rootDir>/packages/kbn-profiling-utils'],
|
||||
};
|
5
packages/kbn-profiling-utils/kibana.jsonc
Normal file
5
packages/kbn-profiling-utils/kibana.jsonc
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"type": "shared-common",
|
||||
"id": "@kbn/profiling-utils",
|
||||
"owner": "@elastic/profiling-ui"
|
||||
}
|
6
packages/kbn-profiling-utils/package.json
Normal file
6
packages/kbn-profiling-utils/package.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "@kbn/profiling-utils",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "SSPL-1.0 OR Elastic License 2.0"
|
||||
}
|
18
packages/kbn-profiling-utils/tsconfig.json
Normal file
18
packages/kbn-profiling-utils/tsconfig.json
Normal file
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": [
|
||||
"jest",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.json",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*"
|
||||
],
|
||||
"kbn_references": []
|
||||
}
|
|
@ -1100,6 +1100,8 @@
|
|||
"@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/profiling-utils": ["packages/kbn-profiling-utils"],
|
||||
"@kbn/profiling-utils/*": ["packages/kbn-profiling-utils/*"],
|
||||
"@kbn/random-sampling": ["x-pack/packages/kbn-random-sampling"],
|
||||
"@kbn/random-sampling/*": ["x-pack/packages/kbn-random-sampling/*"],
|
||||
"@kbn/react-field": ["packages/kbn-react-field"],
|
||||
|
|
|
@ -49,7 +49,8 @@
|
|||
"usageCollection",
|
||||
"customIntegrations", // Move this to requiredPlugins after completely migrating from the Tutorials Home App
|
||||
"licenseManagement",
|
||||
"profiling"
|
||||
"profiling",
|
||||
"profilingDataAccess"
|
||||
],
|
||||
"requiredBundles": [
|
||||
"advancedSettings",
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* 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 { EmbeddableFlamegraph } from '@kbn/observability-shared-plugin/public';
|
||||
import React from 'react';
|
||||
import { useApmParams } from '../../../hooks/use_apm_params';
|
||||
import { isPending, useFetcher } from '../../../hooks/use_fetcher';
|
||||
import { useProfilingPlugin } from '../../../hooks/use_profiling_plugin';
|
||||
import { useTimeRange } from '../../../hooks/use_time_range';
|
||||
import { ApmDocumentType } from '../../../../common/document_type';
|
||||
import { usePreferredDataSourceAndBucketSize } from '../../../hooks/use_preferred_data_source_and_bucket_size';
|
||||
|
||||
export function ProfilingOverview() {
|
||||
const {
|
||||
path: { serviceName },
|
||||
query: { kuery, rangeFrom, rangeTo, environment },
|
||||
} = useApmParams('/services/{serviceName}/profiling');
|
||||
const { isProfilingAvailable } = useProfilingPlugin();
|
||||
|
||||
const { start, end } = useTimeRange({ rangeFrom, rangeTo });
|
||||
const preferred = usePreferredDataSourceAndBucketSize({
|
||||
start,
|
||||
end,
|
||||
kuery,
|
||||
type: ApmDocumentType.TransactionMetric,
|
||||
numBuckets: 20,
|
||||
});
|
||||
const { data, status } = useFetcher(
|
||||
(callApmApi) => {
|
||||
if (isProfilingAvailable && preferred) {
|
||||
return callApmApi(
|
||||
'GET /internal/apm/services/{serviceName}/profiling/flamegraph',
|
||||
{
|
||||
params: {
|
||||
path: { serviceName },
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
kuery,
|
||||
environment,
|
||||
documentType: preferred.source.documentType,
|
||||
rollupInterval: preferred.source.rollupInterval,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
[
|
||||
isProfilingAvailable,
|
||||
preferred,
|
||||
serviceName,
|
||||
start,
|
||||
end,
|
||||
kuery,
|
||||
environment,
|
||||
]
|
||||
);
|
||||
|
||||
if (!isProfilingAvailable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<EmbeddableFlamegraph
|
||||
data={data}
|
||||
height="60vh"
|
||||
isLoading={isPending(status)}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -37,6 +37,8 @@ import { TransactionOverview } from '../../app/transaction_overview';
|
|||
import { ApmServiceTemplate } from '../templates/apm_service_template';
|
||||
import { ApmServiceWrapper } from './apm_service_wrapper';
|
||||
import { RedirectToDefaultServiceRouteView } from './redirect_to_default_service_route_view';
|
||||
import { ProfilingOverview } from '../../app/profiling_overview';
|
||||
import { SearchBar } from '../../shared/search_bar/search_bar';
|
||||
|
||||
function page({
|
||||
title,
|
||||
|
@ -47,12 +49,7 @@ function page({
|
|||
title: string;
|
||||
tab: React.ComponentProps<typeof ApmServiceTemplate>['selectedTab'];
|
||||
element: React.ReactElement<any, any>;
|
||||
searchBarOptions?: {
|
||||
showUnifiedSearchBar?: boolean;
|
||||
showTransactionTypeSelector?: boolean;
|
||||
showTimeComparison?: boolean;
|
||||
hidden?: boolean;
|
||||
};
|
||||
searchBarOptions?: React.ComponentProps<typeof SearchBar>;
|
||||
}): {
|
||||
element: React.ReactElement<any, any>;
|
||||
} {
|
||||
|
@ -365,6 +362,20 @@ export const serviceDetailRoute = {
|
|||
}),
|
||||
}),
|
||||
},
|
||||
'/services/{serviceName}/profiling': {
|
||||
...page({
|
||||
tab: 'profiling',
|
||||
title: i18n.translate('xpack.apm.views.profiling.title', {
|
||||
defaultMessage: 'Universal Profiling',
|
||||
}),
|
||||
element: <ProfilingOverview />,
|
||||
searchBarOptions: {
|
||||
showTimeComparison: false,
|
||||
showTransactionTypeSelector: false,
|
||||
showQueryInput: false,
|
||||
},
|
||||
}),
|
||||
},
|
||||
'/services/{serviceName}/': {
|
||||
element: <RedirectToDefaultServiceRouteView />,
|
||||
},
|
||||
|
|
|
@ -48,6 +48,7 @@ import { AnalyzeDataButton } from './analyze_data_button';
|
|||
import { ServerlessType } from '../../../../../common/serverless';
|
||||
import { useApmFeatureFlag } from '../../../../hooks/use_apm_feature_flag';
|
||||
import { ApmFeatureFlagName } from '../../../../../common/apm_feature_flags';
|
||||
import { useProfilingPlugin } from '../../../../hooks/use_profiling_plugin';
|
||||
|
||||
type Tab = NonNullable<EuiPageHeaderProps['tabs']>[0] & {
|
||||
key:
|
||||
|
@ -60,7 +61,8 @@ type Tab = NonNullable<EuiPageHeaderProps['tabs']>[0] & {
|
|||
| 'infrastructure'
|
||||
| 'service-map'
|
||||
| 'logs'
|
||||
| 'alerts';
|
||||
| 'alerts'
|
||||
| 'profiling';
|
||||
hidden?: boolean;
|
||||
};
|
||||
|
||||
|
@ -215,6 +217,7 @@ function useTabs({ selectedTab }: { selectedTab: Tab['key'] }) {
|
|||
plugins,
|
||||
capabilities
|
||||
);
|
||||
const { isProfilingAvailable } = useProfilingPlugin();
|
||||
|
||||
const router = useApmRouter();
|
||||
const isInfraTabAvailable = useApmFeatureFlag(
|
||||
|
@ -391,6 +394,24 @@ function useTabs({ selectedTab }: { selectedTab: Tab['key'] }) {
|
|||
}),
|
||||
hidden: !(isAlertingAvailable && canReadAlerts),
|
||||
},
|
||||
{
|
||||
key: 'profiling',
|
||||
href: router.link('/services/{serviceName}/profiling', {
|
||||
path: { serviceName },
|
||||
query,
|
||||
}),
|
||||
label: i18n.translate('xpack.apm.home.profilingTabLabel', {
|
||||
defaultMessage: 'Universal Profiling',
|
||||
}),
|
||||
hidden: !isProfilingAvailable,
|
||||
append: (
|
||||
<EuiBadge color="accent">
|
||||
{i18n.translate('xpack.apm.universalProfiling.newLabel', {
|
||||
defaultMessage: 'New',
|
||||
})}
|
||||
</EuiBadge>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return tabs
|
||||
|
|
|
@ -23,6 +23,7 @@ interface Props {
|
|||
hidden?: boolean;
|
||||
showUnifiedSearchBar?: boolean;
|
||||
showTimeComparison?: boolean;
|
||||
showQueryInput?: boolean;
|
||||
showTransactionTypeSelector?: boolean;
|
||||
searchBarPlaceholder?: string;
|
||||
searchBarBoolFilter?: QueryDslQueryContainer[];
|
||||
|
@ -33,6 +34,7 @@ export function SearchBar({
|
|||
showUnifiedSearchBar = true,
|
||||
showTimeComparison = false,
|
||||
showTransactionTypeSelector = false,
|
||||
showQueryInput = true,
|
||||
searchBarPlaceholder,
|
||||
searchBarBoolFilter,
|
||||
}: Props) {
|
||||
|
@ -72,6 +74,7 @@ export function SearchBar({
|
|||
<UnifiedSearchBar
|
||||
placeholder={searchBarPlaceholder}
|
||||
boolFilter={searchBarBoolFilter}
|
||||
showQueryInput={showQueryInput}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
|
|
|
@ -123,6 +123,7 @@ export function UnifiedSearchBar({
|
|||
placeholder,
|
||||
value,
|
||||
showDatePicker = true,
|
||||
showQueryInput = true,
|
||||
showSubmitButton = true,
|
||||
isClearable = true,
|
||||
boolFilter,
|
||||
|
@ -130,6 +131,7 @@ export function UnifiedSearchBar({
|
|||
placeholder?: string;
|
||||
value?: string;
|
||||
showDatePicker?: boolean;
|
||||
showQueryInput?: boolean;
|
||||
showSubmitButton?: boolean;
|
||||
isClearable?: boolean;
|
||||
boolFilter?: QueryDslQueryContainer[];
|
||||
|
@ -303,7 +305,7 @@ export function UnifiedSearchBar({
|
|||
placeholder={searchbarPlaceholder}
|
||||
useDefaultBehaviors={true}
|
||||
indexPatterns={dataView ? [dataView] : undefined}
|
||||
showQueryInput={true}
|
||||
showQueryInput={showQueryInput}
|
||||
showQueryMenu={false}
|
||||
showFilterBar={false}
|
||||
showDatePicker={showDatePicker}
|
||||
|
|
|
@ -31,12 +31,15 @@ export function useProfilingPlugin() {
|
|||
fetchIsProfilingSetup();
|
||||
}, [plugins.profiling]);
|
||||
|
||||
const isProfilingAvailable =
|
||||
isProfilingIntegrationEnabled && isProfilingPluginInitialized;
|
||||
|
||||
return {
|
||||
isProfilingPluginInitialized,
|
||||
profilingLocators:
|
||||
isProfilingIntegrationEnabled && isProfilingPluginInitialized
|
||||
? plugins.profiling?.locators
|
||||
: undefined,
|
||||
profilingLocators: isProfilingAvailable
|
||||
? plugins.profiling?.locators
|
||||
: undefined,
|
||||
isProfilingIntegrationEnabled,
|
||||
isProfilingAvailable,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@ import { timeRangeMetadataRoute } from '../time_range_metadata/route';
|
|||
import { traceRouteRepository } from '../traces/route';
|
||||
import { transactionRouteRepository } from '../transactions/route';
|
||||
import { assistantRouteRepository } from '../assistant_functions/route';
|
||||
import { profilingRouteRepository } from '../profiling/route';
|
||||
|
||||
function getTypedGlobalApmServerRouteRepository() {
|
||||
const repository = {
|
||||
|
@ -83,6 +84,7 @@ function getTypedGlobalApmServerRouteRepository() {
|
|||
...mobileRouteRepository,
|
||||
...diagnosticsRepository,
|
||||
...assistantRouteRepository,
|
||||
...profilingRouteRepository,
|
||||
};
|
||||
|
||||
return repository;
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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 { kqlQuery, rangeQuery } from '@kbn/observability-plugin/server';
|
||||
import { ApmServiceTransactionDocumentType } from '../../../common/document_type';
|
||||
import { HOST_HOSTNAME, SERVICE_NAME } from '../../../common/es_fields/apm';
|
||||
import { RollupInterval } from '../../../common/rollup';
|
||||
import { environmentQuery } from '../../../common/utils/environment_query';
|
||||
import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client';
|
||||
|
||||
export async function getServiceHostNames({
|
||||
apmEventClient,
|
||||
serviceName,
|
||||
start,
|
||||
end,
|
||||
environment,
|
||||
kuery,
|
||||
documentType,
|
||||
rollupInterval,
|
||||
}: {
|
||||
environment: string;
|
||||
kuery: string;
|
||||
serviceName: string;
|
||||
start: number;
|
||||
end: number;
|
||||
apmEventClient: APMEventClient;
|
||||
documentType: ApmServiceTransactionDocumentType;
|
||||
rollupInterval: RollupInterval;
|
||||
}) {
|
||||
const response = await apmEventClient.search('get_service_host_names', {
|
||||
apm: {
|
||||
sources: [{ documentType, rollupInterval }],
|
||||
},
|
||||
body: {
|
||||
track_total_hits: false,
|
||||
size: 0,
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{ term: { [SERVICE_NAME]: serviceName } },
|
||||
...rangeQuery(start, end),
|
||||
...environmentQuery(environment),
|
||||
...kqlQuery(kuery),
|
||||
],
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
hostNames: {
|
||||
terms: {
|
||||
field: HOST_HOSTNAME,
|
||||
size: 500,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
response.aggregations?.hostNames.buckets.map(
|
||||
(bucket) => bucket.key as string
|
||||
) || []
|
||||
);
|
||||
}
|
71
x-pack/plugins/apm/server/routes/profiling/route.ts
Normal file
71
x-pack/plugins/apm/server/routes/profiling/route.ts
Normal file
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import type { BaseFlameGraph } from '@kbn/profiling-utils';
|
||||
import { createApmServerRoute } from '../apm_routes/create_apm_server_route';
|
||||
import {
|
||||
environmentRt,
|
||||
kueryRt,
|
||||
rangeRt,
|
||||
serviceTransactionDataSourceRt,
|
||||
} from '../default_api_types';
|
||||
import { getApmEventClient } from '../../lib/helpers/get_apm_event_client';
|
||||
import { getServiceHostNames } from './get_service_host_names';
|
||||
import { hostNamesToKuery } from './utils';
|
||||
|
||||
const profilingFlamegraphRoute = createApmServerRoute({
|
||||
endpoint: 'GET /internal/apm/services/{serviceName}/profiling/flamegraph',
|
||||
params: t.type({
|
||||
path: t.type({ serviceName: t.string }),
|
||||
query: t.intersection([
|
||||
rangeRt,
|
||||
kueryRt,
|
||||
environmentRt,
|
||||
serviceTransactionDataSourceRt,
|
||||
]),
|
||||
}),
|
||||
options: { tags: ['access:apm'] },
|
||||
handler: async (resources): Promise<BaseFlameGraph | undefined> => {
|
||||
const { context, plugins, params } = resources;
|
||||
const [esClient, apmEventClient, profilingDataAccessStart] =
|
||||
await Promise.all([
|
||||
(await context.core).elasticsearch.client,
|
||||
await getApmEventClient(resources),
|
||||
await plugins.profilingDataAccess?.start(),
|
||||
]);
|
||||
if (profilingDataAccessStart) {
|
||||
const { start, end, kuery, environment, documentType, rollupInterval } =
|
||||
params.query;
|
||||
const { serviceName } = params.path;
|
||||
|
||||
const serviceHostNames = await getServiceHostNames({
|
||||
apmEventClient,
|
||||
start,
|
||||
end,
|
||||
kuery,
|
||||
environment,
|
||||
serviceName,
|
||||
documentType,
|
||||
rollupInterval,
|
||||
});
|
||||
|
||||
return profilingDataAccessStart?.services.fetchFlamechartData({
|
||||
esClient: esClient.asCurrentUser,
|
||||
rangeFromMs: start,
|
||||
rangeToMs: end,
|
||||
kuery: hostNamesToKuery(serviceHostNames),
|
||||
});
|
||||
}
|
||||
|
||||
return undefined;
|
||||
},
|
||||
});
|
||||
|
||||
export const profilingRouteRepository = {
|
||||
...profilingFlamegraphRoute,
|
||||
};
|
26
x-pack/plugins/apm/server/routes/profiling/utils.test.ts
Normal file
26
x-pack/plugins/apm/server/routes/profiling/utils.test.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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 { HOST_NAME } from '../../../common/es_fields/apm';
|
||||
import { hostNamesToKuery } from './utils';
|
||||
|
||||
describe('profiling utils', () => {
|
||||
describe('hostNamesToKuery', () => {
|
||||
it('returns a single hostname', () => {
|
||||
expect(hostNamesToKuery(['foo'])).toEqual(`${HOST_NAME} : "foo"`);
|
||||
});
|
||||
|
||||
it('returns multiple hostnames', () => {
|
||||
expect(hostNamesToKuery(['foo', 'bar', 'baz'])).toEqual(
|
||||
`${HOST_NAME} : "foo" OR ${HOST_NAME} : "bar" OR ${HOST_NAME} : "baz"`
|
||||
);
|
||||
});
|
||||
|
||||
it('return empty string when no hostname', () => {
|
||||
expect(hostNamesToKuery([])).toEqual('');
|
||||
});
|
||||
});
|
||||
});
|
19
x-pack/plugins/apm/server/routes/profiling/utils.ts
Normal file
19
x-pack/plugins/apm/server/routes/profiling/utils.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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 { isEmpty } from 'lodash';
|
||||
import { HOST_NAME } from '../../../common/es_fields/apm';
|
||||
|
||||
export function hostNamesToKuery(hostNames: string[]) {
|
||||
return hostNames.reduce<string>((acc, hostName) => {
|
||||
if (isEmpty(acc)) {
|
||||
return `${HOST_NAME} : "${hostName}"`;
|
||||
}
|
||||
|
||||
return `${acc} OR ${HOST_NAME} : "${hostName}"`;
|
||||
}, '');
|
||||
}
|
|
@ -61,6 +61,10 @@ import {
|
|||
CustomIntegrationsPluginSetup,
|
||||
CustomIntegrationsPluginStart,
|
||||
} from '@kbn/custom-integrations-plugin/server';
|
||||
import {
|
||||
ProfilingDataAccessPluginSetup,
|
||||
ProfilingDataAccessPluginStart,
|
||||
} from '@kbn/profiling-data-access-plugin/server';
|
||||
import { APMConfig } from '.';
|
||||
|
||||
export interface APMPluginSetup {
|
||||
|
@ -91,6 +95,7 @@ export interface APMPluginSetupDependencies {
|
|||
taskManager?: TaskManagerSetupContract;
|
||||
usageCollection?: UsageCollectionSetup;
|
||||
customIntegrations?: CustomIntegrationsPluginSetup;
|
||||
profilingDataAccess?: ProfilingDataAccessPluginSetup;
|
||||
}
|
||||
export interface APMPluginStartDependencies {
|
||||
// required dependencies
|
||||
|
@ -116,4 +121,5 @@ export interface APMPluginStartDependencies {
|
|||
taskManager?: TaskManagerStartContract;
|
||||
usageCollection?: undefined;
|
||||
customIntegrations?: CustomIntegrationsPluginStart;
|
||||
profilingDataAccess?: ProfilingDataAccessPluginStart;
|
||||
}
|
||||
|
|
|
@ -96,6 +96,8 @@
|
|||
"@kbn/discover-plugin",
|
||||
"@kbn/observability-ai-assistant-plugin",
|
||||
"@kbn/apm-data-access-plugin",
|
||||
"@kbn/profiling-data-access-plugin",
|
||||
"@kbn/profiling-utils",
|
||||
"@kbn/core-analytics-server",
|
||||
"@kbn/analytics-client",
|
||||
"@kbn/monaco"
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
"server": false,
|
||||
"browser": true,
|
||||
"configPath": ["xpack", "observability_shared"],
|
||||
"requiredPlugins": ["cases", "guidedOnboarding", "uiActions"],
|
||||
"requiredPlugins": ["cases", "guidedOnboarding", "uiActions", "embeddable"],
|
||||
"optionalPlugins": [],
|
||||
"requiredBundles": ["data", "inspector", "kibanaReact", "kibanaUtils"],
|
||||
"extraPublicDirs": ["common"]
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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 React, { useEffect, useRef, useState } from 'react';
|
||||
import type { BaseFlameGraph } from '@kbn/profiling-utils';
|
||||
import { css } from '@emotion/react';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { ObservabilitySharedStart } from '../../../plugin';
|
||||
import { EMBEDDABLE_FLAMEGRAPH } from '.';
|
||||
|
||||
interface Props {
|
||||
data?: BaseFlameGraph;
|
||||
height?: string;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
export function EmbeddableFlamegraph({ data, height, isLoading }: Props) {
|
||||
const { embeddable: embeddablePlugin } = useKibana<ObservabilitySharedStart>().services;
|
||||
const [embeddable, setEmbeddable] = useState<any>();
|
||||
const embeddableRoot: React.RefObject<HTMLDivElement> = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
async function createEmbeddable() {
|
||||
const factory = embeddablePlugin?.getEmbeddableFactory(EMBEDDABLE_FLAMEGRAPH);
|
||||
const input = { id: 'embeddable_profiling', data, isLoading };
|
||||
const embeddableObject = await factory?.create(input);
|
||||
setEmbeddable(embeddableObject);
|
||||
}
|
||||
createEmbeddable();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (embeddableRoot.current && embeddable) {
|
||||
embeddable.render(embeddableRoot.current);
|
||||
}
|
||||
}, [embeddable, embeddableRoot]);
|
||||
|
||||
useEffect(() => {
|
||||
if (embeddable) {
|
||||
embeddable.updateInput({ data, isLoading });
|
||||
embeddable.reload();
|
||||
}
|
||||
}, [data, embeddable, isLoading]);
|
||||
|
||||
return (
|
||||
<div
|
||||
css={css`
|
||||
width: 100%;
|
||||
height: ${height};
|
||||
display: flex;
|
||||
flex: 1 1 100%;
|
||||
z-index: 1;
|
||||
min-height: 0;
|
||||
`}
|
||||
ref={embeddableRoot}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/** Profiling flamegraph embeddable key */
|
||||
export const EMBEDDABLE_FLAMEGRAPH = 'EMBEDDABLE_FLAMEGRAPH';
|
|
@ -77,3 +77,6 @@ export {
|
|||
casesFeatureId,
|
||||
sloFeatureId,
|
||||
} from '../common';
|
||||
|
||||
export { EMBEDDABLE_FLAMEGRAPH } from './components/profiling/embeddables';
|
||||
export { EmbeddableFlamegraph } from './components/profiling/embeddables/embeddable_flamegraph';
|
||||
|
|
|
@ -11,6 +11,7 @@ import type { CoreStart, Plugin } from '@kbn/core/public';
|
|||
import type { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public';
|
||||
import { CasesUiStart } from '@kbn/cases-plugin/public';
|
||||
import { SpacesPluginStart } from '@kbn/spaces-plugin/public';
|
||||
import type { EmbeddableStart } from '@kbn/embeddable-plugin/public';
|
||||
import { createNavigationRegistry } from './components/page_template/helpers/navigation_registry';
|
||||
import { createLazyObservabilityPageTemplate } from './components/page_template';
|
||||
import { updateGlobalNavigation } from './services/update_global_navigation';
|
||||
|
@ -20,6 +21,7 @@ export interface ObservabilitySharedStart {
|
|||
cases: CasesUiStart;
|
||||
guidedOnboarding: GuidedOnboardingPluginStart;
|
||||
setIsSidebarEnabled: (isEnabled: boolean) => void;
|
||||
embeddable: EmbeddableStart;
|
||||
}
|
||||
|
||||
export type ObservabilitySharedPluginSetup = ReturnType<ObservabilitySharedPlugin['setup']>;
|
||||
|
|
|
@ -32,6 +32,8 @@
|
|||
"@kbn/rison",
|
||||
"@kbn/kibana-utils-plugin",
|
||||
"@kbn/shared-ux-router",
|
||||
"@kbn/embeddable-plugin",
|
||||
"@kbn/profiling-utils"
|
||||
],
|
||||
"exclude": ["target/**/*"]
|
||||
}
|
||||
|
|
17
x-pack/plugins/profiling/common/__fixtures__/README.md
Normal file
17
x-pack/plugins/profiling/common/__fixtures__/README.md
Normal file
|
@ -0,0 +1,17 @@
|
|||
The stacktrace fixtures in this directory are originally from Elasticsearch's
|
||||
`POST /_profiling/stacktraces` endpoint. They were subsequently filtered
|
||||
through the `shrink_stacktrace_response.js` command in `x-pack/plugins/profiling/scripts/`
|
||||
to reduce the size without losing sampling fidelity (see the script for further
|
||||
details).
|
||||
|
||||
The naming convention for each stacktrace fixture follows this pattern:
|
||||
|
||||
```
|
||||
stacktraces_{seconds}s_{upsampling rate}x.json
|
||||
```
|
||||
|
||||
where `seconds` is the time span of the original query and `upsampling rate` is
|
||||
the reciprocal of the sampling rate returned from the original query.
|
||||
|
||||
To add a new stacktrace fixture to the test suite, update `stacktraces.ts`
|
||||
appropriately.
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { StackTraceResponse } from '../stack_traces';
|
||||
import type { StackTraceResponse } from '@kbn/profiling-utils';
|
||||
|
||||
import stackTraces1x from './stacktraces_60s_1x.json';
|
||||
import stackTraces5x from './stacktraces_3600s_5x.json';
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -5,16 +5,15 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { sum } from 'lodash';
|
||||
|
||||
import { createCalleeTree } from '@kbn/profiling-data-access-plugin/common/callee';
|
||||
import { createColumnarViewModel } from './columnar_view_model';
|
||||
import {
|
||||
createBaseFlameGraph,
|
||||
createCalleeTree,
|
||||
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';
|
||||
decodeStackTraceResponse,
|
||||
} from '@kbn/profiling-utils';
|
||||
import { sum } from 'lodash';
|
||||
import { createColumnarViewModel } from './columnar_view_model';
|
||||
import { stackTraceFixtures } from './__fixtures__/stacktraces';
|
||||
|
||||
describe('Columnar view model operations', () => {
|
||||
stackTraceFixtures.forEach(({ response, seconds, upsampledBy }) => {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { ColumnarViewModel } from '@elastic/charts';
|
||||
import { ElasticFlameGraph } from '@kbn/profiling-data-access-plugin/common/flamegraph';
|
||||
import type { ElasticFlameGraph } from '@kbn/profiling-utils';
|
||||
import { frameTypeToRGB, rgbToRGBA } from './frame_type_colors';
|
||||
|
||||
function normalize(n: number, lower: number, upper: number): number {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { FrameType } from '@kbn/profiling-data-access-plugin/common/profiling';
|
||||
import { FrameType } from '@kbn/profiling-utils';
|
||||
|
||||
/*
|
||||
* Helper to calculate the color of a given block to be drawn. The desirable outcomes of this are:
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
|
||||
import { sum } from 'lodash';
|
||||
import { createTopNFunctions } from './functions';
|
||||
import { decodeStackTraceResponse } from '@kbn/profiling-data-access-plugin/common/stack_traces';
|
||||
import { stackTraceFixtures } from '@kbn/profiling-data-access-plugin/common/__fixtures__/stacktraces';
|
||||
import { decodeStackTraceResponse } from '@kbn/profiling-utils';
|
||||
import { stackTraceFixtures } from './__fixtures__/stacktraces';
|
||||
|
||||
describe('TopN function operations', () => {
|
||||
stackTraceFixtures.forEach(({ response, seconds, upsampledBy }) => {
|
||||
|
|
|
@ -4,25 +4,25 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import * as t from 'io-ts';
|
||||
import { sumBy } from 'lodash';
|
||||
import {
|
||||
createFrameGroupID,
|
||||
FrameGroupID,
|
||||
} from '@kbn/profiling-data-access-plugin/common/frame_group';
|
||||
import {
|
||||
createStackFrameMetadata,
|
||||
emptyExecutable,
|
||||
emptyStackFrame,
|
||||
emptyStackTrace,
|
||||
import type {
|
||||
Executable,
|
||||
FileID,
|
||||
FrameGroupID,
|
||||
StackFrame,
|
||||
StackFrameID,
|
||||
StackFrameMetadata,
|
||||
StackTrace,
|
||||
StackTraceID,
|
||||
} from '@kbn/profiling-data-access-plugin/common/profiling';
|
||||
} from '@kbn/profiling-utils';
|
||||
import {
|
||||
createFrameGroupID,
|
||||
createStackFrameMetadata,
|
||||
emptyExecutable,
|
||||
emptyStackFrame,
|
||||
emptyStackTrace,
|
||||
} from '@kbn/profiling-utils';
|
||||
import * as t from 'io-ts';
|
||||
import { sumBy } from 'lodash';
|
||||
|
||||
interface TopNFunctionAndFrameGroup {
|
||||
Frame: StackFrameMetadata;
|
||||
|
|
|
@ -9,8 +9,8 @@ import { euiPaletteColorBlind } from '@elastic/eui';
|
|||
import { InferSearchResponseOf } from '@kbn/es-types';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { orderBy } from 'lodash';
|
||||
import { ProfilingESField } from '@kbn/profiling-data-access-plugin/common/elasticsearch';
|
||||
import { StackFrameMetadata } from '@kbn/profiling-data-access-plugin/common/profiling';
|
||||
import { ProfilingESField } from '@kbn/profiling-utils';
|
||||
import type { StackFrameMetadata } from '@kbn/profiling-utils';
|
||||
import { createUniformBucketsForTimeRange } from './histogram';
|
||||
|
||||
export const OTHER_BUCKET_LABEL = i18n.translate('xpack.profiling.topn.otherBucketLabel', {
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
"observabilityAIAssistant",
|
||||
"unifiedSearch",
|
||||
"share",
|
||||
"embeddable",
|
||||
"profilingDataAccess"
|
||||
],
|
||||
"requiredBundles": [
|
||||
|
|
|
@ -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 '@kbn/profiling-data-access-plugin/common/flamegraph';
|
||||
import type { ElasticFlameGraph } from '@kbn/profiling-utils';
|
||||
import { getFlamegraphModel } from '../../utils/get_flamegraph_model';
|
||||
import { FlameGraphLegend } from './flame_graph_legend';
|
||||
import { FrameInformationWindow } from '../frame_information_window';
|
||||
|
@ -34,10 +34,9 @@ interface Props {
|
|||
comparisonFlamegraph?: ElasticFlameGraph;
|
||||
baseline?: number;
|
||||
comparison?: number;
|
||||
showInformationWindow: boolean;
|
||||
toggleShowInformationWindow: () => void;
|
||||
searchText?: string;
|
||||
onChangeSearchText?: FlameSpec['onSearchTextChange'];
|
||||
isEmbedded?: boolean;
|
||||
}
|
||||
|
||||
export function FlameGraph({
|
||||
|
@ -47,11 +46,14 @@ export function FlameGraph({
|
|||
comparisonFlamegraph,
|
||||
baseline,
|
||||
comparison,
|
||||
showInformationWindow,
|
||||
toggleShowInformationWindow,
|
||||
searchText,
|
||||
onChangeSearchText,
|
||||
isEmbedded = false,
|
||||
}: Props) {
|
||||
const [showInformationWindow, setShowInformationWindow] = useState(false);
|
||||
function toggleShowInformationWindow() {
|
||||
setShowInformationWindow((prev) => !prev);
|
||||
}
|
||||
const theme = useEuiTheme();
|
||||
const trackProfilingEvent = useUiTracker({ app: 'profiling' });
|
||||
|
||||
|
@ -157,9 +159,7 @@ export function FlameGraph({
|
|||
comparisonScaleFactor={comparison}
|
||||
onShowMoreClick={() => {
|
||||
trackProfilingEvent({ metric: 'flamegraph_node_details_click' });
|
||||
if (!showInformationWindow) {
|
||||
toggleShowInformationWindow();
|
||||
}
|
||||
toggleShowInformationWindow();
|
||||
setHighlightedVmIndex(valueIndex);
|
||||
}}
|
||||
/>
|
||||
|
@ -194,6 +194,8 @@ export function FlameGraph({
|
|||
frame={selected}
|
||||
totalSeconds={primaryFlamegraph?.TotalSeconds ?? 0}
|
||||
totalSamples={totalSamples}
|
||||
showAIAssistant={!isEmbedded}
|
||||
showSymbolsStatus={!isEmbedded}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
ContextualInsight,
|
||||
Message,
|
||||
MessageRole,
|
||||
useObservabilityAIAssistant,
|
||||
} from '@kbn/observability-ai-assistant-plugin/public';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Frame } from '.';
|
||||
|
||||
interface Props {
|
||||
frame?: Frame;
|
||||
}
|
||||
|
||||
export function FrameInformationAIAssistant({ frame }: Props) {
|
||||
const aiAssistant = useObservabilityAIAssistant();
|
||||
|
||||
const promptMessages = useMemo<Message[] | undefined>(() => {
|
||||
if (frame?.functionName && frame.exeFileName) {
|
||||
const functionName = frame.functionName;
|
||||
const library = frame.exeFileName;
|
||||
|
||||
const now = new Date().toISOString();
|
||||
|
||||
return [
|
||||
{
|
||||
'@timestamp': now,
|
||||
message: {
|
||||
role: MessageRole.System,
|
||||
content: `You are perf-gpt, a helpful assistant for performance analysis and optimisation
|
||||
of software. Answer as concisely as possible.`,
|
||||
},
|
||||
},
|
||||
{
|
||||
'@timestamp': now,
|
||||
message: {
|
||||
role: MessageRole.User,
|
||||
content: `I am a software engineer. I am trying to understand what a function in a particular
|
||||
software library does.
|
||||
|
||||
The library is: ${library}
|
||||
The function is: ${functionName}
|
||||
|
||||
Your have two tasks. Your first task is to desribe what the library is and what its use cases are, and to
|
||||
describe what the function does. The output format should look as follows:
|
||||
|
||||
Library description: Provide a concise description of the library
|
||||
Library use-cases: Provide a concise description of what the library is typically used for.
|
||||
Function description: Provide a concise, technical, description of what the function does.
|
||||
|
||||
Assume the function ${functionName} from the library ${library} is consuming significant CPU resources.
|
||||
Your second task is to suggest ways to optimize or improve the system that involve the ${functionName} function from the
|
||||
${library} library. Types of improvements that would be useful to me are improvements that result in:
|
||||
|
||||
- Higher performance so that the system runs faster or uses less CPU
|
||||
- Better memory efficient so that the system uses less RAM
|
||||
- Better storage efficient so that the system stores less data on disk.
|
||||
- Better network I/O efficiency so that less data is sent over the network
|
||||
- Better disk I/O efficiency so that less data is read and written from disk
|
||||
|
||||
Make up to five suggestions. Your suggestions must meet all of the following criteria:
|
||||
1. Your suggestions should detailed, technical and include concrete examples.
|
||||
2. Your suggestions should be specific to improving performance of a system in which the ${functionName} function from
|
||||
the ${library} library is consuming significant CPU.
|
||||
3. If you suggest replacing the function or library with a more efficient replacement you must suggest at least
|
||||
one concrete replacement.
|
||||
|
||||
If you know of fewer than five ways to improve the performance of a system in which the ${functionName} function from the
|
||||
${library} library is consuming significant CPU, then provide fewer than five suggestions. If you do not know of any
|
||||
way in which to improve the performance then say "I do not know how to improve the performance of systems where
|
||||
this function is consuming a significant amount of CPU".
|
||||
|
||||
Do not suggest using a CPU profiler. I have already profiled my code. The profiler I used is Elastic Universal Profiler.
|
||||
If there is specific information I should look for in the profiler output then tell me what information to look for
|
||||
in the output of Elastic Universal Profiler.
|
||||
|
||||
You must not include URLs, web addresses or websites of any kind in your output.
|
||||
|
||||
If you have suggestions, the output format should look as follows:
|
||||
|
||||
Here are some suggestions as to how you might optimize your system if ${functionName} in ${library} is consuming
|
||||
significant CPU resources:
|
||||
1. Insert first suggestion
|
||||
2. Insert second suggestion`,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}, [frame?.functionName, frame?.exeFileName]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{aiAssistant.isEnabled() && promptMessages ? (
|
||||
<ContextualInsight
|
||||
messages={promptMessages}
|
||||
title={i18n.translate('xpack.profiling.frameInformationWindow.optimizeFunction', {
|
||||
defaultMessage: 'Optimize function',
|
||||
})}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { describeFrameType } from '@kbn/profiling-data-access-plugin/common/profiling';
|
||||
import { describeFrameType } from '@kbn/profiling-utils';
|
||||
import { NOT_AVAILABLE_LABEL } from '../../../common';
|
||||
|
||||
export function getInformationRows({
|
||||
|
|
|
@ -6,117 +6,42 @@
|
|||
*/
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiText, EuiTitle } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
ContextualInsight,
|
||||
Message,
|
||||
MessageRole,
|
||||
useObservabilityAIAssistant,
|
||||
} from '@kbn/observability-ai-assistant-plugin/public';
|
||||
import React, { useMemo } from 'react';
|
||||
import {
|
||||
FrameSymbolStatus,
|
||||
getFrameSymbolStatus,
|
||||
} from '@kbn/profiling-data-access-plugin/common/profiling';
|
||||
import { FrameSymbolStatus, getFrameSymbolStatus } from '@kbn/profiling-utils';
|
||||
import React from 'react';
|
||||
import { FrameInformationAIAssistant } from './frame_information_ai_assistant';
|
||||
import { FrameInformationPanel } from './frame_information_panel';
|
||||
import { getImpactRows } from './get_impact_rows';
|
||||
import { getInformationRows } from './get_information_rows';
|
||||
import { KeyValueList } from './key_value_list';
|
||||
import { MissingSymbolsCallout } from './missing_symbols_callout';
|
||||
|
||||
export interface Props {
|
||||
frame?: {
|
||||
fileID: string;
|
||||
frameType: number;
|
||||
exeFileName: string;
|
||||
addressOrLine: number;
|
||||
functionName: string;
|
||||
sourceFileName: string;
|
||||
sourceLine: number;
|
||||
countInclusive: number;
|
||||
countExclusive: number;
|
||||
};
|
||||
totalSamples: number;
|
||||
totalSeconds: number;
|
||||
export interface Frame {
|
||||
fileID: string;
|
||||
frameType: number;
|
||||
exeFileName: string;
|
||||
addressOrLine: number;
|
||||
functionName: string;
|
||||
sourceFileName: string;
|
||||
sourceLine: number;
|
||||
countInclusive: number;
|
||||
countExclusive: number;
|
||||
}
|
||||
|
||||
export function FrameInformationWindow({ frame, totalSamples, totalSeconds }: Props) {
|
||||
const aiAssistant = useObservabilityAIAssistant();
|
||||
|
||||
const promptMessages = useMemo<Message[] | undefined>(() => {
|
||||
if (frame?.functionName && frame.exeFileName) {
|
||||
const functionName = frame.functionName;
|
||||
const library = frame.exeFileName;
|
||||
|
||||
const now = new Date().toISOString();
|
||||
|
||||
return [
|
||||
{
|
||||
'@timestamp': now,
|
||||
message: {
|
||||
role: MessageRole.System,
|
||||
content: `You are perf-gpt, a helpful assistant for performance analysis and optimisation
|
||||
of software. Answer as concisely as possible.`,
|
||||
},
|
||||
},
|
||||
{
|
||||
'@timestamp': now,
|
||||
message: {
|
||||
role: MessageRole.User,
|
||||
content: `I am a software engineer. I am trying to understand what a function in a particular
|
||||
software library does.
|
||||
|
||||
The library is: ${library}
|
||||
The function is: ${functionName}
|
||||
|
||||
Your have two tasks. Your first task is to desribe what the library is and what its use cases are, and to
|
||||
describe what the function does. The output format should look as follows:
|
||||
|
||||
Library description: Provide a concise description of the library
|
||||
Library use-cases: Provide a concise description of what the library is typically used for.
|
||||
Function description: Provide a concise, technical, description of what the function does.
|
||||
|
||||
Assume the function ${functionName} from the library ${library} is consuming significant CPU resources.
|
||||
Your second task is to suggest ways to optimize or improve the system that involve the ${functionName} function from the
|
||||
${library} library. Types of improvements that would be useful to me are improvements that result in:
|
||||
|
||||
- Higher performance so that the system runs faster or uses less CPU
|
||||
- Better memory efficient so that the system uses less RAM
|
||||
- Better storage efficient so that the system stores less data on disk.
|
||||
- Better network I/O efficiency so that less data is sent over the network
|
||||
- Better disk I/O efficiency so that less data is read and written from disk
|
||||
|
||||
Make up to five suggestions. Your suggestions must meet all of the following criteria:
|
||||
1. Your suggestions should detailed, technical and include concrete examples.
|
||||
2. Your suggestions should be specific to improving performance of a system in which the ${functionName} function from
|
||||
the ${library} library is consuming significant CPU.
|
||||
3. If you suggest replacing the function or library with a more efficient replacement you must suggest at least
|
||||
one concrete replacement.
|
||||
|
||||
If you know of fewer than five ways to improve the performance of a system in which the ${functionName} function from the
|
||||
${library} library is consuming significant CPU, then provide fewer than five suggestions. If you do not know of any
|
||||
way in which to improve the performance then say "I do not know how to improve the performance of systems where
|
||||
this function is consuming a significant amount of CPU".
|
||||
|
||||
Do not suggest using a CPU profiler. I have already profiled my code. The profiler I used is Elastic Universal Profiler.
|
||||
If there is specific information I should look for in the profiler output then tell me what information to look for
|
||||
in the output of Elastic Universal Profiler.
|
||||
|
||||
You must not include URLs, web addresses or websites of any kind in your output.
|
||||
|
||||
If you have suggestions, the output format should look as follows:
|
||||
|
||||
Here are some suggestions as to how you might optimize your system if ${functionName} in ${library} is consuming
|
||||
significant CPU resources:
|
||||
1. Insert first suggestion
|
||||
2. Insert second suggestion`,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}, [frame?.functionName, frame?.exeFileName]);
|
||||
export interface Props {
|
||||
frame?: Frame;
|
||||
totalSamples: number;
|
||||
totalSeconds: number;
|
||||
showAIAssistant?: boolean;
|
||||
showSymbolsStatus?: boolean;
|
||||
}
|
||||
|
||||
export function FrameInformationWindow({
|
||||
frame,
|
||||
totalSamples,
|
||||
totalSeconds,
|
||||
showAIAssistant = true,
|
||||
showSymbolsStatus = true,
|
||||
}: Props) {
|
||||
if (!frame) {
|
||||
return (
|
||||
<FrameInformationPanel>
|
||||
|
@ -170,23 +95,16 @@ export function FrameInformationWindow({ frame, totalSamples, totalSeconds }: Pr
|
|||
<EuiFlexItem>
|
||||
<KeyValueList data-test-subj="informationRows" rows={informationRows} />
|
||||
</EuiFlexItem>
|
||||
{aiAssistant.isEnabled() && promptMessages ? (
|
||||
<>
|
||||
<EuiFlexItem>
|
||||
<ContextualInsight
|
||||
messages={promptMessages}
|
||||
title={i18n.translate('xpack.profiling.frameInformationWindow.optimizeFunction', {
|
||||
defaultMessage: 'Optimize function',
|
||||
})}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</>
|
||||
) : undefined}
|
||||
{symbolStatus !== FrameSymbolStatus.SYMBOLIZED && (
|
||||
{showAIAssistant ? (
|
||||
<EuiFlexItem>
|
||||
<FrameInformationAIAssistant frame={frame} />
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
{showSymbolsStatus && symbolStatus !== FrameSymbolStatus.SYMBOLIZED ? (
|
||||
<EuiFlexItem>
|
||||
<MissingSymbolsCallout frameType={frame.frameType} />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
) : null}
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup direction="column">
|
||||
<EuiFlexItem>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { Meta } from '@storybook/react';
|
||||
import React from 'react';
|
||||
import { FrameType } from '@kbn/profiling-data-access-plugin/common/profiling';
|
||||
import { FrameType } from '@kbn/profiling-utils';
|
||||
import { MockProfilingDependenciesStorybook } from '../contexts/profiling_dependencies/mock_profiling_dependencies_storybook';
|
||||
import { MissingSymbolsCallout } from './missing_symbols_callout';
|
||||
|
||||
|
|
|
@ -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 '@kbn/profiling-data-access-plugin/common/profiling';
|
||||
import { FrameType, getLanguageType } from '@kbn/profiling-utils';
|
||||
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';
|
||||
|
|
|
@ -6,11 +6,8 @@
|
|||
*/
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiText } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import {
|
||||
getCalleeFunction,
|
||||
getCalleeSource,
|
||||
StackFrameMetadata,
|
||||
} from '@kbn/profiling-data-access-plugin/common/profiling';
|
||||
import { getCalleeFunction, getCalleeSource } from '@kbn/profiling-utils';
|
||||
import type { StackFrameMetadata } from '@kbn/profiling-utils';
|
||||
|
||||
interface Props {
|
||||
frame: StackFrameMetadata;
|
||||
|
|
|
@ -32,7 +32,7 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { StackFrameMetadata } from '@kbn/profiling-data-access-plugin/common/profiling';
|
||||
import type { StackFrameMetadata } from '@kbn/profiling-utils';
|
||||
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';
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import { keyBy } from 'lodash';
|
||||
import { StackFrameMetadata } from '@kbn/profiling-data-access-plugin/common/profiling';
|
||||
import type { StackFrameMetadata } from '@kbn/profiling-utils';
|
||||
import { TopNFunctions } from '../../../common/functions';
|
||||
import { calculateImpactEstimates } from '../../../common/calculate_impact_estimates';
|
||||
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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 { EuiLoadingChart } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
|
||||
interface Props {
|
||||
isLoading: boolean;
|
||||
children: React.ReactElement;
|
||||
}
|
||||
|
||||
export function AsyncEmbeddableComponent({ children, isLoading }: Props) {
|
||||
return (
|
||||
<>
|
||||
{isLoading ? (
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<EuiLoadingChart size="xl" />
|
||||
</div>
|
||||
) : (
|
||||
<>{children}</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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 { Embeddable, EmbeddableOutput } from '@kbn/embeddable-plugin/public';
|
||||
import { EMBEDDABLE_FLAMEGRAPH } from '@kbn/observability-shared-plugin/public';
|
||||
import React from 'react';
|
||||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
import { createFlameGraph } from '@kbn/profiling-utils';
|
||||
import { FlameGraph } from '../../components/flamegraph';
|
||||
import { EmbeddableFlamegraphEmbeddableInput } from './embeddable_flamegraph_factory';
|
||||
import { AsyncEmbeddableComponent } from '../async_embeddable_component';
|
||||
|
||||
export class EmbeddableFlamegraph extends Embeddable<
|
||||
EmbeddableFlamegraphEmbeddableInput,
|
||||
EmbeddableOutput
|
||||
> {
|
||||
readonly type = EMBEDDABLE_FLAMEGRAPH;
|
||||
private _domNode?: HTMLElement;
|
||||
|
||||
render(domNode: HTMLElement) {
|
||||
this._domNode = domNode;
|
||||
const { data, isLoading } = this.input;
|
||||
const flamegraph = !isLoading && data ? createFlameGraph(data) : undefined;
|
||||
render(
|
||||
<AsyncEmbeddableComponent isLoading={isLoading}>
|
||||
<>
|
||||
{flamegraph && (
|
||||
<FlameGraph primaryFlamegraph={flamegraph} id="embddable_profiling" isEmbedded />
|
||||
)}
|
||||
</>
|
||||
</AsyncEmbeddableComponent>,
|
||||
domNode
|
||||
);
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
if (this._domNode) {
|
||||
unmountComponentAtNode(this._domNode);
|
||||
}
|
||||
}
|
||||
|
||||
reload() {
|
||||
if (this._domNode) {
|
||||
this.render(this._domNode);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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 {
|
||||
IContainer,
|
||||
EmbeddableInput,
|
||||
EmbeddableFactoryDefinition,
|
||||
} from '@kbn/embeddable-plugin/public';
|
||||
import type { BaseFlameGraph } from '@kbn/profiling-utils';
|
||||
import { EMBEDDABLE_FLAMEGRAPH } from '@kbn/observability-shared-plugin/public';
|
||||
|
||||
interface EmbeddableFlamegraphInput {
|
||||
data?: BaseFlameGraph;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
export type EmbeddableFlamegraphEmbeddableInput = EmbeddableFlamegraphInput & EmbeddableInput;
|
||||
|
||||
export class EmbeddableFlamegraphFactory
|
||||
implements EmbeddableFactoryDefinition<EmbeddableFlamegraphEmbeddableInput>
|
||||
{
|
||||
readonly type = EMBEDDABLE_FLAMEGRAPH;
|
||||
|
||||
async isEditable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
async create(input: EmbeddableFlamegraphEmbeddableInput, parent?: IContainer) {
|
||||
const { EmbeddableFlamegraph } = await import('./embeddable_flamegraph');
|
||||
return new EmbeddableFlamegraph(input, {}, parent);
|
||||
}
|
||||
|
||||
getDisplayName() {
|
||||
return 'Universal Profiling Flamegraph';
|
||||
}
|
||||
}
|
|
@ -16,11 +16,13 @@ import { i18n } from '@kbn/i18n';
|
|||
import type { NavigationSection } from '@kbn/observability-shared-plugin/public';
|
||||
import type { Location } from 'history';
|
||||
import { BehaviorSubject, combineLatest, from, map } from 'rxjs';
|
||||
import { EMBEDDABLE_FLAMEGRAPH } from '@kbn/observability-shared-plugin/public';
|
||||
import { FlamegraphLocatorDefinition } from './locators/flamegraph_locator';
|
||||
import { StacktracesLocatorDefinition } from './locators/stacktraces_locator';
|
||||
import { TopNFunctionsLocatorDefinition } from './locators/topn_functions_locator';
|
||||
import { getServices } from './services';
|
||||
import type { ProfilingPluginPublicSetupDeps, ProfilingPluginPublicStartDeps } from './types';
|
||||
import { EmbeddableFlamegraphFactory } from './embeddables/flamegraph/embeddable_flamegraph_factory';
|
||||
|
||||
export type ProfilingPluginSetup = ReturnType<ProfilingPlugin['setup']>;
|
||||
export type ProfilingPluginStart = void;
|
||||
|
@ -130,6 +132,11 @@ export class ProfilingPlugin implements Plugin {
|
|||
},
|
||||
});
|
||||
|
||||
pluginsSetup.embeddable.registerEmbeddableFactory(
|
||||
EMBEDDABLE_FLAMEGRAPH,
|
||||
new EmbeddableFlamegraphFactory()
|
||||
);
|
||||
|
||||
return {
|
||||
locators: {
|
||||
flamegraphLocator: pluginsSetup.share.url.locators.create(
|
||||
|
|
|
@ -9,10 +9,7 @@ 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 { StackTracesDisplayOption, TopNType } from '@kbn/profiling-utils';
|
||||
import { TopNFunctionSortField, topNFunctionSortFieldRt } from '../../common/functions';
|
||||
import {
|
||||
indexLifecyclePhaseRt,
|
||||
|
|
|
@ -6,10 +6,10 @@
|
|||
*/
|
||||
import { HttpFetchQuery } from '@kbn/core/public';
|
||||
import {
|
||||
BaseFlameGraph,
|
||||
createFlameGraph,
|
||||
ElasticFlameGraph,
|
||||
} from '@kbn/profiling-data-access-plugin/common/flamegraph';
|
||||
type BaseFlameGraph,
|
||||
type ElasticFlameGraph,
|
||||
} from '@kbn/profiling-utils';
|
||||
import { getRoutePaths } from '../common';
|
||||
import { TopNFunctions } from '../common/functions';
|
||||
import type {
|
||||
|
@ -106,6 +106,7 @@ export function getServices(): Services {
|
|||
timeTo,
|
||||
kuery,
|
||||
};
|
||||
|
||||
const baseFlamegraph = (await http.get(paths.Flamechart, { query })) as BaseFlameGraph;
|
||||
return createFlameGraph(baseFlamegraph);
|
||||
},
|
||||
|
|
|
@ -24,6 +24,7 @@ import {
|
|||
ObservabilityAIAssistantPluginSetup,
|
||||
ObservabilityAIAssistantPluginStart,
|
||||
} from '@kbn/observability-ai-assistant-plugin/public';
|
||||
import { EmbeddableSetup } from '@kbn/embeddable-plugin/public';
|
||||
|
||||
export interface ProfilingPluginPublicSetupDeps {
|
||||
observability: ObservabilityPublicSetup;
|
||||
|
@ -34,6 +35,7 @@ export interface ProfilingPluginPublicSetupDeps {
|
|||
charts: ChartsPluginSetup;
|
||||
licensing: LicensingPluginSetup;
|
||||
share: SharePluginSetup;
|
||||
embeddable: EmbeddableSetup;
|
||||
}
|
||||
|
||||
export interface ProfilingPluginPublicStartDeps {
|
||||
|
|
|
@ -8,8 +8,8 @@ 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 { describeFrameType, FrameType } from '@kbn/profiling-utils';
|
||||
import type { ElasticFlameGraph } from '@kbn/profiling-utils';
|
||||
import { createColumnarViewModel } from '../../../common/columnar_view_model';
|
||||
import { FRAME_TYPE_COLOR_MAP, rgbToRGBA } from '../../../common/frame_type_colors';
|
||||
import { ComparisonMode } from '../../components/normalization_menu';
|
||||
|
|
|
@ -5,14 +5,14 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui';
|
||||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
import { AsyncComponent } from '../../../components/async_component';
|
||||
import { useProfilingDependencies } from '../../../components/contexts/profiling_dependencies/use_profiling_dependencies';
|
||||
import { FlameGraph } from '../../../components/flamegraph';
|
||||
import { NormalizationMode, NormalizationOptions } from '../../../components/normalization_menu';
|
||||
import { useProfilingParams } from '../../../hooks/use_profiling_params';
|
||||
import { useProfilingRouter } from '../../../hooks/use_profiling_router';
|
||||
import { useProfilingRoutePath } from '../../../hooks/use_profiling_route_path';
|
||||
import { useProfilingRouter } from '../../../hooks/use_profiling_router';
|
||||
import { useTimeRange } from '../../../hooks/use_time_range';
|
||||
import { useTimeRangeAsync } from '../../../hooks/use_time_range_async';
|
||||
import { DifferentialFlameGraphSearchPanel } from './differential_flame_graph_search_panel';
|
||||
|
@ -36,7 +36,6 @@ export function DifferentialFlameGraphsView() {
|
|||
} = useProfilingParams('/flamegraphs/differential');
|
||||
const routePath = useProfilingRoutePath();
|
||||
const profilingRouter = useProfilingRouter();
|
||||
const [showInformationWindow, setShowInformationWindow] = useState(false);
|
||||
|
||||
const timeRange = useTimeRange({ rangeFrom, rangeTo });
|
||||
|
||||
|
@ -55,15 +54,15 @@ export function DifferentialFlameGraphsView() {
|
|||
return Promise.all([
|
||||
fetchElasticFlamechart({
|
||||
http,
|
||||
timeFrom: timeRange.inSeconds.start,
|
||||
timeTo: timeRange.inSeconds.end,
|
||||
timeFrom: new Date(timeRange.start).getTime(),
|
||||
timeTo: new Date(timeRange.end).getTime(),
|
||||
kuery,
|
||||
}),
|
||||
comparisonTimeRange.inSeconds.start && comparisonTimeRange.inSeconds.end
|
||||
comparisonTimeRange.start && comparisonTimeRange.end
|
||||
? fetchElasticFlamechart({
|
||||
http,
|
||||
timeFrom: comparisonTimeRange.inSeconds.start,
|
||||
timeTo: comparisonTimeRange.inSeconds.end,
|
||||
timeFrom: new Date(comparisonTimeRange.start).getTime(),
|
||||
timeTo: new Date(comparisonTimeRange.end).getTime(),
|
||||
kuery: comparisonKuery,
|
||||
})
|
||||
: Promise.resolve(undefined),
|
||||
|
@ -75,13 +74,13 @@ export function DifferentialFlameGraphsView() {
|
|||
});
|
||||
},
|
||||
[
|
||||
timeRange.inSeconds.start,
|
||||
timeRange.inSeconds.end,
|
||||
kuery,
|
||||
comparisonTimeRange.inSeconds.start,
|
||||
comparisonTimeRange.inSeconds.end,
|
||||
comparisonKuery,
|
||||
fetchElasticFlamechart,
|
||||
timeRange.start,
|
||||
timeRange.end,
|
||||
kuery,
|
||||
comparisonTimeRange.start,
|
||||
comparisonTimeRange.end,
|
||||
comparisonKuery,
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -105,10 +104,6 @@ export function DifferentialFlameGraphsView() {
|
|||
|
||||
const isNormalizedByTime = normalizationMode === NormalizationMode.Time;
|
||||
|
||||
function toggleShowInformationWindow() {
|
||||
setShowInformationWindow((prev) => !prev);
|
||||
}
|
||||
|
||||
function handleSearchTextChange(newSearchText: string) {
|
||||
// @ts-expect-error Code gets too complicated to satisfy TS constraints
|
||||
profilingRouter.push(routePath, { query: { ...query, searchText: newSearchText } });
|
||||
|
@ -134,8 +129,6 @@ export function DifferentialFlameGraphsView() {
|
|||
comparisonMode={comparisonMode}
|
||||
baseline={isNormalizedByTime ? baselineTime : baseline}
|
||||
comparison={isNormalizedByTime ? comparisonTime : comparison}
|
||||
showInformationWindow={showInformationWindow}
|
||||
toggleShowInformationWindow={toggleShowInformationWindow}
|
||||
searchText={searchText}
|
||||
onChangeSearchText={handleSearchTextChange}
|
||||
/>
|
||||
|
|
|
@ -5,13 +5,13 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
import { AsyncComponent } from '../../../components/async_component';
|
||||
import { useProfilingDependencies } from '../../../components/contexts/profiling_dependencies/use_profiling_dependencies';
|
||||
import { FlameGraph } from '../../../components/flamegraph';
|
||||
import { useProfilingParams } from '../../../hooks/use_profiling_params';
|
||||
import { useProfilingRouter } from '../../../hooks/use_profiling_router';
|
||||
import { useProfilingRoutePath } from '../../../hooks/use_profiling_route_path';
|
||||
import { useProfilingRouter } from '../../../hooks/use_profiling_router';
|
||||
import { useTimeRange } from '../../../hooks/use_time_range';
|
||||
import { useTimeRangeAsync } from '../../../hooks/use_time_range_async';
|
||||
|
||||
|
@ -31,12 +31,12 @@ export function FlameGraphView() {
|
|||
({ http }) => {
|
||||
return fetchElasticFlamechart({
|
||||
http,
|
||||
timeFrom: timeRange.inSeconds.start,
|
||||
timeTo: timeRange.inSeconds.end,
|
||||
timeFrom: new Date(timeRange.start).getTime(),
|
||||
timeTo: new Date(timeRange.end).getTime(),
|
||||
kuery,
|
||||
});
|
||||
},
|
||||
[timeRange.inSeconds.start, timeRange.inSeconds.end, kuery, fetchElasticFlamechart]
|
||||
[fetchElasticFlamechart, timeRange.start, timeRange.end, kuery]
|
||||
);
|
||||
|
||||
const { data } = state;
|
||||
|
@ -45,11 +45,6 @@ export function FlameGraphView() {
|
|||
|
||||
const profilingRouter = useProfilingRouter();
|
||||
|
||||
const [showInformationWindow, setShowInformationWindow] = useState(false);
|
||||
function toggleShowInformationWindow() {
|
||||
setShowInformationWindow((prev) => !prev);
|
||||
}
|
||||
|
||||
function handleSearchTextChange(newSearchText: string) {
|
||||
// @ts-expect-error Code gets too complicated to satisfy TS constraints
|
||||
profilingRouter.push(routePath, { query: { ...query, searchText: newSearchText } });
|
||||
|
@ -62,8 +57,6 @@ export function FlameGraphView() {
|
|||
<FlameGraph
|
||||
id="flamechart"
|
||||
primaryFlamegraph={data}
|
||||
showInformationWindow={showInformationWindow}
|
||||
toggleShowInformationWindow={toggleShowInformationWindow}
|
||||
searchText={searchText}
|
||||
onChangeSearchText={handleSearchTextChange}
|
||||
/>
|
||||
|
|
|
@ -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 '@kbn/profiling-data-access-plugin/common/stack_traces';
|
||||
import { TopNType } from '@kbn/profiling-utils';
|
||||
import { StatefulProfilingRouter } from '../../hooks/use_profiling_router';
|
||||
import { ProfilingRoutes } from '../../routing';
|
||||
|
||||
|
|
|
@ -7,10 +7,7 @@
|
|||
import { EuiButton, EuiButtonGroup, EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import {
|
||||
StackTracesDisplayOption,
|
||||
TopNType,
|
||||
} from '@kbn/profiling-data-access-plugin/common/stack_traces';
|
||||
import { StackTracesDisplayOption, TopNType } from '@kbn/profiling-utils';
|
||||
import { groupSamplesByCategory, TopNResponse } from '../../../common/topn';
|
||||
import { useProfilingParams } from '../../hooks/use_profiling_params';
|
||||
import { useProfilingRouter } from '../../hooks/use_profiling_router';
|
||||
|
|
|
@ -4,10 +4,7 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import {
|
||||
StackTracesDisplayOption,
|
||||
TopNType,
|
||||
} from '@kbn/profiling-data-access-plugin/common/stack_traces';
|
||||
import { StackTracesDisplayOption, TopNType } from '@kbn/profiling-utils';
|
||||
import { getTracesViewRouteParams } from './utils';
|
||||
|
||||
describe('stack traces view utils', () => {
|
||||
|
|
|
@ -6,10 +6,7 @@
|
|||
*/
|
||||
|
||||
import { TypeOf } from '@kbn/typed-react-router-config';
|
||||
import {
|
||||
getFieldNameForTopNType,
|
||||
TopNType,
|
||||
} from '@kbn/profiling-data-access-plugin/common/stack_traces';
|
||||
import { getFieldNameForTopNType, TopNType } from '@kbn/profiling-utils';
|
||||
import { ProfilingRoutes } from '../../routing';
|
||||
|
||||
export function getTracesViewRouteParams({
|
||||
|
|
|
@ -9,10 +9,7 @@ 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 '@kbn/profiling-data-access-plugin/common/stack_traces';
|
||||
import { StackTracesDisplayOption, TopNType } from '@kbn/profiling-utils';
|
||||
import { StorageExplorerSummaryAPIResponse } from '../../../common/storage_explorer';
|
||||
import { useProfilingDependencies } from '../../components/contexts/profiling_dependencies/use_profiling_dependencies';
|
||||
import { LabelWithHint } from '../../components/label_with_hint';
|
||||
|
|
|
@ -38,8 +38,8 @@ export function registerFlameChartSearchRoute({
|
|||
const esClient = await getClient(context);
|
||||
const flamegraph = await profilingDataAccess.services.fetchFlamechartData({
|
||||
esClient,
|
||||
rangeFrom: timeFrom,
|
||||
rangeTo: timeTo,
|
||||
rangeFromMs: timeFrom,
|
||||
rangeToMs: timeTo,
|
||||
kuery,
|
||||
});
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { QueryDslBoolQuery } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { kqlQuery } from '@kbn/observability-plugin/server';
|
||||
import { ProfilingESField } from '@kbn/profiling-data-access-plugin/common/elasticsearch';
|
||||
import { ProfilingESField } from '@kbn/profiling-utils';
|
||||
|
||||
export interface ProjectTimeQuery {
|
||||
bool: QueryDslBoolQuery;
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { decodeStackTraceResponse } from '@kbn/profiling-data-access-plugin/common/stack_traces';
|
||||
import { decodeStackTraceResponse } from '@kbn/profiling-utils';
|
||||
import { ProfilingESClient } from '../utils/create_profiling_es_client';
|
||||
import { ProjectTimeQuery } from './query';
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { kqlQuery, termQuery } from '@kbn/observability-plugin/server';
|
||||
import { ProfilingESField } from '@kbn/profiling-data-access-plugin/common/elasticsearch';
|
||||
import { ProfilingESField } from '@kbn/profiling-utils';
|
||||
import { computeBucketWidthFromTimeRangeAndBucketCount } from '../../../common/histogram';
|
||||
import {
|
||||
IndexLifecyclePhaseSelectOption,
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { kqlQuery, termQuery } from '@kbn/observability-plugin/server';
|
||||
import { ProfilingESField } from '@kbn/profiling-data-access-plugin/common/elasticsearch';
|
||||
import { ProfilingESField } from '@kbn/profiling-utils';
|
||||
import {
|
||||
IndexLifecyclePhaseSelectOption,
|
||||
indexLifeCyclePhaseToDataTier,
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
import { kqlQuery } from '@kbn/observability-plugin/server';
|
||||
import { keyBy } from 'lodash';
|
||||
import { ProfilingESField } from '@kbn/profiling-data-access-plugin/common/elasticsearch';
|
||||
import { ProfilingESField } from '@kbn/profiling-utils';
|
||||
import { ProfilingESClient } from '../../utils/create_profiling_es_client';
|
||||
|
||||
interface HostDetails {
|
||||
|
|
|
@ -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 '@kbn/profiling-data-access-plugin/common/elasticsearch';
|
||||
import { ProfilingESField } from '@kbn/profiling-utils';
|
||||
import { ProfilingESClient } from '../utils/create_profiling_es_client';
|
||||
import { topNElasticSearchQuery } from './topn';
|
||||
|
||||
|
|
|
@ -7,18 +7,18 @@
|
|||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import type { Logger } from '@kbn/core/server';
|
||||
import { ProfilingESField } from '@kbn/profiling-data-access-plugin/common/elasticsearch';
|
||||
import { groupStackFrameMetadataByStackTrace } from '@kbn/profiling-data-access-plugin/common/profiling';
|
||||
import {
|
||||
getFieldNameForTopNType,
|
||||
groupStackFrameMetadataByStackTrace,
|
||||
ProfilingESField,
|
||||
TopNType,
|
||||
} from '@kbn/profiling-data-access-plugin/common/stack_traces';
|
||||
import { getRoutePaths, INDEX_EVENTS } from '../../common';
|
||||
} from '@kbn/profiling-utils';
|
||||
import { RouteRegisterParameters } from '.';
|
||||
import { getRoutePaths, INDEX_EVENTS } from '../../common';
|
||||
import { computeBucketWidthFromTimeRangeAndBucketCount } from '../../common/histogram';
|
||||
import { createTopNSamples, getTopNAggregationRequest, TopNResponse } from '../../common/topn';
|
||||
import { handleRouteHandlerError } from '../utils/handle_route_error_handler';
|
||||
import { ProfilingESClient } from '../utils/create_profiling_es_client';
|
||||
import { handleRouteHandlerError } from '../utils/handle_route_error_handler';
|
||||
import { withProfilingSpan } from '../utils/with_profiling_span';
|
||||
import { getClient } from './compat';
|
||||
import { findDownsampledIndex } from './downsampling';
|
||||
|
|
|
@ -10,10 +10,7 @@ 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 type { ProfilingStatusResponse, StackTraceResponse } from '@kbn/profiling-utils';
|
||||
import { withProfilingSpan } from './with_profiling_span';
|
||||
|
||||
export function cancelEsRequestOnAbort<T extends Promise<any>>(
|
||||
|
|
|
@ -48,7 +48,9 @@
|
|||
"@kbn/utility-types",
|
||||
"@kbn/usage-collection-plugin",
|
||||
"@kbn/observability-ai-assistant-plugin",
|
||||
"@kbn/profiling-data-access-plugin"
|
||||
"@kbn/profiling-data-access-plugin",
|
||||
"@kbn/embeddable-plugin",
|
||||
"@kbn/profiling-utils"
|
||||
// add references to other TypeScript projects the plugin depends on
|
||||
|
||||
// requiredPlugins from ./kibana.json
|
||||
|
|
|
@ -6,31 +6,33 @@
|
|||
*/
|
||||
|
||||
import { ElasticsearchClient } from '@kbn/core/server';
|
||||
import { RegisterServicesParams } from '../register_services';
|
||||
import { createBaseFlameGraph, createCalleeTree } from '@kbn/profiling-utils';
|
||||
import { withProfilingSpan } from '../../utils/with_profiling_span';
|
||||
import { RegisterServicesParams } from '../register_services';
|
||||
import { searchStackTraces } from '../search_stack_traces';
|
||||
import { createCalleeTree } from '../../../common/callee';
|
||||
import { createBaseFlameGraph } from '../../../common/flamegraph';
|
||||
|
||||
interface FetchFlamechartParams {
|
||||
export interface FetchFlamechartParams {
|
||||
esClient: ElasticsearchClient;
|
||||
rangeFrom: number;
|
||||
rangeTo: number;
|
||||
rangeFromMs: number;
|
||||
rangeToMs: number;
|
||||
kuery: string;
|
||||
}
|
||||
|
||||
export function createFetchFlamechart({ createProfilingEsClient }: RegisterServicesParams) {
|
||||
return async ({ esClient, rangeFrom, rangeTo, kuery }: FetchFlamechartParams) => {
|
||||
return async ({ esClient, rangeFromMs, rangeToMs, kuery }: FetchFlamechartParams) => {
|
||||
const rangeFromSecs = rangeFromMs / 1000;
|
||||
const rangeToSecs = rangeToMs / 1000;
|
||||
|
||||
const profilingEsClient = createProfilingEsClient({ esClient });
|
||||
const targetSampleSize = 20000; // minimum number of samples to get statistically sound results
|
||||
|
||||
const totalSeconds = rangeTo - rangeFrom;
|
||||
const totalSeconds = rangeToSecs - rangeFromSecs;
|
||||
|
||||
const { events, stackTraces, executables, stackFrames, totalFrames, samplingRate } =
|
||||
await searchStackTraces({
|
||||
client: profilingEsClient,
|
||||
rangeFrom,
|
||||
rangeTo,
|
||||
rangeFrom: rangeFromSecs,
|
||||
rangeTo: rangeToSecs,
|
||||
kuery,
|
||||
sampleSize: targetSampleSize,
|
||||
});
|
||||
|
@ -45,9 +47,7 @@ export function createFetchFlamechart({ createProfilingEsClient }: RegisterServi
|
|||
samplingRate
|
||||
);
|
||||
|
||||
const fg = createBaseFlameGraph(tree, samplingRate, totalSeconds);
|
||||
|
||||
return fg;
|
||||
return createBaseFlameGraph(tree, samplingRate, totalSeconds);
|
||||
});
|
||||
|
||||
return flamegraph;
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query';
|
||||
import { decodeStackTraceResponse } from '../../../common/stack_traces';
|
||||
import { decodeStackTraceResponse } from '@kbn/profiling-utils';
|
||||
import { ProfilingESClient } from '../../utils/create_profiling_es_client';
|
||||
|
||||
export async function searchStackTraces({
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
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 type { ProfilingStatusResponse, StackTraceResponse } from '@kbn/profiling-utils';
|
||||
import { unwrapEsResponse } from './unwrap_es_response';
|
||||
import { withProfilingSpan } from './with_profiling_span';
|
||||
|
||||
export interface ProfilingESClient {
|
||||
|
|
|
@ -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 { errors } from '@elastic/elasticsearch';
|
||||
import { inspect } from 'util';
|
||||
|
||||
export class WrappedElasticsearchClientError extends Error {
|
||||
originalError: errors.ElasticsearchClientError;
|
||||
constructor(originalError: errors.ElasticsearchClientError) {
|
||||
super(originalError.message);
|
||||
|
||||
const stack = this.stack;
|
||||
|
||||
this.originalError = originalError;
|
||||
|
||||
if (originalError instanceof errors.ResponseError) {
|
||||
// make sure ES response body is visible when logged to the console
|
||||
// @ts-expect-error
|
||||
this.stack = {
|
||||
valueOf() {
|
||||
const value = stack?.valueOf() ?? '';
|
||||
return value;
|
||||
},
|
||||
toString() {
|
||||
const value =
|
||||
stack?.toString() +
|
||||
`\nResponse: ${inspect(originalError.meta.body, { depth: null })}\n`;
|
||||
return value;
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function unwrapEsResponse<T extends Promise<{ body: any }>>(
|
||||
responsePromise: T
|
||||
): Promise<Awaited<T>['body']> {
|
||||
return responsePromise
|
||||
.then((res) => res.body)
|
||||
.catch((err) => {
|
||||
// make sure stacktrace is relative to where client was called
|
||||
throw new WrappedElasticsearchClientError(err);
|
||||
});
|
||||
}
|
|
@ -5,8 +5,6 @@
|
|||
},
|
||||
"include": [
|
||||
"server/**/*",
|
||||
"common/**/*.ts",
|
||||
"common/**/*.json",
|
||||
"jest.config.js"
|
||||
],
|
||||
"exclude": [
|
||||
|
@ -17,7 +15,7 @@
|
|||
"@kbn/core",
|
||||
"@kbn/es-query",
|
||||
"@kbn/es-types",
|
||||
"@kbn/observability-plugin",
|
||||
"@kbn/apm-utils"
|
||||
"@kbn/apm-utils",
|
||||
"@kbn/profiling-utils"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -5142,6 +5142,10 @@
|
|||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/profiling-utils@link:packages/kbn-profiling-utils":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/random-sampling@link:x-pack/packages/kbn-random-sampling":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue