Skip to content

Repository files navigation

Java-Rust Glowroot Agent

English | Turkish

CI Release

Bounded, Rust-first telemetry for Rust-Java REST applications and Spring Boot services. It sends HTTP aggregates when an HTTP adapter exists, plus process/JVM gauges, explicit SQL timings, errors, optional bounded traces, and native Dubbo/Redis timings to an existing Glowroot Central collector.

Your controllers, handlers, services, validation, and database code do not change. The agent does not weave bytecode and does not install Byte Buddy, ASM, Java gRPC, Netty, or a Java executor.

Contents

Choose Your Runtime

Application Add to the application Native runtime Extra telemetry thread
Rust-Java REST 4.5.6 No starter is required Uses the framework's rust_hyper library 1 when enabled
Spring Boot 3.x, MVC java-rust-glowroot-spring-boot-starter:0.4.1 Loads the small standalone agent library and the matching optional server adapter 1
Spring Boot 3.x, non-web Minimum: java-rust-glowroot-spring-runtime:0.4.1; the umbrella starter also works Loads only the web-independent standalone agent library in the minimum setup 1
Spring Boot 3.x, WebFlux java-rust-glowroot-spring-webflux-adapter:0.4.1 Uses the same standalone native runtime 1
Either runtime with -javaagent syntax Add the one-class java-rust-glowroot-agent:0.4.1 bootstrap Same runtime as the row above Same single exporter; bootstrap adds none

The bootstrap JAR only maps -javaagent:key=value arguments to properties. It contains one class, no native binary, no transformer, and no runtime dependency. The Spring starter is a separate JAR so Spring classes never cross the executable-JAR classloader boundary.

The existing Glowroot collector, UI, and database stay unchanged.

Compatibility boundary: runtime profile switching requires REST native ABI 29 and Glowroot ABI 3. Use agent 0.4.1 with Rust-Java REST 4.5.6. Do not copy DLL/SO files from an older package.

Sample Projects

The samples do not enable telemetry by default. This keeps the normal quick start independent of a Glowroot Central deployment.

Sample Agent path What to expect
rest-sample-cache-reader Embedded Rust-Java REST runtime Enable through properties; HTTP and native Redis read aggregates are available
rest-sample-dubbo-consumer Embedded Rust-Java REST runtime Enable through properties; HTTP and native Dubbo aggregates are available
rest-sample-cache-writer Plain Java scheduler Release 0.4.1 does not provide a standalone plain-Java runtime; bootstrap alone is insufficient
rest-sample-dubbo-provider Plain Java Dubbo/Netty provider Release 0.4.1 does not instrument the official Java provider runtime

Do not add Spring Boot or a REST server to a plain-Java sample only to obtain telemetry. If an application already uses Spring Boot for a real application requirement, use the non-web starter. Otherwise keep the smaller runtime and use platform-level metrics until a dedicated standalone runtime is available.

The reader and consumer READMEs contain copy-paste local and Kubernetes examples. For a new production deployment, align them to Rust-Java REST 4.5.6 before enabling agent 0.4.1.

What You Get

Signal Behavior
HTTP count and duration Bounded weighted sampling by normalized route pattern
HTTP 5xx Counted exactly, even when successful requests are sampled
Slow/error trace Optional bounded queue; disabled by default
Rust-native Dubbo Aggregate count, duration, and errors
Rust-native Redis Separate read/write count, duration, and errors
Process gauges RSS and thread count once per export interval
JVM gauges Optional heap, non-heap, memory-pool, GC count, and GC time gauges
SQL aggregates Optional explicit, bounded operation/statement timing; no JDBC proxy or bytecode weaving
Error stacks Optional bounded stack capture for failed HTTP requests and explicit SQL operations
On-demand diagnostics Optional thread dump, heap histogram, or heap dump in the short-lived diagnostic profile
Export health Connect, reconnect, failure, drop, and last-error counters

Request bodies, query values, headers, SQL text, and personal data are not copied into telemetry.

Where The Work Runs

The heavy agent work belongs to Rust. Java is only the event boundary for information that exists inside Spring or the JVM.

