Merge changes Icec4b762,I914a7262 into main
* changes:
Trace Redaction - Implement thread merge strategy
Trace Redaction - Expand Synth Threads to Synth Process
diff --git a/Android.bp b/Android.bp
index 5ce71eb..806c554 100644
--- a/Android.bp
+++ b/Android.bp
@@ -12352,6 +12352,7 @@
srcs: [
"src/trace_processor/importers/proto/additional_modules.cc",
"src/trace_processor/importers/proto/android_camera_event_module.cc",
+ "src/trace_processor/importers/proto/android_input_event_module.cc",
"src/trace_processor/importers/proto/android_probes_module.cc",
"src/trace_processor/importers/proto/android_probes_parser.cc",
"src/trace_processor/importers/proto/android_probes_tracker.cc",
diff --git a/BUILD b/BUILD
index 3505021..17f6aef 100644
--- a/BUILD
+++ b/BUILD
@@ -1803,6 +1803,8 @@
"src/trace_processor/importers/proto/additional_modules.h",
"src/trace_processor/importers/proto/android_camera_event_module.cc",
"src/trace_processor/importers/proto/android_camera_event_module.h",
+ "src/trace_processor/importers/proto/android_input_event_module.cc",
+ "src/trace_processor/importers/proto/android_input_event_module.h",
"src/trace_processor/importers/proto/android_probes_module.cc",
"src/trace_processor/importers/proto/android_probes_module.h",
"src/trace_processor/importers/proto/android_probes_parser.cc",
diff --git a/include/perfetto/trace_processor/ref_counted.h b/include/perfetto/trace_processor/ref_counted.h
index bbe2c67..9a69399 100644
--- a/include/perfetto/trace_processor/ref_counted.h
+++ b/include/perfetto/trace_processor/ref_counted.h
@@ -144,8 +144,8 @@
return !(*this == rhs);
}
- bool operator==(nullptr_t) const noexcept { return ptr_ == nullptr; }
- bool operator!=(nullptr_t) const noexcept { return ptr_ != nullptr; }
+ bool operator==(std::nullptr_t) const noexcept { return ptr_ == nullptr; }
+ bool operator!=(std::nullptr_t) const noexcept { return ptr_ != nullptr; }
T* get() const { return ptr_; }
T* operator->() const { return ptr_; }
diff --git a/include/perfetto/tracing/internal/track_event_data_source.h b/include/perfetto/tracing/internal/track_event_data_source.h
index b16a231..70b0dee 100644
--- a/include/perfetto/tracing/internal/track_event_data_source.h
+++ b/include/perfetto/tracing/internal/track_event_data_source.h
@@ -330,14 +330,13 @@
}
static void Flush() {
- Base::template Trace([](typename Base::TraceContext ctx) { ctx.Flush(); });
+ Base::Trace([](typename Base::TraceContext ctx) { ctx.Flush(); });
}
// Determine if *any* tracing category is enabled.
static bool IsEnabled() {
bool enabled = false;
- Base::template CallIfEnabled(
- [&](uint32_t /*instances*/) { enabled = true; });
+ Base::CallIfEnabled([&](uint32_t /*instances*/) { enabled = true; });
return enabled;
}
@@ -351,7 +350,7 @@
static bool IsDynamicCategoryEnabled(
const DynamicCategory& dynamic_category) {
bool enabled = false;
- Base::template Trace([&](typename Base::TraceContext ctx) {
+ Base::Trace([&](typename Base::TraceContext ctx) {
enabled = enabled || IsDynamicCategoryEnabled(&ctx, dynamic_category);
});
return enabled;
@@ -498,7 +497,7 @@
const protos::gen::TrackDescriptor& desc) {
PERFETTO_DCHECK(track.uuid == desc.uuid());
TrackRegistry::Get()->UpdateTrack(track, desc.SerializeAsString());
- Base::template Trace([&](typename Base::TraceContext ctx) {
+ Base::Trace([&](typename Base::TraceContext ctx) {
TrackEventInternal::WriteTrackDescriptor(
track, ctx.tls_inst_->trace_writer.get(), ctx.GetIncrementalState(),
*ctx.GetCustomTlsState(), TrackEventInternal::GetTraceTime());
@@ -1028,7 +1027,7 @@
Lambda lambda) PERFETTO_ALWAYS_INLINE {
using CatTraits = CategoryTraits<CategoryType>;
if (CatTraits::kIsDynamic) {
- Base::template TraceWithInstances(instances, std::move(lambda));
+ Base::TraceWithInstances(instances, std::move(lambda));
} else {
Base::template TraceWithInstances<CategoryTracePointTraits>(
instances, std::move(lambda), {CatTraits::GetStaticIndex(category)});
diff --git a/src/perfetto_cmd/perfetto_cmd.cc b/src/perfetto_cmd/perfetto_cmd.cc
index e11a485..726b266 100644
--- a/src/perfetto_cmd/perfetto_cmd.cc
+++ b/src/perfetto_cmd/perfetto_cmd.cc
@@ -183,30 +183,6 @@
PERFETTO_FATAL("For GCC");
}
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
-// Reports trace with `uuid` being finalized to the trace marker.
-//
-// This reimplements parts of android libcutils, because:
-// * libcutils is not exposed to the NDK and cannot be used from standalone
-// perfetto
-// * libcutils atrace uses properties to enable tags, which are not required in
-// this case.
-void ReportFinalizeTraceUuidToAtrace(const base::Uuid& uuid) {
- base::ScopedFile file =
- base::OpenFile("/sys/kernel/tracing/trace_marker", O_WRONLY);
- if (!file) {
- file = base::OpenFile("/sys/kernel/debug/tracing/trace_marker", O_WRONLY);
- if (!file) {
- return;
- }
- }
- base::StackString<100> uuid_slice("N|%d|OtherTraces|finalize-uuid-%s",
- base::GetProcessId(),
- uuid.ToPrettyString().c_str());
- PERFETTO_EINTR(write(file.get(), uuid_slice.c_str(), uuid_slice.len()));
-}
-#endif // PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
-
void ArgsAppend(std::string* str, const std::string& arg) {
str->append(arg);
str->append("\0", 1);
@@ -1288,12 +1264,6 @@
}
}
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
- // When multiple traces are being recorded at the same time, this is used to
- // correlate one trace with another.
- ReportFinalizeTraceUuidToAtrace(base::Uuid(uuid_));
-#endif
-
tracing_succeeded_ = true;
task_runner_.Quit();
}
diff --git a/src/trace_processor/containers/interval_tree.h b/src/trace_processor/containers/interval_tree.h
index c083da0..64c8c92 100644
--- a/src/trace_processor/containers/interval_tree.h
+++ b/src/trace_processor/containers/interval_tree.h
@@ -17,117 +17,130 @@
#ifndef SRC_TRACE_PROCESSOR_CONTAINERS_INTERVAL_TREE_H_
#define SRC_TRACE_PROCESSOR_CONTAINERS_INTERVAL_TREE_H_
+#include <algorithm>
#include <cstdint>
#include <limits>
+#include <map>
#include <memory>
+#include <optional>
+#include <set>
#include <vector>
+#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/small_vector.h"
namespace perfetto::trace_processor {
-// An implementation of an interval tree data structure, designed to efficiently
-// perform overlap queries on a set of intervals. Used by `interval_intersect`,
-// where one set of intervals (generally the bigger one) has interval tree
-// created based on it, as another queries `FindOverlaps` function for each
-// interval.
-// As interval tree is build on sorted (by `start`) set of N intervals, the
-// complexity of creating a tree goes down from O(N*logN) to O(N) and the
-// created tree is optimally balanced. Each call to `FindOverlaps` is O(logN).
+using Ts = uint64_t;
+using Id = uint32_t;
+
+// An implementation of a centered interval tree data structure, designed to
+// efficiently find all overlap queries on a set of intervals. Centered interval
+// tree has a build complexity of O(N*logN) and a query time of O(logN + k),
+// where k is the number of overlaps in the dataset.
class IntervalTree {
public:
+ // Maps to one trace processor slice.
struct Interval {
- uint32_t start;
- uint32_t end;
- uint32_t id;
+ Ts start;
+ Ts end;
+ Id id;
};
- // Takes vector of sorted intervals.
- explicit IntervalTree(std::vector<Interval>& sorted_intervals) {
- tree_root_ = BuildFromSortedIntervals(
- sorted_intervals, 0, static_cast<int32_t>(sorted_intervals.size() - 1));
+ // Creates an interval tree from the vector of intervals.
+ explicit IntervalTree(const std::vector<Interval>& sorted_intervals) {
+ nodes_.reserve(sorted_intervals.size());
+ Node root_node(sorted_intervals.data(), sorted_intervals.size(), nodes_);
+ nodes_.emplace_back(std::move(root_node));
+ root_ = nodes_.size() - 1;
}
- // Modifies |overlaps| to contain ids of all intervals in the interval tree
- // that overlap with |interval|.
- void FindOverlaps(Interval interval, std::vector<uint32_t>& overlaps) const {
- if (tree_root_) {
- FindOverlaps(*tree_root_, interval, overlaps);
+ // Modifies |res| to contain Interval::Id of all intervals that overlap
+ // interval (s, e). Has a complexity of O(log(size of tree) + (number of
+ // overlaps)).
+ void FindOverlaps(uint64_t s, uint64_t e, std::vector<Id>& res) const {
+ std::vector<const Node*> stack{nodes_.data() + root_};
+ while (!stack.empty()) {
+ const Node* n = stack.back();
+ stack.pop_back();
+
+ for (const Interval& i : n->intervals_) {
+ // As we know that each interval overlaps the center, if the interval
+ // starts after the |end| we know [start,end] can't intersect the
+ // center.
+ if (i.start > e) {
+ break;
+ }
+
+ if (e > i.start && s < i.end) {
+ res.push_back(i.id);
+ }
+ }
+
+ if (e > n->center_ &&
+ n->right_node_ != std::numeric_limits<size_t>::max()) {
+ stack.push_back(&nodes_[n->right_node_]);
+ }
+ if (s < n->center_ &&
+ n->left_node_ != std::numeric_limits<size_t>::max()) {
+ stack.push_back(&nodes_[n->left_node_]);
+ }
}
}
private:
struct Node {
- Interval interval;
- uint32_t max;
- std::unique_ptr<Node> left;
- std::unique_ptr<Node> right;
+ base::SmallVector<Interval, 2> intervals_;
+ uint64_t center_ = 0;
+ size_t left_node_ = std::numeric_limits<size_t>::max();
+ size_t right_node_ = std::numeric_limits<size_t>::max();
- explicit Node(Interval i) : interval(i), max(i.end) {}
+ explicit Node(const Interval* intervals,
+ size_t intervals_size,
+ std::vector<Node>& nodes) {
+ const Interval& mid_interval = intervals[intervals_size / 2];
+ center_ = (mid_interval.start + mid_interval.end) / 2;
+
+ // Find intervals that overlap the center_ and intervals that belong to
+ // the left node (finish before the center_). If an interval starts after
+ // the center break and assign all remining intervals to the right node.
+ // We can do this as the provided intervals are in sorted order.
+ std::vector<Interval> left;
+ for (uint32_t i = 0; i < intervals_size; i++) {
+ const Interval& inter = intervals[i];
+ // Starts after the center. As intervals are sorted on timestamp we
+ // know the rest of intervals will go to the right node.
+ if (inter.start > center_) {
+ Node n(intervals + i, intervals_size - i, nodes);
+ nodes.emplace_back(std::move(n));
+ right_node_ = nodes.size() - 1;
+ break;
+ }
+
+ // Finishes before the center.
+ if (inter.end < center_) {
+ left.push_back(intervals[i]);
+ } else {
+ // Overlaps the center.
+ intervals_.emplace_back(intervals[i]);
+ }
+ }
+
+ if (!left.empty()) {
+ Node n(left.data(), left.size(), nodes);
+ nodes.emplace_back(std::move(n));
+ left_node_ = nodes.size() - 1;
+ }
+ }
+
+ Node(const Node&) = delete;
+ Node& operator=(const Node&) = delete;
+
+ Node(Node&&) = default;
+ Node& operator=(Node&&) = default;
};
- static std::unique_ptr<Node> Insert(std::unique_ptr<Node> root, Interval i) {
- if (root == nullptr) {
- return std::make_unique<Node>(i);
- }
-
- if (i.start < root->interval.start) {
- root->left = Insert(std::move(root->left), i);
- } else {
- root->right = Insert(std::move(root->right), i);
- }
-
- if (root->max < i.end) {
- root->max = i.end;
- }
-
- return root;
- }
-
- static std::unique_ptr<Node> BuildFromSortedIntervals(
- const std::vector<Interval>& is,
- int32_t start,
- int32_t end) {
- // |start == end| happens if there is one element so we need to check for
- // |start > end| that happens in the next recursive call.
- if (start > end) {
- return nullptr;
- }
-
- int32_t mid = start + (end - start) / 2;
- auto node = std::make_unique<Node>(is[static_cast<uint32_t>(mid)]);
-
- node->left = BuildFromSortedIntervals(is, start, mid - 1);
- node->right = BuildFromSortedIntervals(is, mid + 1, end);
-
- uint32_t max_from_children = std::max(
- node->left ? node->left->max : std::numeric_limits<uint32_t>::min(),
- node->right ? node->right->max : std::numeric_limits<uint32_t>::min());
-
- node->max = std::max(node->interval.end, max_from_children);
-
- return node;
- }
-
- static void FindOverlaps(const Node& node,
- const Interval& i,
- std::vector<uint32_t>& overlaps) {
- // Intervals overlap if one starts before the other ends and ends after it
- // starts.
- if (node.interval.start < i.end && node.interval.end > i.start) {
- overlaps.push_back(node.interval.id);
- }
-
- // Try to find overlaps with left.
- if (i.start <= node.interval.start && node.left) {
- FindOverlaps(*node.left, i, overlaps);
- }
-
- // Try to find overlaps with right.
- if (i.start < node.max && node.right) {
- FindOverlaps(*node.right, i, overlaps);
- }
- }
-
- std::unique_ptr<Node> tree_root_;
+ size_t root_;
+ std::vector<Node> nodes_;
};
} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/containers/interval_tree_unittest.cc b/src/trace_processor/containers/interval_tree_unittest.cc
index 61023d4..ed4509b 100644
--- a/src/trace_processor/containers/interval_tree_unittest.cc
+++ b/src/trace_processor/containers/interval_tree_unittest.cc
@@ -54,7 +54,7 @@
std::vector<Interval> interval({{10, 20, 5}});
IntervalTree tree(interval);
std::vector<uint32_t> overlaps;
- tree.FindOverlaps({5, 30, 0}, overlaps);
+ tree.FindOverlaps(5, 30, overlaps);
ASSERT_THAT(overlaps, UnorderedElementsAre(5));
}
@@ -63,7 +63,7 @@
auto intervals = CreateIntervals({{0, 10}, {5, 20}, {30, 40}});
IntervalTree tree(intervals);
std::vector<uint32_t> overlaps;
- tree.FindOverlaps({4, 30, 0}, overlaps);
+ tree.FindOverlaps(4, 30, overlaps);
ASSERT_THAT(overlaps, UnorderedElementsAre(0, 1));
}
@@ -74,13 +74,13 @@
std::vector<uint32_t> overlaps;
// Overlaps at the start point only
- tree.FindOverlaps({10, 10, 0}, overlaps);
+ tree.FindOverlaps(10, 10, overlaps);
ASSERT_THAT(overlaps, IsEmpty());
overlaps.clear();
// Overlaps at the end point only
- tree.FindOverlaps({20, 20, 0}, overlaps);
+ tree.FindOverlaps(20, 20, overlaps);
ASSERT_THAT(overlaps, IsEmpty());
}
@@ -90,17 +90,17 @@
std::vector<uint32_t> overlaps;
// Before all intervals
- tree.FindOverlaps({5, 9, 0}, overlaps);
+ tree.FindOverlaps(5, 9, overlaps);
ASSERT_THAT(overlaps, IsEmpty());
overlaps.clear();
// Between intervals
- tree.FindOverlaps({21, 29, 0}, overlaps);
+ tree.FindOverlaps(21, 29, overlaps);
ASSERT_THAT(overlaps, IsEmpty());
overlaps.clear();
// After all intervals
- tree.FindOverlaps({41, 50, 0}, overlaps);
+ tree.FindOverlaps(41, 50, overlaps);
ASSERT_THAT(overlaps, IsEmpty());
}
@@ -108,7 +108,7 @@
auto intervals = CreateIntervals({{10, 20}, {10, 20}});
IntervalTree tree(intervals);
std::vector<uint32_t> overlaps;
- tree.FindOverlaps({10, 20, 0}, overlaps);
+ tree.FindOverlaps(10, 20, overlaps);
ASSERT_THAT(overlaps, UnorderedElementsAre(0, 1));
}
@@ -118,17 +118,17 @@
std::vector<uint32_t> overlaps;
/// Starts before, ends within
- tree.FindOverlaps({9, 11, 0}, overlaps);
+ tree.FindOverlaps(9, 11, overlaps);
ASSERT_THAT(overlaps, UnorderedElementsAre(0, 1));
overlaps.clear();
// Starts within, ends within
- tree.FindOverlaps({13, 21, 0}, overlaps);
- ASSERT_THAT(overlaps, UnorderedElementsAre(1, 2));
+ tree.FindOverlaps(13, 21, overlaps);
+ ASSERT_THAT(overlaps, UnorderedElementsAre(0, 1, 2));
overlaps.clear();
// Starts within, ends after
- tree.FindOverlaps({18, 26, 0}, overlaps);
+ tree.FindOverlaps(18, 26, overlaps);
ASSERT_THAT(overlaps, UnorderedElementsAre(1, 2, 3));
}
@@ -137,7 +137,7 @@
IntervalTree tree(intervals);
std::vector<uint32_t> overlaps;
- tree.FindOverlaps({19, 21, 0}, overlaps);
+ tree.FindOverlaps(19, 21, overlaps);
ASSERT_THAT(overlaps, UnorderedElementsAre(0, 1));
}
@@ -153,10 +153,9 @@
{prev_max, prev_max + (static_cast<uint32_t>(rng()) % 100)});
}
auto intervals = CreateIntervals(periods);
- Interval query_i{periods.front().first, periods.back().first + 1, 5};
IntervalTree tree(intervals);
std::vector<uint32_t> overlaps;
- tree.FindOverlaps(query_i, overlaps);
+ tree.FindOverlaps(periods.front().first, periods.back().first + 1, overlaps);
EXPECT_EQ(overlaps.size(), kCount);
}
diff --git a/src/trace_processor/importers/common/args_tracker.h b/src/trace_processor/importers/common/args_tracker.h
index 8b8d35c..e72b7c9 100644
--- a/src/trace_processor/importers/common/args_tracker.h
+++ b/src/trace_processor/importers/common/args_tracker.h
@@ -164,6 +164,21 @@
id);
}
+ BoundInserter AddArgsTo(tables::AndroidKeyEventsTable::Id id) {
+ return AddArgsTo(context_->storage->mutable_android_key_events_table(),
+ id);
+ }
+
+ BoundInserter AddArgsTo(tables::AndroidMotionEventsTable::Id id) {
+ return AddArgsTo(context_->storage->mutable_android_motion_events_table(),
+ id);
+ }
+
+ BoundInserter AddArgsTo(tables::AndroidInputEventDispatchTable::Id id) {
+ return AddArgsTo(
+ context_->storage->mutable_android_input_event_dispatch_table(), id);
+ }
+
BoundInserter AddArgsTo(MetadataId id) {
auto* table = context_->storage->mutable_metadata_table();
uint32_t row = *table->id().IndexOf(id);
diff --git a/src/trace_processor/importers/proto/BUILD.gn b/src/trace_processor/importers/proto/BUILD.gn
index b92bed6..effda82 100644
--- a/src/trace_processor/importers/proto/BUILD.gn
+++ b/src/trace_processor/importers/proto/BUILD.gn
@@ -115,6 +115,8 @@
"additional_modules.h",
"android_camera_event_module.cc",
"android_camera_event_module.h",
+ "android_input_event_module.cc",
+ "android_input_event_module.h",
"android_probes_module.cc",
"android_probes_module.h",
"android_probes_parser.cc",
diff --git a/src/trace_processor/importers/proto/additional_modules.cc b/src/trace_processor/importers/proto/additional_modules.cc
index 91645c3..cf2ee84 100644
--- a/src/trace_processor/importers/proto/additional_modules.cc
+++ b/src/trace_processor/importers/proto/additional_modules.cc
@@ -18,6 +18,7 @@
#include "src/trace_processor/importers/etw/etw_module_impl.h"
#include "src/trace_processor/importers/ftrace/ftrace_module_impl.h"
#include "src/trace_processor/importers/proto/android_camera_event_module.h"
+#include "src/trace_processor/importers/proto/android_input_event_module.h"
#include "src/trace_processor/importers/proto/android_probes_module.h"
#include "src/trace_processor/importers/proto/graphics_event_module.h"
#include "src/trace_processor/importers/proto/heap_graph_module.h"
@@ -45,6 +46,7 @@
context->modules.emplace_back(new MetadataModule(context));
context->modules.emplace_back(new V8Module(context));
context->modules.emplace_back(new WinscopeModule(context));
+ context->modules.emplace_back(new AndroidInputEventModule(context));
// Ftrace/Etw modules are special, because it has one extra method for parsing
// ftrace/etw packets. So we need to store a pointer to it separately.
diff --git a/src/trace_processor/importers/proto/android_input_event_module.cc b/src/trace_processor/importers/proto/android_input_event_module.cc
new file mode 100644
index 0000000..26671a7
--- /dev/null
+++ b/src/trace_processor/importers/proto/android_input_event_module.cc
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2024 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 "src/trace_processor/importers/proto/android_input_event_module.h"
+
+#include "protos/perfetto/trace/android/android_input_event.pbzero.h"
+#include "src/trace_processor/importers/common/args_tracker.h"
+#include "src/trace_processor/importers/proto/args_parser.h"
+#include "src/trace_processor/importers/proto/trace.descriptor.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/android_tables_py.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto::trace_processor {
+
+using perfetto::protos::pbzero::AndroidInputEvent;
+using perfetto::protos::pbzero::AndroidKeyEvent;
+using perfetto::protos::pbzero::AndroidMotionEvent;
+using perfetto::protos::pbzero::AndroidWindowInputDispatchEvent;
+using perfetto::protos::pbzero::TracePacket;
+
+AndroidInputEventModule::AndroidInputEventModule(TraceProcessorContext* context)
+ : context_(*context), args_parser_(pool_) {
+ pool_.AddFromFileDescriptorSet(kTraceDescriptor.data(),
+ kTraceDescriptor.size());
+ RegisterForField(TracePacket::kAndroidInputEventFieldNumber, context);
+}
+
+void AndroidInputEventModule::ParseTracePacketData(
+ const TracePacket::Decoder& decoder,
+ int64_t packet_ts,
+ const TracePacketData&,
+ uint32_t field_id) {
+ if (field_id != TracePacket::kAndroidInputEventFieldNumber)
+ return;
+
+ auto input_event = AndroidInputEvent::Decoder(decoder.android_input_event());
+
+ constexpr static auto supported_fields = std::array{
+ AndroidInputEvent::kDispatcherMotionEventFieldNumber,
+ AndroidInputEvent::kDispatcherMotionEventRedactedFieldNumber,
+ AndroidInputEvent::kDispatcherKeyEventFieldNumber,
+ AndroidInputEvent::kDispatcherKeyEventRedactedFieldNumber,
+ AndroidInputEvent::kDispatcherWindowDispatchEventFieldNumber,
+ AndroidInputEvent::kDispatcherWindowDispatchEventRedactedFieldNumber};
+
+ for (auto sub_field_id : supported_fields) {
+ auto sub_field = input_event.Get(static_cast<uint32_t>(sub_field_id));
+ if (!sub_field.valid())
+ continue;
+
+ switch (sub_field_id) {
+ case AndroidInputEvent::kDispatcherMotionEventFieldNumber:
+ case AndroidInputEvent::kDispatcherMotionEventRedactedFieldNumber:
+ ParseMotionEvent(packet_ts, sub_field.as_bytes());
+ return;
+ case AndroidInputEvent::kDispatcherKeyEventFieldNumber:
+ case AndroidInputEvent::kDispatcherKeyEventRedactedFieldNumber:
+ ParseKeyEvent(packet_ts, sub_field.as_bytes());
+ return;
+ case AndroidInputEvent::kDispatcherWindowDispatchEventFieldNumber:
+ case AndroidInputEvent::kDispatcherWindowDispatchEventRedactedFieldNumber:
+ ParseWindowDispatchEvent(packet_ts, sub_field.as_bytes());
+ return;
+ }
+ }
+}
+
+void AndroidInputEventModule::ParseMotionEvent(
+ int64_t packet_ts,
+ const protozero::ConstBytes& bytes) {
+ AndroidMotionEvent::Decoder event_proto(bytes);
+ tables::AndroidMotionEventsTable::Row event_row;
+ event_row.event_id = event_proto.event_id();
+ event_row.ts = event_proto.event_time_nanos();
+
+ auto event_row_id = context_.storage->mutable_android_motion_events_table()
+ ->Insert(event_row)
+ .id;
+ auto inserter = context_.args_tracker->AddArgsTo(event_row_id);
+ ArgsParser writer{packet_ts, inserter, *context_.storage};
+
+ base::Status status =
+ args_parser_.ParseMessage(bytes, ".perfetto.protos.AndroidMotionEvent",
+ nullptr /*parse all fields*/, writer);
+ if (!status.ok())
+ context_.storage->IncrementStats(stats::android_input_event_parse_errors);
+}
+
+void AndroidInputEventModule::ParseKeyEvent(
+ int64_t packet_ts,
+ const protozero::ConstBytes& bytes) {
+ AndroidKeyEvent::Decoder event_proto(bytes);
+ tables::AndroidKeyEventsTable::Row event_row;
+ event_row.event_id = event_proto.event_id();
+ event_row.ts = event_proto.event_time_nanos();
+
+ auto event_row_id = context_.storage->mutable_android_key_events_table()
+ ->Insert(event_row)
+ .id;
+ auto inserter = context_.args_tracker->AddArgsTo(event_row_id);
+ ArgsParser writer{packet_ts, inserter, *context_.storage};
+
+ base::Status status =
+ args_parser_.ParseMessage(bytes, ".perfetto.protos.AndroidKeyEvent",
+ nullptr /*parse all fields*/, writer);
+ if (!status.ok())
+ context_.storage->IncrementStats(stats::android_input_event_parse_errors);
+}
+
+void AndroidInputEventModule::ParseWindowDispatchEvent(
+ int64_t packet_ts,
+ const protozero::ConstBytes& bytes) {
+ AndroidWindowInputDispatchEvent::Decoder event_proto(bytes);
+ tables::AndroidInputEventDispatchTable::Row event_row;
+ event_row.event_id = event_proto.event_id();
+ event_row.vsync_id = event_proto.vsync_id();
+ event_row.window_id = event_proto.window_id();
+
+ auto event_row_id =
+ context_.storage->mutable_android_input_event_dispatch_table()
+ ->Insert(event_row)
+ .id;
+ auto inserter = context_.args_tracker->AddArgsTo(event_row_id);
+ ArgsParser writer{packet_ts, inserter, *context_.storage};
+
+ base::Status status = args_parser_.ParseMessage(
+ bytes, ".perfetto.protos.AndroidWindowInputDispatchEvent",
+ nullptr /*parse all fields*/, writer);
+ if (!status.ok())
+ context_.storage->IncrementStats(stats::android_input_event_parse_errors);
+}
+
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/proto/android_input_event_module.h b/src/trace_processor/importers/proto/android_input_event_module.h
new file mode 100644
index 0000000..c310909
--- /dev/null
+++ b/src/trace_processor/importers/proto/android_input_event_module.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 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_IMPORTERS_PROTO_ANDROID_INPUT_EVENT_MODULE_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_ANDROID_INPUT_EVENT_MODULE_H_
+
+#include <cstdint>
+#include "perfetto/base/build_config.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+#include "src/trace_processor/importers/common/parser_types.h"
+#include "src/trace_processor/importers/proto/proto_importer_module.h"
+#include "src/trace_processor/util/descriptors.h"
+#include "src/trace_processor/util/proto_to_args_parser.h"
+
+namespace perfetto::trace_processor {
+
+class AndroidInputEventModule : public ProtoImporterModule {
+ public:
+ explicit AndroidInputEventModule(TraceProcessorContext* context);
+
+ void ParseTracePacketData(const protos::pbzero::TracePacket::Decoder&,
+ int64_t packet_ts,
+ const TracePacketData&,
+ uint32_t field_id) override;
+
+ private:
+ TraceProcessorContext& context_;
+ DescriptorPool pool_;
+ util::ProtoToArgsParser args_parser_;
+
+ void ParseMotionEvent(int64_t packet_ts, const protozero::ConstBytes& bytes);
+ void ParseKeyEvent(int64_t packet_ts, const protozero::ConstBytes& bytes);
+ void ParseWindowDispatchEvent(int64_t packet_ts, const protozero::ConstBytes& bytes);
+};
+
+} // namespace perfetto::trace_processor
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_ANDROID_INPUT_EVENT_MODULE_H_
diff --git a/src/trace_processor/importers/proto/proto_trace_reader.cc b/src/trace_processor/importers/proto/proto_trace_reader.cc
index d958c0b..7f8277f 100644
--- a/src/trace_processor/importers/proto/proto_trace_reader.cc
+++ b/src/trace_processor/importers/proto/proto_trace_reader.cc
@@ -233,6 +233,13 @@
return util::OkStatus();
}
timestamp = trace_ts.value();
+ // TODO(lalitm): Ideally this guard is implemented in TraceSorter
+ // to allow for checking of other trace formats
+ if (timestamp < 0) {
+ return base::ErrStatus("Failed due to negative timestamp, %" PRId64
+ "is negative.",
+ timestamp);
+ }
}
} else {
timestamp = std::max(latest_timestamp_, context_->sorter->max_timestamp());
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.cc b/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.cc
index feaa228..82ed018 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.cc
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.cc
@@ -16,18 +16,84 @@
#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.h"
+#include <cstdint>
#include <optional>
+#include <string>
+#include <unordered_map>
#include <unordered_set>
#include <utility>
+#include "perfetto/base/logging.h"
#include "perfetto/base/status.h"
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "perfetto/ext/base/status_or.h"
#include "perfetto/ext/base/string_utils.h"
#include "src/trace_processor/sqlite/sql_source.h"
#include "src/trace_processor/sqlite/sqlite_tokenizer.h"
#include "src/trace_processor/util/status_macros.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
+namespace {
+
+base::Status ErrorAtToken(const SqliteTokenizer& tokenizer,
+ const SqliteTokenizer::Token& token,
+ const char* error) {
+ std::string traceback = tokenizer.AsTraceback(token);
+ return base::ErrStatus("%s%s", traceback.c_str(), error);
+}
+
+struct InvocationArg {
+ std::optional<SqlSource> arg;
+ bool has_more;
+};
+
+base::StatusOr<InvocationArg> ParseMacroInvocationArg(
+ SqliteTokenizer& tokenizer,
+ SqliteTokenizer::Token& tok,
+ bool has_prev_args) {
+ uint32_t nested_parens = 0;
+ bool seen_token_in_arg = false;
+ auto start = tokenizer.NextNonWhitespace();
+ for (tok = start;; tok = tokenizer.NextNonWhitespace()) {
+ if (tok.IsTerminal()) {
+ if (tok.token_type == SqliteTokenType::TK_SEMI) {
+ // TODO(b/290185551): add a link to macro documentation.
+ return ErrorAtToken(tokenizer, tok,
+ "Semi-colon is not allowed in macro invocation");
+ }
+ // TODO(b/290185551): add a link to macro documentation.
+ return ErrorAtToken(tokenizer, tok, "Macro invocation not complete");
+ }
+
+ bool is_arg_terminator = tok.token_type == SqliteTokenType::TK_RP ||
+ tok.token_type == SqliteTokenType::TK_COMMA;
+ if (nested_parens == 0 && is_arg_terminator) {
+ bool token_required =
+ has_prev_args || tok.token_type != SqliteTokenType::TK_RP;
+ if (!seen_token_in_arg && token_required) {
+ // TODO(b/290185551): add a link to macro documentation.
+ return ErrorAtToken(tokenizer, tok, "Macro arg is empty");
+ }
+ return InvocationArg{
+ seen_token_in_arg ? std::make_optional(tokenizer.Substr(start, tok))
+ : std::optional<SqlSource>(std::nullopt),
+ tok.token_type == SqliteTokenType::TK_COMMA,
+ };
+ }
+ seen_token_in_arg = true;
+
+ if (tok.token_type == SqliteTokenType::TK_LP) {
+ nested_parens++;
+ continue;
+ }
+ if (tok.token_type == SqliteTokenType::TK_RP) {
+ nested_parens--;
+ continue;
+ }
+ }
+}
+
+} // namespace
PerfettoSqlPreprocessor::PerfettoSqlPreprocessor(
SqlSource source,
@@ -165,62 +231,7 @@
"Macro invoked with too few args");
}
PERFETTO_CHECK(inner_bindings.size() == macro->args.size());
- return MacroInvocation{macro, inner_bindings};
+ return MacroInvocation{macro, std::move(inner_bindings)};
}
-base::StatusOr<PerfettoSqlPreprocessor::InvocationArg>
-PerfettoSqlPreprocessor::ParseMacroInvocationArg(SqliteTokenizer& tokenizer,
- SqliteTokenizer::Token& tok,
- bool has_prev_args) {
- uint32_t nested_parens = 0;
- bool seen_token_in_arg = false;
- auto start = tokenizer.NextNonWhitespace();
- for (tok = start;; tok = tokenizer.NextNonWhitespace()) {
- if (tok.IsTerminal()) {
- if (tok.token_type == SqliteTokenType::TK_SEMI) {
- // TODO(b/290185551): add a link to macro documentation.
- return ErrorAtToken(tokenizer, tok,
- "Semi-colon is not allowed in macro invocation");
- }
- // TODO(b/290185551): add a link to macro documentation.
- return ErrorAtToken(tokenizer, tok, "Macro invocation not complete");
- }
-
- bool is_arg_terminator = tok.token_type == SqliteTokenType::TK_RP ||
- tok.token_type == SqliteTokenType::TK_COMMA;
- if (nested_parens == 0 && is_arg_terminator) {
- bool token_required =
- has_prev_args || tok.token_type != SqliteTokenType::TK_RP;
- if (!seen_token_in_arg && token_required) {
- // TODO(b/290185551): add a link to macro documentation.
- return ErrorAtToken(tokenizer, tok, "Macro arg is empty");
- }
- return InvocationArg{
- seen_token_in_arg ? std::make_optional(tokenizer.Substr(start, tok))
- : std::optional<SqlSource>(std::nullopt),
- tok.token_type == SqliteTokenType::TK_COMMA,
- };
- }
- seen_token_in_arg = true;
-
- if (tok.token_type == SqliteTokenType::TK_LP) {
- nested_parens++;
- continue;
- }
- if (tok.token_type == SqliteTokenType::TK_RP) {
- nested_parens--;
- continue;
- }
- }
-}
-
-base::Status PerfettoSqlPreprocessor::ErrorAtToken(
- const SqliteTokenizer& tokenizer,
- const SqliteTokenizer::Token& token,
- const char* error) {
- std::string traceback = tokenizer.AsTraceback(token);
- return base::ErrStatus("%s%s", traceback.c_str(), error);
-}
-
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.h b/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.h
index a1d15e3..17681de 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.h
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.h
@@ -23,13 +23,13 @@
#include <unordered_set>
#include <vector>
+#include "perfetto/base/status.h"
#include "perfetto/ext/base/flat_hash_map.h"
#include "perfetto/ext/base/status_or.h"
#include "src/trace_processor/sqlite/sql_source.h"
#include "src/trace_processor/sqlite/sqlite_tokenizer.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
// Preprocessor for PerfettoSQL statements. The main responsiblity of this
// class is to perform similar functions to the C/C++ preprocessor (e.g.
@@ -72,14 +72,7 @@
const Macro* macro;
std::unordered_map<std::string, SqlSource> arg_bindings;
};
- struct InvocationArg {
- std::optional<SqlSource> arg;
- bool has_more;
- };
- base::Status ErrorAtToken(const SqliteTokenizer& tokenizer,
- const SqliteTokenizer::Token& token,
- const char* error);
base::StatusOr<SqlSource> RewriteInternal(
const SqlSource&,
const std::unordered_map<std::string, SqlSource>& arg_bindings);
@@ -89,10 +82,6 @@
SqliteTokenizer::Token& token,
const SqliteTokenizer::Token& name_token,
const std::unordered_map<std::string, SqlSource>& arg_bindings);
- base::StatusOr<InvocationArg> ParseMacroInvocationArg(
- SqliteTokenizer& tokenizer,
- SqliteTokenizer::Token& token,
- bool has_prev_args);
SqliteTokenizer global_tokenizer_;
const base::FlatHashMap<std::string, Macro>* macros_ = nullptr;
@@ -101,7 +90,6 @@
base::Status status_;
};
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
#endif // SRC_TRACE_PROCESSOR_PERFETTO_SQL_ENGINE_PERFETTO_SQL_PREPROCESSOR_H_
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/input.sql b/src/trace_processor/perfetto_sql/stdlib/android/input.sql
index a1d7b11..5ac6c74 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/input.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/input.sql
@@ -130,3 +130,62 @@
AND dispatch.event_seq = finish.event_seq
JOIN (SELECT * FROM _input_message_received WHERE event_type = '0x2') finish_ack
ON finish_ack.event_channel = dispatch.event_channel AND dispatch.event_seq = finish_ack.event_seq;
+
+-- Key events processed by the Android framework (from android.input.inputevent data source).
+CREATE PERFETTO VIEW android_key_events(
+ -- ID of the trace entry
+ id INT,
+ -- The randomly-generated ID associated with each input event processed
+ -- by Android Framework, used to track the event through the input pipeline
+ event_id INT,
+ -- The timestamp associated with the input event
+ ts INT,
+ -- Details of the input event parsed from the proto message
+ arg_set_id INT
+) AS
+SELECT
+ id,
+ event_id,
+ ts,
+ arg_set_id
+FROM __intrinsic_android_key_events;
+
+-- Motion events processed by the Android framework (from android.input.inputevent data source).
+CREATE PERFETTO VIEW android_motion_events(
+ -- ID of the trace entry
+ id INT,
+ -- The randomly-generated ID associated with each input event processed
+ -- by Android Framework, used to track the event through the input pipeline
+ event_id INT,
+ -- The timestamp associated with the input event
+ ts INT,
+ -- Details of the input event parsed from the proto message
+ arg_set_id INT
+) AS
+SELECT
+ id,
+ event_id,
+ ts,
+ arg_set_id
+FROM __intrinsic_android_motion_events;
+
+-- Input event dispatching information in Android (from android.input.inputevent data source).
+CREATE PERFETTO VIEW android_input_event_dispatch(
+ -- ID of the trace entry
+ id INT,
+ -- Event ID of the input event that was dispatched
+ event_id INT,
+ -- Extra args parsed from the proto message
+ arg_set_id INT,
+ -- Vsync ID that identifies the state of the windows during which the dispatch decision was made
+ vsync_id INT,
+ -- Window ID of the window receiving the event
+ window_id INT
+) AS
+SELECT
+ id,
+ event_id,
+ arg_set_id,
+ vsync_id,
+ window_id
+FROM __intrinsic_android_input_event_dispatch;
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/power_rails.sql b/src/trace_processor/perfetto_sql/stdlib/android/power_rails.sql
index 6630c53..f9b018c 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/power_rails.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/power_rails.sql
@@ -22,7 +22,8 @@
ts INT,
-- Power rail name. From `counter_track.name`.
power_rail_name INT,
- -- Power rails counter value in micro watts.
+ -- The energy accumulated by this rail since boot in microwatt-seconds
+ -- (uWs) (AKA micro-joules).
value DOUBLE
)
AS
@@ -33,4 +34,4 @@
c.value
FROM counter c
JOIN counter_track t ON c.track_id = t.id
-WHERE name GLOB 'power.*';
\ No newline at end of file
+WHERE name GLOB 'power.*';
diff --git a/src/trace_processor/storage/stats.h b/src/trace_processor/storage/stats.h
index 6238455..057c2fa 100644
--- a/src/trace_processor/storage/stats.h
+++ b/src/trace_processor/storage/stats.h
@@ -363,7 +363,11 @@
F(ftrace_missing_event_id, kSingle, kInfo, kAnalysis, \
"Indicates that the ftrace event was dropped because the event id was " \
"missing. This is an 'info' stat rather than an error stat because " \
- "this can be legitimately missing due to proto filtering.")
+ "this can be legitimately missing due to proto filtering."), \
+ F(android_input_event_parse_errors, kSingle, kInfo, kAnalysis, \
+ "Android input event packet has unknown fields, which results " \
+ "in some arguments missing. You may need a newer version of trace " \
+ "processor to parse them.")
// clang-format on
enum Type {
diff --git a/src/trace_processor/storage/trace_storage.h b/src/trace_processor/storage/trace_storage.h
index 734fd0d..61da6d9 100644
--- a/src/trace_processor/storage/trace_storage.h
+++ b/src/trace_processor/storage/trace_storage.h
@@ -514,6 +514,29 @@
return &android_dumpstate_table_;
}
+ const tables::AndroidKeyEventsTable& android_key_events_table() const {
+ return android_key_events_table_;
+ }
+ tables::AndroidKeyEventsTable* mutable_android_key_events_table() {
+ return &android_key_events_table_;
+ }
+
+ const tables::AndroidMotionEventsTable& android_motion_events_table() const {
+ return android_motion_events_table_;
+ }
+ tables::AndroidMotionEventsTable* mutable_android_motion_events_table() {
+ return &android_motion_events_table_;
+ }
+
+ const tables::AndroidInputEventDispatchTable&
+ android_input_event_dispatch_table() const {
+ return android_input_event_dispatch_table_;
+ }
+ tables::AndroidInputEventDispatchTable*
+ mutable_android_input_event_dispatch_table() {
+ return &android_input_event_dispatch_table_;
+ }
+
const StatsMap& stats() const { return stats_; }
const tables::MetadataTable& metadata_table() const {
@@ -1097,6 +1120,11 @@
tables::AndroidDumpstateTable android_dumpstate_table_{&string_pool_};
+ tables::AndroidKeyEventsTable android_key_events_table_{&string_pool_};
+ tables::AndroidMotionEventsTable android_motion_events_table_{&string_pool_};
+ tables::AndroidInputEventDispatchTable
+ android_input_event_dispatch_table_{&string_pool_};
+
tables::StackProfileMappingTable stack_profile_mapping_table_{&string_pool_};
tables::StackProfileFrameTable stack_profile_frame_table_{&string_pool_};
tables::StackProfileCallsiteTable stack_profile_callsite_table_{
diff --git a/src/trace_processor/tables/android_tables.py b/src/trace_processor/tables/android_tables.py
index 88f2592..ba200ef 100644
--- a/src/trace_processor/tables/android_tables.py
+++ b/src/trace_processor/tables/android_tables.py
@@ -14,11 +14,11 @@
"""Contains tables for relevant for Android."""
from python.generators.trace_processor_table.public import Column as C
+from python.generators.trace_processor_table.public import ColumnDoc
from python.generators.trace_processor_table.public import CppDouble
from python.generators.trace_processor_table.public import CppInt32
from python.generators.trace_processor_table.public import CppInt64
from python.generators.trace_processor_table.public import CppOptional
-from python.generators.trace_processor_table.public import CppSelfTableId
from python.generators.trace_processor_table.public import CppString
from python.generators.trace_processor_table.public import Table
from python.generators.trace_processor_table.public import TableDoc
@@ -156,9 +156,100 @@
'''
}))
+ANDROID_MOTION_EVENTS_TABLE = Table(
+ python_module=__file__,
+ class_name='AndroidMotionEventsTable',
+ sql_name='__intrinsic_android_motion_events',
+ columns=[
+ C('event_id', CppUint32()),
+ C('ts', CppInt64()),
+ C('arg_set_id', CppUint32()),
+ ],
+ tabledoc=TableDoc(
+ doc='Contains Android MotionEvents processed by the system',
+ group='Android',
+ columns={
+ 'event_id':
+ '''
+ The randomly-generated ID associated with each input event processed
+ by Android Framework, used to track the event through the input pipeline.
+ ''',
+ 'ts':
+ '''The timestamp associated with the input event.''',
+ 'arg_set_id':
+ ColumnDoc(
+ doc='Details of the motion event parsed from the proto message.',
+ joinable='args.arg_set_id'),
+ }))
+
+ANDROID_KEY_EVENTS_TABLE = Table(
+ python_module=__file__,
+ class_name='AndroidKeyEventsTable',
+ sql_name='__intrinsic_android_key_events',
+ columns=[
+ C('event_id', CppUint32()),
+ C('ts', CppInt64()),
+ C('arg_set_id', CppUint32()),
+ ],
+ tabledoc=TableDoc(
+ doc='Contains Android KeyEvents processed by the system',
+ group='Android',
+ columns={
+ 'event_id':
+ '''
+ The randomly-generated ID associated with each input event processed
+ by Android Framework, used to track the event through the input pipeline.
+ ''',
+ 'ts':
+ '''The timestamp associated with the input event.''',
+ 'arg_set_id':
+ ColumnDoc(
+ doc='Details of the key event parsed from the proto message.',
+ joinable='args.arg_set_id'),
+ }))
+
+ANDROID_INPUT_EVENT_DISPATCH_TABLE = Table(
+ python_module=__file__,
+ class_name='AndroidInputEventDispatchTable',
+ sql_name='__intrinsic_android_input_event_dispatch',
+ columns=[
+ C('event_id', CppUint32()),
+ C('arg_set_id', CppUint32()),
+ C('vsync_id', CppInt64()),
+ C('window_id', CppInt32()),
+ ],
+ tabledoc=TableDoc(
+ doc=
+ '''
+ Contains records of Android input events being dispatched to input windows
+ by the Android Framework.
+ ''',
+ group='Android',
+ columns={
+ 'event_id':
+ ColumnDoc(
+ doc='The id of the input event that was dispatched.',
+ joinable='__intrinsic_android_motion_events.event_id'),
+ 'arg_set_id':
+ ColumnDoc(
+ doc='Details of the dispatched event parsed from the proto message.',
+ joinable='args.arg_set_id'),
+ 'vsync_id':
+ '''
+ The id of the vsync during which the Framework made the decision to
+ dispatch this input event, used to identify the state of the input windows
+ when the dispatching decision was made.
+ ''',
+ 'window_id':
+ 'The id of the window to which the event was dispatched.',
+ }))
+
# Keep this list sorted.
ALL_TABLES = [
ANDROID_LOG_TABLE,
ANDROID_DUMPSTATE_TABLE,
ANDROID_GAME_INTERVENTION_LIST_TABLE,
+ ANDROID_KEY_EVENTS_TABLE,
+ ANDROID_MOTION_EVENTS_TABLE,
+ ANDROID_INPUT_EVENT_DISPATCH_TABLE,
]
diff --git a/src/trace_processor/tables/table_destructors.cc b/src/trace_processor/tables/table_destructors.cc
index 8339b48..067cefa 100644
--- a/src/trace_processor/tables/table_destructors.cc
+++ b/src/trace_processor/tables/table_destructors.cc
@@ -40,6 +40,9 @@
AndroidDumpstateTable::~AndroidDumpstateTable() = default;
AndroidGameInterventionListTable::~AndroidGameInterventionListTable() = default;
AndroidLogTable::~AndroidLogTable() = default;
+AndroidKeyEventsTable::~AndroidKeyEventsTable() = default;
+AndroidMotionEventsTable::~AndroidMotionEventsTable() = default;
+AndroidInputEventDispatchTable::~AndroidInputEventDispatchTable() = default;
// counter_tables_py.h
CounterTable::~CounterTable() = default;
diff --git a/src/trace_processor/trace_database_integrationtest.cc b/src/trace_processor/trace_database_integrationtest.cc
index 731f4e3..b466399 100644
--- a/src/trace_processor/trace_database_integrationtest.cc
+++ b/src/trace_processor/trace_database_integrationtest.cc
@@ -811,5 +811,11 @@
ASSERT_THAT(it.Status().message(), HasSubstr("duplicate_b"));
}
+TEST_F(TraceProcessorIntegrationTest, NegativeTimestampError) {
+ base::Status status = LoadTrace("trace_with_negative_timestamp", 4096);
+ ASSERT_FALSE(status.ok());
+ ASSERT_THAT(status.message(), HasSubstr("negative timestamp"));
+}
+
} // namespace
} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 7eac7b7..00f87bf 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -865,6 +865,9 @@
RegisterStaticTable(storage->android_log_table());
RegisterStaticTable(storage->android_dumpstate_table());
RegisterStaticTable(storage->android_game_intervention_list_table());
+ RegisterStaticTable(storage->android_key_events_table());
+ RegisterStaticTable(storage->android_motion_events_table());
+ RegisterStaticTable(storage->android_input_event_dispatch_table());
RegisterStaticTable(storage->vulkan_memory_allocations_table());
diff --git a/src/tracing/service/tracing_service_impl.cc b/src/tracing/service/tracing_service_impl.cc
index 6631d68..0a06023 100644
--- a/src/tracing/service/tracing_service_impl.cc
+++ b/src/tracing/service/tracing_service_impl.cc
@@ -496,8 +496,6 @@
PERFETTO_DLOG("Consumer %p disconnected", reinterpret_cast<void*>(consumer));
PERFETTO_DCHECK(consumers_.count(consumer));
- // TODO(primiano) : Check that this is safe (what happens if there are
- // ReadBuffers() calls posted in the meantime? They need to become noop).
if (consumer->tracing_session_id_)
FreeBuffers(consumer->tracing_session_id_); // Will also DisableTracing().
consumers_.erase(consumer);
@@ -2644,10 +2642,23 @@
bool is_long_trace =
(tracing_session->config.write_into_file() &&
tracing_session->config.file_write_period_ms() < kMillisPerDay);
+ auto pending_clones = std::move(tracing_session->pending_clones);
tracing_sessions_.erase(tsid);
tracing_session = nullptr;
UpdateMemoryGuardrail();
+ for (const auto& id_to_clone_op : pending_clones) {
+ const PendingClone& clone_op = id_to_clone_op.second;
+ if (clone_op.weak_consumer) {
+ task_runner_->PostTask([weak_consumer = clone_op.weak_consumer] {
+ if (weak_consumer) {
+ weak_consumer->consumer_->OnSessionCloned(
+ {false, "Original session ended", {}});
+ }
+ });
+ }
+ }
+
PERFETTO_LOG("Tracing session %" PRIu64 " ended, total sessions:%zu", tsid,
tracing_sessions_.size());
#if PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD) && \
@@ -3217,9 +3228,9 @@
// Sum up all the cloned traced buffers.
for (const auto& id_to_ts : tracing_sessions_) {
const TracingSession& ts = id_to_ts.second;
- for (const auto& id_to_pending_clone : ts.pending_clones) {
- const PendingClone& pending_clone = id_to_pending_clone.second;
- for (const std::unique_ptr<TraceBuffer>& buf : pending_clone.buffers) {
+ for (const auto& id_to_clone_op : ts.pending_clones) {
+ const PendingClone& clone_op = id_to_clone_op.second;
+ for (const std::unique_ptr<TraceBuffer>& buf : clone_op.buffers) {
if (buf) {
total_buffer_bytes += buf->size();
}
@@ -3709,10 +3720,11 @@
return trigger_count;
}
-void TracingServiceImpl::FlushAndCloneSession(ConsumerEndpointImpl* consumer,
- TracingSessionID tsid,
- bool skip_trace_filter,
- bool for_bugreport) {
+base::Status TracingServiceImpl::FlushAndCloneSession(
+ ConsumerEndpointImpl* consumer,
+ TracingSessionID tsid,
+ bool skip_trace_filter,
+ bool for_bugreport) {
PERFETTO_DCHECK_THREAD(thread_checker_);
auto clone_target = FlushFlags::CloneTarget::kUnknown;
@@ -3725,9 +3737,8 @@
PERFETTO_LOG("Looking for sessions for bugreport");
TracingSession* session = FindTracingSessionWithMaxBugreportScore();
if (!session) {
- consumer->consumer_->OnSessionCloned(
- {false, "No tracing sessions eligible for bugreport found", {}});
- return;
+ return base::ErrStatus(
+ "No tracing sessions eligible for bugreport found");
}
tsid = session->id;
clone_target = FlushFlags::CloneTarget::kBugreport;
@@ -3739,9 +3750,14 @@
TracingSession* session = GetTracingSession(tsid);
if (!session) {
- consumer->consumer_->OnSessionCloned(
- {false, "Tracing session not found", {}});
- return;
+ return base::ErrStatus("Tracing session not found");
+ }
+
+ // Skip the UID check for sessions marked with a bugreport_score > 0.
+ // Those sessions, by design, can be stolen by any other consumer for the
+ // sake of creating snapshots for bugreports.
+ if (!session->IsCloneAllowed(consumer->uid_)) {
+ return PERFETTO_SVC_ERR("Not allowed to clone a session from another UID");
}
// If any of the buffers are marked as clear_before_clone, reset them before
@@ -3773,9 +3789,8 @@
// We cannot leave the original tracing session buffer-less as it would
// cause crashes when data sources commit new data.
buf = std::move(old_buf);
- consumer->consumer_->OnSessionCloned(
- {false, "Buffer allocation failed while attempting to clone", {}});
- return;
+ return base::ErrStatus(
+ "Buffer allocation failed while attempting to clone");
}
}
@@ -3821,6 +3836,8 @@
FlushFlags(FlushFlags::Initiator::kTraced,
FlushFlags::Reason::kTraceClone, clone_target));
}
+
+ return base::OkStatus();
}
std::map<ProducerID, std::vector<DataSourceInstanceID>>
@@ -3958,13 +3975,6 @@
"The consumer is already attached to another tracing session");
}
- // Skip the UID check for sessions marked with a bugreport_score > 0.
- // Those sessions, by design, can be stolen by any other consumer for the
- // sake of creating snapshots for bugreports.
- if (!src->IsCloneAllowed(consumer->uid_)) {
- return PERFETTO_SVC_ERR("Not allowed to clone a session from another UID");
- }
-
std::vector<BufferID> buf_ids =
buffer_ids_.AllocateMultiple(buf_snaps.size());
if (buf_ids.size() != buf_snaps.size()) {
@@ -4405,8 +4415,12 @@
CloneSessionArgs args) {
PERFETTO_DCHECK_THREAD(thread_checker_);
// FlushAndCloneSession will call OnSessionCloned after the async flush.
- service_->FlushAndCloneSession(this, tsid, args.skip_trace_filter,
- args.for_bugreport);
+ base::Status result = service_->FlushAndCloneSession(
+ this, tsid, args.skip_trace_filter, args.for_bugreport);
+
+ if (!result.ok()) {
+ consumer_->OnSessionCloned({false, result.message(), {}});
+ }
}
////////////////////////////////////////////////////////////////////////////////
diff --git a/src/tracing/service/tracing_service_impl.h b/src/tracing/service/tracing_service_impl.h
index 1dbcbe6..64b1f50 100644
--- a/src/tracing/service/tracing_service_impl.h
+++ b/src/tracing/service/tracing_service_impl.h
@@ -352,10 +352,10 @@
ConsumerEndpoint::FlushCallback,
FlushFlags);
void FlushAndDisableTracing(TracingSessionID);
- void FlushAndCloneSession(ConsumerEndpointImpl*,
- TracingSessionID,
- bool skip_filter,
- bool for_bugreport);
+ base::Status FlushAndCloneSession(ConsumerEndpointImpl*,
+ TracingSessionID,
+ bool skip_filter,
+ bool for_bugreport);
// Starts reading the internal tracing buffers from the tracing session `tsid`
// and sends them to `*consumer` (which must be != nullptr).
diff --git a/src/tracing/service/tracing_service_impl_unittest.cc b/src/tracing/service/tracing_service_impl_unittest.cc
index 9e54d57..e512f7b 100644
--- a/src/tracing/service/tracing_service_impl_unittest.cc
+++ b/src/tracing/service/tracing_service_impl_unittest.cc
@@ -5221,7 +5221,7 @@
}
TEST_F(TracingServiceImplTest, CloneConsumerDisconnect) {
- // The consumer the creates the initial tracing session.
+ // The consumer that creates the initial tracing session.
std::unique_ptr<MockConsumer> consumer = CreateMockConsumer();
consumer->Connect(svc.get());
@@ -5276,6 +5276,71 @@
consumer->WaitForTracingDisabled();
}
+TEST_F(TracingServiceImplTest, CloneMainSessionGoesAwayDuringFlush) {
+ // The consumer that creates the initial tracing session.
+ std::unique_ptr<MockConsumer> consumer = CreateMockConsumer();
+ consumer->Connect(svc.get());
+
+ std::unique_ptr<MockProducer> producer1 = CreateMockProducer();
+ producer1->Connect(svc.get(), "mock_producer1");
+ producer1->RegisterDataSource("ds_1");
+
+ TraceConfig trace_config;
+ trace_config.add_buffers()->set_size_kb(1024); // Buf 0.
+ auto* ds_cfg = trace_config.add_data_sources()->mutable_config();
+ ds_cfg->set_name("ds_1");
+ ds_cfg->set_target_buffer(0);
+
+ consumer->EnableTracing(trace_config);
+ producer1->WaitForTracingSetup();
+ producer1->WaitForDataSourceSetup("ds_1");
+ producer1->WaitForDataSourceStart("ds_1");
+
+ std::unique_ptr<TraceWriter> writer1 = producer1->CreateTraceWriter("ds_1");
+
+ {
+ auto tp = writer1->NewTracePacket();
+ tp->set_for_testing()->set_str("buf1_beforeflush");
+ }
+ writer1->Flush();
+
+ std::unique_ptr<MockConsumer> clone_consumer = CreateMockConsumer();
+ clone_consumer->Connect(svc.get());
+
+ EXPECT_CALL(*clone_consumer, OnSessionCloned)
+ .Times(1)
+ .WillOnce(Invoke([](const Consumer::OnSessionClonedArgs& args) {
+ EXPECT_FALSE(args.success);
+ EXPECT_THAT(args.error, HasSubstr("Original session ended"));
+ }));
+ clone_consumer->CloneSession(1);
+
+ std::string producer1_flush_checkpoint_name = "producer1_flush_requested";
+ auto flush1_requested =
+ task_runner.CreateCheckpoint(producer1_flush_checkpoint_name);
+ FlushRequestID flush1_req_id;
+
+ // CloneSession() will issue a flush.
+ EXPECT_CALL(*producer1, Flush(_, _, _, _))
+ .WillOnce([&](FlushRequestID flush_id, const DataSourceInstanceID*,
+ size_t, FlushFlags) {
+ flush1_req_id = flush_id;
+ flush1_requested();
+ });
+
+ task_runner.RunUntilCheckpoint(producer1_flush_checkpoint_name);
+
+ // The main session goes away.
+ consumer->DisableTracing();
+ producer1->WaitForDataSourceStop("ds_1");
+ consumer->WaitForTracingDisabled();
+ consumer.reset();
+
+ // producer1 replies to flush much later.
+ producer1->endpoint()->NotifyFlushComplete(flush1_req_id);
+ task_runner.RunUntilIdle();
+}
+
TEST_F(TracingServiceImplTest, CloneTransferFlush) {
// The consumer the creates the initial tracing session.
std::unique_ptr<MockConsumer> consumer = CreateMockConsumer();
diff --git a/test/data/trace_with_negative_timestamp.sha256 b/test/data/trace_with_negative_timestamp.sha256
new file mode 100644
index 0000000..7777c8d
--- /dev/null
+++ b/test/data/trace_with_negative_timestamp.sha256
@@ -0,0 +1 @@
+79d809480f1bea625982e05ed726580a372656f03e7b7e29de4579e578ca13c4
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/include_index.py b/test/trace_processor/diff_tests/include_index.py
index a531d3a..d3dba5c 100644
--- a/test/trace_processor/diff_tests/include_index.py
+++ b/test/trace_processor/diff_tests/include_index.py
@@ -48,6 +48,7 @@
from diff_tests.metrics.webview.tests import WebView
from diff_tests.parser.android_fs.tests import AndroidFs
from diff_tests.parser.android.tests import AndroidParser
+from diff_tests.parser.android.tests_android_input_event import AndroidInputEvent
from diff_tests.parser.android.tests_bugreport import AndroidBugreport
from diff_tests.parser.android.tests_games import AndroidGames
from diff_tests.parser.android.tests_inputmethod_clients import InputMethodClients
@@ -225,6 +226,7 @@
*ParsingTracedStats(index_path, 'parser/parsing',
'ParsingTracedStats').fetch(),
*Zip(index_path, 'parser/zip', 'Zip').fetch(),
+ *AndroidInputEvent(index_path, 'parser/android', 'AndroidInputEvent').fetch(),
]
metrics_tests = [
diff --git a/test/trace_processor/diff_tests/parser/android/input_event_trace.textproto b/test/trace_processor/diff_tests/parser/android/input_event_trace.textproto
new file mode 100644
index 0000000..02ecce4
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/android/input_event_trace.textproto
@@ -0,0 +1,1008 @@
+packet {
+ clock_snapshot {
+ primary_trace_clock: BUILTIN_CLOCK_BOOTTIME
+ clocks {
+ clock_id: 6
+ timestamp: 178674061789798
+ }
+ clocks {
+ clock_id: 2
+ timestamp: 1715375168889637820
+ }
+ clocks {
+ clock_id: 4
+ timestamp: 64176147060835
+ }
+ clocks {
+ clock_id: 1
+ timestamp: 1715375168892622806
+ }
+ clocks {
+ clock_id: 3
+ timestamp: 64176150045943
+ }
+ clocks {
+ clock_id: 5
+ timestamp: 64176150046147
+ }
+ }
+ trusted_uid: 9999
+ trusted_packet_sequence_id: 1
+}
+packet {
+ clock_snapshot {
+ primary_trace_clock: BUILTIN_CLOCK_BOOTTIME
+ clocks {
+ clock_id: 6
+ timestamp: 178674061806929
+ }
+ clocks {
+ clock_id: 2
+ timestamp: 1715375168889637820
+ }
+ clocks {
+ clock_id: 4
+ timestamp: 64176147060835
+ }
+ clocks {
+ clock_id: 1
+ timestamp: 1715375168892639855
+ }
+ clocks {
+ clock_id: 3
+ timestamp: 64176150062992
+ }
+ clocks {
+ clock_id: 5
+ timestamp: 64176150063155
+ }
+ }
+ trusted_uid: 9999
+ trusted_packet_sequence_id: 1
+}
+packet {
+ trusted_uid: 9999
+ trusted_packet_sequence_id: 1
+ synchronization_marker: "\202Gzv\262\215B\272\201\33432mW\240y"
+}
+packet {
+ trusted_uid: 9999
+ trusted_packet_sequence_id: 1
+ trace_config {
+ buffers {
+ size_kb: 1000000
+ fill_policy: RING_BUFFER
+ }
+ data_sources {
+ config {
+ name: "android.input.inputevent"
+ android_input_event_config {
+ mode: TRACE_MODE_TRACE_ALL
+ }
+ }
+ }
+ duration_ms: 10000
+ enable_extra_guardrails: false
+ statsd_metadata {
+ }
+ statsd_logging: STATSD_LOGGING_DISABLED
+ trace_uuid_msb: 6148766808500972866
+ trace_uuid_lsb: -1402094119056663186
+ }
+}
+packet {
+ trusted_uid: 9999
+ trusted_packet_sequence_id: 1
+ trace_uuid {
+ lsb: -1402094119056663186
+ msb: 6148766808500972866
+ }
+}
+packet {
+ system_info {
+ tracing_service_version: "Perfetto v44.0 (N/A)"
+ timezone_off_mins: 0
+ utsname {
+ sysname: "Linux"
+ version: "#1 SMP PREEMPT Thu Mar 28 12:45:51 UTC 2024"
+ machine: "aarch64"
+ release: "5.10.209-android13-4-02808-g9f408f561c85-ab11640454"
+ }
+ page_size: 4096
+ num_cpus: 8
+ android_build_fingerprint: "google/tangorpro/tangorpro:VanillaIceCream/MAIN/eng.prabir.20240508.183909:eng/dev-keys"
+ android_sdk_version: 35
+ }
+ trusted_uid: 9999
+ trusted_packet_sequence_id: 1
+}
+packet {
+ timestamp: 178674061798587
+ trusted_uid: 9999
+ trusted_packet_sequence_id: 1
+ service_event {
+ tracing_started: true
+ }
+}
+packet {
+ timestamp: 178674065835493
+ trusted_uid: 9999
+ trusted_packet_sequence_id: 1
+ service_event {
+ all_data_sources_started: true
+ }
+}
+packet {
+ timestamp: 178683982315600
+ trusted_uid: 9999
+ trusted_packet_sequence_id: 1
+ service_event {
+ all_data_sources_flushed: true
+ }
+}
+packet {
+ timestamp: 178683984185189
+ trusted_uid: 9999
+ trusted_packet_sequence_id: 1
+ service_event {
+ tracing_disabled: true
+ }
+}
+packet {
+ first_packet_on_sequence: true
+ android_input_event {
+ dispatcher_motion_event {
+ event_id: 104114844
+ event_time_nanos: 64179212500000
+ down_time_nanos: 64179212500000
+ source: 4098
+ action: 0
+ device_id: 4
+ display_id: 0
+ classification: 0
+ flags: 0
+ policy_flags: 1644167168
+ cursor_position_x: nan
+ cursor_position_y: nan
+ meta_state: 0
+ pointer {
+ pointer_id: 0
+ tool_type: 1
+ axis_value {
+ axis: 0
+ value: 580.000000
+ }
+ axis_value {
+ axis: 1
+ value: 798.000000
+ }
+ axis_value {
+ axis: 2
+ value: 1.003906
+ }
+ axis_value {
+ axis: 3
+ value: 0.033998
+ }
+ axis_value {
+ axis: 4
+ value: 92.000000
+ }
+ axis_value {
+ axis: 5
+ value: 82.000000
+ }
+ axis_value {
+ axis: 6
+ value: 92.000000
+ }
+ axis_value {
+ axis: 7
+ value: 82.000000
+ }
+ axis_value {
+ axis: 8
+ value: 0.983282
+ }
+ }
+ }
+ }
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 2
+ trusted_pid: 1722
+ previous_packet_dropped: true
+}
+packet {
+ android_input_event {
+ dispatcher_motion_event {
+ event_id: 1141228253
+ event_time_nanos: 64179212500000
+ down_time_nanos: 64179212500000
+ source: 4098
+ action: 4
+ device_id: 4
+ display_id: 0
+ classification: 0
+ flags: 0
+ policy_flags: 1644167168
+ cursor_position_x: nan
+ cursor_position_y: nan
+ meta_state: 0
+ pointer {
+ pointer_id: 0
+ tool_type: 1
+ axis_value {
+ axis: 0
+ value: 580.000000
+ }
+ axis_value {
+ axis: 1
+ value: 798.000000
+ }
+ axis_value {
+ axis: 2
+ value: 1.003906
+ }
+ axis_value {
+ axis: 3
+ value: 0.033998
+ }
+ axis_value {
+ axis: 4
+ value: 92.000000
+ }
+ axis_value {
+ axis: 5
+ value: 82.000000
+ }
+ axis_value {
+ axis: 6
+ value: 92.000000
+ }
+ axis_value {
+ axis: 7
+ value: 82.000000
+ }
+ axis_value {
+ axis: 8
+ value: 0.983282
+ }
+ }
+ }
+ }
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 2
+ trusted_pid: 1722
+}
+packet {
+ android_input_event {
+ dispatcher_window_dispatch_event {
+ event_id: 1141228253
+ vsync_id: 182239
+ window_id: 105
+ resolved_flags: 0
+ dispatched_pointer {
+ pointer_id: 0
+ x_in_display: 1762.000000
+ y_in_display: 580.000000
+ axis_value_in_window {
+ axis: 0
+ value: 1762.000000
+ }
+ axis_value_in_window {
+ axis: 1
+ value: -681.000000
+ }
+ axis_value_in_window {
+ axis: 8
+ value: 2.554132
+ }
+ }
+ }
+ }
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 2
+ trusted_pid: 1722
+}
+packet {
+ android_input_event {
+ dispatcher_window_dispatch_event {
+ event_id: 104114844
+ vsync_id: 182239
+ window_id: 181
+ resolved_flags: 0
+ dispatched_pointer {
+ pointer_id: 0
+ x_in_display: 1762.000000
+ y_in_display: 580.000000
+ axis_value_in_window {
+ axis: 0
+ value: 1762.000000
+ }
+ axis_value_in_window {
+ axis: 1
+ value: 580.000000
+ }
+ axis_value_in_window {
+ axis: 8
+ value: 2.554157
+ }
+ }
+ }
+ }
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 2
+ trusted_pid: 1722
+}
+packet {
+ android_input_event {
+ dispatcher_window_dispatch_event {
+ event_id: 104114844
+ vsync_id: 182239
+ window_id: 58
+ resolved_flags: 3
+ dispatched_pointer {
+ pointer_id: 0
+ x_in_display: 1762.000000
+ y_in_display: 580.000000
+ axis_value_in_window {
+ axis: 0
+ value: 1718.181641
+ }
+ axis_value_in_window {
+ axis: 1
+ value: 600.000000
+ }
+ axis_value_in_window {
+ axis: 8
+ value: 2.554074
+ }
+ }
+ }
+ }
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 2
+ trusted_pid: 1722
+}
+packet {
+ android_input_event {
+ dispatcher_window_dispatch_event {
+ event_id: 104114844
+ vsync_id: 182239
+ window_id: 76
+ resolved_flags: 0
+ dispatched_pointer {
+ pointer_id: 0
+ x_in_display: 1762.000000
+ y_in_display: 580.000000
+ axis_value_in_window {
+ axis: 0
+ value: 1762.000000
+ }
+ axis_value_in_window {
+ axis: 1
+ value: 580.000000
+ }
+ axis_value_in_window {
+ axis: 8
+ value: 2.554157
+ }
+ }
+ }
+ }
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 2
+ trusted_pid: 1722
+}
+packet {
+ android_input_event {
+ dispatcher_window_dispatch_event {
+ event_id: 104114844
+ vsync_id: 182239
+ window_id: 68
+ resolved_flags: 0
+ dispatched_pointer {
+ pointer_id: 0
+ x_in_display: 1762.000000
+ y_in_display: 580.000000
+ axis_value_in_window {
+ axis: 0
+ value: 1762.000000
+ }
+ axis_value_in_window {
+ axis: 1
+ value: 580.000000
+ }
+ axis_value_in_window {
+ axis: 8
+ value: 2.554157
+ }
+ }
+ }
+ }
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 2
+ trusted_pid: 1722
+}
+packet {
+ android_input_event {
+ dispatcher_window_dispatch_event {
+ event_id: 104114844
+ vsync_id: 0
+ window_id: 0
+ resolved_flags: 0
+ dispatched_pointer {
+ pointer_id: 0
+ x_in_display: 1762.000000
+ y_in_display: 580.000000
+ axis_value_in_window {
+ axis: 0
+ value: 1762.000000
+ }
+ axis_value_in_window {
+ axis: 1
+ value: 580.000000
+ }
+ axis_value_in_window {
+ axis: 8
+ value: 2.554157
+ }
+ }
+ }
+ }
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 2
+ trusted_pid: 1722
+}
+packet {
+ android_input_event {
+ dispatcher_motion_event {
+ event_id: 843076721
+ event_time_nanos: 64179262122000
+ down_time_nanos: 64179212500000
+ source: 4098
+ action: 2
+ device_id: 4
+ display_id: 0
+ classification: 1
+ flags: 0
+ policy_flags: 1644167168
+ cursor_position_x: nan
+ cursor_position_y: nan
+ meta_state: 0
+ pointer {
+ pointer_id: 0
+ tool_type: 1
+ axis_value {
+ axis: 0
+ value: 580.000000
+ }
+ axis_value {
+ axis: 1
+ value: 798.000000
+ }
+ axis_value {
+ axis: 2
+ value: 0.113281
+ }
+ axis_value {
+ axis: 3
+ value: 0.011333
+ }
+ axis_value {
+ axis: 4
+ value: 29.000000
+ }
+ axis_value {
+ axis: 5
+ value: 29.000000
+ }
+ axis_value {
+ axis: 6
+ value: 29.000000
+ }
+ axis_value {
+ axis: 7
+ value: 29.000000
+ }
+ axis_value {
+ axis: 8
+ value: 0.912335
+ }
+ }
+ }
+ }
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 2
+ trusted_pid: 1722
+}
+packet {
+ android_input_event {
+ dispatcher_window_dispatch_event {
+ event_id: 843076721
+ vsync_id: 182239
+ window_id: 181
+ resolved_flags: 0
+ dispatched_pointer {
+ pointer_id: 0
+ x_in_display: 1762.000000
+ y_in_display: 580.000000
+ axis_value_in_window {
+ axis: 0
+ value: 1762.000000
+ }
+ axis_value_in_window {
+ axis: 1
+ value: 580.000000
+ }
+ axis_value_in_window {
+ axis: 8
+ value: 2.483198
+ }
+ }
+ }
+ }
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 2
+ trusted_pid: 1722
+}
+packet {
+ android_input_event {
+ dispatcher_window_dispatch_event {
+ event_id: 843076721
+ vsync_id: 182239
+ window_id: 58
+ resolved_flags: 3
+ dispatched_pointer {
+ pointer_id: 0
+ x_in_display: 1762.000000
+ y_in_display: 580.000000
+ axis_value_in_window {
+ axis: 0
+ value: 1718.181641
+ }
+ axis_value_in_window {
+ axis: 1
+ value: 600.000000
+ }
+ axis_value_in_window {
+ axis: 8
+ value: 2.483237
+ }
+ }
+ }
+ }
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 2
+ trusted_pid: 1722
+}
+packet {
+ android_input_event {
+ dispatcher_window_dispatch_event {
+ event_id: 843076721
+ vsync_id: 182239
+ window_id: 76
+ resolved_flags: 0
+ dispatched_pointer {
+ pointer_id: 0
+ x_in_display: 1762.000000
+ y_in_display: 580.000000
+ axis_value_in_window {
+ axis: 0
+ value: 1762.000000
+ }
+ axis_value_in_window {
+ axis: 1
+ value: 580.000000
+ }
+ axis_value_in_window {
+ axis: 8
+ value: 2.483198
+ }
+ }
+ }
+ }
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 2
+ trusted_pid: 1722
+}
+packet {
+ android_input_event {
+ dispatcher_window_dispatch_event {
+ event_id: 843076721
+ vsync_id: 182239
+ window_id: 68
+ resolved_flags: 0
+ dispatched_pointer {
+ pointer_id: 0
+ x_in_display: 1762.000000
+ y_in_display: 580.000000
+ axis_value_in_window {
+ axis: 0
+ value: 1762.000000
+ }
+ axis_value_in_window {
+ axis: 1
+ value: 580.000000
+ }
+ axis_value_in_window {
+ axis: 8
+ value: 2.483198
+ }
+ }
+ }
+ }
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 2
+ trusted_pid: 1722
+}
+packet {
+ android_input_event {
+ dispatcher_window_dispatch_event {
+ event_id: 843076721
+ vsync_id: 0
+ window_id: 0
+ resolved_flags: 0
+ dispatched_pointer {
+ pointer_id: 0
+ x_in_display: 1762.000000
+ y_in_display: 580.000000
+ axis_value_in_window {
+ axis: 0
+ value: 1762.000000
+ }
+ axis_value_in_window {
+ axis: 1
+ value: 580.000000
+ }
+ axis_value_in_window {
+ axis: 8
+ value: 2.483198
+ }
+ }
+ }
+ }
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 2
+ trusted_pid: 1722
+}
+packet {
+ android_input_event {
+ dispatcher_motion_event {
+ event_id: 744146837
+ event_time_nanos: 64179268985000
+ down_time_nanos: 64179212500000
+ source: 4098
+ action: 1
+ device_id: 4
+ display_id: 0
+ classification: 1
+ flags: 0
+ policy_flags: 1644167168
+ cursor_position_x: nan
+ cursor_position_y: nan
+ meta_state: 0
+ pointer {
+ pointer_id: 0
+ tool_type: 1
+ axis_value {
+ axis: 0
+ value: 580.000000
+ }
+ axis_value {
+ axis: 1
+ value: 798.000000
+ }
+ axis_value {
+ axis: 2
+ value: 0.113281
+ }
+ axis_value {
+ axis: 3
+ value: 0.011333
+ }
+ axis_value {
+ axis: 4
+ value: 29.000000
+ }
+ axis_value {
+ axis: 5
+ value: 29.000000
+ }
+ axis_value {
+ axis: 6
+ value: 29.000000
+ }
+ axis_value {
+ axis: 7
+ value: 29.000000
+ }
+ axis_value {
+ axis: 8
+ value: 0.912335
+ }
+ }
+ }
+ }
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 2
+ trusted_pid: 1722
+}
+packet {
+ android_input_event {
+ dispatcher_window_dispatch_event {
+ event_id: 744146837
+ vsync_id: 182239
+ window_id: 181
+ resolved_flags: 0
+ dispatched_pointer {
+ pointer_id: 0
+ x_in_display: 1762.000000
+ y_in_display: 580.000000
+ axis_value_in_window {
+ axis: 0
+ value: 1762.000000
+ }
+ axis_value_in_window {
+ axis: 1
+ value: 580.000000
+ }
+ axis_value_in_window {
+ axis: 8
+ value: 2.483198
+ }
+ }
+ }
+ }
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 2
+ trusted_pid: 1722
+}
+packet {
+ android_input_event {
+ dispatcher_window_dispatch_event {
+ event_id: 744146837
+ vsync_id: 182239
+ window_id: 58
+ resolved_flags: 3
+ dispatched_pointer {
+ pointer_id: 0
+ x_in_display: 1762.000000
+ y_in_display: 580.000000
+ axis_value_in_window {
+ axis: 0
+ value: 1718.181641
+ }
+ axis_value_in_window {
+ axis: 1
+ value: 600.000000
+ }
+ axis_value_in_window {
+ axis: 8
+ value: 2.483237
+ }
+ }
+ }
+ }
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 2
+ trusted_pid: 1722
+}
+packet {
+ android_input_event {
+ dispatcher_window_dispatch_event {
+ event_id: 744146837
+ vsync_id: 182239
+ window_id: 76
+ resolved_flags: 0
+ dispatched_pointer {
+ pointer_id: 0
+ x_in_display: 1762.000000
+ y_in_display: 580.000000
+ axis_value_in_window {
+ axis: 0
+ value: 1762.000000
+ }
+ axis_value_in_window {
+ axis: 1
+ value: 580.000000
+ }
+ axis_value_in_window {
+ axis: 8
+ value: 2.483198
+ }
+ }
+ }
+ }
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 2
+ trusted_pid: 1722
+}
+packet {
+ android_input_event {
+ dispatcher_window_dispatch_event {
+ event_id: 744146837
+ vsync_id: 182239
+ window_id: 68
+ resolved_flags: 0
+ dispatched_pointer {
+ pointer_id: 0
+ x_in_display: 1762.000000
+ y_in_display: 580.000000
+ axis_value_in_window {
+ axis: 0
+ value: 1762.000000
+ }
+ axis_value_in_window {
+ axis: 1
+ value: 580.000000
+ }
+ axis_value_in_window {
+ axis: 8
+ value: 2.483198
+ }
+ }
+ }
+ }
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 2
+ trusted_pid: 1722
+}
+packet {
+ android_input_event {
+ dispatcher_window_dispatch_event {
+ event_id: 744146837
+ vsync_id: 0
+ window_id: 0
+ resolved_flags: 0
+ dispatched_pointer {
+ pointer_id: 0
+ x_in_display: 1762.000000
+ y_in_display: 580.000000
+ axis_value_in_window {
+ axis: 0
+ value: 1762.000000
+ }
+ axis_value_in_window {
+ axis: 1
+ value: 580.000000
+ }
+ axis_value_in_window {
+ axis: 8
+ value: 2.483198
+ }
+ }
+ }
+ }
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 2
+ trusted_pid: 1722
+}
+packet {
+ android_input_event {
+ dispatcher_key_event {
+ event_id: 324105269
+ event_time_nanos: 64182660299000
+ down_time_nanos: 64182660299000
+ source: 257
+ action: 0
+ device_id: 2
+ display_id: -1
+ repeat_count: 0
+ flags: 8
+ policy_flags: 1644167168
+ key_code: 25
+ scan_code: 114
+ meta_state: 0
+ }
+ }
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 2
+ trusted_pid: 1722
+}
+packet {
+ android_input_event {
+ dispatcher_window_dispatch_event {
+ event_id: 324105269
+ vsync_id: 182239
+ window_id: 181
+ resolved_flags: 8
+ }
+ }
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 2
+ trusted_pid: 1722
+}
+packet {
+ android_input_event {
+ dispatcher_window_dispatch_event {
+ event_id: 324105269
+ vsync_id: 0
+ window_id: 0
+ resolved_flags: 8
+ }
+ }
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 2
+ trusted_pid: 1722
+}
+packet {
+ android_input_event {
+ dispatcher_key_event {
+ event_id: 60594531
+ event_time_nanos: 64182816340000
+ down_time_nanos: 64182660299000
+ source: 257
+ action: 1
+ device_id: 2
+ display_id: -1
+ repeat_count: 0
+ flags: 8
+ policy_flags: 1644167168
+ key_code: 25
+ scan_code: 114
+ meta_state: 0
+ }
+ }
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 2
+ trusted_pid: 1722
+}
+packet {
+ android_input_event {
+ dispatcher_window_dispatch_event {
+ event_id: 60594531
+ vsync_id: 182239
+ window_id: 181
+ resolved_flags: 8
+ }
+ }
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 2
+ trusted_pid: 1722
+}
+packet {
+ android_input_event {
+ dispatcher_window_dispatch_event {
+ event_id: 60594531
+ vsync_id: 0
+ window_id: 0
+ resolved_flags: 8
+ }
+ }
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 2
+ trusted_pid: 1722
+}
+packet {
+ timestamp: 178683985660693
+ trusted_uid: 9999
+ trusted_packet_sequence_id: 1
+ service_event {
+ read_tracing_buffers_completed: true
+ }
+}
+packet {
+ trusted_uid: 9999
+ trusted_packet_sequence_id: 1
+ trace_stats {
+ buffer_stats {
+ buffer_size: 1024000000
+ bytes_written: 4096
+ chunks_written: 1
+ }
+ producers_connected: 13
+ producers_seen: 16
+ data_sources_registered: 40
+ data_sources_seen: 13
+ tracing_sessions: 2
+ total_buffers: 4
+ chunks_discarded: 7
+ patches_discarded: 0
+ invalid_packets: 0
+ flushes_requested: 1
+ flushes_succeeded: 1
+ flushes_failed: 0
+ final_flush_outcome: FINAL_FLUSH_UNSPECIFIED
+ }
+}
diff --git a/test/trace_processor/diff_tests/parser/android/tests_android_input_event.py b/test/trace_processor/diff_tests/parser/android/tests_android_input_event.py
new file mode 100644
index 0000000..30ac616
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/android/tests_android_input_event.py
@@ -0,0 +1,272 @@
+#!/usr/bin/env python3
+# Copyright (C) 2024 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 a
+#
+# 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 python.generators.diff_tests.testing import Path
+from python.generators.diff_tests.testing import Csv
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+class AndroidInputEvent(TestSuite):
+
+ def test_key_events_table(self):
+ return DiffTestBlueprint(
+ trace=Path('input_event_trace.textproto'),
+ query="""
+ INCLUDE PERFETTO MODULE android.input;
+ SELECT
+ event_id, ts
+ FROM
+ android_key_events;
+ """,
+ out=Csv("""
+ "event_id","ts"
+ 324105269,64182660299000
+ 60594531,64182816340000
+ """))
+
+ def test_key_events_args(self):
+ return DiffTestBlueprint(
+ trace=Path('input_event_trace.textproto'),
+ query="""
+ INCLUDE PERFETTO MODULE android.input;
+ SELECT
+ args.key, args.display_value
+ FROM
+ android_key_events AS e JOIN args ON e.arg_set_id = args.arg_set_id
+ WHERE e.event_id = 60594531
+ ORDER BY args.key;
+ """,
+ out=Csv("""
+ "key","display_value"
+ "action","1"
+ "device_id","2"
+ "display_id","-1"
+ "down_time_nanos","64182660299000"
+ "event_id","60594531"
+ "event_time_nanos","64182816340000"
+ "flags","8"
+ "key_code","25"
+ "meta_state","0"
+ "policy_flags","1644167168"
+ "repeat_count","0"
+ "scan_code","114"
+ "source","257"
+ """))
+
+ def test_motion_events_table(self):
+ return DiffTestBlueprint(
+ trace=Path('input_event_trace.textproto'),
+ query="""
+ INCLUDE PERFETTO MODULE android.input;
+ SELECT
+ event_id, ts
+ FROM
+ android_motion_events;
+ """,
+ out=Csv("""
+ "event_id","ts"
+ 104114844,64179212500000
+ 1141228253,64179212500000
+ 843076721,64179262122000
+ 744146837,64179268985000
+ """))
+
+ def test_motion_events_args(self):
+ return DiffTestBlueprint(
+ trace=Path('input_event_trace.textproto'),
+ query="""
+ INCLUDE PERFETTO MODULE android.input;
+ SELECT
+ args.key, args.display_value
+ FROM
+ android_motion_events AS e JOIN args ON e.arg_set_id = args.arg_set_id
+ WHERE e.event_id = 1141228253
+ ORDER BY args.key;
+ """,
+ out=Csv("""
+ "key","display_value"
+ "action","4"
+ "classification","0"
+ "cursor_position_x","[NULL]"
+ "cursor_position_y","[NULL]"
+ "device_id","4"
+ "display_id","0"
+ "down_time_nanos","64179212500000"
+ "event_id","1141228253"
+ "event_time_nanos","64179212500000"
+ "flags","0"
+ "meta_state","0"
+ "pointer[0].axis_value[0].axis","0"
+ "pointer[0].axis_value[0].value","580.0"
+ "pointer[0].axis_value[1].axis","1"
+ "pointer[0].axis_value[1].value","798.0"
+ "pointer[0].axis_value[2].axis","2"
+ "pointer[0].axis_value[2].value","1.00390601158142"
+ "pointer[0].axis_value[3].axis","3"
+ "pointer[0].axis_value[3].value","0.0339980013668537"
+ "pointer[0].axis_value[4].axis","4"
+ "pointer[0].axis_value[4].value","92.0"
+ "pointer[0].axis_value[5].axis","5"
+ "pointer[0].axis_value[5].value","82.0"
+ "pointer[0].axis_value[6].axis","6"
+ "pointer[0].axis_value[6].value","92.0"
+ "pointer[0].axis_value[7].axis","7"
+ "pointer[0].axis_value[7].value","82.0"
+ "pointer[0].axis_value[8].axis","8"
+ "pointer[0].axis_value[8].value","0.983282029628754"
+ "pointer[0].pointer_id","0"
+ "pointer[0].tool_type","1"
+ "policy_flags","1644167168"
+ "source","4098"
+ """))
+
+ def test_dispatch_table(self):
+ return DiffTestBlueprint(
+ trace=Path('input_event_trace.textproto'),
+ query="""
+ INCLUDE PERFETTO MODULE android.input;
+ SELECT
+ id, event_id, vsync_id, window_id
+ FROM
+ android_input_event_dispatch;
+ """,
+ out=Csv("""
+ "id","event_id","vsync_id","window_id"
+ 0,1141228253,182239,105
+ 1,104114844,182239,181
+ 2,104114844,182239,58
+ 3,104114844,182239,76
+ 4,104114844,182239,68
+ 5,104114844,0,0
+ 6,843076721,182239,181
+ 7,843076721,182239,58
+ 8,843076721,182239,76
+ 9,843076721,182239,68
+ 10,843076721,0,0
+ 11,744146837,182239,181
+ 12,744146837,182239,58
+ 13,744146837,182239,76
+ 14,744146837,182239,68
+ 15,744146837,0,0
+ 16,324105269,182239,181
+ 17,324105269,0,0
+ 18,60594531,182239,181
+ 19,60594531,0,0
+ """))
+
+ def test_motion_dispatch_args(self):
+ return DiffTestBlueprint(
+ trace=Path('input_event_trace.textproto'),
+ query="""
+ INCLUDE PERFETTO MODULE android.input;
+ SELECT
+ d.id, args.key, args.display_value
+ FROM
+ android_input_event_dispatch AS d JOIN args ON d.arg_set_id = args.arg_set_id
+ WHERE d.event_id = 104114844
+ ORDER BY d.id, args.key;
+ """,
+ out=Csv("""
+ "id","key","display_value"
+ 1,"dispatched_pointer[0].axis_value_in_window[0].axis","0"
+ 1,"dispatched_pointer[0].axis_value_in_window[0].value","1762.0"
+ 1,"dispatched_pointer[0].axis_value_in_window[1].axis","1"
+ 1,"dispatched_pointer[0].axis_value_in_window[1].value","580.0"
+ 1,"dispatched_pointer[0].axis_value_in_window[2].axis","8"
+ 1,"dispatched_pointer[0].axis_value_in_window[2].value","2.5541570186615"
+ 1,"dispatched_pointer[0].pointer_id","0"
+ 1,"dispatched_pointer[0].x_in_display","1762.0"
+ 1,"dispatched_pointer[0].y_in_display","580.0"
+ 1,"event_id","104114844"
+ 1,"resolved_flags","0"
+ 1,"vsync_id","182239"
+ 1,"window_id","181"
+ 2,"dispatched_pointer[0].axis_value_in_window[0].axis","0"
+ 2,"dispatched_pointer[0].axis_value_in_window[0].value","1718.181640625"
+ 2,"dispatched_pointer[0].axis_value_in_window[1].axis","1"
+ 2,"dispatched_pointer[0].axis_value_in_window[1].value","600.0"
+ 2,"dispatched_pointer[0].axis_value_in_window[2].axis","8"
+ 2,"dispatched_pointer[0].axis_value_in_window[2].value","2.55407404899597"
+ 2,"dispatched_pointer[0].pointer_id","0"
+ 2,"dispatched_pointer[0].x_in_display","1762.0"
+ 2,"dispatched_pointer[0].y_in_display","580.0"
+ 2,"event_id","104114844"
+ 2,"resolved_flags","3"
+ 2,"vsync_id","182239"
+ 2,"window_id","58"
+ 3,"dispatched_pointer[0].axis_value_in_window[0].axis","0"
+ 3,"dispatched_pointer[0].axis_value_in_window[0].value","1762.0"
+ 3,"dispatched_pointer[0].axis_value_in_window[1].axis","1"
+ 3,"dispatched_pointer[0].axis_value_in_window[1].value","580.0"
+ 3,"dispatched_pointer[0].axis_value_in_window[2].axis","8"
+ 3,"dispatched_pointer[0].axis_value_in_window[2].value","2.5541570186615"
+ 3,"dispatched_pointer[0].pointer_id","0"
+ 3,"dispatched_pointer[0].x_in_display","1762.0"
+ 3,"dispatched_pointer[0].y_in_display","580.0"
+ 3,"event_id","104114844"
+ 3,"resolved_flags","0"
+ 3,"vsync_id","182239"
+ 3,"window_id","76"
+ 4,"dispatched_pointer[0].axis_value_in_window[0].axis","0"
+ 4,"dispatched_pointer[0].axis_value_in_window[0].value","1762.0"
+ 4,"dispatched_pointer[0].axis_value_in_window[1].axis","1"
+ 4,"dispatched_pointer[0].axis_value_in_window[1].value","580.0"
+ 4,"dispatched_pointer[0].axis_value_in_window[2].axis","8"
+ 4,"dispatched_pointer[0].axis_value_in_window[2].value","2.5541570186615"
+ 4,"dispatched_pointer[0].pointer_id","0"
+ 4,"dispatched_pointer[0].x_in_display","1762.0"
+ 4,"dispatched_pointer[0].y_in_display","580.0"
+ 4,"event_id","104114844"
+ 4,"resolved_flags","0"
+ 4,"vsync_id","182239"
+ 4,"window_id","68"
+ 5,"dispatched_pointer[0].axis_value_in_window[0].axis","0"
+ 5,"dispatched_pointer[0].axis_value_in_window[0].value","1762.0"
+ 5,"dispatched_pointer[0].axis_value_in_window[1].axis","1"
+ 5,"dispatched_pointer[0].axis_value_in_window[1].value","580.0"
+ 5,"dispatched_pointer[0].axis_value_in_window[2].axis","8"
+ 5,"dispatched_pointer[0].axis_value_in_window[2].value","2.5541570186615"
+ 5,"dispatched_pointer[0].pointer_id","0"
+ 5,"dispatched_pointer[0].x_in_display","1762.0"
+ 5,"dispatched_pointer[0].y_in_display","580.0"
+ 5,"event_id","104114844"
+ 5,"resolved_flags","0"
+ 5,"vsync_id","0"
+ 5,"window_id","0"
+ """))
+
+ def test_key_dispatch_args(self):
+ return DiffTestBlueprint(
+ trace=Path('input_event_trace.textproto'),
+ query="""
+ INCLUDE PERFETTO MODULE android.input;
+ SELECT
+ d.id, args.key, args.display_value
+ FROM
+ android_input_event_dispatch AS d JOIN args ON d.arg_set_id = args.arg_set_id
+ WHERE d.event_id = 324105269
+ ORDER BY d.id, args.key;
+ """,
+ out=Csv("""
+ "id","key","display_value"
+ 16,"event_id","324105269"
+ 16,"resolved_flags","8"
+ 16,"vsync_id","182239"
+ 16,"window_id","181"
+ 17,"event_id","324105269"
+ 17,"resolved_flags","8"
+ 17,"vsync_id","0"
+ 17,"window_id","0"
+ """))
diff --git a/ui/src/chrome_extension/index.ts b/ui/src/chrome_extension/index.ts
index 73bd10a..df6c033 100644
--- a/ui/src/chrome_extension/index.ts
+++ b/ui/src/chrome_extension/index.ts
@@ -21,7 +21,7 @@
// Listen for messages from the perfetto ui.
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
-if (window.chrome) {
+if (globalThis.chrome) {
chrome.runtime.onConnectExternal.addListener((port) => {
chromeTraceController = new ChromeTracingController(port);
port.onMessage.addListener(onUIMessage);
diff --git a/ui/src/chrome_extension/manifest.json b/ui/src/chrome_extension/manifest.json
index 0a26e6a..d16e143 100644
--- a/ui/src/chrome_extension/manifest.json
+++ b/ui/src/chrome_extension/manifest.json
@@ -2,9 +2,9 @@
"name": "Perfetto UI",
"key":"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhm3X7qutsrskke84ltokTObnFJakd/d0XFQ6Ox2wQueHTGJM5GUNPTY/x8bdreNtGnfzvt/Sd0vABbR0wsS6lz5yY+g6ksMXJnigFe9N7uz8E3KojDrl3xYjIe+mkiJo8yxxzPydgb7GjQ6jmsX3g+yjj67kXzm9rZFkmoZ5WmqwBZlguPYVRN/W8CIIqBZkC3Qmq6uSG7b/g93YbwqmTmGiL2sAzgvXtqvDOD6503abtQkRC795E4VjJd+ffyeRH38fAEz5ZIrA6GJsfmov1TZTIu1NTwqylSpBYl5as7C6gpmuxDV4SvHvGT2hMQuIufDhZhErjI3B7bcX+XLe1wIDAQAB",
"description": "Enables the Perfetto trace viewer (https://ui.perfetto.dev) to record Chrome browser traces.",
- "version": "0.0.0.18",
- "manifest_version": 2,
- "minimum_chrome_version": "81.0.4022.0",
+ "version": "0.0.0.19",
+ "manifest_version": 3,
+ "minimum_chrome_version": "116.0.0.0",
"permissions": [
"declarativeContent",
"debugger"
@@ -13,10 +13,7 @@
"128": "logo-128.png"
},
"background": {
- "scripts": [
- "chrome_extension_bundle.js"
- ],
- "persistent": false
+ "service_worker": "chrome_extension_bundle.js"
},
"externally_connectable": {
// Only allow Perfetto UI hosts to connect to the extension. Do not add
diff --git a/ui/src/common/recordingV2/recording_utils.ts b/ui/src/common/recordingV2/recording_utils.ts
index c698dd5..152ad96 100644
--- a/ui/src/common/recordingV2/recording_utils.ts
+++ b/ui/src/common/recordingV2/recording_utils.ts
@@ -44,6 +44,12 @@
export function isLinux(userAgent: string) {
return userAgent.toLowerCase().includes(' linux ');
}
+// Sample user agent for Chrome on Windows:
+// Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,
+// like Gecko) Chrome/125.0.0.0 Safari/537.36
+export function isWindows(userAgent: string) {
+ return userAgent.toLowerCase().includes('windows ');
+}
// Sample user agent for Chrome on Chrome OS:
// "Mozilla/5.0 (X11; CrOS x86_64 14816.99.0) AppleWebKit/537.36
diff --git a/ui/src/common/recordingV2/target_factories/chrome_target_factory.ts b/ui/src/common/recordingV2/target_factories/chrome_target_factory.ts
index 6037a31..f68d2f4 100644
--- a/ui/src/common/recordingV2/target_factories/chrome_target_factory.ts
+++ b/ui/src/common/recordingV2/target_factories/chrome_target_factory.ts
@@ -89,6 +89,6 @@
// We only instantiate the factory if Perfetto UI is open in the Chrome browser.
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
-if (window.chrome && chrome.runtime) {
+if (globalThis.chrome && chrome.runtime) {
targetFactoryRegistry.register(new ChromeTargetFactory());
}
diff --git a/ui/src/common/recordingV2/target_factories/host_os_target_factory.ts b/ui/src/common/recordingV2/target_factories/host_os_target_factory.ts
index 1e5ed68..24eac91 100644
--- a/ui/src/common/recordingV2/target_factories/host_os_target_factory.ts
+++ b/ui/src/common/recordingV2/target_factories/host_os_target_factory.ts
@@ -18,7 +18,7 @@
RecordingTargetV2,
TargetFactory,
} from '../recording_interfaces_v2';
-import {isLinux, isMacOs} from '../recording_utils';
+import {isLinux, isMacOs, isWindows} from '../recording_utils';
import {targetFactoryRegistry} from '../target_factory_registry';
import {HostOsTarget} from '../targets/host_os_target';
@@ -79,7 +79,11 @@
}
}
-// We instantiate the host target factory only on Mac and Linux.
-if (isMacOs(navigator.userAgent) || isLinux(navigator.userAgent)) {
+// We instantiate the host target factory only on Mac, Linux, and Windows.
+if (
+ isMacOs(navigator.userAgent) ||
+ isLinux(navigator.userAgent) ||
+ isWindows(navigator.userAgent)
+) {
targetFactoryRegistry.register(new HostOsTargetFactory());
}
diff --git a/ui/src/common/recordingV2/targets/host_os_target.ts b/ui/src/common/recordingV2/targets/host_os_target.ts
index 7b32fcc..06962f8 100644
--- a/ui/src/common/recordingV2/targets/host_os_target.ts
+++ b/ui/src/common/recordingV2/targets/host_os_target.ts
@@ -26,12 +26,13 @@
import {
isLinux,
isMacOs,
+ isWindows,
WEBSOCKET_CLOSED_ABNORMALLY_CODE,
} from '../recording_utils';
import {TracedTracingSession} from '../traced_tracing_session';
export class HostOsTarget implements RecordingTargetV2 {
- private readonly targetType: 'LINUX' | 'MACOS';
+ private readonly targetType: 'LINUX' | 'MACOS' | 'WINDOWS';
private readonly name: string;
private websocket: WebSocket;
private streams = new Set<HostOsByteStream>();
@@ -49,6 +50,9 @@
} else if (isLinux(navigator.userAgent)) {
this.name = 'Linux';
this.targetType = 'LINUX';
+ } else if (isWindows(navigator.userAgent)) {
+ this.name = 'Windows Desktop';
+ this.targetType = 'WINDOWS';
} else {
throw new RecordingError(
'Host OS target created on an unsupported operating system.',
diff --git a/ui/src/controller/record_controller.ts b/ui/src/controller/record_controller.ts
index 1398581..ba87a8b 100644
--- a/ui/src/controller/record_controller.ts
+++ b/ui/src/controller/record_controller.ts
@@ -24,6 +24,7 @@
AdbRecordingTarget,
isAdbTarget,
isChromeTarget,
+ isWindowsTarget,
RecordingTarget,
} from '../common/state';
import {globals} from '../frontend/globals';
@@ -398,7 +399,7 @@
const controllerPromise = new Promise<RpcConsumerPort>(
async (resolve, _) => {
let controller: RpcConsumerPort | undefined = undefined;
- if (isChromeTarget(target)) {
+ if (isChromeTarget(target) || isWindowsTarget(target)) {
controller = new ChromeExtensionConsumerPort(
this.extensionPort,
this,
diff --git a/ui/src/core_plugins/async_slices/async_slice_track.ts b/ui/src/core_plugins/async_slices/async_slice_track.ts
index 275c418..b191d04 100644
--- a/ui/src/core_plugins/async_slices/async_slice_track.ts
+++ b/ui/src/core_plugins/async_slices/async_slice_track.ts
@@ -32,15 +32,15 @@
getSqlSource(): string {
return `
- select
- ts,
- dur,
- layout_depth as depth,
- ifnull(name, '[null]') as name,
- id,
- thread_dur as threadDur
- from experimental_slice_layout
- where filter_track_ids = '${this.trackIds.join(',')}'
+ select
+ ts,
+ dur,
+ layout_depth as depth,
+ ifnull(name, '[null]') as name,
+ id,
+ thread_dur as threadDur
+ from experimental_slice_layout
+ where filter_track_ids = '${this.trackIds.join(',')}'
`;
}
diff --git a/ui/src/core_plugins/chrome_scroll_jank/chrome_tasks_scroll_jank_track.ts b/ui/src/core_plugins/chrome_scroll_jank/chrome_tasks_scroll_jank_track.ts
index a8273b4..c57a63e 100644
--- a/ui/src/core_plugins/chrome_scroll_jank/chrome_tasks_scroll_jank_track.ts
+++ b/ui/src/core_plugins/chrome_scroll_jank/chrome_tasks_scroll_jank_track.ts
@@ -36,9 +36,16 @@
}
getSqlSource(): string {
- return `select s2.ts as ts, s2.dur as dur, s2.id as id, 0 as depth, s1.full_name as name
-from chrome_tasks_delaying_input_processing s1
-join slice s2 on s2.id=s1.slice_id`;
+ return `
+ select
+ s2.ts as ts,
+ s2.dur as dur,
+ s2.id as id,
+ 0 as depth,
+ s1.full_name as name
+ from chrome_tasks_delaying_input_processing s1
+ join slice s2 on s2.id=s1.slice_id
+ `;
}
}
export type GetTrackGroupUuidFn = (utid: number, upid: number | null) => string;
@@ -55,10 +62,12 @@
}
const queryResult = await engine.query(`
- select utid, upid
+ select
+ utid,
+ upid
from thread
where name='CrBrowserMain'
- `);
+ `);
const it = queryResult.iter({
utid: NUM,
diff --git a/ui/src/core_plugins/counter/trace_processor_counter_track.ts b/ui/src/core_plugins/counter/trace_processor_counter_track.ts
index a80ac4b..b47a03d 100644
--- a/ui/src/core_plugins/counter/trace_processor_counter_track.ts
+++ b/ui/src/core_plugins/counter/trace_processor_counter_track.ts
@@ -37,7 +37,13 @@
}
getSqlSource() {
- return `select ts, value from ${this.rootTable} where track_id = ${this.trackId}`;
+ return `
+ select
+ ts,
+ value
+ from ${this.rootTable}
+ where track_id = ${this.trackId}
+ `;
}
onMouseClick({x}: {x: number}): boolean {
diff --git a/ui/src/core_plugins/heap_profile/heap_profile_track.ts b/ui/src/core_plugins/heap_profile/heap_profile_track.ts
index 33e28f8..264a216 100644
--- a/ui/src/core_plugins/heap_profile/heap_profile_track.ts
+++ b/ui/src/core_plugins/heap_profile/heap_profile_track.ts
@@ -52,10 +52,11 @@
}
getSqlSource(): string {
- return `select
- *,
- 0 AS dur,
- 0 AS depth
+ return `
+ select
+ *,
+ 0 AS dur,
+ 0 AS depth
from (
select distinct
id,
@@ -70,7 +71,8 @@
'graph' AS type
from heap_graph_object
where upid = ${this.upid}
- )`;
+ )
+ `;
}
getRowSpec(): HeapProfileRow {
diff --git a/ui/src/core_plugins/sched/active_cpu_count.ts b/ui/src/core_plugins/sched/active_cpu_count.ts
index 2ab49bc..f3a4eee 100644
--- a/ui/src/core_plugins/sched/active_cpu_count.ts
+++ b/ui/src/core_plugins/sched/active_cpu_count.ts
@@ -96,10 +96,10 @@
this.config!.cpuType,
)})`;
return `
- select
- ts,
- active_cpu_count as value
- from ${sourceTable}
+ select
+ ts,
+ active_cpu_count as value
+ from ${sourceTable}
`;
}
}
diff --git a/ui/src/core_plugins/sched/runnable_thread_count.ts b/ui/src/core_plugins/sched/runnable_thread_count.ts
index f5ab7ca..894824e 100644
--- a/ui/src/core_plugins/sched/runnable_thread_count.ts
+++ b/ui/src/core_plugins/sched/runnable_thread_count.ts
@@ -70,10 +70,10 @@
getSqlSource() {
return `
- select
- ts,
- runnable_thread_count as value
- from sched_runnable_thread_count
+ select
+ ts,
+ runnable_thread_count as value
+ from sched_runnable_thread_count
`;
}
}
diff --git a/ui/src/core_plugins/thread_slice/thread_slice_track.ts b/ui/src/core_plugins/thread_slice/thread_slice_track.ts
index 3e50028..90e9f90 100644
--- a/ui/src/core_plugins/thread_slice/thread_slice_track.ts
+++ b/ui/src/core_plugins/thread_slice/thread_slice_track.ts
@@ -60,15 +60,17 @@
}
getSqlSource(): string {
- return `select
- ts,
- dur,
- id,
- depth,
- ifnull(name, '') as name,
- thread_dur as threadDur
- from ${this.tableName}
- where track_id = ${this.trackId}`;
+ return `
+ select
+ ts,
+ dur,
+ id,
+ depth,
+ ifnull(name, '') as name,
+ thread_dur as threadDur
+ from ${this.tableName}
+ where track_id = ${this.trackId}
+ `;
}
// Converts a SQL result row to an "Impl" Slice.
diff --git a/ui/src/frontend/record_page.ts b/ui/src/frontend/record_page.ts
index 49cc7b3..cd5aef3 100644
--- a/ui/src/frontend/record_page.ts
+++ b/ui/src/frontend/record_page.ts
@@ -436,6 +436,14 @@
'.note',
`To trace Chrome from the Perfetto UI, you need to install our `,
m('a', {href: extensionURL, target: '_blank'}, 'Chrome extension'),
+ ' and then reload this page. ',
+ );
+
+ const msgWinEtw = m(
+ '.note',
+ `To trace with Etw on Windows from the Perfetto UI, you to run chrome with`,
+ `administrator permission and you need to install our `,
+ m('a', {href: extensionURL, target: '_blank'}, 'Chrome extension'),
' and then reload this page.',
);
@@ -488,6 +496,9 @@
case 'CrOS':
if (!globals.state.extensionInstalled) notes.push(msgChrome);
break;
+ case 'Win':
+ if (!globals.state.extensionInstalled) notes.push(msgWinEtw);
+ break;
default:
}
if (globals.state.recordConfig.mode === 'LONG_TRACE') {
@@ -575,7 +586,10 @@
) {
buttons.push(start);
}
- } else if (isChromeTarget(target) && state.extensionInstalled) {
+ } else if (
+ (isWindowsTarget(target) || isChromeTarget(target)) &&
+ state.extensionInstalled
+ ) {
buttons.push(start);
}
return m('.button', buttons);
@@ -605,7 +619,11 @@
autosaveConfigStore.save(globals.state.recordConfig);
const target = globals.state.recordingTarget;
- if (isAndroidTarget(target) || isChromeTarget(target)) {
+ if (
+ isAndroidTarget(target) ||
+ isChromeTarget(target) ||
+ isWindowsTarget(target)
+ ) {
globals.logging.logEvent('Record Trace', `Record trace (${target.os})`);
globals.dispatch(Actions.startRecording({}));
}
diff --git a/ui/src/frontend/record_page_v2.ts b/ui/src/frontend/record_page_v2.ts
index dc1d015..797024f 100644
--- a/ui/src/frontend/record_page_v2.ts
+++ b/ui/src/frontend/record_page_v2.ts
@@ -318,7 +318,7 @@
function RecordingSnippet(targetInfo: TargetInfo) {
// We don't need commands to start tracing on chrome
- if (isChromeTargetInfo(targetInfo)) {
+ if (isChromeTargetInfo(targetInfo) || targetInfo.targetType === 'WINDOWS') {
if (controller.getState() > RecordingState.AUTH_P2) {
// If the UI has started tracing, don't display a message guiding the user
// to start recording.
diff --git a/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts b/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts
index 79af0e1..3f8265b 100644
--- a/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts
@@ -100,7 +100,7 @@
when t.name glob '*rmnet*' then 'modem'
else 'unknown'
end as dev_type,
- lower(substr(t.name, instr(t.name, ' ') + 1, 1)) || 'x' as dir,
+ s.name as pkg,
sum(EXTRACT_ARG(arg_set_id, 'packet_length')) AS value
from slice s join track t on s.track_id = t.id
where (t.name glob '*Received' or t.name glob '*Transmitted')
@@ -111,14 +111,14 @@
select
ts,
dev_type,
- dir,
+ pkg,
value
from base
union all
select
ts + 5000000000 as ts,
dev_type,
- dir,
+ pkg,
0 as value
from base
),
@@ -126,7 +126,7 @@
select
ts,
dev_type,
- dir,
+ pkg,
sum(value) as value
from zeroes
group by 1, 2, 3
@@ -1277,69 +1277,25 @@
if (features.has('net.wifi')) {
this.addCounterTrack(
ctx,
- 'Wifi bytes',
+ 'Wifi total',
`select ts, sum(value) as value from network_summary where dev_type = 'wifi' group by 1`,
groupName,
{yDisplay: 'log', yRangeSharingKey: 'net_bytes', unit: 'byte'},
);
- this.addCounterTrack(
- ctx,
- 'Wifi TX bytes',
- `select ts, value from network_summary where dev_type = 'wifi' and dir = 'tx'`,
- groupName,
- {yDisplay: 'log', yRangeSharingKey: 'net_bytes', unit: 'byte'},
+ const result = await e.query(
+ `select pkg, sum(value) from network_summary where dev_type='wifi' group by 1 order by 2 desc limit 10`,
);
- this.addCounterTrack(
- ctx,
- 'Wifi RX bytes',
- `select ts, value from network_summary where dev_type = 'wifi' and dir = 'rx'`,
- groupName,
- {yDisplay: 'log', yRangeSharingKey: 'net_bytes', unit: 'byte'},
- );
+ const it = result.iter({pkg: 'str'});
+ for (; it.valid(); it.next()) {
+ this.addCounterTrack(
+ ctx,
+ `Top wifi: ${it.pkg}`,
+ `select ts, value from network_summary where dev_type = 'wifi' and pkg = '${it.pkg}'`,
+ groupName,
+ {yDisplay: 'log', yRangeSharingKey: 'net_bytes', unit: 'byte'},
+ );
+ }
}
- if (features.has('net.modem')) {
- this.addCounterTrack(
- ctx,
- 'Modem bytes',
- `select ts, sum(value) as value from network_summary where dev_type = 'modem' group by 1`,
- groupName,
- {yDisplay: 'log', yRangeSharingKey: 'net_bytes', unit: 'byte'},
- );
- this.addCounterTrack(
- ctx,
- 'Modem TX bytes',
- `select ts, value from network_summary where dev_type = 'modem' and dir = 'tx'`,
- groupName,
- {yDisplay: 'log', yRangeSharingKey: 'net_bytes', unit: 'byte'},
- );
- this.addCounterTrack(
- ctx,
- 'Modem RX bytes',
- `select ts, value from network_summary where dev_type = 'modem' and dir = 'rx'`,
- groupName,
- {yDisplay: 'log', yRangeSharingKey: 'net_bytes', unit: 'byte'},
- );
- }
- this.addBatteryStatsState(
- ctx,
- 'Cellular interface',
- 'battery_stats.mobile_radio',
- groupName,
- features,
- );
- this.addSliceTrack(
- ctx,
- 'Cellular connection',
- `select ts, dur, name from radio_transport`,
- groupName,
- );
- this.addBatteryStatsState(
- ctx,
- 'Cellular strength',
- 'battery_stats.phone_signal_strength',
- groupName,
- features,
- );
this.addBatteryStatsState(
ctx,
'Wifi interface',
@@ -1361,6 +1317,48 @@
groupName,
features,
);
+ if (features.has('net.modem')) {
+ this.addCounterTrack(
+ ctx,
+ 'Modem total',
+ `select ts, sum(value) as value from network_summary where dev_type = 'modem' group by 1`,
+ groupName,
+ {yDisplay: 'log', yRangeSharingKey: 'net_bytes', unit: 'byte'},
+ );
+ const result = await e.query(
+ `select pkg, sum(value) from network_summary where dev_type='modem' group by 1 order by 2 desc limit 10`,
+ );
+ const it = result.iter({pkg: 'str'});
+ for (; it.valid(); it.next()) {
+ this.addCounterTrack(
+ ctx,
+ `Top modem: ${it.pkg}`,
+ `select ts, value from network_summary where dev_type = 'modem' and pkg = '${it.pkg}'`,
+ groupName,
+ {yDisplay: 'log', yRangeSharingKey: 'net_bytes', unit: 'byte'},
+ );
+ }
+ }
+ this.addBatteryStatsState(
+ ctx,
+ 'Cellular interface',
+ 'battery_stats.mobile_radio',
+ groupName,
+ features,
+ );
+ this.addSliceTrack(
+ ctx,
+ 'Cellular connection',
+ `select ts, dur, name from radio_transport`,
+ groupName,
+ );
+ this.addBatteryStatsState(
+ ctx,
+ 'Cellular strength',
+ 'battery_stats.phone_signal_strength',
+ groupName,
+ features,
+ );
}
async addModemDetail(