storaged: monitor per-uid IO usage

Add uid_monitor class to query /proc/uid_io/stats periodically.
Add a log tag to record any UID that exceeds IO threshold.

Test: adb shell storaged -u
Bug: 34198239
Change-Id: I53568c30dbefe2f4bdb18054d3dedb30b4133d8b
diff --git a/storaged/Android.mk b/storaged/Android.mk
index db97040..0e8b574 100644
--- a/storaged/Android.mk
+++ b/storaged/Android.mk
@@ -2,14 +2,16 @@
 
 LOCAL_PATH := $(call my-dir)
 
-LIBSTORAGED_SHARED_LIBRARIES := libbinder libbase libutils libcutils liblog libsysutils libcap
+LIBSTORAGED_SHARED_LIBRARIES := libbinder libbase libutils libcutils liblog libsysutils libcap libpackagelistparser
 
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES := storaged.cpp \
                   storaged_service.cpp \
                   storaged_utils.cpp \
+                  storaged_uid_monitor.cpp \
                   EventLogTags.logtags
+
 LOCAL_MODULE := libstoraged
 LOCAL_CFLAGS := -Werror
 LOCAL_C_INCLUDES := $(LOCAL_PATH)/include external/googletest/googletest/include
diff --git a/storaged/EventLogTags.logtags b/storaged/EventLogTags.logtags
index ee92dd1..71fda25 100644
--- a/storaged/EventLogTags.logtags
+++ b/storaged/EventLogTags.logtags
@@ -37,3 +37,5 @@
 2732 storaged_disk_stats (type|3),(start_time|2|3),(end_time|2|3),(read_ios|2|1),(read_merges|2|1),(read_sectors|2|1),(read_ticks|2|3),(write_ios|2|1),(write_merges|2|1),(write_sectors|2|1),(write_ticks|2|3),(o_in_flight|2|1),(io_ticks|2|3),(io_in_queue|2|1)
 
 2733 storaged_emmc_info (mmc_ver|3),(eol|1),(lifetime_a|1),(lifetime_b|1)
+
+2734 storaged_uid_io_alert (name|3),(read|2),(write|2),(interval|2)
\ No newline at end of file
diff --git a/storaged/include/storaged.h b/storaged/include/storaged.h
index 521cc9e..7fa9958 100644
--- a/storaged/include/storaged.h
+++ b/storaged/include/storaged.h
@@ -17,13 +17,17 @@
 #ifndef _STORAGED_H_
 #define _STORAGED_H_
 
-#include <queue>
 #include <semaphore.h>
 #include <stdint.h>
+#include <time.h>
+
+#include <queue>
 #include <string>
 #include <unordered_map>
 #include <vector>
 
+#include "storaged_uid_monitor.h"
+
 #define FRIEND_TEST(test_case_name, test_name) \
 friend class test_case_name##_##test_name##_Test
 
@@ -165,6 +169,8 @@
 #define MMC_DISK_STATS_PATH "/sys/block/mmcblk0/stat"
 #define SDA_DISK_STATS_PATH "/sys/block/sda/stat"
 #define EMMC_ECSD_PATH "/d/mmc0/mmc0:0001/ext_csd"
