diff --git a/src/hotspot/os/linux/globals_linux.hpp b/src/hotspot/os/linux/globals_linux.hpp index 72915b5afbb..93f9db5a8bc 100644 --- a/src/hotspot/os/linux/globals_linux.hpp +++ b/src/hotspot/os/linux/globals_linux.hpp @@ -81,6 +81,12 @@ product(bool, UseCpuAllocPath, false, DIAGNOSTIC, \ "Use CPU_ALLOC code path in os::active_processor_count ") \ \ + /* SapMachine 2021-09-01: malloc-trace */ \ + product(bool, EnableMallocTrace, false, DIAGNOSTIC, \ + "Enable malloc trace at VM initialization") \ + product(bool, PrintMallocTraceAtExit, false, DIAGNOSTIC, \ + "Print Malloc Trace upon VM exit") \ + \ product(bool, DumpPerfMapAtExit, false, DIAGNOSTIC, \ "Write map file for Linux perf tool at exit") diff --git a/src/hotspot/os/linux/malloctrace/assertHandling.cpp b/src/hotspot/os/linux/malloctrace/assertHandling.cpp new file mode 100644 index 00000000000..0d462c40632 --- /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(&g_asserting, false, true) != 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..3f01dc380e8 --- /dev/null +++ b/src/hotspot/os/linux/malloctrace/locker.hpp @@ -0,0 +1,77 @@ +/* + * 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() { + 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..91a80786f9f --- /dev/null +++ b/src/hotspot/os/linux/malloctrace/mallocTrace.cpp @@ -0,0 +1,367 @@ +/* + * 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 "malloctrace/siteTable.hpp" +#include "memory/allStatic.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 void verify() { + if (_hooks_are_active) { + malloctrace_assert(__malloc_hook == my_malloc_hook && __realloc_hook == my_realloc_hook && + __memalign_hook == my_memalign_hook, + "Expected my hooks to be active, but found: " + "__malloc_hook=" PTR_FORMAT ", __realloc_hook=" PTR_FORMAT + ", __memalign_hook=" PTR_FORMAT " instead.", + (intptr_t)__malloc_hook, (intptr_t)__realloc_hook, + (intptr_t)__memalign_hook); + } else { + malloctrace_assert(__malloc_hook != my_malloc_hook && __realloc_hook != my_realloc_hook && + __memalign_hook != my_memalign_hook, + "Expected my hooks to be inactive, but found: " + "__malloc_hook=" PTR_FORMAT ", __realloc_hook=" PTR_FORMAT + ", __memalign_hook=" PTR_FORMAT " instead.", + (intptr_t)__malloc_hook, (intptr_t)__realloc_hook, + (intptr_t)__memalign_hook); + } + } +#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) { + 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(); + } + 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; + } + return false; +} + +bool MallocTracer::disable() { + Locker lck; + if (HookControl::hooks_are_active()) { + HookControl::disable(); + return true; + } + return false; +} + +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. + 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..c278e71fd75 --- /dev/null +++ b/src/hotspot/os/linux/malloctrace/mallocTrace.hpp @@ -0,0 +1,54 @@ +/* + * 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/allStatic.hpp" +#include "utilities/globalDefinitions.hpp" + +#ifdef __GLIBC__ + +class outputStream; + +namespace sap { + +class MallocTracer : public AllStatic { +public: + // Enable tracing; return true if it was enabled, false if it had already been enabled. + static bool enable(bool use_backtrace = false); + // Disable tracing; return true if it was disabled, false if it had already been disabled. + static bool 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..a1480bd9469 --- /dev/null +++ b/src/hotspot/os/linux/malloctrace/mallocTraceDCmd.cpp @@ -0,0 +1,112 @@ +/* + * 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 "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 activated"); + } else { + _output->print_raw("Tracing already active"); + } + } else if (::strcmp(_option.value(), "off") == 0) { + if (MallocTracer::disable()) { + _output->print_raw("Tracing deactivated"); + } else { + _output->print_raw("Tracing was already deactivated"); + } + } 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" + " - 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); +} + +} // 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..974e59e11ee --- /dev/null +++ b/src/hotspot/os/linux/malloctrace/mallocTraceDCmd.hpp @@ -0,0 +1,60 @@ +/* + * 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 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..2e4e8eebd0a --- /dev/null +++ b/src/hotspot/os/linux/malloctrace/malloc_trace.md @@ -0,0 +1,154 @@ +(c) SAP 2021 + +# The SapMachine malloctrace facility + +## Motivation + +We have a number of options when analyzing C-heap memory usage in the VM (without external tools): + +1) NMT +2) in the SAP JVM, the SAP JVM malloc statistics + +but both statistics only work for instrumented code. NMT only works for the hotspot, the SAP JVM malloc statistics work for hotspot plus instrumented parts of the JDK. Both cases miss uninstrumented allocations from within the VM, from third party code as well as from system libraries. So there is a gap. + +In the past we were facing what we strongly suspected were C-heap leaks in third party code, but without the ability to prove it, since we were unable to analyze the situation with tools like valgrind; for these situations a tool is needed which +- catches all malloc calls, regardless of where it happens and whether we instrumented it +- gives us a callstack at that point. + +### mtrace? + +One simple 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. The problem with that is that its insanely costly. In my experiments, VM slowed down by factor 4-10. This makes sense since the glibc internal malloc trace just writes an uncompressed trace file. Another nit here is that these files would have to be post-processed to get any kind of valuable information. + +## The malloc trace facility + +The Linux-only malloc trace facility in the SapMachine uses glibc malloc hooks to hook into the allocation process. In that way it works like `mtrace(3)`. But unlike the glibc-internal malloc trace, it does not write a raw file but accumulates call site statistics in memory. This is way faster than `mtrace(3)`, and it can be switched off and on at runtime. + +### 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 malloc trace report. + +Two options exist, a full report which can be lengthy but will show all call sites; or a abridged reports only showing the ten "hottest" call sites. The latter is the default. + +``` +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 + +``` +jcmd (VM) System.malloctrace reset +``` + +will reset the internal call site table. This can make sense if it was filled with irrelevant call sites, and may make subsequent tracing faster. + + +### Usage via command line + +The trace can be switched on at VM startup via `-XX:+EnableMallocTrace`. + +The trace can be printed upon VM shutdown to stdout via `-XX:+PrintMallocTraceAtExit`. + +Both options are diagnostic and need to be unlocked in release builds with `-XX:+UnlockDiagnosticVMOptions`. + +Note that starting the trace at VM startup may not be the best option since the internal call site table fills with lots of call sites which are only relevant during VM startup and will never turn up again. This may slow down registering new sites considerably. + +### Memory costs + +The internal data structures cost about ~5M. This is memory needed to keep call stacks and invocation counters for malloc sites. This memory is limited, and it will not grow - if we hit the limit, we won't register further call sites. Note however that the memory is dimensioned to hold 32K call sites, which far exceeds the normal number of malloc call sites in the VM. + +### Performance costs + +In measurements, tracing enabled brought about 6% slowdown for VM startup for normal applications (measured with eclipse and spring petclinic). The slowdown highly depends on the frequency of malloc calls. If we have an intense amount of them, slowdown can still be factor 2-3. + +Note however that the typical way to use this facility would not be to have it enabled at startup, but to enable it for a short time in a suspected leak situation. + + +### 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. It boils down to the problem that after hooking you need to satisfy the user request for memory, and therefore call the original malloc(), which means you need to temporarily disable the hooks while doing that. The alternative would be to roll your very own heap implmenetation. This is possible, but several magnitudes larger than a simple tracer. + +Therefore: + +1) This is **not** a full leak analysis tool! All we see with this facility is the "hotness" of malloc call sites. These may be innocous; e.g. a malloc call may be followed by a free call right away. +2) **Not every allocation** will be captured. Since there are small time windows where the hooks need to be disabled. + +Therefore, this is the right tool to analyze situations with a growing C-heap where a leak is suspected from a hot malloc site. It shows you which malloc sites are hot; nothing more. But it works with third party code too, even with code which just happens to run in the VM process without having anything to do with the VM. + + + + + + + + + + + diff --git a/src/hotspot/os/linux/malloctrace/siteTable.cpp b/src/hotspot/os/linux/malloctrace/siteTable.cpp new file mode 100644 index 00000000000..4091dd489d9 --- /dev/null +++ b/src/hotspot/os/linux/malloctrace/siteTable.cpp @@ -0,0 +1,238 @@ +/* + * 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__ + +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 number of frames. + bool call(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 (os::print_function_and_library_name(st, _frames[i], tmp, sizeof(tmp), true, true, false)) { + st->cr(); + } else if (CodeCache::contains((void*)_frames[i])) { + CodeBlob* b = CodeCache::find_blob_unsafe((void*)(void*)_frames[i]); + if (b != NULL) { + //b->dump_for_addr(_frames[i], st, false); + b->print_value_on(st); + } + } else { + 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.call(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 ++; + } + } + 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; +} + +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..5246a5d985f --- /dev/null +++ b/src/hotspot/os/linux/malloctrace/siteTable.hpp @@ -0,0 +1,206 @@ +/* + * 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 class 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 | 1; // Hash should never be 0 + } + + 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(); + + // only needed for gtests. + static unsigned max_entries() { return _max_entries; } + unsigned size() const { return _size; } + uint64_t invocations() const { return _invocations; } + 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 1f9244711c6..988ffcb0188 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -75,6 +75,9 @@ #include "utilities/powerOfTwo.hpp" #include "utilities/vmError.hpp" +// SapMachine 2021-09-01: malloc-trace +#include "malloctrace/mallocTrace.hpp" + // put OS-includes here # include # include @@ -4616,6 +4619,11 @@ jint os::init_2(void) { FLAG_SET_DEFAULT(UseCodeCacheFlushing, false); } + // 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 f1c591659a5..3844c4b6540 100644 --- a/src/hotspot/share/runtime/java.cpp +++ b/src/hotspot/share/runtime/java.cpp @@ -92,6 +92,11 @@ #include "jfr/jfr.hpp" #endif +// SapMachine 2021-09-01: malloc-trace +#ifdef LINUX +#include "malloctrace/mallocTrace.hpp" +#endif + GrowableArray* collected_profiled_methods; int compare_methods(Method** a, Method** b) { @@ -484,6 +489,10 @@ void before_exit(JavaThread* thread) { if (DumpPerfMapAtExit) { CodeCache::write_perf_map(); } + // SapMachine 2021-09-01: malloc-trace + if (PrintMallocTraceAtExit) { + sap::MallocTracer::print(tty, true); + } #endif if (JvmtiExport::should_post_thread_life()) { diff --git a/src/hotspot/share/services/diagnosticCommand.cpp b/src/hotspot/share/services/diagnosticCommand.cpp index 57b55091be5..42c538c6091 100644 --- a/src/hotspot/share/services/diagnosticCommand.cpp +++ b/src/hotspot/share/services/diagnosticCommand.cpp @@ -60,8 +60,11 @@ #include "utilities/macros.hpp" #ifdef LINUX #include "trimCHeapDCmd.hpp" +// SapMachine 2021-09-01: malloc-trace +#include "malloctrace/mallocTraceDCmd.hpp" #endif + static void loadAgentModule(TRAPS) { ResourceMark rm(THREAD); HandleMark hm(THREAD); @@ -96,6 +99,7 @@ void DCmdRegistrant::register_dcmds(){ DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); + #if INCLUDE_SERVICES DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(DCmd_Source_Internal | DCmd_Source_AttachAPI, true, false)); DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); @@ -121,6 +125,8 @@ void DCmdRegistrant::register_dcmds(){ #ifdef LINUX DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); + // SapMachine 2021-09-01: malloc-trace + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); #endif // LINUX DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); 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 ec14e2095ca..c0bf732fe96 100644 --- a/src/hotspot/share/utilities/vmError.cpp +++ b/src/hotspot/share/utilities/vmError.cpp @@ -67,6 +67,11 @@ #include "jvmci/jvmci.hpp" #endif +// SapMachine 2021-09-01: malloc-trace +#ifdef LINUX +#include "malloctrace/mallocTrace.hpp" +#endif + #ifndef PRODUCT #include #endif // PRODUCT @@ -1081,6 +1086,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") @@ -1250,6 +1266,12 @@ void VMError::print_vm_info(outputStream* st) { st->print_cr("vm_info: %s", 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..36507dc4e3d --- /dev/null +++ b/test/hotspot/gtest/malloctrace/test_site_table.cpp @@ -0,0 +1,164 @@ +/* + * 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 "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; +using sap::Stack; + +static void init_random_randomly() { + os::init_random((int)os::elapsed_counter()); +} + +// convenience log. switch on if debugging tests. Don't use tty, plain stdio only. +#define LOG(...) { printf(__VA_ARGS__); printf("\n"); fflush(stdout); } +//#define LOG(...) + +static void fill_stack_randomly(sap::Stack* s) { + for (unsigned i = 0; i < 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 Stack* create_unique_stack_array(int num) { + Stack* random_stacks = NEW_C_HEAP_ARRAY(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; +} + +TEST_VM(MallocTrace, site_table_basics) { + + init_random_randomly(); + + SiteTable* table = create_site_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; + 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 + } + } + 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 + } + + 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; + 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 ++) { + Stack* stack = random_stacks + (os::random() % num_stacks); + table->add_site(stack, 1024); + ASSERT_EQ(table->invocations(), (uint64_t)i + 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/gtest/malloctrace/test_tracer.cpp b/test/hotspot/gtest/malloctrace/test_tracer.cpp new file mode 100644 index 00000000000..428611fd533 --- /dev/null +++ b/test/hotspot/gtest/malloctrace/test_tracer.cpp @@ -0,0 +1,164 @@ +/* + * 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 "malloctrace/mallocTrace.hpp" +#include "memory/allocation.hpp" +#include "runtime/os.hpp" +#include "utilities/debug.hpp" +#include "utilities/ostream.hpp" + +#include "concurrentTestRunner.inline.hpp" +#include "unittest.hpp" +#include + +#ifdef __GLIBC__ + +using sap::MallocTracer; + +static void init_random_randomly() { + os::init_random((int)os::elapsed_counter()); +} + +// convenience log. switch on if debugging tests. Don't use tty, plain stdio only. +//#define LOG(...) { printf(__VA_ARGS__); printf("\n"); fflush(stdout); } +#define LOG(...) + +static size_t random_size() { return os::random() % 123; } + +struct MyTestRunnable_raw_malloc : public TestRunnable { + void runUnitTest() const { + void* p = ::malloc(random_size()); + if (os::random() % 2) { + p = ::realloc(p, random_size()); + } + ::free(p); + } +}; + +struct MyTestRunnable_raw_memalign : public TestRunnable { + void runUnitTest() const { + void* p = NULL; + // note min alignment for posix_memalign is sizeof(void*) + size_t alignment = 1 << (4 + (os::random() % 4)); // 16 ... 256 + int rc = ::posix_memalign(&p, alignment, random_size()); + assert(rc == 0 && p != NULL && is_aligned(p, alignment), + "bad memalign result %d, " PTR_FORMAT, rc, p2i(p)); + ::free(p); + } +}; + +struct MyTestRunnable_os_malloc : public TestRunnable { + void runUnitTest() const { + void* p = os::malloc(random_size(), mtTest); + if (os::random() % 2) { + p = os::realloc(p, random_size(), mtTest); + } + os::free(p); + } +}; + +struct MyTestRunnable_mixed_all : public TestRunnable { + void runUnitTest() const { + char buf[128]; // truncation ok and expected + stringStream ss(buf, sizeof(buf)); + int chance = os::random() % 100; + if (chance < 20) { + (void) MallocTracer::disable(); + os::naked_short_sleep(1); + (void) MallocTracer::enable(true); + } else if (chance < 25) { + MallocTracer::print(&ss, false); + } else { + void* p = ::malloc(random_size()); + if (os::random() % 2) { + p = ::realloc(p, random_size()); + } + ::free(p); + } + } +}; + +// Mark to switch on tracing and restore the old state +class TraceRestorer { + const bool _restore; +public: + TraceRestorer() : _restore(MallocTracer::enable(true)) {} + ~TraceRestorer() { + if (_restore) { + MallocTracer::disable(); + } + } +}; + +TEST_VM(MallocTrace, tracer_os_malloc) { + init_random_randomly(); + TraceRestorer restorer; + MyTestRunnable_os_malloc my_runnable; + ConcurrentTestRunner testRunner(&my_runnable, 5, 3000); + testRunner.run(); +#ifdef LOG + MallocTracer::print(tty, false); +#endif +} + +TEST_VM(MallocTrace, tracer_raw_malloc) { + init_random_randomly(); + TraceRestorer restorer; + MyTestRunnable_raw_malloc my_runnable; + ConcurrentTestRunner testRunner(&my_runnable, 5, 3000); + testRunner.run(); +#ifdef LOG + MallocTracer::print(tty, false); +#endif +} + +TEST_VM(MallocTrace, tracer_raw_memalign) { + init_random_randomly(); + TraceRestorer restorer; + MyTestRunnable_raw_memalign my_runnable; + ConcurrentTestRunner testRunner(&my_runnable, 5, 2000); + testRunner.run(); +#ifdef LOG + MallocTracer::print(tty, false); +#endif +} + +TEST_VM(MallocTrace, tracer_mixed_all) { + init_random_randomly(); + TraceRestorer restorer; + MyTestRunnable_mixed_all my_runnable; + ConcurrentTestRunner testRunner(&my_runnable, 5, 3000); + testRunner.run(); +#ifdef LOG + MallocTracer::print(tty, false); +#endif +} + +#endif // __GLIBC__ + +#endif // LINUX diff --git a/test/hotspot/jtreg/runtime/malloctrace/MallocTraceDcmdTest.java b/test/hotspot/jtreg/runtime/malloctrace/MallocTraceDcmdTest.java new file mode 100644 index 00000000000..10fe4ffd3e5 --- /dev/null +++ b/test/hotspot/jtreg/runtime/malloctrace/MallocTraceDcmdTest.java @@ -0,0 +1,186 @@ +/* + * 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 + * @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 jdk.test.lib.helpers.ClassFileInstaller sun.hotspot.WhiteBox + * @run main/othervm/timeout=400 + * -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:-EnableMallocTrace + * 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"); + if (currentState) { + output.shouldContain("Tracing already active"); + } else { + output.shouldContain("Tracing activated"); + } + numSwitchedOn ++; + currentState = true; + } + + // System.malloctrace off + private static void testOff() throws Exception { + OutputAnalyzer output = testCommand("off"); + if (currentState) { + output.shouldContain("Tracing deactivated"); + } else { + output.shouldContain("Tracing was already deactivated"); + } + 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."); + } + } +}