/* * Copyright (c) 2016 Couchbase, Inc. * * 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.couchbase.client.core.metrics; import com.couchbase.client.core.event.CouchbaseEvent; import com.couchbase.client.core.event.EventBus; import com.couchbase.client.core.event.metrics.LatencyMetricsEvent; import com.couchbase.client.core.logging.CouchbaseLogger; import com.couchbase.client.core.logging.CouchbaseLoggerFactory; import org.LatencyUtils.LatencyStats; import org.LatencyUtils.PauseDetector; import org.LatencyUtils.SimplePauseDetector; import rx.Scheduler; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; /** * The default abstract implementation for a latency metrics collector. * * @author Michael Nitschinger * @since 1.2.0 */ public abstract class AbstractLatencyMetricsCollector<I extends LatencyMetricsIdentifier, E extends LatencyMetricsEvent> extends AbstractMetricsCollector implements LatencyMetricsCollector<I> { private static final CouchbaseLogger LOGGER = CouchbaseLoggerFactory.getInstance(AbstractLatencyMetricsCollector.class); private final static Object PAUSE_DETECTOR_LOCK = new Object(); private static int pauseDetectorCount = 0; private static PauseDetector staticPauseDetector; private static PauseDetector acquirePauseDetector() { synchronized (PAUSE_DETECTOR_LOCK) { if (pauseDetectorCount++ == 0) { staticPauseDetector = new SimplePauseDetector( TimeUnit.MILLISECONDS.toNanos(10), TimeUnit.MILLISECONDS.toNanos(10), 3 ); } return staticPauseDetector; } } private static void releasePauseDetector() { synchronized (PAUSE_DETECTOR_LOCK) { if (--pauseDetectorCount == 0) { staticPauseDetector.shutdown(); staticPauseDetector = null; // help GC } } } private final PauseDetector pauseDetector; private final AtomicBoolean pauseDetectorHeld; private final Map<I, LatencyStats> latencyMetrics; private final LatencyMetricsCollectorConfig config; protected AbstractLatencyMetricsCollector(EventBus eventBus, Scheduler scheduler, LatencyMetricsCollectorConfig config) { super(eventBus, scheduler, config); this.config = config; latencyMetrics = new ConcurrentHashMap<I, LatencyStats>(); pauseDetector = acquirePauseDetector(); pauseDetectorHeld = new AtomicBoolean(true); } protected abstract E generateLatencyMetricsEvent(Map<I, LatencyStats> latencyMetrics); @Override protected CouchbaseEvent generateCouchbaseEvent() { return generateLatencyMetricsEvent(new HashMap<I, LatencyStats>(latencyMetrics)); } @Override public void record(I identifier, long latency) { if (config.emitFrequency() <= 0) { return; } LatencyStats metric = latencyMetrics.get(identifier); if (metric == null) { metric = LatencyStats.Builder.create().pauseDetector(pauseDetector).build(); latencyMetrics.put(identifier, metric); } metric.recordLatency(latency); } @Override public boolean shutdown() { if (pauseDetectorHeld.compareAndSet(true, false)) { releasePauseDetector(); } return super.shutdown(); } @Override public LatencyMetricsCollectorConfig config() { return config; } /** * Helper method to remove an item out of the stored metrics. */ protected void remove(I identifier) { LatencyStats removed = latencyMetrics.remove(identifier); if (removed != null) { try { removed.stop(); } catch (Exception ex) { LOGGER.warn("Caught exception while removing LatencyStats, moving on.", ex); } } } }