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: I1073f3ef95f44a383a7f14b0c2ea6f978a84ee24gugelfrei
parent
9468f951e6
commit
0f7ad8cd2c
@ -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
|
@ -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
|
Loading…
Reference in new issue