ui: remove JobStatusUpdate and simplify sidebar logic
This CL simplifies the way deferred conversion jobs
are handled AND the sidebar code.
The end user feature is the following: some of the actions
accessible by the sidebar (convert to json or legacy ui)
take several seconds and are offloaded to a worker. Today
we show a spinner on the sidebar when these are in progress.
Today the way the progress tracking works involves a lot of
plumbing all over the places (globals, publish, etc).
Instead I'm changing this as follows:
- I rewrote the long conversion operations as promises, which
resolve when the conversion is done (which is notified by
a postmessage from the dedicated worker)
- I updated the spinner logic in the sidebar to apply to any
case when the action of the menu item returns a promise.
This removes a lot of special-case handling for those spinners.
Change-Id: I41fc99be10cb789d230551220da66b0f00db7531
diff --git a/ui/src/common/conversion_jobs.ts b/ui/src/common/conversion_jobs.ts
deleted file mode 100644
index 6805bc7..0000000
--- a/ui/src/common/conversion_jobs.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright (C) 2021 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-export enum ConversionJobStatus {
- InProgress = 'InProgress',
- NotRunning = 'NotRunning',
-}
-
-export type ConversionJobName =
- | 'convert_systrace'
- | 'convert_json'
- | 'open_in_legacy'
- | 'convert_pprof'
- | 'create_permalink';
-
-export interface ConversionJobStatusUpdate {
- jobName: ConversionJobName;
- jobStatus: ConversionJobStatus;
-}
diff --git a/ui/src/core_plugins/commands/index.ts b/ui/src/core_plugins/commands/index.ts
index f539cd3..4170338 100644
--- a/ui/src/core_plugins/commands/index.ts
+++ b/ui/src/core_plugins/commands/index.ts
@@ -347,10 +347,9 @@
'Open trace in Legacy UI',
);
if (await isLegacyTrace(file)) {
- openFileWithLegacyTraceViewer(file);
- return;
+ return await openFileWithLegacyTraceViewer(file);
}
- openInOldUIWithSizeCheck(file);
+ return await openInOldUIWithSizeCheck(file);
}
export const plugin: PluginDescriptor = {
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index 347bb91..063dd32 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -16,10 +16,6 @@
import {createStore, Store} from '../base/store';
import {Actions, DeferredAction} from '../common/actions';
import {CommandManagerImpl} from '../core/command_manager';
-import {
- ConversionJobName,
- ConversionJobStatus,
-} from '../common/conversion_jobs';
import {createEmptyState} from '../common/empty_state';
import {State} from '../common/state';
import {setPerfHooks} from '../core/perf';
@@ -49,7 +45,6 @@
private _trackDataStore?: TrackDataStore = undefined;
private _bufferUsage?: number = undefined;
private _recordingLog?: string = undefined;
- private _jobStatus?: Map<ConversionJobName, ConversionJobStatus> = undefined;
httpRpcState: HttpRpcState = {connected: false};
showPanningHint = false;
permalinkHash?: string;
@@ -153,26 +148,6 @@
return this._recordingLog;
}
- getConversionJobStatus(name: ConversionJobName): ConversionJobStatus {
- return this.getJobStatusMap().get(name) ?? ConversionJobStatus.NotRunning;
- }
-
- setConversionJobStatus(name: ConversionJobName, status: ConversionJobStatus) {
- const map = this.getJobStatusMap();
- if (status === ConversionJobStatus.NotRunning) {
- map.delete(name);
- } else {
- map.set(name, status);
- }
- }
-
- private getJobStatusMap(): Map<ConversionJobName, ConversionJobStatus> {
- if (!this._jobStatus) {
- this._jobStatus = new Map();
- }
- return this._jobStatus;
- }
-
setBufferUsage(bufferUsage: number) {
this._bufferUsage = bufferUsage;
}
diff --git a/ui/src/frontend/legacy_trace_viewer.ts b/ui/src/frontend/legacy_trace_viewer.ts
index 3246713..04668f0 100644
--- a/ui/src/frontend/legacy_trace_viewer.ts
+++ b/ui/src/frontend/legacy_trace_viewer.ts
@@ -172,16 +172,21 @@
});
}
-export function openInOldUIWithSizeCheck(trace: Blob) {
+export async function openInOldUIWithSizeCheck(trace: Blob): Promise<void> {
// Perfetto traces smaller than 50mb can be safely opened in the legacy UI.
if (trace.size < 1024 * 1024 * 50) {
- convertToJson(trace, openBufferWithLegacyTraceViewer);
- return;
+ return await convertToJson(trace, openBufferWithLegacyTraceViewer);
}
// Give the user the option to truncate larger perfetto traces.
const size = Math.round(trace.size / (1024 * 1024));
- showModal({
+
+ // If the user presses one of the buttons below, remember the promise that
+ // they trigger, so we await for it before returning.
+ let nextPromise: Promise<void> | undefined;
+ const setNextPromise = (p: Promise<void>) => (nextPromise = p);
+
+ await showModal({
title: 'Legacy UI may fail to open this trace',
content: m(
'div',
@@ -206,30 +211,38 @@
buttons: [
{
text: 'Open full trace (not recommended)',
- action: () => convertToJson(trace, openBufferWithLegacyTraceViewer),
+ action: () =>
+ setNextPromise(convertToJson(trace, openBufferWithLegacyTraceViewer)),
},
{
text: 'Open beginning of trace',
action: () =>
- convertToJson(
- trace,
- openBufferWithLegacyTraceViewer,
- /* truncate*/ 'start',
+ setNextPromise(
+ convertToJson(
+ trace,
+ openBufferWithLegacyTraceViewer,
+ /* truncate*/ 'start',
+ ),
),
},
{
text: 'Open end of trace',
primary: true,
action: () =>
- convertToJson(
- trace,
- openBufferWithLegacyTraceViewer,
- /* truncate*/ 'end',
+ setNextPromise(
+ convertToJson(
+ trace,
+ openBufferWithLegacyTraceViewer,
+ /* truncate*/ 'end',
+ ),
),
},
],
});
- return;
+ // nextPromise is undefined if the user just dimisses the dialog with ESC.
+ if (nextPromise !== undefined) {
+ await nextPromise;
+ }
}
// TraceViewer method that we wire up to trigger the file load.
diff --git a/ui/src/frontend/permalink.ts b/ui/src/frontend/permalink.ts
index 9590347..17ecb8f 100644
--- a/ui/src/frontend/permalink.ts
+++ b/ui/src/frontend/permalink.ts
@@ -15,7 +15,6 @@
import m from 'mithril';
import {assertExists} from '../base/logging';
import {Actions} from '../common/actions';
-import {ConversionJobStatus} from '../common/conversion_jobs';
import {
JsonSerialize,
parseAppState,
@@ -29,10 +28,6 @@
} from '../common/gcs_uploader';
import {globals} from './globals';
import {
- publishConversionJobStatusUpdate,
- publishPermalinkHash,
-} from './publish';
-import {
SERIALIZED_STATE_VERSION,
SerializedAppState,
} from '../public/state_serialization_schema';
@@ -40,6 +35,7 @@
import {showModal} from '../widgets/modal';
import {AppImpl} from '../core/app_impl';
import {Router} from '../core/router';
+import {publishPermalinkHash} from './publish';
// Permalink serialization has two layers:
// 1. Serialization of the app state (state_serialization.ts):
@@ -76,21 +72,8 @@
}
export async function createPermalink(opts: PermalinkOptions): Promise<void> {
- const jobName = 'create_permalink';
- publishConversionJobStatusUpdate({
- jobName,
- jobStatus: ConversionJobStatus.InProgress,
- });
-
- try {
- const hash = await createPermalinkInternal(opts);
- publishPermalinkHash(hash);
- } finally {
- publishConversionJobStatusUpdate({
- jobName,
- jobStatus: ConversionJobStatus.NotRunning,
- });
- }
+ const hash = await createPermalinkInternal(opts);
+ publishPermalinkHash(hash);
}
// Returns the file name, not the full url (i.e. the name of the GCS object).
diff --git a/ui/src/frontend/publish.ts b/ui/src/frontend/publish.ts
index 5eff236..26e0a4c 100644
--- a/ui/src/frontend/publish.ts
+++ b/ui/src/frontend/publish.ts
@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {ConversionJobStatusUpdate} from '../common/conversion_jobs';
import {raf} from '../core/raf_scheduler';
import {HttpRpcState} from '../trace_processor/http_rpc_engine';
import {globals} from './globals';
@@ -27,13 +26,6 @@
raf.scheduleFullRedraw();
}
-export function publishConversionJobStatusUpdate(
- job: ConversionJobStatusUpdate,
-) {
- globals.setConversionJobStatus(job.jobName, job.jobStatus);
- globals.publishRedraw();
-}
-
export function publishBufferUsage(args: {percentage: number}) {
globals.setBufferUsage(args.percentage);
globals.publishRedraw();
diff --git a/ui/src/frontend/sidebar.ts b/ui/src/frontend/sidebar.ts
index 0218d65..f5980c6 100644
--- a/ui/src/frontend/sidebar.ts
+++ b/ui/src/frontend/sidebar.ts
@@ -17,7 +17,6 @@
import {isString} from '../base/object_utils';
import {getCurrentChannel} from '../core/channels';
import {TRACE_SUFFIX} from '../common/constants';
-import {ConversionJobStatus} from '../common/conversion_jobs';
import {
disableMetatracingAndGetTrace,
enableMetatracing,
@@ -47,7 +46,6 @@
import {SidebarMenuItem} from '../public/sidebar';
import {AppImpl} from '../core/app_impl';
import {Trace} from '../public/trace';
-import {Router} from '../core/router';
const GITILES_URL =
'https://android.googlesource.com/platform/external/perfetto';
@@ -112,10 +110,9 @@
interface SectionItem {
t: string;
- a: string | ((e: Event) => void);
+ a: string | (() => void | Promise<void>);
i: string;
- title?: string;
- isPending?: () => boolean;
+ tooltip?: string;
isVisible?: () => boolean;
internalUserOnly?: boolean;
checkDownloadDisabled?: boolean;
@@ -150,10 +147,7 @@
: cmd.name;
return {
t: cmd.name,
- a: (e: Event) => {
- e.preventDefault();
- cmd.callback();
- },
+ a: cmd.callback,
i: item.icon,
title,
};
@@ -170,18 +164,18 @@
...insertSidebarMenuitems('navigation'),
{
t: 'Record new trace',
- a: (e: Event) => navigateToPage(e, 'record'),
+ a: '#!/record',
i: 'fiber_smart_record',
},
{
t: 'Widgets',
- a: (e: Event) => navigateToPage(e, 'widgets'),
+ a: '#!/widgets',
i: 'widgets',
isVisible: () => WIDGETS_PAGE_IN_NAV_FLAG.get(),
},
{
t: 'Plugins',
- a: (e: Event) => navigateToPage(e, 'plugins'),
+ a: '#!/plugins',
i: 'extension',
isVisible: () => PLUGINS_PAGE_IN_NAV_FLAG.get(),
},
@@ -194,59 +188,48 @@
hideIfNoTraceLoaded: true,
appendOpenedTraceTitle: true,
items: [
- {
- t: 'Show timeline',
- a: (e: Event) => navigateToPage(e, 'viewer'),
- i: 'line_style',
- },
+ {t: 'Show timeline', a: '#!/viewer', i: 'line_style'},
{
t: 'Share',
- a: handleShareTrace,
+ a: shareTrace,
i: 'share',
internalUserOnly: true,
- isPending: () =>
- globals.getConversionJobStatus('create_permalink') ===
- ConversionJobStatus.InProgress,
},
{
t: 'Download',
- a: (e: Event) => trace && downloadTrace(e, trace),
+ a: () => {
+ if (trace) {
+ downloadTrace(trace);
+ }
+ },
i: 'file_download',
checkDownloadDisabled: true,
},
{
t: 'Query (SQL)',
- a: (e: Event) => navigateToPage(e, 'query'),
+ a: '#!/query',
i: 'database',
},
{
t: 'Explore',
- a: (e: Event) => navigateToPage(e, 'explore'),
+ a: '#!/explore',
i: 'data_exploration',
isVisible: () => EXPLORE_PAGE_IN_NAV_FLAG.get(),
},
{
t: 'Insights',
- a: (e: Event) => navigateToPage(e, 'insights'),
+ a: '#!/insights',
i: 'insights',
isVisible: () => INSIGHTS_PAGE_IN_NAV_FLAG.get(),
},
{
t: 'Viz',
- a: (e: Event) => navigateToPage(e, 'viz'),
+ a: '#!/viz',
i: 'area_chart',
isVisible: () => VIZ_PAGE_IN_NAV_FLAG.get(),
},
- {
- t: 'Metrics',
- a: (e: Event) => navigateToPage(e, 'metrics'),
- i: 'speed',
- },
- {
- t: 'Info and stats',
- a: (e: Event) => navigateToPage(e, 'info'),
- i: 'info',
- },
+ {t: 'Metrics', a: '#!/metrics', i: 'speed'},
+ {t: 'Info and stats', a: '#!/info', i: 'info'},
],
},
@@ -260,17 +243,11 @@
t: 'Switch to legacy UI',
a: openCurrentTraceWithOldUI,
i: 'filter_none',
- isPending: () =>
- globals.getConversionJobStatus('open_in_legacy') ===
- ConversionJobStatus.InProgress,
},
{
t: 'Convert to .json',
a: convertTraceToJson,
i: 'file_download',
- isPending: () =>
- globals.getConversionJobStatus('convert_json') ===
- ConversionJobStatus.InProgress,
checkDownloadDisabled: true,
},
@@ -279,9 +256,6 @@
a: convertTraceToSystrace,
i: 'file_download',
isVisible: () => Boolean(trace?.traceInfo.hasFtrace),
- isPending: () =>
- globals.getConversionJobStatus('convert_systrace') ===
- ConversionJobStatus.InProgress,
checkDownloadDisabled: true,
},
],
@@ -299,13 +273,9 @@
expanded: true,
summary: 'Documentation & Bugs',
items: [
- {t: 'Keyboard shortcuts', a: openHelp, i: 'help'},
+ {t: 'Keyboard shortcuts', a: toggleHelp, i: 'help'},
{t: 'Documentation', a: 'https://perfetto.dev/docs', i: 'find_in_page'},
- {
- t: 'Flags',
- a: (e: Event) => navigateToPage(e, 'flags'),
- i: 'emoji_flags',
- },
+ {t: 'Flags', a: '#!/flags', i: 'emoji_flags'},
{
t: 'Report a bug',
a: getBugReportUrl(),
@@ -315,13 +285,13 @@
? [
{
t: 'Record metatrace',
- a: (e: Event) => recordMetatrace(e, trace.engine),
+ a: () => recordMetatrace(trace.engine),
i: 'fiber_smart_record',
checkMetatracingDisabled: true,
},
{
t: 'Finalise metatrace',
- a: (e: Event) => finaliseMetatrace(e, trace.engine),
+ a: () => finaliseMetatrace(trace.engine),
i: 'file_download',
checkMetatracingEnabled: true,
},
@@ -332,11 +302,6 @@
];
}
-function openHelp(e: Event) {
- e.preventDefault();
- toggleHelp();
-}
-
function downloadTraceFromUrl(url: string): Promise<File> {
return m.request({
method: 'GET',
@@ -365,69 +330,40 @@
} else if (src.type === 'FILE') {
return src.file;
} else if (src.type === 'URL') {
- return downloadTraceFromUrl(src.url);
+ return await downloadTraceFromUrl(src.url);
} else {
throw new Error(`Loading to catapult from source with type ${src.type}`);
}
}
-function openCurrentTraceWithOldUI(e: Event) {
- e.preventDefault();
+async function openCurrentTraceWithOldUI(): Promise<void> {
assertTrue(isTraceLoaded());
AppImpl.instance.analytics.logEvent(
'Trace Actions',
'Open current trace in legacy UI',
);
if (!isTraceLoaded()) return;
- getCurrentTrace()
- .then((file) => {
- openInOldUIWithSizeCheck(file);
- })
- .catch((error) => {
- throw new Error(`Failed to get current trace ${error}`);
- });
+ const file = await getCurrentTrace();
+ await openInOldUIWithSizeCheck(file);
}
-function convertTraceToSystrace(e: Event) {
- e.preventDefault();
+async function convertTraceToSystrace(): Promise<void> {
assertTrue(isTraceLoaded());
AppImpl.instance.analytics.logEvent('Trace Actions', 'Convert to .systrace');
if (!isTraceLoaded()) return;
- getCurrentTrace()
- .then((file) => {
- convertTraceToSystraceAndDownload(file);
- })
- .catch((error) => {
- throw new Error(`Failed to get current trace ${error}`);
- });
+ const file = await getCurrentTrace();
+ await convertTraceToSystraceAndDownload(file);
}
-function convertTraceToJson(e: Event) {
- e.preventDefault();
+async function convertTraceToJson(): Promise<void> {
assertTrue(isTraceLoaded());
AppImpl.instance.analytics.logEvent('Trace Actions', 'Convert to .json');
if (!isTraceLoaded()) return;
- getCurrentTrace()
- .then((file) => {
- convertTraceToJsonAndDownload(file);
- })
- .catch((error) => {
- throw new Error(`Failed to get current trace ${error}`);
- });
+ const file = await getCurrentTrace();
+ await convertTraceToJsonAndDownload(file);
}
-function navigateToPage(e: Event, pageName: string) {
- e.preventDefault();
- Router.navigate(`#!/${pageName}`);
-}
-
-function handleShareTrace(e: Event) {
- e.preventDefault();
- shareTrace();
-}
-
-function downloadTrace(e: Event, trace: Trace) {
- e.preventDefault();
+function downloadTrace(trace: Trace) {
if (!isDownloadable() || !isTraceLoaded()) return;
AppImpl.instance.analytics.logEvent('Trace Actions', 'Download trace');
@@ -467,8 +403,7 @@
);
}
-function recordMetatrace(e: Event, engine: Engine) {
- e.preventDefault();
+function recordMetatrace(engine: Engine) {
AppImpl.instance.analytics.logEvent('Trace Actions', 'Record metatrace');
if (!highPrecisionTimersAvailable()) {
@@ -505,8 +440,7 @@
}
}
-async function finaliseMetatrace(e: Event, engine: Engine) {
- e.preventDefault();
+async function finaliseMetatrace(engine: Engine) {
AppImpl.instance.analytics.logEvent('Trace Actions', 'Finalise metatrace');
const jsEvents = disableMetatracingAndGetTrace();
@@ -692,10 +626,57 @@
export class Sidebar implements m.ClassComponent<OptionalTraceAttrs> {
private _redrawWhileAnimating = new Animation(() => raf.scheduleFullRedraw());
+ private _asyncJobPending = new Set<string>();
+ private _onClickHandlers = new Map<string, Function>();
+
view({attrs}: m.CVnode<OptionalTraceAttrs>) {
if (AppImpl.instance.sidebar.sidebarHidden) return null;
+
+ // The code below iterates through the sections and SectionActions provided
+ // by getSections() and creates the onClick handlers for the items where
+ // a (async)function is provided.
+ // We do it in view() and not in the constructor because new sidebar items
+ // can be added later by plugins.
+ // What we want to achieve here is the following:
+ // - We want to allow plugins that contribute to the sidebar to just specify
+ // either string URLs or (async) functions as actions for a sidebar menu.
+ // - When they specify an async function, we want to render a spinner, next
+ // to the menu item, until the promise is resolved.
+ // - [Minor] we want to call e.preventDefault() to override the behaviour of
+ // the <a href='#'> which gets rendered for accessibility reasons.
+ const sections = getSections(attrs.trace);
+ for (const section of sections) {
+ for (const item of section.items) {
+ const itemId = item.t;
+ // We call this on every render pass. Don't re-create wrappers on each
+ // render cycle if we did it already as that is wasteful.
+ if (this._onClickHandlers.has(itemId)) continue;
+
+ const itemAction = item.a;
+
+ // item.a can be either a function or a URL. In the latter case, we
+ // don't need to generate any onclick handler.
+ if (typeof itemAction !== 'function') continue;
+ const onClickHandler = (e: Event) => {
+ e.preventDefault(); // Make the <a href="#"> a no-op.
+ const res = itemAction();
+ if (!(res instanceof Promise)) return;
+ if (this._asyncJobPending.has(itemId)) {
+ return; // Don't queue up another action if not yet finished.
+ }
+ this._asyncJobPending.add(itemId);
+ raf.scheduleFullRedraw();
+ res.finally(() => {
+ this._asyncJobPending.delete(itemId);
+ raf.scheduleFullRedraw();
+ });
+ };
+ this._onClickHandlers.set(itemId, onClickHandler);
+ }
+ }
+
const vdomSections = [];
- for (const section of getSections(attrs.trace)) {
+ for (const section of sections) {
if (section.hideIfNoTraceLoaded && !isTraceLoaded()) continue;
const vdomItems = [];
for (const item of section.items) {
@@ -704,14 +685,14 @@
}
let css = '';
let attrs = {
- onclick: typeof item.a === 'function' ? item.a : null,
+ onclick: this._onClickHandlers.get(item.t),
href: isString(item.a) ? item.a : '#',
- target: isString(item.a) ? '_blank' : null,
+ target: isString(item.a) && !item.a.startsWith('#') ? '_blank' : null,
disabled: false,
id: item.t.toLowerCase().replace(/[^\w]/g, '_'),
};
- if (item.isPending && item.isPending()) {
- attrs.onclick = (e) => e.preventDefault();
+
+ if (this._asyncJobPending.has(item.t)) {
css = '.pending';
}
if (item.internalUserOnly && !globals.isInternalUser) {
@@ -739,7 +720,7 @@
}
if (item.checkDownloadDisabled && !isDownloadable()) {
attrs = {
- onclick: (e) => {
+ onclick: (e: Event) => {
e.preventDefault();
alert('Can not download external trace.');
},
@@ -754,7 +735,7 @@
'li',
m(
`a${css}`,
- {...attrs, title: item.title},
+ {...attrs, title: item.tooltip},
m('i.material-icons', item.i),
item.t,
),
diff --git a/ui/src/frontend/trace_attrs.ts b/ui/src/frontend/trace_attrs.ts
index b996265..dd0f1ab 100644
--- a/ui/src/frontend/trace_attrs.ts
+++ b/ui/src/frontend/trace_attrs.ts
@@ -39,7 +39,7 @@
return true;
}
-export function shareTrace() {
+export async function shareTrace() {
const traceSource = assertExists(AppImpl.instance.trace?.traceInfo.source);
const traceUrl = (traceSource as TraceUrlSource).url ?? '';
@@ -74,7 +74,7 @@
);
if (result) {
AppImpl.instance.analytics.logEvent('Trace Actions', 'Create permalink');
- createPermalink({mode: 'APP_STATE'});
+ return await createPermalink({mode: 'APP_STATE'});
}
}
diff --git a/ui/src/frontend/trace_converter.ts b/ui/src/frontend/trace_converter.ts
index be518d8..f220120 100644
--- a/ui/src/frontend/trace_converter.ts
+++ b/ui/src/frontend/trace_converter.ts
@@ -13,20 +13,17 @@
// limitations under the License.
import {download} from '../base/clipboard';
+import {defer} from '../base/deferred';
import {ErrorDetails} from '../base/logging';
import {utf8Decode} from '../base/string_utils';
import {time} from '../base/time';
-import {
- ConversionJobName,
- ConversionJobStatus,
-} from '../common/conversion_jobs';
import {AppImpl} from '../core/app_impl';
import {maybeShowErrorDialog} from './error_dialog';
import {globals} from './globals';
type Args =
| UpdateStatusArgs
- | UpdateJobStatusArgs
+ | JobCompletedArgs
| DownloadFileArgs
| OpenTraceInLegacyArgs
| ErrorArgs;
@@ -36,10 +33,8 @@
status: string;
}
-interface UpdateJobStatusArgs {
- kind: 'updateJobStatus';
- name: ConversionJobName;
- status: ConversionJobStatus;
+interface JobCompletedArgs {
+ kind: 'jobCompleted';
}
interface DownloadFileArgs {
@@ -64,16 +59,18 @@
size: number,
) => void;
-function makeWorkerAndPost(
+async function makeWorkerAndPost(
msg: unknown,
openTraceInLegacy?: OpenTraceInLegacyCallback,
) {
+ const promise = defer<void>();
+
function handleOnMessage(msg: MessageEvent): void {
const args: Args = msg.data;
if (args.kind === 'updateStatus') {
AppImpl.instance.omnibox.showStatusMessage(args.status);
- } else if (args.kind === 'updateJobStatus') {
- globals.setConversionJobStatus(args.name, args.status);
+ } else if (args.kind === 'jobCompleted') {
+ promise.resolve();
} else if (args.kind === 'downloadFile') {
download(new File([new Blob([args.buffer])], args.name));
} else if (args.kind === 'openTraceInLegacy') {
@@ -89,18 +86,19 @@
const worker = new Worker(globals.root + 'traceconv_bundle.js');
worker.onmessage = handleOnMessage;
worker.postMessage(msg);
+ return promise;
}
-export function convertTraceToJsonAndDownload(trace: Blob) {
- makeWorkerAndPost({
+export function convertTraceToJsonAndDownload(trace: Blob): Promise<void> {
+ return makeWorkerAndPost({
kind: 'ConvertTraceAndDownload',
trace,
format: 'json',
});
}
-export function convertTraceToSystraceAndDownload(trace: Blob) {
- makeWorkerAndPost({
+export function convertTraceToSystraceAndDownload(trace: Blob): Promise<void> {
+ return makeWorkerAndPost({
kind: 'ConvertTraceAndDownload',
trace,
format: 'systrace',
@@ -111,8 +109,8 @@
trace: Blob,
openTraceInLegacy: OpenTraceInLegacyCallback,
truncate?: 'start' | 'end',
-) {
- makeWorkerAndPost(
+): Promise<void> {
+ return makeWorkerAndPost(
{
kind: 'ConvertTraceAndOpenInLegacy',
trace,
@@ -126,8 +124,8 @@
trace: Blob,
pid: number,
ts: time,
-) {
- makeWorkerAndPost({
+): Promise<void> {
+ return makeWorkerAndPost({
kind: 'ConvertTraceToPprof',
trace,
pid,
diff --git a/ui/src/traceconv/index.ts b/ui/src/traceconv/index.ts
index 9181920..cc721ba 100644
--- a/ui/src/traceconv/index.ts
+++ b/ui/src/traceconv/index.ts
@@ -20,10 +20,6 @@
reportError,
} from '../base/logging';
import {time} from '../base/time';
-import {
- ConversionJobName,
- ConversionJobStatus,
-} from '../common/conversion_jobs';
import traceconv from '../gen/traceconv';
const selfWorker = self as {} as Worker;
@@ -44,12 +40,8 @@
});
}
-function updateJobStatus(name: ConversionJobName, status: ConversionJobStatus) {
- selfWorker.postMessage({
- kind: 'updateJobStatus',
- name,
- status,
- });
+function notifyJobCompleted() {
+ selfWorker.postMessage({kind: 'jobCompleted'});
}
function downloadFile(buffer: Uint8Array, name: string) {
@@ -131,8 +123,6 @@
format: Format,
truncate?: 'start' | 'end',
): Promise<void> {
- const jobName = format === 'json' ? 'convert_json' : 'convert_systrace';
- updateJobStatus(jobName, ConversionJobStatus.InProgress);
const outPath = '/trace.json';
const args: string[] = [format];
if (truncate !== undefined) {
@@ -145,7 +135,7 @@
downloadFile(fsNodeToBuffer(fsNode), `trace.${format}`);
module.FS.unlink(outPath);
} finally {
- updateJobStatus(jobName, ConversionJobStatus.NotRunning);
+ notifyJobCompleted();
}
}
@@ -168,8 +158,6 @@
trace: Blob,
truncate?: 'start' | 'end',
) {
- const jobName = 'open_in_legacy';
- updateJobStatus(jobName, ConversionJobStatus.InProgress);
const outPath = '/trace.json';
const args: string[] = ['json'];
if (truncate !== undefined) {
@@ -185,7 +173,7 @@
openTraceInLegacy(buffer);
module.FS.unlink(outPath);
} finally {
- updateJobStatus(jobName, ConversionJobStatus.NotRunning);
+ notifyJobCompleted();
}
}
@@ -204,8 +192,6 @@
}
async function ConvertTraceToPprof(trace: Blob, pid: number, ts: time) {
- const jobName = 'convert_pprof';
- updateJobStatus(jobName, ConversionJobStatus.InProgress);
const args = [
'profile',
`--pid`,
@@ -232,7 +218,7 @@
downloadFile(fsNodeToBuffer(fileNode), fileName);
}
} finally {
- updateJobStatus(jobName, ConversionJobStatus.NotRunning);
+ notifyJobCompleted();
}
}