/*
* 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.policy.autoscaling;
import java.util.List;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.collections.TimeWindowedList;
import org.apache.brooklyn.util.collections.TimestampedValue;
import org.apache.brooklyn.util.time.Duration;
import com.google.common.base.Objects;
/**
* Using a {@link TimeWindowedList}, tracks the recent history of values to allow a summary of
* those values to be obtained.
*
* @author aled
*/
public class SizeHistory {
public static class WindowSummary {
/** The most recent value (or -1 if there has been no value) */
public final long latest;
/** The minimum vaule within the given time period */
public final long min;
/** The maximum vaule within the given time period */
public final long max;
/** true if, since that max value, there have not been any higher values */
public final boolean stableForGrowth;
/** true if, since that low value, there have not been any lower values */
public final boolean stableForShrinking;
public WindowSummary(long latest, long min, long max, boolean stableForGrowth, boolean stableForShrinking) {
this.latest = latest;
this.min = min;
this.max = max;
this.stableForGrowth = stableForGrowth;
this.stableForShrinking = stableForShrinking;
}
@Override
public String toString() {
return Objects.toStringHelper(this).add("latest", latest).add("min", min).add("max", max)
.add("stableForGrowth", stableForGrowth).add("stableForShrinking", stableForShrinking).toString();
}
}
private final TimeWindowedList<Number> recentDesiredResizes;
public SizeHistory(long windowSize) {
recentDesiredResizes = new TimeWindowedList<Number>(MutableMap.of("timePeriod", windowSize, "minExpiredVals", 1));
}
public void add(final int val) {
recentDesiredResizes.add(val);
}
public void setWindowSize(Duration newWindowSize) {
recentDesiredResizes.setTimePeriod(newWindowSize);
}
/**
* Summarises the history of values in this time window, with a few special things:
* <ul>
* <li>If entire time-window is not covered by the given values, then min is Integer.MIN_VALUE and max is Integer.MAX_VALUE
* <li>If no values, then latest is -1
* <li>If no recent values, then keeps last-seen value (no matter how old), to use that
* <li>"stable for growth" means that since that max value, there have not been any higher values
* <li>"stable for shrinking" means that since that low value, there have not been any lower values
* </ul>
*/
public WindowSummary summarizeWindow(Duration windowSize) {
long now = System.currentTimeMillis();
List<TimestampedValue<Number>> windowVals = recentDesiredResizes.getValuesInWindow(now, windowSize);
Number latestObj = latestInWindow(windowVals);
long latest = (latestObj == null) ? -1: latestObj.longValue();
long max = maxInWindow(windowVals, windowSize).longValue();
long min = minInWindow(windowVals, windowSize).longValue();
// TODO Could do more sophisticated "stable" check; this is the easiest code - correct but not most efficient
// in terms of the caller having to schedule additional stability checks.
boolean stable = (min == max);
return new WindowSummary(latest, min, max, stable, stable);
}
/**
* If the entire time-window is not covered by the given values, then returns Integer.MAX_VALUE.
*/
private <T extends Number> T maxInWindow(List<TimestampedValue<T>> vals, Duration timeWindow) {
// TODO bad casting from Integer default result to T
long now = System.currentTimeMillis();
long epoch = now - timeWindow.toMilliseconds();
T result = null;
double resultAsDouble = Integer.MAX_VALUE;
for (TimestampedValue<T> val : vals) {
T valAsNum = val.getValue();
double valAsDouble = (valAsNum != null) ? valAsNum.doubleValue() : 0;
if (result == null && val.getTimestamp() > epoch) {
result = withDefault(null, Integer.MAX_VALUE);
resultAsDouble = result.doubleValue();
}
if (result == null || (valAsNum != null && valAsDouble > resultAsDouble)) {
result = valAsNum;
resultAsDouble = valAsDouble;
}
}
return withDefault(result, Integer.MAX_VALUE);
}
/**
* If the entire time-window is not covered by the given values, then returns Integer.MIN_VALUE
*/
private <T extends Number> T minInWindow(List<TimestampedValue<T>> vals, Duration timeWindow) {
long now = System.currentTimeMillis();
long epoch = now - timeWindow.toMilliseconds();
T result = null;
double resultAsDouble = Integer.MIN_VALUE;
for (TimestampedValue<T> val : vals) {
T valAsNum = val.getValue();
double valAsDouble = (valAsNum != null) ? valAsNum.doubleValue() : 0;
if (result == null && val.getTimestamp() > epoch) {
result = withDefault(null, Integer.MIN_VALUE);
resultAsDouble = result.doubleValue();
}
if (result == null || (val.getValue() != null && valAsDouble < resultAsDouble)) {
result = valAsNum;
resultAsDouble = valAsDouble;
}
}
return withDefault(result, Integer.MIN_VALUE);
}
@SuppressWarnings("unchecked")
private <T> T withDefault(T result, Integer defaultValue) {
return result!=null ? result : (T) defaultValue;
}
/**
* @return null if empty, or the most recent value
*/
private <T extends Number> T latestInWindow(List<TimestampedValue<T>> vals) {
return vals.isEmpty() ? null : vals.get(vals.size()-1).getValue();
}
}