com.hazelcast.internal.diagnostics.Diagnostics Maven / Gradle / Ivy
/*
* Copyright (c) 2008-2020, 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.internal.diagnostics;
import com.hazelcast.logging.ILogger;
import com.hazelcast.spi.properties.HazelcastProperties;
import com.hazelcast.spi.properties.HazelcastProperty;
import java.io.File;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicReference;
import static com.hazelcast.internal.diagnostics.DiagnosticsPlugin.DISABLED;
import static com.hazelcast.internal.util.Preconditions.checkNotNull;
import static com.hazelcast.internal.util.ThreadUtil.createThreadName;
import static java.lang.String.format;
import static java.lang.System.arraycopy;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
/**
* The {@link Diagnostics} is a debugging tool that provides insight in all kinds
* of potential performance and stability issues. The actual logic to provide such
* insights, is placed in the {@link DiagnosticsPlugin}.
*/
@SuppressWarnings("WeakerAccess")
public class Diagnostics {
/**
* Use the {@link Diagnostics} to see internal performance metrics and cluster
* related information.
*
* The performance monitor logs all metrics into the log file.
*
* For more detailed information, please check the METRICS_LEVEL.
*
* The default is {@code false}.
*/
public static final HazelcastProperty ENABLED = new HazelcastProperty("hazelcast.diagnostics.enabled", false);
/**
* The {@link DiagnosticsLogFile} uses a rolling file approach to prevent
* eating too much disk space.
*
* This property sets the maximum size in MB for a single file.
*
* Every HazelcastInstance will get its own history of log files.
*
* The default is 50.
*/
@SuppressWarnings("checkstyle:magicnumber")
public static final HazelcastProperty MAX_ROLLED_FILE_SIZE_MB
= new HazelcastProperty("hazelcast.diagnostics.max.rolled.file.size.mb", 50);
/**
* The {@link DiagnosticsLogFile} uses a rolling file approach to prevent
* eating too much disk space.
*
* This property sets the maximum number of rolling files to keep on disk.
*
* The default is 10.
*/
@SuppressWarnings("checkstyle:magicnumber")
public static final HazelcastProperty MAX_ROLLED_FILE_COUNT
= new HazelcastProperty("hazelcast.diagnostics.max.rolled.file.count", 10);
/**
* Configures if the epoch time should be included in the 'top' section.
* This makes it easy to determine the time in epoch format and prevents
* needing to parse the date-format section. The default is {@code false}
* since it will cause more noise.
*/
public static final HazelcastProperty INCLUDE_EPOCH_TIME = new HazelcastProperty("hazelcast.diagnostics.include.epoch", true);
/**
* Configures the output directory of the performance log files.
*
* Defaults to the 'user.dir'.
*/
public static final HazelcastProperty DIRECTORY
= new HazelcastProperty("hazelcast.diagnostics.directory", "" + System.getProperty("user.dir"));
/**
* Configures the prefix for the diagnostics file.
*
* So instead of having e.g. 'diagnostics-...log' you get 'foobar-diagnostics-...log'.
*/
public static final HazelcastProperty FILENAME_PREFIX
= new HazelcastProperty("hazelcast.diagnostics.filename.prefix");
final AtomicReference staticTasks = new AtomicReference<>(
new DiagnosticsPlugin[0]
);
final String baseFileName;
final ILogger logger;
final String hzName;
final HazelcastProperties properties;
final boolean includeEpochTime;
final File directory;
DiagnosticsLogFile diagnosticsLogFile;
private final ConcurrentMap, DiagnosticsPlugin> pluginsMap = new ConcurrentHashMap<>();
private final boolean enabled;
private ScheduledExecutorService scheduler;
public Diagnostics(String baseFileName, ILogger logger, String hzName, HazelcastProperties properties) {
String optionalPrefix = properties.getString(FILENAME_PREFIX);
this.baseFileName = optionalPrefix == null ? baseFileName : optionalPrefix + "-" + baseFileName;
this.logger = logger;
this.hzName = hzName;
this.properties = properties;
this.includeEpochTime = properties.getBoolean(INCLUDE_EPOCH_TIME);
this.directory = new File(properties.getString(DIRECTORY));
this.enabled = properties.getBoolean(ENABLED);
}
// just for testing (returns the current file the system is writing to)
public File currentFile() {
return diagnosticsLogFile.file;
}
/**
* Gets the plugin for a given plugin class. This method should be used if
* the plugin instance is required within some data-structure outside of the
* Diagnostics.
*
* @param pluginClass the class of the DiagnosticsPlugin
* @param type of the plugin
* @return the DiagnosticsPlugin found, or {@code null} if not active
*/
@SuppressWarnings("unchecked")
public
P getPlugin(Class
pluginClass) {
return (P) pluginsMap.get(pluginClass);
}
/**
* Registers a {@link DiagnosticsPlugin}.
*
* This method is thread-safe.
*
* There is no checking for duplicate registration.
*
* If the {@link Diagnostics} is disabled, the call is ignored.
*
* @param plugin the plugin to register
* @throws NullPointerException if plugin is {@code null}
*/
public void register(DiagnosticsPlugin plugin) {
checkNotNull(plugin, "plugin can't be null");
if (!enabled) {
return;
}
long periodMillis = plugin.getPeriodMillis();
if (periodMillis < -1) {
throw new IllegalArgumentException(plugin + " can't return a periodMillis smaller than -1");
}
logger.finest(plugin.getClass().toString() + " is " + (periodMillis == DISABLED ? "disabled" : "enabled"));
if (periodMillis == DISABLED) {
return;
}
pluginsMap.put(plugin.getClass(), plugin);
plugin.onStart();
if (periodMillis > 0) {
// it is a periodic task
scheduler.scheduleAtFixedRate(new WritePluginTask(plugin), 0, periodMillis, MILLISECONDS);
} else {
addStaticPlugin(plugin);
}
}
private void addStaticPlugin(DiagnosticsPlugin plugin) {
for (; ; ) {
DiagnosticsPlugin[] oldPlugins = staticTasks.get();
DiagnosticsPlugin[] newPlugins = new DiagnosticsPlugin[oldPlugins.length + 1];
arraycopy(oldPlugins, 0, newPlugins, 0, oldPlugins.length);
newPlugins[oldPlugins.length] = plugin;
if (staticTasks.compareAndSet(oldPlugins, newPlugins)) {
break;
}
}
}
public void start() {
if (!enabled) {
logger.info(format("Diagnostics disabled. To enable add -D%s=true to the JVM arguments.", ENABLED.getName()));
return;
}
this.diagnosticsLogFile = new DiagnosticsLogFile(this);
this.scheduler = new ScheduledThreadPoolExecutor(1, new DiagnosticSchedulerThreadFactory());
logger.info("Diagnostics started");
}
public void shutdown() {
if (!enabled) {
return;
}
if (scheduler != null) {
scheduler.shutdownNow();
}
}
private class WritePluginTask implements Runnable {
private final DiagnosticsPlugin plugin;
WritePluginTask(DiagnosticsPlugin plugin) {
this.plugin = plugin;
}
@Override
public void run() {
try {
diagnosticsLogFile.write(plugin);
} catch (Throwable t) {
// we need to catch any exception; otherwise the task is going to be removed by the scheduler
logger.severe(t);
}
}
}
private class DiagnosticSchedulerThreadFactory implements ThreadFactory {
@Override
public Thread newThread(Runnable target) {
return new Thread(target, createThreadName(hzName, "DiagnosticsSchedulerThread"));
}
}
}