Skip to content

Commit

Permalink
[llvm-cov] Add lcov tracefile export format.
Browse files Browse the repository at this point in the history
Summary:
lcov tracefiles are used by various coverage reporting tools and build
systems (e.g., Bazel). It is a simple text-based format to parse and
more convenient to use than the JSON export format, which needs
additional processing to map regions/segments back to line numbers.

It's a little unfortunate that "text" format is now overloaded to refer
specifically to JSON for export, but I wanted to avoid making any
breaking changes to the UI of the llvm-cov tool at this time.

Patch by Tony Allevato (@allevato).

Reviewers: Dor1s, vsk

Reviewed By: Dor1s, vsk

Subscribers: mgorny, llvm-commits

Differential Revision: https://reviews.llvm.org/D54266

llvm-svn: 346506
  • Loading branch information
Dor1s committed Nov 9, 2018
1 parent 9cad24a commit b2091c9
Show file tree
Hide file tree
Showing 9 changed files with 270 additions and 13 deletions.
22 changes: 17 additions & 5 deletions llvm/docs/CommandGuide/llvm-cov.rst
Expand Up @@ -374,9 +374,15 @@ SYNOPSIS
DESCRIPTION
^^^^^^^^^^^

The :program:`llvm-cov export` command exports regions, functions, expansions,
and summaries of the coverage of the binaries *BIN*,... using the profile data
*PROFILE* as JSON. It can optionally be filtered to only export the coverage
The :program:`llvm-cov export` command exports coverage data of the binaries
*BIN*,... using the profile data *PROFILE* in either JSON or lcov trace file
format.

When exporting JSON, the regions, functions, expansions, and summaries of the
coverage data will be exported. When exporting an lcov trace file, the
line-based coverage and summaries will be exported.

The exported data can optionally be filtered to only export the coverage
for the files listed in *SOURCES*.

For information on compiling programs for coverage and generating profile data,
Expand All @@ -392,12 +398,18 @@ OPTIONS
universal binary or to use an architecture that does not match a
non-universal binary.

.. option:: -format=<FORMAT>

Use the specified output format. The supported formats are: "text" (JSON),
"lcov".

.. option:: -summary-only

Export only summary information for each file in the coverage data. This mode
will not export coverage information for smaller units such as individual
functions or regions. The result will be the same as produced by :program:
`llvm-cov report` command, but presented in JSON format rather than text.
functions or regions. The result will contain the same information as produced
by the :program:`llvm-cov report` command, but presented in JSON or lcov
format rather than text.

.. option:: -ignore-filename-regex=<PATTERN>

Expand Down
3 changes: 2 additions & 1 deletion llvm/docs/ReleaseNotes.rst
Expand Up @@ -40,7 +40,8 @@ Non-comprehensive list of changes in this release
functionality, or simply have a lot to talk about), see the `NOTE` below
for adding a new subsection.
* Note..
* The **llvm-cov** tool can now export lcov trace files using the
`-format=lcov` option of the `export` command.

.. NOTE
If you would like to document a larger change, then you can add a
Expand Down
38 changes: 38 additions & 0 deletions llvm/test/tools/llvm-cov/showLineExecutionCounts-lcov.test
@@ -0,0 +1,38 @@
// FULL: SF:{{.*}}showLineExecutionCounts.cpp
// FULL: FN:6,main
// FULL: FNDA:161,main
// FULL: FNF:1
// FULL: FNH:1
int main() { // FULL: DA:[[@LINE]],161
int x = 0; // FULL: DA:[[@LINE]],161
// FULL: DA:[[@LINE]],161
if (x) { // FULL: DA:[[@LINE]],161
x = 0; // FULL: DA:[[@LINE]],0
} else { // FULL: DA:[[@LINE]],161
x = 1; // FULL: DA:[[@LINE]],161
} // FULL: DA:[[@LINE]],161
// FULL: DA:[[@LINE]],161
for (int i = 0; i < 100; ++i) { // FULL: DA:[[@LINE]],16261
x = 1; // FULL: DA:[[@LINE]],16100
} // FULL: DA:[[@LINE]],16100
// FULL: DA:[[@LINE]],161
x = x < 10 ? x + 1 : x - 1; // FULL: DA:[[@LINE]],161
x = x > 10 ? // FULL: DA:[[@LINE]],161
x - 1: // FULL: DA:[[@LINE]],0
x + 1; // FULL: DA:[[@LINE]],161
// FULL: DA:[[@LINE]],161
return 0; // FULL: DA:[[@LINE]],161
} // FULL: DA:[[@LINE]],161
// FULL: LF:20
// FULL: LH:18
// FULL: end_of_record
// RUN: llvm-profdata merge %S/Inputs/lineExecutionCounts.proftext -o %t.profdata
// RUN: llvm-cov export -format=lcov %S/Inputs/lineExecutionCounts.covmapping -instr-profile %t.profdata %s | FileCheck -check-prefixes=FULL %s

