1 /*
2 * Copyright 2022 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 "stats/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 using ::aidl::android::hardware::automotive::evs::BufferDesc;
33 using ::android::AutoMutex;
34 using ::android::Looper;
35 using ::android::Message;
36 using ::android::Mutex;
37 using ::android::sp;
38 using ::android::base::EqualsIgnoreCase;
39 using ::android::base::Error;
40 using ::android::base::Result;
41 using ::android::base::StringAppendF;
42 using ::android::base::StringPrintf;
43 using ::android::base::WriteStringToFd;
44
45 constexpr const char kSingleIndent[] = "\t";
46 constexpr const char kDoubleIndent[] = "\t\t";
47 constexpr const char kDumpAllDevices[] = "all";
48
49 constexpr auto kPeriodicCollectionInterval = 10s;
50 constexpr auto kPeriodicCollectionCacheSize = 180;
51 constexpr auto kMinCollectionInterval = 1s;
52 constexpr auto kCustomCollectionMaxDuration = 30min;
53 constexpr auto kMaxDumpHistory = 10;
54
55 } // namespace
56
57 namespace aidl::android::automotive::evs::implementation {
58
handleMessage(const Message & message)59 void StatsCollector::handleMessage(const Message& message) {
60 const auto received = static_cast<CollectionEvent>(message.what);
61 Result<void> ret;
62 switch (received) {
63 case CollectionEvent::PERIODIC:
64 ret = handleCollectionEvent(received, &mPeriodicCollectionInfo);
65 break;
66
67 case CollectionEvent::CUSTOM_START:
68 ret = handleCollectionEvent(received, &mCustomCollectionInfo);
69 break;
70
71 case CollectionEvent::CUSTOM_END: {
72 AutoMutex lock(mMutex);
73 if (mCurrentCollectionEvent != CollectionEvent::CUSTOM_START) {
74 LOG(WARNING) << "Ignoring a message to end custom collection "
75 << "as current collection is " << toString(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 " << toString(event) << " collection event "
107 << "on collection event " << toString(mCurrentCollectionEvent);
108
109 return {};
110 } else {
111 return Error() << "A collection has been terminated "
112 << "while a current event was pending in the message queue.";
113 }
114 }
115
116 if (info->maxCacheSize < 1) {
117 return Error() << "Maximum cache size must be greater than 0";
118 }
119
120 if (info->interval < kMinCollectionInterval) {
121 LOG(WARNING)
122 << "Collection interval of "
123 << std::chrono::duration_cast<std::chrono::seconds>(info->interval).count()
124 << " seconds for " << toString(event) << " collection cannot be shorter than "
125 << std::chrono::duration_cast<std::chrono::seconds>(kMinCollectionInterval).count()
126 << " seconds.";
127 info->interval = kMinCollectionInterval;
128 }
129
130 auto ret = collectLocked(info);
131 if (!ret.ok()) {
132 return Error() << toString(event) << " collection failed: " << ret.error();
133 }
134
135 // Arms a message for next periodic collection
136 info->lastCollectionTime += info->interval.count();
137 mLooper->sendMessageAtTime(info->lastCollectionTime, this, event);
138 return {};
139 }
140
collectLocked(CollectionInfo * info)141 Result<void> StatsCollector::collectLocked(CollectionInfo* info) REQUIRES(mMutex) {
142 for (auto&& [id, ptr] : mClientsToMonitor) {
143 auto pClient = ptr.lock();
144 if (!pClient) {
145 LOG(DEBUG) << id << " seems not alive.";
146 continue;
147 }
148
149 // Pulls a snapshot and puts a timestamp
150 auto snapshot = pClient->getStats();
151 snapshot.timestamp = mLooper->now();
152
153 // Removes the oldest record if cache is full
154 if (info->records[id].history.size() > info->maxCacheSize) {
155 info->records[id].history.pop_front();
156 }
157
158 // Stores the latest record and the deltas
159 auto delta = snapshot - info->records[id].latest;
160 info->records[id].history.push_back(delta);
161 info->records[id].latest = snapshot;
162 }
163
164 return {};
165 }
166
startCollection()167 Result<void> StatsCollector::startCollection() {
168 {
169 AutoMutex lock(mMutex);
170 if (mCurrentCollectionEvent != CollectionEvent::INIT || mCollectionThread.joinable()) {
171 return Error(::android::INVALID_OPERATION)
172 << "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 << toString(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
stopCollection()227 Result<void> StatsCollector::stopCollection() {
228 {
229 AutoMutex lock(mMutex);
230 if (mCurrentCollectionEvent == CollectionEvent::TERMINATED) {
231 LOG(WARNING) << "Camera usage data collection was stopped already.";
232 return {};
233 }
234
235 LOG(INFO) << "Stopping a camera usage data collection";
236 mCurrentCollectionEvent = CollectionEvent::TERMINATED;
237 }
238
239 // Join a background thread
240 if (mCollectionThread.joinable()) {
241 mLooper->removeMessages(this);
242 mLooper->wake();
243 mCollectionThread.join();
244 }
245
246 return {};
247 }
248
startCustomCollection(std::chrono::nanoseconds interval,std::chrono::nanoseconds maxDuration)249 Result<void> StatsCollector::startCustomCollection(std::chrono::nanoseconds interval,
250 std::chrono::nanoseconds maxDuration) {
251 if (interval < kMinCollectionInterval || maxDuration < kMinCollectionInterval) {
252 return Error(::android::INVALID_OPERATION)
253 << "Collection interval and maximum maxDuration must be >= "
254 << std::chrono::duration_cast<std::chrono::milliseconds>(kMinCollectionInterval)
255 .count()
256 << " milliseconds.";
257 }
258
259 if (maxDuration > kCustomCollectionMaxDuration) {
260 return Error(::android::INVALID_OPERATION)
261 << "Collection maximum maxDuration must be less than "
262 << std::chrono::duration_cast<std::chrono::milliseconds>(
263 kCustomCollectionMaxDuration)
264 .count()
265 << " milliseconds.";
266 }
267
268 {
269 AutoMutex lock(mMutex);
270 if (mCurrentCollectionEvent != CollectionEvent::PERIODIC) {
271 return Error(::android::INVALID_OPERATION)
272 << "Cannot start a custom collection when " << "the current collection event "
273 << toString(mCurrentCollectionEvent)
274 << " != " << toString(CollectionEvent::PERIODIC) << " collection event";
275 }
276
277 // Notifies the user if a preview custom collection result is
278 // not used yet.
279 if (mCustomCollectionInfo.records.size() > 0) {
280 LOG(WARNING) << "Previous custom collection result, which was done at "
281 << mCustomCollectionInfo.lastCollectionTime
282 << " has not pulled yet will be overwritten.";
283 }
284
285 // Programs custom collection configurations
286 mCustomCollectionInfo = {
287 .interval = interval,
288 .maxCacheSize = std::numeric_limits<std::size_t>::max(),
289 .lastCollectionTime = mLooper->now(),
290 .records = {},
291 };
292
293 mLooper->removeMessages(this);
294 nsecs_t uptime = mLooper->now() + maxDuration.count();
295 mLooper->sendMessageAtTime(uptime, this, CollectionEvent::CUSTOM_END);
296 mCurrentCollectionEvent = CollectionEvent::CUSTOM_START;
297 mLooper->sendMessage(this, CollectionEvent::CUSTOM_START);
298 }
299
300 return {};
301 }
302
stopCustomCollection(const std::string & targetId)303 Result<std::string> StatsCollector::stopCustomCollection(const std::string& targetId) {
304 Mutex::Autolock lock(mMutex);
305 if (mCurrentCollectionEvent != CollectionEvent::CUSTOM_START) {
306 return Error() << "No custom collection is running; current event is "
307 << toString(mCurrentCollectionEvent);
308 }
309
310 // Stops a running custom collection
311 mLooper->removeMessages(this);
312 mLooper->sendMessage(this, CollectionEvent::CUSTOM_END);
313
314 auto ret = collectLocked(&mCustomCollectionInfo);
315 if (!ret.ok()) {
316 return Error() << toString(mCurrentCollectionEvent)
317 << " collection failed: " << ret.error();
318 }
319
320 // Prints out the all collected statistics
321 std::string buffer;
322 const intmax_t interval =
323 std::chrono::duration_cast<std::chrono::seconds>(mCustomCollectionInfo.interval)
324 .count();
325 if (EqualsIgnoreCase(targetId, kDumpAllDevices)) {
326 for (auto& [id, records] : mCustomCollectionInfo.records) {
327 StringAppendF(&buffer,
328 "%s\n"
329 "%sNumber of collections: %zu\n"
330 "%sCollection interval: %" PRIdMAX " secs\n",
331 id.data(), kSingleIndent, records.history.size(), kSingleIndent,
332 interval);
333 auto it = records.history.rbegin();
334 while (it != records.history.rend()) {
335 buffer += it++->toString(kDoubleIndent);
336 }
337 }
338
339 // Clears the collection
340 mCustomCollectionInfo = {};
341 } else {
342 auto it = mCustomCollectionInfo.records.find(targetId);
343 if (it != mCustomCollectionInfo.records.end()) {
344 StringAppendF(&buffer,
345 "%s\n"
346 "%sNumber of collections: %zu\n"
347 "%sCollection interval: %" PRIdMAX " secs\n",
348 targetId.data(), kSingleIndent, it->second.history.size(), kSingleIndent,
349 interval);
350 auto recordIter = it->second.history.rbegin();
351 while (recordIter != it->second.history.rend()) {
352 buffer += recordIter++->toString(kDoubleIndent);
353 }
354
355 // Clears the collection
356 mCustomCollectionInfo = {};
357 } else {
358 // Keeps the collection as the users may want to execute a command
359 // again with a right device id
360 StringAppendF(&buffer, "%s has not been monitored.", targetId.data());
361 }
362 }
363
364 return buffer;
365 }
366
registerClientToMonitor(const std::shared_ptr<HalCamera> & camera)367 Result<void> StatsCollector::registerClientToMonitor(const std::shared_ptr<HalCamera>& camera) {
368 if (!camera) {
369 return Error(::android::BAD_VALUE) << "Given camera client is invalid";
370 }
371
372 AutoMutex lock(mMutex);
373 const auto id = camera->getId();
374 if (mClientsToMonitor.find(id) != mClientsToMonitor.end()) {
375 LOG(WARNING) << id << " is already registered.";
376 } else {
377 mClientsToMonitor.insert_or_assign(id, camera);
378 }
379
380 return {};
381 }
382
unregisterClientToMonitor(const std::string & id)383 Result<void> StatsCollector::unregisterClientToMonitor(const std::string& id) {
384 AutoMutex lock(mMutex);
385 auto entry = mClientsToMonitor.find(id);
386 if (entry != mClientsToMonitor.end()) {
387 mClientsToMonitor.erase(entry);
388 } else {
389 LOG(WARNING) << id << " has not been registered.";
390 }
391
392 return {};
393 }
394
toString(const CollectionEvent & event) const395 std::string StatsCollector::toString(const CollectionEvent& event) const {
396 switch (event) {
397 case CollectionEvent::INIT:
398 return "CollectionEvent::INIT";
399 case CollectionEvent::PERIODIC:
400 return "CollectionEvent::PERIODIC";
401 case CollectionEvent::CUSTOM_START:
402 return "CollectionEvent::CUSTOM_START";
403 case CollectionEvent::CUSTOM_END:
404 return "CollectionEvent::CUSTOM_END";
405 case CollectionEvent::TERMINATED:
406 return "CollectionEvent::TERMINATED";
407 default:
408 return "Unknown";
409 }
410 }
411
toString(std::unordered_map<std::string,std::string> * usages,const char * indent)412 Result<void> StatsCollector::toString(std::unordered_map<std::string, std::string>* usages,
413 const char* indent) EXCLUDES(mMutex) {
414 std::string double_indent(indent);
415 double_indent += indent;
416
417 {
418 AutoMutex lock(mMutex);
419 const intmax_t interval =
420 std::chrono::duration_cast<std::chrono::seconds>(mPeriodicCollectionInfo.interval)
421 .count();
422
423 for (auto&& [id, records] : mPeriodicCollectionInfo.records) {
424 std::string buffer;
425 StringAppendF(&buffer,
426 "%s\n"
427 "%sNumber of collections: %zu\n"
428 "%sCollection interval: %" PRIdMAX "secs\n",
429 id.data(), indent, records.history.size(), indent, interval);
430
431 // Adding up to kMaxDumpHistory records
432 auto it = records.history.rbegin();
433 auto count = 0;
434 while (it != records.history.rend() && count < kMaxDumpHistory) {
435 buffer += it->toString(double_indent.data());
436 ++it;
437 ++count;
438 }
439
440 usages->insert_or_assign(id, std::move(buffer));
441 }
442 }
443
444 return {};
445 }
446
447 } // namespace aidl::android::automotive::evs::implementation
448