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