/* * 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; } static const int64_t DEFAULT_COOL_DOWN_MILLIS = 1000LL; // 1 second. static const 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; }; static std::mutex pullAtomMutex; static std::shared_ptr sStatsd = nullptr; static std::map> mPullers; static std::shared_ptr getStatsService(); static void binderDied(void* /*cookie*/) { { std::lock_guard lock(pullAtomMutex); sStatsd = nullptr; } std::shared_ptr statsService = 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); } } static ::ndk::ScopedAIBinder_DeathRecipient sDeathRecipient( AIBinder_DeathRecipient_new(binderDied)); static std::shared_ptr getStatsService() { std::lock_guard lock(pullAtomMutex); if (!sStatsd) { // Fetch statsd ::ndk::SpAIBinder binder(AServiceManager_getService("stats")); sStatsd = IStatsd::fromBinder(binder); if (sStatsd) { AIBinder_linkToDeath(binder.get(), sDeathRecipient.get(), /*cookie=*/nullptr); } } return sStatsd; } void registerStatsPullAtomCallbackBlocking(int32_t atomTag, std::shared_ptr cb) { const std::shared_ptr statsService = getStatsService(); if (statsService == nullptr) { // Statsd not available return; } statsService->registerNativePullAtomCallback( atomTag, cb->getCoolDownMillis(), cb->getTimeoutMillis(), cb->getAdditiveFields(), cb); } void unregisterStatsPullAtomCallbackBlocking(int32_t atomTag) { const std::shared_ptr statsService = 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, 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); unregisterThread.detach(); }