+#define UID_IO_STATS_PATH "/proc/uid_io/stats"
+
 class disk_stats_monitor {
 private:
     FRIEND_TEST(storaged_test, disk_stats_monitor);
@@ -260,12 +266,15 @@
 #define DEFAULT_PERIODIC_CHORES_INTERVAL_UNIT ( 60 )
 #define DEFAULT_PERIODIC_CHORES_INTERVAL_DISK_STATS_PUBLISH ( 3600 )
 #define DEFAULT_PERIODIC_CHORES_INTERVAL_EMMC_INFO_PUBLISH ( 86400 )
+#define DEFAULT_PERIODIC_CHORES_INTERVAL_UID_IO_ALERT ( 3600 )
 
 struct storaged_config {
     int periodic_chores_interval_unit;
     int periodic_chores_interval_disk_stats_publish;
     int periodic_chores_interval_emmc_info_publish;
+    int periodic_chores_interval_uid_io;
     bool proc_taskio_readable;  // are /proc/[pid]/{io, comm, cmdline, stat} all readable
+    bool proc_uid_io_available;      // whether uid_io is accessible
     bool emmc_available;        // whether eMMC est_csd file is readable
     bool diskstats_available;   // whether diskstats is accessible
 };
@@ -278,6 +287,7 @@
     disk_stats_monitor mDsm;
     emmc_info_t mEmmcInfo;
     tasks_t mTasks;
+    uid_monitor mUidm;
     time_t mStarttime;
 public:
     storaged_t(void);
@@ -295,6 +305,9 @@
     void set_emmc_interval(int emmc_info) {
         mConfig.periodic_chores_interval_emmc_info_publish = emmc_info;
     }
+    void set_uid_io_interval(int uid_io) {
+        mUidm.set_periodic_chores_interval(uid_io);
+    }
     std::vector<struct task_info> get_tasks(void) {
         // There could be a race when get_tasks() and the main thread is updating at the same time
         // While update_running_tasks() is updating the critical sections at the end of the function
@@ -312,11 +325,16 @@
     time_t get_starttime(void) {
         return mStarttime;
     }
+
+    std::unordered_map<uint32_t, struct uid_info> get_uids(void) {
+        return mUidm.get_uids();
+    }
 };
 
 // Eventlog tag
 // The content must match the definition in EventLogTags.logtags
 #define EVENTLOGTAG_DISKSTATS ( 2732 )
 #define EVENTLOGTAG_EMMCINFO ( 2733 )
+#define EVENTLOGTAG_UID_IO_ALERT ( 2734 )
 
 #endif /* _STORAGED_H_ */
diff --git a/storaged/include/storaged_service.h b/storaged/include/storaged_service.h
index 64a9c81..220038c 100644
--- a/storaged/include/storaged_service.h
+++ b/storaged/include/storaged_service.h
@@ -31,9 +31,11 @@
 public:
     enum {
         DUMPTASKS = IBinder::FIRST_CALL_TRANSACTION,
+        DUMPUIDS  = IBinder::FIRST_CALL_TRANSACTION + 1,
     };
     // Request the service to run the test function
     virtual std::vector<struct task_info> dump_tasks(const char* option) = 0;
+    virtual std::vector<struct uid_info> dump_uids(const char* option) = 0;
 
     DECLARE_META_INTERFACE(Storaged);
 };
@@ -43,6 +45,7 @@
 public:
     BpStoraged(const sp<IBinder>& impl) : BpInterface<IStoraged>(impl){};
     virtual std::vector<struct task_info> dump_tasks(const char* option);
+    virtual std::vector<struct uid_info> dump_uids(const char* option);
 };
 
 // Server
@@ -52,6 +55,7 @@
 
 class Storaged : public BnStoraged {
     virtual std::vector<struct task_info> dump_tasks(const char* option);
+    virtual std::vector<struct uid_info> dump_uids(const char* option);
 };
 
 sp<IStoraged> get_storaged_service();
