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