Skip to content

Commit

Permalink
Merge pull request #108 from CathalOConnorRH/MGDSTRM-3724
Browse files Browse the repository at this point in the history
MGDSTRM-3724 Add the status code to the keycloak_request_duration_bucket metric
  • Loading branch information
pb82 authored Sep 6, 2021
2 parents 2dcf302 + 7b60b74 commit 4dd6b7b
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,25 @@ public final class MetricsFilter implements ContainerRequestFilter, ContainerRes
private static final String METRICS_REQUEST_TIMESTAMP = "metrics.requestTimestamp";
private static final MetricsFilter INSTANCE = new MetricsFilter();

private static final boolean URI_METRICS_ENABLED = Boolean.parseBoolean(System.getenv("URI_METRICS_ENABLED"));

// relevant response content types to be measured
private static final Set<MediaType> contentTypes = new HashSet<>();

static {
contentTypes.add(MediaType.APPLICATION_JSON_TYPE);
contentTypes.add(MediaType.APPLICATION_XML_TYPE);
contentTypes.add(MediaType.TEXT_HTML_TYPE);
}

private static final Set<MediaType> CONTENT_TYPES = Collections.unmodifiableSet(contentTypes);

public static MetricsFilter instance() {
return INSTANCE;
}

private MetricsFilter() { }
private MetricsFilter() {
}

@Override
public void filter(ContainerRequestContext req) {
Expand All @@ -48,20 +53,31 @@ public void filter(ContainerRequestContext req, ContainerResponseContext res) {
int status = res.getStatus();

String resource = ResourceExtractor.getResource(req.getUriInfo());
String uri = ResourceExtractor.getURI(req.getUriInfo());

PrometheusExporter.instance().recordResponseTotal(status, req.getMethod(), resource);
if (status >= 400) {
PrometheusExporter.instance().recordResponseError(status, req.getMethod(), resource);
if (URI_METRICS_ENABLED) {
PrometheusExporter.instance().recordResponseTotal(status, req.getMethod(), resource, uri);
if (status >= 400) {
PrometheusExporter.instance().recordResponseError(status, req.getMethod(), resource, uri);
}
} else {
PrometheusExporter.instance().recordResponseTotal(status, req.getMethod(), resource);
if (status >= 400) {
PrometheusExporter.instance().recordResponseError(status, req.getMethod(), resource);
}
}

// Record request duration if timestamp property is present
// and only if it is relevant (skip pictures)
if (req.getProperty(METRICS_REQUEST_TIMESTAMP) != null &&
contentTypeIsRelevant(res)) {
long time = (long) req.getProperty(METRICS_REQUEST_TIMESTAMP);
long dur = System.currentTimeMillis() - time;
LOG.trace("Duration is calculated as " + dur + " ms.");
PrometheusExporter.instance().recordRequestDuration(dur, req.getMethod(), resource);
if (URI_METRICS_ENABLED) {
PrometheusExporter.instance().recordRequestDuration(status, dur, req.getMethod(), resource, uri);
} else {
PrometheusExporter.instance().recordRequestDuration(status, dur, req.getMethod(), resource);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public final class PrometheusExporter {

private final static String PROMETHEUS_PUSHGATEWAY_GROUPINGKEY_INSTANCE = "PROMETHEUS_GROUPING_KEY_INSTANCE";
private final static Pattern PROMETHEUS_PUSHGATEWAY_GROUPINGKEY_INSTANCE_ENVVALUE_PATTERN = Pattern.compile("ENVVALUE:(.+?)");

private final static String PROMETHEUS_PUSHGATEWAY_JOB = "PROMETHEUS_PUSHGATEWAY_JOB";

private static PrometheusExporter INSTANCE;
Expand Down Expand Up @@ -149,24 +149,46 @@ private PrometheusExporter() {
.labelNames("realm", "provider", "error", "client_id")
.register();

responseTotal = Counter.build()
final boolean URI_METRICS_ENABLED = Boolean.parseBoolean(System.getenv("URI_METRICS_ENABLED"));
if (URI_METRICS_ENABLED){
responseTotal = Counter.build()
.name("keycloak_response_total")
.help("Total number of responses")
.labelNames("code", "method", "resource", "uri")
.register();

responseErrors = Counter.build()
.name("keycloak_response_errors")
.help("Total number of error responses")
.labelNames("code", "method", "resource", "uri")
.register();

requestDuration = Histogram.build()
.name("keycloak_request_duration")
.help("Request duration")
.buckets(50, 100, 250, 500, 1000, 2000, 10000, 30000)
.labelNames("code", "method", "resource", "uri")
.register();
} else {
responseTotal = Counter.build()
.name("keycloak_response_total")
.help("Total number of responses")
.labelNames("code", "method", "resource")
.register();

responseErrors = Counter.build()
responseErrors = Counter.build()
.name("keycloak_response_errors")
.help("Total number of error responses")
.labelNames("code", "method", "resource")
.register();

requestDuration = Histogram.build()
requestDuration = Histogram.build()
.name("keycloak_request_duration")
.help("Request duration")
.buckets(50, 100, 250, 500, 1000, 2000, 10000, 30000)
.labelNames("method", "resource")
.labelNames("code", "method", "resource")
.register();
}

// Counters for all user events
for (EventType type : EventType.values()) {
Expand Down Expand Up @@ -367,8 +389,30 @@ public void recordCodeToTokenError(final Event event) {
* @param amt The duration in milliseconds
* @param method HTTP method of the request
*/
public void recordRequestDuration(double amt, String method, String resource) {
requestDuration.labels(method, resource).observe(amt);
public void recordRequestDuration(int code, double amt, String method, String resource, String uri) {
requestDuration.labels(Integer.toString(code), method, resource, uri).observe(amt);
pushAsync();
}

/**
* Record the duration between one request and response
*
* @param amt The duration in milliseconds
* @param method HTTP method of the request
*/
public void recordRequestDuration(int code, double amt, String method, String resource) {
requestDuration.labels(Integer.toString(code), method, resource).observe(amt);
pushAsync();
}

/**
* Increase the response total count by a given method and response code
*
* @param code The returned http status code
* @param method The request method used
*/
public void recordResponseTotal(int code, String method, String resource, String uri) {
responseTotal.labels(Integer.toString(code), method, resource, uri).inc();
pushAsync();
}

Expand All @@ -383,6 +427,17 @@ public void recordResponseTotal(int code, String method, String resource) {
pushAsync();
}

/**
* Increase the response error count by a given method and response code
*
* @param code The returned http status code
* @param method The request method used
*/
public void recordResponseError(int code, String method, String resource, String uri) {
responseErrors.labels(Integer.toString(code), method, resource, uri).inc();
pushAsync();
}

/**
* Increase the response error count by a given method and response code
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@

import javax.ws.rs.core.UriInfo;
import java.util.List;
import java.util.regex.*;

class ResourceExtractor {

private final static Logger logger = Logger.getLogger(ResourceExtractor.class);

private static final boolean IS_RESOURCE_SCRAPING_DISABLED = Boolean.getBoolean("RESOURCE_SCRAPING_DISABLED");

private static final boolean URI_METRICS_ENABLED = Boolean.getBoolean("URI_METRICS_ENABLED");
private static final boolean URI_METRICS_DETAILED = Boolean.getBoolean("URI_METRICS_DETAILED");

private ResourceExtractor() {
}
Expand All @@ -35,12 +37,12 @@ private ResourceExtractor() {
* @param uriInfo {@link UriInfo} object obtained from JAX-RS
* @return The resource name.
*/
static String getResource(UriInfo uriInfo) {
static String getResource(UriInfo uriInfo) {
if (!IS_RESOURCE_SCRAPING_DISABLED) {
List<String> matchedURIs = uriInfo.getMatchedURIs();
if (matchedURIs.size() >= 2) {
// A special case for all static resources - we're not interested in
// evey particular resource - just an aggregate with all other endpoints.
// every particular resource - just an aggregate with all other endpoints.
if ("resources".equals(matchedURIs.get(matchedURIs.size() - 1))) {
return "";
}
Expand All @@ -54,4 +56,29 @@ static String getResource(UriInfo uriInfo) {
return "";
}

/**
* This method obtains a list of resource info from the {@link UriInfo} object and returns the resource URI.
* @param uriInfo {@link UriInfo} object obtained from JAX-RS
* @return The resource uri.
*/
static String getURI(UriInfo uriInfo) {
if (URI_METRICS_ENABLED) {
List<String> matchedURIs = uriInfo.getMatchedURIs();
StringBuilder sb = new StringBuilder();
if (matchedURIs.get(0).contains("/token"))
{
String uri = matchedURIs.get(0);

if(URI_METRICS_DETAILED) {
sb.append(uri);
} else {
String[] realm = uri.split("/");
uri=uri.replace(realm[1], "{realm}");
sb.append(uri);
}
}
return sb.toString();
}
return "";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -265,28 +265,31 @@ public void shouldCorrectlyRecordGenericAdminEvents() throws IOException {

@Test
public void shouldCorrectlyRecordResponseDurations() throws IOException {
PrometheusExporter.instance().recordRequestDuration(5, "GET", "admin,admin/serverinfo");
environmentVariables.set("URI_METRICS_ENABLED", "true");
PrometheusExporter.instance().recordRequestDuration(200, 5, "GET", "admin,admin/serverinfo", "auth/realm");
assertGenericMetric("keycloak_request_duration_count", 1,
tuple("method", "GET"), tuple("resource", "admin,admin/serverinfo"));
tuple("code","200"), tuple("method", "GET"), tuple("resource", "admin,admin/serverinfo"), tuple("uri", "auth/realm"));
assertGenericMetric("keycloak_request_duration_sum", 5,
tuple("method", "GET"), tuple("resource", "admin,admin/serverinfo"));
tuple("code","200"), tuple("method", "GET"), tuple("resource", "admin,admin/serverinfo"), tuple("uri", "auth/realm"));
}

@Test
public void shouldCorrectlyRecordResponseTotal() throws IOException {
PrometheusExporter.instance().recordResponseTotal(200, "GET", "admin,admin/serverinfo");
PrometheusExporter.instance().recordResponseTotal(500, "POST", "admin,admin/serverinfo");
environmentVariables.set("URI_METRICS_ENABLED", "true");
PrometheusExporter.instance().recordResponseTotal(200, "GET", "admin,admin/serverinfo", "auth/realm");
PrometheusExporter.instance().recordResponseTotal(500, "POST", "admin,admin/serverinfo", "auth/realm");
assertGenericMetric("keycloak_response_total", 1,
tuple("code", "200"), tuple("method", "GET"), tuple("resource", "admin,admin/serverinfo"));
tuple("code", "200"), tuple("method", "GET"), tuple("resource", "admin,admin/serverinfo"), tuple("uri", "auth/realm"));
assertGenericMetric("keycloak_response_total", 1,
tuple("code", "500"), tuple("method", "POST"), tuple("resource", "admin,admin/serverinfo"));
tuple("code", "500"), tuple("method", "POST"), tuple("resource", "admin,admin/serverinfo"), tuple("uri", "auth/realm"));
}

@Test
public void shouldCorrectlyRecordResponseErrors() throws IOException {
PrometheusExporter.instance().recordResponseError(500, "POST", "admin,admin/serverinfo");
environmentVariables.set("URI_METRICS_ENABLED", "true");
PrometheusExporter.instance().recordResponseError(500, "POST", "admin,admin/serverinfo", "auth/realm");
assertGenericMetric("keycloak_response_errors", 1,
tuple("code", "500"), tuple("method", "POST"), tuple("resource", "admin,admin/serverinfo"));
tuple("code", "500"), tuple("method", "POST"), tuple("resource", "admin,admin/serverinfo"), tuple("uri", "auth/realm"));
}

@Test
Expand Down

0 comments on commit 4dd6b7b

Please sign in to comment.