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

wvlet.airframe.jmx.JMXUtil.scala Maven / Gradle / Ivy

/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package wvlet.airframe.jmx

import java.lang.management.ManagementFactory
import java.rmi.server.RemoteObject
import java.util.Properties

import javax.management.remote.JMXConnectorServer
import sun.rmi.server.UnicastRef
import wvlet.log.LogSupport
import wvlet.log.io.IOUtil

import scala.util.{Failure, Success, Try}

/**
  */
object JMXUtil extends LogSupport {
  implicit class WithReflection(className: String) {
    def invokeStaticMethod(methodName: String): Unit = {
      Try(Class.forName(className).getDeclaredMethod(methodName))
        .map(m => m.invoke(null))
        .recoverWith { case e: Throwable => throw e }
    }

    def getStaticField[R](name: String): Option[R] = {
      Class.forName(className).getDeclaredFields.find(_.getName == name).flatMap { field =>
        // Use isAccessible() for JDK8 compatibility for a while.
        // For JDK9 or later, we need to use f.canAccess(obj)
        val isAccessible = field.isAccessible()
        try {
          field.setAccessible(true)
          Option(field.get(null).asInstanceOf[R])
        } finally {
          field.setAccessible(isAccessible)
        }
      }
    }
  }

  def isAtLeastJava9 = {
    // Scala 2.11.12 runs with JDK11
    scala.util.Properties.isJavaAtLeast("9")
  }

  private def startAgent(config: JMXConfig): HostAndPort = {
    val registryPort = config.registryPort.getOrElse(IOUtil.unusedPort)
    val rmiPort      = config.rmiPort.getOrElse(IOUtil.unusedPort)
    val p            = new Properties
    p.setProperty("com.sun.management.jmxremote", "true")
    p.setProperty("com.sun.management.jmxremote.port", registryPort.toString)
    p.setProperty("com.sun.management.jmxremote.rmi.port", rmiPort.toString)
    p.setProperty("com.sun.management.jmxremote.authenticate", "false")
    p.setProperty("com.sun.management.jmxremote.ssl", "false")

    import scala.jdk.CollectionConverters.*
    if (isAtLeastJava9) {
      // TODO Java9 support
//      Try {
//        val processId = getProcessId.toString
//        import com.sun.tools.attach.VirtualMachine
//        VirtualMachine.list().asScala.find(x => x.id == processId).map { vmDesc =>
//          // This will not work because JVM doesn't allow attaching to self using the same VM.
//          // We need to create a new JVM process to run this code
//          val virtualMachine = vmDesc.provider().attachVirtualMachine(processId)
//          try {
//            virtualMachine.startLocalManagementAgent()
//            virtualMachine.startManagementAgent(p)
//          } finally {
//            virtualMachine.detach()
//          }
//        }
//      }
    } else {
      // Java8
      for ((k, v) <- p.asScala) {
        System.setProperty(k, v)
      }
      "sun.management.Agent".invokeStaticMethod("startAgent")
    }
    HostAndPort("localhost", registryPort)
  }

  private[jmx] def startAndGetAgentURL(config: JMXConfig): String = {
    Try(startAgent(config)) match {
      case Success(x) =>
        debug(s"Started JMX agent at localhost:${x.port}")
        s"service:jmx:rmi:///jndi/rmi://localhost:${x.port}/jmxrmi"
      case Failure(e) =>
        warn(e)
        throw e
    }
  }

  private[jmx] def currentJMXRegistry: Option[HostAndPort] = {
    if (isAtLeastJava9) {
      // In Java 9, sun.management.xxx is unavailable
      None
    } else {
      // For Java 8
      val jmxServer = "sun.management.Agent".getStaticField[JMXConnectorServer]("jmxServer")
      val registry  = "sun.management.jmxremote.ConnectorBootstrap".getStaticField[RemoteObject]("registry")

      (jmxServer, registry) match {
        case (Some(jmx), Some(reg)) =>
          Some(HostAndPort(jmx.getAddress.getHost, reg.getRef.asInstanceOf[UnicastRef].getLiveRef.getPort))
        case other =>
          None
      }
    }
  }

  private def getProcessId: Long = {
    val name = ManagementFactory.getRuntimeMXBean().getName()
    name.substring(0, name.indexOf('@')).toLong
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy