1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #define LOG_TAG "incident_helper"
17 
18 #include <android/util/ProtoOutputStream.h>
19 
20 #include "frameworks/base/core/proto/android/os/cpuinfo.proto.h"
21 #include "ih_util.h"
22 #include "CpuInfoParser.h"
23 
24 using namespace android::os;
25 
writeSuffixLine(ProtoOutputStream * proto,uint64_t fieldId,const string & line,const string & delimiter,const int count,const char * names[],const uint64_t ids[])26 static void writeSuffixLine(ProtoOutputStream* proto, uint64_t fieldId,
27         const string& line, const string& delimiter,
28         const int count, const char* names[], const uint64_t ids[])
29 {
30     record_t record = parseRecord(line, delimiter);
31     uint64_t token = proto->start(fieldId);
32     for (int i=0; i<(int)record.size(); i++) {
33         for (int j=0; j<count; j++) {
34             if (stripSuffix(&record[i], names[j], true)) {
35                 proto->write(ids[j], toInt(record[i]));
36                 break;
37             }
38         }
39     }
40     proto->end(token);
41 }
42 
43 status_t
Parse(const int in,const int out) const44 CpuInfoParser::Parse(const int in, const int out) const
45 {
46     Reader reader(in);
47     string line;
48     header_t header;
49     vector<int> columnIndices; // task table can't be split by purely delimiter, needs column positions.
50     record_t record;
51     int nline = 0;
52     int diff = 0;
53     bool nextToSwap = false;
54     bool nextToUsage = false;
55 
56     ProtoOutputStream proto;
57     Table table(CpuInfoProto::Task::_FIELD_NAMES, CpuInfoProto::Task::_FIELD_IDS, CpuInfoProto::Task::_FIELD_COUNT);
58     table.addEnumTypeMap("s", CpuInfoProto::Task::_ENUM_STATUS_NAMES,
59             CpuInfoProto::Task::_ENUM_STATUS_VALUES, CpuInfoProto::Task::_ENUM_STATUS_COUNT);
60     table.addEnumTypeMap("pcy", CpuInfoProto::Task::_ENUM_POLICY_NAMES,
61             CpuInfoProto::Task::_ENUM_POLICY_VALUES, CpuInfoProto::Task::_ENUM_POLICY_COUNT);
62 
63     // parse line by line
64     while (reader.readLine(&line)) {
65         if (line.empty()) continue;
66 
67         nline++;
68         // The format changes from time to time in toybox/toys/posix/ps.c
69         // With -H, it prints Threads instead of Tasks (FLAG(H)?"Thread":"Task")
70         if (stripPrefix(&line, "Threads:")) {
71             writeSuffixLine(&proto, CpuInfoProto::TASK_STATS, line, COMMA_DELIMITER,
72                 CpuInfoProto::TaskStats::_FIELD_COUNT,
73                 CpuInfoProto::TaskStats::_FIELD_NAMES,
74                 CpuInfoProto::TaskStats::_FIELD_IDS);
75             continue;
76         }
77         if (stripPrefix(&line, "Mem:")) {
78             writeSuffixLine(&proto, CpuInfoProto::MEM, line, COMMA_DELIMITER,
79                 CpuInfoProto::MemStats::_FIELD_COUNT,
80                 CpuInfoProto::MemStats::_FIELD_NAMES,
81                 CpuInfoProto::MemStats::_FIELD_IDS);
82             continue;
83         }
84         if (stripPrefix(&line, "Swap:")) {
85             writeSuffixLine(&proto, CpuInfoProto::SWAP, line, COMMA_DELIMITER,
86                 CpuInfoProto::MemStats::_FIELD_COUNT,
87                 CpuInfoProto::MemStats::_FIELD_NAMES,
88                 CpuInfoProto::MemStats::_FIELD_IDS);
89             nextToSwap = true;
90             continue;
91         }
92 
93         if (nextToSwap) {
94             writeSuffixLine(&proto, CpuInfoProto::CPU_USAGE, line, DEFAULT_WHITESPACE,
95                 CpuInfoProto::CpuUsage::_FIELD_COUNT,
96                 CpuInfoProto::CpuUsage::_FIELD_NAMES,
97                 CpuInfoProto::CpuUsage::_FIELD_IDS);
98             nextToUsage = true;
99             nextToSwap = false;
100             continue;
101         }
102 
103         // Header of tasks must be next to usage line
104         if (nextToUsage) {
105             // How to parse Header of Tasks:
106             // PID   TID USER         PR  NI[%CPU]S VIRT  RES PCY CMD             NAME
107             // After parsing, header = { PID, TID, USER, PR, NI, CPU, S, VIRT, RES, PCY, CMD, NAME }
108             // And columnIndices will contain end index of each word.
109             header = parseHeader(line, "[ %]");
110             nextToUsage = false;
111 
112             // NAME is not in the list since we need to modify the end of the CMD index.
113             const char* headerNames[] = { "PID", "TID", "USER", "PR", "NI", "CPU", "S", "VIRT", "RES", "PCY", "CMD", nullptr };
114             if (!getColumnIndices(columnIndices, headerNames, line)) {
115                 return -1;
116             }
117             // Need to remove the end index of CMD and use the start index of NAME because CMD values contain spaces.
118             // for example: ... CMD             NAME
119             //              ... Jit thread pool com.google.android.gms.feedback
120             // If use end index of CMD, parsed result = { "Jit", "thread pool com.google.android.gms.feedback" }
121             // If use start index of NAME, parsed result = { "Jit thread pool", "com.google.android.gms.feedback" }
122             int endCMD = columnIndices.back();
123             columnIndices.pop_back();
124             columnIndices.push_back(line.find("NAME", endCMD) - 1);
125             // Add NAME index to complete the column list.
126             columnIndices.push_back(columnIndices.back() + 4);
127             continue;
128         }
129 
130         record = parseRecordByColumns(line, columnIndices);
131         diff = record.size() - header.size();
132         if (diff < 0) {
133             fprintf(stderr, "[%s]Line %d has %d missing fields\n%s\n", this->name.string(), nline, -diff, line.c_str());
134             printRecord(record);
135             continue;
136         } else if (diff > 0) {
137             fprintf(stderr, "[%s]Line %d has %d extra fields\n%s\n", this->name.string(), nline, diff, line.c_str());
138             printRecord(record);
139             continue;
140         }
141 
142         uint64_t token = proto.start(CpuInfoProto::TASKS);
143         for (int i=0; i<(int)record.size(); i++) {
144             if (!table.insertField(&proto, header[i], record[i])) {
145                 fprintf(stderr, "[%s]Line %d fails to insert field %s with value %s\n",
146                         this->name.string(), nline, header[i].c_str(), record[i].c_str());
147             }
148         }
149         proto.end(token);
150     }
151 
152     if (!reader.ok(&line)) {
153         fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
154         return -1;
155     }
156 
157     if (!proto.flush(out)) {
158         fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
159         return -1;
160     }
161     fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
162     return NO_ERROR;
163 }
164