All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.opentelemetry.instrumentation.jmx.engine.BeanFinder Maven / Gradle / Ivy

/*
 * Copyright The OpenTelemetry Authors
 * SPDX-License-Identifier: Apache-2.0
 */

package io.opentelemetry.instrumentation.jmx.engine;

import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;

/**
 * A class responsible for finding MBeans that match metric definitions specified by a set of
 * MetricDefs.
 */
class BeanFinder {

  private static final Logger logger = Logger.getLogger(BeanFinder.class.getName());

  private final MetricRegistrar registrar;
  private MetricConfiguration conf;
  private final ScheduledExecutorService exec =
      Executors.newSingleThreadScheduledExecutor(
          runnable -> {
            Thread result = new Thread(runnable, "jmx_bean_finder");
            result.setDaemon(true);
            return result;
          });
  private final long discoveryDelay;
  private final long maxDelay;
  private long delay = 1000; // number of milliseconds until first attempt to discover MBeans

  BeanFinder(MetricRegistrar registrar, long discoveryDelay) {
    this.registrar = registrar;
    this.discoveryDelay = Math.max(1000, discoveryDelay); // Enforce sanity
    this.maxDelay = Math.max(60000, discoveryDelay);
  }

  /**
   * Starts bean discovery on a list of local or remote connections
   *
   * @param conf metric configuration
   * @param connections supplier for instances of {@link MBeanServerConnection}, will be invoked
   *     after delayed JMX initialization once the {@link #discoveryDelay} has expired.
   */
  void discoverBeans(
      MetricConfiguration conf, Supplier> connections) {
    this.conf = conf;

    exec.schedule(
        () -> {
          // Issue 9336: Corner case: PlatformMBeanServer will remain uninitialized until a direct
          // reference to it is made. This call makes sure that the PlatformMBeanServer will be in
          // the set of MBeanServers reported by MBeanServerFactory.
          // Issue 11143: This call initializes java.util.logging.LogManager. We should not call it
          // before application has had a chance to configure custom log manager. This is needed for
          // wildfly.
          ManagementFactory.getPlatformMBeanServer();
        },
        discoveryDelay,
        TimeUnit.MILLISECONDS);

    exec.schedule(
        new Runnable() {
          @Override
          public void run() {
            refreshState(connections);
            // Use discoveryDelay as the increment for the actual delay
            delay = Math.min(delay + discoveryDelay, maxDelay);
            exec.schedule(this, delay, TimeUnit.MILLISECONDS);
          }
        },
        delay,
        TimeUnit.MILLISECONDS);
  }

  /**
   * Go over all configured metric definitions and try to find matching MBeans. Once a match is
   * found for a given metric definition, submit the definition to MetricRegistrar for further
   * handling. Successive invocations of this method may find matches that were previously
   * unavailable, in such cases MetricRegistrar will extend the coverage for the new MBeans
   *
   * @param connections supplier providing {@link MBeanServerConnection} instances to query
   */
  private void refreshState(Supplier> connections) {
    List servers = connections.get();

    for (MetricDef metricDef : conf.getMetricDefs()) {
      resolveBeans(metricDef, servers);
    }
  }

  /**
   * Go over the specified list of MBeanServers and try to find any MBeans matching the specified
   * MetricDef. If found, verify that the MBeans support the specified attributes, and set up
   * collection of corresponding metrics.
   *
   * @param metricDef the MetricDef used to find matching MBeans
   * @param connections the list of {@link MBeanServerConnection} to query
   */
  private void resolveBeans(
      MetricDef metricDef, List connections) {
    BeanGroup beans = metricDef.getBeanGroup();

    for (MBeanServerConnection connection : connections) {
      // The set of all matching ObjectNames recognized by the server
      Set allObjectNames = new HashSet<>();

      for (ObjectName pattern : beans.getNamePatterns()) {
        try {
          allObjectNames.addAll(connection.queryNames(pattern, beans.getQueryExp()));
        } catch (IOException e) {
          logger.log(Level.WARNING, "IO error while resolving mbean", e);
        }
      }

      if (!allObjectNames.isEmpty()) {
        resolveAttributes(allObjectNames, connection, metricDef);

        // Assuming that only one MBeanServer has the required MBeans
        break;
      }
    }
  }

  /**
   * Go over the collection of matching MBeans and try to find all matching attributes. For every
   * successful match, activate metric value collection.
   *
   * @param objectNames the collection of {@link ObjectName}s identifying the MBeans
   * @param connection the {@link MBeanServerConnection} which recognized the collection of
   *     ObjectNames
   * @param metricDef the {@link MetricDef} describing the attributes to look for
   */
  private void resolveAttributes(
      Set objectNames, MBeanServerConnection connection, MetricDef metricDef) {
    for (MetricExtractor extractor : metricDef.getMetricExtractors()) {
      // For each MetricExtractor, find the subset of MBeans that have the required attribute
      List validObjectNames = new ArrayList<>();
      AttributeInfo attributeInfo = null;
      for (ObjectName objectName : objectNames) {
        AttributeInfo attr =
            extractor.getMetricValueExtractor().getAttributeInfo(connection, objectName);
        if (attr != null) {
          if (attributeInfo == null) {
            attributeInfo = attr;
          } else {
            attributeInfo.updateFrom(attr);
          }
          validObjectNames.add(objectName);
        }
      }
      if (!validObjectNames.isEmpty()) {
        // Ready to collect metric values
        registrar.enrollExtractor(connection, validObjectNames, extractor, attributeInfo);
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy