Skip to content

Commit 0ced3be

Browse files
authored
Improved spaceout handling. (#401)
See '--large-files' entry in man geninfo(1), lcov(1). This feature is equivalent to finding all the GCDA files which match the regexp(s), processing them serially, then finding the remining GCDA files and processing them in parallel - and then aggregating the result. The feature just simplifies the Jenkins regression script. More sophisticated scheduling is possible - but seems unnecessary at present. Signed-off-by: Henry Cox <[email protected]>
1 parent 7451f44 commit 0ced3be

File tree

5 files changed

+150
-38
lines changed

5 files changed

+150
-38
lines changed

Diff for: bin/genhtml

-2
Original file line numberDiff line numberDiff line change
@@ -3794,7 +3794,6 @@ sub check_path_consistency
37943794
++$diffBaseMap{$b}->[1]->{$alias};
37953795
} elsif (!exists($self->[UNCHANGED]->{$self->findName($curr)})) {
37963796
$missed{$curr} = 1; # in current but not in diff
3797-
#lcovutil::info("missed curr: $curr\n");
37983797
}
37993798
}
38003799
# for each file in 'baseline' info:
@@ -3810,7 +3809,6 @@ sub check_path_consistency
38103809
++$diffBaseMap{$b}->[1]->{$alias};
38113810
} elsif (!exists($self->[UNCHANGED]->{$self->findName($base)})) {
38123811
# in baseline but not in diff
3813-
#lcovutil::info("missed base: $base\n");
38143812
if (exists($missed{$base})) {
38153813
$missed{$base} |= 2;
38163814
} else {

Diff for: bin/geninfo

+106-36
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ our $intervalChildCpuTime = 0; # since last updata
222222
our $defaultChunkSize;
223223
our $defaultInterval;
224224
our %childRetryCounts;
225+
our @large_files;
225226

226227
our $cwd = getcwd();
227228
chomp($cwd);
@@ -255,7 +256,8 @@ my %geninfo_opts = ("test-name|t=s" => \$test_name,
255256
"derive-func-data" => \$opt_derive_func_data,
256257
"external|e" => \$lcovutil::opt_external,
257258
"no-external" => \$lcovutil::opt_no_external,
258-
"compat=s" => \$lcovutil::geninfo_opt_compat,);
259+
"compat=s" => \$lcovutil::geninfo_opt_compat,
260+
'large-file=s' => \@large_files);
259261

260262
# Parse command line options
261263
if (!lcovutil::parseOptions(\%lcovutil::geninfo_rc_opts, \%geninfo_opts,
@@ -268,6 +270,12 @@ $buildDirSearchPath =
268270
SearchPath->new('build directory', @lcovutil::build_directory);
269271
@gcov_tool = @lcovutil::rc_gcov_tool unless @gcov_tool;
270272

273+
eval {
274+
map { qr($_) } @large_files;
275+
};
276+
die("invalid 'large-file' regexp: $@")
277+
if ($@);
278+
271279
# Check regexp
272280
if (defined($lcovutil::rc_adjust_src_path)) {
273281
my ($pattern, $replace) = split(/\s*=>\s*/, $lcovutil::rc_adjust_src_path);
@@ -1065,7 +1073,7 @@ sub _process_one_chunk($$$$)
10651073

10661074
# "name" will be .gcno if "$initial" else will be $gcda
10671075
my $name = defined($gcda_file) ? $gcda_file : $gcno_file;
1068-
info(1, "Processing $name%s\n", defined($pid) ? " in child $pid" : "");
1076+
info(1, "Processing $name%s\n", defined($pid) ? " in child $pid" : "" . "\n");
10691077
my $context = MessageContext->new("capturing from $name");
10701078

10711079
# multiple gcda files may refer to the same source - so generate the
@@ -1181,6 +1189,19 @@ sub _merge_one_child($$$)
11811189
$lcovutil::max_fork_fails != 0) ||
11821190
$lcovutil::verbose);
11831191
print(STDERR $childErr);
1192+
# look for spaceout message in the gcov log
1193+
if (0 == $signal &&
1194+
0 != $childstatus &&
1195+
0 != $lcovutil::max_fork_fails &&
1196+
lcovutil::is_ignored($lcovutil::ERROR_FORK) &&
1197+
grep(
1198+
{ /(std::bad_alloc|annot allocate memory|out of memory|integretity check failed for compressed file)/
1199+
} ($childLog, $childErr))
1200+
) {
1201+
1202+
# pretend it was killed so we retry
1203+
$signal = POSIX::SIGKILL;
1204+
}
11841205
my $data = Storable::retrieve($dumped)
11851206
if (-f $dumped && 0 == $childstatus);
11861207
# note that $data will not be defined (no data dumped) if there was
@@ -1298,15 +1319,26 @@ sub gen_info(@)
12981319
$chunkSize = 1;
12991320
}
13001321
my @worklist;
1301-
my $chunk = [];
1322+
my $serialChunk = [1, []];
1323+
my $chunk = [0, []]; # [isSerial, [fileList]]
13021324
foreach my $j (@$filelist) {
1303-
push(@$chunk, $j);
1304-
if (scalar(@$chunk) == $chunkSize) {
1325+
my $filename = $j->[0] . $lcovutil::dirseparator . $j->[1];
1326+
if (grep({ $filename =~ $_ } @main::large_files)) {
1327+
lcovutil::info(1, "large file: $filename\n");
1328+
push(@{$serialChunk->[1]}, $j);
1329+
next;
1330+
}
1331+
push(@{$chunk->[1]}, $j);
1332+
if (scalar(@{$chunk->[1]}) == $chunkSize) {
13051333
push(@worklist, $chunk);
1306-
$chunk = [];
1334+
$chunk = [0, []];
13071335
}
13081336
} #foreach DATA_FILE
1309-
push(@worklist, $chunk) if @$chunk;
1337+
push(@worklist, $chunk) if @{$chunk->[1]};
1338+
# serial chunk is at the top of the stack - so serial processing
1339+
# happens before we fork multiple processes
1340+
push(@worklist, $serialChunk)
1341+
if (@{$serialChunk->[1]});
13101342

13111343
# Process all files in list
13121344
my $currentParallel = 0;
@@ -1342,7 +1374,8 @@ sub gen_info(@)
13421374
my $chunk = pop(@worklist);
13431375
++$processedChunks;
13441376

1345-
if (1 < $lcovutil::maxParallelism) {
1377+
if (1 < $lcovutil::maxParallelism &&
1378+
1 != $chunk->[0]) {
13461379

13471380
my $currentSize = 0;
13481381
if (0 != $lcovutil::maxMemory) {
@@ -1410,8 +1443,8 @@ sub gen_info(@)
14101443
my ($stdout, $stderr, $code) = Capture::Tiny::capture {
14111444
eval {
14121445
$childInfo =
1413-
_process_one_chunk($chunk, $processedChunks,
1414-
$childInfo, $$);
1446+
_process_one_chunk($chunk->[1],
1447+
$processedChunks, $childInfo, $$);
14151448
};
14161449
if ($@) {
14171450
$status = 1; # error
@@ -1458,12 +1491,14 @@ sub gen_info(@)
14581491
# there is no childInfo data
14591492
$data =
14601493
Storable::store(
1461-
[$single_file ? $childInfo : undef,
1462-
$buildDirCounts,
1463-
[$files_created, scalar(@$chunk), $then],
1464-
lcovutil::compute_update($currentState)
1465-
],
1466-
$dumpf) if defined($childInfo);
1494+
[$single_file ? $childInfo : undef,
1495+
$buildDirCounts,
1496+
[$files_created, scalar(@{$chunk->[1]}),
1497+
$then
1498+
],
1499+
lcovutil::compute_update($currentState)
1500+
],
1501+
$dumpf) if defined($childInfo);
14671502
};
14681503
if ($@ || (defined($childInfo) && !defined($data))) {
14691504
lcovutil::ignorable_error($lcovutil::ERROR_PARALLEL,
@@ -1477,12 +1512,24 @@ sub gen_info(@)
14771512
}
14781513
} else {
14791514
# not parallel..
1515+
my $saveParallel = $lcovutil::maxParallelism;
1516+
$lcovutil::maxParallelism = 1;
1517+
if ($chunk->[0]) {
1518+
my $num = scalar(@{$chunk->[1]});
1519+
lcovutil::info("Processing $num file" .
1520+
($num == 1 ? '' : 's') .
1521+
" from chunk 0 serially\n");
1522+
}
14801523
my $now = Time::HiRes::gettimeofday();
14811524
$trace_data =
1482-
_process_one_chunk($chunk, $processedChunks, $trace_data,
1483-
undef);
1484-
$processedFiles += scalar(@$chunk);
1525+
_process_one_chunk($chunk->[1], $processedChunks,
1526+
$trace_data, undef);
1527+
$processedFiles += scalar(@{$chunk->[1]});
1528+
if ($chunk->[0]) {
1529+
lcovutil::info("Finished processing chunk 0\n");
1530+
}
14851531
my $then = Time::HiRes::gettimeofday();
1532+
$lcovutil::maxParallelism = $saveParallel;
14861533
$lcovutil::profileData{process}{$processedChunks} =
14871534
$then - $now;
14881535
}
@@ -1975,7 +2022,8 @@ sub compute_internal_directories(@)
19752022
my $t = Cwd::realpath($top);
19762023
die("expected directory found '$t'") unless -d $t;
19772024
unless (exists($visited{$t})) {
1978-
lcovutil::info("internal: target '$t' of link '$top'\n");
2025+
lcovutil::info(1,
2026+
"internal directory: target '$t' of link '$top'\n");
19792027
$visited{$t} = $top;
19802028
push(@dirstack, $t);
19812029
push(@lcovutil::internal_dirs, $t)
@@ -2836,11 +2884,22 @@ sub process_intermediate($$$$)
28362884
(0 != $rc ||
28372885
$lcovutil::verbose));
28382886

2887+
my $gcovOutGlobPattern =
2888+
"$tempdir/*.gcov $tempdir/.*.gcov $tempdir/*.gcov.json.gz $tempdir/.*gcov.json.gz";
2889+
28392890
if (0 != $rc) {
28402891
if (check_gcov_fail($err, $file)) {
28412892
return;
28422893
}
28432894
$errmsg = "GCOV failed for $file";
2895+
# can parse the error log to see if it spaced out - then return
2896+
# code so parent can catch it
2897+
if ($err =~ /out of memory allocating/) {
2898+
lcovutil::info("spaceout calling gcov for '$data_file'\n");
2899+
$errmsg .= ' out of memory';
2900+
$errorType = $lcovutil::ERROR_CHILD
2901+
if 1 != $lcovutil::maxParallelism;
2902+
}
28442903
goto err;
28452904
}
28462905

@@ -2853,22 +2912,32 @@ sub process_intermediate($$$$)
28532912
# 'meson' build system likes to use "." as leading character in generated
28542913
# files. Seems an unfortunate decision.
28552914
my $start = Time::HiRes::gettimeofday();
2856-
for my $gcov_filename (
2857-
glob(
2858-
"$tempdir/*.gcov $tempdir/.*.gcov $tempdir/*.gcov.json.gz $tempdir/.*gcov.json.gz"
2859-
)
2860-
) {
2861-
if ($gcov_filename =~ /\.gcov\.json/) {
2862-
read_intermediate_json($gcov_filename, \%data, \$json_basedir);
2863-
$json_format = 1;
2864-
} else {
2865-
read_intermediate_text($gcov_filename, \%data);
2866-
}
2867-
if ($lcovutil::preserve_intermediates) {
2868-
File::Copy::move($gcov_filename, $fdir) or
2869-
die("cannot rename $gcov_filename: $!");
2870-
} else {
2871-
unlink($gcov_filename);
2915+
for my $gcov_filename (glob($gcovOutGlobPattern)) {
2916+
eval {
2917+
if ($gcov_filename =~ /\.gcov\.json/) {
2918+
read_intermediate_json($gcov_filename, \%data, \$json_basedir);
2919+
$json_format = 1;
2920+
} else {
2921+
read_intermediate_text($gcov_filename, \%data);
2922+
}
2923+
if ($lcovutil::preserve_intermediates) {
2924+
File::Copy::move($gcov_filename, $fdir) or
2925+
die("cannot rename $gcov_filename: $!");
2926+
} else {
2927+
unlink($gcov_filename);
2928+
}
2929+
};
2930+
if ($@) {
2931+
if (1 != $lcovutil::maxParallelism &&
2932+
$@ =~ /(integrity check failed|cannot start)/) {
2933+
# looks like we ran out of memory..
2934+
# maybe need new error type ERROR_MEMORY
2935+
#$errorType = $lcovutil::ERROR_GCOV;
2936+
$errmsg = $@;
2937+
goto err;
2938+
} else {
2939+
die("read_intermediate failed: $@");
2940+
}
28722941
}
28732942
}
28742943
my $end = Time::HiRes::gettimeofday();
@@ -2915,6 +2984,7 @@ sub process_intermediate($$$$)
29152984
return $trace;
29162985

29172986
err:
2987+
unlink(glob($gcovOutGlobPattern)); # clean up - in case gcov died
29182988
ignorable_error($errorType, "$errmsg!");
29192989
return undef;
29202990
}

Diff for: bin/lcov

+3
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ our $compat_libtool; # If set, indicates that libtool mode is to be enabled
162162
our $no_compat_libtool; # If set, indicates that libtool mode is to be disabled
163163

164164
our @gcov_tool;
165+
our @large_files; # handled sequentially in geninfo
165166
our $initial;
166167
our $captureAll;
167168
our $no_recursion = 0;
@@ -214,6 +215,7 @@ my %lcov_options = ("directory|d|di=s" => \@directory,
214215
"compat-libtool" => \$compat_libtool,
215216
"no-compat-libtool" => \$no_compat_libtool,
216217
"gcov-tool=s" => \@gcov_tool,
218+
'large-file=s' => \@large_files,
217219

218220
"initial|i" => \$initial,
219221
"all" => \$captureAll,
@@ -938,6 +940,7 @@ sub lcov_geninfo(@)
938940
\@lcovutil::exclude_function_patterns
939941
],
940942
['--filter', \@lcovutil::opt_filter],
943+
['--large-file', \@large_files],
941944
) {
942945
my ($opt, $l) = @$listOpt;
943946
foreach my $v (@$l) {

Diff for: man/geninfo.1

+28
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ geninfo \- Generate tracefiles from GCOV coverage data files
5151
.RB [ \-\-parallel | -j
5252
.IR [integer] ]
5353
.br
54+
.br [ \-\-large\-file
55+
.IR regexp ]
56+
.br
5457
.RB [ \-\-memory
5558
.IR integer_num_Mb ]
5659
.br
@@ -1299,6 +1302,13 @@ generated: host, date, environment,
12991302
.RS
13001303
Specify parallelism to use during processing (maximum number of forked child processes). If the optional integer parallelism parameter is zero or is missing, then use to use up the number of cores on the machine. Default is not to use a single process (no parallelism).
13011304

1305+
The
1306+
.I \-\large\-file
1307+
option described below may be necessary to enable parallelism to succeed
1308+
in the presence of data files which consume excessive memory in
1309+
.B gcov.
1310+
1311+
13021312
Also see the
13031313
.I memory, memory_percentage, max_fork_fails, fork_fail_timeout, geninfo_chunk_size
13041314
and
@@ -1307,6 +1317,24 @@ entries in man
13071317
.B lcovrc(5)
13081318
for a description of some options which may aid in parameter tuning and performance optimization.
13091319

1320+
.RE
1321+
.BI "\-\-large\-file "
1322+
.I regexp
1323+
.RS
1324+
1325+
GCDA files whose name matches a
1326+
.I \-\-large\-file
1327+
regexp are processed serially - not in parallel with other files - so that
1328+
their
1329+
.B gcov
1330+
process can use all available system memory.
1331+
.br
1332+
Use this option is you see errors related to memory allocation from gcov.
1333+
.br
1334+
This feature is exactly as if you had moved the matching GCDA files to another location and processed them serially, then processed remaining GDCA files in parallel and merged the results.
1335+
1336+
This option may be used multiple times to specify more than one regexp.
1337+
13101338
.RE
13111339
.BI "\-\-memory "
13121340
.I integer

Diff for: man/lcov.1

+13
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ Capture coverage data tracefile (from compiler-generated data):
9191
.RB [ \-\-comment
9292
.IR comment_string ]
9393
.br
94+
.RB [ \-\-large\-file
95+
.IR regexp ]
96+
.br
9497
.RE
9598
.RE
9699

@@ -1178,6 +1181,16 @@ specified at a time.
11781181
Follow links when searching for .da files.
11791182
.RE
11801183

1184+
.BI "\-\-large\-file "
1185+
.I regexp
1186+
.RS
1187+
See the
1188+
.I \-\-large\-file
1189+
section of man
1190+
.B geninfo(1)
1191+
for details.
1192+
.RE
1193+
11811194
.B \-\-from\-package
11821195
.I package
11831196
.br

0 commit comments

Comments
 (0)