Merge "Add cpu_frequency event parsing"
diff --git a/Android.bp b/Android.bp
index aa12cd1..fbf6d49 100644
--- a/Android.bp
+++ b/Android.bp
@@ -3666,6 +3666,7 @@
genrule {
name: "perfetto_protos_perfetto_metrics_chrome_descriptor",
srcs: [
+ "protos/perfetto/metrics/android/android_trusty_workqueues.proto",
"protos/perfetto/metrics/android/batt_metric.proto",
"protos/perfetto/metrics/android/camera_metric.proto",
"protos/perfetto/metrics/android/camera_unagg_metric.proto",
@@ -3727,6 +3728,7 @@
genrule {
name: "perfetto_protos_perfetto_metrics_descriptor",
srcs: [
+ "protos/perfetto/metrics/android/android_trusty_workqueues.proto",
"protos/perfetto/metrics/android/batt_metric.proto",
"protos/perfetto/metrics/android/camera_metric.proto",
"protos/perfetto/metrics/android/camera_unagg_metric.proto",
@@ -4173,10 +4175,12 @@
"protos/perfetto/trace/ftrace/scm.proto",
"protos/perfetto/trace/ftrace/sde.proto",
"protos/perfetto/trace/ftrace/signal.proto",
+ "protos/perfetto/trace/ftrace/sock.proto",
"protos/perfetto/trace/ftrace/sync.proto",
"protos/perfetto/trace/ftrace/synthetic.proto",
"protos/perfetto/trace/ftrace/systrace.proto",
"protos/perfetto/trace/ftrace/task.proto",
+ "protos/perfetto/trace/ftrace/tcp.proto",
"protos/perfetto/trace/ftrace/test_bundle_wrapper.proto",
"protos/perfetto/trace/ftrace/thermal.proto",
"protos/perfetto/trace/ftrace/vmscan.proto",
@@ -4392,10 +4396,12 @@
"protos/perfetto/trace/ftrace/scm.proto",
"protos/perfetto/trace/ftrace/sde.proto",
"protos/perfetto/trace/ftrace/signal.proto",
+ "protos/perfetto/trace/ftrace/sock.proto",
"protos/perfetto/trace/ftrace/sync.proto",
"protos/perfetto/trace/ftrace/synthetic.proto",
"protos/perfetto/trace/ftrace/systrace.proto",
"protos/perfetto/trace/ftrace/task.proto",
+ "protos/perfetto/trace/ftrace/tcp.proto",
"protos/perfetto/trace/ftrace/test_bundle_wrapper.proto",
"protos/perfetto/trace/ftrace/thermal.proto",
"protos/perfetto/trace/ftrace/vmscan.proto",
@@ -4445,10 +4451,12 @@
"external/perfetto/protos/perfetto/trace/ftrace/scm.gen.cc",
"external/perfetto/protos/perfetto/trace/ftrace/sde.gen.cc",
"external/perfetto/protos/perfetto/trace/ftrace/signal.gen.cc",
+ "external/perfetto/protos/perfetto/trace/ftrace/sock.gen.cc",
"external/perfetto/protos/perfetto/trace/ftrace/sync.gen.cc",
"external/perfetto/protos/perfetto/trace/ftrace/synthetic.gen.cc",
"external/perfetto/protos/perfetto/trace/ftrace/systrace.gen.cc",
"external/perfetto/protos/perfetto/trace/ftrace/task.gen.cc",
+ "external/perfetto/protos/perfetto/trace/ftrace/tcp.gen.cc",
"external/perfetto/protos/perfetto/trace/ftrace/test_bundle_wrapper.gen.cc",
"external/perfetto/protos/perfetto/trace/ftrace/thermal.gen.cc",
"external/perfetto/protos/perfetto/trace/ftrace/vmscan.gen.cc",
@@ -4498,10 +4506,12 @@
"protos/perfetto/trace/ftrace/scm.proto",
"protos/perfetto/trace/ftrace/sde.proto",
"protos/perfetto/trace/ftrace/signal.proto",
+ "protos/perfetto/trace/ftrace/sock.proto",
"protos/perfetto/trace/ftrace/sync.proto",
"protos/perfetto/trace/ftrace/synthetic.proto",
"protos/perfetto/trace/ftrace/systrace.proto",
"protos/perfetto/trace/ftrace/task.proto",
+ "protos/perfetto/trace/ftrace/tcp.proto",
"protos/perfetto/trace/ftrace/test_bundle_wrapper.proto",
"protos/perfetto/trace/ftrace/thermal.proto",
"protos/perfetto/trace/ftrace/vmscan.proto",
@@ -4551,10 +4561,12 @@
"external/perfetto/protos/perfetto/trace/ftrace/scm.gen.h",
"external/perfetto/protos/perfetto/trace/ftrace/sde.gen.h",
"external/perfetto/protos/perfetto/trace/ftrace/signal.gen.h",
+ "external/perfetto/protos/perfetto/trace/ftrace/sock.gen.h",
"external/perfetto/protos/perfetto/trace/ftrace/sync.gen.h",
"external/perfetto/protos/perfetto/trace/ftrace/synthetic.gen.h",
"external/perfetto/protos/perfetto/trace/ftrace/systrace.gen.h",
"external/perfetto/protos/perfetto/trace/ftrace/task.gen.h",
+ "external/perfetto/protos/perfetto/trace/ftrace/tcp.gen.h",
"external/perfetto/protos/perfetto/trace/ftrace/test_bundle_wrapper.gen.h",
"external/perfetto/protos/perfetto/trace/ftrace/thermal.gen.h",
"external/perfetto/protos/perfetto/trace/ftrace/vmscan.gen.h",
@@ -4608,10 +4620,12 @@
"protos/perfetto/trace/ftrace/scm.proto",
"protos/perfetto/trace/ftrace/sde.proto",
"protos/perfetto/trace/ftrace/signal.proto",
+ "protos/perfetto/trace/ftrace/sock.proto",
"protos/perfetto/trace/ftrace/sync.proto",
"protos/perfetto/trace/ftrace/synthetic.proto",
"protos/perfetto/trace/ftrace/systrace.proto",
"protos/perfetto/trace/ftrace/task.proto",
+ "protos/perfetto/trace/ftrace/tcp.proto",
"protos/perfetto/trace/ftrace/test_bundle_wrapper.proto",
"protos/perfetto/trace/ftrace/thermal.proto",
"protos/perfetto/trace/ftrace/vmscan.proto",
@@ -4660,10 +4674,12 @@
"external/perfetto/protos/perfetto/trace/ftrace/scm.pb.cc",
"external/perfetto/protos/perfetto/trace/ftrace/sde.pb.cc",
"external/perfetto/protos/perfetto/trace/ftrace/signal.pb.cc",
+ "external/perfetto/protos/perfetto/trace/ftrace/sock.pb.cc",
"external/perfetto/protos/perfetto/trace/ftrace/sync.pb.cc",
"external/perfetto/protos/perfetto/trace/ftrace/synthetic.pb.cc",
"external/perfetto/protos/perfetto/trace/ftrace/systrace.pb.cc",
"external/perfetto/protos/perfetto/trace/ftrace/task.pb.cc",
+ "external/perfetto/protos/perfetto/trace/ftrace/tcp.pb.cc",
"external/perfetto/protos/perfetto/trace/ftrace/test_bundle_wrapper.pb.cc",
"external/perfetto/protos/perfetto/trace/ftrace/thermal.pb.cc",
"external/perfetto/protos/perfetto/trace/ftrace/vmscan.pb.cc",
@@ -4713,10 +4729,12 @@
"protos/perfetto/trace/ftrace/scm.proto",
"protos/perfetto/trace/ftrace/sde.proto",
"protos/perfetto/trace/ftrace/signal.proto",
+ "protos/perfetto/trace/ftrace/sock.proto",
"protos/perfetto/trace/ftrace/sync.proto",
"protos/perfetto/trace/ftrace/synthetic.proto",
"protos/perfetto/trace/ftrace/systrace.proto",
"protos/perfetto/trace/ftrace/task.proto",
+ "protos/perfetto/trace/ftrace/tcp.proto",
"protos/perfetto/trace/ftrace/test_bundle_wrapper.proto",
"protos/perfetto/trace/ftrace/thermal.proto",
"protos/perfetto/trace/ftrace/vmscan.proto",
@@ -4765,10 +4783,12 @@
"external/perfetto/protos/perfetto/trace/ftrace/scm.pb.h",
"external/perfetto/protos/perfetto/trace/ftrace/sde.pb.h",
"external/perfetto/protos/perfetto/trace/ftrace/signal.pb.h",
+ "external/perfetto/protos/perfetto/trace/ftrace/sock.pb.h",
"external/perfetto/protos/perfetto/trace/ftrace/sync.pb.h",
"external/perfetto/protos/perfetto/trace/ftrace/synthetic.pb.h",
"external/perfetto/protos/perfetto/trace/ftrace/systrace.pb.h",
"external/perfetto/protos/perfetto/trace/ftrace/task.pb.h",
+ "external/perfetto/protos/perfetto/trace/ftrace/tcp.pb.h",
"external/perfetto/protos/perfetto/trace/ftrace/test_bundle_wrapper.pb.h",
"external/perfetto/protos/perfetto/trace/ftrace/thermal.pb.h",
"external/perfetto/protos/perfetto/trace/ftrace/vmscan.pb.h",
@@ -4822,10 +4842,12 @@
"protos/perfetto/trace/ftrace/scm.proto",
"protos/perfetto/trace/ftrace/sde.proto",
"protos/perfetto/trace/ftrace/signal.proto",
+ "protos/perfetto/trace/ftrace/sock.proto",
"protos/perfetto/trace/ftrace/sync.proto",
"protos/perfetto/trace/ftrace/synthetic.proto",
"protos/perfetto/trace/ftrace/systrace.proto",
"protos/perfetto/trace/ftrace/task.proto",
+ "protos/perfetto/trace/ftrace/tcp.proto",
"protos/perfetto/trace/ftrace/test_bundle_wrapper.proto",
"protos/perfetto/trace/ftrace/thermal.proto",
"protos/perfetto/trace/ftrace/vmscan.proto",
@@ -4875,10 +4897,12 @@
"external/perfetto/protos/perfetto/trace/ftrace/scm.pbzero.cc",
"external/perfetto/protos/perfetto/trace/ftrace/sde.pbzero.cc",
"external/perfetto/protos/perfetto/trace/ftrace/signal.pbzero.cc",
+ "external/perfetto/protos/perfetto/trace/ftrace/sock.pbzero.cc",
"external/perfetto/protos/perfetto/trace/ftrace/sync.pbzero.cc",
"external/perfetto/protos/perfetto/trace/ftrace/synthetic.pbzero.cc",
"external/perfetto/protos/perfetto/trace/ftrace/systrace.pbzero.cc",
"external/perfetto/protos/perfetto/trace/ftrace/task.pbzero.cc",
+ "external/perfetto/protos/perfetto/trace/ftrace/tcp.pbzero.cc",
"external/perfetto/protos/perfetto/trace/ftrace/test_bundle_wrapper.pbzero.cc",
"external/perfetto/protos/perfetto/trace/ftrace/thermal.pbzero.cc",
"external/perfetto/protos/perfetto/trace/ftrace/vmscan.pbzero.cc",
@@ -4928,10 +4952,12 @@
"protos/perfetto/trace/ftrace/scm.proto",
"protos/perfetto/trace/ftrace/sde.proto",
"protos/perfetto/trace/ftrace/signal.proto",
+ "protos/perfetto/trace/ftrace/sock.proto",
"protos/perfetto/trace/ftrace/sync.proto",
"protos/perfetto/trace/ftrace/synthetic.proto",
"protos/perfetto/trace/ftrace/systrace.proto",
"protos/perfetto/trace/ftrace/task.proto",
+ "protos/perfetto/trace/ftrace/tcp.proto",
"protos/perfetto/trace/ftrace/test_bundle_wrapper.proto",
"protos/perfetto/trace/ftrace/thermal.proto",
"protos/perfetto/trace/ftrace/vmscan.proto",
@@ -4981,10 +5007,12 @@
"external/perfetto/protos/perfetto/trace/ftrace/scm.pbzero.h",
"external/perfetto/protos/perfetto/trace/ftrace/sde.pbzero.h",
"external/perfetto/protos/perfetto/trace/ftrace/signal.pbzero.h",
+ "external/perfetto/protos/perfetto/trace/ftrace/sock.pbzero.h",
"external/perfetto/protos/perfetto/trace/ftrace/sync.pbzero.h",
"external/perfetto/protos/perfetto/trace/ftrace/synthetic.pbzero.h",
"external/perfetto/protos/perfetto/trace/ftrace/systrace.pbzero.h",
"external/perfetto/protos/perfetto/trace/ftrace/task.pbzero.h",
+ "external/perfetto/protos/perfetto/trace/ftrace/tcp.pbzero.h",
"external/perfetto/protos/perfetto/trace/ftrace/test_bundle_wrapper.pbzero.h",
"external/perfetto/protos/perfetto/trace/ftrace/thermal.pbzero.h",
"external/perfetto/protos/perfetto/trace/ftrace/vmscan.pbzero.h",
@@ -8121,6 +8149,7 @@
"src/trace_processor/metrics/sql/android/android_task_names.sql",
"src/trace_processor/metrics/sql/android/android_thread_time_in_state.sql",
"src/trace_processor/metrics/sql/android/android_trace_quality.sql",
+ "src/trace_processor/metrics/sql/android/android_trusty_workqueues.sql",
"src/trace_processor/metrics/sql/android/composer_execution.sql",
"src/trace_processor/metrics/sql/android/composition_layers.sql",
"src/trace_processor/metrics/sql/android/cpu_info.sql",
diff --git a/BUILD b/BUILD
index 5b4d5cb..ab6cc4c 100644
--- a/BUILD
+++ b/BUILD
@@ -1070,6 +1070,7 @@
"src/trace_processor/metrics/sql/android/android_task_names.sql",
"src/trace_processor/metrics/sql/android/android_thread_time_in_state.sql",
"src/trace_processor/metrics/sql/android/android_trace_quality.sql",
+ "src/trace_processor/metrics/sql/android/android_trusty_workqueues.sql",
"src/trace_processor/metrics/sql/android/composer_execution.sql",
"src/trace_processor/metrics/sql/android/composition_layers.sql",
"src/trace_processor/metrics/sql/android/cpu_info.sql",
@@ -1263,6 +1264,7 @@
"src/trace_processor/types/softirq_action.h",
"src/trace_processor/types/task_state.cc",
"src/trace_processor/types/task_state.h",
+ "src/trace_processor/types/tcp_state.h",
"src/trace_processor/types/trace_processor_context.h",
"src/trace_processor/types/variadic.cc",
"src/trace_processor/types/variadic.h",
@@ -2605,6 +2607,7 @@
perfetto_proto_library(
name = "protos_perfetto_metrics_android_protos",
srcs = [
+ "protos/perfetto/metrics/android/android_trusty_workqueues.proto",
"protos/perfetto/metrics/android/batt_metric.proto",
"protos/perfetto/metrics/android/camera_metric.proto",
"protos/perfetto/metrics/android/camera_unagg_metric.proto",
@@ -2878,10 +2881,12 @@
"protos/perfetto/trace/ftrace/scm.proto",
"protos/perfetto/trace/ftrace/sde.proto",
"protos/perfetto/trace/ftrace/signal.proto",
+ "protos/perfetto/trace/ftrace/sock.proto",
"protos/perfetto/trace/ftrace/sync.proto",
"protos/perfetto/trace/ftrace/synthetic.proto",
"protos/perfetto/trace/ftrace/systrace.proto",
"protos/perfetto/trace/ftrace/task.proto",
+ "protos/perfetto/trace/ftrace/tcp.proto",
"protos/perfetto/trace/ftrace/test_bundle_wrapper.proto",
"protos/perfetto/trace/ftrace/thermal.proto",
"protos/perfetto/trace/ftrace/vmscan.proto",
diff --git a/CHANGELOG b/CHANGELOG
index 8dbe728..76b42be 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -9,6 +9,50 @@
*
+v24.1 - 2022-02-09:
+ Tracing service and probes:
+ * Fixed build failures on Windows.
+ Trace Processor:
+ * Fixed build failures on Windows.
+ UI:
+ *
+ SDK:
+ *
+
+
+v24.0 - 2022-02-08:
+ Tracing service and probes:
+ * Added "cpufreq_period_ms" in data source "linux.sys_stats" to poll
+ /sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq periodically.
+ * Added support for Trusty TEE workqueue events.
+ * Added support for more PMU events in traced_perf.
+ * Changed output format of perfetto --query. Made the output more compact
+ and added a summary of ongoing tracing sessions for the caller UID.
+ * Changed timeout for traced stall detection from 2s to 4s.
+ * Changed internal buffer management to split trace filtering in smaller
+ tasks and avoid too large memory allocation when filtering.
+ * Fixed a bug that could cause producers to see Flush() requests after an
+ OnStop() and mis-behave if the tracing session is extremely short.
+ Trace Processor:
+ * Added support for passing multiple SQL statements to ExecuteQuery(). All
+ queries will be executed fully, with the returned iterator yielding rows
+ for the final statement.
+ * Added support for multi-line SQL comments; previously only single line
+ comments were supported.
+ UI:
+ * Added support for parsing instant events from legacy systrace formats.
+ * Added ingestion and visualization for inet_sock_set_state and
+ tcp_retransmit_skb events, showing TCP connections on dedicated tracks.
+ * Changed HTTP+RPC to use the /websocket endpoint available in newer
+ versions of trace_processor --httpd.
+ * Changed text selection/copy: now allowed by default for DOM elements.
+ * Changed search to also lookup slices by ID when the term is a number.
+ * Changed postMessage() API, suppressed confirmation dialog when the opener
+ is in the same origin, for cases when the UI is self-hosted.
+ SDK:
+ *
+
+
v23.0 - 2022-01-11:
Tracing service and probes:
* Added workaround for a kernel ftrace bug causing some "comm" fields to be
@@ -65,6 +109,9 @@
the middle of a suspend/resume. Switched from SND_TIMEO to poll(POLLOUT).
* Added "linux.sysfs_power" data source to poll /sys/class/power_supply/
and report periodically battery charge and drain rate if supported.
+ * Add snapshotting for non-BOOTTIME ftrace clocks. This fixes handling of
+ ftrace events from old Linux kernel versions (i.e. 3.x) and adds
+ proper support for using the "global" clock rather than "boot".
Trace Processor:
* Speeded up proto trace ingestion by 2x (~20 MB/s -> ~40 MB/s).
* Changed LIKE comparisions to be case-senstive. This may break existing
@@ -75,6 +122,7 @@
* Changed how displayTimeUnit is handled in JSON traces to match catapult.
* Added websocket endpoint to RPC interface to reduce query latency.
* Added support for hot-reloading metrics (see //docs/analysis/metrics.md).
+ * Added ingestion support for non-BOOTTIME ftrace clocks.
UI:
* Added ability to save/restore record config. Remember last used config.
* Fixed bug causing the recording page to hold onto the USB interface making
diff --git a/TEST_MAPPING b/TEST_MAPPING
index d971a20..785bb2a 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -8,5 +8,10 @@
{
"name": "libsurfaceflinger_unittest"
}
+ ],
+ "hwasan-postsubmit": [
+ {
+ "name": "CtsPerfettoTestCases"
+ }
]
}
diff --git a/docs/contributing/testing.md b/docs/contributing/testing.md
index 67b9407..f45d665 100644
--- a/docs/contributing/testing.md
+++ b/docs/contributing/testing.md
@@ -120,9 +120,9 @@
output in the whole file (usually at the end). Calling
`SELECT RUN_METRIC('metric file')` can trip up this check as this query
generates some hidden output. To address this issue, if a query only has
-column is named `supress_query_output`, even if it has output, this will
+column is named `suppress_query_output`, even if it has output, this will
be ignored (for example,
-`SELECT RUN_METRIC('metric file') as surpress_query_output`)
+`SELECT RUN_METRIC('metric file') as suppress_query_output`)
UI pixel diff tests
-----------------
diff --git a/include/perfetto/trace_processor/iterator.h b/include/perfetto/trace_processor/iterator.h
index c3373f3..95aa4c5 100644
--- a/include/perfetto/trace_processor/iterator.h
+++ b/include/perfetto/trace_processor/iterator.h
@@ -31,6 +31,15 @@
class IteratorImpl;
// Iterator returning SQL rows satisfied by a query.
+//
+// Example usage:
+// auto sql = "select name, ifnull(cat, "[NULL]") from slice";
+// for (auto it = tp.ExecuteQuery(sql); it.Next();)
+// for (uint32_t i = 0; i < it.ColumnCount(); ++i) {
+// printf("%s ", it.Get(i).AsString());
+// }
+// printf("\n");
+// }
class PERFETTO_EXPORT Iterator {
public:
explicit Iterator(std::unique_ptr<IteratorImpl>);
@@ -40,7 +49,7 @@
Iterator& operator=(Iterator&) = delete;
Iterator(Iterator&&) noexcept;
- Iterator& operator=(Iterator&&);
+ Iterator& operator=(Iterator&&) noexcept;
// Forwards the iterator to the next result row and returns a boolean of
// whether there is a next row. If this method returns false,
@@ -61,6 +70,19 @@
// even before calling |Next()|.
uint32_t ColumnCount();
+ // Returns the number of statements in the provided SQL (including the final
+ // statement which is iterated using this iterator). Comments and empty
+ // statements are *not* counted i.e.
+ // "SELECT 1; /* comment */; select 2; -- comment"
+ // returns 2 not 4.
+ uint32_t StatementCount();
+
+ // Returns the number of statements which produced output rows in the provided
+ // SQL (including, potentially, the final statement which is iterated using
+ // this iterator).
+ // This value is guaranteed to be <= |StatementCount()|.
+ uint32_t StatementWithOutputCount();
+
// Returns the status of the iterator.
util::Status Status();
diff --git a/include/perfetto/trace_processor/trace_processor.h b/include/perfetto/trace_processor/trace_processor.h
index 77a7889..972761d 100644
--- a/include/perfetto/trace_processor/trace_processor.h
+++ b/include/perfetto/trace_processor/trace_processor.h
@@ -43,23 +43,29 @@
~TraceProcessor() override;
- // Executes a SQLite query on the loaded portion of the trace. The returned
- // iterator can be used to load rows from the result.
- virtual Iterator ExecuteQuery(const std::string& sql,
- int64_t time_queued = 0) = 0;
+ // Executes the SQL on the loaded portion of the trace.
+ //
+ // More than one SQL statement can be passed to this function; all but the
+ // last will be fully executed by this function before retuning. The last
+ // statement will be executed and will yield rows as the caller calls Next()
+ // over the returned Iterator.
+ //
+ // See documentation of the Iterator class for an example on how to use
+ // the returned iterator.
+ virtual Iterator ExecuteQuery(const std::string& sql) = 0;
// Registers a metric at the given path which will run the specified SQL.
- virtual util::Status RegisterMetric(const std::string& path,
+ virtual base::Status RegisterMetric(const std::string& path,
const std::string& sql) = 0;
// Reads the FileDescriptorSet proto message given by |data| and |size| and
// adds any extensions to the metrics proto to allow them to be available as
// proto builder functions when computing metrics.
- virtual util::Status ExtendMetricsProto(const uint8_t* data, size_t size) = 0;
+ virtual base::Status ExtendMetricsProto(const uint8_t* data, size_t size) = 0;
// Behaves exactly as ExtendMetricsProto, except any FileDescriptor with
// filename matching a prefix in |skip_prefixes| is skipped.
- virtual util::Status ExtendMetricsProto(
+ virtual base::Status ExtendMetricsProto(
const uint8_t* data,
size_t size,
const std::vector<std::string>& skip_prefixes) = 0;
@@ -68,7 +74,7 @@
// successful, the output argument |metrics_proto| will be filled with the
// proto-encoded bytes for the message TraceMetrics in
// perfetto/metrics/metrics.proto.
- virtual util::Status ComputeMetric(
+ virtual base::Status ComputeMetric(
const std::vector<std::string>& metric_names,
std::vector<uint8_t>* metrics_proto) = 0;
@@ -80,7 +86,7 @@
// Computes metrics as the ComputeMetric function above, but instead of
// producing proto encoded bytes, the output argument |metrics_string| is
// filled with the metric formatted in the requested |format|.
- virtual util::Status ComputeMetricText(
+ virtual base::Status ComputeMetricText(
const std::vector<std::string>& metric_names,
MetricResultFormat format,
std::string* metrics_string) = 0;
@@ -110,7 +116,7 @@
// Disables "meta-tracing" of trace processor and writes the trace as a
// sequence of |TracePackets| into |trace_proto| returning the status of this
// read.
- virtual util::Status DisableAndReadMetatrace(
+ virtual base::Status DisableAndReadMetatrace(
std::vector<uint8_t>* trace_proto) = 0;
// Gets all the currently loaded proto descriptors used in metric computation.
diff --git a/include/perfetto/tracing/internal/track_event_data_source.h b/include/perfetto/tracing/internal/track_event_data_source.h
index 9229153..3a3db7e 100644
--- a/include/perfetto/tracing/internal/track_event_data_source.h
+++ b/include/perfetto/tracing/internal/track_event_data_source.h
@@ -35,14 +35,16 @@
namespace perfetto {
-// This template provides a way to convert an abstract timestamp into the trace
-// clock timebase in nanoseconds. By specialising this template and defining
+// A function for converting an abstract timestamp into a
+// perfetto::TraceTimestamp struct. By specialising this template and defining
// static ConvertTimestampToTraceTimeNs function in it the user can register
-// additional timestamp types. The return value should specify the clock used by
-// the timestamp as well as its value in nanoseconds.
+// additional timestamp types. The return value should specify the
+// clock domain used by the timestamp as well as its value.
//
-// The users should see the specialisation for uint64_t below as an example.
-// Note that the specialisation should be defined in perfetto namespace.
+// The supported clock domains are the ones described in
+// perfetto.protos.ClockSnapshot. However, custom clock IDs (>=64) are
+// reserved for internal use by the SDK for the time being.
+// The timestamp value should be in nanoseconds regardless of the clock domain.
template <typename T>
struct TraceTimestampTraits;
@@ -239,7 +241,7 @@
Arguments&&... args) PERFETTO_NO_INLINE {
TraceForCategoryImpl(instances, category, event_name, type,
TrackEventInternal::kDefaultTrack,
- TrackEventInternal::GetTimeNs(),
+ TrackEventInternal::GetTraceTime(),
std::forward<Arguments>(args)...);
}
@@ -261,7 +263,7 @@
Arguments&&... args) PERFETTO_NO_INLINE {
TraceForCategoryImpl(
instances, category, event_name, type, std::forward<TrackType>(track),
- TrackEventInternal::GetTimeNs(), std::forward<Arguments>(args)...);
+ TrackEventInternal::GetTraceTime(), std::forward<Arguments>(args)...);
}
// Trace point which takes a timestamp, but not track.
@@ -314,7 +316,7 @@
ValueType value) PERFETTO_ALWAYS_INLINE {
PERFETTO_DCHECK(type == perfetto::protos::pbzero::TrackEvent::TYPE_COUNTER);
TraceForCategory(instances, category, /*name=*/nullptr, type, track,
- TrackEventInternal::GetTimeNs(), value);
+ TrackEventInternal::GetTraceTime(), value);
}
// Trace point with with a timestamp and a counter sample.
@@ -360,7 +362,8 @@
TrackRegistry::Get()->UpdateTrack(track, desc.SerializeAsString());
Base::template Trace([&](typename Base::TraceContext ctx) {
TrackEventInternal::WriteTrackDescriptor(
- track, ctx.tls_inst_->trace_writer.get());
+ track, ctx.tls_inst_->trace_writer.get(), ctx.GetIncrementalState(),
+ TrackEventInternal::GetTraceTime());
});
}
@@ -452,14 +455,14 @@
TrackEventIncrementalState* incr_state = ctx.GetIncrementalState();
if (incr_state->was_cleared) {
incr_state->was_cleared = false;
- TrackEventInternal::ResetIncrementalState(trace_writer,
+ TrackEventInternal::ResetIncrementalState(trace_writer, incr_state,
trace_timestamp);
}
// Write the track descriptor before any event on the track.
if (track) {
TrackEventInternal::WriteTrackDescriptorIfNeeded(
- track, trace_writer, incr_state);
+ track, trace_writer, incr_state, trace_timestamp);
}
// Write the event itself.
@@ -513,7 +516,8 @@
TrackRegistry::Get()->UpdateTrack(track, std::move(callback));
Base::template Trace([&](typename Base::TraceContext ctx) {
TrackEventInternal::WriteTrackDescriptor(
- track, ctx.tls_inst_->trace_writer.get());
+ track, ctx.tls_inst_->trace_writer.get(), ctx.GetIncrementalState(),
+ TrackEventInternal::GetTraceTime());
});
}
diff --git a/include/perfetto/tracing/internal/track_event_internal.h b/include/perfetto/tracing/internal/track_event_internal.h
index 037dc09..6fed09e 100644
--- a/include/perfetto/tracing/internal/track_event_internal.h
+++ b/include/perfetto/tracing/internal/track_event_internal.h
@@ -35,13 +35,24 @@
// Represents a point in time for the clock specified by |clock_id|.
struct TraceTimestamp {
- protos::pbzero::BuiltinClock clock_id;
- uint64_t nanoseconds;
+ // Clock IDs have the following semantic:
+ // [1, 63]: Builtin types, see BuiltinClock from
+ // ../common/builtin_clock.proto.
+ // [64, 127]: User-defined clocks. These clocks are sequence-scoped. They
+ // are only valid within the same |trusted_packet_sequence_id|
+ // (i.e. only for TracePacket(s) emitted by the same TraceWriter
+ // that emitted the clock snapshot).
+ // [128, MAX]: Reserved for future use. The idea is to allow global clock
+ // IDs and setting this ID to hash(full_clock_name) & ~127.
+ // Learn more: `clock_snapshot.proto`
+ uint32_t clock_id;
+ uint64_t value;
};
class EventContext;
class TrackEventSessionObserver;
struct Category;
+struct TraceTimestamp;
namespace protos {
namespace gen {
class TrackEventConfig;
@@ -85,6 +96,12 @@
struct TrackEventIncrementalState {
static constexpr size_t kMaxInternedDataFields = 32;
+ // Packet-sequence-scoped clock that encodes microsecond timestamps in the
+ // domain of the clock returned by GetClockId() as delta values - see
+ // Clock::is_incremental in perfetto/trace/clock_snapshot.proto.
+ // Default unit: nanoseconds.
+ static constexpr uint32_t kClockIdIncremental = 64;
+
bool was_cleared = true;
// A heap-allocated message for storing newly seen interned data while we are
@@ -116,6 +133,11 @@
// this tracing session. The value in the map indicates whether the category
// is enabled or disabled.
std::unordered_map<std::string, bool> dynamic_categories;
+
+ // The latest reference timestamp that was used in a TracePacket or in a
+ // ClockSnapshot. The increment between this timestamp and the current trace
+ // time (GetTimeNs) is a value in kClockIdIncremental's domain.
+ uint64_t last_timestamp_ns = 0;
};
// The backend portion of the track event trace point implemention. Outlined to
@@ -146,9 +168,11 @@
const Category* category,
const char* name,
perfetto::protos::pbzero::TrackEvent::Type,
- TraceTimestamp timestamp = {GetClockId(), GetTimeNs()});
+ const TraceTimestamp& timestamp);
- static void ResetIncrementalState(TraceWriterBase*, TraceTimestamp);
+ static void ResetIncrementalState(TraceWriterBase* trace_writer,
+ TrackEventIncrementalState* incr_state,
+ const TraceTimestamp& timestamp);
// TODO(altimin): Remove this method once Chrome uses
// EventContext::AddDebugAnnotation directly.
@@ -168,24 +192,29 @@
static void WriteTrackDescriptorIfNeeded(
const TrackType& track,
TraceWriterBase* trace_writer,
- TrackEventIncrementalState* incr_state) {
+ TrackEventIncrementalState* incr_state,
+ const TraceTimestamp& timestamp) {
auto it_and_inserted = incr_state->seen_tracks.insert(track.uuid);
if (PERFETTO_LIKELY(!it_and_inserted.second))
return;
- WriteTrackDescriptor(track, trace_writer);
+ WriteTrackDescriptor(track, trace_writer, incr_state, timestamp);
}
// Unconditionally write a track descriptor into the trace.
template <typename TrackType>
static void WriteTrackDescriptor(const TrackType& track,
- TraceWriterBase* trace_writer) {
+ TraceWriterBase* trace_writer,
+ TrackEventIncrementalState* incr_state,
+ const TraceTimestamp& timestamp) {
TrackRegistry::Get()->SerializeTrack(
- track, NewTracePacket(trace_writer, {GetClockId(), GetTimeNs()}));
+ track, NewTracePacket(trace_writer, incr_state, timestamp));
}
// Get the current time in nanoseconds in the trace clock timebase.
static uint64_t GetTimeNs();
+ static TraceTimestamp GetTraceTime();
+
// Get the clock used by GetTimeNs().
static constexpr protos::pbzero::BuiltinClock GetClockId() {
#if !PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE) && \
@@ -204,7 +233,8 @@
private:
static protozero::MessageHandle<protos::pbzero::TracePacket> NewTracePacket(
TraceWriterBase*,
- TraceTimestamp,
+ TrackEventIncrementalState*,
+ const TraceTimestamp&,
uint32_t seq_flags =
protos::pbzero::TracePacket::SEQ_NEEDS_INCREMENTAL_STATE);
static protos::pbzero::DebugAnnotation* AddDebugAnnotation(
diff --git a/include/perfetto/tracing/track_event_state_tracker.h b/include/perfetto/tracing/track_event_state_tracker.h
index 7b2437e..e97f035 100644
--- a/include/perfetto/tracing/track_event_state_tracker.h
+++ b/include/perfetto/tracing/track_event_state_tracker.h
@@ -80,6 +80,10 @@
std::map<uint64_t /*iid*/, std::string> event_names;
std::map<uint64_t /*iid*/, std::string> event_categories;
std::map<uint64_t /*iid*/, std::string> debug_annotation_names;
+ // Current absolute timestamp of the incremental clock.
+ uint64_t most_recent_absolute_time_ns = 0;
+ // default_clock_id == 0 means, no default clock_id is set.
+ uint32_t default_clock_id = 0;
};
// State for the entire tracing session. Shared by all trace writer sequences
diff --git a/infra/luci/generated/cr-buildbucket.cfg b/infra/luci/generated/cr-buildbucket.cfg
index 18a7c5a..95f68d7 100644
--- a/infra/luci/generated/cr-buildbucket.cfg
+++ b/infra/luci/generated/cr-buildbucket.cfg
@@ -29,7 +29,10 @@
cipd_version: "refs/heads/master"
cmd: "luciexe"
}
- properties: "{\"recipe\":\"perfetto\"}"
+ properties:
+ '{'
+ ' "recipe": "perfetto"'
+ '}'
service_account: "perfetto-luci-official-builder@chops-service-accounts.iam.gserviceaccount.com"
experiments {
key: "luci.use_realms"
@@ -47,7 +50,10 @@
cipd_version: "refs/heads/master"
cmd: "luciexe"
}
- properties: "{\"recipe\":\"perfetto\"}"
+ properties:
+ '{'
+ ' "recipe": "perfetto"'
+ '}'
service_account: "perfetto-luci-official-builder@chops-service-accounts.iam.gserviceaccount.com"
experiments {
key: "luci.use_realms"
@@ -65,7 +71,14 @@
cipd_version: "refs/heads/master"
cmd: "luciexe"
}
- properties: "{\"recipe\":\"perfetto\"}"
+ properties:
+ '{'
+ ' "recipe": "perfetto"'
+ '}'
+ caches {
+ name: "macos_sdk"
+ path: "macos_sdk"
+ }
service_account: "perfetto-luci-official-builder@chops-service-accounts.iam.gserviceaccount.com"
experiments {
key: "luci.use_realms"
@@ -83,7 +96,14 @@
cipd_version: "refs/heads/master"
cmd: "luciexe"
}
- properties: "{\"recipe\":\"perfetto\"}"
+ properties:
+ '{'
+ ' "recipe": "perfetto"'
+ '}'
+ caches {
+ name: "windows_sdk"
+ path: "windows_sdk"
+ }
service_account: "perfetto-luci-official-builder@chops-service-accounts.iam.gserviceaccount.com"
experiments {
key: "luci.use_realms"
diff --git a/infra/luci/generated/project.cfg b/infra/luci/generated/project.cfg
index fbf2a59..809a993 100644
--- a/infra/luci/generated/project.cfg
+++ b/infra/luci/generated/project.cfg
@@ -6,3 +6,9 @@
name: "perfetto"
access: "group:all"
+lucicfg {
+ version: "1.30.5"
+ package_dir: ".."
+ config_dir: "generated"
+ entry_point: "main.star"
+}
diff --git a/infra/luci/main.star b/infra/luci/main.star
index 4119e2d..5cf3b0c 100755
--- a/infra/luci/main.star
+++ b/infra/luci/main.star
@@ -15,8 +15,7 @@
lucicfg.check_version("1.23.3", "Please update depot_tools")
-# Enable LUCI Realms support and launch all builds in realms-aware mode.
-lucicfg.enable_experiment("crbug.com/1085650")
+# Launch all builds in realms-aware mode.
luci.builder.defaults.experiments.set({"luci.use_realms": 100})
# Enable bbagent.
@@ -75,7 +74,7 @@
],
)
-def official_builder(name, os):
+def official_builder(name, os, caches=[]):
luci.builder(
name = name,
bucket = "official",
@@ -98,9 +97,13 @@
refs = ["refs/tags/v.+"],
),
],
+ caches = [
+ swarming.cache(cache, name = cache)
+ for cache in caches
+ ]
)
official_builder("perfetto-official-builder-linux", "Linux")
-official_builder("perfetto-official-builder-mac", "Mac")
-official_builder("perfetto-official-builder-windows", "Windows")
+official_builder("perfetto-official-builder-mac", "Mac", ["macos_sdk"])
+official_builder("perfetto-official-builder-windows", "Windows", ["windows_sdk"])
official_builder("perfetto-official-builder-android", "Linux")
diff --git a/protos/perfetto/common/perf_events.proto b/protos/perfetto/common/perf_events.proto
index 8c48396..270bacb 100644
--- a/protos/perfetto/common/perf_events.proto
+++ b/protos/perfetto/common/perf_events.proto
@@ -64,14 +64,56 @@
optional string name = 10;
}
+ // Builtin counter names from the uapi header. Commented with their perf tool
+ // aliases.
+ // TODO(rsavitski): consider generating enums for cache events (should be
+ // finite), and generally make this list as extensive as possible. Excluding
+ // things like dynamic PMUs since those don't fit into a static enum.
+ // Next id: 21
enum Counter {
UNKNOWN_COUNTER = 0;
- // software:
+
+ // cpu-clock
SW_CPU_CLOCK = 1;
+ // page-faults, faults
SW_PAGE_FAULTS = 2;
- // hardware:
+ // task-clock
+ SW_TASK_CLOCK = 3;
+ // context-switches, cs
+ SW_CONTEXT_SWITCHES = 4;
+ // cpu-migrations, migrations
+ SW_CPU_MIGRATIONS = 5;
+ // minor-faults
+ SW_PAGE_FAULTS_MIN = 6;
+ // major-faults
+ SW_PAGE_FAULTS_MAJ = 7;
+ // alignment-faults
+ SW_ALIGNMENT_FAULTS = 8;
+ // emulation-faults
+ SW_EMULATION_FAULTS = 9;
+ // dummy
+ SW_DUMMY = 20;
+
+ // cpu-cycles, cycles
HW_CPU_CYCLES = 10;
+ // instructions
HW_INSTRUCTIONS = 11;
+ // cache-references
+ HW_CACHE_REFERENCES = 12;
+ // cache-misses
+ HW_CACHE_MISSES = 13;
+ // branch-instructions, branches
+ HW_BRANCH_INSTRUCTIONS = 14;
+ // branch-misses
+ HW_BRANCH_MISSES = 15;
+ // bus-cycles
+ HW_BUS_CYCLES = 16;
+ // stalled-cycles-frontend, idle-cycles-frontend
+ HW_STALLED_CYCLES_FRONTEND = 17;
+ // stalled-cycles-backend, idle-cycles-backend
+ HW_STALLED_CYCLES_BACKEND = 18;
+ // ref-cycles
+ HW_REF_CPU_CYCLES = 19;
}
message Tracepoint {
diff --git a/protos/perfetto/common/tracing_service_state.proto b/protos/perfetto/common/tracing_service_state.proto
index 1ef4c56..6d796bc 100644
--- a/protos/perfetto/common/tracing_service_state.proto
+++ b/protos/perfetto/common/tracing_service_state.proto
@@ -32,6 +32,10 @@
// Typically matches the process name.
optional string name = 2;
+ // Unix pid of the remote process. Supported only on Linux-based systems.
+ // Introduced in v24 / Android T.
+ optional int32 pid = 5;
+
// Unix uid of the remote process.
optional int32 uid = 3;
@@ -52,12 +56,53 @@
optional int32 producer_id = 2;
}
+ message TracingSession {
+ // The TracingSessionID.
+ optional uint64 id = 1;
+
+ // The Unix uid of the consumer that started the session.
+ // This is meaningful only if the caller is root. In all other cases only
+ // tracing sessions that match the caller UID will be displayed.
+ optional int32 consumer_uid = 2;
+
+ // Internal state of the tracing session.
+ // These strings are FYI only and subjected to change.
+ optional string state = 3;
+
+ // The unique_session_name as set in the trace config (might be empty).
+ optional string unique_session_name = 4;
+
+ // The number and size of each buffer.
+ repeated uint32 buffer_size_kb = 5;
+
+ // Duration, as specified in the TraceConfig.duration_ms.
+ optional uint32 duration_ms = 6;
+
+ // Number of data sources involved in the session.
+ optional uint32 num_data_sources = 7;
+
+ // Time when the session was started, in the CLOCK_REALTIME domain.
+ // Available only on Linux-based systems.
+ optional int64 start_realtime_ns = 8;
+ }
+
// Lists all the producers connected.
repeated Producer producers = 1;
// Lists the data sources available.
repeated DataSource data_sources = 2;
+ // Lists the tracing sessions active AND owned by a consumer that has the same
+ // UID of the caller (or all of them if the caller is root).
+ // Introduced in v24 / Android T.
+ repeated TracingSession tracing_sessions = 6;
+
+ // This is always set to true from v24 and beyond. This flag is only used to
+ // tell the difference between: (1) talking to a recent service which happens
+ // to have no tracing session active; (2) talking to an older version of the
+ // service which will never report any tracing session.
+ optional bool supports_tracing_sessions = 7;
+
// Total number of tracing sessions.
optional int32 num_sessions = 3;
diff --git a/protos/perfetto/config/perfetto_config.proto b/protos/perfetto/config/perfetto_config.proto
index 69078d4..30195a4 100644
--- a/protos/perfetto/config/perfetto_config.proto
+++ b/protos/perfetto/config/perfetto_config.proto
@@ -204,6 +204,10 @@
// Typically matches the process name.
optional string name = 2;
+ // Unix pid of the remote process. Supported only on Linux-based systems.
+ // Introduced in v24 / Android T.
+ optional int32 pid = 5;
+
// Unix uid of the remote process.
optional int32 uid = 3;
@@ -224,12 +228,53 @@
optional int32 producer_id = 2;
}
+ message TracingSession {
+ // The TracingSessionID.
+ optional uint64 id = 1;
+
+ // The Unix uid of the consumer that started the session.
+ // This is meaningful only if the caller is root. In all other cases only
+ // tracing sessions that match the caller UID will be displayed.
+ optional int32 consumer_uid = 2;
+
+ // Internal state of the tracing session.
+ // These strings are FYI only and subjected to change.
+ optional string state = 3;
+
+ // The unique_session_name as set in the trace config (might be empty).
+ optional string unique_session_name = 4;
+
+ // The number and size of each buffer.
+ repeated uint32 buffer_size_kb = 5;
+
+ // Duration, as specified in the TraceConfig.duration_ms.
+ optional uint32 duration_ms = 6;
+
+ // Number of data sources involved in the session.
+ optional uint32 num_data_sources = 7;
+
+ // Time when the session was started, in the CLOCK_REALTIME domain.
+ // Available only on Linux-based systems.
+ optional int64 start_realtime_ns = 8;
+ }
+
// Lists all the producers connected.
repeated Producer producers = 1;
// Lists the data sources available.
repeated DataSource data_sources = 2;
+ // Lists the tracing sessions active AND owned by a consumer that has the same
+ // UID of the caller (or all of them if the caller is root).
+ // Introduced in v24 / Android T.
+ repeated TracingSession tracing_sessions = 6;
+
+ // This is always set to true from v24 and beyond. This flag is only used to
+ // tell the difference between: (1) talking to a recent service which happens
+ // to have no tracing session active; (2) talking to an older version of the
+ // service which will never report any tracing session.
+ optional bool supports_tracing_sessions = 7;
+
// Total number of tracing sessions.
optional int32 num_sessions = 3;
@@ -892,14 +937,56 @@
optional string name = 10;
}
+ // Builtin counter names from the uapi header. Commented with their perf tool
+ // aliases.
+ // TODO(rsavitski): consider generating enums for cache events (should be
+ // finite), and generally make this list as extensive as possible. Excluding
+ // things like dynamic PMUs since those don't fit into a static enum.
+ // Next id: 21
enum Counter {
UNKNOWN_COUNTER = 0;
- // software:
+
+ // cpu-clock
SW_CPU_CLOCK = 1;
+ // page-faults, faults
SW_PAGE_FAULTS = 2;
- // hardware:
+ // task-clock
+ SW_TASK_CLOCK = 3;
+ // context-switches, cs
+ SW_CONTEXT_SWITCHES = 4;
+ // cpu-migrations, migrations
+ SW_CPU_MIGRATIONS = 5;
+ // minor-faults
+ SW_PAGE_FAULTS_MIN = 6;
+ // major-faults
+ SW_PAGE_FAULTS_MAJ = 7;
+ // alignment-faults
+ SW_ALIGNMENT_FAULTS = 8;
+ // emulation-faults
+ SW_EMULATION_FAULTS = 9;
+ // dummy
+ SW_DUMMY = 20;
+
+ // cpu-cycles, cycles
HW_CPU_CYCLES = 10;
+ // instructions
HW_INSTRUCTIONS = 11;
+ // cache-references
+ HW_CACHE_REFERENCES = 12;
+ // cache-misses
+ HW_CACHE_MISSES = 13;
+ // branch-instructions, branches
+ HW_BRANCH_INSTRUCTIONS = 14;
+ // branch-misses
+ HW_BRANCH_MISSES = 15;
+ // bus-cycles
+ HW_BUS_CYCLES = 16;
+ // stalled-cycles-frontend, idle-cycles-frontend
+ HW_STALLED_CYCLES_FRONTEND = 17;
+ // stalled-cycles-backend, idle-cycles-backend
+ HW_STALLED_CYCLES_BACKEND = 18;
+ // ref-cycles
+ HW_REF_CPU_CYCLES = 19;
}
message Tracepoint {
@@ -1315,6 +1402,10 @@
// This option can be used to record unchanging values.
// Updates from frequency changes can come from ftrace/set_clock_rate.
optional uint32 devfreq_period_ms = 7;
+
+ // Polls /sys/devices/system/cpu/cpu*/cpufreq/cpuinfo_cur_freq every X ms.
+ // This is required to be > 10ms to avoid excessive CPU usage.
+ optional uint32 cpufreq_period_ms = 8;
}
// End of protos/perfetto/config/sys_stats/sys_stats_config.proto
@@ -1551,7 +1642,7 @@
// It contains the general config for the logging buffer(s) and the configs for
// all the data source being enabled.
//
-// Next id: 34.
+// Next id: 35.
message TraceConfig {
message BufferConfig {
optional uint32 size_kb = 1;
@@ -1962,6 +2053,54 @@
// old field number for trace_filter
reserved 32;
optional TraceFilter trace_filter = 33;
+
+ // Android-only. Not for general use. If set, reports the trace to the
+ // Android framework. This field is read by perfetto_cmd, rather than the
+ // tracing service. This field must be set when passing the --upload flag to
+ // perfetto_cmd.
+ message AndroidReportConfig {
+ // In this message, either:
+ // * |reporter_service_package| and |reporter_service_class| must be set.
+ // * |skip_reporting| must be explicitly set to true.
+
+ optional string reporter_service_package = 1;
+ optional string reporter_service_class = 2;
+
+ // If true, then skips reporting the trace to Android framework.
+ //
+ // This flag is useful in testing (e.g. Perfetto-statsd integration tests)
+ // or when we explicitly don't want to report traces to the framework even
+ // when they usually would (e.g. configs deployed using statsd but only
+ // used for inclusion in bugreports using |bugreport_score|).
+ //
+ // The motivation for having this flag, instead of just not setting
+ // |framework_report_config|, is prevent accidents where
+ // |framework_report_config| is omitted by mistake.
+ optional bool skip_report = 3;
+
+ // If true, will direct the Android framework to read the data in trace
+ // file and pass it to the reporter class over a pipe instead of passing
+ // the file descriptor directly.
+ //
+ // This flag is needed because the Android test framework does not
+ // currently support priv-app helper apps (in terms of SELinux) and we
+ // really don't want to add an allow rule for untrusted_app to receive
+ // trace fds.
+ //
+ // Because of this, we instead will direct the framework to create a new
+ // pipe and pass this to the reporter process instead. As the pipe is
+ // created by the framework, we won't have any problems with SELinux
+ // (system_server is already allowed to pass pipe fds, even
+ // to untrusted apps).
+ //
+ // As the name suggests this option *MUST* only be used for testing.
+ // Note that the framework will reject (and drop) files which are too
+ // large both for simplicity and to be minimize the amount of data we
+ // pass to a non-priv app (note that the framework will still check
+ // manifest permissions even though SELinux permissions are worked around).
+ optional bool use_pipe_in_framework_for_testing = 4;
+ }
+ optional AndroidReportConfig android_report_config = 34;
}
// End of protos/perfetto/config/trace_config.proto
diff --git a/protos/perfetto/config/sys_stats/sys_stats_config.proto b/protos/perfetto/config/sys_stats/sys_stats_config.proto
index 0986924..09d34ea 100644
--- a/protos/perfetto/config/sys_stats/sys_stats_config.proto
+++ b/protos/perfetto/config/sys_stats/sys_stats_config.proto
@@ -63,4 +63,8 @@
// This option can be used to record unchanging values.
// Updates from frequency changes can come from ftrace/set_clock_rate.
optional uint32 devfreq_period_ms = 7;
+
+ // Polls /sys/devices/system/cpu/cpu*/cpufreq/cpuinfo_cur_freq every X ms.
+ // This is required to be > 10ms to avoid excessive CPU usage.
+ optional uint32 cpufreq_period_ms = 8;
}
diff --git a/protos/perfetto/config/trace_config.proto b/protos/perfetto/config/trace_config.proto
index e845f17..d58239f 100644
--- a/protos/perfetto/config/trace_config.proto
+++ b/protos/perfetto/config/trace_config.proto
@@ -26,7 +26,7 @@
// It contains the general config for the logging buffer(s) and the configs for
// all the data source being enabled.
//
-// Next id: 34.
+// Next id: 35.
message TraceConfig {
message BufferConfig {
optional uint32 size_kb = 1;
@@ -437,4 +437,52 @@
// old field number for trace_filter
reserved 32;
optional TraceFilter trace_filter = 33;
+
+ // Android-only. Not for general use. If set, reports the trace to the
+ // Android framework. This field is read by perfetto_cmd, rather than the
+ // tracing service. This field must be set when passing the --upload flag to
+ // perfetto_cmd.
+ message AndroidReportConfig {
+ // In this message, either:
+ // * |reporter_service_package| and |reporter_service_class| must be set.
+ // * |skip_reporting| must be explicitly set to true.
+
+ optional string reporter_service_package = 1;
+ optional string reporter_service_class = 2;
+
+ // If true, then skips reporting the trace to Android framework.
+ //
+ // This flag is useful in testing (e.g. Perfetto-statsd integration tests)
+ // or when we explicitly don't want to report traces to the framework even
+ // when they usually would (e.g. configs deployed using statsd but only
+ // used for inclusion in bugreports using |bugreport_score|).
+ //
+ // The motivation for having this flag, instead of just not setting
+ // |framework_report_config|, is prevent accidents where
+ // |framework_report_config| is omitted by mistake.
+ optional bool skip_report = 3;
+
+ // If true, will direct the Android framework to read the data in trace
+ // file and pass it to the reporter class over a pipe instead of passing
+ // the file descriptor directly.
+ //
+ // This flag is needed because the Android test framework does not
+ // currently support priv-app helper apps (in terms of SELinux) and we
+ // really don't want to add an allow rule for untrusted_app to receive
+ // trace fds.
+ //
+ // Because of this, we instead will direct the framework to create a new
+ // pipe and pass this to the reporter process instead. As the pipe is
+ // created by the framework, we won't have any problems with SELinux
+ // (system_server is already allowed to pass pipe fds, even
+ // to untrusted apps).
+ //
+ // As the name suggests this option *MUST* only be used for testing.
+ // Note that the framework will reject (and drop) files which are too
+ // large both for simplicity and to be minimize the amount of data we
+ // pass to a non-priv app (note that the framework will still check
+ // manifest permissions even though SELinux permissions are worked around).
+ optional bool use_pipe_in_framework_for_testing = 4;
+ }
+ optional AndroidReportConfig android_report_config = 34;
}
diff --git a/protos/perfetto/metrics/android/BUILD.gn b/protos/perfetto/metrics/android/BUILD.gn
index 4b4923b..251670b 100644
--- a/protos/perfetto/metrics/android/BUILD.gn
+++ b/protos/perfetto/metrics/android/BUILD.gn
@@ -20,6 +20,7 @@
"source_set",
]
sources = [
+ "android_trusty_workqueues.proto",
"batt_metric.proto",
"camera_metric.proto",
"camera_unagg_metric.proto",
diff --git a/protos/perfetto/metrics/android/android_trusty_workqueues.proto b/protos/perfetto/metrics/android/android_trusty_workqueues.proto
new file mode 100644
index 0000000..b7a1d5a
--- /dev/null
+++ b/protos/perfetto/metrics/android/android_trusty_workqueues.proto
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+syntax = "proto2";
+
+package perfetto.protos;
+
+// Metric used to generate a simplified view of the Trusty kworker events.
+message AndroidTrustyWorkqueues {}
diff --git a/protos/perfetto/metrics/android/java_heap_histogram.proto b/protos/perfetto/metrics/android/java_heap_histogram.proto
index ce23669..7fa86ef 100644
--- a/protos/perfetto/metrics/android/java_heap_histogram.proto
+++ b/protos/perfetto/metrics/android/java_heap_histogram.proto
@@ -19,13 +19,18 @@
import "protos/perfetto/metrics/android/process_metadata.proto";
message JavaHeapHistogram {
- // Next id: 5
+ // Next id: 9
message TypeCount {
optional string type_name = 1;
optional string category = 4;
optional uint32 obj_count = 2;
optional uint32 reachable_obj_count = 3;
+
+ optional uint32 size_kb = 5;
+ optional uint32 reachable_size_kb = 6;
+ optional uint32 native_size_kb = 7;
+ optional uint32 reachable_native_size_kb = 8;
}
message Sample {
diff --git a/protos/perfetto/metrics/metrics.proto b/protos/perfetto/metrics/metrics.proto
index 1731c8d..8421007 100644
--- a/protos/perfetto/metrics/metrics.proto
+++ b/protos/perfetto/metrics/metrics.proto
@@ -52,6 +52,7 @@
import "protos/perfetto/metrics/android/task_names.proto";
import "protos/perfetto/metrics/android/thread_time_in_state_metric.proto";
import "protos/perfetto/metrics/android/trace_quality.proto";
+import "protos/perfetto/metrics/android/android_trusty_workqueues.proto";
import "protos/perfetto/metrics/android/unsymbolized_frames.proto";
// Trace processor metadata
@@ -98,7 +99,7 @@
// Root message for all Perfetto-based metrics.
//
-// Next id: 44
+// Next id: 45
message TraceMetrics {
reserved 4, 10, 13, 14, 16, 19;
@@ -214,6 +215,9 @@
// Metrics for IRQ runtime.
optional AndroidIrqRuntimeMetric android_irq_runtime = 43;
+ // Metrics for the Trusty team.
+ optional AndroidTrustyWorkqueues android_trusty_workqueues = 44;
+
// Demo extensions.
extensions 450 to 499;
diff --git a/protos/perfetto/metrics/perfetto_merged_metrics.proto b/protos/perfetto/metrics/perfetto_merged_metrics.proto
index 4c4a0bc..03f6690 100644
--- a/protos/perfetto/metrics/perfetto_merged_metrics.proto
+++ b/protos/perfetto/metrics/perfetto_merged_metrics.proto
@@ -13,6 +13,13 @@
option go_package = "github.com/google/perfetto/perfetto_proto";
+// Begin of protos/perfetto/metrics/android/android_trusty_workqueues.proto
+
+// Metric used to generate a simplified view of the Trusty kworker events.
+message AndroidTrustyWorkqueues {}
+
+// End of protos/perfetto/metrics/android/android_trusty_workqueues.proto
+
// Begin of protos/perfetto/metrics/android/batt_metric.proto
message AndroidBatteryMetric {
@@ -636,13 +643,18 @@
// Begin of protos/perfetto/metrics/android/java_heap_histogram.proto
message JavaHeapHistogram {
- // Next id: 5
+ // Next id: 9
message TypeCount {
optional string type_name = 1;
optional string category = 4;
optional uint32 obj_count = 2;
optional uint32 reachable_obj_count = 3;
+
+ optional uint32 size_kb = 5;
+ optional uint32 reachable_size_kb = 6;
+ optional uint32 native_size_kb = 7;
+ optional uint32 reachable_native_size_kb = 8;
}
message Sample {
@@ -1459,7 +1471,7 @@
// Root message for all Perfetto-based metrics.
//
-// Next id: 44
+// Next id: 45
message TraceMetrics {
reserved 4, 10, 13, 14, 16, 19;
@@ -1575,6 +1587,9 @@
// Metrics for IRQ runtime.
optional AndroidIrqRuntimeMetric android_irq_runtime = 43;
+ // Metrics for the Trusty team.
+ optional AndroidTrustyWorkqueues android_trusty_workqueues = 44;
+
// Demo extensions.
extensions 450 to 499;
diff --git a/protos/perfetto/trace/ftrace/all_protos.gni b/protos/perfetto/trace/ftrace/all_protos.gni
index 55655f8..ae4e3d0 100644
--- a/protos/perfetto/trace/ftrace/all_protos.gni
+++ b/protos/perfetto/trace/ftrace/all_protos.gni
@@ -54,10 +54,12 @@
"scm.proto",
"sde.proto",
"signal.proto",
+ "sock.proto",
"sync.proto",
"synthetic.proto",
"systrace.proto",
"task.proto",
+ "tcp.proto",
"thermal.proto",
"vmscan.proto",
"workqueue.proto",
diff --git a/protos/perfetto/trace/ftrace/ftrace_event.proto b/protos/perfetto/trace/ftrace/ftrace_event.proto
index dd718ef..5b42169 100644
--- a/protos/perfetto/trace/ftrace/ftrace_event.proto
+++ b/protos/perfetto/trace/ftrace/ftrace_event.proto
@@ -54,10 +54,12 @@
import "protos/perfetto/trace/ftrace/scm.proto";
import "protos/perfetto/trace/ftrace/sde.proto";
import "protos/perfetto/trace/ftrace/signal.proto";
+import "protos/perfetto/trace/ftrace/sock.proto";
import "protos/perfetto/trace/ftrace/sync.proto";
import "protos/perfetto/trace/ftrace/synthetic.proto";
import "protos/perfetto/trace/ftrace/systrace.proto";
import "protos/perfetto/trace/ftrace/task.proto";
+import "protos/perfetto/trace/ftrace/tcp.proto";
import "protos/perfetto/trace/ftrace/thermal.proto";
import "protos/perfetto/trace/ftrace/vmscan.proto";
import "protos/perfetto/trace/ftrace/workqueue.proto";
@@ -452,5 +454,7 @@
RssStatThrottledFtraceEvent rss_stat_throttled = 359;
NetifReceiveSkbFtraceEvent netif_receive_skb = 360;
NetDevXmitFtraceEvent net_dev_xmit = 361;
+ InetSockSetStateFtraceEvent inet_sock_set_state = 362;
+ TcpRetransmitSkbFtraceEvent tcp_retransmit_skb = 363;
}
}
diff --git a/protos/perfetto/trace/ftrace/sock.proto b/protos/perfetto/trace/ftrace/sock.proto
new file mode 100644
index 0000000..9607117
--- /dev/null
+++ b/protos/perfetto/trace/ftrace/sock.proto
@@ -0,0 +1,18 @@
+// Autogenerated by:
+// ../../tools/ftrace_proto_gen/ftrace_proto_gen.cc
+// Do not edit.
+
+syntax = "proto2";
+package perfetto.protos;
+
+message InetSockSetStateFtraceEvent {
+ optional uint32 daddr = 1;
+ optional uint32 dport = 2;
+ optional uint32 family = 3;
+ optional int32 newstate = 4;
+ optional int32 oldstate = 5;
+ optional uint32 protocol = 6;
+ optional uint32 saddr = 7;
+ optional uint64 skaddr = 8;
+ optional uint32 sport = 9;
+}
diff --git a/protos/perfetto/trace/ftrace/tcp.proto b/protos/perfetto/trace/ftrace/tcp.proto
new file mode 100644
index 0000000..fb854c3
--- /dev/null
+++ b/protos/perfetto/trace/ftrace/tcp.proto
@@ -0,0 +1,16 @@
+// Autogenerated by:
+// ../../tools/ftrace_proto_gen/ftrace_proto_gen.cc
+// Do not edit.
+
+syntax = "proto2";
+package perfetto.protos;
+
+message TcpRetransmitSkbFtraceEvent {
+ optional uint32 daddr = 1;
+ optional uint32 dport = 2;
+ optional uint32 saddr = 3;
+ optional uint64 skaddr = 4;
+ optional uint64 skbaddr = 5;
+ optional uint32 sport = 6;
+ optional int32 state = 7;
+}
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index bfee00c..8e6b639 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -204,6 +204,10 @@
// Typically matches the process name.
optional string name = 2;
+ // Unix pid of the remote process. Supported only on Linux-based systems.
+ // Introduced in v24 / Android T.
+ optional int32 pid = 5;
+
// Unix uid of the remote process.
optional int32 uid = 3;
@@ -224,12 +228,53 @@
optional int32 producer_id = 2;
}
+ message TracingSession {
+ // The TracingSessionID.
+ optional uint64 id = 1;
+
+ // The Unix uid of the consumer that started the session.
+ // This is meaningful only if the caller is root. In all other cases only
+ // tracing sessions that match the caller UID will be displayed.
+ optional int32 consumer_uid = 2;
+
+ // Internal state of the tracing session.
+ // These strings are FYI only and subjected to change.
+ optional string state = 3;
+
+ // The unique_session_name as set in the trace config (might be empty).
+ optional string unique_session_name = 4;
+
+ // The number and size of each buffer.
+ repeated uint32 buffer_size_kb = 5;
+
+ // Duration, as specified in the TraceConfig.duration_ms.
+ optional uint32 duration_ms = 6;
+
+ // Number of data sources involved in the session.
+ optional uint32 num_data_sources = 7;
+
+ // Time when the session was started, in the CLOCK_REALTIME domain.
+ // Available only on Linux-based systems.
+ optional int64 start_realtime_ns = 8;
+ }
+
// Lists all the producers connected.
repeated Producer producers = 1;
// Lists the data sources available.
repeated DataSource data_sources = 2;
+ // Lists the tracing sessions active AND owned by a consumer that has the same
+ // UID of the caller (or all of them if the caller is root).
+ // Introduced in v24 / Android T.
+ repeated TracingSession tracing_sessions = 6;
+
+ // This is always set to true from v24 and beyond. This flag is only used to
+ // tell the difference between: (1) talking to a recent service which happens
+ // to have no tracing session active; (2) talking to an older version of the
+ // service which will never report any tracing session.
+ optional bool supports_tracing_sessions = 7;
+
// Total number of tracing sessions.
optional int32 num_sessions = 3;
@@ -892,14 +937,56 @@
optional string name = 10;
}
+ // Builtin counter names from the uapi header. Commented with their perf tool
+ // aliases.
+ // TODO(rsavitski): consider generating enums for cache events (should be
+ // finite), and generally make this list as extensive as possible. Excluding
+ // things like dynamic PMUs since those don't fit into a static enum.
+ // Next id: 21
enum Counter {
UNKNOWN_COUNTER = 0;
- // software:
+
+ // cpu-clock
SW_CPU_CLOCK = 1;
+ // page-faults, faults
SW_PAGE_FAULTS = 2;
- // hardware:
+ // task-clock
+ SW_TASK_CLOCK = 3;
+ // context-switches, cs
+ SW_CONTEXT_SWITCHES = 4;
+ // cpu-migrations, migrations
+ SW_CPU_MIGRATIONS = 5;
+ // minor-faults
+ SW_PAGE_FAULTS_MIN = 6;
+ // major-faults
+ SW_PAGE_FAULTS_MAJ = 7;
+ // alignment-faults
+ SW_ALIGNMENT_FAULTS = 8;
+ // emulation-faults
+ SW_EMULATION_FAULTS = 9;
+ // dummy
+ SW_DUMMY = 20;
+
+ // cpu-cycles, cycles
HW_CPU_CYCLES = 10;
+ // instructions
HW_INSTRUCTIONS = 11;
+ // cache-references
+ HW_CACHE_REFERENCES = 12;
+ // cache-misses
+ HW_CACHE_MISSES = 13;
+ // branch-instructions, branches
+ HW_BRANCH_INSTRUCTIONS = 14;
+ // branch-misses
+ HW_BRANCH_MISSES = 15;
+ // bus-cycles
+ HW_BUS_CYCLES = 16;
+ // stalled-cycles-frontend, idle-cycles-frontend
+ HW_STALLED_CYCLES_FRONTEND = 17;
+ // stalled-cycles-backend, idle-cycles-backend
+ HW_STALLED_CYCLES_BACKEND = 18;
+ // ref-cycles
+ HW_REF_CPU_CYCLES = 19;
}
message Tracepoint {
@@ -1315,6 +1402,10 @@
// This option can be used to record unchanging values.
// Updates from frequency changes can come from ftrace/set_clock_rate.
optional uint32 devfreq_period_ms = 7;
+
+ // Polls /sys/devices/system/cpu/cpu*/cpufreq/cpuinfo_cur_freq every X ms.
+ // This is required to be > 10ms to avoid excessive CPU usage.
+ optional uint32 cpufreq_period_ms = 8;
}
// End of protos/perfetto/config/sys_stats/sys_stats_config.proto
@@ -1551,7 +1642,7 @@
// It contains the general config for the logging buffer(s) and the configs for
// all the data source being enabled.
//
-// Next id: 34.
+// Next id: 35.
message TraceConfig {
message BufferConfig {
optional uint32 size_kb = 1;
@@ -1962,6 +2053,54 @@
// old field number for trace_filter
reserved 32;
optional TraceFilter trace_filter = 33;
+
+ // Android-only. Not for general use. If set, reports the trace to the
+ // Android framework. This field is read by perfetto_cmd, rather than the
+ // tracing service. This field must be set when passing the --upload flag to
+ // perfetto_cmd.
+ message AndroidReportConfig {
+ // In this message, either:
+ // * |reporter_service_package| and |reporter_service_class| must be set.
+ // * |skip_reporting| must be explicitly set to true.
+
+ optional string reporter_service_package = 1;
+ optional string reporter_service_class = 2;
+
+ // If true, then skips reporting the trace to Android framework.
+ //
+ // This flag is useful in testing (e.g. Perfetto-statsd integration tests)
+ // or when we explicitly don't want to report traces to the framework even
+ // when they usually would (e.g. configs deployed using statsd but only
+ // used for inclusion in bugreports using |bugreport_score|).
+ //
+ // The motivation for having this flag, instead of just not setting
+ // |framework_report_config|, is prevent accidents where
+ // |framework_report_config| is omitted by mistake.
+ optional bool skip_report = 3;
+
+ // If true, will direct the Android framework to read the data in trace
+ // file and pass it to the reporter class over a pipe instead of passing
+ // the file descriptor directly.
+ //
+ // This flag is needed because the Android test framework does not
+ // currently support priv-app helper apps (in terms of SELinux) and we
+ // really don't want to add an allow rule for untrusted_app to receive
+ // trace fds.
+ //
+ // Because of this, we instead will direct the framework to create a new
+ // pipe and pass this to the reporter process instead. As the pipe is
+ // created by the framework, we won't have any problems with SELinux
+ // (system_server is already allowed to pass pipe fds, even
+ // to untrusted apps).
+ //
+ // As the name suggests this option *MUST* only be used for testing.
+ // Note that the framework will reject (and drop) files which are too
+ // large both for simplicity and to be minimize the amount of data we
+ // pass to a non-priv app (note that the framework will still check
+ // manifest permissions even though SELinux permissions are worked around).
+ optional bool use_pipe_in_framework_for_testing = 4;
+ }
+ optional AndroidReportConfig android_report_config = 34;
}
// End of protos/perfetto/config/trace_config.proto
@@ -5258,6 +5397,22 @@
// End of protos/perfetto/trace/ftrace/signal.proto
+// Begin of protos/perfetto/trace/ftrace/sock.proto
+
+message InetSockSetStateFtraceEvent {
+ optional uint32 daddr = 1;
+ optional uint32 dport = 2;
+ optional uint32 family = 3;
+ optional int32 newstate = 4;
+ optional int32 oldstate = 5;
+ optional uint32 protocol = 6;
+ optional uint32 saddr = 7;
+ optional uint64 skaddr = 8;
+ optional uint32 sport = 9;
+}
+
+// End of protos/perfetto/trace/ftrace/sock.proto
+
// Begin of protos/perfetto/trace/ftrace/sync.proto
message SyncPtFtraceEvent {
@@ -5315,6 +5470,20 @@
// End of protos/perfetto/trace/ftrace/task.proto
+// Begin of protos/perfetto/trace/ftrace/tcp.proto
+
+message TcpRetransmitSkbFtraceEvent {
+ optional uint32 daddr = 1;
+ optional uint32 dport = 2;
+ optional uint32 saddr = 3;
+ optional uint64 skaddr = 4;
+ optional uint64 skbaddr = 5;
+ optional uint32 sport = 6;
+ optional int32 state = 7;
+}
+
+// End of protos/perfetto/trace/ftrace/tcp.proto
+
// Begin of protos/perfetto/trace/ftrace/thermal.proto
message ThermalTemperatureFtraceEvent {
@@ -5761,6 +5930,8 @@
RssStatThrottledFtraceEvent rss_stat_throttled = 359;
NetifReceiveSkbFtraceEvent netif_receive_skb = 360;
NetDevXmitFtraceEvent net_dev_xmit = 361;
+ InetSockSetStateFtraceEvent inet_sock_set_state = 362;
+ TcpRetransmitSkbFtraceEvent tcp_retransmit_skb = 363;
}
}
@@ -8660,6 +8831,11 @@
// One entry per device.
repeated DevfreqValue devfreq = 10;
+
+ // Cpu current frequency from
+ // /sys/devices/system/cpu/cpu*/cpufreq/cpuinfo_cur_freq in kHz.
+ // One entry per cpu. Report 0 for offline cpu
+ repeated uint32 cpufreq_khz = 11;
}
// End of protos/perfetto/trace/sys_stats/sys_stats.proto
diff --git a/protos/perfetto/trace/sys_stats/sys_stats.proto b/protos/perfetto/trace/sys_stats/sys_stats.proto
index 30edac0..5bd421f 100644
--- a/protos/perfetto/trace/sys_stats/sys_stats.proto
+++ b/protos/perfetto/trace/sys_stats/sys_stats.proto
@@ -103,4 +103,9 @@
// One entry per device.
repeated DevfreqValue devfreq = 10;
+
+ // Cpu current frequency from
+ // /sys/devices/system/cpu/cpu*/cpufreq/cpuinfo_cur_freq in kHz.
+ // One entry per cpu. Report 0 for offline cpu
+ repeated uint32 cpufreq_khz = 11;
}
diff --git a/protos/perfetto/trace_processor/trace_processor.proto b/protos/perfetto/trace_processor/trace_processor.proto
index 02a230f..2d19871 100644
--- a/protos/perfetto/trace_processor/trace_processor.proto
+++ b/protos/perfetto/trace_processor/trace_processor.proto
@@ -136,16 +136,16 @@
message QueryArgs {
optional string sql_query = 1;
- // Wall time when the query was queued. Used only for query stats.
- optional uint64 time_queued_ns = 2;
+ // Was time_queued_ns
+ reserved 2;
}
// Input for the /raw_query endpoint.
message RawQueryArgs {
optional string sql_query = 1;
- // Wall time when the query was queued. Used only for query stats.
- optional uint64 time_queued_ns = 2;
+ // Was time_queued_ns
+ reserved 2;
}
// Output for the /raw_query endpoint.
@@ -231,6 +231,12 @@
reserved 7;
}
repeated CellsBatch batch = 3;
+
+ // The number of statements in the provided SQL.
+ optional uint32 statement_count = 4;
+
+ // The number of statements which produced output rows in the provided SQL.
+ optional uint32 statement_with_output_count = 5;
}
// Input for the /status endpoint.
diff --git a/python/perfetto/trace_processor/metrics.descriptor b/python/perfetto/trace_processor/metrics.descriptor
index 7c82b50..6c95b59 100644
--- a/python/perfetto/trace_processor/metrics.descriptor
+++ b/python/perfetto/trace_processor/metrics.descriptor
Binary files differ
diff --git a/python/perfetto/trace_processor/metrics.descriptor.sha1 b/python/perfetto/trace_processor/metrics.descriptor.sha1
index fb38d4e..e77358d 100644
--- a/python/perfetto/trace_processor/metrics.descriptor.sha1
+++ b/python/perfetto/trace_processor/metrics.descriptor.sha1
@@ -2,5 +2,5 @@
// SHA1(tools/gen_binary_descriptors)
// c4a38769074f8a8c2ffbf514b267919b5f2d47df
// SHA1(protos/perfetto/metrics/metrics.proto)
-// 3b323de4f6dc7cbf8d725751627d3eb2b0fce8ce
+// 856f1a9caaf87f3c1d47eb5e64bec931fa713434
\ No newline at end of file
diff --git a/python/perfetto/trace_processor/trace_processor.descriptor b/python/perfetto/trace_processor/trace_processor.descriptor
index c7b0668..d8fe39e 100644
--- a/python/perfetto/trace_processor/trace_processor.descriptor
+++ b/python/perfetto/trace_processor/trace_processor.descriptor
Binary files differ
diff --git a/python/perfetto/trace_processor/trace_processor.descriptor.sha1 b/python/perfetto/trace_processor/trace_processor.descriptor.sha1
index 92ce69e..3d9e075 100644
--- a/python/perfetto/trace_processor/trace_processor.descriptor.sha1
+++ b/python/perfetto/trace_processor/trace_processor.descriptor.sha1
@@ -2,5 +2,5 @@
// SHA1(tools/gen_binary_descriptors)
// c4a38769074f8a8c2ffbf514b267919b5f2d47df
// SHA1(protos/perfetto/trace_processor/trace_processor.proto)
-// e303e1fc877a9fe4f8dd8413c03266ee68dfd3aa
+// ea89764637957b3b71978972257aba2f0fddbfc9
\ No newline at end of file
diff --git a/src/android_internal/health_hal.cc b/src/android_internal/health_hal.cc
index d3e0de0..f21af80 100644
--- a/src/android_internal/health_hal.cc
+++ b/src/android_internal/health_hal.cc
@@ -16,8 +16,8 @@
#include "src/android_internal/health_hal.h"
-#include <android/binder_manager.h>
#include <aidl/android/hardware/health/IHealth.h>
+#include <android/binder_manager.h>
#include <android/hardware/health/2.0/IHealth.h>
#include <healthhalutils/HealthHalUtils.h>
diff --git a/src/android_internal/tracing_service_proxy.cc b/src/android_internal/tracing_service_proxy.cc
index e297b75..a886689 100644
--- a/src/android_internal/tracing_service_proxy.cc
+++ b/src/android_internal/tracing_service_proxy.cc
@@ -17,21 +17,29 @@
#include "src/android_internal/tracing_service_proxy.h"
#include <android/tracing/ITracingServiceProxy.h>
+#include <android/tracing/TraceReportParams.h>
#include <binder/IBinder.h>
#include <binder/IServiceManager.h>
+#include <binder/ParcelFileDescriptor.h>
#include <binder/Status.h>
+#include <utils/String16.h>
namespace perfetto {
namespace android_internal {
using android::sp;
using android::binder::Status;
+using android::os::ParcelFileDescriptor;
using android::tracing::ITracingServiceProxy;
+using android::tracing::TraceReportParams;
+
+namespace {
+static constexpr char kServiceName[] = "tracing.proxy";
+}
bool NotifyTraceSessionEnded(bool session_stolen) {
- sp<ITracingServiceProxy> service = android::interface_cast<ITracingServiceProxy>(
- android::defaultServiceManager()->getService(android::String16("tracing.proxy")));
-
+ auto service = android::waitForService<ITracingServiceProxy>(
+ android::String16(kServiceName));
if (service == nullptr) {
return false;
}
@@ -40,5 +48,38 @@
return s.isOk();
}
-} // namespace android_internal
-} // namespace perfetto
+bool ReportTrace(const char* reporter_package_name,
+ const char* reporter_class_name,
+ int owned_trace_fd,
+ int64_t uuid_lsb,
+ int64_t uuid_msb,
+ bool use_pipe_in_framework_for_testing) {
+ // Keep this first so we recapture the raw fd in a RAII type as soon as
+ // possible.
+ android::base::unique_fd fd(owned_trace_fd);
+
+ auto service = android::waitForService<ITracingServiceProxy>(
+ android::String16(kServiceName));
+ if (service == nullptr) {
+ return false;
+ }
+
+ TraceReportParams params{};
+ params.reporterPackageName = android::String16(reporter_package_name);
+ params.reporterClassName = android::String16(reporter_class_name);
+ params.fd = ParcelFileDescriptor(std::move(fd));
+ params.uuidLsb = uuid_lsb;
+ params.uuidMsb = uuid_msb;
+ params.usePipeForTesting = use_pipe_in_framework_for_testing;
+
+ Status s = service->reportTrace(std::move(params));
+ if (!s.isOk()) {
+ __android_log_print(ANDROID_LOG_ERROR, "perfetto", "reportTrace failed: %s",
+ s.toString8().c_str());
+ }
+
+ return s.isOk();
+}
+
+} // namespace android_internal
+} // namespace perfetto
diff --git a/src/android_internal/tracing_service_proxy.h b/src/android_internal/tracing_service_proxy.h
index 0b045e2..5c45934 100644
--- a/src/android_internal/tracing_service_proxy.h
+++ b/src/android_internal/tracing_service_proxy.h
@@ -17,6 +17,8 @@
#ifndef SRC_ANDROID_INTERNAL_TRACING_SERVICE_PROXY_H_
#define SRC_ANDROID_INTERNAL_TRACING_SERVICE_PROXY_H_
+#include <stdint.h>
+
namespace perfetto {
namespace android_internal {
@@ -25,6 +27,14 @@
bool __attribute__((visibility("default")))
NotifyTraceSessionEnded(bool session_stolen);
+bool __attribute__((visibility("default")))
+ReportTrace(const char* reporter_package_name,
+ const char* reporter_class_name,
+ int owned_trace_fd,
+ int64_t uuid_lsb,
+ int64_t uuid_msb,
+ bool use_pipe_in_framework_for_testing);
+
} // extern "C"
} // namespace android_internal
diff --git a/src/android_stats/perfetto_atoms.h b/src/android_stats/perfetto_atoms.h
index 0a857b9..2f48055 100644
--- a/src/android_stats/perfetto_atoms.h
+++ b/src/android_stats/perfetto_atoms.h
@@ -82,19 +82,17 @@
// Deprecated as "success" is misleading; it simply means we were
// able to communicate with incidentd. Will be removed once
- // incidentd is properly instrumented.
+ // incidentd is no longer used.
kUploadIncidentSuccess = 9,
- // Deprecated as has the potential to be too spammy. Will be
- // replaced with a whole new atom proto which uses a count metric
- // instead of the event metric used for this proto.
- kTriggerBegin = 12,
- kTriggerSuccess = 13,
- kTriggerFailure = 14,
+ // Contained trigger begin/success/failure. Replaced by
+ // |PerfettoTriggerAtom| to allow aggregation using a count metric
+ // and reduce spam.
+ // reserved 12, 13, 14;
- // Deprecated as too coarse grained to be useful. Will be replaced
- // with better broken down atoms as we do with traced.
- kHitGuardrails = 15,
+ // Contained that a guardrail in perfetto_cmd was hit. Replaced with
+ // kCmd* guardrails.
+ // reserved 15;
// Contained status of Dropbox uploads. Removed as Perfetto no
// longer supports uploading traces using Dropbox.
diff --git a/src/base/utils.cc b/src/base/utils.cc
index 363a0d6..b4da80c 100644
--- a/src/base/utils.cc
+++ b/src/base/utils.cc
@@ -94,9 +94,9 @@
const bool have_sse4_2 = ecx & (1u << 20);
const bool have_avx =
// Does the OS save/restore XMM and YMM state?
- ((GetXCR0EAX() & xcr0_avx_mask) == xcr0_avx_mask) &&
(ecx & (1u << 27)) && // OS support XGETBV.
- (ecx & (1u << 28)); // AVX supported in hardware
+ (ecx & (1u << 28)) && // AVX supported in hardware
+ ((GetXCR0EAX() & xcr0_avx_mask) == xcr0_avx_mask);
if (!have_sse4_2 || !have_popcnt || !have_avx) {
fprintf(
diff --git a/src/perfetto_cmd/perfetto_cmd.cc b/src/perfetto_cmd/perfetto_cmd.cc
index dc0d61a..ed57c99 100644
--- a/src/perfetto_cmd/perfetto_cmd.cc
+++ b/src/perfetto_cmd/perfetto_cmd.cc
@@ -568,29 +568,66 @@
uuid_ = uuid.ToString();
}
- if (!trace_config_->incident_report_config().destination_package().empty() &&
- !upload_flag_) {
+ bool has_incidentd_package =
+ !trace_config_->incident_report_config().destination_package().empty();
+ if (has_incidentd_package && !upload_flag_) {
PERFETTO_ELOG(
"Unexpected IncidentReportConfig without --dropbox / --upload.");
return 1;
}
+ bool has_android_reporter_package = !trace_config_->android_report_config()
+ .reporter_service_package()
+ .empty();
+ if (has_android_reporter_package && !upload_flag_) {
+ PERFETTO_ELOG(
+ "Unexpected AndroidReportConfig without --dropbox / --upload.");
+ return 1;
+ }
+
+ if (has_incidentd_package && has_android_reporter_package) {
+ PERFETTO_ELOG(
+ "Only one of IncidentReportConfig and AndroidReportConfig "
+ "allowed in the same config.");
+ return 1;
+ }
+
+ // If the upload flag is set, we can only be doing one of three things:
+ // 1. Reporting to either incidentd or Android framework.
+ // 2. Skipping incidentd/Android report because it was explicitly
+ // specified in the config.
+ // 3. Activating triggers.
+ bool incidentd_valid =
+ has_incidentd_package ||
+ trace_config_->incident_report_config().skip_incidentd();
+ bool android_report_valid =
+ has_android_reporter_package ||
+ trace_config_->android_report_config().skip_report();
+ bool has_triggers = !trace_config_->activate_triggers().empty();
+ if (upload_flag_ && !incidentd_valid && !android_report_valid &&
+ !has_triggers) {
+ PERFETTO_ELOG(
+ "One of IncidentReportConfig, AndroidReportConfig or activate_triggers "
+ "must be specified with --dropbox / --upload.");
+ return 1;
+ }
+
// Only save to incidentd if:
- // 1) --upload is set
+ // 1) |destination_package| is set
// 2) |skip_incidentd| is absent or false.
// 3) we are not simply activating triggers.
save_to_incidentd_ =
- upload_flag_ &&
+ has_incidentd_package &&
!trace_config_->incident_report_config().skip_incidentd() &&
- trace_config_->activate_triggers().empty();
+ !has_triggers;
- if (save_to_incidentd_ &&
- trace_config_->incident_report_config().destination_package().empty()) {
- PERFETTO_ELOG(
- "Missing IncidentReportConfig.destination_package with --dropbox / "
- "--upload.");
- return 1;
- }
+ // Only report to the Andorid framework if:
+ // 1) |reporter_service_package| is set
+ // 2) |skip_report| is absent or false.
+ // 3) we are not simply activating triggers.
+ report_to_android_framework_ =
+ has_android_reporter_package &&
+ !trace_config_->android_report_config().skip_report() && !has_triggers;
// Respect the wishes of the config with respect to statsd logging or fall
// back on the presence of the --upload flag if not set.
@@ -640,7 +677,7 @@
// In this case we don't intend to send any trace config to the service,
// rather use that as a signal to the cmdline client to connect as a producer
// and activate triggers.
- if (!trace_config_->activate_triggers().empty()) {
+ if (has_triggers) {
for (const auto& trigger : trace_config_->activate_triggers()) {
triggers_to_activate_.push_back(trigger);
}
@@ -698,13 +735,21 @@
}
}
- if (save_to_incidentd_ && !ignore_guardrails_ &&
- (trace_config_->duration_ms() == 0 &&
- trace_config_->trigger_config().trigger_timeout_ms() == 0)) {
+ bool will_trace_indefinitely =
+ trace_config_->duration_ms() == 0 &&
+ trace_config_->trigger_config().trigger_timeout_ms() == 0;
+ if (will_trace_indefinitely && save_to_incidentd_ && !ignore_guardrails_) {
PERFETTO_ELOG("Can't trace indefinitely when tracing to Incidentd.");
return 1;
}
+ if (will_trace_indefinitely && report_to_android_framework_ &&
+ !ignore_guardrails_) {
+ PERFETTO_ELOG(
+ "Can't trace indefinitely when reporting to Android framework.");
+ return 1;
+ }
+
if (background_) {
if (background_wait_) {
#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
@@ -786,7 +831,6 @@
// connect as a consumer or run the trace. So bail out after processing all
// the options.
if (!triggers_to_activate_.empty()) {
- LogUploadEvent(PerfettoStatsdAtom::kTriggerBegin);
LogTriggerEvents(PerfettoTriggerAtom::kCmdTrigger, triggers_to_activate_);
bool finished_with_success = false;
@@ -798,10 +842,7 @@
},
&triggers_to_activate_);
task_runner_.Run();
- if (finished_with_success) {
- LogUploadEvent(PerfettoStatsdAtom::kTriggerSuccess);
- } else {
- LogUploadEvent(PerfettoStatsdAtom::kTriggerFailure);
+ if (!finished_with_success) {
LogTriggerEvents(PerfettoTriggerAtom::kCmdTriggerFail,
triggers_to_activate_);
}
@@ -817,7 +858,7 @@
RateLimiter::Args args{};
args.is_user_build = IsUserBuild();
- args.is_uploading = save_to_incidentd_;
+ args.is_uploading = save_to_incidentd_ || report_to_android_framework_;
args.current_time = base::GetWallTimeS();
args.ignore_guardrails = ignore_guardrails_;
args.allow_user_build_tracing = trace_config_->allow_user_build_tracing();
@@ -846,8 +887,6 @@
auto err_atom = ConvertRateLimiterResponseToAtom(limiter_->ShouldTrace(args));
if (err_atom) {
- // TODO(lalitm): remove this once we're ready on server side.
- LogUploadEvent(PerfettoStatsdAtom::kHitGuardrails);
LogUploadEvent(err_atom.value());
return 1;
}
@@ -1027,7 +1066,11 @@
if (save_to_incidentd_) {
#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
- SaveTraceIntoDropboxAndIncidentOrCrash();
+ SaveTraceIntoIncidentOrCrash();
+#endif
+ } else if (report_to_android_framework_) {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+ ReportTraceToAndroidFrameworkOrCrash();
#endif
} else {
trace_out_stream_.reset();
@@ -1137,43 +1180,86 @@
return;
}
- printf("Not meant for machine consumption. Use --query-raw for scripts.\n");
+ printf(
+ "\x1b[31mNot meant for machine consumption. Use --query-raw for "
+ "scripts.\x1b[0m\n\n");
+ printf(
+ "Service: %s\n"
+ "Tracing sessions: %d (started: %d)\n",
+ svc_state.tracing_service_version().c_str(), svc_state.num_sessions(),
+ svc_state.num_sessions_started());
+ printf(R"(
+
+PRODUCER PROCESSES CONNECTED:
+
+ID PID UID NAME SDK
+== === === ==== ===
+)");
for (const auto& producer : svc_state.producers()) {
- printf("producers: {\n");
- printf(" id: %d\n", producer.id());
- printf(" name: \"%s\" \n", producer.name().c_str());
- printf(" uid: %d \n", producer.uid());
- printf(" sdk_version: \"%s\" \n", producer.sdk_version().c_str());
- printf("}\n");
+ printf("%-10d %-10d %-10d %-32s %s\n", producer.id(), producer.pid(),
+ producer.uid(), producer.name().c_str(),
+ producer.sdk_version().c_str());
}
+ printf(R"(
+
+DATA SOURCES REGISTERED:
+
+NAME PRODUCER DETAILS
+=== ======== ========
+)");
for (const auto& ds : svc_state.data_sources()) {
- printf("data_sources: {\n");
- printf(" producer_id: %d\n", ds.producer_id());
- printf(" descriptor: {\n");
- printf(" name: \"%s\"\n", ds.ds_descriptor().name().c_str());
+ char producer_id_and_name[128]{};
+ const int ds_producer_id = ds.producer_id();
+ for (const auto& producer : svc_state.producers()) {
+ if (producer.id() == ds_producer_id) {
+ base::SprintfTrunc(producer_id_and_name, sizeof(producer_id_and_name),
+ "%s (%d)", producer.name().c_str(), ds_producer_id);
+ break;
+ }
+ }
+
+ printf("%-40s %-40s ", ds.ds_descriptor().name().c_str(),
+ producer_id_and_name);
+ // Print the category names for clients using the track event SDK.
if (!ds.ds_descriptor().track_event_descriptor_raw().empty()) {
auto raw = ds.ds_descriptor().track_event_descriptor_raw();
perfetto::protos::gen::TrackEventDescriptor desc;
if (desc.ParseFromArray(raw.data(), raw.size())) {
- printf(" track_event_descriptor: {\n");
for (const auto& cat : desc.available_categories()) {
- printf(" available_categories: {\n");
- printf(" name: \"%s\"\n", cat.name().c_str());
- printf(" description: \"%s\"\n", cat.description().c_str());
- printf(" }\n");
+ printf("%s,", cat.name().c_str());
}
- printf(" }\n");
}
}
- printf(" }\n");
- printf("}\n");
- }
- printf("tracing_service_version: \"%s\"\n",
- svc_state.tracing_service_version().c_str());
- printf("num_sessions: %d\n", svc_state.num_sessions());
- printf("num_sessions_started: %d\n", svc_state.num_sessions_started());
+ printf("\n");
+ } // for data_sources()
+
+ if (svc_state.supports_tracing_sessions()) {
+ printf(R"(
+
+TRACING SESSIONS:
+
+ID UID STATE NAME BUF (#) KB DUR (s) #DS STARTED
+=== === ===== ==== ========== ======= === =======
+)");
+ for (const auto& sess : svc_state.tracing_sessions()) {
+ uint32_t buf_tot_kb = 0;
+ for (uint32_t kb : sess.buffer_size_kb())
+ buf_tot_kb += kb;
+ int sec =
+ static_cast<int>((sess.start_realtime_ns() / 1000000000) % 86400);
+ int h = sec / 3600;
+ int m = (sec - (h * 3600)) / 60;
+ int s = (sec - h * 3600 - m * 60);
+ printf("%-7" PRIu64
+ " %-7d %-10s %-12s (%d) %-8u %-9u %-4u %02d:%02d:%02d\n",
+ sess.id(), sess.consumer_uid(), sess.state().c_str(),
+ sess.unique_session_name().c_str(), sess.buffer_size_kb_size(),
+ buf_tot_kb, sess.duration_ms() / 1000, sess.num_data_sources(), h,
+ m, s);
+ } // for tracing_sessions()
+ } // if (supports_tracing_sessions)
}
void PerfettoCmd::OnObservableEvents(
diff --git a/src/perfetto_cmd/perfetto_cmd.h b/src/perfetto_cmd/perfetto_cmd.h
index 9f62f51..28c7733 100644
--- a/src/perfetto_cmd/perfetto_cmd.h
+++ b/src/perfetto_cmd/perfetto_cmd.h
@@ -115,8 +115,9 @@
#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
static base::ScopedFile CreateUnlinkedTmpFile();
- void SaveTraceIntoDropboxAndIncidentOrCrash();
+ void SaveTraceIntoIncidentOrCrash();
void SaveOutputToIncidentTraceOrCrash();
+ void ReportTraceToAndroidFrameworkOrCrash();
#endif
void LogUploadEvent(PerfettoStatsdAtom atom);
void LogTriggerEvents(PerfettoTriggerAtom atom,
@@ -135,6 +136,7 @@
base::EventFd ctrl_c_evt_;
base::Pipe background_wait_pipe_;
bool save_to_incidentd_ = false;
+ bool report_to_android_framework_ = false;
bool statsd_logging_ = false;
bool update_guardrail_state_ = false;
uint64_t bytes_written_ = 0;
diff --git a/src/perfetto_cmd/perfetto_cmd_android.cc b/src/perfetto_cmd/perfetto_cmd_android.cc
index c7953b1..6a2d949 100644
--- a/src/perfetto_cmd/perfetto_cmd_android.cc
+++ b/src/perfetto_cmd/perfetto_cmd_android.cc
@@ -23,10 +23,12 @@
#include "perfetto/base/build_config.h"
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/string_utils.h"
#include "perfetto/ext/base/uuid.h"
#include "perfetto/tracing/core/trace_config.h"
#include "src/android_internal/incident_service.h"
#include "src/android_internal/lazy_library_loader.h"
+#include "src/android_internal/tracing_service_proxy.h"
namespace perfetto {
namespace {
@@ -35,10 +37,12 @@
} // namespace
-void PerfettoCmd::SaveTraceIntoDropboxAndIncidentOrCrash() {
+void PerfettoCmd::SaveTraceIntoIncidentOrCrash() {
PERFETTO_CHECK(save_to_incidentd_);
- PERFETTO_CHECK(
- !trace_config_->incident_report_config().destination_package().empty());
+
+ const auto& cfg = trace_config_->incident_report_config();
+ PERFETTO_CHECK(!cfg.destination_package().empty());
+ PERFETTO_CHECK(!cfg.skip_incidentd());
if (bytes_written_ == 0) {
LogUploadEvent(PerfettoStatsdAtom::kNotUploadingEmptyTrace);
@@ -52,7 +56,7 @@
// Skip the trace-uuid link for traces that are too small. Realistically those
// traces contain only a marker (e.g. seized_for_bugreport, or the trace
// expired without triggers). Those are useless and introduce only noise.
- if (!uuid_.empty() && bytes_written_ > 4096) {
+ if (bytes_written_ > 4096) {
base::Uuid uuid(uuid_);
PERFETTO_LOG("go/trace-uuid/%s name=\"%s\" size=%" PRIu64,
uuid.ToPrettyString().c_str(),
@@ -61,13 +65,53 @@
// Ask incidentd to create a report, which will read the file we just
// wrote.
- const auto& cfg = trace_config_->incident_report_config();
PERFETTO_LAZY_LOAD(android_internal::StartIncidentReport, incident_fn);
PERFETTO_CHECK(incident_fn(cfg.destination_package().c_str(),
cfg.destination_class().c_str(),
cfg.privacy_level()));
}
+void PerfettoCmd::ReportTraceToAndroidFrameworkOrCrash() {
+ PERFETTO_CHECK(report_to_android_framework_);
+ PERFETTO_CHECK(trace_out_stream_);
+
+ const auto& cfg = trace_config_->android_report_config();
+ PERFETTO_CHECK(!cfg.reporter_service_package().empty());
+ PERFETTO_CHECK(!cfg.skip_report());
+
+ if (bytes_written_ == 0) {
+ // TODO(lalitm): change this to a dedicated atom decoupled from
+ // incidentd.
+ LogUploadEvent(PerfettoStatsdAtom::kNotUploadingEmptyTrace);
+ PERFETTO_LOG("Skipping reporting trace to Android. Empty trace.");
+ return;
+ }
+
+ // TODO(lalitm): add atom for beginning report here.
+ base::StackString<128> self_fd("/proc/self/fd/%d",
+ fileno(*trace_out_stream_));
+ base::ScopedFile fd(base::OpenFile(self_fd.c_str(), O_RDONLY | O_CLOEXEC));
+ if (!fd) {
+ PERFETTO_FATAL("Failed to dup fd when reporting to Android");
+ }
+
+ base::Uuid uuid(uuid_);
+ PERFETTO_LAZY_LOAD(android_internal::ReportTrace, report_fn);
+ PERFETTO_CHECK(report_fn(cfg.reporter_service_package().c_str(),
+ cfg.reporter_service_class().c_str(), fd.release(),
+ uuid.lsb(), uuid.msb(),
+ cfg.use_pipe_in_framework_for_testing()));
+
+ // Skip the trace-uuid link for traces that are too small. Realistically those
+ // traces contain only a marker (e.g. seized_for_bugreport, or the trace
+ // expired without triggers). Those are useless and introduce only noise.
+ if (bytes_written_ > 4096) {
+ PERFETTO_LOG("go/trace-uuid/%s name=\"%s\" size=%" PRIu64,
+ uuid.ToPrettyString().c_str(),
+ trace_config_->unique_session_name().c_str(), bytes_written_);
+ }
+}
+
// Open a staging file (unlinking the previous instance), copy the trace
// contents over, then rename to a final hardcoded path (known to incidentd).
// Such tracing sessions should not normally overlap. We do not use unique
diff --git a/src/profiling/common/producer_support.cc b/src/profiling/common/producer_support.cc
index 90eba38..df8316b 100644
--- a/src/profiling/common/producer_support.cc
+++ b/src/profiling/common/producer_support.cc
@@ -56,11 +56,18 @@
constexpr auto kAidAppStart = 10000; // AID_APP_START
constexpr auto kAidAppEnd = 19999; // AID_APP_END
constexpr auto kAidUserOffset = 100000; // AID_USER_OFFSET
+ constexpr auto kAidSystem = 1000; // AID_SYSTEM
if (!build_type.empty() && build_type != "user") {
return true;
}
+ // TODO(b/217368496): remove this.
+ if (uid == kAidSystem) {
+ return ds_config.session_initiator() ==
+ DataSourceConfig::SESSION_INITIATOR_TRUSTED_SYSTEM;
+ }
+
uint64_t uid_without_profile = uid % kAidUserOffset;
if (uid_without_profile < kAidAppStart || kAidAppEnd < uid_without_profile) {
// TODO(fmayer): relax this.
diff --git a/src/profiling/perf/event_config.cc b/src/profiling/perf/event_config.cc
index c45cb0b..3759533 100644
--- a/src/profiling/perf/event_config.cc
+++ b/src/profiling/perf/event_config.cc
@@ -168,6 +168,38 @@
return PerfCounter::BuiltinCounter(name, PerfEvents::SW_PAGE_FAULTS,
PERF_TYPE_SOFTWARE,
PERF_COUNT_SW_PAGE_FAULTS);
+ case PerfEvents::SW_TASK_CLOCK:
+ return PerfCounter::BuiltinCounter(name, PerfEvents::SW_TASK_CLOCK,
+ PERF_TYPE_SOFTWARE,
+ PERF_COUNT_SW_TASK_CLOCK);
+ case PerfEvents::SW_CONTEXT_SWITCHES:
+ return PerfCounter::BuiltinCounter(name, PerfEvents::SW_CONTEXT_SWITCHES,
+ PERF_TYPE_SOFTWARE,
+ PERF_COUNT_SW_CONTEXT_SWITCHES);
+ case PerfEvents::SW_CPU_MIGRATIONS:
+ return PerfCounter::BuiltinCounter(name, PerfEvents::SW_CPU_MIGRATIONS,
+ PERF_TYPE_SOFTWARE,
+ PERF_COUNT_SW_CPU_MIGRATIONS);
+ case PerfEvents::SW_PAGE_FAULTS_MIN:
+ return PerfCounter::BuiltinCounter(name, PerfEvents::SW_PAGE_FAULTS_MIN,
+ PERF_TYPE_SOFTWARE,
+ PERF_COUNT_SW_PAGE_FAULTS_MIN);
+ case PerfEvents::SW_PAGE_FAULTS_MAJ:
+ return PerfCounter::BuiltinCounter(name, PerfEvents::SW_PAGE_FAULTS_MAJ,
+ PERF_TYPE_SOFTWARE,
+ PERF_COUNT_SW_PAGE_FAULTS_MAJ);
+ case PerfEvents::SW_ALIGNMENT_FAULTS:
+ return PerfCounter::BuiltinCounter(name, PerfEvents::SW_ALIGNMENT_FAULTS,
+ PERF_TYPE_SOFTWARE,
+ PERF_COUNT_SW_ALIGNMENT_FAULTS);
+ case PerfEvents::SW_EMULATION_FAULTS:
+ return PerfCounter::BuiltinCounter(name, PerfEvents::SW_EMULATION_FAULTS,
+ PERF_TYPE_SOFTWARE,
+ PERF_COUNT_SW_EMULATION_FAULTS);
+ case PerfEvents::SW_DUMMY:
+ return PerfCounter::BuiltinCounter(
+ name, PerfEvents::SW_DUMMY, PERF_TYPE_SOFTWARE, PERF_COUNT_SW_DUMMY);
+
case PerfEvents::HW_CPU_CYCLES:
return PerfCounter::BuiltinCounter(name, PerfEvents::HW_CPU_CYCLES,
PERF_TYPE_HARDWARE,
@@ -176,6 +208,39 @@
return PerfCounter::BuiltinCounter(name, PerfEvents::HW_INSTRUCTIONS,
PERF_TYPE_HARDWARE,
PERF_COUNT_HW_INSTRUCTIONS);
+ case PerfEvents::HW_CACHE_REFERENCES:
+ return PerfCounter::BuiltinCounter(name, PerfEvents::HW_CACHE_REFERENCES,
+ PERF_TYPE_HARDWARE,
+ PERF_COUNT_HW_CACHE_REFERENCES);
+ case PerfEvents::HW_CACHE_MISSES:
+ return PerfCounter::BuiltinCounter(name, PerfEvents::HW_CACHE_MISSES,
+ PERF_TYPE_HARDWARE,
+ PERF_COUNT_HW_CACHE_MISSES);
+ case PerfEvents::HW_BRANCH_INSTRUCTIONS:
+ return PerfCounter::BuiltinCounter(
+ name, PerfEvents::HW_BRANCH_INSTRUCTIONS, PERF_TYPE_HARDWARE,
+ PERF_COUNT_HW_BRANCH_INSTRUCTIONS);
+ case PerfEvents::HW_BRANCH_MISSES:
+ return PerfCounter::BuiltinCounter(name, PerfEvents::HW_BRANCH_MISSES,
+ PERF_TYPE_HARDWARE,
+ PERF_COUNT_HW_BRANCH_MISSES);
+ case PerfEvents::HW_BUS_CYCLES:
+ return PerfCounter::BuiltinCounter(name, PerfEvents::HW_BUS_CYCLES,
+ PERF_TYPE_HARDWARE,
+ PERF_COUNT_HW_BUS_CYCLES);
+ case PerfEvents::HW_STALLED_CYCLES_FRONTEND:
+ return PerfCounter::BuiltinCounter(
+ name, PerfEvents::HW_STALLED_CYCLES_FRONTEND, PERF_TYPE_HARDWARE,
+ PERF_COUNT_HW_STALLED_CYCLES_FRONTEND);
+ case PerfEvents::HW_STALLED_CYCLES_BACKEND:
+ return PerfCounter::BuiltinCounter(
+ name, PerfEvents::HW_STALLED_CYCLES_BACKEND, PERF_TYPE_HARDWARE,
+ PERF_COUNT_HW_STALLED_CYCLES_BACKEND);
+ case PerfEvents::HW_REF_CPU_CYCLES:
+ return PerfCounter::BuiltinCounter(name, PerfEvents::HW_REF_CPU_CYCLES,
+ PERF_TYPE_HARDWARE,
+ PERF_COUNT_HW_REF_CPU_CYCLES);
+
default:
PERFETTO_ELOG("Unrecognised PerfEvents::Counter enum value: %zu",
static_cast<size_t>(pb_enum));
diff --git a/src/profiling/symbolizer/local_symbolizer.cc b/src/profiling/symbolizer/local_symbolizer.cc
index bcacb37..1fe4dbd 100644
--- a/src/profiling/symbolizer/local_symbolizer.cc
+++ b/src/profiling/symbolizer/local_symbolizer.cc
@@ -423,8 +423,8 @@
}
if (base::StartsWith(filename, kApkPrefix)) {
- symbol_file =
- root_str + "/" + dirname + "/" + filename.substr(sizeof(kApkPrefix));
+ symbol_file = root_str + "/" + dirname + "/" +
+ filename.substr(sizeof(kApkPrefix) - 1);
result = IsCorrectFile(symbol_file, build_id);
if (result) {
return result;
@@ -438,7 +438,7 @@
}
if (base::StartsWith(filename, kApkPrefix)) {
- symbol_file = root_str + "/" + filename.substr(sizeof(kApkPrefix));
+ symbol_file = root_str + "/" + filename.substr(sizeof(kApkPrefix) - 1);
result = IsCorrectFile(symbol_file, build_id);
if (result) {
return result;
diff --git a/src/profiling/symbolizer/local_symbolizer_unittest.cc b/src/profiling/symbolizer/local_symbolizer_unittest.cc
index 921c878..cb0c11d 100644
--- a/src/profiling/symbolizer/local_symbolizer_unittest.cc
+++ b/src/profiling/symbolizer/local_symbolizer_unittest.cc
@@ -162,6 +162,79 @@
EXPECT_EQ(bin2.value().file_name, tmp.path() + "/dir2/elf1");
}
+TEST(LocalBinaryFinderTest, AbsolutePath) {
+ base::TmpDirTree tmp;
+ tmp.AddDir("root");
+ tmp.AddDir("root/dir");
+ tmp.AddFile("root/dir/elf1.so", CreateElfWithBuildId("AAAAAAAAAAAAAAAAAAAA"));
+
+ LocalBinaryFinder finder({tmp.path() + "/root"});
+
+ base::Optional<FoundBinary> bin1 =
+ finder.FindBinary("/dir/elf1.so", "AAAAAAAAAAAAAAAAAAAA");
+ ASSERT_TRUE(bin1.has_value());
+ EXPECT_EQ(bin1.value().file_name, tmp.path() + "/root/dir/elf1.so");
+}
+
+TEST(LocalBinaryFinderTest, AbsolutePathWithoutBaseApk) {
+ base::TmpDirTree tmp;
+ tmp.AddDir("root");
+ tmp.AddDir("root/dir");
+ tmp.AddFile("root/dir/elf1.so", CreateElfWithBuildId("AAAAAAAAAAAAAAAAAAAA"));
+
+ LocalBinaryFinder finder({tmp.path() + "/root"});
+
+ base::Optional<FoundBinary> bin1 =
+ finder.FindBinary("/dir/base.apk!elf1.so", "AAAAAAAAAAAAAAAAAAAA");
+ ASSERT_TRUE(bin1.has_value());
+ EXPECT_EQ(bin1.value().file_name, tmp.path() + "/root/dir/elf1.so");
+}
+
+TEST(LocalBinaryFinderTest, OnlyFilename) {
+ base::TmpDirTree tmp;
+ tmp.AddDir("root");
+ tmp.AddFile("root/elf1.so", CreateElfWithBuildId("AAAAAAAAAAAAAAAAAAAA"));
+
+ LocalBinaryFinder finder({tmp.path() + "/root"});
+
+ base::Optional<FoundBinary> bin1 =
+ finder.FindBinary("/ignored_dir/elf1.so", "AAAAAAAAAAAAAAAAAAAA");
+ ASSERT_TRUE(bin1.has_value());
+ EXPECT_EQ(bin1.value().file_name, tmp.path() + "/root/elf1.so");
+}
+
+TEST(LocalBinaryFinderTest, OnlyFilenameWithoutBaseApk) {
+ base::TmpDirTree tmp;
+ tmp.AddDir("root");
+ tmp.AddFile("root/elf1.so", CreateElfWithBuildId("AAAAAAAAAAAAAAAAAAAA"));
+
+ LocalBinaryFinder finder({tmp.path() + "/root"});
+
+ base::Optional<FoundBinary> bin1 = finder.FindBinary(
+ "/ignored_dir/base.apk!elf1.so", "AAAAAAAAAAAAAAAAAAAA");
+ ASSERT_TRUE(bin1.has_value());
+ EXPECT_EQ(bin1.value().file_name, tmp.path() + "/root/elf1.so");
+}
+
+TEST(LocalBinaryFinderTest, BuildIdSubdir) {
+ base::TmpDirTree tmp;
+ tmp.AddDir("root");
+ tmp.AddDir("root/.build-id");
+ tmp.AddDir("root/.build-id/41");
+ tmp.AddFile("root/.build-id/41/41414141414141414141414141414141414141.debug",
+ CreateElfWithBuildId("AAAAAAAAAAAAAAAAAAAA"));
+
+ LocalBinaryFinder finder({tmp.path() + "/root"});
+
+ base::Optional<FoundBinary> bin1 =
+ finder.FindBinary("/ignored_dir/ignored_name.so", "AAAAAAAAAAAAAAAAAAAA");
+ ASSERT_TRUE(bin1.has_value());
+ EXPECT_EQ(
+ bin1.value().file_name,
+ tmp.path() +
+ "/root/.build-id/41/41414141414141414141414141414141414141.debug");
+}
+
} // namespace
} // namespace profiling
} // namespace perfetto
diff --git a/src/trace_processor/containers/nullable_vector.h b/src/trace_processor/containers/nullable_vector.h
index 2a623c6..2fed430 100644
--- a/src/trace_processor/containers/nullable_vector.h
+++ b/src/trace_processor/containers/nullable_vector.h
@@ -86,15 +86,15 @@
base::Optional<T> Get(uint32_t idx) const {
if (mode_ == Mode::kDense) {
bool contains = valid_.Contains(idx);
- return contains ? base::Optional<T>(data_[idx]) : base::nullopt;
+ return contains ? base::make_optional(data_[idx]) : base::nullopt;
} else {
- auto opt_idx = valid_.IndexOf(idx);
- return opt_idx ? base::Optional<T>(data_[*opt_idx]) : base::nullopt;
+ auto opt_row = valid_.RowOf(idx);
+ return opt_row ? base::make_optional(data_[*opt_row]) : base::nullopt;
}
}
- // Returns the non-null value at |ordinal| where |ordinal| gives the index
- // of the entry in-terms of non-null entries only.
+ // Returns the non-null value at |non_null_idx| where |non_null_idx| gives the
+ // index of the entry in-terms of non-null entries only.
//
// For example:
// this = [0, null, 2, null, 4]
@@ -103,12 +103,12 @@
// GetNonNull(1) = 2
// GetNonNull(2) = 4
// ...
- T GetNonNull(uint32_t ordinal) const {
+ T GetNonNull(uint32_t non_null_idx) const {
if (mode_ == Mode::kDense) {
- return data_[valid_.Get(ordinal)];
+ return data_[valid_.Get(non_null_idx)];
} else {
- PERFETTO_DCHECK(ordinal < data_.size());
- return data_[ordinal];
+ PERFETTO_DCHECK(non_null_idx < data_.size());
+ return data_[non_null_idx];
}
}
@@ -143,18 +143,18 @@
}
data_[idx] = val;
} else {
- auto opt_idx = valid_.IndexOf(idx);
+ auto opt_row = valid_.RowOf(idx);
// Generally, we will be setting a null row to non-null so optimize for
// that path.
- if (PERFETTO_UNLIKELY(opt_idx)) {
- data_[*opt_idx] = val;
+ if (PERFETTO_UNLIKELY(opt_row)) {
+ data_[*opt_row] = val;
} else {
valid_.Insert(idx);
- opt_idx = valid_.IndexOf(idx);
- PERFETTO_DCHECK(opt_idx);
- data_.insert(data_.begin() + static_cast<ptrdiff_t>(*opt_idx), val);
+ opt_row = valid_.RowOf(idx);
+ PERFETTO_DCHECK(opt_row);
+ data_.insert(data_.begin() + static_cast<ptrdiff_t>(*opt_row), val);
}
}
}
@@ -166,7 +166,7 @@
bool IsDense() const { return mode_ == Mode::kDense; }
private:
- NullableVector(Mode mode) : mode_(mode) {}
+ explicit NullableVector(Mode mode) : mode_(mode) {}
Mode mode_ = Mode::kSparse;
diff --git a/src/trace_processor/containers/row_map.cc b/src/trace_processor/containers/row_map.cc
index 297a0ef..ceacf95 100644
--- a/src/trace_processor/containers/row_map.cc
+++ b/src/trace_processor/containers/row_map.cc
@@ -146,8 +146,8 @@
RowMap::RowMap(uint32_t start, uint32_t end, OptimizeFor optimize_for)
: mode_(Mode::kRange),
- start_idx_(start),
- end_idx_(end),
+ start_index_(start),
+ end_index_(end),
optimize_for_(optimize_for) {}
RowMap::RowMap(BitVector bit_vector)
@@ -159,7 +159,7 @@
RowMap RowMap::Copy() const {
switch (mode_) {
case Mode::kRange:
- return RowMap(start_idx_, end_idx_);
+ return RowMap(start_index_, end_index_);
case Mode::kBitVector:
return RowMap(bit_vector_.Copy());
case Mode::kIndexVector:
@@ -176,20 +176,22 @@
case Mode::kRange:
switch (mode_) {
case Mode::kRange:
- return SelectRangeWithRange(start_idx_, end_idx_, selector.start_idx_,
- selector.end_idx_);
+ return SelectRangeWithRange(start_index_, end_index_,
+ selector.start_index_,
+ selector.end_index_);
case Mode::kBitVector:
- return SelectBvWithRange(bit_vector_, selector.start_idx_,
- selector.end_idx_);
+ return SelectBvWithRange(bit_vector_, selector.start_index_,
+ selector.end_index_);
case Mode::kIndexVector:
- return SelectIvWithRange(index_vector_, selector.start_idx_,
- selector.end_idx_);
+ return SelectIvWithRange(index_vector_, selector.start_index_,
+ selector.end_index_);
}
break;
case Mode::kBitVector:
switch (mode_) {
case Mode::kRange:
- return SelectRangeWithBv(start_idx_, end_idx_, selector.bit_vector_);
+ return SelectRangeWithBv(start_index_, end_index_,
+ selector.bit_vector_);
case Mode::kBitVector:
return SelectBvWithBv(bit_vector_, selector.bit_vector_);
case Mode::kIndexVector:
@@ -199,7 +201,7 @@
case Mode::kIndexVector:
switch (mode_) {
case Mode::kRange:
- return SelectRangeWithIv(start_idx_, end_idx_,
+ return SelectRangeWithIv(start_index_, end_index_,
selector.index_vector_);
case Mode::kBitVector:
return SelectBvWithIv(bit_vector_, selector.index_vector_);
diff --git a/src/trace_processor/containers/row_map.h b/src/trace_processor/containers/row_map.h
index cb39ac1..5ca3c9c 100644
--- a/src/trace_processor/containers/row_map.h
+++ b/src/trace_processor/containers/row_map.h
@@ -34,6 +34,20 @@
// columns can refer to the same RowMap. The RowMap defines the access pattern
// to iterate on rows.
//
+// Naming convention:
+//
+// As both the input and output of RowMap is a uint32_t, it can be quite
+// confusing to reason about what parameters/return values of the functions
+// of RowMap actually means. To help with this, we define a strict convention
+// of naming.
+//
+// row: input - that is, rows are what are passed into operator[]; named as
+// such because a "row" number in a table is converted to an index to
+// lookup in the backing vectors.
+// index: output - that is, indices are what are returned from operator[];
+// named as such because an "index" is what's used to lookup data
+// from the backing vectors.
+//
// Implementation details:
//
// Behind the scenes, this class is impelemented using one of three backing
@@ -69,15 +83,15 @@
// BitVector::SetBitsIterator.
class RangeIterator {
public:
- RangeIterator(const RowMap* rm) : rm_(rm), index_(rm->start_idx_) {}
+ RangeIterator(const RowMap* rm) : rm_(rm), index_(rm->start_index_) {}
void Next() { ++index_; }
- operator bool() const { return index_ < rm_->end_idx_; }
+ operator bool() const { return index_ < rm_->end_index_; }
uint32_t index() const { return index_; }
- uint32_t ordinal() const { return index_ - rm_->start_idx_; }
+ uint32_t ordinal() const { return index_ - rm_->start_index_; }
private:
const RowMap* rm_ = nullptr;
@@ -105,6 +119,10 @@
};
public:
+ // Input type.
+ using InputRow = uint32_t;
+ using OutputIndex = uint32_t;
+
// Allows efficient iteration over the rows of a RowMap.
//
// Note: you should usually prefer to use the methods on RowMap directly (if
@@ -158,12 +176,8 @@
PERFETTO_FATAL("For GCC");
}
- // Returns the row pointed to by this iterator.
- uint32_t row() const {
- // RowMap uses the row/index nomenclature for referring to the mapping
- // from index to a row (as the name suggests). However, the data
- // structures used by RowMap use the index/ordinal naming (because they
- // don't have the concept of a "row"). Switch the naming here.
+ // Returns the index pointed to by this iterator.
+ OutputIndex index() const {
switch (rm_->mode_) {
case Mode::kRange:
return range_it_->index();
@@ -175,12 +189,8 @@
PERFETTO_FATAL("For GCC");
}
- // Returns the index of the row the iterator points to.
- uint32_t index() const {
- // RowMap uses the row/index nomenclature for referring to the mapping
- // from index to a row (as the name suggests). However, the data
- // structures used by RowMap use the index/ordinal naming (because they
- // don't have the concept of a "row"). Switch the naming here.
+ // Returns the row of the index the iterator points to.
+ InputRow row() const {
switch (rm_->mode_) {
case Mode::kRange:
return range_it_->ordinal();
@@ -216,21 +226,23 @@
// By default this will be implemented using a range.
RowMap();
- // Creates a RowMap containing the range of rows between |start| and |end|
- // i.e. all rows between |start| (inclusive) and |end| (exclusive).
- explicit RowMap(uint32_t start,
- uint32_t end,
+ // Creates a RowMap containing the range of indices between |start| and |end|
+ // i.e. all indices between |start| (inclusive) and |end| (exclusive).
+ explicit RowMap(OutputIndex start,
+ OutputIndex end,
OptimizeFor optimize_for = OptimizeFor::kMemory);
// Creates a RowMap backed by a BitVector.
explicit RowMap(BitVector bit_vector);
// Creates a RowMap backed by an std::vector<uint32_t>.
- explicit RowMap(std::vector<uint32_t> vec);
+ explicit RowMap(std::vector<OutputIndex> vec);
- // Creates a RowMap containing just |row|.
+ // Creates a RowMap containing just |index|.
// By default this will be implemented using a range.
- static RowMap SingleRow(uint32_t row) { return RowMap(row, row + 1); }
+ static RowMap SingleRow(OutputIndex index) {
+ return RowMap(index, index + 1);
+ }
// Creates a copy of the RowMap.
// We have an explicit copy function because RowMap can hold onto large chunks
@@ -238,11 +250,12 @@
// accidental leaks and copies.
RowMap Copy() const;
- // Returns the size of the RowMap; that is the number of rows in the RowMap.
+ // Returns the size of the RowMap; that is the number of indices in the
+ // RowMap.
uint32_t size() const {
switch (mode_) {
case Mode::kRange:
- return end_idx_ - start_idx_;
+ return end_index_ - start_index_;
case Mode::kBitVector:
return bit_vector_.GetNumBitsSet();
case Mode::kIndexVector:
@@ -254,54 +267,54 @@
// Returns whether this rowmap is empty.
bool empty() const { return size() == 0; }
- // Returns the row at index |row|.
- uint32_t Get(uint32_t idx) const {
- PERFETTO_DCHECK(idx < size());
+ // Returns the index at the given |row|.
+ OutputIndex Get(InputRow row) const {
+ PERFETTO_DCHECK(row < size());
switch (mode_) {
case Mode::kRange:
- return GetRange(idx);
+ return GetRange(row);
case Mode::kBitVector:
- return GetBitVector(idx);
+ return GetBitVector(row);
case Mode::kIndexVector:
- return GetIndexVector(idx);
+ return GetIndexVector(row);
}
PERFETTO_FATAL("For GCC");
}
- // Returns whether the RowMap contains the given row.
- bool Contains(uint32_t row) const {
+ // Returns whether the RowMap contains the given index.
+ bool Contains(OutputIndex index) const {
switch (mode_) {
case Mode::kRange: {
- return row >= start_idx_ && row < end_idx_;
+ return index >= start_index_ && index < end_index_;
}
case Mode::kBitVector: {
- return row < bit_vector_.size() && bit_vector_.IsSet(row);
+ return index < bit_vector_.size() && bit_vector_.IsSet(index);
}
case Mode::kIndexVector: {
- auto it = std::find(index_vector_.begin(), index_vector_.end(), row);
+ auto it = std::find(index_vector_.begin(), index_vector_.end(), index);
return it != index_vector_.end();
}
}
PERFETTO_FATAL("For GCC");
}
- // Returns the first index of the given |row| in the RowMap.
- base::Optional<uint32_t> IndexOf(uint32_t row) const {
+ // Returns the first row of the given |index| in the RowMap.
+ base::Optional<InputRow> RowOf(OutputIndex index) const {
switch (mode_) {
case Mode::kRange: {
- if (row < start_idx_ || row >= end_idx_)
+ if (index < start_index_ || index >= end_index_)
return base::nullopt;
- return row - start_idx_;
+ return index - start_index_;
}
case Mode::kBitVector: {
- return row < bit_vector_.size() && bit_vector_.IsSet(row)
- ? base::make_optional(bit_vector_.GetNumBitsSet(row))
+ return index < bit_vector_.size() && bit_vector_.IsSet(index)
+ ? base::make_optional(bit_vector_.GetNumBitsSet(index))
: base::nullopt;
}
case Mode::kIndexVector: {
- auto it = std::find(index_vector_.begin(), index_vector_.end(), row);
+ auto it = std::find(index_vector_.begin(), index_vector_.end(), index);
return it != index_vector_.end()
- ? base::make_optional(static_cast<uint32_t>(
+ ? base::make_optional(static_cast<InputRow>(
std::distance(index_vector_.begin(), it)))
: base::nullopt;
}
@@ -309,8 +322,8 @@
PERFETTO_FATAL("For GCC");
}
- // Performs an ordered insert the row into the current RowMap (precondition:
- // this RowMap is ordered based on the rows it contains).
+ // Performs an ordered insert of the index into the current RowMap
+ // (precondition: this RowMap is ordered based on the indices it contains).
//
// Example:
// this = [1, 5, 10, 11, 20]
@@ -320,41 +333,41 @@
// Insert(2) // this = [1, 2, 5, 10, 11, 12, 20, 21]
//
// Speecifically, this means that it is only valid to call Insert on a RowMap
- // which is sorted by the rows it contains; this is automatically true when
+ // which is sorted by the indices it contains; this is automatically true when
// the RowMap is in range or BitVector mode but is a required condition for
// IndexVector mode.
- void Insert(uint32_t row) {
+ void Insert(OutputIndex index) {
switch (mode_) {
case Mode::kRange:
- if (row == end_idx_) {
+ if (index == end_index_) {
// Fast path: if we're just appending to the end of the range, we can
// stay in range mode and just bump the end index.
- end_idx_++;
+ end_index_++;
} else {
// Slow path: the insert is somewhere else other than the end. This
// means we need to switch to using a BitVector instead.
- bit_vector_.Resize(start_idx_, false);
- bit_vector_.Resize(end_idx_, true);
+ bit_vector_.Resize(start_index_, false);
+ bit_vector_.Resize(end_index_, true);
*this = RowMap(std::move(bit_vector_));
- InsertIntoBitVector(row);
+ InsertIntoBitVector(index);
}
break;
case Mode::kBitVector:
- InsertIntoBitVector(row);
+ InsertIntoBitVector(index);
break;
case Mode::kIndexVector: {
PERFETTO_DCHECK(
std::is_sorted(index_vector_.begin(), index_vector_.end()));
auto it =
- std::upper_bound(index_vector_.begin(), index_vector_.end(), row);
- index_vector_.insert(it, row);
+ std::upper_bound(index_vector_.begin(), index_vector_.end(), index);
+ index_vector_.insert(it, index);
break;
}
}
}
- // Updates this RowMap by 'picking' the rows at indicies given by |picker|.
+ // Updates this RowMap by 'picking' the indices given by |picker|.
// This is easiest to explain with an example; suppose we have the following
// RowMaps:
// this : [0, 1, 4, 10, 11]
@@ -365,8 +378,8 @@
//
// Conceptually, we are performing the following algorithm:
// RowMap rm = Copy()
- // for (idx : picker)
- // rm[i++] = this[idx]
+ // for (p : picker)
+ // rm[i++] = this[p]
// return rm;
RowMap SelectRows(const RowMap& selector) const {
uint32_t size = selector.size();
@@ -385,8 +398,8 @@
}
// Intersects |other| with |this| writing the result into |this|.
- // By "intersect", we mean to keep only the rows present in both RowMaps. The
- // order of the preserved rows will be the same as |this|.
+ // By "intersect", we mean to keep only the indices present in both RowMaps.
+ // The order of the preserved indices will be the same as |this|.
//
// Conceptually, we are performing the following algorithm:
// for (idx : this)
@@ -398,8 +411,9 @@
// of them as the new RowMap.
// We have this as an explicit fast path as this is very common for
// constraints on id and sorted columns to satisfy this condition.
- start_idx_ = std::max(start_idx_, other.start_idx_);
- end_idx_ = std::max(start_idx_, std::min(end_idx_, other.end_idx_));
+ start_index_ = std::max(start_index_, other.start_index_);
+ end_index_ =
+ std::max(start_index_, std::min(end_index_, other.end_index_));
return;
}
@@ -410,7 +424,7 @@
// Filters the current RowMap into the RowMap given by |out| based on the
// return value of |p(idx)|.
//
- // Precondition: |out| should be sorted by the rows inside it (this is
+ // Precondition: |out| should be sorted by the indices inside it (this is
// required to keep this method efficient). This is automatically true if the
// mode is out is Range or BitVector but needs to be enforced if the mode is
// IndexVector.
@@ -450,13 +464,13 @@
// cases where |out| has only a few entries so we can scan |out| instead of
// scanning |this|.
- // Ideally, we'd always just scan the rows in |out| and keep those which
+ // Ideally, we'd always just scan |out| and keep the indices in |this| which
// meet |p|. However, if |this| is a BitVector, we end up needing expensive
- // |IndexOfNthSet| calls (as we need to lookup the row before passing it to
- // |p|).
+ // |IndexOfNthSet| calls (as we need to convert the row to an index before
+ // passing it to |p|).
switch (mode_) {
case Mode::kRange: {
- auto ip = [this, p](uint32_t idx) { return p(GetRange(idx)); };
+ auto ip = [this, p](uint32_t row) { return p(GetRange(row)); };
out->Filter(ip);
break;
}
@@ -542,12 +556,12 @@
case Mode::kRange: {
// TODO(lalitm): investigate whether we can reuse the data inside
// out->bit_vector_ at some point.
- BitVector bv(out->end_idx_, false);
+ BitVector bv(out->end_index_, false);
for (auto out_it = bv.IterateAllBits(); it; it.Next(), out_it.Next()) {
uint32_t ordinal = it.ordinal();
- if (ordinal < out->start_idx_)
+ if (ordinal < out->start_index_)
continue;
- if (ordinal >= out->end_idx_)
+ if (ordinal >= out->end_index_)
break;
if (p(it.index())) {
@@ -587,16 +601,16 @@
template <typename Predicate>
void FilterRange(Predicate p) {
- uint32_t count = end_idx_ - start_idx_;
+ uint32_t count = end_index_ - start_index_;
- // Optimization: if we are only going to scan a few rows, it's not
+ // Optimization: if we are only going to scan a few indices, it's not
// worth the haslle of working with a BitVector.
constexpr uint32_t kSmallRangeLimit = 2048;
bool is_small_range = count < kSmallRangeLimit;
// Optimization: weif the cost of a BitVector is more than the highest
// possible cost an index vector could have, use the index vector.
- uint32_t bit_vector_cost = BitVector::ApproxBytesCost(end_idx_);
+ uint32_t bit_vector_cost = BitVector::ApproxBytesCost(end_index_);
uint32_t index_vector_cost_ub = sizeof(uint32_t) * count;
// If either of the conditions hold which make it better to use an
@@ -608,21 +622,21 @@
// big and good performance.
std::vector<uint32_t> iv(std::min(kSmallRangeLimit, count));
- uint32_t out_idx = 0;
+ uint32_t out_i = 0;
for (uint32_t i = 0; i < count; ++i) {
// If we reach the capacity add another small set of indices.
- if (PERFETTO_UNLIKELY(out_idx == iv.size()))
+ if (PERFETTO_UNLIKELY(out_i == iv.size()))
iv.resize(iv.size() + kSmallRangeLimit);
// We keep this branch free by always writing the index but only
// incrementing the out index if the return value is true.
- bool value = p(i + start_idx_);
- iv[out_idx] = i + start_idx_;
- out_idx += value;
+ bool value = p(i + start_index_);
+ iv[out_i] = i + start_index_;
+ out_i += value;
}
// Make the vector the correct size and as small as possible.
- iv.resize(out_idx);
+ iv.resize(out_i);
iv.shrink_to_fit();
*this = RowMap(std::move(iv));
@@ -631,7 +645,7 @@
// Otherwise, create a bitvector which spans the full range using
// |p| as the filler for the bits between start and end.
- *this = RowMap(BitVector::Range(start_idx_, end_idx_, p));
+ *this = RowMap(BitVector::Range(start_index_, end_index_, p));
}
void InsertIntoBitVector(uint32_t row) {
@@ -642,17 +656,17 @@
bit_vector_.Set(row);
}
- PERFETTO_ALWAYS_INLINE uint32_t GetRange(uint32_t idx) const {
+ PERFETTO_ALWAYS_INLINE OutputIndex GetRange(InputRow row) const {
PERFETTO_DCHECK(mode_ == Mode::kRange);
- return start_idx_ + idx;
+ return start_index_ + row;
}
- PERFETTO_ALWAYS_INLINE uint32_t GetBitVector(uint32_t idx) const {
+ PERFETTO_ALWAYS_INLINE OutputIndex GetBitVector(uint32_t row) const {
PERFETTO_DCHECK(mode_ == Mode::kBitVector);
- return bit_vector_.IndexOfNthSet(idx);
+ return bit_vector_.IndexOfNthSet(row);
}
- PERFETTO_ALWAYS_INLINE uint32_t GetIndexVector(uint32_t idx) const {
+ PERFETTO_ALWAYS_INLINE OutputIndex GetIndexVector(uint32_t row) const {
PERFETTO_DCHECK(mode_ == Mode::kIndexVector);
- return index_vector_[idx];
+ return index_vector_[row];
}
RowMap SelectRowsSlow(const RowMap& selector) const;
@@ -660,14 +674,14 @@
Mode mode_ = Mode::kRange;
// Only valid when |mode_| == Mode::kRange.
- uint32_t start_idx_ = 0; // This is an inclusive index.
- uint32_t end_idx_ = 0; // This is an exclusive index.
+ OutputIndex start_index_ = 0; // This is an inclusive index.
+ OutputIndex end_index_ = 0; // This is an exclusive index.
// Only valid when |mode_| == Mode::kBitVector.
BitVector bit_vector_;
// Only valid when |mode_| == Mode::kIndexVector.
- std::vector<uint32_t> index_vector_;
+ std::vector<OutputIndex> index_vector_;
OptimizeFor optimize_for_ = OptimizeFor::kMemory;
};
diff --git a/src/trace_processor/containers/row_map_unittest.cc b/src/trace_processor/containers/row_map_unittest.cc
index ac54cb1..16022b2 100644
--- a/src/trace_processor/containers/row_map_unittest.cc
+++ b/src/trace_processor/containers/row_map_unittest.cc
@@ -34,11 +34,11 @@
ASSERT_EQ(rm.Get(1), 31u);
ASSERT_EQ(rm.Get(16), 46u);
- ASSERT_EQ(rm.IndexOf(29), base::nullopt);
- ASSERT_EQ(rm.IndexOf(30), 0u);
- ASSERT_EQ(rm.IndexOf(37), 7u);
- ASSERT_EQ(rm.IndexOf(46), 16u);
- ASSERT_EQ(rm.IndexOf(47), base::nullopt);
+ ASSERT_EQ(rm.RowOf(29), base::nullopt);
+ ASSERT_EQ(rm.RowOf(30), 0u);
+ ASSERT_EQ(rm.RowOf(37), 7u);
+ ASSERT_EQ(rm.RowOf(46), 16u);
+ ASSERT_EQ(rm.RowOf(47), base::nullopt);
}
TEST(RowMapUnittest, SmokeBitVector) {
@@ -50,12 +50,12 @@
ASSERT_EQ(rm.Get(1u), 4u);
ASSERT_EQ(rm.Get(2u), 5u);
- ASSERT_EQ(rm.IndexOf(0u), 0u);
- ASSERT_EQ(rm.IndexOf(4u), 1u);
- ASSERT_EQ(rm.IndexOf(5u), 2u);
+ ASSERT_EQ(rm.RowOf(0u), 0u);
+ ASSERT_EQ(rm.RowOf(4u), 1u);
+ ASSERT_EQ(rm.RowOf(5u), 2u);
- ASSERT_EQ(rm.IndexOf(1u), base::nullopt);
- ASSERT_EQ(rm.IndexOf(100u), base::nullopt);
+ ASSERT_EQ(rm.RowOf(1u), base::nullopt);
+ ASSERT_EQ(rm.RowOf(100u), base::nullopt);
}
TEST(RowMapUnittest, SmokeIndexVector) {
@@ -70,12 +70,12 @@
ASSERT_EQ(rm.Get(4u), 100u);
ASSERT_EQ(rm.Get(5u), 1u);
- ASSERT_EQ(rm.IndexOf(32u), 0u);
- ASSERT_EQ(rm.IndexOf(56u), 1u);
- ASSERT_EQ(rm.IndexOf(24u), 2u);
- ASSERT_EQ(rm.IndexOf(0u), 3u);
- ASSERT_EQ(rm.IndexOf(100u), 4u);
- ASSERT_EQ(rm.IndexOf(1u), 5u);
+ ASSERT_EQ(rm.RowOf(32u), 0u);
+ ASSERT_EQ(rm.RowOf(56u), 1u);
+ ASSERT_EQ(rm.RowOf(24u), 2u);
+ ASSERT_EQ(rm.RowOf(0u), 3u);
+ ASSERT_EQ(rm.RowOf(100u), 4u);
+ ASSERT_EQ(rm.RowOf(1u), 5u);
}
TEST(RowMapUnittest, InsertToRangeAfter) {
@@ -84,7 +84,7 @@
ASSERT_EQ(rm.size(), 5u);
ASSERT_EQ(rm.Get(4u), 10u);
- ASSERT_EQ(rm.IndexOf(10u), 4u);
+ ASSERT_EQ(rm.RowOf(10u), 4u);
}
TEST(RowMapUnittest, InsertToBitVectorBefore) {
@@ -105,7 +105,7 @@
ASSERT_EQ(rm.size(), 5u);
ASSERT_EQ(rm.Get(4u), 10u);
- ASSERT_EQ(rm.IndexOf(10u), 4u);
+ ASSERT_EQ(rm.RowOf(10u), 4u);
}
TEST(RowMapUnittest, InsertToIndexVectorAfter) {
@@ -114,7 +114,7 @@
ASSERT_EQ(rm.size(), 5u);
ASSERT_EQ(rm.Get(4u), 10u);
- ASSERT_EQ(rm.IndexOf(10u), 4u);
+ ASSERT_EQ(rm.RowOf(10u), 4u);
}
TEST(RowMapUnittest, ContainsRange) {
diff --git a/src/trace_processor/db/column.h b/src/trace_processor/db/column.h
index 9df9f70..dacbe60 100644
--- a/src/trace_processor/db/column.h
+++ b/src/trace_processor/db/column.h
@@ -229,7 +229,7 @@
case ColumnType::kId: {
if (value.type != SqlValue::Type::kLong)
return base::nullopt;
- return row_map().IndexOf(static_cast<uint32_t>(value.long_value));
+ return row_map().RowOf(static_cast<uint32_t>(value.long_value));
}
}
PERFETTO_FATAL("For GCC");
diff --git a/src/trace_processor/db/table.h b/src/trace_processor/db/table.h
index fb8b73b..e13ad58 100644
--- a/src/trace_processor/db/table.h
+++ b/src/trace_processor/db/table.h
@@ -60,7 +60,7 @@
// Returns the value at the current row for column |col_idx|.
SqlValue Get(uint32_t col_idx) const {
const auto& col = table_->columns_[col_idx];
- return col.GetAtIdx(its_[col.row_map_idx_].row());
+ return col.GetAtIdx(its_[col.row_map_idx_].index());
}
private:
diff --git a/src/trace_processor/db/typed_column.h b/src/trace_processor/db/typed_column.h
index a8fc424..d286834 100644
--- a/src/trace_processor/db/typed_column.h
+++ b/src/trace_processor/db/typed_column.h
@@ -162,7 +162,7 @@
struct IdColumn : public Column {
Id operator[](uint32_t row) const { return Id(row_map().Get(row)); }
base::Optional<uint32_t> IndexOf(Id id) const {
- return row_map().IndexOf(id.value);
+ return row_map().RowOf(id.value);
}
// Reinterpret cast a Column to IdColumn or crash if that is likely to be
diff --git a/src/trace_processor/dynamic/ancestor_generator.cc b/src/trace_processor/dynamic/ancestor_generator.cc
index 1e479bc..f603d67 100644
--- a/src/trace_processor/dynamic/ancestor_generator.cc
+++ b/src/trace_processor/dynamic/ancestor_generator.cc
@@ -139,11 +139,11 @@
slice_table.FilterToRowMap({slice_table.stack_id().eq(start_id)});
for (auto id_it = slice_ids.IterateRows(); id_it; id_it.Next()) {
- auto slice_id = slice_table.id()[id_it.row()];
+ auto slice_id = slice_table.id()[id_it.index()];
auto ancestors = GetAncestorSlices(slice_table, slice_id);
for (auto row_it = ancestors->IterateRows(); row_it; row_it.Next()) {
- result.Insert(row_it.row());
+ result.Insert(row_it.index());
}
}
diff --git a/src/trace_processor/dynamic/connected_flow_generator.cc b/src/trace_processor/dynamic/connected_flow_generator.cc
index 6dd0a90..d3cf976 100644
--- a/src/trace_processor/dynamic/connected_flow_generator.cc
+++ b/src/trace_processor/dynamic/connected_flow_generator.cc
@@ -162,8 +162,8 @@
auto rows = flow.FilterToRowMap({start_col.eq(slice_id.value)});
for (auto row_it = rows.IterateRows(); row_it; row_it.Next()) {
- flow_rows_.push_back(row_it.row());
- SliceId next_slice_id = end_col[row_it.row()];
+ flow_rows_.push_back(row_it.index());
+ SliceId next_slice_id = end_col[row_it.index()];
if (known_slices_.count(next_slice_id) != 0) {
continue;
}
@@ -179,7 +179,7 @@
void GoToRelativesImpl(RowMap::Iterator it) {
const auto& slice = context_->storage->slice_table();
for (; it; it.Next()) {
- auto relative_slice_id = slice.id()[it.row()];
+ auto relative_slice_id = slice.id()[it.index()];
if (known_slices_.count(relative_slice_id))
continue;
known_slices_.insert(relative_slice_id);
diff --git a/src/trace_processor/dynamic/descendant_generator.cc b/src/trace_processor/dynamic/descendant_generator.cc
index 863178a..cc33bf1 100644
--- a/src/trace_processor/dynamic/descendant_generator.cc
+++ b/src/trace_processor/dynamic/descendant_generator.cc
@@ -115,11 +115,11 @@
auto slice_ids = slices.FilterToRowMap({slices.stack_id().eq(start_id)});
for (auto id_it = slice_ids.IterateRows(); id_it; id_it.Next()) {
- auto slice_id = slices.id()[id_it.row()];
+ auto slice_id = slices.id()[id_it.index()];
auto descendants = GetDescendantSlices(slices, slice_id);
for (auto row_it = descendants->IterateRows(); row_it; row_it.Next()) {
- result.Insert(row_it.row());
+ result.Insert(row_it.index());
}
}
diff --git a/src/trace_processor/export_json_unittest.cc b/src/trace_processor/export_json_unittest.cc
index 08c4fa2..961ecfc 100644
--- a/src/trace_processor/export_json_unittest.cc
+++ b/src/trace_processor/export_json_unittest.cc
@@ -32,6 +32,7 @@
#include "src/trace_processor/importers/common/track_tracker.h"
#include "src/trace_processor/importers/proto/metadata_tracker.h"
#include "src/trace_processor/importers/proto/track_event_tracker.h"
+#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/types/trace_processor_context.h"
#include "test/gtest_and_gmock.h"
@@ -230,8 +231,8 @@
}
TEST_F(ExportJsonTest, SystemEventsIgnored) {
- TrackId track = context_.track_tracker->CreateAndroidAsyncTrack(
- /*name=*/kNullStringId, /*upid=*/0);
+ TrackId track = context_.track_tracker->CreateProcessAsyncTrack(
+ /*name=*/kNullStringId, /*upid=*/0, /*source=*/kNullStringId);
context_.args_tracker->Flush(); // Flush track args.
// System events have no category.
diff --git a/src/trace_processor/importers/common/track_tracker.cc b/src/trace_processor/importers/common/track_tracker.cc
index 73aef4e..36a282d 100644
--- a/src/trace_processor/importers/common/track_tracker.cc
+++ b/src/trace_processor/importers/common/track_tracker.cc
@@ -31,7 +31,6 @@
category_key_(context->storage->InternString("category")),
fuchsia_source_(context->storage->InternString("fuchsia")),
chrome_source_(context->storage->InternString("chrome")),
- android_source_(context->storage->InternString("android")),
context_(context) {}
TrackId TrackTracker::InternThreadTrack(UniqueTid utid) {
@@ -133,28 +132,29 @@
return id;
}
-TrackId TrackTracker::CreateGlobalAsyncTrack(StringId name) {
+TrackId TrackTracker::CreateGlobalAsyncTrack(StringId name, StringId source) {
tables::TrackTable::Row row(name);
auto id = context_->storage->mutable_track_table()->Insert(row).id;
+ if (!source.is_null()) {
+ context_->args_tracker->AddArgsTo(id).AddArg(source_key_,
+ Variadic::String(source));
+ }
return id;
}
-TrackId TrackTracker::CreateAndroidAsyncTrack(StringId name, UniquePid upid) {
+TrackId TrackTracker::CreateProcessAsyncTrack(StringId name,
+ UniquePid upid,
+ StringId source) {
tables::ProcessTrackTable::Row row(name);
row.upid = upid;
auto id = context_->storage->mutable_process_track_table()->Insert(row).id;
- context_->args_tracker->AddArgsTo(id).AddArg(
- source_key_, Variadic::String(android_source_));
+ if (!source.is_null()) {
+ context_->args_tracker->AddArgsTo(id).AddArg(source_key_,
+ Variadic::String(source));
+ }
return id;
}
-TrackId TrackTracker::CreateFrameTimelineAsyncTrack(StringId name,
- UniquePid upid) {
- tables::ProcessTrackTable::Row row(name);
- row.upid = upid;
- return context_->storage->mutable_process_track_table()->Insert(row).id;
-}
-
TrackId TrackTracker::InternLegacyChromeProcessInstantTrack(UniquePid upid) {
auto it = chrome_process_instant_tracks_.find(upid);
if (it != chrome_process_instant_tracks_.end())
diff --git a/src/trace_processor/importers/common/track_tracker.h b/src/trace_processor/importers/common/track_tracker.h
index 6ed95b9..e38cc66 100644
--- a/src/trace_processor/importers/common/track_tracker.h
+++ b/src/trace_processor/importers/common/track_tracker.h
@@ -52,15 +52,6 @@
bool source_id_is_process_scoped,
StringId source_scope);
- // Creates and inserts a global async track into the storage.
- TrackId CreateGlobalAsyncTrack(StringId name);
-
- // Creates and inserts a Android async track into the storage.
- TrackId CreateAndroidAsyncTrack(StringId name, UniquePid upid);
-
- // Creates and inserts a FrameTimeline async track into the storage.
- TrackId CreateFrameTimelineAsyncTrack(StringId name, UniquePid upid);
-
// Interns a track for legacy Chrome process-scoped instant events into the
// storage.
TrackId InternLegacyChromeProcessInstantTrack(UniquePid upid);
@@ -111,6 +102,17 @@
uint32_t cpu,
bool is_timebase);
+ // NOTE:
+ // The below method should only be called by AsyncTrackSetTracker
+
+ // Creates and inserts a global async track into the storage.
+ TrackId CreateGlobalAsyncTrack(StringId name, StringId source);
+
+ // Creates and inserts a Android async track into the storage.
+ TrackId CreateProcessAsyncTrack(StringId name,
+ UniquePid upid,
+ StringId source);
+
private:
struct GpuTrackTuple {
StringId track_name;
@@ -163,7 +165,6 @@
const StringId fuchsia_source_ = kNullStringId;
const StringId chrome_source_ = kNullStringId;
- const StringId android_source_ = kNullStringId;
TraceProcessorContext* const context_;
};
diff --git a/src/trace_processor/importers/ftrace/ftrace_descriptors.cc b/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
index bf50559..4782a3b 100644
--- a/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
@@ -24,7 +24,7 @@
namespace trace_processor {
namespace {
-std::array<MessageDescriptor, 362> descriptors{{
+std::array<MessageDescriptor, 364> descriptors{{
{nullptr, 0, {}},
{nullptr, 0, {}},
{nullptr, 0, {}},
@@ -3875,6 +3875,36 @@
{"skbaddr", ProtoSchemaType::kUint64},
},
},
+ {
+ "tcp_retransmit_skb",
+ 7,
+ {
+ {},
+ {"daddr", ProtoSchemaType::kUint32},
+ {"dport", ProtoSchemaType::kUint32},
+ {"saddr", ProtoSchemaType::kUint32},
+ {"skaddr", ProtoSchemaType::kUint64},
+ {"skbaddr", ProtoSchemaType::kUint64},
+ {"sport", ProtoSchemaType::kUint32},
+ {"state", ProtoSchemaType::kInt32},
+ },
+ },
+ {
+ "inet_sock_set_state",
+ 9,
+ {
+ {},
+ {"daddr", ProtoSchemaType::kUint32},
+ {"dport", ProtoSchemaType::kUint32},
+ {"family", ProtoSchemaType::kUint32},
+ {"newstate", ProtoSchemaType::kInt32},
+ {"oldstate", ProtoSchemaType::kInt32},
+ {"protocol", ProtoSchemaType::kUint32},
+ {"saddr", ProtoSchemaType::kUint32},
+ {"skaddr", ProtoSchemaType::kUint64},
+ {"sport", ProtoSchemaType::kUint32},
+ },
+ },
}};
} // namespace
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.cc b/src/trace_processor/importers/ftrace/ftrace_parser.cc
index 186b541..e9bdddb 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.cc
@@ -27,6 +27,7 @@
#include "src/trace_processor/storage/stats.h"
#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/types/softirq_action.h"
+#include "src/trace_processor/types/tcp_state.h"
#include "protos/perfetto/common/gpu_counter_descriptor.pbzero.h"
#include "protos/perfetto/trace/ftrace/binder.pbzero.h"
@@ -54,8 +55,10 @@
#include "protos/perfetto/trace/ftrace/scm.pbzero.h"
#include "protos/perfetto/trace/ftrace/sde.pbzero.h"
#include "protos/perfetto/trace/ftrace/signal.pbzero.h"
+#include "protos/perfetto/trace/ftrace/sock.pbzero.h"
#include "protos/perfetto/trace/ftrace/systrace.pbzero.h"
#include "protos/perfetto/trace/ftrace/task.pbzero.h"
+#include "protos/perfetto/trace/ftrace/tcp.pbzero.h"
#include "protos/perfetto/trace/ftrace/thermal.pbzero.h"
#include "protos/perfetto/trace/ftrace/vmscan.pbzero.h"
#include "protos/perfetto/trace/ftrace/workqueue.pbzero.h"
@@ -104,6 +107,7 @@
rss_stat_tracker_(context),
sched_wakeup_name_id_(context->storage->InternString("sched_wakeup")),
sched_waking_name_id_(context->storage->InternString("sched_waking")),
+ cpu_id_(context->storage->InternString("cpu")),
cpu_freq_name_id_(context->storage->InternString("cpufreq")),
gpu_freq_name_id_(context->storage->InternString("gpufreq")),
cpu_idle_name_id_(context->storage->InternString("cpuidle")),
@@ -126,6 +130,10 @@
oom_kill_id_(context_->storage->InternString("mem.oom_kill")),
workqueue_id_(context_->storage->InternString("workqueue")),
irq_id_(context_->storage->InternString("irq")),
+ tcp_state_id_(context_->storage->InternString("tcp_state")),
+ tcp_event_id_(context_->storage->InternString("tcp_event")),
+ tcp_retransmited_name_id_(
+ context_->storage->InternString("TCP Retransmit Skb")),
ret_arg_id_(context_->storage->InternString("ret")),
direct_reclaim_nr_reclaimed_id_(
context->storage->InternString("direct_reclaim_nr_reclaimed")),
@@ -583,7 +591,7 @@
break;
}
case FtraceEvent::kWorkqueueExecuteStartFieldNumber: {
- ParseWorkqueueExecuteStart(ts, pid, data, seq_state);
+ ParseWorkqueueExecuteStart(cpu, ts, pid, data, seq_state);
break;
}
case FtraceEvent::kWorkqueueExecuteEndFieldNumber: {
@@ -650,6 +658,14 @@
ParseNetDevXmit(cpu, ts, data);
break;
}
+ case FtraceEvent::kInetSockSetStateFieldNumber: {
+ ParseInetSockSetState(ts, pid, data);
+ break;
+ }
+ case FtraceEvent::kTcpRetransmitSkbFieldNumber: {
+ ParseTcpRetransmitSkb(ts, data);
+ break;
+ }
default:
break;
}
@@ -1416,6 +1432,7 @@
}
void FtraceParser::ParseWorkqueueExecuteStart(
+ uint32_t cpu,
int64_t timestamp,
uint32_t pid,
ConstBytes blob,
@@ -1438,7 +1455,12 @@
UniqueTid utid = context_->process_tracker->GetOrCreateThread(pid);
TrackId track = context_->track_tracker->InternThreadTrack(utid);
- context_->slice_tracker->Begin(timestamp, track, workqueue_id_, name_id);
+
+ auto args_inserter = [this, cpu](ArgsTracker::BoundInserter* inserter) {
+ inserter->AddArg(cpu_id_, Variadic::Integer(cpu));
+ };
+ context_->slice_tracker->Begin(timestamp, track, workqueue_id_, name_id,
+ args_inserter);
}
void FtraceParser::ParseWorkqueueExecuteEnd(int64_t timestamp,
@@ -1710,5 +1732,82 @@
.AddArg(len_key, Variadic::UnsignedInteger(evt.len()));
}
+void FtraceParser::ParseInetSockSetState(int64_t timestamp,
+ uint32_t pid,
+ protozero::ConstBytes blob) {
+ protos::pbzero::InetSockSetStateFtraceEvent::Decoder evt(blob.data,
+ blob.size);
+
+ // Skip non TCP protocol.
+ if (evt.protocol() != kIpprotoTcp) {
+ PERFETTO_ELOG("skip non tcp protocol");
+ return;
+ }
+
+ // Skip non IP protocol.
+ if (evt.family() != kAfNet && evt.family() != kAfNet6) {
+ PERFETTO_ELOG("skip non IP protocol");
+ return;
+ }
+
+ // Skip invalid TCP state.
+ if (evt.newstate() >= TCP_MAX_STATES || evt.oldstate() >= TCP_MAX_STATES) {
+ PERFETTO_ELOG("skip invalid tcp state");
+ return;
+ }
+
+ auto got = skaddr_to_stream_.find(evt.skaddr());
+ if (got == skaddr_to_stream_.end()) {
+ skaddr_to_stream_[evt.skaddr()] = ++num_of_tcp_stream_;
+ }
+ uint32_t stream = skaddr_to_stream_[evt.skaddr()];
+ char stream_str[64];
+ sprintf(stream_str, "TCP stream#%" PRIu32 "", stream);
+ StringId stream_id = context_->storage->InternString(stream_str);
+
+ StringId slice_name_id;
+ if (evt.newstate() == TCP_SYN_SENT) {
+ base::StackString<32> str("%s(pid=%" PRIu32 ")",
+ kTcpStateNames[evt.newstate()], pid);
+ slice_name_id = context_->storage->InternString(str.string_view());
+ } else if (evt.newstate() == TCP_ESTABLISHED) {
+ base::StackString<64> str("%s(sport=%" PRIu32 ",dport=%" PRIu32 ")",
+ kTcpStateNames[evt.newstate()], evt.sport(),
+ evt.dport());
+ slice_name_id = context_->storage->InternString(str.string_view());
+ } else {
+ base::StringView slice_name = kTcpStateNames[evt.newstate()];
+ slice_name_id = context_->storage->InternString(slice_name);
+ }
+
+ // Push to async task set tracker.
+ auto async_track =
+ context_->async_track_set_tracker->InternGlobalTrackSet(stream_id);
+ TrackId end_id = context_->async_track_set_tracker->End(
+ async_track, static_cast<int64_t>(evt.skaddr()));
+ context_->slice_tracker->End(timestamp, end_id);
+ TrackId start_id = context_->async_track_set_tracker->Begin(
+ async_track, static_cast<int64_t>(evt.skaddr()));
+ context_->slice_tracker->Begin(timestamp, start_id, tcp_state_id_,
+ slice_name_id);
+}
+
+void FtraceParser::ParseTcpRetransmitSkb(int64_t timestamp,
+ protozero::ConstBytes blob) {
+ protos::pbzero::TcpRetransmitSkbFtraceEvent::Decoder evt(blob.data,
+ blob.size);
+
+ // Push event as instant to async task set tracker.
+ auto async_track = context_->async_track_set_tracker->InternGlobalTrackSet(
+ tcp_retransmited_name_id_);
+ base::StackString<64> str("sport=%" PRIu32 ",dport=%" PRIu32 "", evt.sport(),
+ evt.dport());
+ StringId slice_name_id = context_->storage->InternString(str.string_view());
+ TrackId track_id =
+ context_->async_track_set_tracker->Scoped(async_track, timestamp, 0);
+ context_->slice_tracker->Scoped(timestamp, track_id, tcp_event_id_,
+ slice_name_id, 0);
+}
+
} // namespace trace_processor
} // namespace perfetto
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.h b/src/trace_processor/importers/ftrace/ftrace_parser.h
index c9ae2c5..6863cac 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.h
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.h
@@ -125,7 +125,8 @@
void ParseDirectReclaimEnd(int64_t timestamp,
uint32_t pid,
protozero::ConstBytes);
- void ParseWorkqueueExecuteStart(int64_t timestamp,
+ void ParseWorkqueueExecuteStart(uint32_t cpu,
+ int64_t timestamp,
uint32_t pid,
protozero::ConstBytes,
PacketSequenceStateGeneration* seq_state);
@@ -156,12 +157,17 @@
int64_t timestamp,
protozero::ConstBytes);
void ParseNetDevXmit(uint32_t cpu, int64_t timestamp, protozero::ConstBytes);
+ void ParseInetSockSetState(int64_t timestamp,
+ uint32_t pid,
+ protozero::ConstBytes);
+ void ParseTcpRetransmitSkb(int64_t timestamp, protozero::ConstBytes);
TraceProcessorContext* context_;
RssStatTracker rss_stat_tracker_;
const StringId sched_wakeup_name_id_;
const StringId sched_waking_name_id_;
+ const StringId cpu_id_;
const StringId cpu_freq_name_id_;
const StringId gpu_freq_name_id_;
const StringId cpu_idle_name_id_;
@@ -182,6 +188,9 @@
const StringId oom_kill_id_;
const StringId workqueue_id_;
const StringId irq_id_;
+ const StringId tcp_state_id_;
+ const StringId tcp_event_id_;
+ const StringId tcp_retransmited_name_id_;
const StringId ret_arg_id_;
const StringId direct_reclaim_nr_reclaimed_id_;
const StringId direct_reclaim_order_id_;
@@ -228,6 +237,12 @@
// Record number of transmitted bytes to the network interface card.
std::unordered_map<StringId, uint64_t> nic_transmitted_bytes_;
+ // Keep sock to stream number mapping.
+ std::unordered_map<uint64_t, uint32_t> skaddr_to_stream_;
+
+ // Record number of tcp steams.
+ uint32_t num_of_tcp_stream_ = 0;
+
bool has_seen_first_ftrace_packet_ = false;
// Stores information about the timestamp from the metadata table which is
diff --git a/src/trace_processor/importers/proto/async_track_set_tracker.cc b/src/trace_processor/importers/proto/async_track_set_tracker.cc
index 4f2714c..c45148c 100644
--- a/src/trace_processor/importers/proto/async_track_set_tracker.cc
+++ b/src/trace_processor/importers/proto/async_track_set_tracker.cc
@@ -17,13 +17,15 @@
#include "src/trace_processor/importers/proto/async_track_set_tracker.h"
#include "src/trace_processor/importers/common/track_tracker.h"
+#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/types/trace_processor_context.h"
namespace perfetto {
namespace trace_processor {
AsyncTrackSetTracker::AsyncTrackSetTracker(TraceProcessorContext* context)
- : context_(context) {}
+ : android_source_(context->storage->InternString("android")),
+ context_(context) {}
AsyncTrackSetTracker::TrackSetId AsyncTrackSetTracker::InternGlobalTrackSet(
StringId name) {
@@ -42,45 +44,45 @@
return global_track_set_ids_[name] = id;
}
-AsyncTrackSetTracker::TrackSetId AsyncTrackSetTracker::InternAndroidSet(
+AsyncTrackSetTracker::TrackSetId AsyncTrackSetTracker::InternProcessTrackSet(
UniquePid upid,
StringId name) {
- AndroidTuple tuple{upid, name};
+ ProcessTuple tuple{upid, name};
- auto it = android_track_set_ids_.find(tuple);
- if (it != android_track_set_ids_.end())
+ auto it = process_track_set_ids_.find(tuple);
+ if (it != process_track_set_ids_.end())
return it->second;
uint32_t id = static_cast<uint32_t>(track_sets_.size());
TrackSet set;
- set.android_tuple = tuple;
- set.type = TrackSetType::kAndroid;
- set.nesting_behaviour = NestingBehaviour::kLegacySaturatingUnnestable;
- track_sets_.emplace_back(set);
-
- android_track_set_ids_[tuple] = id;
- return id;
-}
-
-AsyncTrackSetTracker::TrackSetId AsyncTrackSetTracker::InternFrameTimelineSet(
- UniquePid upid,
- StringId name) {
- FrameTimelineTuple tuple{upid, name};
-
- auto it = frame_timeline_track_set_ids_.find(tuple);
- if (it != frame_timeline_track_set_ids_.end())
- return it->second;
-
- uint32_t id = static_cast<uint32_t>(track_sets_.size());
-
- TrackSet set;
- set.frame_timeline_tuple = tuple;
- set.type = TrackSetType::kFrameTimeline;
+ set.process_tuple = tuple;
+ set.type = TrackSetType::kProcess;
set.nesting_behaviour = NestingBehaviour::kUnnestable;
track_sets_.emplace_back(set);
- frame_timeline_track_set_ids_[tuple] = id;
+ process_track_set_ids_[tuple] = id;
+ return id;
+}
+
+AsyncTrackSetTracker::TrackSetId
+AsyncTrackSetTracker::InternAndroidLegacyUnnestableTrackSet(UniquePid upid,
+ StringId name) {
+ ProcessTuple tuple{upid, name};
+
+ auto it = android_legacy_unnestable_track_set_ids_.find(tuple);
+ if (it != android_legacy_unnestable_track_set_ids_.end())
+ return it->second;
+
+ uint32_t id = static_cast<uint32_t>(track_sets_.size());
+
+ TrackSet set;
+ set.process_tuple = tuple;
+ set.type = TrackSetType::kAndroidLegacyUnnestable;
+ set.nesting_behaviour = NestingBehaviour::kLegacySaturatingUnnestable;
+ track_sets_.emplace_back(set);
+
+ android_legacy_unnestable_track_set_ids_[tuple] = id;
return id;
}
@@ -178,14 +180,18 @@
TrackId AsyncTrackSetTracker::CreateTrackForSet(const TrackSet& set) {
switch (set.type) {
case TrackSetType::kGlobal:
+ // TODO(lalitm): propogate source from callers rather than just passing
+ // kNullStringId here.
return context_->track_tracker->CreateGlobalAsyncTrack(
- set.global_track_name);
- case TrackSetType::kAndroid:
- return context_->track_tracker->CreateAndroidAsyncTrack(
- set.android_tuple.name, set.android_tuple.upid);
- case TrackSetType::kFrameTimeline:
- return context_->track_tracker->CreateFrameTimelineAsyncTrack(
- set.frame_timeline_tuple.name, set.frame_timeline_tuple.upid);
+ set.global_track_name, kNullStringId);
+ case TrackSetType::kProcess:
+ // TODO(lalitm): propogate source from callers rather than just passing
+ // kNullStringId here.
+ return context_->track_tracker->CreateProcessAsyncTrack(
+ set.process_tuple.name, set.process_tuple.upid, kNullStringId);
+ case TrackSetType::kAndroidLegacyUnnestable:
+ return context_->track_tracker->CreateProcessAsyncTrack(
+ set.process_tuple.name, set.process_tuple.upid, android_source_);
}
PERFETTO_FATAL("For GCC");
}
diff --git a/src/trace_processor/importers/proto/async_track_set_tracker.h b/src/trace_processor/importers/proto/async_track_set_tracker.h
index 6e7fa81..83fd95c 100644
--- a/src/trace_processor/importers/proto/async_track_set_tracker.h
+++ b/src/trace_processor/importers/proto/async_track_set_tracker.h
@@ -42,7 +42,7 @@
// The intended usage of this class is for callers to first call one of the
// Intern* methods to obtain a TrackSetId followed by Begin/End just before
// calling into SliceTracker's Begin/End respectively. For example:
-// TrackSetId set_id = track_set_tracker->InternAndroidSet(upid, name);
+// TrackSetId set_id = track_set_tracker->InternProcessTrackSet(upid, name);
// if (event.begin) {
// TrackId id = track_set_tracker->Begin(set_id, cookie);
// slice_tracker->Begin(ts, id, ...)
@@ -61,18 +61,18 @@
// Interns a set of global async slice tracks associated with the given name.
TrackSetId InternGlobalTrackSet(StringId name);
- // Interns a set of Android async slice tracks associated with the given
- // upid and name.
+ // Interns a set of process async slice tracks associated with the given name
+ // and upid.
+ TrackSetId InternProcessTrackSet(UniquePid, StringId name);
+
+ // Interns a set of Android legacy unnesteable async slice tracks
+ // associated with the given upid and name.
// Scoped is *not* supported for this track set type.
- TrackSetId InternAndroidSet(UniquePid, StringId name);
+ TrackSetId InternAndroidLegacyUnnestableTrackSet(UniquePid, StringId name);
// Starts a new slice on the given async track set which has the given cookie.
TrackId Begin(TrackSetId id, int64_t cookie);
- // Interns the expected and actual timeline tracks coming from FrameTimeline
- // producer for the associated upid.
- TrackSetId InternFrameTimelineSet(UniquePid, StringId name);
-
// Ends a new slice on the given async track set which has the given cookie.
TrackId End(TrackSetId id, int64_t cookie);
@@ -86,21 +86,11 @@
private:
friend class AsyncTrackSetTrackerUnittest;
- struct AndroidTuple {
+ struct ProcessTuple {
UniquePid upid;
StringId name;
- friend bool operator<(const AndroidTuple& l, const AndroidTuple& r) {
- return std::tie(l.upid, l.name) < std::tie(r.upid, r.name);
- }
- };
-
- struct FrameTimelineTuple {
- UniquePid upid;
- StringId name;
-
- friend bool operator<(const FrameTimelineTuple& l,
- const FrameTimelineTuple& r) {
+ friend bool operator<(const ProcessTuple& l, const ProcessTuple& r) {
return std::tie(l.upid, l.name) < std::tie(r.upid, r.name);
}
};
@@ -125,8 +115,8 @@
enum class TrackSetType {
kGlobal,
- kAndroid,
- kFrameTimeline,
+ kProcess,
+ kAndroidLegacyUnnestable,
};
struct TrackState {
@@ -150,11 +140,11 @@
struct TrackSet {
TrackSetType type;
union {
+ // Only set when |type| == |TrackSetType::kGlobal|.
StringId global_track_name;
- // Only set when |type| == |TrackSetType::kAndroid|.
- AndroidTuple android_tuple;
- // Only set when |type| == |TrackSetType::kFrameTimeline|.
- FrameTimelineTuple frame_timeline_tuple;
+ // Only set when |type| == |TrackSetType::kFrameTimeline| or
+ // |TrackSetType::kAndroidLegacyUnnestable|.
+ ProcessTuple process_tuple;
};
NestingBehaviour nesting_behaviour;
std::vector<TrackState> tracks;
@@ -162,8 +152,8 @@
TrackSetId CreateUnnestableTrackSetForTesting(UniquePid upid, StringId name) {
AsyncTrackSetTracker::TrackSet set;
- set.android_tuple = AndroidTuple{upid, name};
- set.type = AsyncTrackSetTracker::TrackSetType::kAndroid;
+ set.process_tuple = ProcessTuple{upid, name};
+ set.type = AsyncTrackSetTracker::TrackSetType::kAndroidLegacyUnnestable;
set.nesting_behaviour = NestingBehaviour::kUnnestable;
track_sets_.emplace_back(set);
return static_cast<TrackSetId>(track_sets_.size() - 1);
@@ -180,10 +170,12 @@
TrackId CreateTrackForSet(const TrackSet& set);
std::map<StringId, TrackSetId> global_track_set_ids_;
- std::map<AndroidTuple, TrackSetId> android_track_set_ids_;
- std::map<FrameTimelineTuple, TrackSetId> frame_timeline_track_set_ids_;
+ std::map<ProcessTuple, TrackSetId> process_track_set_ids_;
+ std::map<ProcessTuple, TrackSetId> android_legacy_unnestable_track_set_ids_;
std::vector<TrackSet> track_sets_;
+ const StringId android_source_ = kNullStringId;
+
TraceProcessorContext* const context_;
};
diff --git a/src/trace_processor/importers/proto/async_track_set_tracker_unittest.cc b/src/trace_processor/importers/proto/async_track_set_tracker_unittest.cc
index 852c5c2..eb2e041 100644
--- a/src/trace_processor/importers/proto/async_track_set_tracker_unittest.cc
+++ b/src/trace_processor/importers/proto/async_track_set_tracker_unittest.cc
@@ -39,8 +39,8 @@
unnestable_id_ = tracker_->CreateUnnestableTrackSetForTesting(
1, storage_->InternString("test"));
- legacy_unnestable_id_ =
- tracker_->InternAndroidSet(2, storage_->InternString("test"));
+ legacy_unnestable_id_ = tracker_->InternAndroidLegacyUnnestableTrackSet(
+ 2, storage_->InternString("test"));
}
protected:
@@ -57,7 +57,8 @@
namespace {
TEST_F(AsyncTrackSetTrackerUnittest, Smoke) {
- auto set_id = tracker_->InternAndroidSet(1, storage_->InternString("test"));
+ auto set_id = tracker_->InternAndroidLegacyUnnestableTrackSet(
+ 1, storage_->InternString("test"));
auto begin = tracker_->Begin(set_id, 1);
auto end = tracker_->End(set_id, 1);
diff --git a/src/trace_processor/importers/proto/flamegraph_construction_algorithms.cc b/src/trace_processor/importers/proto/flamegraph_construction_algorithms.cc
index 67184e5..77dc9ec 100644
--- a/src/trace_processor/importers/proto/flamegraph_construction_algorithms.cc
+++ b/src/trace_processor/importers/proto/flamegraph_construction_algorithms.cc
@@ -372,7 +372,7 @@
}
for (auto it = threads_in_pid_rm.IterateRows(); it; it.Next()) {
- utids.insert(storage->thread_table().id()[it.row()]);
+ utids.insert(storage->thread_table().id()[it.index()]);
}
// 3.Get all row indices in perf_sample that correspond to the requested utids
diff --git a/src/trace_processor/importers/proto/frame_timeline_event_parser.cc b/src/trace_processor/importers/proto/frame_timeline_event_parser.cc
index 70efab4..d03dc73 100644
--- a/src/trace_processor/importers/proto/frame_timeline_event_parser.cc
+++ b/src/trace_processor/importers/proto/frame_timeline_event_parser.cc
@@ -92,20 +92,29 @@
}
static bool DisplayFrameJanky(int32_t jank_type) {
- if (jank_type == FrameTimelineEvent::JANK_UNSPECIFIED || jank_type == FrameTimelineEvent::JANK_NONE)
+ if (jank_type == FrameTimelineEvent::JANK_UNSPECIFIED ||
+ jank_type == FrameTimelineEvent::JANK_NONE)
return false;
- int32_t display_frame_jank_bitmask = FrameTimelineEvent::JANK_SF_SCHEDULING | FrameTimelineEvent::JANK_PREDICTION_ERROR | FrameTimelineEvent::JANK_DISPLAY_HAL | FrameTimelineEvent::JANK_SF_CPU_DEADLINE_MISSED | FrameTimelineEvent::JANK_SF_GPU_DEADLINE_MISSED;
+ int32_t display_frame_jank_bitmask =
+ FrameTimelineEvent::JANK_SF_SCHEDULING |
+ FrameTimelineEvent::JANK_PREDICTION_ERROR |
+ FrameTimelineEvent::JANK_DISPLAY_HAL |
+ FrameTimelineEvent::JANK_SF_CPU_DEADLINE_MISSED |
+ FrameTimelineEvent::JANK_SF_GPU_DEADLINE_MISSED;
if (jank_type & display_frame_jank_bitmask)
- return true;
+ return true;
return false;
}
static bool SurfaceFrameJanky(int32_t jank_type) {
- if (jank_type == FrameTimelineEvent::JANK_UNSPECIFIED || jank_type == FrameTimelineEvent::JANK_NONE)
+ if (jank_type == FrameTimelineEvent::JANK_UNSPECIFIED ||
+ jank_type == FrameTimelineEvent::JANK_NONE)
return false;
- int32_t surface_frame_jank_bitmask = FrameTimelineEvent::JANK_APP_DEADLINE_MISSED | FrameTimelineEvent::JANK_UNKNOWN;
+ int32_t surface_frame_jank_bitmask =
+ FrameTimelineEvent::JANK_APP_DEADLINE_MISSED |
+ FrameTimelineEvent::JANK_UNKNOWN;
if (jank_type & surface_frame_jank_bitmask)
return true;
return false;
@@ -206,7 +215,7 @@
UniquePid upid = context_->process_tracker->GetOrCreateProcess(
static_cast<uint32_t>(event.pid()));
auto expected_track_set_id =
- context_->async_track_set_tracker->InternFrameTimelineSet(
+ context_->async_track_set_tracker->InternProcessTrackSet(
upid, expected_timeline_track_name_);
cookie_track_set_id_map_[cookie] = expected_track_set_id;
@@ -255,7 +264,7 @@
UniquePid upid = context_->process_tracker->GetOrCreateProcess(
static_cast<uint32_t>(event.pid()));
auto actual_track_set_id =
- context_->async_track_set_tracker->InternFrameTimelineSet(
+ context_->async_track_set_tracker->InternProcessTrackSet(
upid, actual_timeline_track_name_);
cookie_track_set_id_map_[cookie] = actual_track_set_id;
@@ -291,22 +300,21 @@
actual_row.jank_tag = jank_tag_none_id_;
}
- base::Optional<SliceId> opt_slice_id =
- context_->slice_tracker->BeginTyped(
- context_->storage->mutable_actual_frame_timeline_slice_table(),
- actual_row,
- [this, token, jank_type, present_type, prediction_type,
- &event](ArgsTracker::BoundInserter* inserter) {
- inserter->AddArg(display_frame_token_id_, Variadic::Integer(token));
- inserter->AddArg(present_type_id_, Variadic::String(present_type));
- inserter->AddArg(on_time_finish_id_,
- Variadic::Integer(event.on_time_finish()));
- inserter->AddArg(gpu_composition_id_,
- Variadic::Integer(event.gpu_composition()));
- inserter->AddArg(jank_type_id_, Variadic::String(jank_type));
- inserter->AddArg(prediction_type_id_,
- Variadic::String(prediction_type));
- });
+ base::Optional<SliceId> opt_slice_id = context_->slice_tracker->BeginTyped(
+ context_->storage->mutable_actual_frame_timeline_slice_table(),
+ actual_row,
+ [this, token, jank_type, present_type, prediction_type,
+ &event](ArgsTracker::BoundInserter* inserter) {
+ inserter->AddArg(display_frame_token_id_, Variadic::Integer(token));
+ inserter->AddArg(present_type_id_, Variadic::String(present_type));
+ inserter->AddArg(on_time_finish_id_,
+ Variadic::Integer(event.on_time_finish()));
+ inserter->AddArg(gpu_composition_id_,
+ Variadic::Integer(event.gpu_composition()));
+ inserter->AddArg(jank_type_id_, Variadic::String(jank_type));
+ inserter->AddArg(prediction_type_id_,
+ Variadic::String(prediction_type));
+ });
// SurfaceFrames will always be parsed before the matching DisplayFrame
// (since the app works on the frame before SurfaceFlinger does). Because
@@ -380,7 +388,7 @@
context_->storage->InternString(base::StringView(std::to_string(token)));
auto expected_track_set_id =
- context_->async_track_set_tracker->InternFrameTimelineSet(
+ context_->async_track_set_tracker->InternProcessTrackSet(
upid, expected_timeline_track_name_);
cookie_track_set_id_map_[cookie] = expected_track_set_id;
@@ -446,7 +454,7 @@
context_->storage->InternString(base::StringView(std::to_string(token)));
auto actual_track_set_id =
- context_->async_track_set_tracker->InternFrameTimelineSet(
+ context_->async_track_set_tracker->InternProcessTrackSet(
upid, actual_timeline_track_name_);
cookie_track_set_id_map_[cookie] = actual_track_set_id;
@@ -498,27 +506,26 @@
is_buffer = context_->storage->InternString("No");
}
- base::Optional<SliceId> opt_slice_id =
- context_->slice_tracker->BeginTyped(
- context_->storage->mutable_actual_frame_timeline_slice_table(),
- actual_row,
- [this, jank_type, present_type, token, layer_name_id,
- display_frame_token, prediction_type, is_buffer,
- &event](ArgsTracker::BoundInserter* inserter) {
- inserter->AddArg(surface_frame_token_id_, Variadic::Integer(token));
- inserter->AddArg(display_frame_token_id_,
- Variadic::Integer(display_frame_token));
- inserter->AddArg(layer_name_id_, Variadic::String(layer_name_id));
- inserter->AddArg(present_type_id_, Variadic::String(present_type));
- inserter->AddArg(on_time_finish_id_,
- Variadic::Integer(event.on_time_finish()));
- inserter->AddArg(gpu_composition_id_,
- Variadic::Integer(event.gpu_composition()));
- inserter->AddArg(jank_type_id_, Variadic::String(jank_type));
- inserter->AddArg(prediction_type_id_,
- Variadic::String(prediction_type));
- inserter->AddArg(is_buffer_id_, Variadic::String(is_buffer));
- });
+ base::Optional<SliceId> opt_slice_id = context_->slice_tracker->BeginTyped(
+ context_->storage->mutable_actual_frame_timeline_slice_table(),
+ actual_row,
+ [this, jank_type, present_type, token, layer_name_id, display_frame_token,
+ prediction_type, is_buffer,
+ &event](ArgsTracker::BoundInserter* inserter) {
+ inserter->AddArg(surface_frame_token_id_, Variadic::Integer(token));
+ inserter->AddArg(display_frame_token_id_,
+ Variadic::Integer(display_frame_token));
+ inserter->AddArg(layer_name_id_, Variadic::String(layer_name_id));
+ inserter->AddArg(present_type_id_, Variadic::String(present_type));
+ inserter->AddArg(on_time_finish_id_,
+ Variadic::Integer(event.on_time_finish()));
+ inserter->AddArg(gpu_composition_id_,
+ Variadic::Integer(event.gpu_composition()));
+ inserter->AddArg(jank_type_id_, Variadic::String(jank_type));
+ inserter->AddArg(prediction_type_id_,
+ Variadic::String(prediction_type));
+ inserter->AddArg(is_buffer_id_, Variadic::String(is_buffer));
+ });
if (opt_slice_id) {
display_token_to_surface_slice_.emplace(display_frame_token, *opt_slice_id);
diff --git a/src/trace_processor/importers/proto/heap_graph_tracker.cc b/src/trace_processor/importers/proto/heap_graph_tracker.cc
index 182265e..f789511 100644
--- a/src/trace_processor/importers/proto/heap_graph_tracker.cc
+++ b/src/trace_processor/importers/proto/heap_graph_tracker.cc
@@ -758,14 +758,14 @@
class_tbl.FilterToRowMap({class_tbl.name().eq("sun.misc.Cleaner")});
for (auto class_it = cleaner_classes.IterateRows(); class_it;
class_it.Next()) {
- auto class_id = class_tbl.id()[class_it.row()];
+ auto class_id = class_tbl.id()[class_it.index()];
auto cleaner_objs = objects_tbl.FilterToRowMap(
{objects_tbl.type_id().eq(class_id.value),
objects_tbl.upid().eq(seq.current_upid),
objects_tbl.graph_sample_ts().eq(seq.current_ts)});
for (auto obj_it = cleaner_objs.IterateRows(); obj_it; obj_it.Next()) {
tables::HeapGraphObjectTable::Id cleaner_obj_id =
- objects_tbl.id()[obj_it.row()];
+ objects_tbl.id()[obj_it.index()];
base::Optional<tables::HeapGraphObjectTable::Id> referent_id =
GetReferenceByFieldName(cleaner_obj_id, referent_str_id_);
base::Optional<tables::HeapGraphObjectTable::Id> thunk_id =
diff --git a/src/trace_processor/importers/proto/perf_sample_tracker.cc b/src/trace_processor/importers/proto/perf_sample_tracker.cc
index 042f33c..4da16ad 100644
--- a/src/trace_processor/importers/proto/perf_sample_tracker.cc
+++ b/src/trace_processor/importers/proto/perf_sample_tracker.cc
@@ -36,14 +36,49 @@
const char* StringifyCounter(int32_t counter) {
using protos::pbzero::PerfEvents;
switch (counter) {
- case (PerfEvents::SW_CPU_CLOCK):
+ // software:
+ case PerfEvents::SW_CPU_CLOCK:
return "cpu-clock";
- case (PerfEvents::SW_PAGE_FAULTS):
+ case PerfEvents::SW_PAGE_FAULTS:
return "page-faults";
- case (PerfEvents::HW_CPU_CYCLES):
+ case PerfEvents::SW_TASK_CLOCK:
+ return "task-clock";
+ case PerfEvents::SW_CONTEXT_SWITCHES:
+ return "context-switches";
+ case PerfEvents::SW_CPU_MIGRATIONS:
+ return "cpu-migrations";
+ case PerfEvents::SW_PAGE_FAULTS_MIN:
+ return "minor-faults";
+ case PerfEvents::SW_PAGE_FAULTS_MAJ:
+ return "major-faults";
+ case PerfEvents::SW_ALIGNMENT_FAULTS:
+ return "alignment-faults";
+ case PerfEvents::SW_EMULATION_FAULTS:
+ return "emulation-faults";
+ case PerfEvents::SW_DUMMY:
+ return "dummy";
+ // hardware:
+ case PerfEvents::HW_CPU_CYCLES:
return "cpu-cycles";
- case (PerfEvents::HW_INSTRUCTIONS):
+ case PerfEvents::HW_INSTRUCTIONS:
return "instructions";
+ case PerfEvents::HW_CACHE_REFERENCES:
+ return "cache-references";
+ case PerfEvents::HW_CACHE_MISSES:
+ return "cache-misses";
+ case PerfEvents::HW_BRANCH_INSTRUCTIONS:
+ return "branch-instructions";
+ case PerfEvents::HW_BRANCH_MISSES:
+ return "branch-misses";
+ case PerfEvents::HW_BUS_CYCLES:
+ return "bus-cycles";
+ case PerfEvents::HW_STALLED_CYCLES_FRONTEND:
+ return "stalled-cycles-frontend";
+ case PerfEvents::HW_STALLED_CYCLES_BACKEND:
+ return "stalled-cycles-backend";
+ case PerfEvents::HW_REF_CPU_CYCLES:
+ return "ref-cycles";
+
default:
break;
}
diff --git a/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc b/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
index ce94e27..d98af2d 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
+++ b/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
@@ -283,9 +283,9 @@
RowMap rm = args.FilterToRowMap({args.arg_set_id().eq(set_id)});
bool found = false;
for (auto it = rm.IterateRows(); it; it.Next()) {
- if (args.key()[it.row()] == key_id) {
- EXPECT_EQ(args.flat_key()[it.row()], key_id);
- if (storage_->GetArgValue(it.row()) == value) {
+ if (args.key()[it.index()] == key_id) {
+ EXPECT_EQ(args.flat_key()[it.index()], key_id);
+ if (storage_->GetArgValue(it.index()) == value) {
found = true;
break;
}
diff --git a/src/trace_processor/importers/systrace/systrace_parser.cc b/src/trace_processor/importers/systrace/systrace_parser.cc
index 1eca41c..7e9accd 100644
--- a/src/trace_processor/importers/systrace/systrace_parser.cc
+++ b/src/trace_processor/importers/systrace/systrace_parser.cc
@@ -23,6 +23,7 @@
#include "src/trace_processor/importers/common/slice_tracker.h"
#include "src/trace_processor/importers/common/track_tracker.h"
#include "src/trace_processor/importers/proto/async_track_set_tracker.h"
+#include "src/trace_processor/storage/trace_storage.h"
namespace perfetto {
namespace trace_processor {
@@ -61,7 +62,7 @@
int64_t value) {
systrace_utils::SystraceTracePoint point{};
point.name = name;
- point.value = value;
+ point.int_value = value;
// Hardcode the tgid to 0 (i.e. no tgid available) because zero events can
// come from kernel threads and as we group kernel threads into the kthreadd
@@ -110,7 +111,7 @@
// the UI.
point.tgid = 0;
- point.value = value;
+ point.int_value = value;
// Some versions of this trace point fill trace_type with one of (B/E/C),
// others use the trace_begin boolean and only support begin/end events:
if (trace_type == 0) {
@@ -129,15 +130,16 @@
int64_t ts,
uint32_t pid,
systrace_utils::SystraceTracePoint point) {
+ auto get_utid = [pid, &point, this]() {
+ if (point.tgid == 0)
+ return context_->process_tracker->GetOrCreateThread(pid);
+ return context_->process_tracker->UpdateThread(pid, point.tgid);
+ };
+
switch (point.phase) {
case 'B': {
StringId name_id = context_->storage->InternString(point.name);
- UniqueTid utid;
- if (point.tgid == 0) {
- utid = context_->process_tracker->GetOrCreateThread(pid);
- } else {
- utid = context_->process_tracker->UpdateThread(pid, point.tgid);
- }
+ UniqueTid utid = get_utid();
TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
context_->slice_tracker->Begin(ts, track_id, kNullStringId /* cat */,
name_id);
@@ -167,12 +169,13 @@
case 'S':
case 'F': {
StringId name_id = context_->storage->InternString(point.name);
- int64_t cookie = point.value;
+ int64_t cookie = point.int_value;
UniquePid upid =
context_->process_tracker->GetOrCreateProcess(point.tgid);
auto track_set_id =
- context_->async_track_set_tracker->InternAndroidSet(upid, name_id);
+ context_->async_track_set_tracker
+ ->InternAndroidLegacyUnnestableTrackSet(upid, name_id);
if (point.phase == 'S') {
// Historically, async slices on Android did not support nesting async
@@ -188,7 +191,7 @@
// the *most recent* emitted 'S' event which leads even more inaccurate
// behaviour. To support these quirks, we have the special 'unnestable'
// slice concept which implements workarounds for these very specific
- // issues. No other code should ever use this method.
+ // issues. No other code should ever use |BeginLegacyUnnestable|.
tables::SliceTable::Row row;
row.ts = ts;
row.track_id =
@@ -206,6 +209,28 @@
break;
}
+ case 'I': {
+ StringId name_id = context_->storage->InternString(point.name);
+ UniqueTid utid = get_utid();
+ TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+ context_->slice_tracker->Scoped(ts, track_id, kNullStringId, name_id, 0);
+ break;
+ }
+
+ case 'N': {
+ StringId name_id = context_->storage->InternString(point.name);
+ StringId track_name_id = context_->storage->InternString(point.str_value);
+ UniquePid upid =
+ context_->process_tracker->GetOrCreateProcess(point.tgid);
+ auto track_set_id =
+ context_->async_track_set_tracker->InternProcessTrackSet(
+ upid, track_name_id);
+ TrackId track_id =
+ context_->async_track_set_tracker->Scoped(track_set_id, ts, 0);
+ context_->slice_tracker->Scoped(ts, track_id, kNullStringId, name_id, 0);
+ break;
+ }
+
case 'C': {
// LMK events from userspace are hacked as counter events with the "value"
// of the counter representing the pid of the killed process which is
@@ -213,7 +238,7 @@
// Homogenise this with kernel LMK events as an instant event, ignoring
// the resets to 0.
if (point.name == "kill_one_process") {
- auto killed_pid = static_cast<uint32_t>(point.value);
+ auto killed_pid = static_cast<uint32_t>(point.int_value);
if (killed_pid != 0) {
UniquePid killed_upid =
context_->process_tracker->GetOrCreateProcess(killed_pid);
@@ -227,7 +252,7 @@
TrackId track =
context_->track_tracker->InternGlobalCounterTrack(screen_state_id_);
context_->event_tracker->PushCounter(
- ts, static_cast<double>(point.value), track);
+ ts, static_cast<double>(point.int_value), track);
return;
}
@@ -247,8 +272,8 @@
track_id =
context_->track_tracker->InternProcessCounterTrack(name_id, upid);
}
- context_->event_tracker->PushCounter(ts, static_cast<double>(point.value),
- track_id);
+ context_->event_tracker->PushCounter(
+ ts, static_cast<double>(point.int_value), track_id);
}
}
}
diff --git a/src/trace_processor/importers/systrace/systrace_parser.h b/src/trace_processor/importers/systrace/systrace_parser.h
index df7b92f..c6e506a 100644
--- a/src/trace_processor/importers/systrace/systrace_parser.h
+++ b/src/trace_processor/importers/systrace/systrace_parser.h
@@ -39,33 +39,48 @@
SystraceTracePoint() {}
static SystraceTracePoint B(uint32_t tgid, base::StringView name) {
- return SystraceTracePoint('B', tgid, std::move(name), 0);
+ return SystraceTracePoint('B', tgid, std::move(name), 0, {});
}
static SystraceTracePoint E(uint32_t tgid) {
- return SystraceTracePoint('E', tgid, {}, 0);
+ return SystraceTracePoint('E', tgid, {}, 0, {});
}
static SystraceTracePoint C(uint32_t tgid,
base::StringView name,
int64_t value) {
- return SystraceTracePoint('C', tgid, std::move(name), value);
+ return SystraceTracePoint('C', tgid, std::move(name), value, {});
}
static SystraceTracePoint S(uint32_t tgid,
base::StringView name,
int64_t cookie) {
- return SystraceTracePoint('S', tgid, std::move(name), cookie);
+ return SystraceTracePoint('S', tgid, std::move(name), cookie, {});
}
static SystraceTracePoint F(uint32_t tgid,
base::StringView name,
int64_t cookie) {
- return SystraceTracePoint('F', tgid, std::move(name), cookie);
+ return SystraceTracePoint('F', tgid, std::move(name), cookie, {});
}
- SystraceTracePoint(char p, uint32_t tg, base::StringView n, int64_t v)
- : phase(p), tgid(tg), name(std::move(n)), value(v) {}
+ static SystraceTracePoint I(uint32_t tgid, base::StringView name) {
+ return SystraceTracePoint('I', tgid, std::move(name), 0, {});
+ }
+
+ static SystraceTracePoint N(uint32_t tgid,
+ base::StringView track_name,
+ base::StringView name) {
+ return SystraceTracePoint('N', tgid, std::move(name), 0,
+ std::move(track_name));
+ }
+
+ SystraceTracePoint(char p,
+ uint32_t tg,
+ base::StringView n,
+ int64_t v,
+ base::StringView s)
+ : phase(p), tgid(tg), name(std::move(n)), int_value(v), str_value(s) {}
// Phase can be one of B, E, C, S, F.
char phase = '\0';
@@ -76,13 +91,17 @@
base::StringView name;
// For phase = 'C' (counter value) and 'B', 'F' (async cookie).
- int64_t value = 0;
+ int64_t int_value = 0;
+
+ // For phase = 'N' (instant on track)
+ base::StringView str_value;
// Visible for unittesting.
friend std::ostream& operator<<(std::ostream& os,
const SystraceTracePoint& point) {
return os << "SystraceTracePoint{'" << point.phase << "', " << point.tgid
- << ", \"" << point.name.ToStdString() << "\", " << point.value
+ << ", \"" << point.name.ToStdString() << "\", " << point.int_value
+ << ", \"" << point.str_value.ToStdString() << "\""
<< "}";
}
};
@@ -97,6 +116,8 @@
// Counters emitted by chromium can have a further "category group" appended
// ("Blob" in the example below). We ignore the category group.
// 7. C|3209|TransfersBytesPendingOnDisk-value|0|Blob
+// 8. I|4820|instant
+// 9. N|1938|track_name|instant_name
inline SystraceParseResult ParseSystraceTracePoint(
base::StringView str_untrimmed,
SystraceTracePoint* out) {
@@ -164,7 +185,26 @@
return SystraceParseResult::kFailure;
}
out->name = f2_name;
- out->value = *maybe_cookie;
+ out->int_value = *maybe_cookie;
+ return SystraceParseResult::kSuccess;
+ }
+ case 'I': { // Instant.
+ auto f2_name = read_next_field();
+ if (PERFETTO_UNLIKELY(!has_tgid || f2_name.empty())) {
+ return SystraceParseResult::kFailure;
+ }
+ out->name = f2_name;
+ return SystraceParseResult::kSuccess;
+ }
+ case 'N': { // Instant on track.
+ auto f2_track_name = read_next_field();
+ auto f3_name = read_next_field();
+ if (PERFETTO_UNLIKELY(!has_tgid || f2_track_name.empty() ||
+ f3_name.empty())) {
+ return SystraceParseResult::kFailure;
+ }
+ out->name = f3_name;
+ out->str_value = f2_track_name;
return SystraceParseResult::kSuccess;
}
case 'C': { // Counter.
@@ -176,7 +216,7 @@
return SystraceParseResult::kFailure;
}
out->name = f2_name;
- out->value = *maybe_value;
+ out->int_value = *maybe_value;
return SystraceParseResult::kSuccess;
}
default:
@@ -189,8 +229,8 @@
// Visible for unittesting.
inline bool operator==(const SystraceTracePoint& x,
const SystraceTracePoint& y) {
- return std::tie(x.phase, x.tgid, x.name, x.value) ==
- std::tie(y.phase, y.tgid, y.name, y.value);
+ return std::tie(x.phase, x.tgid, x.name, x.int_value, x.str_value) ==
+ std::tie(y.phase, y.tgid, y.name, y.int_value, y.str_value);
}
} // namespace systrace_utils
diff --git a/src/trace_processor/importers/systrace/systrace_parser_unittest.cc b/src/trace_processor/importers/systrace/systrace_parser_unittest.cc
index d0c89cf..be0d801 100644
--- a/src/trace_processor/importers/systrace/systrace_parser_unittest.cc
+++ b/src/trace_processor/importers/systrace/systrace_parser_unittest.cc
@@ -45,6 +45,8 @@
ASSERT_EQ(ParseSystraceTracePoint("C", &result), Result::kFailure);
ASSERT_EQ(ParseSystraceTracePoint("S", &result), Result::kFailure);
ASSERT_EQ(ParseSystraceTracePoint("F", &result), Result::kFailure);
+ ASSERT_EQ(ParseSystraceTracePoint("I", &result), Result::kFailure);
+ ASSERT_EQ(ParseSystraceTracePoint("N", &result), Result::kFailure);
ASSERT_EQ(ParseSystraceTracePoint("B|42|\n", &result), Result::kSuccess);
EXPECT_EQ(result, SystraceTracePoint::B(42, "[empty slice name]"));
@@ -85,6 +87,19 @@
Result::kSuccess);
EXPECT_EQ(result, SystraceTracePoint::F(123, "foo", 456));
+ ASSERT_EQ(ParseSystraceTracePoint("I||test", &result), Result::kFailure);
+ ASSERT_EQ(ParseSystraceTracePoint("I|123|", &result), Result::kFailure);
+ ASSERT_EQ(ParseSystraceTracePoint("I|123|event\n", &result),
+ Result::kSuccess);
+ EXPECT_EQ(result, SystraceTracePoint::I(123, "event"));
+
+ ASSERT_EQ(ParseSystraceTracePoint("N||test|test", &result), Result::kFailure);
+ ASSERT_EQ(ParseSystraceTracePoint("N|123|test|", &result), Result::kFailure);
+ ASSERT_EQ(ParseSystraceTracePoint("N|123||test", &result), Result::kFailure);
+ ASSERT_EQ(ParseSystraceTracePoint("N|123|track|event\n", &result),
+ Result::kSuccess);
+ EXPECT_EQ(result, SystraceTracePoint::N(123, "track", "event"));
+
ASSERT_EQ(ParseSystraceTracePoint("trace_event_clock_sync: parent_ts=0.123\n",
&result),
Result::kUnsupported);
diff --git a/src/trace_processor/iterator_impl.cc b/src/trace_processor/iterator_impl.cc
index c0b3aac..5ee1e1d 100644
--- a/src/trace_processor/iterator_impl.cc
+++ b/src/trace_processor/iterator_impl.cc
@@ -18,6 +18,7 @@
#include "perfetto/base/time.h"
#include "perfetto/trace_processor/trace_processor_storage.h"
+#include "src/trace_processor/sqlite/scoped_db.h"
#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/trace_processor_impl.h"
@@ -26,15 +27,15 @@
IteratorImpl::IteratorImpl(TraceProcessorImpl* trace_processor,
sqlite3* db,
+ base::Status status,
ScopedStmt stmt,
- uint32_t column_count,
- util::Status status,
+ StmtMetadata metadata,
uint32_t sql_stats_row)
: trace_processor_(trace_processor),
db_(db),
- stmt_(std::move(stmt)),
- column_count_(column_count),
status_(std::move(status)),
+ stmt_(std::move(stmt)),
+ stmt_metadata_(std::move(metadata)),
sql_stats_row_(sql_stats_row) {}
IteratorImpl::~IteratorImpl() {
@@ -58,7 +59,7 @@
Iterator::~Iterator() = default;
Iterator::Iterator(Iterator&&) noexcept = default;
-Iterator& Iterator::operator=(Iterator&&) = default;
+Iterator& Iterator::operator=(Iterator&&) noexcept = default;
bool Iterator::Next() {
return iterator_->Next();
@@ -76,9 +77,17 @@
return iterator_->ColumnCount();
}
-util::Status Iterator::Status() {
+base::Status Iterator::Status() {
return iterator_->Status();
}
+uint32_t Iterator::StatementCount() {
+ return iterator_->StatementCount();
+}
+
+uint32_t Iterator::StatementWithOutputCount() {
+ return iterator_->StatementCountWithOutput();
+}
+
} // namespace trace_processor
} // namespace perfetto
diff --git a/src/trace_processor/iterator_impl.h b/src/trace_processor/iterator_impl.h
index f286f2c..68068e6 100644
--- a/src/trace_processor/iterator_impl.h
+++ b/src/trace_processor/iterator_impl.h
@@ -28,6 +28,7 @@
#include "perfetto/trace_processor/iterator.h"
#include "perfetto/trace_processor/status.h"
#include "src/trace_processor/sqlite/scoped_db.h"
+#include "src/trace_processor/sqlite/sqlite_utils.h"
namespace perfetto {
namespace trace_processor {
@@ -36,11 +37,17 @@
class IteratorImpl {
public:
+ struct StmtMetadata {
+ uint32_t column_count = 0;
+ uint32_t statement_count = 0;
+ uint32_t statement_count_with_output = 0;
+ };
+
IteratorImpl(TraceProcessorImpl* impl,
sqlite3* db,
+ base::Status,
ScopedStmt,
- uint32_t column_count,
- util::Status,
+ StmtMetadata,
uint32_t sql_stats_row);
~IteratorImpl();
@@ -52,10 +59,25 @@
// Methods called by the base Iterator class.
bool Next() {
- // Delegate to the cc file to prevent trace_storage.h include in this file.
+ PERFETTO_DCHECK(stmt_ || !status_.ok());
+
if (!called_next_) {
+ // Delegate to the cc file to prevent trace_storage.h include in this
+ // file.
RecordFirstNextInSqlStats();
called_next_ = true;
+
+ // In the past, we used to call sqlite3_step for the first time in this
+ // function which 1:1 matched Next calls to sqlite3_step calls. However,
+ // with the introduction of multi-statement support, we call
+ // sqlite3_step when tokenizing the queries and so we need to *not* call
+ // step the first time Next is called.
+ //
+ // Aside: if we could, we would change the API to match the new setup
+ // (i.e. implement operator bool, make Next return nothing similar to C++
+ // iterators); however, too many clients depend on the current behavior so
+ // we have to keep the API as is.
+ return status_.ok() && !sqlite_utils::IsStmtDone(*stmt_);
}
if (!status_.ok())
@@ -63,7 +85,8 @@
int ret = sqlite3_step(*stmt_);
if (PERFETTO_UNLIKELY(ret != SQLITE_ROW && ret != SQLITE_DONE)) {
- status_ = util::ErrStatus("%s", sqlite3_errmsg(db_));
+ status_ = base::ErrStatus("%s (errcode %d)", sqlite3_errmsg(db_), ret);
+ stmt_.reset();
return false;
}
return ret == SQLITE_ROW;
@@ -101,12 +124,18 @@
}
std::string GetColumnName(uint32_t col) {
- return sqlite3_column_name(stmt_.get(), static_cast<int>(col));
+ return stmt_ ? sqlite3_column_name(*stmt_, static_cast<int>(col)) : "";
}
- uint32_t ColumnCount() { return column_count_; }
+ base::Status Status() { return status_; }
- util::Status Status() { return status_; }
+ uint32_t ColumnCount() { return stmt_metadata_.column_count; }
+
+ uint32_t StatementCount() { return stmt_metadata_.statement_count; }
+
+ uint32_t StatementCountWithOutput() {
+ return stmt_metadata_.statement_count_with_output;
+ }
private:
// Dummy function to pass to ScopedResource.
@@ -125,9 +154,10 @@
ScopedTraceProcessor trace_processor_;
sqlite3* db_ = nullptr;
+ base::Status status_;
+
ScopedStmt stmt_;
- uint32_t column_count_ = 0;
- util::Status status_;
+ StmtMetadata stmt_metadata_;
uint32_t sql_stats_row_ = 0;
bool called_next_ = false;
diff --git a/src/trace_processor/metrics/metrics.cc b/src/trace_processor/metrics/metrics.cc
index 6c2536e..757781a 100644
--- a/src/trace_processor/metrics/metrics.cc
+++ b/src/trace_processor/metrics/metrics.cc
@@ -623,7 +623,6 @@
if (metric_it == ctx->metrics->end()) {
return base::ErrStatus("RUN_METRIC: Unknown filename provided %s", path);
}
- const auto& sql = metric_it->sql;
std::unordered_map<std::string, std::string> substitutions;
for (size_t i = 1; i < argc; i += 2) {
@@ -642,27 +641,21 @@
substitutions[*key_str] = *value_str;
}
- for (const auto& query : base::SplitString(sql, ";\n")) {
- const auto& trimmed = base::TrimLeading(query);
- if (trimmed.empty())
- continue;
+ std::string subbed_sql;
+ int ret = TemplateReplace(metric_it->sql, substitutions, &subbed_sql);
+ if (ret) {
+ return base::ErrStatus(
+ "RUN_METRIC: Error when performing substitutions: %s",
+ metric_it->sql.c_str());
+ }
- std::string buffer;
- int ret = TemplateReplace(trimmed, substitutions, &buffer);
- if (ret) {
- return base::ErrStatus(
- "RUN_METRIC: Error when performing substitutions: %s", query.c_str());
- }
+ auto it = ctx->tp->ExecuteQuery(subbed_sql);
+ it.Next();
- PERFETTO_DLOG("RUN_METRIC: Executing query: %s", buffer.c_str());
- auto it = ctx->tp->ExecuteQuery(buffer);
- it.Next();
-
- base::Status status = it.Status();
- if (!status.ok()) {
- return base::ErrStatus("RUN_METRIC: Error when running file %s: %s", path,
- status.c_message());
- }
+ base::Status status = it.Status();
+ if (!status.ok()) {
+ return base::ErrStatus("RUN_METRIC: Error when running file %s: %s", path,
+ status.c_message());
}
return base::OkStatus();
}
@@ -731,18 +724,12 @@
return base::ErrStatus("Unknown metric %s", name.c_str());
const auto& sql_metric = *metric_it;
- for (const auto& outer : base::SplitString(sql_metric.sql, ";\n")) {
- for (const auto& query : base::SplitString(outer, ";\r\n")) {
- PERFETTO_DLOG("Executing query: %s", query.c_str());
- auto prep_it = tp->ExecuteQuery(query);
- prep_it.Next();
- RETURN_IF_ERROR(prep_it.Status());
- }
- }
+ auto prep_it = tp->ExecuteQuery(sql_metric.sql);
+ prep_it.Next();
+ RETURN_IF_ERROR(prep_it.Status());
auto output_query =
"SELECT * FROM " + sql_metric.output_table_name.value() + ";";
- PERFETTO_DLOG("Executing output query: %s", output_query.c_str());
PERFETTO_TP_TRACE("COMPUTE_METRIC_QUERY", [&](metatrace::Record* r) {
r->AddArg("SQL", output_query);
});
diff --git a/src/trace_processor/metrics/sql/BUILD.gn b/src/trace_processor/metrics/sql/BUILD.gn
index 9fd8e96..4135e50 100644
--- a/src/trace_processor/metrics/sql/BUILD.gn
+++ b/src/trace_processor/metrics/sql/BUILD.gn
@@ -74,6 +74,7 @@
"android/global_counter_span_view.sql",
"android/gpu_counter_span_view.sql",
"android/thread_counter_span_view.sql",
+ "android/android_trusty_workqueues.sql",
"android/unsymbolized_frames.sql",
"android/startup/launches_maxsdk28.sql",
"android/startup/launches_minsdk29.sql",
diff --git a/src/trace_processor/metrics/sql/android/android_irq_runtime.sql b/src/trace_processor/metrics/sql/android/android_irq_runtime.sql
index e57bdf5..e69683c 100644
--- a/src/trace_processor/metrics/sql/android/android_irq_runtime.sql
+++ b/src/trace_processor/metrics/sql/android/android_irq_runtime.sql
@@ -27,7 +27,7 @@
AS
SELECT ts, dur, name
FROM irq_runtime_all
-WHERE name LIKE 'IRQ (%)'
+WHERE name GLOB 'IRQ (*)'
ORDER BY dur DESC;
DROP VIEW IF EXISTS hw_irq_runtime_statistics;
@@ -46,7 +46,7 @@
AS
SELECT ts, dur, name
FROM irq_runtime_all
-WHERE name NOT LIKE 'IRQ (%)'
+WHERE name NOT GLOB 'IRQ (*)'
ORDER BY dur DESC;
CREATE VIEW sw_irq_runtime_statistics
diff --git a/src/trace_processor/metrics/sql/android/android_trusty_workqueues.sql b/src/trace_processor/metrics/sql/android/android_trusty_workqueues.sql
new file mode 100644
index 0000000..a036c9d
--- /dev/null
+++ b/src/trace_processor/metrics/sql/android/android_trusty_workqueues.sql
@@ -0,0 +1,21 @@
+-- Gather the `nop_work_func` slices and the CPU they each ran on and use that
+-- information to generate a metric that displays just the Trusty workqueue
+-- events grouped by CPU.
+DROP VIEW IF EXISTS android_trusty_workqueues_event;
+CREATE VIEW android_trusty_workqueues_event AS
+SELECT
+ 'slice' as track_type,
+ name as slice_name,
+ ts,
+ dur,
+ 'Cpu ' || EXTRACT_ARG(arg_set_id, 'cpu') as track_name,
+ 'Trusty Workqueues' as group_name
+FROM slice
+WHERE slice.name GLOB 'nop_work_func*';
+
+-- Generate the final metric output. This is empty because we're only using the
+-- metric to generate custom tracks, and so don't have any aggregate data to
+-- generate.
+DROP VIEW IF EXISTS android_trusty_workqueues_output;
+CREATE VIEW android_trusty_workqueues_output AS
+SELECT AndroidTrustyWorkqueues();
diff --git a/src/trace_processor/metrics/sql/android/java_heap_histogram.sql b/src/trace_processor/metrics/sql/android/java_heap_histogram.sql
index 7fd968c..7518594 100644
--- a/src/trace_processor/metrics/sql/android/java_heap_histogram.sql
+++ b/src/trace_processor/metrics/sql/android/java_heap_histogram.sql
@@ -40,36 +40,43 @@
)
SELECT * FROM cls_visitor;
-DROP VIEW IF EXISTS java_heap_histogram_output;
-CREATE VIEW java_heap_histogram_output AS
-WITH
--- Base histogram table
-heap_obj_histograms AS (
+DROP TABLE IF EXISTS heap_obj_histograms;
+CREATE TABLE heap_obj_histograms AS
SELECT
o.upid,
o.graph_sample_ts,
- IFNULL(c.deobfuscated_name, c.name) AS type_name,
- special.category,
+ o.type_id cls_id,
COUNT(1) obj_count,
- SUM(CASE o.reachable WHEN TRUE THEN 1 ELSE 0 END) reachable_obj_count
+ SUM(IIF(o.reachable, 1, 0)) reachable_obj_count,
+ SUM(self_size) / 1024 size_kb,
+ SUM(IIF(o.reachable, self_size, 0)) / 1024 reachable_size_kb,
+ SUM(native_size) / 1024 native_size_kb,
+ SUM(IIF(o.reachable, native_size, 0)) / 1024 reachable_native_size_kb
FROM heap_graph_object o
- JOIN heap_graph_class c ON o.type_id = c.id
- LEFT JOIN android_special_classes special ON special.cls_id = c.id
- GROUP BY 1, 2, 3, 4
- ORDER BY 6 DESC
-),
+ GROUP BY 1, 2, 3
+ ORDER BY 1, 2, 3;
+
+DROP VIEW IF EXISTS java_heap_histogram_output;
+CREATE VIEW java_heap_histogram_output AS
+WITH
-- Group by to build the repeated field by upid, ts
heap_obj_histogram_count_protos AS (
SELECT
upid,
graph_sample_ts,
RepeatedField(JavaHeapHistogram_TypeCount(
- 'type_name', type_name,
+ 'type_name', IFNULL(c.deobfuscated_name, c.name),
'category', category,
'obj_count', obj_count,
- 'reachable_obj_count', reachable_obj_count
+ 'reachable_obj_count', reachable_obj_count,
+ 'size_kb', size_kb,
+ 'reachable_size_kb', reachable_size_kb,
+ 'native_size_kb', native_size_kb,
+ 'reachable_native_size_kb', reachable_native_size_kb
)) AS count_protos
- FROM heap_obj_histograms
+ FROM heap_obj_histograms hist
+ JOIN heap_graph_class c ON hist.cls_id = c.id
+ LEFT JOIN android_special_classes special USING(cls_id)
GROUP BY 1, 2
),
-- Group by to build the repeated field by upid
diff --git a/src/trace_processor/metrics/sql/android/startup/launches.sql b/src/trace_processor/metrics/sql/android/startup/launches.sql
index 492bac5..999ba6e 100644
--- a/src/trace_processor/metrics/sql/android/startup/launches.sql
+++ b/src/trace_processor/metrics/sql/android/startup/launches.sql
@@ -37,17 +37,17 @@
WHERE name = 'android_sdk_version'
");
+SELECT CREATE_FUNCTION(
+ 'METRICS_LOGGER_SLICE_COUNT()',
+ 'INT',
+ "SELECT COUNT(1) FROM slice WHERE name GLOB 'MetricsLogger:*'"
+);
+
-- Note: on Q, we didn't have Android fingerprints but we *did*
-- have ActivityMetricsLogger events so we will use this approach
-- if we see any such events.
SELECT CASE
- WHEN (
- ANDROID_SDK_LEVEL() >= 29
- OR (
- SELECT COUNT(1) FROM slice
- WHERE name GLOB 'MetricsLogger:*'
- ) > 0
- )
+ WHEN (ANDROID_SDK_LEVEL() >= 29 OR METRICS_LOGGER_SLICE_COUNT() > 0)
THEN RUN_METRIC('android/startup/launches_minsdk29.sql')
ELSE RUN_METRIC('android/startup/launches_maxsdk28.sql')
END;
diff --git a/src/trace_processor/metrics/sql/chrome/gesture_flow_event.sql b/src/trace_processor/metrics/sql/chrome/gesture_flow_event.sql
index b9b54c2..69b824d 100644
--- a/src/trace_processor/metrics/sql/chrome/gesture_flow_event.sql
+++ b/src/trace_processor/metrics/sql/chrome/gesture_flow_event.sql
@@ -67,12 +67,13 @@
ts AS gesture_ts,
dur AS {{prefix}}_dur,
track_id AS gesture_track_id,
- trace_id AS {{id_field}},
+ trace_id AS {{prefix}}_trace_id,
jank,
- {{id_field}}
+ {{id_field}},
+ avg_vsync_interval
FROM {{prefix}}_jank
) gesture ON
- flow.trace_id = gesture.{{id_field}}
+ flow.trace_id = gesture.{{prefix}}_trace_id
UNION ALL
SELECT
'InputLatency::{{gesture_update}}' AS name,
@@ -94,9 +95,10 @@
ts AS gesture_ts,
dur AS {{prefix}}_dur,
track_id AS gesture_track_id,
- trace_id AS {{id_field}},
+ trace_id AS {{prefix}}_trace_id,
jank,
- {{id_field}}
+ {{id_field}},
+ avg_vsync_interval
FROM {{prefix}}_jank
ORDER BY {{id_field}} ASC, trace_id ASC, ts ASC;
@@ -138,7 +140,7 @@
MIN(ts) AS max_flow_ts
FROM {{prefix}}_latency_info_flow_step
WHERE
- trace_id = {{id_field}} AND
+ trace_id = {{prefix}}_trace_id AND
ts > gesture_ts + {{prefix}}_dur
GROUP BY gesture_slice_id;
@@ -183,6 +185,7 @@
curr.track_id,
curr.trace_id,
curr.{{id_field}},
+ curr.avg_vsync_interval,
curr.gesture_slice_id,
curr.gesture_ts,
curr.{{prefix}}_dur,
@@ -230,6 +233,7 @@
curr.dur,
curr.track_id,
curr.{{id_field}},
+ curr.avg_vsync_interval,
curr.gesture_slice_id AS {{prefix}}_slice_id,
curr.gesture_ts AS {{prefix}}_ts,
curr.{{prefix}}_dur AS {{prefix}}_dur,
diff --git a/src/trace_processor/metrics/sql/chrome/gesture_flow_event_queuing_delay.sql b/src/trace_processor/metrics/sql/chrome/gesture_flow_event_queuing_delay.sql
index 6701158..e2c069c 100644
--- a/src/trace_processor/metrics/sql/chrome/gesture_flow_event_queuing_delay.sql
+++ b/src/trace_processor/metrics/sql/chrome/gesture_flow_event_queuing_delay.sql
@@ -35,6 +35,7 @@
dur,
track_id,
{{id_field}},
+ avg_vsync_interval,
{{prefix}}_slice_id,
{{prefix}}_ts,
{{prefix}}_dur,
diff --git a/src/trace_processor/metrics/sql/chrome/gesture_jank.sql b/src/trace_processor/metrics/sql/chrome/gesture_jank.sql
index d025622..cfbc96b 100644
--- a/src/trace_processor/metrics/sql/chrome/gesture_jank.sql
+++ b/src/trace_processor/metrics/sql/chrome/gesture_jank.sql
@@ -170,7 +170,8 @@
dur,
track_id,
trace_id,
- dur/avg_vsync_interval AS gesture_frames_exact
+ dur/avg_vsync_interval AS gesture_frames_exact,
+ avg_vsync_interval
FROM joined_{{prefix}}_begin_and_end begin_and_end JOIN gesture_update ON
gesture_update.ts <= begin_and_end.end_ts AND
gesture_update.ts >= begin_and_end.begin_ts AND
diff --git a/src/trace_processor/metrics/sql/chrome/scroll_jank_cause_queuing_delay.sql b/src/trace_processor/metrics/sql/chrome/scroll_jank_cause_queuing_delay.sql
index 23a9908..35220d9 100644
--- a/src/trace_processor/metrics/sql/chrome/scroll_jank_cause_queuing_delay.sql
+++ b/src/trace_processor/metrics/sql/chrome/scroll_jank_cause_queuing_delay.sql
@@ -79,6 +79,7 @@
EXTRACT_ARG(slice.arg_set_id, "task.posted_from.function_name") as function,
trace_id,
queuing_time_ns,
+ avg_vsync_interval,
next_track_id,
CASE WHEN queuing.ancestor_end <= slice.ts THEN
CASE WHEN slice.ts + slice.dur <= queuing.maybe_next_ancestor_ts THEN
@@ -116,24 +117,23 @@
-- descendant slice. So all fields in base.* will be repeated ONCE for each
-- child, but if it has no slice it will occur only once but all the
-- |descendant_.*| fields will be NULL because of the LEFT JOIN.
--- Additionally for mojo events, append "(interface_name)" to the end of the
--- descendant name.
+-- Additionally for mojo events we replace the descendant_name with just the
+-- "interface_name" since that is more descriptive for our jank purposes.
DROP VIEW IF EXISTS all_descendant_blocking_tasks_queuing_delay;
CREATE VIEW all_descendant_blocking_tasks_queuing_delay AS
SELECT
descendant.id AS descendant_id,
descendant.ts AS descendant_ts,
descendant.dur AS descendant_dur,
- COALESCE(descendant.name || "(" ||
+ COALESCE(
IIF(descendant.arg_set_id IS NOT NULL,
EXTRACT_ARG(descendant.arg_set_id,
"chrome_mojo_event_info.watcher_notify_interface_tag"),
- NULL) || ")",
- descendant.name || "(" ||
- IIF(descendant.arg_set_id IS NOT NULL,
+ NULL),
+ IIF(descendant.arg_set_id IS NOT NULL,
EXTRACT_ARG(descendant.arg_set_id,
"chrome_mojo_event_info.mojo_interface_tag"),
- NULL) || ")",
+ NULL),
descendant.name) AS descendant_name,
descendant.parent_id As descendant_parent_id,
descendant.depth AS descendant_depth,
@@ -244,6 +244,7 @@
thread_name,
process_name,
function,
+ avg_vsync_interval,
GROUP_CONCAT(
CASE WHEN descendant_depth < invalid_depth OR descendant_major_slice THEN
descendant_id
@@ -310,7 +311,7 @@
, "-") AS java_name
FROM
blocking_tasks_queuing_delay_with_invalid_depth
- GROUP BY 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19
+ GROUP BY 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
ORDER BY descendant_cpu_percentage DESC;
@@ -351,20 +352,46 @@
END'
);
+SELECT CREATE_FUNCTION(
+ -- Function prototype: Takes a slice name, function, and file, and determines
+ -- if we should use the slice name, or if its a RunTask event uses the
+ -- function & file name, however if the RunTask posted from is one of the
+ -- simple_watcher paths we collapse them for attributation.
+ 'TopLevelName(name STRING, function STRING, file STRING)',
+ 'STRING',
+ -- The difference for the mojom functions are:
+ -- 1) PostDispatchNextMessageFromPipe:
+ -- We knew that there is a message in the pipe, didn't try to set up a
+ -- SimpleWatcher to monitor when a new one arrives.
+ -- 2) ArmOrNotify:
+ -- We tried to set up SimpleWatcher, but the setup failed as the
+ -- message arrived as we were setting this up, so we posted a task
+ -- instead.
+ -- 3) Notify:
+ -- SimpleWatcher was set up and after a period of monitoring detected
+ -- a new message.
+ -- For our jank use case this distinction isn't very useful so we group them
+ -- together.
+ 'SELECT
+ CASE WHEN $name = "ThreadControllerImpl::RunTask" THEN
+ CASE WHEN $function IN
+ ("PostDispatchNextMessageFromPipe", "ArmOrNotify", "Notify") THEN
+ "posted-from-mojo-pipe"
+ ELSE
+ "posted-from-" || $function || "()-in-" || $file
+ END
+ ELSE
+ $name
+ END'
+);
+
-- Create a common name for each "cause" based on the slice stack we found.
DROP VIEW IF EXISTS scroll_jank_cause_queuing_delay_temp;
CREATE VIEW scroll_jank_cause_queuing_delay_temp AS
SELECT
- CASE WHEN name = "ThreadControllerImpl::RunTask" THEN
- 'posted-from-' || function || '()-in-' || file
- ELSE
- name
- END || COALESCE("-" || descendant_name, "") AS location,
- CASE WHEN name = "ThreadControllerImpl::RunTask" THEN
- 'posted-from-' || function || '()-in-' || file
- ELSE
- name
- END || COALESCE(
+ TopLevelName(name, function, file) || COALESCE(
+ "-" || descendant_name, "") AS location,
+ TopLevelName(name, function, file) || COALESCE(
"-" || GetFirstSliceNameOrNull(mojom_name),
"-" || GetFirstSliceNameOrNull(toplevel_name),
"-" || GetFirstSliceNameOrNull(java_name),
diff --git a/src/trace_processor/rpc/query_result_serializer.cc b/src/trace_processor/rpc/query_result_serializer.cc
index f6042fd..d3b9ba2 100644
--- a/src/trace_processor/rpc/query_result_serializer.cc
+++ b/src/trace_processor/rpc/query_result_serializer.cc
@@ -62,9 +62,9 @@
bool QueryResultSerializer::Serialize(protos::pbzero::QueryResult* res) {
PERFETTO_CHECK(!eof_reached_);
- if (!did_write_column_names_) {
- SerializeColumnNames(res);
- did_write_column_names_ = true;
+ if (!did_write_metadata_) {
+ SerializeMetadata(res);
+ did_write_metadata_ = true;
}
// In case of an error we still want to go through SerializeBatch(). That will
@@ -258,11 +258,13 @@
res->set_error(err);
}
-void QueryResultSerializer::SerializeColumnNames(
+void QueryResultSerializer::SerializeMetadata(
protos::pbzero::QueryResult* res) {
- PERFETTO_DCHECK(!did_write_column_names_);
+ PERFETTO_DCHECK(!did_write_metadata_);
for (uint32_t c = 0; c < num_cols_; c++)
res->add_column_names(iter_->GetColumnName(c));
+ res->set_statement_count(iter_->StatementCount());
+ res->set_statement_with_output_count(iter_->StatementCountWithOutput());
}
} // namespace trace_processor
diff --git a/src/trace_processor/rpc/query_result_serializer.h b/src/trace_processor/rpc/query_result_serializer.h
index c29d66d..6cc55df 100644
--- a/src/trace_processor/rpc/query_result_serializer.h
+++ b/src/trace_processor/rpc/query_result_serializer.h
@@ -76,13 +76,13 @@
}
private:
- void SerializeColumnNames(protos::pbzero::QueryResult*);
+ void SerializeMetadata(protos::pbzero::QueryResult*);
void SerializeBatch(protos::pbzero::QueryResult*);
void MaybeSerializeError(protos::pbzero::QueryResult*);
std::unique_ptr<IteratorImpl> iter_;
const uint32_t num_cols_;
- bool did_write_column_names_ = false;
+ bool did_write_metadata_ = false;
bool eof_reached_ = false;
uint32_t col_ = UINT32_MAX;
diff --git a/src/trace_processor/rpc/query_result_serializer_unittest.cc b/src/trace_processor/rpc/query_result_serializer_unittest.cc
index a655410..85c882d 100644
--- a/src/trace_processor/rpc/query_result_serializer_unittest.cc
+++ b/src/trace_processor/rpc/query_result_serializer_unittest.cc
@@ -408,7 +408,7 @@
TestDeserializer deser;
deser.SerializeAndDeserialize(&ser);
EXPECT_EQ(deser.cells.size(), 0u);
- EXPECT_EQ(deser.error, "incomplete input");
+ EXPECT_EQ(deser.error, "incomplete input (errcode: 1)");
EXPECT_TRUE(deser.eof_reached);
}
diff --git a/src/trace_processor/sqlite/sql_stats_table.cc b/src/trace_processor/sqlite/sql_stats_table.cc
index 372dc1e..5902257 100644
--- a/src/trace_processor/sqlite/sql_stats_table.cc
+++ b/src/trace_processor/sqlite/sql_stats_table.cc
@@ -39,8 +39,6 @@
*schema = Schema(
{
SqliteTable::Column(Column::kQuery, "query", SqlValue::Type::kString),
- SqliteTable::Column(Column::kTimeQueued, "queued",
- SqlValue::Type::kLong),
SqliteTable::Column(Column::kTimeStarted, "started",
SqlValue::Type::kLong),
SqliteTable::Column(Column::kTimeFirstNext, "first_next",
@@ -48,7 +46,7 @@
SqliteTable::Column(Column::kTimeEnded, "ended",
SqlValue::Type::kLong),
},
- {Column::kTimeQueued});
+ {Column::kTimeStarted});
return util::OkStatus();
}
@@ -89,9 +87,6 @@
sqlite3_result_text(context, stats.queries()[row_].c_str(), -1,
sqlite_utils::kSqliteStatic);
break;
- case Column::kTimeQueued:
- sqlite3_result_int64(context, stats.times_queued()[row_]);
- break;
case Column::kTimeStarted:
sqlite3_result_int64(context, stats.times_started()[row_]);
break;
diff --git a/src/trace_processor/sqlite/sql_stats_table.h b/src/trace_processor/sqlite/sql_stats_table.h
index c146ee8..d2d1c5d 100644
--- a/src/trace_processor/sqlite/sql_stats_table.h
+++ b/src/trace_processor/sqlite/sql_stats_table.h
@@ -34,10 +34,9 @@
public:
enum Column {
kQuery = 0,
- kTimeQueued = 1,
- kTimeStarted = 2,
- kTimeFirstNext = 3,
- kTimeEnded = 4,
+ kTimeStarted = 1,
+ kTimeFirstNext = 2,
+ kTimeEnded = 3,
};
// Implementation of the SQLite cursor interface.
diff --git a/src/trace_processor/sqlite/sqlite_raw_table.cc b/src/trace_processor/sqlite/sqlite_raw_table.cc
index ec4c263..74475f0 100644
--- a/src/trace_processor/sqlite/sqlite_raw_table.cc
+++ b/src/trace_processor/sqlite/sqlite_raw_table.cc
@@ -182,9 +182,9 @@
// Go through each field id and find the entry in the args table for that
for (uint32_t i = 1; i <= max; ++i) {
for (auto it = row_map_.IterateRows(); it; it.Next()) {
- base::StringView key = args.key().GetString(it.row());
+ base::StringView key = args.key().GetString(it.index());
if (key == descriptor->fields[i].name) {
- (*field_id_to_arg_index)[i] = it.index();
+ (*field_id_to_arg_index)[i] = it.row();
break;
}
}
@@ -468,7 +468,7 @@
return;
}
for (auto it = row_map_.IterateRows(); it; it.Next()) {
- WriteArgAtRow(it.row(), DVW());
+ WriteArgAtRow(it.index(), DVW());
}
}
diff --git a/src/trace_processor/sqlite/sqlite_utils.h b/src/trace_processor/sqlite/sqlite_utils.h
index 9bb26d7..5315b0c 100644
--- a/src/trace_processor/sqlite/sqlite_utils.h
+++ b/src/trace_processor/sqlite/sqlite_utils.h
@@ -123,6 +123,38 @@
}
}
+inline base::Status PrepareStmt(sqlite3* db,
+ const char* sql,
+ ScopedStmt* stmt,
+ const char** tail) {
+ sqlite3_stmt* raw_stmt = nullptr;
+ int err = sqlite3_prepare_v2(db, sql, -1, &raw_stmt, tail);
+ stmt->reset(raw_stmt);
+ if (err != SQLITE_OK)
+ return base::ErrStatus("%s (errcode: %d)", sqlite3_errmsg(db), err);
+ return base::OkStatus();
+}
+
+inline bool IsStmtDone(sqlite3_stmt* stmt) {
+ return !sqlite3_stmt_busy(stmt);
+}
+
+inline base::Status StepStmtUntilDone(sqlite3_stmt* stmt) {
+ PERFETTO_DCHECK(stmt);
+
+ if (IsStmtDone(stmt))
+ return base::OkStatus();
+
+ int err;
+ for (err = sqlite3_step(stmt); err == SQLITE_ROW; err = sqlite3_step(stmt)) {
+ }
+ if (err != SQLITE_DONE) {
+ return base::ErrStatus("%s (errcode: %d)",
+ sqlite3_errmsg(sqlite3_db_handle(stmt)), err);
+ }
+ return base::OkStatus();
+}
+
inline util::Status GetColumnsForTable(
sqlite3* db,
const std::string& raw_table_name,
diff --git a/src/trace_processor/storage/trace_storage.cc b/src/trace_processor/storage/trace_storage.cc
index 20fdd51..1260d8f 100644
--- a/src/trace_processor/storage/trace_storage.cc
+++ b/src/trace_processor/storage/trace_storage.cc
@@ -80,18 +80,15 @@
TraceStorage::~TraceStorage() {}
uint32_t TraceStorage::SqlStats::RecordQueryBegin(const std::string& query,
- int64_t time_queued,
int64_t time_started) {
if (queries_.size() >= kMaxLogEntries) {
queries_.pop_front();
- times_queued_.pop_front();
times_started_.pop_front();
times_first_next_.pop_front();
times_ended_.pop_front();
popped_queries_++;
}
queries_.push_back(query);
- times_queued_.push_back(time_queued);
times_started_.push_back(time_started);
times_first_next_.push_back(0);
times_ended_.push_back(0);
diff --git a/src/trace_processor/storage/trace_storage.h b/src/trace_processor/storage/trace_storage.h
index af98024..14ecdb3 100644
--- a/src/trace_processor/storage/trace_storage.h
+++ b/src/trace_processor/storage/trace_storage.h
@@ -191,13 +191,11 @@
public:
static constexpr size_t kMaxLogEntries = 100;
uint32_t RecordQueryBegin(const std::string& query,
- int64_t time_queued,
int64_t time_started);
void RecordQueryFirstNext(uint32_t row, int64_t time_first_next);
void RecordQueryEnd(uint32_t row, int64_t time_end);
size_t size() const { return queries_.size(); }
const std::deque<std::string>& queries() const { return queries_; }
- const std::deque<int64_t>& times_queued() const { return times_queued_; }
const std::deque<int64_t>& times_started() const { return times_started_; }
const std::deque<int64_t>& times_first_next() const {
return times_first_next_;
@@ -208,7 +206,6 @@
uint32_t popped_queries_ = 0;
std::deque<std::string> queries_;
- std::deque<int64_t> times_queued_;
std::deque<int64_t> times_started_;
std::deque<int64_t> times_first_next_;
std::deque<int64_t> times_ended_;
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 2976e2c..97db464 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -17,7 +17,6 @@
#include "src/trace_processor/trace_processor_impl.h"
#include <algorithm>
-#include <cinttypes>
#include <memory>
#include "perfetto/base/logging.h"
@@ -49,6 +48,7 @@
#include "src/trace_processor/iterator_impl.h"
#include "src/trace_processor/sqlite/create_function.h"
#include "src/trace_processor/sqlite/register_function.h"
+#include "src/trace_processor/sqlite/scoped_db.h"
#include "src/trace_processor/sqlite/span_join_operator_table.h"
#include "src/trace_processor/sqlite/sql_stats_table.h"
#include "src/trace_processor/sqlite/sqlite3_str_split.h"
@@ -738,6 +738,106 @@
}
}
+void IncrementCountForStmt(sqlite3_stmt* stmt,
+ IteratorImpl::StmtMetadata* metadata) {
+ metadata->statement_count++;
+
+ // If the stmt is already done, it clearly didn't have any output.
+ if (sqlite_utils::IsStmtDone(stmt))
+ return;
+
+ // If the statement only has a single column and that column is named
+ // "suppress_query_output", treat it as a statement without output for
+ // accounting purposes. This is done so that embedders (e.g. shell) can
+ // strictly check that only the last query produces output while also
+ // providing an escape hatch for SELECT RUN_METRIC() invocations (which
+ // sadly produce output).
+ if (sqlite3_column_count(stmt) == 1 &&
+ strcmp(sqlite3_column_name(stmt, 0), "suppress_query_output") == 0) {
+ return;
+ }
+
+ // Otherwise, the statement has output and so increment the count.
+ metadata->statement_count_with_output++;
+}
+
+base::Status PrepareAndStepUntilLastValidStmt(
+ sqlite3* db,
+ const std::string& sql,
+ ScopedStmt* output_stmt,
+ IteratorImpl::StmtMetadata* metadata) {
+ ScopedStmt prev_stmt;
+ // A sql string can contain several statements. Some of them might be comment
+ // only, e.g. "SELECT 1; /* comment */; SELECT 2;". Here we process one
+ // statement on each iteration. SQLite's sqlite_prepare_v2 (wrapped by
+ // PrepareStmt) returns on each iteration a pointer to the unprocessed string.
+ //
+ // Unfortunately we cannot call PrepareStmt and tokenize all statements
+ // upfront because sqlite_prepare_v2 also semantically checks the statement
+ // against the schema. In some cases statements might depend on the execution
+ // of previous ones (e.e. CREATE VIEW x; SELECT FROM x; DELETE VIEW x;).
+ //
+ // Also, unfortunately, we need to PrepareStmt to find out if a statement is a
+ // comment or a real statement.
+ //
+ // The logic here is the following:
+ // - We invoke PrepareStmt on each statement.
+ // - If the statement is a comment we simply skip it.
+ // - If the statement is valid, we step once to make sure side effects take
+ // effect.
+ // - If we encounter a valid statement afterwards, we step internally through
+ // all rows of the previous one. This ensures that any further side effects
+ // take hold *before* we step into the next statement.
+ // - Once no further non-comment statements are encountered, we return an
+ // iterator to the last valid statement.
+ for (const char* rem_sql = sql.c_str(); rem_sql && rem_sql[0];) {
+ ScopedStmt cur_stmt;
+ {
+ PERFETTO_TP_TRACE("QUERY_PREPARE");
+ const char* tail = nullptr;
+ RETURN_IF_ERROR(sqlite_utils::PrepareStmt(db, rem_sql, &cur_stmt, &tail));
+ rem_sql = tail;
+ }
+
+ // The only situation where we'd have an ok status but also no prepared
+ // statement is if the statement of SQL we parsed was a pure comment. In
+ // this case, just continue to the next statement.
+ if (!cur_stmt)
+ continue;
+
+ // Before stepping into |cur_stmt|, we need to finish iterating through
+ // the previous statement so we don't have two clashing statements (e.g.
+ // SELECT * FROM v and DROP VIEW v) partially stepped into.
+ if (prev_stmt)
+ RETURN_IF_ERROR(sqlite_utils::StepStmtUntilDone(prev_stmt.get()));
+
+ PERFETTO_DLOG("Executing statement: %s", sqlite3_sql(*cur_stmt));
+
+ // Now step once into |cur_stmt| so that when we prepare the next statment
+ // we will have executed any dependent bytecode in this one.
+ int err = sqlite3_step(*cur_stmt);
+ if (err != SQLITE_ROW && err != SQLITE_DONE)
+ return base::ErrStatus("%s (errcode: %d)", sqlite3_errmsg(db), err);
+
+ // Increment the neecessary counts for the statement.
+ IncrementCountForStmt(cur_stmt.get(), metadata);
+
+ // Propogate the current statement to the next iteration.
+ prev_stmt = std::move(cur_stmt);
+ }
+
+ // If we didn't manage to prepare a single statment, that means everything
+ // in the SQL was treated as a comment.
+ if (!prev_stmt)
+ return base::ErrStatus("No valid SQL to run");
+
+ // Update the output statment and column count.
+ *output_stmt = std::move(prev_stmt);
+ metadata->column_count =
+ static_cast<uint32_t>(sqlite3_column_count(output_stmt->get()));
+ return base::OkStatus();
+}
+
} // namespace
TraceProcessorImpl::TraceProcessorImpl(const Config& cfg)
@@ -908,7 +1008,7 @@
TraceProcessorImpl::~TraceProcessorImpl() = default;
-util::Status TraceProcessorImpl::Parse(TraceBlobView blob) {
+base::Status TraceProcessorImpl::Parse(TraceBlobView blob) {
bytes_parsed_ += blob.size();
return TraceProcessorStorageImpl::Parse(std::move(blob));
}
@@ -980,31 +1080,21 @@
return deletion_list.size();
}
-Iterator TraceProcessorImpl::ExecuteQuery(const std::string& sql,
- int64_t time_queued) {
- sqlite3_stmt* raw_stmt;
- int err;
- {
- PERFETTO_TP_TRACE("QUERY_PREPARE");
- err = sqlite3_prepare_v2(*db_, sql.c_str(), static_cast<int>(sql.size()),
- &raw_stmt, nullptr);
- }
+Iterator TraceProcessorImpl::ExecuteQuery(const std::string& sql) {
+ PERFETTO_TP_TRACE("QUERY_EXECUTE");
- util::Status status;
- uint32_t col_count = 0;
- if (err != SQLITE_OK) {
- status = util::ErrStatus("%s", sqlite3_errmsg(*db_));
- } else {
- col_count = static_cast<uint32_t>(sqlite3_column_count(raw_stmt));
- }
-
- base::TimeNanos t_start = base::GetWallTimeNs();
uint32_t sql_stats_row =
- context_.storage->mutable_sql_stats()->RecordQueryBegin(sql, time_queued,
- t_start.count());
+ context_.storage->mutable_sql_stats()->RecordQueryBegin(
+ sql, base::GetWallTimeNs().count());
+
+ ScopedStmt stmt;
+ IteratorImpl::StmtMetadata metadata;
+ base::Status status =
+ PrepareAndStepUntilLastValidStmt(*db_, sql, &stmt, &metadata);
+ PERFETTO_DCHECK((status.ok() && stmt) || (!status.ok() && !stmt));
std::unique_ptr<IteratorImpl> impl(new IteratorImpl(
- this, *db_, ScopedStmt(raw_stmt), col_count, status, sql_stats_row));
+ this, *db_, status, std::move(stmt), std::move(metadata), sql_stats_row));
return Iterator(std::move(impl));
}
@@ -1024,7 +1114,7 @@
return field_idx != nullptr;
}
-util::Status TraceProcessorImpl::RegisterMetric(const std::string& path,
+base::Status TraceProcessorImpl::RegisterMetric(const std::string& path,
const std::string& sql) {
std::string stripped_sql;
for (base::StringSplitter sp(sql, '\n'); sp.Next();) {
@@ -1041,7 +1131,7 @@
[&path](const metrics::SqlMetricFile& m) { return m.path == path; });
if (it != sql_metrics_.end()) {
it->sql = stripped_sql;
- return util::OkStatus();
+ return base::OkStatus();
}
auto sep_idx = path.rfind('/');
@@ -1050,7 +1140,7 @@
auto sql_idx = basename.rfind(".sql");
if (sql_idx == std::string::npos) {
- return util::ErrStatus("Unable to find .sql extension for metric");
+ return base::ErrStatus("Unable to find .sql extension for metric");
}
auto no_ext_name = basename.substr(0, sql_idx);
@@ -1082,19 +1172,19 @@
}
sql_metrics_.emplace_back(metric);
- return util::OkStatus();
+ return base::OkStatus();
}
-util::Status TraceProcessorImpl::ExtendMetricsProto(const uint8_t* data,
+base::Status TraceProcessorImpl::ExtendMetricsProto(const uint8_t* data,
size_t size) {
return ExtendMetricsProto(data, size, /*skip_prefixes*/ {});
}
-util::Status TraceProcessorImpl::ExtendMetricsProto(
+base::Status TraceProcessorImpl::ExtendMetricsProto(
const uint8_t* data,
size_t size,
const std::vector<std::string>& skip_prefixes) {
- util::Status status =
+ base::Status status =
pool_.AddFromFileDescriptorSet(data, size, skip_prefixes);
if (!status.ok())
return status;
@@ -1109,27 +1199,27 @@
std::unique_ptr<metrics::BuildProto::Context>(
new metrics::BuildProto::Context{this, &pool_, &desc}));
}
- return util::OkStatus();
+ return base::OkStatus();
}
-util::Status TraceProcessorImpl::ComputeMetric(
+base::Status TraceProcessorImpl::ComputeMetric(
const std::vector<std::string>& metric_names,
std::vector<uint8_t>* metrics_proto) {
auto opt_idx = pool_.FindDescriptorIdx(".perfetto.protos.TraceMetrics");
if (!opt_idx.has_value())
- return util::Status("Root metrics proto descriptor not found");
+ return base::Status("Root metrics proto descriptor not found");
const auto& root_descriptor = pool_.descriptors()[opt_idx.value()];
return metrics::ComputeMetrics(this, metric_names, sql_metrics_, pool_,
root_descriptor, metrics_proto);
}
-util::Status TraceProcessorImpl::ComputeMetricText(
+base::Status TraceProcessorImpl::ComputeMetricText(
const std::vector<std::string>& metric_names,
TraceProcessor::MetricResultFormat format,
std::string* metrics_string) {
std::vector<uint8_t> metrics_proto;
- util::Status status = ComputeMetric(metric_names, &metrics_proto);
+ base::Status status = ComputeMetric(metric_names, &metrics_proto);
if (!status.ok())
return status;
switch (format) {
@@ -1155,7 +1245,7 @@
metatrace::Enable();
}
-util::Status TraceProcessorImpl::DisableAndReadMetatrace(
+base::Status TraceProcessorImpl::DisableAndReadMetatrace(
std::vector<uint8_t>* trace_proto) {
protozero::HeapBuffered<protos::pbzero::Trace> trace;
metatrace::DisableAndReadBuffer([&trace](metatrace::Record* record) {
@@ -1180,7 +1270,7 @@
}
});
*trace_proto = trace.SerializeAsArray();
- return util::OkStatus();
+ return base::OkStatus();
}
} // namespace trace_processor
diff --git a/src/trace_processor/trace_processor_impl.h b/src/trace_processor/trace_processor_impl.h
index 33ec127..7195a5c 100644
--- a/src/trace_processor/trace_processor_impl.h
+++ b/src/trace_processor/trace_processor_impl.h
@@ -57,27 +57,26 @@
~TraceProcessorImpl() override;
// TraceProcessorStorage implementation:
- util::Status Parse(TraceBlobView) override;
+ base::Status Parse(TraceBlobView) override;
void NotifyEndOfFile() override;
// TraceProcessor implementation:
- Iterator ExecuteQuery(const std::string& sql,
- int64_t time_queued = 0) override;
+ Iterator ExecuteQuery(const std::string& sql) override;
- util::Status RegisterMetric(const std::string& path,
+ base::Status RegisterMetric(const std::string& path,
const std::string& sql) override;
- util::Status ExtendMetricsProto(const uint8_t* data, size_t size) override;
+ base::Status ExtendMetricsProto(const uint8_t* data, size_t size) override;
- util::Status ExtendMetricsProto(
+ base::Status ExtendMetricsProto(
const uint8_t* data,
size_t size,
const std::vector<std::string>& skip_prefixes) override;
- util::Status ComputeMetric(const std::vector<std::string>& metric_names,
+ base::Status ComputeMetric(const std::vector<std::string>& metric_names,
std::vector<uint8_t>* metrics) override;
- util::Status ComputeMetricText(const std::vector<std::string>& metric_names,
+ base::Status ComputeMetricText(const std::vector<std::string>& metric_names,
TraceProcessor::MetricResultFormat format,
std::string* metrics_string) override;
@@ -92,7 +91,7 @@
void EnableMetatrace() override;
- util::Status DisableAndReadMetatrace(
+ base::Status DisableAndReadMetatrace(
std::vector<uint8_t>* trace_proto) override;
private:
diff --git a/src/trace_processor/trace_processor_shell.cc b/src/trace_processor/trace_processor_shell.cc
index f601930..1050120 100644
--- a/src/trace_processor/trace_processor_shell.cc
+++ b/src/trace_processor/trace_processor_shell.cc
@@ -192,7 +192,7 @@
#endif // PERFETTO_TP_LINENOISE
-util::Status PrintStats() {
+base::Status PrintStats() {
auto it = g_tp->ExecuteQuery(
"SELECT name, idx, source, value from stats "
"where severity IN ('error', 'data_loss') and value > 0");
@@ -237,20 +237,20 @@
fprintf(stderr, "\n");
}
- util::Status status = it.Status();
+ base::Status status = it.Status();
if (!status.ok()) {
- return util::ErrStatus("Error while iterating stats (%s)",
+ return base::ErrStatus("Error while iterating stats (%s)",
status.c_message());
}
- return util::OkStatus();
+ return base::OkStatus();
}
-util::Status ExportTraceToDatabase(const std::string& output_name) {
+base::Status ExportTraceToDatabase(const std::string& output_name) {
PERFETTO_CHECK(output_name.find('\'') == std::string::npos);
{
base::ScopedFile fd(base::OpenFile(output_name, O_CREAT | O_RDWR, 0600));
if (!fd)
- return util::ErrStatus("Failed to create file: %s", output_name.c_str());
+ return base::ErrStatus("Failed to create file: %s", output_name.c_str());
int res = ftruncate(fd.get(), 0);
PERFETTO_CHECK(res == 0);
}
@@ -261,9 +261,9 @@
bool attach_has_more = attach_it.Next();
PERFETTO_DCHECK(!attach_has_more);
- util::Status status = attach_it.Status();
+ base::Status status = attach_it.Status();
if (!status.ok())
- return util::ErrStatus("SQLite error: %s", status.c_message());
+ return base::ErrStatus("SQLite error: %s", status.c_message());
// Export real and virtual tables.
auto tables_it = g_tp->ExecuteQuery(
@@ -281,11 +281,11 @@
status = export_it.Status();
if (!status.ok())
- return util::ErrStatus("SQLite error: %s", status.c_message());
+ return base::ErrStatus("SQLite error: %s", status.c_message());
}
status = tables_it.Status();
if (!status.ok())
- return util::ErrStatus("SQLite error: %s", status.c_message());
+ return base::ErrStatus("SQLite error: %s", status.c_message());
// Export views.
auto views_it =
@@ -305,18 +305,18 @@
status = export_it.Status();
if (!status.ok())
- return util::ErrStatus("SQLite error: %s", status.c_message());
+ return base::ErrStatus("SQLite error: %s", status.c_message());
}
status = views_it.Status();
if (!status.ok())
- return util::ErrStatus("SQLite error: %s", status.c_message());
+ return base::ErrStatus("SQLite error: %s", status.c_message());
auto detach_it = g_tp->ExecuteQuery("DETACH DATABASE perfetto_export");
bool detach_has_more = attach_it.Next();
PERFETTO_DCHECK(!detach_has_more);
status = detach_it.Status();
- return status.ok() ? util::OkStatus()
- : util::ErrStatus("SQLite error: %s", status.c_message());
+ return status.ok() ? base::OkStatus()
+ : base::ErrStatus("SQLite error: %s", status.c_message());
}
class ErrorPrinter : public google::protobuf::io::ErrorCollector {
@@ -338,7 +338,7 @@
: metric_path.substr(slash_idx + 1);
}
-util::Status RegisterMetric(const std::string& register_metric) {
+base::Status RegisterMetric(const std::string& register_metric) {
std::string sql;
base::ReadFile(register_metric, &sql);
@@ -363,7 +363,7 @@
return base::OkStatus();
}
-util::Status ExtendMetricsProto(const std::string& extend_metrics_proto,
+base::Status ExtendMetricsProto(const std::string& extend_metrics_proto,
google::protobuf::DescriptorPool* pool) {
google::protobuf::FileDescriptorSet desc_set;
auto* file_desc = desc_set.add_file();
@@ -392,7 +392,7 @@
base::Optional<std::string> no_ext_path;
};
-util::Status RunMetrics(const std::vector<MetricNameAndPath>& metrics,
+base::Status RunMetrics(const std::vector<MetricNameAndPath>& metrics,
OutputFormat format,
const google::protobuf::DescriptorPool& pool) {
std::vector<std::string> metric_names(metrics.size());
@@ -402,21 +402,21 @@
if (format == OutputFormat::kTextProto) {
std::string out;
- util::Status status =
+ base::Status status =
g_tp->ComputeMetricText(metric_names, TraceProcessor::kProtoText, &out);
if (!status.ok()) {
- return util::ErrStatus("Error when computing metrics: %s",
+ return base::ErrStatus("Error when computing metrics: %s",
status.c_message());
}
out += '\n';
fwrite(out.c_str(), sizeof(char), out.size(), stdout);
- return util::OkStatus();
+ return base::OkStatus();
}
std::vector<uint8_t> metric_result;
- util::Status status = g_tp->ComputeMetric(metric_names, &metric_result);
+ base::Status status = g_tp->ComputeMetric(metric_names, &metric_result);
if (!status.ok()) {
- return util::ErrStatus("Error when computing metrics: %s",
+ return base::ErrStatus("Error when computing metrics: %s",
status.c_message());
}
@@ -451,16 +451,18 @@
PERFETTO_FATAL("This case was already handled.");
}
- return util::OkStatus();
+ return base::OkStatus();
}
void PrintQueryResultInteractively(Iterator* it,
base::TimeNanos t_start,
uint32_t column_width) {
- base::TimeNanos t_end = t_start;
+ base::TimeNanos t_end = base::GetWallTimeNs();
for (uint32_t rows = 0; it->Next(); rows++) {
if (rows % 32 == 0) {
- if (rows > 0) {
+ if (rows == 0) {
+ t_end = base::GetWallTimeNs();
+ } else {
fprintf(stderr, "...\nType 'q' to stop, Enter for more records: ");
fflush(stderr);
char input[32];
@@ -468,8 +470,6 @@
exit(0);
if (input[0] == 'q')
break;
- } else {
- t_end = base::GetWallTimeNs();
}
for (uint32_t i = 0; i < it->ColumnCount(); i++)
printf("%-*.*s ", column_width, column_width,
@@ -507,7 +507,7 @@
printf("\n");
}
- util::Status status = it->Status();
+ base::Status status = it->Status();
if (!status.ok()) {
PERFETTO_ELOG("SQLite error: %s", status.c_message());
}
@@ -515,7 +515,7 @@
static_cast<double>((t_end - t_start).count()) / 1E6);
}
-util::Status PrintQueryResultAsCsv(Iterator* it, FILE* output) {
+base::Status PrintQueryResultAsCsv(Iterator* it, bool has_more, FILE* output) {
for (uint32_t c = 0; c < it->ColumnCount(); c++) {
if (c > 0)
fprintf(output, ",");
@@ -523,7 +523,8 @@
}
fprintf(output, "\n");
- for (uint32_t rows = 0; it->Next(); rows++) {
+ uint32_t rows;
+ for (rows = 0; has_more; rows++, has_more = it->Next()) {
for (uint32_t c = 0; c < it->ColumnCount(); c++) {
if (c > 0)
fprintf(output, ",");
@@ -552,112 +553,59 @@
return it->Status();
}
-bool IsCommentLine(const std::string& buffer) {
- return base::StartsWith(buffer, "--");
+base::Status RunQueriesWithoutOutput(const std::string& sql_query) {
+ auto it = g_tp->ExecuteQuery(sql_query);
+ if (it.StatementWithOutputCount() > 0)
+ return base::ErrStatus("Unexpected result from a query.");
+
+ RETURN_IF_ERROR(it.Status());
+ return it.Next() ? base::ErrStatus("Unexpected result from a query.")
+ : it.Status();
}
-bool HasEndOfQueryDelimiter(const std::string& buffer) {
- return base::EndsWith(buffer, ";\n") || base::EndsWith(buffer, ";") ||
- base::EndsWith(buffer, ";\r\n");
-}
-
-util::Status LoadQueries(FILE* input, std::vector<std::string>* output) {
- char buffer[4096];
- while (!feof(input) && !ferror(input)) {
- std::string sql_query;
- while (fgets(buffer, sizeof(buffer), input)) {
- std::string line = base::TrimLeading(buffer);
-
- if (IsCommentLine(line))
- continue;
-
- sql_query.append(line);
-
- if (HasEndOfQueryDelimiter(line))
- break;
- }
- if (!sql_query.empty() && sql_query.back() == '\n')
- sql_query.resize(sql_query.size() - 1);
-
- // If we have a new line at the end of the file or an extra new line
- // somewhere in the file, we'll end up with an empty query which we should
- // just ignore.
- if (sql_query.empty())
- continue;
-
- output->push_back(sql_query);
- }
- if (ferror(input)) {
- return util::ErrStatus("Error reading query file");
- }
- return util::OkStatus();
-}
-
-util::Status RunQueriesWithoutOutput(const std::vector<std::string>& queries) {
- for (const auto& sql_query : queries) {
- PERFETTO_DLOG("Executing query: %s", sql_query.c_str());
-
- auto it = g_tp->ExecuteQuery(sql_query);
- RETURN_IF_ERROR(it.Status());
- if (it.Next()) {
- return util::ErrStatus("Unexpected result from a query.");
- }
- RETURN_IF_ERROR(it.Status());
- }
- return util::OkStatus();
-}
-
-util::Status RunQueriesAndPrintResult(const std::vector<std::string>& queries,
+base::Status RunQueriesAndPrintResult(const std::string& sql_query,
FILE* output) {
- bool is_first_query = true;
- bool has_output = false;
- for (const auto& sql_query : queries) {
- // Add an extra newline separator between query results.
- if (!is_first_query)
- fprintf(output, "\n");
- is_first_query = false;
+ PERFETTO_ILOG("Executing query: %s", sql_query.c_str());
+ auto query_start = std::chrono::steady_clock::now();
- PERFETTO_ILOG("Executing query: %s", sql_query.c_str());
+ auto it = g_tp->ExecuteQuery(sql_query);
+ RETURN_IF_ERROR(it.Status());
- auto it = g_tp->ExecuteQuery(sql_query);
- RETURN_IF_ERROR(it.Status());
- if (it.ColumnCount() == 0) {
- bool it_has_more = it.Next();
- RETURN_IF_ERROR(it.Status());
- PERFETTO_DCHECK(!it_has_more);
- continue;
- }
+ bool has_more = it.Next();
+ RETURN_IF_ERROR(it.Status());
- // If we have a single column with the name |suppress_query_output| that's
- // a hint to shell that it should not treat the query as having real
- // meaning.
- if (it.ColumnCount() == 1 &&
- it.GetColumnName(0) == "suppress_query_output") {
- // We should only see a single null value as this feature is usually used
- // as SELECT RUN_METRIC(<metric file>) as suppress_query_output and
- // RUN_METRIC returns a single null.
- bool has_next = it.Next();
- RETURN_IF_ERROR(it.Status());
- PERFETTO_DCHECK(has_next);
- PERFETTO_DCHECK(it.Get(0).is_null());
-
- has_next = it.Next();
- RETURN_IF_ERROR(it.Status());
- PERFETTO_DCHECK(!has_next);
- continue;
- }
-
- if (has_output) {
- return util::ErrStatus(
- "More than one query generated result rows. This is unsupported.");
- }
- has_output = true;
- RETURN_IF_ERROR(PrintQueryResultAsCsv(&it, output));
+ uint32_t prev_count = it.StatementCount() - 1;
+ uint32_t prev_with_output = has_more ? it.StatementWithOutputCount() - 1
+ : it.StatementWithOutputCount();
+ uint32_t prev_without_output_count = prev_count - prev_with_output;
+ if (prev_with_output > 0) {
+ return base::ErrStatus(
+ "Result rows were returned for multiples queries. Ensure that only the "
+ "final statement is a SELECT statment or use `suppress_query_output` "
+ "to prevent function invocations causing this "
+ "error (see "
+ "https://perfetto.dev/docs/contributing/"
+ "testing#trace-processor-diff-tests).");
}
- return util::OkStatus();
+ for (uint32_t i = 0; i < prev_without_output_count; ++i) {
+ fprintf(output, "\n");
+ }
+ if (it.ColumnCount() == 0) {
+ PERFETTO_DCHECK(!has_more);
+ return base::OkStatus();
+ }
+
+ auto query_end = std::chrono::steady_clock::now();
+ RETURN_IF_ERROR(PrintQueryResultAsCsv(&it, has_more, output));
+
+ auto dur = query_end - query_start;
+ PERFETTO_ILOG(
+ "Query execution time: %lld ms",
+ std::chrono::duration_cast<std::chrono::milliseconds>(dur).count());
+ return base::OkStatus();
}
-util::Status PrintPerfFile(const std::string& perf_file_path,
+base::Status PrintPerfFile(const std::string& perf_file_path,
base::TimeNanos t_load,
base::TimeNanos t_run) {
char buf[128];
@@ -665,15 +613,15 @@
static_cast<int64_t>(t_load.count()),
static_cast<int64_t>(t_run.count()));
if (count == 0) {
- return util::ErrStatus("Failed to write perf data");
+ return base::ErrStatus("Failed to write perf data");
}
auto fd(base::OpenFile(perf_file_path, O_WRONLY | O_CREAT | O_TRUNC, 0666));
if (!fd) {
- return util::ErrStatus("Failed to open perf file");
+ return base::ErrStatus("Failed to open perf file");
}
base::WriteAll(fd.get(), buf, count);
- return util::OkStatus();
+ return base::OkStatus();
}
class MetricExtension {
@@ -948,14 +896,14 @@
}
}
-util::Status LoadTrace(const std::string& trace_file_path, double* size_mb) {
- util::Status read_status =
+base::Status LoadTrace(const std::string& trace_file_path, double* size_mb) {
+ base::Status read_status =
ReadTrace(g_tp, trace_file_path.c_str(), [&size_mb](size_t parsed_size) {
*size_mb = static_cast<double>(parsed_size) / 1E6;
fprintf(stderr, "\rLoading trace: %.2f MB\r", *size_mb);
});
if (!read_status.ok()) {
- return util::ErrStatus("Could not read trace file (path: %s): %s",
+ return base::ErrStatus("Could not read trace file (path: %s): %s",
trace_file_path.c_str(), read_status.c_message());
}
@@ -992,30 +940,25 @@
}
});
}
- return util::OkStatus();
+ return base::OkStatus();
}
-util::Status RunQueries(const std::string& query_file_path,
+base::Status RunQueries(const std::string& query_file_path,
bool expect_output) {
- std::vector<std::string> queries;
- base::ScopedFstream file(fopen(query_file_path.c_str(), "r"));
- if (!file) {
- return util::ErrStatus("Could not open query file (path: %s)",
- query_file_path.c_str());
- }
- RETURN_IF_ERROR(LoadQueries(file.get(), &queries));
+ std::string queries;
+ base::ReadFile(query_file_path.c_str(), &queries);
- util::Status status;
+ base::Status status;
if (expect_output) {
status = RunQueriesAndPrintResult(queries, stdout);
} else {
status = RunQueriesWithoutOutput(queries);
}
if (!status.ok()) {
- return util::ErrStatus("Encountered error while running queries: %s",
+ return base::ErrStatus("Encountered error while running queries: %s",
status.c_message());
}
- return util::OkStatus();
+ return base::OkStatus();
}
base::Status ParseSingleMetricExtensionPath(bool dev,
@@ -1045,7 +988,7 @@
return base::Status(
"Cannot have 'shell/' as metric extension virtual path.");
}
- return util::OkStatus();
+ return base::OkStatus();
}
base::Status CheckForDuplicateMetricExtension(
@@ -1157,7 +1100,7 @@
return base::OkStatus();
}
-util::Status PopulateDescriptorPool(
+base::Status PopulateDescriptorPool(
google::protobuf::DescriptorPool& pool,
const std::vector<MetricExtension>& metric_extensions) {
// TODO(b/182165266): There is code duplication here with trace_processor_impl
@@ -1176,7 +1119,7 @@
return base::OkStatus();
}
-util::Status LoadMetrics(const std::string& raw_metric_names,
+base::Status LoadMetrics(const std::string& raw_metric_names,
google::protobuf::DescriptorPool& pool,
std::vector<MetricNameAndPath>& name_and_path) {
std::vector<std::string> split;
@@ -1198,15 +1141,15 @@
std::string no_ext_path = metric_or_path.substr(0, ext_idx);
// The proto must be extended before registering the metric.
- util::Status status = ExtendMetricsProto(no_ext_path + ".proto", &pool);
+ base::Status status = ExtendMetricsProto(no_ext_path + ".proto", &pool);
if (!status.ok()) {
- return util::ErrStatus("Unable to extend metrics proto %s: %s",
+ return base::ErrStatus("Unable to extend metrics proto %s: %s",
metric_or_path.c_str(), status.c_message());
}
status = RegisterMetric(no_ext_path + ".sql");
if (!status.ok()) {
- return util::ErrStatus("Unable to register metric %s: %s",
+ return base::ErrStatus("Unable to register metric %s: %s",
metric_or_path.c_str(), status.c_message());
}
name_and_path.emplace_back(
@@ -1268,7 +1211,7 @@
const google::protobuf::DescriptorPool* pool;
};
-util::Status StartInteractiveShell(const InteractiveOptions& options) {
+base::Status StartInteractiveShell(const InteractiveOptions& options) {
SetupLineEditor();
uint32_t column_width = options.column_width;
@@ -1294,7 +1237,7 @@
} else if (strcmp(command, "reset") == 0) {
g_tp->RestoreInitialTables();
} else if (strcmp(command, "read") == 0 && strlen(arg)) {
- util::Status status = RunQueries(arg, true);
+ base::Status status = RunQueries(arg, true);
if (!status.ok()) {
PERFETTO_ELOG("%s", status.c_message());
}
@@ -1332,10 +1275,10 @@
auto it = g_tp->ExecuteQuery(line.get());
PrintQueryResultInteractively(&it, t_start, column_width);
}
- return util::OkStatus();
+ return base::OkStatus();
}
-util::Status TraceProcessorMain(int argc, char** argv) {
+base::Status TraceProcessorMain(int argc, char** argv) {
CommandLineOptions options = ParseCommandLineOptions(argc, argv);
Config config;
@@ -1434,21 +1377,21 @@
if (!options.metatrace_path.empty()) {
std::vector<uint8_t> serialized;
- util::Status status = g_tp->DisableAndReadMetatrace(&serialized);
+ base::Status status = g_tp->DisableAndReadMetatrace(&serialized);
if (!status.ok())
return status;
auto file =
base::OpenFile(options.metatrace_path, O_CREAT | O_RDWR | O_TRUNC);
if (!file)
- return util::ErrStatus("Unable to open metatrace file");
+ return base::ErrStatus("Unable to open metatrace file");
ssize_t res = base::WriteAll(*file, serialized.data(), serialized.size());
if (res < 0)
- return util::ErrStatus("Error while writing metatrace file");
+ return base::ErrStatus("Error while writing metatrace file");
}
- return util::OkStatus();
+ return base::OkStatus();
}
} // namespace
diff --git a/src/trace_processor/trace_sorter.h b/src/trace_processor/trace_sorter.h
index 6625373..7df33a6 100644
--- a/src/trace_processor/trace_sorter.h
+++ b/src/trace_processor/trace_sorter.h
@@ -17,22 +17,19 @@
#ifndef SRC_TRACE_PROCESSOR_TRACE_SORTER_H_
#define SRC_TRACE_PROCESSOR_TRACE_SORTER_H_
+#include <algorithm>
+#include <memory>
+#include <utility>
#include <vector>
#include "perfetto/ext/base/circular_queue.h"
#include "perfetto/trace_processor/basic_types.h"
#include "perfetto/trace_processor/trace_blob_view.h"
-#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/timestamped_trace_piece.h"
-namespace Json {
-class Value;
-} // namespace Json
-
namespace perfetto {
namespace trace_processor {
-class FuchsiaProviderView;
class PacketSequenceState;
struct SystraceLine;
diff --git a/src/trace_processor/types/BUILD.gn b/src/trace_processor/types/BUILD.gn
index 46d16b9..ba26e24 100644
--- a/src/trace_processor/types/BUILD.gn
+++ b/src/trace_processor/types/BUILD.gn
@@ -21,6 +21,7 @@
"softirq_action.h",
"task_state.cc",
"task_state.h",
+ "tcp_state.h",
"trace_processor_context.h",
"variadic.cc",
"variadic.h",
diff --git a/src/trace_processor/types/tcp_state.h b/src/trace_processor/types/tcp_state.h
new file mode 100644
index 0000000..1db06de
--- /dev/null
+++ b/src/trace_processor/types/tcp_state.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 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_TYPES_TCP_STATE_H_
+#define SRC_TRACE_PROCESSOR_TYPES_TCP_STATE_H_
+
+namespace perfetto {
+namespace trace_processor {
+
+// IPV4 protocol
+constexpr int kAfNet = 2;
+// IPV6 protocol
+constexpr int kAfNet6 = 10;
+// TCP protocol
+constexpr int kIpprotoTcp = 6;
+// TCP protocol states, from include/net/tcp_states.h.
+enum {
+ TCP_ESTABLISHED = 1,
+ TCP_SYN_SENT,
+ TCP_SYN_RECV,
+ TCP_FIN_WAIT1,
+ TCP_FIN_WAIT2,
+ TCP_TIME_WAIT,
+ TCP_CLOSE,
+ TCP_CLOSE_WAIT,
+ TCP_LAST_ACK,
+ TCP_LISTEN,
+ TCP_CLOSING,
+ TCP_NEW_SYN_RECV,
+ TCP_MAX_STATES
+};
+// TCP protocol state to string mapping.
+static constexpr const char* const kTcpStateNames[] = {
+ "TCP_UNKNOWN", "TCP_ESTABLISHED", "TCP_SYN_SENT", "TCP_SYN_RECV",
+ "TCP_FIN_WAIT1", "TCP_FIN_WAIT2","TCP_TIME_WAIT", "TCP_CLOSE",
+ "TCP_CLOSE_WAIT","TCP_LAST_ACK", "TCP_LISTEN", "TCP_CLOSING",
+ "TCP_NEW_SYN_RECV","TCP_MAX_STATES"};
+
+} // namespace trace_processor
+} // namespace perfetto
+
+#endif // SRC_TRACE_PROCESSOR_TYPES_TCP_STATE_H_
diff --git a/src/traced/probes/common/cpu_freq_info.cc b/src/traced/probes/common/cpu_freq_info.cc
index 0137369..2d3c1f9 100644
--- a/src/traced/probes/common/cpu_freq_info.cc
+++ b/src/traced/probes/common/cpu_freq_info.cc
@@ -16,6 +16,8 @@
#include "src/traced/probes/common/cpu_freq_info.h"
+#include <unistd.h>
+
#include <set>
#include "perfetto/ext/base/file_utils.h"
@@ -42,10 +44,11 @@
} // namespace
-CpuFreqInfo::CpuFreqInfo(std::string cpu_dir_path) {
- base::ScopedDir cpu_dir(opendir(cpu_dir_path.c_str()));
+CpuFreqInfo::CpuFreqInfo(std::string sysfs_cpu_path)
+ : sysfs_cpu_path_{sysfs_cpu_path} {
+ base::ScopedDir cpu_dir(opendir(sysfs_cpu_path_.c_str()));
if (!cpu_dir) {
- PERFETTO_PLOG("Failed to opendir(%s)", cpu_dir_path.c_str());
+ PERFETTO_PLOG("Failed to opendir(%s)", sysfs_cpu_path_.c_str());
return;
}
// Accumulate cpu and freqs into a set to ensure stable order.
@@ -64,11 +67,11 @@
uint32_t cpu_index = maybe_cpu_index.value();
ReadAndAppendFreqs(
&freqs, cpu_index,
- ReadFile(cpu_dir_path + "/cpu" + std::to_string(cpu_index) +
+ ReadFile(sysfs_cpu_path_ + "/cpu" + std::to_string(cpu_index) +
"/cpufreq/scaling_available_frequencies"));
ReadAndAppendFreqs(
&freqs, cpu_index,
- ReadFile(cpu_dir_path + "/cpu" + std::to_string(cpu_index) +
+ ReadFile(sysfs_cpu_path_ + "/cpu" + std::to_string(cpu_index) +
"/cpufreq/scaling_boost_frequencies"));
}
@@ -118,4 +121,28 @@
return contents;
}
+const std::vector<uint32_t>& CpuFreqInfo::ReadCpuCurrFreq() {
+ // Check if capacity of cpu_curr_freq_ is enough for all CPUs
+ auto num_cpus = static_cast<size_t>(sysconf(_SC_NPROCESSORS_CONF));
+ if (cpu_curr_freq_.size() < num_cpus)
+ cpu_curr_freq_.resize(num_cpus);
+
+ for (uint32_t i = 0; i < cpu_curr_freq_.size(); i++) {
+ // Read CPU current frequency. Set 0 for offline/disabled cpus.
+ std::string buf(ReadFile(sysfs_cpu_path_ + "/cpu" + std::to_string(i) +
+ "/cpufreq/scaling_cur_freq"));
+ if (buf.empty()) {
+ cpu_curr_freq_[i] = 0;
+ continue;
+ }
+ auto freq = base::StringToUInt32(base::StripSuffix(buf, "\n"));
+ if (!freq.has_value()) {
+ cpu_curr_freq_[i] = 0;
+ continue;
+ }
+ cpu_curr_freq_[i] = freq.value();
+ }
+ return cpu_curr_freq_;
+}
+
} // namespace perfetto
diff --git a/src/traced/probes/common/cpu_freq_info.h b/src/traced/probes/common/cpu_freq_info.h
index 1bcdd79..36f7f9c 100644
--- a/src/traced/probes/common/cpu_freq_info.h
+++ b/src/traced/probes/common/cpu_freq_info.h
@@ -35,13 +35,19 @@
Range GetFreqs(uint32_t cpu);
uint32_t GetCpuFreqIndex(uint32_t cpu, uint32_t freq);
+ const std::vector<uint32_t>& ReadCpuCurrFreq();
+
private:
+ // e.g. /sys/devices/system/cpu
+ std::string sysfs_cpu_path_;
// All frequencies of all CPUs, ordered by CPU and frequency. Includes a guard
// at the end.
std::vector<uint32_t> frequencies_;
// frequencies_index_[cpu] points to first frequency in frequencies_. Includes
// a guard at the end.
std::vector<size_t> frequencies_index_;
+ // Placeholder for CPU current frequency, refresh in ReadCpuCurrFreq()
+ std::vector<uint32_t> cpu_curr_freq_;
std::string ReadFile(std::string path);
};
diff --git a/src/traced/probes/common/cpu_freq_info_for_testing.cc b/src/traced/probes/common/cpu_freq_info_for_testing.cc
index ddfa5d2..2fa1637 100644
--- a/src/traced/probes/common/cpu_freq_info_for_testing.cc
+++ b/src/traced/probes/common/cpu_freq_info_for_testing.cc
@@ -44,6 +44,7 @@
kCpuFrequenciesAndroidLittleCore);
tmpdir_.AddFile("cpu0/cpufreq/scaling_boost_frequencies",
kCpuBoostFrequenciesAndroidLittleCore);
+ tmpdir_.AddFile("cpu0/cpufreq/scaling_cur_freq", "2650000");
tmpdir_.AddDir("cpufreq");
tmpdir_.AddDir("cpu1");
tmpdir_.AddDir("cpu1/cpufreq");
@@ -51,6 +52,7 @@
kCpuFrequenciesAndroidBigCore);
tmpdir_.AddFile("cpu1/cpufreq/scaling_boost_frequencies",
kCpuBoostFrequenciesAndroidBigCore);
+ tmpdir_.AddFile("cpu1/cpufreq/scaling_cur_freq", "3698200");
tmpdir_.AddDir("power");
}
diff --git a/src/traced/probes/ftrace/event_info.cc b/src/traced/probes/ftrace/event_info.cc
index 6234363..0b3d5b3 100644
--- a/src/traced/probes/ftrace/event_info.cc
+++ b/src/traced/probes/ftrace/event_info.cc
@@ -6450,6 +6450,40 @@
kUnsetFtraceId,
325,
kUnsetSize},
+ {"inet_sock_set_state",
+ "sock",
+ {
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "daddr", 1, ProtoSchemaType::kUint32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "dport", 2, ProtoSchemaType::kUint32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "family", 3, ProtoSchemaType::kUint32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "newstate", 4, ProtoSchemaType::kInt32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "oldstate", 5, ProtoSchemaType::kInt32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "protocol", 6, ProtoSchemaType::kUint32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "saddr", 7, ProtoSchemaType::kUint32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "skaddr", 8, ProtoSchemaType::kUint64,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "sport", 9, ProtoSchemaType::kUint32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ },
+ kUnsetFtraceId,
+ 362,
+ kUnsetSize},
{"sync_pt",
"sync",
{
@@ -6568,6 +6602,34 @@
kUnsetFtraceId,
236,
kUnsetSize},
+ {"tcp_retransmit_skb",
+ "tcp",
+ {
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "daddr", 1, ProtoSchemaType::kUint32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "dport", 2, ProtoSchemaType::kUint32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "saddr", 3, ProtoSchemaType::kUint32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "skaddr", 4, ProtoSchemaType::kUint64,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "skbaddr", 5, ProtoSchemaType::kUint64,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "sport", 6, ProtoSchemaType::kUint32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "state", 7, ProtoSchemaType::kInt32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ },
+ kUnsetFtraceId,
+ 363,
+ kUnsetSize},
{"thermal_temperature",
"thermal",
{
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/sock/inet_sock_set_state/format b/src/traced/probes/ftrace/test/data/synthetic/events/sock/inet_sock_set_state/format
new file mode 100644
index 0000000..d7c36c7
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/sock/inet_sock_set_state/format
@@ -0,0 +1,21 @@
+name: inet_sock_set_state
+ID: 924
+format:
+ field:unsigned short common_type; offset:0; size:2; signed:0;
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ field:int common_pid; offset:4; size:4; signed:1;
+
+ field:const void * skaddr; offset:8; size:8; signed:0;
+ field:int oldstate; offset:16; size:4; signed:1;
+ field:int newstate; offset:20; size:4; signed:1;
+ field:__u16 sport; offset:24; size:2; signed:0;
+ field:__u16 dport; offset:26; size:2; signed:0;
+ field:__u16 family; offset:28; size:2; signed:0;
+ field:__u16 protocol; offset:30; size:2; signed:0;
+ field:__u8 saddr[4]; offset:32; size:4; signed:0;
+ field:__u8 daddr[4]; offset:36; size:4; signed:0;
+ field:__u8 saddr_v6[16]; offset:40; size:16; signed:0;
+ field:__u8 daddr_v6[16]; offset:56; size:16; signed:0;
+
+print fmt: "family=%s protocol=%s sport=%hu dport=%hu saddr=%pI4 daddr=%pI4 saddrv6=%pI6c daddrv6=%pI6c oldstate=%s newstate=%s", __print_symbolic(REC->family, { 2, "AF_INET" }, { 10, "AF_INET6" }), __print_symbolic(REC->protocol, { 6, "IPPROTO_TCP" }, { 33, "IPPROTO_DCCP" }, { 132, "IPPROTO_SCTP" }, { 262, "IPPROTO_MPTCP" }), REC->sport, REC->dport, REC->saddr, REC->daddr, REC->saddr_v6, REC->daddr_v6, __print_symbolic(REC->oldstate, { 1, "TCP_ESTABLISHED" }, { 2, "TCP_SYN_SENT" }, { 3, "TCP_SYN_RECV" }, { 4, "TCP_FIN_WAIT1" }, { 5, "TCP_FIN_WAIT2" }, { 6, "TCP_TIME_WAIT" }, { 7, "TCP_CLOSE" }, { 8, "TCP_CLOSE_WAIT" }, { 9, "TCP_LAST_ACK" }, { 10, "TCP_LISTEN" }, { 11, "TCP_CLOSING" }, { 12, "TCP_NEW_SYN_RECV" }), __print_symbolic(REC->newstate, { 1, "TCP_ESTABLISHED" }, { 2, "TCP_SYN_SENT" }, { 3, "TCP_SYN_RECV" }, { 4, "TCP_FIN_WAIT1" }, { 5, "TCP_FIN_WAIT2" }, { 6, "TCP_TIME_WAIT" }, { 7, "TCP_CLOSE" }, { 8, "TCP_CLOSE_WAIT" }, { 9, "TCP_LAST_ACK" }, { 10, "TCP_LISTEN" }, { 11, "TCP_CLOSING" }, { 12, "TCP_NEW_SYN_RECV" })
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/tcp/tcp_retransmit_skb/format b/src/traced/probes/ftrace/test/data/synthetic/events/tcp/tcp_retransmit_skb/format
new file mode 100644
index 0000000..a73ee0e
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/tcp/tcp_retransmit_skb/format
@@ -0,0 +1,19 @@
+name: tcp_retransmit_skb
+ID: 967
+format:
+ field:unsigned short common_type; offset:0; size:2; signed:0;
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ field:int common_pid; offset:4; size:4; signed:1;
+
+ field:const void * skbaddr; offset:8; size:8; signed:0;
+ field:const void * skaddr; offset:16; size:8; signed:0;
+ field:int state; offset:24; size:4; signed:1;
+ field:__u16 sport; offset:28; size:2; signed:0;
+ field:__u16 dport; offset:30; size:2; signed:0;
+ field:__u8 saddr[4]; offset:32; size:4; signed:0;
+ field:__u8 daddr[4]; offset:36; size:4; signed:0;
+ field:__u8 saddr_v6[16]; offset:40; size:16; signed:0;
+ field:__u8 daddr_v6[16]; offset:56; size:16; signed:0;
+
+print fmt: "sport=%hu dport=%hu saddr=%pI4 daddr=%pI4 saddrv6=%pI6c daddrv6=%pI6c state=%s", REC->sport, REC->dport, REC->saddr, REC->daddr, REC->saddr_v6, REC->daddr_v6, __print_symbolic(REC->state, { TCP_ESTABLISHED, "TCP_ESTABLISHED" }, { TCP_SYN_SENT, "TCP_SYN_SENT" }, { TCP_SYN_RECV, "TCP_SYN_RECV" }, { TCP_FIN_WAIT1, "TCP_FIN_WAIT1" }, { TCP_FIN_WAIT2, "TCP_FIN_WAIT2" }, { TCP_TIME_WAIT, "TCP_TIME_WAIT" }, { TCP_CLOSE, "TCP_CLOSE" }, { TCP_CLOSE_WAIT, "TCP_CLOSE_WAIT" }, { TCP_LAST_ACK, "TCP_LAST_ACK" }, { TCP_LISTEN, "TCP_LISTEN" }, { TCP_CLOSING, "TCP_CLOSING" }, { TCP_NEW_SYN_RECV, "TCP_NEW_SYN_RECV" })
diff --git a/src/traced/probes/probes_producer.cc b/src/traced/probes/probes_producer.cc
index b2c4297..ebe5745 100644
--- a/src/traced/probes/probes_producer.cc
+++ b/src/traced/probes/probes_producer.cc
@@ -324,9 +324,9 @@
TracingSessionID session_id,
const DataSourceConfig& config) {
auto buffer_id = static_cast<BufferID>(config.target_buffer());
- return std::unique_ptr<SysStatsDataSource>(
- new SysStatsDataSource(task_runner_, session_id,
- endpoint_->CreateTraceWriter(buffer_id), config));
+ return std::unique_ptr<SysStatsDataSource>(new SysStatsDataSource(
+ task_runner_, session_id, endpoint_->CreateTraceWriter(buffer_id), config,
+ std::unique_ptr<CpuFreqInfo>(new CpuFreqInfo())));
}
std::unique_ptr<ProbesDataSource> ProbesProducer::CreateMetatraceDataSource(
diff --git a/src/traced/probes/sys_stats/BUILD.gn b/src/traced/probes/sys_stats/BUILD.gn
index 9c02da8..ceeaaf5 100644
--- a/src/traced/probes/sys_stats/BUILD.gn
+++ b/src/traced/probes/sys_stats/BUILD.gn
@@ -26,6 +26,7 @@
"../../../../protos/perfetto/trace:zero",
"../../../../protos/perfetto/trace/sys_stats:zero",
"../../../base",
+ "../common",
]
sources = [
"sys_stats_data_source.cc",
@@ -43,6 +44,7 @@
"../../../../protos/perfetto/trace/sys_stats:cpp",
"../../../../src/base:test_support",
"../../../../src/tracing/test:test_support",
+ "../common:test_support",
]
sources = [ "sys_stats_data_source_unittest.cc" ]
}
diff --git a/src/traced/probes/sys_stats/sys_stats_data_source.cc b/src/traced/probes/sys_stats/sys_stats_data_source.cc
index a79cb40..93c292b 100644
--- a/src/traced/probes/sys_stats/sys_stats_data_source.cc
+++ b/src/traced/probes/sys_stats/sys_stats_data_source.cc
@@ -72,14 +72,17 @@
/*flags*/ Descriptor::kFlagsNone,
};
-SysStatsDataSource::SysStatsDataSource(base::TaskRunner* task_runner,
- TracingSessionID session_id,
- std::unique_ptr<TraceWriter> writer,
- const DataSourceConfig& ds_config,
- OpenFunction open_fn)
+SysStatsDataSource::SysStatsDataSource(
+ base::TaskRunner* task_runner,
+ TracingSessionID session_id,
+ std::unique_ptr<TraceWriter> writer,
+ const DataSourceConfig& ds_config,
+ std::unique_ptr<CpuFreqInfo> cpu_freq_info,
+ OpenFunction open_fn)
: ProbesDataSource(session_id, &descriptor),
task_runner_(task_runner),
writer_(std::move(writer)),
+ cpu_freq_info_(std::move(cpu_freq_info)),
weak_factory_(this) {
ns_per_user_hz_ = 1000000000ull / static_cast<uint64_t>(sysconf(_SC_CLK_TCK));
@@ -139,14 +142,15 @@
stat_enabled_fields_ |= 1ul << static_cast<uint32_t>(*counter);
}
- std::array<uint32_t, 4> periods_ms{};
- std::array<uint32_t, 4> ticks{};
+ std::array<uint32_t, 5> periods_ms{};
+ std::array<uint32_t, 5> ticks{};
static_assert(periods_ms.size() == ticks.size(), "must have same size");
periods_ms[0] = ClampTo10Ms(cfg.meminfo_period_ms(), "meminfo_period_ms");
periods_ms[1] = ClampTo10Ms(cfg.vmstat_period_ms(), "vmstat_period_ms");
periods_ms[2] = ClampTo10Ms(cfg.stat_period_ms(), "stat_period_ms");
periods_ms[3] = ClampTo10Ms(cfg.devfreq_period_ms(), "devfreq_period_ms");
+ periods_ms[4] = ClampTo10Ms(cfg.cpufreq_period_ms(), "cpufreq_period_ms");
tick_period_ms_ = 0;
for (uint32_t ms : periods_ms) {
@@ -169,6 +173,7 @@
vmstat_ticks_ = ticks[1];
stat_ticks_ = ticks[2];
devfreq_ticks_ = ticks[3];
+ cpufreq_ticks_ = ticks[4];
}
void SysStatsDataSource::Start() {
@@ -212,6 +217,9 @@
if (devfreq_ticks_ && tick_ % devfreq_ticks_ == 0)
ReadDevfreq(sys_stats);
+ if (cpufreq_ticks_ && tick_ % cpufreq_ticks_ == 0)
+ ReadCpufreq(sys_stats);
+
sys_stats->set_collection_end_timestamp(
static_cast<uint64_t>(base::GetBootTimeNs().count()));
@@ -235,6 +243,13 @@
}
}
+void SysStatsDataSource::ReadCpufreq(protos::pbzero::SysStats* sys_stats) {
+ const auto& cpufreq = cpu_freq_info_->ReadCpuCurrFreq();
+
+ for (const auto& c : cpufreq)
+ sys_stats->add_cpufreq_khz(c);
+}
+
base::ScopedDir SysStatsDataSource::OpenDevfreqDir() {
const char* base_dir = "/sys/class/devfreq/";
base::ScopedDir devfreq_dir(opendir(base_dir));
diff --git a/src/traced/probes/sys_stats/sys_stats_data_source.h b/src/traced/probes/sys_stats/sys_stats_data_source.h
index cf47770..3140efa 100644
--- a/src/traced/probes/sys_stats/sys_stats_data_source.h
+++ b/src/traced/probes/sys_stats/sys_stats_data_source.h
@@ -29,6 +29,7 @@
#include "perfetto/ext/tracing/core/basic_types.h"
#include "perfetto/ext/tracing/core/trace_writer.h"
#include "perfetto/tracing/core/data_source_config.h"
+#include "src/traced/probes/common/cpu_freq_info.h"
#include "src/traced/probes/probes_data_source.h"
namespace perfetto {
@@ -52,6 +53,7 @@
TracingSessionID,
std::unique_ptr<TraceWriter> writer,
const DataSourceConfig&,
+ std::unique_ptr<CpuFreqInfo> cpu_freq_info,
OpenFunction = nullptr);
~SysStatsDataSource() override;
@@ -84,6 +86,7 @@
void ReadVmstat(protos::pbzero::SysStats* sys_stats);
void ReadStat(protos::pbzero::SysStats* sys_stats);
void ReadDevfreq(protos::pbzero::SysStats* sys_stats);
+ void ReadCpufreq(protos::pbzero::SysStats* sys_stats);
size_t ReadFile(base::ScopedFile*, const char* path);
base::TaskRunner* const task_runner_;
@@ -103,8 +106,11 @@
uint32_t stat_ticks_ = 0;
uint32_t stat_enabled_fields_ = 0;
uint32_t devfreq_ticks_ = 0;
+ uint32_t cpufreq_ticks_ = 0;
bool devfreq_error_logged_ = false;
+ std::unique_ptr<CpuFreqInfo> cpu_freq_info_;
+
base::WeakPtrFactory<SysStatsDataSource> weak_factory_; // Keep last.
};
diff --git a/src/traced/probes/sys_stats/sys_stats_data_source_unittest.cc b/src/traced/probes/sys_stats/sys_stats_data_source_unittest.cc
index b239ffa..a585edf 100644
--- a/src/traced/probes/sys_stats/sys_stats_data_source_unittest.cc
+++ b/src/traced/probes/sys_stats/sys_stats_data_source_unittest.cc
@@ -19,6 +19,7 @@
#include "perfetto/ext/base/file_utils.h"
#include "perfetto/ext/base/temp_file.h"
#include "src/base/test/test_task_runner.h"
+#include "src/traced/probes/common/cpu_freq_info_for_testing.h"
#include "src/traced/probes/sys_stats/sys_stats_data_source.h"
#include "src/tracing/core/trace_writer_for_testing.h"
#include "test/gtest_and_gmock.h"
@@ -196,11 +197,13 @@
TracingSessionID id,
std::unique_ptr<TraceWriter> writer,
const DataSourceConfig& config,
+ std::unique_ptr<CpuFreqInfo> cpu_freq_info,
OpenFunction open_fn)
: SysStatsDataSource(task_runner,
id,
std::move(writer),
config,
+ std::move(cpu_freq_info),
open_fn) {}
MOCK_METHOD0(OpenDevfreqDir, base::ScopedDir());
@@ -230,7 +233,8 @@
writer_raw_ = writer.get();
auto instance =
std::unique_ptr<TestSysStatsDataSource>(new TestSysStatsDataSource(
- &task_runner_, 0, std::move(writer), cfg, MockOpenReadOnly));
+ &task_runner_, 0, std::move(writer), cfg,
+ cpu_freq_info_for_testing_.GetInstance(), MockOpenReadOnly));
instance->set_ns_per_user_hz_for_testing(1000000000ull / 100); // 100 Hz.
instance->Start();
return instance;
@@ -252,6 +256,7 @@
TraceWriterForTesting* writer_raw_ = nullptr;
base::TestTaskRunner task_runner_;
+ CpuFreqInfoForTesting cpu_freq_info_for_testing_;
};
TEST_F(SysStatsDataSourceTest, Meminfo) {
@@ -482,5 +487,31 @@
ASSERT_EQ(sys_stats.num_softirq_size(), 0);
}
+TEST_F(SysStatsDataSourceTest, Cpufreq) {
+ protos::gen::SysStatsConfig cfg;
+ cfg.set_cpufreq_period_ms(10);
+ DataSourceConfig config_obj;
+ config_obj.set_sys_stats_config_raw(cfg.SerializeAsString());
+ auto data_source = GetSysStatsDataSource(config_obj);
+
+ WaitTick(data_source.get());
+
+ protos::gen::TracePacket packet = writer_raw_->GetOnlyTracePacket();
+ ASSERT_TRUE(packet.has_sys_stats());
+ const auto& sys_stats = packet.sys_stats();
+ EXPECT_GT(sys_stats.cpufreq_khz_size(), 0);
+ EXPECT_EQ(sys_stats.cpufreq_khz()[0], 2650000u);
+ if (sys_stats.cpufreq_khz_size() > 1) {
+ // We emulated 2 CPUs but it is possible the test system is single core.
+ EXPECT_EQ(sys_stats.cpufreq_khz()[1], 3698200u);
+ }
+ for (unsigned int i = 2;
+ i < static_cast<unsigned int>(sys_stats.cpufreq_khz_size()); i++) {
+ // For cpux which scaling_cur_freq was not emulated in unittest, cpufreq
+ // should be recorded as 0
+ EXPECT_EQ(sys_stats.cpufreq_khz()[i], 0u);
+ }
+}
+
} // namespace
} // namespace perfetto
diff --git a/src/traced/service/builtin_producer.cc b/src/traced/service/builtin_producer.cc
index 20d9849..a2007be 100644
--- a/src/traced/service/builtin_producer.cc
+++ b/src/traced/service/builtin_producer.cc
@@ -67,8 +67,16 @@
}
void BuiltinProducer::ConnectInProcess(TracingService* svc) {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+ // TODO(primiano): ConnectProducer should take a base::PlatformProcessId not
+ // pid_t, as they are different on Windows. But that is a larger refactoring
+ // and not worth given this is the only use case where it clashes.
+ const pid_t cur_proc_id = 0;
+#else
+ const pid_t cur_proc_id = base::GetProcessId();
+#endif
endpoint_ = svc->ConnectProducer(
- this, base::GetCurrentUserId(), base::GetProcessId(), "traced",
+ this, base::GetCurrentUserId(), cur_proc_id, "traced",
/*shared_memory_size_hint_bytes=*/16 * 1024, /*in_process=*/true,
TracingService::ProducerSMBScrapingMode::kDisabled,
/*shared_memory_page_size_hint_bytes=*/4096);
diff --git a/src/tracing/core/BUILD.gn b/src/tracing/core/BUILD.gn
index c81c716..e082c08 100644
--- a/src/tracing/core/BUILD.gn
+++ b/src/tracing/core/BUILD.gn
@@ -92,6 +92,7 @@
"../../../protos/perfetto/trace:zero",
"../../../protos/perfetto/trace/ftrace:cpp",
"../../../protos/perfetto/trace/perfetto:cpp",
+ "../../../src/protozero/filtering:bytecode_generator",
"../../base",
"../../base:test_support",
"../test:test_support",
diff --git a/src/tracing/core/tracing_service_impl.cc b/src/tracing/core/tracing_service_impl.cc
index ad5cda3..696732e 100644
--- a/src/tracing/core/tracing_service_impl.cc
+++ b/src/tracing/core/tracing_service_impl.cc
@@ -626,9 +626,10 @@
cfg.write_into_file()) {
// We don't support this usecase because there are subtle assumptions which
// break around TracingServiceEvents and windowed sorting (i.e. if we don't
- // drain the events in ReadBuffers because we are waiting for STOP_TRACING,
- // we can end up queueing up a lot of TracingServiceEvents and emitting them
- // wildy out of order breaking windowed sorting in trace processor).
+ // drain the events in ReadBuffersIntoFile because we are waiting for
+ // STOP_TRACING, we can end up queueing up a lot of TracingServiceEvents and
+ // emitting them wildy out of order breaking windowed sorting in trace
+ // processor).
MaybeLogUploadEvent(
cfg, PerfettoStatsdAtom::kTracedEnableTracingStopTracingWriteIntoFile);
return PERFETTO_SVC_ERR(
@@ -1649,6 +1650,12 @@
return;
}
+ if (tracing_session->state != TracingSession::STARTED) {
+ PERFETTO_ELOG("Flush() called, but tracing has not been started");
+ callback(false);
+ return;
+ }
+
FlushRequestID flush_request_id = ++last_flush_request_id_;
PendingFlush& pending_flush =
tracing_session->pending_flushes
@@ -2012,7 +2019,35 @@
if (IsWaitingForTrigger(tracing_session))
return false;
- return ReadBuffers(tsid, tracing_session, consumer);
+ // This is a rough threshold to determine how much to read from the buffer in
+ // each task. This is to avoid executing a single huge sending task for too
+ // long and risk to hit the watchdog. This is *not* an upper bound: we just
+ // stop accumulating new packets and PostTask *after* we cross this threshold.
+ // This constant essentially balances the PostTask and IPC overhead vs the
+ // responsiveness of the service. An extremely small value will cause one IPC
+ // and one PostTask for each slice but will keep the service extremely
+ // responsive. An extremely large value will batch the send for the full
+ // buffer in one large task, will hit the blocking send() once the socket
+ // buffers are full and hang the service for a bit (until the consumer
+ // catches up).
+ static constexpr size_t kApproxBytesPerTask = 32768;
+ bool has_more;
+ std::vector<TracePacket> packets =
+ ReadBuffers(tracing_session, kApproxBytesPerTask, &has_more);
+
+ if (has_more) {
+ auto weak_consumer = consumer->weak_ptr_factory_.GetWeakPtr();
+ auto weak_this = weak_ptr_factory_.GetWeakPtr();
+ task_runner_->PostTask([weak_this, weak_consumer, tsid] {
+ if (!weak_this || !weak_consumer)
+ return;
+ weak_this->ReadBuffersIntoConsumer(tsid, weak_consumer.get());
+ });
+ }
+
+ // Keep this as tail call, just in case the consumer re-enters.
+ consumer->consumer_->OnTraceData(std::move(packets), has_more);
+ return true;
}
bool TracingServiceImpl::ReadBuffersIntoFile(TracingSessionID tsid) {
@@ -2033,7 +2068,47 @@
IsWaitingForTrigger(tracing_session))
return false;
- return ReadBuffers(tsid, tracing_session, nullptr);
+ // Speculative fix for the memory watchdog crash in b/195145848. This function
+ // uses the heap extensively and might need a M_PURGE. window.gc() is back.
+ // TODO(primiano): if this fixes the crash we might want to coalesce the purge
+ // and throttle it.
+ auto on_ret = base::OnScopeExit([] { base::MaybeReleaseAllocatorMemToOS(); });
+
+ // ReadBuffers() can allocate memory internally, for filtering. By limiting
+ // the data that ReadBuffers() reads to kWriteIntoChunksSize per iteration,
+ // we limit the amount of memory used on each iteration.
+ //
+ // It would be tempting to split this into multiple tasks like in
+ // ReadBuffersIntoConsumer, but that's not currently possible.
+ // ReadBuffersIntoFile has to read the whole available data before returning,
+ // to support the disable_immediately=true code paths.
+ bool has_more = true;
+ bool stop_writing_into_file = false;
+ do {
+ std::vector<TracePacket> packets =
+ ReadBuffers(tracing_session, kWriteIntoFileChunkSize, &has_more);
+
+ stop_writing_into_file = WriteIntoFile(tracing_session, std::move(packets));
+ } while (has_more && !stop_writing_into_file);
+
+ if (stop_writing_into_file || tracing_session->write_period_ms == 0) {
+ // Ensure all data was written to the file before we close it.
+ base::FlushFile(tracing_session->write_into_file.get());
+ tracing_session->write_into_file.reset();
+ tracing_session->write_period_ms = 0;
+ if (tracing_session->state == TracingSession::STARTED)
+ DisableTracing(tsid);
+ return true;
+ }
+
+ auto weak_this = weak_ptr_factory_.GetWeakPtr();
+ task_runner_->PostDelayedTask(
+ [weak_this, tsid] {
+ if (weak_this)
+ weak_this->ReadBuffersIntoFile(tsid);
+ },
+ tracing_session->delay_to_next_write_period_ms());
+ return true;
}
bool TracingServiceImpl::IsWaitingForTrigger(TracingSession* tracing_session) {
@@ -2051,20 +2126,13 @@
return false;
}
-// Note: when this is called to write into a file passed when starting tracing
-// |consumer| will be == nullptr (as opposite to the case of a consumer asking
-// to send the trace data back over IPC).
-bool TracingServiceImpl::ReadBuffers(TracingSessionID tsid,
- TracingSession* tracing_session,
- ConsumerEndpointImpl* consumer) {
+std::vector<TracePacket> TracingServiceImpl::ReadBuffers(
+ TracingSession* tracing_session,
+ size_t threshold,
+ bool* has_more) {
PERFETTO_DCHECK_THREAD(thread_checker_);
PERFETTO_DCHECK(tracing_session);
-
- // Speculative fix for the memory watchdog crash in b/195145848. This function
- // uses the heap extensively and might need a M_PURGE. window.gc() is back.
- // TODO(primiano): if this fixes the crash we might want to coalesce the purge
- // and throttle it.
- auto on_ret = base::OnScopeExit([] { base::MaybeReleaseAllocatorMemToOS(); });
+ *has_more = false;
std::vector<TracePacket> packets;
packets.reserve(1024); // Just an educated guess to avoid trivial expansions.
@@ -2106,22 +2174,8 @@
packets_bytes += packet.size();
}
- // This is a rough threshold to determine how much to read from the buffer in
- // each task. This is to avoid executing a single huge sending task for too
- // long and risk to hit the watchdog. This is *not* an upper bound: we just
- // stop accumulating new packets and PostTask *after* we cross this threshold.
- // This constant essentially balances the PostTask and IPC overhead vs the
- // responsiveness of the service. An extremely small value will cause one IPC
- // and one PostTask for each slice but will keep the service extremely
- // responsive. An extremely large value will batch the send for the full
- // buffer in one large task, will hit the blocking send() once the socket
- // buffers are full and hang the service for a bit (until the consumer
- // catches up).
- static constexpr size_t kApproxBytesPerTask = 32768;
bool did_hit_threshold = false;
- // TODO(primiano): Extend the ReadBuffers API to allow reading only some
- // buffers, not all of them in one go.
for (size_t buf_idx = 0;
buf_idx < tracing_session->num_buffers() && !did_hit_threshold;
buf_idx++) {
@@ -2182,16 +2236,19 @@
// Append the packet (inclusive of the trusted uid) to |packets|.
packets_bytes += packet.size();
- did_hit_threshold = packets_bytes >= kApproxBytesPerTask &&
- !tracing_session->write_into_file;
+ did_hit_threshold = packets_bytes >= threshold;
packets.emplace_back(std::move(packet));
} // for(packets...)
} // for(buffers...)
- const bool has_more = did_hit_threshold;
+ *has_more = did_hit_threshold;
- if (!tracing_session->config.builtin_data_sources()
- .disable_service_events()) {
+ // Only emit the "read complete" lifetime event when there is no more trace
+ // data available to read. These events are used as safe points to limit
+ // sorting in trace processor: the code shouldn't emit the event unless the
+ // buffers are empty.
+ if (!*has_more && !tracing_session->config.builtin_data_sources()
+ .disable_service_events()) {
// We don't bother snapshotting clocks here because we wouldn't be able to
// emit it and we shouldn't have significant drift from the last snapshot in
// any case.
@@ -2207,155 +2264,127 @@
// reflected in the emitted stats. This is particularly important for use
// cases where ReadBuffers is only ever called after the tracing session is
// stopped.
- if (!has_more && tracing_session->should_emit_stats) {
+ if (!*has_more && tracing_session->should_emit_stats) {
EmitStats(tracing_session, &packets);
tracing_session->should_emit_stats = false;
}
- // +-------------------------------------------------------------------------+
- // | NO MORE CHANGES TO |packets| AFTER THIS POINT. |
- // +-------------------------------------------------------------------------+
+ MaybeFilterPackets(tracing_session, &packets);
+ return packets;
+}
+
+void TracingServiceImpl::MaybeFilterPackets(TracingSession* tracing_session,
+ std::vector<TracePacket>* packets) {
// If the tracing session specified a filter, run all packets through the
// filter and replace them with the filter results.
// The process below mantains the cardinality of input packets. Even if an
// entire packet is filtered out, we emit a zero-sized TracePacket proto. That
// makes debugging and reasoning about the trace stats easier.
// This place swaps the contents of each |packets| entry in place.
- if (tracing_session->trace_filter) {
- auto& trace_filter = *tracing_session->trace_filter;
- // The filter root shoud be reset from protos.Trace to protos.TracePacket
- // by the earlier call to SetFilterRoot() in EnableTracing().
- PERFETTO_DCHECK(trace_filter.root_msg_index() != 0);
- std::vector<protozero::MessageFilter::InputSlice> filter_input;
- for (auto it = packets.begin(); it != packets.end(); ++it) {
- const auto& packet_slices = it->slices();
- filter_input.clear();
- filter_input.resize(packet_slices.size());
- ++tracing_session->filter_input_packets;
- tracing_session->filter_input_bytes += it->size();
- for (size_t i = 0; i < packet_slices.size(); ++i)
- filter_input[i] = {packet_slices[i].start, packet_slices[i].size};
- auto filtered_packet = trace_filter.FilterMessageFragments(
- &filter_input[0], filter_input.size());
+ if (!tracing_session->trace_filter) {
+ return;
+ }
+ protozero::MessageFilter& trace_filter = *tracing_session->trace_filter;
+ // The filter root shoud be reset from protos.Trace to protos.TracePacket
+ // by the earlier call to SetFilterRoot() in EnableTracing().
+ PERFETTO_DCHECK(trace_filter.root_msg_index() != 0);
+ std::vector<protozero::MessageFilter::InputSlice> filter_input;
+ for (TracePacket& packet : *packets) {
+ const auto& packet_slices = packet.slices();
+ filter_input.clear();
+ filter_input.resize(packet_slices.size());
+ ++tracing_session->filter_input_packets;
+ tracing_session->filter_input_bytes += packet.size();
+ for (size_t i = 0; i < packet_slices.size(); ++i)
+ filter_input[i] = {packet_slices[i].start, packet_slices[i].size};
+ auto filtered_packet = trace_filter.FilterMessageFragments(
+ &filter_input[0], filter_input.size());
- // Replace the packet in-place with the filtered one (unless failed).
- *it = TracePacket();
- if (filtered_packet.error) {
- ++tracing_session->filter_errors;
- PERFETTO_DLOG("Trace packet filtering failed @ packet %" PRIu64,
- tracing_session->filter_input_packets);
- continue;
- }
- tracing_session->filter_output_bytes += filtered_packet.size;
- AppendOwnedSlicesToPacket(std::move(filtered_packet.data),
- filtered_packet.size, kMaxTracePacketSliceSize,
- &*it);
-
- } // for (packet)
- } // if (trace_filter)
-
- // If the caller asked us to write into a file by setting
- // |write_into_file| == true in the trace config, drain the packets read
- // (if any) into the given file descriptor.
- if (tracing_session->write_into_file) {
- const uint64_t max_size = tracing_session->max_file_size_bytes
- ? tracing_session->max_file_size_bytes
- : std::numeric_limits<size_t>::max();
-
- size_t total_slices = 0;
- for (const TracePacket& packet : packets) {
- total_slices += packet.slices().size();
+ // Replace the packet in-place with the filtered one (unless failed).
+ packet = TracePacket();
+ if (filtered_packet.error) {
+ ++tracing_session->filter_errors;
+ PERFETTO_DLOG("Trace packet filtering failed @ packet %" PRIu64,
+ tracing_session->filter_input_packets);
+ continue;
}
- // When writing into a file, the file should look like a root trace.proto
- // message. Each packet should be prepended with a proto preamble stating
- // its field id (within trace.proto) and size. Hence the addition below.
- const size_t max_iovecs = total_slices + packets.size();
+ tracing_session->filter_output_bytes += filtered_packet.size;
+ AppendOwnedSlicesToPacket(std::move(filtered_packet.data),
+ filtered_packet.size, kMaxTracePacketSliceSize,
+ &packet);
+ }
+}
- size_t num_iovecs = 0;
- bool stop_writing_into_file = false;
- std::unique_ptr<struct iovec[]> iovecs(new struct iovec[max_iovecs]);
- size_t num_iovecs_at_last_packet = 0;
- uint64_t bytes_about_to_be_written = 0;
- for (TracePacket& packet : packets) {
- std::tie(iovecs[num_iovecs].iov_base, iovecs[num_iovecs].iov_len) =
- packet.GetProtoPreamble();
- bytes_about_to_be_written += iovecs[num_iovecs].iov_len;
- num_iovecs++;
- for (const Slice& slice : packet.slices()) {
- // writev() doesn't change the passed pointer. However, struct iovec
- // take a non-const ptr because it's the same struct used by readv().
- // Hence the const_cast here.
- char* start = static_cast<char*>(const_cast<void*>(slice.start));
- bytes_about_to_be_written += slice.size;
- iovecs[num_iovecs++] = {start, slice.size};
- }
+bool TracingServiceImpl::WriteIntoFile(TracingSession* tracing_session,
+ std::vector<TracePacket> packets) {
+ if (!tracing_session->write_into_file) {
+ return false;
+ }
+ const uint64_t max_size = tracing_session->max_file_size_bytes
+ ? tracing_session->max_file_size_bytes
+ : std::numeric_limits<size_t>::max();
- if (tracing_session->bytes_written_into_file +
- bytes_about_to_be_written >=
- max_size) {
- stop_writing_into_file = true;
- num_iovecs = num_iovecs_at_last_packet;
- break;
- }
+ size_t total_slices = 0;
+ for (const TracePacket& packet : packets) {
+ total_slices += packet.slices().size();
+ }
+ // When writing into a file, the file should look like a root trace.proto
+ // message. Each packet should be prepended with a proto preamble stating
+ // its field id (within trace.proto) and size. Hence the addition below.
+ const size_t max_iovecs = total_slices + packets.size();
- num_iovecs_at_last_packet = num_iovecs;
- }
- PERFETTO_DCHECK(num_iovecs <= max_iovecs);
- int fd = *tracing_session->write_into_file;
-
- uint64_t total_wr_size = 0;
-
- // writev() can take at most IOV_MAX entries per call. Batch them.
- constexpr size_t kIOVMax = IOV_MAX;
- for (size_t i = 0; i < num_iovecs; i += kIOVMax) {
- int iov_batch_size = static_cast<int>(std::min(num_iovecs - i, kIOVMax));
- ssize_t wr_size = PERFETTO_EINTR(writev(fd, &iovecs[i], iov_batch_size));
- if (wr_size <= 0) {
- PERFETTO_PLOG("writev() failed");
- stop_writing_into_file = true;
- break;
- }
- total_wr_size += static_cast<size_t>(wr_size);
+ size_t num_iovecs = 0;
+ bool stop_writing_into_file = false;
+ std::unique_ptr<struct iovec[]> iovecs(new struct iovec[max_iovecs]);
+ size_t num_iovecs_at_last_packet = 0;
+ uint64_t bytes_about_to_be_written = 0;
+ for (TracePacket& packet : packets) {
+ std::tie(iovecs[num_iovecs].iov_base, iovecs[num_iovecs].iov_len) =
+ packet.GetProtoPreamble();
+ bytes_about_to_be_written += iovecs[num_iovecs].iov_len;
+ num_iovecs++;
+ for (const Slice& slice : packet.slices()) {
+ // writev() doesn't change the passed pointer. However, struct iovec
+ // take a non-const ptr because it's the same struct used by readv().
+ // Hence the const_cast here.
+ char* start = static_cast<char*>(const_cast<void*>(slice.start));
+ bytes_about_to_be_written += slice.size;
+ iovecs[num_iovecs++] = {start, slice.size};
}
- tracing_session->bytes_written_into_file += total_wr_size;
-
- PERFETTO_DLOG("Draining into file, written: %" PRIu64 " KB, stop: %d",
- (total_wr_size + 1023) / 1024, stop_writing_into_file);
- if (stop_writing_into_file || tracing_session->write_period_ms == 0) {
- // Ensure all data was written to the file before we close it.
- base::FlushFile(fd);
- tracing_session->write_into_file.reset();
- tracing_session->write_period_ms = 0;
- if (tracing_session->state == TracingSession::STARTED)
- DisableTracing(tsid);
- return true;
+ if (tracing_session->bytes_written_into_file + bytes_about_to_be_written >=
+ max_size) {
+ stop_writing_into_file = true;
+ num_iovecs = num_iovecs_at_last_packet;
+ break;
}
- auto weak_this = weak_ptr_factory_.GetWeakPtr();
- task_runner_->PostDelayedTask(
- [weak_this, tsid] {
- if (weak_this)
- weak_this->ReadBuffersIntoFile(tsid);
- },
- tracing_session->delay_to_next_write_period_ms());
- return true;
- } // if (tracing_session->write_into_file)
+ num_iovecs_at_last_packet = num_iovecs;
+ }
+ PERFETTO_DCHECK(num_iovecs <= max_iovecs);
+ int fd = *tracing_session->write_into_file;
- if (has_more) {
- auto weak_consumer = consumer->weak_ptr_factory_.GetWeakPtr();
- auto weak_this = weak_ptr_factory_.GetWeakPtr();
- task_runner_->PostTask([weak_this, weak_consumer, tsid] {
- if (!weak_this || !weak_consumer)
- return;
- weak_this->ReadBuffersIntoConsumer(tsid, weak_consumer.get());
- });
+ uint64_t total_wr_size = 0;
+
+ // writev() can take at most IOV_MAX entries per call. Batch them.
+ constexpr size_t kIOVMax = IOV_MAX;
+ for (size_t i = 0; i < num_iovecs; i += kIOVMax) {
+ int iov_batch_size = static_cast<int>(std::min(num_iovecs - i, kIOVMax));
+ ssize_t wr_size = PERFETTO_EINTR(writev(fd, &iovecs[i], iov_batch_size));
+ if (wr_size <= 0) {
+ PERFETTO_PLOG("writev() failed");
+ stop_writing_into_file = true;
+ break;
+ }
+ total_wr_size += static_cast<size_t>(wr_size);
}
- // Keep this as tail call, just in case the consumer re-enters.
- consumer->consumer_->OnTraceData(std::move(packets), has_more);
- return true;
+ tracing_session->bytes_written_into_file += total_wr_size;
+
+ PERFETTO_DLOG("Draining into file, written: %" PRIu64 " KB, stop: %d",
+ (total_wr_size + 1023) / 1024, stop_writing_into_file);
+ return stop_writing_into_file;
}
void TracingServiceImpl::FreeBuffers(TracingSessionID tsid) {
@@ -3356,7 +3385,7 @@
// If we are stealing a write_into_file session, add a marker that explains
// why the trace has been stolen rather than creating an empty file. This is
// only for write_into_file traces. A similar code path deals with the case
- // of reading-back a seized trace from IPC in ReadBuffers().
+ // of reading-back a seized trace from IPC in ReadBuffersIntoConsumer().
if (!max_session->config.builtin_data_sources().disable_service_events()) {
std::vector<TracePacket> packets;
EmitSeizedForBugreportLifecycleEvent(&packets);
@@ -3667,6 +3696,7 @@
producer->set_name(kv.second->name_);
producer->set_sdk_version(kv.second->sdk_version_);
producer->set_uid(static_cast<int32_t>(kv.second->uid()));
+ producer->set_pid(static_cast<int32_t>(kv.second->pid()));
}
for (const auto& kv : service_->data_sources_) {
@@ -3676,6 +3706,42 @@
data_source->set_producer_id(
static_cast<int>(registered_data_source.producer_id));
}
+
+ svc_state.set_supports_tracing_sessions(true);
+ for (const auto& kv : service_->tracing_sessions_) {
+ const TracingSession& s = kv.second;
+ // List only tracing sessions for the calling UID (or everything for root).
+ if (uid_ != 0 && uid_ != s.consumer_uid)
+ continue;
+ auto* session = svc_state.add_tracing_sessions();
+ session->set_id(s.id);
+ session->set_consumer_uid(static_cast<int>(s.consumer_uid));
+ session->set_duration_ms(s.config.duration_ms());
+ session->set_num_data_sources(
+ static_cast<uint32_t>(s.data_source_instances.size()));
+ session->set_unique_session_name(s.config.unique_session_name());
+ for (const auto& snap_kv : s.initial_clock_snapshot) {
+ if (snap_kv.first == protos::pbzero::BUILTIN_CLOCK_REALTIME)
+ session->set_start_realtime_ns(static_cast<int64_t>(snap_kv.second));
+ }
+ for (const auto& buf : s.config.buffers())
+ session->add_buffer_size_kb(buf.size_kb());
+
+ switch (s.state) {
+ case TracingSession::State::DISABLED:
+ session->set_state("DISABLED");
+ break;
+ case TracingSession::State::CONFIGURED:
+ session->set_state("CONFIGURED");
+ break;
+ case TracingSession::State::STARTED:
+ session->set_state("STARTED");
+ break;
+ case TracingSession::State::DISABLING_WAITING_STOP_ACKS:
+ session->set_state("STOP_WAIT");
+ break;
+ }
+ }
callback(/*success=*/true, svc_state);
}
diff --git a/src/tracing/core/tracing_service_impl.h b/src/tracing/core/tracing_service_impl.h
index 5c4e650..99ee6ea 100644
--- a/src/tracing/core/tracing_service_impl.h
+++ b/src/tracing/core/tracing_service_impl.h
@@ -81,6 +81,11 @@
128 * 1024 - 512; // This is ipc::kIPCBufferSize - 512, see assertion in
// tracing_integration_test.cc and b/195065199
+ // This is a rough threshold to determine how many bytes to read from the
+ // buffers on each iteration when writing into a file. Since filtering
+ // allocates memory, this limits the amount of memory allocated.
+ static constexpr size_t kWriteIntoFileChunkSize = 1024 * 1024ul;
+
// The implementation behind the service endpoint exposed to each producer.
class ProducerEndpointImpl : public TracingService::ProducerEndpoint {
public:
@@ -140,6 +145,7 @@
}
uid_t uid() const { return uid_; }
+ pid_t pid() const { return pid_; }
private:
friend class TracingServiceImpl;
@@ -681,10 +687,33 @@
void ScrapeSharedMemoryBuffers(TracingSession*, ProducerEndpointImpl*);
void PeriodicClearIncrementalStateTask(TracingSessionID, bool post_next_only);
TraceBuffer* GetBufferByID(BufferID);
- bool ReadBuffers(TracingSessionID, TracingSession*, ConsumerEndpointImpl*);
+
// Returns true if `*tracing_session` is waiting for a trigger that hasn't
// happened.
- static bool IsWaitingForTrigger(TracingSession*);
+ static bool IsWaitingForTrigger(TracingSession* tracing_session);
+
+ // Reads the buffers from `*tracing_session` and returns them (along with some
+ // metadata packets).
+ //
+ // The function stops when the cumulative size of the return packets exceeds
+ // `threshold` (so it's not a strict upper bound) and sets `*has_more` to
+ // true, or when there are no more packets (and sets `*has_more` to false).
+ std::vector<TracePacket> ReadBuffers(TracingSession* tracing_session,
+ size_t threshold,
+ bool* has_more);
+
+ // If `*tracing_session` has a filter, applies it to `*packets`. Doesn't
+ // change the number of `*packets`, only their content.
+ void MaybeFilterPackets(TracingSession* tracing_session,
+ std::vector<TracePacket>* packets);
+
+ // If `*tracing_session` is configured to write into a file, writes `packets`
+ // into the file.
+ //
+ // Returns true if the file should be closed (because it's full or there has
+ // been an error), false otherwise.
+ bool WriteIntoFile(TracingSession* tracing_session,
+ std::vector<TracePacket> packets);
void OnStartTriggersTimeout(TracingSessionID tsid);
void MaybeLogUploadEvent(const TraceConfig&,
PerfettoStatsdAtom atom,
diff --git a/src/tracing/core/tracing_service_impl_unittest.cc b/src/tracing/core/tracing_service_impl_unittest.cc
index 41ef5d8..00b7a3a 100644
--- a/src/tracing/core/tracing_service_impl_unittest.cc
+++ b/src/tracing/core/tracing_service_impl_unittest.cc
@@ -30,6 +30,7 @@
#include "perfetto/tracing/core/data_source_config.h"
#include "perfetto/tracing/core/data_source_descriptor.h"
#include "src/base/test/test_task_runner.h"
+#include "src/protozero/filtering/filter_bytecode_generator.h"
#include "src/tracing/core/shared_memory_arbiter_impl.h"
#include "src/tracing/core/trace_writer_impl.h"
#include "src/tracing/test/mock_consumer.h"
@@ -1731,6 +1732,73 @@
Property(&protos::gen::TestEvent::str, Eq("payload")))));
}
+TEST_F(TracingServiceImplTest, WriteIntoFileFilterMultipleChunks) {
+ static const size_t kNumTestPackets = 5;
+ static const size_t kPayloadSize = 500 * 1024UL;
+ static_assert(kNumTestPackets * kPayloadSize >
+ TracingServiceImpl::kWriteIntoFileChunkSize,
+ "This test covers filtering multiple chunks");
+
+ std::unique_ptr<MockConsumer> consumer = CreateMockConsumer();
+ consumer->Connect(svc.get());
+
+ std::unique_ptr<MockProducer> producer = CreateMockProducer();
+ producer->Connect(svc.get(), "mock_producer");
+ producer->RegisterDataSource("data_source");
+
+ TraceConfig trace_config;
+ trace_config.add_buffers()->set_size_kb(4096);
+ auto* ds_config = trace_config.add_data_sources()->mutable_config();
+ ds_config->set_name("data_source");
+ ds_config->set_target_buffer(0);
+ trace_config.set_write_into_file(true);
+ trace_config.set_file_write_period_ms(100000); // 100s
+
+ protozero::FilterBytecodeGenerator filt;
+ // Message 0: root Trace proto.
+ filt.AddNestedField(1 /* root trace.packet*/, 1);
+ filt.EndMessage();
+ // Message 1: TracePacket proto. Allow all fields.
+ filt.AddSimpleFieldRange(1, 1000);
+ filt.EndMessage();
+ trace_config.mutable_trace_filter()->set_bytecode(filt.Serialize());
+
+ base::TempFile tmp_file = base::TempFile::Create();
+ consumer->EnableTracing(trace_config, base::ScopedFile(dup(tmp_file.fd())));
+
+ producer->WaitForTracingSetup();
+ producer->WaitForDataSourceSetup("data_source");
+ producer->WaitForDataSourceStart("data_source");
+
+ std::unique_ptr<TraceWriter> writer =
+ producer->CreateTraceWriter("data_source");
+ for (size_t i = 0; i < kNumTestPackets; i++) {
+ auto tp = writer->NewTracePacket();
+ std::string payload(kPayloadSize, 'c');
+ tp->set_for_testing()->set_str(payload.c_str(), payload.size());
+ }
+
+ writer->Flush();
+ writer.reset();
+
+ consumer->DisableTracing();
+ producer->WaitForDataSourceStop("data_source");
+ consumer->WaitForTracingDisabled();
+
+ consumer->GetTraceStats();
+ TraceStats stats = consumer->WaitForTraceStats(true);
+
+ std::string trace_raw;
+ ASSERT_TRUE(base::ReadFile(tmp_file.path().c_str(), &trace_raw));
+ protozero::ProtoDecoder dec(trace_raw.data(), trace_raw.size());
+ size_t total_size = 0;
+ for (auto field = dec.ReadField(); field.valid(); field = dec.ReadField()) {
+ total_size += field.size();
+ }
+ EXPECT_EQ(total_size, stats.filter_stats().output_bytes());
+ EXPECT_GT(total_size, kNumTestPackets * kPayloadSize);
+}
+
// Test the logic that allows the trace config to set the shm total size and
// page size from the trace config. Also check that, if the config doesn't
// specify a value we fall back on the hint provided by the producer.
diff --git a/src/tracing/internal/track_event_internal.cc b/src/tracing/internal/track_event_internal.cc
index 3717fb2..0c27e4a 100644
--- a/src/tracing/internal/track_event_internal.cc
+++ b/src/tracing/internal/track_event_internal.cc
@@ -26,11 +26,14 @@
#include "perfetto/tracing/track_event_interned_data_index.h"
#include "protos/perfetto/common/data_source_descriptor.gen.h"
#include "protos/perfetto/common/track_event_descriptor.pbzero.h"
+#include "protos/perfetto/trace/clock_snapshot.pbzero.h"
#include "protos/perfetto/trace/interned_data/interned_data.pbzero.h"
#include "protos/perfetto/trace/trace_packet_defaults.pbzero.h"
#include "protos/perfetto/trace/track_event/debug_annotation.pbzero.h"
#include "protos/perfetto/trace/track_event/track_descriptor.pbzero.h"
+using perfetto::protos::pbzero::ClockSnapshot;
+
namespace perfetto {
TrackEventSessionObserver::~TrackEventSessionObserver() = default;
@@ -48,6 +51,13 @@
static constexpr const char kLegacySlowPrefix[] = "disabled-by-default-";
static constexpr const char kSlowTag[] = "slow";
static constexpr const char kDebugTag[] = "debug";
+// Allows to specify a custom unit different than the default (ns) for
+// the incremental clock.
+// A multiplier of 1000 means that a timestamp = 3 should be
+// interpreted as 3000 ns = 3 us.
+// TODO(mohitms): Move it to TrackEventConfig.
+constexpr uint64_t kIncrementalTimestampUnitMultiplier = 1;
+static_assert(kIncrementalTimestampUnitMultiplier >= 1, "");
void ForEachObserver(
std::function<bool(TrackEventSessionObserver*&)> callback) {
@@ -303,50 +313,94 @@
}
// static
+TraceTimestamp TrackEventInternal::GetTraceTime() {
+ return {TrackEventIncrementalState::kClockIdIncremental, GetTimeNs()};
+}
+
+// static
int TrackEventInternal::GetSessionCount() {
return session_count_.load();
}
// static
-void TrackEventInternal::ResetIncrementalState(TraceWriterBase* trace_writer,
- TraceTimestamp timestamp) {
+void TrackEventInternal::ResetIncrementalState(
+ TraceWriterBase* trace_writer,
+ TrackEventIncrementalState* incr_state,
+ const TraceTimestamp& timestamp) {
+ auto sequence_timestamp = timestamp;
+ if (timestamp.clock_id != TrackEventInternal::GetClockId() &&
+ timestamp.clock_id != TrackEventIncrementalState::kClockIdIncremental) {
+ sequence_timestamp = TrackEventInternal::GetTraceTime();
+ }
+
+ incr_state->last_timestamp_ns = sequence_timestamp.value;
auto default_track = ThreadTrack::Current();
{
// Mark any incremental state before this point invalid. Also set up
// defaults so that we don't need to repeat constant data for each packet.
auto packet = NewTracePacket(
- trace_writer, timestamp,
+ trace_writer, incr_state, timestamp,
protos::pbzero::TracePacket::SEQ_INCREMENTAL_STATE_CLEARED);
auto defaults = packet->set_trace_packet_defaults();
- defaults->set_timestamp_clock_id(GetClockId());
+ defaults->set_timestamp_clock_id(
+ TrackEventIncrementalState::kClockIdIncremental);
// Establish the default track for this event sequence.
auto track_defaults = defaults->set_track_event_defaults();
track_defaults->set_track_uuid(default_track.uuid);
+
+ ClockSnapshot* clocks = packet->set_clock_snapshot();
+ // Trace clock.
+ ClockSnapshot::Clock* trace_clock = clocks->add_clocks();
+ trace_clock->set_clock_id(GetClockId());
+ trace_clock->set_timestamp(sequence_timestamp.value);
+ // Delta-encoded incremental clock in nano seconds.
+ // TODO(b/168311581): Make the unit of this clock configurable to allow
+ // trade-off between precision and encoded trace size.
+ ClockSnapshot::Clock* clock_incremental = clocks->add_clocks();
+ clock_incremental->set_clock_id(
+ TrackEventIncrementalState::kClockIdIncremental);
+ auto ts_unit_multiplier = kIncrementalTimestampUnitMultiplier;
+ clock_incremental->set_timestamp(sequence_timestamp.value /
+ ts_unit_multiplier);
+ clock_incremental->set_is_incremental(true);
+ clock_incremental->set_unit_multiplier_ns(ts_unit_multiplier);
}
// Every thread should write a descriptor for its default track, because most
// trace points won't explicitly reference it. We also write the process
// descriptor from every thread that writes trace events to ensure it gets
// emitted at least once.
- WriteTrackDescriptor(default_track, trace_writer);
- WriteTrackDescriptor(ProcessTrack::Current(), trace_writer);
+ WriteTrackDescriptor(default_track, trace_writer, incr_state,
+ sequence_timestamp);
+
+ WriteTrackDescriptor(ProcessTrack::Current(), trace_writer, incr_state,
+ sequence_timestamp);
}
// static
protozero::MessageHandle<protos::pbzero::TracePacket>
TrackEventInternal::NewTracePacket(TraceWriterBase* trace_writer,
- TraceTimestamp timestamp,
+ TrackEventIncrementalState* incr_state,
+ const TraceTimestamp& timestamp,
uint32_t seq_flags) {
auto packet = trace_writer->NewTracePacket();
- packet->set_timestamp(timestamp.nanoseconds);
- if (timestamp.clock_id != GetClockId()) {
- packet->set_timestamp_clock_id(static_cast<uint32_t>(timestamp.clock_id));
- } else if (GetClockId() != protos::pbzero::BUILTIN_CLOCK_BOOTTIME) {
- // TODO(skyostil): Stop emitting the clock id for the default trace clock
- // for every event once the trace processor understands trace packet
- // defaults.
- packet->set_timestamp_clock_id(GetClockId());
+ if (timestamp.clock_id == TrackEventIncrementalState::kClockIdIncremental) {
+ if (incr_state->last_timestamp_ns <= timestamp.value) {
+ // No need to set the clock id here, since kClockIdIncremental is the
+ // clock id assumed by default.
+ auto ts_unit_multiplier = kIncrementalTimestampUnitMultiplier;
+ auto time_diff_ns = timestamp.value - incr_state->last_timestamp_ns;
+ packet->set_timestamp(time_diff_ns / ts_unit_multiplier);
+ incr_state->last_timestamp_ns = timestamp.value;
+ } else {
+ // TODO(mohitms): Consider using kIncrementalTimestampUnitMultiplier.
+ packet->set_timestamp(timestamp.value);
+ packet->set_timestamp_clock_id(GetClockId());
+ }
+ } else {
+ packet->set_timestamp(timestamp.value);
+ packet->set_timestamp_clock_id(timestamp.clock_id);
}
packet->set_sequence_flags(seq_flags);
return packet;
@@ -359,11 +413,10 @@
const Category* category,
const char* name,
perfetto::protos::pbzero::TrackEvent::Type type,
- TraceTimestamp timestamp) {
+ const TraceTimestamp& timestamp) {
PERFETTO_DCHECK(g_main_thread);
PERFETTO_DCHECK(!incr_state->was_cleared);
-
- auto packet = NewTracePacket(trace_writer, timestamp);
+ auto packet = NewTracePacket(trace_writer, incr_state, timestamp);
EventContext ctx(std::move(packet), incr_state);
auto track_event = ctx.event();
diff --git a/src/tracing/test/api_integrationtest.cc b/src/tracing/test/api_integrationtest.cc
index 26d8a03..8aa589c 100644
--- a/src/tracing/test/api_integrationtest.cc
+++ b/src/tracing/test/api_integrationtest.cc
@@ -59,6 +59,7 @@
#include "protos/perfetto/common/track_event_descriptor.pbzero.h"
#include "protos/perfetto/config/interceptor_config.gen.h"
#include "protos/perfetto/config/track_event/track_event_config.gen.h"
+#include "protos/perfetto/trace/clock_snapshot.gen.h"
#include "protos/perfetto/trace/clock_snapshot.pbzero.h"
#include "protos/perfetto/trace/gpu/gpu_render_stage_event.gen.h"
#include "protos/perfetto/trace/gpu/gpu_render_stage_event.pbzero.h"
@@ -72,6 +73,7 @@
#include "protos/perfetto/trace/trace.pbzero.h"
#include "protos/perfetto/trace/trace_packet.gen.h"
#include "protos/perfetto/trace/trace_packet.pbzero.h"
+#include "protos/perfetto/trace/trace_packet_defaults.gen.h"
#include "protos/perfetto/trace/track_event/chrome_process_descriptor.gen.h"
#include "protos/perfetto/trace/track_event/chrome_process_descriptor.pbzero.h"
#include "protos/perfetto/trace/track_event/counter_descriptor.gen.h"
@@ -968,6 +970,11 @@
bool process_descriptor_found = false;
uint32_t sequence_id = 0;
int32_t cur_pid = perfetto::test::GetCurrentProcessId();
+ uint64_t recent_absolute_time_ns = 0;
+ bool found_incremental_clock = false;
+ constexpr auto kClockIdIncremental =
+ perfetto::internal::TrackEventIncrementalState::kClockIdIncremental;
+
for (const auto& packet : trace.packet()) {
if (packet.has_track_descriptor()) {
const auto& desc = packet.track_descriptor();
@@ -984,6 +991,17 @@
incremental_state_was_cleared = true;
categories.clear();
event_names.clear();
+ EXPECT_EQ(kClockIdIncremental,
+ packet.trace_packet_defaults().timestamp_clock_id());
+ }
+ if (packet.has_clock_snapshot()) {
+ for (auto& clock : packet.clock_snapshot().clocks()) {
+ if (clock.is_incremental()) {
+ found_incremental_clock = true;
+ recent_absolute_time_ns = clock.timestamp();
+ EXPECT_EQ(kClockIdIncremental, clock.clock_id());
+ }
+ }
}
if (!packet.has_track_event())
@@ -1013,18 +1031,13 @@
event_names[it.iid()] = it.name();
}
}
-
- EXPECT_GT(packet.timestamp(), 0u);
- EXPECT_LE(packet.timestamp(), now);
-#if !PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE) && \
- !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+ EXPECT_TRUE(found_incremental_clock);
+ uint64_t absolute_timestamp = packet.timestamp() + recent_absolute_time_ns;
+ recent_absolute_time_ns = absolute_timestamp;
+ EXPECT_GT(absolute_timestamp, 0u);
+ EXPECT_LE(absolute_timestamp, now);
+ // Packet uses default (incremental) clock.
EXPECT_FALSE(packet.has_timestamp_clock_id());
-#else
- constexpr auto kClockMonotonic =
- perfetto::protos::pbzero::ClockSnapshot::Clock::MONOTONIC;
- EXPECT_EQ(packet.timestamp_clock_id(),
- static_cast<uint32_t>(kClockMonotonic));
-#endif
if (track_event.type() ==
perfetto::protos::gen::TrackEvent::TYPE_SLICE_BEGIN) {
EXPECT_FALSE(begin_found);
@@ -1051,6 +1064,84 @@
TestCategoryAsTemplateParameter<kTestCategory>();
}
+TEST_P(PerfettoApiTest, TrackEventWithIncrementalTimestamp) {
+ // Create a new trace session.
+ auto* tracing_session = NewTraceWithCategories({"bar"});
+ constexpr auto kClockIdIncremental =
+ perfetto::internal::TrackEventIncrementalState::kClockIdIncremental;
+ tracing_session->get()->StartBlocking();
+
+ std::map<uint64_t, std::string> event_names;
+
+ auto empty_lambda = [](perfetto::EventContext) {};
+
+ constexpr uint64_t kInstantEvent1Time = 92718891479583;
+ TRACE_EVENT_INSTANT(
+ "bar", "InstantEvent1",
+ perfetto::TraceTimestamp{kClockIdIncremental, kInstantEvent1Time},
+ empty_lambda);
+
+ constexpr uint64_t kInstantEvent2Time = 92718891618959;
+ TRACE_EVENT_INSTANT(
+ "bar", "InstantEvent2",
+ perfetto::TraceTimestamp{kClockIdIncremental, kInstantEvent2Time},
+ empty_lambda);
+
+ perfetto::TrackEvent::Flush();
+ tracing_session->get()->StopBlocking();
+
+ std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
+ perfetto::protos::gen::Trace trace;
+ ASSERT_TRUE(trace.ParseFromArray(raw_trace.data(), raw_trace.size()));
+
+ uint64_t absolute_timestamp = 0;
+ int event_count = 0;
+ // Go through the packets and add the timestamps of those packets that use the
+ // incremental clock - in order to get the absolute timestamps of the track
+ // events.
+ for (const auto& packet : trace.packet()) {
+ if (packet.has_clock_snapshot()) {
+ for (auto& clock : packet.clock_snapshot().clocks()) {
+ if (clock.is_incremental()) {
+ absolute_timestamp = clock.timestamp();
+ EXPECT_EQ(clock.clock_id(), kClockIdIncremental);
+ }
+ }
+ } else if (!packet.has_timestamp_clock_id()) {
+ // Packets that don't have a timestamp_clock_id default to the incremental
+ // clock.
+ absolute_timestamp += packet.timestamp();
+ }
+
+ if (packet.sequence_flags() &
+ perfetto::protos::pbzero::TracePacket::SEQ_INCREMENTAL_STATE_CLEARED) {
+ event_names.clear();
+ }
+
+ if (!packet.has_track_event())
+ continue;
+
+ // Update incremental state.
+ if (packet.has_interned_data()) {
+ const auto& interned_data = packet.interned_data();
+ for (const auto& it : interned_data.event_names()) {
+ EXPECT_EQ(event_names.find(it.iid()), event_names.end());
+ event_names[it.iid()] = it.name();
+ }
+ }
+
+ if (event_names[packet.track_event().name_iid()] == "InstantEvent1") {
+ event_count++;
+ ASSERT_EQ(absolute_timestamp, kInstantEvent1Time);
+ } else if (event_names[packet.track_event().name_iid()] ==
+ "InstantEvent2") {
+ event_count++;
+ ASSERT_EQ(absolute_timestamp, kInstantEvent2Time);
+ }
+ }
+ ASSERT_EQ(event_count, 2);
+}
+
TEST_P(PerfettoApiTest, TrackEventCategories) {
// Create a new trace session.
auto* tracing_session = NewTraceWithCategories({"bar"});
@@ -1754,6 +1845,9 @@
for (const auto& packet : trace.packet()) {
if (!packet.has_track_event())
continue;
+
+ EXPECT_EQ(packet.timestamp_clock_id(),
+ static_cast<uint32_t>(perfetto::TrackEvent::GetTraceClockId()));
event_count++;
switch (packet.track_event().type()) {
case perfetto::protos::gen::TrackEvent::TYPE_SLICE_BEGIN:
diff --git a/src/tracing/test/mock_consumer.cc b/src/tracing/test/mock_consumer.cc
index 2d7dc4c..cfc2458 100644
--- a/src/tracing/test/mock_consumer.cc
+++ b/src/tracing/test/mock_consumer.cc
@@ -125,11 +125,13 @@
service_endpoint_->GetTraceStats();
}
-void MockConsumer::WaitForTraceStats(bool success) {
+TraceStats MockConsumer::WaitForTraceStats(bool success) {
static int i = 0;
auto checkpoint_name = "on_trace_stats_" + std::to_string(i++);
auto on_trace_stats = task_runner_->CreateCheckpoint(checkpoint_name);
- auto result_callback = [on_trace_stats](bool, const TraceStats&) {
+ TraceStats stats;
+ auto result_callback = [on_trace_stats, &stats](bool, const TraceStats& s) {
+ stats = s;
on_trace_stats();
};
if (success) {
@@ -142,6 +144,7 @@
.WillOnce(Invoke(result_callback));
}
task_runner_->RunUntilCheckpoint(checkpoint_name);
+ return stats;
}
void MockConsumer::ObserveEvents(uint32_t enabled_event_types) {
diff --git a/src/tracing/test/mock_consumer.h b/src/tracing/test/mock_consumer.h
index 9ba12b4..f6329bf 100644
--- a/src/tracing/test/mock_consumer.h
+++ b/src/tracing/test/mock_consumer.h
@@ -57,7 +57,7 @@
FlushRequest Flush(uint32_t timeout_ms = 10000);
std::vector<protos::gen::TracePacket> ReadBuffers();
void GetTraceStats();
- void WaitForTraceStats(bool success);
+ TraceStats WaitForTraceStats(bool success);
TracingServiceState QueryServiceState();
void ObserveEvents(uint32_t enabled_event_types);
ObservableEvents WaitForObservableEvents();
diff --git a/src/tracing/track_event_state_tracker.cc b/src/tracing/track_event_state_tracker.cc
index 7cb1b09..7ff50ac 100644
--- a/src/tracing/track_event_state_tracker.cc
+++ b/src/tracing/track_event_state_tracker.cc
@@ -17,8 +17,10 @@
#include "perfetto/tracing/track_event_state_tracker.h"
#include "perfetto/ext/base/hash.h"
+#include "perfetto/tracing/internal/track_event_internal.h"
#include "protos/perfetto/common/interceptor_descriptor.gen.h"
+#include "protos/perfetto/trace/clock_snapshot.pbzero.h"
#include "protos/perfetto/trace/interned_data/interned_data.pbzero.h"
#include "protos/perfetto/trace/trace_packet.pbzero.h"
#include "protos/perfetto/trace/trace_packet_defaults.pbzero.h"
@@ -30,6 +32,8 @@
namespace perfetto {
+using internal::TrackEventIncrementalState;
+
TrackEventStateTracker::~TrackEventStateTracker() = default;
TrackEventStateTracker::Delegate::~Delegate() = default;
@@ -45,8 +49,15 @@
perfetto::protos::pbzero::TrackEvent::Decoder track_event(
packet.track_event());
- // TODO(skyostil): Support incremental timestamps.
+ auto clock_id = packet.timestamp_clock_id();
+ if (!packet.has_timestamp_clock_id())
+ clock_id = sequence_state.default_clock_id;
uint64_t timestamp = packet.timestamp();
+ // TODO(mohitms): Incorporate unit multiplier as well.
+ if (clock_id == TrackEventIncrementalState::kClockIdIncremental) {
+ timestamp += sequence_state.most_recent_absolute_time_ns;
+ sequence_state.most_recent_absolute_time_ns = timestamp;
+ }
Track* track = &sequence_state.track;
if (track_event.has_track_uuid()) {
@@ -163,6 +174,19 @@
}
#endif
+ perfetto::protos::pbzero::ClockSnapshot::Decoder snapshot(
+ packet.clock_snapshot());
+ for (auto it = snapshot.clocks(); it; ++it) {
+ perfetto::protos::pbzero::ClockSnapshot::Clock::Decoder clock(*it);
+ // TODO(mohitms) : Handle the incremental clock other than default one.
+ if (clock.is_incremental() &&
+ clock.clock_id() == TrackEventIncrementalState::kClockIdIncremental) {
+ sequence_state.most_recent_absolute_time_ns =
+ clock.timestamp() * clock.unit_multiplier_ns();
+ break;
+ }
+ }
+
if (packet.sequence_flags() &
perfetto::protos::pbzero::TracePacket::SEQ_INCREMENTAL_STATE_CLEARED) {
// Convert any existing event names and categories on the stack to
@@ -208,6 +232,8 @@
perfetto::protos::pbzero::TrackEventDefaults::Decoder
track_event_defaults(defaults.track_event_defaults());
sequence_state.track.uuid = track_event_defaults.track_uuid();
+ if (defaults.has_timestamp_clock_id())
+ sequence_state.default_clock_id = defaults.timestamp_clock_id();
}
}
if (packet.has_track_descriptor()) {
diff --git a/test/cmdline_integrationtest.cc b/test/cmdline_integrationtest.cc
index 0fc0520..0747789 100644
--- a/test/cmdline_integrationtest.cc
+++ b/test/cmdline_integrationtest.cc
@@ -20,10 +20,8 @@
#include "perfetto/base/build_config.h"
#include "perfetto/base/logging.h"
-#include "perfetto/ext/base/file_utils.h"
#include "perfetto/ext/base/pipe.h"
#include "perfetto/ext/base/string_utils.h"
-#include "perfetto/ext/base/subprocess.h"
#include "perfetto/ext/base/utils.h"
#include "perfetto/ext/traced/traced.h"
#include "perfetto/protozero/scattered_heap_buffer.h"
@@ -68,103 +66,6 @@
return path;
}
-// This class is a reference to a child process that has in essence been execv
-// to the requested binary. The process will start and then wait for Run()
-// before proceeding. We use this to fork new processes before starting any
-// additional threads in the parent process (otherwise you would risk
-// deadlocks), but pause the forked processes until remaining setup (including
-// any necessary threads) in the parent process is complete.
-class Exec {
- public:
- // Starts the forked process that was created. If not null then |stderr_out|
- // will contain the stderr of the process.
- int Run(std::string* stderr_out = nullptr) {
- // We can't be the child process.
- PERFETTO_CHECK(getpid() != subprocess_.pid());
- // Will cause the entrypoint to continue.
- PERFETTO_CHECK(write(*sync_pipe_.wr, "1", 1) == 1);
- sync_pipe_.wr.reset();
- subprocess_.Wait();
-
- if (stderr_out) {
- *stderr_out = std::move(subprocess_.output());
- } else {
- PERFETTO_LOG("Child proc %d exited with stderr: \"%s\"",
- subprocess_.pid(), subprocess_.output().c_str());
- }
- return subprocess_.returncode();
- }
-
- Exec(const std::string& argv0,
- std::initializer_list<std::string> args,
- std::string input = "") {
- subprocess_.args.stderr_mode = base::Subprocess::kBuffer;
- subprocess_.args.stdout_mode = base::Subprocess::kDevNull;
- subprocess_.args.input = input;
-
-#if PERFETTO_BUILDFLAG(PERFETTO_START_DAEMONS)
- constexpr bool kUseSystemBinaries = false;
-#else
- constexpr bool kUseSystemBinaries = true;
-#endif
-
- std::vector<std::string>& cmd = subprocess_.args.exec_cmd;
- if (kUseSystemBinaries) {
- PERFETTO_CHECK(TestHelper::kDefaultMode ==
- TestHelper::Mode::kUseSystemService);
- cmd.push_back("/system/bin/" + argv0);
- cmd.insert(cmd.end(), args.begin(), args.end());
- } else {
- PERFETTO_CHECK(TestHelper::kDefaultMode ==
- TestHelper::Mode::kStartDaemons);
- subprocess_.args.env.push_back(
- std::string("PERFETTO_PRODUCER_SOCK_NAME=") +
- TestHelper::GetDefaultModeProducerSocketName());
- subprocess_.args.env.push_back(
- std::string("PERFETTO_CONSUMER_SOCK_NAME=") +
- TestHelper::GetDefaultModeConsumerSocketName());
- cmd.push_back(base::GetCurExecutableDir() + "/" + argv0);
- cmd.insert(cmd.end(), args.begin(), args.end());
- }
-
- if (!base::FileExists(cmd[0])) {
- PERFETTO_FATAL(
- "Cannot find %s. Make sure that the target has been built and, on "
- "Android, pushed to the device.",
- cmd[0].c_str());
- }
-
- // This pipe blocks the execution of the child process until the main test
- // process calls Run(). There are two conflicting problems here:
- // 1) We can't fork() subprocesses too late, because the test spawns threads
- // for hosting the service. fork+threads = bad (see aosp/1089744).
- // 2) We can't run the subprocess too early, because we need to wait that
- // the service threads are ready before trying to connect from the child
- // process.
- sync_pipe_ = base::Pipe::Create();
- int sync_pipe_rd = *sync_pipe_.rd;
- subprocess_.args.preserve_fds.push_back(sync_pipe_rd);
-
- // This lambda will be called on the forked child process after having
- // setup pipe redirection and closed all FDs, right before the exec().
- // The Subprocesss harness will take care of closing also |sync_pipe_.wr|.
- subprocess_.args.posix_entrypoint_for_testing = [sync_pipe_rd] {
- // Don't add any logging here, all file descriptors are closed and trying
- // to log will likely cause undefined behaviors.
- char ignored = 0;
- PERFETTO_CHECK(PERFETTO_EINTR(read(sync_pipe_rd, &ignored, 1)) > 0);
- PERFETTO_CHECK(close(sync_pipe_rd) == 0 || errno == EINTR);
- };
-
- subprocess_.Start();
- sync_pipe_.rd.reset();
- }
-
- private:
- base::Subprocess subprocess_;
- base::Pipe sync_pipe_;
-};
-
class PerfettoCmdlineTest : public ::testing::Test {
public:
void SetUp() override {
diff --git a/test/cts/Android.bp b/test/cts/Android.bp
index 3958566..e8571a6 100644
--- a/test/cts/Android.bp
+++ b/test/cts/Android.bp
@@ -14,6 +14,7 @@
"end_to_end_integrationtest_cts.cc",
"heapprofd_java_test_cts.cc",
"heapprofd_test_cts.cc",
+ "reporter_test_cts.cc",
"traced_perf_test_cts.cc",
":perfetto_protos_perfetto_config_cpp_gen",
],
@@ -48,6 +49,9 @@
suffix: "64",
},
},
+ data: [
+ ":CtsPerfettoReporterApp"
+ ],
stl: "libc++_static",
defaults: [
"perfetto_defaults",
diff --git a/test/cts/AndroidTest.xml b/test/cts/AndroidTest.xml
index e9f5a5f..db14fe6 100644
--- a/test/cts/AndroidTest.xml
+++ b/test/cts/AndroidTest.xml
@@ -27,6 +27,7 @@
<option name="test-file-name" value="CtsPerfettoReleaseApp.apk" />
<option name="test-file-name" value="CtsPerfettoProfileableApp.apk" />
<option name="test-file-name" value="CtsPerfettoNonProfileableApp.apk" />
+ <option name="test-file-name" value="CtsPerfettoReporterApp.apk" />
</target_preparer>
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
<option name="cleanup" value="true" />
diff --git a/test/cts/reporter/Android.bp b/test/cts/reporter/Android.bp
new file mode 100644
index 0000000..ab2633b
--- /dev/null
+++ b/test/cts/reporter/Android.bp
@@ -0,0 +1,30 @@
+// Copyright (C) 2022 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.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "external_perfetto_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["external_perfetto_license"],
+}
+
+android_test_helper_app {
+ name: "CtsPerfettoReporterApp",
+ manifest: "AndroidManifest.xml",
+ srcs: ["src/**/*.java"],
+ platform_apis: true,
+ privileged: true,
+}
diff --git a/test/cts/reporter/AndroidManifest.xml b/test/cts/reporter/AndroidManifest.xml
new file mode 100755
index 0000000..9a2ef40
--- /dev/null
+++ b/test/cts/reporter/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2022 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.perfetto.cts.reporter">
+ <uses-permission android:name="android.permission.DUMP"/>
+ <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
+1
+ <application>
+ <service
+ android:name=".PerfettoReportService"
+ android:permission="android.permission.BIND_TRACE_REPORT_SERVICE"/>
+ </application>
+</manifest>
diff --git a/test/cts/reporter/src/android/perfetto/cts/reporter/PerfettoReportService.java b/test/cts/reporter/src/android/perfetto/cts/reporter/PerfettoReportService.java
new file mode 100644
index 0000000..5d0c8af
--- /dev/null
+++ b/test/cts/reporter/src/android/perfetto/cts/reporter/PerfettoReportService.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package android.perfetto.cts.reporter;
+
+import android.os.ParcelFileDescriptor.AutoCloseInputStream;
+import android.service.tracing.TraceReportService;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.UUID;
+
+public class PerfettoReportService extends TraceReportService {
+ public static final String TAG = "PerfettoReportService";
+
+ @Override
+ public void onReportTrace(TraceParams args) {
+ File f = new File(getExternalFilesDir(null), args.getUuid().toString());
+ try {
+ boolean created = f.createNewFile();
+ if (!created) {
+ throw new IllegalStateException("Failed to create file");
+ }
+ try (AutoCloseInputStream i = new AutoCloseInputStream(args.getFd())) {
+ try (FileOutputStream o = new FileOutputStream((f))) {
+ o.write(i.readAllBytes());
+ }
+ }
+ } catch (IOException ex) {
+ throw new IllegalStateException("IO Exception", ex);
+ }
+ }
+}
diff --git a/test/cts/reporter_test_cts.cc b/test/cts/reporter_test_cts.cc
new file mode 100644
index 0000000..f9294f4
--- /dev/null
+++ b/test/cts/reporter_test_cts.cc
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2022 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 <sys/system_properties.h>
+#include <random>
+#include "test/gtest_and_gmock.h"
+
+#include "perfetto/ext/base/android_utils.h"
+#include "perfetto/ext/base/uuid.h"
+#include "perfetto/tracing/core/data_source_config.h"
+#include "src/base/test/test_task_runner.h"
+#include "test/android_test_utils.h"
+#include "test/test_helper.h"
+
+#include "protos/perfetto/config/test_config.gen.h"
+#include "protos/perfetto/trace/test_event.gen.h"
+#include "protos/perfetto/trace/trace.gen.h"
+#include "protos/perfetto/trace/trace_packet.gen.h"
+
+namespace perfetto {
+namespace {
+
+TEST(PerfettoReporterTest, TestEndToEndReport) {
+ base::TestTaskRunner task_runner;
+ TestHelper helper(&task_runner);
+ helper.ConnectFakeProducer();
+
+ TraceConfig trace_config;
+ trace_config.add_buffers()->set_size_kb(1024);
+ trace_config.set_duration_ms(200);
+ trace_config.set_allow_user_build_tracing(true);
+
+ auto* ds_config = trace_config.add_data_sources()->mutable_config();
+ ds_config->set_name("android.perfetto.FakeProducer");
+ ds_config->set_target_buffer(0);
+
+ base::Uuid uuid = base::Uuidv4();
+ trace_config.set_trace_uuid_lsb(uuid.lsb());
+ trace_config.set_trace_uuid_msb(uuid.msb());
+
+ static constexpr uint32_t kRandomSeed = 42;
+ static constexpr uint32_t kEventCount = 1;
+ static constexpr uint32_t kMessageSizeBytes = 2;
+ ds_config->mutable_for_testing()->set_seed(kRandomSeed);
+ ds_config->mutable_for_testing()->set_message_count(kEventCount);
+ ds_config->mutable_for_testing()->set_message_size(kMessageSizeBytes);
+ ds_config->mutable_for_testing()->set_send_batch_on_register(true);
+
+ auto* report_config = trace_config.mutable_android_report_config();
+ report_config->set_reporter_service_package("android.perfetto.cts.reporter");
+ report_config->set_reporter_service_class(
+ "android.perfetto.cts.reporter.PerfettoReportService");
+ report_config->set_use_pipe_in_framework_for_testing(true);
+
+ // We have to construct all the processes we want to fork before we start the
+ // service with |StartServiceIfRequired()|. this is because it is unsafe
+ // (could deadlock) to fork after we've spawned some threads which might
+ // printf (and thus hold locks).
+ auto perfetto_proc = Exec("perfetto",
+ {
+ "--upload",
+ "-c",
+ "-",
+ },
+ trace_config.SerializeAsString());
+
+ std::string stderr_str;
+ EXPECT_EQ(0, perfetto_proc.Run(&stderr_str)) << stderr_str;
+
+ static constexpr char kPath[] =
+ "/sdcard/Android/data/android.perfetto.cts.reporter/files/";
+ std::string path = kPath + uuid.ToPrettyString();
+ static constexpr uint32_t kIterationSleepMs = 500;
+ static constexpr uint32_t kIterationCount =
+ kDefaultTestTimeoutMs / kIterationSleepMs;
+ for (size_t i = 0; i < kIterationCount; ++i) {
+ if (!base::FileExists(path)) {
+ base::SleepMicroseconds(kIterationSleepMs * 1000);
+ continue;
+ }
+
+ std::string trace_str;
+ ASSERT_TRUE(base::ReadFile(path, &trace_str));
+
+ protos::gen::Trace trace;
+ ASSERT_TRUE(trace.ParseFromString(trace_str));
+ int for_testing = 0;
+ for (const auto& packet : trace.packet()) {
+ for_testing += packet.has_for_testing();
+ }
+ ASSERT_EQ(for_testing, kEventCount);
+ return;
+ }
+ FAIL() << "Timed out waiting for trace file";
+}
+
+} // namespace
+} // namespace perfetto
diff --git a/test/data/ui-screenshots/ui-routing_navigate_navigate_back_and_forward.png.sha256 b/test/data/ui-screenshots/ui-routing_navigate_navigate_back_and_forward.png.sha256
index 58243f3..9972f70 100644
--- a/test/data/ui-screenshots/ui-routing_navigate_navigate_back_and_forward.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_navigate_navigate_back_and_forward.png.sha256
@@ -1 +1 @@
-29cdb8b1a7fb2df704fa16fb9d342aef9f61d17dea58c5c2a57ccc0bda3b874f
\ No newline at end of file
+11b4adcbe2171d25356df8bf15e96c2d87a15a9e6e59f9b6349bbfa919620255
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_open_invalid_trace_from_blank_page.png.sha256 b/test/data/ui-screenshots/ui-routing_open_invalid_trace_from_blank_page.png.sha256
index 2104823..4514e39 100644
--- a/test/data/ui-screenshots/ui-routing_open_invalid_trace_from_blank_page.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_open_invalid_trace_from_blank_page.png.sha256
@@ -1 +1 @@
-459f3bcd870dc4d697510e380983e1daf39355732766e754f6649f066c60007e
\ No newline at end of file
+2a99973826b842e7f64d79bf8ddf5cc188e6bf8973e1fda073f9cd9cb6a9a40d
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_invalid_trace.png.sha256 b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_invalid_trace.png.sha256
index 89e28ad..356685a 100644
--- a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_invalid_trace.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_invalid_trace.png.sha256
@@ -1 +1 @@
-c64e2df83124c5e42b7552d8e673fa390ee31d1b6c5a8acd07eb022afd97b84d
\ No newline at end of file
+5c8a69fa3913fe79644858f21f08f580d8f7a14467fa772bca96ea641ad3a58e
\ No newline at end of file
diff --git a/test/synth_common.py b/test/synth_common.py
index d8c39fc..92d8e3f 100644
--- a/test/synth_common.py
+++ b/test/synth_common.py
@@ -16,7 +16,7 @@
import argparse
from collections import namedtuple
-from google.protobuf import descriptor, descriptor_pb2, message_factory, descriptor_pool
+from google.protobuf import descriptor_pb2, message_factory, descriptor_pool
CLONE_THREAD = 0x00010000
CLONE_VFORK = 0x00004000
diff --git a/test/test_helper.h b/test/test_helper.h
index 904623f..778f88a 100644
--- a/test/test_helper.h
+++ b/test/test_helper.h
@@ -20,8 +20,11 @@
#include <stdio.h>
#include <stdlib.h>
+#include "perfetto/base/build_config.h"
+#include "perfetto/ext/base/file_utils.h"
#include "perfetto/ext/base/optional.h"
#include "perfetto/ext/base/scoped_file.h"
+#include "perfetto/ext/base/subprocess.h"
#include "perfetto/ext/base/thread_task_runner.h"
#include "perfetto/ext/base/utils.h"
#include "perfetto/ext/tracing/core/consumer.h"
@@ -315,6 +318,107 @@
std::unique_ptr<TracingService::ConsumerEndpoint> endpoint_; // Keep last.
};
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+
+// This class is a reference to a child process that has in essence been execv
+// to the requested binary. The process will start and then wait for Run()
+// before proceeding. We use this to fork new processes before starting any
+// additional threads in the parent process (otherwise you would risk
+// deadlocks), but pause the forked processes until remaining setup (including
+// any necessary threads) in the parent process is complete.
+class Exec {
+ public:
+ // Starts the forked process that was created. If not null then |stderr_out|
+ // will contain the stderr of the process.
+ int Run(std::string* stderr_out = nullptr) {
+ // We can't be the child process.
+ PERFETTO_CHECK(getpid() != subprocess_.pid());
+ // Will cause the entrypoint to continue.
+ PERFETTO_CHECK(write(*sync_pipe_.wr, "1", 1) == 1);
+ sync_pipe_.wr.reset();
+ subprocess_.Wait();
+
+ if (stderr_out) {
+ *stderr_out = std::move(subprocess_.output());
+ } else {
+ PERFETTO_LOG("Child proc %d exited with stderr: \"%s\"",
+ subprocess_.pid(), subprocess_.output().c_str());
+ }
+ return subprocess_.returncode();
+ }
+
+ Exec(const std::string& argv0,
+ std::initializer_list<std::string> args,
+ std::string input = "") {
+ subprocess_.args.stderr_mode = base::Subprocess::kBuffer;
+ subprocess_.args.stdout_mode = base::Subprocess::kDevNull;
+ subprocess_.args.input = input;
+
+#if PERFETTO_BUILDFLAG(PERFETTO_START_DAEMONS)
+ constexpr bool kUseSystemBinaries = false;
+#else
+ constexpr bool kUseSystemBinaries = true;
+#endif
+
+ std::vector<std::string>& cmd = subprocess_.args.exec_cmd;
+ if (kUseSystemBinaries) {
+ PERFETTO_CHECK(TestHelper::kDefaultMode ==
+ TestHelper::Mode::kUseSystemService);
+ cmd.push_back("/system/bin/" + argv0);
+ cmd.insert(cmd.end(), args.begin(), args.end());
+ } else {
+ PERFETTO_CHECK(TestHelper::kDefaultMode ==
+ TestHelper::Mode::kStartDaemons);
+ subprocess_.args.env.push_back(
+ std::string("PERFETTO_PRODUCER_SOCK_NAME=") +
+ TestHelper::GetDefaultModeProducerSocketName());
+ subprocess_.args.env.push_back(
+ std::string("PERFETTO_CONSUMER_SOCK_NAME=") +
+ TestHelper::GetDefaultModeConsumerSocketName());
+ cmd.push_back(base::GetCurExecutableDir() + "/" + argv0);
+ cmd.insert(cmd.end(), args.begin(), args.end());
+ }
+
+ if (!base::FileExists(cmd[0])) {
+ PERFETTO_FATAL(
+ "Cannot find %s. Make sure that the target has been built and, on "
+ "Android, pushed to the device.",
+ cmd[0].c_str());
+ }
+
+ // This pipe blocks the execution of the child process until the main test
+ // process calls Run(). There are two conflicting problems here:
+ // 1) We can't fork() subprocesses too late, because the test spawns threads
+ // for hosting the service. fork+threads = bad (see aosp/1089744).
+ // 2) We can't run the subprocess too early, because we need to wait that
+ // the service threads are ready before trying to connect from the child
+ // process.
+ sync_pipe_ = base::Pipe::Create();
+ int sync_pipe_rd = *sync_pipe_.rd;
+ subprocess_.args.preserve_fds.push_back(sync_pipe_rd);
+
+ // This lambda will be called on the forked child process after having
+ // setup pipe redirection and closed all FDs, right before the exec().
+ // The Subprocesss harness will take care of closing also |sync_pipe_.wr|.
+ subprocess_.args.posix_entrypoint_for_testing = [sync_pipe_rd] {
+ // Don't add any logging here, all file descriptors are closed and trying
+ // to log will likely cause undefined behaviors.
+ char ignored = 0;
+ PERFETTO_CHECK(PERFETTO_EINTR(read(sync_pipe_rd, &ignored, 1)) > 0);
+ PERFETTO_CHECK(close(sync_pipe_rd) == 0 || errno == EINTR);
+ };
+
+ subprocess_.Start();
+ sync_pipe_.rd.reset();
+ }
+
+ private:
+ base::Subprocess subprocess_;
+ base::Pipe sync_pipe_;
+};
+
+#endif // !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+
} // namespace perfetto
#endif // TEST_TEST_HELPER_H_
diff --git a/test/trace_processor/chrome/scroll_jank_mojo_simple_watcher.out b/test/trace_processor/chrome/scroll_jank_mojo_simple_watcher.out
index 13709b3..207e7d2 100644
--- a/test/trace_processor/chrome/scroll_jank_mojo_simple_watcher.out
+++ b/test/trace_processor/chrome/scroll_jank_mojo_simple_watcher.out
@@ -2,4 +2,4 @@
"trace_id","jank","dur_overlapping_ns","metric_name"
34577,0,6000000,"InputLatency.LatencyInfo.Flow.QueuingDelay.NoJank.BlockingTasksUs.task-subtask"
34578,1,29000000,"InputLatency.LatencyInfo.Flow.QueuingDelay.Jank.BlockingTasksUs.task-subtask"
-34579,0,29000000,"InputLatency.LatencyInfo.Flow.QueuingDelay.NoJank.BlockingTasksUs.task-subtask(foo)"
+34579,0,29000000,"InputLatency.LatencyInfo.Flow.QueuingDelay.NoJank.BlockingTasksUs.task-foo"
diff --git a/test/trace_processor/network/index b/test/trace_processor/network/index
index a21082a..51ce794 100644
--- a/test/trace_processor/network/index
+++ b/test/trace_processor/network/index
@@ -2,3 +2,5 @@
netif_receive_skb.textproto netif_receive_skb.sql netif_receive_skb.out
net_dev_xmit.textproto net_dev_xmit.sql net_dev_xmit.out
netperf_metric.textproto android_netperf netperf_metric.out
+inet_sock_set_state.textproto inet_sock_set_state.sql inet_sock_set_state.out
+tcp_retransmit_skb.textproto tcp_retransmit_skb.sql tcp_retransmit_skb.out
diff --git a/test/trace_processor/network/inet_sock_set_state.out b/test/trace_processor/network/inet_sock_set_state.out
new file mode 100644
index 0000000..fd67a19
--- /dev/null
+++ b/test/trace_processor/network/inet_sock_set_state.out
@@ -0,0 +1,7 @@
+"ts","name","dur","name"
+10000000,"TCP_SYN_SENT(pid=123)",100000000,"TCP stream#1"
+110000000,"TCP_ESTABLISHED(sport=56789,dport=5001)",500000000,"TCP stream#1"
+610000000,"TCP_CLOSE_WAIT",-1,"TCP stream#1"
+710000000,"TCP_SYN_SENT(pid=567)",10000000,"TCP stream#2"
+720000000,"TCP_ESTABLISHED(sport=56790,dport=5002)",300000000,"TCP stream#2"
+1020000000,"TCP_CLOSE_WAIT",-1,"TCP stream#2"
diff --git a/test/trace_processor/network/inet_sock_set_state.sql b/test/trace_processor/network/inet_sock_set_state.sql
new file mode 100644
index 0000000..badba1e
--- /dev/null
+++ b/test/trace_processor/network/inet_sock_set_state.sql
@@ -0,0 +1,12 @@
+SELECT
+ ts,
+ s.name,
+ dur,
+ t.name
+FROM
+ slice AS s
+ LEFT JOIN track AS t
+ ON s.track_id = t.id
+WHERE
+ t.name GLOB "TCP stream#*"
+ORDER BY ts;
diff --git a/test/trace_processor/network/inet_sock_set_state.textproto b/test/trace_processor/network/inet_sock_set_state.textproto
new file mode 100644
index 0000000..00362f9
--- /dev/null
+++ b/test/trace_processor/network/inet_sock_set_state.textproto
@@ -0,0 +1,121 @@
+packet {
+ ftrace_events {
+ cpu: 0
+ event {
+ timestamp: 10000000
+ pid: 123
+ inet_sock_set_state {
+ family: 2
+ protocol: 6
+ daddr: 19216801
+ saddr: 127001
+ dport: 5001
+ sport: 0
+ newstate: 2
+ oldstate: 7
+ skaddr: 77889900
+ }
+ }
+ }
+}
+packet {
+ ftrace_events {
+ cpu: 1
+ event {
+ timestamp: 110000000
+ pid: 234
+ inet_sock_set_state {
+ family: 2
+ protocol: 6
+ daddr: 19216801
+ saddr: 127001
+ dport: 5001
+ sport: 56789
+ newstate: 1
+ oldstate: 2
+ skaddr: 77889900
+ }
+ }
+ }
+}
+packet {
+ ftrace_events {
+ cpu: 0
+ event {
+ timestamp: 610000000
+ pid: 456
+ inet_sock_set_state {
+ family: 2
+ protocol: 6
+ daddr: 19216801
+ saddr: 127001
+ dport: 5001
+ sport: 56789
+ newstate: 8
+ oldstate: 1
+ skaddr: 77889900
+ }
+ }
+ }
+}
+packet {
+ ftrace_events {
+ cpu: 0
+ event {
+ timestamp: 710000000
+ pid:567
+ inet_sock_set_state {
+ family: 10
+ protocol: 6
+ daddr: 0
+ saddr: 0
+ dport: 5002
+ sport: 0
+ newstate: 2
+ oldstate: 7
+ skaddr: 33445566
+ }
+ }
+ }
+}
+packet {
+ ftrace_events {
+ cpu: 1
+ event {
+ timestamp: 720000000
+ pid: 234
+ inet_sock_set_state {
+ family: 10
+ protocol: 6
+ daddr: 0
+ saddr: 0
+ dport: 5002
+ sport: 56790
+ newstate: 1
+ oldstate: 2
+ skaddr: 33445566
+ }
+ }
+ }
+}
+packet {
+ ftrace_events {
+ cpu: 0
+ event {
+ timestamp: 1020000000
+ pid: 456
+ inet_sock_set_state {
+ family: 10
+ protocol: 6
+ daddr: 0
+ saddr: 0
+ dport: 5002
+ sport: 567090
+ newstate: 8
+ oldstate: 1
+ skaddr: 33445566
+ }
+ }
+ }
+}
+
diff --git a/test/trace_processor/network/tcp_retransmit_skb.out b/test/trace_processor/network/tcp_retransmit_skb.out
new file mode 100644
index 0000000..9a3629f
--- /dev/null
+++ b/test/trace_processor/network/tcp_retransmit_skb.out
@@ -0,0 +1,3 @@
+"ts","name","dur"
+110000000,"sport=56789,dport=5001",0
+720000000,"sport=56790,dport=5002",0
diff --git a/test/trace_processor/network/tcp_retransmit_skb.sql b/test/trace_processor/network/tcp_retransmit_skb.sql
new file mode 100644
index 0000000..44dacb3
--- /dev/null
+++ b/test/trace_processor/network/tcp_retransmit_skb.sql
@@ -0,0 +1,11 @@
+SELECT
+ ts,
+ s.name,
+ dur
+FROM
+ slice AS s
+ LEFT JOIN track AS t
+ ON s.track_id = t.id
+WHERE
+ t.name = "TCP Retransmit Skb"
+ORDER BY ts;
diff --git a/test/trace_processor/network/tcp_retransmit_skb.textproto b/test/trace_processor/network/tcp_retransmit_skb.textproto
new file mode 100644
index 0000000..3e96e75
--- /dev/null
+++ b/test/trace_processor/network/tcp_retransmit_skb.textproto
@@ -0,0 +1,34 @@
+packet {
+ ftrace_events {
+ cpu: 1
+ event {
+ timestamp: 110000000
+ pid: 234
+ tcp_retransmit_skb {
+ daddr: 19216801
+ saddr: 127001
+ dport: 5001
+ sport: 56789
+ state: 1
+ skaddr: 77889900
+ }
+ }
+ }
+}
+packet {
+ ftrace_events {
+ cpu: 1
+ event {
+ timestamp: 720000000
+ pid: 234
+ tcp_retransmit_skb {
+ daddr: 0
+ saddr: 0
+ dport: 5002
+ sport: 56790
+ state: 2
+ skaddr: 33445566
+ }
+ }
+ }
+}
diff --git a/test/trace_processor/parsing/index b/test/trace_processor/parsing/index
index ce6f074..42b2535 100644
--- a/test/trace_processor/parsing/index
+++ b/test/trace_processor/parsing/index
@@ -78,6 +78,8 @@
# Check error handling when parsing print events.
bad_print.textproto list_slices.sql bad_print_textproto_list_slices.out
bad_print.systrace list_slices.sql bad_print_systrace_list_slices.out
+instant_atrace.py instant_with_thread.sql instant_atrace_instant_with_thread.out
+instant_async_atrace.py instant_async.sql instant_async_atrace_instant_async.out
# Match legacy Catapult behaviour when we see multiple S events b2b with the same cookie
# name and upid.
diff --git a/test/trace_processor/parsing/instant_async.sql b/test/trace_processor/parsing/instant_async.sql
new file mode 100644
index 0000000..a02b2ba
--- /dev/null
+++ b/test/trace_processor/parsing/instant_async.sql
@@ -0,0 +1,9 @@
+SELECT
+ process.name AS process_name,
+ process_track.name as track_name,
+ instant.name as instant_name,
+ ts
+FROM slice instant
+JOIN process_track ON instant.track_id = process_track.id
+JOIN process USING (upid)
+WHERE dur = 0;
diff --git a/test/trace_processor/parsing/instant_async_atrace.py b/test/trace_processor/parsing/instant_async_atrace.py
new file mode 100644
index 0000000..effdf8b
--- /dev/null
+++ b/test/trace_processor/parsing/instant_async_atrace.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python3
+# Copyright (C) 2022 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.
+
+from os import sys, path
+
+import synth_common
+
+trace = synth_common.create_trace()
+
+trace.add_packet()
+trace.add_process(pid=1, ppid=0, cmdline="p1")
+trace.add_process(pid=2, ppid=1, cmdline="p2")
+
+trace.add_ftrace_packet(cpu=0)
+trace.add_sched(ts=50, prev_pid=1, next_pid=2, prev_comm='t1', next_comm='t2')
+trace.add_print(ts=51, tid=2, buf='N|2|track_p2|ev1\n')
+trace.add_sched(ts=52, prev_pid=2, next_pid=1, prev_comm='t2', next_comm='t1')
+trace.add_print(ts=53, tid=1, buf='N|1|track_p1|ev2\n')
+
+sys.stdout.buffer.write(trace.trace.SerializeToString())
diff --git a/test/trace_processor/parsing/instant_async_atrace_instant_async.out b/test/trace_processor/parsing/instant_async_atrace_instant_async.out
new file mode 100644
index 0000000..d95fe51
--- /dev/null
+++ b/test/trace_processor/parsing/instant_async_atrace_instant_async.out
@@ -0,0 +1,3 @@
+"process_name","track_name","instant_name","ts"
+"p2","track_p2","ev1",51
+"p1","track_p1","ev2",53
diff --git a/test/trace_processor/parsing/instant_atrace.py b/test/trace_processor/parsing/instant_atrace.py
new file mode 100644
index 0000000..c7d6b62
--- /dev/null
+++ b/test/trace_processor/parsing/instant_atrace.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python3
+# Copyright (C) 2022 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.
+
+from os import sys, path
+
+import synth_common
+
+trace = synth_common.create_trace()
+
+trace.add_ftrace_packet(cpu=0)
+trace.add_sched(ts=50, prev_pid=1, next_pid=2, prev_comm='t1', next_comm='t2')
+trace.add_print(ts=51, tid=2, buf='I|2|t2_event\n')
+trace.add_sched(ts=52, prev_pid=2, next_pid=1, prev_comm='t2', next_comm='t1')
+trace.add_print(ts=53, tid=1, buf='I|1|t1_event\n')
+
+sys.stdout.buffer.write(trace.trace.SerializeToString())
diff --git a/test/trace_processor/parsing/instant_atrace_instant_with_thread.out b/test/trace_processor/parsing/instant_atrace_instant_with_thread.out
new file mode 100644
index 0000000..c1b7305
--- /dev/null
+++ b/test/trace_processor/parsing/instant_atrace_instant_with_thread.out
@@ -0,0 +1,3 @@
+"thread_name","track_name","ts"
+"t2","t2_event",51
+"t1","t1_event",53
diff --git a/test/trace_processor/parsing/instant_with_thread.sql b/test/trace_processor/parsing/instant_with_thread.sql
new file mode 100644
index 0000000..2e39608
--- /dev/null
+++ b/test/trace_processor/parsing/instant_with_thread.sql
@@ -0,0 +1,5 @@
+SELECT thread.name as thread_name, instant.name as track_name, instant.ts
+FROM slice instant
+JOIN thread_track ON instant.track_id = thread_track.id
+JOIN thread USING (utid)
+WHERE dur = 0;
diff --git a/test/trace_processor/profiling/heap_graph.textproto b/test/trace_processor/profiling/heap_graph.textproto
index b4c2037..b3decef 100644
--- a/test/trace_processor/profiling/heap_graph.textproto
+++ b/test/trace_processor/profiling/heap_graph.textproto
@@ -124,7 +124,7 @@
id: 3
class_name: "a"
location_id: 1
- object_size: 256
+ object_size: 1024
}
types {
id: 4
diff --git a/test/trace_processor/profiling/heap_graph_baseapk.textproto b/test/trace_processor/profiling/heap_graph_baseapk.textproto
index 18cae91..86d2d90 100644
--- a/test/trace_processor/profiling/heap_graph_baseapk.textproto
+++ b/test/trace_processor/profiling/heap_graph_baseapk.textproto
@@ -70,7 +70,7 @@
objects {
id: 0x04
type_id: 3
- self_size: 256
+ self_size: 1024
reference_field_id: 2
reference_object_id: 0x01
}
diff --git a/test/trace_processor/profiling/heap_graph_legacy.textproto b/test/trace_processor/profiling/heap_graph_legacy.textproto
index 54d19cd..d0bc1c8 100644
--- a/test/trace_processor/profiling/heap_graph_legacy.textproto
+++ b/test/trace_processor/profiling/heap_graph_legacy.textproto
@@ -70,7 +70,7 @@
objects {
id: 0x04
type_id: 3
- self_size: 256
+ self_size: 1024
reference_field_id: 2
reference_object_id: 0x01
}
diff --git a/test/trace_processor/profiling/heap_graph_object.out b/test/trace_processor/profiling/heap_graph_object.out
index e4aa0b1..ac4a6a3 100644
--- a/test/trace_processor/profiling/heap_graph_object.out
+++ b/test/trace_processor/profiling/heap_graph_object.out
@@ -2,6 +2,6 @@
0,"heap_graph_object",2,10,64,0,1,"FactoryProducerDelegateImplActor","[NULL]","ROOT_JAVA_FRAME"
1,"heap_graph_object",2,10,32,"[NULL]",1,"Foo","[NULL]","[NULL]"
2,"heap_graph_object",2,10,128,"[NULL]",0,"Foo","[NULL]","[NULL]"
-3,"heap_graph_object",2,10,256,3,0,"a","DeobfuscatedA","[NULL]"
+3,"heap_graph_object",2,10,1024,3,0,"a","DeobfuscatedA","[NULL]"
4,"heap_graph_object",2,10,256,"[NULL]",1,"a[]","DeobfuscatedA[]","ROOT_JAVA_FRAME"
5,"heap_graph_object",2,10,256,"[NULL]",0,"java.lang.Class<a[]>","java.lang.Class<DeobfuscatedA[]>","[NULL]"
diff --git a/test/trace_processor/profiling/java_heap_histogram.out b/test/trace_processor/profiling/java_heap_histogram.out
index 824b68c..206dc7c 100644
--- a/test/trace_processor/profiling/java_heap_histogram.out
+++ b/test/trace_processor/profiling/java_heap_histogram.out
@@ -8,30 +8,50 @@
samples {
ts: 10
type_count {
- type_name: "DeobfuscatedA[]"
- obj_count: 1
- reachable_obj_count: 1
- }
- type_count {
type_name: "FactoryProducerDelegateImplActor"
obj_count: 1
reachable_obj_count: 1
+ size_kb: 0
+ reachable_size_kb: 0
+ native_size_kb: 0
+ reachable_native_size_kb: 0
}
type_count {
type_name: "Foo"
obj_count: 2
reachable_obj_count: 1
+ size_kb: 0
+ reachable_size_kb: 0
+ native_size_kb: 0
+ reachable_native_size_kb: 0
}
type_count {
type_name: "DeobfuscatedA"
obj_count: 1
reachable_obj_count: 0
+ size_kb: 1
+ reachable_size_kb: 0
+ native_size_kb: 0
+ reachable_native_size_kb: 0
+ }
+ type_count {
+ type_name: "DeobfuscatedA[]"
+ obj_count: 1
+ reachable_obj_count: 1
+ size_kb: 0
+ reachable_size_kb: 0
+ native_size_kb: 0
+ reachable_native_size_kb: 0
}
type_count {
type_name: "java.lang.Class<DeobfuscatedA[]>"
obj_count: 1
reachable_obj_count: 0
- }
- }
+ size_kb: 0
+ reachable_size_kb: 0
+ native_size_kb: 0
+ reachable_native_size_kb: 0
+ }
+ }
}
}
diff --git a/test/trace_processor/profiling/java_heap_stats.out b/test/trace_processor/profiling/java_heap_stats.out
index 4cecef7..6a1237f 100644
--- a/test/trace_processor/profiling/java_heap_stats.out
+++ b/test/trace_processor/profiling/java_heap_stats.out
@@ -7,7 +7,7 @@
}
samples {
ts: 10
- heap_size: 992
+ heap_size: 1760
heap_native_size: 0
reachable_heap_size: 352
reachable_heap_native_size: 0
diff --git a/tools/build_all_configs.py b/tools/build_all_configs.py
index 0c0ec5d..95fe044 100755
--- a/tools/build_all_configs.py
+++ b/tools/build_all_configs.py
@@ -100,11 +100,6 @@
else:
assert False, 'Unsupported system %r' % system
- machine = platform.machine()
- if machine == 'arm64':
- for name, config in configs.items():
- configs[name] = config + ('host_cpu="arm64"',)
-
if args.ccache:
for config_name, gn_args in iteritems(configs):
configs[config_name] = gn_args + ('cc_wrapper="ccache"',)
diff --git a/tools/check_sql_metrics.py b/tools/check_sql_metrics.py
index 65dcff2..b45edf8 100755
--- a/tools/check_sql_metrics.py
+++ b/tools/check_sql_metrics.py
@@ -57,7 +57,8 @@
if 'like' in line.casefold():
sys.stderr.write(
- 'LIKE is banned in trace processor metrics. Prefer GLOB instead.')
+ 'LIKE is banned in trace processor metrics. Prefer GLOB instead.\n')
+ sys.stderr.write('Offending file: %s\n' % path)
errors += 1
return errors
diff --git a/tools/ftrace_proto_gen/event_list b/tools/ftrace_proto_gen/event_list
index 5a0f31b..0535103 100644
--- a/tools/ftrace_proto_gen/event_list
+++ b/tools/ftrace_proto_gen/event_list
@@ -356,3 +356,5 @@
synthetic/rss_stat_throttled
net/netif_receive_skb
net/net_dev_xmit
+sock/inet_sock_set_state
+tcp/tcp_retransmit_skb
diff --git a/tools/install-build-deps b/tools/install-build-deps
index 89c1484..2d9d595 100755
--- a/tools/install-build-deps
+++ b/tools/install-build-deps
@@ -37,7 +37,7 @@
# root (to avoid ending up with buildtools/protobuf/protobuf-1.2.3/... and have
# instead just buildtools/protobuf).
# |target_os| is either 'darwin', 'linux', 'windows' or 'all'
-# |target_arch| is either 'x64', 'aarch64' or 'all'
+# |target_arch| is either 'x64', 'arm64' or 'all'
# in both cases the dep only applies on matching platforms
# |target_arch| can be 'all' when 'target_os' is not 'all' for example in the
# case of MacOS universal binaries.
@@ -57,21 +57,26 @@
# Dependencies required to build code on the host or when targeting desktop OS.
BUILD_DEPS_TOOLCHAIN_HOST = [
# GN. From https://chrome-infra-packages.appspot.com/dl/gn/gn/.
- # git_revision:83dad00afb232d7235dd70dff1ee90292d72a01e .
+ # git_revision:0725d7827575b239594fbc8fd5192873a1d62f44 .
Dependency(
'buildtools/mac/gn',
- 'https://storage.googleapis.com/perfetto/gn-mac-1695-83dad00a',
- '513d3adeb56b745e62af4e3ccb76b76f023c6aaa25d6a2be9a89e44cd10a4c1a',
+ 'https://storage.googleapis.com/perfetto/gn-mac-1968-0725d782',
+ '9ced623a664560bba38bbadb9b91158ca4186358c847e17ab7d982b351373c2e',
'darwin', 'x64'),
Dependency(
+ 'buildtools/mac/gn',
+ 'https://storage.googleapis.com/perfetto/gn-mac-arm64-1968-0725d782',
+ 'd22336b5210b4dad5e36e8c28ce81187f491822cf4d8fd0a257b30d6bee3fd3f',
+ 'darwin', 'arm64'),
+ Dependency(
'buildtools/linux64/gn',
- 'https://storage.googleapis.com/perfetto/gn-linux64-1695-83dad00a',
- '4f589364153f182b05cd845e93407489d6ce8acc03290c897928a7bd22b20cce',
+ 'https://storage.googleapis.com/perfetto/gn-linux64-1968-0725d782',
+ 'f706aaa0676e3e22f5fc9ca482295d7caee8535d1869f99efa2358177b64f5cd',
'linux', 'x64'),
Dependency(
'buildtools/win/gn.exe',
- 'https://storage.googleapis.com/perfetto/gn-win-1695-83dad00a',
- '908c29556539292203d2952ebf55df03697cbc7cf526a3e295f31ba2576e4cac',
+ 'https://storage.googleapis.com/perfetto/gn-win-1968-0725d782',
+ '001f777f023c7a6959c778fb3a6b6cfc63f6baef953410ecdeaec350fb12285b',
'windows', 'x64'),
# clang-format
@@ -80,7 +85,7 @@
'buildtools/mac/clang-format',
'https://storage.googleapis.com/chromium-clang-format/62bde1baa7196ad9df969fc1f06b66360b1a927b',
'6df686a937443cbe6efc013467a7ba5f98d3f187eb7765bb7abc6ce47626cf66',
- 'darwin', 'x64'),
+ 'darwin', 'all'),
# From https://chromium.googlesource.com/chromium/src/buildtools/+/refs/heads/master/linux64/clang-format.sha1
Dependency(
'buildtools/linux64/clang-format',
@@ -103,18 +108,18 @@
# Ninja
Dependency(
'buildtools/mac/ninja',
- 'https://storage.googleapis.com/perfetto/ninja-mac-c15b0698da038b2bd2e8970c14c75fadc06b1add',
- '4224b90734590b0148ad8ee63ee7b295e88e0652e4d1f4271ef2b91d880b0e19',
- 'darwin', 'x64'),
+ 'https://storage.googleapis.com/perfetto/ninja-mac-x64_and_arm64-182',
+ '36e8b7aaa06911e1334feb664dd731a1cd69a15eb916a231a3d10ff65fca2c73',
+ 'darwin', 'all'),
Dependency(
'buildtools/linux64/ninja',
- 'https://storage.googleapis.com/perfetto/ninja-linux64-c866952bda50c29a669222477309287119bbb7e8',
+ 'https://storage.googleapis.com/perfetto/ninja-linux64-182',
'54ac6a01362190aaabf4cf276f9c8982cdf11b225438940fdde3339be0f2ecdc',
'linux', 'x64'),
Dependency(
'buildtools/win/ninja.exe',
- 'https://storage.googleapis.com/perfetto/ninja-win-4a5f05c24afef05ef03329a1bbfedee0678b524a',
- '6f8af488be74ed8787d04e107080d05330587a4198ba047bd5b7f5b0c3150d61',
+ 'https://storage.googleapis.com/perfetto/ninja-win-182',
+ '09ced0fcd1a4dec7d1b798a2cf9ce5d20e5d2fbc2337343827f192ce47d0f491',
'windows', 'x64'),
# Keep the revision in sync with Chrome's PACKAGE_VERSION in
@@ -244,7 +249,7 @@
'buildtools/bloaty.zip',
'https://storage.googleapis.com/perfetto/bloaty-1.1-b3b829de35babc2fe831b9488ad2e50bca939412-mac.zip',
'2d301bd72a20e3f42888c9274ceb4dca76c103608053572322412c2c65ab8cb8',
- 'darwin', 'x64'),
+ 'darwin', 'all'),
]
# Dependencies required to build Android code.
@@ -257,7 +262,7 @@
'buildtools/ndk.zip',
'https://dl.google.com/android/repository/android-ndk-r21e-darwin-x86_64.zip',
'437278103a3db12632c05b1be5c41bbb8522791a67e415cc54411a65366f499d',
- 'darwin', 'x64'),
+ 'darwin', 'all'),
Dependency(
'buildtools/ndk.zip',
'https://dl.google.com/android/repository/android-ndk-r21e-linux-x86_64.zip',
@@ -279,7 +284,7 @@
'buildtools/android_sdk/platform-tools.zip',
'https://dl.google.com/android/repository/platform-tools_r26.0.0-darwin.zip',
'98d392cbd21ca20d643c7e1605760cc49075611e317c534096b5564053f4ac8e',
- 'darwin', 'x64'),
+ 'darwin', 'all'),
Dependency(
'buildtools/android_sdk/platform-tools.zip',
'https://dl.google.com/android/repository/platform-tools_r26.0.0-linux.zip',
@@ -303,7 +308,7 @@
'buildtools/mac/nodejs.tgz',
'https://storage.googleapis.com/chromium-nodejs/14.15.4/17ba7216e09de1bffb9dc80b7ec617a1cee40330',
'b81a466347d2ae34b1370b6681ba173e9fb082338170a41624b37be7a2052b7e',
- 'darwin', 'x64'),
+ 'darwin', 'all'),
Dependency(
'buildtools/linux64/nodejs.tgz',
'https://storage.googleapis.com/chromium-nodejs/14.15.4/b2e40ddbac04d05baafbb007f203c6663c9d4ca9',
@@ -313,7 +318,7 @@
'buildtools/mac/emsdk.tgz',
'https://storage.googleapis.com/perfetto/emscripten-2.0.12-mac.tgz',
'aa125f8c8ff8a386d43e18c8ea0c98c875cc19160a899403e8967a5478f96f31',
- 'darwin', 'x64'),
+ 'darwin', 'all'),
Dependency(
'buildtools/linux64/emsdk.tgz',
'https://storage.googleapis.com/perfetto/emscripten-2.0.12-linux.tgz',
@@ -361,8 +366,8 @@
def GetArch():
arch = machine()
- if arch == 'aarch64':
- return 'aarch64'
+ if arch == 'arm64':
+ return 'arm64'
else:
# Assume everything else is x64 matching previous behaviour.
return 'x64'
diff --git a/tools/trace_to_text/trace_to_text.cc b/tools/trace_to_text/trace_to_text.cc
index 0bca321..7507c75 100644
--- a/tools/trace_to_text/trace_to_text.cc
+++ b/tools/trace_to_text/trace_to_text.cc
@@ -165,10 +165,9 @@
OstreamOutputStream zero_copy_output(output);
OstreamOutputStream* zero_copy_output_ptr = &zero_copy_output;
- constexpr uint32_t kCompressedPacketFieldDescriptor = 50;
const Reflection* reflect = msg->GetReflection();
- const FieldDescriptor* compressed_desc =
- trace_descriptor->FindFieldByNumber(kCompressedPacketFieldDescriptor);
+ const FieldDescriptor* compressed_desc = trace_descriptor->FindFieldByNumber(
+ protos::pbzero::TracePacket::kCompressedPacketsFieldNumber);
std::unique_ptr<Message> compressed_packets_msg(prototype->New());
std::string compressed_packets;
@@ -177,7 +176,7 @@
printer.SetInitialIndentLevel(1);
static constexpr size_t kMaxMsgSize = protozero::ProtoRingBuffer::kMaxMsgSize;
- std::unique_ptr<char> data(new char[kMaxMsgSize]);
+ std::unique_ptr<char[]> data(new char[kMaxMsgSize]);
protozero::ProtoRingBuffer ring_buffer;
uint32_t packet = 0;
@@ -200,7 +199,7 @@
break;
bytes_processed += token.len;
- if (token.field_id != 1) {
+ if (token.field_id != protos::pbzero::Trace::kPacketFieldNumber) {
PERFETTO_ELOG("Skipping invalid field");
continue;
}
diff --git a/ui/release/channels.json b/ui/release/channels.json
index 90350bb..4f32d9c 100644
--- a/ui/release/channels.json
+++ b/ui/release/channels.json
@@ -2,11 +2,11 @@
"channels": [
{
"name": "stable",
- "rev": "5e6f200f1188f87c3eab9c0e9cdc57e7c947d982"
+ "rev": "c4eb91619a0927616f43959a91cbc5b51bd79439"
},
{
"name": "canary",
- "rev": "3ff938b95864694fcb3df4cd0741889dbe36edd8"
+ "rev": "88e543ae67d219635038cd007ab3b5a45a0e2614"
},
{
"name": "autopush",
diff --git a/ui/src/assets/common.scss b/ui/src/assets/common.scss
index f98aa30..c98a6a1 100644
--- a/ui/src/assets/common.scss
+++ b/ui/src/assets/common.scss
@@ -235,6 +235,12 @@
display: inline-block;
position: relative;
}
+ .indent {
+ display: inline-block;
+ // 24px is the width of expand_more/expand_less icon to pad out cells
+ // without the button
+ width: 24px;
+ }
.popup-menu {
position: absolute;
background-color: white;
@@ -696,6 +702,26 @@
user-select: none;
}
+.pivot-table-redux {
+ user-select: text;
+
+ button.mode-button {
+ border-radius: 10px;
+ padding: 7px;
+ margin: 5px;
+ background-color: #c7d0db;
+ }
+
+ &.edit {
+ padding: 10px;
+ display: flex;
+ }
+
+ &.query-error {
+ color: red;
+ }
+}
+
.pivot-table-editor-container {
font: inherit;
width: 670px;
diff --git a/ui/src/assets/index.html b/ui/src/assets/index.html
index 2e9329f..817945f 100644
--- a/ui/src/assets/index.html
+++ b/ui/src/assets/index.html
@@ -13,9 +13,10 @@
error reporting.
-->
<style>
- #app_load_failure {opacity:0;transition:opacity 1s ease;position:absolute;background:#080082;top:0;left:0;width:100%;height:100%;bottom:0;right:0;margin:0;opacity:0;user-select:text}
- #app_load_failure > pre {color:#fff;position:absolute;margin:auto;white-space:pre-wrap;top:50%;transform:translate(0,-50%);max-width:90vw;width:880px;left:0;right:0;font-size:16px;line-height:30px;font-weight:700}
+ #app_load_failure {opacity:0;transition:opacity 1s ease;position:absolute;overflow:auto;background:#080082;top:0;left:0;width:100%;height:100%;bottom:0;right:0;margin:0;opacity:0;user-select:text}
+ #app_load_failure > pre {color:#fff;position:absolute;margin:auto;white-space:pre-wrap;top:10vh;max-width:90vw;width:880px;left:0;right:0;font-size:16px;line-height:30px;font-weight:700}
#app_load_failure > pre span {background:#fff;color:#080082;padding:2px}
+ #app_load_failure_dbg { overflow-wrap: break-word; font-size: 12px; line-height: 1; font-weight: initial;}
#app_load_failure a {color:#fff}
#app_load { position: absolute; top: 0; left: 0; right:0; bottom: 0; background-color: #2c3e50;}
#app_load_spinner { margin: 30vh auto; width: 150px; height: 150px; border: 3px solid rgba(255,255,255,.3); border-radius: 50%; border-top-color: #fff; animation: app_load_spin 1s ease-in-out infinite; }
diff --git a/ui/src/common/actions.ts b/ui/src/common/actions.ts
index 8ab5034..900c317 100644
--- a/ui/src/common/actions.ts
+++ b/ui/src/common/actions.ts
@@ -51,6 +51,7 @@
LogsPagination,
NewEngineMode,
OmniboxState,
+ PivotTableReduxState,
RecordingTarget,
SCROLLING_TRACK_GROUP,
State,
@@ -183,14 +184,14 @@
state: StateDraft, trackState: TrackState, uiTrackId: string) {
const config = trackState.config as {trackId: number};
if (config.trackId !== undefined) {
- state.uiTrackIdByTraceTrackId.set(config.trackId, uiTrackId);
+ state.uiTrackIdByTraceTrackId[config.trackId] = uiTrackId;
return;
}
const multiple = trackState.config as {trackIds: number[]};
if (multiple.trackIds !== undefined) {
for (const trackId of multiple.trackIds) {
- state.uiTrackIdByTraceTrackId.set(trackId, uiTrackId);
+ state.uiTrackIdByTraceTrackId[trackId] = uiTrackId;
}
}
},
@@ -855,10 +856,11 @@
state.metrics.requestedMetric = undefined;
},
- setAvailableMetrics(state: StateDraft, args: {metrics: string[]}): void {
- state.metrics.availableMetrics = args.metrics;
- if (args.metrics.length > 0) state.metrics.selectedIndex = 0;
- },
+ setAvailableMetrics(state: StateDraft, args: {availableMetrics: string[]}):
+ void {
+ state.metrics.availableMetrics = args.availableMetrics;
+ if (args.availableMetrics.length > 0) state.metrics.selectedIndex = 0;
+ },
setMetricSelectedIndex(state: StateDraft, args: {index: number}): void {
if (!state.metrics.availableMetrics ||
@@ -915,6 +917,10 @@
}
},
+ togglePivotTableRedux(state: StateDraft, args: {enabled: boolean}) {
+ state.pivotTableRedux.enabled = args.enabled;
+ },
+
addNewPivotTable(state: StateDraft, args: {
name: string,
pivotTableId: string,
@@ -940,7 +946,9 @@
resetPivotTableRequest(state: StateDraft, args: {pivotTableId: string}):
void {
- state.pivotTable[args.pivotTableId].requestedAction = undefined;
+ if (state.pivotTable[args.pivotTableId] !== undefined) {
+ state.pivotTable[args.pivotTableId].requestedAction = undefined;
+ }
},
setPivotTableRequest(
@@ -987,6 +995,11 @@
const pivotTable = state.pivotTable[args.pivotTableId];
pivotTable.traceTime = args.traceTime;
pivotTable.selectedTrackIds = args.selectedTrackIds;
+ },
+
+ setPivotStateReduxState(
+ state: StateDraft, args: {pivotTableState: PivotTableReduxState}) {
+ state.pivotTableRedux = args.pivotTableState;
}
};
diff --git a/ui/src/common/arg_types.ts b/ui/src/common/arg_types.ts
index 6ca6a04..864dc4b 100644
--- a/ui/src/common/arg_types.ts
+++ b/ui/src/common/arg_types.ts
@@ -22,9 +22,9 @@
}
export function isArgTreeArray(item: ArgsTree): item is ArgsTreeArray {
- return typeof item === 'object' && item.length !== undefined;
+ return typeof item === 'object' && Array.isArray(item);
}
export function isArgTreeMap(item: ArgsTree): item is ArgsTreeMap {
- return typeof item === 'object' && item.length === undefined;
+ return typeof item === 'object' && !Array.isArray(item);
}
diff --git a/ui/src/common/empty_state.ts b/ui/src/common/empty_state.ts
index a3b1fdc..2b9396e 100644
--- a/ui/src/common/empty_state.ts
+++ b/ui/src/common/empty_state.ts
@@ -39,7 +39,7 @@
engines: {},
traceTime: {...defaultTraceTime},
tracks: {},
- uiTrackIdByTraceTrackId: new Map<number, string>(),
+ uiTrackIdByTraceTrackId: {},
aggregatePreferences: {},
trackGroups: {},
visibleTracks: [],
@@ -102,5 +102,7 @@
fetchChromeCategories: false,
chromeCategories: undefined,
+ pivotTableRedux:
+ {enabled: false, query: null, queryId: 0, queryResult: null},
};
}
\ No newline at end of file
diff --git a/ui/src/common/engine.ts b/ui/src/common/engine.ts
index aea57a9..01c1207 100644
--- a/ui/src/common/engine.ts
+++ b/ui/src/common/engine.ts
@@ -293,7 +293,6 @@
rpc.request = TPM.TPM_QUERY_STREAMING;
rpc.queryArgs = new QueryArgs();
rpc.queryArgs.sqlQuery = sqlQuery;
- rpc.queryArgs.timeQueuedNs = Math.floor(performance.now() * 1e6);
const result = createQueryResult({
query: sqlQuery,
});
diff --git a/ui/src/common/http_rpc_engine.ts b/ui/src/common/http_rpc_engine.ts
index ba9c72e..436dd42 100644
--- a/ui/src/common/http_rpc_engine.ts
+++ b/ui/src/common/http_rpc_engine.ts
@@ -13,12 +13,14 @@
// limitations under the License.
import {fetchWithTimeout} from '../base/http_utils';
-import {assertTrue} from '../base/logging';
+import {assertExists} from '../base/logging';
import {StatusResult} from '../common/protos';
import {Engine, LoadingTracker} from './engine';
export const RPC_URL = 'http://127.0.0.1:9001/';
+export const WS_URL = 'ws://127.0.0.1:9001/websocket';
+
const RPC_CONNECT_TIMEOUT_MS = 2000;
export interface HttpRpcState {
@@ -29,9 +31,10 @@
export class HttpRpcEngine extends Engine {
readonly id: string;
- private requestQueue = new Array<Uint8Array>();
- private requestPending = false;
errorHandler: (err: string) => void = () => {};
+ private requestQueue = new Array<Uint8Array>();
+ private websocket?: WebSocket;
+ private connected = false;
constructor(id: string, loadingTracker?: LoadingTracker) {
super(loadingTracker);
@@ -39,47 +42,35 @@
}
rpcSendRequestBytes(data: Uint8Array): void {
- if (!this.requestPending && this.requestQueue.length === 0) {
- this.beginFetch(data);
+ if (this.websocket === undefined) {
+ this.websocket = new WebSocket(WS_URL);
+ this.websocket.onopen = () => this.onWebsocketConnected();
+ this.websocket.onmessage = (e) => this.onWebsocketMessage(e);
+ this.websocket.onclose = (e) =>
+ this.errorHandler(`Websocket closed (${e.code}: ${e.reason})`);
+ this.websocket.onerror = (e) =>
+ this.errorHandler(`WebSocket error: ${e}`);
+ }
+
+ if (this.connected) {
+ this.websocket.send(data);
} else {
- this.requestQueue.push(data);
+ this.requestQueue.push(data); // onWebsocketConnected() will flush this.
}
}
- private beginFetch(data: Uint8Array) {
- assertTrue(!this.requestPending);
- this.requestPending = true;
- // Deliberately not using fetchWithTimeout() here. These queries can be
- // arbitrarily long.
- // Deliberately not setting cache: no-cache. Doing so invalidates also the
- // CORS pre-flight responses, causing one OPTIONS request for each POST.
- // no-cache is also useless because trace-processor's replies are already
- // marked as no-cache and browsers generally already assume that POST
- // requests are not idempotent.
- fetch(RPC_URL + 'rpc', {
- method: 'post',
- headers: {'Content-Type': 'application/x-protobuf'},
- body: data,
- })
- .then(resp => this.endFetch(resp))
- .catch(err => this.errorHandler(err));
+ private onWebsocketConnected() {
+ for (;;) {
+ const queuedMsg = this.requestQueue.shift();
+ if (queuedMsg === undefined) break;
+ assertExists(this.websocket).send(queuedMsg);
+ }
+ this.connected = true;
}
- private endFetch(resp: Response) {
- assertTrue(this.requestPending);
- if (resp.status !== 200) {
- throw new Error(`HTTP ${resp.status} - ${resp.statusText}`);
- }
- resp.arrayBuffer().then(arrBuf => {
- // Note: another request can sneak in via enqueueRequest() between the
- // arrayBuffer() call and this continuation. At this point
- // this.pendingRequest might be set again.
- // If not (the most common case) submit the next queued request, if any.
- this.requestPending = false;
- if (this.requestQueue.length > 0) {
- this.beginFetch(this.requestQueue.shift()!);
- }
- super.onRpcResponseBytes(new Uint8Array(arrBuf));
+ private onWebsocketMessage(e: MessageEvent) {
+ assertExists(e.data as Blob).arrayBuffer().then(buf => {
+ super.onRpcResponseBytes(new Uint8Array(buf));
});
}
diff --git a/ui/src/common/queries.ts b/ui/src/common/queries.ts
index 1e7e671..a91b126 100644
--- a/ui/src/common/queries.ts
+++ b/ui/src/common/queries.ts
@@ -25,6 +25,8 @@
durationMs: number;
columns: string[];
rows: Row[];
+ statementCount: number;
+ statementWithOutputCount: number;
}
export async function runQuery(
@@ -68,6 +70,8 @@
totalRowCount: queryRes.numRows(),
columns,
rows,
+ statementCount: queryRes.statementCount(),
+ statementWithOutputCount: queryRes.statementWithOutputCount(),
};
return result;
}
\ No newline at end of file
diff --git a/ui/src/common/query_result.ts b/ui/src/common/query_result.ts
index dd80b4d..73f0a98 100644
--- a/ui/src/common/query_result.ts
+++ b/ui/src/common/query_result.ts
@@ -187,6 +187,14 @@
// This should be called only after having awaited for at least one batch.
columns(): string[];
+ // Returns the number of SQL statements in the query
+ // (e.g. 2 'if SELECT 1; SELECT 2;')
+ statementCount(): number;
+
+ // Returns the number of SQL statement that produced output rows. This number
+ // is <= statementCount().
+ statementWithOutputCount(): number;
+
// TODO(primiano): next CLs will introduce a waitMoreRows() to allow tracks
// to await until some more data (but not necessarily all) is available. For
// now everything uses waitAllRows().
@@ -218,6 +226,8 @@
private _numRows = 0;
private _isComplete = false;
private _errorInfo: QueryErrorInfo;
+ private _statementCount = 0;
+ private _statementWithOutputCount = 0;
constructor(errorInfo: QueryErrorInfo) {
this._errorInfo = errorInfo;
@@ -250,6 +260,12 @@
columns(): string[] {
return this.columnNames;
}
+ statementCount(): number {
+ return this._statementCount;
+ }
+ statementWithOutputCount(): number {
+ return this._statementWithOutputCount;
+ }
iter<T extends Row>(spec: T): RowIterator<T> {
const impl = new RowIteratorImplWithRowData(spec, this);
@@ -337,6 +353,15 @@
assertTrue(parsedBatch.numCells === 0);
}
break;
+
+ case 4:
+ this._statementCount = reader.uint32();
+ break;
+
+ case 5:
+ this._statementWithOutputCount = reader.uint32();
+ break;
+
default:
console.warn(`Unexpected QueryResult field ${tag >>> 3}`);
reader.skipType(tag & 7);
@@ -755,6 +780,12 @@
error() {
return this.impl.error();
}
+ statementCount() {
+ return this.impl.statementCount();
+ }
+ statementWithOutputCount() {
+ return this.impl.statementWithOutputCount();
+ }
// WritableQueryResult implementation.
appendResultBatch(resBytes: Uint8Array) {
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index 865982f..db5e9c6 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+import {assertTrue} from '../base/logging';
+import {PivotTree} from '../controller/pivot_table_redux_controller';
import {RecordConfig} from '../controller/record_config_types';
import {
@@ -73,7 +75,11 @@
// 12: Add a field to cache mapping from UI track ID to trace track ID in order
// to speed up flow arrows rendering.
// 13: FlamegraphState changed to support area selection.
-export const STATE_VERSION = 13;
+// 14: Changed the type of uiTrackIdByTraceTrackId from `Map` to an object with
+// typed key/value because a `Map` does not preserve type during
+// serialisation+deserialisation.
+// 15: Added state for Pivot Table V2
+export const STATE_VERSION = 15;
export const SCROLLING_TRACK_GROUP = 'ScrollingTracks';
@@ -116,7 +122,7 @@
url?: string;
fileName?: string;
- // |uuid| is set only when loading from the cache via ?trace_id=123. When set,
+ // |uuid| is set only when loading via ?local_cache_key=1234. When set,
// this matches global.state.traceUuid, with the exception of the following
// time window: When a trace T1 is loaded and the user loads another trace T2,
// this |uuid| will be == T2, but the globals.state.traceUuid will be
@@ -325,6 +331,41 @@
selectedTrackIds?: number[];
}
+// Auxiliary metadata needed to parse the query result, as well as to render it
+// correctly. Generated together with the text of query and passed without the
+// change to the query response.
+export interface PivotTableReduxQueryMetadata {
+ pivotColumns: string[];
+ aggregationColumns: string[];
+}
+
+// Everything that's necessary to run the query for pivot table
+export interface PivotTableReduxQuery {
+ text: string;
+ metadata: PivotTableReduxQueryMetadata;
+}
+
+// Pivot table query result
+export interface PivotTableReduxResult {
+ // Hierarchical pivot structure on top of rows
+ tree: PivotTree;
+ // Copy of the query metadata from the request, bundled up with the query
+ // result to ensure the correct rendering.
+ metadata: PivotTableReduxQueryMetadata;
+}
+
+export interface PivotTableReduxState {
+ // Whether the panel should be visible
+ enabled: boolean;
+ // Increasing identifier of the query request, used to avoid performing the
+ // same query more than once.
+ queryId: number;
+ // Query request
+ query: PivotTableReduxQuery|null;
+ // Query response
+ queryResult: PivotTableReduxResult|null;
+}
+
export interface LoadedConfigNone {
type: 'NONE';
}
@@ -365,7 +406,7 @@
traceUuid?: string;
trackGroups: ObjectById<TrackGroupState>;
tracks: ObjectById<TrackState>;
- uiTrackIdByTraceTrackId: Map<number, string>;
+ uiTrackIdByTraceTrackId: {[key: number]: string;};
areas: ObjectById<AreaById>;
aggregatePreferences: ObjectById<AggregationState>;
visibleTracks: string[];
@@ -384,6 +425,7 @@
traceConversionInProgress: boolean;
pivotTableConfig: PivotTableConfig;
pivotTable: ObjectById<PivotTableState>;
+ pivotTableRedux: PivotTableReduxState;
/**
* This state is updated on the frontend at 60Hz and eventually syncronised to
@@ -436,7 +478,12 @@
'STOP_WHEN_FULL' | 'RING_BUFFER' | 'LONG_TRACE';
// 'Q','P','O' for Android, 'L' for Linux, 'C' for Chrome.
-export declare type TargetOs = 'Q' | 'P' | 'O' | 'C' | 'L' | 'CrOS';
+export declare type TargetOs = 'S' | 'R' | 'Q' | 'P' | 'O' | 'C' | 'L' | 'CrOS';
+
+export function isTargetOsAtLeast(target: RecordingTarget, osVersion: string) {
+ assertTrue(osVersion.length === 1);
+ return target.os >= osVersion;
+}
export function isAndroidP(target: RecordingTarget) {
return target.os === 'P';
diff --git a/ui/src/controller/metrics_controller.ts b/ui/src/controller/metrics_controller.ts
index 92afb8a..d0e0916 100644
--- a/ui/src/controller/metrics_controller.ts
+++ b/ui/src/controller/metrics_controller.ts
@@ -14,7 +14,7 @@
import {Actions} from '../common/actions';
import {Engine} from '../common/engine';
-import {QueryError, STR} from '../common/query_result';
+import {QueryError} from '../common/query_result';
import {publishMetricResult} from '../frontend/publish';
import {Controller} from './controller';
@@ -27,24 +27,7 @@
constructor(args: {engine: Engine}) {
super('main');
this.engine = args.engine;
- this.setup().finally(() => {
- this.run();
- });
- }
-
- private async getMetricNames() {
- const metrics = [];
- const result = await this.engine.query('select name from trace_metrics');
- const it = result.iter({name: STR});
- for (; it.valid(); it.next()) {
- metrics.push(it.name);
- }
- return metrics;
- }
-
- private async setup() {
- const metrics = await this.getMetricNames();
- globals.dispatch(Actions.setAvailableMetrics({metrics}));
+ this.run();
}
private async computeMetric(name: string) {
diff --git a/ui/src/controller/pivot_table_redux_controller.ts b/ui/src/controller/pivot_table_redux_controller.ts
new file mode 100644
index 0000000..59a4c1b
--- /dev/null
+++ b/ui/src/controller/pivot_table_redux_controller.ts
@@ -0,0 +1,170 @@
+import {Actions} from '../common/actions';
+import {Engine} from '../common/engine';
+import {featureFlags} from '../common/feature_flags';
+import {ColumnType} from '../common/query_result';
+import {aggregationIndex} from '../frontend/pivot_table_redux_query_generator';
+
+import {Controller} from './controller';
+import {globals} from './globals';
+
+export const PIVOT_TABLE_REDUX_FLAG = featureFlags.register({
+ id: 'pivotTableRedux',
+ name: 'Pivot tables V2',
+ description: 'Second version of pivot table',
+ defaultValue: false,
+});
+
+// Node in the hierarchical pivot tree. Only leaf nodes contain data from the
+// query result.
+export interface PivotTree {
+ // Whether the node should be collapsed in the UI, false by default and can
+ // be toggled with the button.
+ isCollapsed: boolean;
+
+ // Non-empty only in internal nodes.
+ children: Map<ColumnType, PivotTree>;
+ aggregates: ColumnType[];
+
+ // Non-empty only in leaf nodes.
+ rows: ColumnType[][];
+}
+
+// Auxiliary class to build the tree from query response.
+class TreeBuilder {
+ private readonly root: PivotTree;
+ lastRow: ColumnType[];
+ pivotColumns: number;
+ aggregateColumns: number;
+
+ constructor(
+ pivotColumns: number, aggregateColumns: number, firstRow: ColumnType[]) {
+ this.pivotColumns = pivotColumns;
+ this.aggregateColumns = aggregateColumns;
+ this.root = this.createNode(0, firstRow);
+ let tree = this.root;
+ for (let i = 0; i + 1 < this.pivotColumns; i++) {
+ const value = firstRow[i];
+ tree = TreeBuilder.insertChild(
+ tree, value, this.createNode(i + 1, firstRow));
+ }
+ this.lastRow = firstRow;
+ }
+
+ // Add incoming row to the tree being built.
+ ingestRow(row: ColumnType[]) {
+ let tree = this.root;
+ for (let i = 0; i + 1 < this.pivotColumns; i++) {
+ const nextTree = tree.children.get(row[i]);
+ if (nextTree === undefined) {
+ // Insert the new node into the tree, and make variable `tree` point
+ // to the newly created node.
+ tree =
+ TreeBuilder.insertChild(tree, row[i], this.createNode(i + 1, row));
+ } else {
+ tree = nextTree;
+ }
+ }
+ tree.rows.push(row);
+ this.lastRow = row;
+ }
+
+ build(): PivotTree {
+ return this.root;
+ }
+
+ // Helper method that inserts child node into the tree and returns it, used
+ // for more concise modification of local variable pointing to the current
+ // node being built.
+ static insertChild(tree: PivotTree, key: ColumnType, child: PivotTree):
+ PivotTree {
+ tree.children.set(key, child);
+ return child;
+ }
+
+ // Initialize PivotTree from a row.
+ createNode(depth: number, row: ColumnType[]): PivotTree {
+ const aggregates = [];
+
+ for (let j = 0; j < this.aggregateColumns; j++) {
+ aggregates.push(row[aggregationIndex(this.pivotColumns, j, depth)]);
+ }
+
+ return {
+ isCollapsed: false,
+ children: new Map(),
+ aggregates,
+ rows: [],
+ };
+ }
+}
+
+// Controller responsible for showing the panel with pivot table, as well as
+// executing its queries and post-processing query results.
+export class PivotTableReduxController extends Controller<{}> {
+ engine: Engine;
+ lastStartedQueryId: number;
+
+ constructor(args: {engine: Engine}) {
+ super({});
+ this.engine = args.engine;
+ this.lastStartedQueryId = 0;
+ }
+
+ run() {
+ if (!PIVOT_TABLE_REDUX_FLAG.get()) {
+ return;
+ }
+
+ const selection = globals.state.currentSelection;
+ const hasSelection = selection !== null;
+
+ const pivotTableState = globals.state.pivotTableRedux;
+ if (pivotTableState.queryId > this.lastStartedQueryId &&
+ pivotTableState.query !== null) {
+ this.lastStartedQueryId = pivotTableState.queryId;
+ const query = pivotTableState.query;
+
+ this.engine.query(query.text).then(async (result) => {
+ try {
+ await result.waitAllRows();
+ } catch {
+ // waitAllRows() frequently throws an exception, which is ignored in
+ // its other calls, so it's ignored here as well.
+ }
+
+ const columns = result.columns();
+
+ const it = result.iter({});
+ function nextRow(): ColumnType[] {
+ const row: ColumnType[] = [];
+ for (const column of columns) {
+ row.push(it.get(column));
+ }
+ it.next();
+ return row;
+ }
+ const treeBuilder = new TreeBuilder(
+ query.metadata.pivotColumns.length,
+ query.metadata.aggregationColumns.length,
+ nextRow());
+ while (it.valid()) {
+ treeBuilder.ingestRow(nextRow());
+ }
+
+ globals.dispatch(Actions.setPivotStateReduxState({
+ pivotTableState: {
+ queryId: this.lastStartedQueryId,
+ query: null,
+ queryResult: {
+ tree: treeBuilder.build(),
+ metadata: query.metadata,
+ },
+ enabled: true
+ }
+ }));
+ });
+ }
+
+ globals.dispatch(Actions.togglePivotTableRedux({enabled: hasSelection}));
+ }
+}
\ No newline at end of file
diff --git a/ui/src/controller/record_controller.ts b/ui/src/controller/record_controller.ts
index 0868940..a476450 100644
--- a/ui/src/controller/record_controller.ts
+++ b/ui/src/controller/record_controller.ts
@@ -44,6 +44,7 @@
isChromeTarget,
isCrOSTarget,
isLinuxTarget,
+ isTargetOsAtLeast,
RecordingTarget
} from '../common/state';
import {publishBufferUsage, publishTrackData} from '../frontend/publish';
@@ -127,7 +128,9 @@
procThreadAssociationPolling = true;
procThreadAssociationFtrace = true;
uiCfg.ftrace = true;
- uiCfg.symbolizeKsyms = true;
+ if (isTargetOsAtLeast(target, 'S')) {
+ uiCfg.symbolizeKsyms = true;
+ }
ftraceEvents.add('sched/sched_switch');
ftraceEvents.add('power/suspend_resume');
ftraceEvents.add('sched/sched_wakeup');
diff --git a/ui/src/controller/record_controller_jsdomtest.ts b/ui/src/controller/record_controller_jsdomtest.ts
index d263cf0..e1a3b80 100644
--- a/ui/src/controller/record_controller_jsdomtest.ts
+++ b/ui/src/controller/record_controller_jsdomtest.ts
@@ -39,7 +39,20 @@
expect(ftraceEvents.includes('raw_syscalls/sys_exit')).toBe(true);
});
-test('cpu scheduling includes kSyms', () => {
+test('cpu scheduling includes kSyms if OS >= S', () => {
+ const config = createEmptyRecordConfig();
+ config.cpuSched = true;
+ const result =
+ TraceConfig.decode(genConfigProto(config, {os: 'S', name: 'Android S'}));
+ const sources = assertExists(result.dataSources);
+ const srcConfig = assertExists(sources[1].config);
+ const ftraceConfig = assertExists(srcConfig.ftraceConfig);
+ const ftraceEvents = assertExists(ftraceConfig.ftraceEvents);
+ expect(ftraceConfig.symbolizeKsyms).toBe(true);
+ expect(ftraceEvents.includes('sched/sched_blocked_reason')).toBe(true);
+});
+
+test('cpu scheduling does not include kSyms if OS <= S', () => {
const config = createEmptyRecordConfig();
config.cpuSched = true;
const result =
@@ -48,8 +61,8 @@
const srcConfig = assertExists(sources[1].config);
const ftraceConfig = assertExists(srcConfig.ftraceConfig);
const ftraceEvents = assertExists(ftraceConfig.ftraceEvents);
- expect(ftraceConfig.symbolizeKsyms).toBe(true);
- expect(ftraceEvents.includes('sched/sched_blocked_reason')).toBe(true);
+ expect(ftraceConfig.symbolizeKsyms).toBe(false);
+ expect(ftraceEvents.includes('sched/sched_blocked_reason')).toBe(false);
});
test('kSyms can be enabled individually', () => {
diff --git a/ui/src/controller/search_controller.ts b/ui/src/controller/search_controller.ts
index 3ec461f..8c0946a 100644
--- a/ui/src/controller/search_controller.ts
+++ b/ui/src/controller/search_controller.ts
@@ -257,6 +257,7 @@
0 as utid
from slice
where slice.name like ${searchLiteral}
+ ${isNaN(Number(search)) ? '' : `or sliceId = ${search}`}
union
select
slice_id as sliceId,
diff --git a/ui/src/controller/selection_controller.ts b/ui/src/controller/selection_controller.ts
index 3979b2b..6b621cf 100644
--- a/ui/src/controller/selection_controller.ts
+++ b/ui/src/controller/selection_controller.ts
@@ -378,7 +378,7 @@
const delta = value - previousValue;
const duration = endTs - ts;
const startTime = fromNs(ts) - globals.state.traceTime.startSec;
- const uiTrackId = globals.state.uiTrackIdByTraceTrackId.get(trackId);
+ const uiTrackId = globals.state.uiTrackIdByTraceTrackId[trackId];
const name = uiTrackId ? globals.state.tracks[uiTrackId].name : undefined;
return {startTime, value, delta, duration, name};
}
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index 1686210..d6b4bc0 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -12,20 +12,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {assertExists, assertTrue} from '../base/logging';
+import { assertExists, assertTrue } from '../base/logging';
import {
Actions,
DeferredAction,
} from '../common/actions';
-import {cacheTrace} from '../common/cache_manager';
-import {TRACE_MARGIN_TIME_S} from '../common/constants';
-import {Engine} from '../common/engine';
-import {featureFlags, Flag, PERF_SAMPLE_FLAG} from '../common/feature_flags';
-import {HttpRpcEngine} from '../common/http_rpc_engine';
-import {NUM, NUM_NULL, QueryError, STR, STR_NULL} from '../common/query_result';
-import {EngineMode} from '../common/state';
-import {TimeSpan, toNs, toNsCeil, toNsFloor} from '../common/time';
-import {resetEngineWorker, WasmEngineProxy} from '../common/wasm_engine_proxy';
+import { cacheTrace } from '../common/cache_manager';
+import { TRACE_MARGIN_TIME_S } from '../common/constants';
+import { Engine } from '../common/engine';
+import { featureFlags, Flag, PERF_SAMPLE_FLAG } from '../common/feature_flags';
+import { HttpRpcEngine } from '../common/http_rpc_engine';
+import { NUM, NUM_NULL, QueryError, STR, STR_NULL } from '../common/query_result';
+import { EngineMode } from '../common/state';
+import { TimeSpan, toNs, toNsCeil, toNsFloor } from '../common/time';
+import { resetEngineWorker, WasmEngineProxy } from '../common/wasm_engine_proxy';
import {
globals as frontendGlobals,
QuantizedLoad,
@@ -37,7 +37,7 @@
publishOverviewData,
publishThreads
} from '../frontend/publish';
-import {Router} from '../frontend/router';
+import { Router } from '../frontend/router';
import {
CounterAggregationController
@@ -57,7 +57,7 @@
import {
ThreadAggregationController
} from './aggregation/thread_aggregation_controller';
-import {Child, Children, Controller} from './controller';
+import { Child, Children, Controller } from './controller';
import {
CpuProfileController,
CpuProfileControllerArgs
@@ -70,14 +70,15 @@
FlowEventsController,
FlowEventsControllerArgs
} from './flow_events_controller';
-import {globals} from './globals';
-import {LoadingManager} from './loading_manager';
-import {LogsController} from './logs_controller';
-import {MetricsController} from './metrics_controller';
+import { globals } from './globals';
+import { LoadingManager } from './loading_manager';
+import { LogsController } from './logs_controller';
+import { MetricsController } from './metrics_controller';
import {
PivotTableController,
PivotTableControllerArgs
} from './pivot_table_controller';
+import {PivotTableReduxController} from './pivot_table_redux_controller';
import {QueryController, QueryControllerArgs} from './query_controller';
import {SearchController} from './search_controller';
import {
@@ -93,10 +94,10 @@
TraceHttpStream,
TraceStream
} from './trace_stream';
-import {TrackControllerArgs, trackControllerRegistry} from './track_controller';
-import {decideTracks} from './track_decider';
+import { TrackControllerArgs, trackControllerRegistry } from './track_controller';
+import { decideTracks } from './track_decider';
-type States = 'init'|'loading_trace'|'ready';
+type States = 'init' | 'loading_trace' | 'ready';
const METRICS = [
'android_startup',
@@ -111,6 +112,7 @@
'android_camera',
'chrome_dropped_frames',
'trace_metadata',
+ 'android_trusty_workqueues',
];
const FLAGGED_METRICS: Array<[Flag, string]> = METRICS.map(m => {
const id = `forceMetric${m}`;
@@ -143,17 +145,17 @@
switch (this.state) {
case 'init':
this.loadTrace()
- .then(mode => {
- globals.dispatch(Actions.setEngineReady({
- engineId: this.engineId,
- ready: true,
- mode,
- }));
- })
- .catch(err => {
- this.updateStatus(`${err}`);
- throw err;
- });
+ .then(mode => {
+ globals.dispatch(Actions.setEngineReady({
+ engineId: this.engineId,
+ ready: true,
+ mode,
+ }));
+ })
+ .catch(err => {
+ this.updateStatus(`${err}`);
+ throw err;
+ });
this.updateStatus('Opening trace');
this.setState('loading_trace');
break;
@@ -176,74 +178,76 @@
if (trackCfg.engineId !== this.engineId) continue;
if (!trackControllerRegistry.has(trackCfg.kind)) continue;
const trackCtlFactory = trackControllerRegistry.get(trackCfg.kind);
- const trackArgs: TrackControllerArgs = {trackId, engine};
+ const trackArgs: TrackControllerArgs = { trackId, engine };
childControllers.push(Child(trackId, trackCtlFactory, trackArgs));
}
// Create a QueryController for each query.
for (const queryId of Object.keys(globals.state.queries)) {
- const queryArgs: QueryControllerArgs = {queryId, engine};
+ const queryArgs: QueryControllerArgs = { queryId, engine };
childControllers.push(Child(queryId, QueryController, queryArgs));
}
- const selectionArgs: SelectionControllerArgs = {engine};
+ const selectionArgs: SelectionControllerArgs = { engine };
childControllers.push(
- Child('selection', SelectionController, selectionArgs));
+ Child('selection', SelectionController, selectionArgs));
- const flowEventsArgs: FlowEventsControllerArgs = {engine};
+ const flowEventsArgs: FlowEventsControllerArgs = { engine };
childControllers.push(
- Child('flowEvents', FlowEventsController, flowEventsArgs));
+ Child('flowEvents', FlowEventsController, flowEventsArgs));
- const cpuProfileArgs: CpuProfileControllerArgs = {engine};
+ const cpuProfileArgs: CpuProfileControllerArgs = { engine };
childControllers.push(
- Child('cpuProfile', CpuProfileController, cpuProfileArgs));
+ Child('cpuProfile', CpuProfileController, cpuProfileArgs));
- const flamegraphArgs: FlamegraphControllerArgs = {engine};
+ const flamegraphArgs: FlamegraphControllerArgs = { engine };
childControllers.push(
- Child('flamegraph', FlamegraphController, flamegraphArgs));
+ Child('flamegraph', FlamegraphController, flamegraphArgs));
childControllers.push(Child(
- 'cpu_aggregation',
- CpuAggregationController,
- {engine, kind: 'cpu_aggregation'}));
+ 'cpu_aggregation',
+ CpuAggregationController,
+ { engine, kind: 'cpu_aggregation' }));
childControllers.push(Child(
- 'thread_aggregation',
- ThreadAggregationController,
- {engine, kind: 'thread_state_aggregation'}));
+ 'thread_aggregation',
+ ThreadAggregationController,
+ { engine, kind: 'thread_state_aggregation' }));
childControllers.push(Child(
- 'cpu_process_aggregation',
- CpuByProcessAggregationController,
- {engine, kind: 'cpu_by_process_aggregation'}));
+ 'cpu_process_aggregation',
+ CpuByProcessAggregationController,
+ { engine, kind: 'cpu_by_process_aggregation' }));
childControllers.push(Child(
- 'slice_aggregation',
- SliceAggregationController,
- {engine, kind: 'slice_aggregation'}));
+ 'slice_aggregation',
+ SliceAggregationController,
+ { engine, kind: 'slice_aggregation' }));
childControllers.push(Child(
- 'counter_aggregation',
- CounterAggregationController,
- {engine, kind: 'counter_aggregation'}));
+ 'counter_aggregation',
+ CounterAggregationController,
+ { engine, kind: 'counter_aggregation' }));
childControllers.push(Child(
- 'frame_aggregation',
- FrameAggregationController,
- {engine, kind: 'frame_aggregation'}));
+ 'frame_aggregation',
+ FrameAggregationController,
+ { engine, kind: 'frame_aggregation' }));
childControllers.push(Child('search', SearchController, {
engine,
app: globals,
}));
+ childControllers.push(
+ Child('pivot_table_redux', PivotTableReduxController, {engine}));
childControllers.push(Child('logs', LogsController, {
engine,
app: globals,
}));
childControllers.push(
- Child('traceError', TraceErrorController, {engine}));
- childControllers.push(Child('metrics', MetricsController, {engine}));
+ Child('traceError', TraceErrorController, { engine }));
+ childControllers.push(Child('metrics', MetricsController, { engine }));
// Create a PivotTableController for each pivot table.
for (const pivotTableId of Object.keys(globals.state.pivotTable)) {
const pivotTableArgs:
- PivotTableControllerArgs = {pivotTableId, engine};
+ PivotTableControllerArgs = { pivotTableId, engine };
childControllers.push(
- Child(pivotTableId, PivotTableController, pivotTableArgs));
+ Child(pivotTableId, PivotTableController, pivotTableArgs));
}
return childControllers;
@@ -274,7 +278,7 @@
engine = new HttpRpcEngine(this.engineId, LoadingManager.getInstance);
engine.errorHandler = (err) => {
globals.dispatch(
- Actions.setEngineFailed({mode: 'HTTP_RPC', failure: `${err}`}));
+ Actions.setEngineFailed({ mode: 'HTTP_RPC', failure: `${err}` }));
throw err;
};
} else {
@@ -282,7 +286,7 @@
engineMode = 'WASM';
const enginePort = resetEngineWorker();
engine = new WasmEngineProxy(
- this.engineId, enginePort, LoadingManager.getInstance);
+ this.engineId, enginePort, LoadingManager.getInstance);
}
this.engine = engine;
@@ -293,7 +297,7 @@
mode: engineMode,
}));
const engineCfg = assertExists(globals.state.engines[this.engineId]);
- let traceStream: TraceStream|undefined;
+ let traceStream: TraceStream | undefined;
if (engineCfg.source.type === 'FILE') {
traceStream = new TraceFileStream(engineCfg.source.file);
} else if (engineCfg.source.type === 'ARRAY_BUFFER') {
@@ -313,7 +317,7 @@
// file/stream and we just want to jump to the loading phase.
if (traceStream !== undefined) {
const tStart = performance.now();
- for (;;) {
+ for (; ;) {
const res = await traceStream.readChunk();
await this.engine.parse(res.data);
const elapsed = (performance.now() - tStart) / 1000;
@@ -350,13 +354,13 @@
const emptyOmniboxState = {
omnibox: '',
mode: frontendGlobals.state.frontendLocalState.omniboxState.mode ||
- 'SEARCH',
+ 'SEARCH',
lastUpdate: Date.now() / 1000
};
const actions: DeferredAction[] = [
Actions.setOmnibox(emptyOmniboxState),
- Actions.setTraceUuid({traceUuid}),
+ Actions.setTraceUuid({ traceUuid }),
Actions.setTraceTime(traceTimeState)
];
@@ -365,9 +369,9 @@
const mdTime = await this.engine.getTracingMetadataTimeBounds();
// make sure the bounds hold
if (Math.max(visibleStartSec, mdTime.start - TRACE_MARGIN_TIME_S) <
- Math.min(visibleEndSec, mdTime.end + TRACE_MARGIN_TIME_S)) {
+ Math.min(visibleEndSec, mdTime.end + TRACE_MARGIN_TIME_S)) {
visibleStartSec =
- Math.max(visibleStartSec, mdTime.start - TRACE_MARGIN_TIME_S);
+ Math.max(visibleStartSec, mdTime.start - TRACE_MARGIN_TIME_S);
visibleEndSec = Math.min(visibleEndSec, mdTime.end + TRACE_MARGIN_TIME_S);
}
@@ -382,14 +386,14 @@
}));
globals.dispatchMultiple(actions);
- Router.navigate(`#!/viewer?trace_id=${traceUuid}`);
+ Router.navigate(`#!/viewer?local_cache_key=${traceUuid}`);
// Make sure the helper views are available before we start adding tracks.
await this.initialiseHelperViews();
{
// When we reload from a permalink don't create extra tracks:
- const {pinnedTracks, tracks} = globals.state;
+ const { pinnedTracks, tracks } = globals.state;
if (!pinnedTracks.length && !Object.keys(tracks).length) {
await this.listTracks();
}
@@ -433,11 +437,11 @@
order by ts desc limit 1`;
const profile = await assertExists(this.engine).query(query);
if (profile.numRows() !== 1) return;
- const row = profile.firstRow({ts: NUM, upid: NUM});
+ const row = profile.firstRow({ ts: NUM, upid: NUM });
const ts = row.ts;
const upid = row.upid;
globals.dispatch(
- Actions.selectPerfSamples({id: 0, upid, ts, type: 'perf'}));
+ Actions.selectPerfSamples({ id: 0, upid, ts, type: 'perf' }));
}
private async selectFirstHeapProfile() {
@@ -449,11 +453,11 @@
heap_graph_object) order by ts limit 1`;
const profile = await assertExists(this.engine).query(query);
if (profile.numRows() !== 1) return;
- const row = profile.firstRow({ts: NUM, type: STR, upid: NUM});
+ const row = profile.firstRow({ ts: NUM, type: STR, upid: NUM });
const ts = row.ts;
const type = row.type;
const upid = row.upid;
- globals.dispatch(Actions.selectHeapProfile({id: 0, upid, ts, type}));
+ globals.dispatch(Actions.selectHeapProfile({ id: 0, upid, ts, type }));
}
private async listTracks() {
@@ -494,7 +498,7 @@
const threadName = it.threadName;
const procName = it.procName === null ? undefined : it.procName;
const cmdline = it.cmdline === null ? undefined : it.cmdline;
- threads.push({utid, tid, threadName, pid, procName, cmdline});
+ threads.push({ utid, tid, threadName, pid, procName, cmdline });
}
publishThreads(threads);
}
@@ -506,8 +510,8 @@
let hasSchedOverview = false;
for (let step = 0; step < numSteps; step++) {
this.updateStatus(
- 'Loading overview ' +
- `${Math.round((step + 1) / numSteps * 1000) / 10}%`);
+ 'Loading overview ' +
+ `${Math.round((step + 1) / numSteps * 1000) / 10}%`);
const startSec = traceTime.start + step * stepSec;
const startNs = toNsFloor(startSec);
const endSec = startSec + stepSec;
@@ -515,15 +519,15 @@
// Sched overview.
const schedResult = await engine.query(
- `select sum(dur)/${stepSec}/1e9 as load, cpu from sched ` +
- `where ts >= ${startNs} and ts < ${endNs} and utid != 0 ` +
- 'group by cpu order by cpu');
- const schedData: {[key: string]: QuantizedLoad} = {};
- const it = schedResult.iter({load: NUM, cpu: NUM});
+ `select sum(dur)/${stepSec}/1e9 as load, cpu from sched ` +
+ `where ts >= ${startNs} and ts < ${endNs} and utid != 0 ` +
+ 'group by cpu order by cpu');
+ const schedData: { [key: string]: QuantizedLoad } = {};
+ const it = schedResult.iter({ load: NUM, cpu: NUM });
for (; it.valid(); it.next()) {
const load = it.load;
const cpu = it.cpu;
- schedData[cpu] = {startSec, endSec, load};
+ schedData[cpu] = { startSec, endSec, load };
hasSchedOverview = true;
}
publishOverviewData(schedData);
@@ -543,8 +547,7 @@
from thread
inner join (
select
- ifnull(cast((ts - ${traceStartNs})/${
- stepSecNs} as int), 0) as bucket,
+ ifnull(cast((ts - ${traceStartNs})/${stepSecNs} as int), 0) as bucket,
sum(dur) as utid_sum,
utid
from slice
@@ -554,8 +557,8 @@
where upid is not null
group by bucket, upid`);
- const slicesData: {[key: string]: QuantizedLoad[]} = {};
- const it = sliceResult.iter({bucket: NUM, upid: NUM, load: NUM});
+ const slicesData: { [key: string]: QuantizedLoad[] } = {};
+ const it = sliceResult.iter({ bucket: NUM, upid: NUM, load: NUM });
for (; it.valid(); it.next()) {
const bucket = it.bucket;
const upid = it.upid;
@@ -569,7 +572,7 @@
if (loadArray === undefined) {
loadArray = slicesData[upidStr] = [];
}
- loadArray.push({startSec, endSec, load});
+ loadArray.push({ startSec, endSec, load });
}
publishOverviewData(slicesData);
}
@@ -582,13 +585,14 @@
// One of the cases covered is an empty trace.
return '';
}
- const traceUuid = result.firstRow({uuid: STR}).uuid;
+ const traceUuid = result.firstRow({ uuid: STR }).uuid;
const engineConfig = assertExists(globals.state.engines[engine.id]);
if (!(await cacheTrace(engineConfig.source, traceUuid))) {
// If the trace is not cacheable (cacheable means it has been opened from
- // URL or RPC) only append '?trace_id' to the URL, without the trace_id
- // value. Doing otherwise would cause an error if the tab is discarded or
- // the user hits the reload button because the trace is not in the cache.
+ // URL or RPC) only append '?local_cache_key' to the URL, without the
+ // local_cache_key value. Doing otherwise would cause an error if the tab
+ // is discarded or the user hits the reload button because the trace is
+ // not in the cache.
return '';
}
return traceUuid;
@@ -646,12 +650,19 @@
`);
+ const availableMetrics = [];
+ const metricsResult = await engine.query('select name from trace_metrics');
+ for (const it = metricsResult.iter({name: STR}); it.valid(); it.next()) {
+ availableMetrics.push(it.name);
+ }
+ globals.dispatch(Actions.setAvailableMetrics({availableMetrics}));
+
+ const availableMetricsSet = new Set<string>(availableMetrics);
for (const [flag, metric] of FLAGGED_METRICS) {
- if (!flag.get()) {
+ if (!flag.get() || !availableMetricsSet.has(metric)) {
continue;
}
-
this.updateStatus(`Computing ${metric} metric`);
try {
// We don't care about the actual result of metric here as we are just
@@ -674,7 +685,7 @@
let hasUpid = false;
let hasValue = false;
let hasGroupName = false;
- const it = result.iter({name: STR});
+ const it = result.iter({ name: STR });
for (; it.valid(); it.next()) {
const name = it.name;
hasSliceName = hasSliceName || name === 'slice_name';
@@ -687,7 +698,7 @@
const upidColumnSelect = hasUpid ? 'upid' : '0 AS upid';
const upidColumnWhere = hasUpid ? 'upid' : '0';
const groupNameColumn =
- hasGroupName ? 'group_name' : 'NULL AS group_name';
+ hasGroupName ? 'group_name' : 'NULL AS group_name';
if (hasSliceName && hasDur) {
await engine.query(`
INSERT INTO annotation_slice_track(
@@ -723,7 +734,7 @@
IFNULL(MAX(value), 0) as maxValue
FROM ${metric}_event
WHERE ${upidColumnWhere} != 0`);
- const row = minMax.firstRow({minValue: NUM, maxValue: NUM});
+ const row = minMax.firstRow({ minValue: NUM, maxValue: NUM });
await engine.query(`
INSERT INTO annotation_counter_track(
name, __metric_name, min_value, max_value, upid)
diff --git a/ui/src/controller/track_decider.ts b/ui/src/controller/track_decider.ts
index f1765fc..a82888f 100644
--- a/ui/src/controller/track_decider.ts
+++ b/ui/src/controller/track_decider.ts
@@ -427,7 +427,10 @@
async addAnnotationTracks(): Promise<void> {
const sliceResult = await this.engine.query(`
- SELECT id, name, upid, group_name FROM annotation_slice_track`);
+ select id, name, upid, group_name
+ from annotation_slice_track
+ order by name
+ `);
const sliceIt = sliceResult.iter({
id: NUM,
@@ -449,7 +452,7 @@
const upid = sliceIt.upid;
const groupName = sliceIt.group_name;
- let trackId = undefined;
+ let summaryTrackId = undefined;
let trackGroupId =
upid === 0 ? SCROLLING_TRACK_GROUP : this.upidToUuid.get(upid);
@@ -462,16 +465,16 @@
trackGroupId = groupIds.id;
} else {
trackGroupId = uuidv4();
- trackId = uuidv4();
+ summaryTrackId = uuidv4();
groupNameToIds.set(groupName, {
id: trackGroupId,
- summaryTrackId: trackId,
+ summaryTrackId,
});
}
}
this.tracksToAdd.push({
- id: trackId,
+ id: summaryTrackId,
engineId: this.engineId,
kind: SLICE_TRACK_KIND,
name,
diff --git a/ui/src/frontend/details_panel.ts b/ui/src/frontend/details_panel.ts
index 5a98bd1..0088e33 100644
--- a/ui/src/frontend/details_panel.ts
+++ b/ui/src/frontend/details_panel.ts
@@ -37,6 +37,7 @@
import {PivotTable} from './pivot_table';
import {ColumnDisplay, ColumnPicker} from './pivot_table_editor';
import {PivotTableHelper} from './pivot_table_helper';
+import {PivotTableRedux} from './pivot_table_redux';
import {QueryTable} from './query_table';
import {SliceDetailsPanel} from './slice_details_panel';
import {ThreadStatePanel} from './thread_state_panel';
@@ -326,6 +327,14 @@
});
}
+ if (globals.state.pivotTableRedux.enabled) {
+ detailsPanels.push({
+ key: 'pivot_table_redux',
+ name: 'Pivot Table',
+ vnode: m(PivotTableRedux)
+ });
+ }
+
for (const pivotTableId of Object.keys(globals.state.pivotTable)) {
const pivotTable = globals.state.pivotTable[pivotTableId];
const helper = globals.pivotTableHelper.get(pivotTableId);
diff --git a/ui/src/frontend/flow_events_panel.ts b/ui/src/frontend/flow_events_panel.ts
index b8fa46f..323d993 100644
--- a/ui/src/frontend/flow_events_panel.ts
+++ b/ui/src/frontend/flow_events_panel.ts
@@ -43,7 +43,7 @@
}
const flowClickHandler = (sliceId: number, trackId: number) => {
- const uiTrackId = globals.state.uiTrackIdByTraceTrackId.get(trackId);
+ const uiTrackId = globals.state.uiTrackIdByTraceTrackId[trackId];
if (uiTrackId) {
globals.makeSelection(
Actions.selectChromeSlice(
diff --git a/ui/src/frontend/flow_events_renderer.ts b/ui/src/frontend/flow_events_renderer.ts
index 4cde97b..1d2a719 100644
--- a/ui/src/frontend/flow_events_renderer.ts
+++ b/ui/src/frontend/flow_events_renderer.ts
@@ -97,7 +97,7 @@
export class FlowEventsRenderer {
private getTrackGroupIdByTrackId(trackId: number): string|undefined {
- const uiTrackId = globals.state.uiTrackIdByTraceTrackId.get(trackId);
+ const uiTrackId = globals.state.uiTrackIdByTraceTrackId[trackId];
return uiTrackId ? globals.state.tracks[uiTrackId].trackGroup : undefined;
}
diff --git a/ui/src/frontend/index.ts b/ui/src/frontend/index.ts
index 9265090..c86f52b 100644
--- a/ui/src/frontend/index.ts
+++ b/ui/src/frontend/index.ts
@@ -146,6 +146,7 @@
'connect-src': [
`'self'`,
'http://127.0.0.1:9001', // For trace_processor_shell --httpd.
+ 'ws://127.0.0.1:9001', // Ditto, for the websocket RPC.
'https://www.google-analytics.com',
'https://*.googleapis.com', // For Google Cloud Storage fetches.
'blob:',
@@ -320,7 +321,7 @@
}
installFileDropHandler();
- // Handles the initial ?trace_id=a0b1c2 or ?s=permalink or ?url=... cases.
+ // Handles the initial ?local_cache_key=123 or ?s=permalink or ?url=... cases.
maybeOpenTraceFromRoute(Router.parseUrl(window.location.href));
}
diff --git a/ui/src/frontend/keyboard_event_handler.ts b/ui/src/frontend/keyboard_event_handler.ts
index 4f21346..3f46bf1 100644
--- a/ui/src/frontend/keyboard_event_handler.ts
+++ b/ui/src/frontend/keyboard_event_handler.ts
@@ -163,7 +163,7 @@
if (flow.id === flowId) {
const flowPoint = (direction === 'Backward' ? flow.begin : flow.end);
const uiTrackId =
- globals.state.uiTrackIdByTraceTrackId.get(flowPoint.trackId);
+ globals.state.uiTrackIdByTraceTrackId[flowPoint.trackId];
if (uiTrackId) {
globals.makeSelection(Actions.selectChromeSlice(
{id: flowPoint.sliceId, trackId: uiTrackId, table: 'slice'}));
diff --git a/ui/src/frontend/pivot_table_redux.ts b/ui/src/frontend/pivot_table_redux.ts
new file mode 100644
index 0000000..c2c129a
--- /dev/null
+++ b/ui/src/frontend/pivot_table_redux.ts
@@ -0,0 +1,259 @@
+import * as m from 'mithril';
+
+import {Actions} from '../common/actions';
+import {ColumnType} from '../common/query_result';
+import {PivotTableReduxQuery, PivotTableReduxResult} from '../common/state';
+import {PivotTree} from '../controller/pivot_table_redux_controller';
+
+import {globals} from './globals';
+import {Panel} from './panel';
+import {
+ aggregationIndex,
+ ColumnSet,
+ generateQuery,
+ QueryGeneratorError,
+ sliceAggregationColumns,
+ Table,
+ TableColumn,
+ tables,
+ threadSliceAggregationColumns
+} from './pivot_table_redux_query_generator';
+
+interface ColumnSetCheckboxAttrs {
+ set: ColumnSet;
+ setKey: TableColumn;
+}
+
+interface PathItem {
+ tree: PivotTree;
+ nextKey: ColumnType;
+}
+
+// Helper component that controls whether a particular key is present in a
+// ColumnSet.
+class ColumnSetCheckbox implements m.ClassComponent<ColumnSetCheckboxAttrs> {
+ view({attrs}: m.Vnode<ColumnSetCheckboxAttrs>) {
+ return m('input[type=checkbox]', {
+ onclick: (e: InputEvent) => {
+ const target = e.target as HTMLInputElement;
+ if (target.checked) {
+ attrs.set.add(attrs.setKey);
+ } else {
+ attrs.set.delete(attrs.setKey);
+ }
+ globals.rafScheduler.scheduleFullRedraw();
+ },
+ checked: attrs.set.has(attrs.setKey)
+ });
+ }
+}
+
+export class PivotTableRedux extends Panel {
+ selectedPivotsMap = new ColumnSet();
+ selectedAggregations = new ColumnSet();
+ editMode = true;
+
+ renderCanvas(): void {}
+
+ generateQuery(): PivotTableReduxQuery {
+ return generateQuery(this.selectedPivotsMap, this.selectedAggregations);
+ }
+
+ runQuery() {
+ try {
+ const query = this.generateQuery();
+ const lastPivotTableState = globals.state.pivotTableRedux;
+ globals.dispatch(Actions.setPivotStateReduxState({
+ pivotTableState: {
+ query,
+ queryId: lastPivotTableState.queryId + 1,
+ enabled: true,
+ queryResult: null
+ }
+ }));
+ } catch (e) {
+ console.log(e);
+ }
+ }
+
+ renderTablePivotColumns(t: Table) {
+ return m(
+ 'li',
+ t.name,
+ m('ul',
+ t.columns.map(
+ col =>
+ m('li',
+ m(ColumnSetCheckbox, {
+ set: this.selectedPivotsMap,
+ setKey: [t.name, col],
+ }),
+ col))));
+ }
+
+ renderResultsView() {
+ return m(
+ '.pivot-table-redux',
+ m('button.mode-button',
+ {
+ onclick: () => {
+ this.editMode = true;
+ globals.rafScheduler.scheduleFullRedraw();
+ }
+ },
+ 'Edit'),
+ this.renderResultsTable());
+ }
+
+ renderSectionRow(
+ path: PathItem[], tree: PivotTree,
+ result: PivotTableReduxResult): m.Vnode {
+ const renderedCells = [];
+ for (let j = 0; j + 1 < path.length; j++) {
+ renderedCells.push(m('td', m('span.indent', ' '), `${path[j].nextKey}`));
+ }
+
+ const treeDepth = result.metadata.pivotColumns.length;
+ const colspan = treeDepth - path.length + 1;
+ const button =
+ m('button',
+ {
+ onclick: () => {
+ tree.isCollapsed = !tree.isCollapsed;
+ globals.rafScheduler.scheduleFullRedraw();
+ }
+ },
+ m('i.material-icons',
+ tree.isCollapsed ? 'expand_more' : 'expand_less'));
+
+ renderedCells.push(
+ m('td', {colspan}, button, `${path[path.length - 1].nextKey}`));
+
+ for (const value of tree.aggregates) {
+ renderedCells.push(m('td', `${value}`));
+ }
+
+ return m('tr', renderedCells);
+ }
+
+ renderTree(
+ path: PathItem[], tree: PivotTree, result: PivotTableReduxResult,
+ sink: m.Vnode[]) {
+ if (tree.isCollapsed) {
+ sink.push(this.renderSectionRow(path, tree, result));
+ return;
+ }
+ if (tree.children.size > 0) {
+ // Avoid rendering the intermediate results row for the root of tree
+ // and in case there's only one child subtree.
+ if (!tree.isCollapsed && path.length > 0 && tree.children.size !== 1) {
+ sink.push(this.renderSectionRow(path, tree, result));
+ }
+ for (const [key, childTree] of tree.children.entries()) {
+ path.push({tree: childTree, nextKey: key});
+ this.renderTree(path, childTree, result, sink);
+ path.pop();
+ }
+ return;
+ }
+
+ // Avoid rendering the intermediate results row if it has only one leaf
+ // row.
+ if (!tree.isCollapsed && tree.rows.length > 1) {
+ sink.push(this.renderSectionRow(path, tree, result));
+ }
+ for (const row of tree.rows) {
+ const renderedCells = [];
+ const treeDepth = result.metadata.pivotColumns.length;
+ for (let j = 0; j < treeDepth; j++) {
+ if (j < path.length) {
+ renderedCells.push(m('td', m('span.indent', ' '), `${row[j]}`));
+ } else {
+ renderedCells.push(m(`td`, `${row[j]}`));
+ }
+ }
+ for (let j = 0; j < result.metadata.aggregationColumns.length; j++) {
+ const value = row[aggregationIndex(treeDepth, j, treeDepth)];
+ renderedCells.push(m('td', `${value}`));
+ }
+
+ sink.push(m('tr', renderedCells));
+ }
+ }
+
+ renderResultsTable() {
+ const state = globals.state.pivotTableRedux;
+ if (state.query !== null || state.queryResult === null) {
+ return m('div', 'Loading...');
+ }
+
+ const renderedRows: m.Vnode[] = [];
+ this.renderTree(
+ [], state.queryResult.tree, state.queryResult, renderedRows);
+
+ const allColumns = state.queryResult.metadata.pivotColumns.concat(
+ state.queryResult.metadata.aggregationColumns);
+ return m(
+ 'table.query-table.pivot-table',
+ m('thead', m('tr', allColumns.map(column => m('td', column)))),
+ m('tbody', renderedRows));
+ }
+
+ renderQuery(): m.Vnode {
+ try {
+ return m(
+ 'div',
+ m('pre', this.generateQuery()),
+ m('button.mode-button',
+ {
+ onclick: () => {
+ this.editMode = false;
+ this.runQuery();
+ globals.rafScheduler.scheduleFullRedraw();
+ }
+ },
+ 'Execute'));
+ } catch (e) {
+ if (e instanceof QueryGeneratorError) {
+ return m('div.query-error', e.message);
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ view() {
+ return this.editMode ? this.renderEditView() : this.renderResultsView();
+ }
+
+ renderEditView() {
+ return m(
+ '.pivot-table-redux.edit',
+ m('div',
+ m('h2', 'Pivots'),
+ m('ul',
+ tables.map(
+ t => this.renderTablePivotColumns(t),
+ ))),
+ m('div',
+ m('h2', 'Aggregations'),
+ m('ul',
+ ...sliceAggregationColumns.map(
+ t =>
+ m('li',
+ m(ColumnSetCheckbox, {
+ set: this.selectedAggregations,
+ setKey: ['slice', t],
+ }),
+ t)),
+ ...threadSliceAggregationColumns.map(
+ t =>
+ m('li',
+ m(ColumnSetCheckbox, {
+ set: this.selectedAggregations,
+ setKey: ['thread_slice', t],
+ }),
+ `thread_slice.${t}`)))),
+ this.renderQuery());
+ }
+}
\ No newline at end of file
diff --git a/ui/src/frontend/pivot_table_redux_query_generator.ts b/ui/src/frontend/pivot_table_redux_query_generator.ts
new file mode 100644
index 0000000..9bd62fc
--- /dev/null
+++ b/ui/src/frontend/pivot_table_redux_query_generator.ts
@@ -0,0 +1,221 @@
+import {PivotTableReduxQuery} from '../common/state';
+
+export interface Table {
+ name: string;
+ columns: string[];
+}
+
+export const sliceTable = {
+ name: 'slice',
+ columns: ['type', 'ts', 'dur', 'category', 'name']
+};
+
+// Columns of `slice` table available for aggregation.
+export const sliceAggregationColumns = ['ts', 'dur', 'depth'];
+
+// Columns of `thread_slice` table available for aggregation.
+export const threadSliceAggregationColumns = [
+ 'thread_ts',
+ 'thread_dur',
+ 'thread_instruction_count',
+ 'thread_instruction_delta'
+];
+
+// List of available tables to query, used to populate selectors of pivot
+// columns in the UI.
+export const tables: Table[] = [
+ sliceTable,
+ {
+ name: 'process',
+ columns: [
+ 'type',
+ 'pid',
+ 'name',
+ 'parent_upid',
+ 'uid',
+ 'android_appid',
+ 'cmdline'
+ ]
+ },
+ {name: 'thread', columns: ['type', 'name', 'tid', 'upid', 'is_main_thread']},
+ {name: 'thread_track', columns: ['type', 'name', 'utid']},
+];
+
+// Pair of table name and column name.
+export type TableColumn = [string, string];
+
+// ES6 Set does not allow to reasonably store compound objects; this class
+// rectifies the problem by providing a domain-specific set of pairs of strings.
+export class ColumnSet {
+ // Should've been Set<TableColumn>, but JavaScript Set does not support custom
+ // hashing/equality predicates, so TableColumn is keyed by a string generated
+ // by columnKey method.
+ backingMap = new Map<string, TableColumn>();
+
+ private static columnKey(column: TableColumn): string {
+ // None of table and column names used in Perfetto tables contain periods,
+ // so this function should not lead to collisions.
+ return `${column[0]}.${column[1]}`;
+ }
+
+ has(column: TableColumn): boolean {
+ return this.backingMap.has(ColumnSet.columnKey(column));
+ }
+
+ add(column: TableColumn) {
+ this.backingMap.set(ColumnSet.columnKey(column), column);
+ }
+
+ delete(column: TableColumn) {
+ this.backingMap.delete(ColumnSet.columnKey(column));
+ }
+
+ values(): Iterable<[string, string]> {
+ return this.backingMap.values();
+ }
+}
+
+// Exception thrown by query generator in case incoming parameters are not
+// suitable in order to build a correct query; these are caught by the UI and
+// displayed to the user.
+export class QueryGeneratorError extends Error {}
+
+// Internal column name for different rollover levels of aggregate columns.
+function aggregationAlias(
+ aggregationIndex: number, rolloverLevel: number): string {
+ return `agg_${aggregationIndex}_level_${rolloverLevel}`;
+}
+
+function generateInnerQuery(
+ pivots: string[],
+ aggregations: string[],
+ table: string,
+ includeTrack: boolean): string {
+ const pivotColumns = pivots.concat(includeTrack ? ['track_id'] : []);
+ const aggregationColumns: string[] = [];
+
+ for (let i = 0; i < aggregations.length; i++) {
+ const agg = aggregations[i];
+ aggregationColumns.push(`SUM(${agg}) as ${aggregationAlias(i, 0)}`);
+ }
+
+ return `
+ select
+ ${pivotColumns.concat(aggregationColumns).join(',\n')}
+ from ${table}
+ group by ${pivotColumns.join(', ')}
+ `;
+}
+
+function computeSliceTableAggregations(selectedAggregations: ColumnSet):
+ {tableName: string, flatAggregations: string[]} {
+ let hasThreadSliceColumn = false;
+ const allColumns = [];
+ for (const [table, column] of selectedAggregations.values()) {
+ if (table === 'thread_slice') {
+ hasThreadSliceColumn = true;
+ }
+ allColumns.push(column);
+ }
+
+ return {
+ // If any aggregation column from `thread_slice` is present, it's going to
+ // be the base table for the pivot table query. Otherwise, `slice` is used.
+ // This later is going to be controllable by a UI element.
+ tableName: hasThreadSliceColumn ? 'thread_slice' : 'slice',
+ flatAggregations: allColumns
+ };
+}
+
+// Every aggregation in the request is contained in the result in (number of
+// pivots + 1) times for each rollover level. This helper function returs an
+// index of the necessary column in the response.
+export function aggregationIndex(
+ pivotColumns: number, aggregationNo: number, depth: number) {
+ return pivotColumns + aggregationNo * (pivotColumns + 1) +
+ (pivotColumns - depth);
+}
+
+export function generateQuery(
+ selectedPivots: ColumnSet,
+ selectedAggregations: ColumnSet): PivotTableReduxQuery {
+ const sliceTableAggregations =
+ computeSliceTableAggregations(selectedAggregations);
+ const slicePivots: string[] = [];
+ const nonSlicePivots: string[] = [];
+
+ if (sliceTableAggregations.flatAggregations.length === 0) {
+ throw new QueryGeneratorError('No aggregations selected');
+ }
+
+ for (const [table, pivot] of selectedPivots.values()) {
+ if (table === 'slice' || table === 'thread_slice') {
+ slicePivots.push(pivot);
+ } else {
+ nonSlicePivots.push(`${table}.${pivot}`);
+ }
+ }
+
+ if (slicePivots.length === 0 && nonSlicePivots.length === 0) {
+ throw new QueryGeneratorError('No pivots selected');
+ }
+
+ const outerAggregations = [];
+ const prefixedSlicePivots = slicePivots.map(p => `preaggregated.${p}`);
+ const totalPivotsArray = nonSlicePivots.concat(prefixedSlicePivots);
+ for (let i = 0; i < sliceTableAggregations.flatAggregations.length; i++) {
+ const agg = `preaggregated.${aggregationAlias(i, 0)}`;
+ outerAggregations.push(`SUM(${agg}) as ${aggregationAlias(i, 0)}`);
+
+ for (let level = 1; level < totalPivotsArray.length; level++) {
+ // Peculiar form "SUM(SUM(agg)) over (partition by columns)" here means
+ // following: inner SUM(agg) is an aggregation that is going to collapse
+ // tracks with the same pivot values, which is going to be post-aggregated
+ // by the set of columns by outer **window** SUM function.
+
+ // Need to use complicated query syntax can be avoided by having yet
+ // another nested subquery computing only aggregation values with window
+ // functions in the wrapper, but the generation code is going to be more
+ // complex; so complexity of the query is traded for complexity of the
+ // query generator.
+ outerAggregations.push(`SUM(SUM(${agg})) over (partition by ${
+ totalPivotsArray.slice(0, totalPivotsArray.length - level)
+ .join(', ')}) as ${aggregationAlias(i, level)}`);
+ }
+
+ outerAggregations.push(`SUM(SUM(${agg})) over () as ${
+ aggregationAlias(i, totalPivotsArray.length)}`);
+ }
+
+ const joins = `
+ join thread_track on thread_track.id = preaggregated.track_id
+ join thread using (utid)
+ join process using (upid)
+ `;
+
+ const text = `
+ select
+ ${
+ nonSlicePivots.concat(prefixedSlicePivots, outerAggregations).join(',\n')}
+ from (
+ ${
+ generateInnerQuery(
+ slicePivots,
+ sliceTableAggregations.flatAggregations,
+ sliceTableAggregations.tableName,
+ nonSlicePivots.length > 0)}
+ ) preaggregated
+ ${nonSlicePivots.length > 0 ? joins : ''}
+ group by ${nonSlicePivots.concat(prefixedSlicePivots).join(', ')}
+ `;
+
+ return {
+ text,
+ metadata: {
+ pivotColumns: nonSlicePivots.concat(slicePivots.map(
+ column => `${sliceTableAggregations.tableName}.${column}`)),
+ aggregationColumns: sliceTableAggregations.flatAggregations.map(
+ agg => `SUM(${sliceTableAggregations.tableName}.${agg})`)
+ }
+ };
+}
diff --git a/ui/src/frontend/post_message_handler.ts b/ui/src/frontend/post_message_handler.ts
index 5309b6b..56269e3 100644
--- a/ui/src/frontend/post_message_handler.ts
+++ b/ui/src/frontend/post_message_handler.ts
@@ -30,6 +30,7 @@
'https://chrometto.googleplex.com',
'https://uma.googleplex.com',
];
+ if (origin === window.origin) return true;
if (TRUSTED_ORIGINS.includes(origin)) return true;
if (new URL(origin).hostname.endsWith('corp.google.com')) return true;
return false;
diff --git a/ui/src/frontend/query_table.ts b/ui/src/frontend/query_table.ts
index 8e94fd5..fc2eb7f 100644
--- a/ui/src/frontend/query_table.ts
+++ b/ui/src/frontend/query_table.ts
@@ -56,7 +56,7 @@
const sliceDur = fromNs(Math.max(row.dur as number, 1));
const sliceEnd = sliceStart + sliceDur;
const trackId = row.track_id as number;
- const uiTrackId = globals.state.uiTrackIdByTraceTrackId.get(trackId);
+ const uiTrackId = globals.state.uiTrackIdByTraceTrackId[trackId];
if (uiTrackId === undefined) return;
verticalScrollToTrack(uiTrackId, true);
horizontalScrollAndZoomToRange(sliceStart, sliceEnd);
@@ -135,29 +135,42 @@
rows.push(m(QueryTableRow, {row: resp.rows[i], columns: resp.columns}));
}
+ const headers = [
+ m(
+ 'header.overview',
+ `Query result - ${Math.round(resp.durationMs)} ms`,
+ m('span.code', resp.query),
+ resp.error ? null :
+ m('button.query-ctrl',
+ {
+ onclick: () => {
+ queryResponseToClipboard(resp);
+ },
+ },
+ 'Copy as .tsv'),
+ m('button.query-ctrl',
+ {
+ onclick: () => {
+ globals.queryResults.delete(queryId);
+ globals.rafScheduler.scheduleFullRedraw();
+ }
+ },
+ 'Close'),
+ ),
+ ];
+
+
+ if (resp.statementWithOutputCount > 1) {
+ headers.push(
+ m('header.overview',
+ `${resp.statementWithOutputCount} out of ${resp.statementCount} ` +
+ `statements returned a result. Only the results for the last ` +
+ `statement are displayed in the table below.`));
+ }
+
return m(
'div',
- m(
- 'header.overview',
- `Query result - ${Math.round(resp.durationMs)} ms`,
- m('span.code', resp.query),
- resp.error ? null :
- m('button.query-ctrl',
- {
- onclick: () => {
- queryResponseToClipboard(resp);
- },
- },
- 'Copy as .tsv'),
- m('button.query-ctrl',
- {
- onclick: () => {
- globals.queryResults.delete(queryId);
- globals.rafScheduler.scheduleFullRedraw();
- }
- },
- 'Close'),
- ),
+ ...headers,
// TODO(rsavitski): the x-scrollable works for the
// dedicated query page, but is insufficient in the case of
// the results being presented within the bottom details
diff --git a/ui/src/frontend/router.ts b/ui/src/frontend/router.ts
index c288b58..5a260c1 100644
--- a/ui/src/frontend/router.ts
+++ b/ui/src/frontend/router.ts
@@ -21,8 +21,8 @@
/*
* A broken down representation of a route.
- * For instance: #!/record/gpu?trace_id=a0b1c2
- * becomes: {page: '/record', subpage: '/gpu', args: {trace_id: 'a0b1c2'}}
+ * For instance: #!/record/gpu?local_cache_key=a0b1
+ * becomes: {page: '/record', subpage: '/gpu', args: {local_cache_key: 'a0b1'}}
*/
export interface Route {
page: string;
@@ -37,19 +37,19 @@
* Args are !== the querystring (location.search) which is sent to the
* server. The route args are NOT sent to the HTTP server.
* Given this URL:
- * http://host/?foo=1&bar=2#!/page/subpage?trace_id=a0b1c2&baz=3.
+ * http://host/?foo=1&bar=2#!/page/subpage?local_cache_key=a0b1&baz=3.
*
* location.search = 'foo=1&bar=2'.
* This is seen by the HTTP server. We really don't use querystrings as the
* perfetto UI is client only.
*
- * location.hash = '#!/page/subpage?trace_id=a0b1c2'.
+ * location.hash = '#!/page/subpage?local_cache_key=a0b1'.
* This is client-only. All the routing logic in the Perfetto UI uses only
* this.
*/
export interface RouteArgs {
- // The trace_id is special and is persisted across navigations.
- trace_id?: string;
+ // The local_cache_key is special and is persisted across navigations.
+ local_cache_key?: string;
// These are transient and are really set only on startup.
openFromAndroidBugTool?: string;
@@ -71,18 +71,18 @@
* 2) Handles the (optional) args, e.g. #!/page?arg=1&arg2=2.
* Route args are carry information that is orthogonal to the page (e.g. the
* trace id).
- * trace_id has some special treatment: once a URL has a trace_id argument,
+ * local_cache_key has some special treatment: once a URL has a local_cache_key,
* it gets automatically appended to further navigations that don't have one.
- * For instance if the current url is #!/viewer?trace_id=1234 and a later
+ * For instance if the current url is #!/viewer?local_cache_key=1234 and a later
* action (either user-initiated or code-initited) navigates to #!/info, the
* rotuer will automatically replace the history entry with
- * #!/info?trace_id=1234.
+ * #!/info?local_cache_key=1234.
* This is to keep propagating the trace id across page changes, for handling
* tab discards (b/175041881).
*
* This class does NOT deal with the "load a trace when the url contains ?url=
- * or ?trace_id=". That logic lives in trace_url_handler.ts, which is triggered
- * by Router.onRouteChanged().
+ * or ?local_cache_key=". That logic lives in trace_url_handler.ts, which is
+ * triggered by Router.onRouteChanged().
*/
export class Router {
private readonly recentChanges: number[] = [];
@@ -104,20 +104,21 @@
const oldRoute = Router.parseUrl(e.oldURL);
const newRoute = Router.parseUrl(e.newURL);
- if (newRoute.args.trace_id === undefined && oldRoute.args.trace_id) {
- // Propagate the trace_id across navigations. When a trace is loaded, the
- // URL becomes #!/viewer?trace_id=a0b1c2. The ?trace_id arg allows
+ if (newRoute.args.local_cache_key === undefined &&
+ oldRoute.args.local_cache_key) {
+ // Propagate `local_cache_key across` navigations. When a trace is loaded,
+ // the URL becomes #!/viewer?local_cache_key=123. `local_cache_key` allows
// reopening the trace from cache in the case of a reload or discard.
// When using the UI we can hit "bare" links (e.g. just '#!/info') which
// don't have the trace_uuid:
// - When clicking on an <a> element from the sidebar.
// - When the code calls Router.navigate().
// - When the user pastes a URL from docs page.
- // In all these cases we want to keep propagating the trace_id argument.
- // We do so by re-setting the trace_id argument and doing a
+ // In all these cases we want to keep propagating the `local_cache_key`.
+ // We do so by re-setting the `local_cache_key` and doing a
// location.replace which overwrites the history entry (note
// location.replace is NOT just a String.replace operation).
- newRoute.args.trace_id = oldRoute.args.trace_id;
+ newRoute.args.local_cache_key = oldRoute.args.local_cache_key;
}
const args = m.buildQueryString(newRoute.args);
@@ -153,9 +154,9 @@
/*
* Breaks down a fragment into a Route object.
* Sample input:
- * '#!/record/gpu?trace_id=629329-18bba4'
+ * '#!/record/gpu?local_cache_key=abcd-1234'
* Sample output:
- * {page: '/record', subpage: '/gpu', args: {trace_id: '629329-18bba4'}}
+ * {page: '/record', subpage: '/gpu', args: {local_cache_key: 'abcd-1234'}}
*/
static parseFragment(hash: string): Route {
const prefixLength = ROUTE_PREFIX.length;
@@ -176,6 +177,17 @@
const argsStr = argsStart < 0 ? '' : hash.substr(argsStart + 1);
const args = argsStr ? m.parseQueryString(hash.substr(argsStart)) : {};
+ // TODO(primiano): remove this in mid-2022. trace_id is the same concept of
+ // local_cache_key. Just at some point we renamed it to make it more obvious
+ // to people that those URLs cannot be copy-pasted in bugs. For now this
+ // handles cases of reloading pages from old version.
+ if ('trace_id' in args) {
+ if (!('local_cache_key' in args)) {
+ args['local_cache_key'] = args['trace_id'];
+ }
+ delete args['trace_id'];
+ }
+
return {page, subpage, args};
}
diff --git a/ui/src/frontend/sidebar.ts b/ui/src/frontend/sidebar.ts
index 0ada41c..653ee61 100644
--- a/ui/src/frontend/sidebar.ts
+++ b/ui/src/frontend/sidebar.ts
@@ -100,7 +100,6 @@
with first as (select started as ts from sqlstats limit 1)
select query,
round((max(ended - started, 0))/1e6) as runtime_ms,
- round((max(started - queued, 0))/1e6) as latency_ms,
round((started - first.ts)/1e6) as t_start_ms
from sqlstats, first
order by started desc`;
diff --git a/ui/src/frontend/time_scale.ts b/ui/src/frontend/time_scale.ts
index c5e92a7..cdf3ca2 100644
--- a/ui/src/frontend/time_scale.ts
+++ b/ui/src/frontend/time_scale.ts
@@ -15,7 +15,7 @@
import {assertFalse, assertTrue} from '../base/logging';
import {TimeSpan} from '../common/time';
-const MAX_ZOOM_SPAN_SEC = 1e-6; // 1us.
+const MAX_ZOOM_SPAN_SEC = 1e-8; // 10 ns.
/**
* Defines a mapping between number and seconds for the entire application.
diff --git a/ui/src/frontend/time_scale_unittest.ts b/ui/src/frontend/time_scale_unittest.ts
index c6b3822..7a1be03 100644
--- a/ui/src/frontend/time_scale_unittest.ts
+++ b/ui/src/frontend/time_scale_unittest.ts
@@ -73,5 +73,5 @@
const scale = new TimeScale(span, [200, 300]);
const newSpan = computeZoom(scale, span, 0.0000000001, 225);
expect((newSpan.end - newSpan.start) / 2 + newSpan.start).toBeCloseTo(1010);
- expect(newSpan.end - newSpan.start).toBeCloseTo(1e-6, 8);
+ expect(newSpan.end - newSpan.start).toBeCloseTo(1e-8);
});
diff --git a/ui/src/frontend/trace_url_handler.ts b/ui/src/frontend/trace_url_handler.ts
index fcd464d..690d743 100644
--- a/ui/src/frontend/trace_url_handler.ts
+++ b/ui/src/frontend/trace_url_handler.ts
@@ -53,9 +53,9 @@
return;
}
- if (route.args.trace_id) {
+ if (route.args.local_cache_key) {
// Handles the case of loading traces from the cache storage.
- maybeOpenCachedTrace(route.args.trace_id);
+ maybeOpenCachedTrace(route.args.local_cache_key);
return;
}
}
@@ -68,29 +68,29 @@
* It must take decision based on the app state, not on URL change events.
* Fragment changes are handled by the union of Router.onHashChange() and this
* function, as follows:
- * 1. '' -> URL without a ?trace_id=xxx arg:
+ * 1. '' -> URL without a ?local_cache_key=xxx arg:
* - no effect (except redrawing)
- * 2. URL without trace_id -> URL with trace_id:
+ * 2. URL without local_cache_key -> URL with local_cache_key:
* - Load cached trace (without prompting any dialog).
* - Show a (graceful) error dialog in the case of cache misses.
- * 3. '' -> URL with a ?trace_id=xxx arg:
+ * 3. '' -> URL with a ?local_cache_key=xxx arg:
* - Same as case 2.
- * 4. URL with trace_id=1 -> URL with trace_id=2:
+ * 4. URL with local_cache_key=1 -> URL with local_cache_key=2:
* a) If 2 != uuid of the trace currently loaded (globals.state.traceUuid):
* - Ask the user if they intend to switch trace and load 2.
* b) If 2 == uuid of current trace (e.g., after a new trace has loaded):
* - no effect (except redrawing).
- * 5. URL with trace_id -> URL without trace_id:
- * - Redirect to ?trace_id=1234 where 1234 is the UUID of the previous URL
- * (this might or might not match globals.state.traceUuid).
+ * 5. URL with local_cache_key -> URL without local_cache_key:
+ * - Redirect to ?local_cache_key=1234 where 1234 is the UUID of the previous
+ * URL (this might or might not match globals.state.traceUuid).
*
* Backward navigation cases:
- * 6. URL without trace_id <- URL with trace_id:
+ * 6. URL without local_cache_key <- URL with local_cache_key:
* - Same as case 5.
- * 7. URL with trace_id=1 <- URL with trace_id=2:
- * - Same as case 4a: go back to trace_id=1 but ask the user for confirmation.
- * 8. landing page <- URL with trace_id:
- * - Same as case 5: re-append the trace_id.
+ * 7. URL with local_cache_key=1 <- URL with local_cache_key=2:
+ * - Same as case 4a: go back to local_cache_key=1 but ask the user to confirm.
+ * 8. landing page <- URL with local_cache_key:
+ * - Same as case 5: re-append the local_cache_key.
*/
async function maybeOpenCachedTrace(traceUuid: string) {
if (traceUuid === globals.state.traceUuid) {
@@ -101,12 +101,12 @@
if (traceUuid === '') {
// This can happen if we switch from an empty UI state to an invalid UUID
// (e.g. due to a cache miss, below). This can also happen if the user just
- // types /#!/viewer?trace_id=.
+ // types /#!/viewer?local_cache_key=.
return;
}
// This handles the case when a trace T1 is loaded and then the url is set to
- // ?trace_id=T2. In that case the globals.state.traceUuid remains set to T1
+ // ?local_cache_key=T2. In that case globals.state.traceUuid remains set to T1
// until T2 has been loaded by the trace processor (can take several seconds).
// This early out prevents to re-trigger the openTraceFromXXX() action if the
// URL changes (e.g. if the user navigates back/fwd) while the new trace is
@@ -122,7 +122,8 @@
const maybeTrace = await tryGetTrace(traceUuid);
const navigateToOldTraceUuid = () => {
- Router.navigate(`#!/viewer?trace_id=${globals.state.traceUuid || ''}`);
+ Router.navigate(
+ `#!/viewer?local_cache_key=${globals.state.traceUuid || ''}`);
};
if (!maybeTrace) {
@@ -131,8 +132,8 @@
content: m(
'div',
m('p',
- 'You are trying to load a cached trace by setting the ?trace_id ' +
- 'argument in the URL.'),
+ 'You are trying to load a cached trace by setting the ' +
+ '?local_cache_key argument in the URL.'),
m('p', 'Unfortunately the trace wasn\'t in the cache storage.'),
m('p',
'This can happen if a tab was discarded and wasn\'t opened ' +
@@ -147,7 +148,7 @@
// If the UI is in a blank state (no trace has been ever opened), just load
// the trace without showing any further dialog. This is the case of tab
- // discarding, reloading or pasting a url with a trace_id in an empty
+ // discarding, reloading or pasting a url with a local_cache_key in an empty
// instance.
if (globals.state.traceUuid === undefined) {
globals.dispatch(Actions.openTraceFromBuffer(maybeTrace));
@@ -165,7 +166,7 @@
'div',
m('p',
'You are seeing this because you either pasted a URL with ' +
- 'a different ?trace_id=xxx argument or because you hit ' +
+ 'a different ?local_cache_key=xxx argument or because you hit ' +
'the history back/fwd button and reached a different trace.'),
m('p',
'If you continue another trace will be loaded and the UI ' +
diff --git a/ui/src/test/ui_integrationtest.ts b/ui/src/test/ui_integrationtest.ts
index 41c5107..f6a442f 100644
--- a/ui/src/test/ui_integrationtest.ts
+++ b/ui/src/test/ui_integrationtest.ts
@@ -172,7 +172,7 @@
test('access_subpage_then_go_back', async () => {
await waitForPerfettoIdle(page);
await page.goto(
- 'http://localhost:10000/?testing=1/#!/metrics?trace_id=76c25a80-25dd-1eb7-2246-d7b3c7a10f91');
+ 'http://localhost:10000/?testing=1/#!/metrics?local_cache_key=76c25a80-25dd-1eb7-2246-d7b3c7a10f91');
await page.goBack();
await waitForPerfettoIdle(page);
});
@@ -193,7 +193,7 @@
test('open_trace ', async () => {
await page.goto(
- 'http://localhost:10000/?testing=1#!/viewer?trace_id=76c25a80-25dd-1eb7-2246-d7b3c7a10f91');
+ 'http://localhost:10000/?testing=1#!/viewer?local_cache_key=76c25a80-25dd-1eb7-2246-d7b3c7a10f91');
await waitForPerfettoIdle(page);
});
@@ -204,7 +204,7 @@
test('open_second_trace', async () => {
await page.goto(
- 'http://localhost:10000/?testing=1#!/viewer?trace_id=00000000-0000-0000-e13c-bd7db4ff646f');
+ 'http://localhost:10000/?testing=1#!/viewer?local_cache_key=00000000-0000-0000-e13c-bd7db4ff646f');
await waitForPerfettoIdle(page);
// click on the 'Continue' button in the interstitial
@@ -222,7 +222,7 @@
test('open_invalid_trace', async () => {
await page.goto(
- 'http://localhost:10000/?testing=1#!/viewer?trace_id=invalid');
+ 'http://localhost:10000/?testing=1#!/viewer?local_cache_key=invalid');
await waitForPerfettoIdle(page);
});
});
@@ -262,7 +262,7 @@
const page = await getPage();
await page.goto('http://localhost:10000/?testing=1');
await page.goto(
- 'http://localhost:10000/?testing=1#!/viewer?trace_id=76c25a80-25dd-1eb7-2246-d7b3c7a10f91');
+ 'http://localhost:10000/?testing=1#!/viewer?local_cache_key=76c25a80-25dd-1eb7-2246-d7b3c7a10f91');
await waitForPerfettoIdle(page);
await page.goBack();
await waitForPerfettoIdle(page);
@@ -272,7 +272,7 @@
const page = await getPage();
await page.goto('about:blank');
await page.goto(
- 'http://localhost:10000/?testing=1#!/viewer?trace_id=invalid');
+ 'http://localhost:10000/?testing=1#!/viewer?local_cache_key=invalid');
await waitForPerfettoIdle(page);
});
});
diff --git a/ui/src/tracks/chrome_slices/controller.ts b/ui/src/tracks/chrome_slices/controller.ts
index f0b4a63..5c2cc06 100644
--- a/ui/src/tracks/chrome_slices/controller.ts
+++ b/ui/src/tracks/chrome_slices/controller.ts
@@ -21,6 +21,8 @@
import {Config, Data, SLICE_TRACK_KIND} from './common';
+// the lowest bucketNs gets is 2, but add some room in case of fp error
+const MIN_QUANT_NS = 3;
class ChromeSliceTrackController extends TrackController<Config, Data> {
static readonly kind = SLICE_TRACK_KIND;
@@ -53,9 +55,16 @@
this.maxDurNs = queryRes.firstRow({maxDur: NUM_NULL}).maxDur || 0;
}
+ // Buckets are always even and positive, don't quantize once we zoom to
+ // nanosecond-scale, so that we can see exact sizes.
+ let tsq = `ts`;
+ if (bucketNs > MIN_QUANT_NS) {
+ tsq = `(ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs}`;
+ }
+
const query = `
SELECT
- (ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs} as tsq,
+ ${tsq} as tsq,
ts,
max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur)) as dur,
depth,
@@ -115,15 +124,19 @@
const durNs = it.dur;
const endNs = startNs + durNs;
- let endNsQ = Math.floor((endNs + bucketNs / 2 - 1) / bucketNs) * bucketNs;
- endNsQ = Math.max(endNsQ, startNsQ + bucketNs);
+ let endNsQ = endNs;
+ if (bucketNs > MIN_QUANT_NS) {
+ endNsQ = Math.floor((endNs + bucketNs / 2 - 1) / bucketNs) * bucketNs;
+ endNsQ = Math.max(endNsQ, startNsQ + bucketNs);
+ }
- if (!it.isInstant && startNsQ === endNsQ) {
- throw new Error(
- 'Expected startNsQ and endNsQ to differ (' +
- `startNsQ: ${startNsQ}, startNs: ${startNs},` +
- ` endNsQ: ${endNsQ}, durNs: ${durNs},` +
- ` endNs: ${endNs}, bucketNs: ${bucketNs})`);
+ let isInstant = it.isInstant;
+ // Floating point rounding with large numbers of nanoseconds can mean
+ // there isn't enough precision to distinguish the start and end of a very
+ // short event so we just display the event as an instant when zoomed in
+ // rather than fail completely if the start and end time are the same.
+ if (startNsQ === endNsQ) {
+ isInstant = 1;
}
slices.starts[row] = fromNs(startNsQ);
@@ -131,11 +144,11 @@
slices.depths[row] = it.depth;
slices.sliceIds[row] = it.sliceId;
slices.titles[row] = internString(it.name);
- slices.isInstant[row] = it.isInstant;
+ slices.isInstant[row] = isInstant;
slices.isIncomplete[row] = it.isIncomplete;
let cpuTimeRatio = 1;
- if (!it.isInstant && !it.isIncomplete) {
+ if (!isInstant && !it.isIncomplete) {
// Rounding the CPU time ratio to two decimal places and ensuring
// it is less than or equal to one, incase the thread duration exceeds
// the total duration.