MediaMetrics: Add AnalyticsActions and AnalyticsState

Factor out common analytics code from audio specifics.

Allow for saving and clearing analytics state if audioserver crashes.

Test: atest mediametrics_tests
Test: instrumented check on audioserver restart
Bug: 138583596
Change-Id: I1073f3ef95f44a383a7f14b0c2ea6f978a84ee24
gugelfrei
Andy Hung 5 years ago
parent 9468f951e6
commit 0f7ad8cd2c

@ -1004,7 +1004,81 @@ public:
const char *toCString();
const char *toCString(int version);
/**
* Returns true if the item has a property with a target value.
*
* If propName is nullptr, hasPropElem() returns false.
*
* \param propName is the property name.
* \param elem is the value to match. std::monostate matches any.
*/
bool hasPropElem(const char *propName, const Prop::Elem& elem) const {
if (propName == nullptr) return false;
const Prop::Elem *e = get(propName);
return e != nullptr && (std::holds_alternative<std::monostate>(elem) || elem == *e);
}
/**
* Returns -2, -1, 0 (success) if the item has a property (wildcard matched) with a
* target value.
*
* The enum RecursiveWildcardCheck designates the meaning of the returned value.
*
* RECURSIVE_WILDCARD_CHECK_NO_MATCH_NO_WILDCARD = -2,
* RECURSIVE_WILDCARD_CHECK_NO_MATCH_WILDCARD_FOUND = -1,
* RECURSIVE_WILDCARD_CHECK_MATCH_FOUND = 0.
*
* If url is nullptr, RECURSIVE_WILDCARD_CHECK_NO_MATCH_NO_WILDCARD is returned.
*
* \param url is the full item + property name, which may have wildcards '*'
* denoting an arbitrary sequence of 0 or more characters.
* \param elem is the target property value to match. std::monostate matches any.
* \return 0 if the property was matched,
* -1 if the property was not matched and a wildcard char was encountered,
* -2 if the property was not matched with no wildcard char encountered.
*/
enum RecursiveWildcardCheck {
RECURSIVE_WILDCARD_CHECK_NO_MATCH_NO_WILDCARD = -2,
RECURSIVE_WILDCARD_CHECK_NO_MATCH_WILDCARD_FOUND = -1,
RECURSIVE_WILDCARD_CHECK_MATCH_FOUND = 0,
};
enum RecursiveWildcardCheck recursiveWildcardCheckElem(
const char *url, const Prop::Elem& elem) const {
if (url == nullptr) return RECURSIVE_WILDCARD_CHECK_NO_MATCH_NO_WILDCARD;
return recursiveWildcardCheckElem(getKey().c_str(), url, elem);
}
private:
enum RecursiveWildcardCheck recursiveWildcardCheckElem(
const char *itemKeyPtr, const char *url, const Prop::Elem& elem) const {
for (; *url && *itemKeyPtr; ++url, ++itemKeyPtr) {
if (*url != *itemKeyPtr) {
if (*url == '*') { // wildcard
++url;
while (true) {
if (recursiveWildcardCheckElem(itemKeyPtr, url, elem)
== RECURSIVE_WILDCARD_CHECK_MATCH_FOUND) {
return RECURSIVE_WILDCARD_CHECK_MATCH_FOUND;
}
if (*itemKeyPtr == 0) break;
++itemKeyPtr;
}
return RECURSIVE_WILDCARD_CHECK_NO_MATCH_WILDCARD_FOUND;
}
return RECURSIVE_WILDCARD_CHECK_NO_MATCH_NO_WILDCARD;
}
}
if (itemKeyPtr[0] != 0 || url[0] != '.') {
return RECURSIVE_WILDCARD_CHECK_NO_MATCH_NO_WILDCARD;
}
const char *propName = url + 1; // skip the '.'
return hasPropElem(propName, elem)
? RECURSIVE_WILDCARD_CHECK_MATCH_FOUND
: RECURSIVE_WILDCARD_CHECK_NO_MATCH_NO_WILDCARD;
}
// handle Parcel version 0
int32_t writeToParcel0(Parcel *) const;
int32_t readFromParcel0(const Parcel&);

