mirror of
https://github.com/JetBrains/JetBrainsRuntime.git
synced 2026-01-24 09:20:50 +01:00
JBR-7028 Implement FPS counter on Linux
Use -Dawt.window.counters to enable.
To output counters per second to stdout/stderr,
use -Dawt.window.counters=stdout or =stderr.
A counter by the name swing.RepaintManager.updateWindows
is always available for Swing applications, but it does not
accurately correspond to frames per second.
Toolkit-dependent counters provide much better accuracy.
On Wayland with memory buffers as the backend two are available:
java2d.native.frames - frames delivered to the Wayland server
java2d.native.framesDropped - fully formed frames that were not
delivered to the Wayland server
(cherry picked from commit 872e73ed1e)
This commit is contained in:
@@ -389,6 +389,7 @@ public class Window extends Container implements Accessible {
|
||||
|
||||
private static final PlatformLogger log = PlatformLogger.getLogger("java.awt.Window");
|
||||
private static final PlatformLogger focusRequestLog = PlatformLogger.getLogger("jb.focus.requests");
|
||||
private static final PlatformLogger perfLog = PlatformLogger.getLogger("awt.window.counters");
|
||||
|
||||
private static final boolean locationByPlatformProp;
|
||||
|
||||
@@ -4276,6 +4277,8 @@ public class Window extends Container implements Accessible {
|
||||
}
|
||||
|
||||
static {
|
||||
String counters = System.getProperty("awt.window.counters");
|
||||
|
||||
AWTAccessor.setWindowAccessor(new AWTAccessor.WindowAccessor() {
|
||||
public void updateWindow(Window window) {
|
||||
window.updateWindow();
|
||||
@@ -4300,6 +4303,92 @@ public class Window extends Container implements Accessible {
|
||||
public Window[] getOwnedWindows(Window w) {
|
||||
return w.getOwnedWindows_NoClientCode();
|
||||
}
|
||||
|
||||
public boolean countersEnabled(Window w) {
|
||||
// May want to selectively enable or disable counters per window
|
||||
return counters != null;
|
||||
}
|
||||
|
||||
public void bumpCounter(Window w, String counterName) {
|
||||
Objects.requireNonNull(w);
|
||||
Objects.requireNonNull(counterName);
|
||||
|
||||
PerfCounter newCounter;
|
||||
long curTimeNanos = System.nanoTime();
|
||||
synchronized (w.perfCounters) {
|
||||
newCounter = w.perfCounters.compute(counterName, (k, v) ->
|
||||
v == null
|
||||
? new PerfCounter(curTimeNanos, 1L)
|
||||
: new PerfCounter(curTimeNanos, v.value + 1));
|
||||
}
|
||||
PerfCounter prevCounter;
|
||||
synchronized (w.perfCountersPrev) {
|
||||
prevCounter = w.perfCountersPrev.putIfAbsent(counterName, newCounter);
|
||||
}
|
||||
if (prevCounter != null) {
|
||||
long nanosInSecond = java.util.concurrent.TimeUnit.SECONDS.toNanos(1);
|
||||
long timeDeltaNanos = curTimeNanos - prevCounter.updateTimeNanos;
|
||||
if (timeDeltaNanos > nanosInSecond) {
|
||||
long valPerSecond = (long) ((double) (newCounter.value - prevCounter.value)
|
||||
* nanosInSecond / timeDeltaNanos);
|
||||
boolean traceAllCounters = Objects.equals(counters, "")
|
||||
|| Objects.equals(counters, "stdout")
|
||||
|| Objects.equals(counters, "stderr");
|
||||
boolean traceEnabled = traceAllCounters || (counters != null && counters.contains(counterName));
|
||||
if (traceEnabled) {
|
||||
if (counters.contains("stderr")) {
|
||||
System.err.println(counterName + " per second: " + valPerSecond);
|
||||
} else {
|
||||
System.out.println(counterName + " per second: " + valPerSecond);
|
||||
}
|
||||
}
|
||||
if (perfLog.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
perfLog.fine(counterName + " per second: " + valPerSecond);
|
||||
}
|
||||
synchronized (w.perfCountersPrev) {
|
||||
w.perfCountersPrev.put(counterName, newCounter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public long getCounter(Window w, String counterName) {
|
||||
Objects.requireNonNull(w);
|
||||
Objects.requireNonNull(counterName);
|
||||
|
||||
synchronized (w.perfCounters) {
|
||||
PerfCounter counter = w.perfCounters.get(counterName);
|
||||
return counter != null ? counter.value : -1L;
|
||||
}
|
||||
}
|
||||
|
||||
public long getCounterPerSecond(Window w, String counterName) {
|
||||
Objects.requireNonNull(w);
|
||||
Objects.requireNonNull(counterName);
|
||||
|
||||
PerfCounter newCounter;
|
||||
PerfCounter prevCounter;
|
||||
|
||||
synchronized (w.perfCounters) {
|
||||
newCounter = w.perfCounters.get(counterName);
|
||||
}
|
||||
|
||||
synchronized (w.perfCountersPrev) {
|
||||
prevCounter = w.perfCountersPrev.get(counterName);
|
||||
}
|
||||
|
||||
if (newCounter != null && prevCounter != null) {
|
||||
long timeDeltaNanos = newCounter.updateTimeNanos - prevCounter.updateTimeNanos;
|
||||
// Note that this time delta will usually be above one second.
|
||||
if (timeDeltaNanos > 0) {
|
||||
long nanosInSecond = java.util.concurrent.TimeUnit.SECONDS.toNanos(1);
|
||||
long valPerSecond = (long) ((double) (newCounter.value - prevCounter.value)
|
||||
* nanosInSecond / timeDeltaNanos);
|
||||
return valPerSecond;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}); // WindowAccessor
|
||||
} // static
|
||||
|
||||
@@ -4307,6 +4396,11 @@ public class Window extends Container implements Accessible {
|
||||
@Override
|
||||
void updateZOrder() {}
|
||||
|
||||
private record PerfCounter(Long updateTimeNanos, Long value) {}
|
||||
|
||||
private transient final Map<String, PerfCounter> perfCounters = new HashMap<>(4);
|
||||
private transient final Map<String, PerfCounter> perfCountersPrev = new HashMap<>(4);
|
||||
|
||||
} // class Window
|
||||
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ import sun.java2d.pipe.Region;
|
||||
import sun.swing.SwingAccessor;
|
||||
import sun.swing.SwingUtilities2;
|
||||
import sun.swing.SwingUtilities2.RepaintListener;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* This class manages repaint requests, allowing the number
|
||||
@@ -715,6 +716,13 @@ public class RepaintManager
|
||||
}
|
||||
|
||||
private void updateWindows(Map<Component,Rectangle> dirtyComponents) {
|
||||
dirtyComponents.keySet().stream()
|
||||
.map(c -> c instanceof Window w ? w : SwingUtilities.getWindowAncestor(c))
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.forEach(w -> AWTAccessor.getWindowAccessor()
|
||||
.bumpCounter(w, "swing.RepaintManager.updateWindows"));
|
||||
|
||||
Toolkit toolkit = Toolkit.getDefaultToolkit();
|
||||
if (!(toolkit instanceof SunToolkit &&
|
||||
((SunToolkit)toolkit).needUpdateWindow()))
|
||||
|
||||
@@ -324,6 +324,11 @@ public final class AWTAccessor {
|
||||
* window currently owns.
|
||||
*/
|
||||
Window[] getOwnedWindows(Window w);
|
||||
|
||||
boolean countersEnabled(Window w);
|
||||
void bumpCounter(Window w, String counterName);
|
||||
long getCounter(Window w, String counterName);
|
||||
long getCounterPerSecond(Window w, String counterName);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
108
test/jdk/jb/java/awt/Counters/UpdateWindowsCounter.java
Normal file
108
test/jdk/jb/java/awt/Counters/UpdateWindowsCounter.java
Normal file
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 2024 JetBrains s.r.o.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
|
||||
import jdk.test.lib.process.ProcessTools;
|
||||
import jdk.test.lib.process.OutputAnalyzer;
|
||||
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.SwingConstants;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.Timer;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @summary Verifies that swing.RepaintManager.updateWindows performance counter gets
|
||||
* updated for a Swing application
|
||||
* @key headful
|
||||
* @library /test/lib
|
||||
* @run main UpdateWindowsCounter
|
||||
*/
|
||||
|
||||
public class UpdateWindowsCounter {
|
||||
private static int counter = 25;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (args.length > 0) {
|
||||
runSwingApp();
|
||||
} else {
|
||||
runOneTest("-Dawt.window.counters=stdout", "swing.RepaintManager.updateWindows per second");
|
||||
runOneTest("-Dawt.window.counters=swing.RepaintManager.updateWindows,stdout", "swing.RepaintManager.updateWindows per second");
|
||||
runOneTest("-Dawt.window.counters=stderr,swing.RepaintManager.updateWindows", "swing.RepaintManager.updateWindows per second");
|
||||
runOneTest("-Dawt.window.counters", "swing.RepaintManager.updateWindows per second");
|
||||
runOneTest("-Dawt.window.counters=", "swing.RepaintManager.updateWindows per second");
|
||||
runOneTest("-Dawt.window.counters=swing.RepaintManager.updateWindows", "swing.RepaintManager.updateWindows per second");
|
||||
|
||||
runOneNegativeTest("", "swing.RepaintManager.updateWindows");
|
||||
runOneNegativeTest("-Dawt.window.counters=UNCOUNTABLE", "swing.RepaintManager.updateWindows");
|
||||
}
|
||||
}
|
||||
|
||||
private static void runOneTest(String vmArg, String expectedOutput) throws Exception {
|
||||
ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(vmArg,
|
||||
UpdateWindowsCounter.class.getName(),
|
||||
"test");
|
||||
|
||||
OutputAnalyzer output = new OutputAnalyzer(pb.start());
|
||||
output.shouldContain(expectedOutput);
|
||||
}
|
||||
|
||||
private static void runOneNegativeTest(String vmArg, String unexpectedOutput) throws Exception {
|
||||
ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(vmArg,
|
||||
UpdateWindowsCounter.class.getName(),
|
||||
"test");
|
||||
|
||||
OutputAnalyzer output = new OutputAnalyzer(pb.start());
|
||||
output.shouldNotContain(unexpectedOutput);
|
||||
}
|
||||
|
||||
private static void runSwingApp() {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
JFrame frame = new JFrame("Timer App");
|
||||
frame.setSize(300, 200);
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
|
||||
JLabel label = new JLabel("---", SwingConstants.CENTER);
|
||||
frame.getContentPane().add(label);
|
||||
|
||||
Timer timer = new Timer(100, new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
counter--;
|
||||
label.setText(String.valueOf(counter));
|
||||
|
||||
if (counter == 0) {
|
||||
((Timer) e.getSource()).stop();
|
||||
frame.dispose();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frame.setVisible(true);
|
||||
timer.start();
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user