blob: ddbbc497daed703d4e7f4d6c5dbec10a6fe1ac1a [file] [log] [blame]
/*
* 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.service;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import com.google.common.collect.Iterables;
import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.internal.IgniteEx;
import org.apache.ignite.internal.processors.metric.GridMetricManager;
import org.apache.ignite.internal.processors.service.inner.MyService;
import org.apache.ignite.internal.processors.service.inner.MyServiceFactory;
import org.apache.ignite.internal.util.typedef.G;
import org.apache.ignite.services.Service;
import org.apache.ignite.services.ServiceConfiguration;
import org.apache.ignite.spi.metric.HistogramMetric;
import org.apache.ignite.spi.metric.Metric;
import org.apache.ignite.spi.metric.ReadOnlyMetricRegistry;
import org.apache.ignite.spi.metric.jmx.JmxMetricExporterSpi;
import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
import org.junit.Test;
import static org.apache.ignite.internal.processors.service.IgniteServiceProcessor.serviceMetricRegistryName;
/**
* Tests metrics of service invocations.
*/
public class GridServiceMetricsTest extends GridCommonAbstractTest {
/** Number of service invocations. */
private static final int INVOKE_CNT = 50;
/** Service name used in the tests. */
private static final String SRVC_NAME = "TestService";
/** Error message of created metrics. */
private static final String METRICS_MUST_NOT_BE_CREATED = "Service metric registry must not be created.";
/** Utility holder of current grid number. */
private int gridNum;
/** {@inheritDoc} */
@Override protected void afterTest() throws Exception {
stopAllGrids();
super.afterTest();
}
/** {@inheritDoc} */
@Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName);
// JMX metrics exposition to see actual namings and placement of the metrics.
cfg.setMetricExporterSpi(new JmxMetricExporterSpi());
return cfg;
}
/** Checks service metrics are enabled / disabled properly. */
@Test
public void testServiceMetricsEnabledDisabled() throws Exception {
IgniteEx ignite = startGrid();
ServiceConfiguration srvcCfg = serviceCfg(MyServiceFactory.create(), 0, 1);
srvcCfg.setStatisticsEnabled(false);
ignite.services().deploy(srvcCfg);
assertNull(METRICS_MUST_NOT_BE_CREATED, findMetricRegistry(ignite.context().metric(), SRVC_NAME));
ignite.services().cancel(SRVC_NAME);
srvcCfg.setStatisticsEnabled(true);
ignite.services().deploy(srvcCfg);
assertNotNull("Service metric registry must be created.",
findMetricRegistry(ignite.context().metric(), SRVC_NAME));
}
/** Checks metric are created when service is deployed and removed when service is undeployed. */
@Test
public void testMetricsOnServiceDeployAndCancel() throws Exception {
List<IgniteEx> grids = startGrids(3, false);
// 2 services per node.
grids.get(0).services().deploy(serviceCfg(MyServiceFactory.create(), grids.size() * 2, 2));
awaitPartitionMapExchange();
int expectedCnt = Arrays.stream(MyService.class.getDeclaredMethods()).map(Method::getName).collect(
Collectors.toSet()).size();
// Make sure metrics are registered.
for (IgniteEx ignite : grids)
assertEquals(metricsCnt(ignite), expectedCnt);
grids.get(0).services().cancel(SRVC_NAME);
awaitPartitionMapExchange();
for (IgniteEx ignite : grids)
assertEquals(metricsCnt(ignite), 0);
}
/** Tests service metrics migrates correclty with the service redeployment. */
@Test
public void testRedeploy() throws Exception {
List<IgniteEx> grids = startGrids(3, false);
// 2 services per node.
grid(0).services().deploy(serviceCfg(MyServiceFactory.create(), 1, 0));
awaitPartitionMapExchange();
// Only same method metric count must persist across the cluster for the singleton.
int expectedCnt = Arrays.stream(MyService.class.getDeclaredMethods()).map(Method::getName).collect(
Collectors.toSet()).size();
// Only same method metric count must persist across the cluster for the singleton.
assertEquals("Only one metric registry can persist for one service instance", expectedCnt,
grids.stream().map(GridServiceMetricsTest::metricsCnt).mapToInt(Integer::intValue).sum());
for (int i = 0; i < grids.size(); ++i) {
if (metricsCnt(grid(i)) > 0) {
stopGrid(i);
awaitPartitionMapExchange();
break;
}
}
// Only same method metric count must persist across the cluster for the singleton.
assertEquals("Only one metric registry can persist for one service instance", expectedCnt,
G.allGrids().stream().map(grid -> metricsCnt((IgniteEx)grid)).mapToInt(Integer::intValue).sum());
}
/** Tests service metrics for single service instance. */
@Test
public void testServiceMetricsSingle() throws Throwable {
testServiceMetrics(1, 1, 1, 1);
}
/** Tests service metrics for multy service instance: one per server. */
@Test
public void testServiceMetricsMulty() throws Throwable {
testServiceMetrics(3, 3, 3, 1);
}
/** Tests service metrics for multy service instance: fewer that servers and clients. */
@Test
public void testServiceMetricsMultyFew() throws Throwable {
testServiceMetrics(4, 3, 2, 1);
}
/** Tests service metrics for multy service instance: serveral instances per node. */
@Test
public void testServiceMetricsMultyDuplicated() throws Throwable {
testServiceMetrics(3, 2, 3, 3);
}
/** Tests service metrics for multy service instance: serveral instances per node, total fewer that servers. */
@Test
public void testServiceMetricsMultyFewDuplicated() throws Throwable {
testServiceMetrics(5, 4, 3, 2);
}
/**
* Invokes service in various ways: from clients, servers, etc. Checks these calls reflect in the metrics.
*
* @param serverCnt Number of server nodes.
* @param clientCnt Number of client nodes.
* @param perClusterCnt Number of service instances per cluster.
* @param perNodeCnt Number of service instances per node.
*/
private void testServiceMetrics(int serverCnt, int clientCnt, int perClusterCnt, int perNodeCnt) throws Throwable {
List<IgniteEx> servers = startGrids(serverCnt, false);
List<IgniteEx> clients = startGrids(clientCnt, true);
servers.get(0).services().deploy(serviceCfg(MyServiceFactory.create(), perClusterCnt, perNodeCnt));
awaitPartitionMapExchange();
List<MyService> serverStickyProxies = servers.stream()
.map(ignite -> (MyService)ignite.services().serviceProxy(SRVC_NAME, MyService.class, true))
.collect(Collectors.toList());
List<MyService> clientStickyProxies = clients.stream()
.map(ignite -> (MyService)ignite.services().serviceProxy(SRVC_NAME, MyService.class, true))
.collect(Collectors.toList());
long invokeCollector = 0;
// Call service through the server proxies.
for (int i = 0; i < INVOKE_CNT; ++i) {
// Call from server.
IgniteEx ignite = servers.get(i % servers.size());
callService4Times(ignite, serverStickyProxies.get(i % serverStickyProxies.size()));
// Call from client.
ignite = clients.get(i % clients.size());
callService4Times(ignite, clientStickyProxies.get(i % clientStickyProxies.size()));
invokeCollector += 8;
}
long invokesInMetrics = 0;
// Calculate and check invocations within the metrics.
for (IgniteEx ignite : servers) {
ReadOnlyMetricRegistry metrics = findMetricRegistry(ignite.context().metric(), SRVC_NAME);
// Metrics may not be deployed on this server node.
if (metrics == null)
continue;
for (Metric metric : metrics) {
if (metric instanceof HistogramMetric)
invokesInMetrics += sumHistogramEntries((HistogramMetric)metric);
}
}
// Compare calls number and metrics number.
assertEquals("Calculated wrong service invocation number.", invokesInMetrics, invokeCollector);
}
/** Expose ignite-references of the nodes as list. */
private List<IgniteEx> startGrids(int cnt, boolean client) throws Exception {
List<IgniteEx> grids = new ArrayList<>(cnt);
for (int i = 0; i < cnt; ++i)
grids.add(client ? startClientGrid(gridNum++) : startGrid(gridNum++));
return grids;
}
/**
* Executes 2 calls for {@link MyService} though unsticky proxy and 2 calls to {@code extraSrvc}. Total 4 are
* suposed to present in the metrics.
*
* @param ignite Server or client node.
* @param extraSrvc Extra service instance or proxy to call.
*/
private static void callService4Times(IgniteEx ignite, MyService extraSrvc) {
MyService srvc = ignite.services().serviceProxy(SRVC_NAME, MyService.class, false);
srvc.hello();
srvc.hello(1);
extraSrvc.hello();
extraSrvc.hello(2);
}
/** Provides test service configuration. */
private static ServiceConfiguration serviceCfg(Service srvc, int perClusterCnt, int perNodeCnt) {
ServiceConfiguration svcCfg = new ServiceConfiguration();
svcCfg.setName(SRVC_NAME);
svcCfg.setService(srvc);
svcCfg.setMaxPerNodeCount(perNodeCnt);
svcCfg.setTotalCount(perClusterCnt);
svcCfg.setStatisticsEnabled(true);
return svcCfg;
}
/** @return Number of metrics contained in metric registry of the test service. */
private static int metricsCnt(IgniteEx ignite) {
return Iterables.size(ignite.context().metric().registry(serviceMetricRegistryName(SRVC_NAME)));
}
/**
* Count total of histogram values.
*
* @param histogram Histogram to traverse.
* @return Sum of all entries of {@code histogram} buckets.
*/
public static long sumHistogramEntries(HistogramMetric histogram) {
if (histogram == null)
return 0;
long sum = 0;
for (int i = 0; i < histogram.value().length; ++i)
sum += histogram.value()[i];
return sum;
}
/** @return Metric registry if it is found in {@code metricMgr} by name {@code srvcName}. Null otherwise. */
private static ReadOnlyMetricRegistry findMetricRegistry(GridMetricManager metricMgr, String srvcName) {
for (ReadOnlyMetricRegistry registry : metricMgr) {
if (registry.name().equals(serviceMetricRegistryName(srvcName)))
return registry;
}
return null;
}
}