Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import datadog.instrument.utils.ClassLoaderValue;
import datadog.metrics.api.statsd.StatsDClientManager;
import datadog.trace.api.Config;
import datadog.trace.api.InstrumenterConfig;
import datadog.trace.api.Platform;
import datadog.trace.api.WithGlobalTracer;
import datadog.trace.api.appsec.AppSecEventTracker;
Expand Down Expand Up @@ -336,6 +337,11 @@ public static void start(
StaticEventLogger.end("crashtracking");
}

Object codeCoverageTransformer = null;
if (InstrumenterConfig.get().isCodeCoverageEnabled()) {
codeCoverageTransformer = maybeStartCodeCoverage(inst);
}

startDatadogAgent(initTelemetry, inst);

final EnumSet<Library> libraries = detectLibraries(log);
Expand Down Expand Up @@ -390,7 +396,8 @@ public static void start(
}

InstallDatadogTracerCallback installDatadogTracerCallback =
new InstallDatadogTracerCallback(initTelemetry, inst, okHttpDelayMillis);
new InstallDatadogTracerCallback(
initTelemetry, inst, okHttpDelayMillis, codeCoverageTransformer);
if (waitForJUL) {
log.debug("Custom logger detected. Delaying Datadog Tracer initialization.");
registerLogManagerCallback(installDatadogTracerCallback);
Expand Down Expand Up @@ -645,11 +652,14 @@ protected static class InstallDatadogTracerCallback extends ClassLoadCallBack {
private final Object sco;
private final Class<?> scoClass;
private final int okHttpDelayMillis;
private final Object codeCoverageTransformer;

public InstallDatadogTracerCallback(
InitializationTelemetry initTelemetry,
Instrumentation instrumentation,
int okHttpDelayMillis) {
int okHttpDelayMillis,
Object codeCoverageTransformer) {
this.codeCoverageTransformer = codeCoverageTransformer;
this.okHttpDelayMillis = okHttpDelayMillis;
this.instrumentation = instrumentation;
try {
Expand Down Expand Up @@ -696,6 +706,10 @@ public void execute() {
if (flareEnabled) {
startFlarePoller(scoClass, sco);
}

if (codeCoverageTransformer != null) {
startCodeCoverageCollector(codeCoverageTransformer, sco);
}
}

private void resumeRemoteComponents() {
Expand Down Expand Up @@ -1124,6 +1138,34 @@ private static void maybeStartCiVisibility(Instrumentation inst, Class<?> scoCla
}
}

private static Object maybeStartCodeCoverage(Instrumentation inst) {
StaticEventLogger.begin("Code Coverage");

try {
final Class<?> systemClass =
AGENT_CLASSLOADER.loadClass("datadog.trace.codecoverage.CodeCoverageSystem");
final Method startMethod = systemClass.getMethod("start", Instrumentation.class);
return startMethod.invoke(null, inst);
} catch (final Throwable e) {
log.warn("Not starting Code Coverage subsystem", e);
return null;
} finally {
StaticEventLogger.end("Code Coverage");
}
}

private static void startCodeCoverageCollector(Object transformer, Object sco) {
try {
final Class<?> systemClass =
AGENT_CLASSLOADER.loadClass("datadog.trace.codecoverage.CodeCoverageSystem");
final Method startCollectorMethod =
systemClass.getMethod("startCollector", Object.class, Object.class);
startCollectorMethod.invoke(null, transformer, sco);
} catch (final Throwable e) {
log.warn("Not starting Code Coverage collector", e);
}
}

private static void maybeStartLLMObs(Instrumentation inst, Class<?> scoClass, Object sco) {
if (llmObsEnabled) {
StaticEventLogger.begin("LLM Observability");
Expand Down
1 change: 1 addition & 0 deletions dd-java-agent/agent-ci-visibility/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ dependencies {
implementation project(':components:json')
implementation project(':internal-api')
implementation project(':internal-api:internal-api-9')
implementation project(':utils:coverage-utils')

testImplementation project(':dd-java-agent:testing')
testImplementation("com.google.jimfs:jimfs:1.1") // an in-memory file system for testing code that works with files
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@
import datadog.trace.civisibility.coverage.SkippableAwareCoverageStoreFactory;
import datadog.trace.civisibility.coverage.file.FileCoverageStore;
import datadog.trace.civisibility.coverage.line.LineCoverageStore;
import datadog.communication.http.OkHttpUtils;
import datadog.trace.api.civisibility.telemetry.CiVisibilityCountMetric;
import datadog.trace.api.civisibility.telemetry.CiVisibilityDistributionMetric;
import datadog.trace.civisibility.communication.TelemetryListener;
import datadog.trace.civisibility.coverage.report.CoverageProcessor;
import datadog.trace.civisibility.coverage.report.CoverageReportUploader;
import datadog.trace.civisibility.coverage.report.JacocoCoverageProcessor;
import datadog.trace.coverage.CoverageReportUploader;
import datadog.trace.civisibility.coverage.report.child.ChildProcessCoverageReporter;
import datadog.trace.civisibility.coverage.report.child.JacocoChildProcessCoverageReporter;
import datadog.trace.civisibility.domain.buildsystem.ModuleSignalRouter;
Expand All @@ -34,11 +38,20 @@ static class Parent {

ExecutionSettings executionSettings =
repoServices.executionSettingsFactory.create(JvmInfo.CURRENT_JVM, null);
CoverageReportUploader coverageReportUploader =
executionSettings.isCodeCoverageReportUploadEnabled()
? new CoverageReportUploader(
services.ciIntake, repoServices.ciTags, services.metricCollector)
: null;
CoverageReportUploader coverageReportUploader;
if (executionSettings.isCodeCoverageReportUploadEnabled()) {
OkHttpUtils.CustomListener telemetryListener =
new TelemetryListener.Builder(services.metricCollector)
.requestCount(CiVisibilityCountMetric.COVERAGE_UPLOAD_REQUEST)
.requestBytes(CiVisibilityDistributionMetric.COVERAGE_UPLOAD_REQUEST_BYTES)
.requestErrors(CiVisibilityCountMetric.COVERAGE_UPLOAD_REQUEST_ERRORS)
.requestDuration(CiVisibilityDistributionMetric.COVERAGE_UPLOAD_REQUEST_MS)
.build();
coverageReportUploader =
new CoverageReportUploader(services.ciIntake, repoServices.ciTags, telemetryListener);
} else {
coverageReportUploader = null;
}

coverageProcessorFactory =
new JacocoCoverageProcessor.Factory(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
import datadog.trace.api.civisibility.domain.SourceSet;
import datadog.trace.civisibility.config.ExecutionSettings;
import datadog.trace.civisibility.domain.buildsystem.ModuleSignalRouter;
import datadog.trace.coverage.CoverageReportUploader;
import datadog.trace.coverage.LcovReportWriter;
import datadog.trace.coverage.LinesCoverage;
import datadog.trace.civisibility.ipc.AckResponse;
import datadog.trace.civisibility.ipc.ModuleCoverageDataJacoco;
import datadog.trace.civisibility.ipc.SignalResponse;
Expand Down
31 changes: 31 additions & 0 deletions dd-java-agent/agent-code-coverage/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar

plugins {
id 'com.gradleup.shadow'
}

apply from: "$rootDir/gradle/java.gradle"
apply from: "$rootDir/gradle/version.gradle"

minimumBranchCoverage = 0.0
minimumInstructionCoverage = 0.0

dependencies {
api libs.slf4j

implementation group: 'org.jacoco', name: 'org.jacoco.core', version: '0.8.14'

implementation project(':internal-api')
implementation project(':communication')
implementation project(':utils:coverage-utils')

testImplementation project(':dd-java-agent:testing')
}

tasks.named("shadowJar", ShadowJar) {
dependencies deps.excludeShared
}

tasks.named("jar", Jar) {
archiveClassifier = 'unbundled'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package datadog.trace.codecoverage;

import java.util.BitSet;

/**
* Cached mapping from probe IDs to source lines for a single class. Built once per class version
* (identified by CRC64) and reused across collection cycles.
*/
final class ClassProbeMapping {
final long classId;
final String className; // "com/example/MyClass"
final String sourceFile; // "SourceFile.java"
final BitSet executableLines;
final int[][] probeToLines; // probeToLines[probeId] = sorted line numbers

ClassProbeMapping(
long classId,
String className,
String sourceFile,
BitSet executableLines,
int[][] probeToLines) {
this.classId = classId;
this.className = className;
this.sourceFile = sourceFile;
this.executableLines = executableLines;
this.probeToLines = probeToLines;
}
}
Loading
Loading