Skip to content

Commit 771cb95

Browse files
Merge pull request #483 from lukecotter/perf-parser-speed
2 parents 7284cbf + 4af7aa7 commit 771cb95

3 files changed

Lines changed: 127 additions & 113 deletions

File tree

log-viewer/modules/components/AnalysisView.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -303,17 +303,15 @@ function addNodeToMap(map: Map<string, Metric>, node: TimedNode, key?: string) {
303303
const children = node.children;
304304

305305
if (key) {
306-
const totalTime = node.duration.total;
307-
const selfTime = node.duration.self;
308306
let metric = map.get(key);
309307
if (!metric) {
310308
metric = new Metric(node);
311309
map.set(key, metric);
312310
}
313311

314312
++metric.count;
315-
metric.totalTime += totalTime;
316-
metric.selfTime += selfTime;
313+
metric.totalTime += node.duration.total;
314+
metric.selfTime += node.duration.self;
317315
}
318316

319317
children.forEach(function (child) {

log-viewer/modules/parsers/ApexLogParser.ts

Lines changed: 74 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,10 @@ class ApexLogParser {
4343
maxSizeTimestamp: number | null = null;
4444
reasons: Set<string> = new Set<string>();
4545
cpuUsed = 0;
46+
lastTimestamp = 0;
4647
discontinuity = false;
4748
namespaces: string[] = [];
49+
namespacesUniq = new Set<string>();
4850

4951
/**
5052
* Takes string input of a log and returns the ApexLog class, which represents a log tree
@@ -65,69 +67,71 @@ class ApexLogParser {
6567
}
6668

6769
private parseLine(line: string, lastEntry: LogLine | null): LogLine | null {
68-
const parts = line.split('|'),
69-
type = parts[1] || '';
70+
const parts = line.split('|');
7071

72+
const type = parts[1] ?? '';
7173
const metaCtor = getLogEventClass(type as LogEventType);
7274
if (metaCtor) {
7375
const entry = new metaCtor(parts);
7476
entry.logLine = line;
7577
lastEntry?.onAfter?.(this, entry);
76-
entry.namespace &&
77-
!this.namespaces.includes(entry.namespace) &&
78+
if (entry.namespace && !this.namespacesUniq.has(entry.namespace)) {
7879
this.namespaces.push(entry.namespace);
80+
this.namespacesUniq.add(entry.namespace);
81+
}
7982
return entry;
8083
}
8184

82-
if ((!type || !typePattern.test(type)) && lastEntry && lastEntry.acceptsText) {
85+
const hasType = type && typePattern.test(type);
86+
if (!hasType && lastEntry?.acceptsText) {
8387
// wrapped text from the previous entry?
84-
lastEntry.text += `\n${line}`;
85-
} else if (type && typePattern.test(type)) {
88+
lastEntry.text += '\n' + line;
89+
} else if (hasType) {
8690
const message = `Unsupported log event name: ${type}`;
8791
!this.parsingErrors.includes(message) && this.parsingErrors.push(message);
92+
} else if (lastEntry && line.startsWith('*** Skipped')) {
93+
this.addLogIssue(
94+
lastEntry.timestamp,
95+
'Skipped-Lines',
96+
`${line}. A section of the log has been skipped and the log has been truncated. Full details of this section of log can not be provided.`,
97+
'skip',
98+
);
99+
} else if (lastEntry && line.indexOf('MAXIMUM DEBUG LOG SIZE REACHED') !== -1) {
100+
this.addLogIssue(
101+
lastEntry.timestamp,
102+
'Max-Size-reached',
103+
'The maximum log size has been reached. Part of the log has been truncated.',
104+
'skip',
105+
);
106+
this.maxSizeTimestamp = lastEntry.timestamp;
107+
} else if (!hasType && settingsPattern.test(line)) {
108+
// skip an unexpected settings line
88109
} else {
89-
if (lastEntry && line.startsWith('*** Skipped')) {
90-
this.addLogIssue(
91-
lastEntry.timestamp,
92-
'Skipped-Lines',
93-
`${line}. A section of the log has been skipped and the log has been truncated. Full details of this section of log can not be provided.`,
94-
'skip',
95-
);
96-
} else if (lastEntry && line.indexOf('MAXIMUM DEBUG LOG SIZE REACHED') >= 0) {
97-
this.addLogIssue(
98-
lastEntry.timestamp,
99-
'Max-Size-reached',
100-
'The maximum log size has been reached. Part of the log has been truncated.',
101-
'skip',
102-
);
103-
this.maxSizeTimestamp = lastEntry.timestamp;
104-
} else if (settingsPattern.test(line)) {
105-
// skip an unexpected settings line
106-
} else {
107-
this.parsingErrors.push(`Invalid log line: ${line}`);
108-
}
110+
this.parsingErrors.push(`Invalid log line: ${line}`);
109111
}
110112

111113
return null;
112114
}
113115

114116
private *generateLogLines(log: string): Generator<LogLine> {
115-
const start = log.match(/^.*EXECUTION_STARTED.*$/m)?.index || 0;
117+
const start = log.match(/^.*EXECUTION_STARTED.*$/m)?.index ?? 0;
116118
if (start > 0) {
117119
log = log.slice(start);
118120
}
119121

120122
const hascrlf = log.indexOf('\r\n') > -1;
121123
let lastEntry = null;
122-
let eolIndex = log.indexOf('\n');
124+
let lfIndex = null;
125+
let eolIndex = (lfIndex = log.indexOf('\n'));
123126
let startIndex = 0;
124127
let crlfIndex = -1;
125128

126129
while (eolIndex !== -1) {
127130
if (hascrlf && eolIndex > crlfIndex) {
128131
crlfIndex = log.indexOf('\r', eolIndex - 1);
132+
eolIndex = crlfIndex + 1 === eolIndex ? crlfIndex : lfIndex;
129133
}
130-
const line = log.slice(startIndex, crlfIndex + 1 === eolIndex ? crlfIndex : eolIndex);
134+
const line = log.slice(startIndex, eolIndex);
131135
if (line) {
132136
// ignore blank lines
133137
const entry = this.parseLine(line, lastEntry);
@@ -136,8 +140,8 @@ class ApexLogParser {
136140
yield entry;
137141
}
138142
}
139-
startIndex = eolIndex + 1;
140-
eolIndex = log.indexOf('\n', startIndex);
143+
startIndex = lfIndex + 1;
144+
lfIndex = eolIndex = log.indexOf('\n', startIndex);
141145
}
142146

143147
// Parse the last line
@@ -175,44 +179,41 @@ class ApexLogParser {
175179
}
176180

177181
private parseTree(currentLine: Method, lineIter: LineIterator, stack: Method[]) {
178-
let lastTimestamp = currentLine.timestamp;
182+
this.lastTimestamp = currentLine.timestamp;
179183
currentLine.namespace ||= 'default';
180184

181-
const isEntry = currentLine.exitTypes.length > 0;
185+
const isEntry = currentLine.exitTypes.length;
182186
if (isEntry) {
183187
const exitOnNextLine = currentLine.nextLineIsExit;
184188
let nextLine;
185189

186190
stack.push(currentLine);
187191

188192
while ((nextLine = lineIter.peek())) {
189-
lastTimestamp = nextLine.timestamp;
190193
// discontinuities are stack unwinding (caused by Exceptions)
191194
this.discontinuity ||= nextLine.discontinuity; // start unwinding stack
192195

193-
if (
194-
exitOnNextLine &&
195-
(nextLine.nextLineIsExit || nextLine.isExit || nextLine.exitTypes.length > 0)
196-
) {
197-
currentLine.exitStamp = nextLine.timestamp;
198-
currentLine.onEnd?.(nextLine, stack);
199-
break;
200-
}
201-
202196
// Exit Line has been found no more work needed
203197
if (
198+
!exitOnNextLine &&
204199
!nextLine.nextLineIsExit &&
205200
nextLine.isExit &&
201+
!nextLine.exitTypes.length &&
206202
this.endMethod(currentLine, nextLine, lineIter, stack)
207203
) {
208204
// the method wants to see the exit line
209205
currentLine.onEnd?.(nextLine, stack);
210206
break;
211-
}
212-
213-
if (
214-
this.maxSizeTimestamp &&
207+
} else if (
208+
exitOnNextLine &&
209+
(nextLine.nextLineIsExit || nextLine.isExit || nextLine.exitTypes.length > 0)
210+
) {
211+
currentLine.exitStamp = nextLine.timestamp;
212+
currentLine.onEnd?.(nextLine, stack);
213+
break;
214+
} else if (
215215
this.discontinuity &&
216+
this.maxSizeTimestamp &&
216217
nextLine.timestamp > this.maxSizeTimestamp
217218
) {
218219
// The current line was truncated (we did not find the exit line before the end of log) and there was a discontinuity
@@ -222,6 +223,7 @@ class ApexLogParser {
222223

223224
nextLine.namespace ||= currentLine.namespace || 'default';
224225
lineIter.fetch(); // it's a child - consume the line
226+
this.lastTimestamp = nextLine.timestamp;
225227

226228
if (nextLine instanceof Method) {
227229
this.parseTree(nextLine, lineIter, stack);
@@ -234,24 +236,24 @@ class ApexLogParser {
234236
// of the log without finding an exit line or the current line was truncated)
235237
if (!nextLine || currentLine.isTruncated) {
236238
// truncated method - terminate at the end of the log
237-
currentLine.exitStamp = lastTimestamp;
239+
currentLine.exitStamp = this.lastTimestamp;
238240

239241
// we found an entry event on its own e.g a `METHOD_ENTRY` without a `METHOD_EXIT` and got to the end of the log
240242
this.addLogIssue(
241-
lastTimestamp,
243+
this.lastTimestamp,
242244
'Unexpected-End',
243245
'An entry event was found without a corresponding exit event e.g a `METHOD_ENTRY` event without a `METHOD_EXIT`',
244246
'unexpected',
245247
);
246248

247249
if (currentLine.isTruncated) {
248250
this.updateLogIssue(
249-
lastTimestamp,
251+
this.lastTimestamp,
250252
'Max-Size-reached',
251253
'The maximum log size has been reached. Part of the log has been truncated.',
252254
'skip',
253255
);
254-
this.maxSizeTimestamp = lastTimestamp;
256+
this.maxSizeTimestamp = this.lastTimestamp;
255257
}
256258
currentLine.isTruncated = true;
257259
}
@@ -322,7 +324,7 @@ class ApexLogParser {
322324
});
323325
}
324326
}
325-
currentNodes = result.get(currentDepth++) || [];
327+
currentNodes = result.get(currentDepth++) ?? [];
326328
len = currentNodes.length;
327329
}
328330

@@ -341,7 +343,7 @@ class ApexLogParser {
341343
const nodesByDepth = this.flattenByDepth(nodes);
342344
let depth = nodesByDepth.size;
343345
while (depth--) {
344-
const nds = nodesByDepth.get(depth) || [];
346+
const nds = nodesByDepth.get(depth) ?? [];
345347
let i = nds.length;
346348
while (i--) {
347349
const parent = nds[i];
@@ -353,6 +355,7 @@ class ApexLogParser {
353355
parent.duration.self -= child.duration.total;
354356
});
355357
}
358+
nodesByDepth.delete(depth);
356359
}
357360
}
358361

@@ -365,9 +368,7 @@ class ApexLogParser {
365368
for (let i = 0; i < len; i++) {
366369
const child = children[i];
367370
if (child) {
368-
const childType = child.type,
369-
isPkgType = childType === 'ENTERING_MANAGED_PKG';
370-
371+
const isPkgType = child.type === 'ENTERING_MANAGED_PKG';
371372
if (lastPkg && child instanceof TimedNode) {
372373
if (isPkgType && child.namespace === lastPkg.namespace) {
373374
// combine adjacent (like) packages
@@ -456,7 +457,7 @@ export class DebugLevel {
456457
}
457458

458459
export class LineIterator {
459-
next: LogLine | null = null;
460+
next: LogLine | null;
460461
lineGenerator: Generator<LogLine>;
461462

462463
constructor(lineGenerator: Generator<LogLine>) {
@@ -506,7 +507,7 @@ export abstract class LogLine {
506507
/**
507508
* A parsed version of the log line text useful for display in UIs
508509
*/
509-
text = '';
510+
text;
510511

511512
// optional metadata
512513
/**
@@ -563,7 +564,7 @@ export abstract class LogLine {
563564
/**
564565
* The timestamp of this log line, in nanoseconds
565566
*/
566-
timestamp = 0;
567+
timestamp;
567568

568569
/**
569570
* The time spent.
@@ -628,9 +629,11 @@ export abstract class LogLine {
628629
constructor(parts: string[] | null) {
629630
if (parts) {
630631
const [timeData, type] = parts;
631-
this.type = type as LogEventType;
632-
this.text = this.type;
633-
this.timestamp = this.parseTimestamp(timeData as string);
632+
this.text = this.type = type as LogEventType;
633+
this.timestamp = this.parseTimestamp(timeData || '');
634+
} else {
635+
this.timestamp = 0;
636+
this.text = '';
634637
}
635638
}
636639

@@ -2468,23 +2471,23 @@ class MatchEngineBegin extends Method {
24682471
}
24692472

24702473
function getLogEventClass(eventName: LogEventType): LogLineConstructor | null | undefined {
2474+
if (!eventName) {
2475+
return null;
2476+
}
2477+
24712478
// Fast path for the most commonly occuring types
24722479
switch (eventName) {
24732480
case 'METHOD_ENTRY':
24742481
return MethodEntryLine;
2475-
break;
24762482

24772483
case 'METHOD_EXIT':
24782484
return MethodExitLine;
2479-
break;
24802485

24812486
case 'CONSTRUCTOR_ENTRY':
24822487
return ConstructorEntryLine;
2483-
break;
24842488

24852489
case 'CONSTRUCTOR_EXIT':
24862490
return ConstructorExitLine;
2487-
break;
24882491

24892492
default:
24902493
break;
@@ -2504,7 +2507,10 @@ function getLogEventClass(eventName: LogEventType): LogLineConstructor | null |
25042507
}
25052508

25062509
type LogLineConstructor<T extends LogLine = LogLine> = new (parts: string[]) => T;
2507-
export const lineTypeMap = new Map<LogEventType, LogLineConstructor>([
2510+
export const lineTypeMap: ReadonlyMap<LogEventType, LogLineConstructor> = new Map<
2511+
LogEventType,
2512+
LogLineConstructor
2513+
>([
25082514
['BULK_DML_RETRY', BulkDMLEntry],
25092515
['BULK_HEAP_ALLOCATE', BulkHeapAllocateLine],
25102516
['CALLOUT_REQUEST', CalloutRequestLine],

0 commit comments

Comments
 (0)