Skip to content

Commit

Permalink
feature: add shutdown notifier (#28)
Browse files Browse the repository at this point in the history
* feature: add shutdown notifier

* feature: add shutdown notification model

* little refactor

* use agent ID as request param

* fix test

* test: fix callback concurrency issue

* refactor: ShutdownHookManager

* use config 3.0.3-dev

* make INFO final

* fix test
  • Loading branch information
EddeCCC authored Nov 7, 2024
1 parent 70e1be2 commit e749399
Show file tree
Hide file tree
Showing 26 changed files with 561 additions and 130 deletions.
5 changes: 3 additions & 2 deletions inspectit-gepard-agent/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ plugins {
}

group 'rocks.inspectit.gepard'
def configVersion = "3.0.2-dev"
def configVersion = "3.0.3-dev"

sourceCompatibility = "17"
targetCompatibility = "17"
Expand Down Expand Up @@ -76,7 +76,8 @@ dependencies {
implementation("org.slf4j:slf4j-api:2.0.16")
// http client for server notification
implementation("org.apache.httpcomponents.client5:httpclient5:5.3.1")
implementation("com.fasterxml.jackson.core:jackson-databind:2.17.1")
implementation("com.fasterxml.jackson.core:jackson-databind:2.18.1")
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.1")
implementation("com.github.ben-manes.caffeine:caffeine:3.1.8")

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import rocks.inspectit.gepard.agent.instrumentation.state.configuration.InspectitConfigurationHolder;
import rocks.inspectit.gepard.agent.instrumentation.state.configuration.resolver.ConfigurationResolver;
import rocks.inspectit.gepard.agent.internal.otel.OpenTelemetryAccessor;
import rocks.inspectit.gepard.agent.internal.shutdown.ShutdownHookManager;
import rocks.inspectit.gepard.agent.notification.NotificationManager;
import rocks.inspectit.gepard.agent.transformation.TransformationManager;

Expand All @@ -41,10 +42,6 @@ public AgentBuilder extend(AgentBuilder agentBuilder, ConfigProperties config) {
BootstrapManager bootstrapManager = BootstrapManager.create();
bootstrapManager.appendToBootstrapClassLoader();

// Notify configuration server about this agent
NotificationManager notificationManager = NotificationManager.create();
notificationManager.sendStartNotification();

// Set our global OpenTelemetry instance. For now, we use the Agent SDK
OpenTelemetryAccessor.setOpenTelemetry(GlobalOpenTelemetry.get());

Expand Down Expand Up @@ -72,6 +69,12 @@ public AgentBuilder extend(AgentBuilder agentBuilder, ConfigProperties config) {
ConfigurationManager configurationManager = ConfigurationManager.create();
configurationManager.loadConfiguration();

// Notify configuration server about this agent
NotificationManager notificationManager = NotificationManager.create();
notificationManager.sendStartNotification();
// Set up shutdown notification to configuration server
notificationManager.setUpShutdownNotification();

addShutdownHook();

return agentBuilder;
Expand All @@ -82,12 +85,9 @@ public String extensionName() {
return "inspectit-gepard";
}

/** This should be the last log message of the agent at shutdown. */
private void addShutdownHook() {
Runtime.getRuntime()
.addShutdownHook(
new Thread(
() -> {
log.info("Shutting down inspectIT Gepard agent extension...");
}));
Runnable shutdownHook = () -> log.info("Shutting down inspectIT Gepard agent extension...");
ShutdownHookManager.getInstance().addShutdownHookLast(shutdownHook);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ private MethodHookFactory() {}
public static MethodHook createHook(MethodHookConfiguration hookConfig) {
MethodHook.Builder builder = MethodHook.builder().setConfiguration(hookConfig);

if (hookConfig.getTracing().getStartSpan()) builder.setSpanAction(new SpanAction());
if (hookConfig.getTracing().isStartSpan()) builder.setSpanAction(new SpanAction());

return builder.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,9 @@ public void notifyObservers(InspectitConfiguration configuration) {
observer.handleConfiguration(event);
}
}

/** Method for testing. */
public void clear() {
observers.clear();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ private HttpRequestSender() {}
*
* @param request the HTTP request
* @param callback the callback function
* @return True, if the HTTP request returned a status code in {@link successfulStatusCodes}
* @return True, if the HTTP request returned a status code in {@link #successfulStatusCodes}
*/
public static boolean send(
SimpleHttpRequest request, FutureCallback<SimpleHttpResponse> callback) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,64 +1,68 @@
/* (C) 2024 */
package rocks.inspectit.gepard.agent.internal.identity.model;

import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.opentelemetry.javaagent.bootstrap.internal.InstrumentationConfig;
import io.opentelemetry.javaagent.tooling.AgentVersion;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.time.Instant;
import java.util.Map;
import java.util.Objects;
import rocks.inspectit.gepard.agent.internal.identity.IdentityManager;
import rocks.inspectit.gepard.agent.internal.properties.PropertiesResolver;
import rocks.inspectit.gepard.commons.model.agent.Agent;

/** Meta-information about the current agent */
public class AgentInfo {
public final class AgentInfo {

/** Global instance of agent information */
public static final AgentInfo INFO = new AgentInfo();

private static final ObjectMapper mapper =
new ObjectMapper().setVisibility(PropertyAccessor.FIELD, Visibility.ANY);

private final String serviceName;

private final String gepardVersion;

private final String otelVersion;

private final String javaVersion;

private final long startTime;

private final String vmId;
private final Agent agent;

private final String agentId;

private final Map<String, String> attributes;

private AgentInfo() {
IdentityManager identityManager = IdentityManager.getInstance();
IdentityInfo identityInfo = identityManager.getIdentityInfo();
RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();

this.serviceName = getServiceNameFromSdk();
this.gepardVersion = "0.0.1";
this.otelVersion = AgentVersion.VERSION;
this.javaVersion = System.getProperty("java.version");
this.startTime = runtime.getStartTime();
this.vmId = identityInfo.vmId();
this.agent = createAgent(identityInfo);
this.agentId = identityInfo.agentId();
this.attributes = PropertiesResolver.getAttributes();
}

/**
* @return The agent information as JSON string
* @throws JsonProcessingException corrupted agent information
* Creates an agent model with the current meta-information.
*
* @param identityInfo the agent's identity info
* @return the created agent model
*/
private Agent createAgent(IdentityInfo identityInfo) {
RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();

String serviceName = getServiceNameFromSdk();
String gepardVersion = "0.0.1";
String otelVersion = AgentVersion.VERSION;
String javaVersion = System.getProperty("java.version");
Instant startTime = Instant.ofEpochMilli(runtime.getStartTime());
String vmId = identityInfo.vmId();
Map<String, String> attributes = PropertiesResolver.getAttributes();

return new Agent(
serviceName, gepardVersion, otelVersion, javaVersion, startTime, vmId, attributes);
}

/**
* @return the agent meta-information
*/
public Agent getAgent() {
return agent;
}

/**
* @return the agent id
*/
public static String getAsString() throws JsonProcessingException {
return mapper.writeValueAsString(INFO);
public String getAgentId() {
return agentId;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.util.concurrent.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rocks.inspectit.gepard.agent.internal.shutdown.ShutdownHookManager;

/**
* Global manager, who starts scheduled task and keeps track of them. At shutdown all scheduled
Expand Down Expand Up @@ -58,17 +59,16 @@ public boolean startRunnable(NamedRunnable runnable, Duration interval) {
return true;
}

/** Add hook, so every scheduled future will be cancelled at shutdown */
/** Add hook, so every scheduled future will be cancelled at shutdown. */
private void addShutdownHook() {
Runtime.getRuntime()
.addShutdownHook(
new Thread(
() ->
scheduledFutures.forEach(
(name, future) -> {
log.info("Shutting down {}...", name);
future.cancel(true);
})));
Runnable shutdownHook =
() ->
scheduledFutures.forEach(
(name, future) -> {
log.info("Shutting down {}...", name);
future.cancel(true);
});
ShutdownHookManager.getInstance().addShutdownHook(shutdownHook);
}

/**
Expand All @@ -82,9 +82,16 @@ private boolean isAlreadyScheduled(String runnableName) {
}

/**
* Method for testing
*
* @return the number of scheduled futures
*/
public int getNumberOfScheduledFutures() {
return scheduledFutures.size();
}

/** Method for testing. */
public void clearScheduledFutures() {
scheduledFutures.clear();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/* (C) 2024 */
package rocks.inspectit.gepard.agent.internal.shutdown;

import com.google.common.annotations.VisibleForTesting;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Responsible for executing the agents shutdown hooks in the order they are added. We should try to
* keep the amount of shutdown hooks as well as the logic inside them minimal to prevent long
* shutdown time.
*/
public class ShutdownHookManager {
private static final Logger log = LoggerFactory.getLogger(ShutdownHookManager.class);

private static ShutdownHookManager instance;

/** The registered shutdown hooks */
private final Set<ShutdownHook> shutdownHooks;

private final AtomicBoolean isShutdown;

private ShutdownHookManager() {
shutdownHooks = Collections.synchronizedSet(new HashSet<>());
isShutdown = new AtomicBoolean(false);
}

/**
* @return the singleton instance
*/
public static ShutdownHookManager getInstance() {
if (Objects.isNull(instance)) {
instance = new ShutdownHookManager();
instance.setUpShutdownHooks();
}
return instance;
}

/**
* Adds the runnable to the shutdown hooks at the beginning of the set. During shutdown, no new
* hooks can be added.
*/
public void addShutdownHook(Runnable runnable) {
if (!isShutdown.get()) {
ShutdownHook shutdownHook = new ShutdownHook(runnable, 0);
shutdownHooks.add(shutdownHook);
}
}

/**
* Adds the runnable to the shutdown hooks at the end of the set. During shutdown, no new hooks
* can be added.
*/
public void addShutdownHookLast(Runnable runnable) {
if (!isShutdown.get()) {
ShutdownHook shutdownHook = new ShutdownHook(runnable, Integer.MAX_VALUE);
shutdownHooks.add(shutdownHook);
}
}

/** Sets up the registered shutdown hooks, to be executed at shutdown */
private void setUpShutdownHooks() {
Runtime.getRuntime()
.addShutdownHook(new Thread(this::executeShutdownHooks, "inspectit-shutdown"));
}

/** Executes all registered shutdown hooks by order */
@VisibleForTesting
void executeShutdownHooks() {
if (!isShutdown.compareAndSet(false, true)) log.info("Cannot execute shutdown hooks twice");
else
shutdownHooks.stream()
.sorted(Comparator.comparingInt(ShutdownHook::getOrder))
.forEach(this::tryRunShutdownHook);
}

/** Try-catch-wrapper to run a shutdown hook */
private void tryRunShutdownHook(ShutdownHook shutdownHook) {
try {
shutdownHook.run();
} catch (Exception e) {
log.error("Error while executing shutdown hook", e);
}
}

/**
* Method for testing.
*
* @return the current amount of registered shutdown hooks
*/
public int getShutdownHookCount() {
return shutdownHooks.size();
}

/** Method for testing. */
public void reset() {
shutdownHooks.clear();
isShutdown.set(false);
}

/** Internal class for ordered shutdown hooks */
private static class ShutdownHook {

private final Runnable shutdownHook;

private final int order;

ShutdownHook(Runnable shutdownHook, int order) {
this.shutdownHook = shutdownHook;
this.order = order;
}

void run() {
shutdownHook.run();
}

int getOrder() {
return order;
}
}
}
Loading

0 comments on commit e749399

Please sign in to comment.