blob: 424db01572f5e1c78dce0c8784c0dcdcc8b7c031 [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.jackrabbit.oak.plugins.metric;
import java.io.Closeable;
import java.util.Hashtable;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import com.codahale.metrics.JmxReporter;
import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.ObjectNameFactory;
import com.codahale.metrics.RatioGauge;
import com.codahale.metrics.Timer;
import com.google.common.collect.Maps;
import org.apache.jackrabbit.api.stats.RepositoryStatistics;
import org.apache.jackrabbit.api.stats.RepositoryStatistics.Type;
import org.apache.jackrabbit.oak.commons.jmx.JmxUtil;
import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUtils;
import org.apache.jackrabbit.oak.stats.Clock;
import org.apache.jackrabbit.oak.stats.CounterStats;
import org.apache.jackrabbit.oak.stats.HistogramStats;
import org.apache.jackrabbit.oak.stats.MeterStats;
import org.apache.jackrabbit.oak.stats.SimpleStats;
import org.apache.jackrabbit.oak.stats.StatisticsProvider;
import org.apache.jackrabbit.oak.stats.Stats;
import org.apache.jackrabbit.oak.stats.StatsOptions;
import org.apache.jackrabbit.oak.stats.TimerStats;
import org.apache.jackrabbit.stats.RepositoryStatisticsImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MetricStatisticsProvider implements StatisticsProvider, Closeable {
private static final Logger log = LoggerFactory.getLogger(MetricStatisticsProvider.class);
private static final String JMX_TYPE_METRICS = "Metrics";
private final ConcurrentMap<String, Stats> statsRegistry = Maps.newConcurrentMap();
private final MetricRegistry registry;
private final JmxReporter reporter;
private final RepositoryStatisticsImpl repoStats;
private final Clock.Fast clock;
private final com.codahale.metrics.Clock metricsClock;
public MetricStatisticsProvider(MBeanServer server, ScheduledExecutorService executor) {
clock = new Clock.Fast(executor);
metricsClock = new OakMetricClock(clock);
registry = new MetricRegistry();
repoStats = new RepositoryStatisticsImpl(executor);
reporter = JmxReporter.forRegistry(registry)
.inDomain(WhiteboardUtils.JMX_OAK_DOMAIN)
.registerWith(server)
.createsObjectNamesWith(new OakNameFactory())
.build();
reporter.start();
registerAverages();
}
static String typeToName(Type type) {
return type.name();
}
public void close() {
clock.close();
if (reporter != null) {
reporter.close();
}
}
@Override
public RepositoryStatistics getStats() {
return repoStats;
}
@Override
public MeterStats getMeter(String name, StatsOptions options) {
return getStats(name, StatsBuilder.METERS, options);
}
@Override
public CounterStats getCounterStats(String name, StatsOptions options) {
return getStats(name, StatsBuilder.COUNTERS, options);
}
@Override
public TimerStats getTimer(String name, StatsOptions options) {
return getStats(name, StatsBuilder.TIMERS, options);
}
@Override
public HistogramStats getHistogram(String name, StatsOptions options) {
return getStats(name, StatsBuilder.HISTOGRAMS, options);
}
public MetricRegistry getRegistry() {
return registry;
}
RepositoryStatisticsImpl getRepoStats() {
return repoStats;
}
private <T extends Stats> T getStats(String name, StatsBuilder<T> builder, StatsOptions options) {
Stats stats = statsRegistry.get(name);
//Use double locking pattern. The map should get populated with required set
//during startup phase so for later calls should not hit the synchronized block
if (stats == null) {
synchronized (this){
stats = statsRegistry.get(name);
//If still null then go ahead and create it within lock
if (stats == null){
if (options.isOnlyMetricEnabled()) {
stats = builder.newMetric(this, name);
} else if (options.isOnlyTimeSeriesEnabled()) {
stats = new SimpleStats(getTimerSeriesStats(name, builder), builder.getType());
} else {
stats = builder.newComposite(getTimerSeriesStats(name, builder), this, name);
}
statsRegistry.put(name, stats);
}
}
}
if (builder.isInstance(stats)) {
//noinspection unchecked
return (T) stats;
}
throw new IllegalStateException();
}
private AtomicLong getTimerSeriesStats(String name, StatsBuilder builder){
AtomicLong counter;
Type enumType = Type.getType(name);
if (enumType != null) {
counter = repoStats.getCounter(enumType);
} else {
boolean resetValueEachSecond = builder != StatsBuilder.COUNTERS;
counter = repoStats.getCounter(name, resetValueEachSecond);
}
return counter;
}
private void registerAverages() {
registry.register(typeToName(Type.OBSERVATION_EVENT_AVERAGE),
new AvgGauge(compStats(Type.OBSERVATION_EVENT_COUNTER, StatsBuilder.METERS).getMeter(),
compStats(Type.OBSERVATION_EVENT_DURATION, StatsBuilder.TIMERS).getTimer()));
}
private CompositeStats compStats(Type type, StatsBuilder builder){
Stats stats = getStats(typeToName(type), builder, StatsOptions.DEFAULT);
return (CompositeStats) stats;
}
@SuppressWarnings("unused")
private interface StatsBuilder<T extends Stats> {
StatsBuilder<CounterStats> COUNTERS = new StatsBuilder<CounterStats>() {
@Override
public CompositeStats newComposite(AtomicLong delegate, MetricStatisticsProvider provider, String name) {
return new CompositeStats(delegate, provider.registry.counter(name));
}
@Override
public Stats newMetric(MetricStatisticsProvider provider, String name) {
return new CounterImpl(provider.registry.counter(name));
}
@Override
public boolean isInstance(Stats metric) {
return CounterStats.class.isInstance(metric);
}
@Override
public SimpleStats.Type getType() {
return SimpleStats.Type.COUNTER;
}
};
StatsBuilder<MeterStats> METERS = new StatsBuilder<MeterStats>() {
@Override
public CompositeStats newComposite(AtomicLong delegate, MetricStatisticsProvider provider, String name) {
return new CompositeStats(delegate, getMeter(provider, name));
}
@Override
public Stats newMetric(MetricStatisticsProvider provider, String name) {
return new MeterImpl(getMeter(provider, name));
}
@Override
public boolean isInstance(Stats metric) {
return MeterStats.class.isInstance(metric);
}
@Override
public SimpleStats.Type getType() {
return SimpleStats.Type.METER;
}
private Meter getMeter(MetricStatisticsProvider provider, String name) {
Meter meter = new Meter(provider.metricsClock);
provider.registry.register(name, meter);
return meter;
}
};
StatsBuilder<TimerStats> TIMERS = new StatsBuilder<TimerStats>() {
@Override
public CompositeStats newComposite(AtomicLong delegate, MetricStatisticsProvider provider, String name) {
return new CompositeStats(delegate, provider.registry.timer(name));
}
@Override
public Stats newMetric(MetricStatisticsProvider provider, String name) {
return new TimerImpl(provider.registry.timer(name));
}
@Override
public boolean isInstance(Stats metric) {
return TimerStats.class.isInstance(metric);
}
@Override
public SimpleStats.Type getType() {
return SimpleStats.Type.TIMER;
}
};
StatsBuilder<HistogramStats> HISTOGRAMS = new StatsBuilder<HistogramStats>() {
@Override
public CompositeStats newComposite(AtomicLong delegate, MetricStatisticsProvider provider, String name) {
return new CompositeStats(delegate, provider.registry.histogram(name));
}
@Override
public Stats newMetric(MetricStatisticsProvider provider, String name) {
return new HistogramImpl(provider.registry.histogram(name));
}
@Override
public boolean isInstance(Stats metric) {
return HistogramStats.class.isInstance(metric);
}
@Override
public SimpleStats.Type getType() {
return SimpleStats.Type.HISTOGRAM;
}
};
CompositeStats newComposite(AtomicLong delegate, MetricStatisticsProvider provider, String name);
Stats newMetric(MetricStatisticsProvider provider,String name);
boolean isInstance(Stats stats);
SimpleStats.Type getType();
}
private static class AvgGauge extends RatioGauge {
private final Meter meter;
private final Timer timer;
private AvgGauge(Meter meter, Timer timer) {
this.meter = meter;
this.timer = timer;
}
@Override
protected Ratio getRatio() {
//TODO Should we use getMeanRate
return Ratio.of(meter.getFifteenMinuteRate(),
timer.getFifteenMinuteRate());
}
}
private static class OakNameFactory implements ObjectNameFactory {
@Override
public ObjectName createName(String type, String domain, String name) {
Hashtable<String, String> table = new Hashtable<String, String>();
table.put("type", JMX_TYPE_METRICS);
table.put("name", JmxUtil.quoteValueIfRequired(name));
try {
return new ObjectName(domain, table);
} catch (MalformedObjectNameException e) {
log.warn("Unable to register {} {}", type, name, e);
throw new RuntimeException(e);
}
}
}
private static class OakMetricClock extends com.codahale.metrics.Clock {
private final Clock clock;
public OakMetricClock(Clock clock) {
this.clock = clock;
}
@Override
public long getTick() {
return TimeUnit.NANOSECONDS.convert(clock.getTime(), TimeUnit.MILLISECONDS);
}
}
}