Merge changes I09b11db0,I774b1a44
* changes: libcutils: add file permission for storaged storaged: add storaged native daemon
This commit is contained in:
commit
4769dab866
|
@ -149,6 +149,10 @@ static const struct fs_path_config android_files[] = {
|
|||
"system/bin/run-as" },
|
||||
{ 00700, AID_SYSTEM, AID_SHELL, CAP_MASK_LONG(CAP_BLOCK_SUSPEND),
|
||||
"system/bin/inputflinger" },
|
||||
{ 00750, AID_SYSTEM, AID_SHELL, CAP_MASK_LONG(CAP_SETUID) |
|
||||
CAP_MASK_LONG(CAP_SETGID) |
|
||||
CAP_MASK_LONG(CAP_SYS_PTRACE),
|
||||
"system/bin/storaged" },
|
||||
|
||||
/* Support FIFO scheduling mode in SurfaceFlinger. */
|
||||
{ 00755, AID_SYSTEM, AID_GRAPHICS, CAP_MASK_LONG(CAP_SYS_NICE),
|
||||
|
|
|
@ -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))
|
|
@ -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
|
||||
# (<name>|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)
|
|
@ -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 <queue>
|
||||
#include <semaphore.h>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <syslog.h>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#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<uint32_t, struct task_info> mRunning;
|
||||
// hashmap for all tasks that have been killed (categorized by cmd) w/ cmd as key
|
||||
std::unordered_map<std::string, struct task_info> mOld;
|
||||
std::unordered_map<std::uint32_t, struct task_info> 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<struct task_info> 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<struct disk_perf> 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<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
|
||||
// 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_ */
|
|
@ -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 <vector>
|
||||
|
||||
#include <binder/IInterface.h>
|
||||
#include <binder/IBinder.h>
|
||||
|
||||
#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<struct task_info> dump_tasks(const char* option) = 0;
|
||||
|
||||
DECLARE_META_INTERFACE(Storaged);
|
||||
};
|
||||
|
||||
// Client
|
||||
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);
|
||||
};
|
||||
|
||||
// Server
|
||||
class BnStoraged : public BnInterface<IStoraged> {
|
||||
virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0);
|
||||
};
|
||||
|
||||
class Storaged : public BnStoraged {
|
||||
virtual std::vector<struct task_info> dump_tasks(const char* option);
|
||||
};
|
||||
|
||||
sp<IStoraged> get_storaged_service();
|
||||
|
||||
#endif /* _STORAGED_SERVICE_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 <stdint.h>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#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<struct task_info> &tasks);
|
||||
|
||||
// Logging
|
||||
void log_console_running_tasks_info(std::vector<struct task_info> 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_ */
|
|
@ -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 <fcntl.h>
|
||||
#include <getopt.h>
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/capability.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <vector>
|
||||
|
||||
#include <android-base/macros.h>
|
||||
#include <android-base/stringprintf.h>
|
||||
#include <binder/ProcessState.h>
|
||||
#include <binder/IServiceManager.h>
|
||||
#include <binder/IPCThreadState.h>
|
||||
#include <cutils/android_get_control_file.h>
|
||||
#include <cutils/klog.h>
|
||||
#include <cutils/sched_policy.h>
|
||||
#include <private/android_filesystem_config.h>
|
||||
|
||||
#include <storaged.h>
|
||||
#include <storaged_service.h>
|
||||
#include <storaged_utils.h>
|
||||
|
||||
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<struct _cap_struct, int(*)(void *)> 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/<pid>/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<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 task_info> 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;
|
||||
}
|
|
@ -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 <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
|
||||
#include <android-base/logging.h>
|
||||
|
||||
#include <storaged.h>
|
||||
#include <storaged_utils.h>
|
||||
|
||||
/* 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;
|
||||
}
|
|
@ -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
|
|
@ -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 <vector>
|
||||
|
||||
#include <binder/IBinder.h>
|
||||
#include <binder/IInterface.h>
|
||||
|
||||
#include <binder/IPCThreadState.h>
|
||||
#include <binder/IServiceManager.h>
|
||||
|
||||
#include <storaged.h>
|
||||
#include <storaged_service.h>
|
||||
|
||||
extern storaged_t storaged;
|
||||
|
||||
std::vector<struct task_info> 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<struct task_info> 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<struct task_info> 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<struct task_info> Storaged::dump_tasks(const char* /* option */) {
|
||||
return storaged.get_tasks();
|
||||
}
|
||||
|
||||
sp<IStoraged> get_storaged_service() {
|
||||
sp<IServiceManager> sm = defaultServiceManager();
|
||||
if (sm == NULL) return NULL;
|
||||
|
||||
sp<IBinder> binder = sm->getService(String16("storaged"));
|
||||
if (binder == NULL) return NULL;
|
||||
|
||||
sp<IStoraged> storaged = interface_cast<IStoraged>(binder);
|
||||
|
||||
return storaged;
|
||||
}
|
|
@ -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 <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <linux/time.h>
|
||||
|
||||
#include <dirent.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <android-base/file.h>
|
||||
#include <android-base/stringprintf.h>
|
||||
#include <android-base/strings.h>
|
||||
#include <android-base/logging.h>
|
||||
#include <log/log.h>
|
||||
#include <log/log_event_list.h>
|
||||
#include <cutils/klog.h>
|
||||
|
||||
#include <storaged.h>
|
||||
#include <storaged_utils.h>
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#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<std::string> 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<uint32_t, struct task_info> tasks_t::get_running_tasks() {
|
||||
std::unordered_map<uint32_t, struct task_info> retval;
|
||||
std::unique_ptr<DIR, decltype(&closedir)> 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<uint32_t, struct task_info> tasks_latest = get_running_tasks();
|
||||
std::unordered_map<std::string, struct task_info> 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_t> lock(new lock_t(&mSem));
|
||||
mRunning = tasks_latest;
|
||||
mOld = tasks_old;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::vector<struct task_info> tasks_t::get_tasks(void) {
|
||||
std::unique_ptr<lock_t> lock(new lock_t(&mSem));
|
||||
std::unordered_map<std::string, struct task_info> 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<struct task_info> 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<struct task_info> &tasks) {
|
||||
std::sort(tasks.begin(), tasks.end(), cmp_task_info);
|
||||
}
|
||||
|
||||
/* Logging functions */
|
||||
void log_console_running_tasks_info(std::vector<struct task_info> 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);
|
||||
}
|
|
@ -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)
|
|
@ -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 <deque>
|
||||
#include <fcntl.h>
|
||||
#include <random>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
//#include <private/android_logger.h>
|
||||
|
||||
#include <storaged.h> // data structures
|
||||
#include <storaged_utils.h> // 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<uint32_t> nums) {
|
||||
double sum = 0.0;
|
||||
for (uint32_t i : nums) {
|
||||
sum += i;
|
||||
}
|
||||
return sum / nums.size();
|
||||
}
|
||||
|
||||
static double standard_deviation(std::deque<uint32_t> 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<uint32_t> 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<uint32_t> 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<uint32_t> 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<uint32_t> find_overlap(std::unordered_map<uint32_t, struct task_info> t1,
|
||||
std::unordered_map<uint32_t, struct task_info> t2) {
|
||||
std::set<uint32_t> retval;
|
||||
for (auto i : t1) {
|
||||
if (t2.find(i.first) != t2.end()) {
|
||||
retval.insert(i.first);
|
||||
}
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static std::set<std::string> find_overlap(std::unordered_map<std::string, struct task_info> t1,
|
||||
std::unordered_map<std::string, struct task_info> t2) {
|
||||
std::set<std::string> 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<struct task_info> v1, std::vector<struct task_info> 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<struct task_info>
|
||||
categorize_tasks(std::unordered_map<uint32_t, struct task_info> tasks) {
|
||||
std::unordered_map<std::string, struct task_info> 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<struct task_info> 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<uint32_t, struct task_info> prev_running = tasks.mRunning;
|
||||
std::unordered_map<std::string, struct task_info> prev_old = tasks.mOld;
|
||||
|
||||
// hashmap maintaining
|
||||
std::unordered_map<uint32_t, struct task_info> tasks_pid = tasks.mRunning;
|
||||
|
||||
// get_running_tasks() should return something other than a null map
|
||||
std::unordered_map<uint32_t, struct task_info> 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<uint32_t> overlap_running = find_overlap(prev_running, tasks.mRunning);
|
||||
std::set<std::string> 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<struct task_info> test_tasks = categorize_tasks(tasks_pid);
|
||||
std::vector<struct task_info> 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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue