diff --git a/bootstat/Android.mk b/bootstat/Android.mk index bbb903dd1..c6349c1d6 100644 --- a/bootstat/Android.mk +++ b/bootstat/Android.mk @@ -32,6 +32,7 @@ bootstat_test_src_files := \ bootstat_shared_libs := \ libbase \ + libcutils \ liblog bootstat_cflags := \ diff --git a/bootstat/README.md b/bootstat/README.md index 1b4bf7f74..b3964ce1c 100644 --- a/bootstat/README.md +++ b/bootstat/README.md @@ -7,10 +7,11 @@ analysis. Usage: bootstat [options] options include: - -d Dump the boot event records to the console. - -h Show this help. - -l Log all metrics to logstorage. - -r Record the relative time of a named boot event. + -h, --help Show this help + -l, --log Log all metrics to logstorage + -p, --print Dump the boot event records to the console + -r, --record Record the timestamp of a named boot event + --record_boot_reason Record the reason why the device booted ## Relative time ## diff --git a/bootstat/boot_event_record_store.cpp b/bootstat/boot_event_record_store.cpp index 1bdcf2bac..22a67bb3a 100644 --- a/bootstat/boot_event_record_store.cpp +++ b/bootstat/boot_event_record_store.cpp @@ -56,19 +56,31 @@ void BootEventRecordStore::AddBootEvent(const std::string& name) { LOG(ERROR) << "Failed to read /proc/uptime"; } + // Cast intentionally rounds down. + int32_t uptime = static_cast(strtod(uptime_str.c_str(), NULL)); + AddBootEventWithValue(name, uptime); +} + +// The implementation of AddBootEventValue makes use of the mtime file +// attribute to store the value associated with a boot event in order to +// optimize on-disk size requirements and small-file thrashing. +void BootEventRecordStore::AddBootEventWithValue( + const std::string& name, int32_t value) { std::string record_path = GetBootEventPath(name); if (creat(record_path.c_str(), S_IRUSR | S_IWUSR) == -1) { PLOG(ERROR) << "Failed to create " << record_path; } + // Fill out the stat structure for |record_path| in order to get the atime to + // set in the utime() call. struct stat file_stat; if (stat(record_path.c_str(), &file_stat) == -1) { PLOG(ERROR) << "Failed to read " << record_path; } - // Cast intentionally rounds down. - time_t uptime = static_cast(strtod(uptime_str.c_str(), NULL)); - struct utimbuf times = {file_stat.st_atime, uptime}; + // Set the |modtime| of the file to store the value of the boot event while + // preserving the |actime| (as read by stat). + struct utimbuf times = {/* actime */ file_stat.st_atime, /* modtime */ value}; if (utime(record_path.c_str(), ×) == -1) { PLOG(ERROR) << "Failed to set mtime for " << record_path; } diff --git a/bootstat/boot_event_record_store.h b/bootstat/boot_event_record_store.h index 77978eff7..d1b7835ab 100644 --- a/bootstat/boot_event_record_store.h +++ b/bootstat/boot_event_record_store.h @@ -37,6 +37,10 @@ class BootEventRecordStore { // Persists the boot event named |name| in the record store. void AddBootEvent(const std::string& name); + // Persists the boot event named |name| with the associated |value| in the + // record store. + void AddBootEventWithValue(const std::string& name, int32_t value); + // Returns a list of all of the boot events persisted in the record store. std::vector GetAllBootEvents() const; @@ -45,6 +49,7 @@ class BootEventRecordStore { // more test-friendly path. FRIEND_TEST(BootEventRecordStoreTest, AddSingleBootEvent); FRIEND_TEST(BootEventRecordStoreTest, AddMultipleBootEvents); + FRIEND_TEST(BootEventRecordStoreTest, AddBootEventWithValue); // Sets the filesystem path of the record store. void SetStorePath(const std::string& path); diff --git a/bootstat/boot_event_record_store_test.cpp b/bootstat/boot_event_record_store_test.cpp index 384f84dc1..3e6d70674 100644 --- a/bootstat/boot_event_record_store_test.cpp +++ b/bootstat/boot_event_record_store_test.cpp @@ -154,3 +154,15 @@ TEST_F(BootEventRecordStoreTest, AddMultipleBootEvents) { EXPECT_TRUE(FuzzUptimeEquals(uptime, *i)); } } + +TEST_F(BootEventRecordStoreTest, AddBootEventWithValue) { + BootEventRecordStore store; + store.SetStorePath(GetStorePathForTesting()); + + store.AddBootEventWithValue("permian", 42); + + auto events = store.GetAllBootEvents(); + ASSERT_EQ(1U, events.size()); + EXPECT_EQ("permian", events[0].first); + EXPECT_EQ(42, events[0].second); +} \ No newline at end of file diff --git a/bootstat/bootstat.cpp b/bootstat/bootstat.cpp index a83f8ad0d..31c84c753 100644 --- a/bootstat/bootstat.cpp +++ b/bootstat/bootstat.cpp @@ -18,14 +18,15 @@ // timestamp, dump the persisted events, and log all events to EventLog to be // uploaded to Android log storage via Tron. -//#define LOG_TAG "bootstat" - +#include #include #include #include +#include #include #include #include +#include #include #include "boot_event_record_store.h" #include "event_log_list_builder.h" @@ -35,7 +36,7 @@ namespace { // Builds an EventLog buffer named |event| containing |data| and writes // the log into the Tron histogram logs. void LogBootEvent(const std::string& event, int32_t data) { - LOG(INFO) << "Logging boot time: " << event << " " << data; + LOG(INFO) << "Logging boot metric: " << event << " " << data; EventLogListBuilder log_builder; log_builder.Append(event); @@ -74,10 +75,11 @@ void ShowHelp(const char *cmd) { fprintf(stderr, "Usage: %s [options]\n", cmd); fprintf(stderr, "options include:\n" - " -d Dump the boot event records to the console.\n" - " -h Show this help.\n" - " -l Log all metrics to logstorage.\n" - " -r Record the timestamp of a named boot event.\n"); + " -h, --help Show this help\n" + " -l, --log Log all metrics to logstorage\n" + " -p, --print Dump the boot event records to the console\n" + " -r, --record Record the timestamp of a named boot event\n" + " --record_boot_reason Record the reason why the device booted\n"); } // Constructs a readable, printable string from the givencommand line @@ -92,6 +94,61 @@ std::string GetCommandLine(int argc, char **argv) { return cmd; } +// Convenience wrapper over the property API that returns an +// std::string. +std::string GetProperty(const char* key) { + std::vector temp(PROPERTY_VALUE_MAX); + const int len = property_get(key, &temp[0], nullptr); + if (len < 0) { + return ""; + } + return std::string(&temp[0], len); +} + +// A mapping from boot reason string, as read from the ro.boot.bootreason +// system property, to a unique integer ID. Viewers of log data dashboards for +// the boot_reason metric may refer to this mapping to discern the histogram +// values. +const std::map kBootReasonMap = { + {"normal", 0}, + {"recovery", 1}, + {"reboot", 2}, + {"PowerKey", 3}, + {"hard_reset", 4}, + {"kernel_panic", 5}, + {"rpm_err", 6}, + {"hw_reset", 7}, + {"tz_err", 8}, + {"adsp_err", 9}, + {"modem_err", 10}, + {"mba_err", 11}, + {"Watchdog", 12}, + {"Panic", 13}, +}; + +// Converts a string value representing the reason the system booted to an +// integer representation. This is necessary for logging the boot_reason metric +// via Tron, which does not accept non-integer buckets in histograms. +int32_t BootReasonStrToEnum(const std::string& boot_reason) { + static const int32_t kUnknownBootReason = -1; + + auto mapping = kBootReasonMap.find(boot_reason); + if (mapping != kBootReasonMap.end()) { + return mapping->second; + } + + LOG(INFO) << "Unknown boot reason: " << boot_reason; + return kUnknownBootReason; +} + +// Records the boot_reason metric by querying the ro.boot.bootreason system +// property. +void RecordBootReason() { + int32_t boot_reason = BootReasonStrToEnum(GetProperty("ro.boot.bootreason")); + BootEventRecordStore boot_event_store; + boot_event_store.AddBootEventWithValue("boot_reason", boot_reason); +} + } // namespace int main(int argc, char **argv) { @@ -100,9 +157,31 @@ int main(int argc, char **argv) { const std::string cmd_line = GetCommandLine(argc, argv); LOG(INFO) << "Service started: " << cmd_line; + int option_index = 0; + static const char boot_reason_str[] = "record_boot_reason"; + static const struct option long_options[] = { + { "help", no_argument, NULL, 'h' }, + { "log", no_argument, NULL, 'l' }, + { "print", no_argument, NULL, 'p' }, + { "record", required_argument, NULL, 'r' }, + { boot_reason_str, no_argument, NULL, 0 }, + { NULL, 0, NULL, 0 } + }; + int opt = 0; - while ((opt = getopt(argc, argv, "hlpr:")) != -1) { + while ((opt = getopt_long(argc, argv, "hlpr:", long_options, &option_index)) != -1) { switch (opt) { + // This case handles long options which have no single-character mapping. + case 0: { + const std::string option_name = long_options[option_index].name; + if (option_name == boot_reason_str) { + RecordBootReason(); + } else { + LOG(ERROR) << "Invalid option: " << option_name; + } + break; + } + case 'h': { ShowHelp(argv[0]); break; diff --git a/bootstat/bootstat.rc b/bootstat/bootstat.rc index 2c37dd260..218b9f8bb 100644 --- a/bootstat/bootstat.rc +++ b/bootstat/bootstat.rc @@ -10,5 +10,8 @@ on property:init.svc.bootanim=stopped # Record boot_complete timing event. exec - root root -- /system/bin/bootstat -r boot_complete + # Record the boot reason. + exec - root root -- /system/bin/bootstat --record_boot_reason + # Log all boot events. exec - root root -- /system/bin/bootstat -l