diff --git a/storaged/include/storaged_uid_monitor.h b/storaged/include/storaged_uid_monitor.h
new file mode 100644
index 0000000..eceb7fd
--- /dev/null
+++ b/storaged/include/storaged_uid_monitor.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _STORAGED_UID_MONITOR_H_
+#define _STORAGED_UID_MONITOR_H_
+
+#include <stdint.h>
+
+#include <string>
+#include <unordered_map>
+
+enum {
+    UID_FOREGROUND = 0,
+    UID_BACKGROUND = 1,
+    UID_STATS_SIZE = 2
+};
+
+struct uid_io_stats {
+    uint64_t rchar;                 // characters read
+    uint64_t wchar;                 // characters written
+    uint64_t read_bytes;            // bytes read (from storage layer)
+    uint64_t write_bytes;           // bytes written (to storage layer)
+};
+
+struct uid_info {
+    uint32_t uid;                   // user id
+    std::string name;               // package name
+    struct uid_io_stats io[UID_STATS_SIZE];      // [0]:foreground [1]:background
+
+};
+
+class uid_monitor {
+private:
+    std::unordered_map<uint32_t, struct uid_info> last_uids;
+    void set_last_uids(std::unordered_map<uint32_t, struct uid_info>&& uids, uint64_t ts);
+    int interval; // monitor interval in seconds
+    uint64_t last_report_ts; // timestamp of last report in nsec
+public:
+    uid_monitor();
+    void set_periodic_chores_interval(int t) { interval = t; }
+    int get_periodic_chores_interval() { return interval; }
+    std::unordered_map<uint32_t, struct uid_info> get_uids();
+    void report();
+};
+
+#endif /* _STORAGED_UID_MONITOR_H_ */
diff --git a/storaged/include/storaged_utils.h b/storaged/include/storaged_utils.h
index 83538c2..bb14708 100644
--- a/storaged/include/storaged_utils.h
+++ b/storaged/include/storaged_utils.h
@@ -34,12 +34,15 @@
 // Task I/O
 bool parse_task_info(uint32_t pid, struct task_info* info);
 void sort_running_tasks_info(std::vector<struct task_info> &tasks);
+// UID I/O
+void sort_running_uids_info(std::vector<struct uid_info> &uids);
 
 // Logging
 void log_console_running_tasks_info(std::vector<struct task_info> tasks);
+void log_console_running_uids_info(std::vector<struct uid_info> uids);
 
 void log_debug_disk_perf(struct disk_perf* perf, const char* type);
 
 void log_event_disk_stats(struct disk_stats* stats, const char* type);
 void log_event_emmc_info(struct emmc_info* info_);
-#endif /* _STORAGED_UTILS_H_ */
\ No newline at end of file
+#endif /* _STORAGED_UTILS_H_ */
diff --git a/storaged/main.cpp b/storaged/main.cpp
index 0cb0f5f..9151574 100644
--- a/storaged/main.cpp
+++ b/storaged/main.cpp
@@ -105,9 +105,11 @@
 static void help_message(void) {
     printf("usage: storaged [OPTION]\n");
     printf("  -d    --dump                  Dump task I/O usage to stdout\n");
+    printf("  -u    --uid                   Dump uid I/O usage to stdout\n");
     printf("  -s    --start                 Start storaged (default)\n");
     printf("        --emmc=INTERVAL         Set publish interval of emmc lifetime information (in days)\n");
     printf("        --diskstats=INTERVAL    Set publish interval of diskstats (in hours)\n");
+    printf("        --uidio=INTERVAL        Set publish interval of uid io (in hours)\n");
     printf("        --unit=INTERVAL         Set storaged's refresh interval (in seconds)\n");
     fflush(stdout);
 }
@@ -118,10 +120,12 @@
 int main(int argc, char** argv) {
     int flag_main_service = 0;
     int flag_dump_task = 0;
+    int flag_dump_uid = 0;
     int flag_config = 0;
     int unit_interval = DEFAULT_PERIODIC_CHORES_INTERVAL_UNIT;
     int diskstats_interval = DEFAULT_PERIODIC_CHORES_INTERVAL_DISK_STATS_PUBLISH;
     int emmc_interval = DEFAULT_PERIODIC_CHORES_INTERVAL_EMMC_INFO_PUBLISH;
+    int uid_io_interval = DEFAULT_PERIODIC_CHORES_INTERVAL_UID_IO_ALERT;
     int fd_emmc = -1;
     int opt;
 
@@ -131,12 +135,14 @@
             {"start",       no_argument,        0, 's'},
             {"kill",        no_argument,        0, 'k'},
             {"dump",        no_argument,        0, 'd'},
+            {"uid",         no_argument,        0, 'u'},
             {"help",        no_argument,        0, 'h'},
             {"unit",        required_argument,  0,  0 },
             {"diskstats",   required_argument,  0,  0 },
-            {"emmc",        required_argument,  0,  0 }
+            {"emmc",        required_argument,  0,  0 },
+            {"uidio",       required_argument,  0,  0 }
         };
-        opt = getopt_long(argc, argv, ":skdh0", long_options, &opt_idx);
+        opt = getopt_long(argc, argv, ":skdhu0", long_options, &opt_idx);
         if (opt == -1) {
             break;
         }
@@ -165,7 +171,15 @@
 
                 } else if (strcmp(long_options[opt_idx].name, "emmc") == 0) {
                     emmc_interval = atoi(optarg) * DAY_TO_SEC;
-                    if (diskstats_interval == 0) {
+                    if (emmc_interval == 0) {
+                        fprintf(stderr, "Invalid argument. Option %s requires an integer argument greater than 0.\n",
+                                long_options[opt_idx].name);
+                        help_message();
+                        return -1;
+                    }
+                } else if (strcmp(long_options[opt_idx].name, "uidio") == 0) {
+                    uid_io_interval = atoi(optarg) * HOUR_TO_SEC;
+                    if (uid_io_interval == 0) {
                         fprintf(stderr, "Invalid argument. Option %s requires an integer argument greater than 0.\n",
                                 long_options[opt_idx].name);
                         help_message();
@@ -187,6 +201,9 @@
         case 'd':
             flag_dump_task = 1;
             break;
+        case 'u':
+            flag_dump_uid = 1;
+            break;
         case 'h':
             help_message();
             return 0;
@@ -230,6 +247,7 @@
             storaged.set_unit_interval(unit_interval);
             storaged.set_diskstats_interval(diskstats_interval);
             storaged.set_emmc_interval(emmc_interval);
+            storaged.set_uid_io_interval(uid_io_interval);
         }
 
         // Start the main thread of storaged
@@ -278,5 +296,24 @@
         return 0;
     }
 
+    if (flag_dump_uid) {
+        sp<IStoraged> storaged_service = get_storaged_service();
+        if (storaged_service == NULL) {
+            fprintf(stderr, "Cannot find storaged service.\nMaybe run storaged --start first?\n");
+            return -1;
+        }
+        std::vector<struct uid_info> res = storaged_service->dump_uids(NULL);
+
+        if (res.size() == 0) {
+            fprintf(stderr, "UID I/O is not readable in this version of kernel.\n");
+            return 0;
+        }
+
+        sort_running_uids_info(res);
+        log_console_running_uids_info(res);
+
+        return 0;
+    }
+
     return 0;
 }
diff --git a/storaged/storaged.cpp b/storaged/storaged.cpp
index e4d3e68..0c53f44 100644
--- a/storaged/storaged.cpp
+++ b/storaged/storaged.cpp
@@ -174,9 +174,12 @@
         }
     }
 
+    mConfig.proc_uid_io_available = (access(UID_IO_STATS_PATH, R_OK) == 0);
+
     mConfig.periodic_chores_interval_unit = DEFAULT_PERIODIC_CHORES_INTERVAL_UNIT;
     mConfig.periodic_chores_interval_disk_stats_publish = DEFAULT_PERIODIC_CHORES_INTERVAL_DISK_STATS_PUBLISH;
     mConfig.periodic_chores_interval_emmc_info_publish = DEFAULT_PERIODIC_CHORES_INTERVAL_EMMC_INFO_PUBLISH;
+    mUidm.set_periodic_chores_interval(DEFAULT_PERIODIC_CHORES_INTERVAL_UID_IO_ALERT);
 
     mStarttime = time(NULL);
 }
@@ -202,5 +205,10 @@
         mEmmcInfo.publish();
     }
 
+    if (mConfig.proc_uid_io_available && mTimer &&
+            (mTimer % mUidm.get_periodic_chores_interval()) == 0) {
+         mUidm.report();
+    }
+
     mTimer += mConfig.periodic_chores_interval_unit;
 }
diff --git a/storaged/storaged_service.cpp b/storaged/storaged_service.cpp
index aa38ceb..2a81aef 100644
--- a/storaged/storaged_service.cpp
+++ b/storaged/storaged_service.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include <stdint.h>
+
 #include <vector>
 
 #include <binder/IBinder.h>
@@ -41,6 +43,22 @@
     return res;
 }
 
+std::vector<struct uid_info> BpStoraged::dump_uids(const char* /*option*/) {
+    Parcel data, reply;
+    data.writeInterfaceToken(IStoraged::getInterfaceDescriptor());
+
+    remote()->transact(DUMPUIDS, data, &reply);
+
+    uint32_t res_size = reply.readInt32();
+    std::vector<struct uid_info> res(res_size);
+    for (auto&& uid : res) {
+        uid.uid = reply.readInt32();
+        uid.name = reply.readCString();
+        reply.read(&uid.io, sizeof(uid.io));
+    }
+    return res;
+}
+
 IMPLEMENT_META_INTERFACE(Storaged, "Storaged");
 
 status_t BnStoraged::onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) {
@@ -57,14 +75,36 @@
                 return NO_ERROR;
             }
             break;
+        case DUMPUIDS: {
+                std::vector<struct uid_info> res = dump_uids(NULL);
+                reply->writeInt32(res.size());
+                for (auto uid : res) {
+                    reply->writeInt32(uid.uid);
+                    reply->writeCString(uid.name.c_str());
+                    reply->write(&uid.io, sizeof(uid.io));
+                }
+                return NO_ERROR;
+            }
+            break;
         default:
             return BBinder::onTransact(code, data, reply, flags);
     }
 }
+
 std::vector<struct task_info> Storaged::dump_tasks(const char* /* option */) {
     return storaged.get_tasks();
 }
 
+std::vector<struct uid_info> Storaged::dump_uids(const char* /* option */) {
+    std::vector<struct uid_info> uids_v;
+    std::unordered_map<uint32_t, struct uid_info> uids_m = storaged.get_uids();
+
+    for (const auto& it : uids_m) {
+        uids_v.push_back(it.second);
+    }
+    return uids_v;
+}
+
 sp<IStoraged> get_storaged_service() {
     sp<IServiceManager> sm = defaultServiceManager();
     if (sm == NULL) return NULL;
@@ -75,4 +115,4 @@
     sp<IStoraged> storaged = interface_cast<IStoraged>(binder);
 
     return storaged;
-}
\ No newline at end of file
+}
diff --git a/storaged/storaged_uid_monitor.cpp b/storaged/storaged_uid_monitor.cpp
new file mode 100644
index 0000000..4105dae
--- /dev/null
+++ b/storaged/storaged_uid_monitor.cpp
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#define LOG_TAG "storaged"
+
+#include <stdint.h>
+#include <time.h>
+
+#include <string>
+#include <sstream>
+#include <unordered_map>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/macros.h>
+#include <android-base/stringprintf.h>
+#include <log/log_event_list.h>
+#include <packagelistparser/packagelistparser.h>
+
+#include "storaged.h"
+#include "storaged_uid_monitor.h"
+
+static const uint64_t io_alert_threshold = 1024 * 1024 * 1024; // 1GB
+
+using namespace android;
+using namespace android::base;
+
+static bool packagelist_parse_cb(pkg_info* info, void* userdata)
+{
+    std::unordered_map<uint32_t, struct uid_info>* uids =
+        reinterpret_cast<std::unordered_map<uint32_t, struct uid_info>*>(userdata);
+
+    if (uids->find(info->uid) != uids->end()) {
+        (*uids)[info->uid].name = info->name;
+    }
+
+    packagelist_free(info);
+    return true;
+}
+
+void uid_monitor::set_last_uids(std::unordered_map<uint32_t, struct uid_info>&& uids,
+            uint64_t ts)
+{
+    last_uids = uids;
+    last_report_ts = ts;
+}
+
+std::unordered_map<uint32_t, struct uid_info> uid_monitor::get_uids()
+{
+    std::unordered_map<uint32_t, struct uid_info> uids;
+    std::string buffer;
+    if (!android::base::ReadFileToString(UID_IO_STATS_PATH, &buffer)) {
+        PLOG_TO(SYSTEM, ERROR) << UID_IO_STATS_PATH << ": ReadFileToString failed";
+        return uids;
+    }
+
+    std::stringstream ss(buffer);
+    struct uid_info u;
+    bool refresh_uid = false;
+
+    while (ss >> u.uid) {
+        ss >> u.io[UID_FOREGROUND].rchar >> u.io[UID_FOREGROUND].wchar
+           >> u.io[UID_FOREGROUND].read_bytes >> u.io[UID_FOREGROUND].write_bytes
+           >> u.io[UID_BACKGROUND].rchar >> u.io[UID_BACKGROUND].wchar
+           >> u.io[UID_BACKGROUND].read_bytes >> u.io[UID_BACKGROUND].write_bytes;
+
+        if (!ss.good()) {
+            ss.clear(std::ios_base::badbit);
+            break;
+        }
+
+        if (last_uids.find(u.uid) == last_uids.end()) {
+            refresh_uid = true;
+            u.name = std::to_string(u.uid);
+        } else {
+            u.name = last_uids[u.uid].name;
+        }
+        uids[u.uid] = u;
+    }
+
+    if (!ss.eof() || ss.bad()) {
+        uids.clear();
+        LOG_TO(SYSTEM, ERROR) << "read UID IO stats failed";
+    }
+
+    if (refresh_uid) {
+        packagelist_parse(packagelist_parse_cb, &uids);
+    }
+
+    return uids;
+}
+
+void uid_monitor::report()
+{
+    struct timespec ts;
+
+    // Use monotonic to exclude suspend time so that we measure IO bytes/sec
+    // when system is running.
+    if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) {
+        PLOG_TO(SYSTEM, ERROR) << "clock_gettime() failed";
+        return;
+    }
+
+    uint64_t curr_ts = ts.tv_sec * NS_PER_SEC + ts.tv_nsec;
+    uint64_t ts_delta = curr_ts - last_report_ts;
+    uint64_t adjusted_threshold = io_alert_threshold * ((double)ts_delta / interval / NS_PER_SEC);
+
+    std::unordered_map<uint32_t, struct uid_info> uids = get_uids();
+    if (uids.empty()) {
+        return;
+    }
+
+    for (const auto& it : uids) {
+        const struct uid_info& uid = it.second;
+        uint64_t bg_read_delta = uid.io[UID_BACKGROUND].read_bytes -
+            last_uids[uid.uid].io[UID_BACKGROUND].read_bytes;
+        uint64_t bg_write_delta = uid.io[UID_BACKGROUND].write_bytes -
+            last_uids[uid.uid].io[UID_BACKGROUND].write_bytes;
+
+        if (bg_read_delta + bg_write_delta >= adjusted_threshold) {
+            android_log_event_list(EVENTLOGTAG_UID_IO_ALERT)
+                << uid.name << bg_read_delta << bg_write_delta
+                << uint64_t(ts_delta / NS_PER_SEC) << LOG_ID_EVENTS;
+        }
+    }
+
+    set_last_uids(std::move(uids), curr_ts);
+}
+
+uid_monitor::uid_monitor()
+{
+    struct timespec ts;
+
+    if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) {
+        PLOG_TO(SYSTEM, ERROR) << "clock_gettime() failed";
+        return;
+    }
+    last_report_ts = ts.tv_sec * NS_PER_SEC + ts.tv_nsec;
+}
diff --git a/storaged/storaged_utils.cpp b/storaged/storaged_utils.cpp
index c845ac4..6e4ddb6 100644
--- a/storaged/storaged_utils.cpp
+++ b/storaged/storaged_utils.cpp
@@ -435,6 +435,54 @@
     fflush(stdout);
 }
 
+static bool cmp_uid_info(struct uid_info l, struct uid_info r) {
+    // Compare background I/O first.
+    for (int i = UID_STATS_SIZE - 1; i >= 0; i--) {
+        uint64_t l_bytes = l.io[i].read_bytes + l.io[i].write_bytes;
+        uint64_t r_bytes = r.io[i].read_bytes + r.io[i].write_bytes;
+        uint64_t l_chars = l.io[i].rchar + l.io[i].wchar;
+        uint64_t r_chars = r.io[i].rchar + r.io[i].wchar;
+
+        if (l_bytes != r_bytes) {
+            return l_bytes > r_bytes;
+        }
+        if (l_chars != r_chars) {
+            return l_chars > r_chars;
+        }
+    }
+
+    return l.name < r.name;
+}
+
+void sort_running_uids_info(std::vector<struct uid_info> &uids) {
+    std::sort(uids.begin(), uids.end(), cmp_uid_info);
+}
+
+// Logging functions
+void log_console_running_uids_info(std::vector<struct uid_info> uids) {
+// Sample Output:
+//                                       Application        FG Read       FG Write        FG Read       FG Write        BG Read       BG Write        BG Read       BG Write
+//                                          NAME/UID     Characters     Characters          Bytes          Bytes     Characters     Characters          Bytes          Bytes
+//                                        ----------     ----------     ----------     ----------     ----------     ----------     ----------     ----------     ----------
+//                      com.google.android.gsf.login              0              0              0              0       57195097        5137089      176386048        6512640
+//           com.google.android.googlequicksearchbox              0              0              0              0        4196821       12123468       34295808       13225984
+//                                              1037           4572            537              0              0         131352        5145643       34263040        5144576
+//                        com.google.android.youtube           2182             70              0              0       63969383         482939       38731776         466944
+
+    // Title
+    printf("Per-UID I/O stats\n");
+    printf("                                       Application        FG Read       FG Write        FG Read       FG Write        BG Read       BG Write        BG Read       BG Write\n"
+           "                                          NAME/UID     Characters     Characters          Bytes          Bytes     Characters     Characters          Bytes          Bytes\n"
+           "                                        ----------     ----------     ----------     ----------     ----------     ----------     ----------     ----------     ----------\n");
+
+    for (const auto& uid : uids) {
+        printf("%50s%15ju%15ju%15ju%15ju%15ju%15ju%15ju%15ju\n", uid.name.c_str(),
+            uid.io[0].rchar, uid.io[0].wchar, uid.io[0].read_bytes, uid.io[0].write_bytes,
+            uid.io[1].rchar, uid.io[1].wchar, uid.io[1].read_bytes, uid.io[1].write_bytes);
+    }
+    fflush(stdout);
+}
+
 #if DEBUG
 void log_debug_disk_perf(struct disk_perf* perf, const char* type) {
     // skip if the input structure are all zeros
diff --git a/storaged/tests/Android.mk b/storaged/tests/Android.mk
index 4a0e45c..26d04b1 100644
--- a/storaged/tests/Android.mk
+++ b/storaged/tests/Android.mk
@@ -40,6 +40,6 @@
 LOCAL_MODULE_TAGS := $(test_tags)
 LOCAL_CFLAGS += $(test_c_flags)
 LOCAL_STATIC_LIBRARIES := libstoraged
-LOCAL_SHARED_LIBRARIES := libbase libcutils liblog
+LOCAL_SHARED_LIBRARIES := libbase libcutils liblog libpackagelistparser
 LOCAL_SRC_FILES := $(test_src_files)
 include $(BUILD_NATIVE_TEST)