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