Skip to content

perf: monomorphic logevent #575

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

Draft
wants to merge 19 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
b4e28a2
perf: switch to monomorphic object for LogLine
lukecotter Jan 7, 2025
0b8839c
fix: package wrapper handlinmg when Enetering managed pkg event has n…
lukecotter Jan 7, 2025
79df111
refactor: rename LogLine to LogEvent
lukecotter Jan 7, 2025
360f74f
Merge branch 'main' into perf-monomorphic-logevent
lukecotter Mar 31, 2025
a11f23b
Merge branch 'certinia:main' into perf-monomorphic-logevent
lukecotter Apr 1, 2025
02c30f7
Merge branch 'main' into perf-monomorphic-logevent
lukecotter Apr 7, 2025
c11599b
Merge branch 'main' into perf-monomorphic-logevent
lukecotter Apr 9, 2025
c055062
perf: speed up + simplify getMaxDepth
lukecotter May 1, 2025
d3819b4
perf: speed up + simplify nodeToRectangles
lukecotter May 1, 2025
3672202
refactor: remove remaining external refs to Method class
lukecotter May 1, 2025
d565e65
refactor: use set for namespace instead of set and array
lukecotter May 1, 2025
dfb5523
refactor: reorder assignments
lukecotter May 1, 2025
da2a6d6
perf: switch to asyn processing of log content
lukecotter May 1, 2025
3478615
perf: reduce calls to get from map
lukecotter May 1, 2025
0180a60
refactor: split ApexLogParser into multiple files
lukecotter May 1, 2025
d4de173
Merge branch 'main' into perf-monomorphic-logevent
lukecotter May 1, 2025
cc18d6f
Merge branch 'main' into perf-monomorphic-logevent
lukecotter May 1, 2025
1088c6e
fix: duplicate declaration after bad merge
lukecotter May 2, 2025
5fde7a1
fix: remove duplicate code after merge
lukecotter May 2, 2025
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
21 changes: 13 additions & 8 deletions log-viewer/modules/Database.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
/*
* Copyright (c) 2020 Certinia Inc. All rights reserved.
*/
import { ApexLog, DMLBeginLine, Method, SOQLExecuteBeginLine } from './parsers/ApexLogParser.js';
import {
type ApexLog,
DMLBeginLine,
type LogEvent,
SOQLExecuteBeginLine,
} from './parsers/LogEvents.js';

export type Stack = Method[];
export type Stack = LogEvent[];

