Skip to content

Commit

Permalink
feat(api): remote read access for MBeanMetrics
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewazores committed Mar 15, 2023
1 parent 808f156 commit 109f9b4
Show file tree
Hide file tree
Showing 2 changed files with 238 additions and 26 deletions.
3 changes: 2 additions & 1 deletion src/main/java/io/cryostat/agent/MainModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,13 @@ public static ScheduledExecutorService provideExecutor(AtomicInteger threadId) {
@Provides
@Singleton
public static WebServer provideWebServer(
ObjectMapper mapper,
Lazy<CryostatClient> cryostat,
ScheduledExecutorService executor,
@Named(ConfigModule.CRYOSTAT_AGENT_WEBSERVER_HOST) String host,
@Named(ConfigModule.CRYOSTAT_AGENT_WEBSERVER_PORT) int port,
Lazy<Registration> registration) {
return new WebServer(cryostat, executor, host, port, registration);
return new WebServer(mapper, cryostat, executor, host, port, registration);
}

@Provides
Expand Down
261 changes: 236 additions & 25 deletions src/main/java/io/cryostat/agent/WebServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,24 +37,49 @@
*/
package io.cryostat.agent;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.OperatingSystemMXBean;
import java.lang.management.RuntimeMXBean;
import java.lang.management.ThreadMXBean;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.ScheduledExecutorService;

import io.cryostat.core.net.MBeanMetrics;
import io.cryostat.core.net.MemoryMetrics;
import io.cryostat.core.net.OperatingSystemMetrics;
import io.cryostat.core.net.RuntimeMetrics;
import io.cryostat.core.net.ThreadMetrics;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sun.management.UnixOperatingSystemMXBean;
import com.sun.net.httpserver.BasicAuthenticator;
import com.sun.net.httpserver.Filter;
import com.sun.net.httpserver.HttpContext;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import dagger.Lazy;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -63,6 +88,7 @@ class WebServer {

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

private final ObjectMapper mapper;
private final Lazy<CryostatClient> cryostat;
private final ScheduledExecutorService executor;
private final String host;
Expand All @@ -72,11 +98,13 @@ class WebServer {
private HttpServer http;

WebServer(
ObjectMapper mapper,
Lazy<CryostatClient> cryostat,
ScheduledExecutorService executor,
String host,
int port,
Lazy<Registration> registration) {
this.mapper = mapper;
this.cryostat = cryostat;
this.executor = executor;
this.host = host;
Expand All @@ -96,7 +124,9 @@ void start() throws IOException, NoSuchAlgorithmException {

this.http.setExecutor(executor);

HttpContext pingCtx =
List<HttpContext> ctxs = new ArrayList<>();

ctxs.add(
this.http.createContext(
"/",
new HttpHandler() {
Expand All @@ -122,33 +152,69 @@ public void handle(HttpExchange exchange) throws IOException {
break;
}
}
});
pingCtx.setAuthenticator(new AgentAuthenticator());
pingCtx.getFilters()
.add(
new Filter() {
@Override
public void doFilter(HttpExchange exchange, Chain chain)
throws IOException {
long start = System.nanoTime();
String requestMethod = exchange.getRequestMethod();
String path = exchange.getRequestURI().getPath();
log.info("{} {}", requestMethod, path);
chain.doFilter(exchange);
long elapsed = System.nanoTime() - start;
log.info(
"{} {} : {} {}ms",
requestMethod,
path,
exchange.getResponseCode(),
Duration.ofNanos(elapsed).toMillis());
}
}));

ctxs.add(
this.http.createContext(
"/mbean-metrics",
new HttpHandler() {
@Override
public String description() {
return "requestLog";
public void handle(HttpExchange exchange) throws IOException {
String mtd = exchange.getRequestMethod();
switch (mtd) {
case "GET":
try {
MBeanMetrics metrics = getMbeanMetrics();
String json = mapper.writeValueAsString(metrics);
log.info(json);
exchange.sendResponseHeaders(HttpStatus.SC_OK, 0);
try (OutputStream response =
exchange.getResponseBody();
PrintWriter pw = new PrintWriter(response)) {
pw.write(json);
}
} catch (Exception e) {
log.error("mbean serialization failure", e);
} finally {
exchange.close();
}
break;
default:
exchange.sendResponseHeaders(HttpStatus.SC_NOT_FOUND, -1);
exchange.close();
break;
}
}
});
}));

ctxs.forEach(ctx -> ctx.setAuthenticator(new AgentAuthenticator()));
ctxs.forEach(
ctx ->
ctx.getFilters()
.add(
new Filter() {
@Override
public void doFilter(HttpExchange exchange, Chain chain)
throws IOException {
long start = System.nanoTime();
String requestMethod = exchange.getRequestMethod();
String path = exchange.getRequestURI().getPath();
log.info("{} {}", requestMethod, path);
chain.doFilter(exchange);
long elapsed = System.nanoTime() - start;
log.info(
"{} {} : {} {}ms",
requestMethod,
path,
exchange.getResponseCode(),
Duration.ofNanos(elapsed).toMillis());
}

@Override
public String description() {
return ctx.getPath() + "-requestLog";
}
}));

this.http.start();
}
Expand All @@ -175,6 +241,151 @@ void generateCredentials() throws NoSuchAlgorithmException {
}
}

private <T> void safeStore(String key, Map<String, Object> map, Callable<T> fn) {
try {
map.put(key, fn.call());
} catch (Exception e) {
log.warn("Call failed", e);
}
}

private MBeanMetrics getMbeanMetrics() {
// TODO refactor, extract into -core library?
RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean();
Map<String, Object> runtimeAttrs = new HashMap<>();
safeStore("BootClassPath", runtimeAttrs, runtimeBean::getBootClassPath);
safeStore("ClassPath", runtimeAttrs, runtimeBean::getClassPath);
safeStore(
"InputArguments",
runtimeAttrs,
() -> runtimeBean.getInputArguments().toArray(new String[0]));
safeStore("LibraryPath", runtimeAttrs, runtimeBean::getLibraryPath);
safeStore("ManagementSpecVersion", runtimeAttrs, runtimeBean::getManagementSpecVersion);
safeStore("Name", runtimeAttrs, runtimeBean::getName);
safeStore("SpecName", runtimeAttrs, runtimeBean::getSpecName);
safeStore("SpecVersion", runtimeAttrs, runtimeBean::getSpecVersion);
safeStore("SystemProperties", runtimeAttrs, runtimeBean::getSystemProperties);
safeStore("StartTime", runtimeAttrs, runtimeBean::getStartTime);
safeStore("Uptime", runtimeAttrs, runtimeBean::getUptime);
safeStore("VmName", runtimeAttrs, runtimeBean::getVmName);
safeStore("VmVendor", runtimeAttrs, runtimeBean::getVmVendor);
safeStore("VmVersion", runtimeAttrs, runtimeBean::getVmVersion);
safeStore("BootClassPathSupported", runtimeAttrs, runtimeBean::isBootClassPathSupported);
RuntimeMetrics runtime = new RuntimeMetrics(runtimeAttrs);

MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
Map<String, Object> memoryAttrs = new HashMap<>();
safeStore("HeapMemoryUsage", memoryAttrs, memoryBean::getHeapMemoryUsage);
safeStore("NonHeapMemoryUsage", memoryAttrs, memoryBean::getNonHeapMemoryUsage);
safeStore(
"ObjectPendingFinalizationCount",
memoryAttrs,
memoryBean::getObjectPendingFinalizationCount);
// TODO are these inferred calculations correct (ie do they match what the bean attributes
// say)?
safeStore(
"FreeHeapMemory",
memoryAttrs,
() ->
memoryBean.getHeapMemoryUsage().getCommitted()
- memoryBean.getHeapMemoryUsage().getUsed());
safeStore(
"FreeNonHeapMemory",
memoryAttrs,
() ->
memoryBean.getNonHeapMemoryUsage().getCommitted()
- memoryBean.getNonHeapMemoryUsage().getUsed());
safeStore(
"HeapMemoryUsagePercent",
memoryAttrs,
() ->
((double) memoryBean.getHeapMemoryUsage().getUsed()
/ (double) memoryBean.getHeapMemoryUsage().getCommitted()));
safeStore("Verbose", memoryAttrs, memoryBean::isVerbose);
MemoryMetrics memory = new MemoryMetrics(memoryAttrs);

ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
Map<String, Object> threadAttrs = new HashMap<>();
safeStore("AllThreadIds", threadAttrs, threadBean::getAllThreadIds);
safeStore("CurrentThreadCpuTime", threadAttrs, threadBean::getCurrentThreadCpuTime);
safeStore("CurrentThreadUserTime", threadAttrs, threadBean::getCurrentThreadUserTime);
safeStore("DaemonThreadCount", threadAttrs, threadBean::getDaemonThreadCount);
safeStore("PeakThreadCount", threadAttrs, threadBean::getPeakThreadCount);
safeStore("ThreadCount", threadAttrs, threadBean::getThreadCount);
safeStore("TotalStartedThreadCount", threadAttrs, threadBean::getTotalStartedThreadCount);
safeStore(
"CurrentThreadCpuTimeSupported",
threadAttrs,
threadBean::isCurrentThreadCpuTimeSupported);
safeStore(
"ObjectMonitorUsageSupported",
threadAttrs,
threadBean::isObjectMonitorUsageSupported);
safeStore(
"SynchronizerUsageSupported",
threadAttrs,
threadBean::isSynchronizerUsageSupported);
safeStore(
"ThreadContentionMonitoringEnabled",
threadAttrs,
threadBean::isThreadContentionMonitoringEnabled);
safeStore(
"ThreadContentionMonitoringSupported",
threadAttrs,
threadBean::isThreadContentionMonitoringSupported);
safeStore("ThreadCpuTimeEnabled", threadAttrs, threadBean::isThreadCpuTimeEnabled);
safeStore("ThreadCpuTimeSupported", threadAttrs, threadBean::isThreadCpuTimeSupported);
ThreadMetrics threads = new ThreadMetrics(threadAttrs);

OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();
Map<String, Object> osAttrs = new HashMap<>();
safeStore("Arch", osAttrs, osBean::getArch);
safeStore("AvailableProcessors", osAttrs, osBean::getAvailableProcessors);
safeStore("Name", osAttrs, osBean::getName);
safeStore("SystemLoadAverage", osAttrs, osBean::getSystemLoadAverage);
safeStore("Version", osAttrs, osBean::getVersion);
if (osBean instanceof UnixOperatingSystemMXBean) {
UnixOperatingSystemMXBean unix = (UnixOperatingSystemMXBean) osBean;
safeStore("CommittedVirtualMemorySize", osAttrs, unix::getCommittedVirtualMemorySize);
safeStore("FreePhysicalMemorySize", osAttrs, unix::getFreeMemorySize);
safeStore("FreeSwapSpaceSize", osAttrs, unix::getFreeSwapSpaceSize);
safeStore("ProcessCpuLoad", osAttrs, unix::getProcessCpuLoad);
safeStore("ProcessCpuTime", osAttrs, unix::getProcessCpuTime);
safeStore("SystemCpuLoad", osAttrs, unix::getCpuLoad);
safeStore("TotalPhysicalMemorySize", osAttrs, unix::getTotalMemorySize);
safeStore("TotalSwapSpaceSize", osAttrs, unix::getTotalSwapSpaceSize);
}
OperatingSystemMetrics os = new OperatingSystemMetrics(osAttrs);

String jvmId = null;
try (ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
DataOutputStream dos = new DataOutputStream(baos)) {
dos.writeUTF(runtime.getClassPath());
dos.writeUTF(runtime.getName());
dos.writeUTF(Arrays.toString(runtime.getInputArguments()));
dos.writeUTF(runtime.getLibraryPath());
dos.writeUTF(runtime.getVmVendor());
dos.writeUTF(runtime.getVmVersion());
dos.writeLong(runtime.getStartTime());
byte[] hash = DigestUtils.sha256(baos.toByteArray());
jvmId = new String(Base64.getUrlEncoder().encode(hash), StandardCharsets.UTF_8).trim();
} catch (IOException e) {
log.error("Could not compute own jvmId!", e);
}

try {
log.info(mapper.writeValueAsString(runtimeAttrs));
log.info(mapper.writeValueAsString(memory));
log.info(mapper.writeValueAsString(threads));
log.info(mapper.writeValueAsString(os));
log.info(jvmId);
} catch (JsonProcessingException jpe) {
log.error("couldn't serialize", jpe);
}

return new MBeanMetrics(runtime, memory, threads, os, jvmId);
}

private class AgentAuthenticator extends BasicAuthenticator {

private final Logger log = LoggerFactory.getLogger(getClass());
Expand Down

0 comments on commit 109f9b4

Please sign in to comment.