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