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

Commit f1a23838 authored by Andy Hung's avatar Andy Hung
Browse files

MediaMetrics: Add HeatMap to track items with status.

Audio items with status can be automatically tracked by HeatMap,
which accumulates error statistics.

Test: atest mediametrics_tests
Bug: 199763036
Change-Id: I6dd3d475400aac725e58890181ec309598525c03
parent 5973463b
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -57,6 +57,7 @@ static const std::unordered_map<std::string, int32_t>& getErrorStringMap() {
    // This may be found in frameworks/av/media/libmediametrics/include/MediaMetricsConstants.h
    static std::unordered_map<std::string, int32_t> map{
        {"",                                      NO_ERROR},
        {AMEDIAMETRICS_PROP_ERROR_VALUE_OK,       NO_ERROR},
        {AMEDIAMETRICS_PROP_ERROR_VALUE_ARGUMENT, BAD_VALUE},
        {AMEDIAMETRICS_PROP_ERROR_VALUE_IO,       DEAD_OBJECT},
        {AMEDIAMETRICS_PROP_ERROR_VALUE_MEMORY,   NO_MEMORY},
+3 −2
Original line number Diff line number Diff line
@@ -238,8 +238,9 @@
// https://cs.android.com/android/platform/superproject/+/master:frameworks/base/media/java/android/media/AudioSystem.java;drc=3ac246c43294d7f7012bdcb0ccb7bae1aa695bd4;l=785
// https://cs.android.com/android/platform/superproject/+/master:frameworks/av/media/libaaudio/include/aaudio/AAudio.h;drc=cfd3a6fa3aaaf712a890dc02452b38ef401083b8;l=120

// Error category:
// An empty error string indicates no error.
// Status errors:
// An empty status string or "ok" is interpreted as no error.
#define AMEDIAMETRICS_PROP_ERROR_VALUE_OK                 "ok"

// Error category: argument
//   IllegalArgumentException
+16 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@
#include "AnalyticsActions.h"
#include "AnalyticsState.h"
#include "AudioPowerUsage.h"
#include "HeatMap.h"
#include "StatsdLog.h"
#include "TimedAction.h"
#include "Wrap.h"
@@ -73,11 +74,23 @@ public:
    std::pair<std::string, int32_t> dump(
            int32_t lines = INT32_MAX, int64_t sinceNs = 0, const char *prefix = nullptr) const;

    /**
     * Returns a pair consisting of the dump string and the number of lines in the string.
     *
     * HeatMap dump.
     */
    std::pair<std::string, int32_t> dumpHeatMap(int32_t lines = INT32_MAX) const {
        return mHeatMap.dump(lines);
    }

    void clear() {
        // underlying state is locked.
        mPreviousAnalyticsState->clear();
        mAnalyticsState->clear();

        // Clears the status map
        mHeatMap.clear();

        // Clear power usage state.
        mAudioPowerUsage.clear();
    }
@@ -124,6 +137,9 @@ private:
    TimedAction mTimedAction; // locked internally
    const std::shared_ptr<StatsdLog> mStatsdLog; // locked internally, ok for multiple threads.

    static constexpr size_t kHeatEntries = 100;
    HeatMap mHeatMap{kHeatEntries}; // locked internally, ok for multiple threads.

    // DeviceUse is a nested class which handles audio device usage accounting.
    // We define this class at the end to ensure prior variables all properly constructed.
    // TODO: Track / Thread interaction
+223 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.
 */

#pragma once

#include <iomanip>
#include <map>
#include <sstream>
#include "MediaMetricsConstants.h"

namespace android::mediametrics {

/**
 * HeatData accumulates statistics on the status reported for a given key.
 *
 * HeatData is a helper class used by HeatMap to represent statistics.  We expose it
 * here for testing purposes currently.
 *
 * Note: This class is not thread safe, so mutual exclusion should be obtained by the caller
 * which in this case is HeatMap.  HeatMap getData() returns a local copy of HeatData, so use
 * of that is thread-safe.
 */
class HeatData {
    /* HeatData for a key is stored in a map based on the event (e.g. "start", "pause", create)
     * and then another map based on the status (e.g. "ok", "argument", "state").
     */
    std::map<std::string /* event */,
             std::map<std::string /* status name */, size_t /* count, nonzero */>> mMap;

public:
    /**
     * Add status data.
     *
     * \param suffix  (ignored) the suffix to the key that was stripped, if any.
     * \param event             the event (e.g. create, start, pause, stop, etc.).
     * \param uid     (ignored) the uid associated with the error.
     * \param message (ignored) the status message, if any.
     * \param subCode (ignored) the status subcode, if any.
     */
    void add(const std::string& suffix, const std::string& event, const std::string& status,
            uid_t uid, const std::string& message, int32_t subCode) {
        // Perhaps there could be a more detailed print.
        (void)suffix;
        (void)uid;
        (void)message;
        (void)subCode;
        ++mMap[event][status];
    }

    /** Returns the number of event names with status. */
    size_t size() const {
        return mMap.size();
    }

    /**
     * Returns a deque with pairs indicating the count of Oks and Errors.
     * The first pair is total, the other pairs are in order of mMap.
     *
     * Example return value of {ok, error} pairs:
     *     total     key1      key2
     * { { 2, 1 }, { 1, 0 }, { 1, 1 } }
     */
    std::deque<std::pair<size_t /* oks */, size_t /* errors */>> heatCount() const {
        size_t totalOk = 0;
        size_t totalError = 0;
        std::deque<std::pair<size_t /* oks */, size_t /* errors */>> heat;
        for (const auto &eventPair : mMap) {
            size_t ok = 0;
            size_t error = 0;
            for (const auto &[name, count] : eventPair.second) {
                if (name == AMEDIAMETRICS_PROP_ERROR_VALUE_OK) {
                    ok += count;
                } else {
                    error += count;
                }
            }
            totalOk += ok;
            totalError += error;
            heat.emplace_back(ok, error);
        }
        heat.emplace_front(totalOk, totalError);
        return heat;
    }

    /** Returns the error fraction from a pair <oks, errors>, a float between 0.f to 1.f. */
    static float fraction(const std::pair<size_t, size_t>& count) {
        return (float)count.second / (count.first + count.second);
    }

    /** Returns the HeatMap information in a single line string. */
    std::string dump() const {
        const auto heat = heatCount();
        auto it = heat.begin();
        std::stringstream ss;
        ss << "{ ";
        float errorFraction = fraction(*it++);
        if (errorFraction > 0.f) {
            ss << std::fixed << std::setprecision(2) << errorFraction << " ";
        }
        for (const auto &eventPair : mMap) {
            ss << eventPair.first << ": { ";
            errorFraction = fraction(*it++);
            if (errorFraction > 0.f) {
                ss << std::fixed << std::setprecision(2) << errorFraction << " ";
            }
            for (const auto &[name, count]: eventPair.second) {
                ss << "[ " << name << " : " << count << " ] ";
            }
            ss << "} ";
        }
        ss << " }";
        return ss.str();
    }
};

/**
 * HeatMap is a thread-safe collection that counts activity of status errors per key.
 *
 * The classic heat map is a 2D picture with intensity shown by color.
 * Here we accumulate the status results from keys to see if there are consistent
 * failures in the system.
 *
 * TODO(b/210855555): Heatmap improvements.
 *   1) heat decays in intensity in time for past events, currently we don't decay.
 */

class HeatMap {
    const size_t mMaxSize;
    mutable std::mutex mLock;
    size_t mRejected GUARDED_BY(mLock) = 0;
    std::map<std::string, HeatData> mMap GUARDED_BY(mLock);

public:
    /**
     * Constructs a HeatMap.
     *
     * \param maxSize the maximum number of elements that are tracked.
     */
    explicit HeatMap(size_t maxSize) : mMaxSize(maxSize) {
    }

    /** Returns the number of keys. */
    size_t size() const {
        std::lock_guard l(mLock);
        return mMap.size();
    }

    /** Clears error history. */
    void clear() {
        std::lock_guard l(mLock);
        return mMap.clear();
    }

    /** Returns number of keys rejected due to space. */
    size_t rejected() const {
        std::lock_guard l(mLock);
        return mRejected;
    }

    /** Returns a copy of the heat data associated with key. */
    HeatData getData(const std::string& key) const {
        std::lock_guard l(mLock);
        return mMap.count(key) == 0 ? HeatData{} : mMap.at(key);
    }

    /**
     * Adds a new entry.
     * \param key               the key category (e.g. audio.track).
     * \param suffix  (ignored) the suffix to the key that was stripped, if any.
     * \param event             the event (e.g. create, start, pause, stop, etc.).
     * \param uid     (ignored) the uid associated with the error.
     * \param message (ignored) the status message, if any.
     * \param subCode (ignored) the status subcode, if any.
     */
    void add(const std::string& key, const std::string& suffix, const std::string& event,
            const std::string& status, uid_t uid, const std::string& message, int32_t subCode) {
        std::lock_guard l(mLock);

        // Hard limit on heat map entries.
        // TODO: have better GC.
        if (mMap.size() == mMaxSize && mMap.count(key) == 0) {
            ++mRejected;
            return;
        }
        mMap[key].add(suffix, event, status, uid, message, subCode);
    }

    /**
     * Returns a pair consisting of the dump string and the number of lines in the string.
     */
    std::pair<std::string, int32_t> dump(int32_t lines = INT32_MAX) const {
        std::stringstream ss;
        int32_t ll = lines;
        std::lock_guard l(mLock);
        auto it = mMap.begin();
        if (ll > 0) {
            ss << "Error Heat Map (rejected: " << mRejected << "):\n";
            --ll;
        }
        // TODO: restriction is implemented alphabetically not on priority.
        for (int32_t count = 0; it != mMap.end() && count < lines; ++count, ++it) {
            if (ll <= 0) break;
            ss << it->first << ": " << it->second.dump() << "\n";
            --ll;
        }
        return { ss.str(), lines - ll };
    }
};

} // namespace android::mediametrics
+10 −2
Original line number Diff line number Diff line
@@ -319,11 +319,19 @@ status_t MediaMetricsService::dump(int fd, const Vector<String16>& args)
                result << "-- some lines may be truncated --\n";
            }

            result << "LogSessionId:\n"
            const int32_t heatLinesToDump = all ? INT32_MAX : 20;
            const auto [ heatDumpString, heatLines] =
                    mAudioAnalytics.dumpHeatMap(heatLinesToDump);
            result << "\n" << heatDumpString;
            if (heatLines == heatLinesToDump) {
                result << "-- some lines may be truncated --\n";
            }

            result << "\nLogSessionId:\n"
                   << mediametrics::ValidateId::get()->dump();

            // Dump the statsd atoms we sent out.
            result << "Statsd atoms:\n"
            result << "\nStatsd atoms:\n"
                   << mStatsdLog->dumpToString("  " /* prefix */,
                           all ? STATSD_LOG_LINES_MAX : STATSD_LOG_LINES_DUMP);
        }
Loading