Surface Owner What happens
Aggregation and export Rust Bounded route/SQL state, sampling totals, queues, protobuf encoding, collector HTTP/2 transport, reconnect, timeout, and drop policy on one low-priority batch exporter
JVM gauges Rust The isolated exporter discovers and owns JNI global references, invokes the selected MXBeans, aggregates values, and builds the gauge message
Error detail capture Rust A failed Java request hands off only a bounded weak Throwable reference. The isolated exporter reads class, message, and stack frames later; the application request thread never walks a stack trace
Diagnostics Rust The command queue, JNI calls, bounded orchestration, file creation, atomic publication, failure cleanup, and counters stay in Rust
Profile lifecycle Rust Optional state is allocated, retired, dropped, and optionally trimmed outside Hyper and application workers
Optional Spring MVC edge Java, constant-time only The adapter selected by the application's existing server records completion: Tomcat Valve, Jetty RequestLog, or Undertow completion listener. It passes normalized route, status, duration, and an optional failure reference to Rust
Optional Spring WebFlux edge Java, bounded reactive callback The separate WebFlux module keeps only the state required by the reactive lifecycle, reads the normalized route at commit, and passes the bounded event to Rust. It does not add or select Reactor Netty
JVM internals JVM, invoked by Rust MXBean and dump APIs still execute inside the JVM because that is where the data exists; no Java helper, polling thread, cache, or direct-buffer callback is used

The native lifecycle is independent of Spring Web. A database worker, Kafka application, scheduler, or command-line Spring Boot service can therefore export process and profile-specific JVM/SQL data without adding MVC, a Servlet container, or a Java telemetry executor.

Rust-Java REST HTTP telemetry is already recorded directly in the Rust server. Spring Boot Servlet applications use a direct completion hook for their existing server. The umbrella starter contains only small internal adapter JARs; each server API is provided and optional, so the agent neither adds nor selects Tomcat, Jetty, or Undertow. The dominant unsampled success returns before route lookup, request attributes, or JNI. Sampled, slow, failed, asynchronous, and not-found requests use the same bounded event contract on all supported engines. On an error, Java does not call Throwable.getMessage() or Throwable.getStackTrace(). Rust queues a weak JNI reference under the same hard trace-capacity limit and resolves the detail on the isolated exporter. A collected weak reference or full queue drops only that optional detail and increments the drop counter; business request completion is never blocked and an arbitrary exception object graph is never retained. This hand-off is absent in micro and jvm, and is also absent when error-trace capacity is zero.

This is intentionally not a replacement for every full Glowroot feature. It does not weave arbitrary Java methods, wrap every JDBC object, run a profiler, capture logs, or accept remote instrumentation. Use the full Glowroot agent when those features are required. WebFlux route telemetry is deliberately packaged in a separate optional artifact. A Servlet application therefore does not receive spring-webflux or Reactor Netty, and a WebFlux application does not receive MVC or a Servlet engine from the agent.

Rust-Java REST Setup

Use the coordinated 4.5.6 framework line. It contains Glowroot native ABI 3 and validates the native provenance before the HTTP server starts.

<dependency>
  <groupId>com.reactor</groupId>
  <artifactId>rust-java-rest</artifactId>
  <version>4.5.6</version>
</dependency>

Add these values to rust-spring.properties:

reactor.application.name=catalog-api
reactor.glowroot.enabled=true
reactor.glowroot.profile=micro
reactor.glowroot.collector.address=http://glowroot-collector:8181
reactor.glowroot.agent.id=catalog::local
reactor.glowroot.application.name=catalog-api
reactor.glowroot.http.sample-rate=256
reactor.glowroot.trace.capacity=0

Start the application normally. No agent JAR is required:

java -jar catalog-api.jar

If your platform requires -javaagent syntax, use the optional bootstrap JAR. It changes only how configuration reaches the same embedded Rust engine:

java \
  -javaagent:/opt/agent/java-rust-glowroot-agent-0.4.1.jar=collector=http://glowroot-collector:8181,agent-id=catalog::pod-1,application=catalog-api \
  -jar catalog-api.jar

Spring Boot Setup

1. Add the starter

<dependency>
  <groupId>com.reactor</groupId>
  <artifactId>java-rust-glowroot-spring-boot-starter</artifactId>
  <version>0.4.1</version>
</dependency>

The starter is opt-in. Add these values to application.properties:

reactor.glowroot.enabled=true
reactor.glowroot.collector.address=http://127.0.0.1:8181
reactor.glowroot.agent.id=orders::local
reactor.glowroot.application.name=orders-api
reactor.glowroot.http.sample-rate=256
reactor.glowroot.trace.capacity=0

Then run the existing Spring Boot application:

java -jar orders-api.jar

The application still chooses its embedded server. Auto-configuration detects the server already present in the application and activates exactly one direct adapter. Every server API remains provided and optional inside its adapter module. The umbrella starter therefore adds no server engine and does not change Spring Boot's server selection.

Application runtime Agent HTTP path Server dependency added by agent Feature set
Spring MVC + Tomcat Direct context Valve (tomcat-valve) None Sync, async, mapped status, exception, 404, bounded route and slow/error telemetry
Spring MVC + Jetty Direct completion RequestLog (jetty-request-log) None Same feature set as Tomcat
Spring MVC + Undertow Direct exchange completion listener (undertow-completion-listener) None Same feature set as Tomcat
Spring WebFlux Separate optional WebFilter None Same HTTP lifecycle contract; tested with Reactor Netty

For Jetty, the application owns this dependency choice:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <exclusions>
    <exclusion>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-tomcat</artifactId>
    </exclusion>
  </exclusions>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<dependency>
  <groupId>com.reactor</groupId>
  <artifactId>java-rust-glowroot-spring-boot-starter</artifactId>
  <version>0.4.1</version>
</dependency>

For Undertow, keep the same exclusion and select Undertow instead:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <exclusions>
    <exclusion>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-tomcat</artifactId>
    </exclusion>
  </exclusions>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<dependency>
  <groupId>com.reactor</groupId>
  <artifactId>java-rust-glowroot-spring-boot-starter</artifactId>
  <version>0.4.1</version>
</dependency>

All three Servlet engines read Spring's normalized route, such as /orders/{id}, only when the request must be recorded. Their direct completion hooks add no Java worker pool, application class scan, or per-request wrapper. A portable MVC interceptor remains only as a safety fallback for an unknown Servlet engine; Tomcat, Jetty, and Undertow never use that fallback. Synchronous and asynchronous responses, mapped statuses, unhandled failures, and not-found responses pass the same lifecycle gate.

2. WebFlux applications

Keep the reactive surface separate. Add the optional adapter only to a WebFlux application:

<dependency>
  <groupId>com.reactor</groupId>
  <artifactId>java-rust-glowroot-spring-webflux-adapter</artifactId>
  <version>0.4.1</version>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

The adapter pulls in the common native Spring runtime, but it does not pull Reactor Netty. The application's spring-boot-starter-webflux chooses the reactive server. Do not add spring-boot-starter-web merely for telemetry. The reactor.glowroot.* properties are the same as for MVC.

3. Non-web applications

For the smallest classpath, add only the web-independent runtime to a scheduler, Kafka worker, batch process, command-line process, or database-only Spring Boot application:

<dependency>
  <groupId>com.reactor</groupId>
  <artifactId>java-rust-glowroot-spring-runtime</artifactId>
  <version>0.4.1</version>
</dependency>

The umbrella java-rust-glowroot-spring-boot-starter also works when one organization wants the same dependency in every Spring Boot service. It contains the small adapter JARs, but their server APIs are provided and optional; no adapter auto-configuration becomes active in a non-web application. Choose the runtime-only artifact when the smallest production classpath is the priority.

Do not add spring-webmvc, WebFlux, Tomcat, Jetty, Undertow, Reactor Netty, or the Servlet API only for telemetry. The runtime auto-configuration has no web condition and starts without a web surface.

spring.main.web-application-type=none
reactor.glowroot.enabled=true
reactor.glowroot.profile=jvm
reactor.glowroot.collector.address=http://glowroot-collector:8181
reactor.glowroot.agent.id=invoice-worker::pod-1
reactor.glowroot.application.name=invoice-worker

No application Java code is required for process or JVM gauges. When Spring creates the application context, one process-scoped NativeTelemetry bean loads the packaged DLL/SO, verifies Glowroot ABI 3, and starts one isolated Rust exporter. Closing the Spring context stops that exporter. With reactor.glowroot.enabled=false, the bean is not created, the native library is not loaded, and no exporter thread, route table, SQL table, trace queue, or collector connection is allocated.

Profile Data available without a web server
micro Process RSS, operating-system thread count, exporter/reconnect/drop health
jvm micro plus heap, non-heap, memory pools, GC count, and GC time
sql micro plus explicitly registered SQL duration/error/row aggregates
full JVM gauges plus explicit SQL and bounded error stacks
diagnostic full plus authorized thread dump, heap histogram, and heap dump commands

The runtime does not guess Kafka topic names, scheduler job names, batch step names, or arbitrary business-method boundaries. It does not weave methods. Kafka, scheduler, and batch operation timing is therefore not automatic in 0.4.1. Process and JVM evidence is automatic. Database timing is explicit through the reusable SqlStatement API shown below. This keeps the hot path predictable and avoids a framework-specific Java agent layer.

Application shape Recommended profile Automatic data Explicit application work
Kafka consumer or producer micro; temporarily jvm during an incident Process, exporter health; JVM/GC in jvm Topic/message processing duration is not captured automatically
Scheduler or Spring Batch micro; temporarily jvm or full Process, exporter health; selected JVM/GC gauges Job and step duration is not captured automatically
Database worker micro normally; sql or full while investigating Process and optional JVM/GC gauges Define reusable SqlStatement descriptors for selected repository operations
Command-line or background service micro Process and exporter health No HTTP transaction exists; add only bounded domain metrics required by the service

reactor.glowroot.spring.enabled=false disables Spring HTTP telemetry while process/JVM/SQL telemetry remains available. To stop the native runtime and remove its exporter thread, use reactor.glowroot.enabled=false.

4. Optional early-start bootstrap

Use the bootstrap when your deployment standard expects -javaagent, or when you want process start metadata captured before Spring starts.

The bootstrap is not the telemetry runtime. Keep either java-rust-glowroot-spring-runtime or the Spring Boot starter in the application. The bootstrap only maps early JVM arguments and process start metadata to the same runtime; by itself it does not load a native library or export data.

<dependency>
  <groupId>com.reactor</groupId>
  <artifactId>java-rust-glowroot-agent</artifactId>
  <version>0.4.1</version>
  <scope>runtime</scope>
</dependency>

Keep the bootstrap JAR outside the executable Spring Boot JAR and pass its file path to the JVM:

java \
  -javaagent:/opt/agent/java-rust-glowroot-agent-0.4.1.jar=collector=http://glowroot-collector:8181,agent-id=orders::pod-1,application=orders-api,http-sample-rate=256,trace-capacity=0 \
  -jar orders-api.jar

Do not place Spring classes in the bootstrap JAR. Spring Boot loads nested dependencies with a child classloader; the split artifact design is required for executable-JAR compatibility.

GitHub Packages

GitHub Packages requires authentication for Maven downloads, including packages from public repositories. Create a token with read:packages, then add this server to ~/.m2/settings.xml:

<settings>
  <servers>
    <server>
      <id>github-glowroot</id>
      <username>YOUR_GITHUB_USERNAME</username>
      <password>YOUR_GITHUB_PACKAGES_TOKEN</password>
    </server>
  </servers>
</settings>

Add the package repository to the application POM:

<repositories>
  <repository>
    <id>github-glowroot</id>
    <url>https://maven.pkg.github.com/esasmer-dou/java-rust-glowroot-agent</url>
  </repository>
</repositories>

Kubernetes

Use the pod name as the leaf agent id. A prefix ending with :: creates a Glowroot rollup group.

env:
  - name: REACTOR_GLOWROOT_ENABLED
    value: "true"
  - name: REACTOR_GLOWROOT_COLLECTOR_ADDRESS
    value: "http://glowroot-collector.observability.svc.cluster.local:8181"
  - name: REACTOR_GLOWROOT_AGENT_ID
    valueFrom:
      fieldRef:
        fieldPath: metadata.name
  - name: REACTOR_GLOWROOT_APPLICATION_NAME
    value: "catalog-api"
  - name: REACTOR_GLOWROOT_HTTP_SAMPLE_RATE
    value: "256"
  - name: REACTOR_GLOWROOT_TRACE_CAPACITY
    value: "0"

For a hierarchy such as catalog::pod-name, pass the prefix in the container command or create the complete value in your deployment tooling. Agent ids must be unique per live pod.

Use a stable ClusterIP Service or a localhost sidecar for the collector. DNS is resolved at startup and at most four addresses are retained. Restart the pod when the collector DNS target changes. Do not expose the plain collector port to the internet. Use a service mesh or localhost TLS sidecar when encryption or mTLS is required.

Linux Container Compatibility

Release 0.4.1 supports Linux x64 images with glibc 2.17 or newer. The release build has a fixed GLIBC symbol ceiling and is loaded in both Debian Stretch (glibc 2.24) and Debian Bookworm (glibc 2.36) before publication. This is a build-time compatibility change. It adds no runtime thread, allocation, or JNI call.

Check the final application image, not only the JRE builder image:

docker run --rm --entrypoint sh YOUR_IMAGE -lc 'getconf GNU_LIBC_VERSION'
docker run --rm --entrypoint sh YOUR_IMAGE -lc \
  'ldd /u01/applications/nmc-store-common/glowroot/librust_glowroot_agent.so'

If an image declared as Bookworm reports GLIBC_2.25 not found, the running final image is not the shown Bookworm filesystem. Bookworm provides GLIBC 2.36. Check the deployed image digest, stale custom-image tags, and the final FROM line.

Use one immutable custom-JRE image for both application stages:

ARG JRE_IMAGE=zenia.azurecr.io/example/custom-jre21:1.1.1

FROM ${JRE_IMAGE} AS builder
# Extract the application layers here.

FROM ${JRE_IMAGE}
ARG APP_HOME=/u01/applications/nmc-store-common
WORKDIR ${APP_HOME}

COPY glowroot/librust_glowroot_agent.so ${APP_HOME}/glowroot/
RUN getconf GNU_LIBC_VERSION \
 && ldd ${APP_HOME}/glowroot/librust_glowroot_agent.so \
 && ! ldd ${APP_HOME}/glowroot/librust_glowroot_agent.so | grep -q 'not found'

Do not mix custom JRE tags such as 1.0.0 in the builder and 1.1.0 in the final stage. The builder may pass while production runs against a different libc. Prefer an immutable digest when the image registry supports it.

Configuration

Priority is: JVM -D property, -javaagent argument, environment variable, application property, then default. An environment key is the uppercase property with dots and dashes replaced by underscores. Example: reactor.glowroot.max-export-bytes becomes REACTOR_GLOWROOT_MAX_EXPORT_BYTES.

Property Default Allowed value Purpose
reactor.glowroot.enabled false boolean Enables the bounded telemetry runtime
reactor.glowroot.profile micro micro, jvm, sql, full, diagnostic Selects the bounded startup profile; it can change at runtime
reactor.glowroot.profile.release-timeout-ms 5000 100-60000 Maximum wait for retired profile state to be dropped
reactor.glowroot.collector.address http://127.0.0.1:8181 plaintext HTTP URL Glowroot Central gRPC over HTTP/2 endpoint
reactor.glowroot.agent.id empty 1-256 bytes Required unique agent/rollup id
reactor.glowroot.application.name application name 1-128 bytes Name shown in Glowroot
reactor.glowroot.hostname HOSTNAME up to 255 bytes Host or pod label
reactor.glowroot.export.interval-ms 60000 60000-3600000; 60000 multiple Aggregate export interval
reactor.glowroot.connect-timeout-ms 1000 100-30000 TCP/h2 connection timeout
reactor.glowroot.request-timeout-ms 2000 100-30000 Complete collector request timeout
reactor.glowroot.trace.slow-threshold-ms 500 1-3600000 Slow trace threshold when traces are enabled
reactor.glowroot.http.sample-rate 256 power of two, 1-1024 Samples successful HTTP requests; 5xx stays exact
reactor.glowroot.trace.capacity 0 0-32 Bounded trace queue; 0 allocates no trace queue
reactor.glowroot.sql.capacity 16 0-32 Maximum SQL statement slots allocated only by sql, full, or diagnostic
reactor.glowroot.error.trace.capacity 8 0-16 Maximum retained detailed error stacks in enabled profiles
reactor.glowroot.error.max-frames 24 0-32 Maximum frames copied for one captured error
reactor.glowroot.error.max-bytes 4096 256-8192 Maximum UTF-8 error detail size
reactor.glowroot.max-routes 64 1-64 Maximum retained HTTP route slots
reactor.glowroot.max-export-bytes 65536 16384-65536 Maximum encoded collector request
reactor.glowroot.spring.enabled true boolean Enables the optional Spring HTTP adapter; the native core is controlled by reactor.glowroot.enabled
reactor.glowroot.spring.order -2147483548 integer Portable MVC fallback or optional WebFlux filter order; direct Tomcat, Jetty, and Undertow completion adapters do not require ordering
reactor.glowroot.native.extract-dir user home directory Standalone Spring native extraction directory
reactor.glowroot.native.path empty existing DLL/SO path Development and staging override; production should use packaged binaries

Invalid bounds stop startup. There is no property that enlarges the agent-owned memory ceiling.

Runtime Profiles

Start with micro. Raise one pod only when you need more evidence. Return it to micro after the investigation.

Profile Adds to always-on process gauges and available HTTP/Dubbo/Redis aggregates Good fit
micro Nothing Normal production traffic and the lowest steady memory
jvm Heap, non-heap, memory-pool, GC count, and GC time gauges Short JVM memory or GC investigation
sql Explicit bounded SQL aggregates and detailed error stacks Database latency investigation without JVM gauges
full jvm plus sql and error stacks Short incident window on one pod
diagnostic full plus a two-command queue for dump operations One authorized thread dump, heap histogram, or heap dump

sql is explicit by design. The agent does not proxy DataSource, wrap JDBC objects, or weave drivers. Create a statement descriptor once and reuse it. The timed call does not allocate an observation wrapper:

private final NativeTelemetry.SqlStatement findCustomer;

CustomerRepository(NativeTelemetry telemetry) {
    this.findCustomer = telemetry.sqlStatement(
            "customer.find",
            "select id, name from customer where id = ?"
    );
}

Customer find(long id) {
    long started = findCustomer.start();
    try {
        Customer customer = queryCustomer(id);
        findCustomer.recordSuccess(started, customer == null ? 0 : 1);
        return customer;
    } catch (RuntimeException error) {
        findCustomer.recordFailure(started, error);
        throw error;
    }
}

The statement text is normalized and bounded when its slot is first registered. Bind values are not sent. Keep the descriptor in a singleton service or repository. Do not create it per request.

Rust-Java REST uses the same lifecycle without the Spring starter:

private static final GlowrootTelemetry.SqlStatement FIND_CUSTOMER =
        GlowrootTelemetry.sql("customer.find", "select id, name from customer where id = ?");

long started = FIND_CUSTOMER.start();
try {
    Customer customer = repository.find(id);
    FIND_CUSTOMER.recordSuccess(started, customer == null ? 0 : 1);
    return customer;
} catch (RuntimeException error) {
    FIND_CUSTOMER.recordFailure(started, error);
    throw error;
}

Switch Profiles Without Restarting

Rust-Java REST uses the built-in control API:

import com.reactor.rust.telemetry.GlowrootTelemetry;
import com.reactor.rust.telemetry.TelemetryProfile;
import java.time.Duration;

GlowrootTelemetry.switchTo(TelemetryProfile.FULL, Duration.ofSeconds(5));
// Collect incident data for a bounded period.
GlowrootTelemetry.restoreConfiguredProfile();

Spring Boot injects the existing process-scoped bean:

import com.reactor.glowroot.agent.runtime.NativeTelemetry;
import com.reactor.glowroot.agent.runtime.TelemetryProfile;
import java.time.Duration;

telemetry.updateProfile(TelemetryProfile.JVM, Duration.ofSeconds(5));
// Collect a short JVM window.
telemetry.restoreConfiguredProfile(Duration.ofSeconds(5));

configuredProfile() returns the startup value from reactor.glowroot.profile. Therefore an operations command does not need to hard-code micro. If the service later starts with another bounded baseline, the same restore call still returns to the correct profile.

Do not expose profile changes on a public endpoint. Call the API from an authenticated operations endpoint or an internal control command. The starter and the REST framework intentionally do not open a profile-management endpoint. A profile switch is a control-plane operation, not a request feature. Do not switch profiles per request or on every health-check sample.

SQL slot tokens use a separate positive 32-bit namespace with a 25-bit generation. A stale raw slot cannot alias a new statement during normal process life; exhaustion is fail-fast after more than 33 million state-shape transitions instead of silently wrapping. Profiles are still control-plane incident tools, not per-request or periodic-sampling switches.

The switch is synchronous and serialized. Returning from switchTo or updateProfile means:

  1. the old feature mask no longer accepts new SQL, error, or diagnostic work;
  2. old native queues, statement slots, and profile-derived export payloads have no remaining references;
  3. their Rust allocations were dropped only after an in-flight bounded collector request finished or timed out;
  4. Rust dropped every profile-owned JNI MXBean global reference when it was no longer needed;
  5. Linux glibc received malloc_trim(0) from the isolated agent thread.

The trim call does not run on a Hyper or application worker, but glibc trimming is process-wide. Use profile switching only as a rare control-plane action. Do not infer agent-only RSS savings from a lower post-switch process RSS; validate feature footprint with fresh telemetry-off/on processes.

