blob: 4f96bb582f29e5bffe972dad8ef2250a7c0eb2af [file] [log] [blame]
/*
* Copyright (c) 2023 SAP SE. All rights reserved.
* Copyright (c) 2023, 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 "memory/allocation.hpp"
#include "runtime/java.hpp"
#include "runtime/globals.hpp"
#include "services/mallocLimit.hpp"
#include "services/nmtCommon.hpp"
#include "utilities/globalDefinitions.hpp"
#include "utilities/parseInteger.hpp"
#include "utilities/ostream.hpp"
MallocLimitSet MallocLimitHandler::_limits;
bool MallocLimitHandler::_have_limit = false;
static const char* const MODE_OOM = "oom";
static const char* const MODE_FATAL = "fatal";
static const char* mode_to_name(MallocLimitMode m) {
switch (m) {
case MallocLimitMode::trigger_fatal: return MODE_FATAL;
case MallocLimitMode::trigger_oom: return MODE_OOM;
default: ShouldNotReachHere();
};
return nullptr;
}
class ParserHelper {
// Start, end of parsed string.
const char* const _s;
const char* const _end;
// Current parse position.
const char* _p;
public:
ParserHelper(const char* s) : _s(s), _end(s + strlen(s)), _p(s) {}
bool eof() const { return _p >= _end; }
// Check if string at position matches a malloclimit_mode_t.
// Advance position on match.
bool match_mode_flag(MallocLimitMode* out) {
if (eof()) {
return false;
}
if (strncasecmp(_p, MODE_OOM, strlen(MODE_OOM)) == 0) {
*out = MallocLimitMode::trigger_oom;
_p += 3;
return true;
} else if (strncasecmp(_p, MODE_FATAL, strlen(MODE_FATAL)) == 0) {
*out = MallocLimitMode::trigger_fatal;
_p += 5;
return true;
}
return false;
}
// Check if string at position matches a category name.
// Advances position on match.
bool match_category(MEMFLAGS* out) {
if (eof()) {
return false;
}
const char* end = strchr(_p, ':');
if (end == nullptr) {
end = _end;
}
stringStream ss;
ss.print("%.*s", (int)(end - _p), _p);
MEMFLAGS f = NMTUtil::string_to_flag(ss.base());
if (f != mtNone) {
*out = f;
_p = end;
return true;
}
return false;
}
// Check if string at position matches a memory size (e.g. "100", "100g" etc).
// Advances position on match.
bool match_size(size_t* out) {
if (!eof()) {
char* remainder = nullptr;
if (parse_integer<size_t>(_p, &remainder, out)) {
assert(remainder > _p && remainder <= _end, "sanity");
_p = remainder;
return true;
}
}
return false;
}
// Check if char at pos matches c; return true and advance pos if so.
bool match_char(char c) {
if (!eof() && (*_p) == c) {
_p ++;
return true;
}
return false;
}
};
MallocLimitSet::MallocLimitSet() {
reset();
}
void MallocLimitSet::set_global_limit(size_t s, MallocLimitMode flag) {
_glob.sz = s; _glob.mode = flag;
}
void MallocLimitSet::set_category_limit(MEMFLAGS f, size_t s, MallocLimitMode flag) {
const int i = NMTUtil::flag_to_index(f);
_cat[i].sz = s; _cat[i].mode = flag;
}
void MallocLimitSet::reset() {
set_global_limit(0, MallocLimitMode::trigger_fatal);
_glob.sz = 0; _glob.mode = MallocLimitMode::trigger_fatal;
for (int i = 0; i < mt_number_of_types; i++) {
set_category_limit(NMTUtil::index_to_flag(i), 0, MallocLimitMode::trigger_fatal);
}
}
void MallocLimitSet::print_on(outputStream* st) const {
static const char* flagnames[] = { MODE_FATAL, MODE_OOM };
if (_glob.sz > 0) {
st->print_cr("MallocLimit: total limit: " PROPERFMT " (%s)", PROPERFMTARGS(_glob.sz),
mode_to_name(_glob.mode));
} else {
for (int i = 0; i < mt_number_of_types; i++) {
if (_cat[i].sz > 0) {
st->print_cr("MallocLimit: category \"%s\" limit: " PROPERFMT " (%s)",
NMTUtil::flag_to_enum_name(NMTUtil::index_to_flag(i)),
PROPERFMTARGS(_cat[i].sz), mode_to_name(_cat[i].mode));
}
}
}
}
bool MallocLimitSet::parse_malloclimit_option(const char* v, const char** err) {
#define BAIL_UNLESS(condition, errormessage) if (!(condition)) { *err = errormessage; return false; }
// Global form:
// MallocLimit=<size>[:flag]
// Category-specific form:
// MallocLimit=<category>:<size>[:flag][,<category>:<size>[:flag]...]
reset();
ParserHelper sst(v);
BAIL_UNLESS(!sst.eof(), "Empty string");
// Global form?
if (sst.match_size(&_glob.sz)) {
// Match optional mode flag (e.g. 1g:oom)
if (!sst.eof()) {
BAIL_UNLESS(sst.match_char(':'), "Expected colon");
BAIL_UNLESS(sst.match_mode_flag(&_glob.mode), "Expected flag");
}
}
// Category-specific form?
else {
while (!sst.eof()) {
MEMFLAGS f;
// Match category, followed by :
BAIL_UNLESS(sst.match_category(&f), "Expected category name");
BAIL_UNLESS(sst.match_char(':'), "Expected colon following category");
malloclimit* const modified_limit = &_cat[NMTUtil::flag_to_index(f)];
// Match size
BAIL_UNLESS(sst.match_size(&modified_limit->sz), "Expected size");
// Match optional flag
if (!sst.eof() && sst.match_char(':')) {
BAIL_UNLESS(sst.match_mode_flag(&modified_limit->mode), "Expected flag");
}
// More to come?
if (!sst.eof()) {
BAIL_UNLESS(sst.match_char(','), "Expected comma");
}
}
}
return true;
}
void MallocLimitHandler::initialize(const char* options) {
_have_limit = false;
if (options != nullptr && options[0] != '\0') {
const char* err = nullptr;
if (!_limits.parse_malloclimit_option(options, &err)) {
vm_exit_during_initialization("Failed to parse MallocLimit", err);
}
_have_limit = true;
}
}
void MallocLimitHandler::print_on(outputStream* st) {
if (have_limit()) {
_limits.print_on(st);
} else {
st->print_cr("MallocLimit: unset");
}
}