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

Monitoring fixes/improvements #744

Merged
merged 12 commits into from
May 13, 2019
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ public static List<ServerConfig> from(DeprecatedServerConfig server, Path unixSo
q2tConfig.setCommunicationType(CommunicationType.REST);
String uriValue = "unix:"+ String.valueOf(unixSocketFile);
q2tConfig.setServerAddress(uriValue);
q2tConfig.setInfluxConfig(server.getInfluxConfig());

ServerConfig p2pConfig = new ServerConfig();
p2pConfig.setEnabled(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ public void createConfigsFromDeprecatedServerConfigWithCommTypeRest() {
deprecatedServerConfig.setHostName("somehost");
deprecatedServerConfig.setPort(99);
deprecatedServerConfig.setCommunicationType(CommunicationType.REST);
InfluxConfig influxConfig = new InfluxConfig();
deprecatedServerConfig.setInfluxConfig(influxConfig);

Path unixSocketFile = Paths.get("unixSocketFile");

Expand All @@ -29,12 +31,14 @@ public void createConfigsFromDeprecatedServerConfigWithCommTypeRest() {
assertThat(q2t.getServerUri()).isEqualTo(URI.create("unix:unixSocketFile"));
assertThat(q2t.isEnabled()).isTrue();
assertThat(q2t.getApp()).isEqualTo(AppType.Q2T);
assertThat(q2t.getInfluxConfig()).isEqualTo(influxConfig);

ServerConfig p2p = results.get(1);
assertThat(p2p.getCommunicationType()).isEqualTo(CommunicationType.REST);
assertThat(p2p.getServerUri()).isEqualTo(URI.create("somehost:99"));
assertThat(p2p.isEnabled()).isTrue();
assertThat(p2p.getApp()).isEqualTo(AppType.P2P);
assertThat(p2p.getInfluxConfig()).isEqualTo(influxConfig);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
package com.quorum.tessera.server;

import com.jpmorgan.quorum.server.utils.ServerUtils;
import com.quorum.tessera.config.AppType;
import com.quorum.tessera.config.InfluxConfig;
import com.quorum.tessera.config.ServerConfig;
import com.quorum.tessera.server.jaxrs.CorsDomainResponseFilter;
import com.quorum.tessera.server.jaxrs.LoggingFilter;
import com.quorum.tessera.server.monitoring.InfluxDbClient;
import com.quorum.tessera.server.monitoring.InfluxDbPublisher;
import com.quorum.tessera.server.monitoring.MetricsResource;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.ws.rs.core.Application;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.glassfish.jersey.server.ResourceConfig;
Expand All @@ -24,6 +17,16 @@
import org.slf4j.LoggerFactory;
import org.slf4j.bridge.SLF4JBridgeHandler;

import javax.ws.rs.core.Application;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;

/**
* Implementation of a RestServer using Jersey and Jetty.
*/
Expand All @@ -43,18 +46,17 @@ public class JerseyServer implements TesseraServer {

private final ServerConfig serverConfig;

private final AppType type;

public JerseyServer(final ServerConfig serverConfig, final Application application) {
this.uri = serverConfig.getBindingUri();
this.uri = serverConfig.getServerUri();
this.application = Objects.requireNonNull(application);
this.serverConfig = serverConfig;

this.executor = newSingleThreadScheduledExecutor();

if (serverConfig.getInfluxConfig() != null) {
this.influxConfig = serverConfig.getInfluxConfig();
} else {
this.influxConfig = null;
}
this.influxConfig = serverConfig.getInfluxConfig();
this.type = serverConfig.getApp();
}

@Override
Expand Down Expand Up @@ -106,7 +108,7 @@ public void start() throws Exception {
}

private void startInfluxMonitoring() {
InfluxDbClient influxDbClient = new InfluxDbClient(this.uri, influxConfig);
InfluxDbClient influxDbClient = new InfluxDbClient(uri, influxConfig, type);
Runnable publisher = new InfluxDbPublisher(influxDbClient);

final Runnable exceptionSafePublisher = () -> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package com.quorum.tessera.server.monitoring;

import com.quorum.tessera.config.AppType;
import com.quorum.tessera.config.InfluxConfig;

import javax.management.*;
import javax.management.MBeanServer;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
Expand All @@ -23,23 +24,26 @@ public class InfluxDbClient {

private final String hostName;

private final AppType appType;

private final MBeanServer mbs;

public InfluxDbClient(URI uri, InfluxConfig influxConfig) {
public InfluxDbClient(URI uri, InfluxConfig influxConfig, AppType appType) {
this.uri = uri;
this.port = influxConfig.getPort();
this.hostName = influxConfig.getHostName();
this.dbName = influxConfig.getDbName();
this.appType = appType;

this.mbs = ManagementFactory.getPlatformMBeanServer();
}

public Response postMetrics() {
MetricsEnquirer metricsEnquirer = new MetricsEnquirer(mbs);
List<MBeanMetric> metrics = metricsEnquirer.getMBeanMetrics();
List<MBeanMetric> metrics = metricsEnquirer.getMBeanMetrics(appType);

InfluxDbProtocolFormatter formatter = new InfluxDbProtocolFormatter();
String formattedMetrics = formatter.format(metrics, uri);
String formattedMetrics = formatter.format(metrics, uri, appType);

Client client = ClientBuilder.newClient();
WebTarget influxTarget = client
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
package com.quorum.tessera.server.monitoring;

import com.quorum.tessera.config.AppType;

import java.net.URI;
import java.util.List;

public class InfluxDbProtocolFormatter {

public String format(List<MBeanMetric> metrics, URI uri) {
public String format(List<MBeanMetric> metrics, URI uri, AppType appType) {
StringBuilder formattedMetrics = new StringBuilder();

for(MBeanMetric metric : metrics) {
MBeanResourceMetric resourceMetric = (MBeanResourceMetric) metric;

formattedMetrics.append("tessera_")
.append(appType)
.append("_")
.append(sanitize(resourceMetric.getResourceMethod()))
.append(",")
.append("instance=")
.append(uri.getHost())
.append(":")
.append(uri.getPort())
.append(uri)
.append(" ")
.append(sanitize(resourceMetric.getName()))
.append("=")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.quorum.tessera.server.monitoring;

import com.quorum.tessera.config.AppType;

import javax.management.*;
import java.util.ArrayList;
import java.util.Collections;
Expand All @@ -14,12 +16,12 @@ public MetricsEnquirer(MBeanServer mBeanServer) {
this.mBeanServer = mBeanServer;
}

public List<MBeanMetric> getMBeanMetrics() {
public List<MBeanMetric> getMBeanMetrics(AppType appType) {
List<MBeanMetric> mBeanMetrics = new ArrayList<>();

Set<ObjectName> mBeanNames;
try {
mBeanNames = getTesseraResourceMBeanNames();
mBeanNames = getTesseraResourceMBeanNames(appType);

for(ObjectName mBeanName : mBeanNames) {
List<MBeanMetric> temp;
Expand All @@ -38,8 +40,29 @@ public List<MBeanMetric> getMBeanMetrics() {
return Collections.unmodifiableList(mBeanMetrics);
}

private Set<ObjectName> getTesseraResourceMBeanNames() throws MalformedObjectNameException {
String pattern = "org.glassfish.jersey:type=Tessera,subType=Resources,resource=com.quorum.tessera.api.*,executionTimes=RequestTimes,detail=methods,method=*";
private Set<ObjectName> getTesseraResourceMBeanNames(AppType appType) throws MalformedObjectNameException {
final String type;
switch(appType) {
case P2P:
type = "P2PRestApp";
break;
case Q2T:
type = "Q2TRestApp";
break;
case ADMIN:
type = "AdminRestApp";
break;
case THIRD_PARTY:
type = "ThirdPartyRestApp";
break;
case ENCLAVE:
type = "EnclaveApplication";
break;
default:
throw new MonitoringNotSupportedException(appType);
}

String pattern = String.format("org.glassfish.jersey:type=%s,subType=Resources,resource=com.quorum.tessera.*,executionTimes=RequestTimes,detail=methods,method=*", type);
return Collections.unmodifiableSet(this.mBeanServer.queryNames(new ObjectName(pattern), null));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.quorum.tessera.server.monitoring;

import javax.management.*;
import com.quorum.tessera.config.AppType;

import javax.management.MBeanServer;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
Expand All @@ -23,14 +25,20 @@ public MetricsResource() {
@Produces("text/plain")
public Response getMetrics() {
MetricsEnquirer metricsEnquirer = new MetricsEnquirer(mbs);
List<MBeanMetric> metrics = metricsEnquirer.getMBeanMetrics();
final StringBuilder formattedMetrics = new StringBuilder();

// TODO Each app server has a /metrics endpoint but currently each endpoint returns the metrics for all servers. Would be better to lock this down e.g. <p2puri>/metrics only returns the p2p metrics
for (AppType type : AppType.values()) {
List<MBeanMetric> metrics = metricsEnquirer.getMBeanMetrics(type);
PrometheusProtocolFormatter formatter = new PrometheusProtocolFormatter();

PrometheusProtocolFormatter formatter = new PrometheusProtocolFormatter();
String formattedMetrics = formatter.format(metrics);
formattedMetrics.append(formatter.format(metrics, type))
.append("\n");
}

return Response.status(Response.Status.OK)
.header("Content-Type", TEXT_PLAIN)
.entity(formattedMetrics)
.entity(formattedMetrics.toString().trim())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.quorum.tessera.server.monitoring;

import com.quorum.tessera.config.AppType;

public class MonitoringNotSupportedException extends RuntimeException {

public MonitoringNotSupportedException(final AppType appType) {
super(appType + " app does not support monitoring Jersey metrics");
}

}
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
package com.quorum.tessera.server.monitoring;

import com.quorum.tessera.config.AppType;

import java.util.List;

public class PrometheusProtocolFormatter {

public String format(final List<MBeanMetric> metrics) {
public String format(final List<MBeanMetric> metrics, AppType appType) {
StringBuilder formattedMetrics = new StringBuilder();

for (final MBeanMetric metric : metrics) {
final MBeanResourceMetric resourceMetric = (MBeanResourceMetric) metric;

formattedMetrics.append("tessera_")
.append(appType)
.append("_")
.append(sanitize(resourceMetric.getResourceMethod()))
.append("_")
.append(sanitize(resourceMetric.getName()))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.quorum.tessera.server.monitoring;

import com.quorum.tessera.config.AppType;
import org.junit.Before;
import org.junit.Test;

Expand All @@ -16,62 +17,65 @@ public class InfluxDbProtocolFormatterTest {

URI mockUri;

AppType mockAppType;

@Before
public void setUp() throws URISyntaxException {
this.protocolFormatter = new InfluxDbProtocolFormatter();
this.mockMetrics = new ArrayList<>();
this.mockUri = new URI("http://localhost:8080");
this.mockAppType = AppType.P2P;
}

@Test
public void noArgResourceResponseCorrectlyFormatted() {
mockMetrics.add(new MBeanResourceMetric("GET->upCheck()#a10a4f8d", "AverageTime[ms]_total", "100"));

String expectedResponse = "tessera_GET_upCheck,instance=localhost:8080 AverageTime_ms=100";
String expectedResponse = "tessera_P2P_GET_upCheck,instance=http://localhost:8080 AverageTime_ms=100";

assertThat(protocolFormatter.format(mockMetrics, this.mockUri)).isEqualTo(expectedResponse);
assertThat(protocolFormatter.format(mockMetrics, mockUri, mockAppType)).isEqualTo(expectedResponse);
}

@Test
public void singleArgResourceResponseCorrectlyFormatted() {
mockMetrics.add(new MBeanResourceMetric("POST->resend(ResendRequest)#8ca0a760", "RequestRate[requestsPerSeconds]", "1.3"));

String expectedResponse = "tessera_POST_resend_ResendRequest,instance=localhost:8080 RequestRate_requestsPerSeconds=1.3";
String expectedResponse = "tessera_P2P_POST_resend_ResendRequest,instance=http://localhost:8080 RequestRate_requestsPerSeconds=1.3";

assertThat(protocolFormatter.format(mockMetrics, this.mockUri)).isEqualTo(expectedResponse);
assertThat(protocolFormatter.format(mockMetrics, mockUri, mockAppType)).isEqualTo(expectedResponse);
}

@Test
public void singleArrayArgResourceResponseCorrectlyFormatted() {
mockMetrics.add(new MBeanResourceMetric("POST->push(byte[])#7f702b7e", "MinTime[ms]_total", "3.4"));

String expectedResponse = "tessera_POST_push_byte,instance=localhost:8080 MinTime_ms=3.4";
String expectedResponse = "tessera_P2P_POST_push_byte,instance=http://localhost:8080 MinTime_ms=3.4";

assertThat(protocolFormatter.format(mockMetrics, this.mockUri)).isEqualTo(expectedResponse);
assertThat(protocolFormatter.format(mockMetrics, mockUri, mockAppType)).isEqualTo(expectedResponse);
}

@Test
public void multipleArgResourceResponseCorrectlyFormatted() {
mockMetrics.add(new MBeanResourceMetric("GET->receiveRaw(String;String)#fc8f8357", "AverageTime[ms]_total", "5.2"));

String expectedResponse = "tessera_GET_receiveRaw_StringString,instance=localhost:8080 AverageTime_ms=5.2";
String expectedResponse = "tessera_P2P_GET_receiveRaw_StringString,instance=http://localhost:8080 AverageTime_ms=5.2";

assertThat(protocolFormatter.format(mockMetrics, this.mockUri)).isEqualTo(expectedResponse);
assertThat(protocolFormatter.format(mockMetrics, mockUri, mockAppType)).isEqualTo(expectedResponse);
}

@Test
public void multipleMetricsResponseCorrectlyFormatted() {
mockMetrics.add(new MBeanResourceMetric("GET->upCheck()#a10a4f8d", "AverageTime[ms]_total", "100"));
mockMetrics.add(new MBeanResourceMetric("POST->resend(ResendRequest)#8ca0a760", "RequestRate[requestsPerSeconds]", "1.3"));

String expectedResponse = "tessera_GET_upCheck,instance=localhost:8080 AverageTime_ms=100" + "\n" +
"tessera_POST_resend_ResendRequest,instance=localhost:8080 RequestRate_requestsPerSeconds=1.3";
String expectedResponse = "tessera_P2P_GET_upCheck,instance=http://localhost:8080 AverageTime_ms=100" + "\n" +
"tessera_P2P_POST_resend_ResendRequest,instance=http://localhost:8080 RequestRate_requestsPerSeconds=1.3";

assertThat(protocolFormatter.format(mockMetrics, this.mockUri)).isEqualTo(expectedResponse);
assertThat(protocolFormatter.format(mockMetrics, mockUri, mockAppType)).isEqualTo(expectedResponse);
}

@Test
public void noMetricsToFormatIsHandled() {
assertThat(protocolFormatter.format(mockMetrics, this.mockUri)).isEmpty();
assertThat(protocolFormatter.format(mockMetrics, mockUri, mockAppType)).isEmpty();
}
}
Loading