/* * Copyright (C) 2020 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_AUDIO_THREADMETRICS_H #define ANDROID_AUDIO_THREADMETRICS_H #include namespace android { /** * ThreadMetrics handles the AudioFlinger thread log statistics. * * We aggregate metrics for a particular device for proper analysis. * This includes power, performance, and usage metrics. * * This class is thread-safe with a lock for safety. There is no risk of deadlock * as this class only executes external one-way calls in Mediametrics and does not * call any other AudioFlinger class. * * Terminology: * An AudioInterval is a contiguous playback segment. * An AudioIntervalGroup is a group of continuous playback segments on the same device. * * We currently deliver metrics based on an AudioIntervalGroup. */ class ThreadMetrics final { public: ThreadMetrics(std::string metricsId, bool isOut) : mMetricsId(std::move(metricsId)) , mIsOut(isOut) {} ~ThreadMetrics() { logEndInterval(); // close any open interval groups std::lock_guard l(mLock); deliverCumulativeMetrics(AMEDIAMETRICS_PROP_EVENT_VALUE_ENDAUDIOINTERVALGROUP); mediametrics::LogItem(mMetricsId) .set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_DTOR) .record(); } // Called under the following circumstances // 1) Upon a createPatch and we are not in standby // 2) We come out of standby void logBeginInterval() { std::lock_guard l(mLock); // The devices we look for change depend on whether the Thread is input or output. const std::string& patchDevices = mIsOut ? mCreatePatchOutDevices : mCreatePatchInDevices; if (mDevices != patchDevices) { deliverCumulativeMetrics(AMEDIAMETRICS_PROP_EVENT_VALUE_ENDAUDIOINTERVALGROUP); mDevices = patchDevices; // set after endAudioIntervalGroup resetIntervalGroupMetrics(); deliverDeviceMetrics( AMEDIAMETRICS_PROP_EVENT_VALUE_BEGINAUDIOINTERVALGROUP, mDevices.c_str()); } if (mIntervalStartTimeNs == 0) { ++mIntervalCount; mIntervalStartTimeNs = systemTime(); } } void logConstructor(pid_t pid, const char *threadType, int32_t id) const { mediametrics::LogItem(mMetricsId) .setPid(pid) .set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_CTOR) .set(AMEDIAMETRICS_PROP_TYPE, threadType) .set(AMEDIAMETRICS_PROP_THREADID, id) .record(); } void logCreatePatch(const std::string& inDevices, const std::string& outDevices) { std::lock_guard l(mLock); mCreatePatchInDevices = inDevices; mCreatePatchOutDevices = outDevices; mediametrics::LogItem(mMetricsId) .set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_CREATEAUDIOPATCH) .set(AMEDIAMETRICS_PROP_INPUTDEVICES, inDevices) .set(AMEDIAMETRICS_PROP_OUTPUTDEVICES, outDevices) .record(); } // Called when we are removed from the Thread. void logEndInterval() { std::lock_guard l(mLock); if (mIntervalStartTimeNs != 0) { const int64_t elapsedTimeNs = systemTime() - mIntervalStartTimeNs; mIntervalStartTimeNs = 0; mCumulativeTimeNs += elapsedTimeNs; mDeviceTimeNs += elapsedTimeNs; } } void logThrottleMs(double throttleMs) const { mediametrics::LogItem(mMetricsId) // ms units always double .set(AMEDIAMETRICS_PROP_THROTTLEMS, (double)throttleMs) .record(); } void logLatency(double latencyMs) { mediametrics::LogItem(mMetricsId) .set(AMEDIAMETRICS_PROP_LATENCYMS, latencyMs) .record(); std::lock_guard l(mLock); mDeviceLatencyMs.add(latencyMs); } void logUnderrunFrames(size_t frames) { std::lock_guard l(mLock); if (mLastUnderrun == false && frames > 0) { ++mUnderrunCount; // count non-continguous underrun sequences. } mLastUnderrun = (frames > 0); mUnderrunFrames += frames; } const std::string& getMetricsId() const { return mMetricsId; } private: // no lock required - all arguments and constants. void deliverDeviceMetrics(const char *eventName, const char *devices) const { mediametrics::LogItem(mMetricsId) .set(AMEDIAMETRICS_PROP_EVENT, eventName) .set(mIsOut ? AMEDIAMETRICS_PROP_OUTPUTDEVICES : AMEDIAMETRICS_PROP_INPUTDEVICES, devices) .record(); } void deliverCumulativeMetrics(const char *eventName) const REQUIRES(mLock) { if (mIntervalCount > 0) { mediametrics::LogItem item(mMetricsId); item.set(AMEDIAMETRICS_PROP_CUMULATIVETIMENS, mCumulativeTimeNs) .set(AMEDIAMETRICS_PROP_DEVICETIMENS, mDeviceTimeNs) .set(AMEDIAMETRICS_PROP_EVENT, eventName) .set(AMEDIAMETRICS_PROP_INTERVALCOUNT, (int32_t)mIntervalCount); if (mDeviceLatencyMs.getN() > 0) { item.set(AMEDIAMETRICS_PROP_DEVICELATENCYMS, mDeviceLatencyMs.getMean()); } if (mUnderrunCount > 0) { item.set(AMEDIAMETRICS_PROP_UNDERRUN, (int32_t)mUnderrunCount) .set(AMEDIAMETRICS_PROP_UNDERRUNFRAMES, (int64_t)mUnderrunFrames); } item.record(); } } void resetIntervalGroupMetrics() REQUIRES(mLock) { // mDevices is not reset by clear mIntervalCount = 0; mIntervalStartTimeNs = 0; // mCumulativeTimeNs is not reset by clear. mDeviceTimeNs = 0; mDeviceLatencyMs.reset(); mLastUnderrun = false; mUnderrunCount = 0; mUnderrunFrames = 0; } const std::string mMetricsId; const bool mIsOut; // if true, than a playback track, otherwise used for record. mutable std::mutex mLock; // Devices in the interval group. std::string mDevices GUARDED_BY(mLock); // last input or output devices based on mIsOut. std::string mCreatePatchInDevices GUARDED_BY(mLock); std::string mCreatePatchOutDevices GUARDED_BY(mLock); // Number of intervals and playing time int32_t mIntervalCount GUARDED_BY(mLock) = 0; int64_t mIntervalStartTimeNs GUARDED_BY(mLock) = 0; int64_t mCumulativeTimeNs GUARDED_BY(mLock) = 0; int64_t mDeviceTimeNs GUARDED_BY(mLock) = 0; // latency and startup for each interval. audio_utils::Statistics mDeviceLatencyMs GUARDED_BY(mLock); // underrun count and frames bool mLastUnderrun GUARDED_BY(mLock) = false; // checks consecutive underruns int64_t mUnderrunCount GUARDED_BY(mLock) = 0; // number of consecutive underruns int64_t mUnderrunFrames GUARDED_BY(mLock) = 0; // total estimated frames underrun }; } // namespace android #endif // ANDROID_AUDIO_THREADMETRICS_H