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