Merge "ftrace: record failed atrace & ftrace categories in TraceStats"
diff --git a/protos/perfetto/trace/ftrace/ftrace_stats.proto b/protos/perfetto/trace/ftrace/ftrace_stats.proto
index 1f65456..11b7cbc 100644
--- a/protos/perfetto/trace/ftrace/ftrace_stats.proto
+++ b/protos/perfetto/trace/ftrace/ftrace_stats.proto
@@ -80,4 +80,14 @@
// The memory used by the kernel symbolizer (KernelSymbolMap.size_bytes()).
optional uint32 kernel_symbols_mem_kb = 4;
+
+ // Atrace errors (even non-fatal ones) are reported here. A typical example is
+ // one or more atrace categories not available on the device.
+ optional string atrace_errors = 5;
+
+ // Ftrace events requested by the config but not present on device.
+ repeated string unknown_ftrace_events = 6;
+
+ // Ftrace events requested by the config, present, which we failed to enable.
+ repeated string failed_ftrace_events = 7;
}
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index 0ee96f8..ea46bd2 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -6516,6 +6516,16 @@
// The memory used by the kernel symbolizer (KernelSymbolMap.size_bytes()).
optional uint32 kernel_symbols_mem_kb = 4;
+
+ // Atrace errors (even non-fatal ones) are reported here. A typical example is
+ // one or more atrace categories not available on the device.
+ optional string atrace_errors = 5;
+
+ // Ftrace events requested by the config but not present on device.
+ repeated string unknown_ftrace_events = 6;
+
+ // Ftrace events requested by the config, present, which we failed to enable.
+ repeated string failed_ftrace_events = 7;
}
// End of protos/perfetto/trace/ftrace/ftrace_stats.proto
diff --git a/src/traced/probes/ftrace/atrace_wrapper.cc b/src/traced/probes/ftrace/atrace_wrapper.cc
index 22523f1..632c131 100644
--- a/src/traced/probes/ftrace/atrace_wrapper.cc
+++ b/src/traced/probes/ftrace/atrace_wrapper.cc
@@ -43,7 +43,8 @@
#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
// Args should include "atrace" for argv[0].
-bool ExecvAtrace(const std::vector<std::string>& args) {
+bool ExecvAtrace(const std::vector<std::string>& args,
+ std::string* atrace_errors) {
int status = 1;
std::vector<char*> argv;
@@ -163,20 +164,22 @@
PERFETTO_EINTR(waitpid(pid, &status, 0));
bool ok = WIFEXITED(status) && WEXITSTATUS(status) == 0;
- if (!ok) {
+ if (!ok)
PERFETTO_ELOG("%s", error.c_str());
- }
+ if (atrace_errors)
+ atrace_errors->append(error);
return ok;
}
#endif
} // namespace
-bool RunAtrace(const std::vector<std::string>& args) {
+bool RunAtrace(const std::vector<std::string>& args,
+ std::string* atrace_errors) {
if (g_run_atrace_for_testing)
- return g_run_atrace_for_testing(args);
+ return g_run_atrace_for_testing(args, atrace_errors);
#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
- return ExecvAtrace(args);
+ return ExecvAtrace(args, atrace_errors);
#else
PERFETTO_LOG("Atrace only supported on Android.");
return false;
diff --git a/src/traced/probes/ftrace/atrace_wrapper.h b/src/traced/probes/ftrace/atrace_wrapper.h
index 91f02cb..29847aa 100644
--- a/src/traced/probes/ftrace/atrace_wrapper.h
+++ b/src/traced/probes/ftrace/atrace_wrapper.h
@@ -24,7 +24,8 @@
namespace perfetto {
using RunAtraceFunction =
- std::add_pointer<bool(const std::vector<std::string>& /*args*/)>::type;
+ std::add_pointer<bool(const std::vector<std::string>& /*args*/,
+ std::string* /*atrace_errors*/)>::type;
// When we are sideloaded on an old version of Android (pre P), we cannot use
// atrace --only_userspace because that option doesn't exist. In that case we:
@@ -35,7 +36,8 @@
void SetIsOldAtraceForTesting(bool);
void ClearIsOldAtraceForTesting();
-bool RunAtrace(const std::vector<std::string>& args);
+bool RunAtrace(const std::vector<std::string>& args,
+ std::string* atrace_errors);
void SetRunAtraceForTesting(RunAtraceFunction);
} // namespace perfetto
diff --git a/src/traced/probes/ftrace/ftrace_config_muxer.cc b/src/traced/probes/ftrace/ftrace_config_muxer.cc
index 4ebd30f..ba48c44 100644
--- a/src/traced/probes/ftrace/ftrace_config_muxer.cc
+++ b/src/traced/probes/ftrace/ftrace_config_muxer.cc
@@ -29,6 +29,7 @@
#include "protos/perfetto/trace/ftrace/sched.pbzero.h"
#include "src/traced/probes/ftrace/atrace_wrapper.h"
#include "src/traced/probes/ftrace/compact_sched.h"
+#include "src/traced/probes/ftrace/ftrace_stats.h"
namespace perfetto {
namespace {
@@ -485,7 +486,8 @@
vendor_events_(vendor_events) {}
FtraceConfigMuxer::~FtraceConfigMuxer() = default;
-FtraceConfigId FtraceConfigMuxer::SetupConfig(const FtraceConfig& request) {
+FtraceConfigId FtraceConfigMuxer::SetupConfig(const FtraceConfig& request,
+ FtraceSetupErrors* errors) {
EventFilter filter;
bool is_ftrace_enabled = ftrace_->IsTracingEnabled();
if (ds_configs_.empty()) {
@@ -530,7 +532,7 @@
"bailing out.");
return 0;
}
- UpdateAtrace(request);
+ UpdateAtrace(request, errors ? &errors->atrace_errors : nullptr);
}
for (const auto& group_and_name : events) {
@@ -538,6 +540,8 @@
if (!event) {
PERFETTO_DLOG("Can't enable %s, event not known",
group_and_name.ToString().c_str());
+ if (errors)
+ errors->unknown_ftrace_events.push_back(group_and_name.ToString());
continue;
}
// Note: ftrace events are always implicitly enabled (and don't have an
@@ -554,6 +558,8 @@
filter.AddEnabledEvent(event->ftrace_event_id);
} else {
PERFETTO_DPLOG("Failed to enable %s.", group_and_name.ToString().c_str());
+ if (errors)
+ errors->failed_ftrace_events.push_back(group_and_name.ToString());
}
}
@@ -662,7 +668,8 @@
// some categories this won't disable them (e.g. categories that just
// enable ftrace events) for those there is nothing we can do till the
// last ftrace config is removed.
- if (StartAtrace(expected_apps, expected_categories)) {
+ if (StartAtrace(expected_apps, expected_categories,
+ /*atrace_errors=*/nullptr)) {
// Update current_state_ to reflect this change.
current_state_.atrace_apps = expected_apps;
current_state_.atrace_categories = expected_categories;
@@ -720,7 +727,8 @@
return current_state_.cpu_buffer_size_pages;
}
-void FtraceConfigMuxer::UpdateAtrace(const FtraceConfig& request) {
+void FtraceConfigMuxer::UpdateAtrace(const FtraceConfig& request,
+ std::string* atrace_errors) {
// We want to avoid poisoning current_state_.atrace_{categories, apps}
// if for some reason these args make atrace unhappy so we stash the
// union into temps and only update current_state_ if we successfully
@@ -738,7 +746,7 @@
return;
}
- if (StartAtrace(combined_apps, combined_categories)) {
+ if (StartAtrace(combined_apps, combined_categories, atrace_errors)) {
current_state_.atrace_categories = combined_categories;
current_state_.atrace_apps = combined_apps;
current_state_.atrace_on = true;
@@ -746,9 +754,9 @@
}
// static
-bool FtraceConfigMuxer::StartAtrace(
- const std::vector<std::string>& apps,
- const std::vector<std::string>& categories) {
+bool FtraceConfigMuxer::StartAtrace(const std::vector<std::string>& apps,
+ const std::vector<std::string>& categories,
+ std::string* atrace_errors) {
PERFETTO_DLOG("Update atrace config...");
std::vector<std::string> args;
@@ -771,7 +779,7 @@
args.push_back(arg);
}
- bool result = RunAtrace(args);
+ bool result = RunAtrace(args, atrace_errors);
PERFETTO_DLOG("...done (%s)", result ? "success" : "fail");
return result;
}
@@ -784,7 +792,7 @@
std::vector<std::string> args{"atrace", "--async_stop"};
if (!IsOldAtrace())
args.push_back("--only_userspace");
- if (RunAtrace(args)) {
+ if (RunAtrace(args, /*atrace_errors=*/nullptr)) {
current_state_.atrace_categories.clear();
current_state_.atrace_apps.clear();
current_state_.atrace_on = false;
diff --git a/src/traced/probes/ftrace/ftrace_config_muxer.h b/src/traced/probes/ftrace/ftrace_config_muxer.h
index bcaf1e5..7b21357 100644
--- a/src/traced/probes/ftrace/ftrace_config_muxer.h
+++ b/src/traced/probes/ftrace/ftrace_config_muxer.h
@@ -34,6 +34,8 @@
} // namespace pbzero
} // namespace protos
+struct FtraceSetupErrors;
+
// State held by the muxer per data source, used to parse ftrace according to
// that data source's config.
struct FtraceDataSourceConfig {
@@ -93,7 +95,8 @@
// (if you enable an atrace category we try to give you the matching events).
// If someone else is tracing we won't touch atrace (since it resets the
// buffer).
- FtraceConfigId SetupConfig(const FtraceConfig& request);
+ FtraceConfigId SetupConfig(const FtraceConfig& request,
+ FtraceSetupErrors* = nullptr);
// Activate ftrace for the given config (if not already active).
bool ActivateConfig(FtraceConfigId);
@@ -131,7 +134,8 @@
private:
static bool StartAtrace(const std::vector<std::string>& apps,
- const std::vector<std::string>& categories);
+ const std::vector<std::string>& categories,
+ std::string* atrace_errors);
struct FtraceState {
EventFilter ftrace_events;
@@ -148,7 +152,7 @@
void SetupClock(const FtraceConfig& request);
void SetupBufferSize(const FtraceConfig& request);
- void UpdateAtrace(const FtraceConfig& request);
+ void UpdateAtrace(const FtraceConfig& request, std::string* atrace_errors);
void DisableAtrace();
// This processes the config to get the exact events.
diff --git a/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc b/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc
index 006ee59..af01bd9 100644
--- a/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc
+++ b/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc
@@ -18,9 +18,11 @@
#include <memory>
+#include "ftrace_config_muxer.h"
#include "src/traced/probes/ftrace/atrace_wrapper.h"
#include "src/traced/probes/ftrace/compact_sched.h"
#include "src/traced/probes/ftrace/ftrace_procfs.h"
+#include "src/traced/probes/ftrace/ftrace_stats.h"
#include "src/traced/probes/ftrace/proto_translation_table.h"
#include "test/gtest_and_gmock.h"
@@ -29,6 +31,7 @@
using testing::Contains;
using testing::ElementsAreArray;
using testing::Eq;
+using testing::Invoke;
using testing::IsEmpty;
using testing::MatchesRegex;
using testing::NiceMock;
@@ -63,14 +66,15 @@
MockRunAtrace() {
static MockRunAtrace* instance;
instance = this;
- SetRunAtraceForTesting([](const std::vector<std::string>& args) {
- return instance->RunAtrace(args);
- });
+ SetRunAtraceForTesting(
+ [](const std::vector<std::string>& args, std::string* atrace_errors) {
+ return instance->RunAtrace(args, atrace_errors);
+ });
}
~MockRunAtrace() { SetRunAtraceForTesting(nullptr); }
- MOCK_METHOD1(RunAtrace, bool(const std::vector<std::string>&));
+ MOCK_METHOD2(RunAtrace, bool(const std::vector<std::string>&, std::string*));
};
class MockProtoTranslationTable : public ProtoTranslationTable {
@@ -474,9 +478,9 @@
EXPECT_CALL(ftrace, ReadOneCharFromFile("/root/tracing_on"))
.WillOnce(Return('0'));
- EXPECT_CALL(atrace,
- RunAtrace(ElementsAreArray(
- {"atrace", "--async_start", "--only_userspace", "sched"})))
+ EXPECT_CALL(atrace, RunAtrace(ElementsAreArray({"atrace", "--async_start",
+ "--only_userspace", "sched"}),
+ _))
.WillOnce(Return(true));
FtraceConfigId id = model.SetupConfig(config);
@@ -496,8 +500,10 @@
EXPECT_THAT(central_filter->GetEnabledEvents(),
Contains(FtraceConfigMuxerTest::kFakeSchedSwitchEventId));
- EXPECT_CALL(atrace, RunAtrace(ElementsAreArray(
- {"atrace", "--async_stop", "--only_userspace"})))
+ EXPECT_CALL(
+ atrace,
+ RunAtrace(
+ ElementsAreArray({"atrace", "--async_stop", "--only_userspace"}), _))
.WillOnce(Return(true));
ASSERT_TRUE(model.RemoveConfig(id));
}
@@ -516,9 +522,11 @@
.WillOnce(Return('0'));
EXPECT_CALL(
atrace,
- RunAtrace(ElementsAreArray(
- {"atrace", "--async_start", "--only_userspace", "-a",
- "com.google.android.gms,com.google.android.gms.persistent"})))
+ RunAtrace(
+ ElementsAreArray(
+ {"atrace", "--async_start", "--only_userspace", "-a",
+ "com.google.android.gms,com.google.android.gms.persistent"}),
+ _))
.WillOnce(Return(true));
FtraceConfigId id = model.SetupConfig(config);
@@ -529,8 +537,10 @@
ASSERT_THAT(ds_config->event_filter.GetEnabledEvents(),
Contains(FtraceConfigMuxerTest::kFakePrintEventId));
- EXPECT_CALL(atrace, RunAtrace(ElementsAreArray(
- {"atrace", "--async_stop", "--only_userspace"})))
+ EXPECT_CALL(
+ atrace,
+ RunAtrace(
+ ElementsAreArray({"atrace", "--async_stop", "--only_userspace"}), _))
.WillOnce(Return(true));
ASSERT_TRUE(model.RemoveConfig(id));
}
@@ -555,7 +565,8 @@
EXPECT_CALL(atrace, RunAtrace(ElementsAreArray({"atrace", "--async_start",
"--only_userspace", "cat_a",
- "-a", "app_a"})))
+ "-a", "app_a"}),
+ _))
.WillOnce(Return(true));
FtraceConfigId id_a = model.SetupConfig(config_a);
ASSERT_TRUE(id_a);
@@ -563,7 +574,8 @@
EXPECT_CALL(
atrace,
RunAtrace(ElementsAreArray({"atrace", "--async_start", "--only_userspace",
- "cat_a", "cat_b", "-a", "app_a,app_b"})))
+ "cat_a", "cat_b", "-a", "app_a,app_b"}),
+ _))
.WillOnce(Return(true));
FtraceConfigId id_b = model.SetupConfig(config_b);
ASSERT_TRUE(id_b);
@@ -571,7 +583,8 @@
EXPECT_CALL(atrace,
RunAtrace(ElementsAreArray({"atrace", "--async_start",
"--only_userspace", "cat_a", "cat_b",
- "cat_c", "-a", "app_a,app_b,app_c"})))
+ "cat_c", "-a", "app_a,app_b,app_c"}),
+ _))
.WillOnce(Return(true));
FtraceConfigId id_c = model.SetupConfig(config_c);
ASSERT_TRUE(id_c);
@@ -579,18 +592,22 @@
EXPECT_CALL(
atrace,
RunAtrace(ElementsAreArray({"atrace", "--async_start", "--only_userspace",
- "cat_a", "cat_c", "-a", "app_a,app_c"})))
+ "cat_a", "cat_c", "-a", "app_a,app_c"}),
+ _))
.WillOnce(Return(true));
ASSERT_TRUE(model.RemoveConfig(id_b));
EXPECT_CALL(atrace, RunAtrace(ElementsAreArray({"atrace", "--async_start",
"--only_userspace", "cat_c",
- "-a", "app_c"})))
+ "-a", "app_c"}),
+ _))
.WillOnce(Return(true));
ASSERT_TRUE(model.RemoveConfig(id_a));
- EXPECT_CALL(atrace, RunAtrace(ElementsAreArray(
- {"atrace", "--async_stop", "--only_userspace"})))
+ EXPECT_CALL(
+ atrace,
+ RunAtrace(
+ ElementsAreArray({"atrace", "--async_stop", "--only_userspace"}), _))
.WillOnce(Return(true));
ASSERT_TRUE(model.RemoveConfig(id_c));
}
@@ -620,15 +637,17 @@
EXPECT_CALL(
atrace,
RunAtrace(ElementsAreArray({"atrace", "--async_start", "--only_userspace",
- "cat_1", "cat_2", "-a", "app_1,app_2"})))
+ "cat_1", "cat_2", "-a", "app_1,app_2"}),
+ _))
.WillOnce(Return(true));
FtraceConfigId id_a = model.SetupConfig(config_a);
ASSERT_TRUE(id_a);
- EXPECT_CALL(atrace,
- RunAtrace(ElementsAreArray(
- {"atrace", "--async_start", "--only_userspace", "cat_1",
- "cat_2", "cat_fail", "-a", "app_1,app_2,app_fail"})))
+ EXPECT_CALL(atrace, RunAtrace(ElementsAreArray({"atrace", "--async_start",
+ "--only_userspace", "cat_1",
+ "cat_2", "cat_fail", "-a",
+ "app_1,app_2,app_fail"}),
+ _))
.WillOnce(Return(false));
FtraceConfigId id_b = model.SetupConfig(config_b);
ASSERT_TRUE(id_b);
@@ -636,7 +655,8 @@
EXPECT_CALL(atrace,
RunAtrace(ElementsAreArray({"atrace", "--async_start",
"--only_userspace", "cat_1", "cat_2",
- "cat_3", "-a", "app_1,app_2,app_3"})))
+ "cat_3", "-a", "app_1,app_2,app_3"}),
+ _))
.WillOnce(Return(true));
FtraceConfigId id_c = model.SetupConfig(config_c);
ASSERT_TRUE(id_c);
@@ -644,7 +664,8 @@
EXPECT_CALL(
atrace,
RunAtrace(ElementsAreArray({"atrace", "--async_start", "--only_userspace",
- "cat_1", "cat_2", "-a", "app_1,app_2"})))
+ "cat_1", "cat_2", "-a", "app_1,app_2"}),
+ _))
.WillOnce(Return(true));
ASSERT_TRUE(model.RemoveConfig(id_c));
@@ -652,8 +673,10 @@
// so we don't expect a call here.
ASSERT_TRUE(model.RemoveConfig(id_b));
- EXPECT_CALL(atrace, RunAtrace(ElementsAreArray(
- {"atrace", "--async_stop", "--only_userspace"})))
+ EXPECT_CALL(
+ atrace,
+ RunAtrace(
+ ElementsAreArray({"atrace", "--async_stop", "--only_userspace"}), _))
.WillOnce(Return(true));
ASSERT_TRUE(model.RemoveConfig(id_a));
}
@@ -674,7 +697,8 @@
EXPECT_CALL(atrace, RunAtrace(ElementsAreArray({"atrace", "--async_start",
"--only_userspace", "cat_1",
- "-a", "app_1"})))
+ "-a", "app_1"}),
+ _))
.WillOnce(Return(true));
FtraceConfigId id_a = model.SetupConfig(config_a);
ASSERT_TRUE(id_a);
@@ -684,8 +708,10 @@
ASSERT_TRUE(model.RemoveConfig(id_a));
- EXPECT_CALL(atrace, RunAtrace(ElementsAreArray(
- {"atrace", "--async_stop", "--only_userspace"})))
+ EXPECT_CALL(
+ atrace,
+ RunAtrace(
+ ElementsAreArray({"atrace", "--async_stop", "--only_userspace"}), _))
.WillOnce(Return(true));
ASSERT_TRUE(model.RemoveConfig(id_b));
}
@@ -710,7 +736,8 @@
ASSERT_TRUE(id_a);
EXPECT_CALL(atrace, RunAtrace(ElementsAreArray({"atrace", "--async_start",
- "--only_userspace", "b"})))
+ "--only_userspace", "b"}),
+ _))
.WillOnce(Return(true));
FtraceConfigId id_b = model.SetupConfig(config_b);
ASSERT_TRUE(id_b);
@@ -719,27 +746,61 @@
ASSERT_TRUE(id_c);
EXPECT_CALL(atrace,
- RunAtrace(ElementsAreArray(
- {"atrace", "--async_start", "--only_userspace", "b", "d"})))
+ RunAtrace(ElementsAreArray({"atrace", "--async_start",
+ "--only_userspace", "b", "d"}),
+ _))
.WillOnce(Return(true));
FtraceConfigId id_d = model.SetupConfig(config_d);
ASSERT_TRUE(id_d);
EXPECT_CALL(atrace, RunAtrace(ElementsAreArray({"atrace", "--async_start",
- "--only_userspace", "b"})))
+ "--only_userspace", "b"}),
+ _))
.WillOnce(Return(true));
ASSERT_TRUE(model.RemoveConfig(id_d));
ASSERT_TRUE(model.RemoveConfig(id_c));
- EXPECT_CALL(atrace, RunAtrace(ElementsAreArray(
- {"atrace", "--async_stop", "--only_userspace"})))
+ EXPECT_CALL(
+ atrace,
+ RunAtrace(
+ ElementsAreArray({"atrace", "--async_stop", "--only_userspace"}), _))
.WillOnce(Return(true));
ASSERT_TRUE(model.RemoveConfig(id_b));
ASSERT_TRUE(model.RemoveConfig(id_a));
}
+TEST_F(FtraceConfigMuxerTest, AtraceErrorsPropagated) {
+ NiceMock<MockFtraceProcfs> ftrace;
+ MockRunAtrace atrace;
+
+ FtraceConfig config = CreateFtraceConfig({});
+ *config.add_atrace_categories() = "cat_1";
+ *config.add_atrace_categories() = "cat_2";
+
+ EXPECT_CALL(ftrace, ReadOneCharFromFile("/root/tracing_on"))
+ .WillRepeatedly(Return('0'));
+
+ FtraceConfigMuxer model(&ftrace, table_.get(), {});
+
+ EXPECT_CALL(atrace, RunAtrace(ElementsAreArray({"atrace", "--async_start",
+ "--only_userspace", "cat_1",
+ "cat_2"}),
+ _))
+ .WillOnce(Invoke([](const std::vector<std::string>&, std::string* err) {
+ EXPECT_NE(err, nullptr);
+ if (err)
+ err->append("foo\nbar\n");
+ return true;
+ }));
+
+ FtraceSetupErrors errors{};
+ FtraceConfigId id_a = model.SetupConfig(config, &errors);
+ ASSERT_TRUE(id_a);
+ EXPECT_EQ(errors.atrace_errors, "foo\nbar\n");
+}
+
TEST_F(FtraceConfigMuxerTest, SetupClockForTesting) {
MockFtraceProcfs ftrace;
FtraceConfig config;
diff --git a/src/traced/probes/ftrace/ftrace_controller.cc b/src/traced/probes/ftrace/ftrace_controller.cc
index 0d8cb73..fc25d3b 100644
--- a/src/traced/probes/ftrace/ftrace_controller.cc
+++ b/src/traced/probes/ftrace/ftrace_controller.cc
@@ -393,7 +393,8 @@
if (!ValidConfig(data_source->config()))
return false;
- auto config_id = ftrace_config_muxer_->SetupConfig(data_source->config());
+ auto config_id = ftrace_config_muxer_->SetupConfig(
+ data_source->config(), data_source->mutable_setup_errors());
if (!config_id)
return false;
diff --git a/src/traced/probes/ftrace/ftrace_data_source.cc b/src/traced/probes/ftrace/ftrace_data_source.cc
index 5b6ac34..e5ba144 100644
--- a/src/traced/probes/ftrace/ftrace_data_source.cc
+++ b/src/traced/probes/ftrace/ftrace_data_source.cc
@@ -111,11 +111,13 @@
if (!ftrace->StartDataSource(this))
return;
DumpFtraceStats(&stats_before_);
+ setup_errors_ = FtraceSetupErrors(); // Dump only on START_OF_TRACE.
}
void FtraceDataSource::DumpFtraceStats(FtraceStats* stats) {
if (controller_weak_)
controller_weak_->DumpFtraceStats(stats);
+ stats->setup_errors = std::move(setup_errors_);
}
void FtraceDataSource::Flush(FlushRequestID flush_request_id,
diff --git a/src/traced/probes/ftrace/ftrace_data_source.h b/src/traced/probes/ftrace/ftrace_data_source.h
index cadcd5f..b1a1c45 100644
--- a/src/traced/probes/ftrace/ftrace_data_source.h
+++ b/src/traced/probes/ftrace/ftrace_data_source.h
@@ -81,6 +81,7 @@
}
FtraceMetadata* mutable_metadata() { return &metadata_; }
+ FtraceSetupErrors* mutable_setup_errors() { return &setup_errors_; }
TraceWriter* trace_writer() { return writer_.get(); }
private:
@@ -95,7 +96,8 @@
const FtraceConfig config_;
FtraceMetadata metadata_;
- FtraceStats stats_before_ = {};
+ FtraceStats stats_before_{};
+ FtraceSetupErrors setup_errors_{};
std::map<FlushRequestID, std::function<void()>> pending_flushes_;
// -- Fields initialized by the Initialize() call:
diff --git a/src/traced/probes/ftrace/ftrace_stats.cc b/src/traced/probes/ftrace/ftrace_stats.cc
index a424cc9..bba09ac 100644
--- a/src/traced/probes/ftrace/ftrace_stats.cc
+++ b/src/traced/probes/ftrace/ftrace_stats.cc
@@ -26,6 +26,12 @@
}
writer->set_kernel_symbols_parsed(kernel_symbols_parsed);
writer->set_kernel_symbols_mem_kb(kernel_symbols_mem_kb);
+ if (!setup_errors.atrace_errors.empty())
+ writer->set_atrace_errors(setup_errors.atrace_errors);
+ for (const std::string& err : setup_errors.unknown_ftrace_events)
+ writer->add_unknown_ftrace_events(err);
+ for (const std::string& err : setup_errors.failed_ftrace_events)
+ writer->add_failed_ftrace_events(err);
}
void FtraceCpuStats::Write(protos::pbzero::FtraceCpuStats* writer) const {
diff --git a/src/traced/probes/ftrace/ftrace_stats.h b/src/traced/probes/ftrace/ftrace_stats.h
index ce68fe8..c7a16d8 100644
--- a/src/traced/probes/ftrace/ftrace_stats.h
+++ b/src/traced/probes/ftrace/ftrace_stats.h
@@ -18,6 +18,7 @@
#define SRC_TRACED_PROBES_FTRACE_FTRACE_STATS_H_
#include <cinttypes>
+#include <string>
#include <vector>
namespace perfetto {
@@ -43,8 +44,15 @@
void Write(protos::pbzero::FtraceCpuStats*) const;
};
+struct FtraceSetupErrors {
+ std::string atrace_errors;
+ std::vector<std::string> unknown_ftrace_events;
+ std::vector<std::string> failed_ftrace_events;
+};
+
struct FtraceStats {
std::vector<FtraceCpuStats> cpu_stats;
+ FtraceSetupErrors setup_errors;
uint32_t kernel_symbols_parsed = 0;
uint32_t kernel_symbols_mem_kb = 0;
diff --git a/test/ftrace_integrationtest.cc b/test/ftrace_integrationtest.cc
index 2553d40..42d251f 100644
--- a/test/ftrace_integrationtest.cc
+++ b/test/ftrace_integrationtest.cc
@@ -65,6 +65,7 @@
using ::testing::HasSubstr;
using ::testing::Property;
using ::testing::SizeIs;
+using ::testing::UnorderedElementsAreArray;
class PerfettoFtraceIntegrationTest : public ::testing::Test {
public:
@@ -277,5 +278,66 @@
ASSERT_GT(symbols_parsed, 100);
}
+TEST_F(PerfettoFtraceIntegrationTest, ReportFtraceFailuresInStats) {
+ base::TestTaskRunner task_runner;
+
+ TestHelper helper(&task_runner);
+ helper.StartServiceIfRequired();
+
+#if PERFETTO_BUILDFLAG(PERFETTO_START_DAEMONS)
+ ProbesProducerThread probes(GetTestProducerSockName());
+ probes.Connect();
+#endif
+
+ helper.ConnectConsumer();
+ helper.WaitForConsumerConnect();
+
+ // Wait for the traced_probes service to connect. We want to start tracing
+ // only after it connects, otherwise we'll start a tracing session with 0
+ // producers connected (which is valid but not what we want here).
+ helper.WaitForDataSourceConnected("linux.ftrace");
+
+ TraceConfig trace_config;
+ trace_config.add_buffers()->set_size_kb(32);
+ trace_config.set_duration_ms(1);
+
+ auto* ds_config = trace_config.add_data_sources()->mutable_config();
+ ds_config->set_name("linux.ftrace");
+
+ protos::gen::FtraceConfig ftrace_config;
+ ftrace_config.add_ftrace_events("sched/sched_process_fork"); // Good.
+ ftrace_config.add_ftrace_events("sched/does_not_exist"); // Bad.
+ ftrace_config.add_ftrace_events("foobar/i_just_made_this_up"); // Bad.
+ ftrace_config.add_atrace_categories("madeup_atrace_cat"); // Bad.
+ ds_config->set_ftrace_config_raw(ftrace_config.SerializeAsString());
+
+ helper.StartTracing(trace_config);
+ helper.WaitForTracingDisabled(kDefaultTestTimeoutMs);
+
+ helper.ReadData();
+ helper.WaitForReadData();
+ const auto& packets = helper.trace();
+ ASSERT_GT(packets.size(), 0u);
+
+ base::Optional<protos::gen::FtraceStats> stats;
+ for (const auto& packet : packets) {
+ if (!packet.has_ftrace_stats() ||
+ packet.ftrace_stats().phase() !=
+ protos::gen::FtraceStats::START_OF_TRACE) {
+ continue;
+ }
+ stats = packet.ftrace_stats();
+ }
+ ASSERT_TRUE(stats.has_value());
+ EXPECT_THAT(stats->unknown_ftrace_events(),
+ UnorderedElementsAreArray(
+ {"sched/does_not_exist", "foobar/i_just_made_this_up"}));
+
+ // Atrace is not available on Linux and on the O-based emulator on the CI.
+ if (base::FileExists("/system/bin/atrace")) {
+ EXPECT_THAT(stats->atrace_errors(), HasSubstr("madeup_atrace_cat"));
+ }
+}
+
} // namespace perfetto
#endif // OS_ANDROID || OS_LINUX