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