/* * Copyright (C) 2019, 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. */ #include #include #include #include #include #include #include #include #include #include #include #include using Status = ::ndk::ScopedAStatus; using aidl::android::os::BnPullAtomCallback; using aidl::android::os::IPullAtomResultReceiver; using aidl::android::os::IStatsd; using aidl::android::util::StatsEventParcel; using ::ndk::SharedRefBase; struct AStatsEventList { std::vector data; }; AStatsEvent* AStatsEventList_addStatsEvent(AStatsEventList* pull_data) { AStatsEvent* event = AStatsEvent_obtain(); pull_data->data.push_back(event); return event; } constexpr int64_t DEFAULT_COOL_DOWN_MILLIS = 1000LL; // 1 second. constexpr int64_t DEFAULT_TIMEOUT_MILLIS = 2000LL; // 2 seconds. struct AStatsManager_PullAtomMetadata { int64_t cool_down_millis; int64_t timeout_millis; std::vector additive_fields; }; AStatsManager_PullAtomMetadata* AStatsManager_PullAtomMetadata_obtain() { AStatsManager_PullAtomMetadata* metadata = new AStatsManager_PullAtomMetadata(); metadata->cool_down_millis = DEFAULT_COOL_DOWN_MILLIS; metadata->timeout_millis = DEFAULT_TIMEOUT_MILLIS; metadata->additive_fields = std::vector(); return metadata; } void AStatsManager_PullAtomMetadata_release(AStatsManager_PullAtomMetadata* metadata) { delete metadata; } void AStatsManager_PullAtomMetadata_setCoolDownMillis(AStatsManager_PullAtomMetadata* metadata, int64_t cool_down_millis) { metadata->cool_down_millis = cool_down_millis; } int64_t AStatsManager_PullAtomMetadata_getCoolDownMillis(AStatsManager_PullAtomMetadata* metadata) { return metadata->cool_down_millis; } void AStatsManager_PullAtomMetadata_setTimeoutMillis(AStatsManager_PullAtomMetadata* metadata, int64_t timeout_millis) { metadata->timeout_millis = timeout_millis; } int64_t AStatsManager_PullAtomMetadata_getTimeoutMillis(AStatsManager_PullAtomMetadata* metadata) { return metadata->timeout_millis; } void AStatsManager_PullAtomMetadata_setAdditiveFields(AStatsManager_PullAtomMetadata* metadata, int32_t* additive_fields, int32_t num_fields) { metadata->additive_fields.assign(additive_fields, additive_fields + num_fields); } int32_t AStatsManager_PullAtomMetadata_getNumAdditiveFields( AStatsManager_PullAtomMetadata* metadata) { return metadata->additive_fields.size(); } void AStatsManager_PullAtomMetadata_getAdditiveFields(AStatsManager_PullAtomMetadata* metadata, int32_t* fields) { std::copy(metadata->additive_fields.begin(), metadata->additive_fields.end(), fields); } class StatsPullAtomCallbackInternal : public BnPullAtomCallback { public: StatsPullAtomCallbackInternal(const AStatsManager_PullAtomCallback callback, void* cookie, const int64_t coolDownMillis, const int64_t timeoutMillis, const std::vector additiveFields) : mCallback(callback), mCookie(cookie), mCoolDownMillis(coolDownMillis), mTimeoutMillis(timeoutMillis), mAdditiveFields(additiveFields) {} Status onPullAtom(int32_t atomTag, const std::shared_ptr& resultReceiver) override { AStatsEventList statsEventList; int successInt = mCallback(atomTag, &statsEventList, mCookie); bool success = successInt == AStatsManager_PULL_SUCCESS; // Convert stats_events into StatsEventParcels. std::vector parcels; // Resolves fuzz build failure in b/161575591. #if defined(__ANDROID_APEX__) || defined(LIB_STATS_PULL_TESTS_FLAG) for (int i = 0; i < statsEventList.data.size(); i++) { size_t size; uint8_t* buffer = AStatsEvent_getBuffer(statsEventList.data[i], &size); StatsEventParcel p; // vector.assign() creates a copy, but this is inevitable unless // stats_event.h/c uses a vector as opposed to a buffer. p.buffer.assign(buffer, buffer + size); parcels.push_back(std::move(p)); } #endif Status status = resultReceiver->pullFinished(atomTag, success, parcels); if (!status.isOk()) { std::vector emptyParcels; resultReceiver->pullFinished(atomTag, /*success=*/false, emptyParcels); } for (int i = 0; i < statsEventList.data.size(); i++) { AStatsEvent_release(statsEventList.data[i]); } return Status::ok(); } int64_t getCoolDownMillis() const { return mCoolDownMillis; } int64_t getTimeoutMillis() const { return mTimeoutMillis; } const std::vector& getAdditiveFields() const { return mAdditiveFields; } private: const AStatsManager_PullAtomCallback mCallback; void* mCookie; const int64_t mCoolDownMillis; const int64_t mTimeoutMillis; const std::vector mAdditiveFields; }; /** * @brief pullAtomMutex is used to guard simultaneous access to mPullers from below threads * Main thread * - AStatsManager_setPullAtomCallback() * - AStatsManager_clearPullAtomCallback() * Binder thread: * - StatsdProvider::binderDied() */ static std::mutex pullAtomMutex; static std::map> mPullers; class StatsdProvider { public: StatsdProvider() : deathRecipient(AIBinder_DeathRecipient_new(binderDied)) { } ~StatsdProvider() { resetStatsService(); } std::shared_ptr getStatsService() { std::lock_guard lock(statsdMutex); if (!statsd) { // Fetch statsd ::ndk::SpAIBinder binder(AServiceManager_getService("stats")); statsd = IStatsd::fromBinder(binder); if (statsd) { AIBinder_linkToDeath(binder.get(), deathRecipient.get(), this); } } return statsd; } void resetStatsService() { std::lock_guard lock(statsdMutex); statsd = nullptr; } static void binderDied(void* cookie) { StatsdProvider* statsProvider = static_cast(cookie); statsProvider->resetStatsService(); std::shared_ptr statsService = statsProvider->getStatsService(); if (statsService == nullptr) { return; } // Since we do not want to make an IPC with the lock held, we first create a // copy of the data with the lock held before iterating through the map. std::map> pullersCopy; { std::lock_guard lock(pullAtomMutex); pullersCopy = mPullers; } for (const auto& it : pullersCopy) { statsService->registerNativePullAtomCallback(it.first, it.second->getCoolDownMillis(), it.second->getTimeoutMillis(), it.second->getAdditiveFields(), it.second); } } private: /** * @brief statsdMutex is used to guard simultaneous access to statsd from below threads: * Work thread * - registerStatsPullAtomCallbackBlocking() * - unregisterStatsPullAtomCallbackBlocking() * Binder thread: * - StatsdProvider::binderDied() */ std::mutex statsdMutex; std::shared_ptr statsd; ::ndk::ScopedAIBinder_DeathRecipient deathRecipient; }; static std::shared_ptr statsProvider = std::make_shared(); void registerStatsPullAtomCallbackBlocking(int32_t atomTag, std::shared_ptr statsProvider, std::shared_ptr cb) { const std::shared_ptr statsService = statsProvider->getStatsService(); if (statsService == nullptr) { // Statsd not available return; } statsService->registerNativePullAtomCallback( atomTag, cb->getCoolDownMillis(), cb->getTimeoutMillis(), cb->getAdditiveFields(), cb); } void unregisterStatsPullAtomCallbackBlocking(int32_t atomTag, std::shared_ptr statsProvider) { const std::shared_ptr statsService = statsProvider->getStatsService(); if (statsService == nullptr) { // Statsd not available return; } statsService->unregisterNativePullAtomCallback(atomTag); } void AStatsManager_setPullAtomCallback(int32_t atom_tag, AStatsManager_PullAtomMetadata* metadata, AStatsManager_PullAtomCallback callback, void* cookie) { int64_t coolDownMillis = metadata == nullptr ? DEFAULT_COOL_DOWN_MILLIS : metadata->cool_down_millis; int64_t timeoutMillis = metadata == nullptr ? DEFAULT_TIMEOUT_MILLIS : metadata->timeout_millis; std::vector additiveFields; if (metadata != nullptr) { additiveFields = metadata->additive_fields; } std::shared_ptr callbackBinder = SharedRefBase::make(callback, cookie, coolDownMillis, timeoutMillis, additiveFields); { std::lock_guard lg(pullAtomMutex); // Always add to the map. If statsd is dead, we will add them when it comes back. mPullers[atom_tag] = callbackBinder; } std::thread registerThread(registerStatsPullAtomCallbackBlocking, atom_tag, statsProvider, callbackBinder); registerThread.detach(); } void AStatsManager_clearPullAtomCallback(int32_t atom_tag) { { std::lock_guard lg(pullAtomMutex); // Always remove the puller from our map. // If statsd is down, we will not register it when it comes back. mPullers.erase(atom_tag); } std::thread unregisterThread(unregisterStatsPullAtomCallbackBlocking, atom_tag, statsProvider); unregisterThread.detach(); }