Parse process starttime.
Application developers would like to know how long their process has
been alive for to distinguish between crashes that happen immediately
upon startup and crashes in regular operation.
Test: treehugger
Change-Id: I71d6fea95666b53770cebf919f4c881b142beddf
diff --git a/include/procinfo/process.h b/include/procinfo/process.h
index 9278e18..ee245e4 100644
--- a/include/procinfo/process.h
+++ b/include/procinfo/process.h
@@ -26,6 +26,7 @@
#include <string>
#include <type_traits>
+#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <android-base/unique_fd.h>
@@ -53,6 +54,9 @@
pid_t tracer;
uid_t uid;
uid_t gid;
+
+ // Start time of the process since boot, measured in clock ticks.
+ uint64_t starttime;
};
// Parse the contents of /proc/<tid>/status into |process_info|.
diff --git a/process.cpp b/process.cpp
index 2efd49c..4831ae8 100644
--- a/process.cpp
+++ b/process.cpp
@@ -46,8 +46,8 @@
return GetProcessInfoFromProcPidFd(dirfd.get(), process_info, error);
}
-static ProcessState parse_state(const char* state) {
- switch (*state) {
+static ProcessState parse_state(char state) {
+ switch (state) {
case 'R':
return kProcessStateRunning;
case 'S':
@@ -83,7 +83,7 @@
}
int field_bitmap = 0;
- static constexpr int finished_bitmap = 255;
+ static constexpr int finished_bitmap = 63;
char* line = nullptr;
size_t len = 0;
@@ -103,32 +103,81 @@
process_info->name = std::move(name);
field_bitmap |= 1;
+ } else if (header == "Tgid:") {
+ process_info->pid = atoi(tab + 1);
+ field_bitmap |= 2;
} else if (header == "Pid:") {
process_info->tid = atoi(tab + 1);
- field_bitmap |= 2;
- } else if (header == "Tgid:") {
- process_info->pid = atoi(tab + 1);
field_bitmap |= 4;
- } else if (header == "PPid:") {
- process_info->ppid = atoi(tab + 1);
- field_bitmap |= 8;
} else if (header == "TracerPid:") {
process_info->tracer = atoi(tab + 1);
- field_bitmap |= 16;
+ field_bitmap |= 8;
} else if (header == "Uid:") {
process_info->uid = atoi(tab + 1);
- field_bitmap |= 32;
+ field_bitmap |= 16;
} else if (header == "Gid:") {
process_info->gid = atoi(tab + 1);
- field_bitmap |= 64;
- } else if (header == "State:") {
- process_info->state = parse_state(tab + 1);
- field_bitmap |= 128;
+ field_bitmap |= 32;
}
}
free(line);
- return field_bitmap == finished_bitmap;
+ if (field_bitmap != finished_bitmap) {
+ *error = "failed to parse /proc/<pid>/status";
+ return false;
+ }
+
+ unique_fd stat_fd(openat(fd, "stat", O_RDONLY | O_CLOEXEC));
+ if (stat_fd == -1) {
+ *error = "failed to open /proc/<pid>/stat";
+ }
+
+ std::string stat;
+ if (!android::base::ReadFdToString(stat_fd, &stat)) {
+ *error = "failed to read /proc/<pid>/stat";
+ return false;
+ }
+
+ // See man 5 proc. There's no reason comm can't contain ' ' or ')',
+ // so we search backwards for the end of it.
+ const char* end_of_comm = strrchr(stat.c_str(), ')');
+
+ static constexpr const char* pattern =
+ "%c " // state
+ "%d " // ppid
+ "%*d " // pgrp
+ "%*d " // session
+ "%*d " // tty_nr
+ "%*d " // tpgid
+ "%*u " // flags
+ "%*lu " // minflt
+ "%*lu " // cminflt
+ "%*lu " // majflt
+ "%*lu " // cmajflt
+ "%*lu " // utime
+ "%*lu " // stime
+ "%*ld " // cutime
+ "%*ld " // cstime
+ "%*ld " // priority
+ "%*ld " // nice
+ "%*ld " // num_threads
+ "%*ld " // itrealvalue
+ "%llu " // starttime
+ ;
+
+ char state = '\0';
+ int ppid = 0;
+ unsigned long long start_time = 0;
+ int rc = sscanf(end_of_comm + 2, pattern, &state, &ppid, &start_time);
+ if (rc != 3) {
+ *error = "failed to parse /proc/<pid>/stat";
+ return false;
+ }
+
+ process_info->state = parse_state(state);
+ process_info->ppid = ppid;
+ process_info->starttime = start_time;
+ return true;
}
} /* namespace procinfo */
diff --git a/process_test.cpp b/process_test.cpp
index 9da9278..be00a17 100644
--- a/process_test.cpp
+++ b/process_test.cpp
@@ -28,6 +28,7 @@
#include <gtest/gtest.h>
+#include <android-base/logging.h>
#include <android-base/stringprintf.h>
using namespace std::chrono_literals;
@@ -116,3 +117,44 @@
ASSERT_EQ(forkpid, waitpid(forkpid, nullptr, 0));
}
+
+static uint64_t read_uptime_secs() {
+ std::string uptime;
+ if (!android::base::ReadFileToString("/proc/uptime", &uptime)) {
+ PLOG(FATAL) << "failed to read /proc/uptime";
+ }
+ return strtoll(uptime.c_str(), nullptr, 10);
+}
+
+TEST(process_info, process_start_time) {
+ uint64_t start = read_uptime_secs();
+ int pipefd[2];
+ ASSERT_EQ(0, pipe2(pipefd, O_CLOEXEC));
+
+ std::this_thread::sleep_for(1000ms);
+
+ pid_t forkpid = fork();
+
+ ASSERT_NE(-1, forkpid);
+ if (forkpid == 0) {
+ close(pipefd[1]);
+ char buf;
+ TEMP_FAILURE_RETRY(read(pipefd[0], &buf, 1));
+ _exit(0);
+ }
+
+ std::this_thread::sleep_for(1000ms);
+
+ uint64_t end = read_uptime_secs();
+
+ android::procinfo::ProcessInfo procinfo;
+ ASSERT_TRUE(android::procinfo::GetProcessInfo(forkpid, &procinfo));
+
+ // starttime is measured in clock ticks: uptime is in seconds:
+ uint64_t process_start = procinfo.starttime / sysconf(_SC_CLK_TCK);
+ ASSERT_LE(start, process_start);
+ ASSERT_LE(process_start, end);
+
+ ASSERT_EQ(0, kill(forkpid, SIGKILL));
+ ASSERT_EQ(forkpid, waitpid(forkpid, nullptr, 0));
+}