1 /*
  2  * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  *
 23  */
 24 #include "precompiled.hpp"
 25 
 26 #include "gc/shenandoah/shenandoahAsserts.hpp"
 27 #include "gc/shenandoah/shenandoahMmuTracker.hpp"
 28 #include "gc/shenandoah/shenandoahHeap.inline.hpp"
 29 #include "gc/shenandoah/shenandoahOldGeneration.hpp"
 30 #include "gc/shenandoah/shenandoahYoungGeneration.hpp"
 31 #include "logging/log.hpp"
 32 #include "runtime/os.hpp"
 33 #include "runtime/task.hpp"
 34 
 35 class ShenandoahMmuTask : public PeriodicTask {
 36   ShenandoahMmuTracker* _mmu_tracker;
 37 public:
 38   explicit ShenandoahMmuTask(ShenandoahMmuTracker* mmu_tracker) :
 39     PeriodicTask(GCPauseIntervalMillis), _mmu_tracker(mmu_tracker) {}
 40 
 41   void task() override {
 42     _mmu_tracker->report();
 43   }
 44 };
 45 
 46 class ThreadTimeAccumulator : public ThreadClosure {
 47  public:
 48   size_t total_time;
 49   ThreadTimeAccumulator() : total_time(0) {}
 50   void do_thread(Thread* thread) override {
 51     total_time += os::thread_cpu_time(thread);
 52   }
 53 };
 54 
 55 ShenandoahMmuTracker::ShenandoahMmuTracker() :
 56     _most_recent_timestamp(0.0),
 57     _most_recent_gc_time(0.0),
 58     _most_recent_gcu(0.0),
 59     _most_recent_mutator_time(0.0),
 60     _most_recent_mu(0.0),
 61     _most_recent_periodic_time_stamp(0.0),
 62     _most_recent_periodic_gc_time(0.0),
 63     _most_recent_periodic_mutator_time(0.0),
 64     _mmu_periodic_task(new ShenandoahMmuTask(this)) {
 65 }
 66 
 67 ShenandoahMmuTracker::~ShenandoahMmuTracker() {
 68   _mmu_periodic_task->disenroll();
 69   delete _mmu_periodic_task;
 70 }
 71 
 72 void ShenandoahMmuTracker::fetch_cpu_times(double &gc_time, double &mutator_time) {
 73   ThreadTimeAccumulator cl;
 74   // We include only the gc threads because those are the only threads
 75   // we are responsible for.
 76   ShenandoahHeap::heap()->gc_threads_do(&cl);
 77   double most_recent_gc_thread_time = double(cl.total_time) / NANOSECS_PER_SEC;
 78   gc_time = most_recent_gc_thread_time;
 79 
 80   double process_real_time(0.0), process_user_time(0.0), process_system_time(0.0);
 81   bool valid = os::getTimesSecs(&process_real_time, &process_user_time, &process_system_time);
 82   assert(valid, "don't know why this would not be valid");
 83   mutator_time =(process_user_time + process_system_time) - most_recent_gc_thread_time;
 84 }
 85 
 86 void ShenandoahMmuTracker::update_utilization(ShenandoahGeneration* generation, size_t gcid, const char *msg) {
 87   double current = os::elapsedTime();
 88   _most_recent_gcid = gcid;
 89   _most_recent_is_full = false;
 90 
 91   if (gcid == 0) {
 92     fetch_cpu_times(_most_recent_gc_time, _most_recent_mutator_time);
 93 
 94     _most_recent_timestamp = current;
 95   } else {
 96     double gc_cycle_period = current - _most_recent_timestamp;
 97     _most_recent_timestamp = current;
 98 
 99     double gc_thread_time, mutator_thread_time;
100     fetch_cpu_times(gc_thread_time, mutator_thread_time);
101     double gc_time = gc_thread_time - _most_recent_gc_time;
102     _most_recent_gc_time = gc_thread_time;
103     _most_recent_gcu = gc_time / (_active_processors * gc_cycle_period);
104     double mutator_time = mutator_thread_time - _most_recent_mutator_time;
105     _most_recent_mutator_time = mutator_thread_time;
106     _most_recent_mu = mutator_time / (_active_processors * gc_cycle_period);
107     log_info(gc, ergo)("At end of %s: GCU: %.1f%%, MU: %.1f%% during period of %.3fs",
108                        msg, _most_recent_gcu * 100, _most_recent_mu * 100, gc_cycle_period);
109   }
110 }
111 
112 void ShenandoahMmuTracker::record_young(ShenandoahGeneration* generation, size_t gcid) {
113   update_utilization(generation, gcid, "Concurrent Young GC");
114 }
115 
116 void ShenandoahMmuTracker::record_global(ShenandoahGeneration* generation, size_t gcid) {
117   update_utilization(generation, gcid, "Concurrent Global GC");
118 }
119 
120 void ShenandoahMmuTracker::record_bootstrap(ShenandoahGeneration* generation, size_t gcid, bool candidates_for_mixed) {
121   // Not likely that this will represent an "ideal" GCU, but doesn't hurt to try
122   update_utilization(generation, gcid, "Concurrent Bootstrap GC");
123 }
124 
125 void ShenandoahMmuTracker::record_old_marking_increment(ShenandoahGeneration* generation, size_t gcid, bool old_marking_done,
126                                                         bool has_old_candidates) {
127   // No special processing for old marking
128   double now = os::elapsedTime();
129   double duration = now - _most_recent_timestamp;
130 
131   double gc_time, mutator_time;
132   fetch_cpu_times(gc_time, mutator_time);
133   double gcu = (gc_time - _most_recent_gc_time) / duration;
134   double mu = (mutator_time - _most_recent_mutator_time) / duration;
135   log_info(gc, ergo)("At end of %s: GCU: %.1f%%, MU: %.1f%% for duration %.3fs (totals to be subsumed in next gc report)",
136                      old_marking_done? "last OLD marking increment": "OLD marking increment",
137                      gcu * 100, mu * 100, duration);
138 }
139 
140 void ShenandoahMmuTracker::record_mixed(ShenandoahGeneration* generation, size_t gcid, bool is_mixed_done) {
141   update_utilization(generation, gcid, "Mixed Concurrent GC");
142 }
143 
144 void ShenandoahMmuTracker::record_degenerated(ShenandoahGeneration* generation,
145                                               size_t gcid, bool is_old_bootstrap, bool is_mixed_done) {
146   if ((gcid == _most_recent_gcid) && _most_recent_is_full) {
147     // Do nothing.  This is a redundant recording for the full gc that just completed.
148     // TODO: avoid making the call to record_degenerated() in the case that this degenerated upgraded to full gc.
149   } else if (is_old_bootstrap) {
150     update_utilization(generation, gcid, "Degenerated Bootstrap Old GC");
151   } else {
152     update_utilization(generation, gcid, "Degenerated Young GC");
153   }
154 }
155 
156 void ShenandoahMmuTracker::record_full(ShenandoahGeneration* generation, size_t gcid) {
157   update_utilization(generation, gcid, "Full GC");
158   _most_recent_is_full = true;
159 }
160 
161 void ShenandoahMmuTracker::report() {
162   // This is only called by the periodic thread.
163   double current = os::elapsedTime();
164   double time_delta = current - _most_recent_periodic_time_stamp;
165   _most_recent_periodic_time_stamp = current;
166 
167   double gc_time, mutator_time;
168   fetch_cpu_times(gc_time, mutator_time);
169 
170   double gc_delta = gc_time - _most_recent_periodic_gc_time;
171   _most_recent_periodic_gc_time = gc_time;
172 
173   double mutator_delta = mutator_time - _most_recent_periodic_mutator_time;
174   _most_recent_periodic_mutator_time = mutator_time;
175 
176   double mu = mutator_delta / (_active_processors * time_delta);
177   double gcu = gc_delta / (_active_processors * time_delta);
178   log_info(gc)("Periodic Sample: GCU = %.3f%%, MU = %.3f%% during most recent %.1fs", gcu * 100, mu * 100, time_delta);
179 }
180 
181 void ShenandoahMmuTracker::initialize() {
182   // initialize static data
183   _active_processors = os::initial_active_processor_count();
184 
185   double _most_recent_periodic_time_stamp = os::elapsedTime();
186   fetch_cpu_times(_most_recent_periodic_gc_time, _most_recent_periodic_mutator_time);
187   _mmu_periodic_task->enroll();
188 }
189 
190 ShenandoahGenerationSizer::ShenandoahGenerationSizer(ShenandoahMmuTracker* mmu_tracker)
191   : _sizer_kind(SizerDefaults),
192     _use_adaptive_sizing(true),
193     _min_desired_young_regions(0),
194     _max_desired_young_regions(0),
195     _resize_increment(double(YoungGenerationSizeIncrement) / 100.0),
196     _mmu_tracker(mmu_tracker) {
197 
198   if (FLAG_IS_CMDLINE(NewRatio)) {
199     if (FLAG_IS_CMDLINE(NewSize) || FLAG_IS_CMDLINE(MaxNewSize)) {
200       log_warning(gc, ergo)("-XX:NewSize and -XX:MaxNewSize override -XX:NewRatio");
201     } else {
202       _sizer_kind = SizerNewRatio;
203       _use_adaptive_sizing = false;
204       return;
205     }
206   }
207 
208   if (NewSize > MaxNewSize) {
209     if (FLAG_IS_CMDLINE(MaxNewSize)) {
210       log_warning(gc, ergo)("NewSize (" SIZE_FORMAT "k) is greater than the MaxNewSize (" SIZE_FORMAT "k). "
211                             "A new max generation size of " SIZE_FORMAT "k will be used.",
212                             NewSize/K, MaxNewSize/K, NewSize/K);
213     }
214     FLAG_SET_ERGO(MaxNewSize, NewSize);
215   }
216 
217   if (FLAG_IS_CMDLINE(NewSize)) {
218     _min_desired_young_regions = MAX2(uint(NewSize / ShenandoahHeapRegion::region_size_bytes()), 1U);
219     if (FLAG_IS_CMDLINE(MaxNewSize)) {
220       _max_desired_young_regions = MAX2(uint(MaxNewSize / ShenandoahHeapRegion::region_size_bytes()), 1U);
221       _sizer_kind = SizerMaxAndNewSize;
222       _use_adaptive_sizing = _min_desired_young_regions != _max_desired_young_regions;
223     } else {
224       _sizer_kind = SizerNewSizeOnly;
225     }
226   } else if (FLAG_IS_CMDLINE(MaxNewSize)) {
227     _max_desired_young_regions = MAX2(uint(MaxNewSize / ShenandoahHeapRegion::region_size_bytes()), 1U);
228     _sizer_kind = SizerMaxNewSizeOnly;
229   }
230 }
231 
232 size_t ShenandoahGenerationSizer::calculate_min_young_regions(size_t heap_region_count) {
233   size_t min_young_regions = (heap_region_count * ShenandoahMinYoungPercentage) / 100;
234   return MAX2(min_young_regions, (size_t) 1U);
235 }
236 
237 size_t ShenandoahGenerationSizer::calculate_max_young_regions(size_t heap_region_count) {
238   size_t max_young_regions = (heap_region_count * ShenandoahMaxYoungPercentage) / 100;
239   return MAX2(max_young_regions, (size_t) 1U);
240 }
241 
242 void ShenandoahGenerationSizer::recalculate_min_max_young_length(size_t heap_region_count) {
243   assert(heap_region_count > 0, "Heap must be initialized");
244 
245   switch (_sizer_kind) {
246     case SizerDefaults:
247       _min_desired_young_regions = calculate_min_young_regions(heap_region_count);
248       _max_desired_young_regions = calculate_max_young_regions(heap_region_count);
249       break;
250     case SizerNewSizeOnly:
251       _max_desired_young_regions = calculate_max_young_regions(heap_region_count);
252       _max_desired_young_regions = MAX2(_min_desired_young_regions, _max_desired_young_regions);
253       break;
254     case SizerMaxNewSizeOnly:
255       _min_desired_young_regions = calculate_min_young_regions(heap_region_count);
256       _min_desired_young_regions = MIN2(_min_desired_young_regions, _max_desired_young_regions);
257       break;
258     case SizerMaxAndNewSize:
259       // Do nothing. Values set on the command line, don't update them at runtime.
260       break;
261     case SizerNewRatio:
262       _min_desired_young_regions = MAX2(uint(heap_region_count / (NewRatio + 1)), 1U);
263       _max_desired_young_regions = _min_desired_young_regions;
264       break;
265     default:
266       ShouldNotReachHere();
267   }
268 
269   assert(_min_desired_young_regions <= _max_desired_young_regions, "Invalid min/max young gen size values");
270 }
271 
272 void ShenandoahGenerationSizer::heap_size_changed(size_t heap_size) {
273   recalculate_min_max_young_length(heap_size / ShenandoahHeapRegion::region_size_bytes());
274 }
275 
276 // Returns true iff transfer is successful
277 bool ShenandoahGenerationSizer::transfer_to_old(size_t regions) const {
278   ShenandoahHeap* heap = ShenandoahHeap::heap();
279   ShenandoahGeneration* old_gen = heap->old_generation();
280   ShenandoahGeneration* young_gen = heap->young_generation();
281   size_t region_size_bytes = ShenandoahHeapRegion::region_size_bytes();
282   size_t bytes_to_transfer = regions * region_size_bytes;
283 
284   if (young_gen->free_unaffiliated_regions() < regions) {
285     return false;
286   } else if (old_gen->max_capacity() + bytes_to_transfer > heap->max_size_for(old_gen)) {
287     return false;
288   } else if (young_gen->max_capacity() - bytes_to_transfer < heap->min_size_for(young_gen)) {
289     return false;
290   } else {
291     young_gen->decrease_capacity(bytes_to_transfer);
292     old_gen->increase_capacity(bytes_to_transfer);
293     size_t new_size = old_gen->max_capacity();
294     log_info(gc)("Transfer " SIZE_FORMAT " region(s) from %s to %s, yielding increased size: " SIZE_FORMAT "%s",
295                  regions, young_gen->name(), old_gen->name(),
296                  byte_size_in_proper_unit(new_size), proper_unit_for_byte_size(new_size));
297     return true;
298   }
299 }
300 
301 // This is used when promoting humongous or highly utilized regular regions in place.  It is not required in this situation
302 // that the transferred regions be unaffiliated.
303 void ShenandoahGenerationSizer::force_transfer_to_old(size_t regions) const {
304   ShenandoahHeap* heap = ShenandoahHeap::heap();
305   ShenandoahGeneration* old_gen = heap->old_generation();
306   ShenandoahGeneration* young_gen = heap->young_generation();
307   size_t region_size_bytes = ShenandoahHeapRegion::region_size_bytes();
308   size_t bytes_to_transfer = regions * region_size_bytes;
309 
310   young_gen->decrease_capacity(bytes_to_transfer);
311   old_gen->increase_capacity(bytes_to_transfer);
312   size_t new_size = old_gen->max_capacity();
313   log_info(gc)("Forcing transfer of " SIZE_FORMAT " region(s) from %s to %s, yielding increased size: " SIZE_FORMAT "%s",
314                regions, young_gen->name(), old_gen->name(),
315                byte_size_in_proper_unit(new_size), proper_unit_for_byte_size(new_size));
316 }
317 
318 
319 bool ShenandoahGenerationSizer::transfer_to_young(size_t regions) const {
320   ShenandoahHeap* heap = ShenandoahHeap::heap();
321   ShenandoahGeneration* old_gen = heap->old_generation();
322   ShenandoahGeneration* young_gen = heap->young_generation();
323   size_t region_size_bytes = ShenandoahHeapRegion::region_size_bytes();
324   size_t bytes_to_transfer = regions * region_size_bytes;
325 
326   if (old_gen->free_unaffiliated_regions() < regions) {
327     return false;
328   } else if (young_gen->max_capacity() + bytes_to_transfer > heap->max_size_for(young_gen)) {
329     return false;
330   } else if (old_gen->max_capacity() - bytes_to_transfer < heap->min_size_for(old_gen)) {
331     return false;
332   } else {
333     old_gen->decrease_capacity(bytes_to_transfer);
334     young_gen->increase_capacity(bytes_to_transfer);
335     size_t new_size = young_gen->max_capacity();
336     log_info(gc)("Transfer " SIZE_FORMAT " region(s) from %s to %s, yielding increased size: " SIZE_FORMAT "%s",
337                  regions, old_gen->name(), young_gen->name(),
338                  byte_size_in_proper_unit(new_size), proper_unit_for_byte_size(new_size));
339     return true;
340   }
341 }
342 
343 size_t ShenandoahGenerationSizer::min_young_size() const {
344   return min_young_regions() * ShenandoahHeapRegion::region_size_bytes();
345 }
346 
347 size_t ShenandoahGenerationSizer::max_young_size() const {
348   return max_young_regions() * ShenandoahHeapRegion::region_size_bytes();
349 }