storaged: store io_history as protobuf file on userdata

Convert storaged internal io_history to protobuf format and serialize
it to userdata partition. Also load this file during storaged startup
to reconstruct io history.

Bug: 63740245
Change-Id: I0697525df1c31fdec20f5ed4e3e9363e2dde244f
diff --git a/storaged/Android.bp b/storaged/Android.bp
index f8b0333..25b433c 100644
--- a/storaged/Android.bp
+++ b/storaged/Android.bp
@@ -23,8 +23,10 @@
         "libbinder",
         "libcutils",
         "liblog",
+        "libprotobuf-cpp-lite",
         "libsysutils",
         "libutils",
+        "libz",
     ],
 
     cflags: [
@@ -46,10 +48,16 @@
         "storaged_service.cpp",
         "storaged_utils.cpp",
         "storaged_uid_monitor.cpp",
+        "storaged.proto",
     ],
 
     logtags: ["EventLogTags.logtags"],
 
+    proto: {
+        type: "lite",
+        export_proto_headers: true,
+    },
+
     export_include_dirs: ["include"],
 }
 
diff --git a/storaged/include/storaged_uid_monitor.h b/storaged/include/storaged_uid_monitor.h
index 9bb428e..86d3c28 100644
--- a/storaged/include/storaged_uid_monitor.h
+++ b/storaged/include/storaged_uid_monitor.h
@@ -92,13 +92,15 @@
     // current io usage for next report, app name -> uid_io_usage
     std::unordered_map<std::string, struct uid_io_usage> curr_io_stats;
     // io usage records, end timestamp -> {start timestamp, vector of records}
-    std::map<uint64_t, struct uid_records> records;
+    std::map<uint64_t, struct uid_records> io_history;
     // charger ON/OFF
     charger_stat_t charger_stat;
     // protects curr_io_stats, last_uid_io_stats, records and charger_stat
     sem_t um_lock;
     // start time for IO records
     uint64_t start_ts;
+    // protobuf file for io_history
+    static const std::string io_history_proto_file;
 
     // reads from /proc/uid_io/stats
     std::unordered_map<uint32_t, struct uid_info> get_uid_io_stats_locked();
@@ -106,6 +108,10 @@
     void add_records_locked(uint64_t curr_ts);
     // updates curr_io_stats and set last_uid_io_stats
     void update_curr_io_stats_locked();
+    // restores io_history from protobuf file
+    void load_io_history_from_proto();
+    // converts io_history to protobuf and writes to a file
+    void flush_io_history_to_proto();
 
 public:
     uid_monitor();
diff --git a/storaged/storaged.cpp b/storaged/storaged.cpp
index 06afea6..49592eb 100644
--- a/storaged/storaged.cpp
+++ b/storaged/storaged.cpp
@@ -243,7 +243,7 @@
 
     if (mConfig.proc_uid_io_available && mTimer &&
             (mTimer % mConfig.periodic_chores_interval_uid_io) == 0) {
-         mUidm.report();
+        mUidm.report();
     }
 
     mTimer += mConfig.periodic_chores_interval_unit;
diff --git a/storaged/storaged.proto b/storaged/storaged.proto
new file mode 100644
index 0000000..5222846
--- /dev/null
+++ b/storaged/storaged.proto
@@ -0,0 +1,42 @@
+syntax = "proto2";
+option optimize_for = LITE_RUNTIME;
+package storaged_proto;
+option java_package = "com.android.storaged.proto";
+option java_outer_classname = "Storaged";
+
+message IOUsage {
+  optional uint64 rd_fg_chg_on  = 1;
+  optional uint64 rd_fg_chg_off = 2;
+  optional uint64 rd_bg_chg_on  = 3;
+  optional uint64 rd_bg_chg_off = 4;
+  optional uint64 wr_fg_chg_on  = 5;
+  optional uint64 wr_fg_chg_off = 6;
+  optional uint64 wr_bg_chg_on  = 7;
+  optional uint64 wr_bg_chg_off = 8;
+}
+
+message TaskIOUsage {
+  optional string task_name = 1;
+  optional IOUsage ios = 2;
+}
+
+message UidRecord {
+  optional string uid_name = 1;
+  optional IOUsage uid_io = 2;
+  repeated TaskIOUsage task_io = 3;
+}
+
+message UidIORecords {
+  optional uint64 start_ts = 1;
+  repeated UidRecord entries = 2;
+}
+
+message UidIOItem {
+  optional uint64 end_ts = 1;
+  optional UidIORecords records = 2;
+}
+
+message UidIOHistoryProto {
+  optional uint32 crc = 1;
+  repeated UidIOItem items = 2;
+}
diff --git a/storaged/storaged.rc b/storaged/storaged.rc
index a24c7fb..bd4022b 100644
--- a/storaged/storaged.rc
+++ b/storaged/storaged.rc
@@ -1,7 +1,11 @@
+on post-fs-data
+    mkdir /data/misc/storaged 0700 root root
+    restorecon /data/misc/storaged
+
 service storaged /system/bin/storaged
     class main
     priority 10
     file /d/mmc0/mmc0:0001/ext_csd r
     writepid /dev/cpuset/system-background/tasks
     user root
