Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
9250bf3
TS-47380 Apply KotlinModuleMetadataTransformer explicitly
DreierF Aug 11, 2026
37b7448
TS-47380 Derive the shadow package prefix in logback configs at build…
DreierF Aug 11, 2026
c7448a8
TS-47380 Allow attaching a debugger to JVMs spawned by system tests
DreierF Aug 12, 2026
c34661a
TS-47380 Keep Teamscale credentials for the sample app out of version…
DreierF Aug 12, 2026
46ffe87
TS-47380 Give http-server-shutdown its own system under test
DreierF Aug 12, 2026
f122be4
TS-47380 Cover TS-23151 with a system test instead of a manual script
DreierF Aug 12, 2026
4255702
TS-47380 Remove the sample-app module
DreierF Aug 12, 2026
8d01fc8
TS-47380 Document how to debug the profiler
DreierF Aug 12, 2026
dbb155b
TS-47380 Declare the agent jar dependency in teamscaleAgent()
DreierF Aug 12, 2026
1239483
TS-47380 Make the build configuration cache and project isolation ready
DreierF Aug 12, 2026
a46f332
TS-47380 Drop the redundant dependsOn on the agent jar task
DreierF Aug 12, 2026
98d678e
TS-47380 Make the jlink tasks compatible with the configuration cache
DreierF Aug 12, 2026
ee265c1
TS-47380 Enable the configuration cache by default
DreierF Aug 12, 2026
101c41e
TS-47380 Publish to Maven Central through nmcp
DreierF Aug 12, 2026
4e688ff
TS-47380 Rename the sample-debugging-app module to sample-app
DreierF Aug 13, 2026
737338f
TS-47380 Fix build warnings
DreierF Aug 13, 2026
71552da
TS-47380 Improve debugger support
DreierF Aug 13, 2026
35b5f73
TS-47380 improve sample app setup
DreierF Aug 14, 2026
e322af6
TS-47380 Refined DEBUGGING instructions
DreierF Aug 14, 2026
7e0bbf1
TS-47380 Enable project isolation and swap out the jlink plugin
DreierF Aug 16, 2026
011bc9e
TS-47380 Rework
DreierF Aug 18, 2026
d283afa
TS-47380 Fix findings in the new build logic
DreierF Aug 18, 2026
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
2 changes: 1 addition & 1 deletion .github/workflows/actions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ jobs:
- name: Publish to Maven Central
if: startsWith(github.ref, 'refs/tags/v')
run: |
./gradlew publishMavenPublicationToSonatypeRepository closeAndReleaseSonatypeStagingRepository \
./gradlew publishAggregationToCentralPortal \
-Psigning.secretKeyRingFile=${{ github.workspace }}/.gnupg/secring.gpg \
-PgpgDirectory=${{ github.workspace }}/.gnupg \
-Psigning.password=${{ secrets.MAVEN_CENTRAL_GPG_SIGNATURE }} \
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@ report-generator/test-coverage-*.xml
**/maven-wrapper.jar
**/logTest/**
**/jacoco.exec
# local profiler configuration containing Teamscale credentials, see docs/DEBUGGING.md
java-profiler.local.properties
**/target/**
.claude/settings.local.json
4 changes: 2 additions & 2 deletions .idea/runConfigurations/SampleApp.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ We use [semantic versioning](http://semver.org/):
- PATCH version when you make backwards compatible bug fixes.

# Next version
- [breaking] _agent_: Log lines now show the simple class name (`INFO Agent - ...`) instead of the fully qualified one.

# 37.0.2
- [fix] _agent_: In some cases the hostname was wrongly added to the PID when sending it to Teamscale.
Expand Down
42 changes: 5 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,43 +107,11 @@ git config --local core.hooksPath .githooks

### Debug locally

For IntelliJ, there is a run config `SampleApp` which profiles the included `sample-debugging-app` and can be used to
debug the agent. This run config omits relocating packages in the shadow jar because with relocating, the package
names in the jar would not match with the ones IntelliJ knows from the code and so debugging would not work.
The agent is configured in the config file `sample-debugging-app/jacocoagent.properties`. By default, no upload
is configured but the file includes all required options to upload to Teamscale, they are just commented out.
Feel free to adapt it to your needs.
If you get an `IllegalStateException: Cannot process instrumented class com/example/Main`, make sure that you use
the built-in IntelliJ functionality for building and running instead of
gradle (IntelliJ Settings -> Build, Execution, Deployment -> Build Tools -> Gradle -> Build and run using: IntelliJ IDEA).

### Debugging the Gradle plugin

* increase the plugin version (=`appVersion`) in [build.gradle.kts](build.gradle.kts)
* `./gradlew publishToMavenLocal` will deploy your checked out version to your local m2 cache
* then you can import this version into any other gradle project by
* adding the following to the `settings.gradle.kts`
```kotlin
pluginManagement {
repositories {
mavenLocal()
gradlePluginPortal()
}
}

dependencyResolutionManagement {
repositories {
mavenLocal()
mavenCentral()
}
}
```
* declaring a plugin dependency on the incremented version of the teamscale plugin
* to debug a build that uses the plugin, run `./gradlew` with `--no-daemon -Dorg.gradle.debug=true`.
The build will pause and wait for you to attach a debugger, via IntelliJ's `Run > Attach to Process`.
* to debug the impacted test engine during a build, run `./gradlew` with `--no-daemon --debug-jvm` and wait for the test phase to start.
The build will pause and wait for you to attach a debugger, via IntelliJ's `Run > Attach to Process`.
* These two debug flags can also be combined. The build will then pause twice.
Debug the `SampleApp` run configuration in IntelliJ to debug the included `sample-app` with breakpoints in the agent working.

**[docs/DEBUGGING.md](docs/DEBUGGING.md) is the full guide**: setting up end-to-end communication with a real Teamscale
instance, how the agent's configuration is resolved from its five sources, where the profiler writes its logs, why a
misconfigured profiler fails silently, and how to attach a debugger to system tests.

### Contributing

Expand Down
49 changes: 41 additions & 8 deletions agent/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,33 @@ plugins {
com.teamscale.`java-convention`
application

// we don't want to cause conflicts between our dependencies and the target application
// we don't want to cause conflicts between the classes we ship and the target application
// since the agent will be loaded with the same class loader as the profiled application
// so we use the shadow plugin to relocate our dependencies
// so we use the shadow plugin to relocate our dependencies and our own classes
com.teamscale.`shadow-convention`
com.teamscale.coverage
com.teamscale.publish
com.teamscale.`logger-patch`
alias(libs.plugins.oci)
}

evaluationDependsOn(":installer")
// The jlink runtime image of the installer, which the distribution below ships alongside the agent.
val installerImageDependency = configurations.dependencyScope("installerImage")
val installerImage = configurations.resolvable("installerImagePath") {
extendsFrom(installerImageDependency.get())
}

dependencies {
installerImageDependency(project(":installer", JLINK_IMAGE_CONFIGURATION))
}

publishAs {
artifactId = "teamscale-jacoco-agent"
readableName = "Teamscale Java Profiler"
description = "JVM profiler that simplifies various aspects around recording and uploading test coverage"
}

val appVersion = rootProject.extra["appVersion"].toString()
val appVersion = extra["appVersion"].toString()
val jacocoVersion = libs.versions.jacoco.get()
val outputVersion = "$appVersion-jacoco-$jacocoVersion"

Expand Down Expand Up @@ -64,7 +72,7 @@ dependencies {
}

application {
mainClass = "com.teamscale.jacoco.agent.Main"
mainClass = "$AGENT_PACKAGE.Main"
}

tasks.shadowJar {
Expand All @@ -73,32 +81,57 @@ tasks.shadowJar {
// update
archiveFileName = "teamscale-jacoco-agent.jar"

// The shadow plugin's auto relocation only covers the dependencies, so the agent's own classes are
// relocated explicitly. The entry points below have to name them by their relocated names, and so do the
// logback configuration files, cf. ShadowedPackages.kt.
if (usesShadowedPackages.get()) {
relocate(AGENT_PACKAGE, "$SHADOW_PACKAGE_PREFIX.$AGENT_PACKAGE")
}

manifest {
attributes["Premain-Class"] = "com.teamscale.jacoco.agent.PreMain"
attributes["Premain-Class"] = shadowed("$AGENT_PACKAGE.PreMain")
attributes["Main-Class"] = shadowed("$AGENT_PACKAGE.Main")
}
}

tasks.startShadowScripts {
applicationName = "convert"
mainClass = shadowed("$AGENT_PACKAGE.Main")
}

// Shares the shaded agent jar with the projects that attach the profiler to a JVM, cf. the
// com.teamscale.agent-jar convention plugin.
configurations.consumable(AGENT_JAR_CONFIGURATION)
artifacts.add(AGENT_JAR_CONFIGURATION, tasks.shadowJar)

distributions {
named("shadow") {
distributionBaseName = "teamscale-jacoco-agent"
contents {
from(project(":installer").tasks["jlink"]) {
from(installerImage.get()) {
into("installer")
}

// Captured in a local so the copy action does not reference the build script itself,
// which cannot be stored in the configuration cache.
val distributionVersion = outputVersion
filesMatching("**/VERSION.txt") {
filter {
it.replace("%APP_VERSION_TOKEN_REPLACED_DURING_BUILD%", outputVersion)
it.replace("%APP_VERSION_TOKEN_REPLACED_DURING_BUILD%", distributionVersion)
}
}
}
}
}