If release does not finish before reactor.glowroot.profile.release-timeout-ms, the call fails. The transition id remains visible in diagnostics and the next control call waits for that exact release. The exporter is not stopped while retired state is pending. A downgrade from diagnostic is rejected while a dump is running.

micro keeps the base exporter, route aggregates, and collector connection because telemetry is still enabled. Disable telemetry only at process startup when you want zero telemetry state.

OpenJ9 cannot unload system-classloader metadata or its lazily created Finalizer thread after JVM management APIs are used once. The agent releases its references, native queues, and buffers, but a small one-time JVM/JIT warm-state residue can remain. It must not grow with repeated profile cycles. Linux can return allocator pages immediately; Windows releases ownership but lets the OS allocator reclaim resident pages later instead of forcing a process-wide working-set eviction.

Tuning Recipes

Scenario Profile sample-rate trace.capacity Recommendation
High-traffic production API micro 256 0 Lowest steady overhead; keep exact 5xx
Low-traffic API, exact aggregate trend micro 1 or 8 0 More samples are needed because traffic is sparse
JVM or GC investigation jvm unchanged 0 Raise one pod, observe several export intervals, then return to micro
SQL latency investigation sql unchanged 0 Instrument only selected repository statements
Short incident investigation full unchanged 0 by default JVM, SQL, and error state is dynamic; use one pod and revert after the incident
Authorized dump operation diagnostic unchanged unchanged Run one command, confirm completion, then return to micro

Do not solve missing business metrics by setting the sample rate to 1 on every high-traffic pod. Use explicit business metrics for orders, payments, or domain failures.

http.sample-rate and trace.capacity are startup settings. A profile switch does not resize them. If you start with trace.capacity=16, that bounded HTTP trace queue remains allocated in micro. Keep it at 0 when strict downgrade reclamation is the priority; profile-owned SQL, error, JVM, and diagnostic state is still allocated and released dynamically.

Failure Behavior

  • Invalid local configuration fails startup.
  • A collector outage does not block HTTP, Dubbo, Redis, or business logic.
  • Connect and request timeouts are bounded.
  • Reconnect uses bounded exponential backoff.
  • Failed intervals are dropped at the rollup boundary; they are not queued forever.
  • Route, trace, message, DNS-address, and export sizes have hard limits.
  • An old or mismatched native ABI fails early with an actionable error.

Diagnostics

Rust-Java REST exposes built-in diagnostics:

curl -s http://localhost:8080/diagnostics/glowroot
curl -s http://localhost:8080/metrics | grep reactor_glowroot

For Spring Boot, inject NativeTelemetry into an existing secured diagnostics controller only when you need it, then return diagnosticsJson(). The starter does not open a management endpoint by itself.

Watch connected, export_failure, dropped_intervals, dropped_transactions, dropped_traces, pending_error_captures, queued_error_traces, dropped_error_traces, dropped_routes, reconnects, and last_error_code. pending_error_captures is work waiting for the isolated Rust exporter; it should remain bounded and return to zero after the burst. During a profile change watch active_profile, active_profile_memory_ceiling_bytes, retired_profile_memory_ceiling_bytes, jvm_probe_registered, jvm_probe_owned_global_refs, profile_release_pending, profile_released_transition, profile_release_timeouts, profile_last_release_micros, profile_max_release_micros, and profile_trim_succeeded.

After returning to a profile without JVM gauges or diagnostics, both jvm_probe_registered=false and jvm_probe_owned_global_refs=0 are required. Diagnostic output is written by Rust to a temporary file in the target directory and published only after success. Heap histograms and heap dumps can still create a large one-time JVM diagnostic allocation; use them on one pod, never as a periodic job or during a latency-sensitive peak.

Performance Contract

The micro configuration enforces a deterministic 1 MiB ceiling for agent-attributed state and native feature pages. Both Rust-Java REST and Spring use one isolated current-thread Tokio exporter with a 256 KiB stack when telemetry is enabled. It does not share Hyper workers, application executors, or Spring request threads. Embedded REST also packs request capture into one 32-bit state value; the public sampling rate and exact 5xx behavior do not change. The strict Spring gate enables the starter through properties or environment variables. The optional -javaagent bootstrap is a deployment convenience and is validated separately because starting the JVM instrumentation subsystem adds OpenJ9-owned memory even though no transformer is installed.

