Keywords
- Thread-Local Variables
- Concurrency APIs
- JUnit 5
- Maven (Surefire Plugin)
- Java Microbenchmark Harness (JMH)
- Java API for XML Processing (JAXP)
- W3C Document Object Model (DOM) API
- Generics
- LogBack
- Log4j2
- Java Logging API
- Java I/O API
Brief Overview
Where advisable, the library uses thread-local variables to store relevant state specific to a particular thread. E.g. a QueueTracer which has been retrieved
using takeTracer() is subsequently available via getCurrentQueueTracer() by the calling thread. Under the hood
getCurrentQueueTracer() uses a thread-local variable to provide the QueueTracer until the last pop enqueues it again.
Alternatively, instances of
ConcurrentMap<Long,V>
have been applied for similar use cases where the use of thread-local variables is not sufficient.
Here the type parameter Long represents the thread id.
A Reentrant mutual exclusion
Lock
is used to synchronize access to the underlying TracePrintStream.
Read-Write locks are employed to facilitate configuration changes on-the-fly (experimental feature).
The unit tests are implemented by using JUnit5 and are executed during the
build by the Maven Surefire Plugin. A certain unit test class is dedicated to
perform benchmarks using JMH. Several test cases are using thread pools created by the factory methods of the
Executors
class to generate load.
The Java API for XML Processing is used to evaluate the XML tracing-configuration by obtaining a DOM instance. The DOM instance will be validated against a XML schema.
QueueTracers may come in different flavors, e.g. QueueFileTracers or QueueNullTracers.
In order to avoid code duplication and simultaneously providing a uniform interface to all the different tracers the QueueTracers are
declared as follows:
That is a QueueTracer is a AbstractTracer and delegates calls to the exposed interface to a member of type
T extends AbstractTracer.
The library provides adapters to external logging libraries, e.g. LogBack and Log4j2.
An important feature is the usage of polymorphic IndentablePrintStreams. An IndentablePrintStream itself is
a derived PrintStream with some additional (abstract) methods required for indented prints (and synchronization). The classes
TracePrintStream and NullPrintStream are both implementing IndentablePrintStream's
abstract methods. Whereas instances of TracePrintStream are used for cases where actual output is required, instances of
NullPrintStream are used when the given prints should be discarded, e.g. if the stack size exceeds the configured limit. The
NullPrintStream overwrites all inherited methods with empty method bodies. That is the Java Virtual Machine is able to optimize calls to those
empty methods away by inlining them. The overall performance impact of tracing statements in the case of disabled tracing should therefore be minimal.