Reduce chance of UI/TP version skew
Bug: 326044799
Change-Id: Ie67aca2e18a321137f91574c92c91b6d9c3d3cd1
diff --git a/include/perfetto/ext/base/version.h b/include/perfetto/ext/base/version.h
index 212424a..ad78372 100644
--- a/include/perfetto/ext/base/version.h
+++ b/include/perfetto/ext/base/version.h
@@ -20,9 +20,27 @@
namespace perfetto {
namespace base {
-// The returned pointer is a static string is safe to pass around.
+// The returned pointer is a static string and safe to pass around.
+// Returns a human readable string currently of the approximate form:
+// Perfetto v42.1-deadbeef0 (deadbeef03c641e4b4ea9cf38e9b5696670175a9)
+// However you should not depend on the format of this string.
+// It maybe not be possible to determine the version. In which case the
+// string will be of the approximate form:
+// Perfetto v0.0 (unknown)
const char* GetVersionString();
+// The returned pointer is a static string and safe to pass around.
+// Returns the short code used to identity the version:
+// v42.1-deadbeef0
+// It maybe not be possible to determine the version. In which case
+// this returns nullptr.
+// This can be compared with equality to other
+// version codes to detect matched builds (for example to see if
+// trace_processor_shell and the UI were built at the same revision)
+// but you should not attempt to parse it as the format may change
+// without warning.
+const char* GetVersionCode();
+
} // namespace base
} // namespace perfetto
diff --git a/protos/perfetto/trace_processor/trace_processor.proto b/protos/perfetto/trace_processor/trace_processor.proto
index c96caa0..1e579ca 100644
--- a/protos/perfetto/trace_processor/trace_processor.proto
+++ b/protos/perfetto/trace_processor/trace_processor.proto
@@ -38,9 +38,18 @@
enum TraceProcessorApiVersion {
// This variable has been introduced in v15 and is used to deal with API
- // mismatches between UI and trace_processor_shell --httpd. Increment this
- // every time a new feature that the UI depends on is being introduced (e.g.
- // new tables, new SQL operators, metrics that are required by the UI).
+ // mismatches between UI and trace_processor_shell --httpd.
+ //
+ // Prior to API version 11 this was incremented every time a new
+ // feature that the UI depended on was introduced (e.g. new tables,
+ // new SQL operators, metrics that are required by the UI, etc).
+ // This:
+ // a. Tended to be forgotten
+ // b. Still led to issues when the TP dropped *backwards*
+ // compatibility of a feature (since we checked TP >= UI
+ // TRACE_PROCESSOR_CURRENT_API_VERSION).
+ // Now the UI attempts to redirect the user to the matched version
+ // of the UI if one exists.
// See also StatusResult.api_version (below).
// Changes:
// 7. Introduce GUESS_CPU_SIZE
@@ -48,10 +57,12 @@
// 9. Add get_thread_state_summary_for_interval.
// 10. Add 'slice_is_ancestor' to stdlib.
// 11. Removal of experimental module from stdlib.
- TRACE_PROCESSOR_CURRENT_API_VERSION = 11;
+ // 12. Changed UI to be more aggresive about version matching.
+ // Added version_code.
+ TRACE_PROCESSOR_CURRENT_API_VERSION = 12;
}
-// At lowest level, the wire-format of the RPC procol is a linear sequence of
+// At lowest level, the wire-format of the RPC protocol is a linear sequence of
// TraceProcessorRpc messages on each side of the byte pipe
// Each message is prefixed by a tag (field = 1, type = length delimited) and a
// varint encoding its size (this is so the whole stream can also be read /
@@ -236,6 +247,15 @@
// The API version is incremented every time a change that the UI depends
// on is introduced (e.g. adding a new table that the UI queries).
optional int32 api_version = 3;
+
+ // Typically something like "v42.1-deadbeef0", but could be just
+ // "v42", "v0.0", or unset for binaries built from Bazel or other
+ // build configurations. This can be compared with equality to other
+ // version codes to detect matched builds (for example to see if
+ // trace_processor_shell and the UI were built at the same revision)
+ // but you should not attempt to parse it as the format may change
+ // without warning.
+ optional string version_code = 4;
}
// Input for the /compute_metric endpoint.
diff --git a/python/perfetto/trace_processor/trace_processor.descriptor b/python/perfetto/trace_processor/trace_processor.descriptor
index 261ab0b..ce0bc66 100644
--- a/python/perfetto/trace_processor/trace_processor.descriptor
+++ b/python/perfetto/trace_processor/trace_processor.descriptor
Binary files differ
diff --git a/src/base/version.cc b/src/base/version.cc
index 18569d3..e989534 100644
--- a/src/base/version.cc
+++ b/src/base/version.cc
@@ -23,18 +23,26 @@
#if PERFETTO_BUILDFLAG(PERFETTO_VERSION_GEN)
#include "perfetto_version.gen.h"
#else
-#define PERFETTO_VERSION_STRING() "v0.0"
+#define PERFETTO_VERSION_STRING() nullptr
#define PERFETTO_VERSION_SCM_REVISION() "unknown"
#endif
namespace perfetto {
namespace base {
+const char* GetVersionCode() {
+ return PERFETTO_VERSION_STRING();
+}
+
const char* GetVersionString() {
static const char* version_str = [] {
static constexpr size_t kMaxLen = 256;
+ const char* version_code = PERFETTO_VERSION_STRING();
+ if (version_code == nullptr) {
+ version_code = "v0.0";
+ }
char* version = new char[kMaxLen + 1];
- snprintf(version, kMaxLen, "Perfetto %s (%s)", PERFETTO_VERSION_STRING(),
+ snprintf(version, kMaxLen, "Perfetto %s (%s)", version_code,
PERFETTO_VERSION_SCM_REVISION());
return version;
}();
diff --git a/src/trace_processor/rpc/rpc.cc b/src/trace_processor/rpc/rpc.cc
index 68e0732..9ca121e 100644
--- a/src/trace_processor/rpc/rpc.cc
+++ b/src/trace_processor/rpc/rpc.cc
@@ -491,6 +491,10 @@
protozero::HeapBuffered<protos::pbzero::StatusResult> status;
status->set_loaded_trace_name(trace_processor_->GetCurrentTraceName());
status->set_human_readable_version(base::GetVersionString());
+ const char* version_code = base::GetVersionCode();
+ if (version_code) {
+ status->set_version_code(version_code);
+ }
status->set_api_version(protos::pbzero::TRACE_PROCESSOR_CURRENT_API_VERSION);
return status.SerializeAsArray();
}
diff --git a/ui/src/assets/modal.scss b/ui/src/assets/modal.scss
index f7ed990..25e7706 100644
--- a/ui/src/assets/modal.scss
+++ b/ui/src/assets/modal.scss
@@ -127,7 +127,7 @@
background-color: #e6e6e6;
color: rgba(0, 0, 0, 0.8);
border: 2px solid transparent;
- border-radius: 4px;
+ border-radius: $pf-border-radius;
cursor: pointer;
text-transform: none;
overflow: visible;
diff --git a/ui/src/frontend/router.ts b/ui/src/frontend/router.ts
index 29028b0..c6c316b 100644
--- a/ui/src/frontend/router.ts
+++ b/ui/src/frontend/router.ts
@@ -308,4 +308,40 @@
throw new Error('History rewriting livelock');
}
}
+
+ static getUrlForVersion(versionCode: string): string {
+ const url = `${window.location.origin}/${versionCode}/`;
+ return url;
+ }
+
+ static async isVersionAvailable(versionCode: string):
+ Promise<string|undefined> {
+ if (versionCode === '') {
+ return undefined;
+ }
+ const controller = new AbortController();
+ const timeoutId = setTimeout(() => controller.abort(), 1000);
+ const url = Router.getUrlForVersion(versionCode);
+ let r;
+ try {
+ r = await fetch(url, {signal: controller.signal});
+ } catch (e) {
+ console.error(`No UI version for ${versionCode} at ${url}. This is an error if ${versionCode} is a released Perfetto version`);
+ return undefined;
+ } finally {
+ clearTimeout(timeoutId);
+ }
+ if (!r.ok) {
+ return undefined;
+ }
+ return url;
+ }
+
+ static navigateToVersion(versionCode: string): void {
+ const url = Router.getUrlForVersion(versionCode);
+ if (url === undefined) {
+ throw new Error(`No URL known for UI version ${versionCode}.`);
+ }
+ window.location.replace(url);
+ }
}
diff --git a/ui/src/frontend/rpc_http_dialog.ts b/ui/src/frontend/rpc_http_dialog.ts
index f29c846..97c186e 100644
--- a/ui/src/frontend/rpc_http_dialog.ts
+++ b/ui/src/frontend/rpc_http_dialog.ts
@@ -20,6 +20,7 @@
import {StatusResult, TraceProcessorApiVersion} from '../protos';
import {HttpRpcEngine} from '../trace_processor/http_rpc_engine';
import {showModal} from '../widgets/modal';
+import {Router} from './router';
import {globals} from './globals';
import {publishHttpRpcState} from './publish';
@@ -27,9 +28,9 @@
const CURRENT_API_VERSION =
TraceProcessorApiVersion.TRACE_PROCESSOR_CURRENT_API_VERSION;
-const PROMPT = () => `Trace Processor Native Accelerator detected on ` +
-`${HttpRpcEngine.hostAndPort} with:
-$loadedTraceName
+function getPromptMessage(tpStatus: StatusResult): string {
+ return `Trace Processor Native Accelerator detected on ${HttpRpcEngine.hostAndPort} with:
+${tpStatus.loadedTraceName}
YES, use loaded trace:
Will load from the current state of Trace Processor. If you did run
@@ -46,13 +47,11 @@
Using the native accelerator has some minor caveats:
- Only one tab can be using the accelerator.
- Sharing, downloading and conversion-to-legacy aren't supported.
-- You may encounter UI errors if the Trace Processor version you are using is
-too old. Get the latest version from get.perfetto.dev/trace_processor.
`;
+}
-
-const MSG_TOO_OLD = () => `The Trace Processor instance on ` +
-`${HttpRpcEngine.hostAndPort} is too old.
+function getIncompatibleRpcMessage(tpStatus: StatusResult): string {
+ return `The Trace Processor instance on ${HttpRpcEngine.hostAndPort} is too old.
This UI requires TraceProcessor features that are not present in the
Trace Processor native accelerator you are currently running.
@@ -64,14 +63,88 @@
chmod +x ./trace_processor
./trace_processor --httpd
-UI version: ${VERSION}
-TraceProcessor RPC API required: ${CURRENT_API_VERSION} or higher
+UI version code: ${VERSION}
+UI RPC API: ${CURRENT_API_VERSION}
-TraceProcessor version: $tpVersion
-RPC API: $tpApi
+Trace processor version: ${tpStatus.humanReadableVersion}
+Trace processor version code: ${tpStatus.versionCode}
+Trace processor RPC API: ${tpStatus.apiVersion}
`;
+}
-let forceUseOldVersion = false;
+function getVersionMismatchMessage(tpStatus: StatusResult): string {
+ return `The trace processor instance on ${HttpRpcEngine.hostAndPort} is a different build from the UI.
+
+This may cause problems. Where possible it is better to use the matched version of the UI.
+You can do this by clicking the button below.
+
+UI version code: ${VERSION}
+UI RPC API: ${CURRENT_API_VERSION}
+
+Trace processor version: ${tpStatus.humanReadableVersion}
+Trace processor version code: ${tpStatus.versionCode}
+Trace processor RPC API: ${tpStatus.apiVersion}
+`;
+}
+
+// The flow is fairly complicated:
+// +-----------------------------------+
+// | User loads the UI |
+// +-----------------+-----------------+
+// |
+// +-----------------+-----------------+
+// | Is trace_processor present at |
+// | HttpRpcEngine.hostAndPort? |
+// +--------------------------+--------+
+// |No |Yes
+// | +--------------+-------------------------------+
+// | | Does version code of UI and TP match? |
+// | +--------------+----------------------------+--+
+// | |No |Yes
+// | | |
+// | | |
+// | +-------------+-------------+ |
+// | |Is a build of the UI at the| |
+// | |TP version code existant | |
+// | |and reachable? | |
+// | +---+----------------+------+ |
+// | | No | Yes |
+// | | | |
+// | | +--------+-------+ |
+// | | |Dialog: Mismatch| |
+// | | |Load matched UI +-------------------------------+
+// | | |Continue +-+ | |
+// | | +----------------+ | | |
+// | | | | |
+// | +------+--------------------------+----+ | |
+// | |TP RPC version >= UI RPC version | | |
+// | +----+-------------------+-------------+ | |
+// | | No |Yes | |
+// | +----+--------------+ | | |
+// | |Dialog: Bad RPC | | | |
+// | +---+Use built-in WASM | | | |
+// | | |Continue anyway +----| | |
+// | | +-------------------+ | +-----------+-----------+ |
+// | | +--------+TP has preloaded trace?| |
+// | | +-+---------------+-----+ |
+// | | |No |Yes |
+// | | | +---------------------+ |
+// | | | | Dialog: Preloaded? | |
+// | | +--+ YES, use loaded trace |
+// | | +--------| YES, but reset state| |
+// | | +---------------------------------------| NO, Use builtin Wasm| |
+// | | | | | +---------------------+ |
+// | | | | | |
+// | | | Reset TP | |
+// | | | | | |
+// | | | | | |
+// Show the UI Show the UI Link to
+// (WASM mode) (RPC mode) matched UI
+
+// There are three options in the end:
+// - Show the UI (WASM mode)
+// - Show the UI (RPC mode)
+// - Redirect to a matched version of the UI
// Try to connect to the external Trace Processor HTTP RPC accelerator (if
// available, often it isn't). If connected it will populate the
@@ -83,73 +156,184 @@
export async function CheckHttpRpcConnection(): Promise<void> {
const state = await HttpRpcEngine.checkConnection();
publishHttpRpcState(state);
- if (!state.connected) return;
+ if (!state.connected) {
+ // No RPC = exit immediately to the WASM UI.
+ return;
+ }
const tpStatus = assertExists(state.status);
- if (tpStatus.apiVersion < CURRENT_API_VERSION) {
- await showDialogTraceProcessorTooOld(tpStatus);
- if (!forceUseOldVersion) return;
+ function forceWasm() {
+ globals.dispatch(Actions.setNewEngineMode({mode: 'FORCE_BUILTIN_WASM'}));
}
+ // Check short version:
+ if (tpStatus.versionCode !== '' && tpStatus.versionCode !== VERSION) {
+ const url = await Router.isVersionAvailable(tpStatus.versionCode);
+ if (url !== undefined) {
+ // If matched UI available show a dialog asking the user to
+ // switch.
+ const result = await showDialogVersionMismatch(tpStatus, url);
+ switch (result) {
+ case MismatchedVersionDialog.Dismissed:
+ case MismatchedVersionDialog.UseMatchingUi:
+ Router.navigateToVersion(tpStatus.versionCode);
+ return;
+ case MismatchedVersionDialog.UseMismatchedRpc:
+ break;
+ case MismatchedVersionDialog.UseWasm:
+ forceWasm();
+ return;
+ default:
+ const x: never = result;
+ throw new Error(`Unsupported result ${x}`);
+ }
+ }
+ }
+
+ // Check the RPC version:
+ if (tpStatus.apiVersion < CURRENT_API_VERSION) {
+ const result = await showDialogIncompatibleRPC(tpStatus);
+ switch (result) {
+ case IncompatibleRpcDialogResult.Dismissed:
+ case IncompatibleRpcDialogResult.UseWasm:
+ forceWasm();
+ return;
+ case IncompatibleRpcDialogResult.UseIncompatibleRpc:
+ break;
+ default:
+ const x: never = result;
+ throw new Error(`Unsupported result ${x}`);
+ }
+ }
+
+ // Check if pre-loaded:
if (tpStatus.loadedTraceName) {
// If a trace is already loaded in the trace processor (e.g., the user
// launched trace_processor_shell -D trace_file.pftrace), prompt the user to
// initialize the UI with the already-loaded trace.
- return showDialogToUsePreloadedTrace(tpStatus);
+ const result = await showDialogToUsePreloadedTrace(tpStatus);
+ switch (result) {
+ case PreloadedDialogResult.Dismissed:
+ case PreloadedDialogResult.UseRpcWithPreloadedTrace:
+ globals.dispatch(Actions.openTraceFromHttpRpc({}));
+ return;
+ case PreloadedDialogResult.UseRpc:
+ // Resetting state is the default.
+ return;
+ case PreloadedDialogResult.UseWasm:
+ forceWasm();
+ return;
+ default:
+ const x: never = result;
+ throw new Error(`Unsupported result ${x}`);
+ }
}
}
-async function showDialogTraceProcessorTooOld(tpStatus: StatusResult) {
- return showModal({
- title: 'Your Trace Processor binary is outdated',
- content:
- m('.modal-pre',
- MSG_TOO_OLD().replace('$tpVersion', tpStatus.humanReadableVersion)
- .replace('$tpApi', `${tpStatus.apiVersion}`)),
+enum MismatchedVersionDialog {
+ UseMatchingUi = 'useMatchingUi',
+ UseWasm = 'useWasm',
+ UseMismatchedRpc = 'useMismatchedRpc',
+ Dismissed = 'dismissed',
+}
+
+async function showDialogVersionMismatch(tpStatus: StatusResult,
+ url: string):
+ Promise<MismatchedVersionDialog> {
+ let result = MismatchedVersionDialog.Dismissed;
+ await showModal({
+ title: 'Version mismatch',
+ content: m('.modal-pre', getVersionMismatchMessage(tpStatus)),
+ buttons: [
+ {
+ primary: true,
+ text: `Open ${url}`,
+ action: () => {
+ result = MismatchedVersionDialog.UseMatchingUi;
+ },
+ },
+ {
+ text: 'Use builtin Wasm',
+ action: () => {
+ result = MismatchedVersionDialog.UseWasm;
+ },
+ },
+ {
+ text: 'Use mismatched version regardless (might crash)',
+ action: () => {
+ result = MismatchedVersionDialog.UseMismatchedRpc;
+ },
+ },
+ ],
+ });
+ return result;
+}
+
+enum IncompatibleRpcDialogResult {
+ UseWasm = 'useWasm',
+ UseIncompatibleRpc = 'useIncompatibleRpc',
+ Dismissed = 'dismissed',
+}
+
+async function showDialogIncompatibleRPC(tpStatus: StatusResult):
+ Promise<IncompatibleRpcDialogResult> {
+ let result = IncompatibleRpcDialogResult.Dismissed;
+ await showModal({
+ title: 'Incompatible RPC version',
+ content: m('.modal-pre', getIncompatibleRpcMessage(tpStatus)),
buttons: [
{
text: 'Use builtin Wasm',
primary: true,
action: () => {
- globals.dispatch(
- Actions.setNewEngineMode({mode: 'FORCE_BUILTIN_WASM'}));
+ result = IncompatibleRpcDialogResult.UseWasm;
},
},
{
- text: 'Use old version regardless (might crash)',
- primary: false,
+ text: 'Use old version regardless (will crash)',
action: () => {
- forceUseOldVersion = true;
+ result = IncompatibleRpcDialogResult.UseIncompatibleRpc;
},
},
],
});
+ return result;
}
-async function showDialogToUsePreloadedTrace(tpStatus: StatusResult) {
- return showModal({
- title: 'Use Trace Processor Native Acceleration?',
- content:
- m('.modal-pre',
- PROMPT().replace('$loadedTraceName', tpStatus.loadedTraceName)),
+enum PreloadedDialogResult {
+ UseRpcWithPreloadedTrace = 'useRpcWithPreloadedTrace',
+ UseRpc = 'useRpc',
+ UseWasm = 'useWasm',
+ Dismissed = 'dismissed',
+}
+
+async function showDialogToUsePreloadedTrace(tpStatus: StatusResult):
+ Promise<PreloadedDialogResult> {
+ let result = PreloadedDialogResult.Dismissed;
+ await showModal({
+ title: 'Use trace processor native acceleration?',
+ content: m('.modal-pre', getPromptMessage(tpStatus)),
buttons: [
{
text: 'YES, use loaded trace',
primary: true,
action: () => {
- globals.dispatch(Actions.openTraceFromHttpRpc({}));
+ result = PreloadedDialogResult.UseRpcWithPreloadedTrace;
},
},
{
text: 'YES, but reset state',
+ action: () => {
+ result = PreloadedDialogResult.UseRpc;
+ },
},
{
- text: 'NO, Use builtin Wasm',
+ text: 'NO, Use builtin WASM',
action: () => {
- globals.dispatch(
- Actions.setNewEngineMode({mode: 'FORCE_BUILTIN_WASM'}));
+ result = PreloadedDialogResult.UseWasm;
},
},
],
});
+ return result;
}
diff --git a/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/index.ts b/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/index.ts
index cbd5c9d..eb596c2 100644
--- a/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/index.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/index.ts
@@ -110,7 +110,7 @@
sum(stall_backend_mem) as total_stall_backend_mem,
sum(l3_cache_miss) as total_l3_cache_miss
FROM target_thread_ipc_slice WHERE ts IS NOT NULL`,
- 'target thread ipc statistic'
+ 'target thread ipc statistic',
);
},
});