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

com.google.apphosting.runtime.timer.JmxThreadGroupCpuTimer Maven / Gradle / Ivy

There is a newer version: 2.0.31
Show newest version
/*
 * Copyright 2021 Google LLC
 *
 * 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
 *
 *     https://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 com.google.apphosting.runtime.timer;

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;
import java.util.HashMap;
import java.util.Map;

/**
 * Use the JMX {@link ThreadMXBean} to retrieve CPU timing information
 * about all of the threads in a particular {@link ThreadGroup}.
 *
 * As there is no API for retrieving per-{@link ThreadGroup} CPU
 * directly, this class is implemented by storing the last-known CPU
 * usage for each {@link Thread} in a {@link ThreadGroup} and
 * performing a comparison each time it is updated.  See the caveat in
 * {@link #update} for an important warning.
 *
 */
class JmxThreadGroupCpuTimer implements Timer {
  private static final ThreadMXBean THREAD_MX = ManagementFactory.getThreadMXBean();

  private static final int MAX_THREADS = 64;

  protected volatile boolean running = false;
  private final ThreadGroup threadGroup;
  private final Map lastCounterMap;
  private long currentSum;

  /**
   * Creates a new {@code JmxThreadGroupCpuTimer} initialized with the
   * specified
   * {@link As a side-effect, also ensures that CPU thread timing is enabled.
   */
  public JmxThreadGroupCpuTimer(ThreadGroup threadGroup) {
    this.threadGroup = threadGroup;
    this.lastCounterMap = new HashMap<>();
    THREAD_MX.setThreadCpuTimeEnabled(true);
  }

  /**
   * Returns true if the current JVM supports thread cpu timing via
   * {@code ThreadMXBean}.
   */
  public static boolean isAvailable() {
    return THREAD_MX.isThreadCpuTimeSupported();
  }

  @Override
  public synchronized void start() {
    if (running) {
      throw new IllegalStateException("already running");
    }
    // Before we start the timer, perform an update with !running to
    // get a baseline for all running threads.  This allows us to
    // notice any newly-started threads and include all of their CPU
    // time in subsequent updates.
    update();
    running = true;
  }

  @Override
  public synchronized void stop() {
    if (!running) {
      throw new IllegalStateException("not running");
    }
    update();
    running = false;
  }

  /**
   * Updates the internal snapshot of each thread's CPU usage.
   * Unfortunately, once a thread exits it is no longer possible to
   * retrieve its CPU timing.  Thus, this method should be called
   * periodically -- if possible, right before each thread exits -- to
   * record the last known CPU usage for accurate accouting.
   */
  @Override
  public synchronized void update() {
    Thread[] threads = new Thread[MAX_THREADS];
    int activeThreads = threadGroup.enumerate(threads);
    for (int i = 0; i < activeThreads; i++) {
      Thread thread = threads[i];
      long counter = THREAD_MX.getThreadCpuTime(thread.getId());
      if (counter != -1) {
        Long last = lastCounterMap.get(thread);
        System.err.println(thread + ": " + counter + " (vs. " + last + "), running = " + running);
        if (last != null) {
          long diff = counter - last;
          currentSum += diff;
        } else {
          if (running) {
            // We assume here that the thread was started after the
            // timer was started, since we would otherwise have seen
            // the thread in the update() call that was made at the
            // beginning of start().
            currentSum += counter;
            System.err.println("currentSum = " + currentSum);
          }
        }
        lastCounterMap.put(thread, counter);
      }
    }
  }

  @Override
  public synchronized long getNanoseconds() {
    update();
    return currentSum;
  }

  @Override
  public String toString() {
    return String.format("%.3f", getNanoseconds() / 1000000000.0);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy