blob: b7735cc26c3924059206f70d48f297da0988d262 [file] [log] [blame]
/*
* 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;
}