Skip to content

Commit 6e03d1f

Browse files
committed
[GR-19220] Report a few more GC stats in GC.stat (#2280)
PullRequest: truffleruby/2467
2 parents 3ac8cc4 + 886e6fd commit 6e03d1f

File tree

4 files changed

+176
-9
lines changed

4 files changed

+176
-9
lines changed

spec/ruby/core/gc/stat_spec.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,16 @@
1313
stat.should be_kind_of(Hash)
1414
stat.keys.should include(:count)
1515
end
16+
17+
it "increases count after GC is run" do
18+
count = GC.stat(:count)
19+
GC.start
20+
GC.stat(:count).should > count
21+
end
22+
23+
it "increases major_gc_count after GC is run" do
24+
count = GC.stat(:major_gc_count)
25+
GC.start
26+
GC.stat(:major_gc_count).should > count
27+
end
1628
end

spec/tags/core/gc/stat_tags.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
slow:GC.stat increases count after GC is run
2+
slow:GC.stat increases major_gc_count after GC is run

src/main/java/org/truffleruby/core/GCNodes.java

Lines changed: 111 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,13 @@
1111

1212
import java.lang.management.GarbageCollectorMXBean;
1313
import java.lang.management.ManagementFactory;
14+
import java.lang.management.MemoryPoolMXBean;
15+
import java.lang.management.MemoryUsage;
1416
import java.time.Duration;
17+
import java.util.Arrays;
1518

19+
import com.oracle.truffle.api.dsl.Cached;
20+
import org.jcodings.specific.UTF8Encoding;
1621
import org.truffleruby.SuppressFBWarnings;
1722
import org.truffleruby.builtins.CoreMethod;
1823
import org.truffleruby.builtins.CoreMethodArrayArgumentsNode;
@@ -23,6 +28,9 @@
2328
import org.truffleruby.builtins.Primitive;
2429
import org.truffleruby.builtins.PrimitiveArrayArgumentsNode;
2530
import org.truffleruby.collections.WeakValueCache;
31+
import org.truffleruby.core.array.RubyArray;
32+
import org.truffleruby.core.rope.CodeRange;
33+
import org.truffleruby.core.string.StringNodes;
2634
import org.truffleruby.language.SafepointManager;
2735
import org.truffleruby.language.control.RaiseException;
2836

@@ -103,10 +111,6 @@ public abstract static class CountNode extends CoreMethodArrayArgumentsNode {
103111
@TruffleBoundary
104112
@Specialization
105113
protected int count() {
106-
return getCollectionCount();
107-
}
108-
109-
public static int getCollectionCount() {
110114
int count = 0;
111115
for (GarbageCollectorMXBean bean : ManagementFactory.getGarbageCollectorMXBeans()) {
112116
count += bean.getCollectionCount();
@@ -122,15 +126,115 @@ public abstract static class TimeNode extends CoreMethodArrayArgumentsNode {
122126
@TruffleBoundary
123127
@Specialization
124128
protected long time() {
125-
return getCollectionTime();
129+
long time = 0;
130+
for (GarbageCollectorMXBean bean : ManagementFactory.getGarbageCollectorMXBeans()) {
131+
time += bean.getCollectionTime();
132+
}
133+
return time;
126134
}
127135

128-
public static long getCollectionTime() {
136+
}
137+
138+
@Primitive(name = "gc_stat")
139+
public static abstract class GCStatPrimitiveNode extends PrimitiveArrayArgumentsNode {
140+
141+
@TruffleBoundary
142+
@Specialization
143+
protected RubyArray stat(
144+
@Cached StringNodes.MakeStringNode makeStringNode) {
129145
long time = 0;
146+
int count = 0;
147+
int minorCount = 0;
148+
int majorCount = 0;
149+
int unknownCount = 0;
150+
String[] memoryPoolNames = new String[0];
151+
Object[] memoryPools;
152+
153+
// Get GC time and counts from GarbageCollectorMXBean
130154
for (GarbageCollectorMXBean bean : ManagementFactory.getGarbageCollectorMXBeans()) {
155+
// Get MemoryPoolName relevant to GC
156+
if (bean.getMemoryPoolNames().length > memoryPoolNames.length) {
157+
// Since old generation memory pools are a superset of young generation memory pools,
158+
// it suffices to check that we have the longer list of memory pools
159+
memoryPoolNames = bean.getMemoryPoolNames();
160+
}
131161
time += bean.getCollectionTime();
162+
count += bean.getCollectionCount();
163+
switch (bean.getName()) {
164+
case "G1 Young Generation": // during 'jvm' and 'jvm-ce'
165+
case "PS Scavenge": // during 'jvm --vm.XX:+UseParallelGC'
166+
case "young generation scavenger": // during 'native'
167+
minorCount += bean.getCollectionCount();
168+
break;
169+
case "G1 Old Generation": // during 'jvm' and 'jvm-ce'
170+
case "PS MarkSweep": // during 'jvm --vm.XX:+UseParallelGC'
171+
case "complete scavenger": // during 'native'
172+
majorCount += bean.getCollectionCount();
173+
break;
174+
default:
175+
unknownCount += bean.getCollectionCount();
176+
break;
177+
}
132178
}
133-
return time;
179+
180+
// Get memory usage values from relevant memory pools (2-3 / ~8 are relevant)
181+
memoryPools = new Object[memoryPoolNames.length];
182+
// On Native Image, ManagementFactory.getMemoryPoolMXBeans() is empty
183+
Arrays.fill(memoryPools, nil);
184+
for (int i = 0; i < memoryPoolNames.length; i++) {
185+
String memoryPoolName = memoryPoolNames[i];
186+
for (MemoryPoolMXBean bean : ManagementFactory.getMemoryPoolMXBeans()) {
187+
if (bean.getName().equals(memoryPoolName)) {
188+
memoryPools[i] = beanToArray(bean);
189+
}
190+
}
191+
}
192+
193+
MemoryUsage total = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
194+
195+
Object[] memoryPoolNamesCast = new Object[memoryPoolNames.length];
196+
for (int i = 0; i < memoryPoolNames.length; i++) {
197+
memoryPoolNamesCast[i] = makeStringNode
198+
.executeMake(memoryPoolNames[i], UTF8Encoding.INSTANCE, CodeRange.CR_UNKNOWN);
199+
}
200+
201+
202+
return createArray(
203+
new Object[]{
204+
time,
205+
count,
206+
minorCount,
207+
majorCount,
208+
unknownCount,
209+
createArray(new long[]{
210+
total.getUsed(),
211+
total.getCommitted(),
212+
total.getInit(),
213+
total.getMax(),
214+
}),
215+
createArray(memoryPoolNamesCast),
216+
createArray(memoryPools) });
217+
}
218+
219+
protected RubyArray beanToArray(MemoryPoolMXBean bean) {
220+
MemoryUsage usage = bean.getUsage();
221+
MemoryUsage peak = bean.getPeakUsage();
222+
MemoryUsage last = bean.getCollectionUsage();
223+
return createArray(
224+
new long[]{
225+
usage.getUsed(),
226+
usage.getCommitted(),
227+
usage.getInit(),
228+
usage.getMax(),
229+
peak.getUsed(),
230+
peak.getCommitted(),
231+
peak.getInit(),
232+
peak.getMax(),
233+
last.getUsed(),
234+
last.getCommitted(),
235+
last.getInit(),
236+
last.getMax(),
237+
});
134238
}
135239

136240
}

src/main/ruby/truffleruby/core/gc.rb

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,59 @@ def garbage_collect
7676
end
7777

7878
def self.stat(option = nil)
79+
time, count, minor_count, major_count, unknown_count, heap, memory_pool_names, memory_pool_info = Primitive.gc_stat()
80+
used, committed, init, max = heap
81+
82+
# Initialize stat for statistics that come from memory pools, and populate it with some final stats
7983
stat = {
80-
count: GC.count,
81-
time: GC.time,
84+
count: count,
85+
time: time,
86+
minor_gc_count: minor_count,
87+
major_gc_count: major_count,
88+
unknown_count: unknown_count, # if nonzero, major or minor count needs to be updated for this GC case
89+
heap_available_slots: committed,
90+
heap_live_slots: used,
91+
heap_free_slots: committed - used,
92+
used: used,
93+
committed: committed,
94+
init: init,
95+
max: max,
96+
peak_used: 0,
97+
peak_committed: 0,
98+
peak_init: 0,
99+
peak_max: 0,
100+
last_used: 0,
101+
last_committed: 0,
102+
last_init: 0,
103+
last_max: 0,
82104
}
105+
106+
memory_pool_names.each_with_index do |memory_pool_name, i|
107+
# Populate memory pool specific stats
108+
info = memory_pool_info[i]
109+
if info
110+
stat[memory_pool_name] = data = {
111+
used: info[0],
112+
committed: info[1],
113+
init: info[2],
114+
max: info[3],
115+
peak_used: info[4],
116+
peak_committed: info[5],
117+
peak_init: info[6],
118+
peak_max: info[7],
119+
last_used: info[8],
120+
last_committed: info[9],
121+
last_init: info[10],
122+
last_max: info[11],
123+
}
124+
125+
# Calculate stats across memory pools for peak_/last_ (we already know the values for current usage)
126+
data.each_pair do |key, value|
127+
stat[key] += value if key.start_with?('peak_', 'last_')
128+
end
129+
end
130+
end
131+
83132
return stat unless option
84133

85134
if stat[option]

0 commit comments

Comments
 (0)