UI: Make engine source explicit
The next CL will introduce a new mode (for RPC)
and will make the current "sourc" interface too
ambiguous. Making it an explicit enum instead of
relyinig on RTTI.
Bug: 143074239
Test: manually tested permalink, download and conversion manually with both URL and file
Change-Id: I7f6222ce4980250bdd136153b26c9c052d4b688c
diff --git a/ui/src/common/actions.ts b/ui/src/common/actions.ts
index 95092be..215846b 100644
--- a/ui/src/common/actions.ts
+++ b/ui/src/common/actions.ts
@@ -28,6 +28,7 @@
State,
Status,
TargetOs,
+ TraceSource,
TraceTime,
TrackState,
VisibleState,
@@ -75,7 +76,7 @@
state.engines[id] = {
id,
ready: false,
- source: args.file,
+ source: {type: 'FILE', file: args.file},
};
state.route = `/viewer`;
},
@@ -86,7 +87,7 @@
state.engines[id] = {
id,
ready: false,
- source: args.buffer,
+ source: {type: 'ARRAY_BUFFER', buffer: args.buffer},
};
state.route = `/viewer`;
},
@@ -97,7 +98,7 @@
state.engines[id] = {
id,
ready: false,
- source: args.url,
+ source: {type: 'URL', url: args.url},
};
state.route = `/viewer`;
},
@@ -113,12 +114,9 @@
ConvertTrace(args.file, args.truncate);
},
- convertTraceToPprof(_: StateDraft, args: {
- pid: number,
- src: string|File|ArrayBuffer,
- ts1: number,
- ts2?: number
- }): void {
+ convertTraceToPprof(
+ _: StateDraft,
+ args: {pid: number, src: TraceSource, ts1: number, ts2?: number}): void {
ConvertTraceToPprof(args.pid, args.src, args.ts1, args.ts2);
},
diff --git a/ui/src/common/actions_unittest.ts b/ui/src/common/actions_unittest.ts
index 4214c19..686da55 100644
--- a/ui/src/common/actions_unittest.ts
+++ b/ui/src/common/actions_unittest.ts
@@ -19,7 +19,8 @@
createEmptyState,
SCROLLING_TRACK_GROUP,
State,
- TrackState
+ TraceUrlSource,
+ TrackState,
} from './state';
function fakeTrack(state: State, id: string): TrackState {
@@ -248,7 +249,8 @@
const engineKeys = Object.keys(after.engines);
expect(after.nextId).toBe(101);
expect(engineKeys.length).toBe(1);
- expect(after.engines[engineKeys[0]].source).toBe('https://example.com/bar');
+ expect((after.engines[engineKeys[0]].source as TraceUrlSource).url)
+ .toBe('https://example.com/bar');
expect(after.route).toBe('/viewer');
expect(after.recordConfig).toBe(recordConfig);
});
@@ -277,7 +279,8 @@
const engineKeys = Object.keys(thrice.engines);
expect(engineKeys.length).toBe(1);
- expect(thrice.engines[engineKeys[0]].source).toBe('https://example.com/foo');
+ expect((thrice.engines[engineKeys[0]].source as TraceUrlSource).url)
+ .toBe('https://example.com/foo');
expect(thrice.pinnedTracks.length).toBe(0);
expect(thrice.scrollingTracks.length).toBe(0);
expect(thrice.route).toBe('/viewer');
diff --git a/ui/src/common/engine.ts b/ui/src/common/engine.ts
index d4a6b45..d9f6760 100644
--- a/ui/src/common/engine.ts
+++ b/ui/src/common/engine.ts
@@ -53,6 +53,12 @@
*/
abstract notifyEof(): void;
+ /**
+ * Resets the trace processor state by destroying any table/views created by
+ * the UI after loading.
+ */
+ abstract restoreInitialTables(): void;
+
/*
* Performs a SQL query and retruns a proto-encoded RawQueryResult object.
*/
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index b35aa91..5d6be6a 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -36,6 +36,23 @@
export const SCROLLING_TRACK_GROUP = 'ScrollingTracks';
+export interface TraceFileSource {
+ type: 'FILE';
+ file: File;
+}
+
+export interface TraceArrayBufferSource {
+ type: 'ARRAY_BUFFER';
+ buffer: ArrayBuffer;
+}
+
+export interface TraceUrlSource {
+ type: 'URL';
+ url: string;
+}
+
+export type TraceSource = TraceFileSource|TraceArrayBufferSource|TraceUrlSource;
+
export interface TrackState {
id: string;
engineId: string;
@@ -57,7 +74,7 @@
export interface EngineConfig {
id: string;
ready: boolean;
- source: string|File|ArrayBuffer;
+ source: TraceSource;
}
export interface QueryConfig {
diff --git a/ui/src/common/wasm_engine_proxy.ts b/ui/src/common/wasm_engine_proxy.ts
index e5ff09b..b09c662 100644
--- a/ui/src/common/wasm_engine_proxy.ts
+++ b/ui/src/common/wasm_engine_proxy.ts
@@ -99,6 +99,12 @@
await this.queueRequest('trace_processor_notify_eof', new Uint8Array());
}
+ restoreInitialTables(): Promise<void> {
+ // We should never get here, restoreInitialTables() should be called only
+ // when using the HttpRpcEngine.
+ throw new Error('restoreInitialTables() not supported by the WASM engine');
+ }
+
rawQuery(rawQueryArgs: Uint8Array): Promise<Uint8Array> {
return this.queueRequest('trace_processor_raw_query', rawQueryArgs);
}
diff --git a/ui/src/controller/permalink_controller.ts b/ui/src/controller/permalink_controller.ts
index 7bafa23..d991a89 100644
--- a/ui/src/controller/permalink_controller.ts
+++ b/ui/src/controller/permalink_controller.ts
@@ -12,22 +12,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {Draft, produce} from 'immer';
+import {produce} from 'immer';
import * as uuidv4 from 'uuid/v4';
-import {assertExists} from '../base/logging';
+import {assertExists, assertTrue} from '../base/logging';
import {Actions} from '../common/actions';
-import {EngineConfig, State} from '../common/state';
+import {State} from '../common/state';
import {Controller} from './controller';
import {globals} from './globals';
export const BUCKET_NAME = 'perfetto-ui-data';
-function needsToBeUploaded(obj: {}): obj is ArrayBuffer|File {
- return obj instanceof ArrayBuffer || obj instanceof File;
-}
-
export class PermalinkController extends Controller<'main'> {
private lastRequestId?: string;
constructor() {
@@ -58,29 +54,30 @@
}
private static async createPermalink() {
- const state = globals.state;
-
- // Upload each loaded trace.
- const fileToUrl = new Map<File|ArrayBuffer, string>();
- for (const engine of Object.values<EngineConfig>(state.engines)) {
- if (!needsToBeUploaded(engine.source)) continue;
- const name = engine.source instanceof File ? engine.source.name :
- `trace ${engine.id}`;
- PermalinkController.updateStatus(`Uploading ${name}`);
- const url = await this.saveTrace(engine.source);
- fileToUrl.set(engine.source, url);
+ const engines = Object.values(globals.state.engines);
+ assertTrue(engines.length === 1);
+ const engine = engines[0];
+ let dataToUpload: File|ArrayBuffer|undefined = undefined;
+ let traceName = `trace ${engine.id}`;
+ if (engine.source.type === 'FILE') {
+ dataToUpload = engine.source.file;
+ traceName = dataToUpload.name;
+ } else if (engine.source.type === 'ARRAY_BUFFER') {
+ dataToUpload = engine.source.buffer;
+ } else if (engine.source.type !== 'URL') {
+ throw new Error(`Cannot share trace ${JSON.stringify(engine.source)}`);
}
- // Convert state to use URLs and remove permalink.
- const uploadState = produce(state, draft => {
- for (const engine of Object.values<Draft<EngineConfig>>(
- draft.engines)) {
- if (!needsToBeUploaded(engine.source)) continue;
- const newSource = fileToUrl.get(engine.source);
- if (newSource) engine.source = newSource;
- }
- draft.permalink = {};
- });
+ let uploadState = globals.state;
+ if (dataToUpload !== undefined) {
+ PermalinkController.updateStatus(`Uploading ${traceName}`);
+ const url = await this.saveTrace(dataToUpload);
+ // Convert state to use URLs and remove permalink.
+ uploadState = produce(globals.state, draft => {
+ draft.engines[engine.id].source = {type: 'URL', url};
+ draft.permalink = {};
+ });
+ }
// Upload state.
PermalinkController.updateStatus(`Creating permalink...`);
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index ec4209f..7dab1ab 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -178,14 +178,15 @@
createWasmEngine(this.engineId),
LoadingManager.getInstance);
let traceStream: TraceStream;
- if (engineCfg.source instanceof File) {
- traceStream = new TraceFileStream(engineCfg.source);
- } else if (engineCfg.source instanceof ArrayBuffer) {
- traceStream = new TraceBufferStream(engineCfg.source);
+ if (engineCfg.source.type === 'FILE') {
+ traceStream = new TraceFileStream(engineCfg.source.file);
+ } else if (engineCfg.source.type === 'ARRAY_BUFFER') {
+ traceStream = new TraceBufferStream(engineCfg.source.buffer);
+ } else if (engineCfg.source.type === 'URL') {
+ traceStream = new TraceHttpStream(engineCfg.source.url);
} else {
- traceStream = new TraceHttpStream(engineCfg.source);
+ throw new Error(`Unknown source: ${JSON.stringify(engineCfg.source)}`);
}
-
const tStart = performance.now();
for (;;) {
const res = await traceStream.readChunk();
diff --git a/ui/src/controller/trace_converter.ts b/ui/src/controller/trace_converter.ts
index e4648a0..2a5b787 100644
--- a/ui/src/controller/trace_converter.ts
+++ b/ui/src/controller/trace_converter.ts
@@ -13,6 +13,7 @@
// limitations under the License.
import {Actions} from '../common/actions';
+import {TraceSource} from '../common/state';
import * as trace_to_text from '../gen/trace_to_text';
import {globals} from './globals';
@@ -54,7 +55,7 @@
}
export async function ConvertTraceToPprof(
- pid: number, src: string|File|ArrayBuffer, ts1: number, ts2?: number) {
+ pid: number, src: TraceSource, ts1: number, ts2?: number) {
generateBlob(src).then(result => {
const mod = trace_to_text({
noInitialRun: true,
@@ -101,18 +102,20 @@
});
}
-async function generateBlob(src: string|ArrayBuffer|File) {
+async function generateBlob(src: TraceSource) {
let blob: Blob = new Blob();
- if (typeof src === 'string') {
- const resp = await fetch(src);
+ if (src.type === 'URL') {
+ const resp = await fetch(src.url);
if (resp.status !== 200) {
throw new Error(`fetch() failed with HTTP error ${resp.status}`);
}
blob = await resp.blob();
- } else if (src instanceof ArrayBuffer) {
- blob = new Blob([new Uint8Array(src, 0, src.byteLength)]);
+ } else if (src.type === 'ARRAY_BUFFER') {
+ blob = new Blob([new Uint8Array(src.buffer, 0, src.buffer.byteLength)]);
+ } else if (src.type === 'FILE') {
+ blob = src.file;
} else {
- blob = src;
+ throw new Error(`Conversion not supported for ${JSON.stringify(src)}`);
}
return blob;
}
diff --git a/ui/src/frontend/sidebar.ts b/ui/src/frontend/sidebar.ts
index 5db490d..350a17e 100644
--- a/ui/src/frontend/sidebar.ts
+++ b/ui/src/frontend/sidebar.ts
@@ -137,13 +137,13 @@
t: 'Share',
a: dispatchCreatePermalink,
i: 'share',
- disableInLocalOnlyMode: true,
+ checkDownloadDisabled: true,
},
{
t: 'Download',
a: downloadTrace,
i: 'file_download',
- disableInLocalOnlyMode: true,
+ checkDownloadDisabled: true,
},
{t: 'Legacy UI', a: openCurrentTraceWithOldUI, i: 'filter_none'},
],
@@ -262,15 +262,14 @@
if (!isTraceLoaded) return;
const engine = Object.values(globals.state.engines)[0];
const src = engine.source;
- if (src instanceof ArrayBuffer) {
- openInOldUIWithSizeCheck(new Blob([src]));
- } else if (src instanceof File) {
- openInOldUIWithSizeCheck(src);
+ if (src.type === 'ARRAY_BUFFER') {
+ openInOldUIWithSizeCheck(new Blob([src.buffer]));
+ } else if (src.type === 'FILE') {
+ openInOldUIWithSizeCheck(src.file);
} else {
- console.assert(typeof src === 'string');
- console.error('Loading from an URL to catapult is not yet supported');
+ throw new Error('Loading from a URL to catapult is not yet supported');
// TODO(nicomazz): Find how to get the data of the current trace if it is
- // from an URL. It seems that the trace downloaded is given to the trace
+ // from a URL. It seems that the trace downloaded is given to the trace
// processor, but not kept somewhere accessible. Maybe the only way is to
// download the trace (again), and then open it. An alternative can be to
// save a copy.
@@ -412,13 +411,14 @@
globals.dispatch(Actions.navigate({route: '/viewer'}));
}
-function localOnlyMode(): boolean {
- return globals.frontendLocalState.localOnlyMode;
+function isDownloadAndShareDisabled(): boolean {
+ if (globals.frontendLocalState.localOnlyMode) return true;
+ return false;
}
function dispatchCreatePermalink(e: Event) {
e.preventDefault();
- if (localOnlyMode() || !isTraceLoaded()) return;
+ if (isDownloadAndShareDisabled() || !isTraceLoaded()) return;
const result = confirm(
`Upload the trace and generate a permalink. ` +
@@ -428,27 +428,30 @@
function downloadTrace(e: Event) {
e.preventDefault();
- if (!isTraceLoaded() || localOnlyMode()) return;
+ if (!isTraceLoaded() || isDownloadAndShareDisabled()) return;
const engine = Object.values(globals.state.engines)[0];
if (!engine) return;
- const src = engine.source;
- if (typeof src === 'string') {
- window.open(src);
- return;
- }
-
let url = '';
- if (src instanceof ArrayBuffer) {
- const blob = new Blob([src], {type: 'application/octet-stream'});
+ let fileName = 'trace.pftrace';
+ const src = engine.source;
+ if (src.type === 'URL') {
+ url = src.url;
+ fileName = url.split('/').slice(-1)[0];
+ } else if (src.type === 'ARRAY_BUFFER') {
+ const blob = new Blob([src.buffer], {type: 'application/octet-stream'});
url = URL.createObjectURL(blob);
+ } else if (src.type === 'FILE') {
+ const file = src.file;
+ url = URL.createObjectURL(file);
+ fileName = file.name;
} else {
- console.assert(src instanceof File);
- url = URL.createObjectURL(src);
+ throw new Error(`Download from ${JSON.stringify(src)} is not supported`);
}
const a = document.createElement('a');
a.href = url;
+ a.download = fileName;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
@@ -491,8 +494,8 @@
href: typeof item.a === 'string' ? item.a : '#',
disabled: false,
};
- if (globals.frontendLocalState.localOnlyMode &&
- item.hasOwnProperty('disableInLocalOnlyMode')) {
+ if (isDownloadAndShareDisabled() &&
+ item.hasOwnProperty('checkDownloadDisabled')) {
attrs = {
onclick: () => alert('Can not download or share external trace.'),
href: '#',