Skip to content

Commit

Permalink
coverage: support output in LCOV tracefile format (JuliaLang#30381)
Browse files Browse the repository at this point in the history
Also ensure that `julia_cmd` is forwarding all desirable options,
and provide a general framework for specifying formatted filenames
for similar such options.
  • Loading branch information
vtjnash authored and JeffBezanson committed Dec 20, 2018
1 parent 072ad7d commit 735d755
Show file tree
Hide file tree
Showing 14 changed files with 381 additions and 77 deletions.
1 change: 1 addition & 0 deletions base/options.jl
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ struct JLOptions
outputjitbc::Ptr{UInt8}
outputo::Ptr{UInt8}
outputji::Ptr{UInt8}
output_code_coverage::Ptr{UInt8}
incremental::Int8
end

Expand Down
87 changes: 62 additions & 25 deletions base/util.jl
Original file line number Diff line number Diff line change
Expand Up @@ -399,42 +399,79 @@ printstyled(io::IO, msg...; bold::Bool=false, color::Union{Int,Symbol}=:normal)
with_output_color(print, color, io, msg...; bold=bold)
printstyled(msg...; bold::Bool=false, color::Union{Int,Symbol}=:normal) =
printstyled(stdout, msg...; bold=bold, color=color)

"""
Base.julia_cmd(juliapath=joinpath(Sys.BINDIR::String, julia_exename()))
Return a julia command similar to the one of the running process.
Propagates the `--cpu-target`, `--sysimage`, --compile `, `--depwarn`
and `--inline` command line arguments.
Propagates any of the `--cpu-target`, `--sysimage`, `--compile`, `--sysimage-native-code`,
`--compiled-modules`, `--inline`, `--check-bounds`, `--optimize`, `-g`,
`--code-coverage`, and `--depwarn`
command line arguments that are not at their default values.
Among others, `--math-mode`, `--warn-overwrite`, and `--trace-compile` are notably not propagated currently.
!!! compat "Julia 1.1"
The `--inline` flag is only propagated in Julia 1.1 and later.
Only the `--cpu-target`, `--sysimage`, `--depwarn`, `--compile` and `--check-bounds` flags were propagated before Julia 1.1.
"""
function julia_cmd(julia=joinpath(Sys.BINDIR::String, julia_exename()))
opts = JLOptions()
cpu_target = unsafe_string(opts.cpu_target)
image_file = unsafe_string(opts.image_file)
compile = if opts.compile_enabled == 0
"no"
elseif opts.compile_enabled == 2
"all"
elseif opts.compile_enabled == 3
"min"
else
"yes"
end
depwarn = if opts.depwarn == 0
"no"
elseif opts.depwarn == 2
"error"
else
"yes"
end
inline = if opts.can_inline == 0
"no"
else
"yes"
end
`$julia -C$cpu_target -J$image_file --compile=$compile --depwarn=$depwarn --inline=$inline`
addflags = String[]
let compile = if opts.compile_enabled == 0
"no"
elseif opts.compile_enabled == 2
"all"
elseif opts.compile_enabled == 3
"min"
else
"" # default = "yes"
end
isempty(compile) || push!(addflags, "--compile=$compile")
end
let depwarn = if opts.depwarn == 0
"no"
elseif opts.depwarn == 2
"error"
else
"" # default = "yes"
end
isempty(depwarn) || push!(addflags, "--depwarn=$depwarn")
end
let check_bounds = if opts.check_bounds == 1
"yes" # on
elseif opts.check_bounds == 2
"no" # off
else
"" # "default"
end
isempty(check_bounds) || push!(addflags, "--check-bounds=$check_bounds")
end
opts.can_inline == 0 && push!(addflags, "--inline=no")
opts.use_compiled_modules == 0 && push!(addflags, "--compiled-modules=no")
opts.opt_level == 2 || push!(addflags, "-O$(opts.opt_level)")
push!(addflags, "-g$(opts.debug_level)")
if opts.code_coverage != 0
# Forward the code-coverage flag only if applicable (if the filename is pid-dependent)
coverage_file = (opts.output_code_coverage != C_NULL) ? unsafe_string(opts.output_code_coverage) : ""
if isempty(coverage_file) || occursin("%p", coverage_file)
if opts.code_coverage == 1
push!(addflags, "--code-coverage=user")
elseif opts.code_coverage == 2
push!(addflags, "--code-coverage=all")
end
isempty(coverage_file) || push!(addflags, "--code-coverage=$coverage_file")
end
end
if opts.malloc_log != 0
if opts.malloc_log == 1
push!(addflags, "--track-allocation=user")
elseif opts.malloc_log == 2
push!(addflags, "--track-allocation=all")
end
end
return `$julia -C$cpu_target -J$image_file $addflags`
end

function julia_exename()
Expand Down
70 changes: 59 additions & 11 deletions src/codegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1696,7 +1696,7 @@ static void write_log_data(logdata_t &logData, const char *extension)
std::ifstream inf(filename.c_str());
if (inf.is_open()) {
std::string outfile = filename + extension;
std::ofstream outf(outfile.c_str(), std::ofstream::trunc | std::ofstream::out);
std::ofstream outf(outfile.c_str(), std::ofstream::trunc | std::ofstream::out | std::ofstream::binary);
char line[1024];
int l = 1;
unsigned block = 0;
Expand All @@ -1722,7 +1722,7 @@ static void write_log_data(logdata_t &logData, const char *extension)
else
outf << (value - 1);
outf.width(0);
outf << " " << line << std::endl;
outf << " " << line << '\n';
}
outf.close();
inf.close();
Expand All @@ -1732,20 +1732,68 @@ static void write_log_data(logdata_t &logData, const char *extension)
}

extern "C" int jl_getpid();
extern "C" void jl_write_coverage_data(void)

static void write_lcov_data(logdata_t &logData, const std::string &outfile)
{
std::ostringstream stm;
stm << jl_getpid();
std::string outf = "." + stm.str() + ".cov";
write_log_data(coverageData, outf.c_str());
std::ofstream outf(outfile.c_str(), std::ofstream::ate | std::ofstream::out | std::ofstream::binary);
//std::string base = std::string(jl_options.julia_bindir);
//base = base + "/../share/julia/base/";
logdata_t::iterator it = logData.begin();
for (; it != logData.end(); it++) {
const std::string &filename = it->first();
const std::vector<logdata_block*> &values = it->second;
if (!values.empty()) {
//if (!isabspath(filename.c_str()))
// filename = base + filename;
outf << "SF:" << filename << '\n';
size_t n_covered = 0;
size_t n_instrumented = 0;
size_t lno = 0;
for (auto &itv : values) {
if (itv) {
logdata_block &data = *itv;
for (int i = 0; i < logdata_blocksize; i++) {
auto cov = data[i];
if (cov > 0) {
n_instrumented++;
if (cov > 1)
n_covered++;
outf << "DA:" << lno << ',' << (cov - 1) << '\n';
}
lno++;
}
}
else {
lno += logdata_blocksize;
}
}
outf << "LH:" << n_covered << '\n';
outf << "LF:" << n_instrumented << '\n';
outf << "end_of_record\n";
}
}
outf.close();
}

extern "C" void jl_write_coverage_data(const char *output)
{
if (output) {
StringRef output_pattern(output);
if (output_pattern.endswith(".info"))
write_lcov_data(coverageData, jl_format_filename(output_pattern));
}
else {
std::ostringstream stm;
stm << "." << jl_getpid() << ".cov";
write_log_data(coverageData, stm.str().c_str());
}
}

extern "C" void jl_write_malloc_log(void)
{
std::ostringstream stm;
stm << jl_getpid();
std::string outf = "." + stm.str() + ".mem";
write_log_data(mallocData, outf.c_str());
stm << "." << jl_getpid() << ".mem";
write_log_data(mallocData, stm.str().c_str());
}

// --- constant determination ---
Expand Down Expand Up @@ -7368,7 +7416,7 @@ extern "C" void *jl_init_llvm(void)
cl::ParseEnvironmentOptions("Julia", "JULIA_LLVM_ARGS");

jl_page_size = jl_getpagesize();
imaging_mode = jl_generating_output();
imaging_mode = jl_generating_output() && !jl_options.incremental;
jl_init_debuginfo();

