RPP: show a download raw events button if the trace errors
In the event that the trace parsing fails, this means we have at least
gathered all the events. In that case, let's add a download button
because that will greatly aid in debugging, and allow users to report
bugs with traces even if the engine fails.
Bug: none
Change-Id: I9371b8ccf04558c2a5c34a0f2eb65da456e5c5e0
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/5000426
Commit-Queue: Jack Franklin <[email protected]>
Auto-Submit: Jack Franklin <[email protected]>
Reviewed-by: Andres Olivares <[email protected]>
Commit-Queue: Andres Olivares <[email protected]>
diff --git a/front_end/panels/timeline/TimelinePanel.ts b/front_end/panels/timeline/TimelinePanel.ts
index ca14383..2bad8f1 100644
--- a/front_end/panels/timeline/TimelinePanel.ts
+++ b/front_end/panels/timeline/TimelinePanel.ts
@@ -168,6 +168,10 @@
*/
close: 'Close',
/**
+ *@description Text to download the raw trace files after an error
+ */
+ downloadAfterError: 'Download raw trace events',
+ /**
*@description Status text to indicate the recording has failed in the Performance panel
*/
recordingFailed: 'Recording failed',
@@ -1025,7 +1029,8 @@
}
}
- private async recordingFailed(error: string): Promise<void> {
+ private async recordingFailed(error: string, rawEvents?: TraceEngine.Types.TraceEvents.TraceEventData[]):
+ Promise<void> {
if (this.statusPane) {
this.statusPane.remove();
}
@@ -1045,6 +1050,10 @@
this.statusPane.showPane(this.statusPaneContainer);
this.statusPane.updateStatus(i18nString(UIStrings.recordingFailed));
+ if (rawEvents) {
+ this.statusPane.enableDownloadOfEvents(rawEvents);
+ }
+
this.setState(State.RecordingFailed);
this.performanceModel = null;
this.traceLoadStart = null;
@@ -1378,6 +1387,7 @@
this.#executeNewTraceEngine(
tracingModel, recordingIsFresh, isCpuProfile, this.performanceModel.recordStartTime()),
]);
+
// This code path is only executed when a new trace is recorded/imported,
// so we know that the active index will be the size of the model because
// the newest trace will be automatically set to active.
@@ -1425,7 +1435,18 @@
this.#minimapComponent.addInitialBreadcrumb();
}
} catch (error) {
- void this.recordingFailed(error.message);
+ // Try to get the raw events: if we errored during the parsing stage, it
+ // is useful to get access to the raw events to download the trace. This
+ // allows us to debug crashes!
+ // Because we don't know where the error came from, we wrap it in a
+ // try-catch to protect against the tracing model erroring.
+ let rawEvents: TraceEngine.Types.TraceEvents.TraceEventData[]|undefined = undefined;
+ try {
+ rawEvents = tracingModel.allRawEvents() as unknown as TraceEngine.Types.TraceEvents.TraceEventData[];
+ } catch {
+ }
+
+ void this.recordingFailed(error.message, rawEvents);
console.error(error);
} finally {
this.recordTraceLoadMetric();
@@ -1659,8 +1680,10 @@
private progressBar!: Element;
private readonly description: HTMLElement|undefined;
private button: HTMLButtonElement;
+ private downloadTraceButton: HTMLButtonElement;
private startTime!: number;
private timeUpdateTimer?: number;
+ #rawEvents?: TraceEngine.Types.TraceEvents.TraceEventData[];
constructor(
options: {
@@ -1700,11 +1723,21 @@
this.description.innerText = options.description;
}
+ const buttonContainer = this.contentElement.createChild('div', 'stop-button');
+ this.downloadTraceButton = UI.UIUtils.createTextButton(i18nString(UIStrings.downloadAfterError), async () => {
+ void this.#downloadRawTraceAfterError();
+ });
+
+ this.downloadTraceButton.disabled = true;
+ this.downloadTraceButton.style.visibility = 'hidden';
+
const buttonText = options.buttonText || i18nString(UIStrings.stop);
this.button = UI.UIUtils.createTextButton(buttonText, buttonCallback, '', true);
// Profiling can't be stopped during initialization.
this.button.disabled = !options.buttonDisabled === false;
- this.contentElement.createChild('div', 'stop-button').appendChild(this.button);
+
+ buttonContainer.append(this.downloadTraceButton);
+ buttonContainer.append(this.button);
}
finish(): void {
@@ -1712,6 +1745,30 @@
this.button.disabled = true;
}
+ async #downloadRawTraceAfterError(): Promise<void> {
+ if (!this.#rawEvents || this.#rawEvents.length === 0) {
+ return;
+ }
+ const traceStart = Platform.DateUtilities.toISO8601Compact(new Date());
+ const fileName = `Trace-Load-Error-${traceStart}.json` as Platform.DevToolsPath.RawPathString;
+ const handler = await window.showSaveFilePicker({
+ suggestedName: fileName,
+ });
+ const formattedTraceIter = traceJsonGenerator(this.#rawEvents, {});
+ const traceAsString = Array.from(formattedTraceIter).join('');
+ const encoder = new TextEncoder();
+ const buffer = encoder.encode(traceAsString);
+ const writable = await handler.createWritable();
+ await writable.write(buffer);
+ await writable.close();
+ }
+
+ enableDownloadOfEvents(rawEvents: TraceEngine.Types.TraceEvents.TraceEventData[]): void {
+ this.#rawEvents = rawEvents;
+ this.downloadTraceButton.disabled = false;
+ this.downloadTraceButton.style.visibility = 'visible';
+ }
+
remove(): void {
(this.element.parentNode as HTMLElement).classList.remove('tinted');
this.arrangeDialog((this.element.parentNode as HTMLElement));