| /* |
| * 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. |
| */ |
| |
| #ifndef ANDROID_MEDIA_PERFORMANCEANALYSIS_H |
| #define ANDROID_MEDIA_PERFORMANCEANALYSIS_H |
| |
| #include <deque> |
| #include <map> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <media/nblog/Events.h> |
| #include <media/nblog/ReportPerformance.h> |
| #include <utils/Timers.h> |
| |
| namespace android { |
| |
| class String8; |
| |
| namespace ReportPerformance { |
| |
| // TODO make this a templated class and put it in a separate file. |
| // The templated parameters would be bin size and low limit. |
| /* |
| * Histogram provides a way to store numeric data in histogram format and read it as a serialized |
| * string. The terms "bin" and "bucket" are used interchangeably. |
| * |
| * This class is not thread-safe. |
| */ |
| class Histogram { |
| public: |
| struct Config { |
| const double binSize; // TODO template type |
| const size_t numBins; |
| const double low; // TODO template type |
| }; |
| |
| // Histograms are constructed with fixed configuration numbers. Dynamic configuration based |
| // the data is possible but complex because |
| // - data points are added one by one, not processed as a batch. |
| // - Histograms with different configuration parameters are tricky to aggregate, and they |
| // will need to be aggregated at the Media Metrics cloud side. |
| // - not providing limits theoretically allows for infinite number of buckets. |
| |
| /** |
| * \brief Creates a Histogram object. |
| * |
| * \param binSize the width of each bin of the histogram, must be greater than 0. |
| * Units are whatever data the caller decides to store. |
| * \param numBins the number of bins desired in the histogram range, must be greater than 0. |
| * \param low the lower bound of the histogram bucket values. |
| * Units are whatever data the caller decides to store. |
| * Note that the upper bound can be calculated by the following: |
| * upper = lower + binSize * numBins. |
| */ |
| Histogram(double binSize, size_t numBins, double low = 0.) |
| : mBinSize(binSize), mNumBins(numBins), mLow(low), mBins(mNumBins + 2) {} |
| |
| Histogram(const Config &c) |
| : Histogram(c.binSize, c.numBins, c.low) {} |
| |
| /** |
| * \brief Add a data point to the histogram. The value of the data point |
| * is rounded to the nearest multiple of the bin size (before accounting |
| * for the lower bound offset, which may not be a multiple of the bin size). |
| * |
| * \param value the value of the data point to add. |
| */ |
| void add(double value); |
| |
| /** |
| * \brief Removes all data points from the histogram. |
| */ |
| void clear(); |
| |
| /** |
| * \brief Returns the total number of data points added to the histogram. |
| * |
| * \return the total number of data points in the histogram. |
| */ |
| uint64_t totalCount() const; |
| |
| /** |
| * \brief Serializes the histogram into a string. The format is chosen to be compatible with |
| * the histogram representation to send to the Media Metrics service. |
| * |
| * The string is as follows: |
| * binSize,numBins,low,{-1|lowCount,...,binIndex|count,...,numBins|highCount} |
| * |
| * - binIndex is an integer with 0 <= binIndex < numBins. |
| * - count is the number of occurrences of the (rounded) value |
| * low + binSize * bucketIndex. |
| * - lowCount is the number of (rounded) values less than low. |
| * - highCount is the number of (rounded) values greater than or equal to |
| * low + binSize * numBins. |
| * - a binIndex may be skipped if its count is 0. |
| * |
| * \return the histogram serialized as a string. |
| */ |
| std::string toString() const; |
| |
| // Draw log scale sideways histogram as ASCII art and store as a std::string. |
| // Empty string is returned if totalCount() == 0. |
| std::string asciiArtString(size_t indent = 0) const; |
| |
| private: |
| // Histogram version number. |
| static constexpr int kVersion = 1; |
| |
| const double mBinSize; // Size of each bucket |
| const size_t mNumBins; // Number of buckets in range (excludes low and high) |
| const double mLow; // Lower bound of values |
| |
| // Data structure to store the actual histogram. Counts of bin values less than mLow |
| // are stored in mBins[0]. Bin index i corresponds to mBins[i+1]. Counts of bin values |
| // >= high are stored in mBins[mNumBins + 1]. |
| std::vector<uint64_t> mBins; |
| |
| uint64_t mTotalCount = 0; // Total number of values recorded |
| }; |
| |
| // This is essentially the same as class PerformanceAnalysis, but PerformanceAnalysis |
| // also does some additional analyzing of data, while the purpose of this struct is |
| // to hold data. |
| struct PerformanceData { |
| // TODO the Histogram::Config numbers below are for FastMixer. |
| // Specify different numbers for other thread types. |
| |
| // Values based on mUnderrunNs and mOverrunNs in FastMixer.cpp for frameCount = 192 |
| // and mSampleRate = 48000, which correspond to 2 and 7 seconds. |
| static constexpr Histogram::Config kWorkConfig = { 0.25, 20, 2.}; |
| |
| // Values based on trial and error logging. Need a better way to determine |
| // bin size and lower/upper limits. |
| static constexpr Histogram::Config kLatencyConfig = { 2., 10, 10.}; |
| |
| // Values based on trial and error logging. Need a better way to determine |
| // bin size and lower/upper limits. |
| static constexpr Histogram::Config kWarmupConfig = { 5., 10, 10.}; |
| |
| NBLog::thread_info_t threadInfo{}; |
| NBLog::thread_params_t threadParams{}; |
| |
| // Performance Data |
| Histogram workHist{kWorkConfig}; |
| Histogram latencyHist{kLatencyConfig}; |
| Histogram warmupHist{kWarmupConfig}; |
| int64_t underruns = 0; |
| static constexpr size_t kMaxSnapshotsToStore = 256; |
| std::deque<std::pair<NBLog::Event, int64_t /*timestamp*/>> snapshots; |
| int64_t overruns = 0; |
| nsecs_t active = 0; |
| nsecs_t start{systemTime()}; |
| |
| // Reset the performance data. This does not represent a thread state change. |
| // Thread info is not reset here because the data is meant to be a continuation of the thread |
| // that struct PerformanceData is associated with. |
| void reset() { |
| workHist.clear(); |
| latencyHist.clear(); |
| warmupHist.clear(); |
| underruns = 0; |
| overruns = 0; |
| active = 0; |
| start = systemTime(); |
| } |
| |
| // Return true if performance data has not been recorded yet, false otherwise. |
| bool empty() const { |
| return workHist.totalCount() == 0 && latencyHist.totalCount() == 0 |
| && warmupHist.totalCount() == 0 && underruns == 0 && overruns == 0 |
| && active == 0; |
| } |
| }; |
| |
| //------------------------------------------------------------------------------ |
| |
| class PerformanceAnalysis; |
| |
| // a map of PerformanceAnalysis instances |
| // The outer key is for the thread, the inner key for the source file location. |
| using PerformanceAnalysisMap = std::map<int, std::map<log_hash_t, PerformanceAnalysis>>; |
| |
| class PerformanceAnalysis { |
| // This class stores and analyzes audio processing wakeup timestamps from NBLog |
| // FIXME: currently, all performance data is stored in deques. Turn these into circular |
| // buffers. |
| // TODO: add a mutex. |
| public: |
| |
| PerformanceAnalysis() {}; |
| |
| friend void dump(int fd, int indent, |
| PerformanceAnalysisMap &threadPerformanceAnalysis); |
| |
| // Called in the case of an audio on/off event, e.g., EVENT_AUDIO_STATE. |
| // Used to discard idle time intervals |
| void handleStateChange(); |
| |
| // Writes wakeup timestamp entry to log and runs analysis |
| void logTsEntry(timestamp ts); |
| |
| // FIXME: make peakdetector and storeOutlierData a single function |
| // Input: mOutlierData. Looks at time elapsed between outliers |
| // finds significant changes in the distribution |
| // writes timestamps of significant changes to mPeakTimestamps |
| bool detectAndStorePeak(msInterval delta, timestamp ts); |
| |
| // stores timestamps of intervals above a threshold: these are assumed outliers. |
| // writes to mOutlierData <time elapsed since previous outlier, outlier timestamp> |
| bool detectAndStoreOutlier(const msInterval diffMs); |
| |
| // Generates a string of analysis of the buffer periods and prints to console |
| // FIXME: move this data visualization to a separate class. Model/view/controller |
| void reportPerformance(String8 *body, int author, log_hash_t hash, |
| int maxHeight = 10); |
| |
| private: |
| |
| // TODO use a circular buffer for the deques and vectors below |
| |
| // stores outlier analysis: |
| // <elapsed time between outliers in ms, outlier beginning timestamp> |
| std::deque<std::pair<msInterval, timestamp>> mOutlierData; |
| |
| // stores each timestamp at which a peak was detected |
| // a peak is a moment at which the average outlier interval changed significantly |
| std::deque<timestamp> mPeakTimestamps; |
| |
| // stores buffer period histograms with timestamp of first sample |
| std::deque<std::pair<timestamp, Hist>> mHists; |
| |
| // Parameters used when detecting outliers |
| struct BufferPeriod { |
| double mMean = -1; // average time between audio processing wakeups |
| double mOutlierFactor = -1; // values > mMean * mOutlierFactor are outliers |
| double mOutlier = -1; // this is set to mMean * mOutlierFactor |
| timestamp mPrevTs = -1; // previous timestamp |
| } mBufferPeriod; |
| |
| // capacity allocated to data structures |
| struct MaxLength { |
| size_t Hists; // number of histograms stored in memory |
| size_t Outliers; // number of values stored in outlier array |
| size_t Peaks; // number of values stored in peak array |
| int HistTimespanMs; // maximum histogram timespan |
| }; |
| // These values allow for 10 hours of data allowing for a glitch and a peak |
| // as often as every 3 seconds |
| static constexpr MaxLength kMaxLength = {.Hists = 60, .Outliers = 12000, |
| .Peaks = 12000, .HistTimespanMs = 10 * kSecPerMin * kMsPerSec }; |
| |
| // these variables ensure continuity while analyzing the timestamp |
| // series one sample at a time. |
| // TODO: change this to a running variance/mean class |
| struct OutlierDistribution { |
| msInterval mMean = 0; // sample mean since previous peak |
| msInterval mSd = 0; // sample sd since previous peak |
| msInterval mElapsed = 0; // time since previous detected outlier |
| const int kMaxDeviation = 5; // standard deviations from the mean threshold |
| msInterval mTypicalDiff = 0; // global mean of outliers |
| double mN = 0; // length of sequence since the last peak |
| double mM2 = 0; // used to calculate sd |
| } mOutlierDistribution; |
| }; |
| |
| void dump(int fd, int indent, PerformanceAnalysisMap &threadPerformanceAnalysis); |
| void dumpLine(int fd, int indent, const String8 &body); |
| |
| } // namespace ReportPerformance |
| } // namespace android |
| |
| #endif // ANDROID_MEDIA_PERFORMANCEANALYSIS_H |