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