src/hotspot/share/jfr/recorder/service/jfrMemorySizer.cpp
changeset 50113 caf115bb98ad
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hotspot/share/jfr/recorder/service/jfrMemorySizer.cpp	Tue May 15 20:24:34 2018 +0200
@@ -0,0 +1,434 @@
+/*
+ * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * 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.
+ *
+ */
+
+#include "precompiled.hpp"
+#include "jfr/recorder/service/jfrMemorySizer.hpp"
+#include "logging/log.hpp"
+#include "runtime/os.hpp"
+
+const julong MAX_ADJUSTED_GLOBAL_BUFFER_SIZE = 1 * M;
+const julong MIN_ADJUSTED_GLOBAL_BUFFER_SIZE_CUTOFF = 512 * K;
+const julong MIN_GLOBAL_BUFFER_SIZE = 64 * K;
+// implies at least 2 * MIN_GLOBAL_BUFFER SIZE
+const julong MIN_BUFFER_COUNT = 2;
+// MAX global buffer count open ended
+const julong DEFAULT_BUFFER_COUNT = 20;
+// MAX thread local buffer size == size of a single global buffer (runtime determined)
+// DEFAULT thread local buffer size = 2 * os page size (runtime determined)
+const julong MIN_THREAD_BUFFER_SIZE = 4 * K;
+const julong MIN_MEMORY_SIZE = 1 * M;
+const julong DEFAULT_MEMORY_SIZE = 10 * M;
+
+//
+// In pages:
+//
+// units = total_pages / per_unit_pages
+//
+static julong div_pages(julong& total_pages, julong& per_unit_pages) {
+  assert(total_pages > 0, "invariant");
+  assert(per_unit_pages > 0, "invariant");
+  assert(total_pages >= per_unit_pages, "invariant");
+
+  const julong units = total_pages / per_unit_pages;
+  const julong rem = total_pages % per_unit_pages;
+
+  assert(units > 0, "invariant");
+
+  if (rem > 0) {
+    total_pages -= rem % units;
+    per_unit_pages += rem / units;
+  }
+
+  assert(per_unit_pages > 0, "invariant");
+  assert(total_pages % units == 0, "invariant");
+  assert(units * per_unit_pages == total_pages, "invariant");
+  assert(units == total_pages / per_unit_pages, "invariant");
+
+  return units;
+}
+
+static void page_size_align_up(julong& value) {
+  static const julong alignment = os::vm_page_size() - 1;
+  value = (value + alignment) & ~alignment;
+}
+
+//
+// In bytes:
+// units = total_bytes / per_unit_bytes
+//
+static julong div_total_by_per_unit(julong& total_bytes, julong& per_unit_bytes) {
+  assert(total_bytes > 0, "invariant");
+  assert(per_unit_bytes > 0, "invariant");
+  assert(total_bytes >= per_unit_bytes, "invariant");
+
+  page_size_align_up(total_bytes);
+  assert(total_bytes % os::vm_page_size() == 0, "invariant");
+  julong total_pages = total_bytes / os::vm_page_size();
+
+  page_size_align_up(per_unit_bytes);
+  assert(per_unit_bytes % os::vm_page_size() == 0, "invariant");
+  julong per_unit_pages = per_unit_bytes / os::vm_page_size();
+
+  const julong units = div_pages(total_pages, per_unit_pages);
+  assert(units > 0, "invariant");
+
+  total_bytes = total_pages * os::vm_page_size();
+  per_unit_bytes = per_unit_pages * os::vm_page_size();
+
+  assert(per_unit_bytes > 0, "invariant");
+  assert(total_bytes / per_unit_bytes == units, "invariant");
+
+  return units;
+}
+
+//
+// per_unit_bytes = total_bytes / units
+//
+static julong div_total_by_units(julong& total_bytes, julong& units) {
+  page_size_align_up(total_bytes);
+  assert(total_bytes % os::vm_page_size() == 0, "invariant");
+  julong total_pages = total_bytes / os::vm_page_size();
+  assert(units > 0, "invariant");
+
+  julong per_unit_pages = total_pages <= units ? 1 : total_pages / units;
+  units = div_pages(total_pages, per_unit_pages);
+
+  julong per_unit_bytes = per_unit_pages * os::vm_page_size();
+  assert(per_unit_bytes % os::vm_page_size() == 0, "invariant");
+
+  total_bytes = total_pages * os::vm_page_size();
+  assert(total_bytes % os::vm_page_size() == 0, "invariant");
+
+  assert(total_bytes % units == 0, "invariant");
+  assert(total_bytes / units == per_unit_bytes, "invariant");
+  assert(units * per_unit_bytes == total_bytes, "invariant");
+
+  return per_unit_bytes;
+}
+
+//
+// total_bytes = per_unit_bytes * units;
+//
+static julong multiply(julong& per_unit_bytes, julong& units) {
+  page_size_align_up(per_unit_bytes);
+  assert(per_unit_bytes % os::vm_page_size() == 0, "invariant");
+  assert(units > 0, "invariant");
+
+  julong total_bytes = per_unit_bytes * units;
+  assert(total_bytes % os::vm_page_size() == 0, "invariant");
+
+  assert(total_bytes % units == 0, "invariant");
+  assert(total_bytes / units == per_unit_bytes, "invariant");
+  assert(units * per_unit_bytes == total_bytes, "invariant");
+
+  return total_bytes;
+}
+
+// Total_bytes is explicitly set.
+//
+// Deduce other parameters by delegating to a sizing policy
+template <typename SizingPolicy>
+static julong adjust(JfrMemoryOptions* options) {
+  page_size_align_up(options->memory_size);
+  assert(options->memory_size % os::vm_page_size() == 0, "invariant");
+  julong total_pages = options->memory_size / os::vm_page_size();
+  assert(options->buffer_count > 0, "invariant");
+  julong per_unit_pages = total_pages / options->buffer_count;
+  page_size_align_up(options->thread_buffer_size);
+  assert(options->thread_buffer_size % os::vm_page_size() == 0, "invariant");
+  julong thread_buffer_pages = options->thread_buffer_size / os::vm_page_size();
+
+  SizingPolicy::adjust(total_pages, per_unit_pages, options->buffer_count, thread_buffer_pages, options->thread_buffer_size_configured);
+  assert(options->buffer_count * per_unit_pages == total_pages, "invariant");
+
+  const julong per_unit_bytes = per_unit_pages * os::vm_page_size();
+  options->memory_size = total_pages * os::vm_page_size();
+  options->thread_buffer_size = thread_buffer_pages * os::vm_page_size();
+
+  assert(options->memory_size % options->buffer_count == 0, "invariant");
+  assert(options->memory_size / options->buffer_count == per_unit_bytes, "invariant");
+  assert(options->buffer_count * per_unit_bytes == options->memory_size, "invariant");
+  assert(per_unit_bytes >= options->thread_buffer_size, "invariant");
+  return per_unit_bytes;
+}
+
+static void align_buffer_size(julong& buffer_size_in_pages, julong max_size_pages, julong min_size_pages, bool sizeup = false) {
+  buffer_size_in_pages = MIN2(buffer_size_in_pages, max_size_pages);
+  buffer_size_in_pages = MAX2(buffer_size_in_pages, min_size_pages);
+  size_t multiples = 0;
+  if (buffer_size_in_pages < max_size_pages) {
+    while (buffer_size_in_pages >=
+      (min_size_pages << (multiples + (sizeup ? 0 : 1)))) {
+      ++multiples;
+    }
+    buffer_size_in_pages = min_size_pages << multiples;
+  }
+  assert(buffer_size_in_pages >= min_size_pages && buffer_size_in_pages <= max_size_pages, "invariant");
+}
+
+static void adjust_buffer_size_to_total_memory_size(julong& total_pages, julong& buffer_size_pages) {
+  static const julong max_buffer_size_pages = MAX_ADJUSTED_GLOBAL_BUFFER_SIZE / os::vm_page_size();
+  // If memory size is less than DEFAULT_MEMORY_SIZE,
+  // the adjustment algorithm can decrease the size of the global buffer
+  // all the way down to the MIN_GLOBAL_BUFFER_SIZE (taking embedded use case in account).
+  // If memory size is larger than DEFAULT_MEMORY_SIZE, the lowest size of
+  // a global buffer will be the size of MIN_ADJUSTED_GLOBAL_BUFFER_SIZE_CUTOFF
+  static const julong min_buffer_size_pages =
+    total_pages * os::vm_page_size() < DEFAULT_MEMORY_SIZE ?
+      MIN_GLOBAL_BUFFER_SIZE / os::vm_page_size() :
+      MIN_ADJUSTED_GLOBAL_BUFFER_SIZE_CUTOFF / os::vm_page_size();
+
+  align_buffer_size(buffer_size_pages, max_buffer_size_pages, min_buffer_size_pages);
+  assert(buffer_size_pages % min_buffer_size_pages == 0, "invariant");
+
+  julong remainder = total_pages % buffer_size_pages;
+  while (remainder >= (buffer_size_pages >> 1)) {
+    if (buffer_size_pages <= min_buffer_size_pages) {
+      break;
+    }
+    buffer_size_pages >>= 1;
+    remainder = total_pages % buffer_size_pages;
+  }
+}
+
+// Sizing policy class
+class ScaleOutAdjuster : public AllStatic {
+ public:
+  static void adjust(julong& total_pages,
+                     julong& buffer_size_pages,
+                     julong& buffer_count,
+                     julong& thread_buffer_size_pages,
+                     bool is_thread_buffer_size_set) {
+    assert(buffer_count > 0, "invariant");
+    adjust_buffer_size_to_total_memory_size(total_pages, buffer_size_pages);
+    assert(buffer_size_pages * os::vm_page_size() >= MIN_GLOBAL_BUFFER_SIZE, "invariant");
+    assert((buffer_size_pages * os::vm_page_size()) % MIN_GLOBAL_BUFFER_SIZE == 0, "invariant");
+    if (is_thread_buffer_size_set) {
+      if (thread_buffer_size_pages > buffer_size_pages) {
+        buffer_size_pages = thread_buffer_size_pages;
+      }
+    }
+    // and with this information, calculate what the new buffer count will be
+    buffer_count = div_pages(total_pages, buffer_size_pages);
+  }
+};
+
+static void memory_and_thread_buffer_size(JfrMemoryOptions* options) {
+  assert(options->memory_size_configured, "invariant");
+  assert(!options->buffer_count_configured, "invariant");
+  assert(!options->global_buffer_size_configured, "invariant");
+  // here the only thing specified is the overall total memory size
+  // we can and will apply some sizing heuristics to derive both
+  // the size of an individual global buffer and by implication the number of global
+  // buffers to use. Starting values for buffer count and global_buffer_size
+  // will be the defaults.
+  options->global_buffer_size = adjust<ScaleOutAdjuster>(options);
+}
+
+static void memory_size_and_buffer_count(JfrMemoryOptions* options) {
+  assert(options->memory_size_configured, "invariant");
+  assert(!options->global_buffer_size_configured, "invariant");
+  assert(!options->thread_buffer_size_configured, "invariant");
+  assert(options->buffer_count_configured, "invariant");
+  options->global_buffer_size = div_total_by_units(options->memory_size, options->buffer_count);
+}
+
+static void memory_size_and_global_buffer_size(JfrMemoryOptions* options) {
+  assert(options->memory_size_configured, "invariant");
+  assert(options->global_buffer_size_configured, "invariant");
+  assert(!options->buffer_count_configured, "invariant");
+  page_size_align_up(options->thread_buffer_size);
+  options->buffer_count = div_total_by_per_unit(options->memory_size, options->global_buffer_size);
+  if (options->thread_buffer_size > options->global_buffer_size) {
+    options->global_buffer_size = options->thread_buffer_size;
+    options->buffer_count = div_total_by_per_unit(options->memory_size, options->global_buffer_size);
+  }
+  assert(options->global_buffer_size >= options->thread_buffer_size, "invariant");
+}
+
+static bool is_ambiguous(const JfrMemoryOptions* options) {
+  assert(options->memory_size_configured, "invariant");
+  assert(options->global_buffer_size_configured, "invariant");
+  assert(options->buffer_count_configured, "invariant");
+  assert(options->thread_buffer_size <= options->global_buffer_size, "invariant");
+  // This can cause an ambiguous situation because all three parameters are explicitly set.
+  return options->global_buffer_size * options->buffer_count != options->memory_size;
+}
+
+static void all_options_set(JfrMemoryOptions* options) {
+  options->buffer_count = div_total_by_per_unit(options->memory_size, options->global_buffer_size);
+  page_size_align_up(options->thread_buffer_size);
+  if (options->thread_buffer_size > options->global_buffer_size) {
+    options->global_buffer_size = options->thread_buffer_size;
+    options->buffer_count = div_total_by_per_unit(options->memory_size, options->global_buffer_size);
+  }
+  assert(options->global_buffer_size >= options->thread_buffer_size, "invariant");
+  assert(options->memory_size / options->global_buffer_size == options->buffer_count, "invariant");
+  assert(options->memory_size % options->global_buffer_size == 0, "invariant");
+}
+
+static void global_buffer_size(JfrMemoryOptions* options) {
+  assert(!options->memory_size_configured, "invariant");
+  page_size_align_up(options->thread_buffer_size);
+  if (options->thread_buffer_size > options->global_buffer_size) {
+    options->global_buffer_size = options->thread_buffer_size;
+  }
+  options->memory_size = multiply(options->global_buffer_size, options->buffer_count);
+  assert(options->global_buffer_size >= options->thread_buffer_size, "invariant");
+}
+
+static void thread_buffer_size(JfrMemoryOptions* options) {
+  assert(!options->global_buffer_size_configured, "invariant");
+  assert(options->thread_buffer_size_configured, "invariant");
+  page_size_align_up(options->thread_buffer_size);
+  options->global_buffer_size = div_total_by_units(options->memory_size, options->buffer_count);
+  if (options->thread_buffer_size > options->global_buffer_size) {
+    options->global_buffer_size = options->thread_buffer_size;
+    options->buffer_count = div_total_by_per_unit(options->memory_size, options->global_buffer_size);
+  }
+  assert(options->global_buffer_size >= options->thread_buffer_size, "invariant");
+}
+
+static void default_size(const JfrMemoryOptions* options) {
+  // no memory options explicitly set
+  // default values already statically adjusted
+  assert(!options->thread_buffer_size_configured, "invariant");
+  assert(!options->memory_size_configured, "invariant");
+  assert(!options->buffer_count_configured, "invarinat");
+  assert(!options->global_buffer_size_configured, "invariant");
+}
+
+#ifdef ASSERT
+static void assert_post_condition(const JfrMemoryOptions* options) {
+  assert(options->memory_size % os::vm_page_size() == 0, "invariant");
+  assert(options->global_buffer_size % os::vm_page_size() == 0, "invariant");
+  assert(options->thread_buffer_size % os::vm_page_size() == 0, "invariant");
+  assert(options->buffer_count > 0, "invariant");
+}
+#endif
+
+// MEMORY SIZING ALGORITHM
+
+bool JfrMemorySizer::adjust_options(JfrMemoryOptions* options) {
+  assert(options != NULL, "invariant");
+
+  enum MemoryOptions {
+    MEMORY_SIZE = 1,
+    GLOBAL_BUFFER_SIZE = 2,
+    GLOBAL_BUFFER_COUNT = 4,
+    THREAD_BUFFER_SIZE = 8
+  };
+
+  // LEGEND
+  //
+  // M = "memorysize" option
+  // G = "globalbuffersize" option
+  // C = "numglobalbuffers" option
+  // T = "threadbuffersize" option
+  //
+  // The memory options comprise an n-set (a 4-set) = { M, G, C, T }
+  //
+  // Number of r-subsets = 5 (0, 1, 2, 3, 4) (including null set)
+  //
+  // Unordered selection:
+  //
+  // C(4, 0) = {} = NULL set = 1
+  // C(4, 1) = { (M), (G), (C), (T) } = 4
+  // C(4, 2) = { (M, G), (M, C), (M, T), (G, C), (G, T), (C, T) } = 6
+  // C(4, 3) = { (M, G, C), (M, G, T), (M, C, T), (G, C, T) } = 4
+  // C(4, 4) = { (M, G, C, T) } = 1
+  //
+  // in shorter terms: P({ M, G, C, T}) = 16
+  //
+#define MG   (MEMORY_SIZE | GLOBAL_BUFFER_SIZE)
+#define MC   (MEMORY_SIZE | GLOBAL_BUFFER_COUNT)
+#define MT   (MEMORY_SIZE | THREAD_BUFFER_SIZE)
+#define MGC  (MG | GLOBAL_BUFFER_COUNT)
+#define MGT  (MG | THREAD_BUFFER_SIZE)
+#define MCT  (MC | THREAD_BUFFER_SIZE)
+#define MGCT (MGC | THREAD_BUFFER_SIZE)
+#define GC   (GLOBAL_BUFFER_SIZE | GLOBAL_BUFFER_COUNT)
+#define GT   (GLOBAL_BUFFER_SIZE | THREAD_BUFFER_SIZE)
+#define GCT  (GC | THREAD_BUFFER_SIZE)
+#define CT   (GLOBAL_BUFFER_COUNT | THREAD_BUFFER_SIZE)
+
+  int set_of_options = 0;
+
+  if (options->memory_size_configured) {
+    set_of_options |= MEMORY_SIZE;
+  }
+  if (options->global_buffer_size_configured) {
+    set_of_options |= GLOBAL_BUFFER_SIZE;
+  }
+  if (options->buffer_count_configured) {
+    set_of_options |= GLOBAL_BUFFER_COUNT;
+  }
+  if (options->thread_buffer_size_configured) {
+    set_of_options |= THREAD_BUFFER_SIZE;
+  }
+
+  switch (set_of_options) {
+    case MT:
+    case MEMORY_SIZE:
+      memory_and_thread_buffer_size(options);
+      break;
+    case MC:
+      memory_size_and_buffer_count(options);
+      break;
+    case MGT:
+      assert(options->thread_buffer_size_configured, "invariant");
+    case MG:
+      memory_size_and_global_buffer_size(options);
+      break;
+    case MGC:
+    case MGCT:
+      if (is_ambiguous(options)) {
+        // Let the user resolve the ambiguity by bailing.
+        return false;
+      }
+      all_options_set(options);
+      break;
+    case GCT:
+      assert(options->buffer_count_configured, "invariant");
+      assert(options->thread_buffer_size_configured, "invariant");
+    case GC:
+      assert(options->global_buffer_size_configured, "invariant");
+    case GT:
+    case GLOBAL_BUFFER_COUNT:
+    case GLOBAL_BUFFER_SIZE:
+      global_buffer_size(options);
+      break;
+    case MCT:
+      assert(options->memory_size_configured, "invariant");
+    case CT:
+      assert(options->buffer_count_configured, "invariant");
+    case THREAD_BUFFER_SIZE:
+      thread_buffer_size(options);
+      break;
+    default:
+      default_size(options);
+  }
+  DEBUG_ONLY(assert_post_condition(options);)
+  return true;
+}