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

Commit 2888533e authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Implement Cpu Info Section"

parents 4ae65c7a e2f7f79d
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