summaryrefslogtreecommitdiff
path: root/boottime_tools/bootio/bootio_collector.cpp
diff options
context:
space:
mode:
authorWei Wang <wvw@google.com>2018-03-23 11:28:59 -0700
committerWei Wang <wvw@google.com>2018-03-23 11:28:59 -0700
commitbbf73e5412c44e643a7b751f24099cd3891adfc3 (patch)
tree7e4d0fa8f0ec93e8fbfc31833d2bddb7eef93d48 /boottime_tools/bootio/bootio_collector.cpp
parent56f68caff62f404cff5491b3e84e3d031781db32 (diff)
downloadextras-bbf73e5412c44e643a7b751f24099cd3891adfc3.tar.gz
Move boottime tools to system folder
Bug: 65481007 Test: Build Change-Id: I66b280ff4fe06b47084ce4279705582e3d4d0330
Diffstat (limited to 'boottime_tools/bootio/bootio_collector.cpp')
-rw-r--r--boottime_tools/bootio/bootio_collector.cpp381
1 files changed, 381 insertions, 0 deletions
diff --git a/boottime_tools/bootio/bootio_collector.cpp b/boottime_tools/bootio/bootio_collector.cpp
new file mode 100644
index 00000000..495a9aa4
--- /dev/null
+++ b/boottime_tools/bootio/bootio_collector.cpp
@@ -0,0 +1,381 @@
+/*
+ * 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 "bootio_collector.h"
+#include <android-base/logging.h>
+#include <android-base/file.h>
+#include <log/log.h>
+#include "protos.pb.h"
+#include "time.h"
+#include <unordered_map>
+#include <inttypes.h>
+#include <dirent.h>
+
+namespace android {
+
+#define CPU_STAT_FILE "/proc/stat"
+#define SAMPLES_FILE "/samples"
+#define PID_STAT_FILE "/proc/%d/stat"
+#define PID_CMDLINE_FILE "/proc/%d/cmdline"
+#define PID_IO_FILE "/proc/%d/io"
+#define PROC_DIR "/proc"
+
+static const int MAX_LINE = 256;
+
+#define die(...) { LOG(ERROR) << (__VA_ARGS__); exit(EXIT_FAILURE); }
+
+void PopulateCpu(CpuData& cpu) {
+ long unsigned utime, ntime, stime, itime;
+ long unsigned iowtime, irqtime, sirqtime;
+ FILE *file;
+ file = fopen(CPU_STAT_FILE, "r");
+ if (!file) die("Could not open /proc/stat.\n");
+ fscanf(file, "cpu %lu %lu %lu %lu %lu %lu %lu", &utime, &ntime, &stime,
+ &itime, &iowtime, &irqtime, &sirqtime);
+ fclose(file);
+ cpu.set_utime(utime);
+ cpu.set_ntime(ntime);
+ cpu.set_stime(stime);
+ cpu.set_itime(itime);
+ cpu.set_iowtime(iowtime);
+ cpu.set_irqtime(irqtime);
+ cpu.set_sirqtime(sirqtime);
+}
+
+void ClearPreviousResults(std::string path) {
+ std::string err;
+ if (!android::base::RemoveFileIfExists(path, &err)) {
+ LOG(ERROR) << "failed to remove the file " << path << " " << err;
+ return;
+ }
+}
+
+int ReadIo(char *filename, AppSample *sample) {
+ FILE *file;
+ char line[MAX_LINE];
+ unsigned int rchar, wchar, syscr, syscw, readbytes, writebytes;
+
+ file = fopen(filename, "r");
+ if (!file) return 1;
+ while (fgets(line, MAX_LINE, file)) {
+ sscanf(line, "rchar: %u", &rchar);
+ sscanf(line, "wchar: %u", &wchar);
+ sscanf(line, "syscr: %u", &syscr);
+ sscanf(line, "syscw: %u", &syscw);
+ sscanf(line, "read_bytes: %u", &readbytes);
+ sscanf(line, "write_bytes: %u", &writebytes);
+ }
+ fclose(file);
+ sample->set_rchar(rchar);
+ sample->set_wchar(wchar);
+ sample->set_syscr(syscr);
+ sample->set_syscw(syscw);
+ sample->set_readbytes(readbytes);
+ sample->set_writebytes(writebytes);
+ return 0;
+}
+
+int ReadStatForName(char *filename, AppData *app) {
+ FILE *file;
+ char buf[MAX_LINE], *open_paren, *close_paren;
+
+ file = fopen(filename, "r");
+ if (!file) return 1;
+ fgets(buf, MAX_LINE, file);
+ fclose(file);
+
+ /* Split at first '(' and last ')' to get process name. */
+ open_paren = strchr(buf, '(');
+ close_paren = strrchr(buf, ')');
+ if (!open_paren || !close_paren) return 1;
+
+ *open_paren = *close_paren = '\0';
+ if (!app->has_tname()) {
+ app->set_tname(open_paren + 1, close_paren - open_paren - 1);
+ }
+ return 0;
+}
+
+int ReadStat(char *filename, AppSample *sample) {
+ FILE *file;
+ char buf[MAX_LINE], *open_paren, *close_paren;
+
+ file = fopen(filename, "r");
+ if (!file) return 1;
+ fgets(buf, MAX_LINE, file);
+ fclose(file);
+
+ /* Split at first '(' and last ')' to get process name. */
+ open_paren = strchr(buf, '(');
+ close_paren = strrchr(buf, ')');
+ if (!open_paren || !close_paren) return 1;
+
+ uint64_t utime;
+ uint64_t stime;
+ uint64_t rss;
+
+ /* Scan rest of string. */
+ sscanf(close_paren + 1,
+ " %*c " "%*d %*d %*d %*d %*d %*d %*d %*d %*d %*d "
+ "%" PRIu64 /*SCNu64*/
+ "%" PRIu64 /*SCNu64*/ "%*d %*d %*d %*d %*d %*d %*d %*d "
+ "%" PRIu64 /*SCNu64*/ "%*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d",
+ &utime,
+ &stime,
+ &rss);
+ sample->set_utime(utime);
+ sample->set_stime(stime);
+ sample->set_rss(rss);
+
+ return 0;
+}
+
+int ReadCmdline(char *filename, AppData *app) {
+ FILE *file;
+ char line[MAX_LINE];
+
+ line[0] = '\0';
+ file = fopen(filename, "r");
+ if (!file) return 1;
+ fgets(line, MAX_LINE, file);
+ fclose(file);
+ if (strlen(line) > 0) {
+ app->set_name(line, strlen(line));
+ } else {
+ app->set_name("N/A");
+ }
+ return 0;
+};
+
+void ReadProcData(std::unordered_map<int, AppData*>& pidDataMap, DataContainer& dataContainer,
+ time_t currentTimeUtc, time_t currentUptime) {
+ DIR *procDir;
+ struct dirent *pidDir;
+ pid_t pid;
+ char filename[64];
+ procDir = opendir(PROC_DIR);
+ if (!procDir) die("Could not open /proc.\n");
+ while ((pidDir = readdir(procDir))) {
+ if (!isdigit(pidDir->d_name[0])) {
+ continue;
+ }
+ pid = atoi(pidDir->d_name);
+ AppData *data;
+
+ // TODO: in theory same pid can be shared for multiple processes,
+ // might need add extra check.
+ if (pidDataMap.count(pid) == 0) {
+ data = dataContainer.add_app();
+ data->set_pid(pid);
+ sprintf(filename, PID_STAT_FILE, pid);
+ ReadStatForName(filename, data);
+ sprintf(filename, PID_CMDLINE_FILE, pid);
+ ReadCmdline(filename, data);
+ pidDataMap[pid] = data;
+ } else {
+ data = pidDataMap[pid];
+ }
+ AppSample *sample = data->add_samples();
+ sample->set_timestamp(currentTimeUtc);
+ sample->set_uptime(currentUptime);
+
+ sprintf(filename, PID_STAT_FILE, pid);
+ ReadStat(filename, sample);
+
+ sprintf(filename, PID_IO_FILE, pid);
+ ReadIo(filename, sample);
+ }
+}
+
+uint64_t SumCpuValues(CpuData& cpu) {
+ return cpu.utime() + cpu.ntime() + cpu.stime() + cpu.itime() + cpu.iowtime() +
+ cpu.irqtime() + cpu.sirqtime();
+}
+
+time_t GetUptime() {
+ std::string uptime_str;
+ if (!android::base::ReadFileToString("/proc/uptime", &uptime_str)) {
+ LOG(ERROR) << "Failed to read /proc/uptime";
+ return -1;
+ }
+
+ // Cast intentionally rounds down.
+ return static_cast<time_t>(strtod(uptime_str.c_str(), NULL));
+}
+
+struct Stats {
+ int uptime;
+ float cpu;
+ uint64_t rbytes;
+ uint64_t wbytes;
+};
+
+void PrintPids(DataContainer& data, std::unordered_map<int, uint64_t>& cpuDataMap) {
+ printf("rchar: number of bytes the process read, using any read-like system call "
+ "(from files, pipes, tty...).\n");
+ printf("wchar: number of bytes the process wrote using any write-like system call.\n");
+ printf("wchar: number of bytes the process wrote using any write-like system call.\n");
+ printf("syscr: number of write-like system call invocations that the process performed.\n");
+ printf("rbytes: number of bytes the process directly read from disk.\n");
+ printf("wbytes: number of bytes the process originally dirtied in the page-cache "
+ "(assuming they will go to disk later).\n\n");
+
+ std::unique_ptr<AppSample> bootZeroSample(new AppSample());
+ std::map<int, Stats> statsMap;
+ // Init stats map
+ Stats emptyStat {0, 0., 0, 0};
+ for (auto it = cpuDataMap.begin(); it != cpuDataMap.end(); it++) {
+ statsMap[it->first] = emptyStat;
+ }
+ for (int i = 0; i < data.app_size(); i++) {
+ const AppData appData = data.app(i);
+ printf("\n-----------------------------------------------------------------------------\n");
+ printf("PID:\t%u\n", appData.pid());
+ printf("Name:\t%s\n", appData.name().c_str());
+ printf("ThName:\t%s\n", appData.tname().c_str());
+ printf("%-15s%-13s%-13s%-13s%-13s%-13s%-13s%-13s\n", "Uptime inter.", "rchar", "wchar",
+ "syscr", "syscw", "rbytes", "wbytes", "cpu%");
+ const AppSample *olderSample = NULL;
+ const AppSample *newerSample = NULL;
+ bool isFirstSample = true;
+ for (int j = 0; j < appData.samples_size(); j++) {
+ olderSample = newerSample;
+ newerSample = &(appData.samples(j));
+ if (olderSample == NULL) {
+ olderSample = bootZeroSample.get();
+ }
+ float cpuLoad = 0.;
+ uint64_t cpuDelta;
+ if (isFirstSample) {
+ cpuDelta = cpuDataMap[newerSample->timestamp()];
+ } else {
+ cpuDelta = cpuDataMap[newerSample->timestamp()] -
+ cpuDataMap[olderSample->timestamp()];
+ }
+ if (cpuDelta != 0) {
+ cpuLoad = (newerSample->utime() - olderSample->utime() +
+ newerSample->stime() - olderSample->stime()) * 100. / cpuDelta;
+ }
+ Stats& stats = statsMap[newerSample->timestamp()];
+ stats.uptime = newerSample->uptime();
+ stats.cpu += cpuLoad;
+ stats.rbytes += (newerSample->readbytes() - olderSample->readbytes());
+ stats.wbytes += (newerSample->writebytes() - olderSample->writebytes());
+
+ // Note that all of these are explicitly `long long`s, not int64_t,
+ // so we can't use PRId64 here.
+#define NUMBER "%-13lld"
+ printf("%5lld - %-5lld " NUMBER NUMBER NUMBER NUMBER NUMBER NUMBER "%-9.2f\n",
+#undef NUMBER
+ olderSample->uptime(),
+ newerSample->uptime(),
+ newerSample->rchar() - olderSample->rchar(),
+ newerSample->wchar() - olderSample->wchar(),
+ newerSample->syscr() - olderSample->syscr(),
+ newerSample->syscw() - olderSample->syscw(),
+ newerSample->readbytes() - olderSample->readbytes(),
+ newerSample->writebytes() - olderSample->writebytes(),
+ cpuLoad);
+ isFirstSample = false;
+ }
+ printf("-----------------------------------------------------------------------------\n");
+#define NUMBER "%-13lld"
+ printf("%-15s" NUMBER NUMBER NUMBER NUMBER NUMBER NUMBER "\n",
+#undef NUMBER
+ "Total",
+ newerSample->rchar(),
+ newerSample->wchar(),
+ newerSample->syscr(),
+ newerSample->syscw(),
+ newerSample->readbytes(),
+ newerSample->writebytes());
+ }
+ printf("\nAggregations\n%-10s%-13s%-13s%-13s\n",
+ "Total",
+ "rbytes",
+ "wbytes",
+ "cpu%");
+
+ for (auto it = statsMap.begin(); it != statsMap.end(); it++) {
+ printf("%-10u%-13" PRIu64 "%-13" PRIu64 "%-9.2f\n",
+ it->second.uptime,
+ it->second.rbytes,
+ it->second.wbytes,
+ it->second.cpu);
+ }
+}
+
+}
+
+BootioCollector::BootioCollector(std::string path) {
+ DCHECK_EQ('/', path.back());
+ path_ = path;
+}
+
+void BootioCollector::StartDataCollection(int timeout, int samples) {
+ android::ClearPreviousResults(getStoragePath());
+ int remaining = samples + 1;
+ int delayS = timeout / samples;
+
+ std::unordered_map < int, AppData * > pidDataMap;
+ std::unique_ptr <DataContainer> data(new DataContainer());
+ while (remaining > 0) {
+ time_t currentTimeUtc = time(nullptr);
+ time_t currentUptime = android::GetUptime();
+ CpuData *cpu = data->add_cpu();
+ cpu->set_timestamp(currentTimeUtc);
+ cpu->set_uptime(currentUptime);
+ android::PopulateCpu(*cpu);
+ android::ReadProcData(pidDataMap, *data.get(), currentTimeUtc, currentUptime);
+ remaining--;
+ if (remaining == 0) {
+ continue;
+ }
+ sleep(delayS);
+ }
+ std::string file_data;
+ if (!data->SerializeToString(&file_data)) {
+ LOG(ERROR) << "Failed to serialize";
+ return;
+ }
+ if (!android::base::WriteStringToFile(file_data, getStoragePath())) {
+ LOG(ERROR) << "Failed to write samples";
+ }
+}
+
+void BootioCollector::Print() {
+ std::string file_data;
+ if (!android::base::ReadFileToString(getStoragePath(), &file_data)) {
+ printf("Failed to read data from file.\n");
+ return;
+ }
+ std::unique_ptr <DataContainer> data(new DataContainer());
+ if (!data->ParsePartialFromString(file_data)) {
+ printf("Failed to parse data.\n");
+ return;
+ }
+ std::unordered_map<int, uint64_t> cpuDataMap;
+ for (int i = 0; i < data->cpu_size(); i++) {
+ CpuData cpu_data = data->cpu(i);
+ cpuDataMap[cpu_data.timestamp()] = android::SumCpuValues(cpu_data);
+ }
+ android::PrintPids(*data.get(), cpuDataMap);
+}
+
+
+std::string BootioCollector::getStoragePath() {
+ return path_ + SAMPLES_FILE;
+}