/*
* Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved.
*
* Licensed 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 com.hazelcast.spi.impl.operationexecutor.slowoperationdetector;
import com.eclipsesource.json.JsonArray;
import com.eclipsesource.json.JsonObject;
import com.hazelcast.config.Config;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IMap;
import com.hazelcast.internal.management.TimedMemberStateFactory;
import com.hazelcast.map.EntryBackupProcessor;
import com.hazelcast.map.EntryProcessor;
import com.hazelcast.spi.Operation;
import com.hazelcast.spi.impl.operationservice.InternalOperationService;
import com.hazelcast.spi.impl.operationservice.impl.OperationServiceImpl;
import com.hazelcast.spi.properties.GroupProperty;
import com.hazelcast.test.AssertTask;
import com.hazelcast.test.HazelcastTestSupport;
import com.hazelcast.util.EmptyStatement;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static com.hazelcast.instance.TestUtil.getHazelcastInstanceImpl;
import static java.lang.String.format;
import static java.lang.String.valueOf;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
abstract class SlowOperationDetectorAbstractTest extends HazelcastTestSupport {
private static final String DEFAULT_KEY = "key";
private static final String DEFAULT_VALUE = "value";
private List<SlowEntryProcessor> entryProcessors = new ArrayList<SlowEntryProcessor>();
HazelcastInstance getSingleNodeCluster(int slowOperationThresholdMillis) {
Config config = new Config();
config.setProperty(GroupProperty.SLOW_OPERATION_DETECTOR_THRESHOLD_MILLIS.getName(),
valueOf(slowOperationThresholdMillis));
return createHazelcastInstance(config);
}
static IMap<String, String> getMapWithSingleElement(HazelcastInstance instance) {
IMap<String, String> map = instance.getMap(randomMapName());
map.put(DEFAULT_KEY, DEFAULT_VALUE);
return map;
}
static void executeOperation(HazelcastInstance instance, Operation operation) {
getOperationService(instance).execute(operation);
}
static void executeEntryProcessor(IMap<String, String> map, EntryProcessor<String, String> entryProcessor) {
map.executeOnKey(DEFAULT_KEY, entryProcessor);
}
static void shutdownOperationService(HazelcastInstance instance) {
if (instance == null) {
return;
}
OperationServiceImpl operationService = (OperationServiceImpl) getOperationService(instance);
operationService.shutdownInvocations();
operationService.shutdownOperationExecutor();
}
static Collection<SlowOperationLog.Invocation> getInvocations(SlowOperationLog log) {
Map<Integer, SlowOperationLog.Invocation> invocationMap = getFieldFromObject(log, "invocations");
return invocationMap.values();
}
static int getDefaultPartitionId(HazelcastInstance instance) {
return instance.getPartitionService().getPartition(DEFAULT_KEY).getPartitionId();
}
static JsonArray getSlowOperationLogsJsonArray(HazelcastInstance instance) {
return getOperationStats(instance).get("slowOperations").asArray();
}
static JsonObject getOperationStats(HazelcastInstance instance) {
TimedMemberStateFactory timedMemberStateFactory = new TimedMemberStateFactory(getHazelcastInstanceImpl(instance));
return timedMemberStateFactory.createTimedMemberState().getMemberState().getOperationStats().toJson();
}
static Collection<SlowOperationLog> getSlowOperationLogsAndAssertNumberOfSlowOperationLogs(final HazelcastInstance instance,
final int expected) {
assertTrueEventually(new AssertTask() {
@Override
public void run() throws Exception {
Collection<SlowOperationLog> logs = getSlowOperationLogs(instance);
assertNumberOfSlowOperationLogs(logs, expected);
}
});
return getSlowOperationLogs(instance);
}
static Collection<SlowOperationLog> getSlowOperationLogs(HazelcastInstance instance) {
InternalOperationService operationService = getOperationService(instance);
SlowOperationDetector slowOperationDetector = getFieldFromObject(operationService, "slowOperationDetector");
Map<Integer, SlowOperationLog> slowOperationLogs = getFieldFromObject(slowOperationDetector, "slowOperationLogs");
return slowOperationLogs.values();
}
static void assertNumberOfSlowOperationLogs(Collection<SlowOperationLog> logs, int expected) {
assertEqualsStringFormat("Expected %d slow operation logs, but was %d.", expected, logs.size());
}
static void assertTotalInvocations(SlowOperationLog log, int totalInvocations) {
assertEqualsStringFormat("Expected %d total invocations, but was %d. Log: " + log.createDTO().toJson(),
totalInvocations, log.totalInvocations.get());
}
static void assertEntryProcessorOperation(SlowOperationLog log) {
String operation = log.operation;
assertEqualsStringFormat("Expected operation %s, but was %s",
"com.hazelcast.map.impl.operation.PartitionWideEntryWithPredicateOperation", operation);
}
static void assertOperationContainsClassName(SlowOperationLog log, String className) {
String operation = log.operation;
assertTrue(format("Expected operation to contain '%s'%n%s", className, operation), operation.contains("$" + className));
}
static void assertStackTraceContainsClassName(SlowOperationLog log, String className) {
String stackTrace = log.stackTrace;
assertTrue(format("Expected stacktrace to contain className '%s'%n%s", className, stackTrace),
stackTrace.contains("$" + className + "."));
}
static void assertStackTraceNotContainsClassName(SlowOperationLog log, String className) {
String stackTrace = log.stackTrace;
assertFalse(format("Expected stacktrace to not contain className '%s'%n%s", className, stackTrace),
stackTrace.contains(className));
}
static void assertJSONContainsClassName(JsonObject jsonObject, String className) {
String stackTrace = jsonObject.get("stackTrace").toString();
assertTrue(format("JSON for Management Center should contain stackTrace with class name '%s'%n%s", className, stackTrace),
stackTrace.contains("$" + className + "."));
}
static void assertJSONContainsClassNameJustOnce(JsonObject jsonObject1, JsonObject jsonObject2, String className) {
boolean firstClassFound = jsonObject1.get("stackTrace").toString().contains("$" + className + ".");
boolean secondClassFound = jsonObject2.get("stackTrace").toString().contains("$" + className + ".");
assertTrue(format("JSON for Management Center should contain stackTrace with class name '%s' exactly once", className),
firstClassFound ^ secondClassFound);
}
static void assertInvocationDurationBetween(SlowOperationLog.Invocation invocation, int min, int max) {
Integer duration = invocation.createDTO(0).durationMs;
assertTrue(format("Duration of invocation should be >= %d, but was %d", min, duration), duration >= min);
assertTrue(format("Duration of invocation should be <= %d, but was %d", max, duration), duration <= max);
}
SlowEntryProcessor getSlowEntryProcessor(int sleepSeconds) {
SlowEntryProcessor entryProcessor = new SlowEntryProcessor(sleepSeconds);
entryProcessors.add(entryProcessor);
return entryProcessor;
}
void awaitSlowEntryProcessors() {
for (SlowEntryProcessor slowEntryProcessor : entryProcessors) {
slowEntryProcessor.await();
}
}
@SuppressWarnings("unchecked")
private static <E> E getFieldFromObject(Object object, String fieldName) {
try {
Field field = object.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
return (E) field.get(object);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
static class SlowEntryProcessor extends CountDownLatchHolder implements EntryProcessor<String, String> {
static int GLOBAL_INSTANCE_COUNTER;
final int instance = ++GLOBAL_INSTANCE_COUNTER;
final int sleepSeconds;
SlowEntryProcessor(int sleepSeconds) {
this.sleepSeconds = sleepSeconds;
}
@Override
public Object process(Map.Entry<String, String> entry) {
sleepSeconds(sleepSeconds);
done();
return null;
}
@Override
public EntryBackupProcessor<String, String> getBackupProcessor() {
return null;
}
@Override
public String toString() {
return "SlowEntryProcessor{"
+ "instance=" + instance
+ ", sleepSeconds=" + sleepSeconds
+ '}';
}
}
static class SlowEntryProcessorChild extends SlowEntryProcessor {
SlowEntryProcessorChild(int sleepSeconds) {
super(sleepSeconds);
}
@Override
public Object process(Map.Entry<String, String> entry) {
// not using sleepSeconds() here to have some variants in the stack traces
try {
TimeUnit.SECONDS.sleep(sleepSeconds);
} catch (InterruptedException ignored) {
}
done();
return null;
}
@Override
public String toString() {
return "SlowEntryProcessorChild{"
+ "instance=" + instance
+ ", sleepSeconds=" + sleepSeconds
+ '}';
}
}
static abstract class JoinableOperation extends Operation {
private final CountDownLatch completedLatch = new CountDownLatch(1);
void done() {
completedLatch.countDown();
}
void join() {
try {
completedLatch.await();
} catch (InterruptedException e) {
EmptyStatement.ignore(e);
}
}
}
static abstract class CountDownLatchHolder {
private final CountDownLatch latch = new CountDownLatch(1);
void done() {
latch.countDown();
}
void await() {
try {
latch.await();
} catch (InterruptedException e) {
EmptyStatement.ignore(e);
}
}
}
}