File Coverage

blib/lib/Devel/NYTProf/Util.pm
Criterion Covered Total %
statement 112 115 97.3
branch 55 68 80.8
condition 8 11 72.7
subroutine 18 18 100.0
pod 0 9 0.0
total 193 221 87.3


line stmt bran cond sub pod time code
1             # vim: ts=8 sw=4 expandtab:
2             ##########################################################
3             # This script is part of the Devel::NYTProf distribution
4             #
5             # Copyright, contact and other information can be found
6             # at the bottom of this file, or by going to:
7             # http://metacpan.org/release/Devel-NYTProf/
8             #
9             ###########################################################
10             package Devel::NYTProf::Util;
11              
12             =head1 NAME
13              
14             Devel::NYTProf::Util - general utility functions for L
15              
16             =head1 SYNOPSIS
17              
18             use Devel::NYTProf::Util qw(strip_prefix_from_paths);
19              
20             =head1 DESCRIPTION
21              
22             Contains general utility functions for L
23              
24             B The documentation for this module is currently incomplete and out of date.
25              
26             =head1 FUNCTIONS
27              
28             =encoding ISO8859-1
29              
30             =cut
31              
32              
33 49     49   65759 use warnings;
  49         115  
  49         1665  
34 49     49   281 use strict;
  49         94  
  49         1078  
35              
36 49     49   243 use base qw'Exporter';
  49         106  
  49         4558  
37              
38 49     49   313 use Carp;
  49         111  
  49         2754  
39 49     49   289 use Cwd qw(getcwd);
  49         123  
  49         2378  
40 49     49   288 use List::Util qw(sum);
  49         170  
  49         2481  
41 49     49   732 use Devel::NYTProf::Core;
  49         139  
  49         78848  
