com.onloupe.agent.metrics.SampledMetricDefinition Maven / Gradle / Ivy
Show all versions of core Show documentation
package com.onloupe.agent.metrics;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import com.onloupe.agent.metrics.annotation.SampledMetricClass;
import com.onloupe.agent.metrics.annotation.SampledMetricDivisor;
import com.onloupe.agent.metrics.annotation.SampledMetricDivisors;
import com.onloupe.agent.metrics.annotation.SampledMetricInstanceName;
import com.onloupe.agent.metrics.annotation.SampledMetricValue;
import com.onloupe.agent.metrics.annotation.SampledMetricValues;
import com.onloupe.core.NameValuePair;
import com.onloupe.core.logging.Log;
import com.onloupe.core.metrics.IMetricDefinition;
import com.onloupe.core.metrics.MetricDefinitionCollection;
import com.onloupe.core.metrics.MetricSampleType;
import com.onloupe.core.serialization.monitor.CustomSampledMetricDefinitionPacket;
import com.onloupe.core.serialization.monitor.CustomSampledMetricPacket;
import com.onloupe.core.util.OutObject;
import com.onloupe.core.util.TypeUtils;
import com.onloupe.model.SampleType;
import com.onloupe.model.metric.MemberType;
/**
* The definition of a user-defined sampled metric
*
* Custom Sampled Metrics are the simplest form of Sampled Metrics, not
* requiring the developer to derive their own classes to encapsulate a sampled
* metric. Review if this class can serve your needs before you create your own
* custom set of classes derived from SampledMetric (or derive from this class)
*/
public final class SampledMetricDefinition implements IMetricDefinition {
/** The lock. */
private final Object lock = new Object();
/** The packet. */
private CustomSampledMetricDefinitionPacket packet;
/** The requires multiple samples. */
private boolean requiresMultipleSamples;
/** The bound. */
private boolean bound;
/** The bound type. */
private Class boundType;
/** The name bound. */
private boolean nameBound;
/** The name member name. */
private String nameMemberName;
/** The name member type. */
private MemberType nameMemberType;
/** The metrics. */
private Map metrics = new HashMap();
/** The Constant supportedDataTypes. */
private static final java.lang.Class[] supportedDataTypes = new Class[] { Double.class, Float.class,
BigDecimal.class, Long.class, Integer.class, Short.class, LocalDateTime.class, OffsetDateTime.class,
Duration.class, int.class, long.class, short.class, float.class, double.class };
/** The Constant definitions. */
private static final MetricDefinitionCollection definitions = Log.getMetricDefinitions();
/** The Constant dataTypeSupported. */
private static final Map dataTypeSupported = initTypeSupportedDictionary();
/** The Constant inheritanceMap. */
// Array of all inherited types (that have attributes), by type.
private static final Map inheritanceMap = new HashMap();
/** The Constant definitionsMap. */
// LOCKED List of definitions by specific bound type.
private static final Map> definitionsMap = new HashMap>();
/** The Constant dictionaryLock. */
private static final Object dictionaryLock = new Object(); // Lock for the DefinitionMap dictionary.
/**
* Inits the type supported dictionary.
*
* @return the hash map
*/
private static HashMap initTypeSupportedDictionary() {
// We need to initialize our type-supported dictionary up front....
HashMap dataTypeSupported = new HashMap(
supportedDataTypes.length);
for (java.lang.Class type : supportedDataTypes) {
dataTypeSupported.put(type, true);
}
return dataTypeSupported;
}
/**
* Create a new metric definition for the active log.
*
* At any one time there should only be one metric definition with a given
* combination of metric type, category, and counter name. These values together
* are used to correlate metrics between sessions. The metric definition will
* automatically be added to the provided collection.
*
* @param metricsSystem The metrics capture system label.
* @param categoryName The name of the category with which this definition is
* associated.
* @param counterName The name of the definition within the category.
* @param samplingType The type of data captured for each metric under this
* definition.
* @param unitCaption A displayable caption for the units this metric's values
* represent, or null for unit-less values.
* @param metricCaption A displayable caption for this sampled metric counter.
* @param description A description of what is tracked by this metric,
* suitable for end-user display.
*/
private SampledMetricDefinition(String metricsSystem, String categoryName, String counterName,
SamplingType samplingType, String unitCaption, String metricCaption, String description) {
this.packet = new CustomSampledMetricDefinitionPacket(metricsSystem, categoryName, counterName,
MetricSampleType.forValue(samplingType.getValue()), unitCaption, description);
this.packet.setCaption(metricCaption);
this.requiresMultipleSamples = sampledMetricTypeRequiresMultipleSamples(samplingType);
definitions.add(this);
setReadOnly();
}
/**
* Find or create a sampled metric definition from the specified parameters.
*
* @param metricsSystem The metrics capture system label.
* @param categoryName The name of the category with which this definition is
* associated.
* @param counterName The name of the definition within the category.
* @param samplingType The sampling type of the sampled metric counter.
* @param unitCaption A displayable caption for the units this metric's values
* represent, or null for unit-less values.
* @param metricCaption A displayable caption for this sampled metric counter.
* @param description An extended end-user description of this sampled metric
* counter. If a metric definition does not already exist
* for the specified metrics system, category name, and
* counter name, a new sampled metric definition will be
* created from the provided parameters. If one already
* exists then it will be checked for compatibility. A
* sampled metric defined with the same SamplingType will
* be considered compatible, otherwise an ArgumentException
* will be thrown. Each distinct metric definition (all
* sampled metrics and event metrics) must have a distinct
* Key (the metrics system, category, and counter name).
* Multiple metric instances can then be created (each with
* its own instance name) from the same metric definition.
*
* @throws NullPointerException The
* provided metricsSystem, categoryName, or counterName was
* null.
* @throws IllegalArgumentException There is
* already a metric definition for the same key, but it is
* not a sampled metric.<br />-or-<br /> There
* is already a sampled metric definition for the same key
* but it uses an incompatible sampling type.
* @return the sampled metric definition
*/
public static SampledMetricDefinition register(String metricsSystem, String categoryName, String counterName,
SamplingType samplingType, String unitCaption, String metricCaption, String description) {
SampledMetricDefinition officialDefinition;
boolean newCreation;
// We need to lock the collection while we check for an existing definition and
// maybe add a new one to it.
synchronized (Log.getMetricDefinitions().getLock()) {
IMetricDefinition rawDefinition;
OutObject tempOutRawDefinition = new OutObject();
if (!Log.getMetricDefinitions().tryGetValue(metricsSystem, categoryName, counterName,
tempOutRawDefinition)) {
rawDefinition = tempOutRawDefinition.argValue;
// There isn't already one by that Key. Great! Make one from our parameters.
newCreation = true;
officialDefinition = new SampledMetricDefinition(metricsSystem, categoryName, counterName, samplingType,
unitCaption, metricCaption, description);
} else {
rawDefinition = tempOutRawDefinition.argValue;
// Oooh, we found one already registered. We'll want to do some checking on
// this, but outside the lock.
newCreation = false;
officialDefinition = rawDefinition instanceof SampledMetricDefinition
? (SampledMetricDefinition) rawDefinition
: null;
}
} // End of collection lock
if (officialDefinition == null) {
throw new IllegalArgumentException(String.format(Locale.ROOT,
"There is already a metric definition for the same metrics system (%1$s), category name (%2$s), and counter name (%3$s), but it is not a sampled metric.",
metricsSystem, categoryName, counterName));
} else if (!newCreation) {
// There was one other than us, make sure it's compatible with us. Just check
// SamplingType.
if (officialDefinition.getSamplingType() != samplingType) {
throw new IllegalArgumentException(String.format(Locale.ROOT,
"There is already a sampled metric definition for the same metrics system (%1$s), category name (%2$s), and counter name (%3$s), but it is not compatible; "
+ "it defines sampling type as %4$s rather than %5$s.",
metricsSystem, categoryName, counterName, officialDefinition.getSamplingType(), samplingType));
}
// If the SamplingType matches, then we're okay to return the official one.
}
// Otherwise, we just made this one, so we're all good.
return officialDefinition;
}
/**
* Find or create multiple sampled metrics definitions (defined via
* SampledMetric attributes) for the provided object or Type.
*
* The provided Type or the GetType() of the provided object instance will be
* scanned for SampledMetric attributes on itself and any of its interfaces to
* identify a list of sampled metrics defined for instances of that type,
* creating them as necessary by scanning its members for SampledMetricValue
* attributes. Inheritance will be followed into base types, along with all
* interfaces inherited to the top level. This method will not throw exceptions,
* so a null argument will return an empty array, as will an argument which does
* not define any valid sampled metrics. Also see AddOrGet() to find or create
* sampled metrics definitions for a specific Type, without digging into
* inheritance or interfaces of that Type.
*
* @param userInstanceObject A Type or an instance defining sampled metrics by
* attributes on itself and/or its interfaces and/or
* inherited types.
* @return An array of zero or more sampled metric definitions found for the
* provided object or Type.
*/
public static SampledMetricDefinition[] registerAll(Object userInstanceObject) {
ArrayList definitions = new ArrayList();
if (userInstanceObject != null) {
// Either they gave us a Type, or we need to get the type of the object instance
// they gave us.
java.lang.Class userObjectType = ((userInstanceObject instanceof java.lang.Class
? (java.lang.Class) userInstanceObject
: null) != null)
? (userInstanceObject instanceof java.lang.Class ? (java.lang.Class) userInstanceObject
: null)
: userInstanceObject.getClass();
SampledMetricDefinition[] metricDefinitions;
java.lang.Class[] inheritanceArray;
boolean foundIt;
synchronized (inheritanceMap) // Apparently Dictionaries are not internally threadsafe.
{
inheritanceArray = inheritanceMap.get(userObjectType);
}
if (inheritanceArray != null) {
// We've already scanned this type, so use the cached array of types.
for (java.lang.Class inheritedType : inheritanceArray) {
try {
metricDefinitions = registerGroup(inheritedType, null);
} catch (java.lang.Exception e) {
metricDefinitions = null;
}
if (metricDefinitions != null) {
definitions.addAll(Arrays.asList(metricDefinitions)); // Add them to the list if found.
}
}
} else {
// New top-level type, we have to scan its inheritance.
List inheritanceList = new ArrayList(); // List of all the inherited
// types we find with
// attributes on them.
// First, see if the main type they gave us defines a sampled metric group
// (metricsSystem and categoryName).
if (userObjectType.isAnnotationPresent(SampledMetricClass.class)) {
try {
inheritanceList.add(userObjectType); // Add the top level Type to our list of types.
metricDefinitions = registerGroup(userObjectType, null);
} catch (java.lang.Exception e2) {
metricDefinitions = null;
}
if (metricDefinitions != null) {
definitions.addAll(Arrays.asList(metricDefinitions)); // Add them to the list if found.
}
}
// Now check all of its interfaces.
// Now check all of its interfaces.
Set interfaces = new HashSet();
for (java.lang.Class interfc : userObjectType.getInterfaces()) {
interfaces.add(interfc);
TypeUtils.getSuperInterfaces(interfc, interfaces);
}
for (java.lang.Class interfc : interfaces) {
if (interfc.isAnnotationPresent(SampledMetricClass.class)) {
// We found an interface with the right Attribute, get its group of definitions.
try {
inheritanceList.add(interfc); // Add the interface to our list of types.
metricDefinitions = registerGroup(interfc, null);
} catch (java.lang.Exception e3) {
metricDefinitions = null;
}
if (metricDefinitions != null) {
definitions.addAll(Arrays.asList(metricDefinitions)); // Add them to the list if found.
}
}
}
// And finally, drill down it's inheritance... unless it's an interface (which
// will have a null base type).
java.lang.Class baseObjectType = userObjectType.getSuperclass();
// The IsInterface check shouldn't be needed, but just in case, we want to be
// sure we don't cause a duplicate.
while (baseObjectType != null && baseObjectType != Object.class
&& !baseObjectType.isInterface()) {
// See if an ancestor Type defines a group of sampled metrics.
if (baseObjectType.isAnnotationPresent(SampledMetricClass.class)) {
try {
inheritanceList.add(baseObjectType); // Add the inherited base to our list of types.
metricDefinitions = registerGroup(baseObjectType, null);
} catch (java.lang.Exception e4) {
metricDefinitions = null;
}
if (metricDefinitions != null) {
definitions.addAll(Arrays.asList(metricDefinitions)); // Add them to the list if found.
}
}
// No need to check its interfaces, we already got all of them from the top
// level.
baseObjectType = baseObjectType.getSuperclass(); // Get the next deeper Type.
}
// Now, remember the list of attributed types we found in this walk.
synchronized (inheritanceMap) // Apparently Dictionaries are not internally threadsafe.
{
inheritanceMap.put(userObjectType, inheritanceList.toArray(new java.lang.Class[0]));
}
}
}
// And finally, return the full list of definitions we found.
// If they gave us a null, we'll just return an empty array.
return definitions.toArray(new SampledMetricDefinition[0]);
}
/**
* Find or create sampled metric definitions from SampledMetric and
* SampledMetricValue attributes on a specific Type.
*
* The provided type must have a SampledMetric attribute and can have one or
* more fields, properties or zero-argument methods with SampledMetricValue
* attributes defined. This method creates metric definitions but does not
* create specific metric instances, so it does not require a live object. If
* the sampled metric definition already exists, it is just returned and no
* exception is thrown. If the provided type is not suitable to create sampled
* metrics from because it is missing the appropriate attributes or the
* attributes have been miss-defined, an ArgumentException will be thrown.
* Inheritance and interfaces will not be searched, so the provided Type
* must directly define sampled metrics, but valid objects of a type assignable
* to the specified bound Type of this definition can be sampled from
* these specific sampled metric definitions. Also see AddOrGetDefinitions() to
* find and return all definitions in the inheritance chain of a type or object.
*
* @param userObjectType A specific Type with attributes defining a group of
* sampled metrics.
* @return The group of sampled metric definitions (as an array) determined by
* attributes on the given Type.
*/
public static SampledMetricDefinition[] registerType(java.lang.Class userObjectType) {
if (userObjectType == null) {
throw new NullPointerException("A valid Type must be provided.");
}
return registerGroup(userObjectType, ""); // Search single Type only, no inheritance.
}
/**
* Find or create a single sampled metric definition (by counter name) from
* SampledMetric and SampledMetricValue attributes on a specific Type.
*
* The provided type must have a SampledMetric attribute and can have one or
* more fields, properties or zero-argument methods with SampledMetricValue
* attributes defined. This method creates a metric definition but does not
* create a specific metric instance, so it does not require a live object. If
* the sampled metric definition already exists, it is just returned and no
* exception is thrown. If the provided type is not suitable to create an
* sampled metric from because it is missing the appropriate attribute or the
* attribute has been miss-defined, an ArgumentException will be thrown.
* Inheritance and interfaces will not be searched, so the provided Type
* must directly define an sampled metric, but valid objects of a type
* assignable to the specified bound Type of this definition can be
* sampled from this specific sampled metric definition. Also see
* AddOrGetDefinitions() to find and return an array of definitions.
*
* @param userObjectType A specific Type with attributes defining sampled
* metrics including the specified counter name.
* @param counterName The counterName of a specific sampled metric to find or
* create under the SampledMetric attribute on the
* specified Type.
* @return The single sampled metric definition selected by counter name and
* determined by attributes on the given Type.
*/
public static SampledMetricDefinition register(java.lang.Class userObjectType, String counterName) {
if (userObjectType == null) {
throw new NullPointerException("A valid Type must be provided.");
}
if (TypeUtils.isBlank(counterName)) {
throw new NullPointerException(
"A valid counter name must be specified to select a single sampled metric definition, or use the overload without a counterName parameter.");
}
SampledMetricDefinition[] definitions = registerGroup(userObjectType, counterName); // Specific counter.
return (definitions.length > 0) ? definitions[0] : null;
}
/*
* /// /// Find or create multiple sampled metrics definitions
* (defined via SampledMetric attributes) for the provided object or Type. ///
* /// The provided Type or the GetType() of the provided
* object instance will be scanned for SampledMetric /// attributes on itself
* and any of its interfaces to identify a list of sampled metrics defined for
* instances of /// that type, creating them as necessary by scanning its
* members for SampledMetricValue attributes. Inheritance /// will be followed
* into base types, along with all interfaces inherited to the top level. This
* method may throw /// exceptions, so a null argument will return an empty
* array, as will an argument which does not define any /// valid sampled
* metrics.
*/
/**
* Find or create sampled metric definition from SampledMetric and
* SampledMetricValue attributes on a specific Type.
*
* The provided type must have a SampledMetric attribute and can have one or
* more fields, properties or zero-argument methods with SampledMetricValue
* attributes defined. This method creates a metric definition but does not
* create a specific metric instance, so it does not require a live object. If
* the sampled metric definition already exists, it is just returned and no
* exception is thrown. If the provided type is not suitable to create sampled
* metrics from because it is missing the appropriate attributes or the
* attributes have been miss-defined, an ArgumentException will be thrown.
* Inheritance and interfaces will not be searched, so the provided Type
* must directly define an sampled metric, but valid objects of a type
* assignable to the specified bound Type of this definition can be
* sampled from these specific sampled metric definitions.
*
* @param userObjectType A specific Type with attributes defining an sampled
* metric.
* @param counterName The counterName of a specific sampled metric to find or
* create, string.Empty to return the entire group of
* sampled metric definitions unless there are errors, or
* null to swallow errors.
* @return The single sampled metric definition determined by attributes on the
* given Type.
*/
public static SampledMetricDefinition[] registerGroup(java.lang.Class userObjectType, String counterName) {
if (userObjectType == null) {
return new SampledMetricDefinition[0]; // Return an empty array; This is already checked in the public
// wrappers.
}
List definitions = definitionsMap.get(userObjectType);
// Check if we've scanned this specific Type before. We need the lock...
synchronized (dictionaryLock) {
if (definitions == null) {
// We haven't scanned this Type before, start a new list.
definitions = new ArrayList();
// In this internal catch-all, counterName may be empty or null or a specific
// counter name.
// All errors must be swallowed (but logged) if counterName is null.
// Check if it defines a group at this specific level, no inheritance search, no
// interfaces search.
if (!userObjectType.isAnnotationPresent(SampledMetricClass.class)) {
if (counterName == null) {
return definitions.toArray(new SampledMetricDefinition[0]); // Swallow all errors. Return empty
// array.
}
// Sorry, Attribute not found
throw new IllegalArgumentException(
"The specified Type does not have a SampledMetric attribute, so it can't be used to define sampled metrics.");
}
// OK, now waltz off and get the attribute we want.
SampledMetricClass sampledMetricAnnotation = (SampledMetricClass) userObjectType
.getAnnotation(SampledMetricClass.class);
// Verify that the sampled metric attribute that we got is valid
if (sampledMetricAnnotation == null) {
if (counterName == null) {
return definitions.toArray(new SampledMetricDefinition[0]); // Swallow all errors. Return empty
// array.
}
throw new IllegalArgumentException(
"The specified Type does not have a usable SampledMetric attribute, so it can't be used to define sampled metrics.");
}
// make sure the user didn't do any extraordinary funny business
String metricsSystem = sampledMetricAnnotation.namespace();
if (TypeUtils.isBlank(metricsSystem)) {
if (counterName == null) {
return definitions.toArray(new SampledMetricDefinition[0]); // Swallow all errors. Return empty
// array.
}
throw new IllegalArgumentException(
"The specified Type's SampledMetric attribute has an empty metric namespace which is not allowed, so no metrics can be defined under it.");
}
String metricCategoryName = sampledMetricAnnotation.categoryName();
if (TypeUtils.isBlank(metricCategoryName)) {
if (counterName == null) {
return definitions.toArray(new SampledMetricDefinition[0]); // Swallow all errors. Return empty
// array.
}
throw new IllegalArgumentException(
"The specified Type's SampledMetric attribute has an empty metric category name which is not allowed, so no metrics can be defined under it.");
}
// Now reflect all of the field/property/methods in the type so we can inspect
// them for attributes.
List members = new ArrayList();
members.addAll(Arrays.asList(userObjectType.getFields()));
members.addAll(Arrays.asList(userObjectType.getMethods()));
// These will apply to every sampled metric in the logical group on this Type.
NameValuePair instanceNameBinding = null;
// We need to collect the mapping of divisors for later.
Map divisors = new HashMap();
for (AccessibleObject curMember : members) {
MemberType curMemberType;
String curMemberName;
if (curMember instanceof Field) {
curMemberType = MemberType.FIELD;
curMemberName = ((Field)curMember).getName();
} else {
curMemberType = MemberType.METHOD;
curMemberName = ((Method)curMember).getName();
}
// and what can we get from our little friend?
if (curMember.isAnnotationPresent(SampledMetricInstanceName.class)) {
// have we already bound our name?
if (instanceNameBinding != null) {
// yes, so report a duplicate name warning
} else {
// nope, we're good, so remember our binding information
instanceNameBinding = new NameValuePair(curMemberName, curMemberType);
}
}
if (curMember.isAnnotationPresent(SampledMetricDivisor.class)
|| curMember.isAnnotationPresent(SampledMetricDivisors.class)) {
// they have at least one sampled metric divisor attribute, go get all of them
// we get back an array of objects just in case there are any non-CLS compliant
// attributes defined, which there never are.
SampledMetricDivisor[] curMemberValueAttributes = curMember.getAnnotationsByType(SampledMetricDivisor.class);
for (SampledMetricDivisor curDivisorAttribute : curMemberValueAttributes) {
// cast it and test
if (curDivisorAttribute != null) {
IMetricDefinition existingMetricDefinition;
String divisorCounterName = curDivisorAttribute.counterName();
if (TypeUtils.isBlank(divisorCounterName)) {
} else {
OutObject tempOutExistingMetricDefinition = new OutObject();
if (Log.getMetricDefinitions().tryGetValue(metricsSystem, metricCategoryName,
divisorCounterName, tempOutExistingMetricDefinition)) {
existingMetricDefinition = tempOutExistingMetricDefinition.argValue;
SampledMetricDefinition sampledMetricDefinition = existingMetricDefinition instanceof SampledMetricDefinition
? (SampledMetricDefinition) existingMetricDefinition
: null;
if (sampledMetricDefinition == null) {
// Uh-oh, the definition already exists, but it isn't a sampled metric.
// This is only a warning because this attribute just gets ignored by the
// existing metric,
// And they'll get a real error below if there's actually a
// SampledMetricValue attribute for this counter name.
} else if (!sampledMetricDefinition.isBound()
|| sampledMetricDefinition.getBoundType() != userObjectType) {
// Uh-oh, the definition already exists, but it isn't bound to this Type!
} else if (!bindingMatchesMember(sampledMetricDefinition.getDivisorBinding(),
curMember)) {
// Uh-oh, the definition already exists, but it isn't bound to this member!
}
// Otherwise, the existing definition is correctly bound to this divisor
// already. We're good here.
// If it's already bound, we must have scanned this before and this was the
// first matching
// divisor attribute for the counter name, so mark us as the match.
divisors.put(divisorCounterName, curMember); // Remember this member for later.
} else {
existingMetricDefinition = tempOutExistingMetricDefinition.argValue;
if (divisors.containsKey(divisorCounterName)) {
// They already had one with that counter name!
} else {
divisors.put(divisorCounterName, curMember); // Remember this member for
// later.
}
}
}
}
}
}
}
// We had to scan every member first for the instance name so we could bind it
// in every sampled metric.
// Otherwise we would have problems if it wasn't found before the sampled
// metrics. Now go back and scan again.
for (AccessibleObject curMember : members) {
MemberType curMemberType;
String curMemberName;
if (curMember instanceof Field) {
curMemberType = MemberType.FIELD;
curMemberName = ((Field)curMember).getName();
} else {
curMemberType = MemberType.METHOD;
curMemberName = ((Method)curMember).getName();
}
// Look for SampledMetricValue attributes to actually defined sampled metric
// counters.
if (curMember.isAnnotationPresent(SampledMetricValue.class) ||
curMember.isAnnotationPresent(SampledMetricValues.class)) {
// What type of value does this member give? It'll be the same for every value
// attribute on it!
java.lang.Class curType = getTypeOfMember(curMember);
// they have at least one sampled metric value attribute, go get all of them
// we get back an array of objects just in case there are any non-CLS compliant
// attributes defined, which there never are.
SampledMetricValue[] curMemberValueAttributes = curMember.getAnnotationsByType(SampledMetricValue.class);
for (SampledMetricValue curValueAttribute : curMemberValueAttributes) {
// cast it and test
if (curValueAttribute != null) {
// apply defaults (because this is the only place to get the name of the marked
// member)
String metricCounterName = TypeUtils.trim(curValueAttribute.counterName());
// First time we've seen this Type, scan the whole thing even if they only
// wanted one.
SamplingType samplingType = curValueAttribute.samplingType();
// We use a lock because we need to have the check and the add (which happens as
// part of the new metric definition) happen as a single event.
Object metricDefinitionsLock = Log.getMetricDefinitions().getLock();
synchronized (metricDefinitionsLock) {
// System.Threading.Enter(metricDefinitionsLock);
IMetricDefinition rawMetricDefinition;
OutObject tempOutRawMetricDefinition = new OutObject();
if (Log.getMetricDefinitions().tryGetValue(metricsSystem, metricCategoryName,
metricCounterName, tempOutRawMetricDefinition)) {
rawMetricDefinition = tempOutRawMetricDefinition.argValue;
SampledMetricDefinition sampledMetricDefinition = rawMetricDefinition instanceof SampledMetricDefinition
? (SampledMetricDefinition) rawMetricDefinition
: null;
if (sampledMetricDefinition == null) {
// Uh-oh, the definition already exists, but it isn't a sampled metric!
} else if (!sampledMetricDefinition.isBound()
|| sampledMetricDefinition.getBoundType() != userObjectType) {
// Uh-oh, the definition already exists, but it isn't bound to this Type!
} else if (!bindingMatchesMember(sampledMetricDefinition.getDataBinding(),
curMember)) {
// Uh-oh, the definition already exists, but it isn't bound to this member!
} else {
definitions.add(sampledMetricDefinition); // Found one! Add it to our list.
}
} else {
rawMetricDefinition = tempOutRawMetricDefinition.argValue;
if (curType == null) // Warn about an unreadable property.
{
} else if (curType == void.class) // Warn about a void method.
{
} else if (!isValidDataType(curType)) {
} else {
AccessibleObject divisorInfo;
if (divisors.containsKey(metricCounterName)) {
divisorInfo = divisors.get(metricCounterName);
// We found a divisor attribute for this counter name earlier.
// Does this counter actually need one?
if (!requiresDivisor(samplingType)) {
// It doesn't use one. Warn the user.
divisorInfo = null;
}
// Otherwise, we leave divisorInfo valid, so we bind it below.
} else {
divisorInfo = null; // Didn't find any, make sure it's marked invalid.
if (requiresDivisor(samplingType)) {
// Uh-oh! We need a divisor but they didn't specify one.
}
}
// Now that we have the info for a new sampled metric definition
// and passed all the checks, we need to create it.
SampledMetricDefinition newMetricDefinition = new SampledMetricDefinition(
metricsSystem, metricCategoryName, metricCounterName, samplingType,
curValueAttribute.unitCaption(), curValueAttribute.caption(),
curValueAttribute.description());
newMetricDefinition.setBoundType(userObjectType);
newMetricDefinition.setIsBound(true);
newMetricDefinition.setDataBinding(curMember);
if (divisorInfo != null) {
newMetricDefinition.setDivisorBinding(divisorInfo);
}
if (instanceNameBinding != null) {
// ToDo: Push NVP binding handle through for instanceName binding
// instead of separate properties.
newMetricDefinition.setNameMemberType(instanceNameBinding.getValue());
newMetricDefinition.setNameMemberName(instanceNameBinding.getName());
newMetricDefinition.setNameBound(true);
}
// ToDo: Set it read-only and add to collection, following EventMetric
// model?
definitions.add(newMetricDefinition); // Add it to our list.
}
}
} // end of metric definitions lock
} // end check that this SampledMetricValue attribute is valid
} // end foreach loop over SampledMetricValue attributes on a given member
} // end check for SampledMetricValue attribute
} // end of foreach loop over members
// Now we need to remember this list of definitions, to save time on future
// lookups.
definitionsMap.put(userObjectType, definitions);
} // end of definition map dictionary-lookup-failed.
// If we found an entry, definitions was set for us by the TryGetValue(), so
// we're good.
} // end of dictionary lock
// We scanned for all of them (the first time), but did they ask for just one?
if (TypeUtils.isNotBlank(counterName)) {
SampledMetricDefinition[] mappedDefinitions = definitions.toArray(new SampledMetricDefinition[0]); // Save a
// snapshot
// to
// loop
// over...
definitions = new ArrayList(); // Now reset the list; they only want one.
for (SampledMetricDefinition definition : mappedDefinitions) {
if (definition.getCounterName().equals(counterName)) {
definitions.add(definition); // Add the right one to the list.
}
}
}
return definitions.toArray(new SampledMetricDefinition[0]); // Return a copy of what we found (they can't change
// our internal cached list).
}
/**
* Checks the provided Type against the list of recognized numeric types and
* special types supported for SampledMetric data.
*
* Sampled metrics require inherently numeric samples, so only data with a
* numeric Type or of a recognized Type which can be converted to a numeric
* value in a standard way can be sampled for a sampled metric counter.
* Supported numeric .NET types include: Double, Single, Decimal, Int64, UInt64,
* Int32, UInt32, Int16, and UInt16. The common time representation types:
* DateTime, DateTimeOffset, and TimeSpan are also supported by automatically
* taking their Ticks value. All sampled metric data samples are converted to a
* Double (double-precision floating point) value when sampled.
*
* @param userDataType The typeof(SomeSpecificType) or dataValue.GetType() to
* check.
* @return True if Loupe supports sampled metric data samples with that Type,
* otherwise false.
*/
public static boolean isValidDataType(java.lang.Class userDataType) {
return dataTypeSupported.containsKey(userDataType);
}
/**
* Checks the provided SamplingType enum value to determine if that sampling
* type requires two values per sample.
*
* Sampled metrics sample either single values (*Count sampling types) or pairs
* of values (*Fraction sampling types), determined by their sampling type. This
* method distinguishes between the two scenarios. The *Fraction sampling types
* record a numerator and a divisor for each sample, so when defining sampled
* metric counters with SampledMetric and SampledMetricValue attributes, these
* sampling types require a corresponding SampledMetricDivisor attribute for the
* same counter name. The *Count sampling types only record a single value for
* each sample, so they do not need a divisor to be specified.
*
* @param samplingType A SamplingType enum value to check.
* @return True if the given sampling type requires a second value for each
* sample as the divisor.
*/
public static boolean requiresDivisor(SamplingType samplingType) {
boolean required;
switch (samplingType) {
case RAW_FRACTION:
case INCREMENTAL_FRACTION:
case TOTAL_FRACTION:
required = true;
break;
default:
required = false;
break;
}
return required;
}
/**
* Determine the readable Type for a field, property, or method.
*
* This method assumes that only MemberType of Field, Property, or Method will
* be given. A method with void return type will return typeof(void), and
* properties with no get accessor will return null. This does not currently
* check method signature info for the zero-argument requirement.
*
* @param member The MemberInfo of a Field, Property, or Method member.
* @return The Type of value which can be read from the field, property, or
* method.
*/
private static java.lang.Class getTypeOfMember(AccessibleObject member) {
java.lang.Class readType;
if (member instanceof Method) {
// For methods, it's the return value type.
readType = ((java.lang.reflect.Method) member).getReturnType();
} else if (member instanceof Field) {
// For fields, it's the field type... They can always be read (through
// reflection, that is).
java.lang.reflect.Field fieldInfo = (java.lang.reflect.Field) member;
readType = fieldInfo.getType();
} else {
// Nothing else is supported; return null.
readType = null;
}
return readType;
}
/**
* Check that the specified binding matches the specified member, assuming the
* same declaring type.
*
* @param binding A NameValuePair<MemberType> representing the name and
* member type binding to check.
* @param member The MemberInfo of the member to check against.
* @return True if the binding matches the given member, false if the binding
* (or member) is null or does not match.
*/
private static boolean bindingMatchesMember(NameValuePair binding, AccessibleObject member) {
String name = null;
Class memberType = null;
if (MemberType.FIELD.equals(binding.getValue())) {
Field field = (Field)member;
name = field.getName();
memberType = Field.class;
} else if (MemberType.METHOD.equals(binding.getValue())) {
Method method = (Method)member;
name = method.getName();
memberType = Method.class;
}
return (binding != null && member != null && memberType.isInstance(member)
&& binding.getName().equals(name));
}
/**
* The intended method of interpreting the sampled counter value.
*
* @return the sampling type
*/
public SamplingType getSamplingType() {
return SamplingType.forValue(this.packet.getMetricSampleType().getValue());
}
/**
* Indicates whether a final value can be determined from just one sample or if
* two comparable samples are required.
*
* @return the requires multiple samples
*/
public boolean getRequiresMultipleSamples() {
return this.requiresMultipleSamples;
}
/**
* Indicates if this definition is configured to retrieve its information
* directly from an object.
*
* When true, metric instances and samples can be generated from a live object
* of the same type that was used to generate the data binding. It isn't
* necessary that the same object be used, just that it be a compatible type to
* the original type used to establish the binding.
*
* @return true, if is bound
*/
public boolean isBound() {
return this.bound;
}
/**
* Sets the checks if is bound.
*
* @param value the new checks if is bound
*/
public void setIsBound(boolean value) {
this.bound = value;
}
/**
* When bound, indicates the exact interface or object type that was bound.
*
* When creating new metrics or metric samples, this data type must be provided
* in bound mode.
*
* @return the bound type
*/
public java.lang.Class getBoundType() {
return this.boundType;
}
/**
* Sets the bound type.
*
* @param value the new bound type
*/
public void setBoundType(java.lang.Class value) {
this.boundType = value;
}
/**
* The set of custom sampled metrics that use this definition.
*
* All metrics with the same definition are of the same object type.
*
* @return the metrics
*/
protected Map getMetrics() {
return metrics;
}
/**
* Retrieves the specified metric instance, or creates it if it doesn't exist.
*
* @param instanceName the instance name
* @return The custom sampled metric object.
*/
public SampledMetric addOrGetMetric(String instanceName) {
// Find the right metric sample instance, creating it if we have to.
SampledMetric ourMetric;
if (TypeUtils.isBlank(instanceName)) {
instanceName = null;
}
// This must be protected in a multi-threaded environment
synchronized (lock) {
ourMetric = metrics.get(instanceName);
if (ourMetric == null) {
ourMetric = new SampledMetric(this, new CustomSampledMetricPacket(packet, instanceName));
metrics.put(instanceName, ourMetric);
}
}
// and return the metric
return ourMetric;
}
/**
* Write a metric sample to the specified metric instance with the provided data
* immediately, creating the metric if it doesn't exist.
*
*
*
* The sample is immediately written to the log. If you are sampling multiple
* metrics at the same time, it is faster to create each of the samples and
* write them with one call to Log.Write instead of writing them out
* individually. To do this, you can use CreateMetricSample.
*
*
* Custom metrics using a sample type of AverageFraction and DeltaFraction
* should not use this method because they require a base value as well as a raw
* value.
*
*
* @param instanceName The instance name to use, or blank or null for the
* default metric.
* @param rawValue The raw data value
*/
public void writeSample(String instanceName, double rawValue) {
// Find the right metric sample instance, creating it if we have to.
SampledMetric ourMetric = addOrGetMetric(instanceName);
// now that we have the right metric object, its time to go ahead and create the
// sample.
ourMetric.writeSample(rawValue);
}
/**
* Write a metric sample to the specified metric instance with the provided data
* immediately, creating the metric if it doesn't exist.
*
*
*
* The sample is immediately written to the log. If you are sampling multiple
* metrics at the same time, it is faster to create each of the samples and
* write them with one call to Log.Write instead of writing them out
* individually. To do this, you can use CreateMetricSample.
*
*
* Custom metrics using a sample type of AverageFraction and DeltaFraction
* should not use this method because they require a base value as well as a raw
* value.
*
*
* @param instanceName The instance name to use, or blank or null for the
* default metric.
* @param rawValue The raw data value
* @param rawTimeStamp The exact date and time the raw value was determined
*/
public void writeSample(String instanceName, double rawValue, OffsetDateTime rawTimeStamp) {
// Find the right metric sample instance, creating it if we have to.
SampledMetric ourMetric = addOrGetMetric(instanceName);
// now that we have the right metric object, its time to go ahead and create the
// sample.
ourMetric.writeSample(rawValue, rawTimeStamp);
}
/**
* Write a metric sample to the specified metric instance with the provided data
* immediately, creating the metric if it doesn't exist.
*
* The sample is immediately written to the log. If you are sampling multiple
* metrics at the same time, it is faster to create each of the samples and
* write them with one call to Log.Write instead of writing them out
* individually. To do this, you can use CreateMetricSample
*
* @param instanceName The instance name to use, or blank or null for the
* default metric.
* @param rawValue The raw data value
* @param baseValue The reference value to compare against for come counter
* types
*/
public void writeSample(String instanceName, double rawValue, double baseValue) {
// Find the right metric sample instance, creating it if we have to.
SampledMetric ourMetric = addOrGetMetric(instanceName);
// now that we have the right metric object, its time to go ahead and create the
// sample.
ourMetric.writeSample(rawValue, baseValue);
}
/**
* Write a metric sample to the specified metric instance with the provided data
* immediately, creating the metric if it doesn't exist.
*
* The sample is immediately written to the log. If you are sampling multiple
* metrics at the same time, it is faster to create each of the samples and
* write them with one call to Log.Write instead of writing them out
* individually. To do this, you can use CreateMetricSample
*
* @param instanceName The instance name to use, or blank or null for the
* default metric.
* @param rawValue The raw data value
* @param baseValue The reference value to compare against for come counter
* types
* @param rawTimeStamp The exact date and time the raw value was determined
*/
public void writeSample(String instanceName, double rawValue, double baseValue, OffsetDateTime rawTimeStamp) {
// Find the right metric sample instance, creating it if we have to.
SampledMetric ourMetric = addOrGetMetric(instanceName);
// now that we have the right metric object, its time to go ahead and create the
// sample.
ourMetric.writeSample(rawValue, baseValue, rawTimeStamp);
}
/**
* Write a metric sample to the specified instance of this metric definition
* using the provided data object.
*
*
*
* This overload may only be used if this metric definition was created by
* SampledMetric and SampledMetricValue attributes on a particular Type (class,
* struct, or interface), and only for userDataObjects of a type assignable to
* that bound type for this definition. Also see the static WriteAllSamples()
* method.
*
*
* The provided instanceName parameter will override any instance name binding
* set for this definition with a SampledMetricInstanceName attribute (this
* method overload ignores the instance name binding). The specified metric
* instance is created if it does not already exist. See the other overloads
* taking a userDataObject as the first parameter to use the bound member to
* determine a metric instance name from the user data object automatically,
* with an optional fall-back instance name.
*
*
* @param instanceName The instance name to use, or null or empty for the
* default metric instance.
* @param metricData A data object to sample, compatible with the binding type
* of this definition.
*/
public void writeSample(String instanceName, Object metricData) {
if (metricData == null) {
throw new NullPointerException("metricData");
}
if (!isBound()) {
throw new IllegalArgumentException(
"This sampled metric definition is not bound to sample automatically from a user data object. A different overload must be used to specify the data value(s) directly.");
}
java.lang.Class userDataType = metricData.getClass();
if (!getBoundType().isAssignableFrom(userDataType)) {
throw new IllegalArgumentException(String.format(Locale.ROOT,
"The provided user data object type (%1$s) is not assignable to this sampled metric's bound type (%2$s) and can not be sampled automatically for this metric definition.",
userDataType, getBoundType()));
}
SampledMetric metricInstance = SampledMetric.register(this, instanceName); // Get the particular instance
// specified.
metricInstance.writeSample(metricData); // And write a sample from the provided data object.
}
/**
* Write a metric sample to an automatically-determined instance of this metric
* definition using the provided data object, with a fall-back instance name.
*
*
*
* This overload may only be used if this metric definition was created by
* SampledMetric and SampledMetricValue attributes on a particular Type (class,
* struct, or interface), and only for userDataObjects of a type assignable to
* that bound type for this definition.
*
*
* The metric instance name will be obtained from the member which was marked
* with the SampledMetricInstanceName attribute. If none is bound, the instance
* name parameter will be used as a fall-back. The determined metric instance
* will be created if it does not already exist.
*
*
* @param metricData A data object to sample, compatible with the
* binding type of this definition.
* @param fallbackInstanceName The instance name to fall back on if this
* definition does not specify an instance name
* binding (may be null).
*/
public void writeSample(Object metricData, String fallbackInstanceName) {
if (metricData == null) {
throw new NullPointerException("metricData");
}
if (!isBound()) {
throw new IllegalArgumentException(
"This sampled metric definition is not bound to sample automatically from a user data object. A different overload must be used to specify the data value(s) directly.");
}
java.lang.Class userDataType = metricData.getClass();
if (!getBoundType().isAssignableFrom(userDataType)) {
throw new IllegalArgumentException(String.format(Locale.ROOT,
"The provided user data object type (%1$s) is not assignable to this sampled metric's bound type (%2$s) and can not be sampled automatically for this metric definition.",
userDataType, getBoundType()));
}
String autoInstanceName = fallbackInstanceName; // Use the fall-back instance unless we find a specific instance
// name.
if (getNameBound()) {
String tempVar = invokeInstanceNameBinding(metricData);
autoInstanceName = (tempVar != null) ? tempVar : fallbackInstanceName; // Use fall-back on errors.
}
if (TypeUtils.isBlank(autoInstanceName)) {
autoInstanceName = null; // Convert empty string back to null.
}
// Now use our other overload with the instance name we just grabbed (or the
// default we set first).
writeSample(autoInstanceName, metricData);
}
/**
* Write a metric sample to an automatically-determined instance of this metric
* definition using the provided data object.
*
*
*
* This overload may only be used if this metric definition was created by
* SampledMetric and SampledMetricValue attributes on a particular Type (class,
* struct, or interface), and only for userDataObjects of a type assignable to
* that bound type for this definition.
*
*
* The metric instance name will be obtained from the member which was marked
* with the SampledMetricInstanceName attribute. If none is bound, the default
* instance will be used (a null instance name). The determined metric instance
* will be created if it does not already exist. See the overloads with an
* instanceName parameter to specify a particular metric instance name.
*
*
* @param metricData A data object to sample, compatible with the binding type
* of this definition.
*/
public void writeSample(Object metricData) {
writeSample(metricData, null);
}
/**
* Use the instance name binding for this definition to query the instance name
* of a given user data object.
*
* If not bound, this method returns null.
*
* @param metricData A live object instance (does not work on a Type).
* @return The instance name determined by the binding query.
*/
public String invokeInstanceNameBinding(Object metricData) {
java.lang.Class userDataType = metricData.getClass();
if (!getBoundType().isAssignableFrom(userDataType)) {
return null; // Doesn't match the bound type, can't invoke it.
}
// ToDo: Change instance name binding to use NVP (or a new Binding class).
NameValuePair nameBinding = new NameValuePair(getNameMemberName(), getNameMemberType());
String autoInstanceName = null;
try {
Object rawName = null;
if (getNameMemberType().equals(MemberType.FIELD)) {
Field field = userDataType.getDeclaredField(getNameMemberName());
field.setAccessible(true);
rawName = field.get(metricData);
} else if (getNameMemberType().equals(MemberType.METHOD)) {
Method method = userDataType.getMethod(getNameMemberName());
method.setAccessible(true);
rawName = method.invoke(metricData);
}
if (rawName == null || rawName.getClass() == String.class) {
autoInstanceName = rawName instanceof String ? (String) rawName : null; // Null, or an actual
// string. We're cool with
// either.
} else {
autoInstanceName = rawName.toString(); // Convert it to a string, because that's what we need.
}
if (autoInstanceName == null) {
autoInstanceName = ""; // Use this to report a valid "default instance" result.
}
} catch (java.lang.Exception e) {
autoInstanceName = null; // Null reports a failure case.
}
return autoInstanceName;
}
/**
* Sample every sampled metric defined by SampledMetric and SampledMetricValue
* attributes on the provided data object at any interface or inheritance level.
*
* @param metricData A user data object defining sampled metrics by
* attributes on itself or its interfaces or any
* inherited type.
* @param fallbackInstanceName The instance name to fall back on if a given
* definition does not specify an instance name
* binding (may be null).
*/
public static void write(Object metricData, String fallbackInstanceName) {
// Actual logic is in SampledMetric class.
SampledMetric.write(metricData, fallbackInstanceName);
}
/**
* Sample every sampled metric defined by SampledMetric and SampledMetricValue
* attributes on the provided data object at any interface or inheritance level.
*
* @param metricData A user data object defining sampled metrics by attributes
* on itself or its interfaces or any inherited type.
*/
public static void write(Object metricData) {
// Actual logic is in SampledMetric class.
SampledMetric.write(metricData, "");
}
/**
* Indicates whether two samples are required to calculate a metric value or
* not.
*
*
* Many sample types require multiple samples to determine an output value
* because they work with the change between two points.
*
* @param samplingType the sampling type
* @return true, if successful
*/
public static boolean sampledMetricTypeRequiresMultipleSamples(SamplingType samplingType) {
boolean multipleRequired;
// based purely on the counter type, according to Microsoft documentation
switch (samplingType) {
case RAW_FRACTION:
case RAW_COUNT:
// these just require one sample
multipleRequired = false;
break;
default:
// everything else requires more than one sample
multipleRequired = true;
break;
}
return multipleRequired;
}
/**
* Indicates if there is a binding for metric instance name.
*
* When true, the Name Member Name and Name Member Type properties are
* available.
*
* @return the name bound
*/
public boolean getNameBound() {
return this.nameBound;
}
/**
* Sets the name bound.
*
* @param value the new name bound
*/
public void setNameBound(boolean value) {
this.nameBound = value;
}
/**
* The name of the member to invoke to determine the metric instance name.
*
* This property is only valid when NameBound is true.
*
* @return the name member name
*/
public String getNameMemberName() {
return this.nameMemberName;
}
/**
* Sets the name member name.
*
* @param value the new name member name
*/
public void setNameMemberName(String value) {
this.nameMemberName = value;
}
/**
* The type of the member to be invoked to determine the metric instance name
* (field, method, or property)
*
* This property is only valid when NameBound is true.
*
* @return the name member type
*/
public MemberType getNameMemberType() {
return this.nameMemberType;
}
/**
* Sets the name member type.
*
* @param value the new name member type
*/
public void setNameMemberType(MemberType value) {
this.nameMemberType = value;
}
/**
* Get the lock object for this sampled metric definition.
*
* @return the lock
*/
public Object getLock() {
return this.lock;
}
/**
* Set the binding for the primary sampling data value (numerator);.
*
* @param member The MemberInfo of the member to bind to.
*/
private void setDataBinding(AccessibleObject member) {
MemberType memberType;
String memberName;
if (member instanceof Field) {
memberType = MemberType.FIELD;
memberName = ((Field)member).getName();
} else {
memberType = MemberType.METHOD;
memberName = ((Method)member).getName();
}
setDataBinding(new NameValuePair(memberName, memberType));
}
/**
* Contains a name-value pair of data member name and MemberType, or null if not
* bound.
*/
private NameValuePair dataBinding;
/**
* Gets the data binding.
*
* @return the data binding
*/
public NameValuePair getDataBinding() {
return this.dataBinding;
}
/**
* Sets the data binding.
*
* @param value the new data binding
*/
private void setDataBinding(NameValuePair value) {
this.dataBinding = value;
}
/**
* Indicates whether the value is configured for automatic collection through
* binding.
*
* If true, the other binding-related properties are available.
*
* @return the data bound
*/
public boolean getDataBound() {
return (getDataBinding() != null);
}
/**
* The type of member that this value is bound to (field, property or method).
*
* This property is only valid if Bound is true.
*
* @return the data member type
*/
public MemberType getDataMemberType() {
return getDataBound() ? getDataBinding().getValue() : MemberType.UNBOUND;
}
/**
* The name of the member that this value is bound to.
*
* This property is only valid if Bound is true.
*
* @return the data member name
*/
public String getDataMemberName() {
return getDataBound() ? getDataBinding().getName() : null;
}
/**
* Set the binding for the secondary sampling data value (divisor);.
*
* @param member The MemberInfo of the member to bind to.
*/
private void setDivisorBinding(AccessibleObject member) {
MemberType memberType;
String memberName;
if (member instanceof Field) {
memberType = MemberType.FIELD;
memberName = ((Field)member).getName();
} else {
memberType = MemberType.METHOD;
memberName = ((Method)member).getName();
}
setDivisorBinding(new NameValuePair(memberName, memberType));
}
/**
* Contains a name-value pair of divisor member name and MemberType, or null if
* not bound.
*/
private NameValuePair divisorBinding;
/**
* Gets the divisor binding.
*
* @return the divisor binding
*/
public NameValuePair getDivisorBinding() {
return this.divisorBinding;
}
/**
* Sets the divisor binding.
*
* @param value the new divisor binding
*/
private void setDivisorBinding(NameValuePair value) {
this.divisorBinding = value;
}
/**
* Indicates whether the divisor is configured for automatic collection through
* binding.
*
* If true, the other binding-related properties are available.
*
* @return the divisor bound
*/
public boolean getDivisorBound() {
return (getDivisorBinding() != null);
}
/**
* The type of member that the divisor is bound to (field, property or method).
*
* This property is only valid if Bound is true.
*
* @return the divisor member type
*/
public MemberType getDivisorMemberType() {
return getDivisorBound() ? getDivisorBinding().getValue() : MemberType.UNBOUND;
}
/**
* The name of the member that the divisor is bound to.
*
* This property is only valid if Bound is true.
*
* @return the divisor member name
*/
public String getDivisorMemberName() {
return getDivisorBound() ? getDivisorBinding().getName() : null;
}
/**
* The unique Id of this sampled metric definition. This can reliably be used as
* a key to refer to this item, within the same session which created it.
*
* The Id is limited to a specific session, and thus identifies a consistent
* unchanged definition. The Id can not be used to identify a definition
* across different sessions, which could have different actual definitions due
* to changing user code. See the Key property to identify a metric definition
* across different sessions.
*
* @return the id
*/
@Override
public UUID getId() {
return this.packet.getID();
}
/**
* The three-part key of the metric definition being captured, as a single
* string.
*
* The Key is the combination of metrics capture system label, category name,
* and counter name to uniquely identify a specific metric definition. It can
* also identify the same definition across different sessions.
*
* @return the key
*/
@Override
public String getKey() {
return this.packet.getName();
}
/**
* A short display string for this metric definition, suitable for end-user
* display.
*
* @return the caption
*/
@Override
public String getCaption() {
return this.packet.getCaption();
}
// internal set { _WrappedDefinition.Caption = value; }
/**
* A description of what is tracked by this metric, suitable for end-user
* display.
*
* @return the description
*/
@Override
public String getDescription() {
return this.packet.getDescription();
}
// internal set { _WrappedDefinition.Description = value; }
/**
* The recommended default display interval for graphing.
*
* @return the interval
*/
@Override
public SamplingInterval getInterval() {
return SamplingInterval.forValue(this.packet.getInterval().getValue());
}
/**
* The display caption for the units this metric's values represent, or null for
* unit-less values.
*
* @return the unit caption
*/
public String getUnitCaption() {
return this.packet.getUnitCaption();
}
/**
* The metric capture system label under which this metric definition was
* created.
*
* This label distinguishes metrics defined and captured by different libraries
* from each other, ensuring that metrics defined by different development
* groups will fall under separate namespaces and not require category names to
* be globally unique across third party libraries linked by an application.
* Pick your own label which will uniquely identify your library or namespace.
*
* @return the metrics system
*/
@Override
public String getMetricsSystem() {
return this.packet.getMetricTypeName();
}
/**
* The category of this metric for display purposes. This can be a period
* delimited string to represent a variable height hierarchy.
*
* @return the category name
*/
@Override
public String getCategoryName() {
return this.packet.getCategoryName();
}
/**
* The display name of this metric (unique within the category name).
*
* @return the counter name
*/
@Override
public String getCounterName() {
return this.packet.getCounterName();
}
/**
* The sample type of the metric. Indicates whether the metric represents
* discrete events or a continuous value.
*
* @return the sample type
*/
@Override
public SampleType getSampleType() {
return this.packet.getSampleType();
}
/**
* Indicates that this sampled metric definition has been registered and can not
* be altered (always true for sampled metric definitions).
*
* @return true, if is read only
*/
@Override
public boolean isReadOnly() {
return this.packet.isReadOnly();
}
/**
* Set this metric definition to be read-only and lock out further changes,
* allowing it to be instantiated and sampled.
*/
public void setReadOnly() {
synchronized (getLock()) {
this.packet.setIsReadOnly(true);
}
}
/**
* Determines whether the collection of all metric definitions contains an
* element with the specified key.
*
* @param id The metric definition Id to locate in the collection
* @return true if the collection contains an element with the key; otherwise,
* false.
*/
public static boolean containsKey(UUID id) {
// gateway to our inner dictionary
return definitions.containsKey(id);
}
/**
* Determines whether the collection of all metric definitions contains an
* element with the specified key.
*
* @param key The Key of the event metric definition to check (composed of the
* metrics system, category name, and counter name combined as a
* single string).
* @return true if the collection contains an element with the key; otherwise,
* false.
*/
public static boolean containsKey(String key) {
// protect ourself from a null before we do the trim (or we'll get an odd user
// the error won't understand)
if (TypeUtils.isBlank(key)) {
throw new NullPointerException("key");
}
// gateway to our alternate inner dictionary
return definitions.containsKey(key.trim());
}
/**
* Determines whether the collection of all metric definitions contains an
* element with the specified key.
*
* @param metricsSystem The metrics capture system label.
* @param categoryName The name of the category with which this definition is
* associated.
* @param counterName The name of the definition within the category.
* @return true if the collection contains an element with the key; otherwise,
* false.
*/
public static boolean containsKey(String metricsSystem, String categoryName, String counterName) {
// gateway to our alternate inner dictionary
return definitions.containsKey(metricsSystem, categoryName, counterName);
}
/**
* Retrieve a SampledMetricDefinition by its Id, if present. (Throws an
* ArgumentException if the Id resolves to an EventMetricDefinition instead.)
*
* This method looks in the collection of registered metric definitions for the
* specified Id key. If it is not found, the output is set to null and the
* method returns false. If the Id key is found and resolves to a
* SampledMetricDefinition, it is stored in the value output parameter and the
* method returns true. If the Id key is found but is not a
* SampledMetricDefinition, an ArgumentException is thrown to signal a usage
* inconsistency in your code.
*
* @param id The Id of the sampled metric definition to get.
* @param value The output variable to receive the SampledMetricDefinition
* object if found (null if not).
* @return False if no metric definition is registered with the given Id, true
* if a SampledMetricDefinition is registered with the given Id, or
* throws an exception if the registered definition is not a
* SampledMetricDefinition.
*/
public static boolean tryGetValue(UUID id, OutObject value) {
IMetricDefinition definition;
// gateway to our internal collection TryGetValue()
OutObject tempOutDefinition = new OutObject();
boolean foundValue = definitions.tryGetValue(id, tempOutDefinition);
definition = tempOutDefinition.argValue;
value.argValue = foundValue
? definition instanceof SampledMetricDefinition ? (SampledMetricDefinition) definition : null
: null;
if (foundValue && value.argValue == null) {
// Uh-oh, we found one but it didn't resolve to an EventMetricDefinition!
throw new IllegalArgumentException(String.format(Locale.ROOT,
"The metric definition found by Id (%1$s) is not a sampled metric definition.", id));
}
return foundValue;
}
/**
* Retrieve a SampledMetricDefinition by its combined three-part Key string, if
* present.
*
* This method looks in the collection of registered metric definitions for the
* specified Key. If it is not found, the output is set to null and the method
* returns false. If the Id key is found and resolves to a
* SampledMetricDefinition, it is stored in the value output parameter and the
* method returns true. If the Id key is found but is not a
* SampledMetricDefinition, an ArgumentException is thrown to signal a usage
* inconsistency in your code.
*
* @param key The Key of the sampled metric definition to get (composed of the
* metrics system, category name, and counter name combined as a
* single string).
* @param value The output variable to receive the SampledMetricDefinition
* object if found (null if not).
* @return False if no metric definition is registered with the given Key, true
* if a SampledMetricDefinition is registered with the given Key, or
* throws an exception if the registered definition is not a
* SampledMetricDefinition.
*/
public static boolean tryGetValue(String key, OutObject value) {
// protect ourself from a null before we do the trim (or we'll get an odd user
// the error won't understand)
if (TypeUtils.isBlank(key)) {
throw new NullPointerException("key");
}
IMetricDefinition definition;
// gateway to our inner dictionary try get value
OutObject tempOutDefinition = new OutObject();
boolean foundValue = definitions.tryGetValue(key.trim(), tempOutDefinition);
definition = tempOutDefinition.argValue;
value.argValue = foundValue
? definition instanceof SampledMetricDefinition ? (SampledMetricDefinition) definition : null
: null;
if (foundValue && value.argValue == null) {
// Uh-oh, we found one but it didn't resolve to an EventMetricDefinition!
throw new IllegalArgumentException(String.format(Locale.ROOT,
"The metric definition found by Key \"%1$s\" is not a sampled metric definition.", key));
}
return foundValue;
}
/**
* Retrieve a SampledMetricDefinition by its three key strings (metrics system,
* category name, and counter name), if present.
*
* This method looks in the collection of registered metric definitions for the
* specified 3-part key. If it is not found, the output is set to null and the
* method returns false. If the Id key is found and resolves to a
* SampledMetricDefinition, it is stored in the value output parameter and the
* method returns true. If the Id key is found but is not a
* SampledMetricDefinition, an ArgumentException is thrown to signal a usage
* inconsistency in your code.
*
* @param metricsSystem The metrics capture system label of the definition to
* look up.
* @param categoryName The name of the category with which the definition is
* associated.
* @param counterName The name of the definition within the category.
* @param value The output variable to receive the
* SampledMetricDefinition object if found (null if not).
* @return False if no metric definition is registered with the given Key, true
* if a SampledMetricDefinition is registered with the given Key, or
* throws an exception if the registered definition is not a
* SampledMetricDefinition.
*/
public static boolean tryGetValue(String metricsSystem, String categoryName, String counterName,
OutObject value) {
IMetricDefinition definition;
// gateway to our inner dictionary try get value
OutObject tempOutDefinition = new OutObject();
boolean foundValue = definitions.tryGetValue(metricsSystem, categoryName, counterName, tempOutDefinition);
definition = tempOutDefinition.argValue;
value.argValue = foundValue
? definition instanceof SampledMetricDefinition ? (SampledMetricDefinition) definition : null
: null;
if (foundValue && value.argValue == null) {
// Uh-oh, we found one but it didn't resolve to an EventMetricDefinition!
throw new IllegalArgumentException(String.format(Locale.ROOT,
"The metric definition found by metrics system (%1$s) category name (%2$s) counter name (%3$s) is not a sampled metric definition.",
metricsSystem, categoryName, counterName));
}
return foundValue;
}
/**
* Find an existing sampled metric definition previously registered via
* SampledMetric and SampledMetricValue attributes on a specific Type, by its
* counter name.
*
* This method overload can obtain a previously registered
* SampledMetricDefinition created through SampledMetric and SampledMetricValue
* attributes, by specifying the Type containing those attributes. If the
* specified Type does not have a SampledMetric attribute defined, or if the
* Type has a SampledMetric attribute but has not been registered (e.g. by a
* call to SampledMetricDefinition.Register(userObjectType)), then false is
* returned (with out value set to null). If a sampled metric defined by
* attributes on that Type has been successfully registered, then true is
* returned (with the registered SampledMetricDefinition stored in the out
* value). If the metric definition found by the 3-part Key used in the
* SampledMetric attribute (along with the specified counter name) is not a
* sampled metric (e.g. an event metric definition was registered with that
* Key), then an ArgumentException is thrown to signal your programming mistake.
* Inheritance and interfaces will not be searched, so the specified Type
* must directly define the sampled metric, but valid objects of a type
* assignable to the specified bound Type of this definition can be
* sampled from the specific sampled metric definition found.
*
* @param userObjectType A specific Type with attributes defining one or more
* sampled metrics.
* @param counterName The counter name of the desired individual sampled
* metric definition defined by attributes on the
* specified Type.
* @param value The output variable to receive the
* SampledMetricDefinition object if found (null if not).
* @return False if no SampledMetric attribute is found on the specified Type,
* or if no metric definition is registered with the 3-part Key found in
* that attribute (combined with the specified counter name), true if a
* SampledMetricDefinition is registered with the given Key, or throws
* an exception if the registered definition found is not a
* SampledMetricDefinition.
*/
public static boolean tryGetValue(java.lang.Class userObjectType, String counterName,
OutObject value) {
value.argValue = null; // In case we don't find it.
if (userObjectType == null) {
throw new NullPointerException("userObjectType");
}
if (counterName == null) {
throw new NullPointerException("counterName");
}
counterName = counterName.trim(); // Trim any whitespace around it.
if (TypeUtils.isBlank(counterName)) {
throw new NullPointerException(
"The specified counter name is empty which is not allowed, so no metrics can be found for it.");
}
List definitionList;
boolean foundValue = false; // Haven't found the actual definition yet.
// We shouldn't need a lock because we aren't changing the dictionary, just
// doing a single read check.
synchronized (dictionaryLock) // But apparently Dictionaries are not internally threadsafe.
{
definitionList = definitionsMap.get(userObjectType);
if (definitionList != null && !definitionList.isEmpty()) {
SampledMetricDefinition[] definitionArray = definitionList.toArray(new SampledMetricDefinition[0]);
for (SampledMetricDefinition definition : definitionArray) {
if (definition.getCounterName().equals(counterName)) {
value.argValue = definition; // Hey, we found it!
foundValue = true; // Report success!
break; // Stop looking through the array.
}
}
}
}
if (!foundValue) // If we didn't find it on the known list for that type...
{
SampledMetricClass sampledMetricAnnotation = null;
if (userObjectType.isAnnotationPresent(SampledMetricClass.class)) {
sampledMetricAnnotation = (SampledMetricClass) userObjectType
.getAnnotation(SampledMetricClass.class);
}
if (sampledMetricAnnotation != null) {
String metricsSystem = sampledMetricAnnotation.namespace();
String categoryName = sampledMetricAnnotation.categoryName();
IMetricDefinition definition;
// gateway to our inner dictionary try get value
OutObject tempOutDefinition = new OutObject();
foundValue = definitions.tryGetValue(metricsSystem, categoryName, counterName, tempOutDefinition);
definition = tempOutDefinition.argValue;
value.argValue = foundValue
? definition instanceof SampledMetricDefinition ? (SampledMetricDefinition) definition : null
: null;
if (foundValue && value.argValue == null) {
// Uh-oh, we found one but it didn't resolve to a SampledMetricDefinition!
throw new IllegalArgumentException(String.format(Locale.ROOT,
"The metric definition registered for metrics system (%1$s) and category name (%2$s) from SampledMetric attribute on %4$s, and specified counter name (%3$s) is not a sampled metric definition.",
metricsSystem, categoryName, counterName, userObjectType.getSimpleName()));
}
}
// Otherwise we already pre-set value to null, and foundvalue is still false, so
// we'll report the failure.
}
// Otherwise we found a valid definition in our Type-to-definition map, so we've
// output that and report success.
return foundValue;
}
/**
* Determines if the provided object is identical to this object.
*
* @param other the other
* @return True if the other object is also a MetricDefinition and represents
* the same data.
*/
@Override
public boolean equals(Object other) {
// 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.
}
if (!(other instanceof SampledMetricDefinition))
return false;
SampledMetricDefinition otherDef = (SampledMetricDefinition)other;
// they are the same if their GUID's match
return (getId().equals(otherDef.getId()));
}
/* (non-Javadoc)
* @see com.onloupe.core.metrics.IMetricDefinition#getName()
*/
@Override
public String getName() {
return this.packet.getName();
}
/**
* Creates builder to build {@link builder}.
*
* @param metricsSystem the metrics system
* @param categoryName the category name
* @param counterName the counter name
* @return created builder
*/
public static Builder builder(String metricsSystem, String categoryName, String counterName) {
return new Builder(metricsSystem, categoryName, counterName);
}
/**
* Builder to build {@link builder}.
*/
public static final class Builder {
/** The metrics system. */
private String metricsSystem;
/** The category name. */
private String categoryName;
/** The counter name. */
private String counterName;
/** The sampling type. */
private SamplingType samplingType;
/** The unit caption. */
private String unitCaption;
/** The metric caption. */
private String metricCaption;
/** The description. */
private String description;
/**
* Instantiates a new builder.
*
* @param metricsSystem the metrics system
* @param categoryName the category name
* @param counterName the counter name
*/
private Builder(String metricsSystem, String categoryName, String counterName) {
this.metricsSystem = metricsSystem;
this.categoryName = categoryName;
this.counterName = counterName;
}
/**
* Sampling type.
*
* @param samplingType the sampling type
* @return the builder
*/
public Builder samplingType(SamplingType samplingType) {
this.samplingType = samplingType;
return this;
}
/**
* Unit caption.
*
* @param unitCaption the unit caption
* @return the builder
*/
public Builder unitCaption(String unitCaption) {
this.unitCaption = unitCaption;
return this;
}
/**
* Metric caption.
*
* @param metricCaption the metric caption
* @return the builder
*/
public Builder metricCaption(String metricCaption) {
this.metricCaption = metricCaption;
return this;
}
/**
* Description.
*
* @param description the description
* @return the builder
*/
public Builder description(String description) {
this.description = description;
return this;
}
/**
* Builds the.
*
* @return the sampled metric definition
*/
public SampledMetricDefinition build() {
return register(metricsSystem, categoryName, counterName, samplingType, unitCaption, metricCaption,
description);
}
}
}