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

Commit 3e906582 authored by Bookatz's avatar Bookatz
Browse files

statsd local tool

Adds a tool for local usage of statsd. The tool can:
-upload a config from a file
-get the report data from statsd
Both the config and the report can be either in binary or human-readable
format, as specified.

Usage:
make statsd_localdrive
./out/host/linux-x86/bin/statsd_localdrive

Also, adds the ability to specify whether dump-report should also erase
the data when it returns it. A test for this is added.

Test: make -j8 statsd_test && adb sync data && adb shell data/nativetest64/statsd_test/statsd_test
Test: make statsd_localdrive && ./out/host/linux-x86/bin/statsd_localdrive <commands>
Bug: 77909781
Change-Id: I9a38964988e90c4158a555f41879534267aadd32
parent 344131ba
Loading
Loading
Loading
Loading
+9 −3
Original line number Diff line number Diff line
@@ -424,13 +424,14 @@ void StatsService::print_cmd_help(int out) {
    dprintf(out, "\n                     be removed from memory and disk!\n");
    dprintf(out, "\n");
    dprintf(out,
            "usage: adb shell cmd stats dump-report [UID] NAME [--include_current_bucket] "
            "[--proto]\n");
            "usage: adb shell cmd stats dump-report [UID] NAME [--keep_data] "
            "[--include_current_bucket] [--proto]\n");
    dprintf(out, "  Dump all metric data for a configuration.\n");
    dprintf(out, "  UID           The uid of the configuration. It is only possible to pass\n");
    dprintf(out, "                the UID parameter on eng builds. If UID is omitted the\n");
    dprintf(out, "                calling uid is used.\n");
    dprintf(out, "  NAME          The name of the configuration\n");
    dprintf(out, "  --keep_data   Do NOT erase the data upon dumping it.\n");
    dprintf(out, "  --proto       Print proto binary.\n");
    dprintf(out, "\n");
    dprintf(out, "\n");
@@ -590,6 +591,7 @@ status_t StatsService::cmd_dump_report(int out, const Vector<String8>& args) {
        bool good = false;
        bool proto = false;
        bool includeCurrentBucket = false;
        bool eraseData = true;
        int uid;
        string name;
        if (!std::strcmp("--proto", args[argCount-1].c_str())) {
@@ -600,6 +602,10 @@ status_t StatsService::cmd_dump_report(int out, const Vector<String8>& args) {
            includeCurrentBucket = true;
            argCount -= 1;
        }
        if (!std::strcmp("--keep_data", args[argCount-1].c_str())) {
            eraseData = false;
            argCount -= 1;
        }
        if (argCount == 2) {
            // Automatically pick the UID
            uid = IPCThreadState::self()->getCallingUid();
@@ -627,7 +633,7 @@ status_t StatsService::cmd_dump_report(int out, const Vector<String8>& args) {
        if (good) {
            vector<uint8_t> data;
            mProcessor->onDumpReport(ConfigKey(uid, StrToInt64(name)), getElapsedRealtimeNs(),
                                     includeCurrentBucket, true /* erase_data */, ADB_DUMP, &data);
                                     includeCurrentBucket, eraseData, ADB_DUMP, &data);
            if (proto) {
                for (size_t i = 0; i < data.size(); i ++) {
                    dprintf(out, "%c", data[i]);
+43 −0
Original line number Diff line number Diff line
@@ -240,6 +240,49 @@ TEST(StatsLogProcessorTest, TestReportIncludesSubConfig) {
    EXPECT_EQ(2, report.annotation(0).field_int32());
}

TEST(StatsLogProcessorTest, TestOnDumpReportEraseData) {
    // Setup a simple config.
    StatsdConfig config;
    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
    auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher();
    *config.add_atom_matcher() = wakelockAcquireMatcher;

    auto countMetric = config.add_count_metric();
    countMetric->set_id(123456);
    countMetric->set_what(wakelockAcquireMatcher.id());
    countMetric->set_bucket(FIVE_MINUTES);

    ConfigKey cfgKey;
    sp<StatsLogProcessor> processor = CreateStatsLogProcessor(1, 1, config, cfgKey);

    std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1")};
    auto event = CreateAcquireWakelockEvent(attributions1, "wl1", 2);
    processor->OnLogEvent(event.get());

    vector<uint8_t> bytes;
    ConfigMetricsReportList output;

    // Dump report WITHOUT erasing data.
    processor->onDumpReport(cfgKey, 3, true, false /* Do NOT erase data. */, ADB_DUMP, &bytes);
    output.ParseFromArray(bytes.data(), bytes.size());
    EXPECT_EQ(output.reports_size(), 1);
    EXPECT_EQ(output.reports(0).metrics_size(), 1);
    EXPECT_EQ(output.reports(0).metrics(0).count_metrics().data_size(), 1);

    // Dump report WITH erasing data. There should be data since we didn't previously erase it.
    processor->onDumpReport(cfgKey, 4, true, true /* DO erase data. */, ADB_DUMP, &bytes);
    output.ParseFromArray(bytes.data(), bytes.size());
    EXPECT_EQ(output.reports_size(), 1);
    EXPECT_EQ(output.reports(0).metrics_size(), 1);
    EXPECT_EQ(output.reports(0).metrics(0).count_metrics().data_size(), 1);

    // Dump report again. There should be no data since we erased it.
    processor->onDumpReport(cfgKey, 5, true, true /* DO erase data. */, ADB_DUMP, &bytes);
    output.ParseFromArray(bytes.data(), bytes.size());
    bool noData = (output.reports_size() == 0) || (output.reports(0).metrics_size() == 0);
    EXPECT_TRUE(noData);
}

#else
GTEST_LOG_(INFO) << "This test does nothing.\n";
#endif
+25 −0
Original line number Diff line number Diff line
java_binary_host {
    name: "statsd_localdrive",
    manifest: "localdrive_manifest.txt",
    srcs: [
        "src/com/android/statsd/shelltools/localdrive/*.java",
        "src/com/android/statsd/shelltools/Utils.java",
    ],
    static_libs: [
        "platformprotos",
        "guava",
    ],
}

java_binary_host {
    name: "statsd_testdrive",
    manifest: "testdrive_manifest.txt",
    srcs: [
        "src/com/android/statsd/shelltools/testdrive/*.java",
        "src/com/android/statsd/shelltools/Utils.java",
    ],
    static_libs: [
        "platformprotos",
        "guava",
    ],
}
 No newline at end of file
+1 −0
Original line number Diff line number Diff line
Main-class: com.android.statsd.shelltools.localdrive.LocalDrive
+119 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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.
 */
package com.android.statsd.shelltools;

import com.android.os.StatsLog.ConfigMetricsReportList;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.logging.ConsoleHandler;
import java.util.logging.Formatter;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

/**
 * Utilities for local use of statsd.
 */
public class Utils {

    public static final String CMD_UPDATE_CONFIG = "cmd stats config update";
    public static final String CMD_DUMP_REPORT = "cmd stats dump-report";
    public static final String CMD_REMOVE_CONFIG = "cmd stats config remove";

    public static final String SHELL_UID = "2000"; // Use shell, even if rooted.

    /**
     * Runs adb shell command with output directed to outputFile if non-null.
     */
    public static void runCommand(File outputFile, Logger logger, String... commands)
            throws IOException, InterruptedException {
        ProcessBuilder pb = new ProcessBuilder(commands);
        if (outputFile != null && outputFile.exists() && outputFile.canWrite()) {
            pb.redirectOutput(outputFile);
        }
        Process process = pb.start();

        // Capture any errors
        StringBuilder err = new StringBuilder();
        BufferedReader br = new BufferedReader(new InputStreamReader(process.getErrorStream()));
        for (String line = br.readLine(); line != null; line = br.readLine()) {
            err.append(line).append('\n');
        }
        logger.severe(err.toString());

        // Check result
        if (process.waitFor() == 0) {
            logger.fine("Adb command successful.");
        } else {
            logger.severe("Abnormal adb shell cmd termination for: " + String.join(",", commands));
            throw new RuntimeException("Error running adb command: " + err.toString());
        }
    }

    /**
     * Dumps the report from the device and converts it to a ConfigMetricsReportList.
     * Erases the data if clearData is true.
     */
    public static ConfigMetricsReportList getReportList(long configId, boolean clearData,
            Logger logger) throws IOException, InterruptedException {
        try {
            File outputFile = File.createTempFile("statsdret", ".bin");
            outputFile.deleteOnExit();
            runCommand(
                    outputFile,
                    logger,
                    "adb",
                    "shell",
                    CMD_DUMP_REPORT,
                    SHELL_UID,
                    String.valueOf(configId),
                    clearData ? "" : "--keep_data",
                    "--include_current_bucket",
                    "--proto");
            ConfigMetricsReportList reportList =
                    ConfigMetricsReportList.parseFrom(new FileInputStream(outputFile));
            return reportList;
        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
            logger.severe("Failed to fetch and parse the statsd output report. "
                            + "Perhaps there is not a valid statsd config for the requested "
                            + "uid=" + SHELL_UID
                            + ", configId=" + configId
                            + ".");
            throw (e);
        }
    }

    public static void setUpLogger(Logger logger, boolean debug) {
        ConsoleHandler handler = new ConsoleHandler();
        handler.setFormatter(new LocalToolsFormatter());
        logger.setUseParentHandlers(false);
        if (debug) {
            handler.setLevel(Level.ALL);
            logger.setLevel(Level.ALL);
        }
        logger.addHandler(handler);
    }

    public static class LocalToolsFormatter extends Formatter {
        public String format(LogRecord record) {
            return record.getMessage() + "\n";
        }
    }
}
Loading