// The logging templates in src/dist are checked in without the shadow prefix so they also work when
// relocation is disabled. Add the prefix while packaging them, since the distribution ships the shaded agent.
listOf(tasks.shadowDistZip, tasks.shadowDistTar, tasks.installShadowDist).forEach { task ->
task { shadowLoggingPackages(usesShadowedPackages) }
}

verifyShadowedLoggingConfigs(tasks.shadowJar, tasks.shadowDistZip)

tasks.shadowDistZip {
archiveFileName = "teamscale-jacoco-agent.zip"
}
Expand Down
7 changes: 2 additions & 5 deletions agent/src/dist/logging/logback.console.xml
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="CONSOLE" class="shadow.ch.qos.logback.core.ConsoleAppender">
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%date{yyyy-MM-dd HH:mm:ss} %-5level %logger{35} - %msg%n</pattern>
<pattern>%date{yyyy-MM-dd HH:mm:ss} %-5level %logger{0} - %msg%n</pattern>
</encoder>
</appender>

<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>

<logger name="shadow.org.apache.spark" level="warn" />
<logger name="shadow.org.eclipse.jetty" level="warn" />
</configuration>
15 changes: 6 additions & 9 deletions agent/src/dist/logging/logback.debug.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,34 @@
<configuration>
<define name="defaultLogDir" class="com.teamscale.jacoco.agent.logging.LogDirectoryPropertyDefiner" />

