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

feature: add shutdown notifier #28

Merged
merged 11 commits into from
Nov 7, 2024
Merged
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();
EddeCCC marked this conversation as resolved.
Show resolved Hide resolved

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
Loading