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 24using namespace android::os; 25 26static 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 43status_t 44CpuInfoParser::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 69 if (stripPrefix(&line, "Tasks:")) { 70 writeSuffixLine(&proto, CpuInfoProto::TASK_STATS, line, COMMA_DELIMITER, 71 CpuInfoProto::TaskStats::_FIELD_COUNT, 72 CpuInfoProto::TaskStats::_FIELD_NAMES, 73 CpuInfoProto::TaskStats::_FIELD_IDS); 74 continue; 75 } 76 if (stripPrefix(&line, "Mem:")) { 77 writeSuffixLine(&proto, CpuInfoProto::MEM, line, COMMA_DELIMITER, 78 CpuInfoProto::MemStats::_FIELD_COUNT, 79 CpuInfoProto::MemStats::_FIELD_NAMES, 80 CpuInfoProto::MemStats::_FIELD_IDS); 81 continue; 82 } 83 if (stripPrefix(&line, "Swap:")) { 84 writeSuffixLine(&proto, CpuInfoProto::SWAP, line, COMMA_DELIMITER, 85 CpuInfoProto::MemStats::_FIELD_COUNT, 86 CpuInfoProto::MemStats::_FIELD_NAMES, 87 CpuInfoProto::MemStats::_FIELD_IDS); 88 nextToSwap = true; 89 continue; 90 } 91 92 if (nextToSwap) { 93 writeSuffixLine(&proto, CpuInfoProto::CPU_USAGE, line, DEFAULT_WHITESPACE, 94 CpuInfoProto::CpuUsage::_FIELD_COUNT, 95 CpuInfoProto::CpuUsage::_FIELD_NAMES, 96 CpuInfoProto::CpuUsage::_FIELD_IDS); 97 nextToUsage = true; 98 nextToSwap = false; 99 continue; 100 } 101 102 // Header of tasks must be next to usage line 103 if (nextToUsage) { 104 // How to parse Header of Tasks: 105 // PID TID USER PR NI[%CPU]S VIRT RES PCY CMD NAME 106 // After parsing, header = { PID, TID, USER, PR, NI, CPU, S, VIRT, RES, PCY, CMD, NAME } 107 // And columnIndices will contain end index of each word. 108 header = parseHeader(line, "[ %]"); 109 nextToUsage = false; 110 111 // NAME is not in the list since we need to modify the end of the CMD index. 112 const char* headerNames[] = { "PID", "TID", "USER", "PR", "NI", "CPU", "S", "VIRT", "RES", "PCY", "CMD", NULL }; 113 if (!getColumnIndices(columnIndices, headerNames, line)) { 114 return -1; 115 } 116 // Need to remove the end index of CMD and use the start index of NAME because CMD values contain spaces. 117 // for example: ... CMD NAME 118 // ... Jit thread pool com.google.android.gms.feedback 119 // If use end index of CMD, parsed result = { "Jit", "thread pool com.google.android.gms.feedback" } 120 // If use start index of NAME, parsed result = { "Jit thread pool", "com.google.android.gms.feedback" } 121 int endCMD = columnIndices.back(); 122 columnIndices.pop_back(); 123 columnIndices.push_back(line.find("NAME", endCMD) - 1); 124 // Add NAME index to complete the column list. 125 columnIndices.push_back(columnIndices.back() + 4); 126 continue; 127 } 128 129 record = parseRecordByColumns(line, columnIndices); 130 diff = record.size() - header.size(); 131 if (diff < 0) { 132 fprintf(stderr, "[%s]Line %d has %d missing fields\n%s\n", this->name.string(), nline, -diff, line.c_str()); 133 printRecord(record); 134 continue; 135 } else if (diff > 0) { 136 fprintf(stderr, "[%s]Line %d has %d extra fields\n%s\n", this->name.string(), nline, diff, line.c_str()); 137 printRecord(record); 138 continue; 139 } 140 141 uint64_t token = proto.start(CpuInfoProto::TASKS); 142 for (int i=0; i<(int)record.size(); i++) { 143 if (!table.insertField(&proto, header[i], record[i])) { 144 fprintf(stderr, "[%s]Line %d fails to insert field %s with value %s\n", 145 this->name.string(), nline, header[i].c_str(), record[i].c_str()); 146 } 147 } 148 proto.end(token); 149 } 150 151 if (!reader.ok(&line)) { 152 fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str()); 153 return -1; 154 } 155 156 if (!proto.flush(out)) { 157 fprintf(stderr, "[%s]Error writing proto back\n", this->name.string()); 158 return -1; 159 } 160 fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size()); 161 return NO_ERROR; 162} 163