Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(diagnostics): Add API handler for invoking GC #492

Merged
merged 10 commits into from
Oct 1, 2024
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,18 @@ stopped or the host JVM shuts down.

## Configuration

### Logging

`cryostat-agent` uses [SLF4J](https://www.slf4j.org/) for its logging. Currently it ships with `slf4j-simple` as the
log implementation, but this should change in the future to hook in to any existing SLF4J providers found on the
classpath at runtime, or fallback to `slf4j-simple` if nothing is found.

The Agent's log levels can be controlled by setting a JVM system property on the target JVM:
`-Dio.cryostat.agent.shaded.org.slf4j.simpleLogger.defaultLogLevel=trace`, or replace `trace` with whatever level you
prefer. This can also be passed as a dynamic attachment argument when starting the Agent after the target JVM.

### Agent Properties

`cryostat-agent` uses [smallrye-config](https://github.com/smallrye/smallrye-config) for configuration.
Below is a list of configuration properties that can be used to influence how `cryostat-agent` runs
and how it advertises itself to a Cryostat server instance. Properties that require configuration are indicated with a checked box.
Expand Down
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@
<com.github.spotbugs.plugin.version>4.8.6.4</com.github.spotbugs.plugin.version>
<org.junit.jupiter.version>5.11.1</org.junit.jupiter.version>
<org.hamcrest.version>3.0</org.hamcrest.version>
<org.mockito.version>5.2.0</org.mockito.version>
<org.mockito.version>5.14.1</org.mockito.version>
<org.jacoco.maven.plugin.version>0.8.12</org.jacoco.maven.plugin.version>
<com.diffplug.spotless.maven.plugin.version>2.43.0</com.diffplug.spotless.maven.plugin.version>
<com.google.googlejavaformat.version>1.17.0</com.google.googlejavaformat.version>
Expand Down Expand Up @@ -256,7 +256,7 @@
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<artifactId>mockito-core</artifactId>
<version>${org.mockito.version}</version>
<scope>test</scope>
</dependency>
Expand Down
117 changes: 117 additions & 0 deletions src/main/java/io/cryostat/agent/remote/InvokeContext.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* Copyright The Cryostat Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cryostat.agent.remote;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.management.ManagementFactory;
import java.util.Objects;

import javax.inject.Inject;
import javax.management.MBeanServer;
import javax.management.ObjectName;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.sun.net.httpserver.HttpExchange;
import org.apache.http.HttpStatus;
import org.eclipse.microprofile.config.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

class InvokeContext extends MutatingRemoteContext {

private final Logger log = LoggerFactory.getLogger(getClass());
private final ObjectMapper mapper;

@Inject
InvokeContext(ObjectMapper mapper, Config config) {
super(config);
this.mapper = mapper;
}

@Override
public String path() {
return "/mbean-invoke/";
}

@Override
public void handle(HttpExchange exchange) throws IOException {
try {
String mtd = exchange.getRequestMethod();
switch (mtd) {
case "POST":
try (InputStream body = exchange.getRequestBody()) {
MBeanInvocationRequest req =
mapper.readValue(body, MBeanInvocationRequest.class);
if (!req.isValid()) {
exchange.sendResponseHeaders(
HttpStatus.SC_BAD_REQUEST, BODY_LENGTH_NONE);
}
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
Object response =
server.invoke(
ObjectName.getInstance(req.beanName),
req.operation,
req.parameters,
req.signature);
if (Objects.nonNull(response)) {
exchange.sendResponseHeaders(HttpStatus.SC_OK, BODY_LENGTH_UNKNOWN);
try (OutputStream responseStream = exchange.getResponseBody()) {
mapper.writeValue(responseStream, response);
}
} else {
exchange.sendResponseHeaders(HttpStatus.SC_ACCEPTED, BODY_LENGTH_NONE);
}
} catch (Exception e) {
log.error("mbean serialization failure", e);
exchange.sendResponseHeaders(HttpStatus.SC_BAD_GATEWAY, BODY_LENGTH_NONE);
}
break;
default:
log.warn("Unknown request method {}", mtd);
exchange.sendResponseHeaders(
HttpStatus.SC_METHOD_NOT_ALLOWED, BODY_LENGTH_NONE);
break;
}
} finally {
exchange.close();
}
}

static class MBeanInvocationRequest<T> {

public String beanName;
public String operation;
public Object[] parameters;
public String[] signature;

public boolean isValid() {
if (this.beanName.equals(ManagementFactory.MEMORY_MXBEAN_NAME)) {
return true;
}
return false;
}

public String getBeanName() {
return beanName;
}

public void setBeanName(String beanName) {
this.beanName = beanName;
}
}
}
4 changes: 4 additions & 0 deletions src/main/java/io/cryostat/agent/remote/RemoteModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
@Module
public abstract class RemoteModule {

@Binds
@IntoSet
abstract RemoteContext bindInvokeContext(InvokeContext ctx);

@Binds
@IntoSet
abstract RemoteContext bindMBeanContext(MBeanContext ctx);
Expand Down
Loading