Skip to content

Commit 65f6bf6

Browse files
[GR-52484] Partial support for physical memory usage and JFR tests for periodic native events.
PullRequest: graal/17385
2 parents 643e90f + 1f19cf1 commit 65f6bf6

File tree

3 files changed

+161
-3
lines changed

3 files changed

+161
-3
lines changed

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/PhysicalMemory.java

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,17 @@
2424
*/
2525
package com.oracle.svm.core.heap;
2626

27+
import java.io.BufferedReader;
28+
import java.io.FileReader;
29+
import java.io.IOException;
30+
import java.lang.management.ManagementFactory;
31+
import java.nio.charset.StandardCharsets;
32+
import java.util.ArrayList;
33+
import java.util.List;
2734
import java.util.concurrent.locks.ReentrantLock;
2835

2936
import org.graalvm.nativeimage.ImageSingletons;
37+
import org.graalvm.nativeimage.Platform;
3038
import org.graalvm.word.UnsignedWord;
3139
import org.graalvm.word.WordFactory;
3240

@@ -38,6 +46,7 @@
3846
import com.oracle.svm.core.thread.VMOperation;
3947
import com.oracle.svm.core.util.UnsignedUtils;
4048
import com.oracle.svm.core.util.VMError;
49+
import com.sun.management.OperatingSystemMXBean;
4150

