Merge "threading: add Future<T>/Stream<T> helpers for Channel<T> and ThreadPool"
diff --git a/CHANGELOG b/CHANGELOG
index 5c92fd9..8194fa8 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
Unreleased:
Tracing service and probes:
- *
+ * --continuous-dump in tools/java_heap_dump now keeps recording until it
+ receives CTRL+C.
Trace Processor:
*
UI:
diff --git a/src/base/threading/stream_unittest.cc b/src/base/threading/stream_unittest.cc
index 3036bd9..a9ac9aa 100644
--- a/src/base/threading/stream_unittest.cc
+++ b/src/base/threading/stream_unittest.cc
@@ -18,7 +18,9 @@
#include <vector>
+#include "perfetto/base/platform_handle.h"
#include "perfetto/base/status.h"
+#include "perfetto/ext/base/event_fd.h"
#include "perfetto/ext/base/threading/future_combinators.h"
#include "perfetto/ext/base/threading/poll.h"
#include "test/gtest_and_gmock.h"
@@ -30,6 +32,7 @@
using testing::_;
using testing::ElementsAre;
using testing::Return;
+using testing::UnorderedElementsAre;
template <typename T>
class MockPollable : public FuturePollable<T> {
@@ -258,10 +261,13 @@
}
TEST_F(StreamUnittest, FlattenStreams) {
+ EventFd event_fd1, event_fd2, event_fd3, event_fd4;
+ const PlatformHandle fd1 = event_fd1.fd(), fd2 = event_fd2.fd(),
+ fd3 = event_fd3.fd(), fd4 = event_fd4.fd();
std::unique_ptr<MockStreamPollable<int>> a(new MockStreamPollable<int>());
EXPECT_CALL(*a, PollNext(_))
- .WillOnce([](PollContext* ctx) {
- ctx->RegisterInterested(1);
+ .WillOnce([fd1](PollContext* ctx) {
+ ctx->RegisterInterested(fd1);
return PendingPollResult();
})
.WillOnce(Return(StreamPollResult<int>(1)))
@@ -269,12 +275,12 @@
std::unique_ptr<MockStreamPollable<int>> b(new MockStreamPollable<int>());
EXPECT_CALL(*b, PollNext(_))
- .WillOnce([](PollContext* ctx) {
- ctx->RegisterInterested(2);
+ .WillOnce([fd2](PollContext* ctx) {
+ ctx->RegisterInterested(fd2);
return PendingPollResult();
})
- .WillOnce([](PollContext* ctx) {
- ctx->RegisterInterested(2);
+ .WillOnce([fd2](PollContext* ctx) {
+ ctx->RegisterInterested(fd2);
return PendingPollResult();
})
.WillOnce(Return(StreamPollResult<int>(2)))
@@ -283,9 +289,9 @@
std::unique_ptr<MockStreamPollable<int>> c(new MockStreamPollable<int>());
EXPECT_CALL(*c, PollNext(_))
.WillOnce(Return(StreamPollResult<int>(3)))
- .WillOnce([](PollContext* ctx) {
- ctx->RegisterInterested(3);
- ctx->RegisterInterested(4);
+ .WillOnce([fd3, fd4](PollContext* ctx) {
+ ctx->RegisterInterested(fd3);
+ ctx->RegisterInterested(fd4);
return PendingPollResult();
})
.WillOnce(Return(DonePollResult()));
@@ -300,25 +306,25 @@
ASSERT_THAT(interested_, ElementsAre());
ASSERT_TRUE(stream.PollNext(&ctx_).IsPending());
- ASSERT_THAT(interested_, ElementsAre(1, 2, 3, 4));
+ ASSERT_THAT(interested_, UnorderedElementsAre(fd1, fd2, fd3, fd4));
interested_.clear();
ASSERT_TRUE(stream.PollNext(&ctx_).IsPending());
- ASSERT_THAT(interested_, ElementsAre(1, 2, 3, 4));
+ ASSERT_THAT(interested_, UnorderedElementsAre(fd1, fd2, fd3, fd4));
interested_.clear();
- ready_ = {1};
+ ready_ = {fd1};
ASSERT_EQ(stream.PollNext(&ctx_).item(), 1);
ASSERT_TRUE(stream.PollNext(&ctx_).IsPending());
- ASSERT_THAT(interested_, ElementsAre(2, 3, 4));
+ ASSERT_THAT(interested_, UnorderedElementsAre(fd2, fd3, fd4));
interested_.clear();
ready_ = {};
ASSERT_TRUE(stream.PollNext(&ctx_).IsPending());
- ASSERT_THAT(interested_, ElementsAre(2, 3, 4));
+ ASSERT_THAT(interested_, ElementsAre(fd2, fd3, fd4));
interested_.clear();
- ready_ = {1, 2, 3};
+ ready_ = {fd1, fd2, fd3};
ASSERT_TRUE(stream.PollNext(&ctx_).IsPending());
ASSERT_EQ(stream.PollNext(&ctx_).item(), 2);
ASSERT_TRUE(stream.PollNext(&ctx_).IsDone());
diff --git a/src/profiling/memory/unwinding.cc b/src/profiling/memory/unwinding.cc
index 4a7a5d3..d0c90be 100644
--- a/src/profiling/memory/unwinding.cc
+++ b/src/profiling/memory/unwinding.cc
@@ -204,6 +204,29 @@
return true;
}
+UnwindingWorker::~UnwindingWorker() {
+ if (thread_task_runner_.get() == nullptr) {
+ return;
+ }
+ std::mutex mutex;
+ std::condition_variable cv;
+
+ std::unique_lock<std::mutex> lock(mutex);
+ bool done = false;
+ thread_task_runner_.PostTask([&mutex, &cv, &done, this] {
+ for (auto& it : client_data_) {
+ auto& client_data = it.second;
+ client_data.sock->Shutdown(false);
+ }
+ client_data_.clear();
+
+ std::lock_guard<std::mutex> inner_lock(mutex);
+ done = true;
+ cv.notify_one();
+ });
+ cv.wait(lock, [&done] { return done; });
+}
+
void UnwindingWorker::OnDisconnect(base::UnixSocket* self) {
pid_t peer_pid = self->peer_pid_linux();
auto it = client_data_.find(peer_pid);
diff --git a/src/profiling/memory/unwinding.h b/src/profiling/memory/unwinding.h
index fd2e9f9..b5e2e01 100644
--- a/src/profiling/memory/unwinding.h
+++ b/src/profiling/memory/unwinding.h
@@ -85,6 +85,9 @@
: delegate_(delegate),
thread_task_runner_(std::move(thread_task_runner)) {}
+ ~UnwindingWorker() override;
+ UnwindingWorker(UnwindingWorker&&) = default;
+
// Public API safe to call from other threads.
void PostDisconnectSocket(pid_t pid);
void PostPurgeProcess(pid_t pid);
@@ -149,19 +152,11 @@
std::map<pid_t, ClientData> client_data_;
Delegate* delegate_;
- // Task runner with a dedicated thread. Keep last as instances this class are
- // currently (incorrectly) being destroyed on the main thread, instead of the
- // task thread. By destroying this task runner first, we ensure that the
- // UnwindingWorker is not active while the rest of its state is being
- // destroyed. Additionally this ensures that the destructing thread sees a
- // consistent view of the memory due to the ThreadTaskRunner's destructor
- // joining a thread.
- //
- // Additionally, keep the destructor defaulted, as its body would still race
- // against an active task thread.
- //
- // TODO(rsavitski): make the task thread own the object's lifetime (likely by
- // refactoring base::ThreadTaskRunner).
+ // Task runner with a dedicated thread. Keep last. By destroying this task
+ // runner first, we ensure that the UnwindingWorker is not active while the
+ // rest of its state is being destroyed. Additionally this ensures that the
+ // destructing thread sees a consistent view of the memory due to the
+ // ThreadTaskRunner's destructor joining a thread.
base::ThreadTaskRunner thread_task_runner_;
};
diff --git a/src/trace_processor/importers/fuchsia/fuchsia_parser_unittest.cc b/src/trace_processor/importers/fuchsia/fuchsia_parser_unittest.cc
index 9fa99a5..92ccb20 100644
--- a/src/trace_processor/importers/fuchsia/fuchsia_parser_unittest.cc
+++ b/src/trace_processor/importers/fuchsia/fuchsia_parser_unittest.cc
@@ -421,6 +421,132 @@
context_.sorter->ExtractEventsForced();
}
+TEST_F(FuchsiaTraceParserTest, SchedulerEvents) {
+ uint64_t thread1_tid = 0x1AAA'AAAA'AAAA'AAAA;
+ uint64_t thread2_tid = 0x2CCC'CCCC'CCCC'CCCC;
+
+ // We'll emit a wake up for thread 1, a switch to thread 2, and a switch back
+ // to thread 1 and expect to see that the process tracker was properly updated
+
+ uint64_t wakeup_record_type = uint64_t{2} << 60;
+ uint64_t context_switch_record_type = uint64_t{1} << 60;
+ uint64_t cpu = 1 << 20;
+ uint64_t record_type = 8;
+
+ uint64_t wakeup_size = uint64_t{3} << 4;
+ uint64_t context_switch_size = uint64_t{4} << 4;
+
+ uint64_t wakeup_header = wakeup_record_type | cpu | record_type | wakeup_size;
+ push_word(wakeup_header);
+ // Timestamp
+ push_word(0x1);
+ // wakeup tid
+ push_word(thread1_tid);
+
+ uint64_t context_switch_header =
+ context_switch_record_type | cpu | record_type | context_switch_size;
+ push_word(context_switch_header);
+ // Timestamp
+ push_word(0x2);
+ // outgoing tid
+ push_word(thread1_tid);
+ // incoming tid
+ push_word(thread2_tid);
+
+ push_word(context_switch_header);
+ // Timestamp
+ push_word(0x3);
+ // outgoing tid
+ push_word(thread2_tid);
+ // incoming tid
+ push_word(thread1_tid);
+
+ // We should get:
+ // - A thread1 update call on wake up
+ // - thread1 & thread2 update calls on the first context switch
+ // - thread2 & thread1 update cals on the second context switch
+ EXPECT_CALL(*process_, UpdateThread(static_cast<uint32_t>(thread1_tid), _))
+ .Times(3);
+ EXPECT_CALL(*process_, UpdateThread(static_cast<uint32_t>(thread2_tid), _))
+ .Times(2);
+
+ EXPECT_TRUE(Tokenize().ok());
+ EXPECT_EQ(context_.storage->stats()[stats::fuchsia_invalid_event].value, 0);
+
+ context_.sorter->ExtractEventsForced();
+}
+
+TEST_F(FuchsiaTraceParserTest, LegacySchedulerEvents) {
+ uint64_t thread1_pid = 0x1AAA'AAAA'AAAA'AAAA;
+ uint64_t thread1_tid = 0x1BBB'BBBB'BBBB'BBBB;
+ uint64_t thread2_pid = 0x2CCC'CCCC'CCCC'CCCC;
+ uint64_t thread2_tid = 0x2DDD'DDDD'DDDD'DDDD;
+
+ // We'll emit a wake up for thread 1, a switch to thread 2, and a switch back
+ // to thread 1 and expect to see that the process tracker was properly updated
+
+ uint64_t context_switch_size = uint64_t{6} << 4;
+ uint64_t cpu = 1 << 16;
+ uint64_t record_type = 8;
+ uint64_t outoing_state = 2 << 24;
+ uint64_t outoing_thread = 0; // Inline thread-ref
+ uint64_t incoming_thread = 0; // Inline thread-ref
+ uint64_t outgoing_prio = uint64_t{1} << 44;
+ uint64_t incoming_prio = uint64_t{1} << 52;
+ uint64_t outgoing_idle_prio = uint64_t{0} << 44;
+
+ uint64_t context_switch_header =
+ record_type | context_switch_size | cpu | outoing_state | outoing_thread |
+ incoming_thread | outgoing_prio | incoming_prio;
+ uint64_t wakeup_header = record_type | context_switch_size | cpu |
+ outoing_state | outoing_thread | incoming_thread |
+ outgoing_idle_prio | incoming_prio;
+
+ push_word(wakeup_header);
+ // Timestamp
+ push_word(0x1);
+ // outgoing pid+tid
+ push_word(0); // Idle thread
+ push_word(0); // Idle thread
+ // incoming pid+tid
+ push_word(thread1_pid);
+ push_word(thread1_tid);
+
+ push_word(context_switch_header);
+ // Timestamp
+ push_word(0x2);
+ // outgoing pid+tid
+ push_word(thread1_pid);
+ push_word(thread1_tid);
+ // incoming pid+tid
+ push_word(thread2_pid);
+ push_word(thread2_tid);
+
+ push_word(context_switch_header);
+ // Timestamp
+ push_word(0x3);
+ // outgoing pid+tid
+ push_word(thread2_pid);
+ push_word(thread2_tid);
+ // incoming pid+tid
+ push_word(thread1_pid);
+ push_word(thread1_tid);
+
+ // We should get:
+ // - A thread1 update call on wake up
+ // - thread1 & thread2 update calls on the first context switch
+ // - thread2 & thread1 update cals on the second context switch
+ EXPECT_CALL(*process_, UpdateThread(static_cast<uint32_t>(thread1_tid), _))
+ .Times(3);
+ EXPECT_CALL(*process_, UpdateThread(static_cast<uint32_t>(thread2_tid), _))
+ .Times(2);
+
+ EXPECT_TRUE(Tokenize().ok());
+ EXPECT_EQ(context_.storage->stats()[stats::fuchsia_invalid_event].value, 0);
+
+ context_.sorter->ExtractEventsForced();
+}
+
} // namespace
} // namespace trace_processor
} // namespace perfetto
diff --git a/src/trace_processor/importers/fuchsia/fuchsia_trace_parser.cc b/src/trace_processor/importers/fuchsia/fuchsia_trace_parser.cc
index a4dc251..0143060 100644
--- a/src/trace_processor/importers/fuchsia/fuchsia_trace_parser.cc
+++ b/src/trace_processor/importers/fuchsia/fuchsia_trace_parser.cc
@@ -56,10 +56,6 @@
constexpr uint32_t kPointer = 7;
constexpr uint32_t kKoid = 8;
-struct Arg {
- StringId name;
- fuchsia_trace_utils::ArgValue value;
-};
} // namespace
FuchsiaTraceParser::FuchsiaTraceParser(TraceProcessorContext* context)
@@ -75,9 +71,119 @@
proto_parser_->ParseTracePacket(ts, std::move(data));
}
+std::optional<std::vector<FuchsiaTraceParser::Arg>>
+FuchsiaTraceParser::ParseArgs(
+ fuchsia_trace_utils::RecordCursor& cursor,
+ uint32_t n_args,
+ std::function<StringId(base::StringView string)> intern_string,
+ std::function<StringId(uint32_t index)> get_string) {
+ std::vector<Arg> args;
+ for (uint32_t i = 0; i < n_args; i++) {
+ size_t arg_base = cursor.WordIndex();
+ uint64_t arg_header;
+ if (!cursor.ReadUint64(&arg_header)) {
+ return std::nullopt;
+ }
+ uint32_t arg_type =
+ fuchsia_trace_utils::ReadField<uint32_t>(arg_header, 0, 3);
+ uint32_t arg_size_words =
+ fuchsia_trace_utils::ReadField<uint32_t>(arg_header, 4, 15);
+ uint32_t arg_name_ref =
+ fuchsia_trace_utils::ReadField<uint32_t>(arg_header, 16, 31);
+ Arg arg;
+ if (fuchsia_trace_utils::IsInlineString(arg_name_ref)) {
+ base::StringView arg_name_view;
+ if (!cursor.ReadInlineString(arg_name_ref, &arg_name_view)) {
+ return std::nullopt;
+ }
+ arg.name = intern_string(arg_name_view);
+ } else {
+ arg.name = get_string(arg_name_ref);
+ }
+
+ switch (arg_type) {
+ case kNull:
+ arg.value = fuchsia_trace_utils::ArgValue::Null();
+ break;
+ case kInt32:
+ arg.value = fuchsia_trace_utils::ArgValue::Int32(
+ fuchsia_trace_utils::ReadField<int32_t>(arg_header, 32, 63));
+ break;
+ case kUint32:
+ arg.value = fuchsia_trace_utils::ArgValue::Uint32(
+ fuchsia_trace_utils::ReadField<uint32_t>(arg_header, 32, 63));
+ break;
+ case kInt64: {
+ int64_t value;
+ if (!cursor.ReadInt64(&value)) {
+ return std::nullopt;
+ }
+ arg.value = fuchsia_trace_utils::ArgValue::Int64(value);
+ break;
+ }
+ case kUint64: {
+ uint64_t value;
+ if (!cursor.ReadUint64(&value)) {
+ return std::nullopt;
+ }
+ arg.value = fuchsia_trace_utils::ArgValue::Uint64(value);
+ break;
+ }
+ case kDouble: {
+ double value;
+ if (!cursor.ReadDouble(&value)) {
+ return std::nullopt;
+ }
+ arg.value = fuchsia_trace_utils::ArgValue::Double(value);
+ break;
+ }
+ case kString: {
+ uint32_t arg_value_ref =
+ fuchsia_trace_utils::ReadField<uint32_t>(arg_header, 32, 47);
+ StringId value;
+ if (fuchsia_trace_utils::IsInlineString(arg_value_ref)) {
+ base::StringView arg_value_view;
+ if (!cursor.ReadInlineString(arg_value_ref, &arg_value_view)) {
+ return std::nullopt;
+ }
+ value = intern_string(arg_value_view);
+ } else {
+ value = get_string(arg_value_ref);
+ }
+ arg.value = fuchsia_trace_utils::ArgValue::String(value);
+ break;
+ }
+ case kPointer: {
+ uint64_t value;
+ if (!cursor.ReadUint64(&value)) {
+ return std::nullopt;
+ }
+ arg.value = fuchsia_trace_utils::ArgValue::Pointer(value);
+ break;
+ }
+ case kKoid: {
+ uint64_t value;
+ if (!cursor.ReadUint64(&value)) {
+ return std::nullopt;
+ }
+ arg.value = fuchsia_trace_utils::ArgValue::Koid(value);
+ break;
+ }
+ default:
+ arg.value = fuchsia_trace_utils::ArgValue::Unknown();
+ break;
+ }
+
+ args.push_back(arg);
+ cursor.SetWordIndex(arg_base + arg_size_words);
+ }
+
+ return {std::move(args)};
+}
+
void FuchsiaTraceParser::ParseFuchsiaRecord(int64_t, FuchsiaRecord fr) {
- // The timestamp is also present in the record, so we'll ignore the one passed
- // as an argument.
+ // The timestamp is also present in the record, so we'll ignore the one
+ // passed as an argument.
fuchsia_trace_utils::RecordCursor cursor(fr.record_view()->data(),
fr.record_view()->length());
ProcessTracker* procs = context_->process_tracker.get();
@@ -140,121 +246,28 @@
}
// Read arguments
- std::vector<Arg> args;
- for (uint32_t i = 0; i < n_args; i++) {
- size_t arg_base = cursor.WordIndex();
- uint64_t arg_header;
- if (!cursor.ReadUint64(&arg_header)) {
- context_->storage->IncrementStats(stats::fuchsia_invalid_event);
- return;
- }
- uint32_t arg_type =
- fuchsia_trace_utils::ReadField<uint32_t>(arg_header, 0, 3);
- uint32_t arg_size_words =
- fuchsia_trace_utils::ReadField<uint32_t>(arg_header, 4, 15);
- uint32_t arg_name_ref =
- fuchsia_trace_utils::ReadField<uint32_t>(arg_header, 16, 31);
- Arg arg;
- if (fuchsia_trace_utils::IsInlineString(arg_name_ref)) {
- base::StringView arg_name_view;
- if (!cursor.ReadInlineString(arg_name_ref, &arg_name_view)) {
- context_->storage->IncrementStats(stats::fuchsia_invalid_event);
- return;
- }
- arg.name = context_->storage->InternString(arg_name_view);
- } else {
- arg.name = fr.GetString(arg_name_ref);
- }
-
- switch (arg_type) {
- case kNull:
- arg.value = fuchsia_trace_utils::ArgValue::Null();
- break;
- case kInt32:
- arg.value = fuchsia_trace_utils::ArgValue::Int32(
- fuchsia_trace_utils::ReadField<int32_t>(arg_header, 32, 63));
- break;
- case kUint32:
- arg.value = fuchsia_trace_utils::ArgValue::Uint32(
- fuchsia_trace_utils::ReadField<uint32_t>(arg_header, 32, 63));
- break;
- case kInt64: {
- int64_t value;
- if (!cursor.ReadInt64(&value)) {
- context_->storage->IncrementStats(stats::fuchsia_invalid_event);
- return;
- }
- arg.value = fuchsia_trace_utils::ArgValue::Int64(value);
- break;
- }
- case kUint64: {
- uint64_t value;
- if (!cursor.ReadUint64(&value)) {
- context_->storage->IncrementStats(stats::fuchsia_invalid_event);
- return;
- }
- arg.value = fuchsia_trace_utils::ArgValue::Uint64(value);
- break;
- }
- case kDouble: {
- double value;
- if (!cursor.ReadDouble(&value)) {
- context_->storage->IncrementStats(stats::fuchsia_invalid_event);
- return;
- }
- arg.value = fuchsia_trace_utils::ArgValue::Double(value);
- break;
- }
- case kString: {
- uint32_t arg_value_ref =
- fuchsia_trace_utils::ReadField<uint32_t>(arg_header, 32, 47);
- StringId value;
- if (fuchsia_trace_utils::IsInlineString(arg_value_ref)) {
- base::StringView arg_value_view;
- if (!cursor.ReadInlineString(arg_value_ref, &arg_value_view)) {
- context_->storage->IncrementStats(stats::fuchsia_invalid_event);
- return;
- }
- value = context_->storage->InternString(arg_value_view);
- } else {
- value = fr.GetString(arg_value_ref);
- }
- arg.value = fuchsia_trace_utils::ArgValue::String(value);
- break;
- }
- case kPointer: {
- uint64_t value;
- if (!cursor.ReadUint64(&value)) {
- context_->storage->IncrementStats(stats::fuchsia_invalid_event);
- return;
- }
- arg.value = fuchsia_trace_utils::ArgValue::Pointer(value);
- break;
- }
- case kKoid: {
- uint64_t value;
- if (!cursor.ReadUint64(&value)) {
- context_->storage->IncrementStats(stats::fuchsia_invalid_event);
- return;
- }
- arg.value = fuchsia_trace_utils::ArgValue::Koid(value);
- break;
- }
- default:
- arg.value = fuchsia_trace_utils::ArgValue::Unknown();
- break;
- }
-
- args.push_back(arg);
- cursor.SetWordIndex(arg_base + arg_size_words);
- }
- auto insert_args = [this, args](ArgsTracker::BoundInserter* inserter) {
- for (const Arg& arg : args) {
- inserter->AddArg(
- arg.name, arg.name,
- arg.value.ToStorageVariadic(context_->storage.get()));
- }
+ const auto intern_string = [this](base::StringView string) {
+ return context_->storage->InternString(string);
};
+ const auto get_string = [&fr](uint32_t index) {
+ return fr.GetString(index);
+ };
+
+ auto maybe_args = FuchsiaTraceParser::ParseArgs(
+ cursor, n_args, intern_string, get_string);
+ if (!maybe_args.has_value()) {
+ context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+ return;
+ }
+
+ auto insert_args =
+ [this, args = *maybe_args](ArgsTracker::BoundInserter* inserter) {
+ for (const Arg& arg : args) {
+ inserter->AddArg(
+ arg.name, arg.name,
+ arg.value.ToStorageVariadic(context_->storage.get()));
+ }
+ };
switch (event_type) {
case kInstant: {
@@ -262,7 +275,7 @@
procs->UpdateThread(static_cast<uint32_t>(tinfo.tid),
static_cast<uint32_t>(tinfo.pid));
TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
- slices->Scoped(ts, track_id, cat, name, 0, insert_args);
+ slices->Scoped(ts, track_id, cat, name, 0, std::move(insert_args));
break;
}
case kCounter: {
@@ -275,11 +288,12 @@
context_->storage->IncrementStats(stats::fuchsia_invalid_event);
return;
}
- // Note: In the Fuchsia trace format, counter values are stored in the
- // arguments for the record, with the data series defined by both the
- // record name and the argument name. In Perfetto, counters only have
- // one name, so we combine both names into one here.
- for (const Arg& arg : args) {
+ // Note: In the Fuchsia trace format, counter values are stored
+ // in the arguments for the record, with the data series defined
+ // by both the record name and the argument name. In Perfetto,
+ // counters only have one name, so we combine both names into
+ // one here.
+ for (const Arg& arg : *maybe_args) {
std::string counter_name_str = name_str + ":";
counter_name_str +=
context_->storage->GetString(arg.name).ToStdString();
@@ -332,7 +346,7 @@
procs->UpdateThread(static_cast<uint32_t>(tinfo.tid),
static_cast<uint32_t>(tinfo.pid));
TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
- slices->Begin(ts, track_id, cat, name, insert_args);
+ slices->Begin(ts, track_id, cat, name, std::move(insert_args));
break;
}
case kDurationEnd: {
@@ -340,10 +354,11 @@
procs->UpdateThread(static_cast<uint32_t>(tinfo.tid),
static_cast<uint32_t>(tinfo.pid));
TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
- // TODO(b/131181693): |cat| and |name| are not passed here so that
- // if two slices end at the same timestep, the slices get closed in
- // the correct order regardless of which end event is processed first.
- slices->End(ts, track_id, {}, {}, insert_args);
+ // TODO(b/131181693): |cat| and |name| are not passed here so
+ // that if two slices end at the same timestep, the slices get
+ // closed in the correct order regardless of which end event is
+ // processed first.
+ slices->End(ts, track_id, {}, {}, std::move(insert_args));
break;
}
case kDurationComplete: {
@@ -361,7 +376,8 @@
procs->UpdateThread(static_cast<uint32_t>(tinfo.tid),
static_cast<uint32_t>(tinfo.pid));
TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
- slices->Scoped(ts, track_id, cat, name, duration, insert_args);
+ slices->Scoped(ts, track_id, cat, name, duration,
+ std::move(insert_args));
break;
}
case kAsyncBegin: {
@@ -374,7 +390,7 @@
procs->GetOrCreateProcess(static_cast<uint32_t>(tinfo.pid));
TrackId track_id = context_->track_tracker->InternFuchsiaAsyncTrack(
name, upid, correlation_id);
- slices->Begin(ts, track_id, cat, name, insert_args);
+ slices->Begin(ts, track_id, cat, name, std::move(insert_args));
break;
}
case kAsyncInstant: {
@@ -387,7 +403,7 @@
procs->GetOrCreateProcess(static_cast<uint32_t>(tinfo.pid));
TrackId track_id = context_->track_tracker->InternFuchsiaAsyncTrack(
name, upid, correlation_id);
- slices->Scoped(ts, track_id, cat, name, 0, insert_args);
+ slices->Scoped(ts, track_id, cat, name, 0, std::move(insert_args));
break;
}
case kAsyncEnd: {
@@ -400,7 +416,7 @@
procs->GetOrCreateProcess(static_cast<uint32_t>(tinfo.pid));
TrackId track_id = context_->track_tracker->InternFuchsiaAsyncTrack(
name, upid, correlation_id);
- slices->End(ts, track_id, cat, name, insert_args);
+ slices->End(ts, track_id, cat, name, std::move(insert_args));
break;
}
case kFlowBegin: {
diff --git a/src/trace_processor/importers/fuchsia/fuchsia_trace_parser.h b/src/trace_processor/importers/fuchsia/fuchsia_trace_parser.h
index 01cf3e3..d36a9d7 100644
--- a/src/trace_processor/importers/fuchsia/fuchsia_trace_parser.h
+++ b/src/trace_processor/importers/fuchsia/fuchsia_trace_parser.h
@@ -17,8 +17,13 @@
#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_FUCHSIA_FUCHSIA_TRACE_PARSER_H_
#define SRC_TRACE_PROCESSOR_IMPORTERS_FUCHSIA_FUCHSIA_TRACE_PARSER_H_
+#include <functional>
+#include <optional>
+#include <vector>
+
#include "src/trace_processor/importers/common/trace_parser.h"
#include "src/trace_processor/importers/fuchsia/fuchsia_record.h"
+#include "src/trace_processor/importers/fuchsia/fuchsia_trace_utils.h"
#include "src/trace_processor/importers/proto/proto_trace_parser.h"
namespace perfetto {
@@ -36,6 +41,22 @@
void ParseTrackEvent(int64_t, TrackEventData) override;
void ParseTracePacket(int64_t ts, TracePacketData data) override;
+ struct Arg {
+ StringId name;
+ fuchsia_trace_utils::ArgValue value;
+ };
+
+ // Utility to parse record arguments. Exposed here to provide consistent
+ // parsing between trace parsing and tokenization.
+ //
+ // Returns an empty optional on error, otherwise a vector containing zero or
+ // more arguments.
+ static std::optional<std::vector<Arg>> ParseArgs(
+ fuchsia_trace_utils::RecordCursor& cursor,
+ uint32_t n_args,
+ std::function<StringId(base::StringView string)> intern_string,
+ std::function<StringId(uint32_t index)> get_string);
+
private:
TraceProcessorContext* const context_;
std::unique_ptr<ProtoTraceParser> proto_parser_;
diff --git a/src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.cc b/src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.cc
index 8b7f14f..514bfcb 100644
--- a/src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.cc
+++ b/src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.cc
@@ -17,6 +17,7 @@
#include "src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.h"
#include <cinttypes>
+#include <limits>
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/string_view.h"
@@ -24,6 +25,7 @@
#include "src/trace_processor/importers/common/process_tracker.h"
#include "src/trace_processor/importers/common/slice_tracker.h"
#include "src/trace_processor/importers/fuchsia/fuchsia_record.h"
+#include "src/trace_processor/importers/fuchsia/fuchsia_trace_parser.h"
#include "src/trace_processor/importers/proto/proto_trace_parser.h"
#include "src/trace_processor/importers/proto/proto_trace_reader.h"
#include "src/trace_processor/sorter/trace_sorter.h"
@@ -34,6 +36,9 @@
namespace trace_processor {
namespace {
+
+using fuchsia_trace_utils::ArgValue;
+
// Record types
constexpr uint32_t kMetadata = 0;
constexpr uint32_t kInitialization = 1;
@@ -42,7 +47,11 @@
constexpr uint32_t kEvent = 4;
constexpr uint32_t kBlob = 5;
constexpr uint32_t kKernelObject = 7;
-constexpr uint32_t kContextSwitch = 8;
+constexpr uint32_t kSchedulerEvent = 8;
+
+constexpr uint32_t kSchedulerEventLegacyContextSwitch = 0;
+constexpr uint32_t kSchedulerEventContextSwitch = 1;
+constexpr uint32_t kSchedulerEventThreadWakeup = 2;
// Metadata types
constexpr uint32_t kProviderInfo = 1;
@@ -61,9 +70,8 @@
constexpr uint32_t kZxObjTypeProcess = 1;
constexpr uint32_t kZxObjTypeThread = 2;
-// Argument types
-constexpr uint32_t kArgString = 6;
-constexpr uint32_t kArgKernelObject = 8;
+constexpr int32_t kIdleWeight = std::numeric_limits<int32_t>::min();
+
} // namespace
FuchsiaTraceTokenizer::FuchsiaTraceTokenizer(TraceProcessorContext* context)
@@ -72,10 +80,15 @@
running_string_id_(context->storage->InternString("Running")),
runnable_string_id_(context->storage->InternString("R")),
preempted_string_id_(context->storage->InternString("R+")),
+ waking_string_id_(context->storage->InternString("W")),
blocked_string_id_(context->storage->InternString("S")),
suspended_string_id_(context->storage->InternString("T")),
exit_dying_string_id_(context->storage->InternString("Z")),
- exit_dead_string_id_(context->storage->InternString("X")) {
+ exit_dead_string_id_(context->storage->InternString("X")),
+ incoming_weight_id_(context->storage->InternString("incoming_weight")),
+ outgoing_weight_id_(context->storage->InternString("outgoing_weight")),
+ weight_id_(context->storage->InternString("weight")),
+ process_id_(context->storage->InternString("process")) {
RegisterProvider(0, "");
}
@@ -210,6 +223,124 @@
}
}
+void FuchsiaTraceTokenizer::SwitchFrom(Thread* thread,
+ int64_t ts,
+ uint32_t cpu,
+ uint32_t thread_state) {
+ TraceStorage* storage = context_->storage.get();
+ ProcessTracker* procs = context_->process_tracker.get();
+
+ StringId state = IdForOutgoingThreadState(thread_state);
+ UniqueTid utid = procs->UpdateThread(static_cast<uint32_t>(thread->info.tid),
+ static_cast<uint32_t>(thread->info.pid));
+
+ const auto duration = ts - thread->last_ts;
+ thread->last_ts = ts;
+
+ // Close the slice record if one is open for this thread.
+ if (thread->last_slice_row.has_value()) {
+ auto row_ref = thread->last_slice_row->ToRowReference(
+ storage->mutable_sched_slice_table());
+ row_ref.set_dur(duration);
+ row_ref.set_end_state(state);
+ thread->last_slice_row.reset();
+ }
+
+ // Close the state record if one is open for this thread.
+ if (thread->last_state_row.has_value()) {
+ auto row_ref = thread->last_state_row->ToRowReference(
+ storage->mutable_thread_state_table());
+ row_ref.set_dur(duration);
+ thread->last_state_row.reset();
+ }
+
+ // Open a new state record to track the duration of the outgoing
+ // state.
+ tables::ThreadStateTable::Row state_row;
+ state_row.ts = ts;
+ state_row.cpu = cpu;
+ state_row.dur = -1;
+ state_row.state = state;
+ state_row.utid = utid;
+ auto state_row_number =
+ storage->mutable_thread_state_table()->Insert(state_row).row_number;
+ thread->last_state_row = state_row_number;
+}
+
+void FuchsiaTraceTokenizer::SwitchTo(Thread* thread,
+ int64_t ts,
+ uint32_t cpu,
+ int32_t weight) {
+ TraceStorage* storage = context_->storage.get();
+ ProcessTracker* procs = context_->process_tracker.get();
+
+ UniqueTid utid = procs->UpdateThread(static_cast<uint32_t>(thread->info.tid),
+ static_cast<uint32_t>(thread->info.pid));
+
+ const auto duration = ts - thread->last_ts;
+ thread->last_ts = ts;
+
+ // Close the state record if one is open for this thread.
+ if (thread->last_state_row.has_value()) {
+ auto row_ref = thread->last_state_row->ToRowReference(
+ storage->mutable_thread_state_table());
+ row_ref.set_dur(duration);
+ thread->last_state_row.reset();
+ }
+
+ // Open a new slice record for this thread.
+ tables::SchedSliceTable::Row slice_row;
+ slice_row.ts = ts;
+ slice_row.cpu = cpu;
+ slice_row.dur = -1;
+ slice_row.utid = utid;
+ slice_row.priority = weight;
+ auto slice_row_number =
+ storage->mutable_sched_slice_table()->Insert(slice_row).row_number;
+ thread->last_slice_row = slice_row_number;
+
+ // Open a new state record for this thread.
+ tables::ThreadStateTable::Row state_row;
+ state_row.ts = ts;
+ state_row.cpu = cpu;
+ state_row.dur = -1;
+ state_row.state = running_string_id_;
+ state_row.utid = utid;
+ auto state_row_number =
+ storage->mutable_thread_state_table()->Insert(state_row).row_number;
+ thread->last_state_row = state_row_number;
+}
+
+void FuchsiaTraceTokenizer::Wake(Thread* thread, int64_t ts, uint32_t cpu) {
+ TraceStorage* storage = context_->storage.get();
+ ProcessTracker* procs = context_->process_tracker.get();
+
+ UniqueTid utid = procs->UpdateThread(static_cast<uint32_t>(thread->info.tid),
+ static_cast<uint32_t>(thread->info.pid));
+
+ const auto duration = ts - thread->last_ts;
+ thread->last_ts = ts;
+
+ // Close the state record if one is open for this thread.
+ if (thread->last_state_row.has_value()) {
+ auto row_ref = thread->last_state_row->ToRowReference(
+ storage->mutable_thread_state_table());
+ row_ref.set_dur(duration);
+ thread->last_state_row.reset();
+ }
+
+ // Open a new state record for this thread.
+ tables::ThreadStateTable::Row state_row;
+ state_row.ts = ts;
+ state_row.cpu = cpu;
+ state_row.dur = -1;
+ state_row.state = waking_string_id_;
+ state_row.utid = utid;
+ auto state_row_number =
+ storage->mutable_thread_state_table()->Insert(state_row).row_number;
+ thread->last_state_row = state_row_number;
+}
+
// Most record types are read and recorded in |TraceStorage| here directly.
// Event records are sorted by timestamp before processing, so instead of
// recording them in |TraceStorage| they are given to |TraceSorter|. In order to
@@ -235,6 +366,14 @@
return;
}
+ // Adapters for FuchsiaTraceParser::ParseArgs.
+ const auto intern_string = [this](base::StringView string) {
+ return context_->storage->InternString(string);
+ };
+ const auto get_string = [this](uint16_t index) {
+ return current_provider_->GetString(index);
+ };
+
switch (record_type) {
case kMetadata: {
uint32_t metadata_type =
@@ -334,22 +473,21 @@
cursor.ReadInlineThread(nullptr);
} else {
record.InsertThread(thread_ref,
- current_provider_->thread_table[thread_ref]);
+ current_provider_->GetThread(thread_ref));
}
if (fuchsia_trace_utils::IsInlineString(cat_ref)) {
// Skip over inline string
cursor.ReadInlineString(cat_ref, nullptr);
} else {
- record.InsertString(cat_ref, current_provider_->string_table[cat_ref]);
+ record.InsertString(cat_ref, current_provider_->GetString(cat_ref));
}
if (fuchsia_trace_utils::IsInlineString(name_ref)) {
// Skip over inline string
cursor.ReadInlineString(name_ref, nullptr);
} else {
- record.InsertString(name_ref,
- current_provider_->string_table[name_ref]);
+ record.InsertString(name_ref, current_provider_->GetString(name_ref));
}
uint32_t n_args =
@@ -373,10 +511,10 @@
cursor.ReadInlineString(arg_name_ref, nullptr);
} else {
record.InsertString(arg_name_ref,
- current_provider_->string_table[arg_name_ref]);
+ current_provider_->GetString(arg_name_ref));
}
- if (arg_type == kArgString) {
+ if (arg_type == ArgValue::ArgType::kString) {
uint32_t arg_value_ref =
fuchsia_trace_utils::ReadField<uint32_t>(arg_header, 32, 47);
if (fuchsia_trace_utils::IsInlineString(arg_value_ref)) {
@@ -384,7 +522,7 @@
cursor.ReadInlineString(arg_value_ref, nullptr);
} else {
record.InsertString(arg_value_ref,
- current_provider_->string_table[arg_value_ref]);
+ current_provider_->GetString(arg_value_ref));
}
}
@@ -445,7 +583,7 @@
}
name = storage->InternString(name_view);
} else {
- name = current_provider_->string_table[name_ref];
+ name = current_provider_->GetString(name_ref);
}
switch (obj_type) {
@@ -462,43 +600,23 @@
case kZxObjTypeThread: {
uint32_t n_args =
fuchsia_trace_utils::ReadField<uint32_t>(header, 40, 43);
+
+ auto maybe_args = FuchsiaTraceParser::ParseArgs(
+ cursor, n_args, intern_string, get_string);
+ if (!maybe_args.has_value()) {
+ context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+ return;
+ }
+
uint64_t pid = 0;
-
- // Scan for a Kernel Object argument named "process"
- for (uint32_t i = 0; i < n_args; i++) {
- const size_t arg_base = cursor.WordIndex();
- uint64_t arg_header;
- if (!cursor.ReadUint64(&arg_header)) {
- storage->IncrementStats(stats::fuchsia_invalid_event);
- return;
- }
- uint32_t arg_type =
- fuchsia_trace_utils::ReadField<uint32_t>(arg_header, 0, 3);
- uint32_t arg_size =
- fuchsia_trace_utils::ReadField<uint32_t>(arg_header, 4, 15);
- if (arg_type == kArgKernelObject) {
- uint32_t arg_name_ref =
- fuchsia_trace_utils::ReadField<uint32_t>(arg_header, 16, 31);
- base::StringView arg_name;
- if (fuchsia_trace_utils::IsInlineString(arg_name_ref)) {
- if (!cursor.ReadInlineString(arg_name_ref, &arg_name)) {
- storage->IncrementStats(stats::fuchsia_invalid_event);
- return;
- }
- } else {
- arg_name = storage->GetString(
- current_provider_->string_table[arg_name_ref]);
+ for (const auto arg : *maybe_args) {
+ if (arg.name == process_id_) {
+ if (arg.value.Type() != ArgValue::ArgType::kKoid) {
+ storage->IncrementStats(stats::fuchsia_invalid_event);
+ return;
}
-
- if (arg_name == "process") {
- if (!cursor.ReadUint64(&pid)) {
- storage->IncrementStats(stats::fuchsia_invalid_event);
- return;
- }
- }
+ pid = arg.value.Koid();
}
-
- cursor.SetWordIndex(arg_base + arg_size);
}
Thread& thread = GetThread(obj_id);
@@ -516,140 +634,200 @@
}
break;
}
- case kContextSwitch: {
+ case kSchedulerEvent: {
// Context switch records come in order, so they do not need to go through
// TraceSorter.
- uint32_t cpu = fuchsia_trace_utils::ReadField<uint32_t>(header, 16, 23);
- uint32_t outgoing_state =
- fuchsia_trace_utils::ReadField<uint32_t>(header, 24, 27);
- uint32_t outgoing_thread_ref =
- fuchsia_trace_utils::ReadField<uint32_t>(header, 28, 35);
- int32_t outgoing_priority =
- fuchsia_trace_utils::ReadField<int32_t>(header, 44, 51);
- uint32_t incoming_thread_ref =
- fuchsia_trace_utils::ReadField<uint32_t>(header, 36, 43);
- int32_t incoming_priority =
- fuchsia_trace_utils::ReadField<int32_t>(header, 52, 59);
+ uint32_t event_type =
+ fuchsia_trace_utils::ReadField<uint32_t>(header, 60, 63);
+ switch (event_type) {
+ case kSchedulerEventLegacyContextSwitch: {
+ uint32_t cpu =
+ fuchsia_trace_utils::ReadField<uint32_t>(header, 16, 23);
+ uint32_t outgoing_state =
+ fuchsia_trace_utils::ReadField<uint32_t>(header, 24, 27);
+ uint32_t outgoing_thread_ref =
+ fuchsia_trace_utils::ReadField<uint32_t>(header, 28, 35);
+ int32_t outgoing_priority =
+ fuchsia_trace_utils::ReadField<int32_t>(header, 44, 51);
+ uint32_t incoming_thread_ref =
+ fuchsia_trace_utils::ReadField<uint32_t>(header, 36, 43);
+ int32_t incoming_priority =
+ fuchsia_trace_utils::ReadField<int32_t>(header, 52, 59);
- int64_t ts;
- if (!cursor.ReadTimestamp(current_provider_->ticks_per_second, &ts)) {
- context_->storage->IncrementStats(stats::fuchsia_invalid_event);
- return;
- }
- if (ts == -1) {
- context_->storage->IncrementStats(stats::fuchsia_invalid_event);
- return;
- }
+ int64_t ts;
+ if (!cursor.ReadTimestamp(current_provider_->ticks_per_second, &ts)) {
+ context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+ return;
+ }
+ if (ts == -1) {
+ context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+ return;
+ }
- FuchsiaThreadInfo outgoing_thread_info;
- if (fuchsia_trace_utils::IsInlineThread(outgoing_thread_ref)) {
- if (!cursor.ReadInlineThread(&outgoing_thread_info)) {
- context_->storage->IncrementStats(stats::fuchsia_invalid_event);
- return;
+ FuchsiaThreadInfo outgoing_thread_info;
+ if (fuchsia_trace_utils::IsInlineThread(outgoing_thread_ref)) {
+ if (!cursor.ReadInlineThread(&outgoing_thread_info)) {
+ context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+ return;
+ }
+ } else {
+ outgoing_thread_info =
+ current_provider_->GetThread(outgoing_thread_ref);
+ }
+ Thread& outgoing_thread = GetThread(outgoing_thread_info.tid);
+
+ FuchsiaThreadInfo incoming_thread_info;
+ if (fuchsia_trace_utils::IsInlineThread(incoming_thread_ref)) {
+ if (!cursor.ReadInlineThread(&incoming_thread_info)) {
+ context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+ return;
+ }
+ } else {
+ incoming_thread_info =
+ current_provider_->GetThread(incoming_thread_ref);
+ }
+ Thread& incoming_thread = GetThread(incoming_thread_info.tid);
+
+ // Idle threads are identified by pid == 0 and prio == 0.
+ const bool incoming_is_idle =
+ incoming_thread.info.pid == 0 && incoming_priority == 0;
+ const bool outgoing_is_idle =
+ outgoing_thread.info.pid == 0 && outgoing_priority == 0;
+
+ // Handle switching away from the currently running thread.
+ if (!outgoing_is_idle) {
+ SwitchFrom(&outgoing_thread, ts, cpu, outgoing_state);
+ }
+
+ // Handle switching to the new currently running thread.
+ if (!incoming_is_idle) {
+ SwitchTo(&incoming_thread, ts, cpu, incoming_priority);
+ }
+ break;
}
- } else {
- outgoing_thread_info =
- current_provider_->thread_table[outgoing_thread_ref];
- }
- Thread& outgoing_thread = GetThread(outgoing_thread_info.tid);
+ case kSchedulerEventContextSwitch: {
+ const uint32_t argument_count =
+ fuchsia_trace_utils::ReadField<uint32_t>(header, 16, 19);
+ const uint32_t cpu =
+ fuchsia_trace_utils::ReadField<uint32_t>(header, 20, 35);
+ const uint32_t outgoing_state =
+ fuchsia_trace_utils::ReadField<uint32_t>(header, 36, 39);
- FuchsiaThreadInfo incoming_thread_info;
- if (fuchsia_trace_utils::IsInlineThread(incoming_thread_ref)) {
- if (!cursor.ReadInlineThread(&incoming_thread_info)) {
- context_->storage->IncrementStats(stats::fuchsia_invalid_event);
- return;
+ int64_t ts;
+ if (!cursor.ReadTimestamp(current_provider_->ticks_per_second, &ts)) {
+ context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+ return;
+ }
+ if (ts < 0) {
+ context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+ return;
+ }
+
+ uint64_t outgoing_tid;
+ if (!cursor.ReadUint64(&outgoing_tid)) {
+ context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+ return;
+ }
+ Thread& outgoing_thread = GetThread(outgoing_tid);
+
+ uint64_t incoming_tid;
+ if (!cursor.ReadUint64(&incoming_tid)) {
+ context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+ return;
+ }
+ Thread& incoming_thread = GetThread(incoming_tid);
+
+ auto maybe_args = FuchsiaTraceParser::ParseArgs(
+ cursor, argument_count, intern_string, get_string);
+ if (!maybe_args.has_value()) {
+ context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+ return;
+ }
+
+ int32_t incoming_weight = 0;
+ int32_t outgoing_weight = 0;
+
+ for (const auto& arg : *maybe_args) {
+ if (arg.name == incoming_weight_id_) {
+ if (arg.value.Type() != ArgValue::ArgType::kInt32) {
+ context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+ return;
+ }
+ incoming_weight = arg.value.Int32();
+ } else if (arg.name == outgoing_weight_id_) {
+ if (arg.value.Type() != ArgValue::ArgType::kInt32) {
+ context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+ return;
+ }
+ outgoing_weight = arg.value.Int32();
+ }
+ }
+
+ const bool incoming_is_idle = incoming_weight == kIdleWeight;
+ const bool outgoing_is_idle = outgoing_weight == kIdleWeight;
+
+ // Handle switching away from the currently running thread.
+ if (!outgoing_is_idle) {
+ SwitchFrom(&outgoing_thread, ts, cpu, outgoing_state);
+ }
+
+ // Handle switching to the new currently running thread.
+ if (!incoming_is_idle) {
+ SwitchTo(&incoming_thread, ts, cpu, incoming_weight);
+ }
+ break;
}
- } else {
- incoming_thread_info =
- current_provider_->thread_table[incoming_thread_ref];
- }
- Thread& incoming_thread = GetThread(incoming_thread_info.tid);
+ case kSchedulerEventThreadWakeup: {
+ const uint32_t argument_count =
+ fuchsia_trace_utils::ReadField<uint32_t>(header, 16, 19);
+ const uint32_t cpu =
+ fuchsia_trace_utils::ReadField<uint32_t>(header, 20, 35);
- // Idle threads are identified by pid == 0 and prio == 0.
- const bool incoming_is_idle =
- incoming_thread.info.pid == 0 && incoming_priority == 0;
- const bool outgoing_is_idle =
- outgoing_thread.info.pid == 0 && outgoing_priority == 0;
+ int64_t ts;
+ if (!cursor.ReadTimestamp(current_provider_->ticks_per_second, &ts)) {
+ context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+ return;
+ }
+ if (ts < 0) {
+ context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+ return;
+ }
- // Handle switching away from the currently running thread.
- if (!outgoing_is_idle) {
- UniqueTid utid = procs->UpdateThread(
- static_cast<uint32_t>(outgoing_thread.info.tid),
- static_cast<uint32_t>(outgoing_thread.info.pid));
+ uint64_t waking_tid;
+ if (!cursor.ReadUint64(&waking_tid)) {
+ context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+ return;
+ }
+ Thread& waking_thread = GetThread(waking_tid);
- StringId state = IdForOutgoingThreadState(outgoing_state);
+ auto maybe_args = FuchsiaTraceParser::ParseArgs(
+ cursor, argument_count, intern_string, get_string);
+ if (!maybe_args.has_value()) {
+ context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+ return;
+ }
- const auto duration = ts - outgoing_thread.last_ts;
- outgoing_thread.last_ts = ts;
+ int32_t waking_weight = 0;
- // Close the slice record if one is open for this thread.
- if (outgoing_thread.last_slice_row.has_value()) {
- auto row_ref = outgoing_thread.last_slice_row->ToRowReference(
- storage->mutable_sched_slice_table());
- row_ref.set_dur(duration);
- row_ref.set_end_state(state);
- row_ref.set_priority(outgoing_priority);
- outgoing_thread.last_slice_row.reset();
+ for (const auto& arg : *maybe_args) {
+ if (arg.name == weight_id_) {
+ if (arg.value.Type() != ArgValue::ArgType::kInt32) {
+ context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+ return;
+ }
+ waking_weight = arg.value.Int32();
+ }
+ }
+
+ const bool waking_is_idle = waking_weight == kIdleWeight;
+ if (!waking_is_idle) {
+ Wake(&waking_thread, ts, cpu);
+ }
+ break;
}
-
- // Close the state record if one is open for this thread.
- if (outgoing_thread.last_state_row.has_value()) {
- auto row_ref = outgoing_thread.last_state_row->ToRowReference(
- storage->mutable_thread_state_table());
- row_ref.set_dur(duration);
- outgoing_thread.last_state_row.reset();
- }
-
- // Open a new state record to track the duration of the outgoing state.
- tables::ThreadStateTable::Row state_row;
- state_row.ts = ts;
- state_row.cpu = cpu;
- state_row.dur = -1;
- state_row.state = state;
- state_row.utid = utid;
- auto state_row_number =
- storage->mutable_thread_state_table()->Insert(state_row).row_number;
- outgoing_thread.last_state_row = state_row_number;
- }
-
- // Handle switching to the new currently running thread.
- if (!incoming_is_idle) {
- UniqueTid utid = procs->UpdateThread(
- static_cast<uint32_t>(incoming_thread.info.tid),
- static_cast<uint32_t>(incoming_thread.info.pid));
-
- const auto duration = ts - incoming_thread.last_ts;
- incoming_thread.last_ts = ts;
-
- // Close the state record if one is open for this thread.
- if (incoming_thread.last_state_row.has_value()) {
- auto row_ref = incoming_thread.last_state_row->ToRowReference(
- storage->mutable_thread_state_table());
- row_ref.set_dur(duration);
- incoming_thread.last_state_row.reset();
- }
-
- // Open a new slice record for this thread.
- tables::SchedSliceTable::Row slice_row;
- slice_row.ts = ts;
- slice_row.cpu = cpu;
- slice_row.dur = -1;
- slice_row.utid = utid;
- slice_row.priority = incoming_priority;
- auto slice_row_number =
- storage->mutable_sched_slice_table()->Insert(slice_row).row_number;
- incoming_thread.last_slice_row = slice_row_number;
-
- // Open a new state record for this thread.
- tables::ThreadStateTable::Row state_row;
- state_row.ts = ts;
- state_row.cpu = cpu;
- state_row.dur = -1;
- state_row.state = running_string_id_;
- state_row.utid = utid;
- auto state_row_number =
- storage->mutable_thread_state_table()->Insert(state_row).row_number;
- incoming_thread.last_state_row = state_row_number;
+ default:
+ PERFETTO_DLOG("Skipping unknown scheduler event type %d", event_type);
+ break;
}
break;
diff --git a/src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.h b/src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.h
index 35e66f5..4b4d610 100644
--- a/src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.h
+++ b/src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.h
@@ -46,6 +46,24 @@
std::unordered_map<uint64_t, StringId> string_table;
std::unordered_map<uint64_t, FuchsiaThreadInfo> thread_table;
+ // Returns a StringId for the given FXT string ref id.
+ StringId GetString(uint64_t string_ref) {
+ auto search = string_table.find(string_ref);
+ if (search != string_table.end()) {
+ return search->second;
+ }
+ return kNullStringId;
+ }
+
+ // Returns a FuchsiaThreadInfo for the given FXT thread ref id.
+ FuchsiaThreadInfo GetThread(uint64_t thread_ref) {
+ auto search = thread_table.find(thread_ref);
+ if (search != thread_table.end()) {
+ return search->second;
+ }
+ return {0, 0};
+ }
+
uint64_t ticks_per_second = 1000000000;
};
@@ -59,6 +77,13 @@
base::Optional<tables::ThreadStateTable::RowNumber> last_state_row;
};
+ void SwitchFrom(Thread* thread,
+ int64_t ts,
+ uint32_t cpu,
+ uint32_t thread_state);
+ void SwitchTo(Thread* thread, int64_t ts, uint32_t cpu, int32_t weight);
+ void Wake(Thread* thread, int64_t ts, uint32_t cpu);
+
// Allocates or returns an existing Thread instance for the given tid.
Thread& GetThread(uint64_t tid) {
auto search = threads_.find(tid);
@@ -88,11 +113,18 @@
StringId running_string_id_;
StringId runnable_string_id_;
StringId preempted_string_id_;
+ StringId waking_string_id_;
StringId blocked_string_id_;
StringId suspended_string_id_;
StringId exit_dying_string_id_;
StringId exit_dead_string_id_;
+ // Interned string ids for record arguments.
+ StringId incoming_weight_id_;
+ StringId outgoing_weight_id_;
+ StringId weight_id_;
+ StringId process_id_;
+
// Map from tid to Thread.
std::unordered_map<uint64_t, Thread> threads_;
};
diff --git a/src/trace_processor/metrics/sql/android/OWNERS b/src/trace_processor/metrics/sql/android/OWNERS
index 329c8a2..3053a78 100644
--- a/src/trace_processor/metrics/sql/android/OWNERS
+++ b/src/trace_processor/metrics/sql/android/OWNERS
@@ -1,2 +1,4 @@
ilkos@google.com
-timmurray@google.com
\ No newline at end of file
+timmurray@google.com
+
+per-file hsc.sql=steventerrell@google.com,carmenjackson@google.com
diff --git a/src/trace_processor/metrics/sql/android/startup/hsc.sql b/src/trace_processor/metrics/sql/android/startup/hsc.sql
index c632673..9813b69 100644
--- a/src/trace_processor/metrics/sql/android/startup/hsc.sql
+++ b/src/trace_processor/metrics/sql/android/startup/hsc.sql
@@ -55,6 +55,18 @@
JOIN android_startups launches ON launches.package GLOB '*' || functions.process_name || '*'
WHERE functions.function_name GLOB "Choreographer#doFrame*" AND functions.ts > launches.ts;
+DROP VIEW IF EXISTS android_render_frame_times;
+CREATE VIEW android_render_frame_times AS
+SELECT
+ functions.ts AS ts,
+ functions.ts + functions.dur AS ts_end,
+ launches.package AS name,
+ launches.startup_id,
+ ROW_NUMBER() OVER(PARTITION BY launches.startup_id ORDER BY functions.ts ASC) AS number
+FROM functions
+JOIN android_startups launches ON launches.package GLOB '*' || functions.process_name || '*'
+WHERE functions.function_name GLOB "DrawFrame*" AND functions.ts > launches.ts;
+
DROP VIEW IF EXISTS frame_times;
CREATE VIEW frame_times AS
SELECT startup_id AS launch_id, * FROM android_frame_times;
@@ -73,15 +85,15 @@
WHERE android_frame_times.number = 2 AND android_frame_times.name GLOB "*roid.calcul*" AND android_frame_times.startup_id = launches.startup_id;
-- Calendar
+-- Using the DrawFrame slice from the render thread due to Calendar delaying its rendering
INSERT INTO hsc_based_startup_times
SELECT
launches.package AS package,
launches.startup_id AS id,
- android_frame_times.ts_end - launches.ts AS ts_total
-FROM android_frame_times
-JOIN android_startups launches ON launches.package GLOB '*' || android_frame_times.name || '*'
-WHERE android_frame_times.name GLOB "*id.calendar*" AND android_frame_times.startup_id = launches.startup_id
-ORDER BY ABS(android_frame_times.ts_end - (SELECT ts + dur FROM functions WHERE function_name GLOB "DrawFrame*" AND process_name GLOB "*id.calendar" ORDER BY ts LIMIT 1)) LIMIT 1;
+ android_render_frame_times.ts_end - launches.ts AS ts_total
+FROM android_render_frame_times
+JOIN android_startups launches ON launches.package GLOB '*' || android_render_frame_times.name || '*'
+WHERE android_render_frame_times.number = 5 AND android_render_frame_times.name GLOB "*id.calendar*" AND android_render_frame_times.startup_id = launches.startup_id;
-- Camera
INSERT INTO hsc_based_startup_times
@@ -125,6 +137,7 @@
WHERE android_frame_times.number = 3 AND android_frame_times.name GLOB "*id.contacts" AND android_frame_times.startup_id = launches.startup_id;
-- Dialer
+-- Dialer only runs one animation at startup, use the last animation frame to indicate startup.
INSERT INTO hsc_based_startup_times
SELECT
launches.package AS package,
@@ -132,7 +145,7 @@
android_frame_times.ts_end - launches.ts AS ts_total
FROM android_frame_times
JOIN android_startups launches ON launches.package GLOB '*' || android_frame_times.name || '*'
-WHERE android_frame_times.number = 1 AND android_frame_times.name GLOB "*id.dialer" AND android_frame_times.startup_id = launches.startup_id;
+WHERE android_frame_times.ts > (SELECT ts + dur FROM animators WHERE process_name GLOB "*id.dialer" AND animator_name GLOB "*animator*" ORDER BY (ts + dur) DESC LIMIT 1) AND android_frame_times.name GLOB "*id.dialer" AND android_frame_times.startup_id = launches.startup_id LIMIT 1;
-- Facebook
INSERT INTO hsc_based_startup_times
@@ -179,6 +192,7 @@
ORDER BY ts_total LIMIT 1;
-- Maps
+-- Use the 8th choreographer frame to indicate startup.
INSERT INTO hsc_based_startup_times
SELECT
launches.package AS package,
@@ -186,7 +200,7 @@
android_frame_times.ts_end - launches.ts AS ts_total
FROM android_frame_times
JOIN android_startups launches ON launches.package GLOB '*' || android_frame_times.name || '*'
-WHERE android_frame_times.number = 1 AND android_frame_times.name GLOB "*maps*" AND android_frame_times.startup_id = launches.startup_id;
+WHERE android_frame_times.number = 8 AND android_frame_times.name GLOB "*maps*" AND android_frame_times.startup_id = launches.startup_id;
-- Messages
INSERT INTO hsc_based_startup_times
@@ -211,6 +225,7 @@
ORDER BY ts_total DESC LIMIT 1;
-- Photos
+-- Use the animator:translationZ slice as startup indicator.
INSERT INTO hsc_based_startup_times
SELECT
launches.package AS package,
@@ -218,7 +233,7 @@
android_frame_times.ts_end - launches.ts AS ts_total
FROM android_frame_times
JOIN android_startups launches ON launches.package GLOB '*' || android_frame_times.name || '*'
-WHERE android_frame_times.number = 1 AND android_frame_times.name GLOB "*apps.photos*" AND android_frame_times.startup_id = launches.startup_id;
+WHERE android_frame_times.ts > (SELECT ts + dur FROM animators WHERE process_name GLOB "*apps.photos" AND animator_name GLOB "animator:translationZ" ORDER BY (ts + dur) DESC LIMIT 1) AND android_frame_times.name GLOB "*apps.photos*" AND android_frame_times.startup_id = launches.startup_id LIMIT 1;
-- Settings was deprecated in favor of reportFullyDrawn b/169694037.
@@ -255,11 +270,12 @@
ORDER BY ts_total LIMIT 1;
-- Youtube
+-- Use the 10th frame that is rendered
INSERT INTO hsc_based_startup_times
SELECT
launches.package AS package,
launches.startup_id AS id,
- android_frame_times.ts_end - launches.ts AS ts_total
-FROM android_frame_times
-JOIN android_startups launches ON launches.package GLOB '*' || android_frame_times.name || '*'
-WHERE android_frame_times.number = 2 AND android_frame_times.name GLOB "*id.youtube" AND android_frame_times.startup_id = launches.startup_id;
+ android_render_frame_times.ts_end - launches.ts AS ts_total
+FROM android_render_frame_times
+JOIN android_startups launches ON launches.package GLOB '*' || android_render_frame_times.name || '*'
+WHERE android_render_frame_times.number = 10 AND android_render_frame_times.name GLOB "*id.youtube" AND android_render_frame_times.startup_id = launches.startup_id;
diff --git a/test/cts/Android.bp b/test/cts/Android.bp
index c5a8ed6..c05189a 100644
--- a/test/cts/Android.bp
+++ b/test/cts/Android.bp
@@ -14,7 +14,6 @@
"end_to_end_integrationtest_cts.cc",
"heapprofd_java_test_cts.cc",
"heapprofd_test_cts.cc",
- "reporter_test_cts.cc",
"traced_perf_test_cts.cc",
":perfetto_protos_perfetto_config_cpp_gen",
":perfetto_protos_perfetto_common_cpp_gen"
@@ -59,7 +58,6 @@
":CtsPerfettoProducerApp",
":CtsPerfettoReleaseApp",
":CtsPerfettoDebuggableApp",
- ":CtsPerfettoReporterApp"
],
stl: "libc++_static",
// This test is also run via Mainline Testing against the ART Mainline
diff --git a/test/cts/AndroidTest.xml b/test/cts/AndroidTest.xml
index 84e72bc..27ccef1 100644
--- a/test/cts/AndroidTest.xml
+++ b/test/cts/AndroidTest.xml
@@ -28,7 +28,6 @@
<option name="test-file-name" value="CtsPerfettoReleaseApp.apk" />
<option name="test-file-name" value="CtsPerfettoProfileableApp.apk" />
<option name="test-file-name" value="CtsPerfettoNonProfileableApp.apk" />
- <option name="test-file-name" value="CtsPerfettoReporterApp.apk" />
</target_preparer>
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
<option name="cleanup" value="true" />
diff --git a/test/cts/producer/Android.bp b/test/cts/producer/Android.bp
index 3c07ce4..c2695e9 100644
--- a/test/cts/producer/Android.bp
+++ b/test/cts/producer/Android.bp
@@ -32,9 +32,9 @@
compile_multilib: "both",
srcs: ["src/**/*.java"],
- sdk_version: "current",
// This test is also run via Mainline Testing against the ART Mainline
// Module, which is updatable since Android 12 (API level 31).
+ sdk_version: "31",
min_sdk_version: "31",
jni_libs: [
"libperfettocts_jni",
diff --git a/test/cts/producer/jni/Android.bp b/test/cts/producer/jni/Android.bp
index 696dc97..70cecbf 100644
--- a/test/cts/producer/jni/Android.bp
+++ b/test/cts/producer/jni/Android.bp
@@ -14,8 +14,6 @@
],
header_libs: ["jni_headers"],
static_libs: [
- "libgtest",
- "libprotobuf-cpp-lite",
"perfetto_cts_jni_deps",
"libperfetto_client_experimental",
],
diff --git a/test/cts/reporter/Android.bp b/test/cts/reporter/Android.bp
new file mode 100644
index 0000000..597426c
--- /dev/null
+++ b/test/cts/reporter/Android.bp
@@ -0,0 +1,57 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "external_perfetto_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["external_perfetto_license"],
+}
+
+cc_test {
+ name: "CtsPerfettoReporterTestCases",
+ srcs: [
+ "reporter_test_cts.cc",
+ ":perfetto_protos_perfetto_config_cpp_gen",
+ ":perfetto_protos_perfetto_common_cpp_gen"
+ ],
+ generated_headers: [
+ "perfetto_protos_perfetto_config_cpp_gen_headers",
+ "perfetto_protos_perfetto_common_cpp_gen_headers",
+ ],
+ static_libs: [
+ "libgmock",
+ "libprotobuf-cpp-lite",
+ "libperfetto_client_experimental",
+ "perfetto_cts_deps",
+ "perfetto_trace_protos",
+ ],
+ whole_static_libs: [
+ "perfetto_gtest_logcat_printer",
+ ],
+ shared_libs: [
+ "libandroid",
+ "liblog",
+ ],
+ test_suites: [
+ "cts",
+ "vts10",
+ "general-tests",
+ ],
+ compile_multilib: "both",
+ multilib: {
+ lib32: {
+ suffix: "32",
+ },
+ lib64: {
+ suffix: "64",
+ },
+ },
+ per_testcase_directory: true,
+ data: [
+ ":CtsPerfettoReporterApp"
+ ],
+ stl: "libc++_static",
+ defaults: [
+ "perfetto_defaults",
+ ],
+}
diff --git a/test/cts/reporter/AndroidTest.xml b/test/cts/reporter/AndroidTest.xml
new file mode 100644
index 0000000..0fe8c0e
--- /dev/null
+++ b/test/cts/reporter/AndroidTest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+<configuration description="Config for CTS Perfetto Reporter test cases">
+ <option name="config-descriptor:metadata" key="component" value="metrics" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <option name="test-suite-tag" value="cts" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsPerfettoReporterApp.apk" />
+ </target_preparer>
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="cleanup" value="true" />
+ <option name="push" value="CtsPerfettoReporterTestCases->/data/local/tmp/CtsPerfettoReporterTestCases" />
+ <option name="append-bitness" value="true" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="CtsPerfettoReporterTestCases" />
+ <option name="runtime-hint" value="0m40s" />
+ <!-- test-timeout unit is ms -->
+ <option name="native-test-timeout" value="40000" />
+ </test>
+</configuration>
diff --git a/test/cts/reporter/app/Android.bp b/test/cts/reporter/app/Android.bp
index 6e7c880..ab2633b 100644
--- a/test/cts/reporter/app/Android.bp
+++ b/test/cts/reporter/app/Android.bp
@@ -27,7 +27,4 @@
srcs: ["src/**/*.java"],
platform_apis: true,
privileged: true,
- // This test is also run via Mainline Testing against the ART Mainline
- // Module, which is updatable since Android 12 (API level 31).
- min_sdk_version: "31",
}
diff --git a/test/cts/reporter_test_cts.cc b/test/cts/reporter/reporter_test_cts.cc
similarity index 100%
rename from test/cts/reporter_test_cts.cc
rename to test/cts/reporter/reporter_test_cts.cc
diff --git a/test/cts/test_apps/Android.bp b/test/cts/test_apps/Android.bp
index 648a23c..a11e7cb 100644
--- a/test/cts/test_apps/Android.bp
+++ b/test/cts/test_apps/Android.bp
@@ -34,9 +34,9 @@
compile_multilib: "both",
srcs: ["src/**/*.java"],
- sdk_version: "current",
// This test is also run via Mainline Testing against the ART Mainline
// Module, which is updatable since Android 12 (API level 31).
+ sdk_version: "31",
min_sdk_version: "31",
jni_libs: [
"libperfettocts_native",
@@ -57,9 +57,9 @@
compile_multilib: "both",
srcs: ["src/**/*.java"],
- sdk_version: "current",
// This test is also run via Mainline Testing against the ART Mainline
// Module, which is updatable since Android 12 (API level 31).
+ sdk_version: "31",
min_sdk_version: "31",
jni_libs: [
"libperfettocts_native",
@@ -80,9 +80,9 @@
compile_multilib: "both",
srcs: ["src/**/*.java"],
- sdk_version: "current",
// This test is also run via Mainline Testing against the ART Mainline
// Module, which is updatable since Android 12 (API level 31).
+ sdk_version: "31",
min_sdk_version: "31",
jni_libs: [
"libperfettocts_native",
@@ -103,9 +103,9 @@
compile_multilib: "both",
srcs: ["src/**/*.java"],
- sdk_version: "current",
// This test is also run via Mainline Testing against the ART Mainline
// Module, which is updatable since Android 12 (API level 31).
+ sdk_version: "31",
min_sdk_version: "31",
jni_libs: [
"libperfettocts_native",
diff --git a/test/data/fuchsia_trace_sched.fxt.sha256 b/test/data/fuchsia_trace_sched.fxt.sha256
new file mode 100644
index 0000000..55b5cf8
--- /dev/null
+++ b/test/data/fuchsia_trace_sched.fxt.sha256
@@ -0,0 +1 @@
+e796127dd16d1f4676dc33016f133010fcf138aa68fb5b85443167527f8a8602
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/fuchsia/tests.py b/test/trace_processor/diff_tests/fuchsia/tests.py
index 86d08a7..54d0773 100644
--- a/test/trace_processor/diff_tests/fuchsia/tests.py
+++ b/test/trace_processor/diff_tests/fuchsia/tests.py
@@ -52,6 +52,36 @@
19677791779,3,96082,"S",20,1680
"""))
+ def test_fuchsia_sched(self):
+ return DiffTestBlueprint(
+ trace=DataPath('fuchsia_trace_sched.fxt'),
+ query="""
+ SELECT
+ ts,
+ cpu,
+ dur,
+ end_state,
+ priority,
+ tid
+ FROM sched
+ JOIN thread USING(utid)
+ ORDER BY ts
+ LIMIT 10;
+ """,
+ out=Csv("""
+ "ts","cpu","dur","end_state","priority","tid"
+ 68988611421,3,313611,"S",3122,3196
+ 68988925032,3,98697,"S",3122,23416
+ 68988957574,0,632536,"S",3122,3189
+ 68989023729,3,51371,"S",3122,3196
+ 68989075100,3,46773,"R",3122,25332
+ 68989121873,3,53620,"S",2147483647,24654
+ 68989175493,3,5241,"S",3122,25332
+ 68989180734,3,138507,"S",3122,30933
+ 68989319241,3,25028,"S",3122,30297
+ 68989344269,3,52723,"S",3122,28343
+ """))
+
def test_fuchsia_smoke_slices(self):
return DiffTestBlueprint(
trace=DataPath('fuchsia_trace.fxt'),
diff --git a/tools/gen_android_bp b/tools/gen_android_bp
index b7da65d..450acf8 100755
--- a/tools/gen_android_bp
+++ b/tools/gen_android_bp
@@ -133,7 +133,7 @@
'android.hardware.health@2.0',
'android.hardware.health-V2-ndk',
'android.hardware.power.stats@1.0',
- "android.hardware.power.stats-V1-cpp",
+ 'android.hardware.power.stats-V1-cpp',
'base',
'binder',
'binder_ndk',
@@ -145,7 +145,7 @@
'log',
'services',
'statssocket',
- "tracingproxy",
+ 'tracingproxy',
'utils',
]
diff --git a/tools/java_heap_dump b/tools/java_heap_dump
index c14dde4..5ef3cfd 100755
--- a/tools/java_heap_dump
+++ b/tools/java_heap_dump
@@ -151,11 +151,22 @@
continuous_dump_cfg = CONTINUOUS_DUMP.format(
dump_interval=args.continuous_dump)
- if args.stop_when_done:
+ if args.continuous_dump:
+ # Unlimited trace duration
+ duration_ms = 0
+ elif args.stop_when_done:
+ # Oneshot heapdump and the system supports data_source_stop_timeout_ms, we
+ # can use a short duration.
duration_ms = 1000
+ else:
+ # Oneshot heapdump, but the system doesn't supports
+ # data_source_stop_timeout_ms, we have to use a longer duration in the hope
+ # of giving enough time to capture the whole dump.
+ duration_ms = 20000
+
+ if args.stop_when_done:
data_source_stop_timeout_ms = 100000
else:
- duration_ms = 20000
data_source_stop_timeout_ms = 0
return CFG.format(
@@ -230,7 +241,8 @@
parser.add_argument(
"-c",
"--continuous-dump",
- help="Dump interval in ms. 0 to disable continuous dump.",
+ help="Dump interval in ms. 0 to disable continuous dump. When continuous "
+ "dump is enabled, use CTRL+C to stop",
type=int,
default=0)
parser.add_argument(
@@ -314,11 +326,21 @@
print("Dumping Java Heap.")
exists = True
+ ctrl_c_count = 0
# Wait for perfetto cmd to return.
while exists:
- exists = subprocess.call(
- ['adb', 'shell', '[ -d /proc/{} ]'.format(perfetto_pid)]) == 0
- time.sleep(1)
+ try:
+ exists = subprocess.call(
+ ['adb', 'shell', '[ -d /proc/{} ]'.format(perfetto_pid)]) == 0
+ time.sleep(1)
+ except KeyboardInterrupt as e:
+ ctrl_c_count += 1
+ subprocess.check_call(
+ ['adb', 'shell', 'kill -TERM {}'.format(perfetto_pid)])
+ if ctrl_c_count == 1:
+ print("Stopping perfetto and waiting for data...")
+ else:
+ raise e
subprocess.check_call(['adb', 'pull', PROFILE_PATH, output_file], stdout=NULL)