diff --git a/Makefile b/Makefile index 981ee1f4d..e70949107 100644 --- a/Makefile +++ b/Makefile @@ -118,15 +118,28 @@ build/%: mkdir -p $@ build/$(LAUNCHER): src/launcher/* src/jattach/* src/fdtransfer.h - $(CC) $(CPPFLAGS) $(CFLAGS) -DPROFILER_VERSION=\"$(PROFILER_VERSION)\" -DSUPPRESS_OUTPUT -o $@ src/launcher/*.cpp src/jattach/*.c + $(CC) $(CPPFLAGS) $(CFLAGS) -static -DPROFILER_VERSION=\"$(PROFILER_VERSION)\" -o $@ src/launcher/*.cpp src/jattach/*.c strip $@ +PROFILER_FLAGS=-static-libgcc +ifeq ($(OS_TAG),linux-musl) + PROFILER_FLAGS+= -static-libstdc++ +endif + +# On x86_64 glibc (not musl), build against compat-glibc +# see https://centos.pkgs.org/7/centos-x86_64/compat-glibc-2.12-4.el7.centos.x86_64.rpm.html +ifneq ($(OS_TAG),linux-musl) + ifeq ($(ARCH),x86_64) + PROFILER_FLAGS+= -I /usr/lib/x86_64-redhat-linux6E/include -B /usr/lib/x86_64-redhat-linux6E/lib64/ + endif +endif + build/$(LIB_PROFILER): $(SOURCES) $(HEADERS) $(RESOURCES) $(JAVA_HELPER_CLASSES) ifeq ($(MERGE),true) for f in src/*.cpp; do echo '#include "'$$f'"'; done |\ - $(CXX) $(CPPFLAGS) $(CXXFLAGS) -DPROFILER_VERSION=\"$(PROFILER_VERSION)\" $(INCLUDES) -fPIC -shared -o $@ -xc++ - $(LIBS) + $(CXX) $(CPPFLAGS) $(CXXFLAGS) -DPROFILER_VERSION=\"$(PROFILER_VERSION)\" $(INCLUDES) -fPIC -shared $(PROFILER_FLAGS) -o $@ -xc++ - $(LIBS) else - $(CXX) $(CPPFLAGS) $(CXXFLAGS) -DPROFILER_VERSION=\"$(PROFILER_VERSION)\" $(INCLUDES) -fPIC -shared -o $@ $(SOURCES) $(LIBS) + $(CXX) $(CPPFLAGS) $(CXXFLAGS) -DPROFILER_VERSION=\"$(PROFILER_VERSION)\" $(INCLUDES) -fPIC -shared $(PROFILER_FLAGS) -o $@ $(SOURCES) $(LIBS) endif build/$(API_JAR): $(API_SOURCES) @@ -142,7 +155,7 @@ build/$(CONVERTER_JAR): $(CONVERTER_SOURCES) $(RESOURCES) $(RM) -r build/converter %.class: %.java - $(JAVAC) --source $(JAVA_TARGET) -target $(JAVA_TARGET) -Xlint:-options -g:none $^ + $(JAVAC) -source $(JAVA_TARGET) -target $(JAVA_TARGET) -Xlint:-options -g:none $^ test: all test/smoke-test.sh diff --git a/src/arguments.cpp b/src/arguments.cpp index 336182eba..ae3bd6b4a 100644 --- a/src/arguments.cpp +++ b/src/arguments.cpp @@ -109,6 +109,8 @@ static const Multiplier UNIVERSAL[] = {{'n', 1}, {'u', 1000}, {'m', 1000000}, {' // title=TITLE - FlameGraph title // minwidth=PCT - FlameGraph minimum frame width in percent // reverse - generate stack-reversed FlameGraph / Call tree +// includemm - inclue method modifiers +// recycle - recycle timeout timer (only: dump and status) // // It is possible to specify multiple dump options at the same time @@ -364,6 +366,24 @@ Error Arguments::parse(const char* args) { CASE("reverse") _reverse = true; + CASE("meminfolog") + _log_meminfo_on_dump = true; + + CASE("includemm") + _includemm = true; + + CASE("includeln") + _includeln = true; + + CASE("recycle") + _recycle = true; + + CASE("resettrace") + _reset_trace = true; + + CASE("dumpactive") + _dump_active = true; + DEFAULT() if (_unknown_arg == NULL) _unknown_arg = arg; } @@ -391,6 +411,13 @@ Error Arguments::parse(const char* args) { _action = ACTION_DUMP; } + if (_recycle && _action != ACTION_DUMP && _action != ACTION_STATUS) { + return Error("Recycle argument only valid for actions: dump and status"); + } + if (_recycle && _loop) { + return Error("Recycle argument doesn't support loop mode"); + } + return Error::OK; } diff --git a/src/arguments.h b/src/arguments.h index 763017679..5bd475523 100644 --- a/src/arguments.h +++ b/src/arguments.h @@ -166,6 +166,8 @@ class Arguments { bool _sched; bool _live; bool _fdtransfer; + bool _includemm; + bool _includeln; const char* _fdtransfer_path; int _style; CStack _cstack; @@ -184,6 +186,11 @@ class Arguments { const char* _title; double _minwidth; bool _reverse; + // Granulate Extra + bool _log_meminfo_on_dump; + bool _recycle; + bool _reset_trace; + bool _dump_active; Arguments(bool persistent = false) : _buf(NULL), @@ -230,7 +237,13 @@ class Arguments { _end(NULL), _title(NULL), _minwidth(0), - _reverse(false) { + _reverse(false), + _log_meminfo_on_dump(false), + _includemm(false), + _includeln(false), + _recycle(false), + _reset_trace(false), + _dump_active(false) { } ~Arguments(); diff --git a/src/codeCache.cpp b/src/codeCache.cpp index 3465b9036..9e00accbd 100644 --- a/src/codeCache.cpp +++ b/src/codeCache.cpp @@ -41,6 +41,10 @@ size_t NativeFunc::usedMemory(const char* name) { CodeCache::CodeCache(const char* name, short lib_index, const void* min_address, const void* max_address) { _name = NativeFunc::create(name, -1); + char *tmp = (char*)malloc(strlen(name) + 3); + snprintf(tmp, strlen(name) + 3, "(%s)", name); + _lib_symbol = NativeFunc::create(tmp, -1); + free(tmp); _lib_index = lib_index; _min_address = min_address; _max_address = max_address; @@ -63,6 +67,7 @@ CodeCache::~CodeCache() { for (int i = 0; i < _count; i++) { NativeFunc::destroy(_blobs[i]._name); } + NativeFunc::destroy(_lib_symbol); NativeFunc::destroy(_name); delete[] _blobs; free(_dwarf_table); @@ -153,7 +158,8 @@ const char* CodeCache::binarySearch(const void* address) { if (low > 0 && (_blobs[low - 1]._start == _blobs[low - 1]._end || _blobs[low - 1]._end == address)) { return _blobs[low - 1]._name; } - return _name; + + return _lib_symbol; } const void* CodeCache::findSymbol(const char* name) { diff --git a/src/codeCache.h b/src/codeCache.h index 18a6cbf41..f4192c9cd 100644 --- a/src/codeCache.h +++ b/src/codeCache.h @@ -87,6 +87,7 @@ class FrameDesc; class CodeCache { protected: char* _name; + char* _lib_symbol; short _lib_index; const void* _min_address; const void* _max_address; diff --git a/src/frameName.cpp b/src/frameName.cpp index 08e1bfabd..9e01ae9c5 100644 --- a/src/frameName.cpp +++ b/src/frameName.cpp @@ -28,6 +28,24 @@ static inline bool isDigit(char c) { } +// Based on: https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#:~:text=Table%C2%A04.5.%C2%A0Method%20access%20and%20property%20flags +// Good practice order from: https://checkstyle.sourceforge.io/config_modifier.html#ModifierOrder +const static std::pair access_flags [] = { + std::make_pair(0x0001, "public"), + std::make_pair(0x0002, "private"), + std::make_pair(0x0004, "protected"), + std::make_pair(0x0400, "abstract"), + std::make_pair(0x0008, "static"), + std::make_pair(0x0010, "final"), + std::make_pair(0x0020, "synchronized"), + std::make_pair(0x0100, "native"), + std::make_pair(0x0800, "strict"), + std::make_pair(0x0040, "bridge"), + std::make_pair(0x0080, "varargs"), + std::make_pair(0x1000, "synthetic"), +}; + + Matcher::Matcher(const char* pattern) { if (pattern[0] == '*') { _type = MATCH_ENDS_WITH; @@ -95,7 +113,8 @@ FrameName::FrameName(Arguments& args, int style, int epoch, Mutex& thread_names_ { // Require printf to use standard C format regardless of system locale _saved_locale = uselocale(newlocale(LC_NUMERIC_MASK, "C", (locale_t)0)); - + _includemm = args._includemm; + _includeln = args._includeln; buildFilter(_include, args._buf, args._include); buildFilter(_exclude, args._buf, args._exclude); @@ -133,7 +152,7 @@ const char* FrameName::decodeNativeSymbol(const char* name) { char* demangled = Demangle::demangle(name); if (demangled != NULL) { if (lib_name != NULL) { - _str.assign(lib_name).append("`").append(demangled); + _str.assign(demangled).append(" (").append(lib_name).append(")"); } else { _str.assign(demangled); } @@ -143,7 +162,7 @@ const char* FrameName::decodeNativeSymbol(const char* name) { } if (lib_name != NULL) { - return _str.assign(lib_name).append("`").append(name).c_str(); + return _str.assign(name).append(" (").append(lib_name).append(")").c_str(); } else { return name; } @@ -167,6 +186,9 @@ void FrameName::javaMethodName(jmethodID method) { char* class_name = NULL; char* method_name = NULL; char* method_sig = NULL; + jvmtiLineNumberEntry* line_number_table = NULL; + jint entry_count = 0; + jint modifiers = 0; jvmtiEnv* jvmti = VM::jvmti(); jvmtiError err; @@ -176,7 +198,24 @@ void FrameName::javaMethodName(jmethodID method) { (err = jvmti->GetClassSignature(method_class, &class_name, NULL)) == 0) { // Trim 'L' and ';' off the class descriptor like 'Ljava/lang/Object;' javaClassName(class_name + 1, strlen(class_name) - 2, _style); + if (_includemm) { + jvmti->GetMethodModifiers(method, &modifiers); + std::string modifiers_to_append = ""; + for (int i=0; i<(sizeof(access_flags) / sizeof(access_flags[0])); i++) { + if (modifiers & access_flags[i].first) { + modifiers_to_append.append(access_flags[i].second + " "); + } + } + _str.insert(0, modifiers_to_append); + } _str.append(".").append(method_name); + if (_includeln) { + if (jvmti->GetLineNumberTable(method, &entry_count, &line_number_table) == 0) { + char buf[32]; + sprintf(buf, ":%d", line_number_table[0].line_number); + _str.append(buf); + } + } if (_style & STYLE_SIGNATURES) { if (_style & STYLE_NO_SEMICOLON) { for (char* s = method_sig; *s; s++) { diff --git a/src/frameName.h b/src/frameName.h index 26d8e837c..5139e4731 100644 --- a/src/frameName.h +++ b/src/frameName.h @@ -75,6 +75,8 @@ class FrameName { Mutex& _thread_names_lock; ThreadMap& _thread_names; locale_t _saved_locale; + bool _includemm; + bool _includeln; void buildFilter(std::vector& vector, const char* base, int offset); const char* decodeNativeSymbol(const char* name); diff --git a/src/launcher/fdtransferServer.h b/src/launcher/fdtransferServer.h index d234e8cc8..07ce4ff3e 100644 --- a/src/launcher/fdtransferServer.h +++ b/src/launcher/fdtransferServer.h @@ -36,7 +36,7 @@ class FdTransferServer { public: static bool supported() { return true; } - static bool runOnce(int pid, const char *path); + static bool runOnce(int pid, const char *path, unsigned int timeout); static bool runLoop(const char *path); }; @@ -46,7 +46,7 @@ class FdTransferServer { public: static bool supported() { return false; } - static bool runOnce(int pid, const char *path) { return false; } + static bool runOnce(int pid, const char *path, unsigned int timeout) { return false; } static bool runLoop(const char *path) { return false; } }; diff --git a/src/launcher/fdtransferServer_linux.cpp b/src/launcher/fdtransferServer_linux.cpp index 03cb50044..193e32f5a 100644 --- a/src/launcher/fdtransferServer_linux.cpp +++ b/src/launcher/fdtransferServer_linux.cpp @@ -260,7 +260,7 @@ bool FdTransferServer::sendFd(int fd, struct fd_response *resp, size_t resp_size return true; } -bool FdTransferServer::runOnce(int pid, const char *path) { +bool FdTransferServer::runOnce(int pid, const char *path, unsigned int timeout) { // get its nspid prior to moving to its PID namespace. int nspid; uid_t target_uid; @@ -287,7 +287,7 @@ bool FdTransferServer::runOnce(int pid, const char *path) { } } - if (!bindServer(&sun, addrlen, 30)) { + if (!bindServer(&sun, addrlen, timeout)) { return false; } @@ -298,6 +298,9 @@ bool FdTransferServer::runOnce(int pid, const char *path) { // CLONE_NEWPID affects children only - so we fork here. if (fork() == 0) { + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); return acceptPeer(&nspid) && serveRequests(nspid); } else { // Exit now, let our caller continue. diff --git a/src/launcher/main.cpp b/src/launcher/main.cpp index ea3a64506..c2455e964 100644 --- a/src/launcher/main.cpp +++ b/src/launcher/main.cpp @@ -51,6 +51,10 @@ static const char USAGE_STRING[] = " list list profiling events supported by the target JVM\n" " collect collect profile for the specified period of time\n" " and then stop (default action)\n" + " fdtransfer start fdtransfer to serve perf requests on behalf of profiled process\n" + " jattach invoke jattach directly; requires --jattach-cmd,\n" + " ignores all arguments except --lib-path\n" + "\n" "Options:\n" " -e event profiling event: cpu|alloc|lock|cache-misses etc.\n" " -d duration run profiling for seconds\n" @@ -87,6 +91,11 @@ static const char USAGE_STRING[] = " --jfrsync config synchronize profiler with JFR recording\n" " --fdtransfer use fdtransfer to serve perf requests\n" " from the non-privileged target\n" + " --jattach-cmd string\n" + " arguments to use with jattach action\n" + " -L|--lib-path string\n" + " path to async-profiler's shared lib\n" + " --fd-path string socket path for fdtransfer to bind to\n" "\n" " is a numeric process ID of the target JVM\n" " or 'jps' keyword to find running JVM automatically\n" @@ -97,6 +106,7 @@ static const char USAGE_STRING[] = " " APP_BINARY " stop -o flat jps\n" " " APP_BINARY " -d 5 -e alloc MyAppName\n"; +static const unsigned int DEFAULT_FDTRANSFER_TIMEOUT = 30; extern "C" int jattach(int pid, int argc, const char** argv); @@ -170,6 +180,10 @@ class String { return strcmp(_str, other._str) == 0; } + bool operator!=(const String& other) const { + return !(*this == other); + } + String& operator<<(const char* tail) { size_t len = strlen(_str); _str = (char*)realloc(_str, len + strlen(tail) + 1); @@ -205,11 +219,16 @@ class String { static String action = "collect"; +static const String kEmpty; +static const String kJattachLoad = "load"; +static const String kJattachJcmd = "jcmd"; static String file, logfile, output, params, format, fdtransfer, libpath; static bool use_tmp_file = false; static int duration = 60; static int pid = 0; static volatile unsigned long long end_time; +// gprofiler-specific: holds timeout value for fdtransfer command +static unsigned int fdtransfer_timeout = DEFAULT_FDTRANSFER_TIMEOUT; static void sigint_handler(int sig) { end_time = 0; @@ -314,7 +333,7 @@ static int jps(const char* cmd, const char* app_name = NULL) { return pid; } -static void run_fdtransfer(int pid, String& fdtransfer) { +static void run_fdtransfer(int pid, String& fdtransfer, unsigned int timeout) { if (!FdTransferServer::supported() || fdtransfer == "") return; pid_t child = fork(); @@ -323,7 +342,7 @@ static void run_fdtransfer(int pid, String& fdtransfer) { } if (child == 0) { - exit(FdTransferServer::runOnce(pid, fdtransfer.str()) ? 0 : 1); + exit(FdTransferServer::runOnce(pid, fdtransfer.str(), timeout) ? 0 : 1); } else { int ret = wait_for_exit(child); if (ret != 0) { @@ -332,26 +351,31 @@ static void run_fdtransfer(int pid, String& fdtransfer) { } } -static void run_jattach(int pid, String& cmd) { +static void run_jattach(int pid, const String& verb, String& cmd) { pid_t child = fork(); if (child == -1) { error("fork failed", errno); } if (child == 0) { - const char* argv[] = {"load", libpath.str(), libpath.str()[0] == '/' ? "true" : "false", cmd.str()}; - exit(jattach(pid, 4, argv)); + if (verb == kJattachLoad) { + const char* args[] = {kJattachLoad.str(), libpath.str(), libpath.str()[0] == '/' ? "true" : "false", cmd.str()}; + exit(jattach(pid, 4, args)); + } else if (verb == kJattachJcmd) { + const char* args[] = {kJattachJcmd.str(), cmd.str()}; + exit(jattach(pid, 2, args)); + } else { exit (1); } } else { int ret = wait_for_exit(child); if (ret != 0) { if (WEXITSTATUS(ret) == 255) { fprintf(stderr, "Target JVM failed to load %s\n", libpath.str()); } - print_file(logfile, STDERR_FILENO); + if (logfile != kEmpty) print_file(logfile, STDERR_FILENO); exit(WEXITSTATUS(ret)); } - print_file(logfile, STDERR_FILENO); + if (logfile != kEmpty) print_file(logfile, STDERR_FILENO); if (use_tmp_file) print_file(file, STDOUT_FILENO); } } @@ -359,11 +383,13 @@ static void run_jattach(int pid, String& cmd) { int main(int argc, const char** argv) { Args args(argc, argv); + String jattach_cmd; while (args.hasNext()) { String arg = args.next(); if (arg == "start" || arg == "resume" || arg == "stop" || arg == "dump" || arg == "check" || - arg == "status" || arg == "meminfo" || arg == "list" || arg == "collect") { + arg == "status" || arg == "meminfo" || arg == "list" || arg == "collect" || + arg == "fdtransfer" || arg == "jattach" || arg == "jcmd") { action = arg; } else if (arg == "-h" || arg == "--help") { @@ -454,14 +480,24 @@ int main(int argc, const char** argv) { } else if (arg == "--timeout" || arg == "--loop") { params << "," << (arg.str() + 2) << "=" << args.next(); + if (action == "collect") action = "start"; + } else if (arg == "--fdtransfer-timeout") { + fdtransfer_timeout = atoi(args.next()); + + } else if (arg == "--fd-path") { + fdtransfer = String(args.next()); + } else if (arg == "--fdtransfer") { char buf[64]; snprintf(buf, sizeof(buf), "@async-profiler-%d-%08x", getpid(), (unsigned int)time_micros()); fdtransfer = buf; params << ",fdtransfer=" << fdtransfer; + } else if (arg == "--jattach-cmd") { + jattach_cmd = String(args.next()); + } else if (arg.str()[0] >= '0' && arg.str()[0] <= '9' && pid == 0) { pid = atoi(arg.str()); @@ -474,6 +510,9 @@ int main(int argc, const char** argv) { // The last argument is the application name as it would appear in the jps tool pid = jps("jps -J-XX:+PerfDisableSharedMem", arg.str()); + } else if (arg == "-L" || arg == "--lib-path") { + libpath = String(args.next()); + } else { fprintf(stderr, "Unrecognized option: %s\n", arg.str()); return 1; @@ -485,12 +524,20 @@ int main(int argc, const char** argv) { return 1; } - setup_output_files(pid); - setup_lib_path(); + if (jattach_cmd == kEmpty) { + setup_output_files(pid); + } else if (params != kEmpty) { + fprintf(stderr, "Argument --jattach-cmd was given, need no other parameters\n"); + return 1; + } + + if (libpath == kEmpty) { + setup_lib_path(); + } if (action == "collect") { - run_fdtransfer(pid, fdtransfer); - run_jattach(pid, String("start,file=") << file << "," << output << format << params << ",log=" << logfile); + run_fdtransfer(pid, fdtransfer, 0); + run_jattach(pid, kJattachLoad, String("start,file=") << file << "," << output << format << params << ",log=" << logfile); fprintf(stderr, "Profiling for %d seconds\n", duration); end_time = time_micros() + duration * 1000000ULL; @@ -508,10 +555,31 @@ int main(int argc, const char** argv) { signal(SIGINT, SIG_DFL); fprintf(stderr, "Done\n"); - run_jattach(pid, String("stop,file=") << file << "," << output << format << ",log=" << logfile); + run_jattach(pid, kJattachLoad, String("stop,file=") << file << "," << output << format << ",log=" << logfile); + } else if (action == "fdtransfer") { + if (params != kEmpty) { + fprintf(stderr, "Action fdtransfer was given, all parameters are to be passed with --jattach-cmd\n"); + return 1; + } + run_fdtransfer(pid, fdtransfer, fdtransfer_timeout); + + } else if (action == "jattach") { + if (jattach_cmd == kEmpty) { + fprintf(stderr, "Action jattach was given, missing required --jattach-cmd argument\n"); + return 1; + } + run_jattach(pid, kJattachLoad, jattach_cmd); + + } else if (action == "jcmd") { + if (jattach_cmd == kEmpty) { + fprintf(stderr, "Action jcmd was given, missing required --jattach-cmd argument\n"); + return 1; + } + run_jattach(pid, kJattachJcmd, jattach_cmd); + } else { - if (action == "start" || action == "resume") run_fdtransfer(pid, fdtransfer); - run_jattach(pid, String(action) << ",file=" << file << "," << output << format << params << ",log=" << logfile); + if (action == "start" || action == "resume") run_fdtransfer(pid, fdtransfer, 0); + run_jattach(pid, kJattachLoad, String(action) << ",file=" << file << "," << output << format << params << ",log=" << logfile); } return 0; diff --git a/src/profiler.cpp b/src/profiler.cpp index a9b3351e0..2a9583fe2 100644 --- a/src/profiler.cpp +++ b/src/profiler.cpp @@ -252,8 +252,7 @@ const char* Profiler::getLibraryName(const char* native_symbol) { if (lib_index >= 0 && lib_index < _native_libs.count()) { const char* s = _native_libs[lib_index]->name(); if (s != NULL) { - const char* p = strrchr(s, '/'); - return p != NULL ? p + 1 : s; + return s; } } return NULL; @@ -266,15 +265,24 @@ CodeCache* Profiler::findJvmLibrary(const char* lib_name) { CodeCache* Profiler::findLibraryByName(const char* lib_name) { const size_t lib_name_len = strlen(lib_name); const int native_lib_count = _native_libs.count(); + int basename_match_i = -1; for (int i = 0; i < native_lib_count; i++) { const char* s = _native_libs[i]->name(); if (s != NULL) { - const char* p = strrchr(s, '/'); - if (p != NULL && strncmp(p + 1, lib_name, lib_name_len) == 0) { + if (strncmp(s, lib_name, lib_name_len) == 0) { return _native_libs[i]; } + if (basename_match_i < 0) { + const char* p = strrchr(s, '/'); + if (p != NULL && strncmp(p + 1, lib_name, lib_name_len) == 0) { + basename_match_i = i; + } + } } } + if (basename_match_i >= 0) { + return _native_libs[basename_match_i]; + } return NULL; } @@ -1191,11 +1199,15 @@ Error Profiler::flushJfr() { Error Profiler::dump(std::ostream& out, Arguments& args) { MutexLocker ml(_state_lock); - if (_state != IDLE && _state != RUNNING) { + // in dump-active make sure profiler is not idle + if ((_state != IDLE || args._dump_active) && _state != RUNNING) { return Error("Profiler has not started"); } if (_state == RUNNING) { + if (args._recycle && args._timeout) { + recycleTimeoutTimer(args); + } updateJavaThreadNames(); updateNativeThreadNames(); } @@ -1223,6 +1235,9 @@ Error Profiler::dump(std::ostream& out, Arguments& args) { default: return Error("No output format selected"); } + if (args._reset_trace) { + resetTrace(); + } return Error::OK; } @@ -1506,7 +1521,8 @@ void Profiler::stopTimer() { void Profiler::timerLoop(void* timer_id) { u64 current_micros = OS::micros(); - u64 stop_micros = _stop_time * 1000000ULL; + time_t stop_time = _stop_time; + u64 stop_micros = stop_time * 1000000ULL; u64 sleep_until = _jfr.active() ? current_micros + 1000000 : stop_micros; while (true) { @@ -1517,6 +1533,11 @@ void Profiler::timerLoop(void* timer_id) { // timeout not reached } if (_timer_id != timer_id) return; + if (_stop_time != stop_time) { + // handle timeout recycling + stop_time = _stop_time; + stop_micros = stop_time * 1000000ULL; + } } if ((current_micros = OS::micros()) >= stop_micros) { @@ -1538,6 +1559,27 @@ void Profiler::timerThreadEntry(jvmtiEnv* jvmti, JNIEnv* jni, void* arg) { instance()->timerLoop(arg); } +void Profiler::resetTrace() { + // Reset traces for next collection round, to get comparable outcomes from dump. + // Must be called under _state_lock mutex + _total_samples = 0; + memset(_failures, 0, sizeof(_failures)); + + // Reset call trace storage + lockAll(); + _call_trace_storage.clear(); + unlockAll(); +} + +void Profiler::recycleTimeoutTimer(Arguments& args) { + MutexLocker ml(_timer_lock); + if (_timer_id != NULL) { + time_t current_time = time(NULL); + _stop_time = addTimeout(current_time, args._timeout); + _timer_lock.notify(); + } +} + Error Profiler::runInternal(Arguments& args, std::ostream& out) { switch (args._action) { case ACTION_START: @@ -1546,7 +1588,6 @@ Error Profiler::runInternal(Arguments& args, std::ostream& out) { if (error) { return error; } - out << "Profiling started\n"; break; } case ACTION_STOP: { @@ -1555,13 +1596,16 @@ Error Profiler::runInternal(Arguments& args, std::ostream& out) { if (error) { return error; } - out << "Profiling stopped after " << uptime() << " seconds. No dump options specified\n"; break; } // Fall through } case ACTION_DUMP: { Error error = dump(out, args); + if (args._log_meminfo_on_dump) { + logUsedMemory(); + } + if (error) { return error; } @@ -1579,6 +1623,9 @@ Error Profiler::runInternal(Arguments& args, std::ostream& out) { MutexLocker ml(_state_lock); if (_state == RUNNING) { out << "Profiling is running for " << uptime() << " seconds\n"; + if (args._recycle && args._timeout) { + recycleTimeoutTimer(args); + } } else { out << "Profiler is not active\n"; } @@ -1679,3 +1726,9 @@ void Profiler::shutdown(Arguments& args) { _state = TERMINATED; } + +void Profiler::logUsedMemory() { + std::ostringstream stringStream; + printUsedMemory(stringStream); + Log::info("%s", stringStream.str().c_str()); +} diff --git a/src/profiler.h b/src/profiler.h index dc535725d..92102971d 100644 --- a/src/profiler.h +++ b/src/profiler.h @@ -144,6 +144,8 @@ class Profiler { void stopTimer(); void timerLoop(void* timer_id); static void timerThreadEntry(jvmtiEnv* jvmti, JNIEnv* jni, void* arg); + void resetTrace(); + void recycleTimeoutTimer(Arguments& args); void lockAll(); void unlockAll(); @@ -200,6 +202,7 @@ class Profiler { Error stop(); Error flushJfr(); Error dump(std::ostream& out, Arguments& args); + void logUsedMemory(); void printUsedMemory(std::ostream& out); void switchThreadEvents(jvmtiEventMode mode); int convertNativeTrace(int native_frames, const void** callchain, ASGCT_CallFrame* frames);