/* * Copyright 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. */ #include "HalCamera.h" #include "StatsCollector.h" #include "VirtualCamera.h" #include #include #include #include #include #include namespace { const char* kSingleIndent = "\t"; const char* kDoubleIndent = "\t\t"; const char* kDumpAllDevices = "all"; } namespace android { namespace automotive { namespace evs { namespace V1_1 { namespace implementation { using android::base::Error; using android::base::EqualsIgnoreCase; using android::base::Result; using android::base::StringAppendF; using android::base::StringPrintf; using android::base::WriteStringToFd; using android::hardware::automotive::evs::V1_1::BufferDesc; namespace { const auto kPeriodicCollectionInterval = 10s; const auto kPeriodicCollectionCacheSize = 180; const auto kMinCollectionInterval = 1s; const auto kCustomCollectionMaxDuration = 30min; const auto kMaxDumpHistory = 10; } void StatsCollector::handleMessage(const Message& message) { const auto received = static_cast(message.what); Result ret; switch (received) { case CollectionEvent::PERIODIC: ret = handleCollectionEvent(received, &mPeriodicCollectionInfo); break; case CollectionEvent::CUSTOM_START: ret = handleCollectionEvent(received, &mCustomCollectionInfo); break; case CollectionEvent::CUSTOM_END: { AutoMutex lock(mMutex); if (mCurrentCollectionEvent != CollectionEvent::CUSTOM_START) { LOG(WARNING) << "Ignoring a message to end custom collection " << "as current collection is " << toString(mCurrentCollectionEvent); return; } // Starts a periodic collection mLooper->removeMessages(this); mCurrentCollectionEvent = CollectionEvent::PERIODIC; mPeriodicCollectionInfo.lastCollectionTime = mLooper->now(); mLooper->sendMessage(this, CollectionEvent::PERIODIC); return; } default: LOG(WARNING) << "Unknown event is received: " << received; break; } if (!ret.ok()) { Mutex::Autolock lock(mMutex); LOG(ERROR) << "Terminating data collection: " << ret.error(); mCurrentCollectionEvent = CollectionEvent::TERMINATED; mLooper->removeMessages(this); mLooper->wake(); } } Result StatsCollector::handleCollectionEvent(CollectionEvent event, CollectionInfo* info) { AutoMutex lock(mMutex); if (mCurrentCollectionEvent != event) { LOG(WARNING) << "Skipping " << toString(event) << " collection event " << "on collection event " << toString(mCurrentCollectionEvent); return {}; } if (info->maxCacheSize < 1) { return Error() << "Maximum cache size must be greater than 0"; } using std::chrono::duration_cast; using std::chrono::seconds; if (info->interval < kMinCollectionInterval) { LOG(WARNING) << "Collection interval of " << duration_cast(info->interval).count() << " seconds for " << toString(event) << " collection cannot be shorter than " << duration_cast(kMinCollectionInterval).count() << " seconds."; info->interval = kMinCollectionInterval; } auto ret = collectLocked(info); if (!ret) { return Error() << toString(event) << " collection failed: " << ret.error(); } // Arms a message for next periodic collection info->lastCollectionTime += info->interval.count(); mLooper->sendMessageAtTime(info->lastCollectionTime, this, event); return {}; } Result StatsCollector::collectLocked(CollectionInfo* info) REQUIRES(mMutex) { for (auto&& [id, ptr] : mClientsToMonitor) { auto pClient = ptr.promote(); if (!pClient) { LOG(DEBUG) << id << " seems not alive."; continue; } // Pulls a snapshot and puts a timestamp auto snapshot = pClient->getStats(); snapshot.timestamp = mLooper->now(); // Removes the oldest record if cache is full if (info->records[id].history.size() > info->maxCacheSize) { info->records[id].history.pop_front(); } // Stores the latest record and the deltas auto delta = snapshot - info->records[id].latest; info->records[id].history.emplace_back(delta); info->records[id].latest = snapshot; } return {}; } Result StatsCollector::startCollection() { { AutoMutex lock(mMutex); if (mCurrentCollectionEvent != CollectionEvent::INIT || mCollectionThread.joinable()) { return Error(INVALID_OPERATION) << "Camera usages collection is already running."; } // Create the collection info w/ the default values mPeriodicCollectionInfo = { .interval = kPeriodicCollectionInterval, .maxCacheSize = kPeriodicCollectionCacheSize, .lastCollectionTime = 0, }; } // Starts a background worker thread mCollectionThread = std::thread([&]() { { AutoMutex lock(mMutex); if (mCurrentCollectionEvent != CollectionEvent::INIT) { LOG(ERROR) << "Skipping the statistics collection because " << "the current collection event is " << toString(mCurrentCollectionEvent); return; } // Staring with a periodic collection mCurrentCollectionEvent = CollectionEvent::PERIODIC; } if (set_sched_policy(0, SP_BACKGROUND) != 0) { PLOG(WARNING) << "Failed to set background scheduling prioirty"; } // Sets a looper for the communication mLooper->setLooper(Looper::prepare(/*opts=*/0)); // Starts collecting the usage statistics periodically mLooper->sendMessage(this, CollectionEvent::PERIODIC); // Polls the messages until the collection is stopped. bool isActive = true; while (isActive) { mLooper->pollAll(/*timeoutMillis=*/-1); { AutoMutex lock(mMutex); isActive = mCurrentCollectionEvent != CollectionEvent::TERMINATED; } } }); auto ret = pthread_setname_np(mCollectionThread.native_handle(), "EvsUsageCollect"); if (ret != 0) { PLOG(WARNING) << "Failed to name a collection thread"; } return {}; } Result StatsCollector::stopCollection() { { AutoMutex lock(mMutex); if (mCurrentCollectionEvent == CollectionEvent::TERMINATED) { LOG(WARNING) << "Camera usage data collection was stopped already."; return {}; } LOG(INFO) << "Stopping a camera usage data collection"; mCurrentCollectionEvent = CollectionEvent::TERMINATED; } // Join a background thread if (mCollectionThread.joinable()) { mLooper->removeMessages(this); mLooper->wake(); mCollectionThread.join(); } return {}; } Result StatsCollector::startCustomCollection( std::chrono::nanoseconds interval, std::chrono::nanoseconds maxDuration) { using std::chrono::duration_cast; using std::chrono::milliseconds; if (interval < kMinCollectionInterval || maxDuration < kMinCollectionInterval) { return Error(INVALID_OPERATION) << "Collection interval and maximum maxDuration must be >= " << duration_cast(kMinCollectionInterval).count() << " milliseconds."; } if (maxDuration > kCustomCollectionMaxDuration) { return Error(INVALID_OPERATION) << "Collection maximum maxDuration must be less than " << duration_cast(kCustomCollectionMaxDuration).count() << " milliseconds."; } { AutoMutex lock(mMutex); if (mCurrentCollectionEvent != CollectionEvent::PERIODIC) { return Error(INVALID_OPERATION) << "Cannot start a custom collection when " << "the current collection event " << toString(mCurrentCollectionEvent) << " != " << toString(CollectionEvent::PERIODIC) << " collection event"; } // Notifies the user if a preview custom collection result is // not used yet. if (mCustomCollectionInfo.records.size() > 0) { LOG(WARNING) << "Previous custom collection result, which was done at " << mCustomCollectionInfo.lastCollectionTime << " has not pulled yet will be overwritten."; } // Programs custom collection configurations mCustomCollectionInfo = { .interval = interval, .maxCacheSize = std::numeric_limits::max(), .lastCollectionTime = mLooper->now(), .records = {}, }; mLooper->removeMessages(this); nsecs_t uptime = mLooper->now() + maxDuration.count(); mLooper->sendMessageAtTime(uptime, this, CollectionEvent::CUSTOM_END); mCurrentCollectionEvent = CollectionEvent::CUSTOM_START; mLooper->sendMessage(this, CollectionEvent::CUSTOM_START); } return {}; } Result StatsCollector::stopCustomCollection(std::string targetId) { Mutex::Autolock lock(mMutex); if (mCurrentCollectionEvent == CollectionEvent::CUSTOM_START) { // Stops a running custom collection mLooper->removeMessages(this); mLooper->sendMessage(this, CollectionEvent::CUSTOM_END); } auto ret = collectLocked(&mCustomCollectionInfo); if (!ret) { return Error() << toString(mCurrentCollectionEvent) << " collection failed: " << ret.error(); } // Prints out the all collected statistics std::string buffer; using std::chrono::duration_cast; using std::chrono::seconds; const intmax_t interval = duration_cast(mCustomCollectionInfo.interval).count(); if (EqualsIgnoreCase(targetId, kDumpAllDevices)) { for (auto& [id, records] : mCustomCollectionInfo.records) { StringAppendF(&buffer, "%s\n" "%sNumber of collections: %zu\n" "%sCollection interval: %" PRIdMAX " secs\n", id.c_str(), kSingleIndent, records.history.size(), kSingleIndent, interval); auto it = records.history.rbegin(); while (it != records.history.rend()) { buffer += it++->toString(kDoubleIndent); } } // Clears the collection mCustomCollectionInfo = {}; } else { auto it = mCustomCollectionInfo.records.find(targetId); if (it != mCustomCollectionInfo.records.end()) { StringAppendF(&buffer, "%s\n" "%sNumber of collections: %zu\n" "%sCollection interval: %" PRIdMAX " secs\n", targetId.c_str(), kSingleIndent, it->second.history.size(), kSingleIndent, interval); auto recordIter = it->second.history.rbegin(); while (recordIter != it->second.history.rend()) { buffer += recordIter++->toString(kDoubleIndent); } // Clears the collection mCustomCollectionInfo = {}; } else { // Keeps the collection as the users may want to execute a command // again with a right device id StringAppendF(&buffer, "%s has not been monitored.", targetId.c_str()); } } return buffer; } Result StatsCollector::registerClientToMonitor(android::sp& camera) { if (!camera) { return Error(BAD_VALUE) << "Given camera client is invalid"; } AutoMutex lock(mMutex); const auto id = camera->getId(); if (mClientsToMonitor.find(id) != mClientsToMonitor.end()) { LOG(WARNING) << id << " is already registered."; } else { mClientsToMonitor.insert_or_assign(id, camera); } return {}; } Result StatsCollector::unregisterClientToMonitor(const std::string& id) { AutoMutex lock(mMutex); auto entry = mClientsToMonitor.find(id); if (entry != mClientsToMonitor.end()) { mClientsToMonitor.erase(entry); } else { LOG(WARNING) << id << " has not been registerd."; } return {}; } std::string StatsCollector::toString(const CollectionEvent& event) const { switch(event) { case CollectionEvent::INIT: return "CollectionEvent::INIT"; case CollectionEvent::PERIODIC: return "CollectionEvent::PERIODIC"; case CollectionEvent::CUSTOM_START: return "CollectionEvent::CUSTOM_START"; case CollectionEvent::CUSTOM_END: return "CollectionEvent::CUSTOM_END"; case CollectionEvent::TERMINATED: return "CollectionEvent::TERMINATED"; default: return "Unknown"; } } Result StatsCollector::toString(std::unordered_map* usages, const char* indent) EXCLUDES(mMutex) { std::string double_indent(indent); double_indent += indent; { AutoMutex lock(mMutex); using std::chrono::duration_cast; using std::chrono::seconds; const intmax_t interval = duration_cast(mPeriodicCollectionInfo.interval).count(); for (auto&& [id, records] : mPeriodicCollectionInfo.records) { std::string buffer; StringAppendF(&buffer, "%s\n" "%sNumber of collections: %zu\n" "%sCollection interval: %" PRIdMAX "secs\n", id.c_str(), indent, records.history.size(), indent, interval); // Adding up to kMaxDumpHistory records auto it = records.history.rbegin(); auto count = 0; while (it != records.history.rend() && count < kMaxDumpHistory) { buffer += it->toString(double_indent.c_str()); ++it; ++count; } usages->insert_or_assign(id, std::move(buffer)); } } return {}; } } // namespace implementation } // namespace V1_1 } // namespace evs } // namespace automotive } // namespace android