
com.onloupe.core.metrics.Metric Maven / Gradle / Ivy
package com.onloupe.core.metrics;
import java.time.Duration;
import java.time.OffsetDateTime;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import com.onloupe.core.serialization.monitor.IDisplayable;
import com.onloupe.core.serialization.monitor.MetricPacket;
import com.onloupe.core.serialization.monitor.MetricSamplePacket;
import com.onloupe.core.util.TypeUtils;
import com.onloupe.model.SampleType;
import com.onloupe.model.metric.MetricSampleInterval;
/**
* A single metric that has been captured. A metric is a single measured value
* over time.
*
*
* To display the data captured for this metric, use Calculate Values to
* translate the raw captured data into displayable information.
*
*/
public abstract class Metric implements IDisplayable {
/** The metric definition. */
private MetricDefinition metricDefinition;
/** The packet. */
private MetricPacket packet;
// these variables are not persisted but just used to manage our own state.
/** The sample sequence. */
// used when adding sampled metrics to ensure order.
private AtomicLong sampleSequence = new AtomicLong();
/**
* Instantiates a new metric.
*/
public Metric() {
// TODO Auto-generated constructor stub
}
/**
* Create a new metric with the provided metric definition and metric packet.
*
* Most derived classes will provide a more convenient implementation that will
* automatically create the correct metric packet instead of the caller having
* to first create it. The new metric will automatically be added to the metric
* definition's metrics collection.
*
* @param definition The definition for this metric
* @param packet The metric packet to use for this metric
*/
public Metric(MetricDefinition definition, MetricPacket packet) {
// verify and store off our input
if (definition == null) {
throw new NullPointerException("definition");
}
if (packet == null) {
throw new NullPointerException("packet");
}
// one last safety check: The definition and the packet better agree.
if (!definition.getId().equals(packet.getDefinitionId())) {
throw new IndexOutOfBoundsException(
"The provided metric packet has a different definition Id than the provide metric definition.");
}
// and now that we know everything isn't null, go ahead and store things off
this.metricDefinition = definition;
this.packet = packet;
// finally, add ourself to the metric definition's metrics collection
this.metricDefinition.getMetrics().add(this);
}
/**
* The unique Id of this metric instance. This can reliably be used as a key to
* refer to this item.
*
* The key can be used to compare the same metric across different instances
* (e.g. sessions). This Id is always unique to a particular instance.
*
* @return the id
*/
public final UUID getId() {
return this.packet.getID();
}
/**
* The fully qualified name of the metric being captured.
*
* The name is for comparing the same metric in different sessions. They will
* have the same name but not the same Id.
*
* @return the name
*/
public final String getName() {
return this.packet.getName();
}
/**
* A short caption of what the metric tracks, suitable for end-user display.
*
* @return the caption
*/
@Override
public final String getCaption() {
return this.packet.getCaption();
}
/**
* A description of what is tracked by this metric, suitable for end-user
* display.
*
* @return the description
*/
@Override
public final String getDescription() {
return this.packet.getDescription();
}
/**
* The definition of this metric object.
*
* @return the definition
*/
public MetricDefinition getDefinition() {
return this.metricDefinition;
}
/**
* The internal metric type of this metric definition.
*
* @return the metric type name
*/
public final String getMetricTypeName() {
return getDefinition().getMetricTypeName();
}
/**
* The category of this metric for display purposes. Category is the top
* displayed hierarchy.
*
* @return the category name
*/
public final String getCategoryName() {
return getDefinition().getCategoryName();
}
/**
* Gets or sets an instance name for this performance counter.
*
* @return the instance name
*/
public final String getInstanceName() {
return this.packet.getInstanceName();
}
/**
* Indicates whether this is the default metric instance for this metric
* definition or not.
*
* The default instance has a null instance name. This property is provided as a
* convenience to simplify client code so you don't have to distinguish empty
* strings or null.
*
* @return true, if is default
*/
public final boolean isDefault() {
return (TypeUtils.isBlank(this.packet.getInstanceName()));
}
/**
* The sample type of the metric. Indicates whether the metric represents
* discrete events or a continuous value.
*
* @return the sample type
*/
public final SampleType getSampleType() {
return getDefinition().getSampleType();
}
/**
* Gets the counter name.
*
* @return the counter name
*/
public String getCounterName() {
return this.metricDefinition.getCounterName();
}
/**
* Compare this Metric to another Metric to determine sort order
*
* Metric instances are sorted by their Name property.
*
* @param other The Metric object to compare this Metric object against
* @return An int which is less than zero, equal to zero, or greater than zero
* to reflect whether this Metric should sort as being less-than, equal
* to, or greater-than the other Metric, respectively.
*/
public final int compareTo(Metric other) {
// quick identity comparison based on guid
if (other.getId().equals(this.packet.getID())) {
return 0;
}
// Now we try to sort by name. We already guard against uniqueness
int compareResult = this.packet.getName().compareToIgnoreCase(other.getName());
return compareResult;
}
/**
* Determines if the provided Metric object is identical to this object.
*
* @param other The Metric object to compare this object to
* @return True if the objects represent the same data.
*/
public final boolean equals(Metric other) {
if (other == this) {
return true; // ReferenceEquals means we're the same object, definitely equal.
}
// Careful, it could be null; check it without recursion
if (other == null) {
return false; // Since we're a live object we can't be equal to a null instance.
}
// they are the same if their Guid's match.
return (getId().equals(other.getId()));
}
/**
* Determines if the provided object is identical to this object.
*
* @param obj The object to compare this object to
* @return True if the other object is also a Metric and represents the same
* data.
*/
@Override
public boolean equals(Object obj) {
Metric otherMetric = obj instanceof Metric ? (Metric) obj : null;
return equals(otherMetric); // Just have type-specific Equals do the check (it even handles null)
}
/**
* Provides a representative hash code for objects of this type to spread out
* distribution in hash tables.
*
* Objects which consider themselves to be Equal (a.Equals(b) returns true) are
* expected to have the same hash code. Objects which are not Equal may have the
* same hash code, but minimizing such overlaps helps with efficient operation
* of hash tables.
*
* @return An int representing the hash code calculated for the contents of this
* object.
*
*/
@Override
public int hashCode() {
int myHash = getId().hashCode(); // The ID is all that Equals checks!
return myHash;
}
/**
* Calculates the offset date from the provided baseline for the specified
* interval
*
*
* To calculate a backwards offset (the date that is the specified interval
* before the baseline) use a negative number of intervals. For example, -1
* intervals will give you one interval before the baseline.
*
* @param baseline The date and time to calculate an offset date and time from
* @param interval The interval to add or subtract from the baseline
* @param intervals The number of intervals to go forward or (if negative)
* backwards
* @return the offset date time
*/
public final OffsetDateTime calculateOffset(OffsetDateTime baseline, MetricSampleInterval interval, int intervals) {
OffsetDateTime returnVal; // just so we're initialized with SOMETHING.
int intervalCount = intervals;
// since they aren't using shortest, we are going to use the intervals input
// option which better not be zero or negative.
if ((intervals == 0) && (interval != MetricSampleInterval.SHORTEST)) {
throw new IndexOutOfBoundsException(
"The number of intervals can't be zero if the interval isn't set to Shortest.");
}
switch (interval) {
case DEFAULT: // use how the data was recorded
if (getDefinition().getInterval() != MetricSampleInterval.DEFAULT) {
returnVal = calculateOffset(baseline, getDefinition().getInterval(), intervalCount);
} else {
// default and ours is default - use second.
returnVal = calculateOffset(baseline, MetricSampleInterval.SECOND, intervalCount);
}
break;
case SHORTEST:
// explicitly use the shortest value available, 16 milliseconds
returnVal = baseline.plusNanos(TimeUnit.MILLISECONDS.toNanos(16)); // interval is ignored in the case of the
// "shortest" configuration
break;
case MILLISECOND:
returnVal = baseline.plusNanos(TimeUnit.MILLISECONDS.toNanos(intervalCount));
break;
case SECOND:
returnVal = baseline.plusSeconds(intervalCount);
break;
case MINUTE:
returnVal = baseline.plusMinutes(intervalCount);
break;
case HOUR:
returnVal = baseline.plusHours(intervalCount);
break;
case DAY:
returnVal = baseline.plusDays(intervalCount);
break;
case WEEK:
returnVal = baseline.plusWeeks(intervalCount);
break;
case MONTH:
returnVal = baseline.plusMonths(intervalCount);
break;
default:
throw new IndexOutOfBoundsException("interval");
}
return returnVal;
}
/**
* Calculates the amount we will "pull forward" a future sample by to fit it to
* our requested interval.
*
* Tolerance allows for us to ignore small variations in exact timestamps for
* the purposes of fitting the best data.
*
* @param interval the interval
* @return the duration
*/
public static Duration calculateOffsetTolerance(MetricSampleInterval interval) {
Duration returnVal;
switch (interval) {
case DEFAULT:
case SHORTEST:
case MILLISECOND:
// same as millisecond; we will use 1 clock tick
returnVal = Duration.ofMillis(1);
break;
case SECOND:
// 10 milliseconds
returnVal = Duration.ofMillis(10);
break;
case MINUTE:
// 2 seconds
returnVal = Duration.ofSeconds(2);
break;
case HOUR:
// 1 minute
returnVal = Duration.ofMinutes(1);
break;
case DAY:
// 30 minutes
returnVal = Duration.ofMinutes(30);
break;
case WEEK:
// 12 hours
returnVal = Duration.ofHours(12);
break;
case MONTH:
// two days
returnVal = Duration.ofDays(2);
break;
default:
throw new IndexOutOfBoundsException("interval");
}
return returnVal;
}
/**
* The underlying packet.
*
* @return the packet
*/
public MetricPacket getPacket() {
return this.packet;
}
/**
* A unique, increasing sequence number each time it's called.
*
* This method is thread-safe.
*
* @return the sample sequence
*/
public final long getSampleSequence() {
return this.sampleSequence.incrementAndGet();
}
/**
* Invoked when deserializing a metric sample to allow inheritors to provide
* derived implementations
*
* If you wish to provide a derived class for metric samples in your derived
* metric, use this method to create and return your derived object to support
* the deserialization process. This is used during object construction, so
* implementations should treat it as a static method.
*
* @param packet The metric sample packet being deserialized
* @return The metric sample-compatible object.
*/
public abstract MetricSample onMetricSampleRead(MetricSamplePacket packet);
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy