/** * Copyright 2014 Netflix, Inc. * <p/> * 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 * <p/> * http://www.apache.org/licenses/LICENSE-2.0 * <p/> * 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.netflix.servo.monitor; import com.netflix.servo.util.Clock; import com.netflix.servo.util.ClockWithOffset; import com.netflix.servo.util.UnmodifiableList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; /** * A Monitor for tracking a longer operation that might last for many minutes or hours. For tracking * frequent calls that last less than a polling interval (usually one minute) please use a * {@link com.netflix.servo.monitor.BasicTimer} instead. * <p/> * This monitor will create two gauges: * <ul> * <li>A duration: will report the current duration in seconds. * (defined as the sum of the time of all active tasks.)</li> * <li>Number of active tasks.</li> * </ul> * <p/> * The names for the monitors will be the base name passed to the constructor plus a * suffix of .duration and .activeTasks respectively. */ public class DurationTimer extends AbstractMonitor<Long> implements CompositeMonitor<Long> { private final List<Monitor<?>> monitors; private final AtomicLong nextTaskId = new AtomicLong(0L); private final ConcurrentMap<Long, Long> tasks = new ConcurrentHashMap<>(); private final Clock clock; private static MonitorConfig subId(MonitorConfig config, String sub) { String newName = config.getName() + "." + sub; return MonitorConfig.builder(newName).withTags(config.getTags()) .withPublishingPolicy(config.getPublishingPolicy()) .build(); } /** * Create a new DurationTimer using the provided configuration. */ public DurationTimer(MonitorConfig config) { this(config, ClockWithOffset.INSTANCE); } /** * Create a new DurationTimer using a specific configuration and clock. This is useful for * unit tests that need to manipulate the clock. */ public DurationTimer(MonitorConfig config, final Clock clock) { super(config); this.clock = clock; Monitor<?> duration = new BasicGauge<>(subId(config, "duration"), () -> getDurationMillis() / 1000L); Monitor<?> activeTasks = new BasicGauge<>(subId(config, "activeTasks"), new Callable<Long>() { @Override public Long call() throws Exception { return (long) tasks.size(); } }); monitors = UnmodifiableList.of(duration, activeTasks); } private long getDurationMillis() { long now = clock.now(); long sum = 0L; for (long startTime : tasks.values()) { sum += now - startTime; } return Math.max(sum, 0L); } /** * {@inheritDoc} */ @Override public Long getValue() { return getValue(0); } @Override public Long getValue(int pollerIndex) { return getDurationMillis() / 1000; } /** * {@inheritDoc} */ @Override public List<Monitor<?>> getMonitors() { return monitors; } /** * {@inheritDoc} */ @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } DurationTimer that = (DurationTimer) o; return getConfig().equals(that.getConfig()) && nextTaskId.get() == that.nextTaskId.get() && tasks.equals(that.tasks); } /** * {@inheritDoc} */ @Override public int hashCode() { int result = getConfig().hashCode(); long id = nextTaskId.get(); result = 31 * result + (int) (id ^ (id >>> 32)); result = 31 * result + tasks.hashCode(); return result; } /** * {@inheritDoc} */ @Override public String toString() { return "DurationTimer{config=" + getConfig() + ", tasks=" + tasks + ", monitors=" + monitors + ", nextTaskId=" + nextTaskId.get() + '}'; } /** * Returns a stopwatch that has been started and will automatically * record its result to this timer when stopped. Every time this method is called * the number of active tasks for the timer will be incremented. * The number will be decremented when the stopwatch is stopped. */ public Stopwatch start() { Stopwatch s = new DurationStopwatch(); s.start(); return s; } private class DurationStopwatch implements Stopwatch { private long id = -1L; @Override public void start() { this.id = nextTaskId.getAndIncrement(); tasks.put(id, clock.now()); } @Override public void stop() { if (id >= 0) { tasks.remove(id); id = -1L; } } @Override public void reset() { if (id >= 0) { tasks.put(id, clock.now()); } } @Override public long getDuration(TimeUnit timeUnit) { long durationMs = 0L; if (id >= 0) { long start = tasks.get(id); durationMs = clock.now() - start; } durationMs = Math.max(0L, durationMs); return timeUnit.convert(durationMs, TimeUnit.MILLISECONDS); } @Override public long getDuration() { return getDuration(TimeUnit.SECONDS); } } }