// RUN: llvm-cov export -format=lcov -summary-only %S/Inputs/lineExecutionCounts.covmapping -instr-profile %t.profdata %s | FileCheck -check-prefixes=SUMMARYONLY %s
// SUMMARYONLY: SF:{{.*}}showLineExecutionCounts.cpp
// SUMMARYONLY: FNF:1
// SUMMARYONLY: FNH:1
// SUMMARYONLY: LF:20
// SUMMARYONLY: LH:18
// SUMMARYONLY: end_of_record
1 change: 1 addition & 0 deletions llvm/tools/llvm-cov/CMakeLists.txt
Expand Up @@ -5,6 +5,7 @@ add_llvm_tool(llvm-cov
gcov.cpp
CodeCoverage.cpp
CoverageExporterJson.cpp
CoverageExporterLcov.cpp
CoverageFilters.cpp
CoverageReport.cpp
CoverageSummaryInfo.cpp
Expand Down
47 changes: 41 additions & 6 deletions llvm/tools/llvm-cov/CodeCoverage.cpp
Expand Up @@ -14,6 +14,7 @@
//===----------------------------------------------------------------------===//

#include "CoverageExporterJson.h"
#include "CoverageExporterLcov.h"
#include "CoverageFilters.h"
#include "CoverageReport.h"
#include "CoverageSummaryInfo.h"
Expand Down Expand Up @@ -566,7 +567,9 @@ int CodeCoverageTool::run(Command Cmd, int argc, const char **argv) {
cl::values(clEnumValN(CoverageViewOptions::OutputFormat::Text, "text",
"Text output"),
clEnumValN(CoverageViewOptions::OutputFormat::HTML, "html",
"HTML output")),
"HTML output"),
clEnumValN(CoverageViewOptions::OutputFormat::Lcov, "lcov",
"lcov tracefile output")),
cl::init(CoverageViewOptions::OutputFormat::Text));

