1 /*
  2  * Copyright (c) 2022, Amazon, Inc. 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 
 36 class ShenandoahMmuTask : public PeriodicTask {
 37   ShenandoahMmuTracker* _mmu_tracker;
 38 public:
 39   explicit ShenandoahMmuTask(ShenandoahMmuTracker* mmu_tracker) :
 40     PeriodicTask(GCPauseIntervalMillis), _mmu_tracker(mmu_tracker) {}
 41 
 42   void task() override {
 43     _mmu_tracker->report();
 44   }
 45 };
 46 
 47 class ThreadTimeAccumulator : public ThreadClosure {
 48  public:
 49   size_t total_time;
 50   ThreadTimeAccumulator() : total_time(0) {}
 51   void do_thread(Thread* thread) override {
 52     total_time += os::thread_cpu_time(thread);
 53   }
 54 };
 55 
 56 double ShenandoahMmuTracker::gc_thread_time_seconds() {
 57   ThreadTimeAccumulator cl;
 58   // We include only the gc threads because those are the only threads
 59   // we are responsible for.
 60   ShenandoahHeap::heap()->gc_threads_do(&cl);
 61   return double(cl.total_time) / NANOSECS_PER_SEC;
 62 }
 63 
 64 double ShenandoahMmuTracker::process_time_seconds() {
 65   double process_real_time(0.0), process_user_time(0.0), process_system_time(0.0);
 66   bool valid = os::getTimesSecs(&process_real_time, &process_user_time, &process_system_time);
 67   if (valid) {
 68     return process_user_time + process_system_time;
 69   }
 70   return 0.0;
 71 }
 72 
 73 ShenandoahMmuTracker::ShenandoahMmuTracker() :
 74     _generational_reference_time_s(0.0),
 75     _process_reference_time_s(0.0),
 76     _collector_reference_time_s(0.0),
 77     _mmu_periodic_task(new ShenandoahMmuTask(this)),
 78     _mmu_average(10, ShenandoahAdaptiveDecayFactor) {
 79 }
 80 
 81 ShenandoahMmuTracker::~ShenandoahMmuTracker() {
 82   _mmu_periodic_task->disenroll();
 83   delete _mmu_periodic_task;
 84 }
 85 
 86 void ShenandoahMmuTracker::record(ShenandoahGeneration* generation) {
 87   shenandoah_assert_control_or_vm_thread();
 88   double collector_time_s = gc_thread_time_seconds();
 89   double elapsed_gc_time_s = collector_time_s - _generational_reference_time_s;
 90   generation->add_collection_time(elapsed_gc_time_s);
 91   _generational_reference_time_s = collector_time_s;
 92 }
 93 
 94 void ShenandoahMmuTracker::report() {
 95   // This is only called by the periodic thread.
 96   double process_time_s = process_time_seconds();
 97   double elapsed_process_time_s = process_time_s - _process_reference_time_s;
 98   if (elapsed_process_time_s <= 0.01) {
 99     // No cpu time for this interval?
100     return;
101   }
102 
103   _process_reference_time_s = process_time_s;
104   double collector_time_s = gc_thread_time_seconds();
105   double elapsed_collector_time_s = collector_time_s - _collector_reference_time_s;
106   _collector_reference_time_s = collector_time_s;
107   double minimum_mutator_utilization = ((elapsed_process_time_s - elapsed_collector_time_s) / elapsed_process_time_s) * 100;
108   _mmu_average.add(minimum_mutator_utilization);
109   log_info(gc)("Average MMU = %.3f", _mmu_average.davg());
110 }
111 
112 void ShenandoahMmuTracker::initialize() {
113   _process_reference_time_s = process_time_seconds();
114   _generational_reference_time_s = gc_thread_time_seconds();
115   _collector_reference_time_s = _generational_reference_time_s;
116   _mmu_periodic_task->enroll();
117 }
118 
119 ShenandoahGenerationSizer::ShenandoahGenerationSizer(ShenandoahMmuTracker* mmu_tracker)
120   : _sizer_kind(SizerDefaults),
121     _use_adaptive_sizing(true),
122     _min_desired_young_regions(0),
123     _max_desired_young_regions(0),
124     _resize_increment(double(YoungGenerationSizeIncrement) / 100.0),
125     _mmu_tracker(mmu_tracker) {
126 
127   if (FLAG_IS_CMDLINE(NewRatio)) {
128     if (FLAG_IS_CMDLINE(NewSize) || FLAG_IS_CMDLINE(MaxNewSize)) {
129       log_warning(gc, ergo)("-XX:NewSize and -XX:MaxNewSize override -XX:NewRatio");
130     } else {
131       _sizer_kind = SizerNewRatio;
132       _use_adaptive_sizing = false;
133       return;
134     }
135   }
136 
137   if (NewSize > MaxNewSize) {
138     if (FLAG_IS_CMDLINE(MaxNewSize)) {
139       log_warning(gc, ergo)("NewSize (" SIZE_FORMAT "k) is greater than the MaxNewSize (" SIZE_FORMAT "k). "
140                             "A new max generation size of " SIZE_FORMAT "k will be used.",
141                             NewSize/K, MaxNewSize/K, NewSize/K);
142     }
143     FLAG_SET_ERGO(MaxNewSize, NewSize);
144   }
145 
146   if (FLAG_IS_CMDLINE(NewSize)) {
147     _min_desired_young_regions = MAX2(uint(NewSize / ShenandoahHeapRegion::region_size_bytes()), 1U);
148     if (FLAG_IS_CMDLINE(MaxNewSize)) {
149       _max_desired_young_regions = MAX2(uint(MaxNewSize / ShenandoahHeapRegion::region_size_bytes()), 1U);
150       _sizer_kind = SizerMaxAndNewSize;
151       _use_adaptive_sizing = _min_desired_young_regions != _max_desired_young_regions;
152     } else {
153       _sizer_kind = SizerNewSizeOnly;
154     }
155   } else if (FLAG_IS_CMDLINE(MaxNewSize)) {
156     _max_desired_young_regions = MAX2(uint(MaxNewSize / ShenandoahHeapRegion::region_size_bytes()), 1U);
157     _sizer_kind = SizerMaxNewSizeOnly;
158   }
159 }
160 
161 size_t ShenandoahGenerationSizer::calculate_min_young_regions(size_t heap_region_count) {
162   size_t min_young_regions = (heap_region_count * ShenandoahMinYoungPercentage) / 100;
163   return MAX2(uint(min_young_regions), 1U);
164 }
165 
166 size_t ShenandoahGenerationSizer::calculate_max_young_regions(size_t heap_region_count) {
167   size_t max_young_regions = (heap_region_count * ShenandoahMaxYoungPercentage) / 100;
168   return MAX2(uint(max_young_regions), 1U);
169 }
170 
171 void ShenandoahGenerationSizer::recalculate_min_max_young_length(size_t heap_region_count) {
172   assert(heap_region_count > 0, "Heap must be initialized");
173 
174   switch (_sizer_kind) {
175     case SizerDefaults:
176       _min_desired_young_regions = calculate_min_young_regions(heap_region_count);
177       _max_desired_young_regions = calculate_max_young_regions(heap_region_count);
178       break;
179     case SizerNewSizeOnly:
180       _max_desired_young_regions = calculate_max_young_regions(heap_region_count);
181       _max_desired_young_regions = MAX2(_min_desired_young_regions, _max_desired_young_regions);
182       break;
183     case SizerMaxNewSizeOnly:
184       _min_desired_young_regions = calculate_min_young_regions(heap_region_count);
185       _min_desired_young_regions = MIN2(_min_desired_young_regions, _max_desired_young_regions);
186       break;
187     case SizerMaxAndNewSize:
188       // Do nothing. Values set on the command line, don't update them at runtime.
189       break;
190     case SizerNewRatio:
191       _min_desired_young_regions = MAX2(uint(heap_region_count / (NewRatio + 1)), 1U);
192       _max_desired_young_regions = _min_desired_young_regions;
193       break;
194     default:
195       ShouldNotReachHere();
196   }
197 
198   assert(_min_desired_young_regions <= _max_desired_young_regions, "Invalid min/max young gen size values");
199 }
200 
201 void ShenandoahGenerationSizer::heap_size_changed(size_t heap_size) {
202   recalculate_min_max_young_length(heap_size / ShenandoahHeapRegion::region_size_bytes());
203 }
204 
205 bool ShenandoahGenerationSizer::adjust_generation_sizes() const {
206   shenandoah_assert_generational();
207   if (!use_adaptive_sizing()) {
208     return false;
209   }
210 
211   if (_mmu_tracker->average() >= double(GCTimeRatio)) {
212     return false;
213   }
214 
215   ShenandoahHeap* heap = ShenandoahHeap::heap();
216   ShenandoahOldGeneration *old = heap->old_generation();
217   ShenandoahYoungGeneration *young = heap->young_generation();
218   ShenandoahGeneration *global = heap->global_generation();
219   double old_time_s = old->reset_collection_time();
220   double young_time_s = young->reset_collection_time();
221   double global_time_s = global->reset_collection_time();
222 
223   const double transfer_threshold = 3.0;
224   double delta = young_time_s - old_time_s;
225 
226   log_info(gc)("Thread Usr+Sys YOUNG = %.3f, OLD = %.3f, GLOBAL = %.3f", young_time_s, old_time_s, global_time_s);
227 
228   if (abs(delta) <= transfer_threshold) {
229     log_info(gc, ergo)("Difference (%.3f) for thread utilization for each generation is under threshold (%.3f)", abs(delta), transfer_threshold);
230     return false;
231   }
232 
233   if (delta > 0) {
234     // young is busier than old, increase size of young to raise MMU
235     return transfer_capacity(old, young);
236   } else {
237     // old is busier than young, increase size of old to raise MMU
238     return transfer_capacity(young, old);
239   }
240 }
241 
242 bool ShenandoahGenerationSizer::transfer_capacity(ShenandoahGeneration* target) const {
243   ShenandoahHeapLocker locker(ShenandoahHeap::heap()->lock());
244   if (target->is_young()) {
245     return transfer_capacity(ShenandoahHeap::heap()->old_generation(), target);
246   } else {
247     assert(target->is_old(), "Expected old generation, if not young.");
248     return transfer_capacity(ShenandoahHeap::heap()->young_generation(), target);
249   }
250 }
251 
252 bool ShenandoahGenerationSizer::transfer_capacity(ShenandoahGeneration* from, ShenandoahGeneration* to) const {
253   shenandoah_assert_heaplocked_or_safepoint();
254 
255   size_t available_regions = from->free_unaffiliated_regions();
256   if (available_regions <= 0) {
257     log_info(gc)("%s has no regions available for transfer to %s", from->name(), to->name());
258     return false;
259   }
260 
261   size_t regions_to_transfer = MAX2(1u, uint(double(available_regions) * _resize_increment));
262   if (from->generation_mode() == YOUNG) {
263     regions_to_transfer = adjust_transfer_from_young(from, regions_to_transfer);
264   } else {
265     regions_to_transfer = adjust_transfer_to_young(to, regions_to_transfer);
266   }
267 
268   if (regions_to_transfer == 0) {
269     log_info(gc)("No capacity available to transfer from: %s (" SIZE_FORMAT "%s) to: %s (" SIZE_FORMAT "%s)",
270                   from->name(), byte_size_in_proper_unit(from->max_capacity()), proper_unit_for_byte_size(from->max_capacity()),
271                   to->name(), byte_size_in_proper_unit(to->max_capacity()), proper_unit_for_byte_size(to->max_capacity()));
272     return false;
273   }
274 
275   log_info(gc)("Transfer " SIZE_FORMAT " region(s) from %s to %s", regions_to_transfer, from->name(), to->name());
276   from->decrease_capacity(regions_to_transfer * ShenandoahHeapRegion::region_size_bytes());
277   to->increase_capacity(regions_to_transfer * ShenandoahHeapRegion::region_size_bytes());
278   return true;
279 }
280 
281 size_t ShenandoahGenerationSizer::adjust_transfer_from_young(ShenandoahGeneration* from, size_t regions_to_transfer) const {
282   assert(from->generation_mode() == YOUNG, "Expect to transfer from young");
283   size_t young_capacity_regions = from->max_capacity() / ShenandoahHeapRegion::region_size_bytes();
284   size_t new_young_regions = young_capacity_regions - regions_to_transfer;
285   size_t minimum_young_regions = min_young_regions();
286   // Check that we are not going to violate the minimum size constraint.
287   if (new_young_regions < minimum_young_regions) {
288     assert(minimum_young_regions <= young_capacity_regions, "Young is under minimum capacity.");
289     // If the transfer violates the minimum size and there is still some capacity to transfer,
290     // adjust the transfer to take the size to the minimum. Note that this may be zero.
291     regions_to_transfer = young_capacity_regions - minimum_young_regions;
292   }
293   return regions_to_transfer;
294 }
295 
296 size_t ShenandoahGenerationSizer::adjust_transfer_to_young(ShenandoahGeneration* to, size_t regions_to_transfer) const {
297   assert(to->generation_mode() == YOUNG, "Can only transfer between young and old.");
298   size_t young_capacity_regions = to->max_capacity() / ShenandoahHeapRegion::region_size_bytes();
299   size_t new_young_regions = young_capacity_regions + regions_to_transfer;
300   size_t maximum_young_regions = max_young_regions();
301   // Check that we are not going to violate the maximum size constraint.
302   if (new_young_regions > maximum_young_regions) {
303     assert(maximum_young_regions >= young_capacity_regions, "Young is over maximum capacity");
304     // If the transfer violates the maximum size and there is still some capacity to transfer,
305     // adjust the transfer to take the size to the maximum. Note that this may be zero.
306     regions_to_transfer = maximum_young_regions - young_capacity_regions;
307   }
308   return regions_to_transfer;
309 }
310 
311 size_t ShenandoahGenerationSizer::min_young_size() const {
312   return min_young_regions() * ShenandoahHeapRegion::region_size_bytes();
313 }
314 
315 size_t ShenandoahGenerationSizer::max_young_size() const {
316   return max_young_regions() * ShenandoahHeapRegion::region_size_bytes();
317 }