@ -0,0 +1,150 @@
/*
* 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.
*/
#pragma once
#include <media/MediaMetricsItem.h>
#include <mutex>
namespace android::mediametrics {
/**
* AnalyticsActions consists of a map of pairs <trigger, action> which
* are evaluated for a given incoming MediaMetrics item.
*
* A vector of Actions are returned from getActionsForItem() which
* should be executed outside of any locks.
*
* Mediametrics assumes weak consistency, which is fine as the analytics database
* is generally strictly increasing in size (until gc removes values that are
* supposedly no longer needed).
*/
class AnalyticsActions {
public:
using Elem = mediametrics::Item::Prop::Elem;
/**
* Trigger: a pair consisting of
* std::string: A wildcard url specifying a property in the item,
* where '*' indicates 0 or more arbitrary characters
* for the item key match.
* Elem: A value that needs to match exactly.
*
* Trigger is used in a map sort; default less with std::string as primary key.
* The wildcard accepts a string with '*' as being 0 or more arbitrary
* characters for the item key match. A wildcard is preferred over general
* regexp for simple fast lookup.
*
* TODO: incorporate a regexp option.
*/
using Trigger = std::pair<std::string, Elem>;
/**
* Function: The function to be executed.
*/
using Function = std::function<
void(const std::shared_ptr<const mediametrics::Item>& item)>;
/**
* Action: An action to execute. This is a shared pointer to Function.
*/
using Action = std::shared_ptr<Function>;
/**
* Adds a new action.
*
* \param url references a property in the item with wildcards
* \param value references a value (cast to Elem automatically)
* so be careful of the type. It must be one of
* the types acceptable to Elem.
* \param action is a function or lambda to execute if the url matches value
* in the item.
*/
template <typename T, typename U, typename A>
void addAction(T&& url, U&& value, A&& action) {
std::lock_guard l(mLock);
mFilters[ { std::forward<T>(url), std::forward<U>(value) } ]
= std::forward<A>(action);
}
// TODO: remove an action.
/**
* Get all the actions triggered for a particular item.
*
* \param item to be analyzed for actions.
*/
std::vector<Action>
getActionsForItem(const std::shared_ptr<const mediametrics::Item>& item) {
std::vector<Action> actions;
std::lock_guard l(mLock);
// Essentially the code looks like this:
/*
for (auto &[trigger, action] : mFilters) {
if (isMatch(trigger, item)) {
actions.push_back(action);
}
}
*/
// Optimization: there should only be one match for a non-wildcard url.
auto it = mFilters.upper_bound( {item->getKey(), std::monostate{} });
if (it != mFilters.end()) {
const auto &[trigger, action] = *it;
if (isMatch(trigger, item)) {
actions.push_back(action);
}
}
// Optimization: for wildcard URLs we go backwards until there is no
// match with the prefix before the wildcard.
while (it != mFilters.begin()) { // this walks backwards, cannot start at begin.
const auto &[trigger, action] = *--it; // look backwards
int ret = isWildcardMatch(trigger, item);
if (ret == mediametrics::Item::RECURSIVE_WILDCARD_CHECK_MATCH_FOUND) {
actions.push_back(action); // match found.
} else if (ret == mediametrics::Item::RECURSIVE_WILDCARD_CHECK_NO_MATCH_NO_WILDCARD) {
break; // no match before wildcard.
}
// a wildcard was encountered when matching prefix, so we should check again.
}
return actions;
}
private:
static inline bool isMatch(const Trigger& trigger,
const std::shared_ptr<const mediametrics::Item>& item) {
const auto& [key, elem] = trigger;
if (!startsWith(key, item->getKey())) return false;
// The trigger key is in format (item key).propName, so + 1 skips '.' delimeter.
const char *propName = key.c_str() + item->getKey().size() + 1;
return item->hasPropElem(propName, elem);
}
static inline int isWildcardMatch(const Trigger& trigger,
const std::shared_ptr<const mediametrics::Item>& item) {
const auto& [key, elem] = trigger;
return item->recursiveWildcardCheckElem(key.c_str(), elem);
}
mutable std::mutex mLock;
std::map<Trigger, Action> mFilters; // GUARDED_BY mLock
};
} // namespace android::mediametrics

