Merge "tools: add utilities to record traces with reporter API" into main
diff --git a/Android.bp b/Android.bp
index dac6686..d4db032 100644
--- a/Android.bp
+++ b/Android.bp
@@ -13295,7 +13295,6 @@
":perfetto_src_trace_processor_util_zip_reader",
":perfetto_src_trace_processor_views_views",
"src/trace_processor/trace_processor_shell.cc",
- "src/trace_processor/util/proto_to_json.cc",
],
static_libs: [
"perfetto_src_trace_processor_demangle",
diff --git a/BUILD b/BUILD
index afbb6b8..8cc2689 100644
--- a/BUILD
+++ b/BUILD
@@ -5436,8 +5436,6 @@
":src_trace_processor_util_zip_reader",
":src_trace_processor_views_views",
"src/trace_processor/trace_processor_shell.cc",
- "src/trace_processor/util/proto_to_json.cc",
- "src/trace_processor/util/proto_to_json.h",
],
visibility = [
"//visibility:public",
diff --git a/include/perfetto/public/pb_utils.h b/include/perfetto/public/pb_utils.h
index ef4ed97..32ea759 100644
--- a/include/perfetto/public/pb_utils.h
+++ b/include/perfetto/public/pb_utils.h
@@ -123,4 +123,16 @@
PERFETTO_STATIC_CAST(uint64_t, value >> 63);
}
+static inline int32_t PerfettoPbZigZagDecode32(uint32_t value) {
+ uint32_t mask =
+ PERFETTO_STATIC_CAST(uint32_t, -PERFETTO_STATIC_CAST(int32_t, value & 1));
+ return PERFETTO_STATIC_CAST(int32_t, ((value >> 1) ^ mask));
+}
+
+static inline int64_t PerfettoPbZigZagDecode64(uint64_t value) {
+ uint64_t mask =
+ PERFETTO_STATIC_CAST(uint64_t, -PERFETTO_STATIC_CAST(int64_t, value & 1));
+ return PERFETTO_STATIC_CAST(int64_t, ((value >> 1) ^ mask));
+}
+
#endif // INCLUDE_PERFETTO_PUBLIC_PB_UTILS_H_
diff --git a/include/perfetto/trace_processor/read_trace.h b/include/perfetto/trace_processor/read_trace.h
index 98b3ddd..415d7d2 100644
--- a/include/perfetto/trace_processor/read_trace.h
+++ b/include/perfetto/trace_processor/read_trace.h
@@ -17,6 +17,7 @@
#ifndef INCLUDE_PERFETTO_TRACE_PROCESSOR_READ_TRACE_H_
#define INCLUDE_PERFETTO_TRACE_PROCESSOR_READ_TRACE_H_
+#include <cstdint>
#include <functional>
#include <vector>
diff --git a/perfetto.rc b/perfetto.rc
index 2118172..8a5ee38 100644
--- a/perfetto.rc
+++ b/perfetto.rc
@@ -19,7 +19,7 @@
socket traced_producer stream 0666 root root
user nobody
group nobody
- task_profiles ServiceCapacityLow
+ task_profiles ProcessCapacityHigh
service traced_probes /system/bin/traced_probes
class late_start
@@ -28,7 +28,7 @@
# Despite the "log" group below, traced_probes is allowed to read log
# only on userdebug/eng via selinux (see traced_probes.te).
group nobody readproc log readtracefs
- task_profiles ServiceCapacityLow
+ task_profiles ProcessCapacityHigh
# Clean up procfs configuration even if traced_probes crashes
# unexpectedly.
onrestart exec_background - nobody shell -- /system/bin/traced_probes --cleanup-after-crash
diff --git a/protos/perfetto/common/descriptor.proto b/protos/perfetto/common/descriptor.proto
index 012401f..6684a70 100644
--- a/protos/perfetto/common/descriptor.proto
+++ b/protos/perfetto/common/descriptor.proto
@@ -82,6 +82,34 @@
repeated string reserved_name = 10;
}
+// A message representing a option the parser does not recognize. This only
+// appears in options protos created by the compiler::Parser class.
+// DescriptorPool resolves these when building Descriptor objects. Therefore,
+// options protos in descriptor objects (e.g. returned by Descriptor::options(),
+// or produced by Descriptor::CopyTo()) will never have UninterpretedOptions
+// in them.
+message UninterpretedOption {
+ // The name of the uninterpreted option. Each string represents a segment in
+ // a dot-separated name. is_extension is true iff a segment represents an
+ // extension (denoted with parentheses in options specs in .proto files).
+ // E.g.,{ ["foo", false], ["bar.baz", true], ["moo", false] } represents
+ // "foo.(bar.baz).moo".
+ message NamePart {
+ optional string name_part = 1;
+ optional bool is_extension = 2;
+ }
+ repeated NamePart name = 2;
+
+ // The value of the uninterpreted option, in whatever type the tokenizer
+ // identified it as during parsing. Exactly one of these should be set.
+ optional string identifier_value = 3;
+ optional uint64 positive_int_value = 4;
+ optional int64 negative_int_value = 5;
+ optional double double_value = 6;
+ optional bytes string_value = 7;
+ optional string aggregate_value = 8;
+}
+
message FieldOptions {
// The packed option can be enabled for repeated primitive fields to enable
// a more efficient representation on the wire. Rather than repeatedly
@@ -89,6 +117,9 @@
// a single length-delimited blob. In proto3, only explicit setting it to
// false will avoid using packed encoding.
optional bool packed = 2;
+
+ // The parser stores options it doesn't recognize here. See above.
+ repeated UninterpretedOption uninterpreted_option = 999;
}
// Describes a field within a message.
diff --git a/protos/perfetto/metrics/BUILD.gn b/protos/perfetto/metrics/BUILD.gn
index 751ed94..e09200c 100644
--- a/protos/perfetto/metrics/BUILD.gn
+++ b/protos/perfetto/metrics/BUILD.gn
@@ -25,7 +25,10 @@
}
perfetto_proto_library("custom_options_@TYPE@") {
- proto_generators = [ "source_set" ]
+ proto_generators = [
+ "lite",
+ "source_set",
+ ]
sources = [ "custom_options.proto" ]
import_dirs = [ "${perfetto_protobuf_src_dir}" ]
}
diff --git a/protos/perfetto/metrics/chrome/BUILD.gn b/protos/perfetto/metrics/chrome/BUILD.gn
index 60c60a7..a8b6bc2 100644
--- a/protos/perfetto/metrics/chrome/BUILD.gn
+++ b/protos/perfetto/metrics/chrome/BUILD.gn
@@ -15,7 +15,11 @@
import("../../../../gn/proto_library.gni")
perfetto_proto_library("@TYPE@") {
- proto_generators = [ "source_set" ]
+ proto_generators = [
+ "lite",
+ "source_set",
+ ]
+ import_dirs = [ "${perfetto_protobuf_src_dir}" ]
deps = [
"..:@TYPE@",
"..:custom_options_@TYPE@",
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index 70d9546..4479846 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -5231,6 +5231,34 @@
repeated string reserved_name = 10;
}
+// A message representing a option the parser does not recognize. This only
+// appears in options protos created by the compiler::Parser class.
+// DescriptorPool resolves these when building Descriptor objects. Therefore,
+// options protos in descriptor objects (e.g. returned by Descriptor::options(),
+// or produced by Descriptor::CopyTo()) will never have UninterpretedOptions
+// in them.
+message UninterpretedOption {
+ // The name of the uninterpreted option. Each string represents a segment in
+ // a dot-separated name. is_extension is true iff a segment represents an
+ // extension (denoted with parentheses in options specs in .proto files).
+ // E.g.,{ ["foo", false], ["bar.baz", true], ["moo", false] } represents
+ // "foo.(bar.baz).moo".
+ message NamePart {
+ optional string name_part = 1;
+ optional bool is_extension = 2;
+ }
+ repeated NamePart name = 2;
+
+ // The value of the uninterpreted option, in whatever type the tokenizer
+ // identified it as during parsing. Exactly one of these should be set.
+ optional string identifier_value = 3;
+ optional uint64 positive_int_value = 4;
+ optional int64 negative_int_value = 5;
+ optional double double_value = 6;
+ optional bytes string_value = 7;
+ optional string aggregate_value = 8;
+}
+
message FieldOptions {
// The packed option can be enabled for repeated primitive fields to enable
// a more efficient representation on the wire. Rather than repeatedly
@@ -5238,6 +5266,9 @@
// a single length-delimited blob. In proto3, only explicit setting it to
// false will avoid using packed encoding.
optional bool packed = 2;
+
+ // The parser stores options it doesn't recognize here. See above.
+ repeated UninterpretedOption uninterpreted_option = 999;
}
// Describes a field within a message.
diff --git a/python/perfetto/trace_processor/trace_processor.descriptor b/python/perfetto/trace_processor/trace_processor.descriptor
index cc5813f..eca1b1a 100644
--- a/python/perfetto/trace_processor/trace_processor.descriptor
+++ b/python/perfetto/trace_processor/trace_processor.descriptor
Binary files differ
diff --git a/python/tools/check_imports.py b/python/tools/check_imports.py
index bac9122..ef4ff29 100755
--- a/python/tools/check_imports.py
+++ b/python/tools/check_imports.py
@@ -56,6 +56,24 @@
])
+class AllowList(object):
+
+ def __init__(self, allowed, dst, reasoning):
+ self.allowed = allowed
+ self.dst = dst
+ self.reasoning = reasoning
+
+ def check(self, graph):
+ for node, edges in graph.items():
+ for edge in edges:
+ if re.match(self.dst, edge):
+ if not any(re.match(a, node) for a in self.allowed):
+ yield Failure([node, edge], self)
+
+ def __str__(self):
+ return f'Only items in the allowlist ({self.allowed}) may directly depend on "{self.dst}" ' + self.reasoning
+
+
class NoDirectDep(object):
def __init__(self, src, dst, reasoning):
@@ -114,6 +132,11 @@
# NoDep(a, b) = as above but 'a' may not even transitively import 'b'.
# NoCircularDeps = forbid introduction of circular dependencies
RULES = [
+ AllowList(
+ ['/core/protos'],
+ r'/gen/protos',
+ 'protos should be re-exported from /core/protos without the nesting.',
+ ),
NoDirectDep(
r'/plugins/.*',
r'/core/.*',
diff --git a/src/protozero/test/fake_scattered_buffer.cc b/src/protozero/test/fake_scattered_buffer.cc
index 7b65b29..1c36063 100644
--- a/src/protozero/test/fake_scattered_buffer.cc
+++ b/src/protozero/test/fake_scattered_buffer.cc
@@ -16,6 +16,7 @@
#include "src/protozero/test/fake_scattered_buffer.h"
+#include <iomanip>
#include <sstream>
#include <utility>
diff --git a/src/shared_lib/test/api_integrationtest.cc b/src/shared_lib/test/api_integrationtest.cc
index 24b2ec1..7a2889d 100644
--- a/src/shared_lib/test/api_integrationtest.cc
+++ b/src/shared_lib/test/api_integrationtest.cc
@@ -249,13 +249,13 @@
return thiz->ds2_callbacks_.OnStart(ds_impl, inst_id, thiz->ds2_user_arg_,
inst_ctx, args);
};
- params.on_stop_cb =
- [](struct PerfettoDsImpl* ds_impl, PerfettoDsInstanceIndex inst_id,
- void* user_arg, void* inst_ctx, struct PerfettoDsOnStopArgs* args) {
- auto* thiz = static_cast<SharedLibDataSourceTest*>(user_arg);
- return thiz->ds2_callbacks_.OnStop(
- ds_impl, inst_id, thiz->ds2_user_arg_, inst_ctx, args);
- };
+ params.on_stop_cb = [](struct PerfettoDsImpl* ds_impl,
+ PerfettoDsInstanceIndex inst_id, void* user_arg,
+ void* inst_ctx, struct PerfettoDsOnStopArgs* args) {
+ auto* thiz = static_cast<SharedLibDataSourceTest*>(user_arg);
+ return thiz->ds2_callbacks_.OnStop(ds_impl, inst_id, thiz->ds2_user_arg_,
+ inst_ctx, args);
+ };
params.on_destroy_cb = [](struct PerfettoDsImpl* ds_impl, void* user_arg,
void* inst_ctx) -> void {
auto* thiz = static_cast<SharedLibDataSourceTest*>(user_arg);
@@ -792,6 +792,71 @@
EXPECT_TRUE(found);
}
+TEST_F(SharedLibTrackEventTest, TrackEventHlDynamicCategoryMultipleSessions) {
+ TracingSession tracing_session1 = TracingSession::Builder()
+ .set_data_source_name("track_event")
+ .add_enabled_category("cat1")
+ .add_enabled_category("dyn1")
+ .add_disabled_category("dyn2")
+ .add_disabled_category("*")
+ .Build();
+
+ TracingSession tracing_session2 = TracingSession::Builder()
+ .set_data_source_name("track_event")
+ .add_enabled_category("cat1")
+ .add_enabled_category("dyn2")
+ .add_disabled_category("dyn1")
+ .add_disabled_category("*")
+ .Build();
+
+ PERFETTO_TE(PERFETTO_TE_DYNAMIC_CATEGORY,
+ PERFETTO_TE_INSTANT("interned_string"),
+ PERFETTO_TE_DYNAMIC_CATEGORY_STRING("dyn1"));
+ PERFETTO_TE(PERFETTO_TE_DYNAMIC_CATEGORY,
+ PERFETTO_TE_INSTANT("interned_string"),
+ PERFETTO_TE_DYNAMIC_CATEGORY_STRING("dyn2"));
+ PERFETTO_TE(cat1, PERFETTO_TE_INSTANT(""));
+
+ tracing_session1.StopBlocking();
+ std::vector<uint8_t> data1 = tracing_session1.ReadBlocking();
+ EXPECT_THAT(
+ FieldView(data1),
+ Contains(PbField(
+ perfetto_protos_Trace_packet_field_number,
+ MsgField(AllOf(
+ Contains(PbField(
+ perfetto_protos_TracePacket_track_event_field_number,
+ MsgField(Contains(PbField(
+ perfetto_protos_TrackEvent_categories_field_number,
+ StringField("dyn1")))))),
+ Contains(PbField(
+ perfetto_protos_TracePacket_interned_data_field_number,
+ MsgField(Contains(PbField(
+ perfetto_protos_InternedData_event_names_field_number,
+ MsgField(Contains(
+ PbField(perfetto_protos_EventName_name_field_number,
+ StringField("interned_string"))))))))))))));
+ tracing_session2.StopBlocking();
+ std::vector<uint8_t> data2 = tracing_session2.ReadBlocking();
+ EXPECT_THAT(
+ FieldView(data2),
+ Contains(PbField(
+ perfetto_protos_Trace_packet_field_number,
+ MsgField(AllOf(
+ Contains(PbField(
+ perfetto_protos_TracePacket_track_event_field_number,
+ MsgField(Contains(PbField(
+ perfetto_protos_TrackEvent_categories_field_number,
+ StringField("dyn2")))))),
+ Contains(PbField(
+ perfetto_protos_TracePacket_interned_data_field_number,
+ MsgField(Contains(PbField(
+ perfetto_protos_InternedData_event_names_field_number,
+ MsgField(Contains(
+ PbField(perfetto_protos_EventName_name_field_number,
+ StringField("interned_string"))))))))))))));
+}
+
TEST_F(SharedLibTrackEventTest, TrackEventHlInstant) {
TracingSession tracing_session =
TracingSession::Builder().set_data_source_name("track_event").Build();
diff --git a/src/shared_lib/track_event.cc b/src/shared_lib/track_event.cc
index 46dda54..29d9a8c 100644
--- a/src/shared_lib/track_event.cc
+++ b/src/shared_lib/track_event.cc
@@ -856,10 +856,13 @@
return res;
}
+// If the category `dyn_cat` is enabled on the data source instance pointed by
+// `ii`, returns immediately. Otherwise, advances `ii` to a data source instance
+// where `dyn_cat` is enabled. If there's no data source instance where
+// `dyn_cat` is enabled, `ii->instance` will be nullptr.
static void AdvanceToFirstEnabledDynamicCategory(
perfetto::internal::DataSourceType::InstancesIterator* ii,
perfetto::internal::DataSourceThreadLocalState* tls_state,
- perfetto::shlib::TrackEventIncrementalState* incr_state,
struct PerfettoTeCategoryImpl* cat,
const PerfettoTeCategoryDescriptor& dyn_cat) {
perfetto::internal::DataSourceType* ds =
@@ -867,6 +870,9 @@
for (; ii->instance;
ds->NextIteration</*Traits=*/perfetto::shlib::TracePointTraits>(
ii, tls_state, {cat})) {
+ auto* incr_state =
+ static_cast<perfetto::shlib::TrackEventIncrementalState*>(
+ ds->GetIncrementalState(ii->instance, ii->i));
if (IsDynamicCategoryEnabled(ii->i, incr_state, dyn_cat)) {
break;
}
@@ -933,6 +939,13 @@
ts = TrackEventInternal::GetTraceTime();
}
+ if (PERFETTO_UNLIKELY(dynamic_cat)) {
+ AdvanceToFirstEnabledDynamicCategory(ii, tls_state, cat, *dynamic_cat);
+ if (!ii->instance) {
+ return;
+ }
+ }
+
const auto& track_event_tls =
*static_cast<perfetto::shlib::TrackEventTlsState*>(
ii->instance->data_source_custom_tls.get());
@@ -942,14 +955,6 @@
ResetIncrementalStateIfRequired(ii->instance->trace_writer.get(), incr_state,
track_event_tls, ts);
- if (PERFETTO_UNLIKELY(dynamic_cat)) {
- AdvanceToFirstEnabledDynamicCategory(ii, tls_state, incr_state, cat,
- *dynamic_cat);
- if (!ii->instance) {
- return;
- }
- }
-
if (registered_track) {
if (incr_state->seen_track_uuids.insert(registered_track->uuid).second) {
auto packet = ii->instance->trace_writer->NewTracePacket();
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index 13fcc5e..38c5cf0 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -214,11 +214,7 @@
if (enable_perfetto_trace_processor_httpd) {
deps += [ "rpc:httpd" ]
}
- sources = [
- "trace_processor_shell.cc",
- "util/proto_to_json.cc",
- "util/proto_to_json.h",
- ]
+ sources = [ "trace_processor_shell.cc" ]
if ((perfetto_build_standalone || build_with_chromium) &&
!is_perfetto_build_generator) {
data_deps = [
diff --git a/src/trace_processor/metrics/metrics_unittest.cc b/src/trace_processor/metrics/metrics_unittest.cc
index 7d90e81..7b2d814 100644
--- a/src/trace_processor/metrics/metrics_unittest.cc
+++ b/src/trace_processor/metrics/metrics_unittest.cc
@@ -19,6 +19,7 @@
#include <vector>
#include "protos/perfetto/common/descriptor.pbzero.h"
+#include "src/trace_processor/util/descriptors.h"
#include "test/gtest_and_gmock.h"
namespace perfetto {
@@ -78,8 +79,9 @@
ProtoDescriptor descriptor("file.proto", ".perfetto.protos",
".perfetto.protos.TestProto",
ProtoDescriptor::Type::kMessage, std::nullopt);
- descriptor.AddField(FieldDescriptor(
- "int_value", 1, FieldDescriptorProto::TYPE_INT64, "", false, false));
+ descriptor.AddField(FieldDescriptor("int_value", 1,
+ FieldDescriptorProto::TYPE_INT64, "",
+ std::vector<uint8_t>(), false, false));
ProtoBuilder builder(&pool, &descriptor);
ASSERT_TRUE(builder.AppendLong("int_value", 12345).ok());
@@ -101,8 +103,9 @@
ProtoDescriptor descriptor("file.proto", ".perfetto.protos",
".perfetto.protos.TestProto",
ProtoDescriptor::Type::kMessage, std::nullopt);
- descriptor.AddField(FieldDescriptor(
- "double_value", 1, FieldDescriptorProto::TYPE_DOUBLE, "", false, false));
+ descriptor.AddField(FieldDescriptor("double_value", 1,
+ FieldDescriptorProto::TYPE_DOUBLE, "",
+ std::vector<uint8_t>(), false, false));
ProtoBuilder builder(&pool, &descriptor);
ASSERT_TRUE(builder.AppendDouble("double_value", 1.2345).ok());
@@ -124,8 +127,9 @@
ProtoDescriptor descriptor("file.proto", ".perfetto.protos",
".perfetto.protos.TestProto",
ProtoDescriptor::Type::kMessage, std::nullopt);
- descriptor.AddField(FieldDescriptor(
- "string_value", 1, FieldDescriptorProto::TYPE_STRING, "", false, false));
+ descriptor.AddField(FieldDescriptor("string_value", 1,
+ FieldDescriptorProto::TYPE_STRING, "",
+ std::vector<uint8_t>(), false, false));
ProtoBuilder builder(&pool, &descriptor);
ASSERT_TRUE(builder.AppendString("string_value", "hello world!").ok());
@@ -151,15 +155,16 @@
".perfetto.protos.TestProto.NestedProto",
ProtoDescriptor::Type::kMessage, std::nullopt);
nested.AddField(FieldDescriptor("nested_int_value", 1,
- FieldDescriptorProto::TYPE_INT64, "", false,
- false));
+ FieldDescriptorProto::TYPE_INT64, "",
+ std::vector<uint8_t>(), false, false));
ProtoDescriptor descriptor("file.proto", ".perfetto.protos",
".perfetto.protos.TestProto",
ProtoDescriptor::Type::kMessage, std::nullopt);
auto field =
FieldDescriptor("nested_value", 1, FieldDescriptorProto::TYPE_MESSAGE,
- ".perfetto.protos.TestProto.NestedProto", false, false);
+ ".perfetto.protos.TestProto.NestedProto",
+ std::vector<uint8_t>(), false, false);
field.set_resolved_type_name(".perfetto.protos.TestProto.NestedProto");
descriptor.AddField(field);
@@ -199,8 +204,9 @@
ProtoDescriptor descriptor("file.proto", ".perfetto.protos",
".perfetto.protos.TestProto",
ProtoDescriptor::Type::kMessage, std::nullopt);
- descriptor.AddField(FieldDescriptor(
- "rep_int_value", 1, FieldDescriptorProto::TYPE_INT64, "", true, false));
+ descriptor.AddField(FieldDescriptor("rep_int_value", 1,
+ FieldDescriptorProto::TYPE_INT64, "",
+ std::vector<uint8_t>(), true, false));
RepeatedFieldBuilder rep_builder;
rep_builder.AddLong(1234);
@@ -246,7 +252,8 @@
".perfetto.protos.TestMessage",
ProtoDescriptor::Type::kMessage, std::nullopt);
FieldDescriptor enum_field("enum_value", 1, FieldDescriptorProto::TYPE_ENUM,
- ".perfetto.protos.TestEnum", false, false);
+ ".perfetto.protos.TestEnum",
+ std::vector<uint8_t>(), false, false);
enum_field.set_resolved_type_name(".perfetto.protos.TestEnum");
descriptor.AddField(enum_field);
pool.AddProtoDescriptorForTesting(descriptor);
diff --git a/src/trace_processor/perfetto_sql/engine/created_function.cc b/src/trace_processor/perfetto_sql/engine/created_function.cc
index a4f8678..66be287 100644
--- a/src/trace_processor/perfetto_sql/engine/created_function.cc
+++ b/src/trace_processor/perfetto_sql/engine/created_function.cc
@@ -16,6 +16,7 @@
#include "src/trace_processor/perfetto_sql/engine/created_function.h"
+#include <cstddef>
#include <queue>
#include <stack>
diff --git a/src/trace_processor/read_trace_internal.h b/src/trace_processor/read_trace_internal.h
index f4bb56e..c946ad7 100644
--- a/src/trace_processor/read_trace_internal.h
+++ b/src/trace_processor/read_trace_internal.h
@@ -17,6 +17,7 @@
#ifndef SRC_TRACE_PROCESSOR_READ_TRACE_INTERNAL_H_
#define SRC_TRACE_PROCESSOR_READ_TRACE_INTERNAL_H_
+#include <cstdint>
#include <functional>
#include <vector>
diff --git a/src/trace_processor/sqlite/query_constraints.h b/src/trace_processor/sqlite/query_constraints.h
index a5c299d..6ff7e5b 100644
--- a/src/trace_processor/sqlite/query_constraints.h
+++ b/src/trace_processor/sqlite/query_constraints.h
@@ -17,6 +17,7 @@
#ifndef SRC_TRACE_PROCESSOR_SQLITE_QUERY_CONSTRAINTS_H_
#define SRC_TRACE_PROCESSOR_SQLITE_QUERY_CONSTRAINTS_H_
+#include <cstdint>
#include <limits>
#include <vector>
diff --git a/src/trace_processor/sqlite/sql_source.h b/src/trace_processor/sqlite/sql_source.h
index 805105e..7d3220b 100644
--- a/src/trace_processor/sqlite/sql_source.h
+++ b/src/trace_processor/sqlite/sql_source.h
@@ -17,6 +17,7 @@
#ifndef SRC_TRACE_PROCESSOR_SQLITE_SQL_SOURCE_H_
#define SRC_TRACE_PROCESSOR_SQLITE_SQL_SOURCE_H_
+#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 56fb013..21f4709 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -894,7 +894,8 @@
*metrics_string = protozero_to_json::ProtozeroToJson(
pool_, ".perfetto.protos.TraceMetrics",
protozero::ConstBytes{metrics_proto.data(), metrics_proto.size()},
- protozero_to_json::kPretty | protozero_to_json::kInlineErrors);
+ protozero_to_json::kPretty | protozero_to_json::kInlineErrors |
+ protozero_to_json::kInlineAnnotations);
break;
}
return status;
diff --git a/src/trace_processor/trace_processor_shell.cc b/src/trace_processor/trace_processor_shell.cc
index f4b4e0c..0f0543f 100644
--- a/src/trace_processor/trace_processor_shell.cc
+++ b/src/trace_processor/trace_processor_shell.cc
@@ -53,7 +53,6 @@
#include "src/trace_processor/metrics/metrics.descriptor.h"
#include "src/trace_processor/metrics/metrics.h"
#include "src/trace_processor/read_trace_internal.h"
-#include "src/trace_processor/util/proto_to_json.h"
#include "src/trace_processor/util/sql_modules.h"
#include "src/trace_processor/util/status_macros.h"
diff --git a/src/trace_processor/util/BUILD.gn b/src/trace_processor/util/BUILD.gn
index 12f812b..c717d3e 100644
--- a/src/trace_processor/util/BUILD.gn
+++ b/src/trace_processor/util/BUILD.gn
@@ -281,6 +281,12 @@
"../storage",
"../types",
]
+ if (perfetto_build_standalone) {
+ deps += [
+ "../../../protos/perfetto/metrics/chrome:lite",
+ "../metrics:gen_cc_all_chrome_metrics_descriptor",
+ ]
+ }
if (enable_perfetto_zlib) {
sources += [ "gzip_utils_unittest.cc" ]
deps += [ "../../../gn:zlib" ]
diff --git a/src/trace_processor/util/descriptors.cc b/src/trace_processor/util/descriptors.cc
index c47d4bc..bd365f8 100644
--- a/src/trace_processor/util/descriptors.cc
+++ b/src/trace_processor/util/descriptors.cc
@@ -16,9 +16,16 @@
#include "src/trace_processor/util/descriptors.h"
+#include <cstdint>
+#include <optional>
+#include <vector>
+
+#include "perfetto/base/status.h"
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/ext/base/string_view.h"
#include "perfetto/protozero/field.h"
+#include "perfetto/protozero/message.h"
+#include "perfetto/protozero/proto_decoder.h"
#include "perfetto/protozero/scattered_heap_buffer.h"
#include "protos/perfetto/common/descriptor.pbzero.h"
#include "protos/perfetto/trace_processor/trace_processor.pbzero.h"
@@ -26,7 +33,7 @@
namespace perfetto {
namespace trace_processor {
-
+namespace {
FieldDescriptor CreateFieldFromDecoder(
const protos::pbzero::FieldDescriptorProto::Decoder& f_decoder,
bool is_extension) {
@@ -44,10 +51,35 @@
return FieldDescriptor(
base::StringView(f_decoder.name()).ToStdString(),
static_cast<uint32_t>(f_decoder.number()), type, std::move(type_name),
+ std::vector<uint8_t>(f_decoder.options().data,
+ f_decoder.options().data + f_decoder.options().size),
f_decoder.label() == FieldDescriptorProto::LABEL_REPEATED, opt.packed(),
is_extension);
}
+base::Status CheckExtensionField(const ProtoDescriptor& proto_descriptor,
+ const FieldDescriptor& field) {
+ using FieldDescriptorProto = protos::pbzero::FieldDescriptorProto;
+ auto existing_field = proto_descriptor.FindFieldByTag(field.number());
+ if (existing_field) {
+ if (field.type() != existing_field->type()) {
+ return base::ErrStatus("Field %s is re-introduced with different type",
+ field.name().c_str());
+ }
+ if ((field.type() == FieldDescriptorProto::TYPE_MESSAGE ||
+ field.type() == FieldDescriptorProto::TYPE_ENUM) &&
+ field.raw_type_name() != existing_field->raw_type_name()) {
+ return base::ErrStatus(
+ "Field %s is re-introduced with different type %s (was %s)",
+ field.name().c_str(), field.raw_type_name().c_str(),
+ existing_field->raw_type_name().c_str());
+ }
+ }
+ return base::OkStatus();
+}
+
+} // namespace
+
std::optional<uint32_t> DescriptorPool::ResolveShortType(
const std::string& parent_path,
const std::string& short_type) {
@@ -70,31 +102,33 @@
return ResolveShortType(parent_substr, short_type);
}
-util::Status DescriptorPool::AddExtensionField(
+base::Status DescriptorPool::AddExtensionField(
const std::string& package_name,
protozero::ConstBytes field_desc_proto) {
using FieldDescriptorProto = protos::pbzero::FieldDescriptorProto;
FieldDescriptorProto::Decoder f_decoder(field_desc_proto);
auto field = CreateFieldFromDecoder(f_decoder, true);
- auto extendee_name = base::StringView(f_decoder.extendee()).ToStdString();
+ std::string extendee_name = f_decoder.extendee().ToStdString();
if (extendee_name.empty()) {
- return util::ErrStatus("Extendee name is empty");
+ return base::ErrStatus("Extendee name is empty");
}
if (extendee_name[0] != '.') {
// Only prepend if the extendee is not fully qualified
extendee_name = package_name + "." + extendee_name;
}
- auto extendee = FindDescriptorIdx(extendee_name);
+ std::optional<uint32_t> extendee = FindDescriptorIdx(extendee_name);
if (!extendee.has_value()) {
- return util::ErrStatus("Extendee does not exist %s", extendee_name.c_str());
+ return base::ErrStatus("Extendee does not exist %s", extendee_name.c_str());
}
- descriptors_[extendee.value()].AddField(field);
- return util::OkStatus();
+ ProtoDescriptor& extendee_desc = descriptors_[extendee.value()];
+ RETURN_IF_ERROR(CheckExtensionField(extendee_desc, field));
+ extendee_desc.AddField(field);
+ return base::OkStatus();
}
-util::Status DescriptorPool::AddNestedProtoDescriptors(
+base::Status DescriptorPool::AddNestedProtoDescriptors(
const std::string& file_name,
const std::string& package_name,
std::optional<uint32_t> parent_idx,
@@ -111,7 +145,7 @@
auto idx = FindDescriptorIdx(full_name);
if (idx.has_value() && !merge_existing_messages) {
const auto& existing_descriptor = descriptors_[*idx];
- return util::ErrStatus("%s: %s was already defined in file %s",
+ return base::ErrStatus("%s: %s was already defined in file %s",
file_name.c_str(), full_name.c_str(),
existing_descriptor.file_name().c_str());
}
@@ -123,7 +157,7 @@
}
ProtoDescriptor& proto_descriptor = descriptors_[*idx];
if (proto_descriptor.type() != ProtoDescriptor::Type::kMessage) {
- return util::ErrStatus("%s was enum, redefined as message",
+ return base::ErrStatus("%s was enum, redefined as message",
full_name.c_str());
}
@@ -131,23 +165,8 @@
for (auto it = decoder.field(); it; ++it) {
FieldDescriptorProto::Decoder f_decoder(*it);
auto field = CreateFieldFromDecoder(f_decoder, /*is_extension=*/false);
- auto existing_field = proto_descriptor.FindFieldByTag(field.number());
- if (!existing_field) {
- proto_descriptor.AddField(std::move(field));
- } else {
- if (field.type() != existing_field->type()) {
- return util::ErrStatus("Field %s is re-introduced with different type",
- field.name().c_str());
- }
- if ((field.type() == FieldDescriptorProto::TYPE_MESSAGE ||
- field.type() == FieldDescriptorProto::TYPE_ENUM) &&
- field.raw_type_name() != existing_field->raw_type_name()) {
- return util::ErrStatus(
- "Field %s is re-introduced with different type %s (was %s)",
- field.name().c_str(), field.raw_type_name().c_str(),
- existing_field->raw_type_name().c_str());
- }
- }
+ RETURN_IF_ERROR(CheckExtensionField(proto_descriptor, field));
+ proto_descriptor.AddField(std::move(field));
}
for (auto it = decoder.enum_type(); it; ++it) {
@@ -162,10 +181,10 @@
for (auto ext_it = decoder.extension(); ext_it; ++ext_it) {
extensions->emplace_back(package_name, *ext_it);
}
- return util::OkStatus();
+ return base::OkStatus();
}
-util::Status DescriptorPool::AddEnumProtoDescriptors(
+base::Status DescriptorPool::AddEnumProtoDescriptors(
const std::string& file_name,
const std::string& package_name,
std::optional<uint32_t> parent_idx,
@@ -181,7 +200,7 @@
auto prev_idx = FindDescriptorIdx(full_name);
if (prev_idx.has_value() && !merge_existing_messages) {
const auto& existing_descriptor = descriptors_[*prev_idx];
- return util::ErrStatus("%s: %s was already defined in file %s",
+ return base::ErrStatus("%s: %s was already defined in file %s",
file_name.c_str(), full_name.c_str(),
existing_descriptor.file_name().c_str());
}
@@ -193,7 +212,7 @@
}
ProtoDescriptor& proto_descriptor = descriptors_[*prev_idx];
if (proto_descriptor.type() != ProtoDescriptor::Type::kEnum) {
- return util::ErrStatus("%s was message, redefined as enum",
+ return base::ErrStatus("%s was message, redefined as enum",
full_name.c_str());
}
@@ -204,10 +223,10 @@
enum_value.name().ToStdString());
}
- return util::OkStatus();
+ return base::OkStatus();
}
-util::Status DescriptorPool::AddFromFileDescriptorSet(
+base::Status DescriptorPool::AddFromFileDescriptorSet(
const uint8_t* file_descriptor_set_proto,
size_t size,
const std::vector<std::string>& skip_prefixes,
@@ -242,25 +261,23 @@
// Second pass: Add extension fields to the real protos.
for (const auto& extension : extensions) {
- auto status = AddExtensionField(extension.first, extension.second);
- if (!status.ok())
- return status;
+ RETURN_IF_ERROR(AddExtensionField(extension.first, extension.second));
}
- // Third pass: resolve the types of all the fields to the correct indiices.
+ // Third pass: resolve the types of all the fields.
using FieldDescriptorProto = protos::pbzero::FieldDescriptorProto;
- for (auto& descriptor : descriptors_) {
+ for (ProtoDescriptor& descriptor : descriptors_) {
for (auto& entry : *descriptor.mutable_fields()) {
- auto& field = entry.second;
- if (!field.resolved_type_name().empty())
- continue;
-
- if (field.type() == FieldDescriptorProto::TYPE_MESSAGE ||
- field.type() == FieldDescriptorProto::TYPE_ENUM) {
+ FieldDescriptor& field = entry.second;
+ bool needs_resolution =
+ field.resolved_type_name().empty() &&
+ (field.type() == FieldDescriptorProto::TYPE_MESSAGE ||
+ field.type() == FieldDescriptorProto::TYPE_ENUM);
+ if (needs_resolution) {
auto opt_desc =
ResolveShortType(descriptor.full_name(), field.raw_type_name());
if (!opt_desc.has_value()) {
- return util::ErrStatus(
+ return base::ErrStatus(
"Unable to find short type %s in field inside message %s",
field.raw_type_name().c_str(), descriptor.full_name().c_str());
}
@@ -269,7 +286,102 @@
}
}
}
- return util::OkStatus();
+
+ // Fourth pass: resolve all "uninterpreted" options to real options.
+ for (ProtoDescriptor& descriptor : descriptors_) {
+ for (auto& entry : *descriptor.mutable_fields()) {
+ FieldDescriptor& field = entry.second;
+ if (field.options().empty()) {
+ continue;
+ }
+ ResolveUninterpretedOption(descriptor, field, *field.mutable_options());
+ }
+ }
+ return base::OkStatus();
+}
+
+base::Status DescriptorPool::ResolveUninterpretedOption(
+ const ProtoDescriptor& proto_desc,
+ const FieldDescriptor& field_desc,
+ std::vector<uint8_t>& options) {
+ auto opt_idx = FindDescriptorIdx(".google.protobuf.FieldOptions");
+ if (!opt_idx) {
+ return base::ErrStatus("Unable to find field options for field %s in %s",
+ field_desc.name().c_str(),
+ proto_desc.full_name().c_str());
+ }
+ ProtoDescriptor& field_options_desc = descriptors_[*opt_idx];
+
+ protozero::ProtoDecoder decoder(field_desc.options().data(),
+ field_desc.options().size());
+ protozero::HeapBuffered<protozero::Message> field_options;
+ for (;;) {
+ const uint8_t* start = decoder.begin() + decoder.read_offset();
+ auto field = decoder.ReadField();
+ if (!field.valid()) {
+ break;
+ }
+ const uint8_t* end = decoder.begin() + decoder.read_offset();
+
+ if (field.id() !=
+ protos::pbzero::FieldOptions::kUninterpretedOptionFieldNumber) {
+ field_options->AppendRawProtoBytes(start,
+ static_cast<size_t>(end - start));
+ continue;
+ }
+
+ protos::pbzero::UninterpretedOption::Decoder unint(field.as_bytes());
+ auto it = unint.name();
+ if (!it) {
+ return base::ErrStatus(
+ "Option for field %s in message %s does not have a name",
+ field_desc.name().c_str(), proto_desc.full_name().c_str());
+ }
+ protos::pbzero::UninterpretedOption::NamePart::Decoder name_part(*it);
+ auto option_field_desc =
+ field_options_desc.FindFieldByName(name_part.name_part().ToStdString());
+
+ // It's not immediately clear how options with multiple names should
+ // be parsed. This likely requires digging into protobuf compiler
+ // source; given we don't have any examples of this in the codebase
+ // today, defer handling of this to when we may need it.
+ if (++it) {
+ return base::ErrStatus(
+ "Option for field %s in message %s has multiple name segments",
+ field_desc.name().c_str(), proto_desc.full_name().c_str());
+ }
+ if (unint.has_identifier_value()) {
+ field_options->AppendString(option_field_desc->number(),
+ unint.identifier_value().ToStdString());
+ } else if (unint.has_positive_int_value()) {
+ field_options->AppendVarInt(option_field_desc->number(),
+ unint.positive_int_value());
+ } else if (unint.has_negative_int_value()) {
+ field_options->AppendVarInt(option_field_desc->number(),
+ unint.negative_int_value());
+ } else if (unint.has_double_value()) {
+ field_options->AppendFixed(option_field_desc->number(),
+ unint.double_value());
+ } else if (unint.has_string_value()) {
+ field_options->AppendString(option_field_desc->number(),
+ unint.string_value().ToStdString());
+ } else if (unint.has_aggregate_value()) {
+ field_options->AppendString(option_field_desc->number(),
+ unint.aggregate_value().ToStdString());
+ } else {
+ return base::ErrStatus(
+ "Unknown field set in UninterpretedOption %s for field %s in message "
+ "%s",
+ option_field_desc->name().c_str(), field_desc.name().c_str(),
+ proto_desc.full_name().c_str());
+ }
+ }
+ if (decoder.bytes_left() > 0) {
+ return base::ErrStatus("Unexpected extra bytes when parsing option %zu",
+ decoder.bytes_left());
+ }
+ options = field_options.SerializeAsArray();
+ return base::OkStatus();
}
std::optional<uint32_t> DescriptorPool::FindDescriptorIdx(
@@ -293,8 +405,8 @@
proto_descriptor->add_field();
field_descriptor->set_name(field.name());
field_descriptor->set_number(static_cast<int32_t>(field.number()));
- // We do not support required fields. They will show up as optional
- // after serialization.
+ // We do not support required fields. They will show up as
+ // optional after serialization.
field_descriptor->set_label(
field.is_repeated()
? protos::pbzero::FieldDescriptorProto::LABEL_REPEATED
@@ -329,6 +441,7 @@
uint32_t number,
uint32_t type,
std::string raw_type_name,
+ std::vector<uint8_t> options,
bool is_repeated,
bool is_packed,
bool is_extension)
@@ -336,6 +449,7 @@
number_(number),
type_(type),
raw_type_name_(std::move(raw_type_name)),
+ options_(std::move(options)),
is_repeated_(is_repeated),
is_packed_(is_packed),
is_extension_(is_extension) {}
diff --git a/src/trace_processor/util/descriptors.h b/src/trace_processor/util/descriptors.h
index f876a09..c99a72b 100644
--- a/src/trace_processor/util/descriptors.h
+++ b/src/trace_processor/util/descriptors.h
@@ -18,6 +18,7 @@
#define SRC_TRACE_PROCESSOR_UTIL_DESCRIPTORS_H_
#include <algorithm>
+#include <cstdint>
#include <optional>
#include <set>
#include <string>
@@ -40,6 +41,7 @@
uint32_t number,
uint32_t type,
std::string raw_type_name,
+ std::vector<uint8_t>,
bool is_repeated,
bool is_packed,
bool is_extension = false);
@@ -53,6 +55,9 @@
bool is_packed() const { return is_packed_; }
bool is_extension() const { return is_extension_; }
+ const std::vector<uint8_t>& options() const { return options_; }
+ std::vector<uint8_t>* mutable_options() { return &options_; }
+
void set_resolved_type_name(const std::string& resolved_type_name) {
resolved_type_name_ = resolved_type_name;
}
@@ -63,15 +68,12 @@
uint32_t type_;
std::string raw_type_name_;
std::string resolved_type_name_;
+ std::vector<uint8_t> options_;
bool is_repeated_;
bool is_packed_;
bool is_extension_;
};
-FieldDescriptor CreateFieldFromDecoder(
- const protos::pbzero::FieldDescriptorProto::Decoder& f_decoder,
- bool is_extension);
-
class ProtoDescriptor {
public:
enum class Type { kEnum = 0, kMessage = 1 };
@@ -202,6 +204,10 @@
std::optional<uint32_t> ResolveShortType(const std::string& parent_path,
const std::string& short_type);
+ base::Status ResolveUninterpretedOption(const ProtoDescriptor&,
+ const FieldDescriptor&,
+ std::vector<uint8_t>&);
+
// Adds a new descriptor to the pool and returns its index. There must not be
// already a descriptor with the same full_name in the pool.
uint32_t AddProtoDescriptor(ProtoDescriptor descriptor);
diff --git a/src/trace_processor/util/proto_to_args_parser.cc b/src/trace_processor/util/proto_to_args_parser.cc
index 633eb59..e09359c 100644
--- a/src/trace_processor/util/proto_to_args_parser.cc
+++ b/src/trace_processor/util/proto_to_args_parser.cc
@@ -54,7 +54,7 @@
old_key_length_(key.key.length()) {}
ProtoToArgsParser::ScopedNestedKeyContext::ScopedNestedKeyContext(
- ProtoToArgsParser::ScopedNestedKeyContext&& other)
+ ProtoToArgsParser::ScopedNestedKeyContext&& other) noexcept
: key_(other.key_),
old_flat_key_length_(other.old_flat_key_length_),
old_key_length_(other.old_key_length_) {
@@ -366,14 +366,14 @@
ProtoToArgsParser::ScopedNestedKeyContext ProtoToArgsParser::EnterArray(
size_t index) {
- auto context = ScopedNestedKeyContext(key_prefix_);
+ ScopedNestedKeyContext context(key_prefix_);
key_prefix_.key += "[" + std::to_string(index) + "]";
return context;
}
ProtoToArgsParser::ScopedNestedKeyContext ProtoToArgsParser::EnterDictionary(
const std::string& name) {
- auto context = ScopedNestedKeyContext(key_prefix_);
+ ScopedNestedKeyContext context(key_prefix_);
AppendProtoType(key_prefix_.key, name);
AppendProtoType(key_prefix_.flat_key, name);
return context;
diff --git a/src/trace_processor/util/proto_to_args_parser.h b/src/trace_processor/util/proto_to_args_parser.h
index e797efd..b709a40 100644
--- a/src/trace_processor/util/proto_to_args_parser.h
+++ b/src/trace_processor/util/proto_to_args_parser.h
@@ -67,7 +67,7 @@
struct Key {
Key(const std::string& flat_key, const std::string& key);
- Key(const std::string& key);
+ explicit Key(const std::string& key);
Key();
~Key();
@@ -154,7 +154,7 @@
struct ScopedNestedKeyContext {
public:
~ScopedNestedKeyContext();
- ScopedNestedKeyContext(ScopedNestedKeyContext&&);
+ ScopedNestedKeyContext(ScopedNestedKeyContext&&) noexcept;
ScopedNestedKeyContext(const ScopedNestedKeyContext&) = delete;
ScopedNestedKeyContext& operator=(const ScopedNestedKeyContext&) = delete;
@@ -167,7 +167,7 @@
private:
friend class ProtoToArgsParser;
- ScopedNestedKeyContext(Key& old_value);
+ explicit ScopedNestedKeyContext(Key& old_value);
struct ScopedStringAppender;
diff --git a/src/trace_processor/util/proto_to_json.cc b/src/trace_processor/util/proto_to_json.cc
deleted file mode 100644
index 700357f..0000000
--- a/src/trace_processor/util/proto_to_json.cc
+++ /dev/null
@@ -1,325 +0,0 @@
-/*
- * Copyright (C) 2018 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.
- */
-
-#include <vector>
-
-#include <google/protobuf/descriptor.h>
-#include <google/protobuf/descriptor.pb.h>
-#include <google/protobuf/message.h>
-
-#include "perfetto/base/logging.h"
-#include "perfetto/ext/base/string_utils.h"
-#include "src/trace_processor/util/proto_to_json.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace proto_to_json {
-
-namespace {
-
-std::string QuoteAndEscapeJsonString(const std::string& raw) {
- std::string ret;
- for (auto it = raw.cbegin(); it != raw.cend(); it++) {
- char c = *it;
- switch (c) {
- case '"':
- // Double quote needs to be escaped.
- ret += "\\\"";
- break;
- case '\n':
- // Escape new line specially because it appears often and so is worth
- // treating specially.
- ret += "\\n";
- break;
- default:
- if (c < 0x20) {
- // All 32 ASCII control codes need to be escaped. Instead of using the
- // short forms, we just always use \u escape sequences instead to make
- // things simpler.
- ret += "\\u00";
-
- // Print |c| as a hex character. We reserve 3 bytes of space: 2 for
- // the hex code and one for the null terminator.
- base::StackString<3> buf("%02X", c);
- ret += buf.c_str();
- } else {
- // Everything else can be passed through directly.
- ret += c;
- }
- break;
- }
- }
- return '"' + ret + '"';
-}
-
-std::string FieldToJson(const google::protobuf::Message& message,
- const google::protobuf::FieldDescriptor* field_desc,
- int idx,
- uint32_t indent) {
- using google::protobuf::FieldDescriptor;
-
- const google::protobuf::Reflection* ref = message.GetReflection();
- bool is_repeated = field_desc->is_repeated();
- switch (field_desc->cpp_type()) {
- case FieldDescriptor::CppType::CPPTYPE_BOOL:
- return std::to_string(is_repeated
- ? ref->GetRepeatedBool(message, field_desc, idx)
- : ref->GetBool(message, field_desc));
- case FieldDescriptor::CppType::CPPTYPE_ENUM:
- return QuoteAndEscapeJsonString(
- is_repeated ? ref->GetRepeatedEnum(message, field_desc, idx)->name()
- : ref->GetEnum(message, field_desc)->name());
- case FieldDescriptor::CppType::CPPTYPE_FLOAT:
- return std::to_string(
- is_repeated
- ? static_cast<double>(
- ref->GetRepeatedFloat(message, field_desc, idx))
- : static_cast<double>(ref->GetFloat(message, field_desc)));
- case FieldDescriptor::CppType::CPPTYPE_INT32:
- return std::to_string(
- is_repeated ? ref->GetRepeatedInt32(message, field_desc, idx)
- : ref->GetInt32(message, field_desc));
- case FieldDescriptor::CppType::CPPTYPE_INT64:
- return std::to_string(
- is_repeated ? ref->GetRepeatedInt64(message, field_desc, idx)
- : ref->GetInt64(message, field_desc));
- case FieldDescriptor::CppType::CPPTYPE_DOUBLE:
- return std::to_string(
- is_repeated ? ref->GetRepeatedDouble(message, field_desc, idx)
- : ref->GetDouble(message, field_desc));
- case FieldDescriptor::CppType::CPPTYPE_STRING:
- return QuoteAndEscapeJsonString(
- is_repeated ? ref->GetRepeatedString(message, field_desc, idx)
- : ref->GetString(message, field_desc));
- case FieldDescriptor::CppType::CPPTYPE_UINT32:
- return std::to_string(
- is_repeated ? ref->GetRepeatedUInt32(message, field_desc, idx)
- : ref->GetUInt32(message, field_desc));
- case FieldDescriptor::CppType::CPPTYPE_UINT64:
- return std::to_string(
- is_repeated ? ref->GetRepeatedInt64(message, field_desc, idx)
- : ref->GetInt64(message, field_desc));
- case FieldDescriptor::CppType::CPPTYPE_MESSAGE:
- return MessageToJson(
- is_repeated ? ref->GetRepeatedMessage(message, field_desc, idx)
- : ref->GetMessage(message, field_desc),
- indent);
- }
- PERFETTO_FATAL("For GCC");
-}
-
-std::string RepeatedFieldValuesToJson(
- const google::protobuf::Message& message,
- const google::protobuf::FieldDescriptor* field_desc,
- uint32_t indent) {
- const google::protobuf::Reflection* ref = message.GetReflection();
- std::string ret;
- for (int i = 0; i < ref->FieldSize(message, field_desc); ++i) {
- if (i != 0) {
- ret += ",";
- }
- ret += "\n" + std::string(indent, ' ') +
- FieldToJson(message, field_desc, i, indent);
- }
- return ret;
-}
-
-std::string MessageFieldsToJson(const google::protobuf::Message& message,
- uint32_t indent) {
- const google::protobuf::Reflection* ref = message.GetReflection();
- std::vector<const google::protobuf::FieldDescriptor*> field_descs;
- ref->ListFields(message, &field_descs);
-
- std::string ret;
- uint32_t next_field_idx = 0;
- for (const google::protobuf::FieldDescriptor* field_desc : field_descs) {
- if (next_field_idx++ != 0) {
- ret += ",";
- }
- std::string value;
- if (field_desc->is_repeated()) {
- value = "[" + RepeatedFieldValuesToJson(message, field_desc, indent + 2) +
- "\n" + std::string(indent, ' ') + "]";
- } else {
- value = FieldToJson(message, field_desc, 0, indent);
- }
- const std::string& name = field_desc->is_extension()
- ? field_desc->full_name()
- : field_desc->name();
- ret += "\n" + std::string(indent, ' ') + "\"" + name + "\": " + value;
- }
- return ret;
-}
-
-// This is a class helps avoid the situation where every function has to take
-// field_options_prototype as an argument, which becomes distracting.
-class OptionsConverter {
- public:
- explicit OptionsConverter(
- const google::protobuf::Message* field_options_prototype)
- : field_options_prototype_(field_options_prototype) {}
-
- // Prints all field options for non-empty fields of a message. Example:
- // --- Message definitions ---
- // FooMessage {
- // repeated int64 foo = 1 [op1 = val1, op2 = val2];
- // optional BarMessage bar = 2 [op3 = val3];
- // }
- //
- // BarMessage {
- // optional int64 baz = 1 [op4 = val4];
- // }
- // --- MessageInstance ---
- // foo_msg = { // (As JSON)
- // foo: [23, 24, 25],
- // bar: {
- // baz: 42
- // }
- // }
- // --- Output of MessageFieldOptionsToJson(foo_msg) ---
- // foo: {
- // __field_options: {
- // op1: val1,
- // op2: val2,
- // },
- // __repeated: true
- // }
- // bar: {
- // __field_options: {
- // op3 = val3,
- // },
- // baz: {
- // __field_options: {
- // op4 = val4
- // },
- // }
- // }
- // --- Notes ---
- // This function does not produce the surrounding braces for easier use in
- // recursive use cases. The caller needs to surround the output with braces.
- std::string MessageFieldOptionsToJson(
- const google::protobuf::Message& message,
- uint32_t indent) {
- using google::protobuf::FieldDescriptor;
- std::vector<const FieldDescriptor*> field_descs;
- message.GetReflection()->ListFields(message, &field_descs);
- std::vector<std::string> field_outputs;
- for (auto* field_desc : field_descs) {
- std::vector<std::string> field_entries;
- if (HasFieldOptions(field_desc)) {
- std::string options_entry;
- options_entry +=
- std::string(indent + 2, ' ') + R"("__field_options": )";
- options_entry += FieldOptionsToJson(field_desc, indent + 4);
- field_entries.push_back(std::move(options_entry));
- }
- std::string nested_fields =
- NestedMessageFieldOptionsToJson(message, field_desc, indent + 2);
- if (!nested_fields.empty()) {
- field_entries.push_back(std::move(nested_fields));
- }
- // We don't output annotations for a field if that field and all its
- // descendants have no field options.
- if (!field_entries.empty()) {
- if (field_desc->is_repeated()) {
- field_entries.push_back(std::string(indent, ' ') +
- R"("__repeated": true)");
- }
- std::string field_output;
- const std::string& name = field_desc->is_extension()
- ? field_desc->full_name()
- : field_desc->name();
- field_output += std::string(indent, ' ') + "\"" + name + "\": {\n";
- field_output += base::Join(field_entries, ",\n") + "\n";
- field_output += std::string(indent, ' ') + "}";
- field_outputs.push_back(std::move(field_output));
- }
- }
- return base::Join(field_outputs, ",\n");
- }
-
- private:
- static bool HasFieldOptions(
- const google::protobuf::FieldDescriptor* field_desc) {
- return field_desc->options().ByteSizeLong() > 0;
- }
-
- std::string NestedMessageFieldOptionsToJson(
- const google::protobuf::Message& message,
- const google::protobuf::FieldDescriptor* field_desc,
- uint32_t indent) {
- using google::protobuf::FieldDescriptor;
- if (field_desc->cpp_type() != FieldDescriptor::CppType::CPPTYPE_MESSAGE)
- return "";
- const auto* reflection = message.GetReflection();
- const google::protobuf::Message& nested_message =
- field_desc->is_repeated()
- ? reflection->GetRepeatedMessage(message, field_desc, 0)
- : reflection->GetMessage(message, field_desc);
- return MessageFieldOptionsToJson(nested_message, indent);
- }
-
- std::string FieldOptionsToJson(
- const google::protobuf::FieldDescriptor* field_desc,
- uint32_t indent) {
- PERFETTO_DCHECK(HasFieldOptions(field_desc));
- std::unique_ptr<google::protobuf::Message> options(
- field_options_prototype_->New());
- // Field option extensions are compiled at runtime as opposed to being
- // compiled in and being part of the generated pool, so the field option
- // must be re-parsed as a dynamic message for the extensions to show up. If
- // we do not do this, the extension fields remain "unknown fields" to the
- // reflection API.
- options->ParseFromString(field_desc->options().SerializeAsString());
- return MessageToJson(*options, indent);
- }
-
- const google::protobuf::Message* field_options_prototype_;
-};
-
-} // namespace
-
-std::string MessageToJson(const google::protobuf::Message& message,
- uint32_t indent) {
- return "{" + MessageFieldsToJson(message, indent + 2) + '\n' +
- std::string(indent, ' ') + "}";
-}
-
-std::string MessageToJsonWithAnnotations(
- const google::protobuf::Message& message,
- const google::protobuf::Message* field_options_prototype,
- uint32_t indent) {
- std::string ret;
- OptionsConverter options_converter(field_options_prototype);
-
- ret = "{" + MessageFieldsToJson(message, indent + 2);
- std::string annotation_fields =
- options_converter.MessageFieldOptionsToJson(message, indent + 4);
- if (annotation_fields.empty()) {
- ret += "\n";
- } else {
- ret += ",\n";
- ret += std::string(indent + 2, ' ') + "\"__annotations\": {\n";
- ret += annotation_fields + "\n";
- ret += std::string(indent + 2, ' ') + "}\n";
- }
- ret += std::string(indent, ' ') + "}\n";
- return ret;
-}
-
-} // namespace proto_to_json
-} // namespace trace_processor
-} // namespace perfetto
diff --git a/src/trace_processor/util/proto_to_json.h b/src/trace_processor/util/proto_to_json.h
deleted file mode 100644
index cfe7a11..0000000
--- a/src/trace_processor/util/proto_to_json.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2019 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.
- */
-
-#ifndef SRC_TRACE_PROCESSOR_UTIL_PROTO_TO_JSON_H_
-#define SRC_TRACE_PROCESSOR_UTIL_PROTO_TO_JSON_H_
-
-#include <google/protobuf/message.h>
-
-namespace perfetto {
-namespace trace_processor {
-namespace proto_to_json {
-
-std::string MessageToJson(const google::protobuf::Message& message,
- uint32_t indent = 0);
-
-std::string MessageToJsonWithAnnotations(
- const google::protobuf::Message& message,
- const google::protobuf::Message* field_options_prototype,
- uint32_t indent = 0);
-
-} // namespace proto_to_json
-} // namespace trace_processor
-} // namespace perfetto
-
-#endif // SRC_TRACE_PROCESSOR_UTIL_PROTO_TO_JSON_H_
diff --git a/src/trace_processor/util/protozero_to_json.cc b/src/trace_processor/util/protozero_to_json.cc
index 7c52ad0..fa08f21 100644
--- a/src/trace_processor/util/protozero_to_json.cc
+++ b/src/trace_processor/util/protozero_to_json.cc
@@ -18,9 +18,12 @@
#include <optional>
#include <unordered_set>
+#include <utility>
+#include <vector>
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/ext/base/string_view.h"
+#include "perfetto/protozero/field.h"
#include "perfetto/protozero/proto_decoder.h"
#include "perfetto/protozero/proto_utils.h"
#include "protos/perfetto/common/descriptor.pbzero.h"
@@ -38,11 +41,13 @@
class JsonBuilder {
public:
- JsonBuilder(int flags) : flags_(flags) {}
+ explicit JsonBuilder(int flags) : flags_(flags) {}
void OpenObject() {
- if (is_array_scope() && !is_empty_scope()) {
- Append(",");
+ if (is_array_scope()) {
+ if (!is_empty_scope()) {
+ Append(",");
+ }
MaybeAppendNewline();
MaybeAppendIndent();
}
@@ -51,12 +56,6 @@
}
void CloseObject() {
- // If we're closing the root object add errors if requested:
- if (is_root_scope() && is_inline_errors() && !errors_.empty()) {
- Key("__error");
- StringValue(base::StringView(base::Join(errors_, "\n")));
- }
-
bool needs_newline = !is_empty_scope();
stack_.pop_back();
if (needs_newline) {
@@ -115,9 +114,13 @@
std::string ToString() { return base::Join(parts_, ""); }
- bool is_pretty() { return flags_ & Flags::kPretty; }
+ bool is_empty_scope() { return !stack_.empty() && stack_.back().is_empty; }
- bool is_inline_errors() { return flags_ & Flags::kInlineErrors; }
+ bool is_pretty() const { return flags_ & Flags::kPretty; }
+
+ bool is_inline_errors() const { return flags_ & Flags::kInlineErrors; }
+
+ const std::vector<std::string>& errors() const { return errors_; }
private:
enum class ScopeContext {
@@ -136,19 +139,15 @@
std::vector<std::string> errors_;
bool is_object_scope() {
- return stack_.size() > 0 && stack_.back().ctx == ScopeContext::kObject;
+ return !stack_.empty() && stack_.back().ctx == ScopeContext::kObject;
}
bool is_array_scope() {
- return stack_.size() > 0 && stack_.back().ctx == ScopeContext::kArray;
+ return !stack_.empty() && stack_.back().ctx == ScopeContext::kArray;
}
- bool is_empty_scope() { return stack_.size() > 0 && stack_.back().is_empty; }
-
- bool is_root_scope() { return stack_.size() == 1; }
-
void MarkScopeAsNonEmpty() {
- if (stack_.size() > 0) {
+ if (!stack_.empty()) {
stack_.back().is_empty = false;
}
}
@@ -204,7 +203,7 @@
result += R"(\b)";
break;
case '\f':
- result += R"(\b)";
+ result += R"(\f)";
break;
case '\r':
result += R"(\r)";
@@ -274,6 +273,15 @@
}
};
+bool HasFieldOptions(const FieldDescriptor& field_desc) {
+ return !field_desc.options().empty();
+}
+
+std::string FulllyQualifiedFieldName(const ProtoDescriptor& desc,
+ const FieldDescriptor& field_desc) {
+ return desc.package_name().substr(1) + "." + field_desc.name();
+}
+
bool IsTypeMatch(ProtoWireType wire, uint32_t type) {
switch (wire) {
case ProtoWireType::kVarInt:
@@ -366,6 +374,7 @@
void MessageField(const DescriptorPool& pool,
const std::string& type,
protozero::ConstBytes protobytes,
+ bool fully_qualify_extensions,
JsonBuilder* out);
void EnumField(const DescriptorPool& pool,
const FieldDescriptor& fd,
@@ -418,6 +427,7 @@
void LengthField(const DescriptorPool& pool,
const FieldDescriptor* fd,
const protozero::Field& field,
+ bool fully_qualify_extensions,
JsonBuilder* out) {
uint32_t type = fd ? fd->type() : 0;
switch (type) {
@@ -428,7 +438,8 @@
out->StringValue(field.as_string());
return;
case FieldDescriptorProto::TYPE_MESSAGE:
- MessageField(pool, fd->resolved_type_name(), field.as_bytes(), out);
+ MessageField(pool, fd->resolved_type_name(), field.as_bytes(),
+ fully_qualify_extensions, out);
return;
case FieldDescriptorProto::TYPE_DOUBLE:
PackedField<ProtoWireType::kFixed64, double>(pool, *fd, field, out);
@@ -600,13 +611,14 @@
protozero::ConstBytes protobytes,
const FieldDescriptor* fd,
uint32_t id,
+ bool fully_qualify_extensions,
JsonBuilder* out) {
out->OpenArray();
protozero::ProtoDecoder decoder(protobytes.data, protobytes.size);
for (auto field = decoder.ReadField(); field.valid();
field = decoder.ReadField()) {
if (field.id() == id) {
- LengthField(pool, fd, field, out);
+ LengthField(pool, fd, field, fully_qualify_extensions, out);
}
}
out->CloseArray();
@@ -642,12 +654,11 @@
out->CloseArray();
}
-void MessageField(const DescriptorPool& pool,
- const std::string& type,
- protozero::ConstBytes protobytes,
- JsonBuilder* out) {
- out->OpenObject();
-
+void InnerMessageField(const DescriptorPool& pool,
+ const std::string& type,
+ protozero::ConstBytes protobytes,
+ bool fully_qualify_extensions,
+ JsonBuilder* out) {
std::optional<uint32_t> opt_proto_desc_idx = pool.FindDescriptorIdx(type);
const ProtoDescriptor* opt_proto_descriptor =
opt_proto_desc_idx ? &pool.descriptors()[*opt_proto_desc_idx] : nullptr;
@@ -668,7 +679,12 @@
if (fields_seen.count(field.id())) {
continue;
}
- out->Key(opt_field_descriptor->name());
+ if (opt_field_descriptor->is_extension() && fully_qualify_extensions) {
+ out->Key(FulllyQualifiedFieldName(*opt_proto_descriptor,
+ *opt_field_descriptor));
+ } else {
+ out->Key(opt_field_descriptor->name());
+ }
} else {
out->Key(std::to_string(field.id()));
}
@@ -686,10 +702,11 @@
// wire_type = length + field_type in
// {u,s,}int{32,64}, float, double etc means this is the
// packed case:
- LengthField(pool, opt_field_descriptor, field, out);
+ LengthField(pool, opt_field_descriptor, field,
+ fully_qualify_extensions, out);
} else {
RepeatedLengthField(pool, protobytes, opt_field_descriptor,
- field.id(), out);
+ field.id(), fully_qualify_extensions, out);
}
break;
case ProtoWireType::kFixed32:
@@ -705,7 +722,8 @@
VarIntField(pool, opt_field_descriptor, field, out);
break;
case ProtoWireType::kLengthDelimited:
- LengthField(pool, opt_field_descriptor, field, out);
+ LengthField(pool, opt_field_descriptor, field,
+ fully_qualify_extensions, out);
break;
case ProtoWireType::kFixed32:
Fixed32Field(opt_field_descriptor, field, out);
@@ -720,10 +738,130 @@
if (decoder.bytes_left() != 0) {
out->AddError(std::to_string(decoder.bytes_left()) + " extra bytes");
}
+}
+void MessageField(const DescriptorPool& pool,
+ const std::string& type,
+ protozero::ConstBytes protobytes,
+ bool fully_qualify_extensions,
+ JsonBuilder* out) {
+ out->OpenObject();
+ InnerMessageField(pool, type, protobytes, fully_qualify_extensions, out);
out->CloseObject();
}
+// Prints all field options for non-empty fields of a message. Example:
+// --- Message definitions ---
+// FooMessage {
+// repeated int64 foo = 1 [op1 = val1, op2 = val2];
+// optional BarMessage bar = 2 [op3 = val3];
+// }
+//
+// BarMessage {
+// optional int64 baz = 1 [op4 = val4];
+// }
+// --- MessageInstance ---
+// foo_msg = { // (As JSON)
+// foo: [23, 24, 25],
+// bar: {
+// baz: 42
+// }
+// }
+// --- Output of MessageFieldOptionsToJson(foo_msg) ---
+// foo: {
+// __field_options: {
+// op1: val1,
+// op2: val2,
+// },
+// __repeated: true
+// }
+// bar: {
+// __field_options: {
+// op3 = val3,
+// },
+// baz: {
+// __field_options: {
+// op4 = val4
+// },
+// }
+// }
+void MessageFieldOptionsToJson(
+ const DescriptorPool& pool,
+ const std::string& type,
+ const std::string& field_prefix,
+ const std::unordered_set<std::string>& allowed_fields,
+ JsonBuilder* out) {
+ std::optional<uint32_t> opt_proto_desc_idx = pool.FindDescriptorIdx(type);
+ if (!opt_proto_desc_idx) {
+ return;
+ }
+ const ProtoDescriptor& desc = pool.descriptors()[*opt_proto_desc_idx];
+ for (const auto& id_and_field : desc.fields()) {
+ const FieldDescriptor& field_desc = id_and_field.second;
+ std::string full_field_name = field_prefix + field_desc.name();
+ if (allowed_fields.find(full_field_name) == allowed_fields.end()) {
+ continue;
+ }
+ if (field_desc.is_extension()) {
+ out->Key(FulllyQualifiedFieldName(desc, field_desc));
+ } else {
+ out->Key(field_desc.name());
+ }
+ out->OpenObject();
+ if (HasFieldOptions(field_desc)) {
+ out->Key("__field_options");
+ MessageField(pool, ".google.protobuf.FieldOptions",
+ protozero::ConstBytes{field_desc.options().data(),
+ field_desc.options().size()},
+ false, out);
+ }
+ if (field_desc.type() == FieldDescriptorProto::Type::TYPE_MESSAGE) {
+ MessageFieldOptionsToJson(pool, field_desc.resolved_type_name(),
+ full_field_name + ".", allowed_fields, out);
+ }
+ if (field_desc.is_repeated()) {
+ out->Key("__repeated");
+ out->BoolValue(true);
+ }
+ out->CloseObject();
+ }
+}
+
+bool PopulateAllowedFieldOptionsSet(
+ const DescriptorPool& pool,
+ const std::string& type,
+ const std::string& field_prefix,
+ protozero::ConstBytes protobytes,
+ std::unordered_set<std::string>& allowed_fields) {
+ std::optional<uint32_t> opt_proto_desc_idx = pool.FindDescriptorIdx(type);
+ if (!opt_proto_desc_idx) {
+ return false;
+ }
+ const ProtoDescriptor& desc = pool.descriptors()[*opt_proto_desc_idx];
+ protozero::ProtoDecoder decoder(protobytes);
+ bool allowed = false;
+ for (auto field = decoder.ReadField(); field.valid();
+ field = decoder.ReadField()) {
+ auto* opt_field_descriptor = desc.FindFieldByTag(field.id());
+ if (!opt_field_descriptor) {
+ continue;
+ }
+ std::string full_field_name = field_prefix + opt_field_descriptor->name();
+ bool nested = false;
+ if (opt_field_descriptor->type() ==
+ protos::pbzero::FieldDescriptorProto::TYPE_MESSAGE) {
+ nested = PopulateAllowedFieldOptionsSet(
+ pool, opt_field_descriptor->resolved_type_name(),
+ full_field_name + ".", field.as_bytes(), allowed_fields);
+ }
+ if (nested || HasFieldOptions(*opt_field_descriptor)) {
+ allowed_fields.emplace(full_field_name);
+ allowed = true;
+ }
+ }
+ return allowed;
+}
+
} // namespace
std::string ProtozeroToJson(const DescriptorPool& pool,
@@ -731,7 +869,23 @@
protozero::ConstBytes protobytes,
int flags) {
JsonBuilder builder(flags);
- MessageField(pool, type, protobytes, &builder);
+ builder.OpenObject();
+ InnerMessageField(pool, type, protobytes, true, &builder);
+ if (builder.is_inline_errors() && !builder.errors().empty()) {
+ builder.Key("__error");
+ builder.StringValue(base::StringView(base::Join(builder.errors(), "\n")));
+ }
+ if (flags & kInlineAnnotations) {
+ std::unordered_set<std::string> allowed_fields;
+ PopulateAllowedFieldOptionsSet(pool, type, "", protobytes, allowed_fields);
+ if (!allowed_fields.empty()) {
+ builder.Key("__annotations");
+ builder.OpenObject();
+ MessageFieldOptionsToJson(pool, type, "", allowed_fields, &builder);
+ builder.CloseObject();
+ }
+ }
+ builder.CloseObject();
return builder.ToString();
}
diff --git a/src/trace_processor/util/protozero_to_json.h b/src/trace_processor/util/protozero_to_json.h
index c571cb1..afc8d9d 100644
--- a/src/trace_processor/util/protozero_to_json.h
+++ b/src/trace_processor/util/protozero_to_json.h
@@ -32,16 +32,29 @@
kNone = 0,
// Produce nice json (newlines, 1 space post :, 2 space indents)
- kPretty = 1,
+ kPretty = 1 << 0,
// Report errors as an extra key on the root json object. For example
// the output with this flag might look like:
// {
// "foo": { ... },
// "baz": { ... },
- // __error: "Failed to decode key 'bar' due to <some error>"
+ // "__error": "Failed to decode key 'bar' due to <some error>"
// }
- kInlineErrors = 2,
+ kInlineErrors = 1 << 1,
+
+ // Report annotations as an extra key on the root json object. For example
+ // the output with this flag might look like:
+ // {
+ // "foo": { ... },
+ // "baz": { ... },
+ // "__annotations": {
+ // "foo": {
+ // "__field_options": { "unit": "ms_smallerIsBetter" }
+ // }
+ // }
+ // }
+ kInlineAnnotations = 1 << 2,
};
// Given a protozero message |protobytes| which is of fully qualified name
diff --git a/src/trace_processor/util/protozero_to_json_unittests.cc b/src/trace_processor/util/protozero_to_json_unittests.cc
index 3dcdb24..b8e01a7 100644
--- a/src/trace_processor/util/protozero_to_json_unittests.cc
+++ b/src/trace_processor/util/protozero_to_json_unittests.cc
@@ -27,6 +27,11 @@
#include "protos/perfetto/trace/track_event/chrome_compositor_scheduler_state.pbzero.h"
#include "protos/perfetto/trace/track_event/track_event.pbzero.h"
+#if PERFETTO_BUILDFLAG(PERFETTO_STANDALONE_BUILD)
+#include "protos/perfetto/metrics/chrome/all_chrome_metrics.pb.h" // nogncheck
+#include "src/trace_processor/metrics/all_chrome_metrics.descriptor.h" // nogncheck
+#endif
+
namespace perfetto {
namespace trace_processor {
namespace protozero_to_json {
@@ -117,6 +122,38 @@
kNone));
}
+// This test depends on the CustomOptions message in descriptor.proto which
+// is very tricky to point to on the non-standalone build.
+#if PERFETTO_BUILDFLAG(PERFETTO_STANDALONE_BUILD)
+TEST(ProtozeroToJsonTest, CustomDescriptorPoolAnnotations) {
+ using perfetto::protos::TestChromeMetric;
+ TestChromeMetric msg;
+ msg.set_test_value(1);
+ auto binary_proto = msg.SerializeAsString();
+ protozero::ConstBytes binary_proto_bytes{
+ reinterpret_cast<const uint8_t*>(binary_proto.data()),
+ binary_proto.size()};
+
+ DescriptorPool pool;
+ auto status = pool.AddFromFileDescriptorSet(
+ kAllChromeMetricsDescriptor.data(), kAllChromeMetricsDescriptor.size());
+ ASSERT_TRUE(status.ok());
+
+ EXPECT_EQ(R"({
+ "test_value": 1,
+ "__annotations": {
+ "test_value": {
+ "__field_options": {
+ "unit": "count_smallerIsBetter"
+ }
+ }
+ }
+})",
+ ProtozeroToJson(pool, ".perfetto.protos.TestChromeMetric",
+ binary_proto_bytes, kPretty | kInlineAnnotations));
+}
+#endif
+
// Sets up a descriptor pool with all the messages from
// "src/protozero/test/example_proto/test_messages.proto"
class ProtozeroToJsonTestMessageTest : public testing::Test {
diff --git a/ui/src/chrome_extension/chrome_tracing_controller.ts b/ui/src/chrome_extension/chrome_tracing_controller.ts
index 9ed3930..a40d22f 100644
--- a/ui/src/chrome_extension/chrome_tracing_controller.ts
+++ b/ui/src/chrome_extension/chrome_tracing_controller.ts
@@ -23,13 +23,12 @@
ReadBuffersResponse,
} from '../controller/consumer_port_types';
import {RpcConsumerPort} from '../controller/record_controller_interfaces';
-import {TraceConfig} from '../core/protos';
+import {ITraceStats, TraceConfig} from '../core/protos';
import {
browserSupportsPerfettoConfig,
extractTraceConfig,
hasSystemDataSourceConfig,
} from '../core/trace_config_utils';
-import {perfetto} from '../gen/protos';
import {DevToolsSocket} from './devtools_socket';
@@ -201,7 +200,7 @@
if (this.lastBufferUsageEvent && this.lastBufferUsageEvent.percentFull) {
percentFull = this.lastBufferUsageEvent.percentFull;
}
- const stats: perfetto.protos.ITraceStats = {
+ const stats: ITraceStats = {
bufferStats:
[{bufferSize: 1000, bytesWritten: Math.round(percentFull * 1000)}],
};
diff --git a/ui/src/common/engine.ts b/ui/src/common/engine.ts
index fe05e04..43c5f16 100644
--- a/ui/src/common/engine.ts
+++ b/ui/src/common/engine.ts
@@ -20,10 +20,14 @@
ComputeMetricArgs,
ComputeMetricResult,
DisableAndReadMetatraceResult,
+ EnableMetatraceArgs,
+ MetatraceCategories,
QueryArgs,
+ QueryResult as ProtoQueryResult,
ResetTraceProcessorArgs,
+ TraceProcessorRpc,
+ TraceProcessorRpcStream,
} from '../core/protos';
-import {perfetto} from '../gen/protos';
import {ProtoRingBuffer} from './proto_ring_buffer';
import {
@@ -37,9 +41,7 @@
WritableQueryResult,
} from './query_result';
-import TraceProcessorRpc = perfetto.protos.TraceProcessorRpc;
-import TraceProcessorRpcStream = perfetto.protos.TraceProcessorRpcStream;
-import TPM = perfetto.protos.TraceProcessorRpc.TraceProcessorMethod;
+import TPM = TraceProcessorRpc.TraceProcessorMethod;
export interface LoadingTracker {
beginLoading(): void;
@@ -133,23 +135,20 @@
// 1. We avoid protobufjs decoding the TraceProcessorRpc.query_result field.
// 2. We stash (a view of) the original buffer into the |rawQueryResult| so
// the `case TPM_QUERY_STREAMING` below can take it.
- perfetto.protos.QueryResult.decode =
- (reader: protobuf.Reader, length: number) => {
- const res =
- perfetto.protos.QueryResult.create() as {} as QueryResultBypass;
- res.rawQueryResult =
- reader.buf.subarray(reader.pos, reader.pos + length);
- // All this works only if protobufjs returns the original ArrayBuffer
- // from |rpcMsgEncoded|. It should be always the case given the
- // current implementation. This check mainly guards against future
- // behavioral changes of protobufjs. We don't want to accidentally
- // hold onto some internal protobufjs buffer. We are fine holding
- // onto |rpcMsgEncoded| because those come from ProtoRingBuffer which
- // is buffer-retention-friendly.
- assertTrue(res.rawQueryResult.buffer === rpcMsgEncoded.buffer);
- reader.pos += length;
- return res as {} as perfetto.protos.QueryResult;
- };
+ ProtoQueryResult.decode = (reader: protobuf.Reader, length: number) => {
+ const res = ProtoQueryResult.create() as {} as QueryResultBypass;
+ res.rawQueryResult = reader.buf.subarray(reader.pos, reader.pos + length);
+ // All this works only if protobufjs returns the original ArrayBuffer
+ // from |rpcMsgEncoded|. It should be always the case given the
+ // current implementation. This check mainly guards against future
+ // behavioral changes of protobufjs. We don't want to accidentally
+ // hold onto some internal protobufjs buffer. We are fine holding
+ // onto |rpcMsgEncoded| because those come from ProtoRingBuffer which
+ // is buffer-retention-friendly.
+ assertTrue(res.rawQueryResult.buffer === rpcMsgEncoded.buffer);
+ reader.pos += length;
+ return res as {} as ProtoQueryResult;
+ };
const rpc = TraceProcessorRpc.decode(rpcMsgEncoded);
@@ -352,11 +351,11 @@
return this._isMetatracingEnabled;
}
- enableMetatrace(categories?: perfetto.protos.MetatraceCategories) {
+ enableMetatrace(categories?: MetatraceCategories) {
const rpc = TraceProcessorRpc.create();
rpc.request = TPM.TPM_ENABLE_METATRACE;
if (categories) {
- rpc.enableMetatraceArgs = new perfetto.protos.EnableMetatraceArgs();
+ rpc.enableMetatraceArgs = new EnableMetatraceArgs();
rpc.enableMetatraceArgs.categories = categories;
}
this._isMetatracingEnabled = true;
diff --git a/ui/src/common/metatracing.ts b/ui/src/common/metatracing.ts
index 99c5ea9..09d080f 100644
--- a/ui/src/common/metatracing.ts
+++ b/ui/src/common/metatracing.ts
@@ -12,8 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {PerfettoMetatrace, Trace, TracePacket} from '../core/protos';
-import {perfetto} from '../gen/protos';
+import {
+ MetatraceCategories,
+ PerfettoMetatrace,
+ Trace,
+ TracePacket,
+} from '../core/protos';
import {featureFlags} from './feature_flags';
@@ -28,8 +32,6 @@
kOmniboxStatus = 3,
}
-import MetatraceCategories = perfetto.protos.MetatraceCategories;
-
const AOMT_FLAG = featureFlags.register({
id: 'alwaysOnMetatracing',
name: 'Enable always-on-metatracing',
diff --git a/ui/src/common/query_result_unittest.ts b/ui/src/common/query_result_unittest.ts
index 50641fe..090eb54 100644
--- a/ui/src/common/query_result_unittest.ts
+++ b/ui/src/common/query_result_unittest.ts
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import protoNamespace from '../gen/protos';
+import {QueryResult as QueryResultProto} from '../core/protos';
import {
createQueryResult,
@@ -23,8 +23,7 @@
STR_NULL,
} from './query_result';
-const T = protoNamespace.perfetto.protos.QueryResult.CellsBatch.CellType;
-const QueryResultProto = protoNamespace.perfetto.protos.QueryResult;
+const T = QueryResultProto.CellsBatch.CellType;
test('QueryResult.SimpleOneRow', () => {
const batch = QueryResultProto.CellsBatch.create({
diff --git a/ui/src/common/recordingV2/recording_config_utils.ts b/ui/src/common/recordingV2/recording_config_utils.ts
index 9524670..dd33e36 100644
--- a/ui/src/common/recordingV2/recording_config_utils.ts
+++ b/ui/src/common/recordingV2/recording_config_utils.ts
@@ -30,19 +30,20 @@
NativeContinuousDumpConfig,
NetworkPacketTraceConfig,
PerfEventConfig,
+ PerfEvents,
ProcessStatsConfig,
SysStatsConfig,
TraceConfig,
TrackEventConfig,
VmstatCounters,
} from '../../core/protos';
-import {perfetto} from '../../gen/protos';
import {TargetInfo} from './recording_interfaces_v2';
-import Timebase = perfetto.protos.PerfEvents.Timebase;
-import CallstackSampling = perfetto.protos.PerfEventConfig.CallstackSampling;
-import Scope = perfetto.protos.PerfEventConfig.Scope;
+import PerfClock = PerfEvents.PerfClock;
+import Timebase = PerfEvents.Timebase;
+import CallstackSampling = PerfEventConfig.CallstackSampling;
+import Scope = PerfEventConfig.Scope;
export interface ConfigProtoEncoded {
configProtoText?: string;
@@ -477,8 +478,7 @@
// TODO: The timestampClock needs to be changed to MONOTONIC once we start
// offering a choice of counter to record on through the recording UI, as
// not all clocks are compatible with hardware counters).
- perfEventConfig.timebase.timestampClock =
- perfetto.protos.PerfEvents.PerfClock.PERF_CLOCK_BOOTTIME;
+ perfEventConfig.timebase.timestampClock = PerfClock.PERF_CLOCK_BOOTTIME;
const callstackSampling = new CallstackSampling();
if (uiCfg.targetCmdLine.length > 0) {
diff --git a/ui/src/controller/adb_record_controller_jsdomtest.ts b/ui/src/controller/adb_record_controller_jsdomtest.ts
index 21b2898..5d506a6 100644
--- a/ui/src/controller/adb_record_controller_jsdomtest.ts
+++ b/ui/src/controller/adb_record_controller_jsdomtest.ts
@@ -15,7 +15,7 @@
import {dingus} from 'dingusjs';
import {utf8Encode} from '../base/string_utils';
-import {perfetto} from '../gen/protos';
+import {EnableTracingRequest, TraceConfig} from '../core/protos';
import {AdbStream, MockAdb, MockAdbStream} from './adb_interfaces';
import {AdbConsumerPort} from './adb_shell_controller';
@@ -33,10 +33,10 @@
const adbController = new AdbConsumerPort(adbMock, mainCallback);
const mockIntArray = new Uint8Array();
-const enableTracingRequest = new perfetto.protos.EnableTracingRequest();
-enableTracingRequest.traceConfig = new perfetto.protos.TraceConfig();
+const enableTracingRequest = new EnableTracingRequest();
+enableTracingRequest.traceConfig = new TraceConfig();
const enableTracingRequestProto =
- perfetto.protos.EnableTracingRequest.encode(enableTracingRequest).finish();
+ EnableTracingRequest.encode(enableTracingRequest).finish();
test('handleCommand', async () => {
diff --git a/ui/src/controller/adb_socket_controller.ts b/ui/src/controller/adb_socket_controller.ts
index 87af792..ac761f0 100644
--- a/ui/src/controller/adb_socket_controller.ts
+++ b/ui/src/controller/adb_socket_controller.ts
@@ -14,7 +14,14 @@
import protobuf from 'protobufjs/minimal';
-import {perfetto} from '../gen/protos';
+import {
+ DisableTracingResponse,
+ EnableTracingResponse,
+ FreeBuffersResponse,
+ GetTraceStatsResponse,
+ IPCFrame,
+ ReadBuffersResponse,
+} from '../core/protos';
import {AdbBaseConsumerPort, AdbConnectionState} from './adb_base_controller';
import {Adb, AdbStream} from './adb_interfaces';
@@ -38,10 +45,9 @@
const TRACE_PACKET_PROTO_TAG =
(TRACE_PACKET_PROTO_ID << 3) | PROTO_LEN_DELIMITED_WIRE_TYPE;
-declare type Frame = perfetto.protos.IPCFrame;
-declare type IMethodInfo =
- perfetto.protos.IPCFrame.BindServiceReply.IMethodInfo;
-declare type ISlice = perfetto.protos.ReadBuffersResponse.ISlice;
+declare type Frame = IPCFrame;
+declare type IMethodInfo = IPCFrame.BindServiceReply.IMethodInfo;
+declare type ISlice = ReadBuffersResponse.ISlice;
interface Command {
method: string;
@@ -115,9 +121,9 @@
console.error(`Method ${method} not supported by the target`);
return;
}
- const frame = new perfetto.protos.IPCFrame({
+ const frame = new IPCFrame({
requestId,
- msgInvokeMethod: new perfetto.protos.IPCFrame.InvokeMethod(
+ msgInvokeMethod: new IPCFrame.InvokeMethod(
{serviceId: this.serviceId, methodId, argsProto}),
});
this.requestMethods.set(requestId, method);
@@ -127,8 +133,7 @@
}
static generateFrameBufferToSend(frame: Frame): Uint8Array {
- const frameProto: Uint8Array =
- perfetto.protos.IPCFrame.encode(frame).finish();
+ const frameProto: Uint8Array = IPCFrame.encode(frame).finish();
const frameLen = frameProto.length;
const buf = new Uint8Array(WIRE_PROTOCOL_HEADER_SIZE + frameLen);
const dv = new DataView(buf.buffer);
@@ -165,7 +170,7 @@
const buf = new ArrayBuffer(frameBuffer.byteLength);
const arr = new Uint8Array(buf);
arr.set(frameBuffer);
- const frame = perfetto.protos.IPCFrame.decode(arr);
+ const frame = IPCFrame.decode(arr);
this.handleIncomingFrame(frame);
}
@@ -297,10 +302,9 @@
bind() {
console.assert(this.socket !== undefined);
const requestId = this.requestId++;
- const frame = new perfetto.protos.IPCFrame({
+ const frame = new IPCFrame({
requestId,
- msgBindService: new perfetto.protos.IPCFrame.BindService(
- {serviceName: 'ConsumerPort'}),
+ msgBindService: new IPCFrame.BindService({serviceName: 'ConsumerPort'}),
});
return new Promise<void>((resolve, _) => {
this.resolveBindingPromise = resolve;
@@ -325,7 +329,7 @@
}
}
- handleIncomingFrame(frame: perfetto.protos.IPCFrame) {
+ handleIncomingFrame(frame: IPCFrame) {
const requestId = frame.requestId;
switch (frame.msg) {
case 'msgBindServiceReply': {
@@ -361,10 +365,9 @@
}
}
-const decoders =
- new Map<string, Function>()
- .set('EnableTracing', perfetto.protos.EnableTracingResponse.decode)
- .set('FreeBuffers', perfetto.protos.FreeBuffersResponse.decode)
- .set('ReadBuffers', perfetto.protos.ReadBuffersResponse.decode)
- .set('DisableTracing', perfetto.protos.DisableTracingResponse.decode)
- .set('GetTraceStats', perfetto.protos.GetTraceStatsResponse.decode);
+const decoders = new Map<string, Function>()
+ .set('EnableTracing', EnableTracingResponse.decode)
+ .set('FreeBuffers', FreeBuffersResponse.decode)
+ .set('ReadBuffers', ReadBuffersResponse.decode)
+ .set('DisableTracing', DisableTracingResponse.decode)
+ .set('GetTraceStats', GetTraceStatsResponse.decode);
diff --git a/ui/src/controller/aggregation/cpu_aggregation_controller.ts b/ui/src/controller/aggregation/cpu_aggregation_controller.ts
index 6ff6ec4..46f0f47 100644
--- a/ui/src/controller/aggregation/cpu_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/cpu_aggregation_controller.ts
@@ -17,6 +17,7 @@
import {pluginManager} from '../../common/plugins';
import {Area, Sorting} from '../../common/state';
import {globals} from '../../frontend/globals';
+import {CPU_SLICE_TRACK_KIND} from '../../tracks/cpu_slices';
import {AggregationController} from './aggregation_controller';
@@ -25,12 +26,12 @@
async createAggregateView(engine: Engine, area: Area) {
await engine.query(`drop view if exists ${this.kind};`);
- const selectedCpus: (string|number)[] = [];
+ const selectedCpus: number[] = [];
for (const trackId of area.tracks) {
const track = globals.state.tracks[trackId];
if (track?.uri) {
const trackInfo = pluginManager.resolveTrackInfo(track.uri);
- if (trackInfo?.tags?.type === 'cpu_sched') {
+ if (trackInfo?.tags?.kind === CPU_SLICE_TRACK_KIND) {
const cpu = trackInfo?.tags?.cpu;
cpu && selectedCpus.push(cpu);
}
diff --git a/ui/src/controller/aggregation/cpu_by_process_aggregation_controller.ts b/ui/src/controller/aggregation/cpu_by_process_aggregation_controller.ts
index 3745da3..aebaf94 100644
--- a/ui/src/controller/aggregation/cpu_by_process_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/cpu_by_process_aggregation_controller.ts
@@ -17,6 +17,7 @@
import {pluginManager} from '../../common/plugins';
import {Area, Sorting} from '../../common/state';
import {globals} from '../../frontend/globals';
+import {CPU_SLICE_TRACK_KIND} from '../../tracks/cpu_slices';
import {AggregationController} from './aggregation_controller';
@@ -24,12 +25,12 @@
async createAggregateView(engine: Engine, area: Area) {
await engine.query(`drop view if exists ${this.kind};`);
- const selectedCpus: string[] = [];
+ const selectedCpus: number[] = [];
for (const trackId of area.tracks) {
const track = globals.state.tracks[trackId];
if (track?.uri) {
const trackInfo = pluginManager.resolveTrackInfo(track.uri);
- if (trackInfo?.tags?.type === 'cpu_sched') {
+ if (trackInfo?.tags?.kind === CPU_SLICE_TRACK_KIND) {
const cpu = trackInfo?.tags?.cpu;
cpu && selectedCpus.push(cpu);
}
diff --git a/ui/src/controller/consumer_port_types.ts b/ui/src/controller/consumer_port_types.ts
index 8b3cd45..c221516 100644
--- a/ui/src/controller/consumer_port_types.ts
+++ b/ui/src/controller/consumer_port_types.ts
@@ -12,7 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {perfetto} from '../gen/protos';
+import {
+ IDisableTracingResponse,
+ IEnableTracingResponse,
+ IFreeBuffersResponse,
+ IGetTraceStatsResponse,
+ IReadBuffersResponse,
+} from '../core/protos';
export interface Typed {
type: string;
@@ -29,17 +35,13 @@
return obj.hasOwnProperty('type');
}
-export interface ReadBuffersResponse extends
- Typed, perfetto.protos.IReadBuffersResponse {}
-export interface EnableTracingResponse extends
- Typed, perfetto.protos.IEnableTracingResponse {}
-export interface GetTraceStatsResponse extends
- Typed, perfetto.protos.IGetTraceStatsResponse {}
-export interface FreeBuffersResponse extends
- Typed, perfetto.protos.IFreeBuffersResponse {}
+export interface ReadBuffersResponse extends Typed, IReadBuffersResponse {}
+export interface EnableTracingResponse extends Typed, IEnableTracingResponse {}
+export interface GetTraceStatsResponse extends Typed, IGetTraceStatsResponse {}
+export interface FreeBuffersResponse extends Typed, IFreeBuffersResponse {}
export interface GetCategoriesResponse extends Typed {}
-export interface DisableTracingResponse extends
- Typed, perfetto.protos.IDisableTracingResponse {}
+export interface DisableTracingResponse extends Typed,
+ IDisableTracingResponse {}
export type ConsumerPortResponse =
EnableTracingResponse|ReadBuffersResponse|GetTraceStatsResponse|
diff --git a/ui/src/controller/search_controller.ts b/ui/src/controller/search_controller.ts
index 1d1de5e..b8ee0d3 100644
--- a/ui/src/controller/search_controller.ts
+++ b/ui/src/controller/search_controller.ts
@@ -21,13 +21,16 @@
Time,
TimeSpan,
} from '../base/time';
+import {exists} from '../base/utils';
import {Engine} from '../common/engine';
+import {pluginManager} from '../common/plugins';
import {LONG, NUM, STR} from '../common/query_result';
import {escapeSearchQuery} from '../common/query_utils';
import {CurrentSearchResults, SearchSummary} from '../common/search_data';
import {OmniboxState} from '../common/state';
import {globals} from '../frontend/globals';
import {publishSearch, publishSearchResult} from '../frontend/publish';
+import {CPU_SLICE_TRACK_KIND} from '../tracks/cpu_slices';
import {Controller} from './controller';
@@ -199,9 +202,12 @@
// easier once the track table has entries for all the tracks.
const cpuToTrackId = new Map();
for (const track of Object.values(globals.state.tracks)) {
- if (track.kind === 'CpuSliceTrack') {
- cpuToTrackId.set((track.config as {cpu: number}).cpu, track.id);
- continue;
+ if (exists(track?.uri)) {
+ const trackInfo = pluginManager.resolveTrackInfo(track.uri);
+ if (trackInfo?.tags?.kind === CPU_SLICE_TRACK_KIND) {
+ const cpu = trackInfo?.tags?.cpu;
+ cpu && cpuToTrackId.set(cpu, track.id);
+ }
}
}
diff --git a/ui/src/core/protos.ts b/ui/src/core/protos.ts
index c3fdaca..0e55a5f 100644
--- a/ui/src/core/protos.ts
+++ b/ui/src/core/protos.ts
@@ -17,67 +17,80 @@
// Aliases protos to avoid the super nested namespaces.
// See https://www.typescriptlang.org/docs/handbook/namespaces.html#aliases
import AndroidLogConfig = protos.perfetto.protos.AndroidLogConfig;
-import AndroidPowerConfig = protos.perfetto.protos.AndroidPowerConfig;
import AndroidLogId = protos.perfetto.protos.AndroidLogId;
+import AndroidPowerConfig = protos.perfetto.protos.AndroidPowerConfig;
import BatteryCounters =
protos.perfetto.protos.AndroidPowerConfig.BatteryCounters;
import BufferConfig = protos.perfetto.protos.TraceConfig.BufferConfig;
import ChromeConfig = protos.perfetto.protos.ChromeConfig;
-import TrackEventConfig = protos.perfetto.protos.TrackEventConfig;
-import ConsumerPort = protos.perfetto.protos.ConsumerPort;
-import NetworkPacketTraceConfig =
- protos.perfetto.protos.NetworkPacketTraceConfig;
-import NativeContinuousDumpConfig =
- protos.perfetto.protos.HeapprofdConfig.ContinuousDumpConfig;
-import JavaContinuousDumpConfig =
- protos.perfetto.protos.JavaHprofConfig.ContinuousDumpConfig;
-import DataSourceConfig = protos.perfetto.protos.DataSourceConfig;
-import DataSourceDescriptor = protos.perfetto.protos.DataSourceDescriptor;
-import FtraceConfig = protos.perfetto.protos.FtraceConfig;
-import HeapprofdConfig = protos.perfetto.protos.HeapprofdConfig;
-import JavaHprofConfig = protos.perfetto.protos.JavaHprofConfig;
-import PerfEventConfig = protos.perfetto.protos.PerfEventConfig;
-import IAndroidPowerConfig = protos.perfetto.protos.IAndroidPowerConfig;
-import IBufferConfig = protos.perfetto.protos.TraceConfig.IBufferConfig;
-import IProcessStatsConfig = protos.perfetto.protos.IProcessStatsConfig;
-import ISysStatsConfig = protos.perfetto.protos.ISysStatsConfig;
-import ITraceConfig = protos.perfetto.protos.ITraceConfig;
-import MeminfoCounters = protos.perfetto.protos.MeminfoCounters;
-import ProcessStatsConfig = protos.perfetto.protos.ProcessStatsConfig;
-import StatCounters = protos.perfetto.protos.SysStatsConfig.StatCounters;
-import SysStatsConfig = protos.perfetto.protos.SysStatsConfig;
-import TraceConfig = protos.perfetto.protos.TraceConfig;
-import VmstatCounters = protos.perfetto.protos.VmstatCounters;
-import IPCFrame = protos.perfetto.protos.IPCFrame;
-import IMethodInfo =
- protos.perfetto.protos.IPCFrame.BindServiceReply.IMethodInfo;
-import IBufferStats = protos.perfetto.protos.TraceStats.IBufferStats;
-import ISlice = protos.perfetto.protos.ReadBuffersResponse.ISlice;
-import EnableTracingRequest = protos.perfetto.protos.EnableTracingRequest;
-import DisableTracingRequest = protos.perfetto.protos.DisableTracingRequest;
-import GetTraceStatsRequest = protos.perfetto.protos.GetTraceStatsRequest;
-import FreeBuffersRequest = protos.perfetto.protos.FreeBuffersRequest;
-import ReadBuffersRequest = protos.perfetto.protos.ReadBuffersRequest;
-import QueryServiceStateRequest =
- protos.perfetto.protos.QueryServiceStateRequest;
-import EnableTracingResponse = protos.perfetto.protos.EnableTracingResponse;
-import DisableTracingResponse = protos.perfetto.protos.DisableTracingResponse;
-import GetTraceStatsResponse = protos.perfetto.protos.GetTraceStatsResponse;
-import FreeBuffersResponse = protos.perfetto.protos.FreeBuffersResponse;
-import ReadBuffersResponse = protos.perfetto.protos.ReadBuffersResponse;
-import QueryServiceStateResponse =
- protos.perfetto.protos.QueryServiceStateResponse;
-// Trace Processor protos.
-import QueryArgs = protos.perfetto.protos.QueryArgs;
-import ResetTraceProcessorArgs = protos.perfetto.protos.ResetTraceProcessorArgs;
-import StatusResult = protos.perfetto.protos.StatusResult;
import ComputeMetricArgs = protos.perfetto.protos.ComputeMetricArgs;
import ComputeMetricResult = protos.perfetto.protos.ComputeMetricResult;
+import ConsumerPort = protos.perfetto.protos.ConsumerPort;
+import DataSourceConfig = protos.perfetto.protos.DataSourceConfig;
+import DataSourceDescriptor = protos.perfetto.protos.DataSourceDescriptor;
import DisableAndReadMetatraceResult =
protos.perfetto.protos.DisableAndReadMetatraceResult;
-import Trace = protos.perfetto.protos.Trace;
-import TracePacket = protos.perfetto.protos.TracePacket;
+import DisableTracingRequest = protos.perfetto.protos.DisableTracingRequest;
+import DisableTracingResponse = protos.perfetto.protos.DisableTracingResponse;
+import EnableMetatraceArgs = protos.perfetto.protos.EnableMetatraceArgs;
+import EnableTracingRequest = protos.perfetto.protos.EnableTracingRequest;
+import EnableTracingResponse = protos.perfetto.protos.EnableTracingResponse;
+import FreeBuffersRequest = protos.perfetto.protos.FreeBuffersRequest;
+import FreeBuffersResponse = protos.perfetto.protos.FreeBuffersResponse;
+import FtraceConfig = protos.perfetto.protos.FtraceConfig;
+import GetTraceStatsRequest = protos.perfetto.protos.GetTraceStatsRequest;
+import GetTraceStatsResponse = protos.perfetto.protos.GetTraceStatsResponse;
+import HeapprofdConfig = protos.perfetto.protos.HeapprofdConfig;
+import IAndroidPowerConfig = protos.perfetto.protos.IAndroidPowerConfig;
+import IBufferConfig = protos.perfetto.protos.TraceConfig.IBufferConfig;
+import IBufferStats = protos.perfetto.protos.TraceStats.IBufferStats;
+import IDisableTracingResponse = protos.perfetto.protos.IDisableTracingResponse;
+import IEnableTracingResponse = protos.perfetto.protos.IEnableTracingResponse;
+import IFreeBuffersResponse = protos.perfetto.protos.IFreeBuffersResponse;
+import IGetTraceStatsResponse = protos.perfetto.protos.IGetTraceStatsResponse;
+import IMethodInfo =
+ protos.perfetto.protos.IPCFrame.BindServiceReply.IMethodInfo;
+import IPCFrame = protos.perfetto.protos.IPCFrame;
+import IProcessStatsConfig = protos.perfetto.protos.IProcessStatsConfig;
+import IReadBuffersResponse = protos.perfetto.protos.IReadBuffersResponse;
+import ISlice = protos.perfetto.protos.ReadBuffersResponse.ISlice;
+import ISysStatsConfig = protos.perfetto.protos.ISysStatsConfig;
+import ITraceConfig = protos.perfetto.protos.ITraceConfig;
+import ITraceStats = protos.perfetto.protos.ITraceStats;
+import JavaContinuousDumpConfig =
+ protos.perfetto.protos.JavaHprofConfig.ContinuousDumpConfig;
+import JavaHprofConfig = protos.perfetto.protos.JavaHprofConfig;
+import MeminfoCounters = protos.perfetto.protos.MeminfoCounters;
+import MetatraceCategories = protos.perfetto.protos.MetatraceCategories;
+import NativeContinuousDumpConfig =
+ protos.perfetto.protos.HeapprofdConfig.ContinuousDumpConfig;
+import NetworkPacketTraceConfig =
+ protos.perfetto.protos.NetworkPacketTraceConfig;
+import PerfEventConfig = protos.perfetto.protos.PerfEventConfig;
+import PerfEvents = protos.perfetto.protos.PerfEvents;
import PerfettoMetatrace = protos.perfetto.protos.PerfettoMetatrace;
+import ProcessStatsConfig = protos.perfetto.protos.ProcessStatsConfig;
+import QueryArgs = protos.perfetto.protos.QueryArgs;
+import QueryResult = protos.perfetto.protos.QueryResult;
+import QueryServiceStateRequest =
+ protos.perfetto.protos.QueryServiceStateRequest;
+import QueryServiceStateResponse =
+ protos.perfetto.protos.QueryServiceStateResponse;
+import ReadBuffersRequest = protos.perfetto.protos.ReadBuffersRequest;
+import ReadBuffersResponse = protos.perfetto.protos.ReadBuffersResponse;
+import ResetTraceProcessorArgs = protos.perfetto.protos.ResetTraceProcessorArgs;
+import StatCounters = protos.perfetto.protos.SysStatsConfig.StatCounters;
+import StatusResult = protos.perfetto.protos.StatusResult;
+import SysStatsConfig = protos.perfetto.protos.SysStatsConfig;
+import Trace = protos.perfetto.protos.Trace;
+import TraceConfig = protos.perfetto.protos.TraceConfig;
+import TracePacket = protos.perfetto.protos.TracePacket;
+import TraceProcessorApiVersion =
+ protos.perfetto.protos.TraceProcessorApiVersion;
+import TraceProcessorRpc = protos.perfetto.protos.TraceProcessorRpc;
+import TraceProcessorRpcStream = protos.perfetto.protos.TraceProcessorRpcStream;
+import TrackEventConfig = protos.perfetto.protos.TrackEventConfig;
+import VmstatCounters = protos.perfetto.protos.VmstatCounters;
export {
AndroidLogConfig,
@@ -94,6 +107,7 @@
DisableAndReadMetatraceResult,
DisableTracingRequest,
DisableTracingResponse,
+ EnableMetatraceArgs,
EnableTracingRequest,
EnableTracingResponse,
FreeBuffersRequest,
@@ -105,21 +119,30 @@
IAndroidPowerConfig,
IBufferConfig,
IBufferStats,
+ IDisableTracingResponse,
+ IEnableTracingResponse,
+ IFreeBuffersResponse,
+ IGetTraceStatsResponse,
IMethodInfo,
IPCFrame,
IProcessStatsConfig,
+ IReadBuffersResponse,
ISlice,
ISysStatsConfig,
ITraceConfig,
+ ITraceStats,
JavaContinuousDumpConfig,
JavaHprofConfig,
MeminfoCounters,
+ MetatraceCategories,
NativeContinuousDumpConfig,
NetworkPacketTraceConfig,
PerfettoMetatrace,
PerfEventConfig,
+ PerfEvents,
ProcessStatsConfig,
QueryArgs,
+ QueryResult,
QueryServiceStateRequest,
QueryServiceStateResponse,
ReadBuffersRequest,
@@ -131,6 +154,10 @@
Trace,
TraceConfig,
TracePacket,
+ TraceProcessorApiVersion,
+ TraceProcessorRpc,
+ TraceProcessorRpcStream,
TrackEventConfig,
VmstatCounters,
+
};
diff --git a/ui/src/core/trace_config_utils.ts b/ui/src/core/trace_config_utils.ts
index e7e6daf..3f91edc 100644
--- a/ui/src/core/trace_config_utils.ts
+++ b/ui/src/core/trace_config_utils.ts
@@ -12,8 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {TraceConfig} from '../core/protos';
-import {perfetto} from '../gen/protos';
+import {EnableTracingRequest, TraceConfig} from '../core/protos';
// In this file are contained a few functions to simplify the proto parsing.
@@ -21,10 +20,9 @@
Uint8Array|undefined {
try {
const enableTracingObject =
- perfetto.protos.EnableTracingRequest.decode(enableTracingRequest);
+ EnableTracingRequest.decode(enableTracingRequest);
if (!enableTracingObject.traceConfig) return undefined;
- return perfetto.protos.TraceConfig.encode(enableTracingObject.traceConfig)
- .finish();
+ return TraceConfig.encode(enableTracingObject.traceConfig).finish();
} catch (e) { // This catch is for possible proto encoding/decoding issues.
console.error('Error extracting the config: ', e.message);
return undefined;
@@ -33,7 +31,7 @@
export function extractDurationFromTraceConfig(traceConfigProto: Uint8Array) {
try {
- return perfetto.protos.TraceConfig.decode(traceConfigProto).durationMs;
+ return TraceConfig.decode(traceConfigProto).durationMs;
} catch (e) { // This catch is for possible proto encoding/decoding issues.
return undefined;
}
diff --git a/ui/src/frontend/details_panel.ts b/ui/src/frontend/details_panel.ts
index a7e168d..209583c 100644
--- a/ui/src/frontend/details_panel.ts
+++ b/ui/src/frontend/details_panel.ts
@@ -85,7 +85,7 @@
private height = 0;
private previousHeight = this.height;
private resize: (height: number) => void = () => {};
- private isClosed = this.height <= DRAG_HANDLE_HEIGHT_PX;
+ private isClosed = this.height <= 0;
private isFullscreen = false;
// We can't get real fullscreen height until the pan_and_zoom_handler exists.
private fullscreenHeight = getDetailsHeight();
@@ -94,7 +94,7 @@
oncreate({dom, attrs}: m.CVnodeDOM<DragHandleAttrs>) {
this.resize = attrs.resize;
this.height = attrs.height;
- this.isClosed = this.height <= DRAG_HANDLE_HEIGHT_PX;
+ this.isClosed = this.height <= 0;
this.fullscreenHeight = getFullScreenHeight();
const elem = dom as HTMLElement;
this.trash.add(new DragGestureHandler(
@@ -107,7 +107,7 @@
onupdate({attrs}: m.CVnodeDOM<DragHandleAttrs>) {
this.resize = attrs.resize;
this.height = attrs.height;
- this.isClosed = this.height <= DRAG_HANDLE_HEIGHT_PX;
+ this.isClosed = this.height <= 0;
}
onremove(_: m.CVnodeDOM<DragHandleAttrs>) {
@@ -117,7 +117,7 @@
onDrag(_x: number, y: number) {
const newHeight =
Math.floor(this.dragStartHeight + (DRAG_HANDLE_HEIGHT_PX / 2) - y);
- this.isClosed = newHeight <= DRAG_HANDLE_HEIGHT_PX;
+ this.isClosed = newHeight <= 0;
this.isFullscreen = newHeight >= this.fullscreenHeight;
this.resize(newHeight);
raf.scheduleFullRedraw();
@@ -154,7 +154,7 @@
onclick: () => {
this.isClosed = false;
this.isFullscreen = true;
- this.resize(this.fullscreenHeight - DRAG_HANDLE_HEIGHT_PX);
+ this.resize(this.fullscreenHeight);
raf.scheduleFullRedraw();
},
title: 'Open fullscreen',
@@ -164,7 +164,7 @@
m('i.material-icons',
{
onclick: () => {
- if (this.height === DRAG_HANDLE_HEIGHT_PX) {
+ if (this.height === 0) {
this.isClosed = false;
if (this.previousHeight === 0) {
this.previousHeight = getDetailsHeight();
diff --git a/ui/src/frontend/rpc_http_dialog.ts b/ui/src/frontend/rpc_http_dialog.ts
index 024a113..7684fc5 100644
--- a/ui/src/frontend/rpc_http_dialog.ts
+++ b/ui/src/frontend/rpc_http_dialog.ts
@@ -17,15 +17,14 @@
import {assertExists} from '../base/logging';
import {Actions} from '../common/actions';
import {HttpRpcEngine, RPC_URL} from '../common/http_rpc_engine';
-import {StatusResult} from '../core/protos';
+import {StatusResult, TraceProcessorApiVersion} from '../core/protos';
import {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 CURRENT_API_VERSION =
+ TraceProcessorApiVersion.TRACE_PROCESSOR_CURRENT_API_VERSION;
const PROMPT = `Trace Processor Native Accelerator detected on ${RPC_URL} with:
$loadedTraceName
diff --git a/ui/src/frontend/thread_state.ts b/ui/src/frontend/thread_state.ts
index 687c15a..bb1b2c7 100644
--- a/ui/src/frontend/thread_state.ts
+++ b/ui/src/frontend/thread_state.ts
@@ -20,10 +20,13 @@
Time,
time,
} from '../base/time';
+import {exists} from '../base/utils';
import {Actions} from '../common/actions';
import {EngineProxy} from '../common/engine';
+import {pluginManager} from '../common/plugins';
import {LONG, NUM, NUM_NULL, STR_NULL} from '../common/query_result';
import {translateState} from '../common/thread_state';
+import {CPU_SLICE_TRACK_KIND} from '../tracks/cpu_slices';
import {Anchor} from '../widgets/anchor';
import {globals} from './globals';
@@ -141,9 +144,14 @@
export function goToSchedSlice(cpu: number, id: SchedSqlId, ts: time) {
let trackId: string|undefined;
for (const track of Object.values(globals.state.tracks)) {
- if (track.kind === 'CpuSliceTrack' &&
- (track.config as {cpu: number}).cpu === cpu) {
- trackId = track.id;
+ if (exists(track?.uri)) {
+ const trackInfo = pluginManager.resolveTrackInfo(track.uri);
+ if (trackInfo?.tags?.kind === CPU_SLICE_TRACK_KIND) {
+ if (trackInfo?.tags?.cpu === cpu) {
+ trackId = track.id;
+ break;
+ }
+ }
}
}
if (trackId === undefined) {
diff --git a/ui/src/frontend/track_panel.ts b/ui/src/frontend/track_panel.ts
index b8020fc..8dffff1 100644
--- a/ui/src/frontend/track_panel.ts
+++ b/ui/src/frontend/track_panel.ts
@@ -345,8 +345,7 @@
private track: TrackLike|undefined;
private trackState: TrackState|undefined;
- constructor(vnode: m.CVnode<TrackPanelAttrs>) {
- super();
+ private tryLoadTrack(vnode: m.CVnode<TrackPanelAttrs>) {
const trackId = vnode.attrs.id;
const trackState = globals.state.tracks[trackId];
@@ -359,7 +358,11 @@
this.trackState = trackState;
}
- view() {
+ view(vnode: m.CVnode<TrackPanelAttrs>) {
+ if (!this.track) {
+ this.tryLoadTrack(vnode);
+ }
+
if (this.track === undefined || this.trackState === undefined) {
return m('div', 'No such track');
}
diff --git a/ui/src/public/index.ts b/ui/src/public/index.ts
index adafeb6..b939428 100644
--- a/ui/src/public/index.ts
+++ b/ui/src/public/index.ts
@@ -280,15 +280,27 @@
// A predicate for selecting a groups of tracks.
export type TrackPredicate = (info: TrackTags) => boolean;
-// An set of key/value pairs describing a given track. These
-// are used for selecting tracks to pin/unpin and (in future) the
-// sorting and grouping of tracks. The values are always strings.
-export interface TrackTags {
+interface WellKnownTrackTags {
// A human readable name for this specific track.
- name?: string;
+ name: string;
+ // This is where "XXX_TRACK_KIND" values should be placed.
+ kind: string;
+
+ // The CPU number associated with this track.
+ cpu: number;
+}
+
+// An set of key/value pairs describing a given track. These are used for
+// selecting tracks to pin/unpin and (in future) the sorting and grouping of
+// tracks.
+// These are also (ab)used for communicating information about tracks for the
+// purposes of locating tracks by their properties e.g. aggregation & search.
+// We define a handful of well known fields, and the rest are arbitrary key-
+// value pairs.
+export type TrackTags = Partial<WellKnownTrackTags>&{
// There may be arbitrary other key/value pairs.
- [key: string]: string|undefined;
+ [key: string]: string|number|undefined;
}
// Plugins can be passed as class refs, factory functions, or concrete plugin
diff --git a/ui/src/tracks/android_log/index.ts b/ui/src/tracks/android_log/index.ts
index 73a89dc..948f82d 100644
--- a/ui/src/tracks/android_log/index.ts
+++ b/ui/src/tracks/android_log/index.ts
@@ -155,7 +155,6 @@
ctx.addTrack({
uri: 'perfetto.AndroidLog',
displayName: 'Android logs',
- tags: {type: 'counter'},
trackFactory: ({trackInstanceId}) => {
return new TrackWithControllerAdapter<Config, Data>(
ctx.engine,
diff --git a/ui/src/tracks/cpu_slices/index.ts b/ui/src/tracks/cpu_slices/index.ts
index b2d49b6..40526a4 100644
--- a/ui/src/tracks/cpu_slices/index.ts
+++ b/ui/src/tracks/cpu_slices/index.ts
@@ -43,6 +43,8 @@
TracePluginContext,
} from '../../public';
+export const CPU_SLICE_TRACK_KIND = 'CpuSliceTrack';
+
export interface Data extends TrackData {
// Slices are stored in a columnar fashion. All fields have the same length.
ids: Float64Array;
@@ -483,8 +485,8 @@
uri,
displayName: name,
tags: {
- cpu: `${cpu}`,
- type: 'cpu_sched',
+ cpu,
+ kind: CPU_SLICE_TRACK_KIND,
},
trackFactory: ({trackInstanceId}) => {
return new TrackWithControllerAdapter<Config, Data>(