42              
43             our $VERSION = '6.13';
44              
45             our @EXPORT_OK = qw(
46             fmt_float
47             fmt_time
48             fmt_incl_excl_time
49             make_path_strip_editor
50             strip_prefix_from_paths
51             calculate_median_absolute_deviation
52             get_alternation_regex
53             get_abs_paths_alternation_regex
54             html_safe_filename
55             trace_level
56             _dumper
57             );
58              
59              
60             sub get_alternation_regex {
61 36     36 0 2141 my ($strings, $suffix_regex) = @_;
62 36 100       144 $suffix_regex = '' unless defined $suffix_regex;
63              
64             # sort longest string first
65 36         593 my @strings = sort { length $b <=> length $a } @$strings;
  1181         2146  
66              
67             # build string regex for each string
68 36         207 my $regex = join "|", map { quotemeta($_) . $suffix_regex } @strings;
  466         1400  
69              
70 36         10277 return qr/(?:$regex)/;
71             }
72              
73              
74             sub get_abs_paths_alternation_regex {
75 36     36 0 1966 my ($inc, $cwd) = @_;
76 36 100       536 my @inc = @$inc or croak "No paths";
77              
78             # rewrite relative directories to be absolute
79             # the logic here should match that in get_file_id()
80 35 50       388 my $abs_path_regex = ($^O eq "MSWin32") ? qr,^\w:/, : qr,^/,;
81 35         139 for (@inc) {
82 452 100       2092 next if $_ =~ $abs_path_regex; # already absolute
83 128         1325 $_ =~ s/^\.\///; # remove a leading './'
84 128   66     1529 $cwd ||= getcwd();
85 128 100       641 $_ = ($_ eq '.') ? $cwd : "$cwd/$_";
86             }
87              
88 35         368 return get_alternation_regex(\@inc, '/?');
89             }
90              
91              
92             sub make_path_strip_editor {
93 991     991 0 3750 my ($inc_ref, $anchor, $replacement) = @_;
94 991 100       2831 $anchor = '^' if not defined $anchor;
95 991 50       4549 $replacement = '' if not defined $replacement;
96              
97 991         6341 my @inc = @$inc_ref;
98              
99 991         2069 our %make_path_strip_editor_cache;
100 991         6951 my $key = join "\t", $anchor, $replacement, @inc;
101              
102 991   66     9741 return $make_path_strip_editor_cache{$key} ||= do {
103              
104 33         297 my $inc_regex = get_abs_paths_alternation_regex(\@inc);
105              
106             # anchor at start, capture anchor
107 33         8469 $inc_regex = qr{($anchor)$inc_regex};
108              
109 33     991   912 sub { $_[0] =~ s{$inc_regex}{$1$replacement} };
  991         20922  
110             };
111             }
112              
113              
114             # edit @$paths in-place to remove specified absolute path prefixes
115             sub strip_prefix_from_paths {
116 995     995 0 11278 my ($inc_ref, $paths, $anchor, $replacement) = @_;
117              
118 995 100       4785 croak "strip_prefix_from_paths() first argument must be array ref"
119             unless ref($inc_ref) eq 'ARRAY';
120 994 100       2004 return unless @{$inc_ref};
  994         3172  
121              
122 993 100       3196 return if not defined $paths;
123 992 100       3716 croak "strip_prefix_from_paths() second argument must be array ref"
124             unless ref($paths) eq 'ARRAY';
125              
126 991 50       6382 my $editor = make_path_strip_editor($inc_ref, $anchor, $replacement)
127             or return;
128              
129             # strip off prefix using regex, skip any empty/undef paths
130 991 50       4319 if (UNIVERSAL::isa($paths, 'ARRAY')) {
131 991         5250 for my $path (@$paths) {
132 991 50       5405 if (ref $path) { # recurse to process deeper data
    50          
133 0         0 strip_prefix_from_paths($inc_ref, $path, $anchor, $replacement);
134             }
135             elsif ($path) {
136 991         3482 $editor->($path);
137             }
138             }
139             }
140             # There are no instances in test suite where $paths is a hash ref
141             # elsif (UNIVERSAL::isa($paths, 'HASH')) {
142             # for my $orig (keys %$paths) {
143             # $editor->(my $new = $orig)
144             # or next;
145             # my $value = delete $paths->{$orig};
146             # warn "Stripping prefix from $orig overwrites existing $new"
147             # if defined $paths->{$new};
148             # $paths->{$new} = $value;
149             # }
150             # }
151             else {
152 0         0 croak "Can't strip_prefix_from_paths of $paths";
153             }
154              
155 991         3758 return;
156             }
157              
158              
159             # eg normalize the width/precision so that the tables look good.
160             sub fmt_float {
161 17     17 0 11528 my ($val, $precision) = @_;
162 17   100     72 $precision ||= 5;
163 17 100 66     119 if ($val < 10 ** -($precision - 1) and $val > 0) {
    100          
164             # Give the same width as a larger value formatted with the %f below.
165             # This gives us 2 digits of precision for $precision == 5
166 1         9 $val = sprintf("%." . ($precision - 4) . "e", $val);
167             # But our exponents will always be e-05 to e-09, never e-10 or smaller
168             # so remove the leading zero to make these small numbers stand out less
169             # on the table.
170 1         22 $val =~ s/e-0+/e-/;
171             }
172             elsif ($val != int($val)) {
173 14         145 $val = sprintf("%.${precision}f", $val);
174             }
175 17         57 return $val;
176             }
177              
178              
179             # XXX undocumented hack that may become to an option one day
180             # Useful for making the time data more easily parseable
181             my $fmt_time_opt = $ENV{NYTPROF_FMT_TIME}; # e.g., '%f' for 'raw' times
182              
183             sub fmt_time {
184 28     28 0 134 my ($sec, $width) = @_;
185 28 50       65 $width = '' unless defined $width;
186 28 50       54 return undef if not defined $sec;
187 28 50       65 return '-'.fmt_time(-$sec, $width) if $sec < 0; # negative value, can happen
188 28 50       62 return sprintf $fmt_time_opt, $sec if $fmt_time_opt;
189 28 100       60 return sprintf "%$width.0fs", 0 unless $sec;
190 27 100       76 return sprintf "%$width.0fns", $sec * 1e9 if $sec < 1e-6;
191 23 100       71 return sprintf "%$width.0fµs", $sec * 1e6 if $sec < 1e-3;
192 16 100       84 return sprintf "%$width.*fms", 3 - length(int($sec * 1e3)), $sec * 1e3 if $sec < 1;
193 10 100       73 return sprintf "%$width.*fs", 3 - length(int($sec )), $sec if $sec < 100;
194 2         14 return sprintf "%$width.0fs", $sec;
195             }
196              
197              
198             sub fmt_incl_excl_time {
199 5     5 0 13 my ($incl, $excl) = @_;
200 5         10 my $diff = $incl - $excl;
201 5 100       16 return fmt_time($incl) unless $diff;
202 4         11 $_ = fmt_time($_) for $incl, $excl, $diff;
203 4 50       27 if ($incl =~ /(\D+)$/) {
204             # no need to repeat the unit if it's the same for all time stamps
205 4         12 my $unit = $1;
206 4         6 my $offset = -length($unit);
207 4         8 for ($excl, $diff) {
208 8 50       28 if (/(\D+)$/) {
209 8 100       25 substr($_, $offset) = "" if $1 eq $unit
210             }
211             }
212             }
213 4         26 return sprintf "%s (%s+%s)", $incl, $excl, $diff;
214             }
215              
216              
217             ## Given a ref to an array of numeric values
218             ## returns median distance from the median value, and the median value.
219             ## See http://en.wikipedia.org/wiki/Median_absolute_deviation
220             sub calculate_median_absolute_deviation {
221 20     20 0 4536 my $values_ref = shift;
222 20         35 my ($ignore_zeros) = @_;
223 20 100       248 croak "No array ref given" unless ref $values_ref eq 'ARRAY';
224              
225 19 100       178 my @values = ($ignore_zeros) ? grep {$_} @$values_ref : @$values_ref;
  8         13  
226 19         54 my $median_value = [sort { $a <=> $b } @values]->[@values / 2];
  252         394  
227              
228 19 100       53 return [0, 0] if not defined $median_value; # no data
229              
230 18         40 my @devi = map { abs($_ - $median_value) } @values;
  135         208  
231 18         36 my $median_devi = [sort { $a <=> $b } @devi]->[@devi / 2];
  264         468  
232              
233 18         102 return [$median_devi, $median_value];
234             }
235              
236              
237             sub html_safe_filename {
238 336     336 0 807154 my ($fname) = @_;
239             # replace / and \ with html safe '-', we also do a bunch of other
240             # chars, especially ':' for Windows, to make the namer simpler and safer
241             # also remove dots to keep VMS happy
242 336         3656 $fname =~ s{ [-/\\:\*\?"'<>|.]+ }{-}xg;
243             # remove any leading or trailing '-' chars
244 336         2772 $fname =~ s{^-}{};
245 336         1881 $fname =~ s{-$}{};
246 336 50       2458 if($^O eq 'VMS'){
247             # ODS-2 is limited to 39.39 chars (39 filename, 39 extension)
248             # Reader.pm appends -LEVEL onto html safe filename so must
249             # subtract 1 + max length of (sub block line), so 6.
250 0         0 $fname = substr($fname,-33);
251             }
252 336         1657 return $fname;
253             }
254              
255             sub _dumper {
256 2     2   1831 require Data::Dumper;
257 2         7776 local $Data::Dumper::Sortkeys = 1;
258 2         6 local $Data::Dumper::Indent = 1;
259 2         13 return Data::Dumper::Dumper(@_);
260             }
261              
262             1;
263              
264             __END__