From 90d11dc92b6b6fcbdef7ed50214f4ed3d098750f Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Sun, 9 Jul 2023 01:40:55 -0400 Subject: [PATCH 01/15] collect data in callbacks --- .../jmxmetrics/GroovyMetricEnvironment.java | 221 ++++++++++++------ .../jmxmetrics/InstrumentHelper.groovy | 120 ++++------ .../contrib/jmxmetrics/MBeanHelper.groovy | 2 +- .../contrib/jmxmetrics/OtelHelper.groovy | 12 +- tmp/META-INF/LICENSE.name3/LICENSE.renamed | 202 ++++++++++++++++ tmp/META-INF/LICENSE.renamed | 202 ++++++++++++++++ tmp/session.properties | 5 + tmp/wildfly.properties | 5 + 8 files changed, 619 insertions(+), 150 deletions(-) create mode 100644 tmp/META-INF/LICENSE.name3/LICENSE.renamed create mode 100644 tmp/META-INF/LICENSE.renamed create mode 100644 tmp/session.properties create mode 100644 tmp/wildfly.properties diff --git a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/GroovyMetricEnvironment.java b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/GroovyMetricEnvironment.java index 0e868e9d3..f9698d604 100644 --- a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/GroovyMetricEnvironment.java +++ b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/GroovyMetricEnvironment.java @@ -26,9 +26,11 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; +import java.util.logging.Logger; import javax.annotation.Nullable; public class GroovyMetricEnvironment { + private static final Logger logger = Logger.getLogger(GroovyMetricEnvironment.class.getName()); private final SdkMeterProvider meterProvider; private final Meter meter; @@ -42,6 +44,7 @@ public class GroovyMetricEnvironment { longUpdaterRegistry = new ConcurrentHashMap<>(); private final Map>> doubleUpdaterRegistry = new ConcurrentHashMap<>(); + private final Map instrumentOnceRegistry = new ConcurrentHashMap<>(); /** * A central context for creating and exporting metrics, to be used by groovy scripts via {@link @@ -225,13 +228,30 @@ public void registerDoubleValueCallback( final String description, final String unit, final Consumer updater) { - meter - .gaugeBuilder(name) - .setDescription(description) - .setUnit(unit) - .buildWithCallback( - proxiedDoubleObserver( - name, description, unit, InstrumentType.OBSERVABLE_GAUGE, updater)); + int descriptorHash = + InstrumentDescriptor.create( + name, + description, + unit, + InstrumentType.OBSERVABLE_GAUGE, + InstrumentValueType.DOUBLE) + .hashCode(); + + // Only build the instrument if it isn't already in the registry + Boolean existingValue = instrumentOnceRegistry.putIfAbsent(descriptorHash, true); + if (existingValue == null) { + meter + .gaugeBuilder(name) + .setDescription(description) + .setUnit(unit) + .buildWithCallback(proxiedDoubleObserver(descriptorHash, updater)); + } else { + // If the instrument has already been built with the appropriate proxied observer, + // update the registry so that the callback has the appropriate updater function + AtomicReference> existingUpdater = + doubleUpdaterRegistry.get(descriptorHash); + existingUpdater.set(updater); + } } /** @@ -247,13 +267,27 @@ public void registerLongValueCallback( final String description, final String unit, final Consumer updater) { - meter - .gaugeBuilder(name) - .ofLongs() - .setDescription(description) - .setUnit(unit) - .buildWithCallback( - proxiedLongObserver(name, description, unit, InstrumentType.OBSERVABLE_GAUGE, updater)); + int descriptorHash = + InstrumentDescriptor.create( + name, description, unit, InstrumentType.OBSERVABLE_GAUGE, InstrumentValueType.LONG) + .hashCode(); + + // Only build the instrument if it isn't already in the registry + Boolean existingValue = instrumentOnceRegistry.putIfAbsent(descriptorHash, true); + if (existingValue == null) { + meter + .gaugeBuilder(name) + .ofLongs() + .setDescription(description) + .setUnit(unit) + .buildWithCallback(proxiedLongObserver(descriptorHash, updater)); + } else { + // If the instrument has already been built with the appropriate proxied observer, + // update the registry so that the callback has the appropriate updater function + AtomicReference> existingUpdater = + longUpdaterRegistry.get(descriptorHash); + existingUpdater.set(updater); + } } /** @@ -269,14 +303,31 @@ public void registerDoubleCounterCallback( final String description, final String unit, final Consumer updater) { - meter - .counterBuilder(name) - .ofDoubles() - .setDescription(description) - .setUnit(unit) - .buildWithCallback( - proxiedDoubleObserver( - name, description, unit, InstrumentType.OBSERVABLE_COUNTER, updater)); + int descriptorHash = + InstrumentDescriptor.create( + name, + description, + unit, + InstrumentType.OBSERVABLE_COUNTER, + InstrumentValueType.DOUBLE) + .hashCode(); + + // Only build the instrument if it isn't already in the registry + Boolean existingValue = instrumentOnceRegistry.putIfAbsent(descriptorHash, true); + if (existingValue == null) { + meter + .counterBuilder(name) + .ofDoubles() + .setDescription(description) + .setUnit(unit) + .buildWithCallback(proxiedDoubleObserver(descriptorHash, updater)); + } else { + // If the instrument has already been built with the appropriate proxied observer, + // update the registry so that the callback has the appropriate updater function + AtomicReference> existingUpdater = + doubleUpdaterRegistry.get(descriptorHash); + existingUpdater.set(updater); + } } /** @@ -292,13 +343,30 @@ public void registerLongCounterCallback( final String description, final String unit, final Consumer updater) { - meter - .counterBuilder(name) - .setDescription(description) - .setUnit(unit) - .buildWithCallback( - proxiedLongObserver( - name, description, unit, InstrumentType.OBSERVABLE_COUNTER, updater)); + int descriptorHash = + InstrumentDescriptor.create( + name, + description, + unit, + InstrumentType.OBSERVABLE_COUNTER, + InstrumentValueType.LONG) + .hashCode(); + + // Only build the instrument if it isn't already in the registry + Boolean existingValue = instrumentOnceRegistry.putIfAbsent(descriptorHash, true); + if (existingValue == null) { + meter + .counterBuilder(name) + .setDescription(description) + .setUnit(unit) + .buildWithCallback(proxiedLongObserver(descriptorHash, updater)); + } else { + // If the instrument has already been built with the appropriate proxied observer, + // update the registry so that the callback has the appropriate updater function + AtomicReference> existingUpdater = + longUpdaterRegistry.get(descriptorHash); + existingUpdater.set(updater); + } } /** @@ -314,14 +382,31 @@ public void registerDoubleUpDownCounterCallback( final String description, final String unit, final Consumer updater) { - meter - .upDownCounterBuilder(name) - .ofDoubles() - .setDescription(description) - .setUnit(unit) - .buildWithCallback( - proxiedDoubleObserver( - name, description, unit, InstrumentType.OBSERVABLE_UP_DOWN_COUNTER, updater)); + int descriptorHash = + InstrumentDescriptor.create( + name, + description, + unit, + InstrumentType.OBSERVABLE_UP_DOWN_COUNTER, + InstrumentValueType.DOUBLE) + .hashCode(); + + // Only build the instrument if it isn't already in the registry + Boolean existingValue = instrumentOnceRegistry.putIfAbsent(descriptorHash, true); + if (existingValue == null) { + meter + .upDownCounterBuilder(name) + .ofDoubles() + .setDescription(description) + .setUnit(unit) + .buildWithCallback(proxiedDoubleObserver(descriptorHash, updater)); + } else { + // If the instrument has already been built with the appropriate proxied observer, + // update the registry so that the callback has the appropriate updater function + AtomicReference> existingUpdater = + doubleUpdaterRegistry.get(descriptorHash); + existingUpdater.set(updater); + } } /** @@ -337,49 +422,53 @@ public void registerLongUpDownCounterCallback( final String description, final String unit, final Consumer updater) { - meter - .upDownCounterBuilder(name) - .setDescription(description) - .setUnit(unit) - .buildWithCallback( - proxiedLongObserver( - name, description, unit, InstrumentType.OBSERVABLE_UP_DOWN_COUNTER, updater)); + int descriptorHash = + InstrumentDescriptor.create( + name, + description, + unit, + InstrumentType.OBSERVABLE_UP_DOWN_COUNTER, + InstrumentValueType.LONG) + .hashCode(); + + // Only build the instrument if it isn't already in the registry + Boolean existingValue = instrumentOnceRegistry.putIfAbsent(descriptorHash, true); + if (existingValue == null) { + meter + .upDownCounterBuilder(name) + .setDescription(description) + .setUnit(unit) + .buildWithCallback(proxiedLongObserver(descriptorHash, updater)); + } else { + // If the instrument has already been built with the appropriate proxied observer, + // update the registry so that the callback has the appropriate updater function + AtomicReference> existingUpdater = + longUpdaterRegistry.get(descriptorHash); + existingUpdater.set(updater); + } } private Consumer proxiedDoubleObserver( - final String name, - final String description, - final String unit, - final InstrumentType instrumentType, - final Consumer updater) { - InstrumentDescriptor descriptor = - InstrumentDescriptor.create( - name, description, unit, instrumentType, InstrumentValueType.DOUBLE); - doubleUpdaterRegistry.putIfAbsent(descriptor.hashCode(), new AtomicReference<>()); + final int descriptorHash, final Consumer updater) { + doubleUpdaterRegistry.putIfAbsent(descriptorHash, new AtomicReference<>()); AtomicReference> existingUpdater = - doubleUpdaterRegistry.get(descriptor.hashCode()); + doubleUpdaterRegistry.get(descriptorHash); existingUpdater.set(updater); return doubleResult -> { - Consumer existing = existingUpdater.get(); + Consumer existing = + doubleUpdaterRegistry.get(descriptorHash).get(); existing.accept(doubleResult); }; } private Consumer proxiedLongObserver( - final String name, - final String description, - final String unit, - final InstrumentType instrumentType, - final Consumer updater) { - InstrumentDescriptor descriptor = - InstrumentDescriptor.create( - name, description, unit, instrumentType, InstrumentValueType.LONG); - longUpdaterRegistry.putIfAbsent(descriptor.hashCode(), new AtomicReference<>()); + final int descriptorHash, final Consumer updater) { + longUpdaterRegistry.putIfAbsent(descriptorHash, new AtomicReference<>()); AtomicReference> existingUpdater = - longUpdaterRegistry.get(descriptor.hashCode()); + longUpdaterRegistry.get(descriptorHash); existingUpdater.set(updater); return longResult -> { - Consumer existing = existingUpdater.get(); + Consumer existing = longUpdaterRegistry.get(descriptorHash).get(); existing.accept(longResult); }; } diff --git a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelper.groovy b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelper.groovy index faf0e2bd9..521027de1 100644 --- a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelper.groovy +++ b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelper.groovy @@ -7,6 +7,8 @@ package io.opentelemetry.contrib.jmxmetrics import groovy.jmx.GroovyMBean import groovy.transform.PackageScope + +import javax.management.AttributeNotFoundException import java.util.logging.Logger import javax.management.openmbean.CompositeData @@ -69,70 +71,13 @@ class InstrumentHelper { } void update() { - // Tuples of the form (mbean, attribute, value) - def values = mBeanHelper.getAttributes(mBeanAttributes.keySet()) - - // If there are no tuples with non-null value, return early - if (values.find {it.getV3() != null } == null) { - logger.warning("No valid value(s) for ${instrumentName} - ${mBeanHelper}.${mBeanAttributes.keySet().join(",")}") - return - } - - // Observer instruments need to have a single updater set at build time, so pool all - // update operations in a list of closures per instrument to be executed after all values - // are established, potentially as a single updater. This is done because a single MBeanHelper - // can represent multiple MBeans (each with different values for an attribute) and the labelFuncs - // will create multiple datapoints from the same instrument identifiers. - def tupleToUpdates = [:] // tuple is of form (instrument, instrumentName, description, unit) - - values.each { collectedValue -> - def mbean = collectedValue.getV1() - def attribute = collectedValue.getV2() - def value = collectedValue.getV3() - if (value instanceof CompositeData) { - value.getCompositeType().keySet().each { key -> - def val = value.get(key) - def updatedInstrumentName = "${instrumentName}.${key}" - def labels = getLabels(mbean, labelFuncs, mBeanAttributes[attribute]) - def tuple = new Tuple(instrument, updatedInstrumentName, description, unit) - logger.fine("Recording ${updatedInstrumentName} - ${instrument.method} w/ ${val} - ${labels}") - if (!tupleToUpdates.containsKey(tuple)) { - tupleToUpdates[tuple] = [] - } - tupleToUpdates[tuple].add(prepareUpdateClosure(instrument, val, labels)) - } - } else if (value != null) { - def labels = getLabels(mbean, labelFuncs, mBeanAttributes[attribute]) - def tuple = new Tuple(instrument, instrumentName, description, unit) - logger.fine("Recording ${instrumentName} - ${instrument.method} w/ ${value} - ${labels}") - if (!tupleToUpdates.containsKey(tuple)) { - tupleToUpdates[tuple] = [] - } - tupleToUpdates[tuple].add(prepareUpdateClosure(instrument, value, labels)) - } - } - - tupleToUpdates.each {tuple, updateClosures -> - def instrument = tuple.getAt(0) - def instrumentName = tuple.getAt(1) - def description = tuple.getAt(2) - def unit = tuple.getAt(3) - - if (instrumentIsDoubleObserver(instrument) || instrumentIsLongObserver(instrument)) { - // Though the instrument updater is only set at build time, - // our GroovyMetricEnvironment helpers ensure the updater - // uses the Closure specified here. - instrument(instrumentName, description, unit, { result -> - updateClosures.each { update -> - update(result) - } - }) - } else { - def inst = instrument(instrumentName, description, unit) - updateClosures.each { - it(inst) - } - } + def updateClosure = prepareUpdateClosure(instrument, mBeanHelper.getMBeans(), mBeanAttributes.keySet()) + if (instrumentIsDoubleObserver(instrument) || instrumentIsLongObserver(instrument)) { + instrument(instrumentName, description, unit, { result -> + updateClosure(result) + }) + } else { + updateClosure(instrument(instrumentName, description, unit)) } } @@ -147,22 +92,43 @@ class InstrumentHelper { return labels } - private static Closure prepareUpdateClosure(inst, value, labels) { - def labelMap = GroovyMetricEnvironment.mapToAttributes(labels) - if (instrumentIsLongObserver(inst)) { - return { result -> - result.record((long) value, labelMap) + private Closure prepareUpdateClosure(inst, List mbeans, attributes) { + return { result -> + [mbeans, attributes].combinations().each { pair -> + def (mbean, attribute) = pair + try { + def value = mbean.getProperty(attribute) + if (value instanceof CompositeData) { + value.getCompositeType().keySet().each { key -> + def val = value.get(key) + def updatedInstrumentName = "${instrumentName}.${key}" + def labels = getLabels(mbean, labelFuncs, mBeanAttributes[attribute]) + logger.fine("Recording ${updatedInstrumentName} - ${instrument.method} w/ ${val} - ${labels}") + recordDataPoint(inst, result, val, GroovyMetricEnvironment.mapToAttributes(labels)) } - } else if (instrumentIsDoubleObserver(inst)) { - return { result -> - result.record((double) value, labelMap) - } - } else if (instrumentIsCounter(inst)) { - return { i -> i.add(value, labelMap) } - } else { - return { i -> i.record(value, labelMap) } + } else if (value != null) { + def labels = getLabels(mbean, labelFuncs, mBeanAttributes[attribute]) + logger.fine("Recording ${instrumentName} - ${instrument.method} w/ ${value} - ${labels}") + recordDataPoint(inst, result, value, GroovyMetricEnvironment.mapToAttributes(labels)) + } + } catch (AttributeNotFoundException e ) { + logger.info("Expected attribute ${attribute} not found in mbean ${mbean.name()}") } + } + } + } + + private static void recordDataPoint(inst, result, value, labelMap) { + if (instrumentIsLongObserver(inst)) { + result.record((long) value, labelMap) + } else if (instrumentIsDoubleObserver(inst)) { + result.record((double) value, labelMap) + } else if (instrumentIsCounter(inst)) { + result.add(value, labelMap) + } else { + result.record(value, labelMap) } + } @PackageScope static boolean instrumentIsDoubleObserver(inst) { return [ diff --git a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/MBeanHelper.groovy b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/MBeanHelper.groovy index 42fa92eca..b840ba4e9 100644 --- a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/MBeanHelper.groovy +++ b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/MBeanHelper.groovy @@ -78,7 +78,7 @@ class MBeanHelper { logger.warning("No active MBeans. Be sure to fetch() before updating any applicable instruments.") return [] } - return mbeans + return isSingle ? [mbeans[0]]: mbeans } @PackageScope List getAttribute(String attribute) { diff --git a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/OtelHelper.groovy b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/OtelHelper.groovy index a2233073b..db8223929 100644 --- a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/OtelHelper.groovy +++ b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/OtelHelper.groovy @@ -253,15 +253,15 @@ class OtelHelper { doubleValueCallback(name, '', updater) } - void longValueCallback(String name, String description, String unit, Consumer updater) { - groovyMetricEnvironment.registerLongValueCallback(name, description, unit, updater) + ObservableLongMeasurement longValueCallback(String name, String description, String unit, Consumer updater) { + return groovyMetricEnvironment.registerLongValueCallback(name, description, unit, updater) } - void longValueCallback(String name, String description, Consumer updater) { - longValueCallback(name, description, SCALAR, updater) + ObservableLongMeasurement longValueCallback(String name, String description, Consumer updater) { + return longValueCallback(name, description, SCALAR, updater) } - void longValueCallback(String name, Consumer updater) { - longValueCallback(name, '', updater) + ObservableLongMeasurement longValueCallback(String name, Consumer updater) { + return longValueCallback(name, '', updater) } } diff --git a/tmp/META-INF/LICENSE.name3/LICENSE.renamed b/tmp/META-INF/LICENSE.name3/LICENSE.renamed new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/tmp/META-INF/LICENSE.name3/LICENSE.renamed @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/tmp/META-INF/LICENSE.renamed b/tmp/META-INF/LICENSE.renamed new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/tmp/META-INF/LICENSE.renamed @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/tmp/session.properties b/tmp/session.properties new file mode 100644 index 000000000..969103480 --- /dev/null +++ b/tmp/session.properties @@ -0,0 +1,5 @@ +otel.jmx.service.url = service:jmx:rmi:///jndi/rmi://localhost:9999/jmxrmi +otel.jmx.target.system = kafka +otel.jmx.interval.milliseconds = 15000 + +otel.metrics.exporter = logging diff --git a/tmp/wildfly.properties b/tmp/wildfly.properties new file mode 100644 index 000000000..6bf3b1c75 --- /dev/null +++ b/tmp/wildfly.properties @@ -0,0 +1,5 @@ +otel.jmx.service.url = service:jmx:remote+http://192.168.50.89:9990 +otel.jmx.interval.milliseconds = 10000 +otel.jmx.target.system = wildfly +otel.jmx.username = medora +otel.jmx.password = Password1! From 10e5d8ca36133e3388c8966d30331565da9e930a Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Tue, 11 Jul 2023 00:16:46 -0400 Subject: [PATCH 02/15] support observable/batchcallback to properly support composite value types --- .../jmxmetrics/AbstractIntegrationTest.java | 21 +++ .../JvmTargetSystemIntegrationTest.java | 4 + .../integrationTest/resources/script.groovy | 16 +- .../jmxmetrics/GroovyMetricEnvironment.java | 177 +++++++++++++----- .../jmxmetrics/InstrumentHelper.groovy | 85 +++++++-- .../contrib/jmxmetrics/OtelHelper.groovy | 74 ++++---- .../jmxmetrics/InstrumenterHelperTest.java | 17 +- 7 files changed, 274 insertions(+), 120 deletions(-) diff --git a/jmx-metrics/src/integrationTest/java/io/opentelemetry/contrib/jmxmetrics/AbstractIntegrationTest.java b/jmx-metrics/src/integrationTest/java/io/opentelemetry/contrib/jmxmetrics/AbstractIntegrationTest.java index 1d734aed7..dedee72e2 100644 --- a/jmx-metrics/src/integrationTest/java/io/opentelemetry/contrib/jmxmetrics/AbstractIntegrationTest.java +++ b/jmx-metrics/src/integrationTest/java/io/opentelemetry/contrib/jmxmetrics/AbstractIntegrationTest.java @@ -164,6 +164,27 @@ protected final void waitAndAssertMetrics(Consumer... assertions) { waitAndAssertMetrics(Arrays.asList(assertions)); } + protected final void waitAndAssertNoMetrics() { + await() + .atMost(Duration.ofSeconds(30)) + .untilAsserted( + () -> { + List metrics = + otlpServer.getMetrics().stream() + .map(ExportMetricsServiceRequest::getResourceMetricsList) + .flatMap(rm -> rm.stream().map(ResourceMetrics::getScopeMetricsList)) + .flatMap(Collection::stream) + .filter( + sm -> + sm.getScope().getName().equals("io.opentelemetry.contrib.jmxmetrics") + && sm.getScope().getVersion().equals(expectedMeterVersion())) + .flatMap(sm -> sm.getMetricsList().stream()) + .collect(Collectors.toList()); + + assertThat(metrics).isEmpty(); + }); + } + protected void assertGauge(Metric metric, String name, String description, String unit) { assertThat(metric.getName()).isEqualTo(name); assertThat(metric.getDescription()).isEqualTo(description); diff --git a/jmx-metrics/src/integrationTest/java/io/opentelemetry/contrib/jmxmetrics/target_systems/JvmTargetSystemIntegrationTest.java b/jmx-metrics/src/integrationTest/java/io/opentelemetry/contrib/jmxmetrics/target_systems/JvmTargetSystemIntegrationTest.java index 91de5de0d..4a9197169 100644 --- a/jmx-metrics/src/integrationTest/java/io/opentelemetry/contrib/jmxmetrics/target_systems/JvmTargetSystemIntegrationTest.java +++ b/jmx-metrics/src/integrationTest/java/io/opentelemetry/contrib/jmxmetrics/target_systems/JvmTargetSystemIntegrationTest.java @@ -68,5 +68,9 @@ void endToEnd() { assertTypedGauge( metric, "jvm.memory.pool.used", "current memory pool usage", "by", gcLabels), metric -> assertGauge(metric, "jvm.threads.count", "number of threads", "1")); + + cassandra.stop(); + + waitAndAssertNoMetrics(); } } diff --git a/jmx-metrics/src/integrationTest/resources/script.groovy b/jmx-metrics/src/integrationTest/resources/script.groovy index 0592ad3e5..0ed6c493f 100644 --- a/jmx-metrics/src/integrationTest/resources/script.groovy +++ b/jmx-metrics/src/integrationTest/resources/script.groovy @@ -17,11 +17,13 @@ import io.opentelemetry.api.common.Attributes def loadMatches = otel.queryJmx("org.apache.cassandra.metrics:type=Storage,name=Load") -def load = loadMatches.first() +if (!loadMatches.isEmpty()) { + def load = loadMatches.first() -def lvr = otel.longHistogram( - "cassandra.storage.load", - "Size, in bytes, of the on disk data size this node manages", - "By" - ) -lvr.record(load.Count, Attributes.builder().put("myKey", "myVal").build()) + def lvr = otel.longHistogram( + "cassandra.storage.load", + "Size, in bytes, of the on disk data size this node manages", + "By" + ) + lvr.record(load.Count, Attributes.builder().put("myKey", "myVal").build()) +} diff --git a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/GroovyMetricEnvironment.java b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/GroovyMetricEnvironment.java index f9698d604..bce84444a 100644 --- a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/GroovyMetricEnvironment.java +++ b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/GroovyMetricEnvironment.java @@ -5,6 +5,7 @@ package io.opentelemetry.contrib.jmxmetrics; +import groovy.lang.Closure; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.api.metrics.DoubleCounter; @@ -16,6 +17,7 @@ import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.api.metrics.ObservableDoubleMeasurement; import io.opentelemetry.api.metrics.ObservableLongMeasurement; +import io.opentelemetry.api.metrics.ObservableMeasurement; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; import io.opentelemetry.sdk.metrics.InstrumentType; import io.opentelemetry.sdk.metrics.InstrumentValueType; @@ -44,7 +46,10 @@ public class GroovyMetricEnvironment { longUpdaterRegistry = new ConcurrentHashMap<>(); private final Map>> doubleUpdaterRegistry = new ConcurrentHashMap<>(); - private final Map instrumentOnceRegistry = new ConcurrentHashMap<>(); + private final Map>> batchUpdaterRegistry = + new ConcurrentHashMap<>(); + private final Map instrumentOnceRegistry = + new ConcurrentHashMap<>(); /** * A central context for creating and exporting metrics, to be used by groovy scripts via {@link @@ -222,8 +227,9 @@ public LongHistogram getLongHistogram( * @param description metric description * @param unit - metric unit * @param updater - the value updater + * @return the ObservableDoubleMeasurement for the gauge */ - public void registerDoubleValueCallback( + public ObservableDoubleMeasurement registerDoubleValueCallback( final String name, final String description, final String unit, @@ -238,20 +244,25 @@ public void registerDoubleValueCallback( .hashCode(); // Only build the instrument if it isn't already in the registry - Boolean existingValue = instrumentOnceRegistry.putIfAbsent(descriptorHash, true); + ObservableMeasurement existingValue = instrumentOnceRegistry.get(descriptorHash); if (existingValue == null) { - meter - .gaugeBuilder(name) - .setDescription(description) - .setUnit(unit) - .buildWithCallback(proxiedDoubleObserver(descriptorHash, updater)); - } else { + ObservableDoubleMeasurement obs = + meter.gaugeBuilder(name).setDescription(description).setUnit(unit).buildObserver(); + instrumentOnceRegistry.put(descriptorHash, obs); + if (updater != null) { + Consumer cb = proxiedDoubleObserver(descriptorHash, updater); + meter.batchCallback(() -> cb.accept(obs), obs); + } + return obs; + } else if (updater != null) { // If the instrument has already been built with the appropriate proxied observer, // update the registry so that the callback has the appropriate updater function AtomicReference> existingUpdater = doubleUpdaterRegistry.get(descriptorHash); existingUpdater.set(updater); } + + return (ObservableDoubleMeasurement) existingValue; } /** @@ -261,8 +272,9 @@ public void registerDoubleValueCallback( * @param description metric description * @param unit - metric unit * @param updater - the value updater + * @return the ObservableLongMeasurement for the gauge */ - public void registerLongValueCallback( + public ObservableLongMeasurement registerLongValueCallback( final String name, final String description, final String unit, @@ -273,21 +285,30 @@ public void registerLongValueCallback( .hashCode(); // Only build the instrument if it isn't already in the registry - Boolean existingValue = instrumentOnceRegistry.putIfAbsent(descriptorHash, true); + ObservableMeasurement existingValue = instrumentOnceRegistry.get(descriptorHash); if (existingValue == null) { - meter - .gaugeBuilder(name) - .ofLongs() - .setDescription(description) - .setUnit(unit) - .buildWithCallback(proxiedLongObserver(descriptorHash, updater)); - } else { + ObservableLongMeasurement obs = + meter + .gaugeBuilder(name) + .ofLongs() + .setDescription(description) + .setUnit(unit) + .buildObserver(); + instrumentOnceRegistry.put(descriptorHash, obs); + if (updater != null) { + Consumer cb = proxiedLongObserver(descriptorHash, updater); + meter.batchCallback(() -> cb.accept(obs), obs); + } + return obs; + } else if (updater != null) { // If the instrument has already been built with the appropriate proxied observer, // update the registry so that the callback has the appropriate updater function AtomicReference> existingUpdater = longUpdaterRegistry.get(descriptorHash); existingUpdater.set(updater); } + + return (ObservableLongMeasurement) existingValue; } /** @@ -297,8 +318,9 @@ public void registerLongValueCallback( * @param description metric description * @param unit - metric unit * @param updater - the value updater + * @return the ObservableDoubleMeasurement for the counter */ - public void registerDoubleCounterCallback( + public ObservableDoubleMeasurement registerDoubleCounterCallback( final String name, final String description, final String unit, @@ -313,21 +335,30 @@ public void registerDoubleCounterCallback( .hashCode(); // Only build the instrument if it isn't already in the registry - Boolean existingValue = instrumentOnceRegistry.putIfAbsent(descriptorHash, true); + ObservableMeasurement existingValue = instrumentOnceRegistry.get(descriptorHash); if (existingValue == null) { - meter - .counterBuilder(name) - .ofDoubles() - .setDescription(description) - .setUnit(unit) - .buildWithCallback(proxiedDoubleObserver(descriptorHash, updater)); - } else { + ObservableDoubleMeasurement obs = + meter + .counterBuilder(name) + .setDescription(description) + .setUnit(unit) + .ofDoubles() + .buildObserver(); + instrumentOnceRegistry.put(descriptorHash, obs); + if (updater != null) { + Consumer cb = proxiedDoubleObserver(descriptorHash, updater); + meter.batchCallback(() -> cb.accept(obs), obs); + } + return obs; + } else if (updater != null) { // If the instrument has already been built with the appropriate proxied observer, // update the registry so that the callback has the appropriate updater function AtomicReference> existingUpdater = doubleUpdaterRegistry.get(descriptorHash); existingUpdater.set(updater); } + + return (ObservableDoubleMeasurement) existingValue; } /** @@ -337,8 +368,9 @@ public void registerDoubleCounterCallback( * @param description metric description * @param unit - metric unit * @param updater - the value updater + * @return the ObservableLongMeasurement for the counter */ - public void registerLongCounterCallback( + public ObservableLongMeasurement registerLongCounterCallback( final String name, final String description, final String unit, @@ -353,20 +385,25 @@ public void registerLongCounterCallback( .hashCode(); // Only build the instrument if it isn't already in the registry - Boolean existingValue = instrumentOnceRegistry.putIfAbsent(descriptorHash, true); + ObservableMeasurement existingValue = instrumentOnceRegistry.get(descriptorHash); if (existingValue == null) { - meter - .counterBuilder(name) - .setDescription(description) - .setUnit(unit) - .buildWithCallback(proxiedLongObserver(descriptorHash, updater)); - } else { + ObservableLongMeasurement obs = + meter.counterBuilder(name).setDescription(description).setUnit(unit).buildObserver(); + instrumentOnceRegistry.put(descriptorHash, obs); + if (updater != null) { + Consumer cb = proxiedLongObserver(descriptorHash, updater); + meter.batchCallback(() -> cb.accept(obs), obs); + } + return obs; + } else if (updater != null) { // If the instrument has already been built with the appropriate proxied observer, // update the registry so that the callback has the appropriate updater function AtomicReference> existingUpdater = longUpdaterRegistry.get(descriptorHash); existingUpdater.set(updater); } + + return (ObservableLongMeasurement) existingValue; } /** @@ -376,8 +413,9 @@ public void registerLongCounterCallback( * @param description metric description * @param unit - metric unit * @param updater - the value updater + * @return the ObservableDoubleMeasurement for the counter */ - public void registerDoubleUpDownCounterCallback( + public ObservableDoubleMeasurement registerDoubleUpDownCounterCallback( final String name, final String description, final String unit, @@ -392,21 +430,30 @@ public void registerDoubleUpDownCounterCallback( .hashCode(); // Only build the instrument if it isn't already in the registry - Boolean existingValue = instrumentOnceRegistry.putIfAbsent(descriptorHash, true); + ObservableMeasurement existingValue = instrumentOnceRegistry.get(descriptorHash); if (existingValue == null) { - meter - .upDownCounterBuilder(name) - .ofDoubles() - .setDescription(description) - .setUnit(unit) - .buildWithCallback(proxiedDoubleObserver(descriptorHash, updater)); - } else { + ObservableDoubleMeasurement obs = + meter + .upDownCounterBuilder(name) + .setDescription(description) + .setUnit(unit) + .ofDoubles() + .buildObserver(); + instrumentOnceRegistry.put(descriptorHash, obs); + if (updater != null) { + Consumer cb = proxiedDoubleObserver(descriptorHash, updater); + meter.batchCallback(() -> cb.accept(obs), obs); + } + return obs; + } else if (updater != null) { // If the instrument has already been built with the appropriate proxied observer, // update the registry so that the callback has the appropriate updater function AtomicReference> existingUpdater = doubleUpdaterRegistry.get(descriptorHash); existingUpdater.set(updater); } + + return (ObservableDoubleMeasurement) existingValue; } /** @@ -416,8 +463,9 @@ public void registerDoubleUpDownCounterCallback( * @param description metric description * @param unit - metric unit * @param updater - the value updater + * @return the ObservableLongMeasurement for the counter */ - public void registerLongUpDownCounterCallback( + public ObservableLongMeasurement registerLongUpDownCounterCallback( final String name, final String description, final String unit, @@ -432,20 +480,45 @@ public void registerLongUpDownCounterCallback( .hashCode(); // Only build the instrument if it isn't already in the registry - Boolean existingValue = instrumentOnceRegistry.putIfAbsent(descriptorHash, true); + ObservableMeasurement existingValue = instrumentOnceRegistry.get(descriptorHash); if (existingValue == null) { - meter - .upDownCounterBuilder(name) - .setDescription(description) - .setUnit(unit) - .buildWithCallback(proxiedLongObserver(descriptorHash, updater)); - } else { + ObservableLongMeasurement obs = + meter + .upDownCounterBuilder(name) + .setDescription(description) + .setUnit(unit) + .buildObserver(); + instrumentOnceRegistry.put(descriptorHash, obs); + if (updater != null) { + Consumer cb = proxiedLongObserver(descriptorHash, updater); + meter.batchCallback(() -> cb.accept(obs), obs); + } + return obs; + } else if (updater != null) { // If the instrument has already been built with the appropriate proxied observer, // update the registry so that the callback has the appropriate updater function AtomicReference> existingUpdater = longUpdaterRegistry.get(descriptorHash); existingUpdater.set(updater); } + + return (ObservableLongMeasurement) existingValue; + } + + public void registerBatchCallback( + Object identifier, + Closure callback, + ObservableMeasurement measurement, + ObservableMeasurement... additional) { + int hash = identifier.hashCode(); + AtomicReference> existing = + batchUpdaterRegistry.putIfAbsent(hash, new AtomicReference<>()); + batchUpdaterRegistry.get(hash).set(callback); + + if (existing == null) { + meter.batchCallback( + () -> batchUpdaterRegistry.get(hash).get().call(), measurement, additional); + } } private Consumer proxiedDoubleObserver( diff --git a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelper.groovy b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelper.groovy index 521027de1..c2f7cc2c4 100644 --- a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelper.groovy +++ b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelper.groovy @@ -7,6 +7,7 @@ package io.opentelemetry.contrib.jmxmetrics import groovy.jmx.GroovyMBean import groovy.transform.PackageScope +import io.opentelemetry.api.metrics.ObservableMeasurement import javax.management.AttributeNotFoundException import java.util.logging.Logger @@ -34,7 +35,7 @@ import javax.management.openmbean.CompositeData * updated for each respective value. */ class InstrumentHelper { - private static final Logger logger = Logger.getLogger(InstrumentHelper.class.getName()); + private static final Logger logger = Logger.getLogger(InstrumentHelper.class.getName()) private final MBeanHelper mBeanHelper private final String instrumentName @@ -43,6 +44,7 @@ class InstrumentHelper { private final Map> mBeanAttributes private final Map labelFuncs private final Closure instrument + private final GroovyMetricEnvironment metricEnvironment /** * An InstrumentHelper provides the ability to easily create and update {@link io.opentelemetry.api.metrics.Instrument} @@ -60,7 +62,7 @@ class InstrumentHelper { * @param instrument - The {@link io.opentelemetry.api.metrics.Instrument}-producing {@link OtelHelper} method pointer: * (e.g. new OtelHelper().&doubleValueRecorder) */ - InstrumentHelper(MBeanHelper mBeanHelper, String instrumentName, String description, String unit, Map> labelFuncs, Map>> MBeanAttributes, Closure instrument) { + InstrumentHelper(MBeanHelper mBeanHelper, String instrumentName, String description, String unit, Map> labelFuncs, Map>> MBeanAttributes, Closure instrument, GroovyMetricEnvironment metricEnvironment) { this.mBeanHelper = mBeanHelper this.instrumentName = instrumentName this.description = description @@ -68,16 +70,42 @@ class InstrumentHelper { this.labelFuncs = labelFuncs this.mBeanAttributes = MBeanAttributes this.instrument = instrument + this.metricEnvironment = metricEnvironment } void update() { - def updateClosure = prepareUpdateClosure(instrument, mBeanHelper.getMBeans(), mBeanAttributes.keySet()) - if (instrumentIsDoubleObserver(instrument) || instrumentIsLongObserver(instrument)) { - instrument(instrumentName, description, unit, { result -> - updateClosure(result) - }) - } else { - updateClosure(instrument(instrumentName, description, unit)) + def mbeans = mBeanHelper.getMBeans() + def compositeAttributes = [] + def simpleAttributes = [] + if (mbeans.size() > 0) { + def bean = mbeans.first() + mBeanAttributes.keySet().each { attribute -> + try { + def value = bean.getProperty(attribute) + if (value instanceof CompositeData) { + compositeAttributes.add(new Tuple2>(attribute, value.getCompositeType().keySet())) + } else { + simpleAttributes.add(attribute) + } + } catch (AttributeNotFoundException|NullPointerException e ) { + simpleAttributes.add(attribute) + } + } + } + + if (simpleAttributes.size() > 0) { + def simpleUpdateClosure = prepareUpdateClosure(mbeans, simpleAttributes) + if (instrumentIsDoubleObserver(instrument) || instrumentIsLongObserver(instrument)) { + instrument(instrumentName, description, unit, { result -> + simpleUpdateClosure(result) + }) + } else { + simpleUpdateClosure(instrument(instrumentName, description, unit)) + } + } + + if (compositeAttributes.size() > 0) { + registerCompositeUpdateClosures(mbeans, compositeAttributes) } } @@ -92,24 +120,16 @@ class InstrumentHelper { return labels } - private Closure prepareUpdateClosure(inst, List mbeans, attributes) { + private Closure prepareUpdateClosure(List mbeans, attributes) { return { result -> [mbeans, attributes].combinations().each { pair -> def (mbean, attribute) = pair try { def value = mbean.getProperty(attribute) - if (value instanceof CompositeData) { - value.getCompositeType().keySet().each { key -> - def val = value.get(key) - def updatedInstrumentName = "${instrumentName}.${key}" - def labels = getLabels(mbean, labelFuncs, mBeanAttributes[attribute]) - logger.fine("Recording ${updatedInstrumentName} - ${instrument.method} w/ ${val} - ${labels}") - recordDataPoint(inst, result, val, GroovyMetricEnvironment.mapToAttributes(labels)) - } - } else if (value != null) { + if (value != null) { def labels = getLabels(mbean, labelFuncs, mBeanAttributes[attribute]) logger.fine("Recording ${instrumentName} - ${instrument.method} w/ ${value} - ${labels}") - recordDataPoint(inst, result, value, GroovyMetricEnvironment.mapToAttributes(labels)) + recordDataPoint(instrument, result, value, GroovyMetricEnvironment.mapToAttributes(labels)) } } catch (AttributeNotFoundException e ) { logger.info("Expected attribute ${attribute} not found in mbean ${mbean.name()}") @@ -118,6 +138,31 @@ class InstrumentHelper { } } + private void registerCompositeUpdateClosures(List mbeans, attributes) { + attributes.each { pair -> + def (attribute, keys) = pair + def instruments = keys.collect { new Tuple2(it, instrument("${instrumentName}.${it}", description, unit, null)) } + + metricEnvironment.registerBatchCallback("${instrumentName}.${attribute}", () -> { + mbeans.each { mbean -> + try { + def value = mbean.getProperty(attribute) + if (value != null && value instanceof CompositeData) { + instruments.each { inst -> + def val = value.get(inst.v1) + def labels = getLabels(mbean, labelFuncs, mBeanAttributes[attribute]) + logger.fine("Recording ${"${instrumentName}.${inst.v1}"} - ${instrument.method} w/ ${val} - ${labels}") + recordDataPoint(instrument, inst.v2, val, GroovyMetricEnvironment.mapToAttributes(labels)) + } + } + } catch (AttributeNotFoundException e ) { + logger.info("Expected attribute ${attribute} not found in mbean ${mbean.name()}") + } + } + }, instruments.first().v2, *instruments.tail().collect {it.v2 }) + } + } + private static void recordDataPoint(inst, result, value, labelMap) { if (instrumentIsLongObserver(inst)) { result.record((long) value, labelMap) diff --git a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/OtelHelper.groovy b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/OtelHelper.groovy index db8223929..e3ab6f3a5 100644 --- a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/OtelHelper.groovy +++ b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/OtelHelper.groovy @@ -88,13 +88,13 @@ class OtelHelper { * attribute value(s). The parameters map to the InstrumentHelper constructor. */ InstrumentHelper instrument(MBeanHelper mBeanHelper, String instrumentName, String description, String unit, Map labelFuncs, Map> attributes, Closure otelInstrument) { - def instrumentHelper = new InstrumentHelper(mBeanHelper, instrumentName, description, unit, labelFuncs, attributes, otelInstrument) + def instrumentHelper = new InstrumentHelper(mBeanHelper, instrumentName, description, unit, labelFuncs, attributes, otelInstrument, groovyMetricEnvironment) instrumentHelper.update() return instrumentHelper } InstrumentHelper instrument(MBeanHelper mBeanHelper, String instrumentName, String description, String unit, Map labelFuncs, String attribute, Closure otelInstrument) { - instrument(mBeanHelper, instrumentName, description, unit, labelFuncs, [(attribute): [:] as Map], otelInstrument) + return instrument(mBeanHelper, instrumentName, description, unit, labelFuncs, [(attribute): [:] as Map], otelInstrument) } InstrumentHelper instrument(MBeanHelper mBeanHelper, String instrumentName, String description, String unit, String attribute, Closure otelInstrument) { @@ -102,7 +102,7 @@ class OtelHelper { } InstrumentHelper instrument(MBeanHelper mBeanHelper, String instrumentName, String description, String unit, Map> attributes, Closure otelInstrument) { - return instrument(mBeanHelper, instrumentName, description, unit, [:] as Map, attributes, otelInstrument) + return instrument(mBeanHelper, instrumentName, description, unit, [:] as Map, attributes, otelInstrument) } InstrumentHelper instrument(MBeanHelper mBeanHelper, String instrumentName, String description, String attribute, Closure otelInstrument) { @@ -110,7 +110,7 @@ class OtelHelper { } InstrumentHelper instrument(MBeanHelper mBeanHelper, String instrumentName, String description, Map> attributes, Closure otelInstrument) { - return instrument(mBeanHelper, instrumentName, description, OtelHelper.SCALAR, [:] as Map, attributes, otelInstrument) + return instrument(mBeanHelper, instrumentName, description, OtelHelper.SCALAR, [:] as Map, attributes, otelInstrument) } InstrumentHelper instrument(MBeanHelper mBeanHelper, String instrumentName, String attribute, Closure otelInstrument) { @@ -118,7 +118,7 @@ class OtelHelper { } InstrumentHelper instrument(MBeanHelper mBeanHelper, String instrumentName, Map> attributes, Closure otelInstrument) { - return instrument(mBeanHelper, instrumentName, "", OtelHelper.SCALAR, [:] as Map, attributes, otelInstrument) + return instrument(mBeanHelper, instrumentName, "", OtelHelper.SCALAR, [:] as Map, attributes, otelInstrument) } DoubleCounter doubleCounter(String name, String description, String unit) { @@ -193,68 +193,68 @@ class OtelHelper { return longHistogram(name, '') } - void doubleCounterCallback(String name, String description, String unit, Consumer updater) { - groovyMetricEnvironment.registerDoubleCounterCallback(name, description, unit, updater) + ObservableDoubleMeasurement doubleCounterCallback(String name, String description, String unit, Consumer updater) { + return groovyMetricEnvironment.registerDoubleCounterCallback(name, description, unit, updater) } - void doubleCounterCallback(String name, String description, Consumer updater) { - doubleCounterCallback(name, description, SCALAR, updater) + ObservableDoubleMeasurement doubleCounterCallback(String name, String description, Consumer updater) { + return doubleCounterCallback(name, description, SCALAR, updater) } - void doubleCounterCallback(String name, Consumer updater) { - doubleCounterCallback(name, '', updater) + ObservableDoubleMeasurement doubleCounterCallback(String name, Consumer updater) { + return doubleCounterCallback(name, '', updater) } - void longCounterCallback(String name, String description, String unit, Consumer updater) { - groovyMetricEnvironment.registerLongCounterCallback(name, description, unit, updater) + ObservableLongMeasurement longCounterCallback(String name, String description, String unit, Consumer updater) { + return groovyMetricEnvironment.registerLongCounterCallback(name, description, unit, updater) } - void longCounterCallback(String name, String description, Consumer updater) { - longCounterCallback(name, description, SCALAR, updater) + ObservableLongMeasurement longCounterCallback(String name, String description, Consumer updater) { + return longCounterCallback(name, description, SCALAR, updater) } - void longCounterCallback(String name, Consumer updater) { - longCounterCallback(name, '', updater) + ObservableLongMeasurement longCounterCallback(String name, Consumer updater) { + return longCounterCallback(name, '', updater) } - void doubleUpDownCounterCallback(String name, String description, String unit, Consumer updater) { - groovyMetricEnvironment.registerDoubleUpDownCounterCallback(name, description, unit, updater) + ObservableDoubleMeasurement doubleUpDownCounterCallback(String name, String description, String unit, Consumer updater) { + return groovyMetricEnvironment.registerDoubleUpDownCounterCallback(name, description, unit, updater) } - void doubleUpDownCounterCallback(String name, String description, Consumer updater) { - doubleUpDownCounterCallback(name, description, SCALAR, updater) + ObservableDoubleMeasurement doubleUpDownCounterCallback(String name, String description, Consumer updater) { + return doubleUpDownCounterCallback(name, description, SCALAR, updater) } - void doubleUpDownCounterCallback(String name, Consumer updater) { - doubleUpDownCounterCallback(name, '', updater) + ObservableDoubleMeasurement doubleUpDownCounterCallback(String name, Consumer updater) { + return doubleUpDownCounterCallback(name, '', updater) } - void longUpDownCounterCallback(String name, String description, String unit, Consumer updater) { - groovyMetricEnvironment.registerLongUpDownCounterCallback(name, description, unit, updater) + ObservableLongMeasurement longUpDownCounterCallback(String name, String description, String unit, Consumer updater) { + return groovyMetricEnvironment.registerLongUpDownCounterCallback(name, description, unit, updater) } - void longUpDownCounterCallback(String name, String description, Consumer updater) { - longUpDownCounterCallback(name, description, SCALAR, updater) + ObservableLongMeasurement longUpDownCounterCallback(String name, String description, Consumer updater) { + return longUpDownCounterCallback(name, description, SCALAR, updater) } - void longUpDownCounterCallback(String name, Consumer updater) { - longUpDownCounterCallback(name, '', updater) + ObservableLongMeasurement longUpDownCounterCallback(String name, Consumer updater) { + return longUpDownCounterCallback(name, '', updater) } - void doubleValueCallback(String name, String description, String unit, Consumer updater) { - groovyMetricEnvironment.registerDoubleValueCallback(name, description, unit, updater) + ObservableDoubleMeasurement doubleValueCallback(String name, String description, String unit, Consumer updater) { + return groovyMetricEnvironment.registerDoubleValueCallback(name, description, unit, updater) } - void doubleValueCallback(String name, String description, Consumer updater) { - doubleValueCallback(name, description, SCALAR, updater) + ObservableDoubleMeasurement doubleValueCallback(String name, String description, Consumer updater) { + return doubleValueCallback(name, description, SCALAR, updater) } - void doubleValueCallback(String name, Consumer updater) { - doubleValueCallback(name, '', updater) + ObservableDoubleMeasurement doubleValueCallback(String name, Consumer updater) { + return doubleValueCallback(name, '', updater) } ObservableLongMeasurement longValueCallback(String name, String description, String unit, Consumer updater) { - return groovyMetricEnvironment.registerLongValueCallback(name, description, unit, updater) + return groovyMetricEnvironment.registerLongValueCallback(name, description, unit, updater) } ObservableLongMeasurement longValueCallback(String name, String description, Consumer updater) { @@ -262,6 +262,6 @@ class OtelHelper { } ObservableLongMeasurement longValueCallback(String name, Consumer updater) { - return longValueCallback(name, '', updater) + return longValueCallback(name, '', updater) } } diff --git a/jmx-metrics/src/test/java/io/opentelemetry/contrib/jmxmetrics/InstrumenterHelperTest.java b/jmx-metrics/src/test/java/io/opentelemetry/contrib/jmxmetrics/InstrumenterHelperTest.java index a9abdbef1..11737efb3 100644 --- a/jmx-metrics/src/test/java/io/opentelemetry/contrib/jmxmetrics/InstrumenterHelperTest.java +++ b/jmx-metrics/src/test/java/io/opentelemetry/contrib/jmxmetrics/InstrumenterHelperTest.java @@ -58,6 +58,7 @@ class InstrumenterHelperTest { // Will eventually be replaced with Jupiter extension in sdk-testing private SdkMeterProvider meterProvider; private InMemoryMetricReader metricReader; + private GroovyMetricEnvironment metricEnvironment; private OtelHelper otel; @@ -91,8 +92,8 @@ void confirmServerIsActive() { void setupOtel() { metricReader = InMemoryMetricReader.create(); meterProvider = SdkMeterProvider.builder().registerMetricReader(metricReader).build(); - - otel = new OtelHelper(jmxClient, new GroovyMetricEnvironment(meterProvider, "otel.test")); + metricEnvironment = new GroovyMetricEnvironment(meterProvider, "otel.test"); + otel = new OtelHelper(jmxClient, metricEnvironment); } @AfterEach @@ -688,7 +689,8 @@ void updateWithHelper( "1", labelFuncs, Collections.singletonMap(attribute, null), - instrument); + instrument, + metricEnvironment); instrumentHelper.update(); } @@ -702,7 +704,14 @@ void updateWithHelperMultiAttribute( Map> labelFuncs = new HashMap<>(); InstrumentHelper instrumentHelper = new InstrumentHelper( - mBeanHelper, instrumentName, description, "1", labelFuncs, attributes, instrument); + mBeanHelper, + instrumentName, + description, + "1", + labelFuncs, + attributes, + instrument, + metricEnvironment); instrumentHelper.update(); } From 56bbf4a5fa731f54b5f0a147b325c1a6cd3d9b14 Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Tue, 11 Jul 2023 00:26:20 -0400 Subject: [PATCH 03/15] Remove tmp files accidentally committed --- tmp/META-INF/LICENSE.name3/LICENSE.renamed | 202 --------------------- tmp/META-INF/LICENSE.renamed | 202 --------------------- tmp/session.properties | 5 - tmp/wildfly.properties | 5 - 4 files changed, 414 deletions(-) delete mode 100644 tmp/META-INF/LICENSE.name3/LICENSE.renamed delete mode 100644 tmp/META-INF/LICENSE.renamed delete mode 100644 tmp/session.properties delete mode 100644 tmp/wildfly.properties diff --git a/tmp/META-INF/LICENSE.name3/LICENSE.renamed b/tmp/META-INF/LICENSE.name3/LICENSE.renamed deleted file mode 100644 index d64569567..000000000 --- a/tmp/META-INF/LICENSE.name3/LICENSE.renamed +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. diff --git a/tmp/META-INF/LICENSE.renamed b/tmp/META-INF/LICENSE.renamed deleted file mode 100644 index d64569567..000000000 --- a/tmp/META-INF/LICENSE.renamed +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. diff --git a/tmp/session.properties b/tmp/session.properties deleted file mode 100644 index 969103480..000000000 --- a/tmp/session.properties +++ /dev/null @@ -1,5 +0,0 @@ -otel.jmx.service.url = service:jmx:rmi:///jndi/rmi://localhost:9999/jmxrmi -otel.jmx.target.system = kafka -otel.jmx.interval.milliseconds = 15000 - -otel.metrics.exporter = logging diff --git a/tmp/wildfly.properties b/tmp/wildfly.properties deleted file mode 100644 index 6bf3b1c75..000000000 --- a/tmp/wildfly.properties +++ /dev/null @@ -1,5 +0,0 @@ -otel.jmx.service.url = service:jmx:remote+http://192.168.50.89:9990 -otel.jmx.interval.milliseconds = 10000 -otel.jmx.target.system = wildfly -otel.jmx.username = medora -otel.jmx.password = Password1! From 10c32ae2ccec7766be12dcdc40d2bf68fbeae07a Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Tue, 11 Jul 2023 01:13:58 -0400 Subject: [PATCH 04/15] Back out attempt to check for empty metrics collection in integration test --- .../jmxmetrics/AbstractIntegrationTest.java | 21 ------------------- .../JvmTargetSystemIntegrationTest.java | 4 ---- 2 files changed, 25 deletions(-) diff --git a/jmx-metrics/src/integrationTest/java/io/opentelemetry/contrib/jmxmetrics/AbstractIntegrationTest.java b/jmx-metrics/src/integrationTest/java/io/opentelemetry/contrib/jmxmetrics/AbstractIntegrationTest.java index dedee72e2..1d734aed7 100644 --- a/jmx-metrics/src/integrationTest/java/io/opentelemetry/contrib/jmxmetrics/AbstractIntegrationTest.java +++ b/jmx-metrics/src/integrationTest/java/io/opentelemetry/contrib/jmxmetrics/AbstractIntegrationTest.java @@ -164,27 +164,6 @@ protected final void waitAndAssertMetrics(Consumer... assertions) { waitAndAssertMetrics(Arrays.asList(assertions)); } - protected final void waitAndAssertNoMetrics() { - await() - .atMost(Duration.ofSeconds(30)) - .untilAsserted( - () -> { - List metrics = - otlpServer.getMetrics().stream() - .map(ExportMetricsServiceRequest::getResourceMetricsList) - .flatMap(rm -> rm.stream().map(ResourceMetrics::getScopeMetricsList)) - .flatMap(Collection::stream) - .filter( - sm -> - sm.getScope().getName().equals("io.opentelemetry.contrib.jmxmetrics") - && sm.getScope().getVersion().equals(expectedMeterVersion())) - .flatMap(sm -> sm.getMetricsList().stream()) - .collect(Collectors.toList()); - - assertThat(metrics).isEmpty(); - }); - } - protected void assertGauge(Metric metric, String name, String description, String unit) { assertThat(metric.getName()).isEqualTo(name); assertThat(metric.getDescription()).isEqualTo(description); diff --git a/jmx-metrics/src/integrationTest/java/io/opentelemetry/contrib/jmxmetrics/target_systems/JvmTargetSystemIntegrationTest.java b/jmx-metrics/src/integrationTest/java/io/opentelemetry/contrib/jmxmetrics/target_systems/JvmTargetSystemIntegrationTest.java index 4a9197169..91de5de0d 100644 --- a/jmx-metrics/src/integrationTest/java/io/opentelemetry/contrib/jmxmetrics/target_systems/JvmTargetSystemIntegrationTest.java +++ b/jmx-metrics/src/integrationTest/java/io/opentelemetry/contrib/jmxmetrics/target_systems/JvmTargetSystemIntegrationTest.java @@ -68,9 +68,5 @@ void endToEnd() { assertTypedGauge( metric, "jvm.memory.pool.used", "current memory pool usage", "by", gcLabels), metric -> assertGauge(metric, "jvm.threads.count", "number of threads", "1")); - - cassandra.stop(); - - waitAndAssertNoMetrics(); } } From fc70dfdbe21a7b4315e943aa4e3e8480cb0e9400 Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Tue, 11 Jul 2023 09:31:42 -0400 Subject: [PATCH 05/15] Fix empty vs null issue --- .../io/opentelemetry/contrib/jmxmetrics/MBeanHelper.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/MBeanHelper.groovy b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/MBeanHelper.groovy index b840ba4e9..b14063ebf 100644 --- a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/MBeanHelper.groovy +++ b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/MBeanHelper.groovy @@ -74,7 +74,7 @@ class MBeanHelper { } @PackageScope List getMBeans() { - if (mbeans == null) { + if (mbeans == null || mbeans.size() == 0) { logger.warning("No active MBeans. Be sure to fetch() before updating any applicable instruments.") return [] } From 45e63491a0f1a075faabf22005be96ff1d8f192c Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Tue, 11 Jul 2023 09:48:05 -0400 Subject: [PATCH 06/15] minor refactoring and comments --- .../jmxmetrics/GroovyMetricEnvironment.java | 102 ++++++++++-------- .../jmxmetrics/InstrumentHelper.groovy | 32 ++++-- 2 files changed, 81 insertions(+), 53 deletions(-) diff --git a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/GroovyMetricEnvironment.java b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/GroovyMetricEnvironment.java index bce84444a..b8031b850 100644 --- a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/GroovyMetricEnvironment.java +++ b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/GroovyMetricEnvironment.java @@ -244,16 +244,17 @@ public ObservableDoubleMeasurement registerDoubleValueCallback( .hashCode(); // Only build the instrument if it isn't already in the registry - ObservableMeasurement existingValue = instrumentOnceRegistry.get(descriptorHash); - if (existingValue == null) { - ObservableDoubleMeasurement obs = - meter.gaugeBuilder(name).setDescription(description).setUnit(unit).buildObserver(); + ObservableMeasurement obs = instrumentOnceRegistry.get(descriptorHash); + if (obs == null) { + obs = meter.gaugeBuilder(name).setDescription(description).setUnit(unit).buildObserver(); instrumentOnceRegistry.put(descriptorHash, obs); + // If an updater was not provided, the measurement is expected to be added + // to a group batchcallback using the registerBatchCallback function if (updater != null) { Consumer cb = proxiedDoubleObserver(descriptorHash, updater); - meter.batchCallback(() -> cb.accept(obs), obs); + ObservableDoubleMeasurement finalObs = (ObservableDoubleMeasurement)obs; + meter.batchCallback(() -> cb.accept(finalObs), obs); } - return obs; } else if (updater != null) { // If the instrument has already been built with the appropriate proxied observer, // update the registry so that the callback has the appropriate updater function @@ -262,7 +263,7 @@ public ObservableDoubleMeasurement registerDoubleValueCallback( existingUpdater.set(updater); } - return (ObservableDoubleMeasurement) existingValue; + return (ObservableDoubleMeasurement) obs; } /** @@ -285,21 +286,22 @@ public ObservableLongMeasurement registerLongValueCallback( .hashCode(); // Only build the instrument if it isn't already in the registry - ObservableMeasurement existingValue = instrumentOnceRegistry.get(descriptorHash); - if (existingValue == null) { - ObservableLongMeasurement obs = - meter + ObservableMeasurement obs = instrumentOnceRegistry.get(descriptorHash); + if (obs == null) { + obs = meter .gaugeBuilder(name) .ofLongs() .setDescription(description) .setUnit(unit) .buildObserver(); instrumentOnceRegistry.put(descriptorHash, obs); + // If an updater was not provided, the measurement is expected to be added + // to a group batchcallback using the registerBatchCallback function if (updater != null) { Consumer cb = proxiedLongObserver(descriptorHash, updater); - meter.batchCallback(() -> cb.accept(obs), obs); + ObservableLongMeasurement finalObs = (ObservableLongMeasurement)obs; + meter.batchCallback(() -> cb.accept(finalObs), obs); } - return obs; } else if (updater != null) { // If the instrument has already been built with the appropriate proxied observer, // update the registry so that the callback has the appropriate updater function @@ -308,7 +310,7 @@ public ObservableLongMeasurement registerLongValueCallback( existingUpdater.set(updater); } - return (ObservableLongMeasurement) existingValue; + return (ObservableLongMeasurement) obs; } /** @@ -335,21 +337,22 @@ public ObservableDoubleMeasurement registerDoubleCounterCallback( .hashCode(); // Only build the instrument if it isn't already in the registry - ObservableMeasurement existingValue = instrumentOnceRegistry.get(descriptorHash); - if (existingValue == null) { - ObservableDoubleMeasurement obs = - meter + ObservableMeasurement obs = instrumentOnceRegistry.get(descriptorHash); + if (obs == null) { + obs = meter .counterBuilder(name) .setDescription(description) .setUnit(unit) .ofDoubles() .buildObserver(); instrumentOnceRegistry.put(descriptorHash, obs); + // If an updater was not provided, the measurement is expected to be added + // to a group batchcallback using the registerBatchCallback function if (updater != null) { Consumer cb = proxiedDoubleObserver(descriptorHash, updater); - meter.batchCallback(() -> cb.accept(obs), obs); + ObservableDoubleMeasurement finalObs = (ObservableDoubleMeasurement)obs; + meter.batchCallback(() -> cb.accept(finalObs), obs); } - return obs; } else if (updater != null) { // If the instrument has already been built with the appropriate proxied observer, // update the registry so that the callback has the appropriate updater function @@ -358,7 +361,7 @@ public ObservableDoubleMeasurement registerDoubleCounterCallback( existingUpdater.set(updater); } - return (ObservableDoubleMeasurement) existingValue; + return (ObservableDoubleMeasurement) obs; } /** @@ -385,16 +388,17 @@ public ObservableLongMeasurement registerLongCounterCallback( .hashCode(); // Only build the instrument if it isn't already in the registry - ObservableMeasurement existingValue = instrumentOnceRegistry.get(descriptorHash); - if (existingValue == null) { - ObservableLongMeasurement obs = - meter.counterBuilder(name).setDescription(description).setUnit(unit).buildObserver(); + ObservableMeasurement obs = instrumentOnceRegistry.get(descriptorHash); + if (obs == null) { + obs = meter.counterBuilder(name).setDescription(description).setUnit(unit).buildObserver(); instrumentOnceRegistry.put(descriptorHash, obs); + // If an updater was not provided, the measurement is expected to be added + // to a group batchcallback using the registerBatchCallback function if (updater != null) { Consumer cb = proxiedLongObserver(descriptorHash, updater); - meter.batchCallback(() -> cb.accept(obs), obs); + ObservableLongMeasurement finalObs = (ObservableLongMeasurement)obs; + meter.batchCallback(() -> cb.accept(finalObs), obs); } - return obs; } else if (updater != null) { // If the instrument has already been built with the appropriate proxied observer, // update the registry so that the callback has the appropriate updater function @@ -403,7 +407,7 @@ public ObservableLongMeasurement registerLongCounterCallback( existingUpdater.set(updater); } - return (ObservableLongMeasurement) existingValue; + return (ObservableLongMeasurement) obs; } /** @@ -430,21 +434,22 @@ public ObservableDoubleMeasurement registerDoubleUpDownCounterCallback( .hashCode(); // Only build the instrument if it isn't already in the registry - ObservableMeasurement existingValue = instrumentOnceRegistry.get(descriptorHash); - if (existingValue == null) { - ObservableDoubleMeasurement obs = - meter + ObservableMeasurement obs = instrumentOnceRegistry.get(descriptorHash); + if (obs == null) { + obs = meter .upDownCounterBuilder(name) .setDescription(description) .setUnit(unit) .ofDoubles() .buildObserver(); instrumentOnceRegistry.put(descriptorHash, obs); + // If an updater was not provided, the measurement is expected to be added + // to a group batchcallback using the registerBatchCallback function if (updater != null) { Consumer cb = proxiedDoubleObserver(descriptorHash, updater); - meter.batchCallback(() -> cb.accept(obs), obs); + ObservableDoubleMeasurement finalObs = (ObservableDoubleMeasurement)obs; + meter.batchCallback(() -> cb.accept(finalObs), obs); } - return obs; } else if (updater != null) { // If the instrument has already been built with the appropriate proxied observer, // update the registry so that the callback has the appropriate updater function @@ -453,7 +458,7 @@ public ObservableDoubleMeasurement registerDoubleUpDownCounterCallback( existingUpdater.set(updater); } - return (ObservableDoubleMeasurement) existingValue; + return (ObservableDoubleMeasurement) obs; } /** @@ -480,20 +485,21 @@ public ObservableLongMeasurement registerLongUpDownCounterCallback( .hashCode(); // Only build the instrument if it isn't already in the registry - ObservableMeasurement existingValue = instrumentOnceRegistry.get(descriptorHash); - if (existingValue == null) { - ObservableLongMeasurement obs = - meter + ObservableMeasurement obs = instrumentOnceRegistry.get(descriptorHash); + if (obs == null) { + obs = meter .upDownCounterBuilder(name) .setDescription(description) .setUnit(unit) .buildObserver(); instrumentOnceRegistry.put(descriptorHash, obs); + // If an updater was not provided, the measurement is expected to be added + // to a group batchcallback using the registerBatchCallback function if (updater != null) { Consumer cb = proxiedLongObserver(descriptorHash, updater); - meter.batchCallback(() -> cb.accept(obs), obs); + ObservableLongMeasurement finalObs = (ObservableLongMeasurement)obs; + meter.batchCallback(() -> cb.accept(finalObs), obs); } - return obs; } else if (updater != null) { // If the instrument has already been built with the appropriate proxied observer, // update the registry so that the callback has the appropriate updater function @@ -502,19 +508,31 @@ public ObservableLongMeasurement registerLongUpDownCounterCallback( existingUpdater.set(updater); } - return (ObservableLongMeasurement) existingValue; + return (ObservableLongMeasurement) obs; } + /** + * Register a collection of observables in a single batch callback + * + * @param identifier - object used to identify the callback to have only one callback + * @param callback - closure that records measurements for the observables + * @param measurement - first observable, the SDK expects this is always collected + * @param additional - remaining observable, the SDK expects this is sometimes collected + */ public void registerBatchCallback( Object identifier, Closure callback, ObservableMeasurement measurement, ObservableMeasurement... additional) { int hash = identifier.hashCode(); + // Store the callback in the registry so the proxied callback always runs the latest + // metric collection closure AtomicReference> existing = batchUpdaterRegistry.putIfAbsent(hash, new AtomicReference<>()); batchUpdaterRegistry.get(hash).set(callback); + // If the callback had previously been stored, we assume the back callback is already + // registered with the Meter if (existing == null) { meter.batchCallback( () -> batchUpdaterRegistry.get(hash).get().call(), measurement, additional); diff --git a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelper.groovy b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelper.groovy index c2f7cc2c4..1966e0556 100644 --- a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelper.groovy +++ b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelper.groovy @@ -77,19 +77,23 @@ class InstrumentHelper { def mbeans = mBeanHelper.getMBeans() def compositeAttributes = [] def simpleAttributes = [] - if (mbeans.size() > 0) { - def bean = mbeans.first() - mBeanAttributes.keySet().each { attribute -> - try { - def value = bean.getProperty(attribute) - if (value instanceof CompositeData) { - compositeAttributes.add(new Tuple2>(attribute, value.getCompositeType().keySet())) - } else { - simpleAttributes.add(attribute) - } - } catch (AttributeNotFoundException|NullPointerException e ) { + if (mbeans.size() == 0) { + return + } + // Look at the first mbean collected to evaluate if the attributes requested are + // composite data types or simple. Composite types require different parsing to + // end up with multiple recorders in the same callback. + def bean = mbeans.first() + mBeanAttributes.keySet().each { attribute -> + try { + def value = bean.getProperty(attribute) + if (value instanceof CompositeData) { + compositeAttributes.add(new Tuple2>(attribute, value.getCompositeType().keySet())) + } else { simpleAttributes.add(attribute) } + } catch (AttributeNotFoundException | NullPointerException e) { + simpleAttributes.add(attribute) } } @@ -120,6 +124,8 @@ class InstrumentHelper { return labels } + // Create a closure for simple attributes that will retrieve mbean information on + // callback to ensure that metrics are collected on request private Closure prepareUpdateClosure(List mbeans, attributes) { return { result -> [mbeans, attributes].combinations().each { pair -> @@ -138,6 +144,9 @@ class InstrumentHelper { } } + // Create a closure for composite data attributes that will retrieve mbean information + // on callback to ensure that metrics are collected on request. This will create a single + // batch callback for all of the metrics collected on a single attribute. private void registerCompositeUpdateClosures(List mbeans, attributes) { attributes.each { pair -> def (attribute, keys) = pair @@ -163,6 +172,7 @@ class InstrumentHelper { } } + // Based on the type of instrument, record the data point in the way expected by the observable private static void recordDataPoint(inst, result, value, labelMap) { if (instrumentIsLongObserver(inst)) { result.record((long) value, labelMap) From f24799b4f2a529ba4a05c1556128afa6fcebc904 Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Tue, 11 Jul 2023 09:59:48 -0400 Subject: [PATCH 07/15] run spotless --- .../jmxmetrics/GroovyMetricEnvironment.java | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/GroovyMetricEnvironment.java b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/GroovyMetricEnvironment.java index b8031b850..b74c26874 100644 --- a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/GroovyMetricEnvironment.java +++ b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/GroovyMetricEnvironment.java @@ -252,7 +252,7 @@ public ObservableDoubleMeasurement registerDoubleValueCallback( // to a group batchcallback using the registerBatchCallback function if (updater != null) { Consumer cb = proxiedDoubleObserver(descriptorHash, updater); - ObservableDoubleMeasurement finalObs = (ObservableDoubleMeasurement)obs; + ObservableDoubleMeasurement finalObs = (ObservableDoubleMeasurement) obs; meter.batchCallback(() -> cb.accept(finalObs), obs); } } else if (updater != null) { @@ -288,7 +288,8 @@ public ObservableLongMeasurement registerLongValueCallback( // Only build the instrument if it isn't already in the registry ObservableMeasurement obs = instrumentOnceRegistry.get(descriptorHash); if (obs == null) { - obs = meter + obs = + meter .gaugeBuilder(name) .ofLongs() .setDescription(description) @@ -299,7 +300,7 @@ public ObservableLongMeasurement registerLongValueCallback( // to a group batchcallback using the registerBatchCallback function if (updater != null) { Consumer cb = proxiedLongObserver(descriptorHash, updater); - ObservableLongMeasurement finalObs = (ObservableLongMeasurement)obs; + ObservableLongMeasurement finalObs = (ObservableLongMeasurement) obs; meter.batchCallback(() -> cb.accept(finalObs), obs); } } else if (updater != null) { @@ -339,7 +340,8 @@ public ObservableDoubleMeasurement registerDoubleCounterCallback( // Only build the instrument if it isn't already in the registry ObservableMeasurement obs = instrumentOnceRegistry.get(descriptorHash); if (obs == null) { - obs = meter + obs = + meter .counterBuilder(name) .setDescription(description) .setUnit(unit) @@ -350,7 +352,7 @@ public ObservableDoubleMeasurement registerDoubleCounterCallback( // to a group batchcallback using the registerBatchCallback function if (updater != null) { Consumer cb = proxiedDoubleObserver(descriptorHash, updater); - ObservableDoubleMeasurement finalObs = (ObservableDoubleMeasurement)obs; + ObservableDoubleMeasurement finalObs = (ObservableDoubleMeasurement) obs; meter.batchCallback(() -> cb.accept(finalObs), obs); } } else if (updater != null) { @@ -396,7 +398,7 @@ public ObservableLongMeasurement registerLongCounterCallback( // to a group batchcallback using the registerBatchCallback function if (updater != null) { Consumer cb = proxiedLongObserver(descriptorHash, updater); - ObservableLongMeasurement finalObs = (ObservableLongMeasurement)obs; + ObservableLongMeasurement finalObs = (ObservableLongMeasurement) obs; meter.batchCallback(() -> cb.accept(finalObs), obs); } } else if (updater != null) { @@ -436,7 +438,8 @@ public ObservableDoubleMeasurement registerDoubleUpDownCounterCallback( // Only build the instrument if it isn't already in the registry ObservableMeasurement obs = instrumentOnceRegistry.get(descriptorHash); if (obs == null) { - obs = meter + obs = + meter .upDownCounterBuilder(name) .setDescription(description) .setUnit(unit) @@ -447,7 +450,7 @@ public ObservableDoubleMeasurement registerDoubleUpDownCounterCallback( // to a group batchcallback using the registerBatchCallback function if (updater != null) { Consumer cb = proxiedDoubleObserver(descriptorHash, updater); - ObservableDoubleMeasurement finalObs = (ObservableDoubleMeasurement)obs; + ObservableDoubleMeasurement finalObs = (ObservableDoubleMeasurement) obs; meter.batchCallback(() -> cb.accept(finalObs), obs); } } else if (updater != null) { @@ -487,7 +490,8 @@ public ObservableLongMeasurement registerLongUpDownCounterCallback( // Only build the instrument if it isn't already in the registry ObservableMeasurement obs = instrumentOnceRegistry.get(descriptorHash); if (obs == null) { - obs = meter + obs = + meter .upDownCounterBuilder(name) .setDescription(description) .setUnit(unit) @@ -497,7 +501,7 @@ public ObservableLongMeasurement registerLongUpDownCounterCallback( // to a group batchcallback using the registerBatchCallback function if (updater != null) { Consumer cb = proxiedLongObserver(descriptorHash, updater); - ObservableLongMeasurement finalObs = (ObservableLongMeasurement)obs; + ObservableLongMeasurement finalObs = (ObservableLongMeasurement) obs; meter.batchCallback(() -> cb.accept(finalObs), obs); } } else if (updater != null) { From 8520d266fbf288bd119c84dc007a0f4a3823273b Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Tue, 11 Jul 2023 23:15:00 -0400 Subject: [PATCH 08/15] refactor groovymetricenvironment to use generics, and implement some PR feedback --- .../jmxmetrics/GroovyMetricEnvironment.java | 239 ++++++------------ 1 file changed, 76 insertions(+), 163 deletions(-) diff --git a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/GroovyMetricEnvironment.java b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/GroovyMetricEnvironment.java index b74c26874..f00bb8520 100644 --- a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/GroovyMetricEnvironment.java +++ b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/GroovyMetricEnvironment.java @@ -28,12 +28,10 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; -import java.util.logging.Logger; +import java.util.function.Supplier; import javax.annotation.Nullable; public class GroovyMetricEnvironment { - private static final Logger logger = Logger.getLogger(GroovyMetricEnvironment.class.getName()); - private final SdkMeterProvider meterProvider; private final Meter meter; @@ -243,27 +241,11 @@ public ObservableDoubleMeasurement registerDoubleValueCallback( InstrumentValueType.DOUBLE) .hashCode(); - // Only build the instrument if it isn't already in the registry - ObservableMeasurement obs = instrumentOnceRegistry.get(descriptorHash); - if (obs == null) { - obs = meter.gaugeBuilder(name).setDescription(description).setUnit(unit).buildObserver(); - instrumentOnceRegistry.put(descriptorHash, obs); - // If an updater was not provided, the measurement is expected to be added - // to a group batchcallback using the registerBatchCallback function - if (updater != null) { - Consumer cb = proxiedDoubleObserver(descriptorHash, updater); - ObservableDoubleMeasurement finalObs = (ObservableDoubleMeasurement) obs; - meter.batchCallback(() -> cb.accept(finalObs), obs); - } - } else if (updater != null) { - // If the instrument has already been built with the appropriate proxied observer, - // update the registry so that the callback has the appropriate updater function - AtomicReference> existingUpdater = - doubleUpdaterRegistry.get(descriptorHash); - existingUpdater.set(updater); - } - - return (ObservableDoubleMeasurement) obs; + return registerCallback( + doubleUpdaterRegistry, + () -> meter.gaugeBuilder(name).setDescription(description).setUnit(unit).buildObserver(), + descriptorHash, + updater); } /** @@ -285,33 +267,17 @@ public ObservableLongMeasurement registerLongValueCallback( name, description, unit, InstrumentType.OBSERVABLE_GAUGE, InstrumentValueType.LONG) .hashCode(); - // Only build the instrument if it isn't already in the registry - ObservableMeasurement obs = instrumentOnceRegistry.get(descriptorHash); - if (obs == null) { - obs = - meter - .gaugeBuilder(name) - .ofLongs() - .setDescription(description) - .setUnit(unit) - .buildObserver(); - instrumentOnceRegistry.put(descriptorHash, obs); - // If an updater was not provided, the measurement is expected to be added - // to a group batchcallback using the registerBatchCallback function - if (updater != null) { - Consumer cb = proxiedLongObserver(descriptorHash, updater); - ObservableLongMeasurement finalObs = (ObservableLongMeasurement) obs; - meter.batchCallback(() -> cb.accept(finalObs), obs); - } - } else if (updater != null) { - // If the instrument has already been built with the appropriate proxied observer, - // update the registry so that the callback has the appropriate updater function - AtomicReference> existingUpdater = - longUpdaterRegistry.get(descriptorHash); - existingUpdater.set(updater); - } - - return (ObservableLongMeasurement) obs; + return registerCallback( + longUpdaterRegistry, + () -> + meter + .gaugeBuilder(name) + .ofLongs() + .setDescription(description) + .setUnit(unit) + .buildObserver(), + descriptorHash, + updater); } /** @@ -337,33 +303,17 @@ public ObservableDoubleMeasurement registerDoubleCounterCallback( InstrumentValueType.DOUBLE) .hashCode(); - // Only build the instrument if it isn't already in the registry - ObservableMeasurement obs = instrumentOnceRegistry.get(descriptorHash); - if (obs == null) { - obs = - meter - .counterBuilder(name) - .setDescription(description) - .setUnit(unit) - .ofDoubles() - .buildObserver(); - instrumentOnceRegistry.put(descriptorHash, obs); - // If an updater was not provided, the measurement is expected to be added - // to a group batchcallback using the registerBatchCallback function - if (updater != null) { - Consumer cb = proxiedDoubleObserver(descriptorHash, updater); - ObservableDoubleMeasurement finalObs = (ObservableDoubleMeasurement) obs; - meter.batchCallback(() -> cb.accept(finalObs), obs); - } - } else if (updater != null) { - // If the instrument has already been built with the appropriate proxied observer, - // update the registry so that the callback has the appropriate updater function - AtomicReference> existingUpdater = - doubleUpdaterRegistry.get(descriptorHash); - existingUpdater.set(updater); - } - - return (ObservableDoubleMeasurement) obs; + return registerCallback( + doubleUpdaterRegistry, + () -> + meter + .counterBuilder(name) + .setDescription(description) + .setUnit(unit) + .ofDoubles() + .buildObserver(), + descriptorHash, + updater); } /** @@ -389,27 +339,11 @@ public ObservableLongMeasurement registerLongCounterCallback( InstrumentValueType.LONG) .hashCode(); - // Only build the instrument if it isn't already in the registry - ObservableMeasurement obs = instrumentOnceRegistry.get(descriptorHash); - if (obs == null) { - obs = meter.counterBuilder(name).setDescription(description).setUnit(unit).buildObserver(); - instrumentOnceRegistry.put(descriptorHash, obs); - // If an updater was not provided, the measurement is expected to be added - // to a group batchcallback using the registerBatchCallback function - if (updater != null) { - Consumer cb = proxiedLongObserver(descriptorHash, updater); - ObservableLongMeasurement finalObs = (ObservableLongMeasurement) obs; - meter.batchCallback(() -> cb.accept(finalObs), obs); - } - } else if (updater != null) { - // If the instrument has already been built with the appropriate proxied observer, - // update the registry so that the callback has the appropriate updater function - AtomicReference> existingUpdater = - longUpdaterRegistry.get(descriptorHash); - existingUpdater.set(updater); - } - - return (ObservableLongMeasurement) obs; + return registerCallback( + longUpdaterRegistry, + () -> meter.counterBuilder(name).setDescription(description).setUnit(unit).buildObserver(), + descriptorHash, + updater); } /** @@ -435,33 +369,17 @@ public ObservableDoubleMeasurement registerDoubleUpDownCounterCallback( InstrumentValueType.DOUBLE) .hashCode(); - // Only build the instrument if it isn't already in the registry - ObservableMeasurement obs = instrumentOnceRegistry.get(descriptorHash); - if (obs == null) { - obs = - meter - .upDownCounterBuilder(name) - .setDescription(description) - .setUnit(unit) - .ofDoubles() - .buildObserver(); - instrumentOnceRegistry.put(descriptorHash, obs); - // If an updater was not provided, the measurement is expected to be added - // to a group batchcallback using the registerBatchCallback function - if (updater != null) { - Consumer cb = proxiedDoubleObserver(descriptorHash, updater); - ObservableDoubleMeasurement finalObs = (ObservableDoubleMeasurement) obs; - meter.batchCallback(() -> cb.accept(finalObs), obs); - } - } else if (updater != null) { - // If the instrument has already been built with the appropriate proxied observer, - // update the registry so that the callback has the appropriate updater function - AtomicReference> existingUpdater = - doubleUpdaterRegistry.get(descriptorHash); - existingUpdater.set(updater); - } - - return (ObservableDoubleMeasurement) obs; + return registerCallback( + doubleUpdaterRegistry, + () -> + meter + .upDownCounterBuilder(name) + .setDescription(description) + .setUnit(unit) + .ofDoubles() + .buildObserver(), + descriptorHash, + updater); } /** @@ -487,32 +405,43 @@ public ObservableLongMeasurement registerLongUpDownCounterCallback( InstrumentValueType.LONG) .hashCode(); + return registerCallback( + longUpdaterRegistry, + () -> + meter + .upDownCounterBuilder(name) + .setDescription(description) + .setUnit(unit) + .buildObserver(), + descriptorHash, + updater); + } + + private T registerCallback( + final Map>> registry, + final Supplier observerBuilder, + final int descriptorHash, + final Consumer updater) { + // Only build the instrument if it isn't already in the registry ObservableMeasurement obs = instrumentOnceRegistry.get(descriptorHash); if (obs == null) { - obs = - meter - .upDownCounterBuilder(name) - .setDescription(description) - .setUnit(unit) - .buildObserver(); - instrumentOnceRegistry.put(descriptorHash, obs); + T observer = observerBuilder.get(); + instrumentOnceRegistry.put(descriptorHash, observer); // If an updater was not provided, the measurement is expected to be added // to a group batchcallback using the registerBatchCallback function if (updater != null) { - Consumer cb = proxiedLongObserver(descriptorHash, updater); - ObservableLongMeasurement finalObs = (ObservableLongMeasurement) obs; - meter.batchCallback(() -> cb.accept(finalObs), obs); + Consumer cb = proxiedObserver(descriptorHash, registry, updater); + meter.batchCallback(() -> cb.accept(observer), observer); } + return observer; } else if (updater != null) { // If the instrument has already been built with the appropriate proxied observer, // update the registry so that the callback has the appropriate updater function - AtomicReference> existingUpdater = - longUpdaterRegistry.get(descriptorHash); - existingUpdater.set(updater); + registry.get(descriptorHash).set(updater); } - return (ObservableLongMeasurement) obs; + return (T) obs; } /** @@ -543,28 +472,12 @@ public void registerBatchCallback( } } - private Consumer proxiedDoubleObserver( - final int descriptorHash, final Consumer updater) { - doubleUpdaterRegistry.putIfAbsent(descriptorHash, new AtomicReference<>()); - AtomicReference> existingUpdater = - doubleUpdaterRegistry.get(descriptorHash); - existingUpdater.set(updater); - return doubleResult -> { - Consumer existing = - doubleUpdaterRegistry.get(descriptorHash).get(); - existing.accept(doubleResult); - }; - } - - private Consumer proxiedLongObserver( - final int descriptorHash, final Consumer updater) { - longUpdaterRegistry.putIfAbsent(descriptorHash, new AtomicReference<>()); - AtomicReference> existingUpdater = - longUpdaterRegistry.get(descriptorHash); - existingUpdater.set(updater); - return longResult -> { - Consumer existing = longUpdaterRegistry.get(descriptorHash).get(); - existing.accept(longResult); - }; + private Consumer proxiedObserver( + final int descriptorHash, + final Map>> registry, + final Consumer updater) { + registry.putIfAbsent(descriptorHash, new AtomicReference<>()); + registry.get(descriptorHash).set(updater); + return longResult -> registry.get(descriptorHash).get().accept(longResult); } } From 410454e1dbff94e3a42718082686779b57e1521f Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Wed, 12 Jul 2023 00:02:05 -0400 Subject: [PATCH 09/15] Address more PR feedback and apply editorconfig --- .../jmxmetrics/InstrumentHelper.groovy | 255 ++++++++++-------- 1 file changed, 145 insertions(+), 110 deletions(-) diff --git a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelper.groovy b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelper.groovy index 1966e0556..1d318519f 100644 --- a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelper.groovy +++ b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelper.groovy @@ -10,6 +10,7 @@ import groovy.transform.PackageScope import io.opentelemetry.api.metrics.ObservableMeasurement import javax.management.AttributeNotFoundException +import javax.management.InvalidAttributeValueException import java.util.logging.Logger import javax.management.openmbean.CompositeData @@ -20,14 +21,12 @@ import javax.management.openmbean.CompositeData * * Intended to be used via the script-bound `otel` {@link OtelHelper} instance methods: * - * def threadCount = otel.instrument(myThreadingMBeanHelper, + * otel.instrument(myThreadingMBeanHelper, * "jvm.threads.count", "number of threads", * "1", [ * "myLabel": { mbean -> mbean.name().getKeyProperty("myObjectNameProperty") }, - * "myOtherLabel": { "myLabelValue" } - * ], "ThreadCount", otel.&longUpDownCounter) + * "myOtherLabel": { "myLabelValue" }* ], "ThreadCount", otel.&longUpDownCounter) * - * threadCount.update() * * If the underlying MBean(s) held by the MBeanHelper are * {@link CompositeData} instances, each key of their CompositeType's @@ -35,94 +34,127 @@ import javax.management.openmbean.CompositeData * updated for each respective value. */ class InstrumentHelper { - private static final Logger logger = Logger.getLogger(InstrumentHelper.class.getName()) - - private final MBeanHelper mBeanHelper - private final String instrumentName - private final String description - private final String unit - private final Map> mBeanAttributes - private final Map labelFuncs - private final Closure instrument - private final GroovyMetricEnvironment metricEnvironment - - /** - * An InstrumentHelper provides the ability to easily create and update {@link io.opentelemetry.api.metrics.Instrument} - * instances from an MBeanHelper's underlying {@link GroovyMBean} instances via an {@link OtelHelper}'s instrument - * method pointer. - * - * @param mBeanHelper - the single or multiple {@link GroovyMBean}-representing MBeanHelper from which to access attribute values - * @param instrumentName - the resulting instruments' name to register. - * @param description - the resulting instruments' description to register. - * @param unit - the resulting instruments' unit to register. - * @param labelFuncs - A {@link Map} of label names and values to be determined by custom - * {@link GroovyMBean}-provided Closures: (e.g. [ "myLabelName" : { mbean -> "myLabelValue"} ]). The - * resulting Label instances will be used for each individual update. - * @param attribute - The {@link GroovyMBean} attribute for which to use as the instrument value. - * @param instrument - The {@link io.opentelemetry.api.metrics.Instrument}-producing {@link OtelHelper} method pointer: - * (e.g. new OtelHelper().&doubleValueRecorder) - */ - InstrumentHelper(MBeanHelper mBeanHelper, String instrumentName, String description, String unit, Map> labelFuncs, Map>> MBeanAttributes, Closure instrument, GroovyMetricEnvironment metricEnvironment) { - this.mBeanHelper = mBeanHelper - this.instrumentName = instrumentName - this.description = description - this.unit = unit - this.labelFuncs = labelFuncs - this.mBeanAttributes = MBeanAttributes - this.instrument = instrument - this.metricEnvironment = metricEnvironment + private static final Logger logger = Logger.getLogger(InstrumentHelper.class.getName()) + + private final MBeanHelper mBeanHelper + private final String instrumentName + private final String description + private final String unit + private final Map> mBeanAttributes + private final Map labelFuncs + private final Closure instrument + private final GroovyMetricEnvironment metricEnvironment + + /** + * An InstrumentHelper provides the ability to easily create and update {@link io.opentelemetry.api.metrics.Instrument} + * instances from an MBeanHelper's underlying {@link GroovyMBean} instances via an {@link OtelHelper}'s instrument + * method pointer. + * + * @param mBeanHelper - the single or multiple {@link GroovyMBean}-representing MBeanHelper from which to access attribute values + * @param instrumentName - the resulting instruments' name to register. + * @param description - the resulting instruments' description to register. + * @param unit - the resulting instruments' unit to register. + * @param labelFuncs - A {@link Map} of label names and values to be determined by custom + * {@link GroovyMBean}-provided Closures: (e.g. [ "myLabelName" : { mbean -> "myLabelValue"} ]). The + * resulting Label instances will be used for each individual update. + * @param attribute - The {@link GroovyMBean} attribute for which to use as the instrument value. + * @param instrument - The {@link io.opentelemetry.api.metrics.Instrument}-producing {@link OtelHelper} method pointer: + * (e.g. new OtelHelper().&doubleValueRecorder) + * @param metricenvironment - The {@link GroovyMetricEnvironment} used to register callbacks onto the SDK meter for + * batch callbacks used to handle {@link CompositeData} + */ + InstrumentHelper(MBeanHelper mBeanHelper, String instrumentName, String description, String unit, Map> labelFuncs, Map>> MBeanAttributes, Closure instrument, GroovyMetricEnvironment metricEnvironment) { + this.mBeanHelper = mBeanHelper + this.instrumentName = instrumentName + this.description = description + this.unit = unit + this.labelFuncs = labelFuncs + this.mBeanAttributes = MBeanAttributes + this.instrument = instrument + this.metricEnvironment = metricEnvironment + } + + void update() { + def mbeans = mBeanHelper.getMBeans() + def compositeAttributes = [] + def simpleAttributes = [] + if (mbeans.size() == 0) { + return } - void update() { - def mbeans = mBeanHelper.getMBeans() - def compositeAttributes = [] - def simpleAttributes = [] - if (mbeans.size() == 0) { - return - } - // Look at the first mbean collected to evaluate if the attributes requested are + mBeanAttributes.keySet().each { attribute -> + try { + // Look at the collected mbeans to evaluate if the attributes requested are // composite data types or simple. Composite types require different parsing to // end up with multiple recorders in the same callback. - def bean = mbeans.first() - mBeanAttributes.keySet().each { attribute -> - try { - def value = bean.getProperty(attribute) - if (value instanceof CompositeData) { - compositeAttributes.add(new Tuple2>(attribute, value.getCompositeType().keySet())) - } else { - simpleAttributes.add(attribute) - } - } catch (AttributeNotFoundException | NullPointerException e) { - simpleAttributes.add(attribute) - } + if (isAttributeComposite(attribute, mbeans)) { + def value = (CompositeData) mbeans.first().getProperty(attribute) + compositeAttributes.add(new Tuple2>(attribute, value.getCompositeType().keySet())) + } else { + simpleAttributes.add(attribute) } + } catch (AttributeNotFoundException ignored) { + logger.fine("Attribute ${attribute} not found on any of the collected mbeans") + } catch (InvalidAttributeValueException ignored) { + logger.info("Attribute ${attribute} was not consistently CompositeData for " + + "collected mbeans. The metrics gatherer cannot collect measurements for an instrument " + + "when the mbeans attribute values are not all CompositeData or all simple values.") + } + } - if (simpleAttributes.size() > 0) { - def simpleUpdateClosure = prepareUpdateClosure(mbeans, simpleAttributes) - if (instrumentIsDoubleObserver(instrument) || instrumentIsLongObserver(instrument)) { - instrument(instrumentName, description, unit, { result -> - simpleUpdateClosure(result) - }) - } else { - simpleUpdateClosure(instrument(instrumentName, description, unit)) - } - } + if (simpleAttributes.size() > 0) { + def simpleUpdateClosure = prepareUpdateClosure(mbeans, simpleAttributes) + if (instrumentIsDoubleObserver(instrument) || instrumentIsLongObserver(instrument)) { + instrument(instrumentName, description, unit, { result -> + simpleUpdateClosure(result) + }) + } else { + simpleUpdateClosure(instrument(instrumentName, description, unit)) + } + } - if (compositeAttributes.size() > 0) { - registerCompositeUpdateClosures(mbeans, compositeAttributes) - } + if (compositeAttributes.size() > 0) { + registerCompositeUpdateClosures(mbeans, compositeAttributes) } + } - private static Map getLabels(GroovyMBean mbean, Map labelFuncs, Map additionalLabels) { - def labels = [:] - labelFuncs.each { label, labelFunc -> - labels[label] = labelFunc(mbean) as String + // This function checks all the provided MBeans to see if they are consistently CompositeData or + // a simple value for the given attribute. If they are inconsistent, it will throw an exception. + private static boolean isAttributeComposite(String attribute, List beans) throws AttributeNotFoundException, InvalidAttributeValueException { + def allComposite = beans.collect { bean -> + try { + def value = bean.getProperty(attribute) + if (value instanceof CompositeData) { + true + } else { + false } - additionalLabels.each {label, labelFunc -> - labels[label] = labelFunc(mbean) as String - } - return labels + } catch (AttributeNotFoundException | NullPointerException ignored) { + null + } + }.findAll { it != null } + .toSet() + + switch (allComposite.size()) { + case 0: + throw new AttributeNotFoundException() + case 1: + return allComposite.contains(true) + default: + throw new InvalidAttributeValueException() } + } + + private static Map getLabels(GroovyMBean mbean, Map labelFuncs, Map additionalLabels) { + def labels = [:] + labelFuncs.each { label, labelFunc -> + labels[label] = labelFunc(mbean) as String + } + additionalLabels.each { label, labelFunc -> + labels[label] = labelFunc(mbean) as String + } + return labels + } // Create a closure for simple attributes that will retrieve mbean information on // callback to ensure that metrics are collected on request @@ -137,7 +169,7 @@ class InstrumentHelper { logger.fine("Recording ${instrumentName} - ${instrument.method} w/ ${value} - ${labels}") recordDataPoint(instrument, result, value, GroovyMetricEnvironment.mapToAttributes(labels)) } - } catch (AttributeNotFoundException e ) { + } catch (AttributeNotFoundException ignored) { logger.info("Expected attribute ${attribute} not found in mbean ${mbean.name()}") } } @@ -164,49 +196,52 @@ class InstrumentHelper { recordDataPoint(instrument, inst.v2, val, GroovyMetricEnvironment.mapToAttributes(labels)) } } - } catch (AttributeNotFoundException e ) { + } catch (AttributeNotFoundException ignored) { logger.info("Expected attribute ${attribute} not found in mbean ${mbean.name()}") } } - }, instruments.first().v2, *instruments.tail().collect {it.v2 }) + }, instruments.first().v2, *instruments.tail().collect { it.v2 }) } } // Based on the type of instrument, record the data point in the way expected by the observable private static void recordDataPoint(inst, result, value, labelMap) { if (instrumentIsLongObserver(inst)) { - result.record((long) value, labelMap) + result.record((long) value, labelMap) } else if (instrumentIsDoubleObserver(inst)) { - result.record((double) value, labelMap) + result.record((double) value, labelMap) } else if (instrumentIsCounter(inst)) { - result.add(value, labelMap) + result.add(value, labelMap) } else { - result.record(value, labelMap) + result.record(value, labelMap) } } - @PackageScope static boolean instrumentIsDoubleObserver(inst) { - return [ - "doubleCounterCallback", - "doubleUpDownCounterCallback", - "doubleValueCallback", - ].contains(inst.method) - } + @PackageScope + static boolean instrumentIsDoubleObserver(inst) { + return [ + "doubleCounterCallback", + "doubleUpDownCounterCallback", + "doubleValueCallback", + ].contains(inst.method) + } - @PackageScope static boolean instrumentIsLongObserver(inst) { - return [ - "longCounterCallback", - "longUpDownCounterCallback", - "longValueCallback", - ].contains(inst.method) - } + @PackageScope + static boolean instrumentIsLongObserver(inst) { + return [ + "longCounterCallback", + "longUpDownCounterCallback", + "longValueCallback", + ].contains(inst.method) + } - @PackageScope static boolean instrumentIsCounter(inst) { - return [ - "doubleCounter", - "doubleUpDownCounter", - "longCounter", - "longUpDownCounter" - ].contains(inst.method) - } + @PackageScope + static boolean instrumentIsCounter(inst) { + return [ + "doubleCounter", + "doubleUpDownCounter", + "longCounter", + "longUpDownCounter" + ].contains(inst.method) + } } From 8a0ace8bd540c904fafeef1eed3a313335750c2b Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Wed, 12 Jul 2023 00:05:23 -0400 Subject: [PATCH 10/15] undo significant formatting changes --- .../jmxmetrics/InstrumentHelper.groovy | 391 +++++++++--------- 1 file changed, 196 insertions(+), 195 deletions(-) diff --git a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelper.groovy b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelper.groovy index 1d318519f..f6ce64f70 100644 --- a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelper.groovy +++ b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelper.groovy @@ -25,7 +25,8 @@ import javax.management.openmbean.CompositeData * "jvm.threads.count", "number of threads", * "1", [ * "myLabel": { mbean -> mbean.name().getKeyProperty("myObjectNameProperty") }, - * "myOtherLabel": { "myLabelValue" }* ], "ThreadCount", otel.&longUpDownCounter) + * "myOtherLabel": { "myLabelValue" } + * ], "ThreadCount", otel.&longUpDownCounter) * * * If the underlying MBean(s) held by the MBeanHelper are @@ -34,214 +35,214 @@ import javax.management.openmbean.CompositeData * updated for each respective value. */ class InstrumentHelper { - private static final Logger logger = Logger.getLogger(InstrumentHelper.class.getName()) - - private final MBeanHelper mBeanHelper - private final String instrumentName - private final String description - private final String unit - private final Map> mBeanAttributes - private final Map labelFuncs - private final Closure instrument - private final GroovyMetricEnvironment metricEnvironment - - /** - * An InstrumentHelper provides the ability to easily create and update {@link io.opentelemetry.api.metrics.Instrument} - * instances from an MBeanHelper's underlying {@link GroovyMBean} instances via an {@link OtelHelper}'s instrument - * method pointer. - * - * @param mBeanHelper - the single or multiple {@link GroovyMBean}-representing MBeanHelper from which to access attribute values - * @param instrumentName - the resulting instruments' name to register. - * @param description - the resulting instruments' description to register. - * @param unit - the resulting instruments' unit to register. - * @param labelFuncs - A {@link Map} of label names and values to be determined by custom - * {@link GroovyMBean}-provided Closures: (e.g. [ "myLabelName" : { mbean -> "myLabelValue"} ]). The - * resulting Label instances will be used for each individual update. - * @param attribute - The {@link GroovyMBean} attribute for which to use as the instrument value. - * @param instrument - The {@link io.opentelemetry.api.metrics.Instrument}-producing {@link OtelHelper} method pointer: - * (e.g. new OtelHelper().&doubleValueRecorder) - * @param metricenvironment - The {@link GroovyMetricEnvironment} used to register callbacks onto the SDK meter for - * batch callbacks used to handle {@link CompositeData} - */ - InstrumentHelper(MBeanHelper mBeanHelper, String instrumentName, String description, String unit, Map> labelFuncs, Map>> MBeanAttributes, Closure instrument, GroovyMetricEnvironment metricEnvironment) { - this.mBeanHelper = mBeanHelper - this.instrumentName = instrumentName - this.description = description - this.unit = unit - this.labelFuncs = labelFuncs - this.mBeanAttributes = MBeanAttributes - this.instrument = instrument - this.metricEnvironment = metricEnvironment - } - - void update() { - def mbeans = mBeanHelper.getMBeans() - def compositeAttributes = [] - def simpleAttributes = [] - if (mbeans.size() == 0) { - return + private static final Logger logger = Logger.getLogger(InstrumentHelper.class.getName()) + + private final MBeanHelper mBeanHelper + private final String instrumentName + private final String description + private final String unit + private final Map> mBeanAttributes + private final Map labelFuncs + private final Closure instrument + private final GroovyMetricEnvironment metricEnvironment + + /** + * An InstrumentHelper provides the ability to easily create and update {@link io.opentelemetry.api.metrics.Instrument} + * instances from an MBeanHelper's underlying {@link GroovyMBean} instances via an {@link OtelHelper}'s instrument + * method pointer. + * + * @param mBeanHelper - the single or multiple {@link GroovyMBean}-representing MBeanHelper from which to access attribute values + * @param instrumentName - the resulting instruments' name to register. + * @param description - the resulting instruments' description to register. + * @param unit - the resulting instruments' unit to register. + * @param labelFuncs - A {@link Map} of label names and values to be determined by custom + * {@link GroovyMBean}-provided Closures: (e.g. [ "myLabelName" : { mbean -> "myLabelValue"} ]). The + * resulting Label instances will be used for each individual update. + * @param attribute - The {@link GroovyMBean} attribute for which to use as the instrument value. + * @param instrument - The {@link io.opentelemetry.api.metrics.Instrument}-producing {@link OtelHelper} method pointer: + * (e.g. new OtelHelper().&doubleValueRecorder) + * @param metricenvironment - The {@link GroovyMetricEnvironment} used to register callbacks onto the SDK meter for + * batch callbacks used to handle {@link CompositeData} + */ + InstrumentHelper(MBeanHelper mBeanHelper, String instrumentName, String description, String unit, Map> labelFuncs, Map>> MBeanAttributes, Closure instrument, GroovyMetricEnvironment metricEnvironment) { + this.mBeanHelper = mBeanHelper + this.instrumentName = instrumentName + this.description = description + this.unit = unit + this.labelFuncs = labelFuncs + this.mBeanAttributes = MBeanAttributes + this.instrument = instrument + this.metricEnvironment = metricEnvironment } - mBeanAttributes.keySet().each { attribute -> - try { - // Look at the collected mbeans to evaluate if the attributes requested are - // composite data types or simple. Composite types require different parsing to - // end up with multiple recorders in the same callback. - if (isAttributeComposite(attribute, mbeans)) { - def value = (CompositeData) mbeans.first().getProperty(attribute) - compositeAttributes.add(new Tuple2>(attribute, value.getCompositeType().keySet())) - } else { - simpleAttributes.add(attribute) + void update() { + def mbeans = mBeanHelper.getMBeans() + def compositeAttributes = [] + def simpleAttributes = [] + if (mbeans.size() == 0) { + return } - } catch (AttributeNotFoundException ignored) { - logger.fine("Attribute ${attribute} not found on any of the collected mbeans") - } catch (InvalidAttributeValueException ignored) { - logger.info("Attribute ${attribute} was not consistently CompositeData for " + - "collected mbeans. The metrics gatherer cannot collect measurements for an instrument " + - "when the mbeans attribute values are not all CompositeData or all simple values.") - } - } - if (simpleAttributes.size() > 0) { - def simpleUpdateClosure = prepareUpdateClosure(mbeans, simpleAttributes) - if (instrumentIsDoubleObserver(instrument) || instrumentIsLongObserver(instrument)) { - instrument(instrumentName, description, unit, { result -> - simpleUpdateClosure(result) - }) - } else { - simpleUpdateClosure(instrument(instrumentName, description, unit)) - } - } + mBeanAttributes.keySet().each { attribute -> + try { + // Look at the collected mbeans to evaluate if the attributes requested are + // composite data types or simple. Composite types require different parsing to + // end up with multiple recorders in the same callback. + if (isAttributeComposite(attribute, mbeans)) { + def value = (CompositeData) mbeans.first().getProperty(attribute) + compositeAttributes.add(new Tuple2>(attribute, value.getCompositeType().keySet())) + } else { + simpleAttributes.add(attribute) + } + } catch (AttributeNotFoundException ignored) { + logger.fine("Attribute ${attribute} not found on any of the collected mbeans") + } catch (InvalidAttributeValueException ignored) { + logger.info("Attribute ${attribute} was not consistently CompositeData for " + + "collected mbeans. The metrics gatherer cannot collect measurements for an instrument " + + "when the mbeans attribute values are not all CompositeData or all simple values.") + } + } - if (compositeAttributes.size() > 0) { - registerCompositeUpdateClosures(mbeans, compositeAttributes) + if (simpleAttributes.size() > 0) { + def simpleUpdateClosure = prepareUpdateClosure(mbeans, simpleAttributes) + if (instrumentIsDoubleObserver(instrument) || instrumentIsLongObserver(instrument)) { + instrument(instrumentName, description, unit, { result -> + simpleUpdateClosure(result) + }) + } else { + simpleUpdateClosure(instrument(instrumentName, description, unit)) + } + } + + if (compositeAttributes.size() > 0) { + registerCompositeUpdateClosures(mbeans, compositeAttributes) + } } - } - - // This function checks all the provided MBeans to see if they are consistently CompositeData or - // a simple value for the given attribute. If they are inconsistent, it will throw an exception. - private static boolean isAttributeComposite(String attribute, List beans) throws AttributeNotFoundException, InvalidAttributeValueException { - def allComposite = beans.collect { bean -> - try { - def value = bean.getProperty(attribute) - if (value instanceof CompositeData) { - true - } else { - false + + // This function checks all the provided MBeans to see if they are consistently CompositeData or + // a simple value for the given attribute. If they are inconsistent, it will throw an exception. + private static boolean isAttributeComposite(String attribute, List beans) throws AttributeNotFoundException, InvalidAttributeValueException { + def allComposite = beans.collect { bean -> + try { + def value = bean.getProperty(attribute) + if (value instanceof CompositeData) { + true + } else { + false + } + } catch (AttributeNotFoundException | NullPointerException ignored) { + null + } + }.findAll { it != null } + .toSet() + + switch (allComposite.size()) { + case 0: + throw new AttributeNotFoundException() + case 1: + return allComposite.contains(true) + default: + throw new InvalidAttributeValueException() } - } catch (AttributeNotFoundException | NullPointerException ignored) { - null - } - }.findAll { it != null } - .toSet() - - switch (allComposite.size()) { - case 0: - throw new AttributeNotFoundException() - case 1: - return allComposite.contains(true) - default: - throw new InvalidAttributeValueException() } - } - private static Map getLabels(GroovyMBean mbean, Map labelFuncs, Map additionalLabels) { - def labels = [:] - labelFuncs.each { label, labelFunc -> - labels[label] = labelFunc(mbean) as String + private static Map getLabels(GroovyMBean mbean, Map labelFuncs, Map additionalLabels) { + def labels = [:] + labelFuncs.each { label, labelFunc -> + labels[label] = labelFunc(mbean) as String + } + additionalLabels.each { label, labelFunc -> + labels[label] = labelFunc(mbean) as String + } + return labels } - additionalLabels.each { label, labelFunc -> - labels[label] = labelFunc(mbean) as String + + // Create a closure for simple attributes that will retrieve mbean information on + // callback to ensure that metrics are collected on request + private Closure prepareUpdateClosure(List mbeans, attributes) { + return { result -> + [mbeans, attributes].combinations().each { pair -> + def (mbean, attribute) = pair + try { + def value = mbean.getProperty(attribute) + if (value != null) { + def labels = getLabels(mbean, labelFuncs, mBeanAttributes[attribute]) + logger.fine("Recording ${instrumentName} - ${instrument.method} w/ ${value} - ${labels}") + recordDataPoint(instrument, result, value, GroovyMetricEnvironment.mapToAttributes(labels)) + } + } catch (AttributeNotFoundException ignored) { + logger.info("Expected attribute ${attribute} not found in mbean ${mbean.name()}") + } + } + } } - return labels - } - - // Create a closure for simple attributes that will retrieve mbean information on - // callback to ensure that metrics are collected on request - private Closure prepareUpdateClosure(List mbeans, attributes) { - return { result -> - [mbeans, attributes].combinations().each { pair -> - def (mbean, attribute) = pair - try { - def value = mbean.getProperty(attribute) - if (value != null) { - def labels = getLabels(mbean, labelFuncs, mBeanAttributes[attribute]) - logger.fine("Recording ${instrumentName} - ${instrument.method} w/ ${value} - ${labels}") - recordDataPoint(instrument, result, value, GroovyMetricEnvironment.mapToAttributes(labels)) - } - } catch (AttributeNotFoundException ignored) { - logger.info("Expected attribute ${attribute} not found in mbean ${mbean.name()}") + + // Create a closure for composite data attributes that will retrieve mbean information + // on callback to ensure that metrics are collected on request. This will create a single + // batch callback for all of the metrics collected on a single attribute. + private void registerCompositeUpdateClosures(List mbeans, attributes) { + attributes.each { pair -> + def (attribute, keys) = pair + def instruments = keys.collect { new Tuple2(it, instrument("${instrumentName}.${it}", description, unit, null)) } + + metricEnvironment.registerBatchCallback("${instrumentName}.${attribute}", () -> { + mbeans.each { mbean -> + try { + def value = mbean.getProperty(attribute) + if (value != null && value instanceof CompositeData) { + instruments.each { inst -> + def val = value.get(inst.v1) + def labels = getLabels(mbean, labelFuncs, mBeanAttributes[attribute]) + logger.fine("Recording ${"${instrumentName}.${inst.v1}"} - ${instrument.method} w/ ${val} - ${labels}") + recordDataPoint(instrument, inst.v2, val, GroovyMetricEnvironment.mapToAttributes(labels)) + } + } + } catch (AttributeNotFoundException ignored) { + logger.info("Expected attribute ${attribute} not found in mbean ${mbean.name()}") + } + } + }, instruments.first().v2, *instruments.tail().collect { it.v2 }) } - } } - } - - // Create a closure for composite data attributes that will retrieve mbean information - // on callback to ensure that metrics are collected on request. This will create a single - // batch callback for all of the metrics collected on a single attribute. - private void registerCompositeUpdateClosures(List mbeans, attributes) { - attributes.each { pair -> - def (attribute, keys) = pair - def instruments = keys.collect { new Tuple2(it, instrument("${instrumentName}.${it}", description, unit, null)) } - - metricEnvironment.registerBatchCallback("${instrumentName}.${attribute}", () -> { - mbeans.each { mbean -> - try { - def value = mbean.getProperty(attribute) - if (value != null && value instanceof CompositeData) { - instruments.each { inst -> - def val = value.get(inst.v1) - def labels = getLabels(mbean, labelFuncs, mBeanAttributes[attribute]) - logger.fine("Recording ${"${instrumentName}.${inst.v1}"} - ${instrument.method} w/ ${val} - ${labels}") - recordDataPoint(instrument, inst.v2, val, GroovyMetricEnvironment.mapToAttributes(labels)) - } - } - } catch (AttributeNotFoundException ignored) { - logger.info("Expected attribute ${attribute} not found in mbean ${mbean.name()}") - } + + // Based on the type of instrument, record the data point in the way expected by the observable + private static void recordDataPoint(inst, result, value, labelMap) { + if (instrumentIsLongObserver(inst)) { + result.record((long) value, labelMap) + } else if (instrumentIsDoubleObserver(inst)) { + result.record((double) value, labelMap) + } else if (instrumentIsCounter(inst)) { + result.add(value, labelMap) + } else { + result.record(value, labelMap) } - }, instruments.first().v2, *instruments.tail().collect { it.v2 }) } - } - - // Based on the type of instrument, record the data point in the way expected by the observable - private static void recordDataPoint(inst, result, value, labelMap) { - if (instrumentIsLongObserver(inst)) { - result.record((long) value, labelMap) - } else if (instrumentIsDoubleObserver(inst)) { - result.record((double) value, labelMap) - } else if (instrumentIsCounter(inst)) { - result.add(value, labelMap) - } else { - result.record(value, labelMap) + + @PackageScope + static boolean instrumentIsDoubleObserver(inst) { + return [ + "doubleCounterCallback", + "doubleUpDownCounterCallback", + "doubleValueCallback", + ].contains(inst.method) + } + + @PackageScope + static boolean instrumentIsLongObserver(inst) { + return [ + "longCounterCallback", + "longUpDownCounterCallback", + "longValueCallback", + ].contains(inst.method) + } + + @PackageScope + static boolean instrumentIsCounter(inst) { + return [ + "doubleCounter", + "doubleUpDownCounter", + "longCounter", + "longUpDownCounter" + ].contains(inst.method) } - } - - @PackageScope - static boolean instrumentIsDoubleObserver(inst) { - return [ - "doubleCounterCallback", - "doubleUpDownCounterCallback", - "doubleValueCallback", - ].contains(inst.method) - } - - @PackageScope - static boolean instrumentIsLongObserver(inst) { - return [ - "longCounterCallback", - "longUpDownCounterCallback", - "longValueCallback", - ].contains(inst.method) - } - - @PackageScope - static boolean instrumentIsCounter(inst) { - return [ - "doubleCounter", - "doubleUpDownCounter", - "longCounter", - "longUpDownCounter" - ].contains(inst.method) - } } From f069b9b5ff9b627f069140209b733e89706caaf9 Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Wed, 12 Jul 2023 10:17:08 -0400 Subject: [PATCH 11/15] Update jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/GroovyMetricEnvironment.java Co-authored-by: Ryan Fitzpatrick <10867373+rmfitzpatrick@users.noreply.github.com> --- .../contrib/jmxmetrics/GroovyMetricEnvironment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/GroovyMetricEnvironment.java b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/GroovyMetricEnvironment.java index f00bb8520..32bb58bcc 100644 --- a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/GroovyMetricEnvironment.java +++ b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/GroovyMetricEnvironment.java @@ -478,6 +478,6 @@ private Consumer proxiedObserver( final Consumer updater) { registry.putIfAbsent(descriptorHash, new AtomicReference<>()); registry.get(descriptorHash).set(updater); - return longResult -> registry.get(descriptorHash).get().accept(longResult); + return result -> registry.get(descriptorHash).get().accept(result); } } From a604af92a5962b36a0c5235cbb56725ce3a54d44 Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Thu, 13 Jul 2023 16:58:51 -0400 Subject: [PATCH 12/15] Allow different mbeans within the same metric to have different composite data keys --- .../jmxmetrics/GroovyMetricEnvironment.java | 38 +++++++++++--- .../jmxmetrics/InstrumentHelper.groovy | 50 ++++++++++++------- 2 files changed, 63 insertions(+), 25 deletions(-) diff --git a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/GroovyMetricEnvironment.java b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/GroovyMetricEnvironment.java index f00bb8520..8e43b5b93 100644 --- a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/GroovyMetricEnvironment.java +++ b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/GroovyMetricEnvironment.java @@ -6,8 +6,10 @@ package io.opentelemetry.contrib.jmxmetrics; import groovy.lang.Closure; +import groovy.lang.Tuple2; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.api.metrics.BatchCallback; import io.opentelemetry.api.metrics.DoubleCounter; import io.opentelemetry.api.metrics.DoubleHistogram; import io.opentelemetry.api.metrics.DoubleUpDownCounter; @@ -22,13 +24,17 @@ import io.opentelemetry.sdk.metrics.InstrumentType; import io.opentelemetry.sdk.metrics.InstrumentValueType; import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.Supplier; +import java.util.stream.Collectors; import javax.annotation.Nullable; public class GroovyMetricEnvironment { @@ -46,6 +52,8 @@ public class GroovyMetricEnvironment { doubleUpdaterRegistry = new ConcurrentHashMap<>(); private final Map>> batchUpdaterRegistry = new ConcurrentHashMap<>(); + private final Map>> + batchCallbackRegistry = new ConcurrentHashMap<>(); private final Map instrumentOnceRegistry = new ConcurrentHashMap<>(); @@ -460,15 +468,31 @@ public void registerBatchCallback( int hash = identifier.hashCode(); // Store the callback in the registry so the proxied callback always runs the latest // metric collection closure - AtomicReference> existing = - batchUpdaterRegistry.putIfAbsent(hash, new AtomicReference<>()); + batchUpdaterRegistry.putIfAbsent(hash, new AtomicReference<>()); batchUpdaterRegistry.get(hash).set(callback); - // If the callback had previously been stored, we assume the back callback is already - // registered with the Meter - if (existing == null) { - meter.batchCallback( - () -> batchUpdaterRegistry.get(hash).get().call(), measurement, additional); + // collect the set of instruments into a set so we can compare to what's previously been + // registered + Set instrumentSet = + Arrays.stream(additional).collect(Collectors.toCollection(HashSet::new)); + instrumentSet.add(measurement); + + Tuple2> existingCallback = + batchCallbackRegistry.get(hash); + // If this is our first attempt to register this callback or the list of relevant instruments + // has changed, we need register the callback. + if (existingCallback == null || !existingCallback.getV2().equals(instrumentSet)) { + // If the callback has already been created, and we're here to update the set of instruments + // make sure we close the previous callback + if (existingCallback != null) { + existingCallback.getV1().close(); + } + batchCallbackRegistry.put( + hash, + new Tuple2<>( + meter.batchCallback( + () -> batchUpdaterRegistry.get(hash).get().call(), measurement, additional), + instrumentSet)); } } diff --git a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelper.groovy b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelper.groovy index f6ce64f70..83d64c8a5 100644 --- a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelper.groovy +++ b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelper.groovy @@ -88,9 +88,9 @@ class InstrumentHelper { // Look at the collected mbeans to evaluate if the attributes requested are // composite data types or simple. Composite types require different parsing to // end up with multiple recorders in the same callback. - if (isAttributeComposite(attribute, mbeans)) { - def value = (CompositeData) mbeans.first().getProperty(attribute) - compositeAttributes.add(new Tuple2>(attribute, value.getCompositeType().keySet())) + def keySet = getCompositeKeys(attribute, mbeans) + if (keySet.size() > 0) { + compositeAttributes.add(new Tuple2>(attribute, keySet)) } else { simpleAttributes.add(attribute) } @@ -119,31 +119,45 @@ class InstrumentHelper { } } - // This function checks all the provided MBeans to see if they are consistently CompositeData or - // a simple value for the given attribute. If they are inconsistent, it will throw an exception. - private static boolean isAttributeComposite(String attribute, List beans) throws AttributeNotFoundException, InvalidAttributeValueException { - def allComposite = beans.collect { bean -> + // This function retrieves the set of CompositeData keys for the given attribute for the currently + // collected mbeans. If the attribute is all simple values it will return an empty list. + // If the attribute is inconsistent across mbeans, it will throw an exception. + private static Set getCompositeKeys(String attribute, List beans) throws AttributeNotFoundException, InvalidAttributeValueException { + def isComposite = false + def isFound = false + def keySet = beans.collect { bean -> try { def value = bean.getProperty(attribute) + def keys = [] if (value instanceof CompositeData) { - true + // If we've found a simple attribute, throw an exception as this attribute + // was mixed between simple & composite + if (!isComposite && isFound) { + throw new InvalidAttributeValueException() + } + isComposite = true + keys = value.getCompositeType().keySet() } else { - false + // If we've found a composite attribute, throw an exception as this attribute + // was mixed between simple & composite + if (isComposite) { + throw new InvalidAttributeValueException() + } + keys = [] } + isFound = true + return keys } catch (AttributeNotFoundException | NullPointerException ignored) { - null + [] } - }.findAll { it != null } + }.flatten() .toSet() - switch (allComposite.size()) { - case 0: - throw new AttributeNotFoundException() - case 1: - return allComposite.contains(true) - default: - throw new InvalidAttributeValueException() + if (!isFound) { + throw new AttributeNotFoundException() } + + return keySet } private static Map getLabels(GroovyMBean mbean, Map labelFuncs, Map additionalLabels) { From 9fc4636e5851569d615b1471b0293d135236e772 Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Tue, 18 Jul 2023 22:17:18 -0400 Subject: [PATCH 13/15] Refactor to use mbeanhelper any time accessing a bean attribute --- .../jmxmetrics/InstrumentHelper.groovy | 50 ++++++++----------- .../contrib/jmxmetrics/MBeanHelper.groovy | 23 ++++----- 2 files changed, 33 insertions(+), 40 deletions(-) diff --git a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelper.groovy b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelper.groovy index 83d64c8a5..369a4a6b1 100644 --- a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelper.groovy +++ b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelper.groovy @@ -127,26 +127,28 @@ class InstrumentHelper { def isFound = false def keySet = beans.collect { bean -> try { - def value = bean.getProperty(attribute) - def keys = [] - if (value instanceof CompositeData) { + def value = MBeanHelper.getBeanAttribute(bean, attribute) + if (value == null) { + // Null represents an attribute not found exception in MBeanHelper + [] + } else if (value instanceof CompositeData) { // If we've found a simple attribute, throw an exception as this attribute // was mixed between simple & composite if (!isComposite && isFound) { throw new InvalidAttributeValueException() } isComposite = true - keys = value.getCompositeType().keySet() + isFound = true + value.getCompositeType().keySet() } else { - // If we've found a composite attribute, throw an exception as this attribute + // If we've previously found a composite attribute, throw an exception as this attribute // was mixed between simple & composite if (isComposite) { throw new InvalidAttributeValueException() } - keys = [] + isFound = true + [] } - isFound = true - return keys } catch (AttributeNotFoundException | NullPointerException ignored) { [] } @@ -177,15 +179,11 @@ class InstrumentHelper { return { result -> [mbeans, attributes].combinations().each { pair -> def (mbean, attribute) = pair - try { - def value = mbean.getProperty(attribute) - if (value != null) { - def labels = getLabels(mbean, labelFuncs, mBeanAttributes[attribute]) - logger.fine("Recording ${instrumentName} - ${instrument.method} w/ ${value} - ${labels}") - recordDataPoint(instrument, result, value, GroovyMetricEnvironment.mapToAttributes(labels)) - } - } catch (AttributeNotFoundException ignored) { - logger.info("Expected attribute ${attribute} not found in mbean ${mbean.name()}") + def value = MBeanHelper.getBeanAttribute(mbean, attribute) + if (value != null) { + def labels = getLabels(mbean, labelFuncs, mBeanAttributes[attribute]) + logger.fine("Recording ${instrumentName} - ${instrument.method} w/ ${value} - ${labels}") + recordDataPoint(instrument, result, value, GroovyMetricEnvironment.mapToAttributes(labels)) } } } @@ -201,18 +199,14 @@ class InstrumentHelper { metricEnvironment.registerBatchCallback("${instrumentName}.${attribute}", () -> { mbeans.each { mbean -> - try { - def value = mbean.getProperty(attribute) - if (value != null && value instanceof CompositeData) { - instruments.each { inst -> - def val = value.get(inst.v1) - def labels = getLabels(mbean, labelFuncs, mBeanAttributes[attribute]) - logger.fine("Recording ${"${instrumentName}.${inst.v1}"} - ${instrument.method} w/ ${val} - ${labels}") - recordDataPoint(instrument, inst.v2, val, GroovyMetricEnvironment.mapToAttributes(labels)) - } + def value = MBeanHelper.getBeanAttribute(mbean, attribute) + if (value != null && value instanceof CompositeData) { + instruments.each { inst -> + def val = value.get(inst.v1) + def labels = getLabels(mbean, labelFuncs, mBeanAttributes[attribute]) + logger.fine("Recording ${"${instrumentName}.${inst.v1}"} - ${instrument.method} w/ ${val} - ${labels}") + recordDataPoint(instrument, inst.v2, val, GroovyMetricEnvironment.mapToAttributes(labels)) } - } catch (AttributeNotFoundException ignored) { - logger.info("Expected attribute ${attribute} not found in mbean ${mbean.name()}") } } }, instruments.first().v2, *instruments.tail().collect { it.v2 }) diff --git a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/MBeanHelper.groovy b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/MBeanHelper.groovy index b14063ebf..1ba98467e 100644 --- a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/MBeanHelper.groovy +++ b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/MBeanHelper.groovy @@ -88,12 +88,7 @@ class MBeanHelper { def ofInterest = isSingle ? [mbeans[0]]: mbeans return ofInterest.collect { - try { - it.getProperty(attribute) - } catch (AttributeNotFoundException e) { - logger.warning("Expected attribute ${attribute} not found in mbean ${it.name()}") - null - } + getBeanAttribute(it, attribute) } } @@ -105,12 +100,16 @@ class MBeanHelper { def ofInterest = isSingle ? [mbeans[0]]: mbeans return [ofInterest, attributes].combinations().collect { pair -> def (bean, attribute) = pair - try { - new Tuple3(bean, attribute, bean.getProperty(attribute)) - } catch (AttributeNotFoundException e) { - logger.info("Expected attribute ${attribute} not found in mbean ${bean.name()}") - new Tuple3(bean, attribute, null) - } + new Tuple3(bean, attribute, getBeanAttribute(bean, attribute)) + } + } + + static Object getBeanAttribute(GroovyMBean bean, String attribute) { + try { + bean.getProperty(attribute) + } catch (AttributeNotFoundException e) { + logger.warning("Expected attribute ${attribute} not found in mbean ${bean.name()}") + null } } } From 62c4b2960e230d4b47ee65830f8a3516606aa643 Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Wed, 19 Jul 2023 20:39:59 -0400 Subject: [PATCH 14/15] Add readme note for mixed CompositeData and simple values issue --- jmx-metrics/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/jmx-metrics/README.md b/jmx-metrics/README.md index 30e7a7f91..41fd4af32 100644 --- a/jmx-metrics/README.md +++ b/jmx-metrics/README.md @@ -128,6 +128,11 @@ mutually exclusive with `otel.jmx.groovy.script`. The currently supported target [`CompositeData`](https://docs.oracle.com/javase/7/docs/api/javax/management/openmbean/CompositeData.html) instances, each key of their `CompositeType` `keySet` will be `.`-appended to the specified `instrumentName`, whose resulting instrument will be updated for each respective value. + - If the underlying MBean(s) held by the provided MBeanHelper are a mixed set of + [`CompositeData`](https://docs.oracle.com/javase/7/docs/api/javax/management/openmbean/CompositeData.html) instances + and simple values, the InstrumentHelper will not attempt to collect the metric as it would generate + metrics identified with the `instrumentName` and also the `instrumentName` with the `keySet` `.`-appended, + which breaks OpenTelemetry metric conventions. `otel.instrument()` provides additional signatures to obtain and update the returned `InstrumentHelper`: From 713648ded6f3e381f1b476b0d6afc9e8ad0b0c3e Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Fri, 21 Jul 2023 13:15:26 -0400 Subject: [PATCH 15/15] Update jmx-metrics/README.md Co-authored-by: jason plumb <75337021+breedx-splk@users.noreply.github.com> --- jmx-metrics/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jmx-metrics/README.md b/jmx-metrics/README.md index 41fd4af32..29e2db023 100644 --- a/jmx-metrics/README.md +++ b/jmx-metrics/README.md @@ -130,7 +130,7 @@ mutually exclusive with `otel.jmx.groovy.script`. The currently supported target instrument will be updated for each respective value. - If the underlying MBean(s) held by the provided MBeanHelper are a mixed set of [`CompositeData`](https://docs.oracle.com/javase/7/docs/api/javax/management/openmbean/CompositeData.html) instances - and simple values, the InstrumentHelper will not attempt to collect the metric as it would generate + and simple values, the InstrumentHelper will not attempt to collect the metric. This is to prevent generating metrics identified with the `instrumentName` and also the `instrumentName` with the `keySet` `.`-appended, which breaks OpenTelemetry metric conventions.