com.netflix.servo.monitor.DurationTimer Maven / Gradle / Ivy
/**
* Copyright 2014 Netflix, 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.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.
*
* This monitor will create two gauges:
*
* - A duration: will report the current duration in seconds.
* (defined as the sum of the time of all active tasks.)
* - Number of active tasks.
*
*
* 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 implements CompositeMonitor {
private final List> monitors;
private final AtomicLong nextTaskId = new AtomicLong(0L);
private final ConcurrentMap 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() {
@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> 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);
}
}
}