/*
* 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.enricher;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.brooklyn.api.catalog.Catalog;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.sensor.AttributeSensor;
import org.apache.brooklyn.api.sensor.Sensor;
import org.apache.brooklyn.api.sensor.SensorEvent;
import org.apache.brooklyn.enricher.stock.AbstractTypeTransformingEnricher;
import org.apache.brooklyn.util.core.flags.SetFromFlag;
import org.apache.brooklyn.util.javalang.JavaClassNames;
import org.apache.brooklyn.util.time.Duration;
/**
* Converts an absolute measure of time into a fraction of time, based on the delta between consecutive values
* and the elapsed time between those values.
*
* For example, if the values are for ProcessCpuTime (i.e. microseconds of CPU time for the given process), then
* this would convert it into a fraction (i.e. 100th of the percentage): (newVal-prevVal)/(newTimestamp/prevTimestamp).
*
* It also configured with the time units for the values.
*/
//@Catalog(name="Time-fraction Delta", description="Converts an absolute measure of time into a fraction of time, "
// + "based on the delta between consecutive values and the elapsed time between those values.")
public class TimeFractionDeltaEnricher<T extends Number> extends AbstractTypeTransformingEnricher<T,Double> {
private static final Logger LOG = LoggerFactory.getLogger(TimeFractionDeltaEnricher.class);
@SetFromFlag
private long nanosPerOrigUnit;
protected Number lastValue;
protected long lastTimestamp = -1;
public TimeFractionDeltaEnricher() { // for rebinding
}
public TimeFractionDeltaEnricher(Entity producer, Sensor<T> source, Sensor<Double> target, TimeUnit origUnits) {
this(producer, source, target, origUnits.toNanos(1));
}
public TimeFractionDeltaEnricher(Entity producer, Sensor<T> source, Sensor<Double> target, long nanosPerOrigUnit) {
super(producer, source, target);
this.nanosPerOrigUnit = nanosPerOrigUnit;
if (source!=null && target!=null)
this.uniqueTag = JavaClassNames.simpleClassName(getClass())+":"+source.getName()+"*"+Duration.nanos(nanosPerOrigUnit)+"->"+target.getName();
}
@Override
public void onEvent(SensorEvent<T> event) {
onEvent(event, event.getTimestamp());
}
public void onEvent(SensorEvent<T> event, long eventTimestamp) {
Number current = event.getValue();
if (current == null) {
// Can't compute a delta;
// don't assume current=zero because then things like requestCount->requestsPerSecond is negative!
// instead don't publish anything
if (LOG.isTraceEnabled()) LOG.trace("ignoring null value in {}, at {}", new Object[] {this, eventTimestamp});
return;
}
if (eventTimestamp > lastTimestamp) {
if (lastValue == null) {
// cannot calculate delta with a single value
if (LOG.isTraceEnabled()) LOG.trace("{} received event but no last value so will not emit, null -> {} at {}",
new Object[] {this, current, eventTimestamp});
} else if (lastTimestamp < 0) {
LOG.warn("{} has lastValue {} but last timestamp {}; new value is {} at {}; not publishing",
new Object[] {this, lastValue, lastTimestamp, current, eventTimestamp});
} else {
long duration = eventTimestamp - lastTimestamp;
double fraction = toNanos(current.doubleValue() - lastValue.doubleValue(), nanosPerOrigUnit) / TimeUnit.MILLISECONDS.toNanos(duration);
entity.sensors().set((AttributeSensor<Double>)target, fraction);
if (LOG.isTraceEnabled()) LOG.trace("set {} to {}, {} -> {} at {} (previous at {})",
new Object[] {this, fraction, lastValue, current, eventTimestamp, lastTimestamp});
}
lastValue = current;
lastTimestamp = eventTimestamp;
}
}
private double toNanos(double val, long nanosPerOrigUnit) {
return val*nanosPerOrigUnit;
}
}