/* * 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.brooklyn.util.collections; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.apache.brooklyn.util.time.Duration; import com.google.common.collect.ImmutableList; /** * Keeps a list of timestamped values that are in the given time-period (millis). * It also guarantees to keep the given minimum number of values in the list (even if old), * and to keep the given number of out-of-date values. * * For example, this is useful if we want to determine if a metric has been consistently high. * * @author aled */ public class TimeWindowedList<T> { private final LinkedList<TimestampedValue<T>> values = new LinkedList<TimestampedValue<T>>(); private volatile Duration timePeriod; private final int minVals; private final int minExpiredVals; public TimeWindowedList(Duration timePeriod) { this.timePeriod = timePeriod; minVals = 0; minExpiredVals = 0; } /** * @deprecated since 0.7.0; use {@link #TimeWindowedList(Duration)} */ public TimeWindowedList(long timePeriod) { this(Duration.millis(timePeriod)); } public TimeWindowedList(Map<String,?> flags) { if (!flags.containsKey("timePeriod")) throw new IllegalArgumentException("Must define timePeriod"); timePeriod = Duration.of(flags.get("timePeriod")); if (flags.containsKey("minVals")) { minVals = ((Number)flags.get("minVals")).intValue(); } else { minVals = 0; } if (flags.containsKey("minExpiredVals")) { minExpiredVals = ((Number)flags.get("minExpiredVals")).intValue(); } else { minExpiredVals = 0; } } public void setTimePeriod(Duration newTimePeriod) { timePeriod = newTimePeriod; } public synchronized T getLatestValue() { return (values.isEmpty()) ? null : values.get(values.size()-1).getValue(); } public List<TimestampedValue<T>> getValues() { return getValues(System.currentTimeMillis()); } public synchronized List<TimestampedValue<T>> getValues(long now) { pruneValues(now); return ImmutableList.copyOf(values); } public synchronized List<TimestampedValue<T>> getValuesInWindow(long now, Duration subTimePeriod) { long startTime = now - subTimePeriod.toMilliseconds(); List<TimestampedValue<T>> result = new LinkedList<TimestampedValue<T>>(); TimestampedValue<T> mostRecentExpired = null; for (TimestampedValue<T> val : values) { if (val.getTimestamp() < startTime) { // discard; but remember most recent too-old value so we include that as the "initial" mostRecentExpired = val; } else { result.add(val); } } if (minExpiredVals > 0 && mostRecentExpired != null) { result.add(0, mostRecentExpired); } if (result.size() < minVals) { int minIndex = Math.max(0, values.size()-minVals); return ImmutableList.copyOf(values.subList(minIndex, values.size())); } else { return result; } } public void add(T val) { add(val, System.currentTimeMillis()); } public synchronized void add(T val, long timestamp) { values.add(values.size(), new TimestampedValue<T>(val, timestamp)); pruneValues(timestamp); } public synchronized void pruneValues(long now) { long startTime = now - timePeriod.toMilliseconds(); int expiredValsCount = 0; if (timePeriod.equals(Duration.ZERO)) { expiredValsCount = values.size(); } else { for (TimestampedValue<T> val : values) { if (val.getTimestamp() < startTime) { expiredValsCount++; } else { break; } } } int numToPrune = Math.min(expiredValsCount - minExpiredVals, values.size()-minVals); for (int i = 0; i < numToPrune; i++) { values.removeFirst(); } } @Override public String toString() { return "timePeriod="+timePeriod+", vals="+values; } }