diff --git a/CHANGELOG.md b/CHANGELOG.md index fb9dc2f5..f48aef08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Table actions: Copy to clipboard and Export to CSV directly from the button above the Analysis and Database table ([#589]). - Sort grouped rows by clicking column headers ([#592]). - Tri state on Group name column will sort by number of items in the group +- Replace the Rows column on the Call Tree with two seperate columns, DML rows and SOQL rows ([#93]). +- Replace Rows count on the Timeline tooltip with two new counts, DML rows and SOQL rows ([#93]). ### Changed @@ -391,6 +393,7 @@ Skipped due to adopting odd numbering for pre releases and even number for relea [#589]: https://github.com/certinia/debug-log-analyzer/issues/589 [#590]: https://github.com/certinia/debug-log-analyzer/issues/590 [#592]: https://github.com/certinia/debug-log-analyzer/issues/592 +[#93]: https://github.com/certinia/debug-log-analyzer/issues/93 diff --git a/log-viewer/modules/__tests__/ApexLogParser.test.ts b/log-viewer/modules/__tests__/ApexLogParser.test.ts index c1cb2cca..2b711ca7 100644 --- a/log-viewer/modules/__tests__/ApexLogParser.test.ts +++ b/log-viewer/modules/__tests__/ApexLogParser.test.ts @@ -432,7 +432,8 @@ describe('parseLog tests', () => { parent: execEvent, type: 'SOQL_EXECUTE_BEGIN', aggregations: 2, - rowCount: { self: 50, total: 50 }, + soqlRowCount: { self: 50, total: 50 }, + soqlCount: { self: 1, total: 1 }, }); const soqlExplain = soqlLine.children[0] as SOQLExecuteExplainLine; @@ -1179,3 +1180,182 @@ describe('Line Type Tests', () => { expect(qp.sObjectType).toBe(null); }); }); + +describe('Aggregating Totals', () => { + it('should sum from child to parent', () => { + const logArray = [ + '01:02:03.04 (0)|EXECUTION_STARTED', + '01:02:03.04 (1)|METHOD_ENTRY|[1]|a00000000000000|ns.MyClass.myMethod()', + '01:02:03.04 (2)|METHOD_ENTRY|[1]|a00000000000000|ns.MyClass.soql()', + '01:02:03.04 (3)|SOQL_EXECUTE_BEGIN|[2]|Aggregations:0|SELECT ID FROM MyObject__c', + '01:02:03.04 (4)|SOQL_EXECUTE_END|[2]|Rows:1', + '01:02:03.04 (5)|SOQL_EXECUTE_BEGIN|[2]|Aggregations:0|SELECT ID FROM MyObject__c', + '01:02:03.04 (6)|SOQL_EXECUTE_END|[2]|Rows:2', + '01:02:03.04 (7)|METHOD_EXIT|[1]|a00000000000000|ns.MyClass.soql()', + '01:02:03.04 (8)|METHOD_ENTRY|[1]|a00000000000000|ns.MyClass.dml()', + '01:02:03.04 (9)|DML_BEGIN|[194]|Op:Update|Type:ns2__MyObject__c|Rows:1', + '01:02:03.04 (10)|DML_END|[194]', + '01:02:03.04 (11)|DML_BEGIN|[194]|Op:Update|Type:ns2__MyObject__c|Rows:4', + '01:02:03.04 (12)|DML_END|[194]', + '01:02:03.04 (13)|METHOD_EXIT|[1]|a00000000000000|ns.MyClass.dml()', + '01:02:03.04 (14)|METHOD_ENTRY|[1]|a00000000000000|ns.MyClass.sosl()', + "01:02:03.04 (15)|SOSL_EXECUTE_BEGIN|[1]|FIND 'hello*' IN ALL FIELDS RETURNING account(Id, Name)", + '01:02:03.04 (16)|SOSL_EXECUTE_END|[1]|Rows:250', + "01:02:03.04 (17)|SOSL_EXECUTE_BEGIN|[1]|FIND 'hello*' IN ALL FIELDS RETURNING account(Id, Name)", + '01:02:03.04 (18)|SOSL_EXECUTE_END|[1]|Rows:150', + '01:02:03.04 (19)|EXCEPTION_THROWN|[60]|System.LimitException: c2g:Too many SOQL queries: 101', + '01:02:03.04 (20)|METHOD_EXIT|[1]|a00000000000000|ns.MyClass.sosl()', + '01:02:03.04 (21)|EXCEPTION_THROWN|[60]|System.LimitException: c2g:Too many SOQL queries: 101', + '01:02:03.04 (22)|EXCEPTION_THROWN|[60]|System.LimitException: c2g:Too many SOQL queries: 101', + '01:02:03.04 (23)|METHOD_EXIT|[1]|a00000000000000|ns.MyClass.myMethod()', + '01:02:03.04 (24)|EXECUTION_FINISHED', + ]; + const log1 = logArray.join('\n'); + + const defaultCounts = { + dmlCount: { total: 0, self: 0 }, + soqlCount: { total: 0, self: 0 }, + soslCount: { total: 0, self: 0 }, + dmlRowCount: { total: 0, self: 0 }, + soqlRowCount: { total: 0, self: 0 }, + soslRowCount: { total: 0, self: 0 }, + totalThrownCount: 0, + }; + + const apexLog = parse(log1); + expect(apexLog).toMatchObject( + // EXECUTION_STARTED + { + duration: { total: 24, self: 0 }, + dmlCount: { total: 2, self: 0 }, + soqlCount: { total: 2, self: 0 }, + soslCount: { total: 2, self: 0 }, + dmlRowCount: { total: 5, self: 0 }, + soqlRowCount: { total: 3, self: 0 }, + soslRowCount: { total: 400, self: 0 }, + type: null, + children: [ + { + duration: { total: 24, self: 2 }, + dmlCount: { total: 2, self: 0 }, + soqlCount: { total: 2, self: 0 }, + soslCount: { total: 2, self: 0 }, + dmlRowCount: { total: 5, self: 0 }, + soqlRowCount: { total: 3, self: 0 }, + soslRowCount: { total: 400, self: 0 }, + totalThrownCount: 3, + logLine: logArray[0], + children: [ + // ns.MyClass.myMethod() + { + duration: { total: 22, self: 6 }, + dmlCount: { total: 2, self: 0 }, + soqlCount: { total: 2, self: 0 }, + soslCount: { total: 2, self: 0 }, + dmlRowCount: { total: 5, self: 0 }, + soqlRowCount: { total: 3, self: 0 }, + soslRowCount: { total: 400, self: 0 }, + totalThrownCount: 3, + logLine: logArray[1], + children: [ + // ns.MyClass.soql() + { + ...defaultCounts, + duration: { total: 5, self: 3 }, + logLine: logArray[2], + soqlCount: { total: 2, self: 0 }, + soqlRowCount: { total: 3, self: 0 }, + children: [ + //SELECT ID FROM MyObject__c + { + ...defaultCounts, + duration: { total: 1, self: 1 }, + soqlCount: { total: 1, self: 1 }, + soqlRowCount: { total: 1, self: 1 }, + logLine: logArray[3], + }, + // SELECT ID FROM MyObject__c + { + ...defaultCounts, + duration: { total: 1, self: 1 }, + soqlCount: { total: 1, self: 1 }, + soqlRowCount: { total: 2, self: 2 }, + logLine: logArray[5], + }, + ], + }, + // ns.MyClass.dml() + { + ...defaultCounts, + duration: { total: 5, self: 3 }, + dmlCount: { total: 2, self: 0 }, + dmlRowCount: { total: 5, self: 0 }, + logLine: logArray[8], + children: [ + { + ...defaultCounts, + duration: { total: 1, self: 1 }, + dmlCount: { total: 1, self: 1 }, + dmlRowCount: { total: 1, self: 1 }, + logLine: logArray[9], + }, + { + ...defaultCounts, + duration: { total: 1, self: 1 }, + dmlCount: { total: 1, self: 1 }, + dmlRowCount: { total: 4, self: 4 }, + logLine: logArray[11], + }, + ], + }, + //ns.MyClass.sosl() + { + ...defaultCounts, + duration: { total: 6, self: 4 }, + soslCount: { total: 2, self: 0 }, + soslRowCount: { total: 400, self: 0 }, + totalThrownCount: 1, + logLine: logArray[14], + children: [ + { + ...defaultCounts, + duration: { total: 1, self: 1 }, + soslCount: { total: 1, self: 1 }, + soslRowCount: { total: 250, self: 250 }, + totalThrownCount: 0, + logLine: logArray[15], + }, + { + ...defaultCounts, + duration: { total: 1, self: 1 }, + soslCount: { total: 1, self: 1 }, + soslRowCount: { total: 150, self: 150 }, + totalThrownCount: 0, + logLine: logArray[17], + }, + // Exception + { + ...defaultCounts, + totalThrownCount: 1, + }, + ], + }, + // Exception + { + ...defaultCounts, + totalThrownCount: 1, + }, + // Exception + { + ...defaultCounts, + totalThrownCount: 1, + }, + ], + }, + ], + }, + ], + }, + ); + }); +}); diff --git a/log-viewer/modules/components/calltree-view/CalltreeView.ts b/log-viewer/modules/components/calltree-view/CalltreeView.ts index a9822c5c..a42501fc 100644 --- a/log-viewer/modules/components/calltree-view/CalltreeView.ts +++ b/log-viewer/modules/components/calltree-view/CalltreeView.ts @@ -678,7 +678,7 @@ export class CalltreeView extends LitElement { }, { title: 'DML Count', - field: 'totalDmlCount', + field: 'dmlCount.total', sorter: 'number', width: 60, hozAlign: 'right', @@ -687,7 +687,7 @@ export class CalltreeView extends LitElement { }, { title: 'SOQL Count', - field: 'totalSoqlCount', + field: 'soqlCount.total', sorter: 'number', width: 60, hozAlign: 'right', @@ -704,8 +704,17 @@ export class CalltreeView extends LitElement { bottomCalc: 'max', }, { - title: 'Rows', - field: 'rows', + title: 'DML Rows', + field: 'dmlRowCount.total', + sorter: 'number', + width: 60, + hozAlign: 'right', + headerHozAlign: 'right', + bottomCalc: 'max', + }, + { + title: 'SOQL Rows', + field: 'soqlRowCount.total', sorter: 'number', width: 60, hozAlign: 'right', @@ -714,7 +723,7 @@ export class CalltreeView extends LitElement { }, { title: 'Total Time (ms)', - field: 'duration', + field: 'duration.total', sorter: 'number', headerSortTristate: true, width: 150, @@ -736,7 +745,7 @@ export class CalltreeView extends LitElement { }, { title: 'Self Time (ms)', - field: 'selfTime', + field: 'duration.self', sorter: 'number', headerSortTristate: true, width: 150, @@ -753,7 +762,10 @@ export class CalltreeView extends LitElement { }, headerFilter: MinMaxEditor, headerFilterFunc: MinMaxFilter, - headerFilterFuncParams: { columnName: 'selfTime', filterCache: selfTimeFilterCache }, + headerFilterFuncParams: { + columnName: 'duration.self', + filterCache: selfTimeFilterCache, + }, headerFilterLiveFilter: false, }, ], @@ -829,16 +841,16 @@ export class CalltreeView extends LitElement { const children = node.children.length ? this._toCallTree(node.children) : null; results.push({ id: node.timestamp + '-' + i, + originalData: node, + _children: children, text: node.text, namespace: node.namespace, - duration: node.duration.total, - selfTime: node.duration.self, - _children: children, - totalDmlCount: node.dmlCount.total, - totalSoqlCount: node.soqlCount.total, + duration: node.duration, + dmlCount: node.dmlCount, + soqlCount: node.soqlCount, + dmlRowCount: node.dmlRowCount, + soqlRowCount: node.soqlRowCount, totalThrownCount: node.totalThrownCount, - rows: node.rowCount.total, - originalData: node, }); } return results; @@ -888,17 +900,19 @@ export class CalltreeView extends LitElement { interface CalltreeRow { id: string; originalData: LogLine; + _children: CalltreeRow[] | undefined | null; text: string; - duration: number; + duration: CountTotals; namespace: string; - selfTime: number; - _children: CalltreeRow[] | undefined | null; - totalDmlCount: number; - totalSoqlCount: number; + dmlCount: CountTotals; + soqlCount: CountTotals; + dmlRowCount: CountTotals; + soqlRowCount: CountTotals; totalThrownCount: number; - rows: number; } +type CountTotals = { self: number; total: number }; + export async function goToRow(timestamp: number) { document.dispatchEvent( new CustomEvent('calltree-go-to-row', { detail: { timestamp: timestamp } }), diff --git a/log-viewer/modules/components/database-view/DMLView.ts b/log-viewer/modules/components/database-view/DMLView.ts index 66513405..e6b9f8e3 100644 --- a/log-viewer/modules/components/database-view/DMLView.ts +++ b/log-viewer/modules/components/database-view/DMLView.ts @@ -256,7 +256,7 @@ export class DMLView extends LitElement { for (const dml of dmlLines) { dmlData.push({ dml: dml.text, - rowCount: dml.rowCount.self, + rowCount: dml.dmlRowCount.self, timeTaken: dml.duration.total, timestamp: dml.timestamp, _children: [{ timestamp: dml.timestamp, isDetail: true }], diff --git a/log-viewer/modules/components/database-view/DatabaseSection.ts b/log-viewer/modules/components/database-view/DatabaseSection.ts index ea8089d6..9764cfbb 100644 --- a/log-viewer/modules/components/database-view/DatabaseSection.ts +++ b/log-viewer/modules/components/database-view/DatabaseSection.ts @@ -36,7 +36,7 @@ export class DatabaseSection extends LitElement { const totalCount = this.dbLines.length; let totalRows = 0; this.dbLines.forEach((value) => { - totalRows += value.rowCount.self || 0; + totalRows += value.dmlRowCount.self || value.soqlRowCount.self || 0; }); return html` diff --git a/log-viewer/modules/components/database-view/SOQLView.ts b/log-viewer/modules/components/database-view/SOQLView.ts index 6aac16f1..db59b0bb 100644 --- a/log-viewer/modules/components/database-view/SOQLView.ts +++ b/log-viewer/modules/components/database-view/SOQLView.ts @@ -296,7 +296,7 @@ export class SOQLView extends LitElement { relativeCost: explainLine?.relativeCost, soql: soql.text, namespace: soql.namespace, - rowCount: soql.rowCount.self, + rowCount: soql.soqlRowCount.self, timeTaken: soql.duration.total, aggregations: soql.aggregations, timestamp: soql.timestamp, diff --git a/log-viewer/modules/parsers/ApexLogParser.ts b/log-viewer/modules/parsers/ApexLogParser.ts index 14b1b604..0c6860f1 100644 --- a/log-viewer/modules/parsers/ApexLogParser.ts +++ b/log-viewer/modules/parsers/ApexLogParser.ts @@ -350,9 +350,12 @@ export class ApexLogParser { parent?.children.forEach((child) => { parent.dmlCount.total += child.dmlCount.total; parent.soqlCount.total += child.soqlCount.total; - parent.totalThrownCount += child.totalThrownCount; - parent.rowCount.total += child.rowCount.total; + parent.soslCount.total += child.soslCount.total; + parent.dmlRowCount.total += child.dmlRowCount.total; + parent.soqlRowCount.total += child.soqlRowCount.total; + parent.soslRowCount.total += child.soslRowCount.total; parent.duration.self -= child.duration.total; + parent.totalThrownCount += child.totalThrownCount; }); } nodesByDepth.delete(depth); @@ -585,22 +588,50 @@ export abstract class LogLine { }; /** - * Total + self row counts for DML, SOQL + SOSL. + * Total + self row counts for DML + */ + dmlRowCount: SelfTotal = { + /** + * The net number of DML rows for this node, excluding child nodes + */ + self: 0, + /** + * The total number of DML rows for this node and child nodes + */ + total: 0, + }; + + /** + * Total + self row counts for SOQL */ - rowCount: SelfTotal = { + soqlRowCount: SelfTotal = { /** - * The number of rows in all database operations for this node, excluding child nodes + * The net number of SOQL rows for this node, excluding child nodes */ self: 0, /** - * The total number of rows in all database operations for this node and child nodes + * The total number of SOQL rows for this node and child nodes + */ + total: 0, + }; + + /** + * Total + self row counts for SOSL + */ + soslRowCount: SelfTotal = { + /** + * The net number of SOSL rows for this node, excluding child nodes + */ + self: 0, + /** + * The total number of SOSL rows for this node and child nodes */ total: 0, }; dmlCount: SelfTotal = { /** - * The net number of DML operations (DML_BEGIN) in this node in this node. + * The net number of DML operations (DML_BEGIN) in this node. */ self: 0, /** @@ -620,6 +651,17 @@ export abstract class LogLine { total: 0, }; + soslCount: SelfTotal = { + /** + * The net number of SOSL operations (SOSL_EXECUTE_BEGIN) in this node. + */ + self: 0, + /** + * The total number of SOSL operations (SOSL_EXECUTE_BEGIN) in this node and child nodes + */ + total: 0, + }; + /** * The total number of exceptions thrown (EXCEPTION_THROWN) in this node and child nodes */ @@ -1252,7 +1294,7 @@ class DMLBeginLine extends Method { this.lineNumber = this.parseLineNumber(parts[2]); this.text = 'DML ' + parts[3] + ' ' + parts[4]; const rowCountString = parts[5]; - this.rowCount.total = this.rowCount.self = rowCountString ? parseRows(rowCountString) : 0; + this.dmlRowCount.total = this.dmlRowCount.self = rowCountString ? parseRows(rowCountString) : 0; } } @@ -1294,7 +1336,7 @@ class SOQLExecuteBeginLine extends Method { } onEnd(end: SOQLExecuteEndLine, _stack: LogLine[]): void { - this.rowCount.total = this.rowCount.self = end.rowCount.total; + this.soqlRowCount.total = this.soqlRowCount.self = end.soqlRowCount.total; } } @@ -1304,7 +1346,7 @@ class SOQLExecuteEndLine extends LogLine { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.lineNumber = this.parseLineNumber(parts[2]); - this.rowCount.total = this.rowCount.self = parseRows(parts[3] || ''); + this.soqlRowCount.total = this.soqlRowCount.self = parseRows(parts[3] || ''); } } @@ -1359,10 +1401,15 @@ class SOSLExecuteBeginLine extends Method { super(parser, parts, ['SOSL_EXECUTE_END'], 'SOQL', 'free'); this.lineNumber = this.parseLineNumber(parts[2]); this.text = `SOSL: ${parts[3]}`; + + this.soslCount = { + self: 1, + total: 1, + }; } onEnd(end: SOSLExecuteEndLine, _stack: LogLine[]): void { - this.rowCount.total = this.rowCount.self = end.rowCount.total; + this.soslRowCount.total = this.soslRowCount.self = end.soslRowCount.total; } } @@ -1372,7 +1419,7 @@ class SOSLExecuteEndLine extends LogLine { constructor(parser: ApexLogParser, parts: string[]) { super(parser, parts); this.lineNumber = this.parseLineNumber(parts[2]); - this.rowCount.total = this.rowCount.self = parseRows(parts[3] || ''); + this.soslRowCount.total = this.soslRowCount.self = parseRows(parts[3] || ''); } } diff --git a/log-viewer/modules/timeline/Timeline.ts b/log-viewer/modules/timeline/Timeline.ts index b35dad42..0f0e103c 100644 --- a/log-viewer/modules/timeline/Timeline.ts +++ b/log-viewer/modules/timeline/Timeline.ts @@ -678,14 +678,10 @@ function findTimelineTooltip( const toolTip = document.createElement('div'); const brElem = document.createElement('br'); - let displayText = target.text; - if (target.suffix) { - displayText += target.suffix; - } toolTip.appendChild(document.createTextNode(target.type || '')); toolTip.appendChild(brElem.cloneNode()); - toolTip.appendChild(document.createTextNode(displayText)); + toolTip.appendChild(document.createTextNode(target.text + (target.suffix ?? ''))); if (target.timestamp) { toolTip.appendChild(brElem.cloneNode()); toolTip.appendChild(document.createTextNode('timestamp: ' + target.timestamp)); @@ -706,11 +702,54 @@ function findTimelineTooltip( ); } - if (target.rowCount.total !== null) { + if (target.dmlCount.total) { + toolTip.appendChild(brElem.cloneNode()); + toolTip.appendChild( + document.createTextNode(`DML: ${target.dmlCount.total} (self ${target.dmlCount.self})`), + ); + } + + if (target.dmlRowCount.total) { + toolTip.appendChild(brElem.cloneNode()); + toolTip.appendChild( + document.createTextNode( + `DML rows: ${target.dmlRowCount.total} (self ${target.dmlRowCount.self})`, + ), + ); + } + + if (target.soqlCount.total) { + toolTip.appendChild(brElem.cloneNode()); + toolTip.appendChild( + document.createTextNode( + `SOQL: ${target.soqlCount.total} (self ${target.soqlCount.self})`, + ), + ); + } + + if (target.soqlRowCount.total) { + toolTip.appendChild(brElem.cloneNode()); + toolTip.appendChild( + document.createTextNode( + `SOQL rows: ${target.soqlRowCount.total} (self ${target.soqlRowCount.self})`, + ), + ); + } + + if (target.soslCount.total) { + toolTip.appendChild(brElem.cloneNode()); + toolTip.appendChild( + document.createTextNode( + `SOSL: ${target.soslCount.total} (self ${target.soslCount.self})`, + ), + ); + } + + if (target.soslRowCount.total) { toolTip.appendChild(brElem.cloneNode()); toolTip.appendChild( document.createTextNode( - `rows: ${target.rowCount.total} (self ${target.rowCount.self})`, + `SOSL rows: ${target.soslRowCount.total} (self ${target.soslRowCount.self})`, ), ); }