diff --git a/src/hotspot/os/linux/globals_linux.hpp b/src/hotspot/os/linux/globals_linux.hpp index c0f4d8f3e95..b26b0140fb2 100644 --- a/src/hotspot/os/linux/globals_linux.hpp +++ b/src/hotspot/os/linux/globals_linux.hpp @@ -78,7 +78,15 @@ "be dumped into the corefile.") \ \ diagnostic(bool, UseCpuAllocPath, false, \ - "Use CPU_ALLOC code path in os::active_processor_count ") + "Use CPU_ALLOC code path in os::active_processor_count ") \ + \ + /* SapMachine 2021-09-01: malloc-trace */ \ + diagnostic(bool, EnableMallocTrace, false, \ + "Enable malloc trace at VM initialization") \ + \ + /* SapMachine 2021-09-01: malloc-trace */ \ + diagnostic(bool, PrintMallocTraceAtExit, false, \ + "Print Malloc Trace upon VM exit") \ // // Defines Linux-specific default values. The flags are available on all diff --git a/src/hotspot/os/linux/malloctrace/assertHandling.cpp b/src/hotspot/os/linux/malloctrace/assertHandling.cpp new file mode 100644 index 00000000000..40c27389e68 --- /dev/null +++ b/src/hotspot/os/linux/malloctrace/assertHandling.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2021 SAP SE. All rights reserved. + * Copyright (c) 2021, 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 "malloctrace/assertHandling.hpp" +#include "malloctrace/locker.hpp" +#include "malloctrace/mallocTrace.hpp" +#include "runtime/atomic.hpp" + +#ifdef __GLIBC__ + +namespace sap { + +static volatile bool g_asserting = false; + +bool prepare_assert() { + + // Ignore all but the first assert + if (Atomic::cmpxchg(true, &g_asserting, false) != false) { + ::printf("Ignoring secondary assert in malloc trace...\n"); + return false; + } + + // manually disable lock. + Locker::unlock(); + + // disable hooks (if this asserts too, + // the assert is just ignored, see above) + MallocTracer::disable(); + + return true; +} + +} // namespace sap + +#endif // #ifdef __GLIBC__ diff --git a/src/hotspot/os/linux/malloctrace/assertHandling.hpp b/src/hotspot/os/linux/malloctrace/assertHandling.hpp new file mode 100644 index 00000000000..183c3c0d8a0 --- /dev/null +++ b/src/hotspot/os/linux/malloctrace/assertHandling.hpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2021 SAP SE. All rights reserved. + * Copyright (c) 2021, 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. + * + */ + +#ifndef OS_LINUX_MALLOCTRACE_ASSERTHANDLING_HPP +#define OS_LINUX_MALLOCTRACE_ASSERTHANDLING_HPP + +#include "utilities/globalDefinitions.hpp" + +#ifdef __GLIBC__ + +namespace sap { + +// Asserts in the malloctrace code need a bit of extra attention. +// We must prevent the assert handler itself deadlocking. Therefore, +// before executing the assert, we: +// - must prevent recursive assert from the malloc tracer +// - manually disable the lock to prevent recursive locking (since error reporting +// never rolls back the stack this is okay) +// - disable malloc hooks + +#ifdef ASSERT + +bool prepare_assert(); + +#define malloctrace_assert(cond, ...) \ +do { \ + if (!(cond) && prepare_assert()) { \ + report_vm_error(__FILE__, __LINE__, "malloctrace_assert(" #cond ") failed", __VA_ARGS__); \ + } \ +} while (0) +#else +#define malloctrace_assert(cond, ...) +#endif + +} // namespace sap + +#endif // __GLIBC__ + +#endif // OS_LINUX_MALLOCTRACE_ASSERTHANDLING_HPP diff --git a/src/hotspot/os/linux/malloctrace/locker.cpp b/src/hotspot/os/linux/malloctrace/locker.cpp new file mode 100644 index 00000000000..fe54315e00d --- /dev/null +++ b/src/hotspot/os/linux/malloctrace/locker.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 SAP SE. All rights reserved. + * Copyright (c) 2021, 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 "malloctrace/locker.hpp" +#include + +#ifdef __GLIBC__ + +namespace sap { + +//static +pthread_mutex_t Locker::_pthread_mutex = PTHREAD_MUTEX_INITIALIZER; + +} // namespace sap + +#endif // #ifdef __GLIBC__ diff --git a/src/hotspot/os/linux/malloctrace/locker.hpp b/src/hotspot/os/linux/malloctrace/locker.hpp new file mode 100644 index 00000000000..66e41e44368 --- /dev/null +++ b/src/hotspot/os/linux/malloctrace/locker.hpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2021 SAP SE. All rights reserved. + * Copyright (c) 2021, 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. + * + */ + +#ifndef OS_LINUX_MALLOCTRACE_LOCKER_HPP +#define OS_LINUX_MALLOCTRACE_LOCKER_HPP + +#include "malloctrace/assertHandling.hpp" +#include "utilities/globalDefinitions.hpp" +#include + +#ifdef __GLIBC__ + +class outputStream; + +namespace sap { + +/////// A simple native lock using pthread mutexes + +class Locker { + static pthread_mutex_t _pthread_mutex; + bool _locked; + + bool lock() { + malloctrace_assert(!_locked, "already locked"); + if (::pthread_mutex_lock(&_pthread_mutex) != 0) { + malloctrace_assert(false, "MALLOCTRACE lock failed"); + return false; + } + return true; + } + +public: + + // Manually unlock is public since we need it in case of asserts + // (see malloctrace_assert) + static void unlock() { + ::pthread_mutex_unlock(&_pthread_mutex); + } + + Locker() : _locked(false) { + _locked = lock(); + } + + ~Locker() { + if (_locked) { + unlock(); + } + } + +}; + +} // namespace sap + +#endif // __GLIBC__ + +#endif // OS_LINUX_MALLOCTRACE_LOCKER_HPP diff --git a/src/hotspot/os/linux/malloctrace/mallocTrace.cpp b/src/hotspot/os/linux/malloctrace/mallocTrace.cpp new file mode 100644 index 00000000000..2ad37458bd4 --- /dev/null +++ b/src/hotspot/os/linux/malloctrace/mallocTrace.cpp @@ -0,0 +1,378 @@ +/* + * Copyright (c) 2021 SAP SE. All rights reserved. + * Copyright (c) 2021, 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 "jvm.h" +#include "malloctrace/assertHandling.hpp" +#include "malloctrace/locker.hpp" +#include "malloctrace/mallocTrace.hpp" +#include "malloctrace/siteTable.hpp" +#include "memory/allocation.hpp" +#include "runtime/globals.hpp" +#include "utilities/globalDefinitions.hpp" +#include "utilities/ostream.hpp" + +#include + +#ifdef __GLIBC__ + +namespace sap { + +// Needed to stop the gcc from complaining about malloc hooks being deprecated. +PRAGMA_DISABLE_GCC_WARNING("-Wdeprecated-declarations") + +typedef void* (*malloc_hook_fun_t) (size_t len, const void* caller); +typedef void* (*realloc_hook_fun_t) (void* old, size_t len, const void* caller); +typedef void* (*memalign_hook_fun_t) (size_t alignment, size_t size, const void* caller); + +static void* my_malloc_hook(size_t size, const void *caller); +static void* my_realloc_hook(void* old, size_t size, const void *caller); +static void* my_memalign_hook(size_t alignment, size_t size, const void *caller); + +// Hook changes, hook ownership: +// +// Hooks are a global resource and everyone can change them concurrently. In practice +// this does not happen often, so using them for our purposes here is generally safe +// and we can generally rely on us being the sole changer of hooks. +// +// Exceptions: +// 1) gdb debugging facilities like mtrace() or MALLOC_CHECK_ use them too +// 2) there is a initialization race: both hooks are initially set to glibc-internal +// initialization functions which will do some stuff, them set them to NULL for the +// rest of the program run. These init functions (malloc_hook_ini() and realloc_hook_ini()), +// see malloc/hooks.c) run *lazily*, the first time malloc or realloc is called. +// So there is a race window here where we could possibly install our hooks while +// some other thread calls realloc, still sees the original function pointer, executed +// the init function and resets our hook. To make matters worse and more surprising, the +// realloc hook function also resets the malloc hook for some reason (I consider this a +// bug since realloc(3) may run way later than malloc(3)). +// +// There is nothing we can do about (1) except, well, not do it. About (2), we can effectively +// prevent that from happening by calling malloc and realloc very early. The earliest we +// can manage is during C++ dyn init of the libjvm: +struct RunAtDynInit { + RunAtDynInit() { + // Call malloc, realloc, free, calloc and posix_memalign. + // This may be overkill, but I want all hooks to have executed once, in case + // they have side effects on the other hooks (like the realloc hook which resets the malloc + // hook) + void* p = ::malloc(10); + p = ::realloc(p, 20); + ::free(p); + if (::posix_memalign(&p, 8, 10) == 0) { + ::free(p); + } + } +}; +static RunAtDynInit g_run_at_dyn_init; + +class HookControl : public AllStatic { + static bool _hooks_are_active; + static malloc_hook_fun_t _old_malloc_hook; + static realloc_hook_fun_t _old_realloc_hook; + static memalign_hook_fun_t _old_memalign_hook; + +public: + +#ifdef ASSERT + static char* print_hooks(char* out, size_t outlen) { + jio_snprintf(out, outlen, "__malloc_hook=" PTR_FORMAT ", __realloc_hook=" PTR_FORMAT ", __memalign_hook=" PTR_FORMAT ", " + "my_malloc_hook=" PTR_FORMAT ", my_realloc_hook=" PTR_FORMAT ", my_memalign_hook=" PTR_FORMAT ".", + (intptr_t)__malloc_hook, (intptr_t)__realloc_hook, (intptr_t)__memalign_hook, + (intptr_t)my_malloc_hook, (intptr_t)my_realloc_hook, (intptr_t)my_memalign_hook); + return out; + } + static void verify() { + char tmp[256]; + if (_hooks_are_active) { + malloctrace_assert(__malloc_hook == my_malloc_hook && __realloc_hook == my_realloc_hook && + __memalign_hook == my_memalign_hook, + "Hook mismatch (expected my hooks to be active). Hook state: %s", + print_hooks(tmp, sizeof(tmp))); + } else { + malloctrace_assert(__malloc_hook != my_malloc_hook && __realloc_hook != my_realloc_hook && + __memalign_hook != my_memalign_hook, + "Hook mismatch (expected default hooks to be active). Hook state: %s", + print_hooks(tmp, sizeof(tmp))); + } + } +#endif + + // Return true if my hooks are active + static bool hooks_are_active() { + DEBUG_ONLY(verify();) + return _hooks_are_active; + } + + static void enable() { + DEBUG_ONLY(verify();) + malloctrace_assert(!hooks_are_active(), "Sanity"); + _old_malloc_hook = __malloc_hook; + __malloc_hook = my_malloc_hook; + _old_realloc_hook = __realloc_hook; + __realloc_hook = my_realloc_hook; + _old_memalign_hook = __memalign_hook; + __memalign_hook = my_memalign_hook; + _hooks_are_active = true; + } + + static void disable() { + DEBUG_ONLY(verify();) + malloctrace_assert(hooks_are_active(), "Sanity"); + __malloc_hook = _old_malloc_hook; + __realloc_hook = _old_realloc_hook; + __memalign_hook = _old_memalign_hook; + _hooks_are_active = false; + } +}; + +bool HookControl::_hooks_are_active = false; +malloc_hook_fun_t HookControl::_old_malloc_hook = NULL; +realloc_hook_fun_t HookControl::_old_realloc_hook = NULL; +memalign_hook_fun_t HookControl::_old_memalign_hook = NULL; + +// A stack mark for temporarily disabling hooks - if they are active - and +// restoring the old state +class DisableHookMark { + const bool _state; +public: + DisableHookMark() : _state(HookControl::hooks_are_active()) { + if (_state) { + HookControl::disable(); + } + } + ~DisableHookMark() { + if (_state) { + HookControl::enable(); + } + } +}; + +///////////////////////////////////////////////////////////////// + +static SiteTable* g_sites = NULL; + +static bool g_use_backtrace = true; +static uint64_t g_num_captures = 0; +static uint64_t g_num_captures_without_stack = 0; + +#ifdef ASSERT +static int g_times_enabled = 0; +static int g_times_printed = 0; +#endif + +#define CAPTURE_STACK_AND_ADD_TO_SITE_TABLE \ +{ \ + Stack stack; \ + if (Stack::capture_stack(&stack, g_use_backtrace)) { \ + malloctrace_assert(g_sites != NULL, "Site table not allocated"); \ + g_sites->add_site(&stack, alloc_size); \ + } else { \ + g_num_captures_without_stack ++; \ + } \ +} + +static void* my_malloc_or_realloc_hook(void* old, size_t alloc_size) { + Locker lck; + g_num_captures ++; + + // If someone switched off tracing while we waited for the lock, just quietly do + // malloc/realloc and tippytoe out of this function. Don't modify hooks, don't + // collect stacks. + if (HookControl::hooks_are_active() == false) { + return old != NULL ? ::realloc(old, alloc_size) : ::malloc(alloc_size); + } + + // From here on disable hooks. We will collect a stack, then register it with + // the site table, then call the real malloc to satisfy the allocation for the + // caller. All of these things may internally malloc (even the sitemap, which may + // assert). These recursive mallocs should not end up in this hook otherwise we + // deadlock. + // + // Concurrency note: Concurrent threads will not be disturbed by this since: + // - either they already entered this function, in which case they wait at the lock + // - or they call malloc/realloc after we restored the hooks. In that case they + // just will end up doing the original malloc. We loose them for the statistic, + // but we wont disturb them, nor they us. + // (caveat: we assume here that the order in which we restore the hooks - which + // will appear random for outside threads - does not matter. After studying the + // glibc sources, I believe it does not.) + HookControl::disable(); + + CAPTURE_STACK_AND_ADD_TO_SITE_TABLE + + // Now do the actual allocation for the caller + void* p = old != NULL ? ::realloc(old, alloc_size) : ::malloc(alloc_size); + +#ifdef ASSERT + if ((g_num_captures % 10000) == 0) { // expensive, do this only sometimes + g_sites->verify(); + } +#endif + + // Reinstate my hooks + HookControl::enable(); + + return p; +} + +static void* my_malloc_hook(size_t size, const void *caller) { + return my_malloc_or_realloc_hook(NULL, size); +} + +static void* my_realloc_hook(void* old, size_t size, const void *caller) { + // realloc(0): "If size was equal to 0, either NULL or a pointer suitable to be passed to free() is returned." + // The glibc currently does the former (unlike malloc(0), which does the latter and can cause leaks). As long + // as we are sure the glibc returns NULL for realloc(0), we can shortcut here. + if (size == 0) { + return NULL; + } + return my_malloc_or_realloc_hook(old, size); +} + +static void* posix_memalign_wrapper(size_t alignment, size_t size) { + void* p = NULL; + if (::posix_memalign(&p, alignment, size) == 0) { + return p; + } + return NULL; +} + +static void* my_memalign_hook(size_t alignment, size_t alloc_size, const void *caller) { + Locker lck; + g_num_captures ++; + + // For explanations, see my_malloc_or_realloc_hook + + if (HookControl::hooks_are_active() == false) { + return posix_memalign_wrapper(alignment, alloc_size); + } + + HookControl::disable(); + + CAPTURE_STACK_AND_ADD_TO_SITE_TABLE + + // Now do the actual allocation for the caller + void* p = posix_memalign_wrapper(alignment, alloc_size); + +#ifdef ASSERT + if ((g_num_captures % 10000) == 0) { // expensive, do this only sometimes + g_sites->verify(); + } +#endif + + // Reinstate my hooks + HookControl::enable(); + + return p; +} + + +/////////// Externals ///////////////////////// + +bool MallocTracer::enable(bool use_backtrace) { + Locker lck; + if (!HookControl::hooks_are_active()) { + if (g_sites == NULL) { + // First time malloc trace is enabled, allocate the site table. We don't want to preallocate it + // unconditionally since it costs several MB. + g_sites = SiteTable::create(); + if (g_sites == NULL) { + return false; + } + } + HookControl::enable(); // << from this moment on concurrent threads may enter our hooks but will then wait on the lock + g_use_backtrace = use_backtrace; + DEBUG_ONLY(g_times_enabled ++;) + } + return true; +} + +void MallocTracer::disable() { + Locker lck; + if (HookControl::hooks_are_active()) { + HookControl::disable(); + } +} + +void MallocTracer::reset() { + Locker lck; + if (g_sites != NULL) { + g_sites->reset(); + g_num_captures = g_num_captures_without_stack = 0; + } +} + +void MallocTracer::reset_deltas() { + Locker lck; + if (g_sites != NULL) { + g_sites->reset_deltas(); + } +} + +void MallocTracer::print(outputStream* st, bool all) { + Locker lck; + if (g_sites != NULL) { + bool state_now = HookControl::hooks_are_active(); // query hooks before temporarily disabling them + { + DisableHookMark disableHookMark; + g_sites->print_table(st, all); + g_sites->print_stats(st); + st->cr(); + st->print_cr("Malloc trace %s.", state_now ? "on" : "off"); + if (state_now) { + st->print_cr(" (method: %s)", g_use_backtrace ? "backtrace" : "nmt-ish"); + } + st->cr(); + st->print_cr(UINT64_FORMAT " captures (" UINT64_FORMAT " without stack).", g_num_captures, g_num_captures_without_stack); + DEBUG_ONLY(g_times_printed ++;) + DEBUG_ONLY(st->print_cr("%d times enabled, %d times printed", g_times_enabled, g_times_printed)); + DEBUG_ONLY(g_sites->verify();) + // After each print, we reset table deltas + g_sites->reset_deltas(); + } + } else { + // Malloc trace has never been activated. + st->print_cr("Malloc trace off."); + } +} + +void MallocTracer::print_on_error(outputStream* st) { + // Don't lock. Don't change hooks. Just print the table stats. + if (g_sites != NULL) { + g_sites->print_stats(st); + } +} + +/////////////////////// + +// test: enable at libjvm load +// struct AutoOn { AutoOn() { MallocTracer::enable(); } }; +// static AutoOn g_autoon; + +#endif // GLIBC + +} // namespace sap diff --git a/src/hotspot/os/linux/malloctrace/mallocTrace.hpp b/src/hotspot/os/linux/malloctrace/mallocTrace.hpp new file mode 100644 index 00000000000..773677669da --- /dev/null +++ b/src/hotspot/os/linux/malloctrace/mallocTrace.hpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2021 SAP SE. All rights reserved. + * Copyright (c) 2021, 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. + * + */ + +#ifndef OS_LINUX_MALLOCTRACE_MALLOCTRACE_HPP +#define OS_LINUX_MALLOCTRACE_MALLOCTRACE_HPP + +#include "memory/allocation.hpp" +#include "utilities/globalDefinitions.hpp" + +#ifdef __GLIBC__ + +class outputStream; + +namespace sap { + +class MallocTracer : public AllStatic { +public: + static bool enable(bool use_backtrace = false); + static void disable(); + static void reset(); + static void reset_deltas(); + static void print(outputStream* st, bool all); + static void print_on_error(outputStream* st); +}; + +} + +#endif // __GLIBC__ + +#endif // OS_LINUX_MALLOCTRACE_MALLOCTRACE_HPP diff --git a/src/hotspot/os/linux/malloctrace/mallocTraceDCmd.cpp b/src/hotspot/os/linux/malloctrace/mallocTraceDCmd.cpp new file mode 100644 index 00000000000..0699ef53034 --- /dev/null +++ b/src/hotspot/os/linux/malloctrace/mallocTraceDCmd.cpp @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2021 SAP SE. All rights reserved. + * Copyright (c) 2021, 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 "malloctrace/mallocTrace.hpp" +#include "malloctrace/mallocTraceDCmd.hpp" +#include "memory/resourceArea.hpp" +#include "utilities/globalDefinitions.hpp" + +#include + +namespace sap { + +#ifdef __GLIBC__ + +// By default, lets use nmt-like capturing. I see (very rarely) crashes with backtrace(3) +// on x86. backtrace(3) gives us better callstack but runs a (small) risk of crashing, especially +// on x86. +const bool use_backtrace_default = false; + +void MallocTraceDCmd::execute(DCmdSource source, TRAPS) { + const char* const subopt = _suboption.value(); + if (::strcmp(_option.value(), "on") == 0) { + bool use_backtrace = use_backtrace_default; + if (subopt != NULL) { + if (::strcmp(subopt, "bt") == 0) { + use_backtrace = true; + } else if (::strcmp(subopt, "nmt") == 0) { + use_backtrace = false; + } else { + _output->print_cr("Invalid sub option"); + return; + } + } + if (MallocTracer::enable(use_backtrace)) { + _output->print_raw("Tracing active"); + } else { + _output->print("Failed to activate"); + } + } else if (::strcmp(_option.value(), "off") == 0) { + MallocTracer::disable(); + _output->print_raw("Tracing inactive"); + } else if (::strcmp(_option.value(), "print") == 0) { + bool all = false; + if (subopt != NULL) { + if (::strcmp(subopt, "all") == 0) { + all = true; + } else { + _output->print_cr("Invalid sub option"); + return; + } + } + MallocTracer::print(_output, all); + } else if (::strcmp(_option.value(), "reset") == 0) { + MallocTracer::reset(); + _output->print_raw("Tracing table reset"); + } else { + _output->print_cr("unknown sub command %s", _option.value()); + } + _output->cr(); +} +#else +void MallocTraceDCmd::execute(DCmdSource source, TRAPS) { + _output->print_cr("Not a glibc system."); +} +#endif // __GLIBC__ + +static const char* const usage_for_option = + "Valid Values:\n" + " - on [bt|nmt]\n" + " Switches trace on. Optional second parameter overrides the stack walk method.\n" + " - nmt (default): uses internal stackwalking.\n" + " - bt: uses glibc stackwalking (may give better results, but can be unstable).\n" + " - off\n" + " Switches trace off.\n" + " - print [all]\n" + " Print the capture table. By default only hot sites are printed; specifying \"all\" will print the full table.\n" + " - reset\n" + " Resets the capture table.\n"; + +MallocTraceDCmd::MallocTraceDCmd(outputStream* output, bool heap) : + DCmdWithParser(output, heap), + _option("option", usage_for_option, "STRING", true), + _suboption("suboption", "see option", "STRING", false) +{ + _dcmdparser.add_dcmd_argument(&_option); + _dcmdparser.add_dcmd_argument(&_suboption); +} + +int MallocTraceDCmd::num_arguments() { + ResourceMark rm; + MallocTraceDCmd* dcmd = new MallocTraceDCmd(NULL, false); + if (dcmd != NULL) { + DCmdMark mark(dcmd); + return dcmd->_dcmdparser.num_arguments(); + } else { + return 0; + } +} + +} // namespace sap diff --git a/src/hotspot/os/linux/malloctrace/mallocTraceDCmd.hpp b/src/hotspot/os/linux/malloctrace/mallocTraceDCmd.hpp new file mode 100644 index 00000000000..315f7b65e27 --- /dev/null +++ b/src/hotspot/os/linux/malloctrace/mallocTraceDCmd.hpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2021 SAP SE. All rights reserved. + * Copyright (c) 2021, 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. + * + */ + +#ifndef OS_LINUX_MALLOCTRACE_MALLOCTRACEDCMD_HPP +#define OS_LINUX_MALLOCTRACE_MALLOCTRACEDCMD_HPP + +#include "services/diagnosticCommand.hpp" +#include "utilities/globalDefinitions.hpp" + +class outputStream; + +namespace sap { + +class MallocTraceDCmd : public DCmdWithParser { + DCmdArgument _option; + DCmdArgument _suboption; +public: + MallocTraceDCmd(outputStream* output, bool heap); + static const char* name() { + return "System.malloctrace"; + } + static int num_arguments(); + static const char* description() { + return "Trace malloc call sites\n" + "Note: do *not* use in conjunction with MALLOC_CHECK_..!"; + } + static const char* impact() { + return "Low"; + } + static const JavaPermission permission() { + JavaPermission p = { "java.lang.management.ManagementPermission", "control", NULL }; + return p; + } + virtual void execute(DCmdSource source, TRAPS); +}; + +} + +#endif // OS_LINUX_MALLOCTRACE_MALLOCTRACEDCMD_HPP diff --git a/src/hotspot/os/linux/malloctrace/malloc_trace.md b/src/hotspot/os/linux/malloctrace/malloc_trace.md new file mode 100644 index 00000000000..9acc37c8d14 --- /dev/null +++ b/src/hotspot/os/linux/malloctrace/malloc_trace.md @@ -0,0 +1,140 @@ +(c) SAP 2021 + +# The SapMachine MallocTrace facility + +## Preface + +When analyzing native OOMs resulting from C-heap exhaustion, we have two facilities built into the VM: + +- NMT (available in OpenJDK, SapMachine, and SAP JVM) +- the SAP JVM malloc statistics (as the name indicates, SAP JVM only) + +Both facilities have pros and cons, but they share one significant disadvantage: they require the malloc sites to be instrumented at compile time. Therefore they are unsuited for analyzing situations where outside code within the VM process allocates C-heap. + +In the past, if we were facing a suspected C-heap leak from code outside the VM (outside the coverage of NMT or SAP JVM malloc statistics), we were helpless. In these situations, one would typically use tools like perf or Valgrind, but that is seldom an option for us. + +### mtrace? + +A straightforward possibility to do this would be the Glibc-internal trace. Using `mtrace(3)`, one can force the Glibc to write a trace file for all malloc calls. Unfortunately, it is very costly. In my experiments, VM slowed down by factor 8-10. It also has to be enabled at the start of the VM (when the VM still happens to be single-threaded). That usually rules it out in production scenarios. + +## The SapMachine MallocTrace facility + +The new Linux-only malloc trace facility in the SapMachine uses Glibc malloc hooks to hook into the allocation process. In that way, it resembles the Glibc-internal `mtrace(3)`. But unlike `mtrace(3)`, it accumulates data in memory and provides a condensed report upon request, making it a lot faster. Moreover, it does not have to be started at VM startup (though it can), but one can switch it on when needed. + +### Usage via jcmd + +#### Switch trace on: + +``` +thomas@starfish$ jcmd AllocCHeap System.malloctrace on +268112: +Tracing activated +``` + +#### Switch trace off: +``` +thomas@starfish$ jcmd AllocCHeap System.malloctrace off +268112: +Tracing deactivated +``` + +#### Print a SapMachine MallocTrace report: + +Two options exist: +- a full report which can be lengthy but will show all call sites. +- (default) an abridged report which only shows the ten "hottest" call sites. + +``` +jcmd (VM) System.malloctrace print [all] +``` + +Example: + +``` +thomas@starfish$ jcmd AllocCHeap System.malloctrace print +268112: +---- 10 hottest malloc sites: ---- +---- 0 ---- +Invocs: 2813 (+0) +Alloc Size Range: 8 - 312 +[0x00007fd04159f3d0] sap::my_malloc_hook(unsigned long, void const*)+192 in libjvm.so +[0x00007fd040e0a004] AllocateHeap(unsigned long, MEMFLAGS, AllocFailStrategy::AllocFailEnum)+68 in libjvm.so +[0x00007fd041891eb2] SymbolTable::allocate_symbol(char const*, int, bool)+226 in libjvm.so +[0x00007fd041895c94] SymbolTable::do_add_if_needed(char const*, int, unsigned long, bool)+116 in libjvm.so +[0x00007fd04189669f] SymbolTable::new_symbols(ClassLoaderData*, constantPoolHandle const&, int, char const**, int*, int*, unsigned int*)+95 in libjvm.so +[0x00007fd040fc0042] ClassFileParser::parse_constant_pool_entries(ClassFileStream const*, ConstantPool*, int, JavaThread*)+3026 in libjvm.so +[0x00007fd040fc0282] ClassFileParser::parse_constant_pool(ClassFileStream const*, ConstantPool*, int, JavaThread*)+34 in libjvm.so +[0x00007fd040fc1c9a] ClassFileParser::ClassFileParser(ClassFileStream*, Symbol*, ClassLoaderData*, ClassLoadInfo const*, ClassFileParser::Publicity, JavaThread*)+938 in libjvm.so +[0x00007fd04149dd3e] KlassFactory::create_from_stream(ClassFileStream*, Symbol*, ClassLoaderData*, ClassLoadInfo const&, JavaThread*)+558 in libjvm.so +[0x00007fd0418a3310] SystemDictionary::resolve_class_from_stream(ClassFileStream*, Symbol*, Handle, ClassLoadInfo const&, JavaThread*)+496 in libjvm.so +[0x00007fd041357bce] jvm_define_class_common(char const*, _jobject*, signed char const*, int, _jobject*, char const*, JavaThread*) [clone .constprop.285]+510 in libjvm.so +[0x00007fd041357d06] JVM_DefineClassWithSource+134 in libjvm.so +[0x00007fd0402cf6d2] Java_java_lang_ClassLoader_defineClass1+450 in libjava.so +[0x00007fd0254b453a] 0x00007fd0254b453aBufferBlob (0x00007fd0254afb10) used for Interpreter +---- 1 ---- +Invocs: 2812 (+0) +Alloc Size: 16 +[0x00007fd04159f3d0] sap::my_malloc_hook(unsigned long, void const*)+192 in libjvm.so +[0x00007fd040e0a004] AllocateHeap(unsigned long, MEMFLAGS, AllocFailStrategy::AllocFailEnum)+68 in libjvm.so +[0x00007fd041895cd6] SymbolTable::do_add_if_needed(char const*, int, unsigned long, bool)+182 in libjvm.so +[0x00007fd04189669f] SymbolTable::new_symbols(ClassLoaderData*, constantPoolHandle const&, int, char const**, int*, int*, unsigned int*)+95 in libjvm.so +[0x00007fd040fc0042] ClassFileParser::parse_constant_pool_entries(ClassFileStream const*, ConstantPool*, int, JavaThread*)+3026 in libjvm.so +[0x00007fd040fc0282] ClassFileParser::parse_constant_pool(ClassFileStream const*, ConstantPool*, int, JavaThread*)+34 in libjvm.so +[0x00007fd040fc1c9a] ClassFileParser::ClassFileParser(ClassFileStream*, Symbol*, ClassLoaderData*, ClassLoadInfo const*, ClassFileParser::Publicity, JavaThread*)+938 in libjvm.so +[0x00007fd04149dd3e] KlassFactory::create_from_stream(ClassFileStream*, Symbol*, ClassLoaderData*, ClassLoadInfo const&, JavaThread*)+558 in libjvm.so +[0x00007fd0418a3310] SystemDictionary::resolve_class_from_stream(ClassFileStream*, Symbol*, Handle, ClassLoadInfo const&, JavaThread*)+496 in libjvm.so +[0x00007fd041357bce] jvm_define_class_common(char const*, _jobject*, signed char const*, int, _jobject*, char const*, JavaThread*) [clone .constprop.285]+510 in libjvm.so +[0x00007fd041357d06] JVM_DefineClassWithSource+134 in libjvm.so +[0x00007fd0402cf6d2] Java_java_lang_ClassLoader_defineClass1+450 in libjava.so +[0x00007fd0254b453a] 0x00007fd0254b453aBufferBlob (0x00007fd0254afb10) used for Interpreter +... + +... +Table size: 8171, num_entries: 3351, used slots: 519, longest chain: 5, invocs: 74515, lost: 0, collisions: 5844 +Malloc trace on. + (method: nmt-ish) + +74515 captures (0 without stack). +``` + +#### Reset the call site table: + +It is possible to reset the call site table. + +``` +jcmd (VM) System.malloctrace reset +``` + +This command will clear the table but not affect any running trace (if active) - the table will repopulate. + + +### Usage via command line + +One can switch on tracing at VM startup using the switch `-XX:+EnableMallocTrace`. A final report is printed upon VM shutdown to stdout via `-XX:+PrintMallocTraceAtExit`. + +Both options are diagnostic; one needs to unlock them with `-XX:+UnlockDiagnosticVMOptions` in release builds. + +Note: Starting the trace at VM startup is certainly possible but may not be the best option; the internal call site table will fill with many one-shot call sites that are only relevant during VM startup. A too-full call site table may slow down subsequent tracing. + +### Memory costs + +The internal data structures cost about ~5M. This memory is limited, and it will not grow - if we hit the limit, we won't register new call sites we encounter (but will continue to account for old call sites). + +Note that the call site table holds 32K call sites. That far exceeds the usual number of malloc call sites in the VM, so we should typically never hit this limit. + +### Performance costs + +In measurements, the MallocTrace increased VM startup time by about 6%. The slowdown highly depends on the frequency of malloc calls, though: a thread continuously doing malloc in a tight loop may slow down by factor 2-3. + +### Limitations + +This facility uses Glibc malloc hooks. + +Glibc malloc hooks are decidedly thread-unsafe, and we use them in a multithreaded context. Therefore what we can do with these hooks is very restricted. + +1) This is **not** a complete leak analysis tool! All we see with this facility is the "hotness" of malloc call sites. These may be innocuous; e.g., a malloc call site may be followed by a free call right away - it still would show up as a hot call site in the MallocTrace report. +2) **Not every allocation** will be captured since there are small-time windows where the hooks need to be disabled. + +One should use the MallocTrace tool to analyze suspected C-heap leaks when NMT/SAP JVM malloc statistics show up empty. It shows you which malloc sites are hot; nothing more. + +It works with third-party code, even with code that just happens to run in the VM process, e.g., system libraries. diff --git a/src/hotspot/os/linux/malloctrace/siteTable.cpp b/src/hotspot/os/linux/malloctrace/siteTable.cpp new file mode 100644 index 00000000000..b086d847ee4 --- /dev/null +++ b/src/hotspot/os/linux/malloctrace/siteTable.cpp @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2021 SAP SE. All rights reserved. + * Copyright (c) 2021, 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 "code/codeBlob.hpp" +#include "code/codeCache.hpp" +#include "malloctrace/assertHandling.hpp" +#include "malloctrace/siteTable.hpp" +#include "runtime/frame.inline.hpp" +#include "utilities/debug.hpp" +#include "utilities/globalDefinitions.hpp" +#include "utilities/ostream.hpp" + +#include + +#ifdef __GLIBC__ + +// Pulled from JDK17 (see os::print_function_and_library_name()). +// Note: Abridged version, does not handle function descriptors, which only concerns ppc64. +// But since these are real code pointers, not function descriptors, this should be fine. +static bool print_function_and_library_name(outputStream* st, + address addr, + char* buf, int buflen, + bool shorten_paths, + bool demangle, + bool strip_arguments) { + // If no scratch buffer given, allocate one here on stack. + // (used during error handling; its a coin toss, really, if on-stack allocation + // is worse than (raw) C-heap allocation in that case). + char* p = buf; + if (p == NULL) { + p = (char*)::alloca(O_BUFLEN); + buflen = O_BUFLEN; + } + int offset = 0; + bool have_function_name = os::dll_address_to_function_name(addr, p, buflen, + &offset, demangle); + bool is_function_descriptor = false; + + if (have_function_name) { + // Print function name, optionally demangled + if (demangle && strip_arguments) { + char* args_start = strchr(p, '('); + if (args_start != NULL) { + *args_start = '\0'; + } + } + // Print offset. Omit printing if offset is zero, which makes the output + // more readable if we print function pointers. + if (offset == 0) { + st->print("%s", p); + } else { + st->print("%s+%d", p, offset); + } + } else { + st->print(PTR_FORMAT, p2i(addr)); + } + offset = 0; + + const bool have_library_name = os::dll_address_to_library_name(addr, p, buflen, &offset); + if (have_library_name) { + // Cut path parts + if (shorten_paths) { + char* p2 = strrchr(p, os::file_separator()[0]); + if (p2 != NULL) { + p = p2 + 1; + } + } + st->print(" in %s", p); + if (!have_function_name) { // Omit offset if we already printed the function offset + st->print("+%d", offset); + } + } + + return have_function_name || have_library_name; +} +// End: print_function_and_library_name picked from JDK17 + +namespace sap { + +/////// Wrapper for the glibc backtrace(3) function; +// (we need to load it dynamically since it is not always guaranteed to be there.) + +class BackTraceWrapper { + typedef int (*backtrace_fun_t) (void **buffer, int size); + backtrace_fun_t _fun = NULL; + static backtrace_fun_t load_symbol() { + ::dlerror(); // clear state + void* sym = ::dlsym(RTLD_DEFAULT, "backtrace"); + if (sym != NULL && ::dlerror() == NULL) { + return (backtrace_fun_t)sym; + } + return NULL; + } +public: + BackTraceWrapper() : _fun(load_symbol()) {} + + // Capture a stack using backtrace(3); return true on success. + bool capture(Stack* stack) const { + if (_fun == NULL) { + return false; + } + return _fun((void**)stack->_frames, Stack::num_frames) > 0; + } +}; + +static BackTraceWrapper g_backtrace_wrapper; + +/////// NMT-like callstack function + +static bool capture_stack_nmt_like(Stack* stack) { + int frame_idx = 0; + int num_frames = 0; + frame fr = os::current_frame(); + while (fr.pc() && frame_idx < Stack::num_frames) { + stack->_frames[frame_idx ++] = fr.pc(); + num_frames++; + if (fr.fp() == NULL || fr.cb() != NULL || + fr.sender_pc() == NULL || os::is_first_C_frame(&fr)) break; + if (fr.sender_pc() && !os::is_first_C_frame(&fr)) { + fr = os::get_sender_for_C_frame(&fr); + } else { + break; + } + } + return num_frames > 0; +} + +void Stack::print_on(outputStream* st) const { + char tmp[256]; + for (int i = 0; i < num_frames && _frames[i] != NULL; i++) { + st->print("[" PTR_FORMAT "] ", p2i(_frames[i])); + if (print_function_and_library_name(st, _frames[i], tmp, sizeof(tmp), true, true, false)) { + st->cr(); + } + } +} + +// Capture stack; try both methods and use the result from the +// one getting the better results. +bool Stack::capture_stack(Stack* stack, bool use_backtrace) { + stack->reset(); + return use_backtrace ? g_backtrace_wrapper.capture(stack) : capture_stack_nmt_like(stack); +} + +#ifdef ASSERT +void SiteTable::verify() const { + unsigned num_sites_found = 0; + uint64_t num_invocations_found = 0; + for (unsigned slot = 0; slot < table_size; slot ++) { + for (Node* n = _table[slot]; n != NULL; n = n->next) { + num_sites_found ++; + num_invocations_found += n->site.invocations; + malloctrace_assert(slot_for_stack(&n->site.stack) == slot, "hash mismatch"); + malloctrace_assert(n->site.invocations > 0, "sanity"); + malloctrace_assert(n->site.invocations >= n->site.invocations_delta, "sanity"); + } + } + malloctrace_assert(num_sites_found <= _max_entries && num_sites_found == _size, + "mismatch (found: %u, max: %u, size: %u)", num_sites_found, _max_entries, _size); + malloctrace_assert(num_invocations_found + _lost == _invocations, + "mismatch (" UINT64_FORMAT " vs " UINT64_FORMAT, num_invocations_found, _invocations); + malloctrace_assert(num_sites_found <= max_entries(), "sanity"); +} +#endif // ASSERT + +SiteTable::SiteTable() { + reset(); +} + +void SiteTable::reset_deltas() { + for (unsigned slot = 0; slot < table_size; slot ++) { + for (Node* n = _table[slot]; n != NULL; n = n->next) { + n->site.invocations_delta = 0; + } + } +} + +void SiteTable::reset() { + _size = 0; + _invocations = _lost = _collisions = 0; + ::memset(_table, 0, sizeof(_table)); + _nodeheap.reset(); +}; + +SiteTable* SiteTable::create() { + void* p = ::malloc(sizeof(SiteTable)); + return new(p) SiteTable; +} + +void SiteTable::print_stats(outputStream* st) const { + unsigned longest_chain = 0; + unsigned used_slots = 0; + for (unsigned slot = 0; slot < table_size; slot ++) { + unsigned len = 0; + for (Node* n = _table[slot]; n != NULL; n = n->next) { + len ++; + } + longest_chain = MAX2(len, longest_chain); + if (len > 1) { + used_slots ++; + } + } + // Note: if you change this format, check gtest test_site_table parser. + st->print("Table size: %u, num_entries: %u, used slots: %u, longest chain: %u, invocs: " + UINT64_FORMAT ", lost: " UINT64_FORMAT ", collisions: " UINT64_FORMAT, + table_size, _size, used_slots, longest_chain, + _invocations, _lost, _collisions); +} + +// Sorting stuff for printing the table + +static int qsort_helper(const void* s1, const void* s2) { + return ((const Site*)s2)->invocations > ((const Site*)s1)->invocations ? 1 : -1; +} + +void SiteTable::print_table(outputStream* st, bool all) const { + + if (_size == 0) { + st->print_cr("Table is empty."); + } + + // We build up an index array of the filtered entries, then sort it by invocation counter. + unsigned num_entries = 0; + Site* const sorted_items = NEW_C_HEAP_ARRAY(Site, _size, mtInternal); + + for (unsigned slot = 0; slot < table_size; slot ++) { + for (Node* n = _table[slot]; n != NULL; n = n->next) { + if (n->site.invocations > 0) { + sorted_items[num_entries] = n->site; + num_entries ++; + } + } + } + malloctrace_assert(num_entries <= _size, "sanity"); + malloctrace_assert(num_entries <= max_entries(), "sanity"); + ::qsort(sorted_items, num_entries, sizeof(Site), qsort_helper); + + int rank = 0; + const unsigned max_show = all ? _size : MIN2(_size, (unsigned)10); + if (max_show < _size) { + st->print_cr("---- %d hottest malloc sites: ----", max_show); + } + for (unsigned i = 0; i < max_show; i ++) { + // For each call site, print out ranking, number of invocation, + // alloc size or alloc size range if non-uniform sizes, and stack. + st->print_cr("---- %d ----", i); + st->print_cr("Invocs: " UINT64_FORMAT " (+" UINT64_FORMAT ")", + sorted_items[i].invocations, sorted_items[i].invocations_delta); + if (sorted_items[i].max_alloc_size == sorted_items[i].min_alloc_size) { + st->print_cr("Alloc Size: " UINT32_FORMAT, sorted_items[i].max_alloc_size); + } else { + st->print_cr("Alloc Size Range: " UINT32_FORMAT " - " UINT32_FORMAT, + sorted_items[i].min_alloc_size, sorted_items[i].max_alloc_size); + } + sorted_items[i].stack.print_on(st); + } + if (max_show < _size) { + st->print_cr("---- %d entries omitted - use \"all\" to print full table.", + _size - max_show); + } + st->cr(); + FREE_C_HEAP_ARRAY(Site, sorted_items); +} + + +} // namespace sap + +#endif // GLIBC diff --git a/src/hotspot/os/linux/malloctrace/siteTable.hpp b/src/hotspot/os/linux/malloctrace/siteTable.hpp new file mode 100644 index 00000000000..e7b56d9b2e4 --- /dev/null +++ b/src/hotspot/os/linux/malloctrace/siteTable.hpp @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2021 SAP SE. All rights reserved. + * Copyright (c) 2021, 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. + * + */ + +#ifndef OS_LINUX_MALLOCTRACE_SITETABLE_HPP +#define OS_LINUX_MALLOCTRACE_SITETABLE_HPP + +#include "malloctrace/assertHandling.hpp" +#include "utilities/globalDefinitions.hpp" + +#ifdef __GLIBC__ + +class outputStream; + +namespace sap { + +//////////////////////////////////////////////////// +// We currently support two ways to get a stack trace: +// - using backtrace(3) +// - using an NMT-like callstack walker +// I am not sure yet which is better. Have to experiment. + +enum capture_method_t { + nmt_like = 0, using_backtrace = 1 +}; + +///// Stack //////////////////// +// simple structure holding a fixed-sized native stack + +struct Stack { + static const int num_frames = 16; + address _frames[num_frames]; + + unsigned calculate_hash() const { + uintptr_t hash = 0; + for (int i = 0; i < num_frames; i++) { + hash += (uintptr_t)_frames[i]; + } + return hash; + } + + void reset() { + ::memset(_frames, 0, sizeof(_frames)); + } + + void copy_to(Stack* other) const { + ::memcpy(other->_frames, _frames, sizeof(_frames)); + } + + bool equals(const Stack* other) const { + return ::memcmp(_frames, other->_frames, sizeof(_frames)) == 0; + } + + void print_on(outputStream* st) const; + + static bool capture_stack(Stack* stack, bool use_backtrace); + +}; + +///// Site //////////////////// +// Stack + invocation counters + +struct Site { + Stack stack; + uint64_t invocations; + uint64_t invocations_delta; // delta since last printing + uint32_t min_alloc_size; // min and max allocation size + uint32_t max_alloc_size; // from that call site + // (note: can be zero: we also trace zero-sized allocs since malloc(0) + // could also be a leak) +}; + +///// SiteTable //////////////////// +// A hashmap containing all captured malloc call sites. +// This map is kept very simple. We never remove entries, just +// reset the table as a whole. Space for the nodes is pre-allocated when +// the table is created to prevent malloc calls disturbing the statistics +// run. +class SiteTable { + + static const int _max_entries = 32 * K; + + struct Node { + Node* next; + Site site; + }; + + // We preallocate all nodes in this table to avoid + // swamping the VM with internal malloc calls while the + // trace is running. + class NodeHeap { + Node _nodes[SiteTable::_max_entries]; + int _used; + public: + NodeHeap() : _used(0) { + ::memset(_nodes, 0, sizeof(_nodes)); + } + Node* get_node() { + Node* n = NULL; + if (_used < SiteTable::_max_entries) { + n = _nodes + _used; + _used ++; + } + return n; + } + void reset() { + ::memset(_nodes, 0, sizeof(_nodes)); + _used = 0; + } + }; + + NodeHeap _nodeheap; + const static int table_size = 8171; //prime + Node* _table[table_size]; + + unsigned _size; // Number of entries + uint64_t _invocations; // invocations (including lost) + uint64_t _lost; // lost adds due to table full + uint64_t _collisions; // hash collisions + + static unsigned slot_for_stack(const Stack* stack) { + unsigned hash = stack->calculate_hash(); + malloctrace_assert(hash != 0, "sanity"); + return hash % table_size; + } + +public: + + SiteTable(); + + void add_site(const Stack* stack, uint32_t alloc_size) { + _invocations ++; + + const unsigned slot = slot_for_stack(stack); + + // Find entry + for (Node* p = _table[slot]; p != NULL; p = p->next) { + if (p->site.stack.equals(stack)) { + // Call site already presented in table + p->site.invocations ++; + p->site.invocations_delta ++; + p->site.max_alloc_size = MAX2(p->site.max_alloc_size, alloc_size); + p->site.min_alloc_size = MIN2(p->site.min_alloc_size, alloc_size); + return; + } else { + _collisions ++; + } + } + + Node* n = _nodeheap.get_node(); + if (n == NULL) { // hashtable too full, reject. + assert(_size == max_entries(), "sanity"); + _lost ++; + return; + } + n->site.invocations = n->site.invocations_delta = 1; + n->site.max_alloc_size = n->site.min_alloc_size = alloc_size; + stack->copy_to(&(n->site.stack)); + n->next = _table[slot]; + _table[slot] = n; + _size ++; + } + + void print_table(outputStream* st, bool raw) const; + void print_stats(outputStream* st) const; + void reset_deltas(); + void reset(); + DEBUG_ONLY(void verify() const;) + + // create a table from c-heap + static SiteTable* create(); + + // Maximum number of entries the table can hold. + static unsigned max_entries() { return _max_entries; } + + // Number of entries currently in the table. + unsigned size() const { return _size; } + + // Number of invocations. + uint64_t invocations() const { return _invocations; } + + // Number of invocations lost because table was full. + uint64_t lost() const { return _lost; } + +}; + +} // namespace sap + +#endif // __GLIBC__ + +#endif // OS_LINUX_MALLOCTRACE_SITETABLE_HPP diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp index 4840b34b6fb..6620b8b119f 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -76,6 +76,9 @@ #include "utilities/macros.hpp" #include "utilities/vmError.hpp" +// SapMachine 2021-09-01: malloc-trace +#include "malloctrace/mallocTrace.hpp" + // put OS-includes here # include # include @@ -5485,6 +5488,11 @@ jint os::init_2(void) { set_coredump_filter(FILE_BACKED_SHARED_BIT); } + // SapMachine 2021-09-01: malloc-trace + if (EnableMallocTrace) { + sap::MallocTracer::enable(); + } + return JNI_OK; } diff --git a/src/hotspot/share/runtime/java.cpp b/src/hotspot/share/runtime/java.cpp index 388a8655e33..4c5416b66c8 100644 --- a/src/hotspot/share/runtime/java.cpp +++ b/src/hotspot/share/runtime/java.cpp @@ -93,6 +93,11 @@ #include "runtime/globals.hpp" #include "vitals/vitals.hpp" +// SapMachine 2021-09-01: malloc-trace +#ifdef LINUX +#include "malloctrace/mallocTrace.hpp" +#endif + GrowableArray* collected_profiled_methods; int compare_methods(Method** a, Method** b) { @@ -547,6 +552,13 @@ void before_exit(JavaThread* thread) { BytecodeHistogram::print(); } +#ifdef LINUX + // SapMachine 2021-09-01: malloc-trace + if (PrintMallocTraceAtExit) { + sap::MallocTracer::print(tty, true); + } +#endif + if (JvmtiExport::should_post_thread_life()) { JvmtiExport::post_thread_end(thread); } diff --git a/src/hotspot/share/services/diagnosticCommand.cpp b/src/hotspot/share/services/diagnosticCommand.cpp index ce56b6f78bd..0ea573374da 100644 --- a/src/hotspot/share/services/diagnosticCommand.cpp +++ b/src/hotspot/share/services/diagnosticCommand.cpp @@ -51,6 +51,11 @@ #include "utilities/formatBuffer.hpp" #include "utilities/macros.hpp" +#ifdef LINUX +// SapMachine 2021-09-01: malloc-trace +#include "malloctrace/mallocTraceDCmd.hpp" +#endif + // SapMachine 2019-02-20 : vitals #include "vitals/vitalsDCmd.hpp" @@ -82,8 +87,10 @@ void DCmdRegistrant::register_dcmds(){ | DCmd_Source_MBean; // SapMachine 2021-09-06: Cherrypick 8268893: "jcmd to trim the glibc heap" + // SapMachine 2021-09-01: malloc-trace #ifdef LINUX DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); #endif DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); diff --git a/src/hotspot/share/utilities/vmError.cpp b/src/hotspot/share/utilities/vmError.cpp index 11cda7b3af6..97a2aa86873 100644 --- a/src/hotspot/share/utilities/vmError.cpp +++ b/src/hotspot/share/utilities/vmError.cpp @@ -59,6 +59,11 @@ // SapMachine 2019-02-20 : vitals #include "vitals/vitals.hpp" +// SapMachine 2021-09-01: malloc-trace +#ifdef LINUX +#include "malloctrace/mallocTrace.hpp" +#endif + #ifndef PRODUCT #include #endif // PRODUCT @@ -1046,6 +1051,17 @@ void VMError::report(outputStream* st, bool _verbose) { st->cr(); } + // SapMachine 2021-09-01: malloc-trace +#ifdef LINUX + STEP("printing Malloc Trace info") + + if (_verbose) { + st->print_cr("sapmachine malloc trace"); + sap::MallocTracer::print_on_error(st); + st->cr(); + } +#endif + // print a defined marker to show that error handling finished correctly. STEP("printing end marker") @@ -1215,6 +1231,12 @@ void VMError::print_vm_info(outputStream* st) { st->print_cr("vm_info: %s", Abstract_VM_Version::internal_vm_info_string()); st->cr(); +#ifdef LINUX + // SapMachine 2021-09-01: malloc-trace + st->print_cr("sapmachine malloc trace"); + sap::MallocTracer::print_on_error(st); +#endif + // print a defined marker to show that error handling finished correctly. // STEP("printing end marker") diff --git a/test/hotspot/gtest/malloctrace/test_site_table.cpp b/test/hotspot/gtest/malloctrace/test_site_table.cpp new file mode 100644 index 00000000000..47ce989401f --- /dev/null +++ b/test/hotspot/gtest/malloctrace/test_site_table.cpp @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2021 SAP SE. All rights reserved. + * Copyright (c) 2021, 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" + +#ifdef LINUX + +#include "jvm.h" +#include "malloctrace/siteTable.hpp" +#include "memory/allocation.hpp" +#include "runtime/os.hpp" +#include "utilities/debug.hpp" +#include "utilities/ostream.hpp" +#include "unittest.hpp" + +#ifdef __GLIBC__ + +using sap::SiteTable; + +static void init_random_randomly() { + os::init_random((int)os::elapsed_counter()); +} + +//#define LOG + +static void fill_stack_randomly(sap::Stack* s) { + for (unsigned i = 0; i < sap::Stack::num_frames; i++) { + s->_frames[i] = (address)(intptr_t)os::random(); + } +} + +// Since SiteTable is too large to be put onto the stack of a test function, +// we need to create it dynamically. I don't want to make it a CHeapObj only +// for the sake of these tests though, so I have to use placement new. +static SiteTable* create_site_table() { + void* p = NEW_C_HEAP_ARRAY(SiteTable, 1, mtTest); + return new (p) SiteTable; +} + +static void destroy_site_table(SiteTable* s) { + FREE_C_HEAP_ARRAY(SiteTable, s); +} + +// Helper, create an array of unique stacks, randomly filled; returned array is C-heap allocated +static sap::Stack* create_unique_stack_array(int num) { + sap::Stack* random_stacks = NEW_C_HEAP_ARRAY(sap::Stack, num, mtTest); + for (int i = 0; i < num; i ++) { + fill_stack_randomly(random_stacks + i); + // ensure uniqueness + random_stacks[i]._frames[0] = (address)(intptr_t)i; + } + return random_stacks; +} + +static void test_print_table(const SiteTable* table, int expected_entries) { + stringStream ss; + + table->print_stats(&ss); + if (expected_entries != -1) { + char match[32]; + jio_snprintf(match, sizeof(match), + "num_entries: %u,", expected_entries); + ASSERT_NE(::strstr(ss.base(), match), (char*)NULL); + } + ss.reset(); + + table->print_table(&ss, true); + if (expected_entries != -1) { + if (expected_entries > 0) { + // Note, output buffer may not hold full output + ASSERT_NE(::strstr(ss.base(), "--- 1 ---"), (char*)NULL); + } else { + ASSERT_NE(::strstr(ss.base(), "Table is empty"), (char*)NULL); + } + } +} + +TEST_VM(MallocTrace, site_table_basics) { + + init_random_randomly(); + + SiteTable* table = create_site_table(); + + test_print_table(table, 0); // Test printing empty table. + + const unsigned safe_to_add_without_overflow = SiteTable::max_entries(); + + // Generate a number of random stacks; enough to hit overflow limit from time to time. + const int num_stacks = safe_to_add_without_overflow + 100; + sap::Stack* random_stacks = create_unique_stack_array(num_stacks); + + // Add n guaranteed-to-be-unique call stacks to the table; observe table; do that n times, which should + // increase invoc counters. + uint64_t expected_invocs = 0; + unsigned expected_unique_callsites = 0; + for (int invocs_per_stack = 0; invocs_per_stack < 10; invocs_per_stack++) { + for (unsigned num_callstacks = 0; num_callstacks < safe_to_add_without_overflow; num_callstacks++) { + table->add_site(random_stacks + num_callstacks, 1024); + expected_invocs ++; + if (invocs_per_stack == 0) { + // On the first iteration we expect a new callsite table node to be created for this stack + expected_unique_callsites++; + } + ASSERT_EQ(table->invocations(), expected_invocs); + ASSERT_EQ(table->size(), expected_unique_callsites); // Must be, since all stacks we add are be unique + ASSERT_EQ(table->lost(), (uint64_t)0); // So far we should see no losses + } + } + test_print_table(table, expected_unique_callsites); + DEBUG_ONLY(table->verify();) + + // Now cause table to overflow by adding further unique call stacks. Table should reject these new stacks + // and count them in lost counter + for (int overflow_num = 0; overflow_num < 100; overflow_num++) { + table->add_site(random_stacks + safe_to_add_without_overflow + overflow_num, 1024); + ASSERT_EQ(table->size(), expected_unique_callsites); // Should stay constant, no further adds should be accepted + ASSERT_EQ(table->lost(), (uint64_t)(overflow_num + 1)); // Lost counter should go up + ASSERT_EQ(table->invocations(), expected_invocs + overflow_num + 1); // Invocations counter includes lost + } + + test_print_table(table, expected_unique_callsites); + DEBUG_ONLY(table->verify();) + +#ifdef LOG + //table->print_table(tty, true); + table->print_stats(tty); + tty->cr(); +#endif + + destroy_site_table(table); +} + +TEST_VM(MallocTrace, site_table_random) { + SiteTable* table = create_site_table(); + + init_random_randomly(); + + // Generate a number of random stacks; enough to hit overflow limit from time to time. + const int num_stacks = SiteTable::max_entries() * 1.3; + sap::Stack* random_stacks = create_unique_stack_array(num_stacks); + + for (int i = 0; i < num_stacks; i ++) { + fill_stack_randomly(random_stacks + i); + } + + // Now register these stacks randomly, a lot of times. + for (int i = 0; i < 1000*1000; i ++) { + sap::Stack* stack = random_stacks + (os::random() % num_stacks); + table->add_site(stack, 1024); + ASSERT_EQ(table->invocations(), (uint64_t)i + 1); + } + + // test table printing, but we do not know how many unique stacks we have randomly generated, so don't + // test the exact number of entries + test_print_table(table, -1); + + DEBUG_ONLY(table->verify();) + + FREE_C_HEAP_ARRAY(Stack, random_stacks); + +#ifdef LOG + //table->print_table(tty, true); + table->print_stats(tty); + tty->cr(); +#endif + + destroy_site_table(table); +} + +#endif // __GLIBC__ + +#endif // LINUX diff --git a/test/hotspot/jtreg/TEST.groups b/test/hotspot/jtreg/TEST.groups index 189b4885c80..e6231c1fa5a 100644 --- a/test/hotspot/jtreg/TEST.groups +++ b/test/hotspot/jtreg/TEST.groups @@ -338,7 +338,7 @@ tier1_serviceability = \ # or which tests downstream-only features. tier1_sapmachine = \ runtime/Vitals \ - serviceability/dcmd/vm/TrimLibcHeapTest.java + runtime/malloctrace # SapMachine 2019-02-24 : Add tests where SAPMachine has different behavior to tier1, # or which tests downstream-only features. diff --git a/test/hotspot/jtreg/runtime/malloctrace/MallocTraceDcmdTest.java b/test/hotspot/jtreg/runtime/malloctrace/MallocTraceDcmdTest.java new file mode 100644 index 00000000000..e8e0986e6fe --- /dev/null +++ b/test/hotspot/jtreg/runtime/malloctrace/MallocTraceDcmdTest.java @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, SAP 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. + */ + +import jdk.test.lib.JDKToolFinder; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; +import sun.hotspot.WhiteBox; + +import java.util.ArrayList; +import java.util.Random; + +// For now 64bit only, 32bit stack capturing still does not work that well + +/* + * @test MallocTraceDcmdTest + * @summary Test the System.malloctrace command + * @library /test/lib + * @requires vm.bits == "64" + * @requires os.family == "linux" + * @modules java.base/jdk.internal.misc + * java.management + * @build sun.hotspot.WhiteBox + * @run driver ClassFileInstaller sun.hotspot.WhiteBox + * sun.hotspot.WhiteBox$WhiteBoxPermission + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI MallocTraceDcmdTest + */ + +public class MallocTraceDcmdTest { + + private static class MallocStresser { + int numThreads = 5; + Thread threads[]; + static WhiteBox whiteBox = WhiteBox.getWhiteBox(); + + public MallocStresser(int numThreads) { + this.numThreads = numThreads; + } + + boolean stop = false; + + class StresserThread extends Thread { + public void run() { + Random rand = new Random(); + while (!stop) { + // We misuse NMT's whitebox functions for mallocing + // We also could use Unsafe.allocateMemory, but dealing with + // deprecation is annoying. + long p[] = new long[10]; + for (int i = 0; i < p.length; i++) { + p[i] = whiteBox.NMTMalloc(rand.nextInt(128)); + } + for (int i = 0; i < p.length; i++) { + whiteBox.NMTFree(p[i]); + } + } + } + } + + public void start() { + threads = new Thread[numThreads]; + for (int i = 0; i < numThreads; i ++) { + threads[i] = new StresserThread(); + threads[i].start(); + } + } + + public void stop() throws InterruptedException { + stop = true; + for (int i = 0; i < numThreads; i++) { + threads[i].join(); + } + } + } + + static boolean currentState = false; + static int numSwitchedOn = 0; // How often we switched tracing on + static int numReseted = 0; // How often we reseted + + static OutputAnalyzer testCommand(String... options) throws Exception { + OutputAnalyzer output; + // Grab my own PID + String pid = Long.toString(ProcessTools.getProcessId()); + String jcmdPath = JDKToolFinder.getJDKTool("jcmd"); + ArrayList command = new ArrayList<>(); + command.add(jcmdPath); + command.add(pid); + command.add("System.malloctrace"); + for (String option: options) { + if (option != null) { + command.add(option); + } + } + System.out.println("--- " + command); + ProcessBuilder pb = new ProcessBuilder(command); + output = new OutputAnalyzer(pb.start()); + output.reportDiagnosticSummary(); + output.shouldHaveExitValue(0); + return output; + } + + // System.malloctrace on + private static void testOn() throws Exception { + OutputAnalyzer output = testCommand("on"); + output.shouldContain("Tracing active"); + numSwitchedOn ++; + currentState = true; + } + + // System.malloctrace off + private static void testOff() throws Exception { + OutputAnalyzer output = testCommand("off"); + output.shouldContain("Tracing inactive"); + currentState = false; + } + + // System.malloctrace reset + private static void testReset() throws Exception { + OutputAnalyzer output = testCommand("reset"); + output.shouldContain("Tracing table reset"); + numReseted ++; + } + + // System.malloctrace print + private static void testPrint(boolean all) throws Exception { + OutputAnalyzer output = testCommand("print", all ? "all" : null); + if (currentState) { + output.shouldContain("WB_NMTMalloc"); + output.shouldContain("Malloc trace on"); + } else { + output.shouldContain("Malloc trace off"); + } + } + + public static void main(String args[]) throws Exception { + MallocStresser stresser = new MallocStresser(3); + stresser.start(); + Thread.sleep(1000); + testPrint(false); + Thread.sleep(500); + testOn(); + Thread.sleep(500); + testPrint(false); + testPrint(true); + testReset(); + testPrint(true); + testOn(); + Thread.sleep(500); + testOff(); + testOff(); + Thread.sleep(500); + testPrint(false); + testReset(); + testPrint(false); + testOff(); + } +} diff --git a/test/hotspot/jtreg/runtime/malloctrace/MallocTraceTest.java b/test/hotspot/jtreg/runtime/malloctrace/MallocTraceTest.java new file mode 100644 index 00000000000..ce6198ab7ef --- /dev/null +++ b/test/hotspot/jtreg/runtime/malloctrace/MallocTraceTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2021 SAP SE. 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. + */ + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +// SapMachine 2021-08-01: malloctrace +// For now 64bit only, 32bit stack capturing still does not work that well + + +/* + * @test MallocTraceTest + * @requires os.family == "linux" + * @requires vm.bits == "64" + * @library /test/lib + * @run driver MallocTraceTest on + */ + +/* + * @test MallocTraceTest + * @requires os.family == "linux" + * @requires vm.bits == "64" + * @library /test/lib + * @run driver MallocTraceTest off + */ + +public class MallocTraceTest { + + public static void main(String... args) throws Exception { + if (args[0].equals("off") || args[0].equals("on")) { + boolean active = args[0].equals("on"); + String option = active ? "-XX:+EnableMallocTrace" : "-XX:-EnableMallocTrace"; + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder("-XX:+UnlockDiagnosticVMOptions", + option, "-XX:+PrintMallocTraceAtExit", "-version"); + OutputAnalyzer output = new OutputAnalyzer(pb.start()); + output.shouldHaveExitValue(0); + if (active) { + String stdout = output.getStdout(); + // Checking for the correct frames is a whack-the-mole game since we cannot be sure how frames + // are inlined. Therefore we cannot just use "shouldContain". + output.stdoutShouldMatch("(os::malloc|my_malloc_hook)"); + output.shouldContain("Malloc trace on."); + } else { + output.shouldContain("Malloc trace off."); + } + } else { + throw new RuntimeException("Wrong or missing args."); + } + } +}