-    group package_info
\ No newline at end of file
+    group package_info
diff --git a/storaged/storaged_uid_monitor.cpp b/storaged/storaged_uid_monitor.cpp
index 2bbbfe0..1c98477 100644
--- a/storaged/storaged_uid_monitor.cpp
+++ b/storaged/storaged_uid_monitor.cpp
@@ -17,8 +17,11 @@
 #define LOG_TAG "storaged"
 
 #include <stdint.h>
+#include <stdio.h>
 #include <time.h>
+#include <zlib.h>
 
+#include <fstream>
 #include <string>
 #include <unordered_map>
 
@@ -34,12 +37,19 @@
 
 #include "storaged.h"
 #include "storaged_uid_monitor.h"
+#include "system/core/storaged/storaged.pb.h"
 
 using namespace android;
 using namespace android::base;
 using namespace android::content::pm;
+using namespace google::protobuf::io;
+using namespace storaged_proto;
 
 static bool refresh_uid_names;
+static const uint32_t crc_init = 0x5108A4ED; /* STORAGED */
+
+const std::string uid_monitor::io_history_proto_file =
+    "/data/misc/storaged/io_history.proto";
 
 std::unordered_map<uint32_t, struct uid_info> uid_monitor::get_uid_io_stats()
 {
@@ -187,11 +197,11 @@
 
 static const int MAX_UID_RECORDS_SIZE = 1000 * 48; // 1000 uids in 48 hours
 
-static inline int records_size(
-    const std::map<uint64_t, struct uid_records>& curr_records)
+static inline size_t history_size(
+    const std::map<uint64_t, struct uid_records>& history)
 {
-    int count = 0;
-    for (auto const& it : curr_records) {
+    size_t count = 0;
+    for (auto const& it : history) {
         count += it.second.entries.size();
     }
     return count;
@@ -201,8 +211,8 @@
 {
     // remove records more than 5 days old
     if (curr_ts > 5 * DAY_TO_SEC) {
-        auto it = records.lower_bound(curr_ts - 5 * DAY_TO_SEC);
-        records.erase(records.begin(), it);
+        auto it = io_history.lower_bound(curr_ts - 5 * DAY_TO_SEC);
+        io_history.erase(io_history.begin(), it);
     }
 
     struct uid_records new_records;
@@ -227,15 +237,15 @@
       return;
 
     // make some room for new records
-    int overflow = records_size(records) +
+    ssize_t overflow = history_size(io_history) +
         new_records.entries.size() - MAX_UID_RECORDS_SIZE;
-    while (overflow > 0 && records.size() > 0) {
-        auto del_it = records.begin();
+    while (overflow > 0 && io_history.size() > 0) {
+        auto del_it = io_history.begin();
         overflow -= del_it->second.entries.size();
-        records.erase(records.begin());
+        io_history.erase(io_history.begin());
     }
 
-    records[curr_ts] = new_records;
+    io_history[curr_ts] = new_records;
 }
 
 std::map<uint64_t, struct uid_records> uid_monitor::dump(
@@ -254,7 +264,7 @@
         first_ts = time(NULL) - hours * HOUR_TO_SEC;
     }
 
-    for (auto it = records.lower_bound(first_ts); it != records.end(); ++it) {
+    for (auto it = io_history.lower_bound(first_ts); it != io_history.end(); ++it) {
         const std::vector<struct uid_record>& recs = it->second.entries;
         struct uid_records filtered;
 
@@ -351,6 +361,128 @@
 
     update_curr_io_stats_locked();
     add_records_locked(time(NULL));
+
+    flush_io_history_to_proto();
+}
+
+static void set_io_usage_proto(IOUsage* usage_proto, const struct io_usage& usage)
+{
+    usage_proto->set_rd_fg_chg_on(usage.bytes[READ][FOREGROUND][CHARGER_ON]);
+    usage_proto->set_rd_fg_chg_off(usage.bytes[READ][FOREGROUND][CHARGER_OFF]);
+    usage_proto->set_rd_bg_chg_on(usage.bytes[READ][BACKGROUND][CHARGER_ON]);
+    usage_proto->set_rd_bg_chg_off(usage.bytes[READ][BACKGROUND][CHARGER_OFF]);
+    usage_proto->set_wr_fg_chg_on(usage.bytes[WRITE][FOREGROUND][CHARGER_ON]);
+    usage_proto->set_wr_fg_chg_off(usage.bytes[WRITE][FOREGROUND][CHARGER_OFF]);
+    usage_proto->set_wr_bg_chg_on(usage.bytes[WRITE][BACKGROUND][CHARGER_ON]);
+    usage_proto->set_wr_bg_chg_off(usage.bytes[WRITE][BACKGROUND][CHARGER_OFF]);
+}
+
+static void get_io_usage_proto(struct io_usage* usage, const IOUsage& io_proto)
+{
+    usage->bytes[READ][FOREGROUND][CHARGER_ON] = io_proto.rd_fg_chg_on();
+    usage->bytes[READ][FOREGROUND][CHARGER_OFF] = io_proto.rd_fg_chg_off();
+    usage->bytes[READ][BACKGROUND][CHARGER_ON] = io_proto.rd_bg_chg_on();
+    usage->bytes[READ][BACKGROUND][CHARGER_OFF] = io_proto.rd_bg_chg_off();
+    usage->bytes[WRITE][FOREGROUND][CHARGER_ON] = io_proto.wr_fg_chg_on();
+    usage->bytes[WRITE][FOREGROUND][CHARGER_OFF] = io_proto.wr_fg_chg_off();
+    usage->bytes[WRITE][BACKGROUND][CHARGER_ON] = io_proto.wr_bg_chg_on();
+    usage->bytes[WRITE][BACKGROUND][CHARGER_OFF] = io_proto.wr_bg_chg_off();
+}
+
+void uid_monitor::flush_io_history_to_proto()
+{
+    UidIOHistoryProto out_proto;
+
+    for (const auto& item : io_history) {
+        const uint64_t& end_ts = item.first;
+        const struct uid_records& recs = item.second;
+
+        UidIOItem* item_proto = out_proto.add_items();
+        item_proto->set_end_ts(end_ts);
+
+        UidIORecords* recs_proto = item_proto->mutable_records();
+        recs_proto->set_start_ts(recs.start_ts);
+
+        for (const auto& entry : recs.entries) {
+            UidRecord* rec_proto = recs_proto->add_entries();
+            rec_proto->set_uid_name(entry.name);
+
+            IOUsage* uid_io_proto = rec_proto->mutable_uid_io();
+            const struct io_usage& uio_ios = entry.ios.uid_ios;
+            set_io_usage_proto(uid_io_proto, uio_ios);
+
+            for (const auto& task_io : entry.ios.task_ios) {
+                const std::string& task_name = task_io.first;
+                const struct io_usage& task_ios = task_io.second;
+
+                TaskIOUsage* task_io_proto = rec_proto->add_task_io();
+                task_io_proto->set_task_name(task_name);
+                set_io_usage_proto(task_io_proto->mutable_ios(), task_ios);
+            }
+        }
+    }
+
+    out_proto.set_crc(crc_init);
+    std::string out_proto_str = out_proto.SerializeAsString();
+    out_proto.set_crc(crc32(crc_init,
+        reinterpret_cast<const Bytef*>(out_proto_str.c_str()),
+        out_proto_str.size()));
+
+    std::string tmp_file = io_history_proto_file + "_tmp";
+    std::ofstream out(tmp_file,
+        std::ofstream::out | std::ofstream::binary | std::ofstream::trunc);
+    out << out_proto.SerializeAsString();
+    out.close();
+
+    /* Atomically replace existing proto file to reduce chance of data loss. */
+    rename(tmp_file.c_str(), io_history_proto_file.c_str());
+}
+
+void uid_monitor::load_io_history_from_proto()
+{
+    std::ifstream in(io_history_proto_file,
+        std::ofstream::in | std::ofstream::binary);
+
+    if (!in.good()) {
+        PLOG_TO(SYSTEM, INFO) << "Open " << io_history_proto_file << " failed";
+        return;
+    }
+
+    stringstream ss;
+    ss << in.rdbuf();
+    UidIOHistoryProto in_proto;
+    in_proto.ParseFromString(ss.str());
+
+    uint32_t crc = in_proto.crc();
+    in_proto.set_crc(crc_init);
+    std::string io_proto_str = in_proto.SerializeAsString();
+    uint32_t computed_crc = crc32(crc_init,
+        reinterpret_cast<const Bytef*>(io_proto_str.c_str()),
+        io_proto_str.size());
+
+    if (crc != computed_crc) {
+        LOG_TO(SYSTEM, WARNING) << "CRC mismatch in " << io_history_proto_file;
+        return;
+    }
+
+    for (const auto& item_proto : in_proto.items()) {
+        const UidIORecords& records_proto = item_proto.records();
+        struct uid_records* recs = &io_history[item_proto.end_ts()];
+
+        recs->start_ts = records_proto.start_ts();
+        for (const auto& rec_proto : records_proto.entries()) {
+            struct uid_record record;
+            record.name = rec_proto.uid_name();
+            get_io_usage_proto(&record.ios.uid_ios, rec_proto.uid_io());
+
+            for (const auto& task_io_proto : rec_proto.task_io()) {
+                get_io_usage_proto(
+                    &record.ios.task_ios[task_io_proto.task_name()],
+                    task_io_proto.ios());
+            }
+            recs->entries.push_back(record);
+        }
+    }
 }
 
 void uid_monitor::set_charger_state(charger_stat_t stat)
@@ -368,6 +500,9 @@
 void uid_monitor::init(charger_stat_t stat)
 {
     charger_stat = stat;
+
+    load_io_history_from_proto();
+
     start_ts = time(NULL);
     last_uid_io_stats = get_uid_io_stats();
 }