File Coverage

blib/lib/Benchmark/Perl/Formance.pm
Criterion Covered Total %
statement 68 436 15.6
branch 0 120 0.0
condition 0 38 0.0
subroutine 23 60 38.3
pod 0 19 0.0
total 91 673 13.5


line stmt bran cond sub pod time code
1             package Benchmark::Perl::Formance;
2             # git description: v0.54-2-g52509be
3              
4             our $AUTHORITY = 'cpan:SCHWIGON';
5             # ABSTRACT: Perl 5 performance benchmarking framework
6             $Benchmark::Perl::Formance::VERSION = '0.55';
7 2     2   1715 use 5.008;
  2         10  
8              
9 2     2   11 use warnings;
  2         4  
  2         58  
10 2     2   10 use strict;
  2         5  
  2         38  
11              
12 2     2   10 use Config;
  2         4  
  2         72  
13 2     2   953 use Config::Perl::V;
  2         5281  
  2         118  
14 2     2   15 use Exporter;
  2         5  
  2         69  
15 2     2   1468 use Getopt::Long ":config", "no_ignore_case", "bundling";
  2         20822  
  2         9  
16 2     2   1433 use Data::Structure::Util "unbless";
  2         14162  
  2         154  
17 2     2   1031 use Time::HiRes qw(gettimeofday);
  2         2669  
  2         14  
18 2     2   1240 use Devel::Platform::Info;
  2         1067  
  2         69  
19 2     2   14 use List::Util "max";
  2         5  
  2         158  
20 2     2   1766 use Data::DPath 'dpath', 'dpathi';
  2         222737  
  2         24  
21 2     2   717 use File::Find;
  2         25  
  2         153  
22 2     2   19 use Storable "fd_retrieve", "store_fd";
  2         5  
  2         116  
23 2     2   1003 use Sys::Hostname;
  2         2221  
  2         114  
24 2     2   942 use Sys::Info;
  2         15439  
  2         9  
25 2     2   1070 use FindBin qw($Bin);
  2         2080  
  2         217  
26              
27 2     2   884 use Module::Pluggable;
  2         20544  
  2         14  
28 2     2   201 use Module::Runtime qw/ require_module /;
  2         5  
  2         12  
