Skip to content

Conversation

@mouhamadalmounayar
Copy link

@mouhamadalmounayar mouhamadalmounayar commented Jan 13, 2026

This PR adds methods to capture the current thread's profiling labels. Closes #237 .

The problem

Java applications use asynchronous execution patterns where work started by one thread, completes on another. If we want dynamic labels to propagate between threads, the only option is to pass the labels in business methods, and wrap each async call with Pyroscope.LabelsWrapper.run(). This will make business logic and monitoring code tightly coupled.
It is also impossible to retrieve the current profiling labels if we are not the code that actually set them.

Solution

Add methods which allow capturing the current profiling labels.

public static LabelsSet getCurrentLabels();

This is analogous to OpenTelemetry's Context.current().

Example Use Case

This functionality helped us implement a Microprofile thread context provider to propagate the labels across threads, without polluting all of our business logic methods with label parameters.

import io.pyroscope.labels.v2.LabelsSet;
import io.pyroscope.labels.v2.Pyroscope;
import io.pyroscope.labels.v2.ScopedContext;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.context.spi.ThreadContextController;
import org.eclipse.microprofile.context.spi.ThreadContextProvider;
import org.eclipse.microprofile.context.spi.ThreadContextSnapshot;

import java.util.Map;

@Slf4j
public class PyroscopeThreadContextProvider implements ThreadContextProvider {

	private static final String PYROSCOPE_CONTEXT = "Pyroscope";

	@Override
	public String getThreadContextType() {
		return PYROSCOPE_CONTEXT;
	}


	@Override
	public ThreadContextSnapshot currentContext(Map<String, String> props) {
		LabelsSet capturedLabels = null;
		try {
			capturedLabels = Pyroscope.LabelsWrapper.getCurrentLabels();	
		} catch (Exception e) {
			log.atWarn().addArgument(e.getMessage()).log("[Pyroscope Context] Failed to capture labels on thread: {}");
		}
		return new PyroscopeSnapshot(capturedLabels != null ? capturedLabels : new LabelsSet());
	}

	@Override
	public ThreadContextSnapshot clearedContext(Map<String, String> props) {
		return new PyroscopeSnapshot(new LabelsSet());
	}
	
	private static class PyroscopeSnapshot implements ThreadContextSnapshot {
		private final LabelsSet capturedLabels;

		PyroscopeSnapshot(LabelsSet labels) {
			this.capturedLabels = labels;
		}

		@Override
		public ThreadContextController begin() {
			ScopedContext scopedContext = null;

			try {
				scopedContext = new ScopedContext(capturedLabels);
			} catch (Exception e) {
				log.atWarn().addArgument(e.getMessage()).log("[Pyroscope Context] Failed to create Scoped Context: {}");	
			}

			final ScopedContext finalContext = scopedContext;

			return new ThreadContextController() {
				@Override
				public void endContext() throws IllegalStateException {
					if (finalContext != null) {
						try {
							finalContext.close();
						} catch (Exception e) {
							log.atWarn().addArgument(e.getMessage()).log("[Pyroscope Context] Failed to close scoped context: {}");	
						}
					}
				}
			};
		}
	}
}

Since this is an unsolicited contribution, feel free to close if it's not something you'd like to support right now.

Signed-off-by: mouhamadalmounayar <muhammad.mnayar@gmail.com>
@mouhamadalmounayar mouhamadalmounayar requested review from a team as code owners January 13, 2026 18:29
@CLAassistant
Copy link

CLAassistant commented Jan 13, 2026

CLA assistant check
All committers have signed the CLA.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Propagating Pyroscope labels to async worker threads in Java

2 participants