Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e2f7f79d authored by Yi Jin's avatar Yi Jin
Browse files

Implement Cpu Info Section

Support carriage return in Read class, and add a new way to parse lines
which is not able to split purly by delimiters

Bug: 65642861
Test: unit test and on device test
Change-Id: Ib82dd4e458bb7d2fa33462b23fbe11b828325916
parent 9a753af2
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -58,6 +58,7 @@ cc_library {
            // runtime, as well as the only protos that are actually
            // needed by the device.
            srcs: [
                "core/proto/android/os/cpuinfo.proto",
                "core/proto/android/os/kernelwake.proto",
                "core/proto/android/os/pagetypeinfo.proto",
                "core/proto/android/os/procrank.proto",
@@ -81,6 +82,7 @@ gensrcs {
    ],

    srcs: [
        "core/proto/android/os/cpuinfo.proto",
        "core/proto/android/os/kernelwake.proto",
        "core/proto/android/os/pagetypeinfo.proto",
        "core/proto/android/os/procrank.proto",
+146 −76
Original line number Diff line number Diff line
@@ -22,19 +22,28 @@
#include <sstream>
#include <unistd.h>

const ssize_t BUFFER_SIZE = 16 * 1024; // 4KB

bool isValidChar(char c) {
    uint8_t v = (uint8_t)c;
    return (v >= (uint8_t)'a' && v <= (uint8_t)'z')
        || (v >= (uint8_t)'A' && v <= (uint8_t)'Z')
        || (v >= (uint8_t)'0' && v <= (uint8_t)'9')
        || (v == (uint8_t)'_');
}

static std::string trim(const std::string& s) {
    const auto head = s.find_first_not_of(DEFAULT_WHITESPACE);
static std::string trim(const std::string& s, const std::string& chars) {
    const auto head = s.find_first_not_of(chars);
    if (head == std::string::npos) return "";

    const auto tail = s.find_last_not_of(DEFAULT_WHITESPACE);
    const auto tail = s.find_last_not_of(chars);
    return s.substr(head, tail - head + 1);
}

static std::string trimDefault(const std::string& s) {
    return trim(s, DEFAULT_WHITESPACE);
}

static std::string trimHeader(const std::string& s) {
    std::string res = trim(s);
    std::string res = trimDefault(s);
    std::transform(res.begin(), res.end(), res.begin(), ::tolower);
    return res;
}
@@ -68,22 +77,68 @@ header_t parseHeader(const std::string& line, const std::string& delimiters) {

record_t parseRecord(const std::string& line, const std::string& delimiters) {
    record_t record;
    trans_func f = &trim;
    trans_func f = &trimDefault;
    split(line, record, f, delimiters);
    return record;
}

bool hasPrefix(std::string* line, const char* key) {
record_t parseRecordByColumns(const std::string& line, const std::vector<int>& indices, const std::string& delimiters) {
    record_t record;
    int lastIndex = 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;
            return record;
        }
        while (idx < lineSize && delimiters.find(line[idx++]) == std::string::npos);
        record.push_back(trimDefault(line.substr(lastIndex, idx - lastIndex)));
        lastIndex = idx;
    }
    record.push_back(trimDefault(line.substr(lastIndex, lineSize - lastIndex)));
    return record;
}

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;
    auto i = 0;
    auto j = head;
    int len = (int)line->length();
    int i = 0;
    int j = head;
    while (key[i] != '\0') {
        if (j >= line->size() || key[i++] != line->at(j++)) {
        if (j >= len || key[i++] != line->at(j++)) {
            return false;
        }
    }

    if (endAtDelimiter) {
        // this means if the line only have prefix or no delimiter, we still return false.
        if (j == len || isValidChar(line->at(j))) return false;
    }

    line->assign(trimDefault(line->substr(j)));
    return true;
}

bool stripSuffix(std::string* line, const char* key, bool endAtDelimiter) {
    const auto tail = line->find_last_not_of(DEFAULT_WHITESPACE);
    if (tail == std::string::npos) return false;
    int i = 0;
    while (key[++i] != '\0'); // compute the size of the key
    int j = tail;
    while (i > 0) {
        if (j < 0 || key[--i] != line->at(j--)) {
            return false;
        }
    }
    line->assign(trim(line->substr(j)));

    if (endAtDelimiter) {
        // this means if the line only have suffix or no delimiter, we still return false.
        if (j < 0 || isValidChar(line->at(j))) return false;
    }

    line->assign(trimDefault(line->substr(0, j+1)));
    return true;
}

@@ -95,65 +150,36 @@ long long toLongLong(const std::string& s) {
    return atoll(s.c_str());
}

// ==============================================================================
Reader::Reader(const int fd) : Reader(fd, BUFFER_SIZE) {};
double toDouble(const std::string& s) {
    return atof(s.c_str());
}

Reader::Reader(const int fd, const size_t capacity)
        : mFd(fd), mMaxSize(capacity), mBufSize(0), mRead(0), mFlushed(0)
// ==============================================================================
Reader::Reader(const int fd)
{
    mBuf = capacity > 0 ? (char*)malloc(capacity * sizeof(char)) : NULL;
    mStatus = mFd < 0 ? "Negative fd" : (capacity == 0 ? "Zero buffer capacity" : "");
    mFile = fdopen(fd, "r");
    mStatus = mFile == NULL ? "Invalid fd " + std::to_string(fd) : "";
}

Reader::~Reader()
{
    free(mBuf);
}

bool Reader::readLine(std::string* line, const char newline) {
    if (!ok(line)) return false; // bad status
    line->clear();
    std::stringstream ss;
    while (!EOR()) {
        // read if available
        if (mFd != -1 && mBufSize != mMaxSize) {
            ssize_t amt = 0;
            if (mRead >= mFlushed) {
                amt = ::read(mFd, mBuf + mRead, mMaxSize - mRead);
            } else {
                amt = ::read(mFd, mBuf + mRead, mFlushed - mRead);
            }
            if (amt < 0) {
                mStatus = "Fail to read from fd";
                return false;
            } else if (amt == 0) {
                close(mFd);
                mFd = -1;
            }
            mRead += amt;
            mBufSize += amt;
        }

        bool meetsNewLine = false;
        if (mBufSize > 0) {
            int start = mFlushed;
            int end = mFlushed < mRead ? mRead : mMaxSize;
            while (mFlushed < end && mBuf[mFlushed++] != newline && mBufSize > 0) mBufSize--;
            meetsNewLine = (mBuf[mFlushed-1] == newline);
            if (meetsNewLine) mBufSize--; // deduct the new line character
            size_t len = meetsNewLine ? mFlushed - start - 1 : mFlushed - start;
            ss.write(mBuf + start, len);
    if (mFile != NULL) fclose(mFile);
}

        if (mRead >= (int) mMaxSize) mRead = 0;
        if (mFlushed >= (int) mMaxSize) mFlushed = 0;
bool Reader::readLine(std::string* line) {
    if (mFile == NULL) return false;

        if (EOR() || meetsNewLine) {
            line->assign(ss.str());
            return true;
        }
    char* buf = NULL;
    size_t len = 0;
    ssize_t read = getline(&buf, &len, mFile);
    if (read != -1) {
        std::string s(buf);
        line->assign(trim(s, DEFAULT_NEWLINE));
    } else if (errno == EINVAL) {
        mStatus = "Bad Argument";
    }
    return false;
    free(buf);
    return read != -1;
}

bool Reader::ok(std::string* error) {
@@ -162,10 +188,41 @@ bool Reader::ok(std::string* error) {
}

// ==============================================================================
static int
lookupName(const char** names, const int size, const char* name)
{
    for (int i=0; i<size; i++) {
        if (strcmp(name, names[i]) == 0) {
            return i;
        }
    }
    return -1;
}

EnumTypeMap::EnumTypeMap(const char* enumNames[], const uint32_t enumValues[], const int enumCount)
        :mEnumNames(enumNames),
         mEnumValues(enumValues),
         mEnumCount(enumCount)
{
}

EnumTypeMap::~EnumTypeMap()
{
}

int
EnumTypeMap::parseValue(const std::string& value)
{
    int index = lookupName(mEnumNames, mEnumCount, value.c_str());
    if (index < 0) return mEnumValues[0]; // Assume value 0 is default
    return mEnumValues[index];
}

Table::Table(const char* names[], const uint64_t ids[], const int count)
        :mFieldNames(names),
         mFieldIds(ids),
         mFieldCount(count)
         mFieldCount(count),
         mEnums()
{
}

@@ -173,41 +230,54 @@ Table::~Table()
{
}

bool
Table::insertField(ProtoOutputStream& proto, const std::string& name, const std::string& value)
void
Table::addEnumTypeMap(const char* field, const char* enumNames[], const uint32_t enumValues[], const int enumSize)
{
    uint64_t found = 0;
    for (int i=0; i<mFieldCount; i++) {
        if (strcmp(name.c_str(), mFieldNames[i]) == 0) {
            found = mFieldIds[i];
            break;
        }
    int index = lookupName(mFieldNames, mFieldCount, field);
    if (index < 0) return;

    EnumTypeMap enu(enumNames, enumValues, enumSize);
    mEnums[index] = enu;
}

bool
Table::insertField(ProtoOutputStream* proto, const std::string& name, const std::string& value)
{
    int index = lookupName(mFieldNames, mFieldCount, name.c_str());
    if (index < 0) return false;

    uint64_t found = mFieldIds[index];
    switch (found & FIELD_TYPE_MASK) {
        case FIELD_TYPE_DOUBLE:
        case FIELD_TYPE_FLOAT:
            // TODO: support parse string to float/double
            return false;
            proto->write(found, toDouble(value));
            break;
        case FIELD_TYPE_STRING:
        case FIELD_TYPE_BYTES:
            proto.write(found, value);
            proto->write(found, value);
            break;
        case FIELD_TYPE_INT64:
        case FIELD_TYPE_SINT64:
        case FIELD_TYPE_UINT64:
        case FIELD_TYPE_FIXED64:
        case FIELD_TYPE_SFIXED64:
            proto.write(found, toLongLong(value));
            proto->write(found, toLongLong(value));
            break;
        case FIELD_TYPE_BOOL:
            return false;
        case FIELD_TYPE_ENUM:
            if (mEnums.find(index) == mEnums.end()) {
                // forget to add enum type mapping
                return false;
            }
            proto->write(found, mEnums[index].parseValue(value));
            break;
        case FIELD_TYPE_INT32:
        case FIELD_TYPE_SINT32:
        case FIELD_TYPE_UINT32:
        case FIELD_TYPE_FIXED32:
        case FIELD_TYPE_SFIXED32:
            proto.write(found, toInt(value));
            proto->write(found, toInt(value));
            break;
        default:
            return false;
+45 −17
Original line number Diff line number Diff line
@@ -17,9 +17,9 @@
#ifndef INCIDENT_HELPER_UTIL_H
#define INCIDENT_HELPER_UTIL_H

#include <map>
#include <string>
#include <vector>
#include <sstream>

#include <android/util/ProtoOutputStream.h>

@@ -29,8 +29,13 @@ typedef std::vector<std::string> header_t;
typedef std::vector<std::string> record_t;
typedef std::string (*trans_func) (const std::string&);

const char DEFAULT_NEWLINE = '\n';
const std::string DEFAULT_WHITESPACE = " \t";
const std::string DEFAULT_NEWLINE = "\r\n";
const std::string TAB_DELIMITER = "\t";
const std::string COMMA_DELIMITER = ",";

// returns true if c is a-zA-Z0-9 or underscore _
bool isValidChar(char c);

/**
 * When a text has a table format like this
@@ -47,19 +52,33 @@ 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);

/**
 * When the line starts with the given key, the function returns true
 * as well as the line argument is changed to the rest part of the original.
 * 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);

/**
 * 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.
 * e.g. "ZRAM: 6828K physical used for 31076K in swap (524284K total swap)" becomes
 * "6828K physical used for 31076K in swap (524284K total swap)" when given key "ZRAM:",
 * otherwise the line is not changed.
 *
 * In order to prevent two values have same prefix which cause entering to incorrect conditions,
 * stripPrefix and stripSuffix can turn on a flag that requires the ending char in the line must not be a valid
 * character or digits, this feature is off by default.
 * i.e. ABC%some value, ABCD%other value
 */
bool hasPrefix(std::string* line, const char* key);
bool stripPrefix(std::string* line, const char* key, bool endAtDelimiter = false);
bool stripSuffix(std::string* line, const char* key, bool endAtDelimiter = false);

/**
 * Converts string to the desired type
 */
int toInt(const std::string& s);
long long toLongLong(const std::string& s);
double toDouble(const std::string& s);

/**
 * Reader class reads data from given fd in streaming fashion.
@@ -69,23 +88,29 @@ class Reader
{
public:
    Reader(const int fd);
    Reader(const int fd, const size_t capacity);
    ~Reader();

    bool readLine(std::string* line, const char newline = DEFAULT_NEWLINE);
    bool readLine(std::string* line);
    bool ok(std::string* error);

private:
    int mFd; // set mFd to -1 when read EOF()
    const size_t mMaxSize;
    size_t mBufSize;
    char* mBuf; // implements a circular buffer

    int mRead;
    int mFlushed;
    FILE* mFile;
    std::string mStatus;
    // end of read
    inline bool EOR() { return mFd == -1 && mBufSize == 0; };
};

class EnumTypeMap
{
public:
    EnumTypeMap() {};
    EnumTypeMap(const char* enumNames[], const uint32_t enumValues[], const int enumCount);
    ~EnumTypeMap();

    int parseValue(const std::string& value);

private:
    const char** mEnumNames;
    const uint32_t* mEnumValues;
    int mEnumCount;
};

/**
@@ -98,12 +123,15 @@ public:
    Table(const char* names[], const uint64_t ids[], const int count);
    ~Table();

    bool insertField(ProtoOutputStream& proto, const std::string& name, const std::string& value);
    // Add enum names to values for parsing purpose.
    void addEnumTypeMap(const char* field, const char* enumNames[], const uint32_t enumValues[], const int enumSize);

    bool insertField(ProtoOutputStream* proto, const std::string& name, const std::string& value);
private:
    const char** mFieldNames;
    const uint64_t* mFieldIds;
    const int mFieldCount;
    map<int, EnumTypeMap> mEnums;
};

#endif  // INCIDENT_HELPER_UTIL_H
+3 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

#define LOG_TAG "incident_helper"

#include "parsers/CpuInfoParser.h"
#include "parsers/KernelWakesParser.h"
#include "parsers/PageTypeInfoParser.h"
#include "parsers/ProcrankParser.h"
@@ -54,6 +55,8 @@ static TextParserBase* selectParser(int section) {
            return new PageTypeInfoParser();
        case 2002:
            return new KernelWakesParser();
        case 2003:
            return new CpuInfoParser();
        default:
            return NULL;
    }
+161 −0
Original line number Diff line number Diff line
/*
 * 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/cpuinfo.proto.h"
#include "ih_util.h"
#include "CpuInfoParser.h"

using namespace android::os;

static void writeSuffixLine(ProtoOutputStream* proto, uint64_t fieldId,
        const string& line, const string& delimiter,
        const int count, const char* names[], const uint64_t ids[])
{
    record_t record = parseRecord(line, delimiter);
    long long token = proto->start(fieldId);
    for (int i=0; i<(int)record.size(); i++) {
        for (int j=0; j<count; j++) {
            if (stripSuffix(&record[i], names[j], true)) {
                proto->write(ids[j], toInt(record[i]));
                break;
            }
        }
    }
    proto->end(token);
}

status_t
CpuInfoParser::Parse(const int in, const int out) const
{
    Reader reader(in);
    string line;
    header_t header;
    vector<int> columnIndices; // task table can't be split by purely delimiter, needs column positions.
    record_t record;
    int nline = 0;
    bool nextToSwap = false;
    bool nextToUsage = false;

    ProtoOutputStream proto;
    Table table(CpuInfo::Task::_FIELD_NAMES, CpuInfo::Task::_FIELD_IDS, CpuInfo::Task::_FIELD_COUNT);
    table.addEnumTypeMap("s", CpuInfo::Task::_ENUM_STATUS_NAMES,
            CpuInfo::Task::_ENUM_STATUS_VALUES, CpuInfo::Task::_ENUM_STATUS_COUNT);
    table.addEnumTypeMap("pcy", CpuInfo::Task::_ENUM_POLICY_NAMES,
            CpuInfo::Task::_ENUM_POLICY_VALUES, CpuInfo::Task::_ENUM_POLICY_COUNT);

    // parse line by line
    while (reader.readLine(&line)) {
        if (line.empty()) continue;

        nline++;

        if (stripPrefix(&line, "Tasks:")) {
            writeSuffixLine(&proto, CpuInfo::TASK_STATS, line, COMMA_DELIMITER,
                CpuInfo::TaskStats::_FIELD_COUNT,
                CpuInfo::TaskStats::_FIELD_NAMES,
                CpuInfo::TaskStats::_FIELD_IDS);
            continue;
        }
        if (stripPrefix(&line, "Mem:")) {
            writeSuffixLine(&proto, CpuInfo::MEM, line, COMMA_DELIMITER,
                CpuInfo::MemStats::_FIELD_COUNT,
                CpuInfo::MemStats::_FIELD_NAMES,
                CpuInfo::MemStats::_FIELD_IDS);
            continue;
        }
        if (stripPrefix(&line, "Swap:")) {
            writeSuffixLine(&proto, CpuInfo::SWAP, line, COMMA_DELIMITER,
                CpuInfo::MemStats::_FIELD_COUNT,
                CpuInfo::MemStats::_FIELD_NAMES,
                CpuInfo::MemStats::_FIELD_IDS);
            nextToSwap = true;
            continue;
        }

        if (nextToSwap) {
            writeSuffixLine(&proto, CpuInfo::CPU_USAGE, line, DEFAULT_WHITESPACE,
                CpuInfo::CpuUsage::_FIELD_COUNT,
                CpuInfo::CpuUsage::_FIELD_NAMES,
                CpuInfo::CpuUsage::_FIELD_IDS);
            nextToUsage = true;
            nextToSwap = false;
            continue;
        }

        // Header of tasks must be next to usage line
        if (nextToUsage) {
            // How to parse Header of Tasks:
            // PID   TID USER         PR  NI[%CPU]S VIRT  RES PCY CMD             NAME
            // After parsing, header = { PID, TID, USER, PR, NI, CPU, S, VIRT, RES, PCY, CMD, NAME }
            // And columnIndices will contain end index of each word.
            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);
            }
            // Need to remove the end index of CMD and use the start index of NAME because CMD values contain spaces.
            // for example: ... CMD             NAME
            //              ... Jit thread pool com.google.android.gms.feedback
            // If use end index of CMD, parsed result = { "Jit", "thread pool com.google.android.gms.feedback" }
            // If use start index of NAME, parsed result = { "Jit thread pool", "com.google.android.gms.feedback" }
            int endCMD = columnIndices.back();
            columnIndices.pop_back();
            columnIndices.push_back(line.find("NAME", endCMD) - 1);
            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());
            continue;
        }

        long long token = proto.start(CpuInfo::TASKS);
        for (int i=0; i<(int)record.size(); i++) {
            if (!table.insertField(&proto, header[i], record[i])) {
                fprintf(stderr, "[%s]Line %d fails to insert field %s with value %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;
}
Loading