diff --git a/dev/com.ibm.websphere.appserver.features/visibility/auto/io.openliberty.grpcMonitor-1.0.feature b/dev/com.ibm.websphere.appserver.features/visibility/auto/io.openliberty.grpcMonitor-1.0.feature new file mode 100644 index 00000000000..5301c4ea760 --- /dev/null +++ b/dev/com.ibm.websphere.appserver.features/visibility/auto/io.openliberty.grpcMonitor-1.0.feature @@ -0,0 +1,12 @@ +-include= ~${workspace}/cnf/resources/bnd/feature.props +symbolicName=io.openliberty.grpcMonitor-1.0 +Manifest-Version: 1.0 +IBM-API-Package: io.openliberty.grpc.monitor; type="ibm-api" +IBM-Provision-Capability: \ + osgi.identity; filter:="(&(type=osgi.subsystem.feature)(osgi.identity=com.ibm.websphere.appserver.monitor-1.0))", \ + osgi.identity; filter:="(&(type=osgi.subsystem.feature)(|(osgi.identity=io.openliberty.grpc-1.0)(osgi.identity=io.openliberty.grpcClient-1.0)))" +IBM-Install-Policy: when-satisfied +-bundles=io.openliberty.grpc.1.0.internal.monitor +kind=beta +edition=full + diff --git a/dev/com.ibm.ws.grpc_fat/fat/src/com/ibm/ws/fat/grpc/FATSuite.java b/dev/com.ibm.ws.grpc_fat/fat/src/com/ibm/ws/fat/grpc/FATSuite.java index 54a0116e084..7e0916f5b22 100644 --- a/dev/com.ibm.ws.grpc_fat/fat/src/com/ibm/ws/fat/grpc/FATSuite.java +++ b/dev/com.ibm.ws.grpc_fat/fat/src/com/ibm/ws/fat/grpc/FATSuite.java @@ -18,6 +18,7 @@ @SuiteClasses({ HelloWorldCDITests.class, HelloWorldTest.class, + GrpcMetricsTest.class, ServiceSupportTests.class, ServiceConfigTests.class, ServiceInterceptorTests.class, diff --git a/dev/com.ibm.ws.grpc_fat/fat/src/com/ibm/ws/fat/grpc/GrpcMetricsTest.java b/dev/com.ibm.ws.grpc_fat/fat/src/com/ibm/ws/fat/grpc/GrpcMetricsTest.java new file mode 100644 index 00000000000..b2868718fca --- /dev/null +++ b/dev/com.ibm.ws.grpc_fat/fat/src/com/ibm/ws/fat/grpc/GrpcMetricsTest.java @@ -0,0 +1,193 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package com.ibm.ws.fat.grpc; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; +import org.junit.runner.RunWith; + +import com.gargoylesoftware.htmlunit.WebClient; +import com.gargoylesoftware.htmlunit.html.HtmlForm; +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput; +import com.gargoylesoftware.htmlunit.html.HtmlTextInput; +import com.ibm.websphere.simplicity.ShrinkHelper; +import com.ibm.websphere.simplicity.log.Log; + +import componenttest.annotation.Server; +import componenttest.custom.junit.runner.FATRunner; +import componenttest.topology.impl.LibertyServer; +import componenttest.topology.utils.FATServletClient; + +@RunWith(FATRunner.class) +public class GrpcMetricsTest extends FATServletClient { + + protected static final Class c = GrpcMetricsTest.class; + + @Rule + public TestName name = new TestName(); + + @Server("HelloWorldServer") + public static LibertyServer grpcMetricsServer; //GrpcMetricsServer + + @BeforeClass + public static void setUp() throws Exception { + // add all classes from com.ibm.ws.grpc.fat.helloworld.service and io.grpc.examples.helloworld + // to a new app HelloWorldService.war + ShrinkHelper.defaultDropinApp(grpcMetricsServer, "HelloWorldService.war", + "com.ibm.ws.grpc.fat.helloworld.service", + "io.grpc.examples.helloworld"); + + // add all classes from com.ibm.ws.grpc.fat.helloworld.client, io.grpc.examples.helloworld, + // and com.ibm.ws.fat.grpc.tls to a new app HelloWorldClient.war. + ShrinkHelper.defaultDropinApp(grpcMetricsServer, "HelloWorldClient.war", + "com.ibm.ws.grpc.fat.helloworld.client", + "io.grpc.examples.helloworld", + "com.ibm.ws.fat.grpc.tls"); + + grpcMetricsServer.startServer(GrpcMetricsTest.class.getSimpleName() + ".log"); + assertNotNull("CWWKO0219I.*ssl not recieved", grpcMetricsServer.waitForStringInLog("CWWKO0219I.*ssl")); + } + + @AfterClass + public static void tearDown() throws Exception { + grpcMetricsServer.stopServer(); + } + + /** + * Tests gRPC server-side and client-side metrics. + * + * @throws Exception + */ + @Test + public void testGrpcMetrics() throws Exception { + String contextRoot = "HelloWorldClient"; + try (WebClient webClient = new WebClient()) { + + // Construct the URL for the test + URL url = GrpcTestUtils.createHttpUrl(grpcMetricsServer, contextRoot, "grpcClient"); + + HtmlPage page = (HtmlPage) webClient.getPage(url); + + // Log the page for debugging if necessary in the future. + Log.info(c, name.getMethodName(), page.asText()); + Log.info(c, name.getMethodName(), page.asXml()); + + assertTrue("the servlet was not loaded correctly", + page.asText().contains("gRPC helloworld client example")); + + HtmlForm form = page.getFormByName("form1"); + + // set a name, which we'll expect the RPC to return + HtmlTextInput inputText = (HtmlTextInput) form.getInputByName("user"); + inputText.setValueAttribute("us3r1"); + + // set the port + HtmlTextInput inputPort = (HtmlTextInput) form.getInputByName("port"); + inputPort.setValueAttribute(String.valueOf(grpcMetricsServer.getHttpDefaultPort())); + + // set the hostname + HtmlTextInput inputHost = (HtmlTextInput) form.getInputByName("address"); + inputHost.setValueAttribute(grpcMetricsServer.getHostname()); + + // submit, and execute the RPC + HtmlSubmitInput submitButton = form.getInputByName("submit"); + page = submitButton.click(); + + // check the gRPC client-side metrics + checkMetric("/metrics/vendor/grpc.client.rpcStarted.total", "1"); + checkMetric("/metrics/vendor/grpc.client.rpcCompleted.total", "1"); + checkMetric("/metrics/vendor/grpc.client.sentMessages.total", "1"); + checkMetric("/metrics/vendor/grpc.client.receivedMessages.total", "1"); + + // check the gRPC server-side metrics + checkMetric("/metrics/vendor/grpc.server.rpcStarted.total", "1"); + checkMetric("/metrics/vendor/grpc.server.rpcCompleted.total", "1"); + checkMetric("/metrics/vendor/grpc.server.sentMessages.total", "1"); + checkMetric("/metrics/vendor/grpc.server.receivedMessages.total", "1"); + + // Log the page for debugging if necessary in the future. + Log.info(c, name.getMethodName(), page.asText()); + assertTrue("the gRPC request did not complete correctly", page.asText().contains("us3r1")); + } + } + + /** + * Verifies the given metric by comparing the actual value with the given value + * + * @param metricName - the metric to verify + * @param expectedValue - the expected value + * @return the actual value received from the Metrics endpoint + */ + private String checkMetric(String metricName, String expectedValue) { + HttpURLConnection con = null; + try { + URL url = new URL("http://" + grpcMetricsServer.getHostname() + ":" + grpcMetricsServer.getHttpDefaultPort() + metricName); + int retcode; + con = (HttpURLConnection) url.openConnection(); + con.setDoInput(true); + con.setDoOutput(true); + con.setUseCaches(false); + con.setRequestMethod("GET"); + + retcode = con.getResponseCode(); + if (retcode != 200) { + fail("Bad return code from Metrics method call. Expected 200, got " + retcode); + + return null; + } + + InputStream is = con.getInputStream(); + InputStreamReader isr = new InputStreamReader(is); + + BufferedReader br = new BufferedReader(isr); + + String metricValue = null; + for (String line = br.readLine(); line != null; line = br.readLine()) { + if (!line.startsWith("#")) { + String[] mertricAttr = line.split(" "); + if (mertricAttr.length > 0) { + metricValue = mertricAttr[mertricAttr.length - 1]; + break; + } + } + } + + if (metricValue == null || !metricValue.equals(expectedValue)) { + fail(String.format("Incorrect metric value [%s]. Expected [%s], got [%s]", metricName, expectedValue, metricValue)); + } + return metricValue; + + } catch (Exception e) { + fail("Caught unexpected exception: " + e); + return null; + } finally { + if (con != null) { + con.disconnect(); + } + } + + } +} diff --git a/dev/com.ibm.ws.grpc_fat/publish/servers/ConsumerServer/server.xml b/dev/com.ibm.ws.grpc_fat/publish/servers/ConsumerServer/server.xml index b3c63f93ce3..814fdc44287 100644 --- a/dev/com.ibm.ws.grpc_fat/publish/servers/ConsumerServer/server.xml +++ b/dev/com.ibm.ws.grpc_fat/publish/servers/ConsumerServer/server.xml @@ -70,6 +70,7 @@ + - \ No newline at end of file + diff --git a/dev/com.ibm.ws.grpc_fat/publish/servers/GrpcServer/server.xml b/dev/com.ibm.ws.grpc_fat/publish/servers/GrpcServer/server.xml index 317ca3ca084..27f85a4e36f 100644 --- a/dev/com.ibm.ws.grpc_fat/publish/servers/GrpcServer/server.xml +++ b/dev/com.ibm.ws.grpc_fat/publish/servers/GrpcServer/server.xml @@ -17,4 +17,4 @@ - \ No newline at end of file + diff --git a/dev/com.ibm.ws.grpc_fat/publish/servers/GrpcServerOnly/server.xml b/dev/com.ibm.ws.grpc_fat/publish/servers/GrpcServerOnly/server.xml index 6c45d0ed1dc..fef4093c76c 100644 --- a/dev/com.ibm.ws.grpc_fat/publish/servers/GrpcServerOnly/server.xml +++ b/dev/com.ibm.ws.grpc_fat/publish/servers/GrpcServerOnly/server.xml @@ -16,4 +16,4 @@ - \ No newline at end of file + diff --git a/dev/com.ibm.ws.grpc_fat/publish/servers/HelloWorldServer/server.xml b/dev/com.ibm.ws.grpc_fat/publish/servers/HelloWorldServer/server.xml index 598ada483b9..8edaac23a25 100644 --- a/dev/com.ibm.ws.grpc_fat/publish/servers/HelloWorldServer/server.xml +++ b/dev/com.ibm.ws.grpc_fat/publish/servers/HelloWorldServer/server.xml @@ -7,17 +7,23 @@ Contributors: IBM Corporation - initial API and implementation - --> +--> grpc-1.0 grpcClient-1.0 ssl-1.0 + monitor-1.0 + mpMetrics-2.3 + - \ No newline at end of file + + diff --git a/dev/com.ibm.ws.grpc_fat/publish/servers/ProducerServer/server.xml b/dev/com.ibm.ws.grpc_fat/publish/servers/ProducerServer/server.xml index 1b90a812f15..dce7426270d 100644 --- a/dev/com.ibm.ws.grpc_fat/publish/servers/ProducerServer/server.xml +++ b/dev/com.ibm.ws.grpc_fat/publish/servers/ProducerServer/server.xml @@ -36,4 +36,4 @@ traceSpecification="*=info=enabled:io.openliberty.grpc*=all:com.ibm.testapp.g3store*=all" maxFileSize="40" maxFiles="20" traceFormat="BASIC" /> - \ No newline at end of file + diff --git a/dev/com.ibm.ws.grpc_fat/publish/servers/StoreServer/server.xml b/dev/com.ibm.ws.grpc_fat/publish/servers/StoreServer/server.xml index 3e19a7fd2c0..9006ff8a521 100644 --- a/dev/com.ibm.ws.grpc_fat/publish/servers/StoreServer/server.xml +++ b/dev/com.ibm.ws.grpc_fat/publish/servers/StoreServer/server.xml @@ -64,4 +64,4 @@ - \ No newline at end of file + diff --git a/dev/com.ibm.ws.grpc_fat/publish/servers/StreamingServer/server.xml b/dev/com.ibm.ws.grpc_fat/publish/servers/StreamingServer/server.xml index 2ae6a060051..a1f1e9a1c46 100644 --- a/dev/com.ibm.ws.grpc_fat/publish/servers/StreamingServer/server.xml +++ b/dev/com.ibm.ws.grpc_fat/publish/servers/StreamingServer/server.xml @@ -19,4 +19,4 @@ - \ No newline at end of file + diff --git a/dev/com.ibm.ws.grpc_fat/test-applications/HelloWorldClient.war/src/com/ibm/ws/grpc/fat/helloworld/client/HelloWorldClientServlet.java b/dev/com.ibm.ws.grpc_fat/test-applications/HelloWorldClient.war/src/com/ibm/ws/grpc/fat/helloworld/client/HelloWorldClientServlet.java index 1fc3b9fe1f4..e338de03977 100644 --- a/dev/com.ibm.ws.grpc_fat/test-applications/HelloWorldClient.war/src/com/ibm/ws/grpc/fat/helloworld/client/HelloWorldClientServlet.java +++ b/dev/com.ibm.ws.grpc_fat/test-applications/HelloWorldClient.war/src/com/ibm/ws/grpc/fat/helloworld/client/HelloWorldClientServlet.java @@ -146,4 +146,4 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) stopService(); } -} \ No newline at end of file +} diff --git a/dev/com.ibm.ws.microprofile.metrics.2.3.monitor/src/com/ibm/ws/microprofile/metrics/monitor/MappingTable.java b/dev/com.ibm.ws.microprofile.metrics.2.3.monitor/src/com/ibm/ws/microprofile/metrics/monitor/MappingTable.java index 101b10ae728..0d26fb64546 100644 --- a/dev/com.ibm.ws.microprofile.metrics.2.3.monitor/src/com/ibm/ws/microprofile/metrics/monitor/MappingTable.java +++ b/dev/com.ibm.ws.microprofile.metrics.2.3.monitor/src/com/ibm/ws/microprofile/metrics/monitor/MappingTable.java @@ -40,6 +40,8 @@ public class MappingTable { public static final String SESSION_TAG_NAME = "appname"; public static final String JAXWS_SERVER_TAG_NAME = "endpoint"; public static final String JAXWS_CLIENT_TAG_NAME = "endpoint"; + public static final String GRPC_SERVER_TAG_NAME = "grpc"; + public static final String GRPC_CLIENT_TAG_NAME = "grpc"; public static final String COUNTER = MetricType.COUNTER.toString().toUpperCase(); public static final String GAUGE = MetricType.GAUGE.toString().toUpperCase(); @@ -117,6 +119,22 @@ private MappingTable() { { "vendor", "jaxws.client.responseTime.total", "Total Response Time", "jaxws.responseTime.total.description", GAUGE, MetricUnits.MILLISECONDS, "TotalHandlingTime", null, JAXWS_CLIENT_TAG_NAME } }; mappingTable.put("WebSphere:feature=jaxws,*,type=Performance.Counter.Client", jaxwsClientTable); + + String[][] grpcServerTable = new String[][]{ + { "vendor", "grpc.server.rpcStarted.total", "Total Server RPCs Started Count", "grpc.server.rpcStarted.total.description", COUNTER, MetricUnits.NONE, "RpcStartedCount", null, GRPC_SERVER_TAG_NAME }, + { "vendor", "grpc.server.rpcCompleted.total", "Total Server RPCs Completed Count", "grpc.server.rpcCompleted .total.description", COUNTER, MetricUnits.NONE, "RpcCompletedCount", null, GRPC_SERVER_TAG_NAME }, + { "vendor", "grpc.server.sentMessages.total", "Total Sent Stream Messages", "grpc.server.sentMessages.total.description", COUNTER, MetricUnits.NONE, "SentMessagesCount", null, GRPC_SERVER_TAG_NAME }, + { "vendor", "grpc.server.receivedMessages.total", "Total Received Stream Messages", "grpc.server.receivedMessages.total.description", COUNTER, MetricUnits.NONE, "ReceivedMessagesCount", null, GRPC_SERVER_TAG_NAME } + }; + mappingTable.put("WebSphere:type=GrpcServerStats,name=*", grpcServerTable); + + String[][] grpcClientTable = new String[][]{ + { "vendor", "grpc.client.rpcStarted.total", "Total Client RPCs Started Count", "grpc.client.rpcStarted.total.description", COUNTER, MetricUnits.NONE, "RpcStartedCount", null, GRPC_SERVER_TAG_NAME }, + { "vendor", "grpc.client.rpcCompleted.total", "Total Client RPCs Completed Count", "grpc.client.rpcCompleted.total.description", COUNTER, MetricUnits.NONE, "RpcCompletedCount", null, GRPC_SERVER_TAG_NAME }, + { "vendor", "grpc.client.sentMessages.total", "Total Sent Stream Messages", "grpc.client.sentMessages.total.description", COUNTER, MetricUnits.NONE, "SentMessagesCount", null, GRPC_SERVER_TAG_NAME }, + { "vendor", "grpc.client.receivedMessages.total", "Total Received Stream Messages", "grpc.client.receivedMessages.total.description", COUNTER, MetricUnits.NONE, "ReceivedMessagesCount", null, GRPC_SERVER_TAG_NAME } + }; + mappingTable.put("WebSphere:type=GrpcClientStats,name=*", grpcClientTable); } diff --git a/dev/com.ibm.ws.microprofile.metrics.2.3/resources/com/ibm/ws/microprofile/metrics/resources/Metrics.nlsprops b/dev/com.ibm.ws.microprofile.metrics.2.3/resources/com/ibm/ws/microprofile/metrics/resources/Metrics.nlsprops index 8f21ad11401..cafa581a2ab 100644 --- a/dev/com.ibm.ws.microprofile.metrics.2.3/resources/com/ibm/ws/microprofile/metrics/resources/Metrics.nlsprops +++ b/dev/com.ibm.ws.microprofile.metrics.2.3/resources/com/ibm/ws/microprofile/metrics/resources/Metrics.nlsprops @@ -128,6 +128,15 @@ session.activeSessions.description=The number of concurrently active sessions. ( session.invalidated.total.description=The number of sessions that have logged out since this metric was enabled. session.invalidatedbyTimeout.total.description=The number of sessions that have logged out by timeout since this metric was enabled. +grpc.server.rpcStarted.total.description=The total number of RPCs started on the server. +grpc.server.rpcCompleted.total.description=The total number of RPCs completed on the server, regardless of success or failure. +grpc.server.sentMessages.total.description=The total number of stream messages sent by the server. +grpc.server.receivedMessages.total.description=The total number of stream messages received from the client. + +grpc.client.rpcStarted.total.description=The total number of RPCs started on the client. +grpc.client.rpcCompleted.total.description=The total number of RPCs completed on the client, regardless of success or failure. +grpc.client.sentMessages.total.description=The total number of stream messages sent by the client. +grpc.client.receivedMessages.total.description=The total number of stream messages received from the server. REST.request.description=The number of invocations and total response time of this RESTful resource method since the start of the server. #----------------------------------------------------------------------------------------------------------------------------- diff --git a/dev/io.openliberty.grpc.1.0.internal.client/bnd.bnd b/dev/io.openliberty.grpc.1.0.internal.client/bnd.bnd index 18471329ff4..38bad331de2 100644 --- a/dev/io.openliberty.grpc.1.0.internal.client/bnd.bnd +++ b/dev/io.openliberty.grpc.1.0.internal.client/bnd.bnd @@ -34,13 +34,23 @@ Import-Package: !sun.*,\ !org.eclipse.jetty.*,\ !org.jboss.*,\ javax.annotation;version=!,\ + !com.ibm.websphere.monitor.jmx,\ * +# Use dynamic import of monitor packages to support indepndent/dynamic enablement of monitor feature +DynamicImport-Package: com.ibm.websphere.monitor.meters;version="1.0.0", \ + com.ibm.websphere.monitor.jmx;version="1.0.0", \ + com.ibm.wsspi.request.probe.bci, \ + com.ibm.wsspi.probeExtension, \ + io.openliberty.grpc.internal.monitor + Export-Package: \ io.openliberty.grpc.internal.client.* ,\ io.grpc.netty.shaded.io.* --dsannotations: io.openliberty.grpc.internal.client.config.GrpcClientConfigImpl +-dsannotations: \ + io.openliberty.grpc.internal.client.config.GrpcClientConfigImpl,\ + io.openliberty.grpc.internal.client.GrpcClientComponent # include the service providers and metatype Include-Resource: \ @@ -65,16 +75,21 @@ Provide-Capability: osgi.serviceloader;osgi.serviceloader=io.grpc.ManagedChannel instrument.disabled: true -buildpath: \ + com.ibm.ws.container.service;version=latest,\ com.ibm.websphere.javaee.servlet.4.0;version=latest,\ com.ibm.websphere.security;version=latest,\ com.ibm.ws.container.service;version=latest,\ com.ibm.ws.managedobject;version=latest,\ com.ibm.ws.webcontainer;version=latest,\ com.ibm.ws.logging.core;version=latest,\ + com.ibm.ws.monitor;version=latest,\ com.ibm.websphere.org.osgi.core;version=latest,\ com.ibm.websphere.org.osgi.service.component;version=latest,\ com.ibm.ws.org.osgi.annotation.versioning;version=latest,\ com.ibm.wsspi.org.osgi.service.component.annotations;version=latest,\ + com.ibm.ws.kernel.boot;version=latest,\ + com.ibm.ws.kernel.service;version=latest,\ + com.ibm.ws.kernel.feature;version=latest,\ org.osgi.service.component.annotations;version=latest,\ io.grpc:grpc-netty-shaded;version=1.27.0,\ io.openliberty.grpc.1.0.internal.common;version=latest,\ diff --git a/dev/io.openliberty.grpc.1.0.internal.client/src/io/openliberty/grpc/internal/client/GrpcClientComponent.java b/dev/io.openliberty.grpc.1.0.internal.client/src/io/openliberty/grpc/internal/client/GrpcClientComponent.java new file mode 100644 index 00000000000..499c7cf97c0 --- /dev/null +++ b/dev/io.openliberty.grpc.1.0.internal.client/src/io/openliberty/grpc/internal/client/GrpcClientComponent.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package io.openliberty.grpc.internal.client; + +import java.util.Set; + +import org.osgi.framework.ServiceReference; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.ConfigurationPolicy; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; + +import com.ibm.websphere.ras.Tr; +import com.ibm.websphere.ras.TraceComponent; +import com.ibm.ws.container.service.app.deploy.ApplicationInfo; +import com.ibm.ws.container.service.state.ApplicationStateListener; +import com.ibm.ws.container.service.state.StateChangeException; +import com.ibm.ws.kernel.feature.FeatureProvisioner; +import com.ibm.wsspi.kernel.service.utils.AtomicServiceReference; + +@Component(service = { + ApplicationStateListener.class }, configurationPolicy = ConfigurationPolicy.IGNORE, immediate = true) +public class GrpcClientComponent implements ApplicationStateListener { + + private static final TraceComponent tc = Tr.register(GrpcClientComponent.class, GrpcClientMessages.GRPC_TRACE_NAME, + GrpcClientMessages.GRPC_BUNDLE); + + /** Indicates whether the monitor feature is enabled */ + private static boolean monitoringEnabled = false; + + private final String FEATUREPROVISIONER_REFERENCE_NAME = "featureProvisioner"; + + private final AtomicServiceReference featureProvisioner = new AtomicServiceReference( + FEATUREPROVISIONER_REFERENCE_NAME); + + @Activate + protected void activate(ComponentContext cc) { + featureProvisioner.activate(cc); + } + + @Deactivate + protected void deactivate(ComponentContext cc) { + featureProvisioner.deactivate(cc); + } + + @Reference(name = FEATUREPROVISIONER_REFERENCE_NAME, service = FeatureProvisioner.class, cardinality = ReferenceCardinality.MANDATORY) + protected void setFeatureProvisioner(ServiceReference ref) { + featureProvisioner.setReference(ref); + } + + protected void unsetFeatureProvisioner(ServiceReference ref) { + featureProvisioner.unsetReference(ref); + } + + /** + * Set the indication whether the monitor feature is enabled + */ + private void setMonitoringEnabled() { + Set currentFeatureSet = featureProvisioner.getService().getInstalledFeatures(); + monitoringEnabled = currentFeatureSet.contains("monitor-1.0"); + } + + /** + * @return true if the monitor feature is enabled, + * false otherwise + */ + public static boolean isMonitoringEnabled() { + return monitoringEnabled; + } + + @Override + public void applicationStarting(ApplicationInfo appInfo) throws StateChangeException { + setMonitoringEnabled(); + } + + @Override + public void applicationStarted(ApplicationInfo appInfo) throws StateChangeException { + } + + @Override + public void applicationStopping(ApplicationInfo appInfo) { + } + + @Override + public void applicationStopped(ApplicationInfo appInfo) { + } +} diff --git a/dev/io.openliberty.grpc.1.0.internal.client/src/io/openliberty/grpc/internal/client/LibertyManagedChannelProvider.java b/dev/io.openliberty.grpc.1.0.internal.client/src/io/openliberty/grpc/internal/client/LibertyManagedChannelProvider.java index fddcf7f3ba8..ee85acc4b07 100644 --- a/dev/io.openliberty.grpc.1.0.internal.client/src/io/openliberty/grpc/internal/client/LibertyManagedChannelProvider.java +++ b/dev/io.openliberty.grpc.1.0.internal.client/src/io/openliberty/grpc/internal/client/LibertyManagedChannelProvider.java @@ -71,6 +71,10 @@ private void configureLibertyBuilder(NettyChannelBuilder builder, String target) private void addLibertyInterceptors(NettyChannelBuilder builder) { builder.intercept(new LibertyClientInterceptor()); + ClientInterceptor monitoringInterceptor = createMonitoringClientInterceptor(); + if (monitoringInterceptor != null) { + builder.intercept(monitoringInterceptor); + } } private void addLibertySSLConfig(NettyChannelBuilder builder, String target) { @@ -134,4 +138,24 @@ private void addUserInterceptors(NettyChannelBuilder builder, String target) { } } } + + private ClientInterceptor createMonitoringClientInterceptor() { + // create the interceptor only if the monitor feature is enabled + if (!GrpcClientComponent.isMonitoringEnabled()) { + return null; + } + ClientInterceptor interceptor = null; + // monitoring interceptor + final String className = "io.openliberty.grpc.internal.monitor.GrpcMonitoringClientInterceptor"; + try { + Class clazz = Class.forName(className); + interceptor = (ClientInterceptor) clazz.getDeclaredConstructor() + .newInstance(); + } catch (Exception e) { + // an exception can happen if the monitoring package is not loaded + } + + return interceptor; + } + } diff --git a/dev/io.openliberty.grpc.1.0.internal.common/src/io/openliberty/grpc/internal/Utils.java b/dev/io.openliberty.grpc.1.0.internal.common/src/io/openliberty/grpc/internal/Utils.java index 8c2e2bbbadb..aa0cb78fa8c 100644 --- a/dev/io.openliberty.grpc.1.0.internal.common/src/io/openliberty/grpc/internal/Utils.java +++ b/dev/io.openliberty.grpc.1.0.internal.common/src/io/openliberty/grpc/internal/Utils.java @@ -13,12 +13,6 @@ import static io.grpc.internal.GrpcUtil.CONTENT_TYPE_KEY; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.ibm.websphere.ras.TraceComponent; -import com.ibm.ws.ffdc.FFDCFilter; - import io.grpc.internal.GrpcUtil; public class Utils { diff --git a/dev/io.openliberty.grpc.1.0.internal.monitor/.classpath b/dev/io.openliberty.grpc.1.0.internal.monitor/.classpath new file mode 100644 index 00000000000..173791f0fa3 --- /dev/null +++ b/dev/io.openliberty.grpc.1.0.internal.monitor/.classpath @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/dev/io.openliberty.grpc.1.0.internal.monitor/.project b/dev/io.openliberty.grpc.1.0.internal.monitor/.project new file mode 100644 index 00000000000..b5df983fde8 --- /dev/null +++ b/dev/io.openliberty.grpc.1.0.internal.monitor/.project @@ -0,0 +1,23 @@ + + + io.openliberty.grpc.1.0.internal.monitor + + + + + + org.eclipse.jdt.core.javabuilder + + + + + bndtools.core.bndbuilder + + + + + + org.eclipse.jdt.core.javanature + bndtools.core.bndnature + + diff --git a/dev/io.openliberty.grpc.1.0.internal.monitor/bnd.bnd b/dev/io.openliberty.grpc.1.0.internal.monitor/bnd.bnd new file mode 100644 index 00000000000..99cbef22f58 --- /dev/null +++ b/dev/io.openliberty.grpc.1.0.internal.monitor/bnd.bnd @@ -0,0 +1,58 @@ +#******************************************************************************* +# Copyright (c) 2020 IBM Corporation and others. +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +# +# Contributors: +# IBM Corporation - initial API and implementation +#******************************************************************************* +-include= ~../cnf/resources/bnd/bundle.props +bVersion=1.0 + +Bundle-Name: gRPC Monitor +Bundle-SymbolicName: io.openliberty.grpc.1.0.internal.monitor +Bundle-Description: gRPC Monitor, version ${bVersion} + +# Using version=! in order to not have a version attached to the import for packages that were removed +# from Java after Java 8. Doing this keeps the import like before Java 11 support. It will get the +# packages from Java when using Java 8 or earlier and from the new shipped bundles for Java 9 and later. +Import-Package: \ + !*.internal.*,* + +Export-Package: \ + io.openliberty.grpc.monitor,\ + io.openliberty.grpc.internal.monitor + + +DynamicImport-Package: com.ibm.websphere.monitor.meters;version="1.0.0", \ + com.ibm.websphere.monitor.annotation;version="1.0.0", \ + com.ibm.websphere.monitor.jmx;version="1.0.0", \ + com.ibm.ws.pmi.server;version="[1.1,2)", \ + com.ibm.ws.pmi.stat;version="[1.1,2)", \ + com.ibm.websphere.pmi;version="[1.1,2)", \ + com.ibm.wsspi.pmi.factory;version="[1.1,2)", \ + com.ibm.wsspi.pmi.stat;version="[1.1,2)" + +Liberty-Monitoring-Components: io.openliberty.grpc.internal.monitor.GrpcServerMonitor, \ + io.openliberty.grpc.internal.monitor.GrpcClientMonitor + +instrument.disabled: true + +-buildpath: \ + com.ibm.ws.logging.core,\ + com.ibm.websphere.org.osgi.core;version=latest,\ + com.ibm.websphere.org.osgi.service.component;version=latest,\ + com.ibm.ws.monitor;version=latest,\ + com.ibm.ws.org.osgi.annotation.versioning;version=latest,\ + com.ibm.wsspi.org.osgi.service.component.annotations;version=latest,\ + com.google.api.grpc:proto-google-common-protos;version=1.17.0, \ + com.google.code.findbugs:jsr305;version=3.0.2,\ + com.google.guava:failureaccess;version=1.0.1,\ + com.google.guava:guava;version=27.0.1, \ + com.google.j2objc:j2objc-annotations;version=1.1, \ + com.google.protobuf:protobuf-java;version=3.11.1, \ + org.checkerframework:checker-compat-qual;version=2.5.2,\ + io.openliberty.grpc.1.0.internal.common;version=latest,\ + org.osgi.service.component.annotations;version=latest \ No newline at end of file diff --git a/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcClientMonitor.java b/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcClientMonitor.java new file mode 100644 index 00000000000..f7af803dcbf --- /dev/null +++ b/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcClientMonitor.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package io.openliberty.grpc.internal.monitor; + +import com.ibm.websphere.monitor.annotation.Monitor; +import com.ibm.websphere.monitor.annotation.ProbeAtEntry; +import com.ibm.websphere.monitor.annotation.ProbeAtReturn; +import com.ibm.websphere.monitor.annotation.ProbeSite; +import com.ibm.websphere.monitor.annotation.PublishedMetric; +import com.ibm.websphere.monitor.annotation.This; +import com.ibm.websphere.monitor.meters.MeterCollection; + +/** + * Monitor class for gRPC Client.
+ * This class is responsible for managing the gRPC client MXBean object, as well + * as the actual updating of the values of the counters defined in the MXBean + * object. + */ +@Monitor(group = "GrpcClient") +public class GrpcClientMonitor { + private final static String METRIC_KEY_DELIMETER = "/"; + + @PublishedMetric + public MeterCollection grpcClientCountByName = new MeterCollection( + "GrpcClient", this); + + @ProbeAtEntry + @ProbeSite(clazz = "io.openliberty.grpc.internal.monitor.GrpcClientStatsMonitor", method = "recordCallStarted") + public void atRpcCallStart(@This Object clientStats) { + GrpcClientStatsMonitor stats = (GrpcClientStatsMonitor) clientStats; + getGrpcClientStats(stats.getMethod()).recordCallStarted(); + } + + @ProbeAtEntry + @ProbeSite(clazz = "io.openliberty.grpc.internal.monitor.GrpcClientStatsMonitor", method = "recordClientHandled") + public void atGrpcClientHandled(@This Object clientStats) { + GrpcClientStatsMonitor stats = (GrpcClientStatsMonitor) clientStats; + getGrpcClientStats(stats.getMethod()).recordClientHandled(); + } + + @ProbeAtReturn + @ProbeSite(clazz = "io.openliberty.grpc.internal.monitor.GrpcClientStatsMonitor", method = "recordMsgReceived") + public void atClientMsgReceived(@This Object clientStats) { + GrpcClientStatsMonitor stats = (GrpcClientStatsMonitor) clientStats; + getGrpcClientStats(stats.getMethod()).incrementReceivedMsgCountBy(1); + } + + @ProbeAtReturn + @ProbeSite(clazz = "io.openliberty.grpc.internal.monitor.GrpcClientStatsMonitor", method = "recordMsgSent") + public void atGrpcClientMsgSent(@This Object clientStats) { + GrpcClientStatsMonitor stats = (GrpcClientStatsMonitor) clientStats; + getGrpcClientStats(stats.getMethod()).incrementSentMsgCountBy(1); + } + + private synchronized GrpcClientStats getGrpcClientStats(GrpcMethod method) { + String key = method.serviceName() + METRIC_KEY_DELIMETER + method.methodName(); + GrpcClientStats stats = grpcClientCountByName.get(key); + if (stats == null) { + stats = new GrpcClientStats(method); + grpcClientCountByName.put(key, stats); + } + return stats; + } +} diff --git a/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcClientStats.java b/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcClientStats.java new file mode 100644 index 00000000000..38166032f19 --- /dev/null +++ b/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcClientStats.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package io.openliberty.grpc.internal.monitor; + +import com.ibm.websphere.monitor.meters.Counter; +import com.ibm.websphere.monitor.meters.Meter; + +import io.openliberty.grpc.monitor.GrpcClientStatsMXBean; + +/** + * Holds metrics used for client-side monitoring of gRPC services.
+ * Statistic reported: + *
    + *
  • Total number of RPCs started on the client. + *
  • Total number of RPCs completed on the client, regardless of success or + * failure. + *
  • TODO Histogram of RPC response latency for completed RPCs, in seconds. + *
  • Total number of stream messages received from the server. + *
  • Total number of stream messages sent by the client. + *
+ */ +public class GrpcClientStats extends Meter implements GrpcClientStatsMXBean { + private final Counter rpcStarted; + private final Counter rpcCompleted; + private final Counter streamMessagesReceived; + private final Counter streamMessagesSent; + + private final GrpcMethod method; + + public GrpcClientStats(GrpcMethod method) { + this.method = method; + + rpcStarted = new Counter(); + rpcStarted.setDescription("This shows total number of RPCs started on the client"); + rpcStarted.setUnit("ns"); + + rpcCompleted = new Counter(); + rpcCompleted.setDescription("This shows total number of RPCs completed on the client"); + rpcCompleted.setUnit("ns"); + + streamMessagesReceived = new Counter(); + streamMessagesReceived.setDescription("This shows total number of stream messages received from the server"); + streamMessagesReceived.setUnit("ns"); + + streamMessagesSent = new Counter(); + streamMessagesSent.setDescription("This shows total number of stream messages sent by the client"); + streamMessagesSent.setUnit("ns"); + } + + @Override + public long getRpcStartedCount() { + return rpcStarted.getCurrentValue(); + } + + @Override + public long getRpcCompletedCount() { + return rpcCompleted.getCurrentValue(); + } + + @Override + public long getReceivedMessagesCount() { + return streamMessagesReceived.getCurrentValue(); + } + + @Override + public long getSentMessagesCount() { + return streamMessagesSent.getCurrentValue(); + } + + public void recordCallStarted() { + rpcStarted.incrementBy(1); + } + + public void recordClientHandled() { + rpcCompleted.incrementBy(1); + } + + /** + * This will increment received messages count by the specified number. + * + * @param i + */ + public void incrementReceivedMsgCountBy(int i) { + this.streamMessagesReceived.incrementBy(i); + } + + /** + * This will increment sent messages count by the specified number. + * + * @param i + */ + public void incrementSentMsgCountBy(int i) { + this.streamMessagesSent.incrementBy(i); + } + + public void recordLatency(double latencySec) { + // TODO Auto-generated method stub + + } +} diff --git a/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcClientStatsMonitor.java b/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcClientStatsMonitor.java new file mode 100644 index 00000000000..b7343f9d055 --- /dev/null +++ b/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcClientStatsMonitor.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package io.openliberty.grpc.internal.monitor; + +/** + * Holds metrics used for client-side monitoring of gRPC services.
+ * Statistic monitored: + *
    + *
  • Total number of RPCs started on the client. + *
  • Total number of RPCs completed on the client, regardless of success or + * failure. + *
  • TODO Histogram of RPC response latency for completed RPCs, in seconds. + *
  • Total number of stream messages received from the server. + *
  • Total number of stream messages sent by the client. + *
+ */ +public class GrpcClientStatsMonitor { + + private final GrpcMethod method; + + public GrpcClientStatsMonitor(GrpcMethod method) { + this.method = method; + } + + public void recordCallStarted() { + } + + public void recordClientHandled() { + } + + public void recordMsgReceived() { + } + + public void recordMsgSent() { + } + + public void recordLatency(double latencySec) { + // TODO implement + } + + public String getServiceName() { + return method.serviceName(); + } + + public GrpcMethod getMethod() { + return method; + } +} diff --git a/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcMethod.java b/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcMethod.java new file mode 100644 index 00000000000..c6264baa5b2 --- /dev/null +++ b/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcMethod.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package io.openliberty.grpc.internal.monitor; + +import io.grpc.MethodDescriptor; +import io.grpc.MethodDescriptor.MethodType; + +/** Extracts information about a single gRPC method. */ +class GrpcMethod { + private final String serviceName; + private final String methodName; + private final MethodType type; + + static GrpcMethod of(MethodDescriptor method) { + String serviceName = MethodDescriptor.extractFullServiceName(method.getFullMethodName()); + + // full method names are of the form: "full.serviceName/MethodName". We extract + // the last part. + String methodName = method.getFullMethodName().substring(serviceName.length() + 1); + return new GrpcMethod(serviceName, methodName, method.getType()); + } + + private GrpcMethod(String serviceName, String methodName, MethodType type) { + this.serviceName = serviceName; + this.methodName = methodName; + this.type = type; + } + + String serviceName() { + return serviceName; + } + + String methodName() { + return methodName; + } + + String type() { + return type.toString(); + } + + boolean serverSendsOneMessage() { + return type.serverSendsOneMessage(); + } + + boolean clientSendsOneMessage() { + return type.clientSendsOneMessage(); + } +} diff --git a/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcMonitoringClientCall.java b/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcMonitoringClientCall.java new file mode 100644 index 00000000000..45c02be57e5 --- /dev/null +++ b/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcMonitoringClientCall.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package io.openliberty.grpc.internal.monitor; + +import java.time.Clock; + +import io.grpc.ClientCall; +import io.grpc.ForwardingClientCall; +import io.grpc.Metadata; + +/** + * A {@link SimpleForwardingClientCall} which increments counters for the RPC + * call. + */ +public class GrpcMonitoringClientCall + extends ForwardingClientCall.SimpleForwardingClientCall { + private final GrpcClientStatsMonitor clientMetrics; + private final GrpcMethod grpcMethod; + private final Clock clock; + + protected GrpcMonitoringClientCall(ClientCall delegate, GrpcClientStatsMonitor clientMetrics, + GrpcMethod grpcMethod, Clock clock) { + super(delegate); + this.clientMetrics = clientMetrics; + this.grpcMethod = grpcMethod; + this.clock = clock; + } + + @Override + public void start(ClientCall.Listener delegate, Metadata metadata) { + clientMetrics.recordCallStarted(); + super.start(new GrpcMonitoringClientCallListener<>(delegate, clientMetrics, grpcMethod, clock), + metadata); + } + + @Override + public void sendMessage(ReqT requestMessage) { + if (grpcMethod.serverSendsOneMessage()) { + clientMetrics.recordMsgSent();; + } + super.sendMessage(requestMessage); + } +} diff --git a/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcMonitoringClientCallListener.java b/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcMonitoringClientCallListener.java new file mode 100644 index 00000000000..362a5b3c8d6 --- /dev/null +++ b/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcMonitoringClientCallListener.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package io.openliberty.grpc.internal.monitor; + +import java.time.Clock; +import java.time.Instant; + +import io.grpc.ClientCall; +import io.grpc.ClientCall.Listener; +import io.grpc.ForwardingClientCallListener; +import io.grpc.Metadata; +import io.grpc.Status; + +public class GrpcMonitoringClientCallListener extends ForwardingClientCallListener { + private static final long MILLIS_PER_SECOND = 1000L; + + private final ClientCall.Listener delegate; + private final GrpcClientStatsMonitor clientMetrics; + private final GrpcMethod grpcMethod; + private final Clock clock; + private final Instant startInstant; + + GrpcMonitoringClientCallListener(ClientCall.Listener delegate, GrpcClientStatsMonitor clientMetrics, + GrpcMethod grpcMethod, Clock clock) { + this.delegate = delegate; + this.clientMetrics = clientMetrics; + this.grpcMethod = grpcMethod; + this.clock = clock; + this.startInstant = clock.instant(); + } + + @Override + protected Listener delegate() { + return delegate; + } + + @Override + public void onClose(Status status, Metadata metadata) { + clientMetrics.recordClientHandled();// status.getCode()); + double latencySec = (clock.millis() - startInstant.toEpochMilli()) / (double) MILLIS_PER_SECOND; + clientMetrics.recordLatency(latencySec); + super.onClose(status, metadata); + } + + @Override + public void onMessage(RespT responseMessage) { + if (grpcMethod.clientSendsOneMessage()) { + clientMetrics.recordMsgReceived(); + } + super.onMessage(responseMessage); + } +} diff --git a/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcMonitoringClientInterceptor.java b/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcMonitoringClientInterceptor.java new file mode 100644 index 00000000000..520c4da53a7 --- /dev/null +++ b/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcMonitoringClientInterceptor.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package io.openliberty.grpc.internal.monitor; + +import java.time.Clock; + +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ClientInterceptor; +import io.grpc.MethodDescriptor; + +/** + * A {@link ClientInterceptor} which gathers statistics about incoming GRPC + * calls. + */ +public class GrpcMonitoringClientInterceptor implements ClientInterceptor { + private final Clock clock; + + public GrpcMonitoringClientInterceptor() { + this.clock = Clock.systemDefaultZone(); + } + + @Override + public ClientCall interceptCall(MethodDescriptor methodDescriptor, + CallOptions callOptions, Channel channel) { + GrpcMethod grpcMethod = GrpcMethod.of(methodDescriptor); + GrpcClientStatsMonitor metrics = new GrpcClientStatsMonitor(grpcMethod); + return new GrpcMonitoringClientCall<>(channel.newCall(methodDescriptor, callOptions), metrics, grpcMethod, + clock); + } + +} diff --git a/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcMonitoringServerCall.java b/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcMonitoringServerCall.java new file mode 100644 index 00000000000..cd0a4bdca0f --- /dev/null +++ b/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcMonitoringServerCall.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package io.openliberty.grpc.internal.monitor; + +import java.time.Clock; +import java.time.Instant; + +import io.grpc.ForwardingServerCall; +import io.grpc.Metadata; +import io.grpc.ServerCall; +import io.grpc.Status; + +/** + * A {@link ForwardingServerCall} which updates gRPC metrics based on the + * server-side actions taken for a single RPC, e.g., messages sent, latency, + * etc. + */ +public class GrpcMonitoringServerCall extends ForwardingServerCall.SimpleForwardingServerCall { + private static final long MILLIS_PER_SECOND = 1000L; + + private final Clock clock; + private final GrpcMethod grpcMethod; + private final GrpcServerStatsMonitor serverMetrics; + private final Instant startInstant; + + GrpcMonitoringServerCall(ServerCall delegate, Clock clock, GrpcMethod grpcMethod, GrpcServerStatsMonitor serverMetrics) { + super(delegate); + this.clock = clock; + this.grpcMethod = grpcMethod; + this.serverMetrics = serverMetrics; + this.startInstant = clock.instant(); + + reportStartMetrics(); + } + + @Override + public void close(Status status, Metadata responseHeaders) { + reportEndMetrics(status); + super.close(status, responseHeaders); + } + + @Override + public void sendMessage(S message) { + if (grpcMethod.clientSendsOneMessage()) { + serverMetrics.recordMsgSent(); + } + super.sendMessage(message); + } + + private void reportStartMetrics() { + serverMetrics.recordCallStarted(); + } + + private void reportEndMetrics(Status status) { + serverMetrics.recordServerHandled();//(status.getCode()); + double latencySec = (clock.millis() - startInstant.toEpochMilli()) / (double) MILLIS_PER_SECOND; + serverMetrics.recordLatency(latencySec); + } +} diff --git a/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcMonitoringServerCallListener.java b/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcMonitoringServerCallListener.java new file mode 100644 index 00000000000..3c747e1fe80 --- /dev/null +++ b/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcMonitoringServerCallListener.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package io.openliberty.grpc.internal.monitor; + +import io.grpc.ForwardingServerCallListener; +import io.grpc.ServerCall; + +/** + * A {@link ForwardingServerCallListener} which updates gRPC metrics for a + * single gRPC service based on updates received from gRPC. + */ +class GrpcMonitoringServerCallListener extends ForwardingServerCallListener { + private final ServerCall.Listener delegate; + private final GrpcMethod grpcMethod; + private final GrpcServerStatsMonitor serverMetrics; + + GrpcMonitoringServerCallListener(ServerCall.Listener delegate, GrpcServerStatsMonitor serverMetrics, + GrpcMethod grpcMethod) { + this.delegate = delegate; + this.serverMetrics = serverMetrics; + this.grpcMethod = grpcMethod; + } + + @Override + protected ServerCall.Listener delegate() { + return delegate; + } + + @Override + public void onMessage(R request) { + if (grpcMethod.serverSendsOneMessage()) { + serverMetrics.recordMsgReceived(); + } + super.onMessage(request); + } +} \ No newline at end of file diff --git a/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcMonitoringServerInterceptor.java b/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcMonitoringServerInterceptor.java new file mode 100644 index 00000000000..160d7637d72 --- /dev/null +++ b/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcMonitoringServerInterceptor.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package io.openliberty.grpc.internal.monitor; + +import java.time.Clock; + +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import io.grpc.ServerCall; +import io.grpc.ServerCall.Listener; +import io.grpc.ServerCallHandler; +import io.grpc.ServerInterceptor; + +/** + * A {@link ServerInterceptor} which gathers statistics about incoming GRPC + * calls. + */ +public class GrpcMonitoringServerInterceptor implements ServerInterceptor { + private String serviceName; + private String appName; + private final Clock clock; + + public GrpcMonitoringServerInterceptor(String serviceName, String appName) { + this.serviceName = serviceName; + this.appName = appName; + this.clock = Clock.systemDefaultZone(); + } + + @Override + public Listener interceptCall(ServerCall call, Metadata headers, + ServerCallHandler next) { + MethodDescriptor methodDescriptor = call.getMethodDescriptor(); + GrpcMethod grpcMethod = GrpcMethod.of(methodDescriptor); + GrpcServerStatsMonitor metrics = new GrpcServerStatsMonitor(appName, serviceName); + ServerCall monitoringCall = new GrpcMonitoringServerCall(call, clock, grpcMethod, metrics); + return new GrpcMonitoringServerCallListener<>(next.startCall(monitoringCall, headers), metrics, grpcMethod); + } + +} diff --git a/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcServerMonitor.java b/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcServerMonitor.java new file mode 100644 index 00000000000..b6353d8323c --- /dev/null +++ b/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcServerMonitor.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package io.openliberty.grpc.internal.monitor; + +import com.ibm.websphere.monitor.annotation.Monitor; +import com.ibm.websphere.monitor.annotation.ProbeAtEntry; +import com.ibm.websphere.monitor.annotation.ProbeAtReturn; +import com.ibm.websphere.monitor.annotation.ProbeSite; +import com.ibm.websphere.monitor.annotation.PublishedMetric; +import com.ibm.websphere.monitor.annotation.This; +import com.ibm.websphere.monitor.meters.MeterCollection; + +/** + * Monitor class for gRPC server.
+ * This class is responsible for managing the gRPC server MXBean object, as well + * as the actual updating of the values of the counters defined in the MXBean + * object. + */ +@Monitor(group = "GrpcServer") +public class GrpcServerMonitor { + + @PublishedMetric + public MeterCollection grpcServerCountByName = new MeterCollection( + "GrpcServer", this); + + @ProbeAtEntry + @ProbeSite(clazz = "io.openliberty.grpc.internal.monitor.GrpcServerStatsMonitor", method = "recordCallStarted") + public void atGrpcServerStart(@This Object serverStats) { + GrpcServerStatsMonitor stats = (GrpcServerStatsMonitor) serverStats; + getGrpcServerStats(stats.getAppName(), stats.getServiceName()).recordCallStarted(); + } + + @ProbeAtEntry + @ProbeSite(clazz = "io.openliberty.grpc.internal.monitor.GrpcServerStatsMonitor", method = "recordServerHandled") + public void atGrpcServerHandled(@This Object serverStats) { + GrpcServerStatsMonitor stats = (GrpcServerStatsMonitor) serverStats; + getGrpcServerStats(stats.getAppName(), stats.getServiceName()).recordServerHandled(); + } + + @ProbeAtReturn + @ProbeSite(clazz = "io.openliberty.grpc.internal.monitor.GrpcServerStatsMonitor", method = "recordMsgReceived") + public void atGrpcServerMsgReceived(@This Object serverStats) { + GrpcServerStatsMonitor stats = (GrpcServerStatsMonitor) serverStats; + getGrpcServerStats(stats.getAppName(), stats.getServiceName()).incrementReceivedMsgCountBy(1); + } + + @ProbeAtReturn + @ProbeSite(clazz = "io.openliberty.grpc.internal.monitor.GrpcServerStatsMonitor", method = "recordMsgSent") + public void atGrpcServerMsgSent(@This Object serverStats) { + GrpcServerStatsMonitor stats = (GrpcServerStatsMonitor) serverStats; + getGrpcServerStats(stats.getAppName(), stats.getServiceName()).incrementSentMsgCountBy(1); + } + + private synchronized GrpcServerStats getGrpcServerStats(String appName, String serviceName) { + GrpcServerStats stats = grpcServerCountByName.get(serviceName); + if (stats == null) { + stats = new GrpcServerStats(appName, serviceName); + grpcServerCountByName.put(serviceName, stats); + } + return stats; + } + +} diff --git a/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcServerStats.java b/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcServerStats.java new file mode 100644 index 00000000000..80acfec1cfc --- /dev/null +++ b/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcServerStats.java @@ -0,0 +1,140 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package io.openliberty.grpc.internal.monitor; + +import com.ibm.websphere.monitor.meters.Counter; +import com.ibm.websphere.monitor.meters.Meter; + +import io.openliberty.grpc.monitor.GrpcServerStatsMXBean; + +/** + * This is used to report gRPC Server related statistics.
+ * Statistic reported: + *
    + *
  • Total number of RPCs started on the server. + *
  • Total number of RPCs completed on the server, regardless of success or + * failure. + *
  • TODO Histogram of response latency of RPCs handled by the server, in + * seconds. + *
  • Total number of stream messages received from the client. + *
  • Total number of stream messages sent by the server. + *
+ */ +public class GrpcServerStats extends Meter implements GrpcServerStatsMXBean { + + private String appName; + private String serviceName; + + private Counter receivedMsgCount; + private Counter sentMsgCount; + private Counter serverStarted; + private Counter serverHandled; + + public GrpcServerStats(String aName, String sName) { + setAppName(aName); + setServiceName(sName); + + serverStarted = new Counter(); + serverStarted.setDescription("This shows total number of RPCs started on the server"); + serverStarted.setUnit("ns"); + + serverHandled = new Counter(); + serverHandled.setDescription("This shows total number of RPCs completed on the server"); + serverHandled.setUnit("ns"); + + receivedMsgCount = new Counter(); + receivedMsgCount.setDescription("This shows number of received stream messages"); + receivedMsgCount.setUnit("ns"); + + sentMsgCount = new Counter(); + sentMsgCount.setDescription("This shows number of stream messages sent by the service"); + sentMsgCount.setUnit("ns"); + } + + @Override + public String getAppName() { + return this.appName; + } + + @Override + public String getDescription() { + return "Report gRPC statistics for the specified service and application."; + } + + /** + * @param appName the application name which contains the specified gRPC service + */ + public void setAppName(String appName) { + this.appName = appName; + } + + /** + * @param name the servletName to set + */ + public void setServiceName(String name) { + this.serviceName = name; + } + + public void recordCallStarted() { + serverStarted.incrementBy(1); + } + + public void recordServerHandled() { + serverHandled.incrementBy(1); + } + + /** + * This will increment received messages count by the specified number. + * + * @param i + */ + public void incrementReceivedMsgCountBy(int i) { + this.receivedMsgCount.incrementBy(i); + } + + /** + * This will increment sent messages count by the specified number. + * + * @param i + */ + public void incrementSentMsgCountBy(int i) { + this.sentMsgCount.incrementBy(i); + } + + public void recordLatency(double latencySec) { + // TODO implement + } + + @Override + public String getServiceName() { + return serviceName; + } + + @Override + public long getReceivedMessagesCount() { + return receivedMsgCount.getCurrentValue(); + } + + @Override + public long getSentMessagesCount() { + return sentMsgCount.getCurrentValue(); + } + + @Override + public long getRpcStartedCount() { + return serverStarted.getCurrentValue(); + } + + @Override + public long getRpcCompletedCount() { + return serverHandled.getCurrentValue(); + } +} diff --git a/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcServerStatsMonitor.java b/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcServerStatsMonitor.java new file mode 100644 index 00000000000..5c44bcd6e09 --- /dev/null +++ b/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/GrpcServerStatsMonitor.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package io.openliberty.grpc.internal.monitor; + +/** + * This is used to monitor gRPC Server related statistics.
+ * Statistic monitored: + *
    + *
  • Total number of RPCs started on the server. + *
  • Total number of RPCs completed on the server, regardless of success or + * failure. + *
  • TODO Histogram of response latency of RPCs handled by the server, in + * seconds. + *
  • Total number of stream messages received from the client. + *
  • Total number of stream messages sent by the server. + *
+ */ +public class GrpcServerStatsMonitor { + + private String appName; + private String serviceName; + + public GrpcServerStatsMonitor(String aName, String sName) { + setAppName(aName); + setServiceName(sName); + } + + public String getAppName() { + return this.appName; + } + + /** + * @param appName the application name which contains the specified gRPC service + */ + public void setAppName(String appName) { + this.appName = appName; + } + + /** + * @param name the servletName to set + */ + public void setServiceName(String name) { + this.serviceName = name; + } + + public void recordCallStarted() { + } + + public void recordServerHandled() { + } + + /** + * This will increment received messages count + */ + public void recordMsgReceived() { + } + + /** + * This will increment sent messages count + */ + public void recordMsgSent() { + } + + public void recordLatency(double latencySec) { + // TODO implement + } + + public String getServiceName() { + return serviceName; + } +} diff --git a/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/package-info.java b/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/package-info.java new file mode 100644 index 00000000000..59919033917 --- /dev/null +++ b/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/internal/monitor/package-info.java @@ -0,0 +1,12 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +@org.osgi.annotation.versioning.Version("1.0") +package io.openliberty.grpc.internal.monitor; diff --git a/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/monitor/GrpcClientStatsMXBean.java b/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/monitor/GrpcClientStatsMXBean.java new file mode 100644 index 00000000000..b8c546123a9 --- /dev/null +++ b/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/monitor/GrpcClientStatsMXBean.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package io.openliberty.grpc.monitor; + +/** + * Management interface for MBeans with names of the form "WebSphere:type=GrpcClientStats,name=*" + * where * is the name of a gRPC method of the form /. + * + * @ibm-api + */ +public interface GrpcClientStatsMXBean { + /** + * Retrieves the total number of RPCs started on the client. + * + * @return the total number of RPCs started on the client + */ + public long getRpcStartedCount(); + + /** + * Retrieves the total number of RPCs completed on the client, + * + * @return the total number of RPCs completed on the client + */ + public long getRpcCompletedCount(); + + /** + * Retrieves the total number of stream messages that the client has received from the gRPC server. + * + * @return the total number of stream messages received from the server + */ + public long getReceivedMessagesCount(); + + /** + * Retrieves the total number of stream messages sent by the client. + * + * @return the total number of stream messages sent by the client + */ + public long getSentMessagesCount(); +} diff --git a/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/monitor/GrpcServerStatsMXBean.java b/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/monitor/GrpcServerStatsMXBean.java new file mode 100644 index 00000000000..78691fa3342 --- /dev/null +++ b/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/monitor/GrpcServerStatsMXBean.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package io.openliberty.grpc.monitor; + +/** + * Management interface for MBeans with names of the form "WebSphere:type=GrpcServerStats,name=*" + * where * is the name of a gRPC service. + * + * @ibm-api + */ +public interface GrpcServerStatsMXBean { + + /** + * Retrieves the gRPC service name. + * + * @return the gRPC service name + */ + public String getServiceName(); + + /** + * Retrieves the total number of RPCs started on the server. + * + * @return the total number of RPCs started on the server + */ + public long getRpcStartedCount(); + + /** + * Retrieves the total number of RPCs completed on the server, + * + * @return the total number of RPCs completed on the server + */ + public long getRpcCompletedCount(); + + /** + * Retrieves the total number of stream messages that the server has received for this gRPC service. + * + * @return the total number of stream messages received for this service + */ + public long getReceivedMessagesCount(); + + /** + * Retrieves the total number of stream messages sent by this gRPC service. + * + * @return the total number of stream messages sent by this service + */ + public long getSentMessagesCount(); + + /** + * Retrieves the name of the application of which the gRPC service is a member. + * + * @return application name + */ + public String getAppName(); +} diff --git a/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/monitor/package-info.java b/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/monitor/package-info.java new file mode 100644 index 00000000000..6582559ce4d --- /dev/null +++ b/dev/io.openliberty.grpc.1.0.internal.monitor/src/io/openliberty/grpc/monitor/package-info.java @@ -0,0 +1,15 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +/** + * @version 1.1 + */ +@org.osgi.annotation.versioning.Version("1.1") +package io.openliberty.grpc.monitor; diff --git a/dev/io.openliberty.grpc.1.0.internal/bnd.bnd b/dev/io.openliberty.grpc.1.0.internal/bnd.bnd index 344c9d62462..812ee9390cc 100644 --- a/dev/io.openliberty.grpc.1.0.internal/bnd.bnd +++ b/dev/io.openliberty.grpc.1.0.internal/bnd.bnd @@ -27,7 +27,15 @@ Import-Package: !sun.*,\ !com.google.code.gson,\ !org.checkerframework,\ javax.annotation;version=!,\ + !com.ibm.websphere.monitor.jmx,\ * + +# Use dynamic import of monitor packages to support indepndent/dynamic enablement of monitor feature +DynamicImport-Package: com.ibm.websphere.monitor.meters;version="1.0.0", \ + com.ibm.websphere.monitor.jmx;version="1.0.0", \ + com.ibm.wsspi.request.probe.bci, \ + com.ibm.wsspi.probeExtension, \ + io.openliberty.grpc.internal.monitor Export-Package: \ io.grpc.servlet,\ @@ -45,6 +53,7 @@ Include-Resource: \ com.ibm.websphere.javaee.servlet.4.0;version=latest,\ com.ibm.websphere.org.osgi.core;version=latest,\ com.ibm.websphere.org.osgi.service.component;version=latest,\ + com.ibm.ws.monitor;version=latest,\ com.ibm.ws.org.osgi.annotation.versioning;version=latest,\ com.ibm.wsspi.org.osgi.service.component.annotations;version=latest,\ com.ibm.ws.injection.core;version=latest,\ diff --git a/dev/io.openliberty.grpc.1.0.internal/src/io/grpc/servlet/GrpcServlet.java b/dev/io.openliberty.grpc.1.0.internal/src/io/grpc/servlet/GrpcServlet.java index 5703c23134e..0976809b49b 100644 --- a/dev/io.openliberty.grpc.1.0.internal/src/io/grpc/servlet/GrpcServlet.java +++ b/dev/io.openliberty.grpc.1.0.internal/src/io/grpc/servlet/GrpcServlet.java @@ -16,6 +16,13 @@ package io.grpc.servlet; +import java.io.IOException; +import java.util.List; + +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + import com.google.common.annotations.VisibleForTesting; import com.ibm.websphere.ras.annotation.Trivial; @@ -23,12 +30,6 @@ import io.grpc.ExperimentalApi; import io.openliberty.grpc.internal.servlet.GrpcServletUtils; -import java.io.IOException; -import java.util.List; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - /** * A simple servlet backed by a gRPC server. Must set {@code asyncSupported} to true. The {@code * /contextRoot/urlPattern} must match the gRPC services' path, which is @@ -55,13 +56,13 @@ public class GrpcServlet extends HttpServlet { * added on each gRPC service by {@link * io.grpc.ServerInterceptors#intercept(BindableService, io.grpc.ServerInterceptor...)} */ - public GrpcServlet(List bindableServices) { - this(loadServices(bindableServices)); + public GrpcServlet(List bindableServices, String appName) { + this(loadServices(bindableServices, appName)); } - private static ServletAdapter loadServices(List bindableServices) { + private static ServletAdapter loadServices(List bindableServices, String appName) { ServletServerBuilder serverBuilder = new ServletServerBuilder(); - GrpcServletUtils.addServices(bindableServices, serverBuilder); + GrpcServletUtils.addServices(bindableServices, serverBuilder, appName); return serverBuilder.buildServletAdapter(); } diff --git a/dev/io.openliberty.grpc.1.0.internal/src/io/openliberty/grpc/internal/servlet/GrpcServerComponent.java b/dev/io.openliberty.grpc.1.0.internal/src/io/openliberty/grpc/internal/servlet/GrpcServerComponent.java index 4bbd5eabd96..dab174c5ab6 100644 --- a/dev/io.openliberty.grpc.1.0.internal/src/io/openliberty/grpc/internal/servlet/GrpcServerComponent.java +++ b/dev/io.openliberty.grpc.1.0.internal/src/io/openliberty/grpc/internal/servlet/GrpcServerComponent.java @@ -64,6 +64,8 @@ public class GrpcServerComponent implements ServletContainerInitializer, Applica }; private static boolean useSecurity = false; + /** Indicates whether the monitor feature is enabled */ + private static boolean monitoringEnabled = false; private final String FEATUREPROVISIONER_REFERENCE_NAME = "featureProvisioner"; @@ -124,7 +126,7 @@ public void onStartup(Set> ctx, ServletContext sc) throws ServletExcept // pass all of our grpc service implementors into a new GrpcServlet // and register that new Servlet on this context GrpcServlet grpcServlet = new GrpcServlet( - new ArrayList(grpcServiceClasses.values())); + new ArrayList(grpcServiceClasses.values()), ((WebApp) sc).getApplicationName()); ServletRegistration.Dynamic servletRegistration = sc.addServlet("grpcServlet" + ":" + serviceName, grpcServlet); servletRegistration.setAsyncSupported(true); @@ -213,6 +215,7 @@ private void initGrpcServices(ApplicationInfo appInfo) { @Override public void applicationStarting(ApplicationInfo appInfo) throws StateChangeException { setSecurityEnabled(); + setMonitoringEnabled(); initGrpcServices(appInfo); } @@ -253,4 +256,20 @@ private void setSecurityEnabled() { public static boolean isSecurityEnabled() { return useSecurity; } + + /** + * Set the indication whether the monitor feature is enabled + */ + private void setMonitoringEnabled() { + Set currentFeatureSet = _featureProvisioner.getService().getInstalledFeatures(); + monitoringEnabled = currentFeatureSet.contains("monitor-1.0"); + } + + /** + * @return true if the monitor feature is enabled, + * false otherwise + */ + public static boolean isMonitoringEnabled() { + return monitoringEnabled; + } } diff --git a/dev/io.openliberty.grpc.1.0.internal/src/io/openliberty/grpc/internal/servlet/GrpcServletUtils.java b/dev/io.openliberty.grpc.1.0.internal/src/io/openliberty/grpc/internal/servlet/GrpcServletUtils.java index dbc661f283b..38da38bbd49 100644 --- a/dev/io.openliberty.grpc.1.0.internal/src/io/openliberty/grpc/internal/servlet/GrpcServletUtils.java +++ b/dev/io.openliberty.grpc.1.0.internal/src/io/openliberty/grpc/internal/servlet/GrpcServletUtils.java @@ -233,24 +233,52 @@ public static List getUserInterceptors(String service) { * @param serverBuilder */ public static void addServices(List bindableServices, - ServletServerBuilder serverBuilder) { + ServletServerBuilder serverBuilder, String appName) { for (BindableService service : bindableServices) { - String name = service.bindService().getServiceDescriptor().getName(); + String serviceName = service.bindService().getServiceDescriptor().getName(); // set any user-defined server interceptors and add the service - List interceptors = GrpcServletUtils.getUserInterceptors(name); + List interceptors = GrpcServletUtils.getUserInterceptors(serviceName); // add Liberty auth interceptor to every service interceptors.add(authInterceptor); + // add monitoring interceptor to every service + ServerInterceptor monitoringInterceptor = createMonitoringServerInterceptor(serviceName, appName); + if (monitoringInterceptor != null) { + interceptors.add(monitoringInterceptor); + } serverBuilder.addService(ServerInterceptors.intercept(service, interceptors)); // set the max inbound msg size, if it's configured - int maxInboundMsgSize = GrpcServiceConfigHolder.getMaxInboundMessageSize(name); + int maxInboundMsgSize = GrpcServiceConfigHolder.getMaxInboundMessageSize(serviceName); if (maxInboundMsgSize != -1) { serverBuilder.maxInboundMessageSize(maxInboundMsgSize); } if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) { - Tr.debug(tc, "gRPC service {0} has been registered", name); + Tr.debug(tc, "gRPC service {0} has been registered", serviceName); } } } + + + private static ServerInterceptor createMonitoringServerInterceptor(String serviceName, String appName) { + // create the monitoring interceptor only if the monitor feature is enabled + if (!GrpcServerComponent.isMonitoringEnabled()) { + return null; + } + ServerInterceptor interceptor = null; + // monitoring interceptor + final String className = "io.openliberty.grpc.internal.monitor.GrpcMonitoringServerInterceptor"; + try { + Class clazz = Class.forName(className); + interceptor = (ServerInterceptor) clazz.getDeclaredConstructor(String.class, String.class) + .newInstance(serviceName, appName); + if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) { + Tr.debug(tc, "monitoring interceptor has been added to service {0}", serviceName); + } + } catch (Exception e) { + // an exception can happen if the monitoring package is not loaded + } + + return interceptor; + } }