Skip to content

Commit

Permalink
fixes #155: Add statistics for endpoint responses
Browse files Browse the repository at this point in the history
This commit adds statistics for every response generated in response to a HTTP request on one of the REST API endpoints.

The statistics show the amount of responses for each particular 'family' of status codes (1xx, 2xx, 3xx, 4xx and 5xx).

The Monitoring plugin can be used to review graphs of this data. Note that the current latest release of the Monitoring plugin (2.3.1) has an issue that affects the functionality when a plugin that _provides_ a statistic is unloaded/reloaded. See igniterealtime/openfire-monitoring-plugin#238 for details. It is recommended to use this commit only in combination with a version of the Monitoring plugin in which this issue is fixed. A work-around for the issue, if it does occur, is to restart Openfire after a plugin has been unloaded/reloaded.
  • Loading branch information
guusdk committed Sep 29, 2022
1 parent 72db512 commit fa68733
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 4 deletions.
19 changes: 19 additions & 0 deletions src/i18n/restapi_i18n.properties
Original file line number Diff line number Diff line change
@@ -1 +1,20 @@
system_property.plugin.restapi.muc.case-insensitive-lookup.enabled=Names of MUC rooms should be node-prepped. This, however, was not guaranteed the case in some versions of Openfire and this plugin. Earlier versions of this plugin used a case-insensitive lookup to work around this. As this should be unneeded, and is quite resource intensive, this behavior has been made configurable (disabled by default).

stat.restapi_responses.informational.name=REST API 1xx responses
stat.restapi_responses.informational.desc=The amount of HTTP responses that had an 'Informational' status (a code in the 1xx range).
stat.restapi_responses.informational.units=Responses
stat.restapi_responses.successful.name=REST API 2xx responses
stat.restapi_responses.successful.desc=The amount of HTTP responses that had a 'Successful' status (a code in the 2xx range).
stat.restapi_responses.successful.units=Responses
stat.restapi_responses.redirection.name=REST API 3xx responses
stat.restapi_responses.redirection.desc=The amount of HTTP responses that had a 'Redirection' status (a code in the 3xx range).
stat.restapi_responses.redirection.units=Responses
stat.restapi_responses.client_error.name=REST API 4xx responses
stat.restapi_responses.client_error.desc=The amount of HTTP responses that had a 'Client Error' status (a code in the 4xx range).
stat.restapi_responses.client_error.units=Responses
stat.restapi_responses.server_error.name=REST API 5xx responses
stat.restapi_responses.server_error.desc=The amount of HTTP responses that had a 'Server Error' status (a code in the 5xx range).
stat.restapi_responses.server_error.units=Responses
stat.restapi_responses.other.name=REST API unknown responses
stat.restapi_responses.other.desc=The amount of HTTP responses that had an unrecognized status code.
stat.restapi_responses.other.units=Responses
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,14 @@
import org.jivesoftware.openfire.container.Plugin;
import org.jivesoftware.openfire.container.PluginManager;
import org.jivesoftware.openfire.plugin.rest.service.JerseyWrapper;
import org.jivesoftware.openfire.stats.StatisticsManager;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.PropertyEventDispatcher;
import org.jivesoftware.util.PropertyEventListener;
import org.jivesoftware.util.StringUtils;

import java.io.File;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.*;

/**
* The Class RESTServicePlugin.
Expand Down Expand Up @@ -64,6 +63,8 @@ public void setServiceLoggingEnabled(boolean serviceLoggingEnabled) {
/** The custom authentication filter */
private String customAuthFilterClassName;

private final Set<String> registeredStatisticKeys = new HashSet<>();

