1 /*
2 **
3 ** Copyright 2015, The Android Open Source Project
4 **
5 ** Licensed under the Apache License, Version 2.0 (the "License");
6 ** you may not use this file except in compliance with the License.
7 ** You may obtain a copy of the License at
8 **
9 ** http://www.apache.org/licenses/LICENSE-2.0
10 **
11 ** Unless required by applicable law or agreed to in writing, software
12 ** distributed under the License is distributed on an "AS IS" BASIS,
13 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 ** See the License for the specific language governing permissions and
15 ** limitations under the License.
16 */
17
18 #include <assert.h>
19 #include <dirent.h>
20 #include <errno.h>
21 #include <fcntl.h>
22 #include <signal.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <sys/stat.h>
27 #include <sys/types.h>
28 #include <sys/wait.h>
29 #include <time.h>
30 #include <unistd.h>
31
32 #include <memory>
33 #include <sstream>
34 #include <string>
35
36 #include <android-base/file.h>
37 #include <android-base/logging.h>
38 #include <android-base/macros.h>
39 #include <android-base/scopeguard.h>
40 #include <android-base/stringprintf.h>
41
42 #ifdef __BIONIC__
43 #include <android-base/properties.h>
44 #endif
45
46 #ifdef __ANDROID__
47 #include <healthhalutils/HealthHalUtils.h>
48 #endif
49
50 #include "perfprofd_record.pb.h"
51
52 #include "config.h"
53 #include "cpuconfig.h"
54 #include "perf_data_converter.h"
55 #include "perfprofdcore.h"
56 #include "perfprofd_io.h"
57 #include "perfprofd_perf.h"
58 #include "symbolizer.h"
59
60 //
61 // Perf profiling daemon -- collects system-wide profiles using
62 //
63 // simpleperf record -a
64 //
65 // and encodes them so that they can be uploaded by a separate service.
66 //
67
68 //......................................................................
69
70 using ProtoUniquePtr = std::unique_ptr<android::perfprofd::PerfprofdRecord>;
71
72 //
73 // Output file from 'perf record'.
74 //
75 #define PERF_OUTPUT "perf.data"
76
77 //
78 // This enum holds the results of the "should we profile" configuration check.
79 //
80 typedef enum {
81
82 // All systems go for profile collection.
83 DO_COLLECT_PROFILE,
84
85 // The selected configuration directory doesn't exist.
86 DONT_PROFILE_MISSING_CONFIG_DIR,
87
88 // Destination directory does not contain the semaphore file that
89 // the perf profile uploading service creates when it determines
90 // that the user has opted "in" for usage data collection. No
91 // semaphore -> no user approval -> no profiling.
92 DONT_PROFILE_MISSING_SEMAPHORE,
93
94 // No perf executable present
95 DONT_PROFILE_MISSING_PERF_EXECUTABLE,
96
97 // We're running in the emulator, perf won't be able to do much
98 DONT_PROFILE_RUNNING_IN_EMULATOR
99
100 } CKPROFILE_RESULT;
101
102 static bool common_initialized = false;
103
104 //
105 // Are we running in the emulator? If so, stub out profile collection
106 // Starts as uninitialized (-1), then set to 1 or 0 at init time.
107 //
108 static int running_in_emulator = -1;
109
110 //
111 // Is this a debug build ('userdebug' or 'eng')?
112 //
113 static bool is_debug_build = false;
114
115 //
116 // Random number generator seed (set at startup time).
117 //
118 static unsigned short random_seed[3];
119
120 //
121 // Convert a CKPROFILE_RESULT to a string
122 //
ckprofile_result_to_string(CKPROFILE_RESULT result)123 static const char *ckprofile_result_to_string(CKPROFILE_RESULT result)
124 {
125 switch (result) {
126 case DO_COLLECT_PROFILE:
127 return "DO_COLLECT_PROFILE";
128 case DONT_PROFILE_MISSING_CONFIG_DIR:
129 return "missing config directory";
130 case DONT_PROFILE_MISSING_SEMAPHORE:
131 return "missing semaphore file";
132 case DONT_PROFILE_MISSING_PERF_EXECUTABLE:
133 return "missing 'perf' executable";
134 case DONT_PROFILE_RUNNING_IN_EMULATOR:
135 return "running in emulator";
136 default:
137 return "unknown";
138 }
139 }
140
141 //
142 // Check to see whether we should perform a profile collection
143 //
check_profiling_enabled(const Config & config)144 static CKPROFILE_RESULT check_profiling_enabled(const Config& config)
145 {
146 //
147 // Profile collection in the emulator doesn't make sense
148 //
149 assert(running_in_emulator != -1);
150 if (running_in_emulator) {
151 return DONT_PROFILE_RUNNING_IN_EMULATOR;
152 }
153
154 if (!config.IsProfilingEnabled()) {
155 return DONT_PROFILE_MISSING_CONFIG_DIR;
156 }
157
158 // Check for existence of simpleperf/perf executable
159 std::string pp = config.perf_path;
160 if (access(pp.c_str(), R_OK|X_OK) == -1) {
161 LOG(WARNING) << "unable to access/execute " << pp;
162 return DONT_PROFILE_MISSING_PERF_EXECUTABLE;
163 }
164
165 //
166 // We are good to go
167 //
168 return DO_COLLECT_PROFILE;
169 }
170
get_booting()171 bool get_booting()
172 {
173 #ifdef __BIONIC__
174 return android::base::GetBoolProperty("sys.boot_completed", false) != true;
175 #else
176 return false;
177 #endif
178 }
179
180 //
181 // Constructor takes a timeout (in seconds) and a child pid; If an
182 // alarm set for the specified number of seconds triggers, then a
183 // SIGKILL is sent to the child. Destructor resets alarm. Example:
184 //
185 // pid_t child_pid = ...;
186 // { AlarmHelper h(10, child_pid);
187 // ... = read_from_child(child_pid, ...);
188 // }
189 //
190 // NB: this helper is not re-entrant-- avoid nested use or
191 // use by multiple threads
192 //
193 class AlarmHelper {
194 public:
AlarmHelper(unsigned num_seconds,pid_t child)195 AlarmHelper(unsigned num_seconds, pid_t child)
196 {
197 struct sigaction sigact;
198 assert(child);
199 assert(child_ == 0);
200 memset(&sigact, 0, sizeof(sigact));
201 sigact.sa_sigaction = handler;
202 sigaction(SIGALRM, &sigact, &oldsigact_);
203 child_ = child;
204 alarm(num_seconds);
205 }
~AlarmHelper()206 ~AlarmHelper()
207 {
208 alarm(0);
209 child_ = 0;
210 sigaction(SIGALRM, &oldsigact_, NULL);
211 }
212 static void handler(int, siginfo_t *, void *);
213
214 private:
215 struct sigaction oldsigact_;
216 static pid_t child_;
217 };
218
219 pid_t AlarmHelper::child_;
220
handler(int,siginfo_t *,void *)221 void AlarmHelper::handler(int, siginfo_t *, void *)
222 {
223 LOG(WARNING) << "SIGALRM timeout";
224 kill(child_, SIGKILL);
225 }
226
227 //
228 // This implementation invokes "dumpsys media.camera" and inspects the
229 // output to determine if any camera clients are active. NB: this is
230 // currently disable (via config option) until the selinux issues can
231 // be sorted out. Another possible implementation (not yet attempted)
232 // would be to use the binder to call into the native camera service
233 // via "ICameraService".
234 //
get_camera_active()235 bool get_camera_active()
236 {
237 int pipefds[2];
238 if (pipe2(pipefds, O_CLOEXEC) != 0) {
239 PLOG(ERROR) << "pipe2() failed";
240 return false;
241 }
242 pid_t pid = fork();
243 if (pid == -1) {
244 PLOG(ERROR) << "fork() failed";
245 close(pipefds[0]);
246 close(pipefds[1]);
247 return false;
248 } else if (pid == 0) {
249 // child
250 close(pipefds[0]);
251 dup2(pipefds[1], fileno(stderr));
252 dup2(pipefds[1], fileno(stdout));
253 const char *argv[10];
254 unsigned slot = 0;
255 argv[slot++] = "/system/bin/dumpsys";
256 argv[slot++] = "media.camera";
257 argv[slot++] = nullptr;
258 execvp(argv[0], (char * const *)argv);
259 PLOG(ERROR) << "execvp() failed";
260 return false;
261 }
262 // parent
263 AlarmHelper helper(10, pid);
264 close(pipefds[1]);
265
266 // read output
267 bool have_cam = false;
268 bool have_clients = true;
269 std::string dump_output;
270 bool result = android::base::ReadFdToString(pipefds[0], &dump_output);
271 close(pipefds[0]);
272 if (result) {
273 std::stringstream ss(dump_output);
274 std::string line;
275 while (std::getline(ss,line,'\n')) {
276 if (line.find("Camera module API version:") !=
277 std::string::npos) {
278 have_cam = true;
279 }
280 if (line.find("No camera module available") !=
281 std::string::npos ||
282 line.find("No active camera clients yet") !=
283 std::string::npos) {
284 have_clients = false;
285 }
286 }
287 }
288
289 // reap child (no zombies please)
290 int st = 0;
291 TEMP_FAILURE_RETRY(waitpid(pid, &st, 0));
292 return have_cam && have_clients;
293 }
294
get_charging()295 bool get_charging()
296 {
297 #ifdef __ANDROID__
298 using android::sp;
299 using android::hardware::Return;
300 using android::hardware::health::V2_0::get_health_service;
301 using android::hardware::health::V2_0::HealthInfo;
302 using android::hardware::health::V2_0::IHealth;
303 using android::hardware::health::V2_0::Result;
304
305 sp<IHealth> service = get_health_service();
306 if (service == nullptr) {
307 LOG(ERROR) << "Failed to get health HAL";
308 return false;
309 }
310 Result res = Result::UNKNOWN;
311 HealthInfo val;
312 Return<void> ret =
313 service->getHealthInfo([&](Result out_res, HealthInfo out_val) {
314 res = out_res;
315 val = out_val;
316 });
317 if (!ret.isOk()) {
318 LOG(ERROR) << "Failed to call getChargeStatus on health HAL: " << ret.description();
319 return false;
320 }
321 if (res != Result::SUCCESS) {
322 LOG(ERROR) << "Failed to retrieve charge status from health HAL: result = "
323 << toString(res);
324 return false;
325 }
326 return val.legacy.chargerAcOnline || val.legacy.chargerUsbOnline ||
327 val.legacy.chargerWirelessOnline;
328 #else
329 return false;
330 #endif
331 }
332
postprocess_proc_stat_contents(const std::string & pscontents,long unsigned * idleticks,long unsigned * remainingticks)333 static bool postprocess_proc_stat_contents(const std::string &pscontents,
334 long unsigned *idleticks,
335 long unsigned *remainingticks)
336 {
337 long unsigned usertime, nicetime, systime, idletime, iowaittime;
338 long unsigned irqtime, softirqtime;
339
340 int rc = sscanf(pscontents.c_str(), "cpu %lu %lu %lu %lu %lu %lu %lu",
341 &usertime, &nicetime, &systime, &idletime,
342 &iowaittime, &irqtime, &softirqtime);
343 if (rc != 7) {
344 return false;
345 }
346 *idleticks = idletime;
347 *remainingticks = usertime + nicetime + systime + iowaittime + irqtime + softirqtime;
348 return true;
349 }
350
collect_cpu_utilization()351 unsigned collect_cpu_utilization()
352 {
353 std::string contents;
354 long unsigned idle[2];
355 long unsigned busy[2];
356 for (unsigned iter = 0; iter < 2; ++iter) {
357 if (!android::base::ReadFileToString("/proc/stat", &contents)) {
358 return 0;
359 }
360 if (!postprocess_proc_stat_contents(contents, &idle[iter], &busy[iter])) {
361 return 0;
362 }
363 if (iter == 0) {
364 sleep(1);
365 }
366 }
367 long unsigned total_delta = (idle[1] + busy[1]) - (idle[0] + busy[0]);
368 long unsigned busy_delta = busy[1] - busy[0];
369 return busy_delta * 100 / total_delta;
370 }
371
annotate_encoded_perf_profile(android::perfprofd::PerfprofdRecord * profile,const Config & config,unsigned cpu_utilization)372 static void annotate_encoded_perf_profile(android::perfprofd::PerfprofdRecord* profile,
373 const Config& config,
374 unsigned cpu_utilization)
375 {
376 //
377 // Incorporate cpu utilization (collected prior to perf run)
378 //
379 if (config.collect_cpu_utilization) {
380 profile->SetExtension(quipper::cpu_utilization, cpu_utilization);
381 }
382
383 //
384 // Load average as reported by the kernel
385 //
386 std::string load;
387 double fload = 0.0;
388 if (android::base::ReadFileToString("/proc/loadavg", &load) &&
389 sscanf(load.c_str(), "%lf", &fload) == 1) {
390 int iload = static_cast<int>(fload * 100.0);
391 profile->SetExtension(quipper::sys_load_average, iload);
392 } else {
393 PLOG(ERROR) << "Failed to read or scan /proc/loadavg";
394 }
395
396 //
397 // Device still booting? Camera in use? Plugged into charger?
398 //
399 bool is_booting = get_booting();
400 if (config.collect_booting) {
401 profile->SetExtension(quipper::booting, is_booting);
402 }
403 if (config.collect_camera_active) {
404 profile->SetExtension(quipper::camera_active, is_booting ? false : get_camera_active());
405 }
406 if (config.collect_charging_state) {
407 profile->SetExtension(quipper::on_charger, get_charging());
408 }
409
410 //
411 // Examine the contents of wake_unlock to determine whether the
412 // device display is on or off. NB: is this really the only way to
413 // determine this info?
414 //
415 std::string disp;
416 if (android::base::ReadFileToString("/sys/power/wake_unlock", &disp)) {
417 bool ison = (strstr(disp.c_str(), "PowerManagerService.Display") == 0);
418 profile->SetExtension(quipper::display_on, ison);
419 } else {
420 PLOG(ERROR) << "Failed to read /sys/power/wake_unlock";
421 }
422 }
423
encode_to_proto(const std::string & data_file_path,const Config & config,unsigned cpu_utilization,perfprofd::Symbolizer * symbolizer)424 static ProtoUniquePtr encode_to_proto(const std::string &data_file_path,
425 const Config& config,
426 unsigned cpu_utilization,
427 perfprofd::Symbolizer* symbolizer) {
428 //
429 // Open and read perf.data file
430 //
431 ProtoUniquePtr encodedProfile(
432 android::perfprofd::RawPerfDataToAndroidPerfProfile(data_file_path,
433 symbolizer,
434 config.symbolize_everything));
435 if (encodedProfile == nullptr) {
436 return nullptr;
437 }
438
439 // All of the info in 'encodedProfile' is derived from the perf.data file;
440 // here we tack display status, cpu utilization, system load, etc.
441 annotate_encoded_perf_profile(encodedProfile.get(), config, cpu_utilization);
442
443 return encodedProfile;
444 }
445
encode_to_proto(const std::string & data_file_path,const char * encoded_file_path,const Config & config,unsigned cpu_utilization,perfprofd::Symbolizer * symbolizer)446 PROFILE_RESULT encode_to_proto(const std::string &data_file_path,
447 const char *encoded_file_path,
448 const Config& config,
449 unsigned cpu_utilization,
450 perfprofd::Symbolizer* symbolizer)
451 {
452 ProtoUniquePtr encodedProfile = encode_to_proto(data_file_path,
453 config,
454 cpu_utilization,
455 symbolizer);
456
457 //
458 // Issue error if no samples
459 //
460 if (encodedProfile == nullptr || encodedProfile->events_size() == 0) {
461 return ERR_PERF_ENCODE_FAILED;
462 }
463
464 return android::perfprofd::SerializeProtobuf(encodedProfile.get(),
465 encoded_file_path,
466 config.compress)
467 ? OK_PROFILE_COLLECTION
468 : ERR_WRITE_ENCODED_FILE_FAILED;
469 }
470
471 //
472 // Remove all files in the destination directory during initialization
473 //
cleanup_destination_dir(const std::string & dest_dir)474 static void cleanup_destination_dir(const std::string& dest_dir)
475 {
476 DIR* dir = opendir(dest_dir.c_str());
477 if (dir != NULL) {
478 struct dirent* e;
479 while ((e = readdir(dir)) != 0) {
480 if (e->d_name[0] != '.') {
481 std::string file_path = dest_dir + "/" + e->d_name;
482 remove(file_path.c_str());
483 }
484 }
485 closedir(dir);
486 } else {
487 PLOG(WARNING) << "unable to open destination dir " << dest_dir << " for cleanup";
488 }
489 }
490
491 //
492 // Collect a perf profile. Steps for this operation are:
493 // - kick off 'perf record'
494 // - read perf.data, convert to protocol buf
495 //
collect_profile(Config & config)496 static ProtoUniquePtr collect_profile(Config& config)
497 {
498 //
499 // Collect cpu utilization if enabled
500 //
501 unsigned cpu_utilization = 0;
502 if (config.collect_cpu_utilization) {
503 cpu_utilization = collect_cpu_utilization();
504 }
505
506 //
507 // Form perf.data file name, perf error output file name
508 //
509 const std::string& destdir = config.destination_directory;
510 std::string data_file_path(destdir);
511 data_file_path += "/";
512 data_file_path += PERF_OUTPUT;
513 std::string perf_stderr_path(destdir);
514 perf_stderr_path += "/perferr.txt";
515
516 //
517 // Remove any existing perf.data file -- if we don't do this, perf
518 // will rename the old file and we'll have extra cruft lying around.
519 //
520 struct stat statb;
521 if (stat(data_file_path.c_str(), &statb) == 0) { // if file exists...
522 if (unlink(data_file_path.c_str())) { // then try to remove
523 PLOG(WARNING) << "unable to unlink previous perf.data file";
524 }
525 }
526
527 //
528 // The "mpdecision" daemon can cause problems for profile
529 // collection: if it decides to online a CPU partway through the
530 // 'perf record' run, the activity on that CPU will be invisible to
531 // perf, and if it offlines a CPU during the recording this can
532 // sometimes leave the PMU in an unusable state (dmesg errors of the
533 // form "perfevents: unable to request IRQXXX for ..."). To avoid
534 // these issues, if "mpdecision" is running the helper below will
535 // stop the service and then online all available CPUs. The object
536 // destructor (invoked when this routine terminates) will then
537 // restart the service again when needed.
538 //
539 uint32_t duration = config.sample_duration_in_s;
540 bool hardwire = config.hardwire_cpus;
541 uint32_t max_duration = config.hardwire_cpus_max_duration_in_s;
542 bool take_action = (hardwire && duration <= max_duration);
543 HardwireCpuHelper helper(take_action);
544
545 auto scope_guard = android::base::make_scope_guard(
546 [&data_file_path]() { unlink(data_file_path.c_str()); });
547
548 //
549 // Invoke perf
550 //
551 const char *stack_profile_opt =
552 (config.stack_profile ? "-g" : nullptr);
553 const std::string& perf_path = config.perf_path;
554
555 android::perfprofd::PerfResult invoke_res =
556 android::perfprofd::InvokePerf(config,
557 perf_path,
558 stack_profile_opt,
559 duration,
560 data_file_path,
561 perf_stderr_path);
562 if (invoke_res != android::perfprofd::PerfResult::kOK) {
563 return nullptr;
564 }
565
566 //
567 // Read the resulting perf.data file, encode into protocol buffer, then write
568 // the result to the file perf.data.encoded
569 //
570 std::unique_ptr<perfprofd::Symbolizer> symbolizer;
571 if (config.use_elf_symbolizer) {
572 symbolizer = perfprofd::CreateELFSymbolizer();
573 }
574 return encode_to_proto(data_file_path, config, cpu_utilization, symbolizer.get());
575 }
576
577 //
578 // Assuming that we want to collect a profile every N seconds,
579 // randomly partition N into two sub-intervals.
580 //
determine_before_after(unsigned & sleep_before_collect,unsigned & sleep_after_collect,unsigned collection_interval)581 static void determine_before_after(unsigned &sleep_before_collect,
582 unsigned &sleep_after_collect,
583 unsigned collection_interval)
584 {
585 double frac = erand48(random_seed);
586 sleep_before_collect = (unsigned) (((double)collection_interval) * frac);
587 assert(sleep_before_collect <= collection_interval);
588 sleep_after_collect = collection_interval - sleep_before_collect;
589 }
590
591 //
592 // Set random number generator seed
593 //
set_seed(uint32_t use_fixed_seed)594 static void set_seed(uint32_t use_fixed_seed)
595 {
596 unsigned seed = 0;
597 if (use_fixed_seed) {
598 //
599 // Use fixed user-specified seed
600 //
601 seed = use_fixed_seed;
602 } else {
603 //
604 // Randomized seed
605 //
606 #ifdef __BIONIC__
607 seed = arc4random();
608 #else
609 seed = 12345678u;
610 #endif
611 }
612 LOG(INFO) << "random seed set to " << seed;
613 // Distribute the 32-bit seed into the three 16-bit array
614 // elements. The specific values being written do not especially
615 // matter as long as we are setting them to something based on the seed.
616 random_seed[0] = seed & 0xffff;
617 random_seed[1] = (seed >> 16);
618 random_seed[2] = (random_seed[0] ^ random_seed[1]);
619 }
620
CommonInit(uint32_t use_fixed_seed,const char * dest_dir)621 void CommonInit(uint32_t use_fixed_seed, const char* dest_dir) {
622 // Children of init inherit an artificially low OOM score -- this is not
623 // desirable for perfprofd (its OOM score should be on par with
624 // other user processes).
625 std::stringstream oomscore_path;
626 oomscore_path << "/proc/" << getpid() << "/oom_score_adj";
627 if (!android::base::WriteStringToFile("0", oomscore_path.str())) {
628 LOG(ERROR) << "unable to write to " << oomscore_path.str();
629 }
630
631 set_seed(use_fixed_seed);
632 if (dest_dir != nullptr) {
633 cleanup_destination_dir(dest_dir);
634 }
635
636 #ifdef __BIONIC__
637 running_in_emulator = android::base::GetBoolProperty("ro.kernel.qemu", false);
638 is_debug_build = android::base::GetBoolProperty("ro.debuggable", false);
639 #else
640 running_in_emulator = false;
641 is_debug_build = true;
642 #endif
643
644 common_initialized = true;
645 }
646
GlobalInit(const std::string & perf_path)647 void GlobalInit(const std::string& perf_path) {
648 if (!android::perfprofd::FindSupportedPerfCounters(perf_path)) {
649 LOG(WARNING) << "Could not read supported perf counters.";
650 }
651 }
652
IsDebugBuild()653 bool IsDebugBuild() {
654 CHECK(common_initialized);
655 return is_debug_build;
656 }
657
658 template <typename ConfigFn, typename UpdateFn>
ProfilingLoopImpl(ConfigFn config,UpdateFn update,HandlerFn handler)659 static void ProfilingLoopImpl(ConfigFn config, UpdateFn update, HandlerFn handler) {
660 unsigned iterations = 0;
661 while(config()->main_loop_iterations == 0 ||
662 iterations < config()->main_loop_iterations) {
663 if (config()->ShouldStopProfiling()) {
664 return;
665 }
666
667 // Figure out where in the collection interval we're going to actually
668 // run perf
669 unsigned sleep_before_collect = 0;
670 unsigned sleep_after_collect = 0;
671 determine_before_after(sleep_before_collect,
672 sleep_after_collect,
673 config()->collection_interval_in_s);
674 if (sleep_before_collect > 0) {
675 config()->Sleep(sleep_before_collect);
676 }
677
678 if (config()->ShouldStopProfiling()) {
679 return;
680 }
681
682 // Run any necessary updates.
683 update();
684
685 // Check for profiling enabled...
686 CKPROFILE_RESULT ckresult = check_profiling_enabled(*config());
687 if (ckresult != DO_COLLECT_PROFILE) {
688 LOG(INFO) << "profile collection skipped (" << ckprofile_result_to_string(ckresult) << ")";
689 } else {
690 // Kick off the profiling run...
691 LOG(INFO) << "initiating profile collection";
692 ProtoUniquePtr proto = collect_profile(*config());
693 if (proto == nullptr) {
694 LOG(WARNING) << "profile collection failed";
695 }
696
697 // Always report, even a null result.
698 bool handle_result = handler(proto.get(), config());
699 if (handle_result) {
700 LOG(INFO) << "profile collection complete";
701 } else if (proto != nullptr) {
702 LOG(WARNING) << "profile handling failed";
703 }
704 }
705
706 if (config()->ShouldStopProfiling()) {
707 return;
708 }
709
710 if (sleep_after_collect > 0) {
711 config()->Sleep(sleep_after_collect);
712 }
713 iterations += 1;
714 }
715 }
716
ProfilingLoop(Config & config,HandlerFn handler)717 void ProfilingLoop(Config& config, HandlerFn handler) {
718 CommonInit(config.use_fixed_seed, nullptr);
719
720 auto config_fn = [&config]() {
721 return &config;;
722 };
723 auto do_nothing = []() {
724 };
725 ProfilingLoopImpl(config_fn, do_nothing, handler);
726 }
727
ProfilingLoop(std::function<Config * ()> config_fn,std::function<void ()> update_fn,HandlerFn handler)728 void ProfilingLoop(std::function<Config*()> config_fn,
729 std::function<void()> update_fn,
730 HandlerFn handler) {
731 ProfilingLoopImpl(config_fn, update_fn, handler);
732 }
733