1 // Copyright 2018 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include <perftest/runner.h>
6 
7 #include <getopt.h>
8 #include <pthread.h>
9 #include <regex.h>
10 
11 #include <fbl/function.h>
12 #include <fbl/string.h>
13 #include <fbl/string_printf.h>
14 #include <fbl/vector.h>
15 #include <lib/async-loop/cpp/loop.h>
16 #include <trace-engine/context.h>
17 #include <trace-engine/instrumentation.h>
18 #include <trace-provider/provider.h>
19 #include <trace/event.h>
20 #include <unittest/unittest.h>
21 #include <zircon/assert.h>
22 #include <zircon/syscalls.h>
23 
24 namespace perftest {
25 namespace {
26 
27 // g_tests needs to be POD because this list is populated by constructors.
28 // We don't want g_tests to have a constructor that might get run after
29 // items have been added to the list, because that would clobber the list.
30 internal::TestList* g_tests;
31 
32 class RepeatStateImpl : public RepeatState {
33 public:
RepeatStateImpl(uint32_t run_count)34     RepeatStateImpl(uint32_t run_count)
35         : run_count_(run_count) {}
36 
SetBytesProcessedPerRun(uint64_t bytes)37     void SetBytesProcessedPerRun(uint64_t bytes) override {
38         if (started_) {
39             SetError("SetBytesProcessedPerRun() was called after KeepRunning()");
40             return;
41         }
42         if (bytes == 0) {
43             SetError("Zero argument to SetBytesProcessedPerRun()");
44             return;
45         }
46         if (bytes_processed_per_run_ != 0) {
47             SetError("Multiple calls to SetBytesProcessedPerRun()");
48             return;
49         }
50         bytes_processed_per_run_ = bytes;
51     }
52 
DeclareStep(const char * name)53     void DeclareStep(const char* name) override {
54         if (started_) {
55             SetError("DeclareStep() was called after KeepRunning()");
56             return;
57         }
58         step_names_.push_back(name);
59     }
60 
NextStep()61     void NextStep() override {
62         if (unlikely(next_idx_ >= end_of_run_idx_)) {
63             SetError("Too many calls to NextStep()");
64             return;
65         }
66         timestamps_[next_idx_] = zx_ticks_get();
67         ++next_idx_;
68     }
69 
KeepRunning()70     bool KeepRunning() override {
71         uint64_t timestamp = zx_ticks_get();
72         if (unlikely(next_idx_ != end_of_run_idx_)) {
73             // Slow path, including error cases.
74             if (error_) {
75                 return false;
76             }
77             if (started_) {
78                 SetError("Wrong number of calls to NextStep()");
79                 return false;
80             }
81             // First call to KeepRunning().
82             step_count_ = static_cast<uint32_t>(step_names_.size());
83             if (step_count_ == 0) {
84                 step_count_ = 1;
85             }
86             // Add 1 because we store timestamps for the start of each test
87             // run (which serve as timestamps for the end of the previous
88             // test run), plus one more timestamp for the end of the last
89             // test run.
90             timestamps_size_ = run_count_ * step_count_ + 1;
91             timestamps_.reset(new uint64_t[timestamps_size_]);
92             // Clear the array in order to fault in the pages.  This should
93             // prevent page faults occurring as we cross page boundaries
94             // when writing a test's running times (which would affect the
95             // first test case but not later test cases).
96             memset(timestamps_.get(), 0,
97                    sizeof(timestamps_[0]) * timestamps_size_);
98             next_idx_ = 1;
99             end_of_run_idx_ = step_count_;
100             started_ = true;
101             timestamps_[0] = zx_ticks_get();
102             return run_count_ != 0;
103         }
104         if (unlikely(next_idx_ == timestamps_size_ - 1)) {
105             // End reached.
106             if (finished_) {
107                 SetError("Too many calls to KeepRunning()");
108                 return false;
109             }
110             timestamps_[next_idx_] = timestamp;
111             finished_ = true;
112             return false;
113         }
114         timestamps_[next_idx_] = timestamp;
115         ++next_idx_;
116         end_of_run_idx_ += step_count_;
117         return true;
118     }
119 
120     // Returns nullptr on success, or an error string on failure.
RunTestFunc(const char * test_name,const fbl::Function<TestFunc> & test_func)121     const char* RunTestFunc(const char* test_name,
122                             const fbl::Function<TestFunc>& test_func) {
123         TRACE_DURATION("perftest", "test_group", "test_name", test_name);
124         overall_start_time_ = zx_ticks_get();
125         bool result = test_func(this);
126         overall_end_time_ = zx_ticks_get();
127         if (error_) {
128             return error_;
129         }
130         if (!finished_) {
131             return "Too few calls to KeepRunning()";
132         }
133         if (!result) {
134             return "Test function returned false";
135         }
136         return nullptr;
137     }
138 
CopyTimeResults(const char * test_suite,const char * test_name,ResultsSet * dest) const139     void CopyTimeResults(const char* test_suite, const char* test_name,
140                          ResultsSet* dest) const {
141         // bytes_processed_per_run is used for calculating throughput, but
142         // throughput is only really meaningful to calculate for the test
143         // overall, not for individual steps.  Therefore we only report
144         // bytes_processed_per_run on the overall times.
145 
146         // Report the times for each test run.
147         if (step_count_ == 1 || bytes_processed_per_run_ != 0) {
148             TestCaseResults* results = dest->AddTestCase(
149                 test_suite, test_name, "nanoseconds");
150             results->bytes_processed_per_run = bytes_processed_per_run_;
151             CopyStepTimes(0, step_count_, results);
152         }
153 
154         if (step_count_ > 1) {
155             // Report times for individual steps.
156             for (uint32_t step = 0; step < step_count_; ++step) {
157                 fbl::String name = fbl::StringPrintf(
158                     "%s.%s", test_name, step_names_[step].c_str());
159                 TestCaseResults* results = dest->AddTestCase(
160                     test_suite, name, "nanoseconds");
161                 CopyStepTimes(step, step + 1, results);
162             }
163         }
164     }
165 
166     // Output a trace event for each of the test runs.  Since we do this
167     // after the test runs took place (using the timestamps we recorded),
168     // we avoid incurring the overhead of the tracing system on each test
169     // run.
WriteTraceEvents()170     void WriteTraceEvents() {
171         trace_string_ref_t category_ref;
172         trace_context_t* context =
173             trace_acquire_context_for_category("perftest", &category_ref);
174         if (!context) {
175             return;
176         }
177         trace_thread_ref_t thread_ref;
178         trace_context_register_current_thread(context, &thread_ref);
179 
180         auto WriteEvent = [&](trace_string_ref_t* name_ref,
181                               uint64_t start_time, uint64_t end_time) {
182             trace_context_write_duration_begin_event_record(
183                 context, start_time,
184                 &thread_ref, &category_ref, name_ref, nullptr, 0);
185             trace_context_write_duration_end_event_record(
186                 context, end_time,
187                 &thread_ref, &category_ref, name_ref, nullptr, 0);
188         };
189 
190         trace_string_ref_t test_setup_string;
191         trace_string_ref_t test_run_string;
192         trace_string_ref_t test_step_string;
193         trace_string_ref_t test_teardown_string;
194         trace_context_register_string_literal(
195             context, "test_setup", &test_setup_string);
196         trace_context_register_string_literal(
197             context, "test_run", &test_run_string);
198         trace_context_register_string_literal(
199             context, "test_step", &test_step_string);
200         trace_context_register_string_literal(
201             context, "test_teardown", &test_teardown_string);
202 
203         WriteEvent(&test_setup_string, overall_start_time_, timestamps_[0]);
204         for (uint32_t run = 0; run < run_count_; ++run) {
205             WriteEvent(&test_run_string,
206                        GetTimestamp(run, 0),
207                        GetTimestamp(run + 1, 0));
208             if (step_count_ > 1) {
209                 for (uint32_t step = 0; step < step_count_; ++step) {
210                     WriteEvent(&test_step_string,
211                                GetTimestamp(run, step),
212                                GetTimestamp(run, step + 1));
213                 }
214             }
215         }
216         WriteEvent(&test_teardown_string,
217                    timestamps_[timestamps_size_ - 1], overall_end_time_);
218     }
219 
220 private:
SetError(const char * str)221     void SetError(const char* str) {
222         if (!error_) {
223             error_ = str;
224         }
225     }
226 
227     // The start and end times of run R are GetTimestamp(R, 0) and
228     // GetTimestamp(R+1, 0).
229     // The start and end times of step S within run R are GetTimestamp(R,
230     // S) and GetTimestamp(R, S+1).
GetTimestamp(uint32_t run_number,uint32_t step_number) const231     uint64_t GetTimestamp(uint32_t run_number, uint32_t step_number) const {
232         uint32_t index = run_number * step_count_ + step_number;
233         ZX_ASSERT(step_number <= step_count_);
234         ZX_ASSERT(index < timestamps_size_);
235         return timestamps_[index];
236     }
237 
CopyStepTimes(uint32_t start_step_index,uint32_t end_step_index,TestCaseResults * results) const238     void CopyStepTimes(uint32_t start_step_index, uint32_t end_step_index,
239                        TestCaseResults* results) const {
240         double nanoseconds_per_tick =
241             1e9 / static_cast<double>(zx_ticks_per_second());
242 
243         // Copy the timing results, converting timestamps to elapsed times.
244         results->values.reserve(run_count_);
245         for (uint32_t run = 0; run < run_count_; ++run) {
246             uint64_t time_taken = (GetTimestamp(run, end_step_index) -
247                                    GetTimestamp(run, start_step_index));
248             results->AppendValue(
249                 static_cast<double>(time_taken) * nanoseconds_per_tick);
250         }
251     }
252 
253     // Number of test runs that we intend to do.
254     uint32_t run_count_;
255     // Number of steps per test run.  Once initialized, this is >= 1.
256     uint32_t step_count_;
257     // Names for steps.  May be empty if the test has only one step.
258     fbl::Vector<fbl::String> step_names_;
259     // error_ is set to non-null if an error occurs.
260     const char* error_ = nullptr;
261     // Array of timestamps for the starts and ends of test runs and of
262     // steps within runs.  GetTimestamp() describes the array layout.
263     fbl::unique_ptr<uint64_t[]> timestamps_;
264     // Number of elements allocated for timestamps_ array.
265     uint32_t timestamps_size_ = 0;
266     // Whether the first KeepRunning() call has occurred.
267     bool started_ = false;
268     // Whether the last KeepRunning() call has occurred.
269     bool finished_ = false;
270     // Next index in timestamps_ for writing a timestamp to.  The initial
271     // value helps catch invalid NextStep() calls.
272     uint32_t next_idx_ = ~static_cast<uint32_t>(0);
273     // Index in timestamp_ for writing the end of the current run.
274     uint32_t end_of_run_idx_ = 0;
275     // Start time, before the test's setup phase.
276     uint64_t overall_start_time_;
277     // End time, after the test's teardown phase.
278     uint64_t overall_end_time_;
279     // Used for calculating throughput in bytes per unit time.
280     uint64_t bytes_processed_per_run_ = 0;
281 };
282 
283 } // namespace
284 
RegisterTest(const char * name,fbl::Function<TestFunc> test_func)285 void RegisterTest(const char* name, fbl::Function<TestFunc> test_func) {
286     if (!g_tests) {
287         g_tests = new internal::TestList;
288     }
289     internal::NamedTest new_test{name, std::move(test_func)};
290     g_tests->push_back(std::move(new_test));
291 }
292 
RunTest(const char * test_suite,const char * test_name,const fbl::Function<TestFunc> & test_func,uint32_t run_count,ResultsSet * results_set,fbl::String * error_out)293 bool RunTest(const char* test_suite, const char* test_name,
294              const fbl::Function<TestFunc>& test_func,
295              uint32_t run_count, ResultsSet* results_set,
296              fbl::String* error_out) {
297     RepeatStateImpl state(run_count);
298     const char* error = state.RunTestFunc(test_name, test_func);
299     if (error) {
300         if (error_out) {
301             *error_out = error;
302         }
303         return false;
304     }
305 
306     state.CopyTimeResults(test_suite, test_name, results_set);
307     state.WriteTraceEvents();
308     return true;
309 }
310 
311 namespace internal {
312 
RunTests(const char * test_suite,TestList * test_list,uint32_t run_count,const char * regex_string,FILE * log_stream,ResultsSet * results_set)313 bool RunTests(const char* test_suite, TestList* test_list, uint32_t run_count,
314               const char* regex_string, FILE* log_stream,
315               ResultsSet* results_set) {
316     // Compile the regular expression.
317     regex_t regex;
318     int err = regcomp(&regex, regex_string, REG_EXTENDED);
319     if (err != 0) {
320         char msg[256];
321         msg[0] = '\0';
322         regerror(err, &regex, msg, sizeof(msg));
323         fprintf(log_stream,
324                 "Compiling the regular expression \"%s\" failed: %s\n",
325                 regex_string, msg);
326         return false;
327     }
328 
329     bool found_regex_match = false;
330     bool ok = true;
331     for (const internal::NamedTest& test_case : *test_list) {
332         const char* test_name = test_case.name.c_str();
333         bool matched_regex = regexec(&regex, test_name, 0, nullptr, 0) == 0;
334         if (!matched_regex) {
335             continue;
336         }
337         found_regex_match = true;
338 
339         // Log in a format similar to gtest's output, so that this will
340         // look familiar to readers and to allow parsing by tools that can
341         // parse gtest's output.
342         fprintf(log_stream, "[ RUN      ] %s\n", test_name);
343 
344         fbl::String error_string;
345         if (!RunTest(test_suite, test_name, test_case.test_func, run_count,
346                      results_set, &error_string)) {
347             fprintf(log_stream, "Error: %s\n", error_string.c_str());
348             fprintf(log_stream, "[  FAILED  ] %s\n", test_name);
349             ok = false;
350             continue;
351         }
352         fprintf(log_stream, "[       OK ] %s\n", test_name);
353     }
354 
355     regfree(&regex);
356 
357     if (!found_regex_match) {
358         // Report an error so that this doesn't fail silently if the regex
359         // is wrong.
360         fprintf(log_stream,
361                 "The regular expression \"%s\" did not match any tests\n",
362                 regex_string);
363         return false;
364     }
365     return ok;
366 }
367 
ParseCommandArgs(int argc,char ** argv,CommandArgs * dest)368 void ParseCommandArgs(int argc, char** argv, CommandArgs* dest) {
369     static const struct option opts[] = {
370         {"out", required_argument, nullptr, 'o'},
371         {"filter", required_argument, nullptr, 'f'},
372         {"runs", required_argument, nullptr, 'r'},
373         {"enable-tracing", no_argument, nullptr, 't'},
374         {"startup-delay", required_argument, nullptr, 'd'},
375     };
376     optind = 1;
377     for (;;) {
378         int opt = getopt_long(argc, argv, "", opts, nullptr);
379         if (opt < 0) {
380             break;
381         }
382         switch (opt) {
383         case 'o':
384             dest->output_filename = optarg;
385             break;
386         case 'f':
387             dest->filter_regex = optarg;
388             break;
389         case 'r': {
390             // Convert string to number (uint32_t).
391             char* end;
392             long val = strtol(optarg, &end, 0);
393             // Check that the string contains only a positive number and
394             // that the number doesn't overflow.
395             if (val != static_cast<uint32_t>(val) || *end != '\0' ||
396                 *optarg == '\0' || val == 0) {
397                 fprintf(stderr, "Invalid argument for --runs: \"%s\"\n",
398                         optarg);
399                 exit(1);
400             }
401             dest->run_count = static_cast<uint32_t>(val);
402             break;
403         }
404         case 't':
405             dest->enable_tracing = true;
406             break;
407         case 'd': {
408             // Convert string to number (double type).
409             char* end;
410             double val = strtod(optarg, &end);
411             if (*end != '\0' || *optarg == '\0') {
412                 fprintf(stderr,
413                         "Invalid argument for --startup-delay: \"%s\"\n",
414                         optarg);
415                 exit(1);
416             }
417             dest->startup_delay_seconds = val;
418             break;
419         }
420         default:
421             // getopt_long() will have printed an error already.
422             exit(1);
423         }
424     }
425     if (optind < argc) {
426         fprintf(stderr, "Unrecognized argument: \"%s\"\n", argv[optind]);
427         exit(1);
428     }
429 }
430 
431 } // namespace internal
432 
TraceProviderThread(void * thread_arg)433 static void* TraceProviderThread(void* thread_arg) {
434     async::Loop loop(&kAsyncLoopConfigNoAttachToThread);
435     trace::TraceProvider provider(loop.dispatcher());
436     loop.Run();
437     return nullptr;
438 }
439 
StartTraceProvider()440 static void StartTraceProvider() {
441     pthread_t tid;
442     int err = pthread_create(&tid, nullptr, TraceProviderThread, nullptr);
443     ZX_ASSERT(err == 0);
444     err = pthread_detach(tid);
445     ZX_ASSERT(err == 0);
446 }
447 
PerfTestMode(const char * test_suite,int argc,char ** argv)448 static bool PerfTestMode(const char* test_suite, int argc, char** argv) {
449     internal::CommandArgs args;
450     internal::ParseCommandArgs(argc, argv, &args);
451 
452     if (args.enable_tracing) {
453         StartTraceProvider();
454     }
455     zx_duration_t duration =
456         static_cast<zx_duration_t>(ZX_SEC(1) * args.startup_delay_seconds);
457     zx_nanosleep(zx_deadline_after(duration));
458 
459     ResultsSet results;
460     bool success = RunTests(test_suite, g_tests, args.run_count,
461                             args.filter_regex, stdout, &results);
462 
463     printf("\n");
464     results.PrintSummaryStatistics(stdout);
465     printf("\n");
466 
467     if (args.output_filename) {
468         if (!results.WriteJSONFile(args.output_filename)) {
469             exit(1);
470         }
471     }
472 
473     return success;
474 }
475 
PerfTestMain(int argc,char ** argv,const char * test_suite)476 int PerfTestMain(int argc, char** argv, const char* test_suite) {
477     if (argc == 2 && (strcmp(argv[1], "-h") == 0 ||
478                       strcmp(argv[1], "--help") == 0)) {
479         printf("Usage:\n"
480                "  %s -p [options]  # run in \"perf test mode\"\n"
481                "  %s               # run in \"unit test mode\"\n"
482                "\n"
483                "\"Unit test mode\" runs perf tests as unit tests.  "
484                "This means it only checks that the perf tests pass.  "
485                "It only does a small number of runs of each test, and it "
486                "does not report their performance.  Additionally, it runs "
487                "all of the unit tests in the executable (i.e. those that "
488                "use the unittest library).\n"
489                "\n"
490                "\"Perf test mode\" runs many iterations of each perf test, "
491                "and reports the performance results.  It does not run any "
492                "unittest test cases.\n"
493                "\n"
494                "Options:\n"
495                "  --out FILENAME\n"
496                "      Filename to write JSON results data to.  If this is "
497                "omitted, no JSON output is produced. JSON output will conform to this schema: "
498                "//zircon/system/ulib/perftest/performance-results-schema.json\n"
499                "  --filter REGEX\n"
500                "      Regular expression that specifies a subset of tests "
501                "to run.  By default, all the tests are run.\n"
502                "  --runs NUMBER\n"
503                "      Number of times to run each test.\n"
504                "  --enable-tracing\n"
505                "      Enable use of Fuchsia tracing: Enable registering as a "
506                "TraceProvider.  This is off by default because the "
507                "TraceProvider gets registered asynchronously on a background "
508                "thread (see TO-650), and that activity could introduce noise "
509                "to the tests.\n"
510                "  --startup-delay SECONDS\n"
511                "      Delay in seconds to wait on startup, after registering "
512                "a TraceProvider.  This allows working around a race condition "
513                "where tracing misses initial events from newly-registered "
514                "TraceProviders (see TO-650).\n",
515                argv[0], argv[0]);
516         return 1;
517     }
518 
519     bool success = true;
520 
521     // Check whether to run in perf test mode.
522     if (argc >= 2 && strcmp(argv[1], "-p") == 0) {
523         // Drop the "-p" argument.  Keep argv[0] because getopt_long()
524         // prints it in error messages.
525         argv[1] = argv[0];
526         argc--;
527         argv++;
528         if (!PerfTestMode(test_suite, argc, argv)) {
529             success = false;
530         }
531     } else {
532         printf("Running perf tests in unit test mode...\n");
533         {
534             // Run each test a small number of times to ensure that doing
535             // multiple runs works OK.
536             const int kRunCount = 3;
537             ResultsSet unused_results;
538             if (!RunTests(test_suite, g_tests, kRunCount, "", stdout, &unused_results)) {
539                 success = false;
540             }
541         }
542 
543         // In unit test mode, we pass all command line arguments on to the
544         // unittest library.
545         printf("Running unit tests...\n");
546         if (!unittest_run_all_tests(argc, argv)) {
547             success = false;
548         }
549     }
550 
551     return success ? 0 : 1;
552 }
553 
554 } // namespace perftest
555