/* (non-Javadoc)
* @see org.jivesoftware.openfire.container.Plugin#initializePlugin(org.jivesoftware.openfire.container.PluginManager, java.io.File)
*/
Expand All @@ -77,7 +78,13 @@ public void initializePlugin(PluginManager manager, File pluginDirectory) {

// See if Custom authentication filter has been defined
customAuthFilterClassName = JiveGlobals.getProperty("plugin.restapi.customAuthFilter", "");


// Start collecting statistics.
for (StatisticsFilter.RestResponseFamilyStatistic statistic : StatisticsFilter.generateAllFamilyStatisticInstances()) {
StatisticsManager.getInstance().addStatistic(statistic.getKeyName(), statistic);
registeredStatisticKeys.add(statistic.getKeyName());
}

// See if the service is enabled or not.
enabled = JiveGlobals.getBooleanProperty("plugin.restapi.enabled", false);

Expand All @@ -100,6 +107,13 @@ public void initializePlugin(PluginManager manager, File pluginDirectory) {
* @see org.jivesoftware.openfire.container.Plugin#destroyPlugin()
*/
public void destroyPlugin() {
// Stop registering statistics.
final Iterator<String> iter = registeredStatisticKeys.iterator();
while (iter.hasNext()) {
StatisticsManager.getInstance().removeStatistic(iter.next());
iter.remove();
}

// Release the excluded URL
AuthCheckFilter.removeExclude(JerseyWrapper.SERVLET_URL);
// Stop listening to system property events
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package org.jivesoftware.openfire.plugin.rest;

import org.jivesoftware.openfire.stats.i18nStatistic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class StatisticsFilter implements ContainerResponseFilter
{
private static final Logger Log = LoggerFactory.getLogger(StatisticsFilter.class);

private static final ConcurrentMap<Response.Status.Family, Long> ratePerFamily = new ConcurrentHashMap<>();

private static ConcurrentMap<Response.Status.Family, Long> getStatsCollection() {
return ratePerFamily;
}
@Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException
{
final Response.StatusType statusInfo = responseContext.getStatusInfo();
if (statusInfo == null) {
Log.warn("Cannot record statistics for a response that contains no status info. Response context object: {}", responseContext);
} else {
StatisticsFilter.getStatsCollection().merge(statusInfo.getFamily(), 1L, Long::sum);
}
}

public static Collection<RestResponseFamilyStatistic> generateAllFamilyStatisticInstances() {
final Collection<RestResponseFamilyStatistic> result = new HashSet<>();
for (Response.Status.Family family : Response.Status.Family.values()) {
result.add(new RestResponseFamilyStatistic(family));
}
return result;
}

public static class RestResponseFamilyStatistic extends i18nStatistic
{
public static final String GROUP = "restapi_responses";

private final Response.Status.Family family;

public RestResponseFamilyStatistic(@Nonnull final Response.Status.Family family)
{
super(GROUP + "." + family.toString().toLowerCase(), "restapi", Type.rate);
this.family = family;
}

@Override
public double sample()
{
final Long oldValue = StatisticsFilter.getStatsCollection().replace(family, 0L);
return oldValue == null ? 0 : oldValue;
}

@Override
public boolean isPartialSample()
{
return true;
}

public String getGroupName() {
return GROUP;
}

public String getKeyName() {
return family.toString().toLowerCase();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.jivesoftware.openfire.plugin.rest.AuthFilter;
import org.jivesoftware.openfire.plugin.rest.CORSFilter;
import org.jivesoftware.openfire.plugin.rest.CustomJacksonMapperProvider;
import org.jivesoftware.openfire.plugin.rest.StatisticsFilter;
import org.jivesoftware.openfire.plugin.rest.exceptions.RESTExceptionMapper;
import org.jivesoftware.util.JiveGlobals;

Expand Down Expand Up @@ -95,6 +96,7 @@ public JerseyWrapper(@Context ServletConfig servletConfig) {
// Filters
loadAuthenticationFilter();
register(CORSFilter.class);
register(StatisticsFilter.class);

// Services
registerClasses(
Expand Down

0 comments on commit fa68733

Please sign in to comment.