29              
30             # comma separated list of default plugins - basically the non-troublemakers
31             my $DEFAULT_PLUGINS = join ",", qw(DPath
32             Fib
33             FibOO
34             Mem
35             MatrixReal
36             Prime
37             Rx
38             RxMicro
39             Shootout::fasta
40             Shootout::regexdna
41             Shootout::binarytrees
42             Shootout::revcomp
43             Shootout::nbody
44             Shootout::spectralnorm
45             );
46              
47             # FibMXDeclare
48             my $ALL_PLUGINS = join ",", qw(DPath
49             Fib
50             FibMoose
51             FibMouse
52             FibOO
53             FibOOSig
54             MatrixReal
55             Mem
56             P6STD
57             PerlCritic
58             Prime
59             RegexpCommonTS
60             Rx
61             RxMicro
62             RxCmp
63             Shootout::binarytrees
64             Shootout::fannkuch
65             Shootout::fasta
66             Shootout::knucleotide
67             Shootout::mandelbrot
68             Shootout::nbody
69             Shootout::pidigits
70             Shootout::regexdna
71             Shootout::revcomp
72             Shootout::spectralnorm
73             SpamAssassin
74             Threads
75             ThreadsShared
76             );
77              
78             our $scaling_script = "$Bin/benchmark-perlformance-set-stable-system";
79             our $metric_prefix = "perlformance.perl5";
80              
81             our $DEFAULT_INDENT = 0;
82              
83             my @run_plugins;
84              
85             # incrementaly interesting Perl Config keys
86             my %CONFIG_KEYS = (
87             0 => [],
88             1 => [
89             qw(perlpath
90             version
91             archname
92             archname64
93             osvers
94             usethreads
95             useithreads
96             )],
97             2 => [
98             qw(gccversion
99             gnulibc_version
100             usemymalloc
101             config_args
102             optimize
103             )],
104             3 => [qw(ccflags
105             ccname
106             cccdlflags
107             ccdlflags
108             cppflags
109             nm_so_opt
110             )],
111             4 => [qw(PERL_REVISION
112             PERL_VERSION
113             PERL_SUBVERSION
114             PERL_PATCHLEVEL
115              
116             api_revision
117             api_version
118             api_subversion
119             api_versionstring
120              
121             git_branch
122             git_commit_id
123             git_describe
124             git_uncommitted_changes
125              
126             gnulibc_version
127             dtrace
128             doublesize
129             alignbytes
130             bin_ELF
131             git_commit_date
132             version_patchlevel_string
133             d_mymalloc
134              
135             i16size
136             i16type
137             i32size
138             i32type
139             i64size
140             i64type
141             i8size
142             i8type
143              
144             longdblsize
145             longlongsize
146             longsize
147              
148             perllibs
149             ptrsize
150             quadkind
151             quadtype
152             randbits
153             )],
154             5 => [
155             sort keys %Config
156             ],
157             );
158              
159             sub new {
160 0     0 0   my ($class, %args) = @_;
161 0           bless { %args }, $class;
162             }
163              
164             sub load_all_plugins
165             {
166             map {
167 0 0         my $version = $_->[1] ? $_->[0]->VERSION : '~';
168 0           (my $name = $_->[0]) =~ s/.*::Plugin:://;
169              
170 0           $name => $version;
171             }
172 0     0 0   map { [ $_ => eval { require_module($_) } ] }
  0            
  0            
173             __PACKAGE__->plugins;
174             }
175              
176             sub print_version
177             {
178 0     0 0   my ($self) = @_;
179              
180 0 0         if ($self->{options}{verbose})
181             {
182 0           print "Benchmark::Perl::Formance version $Benchmark::Perl::Formance::VERSION\n";
183 0           print "Plugins:\n";
184 0           my %plugins = load_all_plugins;
185 0           print " (v$plugins{$_}) $_\n" foreach sort keys %plugins;
186             }
187             else
188             {
189 0           print $Benchmark::Perl::Formance::VERSION, "\n";
190             }
191             }
192              
193             sub usage
194             {
195 0     0 0   print 'benchmark-perlformance - Frontend for Benchmark::Perl::Formance
196              
197             Usage:
198              
199             $ benchmark-perlformance
200             $ benchmark-perlformance --fastmode
201             $ benchmark-perlformance --useforks
202             $ benchmark-perlformance --plugins=SpamAssassin,RegexpCommonTS,RxCmp -v
203             $ benchmark-perlformance -ccccc --indent=2
204             $ benchmark-perlformance -q
205              
206             If run directly it uses the perl in your PATH:
207              
208             $ /path/to/benchmark-perlformance
209              
210             To use another perl start it via
211              
212             $ /other/path/to/bin/perl /path/to/benchmark-perlformance
213              
214             For more details see
215              
216             man benchmark-perlformance
217             perldoc Benchmark::Perl::Formance
218              
219             ';
220             }
221              
222             sub do_disk_sync {
223 0     0 0   system("sync ; sync");
224             }
225              
226             sub prepare_stable_system
227             {
228 0     0 0   my ($self) = @_;
229              
230 0           my $orig_values;
231 0 0 0       if ($self->{options}{stabilize_cpu} and $^O eq "linux") {
232 0           $self->{orig_system_values} = qx(sudo $scaling_script lo);
233 0           do_disk_sync();
234             }
235             }
236              
237             sub restore_stable_system
238             {
239 0     0 0   my ($self, $orig_values) = @_;
240 0 0 0       if ($self->{options}{stabilize_cpu} and $^O eq "linux") {
241 0 0         if (open my $RESTORE, "|-", "sudo $scaling_script restore") {
242 0           print $RESTORE $self->{orig_system_values};
243 0           close $RESTORE;
244             }
245             }
246             }
247              
248             sub prepare_fast_system
249             {
250 0     0 0   my ($self) = @_;
251              
252 0           my $orig_values;
253 0 0 0       if ($self->{options}{stabilize_cpu} and $^O eq "linux") {
254 0           $self->{orig_system_values} = qx(sudo $scaling_script hi);
255             }
256             }
257              
258             sub _error_printing
259             {
260 0     0     my ($self, $pluginname, $error) = @_;
261              
262 0           my @errors = split qr/\n/, $error;
263 0 0         my $maxerr = ($#errors < 10) ? $#errors : 10;
264 0 0         print STDERR "# Skip plugin '$pluginname'" if $self->{options}{verbose};
265 0 0         print STDERR ":".$errors[0] if $self->{options}{verbose} > 1;
266 0 0         print STDERR join("\n# ", "", @errors[1..$maxerr]) if $self->{options}{verbose} > 2;
267 0 0         print STDERR "\n" if $self->{options}{verbose};
268             }
269              
270             sub run_plugin
271             {
272 0     0 0   my ($self, $pluginname) = @_;
273              
274 0           $pluginname =~ s,\.,::,g;
275 2     2   2124 no strict 'refs'; ## no critic
  2         5  
  2         126  
276 0 0         print STDERR "# Run $pluginname...\n" if $self->{options}{verbose} >= 2;
277 0           my $res;
278 0           eval {
279 2     2   13 use IO::Handle;
  2         9  
  2         4183  
280 0           pipe(PARENT_RDR, CHILD_WTR);
281 0           CHILD_WTR->autoflush(1);
282 0           my $pid = open(my $PLUGIN, "-|"); # implicit fork
283 0 0         if ($pid == 0) {
284             # run in child process
285 0           close PARENT_RDR;
286 0           eval "use Benchmark::Perl::Formance::Plugin::$pluginname"; ## no critic
287 0 0         if ($@) {
288 0           $self->_error_printing($pluginname, $@);
289 0           exit 0;
290             }
291 0           $0 = "benchmark-perl-formance-$pluginname";
292 0           eval {
293 0           $res = &{"Benchmark::Perl::Formance::Plugin::${pluginname}::main"}($self->{options});
  0            
294             };
295 0 0         if ($@) {
296 0           $self->_error_printing($pluginname, $@);
297 0           $res = { failed => $@ };
298             }
299 0           $res->{PLUGIN_VERSION} = ${"Benchmark::Perl::Formance::Plugin::${pluginname}::VERSION"};
  0            
300 0           store_fd($res, \*CHILD_WTR);
301 0           close CHILD_WTR;
302 0           exit 0;
303             }
304 0           close CHILD_WTR;
305 0           $res = fd_retrieve(\*PARENT_RDR);
306 0           close PARENT_RDR;
307             };
308 0 0         if ($@) {
309             $res = {
310             failed => "Plugin $pluginname failed",
311 0 0         ($self->{options}{verbose} > 3 ? ( error => $@ ) : ()),
312             }
313             }
314 0           return $res;
315             }
316              
317             # ,-----------------------------------------------------------
318             # |
319             # | That's specific to the Tapper wrapper around
320             # | Benchmark::Perl::Formance and should be replaced
321             # | with something generic
322             sub _perl_gitversion {
323 0     0     my $perlpath = "$^X";
324 0           $perlpath =~ s,/[^/]*$,,;
325 0           my $perl_gitversion = "$perlpath/perl -MConfig -e 'print \$Config{bootstrap_perl_git_changeset}";
326              
327 0 0         if (-x $perl_gitversion) {
328 0           my $gitversion = qx!$perl_gitversion! ;
329 0           chomp $gitversion;
330 0           return $gitversion;
331             }
332             }
333              
334             sub _perl_gitdescribe {
335 0     0     my $perlpath = "$^X";
336 0           $perlpath =~ s,/[^/]*$,,;
337 0           my $perl_gitdescribe = "$perlpath/perl -MConfig -e 'print \$Config{bootstrap_perl_git_describe}";
338              
339 0 0         if (-x $perl_gitdescribe) {
340 0           my $gitdescribe = qx!$perl_gitdescribe! ;
341 0           chomp $gitdescribe;
342 0           return $gitdescribe;
343             }
344             }
345              
346             sub _perl_symbolic_name {
347 0     0     my $perlpath = "$^X";
348 0           $perlpath =~ s,/[^/]*$,,;
349 0           my $perl_symbolic_name = "$perlpath/perl -MConfig -e 'print \$Config{bootstrap_perl_symbolic_name}";
350              
351 0 0         if (-x $perl_symbolic_name) {
352 0           my $executable = qx!$perl_symbolic_name! ;
353 0           chomp $executable;
354 0           return $executable;
355             }
356             }
357             # |
358             # '-----------------------------------------------------------
359              
360             sub _get_hostname {
361 0     0     my $host = "unknown-hostname";
362 0           eval { $host = hostname };
  0            
363 0 0         $host = "perl64.org" if $host eq "h1891504"; # special case for PerlFormance.Net Æsthetics
364 0           return $host;
365             }
366              
367             sub _plugin_results {
368 0     0     my ($self, $plugin, $RESULTS) = @_;
369              
370 0           my @resultkeys = split(/\./, $plugin);
371 0           my ($res) = dpath("/results/".join("/", map { qq("$_") } @resultkeys)."/Benchmark/*[0]")->match($RESULTS);
  0            
372              
373 0           return $res;
374             }
375              
376             sub _codespeed_meta {
377 0     0     my ($self, $RESULTS) = @_;
378              
379 0   0       my $codespeed_exe_suffix = $self->{options}{cs_executable_suffix} || $ENV{CODESPEED_EXE_SUFFIX} || "";
380             my $codespeed_exe = $self->{options}{cs_executable} || _perl_symbolic_name || sprintf("perl-%s.%s%s",
381             $Config{PERL_REVISION},
382             $Config{PERL_VERSION},
383 0   0       $codespeed_exe_suffix,
384             );
385 0   0       my $codespeed_project = $self->{options}{cs_project} || $ENV{CODESPEED_PROJECT} || "perl5";
386 0   0       my $codespeed_branch = $self->{options}{cs_branch} || $ENV{CODESPEED_BRANCH} || "default";
387 0   0       my $codespeed_commitid = $self->{options}{cs_commitid} || $ENV{CODESPEED_COMMITID} || $Config{git_commit_id} || _perl_gitversion || "no-commit";
388 0   0       my $codespeed_environment = $self->{options}{cs_environment} || $ENV{CODESPEED_ENVIRONMENT} || _get_hostname || "no-env";
389 0           my %codespeed_meta = (
390             executable => $codespeed_exe,
391             project => $codespeed_project,
392             branch => $codespeed_branch,
393             commitid => $codespeed_commitid,
394             environment => $codespeed_environment,
395             );
396              
397 0           return %codespeed_meta;
398             }
399              
400             sub _get_bootstrap_perl_meta {
401 0     0     my ($self) = @_;
402              
403 0           return map { ("$_" => $Config{$_}) } grep { /^bootstrap_perl/ } keys %Config;
  0            
  0            
404             }
405              
406             # Convert value:
407             # - Perlish undef --> 0
408             # - String "define" --> 1
409             # - everything else keep the same
410             sub _booleanize_define {
411 0     0     my ($value) = @_;
412              
413 0 0         if (not defined $value) {
    0          
414 0           return 0;
415             } elsif ($value eq "define") {
416 0           return 1;
417             } else {
418 0           return $value;
419             }
420             }
421              
422             sub _taint_available {
423 0     0     require Scalar::Util;
424 0           require Cwd;
425 0           Scalar::Util::tainted(Cwd::getcwd());
426             }
427              
428             sub _get_perl_config_notaintsupport {
429 0     0     my ($self) = @_;
430              
431 0           my $config_args = $Config{config_args};
432 0           my $notaintsupport = 0; # standard
433 0 0         if ($config_args =~ /(SILENT_)?NO_TAINT_SUPPORT\b/) {
434 0 0         if ($config_args =~ /SILENT_NO_TAINT_SUPPORT\b/) {
435 0           $notaintsupport = 1; # no further check possible
436             } else {
437 0 0         $notaintsupport = 1 if not _taint_available();
438             }
439             }
440 0           return $notaintsupport;
441             }
442              
443             sub _get_perl_config {
444 0     0     my ($self) = @_;
445              
446 0           my @cfgkeys;
447 0           my $showconfig = 4;
448 0           push @cfgkeys, @{$CONFIG_KEYS{$_}} foreach 1..$showconfig;
  0            
449 0           my %perlconfig = map { ("perlconfig_$_" => $Config{$_}) } @cfgkeys;
  0            
450 0           $perlconfig{perlconfig_derived_notaintsupport} = $self->_get_perl_config_notaintsupport();
451 0           return %perlconfig;
452             }
453              
454             sub _get_perl_config_v {
455 0     0     my ($self) = @_;
456              
457             # only when ultimate verbose config requested
458 0 0         return unless $self->{options}{showconfig} >= 5;
459              
460 0           my $config_v_myconfig = Config::Perl::V::myconfig ();
461 0           my @config_v_keys = sort keys %$config_v_myconfig;
462              
463             # --- flat configs ---
464 0           my $prefix = "perlconfigv";
465 0           my %perlconfigv = ();
466             my %focus = (
467             derived => [ qw( Off_t uname) ],
468             build => [ qw( osname stamp ) ],
469 0           environment => [ keys %{$config_v_myconfig->{environment}} ], # all
  0            
470             );
471 0           foreach my $subcfg (keys %focus) {
472 0           foreach my $k (@{$focus{$subcfg}}) {
  0            
473 0           $perlconfigv{join("_", $prefix, $subcfg, $k)} = $config_v_myconfig->{$subcfg}{$k};
474             }
475             }
476              
477             # --- nested configs ---
478              
479             # build options
480 0           my @buildoptionkeys = keys %{$config_v_myconfig->{build}{options}};
  0            
481 0           foreach my $k (keys %focus) {
482 0           $perlconfigv{join("_", $prefix, "build", "options", $k)} = $config_v_myconfig->{build}{options}{$k};
483             }
484              
485 0           return %perlconfigv;
486             }
487              
488             sub _get_perlformance_config {
489 0     0     my ($self) = @_;
490              
491             # only easy printable data (i.e., no "D" hash)
492 0           my @config_keys = (qw(stabilize_cpu
493             fastmode
494             useforks
495             plugins
496             ));
497              
498 0 0         return map { $self->{options}{$_} ? ("perlformance_$_" => $self->{options}{$_}) : () } @config_keys;
  0            
499             }
500              
501             sub _get_perlformance_env
502             {
503 0     0     my ($self) = @_;
504              
505             # environment variables matching /^PERLFORMANCE_/
506 0           my @config_keys = grep { $ENV{$_} ne '' } grep /^PERLFORMANCE_/, keys %ENV;
  0            
507              
508 0           return map { lc("env_$_") => $ENV{$_} } @config_keys;
  0            
509             }
510              
511             sub _get_platforminfo {
512 0     0     my ($self) = @_;
513              
514 0           my $get_info = Devel::Platform::Info->new->get_info;
515 0           delete $get_info->{source}; # this currently breaks the simplified YAMLish
516 0           return %$get_info;
517             }
518              
519             sub _get_sysinfo {
520 0     0     my ($self) = @_;
521              
522 0           my %sysinfo = ();
523 0           my $prefix = "sysinfo";
524 0           my $cpu = (Sys::Info->new->device("CPU")->identify)[0];
525 0           $sysinfo{"${prefix}_hostname"} = _get_hostname;
526 0           $sysinfo{join("_", $prefix, "cpu", $_)} = $cpu->{$_} foreach qw(name
527             family
528             model
529             stepping
530             architecture
531             number_of_cores
532             number_of_logical_processors
533             architecture
534             manufacturer
535             );
536 0           $sysinfo{join("_", $prefix, "cpu", "l2_cache", "max_cache_size")} = $cpu->{L2_cache}{max_cache_size};
537 0           return %sysinfo;
538             }
539              
540             sub augment_results_with_meta {
541 0     0 0   my ($self, $NAME_KEY, $VALUE_KEY, $META, $RESULTS) = @_;
542              
543 0           my @run_plugins = $self->find_interesting_result_paths($RESULTS);
544 0           my @new_entries = ();
545 0           foreach my $plugin (sort @run_plugins) {
546 2     2   22 no strict 'refs'; ## no critic
  2         5  
  2         3482  
547 0           my $res = $self->_plugin_results($plugin, $RESULTS);
548 0 0         my $benchmark = join ".", $metric_prefix, ($self->{options}{fastmode} ? "$plugin(F)" : $plugin);
549 0   0       push @new_entries, {
550             %$META,
551             # metric name and value at last position to override
552             $NAME_KEY => $benchmark,
553             $VALUE_KEY => ($res || 0),
554             };
555             }
556 0           return \@new_entries;
557             }
558              
559             sub generate_codespeed_data
560             {
561 0     0 0   my ($self, $RESULTS) = @_;
562              
563 0           my %META = _codespeed_meta();
564 0           return $self->augment_results_with_meta("benchmark", "result_value", \%META, $RESULTS);
565             }
566              
567             sub generate_BenchmarkAnythingData_data
568             {
569 0     0 0   my ($self, $RESULTS, $codespeed) = @_;
570              
571             # share a common dataset with Codespeed, yet prefix it
572 0 0         my %codespeed_meta = $codespeed ? _codespeed_meta : ();
573 0           my %prefixed_codespeed_meta = map { ("codespeed_$_" => $codespeed_meta{$_}) } keys %codespeed_meta;
  0            
574              
575 0           my %platforminfo = $self->_get_platforminfo;
576 0           my %prefixed_platforminfo = map { ("platforminfo_$_" => $platforminfo{$_}) } keys %platforminfo;
  0            
577              
578 0           my %META = (
579             %prefixed_platforminfo,
580             %prefixed_codespeed_meta,
581             $self->_get_bootstrap_perl_meta,
582             $self->_get_perl_config,
583             $self->_get_perl_config_v,
584             $self->_get_sysinfo,
585             $self->_get_perlformance_config,
586             $self->_get_perlformance_env,
587             );
588 0           return $self->augment_results_with_meta("NAME", "VALUE", \%META, $RESULTS);
589             }
590              
591             sub run {
592 0     0 0   my ($self) = @_;
593              
594 0           my $help = 0;
595 0           my $showconfig = 0;
596 0           my $outstyle = "summary";
597 0           my $outfile = "";
598 0           my $platforminfo = 0;
599 0           my $codespeed = 0;
600 0           my $tap = 0;
601 0           my $tap_plan = 0;
602 0           my $tap_headers = 0;
603 0           my $benchmarkanything = 0;
604 0           my $benchmarkanything_report = 0;
605 0           my $cs_executable_suffix = "";
606 0           my $cs_executable = "";
607 0           my $cs_project = "";
608 0           my $cs_branch = "";
609 0           my $cs_commitid = "";
610 0           my $cs_environment = "";
611 0           my $verbose = 0;
612 0           my $version = 0;
613 0           my $fastmode = 0;
614 0           my $useforks = 0;
615 0           my $quiet = 0;
616 0           my $stabilize_cpu = 0;
617 0           my $plugins = $DEFAULT_PLUGINS;
618 0           my $indent = $DEFAULT_INDENT;
619 0           my $tapdescription = "";
620 0           my $D = {};
621              
622             # get options
623 0           my $ok = GetOptions (
624             "help|h" => \$help,
625             "quiet|q" => \$quiet,
626             "indent=i" => \$indent,
627             "plugins=s" => \$plugins,
628             "verbose|v+" => \$verbose,
629             "outstyle=s" => \$outstyle,
630             "outfile=s" => \$outfile,
631             "fastmode" => \$fastmode,
632             "version" => \$version,
633             "useforks" => \$useforks,
634             "stabilize-cpu" => \$stabilize_cpu,
635             "showconfig|c+" => \$showconfig,
636             "platforminfo|p" => \$platforminfo,
637             "codespeed" => \$codespeed,
638             "tap" => \$tap,
639             "tap-plan" => \$tap_plan,
640             "tap-headers" => \$tap_headers,
641             "benchmarkanything" => \$benchmarkanything,
642             "benchmarkanything-report" => \$benchmarkanything_report,
643             "cs-executable-suffix=s" => \$cs_executable_suffix,
644             "cs-executable=s" => \$cs_executable,
645             "cs-project=s" => \$cs_project,
646             "cs-branch=s" => \$cs_branch,
647             "cs-commitid=s" => \$cs_commitid,
648             "cs-environment=s" => \$cs_environment,
649             "tapdescription=s" => \$tapdescription,
650             "D=s%" => \$D,
651             );
652              
653             # special meta options - order matters!
654 0 0 0       if ($tap or $tap_plan) {
655 0           $tapdescription = 'perlformance results';
656 0           $outstyle = 'yamlish';
657 0           $indent = 2;
658 0           $platforminfo = 1;
659 0           $showconfig = 4;
660             }
661 0 0         $benchmarkanything = 1 if $benchmarkanything_report;
662 0 0         $platforminfo = 1 if $benchmarkanything; # -p
663 0 0         $showconfig = 4 if $benchmarkanything; # -cccc
664 0 0 0       $outstyle = 'json' if $benchmarkanything and $outstyle !~ /^(json|yaml|yamlish)$/;
665 0 0         $outstyle = 'json' if $benchmarkanything_report;
666              
667             # fill options
668             $self->{options} = {
669 0           help => $help,
670             quiet => $quiet,
671             verbose => $verbose,
672             outfile => $outfile,
673             outstyle => $outstyle,
674             fastmode => $fastmode,
675             useforks => $useforks,
676             stabilize_cpu => $stabilize_cpu,
677             showconfig => $showconfig,
678             platforminfo => $platforminfo,
679             codespeed => $codespeed,
680             tap => $tap,
681             tap_plan => $tap_plan,
682             tap_headers => $tap_headers,
683             benchmarkanything => $benchmarkanything,
684             benchmarkanything_report => $benchmarkanything_report,
685             cs_executable_suffix => $cs_executable_suffix,
686             cs_executable => $cs_executable,
687             cs_project => $cs_project,
688             cs_branch => $cs_branch,
689             cs_commitid => $cs_commitid,
690             cs_environment => $cs_environment,
691             plugins => $plugins,
692             tapdescription => $tapdescription,
693             indent => $indent,
694             D => $D,
695             };
696              
697 0 0         do { $self->print_version; exit 0 } if $version;
  0            
  0            
698 0 0         do { usage; exit 0 } if $help;
  0            
  0            
699 0 0         do { usage; exit -1 } if not $ok;
  0            
  0            
700              
701             # use forks if requested
702 0 0         if ($useforks) {
703 0           eval "use forks"; ## no critic
704 0 0         $useforks = 0 if $@;
705 0 0         print STDERR "# use forks " . ($@ ? "failed" : "") . "\n" if $verbose;
    0          
706             }
707              
708             # static list because dynamic require influences runtimes
709 0 0         $plugins = $ALL_PLUGINS if $plugins eq "ALL";
710              
711             # run plugins
712 0           my $before = gettimeofday();
713 0           my %RESULTS;
714 0           my @plugins = grep /\w/, split '\s*,\s*', $plugins;
715              
716 0           $self->prepare_stable_system;
717 0           foreach (@plugins)
718             {
719 0           my @resultkeys = split(qr/::|\./, $_);
720 0           my $res = $self->run_plugin($_);
721 0           eval "\$RESULTS{results}{".join("}{", @resultkeys)."} = \$res"; ## no critic
722             }
723 0           $self->prepare_fast_system; # simply set to max, as restore_stable_system() is no reliable approach anyway
724              
725 0           my $after = gettimeofday();
726 0           $RESULTS{perlformance}{overall_runtime} = $after - $before;
727 0           $RESULTS{perlformance}{config}{fastmode} = $fastmode;
728 0           $RESULTS{perlformance}{config}{use_forks} = $useforks;
729              
730             # Perl Config
731 0 0         if ($showconfig)
732             {
733             # Config
734 0           my @cfgkeys;
735 0           push @cfgkeys, @{$CONFIG_KEYS{$_}} foreach 1..$showconfig;
  0            
736             $RESULTS{perl_config} =
737             {
738 0           map { $_ => $Config{$_} } sort @cfgkeys
  0            
739             };
740              
741             # Config::Perl::V
742 0           $RESULTS{perl_config_v} = Config::Perl::V::myconfig;
743             }
744              
745             # Perl Config
746 0 0         if ($platforminfo)
747             {
748 0           $RESULTS{platform_info} = { $self->_get_platforminfo };
749             }
750              
751             # Codespeed data blocks
752 0 0         if ($codespeed)
753             {
754 0           $RESULTS{codespeed} = $self->generate_codespeed_data(\%RESULTS);
755             }
756              
757             # TAP or BenchmarkAnythingData blocks
758 0 0 0       if ($tap or $tap_plan or $benchmarkanything)
      0        
759             {
760 0           $RESULTS{BenchmarkAnythingData} = $self->generate_BenchmarkAnythingData_data(\%RESULTS, $codespeed);
761             }
762              
763 0           unbless (\%RESULTS);
764 0           return \%RESULTS;
765             }
766              
767             sub print_outstyle_yaml
768             {
769 0     0 0   my ($self, $RESULTS) = @_;
770              
771 0           require YAML;
772 0           return YAML::Dump($RESULTS);
773             }
774              
775             sub print_outstyle_json
776             {
777 0     0 0   my ($self, $RESULTS) = @_;
778              
779 0           require JSON;
780 0           return JSON->new->allow_nonref->pretty->encode( $RESULTS );
781             }
782              
783             sub print_outstyle_yamlish
784             {
785 0     0 0   my ($self, $RESULTS) = @_;
786              
787 0           require Data::YAML::Writer;
788              
789 0           my $output = '';
790 0           my $indent = $self->{options}{indent};
791 0           my $yw = Data::YAML::Writer->new;
792 0     0     $yw->write($RESULTS, sub { $output .= shift()."\n" });
  0            
793 0           $output =~ s/^/" "x$indent/emsg; # indent
  0            
794              
795 0           my $tapdescription = $self->{options}{tapdescription};
796 0 0         $output = "ok $tapdescription\n".$output if $tapdescription;
797 0           return $output;
798             }
799              
800             sub find_interesting_result_paths
801             {
802 0     0 0   my ($self, $RESULTS) = @_;
803              
804 0           my @all_keys = ();
805              
806 0           my $benchmarks = dpathi($RESULTS)->isearch("//Benchmark");
807              
808 0           while ($benchmarks->isnt_exhausted) {
809 0           my @keys;
810 0           my $benchmark = $benchmarks->value;
811 0           my $ancestors = $benchmark->isearch ("/::ancestor");
812              
813 0           while ($ancestors->isnt_exhausted) {
814 0           my $key = $ancestors->value->first_point->{attrs}{key};
815 0 0         push @keys, $key if defined $key;
816             }
817 0           pop @keys;
818 0           push @all_keys, join(".", reverse @keys);
819             }
820 0           return @all_keys;
821             }
822              
823             sub print_outstyle_summary
824             {
825 0     0 0   my ($self, $RESULTS) = @_;
826              
827 0           my $output = '';
828              
829 0           my @run_plugins = $self->find_interesting_result_paths($RESULTS);
830 0           my $len = max map { length } @run_plugins;
  0            
831 0           $len += 1+length($metric_prefix);
832              
833 0           foreach (sort @run_plugins) {
834 2     2   19 no strict 'refs'; ## no critic
  2         6  
  2         1437  
835 0           my $res = $self->_plugin_results($_, $RESULTS);
836 0   0       $output .= sprintf("%-${len}s : %f\n", join(".", $metric_prefix, $_), ($res || 0));
837             }
838 0           return $output;
839             }
840              
841             sub print_results
842             {
843 0     0 0   my ($self, $RESULTS) = @_;
844 0 0         return if $self->{options}{quiet};
845              
846 0           my $outstyle = lc $self->{options}{outstyle};
847 0 0         $outstyle = "summary" unless $outstyle =~ qr/^(summary|yaml|yamlish|json)$/;
848 0           my $sub = "print_outstyle_$outstyle";
849              
850 0           my $output = $self->$sub($RESULTS);
851              
852             # tap
853 0           my $tap_plan = lc $self->{options}{tap_plan};
854 0           my $tap_headers = lc $self->{options}{tap_headers};
855 0           my $lead_tap = '';
856 0 0         $lead_tap .= "1..$tap_plan\n" if $tap_plan;
857 0 0         if ($tap_headers) {
858 0           $lead_tap .= "# Test-suite-name: benchmark-perlformance\n";
859 0           $lead_tap .= "# Test-machine-name: "._get_hostname."\n";
860             }
861              
862 0           $output = $lead_tap.$output;
863              
864 0 0         if (my $outfile = $self->{options}{outfile})
    0          
865             {
866 0 0         open my $OUTFILE, ">", $outfile or do {
867 0           warn "Can not open $outfile. Printing to STDOUT.\n";
868 0           print $output;
869             };
870 0           print $OUTFILE $output;
871 0           close $OUTFILE;
872             }
873             elsif ($self->{options}{benchmarkanything_report})
874             {
875 0           my $ba_reporter;
876              
877 0           eval {
878 0           require BenchmarkAnything::Reporter;
879 0           $ba_reporter = BenchmarkAnything::Reporter->new(verbose => $self->{options}{verbose});
880 0           $ba_reporter->report({BenchmarkAnythingData => $RESULTS->{BenchmarkAnythingData}});
881             };
882 0 0         if ($@)
883             {
884 0           print STDERR "# Could not add results to storage: $@\n";
885              
886 0           require JSON;
887 0           require File::Path;
888 0           require File::Temp;
889 0           require File::Basename;
890              
891 0           my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
892              
893 0           my $result_dir = File::Basename::dirname($ba_reporter->{config}{cfgfile});
894 0 0         if (! -w $result_dir) {
895 0           require File::HomeDir;
896 0           $result_dir = File::HomeDir->my_home;
897             }
898 0 0         if (! -w $result_dir) {
899 0           require File::Temp;
900 0           $result_dir = tempdir(CLEANUP => 0);
901             }
902              
903 0           my $timestamp1 = sprintf("%04d-%02d-%02d", 1900+$year, $mon, $mday);
904 0           my $timestamp2 = sprintf("%02d-%02d-%02d", $hour, $min, $sec);
905 0           my $result_path = "$result_dir/unreported_results/$timestamp1";
906              
907 0           File::Path::make_path($result_path);
908              
909 0           my ($FH, $result_file) = File::Temp::tempfile ("$timestamp2-XXXX", DIR => $result_path, SUFFIX => ".json");
910 0           print STDERR "# Writing them to file: $result_file\n";
911 0           print $FH JSON->new->allow_nonref->pretty->encode({BenchmarkAnythingData => $RESULTS->{BenchmarkAnythingData}});
912             }
913             }
914             else
915             {
916 0           print $output;
917             }
918             }
919              
920             1;
921              
922             __END__
923              
924             =pod
925              
926             =encoding UTF-8
927              
928             =head1 NAME
929              
930             Benchmark::Perl::Formance - Perl 5 performance benchmarking framework
931              
932             =head1 ABOUT
933              
934             This benchmark suite tries to run some stressful programs and outputs
935             values that you can compare against other runs of this suite,
936             e.g. with other versions of Perl, modified compile parameter, or
937             another set of dependent libraries.
938              
939             =head1 BUGS
940              
941             =head2 No invariant dependencies
942              
943             This distribution only contains the programs to run the tests and
944             according data. It uses a lot of libs from CPAN with all their
945             dependencies but it does not contain invariant versions of those used
946             dependency libs.
947              
948             If total invariance is important to you, you are responsible to
949             provide that invariant environment by yourself. You could, for
950             instance, create a local CPAN mirror with CPAN::Mini and never upgrade
951             it. Then use that mirror for all your installations of Benchmark::Perl::Formance.
952              
953             On the other side this could be used to track the performance of your
954             installation over time by continuously upgrading from CPAN.
955              
956             =head2 It is not scientific
957              
958             The benchmarks are basically just a collection of already existing
959             interesting things like large test suites found on CPAN or just
960             starting long running tasks that seem to stress perl features. It does
961             not really guarantee accuracy of only raw Perl features, i.e., it does
962             not care for underlying I/O speed and does not preallocate ressources
963             from the OS before using them, etc.
964              
965             This is basically because I just wanted to start, even without
966             knowledge about "real" benchmark science.
967              
968             Anyway, feel free to implement "real" benchmark ideas and send me
969             patches.
970              
971             =head1 AUTHOR
972              
973             Steffen Schwigon <ss5@renormalist.net>
974              
975             =head1 COPYRIGHT AND LICENSE
976              
977             This software is copyright (c) 2021 by Steffen Schwigon.
978              
979             This is free software; you can redistribute it and/or modify it under
980             the same terms as the Perl 5 programming language system itself.
981              
982             =cut