export class DatabaseAccess {
private static _instance: DatabaseAccess | null = null;
Expand All @@ -23,13 +28,13 @@ export class DatabaseAccess {
public getStack(
timestamp: number,
stack: Stack = [],
line: Method = DatabaseAccess._treeRoot,
line: LogEvent = DatabaseAccess._treeRoot,
): Stack {
const children = line.children;
const len = children.length;
for (let i = 0; i < len; ++i) {
const child = children[i];
if (child instanceof Method) {
if (child?.exitTypes.length) {
stack.push(child);
if (child.timestamp === timestamp) {
return stack;
Expand All @@ -45,7 +50,7 @@ export class DatabaseAccess {
return [];
}

public getSOQLLines(line: Method = DatabaseAccess._treeRoot): SOQLExecuteBeginLine[] {
public getSOQLLines(line: LogEvent = DatabaseAccess._treeRoot): SOQLExecuteBeginLine[] {
const results: SOQLExecuteBeginLine[] = [];

const children = line.children;
Expand All @@ -56,15 +61,15 @@ export class DatabaseAccess {
results.push(child);
}

if (child instanceof Method) {
if (child?.exitTypes.length) {
Array.prototype.push.apply(results, this.getSOQLLines(child));
}
}

return results;
}

public getDMLLines(line: Method = DatabaseAccess._treeRoot): DMLBeginLine[] {
public getDMLLines(line: LogEvent = DatabaseAccess._treeRoot): DMLBeginLine[] {
const results: DMLBeginLine[] = [];

const children = line.children;
Expand All @@ -75,7 +80,7 @@ export class DatabaseAccess {
results.push(child);
}

if (child instanceof Method) {
if (child?.exitTypes.length) {
// results = results.concat(this.getDMLLines(child));
Array.prototype.push.apply(results, this.getDMLLines(child));
}
Expand Down
88 changes: 45 additions & 43 deletions log-viewer/modules/__tests__/ApexLogParser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,25 @@
* Copyright (c) 2020 Certinia Inc. All rights reserved.
*/
import {
ApexLogParser,
CodeUnitStartedLine,
ExecutionStartedLine,
LogLine,
Method,
LogEvent,
MethodEntryLine,
SOQLExecuteBeginLine,
SOQLExecuteExplainLine,
TimedNode,
lineTypeMap,
parse,
parseObjectNamespace,
parseRows,
parseVfNamespace,
} from '../parsers/ApexLogParser.js';
} from '../parsers/LogEvents.js';
import { lineTypeMap } from '../parsers/LogLineMapping.js';

import { ApexLogParser, parse } from '../parsers/ApexLogParser.js';

class DummyLine extends LogEvent {
constructor(parser: ApexLogParser, parts: string[]) {
super(parser, parts);
}
}

describe('parseObjectNamespace tests', () => {
it('Should consider no separator to be unmanaged', () => {
Expand Down Expand Up @@ -79,30 +83,30 @@ describe('Pseudo EXIT events', () => {
expect(log1.children.length).toEqual(4);
expect(log1.duration).toEqual({ self: 0, total: 3 });

const approval1 = log1.children[0] as Method;
const approval1 = log1.children[0];
expect(approval1).toMatchObject({
type: 'WF_APPROVAL_SUBMIT',
timestamp: 1,
duration: { self: 1, total: 1 },
});

const processFound1 = log1.children[1] as Method;
const processFound1 = log1.children[1];
expect(processFound1).toMatchObject({
parent: log1,
type: 'WF_PROCESS_FOUND',
timestamp: 2,
duration: { self: 1, total: 1 },
});

const approval2 = log1.children[2] as Method;
const approval2 = log1.children[2];
expect(approval2).toMatchObject({
parent: log1,
type: 'WF_APPROVAL_SUBMIT',
timestamp: 3,
duration: { self: 1, total: 1 },
});

const processFound2 = log1.children[3] as Method;
const processFound2 = log1.children[3];
expect(processFound2).toMatchObject({
parent: log1,
type: 'WF_PROCESS_FOUND',
Expand All @@ -125,22 +129,22 @@ describe('Pseudo EXIT events', () => {
expect(log1.children.length).toEqual(1);
expect(log1.duration).toEqual({ self: 0, total: 6 });

const children = (log1.children[0] as Method).children;
const children = log1.children[0]?.children ?? [];
expect(children.length).toEqual(4);

const child1 = children[0] as Method;
const child1 = children[0]!;
expect(child1.timestamp).toEqual(2);
expect(child1.exitStamp).toEqual(3);

const child2 = children[1] as Method;
const child2 = children[1]!;
expect(child2.timestamp).toEqual(3);
expect(child2.exitStamp).toEqual(4);

const child3 = children[2] as Method;
const child3 = children[2]!;
expect(child3.timestamp).toEqual(4);
expect(child3.exitStamp).toEqual(5);

const child4 = children[3] as Method;
const child4 = children[3]!;
expect(child4.timestamp).toEqual(5);
expect(child4.exitStamp).toEqual(6);
});
Expand All @@ -157,18 +161,18 @@ describe('Pseudo EXIT events', () => {
expect(log1.children.length).toEqual(1);
expect(log1.duration).toEqual({ self: 0, total: 4 });

const children = (log1.children[0] as Method).children;
const children = log1.children[0]?.children ?? [];
expect(children.length).toEqual(3);

const child1 = children[0] as Method;
const child1 = children[0]!;
expect(child1.timestamp).toEqual(2);
expect(child1.exitStamp).toEqual(3);

const child2 = children[1] as Method;
const child2 = children[1]!;
expect(child2.timestamp).toEqual(3);
expect(child2.exitStamp).toEqual(4);

const child3 = children[2] as Method;
const child3 = children[2]!;
expect(child3.timestamp).toEqual(4);
expect(child3.exitStamp).toEqual(5);
});
Expand Down Expand Up @@ -204,7 +208,7 @@ describe('parseLog tests', () => {
expect(logLines.length).toEqual(1);
expect(logLines[0]).toBeInstanceOf(ExecutionStartedLine);

const firstChildren = (logLines[0] as Method).children;
const firstChildren = logLines[0]?.children ?? [];
expect(firstChildren.length).toEqual(1);
expect(firstChildren[0]).toBeInstanceOf(CodeUnitStartedLine);
});
Expand All @@ -222,7 +226,7 @@ describe('parseLog tests', () => {
expect(apexLog.children.length).toEqual(1);
expect(apexLog.children[0]).toBeInstanceOf(ExecutionStartedLine);

const firstChildren = (apexLog.children[0] as Method).children;
const firstChildren = apexLog.children[0]?.children ?? [];
expect(firstChildren.length).toEqual(1);
expect(firstChildren[0]).toBeInstanceOf(CodeUnitStartedLine);
});
Expand All @@ -237,7 +241,7 @@ describe('parseLog tests', () => {
expect(apexLog.children.length).toBe(1);
expect(apexLog.children[0]).toBeInstanceOf(ExecutionStartedLine);

const firstChildren = (apexLog.children[0] as Method).children;
const firstChildren = apexLog.children[0]?.children ?? [];
expect(firstChildren[0]).toBeInstanceOf(CodeUnitStartedLine);
});

Expand Down Expand Up @@ -406,7 +410,7 @@ describe('parseLog tests', () => {

const apexLog = parse(log);

const methods = apexLog.children as Method[];
const methods = apexLog.children;
expect(methods.length).toBe(24);
methods.forEach((line) => {
expect(line.exitTypes.length).toBe(0);
Expand Down Expand Up @@ -464,7 +468,7 @@ describe('getRootMethod tests', () => {

const apexLog = parse(log);

const timedLogLines = apexLog.children as TimedNode[];
const timedLogLines = apexLog.children;
expect(timedLogLines.length).toBe(1);
const startLine = timedLogLines[0];
expect(startLine?.type).toBe('EXECUTION_STARTED');
Expand All @@ -478,7 +482,7 @@ describe('getRootMethod tests', () => {
});

expect(unitStart.children.length).toBe(1);
const interViewsBegin = unitStart.children[0] as TimedNode;
const interViewsBegin = unitStart.children[0]!;
expect(interViewsBegin).toMatchObject({
parent: unitStart,
type: 'FLOW_START_INTERVIEWS_BEGIN',
Expand Down Expand Up @@ -508,7 +512,7 @@ describe('getRootMethod tests', () => {

const apexLog = parse(log);

const timedLogLines = apexLog.children as TimedNode[];
const timedLogLines = apexLog.children;
expect(timedLogLines.length).toBe(1);
const startLine = timedLogLines[0];
expect(startLine?.type).toBe('EXECUTION_STARTED');
Expand All @@ -519,7 +523,7 @@ describe('getRootMethod tests', () => {
expect(unitStart.codeUnitType).toBe('Flow');

expect(unitStart.children.length).toBe(1);
const interViewsBegin = unitStart.children[0] as TimedNode;
const interViewsBegin = unitStart.children[0]!;
expect(interViewsBegin.type).toBe('FLOW_START_INTERVIEWS_BEGIN');
expect(interViewsBegin.text).toBe('FLOW_START_INTERVIEWS : Example Flow');
expect(interViewsBegin.suffix).toBe(' (Flow)');
Expand Down Expand Up @@ -547,7 +551,7 @@ describe('getRootMethod tests', () => {

const apexLog = parse(log);

const timedLogLines = apexLog.children as TimedNode[];
const timedLogLines = apexLog.children;
expect(timedLogLines.length).toBe(1);
const startLine = timedLogLines[0];
expect(startLine?.type).toBe('EXECUTION_STARTED');
Expand All @@ -558,17 +562,17 @@ describe('getRootMethod tests', () => {
expect(unitStart.codeUnitType).toBe('Workflow');

expect(unitStart.children.length).toBe(1);
const pbBegin = unitStart.children[0] as TimedNode;
const pbBegin = unitStart.children[0]!;
expect(pbBegin.type).toBe('FLOW_START_INTERVIEWS_BEGIN');
expect(pbBegin.text).toBe('FLOW_START_INTERVIEWS : Example Process Builder');
expect(pbBegin.suffix).toBe(' (Process Builder)');

expect(pbBegin.children.length).toBe(1);
const pbDetail = pbBegin.children[0] as TimedNode;
const pbDetail = pbBegin.children[0]!;
expect(pbDetail.type).toBe('FLOW_START_INTERVIEW_BEGIN');
expect(pbDetail.text).toBe('Example Process Builder');

const interViewsBegin = pbDetail.children[0] as TimedNode;
const interViewsBegin = pbDetail.children[0]!;
expect(interViewsBegin.type).toBe('FLOW_START_INTERVIEWS_BEGIN');
expect(interViewsBegin.text).toBe('FLOW_START_INTERVIEWS : Example Flow');
expect(interViewsBegin.suffix).toBe(' (Flow)');
Expand Down Expand Up @@ -643,9 +647,9 @@ describe('getRootMethod tests', () => {
expect(apexLog.exitStamp).toBe(1100);
expect(apexLog.executionEndTime).toBe(1100);

const rootChildren = apexLog.children as Method[];
const rootChildren = apexLog.children;
const executionStarted = rootChildren[0];
const executionChildren = executionStarted?.children as Method[];
const executionChildren = executionStarted?.children ?? [];
expect(executionChildren.length).toBe(5);

expect(executionChildren[0]).toMatchObject({
Expand Down Expand Up @@ -1118,7 +1122,8 @@ describe('namespace tests', () => {
describe('Recalculate durations tests', () => {
it('Recalculates parent node', () => {
const parser = new ApexLogParser();
const node = new Method(parser, ['14:32:07.563 (1)', 'DUMMY'], [], 'Method', '');
const node = new DummyLine(parser, ['14:32:07.563 (1)', 'DUMMY']);
node.subCategory = 'Method';
node.exitStamp = 3;

node.recalculateDurations();
Expand All @@ -1130,20 +1135,17 @@ describe('Recalculate durations tests', () => {
describe('Line Type Tests', () => {
it('Lines referenced by exitTypes should be exits', () => {
const parser = new ApexLogParser();
for (const [key, lineType] of lineTypeMap) {
for (const lineType of Object.values(lineTypeMap)) {
const line = new lineType(parser, [
'14:32:07.563 (17358806534)',
'DUMMY',
'[10]',
'Rows:3',
'',
'Rows:5',
]) as LogLine;
if (line instanceof Method) {
expect(line.exitTypes).not.toBe(null);
if (line.isExit) {
expect(line.exitTypes).toEqual([key]);
}
]) as LogEvent;

if (line.exitTypes.length) {
line.exitTypes.forEach((exitType) => {
const exitCls = lineTypeMap.get(exitType);
expect(exitCls).not.toBe(null);
Expand All @@ -1155,7 +1157,7 @@ describe('Line Type Tests', () => {
'Rows:3',
'',
'Rows:5',
]) as LogLine;
]) as LogEvent;
expect(exitLine.isExit).toBe(true);
}
});
Expand Down
34 changes: 19 additions & 15 deletions log-viewer/modules/__tests__/soql/SOQLLinter.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
/*
* Copyright (c) 2021 Certinia Inc. All rights reserved.
*/
import { ApexLogParser, Method } from '../../parsers/ApexLogParser.js';
import { ApexLogParser } from '../../parsers/ApexLogParser.js';
import { LogEvent } from '../../parsers/LogEvents.js';
import { SOQLLinter } from '../../soql/SOQLLinter.js';

class DummySOQLLine extends LogEvent {
constructor(parser: ApexLogParser, parts: string[] | null) {
super(parser, parts);
this.subCategory = 'Code Unit';
this.cpuType = 'method';
this.exitTypes = ['CODE_UNIT_FINISHED'];
}
}

describe('SOQL Linter rule tests', () => {
it('No where clause should return rule', async () => {
const soql = 'SELECT Id FROM ANOBJECT__c';
Expand Down Expand Up @@ -155,20 +165,14 @@ describe('SOQL in Trigger Rule tests', () => {
it('soql in trigger should return rule', async () => {
const parser = new ApexLogParser();
const soql = 'SELECT Id FROM AnObject__c WHERE value__c > 0';
const mockTriggerLine = new Method(
parser,
[
'04:16:39.166 (1166781977)',
'CODE_UNIT_STARTED',
'[EXTERNAL]',
'a0000000000aaaa',
'Account on Account trigger event AfterInsert',
'__sfdc_trigger/Account',
],
['CODE_UNIT_FINISHED'],
'Code Unit',
'method',
);
const mockTriggerLine = new DummySOQLLine(parser, [
'04:16:39.166 (1166781977)',
'CODE_UNIT_STARTED',
'[EXTERNAL]',
'a0000000000aaaa',
'Account on Account trigger event AfterInsert',
'__sfdc_trigger/Account',
]);
mockTriggerLine.text = 'Account on Account trigger event AfterInsert';

const results = await new SOQLLinter().lint(soql, [mockTriggerLine]);
Expand Down
Loading
Loading