Skip to content

Commit 001703f

Browse files
Version 0.1.2
1 parent 216723b commit 001703f

File tree

5 files changed

+59
-20
lines changed

5 files changed

+59
-20
lines changed

README.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,11 @@ The first argument to MeterThread's constructor is a LongConsumer callback that
3535
will be called with every new measurement of allocation rate in bytes/sec.
3636

3737
The second (optional) argument is the measurement interval in milliseconds (1000
38-
by default). Note that the callback might not be called on each interval,
39-
because we can reliably measure the allocation rate only if the garbage
40-
collection hasn't been performed between two iterations. The default interval
41-
should work fine most of the time; unless your allocation rate is so high that
42-
the GC is triggered every second or two — in that case, in order not to lose
43-
most of the measurements, try to set the interval to a lower value.
38+
by default). Regardless of the interval length, the reported rate will be
39+
normalized to per-second value. Note that the callback might not be called on
40+
each interval, because under certain conditions we can't reliably measure the
41+
allocation rate (if both the garbage collection happened and a few of the
42+
heavy-allocating threads died).
4443

4544
Here's an example of combining jvm-alloc-rate-meter with [Dropwizard
4645
Metrics](https://github.com/dropwizard/metrics):
@@ -54,7 +53,7 @@ import java.util.concurrent.TimeUnit;
5453
// ...
5554

5655
Histogram hist = new Histogram(new SlidingTimeWindowArrayReservoir(10, TimeUnit.SECONDS));
57-
MeterThread mt = new MeterThread((r) -> hist.update(r));
56+
MeterThread mt = new MeterThread(hist::update);
5857
mt.start()
5958

6059
// Now, you can forward this histogram to Graphite, or check the values manually, e.g.:

boot.properties

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#http://boot-clj.com
2+
#Tue Jun 26 19:08:05 EEST 2018
3+
BOOT_CLOJURE_NAME=org.clojure/clojure
4+
BOOT_CLOJURE_VERSION=1.10.0
5+
BOOT_VERSION=2.8.2

build.boot

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
(task-options!
22
pom {:project 'com.clojure-goes-fast/jvm-alloc-rate-meter
3-
:version "0.1.1"
3+
:version "0.1.2"
44
:description "Measure JVM heap allocation rate in real time"
55
:url "http://github.com/clojure-goes-fast/jvm-alloc-rate-meter"
66
:scm {:url "http://github.com/clojure-goes-fast/jvm-alloc-rate-meter"}

src/jvm_alloc_rate_meter/MeterThread.java

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package jvm_alloc_rate_meter;
22

3+
import com.sun.management.ThreadMXBean;
34
import java.util.function.LongConsumer;
45
import java.lang.management.GarbageCollectorMXBean;
56
import java.lang.management.ManagementFactory;
@@ -22,26 +23,48 @@ public MeterThread(LongConsumer callback, int intervalMs) {
2223
setDaemon(true);
2324
}
2425

26+
// Basically, we have two ways of measuring the allocation rate.
27+
// First relies on checking heap usage between two points of time and
28+
// substracting. This is accurate, but works only if the GC didn't trigger
29+
// in the meantime.
30+
// Second works by taking allocation stats by each alive thread. This is
31+
// more reliable, but potentially less accurate (because threads can go
32+
// away, and we lose their allocation stats).
33+
// The idea is to use the first approach if GC didn't happen, and the second
34+
// one if it did.
35+
2536
public void run() {
26-
long lastTime = 0;
37+
long lastTime = 0, lastHeapUsage = -1, lastGcCounts = -1, lastThreadAllocated = -1;
2738
try {
28-
long prevUsage = -1, prevGcCounts = -1;
2939
while (doRun) {
30-
long usage = usedHeap();
40+
long heapUsage = usedHeap();
3141
long gcCounts = gcCounts();
32-
long ts = System.currentTimeMillis();
42+
long threadAllocated = allocatedByAllThreads();
43+
long time = System.currentTimeMillis();
44+
45+
double multiplier = 1000.0 / (time - lastTime);
46+
long deltaUsage = heapUsage - lastHeapUsage;
47+
long deltaThreadAllocated = threadAllocated - lastThreadAllocated;
3348

34-
if ((gcCounts == prevGcCounts) && (usage >= prevUsage)) {
35-
long deltaTime = ts - lastTime;
36-
long rate = Math.round((usage - prevUsage) * (1000.0 / deltaTime));
37-
callback.accept(rate);
49+
if (lastTime != 0) {
50+
if ((gcCounts == lastGcCounts) && (deltaUsage >= 0)) {
51+
long rate = Math.round(deltaUsage * multiplier);
52+
callback.accept(rate);
53+
} else if (deltaThreadAllocated >= 0) {
54+
long rate = Math.round(deltaThreadAllocated * multiplier);
55+
callback.accept(rate);
56+
} else {
57+
// Apparently, neither approach did well, just skip this
58+
// iteration.
59+
}
3860
}
3961

4062
Thread.sleep(intervalMs);
4163

42-
prevUsage = usage;
43-
prevGcCounts = gcCounts;
44-
lastTime = ts;
64+
lastTime = time;
65+
lastHeapUsage = heapUsage;
66+
lastGcCounts = gcCounts;
67+
lastThreadAllocated = threadAllocated;
4568
}
4669
} catch (InterruptedException e) {
4770
System.err.println("MeterThread terminating...");
@@ -64,4 +87,16 @@ private static long gcCounts() {
6487
}
6588
return total;
6689
}
90+
91+
private static long allocatedByAllThreads() {
92+
ThreadMXBean bean = (ThreadMXBean)ManagementFactory.getThreadMXBean();
93+
long[] ids = bean.getAllThreadIds();
94+
long[] allocatedBytes = bean.getThreadAllocatedBytes(ids);
95+
long result = 0;
96+
// This is not correct because we will lose allocation data from threads
97+
// that died. Oh well.
98+
for (long abytes : allocatedBytes)
99+
result += abytes;
100+
return result;
101+
}
67102
}

src/jvm_alloc_rate_meter/core.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
(let [iw (MeterThread. (reify LongConsumer
1414
(accept [_ v]
1515
(callback-fn v)))
16-
(or interval-ms 1000))]
16+
(or interval-ms 1000))]
1717
(.start iw)
1818
#(.terminate iw)))
1919

0 commit comments

Comments
 (0)