Java tutorial
/* * Copyright 2009-2017 Aconex * * Licensed under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. See the License for the specific language governing * permissions and limitations under the License. */ package io.pcp.parfait.dxm; import static com.google.common.collect.Maps.newConcurrentMap; import static io.pcp.parfait.dxm.MmvVersion.MMV_VERSION1; import static io.pcp.parfait.dxm.PcpString.STRING_BLOCK_LENGTH; import static io.pcp.parfait.dxm.PcpString.STRING_BLOCK_LIMIT; import static systems.uom.unicode.CLDR.BYTE; import static tec.uom.se.unit.MetricPrefix.KILO; import static tec.uom.se.unit.Units.HERTZ; import static tec.uom.se.unit.Units.HOUR; import static tec.uom.se.unit.Units.SECOND; import static tec.uom.se.AbstractUnit.ONE; import java.io.File; import java.io.IOException; import java.lang.management.ManagementFactory; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.EnumSet; import java.util.GregorianCalendar; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import javax.measure.Unit; import com.google.common.collect.Maps; import io.pcp.parfait.dxm.PcpString.PcpStringStore; import io.pcp.parfait.dxm.semantics.Semantics; import io.pcp.parfait.dxm.types.AbstractTypeHandler; import io.pcp.parfait.dxm.types.DefaultTypeHandlers; import io.pcp.parfait.dxm.types.MmvMetricType; import io.pcp.parfait.dxm.types.TypeHandler; import com.google.common.base.Preconditions; import net.jcip.annotations.GuardedBy; import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.commons.lang.builder.ToStringStyle; /** * <p> * Creates and updates a memory-mapped file suitable for reading by the PCP MMV PMDA. The method * used to 'render' the Java values into the file (converting them to a corresponding PCP Type and * byte array) are determined by {@link TypeHandler TypeHandlers}; some default TypeHandlers are * supplied (see {@link DefaultTypeHandlers#getDefaultMappings()}) but additional ones can be * supplied for a particular Java class ({@link #registerType(Class, TypeHandler)}, or on a * metric-by-metric basis. * </p> * Standard lifecycle for this class is: * <ul> * <li>create the PcpMmvFile * <li>{@link #addMetric(MetricName, Semantics, Unit, Object)} for every metric which will be monitored by the file * <li>{@link #start()} to write out the file ready for the MMV agent to read * <li>{@link #updateMetric(MetricName, Object)} metrics as new values come to hand * </ul> * <p> * Adding metrics after the file is started will result in an {@link IllegalStateException}, as will * updating metric values <em>before</em> it's started. * </p> * <p> * This class currently has a few important limitations: * </p> * <ul> * <li>Process ID is obtained in a HotSpot-JVM specific way (may work on other JVMs, not known)</li> * <li>Receiving agent must be using MMV agent from pcp-2.8.10 or later (v1 MMV on-disk format)</li> * </ul> * * @author Cowan */ public class PcpMmvWriter implements PcpWriter { private static enum TocType { INSTANCE_DOMAINS(1), INSTANCES(2), METRICS(3), VALUES(4), STRINGS(5); private final int identifier; private TocType(int identifier) { this.identifier = identifier; } } public static enum MmvFlag { MMV_FLAG_NOPREFIX(1), MMV_FLAG_PROCESS(2); private final int bitmask; MmvFlag(int bitmask) { this.bitmask = bitmask; } public int getBitmask() { return bitmask; } } private static final Set<MmvFlag> DEFAULT_FLAGS = Collections .unmodifiableSet(EnumSet.of(MmvFlag.MMV_FLAG_NOPREFIX, MmvFlag.MMV_FLAG_PROCESS)); private static final int HEADER_LENGTH = 40; private static final int TOC_LENGTH = 16; /** * The charset used for PCP metrics names and String values. */ static final Charset PCP_CHARSET = Charset.forName("US-ASCII"); private static final byte[] TAG = "MMV\0".getBytes(PCP_CHARSET); static final int DATA_VALUE_LENGTH = 16; private static final TypeHandler<String> MMV_STRING_HANDLER = new AbstractTypeHandler<String>( MmvMetricType.STRING, STRING_BLOCK_LENGTH) { @Override public boolean requiresLargeStorage() { return true; } @Override public void putBytes(ByteBuffer buffer, String value) { byte[] bytes = value.getBytes(PCP_CHARSET); int length = Math.min(bytes.length, STRING_BLOCK_LIMIT); buffer.put(bytes, 0, length); buffer.put((byte) 0); } }; private final ByteBufferFactory byteBufferFactory; private final Store<PcpMetricInfo> metricInfoStore; private final Store<InstanceDomain> instanceDomainStore; private final MmvVersion mmvVersion; private final MetricNameValidator metricNameValidator; private final Map<MetricName, PcpValueInfo> metricData = Maps.newConcurrentMap(); private final Map<Class<?>, TypeHandler<?>> typeHandlers = new ConcurrentHashMap<Class<?>, TypeHandler<?>>( DefaultTypeHandlers.getDefaultMappings()); private final PcpStringStore stringStore = new PcpStringStore(); private volatile boolean started = false; private volatile boolean usePerMetricLock = true; private final Map<PcpValueInfo, ByteBuffer> perMetricByteBuffers = newConcurrentMap(); private final Object globalLock = new Object(); @GuardedBy("itself") private volatile ByteBuffer dataFileBuffer = null; private File file = null; private volatile int processIdentifier = 0; private volatile int clusterIdentifier = 0; private volatile Set<MmvFlag> flags = DEFAULT_FLAGS; /** * A new PcpMmvWriter using a simple default {@link IdentifierSourceSet}. * * @see #PcpMmvWriter(File, IdentifierSourceSet) * @deprecated should pass in an explicit IdentifierSourceSet */ @Deprecated public PcpMmvWriter(File file) { this(file, IdentifierSourceSet.DEFAULT_SET); } /** * Creates a new PcpMmvWriter writing to the underlying file, which will be created + opened as a * memory-mapped file. Uses the provided architecture to determine whether to write 32- or * 64-bit longs for some key header fields. Defaults to {@link MmvVersion#MMV_VERSION1} format * * @param file * the file to map * @param identifierSources * the sources to use for coming up with identifiers for new metrics etc. */ public PcpMmvWriter(File file, IdentifierSourceSet identifierSources) { this(file, identifierSources, MMV_VERSION1); } /** * Creates a new PcpMmvWriter writing to the underlying file, which will be created + opened as a * memory-mapped file. Uses the provided architecture to determine whether to write 32- or * 64-bit longs for some key header fields. * * @param file * the file to map * @param identifierSources * the sources to use for coming up with identifiers for new metrics etc. * @param mmvVersion * the MMV version format to use */ public PcpMmvWriter(File file, IdentifierSourceSet identifierSources, MmvVersion mmvVersion) { this(new FileByteBufferFactory(file), identifierSources, mmvVersion); this.file = file; } public PcpMmvWriter(ByteBufferFactory byteBufferFactory, IdentifierSourceSet identifierSources) { this(byteBufferFactory, identifierSources, MMV_VERSION1); } public PcpMmvWriter(ByteBufferFactory byteBufferFactory, IdentifierSourceSet identifierSources, MmvVersion mmvVersion) { this.byteBufferFactory = byteBufferFactory; this.metricInfoStore = mmvVersion.createMetricInfoStore(identifierSources, stringStore); this.instanceDomainStore = mmvVersion.createInstanceDomainStore(identifierSources, stringStore); this.mmvVersion = mmvVersion; this.metricNameValidator = mmvVersion.createMetricNameValidator(); registerType(String.class, MMV_STRING_HANDLER); } /** * Creates a new PcpMmvWriter writing to the underlying file, which will be created + opened as a * memory-mapped file. This is the constructor most people should use, unless you have a really * good reason not to. It automatically handles all (incl. cross-platform) file location issues. * It will default to {@link MmvVersion#MMV_VERSION1} format * * @param name * logical name of instrumented subsystem (e.g. "hadoop") * @param identifierSources * the sources to use for coming up with identifiers for new metrics etc. */ public PcpMmvWriter(String name, IdentifierSourceSet identifierSources) { this(mmvFileFromName(name), identifierSources, MMV_VERSION1); } /** * Creates a new PcpMmvWriter writing to the underlying file, which will be created + opened as a * memory-mapped file. This is the constructor most people should use, unless you have a really * good reason not to. It automatically handles all (incl. cross-platform) file location issues. * * @param name * logical name of instrumented subsystem (e.g. "hadoop") * @param identifierSources * the sources to use for coming up with identifiers for new metrics etc. * @param mmvVersion * the MMV version format to use */ public PcpMmvWriter(String name, IdentifierSourceSet identifierSources, MmvVersion mmvVersion) { this(mmvFileFromName(name), identifierSources, mmvVersion); } private static File mmvFileFromName(String name) { Preconditions.checkArgument(!name.contains(File.separator), "MMV logical name must not contain path separators"); File tmpDir = null; PcpConfig pcp = new PcpConfig(); String pcpTemp = pcp.getValue("PCP_TMP_DIR"); if (pcpTemp == null) { // PCP not installed, so create mapping in tmpdir tmpDir = new File(System.getProperty("java.io.tmpdir")); } else { tmpDir = new File(pcp.getRoot(), pcpTemp); } File mmvDir = new File(tmpDir, "mmv"); return new File(mmvDir, name); } public final void addMetric(MetricName name, Semantics semantics, Unit<?> unit, Object initialValue) { TypeHandler<?> handler = typeHandlers.get(initialValue.getClass()); if (handler == null) { throw new IllegalArgumentException("No default handler registered for type " + initialValue.getClass()); } addMetricInfo(name, semantics, unit, initialValue, handler); } public final <T> void addMetric(MetricName name, Semantics semantics, Unit<?> unit, T initialValue, TypeHandler<T> pcpType) { if (pcpType == null) { throw new IllegalArgumentException("PCP Type handler must not be null"); } addMetricInfo(name, semantics, unit, initialValue, pcpType); } /* * (non-Javadoc) * @see io.pcp.parfait.pcp.PcpWriter#registerType(java.lang.Class, * io.pcp.parfait.pcp.types.TypeHandler) */ public final <T> void registerType(Class<T> runtimeClass, TypeHandler<T> handler) { if (started) { // Can't add any more metrics anyway; harmless return; } typeHandlers.put(runtimeClass, handler); } /* * (non-Javadoc) * @see io.pcp.parfait.pcp.PcpWriter#updateMetric(java.lang.String, java.lang.Object) */ public final void updateMetric(MetricName name, Object value) { if (!started) { return; } PcpValueInfo info = metricData.get(name); if (info == null) { throw new IllegalArgumentException("Metric " + name + " was not added before initialising the writer"); } updateValue(info, value); } /* * (non-Javadoc) * @see io.pcp.parfait.pcp.PcpWriter#start() */ public final void start() throws IOException { initialiseOffsets(); dataFileBuffer = byteBufferFactory.build(getBufferLength()); synchronized (globalLock) { populateDataBuffer(dataFileBuffer, metricData.values()); preparePerMetricBufferSlices(); } started = true; } @Override public void reset() { started = false; metricData.clear(); } @Override public final void setInstanceDomainHelpText(String instanceDomain, String shortHelpText, String longHelpText) { InstanceDomain domain = getInstanceDomain(instanceDomain); domain.setHelpText(stringStore.createPcpString(shortHelpText), stringStore.createPcpString(longHelpText)); } @Override public final void setMetricHelpText(String metricName, String shortHelpText, String longHelpText) { PcpMetricInfo info = getMetricInfo(metricName); if (!info.hasHelpText()) { info.setHelpText(stringStore.createPcpString(shortHelpText), stringStore.createPcpString(longHelpText)); } } private void preparePerMetricBufferSlices() { for (PcpValueInfo info : metricData.values()) { TypeHandler<?> rawHandler = info.getTypeHandler(); int bufferPosition = rawHandler.requiresLargeStorage() ? info.getLargeValue().getOffset() : info.getOffset(); // need to position the original buffer first, as the sliced buffer starts from there dataFileBuffer.position(bufferPosition); ByteBuffer metricByteBufferSlice = dataFileBuffer.slice(); metricByteBufferSlice.limit(rawHandler.getDataLength()); perMetricByteBuffers.put(info, metricByteBufferSlice); metricByteBufferSlice.order(dataFileBuffer.order()); } } public void setClusterIdentifier(int clusterIdentifier) { Preconditions.checkArgument((clusterIdentifier & 0xFFFFF000) == 0, "ClusterIdentifier can only be a 12bit value"); this.clusterIdentifier = clusterIdentifier; } public void setProcessIdentifier(int pid) { Preconditions.checkArgument(pid > 0, "ProcessIdentifier can only be a positive integer"); this.processIdentifier = pid; } public void setPerMetricLock(boolean usePerMetricLock) { Preconditions.checkState(!started, "Cannot change use of perMetricLock when started"); this.usePerMetricLock = usePerMetricLock; } public void setFlags(Set<MmvFlag> flags) { this.flags = EnumSet.copyOf(flags); } private synchronized void addMetricInfo(MetricName name, Semantics semantics, Unit<?> unit, Object initialValue, TypeHandler<?> pcpType) { if (metricData.containsKey(name)) { throw new IllegalArgumentException("Metric " + name + " has already been added to writer"); } metricNameValidator.validateNameConstraints(name); PcpMetricInfo metricInfo = getMetricInfo(name.getMetric()); InstanceDomain domain = null; Instance instance = null; if (name.hasInstance()) { domain = getInstanceDomain(name.getInstanceDomainTag()); instance = domain.getInstance(name.getInstance()); metricInfo.setInstanceDomain(domain); } metricInfo.setTypeHandler(pcpType); metricInfo.setUnit(unit); metricInfo.setSemantics(semantics); PcpValueInfo info = new PcpValueInfo(name, metricInfo, instance, initialValue, stringStore); metricData.put(name, info); } private PcpMetricInfo getMetricInfo(String name) { return metricInfoStore.byName(name); } private InstanceDomain getInstanceDomain(String name) { return instanceDomainStore.byName(name); } private Collection<PcpMetricInfo> getMetricInfos() { return metricInfoStore.all(); } private Collection<InstanceDomain> getInstanceDomains() { return instanceDomainStore.all(); } private Collection<Instance> getInstances() { Collection<Instance> instances = new ArrayList<Instance>(); for (InstanceDomain domain : instanceDomainStore.all()) { instances.addAll(domain.getInstances()); } return instances; } private Collection<PcpValueInfo> getValueInfos() { return metricData.values(); } private Collection<PcpString> getStrings() { return stringStore.getStrings(); } @SuppressWarnings("unchecked") private void updateValue(PcpValueInfo info, Object value) { @SuppressWarnings("rawtypes") TypeHandler rawHandler = info.getTypeHandler(); if (usePerMetricLock) { writeValueWithLockPerMetric(info, value, rawHandler); } else { writeValueWithGlobalLock(info, value, rawHandler); } } private void writeValueWithLockPerMetric(PcpValueInfo info, Object value, TypeHandler rawHandler) { ByteBuffer perMetricByteBuffer = perMetricByteBuffers.get(info); synchronized (perMetricByteBuffer) { perMetricByteBuffer.position(0); rawHandler.putBytes(perMetricByteBuffer, value); } } private void writeValueWithGlobalLock(PcpValueInfo info, Object value, TypeHandler rawHandler) { synchronized (globalLock) { dataFileBuffer.position( rawHandler.requiresLargeStorage() ? info.getLargeValue().getOffset() : info.getOffset()); rawHandler.putBytes(dataFileBuffer, value); } } private void populateDataBuffer(ByteBuffer dataFileBuffer, Collection<PcpValueInfo> valueInfos) throws IOException { // Automatically cleanup the file if this is a mapping where we // mandate PID checking from the MMV PMDA (MMV_FLAG_PROCESS) and // we were able to stash a path name earlier if (file != null && flags.contains(MmvFlag.MMV_FLAG_PROCESS)) { file.deleteOnExit(); } dataFileBuffer.position(0); dataFileBuffer.put(TAG); dataFileBuffer.putInt(mmvVersion.getVersion()); long generation = System.currentTimeMillis() / 1000; dataFileBuffer.putLong(generation); int gen2Offset = dataFileBuffer.position(); // Generation 2 will be filled in later, once the file's ready dataFileBuffer.putLong(0); // 2 TOC blocks, 3 if there are instances dataFileBuffer.putInt(tocCount()); dataFileBuffer.putInt(getFlagMask()); dataFileBuffer.putInt(getProcessIdentifier()); dataFileBuffer.putInt(clusterIdentifier); Collection<? extends MmvWritable> instanceDomains = getInstanceDomains(); Collection<? extends MmvWritable> instances = getInstances(); Collection<? extends MmvWritable> metrics = getMetricInfos(); Collection<? extends MmvWritable> strings = getStrings(); int tocBlockIndex = 0; if (!instanceDomains.isEmpty()) { dataFileBuffer.position(getTocOffset(tocBlockIndex++)); writeToc(dataFileBuffer, TocType.INSTANCE_DOMAINS, instanceDomains.size(), instanceDomains.iterator().next().getOffset()); } if (!instances.isEmpty()) { dataFileBuffer.position(getTocOffset(tocBlockIndex++)); writeToc(dataFileBuffer, TocType.INSTANCES, instances.size(), instances.iterator().next().getOffset()); } dataFileBuffer.position(getTocOffset(tocBlockIndex++)); int metricsFirstEntryOffset = metrics.isEmpty() ? 0 : metrics.iterator().next().getOffset(); int valuesFirstEntryOffset = valueInfos.isEmpty() ? 0 : valueInfos.iterator().next().getOffset(); writeToc(dataFileBuffer, TocType.METRICS, metrics.size(), metricsFirstEntryOffset); dataFileBuffer.position(getTocOffset(tocBlockIndex++)); writeToc(dataFileBuffer, TocType.VALUES, valueInfos.size(), valuesFirstEntryOffset); if (!getStrings().isEmpty()) { dataFileBuffer.position(getTocOffset(tocBlockIndex++)); writeToc(dataFileBuffer, TocType.STRINGS, strings.size(), strings.iterator().next().getOffset()); } for (MmvWritable instanceDomain : instanceDomains) { instanceDomain.writeToMmv(dataFileBuffer); } for (MmvWritable info : metrics) { info.writeToMmv(dataFileBuffer); } for (MmvWritable info : valueInfos) { info.writeToMmv(dataFileBuffer); } for (MmvWritable string : strings) { string.writeToMmv(dataFileBuffer); } // Once it's set up, let the agent know dataFileBuffer.position(gen2Offset); dataFileBuffer.putLong(generation); } private int getFlagMask() { int flagMask = 0; for (MmvFlag flag : flags) { flagMask |= flag.bitmask; } return flagMask; } private int getBufferLength() { return HEADER_LENGTH + (TOC_LENGTH * tocCount()) + getByteSizeTotalFor(getInstanceDomains()) + getByteSizeTotalFor(getInstances()) + getByteSizeTotalFor(getMetricInfos()) + getByteSizeTotalFor(getValueInfos()) + getByteSizeTotalFor(getStrings()); } private int getByteSizeTotalFor(Collection<? extends PcpOffset> offsettables) { int bytes = 0; for (PcpOffset offsetable : offsettables) { bytes += offsetable.byteSize(); } return bytes; } /** * Writes out a PCP MMV table-of-contents block. * * @param dataFileBuffer * ByteBuffer positioned at the correct offset in the file for the block * @param tocType * the type of TOC block to write * @param entryCount * the number of blocks of type tocType to be found in the file * @param firstEntryOffset * the offset of the first tocType block, relative to start of the file */ private void writeToc(ByteBuffer dataFileBuffer, TocType tocType, int entryCount, int firstEntryOffset) { dataFileBuffer.putInt(tocType.identifier); dataFileBuffer.putInt(entryCount); dataFileBuffer.putLong(firstEntryOffset); } /** * Calculates the file offset of a given PCP MMV TOC block * * @param tocIndex * the 0-based index of the TOC block to be written * @return the file offset used to store that TOC block (32-bit regardless of architecture) */ private int getTocOffset(int tocIndex) { return HEADER_LENGTH + (tocIndex * TOC_LENGTH); } private synchronized void initialiseOffsets() { int nextOffset = HEADER_LENGTH + (TOC_LENGTH * tocCount()); nextOffset = initializeOffsets(getInstanceDomains(), nextOffset); nextOffset = initializeOffsets(getInstances(), nextOffset); nextOffset = initializeOffsets(getMetricInfos(), nextOffset); nextOffset = initializeOffsets(getValueInfos(), nextOffset); initializeOffsets(getStrings(), nextOffset); } private int initializeOffsets(Collection<? extends PcpOffset> offsettables, int nextOffset) { for (PcpOffset offsettable : offsettables) { offsettable.setOffset(nextOffset); nextOffset += offsettable.byteSize(); } return nextOffset; } private int tocCount() { int tocCount = 2; // metrics + values if (!getInstances().isEmpty()) { tocCount += 2; } if (!getStrings().isEmpty()) { tocCount++; } return tocCount; } /** * @return the PID of the current running Java Process, or a proxied PID if requested. */ private int getProcessIdentifier() { if (processIdentifier == 0) { String processName = ManagementFactory.getRuntimeMXBean().getName(); processIdentifier = Integer.valueOf(processName.split("@")[0]); } return processIdentifier; } public static void main(String[] args) throws IOException { PcpMmvWriter bridge; if (args.length == 0) { // use $PCP_PMDAS_DIR/mmv/mmvdump (no args) as diagnostic tool bridge = new PcpMmvWriter("test", IdentifierSourceSet.DEFAULT_SET); } else { bridge = new PcpMmvWriter(new File(args[0]), IdentifierSourceSet.DEFAULT_SET); } // Automatically uses default int handler bridge.addMetric(MetricName.parse("sheep[baabaablack].bagsfull.count"), Semantics.COUNTER, ONE.multiply(1000), 3); // Automatically uses default boolean-to-int handler bridge.addMetric(MetricName.parse("sheep[baabaablack].bagsfull.haveany"), Semantics.INSTANT, null, new AtomicBoolean(true)); bridge.addMetric(MetricName.parse("sheep[limpy].bagsfull.haveany"), Semantics.INSTANT, null, new AtomicBoolean(false)); // Automatically uses default long handler bridge.addMetric(MetricName.parse("sheep[insomniac].jumps"), Semantics.COUNTER, ONE, 12345678901234L); // Automatically uses default double handler bridge.addMetric(MetricName.parse("sheep[limpy].legs.available"), Semantics.DISCRETE, ONE, 0.75); // Uses this class' custom String handler bridge.addMetric(MetricName.parse("sheep[limpy].jumpitem"), Semantics.DISCRETE, null, "fence"); // addMetric(GregorianCalendar) would fail, as there's no handler registered by default for // GregorianCalendars; use a custom one which puts the year as an int bridge.addMetric(MetricName.parse("sheep[insomniac].lastjumped"), Semantics.INSTANT, null, new GregorianCalendar(), new AbstractTypeHandler<GregorianCalendar>(MmvMetricType.I32, 4) { public void putBytes(ByteBuffer buffer, GregorianCalendar value) { buffer.putInt(value.get(GregorianCalendar.YEAR)); } }); // addMetric(Date) would fail, as there's no handler registered; register one for all date // types from now on bridge.registerType(Date.class, new AbstractTypeHandler<Date>(MmvMetricType.I64, 8) { public void putBytes(ByteBuffer buffer, Date value) { buffer.putLong(value.getTime()); } }); // These will both use the handler we just registered bridge.addMetric(MetricName.parse("cow.how.now"), Semantics.INSTANT, null, new Date()); bridge.addMetric(MetricName.parse("cow.how.then"), Semantics.INSTANT, null, new GregorianCalendar(1990, 1, 1, 12, 34, 56).getTime()); // Uses units bridge.addMetric(MetricName.parse("cow.bytes.total"), Semantics.COUNTER, BYTE, 10000001); bridge.addMetric(MetricName.parse("cow.bytes.rate"), Semantics.INSTANT, BYTE.multiply(1024).divide(SECOND), new Date()); bridge.addMetric(MetricName.parse("cow.bytes.chewtime"), Semantics.INSTANT, HOUR.divide(BYTE), 7); bridge.addMetric(MetricName.parse("cow.bytes.jawmotion"), Semantics.INSTANT, KILO(HERTZ), 0.5); // Set up some help text bridge.setInstanceDomainHelpText("sheep", "sheep in the paddock", "List of all the sheep in the paddock. Includes 'baabaablack', 'insomniac' (who likes to jump fences), and 'limpy' the three-legged wonder sheep."); bridge.setMetricHelpText("sheep.jumps", "# of jumps done", "Number of times the sheep has jumped over its jumpitem"); // All the metrics are added; write the file bridge.start(); // Metrics are visible to the agent from this point on // Sold a bag! Better update the count bridge.updateMetric(MetricName.parse("sheep[baabaablack].bagsfull.count"), 2); // The fence broke! Need something new to jump over bridge.updateMetric(MetricName.parse("sheep[limpy].jumpitem"), "Honda Civic"); // Values will be reflected in the agent immediately } static abstract class Store<T extends PcpId> { private final Map<String, T> byName = new LinkedHashMap<String, T>(); private final Map<Integer, T> byId = new LinkedHashMap<Integer, T>(); final IdentifierSource identifierSource; Store(IdentifierSource source) { this.identifierSource = source; } synchronized T byName(String name) { T value = byName.get(name); if (value == null) { value = newInstance(name, byId.keySet()); byName.put(name, value); byId.put(value.getId(), value); } return value; } synchronized Collection<T> all() { return byName.values(); } protected abstract T newInstance(String name, Set<Integer> usedIds); synchronized int size() { return byName.size(); } } @Override public String toString() { return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) .append("byteBufferFactory", this.byteBufferFactory).toString(); } }