This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Timer Ninja is a lightweight Java library that measures method execution time using Aspect-Oriented Programming (AOP) with AspectJ. It provides annotation-based tracking (@TimerNinjaTracker) and programmatic tracking (TimerNinjaBlock.measure()) with automatic call hierarchy preservation and visual tree output.
This project uses Gradle with the io.freefair.aspectj plugin (v9.1.0) for AspectJ post-compile weaving.
# Build the entire project
./gradlew build
# Run all tests
./gradlew test
# Run a specific test class
./gradlew test --tests TimerNinjaIntegrationTest
# Run a specific test method
./gradlew test --tests TimerNinjaIntegrationTest.testSingleTracker
# Build and publish to local Maven repository
./gradlew publishToMavenLocal
# Clean build artifacts
./gradlew clean# Build and stage for release (requires signing credentials)
./gradlew build publish
# Deploy staged artifacts to Maven Central via JReleaser
./gradlew jreleaserDeployNote: JReleaser configuration in build.gradle:79-121 handles signing and deployment. Credentials are currently placeholder values.
AspectJ Layer (TimeTrackingAspect.aj)
- Native AspectJ syntax (not @AspectJ annotation style)
- Two pointcuts: methods and constructors annotated with
@TimerNinjaTracker - Uses around advice to intercept execution, measure time, and manage tracking context
- Leverages
thisJoinPointandthisJoinPointStaticPartfor reflection - Located in
src/main/aspectj(custom source set, NOTsrc/main/java)
Context Management (TimerNinjaContextManager)
- Centralized ThreadLocal management for per-thread tracking state
- Replaces direct ThreadLocal access in newer code
- Both annotation-based and block-based tracking share the same context
Thread Context (TimerNinjaThreadContext)
- Per-thread tracking state stored in ThreadLocal
- Tracks:
traceContextId(UUID),pointerDepth(call stack depth),itemContextMap(LinkedHashMap preserving execution order) - Context lifecycle: created on first tracked method, cleaned up when depth returns to 0
Tracker Data (TrackerItemContext)
- Immutable data holder for each tracked method/block
- Stores: method signature, arguments, execution time, time unit, threshold
Utilities (TimerNinjaUtil)
- Static utility methods for signature formatting, argument extraction, time conversion, tree formatting
- SLF4J logger (
INFOlevel default, DEBUG onTimerNinjaThreadContextfor troubleshooting)
Annotation-Based Tracking
@TimerNinjaTracker(timeUnit = ChronoUnit.MILLIS, includeArgs = true, threshold = 200)
public void myMethod() { }Programmatic Block Tracking (NEW - in development)
TimerNinjaBlock.measure(() -> {
// Code to measure
}, BlockTrackerConfig.builder().timeUnit(ChronoUnit.MILLIS).build());Both mechanisms integrate seamlessly through the shared TimerNinjaContextManager.
- Aspect intercepts method call OR
TimerNinjaBlock.measure()is invoked - Check if ThreadLocal context exists; create if null
- Create
TrackerItemContext, incrementpointerDepth, start timer - Execute actual method/block via
proceed()(AspectJ) orRunnable.run()(block) - Calculate duration, update context, decrement
pointerDepth - When
pointerDepthreturns to 0: log full trace tree, cleanup ThreadLocal
- ThreadLocal Pattern: Ensures thread-safe context isolation
- LinkedHashMap: Preserves insertion order for trace visualization
- Pointer Depth Tracking: Simple integer counter for tree indentation
- Builder Pattern:
BlockTrackerConfigfor fluent block configuration
- Integration Tests:
TimerNinjaIntegrationTest- full tracking scenarios with nested calls - Unit Tests: Individual component tests (Utils, Context, ThreadContext)
- Test Fixtures:
servicesample/package with realistic domain scenarios (BankService, UserService, etc.) - Custom JUnit Extension:
LogCaptureExtensioncaptures SLF4J output via LogbackListAppender
Tests use JUnit 5 with Mockito for mocking. Log capture is done via @ExtendWith(LogCaptureExtension.class).
# Run all tests
./gradlew test
# Run specific test
./gradlew test --tests TimerNinjaUtilTest.testGetExecutionTimeStringNote: Tests require testAspect configuration for AspectJ weaving in test classes.
- Main code:
src/main/aspectj/(NOTsrc/main/java/) - Test code:
src/test/aspectj/
Consumers of this library MUST add an AspectJ plugin to their build:
Gradle:
plugins {
id "io.freefair.aspectj.post-compile-weaving" version "9.1.0"
}
dependencies {
aspect 'io.github.thanglequoc:timer-ninja:1.2.0'
testAspect("io.github.thanglequoc:timer-ninja:1.2.0") // for test weaving
}Maven:
<plugin>
<groupId>dev.aspectj</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.14.1</version>
<configuration>
<complianceLevel>${java.version}</complianceLevel>
<aspectLibraries>
<aspectLibrary>
<groupId>io.github.thanglequoc</groupId>
<artifactId>timer-ninja</artifactId>
</aspectLibrary>
</aspectLibraries>
</configuration>
</plugin>- Methods:
execution(@TimerNinjaTracker * *(..)) - Constructors:
execution(@TimerNinjaTracker *.new(..))
The aspect uses:
MethodSignature/ConstructorSignaturefor signature introspectionCodeSignature.getParameterNames()for parameter name extractionJoinPoint.getArgs()for argument valuesSignature.getAnnotation()for reading annotation options
Branch: 26_InlineCodeBlockTracking
New features in development:
TimerNinjaBlock- Programmatic code block tracking APIBlockTrackerConfig- Builder-style configuration for blocksTimerNinjaContextManager- Refactored centralized ThreadLocal management- Integration of annotation and block tracking in shared context
- Java 17 target/source compatibility
- SLF4J for logging (users need Logback/Log4J implementation)
- Optional System.out fallback via
TimerNinjaConfiguration.getInstance().toggleSystemOutLog(true) - Apache License 2.0
- LinkedHashMap preserves execution order for trace visualization
- ThreadLocal cleanup is critical (happens when depth returns to 0)