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
This commit is contained in:
Jin Qian 2016-12-28 15:43:51 -08:00
parent 933d02818b
commit bcd6e3b9d9
12 changed files with 381 additions and 8 deletions

View File

@ -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

View File

@ -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)

View File

@ -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 @@ public:
#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 @@ public:
#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 @@ private:
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 @@ public:
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 @@ public:
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_ */

View File

@ -31,9 +31,11 @@ class IStoraged : public IInterface {
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 @@ class BpStoraged : public BpInterface<IStoraged> {
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 BnStoraged : public BnInterface<IStoraged> {
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();

View File

@ -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_ */

View File

@ -34,12 +34,15 @@ bool parse_emmc_ecsd(int ext_csd_fd, struct emmc_info* info);
// 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_ */
#endif /* _STORAGED_UTILS_H_ */

View File

@ -105,9 +105,11 @@ void* storaged_main(void* s) {
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 @@ static void help_message(void) {
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 @@ int main(int argc, char** argv) {
{"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 @@ int main(int argc, char** argv) {
} 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 @@ int main(int argc, char** argv) {
case 'd':
flag_dump_task = 1;
break;
case 'u':
flag_dump_uid = 1;
break;
case 'h':
help_message();
return 0;
@ -230,6 +247,7 @@ int main(int argc, char** argv) {
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 @@ int main(int argc, char** argv) {
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;
}

View File

@ -174,9 +174,12 @@ storaged_t::storaged_t(void) {
}
}
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 @@ void storaged_t::event(void) {
mEmmcInfo.publish();
}
if (mConfig.proc_uid_io_available && mTimer &&
(mTimer % mUidm.get_periodic_chores_interval()) == 0) {
mUidm.report();
}
mTimer += mConfig.periodic_chores_interval_unit;
}

View File

@ -14,6 +14,8 @@
* limitations under the License.
*/
#include <stdint.h>
#include <vector>
#include <binder/IBinder.h>
@ -41,6 +43,22 @@ std::vector<struct task_info> BpStoraged::dump_tasks(const char* /*option*/) {
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 @@ status_t BnStoraged::onTransact(uint32_t code, const Parcel& data, Parcel* reply
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> get_storaged_service() {
sp<IStoraged> storaged = interface_cast<IStoraged>(binder);
return storaged;
}
}

View File

@ -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;
}

View File

@ -435,6 +435,54 @@ void log_console_running_tasks_info(std::vector<struct task_info> tasks) {
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

View File

@ -40,6 +40,6 @@ LOCAL_MODULE := $(test_module_prefix)unit-tests
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)