| /* |
| * 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 <map> |
| #include <thread> |
| #include <vector> |
| |
| #include <stats_event.h> |
| #include <stats_pull_atom_callback.h> |
| |
| #include <aidl/android/os/BnPullAtomCallback.h> |
| #include <aidl/android/os/IPullAtomResultReceiver.h> |
| #include <aidl/android/os/IStatsd.h> |
| #include <aidl/android/util/StatsEventParcel.h> |
| #include <android/binder_auto_utils.h> |
| #include <android/binder_ibinder.h> |
| #include <android/binder_manager.h> |
| |
| 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<AStatsEvent*> 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<int32_t> 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<int32_t>(); |
| 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<int32_t> additiveFields) |
| : mCallback(callback), |
| mCookie(cookie), |
| mCoolDownMillis(coolDownMillis), |
| mTimeoutMillis(timeoutMillis), |
| mAdditiveFields(additiveFields) {} |
| |
| Status onPullAtom(int32_t atomTag, |
| const std::shared_ptr<IPullAtomResultReceiver>& resultReceiver) override { |
| AStatsEventList statsEventList; |
| int successInt = mCallback(atomTag, &statsEventList, mCookie); |
| bool success = successInt == AStatsManager_PULL_SUCCESS; |
| |
| // Convert stats_events into StatsEventParcels. |
| std::vector<StatsEventParcel> 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<StatsEventParcel> 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<int32_t>& getAdditiveFields() const { return mAdditiveFields; } |
| |
| private: |
| const AStatsManager_PullAtomCallback mCallback; |
| void* mCookie; |
| const int64_t mCoolDownMillis; |
| const int64_t mTimeoutMillis; |
| const std::vector<int32_t> 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<int32_t, std::shared_ptr<StatsPullAtomCallbackInternal>> mPullers; |
| |
| class StatsdProvider { |
| public: |
| StatsdProvider() : deathRecipient(AIBinder_DeathRecipient_new(binderDied)) { |
| } |
| |
| ~StatsdProvider() { |
| resetStatsService(); |
| } |
| |
| std::shared_ptr<IStatsd> getStatsService() { |
| std::lock_guard<std::mutex> 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<std::mutex> lock(statsdMutex); |
| statsd = nullptr; |
| } |
| |
| static void binderDied(void* cookie) { |
| StatsdProvider* statsProvider = static_cast<StatsdProvider*>(cookie); |
| statsProvider->resetStatsService(); |
| |
| std::shared_ptr<IStatsd> 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<int32_t, std::shared_ptr<StatsPullAtomCallbackInternal>> pullersCopy; |
| { |
| std::lock_guard<std::mutex> 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<IStatsd> statsd; |
| ::ndk::ScopedAIBinder_DeathRecipient deathRecipient; |
| }; |
| |
| static std::shared_ptr<StatsdProvider> statsProvider = std::make_shared<StatsdProvider>(); |
| |
| void registerStatsPullAtomCallbackBlocking(int32_t atomTag, |
| std::shared_ptr<StatsdProvider> statsProvider, |
| std::shared_ptr<StatsPullAtomCallbackInternal> cb) { |
| const std::shared_ptr<IStatsd> 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<StatsdProvider> statsProvider) { |
| const std::shared_ptr<IStatsd> 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<int32_t> additiveFields; |
| if (metadata != nullptr) { |
| additiveFields = metadata->additive_fields; |
| } |
| |
| std::shared_ptr<StatsPullAtomCallbackInternal> callbackBinder = |
| SharedRefBase::make<StatsPullAtomCallbackInternal>(callback, cookie, coolDownMillis, |
| timeoutMillis, additiveFields); |
| |
| { |
| std::lock_guard<std::mutex> 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<std::mutex> 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(); |
| } |