/*
* 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.internal.metrics.impl;
import com.hazelcast.internal.metrics.DiscardableMetricsProvider;
import com.hazelcast.internal.metrics.DoubleGauge;
import com.hazelcast.internal.metrics.DoubleProbeFunction;
import com.hazelcast.internal.metrics.LongProbeFunction;
import com.hazelcast.internal.metrics.MetricsProvider;
import com.hazelcast.internal.metrics.MetricsRegistry;
import com.hazelcast.internal.metrics.ProbeFunction;
import com.hazelcast.internal.metrics.ProbeLevel;
import com.hazelcast.internal.metrics.renderers.ProbeRenderer;
import com.hazelcast.internal.util.concurrent.ThreadFactoryImpl;
import com.hazelcast.logging.ILogger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import static com.hazelcast.util.Preconditions.checkNotNull;
import static java.lang.String.format;
/**
* The {@link MetricsRegistry} implementation.
*/
public class MetricsRegistryImpl implements MetricsRegistry {
private static final Comparator<ProbeInstance> COMPARATOR = new Comparator<ProbeInstance>() {
@Override
public int compare(ProbeInstance o1, ProbeInstance o2) {
return o1.name.compareTo(o2.name);
}
};
final ILogger logger;
final ProbeLevel minimumLevel;
private final ScheduledExecutorService scheduledExecutorService
= new ScheduledThreadPoolExecutor(2, new ThreadFactoryImpl("MetricsRegistry-thread-"));
private final ConcurrentMap<String, ProbeInstance> probeInstances = new ConcurrentHashMap<String, ProbeInstance>();
private final ConcurrentMap<Class<?>, SourceMetadata> metadataMap
= new ConcurrentHashMap<Class<?>, SourceMetadata>();
private final LockStripe lockStripe = new LockStripe();
private final AtomicReference<SortedProbeInstances> sortedProbeInstancesRef
= new AtomicReference<SortedProbeInstances>(new SortedProbeInstances(0));
/**
* Creates a MetricsRegistryImpl instance.
*
* @param logger the ILogger used
* @param minimumLevel the minimum ProbeLevel. If a probe is registered with a ProbeLevel lower than the minimum ProbeLevel,
* then the registration is skipped.
* @throws NullPointerException if logger or minimumLevel is null
*/
public MetricsRegistryImpl(ILogger logger, ProbeLevel minimumLevel) {
this.logger = checkNotNull(logger, "logger can't be null");
this.minimumLevel = checkNotNull(minimumLevel, "minimumLevel can't be null");
if (logger.isFinestEnabled()) {
logger.finest("MetricsRegistry minimumLevel:" + minimumLevel);
}
}
@Override
public ProbeLevel minimumLevel() {
return minimumLevel;
}
long modCount() {
return sortedProbeInstancesRef.get().mod;
}
@Override
public Set<String> getNames() {
Set<String> names = new HashSet<String>(probeInstances.keySet());
return Collections.unmodifiableSet(names);
}
/**
* Loads the {@link SourceMetadata}.
*
* @param clazz the Class to be analyzed.
* @return the loaded SourceMetadata.
*/
private SourceMetadata loadSourceMetadata(Class<?> clazz) {
SourceMetadata metadata = metadataMap.get(clazz);
if (metadata == null) {
metadata = new SourceMetadata(clazz);
SourceMetadata found = metadataMap.putIfAbsent(clazz, metadata);
metadata = found == null ? metadata : found;
}
return metadata;
}
@Override
public <S> void scanAndRegister(S source, String namePrefix) {
checkNotNull(source, "source can't be null");
checkNotNull(namePrefix, "namePrefix can't be null");
SourceMetadata metadata = loadSourceMetadata(source.getClass());
metadata.register(this, source, namePrefix);
}
@Override
public <S> void register(S source, String name, ProbeLevel level, LongProbeFunction<S> function) {
checkNotNull(source, "source can't be null");
checkNotNull(name, "name can't be null");
checkNotNull(function, "function can't be null");
checkNotNull(level, "level can't be null");
registerInternal(source, name, level, function);
}
@Override
public <S> void register(S source, String name, ProbeLevel level, DoubleProbeFunction<S> function) {
checkNotNull(source, "source can't be null");
checkNotNull(name, "name can't be null");
checkNotNull(function, "function can't be null");
checkNotNull(level, "level can't be null");
registerInternal(source, name, level, function);
}
ProbeInstance getProbeInstance(String name) {
checkNotNull(name, "name can't be null");
return probeInstances.get(name);
}
<S> void registerInternal(S source, String name, ProbeLevel probeLevel, ProbeFunction function) {
if (!probeLevel.isEnabled(minimumLevel)) {
return;
}
synchronized (lockStripe.getLock(source)) {
ProbeInstance probeInstance = probeInstances.get(name);
if (probeInstance == null) {
probeInstance = new ProbeInstance<S>(name, source, function);
probeInstances.put(name, probeInstance);
} else {
logOverwrite(probeInstance);
}
if (logger.isFinestEnabled()) {
logger.finest("Registered probeInstance " + name);
}
probeInstance.source = source;
probeInstance.function = function;
}
incrementMod();
}
private void incrementMod() {
for (; ; ) {
SortedProbeInstances current = sortedProbeInstancesRef.get();
SortedProbeInstances update = new SortedProbeInstances(current.mod + 1);
if (sortedProbeInstancesRef.compareAndSet(current, update)) {
break;
}
}
}
private void logOverwrite(ProbeInstance probeInstance) {
if (probeInstance.function != null || probeInstance.source != null) {
logger.warning(format("Overwriting existing probe '%s'", probeInstance.name));
}
}
@Override
public LongGaugeImpl newLongGauge(String name) {
checkNotNull(name, "name can't be null");
return new LongGaugeImpl(this, name);
}
@Override
public DoubleGauge newDoubleGauge(String name) {
checkNotNull(name, "name can't be null");
return new DoubleGaugeImpl(this, name);
}
@Override
public <S> void deregister(S source) {
if (source == null) {
return;
}
boolean changed = false;
for (Map.Entry<String, ProbeInstance> entry : probeInstances.entrySet()) {
ProbeInstance probeInstance = entry.getValue();
if (probeInstance.source != source) {
continue;
}
String name = entry.getKey();
boolean destroyed = false;
synchronized (lockStripe.getLock(source)) {
if (probeInstance.source == source) {
changed = true;
probeInstances.remove(name);
probeInstance.source = null;
probeInstance.function = null;
destroyed = true;
}
}
if (destroyed && logger.isFinestEnabled()) {
logger.finest("Destroying probeInstance " + name);
}
}
if (changed) {
incrementMod();
}
}
@Override
public void render(ProbeRenderer renderer) {
checkNotNull(renderer, "renderer can't be null");
for (ProbeInstance probeInstance : getSortedProbeInstances()) {
render(renderer, probeInstance);
}
}
@Override
public void collectMetrics(Object... objects) {
for (Object object : objects) {
if (object instanceof MetricsProvider) {
((MetricsProvider) object).provideMetrics(this);
}
}
}
@Override
public void discardMetrics(Object... objects) {
for (Object object : objects) {
if (object instanceof DiscardableMetricsProvider) {
((DiscardableMetricsProvider) object).discardMetrics(this);
}
}
}
List<ProbeInstance> getSortedProbeInstances() {
for (; ; ) {
SortedProbeInstances current = sortedProbeInstancesRef.get();
if (current.probeInstances != null) {
return current.probeInstances;
}
List<ProbeInstance> probeInstanceList = new ArrayList<ProbeInstance>(probeInstances.values());
Collections.sort(probeInstanceList, COMPARATOR);
SortedProbeInstances update = new SortedProbeInstances(current.mod, probeInstanceList);
if (sortedProbeInstancesRef.compareAndSet(current, update)) {
return update.probeInstances;
}
}
}
private void render(ProbeRenderer renderer, ProbeInstance probeInstance) {
ProbeFunction function = probeInstance.function;
Object source = probeInstance.source;
String name = probeInstance.name;
if (function == null || source == null) {
renderer.renderNoValue(name);
return;
}
try {
if (function instanceof LongProbeFunction) {
LongProbeFunction longFunction = (LongProbeFunction) function;
renderer.renderLong(name, longFunction.get(source));
} else {
DoubleProbeFunction doubleFunction = (DoubleProbeFunction) function;
renderer.renderDouble(name, doubleFunction.get(source));
}
} catch (Exception e) {
renderer.renderException(name, e);
}
}
@Override
public void scheduleAtFixedRate(final Runnable publisher, long period, TimeUnit timeUnit) {
scheduledExecutorService.scheduleAtFixedRate(publisher, 0, period, timeUnit);
}
public void shutdown() {
scheduledExecutorService.shutdown();
}
private static class SortedProbeInstances {
private final long mod;
private final List<ProbeInstance> probeInstances;
private SortedProbeInstances(long mod) {
this.mod = mod;
this.probeInstances = null;
}
public SortedProbeInstances(long mod, List<ProbeInstance> probeInstances) {
this.mod = mod;
this.probeInstances = probeInstances;
}
}
}