Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Profiling] Prepare for inline stackframes #150401

Merged
merged 8 commits into from
Feb 7, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions x-pack/plugins/profiling/common/stack_traces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ interface ProfilingStackTraces {
[key: string]: ProfilingStackTrace;
}

interface ProfilingStackFrame {
['file_name']: string | undefined;
['function_name']: string;
['function_offset']: number | undefined;
['line_number']: number | undefined;
export interface ProfilingStackFrame {
['file_name']: string[];
['function_name']: string[];
['function_offset']: number[];
['line_number']: number[];
}

interface ProfilingStackFrames {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@ describe('Stack trace response operations', () => {
},
stack_frames: {
abc: {
file_name: 'pthread.c',
function_name: 'pthread_create',
function_offset: 0,
line_number: 0,
file_name: ['pthread.c'],
function_name: ['pthread_create'],
function_offset: [0],
line_number: [0],
},
},
executables: {
Expand Down Expand Up @@ -128,10 +128,10 @@ describe('Stack trace response operations', () => {
},
stack_frames: {
abc: {
file_name: undefined,
function_name: 'pthread_create',
function_offset: undefined,
line_number: undefined,
file_name: [],
function_name: ['pthread_create'],
function_offset: [],
line_number: [],
},
},
executables: {
Expand Down
13 changes: 9 additions & 4 deletions x-pack/plugins/profiling/server/routes/search_stacktraces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,16 @@ export function decodeStackTraceResponse(response: StackTraceResponse) {

const stackFrames: Map<StackFrameID, StackFrame> = new Map();
for (const [key, value] of Object.entries(response.stack_frames ?? {})) {
// Each field in a stackframe is represented by an array. This is
// necessary to support inline frames.
//
// We only take the first available inline stackframe until the UI
// can support all of them.
stackFrames.set(key, {
FileName: value.file_name ? value.file_name[0] : [],
FunctionName: value.function_name ? value.function_name[0] : [],
FunctionOffset: value.function_offset,
LineNumber: value.line_number,
FileName: value.file_name[0],
FunctionName: value.function_name[0],
FunctionOffset: value.function_offset[0],
LineNumber: value.line_number[0],
} as StackFrame);
}

Expand Down
111 changes: 109 additions & 2 deletions x-pack/plugins/profiling/server/routes/stacktrace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
* 2.0.
*/

import { createStackFrameID, StackTrace } from '../../common/profiling';
import LRUCache from 'lru-cache';
import { createStackFrameID, StackFrame, StackFrameID, StackTrace } from '../../common/profiling';
import { runLengthEncode } from '../../common/run_length_encoding';
import { decodeStackTrace, EncodedStackTrace } from './stacktrace';
import { decodeStackTrace, EncodedStackTrace, updateStackFrameMap } from './stacktrace';

enum fileID {
A = 'aQpJmTLWydNvOapSFZOwKg',
Expand Down Expand Up @@ -86,3 +87,109 @@ describe('Stack trace operations', () => {
}
});
});

describe('Stack frame operations', () => {
test('updateStackFrameMap with no frames', () => {
const stackFrameMap = new Map<StackFrameID, StackFrame>();
const stackFrameCache = new LRUCache<StackFrameID, StackFrame>();

const hits = updateStackFrameMap([], stackFrameMap, stackFrameCache);

expect(hits).toEqual(0);
expect(stackFrameMap.size).toEqual(0);
expect(stackFrameCache.length).toEqual(0);
});

test('updateStackFrameMap with missing frames', () => {
const stackFrameMap = new Map<StackFrameID, StackFrame>();
const stackFrameCache = new LRUCache<StackFrameID, StackFrame>();

const stackFrames = [
{
_index: 'profiling-stackframes',
_id: 'stackframe-001',
found: false,
},
];

const hits = updateStackFrameMap(stackFrames, stackFrameMap, stackFrameCache);

expect(hits).toEqual(0);
expect(stackFrameMap.size).toEqual(1);
expect(stackFrameCache.length).toEqual(1);
});

test('updateStackFrameMap with one partial non-inlined frame', () => {
const stackFrameMap = new Map<StackFrameID, StackFrame>();
const stackFrameCache = new LRUCache<StackFrameID, StackFrame>();

const id = 'stackframe-001';
const source = {
'ecs.version': '1.0.0',
'Stackframe.function.name': 'calloc',
};
const expected = {
FileName: undefined,
FunctionName: 'calloc',
FunctionOffset: undefined,
LineNumber: undefined,
SourceType: undefined,
};

const stackFrames = [
{
_index: 'profiling-stackframes',
_id: id,
_version: 1,
_seq_no: 1,
_primary_term: 1,
found: true,
_source: source,
},
];

const hits = updateStackFrameMap(stackFrames, stackFrameMap, stackFrameCache);

expect(hits).toEqual(1);
expect(stackFrameMap.size).toEqual(1);
expect(stackFrameCache.length).toEqual(1);
expect(stackFrameMap.get(id)).toEqual(expected);
});

test('updateStackFrameMap with one partial inlined frame', () => {
const stackFrameMap = new Map<StackFrameID, StackFrame>();
const stackFrameCache = new LRUCache<StackFrameID, StackFrame>();

const id = 'stackframe-001';
const source = {
'ecs.version': '1.0.0',
'Stackframe.function.name': ['calloc', 'memset'],
};
const expected = {
FileName: undefined,
FunctionName: 'calloc',
FunctionOffset: undefined,
LineNumber: undefined,
SourceType: undefined,
};

const stackFrames = [
{
_index: 'profiling-stackframes',
_id: id,
_version: 1,
_seq_no: 1,
_primary_term: 1,
found: true,
_source: source,
},
];

const hits = updateStackFrameMap(stackFrames, stackFrameMap, stackFrameCache);

expect(hits).toEqual(1);
expect(stackFrameMap.size).toEqual(1);
expect(stackFrameCache.length).toEqual(1);
expect(stackFrameMap.get(id)).toEqual(expected);
});
});
87 changes: 63 additions & 24 deletions x-pack/plugins/profiling/server/routes/stacktrace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,68 @@ export function clearStackFrameCache(): number {
return numDeleted;
}

export function updateStackFrameMap(
stackFrames: any,
stackFrameMap: Map<StackFrameID, StackFrame>,
stackFrameCache: LRUCache<StackFrameID, StackFrame>
): number {
let found = 0;
for (const frame of stackFrames) {
if ('error' in frame) {
continue;
}
if (frame.found) {
found++;

const fileName = frame._source[ProfilingESField.StackframeFileName];
const functionName = frame._source[ProfilingESField.StackframeFunctionName];
const functionOffset = frame._source[ProfilingESField.StackframeFunctionOffset];
const lineNumber = frame._source[ProfilingESField.StackframeLineNumber];

let stackFrame;
if (Array.isArray(functionName)) {
// Each field in a stackframe is represented by an array. This is
// necessary to support inline frames.
//
// We only take the first available inline stackframe until the UI
// can support all of them.
stackFrame = {
FileName: fileName && fileName[0],
FunctionName: functionName && functionName[0],
FunctionOffset: functionOffset && functionOffset[0],
LineNumber: lineNumber && lineNumber[0],
};
} else {
if (fileName || functionName) {
stackFrame = {
FileName: fileName,
FunctionName: functionName,
FunctionOffset: functionOffset,
LineNumber: lineNumber,
};
} else {
// pre 8.7 format with synthetic source
const sf = frame._source.Stackframe;
stackFrame = {
FileName: sf?.file?.name,
FunctionName: sf?.function?.name,
FunctionOffset: sf?.function?.offset,
LineNumber: sf?.line?.number,
};
}
}

stackFrameMap.set(frame._id, stackFrame);
stackFrameCache.set(frame._id, stackFrame);
continue;
}

stackFrameMap.set(frame._id, emptyStackFrame);
stackFrameCache.set(frame._id, emptyStackFrame);
}
return found;
}

export async function mgetStackFrames({
logger,
client,
Expand Down Expand Up @@ -319,31 +381,8 @@ export async function mgetStackFrames({
realtime: true,
});

// Create a lookup map StackFrameID -> StackFrame.
let queryHits = 0;
const t0 = Date.now();
const docs = resStackFrames.docs;
for (const frame of docs) {
if ('error' in frame) {
continue;
}
if (frame.found) {
queryHits++;
const stackFrame = {
FileName: frame._source!.Stackframe.file?.name,
FunctionName: frame._source!.Stackframe.function?.name,
FunctionOffset: frame._source!.Stackframe.function?.offset,
LineNumber: frame._source!.Stackframe.line?.number,
};
stackFrames.set(frame._id, stackFrame);
frameLRU.set(frame._id, stackFrame);
continue;
}

stackFrames.set(frame._id, emptyStackFrame);
frameLRU.set(frame._id, emptyStackFrame);
}

const queryHits = updateStackFrameMap(resStackFrames.docs, stackFrames, frameLRU);
logger.info(`processing data took ${Date.now() - t0} ms`);

summarizeCacheAndQuery(logger, 'frames', cacheHits, cacheTotal, queryHits, stackFrameIDs.size);
Expand Down