diff options
author | Kweku Adams <kwekua@google.com> | 2017-12-20 17:59:17 -0800 |
---|---|---|
committer | Kweku Adams <kwekua@google.com> | 2018-01-02 15:49:23 -0800 |
commit | f5cc5759d55f803cd230c7a595e89e634c3c36ee (patch) | |
tree | 7632d267b45d11c144fc54e411f2151e71671176 | |
parent | b944bc86078146c523d58d2b70d56044be3bf216 (diff) |
incidentd: parsing ps dump into proto.
Also changing from execv to execvp so that we don't have to specify the full command path.
Bug: 65750831
Test: atest incident_helper_test
Change-Id: I92191afff4e7f9a6d08ea22ecfc2de5623d3bde5
-rw-r--r-- | Android.bp | 1 | ||||
-rw-r--r-- | cmds/incident_helper/src/ih_util.cpp | 75 | ||||
-rw-r--r-- | cmds/incident_helper/src/ih_util.h | 11 | ||||
-rw-r--r-- | cmds/incident_helper/src/main.cpp | 3 | ||||
-rw-r--r-- | cmds/incident_helper/src/parsers/CpuInfoParser.cpp | 29 | ||||
-rw-r--r-- | cmds/incident_helper/src/parsers/KernelWakesParser.cpp | 6 | ||||
-rw-r--r-- | cmds/incident_helper/src/parsers/PsParser.cpp | 95 | ||||
-rw-r--r-- | cmds/incident_helper/src/parsers/PsParser.h | 33 | ||||
-rw-r--r-- | cmds/incident_helper/testdata/ps.txt | 9 | ||||
-rw-r--r-- | cmds/incident_helper/tests/PsParser_test.cpp | 300 | ||||
-rw-r--r-- | cmds/incident_helper/tests/ih_util_test.cpp | 24 | ||||
-rw-r--r-- | cmds/incidentd/src/Section.cpp | 2 | ||||
-rw-r--r-- | core/proto/android/os/cpuinfo.proto | 4 | ||||
-rw-r--r-- | core/proto/android/os/incident.proto | 10 | ||||
-rw-r--r-- | core/proto/android/os/ps.proto | 110 |
15 files changed, 686 insertions, 26 deletions
diff --git a/Android.bp b/Android.bp index 00f42d237936..69ee848cadad 100644 --- a/Android.bp +++ b/Android.bp @@ -728,6 +728,7 @@ gensrcs { "core/proto/android/os/kernelwake.proto", "core/proto/android/os/pagetypeinfo.proto", "core/proto/android/os/procrank.proto", + "core/proto/android/os/ps.proto", "core/proto/android/os/system_properties.proto", ], diff --git a/cmds/incident_helper/src/ih_util.cpp b/cmds/incident_helper/src/ih_util.cpp index 4bf956a9a03d..e23e80ae21e8 100644 --- a/cmds/incident_helper/src/ih_util.cpp +++ b/cmds/incident_helper/src/ih_util.cpp @@ -52,6 +52,12 @@ static inline std::string trimHeader(const std::string& s) { return toLowerStr(trimDefault(s)); } +static inline bool isNumber(const std::string& s) { + std::string::const_iterator it = s.begin(); + while (it != s.end() && std::isdigit(*it)) ++it; + return !s.empty() && it == s.end(); +} + // This is similiar to Split in android-base/file.h, but it won't add empty string static void split(const std::string& line, std::vector<std::string>& words, const trans_func& func, const std::string& delimiters) { @@ -86,24 +92,80 @@ record_t parseRecord(const std::string& line, const std::string& delimiters) { return record; } +bool getColumnIndices(std::vector<int>& indices, const char** headerNames, const std::string& line) { + indices.clear(); + + size_t lastIndex = 0; + int i = 0; + while (headerNames[i] != NULL) { + string s = headerNames[i]; + lastIndex = line.find(s, lastIndex); + if (lastIndex == string::npos) { + fprintf(stderr, "Bad Task Header: %s\n", line.c_str()); + return false; + } + lastIndex += s.length(); + indices.push_back(lastIndex); + i++; + } + + return true; +} + record_t parseRecordByColumns(const std::string& line, const std::vector<int>& indices, const std::string& delimiters) { record_t record; int lastIndex = 0; + int lastBeginning = 0; int lineSize = (int)line.size(); for (std::vector<int>::const_iterator it = indices.begin(); it != indices.end(); ++it) { int idx = *it; - if (lastIndex > idx || idx > lineSize) { - record.clear(); // The indices is wrong, return empty; + if (idx <= lastIndex) { + // We saved up until lastIndex last time, so we should start at + // lastIndex + 1 this time. + idx = lastIndex + 1; + } + if (idx > lineSize) { + if (lastIndex < idx && lastIndex < lineSize) { + // There's a little bit more for us to save, which we'll do + // outside of the loop. + break; + } + // If we're past the end of the line AND we've already saved everything up to the end. + fprintf(stderr, "index wrong: lastIndex: %d, idx: %d, lineSize: %d\n", lastIndex, idx, lineSize); + record.clear(); // The indices are wrong, return empty. return record; } while (idx < lineSize && delimiters.find(line[idx++]) == std::string::npos); record.push_back(trimDefault(line.substr(lastIndex, idx - lastIndex))); + lastBeginning = lastIndex; lastIndex = idx; } - record.push_back(trimDefault(line.substr(lastIndex, lineSize - lastIndex))); + if (lineSize - lastIndex > 0) { + int beginning = lastIndex; + if (record.size() == indices.size()) { + // We've already encountered all of the columns...put whatever is + // left in the last column. + record.pop_back(); + beginning = lastBeginning; + } + record.push_back(trimDefault(line.substr(beginning, lineSize - beginning))); + } return record; } +void printRecord(const record_t& record) { + fprintf(stderr, "Record: { "); + if (record.size() == 0) { + fprintf(stderr, "}\n"); + return; + } + for(size_t i = 0; i < record.size(); ++i) { + if(i != 0) fprintf(stderr, "\", "); + fprintf(stderr, "\"%s", record[i].c_str()); + } + fprintf(stderr, "\" }\n"); +} + bool stripPrefix(std::string* line, const char* key, bool endAtDelimiter) { const auto head = line->find_first_not_of(DEFAULT_WHITESPACE); if (head == std::string::npos) return false; @@ -210,7 +272,10 @@ Table::~Table() void Table::addEnumTypeMap(const char* field, const char* enumNames[], const int enumValues[], const int enumSize) { - if (mFields.find(field) == mFields.end()) return; + if (mFields.find(field) == mFields.end()) { + fprintf(stderr, "Field '%s' not found", string(field).c_str()); + return; + } map<std::string, int> enu; for (int i = 0; i < enumSize; i++) { @@ -268,6 +333,8 @@ Table::insertField(ProtoOutputStream* proto, const std::string& name, const std: } } else if (mEnumValuesByName.find(value) != mEnumValuesByName.end()) { proto->write(found, mEnumValuesByName[value]); + } else if (isNumber(value)) { + proto->write(found, toInt(value)); } else { return false; } diff --git a/cmds/incident_helper/src/ih_util.h b/cmds/incident_helper/src/ih_util.h index 58ef29044048..b063b2fe0bba 100644 --- a/cmds/incident_helper/src/ih_util.h +++ b/cmds/incident_helper/src/ih_util.h @@ -56,12 +56,23 @@ header_t parseHeader(const std::string& line, const std::string& delimiters = DE record_t parseRecord(const std::string& line, const std::string& delimiters = DEFAULT_WHITESPACE); /** + * Gets the list of end indices of each word in the line and places it in the given vector, + * clearing out the vector beforehand. These indices can be used with parseRecordByColumns. + * Will return false if there was a problem getting the indices. headerNames + * must be NULL terminated. + */ +bool getColumnIndices(std::vector<int>& indices, const char* headerNames[], const std::string& line); + +/** * When a text-format table aligns by its vertical position, it is not possible to split them by purely delimiters. * This function allows to parse record by its header's column position' indices, must in ascending order. * At the same time, it still looks at the char at index, if it doesn't belong to delimiters, moves forward to find the delimiters. */ record_t parseRecordByColumns(const std::string& line, const std::vector<int>& indices, const std::string& delimiters = DEFAULT_WHITESPACE); +/** Prints record_t to stderr */ +void printRecord(const record_t& record); + /** * When the line starts/ends with the given key, the function returns true * as well as the line argument is changed to the rest trimmed part of the original. diff --git a/cmds/incident_helper/src/main.cpp b/cmds/incident_helper/src/main.cpp index ab92473b8ba3..8c6cd78d3bf2 100644 --- a/cmds/incident_helper/src/main.cpp +++ b/cmds/incident_helper/src/main.cpp @@ -22,6 +22,7 @@ #include "parsers/KernelWakesParser.h" #include "parsers/PageTypeInfoParser.h" #include "parsers/ProcrankParser.h" +#include "parsers/PsParser.h" #include "parsers/SystemPropertiesParser.h" #include <android-base/file.h> @@ -64,6 +65,8 @@ static TextParserBase* selectParser(int section) { return new CpuInfoParser(); case 2004: return new CpuFreqParser(); + case 2005: + return new PsParser(); case 2006: return new BatteryTypeParser(); default: diff --git a/cmds/incident_helper/src/parsers/CpuInfoParser.cpp b/cmds/incident_helper/src/parsers/CpuInfoParser.cpp index 3faca00c1b88..d73de54d8c5d 100644 --- a/cmds/incident_helper/src/parsers/CpuInfoParser.cpp +++ b/cmds/incident_helper/src/parsers/CpuInfoParser.cpp @@ -49,6 +49,7 @@ CpuInfoParser::Parse(const int in, const int out) const vector<int> columnIndices; // task table can't be split by purely delimiter, needs column positions. record_t record; int nline = 0; + int diff = 0; bool nextToSwap = false; bool nextToUsage = false; @@ -107,18 +108,10 @@ CpuInfoParser::Parse(const int in, const int out) const header = parseHeader(line, "[ %]"); nextToUsage = false; - // NAME is not in the list since the last split index is default to the end of line. - const char* headerNames[11] = { "PID", "TID", "USER", "PR", "NI", "CPU", "S", "VIRT", "RES", "PCY", "CMD" }; - size_t lastIndex = 0; - for (int i = 0; i < 11; i++) { - string s = headerNames[i]; - lastIndex = line.find(s, lastIndex); - if (lastIndex == string::npos) { - fprintf(stderr, "Bad Task Header: %s\n", line.c_str()); - return -1; - } - lastIndex += s.length(); - columnIndices.push_back(lastIndex); + // NAME is not in the list since we need to modify the end of the CMD index. + const char* headerNames[] = { "PID", "TID", "USER", "PR", "NI", "CPU", "S", "VIRT", "RES", "PCY", "CMD", NULL }; + if (!getColumnIndices(columnIndices, headerNames, line)) { + return -1; } // Need to remove the end index of CMD and use the start index of NAME because CMD values contain spaces. // for example: ... CMD NAME @@ -128,12 +121,20 @@ CpuInfoParser::Parse(const int in, const int out) const int endCMD = columnIndices.back(); columnIndices.pop_back(); columnIndices.push_back(line.find("NAME", endCMD) - 1); + // Add NAME index to complete the column list. + columnIndices.push_back(columnIndices.back() + 4); continue; } record = parseRecordByColumns(line, columnIndices); - if (record.size() != header.size()) { - fprintf(stderr, "[%s]Line %d has missing fields:\n%s\n", this->name.string(), nline, line.c_str()); + diff = record.size() - header.size(); + if (diff < 0) { + fprintf(stderr, "[%s]Line %d has %d missing fields\n%s\n", this->name.string(), nline, -diff, line.c_str()); + printRecord(record); + continue; + } else if (diff > 0) { + fprintf(stderr, "[%s]Line %d has %d extra fields\n%s\n", this->name.string(), nline, diff, line.c_str()); + printRecord(record); continue; } diff --git a/cmds/incident_helper/src/parsers/KernelWakesParser.cpp b/cmds/incident_helper/src/parsers/KernelWakesParser.cpp index ada4a5d0ffe2..cae51abbe57f 100644 --- a/cmds/incident_helper/src/parsers/KernelWakesParser.cpp +++ b/cmds/incident_helper/src/parsers/KernelWakesParser.cpp @@ -47,10 +47,14 @@ KernelWakesParser::Parse(const int in, const int out) const // parse for each record, the line delimiter is \t only! record = parseRecord(line, TAB_DELIMITER); - if (record.size() != header.size()) { + if (record.size() < header.size()) { // TODO: log this to incident report! fprintf(stderr, "[%s]Line %d has missing fields\n%s\n", this->name.string(), nline, line.c_str()); continue; + } else if (record.size() > header.size()) { + // TODO: log this to incident report! + fprintf(stderr, "[%s]Line %d has extra fields\n%s\n", this->name.string(), nline, line.c_str()); + continue; } long long token = proto.start(KernelWakeSources::WAKEUP_SOURCES); diff --git a/cmds/incident_helper/src/parsers/PsParser.cpp b/cmds/incident_helper/src/parsers/PsParser.cpp new file mode 100644 index 000000000000..e9014cacfa0b --- /dev/null +++ b/cmds/incident_helper/src/parsers/PsParser.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2017 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 "incident_helper" + +#include <android/util/ProtoOutputStream.h> + +#include "frameworks/base/core/proto/android/os/ps.proto.h" +#include "ih_util.h" +#include "PsParser.h" + +using namespace android::os; + +status_t PsParser::Parse(const int in, const int out) const { + Reader reader(in); + string line; + header_t header; // the header of /d/wakeup_sources + vector<int> columnIndices; // task table can't be split by purely delimiter, needs column positions. + record_t record; // retain each record + int nline = 0; + int diff = 0; + + ProtoOutputStream proto; + Table table(PsDumpProto::Process::_FIELD_NAMES, PsDumpProto::Process::_FIELD_IDS, PsDumpProto::Process::_FIELD_COUNT); + const char* pcyNames[] = { "fg", "bg", "ta" }; + const int pcyValues[] = {PsDumpProto::Process::POLICY_FG, PsDumpProto::Process::POLICY_BG, PsDumpProto::Process::POLICY_TA}; + table.addEnumTypeMap("pcy", pcyNames, pcyValues, 3); + const char* sNames[] = { "D", "R", "S", "T", "t", "X", "Z" }; + const int sValues[] = {PsDumpProto::Process::STATE_D, PsDumpProto::Process::STATE_R, PsDumpProto::Process::STATE_S, PsDumpProto::Process::STATE_T, PsDumpProto::Process::STATE_TRACING, PsDumpProto::Process::STATE_X, PsDumpProto::Process::STATE_Z}; + table.addEnumTypeMap("s", sNames, sValues, 7); + + // Parse line by line + while (reader.readLine(&line)) { + if (line.empty()) continue; + + if (nline++ == 0) { + header = parseHeader(line, DEFAULT_WHITESPACE); + + const char* headerNames[] = { "LABEL", "USER", "PID", "TID", "PPID", "VSZ", "RSS", "WCHAN", "ADDR", "S", "PRI", "NI", "RTPRIO", "SCH", "PCY", "TIME", "CMD", NULL }; + if (!getColumnIndices(columnIndices, headerNames, line)) { + return -1; + } + + continue; + } + + record = parseRecordByColumns(line, columnIndices); + + diff = record.size() - header.size(); + if (diff < 0) { + // TODO: log this to incident report! + fprintf(stderr, "[%s]Line %d has %d missing fields\n%s\n", this->name.string(), nline, -diff, line.c_str()); + printRecord(record); + continue; + } else if (diff > 0) { + // TODO: log this to incident report! + fprintf(stderr, "[%s]Line %d has %d extra fields\n%s\n", this->name.string(), nline, diff, line.c_str()); + printRecord(record); + continue; + } + + long long token = proto.start(PsDumpProto::PROCESSES); + for (int i=0; i<(int)record.size(); i++) { + if (!table.insertField(&proto, header[i], record[i])) { + fprintf(stderr, "[%s]Line %d has bad value %s of %s\n", + this->name.string(), nline, header[i].c_str(), record[i].c_str()); + } + } + proto.end(token); + } + + if (!reader.ok(&line)) { + fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str()); + return -1; + } + + if (!proto.flush(out)) { + fprintf(stderr, "[%s]Error writing proto back\n", this->name.string()); + return -1; + } + fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size()); + return NO_ERROR; +} diff --git a/cmds/incident_helper/src/parsers/PsParser.h b/cmds/incident_helper/src/parsers/PsParser.h new file mode 100644 index 000000000000..9488e40e88fe --- /dev/null +++ b/cmds/incident_helper/src/parsers/PsParser.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2017 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 PS_PARSER_H +#define PS_PARSER_H + +#include "TextParserBase.h" + +/** + * PS parser, parses output of 'ps' command to protobuf. + */ +class PsParser : public TextParserBase { +public: + PsParser() : TextParserBase(String8("Ps")) {}; + ~PsParser() {}; + + virtual status_t Parse(const int in, const int out) const; +}; + +#endif // PS_PARSER_H diff --git a/cmds/incident_helper/testdata/ps.txt b/cmds/incident_helper/testdata/ps.txt new file mode 100644 index 000000000000..72dafc2c4378 --- /dev/null +++ b/cmds/incident_helper/testdata/ps.txt @@ -0,0 +1,9 @@ +LABEL USER PID TID PPID VSZ RSS WCHAN ADDR S PRI NI RTPRIO SCH PCY TIME CMD +u:r:init:s0 root 1 1 0 15816 2636 SyS_epoll_wait 0 S 19 0 - 0 fg 00:00:01 init +u:r:kernel:s0 root 2 2 0 0 0 kthreadd 0 S 19 0 - 0 fg 00:00:00 kthreadd +u:r:surfaceflinger:s0 system 499 534 1 73940 22024 futex_wait_queue_me 0 S 42 -9 2 1 fg 00:00:00 EventThread +u:r:hal_gnss_default:s0 gps 670 2004 1 43064 7272 poll_schedule_timeout 0 S 19 0 - 0 fg 00:00:00 Loc_hal_worker +u:r:platform_app:s0:c512,c768 u0_a48 1660 1976 806 4468612 138328 binder_thread_read 0 S 35 -16 - 0 ta 00:00:00 HwBinder:1660_1 +u:r:perfd:s0 root 1939 1946 1 18132 2088 __skb_recv_datagram 7b9782fd14 S 19 0 - 0 00:00:00 perfd +u:r:perfd:s0 root 1939 1955 1 18132 2088 do_sigtimedwait 7b9782ff6c S 19 0 - 0 00:00:00 POSIX timer 0 +u:r:shell:s0 shell 2645 2645 802 11664 2972 0 7f67a2f8b4 R 19 0 - 0 fg 00:00:00 ps diff --git a/cmds/incident_helper/tests/PsParser_test.cpp b/cmds/incident_helper/tests/PsParser_test.cpp new file mode 100644 index 000000000000..1f03a7f3a332 --- /dev/null +++ b/cmds/incident_helper/tests/PsParser_test.cpp @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2017 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 "PsParser.h" + +#include "frameworks/base/core/proto/android/os/ps.pb.h" + +#include <android-base/file.h> +#include <android-base/test_utils.h> +#include <gmock/gmock.h> +#include <google/protobuf/message_lite.h> +#include <gtest/gtest.h> +#include <string.h> +#include <fcntl.h> + +using namespace android::base; +using namespace android::os; +using namespace std; +using ::testing::StrEq; +using ::testing::Test; +using ::testing::internal::CaptureStderr; +using ::testing::internal::CaptureStdout; +using ::testing::internal::GetCapturedStderr; +using ::testing::internal::GetCapturedStdout; + +class PsParserTest : public Test { +public: + virtual void SetUp() override { + ASSERT_TRUE(tf.fd != -1); + } + +protected: + TemporaryFile tf; + + const string kTestPath = GetExecutableDirectory(); + const string kTestDataPath = kTestPath + "/testdata/"; +}; + +TEST_F(PsParserTest, Normal) { + const string testFile = kTestDataPath + "ps.txt"; + PsParser parser; + PsDumpProto expected; + PsDumpProto got; + + PsDumpProto::Process* record1 = expected.add_processes(); + record1->set_label("u:r:init:s0"); + record1->set_user("root"); + record1->set_pid(1); + record1->set_tid(1); + record1->set_ppid(0); + record1->set_vsz(15816); + record1->set_rss(2636); + record1->set_wchan("SyS_epoll_wait"); + record1->set_addr("0"); + record1->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S); + record1->set_pri(19); + record1->set_ni(0); + record1->set_rtprio("-"); + record1->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL); + record1->set_pcy(PsDumpProto::Process::POLICY_FG); + record1->set_time("00:00:01"); + record1->set_cmd("init"); + + PsDumpProto::Process* record2 = expected.add_processes(); + record2->set_label("u:r:kernel:s0"); + record2->set_user("root"); + record2->set_pid(2); + record2->set_tid(2); + record2->set_ppid(0); + record2->set_vsz(0); + record2->set_rss(0); + record2->set_wchan("kthreadd"); + record2->set_addr("0"); + record2->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S); + record2->set_pri(19); + record2->set_ni(0); + record2->set_rtprio("-"); + record2->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL); + record2->set_pcy(PsDumpProto::Process::POLICY_FG); + record2->set_time("00:00:00"); + record2->set_cmd("kthreadd"); + + PsDumpProto::Process* record3 = expected.add_processes(); + record3->set_label("u:r:surfaceflinger:s0"); + record3->set_user("system"); + record3->set_pid(499); + record3->set_tid(534); + record3->set_ppid(1); + record3->set_vsz(73940); + record3->set_rss(22024); + record3->set_wchan("futex_wait_queue_me"); + record3->set_addr("0"); + record3->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S); + record3->set_pri(42); + record3->set_ni(-9); + record3->set_rtprio("2"); + record3->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_FIFO); + record3->set_pcy(PsDumpProto::Process::POLICY_FG); + record3->set_time("00:00:00"); + record3->set_cmd("EventThread"); + + PsDumpProto::Process* record4 = expected.add_processes(); + record4->set_label("u:r:hal_gnss_default:s0"); + record4->set_user("gps"); + record4->set_pid(670); + record4->set_tid(2004); + record4->set_ppid(1); + record4->set_vsz(43064); + record4->set_rss(7272); + record4->set_wchan("poll_schedule_timeout"); + record4->set_addr("0"); + record4->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S); + record4->set_pri(19); + record4->set_ni(0); + record4->set_rtprio("-"); + record4->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL); + record4->set_pcy(PsDumpProto::Process::POLICY_FG); + record4->set_time("00:00:00"); + record4->set_cmd("Loc_hal_worker"); + + PsDumpProto::Process* record5 = expected.add_processes(); + record5->set_label("u:r:platform_app:s0:c512,c768"); + record5->set_user("u0_a48"); + record5->set_pid(1660); + record5->set_tid(1976); + record5->set_ppid(806); + record5->set_vsz(4468612); + record5->set_rss(138328); + record5->set_wchan("binder_thread_read"); + record5->set_addr("0"); + record5->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S); + record5->set_pri(35); + record5->set_ni(-16); + record5->set_rtprio("-"); + record5->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL); + record5->set_pcy(PsDumpProto::Process::POLICY_TA); + record5->set_time("00:00:00"); + record5->set_cmd("HwBinder:1660_1"); + + PsDumpProto::Process* record6 = expected.add_processes(); + record6->set_label("u:r:perfd:s0"); + record6->set_user("root"); + record6->set_pid(1939); + record6->set_tid(1946); + record6->set_ppid(1); + record6->set_vsz(18132); + record6->set_rss(2088); + record6->set_wchan("__skb_recv_datagram"); + record6->set_addr("7b9782fd14"); + record6->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S); + record6->set_pri(19); + record6->set_ni(0); + record6->set_rtprio("-"); + record6->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL); + record6->set_pcy(PsDumpProto::Process::POLICY_UNKNOWN); + record6->set_time("00:00:00"); + record6->set_cmd("perfd"); + + PsDumpProto::Process* record7 = expected.add_processes(); + record7->set_label("u:r:perfd:s0"); + record7->set_user("root"); + record7->set_pid(1939); + record7->set_tid(1955); + record7->set_ppid(1); + record7->set_vsz(18132); + record7->set_rss(2088); + record7->set_wchan("do_sigtimedwait"); + record7->set_addr("7b9782ff6c"); + record7->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S); + record7->set_pri(19); + record7->set_ni(0); + record7->set_rtprio("-"); + record7->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL); + record7->set_pcy(PsDumpProto::Process::POLICY_UNKNOWN); + record7->set_time("00:00:00"); + record7->set_cmd("POSIX timer 0"); + + PsDumpProto::Process* record8 = expected.add_processes(); + record8->set_label("u:r:shell:s0"); + record8->set_user("shell"); + record8->set_pid(2645); + record8->set_tid(2645); + record8->set_ppid(802); + record8->set_vsz(11664); + record8->set_rss(2972); + record8->set_wchan("0"); + record8->set_addr("7f67a2f8b4"); + record8->set_s(PsDumpProto_Process_ProcessStateCode_STATE_R); + record8->set_pri(19); + record8->set_ni(0); + record8->set_rtprio("-"); + record8->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL); + record8->set_pcy(PsDumpProto::Process::POLICY_FG); + record8->set_time("00:00:00"); + record8->set_cmd("ps"); + + int fd = open(testFile.c_str(), O_RDONLY); + ASSERT_TRUE(fd != -1); + + CaptureStdout(); + ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO)); + got.ParseFromString(GetCapturedStdout()); + bool matches = true; + + if (got.processes_size() != expected.processes_size()) { + fprintf(stderr, "Got %d processes, want %d\n", got.processes_size(), expected.processes_size()); + matches = false; + } else { + int n = got.processes_size(); + for (int i = 0; i < n; i++) { + PsDumpProto::Process g = got.processes(i); + PsDumpProto::Process e = expected.processes(i); + + if (g.label() != e.label()) { + fprintf(stderr, "prcs[%d]: Invalid label. Got %s, want %s\n", i, g.label().c_str(), e.label().c_str()); + matches = false; + } + if (g.user() != e.user()) { + fprintf(stderr, "prcs[%d]: Invalid user. Got %s, want %s\n", i, g.user().c_str(), e.user().c_str()); + matches = false; + } + if (g.pid() != e.pid()) { + fprintf(stderr, "prcs[%d]: Invalid pid. Got %d, want %d\n", i, g.pid(), e.pid()); + matches = false; + } + if (g.tid() != e.tid()) { + fprintf(stderr, "prcs[%d]: Invalid tid. Got %d, want %d\n", i, g.tid(), e.tid()); + matches = false; + } + if (g.ppid() != e.ppid()) { + fprintf(stderr, "prcs[%d]: Invalid ppid. Got %d, want %d\n", i, g.ppid(), e.ppid()); + matches = false; + } + if (g.vsz() != e.vsz()) { + fprintf(stderr, "prcs[%d]: Invalid vsz. Got %d, want %d\n", i, g.vsz(), e.vsz()); + matches = false; + } + if (g.rss() != e.rss()) { + fprintf(stderr, "prcs[%d]: Invalid rss. Got %d, want %d\n", i, g.rss(), e.rss()); + matches = false; + } + if (g.wchan() != e.wchan()) { + fprintf(stderr, "prcs[%d]: Invalid wchan. Got %s, want %s\n", i, g.wchan().c_str(), e.wchan().c_str()); + matches = false; + } + if (g.addr() != e.addr()) { + fprintf(stderr, "prcs[%d]: Invalid addr. Got %s, want %s\n", i, g.addr().c_str(), e.addr().c_str()); + matches = false; + } + if (g.s() != e.s()) { + fprintf(stderr, "prcs[%d]: Invalid s. Got %u, want %u\n", i, g.s(), e.s()); + matches = false; + } + if (g.pri() != e.pri()) { + fprintf(stderr, "prcs[%d]: Invalid pri. Got %d, want %d\n", i, g.pri(), e.pri()); + matches = false; + } + if (g.ni() != e.ni()) { + fprintf(stderr, "prcs[%d]: Invalid ni. Got %d, want %d\n", i, g.ni(), e.ni()); + matches = false; + } + if (g.rtprio() != e.rtprio()) { + fprintf(stderr, "prcs[%d]: Invalid rtprio. Got %s, want %s\n", i, g.rtprio().c_str(), e.rtprio().c_str()); + matches = false; + } + if (g.sch() != e.sch()) { + fprintf(stderr, "prcs[%d]: Invalid sch. Got %u, want %u\n", i, g.sch(), e.sch()); + matches = false; + } + if (g.pcy() != e.pcy()) { + fprintf(stderr, "prcs[%d]: Invalid pcy. Got %u, want %u\n", i, g.pcy(), e.pcy()); + matches = false; + } + if (g.time() != e.time()) { + fprintf(stderr, "prcs[%d]: Invalid time. Got %s, want %s\n", i, g.time().c_str(), e.time().c_str()); + matches = false; + } + if (g.cmd() != e.cmd()) { + fprintf(stderr, "prcs[%d]: Invalid cmd. Got %s, want %s\n", i, g.cmd().c_str(), e.cmd().c_str()); + matches = false; + } + } + } + + EXPECT_TRUE(matches); + close(fd); +} diff --git a/cmds/incident_helper/tests/ih_util_test.cpp b/cmds/incident_helper/tests/ih_util_test.cpp index 5740b330d949..7b8cf52c8bee 100644 --- a/cmds/incident_helper/tests/ih_util_test.cpp +++ b/cmds/incident_helper/tests/ih_util_test.cpp @@ -71,11 +71,29 @@ TEST(IhUtilTest, ParseRecordByColumns) { EXPECT_EQ(expected, result); result = parseRecordByColumns("abc \t2345 6789 ", indices); - expected = { "abc", "2345", "6789" }; + expected = { "abc", "2345 6789" }; EXPECT_EQ(expected, result); - result = parseRecordByColumns("abc \t23456789 bob", indices); - expected = { "abc", "23456789", "bob" }; + std::string extraColumn1 = "abc \t23456789 bob"; + std::string emptyMidColm = "abc \t bob"; + std::string longFirstClm = "abcdefgt\t6789 bob"; + std::string lngFrstEmpty = "abcdefgt\t bob"; + + result = parseRecordByColumns(extraColumn1, indices); + expected = { "abc", "23456789 bob" }; + EXPECT_EQ(expected, result); + + // 2nd column should be treated as an empty entry. + result = parseRecordByColumns(emptyMidColm, indices); + expected = { "abc", "bob" }; + EXPECT_EQ(expected, result); + + result = parseRecordByColumns(longFirstClm, indices); + expected = { "abcdefgt", "6789 bob" }; + EXPECT_EQ(expected, result); + + result = parseRecordByColumns(lngFrstEmpty, indices); + expected = { "abcdefgt", "bob" }; EXPECT_EQ(expected, result); } diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp index 1bf795bb6557..22053ef3c53a 100644 --- a/cmds/incidentd/src/Section.cpp +++ b/cmds/incidentd/src/Section.cpp @@ -521,7 +521,7 @@ CommandSection::Execute(ReportRequestSet* requests) const ALOGW("CommandSection '%s' failed to set up stdout: %s", this->name.string(), strerror(errno)); _exit(EXIT_FAILURE); } - execv(this->mCommand[0], (char *const *) this->mCommand); + execvp(this->mCommand[0], (char *const *) this->mCommand); int err = errno; // record command error code ALOGW("CommandSection '%s' failed in executing command: %s", this->name.string(), strerror(errno)); _exit(err); // exit with command error code diff --git a/core/proto/android/os/cpuinfo.proto b/core/proto/android/os/cpuinfo.proto index 522ff24c1e60..cd151e253e7a 100644 --- a/core/proto/android/os/cpuinfo.proto +++ b/core/proto/android/os/cpuinfo.proto @@ -81,7 +81,9 @@ message CpuInfo { optional string virt = 8; // virtual memory size, i.e. 14.0G, 13.5M optional string res = 9; // Resident size, i.e. 0, 3.1G - // How Android memory manager will treat the task + // How Android memory manager will treat the task. + // TODO: use PsDumpProto.Process.Policy instead once we extern variables + // and are able to include the same .h file in two files. enum Policy { POLICY_UNKNOWN = 0; POLICY_fg = 1; // foreground, the name is lower case for parsing the value diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto index 09c08a9764ed..a6db31f67b33 100644 --- a/core/proto/android/os/incident.proto +++ b/core/proto/android/os/incident.proto @@ -25,6 +25,7 @@ import "frameworks/base/core/proto/android/os/incidentheader.proto"; import "frameworks/base/core/proto/android/os/kernelwake.proto"; import "frameworks/base/core/proto/android/os/pagetypeinfo.proto"; import "frameworks/base/core/proto/android/os/procrank.proto"; +import "frameworks/base/core/proto/android/os/ps.proto"; import "frameworks/base/core/proto/android/os/system_properties.proto"; import "frameworks/base/core/proto/android/providers/settings.proto"; import "frameworks/base/core/proto/android/server/activitymanagerservice.proto"; @@ -66,7 +67,7 @@ message IncidentProto { // Device information optional SystemPropertiesProto system_properties = 1000 [ (section).type = SECTION_COMMAND, - (section).args = "/system/bin/getprop" + (section).args = "getprop" ]; // Linux services @@ -87,7 +88,7 @@ message IncidentProto { optional CpuInfo cpu_info = 2003 [ (section).type = SECTION_COMMAND, - (section).args = "/system/bin/top -b -n 1 -H -s 6 -o pid,tid,user,pr,ni,%cpu,s,virt,res,pcy,cmd,name" + (section).args = "top -b -n 1 -H -s 6 -o pid,tid,user,pr,ni,%cpu,s,virt,res,pcy,cmd,name" ]; optional CpuFreq cpu_freq = 2004 [ @@ -95,6 +96,11 @@ message IncidentProto { (section).args = "/sys/devices/system/cpu/cpufreq/all_time_in_state" ]; + optional PsDumpProto processes_and_threads = 2005 [ + (section).type = SECTION_COMMAND, + (section).args = "ps -A -T -Z -O pri,nice,rtprio,sched,pcy,time" + ]; + optional BatteryTypeProto battery_type = 2006 [ (section).type = SECTION_FILE, (section).args = "/sys/class/power_supply/bms/battery_type" diff --git a/core/proto/android/os/ps.proto b/core/proto/android/os/ps.proto new file mode 100644 index 000000000000..88c6609a92b6 --- /dev/null +++ b/core/proto/android/os/ps.proto @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2017 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. + */ + +syntax = "proto2"; + +package android.os; + +option java_multiple_files = true; + +message PsDumpProto { + message Process { + // Security label, most commonly used for SELinux context data. + optional string label = 1; + optional string user = 2; + // Process ID number. + optional int32 pid = 3; + // The unique number representing a dispatchable entity (alias lwp, + // spid). This value may also appear as: a process ID (pid); a process + // group ID (pgrp); a session ID for the session leader (sid); a thread + // group ID for the thread group leader (tgid); and a tty process group + // ID for the process group leader (tpgid). + optional int32 tid = 4; + // Parent process ID. + optional int32 ppid = 5; + // Virtual set size (memory size) of the process, in KiB. + optional int32 vsz = 6; + // Resident set size. How many physical pages are associated with the + // process; real memory usage, in KiB. + optional int32 rss = 7; + // Name of the kernel function in which the process is sleeping, a "-" + // if the process is running, or a "*" if the process is multi-threaded + // and ps is not displaying threads. + optional string wchan = 8; + // Memory address of the process. + optional string addr = 9; + + enum ProcessStateCode { + STATE_UNKNOWN = 0; + // Uninterruptible sleep (usually IO). + STATE_D = 1; + // Running or runnable (on run queue). + STATE_R = 2; + // Interruptible sleep (waiting for an event to complete). + STATE_S = 3; + // Stopped by job control signal. + STATE_T = 4; + // Stopped by debugger during the tracing. + STATE_TRACING = 5; + // Dead (should never be seen). + STATE_X = 6; + // Defunct ("zombie") process. Terminated but not reaped by its + // parent. + STATE_Z = 7; + } + // Minimal state display + optional ProcessStateCode s = 10; + // Priority of the process. Higher number means lower priority. + optional int32 pri = 11; + // Nice value. This ranges from 19 (nicest) to -20 (not nice to others). + optional sint32 ni = 12; + // Realtime priority. + optional string rtprio = 13; // Number or - + + enum SchedulingPolicy { + option allow_alias = true; + + // Regular names conflict with macros defined in + // bionic/libc/kernel/uapi/linux/sched.h. + SCH_OTHER = 0; + SCH_NORMAL = 0; + + SCH_FIFO = 1; + SCH_RR = 2; + SCH_BATCH = 3; + SCH_ISO = 4; + SCH_IDLE = 5; + } + // Scheduling policy of the process. + optional SchedulingPolicy sch = 14; + + // How Android memory manager will treat the task + enum Policy { + POLICY_UNKNOWN = 0; + // Foreground. + POLICY_FG = 1; + // Background. + POLICY_BG = 2; + POLICY_TA = 3; // TODO: figure out what this value is + } + optional Policy pcy = 15; + // Total CPU time, "[DD-]HH:MM:SS" format. + optional string time = 16; + // Command with all its arguments as a string. + optional string cmd = 17; + } + repeated Process processes = 1; +} |