Release gates compare telemetry off/on in the same image with randomized paired runs. Every endpoint/concurrency cell must keep:

  • useful HTTP 200 RPS loss at or above -2%;
  • p99 regression at or below +10%;
  • non-2xx regression at 0 percentage points for every stable-release cell. Baseline and candidate aggregate and peak error rates must remain at or below 0.05%;
  • additional agent threads at most 1 for both embedded Rust-Java and standalone Spring.

The stable release runs the same per-request telemetry matrix separately for Spring Boot and Rust-Java REST. Both cover small JSON and precomputed raw JSON at c64/c256. These paths maximize request rate and expose the agent's fixed cost more strongly than a serializer-bound route. Dynamic heavy JSON is still called by the functional route smoke. The optional extended workflow adds measured heavy JSON at c64/c128 and always runs all six pairs, but it is stress evidence rather than the stable publish gate. The release starts with three independent pairs. It stops only when a stricter early-pass envelope succeeds; otherwise it continues to six. RPS, p99, and startup use each pair's delta before the median is calculated. Non-2xx uses the paired median, request-weighted total, peak error envelope, and the absolute 0.05% ceiling together. The normal release matrix uses the zero-delta rule for every cell. The bounded 0.02 percentage-point margin applies only when heavy JSON is tested manually at the saturated embedded REST c256+ range. One saturated-run delta remains visible without replacing the overall error decision. After both variants complete the same full workload, a controlled equal-process-age phase requires paired-median process RSS and cgroup deltas at or below +3 MiB for micro. Both runtimes may add one bounded exporter thread. jvm, sql, full, and diagnostic are separate temporary-profile gates because OpenJ9 management/error classes can create one-time JVM warm state. REST wire compatibility, collector-down fail-open, and the optional bootstrap are separate mandatory checks.

See Architecture And Production Boundary. User-facing changes and compatibility details are in the 0.4.1 release notes. Internal benchmark tools and raw evidence are intentionally not part of the public repository.

Compatibility

Component Release Contract
Java 21 Semeru OpenJ9 is the primary tested JVM
Rust-Java REST 4.5.6 REST ABI 29, Glowroot ABI 3
Agent bootstrap 0.4.1 One class; works with either supported runtime
Spring Boot starter 0.4.1 Spring Boot 3.x; web-independent core plus direct full-lifecycle adapters for Tomcat, Jetty, and Undertow; no server engine dependency
Spring WebFlux adapter 0.4.1 Separate optional WebFilter; no Reactor Netty or Servlet engine dependency
Standalone native source rust-spring v4.5.6 Glowroot ABI 3; clean CI DLL/SO
Glowroot Central wire contract upstream 0.14.8-beta.5-SNAPSHOT checkout Unary h2/protobuf compatibility gate
Native platforms Windows x64, Linux glibc x64 Clean CI-built DLL/SO with SHA-256 provenance

Runtime profile switching requires the coordinated REST ABI 29 and Glowroot ABI 3 pair shown above. Startup provenance checks reject an older or locally copied native binary.

Do not copy DLL/SO files between versions. The framework, cache, Dubbo, and agent libraries validate their coordinated native ABI at startup.

Build

$env:JAVA_HOME = "D:\Dropbox\java64\Semeru\jdk-21.0.2.13-openj9"
mvn -B -ntp clean verify

The Maven reactor builds:

  • agent-bootstrap/target/java-rust-glowroot-agent-0.4.1.jar
  • spring-runtime-core/target/java-rust-glowroot-spring-runtime-0.4.1.jar
  • spring-mvc-adapter/target/java-rust-glowroot-spring-mvc-adapter-0.4.1.jar
  • spring-tomcat-adapter/target/java-rust-glowroot-spring-tomcat-adapter-0.4.1.jar
  • spring-jetty-adapter/target/java-rust-glowroot-spring-jetty-adapter-0.4.1.jar
  • spring-undertow-adapter/target/java-rust-glowroot-spring-undertow-adapter-0.4.1.jar
  • spring-boot-starter/target/java-rust-glowroot-spring-boot-starter-0.4.1.jar
  • spring-webflux-adapter/target/java-rust-glowroot-spring-webflux-adapter-0.4.1.jar

The native DLL/SO are built only from the clean rust-spring commit recorded in native-provenance.properties. scripts/sync-native-artifacts.ps1 is retained because it is part of the reproducible release build. Internal load generators, raw benchmark evidence, and local runner configuration stay outside the public repository.

About

Bounded Rust-first telemetry for Rust-Java REST and Spring Boot MVC, exporting to Glowroot Central with minimal RSS and no bytecode weaving.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages