com.onloupe.agent.metrics.EventMetricDefinition 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.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.EventMetricClass;
import com.onloupe.agent.metrics.annotation.EventMetricValue;
import com.onloupe.core.NameValuePair;
import com.onloupe.core.logging.Log;
import com.onloupe.core.metrics.IMetricDefinition;
import com.onloupe.core.metrics.Metric;
import com.onloupe.core.metrics.MetricDefinition;
import com.onloupe.core.metrics.MetricDefinitionCollection;
import com.onloupe.core.serialization.monitor.EventMetricDefinitionPacket;
import com.onloupe.core.util.OutObject;
import com.onloupe.core.util.RefObject;
import com.onloupe.core.util.SystemUtils;
import com.onloupe.core.util.TypeUtils;
import com.onloupe.model.SampleType;
import com.onloupe.model.metric.MemberType;
/**
* The definition of an event metric, which must be registered before any
* specific event metric instance can be created or sampled.
*
*
*
* Unlike sampled metrics which represent continuous values by sampling at
* periodic intervals, event metrics have meaning only at discrete moments in
* time when some "event" happens and records a sample to describe it.
*
*
* Event metrics can define multiple values to be collected with each sample and
* can include both numeric data types (recorded as their native type) and
* strings (all non-numeric types are converted to strings). Numeric data
* columns can then be processed later to be graphed like Sampled Metrics. Both
* numeric and string data can be analyzed in a variety of ways to produce
* charts. This makes event metrics a powerful instrument for analyzing your
* application's behavior.
*
*
* For more information Event Metrics, see
* Developer's Reference - Metrics -
* Designing Event Metrics.
*
*
* Defining Event Metrics
*
*
* Event metrics can be defined either programmatically or declaratively with
* attributes.
*
*
* To define an event metric with attributes, apply the
* EventMetric attribute to the source
* code for any class, struct, or interface, and apply the
* EventMetricValue attribute to
* desired members to define the value columns. This approach provides a simple
* and powerful way to design and collect event metrics for your application.
* See the EventMetric Class Overview for an
* example.
*
*
* To define an event metric programmatically requires more coding, but allows
* you to optimize the performance of recording event metrics and works in
* environments where it isn't feasible to decorate a class with attributes. See
* the EventMetric Class Overview for an example.
*
*
*/
public final class EventMetricDefinition implements IMetricDefinition {
/** The Constant definitions. */
private static final MetricDefinitionCollection definitions = Log.getMetricDefinitions();;
/** The packet. */
private EventMetricDefinitionPacket packet;
/** The lock. */
private final Object lock = new Object();
/** The metrics. */
private EventMetricCollection metrics;
/** The metric values. */
private EventMetricValueDefinitionCollection metricValues;
/** The bound. */
private boolean bound;
/** The bound type. */
private java.lang.Class boundType;
/** The name bound. */
private boolean nameBound;
/** The name member name. */
private String nameMemberName;
/** The name member type. */
private MemberType nameMemberType;
/** The Constant inheritanceMap. */
private static final Map inheritanceMap = new HashMap(); // Array
// of
// all
// inherited
// types
// (that
// have
// attributes),
// by
/** The Constant definitionMap. */
// type.
private static final Map definitionMap = new HashMap(); // LOCKED
// definition
// by
// specific
// bound
/** The Constant dictionaryLock. */
// type.
private static final Object dictionaryLock = new Object(); // Lock for the DefinitionMap dictionary.
/**
* Create a new event 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
* not be automatically added to the provided collection.
*
* @param metricTypeName The unique metric type
* @param categoryName The name of the category with which this definition is
* associated.
* @param counterName The name of the definition within the category.
*/
private EventMetricDefinition(String metricTypeName, String categoryName, String counterName) {
this.packet = new EventMetricDefinitionPacket(metricTypeName, categoryName, counterName);
this.metrics = new EventMetricCollection(this);
this.metricValues = new EventMetricValueDefinitionCollection(this);
// and we need to set that to our packet, all part of our bogus reach-around to
// make persistence work
packet.setMetricValues(this.metricValues);
}
/**
* Create a new event metric object from the provided raw data packet.
*
* @param packet The packet to create a definition from.
*/
private EventMetricDefinition(EventMetricDefinitionPacket packet) {
// make sure our packet isn't null
if (packet == null) {
throw new NullPointerException("packet");
}
this.packet = packet;
this.metrics = new EventMetricCollection(this);
this.metricValues = new EventMetricValueDefinitionCollection(this);
// and we need to set that to our packet, all part of our bogus reach-around to
// make persistence work
packet.setMetricValues(this.metricValues);
}
/**
* Create a new value column definition with the supplied name and type. The
* name must be unique within this definition.
*
* Internally, only simple types are supported. Any non-numeric,
* non-DateTimeOffset type will be converted to a string using the default
* ToString capability when it is recorded.
*
* @param name The unique name for this value column definition.
* @param type The simple type of this value (e.g. typeof(int) or
* typeof(string)).
* @param summaryFunction The default way that individual samples of this value
* column can be aggregated to create a graphable
* summary. (Use SummaryFunction.Count for non-numeric
* types.)
* @param unitCaption A displayable caption for the units this value
* represents, or null for unit-less values.
* @param caption The end-user display caption for this value column.
* @param description The end-user description for this value column.
* @return The newly created value column definition. See the
* EventMetric Class Overview for an
* example.
*/
public EventMetricValueDefinition addValue(String name, java.lang.Class type, SummaryFunction summaryFunction,
String unitCaption, String caption, String description) {
// Error checking will be done by Values.Add(...), which is also publicly
// available.
// create a new value definition
return getValueCollection().add(name, type, summaryFunction, unitCaption, caption, description);
}
/**
* Find or create multiple event metrics definitions (defined via EventMetric
* attributes) for the provided object or Type.
*
* The provided Type or the GetType() of the provided object instance will be
* scanned for EventMetric attributes on itself and any of its interfaces to
* identify a list of event metrics defined for instances of that type, creating
* them as necessary by scanning its members for EventMetricValue 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 event metrics. Also see RegisterType(Type) to find or create
* a single event metric definition for a specific Type.
*
* @param metricData A Type or an instance defining event metrics by attributes
* on itself and/or its interfaces.
* @return An array of zero or more event metric definitions found for the
* provided object or Type. See the
* EventMetric Class Overview for an
* example.
*/
public static EventMetricDefinition[] registerAll(Object metricData) {
List definitions = new ArrayList();
if (metricData != 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 = ((metricData instanceof java.lang.Class ? (java.lang.Class) metricData
: null) != null) ? (metricData instanceof java.lang.Class ? (java.lang.Class) metricData : null)
: metricData.getClass();
EventMetricDefinition metricDefinition;
java.lang.Class[] inheritanceArray;
boolean foundIt;
synchronized (inheritanceMap) // Apparently Dictionaries aren't 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 {
metricDefinition = registerType(inheritedType);
} catch (Exception e) {
if (SystemUtils.isInDebugMode()) {
e.printStackTrace();
}
metricDefinition = null;
}
if (metricDefinition != null) {
definitions.add(metricDefinition); // Add it 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 an event metric.
if (userObjectType.isAnnotationPresent(EventMetricClass.class)) {
try {
inheritanceList.add(userObjectType); // Add the top level Type to our list of types.
metricDefinition = registerType(userObjectType);
} catch (Exception e) {
if (SystemUtils.isInDebugMode()) {
e.printStackTrace();
}
metricDefinition = null;
}
if (metricDefinition != null) {
definitions.add(metricDefinition); // Add it to the list if found.
}
}
// 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(com.onloupe.agent.metrics.annotation.EventMetricClass.class)) {
// We found an interface with the right Attribute, get its definition.
try {
inheritanceList.add(interfc); // Add the interface to our list of types.
metricDefinition = registerType(interfc);
} catch (Exception e) {
if (SystemUtils.isInDebugMode()) {
e.printStackTrace();
}
metricDefinition = null;
}
if (metricDefinition != null) {
definitions.add(metricDefinition); // Add it to the list if found.
}
}
}
// And finally, drill down it's inheritance...
java.lang.Class baseObjectType = (userObjectType.isInterface()) ? null : userObjectType.getSuperclass();
// ...unless it's an interface.
while (baseObjectType != null && baseObjectType != Object.class
&& !baseObjectType.isInterface()) {
// See if an ancestor Type defines an event metric.
if (baseObjectType.isAnnotationPresent(com.onloupe.agent.metrics.annotation.EventMetricClass.class)) {
try {
inheritanceList.add(baseObjectType); // Add the inherited base to our list of types.
metricDefinition = registerType(baseObjectType);
} catch (Exception e) {
if (SystemUtils.isInDebugMode()) {
e.printStackTrace();
}
metricDefinition = null;
}
if (metricDefinition != null) {
definitions.add(metricDefinition); // Add it 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 aren't internally threadsafe.
{
inheritanceMap.put(userObjectType, inheritanceList.toArray(new java.lang.Class[0]));
}
}
}
// If they gave us a null, we'll just return an empty array.
return definitions.toArray(new EventMetricDefinition[0]);
}
/**
* 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 memberInfo the member info
* @return The Type of value which can be read from the field, property, or
* method.
*/
private static java.lang.Class getTypeOfMember(AccessibleObject memberInfo) {
java.lang.Class readType = null;
if (memberInfo instanceof Method) {
// For methods, it's the return value type.
readType = ((java.lang.reflect.Method) memberInfo).getReturnType();
} else if (memberInfo instanceof Field) {
// For fields, it's the field type... They can always be read (through
// reflection, that is).
readType = ((java.lang.reflect.Field) memberInfo).getType();
}
return readType;
}
/**
* Find or create an event metric definition from EventMetric and
* EventMetricValue attributes on a specific Type.
*
* The provided type must have an EventMetric attribute and can have one or more
* fields, properties or zero-argument methods with EventMetricValue 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 event
* metric definition already exists, it is just returned and no exception is
* thrown. If the provided type is not suitable to create an event 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
* event metric, but valid objects of a type assignable to the specified bound
* Type of this definition can be sampled from this specific event metric
* definition. Also see AddOrGetDefinitions() to find and return an array of
* definitions.
*
* @param metricDataObjectType A specific Type with attributes defining an event
* metric.
* @return The single event metric definition determined by attributes on the
* given Type.
*/
//THIS SCANS CLASS FIELDS AND METHODS FOR ATTRIBUTE/ANNOTATIONS
public static EventMetricDefinition registerType(java.lang.Class metricDataObjectType) {
// See if there is already a definition known on this Type.
// If there is, we just want to return it and not do any more.
EventMetricDefinition newMetricDefinition = definitionMap.get(metricDataObjectType);
// ToDo: Need to overhaul error reporting, should log before throwing exceptions
// in case they are caught internally.
// And throwing exceptions may be pointless if they can never get here without
// us catching exceptions and swallowing them.
synchronized (dictionaryLock) {
if (newMetricDefinition == null) {
definitionMap.put(metricDataObjectType, null); // Pre-set to null in case of exception, so we don't scan
// it again.
// Check if it defines it at this specific level, no inheritance search, no
// interfaces search.
if (!metricDataObjectType.isAnnotationPresent(EventMetricClass.class)) {
// Sorry, Attribute not found.
throw new IllegalArgumentException(
"The specified Type does not have an EventMetric attribute, so it can't be used to define an event metric.");
}
// OK, now waltz off and get the attribute we want.
EventMetricClass eventMetricAnnotation = (EventMetricClass)metricDataObjectType.getAnnotation(EventMetricClass.class);
// Try to cast it to the specific kind of attribute we need
// Verify that the event metric attribute that we got is valid
if (eventMetricAnnotation == null) {
throw new IllegalArgumentException(
"The specified Type does not have a usable EventMetric attribute, so it can't be used to define an event metric.");
}
// make sure the user didn't do any extraordinary funny business
String metricsSystem = eventMetricAnnotation.namespace();
if (TypeUtils.isBlank(metricsSystem)) {
throw new IllegalArgumentException(
"The specified Type's EventMetric has an empty metric namespace which is not allowed, so no metric can be defined.");
}
String metricCategoryName = eventMetricAnnotation.categoryName();
if (TypeUtils.isBlank(metricCategoryName)) {
throw new IllegalArgumentException(
"The specified Type's EventMetric has an empty metric category name which is not allowed, so no metric can be defined.");
}
String metricCounterName = eventMetricAnnotation.counterName();
if (TypeUtils.isBlank(metricCounterName)) {
throw new IllegalArgumentException(
"The specified Type's EventMetric has an empty metric counter name which is not allowed, so no metric can be defined.");
}
// See if there is already a definition with the specified keys.
// If there is, we just want to return it and not do any more.
// We use a lock because we need to have the check and the add (which we do at
// the end) happen as a single event.
// We'll just hold the collection lock the whole time since we don't have to
// wait on arbitrary client code.
synchronized (Log.getMetricDefinitions().getLock()) {
IMetricDefinition existingMetricDefinition;
OutObject tempOutExistingMetricDefinition = new OutObject();
if (Log.getMetricDefinitions().tryGetValue(metricsSystem, metricCategoryName,
metricCounterName, tempOutExistingMetricDefinition)) {
existingMetricDefinition = tempOutExistingMetricDefinition.argValue;
// eh, we already had a definition. We want to go no further.
newMetricDefinition = existingMetricDefinition instanceof EventMetricDefinition
? (EventMetricDefinition) existingMetricDefinition
: null;
if (newMetricDefinition == null) {
throw new IllegalArgumentException(
"The specified Type's EventMetric attribute's 3-part Key is already used for a metric definition which is not an event metric.");
}
} else {
existingMetricDefinition = tempOutExistingMetricDefinition.argValue;
// OK, now we know we'll be good.
newMetricDefinition = new EventMetricDefinition(metricsSystem, metricCategoryName,
metricCounterName);
newMetricDefinition.setBoundType(metricDataObjectType);
newMetricDefinition.setCaption(eventMetricAnnotation.caption());
newMetricDefinition.setDescription(eventMetricAnnotation.description());
// now that we have our new metric definition, do our level best to add the rest
// of the information to it
List members = new ArrayList();
members.addAll(Arrays.asList(metricDataObjectType.getFields()));
members.addAll(Arrays.asList(metricDataObjectType.getMethods()));
// reflect all of the field/property/methods in the type so we can inspect them
// for attributes
for (AccessibleObject curMember : members) {
MemberType curMemberType;
if (curMember instanceof Field) {
curMemberType = MemberType.FIELD;
} else {
curMemberType = MemberType.METHOD;
}
// and what can we get from our little friend?
if (curMember.isAnnotationPresent(com.onloupe.agent.metrics.annotation.EventMetricInstanceName.class)) {
// have we already bound our instance name?
if (newMetricDefinition.getNameBound()) {
// yes, so report a duplicate name warning
} else {
// nope, so lets go for it, set up our binding information
if (MemberType.FIELD.equals(curMemberType)) {
Field field = (Field)curMember;
newMetricDefinition.setNameBound(true);
newMetricDefinition.setNameMemberType(MemberType.FIELD);
newMetricDefinition.setNameMemberName(field.getName());
} else if (MemberType.METHOD.equals(curMemberType)) {
Method method = (Method)curMember;
newMetricDefinition.setNameBound(true);
newMetricDefinition.setNameMemberType(MemberType.METHOD);
newMetricDefinition.setNameMemberName(method.getName());
}
}
}
// What about mappings to values?
if (curMember.isAnnotationPresent(com.onloupe.agent.metrics.annotation.EventMetricValues.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 event 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.
EventMetricValue[] curMemberValueAttributes = curMember.getAnnotationsByType(EventMetricValue.class);
for (com.onloupe.agent.metrics.annotation.EventMetricValue curValueAttribute : curMemberValueAttributes) {
String memberName = null;
if (MemberType.FIELD.equals(curMemberType)) {
memberName = ((Field)curMember).getName();
} else if (MemberType.METHOD.equals(curMemberType)) {
memberName = ((Method)curMember).getName();
}
// cast it and test
if (curValueAttribute != null) {
if (newMetricDefinition.getValueCollection()
.containsKey(curValueAttribute.name())) // Warn about duplicates.
{
} else if (curType == null) // Warn about an unreadable property.
{
} else if (curType == void.class) // Warn about a void method.
{
} else {
// We finally have validated everything and we're ready to set up the new
// value.
EventMetricValueDefinition newValue = newMetricDefinition
.getValueCollection().add(curValueAttribute.name(), curType);
// set up our binding information
newValue.setBound(true);
newValue.setMemberType(curMemberType);
newValue.setMemberName(memberName);
// now that we've added it, what else can we set?
newValue.setUnitCaption(curValueAttribute.unitCaption());
newValue.setSummaryFunction(curValueAttribute.summaryFunction().getValue());
newValue.setCaption(curValueAttribute.caption());
newValue.setDescription(curValueAttribute.description());
// and finally, is this our default value?
if ((newMetricDefinition.getDefaultValue() == null)
&& (curValueAttribute.defaultValue())) {
newMetricDefinition.setDefaultValue(newValue);
}
}
}
}
} // End of if value attribute defined
} // End of foreach over members
// Indicate that the specified metric definition is a bound definition, and
// register ourselves.
newMetricDefinition.setIsBound(true);
//TODO track this down. See: metricdefinition
newMetricDefinition.setReadOnly(); // Mark the internal definition as
// completed.
Log.getMetricDefinitions().add(newMetricDefinition);
} // End of if Log.MetricDefinitions.TryGetValue ELSE
} // End of LOCK on Log.MetricDefinitions
definitionMap.put(metricDataObjectType, newMetricDefinition); // Remember it for next time.
} // End of if DefinitionMap.TryGetValue == false
// Otherwise, we read out the definition we found on this Type before, so we'll
// just return it.
} // End of LOCK on DefinitionMap
return newMetricDefinition;
}
/**
* Register the referenced EventMetricDefinition template, or update the
* reference to the official definition if a compatible event metric definition
* already exists for the same 3-part Key.
*
* This is the final step for creating a new event metric definition
* programmatically, after constructing a new EventMetricDefinition(...) and
* calling AddValue(...) as desired to define value columns. If a metric
* definition is already registered with the same Key, it will be checked for
* compatibility. An incompatible existing definition (e.g. a sampled metric, or
* missing value columns from the provided template) will result in an
* ArgumentException to signal your programming mistake; each different metric
* definition in an application session must have a unique 3-part Key. If a
* compatible existing event metric definition is found, the reference to the
* EventMetricDefinition will be updated to the registered definition. If no
* metric definition exists with the same 3-part key as the template, then the
* new definition will be officially registered and may be used as a valid
* definition. This approach ensures thread-safe creation of singular event
* metric definitions without the need for locking by your code.
*
* @param newDefinition A reference to an event metric definition template to be
* registered, and to receive the official registered event
* metric definition.
*/
public static void register(RefObject newDefinition) {
// ToDo: Consider copy-in/copy-out of newDefinition to protect against
// pathological clients bypassing our sanity checks.
EventMetricDefinition theDefinition = newDefinition.argValue;
if (theDefinition == null) {
throw new NullPointerException(
"A null definition can not be registered nor used to look up a registered event metric definition.");
}
EventMetricDefinition registeredDefinition = theDefinition.register();
// We don't overwrite newDefinition immediately, in case we get back a null, so
// we can still inspect the
// template in case of errors. Also, they can inspect it in a debugger after we
// throw this exception...
if (registeredDefinition == null || !registeredDefinition.isReadOnly()) {
// Hmmm, this really should not happen if we've coded registration correctly.
// Any errors should already throw exceptions above.
throw new IllegalArgumentException(String.format(Locale.ROOT,
"Unknown error registering new event metric definition: metrics system (%1$s), category name (%2$s), counter name (%3$s)",
theDefinition.getMetricsSystem(), theDefinition.getCategoryName(), theDefinition.getCounterName()));
}
newDefinition.argValue = registeredDefinition; // Finally, update the reference to whatever the official
// registration is.
}
/**
* Register the referenced EventMetricDefinition template, or update the
* reference to the official definition if a compatible event metric definition
* already exists for the same 3-part Key.
*
*
* This is the final step for creating a new event metric definition
* programmatically, after constructing a new EventMetricDefinition(...) and
* calling AddValue(...) as desired to define value columns. If a metric
* definition is already registered with the same Key, it will be checked for
* compatibility. An incompatible existing definition (e.g. a sampled metric, or
* missing value columns from the provided template) will result in an
* ArgumentException to signal your programming mistake; each different metric
* definition in an application session must have a unique 3-part Key. If a
* compatible existing event metric definition is found, the reference to the
* EventMetricDefinition will be updated to the registered definition. If no
* metric definition exists with the same 3-part key as the template, then the
* new definition will be officially registered and may be used as a valid
* definition. This approach ensures thread-safe creation of singular event
* metric definitions without the need for locking by your code.
*
*
* This overload allows a value column of the definition template to be
* designated as the default one to graph for this event metric. The specified
* name must match a value column name in the definition template or a
* KeyNotFoundException will be thrown (and the template will not be
* registered). The defaultValue parameter will overwrite any previous setting
* of the DefaultValue property of the event metric definition template. If the
* completed template is not ultimately used because a metric definition already
* exists with the same 3-part Key, then the defaultValue parameter will have no
* effect; a metric definition which is already registered can not be altered,
* to ensure consistency within the session log.
*
*
* Also see the overload directly taking an EventMetricValueDefinition as the
* defaultValue for an approach which may be less prone to mistakes.
*
*
* @param newDefinition A reference to an event metric definition template to be
* registered, and to receive the official registered event
* metric definition.
* @param defaultValue The name of a value column to designate as the default
* one to graph for this metric.
* @throws Exception the exception
*/
public static void register(RefObject newDefinition, String defaultValue) throws Exception {
EventMetricDefinition theDefinition = newDefinition.argValue;
if (theDefinition == null) {
throw new NullPointerException(
"A null definition can not be registered nor used to look up a registered event metric definition.");
}
if (TypeUtils.isBlank(defaultValue)) {
throw new NullPointerException(
"The specified defaultValue name must be a legal value column name and thus may not be null or empty.");
}
// The definition should be held only by one thread until we're actually
// registered, anyway, but just to be safe...
// Lock the new definition so there can't be any other attempted changes while
// we do this.
synchronized (theDefinition.getLock()) {
EventMetricValueDefinition defaultValueDefinition = theDefinition.getValueCollection().get(defaultValue);
if (defaultValueDefinition == null) {
throw new Exception(String.format(Locale.ROOT,
"The specified defaultValue column name (%1$s) was not found in the definition.", defaultValue));
}
theDefinition.setDefaultValue(defaultValueDefinition); // Set the DefaultValue to the one identified.
// And finally do the actual registration with our other overload.
RefObject tempRefTheDefinition = new RefObject(theDefinition);
register(tempRefTheDefinition);
theDefinition = tempRefTheDefinition.argValue;
}
newDefinition.argValue = theDefinition;
}
/**
* Register the referenced EventMetricDefinition template, or update the
* reference to the official definition if a compatible event metric definition
* already exists for the same 3-part Key.
*
*
* This is the final step for creating a new event metric definition
* programmatically, after constructing a new EventMetricDefinition(...) and
* calling AddValue(...) as desired to define value columns. If a metric
* definition is already registered with the same Key, it will be checked for
* compatibility. An incompatible existing definition (e.g. a sampled metric, or
* missing value columns from the provided template) will result in an
* ArgumentException to signal your programming mistake; each different metric
* definition in an application session must have a unique 3-part Key. If a
* compatible existing event metric definition is found, the reference to the
* EventMetricDefinition will be updated to the registered definition. If no
* metric definition exists with the same 3-part key as the template, then the
* new definition will be officially registered and may be used as a valid
* definition. This approach ensures thread-safe creation of singular event
* metric definitions without the need for locking by your code.
*
*
* This overload allows an EventMetricValueDefinition to be designated as the
* default value column to graph for this event metric. When adding value
* columns to the definition template, the EventMetricValueDefinition returned
* by one of them can be saved to pass in this overload, for convenience. The
* defaultValue parameter will overwrite any previous setting of the
* DefaultValue property of the event metric definition template. If the
* completed template is not ultimately used because a metric definition already
* exists with the same 3-part Key, then the defaultValue parameter will have no
* effect; a metric definition which is already registered can not be altered,
* to ensure consistency within the session log.
*
*
* @param newDefinition A reference to an event metric definition template to be
* registered, and to receive the official registered event
* metric definition.
* @param defaultValue The definition of a value column in this event metric
* definition to designate as the default one to graph for
* this metric.
* @throws Exception the exception
*/
public static void register(RefObject newDefinition,
EventMetricValueDefinition defaultValue) throws Exception {
EventMetricDefinition theDefinition = newDefinition.argValue;
if (theDefinition == null) {
throw new NullPointerException(
"A null definition can not be registered nor used to look up a registered event metric definition.");
}
synchronized (theDefinition.getLock()) {
if (defaultValue != null && (defaultValue.getDefinition() != theDefinition
|| !theDefinition.getValueCollection().contains(defaultValue))) {
throw new Exception(
"The event metric value column definition specified is not associated with the specified event metric definition.");
}
theDefinition.setDefaultValue(defaultValue); // Set the DefaultValue to the one identified.
// And finally do the actual registration with our other overload.
RefObject tempRefTheDefinition = new RefObject(theDefinition);
register(tempRefTheDefinition);
theDefinition = tempRefTheDefinition.argValue;
}
newDefinition.argValue = theDefinition;
}
/**
* Register this instance as a completed definition and return the valid usable
* definition for this event metric.
*
* This call is necessary to complete a new event metric definition (after calls
* to AddValue(...)) before it can be used, and it signifies that all desired
* value columns have been added to the definition. Only the first registration
* of a metric definition with a given Key (metrics system, category name, and
* counter name) will be effective and return the same definition object;
* subsequent calls (perhaps by another thread) will instead return the existing
* definition already registered. If a definition already registered with that
* Key can not be an event metric (e.g. a sampled metric is defined with that
* Key) or if this instance defined value columns not present as compatible
* value columns in the existing registered definition with that Key, then an
* ArgumentException will be thrown to signal your programming mistake.
*
* @return The actual usable definition with the same metrics system, category
* name, and counter name as this instance. See the
* EventMetric Class Overview for an
* example.
*/
public EventMetricDefinition register() {
EventMetricDefinition officialDefinition;
// We should be held only by one thread until we're actually registered, anyway,
// but just to be safe...
// Lock our own definition so there can't be any other attempted changes while
// we do this.
synchronized (getLock()) {
// We need to lock the collection while we check for an existing definition and
// maybe add this one to it.
synchronized (Log.getMetricDefinitions().getLock()) {
IMetricDefinition rawDefinition;
OutObject tempOutRawDefinition = new OutObject();
if (!Log.getMetricDefinitions().tryGetValue(getMetricsSystem(), getCategoryName(), getCounterName(),
tempOutRawDefinition)) {
rawDefinition = tempOutRawDefinition.argValue;
// There isn't already one by that Key. Great! Register ourselves.
this.setReadOnly(); // Mark the internal definition as completed.
officialDefinition = this;
Log.getMetricDefinitions().add(this);
} else {
rawDefinition = tempOutRawDefinition.argValue;
// Oooh, we found one already registered. We'll want to do some checking on
// this, but outside the lock.
officialDefinition = rawDefinition instanceof EventMetricDefinition
? (EventMetricDefinition) 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 an event metric.",
getMetricsSystem(), getCategoryName(), getCounterName()));
} else if (this != officialDefinition) {
// There was one other than us, make sure it's compatible with us.
// It's read-only, so we don't need the definition lock for this check.
EventMetricValueDefinitionCollection officialValues = officialDefinition.getValues();
for (EventMetricValueDefinition ourValue : getValues().getList()) {
EventMetricValueDefinition officialValue;
OutObject tempOutOfficialValue = new OutObject();
if (!officialValues.tryGetValue(ourValue.getName(), tempOutOfficialValue)) {
officialValue = tempOutOfficialValue.argValue;
// It doesn't have one of our value columns!
throw new IllegalArgumentException(String.format(Locale.ROOT,
"There is already an event metric definition for the same metrics system (%1$s), category name (%2$s), and counter name (%3$s), but it is not compatible; it does not define value column \"%4$s\".",
getMetricsSystem(), getCategoryName(), getCounterName(), ourValue.getName()));
} else {
officialValue = tempOutOfficialValue.argValue;
if (ourValue.getSerializedType() != ((EventMetricValueDefinition) officialValue)
.getSerializedType()) {
throw new IllegalArgumentException(String.format(Locale.ROOT,
"There is already an event 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 value column \"%4$s\" with type %5$s rather than type %6$s.",
getMetricsSystem(), getCategoryName(), getCounterName(), ourValue.getName(),
officialValue.getClass().getName(), ourValue.getType().getSimpleName()));
}
}
}
// We got through all the values defined in this instance? Then we're okay to
// return the official one.
}
// Otherwise, it's just us, so we're all good.
}
return officialDefinition;
}
///////////////////////////////////////////////////////
/**
* Adds the or get.
*
* @param instanceName the instance name
* @return the event metric
*/
public EventMetric addOrGet(String instanceName) {
// now that we have our instance name, we go ahead and see if there is already
// an instance with the right name or just add it
// make sure the try & add are atomic
synchronized (getLock()) {
EventMetric metric = getMetrics().get(instanceName);
EventMetric eventMetric;
if (metric == null) {
// there isn't one with the right name, we need to create it. It will add itself
// to the metrics collection
// in the constructor so we don't have to.
eventMetric = add(instanceName);
} else {
eventMetric = metric;
}
return eventMetric;
}
}
/**
* Adds the.
*
* @param instanceName the instance name
* @return the event metric
*/
public EventMetric add(String instanceName) {
return this.getMetrics().add(TypeUtils.trimToNull(instanceName));
}
/**
* Creates a new metric definition from the provided information, or returns an
* existing matching definition if found. If the metric definition doesn't
* exist, it will be created. If the metric definition does exist, but is not a
* Custom Sampled Metric (or a derived class) an exception will be thrown.
* Definitions are looked up and added to the provided definitions dictionary.
*
* @param definitions The definitions dictionary this definition is a part of
* @param metricTypeName The unique metric type
* @param categoryName The name of the category with which this definition is
* associated.
* @param counterName The name of the definition within the category.
* @return the event metric definition
*/
public static EventMetricDefinition addOrGet(MetricDefinitionCollection definitions, String metricTypeName,
String categoryName, String counterName) {
// we must have a definitions collection, or we have a problem
if (definitions == null) {
throw new NullPointerException("definitions");
}
// we need to find the definition, adding it if necessary
String definitionKey = getKey(metricTypeName, categoryName, counterName);
IMetricDefinition definition;
// We need to grab a lock so our try get & the create are done as one lock.
synchronized (definitions.getLock()) {
OutObject tempOutDefinition = new OutObject();
if (definitions.tryGetValue(definitionKey, tempOutDefinition)) {
definition = tempOutDefinition.argValue;
} else {
definition = tempOutDefinition.argValue;
// we didn't find one, make a new one
definition = new EventMetricDefinition(metricTypeName, categoryName, counterName);
definitions.add(definition); // Add it to the collection, no longer done in the constructor.
// ToDo: Reconsider this implementation; putting incomplete event metric
// definitions in the collection is not ideal.
}
}
return (EventMetricDefinition) definition;
}
/**
* Creates a new metric definition from the provided information, or returns an
* existing matching definition if found. If the metric definition doesn't
* exist, it will be created. If the metric definition does exist, but is not an
* Event Metric an exception will be thrown. Definitions are looked up and added
* to the active logging metrics collection (Log.Metrics)
*
* @param metricTypeName The unique metric type
* @param categoryName The name of the category with which this definition is
* associated.
* @param counterName The name of the definition within the category.
* @return the event metric definition
*/
public static EventMetricDefinition addOrGet(String metricTypeName, String categoryName, String counterName) {
// just forward into our call that requires the definition to be specified
return addOrGet(Log.getMetrics(), metricTypeName, categoryName, counterName);
}
/**
* Calculate the string key for a metric definition.
*
* @param metric The existing metric object to generate a string key for
* @return The unique string key for this item
*/
public static String getKey(Metric metric) {
// make sure the metric object isn't null
if (metric == null) {
throw new NullPointerException("metric");
}
// We are explicitly NOT passing the instance name here - we want the key of the
// DEFINITION.
return getKey(metric.getMetricTypeName(), metric.getCategoryName(), metric.getCounterName());
}
/**
* Calculate the string key for a metric definition.
*
* @param metricDefinition The existing metric definition object to generate a
* string key for
* @return The unique string key for this item
*/
public static String getKey(MetricDefinition metricDefinition) {
// make sure the metric definition object isn't null
if (metricDefinition == null) {
throw new NullPointerException("metricDefinition");
}
return getKey(metricDefinition.getMetricTypeName(), metricDefinition.getCategoryName(),
metricDefinition.getCounterName());
}
/**
* Calculate the string key for a metric.
*
* @param metricDefinition The existing metric definition object to generate a
* string key for
* @param instanceName The name of the performance counter category
* instance, or an empty string (""), if the category
* contains a single instance.
* @return The unique string key for this item
*/
public static String getKey(MetricDefinition metricDefinition, String instanceName) {
// make sure the metric definition object isn't null
if (metricDefinition == null) {
throw new NullPointerException("metricDefinition");
}
return getKey(metricDefinition.getMetricTypeName(), metricDefinition.getCategoryName(),
metricDefinition.getCounterName(), instanceName);
}
/**
* Calculate the string key for a metric definition.
*
* @param metricTypeName The unique metric type
* @param categoryName The name of the performance counter category
* (performance object) with which this performance
* counter is associated.
* @param counterName The name of the performance counter.
* @return The unique string key for this item
*/
public static String getKey(String metricTypeName, String categoryName, String counterName) {
return getKey(metricTypeName, categoryName, counterName, null);
}
/**
* Calculate the string key for a metric.
*
* @param metricTypeName The unique metric type
* @param categoryName The name of the performance counter category
* (performance object) with which this performance
* counter is associated.
* @param counterName The name of the performance counter.
* @param instanceName The name of the performance counter category instance,
* or an empty string (""), if the category contains a
* single instance.
* @return The unique string key for this item
*/
public static String getKey(String metricTypeName, String categoryName, String counterName, String instanceName) {
String key;
if (TypeUtils.isBlank(metricTypeName)) {
throw new NullPointerException("metricTypeName");
}
if (TypeUtils.isBlank(categoryName)) {
throw new NullPointerException("categoryName");
}
if (TypeUtils.isBlank(counterName)) {
throw new NullPointerException("counterName");
}
// we assemble the key by appending the parts of the name of the counter
// together. We have to guard for a NULL or EMPTY instance name
if ((TypeUtils.isBlank(instanceName)) || (TypeUtils.isBlank(instanceName.trim()))) {
// there is no instance name - just the first two parts
key = String.format("%1$s~%2$s~%3$s", metricTypeName.trim(), categoryName.trim(), counterName.trim());
} else {
key = String.format("%1$s~%2$s~%3$s~%4$s", metricTypeName.trim(), categoryName.trim(), counterName.trim(),
instanceName.trim());
}
return key;
}
///////////////////////////////////////////////////////
/**
* The set of values defined for this metric definition
*
* Any number of different values can be recorded along with each event to
* provide additional trends and filtering ability for later client analysis.
*
* @return the values
*/
public final EventMetricValueDefinitionCollection getValues() {
return this.metricValues;
}
/**
* Indicates if this definition is configured to retrieve its information
* directly from an object.
*
* When true, metric instances and samples can be defined 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 metrics that use this definition.
*
* All metrics with the same definition are of the same object type.
*
* @return the metrics
*/
public EventMetricCollection getMetrics() {
return this.metrics;
}
/**
* The set of values defined for this metric definition.
*
* Any number of different values can be recorded along with each event to
* provide additional summarization and filtering ability for later client
* analysis.
*
* @return the value collection
*/
protected EventMetricValueDefinitionCollection getValueCollection() {
return this.metricValues;
}
/**
* The set of values defined for this metric definition. (A snapshot array copy.
* AddValue() through this definition object.)
*
* Any number of different values can be recorded along with each event to
* provide additional summarization and filtering ability for later client
* analysis. While the definition is being built (with
* AddValue the current set of value definitions can
* be examined as an array snapshot returned by this property. Changes to the
* array will only affect that copy.
*
* @return the value definitions
*/
public EventMetricValueDefinition[] getValueDefinitions() {
return this.metricValues.toArray();
}
/**
* Indicates whether the provided object is a numeric type or can only be
* graphed by a SummaryFunction.Count.
*
* @param type The type to be verified.
* @return True if the supplied type is mathematically graphable, false
* otherwise.
*/
public static boolean isNumericValueType(java.lang.Class type) {
// Just ask our internal class.
return EventMetricDefinition.isTrendableValueType(type);
}
/**
* Indicates whether the provided object can be graphed as a trend.
*
* @param type The type to be verified
* @return True if the supplied type is trendable, false otherwise.
*/
public static boolean isTrendableValueType(java.lang.Class type) {
boolean trendable = false;
// we're using Is so we can check for compatible types, not just base types.
if ((type == Short.class) || (type == Short.class) || (type == Integer.class) || (type == Integer.class)
|| (type == Long.class) || (type == Long.class) || (type == BigDecimal.class) || (type == Double.class)
|| (type == Float.class)) {
trendable = true;
}
// Now check object types
else if ((type == OffsetDateTime.class) || (type == Duration.class)) {
trendable = true;
}
return trendable;
}
/**
* Indicates whether the provided type can be stored as a value or not.
*
* Most types can be stored, with the value of non-numeric types being the
* string representation of the type. Collections, arrays, and other such sets
* can't be stored as a single value.
*
* @param type The type to be verified.
* @return True if the supplied type is supported, false otherwise.
*/
public static boolean isSupportedValueType(java.lang.Class type) {
// Just ask our internal class.
return EventMetricDefinition.isSupportedValueType(type);
}
/**
* The default value to display for this event metric. Typically this should be
* a trendable value.
*
* @return the default value
*/
public final EventMetricValueDefinition getDefaultValue() {
return ((TypeUtils.isBlank(packet.getDefaultValueName())) ? null
: getValues().get(packet.getDefaultValueName()));
}
/**
* Sets the default value.
*
* @param value the new default value
*/
public final void setDefaultValue(EventMetricValueDefinition value) {
packet.setDefaultValueName(((value == null) ? null : getValues().get(value.getName()).getName()));
}
/**
* Write a metric sample to the specified instance of this event metric
* definition using the provided data object.
*
*
*
* This overload may only be used if this metric definition was created by
* EventMetric and EventMetricValue 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 an EventMetricInstanceName 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 userDataObject A data object to sample, compatible with the binding
* type of this definition.
*/
public void writeSample(String instanceName, Object userDataObject) {
if (userDataObject == null) {
throw new NullPointerException("userDataObject");
}
boolean weAreRegistered = true;
if (!isReadOnly()) {
// Uh-oh, we're not actually a registered definition! Try to register ourselves.
EventMetricDefinition registeredDefinition = register();
if (registeredDefinition == null) {
throw new IllegalArgumentException(String.format(Locale.ROOT,
"Unknown error registering event metric definition: metrics system (%1$s), category name (%2$s), counter name (%3$s).",
getMetricsSystem(), getCategoryName(), getCounterName()));
}
if (this != registeredDefinition) {
weAreRegistered = false; // So we won't try to do this again below.
registeredDefinition.writeSample(instanceName, userDataObject); // Have the registered one do it.
}
}
if (weAreRegistered) {
if (!isBound()) {
throw new IllegalArgumentException(
"This event metric definition is not bound to sample automatically from a user data object. CreateSample() and SetValue() must be used to specify the data values directly.");
}
java.lang.Class userDataType = userDataObject.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 event metric's bound type (%2$s) and can not be sampled automatically for this metric definition.",
userDataType, getBoundType()));
}
EventMetric metricInstance = EventMetric.register(this, instanceName); // Get the particular instance
// specified.
metricInstance.writeSample(userDataObject); // 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.
*
* @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).
*
*
* This overload may only be used if this metric
* definition was created by EventMetric and
* EventMetricValue 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
* EventMetricInstanceName 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.
*
*
* @throws NullPointerException No metricData object was
* provided.
* @throws IllegalArgumentException This event metric definition
* is not bound to sample automatically from a user
* data object. CreateSample() and SetValue() must
* be used to specify the data values
* directly.<br /> -or-<br /> The
* provided user data object is not assignable to
* this event metric's bound type and can not be
* sampled automatically for this metric definition.
* See the
* EventMetric Class
* Overview for an example.
*/
public void writeSample(Object metricData, String fallbackInstanceName) {
if (metricData == null) {
throw new NullPointerException("metricData");
}
boolean weAreRegistered = true;
if (!isReadOnly()) {
// Uh-oh, we're not actually a registered definition! Try to register ourselves.
EventMetricDefinition registeredDefinition = register();
if (registeredDefinition == null) {
throw new IllegalArgumentException(String.format(Locale.ROOT,
"Unknown error registering event metric definition: metrics system (%1$s), category name (%2$s), counter name (%3$s).",
getMetricsSystem(), getCategoryName(), getCounterName()));
}
if (this != registeredDefinition) {
weAreRegistered = false; // So we won't try to do this again below.
registeredDefinition.writeSample(metricData, fallbackInstanceName); // Have the registered one do it.
}
}
if (weAreRegistered) {
if (!isBound()) {
throw new IllegalArgumentException(
"This event metric definition is not bound to sample automatically from a user data object. CreateSample() and SetValue() must be used to specify the data values 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 event 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
* EventMetric and EventMetricValue 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 EventMetricInstanceName 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.
* @throws IllegalArgumentException This event
* metric definition is not bound to sample automatically from
* a user data object. CreateSample() and SetValue() must be
* used to specify the data values directly.<br />
* -or-<br /> The provided user data object is not
* assignable to this event metric's bound type and can not be
* sampled automatically for this metric definition.
* See the EventMetric Class
* Overview for an example.
*/
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 instanceof String) {
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 event metric defined by EventMetric and EventMetricValue
* attributes on the provided data object at any interface or inheritance level.
*
* @param metricData A user data object defining event 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).
* @throws IllegalArgumentException The specified
* metricDataObjectType does not have an EventMetric
* attribute <br /> <br /> -or- <br
* /> <br /> The specified Type does not
* have a usable EventMetric attribute, so it can't
* be used to define an event metric.<br />
* <br /> -or- <br /> <br /> The
* specified Type's EventMetric has an empty metric
* namespace which is not allowed, so no metric can
* be defined.<br /> <br /> -or- <br
* /> <br /> The specified Type's
* EventMetric has an empty metric category name
* which is not allowed, so no metric can be
* defined.<br /> <br /> -or- <br
* /> <br /> The specified Type's
* EventMetric has an empty metric counter name
* which is not allowed, so no metric can be
* defined.<br /> <br /> -or- <br
* /> <br /> The specified Type's
* EventMetric attribute's 3-part Key is already
* used for a metric definition which is not an
* event metric. See the
* EventMetric Class
* Overview for an example.
*/
public static void write(Object metricData, String fallbackInstanceName) {
EventMetricDefinition[] allDefinitions = registerAll(metricData);
for (EventMetricDefinition definition : allDefinitions) {
try {
definition.writeSample(metricData, fallbackInstanceName);
}
// ReSharper disable EmptyGeneralCatchClause
catch (Exception e) {
if (SystemUtils.isInDebugMode()) {
e.printStackTrace();
}
}
}
return;
}
/**
* Sample every event metric defined by EventMetric and EventMetricValue
* attributes on the provided data object at any interface or inheritance level.
*
* @param metricData A user data object defining event metrics by attributes on
* itself or its interfaces or any inherited type.
* @throws IllegalArgumentException The
* specified metricDataObjectType does not have an EventMetric
* attribute <br /> <br /> -or- <br />
* <br /> The specified Type does not have a usable
* EventMetric attribute, so it can't be used to define an
* event metric.<br /> <br /> -or- <br />
* <br /> The specified Type's EventMetric has an empty
* metric namespace which is not allowed, so no metric can be
* defined.<br /> <br /> -or- <br /> <br
* /> The specified Type's EventMetric has an empty metric
* category name which is not allowed, so no metric can be
* defined.<br /> <br /> -or- <br /> <br
* /> The specified Type's EventMetric has an empty metric
* counter name which is not allowed, so no metric can be
* defined.<br /> <br /> -or- <br /> <br
* /> The specified Type's EventMetric attribute's 3-part
* Key is already used for a metric definition which is not an
* event metric. See the
* EventMetric Class Overview
* for an example.
*/
public static void write(Object metricData) {
write(metricData, null);
}
/**
* Object Change Locking object.
*
* @return the lock
*/
public Object getLock() {
return this.lock;
}
/**
* Gets the packet.
*
* @return the packet
*/
protected EventMetricDefinitionPacket getPacket() {
return packet;
}
/**
* 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
*/
protected boolean getNameBound() {
return this.nameBound;
}
/**
* Sets the name bound.
*
* @param value the new name bound
*/
protected 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
*/
protected String getNameMemberName() {
return this.nameMemberName;
}
/**
* Sets the name member name.
*
* @param value the new name member name
*/
protected 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
*/
protected MemberType getNameMemberType() {
return this.nameMemberType;
}
/**
* Sets the name member type.
*
* @param value the new name member type
*/
protected void setNameMemberType(MemberType value) {
this.nameMemberType = value;
}
/**
* The unique Id of this event 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 name of the metric definition being captured.
*
* The name is for comparing the same definition 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();
}
/**
* 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();
}
/**
* Sets the caption.
*
* @param value the new caption
*/
public void setCaption(String value) {
this.packet.setCaption(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();
}
/**
* Sets the description.
*
* @param value the new description
*/
public void setDescription(String value) {
this.packet.setDescription(value);
}
/**
* The recommended default display interval for graphing.
*
* @return the interval
*/
//TODO refactor this when we pull up the model, use only agent types.
@Override
public SamplingInterval getInterval() {
return SamplingInterval.forValue(this.packet.getInterval().getValue());
}
/*
* /// /// The definitions collection that contains this definition.
* /// /// This parent pointer should be used when walking
* from an object back to its parent instead of taking /// advantage of the
* static metrics definition collection to ensure your application works as
* expected when handling /// data that has been loaded from a database or data
* file. The static metrics collection is for the metrics being /// actively
* captured in the current process, not for metrics that are being read or
* manipulated. internal MetricDefinitionCollection Definitions { get
* { return Definitions; } }
*/
/**
* The metric capture system label under which this metric definition was
* created.
*
* This label distinguish 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 whether this event metric definition is now read-only because it
* has been officially registered and can be used to create event metric
* instances.
*
* @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);
this.metricValues.setAllIndex();
}
}
/**
* 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 an EventMetricDefinition by its Id, if present. (Throws an
* ArgumentException if the Id resolves to a SampledMetricDefinition 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 an
* EventMetricDefinition, it is stored in the value output parameter and the
* method returns true. If the Id key is found but is not an
* EventMetricDefinition, an ArgumentException is thrown to signal a usage
* inconsistency in your code.
*
* @param id The Id of the event metric definition to get.
* @param value The output variable to receive the EventMetricDefinition object
* if found (null if not).
* @return False if no metric definition is registered with the given Id, true
* if an EventMetricDefinition is registered with the given Id, or
* throws an exception if the registered definition is not an
* EventMetricDefinition.
*/
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 EventMetricDefinition ? (EventMetricDefinition) 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 an event metric definition.", id));
}
return foundValue;
}
/**
* Retrieve an EventMetricDefinition by its combined three-part Key string, if
* present.
*
* 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 an
* EventMetricDefinition, it is stored in the value output parameter and the
* method returns true. If the Id key is found but is not an
* EventMetricDefinition, an ArgumentException is thrown to signal a usage
* inconsistency in your code.
*
* @param key The Key of the event 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 EventMetricDefinition object
* if found (null if not).
* @return False if no metric definition is registered with the given Key, true
* if an EventMetricDefinition is registered with the given Key, or
* throws an exception if the registered definition is not an
* EventMetricDefinition.
*/
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 EventMetricDefinition ? (EventMetricDefinition) 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 an event metric definition.", key));
}
return foundValue;
}
/**
* Retrieve an EventMetricDefinition 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 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 an
* EventMetricDefinition, it is stored in the value output parameter and the
* method returns true. If the Id key is found but is not an
* EventMetricDefinition, 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 EventMetricDefinition
* object if found (null if not).
* @return False if no metric definition is registered with the given Key, true
* if an EventMetricDefinition is registered with the given Key, or
* throws an exception if the registered definition is not an
* EventMetricDefinition.
*/
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 EventMetricDefinition ? (EventMetricDefinition) 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 an event metric definition.",
metricsSystem, categoryName, counterName));
}
return foundValue;
}
/**
* Find an existing event metric definition previously registered via
* EventMetric and EventMetricValue attributes on a specific Type.
*
* This method overload can obtain a previously registered EventMetricDefinition
* created through EventMetric and EventMetricValue attributes, by specifying
* the Type containing those attributes. If the specified Type does not have an
* EventMetric attribute defined, or if the Type has an EventMetric attribute
* but has not been registered (e.g. by a call to
* EventMetricDefinition.Register(userObjectType)), then false is returned (with
* out value set to null). If an event metric defined by attributes on that Type
* has been successfully registered, then true is returned (with the registered
* EventMetricDefinition stored in the out value). If the metric definition
* found by the 3-part Key used in the EventMetric attribute is not an event
* metric (e.g. a sampled 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 an event metric, but valid objects of a type assignable
* to the specified bound Type of this definition can be sampled from the
* specific event metric definition found.
*
* @param metricDataObjectType A specific Type with attributes defining an event
* metric.
* @param value The output variable to receive the
* EventMetricDefinition object if found (null if
* not).
* @return False if no EventMetric attribute is found on the specified Type, or
* if no metric definition is registered with the 3-part Key found in
* that attribute, true if an EventMetricDefinition is registered with
* the given Key, or throws an exception if the registered definition
* found is not an EventMetricDefinition.
*/
public static boolean tryGetValue(java.lang.Class metricDataObjectType, OutObject value) {
if (metricDataObjectType == null) {
value.argValue = null;
throw new NullPointerException("metricDataObjectType");
}
boolean foundValue;
// We shouldn't need a lock here because we aren't changing the dictionary, just
// doing a single read check.
synchronized (dictionaryLock) // But apparently Dictionary may not be internally threadsafe, so we do need our
// lock.
{
value.argValue = definitionMap.get(metricDataObjectType);
foundValue = value.argValue != null;
// Fast lookup, for efficiency.
}
// We have to check for a possible null in the map, meaning we've seen that Type
// but it couldn't register it.
// We'll treat a null as a not-found case, and look for the attribute.
if (!foundValue || value.argValue == null) {
EventMetricClass eventMetricAnnotation = null;
if (metricDataObjectType.isAnnotationPresent(EventMetricClass.class)) {
eventMetricAnnotation = (EventMetricClass) metricDataObjectType.getAnnotation(EventMetricClass.class);
}
if (eventMetricAnnotation != null) {
String metricsSystem = eventMetricAnnotation.namespace();
String categoryName = eventMetricAnnotation.categoryName();
String counterName = eventMetricAnnotation.counterName();
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 EventMetricDefinition ? (EventMetricDefinition) 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 registered for metrics system (%1$s) category name (%2$s) counter name (%3$s) specified in EventMetric attribute on %4$s is not an event metric definition.",
metricsSystem, categoryName, counterName, metricDataObjectType.getSimpleName()));
}
} else {
foundValue = false;
value.argValue = null;
}
}
// else we found a valid definition in our Type-to-definition map, so we've
// output that and report success.
return foundValue;
}
/**
* Builder.
*
* @param metricTypeName the metric type name
* @param categoryName the category name
* @param counterName the counter name
* @return the builder
*/
public static Builder builder(String metricTypeName, String categoryName, String counterName) {
return new Builder(metricTypeName, categoryName, counterName);
}
/**
* Builder.
*
* @param packet the packet
* @return the builder
*/
public static Builder builder(EventMetricDefinitionPacket packet) {
return new Builder(packet);
}
/**
* The Class Builder.
*/
public static final class Builder {
/** The definition. */
private EventMetricDefinition definition;
/**
* Instantiates a new builder.
*
* @param metricTypeName the metric type name
* @param categoryName the category name
* @param counterName the counter name
*/
private Builder(String metricTypeName, String categoryName, String counterName) {
definition = new EventMetricDefinition(metricTypeName, categoryName, counterName);
}
/**
* Instantiates a new builder.
*
* @param packet the packet
*/
private Builder(EventMetricDefinitionPacket packet) {
definition = new EventMetricDefinition(packet);
}
/**
* Default value.
*
* @param eventMetricValueDefinition the event metric value definition
* @return the builder
*/
public Builder defaultValue(EventMetricValueDefinition eventMetricValueDefinition) {
definition.setDefaultValue(eventMetricValueDefinition);
return this;
}
/**
* Adds the value.
*
* @param name the name
* @param type the type
* @param summaryFunction the summary function
* @param unitCaption the unit caption
* @param caption the caption
* @param description the description
* @return the builder
*/
public Builder addValue(String name, java.lang.Class type, SummaryFunction summaryFunction,
String unitCaption, String caption, String description) {
definition.addValue(name, type, summaryFunction, unitCaption, caption, description);
return this;
}
/**
* Description.
*
* @param description the description
* @return the builder
*/
public Builder description(String description) {
definition.setDescription(description);
return this;
}
/**
* Caption.
*
* @param caption the caption
* @return the builder
*/
public Builder caption(String caption) {
definition.getPacket().setCaption(caption);
return this;
}
/**
* Builds the.
*
* @return the event metric definition
*/
public EventMetricDefinition build() {
return definition;
}
/**
* Register.
*
* @return the event metric definition
*/
public EventMetricDefinition register() {
return definition.register();
}
}
}