https://github.com/col-e/instrumentationserver
Minimal client-server library for interacting with remote JVM instrumentation instances
https://github.com/col-e/instrumentationserver
Last synced: 3 months ago
JSON representation
Minimal client-server library for interacting with remote JVM instrumentation instances
- Host: GitHub
- URL: https://github.com/col-e/instrumentationserver
- Owner: Col-E
- License: mit
- Created: 2022-05-21T08:15:48.000Z (about 4 years ago)
- Default Branch: master
- Last Pushed: 2025-07-01T03:20:54.000Z (12 months ago)
- Last Synced: 2026-03-27T23:42:21.747Z (3 months ago)
- Language: Java
- Size: 248 KB
- Stars: 17
- Watchers: 2
- Forks: 4
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Instrumentation Server [](https://jitpack.io/#Col-E/InstrumentationServer)
A minimal client-server library that allows simple interaction with a remote process's `Instrumentation` API over a message system.
## Usage
Maven dependency:
```xml
software.coley
instrumentation-server
${serverVersion}
```
Gradle dependency:
```groovy
implementation group: 'software.coley', name: 'instrumentation-server', version: serverVersion
implementation "software.coley:instrumentation-server:${serverVersion}"
```
### Starting a new process
Example logic to programmatically launch a new process with the agent active:
```java
// Extract the agent jar to some path
Path agentJarPath = // ...
Extractor.extractToPath(agentJarPath);
// Start new process
String agent = "-javaagent:" + agentJarPath.toString().replace("\\", "/");
Process remote = new ProcessBuilder("java", agent, "-cp", "", "").start();
// Connect
int port = Server.DEFAULT_PORT;
Client client = new Client("127.0.0.1", port, ByteBufferAllocator.HEAP, MessageFactory.create());
if (!client.connect()) System.err.println("Connect failed!");
```
### Connecting to an existing process
Example logic to programmatically connect to an existing JVM and load the agent:
```java
// Extract the agent jar to some path
Path agentJarPath = // ...
Extractor.extractToPath(agentJarPath);
// Use attach API to connect to remote VM.
// Options string is optional, but can be used to run the server on a unique port.
int openPort = SocketAvailability.findAvailable();
String optionsStr = "port=" + openPort;
for (VirtualMachineDescriptor descriptor : VirtualMachine.list()) {
// Filter to only apply to a target VM
if (!match(descriptor)) continue;
// Attach to the VM
try {
descriptor.provider().attachVirtualMachine(descriptor)
.loadAgent(agentJarPath.toAbsolutePath().toString(), optionsStr);
} catch (AgentLoadException e) {
e.printStackTrace();
} catch (AgentInitializationException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (AttachNotSupportedException e) {
e.printStackTrace();
}
}
// Connect
Client client = new Client("127.0.0.1", openPort, ByteBufferAllocator.HEAP);
if (!client.connect()) System.err.println("Connect failed!");
// Send request + handle reply
MemberData memberData = new MemberData("java/lang/Integer", "MAX_VALUE", "I");
client.sendBlocking(new RequestFieldGetMessage(memberData), reply -> {
// reply is asserted to be ReplyFieldGetMessage
System.out.println(reply.getValueText());
});
// Handle general broadcasts
client.setBroadcastListener((type, message) -> { });
```
### API
The `Client` class has four primary methods:
| Method | Usage |
|------------------------------------------------------------------------------------------------------|-------|
| `WriteResult sendAsync(AbstractMessage message)` | Send a `AbstractMessage` value, return wrapper of of write operation information. |
| `ReplyResult sendAsync(AbstractRequestMessage message, Consumer replyHandler)` | Send a `AbstractMessage` value and handle a reply value _(ideally of an expected type)_, return wrapper of the write operation and read operation for the handled response. |
| `void sendBlocking(AbstractMessage message)` | Send a `AbstractMessage` value, return when the message has been sent. |
| `void sendBlocking(AbstractRequestMessage message, Consumer replyHandler)` | Send a `AbstractMessage` value and handle a reply value _(ideally of an expected type)_, return when reply has been handled. |
The available request/response messages:
| Request type | Response type | Description |
|------------------------------------|----------------------------------|-------------|
| `RequestClassMessage` | `ReplyClassMessage` | Get the `byte[]` of a class, wrapped as a `ClassData` type. |
| `RequestClassloaderClassesMessage` | `ReplyClassloaderClassesMessage` | Get the names of classes belonging to a given `ClassLoader`. |
| `RequestClassloadersMessage` | `ReplyClassloadersMessage` | Get the `int loaderId` values of all `ClassLoader` values. |
| `RequestFieldGetMessage` | `ReplyFieldGetMessage` | Get the `String` representation of a `static` field's value. |
| `RequestFieldSetMessage` | `ReplyFieldSetMessage` | Set the value of a `static` field's value. |
| `RequestPingMessage` | `ReplyPingMessage` | Ping pong. |
| `RequestPropertiesMessage` | `ReplyPropertiesMessage` | Get the `System.getProperties()` values. |
| `RequestRedefineMessage` | `ReplyRedefineMessage` | Redefine a class. |
| `RequestSetPropertyMessage` | `ReplySetPropertyMessage` | Set a value within the `System.getProperties()`. |
| `RequestThreadsMessage` | `ReplyThreadsMessage` | Get thread information about all running threads. |
The available broadcast messages:
| Type | Description |
|-------------------------------|-------------|
| `BroadcastClassloaderMessage` | Sent any time a new `ClassLoader` has been used. |
| `BroadcastClassMessage` | Sent any time a class definition has been updated. |