<appender name="CONSOLE" class="shadow.ch.qos.logback.core.ConsoleAppender">
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%date{yyyy-MM-dd HH:mm:ss} %-5level %logger{35} - %msg%n</pattern>
<pattern>%date{yyyy-MM-dd HH:mm:ss} %-5level %logger{0} - %msg%n</pattern>
</encoder>
</appender>

<appender name="RollingFile" class="shadow.ch.qos.logback.core.rolling.RollingFileAppender">
<appender name="RollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- TODO: adjust the path where the log file should be written -->
<file>${defaultLogDir}/teamscale-jacoco-agent.log</file>

<rollingPolicy class="shadow.ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<!-- TODO: adjust the path where the log file should be written -->
<fileNamePattern>${defaultLogDir}/teamscale-jacoco-agent-%i.log.zip</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>10</maxIndex>
</rollingPolicy>

<triggeringPolicy class="shadow.ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>1MB</maxFileSize>
</triggeringPolicy>

<encoder>
<pattern>%date{yyyy-MM-dd HH:mm:ss} %-5level %logger{35} - %msg%n</pattern>
<pattern>%date{yyyy-MM-dd HH:mm:ss} %-5level %logger{0} - %msg%n</pattern>
</encoder>
</appender>

<root level="DEBUG">
<appender-ref ref="RollingFile"/>
<appender-ref ref="CONSOLE"/>
</root>

<logger name="shadow.org.apache.spark" level="warn" />
<logger name="shadow.org.eclipse.jetty" level="warn" />
</configuration>
11 changes: 4 additions & 7 deletions agent/src/dist/logging/logback.rolling-file.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,27 @@
<configuration>
<define name="defaultLogDir" class="com.teamscale.jacoco.agent.logging.LogDirectoryPropertyDefiner"/>

