451 lines
16 KiB
C++
451 lines
16 KiB
C++
/*
|
|
* 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 <processgroup/sched_policy.h>
|
|
#include <pthread.h>
|
|
|
|
#include <android-base/file.h>
|
|
#include <android-base/strings.h>
|
|
#include <android-base/stringprintf.h>
|
|
#include <utils/SystemClock.h>
|
|
|
|
namespace {
|
|
|
|
const char* kSingleIndent = "\t";
|
|
const char* kDoubleIndent = "\t\t";
|
|
const char* kDumpAllDevices = "all";
|
|
|
|
}
|
|
|
|
namespace android::automotive::evs::V1_1::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<CollectionEvent>(message.what);
|
|
Result<void> 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 "
|
|
<< collectionEventToString(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<void> StatsCollector::handleCollectionEvent(CollectionEvent event,
|
|
CollectionInfo* info) {
|
|
AutoMutex lock(mMutex);
|
|
if (mCurrentCollectionEvent != event) {
|
|
if (mCurrentCollectionEvent != CollectionEvent::TERMINATED) {
|
|
LOG(WARNING) << "Skipping " << collectionEventToString(event) << " collection event "
|
|
<< "on collection event "
|
|
<< collectionEventToString(mCurrentCollectionEvent);
|
|
|
|
return {};
|
|
} else {
|
|
return Error() << "A collection has been terminated "
|
|
<< "while a current event was pending in the message queue.";
|
|
}
|
|
}
|
|
|
|
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<seconds>(info->interval).count()
|
|
<< " seconds for " << collectionEventToString(event)
|
|
<< " collection cannot be shorter than "
|
|
<< duration_cast<seconds>(kMinCollectionInterval).count() << " seconds.";
|
|
info->interval = kMinCollectionInterval;
|
|
}
|
|
|
|
auto ret = collectLocked(info);
|
|
if (!ret.ok()) {
|
|
return Error() << collectionEventToString(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<void> 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 {};
|
|
}
|
|
|
|
android::base::Result<void> 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 "
|
|
<< collectionEventToString(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 {};
|
|
}
|
|
|
|
StatsCollector::~StatsCollector() {
|
|
{
|
|
AutoMutex lock(mMutex);
|
|
if (mCurrentCollectionEvent == CollectionEvent::TERMINATED) {
|
|
LOG(WARNING) << "Camera usage data collection was stopped already.";
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
Result<void> 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<milliseconds>(kMinCollectionInterval).count()
|
|
<< " milliseconds.";
|
|
}
|
|
|
|
if (maxDuration > kCustomCollectionMaxDuration) {
|
|
return Error(INVALID_OPERATION)
|
|
<< "Collection maximum maxDuration must be less than "
|
|
<< duration_cast<milliseconds>(kCustomCollectionMaxDuration).count()
|
|
<< " milliseconds.";
|
|
}
|
|
|
|
{
|
|
AutoMutex lock(mMutex);
|
|
if (mCurrentCollectionEvent != CollectionEvent::PERIODIC) {
|
|
return Error(INVALID_OPERATION)
|
|
<< "Cannot start a custom collection when "
|
|
<< "the current collection event "
|
|
<< collectionEventToString(mCurrentCollectionEvent)
|
|
<< " != " << collectionEventToString(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<std::size_t>::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<std::string> 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.ok()) {
|
|
return Error() << collectionEventToString(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<seconds>(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<void> StatsCollector::registerClientToMonitor(const android::sp<HalCamera>& 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<void> 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::collectionEventToString(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";
|
|
}
|
|
}
|
|
|
|
std::unordered_map<std::string, std::string> StatsCollector::toString(const char* indent)
|
|
EXCLUDES(mMutex) {
|
|
std::unordered_map<std::string, std::string> ret_val{};
|
|
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<seconds>(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;
|
|
}
|
|
|
|
ret_val.insert_or_assign(id, std::move(buffer));
|
|
}
|
|
|
|
return ret_val;
|
|
}
|
|
|
|
} // namespace android::automotive::evs::V1_1::implementation
|