#ifdef USE_POLLY
Expand Down
56 changes: 52 additions & 4 deletions src/init.c
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ static void jl_uv_exitcleanup_walk(uv_handle_t *handle, void *arg)
jl_uv_exitcleanup_add(handle, (struct uv_shutdown_queue*)arg);
}

void jl_write_coverage_data(void);
void jl_write_coverage_data(const char*);
void jl_write_malloc_log(void);
void jl_write_compiler_output(void);

Expand Down Expand Up @@ -222,7 +222,7 @@ JL_DLLEXPORT void jl_atexit_hook(int exitcode)
jl_write_compiler_output();
jl_print_gc_stats(JL_STDERR);
if (jl_options.code_coverage)
jl_write_coverage_data();
jl_write_coverage_data(jl_options.output_code_coverage);
if (jl_options.malloc_log)
jl_write_malloc_log();
if (jl_base_module) {
Expand Down Expand Up @@ -462,7 +462,7 @@ int isabspath(const char *in)
}

static char *abspath(const char *in, int nprefix)
{ // compute an absolute path location, so that chdir doesn't change the file reference
{ // compute an absolute realpath location, so that chdir doesn't change the file reference
// ignores (copies directly over) nprefix characters at the start of abspath
#ifndef _OS_WINDOWS_
char *out = realpath(in + nprefix, NULL);
Expand Down Expand Up @@ -495,6 +495,8 @@ static char *abspath(const char *in, int nprefix)
jl_error("fatal error: unexpected error while retrieving current working directory");
}
out = (char*)malloc(path_size + 1 + sz + nprefix);
if (!out)
jl_errorf("fatal error: failed to allocate memory: %s", strerror(errno));
memcpy(out, in, nprefix);
memcpy(out + nprefix, path, path_size);
out[nprefix + path_size] = PATHSEPSTRING[0];
Expand All @@ -508,6 +510,8 @@ static char *abspath(const char *in, int nprefix)
jl_error("fatal error: jl_options.image_file path too long or GetFullPathName failed");
}
char *out = (char*)malloc(n + nprefix);
if (!out)
jl_errorf("fatal error: failed to allocate memory: %s", strerror(errno));
DWORD m = GetFullPathName(in + nprefix, n, out + nprefix, NULL);
if (n != m + 1) {
jl_error("fatal error: jl_options.image_file path too long or GetFullPathName failed");
Expand All @@ -517,6 +521,38 @@ static char *abspath(const char *in, int nprefix)
return out;
}

// create an absolute-path copy of the input path format string
// formed as `joinpath(replace(pwd(), "%" => "%%"), in)`
// unless `in` starts with `%`
static const char *absformat(const char *in)
{
if (in[0] == '%' || isabspath(in))
return in;
// get an escaped copy of cwd
size_t path_size = PATH_MAX;
char path[PATH_MAX];
if (uv_cwd(path, &path_size)) {
jl_error("fatal error: unexpected error while retrieving current working directory");
}
size_t sz = strlen(in) + 1;
size_t i, fmt_size = 0;
for (i = 0; i < path_size; i++)
fmt_size += (path[i] == '%' ? 2 : 1);
char *out = (char*)malloc(fmt_size + 1 + sz);
if (!out)
jl_errorf("fatal error: failed to allocate memory: %s", strerror(errno));
fmt_size = 0;
for (i = 0; i < path_size; i++) { // copy-replace pwd portion
char c = path[i];
out[fmt_size++] = c;
if (c == '%')
out[fmt_size++] = '%';
}
out[fmt_size++] = PATHSEPSTRING[0]; // path sep
memcpy(out + fmt_size, in, sz); // copy over format, including nul
return out;
}

