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