added automatic metrics for micrometer

This commit is contained in:
lucio 2023-02-01 17:18:13 +01:00
parent a76b823c49
commit e48f50e91b
9 changed files with 126 additions and 89 deletions

View File

@ -16,9 +16,11 @@ import org.slf4j.LoggerFactory;
import io.micrometer.core.instrument.Metrics; import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.binder.jvm.ClassLoaderMetrics; import io.micrometer.core.instrument.binder.jvm.ClassLoaderMetrics;
import io.micrometer.core.instrument.binder.jvm.JvmGcMetrics;
import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics; import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics;
import io.micrometer.core.instrument.binder.jvm.JvmThreadMetrics; import io.micrometer.core.instrument.binder.jvm.JvmThreadMetrics;
import io.micrometer.core.instrument.binder.system.ProcessorMetrics; import io.micrometer.core.instrument.binder.system.ProcessorMetrics;
import io.micrometer.core.instrument.binder.system.UptimeMetrics;
import io.micrometer.prometheus.PrometheusConfig; import io.micrometer.prometheus.PrometheusConfig;
import io.micrometer.prometheus.PrometheusMeterRegistry; import io.micrometer.prometheus.PrometheusMeterRegistry;
@ -95,12 +97,14 @@ public class Bootstrap implements ServletContainerInitializer {
context = ProviderFactory.provider().containerContext(); context = ProviderFactory.provider().containerContext();
PrometheusMeterRegistry registry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT); PrometheusMeterRegistry registry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
registry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
new ClassLoaderMetrics().bindTo(registry); new ClassLoaderMetrics().bindTo(registry);
new JvmMemoryMetrics().bindTo(registry); new JvmMemoryMetrics().bindTo(registry);
//new JvmGcMetrics().bindTo(registry); new JvmGcMetrics().bindTo(registry);
new ProcessorMetrics().bindTo(registry); new ProcessorMetrics().bindTo(registry);
new JvmThreadMetrics().bindTo(registry); new JvmThreadMetrics().bindTo(registry);
new UptimeMetrics().bindTo(registry);
new ProcessorMetrics().bindTo(registry);
Metrics.addRegistry(registry); Metrics.addRegistry(registry);

View File

@ -98,6 +98,11 @@ public class Constants {
*/ */
public static final String request_validation = "request-validation"; public static final String request_validation = "request-validation";
/**
* The configuration name of {@link RequestMetrics}s.
*/
public static final String request_metrics = "request-metrics";
/** /**
* The configuration name of {@link RequestValidator}s. * The configuration name of {@link RequestValidator}s.
*/ */

View File

@ -19,6 +19,7 @@ import org.gcube.smartgears.handlers.application.ApplicationLifecycleHandler;
import org.gcube.smartgears.handlers.application.RequestHandler; import org.gcube.smartgears.handlers.application.RequestHandler;
import org.gcube.smartgears.handlers.application.lifecycle.ApplicationProfileManager; import org.gcube.smartgears.handlers.application.lifecycle.ApplicationProfileManager;
import org.gcube.smartgears.handlers.application.request.RequestAccounting; import org.gcube.smartgears.handlers.application.request.RequestAccounting;
import org.gcube.smartgears.handlers.application.request.RequestMetrics;
import org.gcube.smartgears.handlers.application.request.RequestValidator; import org.gcube.smartgears.handlers.application.request.RequestValidator;
/** /**
@ -66,6 +67,7 @@ public class ApplicationConfigurationBinder {
List<RequestHandler> requestHandlers = new LinkedList<RequestHandler>(); List<RequestHandler> requestHandlers = new LinkedList<RequestHandler>();
//ADDING BASE Handler (order is important) //ADDING BASE Handler (order is important)
requestHandlers.add(new RequestMetrics());
requestHandlers.add(new RequestValidator()); requestHandlers.add(new RequestValidator());
requestHandlers.add(new RequestAccounting()); requestHandlers.add(new RequestAccounting());

View File

@ -6,7 +6,6 @@ import org.gcube.common.events.Hub;
import org.gcube.smartgears.configuration.application.ApplicationConfiguration; import org.gcube.smartgears.configuration.application.ApplicationConfiguration;
import org.gcube.smartgears.context.Properties; import org.gcube.smartgears.context.Properties;
import org.gcube.smartgears.context.container.ContainerContext; import org.gcube.smartgears.context.container.ContainerContext;
import org.gcube.smartgears.health.HealthManager;
import org.gcube.smartgears.lifecycle.application.ApplicationLifecycle; import org.gcube.smartgears.lifecycle.application.ApplicationLifecycle;
import org.gcube.smartgears.persistence.PersistenceWriter; import org.gcube.smartgears.persistence.PersistenceWriter;
import org.gcube.smartgears.security.AuthorizationProvider; import org.gcube.smartgears.security.AuthorizationProvider;

View File

@ -22,6 +22,10 @@ public abstract class RequestHandler extends AbstractHandler implements Applicat
abstract public String getName(); abstract public String getName();
public boolean isUnfiltrable() {
return false;
}
/** /**
* Initialises the handler. * Initialises the handler.
* *

View File

@ -83,8 +83,9 @@ public class ApplicationProfileManager extends ApplicationLifecycleHandler {
publishers = context.container().configuration().mode()!=Mode.offline? publishers = context.container().configuration().mode()!=Mode.offline?
ProviderFactory.provider().publishers(): ProviderFactory.provider().publishers():
Collections.emptyList(); Collections.emptyList();
registerObservers();
schedulePeriodicUpdates(); registerObservers();
schedulePeriodicUpdates();
} }
// helpers // helpers

View File

@ -1,7 +1,5 @@
package org.gcube.smartgears.handlers.application.request; package org.gcube.smartgears.handlers.application.request;
import java.util.concurrent.TimeUnit;
import org.gcube.accounting.datamodel.UsageRecord.OperationResult; import org.gcube.accounting.datamodel.UsageRecord.OperationResult;
import org.gcube.accounting.datamodel.usagerecords.ServiceUsageRecord; import org.gcube.accounting.datamodel.usagerecords.ServiceUsageRecord;
import org.gcube.accounting.persistence.AccountingPersistence; import org.gcube.accounting.persistence.AccountingPersistence;
@ -17,13 +15,11 @@ import org.gcube.smartgears.utils.InnerMethodName;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import io.micrometer.core.instrument.Metrics;
public class RequestAccounting extends RequestHandler { public class RequestAccounting extends RequestHandler {
private static Logger log = LoggerFactory.getLogger(RequestAccounting.class); private static Logger log = LoggerFactory.getLogger(RequestAccounting.class);
private static ThreadLocal<Long> startCallThreadLocal = new ThreadLocal<Long>(); private static ThreadLocal<Long> startCallThreadLocal = new ThreadLocal<Long>();
private static final String UNKNOWN = "Unknown"; private static final String UNKNOWN = "Unknown";
@ -36,24 +32,24 @@ public class RequestAccounting extends RequestHandler {
public void handleRequest(RequestEvent e) { public void handleRequest(RequestEvent e) {
ApplicationContext appContext = e.context(); ApplicationContext appContext = e.context();
String context = getContext(appContext); String context = getContext(appContext);
if (InnerMethodName.instance.get()==null) { if (InnerMethodName.instance.get() == null) {
String calledMethod = e.request().getRequestURI().substring(e.request().getContextPath().length()); String calledMethod = e.request().getRequestURI().substring(e.request().getContextPath().length());
if (calledMethod.isEmpty()) if (calledMethod.isEmpty())
calledMethod = "/"; calledMethod = "/";
calledMethod= e.request().getMethod()+" "+calledMethod; calledMethod = e.request().getMethod() + " " + calledMethod;
InnerMethodName.instance.set(calledMethod); InnerMethodName.instance.set(calledMethod);
} }
String caller = SecretManagerProvider.instance.get()!=null ? SecretManagerProvider.instance.get().getOwner().getId(): String caller = SecretManagerProvider.instance.get() != null
UNKNOWN; ? SecretManagerProvider.instance.get().getOwner().getId()
: UNKNOWN;
startCallThreadLocal.set(System.currentTimeMillis()); startCallThreadLocal.set(System.currentTimeMillis());
log.info("REQUEST START ON {}:{}({}) CALLED FROM {}@{} IN SCOPE {} ", log.info("REQUEST START ON {}:{}({}) CALLED FROM {}@{} IN SCOPE {} ", appContext.configuration().name(),
appContext.configuration().name(),appContext.configuration().serviceClass(), InnerMethodName.instance.get(), appContext.configuration().serviceClass(), InnerMethodName.instance.get(), caller,
caller, e.request().getRemoteHost(), context ); e.request().getRemoteHost(), context);
} }
@ -65,33 +61,41 @@ public class RequestAccounting extends RequestHandler {
String context = getContext(appContext); String context = getContext(appContext);
String caller = SecretManagerProvider.instance.get()!=null ? SecretManagerProvider.instance.get().getOwner().getId(): String caller = SecretManagerProvider.instance.get() != null
UNKNOWN; ? SecretManagerProvider.instance.get().getOwner().getId()
: UNKNOWN;
String callerQualifier = UNKNOWN; String callerQualifier = UNKNOWN;
//retieves caller Ip when there is a proxy // retieves caller Ip when there is a proxy
String callerIp = e.request().getHeader("x-forwarded-for"); String callerIp = e.request().getHeader("x-forwarded-for");
if(callerIp==null) if (callerIp == null)
callerIp=e.request().getRemoteHost(); callerIp = e.request().getRemoteHost();
boolean success = e.response().getStatus()<400; boolean success = e.response().getStatus() < 400;
if (appContext.container().configuration().mode()!=Mode.offline) if (appContext.container().configuration().mode() != Mode.offline)
generateAccounting(caller,callerQualifier,callerIp==null?UNKNOWN:callerIp , success, context, appContext); generateAccounting(caller, callerQualifier, callerIp == null ? UNKNOWN : callerIp, success, context,
appContext);
long durationInMillis = System.currentTimeMillis()-startCallThreadLocal.get(); long durationInMillis = System.currentTimeMillis() - startCallThreadLocal.get();
Metrics.globalRegistry.timer("http.requests", "response",Integer.toString(e.response().getStatus()) /*
, "context", context, "result", success?"SUCCEDED":"FAILED", "caller-ip", callerIp, * Metrics.globalRegistry.timer("smartgears.requests",
"caller-username", caller, "service-class", appContext.configuration().serviceClass(), "service-name", appContext.configuration().name(), * "response",Integer.toString(e.response().getStatus()) , "context", context,
"method", InnerMethodName.instance.get()).record(durationInMillis, TimeUnit.MILLISECONDS); * "result", success?"SUCCEDED":"FAILED", "caller-ip", callerIp,
* "caller-username", caller, "service-class",
log.info("REQUEST SERVED ON {}:{}({}) CALLED FROM {}@{} IN SCOPE {} {}(CODE {}) IN {} millis", * appContext.configuration().serviceClass(), "service-name",
appContext.configuration().name(),appContext.configuration().serviceClass(), InnerMethodName.instance.get(), * appContext.configuration().name(), "method",
caller, callerIp, context, success?"SUCCEDED":"FAILED", e.response().getStatus(),durationInMillis); * InnerMethodName.instance.get()).record(durationInMillis,
* TimeUnit.MILLISECONDS);
*/
log.info("REQUEST SERVED ON {}:{}({}) CALLED FROM {}@{} IN SCOPE {} {}(CODE {}) IN {} millis",
appContext.configuration().name(), appContext.configuration().serviceClass(),
InnerMethodName.instance.get(), caller, callerIp, context, success ? "SUCCEDED" : "FAILED",
e.response().getStatus(), durationInMillis);
}catch (Exception e1) { } catch (Exception e1) {
log.error("error on accounting",e); log.error("error on accounting", e);
throw e1; throw e1;
} finally { } finally {
startCallThreadLocal.remove(); startCallThreadLocal.remove();
@ -100,11 +104,13 @@ public class RequestAccounting extends RequestHandler {
} }
void generateAccounting(String caller, String callerQualifier, String remoteHost, boolean success, String gcubeContext, ApplicationContext appContext){ void generateAccounting(String caller, String callerQualifier, String remoteHost, boolean success,
AccountingPersistenceFactory.setFallbackLocation(appContext.container().configuration().accountingFallbackLocation()); String gcubeContext, ApplicationContext appContext) {
AccountingPersistenceFactory
.setFallbackLocation(appContext.container().configuration().accountingFallbackLocation());
AccountingPersistence persistence = AccountingPersistenceFactory.getPersistence(); AccountingPersistence persistence = AccountingPersistenceFactory.getPersistence();
ServiceUsageRecord serviceUsageRecord = new ServiceUsageRecord(); ServiceUsageRecord serviceUsageRecord = new ServiceUsageRecord();
try{ try {
serviceUsageRecord.setConsumerId(caller); serviceUsageRecord.setConsumerId(caller);
serviceUsageRecord.setCallerQualifier(callerQualifier); serviceUsageRecord.setCallerQualifier(callerQualifier);
@ -112,21 +118,22 @@ public class RequestAccounting extends RequestHandler {
serviceUsageRecord.setServiceClass(appContext.configuration().serviceClass()); serviceUsageRecord.setServiceClass(appContext.configuration().serviceClass());
serviceUsageRecord.setServiceName(appContext.configuration().name()); serviceUsageRecord.setServiceName(appContext.configuration().name());
serviceUsageRecord.setHost(appContext.container().configuration().hostname()+":"+appContext.container().configuration().port()); serviceUsageRecord.setHost(appContext.container().configuration().hostname() + ":"
+ appContext.container().configuration().port());
serviceUsageRecord.setCalledMethod(InnerMethodName.instance.get()); serviceUsageRecord.setCalledMethod(InnerMethodName.instance.get());
serviceUsageRecord.setCallerHost(remoteHost); serviceUsageRecord.setCallerHost(remoteHost);
serviceUsageRecord.setOperationResult(success?OperationResult.SUCCESS:OperationResult.FAILED); serviceUsageRecord.setOperationResult(success ? OperationResult.SUCCESS : OperationResult.FAILED);
serviceUsageRecord.setDuration(System.currentTimeMillis()-startCallThreadLocal.get()); serviceUsageRecord.setDuration(System.currentTimeMillis() - startCallThreadLocal.get());
persistence.account(serviceUsageRecord); persistence.account(serviceUsageRecord);
}catch(Exception ex){ } catch (Exception ex) {
log.warn("invalid record passed to accounting ",ex); log.warn("invalid record passed to accounting ", ex);
} }
} }
private String getContext(ApplicationContext appContext) { private String getContext(ApplicationContext appContext) {
String infrastructure = appContext.container().configuration().infrastructure(); String infrastructure = appContext.container().configuration().infrastructure();
String context= "/"+infrastructure; String context = "/" + infrastructure;
if (SecretManagerProvider.instance.get() != null) if (SecretManagerProvider.instance.get() != null)
context = SecretManagerProvider.instance.get().getContext(); context = SecretManagerProvider.instance.get().getContext();
return context; return context;
@ -137,6 +144,4 @@ public class RequestAccounting extends RequestHandler {
return getName(); return getName();
} }
} }

View File

@ -0,0 +1,48 @@
package org.gcube.smartgears.handlers.application.request;
import java.time.Duration;
import org.gcube.smartgears.Constants;
import org.gcube.smartgears.handlers.application.RequestEvent;
import org.gcube.smartgears.handlers.application.RequestHandler;
import org.gcube.smartgears.handlers.application.ResponseEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.micrometer.core.instrument.Metrics;
public class RequestMetrics extends RequestHandler {
private static Logger log = LoggerFactory.getLogger(RequestMetrics.class);
private static ThreadLocal<Long> startCallThreadLocal = new ThreadLocal<Long>();
private static final String HTTP_REQUEST_METRICS_NAME = "http.server.requests";
@Override
public String getName() {
return Constants.request_metrics;
}
@Override
public boolean isUnfiltrable() {
return true;
}
@Override
public void handleRequest(RequestEvent e) {
startCallThreadLocal.set(System.currentTimeMillis());
}
@Override
public void handleResponse(ResponseEvent e) {
String statusCode = Integer.toString(e.response().getStatus());
Metrics.globalRegistry.timer(HTTP_REQUEST_METRICS_NAME, "status", statusCode).record(Duration.ofMillis(System.currentTimeMillis() - startCallThreadLocal.get()));
Metrics.globalRegistry.counter(HTTP_REQUEST_METRICS_NAME, "status", statusCode);
startCallThreadLocal.remove();
log.debug("metrics for current call saved");
}
}

View File

@ -8,6 +8,7 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import javax.servlet.Filter; import javax.servlet.Filter;
import javax.servlet.FilterChain; import javax.servlet.FilterChain;
@ -139,19 +140,18 @@ public class RequestManager implements Filter {
for (Exclude exclude : context.configuration().excludes()){ for (Exclude exclude : context.configuration().excludes()){
String excludePath= exclude.getPath(); String excludePath= exclude.getPath();
log.trace("exclude is {}",exclude); log.trace("exclude is {}",exclude);
if ( if ((WILDCARD).equals(excludePath) ||
(WILDCARD).equals(excludePath) ||
(excludePath.endsWith(WILDCARD) && path!=null && path.startsWith(excludePath.substring(0,excludePath.length()-2))) || (excludePath.endsWith(WILDCARD) && path!=null && path.startsWith(excludePath.substring(0,excludePath.length()-2))) ||
excludePath.equals(path) || (path.endsWith("/") && excludePath.equals(path.substring(0, path.length()-1))) excludePath.equals(path) || (path.endsWith("/") && excludePath.equals(path.substring(0, path.length()-1)))
){ ){
//ALL handler are filtered //ALL handler are filtered except for unfiltrable
if (exclude.getHandlers().isEmpty()) return Collections.emptyList(); if (exclude.getHandlers().isEmpty()) return handlersToFilter.stream().filter(RequestHandler::isUnfiltrable).toList();
List<RequestHandler> filteredHandlers = new ArrayList<>(); List<RequestHandler> filteredHandlers = new ArrayList<>();
for (RequestHandler rh : handlersToFilter){ for (RequestHandler rh : handlersToFilter)
if (!exclude.getHandlers().contains(rh.getName())) if (rh.isUnfiltrable() || !exclude.getHandlers().contains(rh.getName()))
filteredHandlers.add(rh); filteredHandlers.add(rh);
}
return filteredHandlers; return filteredHandlers;
} }
} }
@ -168,10 +168,10 @@ public class RequestManager implements Filter {
if (include.getHandlers().isEmpty()) return handlersToFilter; if (include.getHandlers().isEmpty()) return handlersToFilter;
List<RequestHandler> filteredHandlers = new ArrayList<>(); List<RequestHandler> filteredHandlers = new ArrayList<>();
for (RequestHandler rh : handlersToFilter){ for (RequestHandler rh : handlersToFilter)
if (include.getHandlers().contains(rh.getName())) if (rh.isUnfiltrable() || include.getHandlers().contains(rh.getName()))
filteredHandlers.add(rh); filteredHandlers.add(rh);
}
return filteredHandlers; return filteredHandlers;
} }
} }
@ -213,37 +213,6 @@ public class RequestManager implements Filter {
} }
// helpers
/*
private boolean shouldExcludeRequest(HttpServletRequest request) {
String query = request.getQueryString();
log.debug("servletPath is {} and pathInfo is {}",request.getServletPath(), request.getPathInfo());
if ("wsdl".equals(query) || "wsdl=1".equals(query))
return true;
String path = request.getServletPath()==null?"":request.getServletPath();
path += request.getPathInfo() ==null?"":request.getPathInfo();
log.debug("check if should exclude call with path {}", path);
for (Exclude exclude : context.configuration().excludes()){
if (!exclude.getHandlers().isEmpty()) continue;
String excludePath= exclude.getPath();
log.trace("exclude is {}",exclude);
if (
(EXCLUDE_ALL).equals(exclude) ||
(excludePath.endsWith(EXCLUDE_ALL) && path!=null && path.startsWith(excludePath.substring(0,excludePath.length()-2))) ||
excludePath.equals(path) || (path.endsWith("/") && excludePath.equals(path.substring(0, path.length()-1)))
)
return true;
}
return false;
}*/
private void handleError(HttpServletRequest request, HttpServletResponse response,Throwable t) throws IOException { private void handleError(HttpServletRequest request, HttpServletResponse response,Throwable t) throws IOException {