MediaMetrics: Implement AudioTrack status logging
Convert error field to status field to be more generic.
Incorporate the HeatMap to track general item status.
Test: atest AudioTrackTest#testBuilderError
Test: adb shell dumpsys media.metrics
Bug: 199763036
Merged-In: I53f3064e614d7e49bed3bf63cc5e6faf19684d2f
Change-Id: I53f3064e614d7e49bed3bf63cc5e6faf19684d2f
(cherry picked from commit 73dc2f9f850e226615d4b7b832dbcd4ce49dc55d)
diff --git a/services/mediametrics/Android.bp b/services/mediametrics/Android.bp
index 0351d2d..b2c9465 100644
--- a/services/mediametrics/Android.bp
+++ b/services/mediametrics/Android.bp
@@ -183,6 +183,10 @@
"libplatformprotos",
],
+ header_libs: [
+ "libaaudio_headers",
+ ],
+
include_dirs: [
"system/media/audio_utils/include",
],
diff --git a/services/mediametrics/AudioAnalytics.cpp b/services/mediametrics/AudioAnalytics.cpp
index 270fe2f..21768f8 100644
--- a/services/mediametrics/AudioAnalytics.cpp
+++ b/services/mediametrics/AudioAnalytics.cpp
@@ -21,6 +21,7 @@
#include "AudioAnalytics.h"
+#include <aaudio/AAudio.h> // error codes
#include <audio_utils/clock.h> // clock conversions
#include <cutils/properties.h>
#include <statslog.h> // statsd
@@ -64,6 +65,59 @@
}
}
+// The status variable contains status_t codes which are used by
+// the core audio framework.
+//
+// We also consider AAudio status codes as they are non-overlapping with status_t
+// and compiler checked here.
+//
+// Caution: As AAUDIO_ERROR codes have a unique range (AAUDIO_ERROR_BASE = -900),
+// overlap with status_t should not present an issue.
+//
+// See: system/core/libutils/include/utils/Errors.h
+// frameworks/av/media/libaaudio/include/aaudio/AAudio.h
+//
+// Compare with mediametrics::statusToStatusString
+//
+inline constexpr const char* extendedStatusToStatusString(status_t status) {
+ switch (status) {
+ case BAD_VALUE: // status_t
+ case AAUDIO_ERROR_ILLEGAL_ARGUMENT:
+ case AAUDIO_ERROR_INVALID_FORMAT:
+ case AAUDIO_ERROR_INVALID_RATE:
+ case AAUDIO_ERROR_NULL:
+ case AAUDIO_ERROR_OUT_OF_RANGE:
+ return AMEDIAMETRICS_PROP_STATUS_VALUE_ARGUMENT;
+ case DEAD_OBJECT: // status_t
+ case FAILED_TRANSACTION: // status_t
+ case AAUDIO_ERROR_DISCONNECTED:
+ case AAUDIO_ERROR_INVALID_HANDLE:
+ case AAUDIO_ERROR_NO_SERVICE:
+ return AMEDIAMETRICS_PROP_STATUS_VALUE_IO;
+ case NO_MEMORY: // status_t
+ case AAUDIO_ERROR_NO_FREE_HANDLES:
+ case AAUDIO_ERROR_NO_MEMORY:
+ return AMEDIAMETRICS_PROP_STATUS_VALUE_MEMORY;
+ case PERMISSION_DENIED: // status_t
+ return AMEDIAMETRICS_PROP_STATUS_VALUE_SECURITY;
+ case INVALID_OPERATION: // status_t
+ case NO_INIT: // status_t
+ case AAUDIO_ERROR_INVALID_STATE:
+ case AAUDIO_ERROR_UNAVAILABLE:
+ case AAUDIO_ERROR_UNIMPLEMENTED:
+ return AMEDIAMETRICS_PROP_STATUS_VALUE_STATE;
+ case WOULD_BLOCK: // status_t
+ case AAUDIO_ERROR_TIMEOUT:
+ case AAUDIO_ERROR_WOULD_BLOCK:
+ return AMEDIAMETRICS_PROP_STATUS_VALUE_TIMEOUT;
+ default:
+ if (status >= 0) return AMEDIAMETRICS_PROP_STATUS_VALUE_OK; // non-negative values "OK"
+ [[fallthrough]]; // negative values are error.
+ case UNKNOWN_ERROR: // status_t
+ return AMEDIAMETRICS_PROP_STATUS_VALUE_UNKNOWN;
+ }
+}
+
static constexpr const auto LOG_LEVEL = android::base::VERBOSE;
static constexpr int PREVIOUS_STATE_EXPIRE_SEC = 60 * 60; // 1 hour.
@@ -392,11 +446,15 @@
{
if (!startsWith(item->getKey(), AMEDIAMETRICS_KEY_PREFIX_AUDIO)) return BAD_VALUE;
status_t status = mAnalyticsState->submit(item, isTrusted);
+
+ // Status is selectively authenticated.
+ processStatus(item);
+
if (status != NO_ERROR) return status; // may not be permitted.
// Only if the item was successfully submitted (permission)
// do we check triggered actions.
- checkActions(item);
+ processActions(item);
return NO_ERROR;
}
@@ -430,7 +488,7 @@
return { ss.str(), lines - ll };
}
-void AudioAnalytics::checkActions(const std::shared_ptr<const mediametrics::Item>& item)
+void AudioAnalytics::processActions(const std::shared_ptr<const mediametrics::Item>& item)
{
auto actions = mActions.getActionsForItem(item); // internally locked.
// Execute actions with no lock held.
@@ -439,6 +497,36 @@
}
}
+void AudioAnalytics::processStatus(const std::shared_ptr<const mediametrics::Item>& item)
+{
+ int32_t status;
+ if (!item->get(AMEDIAMETRICS_PROP_STATUS, &status)) return;
+
+ // Any record with a status will automatically be added to a heat map.
+ // Standard information.
+ const auto key = item->getKey();
+ const auto uid = item->getUid();
+
+ // from audio.track.10 -> prefix = audio.track, suffix = 10
+ // from audio.track.error -> prefix = audio.track, suffix = error
+ const auto [prefixKey, suffixKey] = stringutils::splitPrefixKey(key);
+
+ std::string message;
+ item->get(AMEDIAMETRICS_PROP_STATUSMESSAGE, &message); // optional
+
+ int32_t subCode = 0; // not used
+ (void)item->get(AMEDIAMETRICS_PROP_STATUSSUBCODE, &subCode); // optional
+
+ std::string eventStr; // optional
+ item->get(AMEDIAMETRICS_PROP_EVENT, &eventStr);
+
+ const std::string statusString = extendedStatusToStatusString(status);
+
+ // Add to the heat map - we automatically track every item's status to see
+ // the types of errors and the frequency of errors.
+ mHeatMap.add(prefixKey, suffixKey, eventStr, statusString, uid, message, subCode);
+}
+
// HELPER METHODS
std::string AudioAnalytics::getThreadFromTrack(const std::string& track) const
diff --git a/services/mediametrics/AudioAnalytics.h b/services/mediametrics/AudioAnalytics.h
index bcc407a..9b54cf3 100644
--- a/services/mediametrics/AudioAnalytics.h
+++ b/services/mediametrics/AudioAnalytics.h
@@ -109,11 +109,18 @@
*/
/**
- * Checks for any pending actions for a particular item.
+ * Processes any pending actions for a particular item.
*
* \param item to check against the current AnalyticsActions.
*/
- void checkActions(const std::shared_ptr<const mediametrics::Item>& item);
+ void processActions(const std::shared_ptr<const mediametrics::Item>& item);
+
+ /**
+ * Processes status information contained in the item.
+ *
+ * \param item to check against for status handling
+ */
+ void processStatus(const std::shared_ptr<const mediametrics::Item>& item);
// HELPER METHODS
/**
diff --git a/services/mediametrics/AudioTypes.cpp b/services/mediametrics/AudioTypes.cpp
index 838cdd5..b67967b 100644
--- a/services/mediametrics/AudioTypes.cpp
+++ b/services/mediametrics/AudioTypes.cpp
@@ -15,8 +15,10 @@
*/
#include "AudioTypes.h"
+#include "MediaMetricsConstants.h"
#include "StringUtils.h"
#include <media/TypeConverter.h> // requires libmedia_helper to get the Audio code.
+#include <statslog.h> // statsd
namespace android::mediametrics::types {
diff --git a/services/mediametrics/HeatMap.h b/services/mediametrics/HeatMap.h
index 230d1ad..950501a 100644
--- a/services/mediametrics/HeatMap.h
+++ b/services/mediametrics/HeatMap.h
@@ -81,7 +81,7 @@
size_t ok = 0;
size_t error = 0;
for (const auto &[name, count] : eventPair.second) {
- if (name == AMEDIAMETRICS_PROP_ERROR_VALUE_OK) {
+ if (name == AMEDIAMETRICS_PROP_STATUS_VALUE_OK) {
ok += count;
} else {
error += count;
diff --git a/services/mediametrics/StringUtils.h b/services/mediametrics/StringUtils.h
index 01034d9..a56f5b8 100644
--- a/services/mediametrics/StringUtils.h
+++ b/services/mediametrics/StringUtils.h
@@ -167,4 +167,41 @@
return ss.str();
}
+/**
+ * Returns true if the string is non-null, not empty, and contains only digits.
+ */
+inline constexpr bool isNumeric(const char *s)
+{
+ if (s == nullptr || *s == 0) return false;
+ do {
+ if (!isdigit(*s)) return false;
+ } while (*++s != 0);
+ return true; // all digits
+}
+
+/**
+ * Extracts out the prefix from the key, returning a pair of prefix, suffix.
+ *
+ * Usually the key is something like:
+ * Prefix.(ID)
+ * where ID is an integer,
+ * or "error" if the id was not returned because of failure,
+ * or "status" if general status.
+ *
+ * Example: audio.track.10 -> prefix = audio.track, suffix = 10
+ * audio.track.error -> prefix = audio.track, suffix = error
+ * audio.track.status -> prefix = audio.track, suffix = status
+ * audio.mute -> prefix = audio.mute, suffix = ""
+ */
+inline std::pair<std::string /* prefix */,
+ std::string /* suffix */> splitPrefixKey(const std::string &key)
+{
+ const size_t split = key.rfind('.');
+ const char* suffix = key.c_str() + split + 1;
+ if (*suffix && (!strcmp(suffix, "error") || !strcmp(suffix, "status") || isNumeric(suffix))) {
+ return { key.substr(0, split), suffix };
+ }
+ return { key, "" };
+}
+
} // namespace android::mediametrics::stringutils
diff --git a/services/mediametrics/tests/mediametrics_tests.cpp b/services/mediametrics/tests/mediametrics_tests.cpp
index d334676..102700a 100644
--- a/services/mediametrics/tests/mediametrics_tests.cpp
+++ b/services/mediametrics/tests/mediametrics_tests.cpp
@@ -1226,8 +1226,8 @@
}
}
-TEST(mediametrics_tests, ErrorConversion) {
- constexpr status_t errors[] = {
+TEST(mediametrics_tests, StatusConversion) {
+ constexpr status_t statuses[] = {
NO_ERROR,
BAD_VALUE,
DEAD_OBJECT,
@@ -1239,13 +1239,13 @@
};
auto roundTrip = [](status_t status) {
- return android::mediametrics::errorStringToStatus(
- android::mediametrics::statusToErrorString(status));
+ return android::mediametrics::statusStringToStatus(
+ android::mediametrics::statusToStatusString(status));
};
// Primary status error categories.
- for (const auto error : errors) {
- ASSERT_EQ(error, roundTrip(error));
+ for (const auto status : statuses) {
+ ASSERT_EQ(status, roundTrip(status));
}
// Status errors specially considered.
@@ -1260,20 +1260,20 @@
ASSERT_EQ((size_t)0, heatMap.size());
heatMap.add("someKey", "someSuffix", "someEvent",
- AMEDIAMETRICS_PROP_ERROR_VALUE_OK, UID, "message", SUBCODE);
+ AMEDIAMETRICS_PROP_STATUS_VALUE_OK, UID, "message", SUBCODE);
ASSERT_EQ((size_t)1, heatMap.size());
heatMap.add("someKey", "someSuffix", "someEvent",
- AMEDIAMETRICS_PROP_ERROR_VALUE_OK, UID, "message", SUBCODE);
+ AMEDIAMETRICS_PROP_STATUS_VALUE_OK, UID, "message", SUBCODE);
heatMap.add("someKey", "someSuffix", "anotherEvent",
- AMEDIAMETRICS_PROP_ERROR_VALUE_ARGUMENT, UID, "message", SUBCODE);
+ AMEDIAMETRICS_PROP_STATUS_VALUE_ARGUMENT, UID, "message", SUBCODE);
ASSERT_EQ((size_t)1, heatMap.size());
heatMap.add("anotherKey", "someSuffix", "someEvent",
- AMEDIAMETRICS_PROP_ERROR_VALUE_OK, UID, "message", SUBCODE);
+ AMEDIAMETRICS_PROP_STATUS_VALUE_OK, UID, "message", SUBCODE);
ASSERT_EQ((size_t)2, heatMap.size());
ASSERT_EQ((size_t)0, heatMap.rejected());
heatMap.add("thirdKey", "someSuffix", "someEvent",
- AMEDIAMETRICS_PROP_ERROR_VALUE_OK, UID, "message", SUBCODE);
+ AMEDIAMETRICS_PROP_STATUS_VALUE_OK, UID, "message", SUBCODE);
ASSERT_EQ((size_t)2, heatMap.size());
ASSERT_EQ((size_t)1, heatMap.rejected());