@ -0,0 +1,113 @@
/*
* 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.
*/
#pragma once
#include "TimeMachine.h"
#include "TransactionLog.h"
namespace android::mediametrics {
/**
* AnalyticsState consists of a TimeMachine and TransactionLog for a set
* of MediaMetrics Items.
*
* One can add new Items with the submit() method.
*
* The AnalyticsState may be cleared or duplicated to preserve state after crashes
* in services are detected.
*
* As its members may not be moveable due to mutexes, we use this encapsulation
* with a shared pointer in order to save it or duplicate it.
*/
class AnalyticsState {
public:
/**
* Returns success if AnalyticsState accepts the item.
*
* A trusted source can create a new key, an untrusted source
* can only modify the key if the uid will match that authorized
* on the existing key.
*
* \param item the item to be submitted.
* \param isTrusted whether the transaction comes from a trusted source.
* In this case, a trusted source is verified by binder
* UID to be a system service by MediaMetrics service.
* Do not use true if you haven't really checked!
*
* \return NO_ERROR on success or
* PERMISSION_DENIED if the item cannot be put into the AnalyticsState.
*/
status_t submit(const std::shared_ptr<const mediametrics::Item>& item, bool isTrusted) {
return mTimeMachine.put(item, isTrusted) ?: mTransactionLog.put(item);
}
/**
* Returns a pair consisting of the dump string, and the number of lines in the string.
*
* The number of lines in the returned pair is used as an optimization
* for subsequent line limiting.
*
* The TimeMachine and the TransactionLog are dumped separately under
* different locks, so may not be 100% consistent with the last data
* delivered.
*
* \param lines the maximum number of lines in the string returned.
*/
std::pair<std::string, int32_t> dump(int32_t lines = INT32_MAX) const {
std::stringstream ss;
int32_t ll = lines;
if (ll > 0) {
ss << "TransactionLog:\n";
--ll;
}
if (ll > 0) {
auto [s, l] = mTransactionLog.dump(ll);
ss << s;
ll -= l;
}
if (ll > 0) {
ss << "TimeMachine:\n";
--ll;
}
if (ll > 0) {
auto [s, l] = mTimeMachine.dump(ll);
ss << s;
ll -= l;
}
return { ss.str(), lines - ll };
}
/**
* Clears the AnalyticsState.
*/
void clear() {
mTimeMachine.clear();
mTransactionLog.clear();
}
private:
// Note: TimeMachine and TransactionLog are individually locked.
// Access to these objects under multiple threads will be weakly synchronized,
// which is acceptable as modifications only increase the history (or with GC,
// eliminates very old history).
TimeMachine mTimeMachine;
TransactionLog mTransactionLog;
};
} // namespace android::mediametrics

@ -27,6 +27,22 @@ namespace android::mediametrics {
AudioAnalytics::AudioAnalytics()
{
ALOGD("%s", __func__);
// Add action to save AnalyticsState if audioserver is restarted.
// This triggers on an item of "audio.flinger"
// with a property "event" set to "AudioFlinger" (the constructor).
mActions.addAction(
"audio.flinger.event",
std::string("AudioFlinger"),
std::make_shared<AnalyticsActions::Function>(
[this](const std::shared_ptr<const android::mediametrics::Item> &){
ALOGW("Audioflinger() constructor event detected");
mPreviousAnalyticsState.set(std::make_shared<AnalyticsState>(
*mAnalyticsState.get()));
// Note: get returns shared_ptr temp, whose lifetime is extended
// to end of full expression.
mAnalyticsState->clear(); // TODO: filter the analytics state.
}));
}
AudioAnalytics::~AudioAnalytics()
@ -37,11 +53,14 @@ AudioAnalytics::~AudioAnalytics()
status_t AudioAnalytics::submit(
const std::shared_ptr<const mediametrics::Item>& item, bool isTrusted)
{
if (startsWith(item->getKey(), "audio.")) {
return mTimeMachine.put(item, isTrusted)
?: mTransactionLog.put(item);
}
return BAD_VALUE;
if (!startsWith(item->getKey(), "audio.")) return BAD_VALUE;
status_t status = mAnalyticsState->submit(item, isTrusted);
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);
return NO_ERROR;
}
std::pair<std::string, int32_t> AudioAnalytics::dump(int32_t lines) const
@ -50,24 +69,29 @@ std::pair<std::string, int32_t> AudioAnalytics::dump(int32_t lines) const
int32_t ll = lines;
if (ll > 0) {
ss << "TransactionLog:\n";
--ll;
}
if (ll > 0) {
auto [s, l] = mTransactionLog.dump(ll);
auto [s, l] = mAnalyticsState->dump(ll);
ss << s;
ll -= l;
}
if (ll > 0) {
ss << "TimeMachine:\n";
ss << "Prior audioserver state:\n";
--ll;
}
if (ll > 0) {
auto [s, l] = mTimeMachine.dump(ll);
auto [s, l] = mPreviousAnalyticsState->dump(ll);
ss << s;
ll -= l;
}
return { ss.str(), lines - ll };
}
void AudioAnalytics::checkActions(const std::shared_ptr<const mediametrics::Item>& item)
{
auto actions = mActions.getActionsForItem(item); // internally locked.
// Execute actions with no lock held.
for (const auto& action : actions) {
(*action)(item);
}
}
} // namespace android