<appender name="RollingFile" class="shadow.ch.qos.logback.core.rolling.RollingFileAppender">
<appender name="RollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- TODO: adjust the path where the log file should be written -->
<file>${defaultLogDir}/teamscale-jacoco-agent.log</file>

<rollingPolicy class="shadow.ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<!-- TODO: adjust the path where the log file should be written -->
<fileNamePattern>${defaultLogDir}/teamscale-jacoco-agent-%i.log.zip</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>10</maxIndex>
</rollingPolicy>

<triggeringPolicy class="shadow.ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>1MB</maxFileSize>
</triggeringPolicy>

<encoder>
<pattern>%date{yyyy-MM-dd HH:mm:ss} %-5level %logger{35} - %msg%n</pattern>
<pattern>%date{yyyy-MM-dd HH:mm:ss} %-5level %logger{0} - %msg%n</pattern>
</encoder>
</appender>

<root level="INFO">
<appender-ref ref="RollingFile"/>
</root>

<logger name="shadow.org.apache.spark" level="warn" />
<logger name="shadow.org.eclipse.jetty" level="warn" />
</configuration>
2 changes: 1 addition & 1 deletion agent/src/main/kotlin/com/teamscale/jacoco/agent/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ object Main {

/** Creates a builder for a [com.beust.jcommander.JCommander] object. */
private fun createJCommanderBuilder() =
JCommander.newBuilder().programName(Main::class.java.getName())
JCommander.newBuilder().programName("convert")
.addObject(defaultArguments)
.addObject(command)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,34 @@
<configuration>
<define name="defaultLogDir" class="com.teamscale.jacoco.agent.logging.DebugLogDirectoryPropertyDefiner" />

<appender name="CONSOLE" class="shadow.ch.qos.logback.core.ConsoleAppender">
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%date{yyyy-MM-dd HH:mm:ss} %-5level %logger{35} - %msg%n</pattern>
<pattern>%date{yyyy-MM-dd HH:mm:ss} %-5level %logger{0} - %msg%n</pattern>
</encoder>
</appender>

<appender name="RollingFile" class="shadow.ch.qos.logback.core.rolling.RollingFileAppender">
<appender name="RollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- TODO: adjust the path where the log file should be written -->
<file>${defaultLogDir}/teamscale-jacoco-agent.log</file>

<rollingPolicy class="shadow.ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<!-- TODO: adjust the path where the log file should be written -->
<fileNamePattern>${defaultLogDir}/teamscale-jacoco-agent-%i.log.zip</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>10</maxIndex>
</rollingPolicy>

<triggeringPolicy class="shadow.ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>1MB</maxFileSize>
</triggeringPolicy>

<encoder>
<pattern>%date{yyyy-MM-dd HH:mm:ss} %-5level %logger{35} - %msg%n</pattern>
<pattern>%date{yyyy-MM-dd HH:mm:ss} %-5level %logger{0} - %msg%n</pattern>
</encoder>
</appender>

<root level="DEBUG">
<appender-ref ref="RollingFile"/>
<appender-ref ref="CONSOLE"/>
</root>

<logger name="shadow.org.apache.spark" level="warn" />
<logger name="shadow.org.eclipse.jetty" level="warn" />
</configuration>
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,26 @@
<configuration>
<define name="defaultLogDir" class="com.teamscale.jacoco.agent.logging.LogDirectoryPropertyDefiner" />

<appender name="RollingFile" class="shadow.ch.qos.logback.core.rolling.RollingFileAppender">
<appender name="RollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- We write to a file that is located in a folder provided via the context -->
<file>${defaultLogDir}/teamscale-jacoco-agent.log</file>

<rollingPolicy class="shadow.ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>${defaultLogDir}/teamscale-jacoco-agent-%i.log.zip</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>10</maxIndex>
</rollingPolicy>

<triggeringPolicy class="shadow.ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>1MB</maxFileSize>
</triggeringPolicy>

<encoder>
<pattern>%date{yyyy-MM-dd HH:mm:ss} %-5level %logger{35} - %msg%n</pattern>
<pattern>%date{yyyy-MM-dd HH:mm:ss} %-5level %logger{0} - %msg%n</pattern>
</encoder>
</appender>

<root level="INFO">
<appender-ref ref="RollingFile"/>
</root>

<logger name="shadow.org.apache.spark" level="warn" />
<logger name="shadow.org.eclipse.jetty" level="warn" />
</configuration>
Loading
Loading