1 /*
2  * Copyright (C) 2019 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 #include "profiler.h"
17 
18 #include <cutils/properties.h>
19 #include <log/log.h>
20 #include <sys/stat.h>
21 
22 #include <fstream>
23 #include <list>
24 #include <mutex>
25 #include <unordered_map>
26 #include <vector>
27 
28 namespace google {
29 namespace camera_common {
30 namespace {
31 
32 #undef LOG_TAG
33 #define LOG_TAG "profiler"
34 
35 // Profiler implementatoin.
36 class ProfilerImpl : public Profiler {
37  public:
ProfilerImpl(SetPropFlag setting)38   ProfilerImpl(SetPropFlag setting) : setting_(setting){};
39   ~ProfilerImpl();
40 
41   // Setup the name of use case the profiler is running.
42   // Argument:
43   //  usecase: the name use case of the profiler is running.
SetUseCase(std::string usecase)44   void SetUseCase(std::string usecase) override final {
45     use_case_ = std::move(usecase);
46   }
47 
48   // Set the file prefix name for dumpping the profiling file.
49   // Argument:
50   //  dump_file_prefix: file prefix name. In the current setting,
51   //    "/data/vendor/camera/" is a valid folder for camera to dump file.
52   //    A valid prefix can be "/data/vendor/camera/test_prefix_".
53   void SetDumpFilePrefix(std::string dump_file_prefix) override final;
54 
55   // Start to profile.
56   // We use start and end to choose which code snippet to be profile.
57   // The user specifies the name, and the profiler will print the name and its
58   // timing.
59   // Arguments:
60   //   name: the name of the node to be profiled.
61   //   request_id: frame requesd id.
62   void Start(const std::string name,
63              int request_id = kInvalidRequestId) override final;
64 
65   // End the profileing.
66   // Arguments:
67   //   name: the name of the node to be profiled. Should be the same in Start().
68   //   request_id: frame requesd id.
69   void End(const std::string name,
70            int request_id = kInvalidRequestId) override final;
71 
72   // Print out the profiling result in the standard output (ANDROID_LOG_ERROR).
73   virtual void PrintResult() override;
74 
75  protected:
76   // A structure to hold start time, end time, and count of profiling code
77   // snippet.
78   struct TimeSlot {
79     int64_t start;
80     int64_t end;
81     int32_t count;
TimeSlotgoogle::camera_common::__anon477d26810111::ProfilerImpl::TimeSlot82     TimeSlot() : start(0), end(0), count(0) {
83     }
84   };
85 
86   // A structure to store node's profiling result.
87   struct TimeResult {
88     std::string node_name;
89     float max_dt;
90     float avg_dt;
91     float avg_count;
TimeResultgoogle::camera_common::__anon477d26810111::ProfilerImpl::TimeResult92     TimeResult(std::string node_name, float max_dt, float avg_dt, float count)
93         : node_name(node_name),
94           max_dt(max_dt),
95           avg_dt(avg_dt),
96           avg_count(count) {
97     }
98   };
99 
100   using TimeSeries = std::vector<TimeSlot>;
101   using NodeTimingMap = std::unordered_map<std::string, TimeSeries>;
102 
103   static constexpr int64_t kNsPerSec = 1000000000;
104   static constexpr float kNanoToMilli = 0.000001f;
105 
106   // The setting_ is used to memorize the getprop result.
107   SetPropFlag setting_;
108   // The map to record the timing of all nodes.
109   NodeTimingMap timing_map_;
110   // Use case name.
111   std::string use_case_;
112   // The prefix for the dump filename.
113   std::string dump_file_prefix_;
114   // Mutex lock.
115   std::mutex lock_;
116 
117   // Get boot time.
CurrentTime() const118   int64_t CurrentTime() const {
119     if (timespec now; clock_gettime(CLOCK_BOOTTIME, &now) == 0) {
120       return now.tv_sec * kNsPerSec + now.tv_nsec;
121     } else {
122       ALOGE("clock_gettime failed");
123       return -1;
124     }
125   }
126 
127   // Create folder if not exist.
128   void CreateFolder(std::string folder_path);
129 
130   // Dump the result to the disk.
131   // Argument:
132   //   filepath: file path to dump file.
133   void DumpResult(std::string filepath);
134 };
135 
~ProfilerImpl()136 ProfilerImpl::~ProfilerImpl() {
137   if (setting_ == SetPropFlag::kDisable || timing_map_.size() == 0) {
138     return;
139   }
140   if (setting_ & SetPropFlag::kPrintBit) {
141     PrintResult();
142   }
143   if (setting_ & SetPropFlag::kDumpBit) {
144     DumpResult(dump_file_prefix_ + use_case_ + ".txt");
145   }
146 }
147 
CreateFolder(std::string folder_path)148 void ProfilerImpl::CreateFolder(std::string folder_path) {
149   struct stat folder_stat;
150   memset(&folder_stat, 0, sizeof(folder_stat));
151   if (stat(folder_path.c_str(), &folder_stat) != 0) {
152     if (errno != ENOENT ||
153         mkdir(folder_path.c_str(), S_IRWXU | S_IRWXG | S_IRWXO) == 0) {
154       ALOGE("Failed to create %s. errno: %d", folder_path.c_str(), errno);
155     }
156   }
157 }
158 
SetDumpFilePrefix(std::string dump_file_prefix)159 void ProfilerImpl::SetDumpFilePrefix(std::string dump_file_prefix) {
160   dump_file_prefix_ = dump_file_prefix;
161   if (setting_ & SetPropFlag::kDumpBit) {
162     if (auto index = dump_file_prefix_.rfind('/'); index != std::string::npos) {
163       CreateFolder(dump_file_prefix_.substr(0, index));
164     }
165   }
166 }
167 
Start(const std::string name,int request_id)168 void ProfilerImpl::Start(const std::string name, int request_id) {
169   if (setting_ == SetPropFlag::kDisable) {
170     return;
171   }
172   int index = (request_id == kInvalidRequestId ? 0 : request_id);
173 
174   std::lock_guard<std::mutex> lk(lock_);
175   TimeSeries& time_series = timing_map_[name];
176   for (int i = time_series.size(); i <= index; i++) {
177     time_series.push_back(TimeSlot());
178   }
179   TimeSlot& slot = time_series[index];
180   slot.start += CurrentTime();
181 }
182 
End(const std::string name,int request_id)183 void ProfilerImpl::End(const std::string name, int request_id) {
184   if (setting_ == SetPropFlag::kDisable) {
185     return;
186   }
187   int index = (request_id == kInvalidRequestId ? 0 : request_id);
188 
189   std::lock_guard<std::mutex> lk(lock_);
190 
191   if (index < timing_map_[name].size()) {
192     TimeSlot& slot = timing_map_[name][index];
193     slot.end += CurrentTime();
194     slot.count++;
195   }
196 }
197 
PrintResult()198 void ProfilerImpl::PrintResult() {
199   ALOGE("UseCase: %s. Profiled Frames: %d.", use_case_.c_str(),
200         static_cast<int>(timing_map_.begin()->second.size()));
201 
202   std::vector<TimeResult> time_results;
203 
204   float sum_avg = 0.f;
205   float max_max = 0.f;
206   float sum_max = 0.f;
207   for (const auto& [node_name, time_series] : timing_map_) {
208     int num_frames = 0;
209     int num_samples = 0;
210     float sum_dt = 0;
211     float max_dt = 0;
212     for (const auto& slot : time_series) {
213       if (slot.count > 0) {
214         float elapsed = (slot.end - slot.start) * kNanoToMilli;
215         sum_dt += elapsed;
216         num_samples += slot.count;
217         max_dt = std::max(max_dt, elapsed);
218         num_frames++;
219       }
220     }
221     if (num_samples == 0) {
222       continue;
223     }
224     float avg = sum_dt / std::max(1, num_samples);
225     float avg_count = static_cast<float>(num_samples) /
226                       static_cast<float>(std::max(1, num_frames));
227     sum_avg += avg * avg_count;
228     sum_max += max_dt;
229     max_max = std::max(max_max, max_dt);
230 
231     time_results.push_back({node_name, max_dt, avg * avg_count, avg_count});
232   }
233 
234   std::sort(time_results.begin(), time_results.end(),
235             [](auto a, auto b) { return a.avg_dt > b.avg_dt; });
236 
237   for (const auto it : time_results) {
238     ALOGE("%51.51s Max: %8.3f ms       Avg: %7.3f ms (Count = %3.1f)",
239           it.node_name.c_str(), it.max_dt, it.avg_dt, it.avg_count);
240   }
241 
242   ALOGE("%43.43s     MAX SUM: %8.3f ms,  AVG SUM: %7.3f ms", "", sum_max,
243         sum_avg);
244   ALOGE("");
245 }
246 
DumpResult(std::string filepath)247 void ProfilerImpl::DumpResult(std::string filepath) {
248   if (std::ofstream fout(filepath, std::ios::out); fout.is_open()) {
249     for (const auto& [node_name, time_series] : timing_map_) {
250       fout << node_name << " ";
251       for (const auto& time_slot : time_series) {
252         float elapsed = static_cast<float>(time_slot.end - time_slot.start) /
253                         std::max(1, time_slot.count);
254         fout << elapsed * kNanoToMilli << " ";
255       }
256       fout << "\n";
257     }
258     fout.close();
259   }
260 }
261 
262 class ProfilerStopwatchImpl : public ProfilerImpl {
263  public:
ProfilerStopwatchImpl(SetPropFlag setting)264   ProfilerStopwatchImpl(SetPropFlag setting) : ProfilerImpl(setting){};
265 
~ProfilerStopwatchImpl()266   ~ProfilerStopwatchImpl() {
267     if (setting_ == SetPropFlag::kDisable || timing_map_.size() == 0) {
268       return;
269     }
270     if (setting_ & SetPropFlag::kPrintBit) {
271       // Virtual function won't work in parent class's destructor. need to call
272       // it by ourself.
273       PrintResult();
274       // Erase the print bit to prevent parent class print again.
275       setting_ = static_cast<SetPropFlag>(setting_ & (!SetPropFlag::kPrintBit));
276     }
277   }
278 
279   // Print out the profiling result in the standard output (ANDROID_LOG_ERROR)
280   // with stopwatch mode.
PrintResult()281   void PrintResult() override {
282     ALOGE("Profiling Case: %s", use_case_.c_str());
283 
284     // Sort by end time.
285     std::list<std::pair<std::string, TimeSlot>> time_results;
286     for (const auto& [node_name, time_series] : timing_map_) {
287       for (const auto& slot : time_series) {
288         if (slot.count > 0 && time_results.size() < time_results.max_size()) {
289           time_results.push_back({node_name, slot});
290         }
291       }
292     }
293     time_results.sort([](const auto& a, const auto& b) {
294       return a.second.end < b.second.end;
295     });
296 
297     for (const auto& [node_name, slot] : time_results) {
298       if (slot.count > 0) {
299         float elapsed = (slot.end - slot.start) * kNanoToMilli;
300         ALOGE("%51.51s: %8.3f ms", node_name.c_str(), elapsed);
301       }
302     }
303 
304     ALOGE("");
305   }
306 };
307 
308 // Dummpy profiler class.
309 class ProfilerDummy : public Profiler {
310  public:
ProfilerDummy()311   ProfilerDummy(){};
~ProfilerDummy()312   ~ProfilerDummy(){};
313 
SetUseCase(std::string)314   void SetUseCase(std::string) override final {
315   }
316 
SetDumpFilePrefix(std::string)317   void SetDumpFilePrefix(std::string) override final {
318   }
319 
Start(const std::string,int)320   void Start(const std::string, int) override final {
321   }
322 
End(const std::string,int)323   void End(const std::string, int) override final {
324   }
325 
PrintResult()326   void PrintResult() override final {
327   }
328 };
329 
330 }  // anonymous namespace
331 
Create(int option)332 std::shared_ptr<Profiler> Profiler::Create(int option) {
333   SetPropFlag flag = static_cast<SetPropFlag>(option);
334 
335   if (flag == SetPropFlag::kDisable) {
336     return std::make_unique<ProfilerDummy>();
337   } else if (flag & SetPropFlag::kStopWatch) {
338     return std::make_unique<ProfilerStopwatchImpl>(flag);
339   } else {
340     return std::make_unique<ProfilerImpl>(flag);
341   }
342 }
343 
344 }  // namespace camera_common
345 }  // namespace google
346