@ -16,8 +16,9 @@
#pragma once
#include "TimeMachine.h"
#include "TransactionLog.h"
#include "AnalyticsActions.h"
#include "AnalyticsState.h"
#include "Wrap.h"
namespace android::mediametrics {
@ -27,7 +28,6 @@ public:
AudioAnalytics();
~AudioAnalytics();
// TODO: update with conditions for keys.
/**
* Returns success if AudioAnalytics recognizes item.
*
@ -42,6 +42,10 @@ public:
* In this case, a trusted source is verified by binder
* UID to be a system service by MediaMetrics service.
* Do not use true if you haven't really checked!
*
* \return NO_ERROR on success,
* PERMISSION_DENIED if the item cannot be put into the AnalyticsState,
* BAD_VALUE if the item key does not start with "audio.".
*/
status_t submit(const std::shared_ptr<const mediametrics::Item>& item, bool isTrusted);
@ -60,9 +64,22 @@ public:
std::pair<std::string, int32_t> dump(int32_t lines = INT32_MAX) const;
private:
// The following are locked internally
TimeMachine mTimeMachine;
TransactionLog mTransactionLog;
/**
* Checks for 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);
// Actions is individually locked
AnalyticsActions mActions;
// AnalyticsState is individually locked, and we use SharedPtrWrap
// to allow safe access even if the shared pointer changes underneath.
SharedPtrWrap<AnalyticsState> mAnalyticsState;
SharedPtrWrap<AnalyticsState> mPreviousAnalyticsState;
};
} // namespace android::mediametrics

@ -291,7 +291,11 @@ status_t MediaMetricsService::dump(int fd, const Vector<String16>& args)
}
// TODO: maybe consider a better way of dumping audio analytics info.
constexpr int32_t linesToDump = 1000;
result.append(mAudioAnalytics.dump(linesToDump).first.c_str());
auto [ dumpString, lines ] = mAudioAnalytics.dump(linesToDump);
result.append(dumpString.c_str());
if (lines == linesToDump) {
result.append("-- some lines may be truncated --\n");
}
}
write(fd, result.string(), result.size());

@ -0,0 +1,190 @@
/*
* 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.
*/
#pragma once
#include <memory>
#include <mutex>
namespace android::mediametrics {
/**
* Wraps a shared-ptr for which member access through operator->() behaves
* as if the shared-ptr is atomically copied and then (without a lock) -> called.
*
* See related C++ 20:
* https://en.cppreference.com/w/cpp/memory/shared_ptr/atomic2
*
* EXAMPLE:
*
* SharedPtrWrap<T> t{};
*
* thread1() {
* t->func(); // safely executes either the original t or the one created by thread2.
* }
*
* thread2() {
* t.set(std::make_shared<T>()); // overwrites the original t.
* }
*/
template <typename T>
class SharedPtrWrap {
mutable std::mutex mLock;
std::shared_ptr<T> mPtr;
public:
template <typename... Args>
explicit SharedPtrWrap(Args&&... args)
: mPtr(std::make_shared<T>(std::forward<Args>(args)...))
{}
/**
* Gets the current shared pointer. This must return a value, not a reference.
*
* For compatibility with existing shared_ptr, we do not pass back a
* shared_ptr<const T> for the const getter.
*/
std::shared_ptr<T> get() const {
std::lock_guard lock(mLock);
return mPtr;
}
/**
* Sets the current shared pointer, returning the previous shared pointer.
*/
std::shared_ptr<T> set(std::shared_ptr<T> ptr) { // pass by value as we use swap.
std::lock_guard lock(mLock);
std::swap(ptr, mPtr);
return ptr;
}
/**
* Returns a shared pointer value representing T at the instant of time when
* the call executes. The lifetime of the shared pointer will
* be extended as we are returning an instance of the shared_ptr
* not a reference to it. The destructor to the returned shared_ptr
* will be called sometime after the expression including the member function or
* the member variable is evaluated. Do not change to a reference!
*/
// For compatibility with existing shared_ptr, we do not pass back a
// shared_ptr<const T> for the const operator pointer access.
std::shared_ptr<T> operator->() const {
return get();
}
/**
* We do not overload operator*() as the reference is not stable if the
* lock is not held.
*/
};
/**
* Wraps member access to the class T by a lock.
*
* The object T is constructed within the LockWrap to guarantee
* locked access at all times. When T's methods are accessed through ->,
* a monitor style lock is obtained to prevent multiple threads from executing
* methods in the object T at the same time.
* Suggested by Kevin R.
*
* EXAMPLE:
*
* // Accumulator class which is very slow, requires locking for multiple threads.
*
* class Accumulator {
* int32_t value_ = 0;
* public:
* void add(int32_t incr) {
* const int32_t temp = value_;
* sleep(0); // yield
* value_ = temp + incr;
* }
* int32_t get() { return value_; }
* };
*
* // We use LockWrap on Accumulator to have safe multithread access.
* android::mediametrics::LockWrap<Accumulator> a{}; // locked accumulator succeeds
*
* // Conversely, the following line fails:
* // auto a = std::make_shared<Accumulator>(); // this fails, only 50% adds atomic.
*
* constexpr size_t THREADS = 100;
* constexpr size_t ITERATIONS = 10;
* constexpr int32_t INCREMENT = 1;
*
* // Test by generating multiple threads, all adding simultaneously.
* std::vector<std::future<void>> threads(THREADS);
* for (size_t i = 0; i < THREADS; ++i) {
* threads.push_back(std::async(std::launch::async, [&] {
* for (size_t j = 0; j < ITERATIONS; ++j) {
* a->add(INCREMENT); // add needs locked access here.
* }
* }));
* }
* threads.clear();
*
* // If the add operations are not atomic, value will be smaller than expected.
* ASSERT_EQ(INCREMENT * THREADS * ITERATIONS, (size_t)a->get());
*
*/
template <typename T>
class LockWrap {
/**
* Holding class that keeps the pointer and the lock.
*
* We return this holding class from operator->() to keep the lock until the
* method function or method variable access is completed.
*/
class LockedPointer {
friend LockWrap;
LockedPointer(T *t, std::mutex *lock)
: mT(t), mLock(*lock) {}
T* const mT;
std::lock_guard<std::mutex> mLock;
public:
const T* operator->() const {
return mT;
}
T* operator->() {
return mT;
}
};
mutable std::mutex mLock;
mutable T mT;
public:
template <typename... Args>
explicit LockWrap(Args&&... args) : mT(std::forward<Args>(args)...) {}
const LockedPointer operator->() const {
return LockedPointer(&mT, &mLock);
}
LockedPointer operator->() {
return LockedPointer(&mT, &mLock);
}
// @TestApi
bool isLocked() const {
if (mLock.try_lock()) {
mLock.unlock();
return false; // we were able to get the lock.
}
return true; // we were NOT able to get the lock.
}
};
} // namespace android::mediametrics

@ -52,6 +52,117 @@ TEST(mediametrics_tests, defer) {
ASSERT_EQ(true, check);
}
TEST(mediametrics_tests, shared_ptr_wrap) {
// Test shared pointer wrap with simple access
android::mediametrics::SharedPtrWrap<std::string> s("123");
ASSERT_EQ('1', s->at(0));
ASSERT_EQ('2', s->at(1));
s->push_back('4');
ASSERT_EQ('4', s->at(3));
const android::mediametrics::SharedPtrWrap<std::string> s2("345");
ASSERT_EQ('3', s2->operator[](0)); // s2[0] == '3'
// we allow modification through a const shared pointer wrap
// for compatibility with shared_ptr.
s2->push_back('6');
ASSERT_EQ('6', s2->operator[](3)); // s2[3] == '6'
android::mediametrics::SharedPtrWrap<std::string> s3("");
s3.set(std::make_shared<std::string>("abc"));
ASSERT_EQ('b', s3->operator[](1)); // s2[1] = 'b';
// Use Thunk to check whether the destructor was called prematurely
// when setting the shared ptr wrap in the middle of a method.
class Thunk {
std::function<void(int)> mF;
const int mFinal;
public:
Thunk(decltype(mF) f, int final) : mF(f), mFinal(final) {}
~Thunk() { mF(mFinal); }
void thunk(int value) { mF(value); }
};
int counter = 0;
android::mediametrics::SharedPtrWrap<Thunk> s4(
[&](int value) {
s4.set(std::make_shared<Thunk>([](int){}, 0)); // recursively set s4 while in s4.
++counter;
ASSERT_EQ(value, counter); // on thunk() value is 1, on destructor this is 2.
}, 2);
// This will fail if the shared ptr wrap doesn't hold a ref count during method access.
s4->thunk(1);
}
TEST(mediametrics_tests, lock_wrap) {
// Test lock wrap with simple access
android::mediametrics::LockWrap<std::string> s("123");
ASSERT_EQ('1', s->at(0));
ASSERT_EQ('2', s->at(1));
s->push_back('4');
ASSERT_EQ('4', s->at(3));
const android::mediametrics::LockWrap<std::string> s2("345");
ASSERT_EQ('3', s2->operator[](0)); // s2[0] == '3'
// note: we can't modify s2 due to const, s2->push_back('6');
android::mediametrics::LockWrap<std::string> s3("");
s3->operator=("abc");
ASSERT_EQ('b', s3->operator[](1)); // s2[1] = 'b';
// Use Thunk to check whether we have the lock when calling a method through LockWrap.
class Thunk {
std::function<void()> mF;
public:
Thunk(decltype(mF) f) : mF(f) {}
void thunk() { mF(); }
};
android::mediametrics::LockWrap<Thunk> s4([&]{
ASSERT_EQ(true, s4.isLocked()); // we must be locked when thunk() is called.
});
// This will fail if we are not locked during method access.
s4->thunk();
}
TEST(mediametrics_tests, lock_wrap_multithread) {
class Accumulator {
int32_t value_ = 0;
public:
void add(int32_t incr) {
const int32_t temp = value_;
sleep(0); // yield
value_ = temp + incr;
}
int32_t get() { return value_; }
};
android::mediametrics::LockWrap<Accumulator> a{}; // locked accumulator succeeds
// auto a = std::make_shared<Accumulator>(); // this fails, only 50% adds atomic.
constexpr size_t THREADS = 100;
constexpr size_t ITERATIONS = 10;
constexpr int32_t INCREMENT = 1;
std::vector<std::future<void>> threads(THREADS);
for (size_t i = 0; i < THREADS; ++i) {
threads.push_back(std::async(std::launch::async, [&] {
for (size_t j = 0; j < ITERATIONS; ++j) {
a->add(INCREMENT);
}
}));
}
threads.clear();
// If the add operations are not atomic, value will be smaller than expected.
ASSERT_EQ(INCREMENT * THREADS * ITERATIONS, (size_t)a->get());
}
TEST(mediametrics_tests, instantiate) {
sp mediaMetrics = new MediaMetricsService();
status_t status;
@ -592,6 +703,61 @@ TEST(mediametrics_tests, transaction_log_gc) {
ASSERT_EQ((size_t)2, transactionLog.size());
}
TEST(mediametrics_tests, analytics_actions) {
mediametrics::AnalyticsActions analyticsActions;
bool action1 = false;
bool action2 = false;
bool action3 = false;
bool action4 = false;
// check to see whether various actions have been matched.
analyticsActions.addAction(
"audio.flinger.event",
std::string("AudioFlinger"),
std::make_shared<mediametrics::AnalyticsActions::Function>(
[&](const std::shared_ptr<const android::mediametrics::Item> &) {
action1 = true;
}));
analyticsActions.addAction(
"audio.*.event",
std::string("AudioFlinger"),
std::make_shared<mediametrics::AnalyticsActions::Function>(
[&](const std::shared_ptr<const android::mediametrics::Item> &) {
action2 = true;
}));
analyticsActions.addAction("audio.fl*n*g*r.event",
std::string("AudioFlinger"),
std::make_shared<mediametrics::AnalyticsActions::Function>(
[&](const std::shared_ptr<const android::mediametrics::Item> &) {
action3 = true;
}));
analyticsActions.addAction("audio.fl*gn*r.event",
std::string("AudioFlinger"),
std::make_shared<mediametrics::AnalyticsActions::Function>(
[&](const std::shared_ptr<const android::mediametrics::Item> &) {
action4 = true;
}));
// make a test item
auto item = std::make_shared<mediametrics::Item>("audio.flinger");
(*item).set("event", "AudioFlinger");
// get the actions and execute them
auto actions = analyticsActions.getActionsForItem(item);
for (const auto& action : actions) {
action->operator()(item);
}
// The following should match.
ASSERT_EQ(true, action1);
ASSERT_EQ(true, action2);
ASSERT_EQ(true, action3);
ASSERT_EQ(false, action4); // audio.fl*gn*r != audio.flinger
}
TEST(mediametrics_tests, audio_analytics_permission) {
auto item = std::make_shared<mediametrics::Item>("audio.1");
(*item).set("one", (int32_t)1)
@ -614,14 +780,14 @@ TEST(mediametrics_tests, audio_analytics_permission) {
// TODO: Verify contents of AudioAnalytics.
// Currently there is no getter API in AudioAnalytics besides dump.
ASSERT_EQ(4, audioAnalytics.dump(1000).second /* lines */);
ASSERT_EQ(9, audioAnalytics.dump(1000).second /* lines */);
ASSERT_EQ(NO_ERROR, audioAnalytics.submit(item, true /* isTrusted */));
// untrusted entities can add to an existing key
ASSERT_EQ(NO_ERROR, audioAnalytics.submit(item2, false /* isTrusted */));
// Check that we have some info in the dump.
ASSERT_LT(4, audioAnalytics.dump(1000).second /* lines */);
ASSERT_LT(9, audioAnalytics.dump(1000).second /* lines */);
}
TEST(mediametrics_tests, audio_analytics_dump) {

Loading…
Cancel
Save