Skip to content

Commit 3e684e5

Browse files
committed
%time magic #104
restoring CPU time measurements
1 parent 7bc16de commit 3e684e5

2 files changed

Lines changed: 114 additions & 2 deletions

File tree

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package org.dflib.jjava.jupyter.instrumentation;
2+
3+
import java.lang.management.ThreadMXBean;
4+
import java.util.concurrent.atomic.AtomicLong;
5+
import java.util.function.Supplier;
6+
7+
/**
8+
* A timer that makes an attempt to capture CPU and user time in addition to the overall elapsed time.
9+
*/
10+
public abstract class WallAndCpuTimer implements EvalTimer {
11+
12+
private final ThreadMXBean threadMXBean;
13+
private final AtomicLong totalWallTime;
14+
private final AtomicLong totalUserTime;
15+
private final AtomicLong totalCpuTime;
16+
17+
public static boolean canMeasureCpuTimes(ThreadMXBean threadMXBean) {
18+
// according to JavaDocs, isThreadCpuTimeEnabled() can throw in some cases
19+
try {
20+
return threadMXBean.isCurrentThreadCpuTimeSupported() && threadMXBean.isThreadCpuTimeEnabled();
21+
} catch (Exception e) {
22+
return false;
23+
}
24+
}
25+
26+
public WallAndCpuTimer(ThreadMXBean threadMXBean) {
27+
this.threadMXBean = threadMXBean;
28+
this.totalWallTime = new AtomicLong();
29+
this.totalUserTime = new AtomicLong();
30+
this.totalCpuTime = new AtomicLong();
31+
}
32+
33+
@Override
34+
public void start() {
35+
totalWallTime.set(0);
36+
totalUserTime.set(0);
37+
totalCpuTime.set(0);
38+
}
39+
40+
@Override
41+
public <T> T runAndMeasureStep(Supplier<T> timedAction) {
42+
43+
long t0 = System.nanoTime();
44+
long ut0 = threadMXBean.getCurrentThreadUserTime();
45+
long ct0 = threadMXBean.getCurrentThreadCpuTime();
46+
47+
try {
48+
return timedAction.get();
49+
} finally {
50+
51+
long ct1 = threadMXBean.getCurrentThreadCpuTime();
52+
long ut1 = threadMXBean.getCurrentThreadUserTime();
53+
long t1 = System.nanoTime();
54+
55+
totalWallTime.addAndGet(t1 - t0);
56+
57+
// sanity check
58+
if (ut0 != -1 && ut1 != -1 && ct0 != -1 && ct1 != -1) {
59+
totalUserTime.addAndGet(ut1 - ut0);
60+
totalCpuTime.addAndGet(ct1 - ct0);
61+
} else {
62+
totalUserTime.set(-1);
63+
totalCpuTime.set(-1);
64+
}
65+
}
66+
}
67+
68+
@Override
69+
public void stop() {
70+
onStop(totalWallTime.get(), totalUserTime.get(), totalCpuTime.get());
71+
}
72+
73+
protected abstract void onStop(
74+
long wallTimeNanos,
75+
long userTimeNanos,
76+
long totalTimeNanos);
77+
78+
}

jjava-kernel/src/main/java/org/dflib/jjava/kernel/magics/TimeMagic.java

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package org.dflib.jjava.kernel.magics;
22

3+
import org.dflib.jjava.jupyter.instrumentation.WallAndCpuTimer;
34
import org.dflib.jjava.jupyter.instrumentation.WallTimer;
45
import org.dflib.jjava.jupyter.kernel.BaseKernel;
56
import org.dflib.jjava.jupyter.kernel.display.DisplayData;
67
import org.dflib.jjava.jupyter.kernel.magic.CellMagic;
78
import org.dflib.jjava.jupyter.kernel.magic.LineMagic;
89
import org.dflib.jjava.kernel.JavaKernel;
910

11+
import java.lang.management.ManagementFactory;
12+
import java.lang.management.ThreadMXBean;
1013
import java.util.List;
1114
import java.util.UUID;
1215

@@ -28,14 +31,25 @@ public DisplayData eval(JavaKernel kernel, List<String> args, String body) throw
2831
}
2932

3033
private DisplayData timeAndRunCode(JavaKernel kernel, String code) {
31-
System.out.println("TimeMagic thread: " + Thread.currentThread().getName());
34+
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
3235
return kernel.evalBuilder(code)
3336
.resolveMagics()
3437
.renderResults()
35-
.timed(new TMWallTimer(kernel))
38+
.timed(WallAndCpuTimer.canMeasureCpuTimes(threadMXBean)
39+
? new TMWallAndCpuTimer(kernel, threadMXBean)
40+
: new TMWallTimer(kernel))
3641
.eval();
3742
}
3843

44+
private static void displayCpuTime(BaseKernel kernel, long userTimeNanos, long sysTimeNanos, long totalTimeNanos) {
45+
String cpuTimes = String.format("CPU times: user %.3f s, sys %.3f s, total %.3f s",
46+
userTimeNanos / NANONS_IN_SEC,
47+
sysTimeNanos / NANONS_IN_SEC,
48+
totalTimeNanos / NANONS_IN_SEC);
49+
50+
kernel.display(new DisplayData(cpuTimes).setDisplayId(UUID.randomUUID().toString()));
51+
}
52+
3953
private static void displayWallTime(BaseKernel kernel, long wallTimeNanos) {
4054
String wallTime = String.format("Wall time: %.3f s", wallTimeNanos / NANONS_IN_SEC);
4155
kernel.display(new DisplayData(wallTime).setDisplayId(UUID.randomUUID().toString()));
@@ -53,4 +67,24 @@ protected void onStop(long wallTimeNanos) {
5367
displayWallTime(kernel, wallTimeNanos);
5468
}
5569
}
70+
71+
72+
static class TMWallAndCpuTimer extends WallAndCpuTimer {
73+
private final BaseKernel kernel;
74+
75+
public TMWallAndCpuTimer(BaseKernel kernel, ThreadMXBean threadMXBean) {
76+
super(threadMXBean);
77+
this.kernel = kernel;
78+
}
79+
80+
@Override
81+
protected void onStop(long wallTimeNanos, long userTimeNanos, long totalTimeNanos) {
82+
// sanity check: CPU and user times can't be measured sometimes
83+
if (userTimeNanos != -1 && totalTimeNanos != -1) {
84+
displayCpuTime(kernel, userTimeNanos, totalTimeNanos - userTimeNanos, totalTimeNanos);
85+
}
86+
87+
displayWallTime(kernel, wallTimeNanos);
88+
}
89+
}
5690
}

0 commit comments

Comments
 (0)