Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ During compilation, `gcc` will generate a `.gcno` file next to every `.o` file.
This vscode extension uses the `gcov` program to parse these additional files and displays the information on top of your source files. For this to work, it has to find the generated `.gcda` for a given project. Those are usually in your build directory. If the extension does not find them on its own, you have to edit the `gcovViewer.buildDirectories` setting of your workspace folder. The most convenient way to do this is to use the `Gcov Viewer: Select Build Directory` command.

## Troubleshooting

- Try passing `-fprofile-abs-path` to gcc. This helps the extension to match source files with their corresponding coverage data.
- Each of Methods can help the extension to match relative-path source files with their corresponding coverage data:
- Configuring `gcovViewer.buildDirectories` as build root directories correctly.
- Try passing `-fprofile-abs-path` to gcc.

## Known Issues

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "gcov-viewer",
"displayName": "Gcov Viewer",
"description": "Decorate C/C++ source files with code coverage information generated by gcov.",
"version": "0.4.0",
"version": "0.5.0",
"license": "MIT",
"publisher": "JacquesLucke",
"author": {
Expand Down
15 changes: 10 additions & 5 deletions src/coverageCache.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { loadGcovData, GcovFileData } from './gcovInterface';

import { resolve } from 'path';
/**
* Cache for all data loaded using gcov. This way we don't have to reload
* it everytime the user looks at a new file.
Expand All @@ -9,15 +9,20 @@ export class CoverageCache {
demangledNames: Map<string, string> = new Map();
loadedGcdaFiles: string[] = [];

async loadGcdaFiles(gcdaPaths: string[]) {
const gcovDataArray = await loadGcovData(gcdaPaths);
async loadGcdaFiles(buildDirectory: string, gcdaPaths: string[]) {
const gcovDataArray = await loadGcovData(buildDirectory, gcdaPaths);

for (const gcovData of gcovDataArray) {
for (const fileData of gcovData.files) {
const cachedFileData = this.dataByFile.get(fileData.file);
if (cachedFileData === undefined) {
this.dataByFile.set(fileData.file, {
file: fileData.file,
let realpath = fileData.file;
if (!realpath.startsWith("/")) { /* relative path to absolute path */
realpath = resolve(buildDirectory, realpath)
}

this.dataByFile.set(realpath, {
file: realpath,
lines: [...fileData.lines],
functions: [...fileData.functions],
});
Expand Down
54 changes: 42 additions & 12 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,22 +88,46 @@ function getBuildDirectories(): string[] {
return buildDirectories;
}

async function getGcdaPaths(progress?: MyProgress, token?: vscode.CancellationToken) {
class GcdaPathsGroup {
buildDirectory: string = "";
gcdaPaths: string[] = [];
};

async function getGcdaPathsGroups(progress?: MyProgress, token?: vscode.CancellationToken) {
progress?.report({ message: 'Searching .gcda files' });
const buildDirectories = getBuildDirectories();

let counter = 0;
const gcdaPaths: Set<string> = new Set();
const gcdaPathsGroups: Set<GcdaPathsGroup> = new Set();
for (const buildDirectory of buildDirectories) {
const gcdaPaths: Set<string> = new Set();
await findAllFilesRecursively(buildDirectory, path => {
if (path.endsWith('.gcda')) {
gcdaPaths.add(path);
}
counter++;
progress?.report({ message: `[${counter}] Scanning (found ${gcdaPaths.size}): ${path}` });
}, token);

if (gcdaPaths.size === 0) {
continue;
}
gcdaPathsGroups.add({buildDirectory : buildDirectory, gcdaPaths : Array.from(gcdaPaths)});
}

return Array.from(gcdaPathsGroups);
}

async function getGcdaPaths(progress?: MyProgress, token?: vscode.CancellationToken) {
let gcdaPathsGroups: GcdaPathsGroup[] = await getGcdaPathsGroups(progress, token);
const gcdaPaths: Set<string> = new Set();

for (const gcdaPathsGroup of gcdaPathsGroups) {
for (const path of gcdaPathsGroup.gcdaPaths) {
gcdaPaths.add(path);
}
}

return Array.from(gcdaPaths);
}

Expand All @@ -112,6 +136,7 @@ let coverageCache = new CoverageCache();
type MyProgress = vscode.Progress<{ message?: string; increment?: number }>;

async function reloadCoverageDataFromPaths(
buildDirectory: string,
paths: string[], totalPaths: number,
progress: MyProgress,
token: vscode.CancellationToken) {
Expand All @@ -124,7 +149,7 @@ async function reloadCoverageDataFromPaths(
return;
}

await coverageCache.loadGcdaFiles(pathsChunk);
await coverageCache.loadGcdaFiles(buildDirectory, pathsChunk);

progress.report({
increment: 100 * pathsChunk.length / totalPaths,
Expand Down Expand Up @@ -164,22 +189,27 @@ async function reloadGcdaFiles() {
coverageCache = new CoverageCache();
progress.report({ increment: 0 });

const gcdaPaths = await getGcdaPaths(progress, token);
if (gcdaPaths.length === 0) {
const gcdaPathsGroups: GcdaPathsGroup[] = await getGcdaPathsGroups(progress, token);
if (gcdaPathsGroups.length === 0) {
showNoFilesFoundMessage();
return;
}

/* Shuffle paths make the processing time of the individual chunks more similar. */
shuffleArray(gcdaPaths);
const pathChunks = splitArrayInChunks(gcdaPaths, os.cpus().length);

/* Process chunks asynchronously, so that gcov is invoked multiple times in parallel. */
const promises = [];
for (const pathChunk of pathChunks) {
promises.push(reloadCoverageDataFromPaths(
pathChunk, gcdaPaths.length, progress, token));

for (const gcdaPathsGroup of gcdaPathsGroups) {
/* Shuffle paths make the processing time of the individual chunks more similar. */
shuffleArray(gcdaPathsGroup.gcdaPaths);
const pathChunks = splitArrayInChunks(gcdaPathsGroup.gcdaPaths, os.cpus().length);


for (const pathChunk of pathChunks) {
promises.push(reloadCoverageDataFromPaths(gcdaPathsGroup.buildDirectory,
pathChunk, gcdaPathsGroup.gcdaPaths.length, progress, token));
}
}

await Promise.all(promises);
}
);
Expand Down
5 changes: 3 additions & 2 deletions src/gcovInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export async function isGcovCompatible() {
});
}

export async function loadGcovData(paths: string[]): Promise<GcovData[]> {
export async function loadGcovData(buildDirectory: string, paths: string[]): Promise<GcovData[]> {
if (paths.length === 0) {
return [];
}
Expand All @@ -70,7 +70,8 @@ export async function loadGcovData(paths: string[]): Promise<GcovData[]> {
command += ` "${path}"`;
}
return new Promise<GcovData[]>((resolve, reject) => {
child_process.exec(command, { maxBuffer: 256 * 1024 * 1024 }, (err, stdout, stderr) => {
child_process.exec(command, { maxBuffer: 256 * 1024 * 1024, cwd : buildDirectory },
(err, stdout, stderr) => {
if (err) {
console.error(`exec error: ${err}`);
reject();
Expand Down