diff --git a/storaged/Android.mk b/storaged/Android.mk new file mode 100644 index 000000000..db97040dc --- /dev/null +++ b/storaged/Android.mk @@ -0,0 +1,34 @@ +# Copyright 2016 The Android Open Source Project + +LOCAL_PATH := $(call my-dir) + +LIBSTORAGED_SHARED_LIBRARIES := libbinder libbase libutils libcutils liblog libsysutils libcap + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := storaged.cpp \ + storaged_service.cpp \ + storaged_utils.cpp \ + EventLogTags.logtags +LOCAL_MODULE := libstoraged +LOCAL_CFLAGS := -Werror +LOCAL_C_INCLUDES := $(LOCAL_PATH)/include external/googletest/googletest/include +LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include +LOCAL_SHARED_LIBRARIES := $(LIBSTORAGED_SHARED_LIBRARIES) + +include $(BUILD_STATIC_LIBRARY) + +include $(CLEAR_VARS) + +LOCAL_MODULE := storaged +LOCAL_INIT_RC := storaged.rc +LOCAL_SRC_FILES := main.cpp +# libstoraged is an internal static library, only main.cpp and storaged_test.cpp should be using it +LOCAL_STATIC_LIBRARIES := libstoraged +LOCAL_SHARED_LIBRARIES := $(LIBSTORAGED_SHARED_LIBRARIES) +LOCAL_CFLAGS := -Wall -Werror -Wno-unused-parameter +LOCAL_C_INCLUDES := external/googletest/googletest/include + +include $(BUILD_EXECUTABLE) + +include $(call first-makefiles-under,$(LOCAL_PATH)) diff --git a/storaged/EventLogTags.logtags b/storaged/EventLogTags.logtags new file mode 100644 index 000000000..2e25d4a29 --- /dev/null +++ b/storaged/EventLogTags.logtags @@ -0,0 +1,39 @@ +# The entries in this file map a sparse set of log tag numbers to tag names. +# This is installed on the device, in /system/etc, and parsed by logcat. +# +# Tag numbers are decimal integers, from 0 to 2^31. (Let's leave the +# negative values alone for now.) +# +# Tag names are one or more ASCII letters and numbers or underscores, i.e. +# "[A-Z][a-z][0-9]_". Do not include spaces or punctuation (the former +# impacts log readability, the latter makes regex searches more annoying). +# +# Tag numbers and names are separated by whitespace. Blank lines and lines +# starting with '#' are ignored. +# +# Optionally, after the tag names can be put a description for the value(s) +# of the tag. Description are in the format +# (|data type[|data unit]) +# Multiple values are separated by commas. +# +# The data type is a number from the following values: +# 1: int +# 2: long +# 3: string +# 4: list +# 5: float +# +# The data unit is a number taken from the following list: +# 1: Number of objects +# 2: Number of bytes +# 3: Number of milliseconds +# 4: Number of allocations +# 5: Id +# 6: Percent +# Default value for data of type int/long is 2 (bytes). +# +# TODO: generate ".java" and ".h" files with integer constants from this file. + +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) \ No newline at end of file diff --git a/storaged/include/storaged.h b/storaged/include/storaged.h new file mode 100644 index 000000000..eb827cf9d --- /dev/null +++ b/storaged/include/storaged.h @@ -0,0 +1,340 @@ +/* + * 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_H_ +#define _STORAGED_H_ + +#define DEBUG + +#include +#include +#include +#include +#include +#include +#include + +#define FRIEND_TEST(test_case_name, test_name) \ +friend class test_case_name##_##test_name##_Test + +/* For debug */ +#ifdef DEBUG +#define debuginfo(fmt, ...) \ + do {printf("%s():\t" fmt "\t[%s:%d]\n", __FUNCTION__, ##__VA_ARGS__, __FILE__, __LINE__);} \ + while(0) +#else +#define debuginfo(...) +#endif + +#define KMSG_PRIORITY(PRI) \ + '<', \ + '0' + LOG_MAKEPRI(LOG_DAEMON, LOG_PRI(PRI)) / 10, \ + '0' + LOG_MAKEPRI(LOG_DAEMON, LOG_PRI(PRI)) % 10, \ + '>' + +static char kmsg_error_prefix[] = { KMSG_PRIORITY(LOG_ERR), + 's', 't', 'o', 'r', 'a', 'g', 'e', 'd', ':', '\0' }; + +static char kmsg_info_prefix[] = { KMSG_PRIORITY(LOG_INFO), + 's', 't', 'o', 'r', 'a', 'g', 'e', 'd', ':', '\0' }; + +static char kmsg_warning_prefix[] = { KMSG_PRIORITY(LOG_WARNING), + 's', 't', 'o', 'r', 'a', 'g', 'e', 'd', ':', '\0' }; + +// number of attributes diskstats has +#define DISK_STATS_SIZE ( 11 ) +// maximum size limit of a stats file +#define DISK_STATS_FILE_MAX_SIZE ( 256 ) +#define DISK_STATS_IO_IN_FLIGHT_IDX ( 8 ) +struct disk_stats { + /* It will be extremely unlikely for any of the following entries to overflow. + * For read_bytes(which will be greater than any of the following entries), it + * will take 27 years to overflow uint64_t at the reading rate of 20GB/s, which + * is the peak memory transfer rate for current memory. + * The diskstats entries (first 11) need to be at top in this structure _after_ + * compiler's optimization. + */ + uint64_t read_ios; // number of read I/Os processed + uint64_t read_merges; // number of read I/Os merged with in-queue I/Os + uint64_t read_sectors; // number of sectors read + uint64_t read_ticks; // total wait time for read requests + uint64_t write_ios; // number of write I/Os processed + uint64_t write_merges; // number of write I/Os merged with in-queue I/Os + uint64_t write_sectors; // number of sectors written + uint64_t write_ticks; // total wait time for write requests + uint64_t io_in_flight; // number of I/Os currently in flight + uint64_t io_ticks; // total time this block device has been active + uint64_t io_in_queue; // total wait time for all requests + + uint64_t start_time; // monotonic time accounting starts + uint64_t end_time; // monotonic time accounting ends + uint32_t counter; // private counter for accumulate calculations + double io_avg; // average io_in_flight for accumulate calculations +}; + +#define MMC_VER_STR_LEN ( 8 ) // maximum length of the MMC version string +// minimum size of a ext_csd file +#define EXT_CSD_FILE_MIN_SIZE ( 1024 ) +struct emmc_info { + int eol; // pre-eol (end of life) information + int lifetime_a; // device life time estimation (type A) + int lifetime_b; // device life time estimation (type B) + char mmc_ver[MMC_VER_STR_LEN]; // device version string +}; + +struct disk_perf { + uint32_t read_perf; // read speed (kbytes/s) + uint32_t read_ios; // read I/Os per second + uint32_t write_perf; // write speed (kbytes/s) + uint32_t write_ios; // write I/Os per second + uint32_t queue; // I/Os in queue +}; + +#define CMD_MAX_LEN ( 64 ) +struct task_info { + uint32_t pid; // task id + uint64_t rchar; // characters read + uint64_t wchar; // characters written + uint64_t syscr; // read syscalls + uint64_t syscw; // write syscalls + uint64_t read_bytes; // bytes read (from storage layer) + uint64_t write_bytes; // bytes written (to storage layer) + uint64_t cancelled_write_bytes; // cancelled write byte by truncate + + uint64_t starttime; // start time of task + + char cmd[CMD_MAX_LEN]; // filename of the executable +}; + +class lock_t { + sem_t* mSem; +public: + lock_t(sem_t* sem) { + mSem = sem; + sem_wait(mSem); + } + ~lock_t() { + sem_post(mSem); + } +}; + +class tasks_t { +private: + FRIEND_TEST(storaged_test, tasks_t); + sem_t mSem; + // hashmap for all running tasks w/ pid as key + std::unordered_map mRunning; + // hashmap for all tasks that have been killed (categorized by cmd) w/ cmd as key + std::unordered_map mOld; + std::unordered_map get_running_tasks(); +public: + tasks_t() { + sem_init(&mSem, 0, 1); // TODO: constructor don't have a return value, what if sem_init fails + } + + ~tasks_t() { + sem_destroy(&mSem); + } + + void update_running_tasks(void); + std::vector get_tasks(void); +}; + +class stream_stats { +private: + double mSum; + double mSquareSum; + uint32_t mCnt; +public: + stream_stats() : mSum(0), mSquareSum(0), mCnt(0) {}; + ~stream_stats() {}; + double get_mean() { + return mSum / mCnt; + } + double get_std() { + return sqrt(mSquareSum / mCnt - mSum * mSum / (mCnt * mCnt)); + } + void add(uint32_t num) { + mSum += (double)num; + mSquareSum += (double)num * (double)num; + mCnt++; + } + void evict(uint32_t num) { + if (mSum < num || mSquareSum < (double)num * (double)num) return; + mSum -= (double)num; + mSquareSum -= (double)num * (double)num; + mCnt--; + } +}; + +#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" +class disk_stats_monitor { +private: + FRIEND_TEST(storaged_test, disk_stats_monitor); + const char* DISK_STATS_PATH; + struct disk_stats mPrevious; + struct disk_stats mAccumulate; + bool mStall; + std::queue mBuffer; + struct { + stream_stats read_perf; // read speed (bytes/s) + stream_stats read_ios; // read I/Os per second + stream_stats write_perf; // write speed (bytes/s) + stream_stats write_ios; // write I/O per second + stream_stats queue; // I/Os in queue + } mStats; + bool mValid; + const uint32_t mWindow; + const double mSigma; + struct disk_perf mMean; + struct disk_perf mStd; + + void update_mean(); + void update_std(); + void add(struct disk_perf* perf); + void evict(struct disk_perf* perf); + bool detect(struct disk_perf* perf); + + void update(struct disk_stats* stats); + +public: + disk_stats_monitor(uint32_t window_size = 5, double sigma = 1.0) : + mStall(false), + mValid(false), + mWindow(window_size), + mSigma(sigma) { + memset(&mPrevious, 0, sizeof(mPrevious)); + memset(&mMean, 0, sizeof(mMean)); + memset(&mStd, 0, sizeof(mStd)); + + if (access(MMC_DISK_STATS_PATH, R_OK) >= 0) { + DISK_STATS_PATH = MMC_DISK_STATS_PATH; + } else { + DISK_STATS_PATH = SDA_DISK_STATS_PATH; + } + } + void update(void); +}; + +class disk_stats_publisher { +private: + FRIEND_TEST(storaged_test, disk_stats_publisher); + const char* DISK_STATS_PATH; + struct disk_stats mAccumulate; + struct disk_stats mPrevious; +public: + disk_stats_publisher(void) { + memset(&mAccumulate, 0, sizeof(struct disk_stats)); + memset(&mPrevious, 0, sizeof(struct disk_stats)); + + if (access(MMC_DISK_STATS_PATH, R_OK) >= 0) { + DISK_STATS_PATH = MMC_DISK_STATS_PATH; + } else { + DISK_STATS_PATH = SDA_DISK_STATS_PATH; + } + } + + ~disk_stats_publisher(void) {} + void publish(void); + void update(void); +}; + +class emmc_info_t { +private: + struct emmc_info mInfo; + bool mValid; + int mFdEmmc; +public: + emmc_info_t(void) : + mValid(false), + mFdEmmc(-1) { + memset(&mInfo, 0, sizeof(struct emmc_info)); + } + ~emmc_info_t(void) {} + + void publish(void); + void update(void); + void set_emmc_fd(int fd) { + mFdEmmc = fd; + } +}; + +// Periodic chores intervals in seconds +#define DEFAULT_PERIODIC_CHORES_INTERVAL_UNIT ( 20 ) +#define DEFAULT_PERIODIC_CHORES_INTERVAL_DISK_STATS_PUBLISH ( 60 ) +#define DEFAULT_PERIODIC_CHORES_INTERVAL_EMMC_INFO_PUBLISH ( 60 * 2 ) + +struct storaged_config { + int periodic_chores_interval_unit; + int periodic_chores_interval_disk_stats_publish; + int periodic_chores_interval_emmc_info_publish; + bool proc_taskio_readable; // are /proc/[pid]/{io, comm, cmdline, stat} all readable + bool emmc_available; // whether eMMC est_csd file is readable + bool diskstats_available; // whether diskstats is accessible +}; + +class storaged_t { +private: + time_t mTimer; + storaged_config mConfig; + disk_stats_publisher mDiskStats; + disk_stats_monitor mDsm; + emmc_info_t mEmmcInfo; + tasks_t mTasks; + time_t mStarttime; +public: + storaged_t(void); + ~storaged_t() {} + void event(void); + void pause(void) { + sleep(mConfig.periodic_chores_interval_unit); + } + void set_unit_interval(int unit) { + mConfig.periodic_chores_interval_unit = unit; + } + void set_diskstats_interval(int disk_stats) { + mConfig.periodic_chores_interval_disk_stats_publish = disk_stats; + } + void set_emmc_interval(int emmc_info) { + mConfig.periodic_chores_interval_emmc_info_publish = emmc_info; + } + std::vector 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 + // all together atomically, the final state of task_t can only be either the main thread's + // update or this update. Since the race can only occur when both threads are updating + // "simultaneously", either final state is acceptable. + mTasks.update_running_tasks(); + return mTasks.get_tasks(); + } + + void set_privileged_fds(int fd_emmc) { + mEmmcInfo.set_emmc_fd(fd_emmc); + } + + time_t get_starttime(void) { + return mStarttime; + } +}; + +// Eventlog tag +// The content must match the definition in EventLogTags.logtags +#define EVENTLOGTAG_DISKSTATS ( 2732 ) +#define EVENTLOGTAG_EMMCINFO ( 2733 ) + +#endif /* _STORAGED_H_ */ diff --git a/storaged/include/storaged_service.h b/storaged/include/storaged_service.h new file mode 100644 index 000000000..64a9c81c5 --- /dev/null +++ b/storaged/include/storaged_service.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_SERVICE_H_ +#define _STORAGED_SERVICE_H_ + +#include + +#include +#include + +#include "storaged.h" + +using namespace android; + +// Interface +class IStoraged : public IInterface { +public: + enum { + DUMPTASKS = IBinder::FIRST_CALL_TRANSACTION, + }; + // Request the service to run the test function + virtual std::vector dump_tasks(const char* option) = 0; + + DECLARE_META_INTERFACE(Storaged); +}; + +// Client +class BpStoraged : public BpInterface { +public: + BpStoraged(const sp& impl) : BpInterface(impl){}; + virtual std::vector dump_tasks(const char* option); +}; + +// Server +class BnStoraged : public BnInterface { + virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0); +}; + +class Storaged : public BnStoraged { + virtual std::vector dump_tasks(const char* option); +}; + +sp get_storaged_service(); + +#endif /* _STORAGED_SERVICE_H_ */ \ No newline at end of file diff --git a/storaged/include/storaged_utils.h b/storaged/include/storaged_utils.h new file mode 100644 index 000000000..b0e514605 --- /dev/null +++ b/storaged/include/storaged_utils.h @@ -0,0 +1,46 @@ +/* + * 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_UTILS_H_ +#define _STORAGED_UTILS_H_ + +#include +#include +#include +#include + +#include "storaged.h" + +// Diskstats +bool parse_disk_stats(const char* disk_stats_path, struct disk_stats* stats); +struct disk_perf get_disk_perf(struct disk_stats* stats); +struct disk_stats get_inc_disk_stats(struct disk_stats* prev, struct disk_stats* curr); +void add_disk_stats(struct disk_stats* src, struct disk_stats* dst); +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 &tasks); + +// Logging +void log_console_running_tasks_info(std::vector tasks); +void log_kernel_disk_stats(struct disk_stats* stats, const char* type); +void log_kernel_disk_perf(struct disk_perf* perf, const char* type); +void log_kernel_emmc_info(struct emmc_info* info); + +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 diff --git a/storaged/main.cpp b/storaged/main.cpp new file mode 100644 index 000000000..bd1166a6c --- /dev/null +++ b/storaged/main.cpp @@ -0,0 +1,298 @@ +/* + * 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" +#define KLOG_LEVEL 6 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +storaged_t storaged; + +static int drop_privs() { + // privilege setting + struct sched_param param; + memset(¶m, 0, sizeof(param)); + + if (set_sched_policy(0, SP_BACKGROUND) < 0) return -1; + + if (sched_setscheduler((pid_t) 0, SCHED_BATCH, ¶m) < 0) return -1; + + if (setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_BACKGROUND) < 0) return -1; + + if (prctl(PR_SET_KEEPCAPS, 1) < 0) return -1; + + std::unique_ptr caps(cap_init(), cap_free); + if (cap_clear(caps.get()) < 0) return -1; + cap_value_t cap_value[] = { + CAP_SETGID, + CAP_SETUID, + CAP_SYS_PTRACE // allow access to proc//io as non-root user + }; + if (cap_set_flag(caps.get(), CAP_PERMITTED, + arraysize(cap_value), cap_value, + CAP_SET) < 0) return -1; + if (cap_set_flag(caps.get(), CAP_EFFECTIVE, + arraysize(cap_value), cap_value, + CAP_SET) < 0) return -1; + if (cap_set_proc(caps.get()) < 0) + return -1; + + gid_t groups[] = { AID_READPROC }; + + if (setgroups(sizeof(groups) / sizeof(groups[0]), groups) == -1) return -1; + + if (setgid(AID_SYSTEM) != 0) return -1; + + if (setuid(AID_SYSTEM) != 0) return -1; + + if (cap_set_flag(caps.get(), CAP_PERMITTED, 2, cap_value, CAP_CLEAR) < 0) return -1; + if (cap_set_flag(caps.get(), CAP_EFFECTIVE, 2, cap_value, CAP_CLEAR) < 0) return -1; + if (cap_set_proc(caps.get()) < 0) + return -1; + + return 0; +} + +// Function of storaged's main thread +extern int fd_dmesg; +void* storaged_main(void* s) { + storaged_t* storaged = (storaged_t*)s; + + if (fd_dmesg >= 0) { + static const char start_message[] = {KMSG_PRIORITY(LOG_INFO), + 's', 't', 'o', 'r', 'a', 'g', 'e', 'd', ':', ' ', 'S', 't', 'a', 'r', 't', '\n'}; + write(fd_dmesg, start_message, sizeof(start_message)); + } + + for (;;) { + storaged->event(); + storaged->pause(); + } + return NULL; +} + +static void help_message(void) { + printf("usage: storaged [OPTION]\n"); + printf(" -d --dump Dump task 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(" --unit=INTERVAL Set storaged's refresh interval (in seconds)\n"); + fflush(stdout); +} + +#define HOUR_TO_SEC ( 3600 ) +#define DAY_TO_SEC ( 3600 * 24 ) + +int main(int argc, char** argv) { + klog_set_level(KLOG_LEVEL); + int flag_main_service = 0; + int flag_dump_task = 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 fd_emmc = -1; + int opt; + + for (;;) { + int opt_idx = 0; + static struct option long_options[] = { + {"start", no_argument, 0, 's'}, + {"kill", no_argument, 0, 'k'}, + {"dump", no_argument, 0, 'd'}, + {"help", no_argument, 0, 'h'}, + {"unit", required_argument, 0, 0 }, + {"diskstats", required_argument, 0, 0 }, + {"emmc", required_argument, 0, 0 } + }; + opt = getopt_long(argc, argv, ":skdh0", long_options, &opt_idx); + if (opt == -1) { + break; + } + + switch (opt) { + case 0: + printf("option %s", long_options[opt_idx].name); + if (optarg) { + printf(" with arg %s", optarg); + if (strcmp(long_options[opt_idx].name, "unit") == 0) { + unit_interval = atoi(optarg); + if (unit_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, "diskstats") == 0) { + diskstats_interval = atoi(optarg) * HOUR_TO_SEC; + if (diskstats_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, "emmc") == 0) { + emmc_interval = atoi(optarg) * DAY_TO_SEC; + if (diskstats_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; + } + } + flag_config = 1; + } else { + fprintf(stderr, "Invalid argument. Option %s requires an argument.\n", + long_options[opt_idx].name); + help_message(); + return -1; + } + printf("\n"); + break; + case 's': + flag_main_service = 1; + break; + case 'd': + flag_dump_task = 1; + break; + case 'h': + help_message(); + return 0; + case '?': + default: + fprintf(stderr, "no supported option\n"); + help_message(); + return -1; + } + } + + if (argc == 1) { + flag_main_service = 1; + } + + if (flag_main_service && flag_dump_task) { + fprintf(stderr, "Invalid arguments. Option \"start\" and \"dump\" cannot be used together.\n"); + help_message(); + return -1; + } + + if (flag_config && flag_dump_task) { + fprintf(stderr, "Invalid arguments. Cannot set configs in \'dump\' option.\n"); + help_message(); + return -1; + } + + if (flag_main_service) { // start main thread + static const char dev_kmsg[] = "/dev/kmsg"; + fd_dmesg = android_get_control_file(dev_kmsg); + if (fd_dmesg < 0) + fd_dmesg = TEMP_FAILURE_RETRY(open(dev_kmsg, O_WRONLY)); + + static const char mmc0_ext_csd[] = "/d/mmc0/mmc0:0001/ext_csd"; + fd_emmc = android_get_control_file(mmc0_ext_csd); + if (fd_emmc < 0) + fd_emmc = TEMP_FAILURE_RETRY(open(mmc0_ext_csd, O_RDONLY)); + + if (drop_privs() != 0) { + return -1; + } + + storaged.set_privileged_fds(fd_emmc); + + if (flag_config) { + storaged.set_unit_interval(unit_interval); + storaged.set_diskstats_interval(diskstats_interval); + storaged.set_emmc_interval(emmc_interval); + } + + // Start the main thread of storaged + pthread_t storaged_main_thread; + if (pthread_create(&storaged_main_thread, NULL, storaged_main, &storaged)) { + if (fd_dmesg >= 0) { + std::string error_message = android::base::StringPrintf( + "%s Failed to create main thread\n", kmsg_error_prefix); + write(fd_dmesg, error_message.c_str(), error_message.length()); + } + return -1; + } + + defaultServiceManager()->addService(String16("storaged"), new Storaged()); + android::ProcessState::self()->startThreadPool(); + IPCThreadState::self()->joinThreadPool(); + pthread_join(storaged_main_thread, NULL); + + close(fd_dmesg); + close(fd_emmc); + + return 0; + } + + if (flag_dump_task) { + sp 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 res = storaged_service->dump_tasks(NULL); + + if (res.size() == 0) { + fprintf(stderr, "Task I/O is not readable in this version of kernel.\n"); + return 0; + } + + time_t starttime = storaged.get_starttime(); + + if (starttime == (time_t)-1) { + fprintf(stderr, "Unknown start time\n"); + } else { + char* time_str = ctime(&starttime); + printf("Application I/O was collected by storaged since %s", time_str); + } + + sort_running_tasks_info(res); + log_console_running_tasks_info(res); + + + return 0; + } + + return 0; +} \ No newline at end of file diff --git a/storaged/storaged.cpp b/storaged/storaged.cpp new file mode 100644 index 000000000..140220867 --- /dev/null +++ b/storaged/storaged.cpp @@ -0,0 +1,211 @@ +/* + * 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 +#include +#include + + +#include + +#include +#include + +/* disk_stats_publisher */ +void disk_stats_publisher::publish(void) { + // Logging + log_kernel_disk_stats(&mAccumulate, "regular"); + struct disk_perf perf = get_disk_perf(&mAccumulate); + log_kernel_disk_perf(&perf, "regular"); + log_event_disk_stats(&mAccumulate, "regular"); + // Reset global structures + memset(&mAccumulate, 0, sizeof(struct disk_stats)); +} + +void disk_stats_publisher::update(void) { + struct disk_stats curr; + if (parse_disk_stats(DISK_STATS_PATH, &curr)) { + struct disk_stats inc = get_inc_disk_stats(&mPrevious, &curr); + add_disk_stats(&inc, &mAccumulate); + #ifdef DEBUG +// log_kernel_disk_stats(&mPrevious, "prev stats"); +// log_kernel_disk_stats(&curr, "curr stats"); +// log_kernel_disk_stats(&inc, "inc stats"); +// log_kernel_disk_stats(&mAccumulate, "accumulated stats"); + #endif + mPrevious = curr; + } +} + +/* disk_stats_monitor */ +void disk_stats_monitor::update_mean() { + CHECK(mValid); + mMean.read_perf = (uint32_t)mStats.read_perf.get_mean(); + mMean.read_ios = (uint32_t)mStats.read_ios.get_mean(); + mMean.write_perf = (uint32_t)mStats.write_perf.get_mean(); + mMean.write_ios = (uint32_t)mStats.write_ios.get_mean(); + mMean.queue = (uint32_t)mStats.queue.get_mean(); +} + +void disk_stats_monitor::update_std() { + CHECK(mValid); + mStd.read_perf = (uint32_t)mStats.read_perf.get_std(); + mStd.read_ios = (uint32_t)mStats.read_ios.get_std(); + mStd.write_perf = (uint32_t)mStats.write_perf.get_std(); + mStd.write_ios = (uint32_t)mStats.write_ios.get_std(); + mStd.queue = (uint32_t)mStats.queue.get_std(); +} + +void disk_stats_monitor::add(struct disk_perf* perf) { + mStats.read_perf.add(perf->read_perf); + mStats.read_ios.add(perf->read_ios); + mStats.write_perf.add(perf->write_perf); + mStats.write_ios.add(perf->write_ios); + mStats.queue.add(perf->queue); +} + +void disk_stats_monitor::evict(struct disk_perf* perf) { + mStats.read_perf.evict(perf->read_perf); + mStats.read_ios.evict(perf->read_ios); + mStats.write_perf.evict(perf->write_perf); + mStats.write_ios.evict(perf->write_ios); + mStats.queue.evict(perf->queue); +} + +bool disk_stats_monitor::detect(struct disk_perf* perf) { + return ((double)perf->queue >= (double)mMean.queue + mSigma * (double)mStd.queue) && + ((double)perf->read_perf < (double)mMean.read_perf - mSigma * (double)mStd.read_perf) && + ((double)perf->write_perf < (double)mMean.write_perf - mSigma * (double)mStd.write_perf); +} + +void disk_stats_monitor::update(struct disk_stats* stats) { + struct disk_stats inc = get_inc_disk_stats(&mPrevious, stats); + struct disk_perf perf = get_disk_perf(&inc); + // Update internal data structures + if (LIKELY(mValid)) { + CHECK_EQ(mBuffer.size(), mWindow); + + if (UNLIKELY(detect(&perf))) { + mStall = true; + add_disk_stats(&inc, &mAccumulate); + #ifdef DEBUG + log_kernel_disk_perf(&mMean, "stalled_mean"); + log_kernel_disk_perf(&mStd, "stalled_std"); + #endif + } else { + if (mStall) { + log_kernel_disk_stats(&mAccumulate, "stalled"); + struct disk_perf acc_perf = get_disk_perf(&mAccumulate); + log_kernel_disk_perf(&acc_perf, "stalled"); + + log_event_disk_stats(&mAccumulate, "stalled"); + mStall = false; + memset(&mAccumulate, 0, sizeof(mAccumulate)); + } + } + + evict(&mBuffer.front()); + mBuffer.pop(); + add(&perf); + mBuffer.push(perf); + + update_mean(); + update_std(); + + } else { /* mValid == false */ + CHECK_LT(mBuffer.size(), mWindow); + add(&perf); + mBuffer.push(perf); + if (mBuffer.size() == mWindow) { + mValid = true; + update_mean(); + update_std(); + } + } + + mPrevious = *stats; +} + +void disk_stats_monitor::update(void) { + struct disk_stats curr; + if (LIKELY(parse_disk_stats(DISK_STATS_PATH, &curr))) { + update(&curr); + } +} + +/* emmc_info_t */ +void emmc_info_t::publish(void) { + if (mValid) { + log_kernel_emmc_info(&mInfo); + log_event_emmc_info(&mInfo); + } +} + +void emmc_info_t::update(void) { + if (mFdEmmc >= 0) { + mValid = parse_emmc_ecsd(mFdEmmc, &mInfo); + } +} + +/* storaged_t */ +storaged_t::storaged_t(void) { + mConfig.emmc_available = (access(EMMC_ECSD_PATH, R_OK) >= 0); + + if (access(MMC_DISK_STATS_PATH, R_OK) < 0 && access(SDA_DISK_STATS_PATH, R_OK) < 0) { + mConfig.diskstats_available = false; + } else { + mConfig.diskstats_available = true; + } + + mConfig.proc_taskio_readable = true; + const char* test_paths[] = {"/proc/1/io", "/proc/1/comm", "/proc/1/cmdline", "/proc/1/stat"}; + for (uint i = 0; i < sizeof(test_paths) / sizeof(const char*); ++i) { + if (access(test_paths[i], R_OK) < 0) { + mConfig.proc_taskio_readable = false; + break; + } + } + + 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; + + mStarttime = time(NULL); +} + +void storaged_t::event(void) { + if (mConfig.diskstats_available) { + mDiskStats.update(); + mDsm.update(); + if (mTimer && (mTimer % mConfig.periodic_chores_interval_disk_stats_publish) == 0) { + mDiskStats.publish(); + } + } + + if (mConfig.proc_taskio_readable) { + mTasks.update_running_tasks(); + } + + if (mConfig.emmc_available && mTimer && + (mTimer % mConfig.periodic_chores_interval_emmc_info_publish) == 0) { + mEmmcInfo.update(); + mEmmcInfo.publish(); + } + + mTimer += mConfig.periodic_chores_interval_unit; +} \ No newline at end of file diff --git a/storaged/storaged.rc b/storaged/storaged.rc new file mode 100644 index 000000000..f72521c8e --- /dev/null +++ b/storaged/storaged.rc @@ -0,0 +1,5 @@ +service storaged /system/bin/storaged + class main + file /d/mmc0/mmc0:0001/ext_csd r + file /dev/kmsg w + group root readproc diff --git a/storaged/storaged_service.cpp b/storaged/storaged_service.cpp new file mode 100644 index 000000000..aa38ceb64 --- /dev/null +++ b/storaged/storaged_service.cpp @@ -0,0 +1,78 @@ +/* + * 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. + */ + +#include + +#include +#include + +#include +#include + +#include +#include + +extern storaged_t storaged; + +std::vector BpStoraged::dump_tasks(const char* /*option*/) { + Parcel data, reply; + data.writeInterfaceToken(IStoraged::getInterfaceDescriptor()); + + remote()->transact(DUMPTASKS, data, &reply); + + uint32_t res_size = reply.readInt32(); + std::vector res(res_size); + for (auto&& task : res) { + reply.read(&task, sizeof(task)); + } + return res; +} + +IMPLEMENT_META_INTERFACE(Storaged, "Storaged"); + +status_t BnStoraged::onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { + data.checkInterface(this); + + switch(code) { + case DUMPTASKS: { + std::vector res = dump_tasks(NULL); + + reply->writeInt32(res.size()); + for (auto task : res) { + reply->write(&task, sizeof(task)); + } + return NO_ERROR; + } + break; + default: + return BBinder::onTransact(code, data, reply, flags); + } +} +std::vector Storaged::dump_tasks(const char* /* option */) { + return storaged.get_tasks(); +} + +sp get_storaged_service() { + sp sm = defaultServiceManager(); + if (sm == NULL) return NULL; + + sp binder = sm->getService(String16("storaged")); + if (binder == NULL) return NULL; + + sp storaged = interface_cast(binder); + + return storaged; +} \ No newline at end of file diff --git a/storaged/storaged_utils.cpp b/storaged/storaged_utils.cpp new file mode 100644 index 000000000..9c0b62595 --- /dev/null +++ b/storaged/storaged_utils.cpp @@ -0,0 +1,582 @@ +/* + * 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 +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#define SECTOR_SIZE ( 512 ) +#define SEC_TO_MSEC ( 1000 ) +#define MSEC_TO_USEC ( 1000 ) +#define USEC_TO_NSEC ( 1000 ) + +int fd_dmesg = -1; + +bool parse_disk_stats(const char* disk_stats_path, struct disk_stats* stats) { + // Get time + struct timespec ts; + // Use monotonic to exclude suspend time so that we measure IO bytes/sec + // when system is running. + int ret = clock_gettime(CLOCK_MONOTONIC, &ts); + if (ret < 0) { + if (fd_dmesg >= 0) { + std::string error_message = android::base::StringPrintf( + "%s clock_gettime() failed with errno %d\n", + kmsg_error_prefix, ret); + write(fd_dmesg, error_message.c_str(), error_message.length()); + } + return false; + } + + std::string buffer; + if (!android::base::ReadFileToString(disk_stats_path, &buffer)) { + if (fd_dmesg >= 0) { + std::string error_message = android::base::StringPrintf( + "%s %s: ReadFileToString failed.\n", kmsg_error_prefix, disk_stats_path); + write(fd_dmesg, error_message.c_str(), error_message.length()); + } + return false; + } + + // Regular diskstats entries + std::stringstream ss(buffer); + for (uint i = 0; i < DISK_STATS_SIZE; ++i) { + ss >> *((uint64_t*)stats + i); + } + // Other entries + stats->start_time = 0; + stats->end_time = (uint64_t)ts.tv_sec * SEC_TO_MSEC + + ts.tv_nsec / (MSEC_TO_USEC * USEC_TO_NSEC); + stats->counter = 1; + stats->io_avg = (double)stats->io_in_flight; + return true; +} + +struct disk_perf get_disk_perf(struct disk_stats* stats) { + struct disk_perf perf; + memset(&perf, 0, sizeof(struct disk_perf)); // initialize + + if (stats->io_ticks) { + if (stats->read_ticks) { + unsigned long long divisor = stats->read_ticks * stats->io_ticks; + perf.read_perf = ((unsigned long long)SECTOR_SIZE * + stats->read_sectors * + stats->io_in_queue + + (divisor >> 1)) / + divisor; + perf.read_ios = ((unsigned long long)SEC_TO_MSEC * + stats->read_ios * + stats->io_in_queue + + (divisor >> 1)) / + divisor; + } + if (stats->write_ticks) { + unsigned long long divisor = stats->write_ticks * stats->io_ticks; + perf.write_perf = ((unsigned long long)SECTOR_SIZE * + stats->write_sectors * + stats->io_in_queue + + (divisor >> 1)) / + divisor; + perf.write_ios = ((unsigned long long)SEC_TO_MSEC * + stats->write_ios * + stats->io_in_queue + + (divisor >> 1)) / + divisor; + } + perf.queue = (stats->io_in_queue + (stats->io_ticks >> 1)) / + stats->io_ticks; + } + return perf; +} + +struct disk_stats get_inc_disk_stats(struct disk_stats* prev, struct disk_stats* curr) { + struct disk_stats inc; + for (uint i = 0; i < DISK_STATS_SIZE; ++i) { + if (i == DISK_STATS_IO_IN_FLIGHT_IDX) { + continue; + } + + *((uint64_t*)&inc + i) = + *((uint64_t*)curr + i) - *((uint64_t*)prev + i); + } + // io_in_flight is exception + inc.io_in_flight = curr->io_in_flight; + + inc.start_time = prev->end_time; + inc.end_time = curr->end_time; + inc.io_avg = curr->io_avg; + inc.counter = 1; + + return inc; +} + +// Add src to dst +void add_disk_stats(struct disk_stats* src, struct disk_stats* dst) { + if (dst->end_time != 0 && dst->end_time != src->start_time && fd_dmesg >= 0) { + std::string warning_message = android::base::StringPrintf( + "%s Two dis-continuous periods of diskstats are added. " + "dst end with %jd, src start with %jd\n", + kmsg_warning_prefix, dst->end_time, src->start_time); + + write(fd_dmesg, warning_message.c_str(), warning_message.length()); + } + + for (uint i = 0; i < DISK_STATS_SIZE; ++i) { + if (i == DISK_STATS_IO_IN_FLIGHT_IDX) { + continue; + } + + *((uint64_t*)dst + i) += *((uint64_t*)src + i); + } + + dst->io_in_flight = src->io_in_flight; + if (dst->counter + src->counter) { + dst->io_avg = ((dst->io_avg * dst->counter) + (src->io_avg * src->counter)) / + (dst->counter + src->counter); + } + dst->counter += src->counter; + dst->end_time = src->end_time; + if (dst->start_time == 0) { + dst->start_time = src->start_time; + } +} + +bool parse_emmc_ecsd(int ext_csd_fd, struct emmc_info* info) { + CHECK(ext_csd_fd >= 0); + struct hex { + char str[2]; + }; + // List of interesting offsets + static const size_t EXT_CSD_REV_IDX = 192 * sizeof(hex); + static const size_t EXT_PRE_EOL_INFO_IDX = 267 * sizeof(hex); + static const size_t EXT_DEVICE_LIFE_TIME_EST_A_IDX = 268 * sizeof(hex); + static const size_t EXT_DEVICE_LIFE_TIME_EST_B_IDX = 269 * sizeof(hex); + + // Read file + CHECK(lseek(ext_csd_fd, 0, SEEK_SET) == 0); + std::string buffer; + if (!android::base::ReadFdToString(ext_csd_fd, &buffer)) { + if (fd_dmesg >= 0) { + std::string error_message = android::base::StringPrintf( + "%s ReadFdToString failed.\n", kmsg_error_prefix); + write(fd_dmesg, error_message.c_str(), error_message.length()); + } + return false; + } + + if (buffer.length() < EXT_CSD_FILE_MIN_SIZE) { + if (fd_dmesg >= 0) { + std::string error_message = android::base::StringPrintf( + "%s EMMC ext csd file has truncated content. File length: %d\n", + kmsg_error_prefix, (int)buffer.length()); + write(fd_dmesg, error_message.c_str(), error_message.length()); + } + return false; + } + + std::string sub; + std::stringstream ss; + // Parse EXT_CSD_REV + int ext_csd_rev = -1; + sub = buffer.substr(EXT_CSD_REV_IDX, sizeof(hex)); + ss << sub; + ss >> std::hex >> ext_csd_rev; + if (ext_csd_rev < 0) { + if (fd_dmesg >= 0) { + std::string error_message = android::base::StringPrintf( + "%s Failure on parsing EXT_CSD_REV.\n", kmsg_error_prefix); + write(fd_dmesg, error_message.c_str(), error_message.length()); + } + return false; + } + ss.clear(); + + static const char* ver_str[] = { + "4.0", "4.1", "4.2", "4.3", "Obsolete", "4.41", "4.5", "5.0" + }; + + strncpy(info->mmc_ver, + (ext_csd_rev < (int)(sizeof(ver_str) / sizeof(ver_str[0]))) ? + ver_str[ext_csd_rev] : + "Unknown", + MMC_VER_STR_LEN); + + if (ext_csd_rev < 7) { + return 0; + } + + // Parse EXT_PRE_EOL_INFO + info->eol = -1; + sub = buffer.substr(EXT_PRE_EOL_INFO_IDX, sizeof(hex)); + ss << sub; + ss >> std::hex >> info->eol; + if (info->eol < 0) { + if (fd_dmesg >= 0) { + std::string error_message = android::base::StringPrintf( + "%s Failure on parsing EXT_PRE_EOL_INFO.\n", kmsg_error_prefix); + write(fd_dmesg, error_message.c_str(), error_message.length()); + } + return false; + } + ss.clear(); + + // Parse DEVICE_LIFE_TIME_EST + info->lifetime_a = -1; + sub = buffer.substr(EXT_DEVICE_LIFE_TIME_EST_A_IDX, sizeof(hex)); + ss << sub; + ss >> std::hex >> info->lifetime_a; + if (info->lifetime_a < 0) { + if (fd_dmesg >= 0) { + std::string error_message = android::base::StringPrintf( + "%s Failure on parsing EXT_DEVICE_LIFE_TIME_EST_TYP_A.\n", kmsg_error_prefix); + write(fd_dmesg, error_message.c_str(), error_message.length()); + } + return false; + } + ss.clear(); + + info->lifetime_b = -1; + sub = buffer.substr(EXT_DEVICE_LIFE_TIME_EST_B_IDX, sizeof(hex)); + ss << sub; + ss >> std::hex >> info->lifetime_b; + if (info->lifetime_b < 0) { + if (fd_dmesg >= 0) { + std::string error_message = android::base::StringPrintf( + "%s Failure on parsing EXT_DEVICE_LIFE_TIME_EST_TYP_B.\n", kmsg_error_prefix); + write(fd_dmesg, error_message.c_str(), error_message.length()); + } + return false; + } + ss.clear(); + + return true; +} + +#define PROC_DIR "/proc/" +#define PROC_STAT_STARTTIME_IDX ( 22 ) // This index is 1 based according to the linux proc man page +bool parse_task_info(uint32_t pid, struct task_info* info) { + std::string buffer; + std::string pid_str = std::to_string(pid); + info->pid = pid; + + // Get task I/O + std::string task_io_path = android::base::StringPrintf(PROC_DIR "%s/io", pid_str.c_str()); + if (!android::base::ReadFileToString(task_io_path, &buffer)) return false; + + std::stringstream ss(buffer); + std::string title; + + ss >> title >> info->rchar + >> title >> info->wchar + >> title >> info->syscr + >> title >> info->syscw + >> title >> info->read_bytes + >> title >> info->write_bytes + >> title >> info->cancelled_write_bytes; + ss.clear(); + + // Get cmd string + std::string task_cmdline_path = android::base::StringPrintf(PROC_DIR "%u/cmdline", pid); + if (!android::base::ReadFileToString(task_cmdline_path, &buffer)) return false; + strcpy(info->cmd, android::base::Trim(buffer).c_str()); + + if (info->cmd[0] == '\0') { + std::string task_comm_path = android::base::StringPrintf(PROC_DIR "%u/comm", pid); + if (!android::base::ReadFileToString(task_comm_path, &buffer)) return false; + strcpy(info->cmd, android::base::Trim(buffer).c_str()); + } + + // Get task start time + std::string task_stat_path = android::base::StringPrintf(PROC_DIR "%u/stat", pid); + if (!android::base::ReadFileToString(task_stat_path, &buffer)) return false; + + std::vector stat_parts = android::base::Split(buffer, " "); + info->starttime = atoll(stat_parts[PROC_STAT_STARTTIME_IDX - 1].c_str()); + + return true; +} + +static bool is_pid(char* d_name) { + if (!d_name || d_name[0] == '\0') return false; + char* c = d_name; + while (*c) { + if (!isdigit(*c)) return false; + ++c; + } + return true; +} + +static bool cmp_task_info(struct task_info i, struct task_info j) { + if (i.write_bytes + i.read_bytes != j.write_bytes + j.read_bytes) { + return i.write_bytes + i.read_bytes > j.write_bytes + j.read_bytes; + } + if (i.wchar + i.rchar != j.wchar + j.rchar) { + return i.wchar + i.rchar > j.wchar + j.rchar; + } + if (i.syscw + i.syscr != j.syscw + j.syscr) { + return i.syscw + i.syscr > j.syscw + j.syscr; + } + + return strcmp(i.cmd, j.cmd) < 0; +} + +std::unordered_map tasks_t::get_running_tasks() { + std::unordered_map retval; + std::unique_ptr dir(opendir(PROC_DIR), closedir); + CHECK(dir != NULL); + struct dirent* dp; + + for (;;) { + if ((dp = readdir(dir.get())) == NULL) break; + if (!is_pid(dp->d_name)) continue; + + uint32_t pid = atol(dp->d_name); + struct task_info info; + if (parse_task_info(pid, &info)) { + retval[pid] = info; + } + } + return retval; +} + +static void add_task_info(struct task_info* src, struct task_info* dst) { + CHECK(strcmp(src->cmd, dst->cmd) == 0); + + dst->pid = 0; + dst->rchar += src->rchar; + dst->wchar += src->wchar; + dst->syscr += src->syscr; + dst->syscw += src->syscw; + dst->read_bytes += src->read_bytes; + dst->write_bytes += src->write_bytes; + dst->cancelled_write_bytes += src->cancelled_write_bytes; + dst->starttime = 0; +} + +void tasks_t::update_running_tasks(void) { + std::unordered_map tasks_latest = get_running_tasks(); + std::unordered_map tasks_old = mOld; + + for (auto t : mRunning) { + uint32_t pid = t.first; + // old task on mRunning still exist on tasks_latest + if (tasks_latest.find(pid) != tasks_latest.end() && + tasks_latest[pid].starttime == t.second.starttime) { + continue; + } else { + // This branch will handle 2 cases: + // - Task get killed between the 2 samplings + // - Task get killed and its pid is reused + std::string cmd = t.second.cmd; + struct task_info info = t.second; + + if (tasks_old.find(cmd) == tasks_old.end()) { + tasks_old[cmd] = info; + } else { + add_task_info(&info, &tasks_old[cmd]); + } + } + } + { // update critical area + // this is really fast! + std::unique_ptr lock(new lock_t(&mSem)); + mRunning = tasks_latest; + mOld = tasks_old; + } + +} + +std::vector tasks_t::get_tasks(void) { + std::unique_ptr lock(new lock_t(&mSem)); + std::unordered_map tasks_map = mOld; + + for (auto i : mRunning) { + std::string cmd = i.second.cmd; + if (tasks_map.find(cmd) == tasks_map.end()) { + tasks_map[cmd] = i.second; + } else { + add_task_info(&i.second, &tasks_map[cmd]); + } + } + + std::vector retval(tasks_map.size()); + int idx = 0; + for (auto i : tasks_map) { + retval[idx++] = i.second; + } + + return retval; +} + +void sort_running_tasks_info(std::vector &tasks) { + std::sort(tasks.begin(), tasks.end(), cmp_task_info); +} + +/* Logging functions */ +void log_console_running_tasks_info(std::vector tasks) { +// Sample Output: +// Application Read Write Read Write Read Write Cancelled +// Name Characters Characters Syscalls Syscalls Bytes Bytes Writebytes +// ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- +// zygote64 37688308 3388467 7607 4363 314519552 5373952 8192 +// system_server 95874193 2216913 74613 52257 213078016 7237632 16384 +// zygote 506279 1726194 921 263 128114688 1765376 0 +// /vendor/bin/qcks 75415632 75154382 21672 25036 63627264 29974528 10485760 +// /init 86658523 5107871 82113 8633 91015168 1245184 0 + + // Title + printf(" Application Read Write Read Write Read Write Cancelled\n" + " Name Characters Characters Syscalls Syscalls Bytes Bytes Writebytes\n" + " ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------\n"); + + for (struct task_info task : tasks) { + printf("%50s%15ju%15ju%15ju%15ju%15ju%15ju%15ju\n", + task.cmd, task.rchar, task.wchar, task.syscr, task.syscw, + task.read_bytes, task.write_bytes, task.cancelled_write_bytes); + } + fflush(stdout); +} + +void log_kernel_disk_stats(struct disk_stats* stats, const char* type) { + // skip if the input structure are all zeros + if (stats == NULL) return; + struct disk_stats zero_cmp; + memset(&zero_cmp, 0, sizeof(zero_cmp)); + if (memcmp(&zero_cmp, stats, sizeof(struct disk_stats)) == 0) return; + + if (fd_dmesg >= 0) { + std::string info_message = android::base::StringPrintf( + "%s diskstats %s: %ju %ju %ju %ju %ju %ju %ju %ju %ju %ju %.1f %ju %ju\n", + kmsg_info_prefix, type, stats->start_time, stats->end_time, + stats->read_ios, stats->read_merges, + stats->read_sectors, stats->read_ticks, + stats->write_ios, stats->write_merges, + stats->write_sectors, stats->write_ticks, + stats->io_avg, stats->io_ticks, + stats->io_in_queue); + + write(fd_dmesg, info_message.c_str(), info_message.length()); + } +} + +void log_kernel_disk_perf(struct disk_perf* perf, const char* type) { + // skip if the input structure are all zeros + if (perf == NULL) return; + struct disk_perf zero_cmp; + memset(&zero_cmp, 0, sizeof(zero_cmp)); + if (memcmp(&zero_cmp, perf, sizeof(struct disk_perf)) == 0) return; + + if (fd_dmesg >= 0) { + std::string info_message = android::base::StringPrintf( + "%s perf(ios) %s rd:%luKB/s(%lu/s) wr:%luKB/s(%lu/s) q:%lu\n", + kmsg_info_prefix, type, + (unsigned long)perf->read_perf, (unsigned long)perf->read_ios, + (unsigned long)perf->write_perf, (unsigned long)perf->write_ios, + (unsigned long)perf->queue); + + write(fd_dmesg, info_message.c_str(), info_message.length()); + } +} + +void log_kernel_emmc_info(struct emmc_info* info) { + // skip if the input structure are all zeros + if (info == NULL) return; + struct emmc_info zero_cmp; + memset(&zero_cmp, 0, sizeof(zero_cmp)); + if (memcmp(&zero_cmp, info, sizeof(struct emmc_info)) == 0) return; + + if (fd_dmesg >= 0) { + std::string info_message = android::base::StringPrintf( + "%s MMC %s eol:%d, lifetime typA:%d, typB:%d\n", + kmsg_info_prefix, info->mmc_ver, info->eol, info->lifetime_a, info->lifetime_b); + + write(fd_dmesg, info_message.c_str(), info_message.length()); + } +} + +void log_event_disk_stats(struct disk_stats* stats, const char* type) { + // skip if the input structure are all zeros + if (stats == NULL) return; + struct disk_stats zero_cmp; + memset(&zero_cmp, 0, sizeof(zero_cmp)); + // skip event logging diskstats when it is zero increment (all first 11 entries are zero) + if (memcmp(&zero_cmp, stats, sizeof(uint64_t) * DISK_STATS_SIZE) == 0) return; + + // Construct eventlog list + android_log_context ctx = create_android_logger(EVENTLOGTAG_DISKSTATS); + + android_log_write_string8(ctx, type); + android_log_write_int64(ctx, stats->start_time); + android_log_write_int64(ctx, stats->end_time); + android_log_write_int64(ctx, stats->read_ios); + android_log_write_int64(ctx, stats->read_merges); + android_log_write_int64(ctx, stats->read_sectors); + android_log_write_int64(ctx, stats->read_ticks); + android_log_write_int64(ctx, stats->write_ios); + android_log_write_int64(ctx, stats->write_merges); + android_log_write_int64(ctx, stats->write_sectors); + android_log_write_int64(ctx, stats->write_ticks); + android_log_write_int64(ctx, (uint64_t)stats->io_avg); + android_log_write_int64(ctx, stats->io_ticks); + android_log_write_int64(ctx, stats->io_in_queue); + + android_log_write_list(ctx, LOG_ID_EVENTS); + android_log_destroy(&ctx); +} + +void log_event_emmc_info(struct emmc_info* info) { + // skip if the input structure are all zeros + if (info == NULL) return; + struct emmc_info zero_cmp; + memset(&zero_cmp, 0, sizeof(zero_cmp)); + if (memcmp(&zero_cmp, info, sizeof(struct emmc_info)) == 0) return; + + android_log_context ctx = create_android_logger(EVENTLOGTAG_EMMCINFO); + + android_log_write_string8(ctx, info->mmc_ver); + android_log_write_int32(ctx, info->eol); + android_log_write_int32(ctx, info->lifetime_a); + android_log_write_int32(ctx, info->lifetime_b); + + android_log_write_list(ctx, LOG_ID_EVENTS); + android_log_destroy(&ctx); +} diff --git a/storaged/tests/Android.mk b/storaged/tests/Android.mk new file mode 100644 index 000000000..4a0e45c78 --- /dev/null +++ b/storaged/tests/Android.mk @@ -0,0 +1,45 @@ +# +# Copyright (C) 2014 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. +# + +LOCAL_PATH := $(call my-dir) + +test_module_prefix := storaged- +test_tags := tests + +# ----------------------------------------------------------------------------- +# Unit tests. +# ----------------------------------------------------------------------------- + +test_c_flags := \ + -fstack-protector-all \ + -g \ + -Wall -Wextra \ + -Werror \ + -fno-builtin \ + +test_src_files := \ + storaged_test.cpp \ + +# Build tests for the logger. Run with: +# adb shell /data/nativetest/storaged-unit-tests/storaged-unit-tests +include $(CLEAR_VARS) +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_SRC_FILES := $(test_src_files) +include $(BUILD_NATIVE_TEST) diff --git a/storaged/tests/storaged_test.cpp b/storaged/tests/storaged_test.cpp new file mode 100644 index 000000000..4ca9839b6 --- /dev/null +++ b/storaged/tests/storaged_test.cpp @@ -0,0 +1,587 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +//#include + +#include // data structures +#include // functions to test + +#define MMC_DISK_STATS_PATH "/sys/block/mmcblk0/stat" +#define SDA_DISK_STATS_PATH "/sys/block/sda/stat" +#define EMMC_EXT_CSD_PATH "/d/mmc0/mmc0:0001/ext_csd" +#define INIT_TASK_IO_PATH "/proc/1/io" + +static void pause(uint32_t sec) { + const char* path = "/cache/test"; + int fd = open(path, O_WRONLY | O_CREAT); + ASSERT_LT(-1, fd); + char buffer[2048]; + memset(buffer, 1, sizeof(buffer)); + int loop_size = 100; + for (int i = 0; i < loop_size; ++i) { + ASSERT_EQ(2048, write(fd, buffer, sizeof(buffer))); + } + fsync(fd); + close(fd); + + fd = open(path, O_RDONLY); + ASSERT_LT(-1, fd); + for (int i = 0; i < loop_size; ++i) { + ASSERT_EQ(2048, read(fd, buffer, sizeof(buffer))); + } + close(fd); + + sleep(sec); +} + +// the return values of the tested functions should be the expected ones +const char* DISK_STATS_PATH; +TEST(storaged_test, retvals) { + struct disk_stats stats; + struct emmc_info info; + memset(&stats, 0, sizeof(struct disk_stats)); + memset(&info, 0, sizeof(struct emmc_info)); + + int emmc_fd = open(EMMC_EXT_CSD_PATH, O_RDONLY); + if (emmc_fd >= 0) { + EXPECT_TRUE(parse_emmc_ecsd(emmc_fd, &info)); + } + + if (access(MMC_DISK_STATS_PATH, R_OK) >= 0) { + DISK_STATS_PATH = MMC_DISK_STATS_PATH; + } else if (access(SDA_DISK_STATS_PATH, R_OK) >= 0) { + DISK_STATS_PATH = SDA_DISK_STATS_PATH; + } else { + return; + } + + EXPECT_TRUE(parse_disk_stats(DISK_STATS_PATH, &stats)); + + struct disk_stats old_stats; + memset(&old_stats, 0, sizeof(struct disk_stats)); + old_stats = stats; + + const char wrong_path[] = "/this/is/wrong"; + EXPECT_FALSE(parse_disk_stats(wrong_path, &stats)); + + // reading a wrong path should not damage the output structure + EXPECT_EQ(0, memcmp(&stats, &old_stats, sizeof(disk_stats))); +} + +TEST(storaged_test, disk_stats) { + struct disk_stats stats; + memset(&stats, 0, sizeof(struct disk_stats)); + + ASSERT_TRUE(parse_disk_stats(DISK_STATS_PATH, &stats)); + + // every entry of stats (except io_in_flight) should all be greater than 0 + for (uint i = 0; i < DISK_STATS_SIZE; ++i) { + if (i == 8) continue; // skip io_in_flight which can be 0 + EXPECT_LT((uint64_t)0, *((uint64_t*)&stats + i)); + } + + // accumulation of the increments should be the same with the overall increment + struct disk_stats base, tmp, curr, acc, inc[5]; + memset(&base, 0, sizeof(struct disk_stats)); + memset(&tmp, 0, sizeof(struct disk_stats)); + memset(&acc, 0, sizeof(struct disk_stats)); + + for (uint i = 0; i < 5; ++i) { + ASSERT_TRUE(parse_disk_stats(DISK_STATS_PATH, &curr)); + if (i == 0) { + base = curr; + tmp = curr; + sleep(5); + continue; + } + inc[i] = get_inc_disk_stats(&tmp, &curr); + add_disk_stats(&inc[i], &acc); + tmp = curr; + pause(5); + } + struct disk_stats overall_inc; + memset(&overall_inc, 0, sizeof(disk_stats)); + overall_inc= get_inc_disk_stats(&base, &curr); + + for (uint i = 0; i < DISK_STATS_SIZE; ++i) { + if (i == 8) continue; // skip io_in_flight which can be 0 + EXPECT_EQ(*((uint64_t*)&overall_inc + i), *((uint64_t*)&acc + i)); + } +} + +TEST(storaged_test, emmc_info) { + struct emmc_info info, void_info; + memset(&info, 0, sizeof(struct emmc_info)); + memset(&void_info, 0, sizeof(struct emmc_info)); + + if (access(EMMC_EXT_CSD_PATH, R_OK) >= 0) { + int emmc_fd = open(EMMC_EXT_CSD_PATH, O_RDONLY); + ASSERT_GE(emmc_fd, 0); + ASSERT_TRUE(parse_emmc_ecsd(emmc_fd, &info)); + // parse_emmc_ecsd() should put something in info. + EXPECT_NE(0, memcmp(&void_info, &info, sizeof(struct emmc_info))); + } +} + +TEST(storaged_test, task_info) { + // parse_task_info should read something other than 0 from /proc/1/* + struct task_info task_info; + memset(&task_info, 0, sizeof(task_info)); + + if (!parse_task_info(1, &task_info)) return; + + EXPECT_EQ((uint32_t)1, task_info.pid); + EXPECT_LT((uint64_t)0, task_info.rchar); + EXPECT_LT((uint64_t)0, task_info.wchar); + EXPECT_LT((uint64_t)0, task_info.syscr); + EXPECT_LT((uint64_t)0, task_info.syscw); + EXPECT_LT((uint64_t)0, task_info.read_bytes); + EXPECT_LT((uint64_t)0, task_info.write_bytes); + // cancelled_write_bytes of init could be 0, there is no need to test + EXPECT_LE((uint64_t)0, task_info.starttime); + EXPECT_NE((char*)NULL, strstr(task_info.cmd, "init")); + + // Entries in /proc/1/io should be increasing through time + struct task_info task_old, task_new; + memset(&task_old, 0, sizeof(task_old)); + memset(&task_new, 0, sizeof(task_new)); + + // parse_task_info should succeed at this point + ASSERT_TRUE(parse_task_info(1, &task_old)); + sleep(1); + ASSERT_TRUE(parse_task_info(1, &task_new)); + + EXPECT_EQ(task_old.pid, task_new.pid); + EXPECT_LE(task_old.rchar, task_new.rchar); + EXPECT_LE(task_old.wchar, task_new.wchar); + EXPECT_LE(task_old.syscr, task_new.syscr); + EXPECT_LE(task_old.syscw, task_new.syscw); + EXPECT_LE(task_old.read_bytes, task_new.read_bytes); + EXPECT_LE(task_old.write_bytes, task_new.write_bytes); + EXPECT_LE(task_old.cancelled_write_bytes, task_new.cancelled_write_bytes); + EXPECT_EQ(task_old.starttime, task_new.starttime); + EXPECT_EQ(0, strcmp(task_old.cmd, task_new.cmd)); +} + +static double mean(std::deque nums) { + double sum = 0.0; + for (uint32_t i : nums) { + sum += i; + } + return sum / nums.size(); +} + +static double standard_deviation(std::deque nums) { + double sum = 0.0; + double avg = mean(nums); + for (uint32_t i : nums) { + sum += ((double)i - avg) * ((double)i - avg); + } + return sqrt(sum / nums.size()); +} + +TEST(storaged_test, stream_stats) { + // 100 random numbers + std::vector data = {8147,9058,1270,9134,6324,975,2785,5469,9575,9649,1576,9706,9572,4854,8003,1419,4218,9157,7922,9595,6557,357,8491,9340,6787,7577,7431,3922,6555,1712,7060,318,2769,462,971,8235,6948,3171,9502,344,4387,3816,7655,7952,1869,4898,4456,6463,7094,7547,2760,6797,6551,1626,1190,4984,9597,3404,5853,2238,7513,2551,5060,6991,8909,9593,5472,1386,1493,2575,8407,2543,8143,2435,9293,3500,1966,2511,6160,4733,3517,8308,5853,5497,9172,2858,7572,7537,3804,5678,759,540,5308,7792,9340,1299,5688,4694,119,3371}; + std::deque test_data; + stream_stats sstats; + for (uint32_t i : data) { + test_data.push_back(i); + sstats.add(i); + + EXPECT_EQ((int)standard_deviation(test_data), (int)sstats.get_std()); + EXPECT_EQ((int)mean(test_data), (int)sstats.get_mean()); + } + + for (uint32_t i : data) { + test_data.pop_front(); + sstats.evict(i); + + EXPECT_EQ((int)standard_deviation(test_data), (int)sstats.get_std()); + EXPECT_EQ((int)mean(test_data), (int)sstats.get_mean()); + } + + // some real data + std::vector another_data = {113875,81620,103145,28327,86855,207414,96526,52567,28553,250311}; + test_data.clear(); + uint32_t window_size = 2; + uint32_t idx; + stream_stats sstats1; + for (idx = 0; idx < window_size; ++idx) { + test_data.push_back(another_data[idx]); + sstats1.add(another_data[idx]); + } + EXPECT_EQ((int)standard_deviation(test_data), (int)sstats1.get_std()); + EXPECT_EQ((int)mean(test_data), (int)sstats1.get_mean()); + for (;idx < another_data.size(); ++idx) { + test_data.pop_front(); + sstats1.evict(another_data[idx - window_size]); + test_data.push_back(another_data[idx]); + sstats1.add(another_data[idx]); + EXPECT_EQ((int)standard_deviation(test_data), (int)sstats1.get_std()); + EXPECT_EQ((int)mean(test_data), (int)sstats1.get_mean()); + } +} + +static void expect_increasing(struct task_info told, struct task_info tnew) { + ASSERT_EQ(told.pid, tnew.pid); + ASSERT_EQ(told.starttime, tnew.starttime); + ASSERT_EQ(strcmp(told.cmd, tnew.cmd), 0); + + EXPECT_LE(told.rchar, tnew.rchar); + EXPECT_LE(told.wchar, tnew.wchar); + EXPECT_LE(told.syscr, tnew.syscr); + EXPECT_LE(told.syscw, tnew.syscw); + EXPECT_LE(told.read_bytes, tnew.read_bytes); + EXPECT_LE(told.write_bytes, tnew.write_bytes); + EXPECT_LE(told.cancelled_write_bytes, tnew.cancelled_write_bytes); +} + +static void expect_equal(struct task_info told, struct task_info tnew) { + ASSERT_EQ(told.pid, tnew.pid); + ASSERT_EQ(told.starttime, tnew.starttime); + ASSERT_EQ(strcmp(told.cmd, tnew.cmd), 0); + + EXPECT_EQ(told.rchar, tnew.rchar); + EXPECT_EQ(told.wchar, tnew.wchar); + EXPECT_EQ(told.syscr, tnew.syscr); + EXPECT_EQ(told.syscw, tnew.syscw); + EXPECT_EQ(told.read_bytes, tnew.read_bytes); + EXPECT_EQ(told.write_bytes, tnew.write_bytes); + EXPECT_EQ(told.cancelled_write_bytes, tnew.cancelled_write_bytes); +} + +static std::set find_overlap(std::unordered_map t1, + std::unordered_map t2) { + std::set retval; + for (auto i : t1) { + if (t2.find(i.first) != t2.end()) { + retval.insert(i.first); + } + } + + return retval; +} + +static std::set find_overlap(std::unordered_map t1, + std::unordered_map t2) { + std::set retval; + for (auto i : t1) { + if (t2.find(i.first) != t2.end()) { + retval.insert(i.first); + } + } + + return retval; +} + +static bool cmp_app_name(struct task_info i, struct task_info j) { + return strcmp(i.cmd, j.cmd) > 0; +} + +static void expect_match(std::vector v1, std::vector v2) { + ASSERT_EQ(v1.size(), v2.size()); + std::sort(v1.begin(), v1.end(), cmp_app_name); + std::sort(v2.begin(), v2.end(), cmp_app_name); + + for (uint i = 0; i < v1.size(); ++i) { + expect_equal(v1[i], v2[i]); + } +} + +static void add_task_info(struct task_info* src, struct task_info* dst) { + ASSERT_EQ(0, strcmp(src->cmd, dst->cmd)); + + dst->pid = 0; + dst->rchar += src->rchar; + dst->wchar += src->wchar; + dst->syscr += src->syscr; + dst->syscw += src->syscw; + dst->read_bytes += src->read_bytes; + dst->write_bytes += src->write_bytes; + dst->cancelled_write_bytes += src->cancelled_write_bytes; + dst->starttime = 0; +} + +static std::vector +categorize_tasks(std::unordered_map tasks) { + std::unordered_map tasks_cmd; + for (auto i : tasks) { + std::string cmd = i.second.cmd; + if (tasks_cmd.find(cmd) == tasks_cmd.end()) { + tasks_cmd[cmd] = i.second; + } else { + add_task_info(&i.second, &tasks_cmd[cmd]); + } + } + + std::vector retval(tasks_cmd.size()); + int cnt = 0; + for (auto i : tasks_cmd) { + retval[cnt++] = i.second; + } + + return retval; +} + +#define TEST_LOOPS 20 +TEST(storaged_test, tasks_t) { + // pass this test if /proc/[pid]/io is not readable + const char* test_paths[] = {"/proc/1/io", "/proc/1/comm", "/proc/1/cmdline", "/proc/1/stat"}; + for (uint i = 0; i < sizeof(test_paths) / sizeof(const char*); ++i) { + if (access(test_paths[i], R_OK) < 0) return; + } + + tasks_t tasks; + EXPECT_EQ((uint32_t)0, tasks.mRunning.size()); + EXPECT_EQ((uint32_t)0, tasks.mOld.size()); + + tasks.update_running_tasks(); + + std::unordered_map prev_running = tasks.mRunning; + std::unordered_map prev_old = tasks.mOld; + + // hashmap maintaining + std::unordered_map tasks_pid = tasks.mRunning; + + // get_running_tasks() should return something other than a null map + std::unordered_map test = tasks.get_running_tasks(); + EXPECT_LE((uint32_t)1, test.size()); + + for (int i = 0; i < TEST_LOOPS; ++i) { + tasks.update_running_tasks(); + + std::set overlap_running = find_overlap(prev_running, tasks.mRunning); + std::set overlap_old = find_overlap(prev_old, tasks.mOld); + + // overlap_running should capture init(pid == 1), since init never get killed + EXPECT_LE((uint32_t)1, overlap_running.size()); + EXPECT_NE(overlap_running.find((uint32_t)1), overlap_running.end()); + // overlap_old should never capture init, since init never get killed + EXPECT_EQ(overlap_old.find("init"), overlap_old.end()); + + // overlapping entries in previous and current running-tasks map should have increasing contents + for (uint32_t i : overlap_running) { + expect_increasing(prev_running[i], tasks.mRunning[i]); + } + + // overlapping entries in previous and current killed-tasks map should have increasing contents + // and the map size should also be increasing + for (std::string i : overlap_old) { + expect_increasing(prev_old[i], tasks.mOld[i]); + } + EXPECT_LE(prev_old.size(), tasks.mRunning.size()); + + // update app name & tasks_pid + for (auto i : tasks.mRunning) { + // test will fail if the pid got wrapped + if (tasks_pid.find(i.first) != tasks_pid.end()) { + expect_increasing(tasks_pid[i.first], i.second); + tasks_pid[i.first] = i.second; + } else { + tasks_pid[i.first] = i.second; + } + } + + // get maintained tasks + std::vector test_tasks = categorize_tasks(tasks_pid); + std::vector real_tasks = tasks.get_tasks(); + + expect_match(test_tasks, real_tasks); + + prev_running = tasks.mRunning; + prev_old = tasks.mOld; + + pause(5); + } +} + +static struct disk_perf disk_perf_multiply(struct disk_perf perf, double mul) { + struct disk_perf retval; + retval.read_perf = (double)perf.read_perf * mul; + retval.read_ios = (double)perf.read_ios * mul; + retval.write_perf = (double)perf.write_perf * mul; + retval.write_ios = (double)perf.write_ios * mul; + retval.queue = (double)perf.queue * mul; + + return retval; +} + +static struct disk_stats disk_stats_add(struct disk_stats stats1, struct disk_stats stats2) { + struct disk_stats retval; + retval.read_ios = stats1.read_ios + stats2.read_ios; + retval.read_merges = stats1.read_merges + stats2.read_merges; + retval.read_sectors = stats1.read_sectors + stats2.read_sectors; + retval.read_ticks = stats1.read_ticks + stats2.read_ticks; + retval.write_ios = stats1.write_ios + stats2.write_ios; + retval.write_merges = stats1.write_merges + stats2.write_merges; + retval.write_sectors = stats1.write_sectors + stats2.write_sectors; + retval.write_ticks = stats1.write_ticks + stats2.write_ticks; + retval.io_in_flight = stats1.io_in_flight + stats2.io_in_flight; + retval.io_ticks = stats1.io_ticks + stats2.io_ticks; + retval.io_in_queue = stats1.io_in_queue + stats2.io_in_queue; + retval.end_time = stats1.end_time + stats2.end_time; + + return retval; +} + +TEST(storaged_test, disk_stats_monitor) { + // asserting that there is one file for diskstats + ASSERT_TRUE(access(MMC_DISK_STATS_PATH, R_OK) >= 0 || access(SDA_DISK_STATS_PATH, R_OK) >= 0); + // testing if detect() will return the right value + disk_stats_monitor dsm_detect; + // feed monitor with constant perf data for io perf baseline + // using constant perf is reasonable since the functionality of stream_stats + // has already been tested + struct disk_perf norm_perf = { + .read_perf = 10 * 1024, + .read_ios = 50, + .write_perf = 5 * 1024, + .write_ios = 25, + .queue = 5 + }; + + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_real_distribution<> rand(0.8, 1.2); + + for (uint i = 0; i < dsm_detect.mWindow; ++i) { + struct disk_perf perf = disk_perf_multiply(norm_perf, rand(gen)); + + dsm_detect.add(&perf); + dsm_detect.mBuffer.push(perf); + EXPECT_EQ(dsm_detect.mBuffer.size(), (uint64_t)i + 1); + } + + dsm_detect.mValid = true; + dsm_detect.update_mean(); + dsm_detect.update_std(); + + for (double i = 0; i < 2 * dsm_detect.mSigma; i += 0.5) { + struct disk_perf test_perf; + struct disk_perf test_mean = dsm_detect.mMean; + struct disk_perf test_std = dsm_detect.mStd; + + test_perf.read_perf = (double)test_mean.read_perf - i * test_std.read_perf; + test_perf.read_ios = (double)test_mean.read_ios - i * test_std.read_ios; + test_perf.write_perf = (double)test_mean.write_perf - i * test_std.write_perf; + test_perf.write_ios = (double)test_mean.write_ios - i * test_std.write_ios; + test_perf.queue = (double)test_mean.queue + i * test_std.queue; + + EXPECT_EQ((i > dsm_detect.mSigma), dsm_detect.detect(&test_perf)); + } + + // testing if stalled disk_stats can be correctly accumulated in the monitor + disk_stats_monitor dsm_acc; + struct disk_stats norm_inc = { + .read_ios = 200, + .read_merges = 0, + .read_sectors = 200, + .read_ticks = 200, + .write_ios = 100, + .write_merges = 0, + .write_sectors = 100, + .write_ticks = 100, + .io_in_flight = 0, + .io_ticks = 600, + .io_in_queue = 300, + .start_time = 0, + .end_time = 100, + .counter = 0, + .io_avg = 0 + }; + + struct disk_stats stall_inc = { + .read_ios = 200, + .read_merges = 0, + .read_sectors = 20, + .read_ticks = 200, + .write_ios = 100, + .write_merges = 0, + .write_sectors = 10, + .write_ticks = 100, + .io_in_flight = 0, + .io_ticks = 600, + .io_in_queue = 1200, + .start_time = 0, + .end_time = 100, + .counter = 0, + .io_avg = 0 + }; + + struct disk_stats stats_base; + memset(&stats_base, 0, sizeof(stats_base)); + + int loop_size = 100; + for (int i = 0; i < loop_size; ++i) { + stats_base = disk_stats_add(stats_base, norm_inc); + dsm_acc.update(&stats_base); + EXPECT_EQ(dsm_acc.mValid, (uint32_t)(i + 1) >= dsm_acc.mWindow); + EXPECT_FALSE(dsm_acc.mStall); + } + + stats_base = disk_stats_add(stats_base, stall_inc); + dsm_acc.update(&stats_base); + EXPECT_TRUE(dsm_acc.mValid); + EXPECT_TRUE(dsm_acc.mStall); + + for (int i = 0; i < 10; ++i) { + stats_base = disk_stats_add(stats_base, norm_inc); + dsm_acc.update(&stats_base); + EXPECT_TRUE(dsm_acc.mValid); + EXPECT_FALSE(dsm_acc.mStall); + } +} + +static void expect_increasing(struct disk_stats stats1, struct disk_stats stats2) { + EXPECT_LE(stats1.read_ios, stats2.read_ios); + EXPECT_LE(stats1.read_merges, stats2.read_merges); + EXPECT_LE(stats1.read_sectors, stats2.read_sectors); + EXPECT_LE(stats1.read_ticks, stats2.read_ticks); + + EXPECT_LE(stats1.write_ios, stats2.write_ios); + EXPECT_LE(stats1.write_merges, stats2.write_merges); + EXPECT_LE(stats1.write_sectors, stats2.write_sectors); + EXPECT_LE(stats1.write_ticks, stats2.write_ticks); + + EXPECT_LE(stats1.io_ticks, stats2.io_ticks); + EXPECT_LE(stats1.io_in_queue, stats2.io_in_queue); +} + +TEST(storaged_test, disk_stats_publisher) { + // asserting that there is one file for diskstats + ASSERT_TRUE(access(MMC_DISK_STATS_PATH, R_OK) >= 0 || access(SDA_DISK_STATS_PATH, R_OK) >= 0); + disk_stats_publisher dsp; + struct disk_stats prev; + memset(&prev, 0, sizeof(prev)); + + for (int i = 0; i < TEST_LOOPS; ++i) { + dsp.update(); + expect_increasing(prev, dsp.mPrevious); + prev = dsp.mPrevious; + pause(10); + } +} +