Skip to content

Commit fa68733

Browse files
committed
fixes #155: Add statistics for endpoint responses
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.
1 parent 72db512 commit fa68733

File tree

4 files changed

+118
-4
lines changed

4 files changed

+118
-4
lines changed

src/i18n/restapi_i18n.properties

+19
Original file line numberDiff line numberDiff line change
@@ -1 +1,20 @@
11
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).
2+
3+
stat.restapi_responses.informational.name=REST API 1xx responses
4+
stat.restapi_responses.informational.desc=The amount of HTTP responses that had an 'Informational' status (a code in the 1xx range).
5+
stat.restapi_responses.informational.units=Responses
6+
stat.restapi_responses.successful.name=REST API 2xx responses
7+
stat.restapi_responses.successful.desc=The amount of HTTP responses that had a 'Successful' status (a code in the 2xx range).
8+
stat.restapi_responses.successful.units=Responses
9+
stat.restapi_responses.redirection.name=REST API 3xx responses
10+
stat.restapi_responses.redirection.desc=The amount of HTTP responses that had a 'Redirection' status (a code in the 3xx range).
11+
stat.restapi_responses.redirection.units=Responses
12+
stat.restapi_responses.client_error.name=REST API 4xx responses
13+
stat.restapi_responses.client_error.desc=The amount of HTTP responses that had a 'Client Error' status (a code in the 4xx range).
14+
stat.restapi_responses.client_error.units=Responses
15+
stat.restapi_responses.server_error.name=REST API 5xx responses
16+
stat.restapi_responses.server_error.desc=The amount of HTTP responses that had a 'Server Error' status (a code in the 5xx range).
17+
stat.restapi_responses.server_error.units=Responses
18+
stat.restapi_responses.other.name=REST API unknown responses
19+
stat.restapi_responses.other.desc=The amount of HTTP responses that had an unrecognized status code.
20+
stat.restapi_responses.other.units=Responses

src/java/org/jivesoftware/openfire/plugin/rest/RESTServicePlugin.java

+18-4
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,14 @@
2020
import org.jivesoftware.openfire.container.Plugin;
2121
import org.jivesoftware.openfire.container.PluginManager;
2222
import org.jivesoftware.openfire.plugin.rest.service.JerseyWrapper;
23+
import org.jivesoftware.openfire.stats.StatisticsManager;
2324
import org.jivesoftware.util.JiveGlobals;
2425
import org.jivesoftware.util.PropertyEventDispatcher;
2526
import org.jivesoftware.util.PropertyEventListener;
2627
import org.jivesoftware.util.StringUtils;
2728

2829
import java.io.File;
29-
import java.util.Collection;
30-
import java.util.Collections;
31-
import java.util.Map;
30+
import java.util.*;
3231

3332
/**
3433
* The Class RESTServicePlugin.
@@ -64,6 +63,8 @@ public void setServiceLoggingEnabled(boolean serviceLoggingEnabled) {
6463
/** The custom authentication filter */
6564
private String customAuthFilterClassName;
6665

66+
private final Set<String> registeredStatisticKeys = new HashSet<>();
67+
6768
/* (non-Javadoc)
6869
* @see org.jivesoftware.openfire.container.Plugin#initializePlugin(org.jivesoftware.openfire.container.PluginManager, java.io.File)
6970
*/
@@ -77,7 +78,13 @@ public void initializePlugin(PluginManager manager, File pluginDirectory) {
7778

7879
// See if Custom authentication filter has been defined
7980
customAuthFilterClassName = JiveGlobals.getProperty("plugin.restapi.customAuthFilter", "");
80-
81+
82+
// Start collecting statistics.
83+
for (StatisticsFilter.RestResponseFamilyStatistic statistic : StatisticsFilter.generateAllFamilyStatisticInstances()) {
84+
StatisticsManager.getInstance().addStatistic(statistic.getKeyName(), statistic);
85+
registeredStatisticKeys.add(statistic.getKeyName());
86+
}
87+
8188
// See if the service is enabled or not.
8289
enabled = JiveGlobals.getBooleanProperty("plugin.restapi.enabled", false);
8390

@@ -100,6 +107,13 @@ public void initializePlugin(PluginManager manager, File pluginDirectory) {
100107
* @see org.jivesoftware.openfire.container.Plugin#destroyPlugin()
101108
*/
102109
public void destroyPlugin() {
110+
// Stop registering statistics.
111+
final Iterator<String> iter = registeredStatisticKeys.iterator();
112+
while (iter.hasNext()) {
113+
StatisticsManager.getInstance().removeStatistic(iter.next());
114+
iter.remove();
115+
}
116+
103117
// Release the excluded URL
104118
AuthCheckFilter.removeExclude(JerseyWrapper.SERVLET_URL);
105119
// Stop listening to system property events
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package org.jivesoftware.openfire.plugin.rest;
2+
3+
import org.jivesoftware.openfire.stats.i18nStatistic;
4+
import org.slf4j.Logger;
5+
import org.slf4j.LoggerFactory;
6+
7+
import javax.annotation.Nonnull;
8+
import javax.ws.rs.container.ContainerRequestContext;
9+
import javax.ws.rs.container.ContainerResponseContext;
10+
import javax.ws.rs.container.ContainerResponseFilter;
11+
import javax.ws.rs.core.Response;
12+
import java.io.IOException;
13+
import java.util.Collection;
14+
import java.util.HashSet;
15+
import java.util.concurrent.ConcurrentHashMap;
16+
import java.util.concurrent.ConcurrentMap;
17+
18+
public class StatisticsFilter implements ContainerResponseFilter
19+
{
20+
private static final Logger Log = LoggerFactory.getLogger(StatisticsFilter.class);
21+
22+
private static final ConcurrentMap<Response.Status.Family, Long> ratePerFamily = new ConcurrentHashMap<>();
23+
24+
private static ConcurrentMap<Response.Status.Family, Long> getStatsCollection() {
25+
return ratePerFamily;
26+
}
27+
@Override
28+
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException
29+
{
30+
final Response.StatusType statusInfo = responseContext.getStatusInfo();
31+
if (statusInfo == null) {
32+
Log.warn("Cannot record statistics for a response that contains no status info. Response context object: {}", responseContext);
33+
} else {
34+
StatisticsFilter.getStatsCollection().merge(statusInfo.getFamily(), 1L, Long::sum);
35+
}
36+
}
37+
38+
public static Collection<RestResponseFamilyStatistic> generateAllFamilyStatisticInstances() {
39+
final Collection<RestResponseFamilyStatistic> result = new HashSet<>();
40+
for (Response.Status.Family family : Response.Status.Family.values()) {
41+
result.add(new RestResponseFamilyStatistic(family));
42+
}
43+
return result;
44+
}
45+
46+
public static class RestResponseFamilyStatistic extends i18nStatistic
47+
{
48+
public static final String GROUP = "restapi_responses";
49+
50+
private final Response.Status.Family family;
51+
52+
public RestResponseFamilyStatistic(@Nonnull final Response.Status.Family family)
53+
{
54+
super(GROUP + "." + family.toString().toLowerCase(), "restapi", Type.rate);
55+
this.family = family;
56+
}
57+
58+
@Override
59+
public double sample()
60+
{
61+
final Long oldValue = StatisticsFilter.getStatsCollection().replace(family, 0L);
62+
return oldValue == null ? 0 : oldValue;
63+
}
64+
65+
@Override
66+
public boolean isPartialSample()
67+
{
68+
return true;
69+
}
70+
71+
public String getGroupName() {
72+
return GROUP;
73+
}
74+
75+
public String getKeyName() {
76+
return family.toString().toLowerCase();
77+
}
78+
}
79+
}

src/java/org/jivesoftware/openfire/plugin/rest/service/JerseyWrapper.java

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.jivesoftware.openfire.plugin.rest.AuthFilter;
2121
import org.jivesoftware.openfire.plugin.rest.CORSFilter;
2222
import org.jivesoftware.openfire.plugin.rest.CustomJacksonMapperProvider;
23+
import org.jivesoftware.openfire.plugin.rest.StatisticsFilter;
2324
import org.jivesoftware.openfire.plugin.rest.exceptions.RESTExceptionMapper;
2425
import org.jivesoftware.util.JiveGlobals;
2526

@@ -95,6 +96,7 @@ public JerseyWrapper(@Context ServletConfig servletConfig) {
9596
// Filters
9697
loadAuthenticationFilter();
9798
register(CORSFilter.class);
99+
register(StatisticsFilter.class);
98100

99101
// Services
100102
registerClasses(

0 commit comments

Comments
 (0)