4251
/**
4352
* Contains static methods to get configuration of physical memory.
@@ -50,6 +59,8 @@ public interface PhysicalMemorySupport {
5059
UnsignedWord size();
5160
}
5261

62+
private static final long K = 1024;
63+
5364
private static final ReentrantLock LOCK = new ReentrantLock();
5465
private static final UnsignedWord UNSET_SENTINEL = UnsignedUtils.MAX_VALUE;
5566
private static UnsignedWord cachedSize = UNSET_SENTINEL;
@@ -107,6 +118,73 @@ public static UnsignedWord size() {
107118
return cachedSize;
108119
}
109120

121+
/** Returns the amount of used physical memory in bytes, or -1 if not supported. */
122+
public static long usedSize() {
123+
// Windows, macOS, and containerized Linux use the OS bean.
124+
if (Platform.includedIn(Platform.WINDOWS.class) ||
125+
Platform.includedIn(Platform.MACOS.class) ||
126+
(Containers.isContainerized() && Containers.memoryLimitInBytes() > 0)) {
127+
OperatingSystemMXBean osBean = (com.sun.management.OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
128+
return osBean.getTotalMemorySize() - osBean.getFreeMemorySize();
129+
}
130+
131+
// Non-containerized Linux uses /proc/meminfo.
132+
if (Platform.includedIn(Platform.LINUX.class)) {
133+
return getUsedSizeFromProcMemInfo();
134+
}
135+
136+
return -1L;
137+
}
138+
139+
// Will be removed as part of GR-51479.
140+
private static long getUsedSizeFromProcMemInfo() {
141+
try {
142+
List<String> lines = readAllLines("/proc/meminfo");
143+
for (String line : lines) {
144+
if (line.contains("MemAvailable")) {
145+
return size().rawValue() - parseFirstNumber(line) * K;
146+
}
147+
}
148+
} catch (Exception e) {
149+
/* Nothing to do. */
150+
}
151+
return -1L;
152+
}
153+
154+
private static List<String> readAllLines(String fileName) throws IOException {
155+
List<String> lines = new ArrayList<>();
156+
try (BufferedReader bufferedReader = new BufferedReader(new FileReader(fileName, StandardCharsets.UTF_8))) {
157+
String line;
158+
while ((line = bufferedReader.readLine()) != null) {
159+
lines.add(line);
160+
}
161+
}
162+
return lines;
163+
}
164+
165+
/** Parses the first number in the String as a long value. */
166+
private static long parseFirstNumber(String str) {
167+
int firstDigit = -1;
168+
int lastDigit = -1;
169+
170+
for (int i = 0; i < str.length(); i++) {
171+
if (Character.isDigit(str.charAt(i))) {
172+
if (firstDigit == -1) {
173+
firstDigit = i;
174+
}
175+
lastDigit = i;
176+
} else if (firstDigit != -1) {
177+
break;
178+
}
179+
}
180+
181+
if (firstDigit >= 0) {
182+
String number = str.substring(firstDigit, lastDigit + 1);
183+
return Long.parseLong(number);
184+
}
185+
return -1;
186+
}
187+
110188
/**
111189
* Returns the size of physical memory in bytes that has been previously cached. This method
112190
* must not be called if {@link #isInitialized()} is still false.

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/EveryChunkNativePeriodicEvents.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public class EveryChunkNativePeriodicEvents extends Event {
5151

5252
public static void emit() {
5353
emitJavaThreadStats();
54-
emitPhysicalMemory();
54+
emitPhysicalMemory(PhysicalMemory.usedSize());
5555
emitClassLoadingStatistics();
5656
emitPerThreadEvents();
5757
}
@@ -75,15 +75,15 @@ private static void emitJavaThreadStats() {
7575
}
7676

7777
@Uninterruptible(reason = "Accesses a JFR buffer.")
78-
private static void emitPhysicalMemory() {
78+
private static void emitPhysicalMemory(long usedSize) {
7979
if (JfrEvent.PhysicalMemory.shouldEmit()) {
8080
JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class);
8181
JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data);
8282

8383
JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.PhysicalMemory);
8484
JfrNativeEventWriter.putLong(data, JfrTicks.elapsedTicks());
8585
JfrNativeEventWriter.putLong(data, PhysicalMemory.getCachedSize().rawValue());
86-
JfrNativeEventWriter.putLong(data, 0); /* used size */
86+
JfrNativeEventWriter.putLong(data, usedSize); /* used size */
8787
JfrNativeEventWriter.endSmallEvent(data);
8888
}
8989
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved.
3+
* Copyright (c) 2024, 2024, Red Hat Inc. All rights reserved.
4+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5+
*
6+
* This code is free software; you can redistribute it and/or modify it
7+
* under the terms of the GNU General Public License version 2 only, as
8+
* published by the Free Software Foundation. Oracle designates this
9+
* particular file as subject to the "Classpath" exception as provided
10+
* by Oracle in the LICENSE file that accompanied this code.
11+
*
12+
* This code is distributed in the hope that it will be useful, but WITHOUT
13+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15+
* version 2 for more details (a copy is included in the LICENSE file that
16+
* accompanied this code).
17+
*
18+
* You should have received a copy of the GNU General Public License version
19+
* 2 along with this work; if not, write to the Free Software Foundation,
20+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21+
*
22+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
23+
* or visit www.oracle.com if you need additional information or have any
24+
* questions.
25+
*/
26+
27+
package com.oracle.svm.test.jfr;
28+
29+
import static org.junit.Assert.assertTrue;
30+
31+
import java.util.List;
32+
33+
import org.junit.Test;
34+
35+
import com.oracle.svm.core.jfr.JfrEvent;
36+
37+
import jdk.jfr.Recording;
38+
import jdk.jfr.consumer.RecordedEvent;
39+
40+
/**
41+
* Tests the VM-level JFR events that are created periodically upon every chunk. Note that the
42+
* events ThreadCPULoad and ThreadAllocationStatistics are not tested here since they already have
43+
* their own individual tests.
44+
*/
45+
public class TestEveryChunkNativePeriodicEvents extends JfrRecordingTest {
46+
@Test
47+
public void test() throws Throwable {
48+
String[] events = new String[]{JfrEvent.JavaThreadStatistics.getName(), JfrEvent.PhysicalMemory.getName(), JfrEvent.ClassLoadingStatistics.getName()};
49+
Recording recording = startRecording(events);
50+
stopRecording(recording, TestEveryChunkNativePeriodicEvents::validateEvents);
51+
}
52+
53+
private static void validateEvents(List<RecordedEvent> events) {
54+
boolean foundJavaThreadStatistics = false;
55+
boolean foundPhysicalMemory = false;
56+
boolean foundClassLoadingStatistics = false;
57+
58+
for (RecordedEvent e : events) {
59+
String eventName = e.getEventType().getName();
60+
if (eventName.equals(JfrEvent.JavaThreadStatistics.getName())) {
61+
foundJavaThreadStatistics = true;
62+
assertTrue(e.getLong("activeCount") > 1);
63+
assertTrue(e.getLong("daemonCount") > 0);
64+
assertTrue(e.getLong("accumulatedCount") > 1);
65+
assertTrue(e.getLong("peakCount") > 1);
66+
} else if (eventName.equals(JfrEvent.PhysicalMemory.getName())) {
67+
foundPhysicalMemory = true;
68+
assertTrue(e.getLong("totalSize") > 0);
69+
assertTrue(e.getLong("usedSize") > 0);
70+
} else if (eventName.equals(JfrEvent.ClassLoadingStatistics.getName())) {
71+
foundClassLoadingStatistics = true;
72+
assertTrue(e.getLong("loadedClassCount") > 0);
73+
}
74+
}
75+
76+
assertTrue(foundJavaThreadStatistics);
77+
assertTrue(foundPhysicalMemory);
78+
assertTrue(foundClassLoadingStatistics);
79+
}
80+
}

0 commit comments

Comments
 (0)