TraceProcessor RPC: Add API version to handle version mismatch
This CL adds a notion of API version to the TraceProcessor
RPC protocol. This is to deal with cases where we introduce
a new TP feature (a new table, a new operator) that the UI
depends on.
Today the UI breaks in unexpected ways when the user is using
a version of trace_processor_shell --http that is too old.
This change makes it more explicit, telling the user what to do.
Preview: https://imgur.com/a/Jvap5K2
Bug: 159142289
Change-Id: Ic2d68f94c7eb7bd5b95eabd842dae71fe61222d4
diff --git a/CHANGELOG b/CHANGELOG
index d1bba4c..a7aa8fd 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -7,7 +7,10 @@
Trace Processor:
*
UI:
- *
+ * Added warning dialog when trying to use a trace_processor_shell --httpd
+ which is too old.
+ * Added warning dialog when trying to use a trace_processor_shell --httpd
+ RPC instance from more than one tab.
SDK:
*
diff --git a/protos/perfetto/trace_processor/trace_processor.proto b/protos/perfetto/trace_processor/trace_processor.proto
index b47bd3f..fbff9d7 100644
--- a/protos/perfetto/trace_processor/trace_processor.proto
+++ b/protos/perfetto/trace_processor/trace_processor.proto
@@ -35,6 +35,15 @@
// In this case these messages are used to {,un}marshall HTTP requests and
// response made through src/trace_processor/rpc/httpd.cc .
+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).
+ // See also TraceProcessorVersion (below).
+ TRACE_PROCESSOR_CURRENT_API_VERSION = 1;
+}
+
// At lowest level, the wire-format of the RPC procol 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
@@ -70,6 +79,7 @@
TPM_RESTORE_INITIAL_TABLES = 7;
TPM_ENABLE_METATRACE = 8;
TPM_DISABLE_AND_READ_METATRACE = 9;
+ TPM_GET_STATUS = 10;
}
oneof type {
@@ -113,6 +123,8 @@
DescriptorSet metric_descriptors = 206;
// For TPM_DISABLE_AND_READ_METATRACE.
DisableAndReadMetatraceResult metatrace = 209;
+ // For TPM_GET_STATUS.
+ StatusResult status = 210;
}
}
@@ -230,6 +242,15 @@
// when using the HTTP+RPC mode nad passing a trace file to the shell, via
// trace_processor_shell -D trace_file.pftrace .
optional string loaded_trace_name = 1;
+
+ // Typically something like "v11.0.123", but could be just "v11" or "unknown",
+ // for binaries built from Bazel or other build configurations. This is for
+ // human presentation only, don't attempt to parse and reason on it.
+ optional string human_readable_version = 2;
+
+ // 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;
}
// Input for the /compute_metric endpoint.
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index f2358ac..23625b3 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -356,6 +356,7 @@
":lib",
"../../gn:default_deps",
"../../gn:protobuf_full",
+ "../../protos/perfetto/trace_processor:zero",
"../../src/profiling:deobfuscator",
"../../src/profiling/symbolizer",
"../../src/profiling/symbolizer:symbolize_database",
diff --git a/src/trace_processor/python/perfetto/trace_processor/trace_processor.descriptor b/src/trace_processor/python/perfetto/trace_processor/trace_processor.descriptor
index 476c0b9..e3e9b24 100644
--- a/src/trace_processor/python/perfetto/trace_processor/trace_processor.descriptor
+++ b/src/trace_processor/python/perfetto/trace_processor/trace_processor.descriptor
Binary files differ
diff --git a/src/trace_processor/python/perfetto/trace_processor/trace_processor.descriptor.sha1 b/src/trace_processor/python/perfetto/trace_processor/trace_processor.descriptor.sha1
index 1b19c08..6e01bd0 100644
--- a/src/trace_processor/python/perfetto/trace_processor/trace_processor.descriptor.sha1
+++ b/src/trace_processor/python/perfetto/trace_processor/trace_processor.descriptor.sha1
@@ -2,5 +2,5 @@
// SHA1(tools/gen_binary_descriptors)
// 9fc6d77de57ec76a80b76aa282f4c7cf5ce55eec
// SHA1(protos/perfetto/trace_processor/trace_processor.proto)
-// f0ba693bdd1111c81e9240c966e16babd489bb03
+// b4135d9bc2551939bfdaa4d03423cf34a8fbcffb
\ No newline at end of file
diff --git a/src/trace_processor/rpc/httpd.cc b/src/trace_processor/rpc/httpd.cc
index da690fc..2cdf80f 100644
--- a/src/trace_processor/rpc/httpd.cc
+++ b/src/trace_processor/rpc/httpd.cc
@@ -401,12 +401,9 @@
}
if (req.uri == "/status") {
- protozero::HeapBuffered<protos::pbzero::StatusResult> res;
- res->set_loaded_trace_name(
- trace_processor_rpc_.GetCurrentTraceName().c_str());
- std::vector<uint8_t> buf = res.SerializeAsArray();
- return HttpReply(client->sock.get(), "200 OK", headers, buf.data(),
- buf.size());
+ auto status = trace_processor_rpc_.GetStatus();
+ return HttpReply(client->sock.get(), "200 OK", headers, status.data(),
+ status.size());
}
if (req.uri == "/compute_metric") {
diff --git a/src/trace_processor/rpc/rpc.cc b/src/trace_processor/rpc/rpc.cc
index 17df0be..a58cd7c 100644
--- a/src/trace_processor/rpc/rpc.cc
+++ b/src/trace_processor/rpc/rpc.cc
@@ -23,6 +23,7 @@
#include "perfetto/base/logging.h"
#include "perfetto/base/time.h"
#include "perfetto/ext/base/utils.h"
+#include "perfetto/ext/base/version.h"
#include "perfetto/protozero/scattered_heap_buffer.h"
#include "perfetto/protozero/scattered_stream_writer.h"
#include "perfetto/trace_processor/trace_processor.h"
@@ -241,6 +242,13 @@
resp.Send(rpc_response_fn_);
break;
}
+ case RpcProto::TPM_GET_STATUS: {
+ Response resp(tx_seq_id_++, req_type);
+ std::vector<uint8_t> status = GetStatus();
+ resp->set_status()->AppendRawProtoBytes(status.data(), status.size());
+ resp.Send(rpc_response_fn_);
+ break;
+ }
default: {
// This can legitimately happen if the client is newer. We reply with a
// generic "unkown request" response, so the client can do feature
@@ -453,10 +461,6 @@
PERFETTO_DLOG("[RPC] RawQuery > %d rows (err: %d)", rows, !status.ok());
}
-std::string Rpc::GetCurrentTraceName() {
- return trace_processor_->GetCurrentTraceName();
-}
-
void Rpc::RestoreInitialTables() {
trace_processor_->RestoreInitialTables();
}
@@ -534,5 +538,13 @@
}
}
+std::vector<uint8_t> Rpc::GetStatus() {
+ protozero::HeapBuffered<protos::pbzero::StatusResult> status;
+ status->set_loaded_trace_name(trace_processor_->GetCurrentTraceName());
+ status->set_human_readable_version(base::GetVersionString());
+ status->set_api_version(protos::pbzero::TRACE_PROCESSOR_CURRENT_API_VERSION);
+ return status.SerializeAsArray();
+}
+
} // namespace trace_processor
} // namespace perfetto
diff --git a/src/trace_processor/rpc/rpc.h b/src/trace_processor/rpc/rpc.h
index 9006154..8501b56 100644
--- a/src/trace_processor/rpc/rpc.h
+++ b/src/trace_processor/rpc/rpc.h
@@ -106,6 +106,7 @@
std::vector<uint8_t> ComputeMetric(const uint8_t* data, size_t len);
void EnableMetatrace();
std::vector<uint8_t> DisableAndReadMetatrace();
+ std::vector<uint8_t> GetStatus();
// Creates a new RPC session by deleting all tables and views that have been
// created (by the UI or user) after the trace was loaded; built-in
diff --git a/src/trace_processor/trace_processor_shell.cc b/src/trace_processor/trace_processor_shell.cc
index 9db56c4..6925948 100644
--- a/src/trace_processor/trace_processor_shell.cc
+++ b/src/trace_processor/trace_processor_shell.cc
@@ -45,6 +45,8 @@
#include "src/trace_processor/util/proto_to_json.h"
#include "src/trace_processor/util/status_macros.h"
+#include "protos/perfetto/trace_processor/trace_processor.pbzero.h"
+
#if PERFETTO_BUILDFLAG(PERFETTO_TP_HTTPD)
#include "src/trace_processor/rpc/httpd.h"
#endif
@@ -756,6 +758,8 @@
if (option == 'v') {
printf("%s\n", base::GetVersionString());
+ printf("Trace Processor RPC API version: %d\n",
+ protos::pbzero::TRACE_PROCESSOR_CURRENT_API_VERSION);
exit(0);
}
diff --git a/ui/src/common/http_rpc_engine.ts b/ui/src/common/http_rpc_engine.ts
index 498d086..d404ea9 100644
--- a/ui/src/common/http_rpc_engine.ts
+++ b/ui/src/common/http_rpc_engine.ts
@@ -23,7 +23,7 @@
export interface HttpRpcState {
connected: boolean;
- loadedTraceName?: string;
+ status?: StatusResult;
failure?: string;
}
@@ -98,11 +98,8 @@
httpRpcState.failure = `${resp.status} - ${resp.statusText}`;
} else {
const buf = new Uint8Array(await resp.arrayBuffer());
- const status = StatusResult.decode(buf);
httpRpcState.connected = true;
- if (status.loadedTraceName) {
- httpRpcState.loadedTraceName = status.loadedTraceName;
- }
+ httpRpcState.status = StatusResult.decode(buf);
}
} catch (err) {
httpRpcState.failure = `${err}`;
diff --git a/ui/src/frontend/rpc_http_dialog.ts b/ui/src/frontend/rpc_http_dialog.ts
index 40e9036..77ee0fe 100644
--- a/ui/src/frontend/rpc_http_dialog.ts
+++ b/ui/src/frontend/rpc_http_dialog.ts
@@ -13,14 +13,22 @@
// limitations under the License.
import '../tracks/all_frontend';
+
import * as m from 'mithril';
+import {assertExists} from '../base/logging';
import {Actions} from '../common/actions';
import {HttpRpcEngine, RPC_URL} from '../common/http_rpc_engine';
+import {StatusResult} from '../common/protos';
+import * as version from '../gen/perfetto_version';
+import {perfetto} from '../gen/protos';
import {globals} from './globals';
import {showModal} from './modal';
+const CURRENT_API_VERSION = perfetto.protos.TraceProcessorApiVersion
+ .TRACE_PROCESSOR_CURRENT_API_VERSION;
+
const PROMPT = `Trace Processor Native Accelerator detected on ${RPC_URL} with:
$loadedTraceName
@@ -43,6 +51,28 @@
too old. Get the latest version from get.perfetto.dev/trace_processor.
`;
+
+const MSG_TOO_OLD = `The Trace Processor instance on ${RPC_URL} is too old.
+
+This UI requires TraceProcessor features that are not present in the
+Trace Processor native accelerator you are currently running.
+If you continue, this is almost surely going to cause UI failures.
+
+Please update your local Trace Processor binary:
+
+curl -LO https://get.perfetto.dev/trace_processor
+chmod +x ./trace_processor
+./trace_processor --httpd
+
+UI version: ${version.VERSION}
+TraceProcessor RPC API required: ${CURRENT_API_VERSION} or higher
+
+TraceProcessor version: $tpVersion
+RPC API: $tpApi
+`;
+
+let forceUseOldVersion = false;
+
// Try to connect to the external Trace Processor HTTP RPC accelerator (if
// available, often it isn't). If connected it will populate the
// |httpRpcState| in the frontend local state. In turn that will show the UI
@@ -53,41 +83,81 @@
export async function CheckHttpRpcConnection(): Promise<void> {
const state = await HttpRpcEngine.checkConnection();
globals.frontendLocalState.setHttpRpcState(state);
+ if (!state.connected) return;
+ const tpStatus = assertExists(state.status);
- // 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.
- if (state.connected && state.loadedTraceName) {
- showModal({
- title: 'Use Trace Processor Native Acceleration?',
- content:
- m('.modal-pre',
- PROMPT.replace('$loadedTraceName', state.loadedTraceName)),
- buttons: [
- {
- text: 'YES, use loaded trace',
- primary: true,
- id: 'rpc_load',
- action: () => {
- globals.dispatch(Actions.openTraceFromHttpRpc({}));
- }
- },
- {
- text: 'YES, but reset state',
- primary: false,
- id: 'rpc_reset',
- action: () => {}
- },
- {
- text: 'NO, Use builtin WASM',
- primary: false,
- id: 'rpc_force_wasm',
- action: () => {
- globals.dispatch(
- Actions.setNewEngineMode({mode: 'FORCE_BUILTIN_WASM'}));
- }
- },
- ],
- });
+ if (tpStatus.apiVersion < CURRENT_API_VERSION) {
+ await showDialogTraceProcessorTooOld(tpStatus);
+ if (!forceUseOldVersion) return;
}
+
+ 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);
+ }
+}
+
+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}`)),
+ buttons: [
+ {
+ text: 'Use builtin Wasm',
+ primary: true,
+ id: 'tp_old_wasm',
+ action: () => {
+ globals.dispatch(
+ Actions.setNewEngineMode({mode: 'FORCE_BUILTIN_WASM'}));
+ }
+ },
+ {
+ text: 'Use old version regardless (might crash)',
+ primary: false,
+ id: 'tp_old_cont',
+ action: () => {
+ forceUseOldVersion = true;
+ }
+ },
+ ],
+ });
+}
+
+async function showDialogToUsePreloadedTrace(tpStatus: StatusResult) {
+ return showModal({
+ title: 'Use Trace Processor Native Acceleration?',
+ content:
+ m('.modal-pre',
+ PROMPT.replace('$loadedTraceName', tpStatus.loadedTraceName)),
+ buttons: [
+ {
+ text: 'YES, use loaded trace',
+ primary: true,
+ id: 'rpc_load',
+ action: () => {
+ globals.dispatch(Actions.openTraceFromHttpRpc({}));
+ }
+ },
+ {
+ text: 'YES, but reset state',
+ primary: false,
+ id: 'rpc_reset',
+ action: () => {}
+ },
+ {
+ text: 'NO, Use builtin Wasm',
+ primary: false,
+ id: 'rpc_force_wasm',
+ action: () => {
+ globals.dispatch(
+ Actions.setNewEngineMode({mode: 'FORCE_BUILTIN_WASM'}));
+ }
+ },
+ ],
+ });
}