| // Copyright 2016 The Android Open Source Project |
| // |
| // This software is licensed under the terms of the GNU General Public |
| // License version 2, as published by the Free Software Foundation, and |
| // may be copied, distributed, and modified under those terms. |
| // |
| // This program is distributed in the hope that it will be useful, |
| // but WITHOUT ANY WARRANTY; without even the implied warranty of |
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| // GNU General Public License for more details. |
| |
| #include "android/metrics/PeriodicReporter.h" |
| |
| #include "android/base/memory/LazyInstance.h" |
| |
| #include <iterator> |
| #include <type_traits> |
| #include <utility> |
| |
| using android::base::AutoLock; |
| using android::base::LazyInstance; |
| using android::base::Looper; |
| using android::base::System; |
| |
| namespace android { |
| namespace metrics { |
| |
| // An instance holder object: it manages the creation of the default reporter |
| // instance. |
| struct PeriodicReporter::InstanceHolder { |
| PeriodicReporter::Ptr reporter; |
| |
| InstanceHolder() : reporter(new PeriodicReporter()) {} |
| }; |
| |
| static LazyInstance<PeriodicReporter::InstanceHolder> sReporter = {}; |
| |
| void PeriodicReporter::start(MetricsReporter* metricsReporter, Looper* looper) { |
| sReporter->reporter->startImpl(metricsReporter, looper); |
| } |
| |
| void PeriodicReporter::stop() { |
| // Make sure we create a new instance of periodic reporter here: |
| // this way we expire all weak pointers in TaskGuards and they won't |
| // try erasing from a list using invalid stale iterators. |
| sReporter->reporter.reset(new PeriodicReporter()); |
| } |
| |
| PeriodicReporter& PeriodicReporter::get() { |
| assert(sReporter->reporter.get()); |
| return *sReporter->reporter; |
| } |
| |
| PeriodicReporter::PeriodicReporter() = default; |
| |
| PeriodicReporter::~PeriodicReporter() { |
| AutoLock lock(mLock); |
| mPeriodDataByPeriod.clear(); |
| } |
| |
| void PeriodicReporter::addTask(System::Duration periodMs, Callback callback) { |
| AutoLock lock(mLock); |
| addTaskInternalNoLock(periodMs, std::move(callback)); |
| } |
| |
| PeriodicReporter::TaskToken PeriodicReporter::addCancelableTask( |
| System::Duration periodMs, |
| PeriodicReporter::Callback callback) { |
| // Capture a weak pointer to |this| into the task guard to make sure it can |
| // outlive |this| and won't try to call into a dangling pointer. |
| const auto weakThis = WeakPtr(shared_from_this()); |
| |
| AutoLock lock(mLock); |
| const auto iter = addTaskInternalNoLock(periodMs, std::move(callback)); |
| lock.unlock(); |
| |
| return std::make_shared<TaskGuard>([periodMs, iter, weakThis]() { |
| if (const auto sharedThis = weakThis.lock()) { |
| sharedThis->removeTask(periodMs, iter); |
| } |
| }); |
| } |
| |
| void PeriodicReporter::startImpl(MetricsReporter* metricsReporter, |
| Looper* looper) { |
| AutoLock lock(mLock); |
| |
| assert(!mMetricsReporter); |
| assert(!mLooper); |
| |
| assert(metricsReporter); |
| assert(looper); |
| |
| mMetricsReporter = metricsReporter; |
| mLooper = looper; |
| |
| for (auto& periodAndData : mPeriodDataByPeriod) { |
| assert(!periodAndData.second.task); |
| |
| createPerPeriodTimerNoLock(&periodAndData.second, periodAndData.first); |
| } |
| } |
| |
| void PeriodicReporter::createPerPeriodTimerNoLock(PerPeriodData* const data, |
| System::Duration periodMs) { |
| static_assert(!std::is_reference<decltype(data)>::value, |
| "|data| must not be a reference: gcc 4.6 used to have a bug " |
| "where capturing a reference by value resulted in silent bad " |
| "code generation"); |
| |
| assert(mLooper); |
| |
| auto timerFunc = [this, data]() { |
| mMetricsReporter->reportConditional( |
| [this, data](android_studio::AndroidStudioEvent* event) { |
| AutoLock lock(mLock); |
| bool result = false; |
| // Callback may erase itself from the list, deal with that |
| // by using raw iterators in the loop. |
| auto itCallback = data->callbacks.begin(); |
| while (itCallback != data->callbacks.end()) { |
| const auto itCurrentCallback = itCallback++; |
| // As task may try removing itself, unlock |
| // the object temporarily here. |
| lock.unlock(); |
| result |= (*itCurrentCallback)(event); |
| lock.lock(); |
| } |
| return result; |
| }); |
| return true; |
| }; |
| |
| data->task.emplace(mLooper, std::move(timerFunc), periodMs); |
| data->task->start(); |
| } |
| |
| PeriodicReporter::CallbackList::iterator |
| PeriodicReporter::addTaskInternalNoLock(System::Duration periodMs, |
| PeriodicReporter::Callback callback) { |
| PerPeriodData& data = mPeriodDataByPeriod[periodMs]; |
| data.callbacks.push_back(std::move(callback)); |
| if (mLooper && !data.task) { |
| createPerPeriodTimerNoLock(&data, periodMs); |
| } |
| |
| const auto myIt = std::prev(data.callbacks.end()); |
| return myIt; |
| } |
| |
| void PeriodicReporter::removeTask(System::Duration periodMs, |
| CallbackList::iterator iter) { |
| AutoLock lock(mLock); |
| |
| PerPeriodData& data = mPeriodDataByPeriod[periodMs]; |
| data.callbacks.erase(iter); |
| if (data.callbacks.empty()) { |
| data.task.clear(); |
| } |
| } |
| |
| } // namespace metrics |
| } // namespace android |