使用 Dropwizard 指标报告 JVM CPU 的使用情况

Reporting JVM's CPU usage with Dropwizard metrics

我使用 Dropwizard metrics to measure various metrics in my application. They are several predefined reporters in JVM instrumentation,但奇怪的是我找不到任何关于 CPU 用法的报告。

我可以创建自己的仪表(使用 getThreadCpuTime 或类似的),但我最好的猜测是我遗漏了一些东西。

我是不是在当前的实现中错过了它,还是它比我最初想象的要复杂?

我对 Dropwizard 了解不多,但我过去曾使用 ThreadMXBean 来估计可扩展分布式计算系统中的 CPU 利用率,因此我将分享我的想法与问题相关。事情肯定比乍看起来要复杂:

ThreadMxBean 有点误导...

ThreadMxBean.getThreadCpuTime(id) 只有 return 是特定线程在 CPU 上执行代码所花费的 时间,以纳秒为单位,自线程启动以来。它没有提供有关您的线程可能已被阻塞或等待(休眠)多长时间的信息,因此它确实无法让您很好地了解 CPU 用法。您还需要测量总 blocked/waited 时间,然后在程序运行期间跟踪所有这三个值以跟踪 CPU 使用情况。奇怪的是,ThreadMXBean没有直接获取blocked/waited时间的方法,所以你可能会想放弃。

...但是你可以用它来得到一个ThreadInfo对象...

首先,要启用它,请调用以下两行(如果您的 JVM 不支持,这可能会引发异常):

ManagementFactory.getThreadMXBean().setThreadCpuTimeEnabled(true);
ManagementFactory.getThreadMXBean().setThreadContentionMonitoringEnabled(true);

现在您可以调用 ThreadMXBean.getThreadInfo(threadId) 来获取对应于特定线程的 ThreadInfo 的实例。此信息对象有两个方法,getBlockedTime()getWaitedTime(),其中 return 您的线程在这两种状态中花费的总毫秒数。没有 getCpuTime() 方法(如果你问我,这是这个对象的一个​​非常愚蠢的缺点),但如果你知道你的线程何时启动,你可以这样做:

//Initialized somewhere else:
ThreadMXBean bean = ...
long threadStartTime = System.currentTimeMillis();
Thread myThread = ...

//Inside your metrics-gathering code:
long now = System.currentTimeMillis();
ThreadInfo info = bean.getThreadInfo(myThread.getId());
long totalCpuTime = now - (info.getBlockedTime()+info.getWaitedTime()+threadStartTime);

现在您可以计算线程利用率的百分比。

我们快完成了,但还没有完全完成。每次我们查看上面发布的代码的最后三行时,我们只会收集线程 executing/blocked/waiting 状态的总时间。要计算百分比,我们需要跟踪收集这些指标的时间,以便我们可以知道自上次指标更新以来线程在每个状态中花费了多少时间。所以,做这样的事情:

class ThreadUsageMetrics{
    long timestamp, totalBlockedTime, totalWaitTime;

    ThreadUsageMetrics(long ts, long blocked, long wait){
        timestamp = ts;
        totalBlockedTime = blocked;
        totalWaitTime = wait;
    }

    double computeCpuUsageSince(ThreadUsageMetrics prev){
        long time = timestamp - prev.timestamp;
        long blocked = totalBlockedTime - prev.totalBlockedTime;
        long waited = totalWaitTime - prev.totalWaitTime;
        return (time-(blocked+waited))/(double)time;
    }
}

这将为我们提供 0.0 到 1.0 范围内的双倍值,指示 CPU 自上次指标更新以来使用量占总时间的百分比。我假设您可以将此值转换为百分比,并每隔 5 秒左右将其提供给 Dropwizard 的 Gauge 实例。在我的项目中,这就是我们估计 CPU 几年来使用情况的方式,它对我们来说效果很好。

关于此的几点说明 - 我们实际上不需要在此对象中显式存储总 CPU 时间,因为任何未花费在阻塞或等待上的时间要么是执行时间,要么是在上下文切换期间花费的时间。我们无法知道上下文切换时间,但可以安全地假设在所有情况下 99.9% 的总上下文切换时间可以忽略不计。

请注意 - 我们并未真正衡量 CPU 使用情况。

如果您仔细阅读,您会发现我说的是 "estimating" CPU 用法。我这样说的原因是我们正在测量特定 Java Thread 的总执行时间。 Java 不提供实际 CPU 硬件使用的概念 - 它只是线程执行所花费的总时间。超线程之类的东西进一步混淆了这一点,其中花费的时间 "executing" 实际上可能意味着等待另一个线程离开 ALU 或内存总线所花费的时间。我认为这提供了一个很好的衡量代码何时在物理硬件线程上 运行 的方法,但是如果你想衡量实际的 CPU 使用情况,你将无法在纯 Java.