This is an automated email from the ASF dual-hosted git repository. timoninmaxim pushed a commit to branch ignite-2.13 in repository https://gitbox.apache.org/repos/asf/ignite.git
The following commit(s) were added to refs/heads/ignite-2.13 by this push: new 16b329c IGNITE-15650 : Introduce statistics for platform services (#9768) 16b329c is described below commit 16b329c0c0de56e16b6d9c16d5f47c9bbfc5540f Author: Vladimir Steshin <vlads...@gmail.com> AuthorDate: Mon Mar 28 13:23:35 2022 +0300 IGNITE-15650 : Introduce statistics for platform services (#9768) --- .../services/PlatformServiceConfiguration.java | 52 ++++++ .../platform/services/PlatformServices.java | 14 +- .../processors/service/GridServiceProxy.java | 18 +- .../processors/service/IgniteServiceProcessor.java | 36 +++- .../service/LazyServiceConfiguration.java | 16 ++ .../processors/service/GridServiceMetricsTest.java | 2 +- .../ignite/platform/PlatformDeployServiceTask.java | 122 +++++++++++- .../Services/IJavaService.cs | 12 ++ .../Services/ServicesAsyncWrapper.cs | 3 +- .../Services/ServicesTest.cs | 206 +++++++++++++++++++-- .../Apache.Ignite.Core/Impl/Services/Services.cs | 16 -- .../Apache.Ignite.Core/Services/IServices.cs | 41 ++-- .../Services/ServiceConfiguration.cs | 33 ++++ 13 files changed, 496 insertions(+), 75 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformServiceConfiguration.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformServiceConfiguration.java new file mode 100644 index 0000000..b77f06f --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformServiceConfiguration.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.processors.platform.services; + +import org.apache.ignite.services.ServiceConfiguration; + +/** + * Extended service configuration. Keeps known method names of service to build proper service statistics. + */ +public class PlatformServiceConfiguration extends ServiceConfiguration { + /** */ + private static final long serialVersionUID = 1L; + + /** Known method names of platform service. */ + private String[] mtdNames; + + /** + * Constr. + */ + PlatformServiceConfiguration() { + mtdNames(null); + } + + /** + * @return Known method names of platform service. + */ + public String[] mtdNames() { + return mtdNames; + } + + /** + * Sets known method names of platform service. + */ + void mtdNames(String[] mtdNames) { + this.mtdNames = mtdNames; + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformServices.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformServices.java index 9178acc..06ec10d 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformServices.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformServices.java @@ -484,8 +484,8 @@ public class PlatformServices extends PlatformAbstractTarget { * @param reader Binary reader, * @return Service configuration. */ - @NotNull private ServiceConfiguration dotnetConfiguration(BinaryRawReaderEx reader) { - ServiceConfiguration cfg = new ServiceConfiguration(); + @NotNull private PlatformServiceConfiguration dotnetConfiguration(BinaryRawReaderEx reader) { + PlatformServiceConfiguration cfg = new PlatformServiceConfiguration(); cfg.setName(reader.readString()); cfg.setService(new PlatformDotNetServiceImpl(reader.readObjectDetached(), platformCtx, srvKeepBinary)); @@ -499,6 +499,11 @@ public class PlatformServices extends PlatformAbstractTarget { if (filter != null) cfg.setNodeFilter(platformCtx.createClusterNodeFilter(filter)); + cfg.setStatisticsEnabled(reader.readBoolean()); + + if (cfg.isStatisticsEnabled()) + cfg.mtdNames(reader.readStringArray()); + return cfg; } @@ -513,9 +518,8 @@ public class PlatformServices extends PlatformAbstractTarget { List<ServiceConfiguration> cfgs = new ArrayList<>(numServices); - for (int i = 0; i < numServices; i++) { + for (int i = 0; i < numServices; i++) cfgs.add(dotnetConfiguration(reader)); - } return cfgs; } @@ -822,5 +826,7 @@ public class PlatformServices extends PlatformAbstractTarget { if (svcCfg.getNodeFilter() instanceof PlatformClusterNodeFilterImpl) dotnetFilter = ((PlatformClusterNodeFilterImpl)svcCfg.getNodeFilter()).getInternalPredicate(); w.writeObjectDetached(dotnetFilter); + + w.writeBoolean(svcCfg.isStatisticsEnabled()); } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/service/GridServiceProxy.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/service/GridServiceProxy.java index 8f60b2a..39e7122 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/service/GridServiceProxy.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/service/GridServiceProxy.java @@ -209,7 +209,7 @@ public class GridServiceProxy<T> implements Serializable { if (svc != null) { HistogramMetricImpl hist = svcCtx.isStatisticsEnabled() ? - svcCtx.metrics().findMetric(mtd.getName()) : null; + invocationHistogramm(svcCtx, mtd.getName(), args) : null; return hist == null ? callServiceLocally(svc, mtd, args, callAttrs) : measureCall(hist, () -> callServiceLocally(svc, mtd, args, callAttrs)); @@ -564,7 +564,7 @@ public class GridServiceProxy<T> implements Serializable { Method mtd = ctx.method(key); - HistogramMetricImpl hist = ctx.isStatisticsEnabled() ? ctx.metrics().findMetric(mtd.getName()) : null; + HistogramMetricImpl hist = ctx.isStatisticsEnabled() ? invocationHistogramm(ctx, mtdName, args) : null; Object res = hist == null ? callService(ctx, mtd) : measureCall(hist, () -> callService(ctx, mtd)); @@ -630,6 +630,20 @@ public class GridServiceProxy<T> implements Serializable { } /** + * @return Invocation histogramm for the method. + */ + private static HistogramMetricImpl invocationHistogramm(ServiceContextImpl ctx, String mtdName, Object[] args) { + if (ctx.service() instanceof PlatformService) { + assert args.length > 0 && args[0] instanceof String; + assert ctx.metrics() != null; + + return ctx.metrics().findMetric((String)args[0]); + } + else + return ctx.metrics().findMetric(mtdName); + } + + /** * Exception class that wraps an exception thrown by the service implementation. */ private static class ServiceProxyException extends RuntimeException { diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/service/IgniteServiceProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/service/IgniteServiceProcessor.java index edafbf4..5d5fb3d 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/service/IgniteServiceProcessor.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/service/IgniteServiceProcessor.java @@ -71,6 +71,7 @@ import org.apache.ignite.internal.processors.cluster.DiscoveryDataClusterState; import org.apache.ignite.internal.processors.cluster.IgniteChangeGlobalStateSupport; import org.apache.ignite.internal.processors.metric.MetricRegistry; import org.apache.ignite.internal.processors.platform.services.PlatformService; +import org.apache.ignite.internal.processors.platform.services.PlatformServiceConfiguration; import org.apache.ignite.internal.processors.security.OperationSecurityContext; import org.apache.ignite.internal.processors.security.SecurityContext; import org.apache.ignite.internal.util.future.GridCompoundFuture; @@ -554,6 +555,9 @@ public class IgniteServiceProcessor extends GridProcessorAdapter implements Igni ensure(c.getService() != null, "getService() != null", c.getService()); ensure(c.getTotalCount() > 0 || c.getMaxPerNodeCount() > 0, "c.getTotalCount() > 0 || c.getMaxPerNodeCount() > 0", null); + ensure(!c.isStatisticsEnabled() || !(c.getService() instanceof PlatformService) || + c instanceof PlatformServiceConfiguration, "The service is a platform service and has statistics" + + "enabled. Service configuration must be PlatformServiceConfiguration.", null); } /** @@ -664,7 +668,10 @@ public class IgniteServiceProcessor extends GridProcessorAdapter implements Igni try { byte[] srvcBytes = U.marshal(marsh, cfg.getService()); - cfgsCp.add(new LazyServiceConfiguration(cfg, srvcBytes)); + String[] knownSvcMdtNames = cfg instanceof PlatformServiceConfiguration ? + ((PlatformServiceConfiguration)cfg).mtdNames() : null; + + cfgsCp.add(new LazyServiceConfiguration(cfg, srvcBytes).platformMtdNames(knownSvcMdtNames)); } catch (Exception e) { U.error(log, "Failed to marshal service with configured marshaller " + @@ -1302,7 +1309,7 @@ public class IgniteServiceProcessor extends GridProcessorAdapter implements Igni if (cfg.isStatisticsEnabled()) { if (invocationMetrics == null) - invocationMetrics = createServiceMetrics(srvcCtx); + invocationMetrics = createServiceMetrics(srvcCtx, cfg); srvcCtx.metrics(invocationMetrics); } @@ -1982,18 +1989,27 @@ public class IgniteServiceProcessor extends GridProcessorAdapter implements Igni * Creates metrics registry for the invocation histograms. * * @param srvcCtx ServiceContext. + * @param cfg Service configuration. * @return Created metric registry. */ - private ReadOnlyMetricRegistry createServiceMetrics(ServiceContextImpl srvcCtx) { + private ReadOnlyMetricRegistry createServiceMetrics(ServiceContextImpl srvcCtx, ServiceConfiguration cfg) { MetricRegistry metricRegistry = ctx.metric().registry(serviceMetricRegistryName(srvcCtx.name())); - for (Class<?> itf : allInterfaces(srvcCtx.service().getClass())) { - for (Method mtd : itf.getMethods()) { - if (metricIgnored(mtd.getDeclaringClass())) - continue; + if (cfg instanceof LazyServiceConfiguration && ((LazyServiceConfiguration)cfg).platformMtdNames() != null) { + for (String definedMtdName : ((LazyServiceConfiguration)cfg).platformMtdNames()) { + metricRegistry.histogram(definedMtdName, DEFAULT_INVOCATION_BOUNDS, + DESCRIPTION_OF_INVOCATION_METRIC_PREF + '\'' + definedMtdName + "()'"); + } + } + else { + for (Class<?> itf : allInterfaces(srvcCtx.service().getClass())) { + for (Method mtd : itf.getMethods()) { + if (metricIgnored(mtd.getDeclaringClass())) + continue; - metricRegistry.histogram(mtd.getName(), DEFAULT_INVOCATION_BOUNDS, DESCRIPTION_OF_INVOCATION_METRIC_PREF + - '\'' + mtd.getName() + "()'"); + metricRegistry.histogram(mtd.getName(), DEFAULT_INVOCATION_BOUNDS, + DESCRIPTION_OF_INVOCATION_METRIC_PREF + '\'' + mtd.getName() + "()'"); + } } } @@ -2013,7 +2029,7 @@ public class IgniteServiceProcessor extends GridProcessorAdapter implements Igni * @param srvcName Name of the service. * @return registry name for service {@code srvcName}. */ - static String serviceMetricRegistryName(String srvcName) { + public static String serviceMetricRegistryName(String srvcName) { return metricName(SERVICE_METRIC_REGISTRY, srvcName); } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/service/LazyServiceConfiguration.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/service/LazyServiceConfiguration.java index 7d34665..d0d4df1 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/service/LazyServiceConfiguration.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/service/LazyServiceConfiguration.java @@ -40,6 +40,10 @@ public class LazyServiceConfiguration extends ServiceConfiguration { /** */ private byte[] srvcBytes; + /** Names of platform service methods to build service statistics. */ + @GridToStringExclude + private String[] platformMtdNames; + /** * Default constructor. */ @@ -120,6 +124,18 @@ public class LazyServiceConfiguration extends ServiceConfiguration { return true; } + /** */ + LazyServiceConfiguration platformMtdNames(String[] platformMtdNames) { + this.platformMtdNames = platformMtdNames; + + return this; + } + + /** @return Names of known service methods. */ + String[] platformMtdNames() { + return platformMtdNames; + } + /** {@inheritDoc} */ @Override public String toString() { String svcCls = srvc == null ? "" : srvc.getClass().getSimpleName(); diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/service/GridServiceMetricsTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/service/GridServiceMetricsTest.java index 9b036cf..ddbbc49 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/service/GridServiceMetricsTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/service/GridServiceMetricsTest.java @@ -299,7 +299,7 @@ public class GridServiceMetricsTest extends GridCommonAbstractTest { * @param histogram Histogram to traverse. * @return Sum of all entries of {@code histogram} buckets. */ - private static long sumHistogramEntries(HistogramMetric histogram) { + public static long sumHistogramEntries(HistogramMetric histogram) { if (histogram == null) return 0; diff --git a/modules/core/src/test/java/org/apache/ignite/platform/PlatformDeployServiceTask.java b/modules/core/src/test/java/org/apache/ignite/platform/PlatformDeployServiceTask.java index 0436d4c..275d905 100644 --- a/modules/core/src/test/java/org/apache/ignite/platform/PlatformDeployServiceTask.java +++ b/modules/core/src/test/java/org/apache/ignite/platform/PlatformDeployServiceTask.java @@ -30,6 +30,8 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.ignite.Ignite; import org.apache.ignite.IgniteCache; import org.apache.ignite.IgniteException; @@ -39,7 +41,10 @@ import org.apache.ignite.compute.ComputeJob; import org.apache.ignite.compute.ComputeJobAdapter; import org.apache.ignite.compute.ComputeJobResult; import org.apache.ignite.compute.ComputeTaskAdapter; +import org.apache.ignite.compute.ComputeTaskSplitAdapter; +import org.apache.ignite.internal.IgniteEx; import org.apache.ignite.internal.binary.BinaryArray; +import org.apache.ignite.internal.util.lang.IgnitePair; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.platform.model.ACL; @@ -55,11 +60,17 @@ import org.apache.ignite.platform.model.User; import org.apache.ignite.platform.model.Value; import org.apache.ignite.resources.IgniteInstanceResource; import org.apache.ignite.services.Service; +import org.apache.ignite.services.ServiceConfiguration; import org.apache.ignite.services.ServiceContext; +import org.apache.ignite.spi.metric.HistogramMetric; +import org.apache.ignite.spi.metric.Metric; +import org.apache.ignite.spi.metric.ReadOnlyMetricRegistry; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import static java.util.Calendar.JANUARY; +import static org.apache.ignite.internal.processors.service.GridServiceMetricsTest.sumHistogramEntries; +import static org.apache.ignite.internal.processors.service.IgniteServiceProcessor.serviceMetricRegistryName; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -102,7 +113,14 @@ public class PlatformDeployServiceTask extends ComputeTaskAdapter<String, Object /** {@inheritDoc} */ @Override public Object execute() throws IgniteException { - ignite.services().deployNodeSingleton(serviceName, new PlatformTestService()); + ServiceConfiguration svcCfg = new ServiceConfiguration(); + + svcCfg.setStatisticsEnabled(true); + svcCfg.setName(serviceName); + svcCfg.setMaxPerNodeCount(1); + svcCfg.setService(new PlatformTestService()); + + ignite.services().deploy(svcCfg); return null; } @@ -111,10 +129,10 @@ public class PlatformDeployServiceTask extends ComputeTaskAdapter<String, Object /** * Test service. */ - public static class PlatformTestService implements Service { + public static class PlatformTestService implements Service, PlatformHelperService { /** */ @IgniteInstanceResource - private Ignite ignite; + private IgniteEx ignite; /** */ private boolean isCancelled; @@ -671,10 +689,96 @@ public class PlatformDeployServiceTask extends ComputeTaskAdapter<String, Object } } + /** {@inheritDoc} */ + @Override public int testNumberOfInvocations(String svcName, String histName) { + return ignite.compute().execute(new CountServiceMetricsTask(), new IgnitePair<>(svcName, histName)); + } + /** */ public Object contextAttribute(String name) { return svcCtx.currentCallContext().attribute(name); } + + /** + * Calculates number of registered values among the service statistics. Can process all service metrics or + * certain named one. + */ + private static class CountServiceMetricsTask extends ComputeTaskSplitAdapter<IgnitePair<String>, Integer> { + /** {@inheritDoc} */ + @Override public Integer reduce(List<ComputeJobResult> results) throws IgniteException { + int cnt = 0; + + for (ComputeJobResult res : results) { + if (res.isCancelled()) { + throw new IgniteException("Unable to count invocations in service metrics. Job was canceled " + + "on node [" + res.getNode() + "]."); + } + + if (res.getException() != null) { + throw new IgniteException("Unable to count invocations in service metrics. Job failed on " + + "node [" + res.getNode() + "]: " + res.getException().getMessage(), res.getException()); + } + + if (res.getData() == null) + continue; + + cnt += (int)res.getData(); + } + + return cnt; + } + + /** {@inheritDoc} */ + @Override protected Collection<? extends ComputeJob> split(int gridSize, + IgnitePair<String> arg) throws IgniteException { + return Stream.generate(() -> new CountServiceMetricsLocallyJob(arg.get1(), arg.get2())).limit(gridSize). + collect(Collectors.toList()); + } + + /** Summs invocation of service methods by service statistics on certain node. */ + private static class CountServiceMetricsLocallyJob extends ComputeJobAdapter { + /** Service name. */ + private final String svcName; + + /** Name of the histogramm. If {@code null}, every histogram in the service metric is processed. */ + @Nullable private final String histName; + + /** */ + @IgniteInstanceResource + private IgniteEx ignite; + + /** + * @param svcName Service name. + * @param histName Name of the histogramm. If {@code null}, every histogram in the service metric is + * processed. + */ + private CountServiceMetricsLocallyJob(String svcName, @Nullable String histName) { + this.svcName = svcName; + this.histName = histName; + } + + /** {@inheritDoc} */ + @Override public Integer execute() throws IgniteException { + ReadOnlyMetricRegistry metrics = ignite.context().metric().registry( + serviceMetricRegistryName(svcName)); + + if (histName != null && !histName.isEmpty()) { + HistogramMetric hist = metrics.findMetric(histName); + + return hist == null ? 0 : (int)sumHistogramEntries(hist); + } + + int cnt = 0; + + for (Metric metric : metrics) { + if (metric instanceof HistogramMetric) + cnt += sumHistogramEntries((HistogramMetric)metric); + } + + return cnt; + } + } + } } /** */ @@ -700,4 +804,16 @@ public class PlatformDeployServiceTask extends ComputeTaskAdapter<String, Object super(msg); } } + + /** + * Platform helper service. + */ + private interface PlatformHelperService { + /** + * Calculates number of registered values among the service statistics. + * + * @return Number of registered values among the service statistics. + */ + int testNumberOfInvocations(String svcName, String histName); + } } diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/IJavaService.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/IJavaService.cs index 12fd52d..c1f7591 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/IJavaService.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/IJavaService.cs @@ -216,4 +216,16 @@ namespace Apache.Ignite.Core.Tests.Services /** */ object contextAttribute(string name); } + + /// <summary> + /// Interface for the methods that are available only on Java side. + /// </summary> + [SuppressMessage("ReSharper", "InconsistentNaming")] + public interface IJavaOnlyService : IJavaService + { + /// <summary> + /// Returns number of measured by service metrics invocations of all service's methods or of its certain method. + /// </summary> + int testNumberOfInvocations(string svcName, string histName = null); + } } diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesAsyncWrapper.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesAsyncWrapper.cs index 6b834bf..42fbb3f 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesAsyncWrapper.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesAsyncWrapper.cs @@ -169,17 +169,18 @@ namespace Apache.Ignite.Core.Tests.Services return _services.GetServiceDescriptors(); } +#pragma warning disable 618 /** <inheritDoc /> */ public T GetService<T>(string name) { return _services.GetService<T>(name); } - /** <inheritDoc /> */ public ICollection<T> GetServices<T>(string name) { return _services.GetServices<T>(name); } +#pragma warning restore 618 /** <inheritDoc /> */ public T GetServiceProxy<T>(string name) where T : class diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs index 798a98e..a89a539 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs @@ -41,6 +41,7 @@ namespace Apache.Ignite.Core.Tests.Services /// <summary> /// Services tests. /// </summary> +#pragma warning disable 618 public class ServicesTest { /** */ @@ -410,10 +411,6 @@ namespace Apache.Ignite.Core.Tests.Services Assert.AreEqual(43, svc.TestOverload(2, ServicesTypeAutoResolveTest.Param)); - // Check local scenario (proxy should not be created for local instance) - Assert.IsTrue(ReferenceEquals(Grid2.GetServices().GetService<ITestIgniteService>(SvcName), - Grid2.GetServices().GetServiceProxy<ITestIgniteService>(SvcName))); - // Check sticky = false: call multiple times, check that different nodes get invoked var invokedIds = Enumerable.Range(1, 100).Select(x => prx.NodeId).Distinct().ToList(); Assert.AreEqual(2, invokedIds.Count); @@ -490,10 +487,111 @@ namespace Apache.Ignite.Core.Tests.Services // Make sure there is an instance on grid1. var svcInst = Grid1.GetServices().GetService<ITestIgniteService>(SvcName); Assert.IsNotNull(svcInst); + } + + /// <summary> + /// Tests statistics of pure Java service. Call the service itself. + /// </summary> + [Test] + public void TestJavaServiceStatistics() + { + // Java service itself. + var helperSvc = Grid1.GetServices().GetServiceProxy<IJavaOnlyService>(_javaSvcName, false); + + // Check metrics of pure java service. There were no invocations yet. + Assert.AreEqual(0, helperSvc.testNumberOfInvocations(_javaSvcName)); + // Now we did 1 invocation of pure Java service just before. + Assert.AreEqual(1, helperSvc.testNumberOfInvocations(_javaSvcName)); + // In total we did 2 calls by now. + Assert.AreEqual(2, helperSvc.testNumberOfInvocations(_javaSvcName)); + } + + /// <summary> + /// Tests statistics of a platform service from client/remote node. + /// </summary> + [Test] + public void TestStatisticsRemote() + { + DoTestMetrics(Grid1.GetServices(), _client.GetServices(), null, false); + + DoTestMetrics(_client.GetServices(), _client.GetServices(), null, false); + } + + /// <summary> + /// Tests statistics of a platform service from client/remote node using a call context. + /// </summary> + [Test] + public void TestStatisticsRemoteWithCallCtx() + { + DoTestMetrics(Grid1.GetServices(), _client.GetServices(), callContext(), false); + + DoTestMetrics(_client.GetServices(), _client.GetServices(), callContext(), false); + } + + /// <summary> + /// Tests statistics of a dynamically-proxied platform service from client/remote node. + /// </summary> + [Test] + public void TestStatisticsRemoteDynamically() + { + DoTestMetrics(Grid1.GetServices(), _client.GetServices(), null, true); + + DoTestMetrics(_client.GetServices(), _client.GetServices(), null, true); + } + + /// <summary> + /// Tests statistics of a dynamically-proxied platform service from client/remote node using a call context. + /// </summary> + [Test] + public void TestStatisticsRemoteDynamicallyWithCallContext() + { + DoTestMetrics(Grid1.GetServices(), _client.GetServices(), callContext(), true); + + DoTestMetrics(_client.GetServices(), _client.GetServices(), callContext(), true); + } + + /// <summary> + /// Tests statistics of a platform service from server/local node. + /// </summary> + [Test] + public void TestStatisticsLocal() + { + DoTestMetrics(Grid1.GetServices(), Grid1.GetServices(), null, false); + + DoTestMetrics(_client.GetServices(), Grid1.GetServices(), null, false); + } + + /// <summary> + /// Tests statistics of a platform service from server/local node using the call context. + /// </summary> + [Test] + public void TestStatisticsLocalWithCallContext() + { + DoTestMetrics(Grid1.GetServices(), Grid1.GetServices(), callContext(), false); - // Get dynamic proxy that simply wraps the service instance. - var prx = Grid1.GetServices().GetDynamicServiceProxy(SvcName); - Assert.AreSame(prx, svcInst); + DoTestMetrics(_client.GetServices(), Grid1.GetServices(), callContext(), false); + } + + /// <summary> + /// Tests statistics of a dynamically-proxied platform service from server/local node. + /// </summary> + [Test] + public void TestStatisticsLocalDynamic() + { + DoTestMetrics(Grid1.GetServices(), Grid1.GetServices(), null, true); + + DoTestMetrics(_client.GetServices(), Grid1.GetServices(), null, true); + } + + /// <summary> + /// Tests statistics of a dynamically-proxied platform service from server/local node using a call context. + /// </summary> + [Test] + public void TestStatisticsLocalDynamicWithCallContext() + { + DoTestMetrics(Grid1.GetServices(), Grid1.GetServices(), callContext(), true); + + DoTestMetrics(_client.GetServices(), Grid1.GetServices(), callContext(), true); } /// <summary> @@ -1287,7 +1385,7 @@ namespace Apache.Ignite.Core.Tests.Services } /// <summary> - /// Tets binary methods in services. + /// Tests binary methods in services. /// </summary> private void DoTestBinary(IJavaService svc, IJavaService binSvc, bool isPlatform) { @@ -1331,6 +1429,88 @@ namespace Apache.Ignite.Core.Tests.Services } /// <summary> + /// Tests platform service statistics. + /// </summary> + private void DoTestMetrics(IServices producer, IServices consumer, IServiceCallContext callCtx, bool dyn) + { + var cfg = new ServiceConfiguration + { + Name = "TestMetricsSrv", + MaxPerNodeCount = 1, + TotalCount = 3, + Service = new PlatformTestService(), + }; + + producer.Deploy(cfg); + + var svc = dyn + ? consumer.GetDynamicServiceProxy(cfg.Name, false, callCtx) + : consumer.GetServiceProxy<IJavaService>(cfg.Name, false, callCtx); + + // Subject service, calculates invocations. + var helperSvc = producer.GetServiceProxy<IJavaOnlyService>(_javaSvcName, false); + + // Do 4 invocations. + Assert.AreEqual(3, dyn ? svc.testOverload(1, 2) : ((IJavaService)svc).testOverload(1, 2)); + Assert.AreEqual(2, dyn ? svc.test(1) : ((IJavaService)svc).test(1)); + Assert.AreEqual(true, dyn ? svc.test(false) : ((IJavaService)svc).test(false)); + Assert.AreEqual(null, dyn ? svc.testNull(null) : ((IJavaService)svc).testNull(null)); + + // Service stats. is not enabled. + Assert.AreEqual(0, helperSvc.testNumberOfInvocations(cfg.Name)); + + producer.Cancel(cfg.Name); + + AssertNoService(cfg.Name); + + // Redeploy service with enabled stats. + cfg.StatisticsEnabled = true; + cfg.Service = new PlatformTestService(); + producer.Deploy(cfg); + + svc = dyn + ? consumer.GetDynamicServiceProxy(cfg.Name, false, callCtx) + : consumer.GetServiceProxy<IJavaService>(cfg.Name, false, callCtx); + + // Service metrics exists but holds no values. + Assert.AreEqual(0, helperSvc.testNumberOfInvocations(cfg.Name)); + + // One invocation. + Assert.AreEqual(2, dyn ? svc.test(1) : ((IJavaService)svc).test(1)); + + // There should be just one certain and one total invocation. + Assert.AreEqual(1, helperSvc.testNumberOfInvocations(cfg.Name, "test")); + Assert.AreEqual(1, helperSvc.testNumberOfInvocations(cfg.Name)); + + // Do 4 more invocations. + Assert.AreEqual(3, dyn ? svc.testOverload(1, 2) : ((IJavaService)svc).testOverload(1, 2)); + Assert.AreEqual(2, dyn ? svc.test(1) : ((IJavaService)svc).test(1)); + Assert.AreEqual(true, dyn ? svc.test(false) : ((IJavaService)svc).test(false)); + Assert.AreEqual(null, dyn ? svc.testNull(null) : ((IJavaService)svc).testNull(null)); + + // We did 3 invocations of method named 'test(...)' in total. + Assert.AreEqual(3, helperSvc.testNumberOfInvocations(cfg.Name, "test")); + + // We did 1 invocations of method named 'testOverload(...)' in total. + Assert.AreEqual(1, helperSvc.testNumberOfInvocations(cfg.Name, "testOverload")); + + Assert.AreEqual(1, helperSvc.testNumberOfInvocations(cfg.Name, "testNull")); + + // We did 5 total invocations. + Assert.AreEqual(5, helperSvc.testNumberOfInvocations(cfg.Name)); + + // Check side methods are not measured. We still have only 5 invocations. + Assert.AreEqual("Apache.Ignite.Core.Tests.Services.PlatformTestService", svc.ToString()); + Assert.AreEqual(5, helperSvc.testNumberOfInvocations(cfg.Name)); + // 'ToString' must not be measured. Like Java service metrics, it's not declared as a service interface. + Assert.AreEqual(0, helperSvc.testNumberOfInvocations(cfg.Name, "ToString")); + + // Undeploy again. + producer.Cancel(cfg.Name); + AssertNoService(cfg.Name); + } + + /// <summary> /// Tests the footer setting. /// </summary> [Test] @@ -1964,14 +2144,4 @@ namespace Apache.Ignite.Core.Tests.Services } #endif } - - /// <summary> Tests with UseBinaryArray = true. </summary> - public class ServicesTestBinaryArrays : ServicesTest - { - /** */ - public ServicesTestBinaryArrays() : base(true) - { - // No-op. - } - } } diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/Services.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/Services.cs index eeb4b96..1d986f4 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/Services.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/Services.cs @@ -378,13 +378,6 @@ namespace Apache.Ignite.Core.Impl.Services IgniteArgumentCheck.Ensure(typeof(T).IsInterface, "T", "Service proxy type should be an interface: " + typeof(T)); - T locInst; - - // In local scenario try to return service instance itself instead of a proxy - // Get as object because proxy interface may be different from real interface - if (callCtx == null && (locInst = GetService<object>(name) as T) != null) - return locInst; - var javaProxy = DoOutOpObject(OpServiceProxy, w => { w.WriteString(name); @@ -415,15 +408,6 @@ namespace Apache.Ignite.Core.Impl.Services { IgniteArgumentCheck.NotNullOrEmpty(name, "name"); - // In local scenario try to return service instance itself instead of a proxy - if (callCtx == null) - { - var locInst = GetService<object>(name); - - if (locInst != null) - return locInst; - } - var javaProxy = DoOutOpObject(OpServiceProxy, w => { w.WriteString(name); diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Services/IServices.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Services/IServices.cs index 569712e..07a36ec 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Services/IServices.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Services/IServices.cs @@ -17,6 +17,7 @@ namespace Apache.Ignite.Core.Services { + using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; @@ -240,6 +241,8 @@ namespace Apache.Ignite.Core.Services /// <typeparam name="T">Service type.</typeparam> /// <param name="name">Service name.</param> /// <returns>Deployed service with specified name.</returns> + [Obsolete("Corrupts the service statistics. Use the proxies like GetServiceProxy() or " + + "GetDynamicServiceProxy() instead.")] T GetService<T>(string name); /// <summary> @@ -248,33 +251,33 @@ namespace Apache.Ignite.Core.Services /// <typeparam name="T">Service type.</typeparam> /// <param name="name">Service name.</param> /// <returns>All deployed services with specified name.</returns> + [Obsolete("Corrupts the service statistics. Use the proxies like GetServiceProxy() or " + + "GetDynamicServiceProxy() instead.")] ICollection<T> GetServices<T>(string name); /// <summary> - /// Gets a remote handle on the service. If service is available locally, - /// then local instance is returned, otherwise, a remote proxy is dynamically - /// created and provided for the specified service. + /// Gets a handle on remote or local service. The proxy is dynamically created and provided for the specified + /// service. /// </summary> /// <typeparam name="T">Service type.</typeparam> /// <param name="name">Service name.</param> - /// <returns>Either proxy over remote service or local service if it is deployed locally.</returns> + /// <returns>Proxy over service.</returns> T GetServiceProxy<T>(string name) where T : class; /// <summary> - /// Gets a remote handle on the service. If service is available locally, - /// then local instance is returned, otherwise, a remote proxy is dynamically - /// created and provided for the specified service. + /// Gets a handle on remote or local service. The proxy is dynamically created and provided for the specified + /// service. /// </summary> /// <typeparam name="T">Service type.</typeparam> /// <param name="name">Service name.</param> /// <param name="sticky">Whether or not Ignite should always contact the same remote /// service or try to load-balance between services.</param> - /// <returns>Either proxy over remote service or local service if it is deployed locally.</returns> + /// <returns>Proxy over service.</returns> T GetServiceProxy<T>(string name, bool sticky) where T : class; /// <summary> - /// Gets a remote handle on the service with the specified caller context. - /// The proxy is dynamically created and provided for the specified service. + /// Gets a handle on remote or local service with the specified caller context. The proxy is dynamically + /// created and provided for the specified service. /// </summary> /// <typeparam name="T">Service type.</typeparam> /// <param name="name">Service name.</param> @@ -287,21 +290,19 @@ namespace Apache.Ignite.Core.Services T GetServiceProxy<T>(string name, bool sticky, IServiceCallContext callCtx) where T : class; /// <summary> - /// Gets a remote handle on the service as a dynamic object. If service is available locally, - /// then local instance is returned, otherwise, a remote proxy is dynamically - /// created and provided for the specified service. + /// Gets a handle on remote or local service as a dynamic object. The proxy is dynamically created and provided + /// for the specified service. /// <para /> /// This method utilizes <c>dynamic</c> feature of the language and does not require any /// service interfaces or classes. Java services can be accessed as well as .NET services. /// </summary> /// <param name="name">Service name.</param> - /// <returns>Either proxy over remote service or local service if it is deployed locally.</returns> + /// <returns>Proxy over service.</returns> dynamic GetDynamicServiceProxy(string name); /// <summary> - /// Gets a remote handle on the service as a dynamic object. If service is available locally, - /// then local instance is returned, otherwise, a remote proxy is dynamically - /// created and provided for the specified service. + /// Gets a handle on remote or local service as a dynamic object. The proxy is dynamically created and provided + /// for the specified service. /// <para /> /// This method utilizes <c>dynamic</c> feature of the language and does not require any /// service interfaces or classes. Java services can be accessed as well as .NET services. @@ -309,12 +310,12 @@ namespace Apache.Ignite.Core.Services /// <param name="name">Service name.</param> /// <param name="sticky">Whether or not Ignite should always contact the same remote /// service or try to load-balance between services.</param> - /// <returns>Either proxy over remote service or local service if it is deployed locally.</returns> + /// <returns>Proxy over service.</returns> dynamic GetDynamicServiceProxy(string name, bool sticky); /// <summary> - /// Gets a remote handle on the service with the specified caller context. - /// The proxy is dynamically created and provided for the specified service. + /// Gets a handle on remote or local service as a dynamic object with the specified caller context. The proxy + /// is dynamically created and provided for the specified service. /// <para /> /// This method utilizes <c>dynamic</c> feature of the language and does not require any /// service interfaces or classes. Java services can be accessed as well as .NET services. diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Services/ServiceConfiguration.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Services/ServiceConfiguration.cs index a7b9e7f..9753b98 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Services/ServiceConfiguration.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Services/ServiceConfiguration.cs @@ -20,6 +20,8 @@ namespace Apache.Ignite.Core.Services using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; + using System.Linq; + using System.Reflection; using Apache.Ignite.Core.Binary; using Apache.Ignite.Core.Cluster; @@ -63,6 +65,12 @@ namespace Apache.Ignite.Core.Services /// Gets or sets node filter used to filter nodes on which the service will be deployed. /// </summary> public IClusterNodeFilter NodeFilter { get; set; } + + /// <summary> + /// Enables or disables service statistics. + /// NOTE: Service statistics work only via service proxies. <see cref="IServices.GetServiceProxy{T}(string)"/> + /// </summary> + public bool StatisticsEnabled { get; set; } /// <summary> /// Serializes the Service configuration using IBinaryRawWriter @@ -83,6 +91,29 @@ namespace Apache.Ignite.Core.Services w.WriteObject(NodeFilter); else w.WriteObject<object>(null); + + w.WriteBoolean(StatisticsEnabled); + + WriteExtraDescription(w); + } + + /// <summary> + /// Provides extra info about platform service to avoid on-demand creation of service statistics on any + /// out-of-interface calls or things like 'ToString()'. + /// </summary> + private void WriteExtraDescription(IBinaryRawWriter writer) + { + if (StatisticsEnabled) + { + // Methods names of user interfaces of the service. + var mtdNames = Service.GetType().GetInterfaces() + // No need to measure methods of these interface. + .Where(t => t != typeof(IService)) + .SelectMany(t => t.GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | + BindingFlags.Public ).Select(mtd => mtd.Name)).Distinct(); + + writer.WriteStringArray(mtdNames.ToArray()); + } } /// <summary> @@ -125,6 +156,8 @@ namespace Apache.Ignite.Core.Services { // Ignore exceptions in user deserealization code. } + + StatisticsEnabled = r.ReadBoolean(); } } } \ No newline at end of file