static void jl_resolve_sysimg_location(JL_IMAGE_SEARCH rel)
{ // this function resolves the paths in jl_options to absolute file locations as needed
// and it replaces the pointers to `julia_bindir`, `julia_bin`, `image_file`, and output file paths
Expand All @@ -527,13 +563,17 @@ static void jl_resolve_sysimg_location(JL_IMAGE_SEARCH rel)
// calling `julia_init()`
char *free_path = (char*)malloc(PATH_MAX);
size_t path_size = PATH_MAX;
if (!free_path)
jl_errorf("fatal error: failed to allocate memory: %s", strerror(errno));
if (uv_exepath(free_path, &path_size)) {
jl_error("fatal error: unexpected error while retrieving exepath");
}
if (path_size >= PATH_MAX) {
jl_error("fatal error: jl_options.julia_bin path too long");
}
jl_options.julia_bin = (char*)malloc(path_size+1);
if (!jl_options.julia_bin)
jl_errorf("fatal error: failed to allocate memory: %s", strerror(errno));
memcpy((char*)jl_options.julia_bin, free_path, path_size);
((char*)jl_options.julia_bin)[path_size] = '\0';
if (!jl_options.julia_bindir) {
Expand All @@ -550,6 +590,8 @@ static void jl_resolve_sysimg_location(JL_IMAGE_SEARCH rel)
if (rel == JL_IMAGE_JULIA_HOME && !isabspath(jl_options.image_file)) {
// build time path, relative to JULIA_BINDIR
free_path = (char*)malloc(PATH_MAX);
if (!free_path)
jl_errorf("fatal error: failed to allocate memory: %s", strerror(errno));
int n = snprintf(free_path, PATH_MAX, "%s" PATHSEPSTRING "%s",
jl_options.julia_bindir, jl_options.image_file);
if (n >= PATH_MAX || n < 0) {
Expand All @@ -572,8 +614,13 @@ static void jl_resolve_sysimg_location(JL_IMAGE_SEARCH rel)
jl_options.outputbc = abspath(jl_options.outputbc, 0);
if (jl_options.machine_file)
jl_options.machine_file = abspath(jl_options.machine_file, 0);
if (jl_options.project && strncmp(jl_options.project, "@.", strlen(jl_options.project)) != 0)
if (jl_options.project
&& strcmp(jl_options.project, "@.") != 0
&& strcmp(jl_options.project, "@") != 0
&& strcmp(jl_options.project, "") != 0)
jl_options.project = abspath(jl_options.project, 0);
if (jl_options.output_code_coverage)
jl_options.output_code_coverage = absformat(jl_options.output_code_coverage);

const char **cmdp = jl_options.cmds;
if (cmdp) {
Expand All @@ -600,6 +647,7 @@ void _julia_init(JL_IMAGE_SEARCH rel)
jl_get_ptls_states_getter();
#endif
jl_ptls_t ptls = jl_get_ptls_states();
(void)ptls; assert(ptls); // make sure early that we have initialized ptls
jl_safepoint_init();
libsupport_init();
htable_new(&jl_current_modules, 0);
Expand Down
16 changes: 13 additions & 3 deletions src/jloptions.c
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ jl_options_t jl_options = { 0, // quiet
NULL, // output-jit-bc
NULL, // output-o
NULL, // output-ji
NULL, // output-code_coverage
0, // incremental
0 // image_file_specified
};
Expand Down Expand Up @@ -129,6 +130,9 @@ static const char opts[] =
// instrumentation options
" --code-coverage={none|user|all}, --code-coverage\n"
" Count executions of source lines (omitting setting is equivalent to \"user\")\n"
" --code-coverage=tracefile.info\n"
" Append coverage information to the LCOV tracefile (filename supports format tokens).\n"
// TODO: These TOKENS are defined in `runtime_ccall.cpp`. A more verbose `--help` should include that list here.
" --track-allocation={none|user|all}, --track-allocation\n"
" Count bytes allocated by each source line\n\n"

Expand Down Expand Up @@ -437,12 +441,18 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
break;
case opt_code_coverage:
if (optarg != NULL) {
if (!strcmp(optarg,"user"))
size_t endof = strlen(optarg);
if (!strcmp(optarg, "user"))
codecov = JL_LOG_USER;
else if (!strcmp(optarg,"all"))
else if (!strcmp(optarg, "all"))
codecov = JL_LOG_ALL;
else if (!strcmp(optarg,"none"))
else if (!strcmp(optarg, "none"))
codecov = JL_LOG_NONE;
else if (endof > 5 && !strcmp(optarg + endof - 5, ".info")) {
if (codecov == JL_LOG_NONE)
codecov = JL_LOG_ALL;
jl_options.output_code_coverage = optarg;
}
else
jl_errorf("julia: invalid argument to --code-coverage (%s)", optarg);
break;
Expand Down
Loading

0 comments on commit 735d755

Please sign in to comment.