1 /*
2 * Copyright 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "StatsCollector.h"
18
19 #include "HalCamera.h"
20 #include "VirtualCamera.h"
21
22 #include <android-base/file.h>
23 #include <android-base/stringprintf.h>
24 #include <android-base/strings.h>
25 #include <processgroup/sched_policy.h>
26 #include <utils/SystemClock.h>
27
28 #include <pthread.h>
29
30 namespace {
31
32 const char* kSingleIndent = "\t";
33 const char* kDoubleIndent = "\t\t";
34 const char* kDumpAllDevices = "all";
35
36 } // namespace
37
38 namespace android::automotive::evs::V1_1::implementation {
39
40 using android::base::EqualsIgnoreCase;
41 using android::base::Error;
42 using android::base::Result;
43 using android::base::StringAppendF;
44 using android::base::StringPrintf;
45 using android::base::WriteStringToFd;
46 using android::hardware::automotive::evs::V1_1::BufferDesc;
47
48 namespace {
49
50 const auto kPeriodicCollectionInterval = 10s;
51 const auto kPeriodicCollectionCacheSize = 180;
52 const auto kMinCollectionInterval = 1s;
53 const auto kCustomCollectionMaxDuration = 30min;
54 const auto kMaxDumpHistory = 10;
55
56 } // namespace
57
handleMessage(const Message & message)58 void StatsCollector::handleMessage(const Message& message) {
59 const auto received = static_cast<CollectionEvent>(message.what);
60 Result<void> ret;
61 switch (received) {
62 case CollectionEvent::PERIODIC:
63 ret = handleCollectionEvent(received, &mPeriodicCollectionInfo);
64 break;
65
66 case CollectionEvent::CUSTOM_START:
67 ret = handleCollectionEvent(received, &mCustomCollectionInfo);
68 break;
69
70 case CollectionEvent::CUSTOM_END: {
71 AutoMutex lock(mMutex);
72 if (mCurrentCollectionEvent != CollectionEvent::CUSTOM_START) {
73 LOG(WARNING) << "Ignoring a message to end custom collection "
74 << "as current collection is "
75 << collectionEventToString(mCurrentCollectionEvent);
76 return;
77 }
78
79 // Starts a periodic collection
80 mLooper->removeMessages(this);
81 mCurrentCollectionEvent = CollectionEvent::PERIODIC;
82 mPeriodicCollectionInfo.lastCollectionTime = mLooper->now();
83 mLooper->sendMessage(this, CollectionEvent::PERIODIC);
84 return;
85 }
86
87 default:
88 LOG(WARNING) << "Unknown event is received: " << received;
89 break;
90 }
91
92 if (!ret.ok()) {
93 Mutex::Autolock lock(mMutex);
94 LOG(ERROR) << "Terminating data collection: " << ret.error();
95
96 mCurrentCollectionEvent = CollectionEvent::TERMINATED;
97 mLooper->removeMessages(this);
98 mLooper->wake();
99 }
100 }
101
handleCollectionEvent(CollectionEvent event,CollectionInfo * info)102 Result<void> StatsCollector::handleCollectionEvent(CollectionEvent event, CollectionInfo* info) {
103 AutoMutex lock(mMutex);
104 if (mCurrentCollectionEvent != event) {
105 if (mCurrentCollectionEvent != CollectionEvent::TERMINATED) {
106 LOG(WARNING) << "Skipping " << collectionEventToString(event) << " collection event "
107 << "on collection event "
108 << collectionEventToString(mCurrentCollectionEvent);
109
110 return {};
111 } else {
112 return Error() << "A collection has been terminated "
113 << "while a current event was pending in the message queue.";
114 }
115 }
116
117 if (info->maxCacheSize < 1) {
118 return Error() << "Maximum cache size must be greater than 0";
119 }
120
121 using std::chrono::duration_cast;
122 using std::chrono::seconds;
123 if (info->interval < kMinCollectionInterval) {
124 LOG(WARNING) << "Collection interval of " << duration_cast<seconds>(info->interval).count()
125 << " seconds for " << collectionEventToString(event)
126 << " collection cannot be shorter than "
127 << duration_cast<seconds>(kMinCollectionInterval).count() << " seconds.";
128 info->interval = kMinCollectionInterval;
129 }
130
131 auto ret = collectLocked(info);
132 if (!ret.ok()) {
133 return Error() << collectionEventToString(event) << " collection failed: " << ret.error();
134 }
135
136 // Arms a message for next periodic collection
137 info->lastCollectionTime += info->interval.count();
138 mLooper->sendMessageAtTime(info->lastCollectionTime, this, event);
139 return {};
140 }
141
collectLocked(CollectionInfo * info)142 Result<void> StatsCollector::collectLocked(CollectionInfo* info) REQUIRES(mMutex) {
143 for (auto&& [id, ptr] : mClientsToMonitor) {
144 auto pClient = ptr.promote();
145 if (!pClient) {
146 LOG(DEBUG) << id << " seems not alive.";
147 continue;
148 }
149
150 // Pulls a snapshot and puts a timestamp
151 auto snapshot = pClient->getStats();
152 snapshot.timestamp = mLooper->now();
153
154 // Removes the oldest record if cache is full
155 if (info->records[id].history.size() > info->maxCacheSize) {
156 info->records[id].history.pop_front();
157 }
158
159 // Stores the latest record and the deltas
160 auto delta = snapshot - info->records[id].latest;
161 info->records[id].history.emplace_back(delta);
162 info->records[id].latest = snapshot;
163 }
164
165 return {};
166 }
167
startCollection()168 android::base::Result<void> StatsCollector::startCollection() {
169 {
170 AutoMutex lock(mMutex);
171 if (mCurrentCollectionEvent != CollectionEvent::INIT || mCollectionThread.joinable()) {
172 return Error(INVALID_OPERATION) << "Camera usages collection is already running.";
173 }
174
175 // Create the collection info w/ the default values
176 mPeriodicCollectionInfo = {
177 .interval = kPeriodicCollectionInterval,
178 .maxCacheSize = kPeriodicCollectionCacheSize,
179 .lastCollectionTime = 0,
180 };
181 }
182
183 // Starts a background worker thread
184 mCollectionThread = std::thread([&]() {
185 {
186 AutoMutex lock(mMutex);
187 if (mCurrentCollectionEvent != CollectionEvent::INIT) {
188 LOG(ERROR) << "Skipping the statistics collection because "
189 << "the current collection event is "
190 << collectionEventToString(mCurrentCollectionEvent);
191 return;
192 }
193
194 // Staring with a periodic collection
195 mCurrentCollectionEvent = CollectionEvent::PERIODIC;
196 }
197
198 if (set_sched_policy(0, SP_BACKGROUND) != 0) {
199 PLOG(WARNING) << "Failed to set background scheduling prioirty";
200 }
201
202 // Sets a looper for the communication
203 mLooper->setLooper(Looper::prepare(/*opts=*/0));
204
205 // Starts collecting the usage statistics periodically
206 mLooper->sendMessage(this, CollectionEvent::PERIODIC);
207
208 // Polls the messages until the collection is stopped.
209 bool isActive = true;
210 while (isActive) {
211 mLooper->pollAll(/*timeoutMillis=*/-1);
212 {
213 AutoMutex lock(mMutex);
214 isActive = mCurrentCollectionEvent != CollectionEvent::TERMINATED;
215 }
216 }
217 });
218
219 auto ret = pthread_setname_np(mCollectionThread.native_handle(), "EvsUsageCollect");
220 if (ret != 0) {
221 PLOG(WARNING) << "Failed to name a collection thread";
222 }
223
224 return {};
225 }
226
~StatsCollector()227 StatsCollector::~StatsCollector() {
228 {
229 AutoMutex lock(mMutex);
230 if (mCurrentCollectionEvent == CollectionEvent::TERMINATED) {
231 LOG(WARNING) << "Camera usage data collection was stopped already.";
232 }
233
234 LOG(INFO) << "Stopping a camera usage data collection";
235 mCurrentCollectionEvent = CollectionEvent::TERMINATED;
236 }
237
238 // Join a background thread
239 if (mCollectionThread.joinable()) {
240 mLooper->removeMessages(this);
241 mLooper->wake();
242 mCollectionThread.join();
243 }
244 }
245
startCustomCollection(std::chrono::nanoseconds interval,std::chrono::nanoseconds maxDuration)246 Result<void> StatsCollector::startCustomCollection(std::chrono::nanoseconds interval,
247 std::chrono::nanoseconds maxDuration) {
248 using std::chrono::duration_cast;
249 using std::chrono::milliseconds;
250 if (interval < kMinCollectionInterval || maxDuration < kMinCollectionInterval) {
251 return Error(INVALID_OPERATION)
252 << "Collection interval and maximum maxDuration must be >= "
253 << duration_cast<milliseconds>(kMinCollectionInterval).count() << " milliseconds.";
254 }
255
256 if (maxDuration > kCustomCollectionMaxDuration) {
257 return Error(INVALID_OPERATION)
258 << "Collection maximum maxDuration must be less than "
259 << duration_cast<milliseconds>(kCustomCollectionMaxDuration).count()
260 << " milliseconds.";
261 }
262
263 {
264 AutoMutex lock(mMutex);
265 if (mCurrentCollectionEvent != CollectionEvent::PERIODIC) {
266 return Error(INVALID_OPERATION)
267 << "Cannot start a custom collection when " << "the current collection event "
268 << collectionEventToString(mCurrentCollectionEvent)
269 << " != " << collectionEventToString(CollectionEvent::PERIODIC)
270 << " collection event";
271 }
272
273 // Notifies the user if a preview custom collection result is
274 // not used yet.
275 if (mCustomCollectionInfo.records.size() > 0) {
276 LOG(WARNING) << "Previous custom collection result, which was done at "
277 << mCustomCollectionInfo.lastCollectionTime
278 << " has not pulled yet will be overwritten.";
279 }
280
281 // Programs custom collection configurations
282 mCustomCollectionInfo = {
283 .interval = interval,
284 .maxCacheSize = std::numeric_limits<std::size_t>::max(),
285 .lastCollectionTime = mLooper->now(),
286 .records = {},
287 };
288
289 mLooper->removeMessages(this);
290 nsecs_t uptime = mLooper->now() + maxDuration.count();
291 mLooper->sendMessageAtTime(uptime, this, CollectionEvent::CUSTOM_END);
292 mCurrentCollectionEvent = CollectionEvent::CUSTOM_START;
293 mLooper->sendMessage(this, CollectionEvent::CUSTOM_START);
294 }
295
296 return {};
297 }
298
stopCustomCollection(std::string targetId)299 Result<std::string> StatsCollector::stopCustomCollection(std::string targetId) {
300 Mutex::Autolock lock(mMutex);
301 if (mCurrentCollectionEvent == CollectionEvent::CUSTOM_START) {
302 // Stops a running custom collection.
303 mLooper->removeMessages(this);
304 mLooper->sendMessage(this, CollectionEvent::CUSTOM_END);
305 }
306
307 auto ret = collectLocked(&mCustomCollectionInfo);
308 if (!ret.ok()) {
309 return Error() << collectionEventToString(mCurrentCollectionEvent)
310 << " collection failed: " << ret.error();
311 }
312
313 // Prints out the all collected statistics.
314 std::string buffer;
315 using std::chrono::duration_cast;
316 using std::chrono::seconds;
317 const intmax_t interval = duration_cast<seconds>(mCustomCollectionInfo.interval).count();
318 if (EqualsIgnoreCase(targetId, kDumpAllDevices)) {
319 for (auto& [id, records] : mCustomCollectionInfo.records) {
320 StringAppendF(&buffer,
321 "%s\n"
322 "%sNumber of collections: %zu\n"
323 "%sCollection interval: %" PRIdMAX " secs\n",
324 id.c_str(), kSingleIndent, records.history.size(), kSingleIndent,
325 interval);
326 auto it = records.history.rbegin();
327 while (it != records.history.rend()) {
328 buffer += it++->toString(kDoubleIndent);
329 }
330 }
331
332 // Clears the collection
333 mCustomCollectionInfo = {};
334 } else {
335 auto it = mCustomCollectionInfo.records.find(targetId);
336 if (it != mCustomCollectionInfo.records.end()) {
337 StringAppendF(&buffer,
338 "%s\n"
339 "%sNumber of collections: %zu\n"
340 "%sCollection interval: %" PRIdMAX " secs\n",
341 targetId.c_str(), kSingleIndent, it->second.history.size(), kSingleIndent,
342 interval);
343 auto recordIter = it->second.history.rbegin();
344 while (recordIter != it->second.history.rend()) {
345 buffer += recordIter++->toString(kDoubleIndent);
346 }
347
348 // Clears the collection
349 mCustomCollectionInfo = {};
350 } else {
351 // Keeps the collection as the users may want to execute a command
352 // again with a right device id
353 StringAppendF(&buffer, "%s has not been monitored.", targetId.c_str());
354 }
355 }
356
357 return buffer;
358 }
359
registerClientToMonitor(const android::sp<HalCamera> & camera)360 Result<void> StatsCollector::registerClientToMonitor(const android::sp<HalCamera>& camera) {
361 if (!camera) {
362 return Error(BAD_VALUE) << "Given camera client is invalid";
363 }
364
365 AutoMutex lock(mMutex);
366 const auto id = camera->getId();
367 if (mClientsToMonitor.find(id) != mClientsToMonitor.end()) {
368 LOG(WARNING) << id << " is already registered.";
369 } else {
370 mClientsToMonitor.insert_or_assign(id, camera);
371 }
372
373 return {};
374 }
375
unregisterClientToMonitor(const std::string & id)376 Result<void> StatsCollector::unregisterClientToMonitor(const std::string& id) {
377 AutoMutex lock(mMutex);
378 auto entry = mClientsToMonitor.find(id);
379 if (entry != mClientsToMonitor.end()) {
380 mClientsToMonitor.erase(entry);
381 } else {
382 LOG(WARNING) << id << " has not been registerd.";
383 }
384
385 return {};
386 }
387
collectionEventToString(const CollectionEvent & event) const388 std::string StatsCollector::collectionEventToString(const CollectionEvent& event) const {
389 switch (event) {
390 case CollectionEvent::INIT:
391 return "CollectionEvent::INIT";
392 case CollectionEvent::PERIODIC:
393 return "CollectionEvent::PERIODIC";
394 case CollectionEvent::CUSTOM_START:
395 return "CollectionEvent::CUSTOM_START";
396 case CollectionEvent::CUSTOM_END:
397 return "CollectionEvent::CUSTOM_END";
398 case CollectionEvent::TERMINATED:
399 return "CollectionEvent::TERMINATED";
400
401 default:
402 return "Unknown";
403 }
404 }
405
toString(const char * indent)406 std::unordered_map<std::string, std::string> StatsCollector::toString(const char* indent)
407 EXCLUDES(mMutex) {
408 std::unordered_map<std::string, std::string> ret_val{};
409 std::string double_indent(indent);
410 double_indent += indent;
411
412 AutoMutex lock(mMutex);
413 using std::chrono::duration_cast;
414 using std::chrono::seconds;
415 const intmax_t interval = duration_cast<seconds>(mPeriodicCollectionInfo.interval).count();
416
417 for (auto&& [id, records] : mPeriodicCollectionInfo.records) {
418 std::string buffer;
419 StringAppendF(&buffer,
420 "%s\n"
421 "%sNumber of collections: %zu\n"
422 "%sCollection interval: %" PRIdMAX "secs\n",
423 id.c_str(), indent, records.history.size(), indent, interval);
424
425 // Adding up to kMaxDumpHistory records
426 auto it = records.history.rbegin();
427 auto count = 0;
428 while (it != records.history.rend() && count < kMaxDumpHistory) {
429 buffer += it->toString(double_indent.c_str());
430 ++it;
431 ++count;
432 }
433
434 ret_val.insert_or_assign(id, std::move(buffer));
435 }
436
437 return ret_val;
438 }
439
440 } // namespace android::automotive::evs::V1_1::implementation
441