cl::opt<std::string> PathRemap(
Expand Down Expand Up @@ -674,6 +677,11 @@ int CodeCoverageTool::run(Command Cmd, int argc, const char **argv) {
errs() << "Color output cannot be disabled when generating html.\n";
ViewOpts.Colors = true;
break;
case CoverageViewOptions::OutputFormat::Lcov:
if (UseColor == cl::BOU_TRUE)
errs() << "Color output cannot be enabled when generating lcov.\n";
ViewOpts.Colors = false;
break;
}

// If path-equivalence was given and is a comma seperated pair then set
Expand Down Expand Up @@ -833,6 +841,11 @@ int CodeCoverageTool::doShow(int argc, const char **argv,
if (Err)
return Err;

if (ViewOpts.Format == CoverageViewOptions::OutputFormat::Lcov) {
error("Lcov format should be used with 'llvm-cov export'.");
return 1;
}

ViewOpts.ShowLineNumbers = true;
ViewOpts.ShowLineStats = ShowLineExecutionCounts.getNumOccurrences() != 0 ||
!ShowRegions || ShowBestLineRegionsCounts;
Expand Down Expand Up @@ -964,6 +977,9 @@ int CodeCoverageTool::doReport(int argc, const char **argv,
if (ViewOpts.Format == CoverageViewOptions::OutputFormat::HTML) {
error("HTML output for summary reports is not yet supported.");
return 1;
} else if (ViewOpts.Format == CoverageViewOptions::OutputFormat::Lcov) {
error("Lcov format should be used with 'llvm-cov export'.");
return 1;
}

auto Coverage = load();
Expand Down Expand Up @@ -995,8 +1011,10 @@ int CodeCoverageTool::doExport(int argc, const char **argv,
if (Err)
return Err;

if (ViewOpts.Format != CoverageViewOptions::OutputFormat::Text) {
error("Coverage data can only be exported as textual JSON.");
if (ViewOpts.Format != CoverageViewOptions::OutputFormat::Text &&
ViewOpts.Format != CoverageViewOptions::OutputFormat::Lcov) {
error("Coverage data can only be exported as textual JSON or an "
"lcov tracefile.");
return 1;
}

Expand All @@ -1006,12 +1024,29 @@ int CodeCoverageTool::doExport(int argc, const char **argv,
return 1;
}

auto Exporter = CoverageExporterJson(*Coverage.get(), ViewOpts, outs());
std::unique_ptr<CoverageExporter> Exporter;

switch (ViewOpts.Format) {
case CoverageViewOptions::OutputFormat::Text:
Exporter = llvm::make_unique<CoverageExporterJson>(*Coverage.get(),
ViewOpts, outs());
break;
case CoverageViewOptions::OutputFormat::HTML:
// Unreachable because we should have gracefully terminated with an error
// above.
llvm_unreachable("Export in HTML is not supported!");
case CoverageViewOptions::OutputFormat::Lcov:
Exporter = llvm::make_unique<CoverageExporterLcov>(*Coverage.get(),
ViewOpts, outs());
break;
default:
llvm_unreachable("Unknown coverage output format!");
}

if (SourceFiles.empty())
Exporter.renderRoot(IgnoreFilenameFilters);
Exporter->renderRoot(IgnoreFilenameFilters);
else
Exporter.renderRoot(SourceFiles);
Exporter->renderRoot(SourceFiles);

return 0;
}
Expand Down
125 changes: 125 additions & 0 deletions llvm/tools/llvm-cov/CoverageExporterLcov.cpp
@@ -0,0 +1,125 @@
//===- CoverageExporterLcov.cpp - Code coverage export --------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file implements export of code coverage data to lcov trace file format.
//
//===----------------------------------------------------------------------===//

//===----------------------------------------------------------------------===//
//
// The trace file code coverage export follows the following format (see also
// https://linux.die.net/man/1/geninfo). Each quoted string appears on its own
// line; the indentation shown here is only for documentation purposes.
//
// - for each source file:
// - "SF:<absolute path to source file>"
// - for each function:
// - "FN:<line number of function start>,<function name>"
// - for each function:
// - "FNDA:<execution count>,<function name>"
// - "FNF:<number of functions found>"
// - "FNH:<number of functions hit>"
// - for each instrumented line:
// - "DA:<line number>,<execution count>[,<checksum>]
// - "LH:<number of lines with non-zero execution count>"
// - "LF:<nubmer of instrumented lines>"
// - "end_of_record"
//
// If the user is exporting summary information only, then the FN, FNDA, and DA
// lines will not be present.
//
//===----------------------------------------------------------------------===//

#include "CoverageExporterLcov.h"
#include "CoverageReport.h"

using namespace llvm;

namespace {

void renderFunctionSummary(raw_ostream &OS,
const FileCoverageSummary &Summary) {
OS << "FNF:" << Summary.FunctionCoverage.getNumFunctions() << '\n'
<< "FNH:" << Summary.FunctionCoverage.getExecuted() << '\n';
}

void renderFunctions(
raw_ostream &OS,
const iterator_range<coverage::FunctionRecordIterator> &Functions) {
for (const auto &F : Functions) {
auto StartLine = F.CountedRegions.front().LineStart;
OS << "FN:" << StartLine << ',' << F.Name << '\n';
}
for (const auto &F : Functions)
OS << "FNDA:" << F.ExecutionCount << ',' << F.Name << '\n';
}

void renderLineExecutionCounts(raw_ostream &OS,
const coverage::CoverageData &FileCoverage) {
coverage::LineCoverageIterator LCI{FileCoverage, 1};
coverage::LineCoverageIterator LCIEnd = LCI.getEnd();
for (; LCI != LCIEnd; ++LCI) {
const coverage::LineCoverageStats &LCS = *LCI;
if (LCS.isMapped()) {
OS << "DA:" << LCS.getLine() << ',' << LCS.getExecutionCount() << '\n';
}
}
}

void renderLineSummary(raw_ostream &OS, const FileCoverageSummary &Summary) {
OS << "LF:" << Summary.LineCoverage.getNumLines() << '\n'
<< "LH:" << Summary.LineCoverage.getCovered() << '\n';
}

void renderFile(raw_ostream &OS, const coverage::CoverageMapping &Coverage,
const std::string &Filename,
const FileCoverageSummary &FileReport, bool ExportSummaryOnly) {
OS << "SF:" << Filename << '\n';

if (!ExportSummaryOnly) {
renderFunctions(OS, Coverage.getCoveredFunctions());
}
renderFunctionSummary(OS, FileReport);

if (!ExportSummaryOnly) {
// Calculate and render detailed coverage information for given file.
auto FileCoverage = Coverage.getCoverageForFile(Filename);
renderLineExecutionCounts(OS, FileCoverage);
}
renderLineSummary(OS, FileReport);

OS << "end_of_record\n";
}

void renderFiles(raw_ostream &OS, const coverage::CoverageMapping &Coverage,
ArrayRef<std::string> SourceFiles,
ArrayRef<FileCoverageSummary> FileReports,
bool ExportSummaryOnly) {
for (unsigned I = 0, E = SourceFiles.size(); I < E; ++I)
renderFile(OS, Coverage, SourceFiles[I], FileReports[I], ExportSummaryOnly);
}

} // end anonymous namespace

void CoverageExporterLcov::renderRoot(const CoverageFilters &IgnoreFilters) {
std::vector<std::string> SourceFiles;
for (StringRef SF : Coverage.getUniqueSourceFiles()) {
if (!IgnoreFilters.matchesFilename(SF))
SourceFiles.emplace_back(SF);
}
renderRoot(SourceFiles);
}

void CoverageExporterLcov::renderRoot(ArrayRef<std::string> SourceFiles) {
FileCoverageSummary Totals = FileCoverageSummary("Totals");
auto FileReports = CoverageReport::prepareFileReports(Coverage, Totals,
SourceFiles, Options);
renderFiles(OS, Coverage, SourceFiles, FileReports,
Options.ExportSummaryOnly);
}
36 changes: 36 additions & 0 deletions llvm/tools/llvm-cov/CoverageExporterLcov.h
@@ -0,0 +1,36 @@
//===- CoverageExporterLcov.h - Code coverage lcov exporter ---------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This class implements a code coverage exporter for lcov trace file format.
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_COV_COVERAGEEXPORTERLCOV_H
#define LLVM_COV_COVERAGEEXPORTERLCOV_H

#include "CoverageExporter.h"

namespace llvm {

class CoverageExporterLcov : public CoverageExporter {
public:
CoverageExporterLcov(const coverage::CoverageMapping &CoverageMapping,
const CoverageViewOptions &Options, raw_ostream &OS)
: CoverageExporter(CoverageMapping, Options, OS) {}

/// Render the CoverageMapping object.
void renderRoot(const CoverageFilters &IgnoreFilters) override;

/// Render the CoverageMapping object for specified source files.
void renderRoot(ArrayRef<std::string> SourceFiles) override;
};

} // end namespace llvm

#endif // LLVM_COV_COVERAGEEXPORTERLCOV_H
3 changes: 2 additions & 1 deletion llvm/tools/llvm-cov/CoverageViewOptions.h
Expand Up @@ -20,7 +20,8 @@ namespace llvm {
struct CoverageViewOptions {
enum class OutputFormat {
Text,
HTML
HTML,
Lcov
};

bool Debug;
Expand Down
8 changes: 8 additions & 0 deletions llvm/tools/llvm-cov/SourceCoverageView.cpp
Expand Up @@ -80,6 +80,10 @@ CoveragePrinter::create(const CoverageViewOptions &Opts) {
return llvm::make_unique<CoveragePrinterText>(Opts);
case CoverageViewOptions::OutputFormat::HTML:
return llvm::make_unique<CoveragePrinterHTML>(Opts);
case CoverageViewOptions::OutputFormat::Lcov:
// Unreachable because CodeCoverage.cpp should terminate with an error
// before we get here.
llvm_unreachable("Lcov format is not supported!");
}
llvm_unreachable("Unknown coverage output format!");
}
Expand Down Expand Up @@ -143,6 +147,10 @@ SourceCoverageView::create(StringRef SourceName, const MemoryBuffer &File,
case CoverageViewOptions::OutputFormat::HTML:
return llvm::make_unique<SourceCoverageViewHTML>(
SourceName, File, Options, std::move(CoverageInfo));
case CoverageViewOptions::OutputFormat::Lcov:
// Unreachable because CodeCoverage.cpp should terminate with an error
// before we get here.
llvm_unreachable("Lcov format is not supported!");
}
llvm_unreachable("Unknown coverage output format!");
}
Expand Down

0 comments on commit b2091c9

Please sign in to comment.