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 <hardware/google/camera/common/profiler/profiler.pb.h>
20 #include <log/log.h>
21 #include <sys/stat.h>
22
23 #include <fstream>
24 #include <list>
25 #include <mutex>
26 #include <unordered_map>
27 #include <vector>
28
29 namespace google {
30 namespace camera_common {
31 namespace {
32
33 #undef LOG_TAG
34 #define LOG_TAG "profiler"
35
StandardDeviation(std::vector<float> samples,float mean)36 float StandardDeviation(std::vector<float> samples, float mean) {
37 int size = samples.size();
38
39 double sum = 0;
40 for (int i = 0; i < size; i++) {
41 sum += pow((samples[i] - mean), 2);
42 }
43
44 return static_cast<float>(sqrt(sum / (size - 1)));
45 }
46
47 // Profiler implementatoin.
48 class ProfilerImpl : public Profiler {
49 public:
ProfilerImpl(SetPropFlag setting)50 ProfilerImpl(SetPropFlag setting) : setting_(setting) {
51 object_init_real_time_ = GetRealTimeNs();
52 object_init_boot_time_ = GetBootTimeNs();
53 };
54 ~ProfilerImpl();
55
56 // Setup the name of use case the profiler is running.
57 // Argument:
58 // usecase: the name use case of the profiler is running.
SetUseCase(std::string usecase)59 void SetUseCase(std::string usecase) override final {
60 use_case_ = std::move(usecase);
61 }
62
63 // Set the file prefix name for dumpping the profiling file.
64 // Argument:
65 // dump_file_prefix: file prefix name. In the current setting,
66 // "/data/vendor/camera/" is a valid folder for camera to dump file.
67 // A valid prefix can be "/data/vendor/camera/test_prefix_".
68 void SetDumpFilePrefix(const std::string& dump_file_prefix) override final;
69
70 // Start to profile.
71 // We use start and end to choose which code snippet to be profile.
72 // The user specifies the name, and the profiler will print the name and its
73 // timing.
74 // Arguments:
75 // name: the name of the node to be profiled.
76 // request_id: frame requesd id.
77 void Start(const std::string& name,
78 int request_id = kInvalidRequestId) override final;
79
80 // End the profileing.
81 // Arguments:
82 // name: the name of the node to be profiled. Should be the same in Start().
83 // request_id: frame requesd id.
84 void End(const std::string& name,
85 int request_id = kInvalidRequestId) override final;
86
87 // Print out the profiling result in the standard output (ANDROID_LOG_ERROR).
88 void PrintResult() override;
89
90 // Profile the frame rate
91 void ProfileFrameRate(const std::string&) override final;
92
93 // Set the interval of FPS print
94 // The unit is second and interval_seconds must >= 1
95 void SetFpsPrintInterval(int32_t interval_seconds) override final;
96
97 // Get the latency associated with the name
98 std::list<std::pair<std::string, float>> GetLatencyData() override final;
99
GetUseCase() const100 std::string GetUseCase() const override final {
101 return use_case_;
102 }
103
104 protected:
105 // A structure to hold start time, end time, and count of profiling code
106 // snippet.
107 struct TimeSlot {
108 int64_t start = 0;
109 int64_t end = 0;
110 int32_t count = 0;
111 int32_t request_id = 0;
112 };
113
114 // A structure to store node's profiling result.
115 struct TimeResult {
116 std::string node_name;
117 float min_dt;
118 float max_dt;
119 float avg_dt;
120 float avg_count;
121 float fps;
122 float mean_max_stddevs;
TimeResultgoogle::camera_common::__anonca3bec460111::ProfilerImpl::TimeResult123 TimeResult(std::string node_name, float min_dt, float max_dt, float avg_dt,
124 float count, float fps, float mean_max_stddevs)
125 : node_name(node_name),
126 min_dt(min_dt),
127 max_dt(max_dt),
128 avg_dt(avg_dt),
129 avg_count(count),
130 fps(fps),
131 mean_max_stddevs(mean_max_stddevs) {
132 }
133 };
134
135 using TimeSeries = std::vector<TimeSlot>;
136 using NodeTimingMap = std::unordered_map<std::string, TimeSeries>;
137 using NodeFrameRateMap = std::unordered_map<std::string, TimeSlot>;
138
139 static constexpr int64_t kNsPerSec = 1000000000;
140 static constexpr float kNanoToMilli = 0.000001f;
141
142 // The setting_ is used to memorize the getprop result.
143 SetPropFlag setting_;
144 // The map to record the timing of all nodes.
145 NodeTimingMap timing_map_;
146 // The map to record the timing to print fps when close.
147 NodeFrameRateMap frame_rate_map_;
148 // The map to record the timing to print fps per second.
149 NodeFrameRateMap realtime_frame_rate_map_;
150 // Use case name.
151 std::string use_case_;
152 // The prefix for the dump filename.
153 std::string dump_file_prefix_;
154 // Mutex lock.
155 std::mutex lock_;
156
157 // Get clock boot time.
GetBootTimeNs() const158 int64_t GetBootTimeNs() const {
159 if (timespec now; clock_gettime(CLOCK_BOOTTIME, &now) == 0) {
160 return now.tv_sec * kNsPerSec + now.tv_nsec;
161 } else {
162 ALOGE("clock_gettime failed");
163 return -1;
164 }
165 }
166 // Get clock real time.
GetRealTimeNs() const167 int64_t GetRealTimeNs() const {
168 if (timespec now; clock_gettime(CLOCK_REALTIME, &now) == 0) {
169 return now.tv_sec * kNsPerSec + now.tv_nsec;
170 } else {
171 ALOGE("clock_gettime failed");
172 return -1;
173 }
174 }
175
176 // Timestamp of the class object initialized using CLOCK_BOOTTIME.
177 int64_t object_init_boot_time_;
178 // Timestamp of the class object initialized using CLOCK_REALTIME.
179 int64_t object_init_real_time_;
180
181 // Create folder if not exist.
182 void CreateFolder(const std::string& folder_path);
183
184 // Dump the result to the disk.
185 // Argument:
186 // filepath: file path to dump file.
187 virtual void DumpResult(const std::string& filepath);
188
189 // Dump result in text format.
190 void DumpTxt(std::string_view filepath);
191
192 // Dump result in proto binary format.
193 void DumpPb(std::string_view filepath);
194
195 // Dump result format extension: proto or text.
196 constexpr static char kStrPb[] = ".pb";
197 constexpr static char kStrTxt[] = ".txt";
198
199 int32_t fps_print_interval_seconds_ = 1;
200 };
201
~ProfilerImpl()202 ProfilerImpl::~ProfilerImpl() {
203 if (setting_ == SetPropFlag::kDisable || timing_map_.size() == 0) {
204 return;
205 }
206 if (setting_ & SetPropFlag::kPrintBit) {
207 PrintResult();
208 }
209 if (setting_ & SetPropFlag::kDumpBit) {
210 std::string filename = std::to_string(object_init_real_time_);
211 DumpResult(dump_file_prefix_ + use_case_ + "-TS" + filename);
212 }
213 }
214
DumpResult(const std::string & filepath)215 void ProfilerImpl::DumpResult(const std::string& filepath) {
216 if (setting_ & SetPropFlag::kProto) {
217 DumpPb(filepath + kStrPb);
218 } else {
219 DumpTxt(filepath + kStrTxt);
220 }
221 }
222
CreateFolder(const std::string & folder_path)223 void ProfilerImpl::CreateFolder(const std::string& folder_path) {
224 struct stat folder_stat;
225 memset(&folder_stat, 0, sizeof(folder_stat));
226 if (stat(folder_path.c_str(), &folder_stat) != 0) {
227 if (errno != ENOENT ||
228 mkdir(folder_path.c_str(), S_IRWXU | S_IRWXG | S_IRWXO) == 0) {
229 ALOGE("Failed to create %s. errno: %d", folder_path.c_str(), errno);
230 }
231 }
232 }
233
SetDumpFilePrefix(const std::string & dump_file_prefix)234 void ProfilerImpl::SetDumpFilePrefix(const std::string& dump_file_prefix) {
235 dump_file_prefix_ = dump_file_prefix;
236 if (setting_ & SetPropFlag::kDumpBit) {
237 if (auto index = dump_file_prefix_.rfind('/'); index != std::string::npos) {
238 CreateFolder(dump_file_prefix_.substr(0, index));
239 }
240 }
241 }
242
SetFpsPrintInterval(int32_t interval_seconds)243 void ProfilerImpl::SetFpsPrintInterval(int32_t interval_seconds) {
244 if (interval_seconds < 1) {
245 ALOGE("Wrong interval: %d, must >= 1", interval_seconds);
246 return;
247 }
248 fps_print_interval_seconds_ = interval_seconds;
249 }
250
ProfileFrameRate(const std::string & name)251 void ProfilerImpl::ProfileFrameRate(const std::string& name) {
252 std::lock_guard<std::mutex> lk(lock_);
253 // Save the timeing for each whole process
254 TimeSlot& frame_rate = frame_rate_map_[name];
255 if (frame_rate.start == 0) {
256 frame_rate.start = GetBootTimeNs();
257 frame_rate.count = 0;
258 frame_rate.end = 0;
259 } else {
260 ++frame_rate.count;
261 frame_rate.end = GetBootTimeNs();
262 }
263
264 if ((setting_ & SetPropFlag::kPrintFpsPerIntervalBit) == 0) {
265 return;
266 }
267 // Print FPS every second
268 TimeSlot& realtime_frame_rate = realtime_frame_rate_map_[name];
269 if (realtime_frame_rate.start == 0) {
270 realtime_frame_rate.start = GetBootTimeNs();
271 realtime_frame_rate.count = 0;
272 } else {
273 ++realtime_frame_rate.count;
274 int64_t current = GetBootTimeNs();
275 int64_t elapsed = current - realtime_frame_rate.start;
276 if (elapsed > kNsPerSec * fps_print_interval_seconds_) {
277 float fps =
278 realtime_frame_rate.count * kNsPerSec / static_cast<float>(elapsed);
279 float avg_fps = frame_rate.count * kNsPerSec /
280 static_cast<float>(frame_rate.end - frame_rate.start);
281 ALOGI("%s: current FPS %3.2f, avg %3.2f", name.c_str(), fps, avg_fps);
282 realtime_frame_rate.count = 0;
283 realtime_frame_rate.start = current;
284 }
285 }
286 }
287
Start(const std::string & name,int request_id)288 void ProfilerImpl::Start(const std::string& name, int request_id) {
289 if (setting_ == SetPropFlag::kDisable) {
290 return;
291 }
292
293 // When the request_id == kInvalidRequestId, it is served as a different
294 // purpose, eg. profiling first frame latency, or HAL total runtime. The valid
295 // request id is shifted by 1 to avoid the conflict.
296 int valid_request_id = (request_id == kInvalidRequestId) ? 0 : request_id + 1;
297
298 {
299 std::lock_guard<std::mutex> lk(lock_);
300 TimeSeries& time_series = timing_map_[name];
301 for (int i = time_series.size(); i <= valid_request_id; ++i) {
302 time_series.push_back(TimeSlot());
303 }
304 TimeSlot& slot = time_series[valid_request_id];
305 slot.request_id = valid_request_id;
306 slot.start += GetBootTimeNs();
307 }
308
309 if ((setting_ & SetPropFlag::kCalculateFpsOnEndBit) == 0) {
310 ProfileFrameRate(name);
311 }
312 }
313
End(const std::string & name,int request_id)314 void ProfilerImpl::End(const std::string& name, int request_id) {
315 if (setting_ == SetPropFlag::kDisable) {
316 return;
317 }
318
319 // When the request_id == kInvalidRequestId, it is served as a different
320 // purpose, eg. profiling first frame latency, or HAL total runtime. The valid
321 // request id is shifted by 1 to avoid the conflict.
322 int valid_request_id = (request_id == kInvalidRequestId) ? 0 : request_id + 1;
323
324 {
325 std::lock_guard<std::mutex> lk(lock_);
326 if (static_cast<std::size_t>(valid_request_id) < timing_map_[name].size()) {
327 TimeSlot& slot = timing_map_[name][valid_request_id];
328 slot.end += GetBootTimeNs();
329 ++slot.count;
330 }
331 }
332
333 if ((setting_ & SetPropFlag::kCalculateFpsOnEndBit) != 0) {
334 ProfileFrameRate(name);
335 }
336 }
337
PrintResult()338 void ProfilerImpl::PrintResult() {
339 ALOGI("UseCase: %s. Profiled Frames: %d.", use_case_.c_str(),
340 static_cast<int>(timing_map_.begin()->second.size()));
341
342 std::vector<TimeResult> time_results;
343
344 float sum_avg = 0.f;
345 float max_max = 0.f;
346 float sum_min = 0.f;
347 float sum_max = 0.f;
348 for (const auto& [node_name, time_series] : timing_map_) {
349 int num_frames = 0;
350 int num_samples = 0;
351 float sum_dt = 0.f;
352 float min_dt = std::numeric_limits<float>::max();
353 float max_dt = 0.f;
354 float mean_dt = 0.f;
355 std::vector<float> elapses;
356 for (const auto& slot : time_series) {
357 if (slot.count > 0) {
358 float elapsed = (slot.end - slot.start) * kNanoToMilli;
359 sum_dt += elapsed;
360 num_samples += slot.count;
361 min_dt = std::min(min_dt, elapsed);
362 max_dt = std::max(max_dt, elapsed);
363 num_frames++;
364 elapses.push_back(elapsed);
365 }
366 }
367 if (num_samples == 0) {
368 continue;
369 }
370 float avg = sum_dt / std::max(1, num_samples);
371 float avg_count = static_cast<float>(num_samples) /
372 static_cast<float>(std::max(1, num_frames));
373 mean_dt = avg * avg_count;
374 sum_avg += mean_dt;
375 sum_min += min_dt;
376 sum_max += max_dt;
377 max_max = std::max(max_max, max_dt);
378
379 // calculate StandardDeviation
380 float mean_max_stddevs = 0.f;
381 if (elapses.size() > 1) {
382 float dev_dt = StandardDeviation(elapses, mean_dt);
383 mean_max_stddevs = (max_dt - mean_dt) / dev_dt;
384 }
385
386 TimeSlot& frame_rate = frame_rate_map_[node_name];
387 int64_t duration = frame_rate.end - frame_rate.start;
388 float fps = 0;
389 if (duration > kNsPerSec) {
390 fps = frame_rate.count * kNsPerSec / static_cast<float>(duration);
391 }
392 time_results.push_back(
393 {node_name, min_dt, max_dt, mean_dt, avg_count, fps, mean_max_stddevs});
394 }
395
396 std::sort(time_results.begin(), time_results.end(),
397 [](auto a, auto b) { return a.avg_dt > b.avg_dt; });
398
399 for (const auto& result : time_results) {
400 if (result.fps == 0) {
401 ALOGI(
402 "%51.51s Min: %8.3f ms, Max: %8.3f ms, Avg: %7.3f ms "
403 "(Count = %3.1f), mean_max_stddevs: %6.2f, fps: NA",
404 result.node_name.c_str(), result.min_dt, result.max_dt, result.avg_dt,
405 result.avg_count, result.mean_max_stddevs);
406 } else {
407 ALOGI(
408 "%51.51s Min: %8.3f ms, Max: %8.3f ms, Avg: %7.3f ms "
409 "(Count = %3.1f), mean_max_stddevs: %6.2f, fps: %8.2f",
410 result.node_name.c_str(), result.min_dt, result.max_dt, result.avg_dt,
411 result.avg_count, result.mean_max_stddevs, result.fps);
412 }
413 }
414
415 ALOGI("%43.43s MIN SUM: %8.3f ms, MAX SUM: %8.3f ms, AVG SUM: %7.3f ms",
416 "", sum_min, sum_max, sum_avg);
417 ALOGI("");
418 }
419
DumpTxt(std::string_view filepath)420 void ProfilerImpl::DumpTxt(std::string_view filepath) {
421 // The dump result data is organized as 3 sections:
422 // 1. detla time and fps of each frame.
423 // 2. start time of each frame.
424 // 3. end time of each frame.
425 if (std::ofstream fout(filepath, std::ios::out); fout.is_open()) {
426 fout << "// PROFILER_DELTA_TIME_AND_FPS, UNIT:MILLISECOND //\n";
427 for (const auto& [node_name, time_series] : timing_map_) {
428 fout << node_name << " ";
429 for (const auto& time_slot : time_series) {
430 float elapsed = static_cast<float>(time_slot.end - time_slot.start) /
431 std::max(1, time_slot.count);
432 fout << elapsed * kNanoToMilli << " ";
433 }
434 fout << "\n";
435 TimeSlot& frame_rate = frame_rate_map_[node_name];
436 int64_t duration = frame_rate.end - frame_rate.start;
437 float fps = 0;
438 if (duration > kNsPerSec) {
439 fps = frame_rate.count * kNsPerSec / static_cast<float>(duration);
440 }
441 if (fps > 0) {
442 fout << node_name << " fps:" << fps;
443 } else {
444 fout << node_name << " fps: NA";
445 }
446 fout << "\n";
447 }
448
449 fout << "\n// PROFILER_START_TIME, AVG TIMESTAMP, UNIT:NANOSECOND //\n";
450 for (const auto& [node_name, time_series] : timing_map_) {
451 fout << node_name << " ";
452 for (const auto& time_slot : time_series) {
453 int64_t avg_time_stamp = time_slot.start / std::max(1, time_slot.count);
454 fout << avg_time_stamp << " ";
455 }
456 fout << "\n";
457 }
458
459 fout << "\n// PROFILER_END_TIME, AVG TIMESTAMP, UNIT:NANOSECOND //\n";
460 for (const auto& [node_name, time_series] : timing_map_) {
461 fout << node_name << " ";
462 for (const auto& time_slot : time_series) {
463 int64_t avg_time_stamp = time_slot.end / std::max(1, time_slot.count);
464 fout << avg_time_stamp << " ";
465 }
466 fout << "\n";
467 }
468 fout.close();
469 }
470 }
471
DumpPb(std::string_view filepath)472 void ProfilerImpl::DumpPb(std::string_view filepath) {
473 if (std::ofstream fout(filepath, std::ios::out); fout.is_open()) {
474 profiler::ProfilingResult profiling_result;
475 profiling_result.set_usecase(use_case_);
476 profiling_result.set_profile_start_time_nanos(object_init_real_time_);
477 profiling_result.set_profile_start_boottime_nanos(object_init_boot_time_);
478 profiling_result.set_profile_end_time_nanos(GetRealTimeNs());
479
480 for (const auto& [node_name, time_series] : timing_map_) {
481 profiler::TimeSeries& target = *profiling_result.add_target();
482 target.set_name(node_name);
483 for (const auto& time_slot : time_series) {
484 profiler::TimeStamp& time_stamp = *target.add_runtime();
485 // A single node can be called multiple times in a frame. Every time the
486 // node is called in the same frame, the profiler accumulates the
487 // timestamp value in time_slot.start/end, and increments the count.
488 // Therefore the result timestamp we stored is the `average` timestamp.
489 // Note: consider using minimum-start, and maximum-end.
490 time_stamp.set_start(time_slot.start / std::max(1, time_slot.count));
491 time_stamp.set_end(time_slot.end / std::max(1, time_slot.count));
492 time_stamp.set_count(time_slot.count);
493 time_stamp.set_request_id(time_slot.request_id);
494 }
495 }
496 profiling_result.SerializeToOstream(&fout);
497 fout.close();
498 }
499 }
500
501 // Get the latency associated with the name
GetLatencyData()502 std::list<std::pair<std::string, float>> ProfilerImpl::GetLatencyData() {
503 std::list<std::pair<std::string, TimeSlot>> time_results;
504 std::list<std::pair<std::string, float>> latency_data;
505 for (const auto& [node_name, time_series] : timing_map_) {
506 for (const auto& slot : time_series) {
507 if (slot.count > 0 && time_results.size() < time_results.max_size()) {
508 time_results.push_back({node_name, slot});
509 }
510 }
511 }
512 time_results.sort(
513 [](const auto& a, const auto& b) { return a.second.end < b.second.end; });
514
515 for (const auto& [node_name, slot] : time_results) {
516 if (slot.count > 0) {
517 float elapsed = (slot.end - slot.start) * kNanoToMilli;
518 latency_data.push_back({node_name, elapsed});
519 }
520 }
521 return latency_data;
522 }
523
524 class ProfilerStopwatchImpl : public ProfilerImpl {
525 public:
ProfilerStopwatchImpl(SetPropFlag setting)526 ProfilerStopwatchImpl(SetPropFlag setting) : ProfilerImpl(setting){};
527
~ProfilerStopwatchImpl()528 ~ProfilerStopwatchImpl() {
529 if (setting_ == SetPropFlag::kDisable || timing_map_.size() == 0) {
530 return;
531 }
532 if (setting_ & SetPropFlag::kPrintBit) {
533 // Virtual function won't work in parent class's destructor. need to
534 // call it by ourself.
535 PrintResult();
536 // Erase the print bit to prevent parent class print again.
537 setting_ = static_cast<SetPropFlag>(setting_ & (~SetPropFlag::kPrintBit));
538 }
539 if (setting_ & SetPropFlag::kDumpBit) {
540 DumpResult(dump_file_prefix_ + use_case_ + "-TS" +
541 std::to_string(object_init_real_time_) + ".txt");
542 setting_ = static_cast<SetPropFlag>(setting_ & (~SetPropFlag::kDumpBit));
543 }
544 }
545
546 // Print out the profiling result in the standard output (ANDROID_LOG_ERROR)
547 // with stopwatch mode.
PrintResult()548 void PrintResult() override {
549 ALOGI("Profiling Case: %s", use_case_.c_str());
550
551 // Sort by end time.
552 std::list<std::pair<std::string, TimeSlot>> time_results;
553 for (const auto& [node_name, time_series] : timing_map_) {
554 for (const auto& slot : time_series) {
555 if (slot.count > 0 && time_results.size() < time_results.max_size()) {
556 time_results.push_back({node_name, slot});
557 }
558 }
559 }
560 time_results.sort([](const auto& a, const auto& b) {
561 return a.second.end < b.second.end;
562 });
563
564 for (const auto& [node_name, slot] : time_results) {
565 if (slot.count > 0) {
566 float elapsed = (slot.end - slot.start) * kNanoToMilli;
567 ALOGI("%51.51s: %8.3f ms", node_name.c_str(), elapsed);
568 }
569 }
570
571 ALOGI("");
572 }
573
DumpResult(const std::string & filepath)574 void DumpResult(const std::string& filepath) override {
575 if (std::ofstream fout(filepath, std::ios::out); fout.is_open()) {
576 for (const auto& [node_name, time_series] : timing_map_) {
577 fout << node_name << " ";
578 for (const auto& slot : time_series) {
579 fout << (slot.end - slot.start) * kNanoToMilli << " ";
580 }
581 fout << "\n";
582 }
583 fout.close();
584 }
585 }
586 };
587
588 // Dummpy profiler class.
589 class ProfilerDummy : public Profiler {
590 public:
ProfilerDummy()591 ProfilerDummy(){};
~ProfilerDummy()592 ~ProfilerDummy(){};
593
SetUseCase(std::string)594 void SetUseCase(std::string) override final{};
SetDumpFilePrefix(const std::string &)595 void SetDumpFilePrefix(const std::string&) override final{};
Start(const std::string &,int)596 void Start(const std::string&, int) override final{};
End(const std::string &,int)597 void End(const std::string&, int) override final{};
PrintResult()598 void PrintResult() override final{};
ProfileFrameRate(const std::string &)599 void ProfileFrameRate(const std::string&) override final{};
SetFpsPrintInterval(int32_t)600 void SetFpsPrintInterval(int32_t) override final{};
GetLatencyData()601 std::list<std::pair<std::string, float>> GetLatencyData() override final {
602 return {};
603 }
GetUseCase() const604 std::string GetUseCase() const override final {
605 return "";
606 }
607 };
608
609 } // anonymous namespace
610
Create(int option)611 std::shared_ptr<Profiler> Profiler::Create(int option) {
612 SetPropFlag flag = static_cast<SetPropFlag>(option);
613
614 if (flag == SetPropFlag::kDisable) {
615 return std::make_shared<ProfilerDummy>();
616 } else if (flag & SetPropFlag::kStopWatch) {
617 return std::make_shared<ProfilerStopwatchImpl>(flag);
618 } else {
619 return std::make_shared<ProfilerImpl>(flag);
620 }
621 }
622
623 } // namespace camera_common
624 } // namespace google
625