Added support for importing v1 flow events from json
Previously we didn't import flow events to the database.
Added FlowTracker - a data structure that helps to import flow events
Added additional logic to SliceTracker, now it interacts with
FlowTracker and notifies about any new slices via callback.
Flow Events version 1 are now importing to the database, table "flow",
support for version 2 would be added in the following CLs
Bug: 162311144
Change-Id: I570675e883f5ecbbdd8966a7e253bde85fc915b8
diff --git a/Android.bp b/Android.bp
index 1bf9cf2..802ef91 100644
--- a/Android.bp
+++ b/Android.bp
@@ -6712,6 +6712,7 @@
"src/trace_processor/importers/common/args_tracker.cc",
"src/trace_processor/importers/common/clock_tracker.cc",
"src/trace_processor/importers/common/event_tracker.cc",
+ "src/trace_processor/importers/common/flow_tracker.cc",
"src/trace_processor/importers/common/global_args_tracker.cc",
"src/trace_processor/importers/common/process_tracker.cc",
"src/trace_processor/importers/common/slice_tracker.cc",
@@ -6726,6 +6727,7 @@
srcs: [
"src/trace_processor/importers/common/clock_tracker_unittest.cc",
"src/trace_processor/importers/common/event_tracker_unittest.cc",
+ "src/trace_processor/importers/common/flow_tracker_unittest.cc",
"src/trace_processor/importers/common/process_tracker_unittest.cc",
"src/trace_processor/importers/common/slice_tracker_unittest.cc",
],
diff --git a/BUILD b/BUILD
index 72eae9b..a63cb85 100644
--- a/BUILD
+++ b/BUILD
@@ -727,6 +727,8 @@
"src/trace_processor/importers/common/clock_tracker.h",
"src/trace_processor/importers/common/event_tracker.cc",
"src/trace_processor/importers/common/event_tracker.h",
+ "src/trace_processor/importers/common/flow_tracker.cc",
+ "src/trace_processor/importers/common/flow_tracker.h",
"src/trace_processor/importers/common/global_args_tracker.cc",
"src/trace_processor/importers/common/global_args_tracker.h",
"src/trace_processor/importers/common/process_tracker.cc",
@@ -876,6 +878,7 @@
srcs = [
"src/trace_processor/tables/android_tables.h",
"src/trace_processor/tables/counter_tables.h",
+ "src/trace_processor/tables/flow_tables.h",
"src/trace_processor/tables/macros.h",
"src/trace_processor/tables/macros_internal.h",
"src/trace_processor/tables/metadata_tables.h",
diff --git a/src/trace_processor/importers/BUILD.gn b/src/trace_processor/importers/BUILD.gn
index 082f7bb..8529ee8 100644
--- a/src/trace_processor/importers/BUILD.gn
+++ b/src/trace_processor/importers/BUILD.gn
@@ -22,6 +22,8 @@
"common/clock_tracker.h",
"common/event_tracker.cc",
"common/event_tracker.h",
+ "common/flow_tracker.cc",
+ "common/flow_tracker.h",
"common/global_args_tracker.cc",
"common/global_args_tracker.h",
"common/process_tracker.cc",
@@ -48,6 +50,7 @@
sources = [
"common/clock_tracker_unittest.cc",
"common/event_tracker_unittest.cc",
+ "common/flow_tracker_unittest.cc",
"common/process_tracker_unittest.cc",
"common/slice_tracker_unittest.cc",
]
diff --git a/src/trace_processor/importers/common/flow_tracker.cc b/src/trace_processor/importers/common/flow_tracker.cc
new file mode 100644
index 0000000..4e95530
--- /dev/null
+++ b/src/trace_processor/importers/common/flow_tracker.cc
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2020 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 <limits>
+
+#include <stdint.h>
+
+#include "src/trace_processor/importers/common/flow_tracker.h"
+#include "src/trace_processor/importers/common/slice_tracker.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+FlowTracker::FlowTracker(TraceProcessorContext* context) : context_(context) {}
+
+FlowTracker::~FlowTracker() = default;
+
+/* TODO: if we report a flow event earlier that a corresponding slice then
+ flow event would not be added, and it will increase "flow_no_enclosing_slice"
+ In catapult, it was possible to report a flow after an enclosing slice if
+ timestamps were equal. But because of our seqential processing of a trace
+ it is a bit tricky to make it here.
+ We suspect that this case is too rare or impossible */
+void FlowTracker::Begin(TrackId track_id, FlowId flow_id) {
+ base::Optional<SliceId> open_slice_id =
+ context_->slice_tracker->GetTopmostSliceOnTrack(track_id);
+ if (!open_slice_id) {
+ context_->storage->IncrementStats(stats::flow_no_enclosing_slice);
+ return;
+ }
+ if (flow_to_slice_map_.count(flow_id) != 0) {
+ context_->storage->IncrementStats(stats::flow_duplicate_id);
+ return;
+ }
+ flow_to_slice_map_[flow_id] = open_slice_id.value();
+}
+
+void FlowTracker::Step(TrackId track_id, FlowId flow_id) {
+ base::Optional<SliceId> open_slice_id =
+ context_->slice_tracker->GetTopmostSliceOnTrack(track_id);
+ if (!open_slice_id) {
+ context_->storage->IncrementStats(stats::flow_no_enclosing_slice);
+ return;
+ }
+ if (flow_to_slice_map_.count(flow_id) == 0) {
+ context_->storage->IncrementStats(stats::flow_step_without_start);
+ return;
+ }
+ SliceId slice_out_id = flow_to_slice_map_[flow_id];
+ InsertFlow(slice_out_id, open_slice_id.value());
+ flow_to_slice_map_[flow_id] = open_slice_id.value();
+}
+
+void FlowTracker::End(TrackId track_id,
+ FlowId flow_id,
+ bool bind_enclosing_slice) {
+ if (!bind_enclosing_slice) {
+ pending_flow_ids_map_[track_id].push_back(flow_id);
+ return;
+ }
+ base::Optional<SliceId> open_slice_id =
+ context_->slice_tracker->GetTopmostSliceOnTrack(track_id);
+ if (!open_slice_id) {
+ context_->storage->IncrementStats(stats::flow_no_enclosing_slice);
+ return;
+ }
+ if (flow_to_slice_map_.count(flow_id) == 0) {
+ context_->storage->IncrementStats(stats::flow_end_without_start);
+ return;
+ }
+ SliceId slice_out_id = flow_to_slice_map_[flow_id];
+ InsertFlow(slice_out_id, open_slice_id.value());
+ // TODO(andrewbb): Don't erase the flow_id if we're a version 2 event.
+ flow_to_slice_map_.erase(flow_id);
+}
+
+FlowId FlowTracker::GetFlowIdForV1Event(uint64_t source_id,
+ StringId cat,
+ StringId name) {
+ V1FlowId v1_flow_id = {source_id, cat, name};
+ auto iter = v1_flow_id_to_flow_id_map_.find(v1_flow_id);
+ if (iter != v1_flow_id_to_flow_id_map_.end()) {
+ return iter->second;
+ }
+ return v1_flow_id_to_flow_id_map_[v1_flow_id] = v1_id_counter_++;
+}
+
+void FlowTracker::ClosePendingEventsOnTrack(TrackId track_id,
+ SliceId slice_id) {
+ auto iter = pending_flow_ids_map_.find(track_id);
+ if (iter == pending_flow_ids_map_.end())
+ return;
+
+ for (FlowId flow_id : iter->second) {
+ SliceId slice_out_id = flow_to_slice_map_[flow_id];
+ InsertFlow(slice_out_id, slice_id);
+ }
+
+ pending_flow_ids_map_.erase(iter);
+}
+
+void FlowTracker::InsertFlow(SliceId slice_out_id, SliceId slice_in_id) {
+ tables::FlowTable::Row row(slice_out_id, slice_in_id);
+ context_->storage->mutable_flow_table()->Insert(row);
+}
+
+} // namespace trace_processor
+} // namespace perfetto
diff --git a/src/trace_processor/importers/common/flow_tracker.h b/src/trace_processor/importers/common/flow_tracker.h
new file mode 100644
index 0000000..e1b734b
--- /dev/null
+++ b/src/trace_processor/importers/common/flow_tracker.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2020 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_COMMON_FLOW_TRACKER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_FLOW_TRACKER_H_
+
+#include <stdint.h>
+
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+using FlowId = uint32_t;
+
+class FlowTracker {
+ public:
+ explicit FlowTracker(TraceProcessorContext*);
+ ~FlowTracker();
+
+ void Begin(TrackId track_id, FlowId flow_id);
+
+ void Step(TrackId track_id, FlowId flow_id);
+
+ void End(TrackId track_id, FlowId flow_id, bool bind_enclosing_slice);
+
+ FlowId GetFlowIdForV1Event(uint64_t source_id, StringId cat, StringId name);
+
+ void ClosePendingEventsOnTrack(TrackId track_id, SliceId slice_id);
+
+ private:
+ struct V1FlowId {
+ uint64_t source_id;
+ StringId cat;
+ StringId name;
+
+ bool operator==(const V1FlowId& o) const {
+ return o.source_id == source_id && o.cat == cat && o.name == name;
+ }
+ };
+
+ struct V1FlowIdHasher {
+ size_t operator()(const V1FlowId& c) const {
+ base::Hash hasher;
+ hasher.Update(c.source_id);
+ hasher.Update(c.cat.raw_id());
+ hasher.Update(c.name.raw_id());
+ return std::hash<uint64_t>{}(hasher.digest());
+ }
+ };
+
+ using FlowToSourceSliceMap = std::unordered_map<FlowId, SliceId>;
+ using PendingFlowIdsMap = std::unordered_map<TrackId, std::vector<FlowId>>;
+ using V1FlowIdToFlowIdMap =
+ std::unordered_map<V1FlowId, FlowId, V1FlowIdHasher>;
+
+ void InsertFlow(SliceId outgoing_slice_id, SliceId incoming_slice_id);
+
+ // List of flow end calls waiting for the next slice
+ PendingFlowIdsMap pending_flow_ids_map_;
+ // Flows generated by Begin() or Step()
+ FlowToSourceSliceMap flow_to_slice_map_;
+
+ V1FlowIdToFlowIdMap v1_flow_id_to_flow_id_map_;
+ uint32_t v1_id_counter_ = 0;
+
+ TraceProcessorContext* const context_;
+};
+
+} // namespace trace_processor
+} // namespace perfetto
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_FLOW_TRACKER_H_
diff --git a/src/trace_processor/importers/common/flow_tracker_unittest.cc b/src/trace_processor/importers/common/flow_tracker_unittest.cc
new file mode 100644
index 0000000..b17d6a4
--- /dev/null
+++ b/src/trace_processor/importers/common/flow_tracker_unittest.cc
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <vector>
+
+#include "src/trace_processor/importers/common/flow_tracker.h"
+#include "src/trace_processor/importers/common/slice_tracker.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace {
+
+using ::testing::Eq;
+
+TEST(FlowTrackerTest, SingleFlowEventExplicitInSliceBinding) {
+ TraceProcessorContext context;
+ context.storage.reset(new TraceStorage());
+ context.slice_tracker.reset(new SliceTracker(&context));
+ auto& slice_tracker = context.slice_tracker;
+ FlowTracker tracker(&context);
+ slice_tracker->SetOnSliceBeginCallback(
+ [&tracker](TrackId track_id, SliceId slice_id) {
+ tracker.ClosePendingEventsOnTrack(track_id, slice_id);
+ });
+
+ FlowId flow_id = 1;
+ TrackId track_1(1);
+ TrackId track_2(2);
+
+ slice_tracker->Begin(100, track_1, StringId::Raw(1), StringId::Raw(1));
+ SliceId out_slice_id = slice_tracker->GetTopmostSliceOnTrack(track_1).value();
+ tracker.Begin(track_1, flow_id);
+ slice_tracker->End(120, track_1, StringId::Raw(1), StringId::Raw(1));
+
+ slice_tracker->Begin(140, track_2, StringId::Raw(2), StringId::Raw(2));
+ SliceId in_slice_id = slice_tracker->GetTopmostSliceOnTrack(track_2).value();
+ tracker.End(track_2, flow_id, true);
+ slice_tracker->End(160, track_2, StringId::Raw(2), StringId::Raw(2));
+
+ const auto& flows = context.storage->flow_table();
+ EXPECT_EQ(flows.row_count(), 1u);
+ EXPECT_EQ(flows.slice_out()[0], out_slice_id);
+ EXPECT_EQ(flows.slice_in()[0], in_slice_id);
+}
+
+TEST(FlowTrackerTest, SingleFlowEventWaitForNextSlice) {
+ TraceProcessorContext context;
+ context.storage.reset(new TraceStorage());
+ context.slice_tracker.reset(new SliceTracker(&context));
+ auto& slice_tracker = context.slice_tracker;
+ FlowTracker tracker(&context);
+ slice_tracker->SetOnSliceBeginCallback(
+ [&tracker](TrackId track_id, SliceId slice_id) {
+ tracker.ClosePendingEventsOnTrack(track_id, slice_id);
+ });
+
+ FlowId flow_id = 1;
+ TrackId track_1(1);
+ TrackId track_2(2);
+
+ slice_tracker->Begin(100, track_1, StringId::Raw(1), StringId::Raw(1));
+ SliceId out_slice_id = slice_tracker->GetTopmostSliceOnTrack(track_1).value();
+ tracker.Begin(track_1, flow_id);
+ slice_tracker->End(120, track_1, StringId::Raw(1), StringId::Raw(1));
+
+ tracker.End(track_2, flow_id, false);
+
+ const auto& flows = context.storage->flow_table();
+
+ EXPECT_EQ(flows.row_count(), 0u);
+
+ slice_tracker->Begin(140, track_2, StringId::Raw(2), StringId::Raw(2));
+ SliceId in_slice_id = slice_tracker->GetTopmostSliceOnTrack(track_2).value();
+ slice_tracker->End(160, track_2, StringId::Raw(2), StringId::Raw(2));
+
+ EXPECT_EQ(flows.row_count(), 1u);
+ EXPECT_EQ(flows.slice_out()[0], out_slice_id);
+ EXPECT_EQ(flows.slice_in()[0], in_slice_id);
+}
+
+TEST(FlowTrackerTest, SingleFlowEventWaitForNextSliceScoped) {
+ TraceProcessorContext context;
+ context.storage.reset(new TraceStorage());
+ context.slice_tracker.reset(new SliceTracker(&context));
+ auto& slice_tracker = context.slice_tracker;
+ FlowTracker tracker(&context);
+ slice_tracker->SetOnSliceBeginCallback(
+ [&tracker](TrackId track_id, SliceId slice_id) {
+ tracker.ClosePendingEventsOnTrack(track_id, slice_id);
+ });
+
+ FlowId flow_id = 1;
+ TrackId track_1(1);
+ TrackId track_2(2);
+
+ slice_tracker->Begin(100, track_1, StringId::Raw(1), StringId::Raw(1));
+ SliceId out_slice_id = slice_tracker->GetTopmostSliceOnTrack(track_1).value();
+ tracker.Begin(track_1, flow_id);
+ slice_tracker->End(120, track_1, StringId::Raw(1), StringId::Raw(1));
+
+ tracker.End(track_2, flow_id, false);
+
+ const auto& flows = context.storage->flow_table();
+
+ EXPECT_EQ(flows.row_count(), 0u);
+
+ slice_tracker->Scoped(140, track_2, StringId::Raw(2), StringId::Raw(2), 100);
+ SliceId in_slice_id = slice_tracker->GetTopmostSliceOnTrack(track_2).value();
+
+ EXPECT_EQ(flows.row_count(), 1u);
+ EXPECT_EQ(flows.slice_out()[0], out_slice_id);
+ EXPECT_EQ(flows.slice_in()[0], in_slice_id);
+}
+
+TEST(FlowTrackerTest, TwoFlowEventsWaitForNextSlice) {
+ TraceProcessorContext context;
+ context.storage.reset(new TraceStorage());
+ context.slice_tracker.reset(new SliceTracker(&context));
+ auto& slice_tracker = context.slice_tracker;
+ FlowTracker tracker(&context);
+ slice_tracker->SetOnSliceBeginCallback(
+ [&tracker](TrackId track_id, SliceId slice_id) {
+ tracker.ClosePendingEventsOnTrack(track_id, slice_id);
+ });
+
+ FlowId flow1_id = 1;
+ FlowId flow2_id = 2;
+ TrackId track_1(1);
+ TrackId track_2(2);
+
+ // begin flow1 in enclosing slice1
+ slice_tracker->Begin(100, track_1, StringId::Raw(1), StringId::Raw(1));
+ SliceId out_slice1_id =
+ slice_tracker->GetTopmostSliceOnTrack(track_1).value();
+ tracker.Begin(track_1, flow1_id);
+ tracker.End(track_2, flow1_id, false);
+ slice_tracker->End(120, track_1, StringId::Raw(1), StringId::Raw(1));
+
+ // begin flow2 in enclosing slice2
+ slice_tracker->Begin(130, track_1, StringId::Raw(2), StringId::Raw(2));
+ SliceId out_slice2_id =
+ slice_tracker->GetTopmostSliceOnTrack(track_1).value();
+ tracker.Begin(track_1, flow2_id);
+ tracker.End(track_2, flow2_id, false);
+ slice_tracker->End(140, track_1, StringId::Raw(2), StringId::Raw(2));
+
+ const auto& flows = context.storage->flow_table();
+
+ EXPECT_EQ(flows.row_count(), 0u);
+
+ // close all pending flows
+ slice_tracker->Begin(160, track_2, StringId::Raw(3), StringId::Raw(3));
+ SliceId in_slice_id = slice_tracker->GetTopmostSliceOnTrack(track_2).value();
+ slice_tracker->End(170, track_2, StringId::Raw(3), StringId::Raw(3));
+
+ EXPECT_EQ(flows.row_count(), 2u);
+ EXPECT_EQ(flows.slice_out()[0], out_slice1_id);
+ EXPECT_EQ(flows.slice_in()[0], in_slice_id);
+ EXPECT_EQ(flows.slice_out()[1], out_slice2_id);
+ EXPECT_EQ(flows.slice_in()[1], in_slice_id);
+}
+
+TEST(FlowTrackerTest, TwoFlowEventsSliceInSlice) {
+ TraceProcessorContext context;
+ context.storage.reset(new TraceStorage());
+ context.slice_tracker.reset(new SliceTracker(&context));
+ auto& slice_tracker = context.slice_tracker;
+ FlowTracker tracker(&context);
+ slice_tracker->SetOnSliceBeginCallback(
+ [&tracker](TrackId track_id, SliceId slice_id) {
+ tracker.ClosePendingEventsOnTrack(track_id, slice_id);
+ });
+
+ FlowId flow1_id = 1;
+ FlowId flow2_id = 2;
+ TrackId track_1(1);
+ TrackId track_2(2);
+
+ // start two nested slices
+ slice_tracker->Begin(100, track_1, StringId::Raw(1), StringId::Raw(1));
+ SliceId out_slice1_id =
+ slice_tracker->GetTopmostSliceOnTrack(track_1).value();
+ slice_tracker->Begin(120, track_1, StringId::Raw(2), StringId::Raw(2));
+ SliceId out_slice2_id =
+ slice_tracker->GetTopmostSliceOnTrack(track_1).value();
+
+ tracker.Begin(track_1, flow1_id);
+
+ slice_tracker->End(140, track_1, StringId::Raw(2), StringId::Raw(2));
+
+ tracker.Begin(track_1, flow2_id);
+
+ slice_tracker->End(150, track_1, StringId::Raw(1), StringId::Raw(1));
+
+ slice_tracker->Begin(160, track_2, StringId::Raw(3), StringId::Raw(3));
+ SliceId in_slice_id = slice_tracker->GetTopmostSliceOnTrack(track_2).value();
+
+ tracker.End(track_2, flow1_id, true);
+ tracker.End(track_2, flow2_id, true);
+
+ slice_tracker->End(170, track_2, StringId::Raw(3), StringId::Raw(3));
+
+ const auto& flows = context.storage->flow_table();
+ EXPECT_EQ(flows.row_count(), 2u);
+ EXPECT_EQ(flows.slice_out()[0], out_slice2_id);
+ EXPECT_EQ(flows.slice_in()[0], in_slice_id);
+ EXPECT_EQ(flows.slice_out()[1], out_slice1_id);
+ EXPECT_EQ(flows.slice_in()[1], in_slice_id);
+}
+
+TEST(FlowTrackerTest, FlowEventsWithStep) {
+ TraceProcessorContext context;
+ context.storage.reset(new TraceStorage());
+ context.slice_tracker.reset(new SliceTracker(&context));
+ auto& slice_tracker = context.slice_tracker;
+ FlowTracker tracker(&context);
+ slice_tracker->SetOnSliceBeginCallback(
+ [&tracker](TrackId track_id, SliceId slice_id) {
+ tracker.ClosePendingEventsOnTrack(track_id, slice_id);
+ });
+
+ FlowId flow_id = 1;
+ TrackId track_1(1);
+ TrackId track_2(2);
+
+ // flow begin inside slice1 on track1
+ slice_tracker->Begin(100, track_1, StringId::Raw(1), StringId::Raw(1));
+ SliceId out_slice1_id =
+ slice_tracker->GetTopmostSliceOnTrack(track_1).value();
+ tracker.Begin(track_1, flow_id);
+ slice_tracker->End(140, track_1, StringId::Raw(1), StringId::Raw(1));
+
+ // flow step inside slice2 on track2
+ slice_tracker->Begin(160, track_2, StringId::Raw(2), StringId::Raw(2));
+ SliceId inout_slice2_id =
+ slice_tracker->GetTopmostSliceOnTrack(track_2).value();
+ tracker.Step(track_2, flow_id);
+ slice_tracker->End(170, track_2, StringId::Raw(2), StringId::Raw(2));
+
+ // flow end inside slice3 on track3
+ slice_tracker->Begin(180, track_1, StringId::Raw(3), StringId::Raw(3));
+ SliceId in_slice_id = slice_tracker->GetTopmostSliceOnTrack(track_1).value();
+ tracker.End(track_1, flow_id, true);
+ slice_tracker->End(190, track_1, StringId::Raw(3), StringId::Raw(3));
+
+ const auto& flows = context.storage->flow_table();
+ EXPECT_EQ(flows.row_count(), 2u);
+ EXPECT_EQ(flows.slice_out()[0], out_slice1_id);
+ EXPECT_EQ(flows.slice_in()[0], inout_slice2_id);
+ EXPECT_EQ(flows.slice_out()[1], inout_slice2_id);
+ EXPECT_EQ(flows.slice_in()[1], in_slice_id);
+}
+
+} // namespace
+} // namespace trace_processor
+} // namespace perfetto
diff --git a/src/trace_processor/importers/common/slice_tracker.cc b/src/trace_processor/importers/common/slice_tracker.cc
index 29d64cb..dd5cffc 100644
--- a/src/trace_processor/importers/common/slice_tracker.cc
+++ b/src/trace_processor/importers/common/slice_tracker.cc
@@ -220,7 +220,7 @@
}
auto* slices = context_->storage->mutable_slice_table();
- MaybeCloseStack(timestamp, stack);
+ MaybeCloseStack(timestamp, stack, track_id);
const uint8_t depth = static_cast<uint8_t>(stack->size());
if (depth >= std::numeric_limits<uint8_t>::max()) {
@@ -235,8 +235,7 @@
SliceId id = inserter();
uint32_t slice_idx = *slices->id().IndexOf(id);
-
- stack->emplace_back(SliceInfo{slice_idx, ArgsTracker(context_)});
+ StackPush(track_id, slice_idx);
// Post fill all the relevant columns. All the other columns should have
// been filled by the inserter.
@@ -272,7 +271,7 @@
TrackInfo& track_info = it->second;
SlicesStack& stack = track_info.slice_stack;
- MaybeCloseStack(timestamp, &stack);
+ MaybeCloseStack(timestamp, &stack, track_id);
if (stack.empty())
return base::nullopt;
@@ -347,7 +346,25 @@
stacks_.clear();
}
-void SliceTracker::MaybeCloseStack(int64_t ts, SlicesStack* stack) {
+void SliceTracker::SetOnSliceBeginCallback(OnSliceBeginCallback callback) {
+ on_slice_begin_callback_ = callback;
+}
+
+base::Optional<SliceId> SliceTracker::GetTopmostSliceOnTrack(
+ TrackId track_id) const {
+ const auto iter = stacks_.find(track_id);
+ if (iter == stacks_.end())
+ return base::nullopt;
+ const auto& stack = iter->second.slice_stack;
+ if (stack.empty())
+ return base::nullopt;
+ uint32_t slice_idx = stack.back().row;
+ return context_->storage->slice_table().id()[slice_idx];
+}
+
+void SliceTracker::MaybeCloseStack(int64_t ts,
+ SlicesStack* stack,
+ TrackId track_id) {
auto* slices = context_->storage->mutable_slice_table();
bool incomplete_descendent = false;
for (int i = static_cast<int>(stack->size()) - 1; i >= 0; i--) {
@@ -387,18 +404,18 @@
uint32_t child_idx = (*stack)[static_cast<size_t>(j)].row;
PERFETTO_DCHECK(slices->dur()[child_idx] == kPendingDuration);
slices->mutable_dur()->Set(child_idx, end_ts - slices->ts()[child_idx]);
- stack->pop_back();
+ StackPop(track_id);
}
// Also pop the current row itself and reset the incomplete flag.
- stack->pop_back();
+ StackPop(track_id);
incomplete_descendent = false;
continue;
}
if (end_ts <= ts) {
- stack->pop_back();
+ StackPop(track_id);
}
}
}
@@ -424,5 +441,19 @@
return static_cast<int64_t>(hash.digest() & kSafeBitmask);
}
+void SliceTracker::StackPop(TrackId track_id) {
+ stacks_[track_id].slice_stack.pop_back();
+}
+
+void SliceTracker::StackPush(TrackId track_id, uint32_t slice_idx) {
+ stacks_[track_id].slice_stack.push_back(
+ SliceInfo{slice_idx, ArgsTracker(context_)});
+
+ const auto& slices = context_->storage->slice_table();
+ if (on_slice_begin_callback_) {
+ on_slice_begin_callback_(track_id, slices.id()[slice_idx]);
+ }
+}
+
} // namespace trace_processor
} // namespace perfetto
diff --git a/src/trace_processor/importers/common/slice_tracker.h b/src/trace_processor/importers/common/slice_tracker.h
index 6cf02e4..c54575e 100644
--- a/src/trace_processor/importers/common/slice_tracker.h
+++ b/src/trace_processor/importers/common/slice_tracker.h
@@ -31,6 +31,7 @@
class SliceTracker {
public:
using SetArgsCallback = std::function<void(ArgsTracker::BoundInserter*)>;
+ using OnSliceBeginCallback = std::function<void(TrackId, SliceId)>;
explicit SliceTracker(TraceProcessorContext*);
virtual ~SliceTracker();
@@ -102,6 +103,10 @@
void FlushPendingSlices();
+ void SetOnSliceBeginCallback(OnSliceBeginCallback callback);
+
+ base::Optional<SliceId> GetTopmostSliceOnTrack(TrackId track_id) const;
+
private:
struct SliceInfo {
uint32_t row;
@@ -130,14 +135,21 @@
SetArgsCallback args_callback,
std::function<base::Optional<uint32_t>(const SlicesStack&)> finder);
- void MaybeCloseStack(int64_t end_ts, SlicesStack*);
+ void MaybeCloseStack(int64_t end_ts, SlicesStack*, TrackId track_id);
base::Optional<uint32_t> MatchingIncompleteSliceIndex(
const SlicesStack& stack,
StringId name,
StringId category);
+
int64_t GetStackHash(const SlicesStack&);
+ void StackPop(TrackId track_id);
+ void StackPush(TrackId track_id, uint32_t slice_idx);
+ void FlowTrackerUpdate(TrackId track_id);
+
+ OnSliceBeginCallback on_slice_begin_callback_;
+
// Timestamp of the previous event. Used to discard events arriving out
// of order.
int64_t prev_timestamp_ = 0;
diff --git a/src/trace_processor/importers/common/slice_tracker_unittest.cc b/src/trace_processor/importers/common/slice_tracker_unittest.cc
index 35b1f5b..929e186 100644
--- a/src/trace_processor/importers/common/slice_tracker_unittest.cc
+++ b/src/trace_processor/importers/common/slice_tracker_unittest.cc
@@ -305,6 +305,72 @@
EXPECT_EQ(context.storage->slice_table().depth()[3], 0u);
}
+TEST(SliceTrackerTest, GetTopmostSliceOnTrack) {
+ TraceProcessorContext context;
+ context.storage.reset(new TraceStorage());
+ SliceTracker tracker(&context);
+
+ TrackId track{1u};
+ TrackId track2{2u};
+
+ EXPECT_EQ(tracker.GetTopmostSliceOnTrack(track), base::nullopt);
+
+ tracker.Begin(100, track, StringId::Raw(11), StringId::Raw(11));
+ SliceId slice1 = context.storage->slice_table().id()[0];
+
+ EXPECT_EQ(tracker.GetTopmostSliceOnTrack(track).value(), slice1);
+
+ tracker.Begin(120, track, StringId::Raw(22), StringId::Raw(22));
+ SliceId slice2 = context.storage->slice_table().id()[1];
+
+ EXPECT_EQ(tracker.GetTopmostSliceOnTrack(track).value(), slice2);
+
+ EXPECT_EQ(tracker.GetTopmostSliceOnTrack(track2), base::nullopt);
+
+ tracker.End(140, track, StringId::Raw(22), StringId::Raw(22));
+
+ EXPECT_EQ(tracker.GetTopmostSliceOnTrack(track).value(), slice1);
+
+ tracker.End(330, track, StringId::Raw(11), StringId::Raw(11));
+
+ EXPECT_EQ(tracker.GetTopmostSliceOnTrack(track), base::nullopt);
+}
+
+TEST(SliceTrackerTest, OnSliceBeginCallback) {
+ TraceProcessorContext context;
+ context.storage.reset(new TraceStorage());
+ SliceTracker tracker(&context);
+
+ TrackId track1{1u};
+ TrackId track2{2u};
+
+ std::vector<TrackId> track_records;
+ std::vector<SliceId> slice_records;
+ tracker.SetOnSliceBeginCallback([&](TrackId track_id, SliceId slice_id) {
+ track_records.emplace_back(track_id);
+ slice_records.emplace_back(slice_id);
+ });
+
+ EXPECT_TRUE(track_records.empty());
+ EXPECT_TRUE(slice_records.empty());
+
+ tracker.Begin(100, track1, StringId::Raw(11), StringId::Raw(11));
+ SliceId slice1 = context.storage->slice_table().id()[0];
+ EXPECT_THAT(track_records, ElementsAre(TrackId{1u}));
+ EXPECT_THAT(slice_records, ElementsAre(slice1));
+
+ tracker.Begin(120, track2, StringId::Raw(22), StringId::Raw(22));
+ SliceId slice2 = context.storage->slice_table().id()[1];
+ EXPECT_THAT(track_records, ElementsAre(TrackId{1u}, TrackId{2u}));
+ EXPECT_THAT(slice_records, ElementsAre(slice1, slice2));
+
+ tracker.Begin(330, track1, StringId::Raw(33), StringId::Raw(33));
+ SliceId slice3 = context.storage->slice_table().id()[2];
+ EXPECT_THAT(track_records,
+ ElementsAre(TrackId{1u}, TrackId{2u}, TrackId{1u}));
+ EXPECT_THAT(slice_records, ElementsAre(slice1, slice2, slice3));
+}
+
} // namespace
} // namespace trace_processor
} // namespace perfetto
diff --git a/src/trace_processor/importers/json/json_trace_parser.cc b/src/trace_processor/importers/json/json_trace_parser.cc
index d58df09..d6b14ff 100644
--- a/src/trace_processor/importers/json/json_trace_parser.cc
+++ b/src/trace_processor/importers/json/json_trace_parser.cc
@@ -22,8 +22,10 @@
#include <string>
#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/string_utils.h"
#include "perfetto/ext/base/string_view.h"
#include "perfetto/ext/base/utils.h"
+#include "src/trace_processor/importers/common/flow_tracker.h"
#include "src/trace_processor/importers/common/process_tracker.h"
#include "src/trace_processor/importers/common/slice_tracker.h"
#include "src/trace_processor/importers/common/track_tracker.h"
@@ -34,6 +36,24 @@
namespace perfetto {
namespace trace_processor {
+#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
+namespace {
+
+base::Optional<uint64_t> MaybeExtractIdFromJsonValue(const Json::Value& value) {
+ if (!value.isMember("id"))
+ return base::nullopt;
+ auto id = value["id"];
+ if (id.isNumeric())
+ return id.asUInt64();
+ if (!id.isString())
+ return base::nullopt;
+ const char* c_string = id.asCString();
+ return base::CStringToUInt64(c_string, 16);
+}
+
+} // namespace
+#endif
+
JsonTraceParser::JsonTraceParser(TraceProcessorContext* context)
: context_(context), systrace_line_parser_(context) {}
@@ -62,6 +82,7 @@
ProcessTracker* procs = context_->process_tracker.get();
TraceStorage* storage = context_->storage.get();
SliceTracker* slice_tracker = context_->slice_tracker.get();
+ FlowTracker* flow_tracker = context_->flow_tracker.get();
auto& ph = value["ph"];
if (!ph.isString())
@@ -118,6 +139,44 @@
opt_dur.value(), args_inserter);
break;
}
+ case 's': { // TRACE_EVENT_FLOW_START
+ TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+ auto opt_source_id = MaybeExtractIdFromJsonValue(value);
+ if (opt_source_id) {
+ FlowId flow_id = flow_tracker->GetFlowIdForV1Event(
+ opt_source_id.value(), cat_id, name_id);
+ flow_tracker->Begin(track_id, flow_id);
+ } else {
+ context_->storage->IncrementStats(stats::flow_invalid_id);
+ }
+ break;
+ }
+ case 't': { // TRACE_EVENT_FLOW_STEP
+ TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+ auto opt_source_id = MaybeExtractIdFromJsonValue(value);
+ if (opt_source_id) {
+ FlowId flow_id = flow_tracker->GetFlowIdForV1Event(
+ opt_source_id.value(), cat_id, name_id);
+ flow_tracker->Step(track_id, flow_id);
+ } else {
+ context_->storage->IncrementStats(stats::flow_invalid_id);
+ }
+ break;
+ }
+ case 'f': { // TRACE_EVENT_FLOW_END
+ TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+ auto opt_source_id = MaybeExtractIdFromJsonValue(value);
+ if (opt_source_id) {
+ FlowId flow_id = flow_tracker->GetFlowIdForV1Event(
+ opt_source_id.value(), cat_id, name_id);
+ bool bind_enclosing_slice =
+ value.isMember("bp") && strcmp(value["bp"].asCString(), "e") == 0;
+ flow_tracker->End(track_id, flow_id, bind_enclosing_slice);
+ } else {
+ context_->storage->IncrementStats(stats::flow_invalid_id);
+ }
+ break;
+ }
case 'M': { // Metadata events (process and thread names).
if (strcmp(value["name"].asCString(), "thread_name") == 0 &&
!value["args"]["name"].empty()) {
diff --git a/src/trace_processor/storage/stats.h b/src/trace_processor/storage/stats.h
index 4656953..bcfc0f7 100644
--- a/src/trace_processor/storage/stats.h
+++ b/src/trace_processor/storage/stats.h
@@ -71,6 +71,11 @@
F(rss_stat_unknown_thread_for_mm_id, kSingle, kInfo, kAnalysis, ""), \
F(sched_switch_out_of_order, kSingle, kError, kAnalysis, ""), \
F(slice_out_of_order, kSingle, kError, kAnalysis, ""), \
+ F(flow_duplicate_id, kSingle, kError, kAnalysis, ""), \
+ F(flow_no_enclosing_slice, kSingle, kError, kAnalysis, ""), \
+ F(flow_step_without_start, kSingle, kError, kAnalysis, ""), \
+ F(flow_end_without_start, kSingle, kError, kAnalysis, ""), \
+ F(flow_invalid_id, kSingle, kError, kAnalysis, ""), \
F(stackprofile_invalid_string_id, kSingle, kError, kTrace, ""), \
F(stackprofile_invalid_mapping_id, kSingle, kError, kTrace, ""), \
F(stackprofile_invalid_frame_id, kSingle, kError, kTrace, ""), \
diff --git a/src/trace_processor/storage/trace_storage.h b/src/trace_processor/storage/trace_storage.h
index 7f2fd2a..160ffc8 100644
--- a/src/trace_processor/storage/trace_storage.h
+++ b/src/trace_processor/storage/trace_storage.h
@@ -37,6 +37,7 @@
#include "src/trace_processor/storage/stats.h"
#include "src/trace_processor/tables/android_tables.h"
#include "src/trace_processor/tables/counter_tables.h"
+#include "src/trace_processor/tables/flow_tables.h"
#include "src/trace_processor/tables/metadata_tables.h"
#include "src/trace_processor/tables/profiler_tables.h"
#include "src/trace_processor/tables/slice_tables.h"
@@ -447,6 +448,9 @@
const tables::SliceTable& slice_table() const { return slice_table_; }
tables::SliceTable* mutable_slice_table() { return &slice_table_; }
+ const tables::FlowTable& flow_table() const { return flow_table_; }
+ tables::FlowTable* mutable_flow_table() { return &flow_table_; }
+
const ThreadSlices& thread_slices() const { return thread_slices_; }
ThreadSlices* mutable_thread_slices() { return &thread_slices_; }
@@ -765,6 +769,9 @@
// Slices coming from userspace events (e.g. Chromium TRACE_EVENT macros).
tables::SliceTable slice_table_{&string_pool_, nullptr};
+ // Flow events from userspace events (e.g. Chromium TRACE_EVENT macros).
+ tables::FlowTable flow_table_{&string_pool_, nullptr};
+
// Slices from CPU scheduling data.
tables::SchedSliceTable sched_slice_table_{&string_pool_, nullptr};
diff --git a/src/trace_processor/tables/BUILD.gn b/src/trace_processor/tables/BUILD.gn
index 7f6ac2b..d14c30a 100644
--- a/src/trace_processor/tables/BUILD.gn
+++ b/src/trace_processor/tables/BUILD.gn
@@ -18,6 +18,7 @@
sources = [
"android_tables.h",
"counter_tables.h",
+ "flow_tables.h",
"macros.h",
"macros_internal.h",
"metadata_tables.h",
diff --git a/src/trace_processor/tables/flow_tables.h b/src/trace_processor/tables/flow_tables.h
new file mode 100644
index 0000000..c20cfc9
--- /dev/null
+++ b/src/trace_processor/tables/flow_tables.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_TABLES_FLOW_TABLES_H_
+#define SRC_TRACE_PROCESSOR_TABLES_FLOW_TABLES_H_
+
+#include "src/trace_processor/tables/macros.h"
+#include "src/trace_processor/tables/slice_tables.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+namespace tables {
+
+#define PERFETTO_TP_FLOW_DEF(NAME, PARENT, C) \
+ NAME(FlowTable, "flow") \
+ PERFETTO_TP_ROOT_TABLE(PARENT, C) \
+ C(SliceTable::Id, slice_out) \
+ C(SliceTable::Id, slice_in)
+
+PERFETTO_TP_TABLE(PERFETTO_TP_FLOW_DEF);
+
+} // namespace tables
+} // namespace trace_processor
+} // namespace perfetto
+
+#endif // SRC_TRACE_PROCESSOR_TABLES_FLOW_TABLES_H_
diff --git a/src/trace_processor/tables/table_destructors.cc b/src/trace_processor/tables/table_destructors.cc
index d33499e..9f4d207 100644
--- a/src/trace_processor/tables/table_destructors.cc
+++ b/src/trace_processor/tables/table_destructors.cc
@@ -16,6 +16,7 @@
#include "src/trace_processor/tables/android_tables.h"
#include "src/trace_processor/tables/counter_tables.h"
+#include "src/trace_processor/tables/flow_tables.h"
#include "src/trace_processor/tables/metadata_tables.h"
#include "src/trace_processor/tables/profiler_tables.h"
#include "src/trace_processor/tables/slice_tables.h"
@@ -64,6 +65,7 @@
// slice_tables.h
SliceTable::~SliceTable() = default;
+FlowTable::~FlowTable() = default;
InstantTable::~InstantTable() = default;
SchedSliceTable::~SchedSliceTable() = default;
GpuSliceTable::~GpuSliceTable() = default;
diff --git a/src/trace_processor/trace_processor_context.cc b/src/trace_processor/trace_processor_context.cc
index 961a77f..4187959 100644
--- a/src/trace_processor/trace_processor_context.cc
+++ b/src/trace_processor/trace_processor_context.cc
@@ -21,6 +21,7 @@
#include "src/trace_processor/importers/common/args_tracker.h"
#include "src/trace_processor/importers/common/clock_tracker.h"
#include "src/trace_processor/importers/common/event_tracker.h"
+#include "src/trace_processor/importers/common/flow_tracker.h"
#include "src/trace_processor/importers/common/global_args_tracker.h"
#include "src/trace_processor/importers/common/process_tracker.h"
#include "src/trace_processor/importers/common/slice_tracker.h"
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 36620f9..44039b3 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -622,6 +622,7 @@
RegisterDbTable(storage->process_table());
RegisterDbTable(storage->slice_table());
+ RegisterDbTable(storage->flow_table());
RegisterDbTable(storage->sched_slice_table());
RegisterDbTable(storage->instant_table());
RegisterDbTable(storage->gpu_slice_table());
diff --git a/src/trace_processor/trace_processor_storage_impl.cc b/src/trace_processor/trace_processor_storage_impl.cc
index 32146b1..4d88087 100644
--- a/src/trace_processor/trace_processor_storage_impl.cc
+++ b/src/trace_processor/trace_processor_storage_impl.cc
@@ -21,6 +21,7 @@
#include "src/trace_processor/importers/common/args_tracker.h"
#include "src/trace_processor/importers/common/clock_tracker.h"
#include "src/trace_processor/importers/common/event_tracker.h"
+#include "src/trace_processor/importers/common/flow_tracker.h"
#include "src/trace_processor/importers/common/process_tracker.h"
#include "src/trace_processor/importers/common/slice_tracker.h"
#include "src/trace_processor/importers/common/track_tracker.h"
@@ -43,6 +44,7 @@
context_.track_tracker.reset(new TrackTracker(&context_));
context_.args_tracker.reset(new ArgsTracker(&context_));
context_.slice_tracker.reset(new SliceTracker(&context_));
+ context_.flow_tracker.reset(new FlowTracker(&context_));
context_.event_tracker.reset(new EventTracker(&context_));
context_.process_tracker.reset(new ProcessTracker(&context_));
context_.clock_tracker.reset(new ClockTracker(&context_));
@@ -51,6 +53,11 @@
context_.global_args_tracker.reset(new GlobalArgsTracker(&context_));
context_.proto_to_args_table_.reset(new ProtoToArgsTable(&context_));
+ context_.slice_tracker->SetOnSliceBeginCallback(
+ [this](TrackId track_id, SliceId slice_id) {
+ context_.flow_tracker->ClosePendingEventsOnTrack(track_id, slice_id);
+ });
+
RegisterDefaultModules(&context_);
}
diff --git a/src/trace_processor/types/trace_processor_context.h b/src/trace_processor/types/trace_processor_context.h
index ab9c016..19a0ee5 100644
--- a/src/trace_processor/types/trace_processor_context.h
+++ b/src/trace_processor/types/trace_processor_context.h
@@ -40,6 +40,7 @@
class ProtoImporterModule;
class ProcessTracker;
class SliceTracker;
+class FlowTracker;
class TraceParser;
class TraceSorter;
class TraceStorage;
@@ -67,6 +68,7 @@
std::unique_ptr<TrackTracker> track_tracker;
std::unique_ptr<SliceTracker> slice_tracker;
+ std::unique_ptr<FlowTracker> flow_tracker;
std::unique_ptr<ProcessTracker> process_tracker;
std::unique_ptr<EventTracker> event_tracker;
std::unique_ptr<ClockTracker> clock_tracker;
diff --git a/test/trace_processor/parsing/flow_events_small.json b/test/trace_processor/parsing/flow_events_small.json
new file mode 100644
index 0000000..461aa39
--- /dev/null
+++ b/test/trace_processor/parsing/flow_events_small.json
@@ -0,0 +1,123 @@
+{
+ "traceEvents": [
+ {
+ "cat": "ipc",
+ "pid": 15902,
+ "tid": 15903,
+ "ts": 3002,
+ "ph": "X",
+ "name": "SenderA",
+ "args":{},
+ "dur": 10
+ },
+ {
+ "cat": "ipc",
+ "pid": 15902,
+ "tid": 15903,
+ "ts": 3002,
+ "ph": "s",
+ "name": "IPC",
+ "args":{},
+ "id":"0x402",
+ "sf": "7"
+ },
+ {
+ "cat": "ipc",
+ "pid": 15903,
+ "tid": 15904,
+ "ts": 0,
+ "ph": "X",
+ "name": "SenderB",
+ "args":{},
+ "dur": 10
+ },
+ {
+ "cat": "ipc",
+ "pid": 15903,
+ "tid": 15904,
+ "ts": 1,
+ "ph": "s",
+ "name": "IPC",
+ "args":{},
+ "id":"0x403"
+ },
+ {
+ "cat": "ipc",
+ "pid": 15875,
+ "tid": 15895,
+ "ts": 1001,
+ "ph": "f",
+ "name": "IPC",
+ "args": {},
+ "id": "0x403"
+ },
+ {
+ "cat": "ipc",
+ "pid": 15875,
+ "tid": 15895,
+ "ts": 1001,
+ "ph": "X",
+ "name": "Blergh",
+ "args": {},
+ "dur": 100
+ },
+ {
+ "cat": "ipc",
+ "pid": 15774,
+ "tid": 15794,
+ "ts": 3200,
+ "ph": "X",
+ "name": "OtherSlice",
+ "args": {},
+ "dur": 500
+ },
+ {
+ "cat": "ipc",
+ "pid": 15774,
+ "tid": 15794,
+ "ts": 3220,
+ "ph": "t",
+ "name": "IPC",
+ "args": {},
+ "id": "0x402",
+ "sf": 8
+ },
+ {
+ "cat": "ipc",
+ "pid": 15874,
+ "tid": 15894,
+ "ts": 3330,
+ "ph": "f",
+ "name": "IPC",
+ "args": {},
+ "id": "0x402",
+ "sf": "1"
+ },
+ {
+ "cat": "ipc",
+ "pid": 15874,
+ "tid": 15894,
+ "ts": 3331,
+ "ph": "X",
+ "name": "SomeSlice",
+ "args": {},
+ "dur": 400
+ }
+ ],
+ "stackFrames": {
+ "1": {
+ "category": "m1",
+ "name": "main"
+ },
+ "7": {
+ "category": "m2",
+ "name": "frame7",
+ "parent": "1"
+ },
+ "8": {
+ "category": "m2",
+ "name": "frame8",
+ "parent": "1"
+ }
+ }
+ }
\ No newline at end of file
diff --git a/test/trace_processor/parsing/flow_events_small.out b/test/trace_processor/parsing/flow_events_small.out
new file mode 100644
index 0000000..3cef532
--- /dev/null
+++ b/test/trace_processor/parsing/flow_events_small.out
@@ -0,0 +1,4 @@
+"slice_out","slice_in"
+"SenderB","Blergh"
+"SenderA","OtherSlice"
+"OtherSlice","SomeSlice"
diff --git a/test/trace_processor/parsing/flow_events_small.sql b/test/trace_processor/parsing/flow_events_small.sql
new file mode 100644
index 0000000..11ef1a9
--- /dev/null
+++ b/test/trace_processor/parsing/flow_events_small.sql
@@ -0,0 +1,18 @@
+--
+-- Copyright 2020 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
+--
+-- https://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.
+--
+select t1.name as slice_out, t2.name as slice_in from flow t
+join slice t1 on t.slice_out == t1.slice_id
+join slice t2 on t.slice_in == t2.slice_id;
diff --git a/test/trace_processor/parsing/index b/test/trace_processor/parsing/index
index d21449f..7aca870 100644
--- a/test/trace_processor/parsing/index
+++ b/test/trace_processor/parsing/index
@@ -126,3 +126,6 @@
# Ensures process -> package matching works as expected.
process_metadata_matching.textproto process_metadata_matching.sql process_metadata_matching.out
+
+# Flow events vesion 1 importing from json
+flow_events_small.json flow_events_small.sql flow_events_small.out