diff --git a/make/CompileJavaModules.gmk b/make/CompileJavaModules.gmk index 4f33b17247a..72c7391d45d 100644 --- a/make/CompileJavaModules.gmk +++ b/make/CompileJavaModules.gmk @@ -509,6 +509,15 @@ jdk.localedata_COPY += _dict _th # Exclude BreakIterator classes that are just used in compile process to generate # data files and shouldn't go in the product jdk.localedata_EXCLUDE_FILES += sun/text/resources/ext/BreakIteratorRules_th.java +################################################################################ + +# There is an issue in sjavac that triggers a warning in jdk.jfr that isn't +# triggered without sjavac. +ifeq ($(ENABLE_SJAVAC), yes) + jdk.jfr_SETUP := GENERATE_JDKBYTECODE_NOWARNINGS +endif +jdk.jfr_COPY := .xsd .xml .dtd +jdk.jfr_ADD_JAVAC_FLAGS := -XDstringConcat=inline -Xlint:-exports ################################################################################ # If this is an imported module that has prebuilt classes, only compile diff --git a/make/autoconf/hotspot.m4 b/make/autoconf/hotspot.m4 index 1cca6d883d4..7df870379fb 100644 --- a/make/autoconf/hotspot.m4 +++ b/make/autoconf/hotspot.m4 @@ -26,7 +26,7 @@ # All valid JVM features, regardless of platform VALID_JVM_FEATURES="compiler1 compiler2 zero minimal dtrace jvmti jvmci \ graal vm-structs jni-check services management cmsgc g1gc parallelgc serialgc nmt cds \ - static-build link-time-opt aot" + static-build link-time-opt aot jfr" # All valid JVM variants VALID_JVM_VARIANTS="server client minimal core zero custom" @@ -309,6 +309,11 @@ AC_DEFUN_ONCE([HOTSPOT_SETUP_JVM_FEATURES], AC_MSG_ERROR([Specified JVM feature 'cmsgc' requires feature 'serialgc']) fi + # Enable JFR by default, except on linux-sparcv9 and on minimal. + if test "x$OPENJDK_TARGET_OS" != xlinux || test "x$OPENJDK_TARGET_CPU" != xsparcv9; then + NON_MINIMAL_FEATURES="$NON_MINIMAL_FEATURES jfr" + fi + # Turn on additional features based on other parts of configure if test "x$INCLUDE_DTRACE" = "xtrue"; then JVM_FEATURES="$JVM_FEATURES dtrace" @@ -396,7 +401,7 @@ AC_DEFUN_ONCE([HOTSPOT_SETUP_JVM_FEATURES], NON_MINIMAL_FEATURES="$NON_MINIMAL_FEATURES cds" fi - # Enable default features depending on variant. + # Enable features depending on variant. JVM_FEATURES_server="compiler1 compiler2 $NON_MINIMAL_FEATURES $JVM_FEATURES $JVM_FEATURES_jvmci $JVM_FEATURES_aot $JVM_FEATURES_graal" JVM_FEATURES_client="compiler1 $NON_MINIMAL_FEATURES $JVM_FEATURES $JVM_FEATURES_jvmci" JVM_FEATURES_core="$NON_MINIMAL_FEATURES $JVM_FEATURES" diff --git a/make/autoconf/libraries.m4 b/make/autoconf/libraries.m4 index 1d33778e2ad..5bf8569ab2f 100644 --- a/make/autoconf/libraries.m4 +++ b/make/autoconf/libraries.m4 @@ -130,7 +130,7 @@ AC_DEFUN_ONCE([LIB_SETUP_LIBRARIES], if test "x$OPENJDK_TARGET_OS" = xsolaris; then BASIC_JVM_LIBS="$BASIC_JVM_LIBS -lsocket -lsched -ldoor -ldemangle -lnsl \ - -lrt" + -lrt -lkstat" BASIC_JVM_LIBS="$BASIC_JVM_LIBS $LIBCXX_JVM" fi diff --git a/make/common/Modules.gmk b/make/common/Modules.gmk index 9bc76095abe..f4646f16efd 100644 --- a/make/common/Modules.gmk +++ b/make/common/Modules.gmk @@ -59,7 +59,9 @@ BOOT_MODULES += \ java.security.sasl \ java.xml \ jdk.internal.vm.ci \ + jdk.jfr \ jdk.management \ + jdk.management.jfr \ jdk.management.agent \ jdk.net \ jdk.sctp \ @@ -152,6 +154,7 @@ DOCS_MODULES += \ jdk.jdeps \ jdk.jdi \ jdk.jdwp.agent \ + jdk.jfr \ jdk.jlink \ jdk.jsobject \ jdk.jshell \ @@ -159,6 +162,7 @@ DOCS_MODULES += \ jdk.localedata \ jdk.management \ jdk.management.agent \ + jdk.management.jfr \ jdk.naming.dns \ jdk.naming.rmi \ jdk.net \ diff --git a/make/copy/Copy-jdk.jfr.gmk b/make/copy/Copy-jdk.jfr.gmk new file mode 100644 index 00000000000..dc752d3a44f --- /dev/null +++ b/make/copy/Copy-jdk.jfr.gmk @@ -0,0 +1,46 @@ +# +# Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# 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 CopyCommon.gmk + +################################################################################ + +$(eval $(call SetupCopyFiles, COPY_JFR_METADATA, \ + SRC := $(TOPDIR)/src/hotspot/share/jfr/metadata, \ + DEST := $(JDK_OUTPUTDIR)/modules/jdk.jfr/jdk/jfr/internal/types, \ + FILES := metadata.xml \ +)) + +TARGETS += $(COPY_JFR_METADATA) + +JFR_CONF_DIR := $(TOPDIR)/src/jdk.jfr/share/conf/jfr +$(eval $(call SetupCopyFiles, COPY_JFR_CONF, \ + DEST := $(LIB_DST_DIR)/jfr, \ + FILES := $(wildcard $(JFR_CONF_DIR)/*.jfc), \ + FLATTEN := true, \ +)) +TARGETS += $(COPY_JFR_CONF) + +################################################################################ diff --git a/make/hotspot/gensrc/GenerateSources.gmk b/make/hotspot/gensrc/GenerateSources.gmk index 644bb4f5cb1..cdcf644c148 100644 --- a/make/hotspot/gensrc/GenerateSources.gmk +++ b/make/hotspot/gensrc/GenerateSources.gmk @@ -38,6 +38,7 @@ include HotspotCommon.gmk include gensrc/GensrcAdlc.gmk include gensrc/GensrcDtrace.gmk include gensrc/GensrcJvmti.gmk +include gensrc/GensrcJfr.gmk $(eval $(call IncludeCustomExtension, hotspot/gensrc/GenerateSources.gmk)) diff --git a/make/hotspot/gensrc/GensrcJfr.gmk b/make/hotspot/gensrc/GensrcJfr.gmk new file mode 100644 index 00000000000..61706113922 --- /dev/null +++ b/make/hotspot/gensrc/GensrcJfr.gmk @@ -0,0 +1,94 @@ +# +# Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# 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. +# + +$(eval $(call IncludeCustomExtension, hotspot/gensrc/GensrcJfr.gmk)) + +################################################################################ +# Build tools needed for the Jfr source code generation + +JFR_TOOLS_SRCDIR := $(TOPDIR)/src/hotspot/share/jfr/metadata +JFR_TOOLS_OUTPUTDIR := $(JVM_VARIANT_OUTPUTDIR)/tools/jfr + +$(eval $(call SetupJavaCompiler, GENERATE_JFRBYTECODE, \ + JAVAC := $(JAVAC), \ + FLAGS := $(DISABLE_WARNINGS), \ + SERVER_DIR := $(SJAVAC_SERVER_DIR), \ + SERVER_JVM := $(SJAVAC_SERVER_JAVA), \ + DISABLE_SJAVAC := true, \ +)) + +$(eval $(call SetupJavaCompilation, BUILD_JFR_TOOLS, \ + SETUP := GENERATE_JFRBYTECODE, \ + SRC := $(JFR_TOOLS_SRCDIR), \ + INCLUDE_FILES := GenerateJfrFiles.java, \ + BIN := $(JFR_TOOLS_OUTPUTDIR), \ +)) + +TOOL_JFR_GEN := $(JAVA_SMALL) -cp $(JFR_TOOLS_OUTPUTDIR) GenerateJfrFiles + +################################################################################ +# Setup make rules for Jfr file file generation. +# +# Parameter 1 is the name of the rule. This name is used as variable prefix, +# and the targets generated are listed in a variable by that name. This name is +# also used as the name of the output file. +# +# Remaining parameters are named arguments. These include: +# XML_FILE -- The input source file to use +# XSD_FILE -- The input schema for validation +# OUTPUT_DIR -- The directory to put the generated file in +SetupJfrGeneration = $(NamedParamsMacroTemplate) +define SetupJfrGenerationBody + $$($1_OUTPUT_DIR)/$1: $$($1_XML_FILE) $$($1_XSD_FILE) $$(BUILD_JFR_TOOLS) + $$(call LogInfo, Generating $$(@F)) + $$(call MakeDir, $$(@D)) + $$(call ExecuteWithLog, $$@, $$(TOOL_JFR_GEN) $$($1_XML_FILE) $$($1_XSD_FILE) $$($1_OUTPUT_DIR)) + test -f $$@ + + TARGETS += $$($1_OUTPUT_DIR)/$1 + +endef + +################################################################################ +# Create files in gensrc/jfrfiles + +JFR_OUTPUTDIR := $(JVM_VARIANT_OUTPUTDIR)/gensrc/jfrfiles +JFR_SRCDIR := $(TOPDIR)/src/hotspot/share/jfr/metadata + +METADATA_XML ?= $(JFR_SRCDIR)/metadata.xml +METADATA_XSD ?= $(JFR_SRCDIR)/metadata.xsd + +# Changing these will trigger a rebuild of generated jfr files. +JFR_DEPS += \ + $(METADATA_XML) \ + $(METADATA_XSD) \ + # + +# our generator will generate all files in one go, so only need to setup one target rule +$(eval $(call SetupJfrGeneration, jfrEventClasses.hpp, \ + XML_FILE := $(METADATA_XML), \ + XSD_FILE := $(METADATA_XSD), \ + OUTPUT_DIR := $(JFR_OUTPUTDIR), \ +)) \ No newline at end of file diff --git a/make/hotspot/gensrc/GensrcJvmti.gmk b/make/hotspot/gensrc/GensrcJvmti.gmk index 70a35e1989c..d48981c5a3f 100644 --- a/make/hotspot/gensrc/GensrcJvmti.gmk +++ b/make/hotspot/gensrc/GensrcJvmti.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -50,7 +50,7 @@ TOOL_JVMTI_GEN := $(JAVA_SMALL) -cp $(JVMTI_TOOLS_OUTPUTDIR) jvmtiGen TOOL_JVMTI_ENV_FILL := $(JAVA_SMALL) -cp $(JVMTI_TOOLS_OUTPUTDIR) jvmtiEnvFill ################################################################################ -# Setup make rules for an xml transform for jvmti/trace file generation. +# Setup make rules for an xml transform for jvmti file generation. # # Parameter 1 is the name of the rule. This name is used as variable prefix, # and the targets generated are listed in a variable by that name. This name is @@ -126,48 +126,3 @@ ifeq ($(JVM_VARIANT), $(firstword $(JVM_VARIANTS))) TARGETS += $(COPY_JVMTI_H) endif - -################################################################################ -# Create trace files in gensrc/tracefiles - -TRACE_OUTPUTDIR := $(JVM_VARIANT_OUTPUTDIR)/gensrc/tracefiles -TRACE_SRCDIR := $(TOPDIR)/src/hotspot/share/trace - -# Append list of XSL files to search (might have been set by custom extensions) -TRACE_XSL_FILES += $(wildcard $(TRACE_SRCDIR)/*.xsl) - -TRACE_XML ?= $(TRACE_SRCDIR)/trace.xml - -# Changing these will trigger a rebuild of generated trace files. -TRACE_DEPS += \ - $(TRACE_XML) \ - $(TRACE_SRCDIR)/tracetypes.xml \ - $(TRACE_SRCDIR)/tracerelationdecls.xml \ - $(TRACE_SRCDIR)/traceevents.xml \ - $(TRACE_SRCDIR)/trace.dtd \ - $(TRACE_SRCDIR)/xinclude.mod \ - # - -# Setup rule for generating a trace file -# -# $1 is generated source file name in $(TRACE_OUTPUTDIR) -define SetupTraceGeneration - $$(eval $$(call SetupXslTransform, $1, \ - XML_FILE := $$(TRACE_XML), \ - XSL_FILE := $$(firstword $$(filter %/$$(basename $1).xsl, $$(TRACE_XSL_FILES))), \ - OUTPUT_DIR := $$(TRACE_OUTPUTDIR), \ - DEPS := $$(TRACE_DEPS), \ - )) -endef - -# Append files to generated (might have been set by custom extensions) -TRACE_GENSRC_FILES += \ - traceEventClasses.hpp \ - traceEventIds.hpp \ - traceTypes.hpp \ - # - -# Call SetupTraceGeneration for all trace gensrc files -$(foreach tracefile, $(TRACE_GENSRC_FILES), \ - $(eval $(call SetupTraceGeneration, $(tracefile))) \ -) diff --git a/make/hotspot/lib/JvmFeatures.gmk b/make/hotspot/lib/JvmFeatures.gmk index 25149c9c13e..caf30c9dc8b 100644 --- a/make/hotspot/lib/JvmFeatures.gmk +++ b/make/hotspot/lib/JvmFeatures.gmk @@ -154,6 +154,12 @@ ifneq ($(call check-jvm-feature, serialgc), true) # If serial is disabled, we cannot use serial as OldGC in parallel JVM_EXCLUDE_FILES += psMarkSweep.cpp psMarkSweepDecorator.cpp endif + +ifneq ($(call check-jvm-feature, jfr), true) + JVM_CFLAGS_FEATURES += -DINCLUDE_JFR=0 + JVM_EXCLUDE_PATTERNS += jfr +endif + ################################################################################ ifeq ($(call check-jvm-feature, link-time-opt), true) diff --git a/make/hotspot/src/classes/build/tools/projectcreator/BuildConfig.java b/make/hotspot/src/classes/build/tools/projectcreator/BuildConfig.java index 81e555233e2..76e2b467565 100644 --- a/make/hotspot/src/classes/build/tools/projectcreator/BuildConfig.java +++ b/make/hotspot/src/classes/build/tools/projectcreator/BuildConfig.java @@ -223,7 +223,7 @@ class BuildConfig { sysDefines.add("_WINDOWS"); sysDefines.add("HOTSPOT_BUILD_USER=\\\""+System.getProperty("user.name")+"\\\""); sysDefines.add("HOTSPOT_BUILD_TARGET=\\\""+get("Build")+"\\\""); - sysDefines.add("INCLUDE_TRACE=1"); + sysDefines.add("INCLUDE_JFR=1"); sysDefines.add("_JNI_IMPLEMENTATION_"); if (vars.get("PlatformName").equals("Win32")) { sysDefines.add("HOTSPOT_LIB_ARCH=\\\"i386\\\""); diff --git a/make/jprt.properties b/make/jprt.properties index 9e10f8cf1a2..c4c5541f82c 100644 --- a/make/jprt.properties +++ b/make/jprt.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2006, 2017, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2006, 2018, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -273,6 +273,7 @@ my.make.rule.test.targets.svc= \ ${my.test.target.set:TESTNAME=jdk_instrument}, \ ${my.test.target.set:TESTNAME=jdk_jmx}, \ ${my.test.target.set:TESTNAME=jdk_jdi}, \ + ${my.test.target.set:TESTNAME=jdk_jfr}, \ ${my.test.target.set:TESTNAME=svc_tools}, \ ${my.make.rule.test.targets.svc.extra} diff --git a/make/nashorn/project.properties b/make/nashorn/project.properties index 1839ff5cfee..0e6c69b4698 100644 --- a/make/nashorn/project.properties +++ b/make/nashorn/project.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -98,8 +98,8 @@ dist.javadoc.dir=${dist.dir}/javadoc dist.nashornapi.javadoc.dir=${dist.javadoc.dir}/nashornapi dist.dynalinkapi.javadoc.dir=${dist.javadoc.dir}/dynalinkapi -# configuration for java flight recorder -run.test.jvmargs.jfr=-XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:FlightRecorderOptions=defaultrecording=true,disk=true,dumponexit=true,dumponexitpath=${build.dir},stackdepth=128 +# configuration for flight recorder +run.test.jvmargs.jfr=XX:StartFlightRecording=disk=true,dumponexit=true,dumponexitpath=${build.dir},stackdepth=128 # test library location test.lib=test/nashorn/lib @@ -354,7 +354,7 @@ run.test.xms=2G # uncomment this jfr.args to enable light recordings. the stack needs to be cranked up to 1024 frames, # or everything will as of the now drown in lambda forms and be cut off. # -#jfr.args=-XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:FlightRecorderOptions=defaultrecording=true,disk=true,dumponexit=true,dumponexitpath="test_suite.jfr",stackdepth=1024 \ +#jfr.args=-XX:StartFlightRecording=disk=true,dumponexit=true,dumponexitpath="test_suite.jfr",stackdepth=1024 jfr.args= diff --git a/make/nb_native/nbproject/configurations.xml b/make/nb_native/nbproject/configurations.xml index 29922e333ff..43bcc96ffd0 100644 --- a/make/nb_native/nbproject/configurations.xml +++ b/make/nb_native/nbproject/configurations.xml @@ -2891,7 +2891,6 @@ stringUtils.hpp ticks.cpp ticks.hpp - ticks.inline.hpp utf8.cpp utf8.hpp vmError.cpp @@ -6887,7 +6886,7 @@ ../../hotspot/src/share/vm/ci ../../hotspot/src/share/vm/oops ../../hotspot/src/share/vm/trace - ../../build/macosx-x86_64-normal-server-release/hotspot/variant-server/gensrc/tracefiles + ../../build/macosx-x86_64-normal-server-release/hotspot/variant-server/gensrc/jfrfiles ../../hotspot/src/share/vm/gc/parallel ../../hotspot/src/share/vm/gc/shared ../../hotspot/src/share/vm/classfile @@ -15408,11 +15407,6 @@ tool="3" flavor2="0"> - - - - 0) && (kcid != _kcid)) || + ((kcid == 0) && (_kcid == -1)); + if (!updated) { + kstat_close(kc); + return true; + } + + // update the cached _kcid + _kcid = kcid; + + // find the number of online processors + // for modern processsors, it is also known as the + // hardware threads. + _no_of_threads = sysconf(_SC_NPROCESSORS_ONLN); + + if (_no_of_threads <= 0 ) { + kstat_close(kc); + return false; + } + + _no_of_cores = 0; + _no_of_sockets = 0; + + // loop through the kstat chain + kstat_t* ksp = NULL; + for (ksp = kc->kc_chain; ksp != NULL; ksp = ksp->ks_next) { + // only interested in "cpu_info" + if (strcmp(ksp->ks_module, (char*)CPU_INFO) == 0) { + if (kstat_read(kc, ksp, NULL) == -1) { + kstat_close(kc); + return false; + } + if (ksp->ks_data != NULL) { + kstat_named_t* knm = (kstat_named_t *)ksp->ks_data; + // loop through the number of fields in each record + for (int i = 0; i < ksp->ks_ndata; i++) { + // set cpu type if it hasn't been already set + if ((strcmp((const char*)&(knm[i].name), CPU_TYPE) == 0) && + (_cpu_name[0] == '\0')) { + if (knm[i].data_type == KSTAT_DATA_STRING) { + src_string = (char*)KSTAT_NAMED_STR_PTR(&knm[i]); + } else { + src_string = (char*)&(knm[i].value.c[0]); + } + len = strlen(src_string); + if (len < CPU_TYPE_DESC_BUF_SIZE) { + jio_snprintf(_cpu_name, CPU_TYPE_DESC_BUF_SIZE, + "%s", src_string); + } + } + + // set cpu description if it hasn't been already set + if ((strcmp((const char*)&(knm[i].name), CPU_DESCRIPTION) == 0) && + (_cpu_desc[0] == '\0')) { + if (knm[i].data_type == KSTAT_DATA_STRING) { + src_string = (char*)KSTAT_NAMED_STR_PTR(&knm[i]); + } else { + src_string = (char*)&(knm[i].value.c[0]); + } + len = strlen(src_string); + if (len < CPU_DETAILED_DESC_BUF_SIZE) { + jio_snprintf(_cpu_desc, CPU_DETAILED_DESC_BUF_SIZE, + "%s", src_string); + } + } + + // count the number of sockets based on the chip id + if (strcmp((const char*)&(knm[i].name), CHIP_ID) == 0) { + if (chip_id != knm[i].value.l) { + chip_id = knm[i].value.l; + _no_of_sockets++; + } + } + + // count the number of cores based on the core id + if (strcmp((const char*)&(knm[i].name), CORE_ID) == 0) { + if (core_id != knm[i].value.l) { + core_id = knm[i].value.l; + _no_of_cores++; + } + } + } + } + } + } + + kstat_close(kc); + return true; +} + +int VM_Version_Ext::number_of_threads(void) { + initialize_cpu_information(); + return _no_of_threads; +} + +int VM_Version_Ext::number_of_cores(void) { + initialize_cpu_information(); + return _no_of_cores; +} + +int VM_Version_Ext::number_of_sockets(void) { + initialize_cpu_information(); + return _no_of_sockets; +} + +const char* VM_Version_Ext::cpu_name(void) { + if (!initialize_cpu_information()) { + return NULL; + } + char* tmp = NEW_C_HEAP_ARRAY_RETURN_NULL(char, CPU_TYPE_DESC_BUF_SIZE, mtTracing); + if (NULL == tmp) { + return NULL; + } + strncpy(tmp, _cpu_name, CPU_TYPE_DESC_BUF_SIZE); + return tmp; +} + +const char* VM_Version_Ext::cpu_description(void) { + if (!initialize_cpu_information()) { + return NULL; + } + char* tmp = NEW_C_HEAP_ARRAY_RETURN_NULL(char, CPU_DETAILED_DESC_BUF_SIZE, mtTracing); + if (NULL == tmp) { + return NULL; + } + strncpy(tmp, _cpu_desc, CPU_DETAILED_DESC_BUF_SIZE); + return tmp; +} diff --git a/src/hotspot/cpu/sparc/vm_version_ext_sparc.hpp b/src/hotspot/cpu/sparc/vm_version_ext_sparc.hpp new file mode 100644 index 00000000000..f7e4368ccc9 --- /dev/null +++ b/src/hotspot/cpu/sparc/vm_version_ext_sparc.hpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef CPU_SPARC_VM_VM_VERSION_EXT_SPARC_HPP +#define CPU_SPARC_VM_VM_VERSION_EXT_SPARC_HPP + +#include "utilities/macros.hpp" +#include "vm_version_sparc.hpp" +#include +#include + +#define CPU_INFO "cpu_info" +#define CPU_TYPE "fpu_type" +#define CPU_DESCRIPTION "implementation" +#define CHIP_ID "chip_id" +#define CORE_ID "core_id" + +class VM_Version_Ext : public VM_Version { + private: + + static const size_t CPU_TYPE_DESC_BUF_SIZE = 256; + static const size_t CPU_DETAILED_DESC_BUF_SIZE = 4096; + + static int _no_of_threads; + static int _no_of_cores; + static int _no_of_sockets; + static kid_t _kcid; + static char _cpu_name[CPU_TYPE_DESC_BUF_SIZE]; + static char _cpu_desc[CPU_DETAILED_DESC_BUF_SIZE]; + + static bool initialize_cpu_information(void); + + public: + + static int number_of_threads(void); + static int number_of_cores(void); + static int number_of_sockets(void); + + static const char* cpu_name(void); + static const char* cpu_description(void); +}; + +#endif // CPU_SPARC_VM_VM_VERSION_EXT_SPARC_HPP diff --git a/src/hotspot/cpu/x86/rdtsc_x86.cpp b/src/hotspot/cpu/x86/rdtsc_x86.cpp new file mode 100644 index 00000000000..8d3f8f303a9 --- /dev/null +++ b/src/hotspot/cpu/x86/rdtsc_x86.cpp @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "rdtsc_x86.hpp" +#include "runtime/thread.inline.hpp" +#include "vm_version_ext_x86.hpp" + +// The following header contains the implementations of rdtsc() +#include OS_CPU_HEADER_INLINE(os) + +static jlong _epoch = 0; +static bool rdtsc_elapsed_counter_enabled = false; +static jlong tsc_frequency = 0; + +static jlong set_epoch() { + assert(0 == _epoch, "invariant"); + _epoch = os::rdtsc(); + return _epoch; +} + +// Base loop to estimate ticks frequency for tsc counter from user mode. +// Volatiles and sleep() are used to prevent compiler from applying optimizations. +static void do_time_measurements(volatile jlong& time_base, + volatile jlong& time_fast, + volatile jlong& time_base_elapsed, + volatile jlong& time_fast_elapsed) { + static const unsigned int FT_SLEEP_MILLISECS = 1; + const unsigned int loopcount = 3; + + volatile jlong start = 0; + volatile jlong fstart = 0; + volatile jlong end = 0; + volatile jlong fend = 0; + + // Figure out the difference between rdtsc and os provided timer. + // base algorithm adopted from JRockit. + for (unsigned int times = 0; times < loopcount; times++) { + start = os::elapsed_counter(); + OrderAccess::fence(); + fstart = os::rdtsc(); + + // use sleep to prevent compiler from optimizing + os::sleep(Thread::current(), FT_SLEEP_MILLISECS, true); + + end = os::elapsed_counter(); + OrderAccess::fence(); + fend = os::rdtsc(); + + time_base += end - start; + time_fast += fend - fstart; + + // basis for calculating the os tick start + // to fast time tick start offset + time_base_elapsed += end; + time_fast_elapsed += (fend - _epoch); + } + + time_base /= loopcount; + time_fast /= loopcount; + time_base_elapsed /= loopcount; + time_fast_elapsed /= loopcount; +} + +static jlong initialize_frequency() { + assert(0 == tsc_frequency, "invariant"); + assert(0 == _epoch, "invariant"); + const jlong initial_counter = set_epoch(); + if (initial_counter == 0) { + return 0; + } + // os time frequency + static double os_freq = (double)os::elapsed_frequency(); + assert(os_freq > 0, "os_elapsed frequency corruption!"); + + double tsc_freq = .0; + double os_to_tsc_conv_factor = 1.0; + + // if platform supports invariant tsc, + // apply higher resolution and granularity for conversion calculations + if (VM_Version_Ext::supports_tscinv_ext()) { + // for invariant tsc platforms, take the maximum qualified cpu frequency + tsc_freq = (double)VM_Version_Ext::maximum_qualified_cpu_frequency(); + os_to_tsc_conv_factor = tsc_freq / os_freq; + } else { + // use measurements to estimate + // a conversion factor and the tsc frequency + + volatile jlong time_base = 0; + volatile jlong time_fast = 0; + volatile jlong time_base_elapsed = 0; + volatile jlong time_fast_elapsed = 0; + + // do measurements to get base data + // on os timer and fast ticks tsc time relation. + do_time_measurements(time_base, time_fast, time_base_elapsed, time_fast_elapsed); + + // if invalid measurements, cannot proceed + if (time_fast == 0 || time_base == 0) { + return 0; + } + + os_to_tsc_conv_factor = (double)time_fast / (double)time_base; + if (os_to_tsc_conv_factor > 1) { + // estimate on tsc counter frequency + tsc_freq = os_to_tsc_conv_factor * os_freq; + } + } + + if ((tsc_freq < 0) || (tsc_freq > 0 && tsc_freq <= os_freq) || (os_to_tsc_conv_factor <= 1)) { + // safer to run with normal os time + tsc_freq = .0; + } + + // frequency of the tsc_counter + return (jlong)tsc_freq; +} + +static bool initialize_elapsed_counter() { + tsc_frequency = initialize_frequency(); + return tsc_frequency != 0 && _epoch != 0; +} + +static bool ergonomics() { + const bool invtsc_support = Rdtsc::is_supported(); + if (FLAG_IS_DEFAULT(UseFastUnorderedTimeStamps) && invtsc_support) { + FLAG_SET_ERGO(bool, UseFastUnorderedTimeStamps, true); + } + + bool ft_enabled = UseFastUnorderedTimeStamps && invtsc_support; + + if (!ft_enabled) { + if (UseFastUnorderedTimeStamps && VM_Version::supports_tsc()) { + warning("\nThe hardware does not support invariant tsc (INVTSC) register and/or cannot guarantee tsc synchronization between sockets at startup.\n"\ + "Values returned via rdtsc() are not guaranteed to be accurate, esp. when comparing values from cross sockets reads. Enabling UseFastUnorderedTimeStamps on non-invariant tsc hardware should be considered experimental.\n"); + ft_enabled = true; + } + } + + if (!ft_enabled) { + // Warn if unable to support command-line flag + if (UseFastUnorderedTimeStamps && !VM_Version::supports_tsc()) { + warning("Ignoring UseFastUnorderedTimeStamps, hardware does not support normal tsc"); + } + } + + return ft_enabled; +} + +bool Rdtsc::is_supported() { + return VM_Version_Ext::supports_tscinv_ext(); +} + +bool Rdtsc::is_elapsed_counter_enabled() { + return rdtsc_elapsed_counter_enabled; +} + +jlong Rdtsc::frequency() { + return tsc_frequency; +} + +jlong Rdtsc::elapsed_counter() { + return os::rdtsc() - _epoch; +} + +jlong Rdtsc::epoch() { + return _epoch; +} + +jlong Rdtsc::raw() { + return os::rdtsc(); +} + +bool Rdtsc::initialize() { + static bool initialized = false; + if (!initialized) { + assert(!rdtsc_elapsed_counter_enabled, "invariant"); + VM_Version_Ext::initialize(); + assert(0 == tsc_frequency, "invariant"); + assert(0 == _epoch, "invariant"); + bool result = initialize_elapsed_counter(); // init hw + if (result) { + result = ergonomics(); // check logical state + } + rdtsc_elapsed_counter_enabled = result; + initialized = true; + } + return rdtsc_elapsed_counter_enabled; +} diff --git a/src/hotspot/cpu/x86/rdtsc_x86.hpp b/src/hotspot/cpu/x86/rdtsc_x86.hpp new file mode 100644 index 00000000000..c455c0018b6 --- /dev/null +++ b/src/hotspot/cpu/x86/rdtsc_x86.hpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef CPU_X86_VM_RDTSC_X86_HPP +#define CPU_X86_VM_RDTSC_X86_HPP + +#include "memory/allocation.hpp" +#include "utilities/macros.hpp" + +// Interface to the x86 rdtsc() time counter, if available. +// Not guaranteed to be synchronized across hardware threads and +// therefore software threads, and can be updated asynchronously +// by software. elapsed_counter() can jump backwards +// as well as jump forward when threads query different cores/sockets. +// Very much not recommended for general use. +// INVTSC is a minimal requirement for auto-enablement. + +class Rdtsc : AllStatic { + public: + static jlong elapsed_counter(); // provides quick time stamps + static jlong frequency(); // tsc register + static bool is_supported(); // InvariantTSC + static jlong raw(); // direct rdtsc() access + static bool is_elapsed_counter_enabled(); // turn off with -XX:-UseFastUnorderedTimeStamps + static jlong epoch(); + static bool initialize(); +}; + +#endif // CPU_X86_VM_RDTSC_X86_HPP diff --git a/src/hotspot/cpu/x86/vm_version_ext_x86.cpp b/src/hotspot/cpu/x86/vm_version_ext_x86.cpp new file mode 100644 index 00000000000..a5d60aebe2e --- /dev/null +++ b/src/hotspot/cpu/x86/vm_version_ext_x86.cpp @@ -0,0 +1,967 @@ +/* + * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "jvm.h" +#include "utilities/macros.hpp" +#include "asm/macroAssembler.hpp" +#include "asm/macroAssembler.inline.hpp" +#include "memory/allocation.inline.hpp" +#include "memory/resourceArea.hpp" +#include "runtime/java.hpp" +#include "runtime/stubCodeGenerator.hpp" +#include "vm_version_ext_x86.hpp" + +typedef enum { + CPU_FAMILY_8086_8088 = 0, + CPU_FAMILY_INTEL_286 = 2, + CPU_FAMILY_INTEL_386 = 3, + CPU_FAMILY_INTEL_486 = 4, + CPU_FAMILY_PENTIUM = 5, + CPU_FAMILY_PENTIUMPRO = 6, // Same family several models + CPU_FAMILY_PENTIUM_4 = 0xF +} FamilyFlag; + + typedef enum { + RDTSCP_FLAG = 0x08000000, // bit 27 + INTEL64_FLAG = 0x20000000 // bit 29 + } _featureExtendedEdxFlag; + +#define CPUID_STANDARD_FN 0x0 +#define CPUID_STANDARD_FN_1 0x1 +#define CPUID_STANDARD_FN_4 0x4 +#define CPUID_STANDARD_FN_B 0xb + +#define CPUID_EXTENDED_FN 0x80000000 +#define CPUID_EXTENDED_FN_1 0x80000001 +#define CPUID_EXTENDED_FN_2 0x80000002 +#define CPUID_EXTENDED_FN_3 0x80000003 +#define CPUID_EXTENDED_FN_4 0x80000004 +#define CPUID_EXTENDED_FN_7 0x80000007 +#define CPUID_EXTENDED_FN_8 0x80000008 + +typedef enum { + FPU_FLAG = 0x00000001, + VME_FLAG = 0x00000002, + DE_FLAG = 0x00000004, + PSE_FLAG = 0x00000008, + TSC_FLAG = 0x00000010, + MSR_FLAG = 0x00000020, + PAE_FLAG = 0x00000040, + MCE_FLAG = 0x00000080, + CX8_FLAG = 0x00000100, + APIC_FLAG = 0x00000200, + SEP_FLAG = 0x00000800, + MTRR_FLAG = 0x00001000, + PGE_FLAG = 0x00002000, + MCA_FLAG = 0x00004000, + CMOV_FLAG = 0x00008000, + PAT_FLAG = 0x00010000, + PSE36_FLAG = 0x00020000, + PSNUM_FLAG = 0x00040000, + CLFLUSH_FLAG = 0x00080000, + DTS_FLAG = 0x00200000, + ACPI_FLAG = 0x00400000, + MMX_FLAG = 0x00800000, + FXSR_FLAG = 0x01000000, + SSE_FLAG = 0x02000000, + SSE2_FLAG = 0x04000000, + SS_FLAG = 0x08000000, + HTT_FLAG = 0x10000000, + TM_FLAG = 0x20000000 +} FeatureEdxFlag; + +static BufferBlob* cpuid_brand_string_stub_blob; +static const int cpuid_brand_string_stub_size = 550; + +extern "C" { + typedef void (*getCPUIDBrandString_stub_t)(void*); +} + +static getCPUIDBrandString_stub_t getCPUIDBrandString_stub = NULL; + +class VM_Version_Ext_StubGenerator: public StubCodeGenerator { + public: + + VM_Version_Ext_StubGenerator(CodeBuffer *c) : StubCodeGenerator(c) {} + + address generate_getCPUIDBrandString(void) { + // Flags to test CPU type. + const uint32_t HS_EFL_AC = 0x40000; + const uint32_t HS_EFL_ID = 0x200000; + // Values for when we don't have a CPUID instruction. + const int CPU_FAMILY_SHIFT = 8; + const uint32_t CPU_FAMILY_386 = (3 << CPU_FAMILY_SHIFT); + const uint32_t CPU_FAMILY_486 = (4 << CPU_FAMILY_SHIFT); + + Label detect_486, cpu486, detect_586, done, ext_cpuid; + + StubCodeMark mark(this, "VM_Version_Ext", "getCPUIDNameInfo_stub"); +# define __ _masm-> + + address start = __ pc(); + + // + // void getCPUIDBrandString(VM_Version::CpuidInfo* cpuid_info); + // + // LP64: rcx and rdx are first and second argument registers on windows + + __ push(rbp); +#ifdef _LP64 + __ mov(rbp, c_rarg0); // cpuid_info address +#else + __ movptr(rbp, Address(rsp, 8)); // cpuid_info address +#endif + __ push(rbx); + __ push(rsi); + __ pushf(); // preserve rbx, and flags + __ pop(rax); + __ push(rax); + __ mov(rcx, rax); + // + // if we are unable to change the AC flag, we have a 386 + // + __ xorl(rax, HS_EFL_AC); + __ push(rax); + __ popf(); + __ pushf(); + __ pop(rax); + __ cmpptr(rax, rcx); + __ jccb(Assembler::notEqual, detect_486); + + __ movl(rax, CPU_FAMILY_386); + __ jmp(done); + + // + // If we are unable to change the ID flag, we have a 486 which does + // not support the "cpuid" instruction. + // + __ bind(detect_486); + __ mov(rax, rcx); + __ xorl(rax, HS_EFL_ID); + __ push(rax); + __ popf(); + __ pushf(); + __ pop(rax); + __ cmpptr(rcx, rax); + __ jccb(Assembler::notEqual, detect_586); + + __ bind(cpu486); + __ movl(rax, CPU_FAMILY_486); + __ jmp(done); + + // + // At this point, we have a chip which supports the "cpuid" instruction + // + __ bind(detect_586); + __ xorl(rax, rax); + __ cpuid(); + __ orl(rax, rax); + __ jcc(Assembler::equal, cpu486); // if cpuid doesn't support an input + // value of at least 1, we give up and + // assume a 486 + + // + // Extended cpuid(0x80000000) for processor brand string detection + // + __ bind(ext_cpuid); + __ movl(rax, CPUID_EXTENDED_FN); + __ cpuid(); + __ cmpl(rax, CPUID_EXTENDED_FN_4); + __ jcc(Assembler::below, done); + + // + // Extended cpuid(0x80000002) // first 16 bytes in brand string + // + __ movl(rax, CPUID_EXTENDED_FN_2); + __ cpuid(); + __ lea(rsi, Address(rbp, in_bytes(VM_Version_Ext::proc_name_0_offset()))); + __ movl(Address(rsi, 0), rax); + __ lea(rsi, Address(rbp, in_bytes(VM_Version_Ext::proc_name_1_offset()))); + __ movl(Address(rsi, 0), rbx); + __ lea(rsi, Address(rbp, in_bytes(VM_Version_Ext::proc_name_2_offset()))); + __ movl(Address(rsi, 0), rcx); + __ lea(rsi, Address(rbp, in_bytes(VM_Version_Ext::proc_name_3_offset()))); + __ movl(Address(rsi,0), rdx); + + // + // Extended cpuid(0x80000003) // next 16 bytes in brand string + // + __ movl(rax, CPUID_EXTENDED_FN_3); + __ cpuid(); + __ lea(rsi, Address(rbp, in_bytes(VM_Version_Ext::proc_name_4_offset()))); + __ movl(Address(rsi, 0), rax); + __ lea(rsi, Address(rbp, in_bytes(VM_Version_Ext::proc_name_5_offset()))); + __ movl(Address(rsi, 0), rbx); + __ lea(rsi, Address(rbp, in_bytes(VM_Version_Ext::proc_name_6_offset()))); + __ movl(Address(rsi, 0), rcx); + __ lea(rsi, Address(rbp, in_bytes(VM_Version_Ext::proc_name_7_offset()))); + __ movl(Address(rsi,0), rdx); + + // + // Extended cpuid(0x80000004) // last 16 bytes in brand string + // + __ movl(rax, CPUID_EXTENDED_FN_4); + __ cpuid(); + __ lea(rsi, Address(rbp, in_bytes(VM_Version_Ext::proc_name_8_offset()))); + __ movl(Address(rsi, 0), rax); + __ lea(rsi, Address(rbp, in_bytes(VM_Version_Ext::proc_name_9_offset()))); + __ movl(Address(rsi, 0), rbx); + __ lea(rsi, Address(rbp, in_bytes(VM_Version_Ext::proc_name_10_offset()))); + __ movl(Address(rsi, 0), rcx); + __ lea(rsi, Address(rbp, in_bytes(VM_Version_Ext::proc_name_11_offset()))); + __ movl(Address(rsi,0), rdx); + + // + // return + // + __ bind(done); + __ popf(); + __ pop(rsi); + __ pop(rbx); + __ pop(rbp); + __ ret(0); + +# undef __ + + return start; + }; +}; + + +// VM_Version_Ext statics +const size_t VM_Version_Ext::VENDOR_LENGTH = 13; +const size_t VM_Version_Ext::CPU_EBS_MAX_LENGTH = (3 * 4 * 4 + 1); +const size_t VM_Version_Ext::CPU_TYPE_DESC_BUF_SIZE = 256; +const size_t VM_Version_Ext::CPU_DETAILED_DESC_BUF_SIZE = 4096; +char* VM_Version_Ext::_cpu_brand_string = NULL; +jlong VM_Version_Ext::_max_qualified_cpu_frequency = 0; + +int VM_Version_Ext::_no_of_threads = 0; +int VM_Version_Ext::_no_of_cores = 0; +int VM_Version_Ext::_no_of_packages = 0; + +void VM_Version_Ext::initialize(void) { + ResourceMark rm; + + cpuid_brand_string_stub_blob = BufferBlob::create("getCPUIDBrandString_stub", cpuid_brand_string_stub_size); + if (cpuid_brand_string_stub_blob == NULL) { + vm_exit_during_initialization("Unable to allocate getCPUIDBrandString_stub"); + } + CodeBuffer c(cpuid_brand_string_stub_blob); + VM_Version_Ext_StubGenerator g(&c); + getCPUIDBrandString_stub = CAST_TO_FN_PTR(getCPUIDBrandString_stub_t, + g.generate_getCPUIDBrandString()); +} + +const char* VM_Version_Ext::cpu_model_description(void) { + uint32_t cpu_family = extended_cpu_family(); + uint32_t cpu_model = extended_cpu_model(); + const char* model = NULL; + + if (cpu_family == CPU_FAMILY_PENTIUMPRO) { + for (uint32_t i = 0; i <= cpu_model; i++) { + model = _model_id_pentium_pro[i]; + if (model == NULL) { + break; + } + } + } + return model; +} + +const char* VM_Version_Ext::cpu_brand_string(void) { + if (_cpu_brand_string == NULL) { + _cpu_brand_string = NEW_C_HEAP_ARRAY_RETURN_NULL(char, CPU_EBS_MAX_LENGTH, mtInternal); + if (NULL == _cpu_brand_string) { + return NULL; + } + int ret_val = cpu_extended_brand_string(_cpu_brand_string, CPU_EBS_MAX_LENGTH); + if (ret_val != OS_OK) { + FREE_C_HEAP_ARRAY(char, _cpu_brand_string); + _cpu_brand_string = NULL; + } + } + return _cpu_brand_string; +} + +const char* VM_Version_Ext::cpu_brand(void) { + const char* brand = NULL; + + if ((_cpuid_info.std_cpuid1_ebx.value & 0xFF) > 0) { + int brand_num = _cpuid_info.std_cpuid1_ebx.value & 0xFF; + brand = _brand_id[0]; + for (int i = 0; brand != NULL && i <= brand_num; i += 1) { + brand = _brand_id[i]; + } + } + return brand; +} + +bool VM_Version_Ext::cpu_is_em64t(void) { + return ((_cpuid_info.ext_cpuid1_edx.value & INTEL64_FLAG) == INTEL64_FLAG); +} + +bool VM_Version_Ext::is_netburst(void) { + return (is_intel() && (extended_cpu_family() == CPU_FAMILY_PENTIUM_4)); +} + +bool VM_Version_Ext::supports_tscinv_ext(void) { + if (!supports_tscinv_bit()) { + return false; + } + + if (is_intel()) { + return true; + } + + if (is_amd()) { + return !is_amd_Barcelona(); + } + + return false; +} + +void VM_Version_Ext::resolve_cpu_information_details(void) { + + // in future we want to base this information on proper cpu + // and cache topology enumeration such as: + // Intel 64 Architecture Processor Topology Enumeration + // which supports system cpu and cache topology enumeration + // either using 2xAPICIDs or initial APICIDs + + // currently only rough cpu information estimates + // which will not necessarily reflect the exact configuration of the system + + // this is the number of logical hardware threads + // visible to the operating system + _no_of_threads = os::processor_count(); + + // find out number of threads per cpu package + int threads_per_package = threads_per_core() * cores_per_cpu(); + + // use amount of threads visible to the process in order to guess number of sockets + _no_of_packages = _no_of_threads / threads_per_package; + + // process might only see a subset of the total number of threads + // from a single processor package. Virtualization/resource management for example. + // If so then just write a hard 1 as num of pkgs. + if (0 == _no_of_packages) { + _no_of_packages = 1; + } + + // estimate the number of cores + _no_of_cores = cores_per_cpu() * _no_of_packages; +} + +int VM_Version_Ext::number_of_threads(void) { + if (_no_of_threads == 0) { + resolve_cpu_information_details(); + } + return _no_of_threads; +} + +int VM_Version_Ext::number_of_cores(void) { + if (_no_of_cores == 0) { + resolve_cpu_information_details(); + } + return _no_of_cores; +} + +int VM_Version_Ext::number_of_sockets(void) { + if (_no_of_packages == 0) { + resolve_cpu_information_details(); + } + return _no_of_packages; +} + +const char* VM_Version_Ext::cpu_family_description(void) { + int cpu_family_id = extended_cpu_family(); + if (is_amd()) { + return _family_id_amd[cpu_family_id]; + } + if (is_intel()) { + if (cpu_family_id == CPU_FAMILY_PENTIUMPRO) { + return cpu_model_description(); + } + return _family_id_intel[cpu_family_id]; + } + return "Unknown x86"; +} + +int VM_Version_Ext::cpu_type_description(char* const buf, size_t buf_len) { + assert(buf != NULL, "buffer is NULL!"); + assert(buf_len >= CPU_TYPE_DESC_BUF_SIZE, "buffer len should at least be == CPU_TYPE_DESC_BUF_SIZE!"); + + const char* cpu_type = NULL; + const char* x64 = NULL; + + if (is_intel()) { + cpu_type = "Intel"; + x64 = cpu_is_em64t() ? " Intel64" : ""; + } else if (is_amd()) { + cpu_type = "AMD"; + x64 = cpu_is_em64t() ? " AMD64" : ""; + } else { + cpu_type = "Unknown x86"; + x64 = cpu_is_em64t() ? " x86_64" : ""; + } + + jio_snprintf(buf, buf_len, "%s %s%s SSE SSE2%s%s%s%s%s%s%s%s", + cpu_type, + cpu_family_description(), + supports_ht() ? " (HT)" : "", + supports_sse3() ? " SSE3" : "", + supports_ssse3() ? " SSSE3" : "", + supports_sse4_1() ? " SSE4.1" : "", + supports_sse4_2() ? " SSE4.2" : "", + supports_sse4a() ? " SSE4A" : "", + is_netburst() ? " Netburst" : "", + is_intel_family_core() ? " Core" : "", + x64); + + return OS_OK; +} + +int VM_Version_Ext::cpu_extended_brand_string(char* const buf, size_t buf_len) { + assert(buf != NULL, "buffer is NULL!"); + assert(buf_len >= CPU_EBS_MAX_LENGTH, "buffer len should at least be == CPU_EBS_MAX_LENGTH!"); + assert(getCPUIDBrandString_stub != NULL, "not initialized"); + + // invoke newly generated asm code to fetch CPU Brand String + getCPUIDBrandString_stub(&_cpuid_info); + + // fetch results into buffer + *((uint32_t*) &buf[0]) = _cpuid_info.proc_name_0; + *((uint32_t*) &buf[4]) = _cpuid_info.proc_name_1; + *((uint32_t*) &buf[8]) = _cpuid_info.proc_name_2; + *((uint32_t*) &buf[12]) = _cpuid_info.proc_name_3; + *((uint32_t*) &buf[16]) = _cpuid_info.proc_name_4; + *((uint32_t*) &buf[20]) = _cpuid_info.proc_name_5; + *((uint32_t*) &buf[24]) = _cpuid_info.proc_name_6; + *((uint32_t*) &buf[28]) = _cpuid_info.proc_name_7; + *((uint32_t*) &buf[32]) = _cpuid_info.proc_name_8; + *((uint32_t*) &buf[36]) = _cpuid_info.proc_name_9; + *((uint32_t*) &buf[40]) = _cpuid_info.proc_name_10; + *((uint32_t*) &buf[44]) = _cpuid_info.proc_name_11; + + return OS_OK; +} + +size_t VM_Version_Ext::cpu_write_support_string(char* const buf, size_t buf_len) { + assert(buf != NULL, "buffer is NULL!"); + assert(buf_len > 0, "buffer len not enough!"); + + unsigned int flag = 0; + unsigned int fi = 0; + size_t written = 0; + const char* prefix = ""; + +#define WRITE_TO_BUF(string) \ + { \ + int res = jio_snprintf(&buf[written], buf_len - written, "%s%s", prefix, string); \ + if (res < 0 || (size_t) res >= buf_len - 1) { \ + buf[buf_len-1] = '\0'; \ + return buf_len - 1; \ + } \ + written += res; \ + if (prefix[0] == '\0') { \ + prefix = ", "; \ + } \ + } + + for (flag = 1, fi = 0; flag <= 0x20000000 ; flag <<= 1, fi++) { + if (flag == HTT_FLAG && (((_cpuid_info.std_cpuid1_ebx.value >> 16) & 0xff) <= 1)) { + continue; /* no hyperthreading */ + } else if (flag == SEP_FLAG && (cpu_family() == CPU_FAMILY_PENTIUMPRO && ((_cpuid_info.std_cpuid1_eax.value & 0xff) < 0x33))) { + continue; /* no fast system call */ + } + if ((_cpuid_info.std_cpuid1_edx.value & flag) && strlen(_feature_edx_id[fi]) > 0) { + WRITE_TO_BUF(_feature_edx_id[fi]); + } + } + + for (flag = 1, fi = 0; flag <= 0x20000000; flag <<= 1, fi++) { + if ((_cpuid_info.std_cpuid1_ecx.value & flag) && strlen(_feature_ecx_id[fi]) > 0) { + WRITE_TO_BUF(_feature_ecx_id[fi]); + } + } + + for (flag = 1, fi = 0; flag <= 0x20000000 ; flag <<= 1, fi++) { + if ((_cpuid_info.ext_cpuid1_ecx.value & flag) && strlen(_feature_extended_ecx_id[fi]) > 0) { + WRITE_TO_BUF(_feature_extended_ecx_id[fi]); + } + } + + for (flag = 1, fi = 0; flag <= 0x20000000; flag <<= 1, fi++) { + if ((_cpuid_info.ext_cpuid1_edx.value & flag) && strlen(_feature_extended_edx_id[fi]) > 0) { + WRITE_TO_BUF(_feature_extended_edx_id[fi]); + } + } + + if (supports_tscinv_bit()) { + WRITE_TO_BUF("Invariant TSC"); + } + + return written; +} + +/** + * Write a detailed description of the cpu to a given buffer, including + * feature set. + */ +int VM_Version_Ext::cpu_detailed_description(char* const buf, size_t buf_len) { + assert(buf != NULL, "buffer is NULL!"); + assert(buf_len >= CPU_DETAILED_DESC_BUF_SIZE, "buffer len should at least be == CPU_DETAILED_DESC_BUF_SIZE!"); + + static const char* unknown = ""; + char vendor_id[VENDOR_LENGTH]; + const char* family = NULL; + const char* model = NULL; + const char* brand = NULL; + int outputLen = 0; + + family = cpu_family_description(); + if (family == NULL) { + family = unknown; + } + + model = cpu_model_description(); + if (model == NULL) { + model = unknown; + } + + brand = cpu_brand_string(); + + if (brand == NULL) { + brand = cpu_brand(); + if (brand == NULL) { + brand = unknown; + } + } + + *((uint32_t*) &vendor_id[0]) = _cpuid_info.std_vendor_name_0; + *((uint32_t*) &vendor_id[4]) = _cpuid_info.std_vendor_name_2; + *((uint32_t*) &vendor_id[8]) = _cpuid_info.std_vendor_name_1; + vendor_id[VENDOR_LENGTH-1] = '\0'; + + outputLen = jio_snprintf(buf, buf_len, "Brand: %s, Vendor: %s\n" + "Family: %s (0x%x), Model: %s (0x%x), Stepping: 0x%x\n" + "Ext. family: 0x%x, Ext. model: 0x%x, Type: 0x%x, Signature: 0x%8.8x\n" + "Features: ebx: 0x%8.8x, ecx: 0x%8.8x, edx: 0x%8.8x\n" + "Ext. features: eax: 0x%8.8x, ebx: 0x%8.8x, ecx: 0x%8.8x, edx: 0x%8.8x\n" + "Supports: ", + brand, + vendor_id, + family, + extended_cpu_family(), + model, + extended_cpu_model(), + cpu_stepping(), + _cpuid_info.std_cpuid1_eax.bits.ext_family, + _cpuid_info.std_cpuid1_eax.bits.ext_model, + _cpuid_info.std_cpuid1_eax.bits.proc_type, + _cpuid_info.std_cpuid1_eax.value, + _cpuid_info.std_cpuid1_ebx.value, + _cpuid_info.std_cpuid1_ecx.value, + _cpuid_info.std_cpuid1_edx.value, + _cpuid_info.ext_cpuid1_eax, + _cpuid_info.ext_cpuid1_ebx, + _cpuid_info.ext_cpuid1_ecx, + _cpuid_info.ext_cpuid1_edx); + + if (outputLen < 0 || (size_t) outputLen >= buf_len - 1) { + buf[buf_len-1] = '\0'; + return OS_ERR; + } + + cpu_write_support_string(&buf[outputLen], buf_len - outputLen); + + return OS_OK; +} + +const char* VM_Version_Ext::cpu_name(void) { + char cpu_type_desc[CPU_TYPE_DESC_BUF_SIZE]; + size_t cpu_desc_len = sizeof(cpu_type_desc); + + cpu_type_description(cpu_type_desc, cpu_desc_len); + char* tmp = NEW_C_HEAP_ARRAY_RETURN_NULL(char, cpu_desc_len, mtTracing); + if (NULL == tmp) { + return NULL; + } + strncpy(tmp, cpu_type_desc, cpu_desc_len); + return tmp; +} + +const char* VM_Version_Ext::cpu_description(void) { + char cpu_detailed_desc_buffer[CPU_DETAILED_DESC_BUF_SIZE]; + size_t cpu_detailed_desc_len = sizeof(cpu_detailed_desc_buffer); + + cpu_detailed_description(cpu_detailed_desc_buffer, cpu_detailed_desc_len); + + char* tmp = NEW_C_HEAP_ARRAY_RETURN_NULL(char, cpu_detailed_desc_len, mtTracing); + + if (NULL == tmp) { + return NULL; + } + + strncpy(tmp, cpu_detailed_desc_buffer, cpu_detailed_desc_len); + return tmp; +} + +/** + * See Intel Application note 485 (chapter 10) for details + * on frequency extraction from cpu brand string. + * http://www.intel.com/content/dam/www/public/us/en/documents/application-notes/processor-identification-cpuid-instruction-note.pdf + * + */ +jlong VM_Version_Ext::max_qualified_cpu_freq_from_brand_string(void) { + // get brand string + const char* const brand_string = cpu_brand_string(); + if (brand_string == NULL) { + return 0; + } + + const u8 MEGA = 1000000; + u8 multiplier = 0; + jlong frequency = 0; + + // the frequency information in the cpu brand string + // is given in either of two formats "x.xxyHz" or "xxxxyHz", + // where y=M,G,T and x is digits + const char* Hz_location = strchr(brand_string, 'H'); + + if (Hz_location != NULL) { + if (*(Hz_location + 1) == 'z') { + // switch on y in "yHz" + switch(*(Hz_location - 1)) { + case 'M' : + // Set multiplier to frequency is in Hz + multiplier = MEGA; + break; + case 'G' : + multiplier = MEGA * 1000; + break; + case 'T' : + multiplier = MEGA * 1000 * 1000; + break; + } + } + } + + if (multiplier > 0) { + // compute frequency (in Hz) from brand string + if (*(Hz_location - 4) == '.') { // if format is "x.xx" + frequency = (jlong)(*(Hz_location - 5) - '0') * (multiplier); + frequency += (jlong)(*(Hz_location - 3) - '0') * (multiplier / 10); + frequency += (jlong)(*(Hz_location - 2) - '0') * (multiplier / 100); + } else { // format is "xxxx" + frequency = (jlong)(*(Hz_location - 5) - '0') * 1000; + frequency += (jlong)(*(Hz_location - 4) - '0') * 100; + frequency += (jlong)(*(Hz_location - 3) - '0') * 10; + frequency += (jlong)(*(Hz_location - 2) - '0'); + frequency *= multiplier; + } + } + return frequency; +} + + +jlong VM_Version_Ext::maximum_qualified_cpu_frequency(void) { + if (_max_qualified_cpu_frequency == 0) { + _max_qualified_cpu_frequency = max_qualified_cpu_freq_from_brand_string(); + } + return _max_qualified_cpu_frequency; +} + +const char* const VM_Version_Ext::_family_id_intel[] = { + "8086/8088", + "", + "286", + "386", + "486", + "Pentium", + "Pentium Pro", //or Pentium-M/Woodcrest depeding on model + "", + "", + "", + "", + "", + "", + "", + "", + "Pentium 4" +}; + +const char* const VM_Version_Ext::_family_id_amd[] = { + "", + "", + "", + "", + "5x86", + "K5/K6", + "Athlon/AthlonXP", + "", + "", + "", + "", + "", + "", + "", + "", + "Opteron/Athlon64", + "Opteron QC/Phenom" // Barcelona et.al. +}; +// Partially from Intel 64 and IA-32 Architecture Software Developer's Manual, +// September 2013, Vol 3C Table 35-1 +const char* const VM_Version_Ext::_model_id_pentium_pro[] = { + "", + "Pentium Pro", + "", + "Pentium II model 3", + "", + "Pentium II model 5/Xeon/Celeron", + "Celeron", + "Pentium III/Pentium III Xeon", + "Pentium III/Pentium III Xeon", + "Pentium M model 9", // Yonah + "Pentium III, model A", + "Pentium III, model B", + "", + "Pentium M model D", // Dothan + "", + "Core 2", // 0xf Woodcrest/Conroe/Merom/Kentsfield/Clovertown + "", + "", + "", + "", + "", + "", + "Celeron", // 0x16 Celeron 65nm + "Core 2", // 0x17 Penryn / Harpertown + "", + "", + "Core i7", // 0x1A CPU_MODEL_NEHALEM_EP + "Atom", // 0x1B Z5xx series Silverthorn + "", + "Core 2", // 0x1D Dunnington (6-core) + "Nehalem", // 0x1E CPU_MODEL_NEHALEM + "", + "", + "", + "", + "", + "", + "Westmere", // 0x25 CPU_MODEL_WESTMERE + "", + "", + "", // 0x28 + "", + "Sandy Bridge", // 0x2a "2nd Generation Intel Core i7, i5, i3" + "", + "Westmere-EP", // 0x2c CPU_MODEL_WESTMERE_EP + "Sandy Bridge-EP", // 0x2d CPU_MODEL_SANDYBRIDGE_EP + "Nehalem-EX", // 0x2e CPU_MODEL_NEHALEM_EX + "Westmere-EX", // 0x2f CPU_MODEL_WESTMERE_EX + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "Ivy Bridge", // 0x3a + "", + "Haswell", // 0x3c "4th Generation Intel Core Processor" + "", // 0x3d "Next Generation Intel Core Processor" + "Ivy Bridge-EP", // 0x3e "Next Generation Intel Xeon Processor E7 Family" + "", // 0x3f "Future Generation Intel Xeon Processor" + "", + "", + "", + "", + "", + "Haswell", // 0x45 "4th Generation Intel Core Processor" + "Haswell", // 0x46 "4th Generation Intel Core Processor" + NULL +}; + +/* Brand ID is for back compability + * Newer CPUs uses the extended brand string */ +const char* const VM_Version_Ext::_brand_id[] = { + "", + "Celeron processor", + "Pentium III processor", + "Intel Pentium III Xeon processor", + "", + "", + "", + "", + "Intel Pentium 4 processor", + NULL +}; + + +const char* const VM_Version_Ext::_feature_edx_id[] = { + "On-Chip FPU", + "Virtual Mode Extensions", + "Debugging Extensions", + "Page Size Extensions", + "Time Stamp Counter", + "Model Specific Registers", + "Physical Address Extension", + "Machine Check Exceptions", + "CMPXCHG8B Instruction", + "On-Chip APIC", + "", + "Fast System Call", + "Memory Type Range Registers", + "Page Global Enable", + "Machine Check Architecture", + "Conditional Mov Instruction", + "Page Attribute Table", + "36-bit Page Size Extension", + "Processor Serial Number", + "CLFLUSH Instruction", + "", + "Debug Trace Store feature", + "ACPI registers in MSR space", + "Intel Architecture MMX Technology", + "Fast Float Point Save and Restore", + "Streaming SIMD extensions", + "Streaming SIMD extensions 2", + "Self-Snoop", + "Hyper Threading", + "Thermal Monitor", + "", + "Pending Break Enable" +}; + +const char* const VM_Version_Ext::_feature_extended_edx_id[] = { + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "SYSCALL/SYSRET", + "", + "", + "", + "", + "", + "", + "", + "", + "Execute Disable Bit", + "", + "", + "", + "", + "", + "", + "RDTSCP", + "", + "Intel 64 Architecture", + "", + "" +}; + +const char* const VM_Version_Ext::_feature_ecx_id[] = { + "Streaming SIMD Extensions 3", + "PCLMULQDQ", + "64-bit DS Area", + "MONITOR/MWAIT instructions", + "CPL Qualified Debug Store", + "Virtual Machine Extensions", + "Safer Mode Extensions", + "Enhanced Intel SpeedStep technology", + "Thermal Monitor 2", + "Supplemental Streaming SIMD Extensions 3", + "L1 Context ID", + "", + "Fused Multiply-Add", + "CMPXCHG16B", + "xTPR Update Control", + "Perfmon and Debug Capability", + "", + "Process-context identifiers", + "Direct Cache Access", + "Streaming SIMD extensions 4.1", + "Streaming SIMD extensions 4.2", + "x2APIC", + "MOVBE", + "Popcount instruction", + "TSC-Deadline", + "AESNI", + "XSAVE", + "OSXSAVE", + "AVX", + "F16C", + "RDRAND", + "" +}; + +const char* const VM_Version_Ext::_feature_extended_ecx_id[] = { + "LAHF/SAHF instruction support", + "Core multi-processor leagacy mode", + "", + "", + "", + "Advanced Bit Manipulations: LZCNT", + "SSE4A: MOVNTSS, MOVNTSD, EXTRQ, INSERTQ", + "Misaligned SSE mode", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" +}; diff --git a/src/hotspot/cpu/x86/vm_version_ext_x86.hpp b/src/hotspot/cpu/x86/vm_version_ext_x86.hpp new file mode 100644 index 00000000000..de35616b6e2 --- /dev/null +++ b/src/hotspot/cpu/x86/vm_version_ext_x86.hpp @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef CPU_X86_VM_VM_VERSION_EXT_X86_HPP +#define CPU_X86_VM_VM_VERSION_EXT_X86_HPP + +#include "utilities/macros.hpp" +#include "vm_version_x86.hpp" + +class VM_Version_Ext : public VM_Version { + private: + static const size_t VENDOR_LENGTH; + static const size_t CPU_EBS_MAX_LENGTH; + static const size_t CPU_TYPE_DESC_BUF_SIZE; + static const size_t CPU_DETAILED_DESC_BUF_SIZE; + + static const char* const _family_id_intel[]; + static const char* const _family_id_amd[]; + static const char* const _brand_id[]; + static const char* const _model_id_pentium_pro[]; + + static const char* const _feature_edx_id[]; + static const char* const _feature_extended_edx_id[]; + static const char* const _feature_ecx_id[]; + static const char* const _feature_extended_ecx_id[]; + + static int _no_of_threads; + static int _no_of_cores; + static int _no_of_packages; + static char* _cpu_brand_string; + static jlong _max_qualified_cpu_frequency; + + static const char* cpu_family_description(void); + static const char* cpu_model_description(void); + static const char* cpu_brand(void); + static const char* cpu_brand_string(void); + + static int cpu_type_description(char* const buf, size_t buf_len); + static int cpu_detailed_description(char* const buf, size_t buf_len); + static int cpu_extended_brand_string(char* const buf, size_t buf_len); + + static bool cpu_is_em64t(void); + static bool is_netburst(void); + + static size_t cpu_write_support_string(char* const buf, size_t buf_len); + static void resolve_cpu_information_details(void); + static jlong max_qualified_cpu_freq_from_brand_string(void); + + public: + // Offsets for cpuid asm stub brand string + static ByteSize proc_name_0_offset() { return byte_offset_of(CpuidInfo, proc_name_0); } + static ByteSize proc_name_1_offset() { return byte_offset_of(CpuidInfo, proc_name_1); } + static ByteSize proc_name_2_offset() { return byte_offset_of(CpuidInfo, proc_name_2); } + static ByteSize proc_name_3_offset() { return byte_offset_of(CpuidInfo, proc_name_3); } + static ByteSize proc_name_4_offset() { return byte_offset_of(CpuidInfo, proc_name_4); } + static ByteSize proc_name_5_offset() { return byte_offset_of(CpuidInfo, proc_name_5); } + static ByteSize proc_name_6_offset() { return byte_offset_of(CpuidInfo, proc_name_6); } + static ByteSize proc_name_7_offset() { return byte_offset_of(CpuidInfo, proc_name_7); } + static ByteSize proc_name_8_offset() { return byte_offset_of(CpuidInfo, proc_name_8); } + static ByteSize proc_name_9_offset() { return byte_offset_of(CpuidInfo, proc_name_9); } + static ByteSize proc_name_10_offset() { return byte_offset_of(CpuidInfo, proc_name_10); } + static ByteSize proc_name_11_offset() { return byte_offset_of(CpuidInfo, proc_name_11); } + + static int number_of_threads(void); + static int number_of_cores(void); + static int number_of_sockets(void); + + static jlong maximum_qualified_cpu_frequency(void); + + static bool supports_tscinv_ext(void); + + static const char* cpu_name(void); + static const char* cpu_description(void); + + static void initialize(); +}; + +#endif // CPU_X86_VM_VM_VERSION_EXT_X86_HPP diff --git a/src/hotspot/os/bsd/os_perf_bsd.cpp b/src/hotspot/os/bsd/os_perf_bsd.cpp new file mode 100644 index 00000000000..c0aaa1bb02b --- /dev/null +++ b/src/hotspot/os/bsd/os_perf_bsd.cpp @@ -0,0 +1,405 @@ +/* + * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +#include "precompiled.hpp" +#include "memory/allocation.inline.hpp" +#include "memory/resourceArea.hpp" +#include "runtime/os.hpp" +#include "runtime/os_perf.hpp" +#include "vm_version_ext_x86.hpp" + +#ifdef __APPLE__ + #import + #include + #include + #include + #include +#endif + +static const double NANOS_PER_SEC = 1000000000.0; + +class CPUPerformanceInterface::CPUPerformance : public CHeapObj { + friend class CPUPerformanceInterface; + private: + long _total_cpu_nanos; + long _total_csr_nanos; + long _jvm_user_nanos; + long _jvm_system_nanos; + long _jvm_context_switches; + long _used_ticks; + long _total_ticks; + int _active_processor_count; + + bool now_in_nanos(long* resultp) { + timeval current_time; + if (gettimeofday(¤t_time, NULL) != 0) { + // Error getting current time + return false; + } + *resultp = current_time.tv_sec * NANOS_PER_SEC + 1000L * current_time.tv_usec; + return true; + } + + double normalize(double value) { + return MIN2(MAX2(value, 0.0), 1.0); + } + int cpu_load(int which_logical_cpu, double* cpu_load); + int context_switch_rate(double* rate); + int cpu_load_total_process(double* cpu_load); + int cpu_loads_process(double* pjvmUserLoad, double* pjvmKernelLoad, double* psystemTotalLoad); + + CPUPerformance(const CPUPerformance& rhs); // no impl + CPUPerformance& operator=(const CPUPerformance& rhs); // no impl + public: + CPUPerformance(); + bool initialize(); + ~CPUPerformance(); +}; + +CPUPerformanceInterface::CPUPerformance::CPUPerformance() { + _total_cpu_nanos= 0; + _total_csr_nanos= 0; + _jvm_context_switches = 0; + _jvm_user_nanos = 0; + _jvm_system_nanos = 0; + _used_ticks = 0; + _total_ticks = 0; + _active_processor_count = 0; +} + +bool CPUPerformanceInterface::CPUPerformance::initialize() { + return true; +} + +CPUPerformanceInterface::CPUPerformance::~CPUPerformance() { +} + +int CPUPerformanceInterface::CPUPerformance::cpu_load(int which_logical_cpu, double* cpu_load) { + return FUNCTIONALITY_NOT_IMPLEMENTED; +} + +int CPUPerformanceInterface::CPUPerformance::cpu_load_total_process(double* cpu_load) { +#ifdef __APPLE__ + host_name_port_t host = mach_host_self(); + host_flavor_t flavor = HOST_CPU_LOAD_INFO; + mach_msg_type_number_t host_info_count = HOST_CPU_LOAD_INFO_COUNT; + host_cpu_load_info_data_t cpu_load_info; + + kern_return_t kr = host_statistics(host, flavor, (host_info_t)&cpu_load_info, &host_info_count); + if (kr != KERN_SUCCESS) { + return OS_ERR; + } + + long used_ticks = cpu_load_info.cpu_ticks[CPU_STATE_USER] + cpu_load_info.cpu_ticks[CPU_STATE_NICE] + cpu_load_info.cpu_ticks[CPU_STATE_SYSTEM]; + long total_ticks = used_ticks + cpu_load_info.cpu_ticks[CPU_STATE_IDLE]; + + if (_used_ticks == 0 || _total_ticks == 0) { + // First call, just set the values + _used_ticks = used_ticks; + _total_ticks = total_ticks; + return OS_ERR; + } + + long used_delta = used_ticks - _used_ticks; + long total_delta = total_ticks - _total_ticks; + + _used_ticks = used_ticks; + _total_ticks = total_ticks; + + if (total_delta == 0) { + // Avoid division by zero + return OS_ERR; + } + + *cpu_load = (double)used_delta / total_delta; + + return OS_OK; +#else + return FUNCTIONALITY_NOT_IMPLEMENTED; +#endif +} + +int CPUPerformanceInterface::CPUPerformance::cpu_loads_process(double* pjvmUserLoad, double* pjvmKernelLoad, double* psystemTotalLoad) { +#ifdef __APPLE__ + int result = cpu_load_total_process(psystemTotalLoad); + mach_port_t task = mach_task_self(); + mach_msg_type_number_t task_info_count = TASK_INFO_MAX; + task_info_data_t task_info_data; + kern_return_t kr = task_info(task, TASK_ABSOLUTETIME_INFO, (task_info_t)task_info_data, &task_info_count); + if (kr != KERN_SUCCESS) { + return OS_ERR; + } + task_absolutetime_info_t absolutetime_info = (task_absolutetime_info_t)task_info_data; + + int active_processor_count = os::active_processor_count(); + long jvm_user_nanos = absolutetime_info->total_user; + long jvm_system_nanos = absolutetime_info->total_system; + + long total_cpu_nanos; + if(!now_in_nanos(&total_cpu_nanos)) { + return OS_ERR; + } + + if (_total_cpu_nanos == 0 || active_processor_count != _active_processor_count) { + // First call or change in active processor count + result = OS_ERR; + } + + long delta_nanos = active_processor_count * (total_cpu_nanos - _total_cpu_nanos); + if (delta_nanos == 0) { + // Avoid division by zero + return OS_ERR; + } + + *pjvmUserLoad = normalize((double)(jvm_user_nanos - _jvm_user_nanos)/delta_nanos); + *pjvmKernelLoad = normalize((double)(jvm_system_nanos - _jvm_system_nanos)/delta_nanos); + + _active_processor_count = active_processor_count; + _total_cpu_nanos = total_cpu_nanos; + _jvm_user_nanos = jvm_user_nanos; + _jvm_system_nanos = jvm_system_nanos; + + return result; +#else + return FUNCTIONALITY_NOT_IMPLEMENTED; +#endif +} + +int CPUPerformanceInterface::CPUPerformance::context_switch_rate(double* rate) { +#ifdef __APPLE__ + mach_port_t task = mach_task_self(); + mach_msg_type_number_t task_info_count = TASK_INFO_MAX; + task_info_data_t task_info_data; + kern_return_t kr = task_info(task, TASK_EVENTS_INFO, (task_info_t)task_info_data, &task_info_count); + if (kr != KERN_SUCCESS) { + return OS_ERR; + } + + int result = OS_OK; + if (_total_csr_nanos == 0 || _jvm_context_switches == 0) { + // First call just set initial values. + result = OS_ERR; + } + + long jvm_context_switches = ((task_events_info_t)task_info_data)->csw; + + long total_csr_nanos; + if(!now_in_nanos(&total_csr_nanos)) { + return OS_ERR; + } + double delta_in_sec = (double)(total_csr_nanos - _total_csr_nanos) / NANOS_PER_SEC; + if (delta_in_sec == 0.0) { + // Avoid division by zero + return OS_ERR; + } + + *rate = (jvm_context_switches - _jvm_context_switches) / delta_in_sec; + + _jvm_context_switches = jvm_context_switches; + _total_csr_nanos = total_csr_nanos; + + return result; +#else + return FUNCTIONALITY_NOT_IMPLEMENTED; +#endif +} + +CPUPerformanceInterface::CPUPerformanceInterface() { + _impl = NULL; +} + +bool CPUPerformanceInterface::initialize() { + _impl = new CPUPerformanceInterface::CPUPerformance(); + return _impl != NULL && _impl->initialize(); +} + +CPUPerformanceInterface::~CPUPerformanceInterface() { + if (_impl != NULL) { + delete _impl; + } +} + +int CPUPerformanceInterface::cpu_load(int which_logical_cpu, double* cpu_load) const { + return _impl->cpu_load(which_logical_cpu, cpu_load); +} + +int CPUPerformanceInterface::cpu_load_total_process(double* cpu_load) const { + return _impl->cpu_load_total_process(cpu_load); +} + +int CPUPerformanceInterface::cpu_loads_process(double* pjvmUserLoad, double* pjvmKernelLoad, double* psystemTotalLoad) const { + return _impl->cpu_loads_process(pjvmUserLoad, pjvmKernelLoad, psystemTotalLoad); +} + +int CPUPerformanceInterface::context_switch_rate(double* rate) const { + return _impl->context_switch_rate(rate); +} + +class SystemProcessInterface::SystemProcesses : public CHeapObj { + friend class SystemProcessInterface; + private: + SystemProcesses(); + bool initialize(); + SystemProcesses(const SystemProcesses& rhs); // no impl + SystemProcesses& operator=(const SystemProcesses& rhs); // no impl + ~SystemProcesses(); + + //information about system processes + int system_processes(SystemProcess** system_processes, int* no_of_sys_processes) const; +}; + +SystemProcessInterface::SystemProcesses::SystemProcesses() { +} + +bool SystemProcessInterface::SystemProcesses::initialize() { + return true; +} + +SystemProcessInterface::SystemProcesses::~SystemProcesses() { +} +int SystemProcessInterface::SystemProcesses::system_processes(SystemProcess** system_processes, int* no_of_sys_processes) const { + assert(system_processes != NULL, "system_processes pointer is NULL!"); + assert(no_of_sys_processes != NULL, "system_processes counter pointer is NULL!"); +#ifdef __APPLE__ + pid_t* pids = NULL; + int pid_count = 0; + ResourceMark rm; + + int try_count = 0; + while (pids == NULL) { + // Find out buffer size + size_t pids_bytes = proc_listpids(PROC_ALL_PIDS, 0, NULL, 0); + if (pids_bytes <= 0) { + return OS_ERR; + } + pid_count = pids_bytes / sizeof(pid_t); + pids = NEW_RESOURCE_ARRAY(pid_t, pid_count); + memset(pids, 0, pids_bytes); + + pids_bytes = proc_listpids(PROC_ALL_PIDS, 0, pids, pids_bytes); + if (pids_bytes <= 0) { + // couldn't fit buffer, retry. + FREE_RESOURCE_ARRAY(pid_t, pids, pid_count); + pids = NULL; + try_count++; + if (try_count > 3) { + return OS_ERR; + } + } else { + pid_count = pids_bytes / sizeof(pid_t); + } + } + + int process_count = 0; + SystemProcess* next = NULL; + for (int i = 0; i < pid_count; i++) { + pid_t pid = pids[i]; + if (pid != 0) { + char buffer[PROC_PIDPATHINFO_MAXSIZE]; + memset(buffer, 0 , sizeof(buffer)); + if (proc_pidpath(pid, buffer, sizeof(buffer)) != -1) { + int length = strlen(buffer); + if (length > 0) { + SystemProcess* current = new SystemProcess(); + char * path = NEW_C_HEAP_ARRAY(char, length + 1, mtInternal); + strcpy(path, buffer); + current->set_path(path); + current->set_pid((int)pid); + current->set_next(next); + next = current; + process_count++; + } + } + } + } + + *no_of_sys_processes = process_count; + *system_processes = next; + + return OS_OK; +#endif + return FUNCTIONALITY_NOT_IMPLEMENTED; +} + +int SystemProcessInterface::system_processes(SystemProcess** system_procs, int* no_of_sys_processes) const { + return _impl->system_processes(system_procs, no_of_sys_processes); +} + +SystemProcessInterface::SystemProcessInterface() { + _impl = NULL; +} + +bool SystemProcessInterface::initialize() { + _impl = new SystemProcessInterface::SystemProcesses(); + return _impl != NULL && _impl->initialize(); +} + +SystemProcessInterface::~SystemProcessInterface() { + if (_impl != NULL) { + delete _impl; + } +} + +CPUInformationInterface::CPUInformationInterface() { + _cpu_info = NULL; +} + +bool CPUInformationInterface::initialize() { + _cpu_info = new CPUInformation(); + + if (NULL == _cpu_info) { + return false; + } + _cpu_info->set_number_of_hardware_threads(VM_Version_Ext::number_of_threads()); + _cpu_info->set_number_of_cores(VM_Version_Ext::number_of_cores()); + _cpu_info->set_number_of_sockets(VM_Version_Ext::number_of_sockets()); + _cpu_info->set_cpu_name(VM_Version_Ext::cpu_name()); + _cpu_info->set_cpu_description(VM_Version_Ext::cpu_description()); + + return true; +} + +CPUInformationInterface::~CPUInformationInterface() { + if (_cpu_info != NULL) { + if (_cpu_info->cpu_name() != NULL) { + const char* cpu_name = _cpu_info->cpu_name(); + FREE_C_HEAP_ARRAY(char, cpu_name); + _cpu_info->set_cpu_name(NULL); + } + if (_cpu_info->cpu_description() != NULL) { + const char* cpu_desc = _cpu_info->cpu_description(); + FREE_C_HEAP_ARRAY(char, cpu_desc); + _cpu_info->set_cpu_description(NULL); + } + delete _cpu_info; + } +} + +int CPUInformationInterface::cpu_information(CPUInformation& cpu_info) { + if (NULL == _cpu_info) { + return OS_ERR; + } + + cpu_info = *_cpu_info; // shallow copy assignment + return OS_OK; +} diff --git a/src/hotspot/os/linux/os_perf_linux.cpp b/src/hotspot/os/linux/os_perf_linux.cpp new file mode 100644 index 00000000000..3ad0a6ef136 --- /dev/null +++ b/src/hotspot/os/linux/os_perf_linux.cpp @@ -0,0 +1,1060 @@ +/* + * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "jvm.h" +#include "memory/allocation.inline.hpp" +#include "os_linux.inline.hpp" +#include "runtime/os.hpp" +#include "runtime/os_perf.hpp" + +#ifdef X86 +#include "vm_version_ext_x86.hpp" +#endif +#ifdef ARM +#include "vm_version_ext_arm.hpp" +#endif +#ifndef ARM +#ifdef AARCH64 +#include "vm_version_ext_aarch64.hpp" +#endif +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + /proc/[number]/stat + Status information about the process. This is used by ps(1). It is defined in /usr/src/linux/fs/proc/array.c. + + The fields, in order, with their proper scanf(3) format specifiers, are: + + 1. pid %d The process id. + + 2. comm %s + The filename of the executable, in parentheses. This is visible whether or not the executable is swapped out. + + 3. state %c + One character from the string "RSDZTW" where R is running, S is sleeping in an interruptible wait, D is waiting in uninterruptible disk + sleep, Z is zombie, T is traced or stopped (on a signal), and W is paging. + + 4. ppid %d + The PID of the parent. + + 5. pgrp %d + The process group ID of the process. + + 6. session %d + The session ID of the process. + + 7. tty_nr %d + The tty the process uses. + + 8. tpgid %d + The process group ID of the process which currently owns the tty that the process is connected to. + + 9. flags %lu + The flags of the process. The math bit is decimal 4, and the traced bit is decimal 10. + + 10. minflt %lu + The number of minor faults the process has made which have not required loading a memory page from disk. + + 11. cminflt %lu + The number of minor faults that the process's waited-for children have made. + + 12. majflt %lu + The number of major faults the process has made which have required loading a memory page from disk. + + 13. cmajflt %lu + The number of major faults that the process's waited-for children have made. + + 14. utime %lu + The number of jiffies that this process has been scheduled in user mode. + + 15. stime %lu + The number of jiffies that this process has been scheduled in kernel mode. + + 16. cutime %ld + The number of jiffies that this process's waited-for children have been scheduled in user mode. (See also times(2).) + + 17. cstime %ld + The number of jiffies that this process' waited-for children have been scheduled in kernel mode. + + 18. priority %ld + The standard nice value, plus fifteen. The value is never negative in the kernel. + + 19. nice %ld + The nice value ranges from 19 (nicest) to -19 (not nice to others). + + 20. 0 %ld This value is hard coded to 0 as a placeholder for a removed field. + + 21. itrealvalue %ld + The time in jiffies before the next SIGALRM is sent to the process due to an interval timer. + + 22. starttime %lu + The time in jiffies the process started after system boot. + + 23. vsize %lu + Virtual memory size in bytes. + + 24. rss %ld + Resident Set Size: number of pages the process has in real memory, minus 3 for administrative purposes. This is just the pages which count + towards text, data, or stack space. This does not include pages which have not been demand-loaded in, or which are swapped out. + + 25. rlim %lu + Current limit in bytes on the rss of the process (usually 4294967295 on i386). + + 26. startcode %lu + The address above which program text can run. + + 27. endcode %lu + The address below which program text can run. + + 28. startstack %lu + The address of the start of the stack. + + 29. kstkesp %lu + The current value of esp (stack pointer), as found in the kernel stack page for the process. + + 30. kstkeip %lu + The current EIP (instruction pointer). + + 31. signal %lu + The bitmap of pending signals (usually 0). + + 32. blocked %lu + The bitmap of blocked signals (usually 0, 2 for shells). + + 33. sigignore %lu + The bitmap of ignored signals. + + 34. sigcatch %lu + The bitmap of catched signals. + + 35. wchan %lu + This is the "channel" in which the process is waiting. It is the address of a system call, and can be looked up in a namelist if you need + a textual name. (If you have an up-to-date /etc/psdatabase, then try ps -l to see the WCHAN field in action.) + + 36. nswap %lu + Number of pages swapped - not maintained. + + 37. cnswap %lu + Cumulative nswap for child processes. + + 38. exit_signal %d + Signal to be sent to parent when we die. + + 39. processor %d + CPU number last executed on. + + + + ///// SSCANF FORMAT STRING. Copy and use. + +field: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 +format: %d %s %c %d %d %d %d %d %lu %lu %lu %lu %lu %lu %lu %ld %ld %ld %ld %ld %ld %lu %lu %ld %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %d %d + + +*/ + +/** + * For platforms that have them, when declaring + * a printf-style function, + * formatSpec is the parameter number (starting at 1) + * that is the format argument ("%d pid %s") + * params is the parameter number where the actual args to + * the format starts. If the args are in a va_list, this + * should be 0. + */ +#ifndef PRINTF_ARGS +# define PRINTF_ARGS(formatSpec, params) ATTRIBUTE_PRINTF(formatSpec, params) +#endif + +#ifndef SCANF_ARGS +# define SCANF_ARGS(formatSpec, params) ATTRIBUTE_SCANF(formatSpec, params) +#endif + +#ifndef _PRINTFMT_ +# define _PRINTFMT_ +#endif + +#ifndef _SCANFMT_ +# define _SCANFMT_ +#endif + + +struct CPUPerfTicks { + uint64_t used; + uint64_t usedKernel; + uint64_t total; +}; + +typedef enum { + CPU_LOAD_VM_ONLY, + CPU_LOAD_GLOBAL, +} CpuLoadTarget; + +enum { + UNDETECTED, + UNDETECTABLE, + LINUX26_NPTL, + BAREMETAL +}; + +struct CPUPerfCounters { + int nProcs; + CPUPerfTicks jvmTicks; + CPUPerfTicks* cpus; +}; + +static double get_cpu_load(int which_logical_cpu, CPUPerfCounters* counters, double* pkernelLoad, CpuLoadTarget target); + +/** reads /proc//stat data, with some checks and some skips. + * Ensure that 'fmt' does _NOT_ contain the first two "%d %s" + */ +static int SCANF_ARGS(2, 0) vread_statdata(const char* procfile, _SCANFMT_ const char* fmt, va_list args) { + FILE*f; + int n; + char buf[2048]; + + if ((f = fopen(procfile, "r")) == NULL) { + return -1; + } + + if ((n = fread(buf, 1, sizeof(buf), f)) != -1) { + char *tmp; + + buf[n-1] = '\0'; + /** skip through pid and exec name. */ + if ((tmp = strrchr(buf, ')')) != NULL) { + // skip the ')' and the following space + // but check that buffer is long enough + tmp += 2; + if (tmp < buf + n) { + n = vsscanf(tmp, fmt, args); + } + } + } + + fclose(f); + + return n; +} + +static int SCANF_ARGS(2, 3) read_statdata(const char* procfile, _SCANFMT_ const char* fmt, ...) { + int n; + va_list args; + + va_start(args, fmt); + n = vread_statdata(procfile, fmt, args); + va_end(args); + return n; +} + +static FILE* open_statfile(void) { + FILE *f; + + if ((f = fopen("/proc/stat", "r")) == NULL) { + static int haveWarned = 0; + if (!haveWarned) { + haveWarned = 1; + } + } + return f; +} + +static void +next_line(FILE *f) { + int c; + do { + c = fgetc(f); + } while (c != '\n' && c != EOF); +} + +/** + * Return the total number of ticks since the system was booted. + * If the usedTicks parameter is not NULL, it will be filled with + * the number of ticks spent on actual processes (user, system or + * nice processes) since system boot. Note that this is the total number + * of "executed" ticks on _all_ CPU:s, that is on a n-way system it is + * n times the number of ticks that has passed in clock time. + * + * Returns a negative value if the reading of the ticks failed. + */ +static OSReturn get_total_ticks(int which_logical_cpu, CPUPerfTicks* pticks) { + FILE* fh; + uint64_t userTicks, niceTicks, systemTicks, idleTicks; + uint64_t iowTicks = 0, irqTicks = 0, sirqTicks= 0; + int logical_cpu = -1; + const int expected_assign_count = (-1 == which_logical_cpu) ? 4 : 5; + int n; + + if ((fh = open_statfile()) == NULL) { + return OS_ERR; + } + if (-1 == which_logical_cpu) { + n = fscanf(fh, "cpu " UINT64_FORMAT " " UINT64_FORMAT " " UINT64_FORMAT " " + UINT64_FORMAT " " UINT64_FORMAT " " UINT64_FORMAT " " UINT64_FORMAT, + &userTicks, &niceTicks, &systemTicks, &idleTicks, + &iowTicks, &irqTicks, &sirqTicks); + } else { + // Move to next line + next_line(fh); + + // find the line for requested cpu faster to just iterate linefeeds? + for (int i = 0; i < which_logical_cpu; i++) { + next_line(fh); + } + + n = fscanf(fh, "cpu%u " UINT64_FORMAT " " UINT64_FORMAT " " UINT64_FORMAT " " + UINT64_FORMAT " " UINT64_FORMAT " " UINT64_FORMAT " " UINT64_FORMAT, + &logical_cpu, &userTicks, &niceTicks, + &systemTicks, &idleTicks, &iowTicks, &irqTicks, &sirqTicks); + } + + fclose(fh); + if (n < expected_assign_count || logical_cpu != which_logical_cpu) { +#ifdef DEBUG_LINUX_PROC_STAT + vm_fprintf(stderr, "[stat] read failed"); +#endif + return OS_ERR; + } + +#ifdef DEBUG_LINUX_PROC_STAT + vm_fprintf(stderr, "[stat] read " + UINT64_FORMAT " " UINT64_FORMAT " " UINT64_FORMAT " " UINT64_FORMAT " " + UINT64_FORMAT " " UINT64_FORMAT " " UINT64_FORMAT " \n", + userTicks, niceTicks, systemTicks, idleTicks, + iowTicks, irqTicks, sirqTicks); +#endif + + pticks->used = userTicks + niceTicks; + pticks->usedKernel = systemTicks + irqTicks + sirqTicks; + pticks->total = userTicks + niceTicks + systemTicks + idleTicks + + iowTicks + irqTicks + sirqTicks; + + return OS_OK; +} + + +static int get_systemtype(void) { + static int procEntriesType = UNDETECTED; + DIR *taskDir; + + if (procEntriesType != UNDETECTED) { + return procEntriesType; + } + + // Check whether we have a task subdirectory + if ((taskDir = opendir("/proc/self/task")) == NULL) { + procEntriesType = UNDETECTABLE; + } else { + // The task subdirectory exists; we're on a Linux >= 2.6 system + closedir(taskDir); + procEntriesType = LINUX26_NPTL; + } + + return procEntriesType; +} + +/** read user and system ticks from a named procfile, assumed to be in 'stat' format then. */ +static int read_ticks(const char* procfile, uint64_t* userTicks, uint64_t* systemTicks) { + return read_statdata(procfile, "%*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u " UINT64_FORMAT " " UINT64_FORMAT, + userTicks, systemTicks); +} + +/** + * Return the number of ticks spent in any of the processes belonging + * to the JVM on any CPU. + */ +static OSReturn get_jvm_ticks(CPUPerfTicks* pticks) { + uint64_t userTicks; + uint64_t systemTicks; + + if (get_systemtype() != LINUX26_NPTL) { + return OS_ERR; + } + + if (read_ticks("/proc/self/stat", &userTicks, &systemTicks) != 2) { + return OS_ERR; + } + + // get the total + if (get_total_ticks(-1, pticks) != OS_OK) { + return OS_ERR; + } + + pticks->used = userTicks; + pticks->usedKernel = systemTicks; + + return OS_OK; +} + +/** + * Return the load of the CPU as a double. 1.0 means the CPU process uses all + * available time for user or system processes, 0.0 means the CPU uses all time + * being idle. + * + * Returns a negative value if there is a problem in determining the CPU load. + */ +static double get_cpu_load(int which_logical_cpu, CPUPerfCounters* counters, double* pkernelLoad, CpuLoadTarget target) { + uint64_t udiff, kdiff, tdiff; + CPUPerfTicks* pticks; + CPUPerfTicks tmp; + double user_load; + + *pkernelLoad = 0.0; + + if (target == CPU_LOAD_VM_ONLY) { + pticks = &counters->jvmTicks; + } else if (-1 == which_logical_cpu) { + pticks = &counters->cpus[counters->nProcs]; + } else { + pticks = &counters->cpus[which_logical_cpu]; + } + + tmp = *pticks; + + if (target == CPU_LOAD_VM_ONLY) { + if (get_jvm_ticks(pticks) != OS_OK) { + return -1.0; + } + } else if (get_total_ticks(which_logical_cpu, pticks) != OS_OK) { + return -1.0; + } + + // seems like we sometimes end up with less kernel ticks when + // reading /proc/self/stat a second time, timing issue between cpus? + if (pticks->usedKernel < tmp.usedKernel) { + kdiff = 0; + } else { + kdiff = pticks->usedKernel - tmp.usedKernel; + } + tdiff = pticks->total - tmp.total; + udiff = pticks->used - tmp.used; + + if (tdiff == 0) { + return 0.0; + } else if (tdiff < (udiff + kdiff)) { + tdiff = udiff + kdiff; + } + *pkernelLoad = (kdiff / (double)tdiff); + // BUG9044876, normalize return values to sane values + *pkernelLoad = MAX2(*pkernelLoad, 0.0); + *pkernelLoad = MIN2(*pkernelLoad, 1.0); + + user_load = (udiff / (double)tdiff); + user_load = MAX2(user_load, 0.0); + user_load = MIN2(user_load, 1.0); + + return user_load; +} + +static int SCANF_ARGS(1, 2) parse_stat(_SCANFMT_ const char* fmt, ...) { + FILE *f; + va_list args; + + va_start(args, fmt); + + if ((f = open_statfile()) == NULL) { + va_end(args); + return OS_ERR; + } + for (;;) { + char line[80]; + if (fgets(line, sizeof(line), f) != NULL) { + if (vsscanf(line, fmt, args) == 1) { + fclose(f); + va_end(args); + return OS_OK; + } + } else { + fclose(f); + va_end(args); + return OS_ERR; + } + } +} + +static int get_noof_context_switches(uint64_t* switches) { + return parse_stat("ctxt " UINT64_FORMAT "\n", switches); +} + +/** returns boot time in _seconds_ since epoch */ +static int get_boot_time(uint64_t* time) { + return parse_stat("btime " UINT64_FORMAT "\n", time); +} + +static int perf_context_switch_rate(double* rate) { + static pthread_mutex_t contextSwitchLock = PTHREAD_MUTEX_INITIALIZER; + static uint64_t lastTime; + static uint64_t lastSwitches; + static double lastRate; + + uint64_t lt = 0; + int res = 0; + + if (lastTime == 0) { + uint64_t tmp; + if (get_boot_time(&tmp) < 0) { + return OS_ERR; + } + lt = tmp * 1000; + } + + res = OS_OK; + + pthread_mutex_lock(&contextSwitchLock); + { + + uint64_t sw; + s8 t, d; + + if (lastTime == 0) { + lastTime = lt; + } + + t = os::javaTimeMillis(); + d = t - lastTime; + + if (d == 0) { + *rate = lastRate; + } else if (!get_noof_context_switches(&sw)) { + *rate = ( (double)(sw - lastSwitches) / d ) * 1000; + lastRate = *rate; + lastSwitches = sw; + lastTime = t; + } else { + *rate = 0; + res = OS_ERR; + } + if (*rate <= 0) { + *rate = 0; + lastRate = 0; + } + } + pthread_mutex_unlock(&contextSwitchLock); + + return res; +} + +class CPUPerformanceInterface::CPUPerformance : public CHeapObj { + friend class CPUPerformanceInterface; + private: + CPUPerfCounters _counters; + + int cpu_load(int which_logical_cpu, double* cpu_load); + int context_switch_rate(double* rate); + int cpu_load_total_process(double* cpu_load); + int cpu_loads_process(double* pjvmUserLoad, double* pjvmKernelLoad, double* psystemTotalLoad); + + public: + CPUPerformance(); + bool initialize(); + ~CPUPerformance(); +}; + +CPUPerformanceInterface::CPUPerformance::CPUPerformance() { + _counters.nProcs = os::active_processor_count(); + _counters.cpus = NULL; +} + +bool CPUPerformanceInterface::CPUPerformance::initialize() { + size_t tick_array_size = (_counters.nProcs +1) * sizeof(CPUPerfTicks); + _counters.cpus = (CPUPerfTicks*)NEW_C_HEAP_ARRAY(char, tick_array_size, mtInternal); + if (NULL == _counters.cpus) { + return false; + } + memset(_counters.cpus, 0, tick_array_size); + + // For the CPU load total + get_total_ticks(-1, &_counters.cpus[_counters.nProcs]); + + // For each CPU + for (int i = 0; i < _counters.nProcs; i++) { + get_total_ticks(i, &_counters.cpus[i]); + } + // For JVM load + get_jvm_ticks(&_counters.jvmTicks); + + // initialize context switch system + // the double is only for init + double init_ctx_switch_rate; + perf_context_switch_rate(&init_ctx_switch_rate); + + return true; +} + +CPUPerformanceInterface::CPUPerformance::~CPUPerformance() { + if (_counters.cpus != NULL) { + FREE_C_HEAP_ARRAY(char, _counters.cpus); + } +} + +int CPUPerformanceInterface::CPUPerformance::cpu_load(int which_logical_cpu, double* cpu_load) { + double u, s; + u = get_cpu_load(which_logical_cpu, &_counters, &s, CPU_LOAD_GLOBAL); + if (u < 0) { + *cpu_load = 0.0; + return OS_ERR; + } + // Cap total systemload to 1.0 + *cpu_load = MIN2((u + s), 1.0); + return OS_OK; +} + +int CPUPerformanceInterface::CPUPerformance::cpu_load_total_process(double* cpu_load) { + double u, s; + u = get_cpu_load(-1, &_counters, &s, CPU_LOAD_VM_ONLY); + if (u < 0) { + *cpu_load = 0.0; + return OS_ERR; + } + *cpu_load = u + s; + return OS_OK; +} + +int CPUPerformanceInterface::CPUPerformance::cpu_loads_process(double* pjvmUserLoad, double* pjvmKernelLoad, double* psystemTotalLoad) { + double u, s, t; + + assert(pjvmUserLoad != NULL, "pjvmUserLoad not inited"); + assert(pjvmKernelLoad != NULL, "pjvmKernelLoad not inited"); + assert(psystemTotalLoad != NULL, "psystemTotalLoad not inited"); + + u = get_cpu_load(-1, &_counters, &s, CPU_LOAD_VM_ONLY); + if (u < 0) { + *pjvmUserLoad = 0.0; + *pjvmKernelLoad = 0.0; + *psystemTotalLoad = 0.0; + return OS_ERR; + } + + cpu_load(-1, &t); + // clamp at user+system and 1.0 + if (u + s > t) { + t = MIN2(u + s, 1.0); + } + + *pjvmUserLoad = u; + *pjvmKernelLoad = s; + *psystemTotalLoad = t; + + return OS_OK; +} + +int CPUPerformanceInterface::CPUPerformance::context_switch_rate(double* rate) { + return perf_context_switch_rate(rate); +} + +CPUPerformanceInterface::CPUPerformanceInterface() { + _impl = NULL; +} + +bool CPUPerformanceInterface::initialize() { + _impl = new CPUPerformanceInterface::CPUPerformance(); + return NULL == _impl ? false : _impl->initialize(); +} + +CPUPerformanceInterface::~CPUPerformanceInterface() { + if (_impl != NULL) { + delete _impl; + } +} + +int CPUPerformanceInterface::cpu_load(int which_logical_cpu, double* cpu_load) const { + return _impl->cpu_load(which_logical_cpu, cpu_load); +} + +int CPUPerformanceInterface::cpu_load_total_process(double* cpu_load) const { + return _impl->cpu_load_total_process(cpu_load); +} + +int CPUPerformanceInterface::cpu_loads_process(double* pjvmUserLoad, double* pjvmKernelLoad, double* psystemTotalLoad) const { + return _impl->cpu_loads_process(pjvmUserLoad, pjvmKernelLoad, psystemTotalLoad); +} + +int CPUPerformanceInterface::context_switch_rate(double* rate) const { + return _impl->context_switch_rate(rate); +} + +class SystemProcessInterface::SystemProcesses : public CHeapObj { + friend class SystemProcessInterface; + private: + class ProcessIterator : public CHeapObj { + friend class SystemProcessInterface::SystemProcesses; + private: + DIR* _dir; + struct dirent* _entry; + bool _valid; + char _exeName[PATH_MAX]; + char _exePath[PATH_MAX]; + + ProcessIterator(); + ~ProcessIterator(); + bool initialize(); + + bool is_valid() const { return _valid; } + bool is_valid_entry(struct dirent* entry) const; + bool is_dir(const char* name) const; + int fsize(const char* name, uint64_t& size) const; + + char* allocate_string(const char* str) const; + void get_exe_name(); + char* get_exe_path(); + char* get_cmdline(); + + int current(SystemProcess* process_info); + int next_process(); + }; + + ProcessIterator* _iterator; + SystemProcesses(); + bool initialize(); + ~SystemProcesses(); + + //information about system processes + int system_processes(SystemProcess** system_processes, int* no_of_sys_processes) const; +}; + +bool SystemProcessInterface::SystemProcesses::ProcessIterator::is_dir(const char* name) const { + struct stat mystat; + int ret_val = 0; + + ret_val = stat(name, &mystat); + if (ret_val < 0) { + return false; + } + ret_val = S_ISDIR(mystat.st_mode); + return ret_val > 0; +} + +int SystemProcessInterface::SystemProcesses::ProcessIterator::fsize(const char* name, uint64_t& size) const { + assert(name != NULL, "name pointer is NULL!"); + size = 0; + struct stat fbuf; + + if (stat(name, &fbuf) < 0) { + return OS_ERR; + } + size = fbuf.st_size; + return OS_OK; +} + +// if it has a numeric name, is a directory and has a 'stat' file in it +bool SystemProcessInterface::SystemProcesses::ProcessIterator::is_valid_entry(struct dirent* entry) const { + char buffer[PATH_MAX]; + uint64_t size = 0; + + if (atoi(entry->d_name) != 0) { + jio_snprintf(buffer, PATH_MAX, "/proc/%s", entry->d_name); + buffer[PATH_MAX - 1] = '\0'; + + if (is_dir(buffer)) { + jio_snprintf(buffer, PATH_MAX, "/proc/%s/stat", entry->d_name); + buffer[PATH_MAX - 1] = '\0'; + if (fsize(buffer, size) != OS_ERR) { + return true; + } + } + } + return false; +} + +// get exe-name from /proc//stat +void SystemProcessInterface::SystemProcesses::ProcessIterator::get_exe_name() { + FILE* fp; + char buffer[PATH_MAX]; + + jio_snprintf(buffer, PATH_MAX, "/proc/%s/stat", _entry->d_name); + buffer[PATH_MAX - 1] = '\0'; + if ((fp = fopen(buffer, "r")) != NULL) { + if (fgets(buffer, PATH_MAX, fp) != NULL) { + char* start, *end; + // exe-name is between the first pair of ( and ) + start = strchr(buffer, '('); + if (start != NULL && start[1] != '\0') { + start++; + end = strrchr(start, ')'); + if (end != NULL) { + size_t len; + len = MIN2(end - start, sizeof(_exeName) - 1); + memcpy(_exeName, start, len); + _exeName[len] = '\0'; + } + } + } + fclose(fp); + } +} + +// get command line from /proc//cmdline +char* SystemProcessInterface::SystemProcesses::ProcessIterator::get_cmdline() { + FILE* fp; + char buffer[PATH_MAX]; + char* cmdline = NULL; + + jio_snprintf(buffer, PATH_MAX, "/proc/%s/cmdline", _entry->d_name); + buffer[PATH_MAX - 1] = '\0'; + if ((fp = fopen(buffer, "r")) != NULL) { + size_t size = 0; + char dummy; + + // find out how long the file is (stat always returns 0) + while (fread(&dummy, 1, 1, fp) == 1) { + size++; + } + if (size > 0) { + cmdline = NEW_C_HEAP_ARRAY(char, size + 1, mtInternal); + if (cmdline != NULL) { + cmdline[0] = '\0'; + if (fseek(fp, 0, SEEK_SET) == 0) { + if (fread(cmdline, 1, size, fp) == size) { + // the file has the arguments separated by '\0', + // so we translate '\0' to ' ' + for (size_t i = 0; i < size; i++) { + if (cmdline[i] == '\0') { + cmdline[i] = ' '; + } + } + cmdline[size] = '\0'; + } + } + } + } + fclose(fp); + } + return cmdline; +} + +// get full path to exe from /proc//exe symlink +char* SystemProcessInterface::SystemProcesses::ProcessIterator::get_exe_path() { + char buffer[PATH_MAX]; + + jio_snprintf(buffer, PATH_MAX, "/proc/%s/exe", _entry->d_name); + buffer[PATH_MAX - 1] = '\0'; + return realpath(buffer, _exePath); +} + +char* SystemProcessInterface::SystemProcesses::ProcessIterator::allocate_string(const char* str) const { + if (str != NULL) { + size_t len = strlen(str); + char* tmp = NEW_C_HEAP_ARRAY(char, len+1, mtInternal); + strncpy(tmp, str, len); + tmp[len] = '\0'; + return tmp; + } + return NULL; +} + +int SystemProcessInterface::SystemProcesses::ProcessIterator::current(SystemProcess* process_info) { + if (!is_valid()) { + return OS_ERR; + } + + process_info->set_pid(atoi(_entry->d_name)); + + get_exe_name(); + process_info->set_name(allocate_string(_exeName)); + + if (get_exe_path() != NULL) { + process_info->set_path(allocate_string(_exePath)); + } + + char* cmdline = NULL; + cmdline = get_cmdline(); + if (cmdline != NULL) { + process_info->set_command_line(allocate_string(cmdline)); + FREE_C_HEAP_ARRAY(char, cmdline); + } + + return OS_OK; +} + +int SystemProcessInterface::SystemProcesses::ProcessIterator::next_process() { + struct dirent* entry; + + if (!is_valid()) { + return OS_ERR; + } + + do { + entry = os::readdir(_dir, _entry); + if (entry == NULL) { + // error + _valid = false; + return OS_ERR; + } + if (_entry == NULL) { + // reached end + _valid = false; + return OS_ERR; + } + } while(!is_valid_entry(_entry)); + + _valid = true; + return OS_OK; +} + +SystemProcessInterface::SystemProcesses::ProcessIterator::ProcessIterator() { + _dir = NULL; + _entry = NULL; + _valid = false; +} + +bool SystemProcessInterface::SystemProcesses::ProcessIterator::initialize() { + _dir = opendir("/proc"); + _entry = (struct dirent*)NEW_C_HEAP_ARRAY(char, sizeof(struct dirent) + NAME_MAX + 1, mtInternal); + if (NULL == _entry) { + return false; + } + _valid = true; + next_process(); + + return true; +} + +SystemProcessInterface::SystemProcesses::ProcessIterator::~ProcessIterator() { + if (_entry != NULL) { + FREE_C_HEAP_ARRAY(char, _entry); + } + if (_dir != NULL) { + closedir(_dir); + } +} + +SystemProcessInterface::SystemProcesses::SystemProcesses() { + _iterator = NULL; +} + +bool SystemProcessInterface::SystemProcesses::initialize() { + _iterator = new SystemProcessInterface::SystemProcesses::ProcessIterator(); + return NULL == _iterator ? false : _iterator->initialize(); +} + +SystemProcessInterface::SystemProcesses::~SystemProcesses() { + if (_iterator != NULL) { + delete _iterator; + } +} + +int SystemProcessInterface::SystemProcesses::system_processes(SystemProcess** system_processes, int* no_of_sys_processes) const { + assert(system_processes != NULL, "system_processes pointer is NULL!"); + assert(no_of_sys_processes != NULL, "system_processes counter pointers is NULL!"); + assert(_iterator != NULL, "iterator is NULL!"); + + // initialize pointers + *no_of_sys_processes = 0; + *system_processes = NULL; + + while (_iterator->is_valid()) { + SystemProcess* tmp = new SystemProcess(); + _iterator->current(tmp); + + //if already existing head + if (*system_processes != NULL) { + //move "first to second" + tmp->set_next(*system_processes); + } + // new head + *system_processes = tmp; + // increment + (*no_of_sys_processes)++; + // step forward + _iterator->next_process(); + } + return OS_OK; +} + +int SystemProcessInterface::system_processes(SystemProcess** system_procs, int* no_of_sys_processes) const { + return _impl->system_processes(system_procs, no_of_sys_processes); +} + +SystemProcessInterface::SystemProcessInterface() { + _impl = NULL; +} + +bool SystemProcessInterface::initialize() { + _impl = new SystemProcessInterface::SystemProcesses(); + return NULL == _impl ? false : _impl->initialize(); +} + +SystemProcessInterface::~SystemProcessInterface() { + if (_impl != NULL) { + delete _impl; + } +} + +CPUInformationInterface::CPUInformationInterface() { + _cpu_info = NULL; +} + +bool CPUInformationInterface::initialize() { + _cpu_info = new CPUInformation(); + if (NULL == _cpu_info) { + return false; + } + _cpu_info->set_number_of_hardware_threads(VM_Version_Ext::number_of_threads()); + _cpu_info->set_number_of_cores(VM_Version_Ext::number_of_cores()); + _cpu_info->set_number_of_sockets(VM_Version_Ext::number_of_sockets()); + _cpu_info->set_cpu_name(VM_Version_Ext::cpu_name()); + _cpu_info->set_cpu_description(VM_Version_Ext::cpu_description()); + + return true; +} + +CPUInformationInterface::~CPUInformationInterface() { + if (_cpu_info != NULL) { + if (_cpu_info->cpu_name() != NULL) { + const char* cpu_name = _cpu_info->cpu_name(); + FREE_C_HEAP_ARRAY(char, cpu_name); + _cpu_info->set_cpu_name(NULL); + } + if (_cpu_info->cpu_description() != NULL) { + const char* cpu_desc = _cpu_info->cpu_description(); + FREE_C_HEAP_ARRAY(char, cpu_desc); + _cpu_info->set_cpu_description(NULL); + } + delete _cpu_info; + } +} + +int CPUInformationInterface::cpu_information(CPUInformation& cpu_info) { + if (_cpu_info == NULL) { + return OS_ERR; + } + + cpu_info = *_cpu_info; // shallow copy assignment + return OS_OK; +} diff --git a/src/hotspot/os/solaris/os_perf_solaris.cpp b/src/hotspot/os/solaris/os_perf_solaris.cpp new file mode 100644 index 00000000000..9b04ee7a966 --- /dev/null +++ b/src/hotspot/os/solaris/os_perf_solaris.cpp @@ -0,0 +1,756 @@ +/* + * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "jvm.h" +#include "memory/allocation.inline.hpp" +#include "runtime/os.hpp" +#include "runtime/os_perf.hpp" +#include "os_solaris.inline.hpp" +#include "utilities/macros.hpp" + +#include CPU_HEADER(vm_version_ext) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const double NANOS_PER_SEC = 1000000000.0; + +struct CPUPerfTicks { + kstat_t* kstat; + uint64_t last_idle; + uint64_t last_total; + double last_ratio; +}; + +struct CPUPerfCounters { + int nProcs; + CPUPerfTicks* jvmTicks; + kstat_ctl_t* kstat_ctrl; +}; + +static int get_info(const char* path, void* info, size_t s, off_t o) { + assert(path != NULL, "path is NULL!"); + assert(info != NULL, "info is NULL!"); + + int fd = -1; + + if ((fd = open(path, O_RDONLY)) < 0) { + return OS_ERR; + } + if (pread(fd, info, s, o) != s) { + close(fd); + return OS_ERR; + } + close(fd); + return OS_OK; +} + +static int get_psinfo2(void* info, size_t s, off_t o) { + return get_info("/proc/self/psinfo", info, s, o); +} + +static int get_psinfo(psinfo_t* info) { + return get_psinfo2(info, sizeof(*info), 0); +} + +static int get_psinfo(char* file, psinfo_t* info) { + assert(file != NULL, "file is NULL!"); + assert(info != NULL, "info is NULL!"); + return get_info(file, info, sizeof(*info), 0); +} + + +static int get_usage(prusage_t* usage) { + assert(usage != NULL, "usage is NULL!"); + return get_info("/proc/self/usage", usage, sizeof(*usage), 0); +} + +static int read_cpustat(kstat_ctl_t* kstat_ctrl, CPUPerfTicks* load, cpu_stat_t* cpu_stat) { + assert(kstat_ctrl != NULL, "kstat_ctrl pointer is NULL!"); + assert(load != NULL, "load pointer is NULL!"); + assert(cpu_stat != NULL, "cpu_stat pointer is NULL!"); + + if (load->kstat == NULL) { + // no handle. + return OS_ERR; + } + if (kstat_read(kstat_ctrl, load->kstat, cpu_stat) == OS_ERR) { + // disable handle for this CPU + load->kstat = NULL; + return OS_ERR; + } + return OS_OK; +} + +static double get_cpu_load(int which_logical_cpu, CPUPerfCounters* counters) { + assert(counters != NULL, "counters pointer is NULL!"); + + cpu_stat_t cpu_stat = {0}; + + if (which_logical_cpu >= counters->nProcs) { + return .0; + } + + CPUPerfTicks load = counters->jvmTicks[which_logical_cpu]; + if (read_cpustat(counters->kstat_ctrl, &load, &cpu_stat) != OS_OK) { + return .0; + } + + uint_t* usage = cpu_stat.cpu_sysinfo.cpu; + if (usage == NULL) { + return .0; + } + + uint64_t c_idle = usage[CPU_IDLE]; + uint64_t c_total = 0; + + for (int i = 0; i < CPU_STATES; i++) { + c_total += usage[i]; + } + + // Calculate diff against previous snapshot + uint64_t d_idle = c_idle - load.last_idle; + uint64_t d_total = c_total - load.last_total; + + /** update if weve moved */ + if (d_total > 0) { + // Save current values for next time around + load.last_idle = c_idle; + load.last_total = c_total; + load.last_ratio = (double) (d_total - d_idle) / d_total; + } + + return load.last_ratio; +} + +static int get_boot_time(uint64_t* time) { + assert(time != NULL, "time pointer is NULL!"); + setutxent(); + for(;;) { + struct utmpx* u; + if ((u = getutxent()) == NULL) { + break; + } + if (u->ut_type == BOOT_TIME) { + *time = u->ut_xtime; + endutxent(); + return OS_OK; + } + } + endutxent(); + return OS_ERR; +} + +static int get_noof_context_switches(CPUPerfCounters* counters, uint64_t* switches) { + assert(switches != NULL, "switches pointer is NULL!"); + assert(counters != NULL, "counter pointer is NULL!"); + *switches = 0; + uint64_t s = 0; + + // Collect data from all CPUs + for (int i = 0; i < counters->nProcs; i++) { + cpu_stat_t cpu_stat = {0}; + CPUPerfTicks load = counters->jvmTicks[i]; + + if (read_cpustat(counters->kstat_ctrl, &load, &cpu_stat) == OS_OK) { + s += cpu_stat.cpu_sysinfo.pswitch; + } else { + //fail fast... + return OS_ERR; + } + } + *switches = s; + return OS_OK; +} + +static int perf_context_switch_rate(CPUPerfCounters* counters, double* rate) { + assert(counters != NULL, "counters is NULL!"); + assert(rate != NULL, "rate pointer is NULL!"); + static pthread_mutex_t contextSwitchLock = PTHREAD_MUTEX_INITIALIZER; + static uint64_t lastTime = 0; + static uint64_t lastSwitches = 0; + static double lastRate = 0.0; + + uint64_t lt = 0; + int res = 0; + + if (lastTime == 0) { + uint64_t tmp; + if (get_boot_time(&tmp) < 0) { + return OS_ERR; + } + lt = tmp * 1000; + } + + res = OS_OK; + + pthread_mutex_lock(&contextSwitchLock); + { + + uint64_t sw = 0; + clock_t t, d; + + if (lastTime == 0) { + lastTime = lt; + } + + t = clock(); + d = t - lastTime; + + if (d == 0) { + *rate = lastRate; + } else if (get_noof_context_switches(counters, &sw)== OS_OK) { + *rate = ((double)(sw - lastSwitches) / d) * 1000; + lastRate = *rate; + lastSwitches = sw; + lastTime = t; + } else { + *rate = 0.0; + res = OS_ERR; + } + if (*rate < 0.0) { + *rate = 0.0; + lastRate = 0.0; + } + } + pthread_mutex_unlock(&contextSwitchLock); + return res; + } + + + +class CPUPerformanceInterface::CPUPerformance : public CHeapObj { + friend class CPUPerformanceInterface; + private: + CPUPerfCounters _counters; + int cpu_load(int which_logical_cpu, double* cpu_load); + int context_switch_rate(double* rate); + int cpu_load_total_process(double* cpu_load); + int cpu_loads_process(double* pjvmUserLoad, double* pjvmKernelLoad, double* psystemTotalLoad); + + CPUPerformance(); + ~CPUPerformance(); + bool initialize(); +}; + +CPUPerformanceInterface::CPUPerformance::CPUPerformance() { + _counters.nProcs = 0; + _counters.jvmTicks = NULL; + _counters.kstat_ctrl = NULL; +} + +bool CPUPerformanceInterface::CPUPerformance::initialize() { + // initialize kstat control structure, + _counters.kstat_ctrl = kstat_open(); + assert(_counters.kstat_ctrl != NULL, "error initializing kstat control structure!"); + + if (NULL == _counters.kstat_ctrl) { + return false; + } + + // Get number of CPU(s) + if ((_counters.nProcs = sysconf(_SC_NPROCESSORS_ONLN)) == OS_ERR) { + // ignore error? + _counters.nProcs = 1; + } + + assert(_counters.nProcs > 0, "no CPUs detected in sysconf call!"); + if (_counters.nProcs == 0) { + return false; + } + + // Data structure(s) for saving CPU load (one per CPU) + size_t tick_array_size = _counters.nProcs * sizeof(CPUPerfTicks); + _counters.jvmTicks = (CPUPerfTicks*)NEW_C_HEAP_ARRAY(char, tick_array_size, mtInternal); + if (NULL == _counters.jvmTicks) { + return false; + } + memset(_counters.jvmTicks, 0, tick_array_size); + + // Get kstat cpu_stat counters for every CPU + // loop over kstat to find our cpu_stat(s) + int i = 0; + for (kstat_t* kstat = _counters.kstat_ctrl->kc_chain; kstat != NULL; kstat = kstat->ks_next) { + if (strncmp(kstat->ks_module, "cpu_stat", 8) == 0) { + if (kstat_read(_counters.kstat_ctrl, kstat, NULL) == OS_ERR) { + continue; + } + if (i == _counters.nProcs) { + // more cpu_stats than reported CPUs + break; + } + _counters.jvmTicks[i++].kstat = kstat; + } + } + return true; +} + +CPUPerformanceInterface::CPUPerformance::~CPUPerformance() { + if (_counters.jvmTicks != NULL) { + FREE_C_HEAP_ARRAY(char, _counters.jvmTicks); + } + if (_counters.kstat_ctrl != NULL) { + kstat_close(_counters.kstat_ctrl); + } +} + +int CPUPerformanceInterface::CPUPerformance::cpu_load(int which_logical_cpu, double* cpu_load) { + assert(cpu_load != NULL, "cpu_load pointer is NULL!"); + double t = .0; + if (-1 == which_logical_cpu) { + for (int i = 0; i < _counters.nProcs; i++) { + t += get_cpu_load(i, &_counters); + } + // Cap total systemload to 1.0 + t = MIN2((t / _counters.nProcs), 1.0); + } else { + t = MIN2(get_cpu_load(which_logical_cpu, &_counters), 1.0); + } + + *cpu_load = t; + return OS_OK; +} + +int CPUPerformanceInterface::CPUPerformance::cpu_load_total_process(double* cpu_load) { + assert(cpu_load != NULL, "cpu_load pointer is NULL!"); + + psinfo_t info; + + // Get the percentage of "recent cpu usage" from all the lwp:s in the JVM:s + // process. This is returned as a value between 0.0 and 1.0 multiplied by 0x8000. + if (get_psinfo2(&info.pr_pctcpu, sizeof(info.pr_pctcpu), offsetof(psinfo_t, pr_pctcpu)) != 0) { + *cpu_load = 0.0; + return OS_ERR; + } + *cpu_load = (double) info.pr_pctcpu / 0x8000; + return OS_OK; +} + +int CPUPerformanceInterface::CPUPerformance::cpu_loads_process(double* pjvmUserLoad, double* pjvmKernelLoad, double* psystemTotalLoad) { + assert(pjvmUserLoad != NULL, "pjvmUserLoad not inited"); + assert(pjvmKernelLoad != NULL, "pjvmKernelLoad not inited"); + assert(psystemTotalLoad != NULL, "psystemTotalLoad not inited"); + + static uint64_t lastTime; + static uint64_t lastUser, lastKernel; + static double lastUserRes, lastKernelRes; + + pstatus_t pss; + psinfo_t info; + + *pjvmKernelLoad = *pjvmUserLoad = *psystemTotalLoad = 0; + if (get_info("/proc/self/status", &pss.pr_utime, sizeof(timestruc_t)*2, offsetof(pstatus_t, pr_utime)) != 0) { + return OS_ERR; + } + + if (get_psinfo(&info) != 0) { + return OS_ERR; + } + + // get the total time in user, kernel and total time + // check ratios for 'lately' and multiply the 'recent load'. + uint64_t time = (info.pr_time.tv_sec * NANOS_PER_SEC) + info.pr_time.tv_nsec; + uint64_t user = (pss.pr_utime.tv_sec * NANOS_PER_SEC) + pss.pr_utime.tv_nsec; + uint64_t kernel = (pss.pr_stime.tv_sec * NANOS_PER_SEC) + pss.pr_stime.tv_nsec; + uint64_t diff = time - lastTime; + double load = (double) info.pr_pctcpu / 0x8000; + + if (diff > 0) { + lastUserRes = (load * (user - lastUser)) / diff; + lastKernelRes = (load * (kernel - lastKernel)) / diff; + + // BUG9182835 - patch for clamping these values to sane ones. + lastUserRes = MIN2(1, lastUserRes); + lastUserRes = MAX2(0, lastUserRes); + lastKernelRes = MIN2(1, lastKernelRes); + lastKernelRes = MAX2(0, lastKernelRes); + } + + double t = .0; + cpu_load(-1, &t); + // clamp at user+system and 1.0 + if (lastUserRes + lastKernelRes > t) { + t = MIN2(lastUserRes + lastKernelRes, 1.0); + } + + *pjvmUserLoad = lastUserRes; + *pjvmKernelLoad = lastKernelRes; + *psystemTotalLoad = t; + + lastTime = time; + lastUser = user; + lastKernel = kernel; + + return OS_OK; +} + +int CPUPerformanceInterface::CPUPerformance::context_switch_rate(double* rate) { + return perf_context_switch_rate(&_counters, rate); +} + +CPUPerformanceInterface::CPUPerformanceInterface() { + _impl = NULL; +} + +bool CPUPerformanceInterface::initialize() { + _impl = new CPUPerformanceInterface::CPUPerformance(); + return _impl != NULL && _impl->initialize(); +} + +CPUPerformanceInterface::~CPUPerformanceInterface(void) { + if (_impl != NULL) { + delete _impl; + } +} + +int CPUPerformanceInterface::cpu_load(int which_logical_cpu, double* cpu_load) const { + return _impl->cpu_load(which_logical_cpu, cpu_load); +} + +int CPUPerformanceInterface::cpu_load_total_process(double* cpu_load) const { + return _impl->cpu_load_total_process(cpu_load); +} + +int CPUPerformanceInterface::cpu_loads_process(double* pjvmUserLoad, double* pjvmKernelLoad, double* psystemTotalLoad) const { + return _impl->cpu_loads_process(pjvmUserLoad, pjvmKernelLoad, psystemTotalLoad); +} + +int CPUPerformanceInterface::context_switch_rate(double* rate) const { + return _impl->context_switch_rate(rate); +} + +class SystemProcessInterface::SystemProcesses : public CHeapObj { + friend class SystemProcessInterface; + private: + class ProcessIterator : public CHeapObj { + friend class SystemProcessInterface::SystemProcesses; + private: + DIR* _dir; + struct dirent* _entry; + bool _valid; + + ProcessIterator(); + ~ProcessIterator(); + bool initialize(); + + bool is_valid() const { return _valid; } + bool is_valid_entry(struct dirent* const entry) const; + bool is_dir(const char* const name) const; + char* allocate_string(const char* const str) const; + int current(SystemProcess* const process_info); + int next_process(); + }; + + ProcessIterator* _iterator; + SystemProcesses(); + bool initialize(); + ~SystemProcesses(); + + //information about system processes + int system_processes(SystemProcess** system_processes, int* no_of_sys_processes) const; +}; + +bool SystemProcessInterface::SystemProcesses::ProcessIterator::is_dir(const char* name) const { + struct stat64 mystat; + int ret_val = 0; + + ret_val = ::stat64(name, &mystat); + + if (ret_val < 0) { + return false; + } + ret_val = S_ISDIR(mystat.st_mode); + return ret_val > 0; +} + +// if it has a numeric name, is a directory and has a 'psinfo' file in it +bool SystemProcessInterface::SystemProcesses::ProcessIterator::is_valid_entry(struct dirent* entry) const { + // ignore the "." and ".." directories + if ((strcmp(entry->d_name, ".") == 0) || + (strcmp(entry->d_name, "..") == 0)) { + return false; + } + + char buffer[PATH_MAX] = {0}; + uint64_t size = 0; + bool result = false; + FILE *fp = NULL; + + if (atoi(entry->d_name) != 0) { + jio_snprintf(buffer, PATH_MAX, "/proc/%s", entry->d_name); + + if (is_dir(buffer)) { + memset(buffer, 0, PATH_MAX); + jio_snprintf(buffer, PATH_MAX, "/proc/%s/psinfo", entry->d_name); + if ((fp = fopen(buffer, "r")) != NULL) { + int nread = 0; + psinfo_t psinfo_data; + if ((nread = fread(&psinfo_data, 1, sizeof(psinfo_t), fp)) != -1) { + // only considering system process owned by root + if (psinfo_data.pr_uid == 0) { + result = true; + } + } + } + } + } + + if (fp != NULL) { + fclose(fp); + } + + return result; +} + +char* SystemProcessInterface::SystemProcesses::ProcessIterator::allocate_string(const char* str) const { + if (str != NULL) { + size_t len = strlen(str); + char* tmp = NEW_C_HEAP_ARRAY(char, len+1, mtInternal); + strncpy(tmp, str, len); + tmp[len] = '\0'; + return tmp; + } + return NULL; +} + +int SystemProcessInterface::SystemProcesses::ProcessIterator::current(SystemProcess* process_info) { + if (!is_valid()) { + return OS_ERR; + } + + char psinfo_path[PATH_MAX] = {0}; + jio_snprintf(psinfo_path, PATH_MAX, "/proc/%s/psinfo", _entry->d_name); + + FILE *fp = NULL; + if ((fp = fopen(psinfo_path, "r")) == NULL) { + return OS_ERR; + } + + int nread = 0; + psinfo_t psinfo_data; + if ((nread = fread(&psinfo_data, 1, sizeof(psinfo_t), fp)) == -1) { + fclose(fp); + return OS_ERR; + } + + char *exe_path = NULL; + if ((psinfo_data.pr_fname != NULL) && + (psinfo_data.pr_psargs != NULL)) { + char *path_substring = strstr(psinfo_data.pr_psargs, psinfo_data.pr_fname); + if (path_substring != NULL) { + int len = path_substring - psinfo_data.pr_psargs; + exe_path = NEW_C_HEAP_ARRAY(char, len+1, mtInternal); + if (exe_path != NULL) { + jio_snprintf(exe_path, len, "%s", psinfo_data.pr_psargs); + exe_path[len] = '\0'; + } + } + } + + process_info->set_pid(atoi(_entry->d_name)); + process_info->set_name(allocate_string(psinfo_data.pr_fname)); + process_info->set_path(allocate_string(exe_path)); + process_info->set_command_line(allocate_string(psinfo_data.pr_psargs)); + + if (exe_path != NULL) { + FREE_C_HEAP_ARRAY(char, exe_path); + } + + if (fp != NULL) { + fclose(fp); + } + + return OS_OK; +} + +int SystemProcessInterface::SystemProcesses::ProcessIterator::next_process() { + struct dirent* entry; + + if (!is_valid()) { + return OS_ERR; + } + + do { + if ((entry = os::readdir(_dir, _entry)) == NULL) { + // error + _valid = false; + return OS_ERR; + } + } while(!is_valid_entry(_entry)); + + _valid = true; + return OS_OK; +} + +SystemProcessInterface::SystemProcesses::ProcessIterator::ProcessIterator() { + _dir = NULL; + _entry = NULL; + _valid = false; +} + +bool SystemProcessInterface::SystemProcesses::ProcessIterator::initialize() { + _dir = opendir("/proc"); + _entry = (struct dirent*)NEW_C_HEAP_ARRAY(char, sizeof(struct dirent) + _PC_NAME_MAX + 1, mtInternal); + if (NULL == _entry) { + return false; + } + _valid = true; + next_process(); + + return true; +} + +SystemProcessInterface::SystemProcesses::ProcessIterator::~ProcessIterator() { + if (_entry != NULL) { + FREE_C_HEAP_ARRAY(char, _entry); + } + + if (_dir != NULL) { + closedir(_dir); + } +} + +SystemProcessInterface::SystemProcesses::SystemProcesses() { + _iterator = NULL; +} + +bool SystemProcessInterface::SystemProcesses::initialize() { + _iterator = new SystemProcessInterface::SystemProcesses::ProcessIterator(); + return _iterator != NULL && _iterator->initialize(); +} + +SystemProcessInterface::SystemProcesses::~SystemProcesses() { + if (_iterator != NULL) { + delete _iterator; + } +} + +int SystemProcessInterface::SystemProcesses::system_processes(SystemProcess** system_processes, int* no_of_sys_processes) const { + assert(system_processes != NULL, "system_processes pointer is NULL!"); + assert(no_of_sys_processes != NULL, "system_processes counter pointer is NULL!"); + assert(_iterator != NULL, "iterator is NULL!"); + + // initialize pointers + *no_of_sys_processes = 0; + *system_processes = NULL; + + while (_iterator->is_valid()) { + SystemProcess* tmp = new SystemProcess(); + _iterator->current(tmp); + + //if already existing head + if (*system_processes != NULL) { + //move "first to second" + tmp->set_next(*system_processes); + } + // new head + *system_processes = tmp; + // increment + (*no_of_sys_processes)++; + // step forward + _iterator->next_process(); + } + return OS_OK; +} + +int SystemProcessInterface::system_processes(SystemProcess** system_procs, int* no_of_sys_processes) const { + return _impl->system_processes(system_procs, no_of_sys_processes); +} + +SystemProcessInterface::SystemProcessInterface() { + _impl = NULL; +} + +bool SystemProcessInterface::initialize() { + _impl = new SystemProcessInterface::SystemProcesses(); + return _impl != NULL && _impl->initialize(); + +} + +SystemProcessInterface::~SystemProcessInterface() { + if (_impl != NULL) { + delete _impl; + } +} + +CPUInformationInterface::CPUInformationInterface() { + _cpu_info = NULL; +} + +bool CPUInformationInterface::initialize() { + _cpu_info = new CPUInformation(); + if (_cpu_info == NULL) { + return false; + } + _cpu_info->set_number_of_hardware_threads(VM_Version_Ext::number_of_threads()); + _cpu_info->set_number_of_cores(VM_Version_Ext::number_of_cores()); + _cpu_info->set_number_of_sockets(VM_Version_Ext::number_of_sockets()); + _cpu_info->set_cpu_name(VM_Version_Ext::cpu_name()); + _cpu_info->set_cpu_description(VM_Version_Ext::cpu_description()); + return true; +} + +CPUInformationInterface::~CPUInformationInterface() { + if (_cpu_info != NULL) { + if (_cpu_info->cpu_name() != NULL) { + const char* cpu_name = _cpu_info->cpu_name(); + FREE_C_HEAP_ARRAY(char, cpu_name); + _cpu_info->set_cpu_name(NULL); + } + if (_cpu_info->cpu_description() != NULL) { + const char* cpu_desc = _cpu_info->cpu_description(); + FREE_C_HEAP_ARRAY(char, cpu_desc); + _cpu_info->set_cpu_description(NULL); + } + delete _cpu_info; + } +} + +int CPUInformationInterface::cpu_information(CPUInformation& cpu_info) { + if (_cpu_info == NULL) { + return OS_ERR; + } + + cpu_info = *_cpu_info; // shallow copy assignment + return OS_OK; +} diff --git a/src/hotspot/os/windows/os_perf_windows.cpp b/src/hotspot/os/windows/os_perf_windows.cpp new file mode 100644 index 00000000000..88d3f314f22 --- /dev/null +++ b/src/hotspot/os/windows/os_perf_windows.cpp @@ -0,0 +1,1332 @@ +/* + * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "logging/log.hpp" +#include "memory/allocation.inline.hpp" +#include "memory/resourceArea.hpp" +#include "pdh_interface.hpp" +#include "runtime/os_perf.hpp" +#include "runtime/os.hpp" +#include "vm_version_ext_x86.hpp" +#include "utilities/macros.hpp" +#include +#include +#include + +/* + * Windows provides a vast plethora of performance objects and counters, + * consumption of which is assisted using the Performance Data Helper (PDH) interface. + * We import a selected few api entry points from PDH, see pdh_interface.hpp. + * + * The code located in this file is to a large extent an abstraction over much of the + * plumbing needed to start consuming an object and/or counter of choice. + * + */ + + /* + * How to use: + * 1. Create query + * 2. Add counters to the query + * 3. Collect the performance data using the query + * 4. Display the performance data using the counters associated with the query + * 5. Destroy query (counter destruction implied) + */ + +/* + * Every PDH artifact, like processor, process, thread, memory, and so forth are + * identified with an index that is always the same irrespective + * of the localized version of the operating system or service pack installed. + * INFO: Using PDH APIs Correctly in a Localized Language (Q287159) + * http://support.microsoft.com/default.aspx?scid=kb;EN-US;q287159 + * + * To find the correct index for an object or counter, inspect the registry key / value: + * [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\009\Counter] + * + * some common PDH indexes + */ +static const DWORD PDH_PROCESSOR_IDX = 238; +static const DWORD PDH_PROCESSOR_TIME_IDX = 6; +static const DWORD PDH_PRIV_PROCESSOR_TIME_IDX = 144; +static const DWORD PDH_PROCESS_IDX = 230; +static const DWORD PDH_ID_PROCESS_IDX = 784; +static const DWORD PDH_CONTEXT_SWITCH_RATE_IDX = 146; +static const DWORD PDH_SYSTEM_IDX = 2; + +/* useful pdh fmt's */ +static const char* const OBJECT_COUNTER_FMT = "\\%s\\%s"; +static const size_t OBJECT_COUNTER_FMT_LEN = 2; +static const char* const OBJECT_WITH_INSTANCES_COUNTER_FMT = "\\%s(%s)\\%s"; +static const size_t OBJECT_WITH_INSTANCES_COUNTER_FMT_LEN = 4; +static const char* const PROCESS_OBJECT_INSTANCE_COUNTER_FMT = "\\%s(%s#%s)\\%s"; +static const size_t PROCESS_OBJECT_INSTANCE_COUNTER_FMT_LEN = 5; + +static const char* process_image_name = NULL; // for example, "java" but could have another image name +static char* pdh_IDProcess_counter_fmt = NULL; // "\Process(java#%d)\ID Process" */ + +// Need to limit how often we update a query to minimize the heisenberg effect. +// (PDH behaves erratically if the counters are queried too often, especially counters that +// store and use values from two consecutive updates, like cpu load.) +static const int min_update_interval_millis = 500; + +/* +* Structs for PDH queries. +*/ +typedef struct { + HQUERY query; + s8 lastUpdate; // Last time query was updated (current millis). +} UpdateQueryS, *UpdateQueryP; + + +typedef struct { + UpdateQueryS query; + HCOUNTER counter; + bool initialized; +} CounterQueryS, *CounterQueryP; + +typedef struct { + UpdateQueryS query; + HCOUNTER* counters; + int noOfCounters; + bool initialized; +} MultiCounterQueryS, *MultiCounterQueryP; + +typedef struct { + MultiCounterQueryP queries; + int size; + bool initialized; +} MultiCounterQuerySetS, *MultiCounterQuerySetP; + +static void pdh_cleanup(HQUERY* const query, HCOUNTER* const counter) { + if (counter != NULL && *counter != NULL) { + PdhDll::PdhRemoveCounter(*counter); + *counter = NULL; + } + if (query != NULL && *query != NULL) { + PdhDll::PdhCloseQuery(*query); + *query = NULL; + } +} + +static CounterQueryP create_counter_query() { + CounterQueryP const query = NEW_C_HEAP_ARRAY(CounterQueryS, 1, mtInternal); + memset(query, 0, sizeof(CounterQueryS)); + return query; +} + +static void destroy_counter_query(CounterQueryP query) { + assert(query != NULL, "invariant"); + pdh_cleanup(&query->query.query, &query->counter); + FREE_C_HEAP_ARRAY(CounterQueryS, query); +} + +static MultiCounterQueryP create_multi_counter_query() { + MultiCounterQueryP const query = NEW_C_HEAP_ARRAY(MultiCounterQueryS, 1, mtInternal); + memset(query, 0, sizeof(MultiCounterQueryS)); + return query; +} + +static void destroy_counter_query(MultiCounterQueryP counter_query) { + if (counter_query != NULL) { + for (int i = 0; i < counter_query->noOfCounters; ++i) { + pdh_cleanup(NULL, &counter_query->counters[i]); + } + FREE_C_HEAP_ARRAY(char, counter_query->counters); + pdh_cleanup(&counter_query->query.query, NULL); + FREE_C_HEAP_ARRAY(MultiCounterQueryS, counter_query); + } +} + +static void destroy_counter_query(MultiCounterQuerySetP counter_query_set) { + for (int i = 0; i < counter_query_set->size; i++) { + for (int j = 0; j < counter_query_set->queries[i].noOfCounters; ++j) { + pdh_cleanup(NULL, &counter_query_set->queries[i].counters[j]); + } + FREE_C_HEAP_ARRAY(char, counter_query_set->queries[i].counters); + pdh_cleanup(&counter_query_set->queries[i].query.query, NULL); + } + FREE_C_HEAP_ARRAY(MultiCounterQueryS, counter_query_set->queries); + FREE_C_HEAP_ARRAY(MultiCounterQuerySetS, counter_query_set); +} + +static int open_query(HQUERY* query) { + return PdhDll::PdhOpenQuery(NULL, 0, query); +} + +template +static int open_query(QueryP query) { + return open_query(&query->query); +} + +static int allocate_counters(MultiCounterQueryP query, size_t nofCounters) { + assert(query != NULL, "invariant"); + assert(!query->initialized, "invariant"); + assert(0 == query->noOfCounters, "invariant"); + assert(query->counters == NULL, "invariant"); + query->counters = (HCOUNTER*)NEW_C_HEAP_ARRAY(char, nofCounters * sizeof(HCOUNTER), mtInternal); + if (query->counters == NULL) { + return OS_ERR; + } + memset(query->counters, 0, nofCounters * sizeof(HCOUNTER)); + query->noOfCounters = (int)nofCounters; + return OS_OK; +} + +static int allocate_counters(MultiCounterQuerySetP query_set, size_t nofCounters) { + assert(query_set != NULL, "invariant"); + assert(!query_set->initialized, "invariant"); + for (int i = 0; i < query_set->size; ++i) { + if (allocate_counters(&query_set->queries[i], nofCounters) != OS_OK) { + return OS_ERR; + } + } + return OS_OK; +} + +static void deallocate_counters(MultiCounterQueryP query) { + if (query->counters != NULL) { + FREE_C_HEAP_ARRAY(char, query->counters); + query->counters = NULL; + query->noOfCounters = 0; + } +} + +static OSReturn add_counter(UpdateQueryP query, HCOUNTER* counter, const char* path, bool first_sample_on_init) { + assert(query != NULL, "invariant"); + assert(counter != NULL, "invariant"); + assert(path != NULL, "invariant"); + if (query->query == NULL) { + if (open_query(query) != ERROR_SUCCESS) { + return OS_ERR; + } + } + assert(query->query != NULL, "invariant"); + PDH_STATUS status = PdhDll::PdhAddCounter(query->query, path, 0, counter); + if (PDH_CSTATUS_NO_OBJECT == status || PDH_CSTATUS_NO_COUNTER == status) { + return OS_ERR; + } + /* + * According to the MSDN documentation, rate counters must be read twice: + * + * "Obtaining the value of rate counters such as Page faults/sec requires that + * PdhCollectQueryData be called twice, with a specific time interval between + * the two calls, before calling PdhGetFormattedCounterValue. Call Sleep to + * implement the waiting period between the two calls to PdhCollectQueryData." + * + * Take the first sample here already to allow for the next "real" sample + * to succeed. + */ + if (first_sample_on_init) { + PdhDll::PdhCollectQueryData(query->query); + } + return OS_OK; +} + +template +static OSReturn add_counter(QueryP counter_query, HCOUNTER* counter, const char* path, bool first_sample_on_init) { + assert(counter_query != NULL, "invariant"); + assert(counter != NULL, "invariant"); + assert(path != NULL, "invariant"); + return add_counter(&counter_query->query, counter, path, first_sample_on_init); +} + +static OSReturn add_counter(CounterQueryP counter_query, const char* path, bool first_sample_on_init) { + if (add_counter(counter_query, &counter_query->counter, path, first_sample_on_init) != OS_OK) { + // performance counter might be disabled in the registry + return OS_ERR; + } + counter_query->initialized = true; + return OS_OK; +} + +static OSReturn add_process_counter(MultiCounterQueryP query, int slot_index, const char* path, bool first_sample_on_init) { + assert(query != NULL, "invariant"); + assert(query != NULL, "invariant"); + assert(slot_index < query->noOfCounters, "invariant"); + assert(query->counters[slot_index] == NULL, "invariant"); + const OSReturn ret = add_counter(query, &query->counters[slot_index], path, first_sample_on_init); + if (OS_OK == ret) { + if (slot_index + 1 == query->noOfCounters) { + query->initialized = true; + } + } + return ret; +} + +static int collect_query_data(UpdateQueryP update_query) { + assert(update_query != NULL, "invariant"); + const s8 now = os::javaTimeMillis(); + if (now - update_query->lastUpdate > min_update_interval_millis) { + if (PdhDll::PdhCollectQueryData(update_query->query) != ERROR_SUCCESS) { + return OS_ERR; + } + update_query->lastUpdate = now; + } + return OS_OK; +} + +template +static int collect_query_data(Query* counter_query) { + assert(counter_query != NULL, "invariant"); + return collect_query_data(&counter_query->query); +} + +static int formatted_counter_value(HCOUNTER counter, DWORD format, PDH_FMT_COUNTERVALUE* const value) { + assert(value != NULL, "invariant"); + if (PdhDll::PdhGetFormattedCounterValue(counter, format, NULL, value) != ERROR_SUCCESS) { + return OS_ERR; + } + return OS_OK; +} + +/* +* Working against the Process object and it's related counters is inherently problematic +* when using the PDH API: +* +* Using PDH, a process is not primarily identified by the process id, +* but with a sequential number, for example \Process(java#0), \Process(java#1), ... +* The really bad part is that this list is reset as soon as a process exits: +* If \Process(java#1) exits, \Process(java#3) now becomes \Process(java#2) etc. +* +* The PDH api requires a process identifier to be submitted when registering +* a query, but as soon as the list resets, the query is invalidated (since the name changed). +* +* Solution: +* The #number identifier for a Process query can only decrease after process creation. +* +* We therefore create an array of counter queries for all process object instances +* up to and including ourselves: +* +* Ex. we come in as third process instance (java#2), we then create and register +* queries for the following Process object instances: +* java#0, java#1, java#2 +* +* current_query_index_for_process() keeps track of the current "correct" query +* (in order to keep this index valid when the list resets from underneath, +* ensure to call current_query_index_for_process() before every query involving +* Process object instance data). +*/ +static int current_query_index_for_process() { + assert(process_image_name != NULL, "invariant"); + assert(pdh_IDProcess_counter_fmt != NULL, "invariant"); + HQUERY tmpQuery = NULL; + if (open_query(&tmpQuery) != ERROR_SUCCESS) { + return 0; + } + char counter[512]; + HCOUNTER handle_counter = NULL; + // iterate over all instance indexes and try to find our own pid + for (int index = 0; index < max_intx; index++) { + jio_snprintf(counter, sizeof(counter) - 1, pdh_IDProcess_counter_fmt, index); + assert(strlen(counter) < sizeof(counter), "invariant"); + if (PdhDll::PdhAddCounter(tmpQuery, counter, 0, &handle_counter) != ERROR_SUCCESS) { + pdh_cleanup(&tmpQuery, &handle_counter); + return 0; + } + const PDH_STATUS res = PdhDll::PdhCollectQueryData(tmpQuery); + if (res == PDH_INVALID_HANDLE || res == PDH_NO_DATA) { + pdh_cleanup(&tmpQuery, &handle_counter); + return 0; + } else { + PDH_FMT_COUNTERVALUE counter_value; + formatted_counter_value(handle_counter, PDH_FMT_LONG, &counter_value); + pdh_cleanup(NULL, &handle_counter); + if ((LONG)os::current_process_id() == counter_value.longValue) { + pdh_cleanup(&tmpQuery, NULL); + return index; + } + } + } + pdh_cleanup(&tmpQuery, NULL); + return 0; +} + +static MultiCounterQuerySetP create_process_counter_query() { + MultiCounterQuerySetP const query = NEW_C_HEAP_ARRAY(MultiCounterQuerySetS, 1, mtInternal); + memset(query, 0, sizeof(MultiCounterQuerySetS)); + const int current_process_idx = current_query_index_for_process(); + query->queries = NEW_C_HEAP_ARRAY(MultiCounterQueryS, current_process_idx + 1, mtInternal); + memset(query->queries, 0, sizeof(MultiCounterQueryS) * (current_process_idx + 1)); + query->size = current_process_idx + 1; + return query; +} + +static MultiCounterQueryP current_process_counter_query(MultiCounterQuerySetP process_query_set) { + assert(process_query_set != NULL, "invariant"); + const int current_query_index = current_query_index_for_process(); + assert(current_query_index < process_query_set->size, "invariant"); + return &process_query_set->queries[current_query_index]; +} + +static void clear_multi_counter(MultiCounterQueryP query) { + for (int i = 0; i < query->noOfCounters; ++i) { + pdh_cleanup(NULL, &query->counters[i]); + } + pdh_cleanup(&query->query.query, NULL); +} + +static int collect_process_query_data(MultiCounterQuerySetP counter_query_set) { + const int current_process_idx = current_query_index_for_process(); + while (current_process_idx < counter_query_set->size - 1) { + const int new_size = --counter_query_set->size; + clear_multi_counter(&counter_query_set->queries[new_size]); + } + return collect_query_data(&counter_query_set->queries[current_process_idx]); +} + +static int query_process_counter(MultiCounterQuerySetP process_query_set, int slot_index, DWORD format, PDH_FMT_COUNTERVALUE* const value) { + MultiCounterQueryP const current_query = current_process_counter_query(process_query_set); + assert(current_query != NULL, "invariant"); + assert(slot_index < current_query->noOfCounters, "invariant"); + assert(current_query->counters[slot_index] != NULL, "invariant"); + return formatted_counter_value(current_query->counters[slot_index], format, value); +} + +/* + * Construct a fully qualified PDH path + * + * @param objectName a PDH Object string representation(required) + * @param counterName a PDH Counter string representation(required) + * @param imageName a process image name string, ex. "java" (opt) + * @param instance an instance string, ex. "0", "1", ... (opt) + * @return the fully qualified PDH path. + * + * Caller will need a ResourceMark. + * + * (PdhMakeCounterPath() seems buggy on concatenating instances, hence this function instead) + */ +static const char* make_fully_qualified_counter_path(const char* object_name, + const char* counter_name, + const char* image_name = NULL, + const char* instance = NULL) { + assert(object_name != NULL, "invariant"); + assert(counter_name != NULL, "invariant"); + size_t full_counter_path_len = strlen(object_name) + strlen(counter_name); + + char* full_counter_path; + size_t jio_snprintf_result = 0; + if (image_name) { + /* + * For paths using the "Process" Object. + * + * Examples: + * form: "\object_name(image_name#instance)\counter_name" + * actual: "\Process(java#2)\ID Process" + */ + full_counter_path_len += PROCESS_OBJECT_INSTANCE_COUNTER_FMT_LEN; + full_counter_path_len += strlen(image_name); + /* + * image_name must be passed together with an associated + * instance "number" ("0", "1", "2", ...). + * This is required in order to create valid "Process" Object paths. + * + * Examples: "\Process(java#0)", \Process(java#1"), ... + */ + assert(instance != NULL, "invariant"); + full_counter_path_len += strlen(instance); + full_counter_path = NEW_RESOURCE_ARRAY_RETURN_NULL(char, full_counter_path_len + 1); + if (full_counter_path == NULL) { + return NULL; + } + jio_snprintf_result = jio_snprintf(full_counter_path, + full_counter_path_len + 1, + PROCESS_OBJECT_INSTANCE_COUNTER_FMT, + object_name, + image_name, + instance, + counter_name); + } else { + if (instance) { + /* + * For paths where the Object has multiple instances. + * + * Examples: + * form: "\object_name(instance)\counter_name" + * actual: "\Processor(0)\% Privileged Time" + */ + full_counter_path_len += strlen(instance); + full_counter_path_len += OBJECT_WITH_INSTANCES_COUNTER_FMT_LEN; + } else { + /* + * For "normal" paths. + * + * Examples: + * form: "\object_name\counter_name" + * actual: "\Memory\Available Mbytes" + */ + full_counter_path_len += OBJECT_COUNTER_FMT_LEN; + } + full_counter_path = NEW_RESOURCE_ARRAY_RETURN_NULL(char, full_counter_path_len + 1); + if (full_counter_path == NULL) { + return NULL; + } + if (instance) { + jio_snprintf_result = jio_snprintf(full_counter_path, + full_counter_path_len + 1, + OBJECT_WITH_INSTANCES_COUNTER_FMT, + object_name, + instance, + counter_name); + } else { + jio_snprintf_result = jio_snprintf(full_counter_path, + full_counter_path_len + 1, + OBJECT_COUNTER_FMT, + object_name, + counter_name); + } + } + assert(full_counter_path_len == jio_snprintf_result, "invariant"); + return full_counter_path; +} + +static void log_invalid_pdh_index(DWORD index) { + log_warning(os)("Unable to resolve PDH index: (%ld)", index); + log_warning(os)("Please check the registry if this performance object/counter is disabled"); +} + +static bool is_valid_pdh_index(DWORD index) { + DWORD dummy = 0; + if (PdhDll::PdhLookupPerfNameByIndex(NULL, index, NULL, &dummy) != PDH_MORE_DATA) { + log_invalid_pdh_index(index); + return false; + } + return true; +} + +/* + * Maps an index to a resource area allocated string for the localized PDH artifact. + * + * Caller will need a ResourceMark. + * + * @param index the counter index as specified in the registry + * @param ppBuffer pointer to a char* + * @return OS_OK if successful, OS_ERR on failure. + */ +static OSReturn lookup_name_by_index(DWORD index, char** p_string) { + assert(p_string != NULL, "invariant"); + if (!is_valid_pdh_index(index)) { + return OS_ERR; + } + // determine size needed + DWORD size = 0; + PDH_STATUS status = PdhDll::PdhLookupPerfNameByIndex(NULL, index, NULL, &size); + assert(status == PDH_MORE_DATA, "invariant"); + *p_string = NEW_RESOURCE_ARRAY_RETURN_NULL(char, size); + if (*p_string== NULL) { + return OS_ERR; + } + if (PdhDll::PdhLookupPerfNameByIndex(NULL, index, *p_string, &size) != ERROR_SUCCESS) { + return OS_ERR; + } + if (0 == size || *p_string == NULL) { + return OS_ERR; + } + // windows vista does not null-terminate the string (although the docs says it will) + (*p_string)[size - 1] = '\0'; + return OS_OK; +} + +static const char* copy_string_to_c_heap(const char* string) { + assert(string != NULL, "invariant"); + const size_t len = strlen(string); + char* const cheap_allocated_string = NEW_C_HEAP_ARRAY(char, len + 1, mtInternal); + if (NULL == cheap_allocated_string) { + return NULL; + } + strncpy(cheap_allocated_string, string, len + 1); + return cheap_allocated_string; +} + +/* +* Maps an index to a resource area allocated string for the localized PDH artifact. +* +* Caller will need a ResourceMark. +* +* @param index the counter index as specified in the registry +* @return localized pdh artifact string if successful, NULL on failure. +*/ +static const char* pdh_localized_artifact(DWORD pdh_artifact_index) { + char* pdh_localized_artifact_string = NULL; + // get localized name from pdh artifact index + if (lookup_name_by_index(pdh_artifact_index, &pdh_localized_artifact_string) != OS_OK) { + return NULL; + } + return pdh_localized_artifact_string; +} + +/* + * Returns the PDH string identifying the current process image name. + * Use this prefix when getting counters from the PDH process object + * representing your process. + * Ex. "Process(java#0)\Virtual Bytes" - where "java" is the PDH process + * image description. + * + * Caller needs ResourceMark. + * + * @return the process image description. NULL if the call failed. +*/ +static const char* pdh_process_image_name() { + char* module_name = NEW_RESOURCE_ARRAY_RETURN_NULL(char, MAX_PATH); + if (NULL == module_name) { + return NULL; + } + // Find our module name and use it to extract the image name used by PDH + DWORD getmfn_return = GetModuleFileName(NULL, module_name, MAX_PATH); + if (getmfn_return >= MAX_PATH || 0 == getmfn_return) { + return NULL; + } + if (os::get_last_error() == ERROR_INSUFFICIENT_BUFFER) { + return NULL; + } + char* process_image_name = strrchr(module_name, '\\'); //drop path + process_image_name++; //skip slash + char* dot_pos = strrchr(process_image_name, '.'); //drop .exe + dot_pos[0] = '\0'; + return process_image_name; +} + +static void deallocate_pdh_constants() { + if (process_image_name != NULL) { + FREE_C_HEAP_ARRAY(char, process_image_name); + process_image_name = NULL; + } + if (pdh_IDProcess_counter_fmt != NULL) { + FREE_C_HEAP_ARRAY(char, pdh_IDProcess_counter_fmt); + pdh_IDProcess_counter_fmt = NULL; + } +} + +static int allocate_pdh_constants() { + assert(process_image_name == NULL, "invariant"); + const char* pdh_image_name = pdh_process_image_name(); + if (pdh_image_name == NULL) { + return OS_ERR; + } + process_image_name = copy_string_to_c_heap(pdh_image_name); + + const char* pdh_localized_process_object = pdh_localized_artifact(PDH_PROCESS_IDX); + if (pdh_localized_process_object == NULL) { + return OS_ERR; + } + + const char* pdh_localized_IDProcess_counter = pdh_localized_artifact(PDH_ID_PROCESS_IDX); + if (pdh_localized_IDProcess_counter == NULL) { + return OS_ERR; + } + + size_t pdh_IDProcess_counter_fmt_len = strlen(process_image_name); + pdh_IDProcess_counter_fmt_len += strlen(pdh_localized_process_object); + pdh_IDProcess_counter_fmt_len += strlen(pdh_localized_IDProcess_counter); + pdh_IDProcess_counter_fmt_len += PROCESS_OBJECT_INSTANCE_COUNTER_FMT_LEN; + pdh_IDProcess_counter_fmt_len += 2; // "%d" + + assert(pdh_IDProcess_counter_fmt == NULL, "invariant"); + pdh_IDProcess_counter_fmt = NEW_C_HEAP_ARRAY_RETURN_NULL(char, pdh_IDProcess_counter_fmt_len + 1, mtInternal); + if (pdh_IDProcess_counter_fmt == NULL) { + return OS_ERR; + } + + /* "\Process(java#%d)\ID Process" */ + const size_t len = jio_snprintf(pdh_IDProcess_counter_fmt, + pdh_IDProcess_counter_fmt_len + 1, + PROCESS_OBJECT_INSTANCE_COUNTER_FMT, + pdh_localized_process_object, + process_image_name, + "%d", + pdh_localized_IDProcess_counter); + + assert(pdh_IDProcess_counter_fmt != NULL, "invariant"); + assert(len == pdh_IDProcess_counter_fmt_len, "invariant"); + return OS_OK; +} + +/* + * Enuerate the Processor PDH object and returns a buffer containing the enumerated instances. + * Caller needs ResourceMark; + * + * @return buffer if successful, NULL on failure. +*/ +static const char* enumerate_cpu_instances() { + char* processor; //'Processor' == PDH_PROCESSOR_IDX + if (lookup_name_by_index(PDH_PROCESSOR_IDX, &processor) != OS_OK) { + return NULL; + } + DWORD c_size = 0; + DWORD i_size = 0; + // enumerate all processors. + PDH_STATUS pdhStat = PdhDll::PdhEnumObjectItems(NULL, // reserved + NULL, // local machine + processor, // object to enumerate + NULL, + &c_size, + NULL, // instance buffer is NULL and + &i_size, // pass 0 length in order to get the required size + PERF_DETAIL_WIZARD, // counter detail level + 0); + if (PdhDll::PdhStatusFail((pdhStat))) { + return NULL; + } + char* const instances = NEW_RESOURCE_ARRAY_RETURN_NULL(char, i_size); + if (instances == NULL) { + return NULL; + } + c_size = 0; + pdhStat = PdhDll::PdhEnumObjectItems(NULL, // reserved + NULL, // local machine + processor, // object to enumerate + NULL, + &c_size, + instances, // now instance buffer is allocated to be filled in + &i_size, // and the required size is known + PERF_DETAIL_WIZARD, // counter detail level + 0); + if (PdhDll::PdhStatusFail((pdhStat))) { + return NULL; + } + return instances; +} + +static int count_logical_cpus(const char* instances) { + assert(instances != NULL, "invariant"); + // count logical instances. + DWORD count; + char* tmp; + for (count = 0, tmp = const_cast(instances); *tmp != '\0'; tmp = &tmp[strlen(tmp) + 1], count++); + // PDH reports an instance for each logical processor plus an instance for the total (_Total) + assert(count == os::processor_count() + 1, "invalid enumeration!"); + return count - 1; +} + +static int number_of_logical_cpus() { + static int numberOfCPUS = 0; + if (numberOfCPUS == 0) { + const char* instances = enumerate_cpu_instances(); + if (instances == NULL) { + return OS_ERR; + } + numberOfCPUS = count_logical_cpus(instances); + } + return numberOfCPUS; +} + +static double cpu_factor() { + static DWORD numCpus = 0; + static double cpuFactor = .0; + if (numCpus == 0) { + numCpus = number_of_logical_cpus(); + assert(os::processor_count() <= (int)numCpus, "invariant"); + cpuFactor = numCpus * 100; + } + return cpuFactor; +} + +static void log_error_message_on_no_PDH_artifact(const char* full_counter_name) { + log_warning(os)("Unable to register PDH query for \"%s\"", full_counter_name); + log_warning(os)("Please check the registry if this performance object/counter is disabled"); +} + +static int initialize_cpu_query_counters(MultiCounterQueryP cpu_query, DWORD pdh_counter_idx) { + assert(cpu_query != NULL, "invariant"); + assert(cpu_query->counters != NULL, "invariant"); + char* processor; //'Processor' == PDH_PROCESSOR_IDX + if (lookup_name_by_index(PDH_PROCESSOR_IDX, &processor) != OS_OK) { + return OS_ERR; + } + char* counter_name = NULL; + if (lookup_name_by_index(pdh_counter_idx, &counter_name) != OS_OK) { + return OS_ERR; + } + if (cpu_query->query.query == NULL) { + if (open_query(cpu_query)) { + return OS_ERR; + } + } + assert(cpu_query->query.query != NULL, "invariant"); + size_t counter_len = strlen(processor); + counter_len += strlen(counter_name); + counter_len += OBJECT_WITH_INSTANCES_COUNTER_FMT_LEN; // "\\%s(%s)\\%s" + + DWORD index; + char* tmp; + const char* instances = enumerate_cpu_instances(); + for (index = 0, tmp = const_cast(instances); *tmp != '\0'; tmp = &tmp[strlen(tmp) + 1], index++) { + const size_t tmp_len = strlen(tmp); + char* counter_path = NEW_RESOURCE_ARRAY_RETURN_NULL(char, counter_len + tmp_len + 1); + if (counter_path == NULL) { + return OS_ERR; + } + const size_t jio_snprintf_result = jio_snprintf(counter_path, + counter_len + tmp_len + 1, + OBJECT_WITH_INSTANCES_COUNTER_FMT, + processor, + tmp, // instance "0", "1", .."_Total" + counter_name); + assert(counter_len + tmp_len == jio_snprintf_result, "invariant"); + if (add_counter(cpu_query, &cpu_query->counters[index], counter_path, false) != OS_OK) { + // performance counter is disabled in registry and not accessible via PerfLib + log_error_message_on_no_PDH_artifact(counter_path); + // return OS_OK to have the system continue to run without the missing counter + return OS_OK; + } + } + cpu_query->initialized = true; + // Query once to initialize the counters which require at least two samples + // (like the % CPU usage) to calculate correctly. + collect_query_data(cpu_query); + return OS_OK; +} + +static int initialize_cpu_query(MultiCounterQueryP cpu_query, DWORD pdh_counter_idx) { + assert(cpu_query != NULL, "invariant"); + assert(!cpu_query->initialized, "invariant"); + const int logical_cpu_count = number_of_logical_cpus(); + assert(logical_cpu_count >= os::processor_count(), "invariant"); + // we also add another counter for instance "_Total" + if (allocate_counters(cpu_query, logical_cpu_count + 1) != OS_OK) { + return OS_ERR; + } + assert(cpu_query->noOfCounters == logical_cpu_count + 1, "invariant"); + return initialize_cpu_query_counters(cpu_query, pdh_counter_idx); +} + +static int initialize_process_counter(MultiCounterQuerySetP query_set, int slot_index, DWORD pdh_counter_index) { + char* localized_process_object; + if (lookup_name_by_index(PDH_PROCESS_IDX, &localized_process_object) != OS_OK) { + return OS_ERR; + } + assert(localized_process_object != NULL, "invariant"); + char* localized_counter_name; + if (lookup_name_by_index(pdh_counter_index, &localized_counter_name) != OS_OK) { + return OS_ERR; + } + assert(localized_counter_name != NULL, "invariant"); + for (int i = 0; i < query_set->size; ++i) { + char instanceIndexBuffer[32]; + const char* counter_path = make_fully_qualified_counter_path(localized_process_object, + localized_counter_name, + process_image_name, + itoa(i, instanceIndexBuffer, 10)); + if (counter_path == NULL) { + return OS_ERR; + } + MultiCounterQueryP const query = &query_set->queries[i]; + if (add_process_counter(query, slot_index, counter_path, true)) { + return OS_ERR; + } + } + return OS_OK; +} + +static CounterQueryP create_counter_query(DWORD pdh_object_idx, DWORD pdh_counter_idx) { + assert(is_valid_pdh_index(pdh_object_idx), "invariant"); + assert(is_valid_pdh_index(pdh_counter_idx), "invariant"); + CounterQueryP const query = create_counter_query(); + const char* object = pdh_localized_artifact(pdh_object_idx); + assert(object != NULL, "invariant"); + const char* counter = pdh_localized_artifact(pdh_counter_idx); + assert(counter != NULL, "invariant"); + const char* full_counter_path = make_fully_qualified_counter_path(object, counter); + assert(full_counter_path != NULL, "invariant"); + add_counter(query, full_counter_path, true); + return query; +} + +static void deallocate() { + deallocate_pdh_constants(); + PdhDll::PdhDetach(); +} + +static LONG critical_section = 0; +static LONG reference_count = 0; +static bool pdh_initialized = false; + +static void on_initialization_failure() { + // still holder of critical section + deallocate(); + InterlockedExchangeAdd(&reference_count, -1); +} + +static OSReturn initialize() { + ResourceMark rm; + if (!PdhDll::PdhAttach()) { + return OS_ERR; + } + if (allocate_pdh_constants() != OS_OK) { + on_initialization_failure(); + return OS_ERR; + } + return OS_OK; +} + +/* +* Helper to initialize the PDH library, function pointers, constants and counters. +* +* Reference counting allows for unloading of pdh.dll granted all sessions use the pair: +* +* pdh_acquire(); +* pdh_release(); +* +* @return OS_OK if successful, OS_ERR on failure. +*/ +static bool pdh_acquire() { + while (InterlockedCompareExchange(&critical_section, 1, 0) == 1); + InterlockedExchangeAdd(&reference_count, 1); + if (pdh_initialized) { + return true; + } + const OSReturn ret = initialize(); + if (OS_OK == ret) { + pdh_initialized = true; + } + while (InterlockedCompareExchange(&critical_section, 0, 1) == 0); + return ret == OS_OK; +} + +static void pdh_release() { + while (InterlockedCompareExchange(&critical_section, 1, 0) == 1); + const LONG prev_ref_count = InterlockedExchangeAdd(&reference_count, -1); + if (1 == prev_ref_count) { + deallocate(); + pdh_initialized = false; + } + while (InterlockedCompareExchange(&critical_section, 0, 1) == 0); +} + +class CPUPerformanceInterface::CPUPerformance : public CHeapObj { + friend class CPUPerformanceInterface; + private: + CounterQueryP _context_switches; + MultiCounterQuerySetP _process_cpu_load; + MultiCounterQueryP _machine_cpu_load; + + int cpu_load(int which_logical_cpu, double* cpu_load); + int context_switch_rate(double* rate); + int cpu_load_total_process(double* cpu_load); + int cpu_loads_process(double* jvm_user_load, double* jvm_kernel_load, double* psystemTotalLoad); + CPUPerformance(); + ~CPUPerformance(); + bool initialize(); +}; + +class SystemProcessInterface::SystemProcesses : public CHeapObj { + friend class SystemProcessInterface; + private: + class ProcessIterator : public CHeapObj { + friend class SystemProcessInterface::SystemProcesses; + private: + HANDLE _hProcessSnap; + PROCESSENTRY32 _pe32; + BOOL _valid; + char _exePath[MAX_PATH]; + ProcessIterator(); + ~ProcessIterator(); + bool initialize(); + + int current(SystemProcess* const process_info); + int next_process(); + bool is_valid() const { return _valid != FALSE; } + char* allocate_string(const char* str) const; + int snapshot(); + }; + + ProcessIterator* _iterator; + SystemProcesses(); + ~SystemProcesses(); + bool initialize(); + + // information about system processes + int system_processes(SystemProcess** system_processes, int* no_of_sys_processes) const; +}; + +CPUPerformanceInterface::CPUPerformance::CPUPerformance() : _context_switches(NULL), _process_cpu_load(NULL), _machine_cpu_load(NULL) {} + +bool CPUPerformanceInterface::CPUPerformance::initialize() { + if (!pdh_acquire()) { + return false; + } + _context_switches = create_counter_query(PDH_SYSTEM_IDX, PDH_CONTEXT_SWITCH_RATE_IDX); + if (_context_switches == NULL) { + return false; + } + _process_cpu_load = create_process_counter_query(); + if (_process_cpu_load == NULL) { + return false; + } + if (allocate_counters(_process_cpu_load, 2) != OS_OK) { + return false; + } + if (initialize_process_counter(_process_cpu_load, 0, PDH_PROCESSOR_TIME_IDX) != OS_OK) { + return false; + } + if (initialize_process_counter(_process_cpu_load, 1, PDH_PRIV_PROCESSOR_TIME_IDX) != OS_OK) { + return false; + } + _process_cpu_load->initialized = true; + + _machine_cpu_load = create_multi_counter_query(); + if (_machine_cpu_load == NULL) { + return false; + } + if (initialize_cpu_query(_machine_cpu_load, PDH_PROCESSOR_TIME_IDX) != OS_OK) { + return false; + } + return true; +} + +CPUPerformanceInterface::CPUPerformance::~CPUPerformance() { + if (_context_switches != NULL) { + destroy_counter_query(_context_switches); + _context_switches = NULL; + } + if (_process_cpu_load != NULL) { + destroy_counter_query(_process_cpu_load); + _process_cpu_load = NULL; + } + if (_machine_cpu_load != NULL) { + destroy_counter_query(_machine_cpu_load); + _machine_cpu_load = NULL; + } + pdh_release(); +} + +CPUPerformanceInterface::CPUPerformanceInterface() { + _impl = NULL; +} + +bool CPUPerformanceInterface::initialize() { + _impl = new CPUPerformanceInterface::CPUPerformance(); + return _impl != NULL && _impl->initialize(); +} + +CPUPerformanceInterface::~CPUPerformanceInterface() { + if (_impl != NULL) { + delete _impl; + } +} + +int CPUPerformanceInterface::cpu_load(int which_logical_cpu, double* cpu_load) const { + return _impl->cpu_load(which_logical_cpu, cpu_load); +} + +int CPUPerformanceInterface::context_switch_rate(double* rate) const { + return _impl->context_switch_rate(rate); +} + +int CPUPerformanceInterface::cpu_load_total_process(double* cpu_load) const { + return _impl->cpu_load_total_process(cpu_load); +} + +int CPUPerformanceInterface::cpu_loads_process(double* pjvmUserLoad, + double* pjvmKernelLoad, + double* psystemTotalLoad) const { + return _impl->cpu_loads_process(pjvmUserLoad, pjvmKernelLoad, psystemTotalLoad); +} + +int CPUPerformanceInterface::CPUPerformance::cpu_load(int which_logical_cpu, double* cpu_load) { + assert(_machine_cpu_load != NULL, "invariant"); + assert(which_logical_cpu < _machine_cpu_load->noOfCounters, "invariant"); + *cpu_load = .0; + if (!_machine_cpu_load->initialized) { + return OS_ERR; + } + if (collect_query_data(_machine_cpu_load)) { + return OS_ERR; + } + // -1 is total (all cpus) + const int counter_idx = -1 == which_logical_cpu ? _machine_cpu_load->noOfCounters - 1 : which_logical_cpu; + PDH_FMT_COUNTERVALUE counter_value; + formatted_counter_value(_machine_cpu_load->counters[counter_idx], PDH_FMT_DOUBLE, &counter_value); + *cpu_load = counter_value.doubleValue / 100; + return OS_OK; +} + +int CPUPerformanceInterface::CPUPerformance::cpu_load_total_process(double* cpu_load) { + assert(_process_cpu_load != NULL, "invariant"); + *cpu_load = .0; + if (!_process_cpu_load->initialized) { + return OS_ERR; + } + if (collect_process_query_data(_process_cpu_load)) { + return OS_ERR; + } + PDH_FMT_COUNTERVALUE counter_value; + if (query_process_counter(_process_cpu_load, 0, PDH_FMT_DOUBLE | PDH_FMT_NOCAP100, &counter_value) != OS_OK) { + return OS_ERR; + } + double process_load = counter_value.doubleValue / cpu_factor(); + process_load = MIN2(1, process_load); + process_load = MAX2(0, process_load); + *cpu_load = process_load; + return OS_OK; +} + +int CPUPerformanceInterface::CPUPerformance::cpu_loads_process(double* pjvmUserLoad, + double* pjvmKernelLoad, + double* psystemTotalLoad) { + assert(pjvmUserLoad != NULL, "pjvmUserLoad is NULL!"); + assert(pjvmKernelLoad != NULL, "pjvmKernelLoad is NULL!"); + assert(psystemTotalLoad != NULL, "psystemTotalLoad is NULL!"); + *pjvmUserLoad = .0; + *pjvmKernelLoad = .0; + *psystemTotalLoad = .0; + if (!_process_cpu_load->initialized) { + return OS_ERR; + } + if (collect_process_query_data(_process_cpu_load)) { + return OS_ERR; + } + double process_load = .0; + PDH_FMT_COUNTERVALUE counter_value; + // Read PDH_PROCESSOR_TIME_IDX + if (query_process_counter(_process_cpu_load, 0, PDH_FMT_DOUBLE | PDH_FMT_NOCAP100, &counter_value) != OS_OK) { + return OS_ERR; + } + process_load = counter_value.doubleValue / cpu_factor(); + process_load = MIN2(1, process_load); + process_load = MAX2(0, process_load); + // Read PDH_PRIV_PROCESSOR_TIME_IDX + if (query_process_counter(_process_cpu_load, 1, PDH_FMT_DOUBLE | PDH_FMT_NOCAP100, &counter_value) != OS_OK) { + return OS_ERR; + } + double kernel_load = counter_value.doubleValue / cpu_factor(); + kernel_load = MIN2(1, kernel_load); + kernel_load = MAX2(0, kernel_load); + *pjvmKernelLoad = kernel_load; + + double user_load = process_load - kernel_load; + user_load = MIN2(1, user_load); + user_load = MAX2(0, user_load); + *pjvmUserLoad = user_load; + + if (collect_query_data(_machine_cpu_load)) { + return OS_ERR; + } + if (formatted_counter_value(_machine_cpu_load->counters[_machine_cpu_load->noOfCounters - 1], PDH_FMT_DOUBLE, &counter_value) != OS_OK) { + return OS_ERR; + } + double machine_load = counter_value.doubleValue / 100; + assert(machine_load >= 0, "machine_load is negative!"); + // clamp at user+system and 1.0 + if (*pjvmKernelLoad + *pjvmUserLoad > machine_load) { + machine_load = MIN2(*pjvmKernelLoad + *pjvmUserLoad, 1.0); + } + *psystemTotalLoad = machine_load; + return OS_OK; +} + +int CPUPerformanceInterface::CPUPerformance::context_switch_rate(double* rate) { + assert(rate != NULL, "invariant"); + *rate = .0; + if (!_context_switches->initialized) { + return OS_ERR; + } + if (collect_query_data(_context_switches) != OS_OK) { + return OS_ERR; + } + PDH_FMT_COUNTERVALUE counter_value; + if (formatted_counter_value(_context_switches->counter, PDH_FMT_DOUBLE, &counter_value) != OS_OK) { + return OS_ERR; + } + *rate = counter_value.doubleValue; + return OS_OK; +} + +SystemProcessInterface::SystemProcesses::ProcessIterator::ProcessIterator() { + _hProcessSnap = INVALID_HANDLE_VALUE; + _valid = FALSE; + _pe32.dwSize = sizeof(PROCESSENTRY32); +} + +bool SystemProcessInterface::SystemProcesses::ProcessIterator::initialize() { + return true; +} + +int SystemProcessInterface::SystemProcesses::ProcessIterator::snapshot() { + // take snapshot of all process in the system + _hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (_hProcessSnap == INVALID_HANDLE_VALUE) { + return OS_ERR; + } + // step to first process + _valid = Process32First(_hProcessSnap, &_pe32); + return is_valid() ? OS_OK : OS_ERR; +} + +SystemProcessInterface::SystemProcesses::ProcessIterator::~ProcessIterator() { + if (_hProcessSnap != INVALID_HANDLE_VALUE) { + CloseHandle(_hProcessSnap); + } +} + +int SystemProcessInterface::SystemProcesses::ProcessIterator::current(SystemProcess* process_info) { + assert(is_valid(), "no current process to be fetched!"); + assert(process_info != NULL, "process_info is NULL!"); + char* exePath = NULL; + HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, _pe32.th32ProcessID); + if (hProcess != NULL) { + HMODULE hMod; + DWORD cbNeeded; + if (EnumProcessModules(hProcess, &hMod, sizeof(hMod), &cbNeeded) != 0) { + if (GetModuleFileNameExA(hProcess, hMod, _exePath, sizeof(_exePath)) != 0) { + exePath = _exePath; + } + } + CloseHandle (hProcess); + } + process_info->set_pid((int)_pe32.th32ProcessID); + process_info->set_name(allocate_string(_pe32.szExeFile)); + process_info->set_path(allocate_string(exePath)); + return OS_OK; +} + +char* SystemProcessInterface::SystemProcesses::ProcessIterator::allocate_string(const char* str) const { + if (str != NULL) { + size_t len = strlen(str); + char* tmp = NEW_C_HEAP_ARRAY(char, len+1, mtInternal); + if (NULL == tmp) { + return NULL; + } + strncpy(tmp, str, len); + tmp[len] = '\0'; + return tmp; + } + return NULL; +} + +int SystemProcessInterface::SystemProcesses::ProcessIterator::next_process() { + _valid = Process32Next(_hProcessSnap, &_pe32); + return OS_OK; +} + +SystemProcessInterface::SystemProcesses::SystemProcesses() { + _iterator = NULL; +} + +bool SystemProcessInterface::SystemProcesses::initialize() { + _iterator = new SystemProcessInterface::SystemProcesses::ProcessIterator(); + return _iterator != NULL && _iterator->initialize(); +} + +SystemProcessInterface::SystemProcesses::~SystemProcesses() { + if (_iterator != NULL) { + delete _iterator; + _iterator = NULL; + } +} + +int SystemProcessInterface::SystemProcesses::system_processes(SystemProcess** system_processes, + int* no_of_sys_processes) const { + assert(system_processes != NULL, "system_processes pointer is NULL!"); + assert(no_of_sys_processes != NULL, "system_processes counter pointers is NULL!"); + assert(_iterator != NULL, "iterator is NULL!"); + + // initialize pointers + *no_of_sys_processes = 0; + *system_processes = NULL; + + // take process snapshot + if (_iterator->snapshot() != OS_OK) { + return OS_ERR; + } + + while (_iterator->is_valid()) { + SystemProcess* tmp = new SystemProcess(); + _iterator->current(tmp); + + //if already existing head + if (*system_processes != NULL) { + //move "first to second" + tmp->set_next(*system_processes); + } + // new head + *system_processes = tmp; + // increment + (*no_of_sys_processes)++; + // step forward + _iterator->next_process(); + } + return OS_OK; +} + +int SystemProcessInterface::system_processes(SystemProcess** system_procs, + int* no_of_sys_processes) const { + return _impl->system_processes(system_procs, no_of_sys_processes); +} + +SystemProcessInterface::SystemProcessInterface() { + _impl = NULL; +} + +bool SystemProcessInterface::initialize() { + _impl = new SystemProcessInterface::SystemProcesses(); + return _impl != NULL && _impl->initialize(); +} + +SystemProcessInterface::~SystemProcessInterface() { + if (_impl != NULL) { + delete _impl; + } +} + +CPUInformationInterface::CPUInformationInterface() { + _cpu_info = NULL; +} + +bool CPUInformationInterface::initialize() { + _cpu_info = new CPUInformation(); + if (NULL == _cpu_info) { + return false; + } + _cpu_info->set_number_of_hardware_threads(VM_Version_Ext::number_of_threads()); + _cpu_info->set_number_of_cores(VM_Version_Ext::number_of_cores()); + _cpu_info->set_number_of_sockets(VM_Version_Ext::number_of_sockets()); + _cpu_info->set_cpu_name(VM_Version_Ext::cpu_name()); + _cpu_info->set_cpu_description(VM_Version_Ext::cpu_description()); + return true; +} + +CPUInformationInterface::~CPUInformationInterface() { + if (_cpu_info != NULL) { + const char* cpu_name = _cpu_info->cpu_name(); + if (cpu_name != NULL) { + FREE_C_HEAP_ARRAY(char, cpu_name); + _cpu_info->set_cpu_name(NULL); + } + const char* cpu_desc = _cpu_info->cpu_description(); + if (cpu_desc != NULL) { + FREE_C_HEAP_ARRAY(char, cpu_desc); + _cpu_info->set_cpu_description(NULL); + } + delete _cpu_info; + _cpu_info = NULL; + } +} + +int CPUInformationInterface::cpu_information(CPUInformation& cpu_info) { + if (NULL == _cpu_info) { + return OS_ERR; + } + cpu_info = *_cpu_info; // shallow copy assignment + return OS_OK; +} diff --git a/src/hotspot/os/windows/pdh_interface.cpp b/src/hotspot/os/windows/pdh_interface.cpp new file mode 100644 index 00000000000..2ad7af2789e --- /dev/null +++ b/src/hotspot/os/windows/pdh_interface.cpp @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "pdh_interface.hpp" +#include "runtime/os.hpp" +#include "utilities/macros.hpp" + +// PDH API +typedef PDH_STATUS (WINAPI *PdhAddCounter_Fn)(HQUERY, LPCSTR, DWORD, HCOUNTER*); +typedef PDH_STATUS (WINAPI *PdhOpenQuery_Fn)(LPCWSTR, DWORD, HQUERY*); +typedef DWORD (WINAPI *PdhCloseQuery_Fn)(HQUERY); +typedef PDH_STATUS (WINAPI *PdhCollectQueryData_Fn)(HQUERY); +typedef DWORD (WINAPI *PdhGetFormattedCounterValue_Fn)(HCOUNTER, DWORD, LPDWORD, PPDH_FMT_COUNTERVALUE); +typedef PDH_STATUS (WINAPI *PdhEnumObjectItems_Fn)(LPCTSTR, LPCTSTR, LPCTSTR, LPTSTR, LPDWORD, LPTSTR, LPDWORD, DWORD, DWORD); +typedef PDH_STATUS (WINAPI *PdhRemoveCounter_Fn)(HCOUNTER); +typedef PDH_STATUS (WINAPI *PdhLookupPerfNameByIndex_Fn)(LPCSTR, DWORD, LPSTR, LPDWORD); +typedef PDH_STATUS (WINAPI *PdhMakeCounterPath_Fn)(PDH_COUNTER_PATH_ELEMENTS*, LPTSTR, LPDWORD, DWORD); + +PdhAddCounter_Fn PdhDll::_PdhAddCounter = NULL; +PdhOpenQuery_Fn PdhDll::_PdhOpenQuery = NULL; +PdhCloseQuery_Fn PdhDll::_PdhCloseQuery = NULL; +PdhCollectQueryData_Fn PdhDll::_PdhCollectQueryData = NULL; +PdhGetFormattedCounterValue_Fn PdhDll::_PdhGetFormattedCounterValue = NULL; +PdhEnumObjectItems_Fn PdhDll::_PdhEnumObjectItems = NULL; +PdhRemoveCounter_Fn PdhDll::_PdhRemoveCounter = NULL; +PdhLookupPerfNameByIndex_Fn PdhDll::_PdhLookupPerfNameByIndex = NULL; +PdhMakeCounterPath_Fn PdhDll::_PdhMakeCounterPath = NULL; + +LONG PdhDll::_critical_section = 0; +LONG PdhDll::_initialized = 0; +LONG PdhDll::_pdh_reference_count = 0; +HMODULE PdhDll::_hModule = NULL; + +void PdhDll::initialize(void) { + _hModule = os::win32::load_Windows_dll("pdh.dll", NULL, 0); + if (NULL == _hModule) { + return; + } + // The 'A' at the end means the ANSI (not the UNICODE) vesions of the methods + _PdhAddCounter = (PdhAddCounter_Fn)::GetProcAddress(_hModule, "PdhAddCounterA"); + _PdhOpenQuery = (PdhOpenQuery_Fn)::GetProcAddress(_hModule, "PdhOpenQueryA"); + _PdhCloseQuery = (PdhCloseQuery_Fn)::GetProcAddress(_hModule, "PdhCloseQuery"); + _PdhCollectQueryData = (PdhCollectQueryData_Fn)::GetProcAddress(_hModule, "PdhCollectQueryData"); + _PdhGetFormattedCounterValue = (PdhGetFormattedCounterValue_Fn)::GetProcAddress(_hModule, "PdhGetFormattedCounterValue"); + _PdhEnumObjectItems = (PdhEnumObjectItems_Fn)::GetProcAddress(_hModule, "PdhEnumObjectItemsA"); + _PdhRemoveCounter = (PdhRemoveCounter_Fn)::GetProcAddress(_hModule, "PdhRemoveCounter"); + _PdhLookupPerfNameByIndex = (PdhLookupPerfNameByIndex_Fn)::GetProcAddress(_hModule, "PdhLookupPerfNameByIndexA"); + _PdhMakeCounterPath = (PdhMakeCounterPath_Fn)::GetProcAddress(_hModule, "PdhMakeCounterPathA"); + InterlockedExchange(&_initialized, 1); +} + +bool PdhDll::PdhDetach(void) { + LONG prev_ref_count = InterlockedExchangeAdd(&_pdh_reference_count, -1); + BOOL ret = false; + if (1 == prev_ref_count) { + if (_initialized && _hModule != NULL) { + ret = FreeLibrary(_hModule); + if (ret) { + _hModule = NULL; + _PdhAddCounter = NULL; + _PdhOpenQuery = NULL; + _PdhCloseQuery = NULL; + _PdhCollectQueryData = NULL; + _PdhGetFormattedCounterValue = NULL; + _PdhEnumObjectItems = NULL; + _PdhRemoveCounter = NULL; + _PdhLookupPerfNameByIndex = NULL; + _PdhMakeCounterPath = NULL; + InterlockedExchange(&_initialized, 0); + } + } + } + return ret != 0; +} + +bool PdhDll::PdhAttach(void) { + InterlockedExchangeAdd(&_pdh_reference_count, 1); + if (1 == _initialized) { + return true; + } + while (InterlockedCompareExchange(&_critical_section, 1, 0) == 1); + if (0 == _initialized) { + initialize(); + } + while (InterlockedCompareExchange(&_critical_section, 0, 1) == 0); + return (_PdhAddCounter != NULL && _PdhOpenQuery != NULL + && _PdhCloseQuery != NULL && PdhCollectQueryData != NULL + && _PdhGetFormattedCounterValue != NULL && _PdhEnumObjectItems != NULL + && _PdhRemoveCounter != NULL && PdhLookupPerfNameByIndex != NULL + && _PdhMakeCounterPath != NULL); +} + +PDH_STATUS PdhDll::PdhAddCounter(HQUERY hQuery, LPCSTR szFullCounterPath, DWORD dwUserData, HCOUNTER* phCounter) { + assert(_initialized && _PdhAddCounter != NULL, "PdhAvailable() not yet called"); + return _PdhAddCounter(hQuery, szFullCounterPath, dwUserData, phCounter); +} + +PDH_STATUS PdhDll::PdhOpenQuery(LPCWSTR szDataSource, DWORD dwUserData, HQUERY* phQuery) { + assert(_initialized && _PdhOpenQuery != NULL, "PdhAvailable() not yet called"); + return _PdhOpenQuery(szDataSource, dwUserData, phQuery); +} + +DWORD PdhDll::PdhCloseQuery(HQUERY hQuery) { + assert(_initialized && _PdhCloseQuery != NULL, "PdhAvailable() not yet called"); + return _PdhCloseQuery(hQuery); +} + +PDH_STATUS PdhDll::PdhCollectQueryData(HQUERY hQuery) { + assert(_initialized && _PdhCollectQueryData != NULL, "PdhAvailable() not yet called"); + return _PdhCollectQueryData(hQuery); +} + +DWORD PdhDll::PdhGetFormattedCounterValue(HCOUNTER hCounter, DWORD dwFormat, LPDWORD lpdwType, PPDH_FMT_COUNTERVALUE pValue) { + assert(_initialized && _PdhGetFormattedCounterValue != NULL, "PdhAvailable() not yet called"); + return _PdhGetFormattedCounterValue(hCounter, dwFormat, lpdwType, pValue); +} + +PDH_STATUS PdhDll::PdhEnumObjectItems(LPCTSTR szDataSource, LPCTSTR szMachineName, LPCTSTR szObjectName, + LPTSTR mszCounterList, LPDWORD pcchCounterListLength, LPTSTR mszInstanceList, + LPDWORD pcchInstanceListLength, DWORD dwDetailLevel, DWORD dwFlags) { + assert(_initialized && _PdhEnumObjectItems != NULL, "PdhAvailable() not yet called"); + return _PdhEnumObjectItems(szDataSource, szMachineName, szObjectName, mszCounterList, pcchCounterListLength, + mszInstanceList, pcchInstanceListLength, dwDetailLevel, dwFlags); +} + +PDH_STATUS PdhDll::PdhRemoveCounter(HCOUNTER hCounter) { + assert(_initialized && _PdhRemoveCounter != NULL, "PdhAvailable() not yet called"); + return _PdhRemoveCounter(hCounter); +} + +PDH_STATUS PdhDll::PdhLookupPerfNameByIndex(LPCSTR szMachineName, DWORD dwNameIndex, LPSTR szNameBuffer, LPDWORD pcchNameBufferSize) { + assert(_initialized && _PdhLookupPerfNameByIndex != NULL, "PdhAvailable() not yet called"); + return _PdhLookupPerfNameByIndex(szMachineName, dwNameIndex, szNameBuffer, pcchNameBufferSize); +} + +PDH_STATUS PdhDll::PdhMakeCounterPath(PDH_COUNTER_PATH_ELEMENTS* pCounterPathElements, LPTSTR szFullPathBuffer, LPDWORD pcchBufferSize, DWORD dwFlags) { + assert(_initialized && _PdhMakeCounterPath != NULL, "PdhAvailable() not yet called"); + return _PdhMakeCounterPath(pCounterPathElements, szFullPathBuffer, pcchBufferSize, dwFlags); +} + +bool PdhDll::PdhStatusFail(PDH_STATUS pdhStat) { + return pdhStat != ERROR_SUCCESS && pdhStat != PDH_MORE_DATA; +} diff --git a/src/hotspot/os/windows/pdh_interface.hpp b/src/hotspot/os/windows/pdh_interface.hpp new file mode 100644 index 00000000000..06a9526e028 --- /dev/null +++ b/src/hotspot/os/windows/pdh_interface.hpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef OS_WINDOWS_VM_PDH_INTERFACE_HPP +#define OS_WINDOWS_VM_PDH_INTERFACE_HPP + +#include "memory/allocation.hpp" +#include +#include + +class PdhDll: public AllStatic { + private: + static LONG _pdh_reference_count; + static LONG _critical_section; + static LONG _initialized; + static HMODULE _hModule; + static void initialize(); + static PDH_STATUS (WINAPI *_PdhAddCounter)(HQUERY, LPCSTR, DWORD, HCOUNTER*); + static PDH_STATUS (WINAPI *_PdhOpenQuery)(LPCWSTR, DWORD, HQUERY*); + static DWORD (WINAPI *_PdhCloseQuery)(HQUERY); + static PDH_STATUS (WINAPI *_PdhCollectQueryData)(HQUERY); + static DWORD (WINAPI *_PdhGetFormattedCounterValue)(HCOUNTER, DWORD, LPDWORD, PPDH_FMT_COUNTERVALUE); + static PDH_STATUS (WINAPI *_PdhEnumObjectItems)(LPCTSTR, LPCTSTR, LPCTSTR, LPTSTR, LPDWORD, LPTSTR, LPDWORD, DWORD, DWORD); + static PDH_STATUS (WINAPI *_PdhRemoveCounter)(HCOUNTER); + static PDH_STATUS (WINAPI *_PdhLookupPerfNameByIndex)(LPCSTR, DWORD, LPSTR, LPDWORD); + static PDH_STATUS (WINAPI *_PdhMakeCounterPath)(PPDH_COUNTER_PATH_ELEMENTS, LPTSTR, LPDWORD, DWORD); + + public: + static PDH_STATUS PdhAddCounter(HQUERY, LPCSTR, DWORD, HCOUNTER*); + static PDH_STATUS PdhOpenQuery(LPCWSTR, DWORD, HQUERY*); + static DWORD PdhCloseQuery(HQUERY); + static PDH_STATUS PdhCollectQueryData(HQUERY); + static DWORD PdhGetFormattedCounterValue(HCOUNTER, DWORD, LPDWORD, PPDH_FMT_COUNTERVALUE); + static PDH_STATUS PdhEnumObjectItems(LPCTSTR, LPCTSTR, LPCTSTR, LPTSTR, LPDWORD, LPTSTR, LPDWORD, DWORD, DWORD); + static PDH_STATUS PdhRemoveCounter(HCOUNTER); + static PDH_STATUS PdhLookupPerfNameByIndex(LPCSTR, DWORD, LPSTR, LPDWORD); + static PDH_STATUS PdhMakeCounterPath(PPDH_COUNTER_PATH_ELEMENTS, LPTSTR, LPDWORD, DWORD); + static bool PdhStatusFail(PDH_STATUS pdhStat); + static bool PdhAttach(); + static bool PdhDetach(); +}; + +#endif // OS_WINDOWS_VM_PDH_INTERFACE_HPP diff --git a/src/hotspot/os_cpu/solaris_x86/os_solaris_x86.inline.hpp b/src/hotspot/os_cpu/solaris_x86/os_solaris_x86.inline.hpp index cedbe5e5033..db90eb4b1f2 100644 --- a/src/hotspot/os_cpu/solaris_x86/os_solaris_x86.inline.hpp +++ b/src/hotspot/os_cpu/solaris_x86/os_solaris_x86.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,6 +27,8 @@ #include "runtime/os.hpp" +extern "C" jlong _raw_rdtsc(); // In .il file + inline jlong os::rdtsc() { return _raw_rdtsc(); } #endif // OS_CPU_SOLARIS_X86_VM_OS_SOLARIS_X86_INLINE_HPP diff --git a/src/hotspot/share/c1/c1_Compiler.cpp b/src/hotspot/share/c1/c1_Compiler.cpp index e7f37ee5401..6ed4a29a48e 100644 --- a/src/hotspot/share/c1/c1_Compiler.cpp +++ b/src/hotspot/share/c1/c1_Compiler.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,6 +33,7 @@ #include "c1/c1_ValueType.hpp" #include "compiler/compileBroker.hpp" #include "interpreter/linkResolver.hpp" +#include "jfr/support/jfrIntrinsics.hpp" #include "memory/allocation.hpp" #include "memory/allocation.inline.hpp" #include "memory/resourceArea.hpp" @@ -41,6 +42,7 @@ #include "runtime/interfaceSupport.inline.hpp" #include "runtime/sharedRuntime.hpp" #include "utilities/bitMap.inline.hpp" +#include "utilities/macros.hpp" Compiler::Compiler() : AbstractCompiler(compiler_c1) { @@ -222,10 +224,10 @@ bool Compiler::is_intrinsic_supported(const methodHandle& method) { case vmIntrinsics::_compareAndSetObject: case vmIntrinsics::_getCharStringU: case vmIntrinsics::_putCharStringU: -#ifdef TRACE_HAVE_INTRINSICS +#ifdef JFR_HAVE_INTRINSICS case vmIntrinsics::_counterTime: - case vmIntrinsics::_getBufferWriter: -#if defined(_LP64) || !defined(TRACE_ID_CLASS_SHIFT) + case vmIntrinsics::_getEventWriter: +#if defined(_LP64) || !defined(TRACE_ID_SHIFT) case vmIntrinsics::_getClassId: #endif #endif diff --git a/src/hotspot/share/c1/c1_GraphBuilder.cpp b/src/hotspot/share/c1/c1_GraphBuilder.cpp index ca9fc3edd15..360e965aad3 100644 --- a/src/hotspot/share/c1/c1_GraphBuilder.cpp +++ b/src/hotspot/share/c1/c1_GraphBuilder.cpp @@ -35,6 +35,7 @@ #include "ci/ciUtilities.inline.hpp" #include "compiler/compileBroker.hpp" #include "interpreter/bytecode.hpp" +#include "jfr/jfrEvents.hpp" #include "memory/resourceArea.hpp" #include "oops/oop.inline.hpp" #include "runtime/sharedRuntime.hpp" @@ -4300,6 +4301,30 @@ void GraphBuilder::append_char_access(ciMethod* callee, bool is_store) { } } +static void post_inlining_event(EventCompilerInlining* event, + int compile_id, + const char* msg, + bool success, + int bci, + ciMethod* caller, + ciMethod* callee) { + assert(caller != NULL, "invariant"); + assert(callee != NULL, "invariant"); + assert(event != NULL, "invariant"); + assert(event->should_commit(), "invariant"); + JfrStructCalleeMethod callee_struct; + callee_struct.set_type(callee->holder()->name()->as_utf8()); + callee_struct.set_name(callee->name()->as_utf8()); + callee_struct.set_descriptor(callee->signature()->as_symbol()->as_utf8()); + event->set_compileId(compile_id); + event->set_message(msg); + event->set_succeeded(success); + event->set_bci(bci); + event->set_caller(caller->get_Method()); + event->set_callee(callee_struct); + event->commit(); +} + void GraphBuilder::print_inlining(ciMethod* callee, const char* msg, bool success) { CompileLog* log = compilation()->log(); if (log != NULL) { @@ -4315,18 +4340,10 @@ void GraphBuilder::print_inlining(ciMethod* callee, const char* msg, bool succes log->inline_fail("reason unknown"); } } -#if INCLUDE_TRACE EventCompilerInlining event; if (event.should_commit()) { - event.set_compileId(compilation()->env()->task()->compile_id()); - event.set_message(msg); - event.set_succeeded(success); - event.set_bci(bci()); - event.set_caller(method()->get_Method()); - event.set_callee(callee->to_trace_struct()); - event.commit(); + post_inlining_event(&event, compilation()->env()->task()->compile_id(), msg, success, bci(), method(), callee); } -#endif // INCLUDE_TRACE CompileTask::print_inlining_ul(callee, scope()->level(), bci(), msg); diff --git a/src/hotspot/share/c1/c1_LIRGenerator.cpp b/src/hotspot/share/c1/c1_LIRGenerator.cpp index 560d248bf81..768ab6343ec 100644 --- a/src/hotspot/share/c1/c1_LIRGenerator.cpp +++ b/src/hotspot/share/c1/c1_LIRGenerator.cpp @@ -42,9 +42,6 @@ #include "runtime/vm_version.hpp" #include "utilities/bitMap.inline.hpp" #include "utilities/macros.hpp" -#ifdef TRACE_HAVE_INTRINSICS -#include "trace/traceMacros.hpp" -#endif #ifdef ASSERT #define __ gen()->lir(__FILE__, __LINE__)-> @@ -2916,7 +2913,7 @@ void LIRGenerator::do_IfOp(IfOp* x) { __ cmove(lir_cond(x->cond()), t_val.result(), f_val.result(), reg, as_BasicType(x->x()->type())); } -#ifdef TRACE_HAVE_INTRINSICS +#ifdef JFR_HAVE_INTRINSICS void LIRGenerator::do_ClassIDIntrinsic(Intrinsic* x) { CodeEmitInfo* info = state_for(x); CodeEmitInfo* info2 = new CodeEmitInfo(info); // Clone for the second null check @@ -2928,7 +2925,7 @@ void LIRGenerator::do_ClassIDIntrinsic(Intrinsic* x) { LIR_Opr klass = new_register(T_METADATA); __ move(new LIR_Address(arg.result(), java_lang_Class::klass_offset_in_bytes(), T_ADDRESS), klass, info); LIR_Opr id = new_register(T_LONG); - ByteSize offset = TRACE_KLASS_TRACE_ID_OFFSET; + ByteSize offset = KLASS_TRACE_ID_OFFSET; LIR_Address* trace_id_addr = new LIR_Address(klass, in_bytes(offset), T_LONG); __ move(trace_id_addr, id); @@ -2938,18 +2935,18 @@ void LIRGenerator::do_ClassIDIntrinsic(Intrinsic* x) { #ifdef TRACE_ID_META_BITS __ logical_and(id, LIR_OprFact::longConst(~TRACE_ID_META_BITS), id); #endif -#ifdef TRACE_ID_CLASS_SHIFT - __ unsigned_shift_right(id, TRACE_ID_CLASS_SHIFT, id); +#ifdef TRACE_ID_SHIFT + __ unsigned_shift_right(id, TRACE_ID_SHIFT, id); #endif __ move(id, rlock_result(x)); } -void LIRGenerator::do_getBufferWriter(Intrinsic* x) { +void LIRGenerator::do_getEventWriter(Intrinsic* x) { LabelObj* L_end = new LabelObj(); LIR_Address* jobj_addr = new LIR_Address(getThreadPointer(), - in_bytes(TRACE_THREAD_DATA_WRITER_OFFSET), + in_bytes(THREAD_LOCAL_WRITER_OFFSET_JFR), T_OBJECT); LIR_Opr result = rlock_result(x); __ move_wide(jobj_addr, result); @@ -2987,15 +2984,15 @@ void LIRGenerator::do_Intrinsic(Intrinsic* x) { break; } -#ifdef TRACE_HAVE_INTRINSICS +#ifdef JFR_HAVE_INTRINSICS case vmIntrinsics::_getClassId: do_ClassIDIntrinsic(x); break; - case vmIntrinsics::_getBufferWriter: - do_getBufferWriter(x); + case vmIntrinsics::_getEventWriter: + do_getEventWriter(x); break; case vmIntrinsics::_counterTime: - do_RuntimeCall(CAST_FROM_FN_PTR(address, TRACE_TIME_METHOD), x); + do_RuntimeCall(CAST_FROM_FN_PTR(address, JFR_TIME_FUNCTION), x); break; #endif diff --git a/src/hotspot/share/c1/c1_LIRGenerator.hpp b/src/hotspot/share/c1/c1_LIRGenerator.hpp index 5f152cff6e3..fff7d6f9fb6 100644 --- a/src/hotspot/share/c1/c1_LIRGenerator.hpp +++ b/src/hotspot/share/c1/c1_LIRGenerator.hpp @@ -30,6 +30,7 @@ #include "c1/c1_LIR.hpp" #include "ci/ciMethodData.hpp" #include "gc/shared/barrierSet.hpp" +#include "jfr/support/jfrIntrinsics.hpp" #include "utilities/macros.hpp" #include "utilities/sizes.hpp" @@ -459,9 +460,9 @@ class LIRGenerator: public InstructionVisitor, public BlockClosure { SwitchRangeArray* create_lookup_ranges(LookupSwitch* x); void do_SwitchRanges(SwitchRangeArray* x, LIR_Opr value, BlockBegin* default_sux); -#ifdef TRACE_HAVE_INTRINSICS +#ifdef JFR_HAVE_INTRINSICS void do_ClassIDIntrinsic(Intrinsic* x); - void do_getBufferWriter(Intrinsic* x); + void do_getEventWriter(Intrinsic* x); #endif void do_RuntimeCall(address routine, Intrinsic* x); diff --git a/src/hotspot/share/c1/c1_Runtime1.cpp b/src/hotspot/share/c1/c1_Runtime1.cpp index 48059c27741..1627849bcd2 100644 --- a/src/hotspot/share/c1/c1_Runtime1.cpp +++ b/src/hotspot/share/c1/c1_Runtime1.cpp @@ -43,6 +43,7 @@ #include "gc/shared/collectedHeap.hpp" #include "interpreter/bytecode.hpp" #include "interpreter/interpreter.hpp" +#include "jfr/support/jfrIntrinsics.hpp" #include "logging/log.hpp" #include "memory/allocation.inline.hpp" #include "memory/oopFactory.hpp" @@ -320,8 +321,8 @@ const char* Runtime1::name_for_address(address entry) { FUNCTION_CASE(entry, SharedRuntime::dtrace_method_exit); FUNCTION_CASE(entry, is_instance_of); FUNCTION_CASE(entry, trace_block_entry); -#ifdef TRACE_HAVE_INTRINSICS - FUNCTION_CASE(entry, TRACE_TIME_METHOD); +#ifdef JFR_HAVE_INTRINSICS + FUNCTION_CASE(entry, JFR_TIME_FUNCTION); #endif FUNCTION_CASE(entry, StubRoutines::updateBytesCRC32()); FUNCTION_CASE(entry, StubRoutines::updateBytesCRC32C()); diff --git a/src/hotspot/share/ci/ciEnv.cpp b/src/hotspot/share/ci/ciEnv.cpp index 4870b70fbc8..dd107632a3a 100644 --- a/src/hotspot/share/ci/ciEnv.cpp +++ b/src/hotspot/share/ci/ciEnv.cpp @@ -42,6 +42,7 @@ #include "compiler/disassembler.hpp" #include "gc/shared/collectedHeap.inline.hpp" #include "interpreter/linkResolver.hpp" +#include "jfr/jfrEvents.hpp" #include "memory/allocation.inline.hpp" #include "memory/oopFactory.hpp" #include "memory/resourceArea.hpp" @@ -60,7 +61,6 @@ #include "runtime/safepointVerifiers.hpp" #include "runtime/sharedRuntime.hpp" #include "runtime/thread.inline.hpp" -#include "trace/tracing.hpp" #include "utilities/dtrace.hpp" #include "utilities/macros.hpp" #ifdef COMPILER1 @@ -1144,7 +1144,6 @@ void ciEnv::record_failure(const char* reason) { } void ciEnv::report_failure(const char* reason) { - // Create and fire JFR event EventCompilationFailure event; if (event.should_commit()) { event.set_compileId(compile_id()); diff --git a/src/hotspot/share/ci/ciMethod.cpp b/src/hotspot/share/ci/ciMethod.cpp index 251ce5b0c76..5b0403fed1d 100644 --- a/src/hotspot/share/ci/ciMethod.cpp +++ b/src/hotspot/share/ci/ciMethod.cpp @@ -48,7 +48,6 @@ #include "runtime/deoptimization.hpp" #include "utilities/bitMap.inline.hpp" #include "utilities/xmlstream.hpp" -#include "trace/tracing.hpp" #ifdef COMPILER2 #include "ci/bcEscapeAnalyzer.hpp" #include "ci/ciTypeFlow.hpp" @@ -1495,13 +1494,3 @@ bool ciMethod::is_consistent_info(ciMethod* declared_method, ciMethod* resolved_ } // ------------------------------------------------------------------ - -#if INCLUDE_TRACE -TraceStructCalleeMethod ciMethod::to_trace_struct() const { - TraceStructCalleeMethod result; - result.set_type(holder()->name()->as_utf8()); - result.set_name(name()->as_utf8()); - result.set_descriptor(signature()->as_symbol()->as_utf8()); - return result; -} -#endif diff --git a/src/hotspot/share/ci/ciMethod.hpp b/src/hotspot/share/ci/ciMethod.hpp index a9b0352aa25..57f1a9efdf4 100644 --- a/src/hotspot/share/ci/ciMethod.hpp +++ b/src/hotspot/share/ci/ciMethod.hpp @@ -32,7 +32,6 @@ #include "compiler/methodLiveness.hpp" #include "prims/methodHandles.hpp" #include "utilities/bitMap.hpp" -#include "trace/tracing.hpp" class ciMethodBlocks; class MethodLiveness; @@ -362,10 +361,6 @@ class ciMethod : public ciMetadata { void print_short_name(outputStream* st = tty); static bool is_consistent_info(ciMethod* declared_method, ciMethod* resolved_method); - -#if INCLUDE_TRACE - TraceStructCalleeMethod to_trace_struct() const; -#endif }; #endif // SHARE_VM_CI_CIMETHOD_HPP diff --git a/src/hotspot/share/classfile/classFileParser.cpp b/src/hotspot/share/classfile/classFileParser.cpp index 6e75e392183..059f9abca00 100644 --- a/src/hotspot/share/classfile/classFileParser.cpp +++ b/src/hotspot/share/classfile/classFileParser.cpp @@ -67,7 +67,6 @@ #include "runtime/timer.hpp" #include "services/classLoadingService.hpp" #include "services/threadService.hpp" -#include "trace/traceMacros.hpp" #include "utilities/align.hpp" #include "utilities/bitMap.inline.hpp" #include "utilities/copy.hpp" @@ -80,6 +79,9 @@ #if INCLUDE_CDS #include "classfile/systemDictionaryShared.hpp" #endif +#if INCLUDE_JFR +#include "jfr/support/jfrTraceIdExtension.hpp" +#endif // We generally try to create the oops directly when parsing, rather than // allocating temporary data structures and copying the bytes twice. A @@ -5639,7 +5641,7 @@ void ClassFileParser::fill_instance_klass(InstanceKlass* ik, bool changed_by_loa } } - TRACE_INIT_ID(ik); + JFR_ONLY(INIT_ID(ik);) // If we reach here, all is well. // Now remove the InstanceKlass* from the _klass_to_deallocate field diff --git a/src/hotspot/share/classfile/classLoaderData.cpp b/src/hotspot/share/classfile/classLoaderData.cpp index 0120c9f10e5..655cc79eedf 100644 --- a/src/hotspot/share/classfile/classLoaderData.cpp +++ b/src/hotspot/share/classfile/classLoaderData.cpp @@ -76,8 +76,10 @@ #include "utilities/growableArray.hpp" #include "utilities/macros.hpp" #include "utilities/ostream.hpp" -#if INCLUDE_TRACE -#include "trace/tracing.hpp" +#include "utilities/ticks.hpp" +#if INCLUDE_JFR +#include "jfr/jfr.hpp" +#include "jfr/jfrEvents.hpp" #endif volatile size_t ClassLoaderDataGraph::_num_array_classes = 0; @@ -161,7 +163,7 @@ ClassLoaderData::ClassLoaderData(Handle h_class_loader, bool is_anonymous) : NOT_PRODUCT(_dependency_count = 0); // number of class loader dependencies - TRACE_INIT_ID(this); + JFR_ONLY(INIT_ID(this);) } ClassLoaderData::ChunkedHandleList::~ChunkedHandleList() { @@ -1276,6 +1278,28 @@ bool ClassLoaderDataGraph::contains_loader_data(ClassLoaderData* loader_data) { } #endif // PRODUCT +#if INCLUDE_JFR +static Ticks class_unload_time; +static void post_class_unload_event(Klass* const k) { + assert(k != NULL, "invariant"); + EventClassUnload event(UNTIMED); + event.set_endtime(class_unload_time); + event.set_unloadedClass(k); + event.set_definingClassLoader(k->class_loader_data()); + event.commit(); +} + +static void post_class_unload_events() { + assert(SafepointSynchronize::is_at_safepoint(), "must be at safepoint!"); + if (Jfr::is_enabled()) { + if (EventClassUnload::is_enabled()) { + class_unload_time = Ticks::now(); + ClassLoaderDataGraph::classes_unloading_do(&post_class_unload_event); + } + Jfr::on_unloading_classes(); + } +} +#endif // INCLUDE_JFR // Move class loader data from main list to the unloaded list for unloading // and deallocation later. @@ -1353,8 +1377,7 @@ bool ClassLoaderDataGraph::do_unloading(bool clean_previous_versions) { } data = data->next(); } - - post_class_unload_events(); + JFR_ONLY(post_class_unload_events();) } log_debug(class, loader, data)("do_unloading: loaders processed %u, loaders removed %u", loaders_processed, loaders_removed); @@ -1393,20 +1416,6 @@ int ClassLoaderDataGraph::resize_if_needed() { return resized; } -void ClassLoaderDataGraph::post_class_unload_events() { -#if INCLUDE_TRACE - assert(SafepointSynchronize::is_at_safepoint(), "must be at safepoint!"); - if (Tracing::enabled()) { - if (Tracing::is_event_enabled(TraceClassUnloadEvent)) { - assert(_unloading != NULL, "need class loader data unload list!"); - _class_unload_time = Ticks::now(); - classes_unloading_do(&class_unload_event); - } - Tracing::on_unloading_classes(); - } -#endif -} - ClassLoaderDataGraphKlassIteratorAtomic::ClassLoaderDataGraphKlassIteratorAtomic() : _next_klass(NULL) { ClassLoaderData* cld = ClassLoaderDataGraph::_head; @@ -1490,20 +1499,3 @@ void ClassLoaderDataGraph::print_on(outputStream * const out) { } } #endif // PRODUCT - -#if INCLUDE_TRACE - -Ticks ClassLoaderDataGraph::_class_unload_time; - -void ClassLoaderDataGraph::class_unload_event(Klass* const k) { - assert(k != NULL, "invariant"); - - // post class unload event - EventClassUnload event(UNTIMED); - event.set_endtime(_class_unload_time); - event.set_unloadedClass(k); - event.set_definingClassLoader(k->class_loader_data()); - event.commit(); -} - -#endif // INCLUDE_TRACE diff --git a/src/hotspot/share/classfile/classLoaderData.hpp b/src/hotspot/share/classfile/classLoaderData.hpp index 92745fc46b5..e88f77ecebf 100644 --- a/src/hotspot/share/classfile/classLoaderData.hpp +++ b/src/hotspot/share/classfile/classLoaderData.hpp @@ -31,13 +31,13 @@ #include "oops/oopHandle.hpp" #include "oops/weakHandle.hpp" #include "runtime/mutex.hpp" -#include "trace/traceMacros.hpp" #include "utilities/growableArray.hpp" #include "utilities/macros.hpp" -#if INCLUDE_TRACE -#include "utilities/ticks.hpp" +#if INCLUDE_JFR +#include "jfr/support/jfrTraceIdExtension.hpp" #endif + // // A class loader represents a linkset. Conceptually, a linkset identifies // the complete transitive closure of resolved links that a dynamic linker can @@ -85,7 +85,6 @@ class ClassLoaderDataGraph : public AllStatic { static ClassLoaderData* add_to_graph(Handle class_loader, bool anonymous); static ClassLoaderData* add(Handle class_loader, bool anonymous); - static void post_class_unload_events(); public: static ClassLoaderData* find_or_create(Handle class_loader); static void purge(); @@ -167,12 +166,6 @@ class ClassLoaderDataGraph : public AllStatic { #ifndef PRODUCT static bool contains_loader_data(ClassLoaderData* loader_data); #endif - -#if INCLUDE_TRACE - private: - static Ticks _class_unload_time; - static void class_unload_event(Klass* const k); -#endif }; // ClassLoaderData class @@ -268,7 +261,7 @@ class ClassLoaderData : public CHeapObj { // JFR support Klass* _class_loader_klass; Symbol* _class_loader_name; - TRACE_DEFINE_TRACE_ID_FIELD; + JFR_ONLY(DEFINE_TRACE_ID_FIELD;) void set_next(ClassLoaderData* next) { _next = next; } ClassLoaderData* next() const { return _next; } @@ -410,7 +403,7 @@ class ClassLoaderData : public CHeapObj { Klass* class_loader_klass() const { return _class_loader_klass; } Symbol* class_loader_name() const { return _class_loader_name; } - TRACE_DEFINE_TRACE_ID_METHODS; + JFR_ONLY(DEFINE_TRACE_ID_METHODS;) }; // An iterator that distributes Klasses to parallel worker threads. diff --git a/src/hotspot/share/classfile/klassFactory.cpp b/src/hotspot/share/classfile/klassFactory.cpp index 474d7f8c66a..d6214e6fd70 100644 --- a/src/hotspot/share/classfile/klassFactory.cpp +++ b/src/hotspot/share/classfile/klassFactory.cpp @@ -35,7 +35,11 @@ #include "prims/jvmtiEnvBase.hpp" #include "prims/jvmtiRedefineClasses.hpp" #include "runtime/handles.inline.hpp" -#include "trace/traceMacros.hpp" +#include "utilities/macros.hpp" +#if INCLUDE_JFR +#include "jfr/support/jfrKlassExtension.hpp" +#endif + // called during initial loading of a shared class InstanceKlass* KlassFactory::check_shared_class_file_load_hook( @@ -228,7 +232,7 @@ InstanceKlass* KlassFactory::create_from_stream(ClassFileStream* stream, result->store_fingerprint(stream->compute_fingerprint()); } - TRACE_KLASS_CREATION(result, parser, THREAD); + JFR_ONLY(ON_KLASS_CREATION(result, parser, THREAD);) #if INCLUDE_CDS if (DumpSharedSpaces) { diff --git a/src/hotspot/share/classfile/moduleEntry.cpp b/src/hotspot/share/classfile/moduleEntry.cpp index ce2c960b714..1a64a6e6a58 100644 --- a/src/hotspot/share/classfile/moduleEntry.cpp +++ b/src/hotspot/share/classfile/moduleEntry.cpp @@ -33,7 +33,6 @@ #include "oops/symbol.hpp" #include "runtime/handles.inline.hpp" #include "runtime/safepoint.hpp" -#include "trace/traceMacros.hpp" #include "utilities/events.hpp" #include "utilities/growableArray.hpp" #include "utilities/hashtable.inline.hpp" @@ -215,7 +214,7 @@ void ModuleEntry::purge_reads() { } } -void ModuleEntry::module_reads_do(ModuleClosure* const f) { +void ModuleEntry::module_reads_do(ModuleClosure* f) { assert_locked_or_safepoint(Module_lock); assert(f != NULL, "invariant"); @@ -279,7 +278,7 @@ ModuleEntry* ModuleEntry::new_unnamed_module_entry(Handle module_handle, ClassLo entry->set_loader_data(cld); entry->_is_open = true; - TRACE_INIT_ID(entry); + JFR_ONLY(INIT_ID(entry);) return entry; } @@ -367,7 +366,7 @@ ModuleEntry* ModuleEntryTable::new_entry(unsigned int hash, Handle module_handle } } - TRACE_INIT_ID(entry); + JFR_ONLY(INIT_ID(entry);) return entry; } diff --git a/src/hotspot/share/classfile/moduleEntry.hpp b/src/hotspot/share/classfile/moduleEntry.hpp index 37a6756003c..232b2644f3c 100644 --- a/src/hotspot/share/classfile/moduleEntry.hpp +++ b/src/hotspot/share/classfile/moduleEntry.hpp @@ -32,10 +32,13 @@ #include "oops/symbol.hpp" #include "runtime/jniHandles.hpp" #include "runtime/mutexLocker.hpp" -#include "trace/traceMacros.hpp" #include "utilities/growableArray.hpp" #include "utilities/hashtable.hpp" +#include "utilities/macros.hpp" #include "utilities/ostream.hpp" +#if INCLUDE_JFR +#include "jfr/support/jfrTraceIdExtension.hpp" +#endif #define UNNAMED_MODULE "Unnamed Module" #define JAVAPKG "java" @@ -69,7 +72,7 @@ private: bool _must_walk_reads; // walk module's reads list at GC safepoints to purge out dead modules bool _is_open; // whether the packages in the module are all unqualifiedly exported bool _is_patched; // whether the module is patched via --patch-module - TRACE_DEFINE_TRACE_ID_FIELD; + JFR_ONLY(DEFINE_TRACE_ID_FIELD;) enum {MODULE_READS_SIZE = 101}; // Initial size of list of modules that the module can read. public: @@ -164,8 +167,6 @@ public: // iteration support for readability void module_reads_do(ModuleClosure* const f); - TRACE_DEFINE_TRACE_ID_METHODS; - // Purge dead weak references out of reads list when any given class loader is unloaded. void purge_reads(); void delete_reads(); @@ -178,12 +179,14 @@ public: void print(outputStream* st = tty); void verify(); + + JFR_ONLY(DEFINE_TRACE_ID_METHODS;) }; // Iterator interface class ModuleClosure: public StackObj { public: - virtual void do_module(ModuleEntry* const module) = 0; + virtual void do_module(ModuleEntry* module) = 0; }; diff --git a/src/hotspot/share/classfile/packageEntry.cpp b/src/hotspot/share/classfile/packageEntry.cpp index aba979937b9..1495c207767 100644 --- a/src/hotspot/share/classfile/packageEntry.cpp +++ b/src/hotspot/share/classfile/packageEntry.cpp @@ -29,7 +29,6 @@ #include "memory/resourceArea.hpp" #include "oops/symbol.hpp" #include "runtime/handles.inline.hpp" -#include "trace/traceMacros.hpp" #include "utilities/events.hpp" #include "utilities/growableArray.hpp" #include "utilities/hashtable.inline.hpp" @@ -200,7 +199,7 @@ PackageEntry* PackageEntryTable::new_entry(unsigned int hash, Symbol* name, Modu assert(Module_lock->owned_by_self(), "should have the Module_lock"); PackageEntry* entry = (PackageEntry*)Hashtable::allocate_new_entry(hash, name); - TRACE_INIT_ID(entry); + JFR_ONLY(INIT_ID(entry);) // Initialize fields specific to a PackageEntry entry->init(); @@ -283,7 +282,7 @@ void PackageEntryTable::verify_javabase_packages(GrowableArray *pkg_lis } // iteration of qualified exports -void PackageEntry::package_exports_do(ModuleClosure* const f) { +void PackageEntry::package_exports_do(ModuleClosure* f) { assert_locked_or_safepoint(Module_lock); assert(f != NULL, "invariant"); diff --git a/src/hotspot/share/classfile/packageEntry.hpp b/src/hotspot/share/classfile/packageEntry.hpp index ecee6a1d083..f26ce3a6487 100644 --- a/src/hotspot/share/classfile/packageEntry.hpp +++ b/src/hotspot/share/classfile/packageEntry.hpp @@ -29,7 +29,12 @@ #include "oops/symbol.hpp" #include "utilities/growableArray.hpp" #include "utilities/hashtable.hpp" +#include "utilities/macros.hpp" #include "utilities/ostream.hpp" +#if INCLUDE_JFR +#include "jfr/support/jfrTraceIdExtension.hpp" +#endif + // A PackageEntry basically represents a Java package. It contains: // - Symbol* containing the package's name. @@ -104,7 +109,7 @@ private: // Contains list of modules this package is qualifiedly exported to. Access // to this list is protected by the Module_lock. GrowableArray* _qualified_exports; - TRACE_DEFINE_TRACE_ID_FIELD; + JFR_ONLY(DEFINE_TRACE_ID_FIELD;) // Initial size of a package entry's list of qualified exports. enum {QUAL_EXP_SIZE = 43}; @@ -197,9 +202,9 @@ public: } // iteration of qualified exports - void package_exports_do(ModuleClosure* const f); + void package_exports_do(ModuleClosure* f); - TRACE_DEFINE_TRACE_ID_METHODS; + JFR_ONLY(DEFINE_TRACE_ID_METHODS;) // Purge dead weak references out of exported list when any given class loader is unloaded. void purge_qualified_exports(); diff --git a/src/hotspot/share/classfile/systemDictionary.cpp b/src/hotspot/share/classfile/systemDictionary.cpp index cd705214330..b578e63283e 100644 --- a/src/hotspot/share/classfile/systemDictionary.cpp +++ b/src/hotspot/share/classfile/systemDictionary.cpp @@ -47,6 +47,7 @@ #include "gc/shared/oopStorage.inline.hpp" #include "interpreter/bytecodeStream.hpp" #include "interpreter/interpreter.hpp" +#include "jfr/jfrEvents.hpp" #include "logging/log.hpp" #include "logging/logStream.hpp" #include "memory/filemap.hpp" @@ -81,7 +82,6 @@ #include "services/classLoadingService.hpp" #include "services/diagnosticCommand.hpp" #include "services/threadService.hpp" -#include "trace/tracing.hpp" #include "utilities/macros.hpp" #if INCLUDE_CDS #include "classfile/systemDictionaryShared.hpp" @@ -623,32 +623,16 @@ InstanceKlass* SystemDictionary::handle_parallel_super_load( return NULL; } -static void post_class_load_event(EventClassLoad* event, - const InstanceKlass* k, - const ClassLoaderData* init_cld) { -#if INCLUDE_TRACE +static void post_class_load_event(EventClassLoad* event, const InstanceKlass* k, const ClassLoaderData* init_cld) { assert(event != NULL, "invariant"); assert(k != NULL, "invariant"); - if (event->should_commit()) { - event->set_loadedClass(k); - event->set_definingClassLoader(k->class_loader_data()); - event->set_initiatingClassLoader(init_cld); - event->commit(); - } -#endif // INCLUDE_TRACE + assert(event->should_commit(), "invariant"); + event->set_loadedClass(k); + event->set_definingClassLoader(k->class_loader_data()); + event->set_initiatingClassLoader(init_cld); + event->commit(); } -static void class_define_event(InstanceKlass* k, - const ClassLoaderData* def_cld) { -#if INCLUDE_TRACE - EventClassDefine event; - if (event.should_commit()) { - event.set_definedClass(k); - event.set_definingClassLoader(def_cld); - event.commit(); - } -#endif // INCLUDE_TRACE -} // Be careful when modifying this code: once you have run // placeholders()->find_and_add(PlaceholderTable::LOAD_INSTANCE), @@ -881,9 +865,9 @@ Klass* SystemDictionary::resolve_instance_class_or_null(Symbol* name, if (HAS_PENDING_EXCEPTION || k == NULL) { return NULL; } - - post_class_load_event(&class_load_start_event, k, loader_data); - + if (class_load_start_event.should_commit()) { + post_class_load_event(&class_load_start_event, k, loader_data); + } #ifdef ASSERT { ClassLoaderData* loader_data = k->class_loader_data(); @@ -1045,8 +1029,9 @@ InstanceKlass* SystemDictionary::parse_stream(Symbol* class_name, assert(THREAD->is_Java_thread(), "thread->is_Java_thread()"); JvmtiExport::post_class_load((JavaThread *) THREAD, k); } - - post_class_load_event(&class_load_start_event, k, loader_data); + if (class_load_start_event.should_commit()) { + post_class_load_event(&class_load_start_event, k, loader_data); + } } assert(host_klass != NULL || NULL == cp_patches, "cp_patches only found with host_klass"); @@ -1558,6 +1543,15 @@ InstanceKlass* SystemDictionary::load_instance_class(Symbol* class_name, Handle } } +static void post_class_define_event(InstanceKlass* k, const ClassLoaderData* def_cld) { + EventClassDefine event; + if (event.should_commit()) { + event.set_definedClass(k); + event.set_definingClassLoader(def_cld); + event.commit(); + } +} + void SystemDictionary::define_instance_class(InstanceKlass* k, TRAPS) { HandleMark hm(THREAD); @@ -1626,7 +1620,7 @@ void SystemDictionary::define_instance_class(InstanceKlass* k, TRAPS) { JvmtiExport::post_class_load((JavaThread *) THREAD, k); } - class_define_event(k, loader_data); + post_class_define_event(k, loader_data); } // Support parallel classloading diff --git a/src/hotspot/share/classfile/vmSymbols.cpp b/src/hotspot/share/classfile/vmSymbols.cpp index 50ac81381d9..ebbf66f8ab0 100644 --- a/src/hotspot/share/classfile/vmSymbols.cpp +++ b/src/hotspot/share/classfile/vmSymbols.cpp @@ -350,7 +350,7 @@ vmIntrinsics::ID vmIntrinsics::for_raw_conversion(BasicType src, BasicType dest) bool vmIntrinsics::preserves_state(vmIntrinsics::ID id) { assert(id != vmIntrinsics::_none, "must be a VM intrinsic"); switch(id) { -#ifdef TRACE_HAVE_INTRINSICS +#ifdef JFR_HAVE_INTRINSICS case vmIntrinsics::_counterTime: #endif case vmIntrinsics::_currentTimeMillis: @@ -388,7 +388,7 @@ bool vmIntrinsics::preserves_state(vmIntrinsics::ID id) { bool vmIntrinsics::can_trap(vmIntrinsics::ID id) { assert(id != vmIntrinsics::_none, "must be a VM intrinsic"); switch(id) { -#ifdef TRACE_HAVE_INTRINSICS +#ifdef JFR_HAVE_INTRINSICS case vmIntrinsics::_counterTime: case vmIntrinsics::_getClassId: #endif @@ -424,7 +424,7 @@ bool vmIntrinsics::can_trap(vmIntrinsics::ID id) { bool vmIntrinsics::should_be_pinned(vmIntrinsics::ID id) { assert(id != vmIntrinsics::_none, "must be a VM intrinsic"); switch(id) { -#ifdef TRACE_HAVE_INTRINSICS +#ifdef JFR_HAVE_INTRINSICS case vmIntrinsics::_counterTime: #endif case vmIntrinsics::_currentTimeMillis: diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index 46554467f70..f9f22a68649 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -26,10 +26,12 @@ #define SHARE_VM_CLASSFILE_VMSYMBOLS_HPP #include "classfile/moduleEntry.hpp" -#include "oops/symbol.hpp" -#include "memory/iterator.hpp" -#include "trace/traceMacros.hpp" +#include "jfr/support/jfrIntrinsics.hpp" #include "jvmci/vmSymbols_jvmci.hpp" +#include "memory/iterator.hpp" +#include "oops/symbol.hpp" +#include "utilities/macros.hpp" + // The class vmSymbols is a name space for fast lookup of // symbols commonly used in the VM. @@ -640,8 +642,8 @@ /* forEachRemaining support */ \ template(java_util_stream_StreamsRangeIntSpliterator, "java/util/stream/Streams$RangeIntSpliterator") \ \ - /* trace signatures */ \ - TRACE_TEMPLATES(template) \ + /* jfr signatures */ \ + JFR_TEMPLATES(template) \ \ /* cds */ \ template(jdk_internal_loader_ClassLoaders, "jdk/internal/loader/ClassLoaders") \ @@ -827,7 +829,7 @@ do_intrinsic(_nanoTime, java_lang_System, nanoTime_name, void_long_signature, F_S) \ do_name( nanoTime_name, "nanoTime") \ \ - TRACE_INTRINSICS(do_intrinsic, do_class, do_name, do_signature, do_alias) \ + JFR_INTRINSICS(do_intrinsic, do_class, do_name, do_signature, do_alias) \ \ do_intrinsic(_arraycopy, java_lang_System, arraycopy_name, arraycopy_signature, F_S) \ do_name( arraycopy_name, "arraycopy") \ diff --git a/src/hotspot/share/code/codeCache.cpp b/src/hotspot/share/code/codeCache.cpp index 68e2d624391..8c3438210c2 100644 --- a/src/hotspot/share/code/codeCache.cpp +++ b/src/hotspot/share/code/codeCache.cpp @@ -33,6 +33,7 @@ #include "code/nmethod.hpp" #include "code/pcDesc.hpp" #include "compiler/compileBroker.hpp" +#include "jfr/jfrEvents.hpp" #include "logging/log.hpp" #include "logging/logStream.hpp" #include "memory/allocation.inline.hpp" @@ -53,7 +54,6 @@ #include "runtime/sweeper.hpp" #include "runtime/vmThread.hpp" #include "services/memoryService.hpp" -#include "trace/tracing.hpp" #include "utilities/align.hpp" #include "utilities/vmError.hpp" #include "utilities/xmlstream.hpp" diff --git a/src/hotspot/share/compiler/compileBroker.cpp b/src/hotspot/share/compiler/compileBroker.cpp index e72a809fa0c..375153c0c7a 100644 --- a/src/hotspot/share/compiler/compileBroker.cpp +++ b/src/hotspot/share/compiler/compileBroker.cpp @@ -35,6 +35,7 @@ #include "compiler/compilerOracle.hpp" #include "compiler/directivesParser.hpp" #include "interpreter/linkResolver.hpp" +#include "jfr/jfrEvents.hpp" #include "logging/log.hpp" #include "logging/logStream.hpp" #include "memory/allocation.inline.hpp" @@ -57,11 +58,11 @@ #include "runtime/sweeper.hpp" #include "runtime/timerTrace.hpp" #include "runtime/vframe.inline.hpp" -#include "trace/tracing.hpp" #include "utilities/debug.hpp" #include "utilities/dtrace.hpp" #include "utilities/events.hpp" #include "utilities/formatBuffer.hpp" +#include "utilities/macros.hpp" #ifdef COMPILER1 #include "c1/c1_Compiler.hpp" #endif @@ -1945,8 +1946,7 @@ static void codecache_print(outputStream* out, bool detailed) { } } -void CompileBroker::post_compile(CompilerThread* thread, CompileTask* task, EventCompilation& event, bool success, ciEnv* ci_env) { - +void CompileBroker::post_compile(CompilerThread* thread, CompileTask* task, bool success, ciEnv* ci_env) { if (success) { task->mark_success(); if (ci_env != NULL) { @@ -1959,19 +1959,21 @@ void CompileBroker::post_compile(CompilerThread* thread, CompileTask* task, Even } } } - // simulate crash during compilation assert(task->compile_id() != CICrashAt, "just as planned"); - if (event.should_commit()) { - event.set_method(task->method()); - event.set_compileId(task->compile_id()); - event.set_compileLevel(task->comp_level()); - event.set_succeded(task->is_success()); - event.set_isOsr(task->osr_bci() != CompileBroker::standard_entry_bci); - event.set_codeSize((task->code() == NULL) ? 0 : task->code()->total_size()); - event.set_inlinedBytes(task->num_inlined_bytecodes()); - event.commit(); - } +} + +static void post_compilation_event(EventCompilation* event, CompileTask* task) { + assert(event != NULL, "invariant"); + assert(event->should_commit(), "invariant"); + event->set_method(task->method()); + event->set_compileId(task->compile_id()); + event->set_compileLevel(task->comp_level()); + event->set_succeded(task->is_success()); + event->set_isOsr(task->osr_bci() != CompileBroker::standard_entry_bci); + event->set_codeSize((task->code() == NULL) ? 0 : task->code()->total_size()); + event->set_inlinedBytes(task->num_inlined_bytecodes()); + event->commit(); } int DirectivesStack::_depth = 0; @@ -2066,7 +2068,10 @@ void CompileBroker::invoke_compiler_on_method(CompileTask* task) { compilable = ciEnv::MethodCompilable_not_at_tier; } } - post_compile(thread, task, event, task->code() != NULL, NULL); + post_compile(thread, task, task->code() != NULL, NULL); + if (event.should_commit()) { + post_compilation_event(&event, task); + } } else #endif // INCLUDE_JVMCI @@ -2123,7 +2128,10 @@ void CompileBroker::invoke_compiler_on_method(CompileTask* task) { ci_env.report_failure(failure_reason); } - post_compile(thread, task, event, !ci_env.failing(), &ci_env); + post_compile(thread, task, !ci_env.failing(), &ci_env); + if (event.should_commit()) { + post_compilation_event(&event, task); + } } // Remove the JNI handle block after the ciEnv destructor has run in // the previous block. diff --git a/src/hotspot/share/compiler/compileBroker.hpp b/src/hotspot/share/compiler/compileBroker.hpp index e0a9b4be3a8..312a4a5c526 100644 --- a/src/hotspot/share/compiler/compileBroker.hpp +++ b/src/hotspot/share/compiler/compileBroker.hpp @@ -30,7 +30,6 @@ #include "compiler/compileTask.hpp" #include "compiler/compilerDirectives.hpp" #include "runtime/perfData.hpp" -#include "trace/tracing.hpp" #include "utilities/stack.hpp" #if INCLUDE_JVMCI #include "jvmci/jvmciCompiler.hpp" @@ -252,7 +251,7 @@ class CompileBroker: AllStatic { #endif static void invoke_compiler_on_method(CompileTask* task); - static void post_compile(CompilerThread* thread, CompileTask* task, EventCompilation& event, bool success, ciEnv* ci_env); + static void post_compile(CompilerThread* thread, CompileTask* task, bool success, ciEnv* ci_env); static void set_last_compile(CompilerThread *thread, const methodHandle& method, bool is_osr, int comp_level); static void push_jni_handle_block(); static void pop_jni_handle_block(); diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp index 951e1408aca..2d9c042bced 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp @@ -86,7 +86,6 @@ class STWGCTimer; class G1NewTracer; class EvacuationFailedInfo; class nmethod; -class Ticks; class WorkGang; class G1Allocator; class G1ArchiveAllocator; diff --git a/src/hotspot/share/gc/g1/g1EvacStats.cpp b/src/hotspot/share/gc/g1/g1EvacStats.cpp index bdfef475ddf..70c3996911a 100644 --- a/src/hotspot/share/gc/g1/g1EvacStats.cpp +++ b/src/hotspot/share/gc/g1/g1EvacStats.cpp @@ -23,12 +23,11 @@ */ #include "precompiled.hpp" -#include "memory/allocation.inline.hpp" #include "gc/g1/g1_globals.hpp" #include "gc/g1/g1EvacStats.hpp" #include "gc/shared/gcId.hpp" #include "logging/log.hpp" -#include "trace/tracing.hpp" +#include "memory/allocation.inline.hpp" void G1EvacStats::log_plab_allocation() { PLABStats::log_plab_allocation(); diff --git a/src/hotspot/share/gc/g1/g1FullGCAdjustTask.cpp b/src/hotspot/share/gc/g1/g1FullGCAdjustTask.cpp index b608984fe84..5e7b291e26e 100644 --- a/src/hotspot/share/gc/g1/g1FullGCAdjustTask.cpp +++ b/src/hotspot/share/gc/g1/g1FullGCAdjustTask.cpp @@ -34,7 +34,6 @@ #include "gc/shared/gcTraceTime.inline.hpp" #include "gc/shared/referenceProcessor.hpp" #include "logging/log.hpp" -#include "utilities/ticks.inline.hpp" class G1AdjustLiveClosure : public StackObj { G1AdjustClosure* _adjust_closure; diff --git a/src/hotspot/share/gc/g1/g1FullGCCompactTask.cpp b/src/hotspot/share/gc/g1/g1FullGCCompactTask.cpp index 222737bbf5a..660afc88f02 100644 --- a/src/hotspot/share/gc/g1/g1FullGCCompactTask.cpp +++ b/src/hotspot/share/gc/g1/g1FullGCCompactTask.cpp @@ -32,7 +32,7 @@ #include "gc/shared/gcTraceTime.inline.hpp" #include "logging/log.hpp" #include "oops/oop.inline.hpp" -#include "utilities/ticks.inline.hpp" +#include "utilities/ticks.hpp" class G1ResetHumongousClosure : public HeapRegionClosure { G1CMBitMap* _bitmap; diff --git a/src/hotspot/share/gc/g1/g1FullGCCompactTask.hpp b/src/hotspot/share/gc/g1/g1FullGCCompactTask.hpp index cd6a41f078a..6c8eaf5967e 100644 --- a/src/hotspot/share/gc/g1/g1FullGCCompactTask.hpp +++ b/src/hotspot/share/gc/g1/g1FullGCCompactTask.hpp @@ -31,7 +31,6 @@ #include "gc/g1/g1StringDedup.hpp" #include "gc/g1/heapRegionManager.hpp" #include "gc/shared/referenceProcessor.hpp" -#include "utilities/ticks.hpp" class G1CollectedHeap; class G1CMBitMap; diff --git a/src/hotspot/share/gc/g1/g1FullGCPrepareTask.cpp b/src/hotspot/share/gc/g1/g1FullGCPrepareTask.cpp index 05335ca837d..3e08b5a41bd 100644 --- a/src/hotspot/share/gc/g1/g1FullGCPrepareTask.cpp +++ b/src/hotspot/share/gc/g1/g1FullGCPrepareTask.cpp @@ -36,7 +36,7 @@ #include "gc/shared/referenceProcessor.hpp" #include "logging/log.hpp" #include "oops/oop.inline.hpp" -#include "utilities/ticks.inline.hpp" +#include "utilities/ticks.hpp" bool G1FullGCPrepareTask::G1CalculatePointersClosure::do_heap_region(HeapRegion* hr) { if (hr->is_humongous()) { diff --git a/src/hotspot/share/gc/g1/g1FullGCPrepareTask.hpp b/src/hotspot/share/gc/g1/g1FullGCPrepareTask.hpp index 3be04d160f5..fcaf797a12f 100644 --- a/src/hotspot/share/gc/g1/g1FullGCPrepareTask.hpp +++ b/src/hotspot/share/gc/g1/g1FullGCPrepareTask.hpp @@ -32,7 +32,6 @@ #include "gc/g1/g1StringDedup.hpp" #include "gc/g1/heapRegionManager.hpp" #include "gc/shared/referenceProcessor.hpp" -#include "utilities/ticks.hpp" class G1CMBitMap; diff --git a/src/hotspot/share/gc/g1/g1FullGCTask.cpp b/src/hotspot/share/gc/g1/g1FullGCTask.cpp index 52fe559098e..b9faa7af603 100644 --- a/src/hotspot/share/gc/g1/g1FullGCTask.cpp +++ b/src/hotspot/share/gc/g1/g1FullGCTask.cpp @@ -25,7 +25,7 @@ #include "precompiled.hpp" #include "gc/g1/g1FullGCTask.hpp" #include "logging/log.hpp" -#include "utilities/ticks.inline.hpp" +#include "utilities/ticks.hpp" void G1FullGCTask::log_task(const char* name, uint worker_id, const Ticks& start, const Ticks& stop) { Tickspan duration = stop - start; diff --git a/src/hotspot/share/gc/g1/g1GCPhaseTimes.cpp b/src/hotspot/share/gc/g1/g1GCPhaseTimes.cpp index 50662f273a5..5aeb4baa453 100644 --- a/src/hotspot/share/gc/g1/g1GCPhaseTimes.cpp +++ b/src/hotspot/share/gc/g1/g1GCPhaseTimes.cpp @@ -488,7 +488,7 @@ G1GCParPhaseTimesTracker::G1GCParPhaseTimesTracker(G1GCPhaseTimes* phase_times, G1GCParPhaseTimesTracker::~G1GCParPhaseTimesTracker() { if (_phase_times != NULL) { - _phase_times->record_time_secs(_phase, _worker_id, TicksToTimeHelper::seconds(Ticks::now() - _start_time)); + _phase_times->record_time_secs(_phase, _worker_id, (Ticks::now() - _start_time).seconds()); } } @@ -506,7 +506,7 @@ G1EvacPhaseTimesTracker::~G1EvacPhaseTimesTracker() { if (_phase_times != NULL) { // Exclude trim time by increasing the start time. _start_time += _trim_time; - _phase_times->record_or_add_objcopy_time_secs(_worker_id, TicksToTimeHelper::seconds(_trim_time)); + _phase_times->record_or_add_objcopy_time_secs(_worker_id, _trim_time.seconds()); } } diff --git a/src/hotspot/share/gc/g1/g1HeapRegionEventSender.cpp b/src/hotspot/share/gc/g1/g1HeapRegionEventSender.cpp new file mode 100644 index 00000000000..28fc58d19a5 --- /dev/null +++ b/src/hotspot/share/gc/g1/g1HeapRegionEventSender.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "gc/g1/g1CollectedHeap.hpp" +#include "gc/g1/heapRegion.hpp" +#include "g1HeapRegionEventSender.hpp" +#include "jfr/jfrEvents.hpp" + +class DumpEventInfoClosure : public HeapRegionClosure { +public: + bool do_heap_region(HeapRegion* r) { + EventG1HeapRegionInformation evt; + evt.set_index(r->hrm_index()); + evt.set_type(r->get_trace_type()); + evt.set_start((uintptr_t)r->bottom()); + evt.set_used(r->used()); + evt.commit(); + return false; + } +}; + + +void G1HeapRegionEventSender::send_events() { + DumpEventInfoClosure c; + + G1CollectedHeap::heap()->heap_region_iterate(&c); +} diff --git a/src/hotspot/share/trace/noTraceBackend.hpp b/src/hotspot/share/gc/g1/g1HeapRegionEventSender.hpp similarity index 71% rename from src/hotspot/share/trace/noTraceBackend.hpp rename to src/hotspot/share/gc/g1/g1HeapRegionEventSender.hpp index fc557907c95..1a7dc7b0ca2 100644 --- a/src/hotspot/share/trace/noTraceBackend.hpp +++ b/src/hotspot/share/gc/g1/g1HeapRegionEventSender.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -21,24 +21,15 @@ * questions. * */ -#ifndef SHARE_VM_TRACE_NOTRACEBACKEND_HPP -#define SHARE_VM_TRACE_NOTRACEBACKEND_HPP -#include "jni.h" -#include "trace/traceTime.hpp" +#ifndef SHARE_VM_GC_G1_G1HEAPREGIONEVENTSENDER_HPP +#define SHARE_VM_GC_G1_G1HEAPREGIONEVENTSENDER_HPP -class NoTraceBackend { +#include "memory/allocation.hpp" + +class G1HeapRegionEventSender : public AllStatic { public: - static TracingTime time() { - return 0; - } + static void send_events(); }; -class TraceThreadData { -public: - TraceThreadData() {} -}; - -typedef NoTraceBackend Tracing; - -#endif // SHARE_VM_TRACE_NOTRACEBACKEND_HPP +#endif // SHARE_VM_GC_G1_G1HEAPREGIONEVENTSENDER_HPP diff --git a/src/hotspot/share/gc/g1/g1ParScanThreadState.inline.hpp b/src/hotspot/share/gc/g1/g1ParScanThreadState.inline.hpp index 06eb276a08a..d59afd27e2a 100644 --- a/src/hotspot/share/gc/g1/g1ParScanThreadState.inline.hpp +++ b/src/hotspot/share/gc/g1/g1ParScanThreadState.inline.hpp @@ -29,7 +29,6 @@ #include "gc/g1/g1RemSet.hpp" #include "oops/access.inline.hpp" #include "oops/oop.inline.hpp" -#include "utilities/ticks.inline.hpp" template void G1ParScanThreadState::do_oop_evac(T* p) { // Reference should not be NULL here as such are never pushed to the task queue. diff --git a/src/hotspot/share/gc/g1/g1RemSet.cpp b/src/hotspot/share/gc/g1/g1RemSet.cpp index 9e3fcb3e8c1..de759878bba 100644 --- a/src/hotspot/share/gc/g1/g1RemSet.cpp +++ b/src/hotspot/share/gc/g1/g1RemSet.cpp @@ -44,11 +44,12 @@ #include "memory/resourceArea.hpp" #include "oops/access.inline.hpp" #include "oops/oop.inline.hpp" +#include "runtime/os.hpp" #include "utilities/align.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/intHisto.hpp" #include "utilities/stack.inline.hpp" -#include "utilities/ticks.inline.hpp" +#include "utilities/ticks.hpp" // Collects information about the overall remembered set scan progress during an evacuation. class G1RemSetScanState : public CHeapObj { @@ -428,15 +429,15 @@ void G1RemSet::scan_rem_set(G1ParScanThreadState* pss, uint worker_i) { G1GCPhaseTimes* p = _g1p->phase_times(); - p->record_time_secs(G1GCPhaseTimes::ScanRS, worker_i, TicksToTimeHelper::seconds(cl.rem_set_root_scan_time())); - p->add_time_secs(G1GCPhaseTimes::ObjCopy, worker_i, TicksToTimeHelper::seconds(cl.rem_set_trim_partially_time())); + p->record_time_secs(G1GCPhaseTimes::ScanRS, worker_i, cl.rem_set_root_scan_time().seconds()); + p->add_time_secs(G1GCPhaseTimes::ObjCopy, worker_i, cl.rem_set_trim_partially_time().seconds()); p->record_thread_work_item(G1GCPhaseTimes::ScanRS, worker_i, cl.cards_scanned(), G1GCPhaseTimes::ScanRSScannedCards); p->record_thread_work_item(G1GCPhaseTimes::ScanRS, worker_i, cl.cards_claimed(), G1GCPhaseTimes::ScanRSClaimedCards); p->record_thread_work_item(G1GCPhaseTimes::ScanRS, worker_i, cl.cards_skipped(), G1GCPhaseTimes::ScanRSSkippedCards); - p->record_time_secs(G1GCPhaseTimes::CodeRoots, worker_i, TicksToTimeHelper::seconds(cl.strong_code_root_scan_time())); - p->add_time_secs(G1GCPhaseTimes::ObjCopy, worker_i, TicksToTimeHelper::seconds(cl.strong_code_root_trim_partially_time())); + p->record_time_secs(G1GCPhaseTimes::CodeRoots, worker_i, cl.strong_code_root_scan_time().seconds()); + p->add_time_secs(G1GCPhaseTimes::ObjCopy, worker_i, cl.strong_code_root_trim_partially_time().seconds()); } // Closure used for updating rem sets. Only called during an evacuation pause. @@ -935,7 +936,7 @@ public: "TARS " PTR_FORMAT, region_idx, _cm->liveness(region_idx) * HeapWordSize, - TicksToTimeHelper::seconds(time) * 1000.0, + time.seconds() * 1000.0, marked_bytes, p2i(hr->bottom()), p2i(top_at_mark_start), diff --git a/src/hotspot/share/gc/g1/heapRegionTracer.cpp b/src/hotspot/share/gc/g1/heapRegionTracer.cpp index 6cffce22bfe..e039ae219a1 100644 --- a/src/hotspot/share/gc/g1/heapRegionTracer.cpp +++ b/src/hotspot/share/gc/g1/heapRegionTracer.cpp @@ -24,7 +24,7 @@ #include "precompiled.hpp" #include "gc/g1/heapRegionTracer.hpp" -#include "trace/tracing.hpp" +#include "jfr/jfrEvents.hpp" void HeapRegionTracer::send_region_type_change(uint index, G1HeapRegionTraceType::Type from, diff --git a/src/hotspot/share/gc/shared/ageTableTracer.cpp b/src/hotspot/share/gc/shared/ageTableTracer.cpp index 2ae79b7cd37..5e0fbcfbcb1 100644 --- a/src/hotspot/share/gc/shared/ageTableTracer.cpp +++ b/src/hotspot/share/gc/shared/ageTableTracer.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,7 +25,7 @@ #include "precompiled.hpp" #include "gc/shared/ageTableTracer.hpp" #include "gc/shared/gcId.hpp" -#include "trace/tracing.hpp" +#include "jfr/jfrEvents.hpp" void AgeTableTracer::send_tenuring_distribution_event(uint age, size_t size) { EventTenuringDistribution e; diff --git a/src/hotspot/share/gc/shared/allocTracer.cpp b/src/hotspot/share/gc/shared/allocTracer.cpp index a1efeed104e..db89e4cf08a 100644 --- a/src/hotspot/share/gc/shared/allocTracer.cpp +++ b/src/hotspot/share/gc/shared/allocTracer.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,13 +24,16 @@ #include "precompiled.hpp" #include "gc/shared/allocTracer.hpp" +#include "jfr/jfrEvents.hpp" #include "runtime/handles.hpp" -#include "trace/tracing.hpp" -#include "trace/traceMacros.hpp" #include "utilities/globalDefinitions.hpp" +#include "utilities/macros.hpp" +#if INCLUDE_JFR +#include "jfr/support/jfrAllocationTracer.hpp" +#endif void AllocTracer::send_allocation_outside_tlab(Klass* klass, HeapWord* obj, size_t alloc_size, Thread* thread) { - TRACE_ALLOCATION(obj, alloc_size, thread); + JFR_ONLY(JfrAllocationTracer tracer(obj, alloc_size, thread);) EventObjectAllocationOutsideTLAB event; if (event.should_commit()) { event.set_objectClass(klass); @@ -40,7 +43,7 @@ void AllocTracer::send_allocation_outside_tlab(Klass* klass, HeapWord* obj, size } void AllocTracer::send_allocation_in_new_tlab(Klass* klass, HeapWord* obj, size_t tlab_size, size_t alloc_size, Thread* thread) { - TRACE_ALLOCATION(obj, tlab_size, thread); + JFR_ONLY(JfrAllocationTracer tracer(obj, alloc_size, thread);) EventObjectAllocationInNewTLAB event; if (event.should_commit()) { event.set_objectClass(klass); diff --git a/src/hotspot/share/gc/shared/copyFailedInfo.hpp b/src/hotspot/share/gc/shared/copyFailedInfo.hpp index b7e0bbe98b0..2b0854789b6 100644 --- a/src/hotspot/share/gc/shared/copyFailedInfo.hpp +++ b/src/hotspot/share/gc/shared/copyFailedInfo.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,8 +25,8 @@ #ifndef SHARE_VM_GC_SHARED_COPYFAILEDINFO_HPP #define SHARE_VM_GC_SHARED_COPYFAILEDINFO_HPP +#include "jfr/support/jfrThreadId.hpp" #include "runtime/thread.hpp" -#include "trace/traceMacros.hpp" #include "utilities/globalDefinitions.hpp" class CopyFailedInfo : public CHeapObj { @@ -72,9 +72,9 @@ class PromotionFailedInfo : public CopyFailedInfo { void register_copy_failure(size_t size) { CopyFailedInfo::register_copy_failure(size); if (_thread_trace_id == 0) { - _thread_trace_id = THREAD_TRACE_ID(Thread::current()); + _thread_trace_id = JFR_THREAD_ID(Thread::current()); } else { - assert(THREAD_TRACE_ID(Thread::current()) == _thread_trace_id, + assert(JFR_THREAD_ID(Thread::current()) == _thread_trace_id, "The PromotionFailedInfo should be thread local."); } } diff --git a/src/hotspot/share/gc/shared/gcConfiguration.cpp b/src/hotspot/share/gc/shared/gcConfiguration.cpp new file mode 100644 index 00000000000..453d8c633c2 --- /dev/null +++ b/src/hotspot/share/gc/shared/gcConfiguration.cpp @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +#include "precompiled.hpp" + +#include "gc/shared/collectedHeap.hpp" +#include "gc/shared/gcConfiguration.hpp" +#include "memory/universe.hpp" +#include "runtime/arguments.hpp" +#include "runtime/globals.hpp" +#include "utilities/debug.hpp" + +GCName GCConfiguration::young_collector() const { + if (UseG1GC) { + return G1New; + } + + if (UseParallelGC) { + return ParallelScavenge; + } + + if (UseConcMarkSweepGC) { + return ParNew; + } + + return DefNew; +} + +GCName GCConfiguration::old_collector() const { + if (UseG1GC) { + return G1Old; + } + + if (UseConcMarkSweepGC) { + return ConcurrentMarkSweep; + } + + if (UseParallelOldGC) { + return ParallelOld; + } + + return SerialOld; +} + +uint GCConfiguration::num_parallel_gc_threads() const { + return ParallelGCThreads; +} + +uint GCConfiguration::num_concurrent_gc_threads() const { + return ConcGCThreads; +} + +bool GCConfiguration::uses_dynamic_gc_threads() const { + return UseDynamicNumberOfGCThreads; +} + +bool GCConfiguration::is_explicit_gc_concurrent() const { + return ExplicitGCInvokesConcurrent; +} + +bool GCConfiguration::is_explicit_gc_disabled() const { + return DisableExplicitGC; +} + +bool GCConfiguration::has_pause_target_default_value() const { + return FLAG_IS_DEFAULT(MaxGCPauseMillis); +} + +uintx GCConfiguration::pause_target() const { + return MaxGCPauseMillis; +} + +uintx GCConfiguration::gc_time_ratio() const { + return GCTimeRatio; +} + +bool GCTLABConfiguration::uses_tlabs() const { + return UseTLAB; +} + +size_t GCTLABConfiguration::min_tlab_size() const { + return MinTLABSize; +} + +uint GCTLABConfiguration::tlab_refill_waste_limit() const { + return TLABRefillWasteFraction; +} + +intx GCSurvivorConfiguration::max_tenuring_threshold() const { + return MaxTenuringThreshold; +} + +intx GCSurvivorConfiguration::initial_tenuring_threshold() const { + return InitialTenuringThreshold; +} + +size_t GCHeapConfiguration::max_size() const { + return MaxHeapSize; +} + +size_t GCHeapConfiguration::min_size() const { + return Arguments::min_heap_size(); +} + +size_t GCHeapConfiguration::initial_size() const { + return InitialHeapSize; +} + +bool GCHeapConfiguration::uses_compressed_oops() const { + return UseCompressedOops; +} + +Universe::NARROW_OOP_MODE GCHeapConfiguration::narrow_oop_mode() const { + return Universe::narrow_oop_mode(); +} + +uint GCHeapConfiguration::object_alignment_in_bytes() const { + return ObjectAlignmentInBytes; +} + +int GCHeapConfiguration::heap_address_size_in_bits() const { + return BitsPerHeapOop; +} + +bool GCYoungGenerationConfiguration::has_max_size_default_value() const { + return FLAG_IS_DEFAULT(MaxNewSize); +} + +uintx GCYoungGenerationConfiguration::max_size() const { + return MaxNewSize; +} + +uintx GCYoungGenerationConfiguration::min_size() const { + return NewSize; +} + +intx GCYoungGenerationConfiguration::new_ratio() const { + return NewRatio; +} diff --git a/src/hotspot/share/gc/shared/gcConfiguration.hpp b/src/hotspot/share/gc/shared/gcConfiguration.hpp new file mode 100644 index 00000000000..3360eeac2ce --- /dev/null +++ b/src/hotspot/share/gc/shared/gcConfiguration.hpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_GC_SHARED_GCCONFIGURATION_HPP +#define SHARE_VM_GC_SHARED_GCCONFIGURATION_HPP + +#include "gc/shared/gcName.hpp" +#include "memory/universe.hpp" +#include "utilities/globalDefinitions.hpp" + +class GCConfiguration { + public: + GCName young_collector() const; + GCName old_collector() const; + uint num_parallel_gc_threads() const; + uint num_concurrent_gc_threads() const; + bool uses_dynamic_gc_threads() const; + bool is_explicit_gc_concurrent() const; + bool is_explicit_gc_disabled() const; + uintx gc_time_ratio() const; + + bool has_pause_target_default_value() const; + uintx pause_target() const; +}; + +class GCTLABConfiguration { + public: + bool uses_tlabs() const; + size_t min_tlab_size() const; + uint tlab_refill_waste_limit() const; +}; + +class GCSurvivorConfiguration { + public: + intx initial_tenuring_threshold() const; + intx max_tenuring_threshold() const; +}; + +class GCHeapConfiguration { + public: + size_t max_size() const; + size_t min_size() const; + size_t initial_size() const; + bool uses_compressed_oops() const; + Universe::NARROW_OOP_MODE narrow_oop_mode() const; + uint object_alignment_in_bytes() const; + int heap_address_size_in_bits() const; +}; + +class GCYoungGenerationConfiguration { + public: + bool has_max_size_default_value() const; + uintx max_size() const; + + uintx min_size() const; + intx new_ratio() const; +}; + +#endif // SHARE_VM_GC_SHARED_GCCONFIGURATION_HPP diff --git a/src/hotspot/share/gc/shared/gcTimer.cpp b/src/hotspot/share/gc/shared/gcTimer.cpp index c17cd184586..229812a0a63 100644 --- a/src/hotspot/share/gc/shared/gcTimer.cpp +++ b/src/hotspot/share/gc/shared/gcTimer.cpp @@ -25,7 +25,6 @@ #include "precompiled.hpp" #include "gc/shared/gcTimer.hpp" #include "utilities/growableArray.hpp" -#include "utilities/ticks.inline.hpp" // the "time" parameter for most functions // has a default value set by Ticks::now() @@ -376,7 +375,7 @@ public: GCTimer gc_timer; gc_timer.register_gc_start(1); - assert(gc_timer.gc_start() == 1, "Incorrect"); + assert(gc_timer.gc_start() == Ticks(1), "Incorrect"); } static void gc_end() { @@ -384,7 +383,7 @@ public: gc_timer.register_gc_start(1); gc_timer.register_gc_end(2); - assert(gc_timer.gc_end() == 2, "Incorrect"); + assert(gc_timer.gc_end() == Ticks(2), "Incorrect"); } }; diff --git a/src/hotspot/share/gc/shared/gcTrace.cpp b/src/hotspot/share/gc/shared/gcTrace.cpp index 6b5f4eca33e..eea530760b7 100644 --- a/src/hotspot/share/gc/shared/gcTrace.cpp +++ b/src/hotspot/share/gc/shared/gcTrace.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -35,7 +35,7 @@ #include "runtime/os.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" -#include "utilities/ticks.inline.hpp" +#include "utilities/ticks.hpp" #if INCLUDE_G1GC #include "gc/g1/evacuationInfo.hpp" #endif diff --git a/src/hotspot/share/gc/shared/gcTraceSend.cpp b/src/hotspot/share/gc/shared/gcTraceSend.cpp index 869987bd7d0..191d6bf89fe 100644 --- a/src/hotspot/share/gc/shared/gcTraceSend.cpp +++ b/src/hotspot/share/gc/shared/gcTraceSend.cpp @@ -28,10 +28,8 @@ #include "gc/shared/gcTimer.hpp" #include "gc/shared/gcTrace.hpp" #include "gc/shared/gcWhen.hpp" +#include "jfr/jfrEvents.hpp" #include "runtime/os.hpp" -#include "trace/traceBackend.hpp" -#include "trace/tracing.hpp" -#include "tracefiles/traceEventClasses.hpp" #include "utilities/macros.hpp" #if INCLUDE_G1GC #include "gc/g1/evacuationInfo.hpp" @@ -160,8 +158,8 @@ void OldGCTracer::send_old_gc_event() const { } } -static TraceStructCopyFailed to_trace_struct(const CopyFailedInfo& cf_info) { - TraceStructCopyFailed failed_info; +static JfrStructCopyFailed to_struct(const CopyFailedInfo& cf_info) { + JfrStructCopyFailed failed_info; failed_info.set_objectCount(cf_info.failed_count()); failed_info.set_firstSize(cf_info.first_size()); failed_info.set_smallestSize(cf_info.smallest_size()); @@ -173,7 +171,7 @@ void YoungGCTracer::send_promotion_failed_event(const PromotionFailedInfo& pf_in EventPromotionFailed e; if (e.should_commit()) { e.set_gcId(GCId::current()); - e.set_promotionFailed(to_trace_struct(pf_info)); + e.set_promotionFailed(to_struct(pf_info)); e.set_thread(pf_info.thread_trace_id()); e.commit(); } @@ -231,14 +229,14 @@ void G1NewTracer::send_evacuation_failed_event(const EvacuationFailedInfo& ef_in EventEvacuationFailed e; if (e.should_commit()) { e.set_gcId(GCId::current()); - e.set_evacuationFailed(to_trace_struct(ef_info)); + e.set_evacuationFailed(to_struct(ef_info)); e.commit(); } } -static TraceStructG1EvacuationStatistics +static JfrStructG1EvacuationStatistics create_g1_evacstats(unsigned gcid, const G1EvacSummary& summary) { - TraceStructG1EvacuationStatistics s; + JfrStructG1EvacuationStatistics s; s.set_gcId(gcid); s.set_allocated(summary.allocated() * HeapWordSize); s.set_wasted(summary.wasted() * HeapWordSize); @@ -313,8 +311,8 @@ void G1NewTracer::send_adaptive_ihop_statistics(size_t threshold, #endif // INCLUDE_G1GC -static TraceStructVirtualSpace to_trace_struct(const VirtualSpaceSummary& summary) { - TraceStructVirtualSpace space; +static JfrStructVirtualSpace to_struct(const VirtualSpaceSummary& summary) { + JfrStructVirtualSpace space; space.set_start((TraceAddress)summary.start()); space.set_committedEnd((TraceAddress)summary.committed_end()); space.set_committedSize(summary.committed_size()); @@ -323,8 +321,8 @@ static TraceStructVirtualSpace to_trace_struct(const VirtualSpaceSummary& summar return space; } -static TraceStructObjectSpace to_trace_struct(const SpaceSummary& summary) { - TraceStructObjectSpace space; +static JfrStructObjectSpace to_struct(const SpaceSummary& summary) { + JfrStructObjectSpace space; space.set_start((TraceAddress)summary.start()); space.set_end((TraceAddress)summary.end()); space.set_used(summary.used()); @@ -344,7 +342,7 @@ class GCHeapSummaryEventSender : public GCHeapSummaryVisitor { if (e.should_commit()) { e.set_gcId(GCId::current()); e.set_when((u1)_when); - e.set_heapSpace(to_trace_struct(heap_space)); + e.set_heapSpace(to_struct(heap_space)); e.set_heapUsed(heap_summary->used()); e.commit(); } @@ -380,12 +378,12 @@ class GCHeapSummaryEventSender : public GCHeapSummaryVisitor { e.set_gcId(GCId::current()); e.set_when((u1)_when); - e.set_oldSpace(to_trace_struct(ps_heap_summary->old())); - e.set_oldObjectSpace(to_trace_struct(ps_heap_summary->old_space())); - e.set_youngSpace(to_trace_struct(ps_heap_summary->young())); - e.set_edenSpace(to_trace_struct(ps_heap_summary->eden())); - e.set_fromSpace(to_trace_struct(ps_heap_summary->from())); - e.set_toSpace(to_trace_struct(ps_heap_summary->to())); + e.set_oldSpace(to_struct(ps_heap_summary->old())); + e.set_oldObjectSpace(to_struct(ps_heap_summary->old_space())); + e.set_youngSpace(to_struct(ps_heap_summary->young())); + e.set_edenSpace(to_struct(ps_heap_summary->eden())); + e.set_fromSpace(to_struct(ps_heap_summary->from())); + e.set_toSpace(to_struct(ps_heap_summary->to())); e.commit(); } } @@ -396,8 +394,8 @@ void GCTracer::send_gc_heap_summary_event(GCWhen::Type when, const GCHeapSummary heap_summary.accept(&visitor); } -static TraceStructMetaspaceSizes to_trace_struct(const MetaspaceSizes& sizes) { - TraceStructMetaspaceSizes meta_sizes; +static JfrStructMetaspaceSizes to_struct(const MetaspaceSizes& sizes) { + JfrStructMetaspaceSizes meta_sizes; meta_sizes.set_committed(sizes.committed()); meta_sizes.set_used(sizes.used()); @@ -412,9 +410,9 @@ void GCTracer::send_meta_space_summary_event(GCWhen::Type when, const MetaspaceS e.set_gcId(GCId::current()); e.set_when((u1) when); e.set_gcThreshold(meta_space_summary.capacity_until_GC()); - e.set_metaspace(to_trace_struct(meta_space_summary.meta_space())); - e.set_dataSpace(to_trace_struct(meta_space_summary.data_space())); - e.set_classSpace(to_trace_struct(meta_space_summary.class_space())); + e.set_metaspace(to_struct(meta_space_summary.meta_space())); + e.set_dataSpace(to_struct(meta_space_summary.data_space())); + e.set_classSpace(to_struct(meta_space_summary.class_space())); e.commit(); } } diff --git a/src/hotspot/share/gc/shared/objectCountEventSender.cpp b/src/hotspot/share/gc/shared/objectCountEventSender.cpp index d9ca9294da6..bf24a98cf67 100644 --- a/src/hotspot/share/gc/shared/objectCountEventSender.cpp +++ b/src/hotspot/share/gc/shared/objectCountEventSender.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,34 +26,50 @@ #include "precompiled.hpp" #include "gc/shared/gcId.hpp" #include "gc/shared/objectCountEventSender.hpp" +#include "jfr/jfrEvents.hpp" #include "memory/heapInspection.hpp" -#include "trace/tracing.hpp" -#include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" #include "utilities/ticks.hpp" #if INCLUDE_SERVICES -void ObjectCountEventSender::send(const KlassInfoEntry* entry, const Ticks& timestamp) { -#if INCLUDE_TRACE - assert(Tracing::is_event_enabled(EventObjectCountAfterGC::eventId), - "Only call this method if the event is enabled"); - - EventObjectCountAfterGC event(UNTIMED); - event.set_gcId(GCId::current()); - event.set_objectClass(entry->klass()); - event.set_count(entry->count()); - event.set_totalSize(entry->words() * BytesPerWord); - event.set_endtime(timestamp); - event.commit(); -#endif // INCLUDE_TRACE -} - bool ObjectCountEventSender::should_send_event() { -#if INCLUDE_TRACE - return Tracing::is_event_enabled(EventObjectCountAfterGC::eventId); +#if INCLUDE_JFR + return _should_send_requestable_event || EventObjectCountAfterGC::is_enabled(); #else return false; -#endif // INCLUDE_TRACE +#endif // INCLUDE_JFR +} + +bool ObjectCountEventSender::_should_send_requestable_event = false; + +void ObjectCountEventSender::enable_requestable_event() { + _should_send_requestable_event = true; +} + +void ObjectCountEventSender::disable_requestable_event() { + _should_send_requestable_event = false; +} + +template +void ObjectCountEventSender::send_event_if_enabled(Klass* klass, jlong count, julong size, const Ticks& timestamp) { + T event(UNTIMED); + if (event.should_commit()) { + event.set_gcId(GCId::current()); + event.set_objectClass(klass); + event.set_count(count); + event.set_totalSize(size); + event.set_endtime(timestamp); + event.commit(); + } +} + +void ObjectCountEventSender::send(const KlassInfoEntry* entry, const Ticks& timestamp) { + Klass* klass = entry->klass(); + jlong count = entry->count(); + julong total_size = entry->words() * BytesPerWord; + + send_event_if_enabled(klass, count, total_size, timestamp); + send_event_if_enabled(klass, count, total_size, timestamp); } #endif // INCLUDE_SERVICES diff --git a/src/hotspot/share/gc/shared/objectCountEventSender.hpp b/src/hotspot/share/gc/shared/objectCountEventSender.hpp index c96e7790d2d..29c8d975a15 100644 --- a/src/hotspot/share/gc/shared/objectCountEventSender.hpp +++ b/src/hotspot/share/gc/shared/objectCountEventSender.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,15 +27,25 @@ #include "gc/shared/gcTrace.hpp" #include "memory/allocation.hpp" +#include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" +#include "utilities/ticks.hpp" #if INCLUDE_SERVICES class KlassInfoEntry; -class Ticks; +class Klass; class ObjectCountEventSender : public AllStatic { + static bool _should_send_requestable_event; + + template + static void send_event_if_enabled(Klass* klass, jlong count, julong size, const Ticks& timestamp); + public: + static void enable_requestable_event(); + static void disable_requestable_event(); + static void send(const KlassInfoEntry* entry, const Ticks& timestamp); static bool should_send_event(); }; diff --git a/src/hotspot/share/gc/shared/weakProcessor.cpp b/src/hotspot/share/gc/shared/weakProcessor.cpp index c230b14d549..6be1bd2d96d 100644 --- a/src/hotspot/share/gc/shared/weakProcessor.cpp +++ b/src/hotspot/share/gc/shared/weakProcessor.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,13 +26,15 @@ #include "gc/shared/weakProcessor.hpp" #include "prims/jvmtiExport.hpp" #include "runtime/jniHandles.hpp" -#include "trace/tracing.hpp" -#include "trace/traceMacros.hpp" +#include "utilities/macros.hpp" +#if INCLUDE_JFR +#include "jfr/jfr.hpp" +#endif void WeakProcessor::weak_oops_do(BoolObjectClosure* is_alive, OopClosure* keep_alive) { JNIHandles::weak_oops_do(is_alive, keep_alive); JvmtiExport::weak_oops_do(is_alive, keep_alive); - TRACE_WEAK_OOPS_DO(is_alive, keep_alive); + JFR_ONLY(Jfr::weak_oops_do(is_alive, keep_alive);) } void WeakProcessor::oops_do(OopClosure* closure) { diff --git a/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp b/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp new file mode 100644 index 00000000000..a4df453d133 --- /dev/null +++ b/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp @@ -0,0 +1,633 @@ +/* + * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "classfile/javaClasses.hpp" +#include "classfile/vmSymbols.hpp" +#include "jfr/jfr.hpp" +#include "jfr/dcmd/jfrDcmds.hpp" +#include "jfr/jni/jfrJavaSupport.hpp" +#include "jfr/recorder/jfrRecorder.hpp" +#include "jfr/recorder/service/jfrOptionSet.hpp" +#include "memory/resourceArea.hpp" +#include "oops/oop.inline.hpp" +#include "oops/symbol.hpp" +#include "runtime/handles.inline.hpp" +#include "services/diagnosticArgument.hpp" +#include "services/diagnosticFramework.hpp" +#include "utilities/globalDefinitions.hpp" + +#ifdef _WINDOWS +#define JFR_FILENAME_EXAMPLE "C:\\Users\\user\\My Recording.jfr" +#endif + +#ifdef __APPLE__ +#define JFR_FILENAME_EXAMPLE "/Users/user/My Recording.jfr" +#endif + +#ifndef JFR_FILENAME_EXAMPLE +#define JFR_FILENAME_EXAMPLE "/home/user/My Recording.jfr" +#endif + +// JNIHandle management + +// ------------------------------------------------------------------ +// push_jni_handle_block +// +// Push on a new block of JNI handles. +static void push_jni_handle_block(Thread* const thread) { + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(thread)); + + // Allocate a new block for JNI handles. + // Inlined code from jni_PushLocalFrame() + JNIHandleBlock* prev_handles = thread->active_handles(); + JNIHandleBlock* entry_handles = JNIHandleBlock::allocate_block(thread); + assert(entry_handles != NULL && prev_handles != NULL, "should not be NULL"); + entry_handles->set_pop_frame_link(prev_handles); // make sure prev handles get gc'd. + thread->set_active_handles(entry_handles); +} + +// ------------------------------------------------------------------ +// pop_jni_handle_block +// +// Pop off the current block of JNI handles. +static void pop_jni_handle_block(Thread* const thread) { + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(thread)); + + // Release our JNI handle block + JNIHandleBlock* entry_handles = thread->active_handles(); + JNIHandleBlock* prev_handles = entry_handles->pop_frame_link(); + // restore + thread->set_active_handles(prev_handles); + entry_handles->set_pop_frame_link(NULL); + JNIHandleBlock::release_block(entry_handles, thread); // may block +} + +class JNIHandleBlockManager : public StackObj { + private: + Thread* const _thread; + public: + JNIHandleBlockManager(Thread* thread) : _thread(thread) { + push_jni_handle_block(_thread); + } + + ~JNIHandleBlockManager() { + pop_jni_handle_block(_thread); + } +}; + +static bool is_module_available(outputStream* output, TRAPS) { + return JfrJavaSupport::is_jdk_jfr_module_available(output, THREAD); +} + +static bool is_disabled(outputStream* output) { + if (Jfr::is_disabled()) { + if (output != NULL) { + output->print_cr("Flight Recorder is disabled.\n"); + } + return true; + } + return false; +} + +static bool is_recorder_instance_created(outputStream* output) { + if (!JfrRecorder::is_created()) { + if (output != NULL) { + output->print_cr("No available recordings.\n"); + output->print_cr("Use JFR.start to start a recording.\n"); + } + return false; + } + return true; +} + +static bool invalid_state(outputStream* out, TRAPS) { + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD)); + return is_disabled(out) || !is_module_available(out, THREAD); +} + +static void print_pending_exception(outputStream* output, oop throwable) { + assert(throwable != NULL, "invariant"); + + oop msg = java_lang_Throwable::message(throwable); + + if (msg != NULL) { + char* text = java_lang_String::as_utf8_string(msg); + output->print_raw_cr(text); + } +} + +static void print_message(outputStream* output, const char* message) { + if (message != NULL) { + output->print_raw(message); + } +} + +static void handle_dcmd_result(outputStream* output, + const oop result, + const DCmdSource source, + TRAPS) { + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD)); + assert(output != NULL, "invariant"); + if (HAS_PENDING_EXCEPTION) { + print_pending_exception(output, PENDING_EXCEPTION); + // Don't clear excption on startup, JVM should fail initialization. + if (DCmd_Source_Internal != source) { + CLEAR_PENDING_EXCEPTION; + } + return; + } + + assert(!HAS_PENDING_EXCEPTION, "invariant"); + + if (result != NULL) { + const char* result_chars = java_lang_String::as_utf8_string(result); + print_message(output, result_chars); + } +} + +static oop construct_dcmd_instance(JfrJavaArguments* args, TRAPS) { + assert(args != NULL, "invariant"); + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD)); + assert(args->klass() != NULL, "invariant"); + args->set_name("", CHECK_NULL); + args->set_signature("()V", CHECK_NULL); + JfrJavaSupport::new_object(args, CHECK_NULL); + return (oop)args->result()->get_jobject(); +} + +JfrDumpFlightRecordingDCmd::JfrDumpFlightRecordingDCmd(outputStream* output, + bool heap) : DCmdWithParser(output, heap), + _name("name", "Recording name, e.g. \\\"My Recording\\\"", "STRING", true, NULL), + _filename("filename", "Copy recording data to file, i.e \\\"" JFR_FILENAME_EXAMPLE "\\\"", "STRING", true), + _path_to_gc_roots("path-to-gc-roots", "Collect path to GC roots", "BOOLEAN", false, "false") { + _dcmdparser.add_dcmd_option(&_name); + _dcmdparser.add_dcmd_option(&_filename); + _dcmdparser.add_dcmd_option(&_path_to_gc_roots); +}; + +int JfrDumpFlightRecordingDCmd::num_arguments() { + ResourceMark rm; + JfrDumpFlightRecordingDCmd* dcmd = new JfrDumpFlightRecordingDCmd(NULL, false); + if (dcmd != NULL) { + DCmdMark mark(dcmd); + return dcmd->_dcmdparser.num_arguments(); + } + return 0; +} + +void JfrDumpFlightRecordingDCmd::execute(DCmdSource source, TRAPS) { + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD)); + + if (invalid_state(output(), THREAD) || !is_recorder_instance_created(output())) { + return; + } + + ResourceMark rm(THREAD); + HandleMark hm(THREAD); + JNIHandleBlockManager jni_handle_management(THREAD); + + JavaValue result(T_OBJECT); + JfrJavaArguments constructor_args(&result); + constructor_args.set_klass("jdk/jfr/internal/dcmd/DCmdDump", CHECK); + const oop dcmd = construct_dcmd_instance(&constructor_args, CHECK); + Handle h_dcmd_instance(THREAD, dcmd); + assert(h_dcmd_instance.not_null(), "invariant"); + + jstring name = NULL; + if (_name.is_set() && _name.value() != NULL) { + name = JfrJavaSupport::new_string(_name.value(), CHECK); + } + + jstring filepath = NULL; + if (_filename.is_set() && _filename.value() != NULL) { + filepath = JfrJavaSupport::new_string(_filename.value(), CHECK); + } + + jobject path_to_gc_roots = NULL; + if (_path_to_gc_roots.is_set()) { + path_to_gc_roots = JfrJavaSupport::new_java_lang_Boolean(_path_to_gc_roots.value(), CHECK); + } + + static const char klass[] = "jdk/jfr/internal/dcmd/DCmdDump"; + static const char method[] = "execute"; + static const char signature[] = "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)Ljava/lang/String;"; + + JfrJavaArguments execute_args(&result, klass, method, signature, CHECK); + execute_args.set_receiver(h_dcmd_instance); + + // arguments + execute_args.push_jobject(name); + execute_args.push_jobject(filepath); + execute_args.push_jobject(path_to_gc_roots); + + JfrJavaSupport::call_virtual(&execute_args, THREAD); + handle_dcmd_result(output(), (oop)result.get_jobject(), source, THREAD); +} + +JfrCheckFlightRecordingDCmd::JfrCheckFlightRecordingDCmd(outputStream* output, bool heap) : DCmdWithParser(output, heap), + _name("name","Recording text, e.g. \\\"My Recording\\\" or omit to see all recordings","STRING",false, NULL), + _verbose("verbose","Print event settings for the recording(s)","BOOLEAN", + false, "false") { + _dcmdparser.add_dcmd_option(&_name); + _dcmdparser.add_dcmd_option(&_verbose); +}; + +int JfrCheckFlightRecordingDCmd::num_arguments() { + ResourceMark rm; + JfrCheckFlightRecordingDCmd* dcmd = new JfrCheckFlightRecordingDCmd(NULL, false); + if (dcmd != NULL) { + DCmdMark mark(dcmd); + return dcmd->_dcmdparser.num_arguments(); + } + return 0; +} + +void JfrCheckFlightRecordingDCmd::execute(DCmdSource source, TRAPS) { + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD)); + + if (invalid_state(output(), THREAD) || !is_recorder_instance_created(output())) { + return; + } + + ResourceMark rm(THREAD); + HandleMark hm(THREAD); + JNIHandleBlockManager jni_handle_management(THREAD); + + JavaValue result(T_OBJECT); + JfrJavaArguments constructor_args(&result); + constructor_args.set_klass("jdk/jfr/internal/dcmd/DCmdCheck", CHECK); + const oop dcmd = construct_dcmd_instance(&constructor_args, CHECK); + Handle h_dcmd_instance(THREAD, dcmd); + assert(h_dcmd_instance.not_null(), "invariant"); + + jstring name = NULL; + if (_name.is_set() && _name.value() != NULL) { + name = JfrJavaSupport::new_string(_name.value(), CHECK); + } + + jobject verbose = NULL; + if (_verbose.is_set()) { + verbose = JfrJavaSupport::new_java_lang_Boolean(_verbose.value(), CHECK); + } + + static const char klass[] = "jdk/jfr/internal/dcmd/DCmdCheck"; + static const char method[] = "execute"; + static const char signature[] = "(Ljava/lang/String;Ljava/lang/Boolean;)Ljava/lang/String;"; + + JfrJavaArguments execute_args(&result, klass, method, signature, CHECK); + execute_args.set_receiver(h_dcmd_instance); + + // arguments + execute_args.push_jobject(name); + execute_args.push_jobject(verbose); + + JfrJavaSupport::call_virtual(&execute_args, THREAD); + handle_dcmd_result(output(), (oop)result.get_jobject(), source, THREAD); +} + +JfrStartFlightRecordingDCmd::JfrStartFlightRecordingDCmd(outputStream* output, + bool heap) : DCmdWithParser(output, heap), + _name("name", "Name that can be used to identify recording, e.g. \\\"My Recording\\\"", "STRING", false, NULL), + _settings("settings", "Settings file(s), e.g. profile or default. See JRE_HOME/lib/jfr", "STRING SET", false), + _delay("delay", "Delay recording start with (s)econds, (m)inutes), (h)ours), or (d)ays, e.g. 5h.", "NANOTIME", false, "0"), + _duration("duration", "Duration of recording in (s)econds, (m)inutes, (h)ours, or (d)ays, e.g. 300s.", "NANOTIME", false, "0"), + _filename("filename", "Resulting recording filename, e.g. \\\"" JFR_FILENAME_EXAMPLE "\\\"", "STRING", false), + _disk("disk", "Recording should be persisted to disk", "BOOLEAN", false), + _maxage("maxage", "Maximum time to keep recorded data (on disk) in (s)econds, (m)inutes, (h)ours, or (d)ays, e.g. 60m, or 0 for no limit", "NANOTIME", false, "0"), + _maxsize("maxsize", "Maximum amount of bytes to keep (on disk) in (k)B, (M)B or (G)B, e.g. 500M, or 0 for no limit", "MEMORY SIZE", false, "0"), + _dump_on_exit("dumponexit", "Dump running recording when JVM shuts down", "BOOLEAN", false), + _path_to_gc_roots("path-to-gc-roots", "Collect path to GC roots", "BOOLEAN", false, "false") { + _dcmdparser.add_dcmd_option(&_name); + _dcmdparser.add_dcmd_option(&_settings); + _dcmdparser.add_dcmd_option(&_delay); + _dcmdparser.add_dcmd_option(&_duration); + _dcmdparser.add_dcmd_option(&_disk); + _dcmdparser.add_dcmd_option(&_filename); + _dcmdparser.add_dcmd_option(&_maxage); + _dcmdparser.add_dcmd_option(&_maxsize); + _dcmdparser.add_dcmd_option(&_dump_on_exit); + _dcmdparser.add_dcmd_option(&_path_to_gc_roots); +}; + +int JfrStartFlightRecordingDCmd::num_arguments() { + ResourceMark rm; + JfrStartFlightRecordingDCmd* dcmd = new JfrStartFlightRecordingDCmd(NULL, false); + if (dcmd != NULL) { + DCmdMark mark(dcmd); + return dcmd->_dcmdparser.num_arguments(); + } + return 0; +} + +void JfrStartFlightRecordingDCmd::execute(DCmdSource source, TRAPS) { + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD)); + + if (invalid_state(output(), THREAD)) { + return; + } + + ResourceMark rm(THREAD); + HandleMark hm(THREAD); + JNIHandleBlockManager jni_handle_management(THREAD); + + JavaValue result(T_OBJECT); + JfrJavaArguments constructor_args(&result); + constructor_args.set_klass("jdk/jfr/internal/dcmd/DCmdStart", THREAD); + const oop dcmd = construct_dcmd_instance(&constructor_args, CHECK); + Handle h_dcmd_instance(THREAD, dcmd); + assert(h_dcmd_instance.not_null(), "invariant"); + + jstring name = NULL; + if (_name.is_set() && _name.value() != NULL) { + name = JfrJavaSupport::new_string(_name.value(), CHECK); + } + + jstring filename = NULL; + if (_filename.is_set() && _filename.value() != NULL) { + filename = JfrJavaSupport::new_string(_filename.value(), CHECK); + } + + jobject maxage = NULL; + if (_maxage.is_set()) { + maxage = JfrJavaSupport::new_java_lang_Long(_maxage.value()._nanotime, CHECK); + } + + jobject maxsize = NULL; + if (_maxsize.is_set()) { + maxsize = JfrJavaSupport::new_java_lang_Long(_maxsize.value()._size, CHECK); + } + + jobject duration = NULL; + if (_duration.is_set()) { + duration = JfrJavaSupport::new_java_lang_Long(_duration.value()._nanotime, CHECK); + } + + jobject delay = NULL; + if (_delay.is_set()) { + delay = JfrJavaSupport::new_java_lang_Long(_delay.value()._nanotime, CHECK); + } + + jobject disk = NULL; + if (_disk.is_set()) { + disk = JfrJavaSupport::new_java_lang_Boolean(_disk.value(), CHECK); + } + + jobject dump_on_exit = NULL; + if (_dump_on_exit.is_set()) { + dump_on_exit = JfrJavaSupport::new_java_lang_Boolean(_dump_on_exit.value(), CHECK); + } + + jobject path_to_gc_roots = NULL; + if (_path_to_gc_roots.is_set()) { + path_to_gc_roots = JfrJavaSupport::new_java_lang_Boolean(_path_to_gc_roots.value(), CHECK); + } + + jobjectArray settings = NULL; + if (_settings.is_set()) { + const int length = _settings.value()->array()->length(); + settings = JfrJavaSupport::new_string_array(length, CHECK); + assert(settings != NULL, "invariant"); + for (int i = 0; i < length; ++i) { + jobject element = JfrJavaSupport::new_string(_settings.value()->array()->at(i), CHECK); + assert(element != NULL, "invariant"); + JfrJavaSupport::set_array_element(settings, element, i, CHECK); + } + } + + static const char klass[] = "jdk/jfr/internal/dcmd/DCmdStart"; + static const char method[] = "execute"; + static const char signature[] = "(Ljava/lang/String;[Ljava/lang/String;Ljava/lang/Long;" + "Ljava/lang/Long;Ljava/lang/Boolean;Ljava/lang/String;" + "Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Boolean;Ljava/lang/Boolean;)Ljava/lang/String;"; + + JfrJavaArguments execute_args(&result, klass, method, signature, CHECK); + execute_args.set_receiver(h_dcmd_instance); + + // arguments + execute_args.push_jobject(name); + execute_args.push_jobject(settings); + execute_args.push_jobject(delay); + execute_args.push_jobject(duration); + execute_args.push_jobject(disk); + execute_args.push_jobject(filename); + execute_args.push_jobject(maxage); + execute_args.push_jobject(maxsize); + execute_args.push_jobject(dump_on_exit); + execute_args.push_jobject(path_to_gc_roots); + + JfrJavaSupport::call_virtual(&execute_args, THREAD); + handle_dcmd_result(output(), (oop)result.get_jobject(), source, THREAD); +} + +JfrStopFlightRecordingDCmd::JfrStopFlightRecordingDCmd(outputStream* output, + bool heap) : DCmdWithParser(output, heap), + _name("name", "Recording text,.e.g \\\"My Recording\\\"", "STRING", false, NULL), + _filename("filename", "Copy recording data to file, e.g. \\\"" JFR_FILENAME_EXAMPLE "\\\"", "STRING", false, NULL) { + _dcmdparser.add_dcmd_option(&_name); + _dcmdparser.add_dcmd_option(&_filename); +}; + +int JfrStopFlightRecordingDCmd::num_arguments() { + ResourceMark rm; + JfrStopFlightRecordingDCmd* dcmd = new JfrStopFlightRecordingDCmd(NULL, false); + if (dcmd != NULL) { + DCmdMark mark(dcmd); + return dcmd->_dcmdparser.num_arguments(); + } + return 0; +} + +void JfrStopFlightRecordingDCmd::execute(DCmdSource source, TRAPS) { + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD)); + + if (invalid_state(output(), THREAD) || !is_recorder_instance_created(output())) { + return; + } + + ResourceMark rm(THREAD); + HandleMark hm(THREAD); + JNIHandleBlockManager jni_handle_management(THREAD); + + JavaValue result(T_OBJECT); + JfrJavaArguments constructor_args(&result); + constructor_args.set_klass("jdk/jfr/internal/dcmd/DCmdStop", CHECK); + const oop dcmd = construct_dcmd_instance(&constructor_args, CHECK); + Handle h_dcmd_instance(THREAD, dcmd); + assert(h_dcmd_instance.not_null(), "invariant"); + + jstring name = NULL; + if (_name.is_set() && _name.value() != NULL) { + name = JfrJavaSupport::new_string(_name.value(), CHECK); + } + + jstring filepath = NULL; + if (_filename.is_set() && _filename.value() != NULL) { + filepath = JfrJavaSupport::new_string(_filename.value(), CHECK); + } + + static const char klass[] = "jdk/jfr/internal/dcmd/DCmdStop"; + static const char method[] = "execute"; + static const char signature[] = "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"; + + JfrJavaArguments execute_args(&result, klass, method, signature, CHECK); + execute_args.set_receiver(h_dcmd_instance); + + // arguments + execute_args.push_jobject(name); + execute_args.push_jobject(filepath); + + JfrJavaSupport::call_virtual(&execute_args, THREAD); + handle_dcmd_result(output(), (oop)result.get_jobject(), source, THREAD); +} + +JfrConfigureFlightRecorderDCmd::JfrConfigureFlightRecorderDCmd(outputStream* output, + bool heap) : DCmdWithParser(output, heap), + _repository_path("repositorypath", "Path to repository,.e.g \\\"My Repository\\\"", "STRING", false, NULL), + _dump_path("dumppath", "Path to dump,.e.g \\\"My Dump path\\\"", "STRING", false, NULL), + _stack_depth("stackdepth", "Stack Depth", "JLONG", false, "64"), + _global_buffer_count("globalbuffercount", "Number of global buffers,", "JLONG", false, "32"), + _global_buffer_size("globalbuffersize", "Size of a global buffers,", "JLONG", false, "524288"), + _thread_buffer_size("thread_buffer_size", "Size of a thread buffer", "JLONG", false, "8192"), + _memory_size("memorysize", "Overall memory size, ", "JLONG", false, "16777216"), + _max_chunk_size("maxchunksize", "Size of an individual disk chunk", "JLONG", false, "12582912"), + _sample_threads("samplethreads", "Activate Thread sampling", "BOOLEAN", false, "true") { + _dcmdparser.add_dcmd_option(&_repository_path); + _dcmdparser.add_dcmd_option(&_dump_path); + _dcmdparser.add_dcmd_option(&_stack_depth); + _dcmdparser.add_dcmd_option(&_global_buffer_count); + _dcmdparser.add_dcmd_option(&_global_buffer_size); + _dcmdparser.add_dcmd_option(&_thread_buffer_size); + _dcmdparser.add_dcmd_option(&_memory_size); + _dcmdparser.add_dcmd_option(&_max_chunk_size); + _dcmdparser.add_dcmd_option(&_sample_threads); +}; + +int JfrConfigureFlightRecorderDCmd::num_arguments() { + ResourceMark rm; + JfrConfigureFlightRecorderDCmd* dcmd = new JfrConfigureFlightRecorderDCmd(NULL, false); + if (dcmd != NULL) { + DCmdMark mark(dcmd); + return dcmd->_dcmdparser.num_arguments(); + } + return 0; +} + +void JfrConfigureFlightRecorderDCmd::execute(DCmdSource source, TRAPS) { + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD)); + + if (invalid_state(output(), THREAD)) { + return; + } + + ResourceMark rm(THREAD); + HandleMark hm(THREAD); + JNIHandleBlockManager jni_handle_management(THREAD); + + JavaValue result(T_OBJECT); + JfrJavaArguments constructor_args(&result); + constructor_args.set_klass("jdk/jfr/internal/dcmd/DCmdConfigure", CHECK); + const oop dcmd = construct_dcmd_instance(&constructor_args, CHECK); + Handle h_dcmd_instance(THREAD, dcmd); + assert(h_dcmd_instance.not_null(), "invariant"); + + jstring repository_path = NULL; + if (_repository_path.is_set() && _repository_path.value() != NULL) { + repository_path = JfrJavaSupport::new_string(_repository_path.value(), CHECK); + } + + jstring dump_path = NULL; + if (_dump_path.is_set() && _dump_path.value() != NULL) { + dump_path = JfrJavaSupport::new_string(_dump_path.value(), CHECK); + } + + jobject stack_depth = NULL; + if (_stack_depth.is_set()) { + stack_depth = JfrJavaSupport::new_java_lang_Integer((jint)_stack_depth.value(), CHECK); + } + + jobject global_buffer_count = NULL; + if (_global_buffer_count.is_set()) { + global_buffer_count = JfrJavaSupport::new_java_lang_Long(_global_buffer_count.value(), CHECK); + } + + jobject global_buffer_size = NULL; + if (_global_buffer_size.is_set()) { + global_buffer_size = JfrJavaSupport::new_java_lang_Long(_global_buffer_size.value(), CHECK); + } + + jobject thread_buffer_size = NULL; + if (_thread_buffer_size.is_set()) { + thread_buffer_size = JfrJavaSupport::new_java_lang_Long(_thread_buffer_size.value(), CHECK); + } + + jobject max_chunk_size = NULL; + if (_max_chunk_size.is_set()) { + max_chunk_size = JfrJavaSupport::new_java_lang_Long(_max_chunk_size.value(), CHECK); + } + + jobject memory_size = NULL; + if (_memory_size.is_set()) { + memory_size = JfrJavaSupport::new_java_lang_Long(_memory_size.value(), CHECK); + } + + jobject sample_threads = NULL; + if (_sample_threads.is_set()) { + sample_threads = JfrJavaSupport::new_java_lang_Boolean(_sample_threads.value(), CHECK); + } + + static const char klass[] = "jdk/jfr/internal/dcmd/DCmdConfigure"; + static const char method[] = "execute"; + static const char signature[] = "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;" + "Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Long;" + "Ljava/lang/Long;Ljava/lang/Boolean;)Ljava/lang/String;"; + + JfrJavaArguments execute_args(&result, klass, method, signature, CHECK); + execute_args.set_receiver(h_dcmd_instance); + + // params + execute_args.push_jobject(repository_path); + execute_args.push_jobject(dump_path); + execute_args.push_jobject(stack_depth); + execute_args.push_jobject(global_buffer_count); + execute_args.push_jobject(global_buffer_size); + execute_args.push_jobject(thread_buffer_size); + execute_args.push_jobject(memory_size); + execute_args.push_jobject(max_chunk_size); + execute_args.push_jobject(sample_threads); + + JfrJavaSupport::call_virtual(&execute_args, THREAD); + handle_dcmd_result(output(), (oop)result.get_jobject(), source, THREAD); +} + +bool register_jfr_dcmds() { + uint32_t full_export = DCmd_Source_Internal | DCmd_Source_AttachAPI | DCmd_Source_MBean; + 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)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); + return true; +} + diff --git a/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp b/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp new file mode 100644 index 00000000000..afd2f86138d --- /dev/null +++ b/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_JFR_JFRDCMDS_HPP +#define SHARE_VM_JFR_JFRDCMDS_HPP + +#include "services/diagnosticCommand.hpp" + +class JfrDumpFlightRecordingDCmd : public DCmdWithParser { + protected: + DCmdArgument _name; + DCmdArgument _filename; + DCmdArgument _path_to_gc_roots; + + public: + JfrDumpFlightRecordingDCmd(outputStream* output, bool heap); + static const char* name() { + return "JFR.dump"; + } + static const char* description() { + return "Copies contents of a JFR recording to file. Either the name or the recording id must be specified."; + } + static const char* impact() { + return "Low"; + } + static const JavaPermission permission() { + JavaPermission p = {"java.lang.management.ManagementPermission", "monitor", NULL}; + return p; + } + static int num_arguments(); + virtual void execute(DCmdSource source, TRAPS); +}; + +class JfrCheckFlightRecordingDCmd : public DCmdWithParser { + protected: + DCmdArgument _name; + DCmdArgument _verbose; + + public: + JfrCheckFlightRecordingDCmd(outputStream* output, bool heap); + static const char* name() { + return "JFR.check"; + } + static const char* description() { + return "Checks running JFR recording(s)"; + } + static const char* impact() { + return "Low"; + } + static const JavaPermission permission() { + JavaPermission p = {"java.lang.management.ManagementPermission", "monitor", NULL}; + return p; + } + static int num_arguments(); + virtual void execute(DCmdSource source, TRAPS); +}; + +class JfrStartFlightRecordingDCmd : public DCmdWithParser { + protected: + DCmdArgument _name; + DCmdArgument _settings; + DCmdArgument _delay; + DCmdArgument _duration; + DCmdArgument _disk; + DCmdArgument _filename; + DCmdArgument _maxage; + DCmdArgument _maxsize; + DCmdArgument _dump_on_exit; + DCmdArgument _path_to_gc_roots; + + public: + JfrStartFlightRecordingDCmd(outputStream* output, bool heap); + static const char* name() { + return "JFR.start"; + } + static const char* description() { + return "Starts a new JFR recording"; + } + static const char* impact() { + return "Medium: Depending on the settings for a recording, the impact can range from low to high."; + } + static const JavaPermission permission() { + JavaPermission p = {"java.lang.management.ManagementPermission", "monitor", NULL}; + return p; + } + static int num_arguments(); + virtual void execute(DCmdSource source, TRAPS); +}; + +class JfrStopFlightRecordingDCmd : public DCmdWithParser { + protected: + DCmdArgument _name; + DCmdArgument _filename; + + public: + JfrStopFlightRecordingDCmd(outputStream* output, bool heap); + static const char* name() { + return "JFR.stop"; + } + static const char* description() { + return "Stops a JFR recording"; + } + static const char* impact() { + return "Low"; + } + static const JavaPermission permission() { + JavaPermission p = {"java.lang.management.ManagementPermission", "monitor", NULL}; + return p; + } + static int num_arguments(); + virtual void execute(DCmdSource source, TRAPS); +}; + +class JfrRuntimeOptions; + +class JfrConfigureFlightRecorderDCmd : public DCmdWithParser { + friend class JfrOptionSet; + protected: + DCmdArgument _repository_path; + DCmdArgument _dump_path; + DCmdArgument _stack_depth; + DCmdArgument _global_buffer_count; + DCmdArgument _global_buffer_size; + DCmdArgument _thread_buffer_size; + DCmdArgument _memory_size; + DCmdArgument _max_chunk_size; + DCmdArgument _sample_threads; + + public: + JfrConfigureFlightRecorderDCmd(outputStream* output, bool heap); + static const char* name() { + return "JFR.configure"; + } + static const char* description() { + return "Configure JFR"; + } + static const char* impact() { + return "Low"; + } + static const JavaPermission permission() { + JavaPermission p = {"java.lang.management.ManagementPermission", "monitor", NULL}; + return p; + } + static int num_arguments(); + virtual void execute(DCmdSource source, TRAPS); +}; + +bool register_jfr_dcmds(); + +#endif // SHARE_VM_JFR_JFRDCMDS_HPP diff --git a/src/hotspot/share/jfr/instrumentation/jfrEventClassTransformer.cpp b/src/hotspot/share/jfr/instrumentation/jfrEventClassTransformer.cpp new file mode 100644 index 00000000000..6c25a2e56c6 --- /dev/null +++ b/src/hotspot/share/jfr/instrumentation/jfrEventClassTransformer.cpp @@ -0,0 +1,1556 @@ +/* + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "jvm.h" +#include "classfile/classFileParser.hpp" +#include "classfile/classFileStream.hpp" +#include "classfile/javaClasses.inline.hpp" +#include "classfile/stackMapTable.hpp" +#include "classfile/verificationType.hpp" +#include "interpreter/bytecodes.hpp" +#include "jfr/instrumentation/jfrEventClassTransformer.hpp" +#include "jfr/jfr.hpp" +#include "jfr/jni/jfrJavaSupport.hpp" +#include "jfr/jni/jfrUpcalls.hpp" +#include "jfr/support/jfrEventClass.hpp" +#include "jfr/utilities/jfrBigEndian.hpp" +#include "jfr/writers/jfrBigEndianWriter.hpp" +#include "logging/log.hpp" +#include "memory/allocation.inline.hpp" +#include "memory/resourceArea.hpp" +#include "oops/array.hpp" +#include "oops/instanceKlass.hpp" +#include "oops/method.hpp" +#include "prims/jvmtiRedefineClasses.hpp" +#include "runtime/handles.inline.hpp" +#include "runtime/os.hpp" +#include "runtime/thread.inline.hpp" +#include "utilities/exceptions.hpp" +#include "utilities/globalDefinitions.hpp" +#include "utilities/macros.hpp" + +static const u2 number_of_new_methods = 5; +static const u2 number_of_new_fields = 3; +static const int extra_stream_bytes = 0x280; +static const u2 invalid_cp_index = 0; + +static const char* utf8_constants[] = { + "Code", // 0 + "J", // 1 + "commit", // 2 + "eventHandler", // 3 + "Ljdk/jfr/internal/handlers/EventHandler;", // 4 + "duration", // 5 + "begin", // 6 + "()V", // 7 + "isEnabled", // 8 + "()Z", // 9 + "end", // 10 + "shouldCommit", // 11 + "startTime", // 12 + "", // 13 + "jdk/jfr/FlightRecorder", // 14 + "register", // 15 + "(Ljava/lang/Class;)V", // 16 // LAST_REQUIRED_UTF8 + "StackMapTable", // 17 + "Exceptions", // 18 + "LineNumberTable", // 20 + "LocalVariableTable", // 21 + "LocalVariableTypeTable", // 22 + "RuntimeVisibleAnnotation" // 23 +}; + +enum utf8_req_symbols { + UTF8_REQ_Code, + UTF8_REQ_J_FIELD_DESC, + UTF8_REQ_commit, + UTF8_REQ_eventHandler, + UTF8_REQ_eventHandler_FIELD_DESC, + UTF8_REQ_duration, + UTF8_REQ_begin, + UTF8_REQ_EMPTY_VOID_METHOD_DESC, + UTF8_REQ_isEnabled, + UTF8_REQ_EMPTY_BOOLEAN_METHOD_DESC, + UTF8_REQ_end, + UTF8_REQ_shouldCommit, + UTF8_REQ_startTime, + UTF8_REQ_clinit, + UTF8_REQ_FlightRecorder, + UTF8_REQ_register, + UTF8_REQ_CLASS_VOID_METHOD_DESC, + NOF_UTF8_REQ_SYMBOLS +}; + +enum utf8_opt_symbols { + UTF8_OPT_StackMapTable = NOF_UTF8_REQ_SYMBOLS, + UTF8_OPT_Exceptions, + UTF8_OPT_LineNumberTable, + UTF8_OPT_LocalVariableTable, + UTF8_OPT_LocalVariableTypeTable, + UTF8_OPT_RuntimeVisibleAnnotation, + NOF_UTF8_SYMBOLS +}; + +static u1 empty_void_method_code_attribute[] = { + 0x0, + 0x0, + 0x0, + 0xd, // attribute len + 0x0, + 0x0, // max stack + 0x0, + 0x1, // max locals + 0x0, + 0x0, + 0x0, + 0x1, // code length + Bytecodes::_return, + 0x0, + 0x0, // ex table len + 0x0, + 0x0 // attributes_count +}; + +static u1 boolean_method_code_attribute[] = { + 0x0, + 0x0, + 0x0, + 0xe, + 0x0, + 0x1, // max stack + 0x0, + 0x1, // max locals + 0x0, + 0x0, + 0x0, + 0x2, + Bytecodes::_iconst_0, + Bytecodes::_ireturn, + 0x0, + 0x0, // ex table len + 0x0, + 0x0, // attributes_count +}; + +// annotation processing support + +enum { // initial annotation layout + atype_off = 0, // utf8 such as 'Ljava/lang/annotation/Retention;' + count_off = 2, // u2 such as 1 (one value) + member_off = 4, // utf8 such as 'value' + tag_off = 6, // u1 such as 'c' (type) or 'e' (enum) + e_tag_val = 'e', + e_type_off = 7, // utf8 such as 'Ljava/lang/annotation/RetentionPolicy;' + e_con_off = 9, // utf8 payload, such as 'SOURCE', 'CLASS', 'RUNTIME' + e_size = 11, // end of 'e' annotation + c_tag_val = 'c', // payload is type + c_con_off = 7, // utf8 payload, such as 'I' + c_size = 9, // end of 'c' annotation + s_tag_val = 's', // payload is String + s_con_off = 7, // utf8 payload, such as 'Ljava/lang/String;' + s_size = 9, + min_size = 6 // smallest possible size (zero members) +}; + +static int skip_annotation_value(const address, int, int); // fwd decl + +// Skip an annotation. Return >=limit if there is any problem. +static int next_annotation_index(const address buffer, int limit, int index) { + assert(buffer != NULL, "invariant"); + index += 2; // skip atype + if ((index += 2) >= limit) { + return limit; + } + int nof_members = JfrBigEndian::read(buffer + index - 2); + while (--nof_members >= 0 && index < limit) { + index += 2; // skip member + index = skip_annotation_value(buffer, limit, index); + } + return index; +} + +// Skip an annotation value. Return >=limit if there is any problem. +static int skip_annotation_value(const address buffer, int limit, int index) { + assert(buffer != NULL, "invariant"); + // value := switch (tag:u1) { + // case B, C, I, S, Z, D, F, J, c: con:u2; + // case e: e_class:u2 e_name:u2; + // case s: s_con:u2; + // case [: do(nval:u2) {value}; + // case @: annotation; + // case s: s_con:u2; + // } + if ((index += 1) >= limit) { + return limit; + } + const u1 tag = buffer[index - 1]; + switch (tag) { + case 'B': + case 'C': + case 'I': + case 'S': + case 'Z': + case 'D': + case 'F': + case 'J': + case 'c': + case 's': + index += 2; // skip con or s_con + break; + case 'e': + index += 4; // skip e_class, e_name + break; + case '[': + { + if ((index += 2) >= limit) { + return limit; + } + int nof_values = JfrBigEndian::read(buffer + index - 2); + while (--nof_values >= 0 && index < limit) { + index = skip_annotation_value(buffer, limit, index); + } + } + break; + case '@': + index = next_annotation_index(buffer, limit, index); + break; + default: + return limit; // bad tag byte + } + return index; +} + +static const u2 number_of_elements_offset = (u2)2; +static const u2 element_name_offset = (u2)(number_of_elements_offset + 2); +static const u2 element_name_size = (u2)2; +static const u2 value_type_relative_offset = (u2)2; +static const u2 value_relative_offset = (u2)(value_type_relative_offset + 1); + +// see JVMS - 4.7.16. The RuntimeVisibleAnnotations Attribute + +class AnnotationElementIterator : public StackObj { + private: + const InstanceKlass* _ik; + const address _buffer; + const u2 _limit; // length of annotation + mutable u2 _current; // element + mutable u2 _next; // element + u2 value_index() const { + return JfrBigEndian::read(_buffer + _current + value_relative_offset); + } + + public: + AnnotationElementIterator(const InstanceKlass* ik, address buffer, u2 limit) : _ik(ik), + _buffer(buffer), + _limit(limit), + _next(element_name_offset), + _current(element_name_offset) { + assert(_buffer != NULL, "invariant"); + assert(_next == element_name_offset, "invariant"); + assert(_current == element_name_offset, "invariant"); + } + + bool has_next() const { + return _next < _limit; + } + + void move_to_next() const { + assert(has_next(), "invariant"); + _current = _next; + if (_next < _limit) { + _next = skip_annotation_value(_buffer, _limit, _next + element_name_size); + } + assert(_next <= _limit, "invariant"); + assert(_current <= _limit, "invariant"); + } + + u2 number_of_elements() const { + return JfrBigEndian::read(_buffer + number_of_elements_offset); + } + + const Symbol* name() const { + assert(_current < _next, "invariant"); + return _ik->constants()->symbol_at(JfrBigEndian::read(_buffer + _current)); + } + + char value_type() const { + return JfrBigEndian::read(_buffer + _current + value_type_relative_offset); + } + + jint read_int() const { + return _ik->constants()->int_at(value_index()); + } + + bool read_bool() const { + return read_int() != 0; + } +}; + +class AnnotationIterator : public StackObj { + private: + const InstanceKlass* _ik; + // ensure _limit field is declared before _buffer + u2 _limit; // length of annotations array + const address _buffer; + mutable u2 _current; // annotation + mutable u2 _next; // annotation + + public: + AnnotationIterator(const InstanceKlass* ik, AnnotationArray* ar) : _ik(ik), + _current(0), + _next(0), + _limit(ar != NULL ? ar->length() : 0), + _buffer(_limit > 2 ? ar->adr_at(2) : NULL) { + if (_buffer != NULL) { + _limit -= 2; // subtract sizeof(u2) number of annotations field + } + } + bool has_next() const { + return _next < _limit; + } + + void move_to_next() const { + assert(has_next(), "invariant"); + _current = _next; + if (_next < _limit) { + _next = next_annotation_index(_buffer, _limit, _next); + } + assert(_next <= _limit, "invariant"); + assert(_current <= _limit, "invariant"); + } + const AnnotationElementIterator elements() const { + assert(_current < _next, "invariant"); + return AnnotationElementIterator(_ik, _buffer + _current, _next - _current); + } + const Symbol* type() const { + assert(_buffer != NULL, "invariant"); + assert(_current < _limit, "invariant"); + return _ik->constants()->symbol_at(JfrBigEndian::read(_buffer + _current)); + } +}; + +static unsigned int unused_hash = 0; +static const char value_name[] = "value"; +static bool has_registered_annotation(const InstanceKlass* ik, const Symbol* annotation_type, bool& value) { + assert(annotation_type != NULL, "invariant"); + AnnotationArray* class_annotations = ik->class_annotations(); + if (class_annotations == NULL) { + return false; + } + + const AnnotationIterator annotation_iterator(ik, class_annotations); + while (annotation_iterator.has_next()) { + annotation_iterator.move_to_next(); + if (annotation_iterator.type() == annotation_type) { + // target annotation found + static const Symbol* value_symbol = + SymbolTable::lookup_only(value_name, sizeof value_name - 1, unused_hash); + assert(value_symbol != NULL, "invariant"); + const AnnotationElementIterator element_iterator = annotation_iterator.elements(); + while (element_iterator.has_next()) { + element_iterator.move_to_next(); + if (value_symbol == element_iterator.name()) { + // "value" element + assert('Z' == element_iterator.value_type(), "invariant"); + value = element_iterator.read_bool(); + return true; + } + } + } + } + return false; +} + +static bool registered_annotation_value(const InstanceKlass* ik, const Symbol* const registered_symbol) { + assert(registered_symbol != NULL, "invariant"); + assert(ik != NULL, "invariant"); + assert(JdkJfrEvent::is_a(ik), "invariant"); + bool registered_value = false; + if (has_registered_annotation(ik, registered_symbol, registered_value)) { + return registered_value; + } + InstanceKlass* super = InstanceKlass::cast(ik->super()); + return registered_annotation_value(super, registered_symbol); +} + +static const char registered_constant[] = "Ljdk/jfr/Registered;"; + +// Evaluate to the value of the first found "Ljdk/jfr/Registered;" annotation. +// Searching moves upwards in the klass hierarchy in order to support +// inherited annotations in addition to the ability to override. +static bool should_register_klass(const InstanceKlass* ik) { + static const Symbol* const registered_symbol = SymbolTable::lookup_only(registered_constant, + sizeof registered_constant - 1, + unused_hash); + assert(registered_symbol != NULL, "invariant"); + return registered_annotation_value(ik, registered_symbol); +} +/* + * Map an utf8 constant back to its CONSTANT_UTF8_INFO + */ +static u2 utf8_info_index(const InstanceKlass* ik, const Symbol* const target, TRAPS) { + assert(target != NULL, "invariant"); + const ConstantPool* cp = ik->constants(); + const int cp_len = cp->length(); + for (u2 index = 1; index < cp_len; ++index) { + const constantTag tag = cp->tag_at(index); + if (tag.is_utf8()) { + const Symbol* const utf8_sym = cp->symbol_at(index); + assert(utf8_sym != NULL, "invariant"); + if (utf8_sym == target) { + return index; + } + } + } + // not in constant pool + return invalid_cp_index; +} + +#ifdef ASSERT +static bool is_index_within_range(u2 index, u2 orig_cp_len, u2 new_cp_entries_len) { + return index > 0 && index < orig_cp_len + new_cp_entries_len; +} +#endif + +static u2 add_utf8_info(JfrBigEndianWriter& writer, const char* utf8_constant, u2 orig_cp_len, u2& new_cp_entries_len) { + assert(utf8_constant != NULL, "invariant"); + writer.write(JVM_CONSTANT_Utf8); + writer.write_utf8_u2_len(utf8_constant); + assert(writer.is_valid(), "invariant"); + // return index for the added utf8 info + return orig_cp_len + new_cp_entries_len++; +} + +static u2 add_method_ref_info(JfrBigEndianWriter& writer, + u2 cls_name_index, + u2 method_index, + u2 desc_index, + u2 orig_cp_len, + u2& number_of_new_constants, + TRAPS) { + assert(is_index_within_range(cls_name_index, orig_cp_len, number_of_new_constants), "invariant"); + assert(is_index_within_range(method_index, orig_cp_len, number_of_new_constants), "invariant"); + assert(is_index_within_range(desc_index, orig_cp_len, number_of_new_constants), "invariant"); + writer.write(JVM_CONSTANT_Class); + writer.write(cls_name_index); + const u2 cls_entry_index = orig_cp_len + number_of_new_constants; + ++number_of_new_constants; + writer.write(JVM_CONSTANT_NameAndType); + writer.write(method_index); + writer.write(desc_index); + const u2 nat_entry_index = orig_cp_len + number_of_new_constants; + ++number_of_new_constants; + writer.write(JVM_CONSTANT_Methodref); + writer.write(cls_entry_index); + writer.write(nat_entry_index); + // post-increment number_of_new_constants + // value returned is the index to the added method_ref + return orig_cp_len + number_of_new_constants++; +} + +static u2 add_flr_register_method_constants(JfrBigEndianWriter& writer, + const u2* utf8_indexes, + u2 orig_cp_len, + u2& number_of_new_constants, + TRAPS) { + assert(utf8_indexes != NULL, "invariant"); + return add_method_ref_info(writer, + utf8_indexes[UTF8_REQ_FlightRecorder], + utf8_indexes[UTF8_REQ_register], + utf8_indexes[UTF8_REQ_CLASS_VOID_METHOD_DESC], + orig_cp_len, + number_of_new_constants, + THREAD); +} + +/* + * field_info { + * u2 access_flags; + * u2 name_index; + * u2 descriptor_index; + * u2 attributes_count; + * attribute_info attributes[attributes_count]; + * } + */ +static jlong add_field_info(JfrBigEndianWriter& writer, u2 name_index, u2 desc_index, bool is_static = false) { + assert(name_index > 0, "invariant"); + assert(desc_index > 0, "invariant"); + DEBUG_ONLY(const jlong start_offset = writer.current_offset();) + writer.write(JVM_ACC_SYNTHETIC | JVM_ACC_PRIVATE | (is_static ? JVM_ACC_STATIC : JVM_ACC_TRANSIENT)); // flags + writer.write(name_index); + writer.write(desc_index); + writer.write((u2)0x0); // attributes_count + assert(writer.is_valid(), "invariant"); + DEBUG_ONLY(assert(start_offset + 8 == writer.current_offset(), "invariant");) + return writer.current_offset(); +} + +static u2 add_field_infos(JfrBigEndianWriter& writer, const u2* utf8_indexes) { + assert(utf8_indexes != NULL, "invariant"); + add_field_info(writer, + utf8_indexes[UTF8_REQ_eventHandler], + utf8_indexes[UTF8_REQ_eventHandler_FIELD_DESC], + true); // static + + add_field_info(writer, + utf8_indexes[UTF8_REQ_startTime], + utf8_indexes[UTF8_REQ_J_FIELD_DESC]); + + add_field_info(writer, + utf8_indexes[UTF8_REQ_duration], + utf8_indexes[UTF8_REQ_J_FIELD_DESC]); + + return number_of_new_fields; +} + +/* + * method_info { + * u2 access_flags; + * u2 name_index; + * u2 descriptor_index; + * u2 attributes_count; + * attribute_info attributes[attributes_count]; + * } + * + * Code_attribute { + * u2 attribute_name_index; + * u4 attribute_length; + * u2 max_stack; + * u2 max_locals; + * u4 code_length; + * u1 code[code_length]; + * u2 exception_table_length; + * { u2 start_pc; + * u2 end_pc; + * u2 handler_pc; + * u2 catch_type; + * } exception_table[exception_table_length]; + * u2 attributes_count; + * attribute_info attributes[attributes_count]; + * } + */ + +static jlong add_method_info(JfrBigEndianWriter& writer, + u2 name_index, + u2 desc_index, + u2 code_index, + const u1* const code, + const size_t code_len) { + assert(name_index > 0, "invariant"); + assert(desc_index > 0, "invariant"); + assert(code_index > 0, "invariant"); + DEBUG_ONLY(const jlong start_offset = writer.current_offset();) + writer.write(JVM_ACC_SYNTHETIC | JVM_ACC_PUBLIC); // flags + writer.write(name_index); + writer.write(desc_index); + writer.write(0x1); // attributes_count ; 1 for "Code" attribute + assert(writer.is_valid(), "invariant"); + DEBUG_ONLY(assert(start_offset + 8 == writer.current_offset(), "invariant");) + // Code attribute + writer.write(code_index); // "Code" + writer.bytes(code, code_len); + DEBUG_ONLY(assert((start_offset + 8 + 2 + (jlong)code_len) == writer.current_offset(), "invariant");) + return writer.current_offset(); +} + +/* + * On return, the passed stream will be positioned + * just after the constant pool section in the classfile + * and the cp length is returned. + * + * Stream should come in at the start position. + */ +static u2 position_stream_after_cp(const ClassFileStream* stream) { + assert(stream != NULL, "invariant"); + assert(stream->current_offset() == 0, "invariant"); + stream->skip_u4_fast(2); // 8 bytes skipped + const u2 cp_len = stream->get_u2_fast(); + assert(cp_len > 0, "invariant"); + // now spin the stream position to just after the constant pool + for (u2 index = 1; index < cp_len; ++index) { + const u1 tag = stream->get_u1_fast(); // cp tag + switch (tag) { + case JVM_CONSTANT_Class: + case JVM_CONSTANT_String: { + stream->skip_u2_fast(1); // skip 2 bytes + continue; + } + case JVM_CONSTANT_Fieldref: + case JVM_CONSTANT_Methodref: + case JVM_CONSTANT_InterfaceMethodref: + case JVM_CONSTANT_Integer: + case JVM_CONSTANT_Float: + case JVM_CONSTANT_NameAndType: + case JVM_CONSTANT_InvokeDynamic: { + stream->skip_u4_fast(1); // skip 4 bytes + continue; + } + case JVM_CONSTANT_Long: + case JVM_CONSTANT_Double: { + stream->skip_u4_fast(2); // skip 8 bytes + // Skip entry following eigth-byte constant, see JVM book p. 98 + ++index; + continue; + } + case JVM_CONSTANT_Utf8: { + u2 utf8_length = stream->get_u2_fast(); + stream->skip_u1_fast(utf8_length); // skip 2 + len bytes + continue; + } + case JVM_CONSTANT_MethodHandle: + case JVM_CONSTANT_MethodType: { + if (tag == JVM_CONSTANT_MethodHandle) { + stream->skip_u1_fast(1); + stream->skip_u2_fast(1); // skip 3 bytes + } + else if (tag == JVM_CONSTANT_MethodType) { + stream->skip_u2_fast(1); // skip 3 bytes + } + } + continue; + default: + assert(false, "error in skip logic!"); + break; + } // end switch(tag) + } + return cp_len; +} + +/* +* On return, the passed stream will be positioned +* just after the fields section in the classfile +* and the number of fields will be returned. +* +* Stream should come in positioned just before fields_count +*/ +static u2 position_stream_after_fields(const ClassFileStream* stream) { + assert(stream != NULL, "invariant"); + assert(stream->current_offset() > 0, "invariant"); + // fields len + const u2 orig_fields_len = stream->get_u2_fast(); + // fields + for (u2 i = 0; i < orig_fields_len; ++i) { + stream->skip_u2_fast(3); + const u2 attrib_info_len = stream->get_u2_fast(); + for (u2 j = 0; j < attrib_info_len; ++j) { + stream->skip_u2_fast(1); + const u4 attrib_len = stream->get_u4_fast(); + stream->skip_u1_fast(attrib_len); + } + } + return orig_fields_len; +} + +/* +* On return, the passed stream will be positioned +* just after the methods section in the classfile +* and the number of methods will be returned. +* +* Stream should come in positioned just before methods_count +*/ +static u2 position_stream_after_methods(JfrBigEndianWriter& writer, + const ClassFileStream* stream, + const u2* utf8_indexes, + bool register_klass, + const Method* clinit_method, + u4& orig_method_len_offset) { + assert(stream != NULL, "invariant"); + assert(stream->current_offset() > 0, "invariant"); + assert(utf8_indexes != NULL, "invariant"); + // We will come back to this location when we + // know how many methods there will be. + writer.reserve(sizeof(u2)); + const u2 orig_methods_len = stream->get_u2_fast(); + // Move copy position past original method_count + // in order to not copy the original count + orig_method_len_offset += sizeof(u2); + for (u2 i = 0; i < orig_methods_len; ++i) { + const u4 method_offset = stream->current_offset(); + stream->skip_u2_fast(1); // Access Flags + const u2 name_index = stream->get_u2_fast(); // Name index + stream->skip_u2_fast(1); // Descriptor index + const u2 attributes_count = stream->get_u2_fast(); + for (u2 j = 0; j < attributes_count; ++j) { + stream->skip_u2_fast(1); + const u4 attrib_len = stream->get_u4_fast(); + stream->skip_u1_fast(attrib_len); + } + if (clinit_method != NULL && name_index == clinit_method->name_index()) { + // The method just parsed is an existing method. + // If the class has the @Registered(false) annotation, i.e. marking a class + // for opting out from automatic registration, then we do not need to do anything. + if (!register_klass) { + continue; + } + // Automatic registration with the jfr system is acccomplished + // by pre-pending code to the method of the class. + // We will need to re-create a new in a later step. + // For now, ensure that this method is excluded from the methods + // being copied. + writer.bytes(stream->buffer() + orig_method_len_offset, + method_offset - orig_method_len_offset); + assert(writer.is_valid(), "invariant"); + + // Update copy position to skip copy of method + orig_method_len_offset = stream->current_offset(); + } + } + return orig_methods_len; +} + +static u2 add_method_infos(JfrBigEndianWriter& writer, const u2* utf8_indexes) { + assert(utf8_indexes != NULL, "invariant"); + add_method_info(writer, + utf8_indexes[UTF8_REQ_begin], + utf8_indexes[UTF8_REQ_EMPTY_VOID_METHOD_DESC], + utf8_indexes[UTF8_REQ_Code], + empty_void_method_code_attribute, + sizeof(empty_void_method_code_attribute)); + + assert(writer.is_valid(), "invariant"); + + add_method_info(writer, + utf8_indexes[UTF8_REQ_end], + utf8_indexes[UTF8_REQ_EMPTY_VOID_METHOD_DESC], + utf8_indexes[UTF8_REQ_Code], + empty_void_method_code_attribute, + sizeof(empty_void_method_code_attribute)); + + assert(writer.is_valid(), "invariant"); + + add_method_info(writer, + utf8_indexes[UTF8_REQ_commit], + utf8_indexes[UTF8_REQ_EMPTY_VOID_METHOD_DESC], + utf8_indexes[UTF8_REQ_Code], + empty_void_method_code_attribute, + sizeof(empty_void_method_code_attribute)); + + assert(writer.is_valid(), "invariant"); + + add_method_info(writer, + utf8_indexes[UTF8_REQ_isEnabled], + utf8_indexes[UTF8_REQ_EMPTY_BOOLEAN_METHOD_DESC], + utf8_indexes[UTF8_REQ_Code], + boolean_method_code_attribute, + sizeof(boolean_method_code_attribute)); + + assert(writer.is_valid(), "invariant"); + + add_method_info(writer, + utf8_indexes[UTF8_REQ_shouldCommit], + utf8_indexes[UTF8_REQ_EMPTY_BOOLEAN_METHOD_DESC], + utf8_indexes[UTF8_REQ_Code], + boolean_method_code_attribute, + sizeof(boolean_method_code_attribute)); + assert(writer.is_valid(), "invariant"); + return number_of_new_methods; +} + +static void adjust_exception_table(JfrBigEndianWriter& writer, u2 bci_adjustment_offset, const Method* method, TRAPS) { + const u2 ex_table_length = method != NULL ? (u2)method->exception_table_length() : 0; + writer.write(ex_table_length); // Exception table length + if (ex_table_length > 0) { + assert(method != NULL, "invariant"); + const ExceptionTableElement* const ex_elements = method->exception_table_start(); + for (int i = 0; i < ex_table_length; ++i) { + assert(ex_elements != NULL, "invariant"); + writer.write(ex_elements[i].start_pc + bci_adjustment_offset); + writer.write(ex_elements[i].end_pc + bci_adjustment_offset); + writer.write(ex_elements[i].handler_pc + bci_adjustment_offset); + writer.write(ex_elements[i].catch_type_index); // no adjustment + } + } +} + +enum StackMapFrameTypes { + SAME_FRAME_BEGIN = 0, + SAME_FRAME_END = 63, + SAME_LOCALS_1_STACK_ITEM_FRAME_BEGIN = 64, + SAME_LOCALS_1_STACK_ITEM_FRAME_END = 127, + SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED = 247, + CHOP_FRAME_BEGIN = 248, + CHOP_FRAME_END = 250, + SAME_FRAME_EXTENDED = 251, + APPEND_FRAME_BEGIN = 252, + APPEND_FRAME_END = 254, + FULL_FRAME = 255 +}; + +static void adjust_stack_map(JfrBigEndianWriter& writer, + Array* stack_map, + const u2* utf8_indexes, + u2 bci_adjustment_offset, + TRAPS) { + assert(stack_map != NULL, "invariant"); + assert(utf8_indexes != NULL, "invariant"); + writer.write(utf8_indexes[UTF8_OPT_StackMapTable]); + const jlong stack_map_attrib_len_offset = writer.current_offset(); + writer.reserve(sizeof(u4)); + StackMapStream stream(stack_map); + const u2 stack_map_entries = stream.get_u2(THREAD); + // number of entries + writer.write(stack_map_entries); // new stack map entry added + const u1 frame_type = stream.get_u1(THREAD); + // SAME_FRAME and SAME_LOCALS_1_STACK_ITEM_FRAME encode + // their offset_delta into the actual frame type itself. + // If such a frame type is the first frame, then we transform + // it to a SAME_FRAME_EXTENDED or a SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED frame. + // This is done in order to not overflow frame types accidentally + // when adjusting the offset_delta. In changing the frame types, + // we can work with an explicit u2 offset_delta field (like the other frame types) + if (frame_type <= SAME_FRAME_END) { + writer.write(SAME_FRAME_EXTENDED); + writer.write(frame_type + bci_adjustment_offset); + } else if (frame_type >= SAME_LOCALS_1_STACK_ITEM_FRAME_BEGIN && + frame_type <= SAME_LOCALS_1_STACK_ITEM_FRAME_END) { + writer.write(SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED); + writer.write((frame_type - SAME_LOCALS_1_STACK_ITEM_FRAME_BEGIN) + bci_adjustment_offset); + } else if (frame_type >= SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) { + // SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED to FULL_FRAME + // has a u2 offset_delta field + writer.write(frame_type); + writer.write(stream.get_u2(THREAD) + bci_adjustment_offset); + } else { + assert(false, "stackMapFrame type is invalid"); + } + + while (!stream.at_end()) { + writer.write(stream.get_u1(THREAD)); + } + + u4 stack_map_attrib_len = writer.current_offset() - stack_map_attrib_len_offset; + // the stack_map_table_attributes_length value is exclusive + stack_map_attrib_len -= sizeof(u4); + writer.write_at_offset(stack_map_attrib_len, stack_map_attrib_len_offset); +} + +static void adjust_line_number_table(JfrBigEndianWriter& writer, + const u2* utf8_indexes, + u4 bci_adjustement_offset, + const Method* method, + TRAPS) { + assert(utf8_indexes != NULL, "invariant"); + assert(method != NULL, "invariant"); + assert(method->has_linenumber_table(), "invariant"); + writer.write(utf8_indexes[UTF8_OPT_LineNumberTable]); + const jlong lnt_attributes_length_offset = writer.current_offset(); + writer.reserve(sizeof(u4)); + const jlong lnt_attributes_entries_offset = writer.current_offset(); + writer.reserve(sizeof(u2)); + u1* lnt = method->compressed_linenumber_table(); + CompressedLineNumberReadStream lnt_stream(lnt); + u2 line_number_table_entries = 0; + while (lnt_stream.read_pair()) { + ++line_number_table_entries; + const u2 bci = (u2)lnt_stream.bci(); + writer.write(bci + (u2)bci_adjustement_offset); + writer.write((u2)lnt_stream.line()); + } + writer.write_at_offset(line_number_table_entries, lnt_attributes_entries_offset); + u4 lnt_table_attributes_len = writer.current_offset() - lnt_attributes_length_offset; + // the line_number_table_attributes_length value is exclusive + lnt_table_attributes_len -= sizeof(u4); + writer.write_at_offset(lnt_table_attributes_len, lnt_attributes_length_offset); +} + +// returns the number of lvtt entries +static u2 adjust_local_variable_table(JfrBigEndianWriter& writer, + const u2* utf8_indexes, + u2 bci_adjustment_offset, + const Method* method, + TRAPS) { + assert(utf8_indexes != NULL, "invariant"); + assert(method != NULL, "invariant"); + assert(method->has_localvariable_table(), "invariant"); + writer.write(utf8_indexes[UTF8_OPT_LocalVariableTable]); + const jlong lvt_attributes_length_offset = writer.current_offset(); + writer.reserve(sizeof(u4)); + const int lvt_len = method->localvariable_table_length(); + writer.write((u2)lvt_len); + const LocalVariableTableElement* table = method->localvariable_table_start(); + assert(table != NULL, "invariant"); + u2 num_lvtt_entries = 0; + for (int i = 0; i < lvt_len; ++i) { + writer.write(table[i].start_bci + bci_adjustment_offset); + writer.write(table[i].length); + writer.write(table[i].name_cp_index); + writer.write(table[i].descriptor_cp_index); + writer.write(table[i].slot); + if (table[i].signature_cp_index > 0) { + ++num_lvtt_entries; + } + } + u4 lvt_table_attributes_len = writer.current_offset() - lvt_attributes_length_offset; + // the lvt_table_attributes_length value is exclusive + lvt_table_attributes_len -= sizeof(u4); + writer.write_at_offset(lvt_table_attributes_len, lvt_attributes_length_offset); + return num_lvtt_entries; +} + +static void adjust_local_variable_type_table(JfrBigEndianWriter& writer, + const u2* utf8_indexes, + u2 bci_adjustment_offset, + u2 num_lvtt_entries, + const Method* method, + TRAPS) { + assert(num_lvtt_entries > 0, "invariant"); + writer.write(utf8_indexes[UTF8_OPT_LocalVariableTypeTable]); + const jlong lvtt_attributes_length_offset = writer.current_offset(); + writer.reserve(sizeof(u4)); + writer.write(num_lvtt_entries); + const LocalVariableTableElement* table = method->localvariable_table_start(); + assert(table != NULL, "invariant"); + const int lvt_len = method->localvariable_table_length(); + for (int i = 0; i < lvt_len; ++i) { + if (table[i].signature_cp_index > 0) { + writer.write(table[i].start_bci + bci_adjustment_offset); + writer.write(table[i].length); + writer.write(table[i].name_cp_index); + writer.write(table[i].signature_cp_index); + writer.write(table[i].slot); + } + } + u4 lvtt_table_attributes_len = writer.current_offset() - lvtt_attributes_length_offset; + // the lvtt_table_attributes_length value is exclusive + lvtt_table_attributes_len -= sizeof(u4); + writer.write_at_offset(lvtt_table_attributes_len, lvtt_attributes_length_offset); +} + +static void adjust_code_attributes(JfrBigEndianWriter& writer, + const u2* utf8_indexes, + u2 bci_adjustment_offset, + const Method* clinit_method, + TRAPS) { + // "Code" attributes + assert(utf8_indexes != NULL, "invariant"); + const jlong code_attributes_offset = writer.current_offset(); + writer.reserve(sizeof(u2)); + u2 number_of_code_attributes = 0; + if (clinit_method != NULL) { + Array* stack_map = clinit_method->stackmap_data(); + if (stack_map != NULL) { + ++number_of_code_attributes; + adjust_stack_map(writer, stack_map, utf8_indexes, bci_adjustment_offset, THREAD); + assert(writer.is_valid(), "invariant"); + } + if (clinit_method != NULL && clinit_method->has_linenumber_table()) { + ++number_of_code_attributes; + adjust_line_number_table(writer, utf8_indexes, bci_adjustment_offset, clinit_method, THREAD); + assert(writer.is_valid(), "invariant"); + } + if (clinit_method != NULL && clinit_method->has_localvariable_table()) { + ++number_of_code_attributes; + const u2 num_of_lvtt_entries = adjust_local_variable_table(writer, utf8_indexes, bci_adjustment_offset, clinit_method, THREAD); + assert(writer.is_valid(), "invariant"); + if (num_of_lvtt_entries > 0) { + ++number_of_code_attributes; + adjust_local_variable_type_table(writer, utf8_indexes, bci_adjustment_offset, num_of_lvtt_entries, clinit_method, THREAD); + assert(writer.is_valid(), "invariant"); + } + } + } + + // Store the number of code_attributes + writer.write_at_offset(number_of_code_attributes, code_attributes_offset); +} + +static jlong insert_clinit_method(const InstanceKlass* ik, + const ClassFileParser& parser, + JfrBigEndianWriter& writer, + u2 orig_constant_pool_len, + const u2* utf8_indexes, + const u2 register_method_ref_index, + const Method* clinit_method, + TRAPS) { + assert(utf8_indexes != NULL, "invariant"); + // The injected code length is always this value. + // This is to ensure that padding can be done + // where needed and to simplify size calculations. + static const u2 injected_code_length = 8; + const u2 name_index = utf8_indexes[UTF8_REQ_clinit]; + const u2 desc_index = utf8_indexes[UTF8_REQ_EMPTY_VOID_METHOD_DESC]; + const u2 max_stack = MAX2(clinit_method != NULL ? clinit_method->verifier_max_stack() : 1, 1); + const u2 max_locals = MAX2(clinit_method != NULL ? clinit_method->max_locals() : 0, 0); + const u2 orig_bytecodes_length = clinit_method != NULL ? (u2)clinit_method->code_size() : 0; + const address orig_bytecodes = clinit_method != NULL ? clinit_method->code_base() : NULL; + const u2 new_code_length = injected_code_length + orig_bytecodes_length; + DEBUG_ONLY(const jlong start_offset = writer.current_offset();) + writer.write(JVM_ACC_STATIC); // flags + writer.write(name_index); + writer.write(desc_index); + writer.write((u2)0x1); // attributes_count // "Code" + assert(writer.is_valid(), "invariant"); + DEBUG_ONLY(assert(start_offset + 8 == writer.current_offset(), "invariant");) + // "Code" attribute + writer.write(utf8_indexes[UTF8_REQ_Code]); // "Code" + const jlong code_attribute_length_offset = writer.current_offset(); + writer.reserve(sizeof(u4)); + writer.write(max_stack); // max stack + writer.write(max_locals); // max locals + writer.write((u4)new_code_length); // code length + + /* BEGIN CLINIT CODE */ + + // Note the use of ldc_w here instead of ldc. + // This is to handle all values of "this_class_index" + writer.write((u1)Bytecodes::_ldc_w); + writer.write((u2)parser.this_class_index()); // load constant "this class" + writer.write((u1)Bytecodes::_invokestatic); + // invoke "FlightRecorder.register(Ljava/lang/Class;") + writer.write(register_method_ref_index); + if (clinit_method == NULL) { + writer.write((u1)Bytecodes::_nop); + writer.write((u1)Bytecodes::_return); + } else { + // If we are pre-pending to original code, + // do padding to minimize disruption to the original. + // It might have dependencies on 4-byte boundaries + // i.e. lookupswitch and tableswitch instructions + writer.write((u1)Bytecodes::_nop); + writer.write((u1)Bytecodes::_nop); + // insert original clinit code + writer.bytes(orig_bytecodes, orig_bytecodes_length); + } + + /* END CLINIT CODE */ + + assert(writer.is_valid(), "invariant"); + adjust_exception_table(writer, injected_code_length, clinit_method, THREAD); + assert(writer.is_valid(), "invariant"); + adjust_code_attributes(writer, utf8_indexes, injected_code_length, clinit_method, THREAD); + assert(writer.is_valid(), "invariant"); + u4 code_attribute_len = writer.current_offset() - code_attribute_length_offset; + // the code_attribute_length value is exclusive + code_attribute_len -= sizeof(u4); + writer.write_at_offset(code_attribute_len, code_attribute_length_offset); + return writer.current_offset(); +} + +// Caller needs ResourceMark +static ClassFileStream* create_new_bytes_for_event_klass(const InstanceKlass* ik, const ClassFileParser& parser, TRAPS) { + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD)); + static const u2 public_final_flag_mask = JVM_ACC_PUBLIC | JVM_ACC_FINAL; + const ClassFileStream* const orig_stream = parser.clone_stream(); + const int orig_stream_length = orig_stream->length(); + // allocate an identically sized buffer + u1* const new_buffer = NEW_RESOURCE_ARRAY_IN_THREAD_RETURN_NULL(THREAD, u1, orig_stream_length); + if (new_buffer == NULL) { + return NULL; + } + assert(new_buffer != NULL, "invariant"); + // memcpy the entire [B + memcpy(new_buffer, orig_stream->buffer(), orig_stream_length); + const u2 orig_cp_len = position_stream_after_cp(orig_stream); + assert(orig_cp_len > 0, "invariant"); + assert(orig_stream->current_offset() > 0, "invariant"); + orig_stream->skip_u2_fast(3); // access_flags, this_class_index, super_class_index + const u2 iface_len = orig_stream->get_u2_fast(); + orig_stream->skip_u2_fast(iface_len); + // fields len + const u2 orig_fields_len = orig_stream->get_u2_fast(); + // fields + for (u2 i = 0; i < orig_fields_len; ++i) { + orig_stream->skip_u2_fast(3); + const u2 attrib_info_len = orig_stream->get_u2_fast(); + for (u2 j = 0; j < attrib_info_len; ++j) { + orig_stream->skip_u2_fast(1); + const u4 attrib_len = orig_stream->get_u4_fast(); + orig_stream->skip_u1_fast(attrib_len); + } + } + // methods + const u2 orig_methods_len = orig_stream->get_u2_fast(); + for (u2 i = 0; i < orig_methods_len; ++i) { + const u4 access_flag_offset = orig_stream->current_offset(); + const u2 flags = orig_stream->get_u2_fast(); + // Rewrite JVM_ACC_FINAL -> JVM_ACC_PUBLIC + if (public_final_flag_mask == flags) { + JfrBigEndianWriter accessflagsrewriter(new_buffer + access_flag_offset, sizeof(u2)); + accessflagsrewriter.write(JVM_ACC_PUBLIC); + assert(accessflagsrewriter.is_valid(), "invariant"); + } + orig_stream->skip_u2_fast(2); + const u2 attributes_count = orig_stream->get_u2_fast(); + for (u2 j = 0; j < attributes_count; ++j) { + orig_stream->skip_u2_fast(1); + const u4 attrib_len = orig_stream->get_u4_fast(); + orig_stream->skip_u1_fast(attrib_len); + } + } + return new ClassFileStream(new_buffer, orig_stream_length, NULL, ClassFileStream::verify); +} + +// Attempt to locate an existing UTF8_INFO mapping the utf8_constant. +// If no UTF8_INFO exists, add (append) a new one to the constant pool. +static u2 find_or_add_utf8_info(JfrBigEndianWriter& writer, + const InstanceKlass* ik, + const char* const utf8_constant, + u2 orig_cp_len, + u2& added_cp_entries, + TRAPS) { + assert(utf8_constant != NULL, "invariant"); + TempNewSymbol utf8_sym = SymbolTable::new_symbol(utf8_constant, THREAD); + // lookup existing + const int utf8_orig_idx = utf8_info_index(ik, utf8_sym, THREAD); + if (utf8_orig_idx != invalid_cp_index) { + // existing constant pool entry found + return utf8_orig_idx; + } + // no existing match, need to add a new utf8 cp entry + assert(invalid_cp_index == utf8_orig_idx, "invariant"); + // add / append new + return add_utf8_info(writer, utf8_constant, orig_cp_len, added_cp_entries); +} + +/* + * This routine will resolve the required utf8_constants array + * to their constant pool indexes (mapping to their UTF8_INFO's) + * Only if a constant is actually needed and does not already exist + * will it be added. + * + * The passed in indexes array will be populated with the resolved indexes. + * The number of newly added constant pool entries is returned. + */ +static u2 resolve_utf8_indexes(JfrBigEndianWriter& writer, + const InstanceKlass* ik, + u2* const utf8_indexes, + u2 orig_cp_len, + const Method* clinit_method, + TRAPS) { + assert(utf8_indexes != NULL, "invariant"); + u2 added_cp_entries = 0; + // resolve all required symbols + for (u2 index = 0; index < NOF_UTF8_REQ_SYMBOLS; ++index) { + utf8_indexes[index] = find_or_add_utf8_info(writer, + ik, + utf8_constants[index], + orig_cp_len, + added_cp_entries, + THREAD); + } + // Now determine optional constants (mainly "Code" attributes) + if (clinit_method != NULL && clinit_method->has_stackmap_table()) { + utf8_indexes[UTF8_OPT_StackMapTable] = + find_or_add_utf8_info(writer, + ik, + utf8_constants[UTF8_OPT_StackMapTable], + orig_cp_len, + added_cp_entries, + THREAD); + } else { + utf8_indexes[UTF8_OPT_StackMapTable] = invalid_cp_index; + } + + if (clinit_method != NULL && clinit_method->has_linenumber_table()) { + utf8_indexes[UTF8_OPT_LineNumberTable] = + find_or_add_utf8_info(writer, + ik, + utf8_constants[UTF8_OPT_LineNumberTable], + orig_cp_len, + added_cp_entries, + THREAD); + } else { + utf8_indexes[UTF8_OPT_LineNumberTable] = invalid_cp_index; + } + + if (clinit_method != NULL && clinit_method->has_localvariable_table()) { + utf8_indexes[UTF8_OPT_LocalVariableTable] = + find_or_add_utf8_info(writer, + ik, + utf8_constants[UTF8_OPT_LocalVariableTable], + orig_cp_len, + added_cp_entries, + THREAD); + + utf8_indexes[UTF8_OPT_LocalVariableTypeTable] = + find_or_add_utf8_info(writer, + ik, + utf8_constants[UTF8_OPT_LocalVariableTypeTable], + orig_cp_len, + added_cp_entries, + THREAD); + } else { + utf8_indexes[UTF8_OPT_LocalVariableTable] = invalid_cp_index; + utf8_indexes[UTF8_OPT_LocalVariableTypeTable] = invalid_cp_index; + } + + return added_cp_entries; +} + +static u1* new_bytes_for_lazy_instrumentation(const InstanceKlass* ik, + const ClassFileParser& parser, + jint& size_of_new_bytes, + TRAPS) { + assert(ik != NULL, "invariant"); + // If the class already has a clinit method + // we need to take that into account + const Method* clinit_method = ik->class_initializer(); + const bool register_klass = should_register_klass(ik); + const ClassFileStream* const orig_stream = parser.clone_stream(); + const int orig_stream_size = orig_stream->length(); + assert(orig_stream->current_offset() == 0, "invariant"); + const u2 orig_cp_len = position_stream_after_cp(orig_stream); + assert(orig_cp_len > 0, "invariant"); + assert(orig_stream->current_offset() > 0, "invariant"); + // Dimension and allocate a working byte buffer + // to be used in building up a modified class [B. + const jint new_buffer_size = extra_stream_bytes + orig_stream_size; + u1* const new_buffer = NEW_RESOURCE_ARRAY_IN_THREAD_RETURN_NULL(THREAD, u1, new_buffer_size); + if (new_buffer == NULL) { + log_error(jfr, system) ("Thread local allocation (native) for " SIZE_FORMAT + " bytes failed in JfrClassAdapter::on_klass_creation", (size_t)new_buffer_size); + return NULL; + } + assert(new_buffer != NULL, "invariant"); + // [B wrapped in a big endian writer + JfrBigEndianWriter writer(new_buffer, new_buffer_size); + assert(writer.current_offset() == 0, "invariant"); + const u4 orig_access_flag_offset = orig_stream->current_offset(); + // Copy original stream from the beginning up to AccessFlags + // This means the original constant pool contents are copied unmodified + writer.bytes(orig_stream->buffer(), orig_access_flag_offset); + assert(writer.is_valid(), "invariant"); + assert(writer.current_offset() == orig_access_flag_offset, "invariant"); // same positions + // Our writer now sits just after the last original constant pool entry. + // I.e. we are in a good position to append new constant pool entries + // This array will contain the resolved indexes + // in order to reference UTF8_INFO's needed + u2 utf8_indexes[NOF_UTF8_SYMBOLS]; + // Resolve_utf8_indexes will be conservative in attempting to + // locate an existing UTF8_INFO; it will only append constants + // that is absolutely required + u2 number_of_new_constants = resolve_utf8_indexes(writer, ik, utf8_indexes, orig_cp_len, clinit_method, THREAD); + // UTF8_INFO entries now added to the constant pool + // In order to invoke a method we would need additional + // constants, JVM_CONSTANT_Class, JVM_CONSTANT_NameAndType + // and JVM_CONSTANT_Methodref. + const u2 flr_register_method_ref_index = + register_klass ? + add_flr_register_method_constants(writer, + utf8_indexes, + orig_cp_len, + number_of_new_constants, + THREAD) : invalid_cp_index; + + // New constant pool entries added and all UTF8_INFO indexes resolved + // Now update the class file constant_pool_count with an updated count + writer.write_at_offset(orig_cp_len + number_of_new_constants, 8); + assert(writer.is_valid(), "invariant"); + orig_stream->skip_u2_fast(3); // access_flags, this_class_index, super_class_index + const u2 iface_len = orig_stream->get_u2_fast(); // interfaces + orig_stream->skip_u2_fast(iface_len); + const u4 orig_fields_len_offset = orig_stream->current_offset(); + // Copy from AccessFlags up to and including interfaces + writer.bytes(orig_stream->buffer() + orig_access_flag_offset, + orig_fields_len_offset - orig_access_flag_offset); + assert(writer.is_valid(), "invariant"); + const jlong new_fields_len_offset = writer.current_offset(); + const u2 orig_fields_len = position_stream_after_fields(orig_stream); + u4 orig_method_len_offset = orig_stream->current_offset(); + // Copy up to and including fields + writer.bytes(orig_stream->buffer() + orig_fields_len_offset, orig_method_len_offset - orig_fields_len_offset); + assert(writer.is_valid(), "invariant"); + // We are sitting just after the original number of field_infos + // so this is a position where we can add (append) new field_infos + const u2 number_of_new_fields = add_field_infos(writer, utf8_indexes); + assert(writer.is_valid(), "invariant"); + const jlong new_method_len_offset = writer.current_offset(); + // Additional field_infos added, update classfile fields_count + writer.write_at_offset(orig_fields_len + number_of_new_fields, new_fields_len_offset); + assert(writer.is_valid(), "invariant"); + // Our current location is now at classfile methods_count + const u2 orig_methods_len = position_stream_after_methods(writer, + orig_stream, + utf8_indexes, + register_klass, + clinit_method, + orig_method_len_offset); + const u4 orig_attributes_count_offset = orig_stream->current_offset(); + // Copy existing methods + writer.bytes(orig_stream->buffer() + orig_method_len_offset, orig_attributes_count_offset - orig_method_len_offset); + assert(writer.is_valid(), "invariant"); + // We are sitting just after the original number of method_infos + // so this is a position where we can add (append) new method_infos + u2 number_of_new_methods = add_method_infos(writer, utf8_indexes); + + // We have just added the new methods. + // + // What about the state of ? + // We would need to do: + // 1. Nothing (@Registered(false) annotation) + // 2. Build up a new - and if the original class already contains a , + // merging will be neccessary. + // + if (register_klass) { + insert_clinit_method(ik, parser, writer, orig_cp_len, utf8_indexes, flr_register_method_ref_index, clinit_method, THREAD); + } + number_of_new_methods += clinit_method != NULL ? 0 : register_klass ? 1 : 0; + // Update classfile methods_count + writer.write_at_offset(orig_methods_len + number_of_new_methods, new_method_len_offset); + assert(writer.is_valid(), "invariant"); + // Copy last remaining bytes + writer.bytes(orig_stream->buffer() + orig_attributes_count_offset, orig_stream_size - orig_attributes_count_offset); + assert(writer.is_valid(), "invariant"); + assert(writer.current_offset() > orig_stream->length(), "invariant"); + size_of_new_bytes = (jint)writer.current_offset(); + return new_buffer; +} + +static void log_pending_exception(oop throwable) { + assert(throwable != NULL, "invariant"); + oop msg = java_lang_Throwable::message(throwable); + if (msg != NULL) { + char* text = java_lang_String::as_utf8_string(msg); + if (text != NULL) { + log_error(jfr, system) ("%s", text); + } + } +} + +static bool should_force_instrumentation() { + return !JfrOptionSet::allow_event_retransforms() || JfrEventClassTransformer::is_force_instrumentation(); +} + +static ClassFileStream* create_new_bytes_for_subklass(const InstanceKlass* ik, const ClassFileParser& parser, Thread* t) { + assert(JdkJfrEvent::is_a(ik), "invariant"); + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(t)); + jint size_of_new_bytes = 0; + const u1* new_bytes = new_bytes_for_lazy_instrumentation(ik, parser, size_of_new_bytes, t); + if (new_bytes == NULL) { + return NULL; + } + assert(new_bytes != NULL, "invariant"); + assert(size_of_new_bytes > 0, "invariant"); + + bool force_instrumentation = should_force_instrumentation(); + if (Jfr::is_recording() || force_instrumentation) { + jint size_instrumented_data = 0; + unsigned char* instrumented_data = NULL; + const jclass super = (jclass)JNIHandles::make_local(ik->super()->java_mirror()); + JfrUpcalls::new_bytes_eager_instrumentation(TRACE_ID(ik), + force_instrumentation, + super, + size_of_new_bytes, + new_bytes, + &size_instrumented_data, + &instrumented_data, + t); + if (t->has_pending_exception()) { + log_pending_exception(t->pending_exception()); + t->clear_pending_exception(); + return NULL; + } + assert(instrumented_data != NULL, "invariant"); + assert(size_instrumented_data > 0, "invariant"); + return new ClassFileStream(instrumented_data, size_instrumented_data, NULL, ClassFileStream::verify); + } + return new ClassFileStream(new_bytes, size_of_new_bytes, NULL, ClassFileStream::verify); +} + +static bool cache_bytes(InstanceKlass* ik, ClassFileStream* new_stream, InstanceKlass* new_ik, TRAPS) { + assert(ik != NULL, "invariant"); + assert(new_ik != NULL, "invariant"); + assert(new_ik->name() != NULL, "invariant"); + assert(new_stream != NULL, "invariant"); + assert(!HAS_PENDING_EXCEPTION, "invariant"); + static const bool can_retransform = JfrOptionSet::allow_retransforms(); + if (!can_retransform) { + return true; + } + const jint stream_len = new_stream->length(); + JvmtiCachedClassFileData* p = + (JvmtiCachedClassFileData*)NEW_C_HEAP_ARRAY_RETURN_NULL(u1, offset_of(JvmtiCachedClassFileData, data) + stream_len, mtInternal); + if (p == NULL) { + log_error(jfr, system)("Allocation using C_HEAP_ARRAY for " SIZE_FORMAT + " bytes failed in JfrClassAdapter::on_klass_creation", (size_t)offset_of(JvmtiCachedClassFileData, data) + stream_len); + return false; + } + p->length = stream_len; + memcpy(p->data, new_stream->buffer(), stream_len); + new_ik->set_cached_class_file(p); + JvmtiCachedClassFileData* const cached_class_data = ik->get_cached_class_file(); + if (cached_class_data != NULL) { + os::free(cached_class_data); + ik->set_cached_class_file(NULL); + } + return true; +} + +static InstanceKlass* create_new_instance_klass(InstanceKlass* ik, ClassFileStream* stream, TRAPS) { + assert(stream != NULL, "invariant"); + ResourceMark rm(THREAD); + ClassLoaderData* const cld = ik->class_loader_data(); + Handle pd(THREAD, ik->protection_domain()); + Symbol* const class_name = ik->name(); + const char* const klass_name = class_name != NULL ? class_name->as_C_string() : ""; + ClassFileParser new_parser(stream, + class_name, + cld, + pd, + NULL, // host klass + NULL, // cp_patches + ClassFileParser::INTERNAL, // internal visibility + THREAD); + if (HAS_PENDING_EXCEPTION) { + log_pending_exception(PENDING_EXCEPTION); + CLEAR_PENDING_EXCEPTION; + return NULL; + } + InstanceKlass* const new_ik = new_parser.create_instance_klass(false, THREAD); + if (HAS_PENDING_EXCEPTION) { + log_pending_exception(PENDING_EXCEPTION); + CLEAR_PENDING_EXCEPTION; + return NULL; + } + assert(new_ik != NULL, "invariant"); + assert(new_ik->name() != NULL, "invariant"); + assert(strncmp(ik->name()->as_C_string(), new_ik->name()->as_C_string(), strlen(ik->name()->as_C_string())) == 0, "invariant"); + return cache_bytes(ik, stream, new_ik, THREAD) ? new_ik : NULL; +} + +static void rewrite_klass_pointer(InstanceKlass*& ik, InstanceKlass* new_ik, ClassFileParser& parser, TRAPS) { + assert(ik != NULL, "invariant"); + assert(new_ik != NULL, "invariant"); + assert(new_ik->name() != NULL, "invariant"); + assert(JdkJfrEvent::is(new_ik) || JdkJfrEvent::is_subklass(new_ik), "invariant"); + assert(!HAS_PENDING_EXCEPTION, "invariant"); + // assign original InstanceKlass* back onto "its" parser object for proper destruction + parser.set_klass_to_deallocate(ik); + // now rewrite original pointer to newly created InstanceKlass + ik = new_ik; +} + +// During retransform/redefine, copy the Method specific trace flags +// from the previous ik ("the original klass") to the new ik ("the scratch_klass"). +// The open code for retransform/redefine does not know about these. +// In doing this migration here, we ensure the new Methods (defined in scratch klass) +// will carry over trace tags from the old Methods being replaced, +// ensuring flag/tag continuity while being transparent to open code. +static void copy_method_trace_flags(const InstanceKlass* the_original_klass, const InstanceKlass* the_scratch_klass) { + assert(the_original_klass != NULL, "invariant"); + assert(the_scratch_klass != NULL, "invariant"); + assert(the_original_klass->name() == the_scratch_klass->name(), "invariant"); + const Array* old_methods = the_original_klass->methods(); + const Array* new_methods = the_scratch_klass->methods(); + const bool equal_array_length = old_methods->length() == new_methods->length(); + // The Method array has the property of being sorted. + // If they are the same length, there is a one-to-one mapping. + // If they are unequal, there was a method added (currently only + // private static methods allowed to be added), use lookup. + for (int i = 0; i < old_methods->length(); ++i) { + const Method* const old_method = old_methods->at(i); + Method* const new_method = equal_array_length ? new_methods->at(i) : + the_scratch_klass->find_method(old_method->name(), old_method->signature()); + assert(new_method != NULL, "invariant"); + assert(new_method->name() == old_method->name(), "invariant"); + assert(new_method->signature() == old_method->signature(), "invariant"); + *new_method->trace_flags_addr() = old_method->trace_flags(); + assert(new_method->trace_flags() == old_method->trace_flags(), "invariant"); + } +} + +static bool is_retransforming(const InstanceKlass* ik, TRAPS) { + assert(ik != NULL, "invariant"); + assert(JdkJfrEvent::is_a(ik), "invariant"); + Symbol* const name = ik->name(); + assert(name != NULL, "invariant"); + Handle class_loader(THREAD, ik->class_loader()); + Handle protection_domain(THREAD, ik->protection_domain()); + // nota bene: use lock-free dictionary lookup + const InstanceKlass* prev_ik = (const InstanceKlass*)SystemDictionary::find(name, class_loader, protection_domain, THREAD); + if (prev_ik == NULL) { + return false; + } + // an existing ik implies a retransform/redefine + assert(prev_ik != NULL, "invariant"); + assert(JdkJfrEvent::is_a(prev_ik), "invariant"); + copy_method_trace_flags(prev_ik, ik); + return true; +} + +// target for JFR_ON_KLASS_CREATION hook +void JfrEventClassTransformer::on_klass_creation(InstanceKlass*& ik, ClassFileParser& parser, TRAPS) { + assert(ik != NULL, "invariant"); + if (JdkJfrEvent::is(ik)) { + ResourceMark rm(THREAD); + HandleMark hm(THREAD); + ClassFileStream* new_stream = create_new_bytes_for_event_klass(ik, parser, THREAD); + if (new_stream == NULL) { + log_error(jfr, system)("JfrClassAdapter: unable to create ClassFileStream"); + return; + } + assert(new_stream != NULL, "invariant"); + InstanceKlass* new_ik = create_new_instance_klass(ik, new_stream, THREAD); + if (new_ik == NULL) { + log_error(jfr, system)("JfrClassAdapter: unable to create InstanceKlass"); + return; + } + assert(new_ik != NULL, "invariant"); + // We now need to explicitly tag the replaced klass as the jdk.jfr.Event klass + assert(!JdkJfrEvent::is(new_ik), "invariant"); + JdkJfrEvent::tag_as(new_ik); + assert(JdkJfrEvent::is(new_ik), "invariant"); + rewrite_klass_pointer(ik, new_ik, parser, THREAD); + return; + } + assert(JdkJfrEvent::is_subklass(ik), "invariant"); + if (is_retransforming(ik, THREAD)) { + // not the initial klass load + return; + } + if (ik->is_abstract()) { + // abstract classes are not instrumented + return; + } + ResourceMark rm(THREAD); + HandleMark hm(THREAD); + ClassFileStream* const new_stream = create_new_bytes_for_subklass(ik, parser, THREAD); + if (NULL == new_stream) { + log_error(jfr, system)("JfrClassAdapter: unable to create ClassFileStream"); + return; + } + assert(new_stream != NULL, "invariant"); + InstanceKlass* new_ik = create_new_instance_klass(ik, new_stream, THREAD); + if (new_ik == NULL) { + log_error(jfr, system)("JfrClassAdapter: unable to create InstanceKlass"); + return; + } + assert(new_ik != NULL, "invariant"); + // would have been tagged already as a subklass during the normal process of traceid assignment + assert(JdkJfrEvent::is_subklass(new_ik), "invariant"); + traceid id = ik->trace_id(); + ik->set_trace_id(0); + new_ik->set_trace_id(id); + rewrite_klass_pointer(ik, new_ik, parser, THREAD); +} + +static bool _force_instrumentation = false; +void JfrEventClassTransformer::set_force_instrumentation(bool force_instrumentation) { + _force_instrumentation = force_instrumentation; +} + +bool JfrEventClassTransformer::is_force_instrumentation() { + return _force_instrumentation; +} diff --git a/src/hotspot/share/jfr/instrumentation/jfrEventClassTransformer.hpp b/src/hotspot/share/jfr/instrumentation/jfrEventClassTransformer.hpp new file mode 100644 index 00000000000..1b35fb183d3 --- /dev/null +++ b/src/hotspot/share/jfr/instrumentation/jfrEventClassTransformer.hpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_JFR_INSTRUMENTATION_JFREVENTCLASSTRANSFORMER_HPP +#define SHARE_VM_JFR_INSTRUMENTATION_JFREVENTCLASSTRANSFORMER_HPP + +#include "memory/allocation.hpp" +#include "utilities/exceptions.hpp" + +class ClassFileParser; +class InstanceKlass; + +// +// Intercepts the initial class load of jdk.jfr.Event and subclasses. +// Will replace the sent in InstanceKlass* with a class file schema extended InstanceKlass*. +// +class JfrEventClassTransformer : AllStatic { + public: + static void on_klass_creation(InstanceKlass*& ik, ClassFileParser& parser, TRAPS); + static void set_force_instrumentation(bool force_instrumentation); + static bool is_force_instrumentation(); +}; + +#endif // SHARE_VM_JFR_INSTRUMENTATION_JFREVENTCLASSTRANSFORMER_HPP diff --git a/src/hotspot/share/jfr/instrumentation/jfrJvmtiAgent.cpp b/src/hotspot/share/jfr/instrumentation/jfrJvmtiAgent.cpp new file mode 100644 index 00000000000..3a77db84490 --- /dev/null +++ b/src/hotspot/share/jfr/instrumentation/jfrJvmtiAgent.cpp @@ -0,0 +1,279 @@ +/* + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "jvm.h" +#include "jfr/instrumentation/jfrJvmtiAgent.hpp" +#include "jfr/jni/jfrJavaSupport.hpp" +#include "jfr/jni/jfrUpcalls.hpp" +#include "jfr/recorder/checkpoint/types/traceid/jfrTraceId.inline.hpp" +#include "jfr/recorder/service/jfrOptionSet.hpp" +#include "jfr/support/jfrEventClass.hpp" +#include "logging/log.hpp" +#include "memory/resourceArea.hpp" +#include "prims/jvmtiExport.hpp" +#include "runtime/interfaceSupport.inline.hpp" +#include "runtime/thread.inline.hpp" +#include "utilities/exceptions.hpp" + +static const size_t ERROR_MSG_BUFFER_SIZE = 256; +static JfrJvmtiAgent* agent = NULL; +static jvmtiEnv* jfr_jvmti_env = NULL; + +static void check_jvmti_error(jvmtiEnv* jvmti, jvmtiError errnum, const char* str) { + if (errnum != JVMTI_ERROR_NONE) { + char* errnum_str = NULL; + jvmti->GetErrorName(errnum, &errnum_str); + log_error(jfr, system)("ERROR: JfrJvmtiAgent: " INT32_FORMAT " (%s): %s\n", + errnum, + NULL == errnum_str ? "Unknown" : errnum_str, + NULL == str ? "" : str); + } +} + +static jvmtiError set_event_notification_mode(jvmtiEventMode mode, + jvmtiEvent event, + jthread event_thread, + ...) { + if (jfr_jvmti_env == NULL) { + return JVMTI_ERROR_NONE; + } + const jvmtiError jvmti_ret_code = jfr_jvmti_env->SetEventNotificationMode(mode, event, event_thread); + check_jvmti_error(jfr_jvmti_env, jvmti_ret_code, "SetEventNotificationMode"); + return jvmti_ret_code; +} + +static jvmtiError update_class_file_load_hook_event(jvmtiEventMode mode) { + return set_event_notification_mode(mode, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, NULL); +} + +static JavaThread* current_java_thread() { + Thread* this_thread = Thread::current(); + assert(this_thread != NULL && this_thread->is_Java_thread(), "invariant"); + return static_cast(this_thread); +} + +// jvmti event callbacks require C linkage +extern "C" void JNICALL jfr_on_class_file_load_hook(jvmtiEnv *jvmti_env, + JNIEnv* jni_env, + jclass class_being_redefined, + jobject loader, + const char* name, + jobject protection_domain, + jint class_data_len, + const unsigned char* class_data, + jint* new_class_data_len, + unsigned char** new_class_data) { + if (class_being_redefined == NULL) { + return; + } + JavaThread* jt = JavaThread::thread_from_jni_environment(jni_env); + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_native(jt));; + ThreadInVMfromNative tvmfn(jt); + JfrUpcalls::on_retransform(JfrTraceId::get(class_being_redefined), + class_being_redefined, + class_data_len, + class_data, + new_class_data_len, + new_class_data, + jt); +} + +// caller needs ResourceMark +static jclass* create_classes_array(jint classes_count, TRAPS) { + assert(classes_count > 0, "invariant"); + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_native(THREAD)); + ThreadInVMfromNative tvmfn((JavaThread*)THREAD); + jclass* const classes = NEW_RESOURCE_ARRAY_IN_THREAD_RETURN_NULL(THREAD, jclass, classes_count); + if (NULL == classes) { + char error_buffer[ERROR_MSG_BUFFER_SIZE]; + jio_snprintf(error_buffer, ERROR_MSG_BUFFER_SIZE, + "Thread local allocation (native) of " SIZE_FORMAT " bytes failed " + "in retransform classes", sizeof(jclass) * classes_count); + log_error(jfr, system)("%s", error_buffer); + JfrJavaSupport::throw_out_of_memory_error(error_buffer, CHECK_NULL); + } + return classes; +} + +static void log_and_throw(TRAPS) { + if (!HAS_PENDING_EXCEPTION) { + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_native(THREAD)); + ThreadInVMfromNative tvmfn((JavaThread*)THREAD); + log_error(jfr, system)("JfrJvmtiAgent::retransformClasses failed"); + JfrJavaSupport::throw_class_format_error("JfrJvmtiAgent::retransformClasses failed", THREAD); + } +} + +static void check_exception_and_log(JNIEnv* env, TRAPS) { + assert(env != NULL, "invariant"); + if (env->ExceptionOccurred()) { + // array index out of bound + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_native(THREAD)); + ThreadInVMfromNative tvmfn((JavaThread*)THREAD); + log_error(jfr, system)("GetObjectArrayElement threw an exception"); + return; + } +} + +void JfrJvmtiAgent::retransform_classes(JNIEnv* env, jobjectArray classes_array, TRAPS) { + assert(env != NULL, "invariant"); + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_native(THREAD)); + if (classes_array == NULL) { + return; + } + const jint classes_count = env->GetArrayLength(classes_array); + if (classes_count <= 0) { + return; + } + ResourceMark rm(THREAD); + jclass* const classes = create_classes_array(classes_count, CHECK); + assert(classes != NULL, "invariant"); + for (jint i = 0; i < classes_count; i++) { + jclass clz = (jclass)env->GetObjectArrayElement(classes_array, i); + check_exception_and_log(env, THREAD); + + // inspecting the oop/klass requires a thread transition + { + ThreadInVMfromNative transition((JavaThread*)THREAD); + if (JdkJfrEvent::is_a(clz)) { + // should have been tagged already + assert(JdkJfrEvent::is_subklass(clz), "invariant"); + } else { + // outside the event hierarchy + JdkJfrEvent::tag_as_host(clz); + } + } + + classes[i] = clz; + } + if (jfr_jvmti_env->RetransformClasses(classes_count, classes) != JVMTI_ERROR_NONE) { + log_and_throw(THREAD); + } +} + +static jvmtiError register_callbacks(JavaThread* jt) { + assert(jfr_jvmti_env != NULL, "invariant"); + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_native(jt)); + jvmtiEventCallbacks callbacks; + /* Set callbacks */ + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.ClassFileLoadHook = jfr_on_class_file_load_hook; + const jvmtiError jvmti_ret_code = jfr_jvmti_env->SetEventCallbacks(&callbacks, sizeof(callbacks)); + check_jvmti_error(jfr_jvmti_env, jvmti_ret_code, "SetEventCallbacks"); + return jvmti_ret_code; +} + +static jvmtiError register_capabilities(JavaThread* jt) { + assert(jfr_jvmti_env != NULL, "invariant"); + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_native(jt)); + jvmtiCapabilities capabilities; + /* Add JVMTI capabilities */ + (void)memset(&capabilities, 0, sizeof(capabilities)); + capabilities.can_retransform_classes = 1; + capabilities.can_retransform_any_class = 1; + const jvmtiError jvmti_ret_code = jfr_jvmti_env->AddCapabilities(&capabilities); + check_jvmti_error(jfr_jvmti_env, jvmti_ret_code, "Add Capabilities"); + return jvmti_ret_code; +} + +static jint create_jvmti_env(JavaThread* jt) { + assert(jfr_jvmti_env == NULL, "invariant"); + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_native(jt)); + extern struct JavaVM_ main_vm; + JavaVM* vm = &main_vm; + return vm->GetEnv((void **)&jfr_jvmti_env, JVMTI_VERSION); +} + +static jvmtiError unregister_callbacks(JavaThread* jt) { + if (jfr_jvmti_env == NULL) { + return JVMTI_ERROR_NONE; + } + jvmtiEventCallbacks callbacks; + /* Set empty callbacks */ + memset(&callbacks, 0, sizeof(callbacks)); + const jvmtiError jvmti_ret_code = jfr_jvmti_env->SetEventCallbacks(&callbacks, sizeof(callbacks)); + check_jvmti_error(jfr_jvmti_env, jvmti_ret_code, "SetEventCallbacks"); + return jvmti_ret_code; +} + +JfrJvmtiAgent::JfrJvmtiAgent() {} + +JfrJvmtiAgent::~JfrJvmtiAgent() { + JavaThread* jt = current_java_thread(); + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(jt)); + ThreadToNativeFromVM transition(jt); + update_class_file_load_hook_event(JVMTI_DISABLE); + unregister_callbacks(jt); + if (jfr_jvmti_env != NULL) { + jfr_jvmti_env->DisposeEnvironment(); + jfr_jvmti_env = NULL; + } + agent = NULL; +} + +static bool initialize() { + JavaThread* const jt = current_java_thread(); + assert(jt != NULL, "invariant"); + assert(jt->thread_state() == _thread_in_vm, "invariant"); + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(jt)); + ThreadToNativeFromVM transition(jt); + if (create_jvmti_env(jt) != JNI_OK) { + assert(jfr_jvmti_env == NULL, "invariant"); + return false; + } + assert(jfr_jvmti_env != NULL, "invariant"); + if (register_capabilities(jt) != JVMTI_ERROR_NONE) { + return false; + } + if (register_callbacks(jt) != JVMTI_ERROR_NONE) { + return false; + } + if (update_class_file_load_hook_event(JVMTI_ENABLE) != JVMTI_ERROR_NONE) { + return false; + } + return true; +} + +bool JfrJvmtiAgent::create() { + assert(jfr_jvmti_env == NULL, "invariant"); + agent = new JfrJvmtiAgent(); + if (agent == NULL) { + return false; + } + if (!initialize()) { + delete agent; + agent = NULL; + return false; + } + return true; +} + +void JfrJvmtiAgent::destroy() { + if (agent != NULL) { + delete agent; + agent = NULL; + } +} + diff --git a/src/hotspot/share/jfr/instrumentation/jfrJvmtiAgent.hpp b/src/hotspot/share/jfr/instrumentation/jfrJvmtiAgent.hpp new file mode 100644 index 00000000000..d4ad1fe6da7 --- /dev/null +++ b/src/hotspot/share/jfr/instrumentation/jfrJvmtiAgent.hpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_JFR_INSTRUMENTATION_JFRJVMTIAGENT_HPP +#define SHARE_VM_JFR_INSTRUMENTATION_JFRJVMTIAGENT_HPP + +#include "jfr/utilities/jfrAllocation.hpp" + +class JfrJvmtiAgent : public JfrCHeapObj { + friend class JfrRecorder; + private: + JfrJvmtiAgent(); + ~JfrJvmtiAgent(); + static bool create(); + static void destroy(); + public: + static void retransform_classes(JNIEnv* env, jobjectArray classes, TRAPS); +}; + +#endif // SHARE_VM_JFR_INSTRUMENTATION_JFRJVMTIAGENT_HPP diff --git a/src/hotspot/share/jfr/jfr.cpp b/src/hotspot/share/jfr/jfr.cpp new file mode 100644 index 00000000000..15db5e86c5d --- /dev/null +++ b/src/hotspot/share/jfr/jfr.cpp @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "jfr/jfr.hpp" +#include "jfr/leakprofiler/leakProfiler.hpp" +#include "jfr/periodic/sampling/jfrThreadSampler.hpp" +#include "jfr/recorder/service/jfrOptionSet.hpp" +#include "jfr/recorder/jfrRecorder.hpp" +#include "jfr/recorder/checkpoint/jfrCheckpointManager.hpp" +#include "jfr/recorder/repository/jfrEmergencyDump.hpp" +#include "jfr/support/jfrThreadLocal.hpp" +#include "runtime/java.hpp" + +bool Jfr::is_enabled() { + return JfrRecorder::is_enabled(); +} + +bool Jfr::is_disabled() { + return JfrRecorder::is_disabled(); +} + +bool Jfr::is_recording() { + return JfrRecorder::is_recording(); +} + +void Jfr::on_vm_init() { + if (!JfrRecorder::on_vm_init()) { + vm_exit_during_initialization("Failure when starting JFR on_vm_init"); + } +} + +void Jfr::on_vm_start() { + if (!JfrRecorder::on_vm_start()) { + vm_exit_during_initialization("Failure when starting JFR on_vm_start"); + } +} + +void Jfr::on_unloading_classes() { + if (JfrRecorder::is_created()) { + JfrCheckpointManager::write_type_set_for_unloaded_classes(); + } +} + +void Jfr::on_thread_exit(JavaThread* thread) { + if (JfrRecorder::is_recording()) { + JfrThreadLocal::on_exit(thread); + } +} + +void Jfr::on_thread_destruct(Thread* thread) { + if (JfrRecorder::is_created()) { + JfrThreadLocal::on_destruct(thread); + } +} + +void Jfr::on_vm_shutdown(bool exception_handler) { + if (JfrRecorder::is_recording()) { + JfrEmergencyDump::on_vm_shutdown(exception_handler); + } +} + +void Jfr::weak_oops_do(BoolObjectClosure* is_alive, OopClosure* f) { + LeakProfiler::oops_do(is_alive, f); +} + +bool Jfr::on_start_flight_recording_option(const JavaVMOption** option, char* tail) { + return JfrOptionSet::parse_start_flight_recording_option(option, tail); +} + +bool Jfr::on_flight_recorder_option(const JavaVMOption** option, char* tail) { + return JfrOptionSet::parse_flight_recorder_option(option, tail); +} + +Thread* Jfr::sampler_thread() { + return JfrThreadSampling::sampler_thread(); +} diff --git a/src/hotspot/share/jfr/jfr.hpp b/src/hotspot/share/jfr/jfr.hpp new file mode 100644 index 00000000000..302e8b1eb49 --- /dev/null +++ b/src/hotspot/share/jfr/jfr.hpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_JFR_JFR_HPP +#define SHARE_VM_JFR_JFR_HPP + +#include "jni.h" +#include "memory/allocation.hpp" + +class BoolObjectClosure; +class JavaThread; +class OopClosure; +class Thread; + +extern "C" void JNICALL jfr_register_natives(JNIEnv*, jclass); + +// +// The VM interface to Flight Recorder. +// +class Jfr : AllStatic { + public: + static bool is_enabled(); + static bool is_disabled(); + static bool is_recording(); + static void on_vm_init(); + static void on_vm_start(); + static void on_unloading_classes(); + static void on_thread_exit(JavaThread* thread); + static void on_thread_destruct(Thread* thread); + static void on_vm_shutdown(bool exception_handler = false); + static bool on_start_flight_recording_option(const JavaVMOption** option, char* tail); + static bool on_flight_recorder_option(const JavaVMOption** option, char* tail); + static void weak_oops_do(BoolObjectClosure* is_alive, OopClosure* f); + static Thread* sampler_thread(); +}; + +#endif // SHARE_VM_JFR_JFR_HPP diff --git a/src/hotspot/share/jfr/jfrEvents.hpp b/src/hotspot/share/jfr/jfrEvents.hpp new file mode 100644 index 00000000000..d8ea0079c61 --- /dev/null +++ b/src/hotspot/share/jfr/jfrEvents.hpp @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_JFR_JFREVENTS_HPP +#define SHARE_VM_JFR_JFREVENTS_HPP +/* + * Declare your event in jfr/metadata/metadata.xml. + * + * Include this header to access the machine generated event class. + */ +#include "jfrfiles/jfrEventClasses.hpp" +#include "jfrfiles/jfrEventIds.hpp" + +#endif // SHARE_VM_JFR_JFREVENTS_HPP diff --git a/src/hotspot/share/jfr/jni/jfrGetAllEventClasses.cpp b/src/hotspot/share/jfr/jni/jfrGetAllEventClasses.cpp new file mode 100644 index 00000000000..2854c2c30c1 --- /dev/null +++ b/src/hotspot/share/jfr/jni/jfrGetAllEventClasses.cpp @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "classfile/javaClasses.inline.hpp" +#include "classfile/symbolTable.hpp" +#include "jfr/jni/jfrGetAllEventClasses.hpp" +#include "jfr/jni/jfrJavaSupport.hpp" +#include "jfr/support/jfrEventClass.hpp" +#include "oops/instanceKlass.hpp" +#include "memory/allocation.inline.hpp" +#include "memory/resourceArea.hpp" +#include "runtime/handles.inline.hpp" +#include "runtime/mutexLocker.hpp" +#include "runtime/safepoint.hpp" +#include "runtime/thread.inline.hpp" +#include "utilities/growableArray.hpp" +#include "utilities/stack.inline.hpp" + + // incremented during class unloading (safepoint) for each unloaded event class +static jlong unloaded_event_classes = 0; + +jlong JfrEventClasses::unloaded_event_classes_count() { + return unloaded_event_classes; +} + +void JfrEventClasses::increment_unloaded_event_class() { + // incremented during class unloading (safepoint) for each unloaded event class + assert(SafepointSynchronize::is_at_safepoint(), "invariant"); + ++unloaded_event_classes; +} + +static jobject empty_java_util_arraylist = NULL; + +static oop new_java_util_arraylist(TRAPS) { + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD)); + JavaValue result(T_OBJECT); + JfrJavaArguments args(&result, "java/util/ArrayList", "", "()V", CHECK_NULL); + JfrJavaSupport::new_object(&args, CHECK_NULL); + return (oop)result.get_jobject(); +} + +static bool initialize(TRAPS) { + static bool initialized = false; + if (!initialized) { + unloaded_event_classes = 0; + assert(NULL == empty_java_util_arraylist, "invariant"); + const oop array_list = new_java_util_arraylist(CHECK_false); + empty_java_util_arraylist = JfrJavaSupport::global_jni_handle(array_list, THREAD); + initialized = empty_java_util_arraylist != NULL; + } + return initialized; +} + +/* + * Abstract klasses are filtered out unconditionally. + * If a klass is not yet initialized, i.e yet to run its + * it is also filtered out so we don't accidentally + * trigger initialization. + */ +static bool is_whitelisted(const Klass* k) { + assert(k != NULL, "invariant"); + return !(k->is_abstract() || k->should_be_initialized()); +} + +static void fill_klasses(GrowableArray& event_subklasses, const Klass* event_klass, Thread* thread) { + assert(event_subklasses.length() == 0, "invariant"); + assert(event_klass != NULL, "invariant"); + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(thread)); + + Stack mark_stack; + MutexLocker ml(Compile_lock, thread); + mark_stack.push(event_klass->subklass()); + + while (!mark_stack.is_empty()) { + const Klass* const current = mark_stack.pop(); + assert(current != NULL, "null element in stack!"); + + if (is_whitelisted(current)) { + event_subklasses.append(current); + } + + // subclass (depth) + const Klass* next_klass = current->subklass(); + if (next_klass != NULL) { + mark_stack.push(next_klass); + } + + // siblings (breadth) + next_klass = current->next_sibling(); + if (next_klass != NULL) { + mark_stack.push(next_klass); + } + } + assert(mark_stack.is_empty(), "invariant"); +} + + static void transform_klasses_to_local_jni_handles(GrowableArray& event_subklasses, Thread* thread) { + assert(event_subklasses.is_nonempty(), "invariant"); + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(thread)); + + for (int i = 0; i < event_subklasses.length(); ++i) { + const InstanceKlass* k = static_cast(event_subklasses.at(i)); + assert(is_whitelisted(k), "invariant"); + event_subklasses.at_put(i, JfrJavaSupport::local_jni_handle(k->java_mirror(), thread)); + } +} + +static const int initial_size_growable_array = 64; + +jobject JfrEventClasses::get_all_event_classes(TRAPS) { + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD)); + initialize(THREAD); + assert(empty_java_util_arraylist != NULL, "should have been setup already!"); + static const char jdk_jfr_event_name[] = "jdk/jfr/Event"; + unsigned int unused_hash = 0; + Symbol* const event_klass_name = SymbolTable::lookup_only(jdk_jfr_event_name, sizeof jdk_jfr_event_name - 1, unused_hash); + + if (NULL == event_klass_name) { + // not loaded yet + return empty_java_util_arraylist; + } + + const Klass* const klass = SystemDictionary::resolve_or_null(event_klass_name, THREAD); + assert(klass != NULL, "invariant"); + assert(JdkJfrEvent::is(klass), "invariant"); + + if (klass->subklass() == NULL) { + return empty_java_util_arraylist; + } + + ResourceMark rm(THREAD); + GrowableArray event_subklasses(THREAD, initial_size_growable_array); + fill_klasses(event_subklasses, klass, THREAD); + + if (event_subklasses.is_empty()) { + return empty_java_util_arraylist; + } + + transform_klasses_to_local_jni_handles(event_subklasses, THREAD); + + Handle h_array_list(THREAD, new_java_util_arraylist(THREAD)); + assert(h_array_list.not_null(), "invariant"); + + static const char add_method_name[] = "add"; + static const char add_method_signature[] = "(Ljava/lang/Object;)Z"; + const Klass* const array_list_klass = JfrJavaSupport::klass(empty_java_util_arraylist); + assert(array_list_klass != NULL, "invariant"); + + const Symbol* const add_method_sym = SymbolTable::lookup(add_method_name, sizeof add_method_name - 1, THREAD); + assert(add_method_sym != NULL, "invariant"); + + const Symbol* const add_method_sig_sym = SymbolTable::lookup(add_method_signature, sizeof add_method_signature - 1, THREAD); + assert(add_method_signature != NULL, "invariant"); + + JavaValue result(T_BOOLEAN); + for (int i = 0; i < event_subklasses.length(); ++i) { + const jclass clazz = (const jclass)event_subklasses.at(i); + assert(JdkJfrEvent::is_subklass(clazz), "invariant"); + JfrJavaArguments args(&result, array_list_klass, add_method_sym, add_method_sig_sym); + args.set_receiver(h_array_list()); + args.push_jobject(clazz); + JfrJavaSupport::call_virtual(&args, THREAD); + if (HAS_PENDING_EXCEPTION || JNI_FALSE == result.get_jboolean()) { + return empty_java_util_arraylist; + } + } + return JfrJavaSupport::local_jni_handle(h_array_list(), THREAD); +} diff --git a/src/hotspot/share/jfr/jni/jfrGetAllEventClasses.hpp b/src/hotspot/share/jfr/jni/jfrGetAllEventClasses.hpp new file mode 100644 index 00000000000..a94a0b0eb82 --- /dev/null +++ b/src/hotspot/share/jfr/jni/jfrGetAllEventClasses.hpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +#ifndef SHARE_VM_JFR_JNI_JFRGETALLEVENTCLASSES_HPP +#define SHARE_VM_JFR_JNI_JFRGETALLEVENTCLASSES_HPP + +#include "jni.h" +#include "memory/allocation.hpp" +#include "utilities/exceptions.hpp" + +// +// Responsible for the delivery of currently loaded jdk.jfr.Event subklasses to Java. +// +class JfrEventClasses : AllStatic { + public: + static void increment_unloaded_event_class(); + static jlong unloaded_event_classes_count(); + static jobject get_all_event_classes(TRAPS); +}; + +#endif // SHARE_VM_JFR_JNI_JFRGETALLEVENTCLASSES_HPP diff --git a/src/hotspot/share/jfr/jni/jfrJavaCall.cpp b/src/hotspot/share/jfr/jni/jfrJavaCall.cpp new file mode 100644 index 00000000000..77a2f8c89f2 --- /dev/null +++ b/src/hotspot/share/jfr/jni/jfrJavaCall.cpp @@ -0,0 +1,380 @@ +/* + * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "classfile/symbolTable.hpp" +#include "classfile/systemDictionary.hpp" +#include "jfr/jni/jfrJavaCall.hpp" +#include "jfr/jni/jfrJavaSupport.hpp" +#include "memory/resourceArea.hpp" +#include "runtime/handles.inline.hpp" +#include "runtime/javaCalls.hpp" +#include "utilities/globalDefinitions.hpp" + +#ifdef ASSERT +static bool is_large_value(const JavaValue& value) { + return value.get_type() == T_LONG || value.get_type() == T_DOUBLE; +} +#endif // ASSERT + +static Symbol* resolve(const char* str, TRAPS) { + assert(str != NULL, "invariant"); + return SymbolTable::lookup(str, (int)strlen(str), THREAD); +} + +static Klass* resolve(Symbol* k_sym, TRAPS) { + assert(k_sym != NULL, "invariant"); + return SystemDictionary::resolve_or_fail(k_sym, true, THREAD); +} + +JfrJavaArguments::Parameters::Parameters() : _storage_index(0), _java_stack_slots(0) { + JavaValue value(T_VOID); + push(value); +} + +void JfrJavaArguments::Parameters::push(const JavaValue& value) { + assert(_storage != NULL, "invariant"); + assert(!is_large_value(value), "invariant"); + assert(_storage_index < SIZE, "invariant"); + _storage[_storage_index++] = value; + _java_stack_slots++; +} + +void JfrJavaArguments::Parameters::push_large(const JavaValue& value) { + assert(_storage != NULL, "invariant"); + assert(is_large_value(value), "invariant"); + assert(_storage_index < SIZE, "invariant"); + _storage[_storage_index++] = value; + _java_stack_slots += 2; +} + +void JfrJavaArguments::Parameters::set_receiver(const oop receiver) { + assert(_storage != NULL, "invariant"); + assert(receiver != NULL, "invariant"); + JavaValue value(T_OBJECT); + value.set_jobject((jobject)receiver); + _storage[0] = value; +} + +void JfrJavaArguments::Parameters::set_receiver(Handle receiver) { + set_receiver(receiver()); +} + +oop JfrJavaArguments::Parameters::receiver() const { + assert(has_receiver(), "invariant"); + assert(_storage[0].get_type() == T_OBJECT, "invariant"); + return (oop)_storage[0].get_jobject(); +} + +bool JfrJavaArguments::Parameters::has_receiver() const { + assert(_storage != NULL, "invariant"); + assert(_storage_index >= 1, "invariant"); + assert(_java_stack_slots >= 1, "invariant"); + return _storage[0].get_type() == T_OBJECT; +} + +void JfrJavaArguments::Parameters::push_oop(const oop obj) { + JavaValue value(T_OBJECT); + value.set_jobject((jobject)obj); + push(value); +} + +void JfrJavaArguments::Parameters::push_oop(Handle h_obj) { + push_oop(h_obj()); +} + +void JfrJavaArguments::Parameters::push_jobject(jobject h) { + JavaValue value(T_ADDRESS); + value.set_jobject(h); + push(value); +} + +void JfrJavaArguments::Parameters::push_jint(jint i) { + JavaValue value(T_INT); + value.set_jint(i); + push(value); +} + +void JfrJavaArguments::Parameters::push_jfloat(jfloat f) { + JavaValue value(T_FLOAT); + value.set_jfloat(f); + push(value); +} + +void JfrJavaArguments::Parameters::push_jdouble(jdouble d) { + JavaValue value(T_DOUBLE); + value.set_jdouble(d); + push_large(value); +} + +void JfrJavaArguments::Parameters::push_jlong(jlong l) { + JavaValue value(T_LONG); + value.set_jlong(l); + push_large(value); +} + +// including receiver (even if there is none) +inline int JfrJavaArguments::Parameters::length() const { + assert(_storage_index >= 1, "invariant"); + return _storage_index; +} + +inline int JfrJavaArguments::Parameters::java_stack_slots() const { + return _java_stack_slots; +} + +const JavaValue& JfrJavaArguments::Parameters::values(int idx) const { + assert(idx >= 0, "invariant"); + assert(idx < SIZE, "invariant"); + return _storage[idx]; +} + +void JfrJavaArguments::Parameters::copy(JavaCallArguments& args, TRAPS) const { + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD)); + if (has_receiver()) { + args.set_receiver(Handle(THREAD, receiver())); + } + for (int i = 1; i < length(); ++i) { + switch(values(i).get_type()) { + case T_BOOLEAN: + case T_CHAR: + case T_SHORT: + case T_INT: + args.push_int(values(i).get_jint()); + break; + case T_LONG: + args.push_long(values(i).get_jlong()); + break; + case T_FLOAT: + args.push_float(values(i).get_jfloat()); + break; + case T_DOUBLE: + args.push_double(values(i).get_jdouble()); + break; + case T_OBJECT: + args.push_oop(Handle(THREAD, (oop)values(i).get_jobject())); + break; + case T_ADDRESS: + args.push_jobject(values(i).get_jobject()); + break; + default: + ShouldNotReachHere(); + } + } +} + +JfrJavaArguments::JfrJavaArguments(JavaValue* result) : _result(result), _klass(NULL), _name(NULL), _signature(NULL), _array_length(0) { + assert(result != NULL, "invariant"); +} + +JfrJavaArguments::JfrJavaArguments(JavaValue* result, const char* klass_name, const char* name, const char* signature, TRAPS) : + _result(result), + _klass(NULL), + _name(NULL), + _signature(NULL), + _array_length(0) { + assert(result != NULL, "invariant"); + if (klass_name != NULL) { + set_klass(klass_name, CHECK); + } + if (name != NULL) { + set_name(name, CHECK); + } + if (signature != NULL) { + set_signature(signature, THREAD); + } +} + +JfrJavaArguments::JfrJavaArguments(JavaValue* result, const Klass* klass, const Symbol* name, const Symbol* signature) : _result(result), + _klass(NULL), + _name(NULL), + _signature(NULL), + _array_length(0) { + assert(result != NULL, "invariant"); + if (klass != NULL) { + set_klass(klass); + } + if (name != NULL) { + set_name(name); + } + if (signature != NULL) { + set_signature(signature); + } +} + +Klass* JfrJavaArguments::klass() const { + assert(_klass != NULL, "invariant"); + return const_cast(_klass); +} + +void JfrJavaArguments::set_klass(const char* klass_name, TRAPS) { + assert(klass_name != NULL, "invariant"); + Symbol* const k_sym = resolve(klass_name, CHECK); + assert(k_sym != NULL, "invariant"); + const Klass* const klass = resolve(k_sym, CHECK); + set_klass(klass); +} + +void JfrJavaArguments::set_klass(const Klass* klass) { + assert(klass != NULL, "invariant"); + _klass = klass; +} + +Symbol* JfrJavaArguments::name() const { + assert(_name != NULL, "invariant"); + return const_cast(_name); +} + +void JfrJavaArguments::set_name(const char* name, TRAPS) { + assert(name != NULL, "invariant"); + const Symbol* const sym = resolve(name, CHECK); + set_name(sym); +} + +void JfrJavaArguments::set_name(const Symbol* name) { + assert(name != NULL, "invariant"); + _name = name; +} + +Symbol* JfrJavaArguments::signature() const { + assert(_signature != NULL, "invariant"); + return const_cast(_signature); +} + +void JfrJavaArguments::set_signature(const char* signature, TRAPS) { + assert(signature != NULL, "invariant"); + const Symbol* const sym = resolve(signature, CHECK); + set_signature(sym); +} + +void JfrJavaArguments::set_signature(const Symbol* signature) { + assert(signature != NULL, "invariant"); + _signature = signature; +} + +int JfrJavaArguments::array_length() const { + return _array_length; +} + +void JfrJavaArguments::set_array_length(int length) { + assert(length >= 0, "invariant"); + _array_length = length; +} + +JavaValue* JfrJavaArguments::result() const { + assert(_result != NULL, "invariant"); + return const_cast(_result); +} + +int JfrJavaArguments::length() const { + return _params.length(); +} + +bool JfrJavaArguments::has_receiver() const { + return _params.has_receiver(); +} + +oop JfrJavaArguments::receiver() const { + return _params.receiver(); +} + +void JfrJavaArguments::set_receiver(const oop receiver) { + _params.set_receiver(receiver); +} + +void JfrJavaArguments::set_receiver(Handle receiver) { + _params.set_receiver(receiver); +} + +void JfrJavaArguments::push_oop(const oop obj) { + _params.push_oop(obj); +} + +void JfrJavaArguments::push_oop(Handle h_obj) { + _params.push_oop(h_obj); +} + +void JfrJavaArguments::push_jobject(jobject h) { + _params.push_jobject(h); +} + +void JfrJavaArguments::push_int(jint i) { + _params.push_jint(i); +} + +void JfrJavaArguments::push_float(jfloat f) { + _params.push_jfloat(f); +} + +void JfrJavaArguments::push_double(jdouble d) { + _params.push_jdouble(d); +} + +void JfrJavaArguments::push_long(jlong l) { + _params.push_jlong(l); +} + +const JavaValue& JfrJavaArguments::param(int idx) const { + return _params.values(idx); +} + +int JfrJavaArguments::java_call_arg_slots() const { + return _params.java_stack_slots(); +} + +void JfrJavaArguments::copy(JavaCallArguments& args, TRAPS) { + _params.copy(args, THREAD); +} + +void JfrJavaCall::call_static(JfrJavaArguments* args, TRAPS) { + assert(args != NULL, "invariant"); + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD)); + ResourceMark rm(THREAD); + HandleMark hm(THREAD); + JavaCallArguments jcas(args->java_call_arg_slots()); + args->copy(jcas, CHECK); + JavaCalls::call_static(args->result(), args->klass(), args->name(), args->signature(), &jcas, THREAD); +} + +void JfrJavaCall::call_special(JfrJavaArguments* args, TRAPS) { + assert(args != NULL, "invariant"); + assert(args->has_receiver(), "invariant"); + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD)); + ResourceMark rm(THREAD); + HandleMark hm(THREAD); + JavaCallArguments jcas(args->java_call_arg_slots()); + args->copy(jcas, CHECK); + JavaCalls::call_special(args->result(), args->klass(), args->name(), args->signature(), &jcas, THREAD); +} + +void JfrJavaCall::call_virtual(JfrJavaArguments* args, TRAPS) { + assert(args != NULL, "invariant"); + assert(args->has_receiver(), "invariant"); + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD)); + ResourceMark rm(THREAD); + HandleMark hm(THREAD); + JavaCallArguments jcas(args->java_call_arg_slots()); + args->copy(jcas, CHECK); + JavaCalls::call_virtual(args->result(), args->klass(), args->name(), args->signature(), &jcas, THREAD); +} diff --git a/src/hotspot/share/jfr/jni/jfrJavaCall.hpp b/src/hotspot/share/jfr/jni/jfrJavaCall.hpp new file mode 100644 index 00000000000..89b2f7b98dd --- /dev/null +++ b/src/hotspot/share/jfr/jni/jfrJavaCall.hpp @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_JFR_JNI_JFRJAVACALL_HPP +#define SHARE_VM_JFR_JNI_JFRJAVACALL_HPP + +#include "jni.h" +#include "jfr/utilities/jfrAllocation.hpp" +#include "utilities/exceptions.hpp" + +class JavaCallArguments; +class JavaThread; +class JavaValue; +class Klass; +class Symbol; + +class JfrJavaArguments : public StackObj { + friend class JfrJavaCall; + public: + JfrJavaArguments(JavaValue* result); + JfrJavaArguments(JavaValue* result, const char* klass_name, const char* name, const char* signature, TRAPS); + JfrJavaArguments(JavaValue* result, const Klass* klass, const Symbol* name, const Symbol* signature); + + Klass* klass() const; + void set_klass(const char* klass_name, TRAPS); + void set_klass(const Klass* klass); + + Symbol* name() const; + void set_name(const char* name, TRAPS); + void set_name(const Symbol* name); + + Symbol* signature() const; + void set_signature(const char* signature, TRAPS); + void set_signature(const Symbol* signature); + + int array_length() const; + void set_array_length(int length); + + JavaValue* result() const; + + bool has_receiver() const; + void set_receiver(const oop receiver); + void set_receiver(Handle receiver); + oop receiver() const; + + // parameters + void push_oop(const oop obj); + void push_oop(Handle h_obj); + void push_jobject(jobject h); + void push_int(jint i); + void push_double(jdouble d); + void push_long(jlong l); + void push_float(jfloat f); + + int length() const; + const JavaValue& param(int idx) const; + + private: + class Parameters { + friend class JfrJavaArguments; + private: + enum { SIZE = 16}; + JavaValue _storage[SIZE]; + int _storage_index; + int _java_stack_slots; + + Parameters(); + Parameters(const Parameters&); // no impl + Parameters& operator=(const Parameters&); // no impl + + void push(const JavaValue& value); + void push_large(const JavaValue& value); + + void push_oop(const oop obj); + void push_oop(Handle h_obj); + void push_jobject(jobject h); + void push_jint(jint i); + void push_jdouble(jdouble d); + void push_jlong(jlong l); + void push_jfloat(jfloat f); + + bool has_receiver() const; + void set_receiver(const oop receiver); + void set_receiver(Handle receiver); + oop receiver() const; + + int length() const; + int java_stack_slots() const; + + void copy(JavaCallArguments& args, TRAPS) const; + const JavaValue& values(int idx) const; + }; + + Parameters _params; + const JavaValue* const _result; + const Klass* _klass; + const Symbol* _name; + const Symbol* _signature; + int _array_length; + + int java_call_arg_slots() const; + void copy(JavaCallArguments& args, TRAPS); +}; + +class JfrJavaCall : public AllStatic { + friend class JfrJavaSupport; + private: + static void call_static(JfrJavaArguments* args, TRAPS); + static void call_special(JfrJavaArguments* args, TRAPS); + static void call_virtual(JfrJavaArguments* args, TRAPS); +}; + +#endif // SHARE_VM_JFR_JNI_JFRJAVACALL_HPP diff --git a/src/hotspot/share/jfr/jni/jfrJavaSupport.cpp b/src/hotspot/share/jfr/jni/jfrJavaSupport.cpp new file mode 100644 index 00000000000..d1478391bda --- /dev/null +++ b/src/hotspot/share/jfr/jni/jfrJavaSupport.cpp @@ -0,0 +1,612 @@ +/* + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "jni.h" +#include "classfile/javaClasses.inline.hpp" +#include "classfile/modules.hpp" +#include "classfile/symbolTable.hpp" +#include "classfile/systemDictionary.hpp" +#include "classfile/vmSymbols.hpp" +#include "jfr/jni/jfrJavaCall.hpp" +#include "jfr/jni/jfrJavaSupport.hpp" +#include "jfr/support/jfrThreadId.hpp" +#include "logging/log.hpp" +#include "memory/resourceArea.hpp" +#include "oops/instanceOop.hpp" +#include "oops/oop.inline.hpp" +#include "oops/objArrayKlass.hpp" +#include "oops/objArrayOop.inline.hpp" +#include "runtime/handles.inline.hpp" +#include "runtime/fieldDescriptor.hpp" +#include "runtime/java.hpp" +#include "runtime/jniHandles.inline.hpp" +#include "runtime/synchronizer.hpp" +#include "runtime/thread.inline.hpp" +#include "runtime/threadSMR.hpp" + +#ifdef ASSERT +void JfrJavaSupport::check_java_thread_in_vm(Thread* t) { + assert(t != NULL, "invariant"); + assert(t->is_Java_thread(), "invariant"); + assert(((JavaThread*)t)->thread_state() == _thread_in_vm, "invariant"); +} + +void JfrJavaSupport::check_java_thread_in_native(Thread* t) { + assert(t != NULL, "invariant"); + assert(t->is_Java_thread(), "invariant"); + assert(((JavaThread*)t)->thread_state() == _thread_in_native, "invariant"); +} +#endif + +/* + * Handles and references + */ +jobject JfrJavaSupport::local_jni_handle(const oop obj, Thread* t) { + DEBUG_ONLY(check_java_thread_in_vm(t)); + return t->active_handles()->allocate_handle(obj); +} + +jobject JfrJavaSupport::local_jni_handle(const jobject handle, Thread* t) { + DEBUG_ONLY(check_java_thread_in_vm(t)); + const oop obj = JNIHandles::resolve(handle); + return obj == NULL ? NULL : local_jni_handle(obj, t); +} + +void JfrJavaSupport::destroy_local_jni_handle(jobject handle) { + JNIHandles::destroy_local(handle); +} + +jobject JfrJavaSupport::global_jni_handle(const oop obj, Thread* t) { + DEBUG_ONLY(check_java_thread_in_vm(t)); + HandleMark hm(t); + return JNIHandles::make_global(Handle(t, obj)); +} + +jobject JfrJavaSupport::global_jni_handle(const jobject handle, Thread* t) { + const oop obj = JNIHandles::resolve(handle); + return obj == NULL ? NULL : global_jni_handle(obj, t); +} + +void JfrJavaSupport::destroy_global_jni_handle(jobject handle) { + JNIHandles::destroy_global(handle); +} + +oop JfrJavaSupport::resolve_non_null(jobject obj) { + return JNIHandles::resolve_non_null(obj); +} + +/* + * Method invocation + */ +void JfrJavaSupport::call_static(JfrJavaArguments* args, TRAPS) { + JfrJavaCall::call_static(args, THREAD); +} + +void JfrJavaSupport::call_special(JfrJavaArguments* args, TRAPS) { + JfrJavaCall::call_special(args, THREAD); +} + +void JfrJavaSupport::call_virtual(JfrJavaArguments* args, TRAPS) { + JfrJavaCall::call_virtual(args, THREAD); +} + +void JfrJavaSupport::notify_all(jobject object, TRAPS) { + assert(object != NULL, "invariant"); + DEBUG_ONLY(check_java_thread_in_vm(THREAD)); + HandleMark hm(THREAD); + Handle h_obj(THREAD, resolve_non_null(object)); + assert(h_obj.not_null(), "invariant"); + ObjectSynchronizer::jni_enter(h_obj, THREAD); + ObjectSynchronizer::notifyall(h_obj, THREAD); + ObjectSynchronizer::jni_exit(h_obj(), THREAD); + DEBUG_ONLY(check_java_thread_in_vm(THREAD)); +} + +/* + * Object construction + */ +static void object_construction(JfrJavaArguments* args, JavaValue* result, InstanceKlass* klass, TRAPS) { + assert(args != NULL, "invariant"); + assert(result != NULL, "invariant"); + assert(klass != NULL, "invariant"); + assert(klass->is_initialized(), "invariant"); + + HandleMark hm(THREAD); + instanceOop obj = klass->allocate_instance(CHECK); + instanceHandle h_obj(THREAD, obj); + assert(h_obj.not_null(), "invariant"); + args->set_receiver(h_obj); + result->set_type(T_VOID); // constructor result type + JfrJavaSupport::call_special(args, CHECK); + result->set_type(T_OBJECT); // set back to original result type + result->set_jobject((jobject)h_obj()); +} + +static void array_construction(JfrJavaArguments* args, JavaValue* result, InstanceKlass* klass, int array_length, TRAPS) { + assert(args != NULL, "invariant"); + assert(result != NULL, "invariant"); + assert(klass != NULL, "invariant"); + assert(klass->is_initialized(), "invariant"); + + Klass* const ak = klass->array_klass(THREAD); + ObjArrayKlass::cast(ak)->initialize(THREAD); + HandleMark hm(THREAD); + objArrayOop arr = ObjArrayKlass::cast(ak)->allocate(array_length, CHECK); + result->set_jobject((jobject)arr); +} + +static void create_object(JfrJavaArguments* args, JavaValue* result, TRAPS) { + assert(args != NULL, "invariant"); + assert(result != NULL, "invariant"); + assert(result->get_type() == T_OBJECT, "invariant"); + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD)); + + InstanceKlass* const klass = static_cast(args->klass()); + klass->initialize(CHECK); + + const int array_length = args->array_length(); + + if (array_length > 0) { + array_construction(args, result, klass, array_length, CHECK); + } else { + object_construction(args, result, klass, THREAD); + } +} + +static void handle_result(JavaValue* result, bool global_ref, Thread* t) { + assert(result != NULL, "invariant"); + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(t)); + const oop result_oop = (const oop)result->get_jobject(); + if (result_oop == NULL) { + return; + } + result->set_jobject(global_ref ? + JfrJavaSupport::global_jni_handle(result_oop, t) : + JfrJavaSupport::local_jni_handle(result_oop, t)); +} + +void JfrJavaSupport::new_object(JfrJavaArguments* args, TRAPS) { + assert(args != NULL, "invariant"); + DEBUG_ONLY(check_java_thread_in_vm(THREAD)); + create_object(args, args->result(), THREAD); +} + +void JfrJavaSupport::new_object_local_ref(JfrJavaArguments* args, TRAPS) { + assert(args != NULL, "invariant"); + DEBUG_ONLY(check_java_thread_in_vm(THREAD)); + JavaValue* const result = args->result(); + assert(result != NULL, "invariant"); + create_object(args, result, CHECK); + handle_result(result, false, THREAD); +} + +void JfrJavaSupport::new_object_global_ref(JfrJavaArguments* args, TRAPS) { + assert(args != NULL, "invariant"); + DEBUG_ONLY(check_java_thread_in_vm(THREAD)); + JavaValue* const result = args->result(); + assert(result != NULL, "invariant"); + create_object(args, result, CHECK); + handle_result(result, true, THREAD); +} + +jstring JfrJavaSupport::new_string(const char* c_str, TRAPS) { + assert(c_str != NULL, "invariant"); + DEBUG_ONLY(check_java_thread_in_vm(THREAD)); + const oop result = java_lang_String::create_oop_from_str(c_str, THREAD); + return (jstring)local_jni_handle(result, THREAD); +} + +jobjectArray JfrJavaSupport::new_string_array(int length, TRAPS) { + DEBUG_ONLY(check_java_thread_in_vm(THREAD)); + JavaValue result(T_OBJECT); + JfrJavaArguments args(&result, "java/lang/String", "", "()V", CHECK_NULL); + args.set_array_length(length); + new_object_local_ref(&args, THREAD); + return (jobjectArray)args.result()->get_jobject(); +} + +jobject JfrJavaSupport::new_java_lang_Boolean(bool value, TRAPS) { + DEBUG_ONLY(check_java_thread_in_vm(THREAD)); + JavaValue result(T_OBJECT); + JfrJavaArguments args(&result, "java/lang/Boolean", "", "(Z)V", CHECK_NULL); + args.push_int(value ? (jint)JNI_TRUE : (jint)JNI_FALSE); + new_object_local_ref(&args, THREAD); + return args.result()->get_jobject(); +} + +jobject JfrJavaSupport::new_java_lang_Integer(jint value, TRAPS) { + DEBUG_ONLY(check_java_thread_in_vm(THREAD)); + JavaValue result(T_OBJECT); + JfrJavaArguments args(&result, "java/lang/Integer", "", "(I)V", CHECK_NULL); + args.push_int(value); + new_object_local_ref(&args, THREAD); + return args.result()->get_jobject(); +} + +jobject JfrJavaSupport::new_java_lang_Long(jlong value, TRAPS) { + DEBUG_ONLY(check_java_thread_in_vm(THREAD)); + JavaValue result(T_OBJECT); + JfrJavaArguments args(&result, "java/lang/Long", "", "(J)V", CHECK_NULL); + args.push_long(value); + new_object_local_ref(&args, THREAD); + return args.result()->get_jobject(); +} + +void JfrJavaSupport::set_array_element(jobjectArray arr, jobject element, int index, Thread* t) { + assert(arr != NULL, "invariant"); + DEBUG_ONLY(check_java_thread_in_vm(t)); + HandleMark hm(t); + objArrayHandle a(t, (objArrayOop)resolve_non_null(arr)); + a->obj_at_put(index, resolve_non_null(element)); +} + +/* + * Field access + */ +static void write_int_field(const Handle& h_oop, fieldDescriptor* fd, jint value) { + assert(h_oop.not_null(), "invariant"); + assert(fd != NULL, "invariant"); + h_oop->int_field_put(fd->offset(), value); +} + +static void write_float_field(const Handle& h_oop, fieldDescriptor* fd, jfloat value) { + assert(h_oop.not_null(), "invariant"); + assert(fd != NULL, "invariant"); + h_oop->float_field_put(fd->offset(), value); +} + +static void write_double_field(const Handle& h_oop, fieldDescriptor* fd, jdouble value) { + assert(h_oop.not_null(), "invariant"); + assert(fd != NULL, "invariant"); + h_oop->double_field_put(fd->offset(), value); +} + +static void write_long_field(const Handle& h_oop, fieldDescriptor* fd, jlong value) { + assert(h_oop.not_null(), "invariant"); + assert(fd != NULL, "invariant"); + h_oop->long_field_put(fd->offset(), value); +} + +static void write_oop_field(const Handle& h_oop, fieldDescriptor* fd, const oop value) { + assert(h_oop.not_null(), "invariant"); + assert(fd != NULL, "invariant"); + h_oop->obj_field_put(fd->offset(), value); +} + +static void write_specialized_field(JfrJavaArguments* args, const Handle& h_oop, fieldDescriptor* fd, bool static_field) { + assert(args != NULL, "invariant"); + assert(h_oop.not_null(), "invariant"); + assert(fd != NULL, "invariant"); + assert(fd->offset() > 0, "invariant"); + assert(args->length() >= 1, "invariant"); + + // attempt must set a real value + assert(args->param(1).get_type() != T_VOID, "invariant"); + + switch(fd->field_type()) { + case T_BOOLEAN: + case T_CHAR: + case T_SHORT: + case T_INT: + write_int_field(h_oop, fd, args->param(1).get_jint()); + break; + case T_FLOAT: + write_float_field(h_oop, fd, args->param(1).get_jfloat()); + break; + case T_DOUBLE: + write_double_field(h_oop, fd, args->param(1).get_jdouble()); + break; + case T_LONG: + write_long_field(h_oop, fd, args->param(1).get_jlong()); + break; + case T_OBJECT: + write_oop_field(h_oop, fd, (oop)args->param(1).get_jobject()); + break; + case T_ADDRESS: + write_oop_field(h_oop, fd, JfrJavaSupport::resolve_non_null(args->param(1).get_jobject())); + break; + default: + ShouldNotReachHere(); + } +} + +static void read_specialized_field(JavaValue* result, const Handle& h_oop, fieldDescriptor* fd) { + assert(result != NULL, "invariant"); + assert(h_oop.not_null(), "invariant"); + assert(fd != NULL, "invariant"); + assert(fd->offset() > 0, "invariant"); + + switch(fd->field_type()) { + case T_BOOLEAN: + case T_CHAR: + case T_SHORT: + case T_INT: + result->set_jint(h_oop->int_field(fd->offset())); + break; + case T_FLOAT: + result->set_jfloat(h_oop->float_field(fd->offset())); + break; + case T_DOUBLE: + result->set_jdouble(h_oop->double_field(fd->offset())); + break; + case T_LONG: + result->set_jlong(h_oop->long_field(fd->offset())); + break; + case T_OBJECT: + result->set_jobject((jobject)h_oop->obj_field(fd->offset())); + break; + default: + ShouldNotReachHere(); + } +} + +static bool find_field(InstanceKlass* ik, + Symbol* name_symbol, + Symbol* signature_symbol, + fieldDescriptor* fd, + bool is_static = false, + bool allow_super = false) { + if (allow_super || is_static) { + return ik->find_field(name_symbol, signature_symbol, is_static, fd) != NULL; + } + return ik->find_local_field(name_symbol, signature_symbol, fd); +} + +static void lookup_field(JfrJavaArguments* args, InstanceKlass* klass, fieldDescriptor* fd, bool static_field) { + assert(args != NULL, "invariant"); + assert(klass != NULL, "invariant"); + assert(klass->is_initialized(), "invariant"); + assert(fd != NULL, "invariant"); + find_field(klass, args->name(), args->signature(), fd, static_field, true); +} + +static void read_field(JfrJavaArguments* args, JavaValue* result, TRAPS) { + assert(args != NULL, "invariant"); + assert(result != NULL, "invariant"); + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD)); + + InstanceKlass* const klass = static_cast(args->klass()); + klass->initialize(CHECK); + const bool static_field = !args->has_receiver(); + fieldDescriptor fd; + lookup_field(args, klass, &fd, static_field); + assert(fd.offset() > 0, "invariant"); + + HandleMark hm(THREAD); + Handle h_oop(static_field ? Handle(THREAD, klass->java_mirror()) : Handle(THREAD, args->receiver())); + read_specialized_field(result, h_oop, &fd); +} + +static void write_field(JfrJavaArguments* args, JavaValue* result, TRAPS) { + assert(args != NULL, "invariant"); + assert(result != NULL, "invariant"); + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD)); + + InstanceKlass* const klass = static_cast(args->klass()); + klass->initialize(CHECK); + + const bool static_field = !args->has_receiver(); + fieldDescriptor fd; + lookup_field(args, klass, &fd, static_field); + assert(fd.offset() > 0, "invariant"); + + HandleMark hm(THREAD); + Handle h_oop(static_field ? Handle(THREAD, klass->java_mirror()) : Handle(THREAD, args->receiver())); + write_specialized_field(args, h_oop, &fd, static_field); +} + +void JfrJavaSupport::set_field(JfrJavaArguments* args, TRAPS) { + assert(args != NULL, "invariant"); + write_field(args, args->result(), THREAD); +} + +void JfrJavaSupport::get_field(JfrJavaArguments* args, TRAPS) { + assert(args != NULL, "invariant"); + read_field(args, args->result(), THREAD); +} + +void JfrJavaSupport::get_field_local_ref(JfrJavaArguments* args, TRAPS) { + assert(args != NULL, "invariant"); + DEBUG_ONLY(check_java_thread_in_vm(THREAD)); + + JavaValue* const result = args->result(); + assert(result != NULL, "invariant"); + assert(result->get_type() == T_OBJECT, "invariant"); + + read_field(args, result, CHECK); + const oop obj = (const oop)result->get_jobject(); + + if (obj != NULL) { + result->set_jobject(local_jni_handle(obj, THREAD)); + } +} + +void JfrJavaSupport::get_field_global_ref(JfrJavaArguments* args, TRAPS) { + assert(args != NULL, "invariant"); + DEBUG_ONLY(check_java_thread_in_vm(THREAD)); + + JavaValue* const result = args->result(); + assert(result != NULL, "invariant"); + assert(result->get_type() == T_OBJECT, "invariant"); + read_field(args, result, CHECK); + const oop obj = (const oop)result->get_jobject(); + if (obj != NULL) { + result->set_jobject(global_jni_handle(obj, THREAD)); + } +} + +/* + * Misc + */ +Klass* JfrJavaSupport::klass(const jobject handle) { + const oop obj = resolve_non_null(handle); + assert(obj != NULL, "invariant"); + return obj->klass(); +} + +// caller needs ResourceMark +const char* JfrJavaSupport::c_str(jstring string, Thread* t) { + DEBUG_ONLY(check_java_thread_in_vm(t)); + if (string == NULL) { + return NULL; + } + const char* temp = NULL; + const oop java_string = resolve_non_null(string); + if (java_lang_String::value(java_string) != NULL) { + const size_t length = java_lang_String::utf8_length(java_string); + temp = NEW_RESOURCE_ARRAY_IN_THREAD(t, const char, (length + 1)); + if (temp == NULL) { + JfrJavaSupport::throw_out_of_memory_error("Unable to allocate thread local native memory", t); + return NULL; + } + assert(temp != NULL, "invariant"); + java_lang_String::as_utf8_string(java_string, const_cast(temp), (int) length + 1); + } + return temp; +} + +/* + * Exceptions and errors + */ +static void create_and_throw(Symbol* name, const char* message, TRAPS) { + assert(name != NULL, "invariant"); + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD)); + assert(!HAS_PENDING_EXCEPTION, "invariant"); + THROW_MSG(name, message); +} + +void JfrJavaSupport::throw_illegal_state_exception(const char* message, TRAPS) { + create_and_throw(vmSymbols::java_lang_IllegalStateException(), message, THREAD); +} + +void JfrJavaSupport::throw_internal_error(const char* message, TRAPS) { + create_and_throw(vmSymbols::java_lang_InternalError(), message, THREAD); +} + +void JfrJavaSupport::throw_illegal_argument_exception(const char* message, TRAPS) { + create_and_throw(vmSymbols::java_lang_IllegalArgumentException(), message, THREAD); +} + +void JfrJavaSupport::throw_out_of_memory_error(const char* message, TRAPS) { + create_and_throw(vmSymbols::java_lang_OutOfMemoryError(), message, THREAD); +} + +void JfrJavaSupport::throw_class_format_error(const char* message, TRAPS) { + create_and_throw(vmSymbols::java_lang_ClassFormatError(), message, THREAD); +} + +void JfrJavaSupport::abort(jstring errorMsg, Thread* t) { + DEBUG_ONLY(check_java_thread_in_vm(t)); + + ResourceMark rm(t); + const char* const error_msg = c_str(errorMsg, t); + if (error_msg != NULL) { + log_error(jfr, system)("%s",error_msg); + } + log_error(jfr, system)("%s", "An irrecoverable error in Jfr. Shutting down VM..."); + vm_abort(); +} + +JfrJavaSupport::CAUSE JfrJavaSupport::_cause = JfrJavaSupport::VM_ERROR; +void JfrJavaSupport::set_cause(jthrowable throwable, Thread* t) { + DEBUG_ONLY(check_java_thread_in_vm(t)); + + HandleMark hm(t); + Handle ex(t, JNIHandles::resolve_external_guard(throwable)); + + if (ex.is_null()) { + return; + } + + if (ex->is_a(SystemDictionary::OutOfMemoryError_klass())) { + _cause = OUT_OF_MEMORY; + return; + } + if (ex->is_a(SystemDictionary::StackOverflowError_klass())) { + _cause = STACK_OVERFLOW; + return; + } + if (ex->is_a(SystemDictionary::Error_klass())) { + _cause = VM_ERROR; + return; + } + if (ex->is_a(SystemDictionary::RuntimeException_klass())) { + _cause = RUNTIME_EXCEPTION; + return; + } + if (ex->is_a(SystemDictionary::Exception_klass())) { + _cause = UNKNOWN; + return; + } +} + +void JfrJavaSupport::uncaught_exception(jthrowable throwable, Thread* t) { + DEBUG_ONLY(check_java_thread_in_vm(t)); + assert(throwable != NULL, "invariant"); + set_cause(throwable, t); +} + +JfrJavaSupport::CAUSE JfrJavaSupport::cause() { + return _cause; +} + +const char* const JDK_JFR_MODULE_NAME = "jdk.jfr"; +const char* const JDK_JFR_PACKAGE_NAME = "jdk/jfr"; + +static bool is_jdk_jfr_module_in_readability_graph() { + Thread* const t = Thread::current(); + // take one of the packages in the module to be located and query for its definition. + TempNewSymbol pkg_sym = SymbolTable::new_symbol(JDK_JFR_PACKAGE_NAME, t); + return Modules::is_package_defined(pkg_sym, Handle(), t); +} + +static void print_module_resolution_error(outputStream* stream) { + assert(stream != NULL, "invariant"); + stream->print_cr("%s not found.", JDK_JFR_MODULE_NAME); + stream->print_cr("Flight Recorder can not be enabled."); + stream->print_cr("To use Flight Recorder, you might need to add" \ + " \"--add-modules %s\" to the VM command-line options.", JDK_JFR_MODULE_NAME); +} + +bool JfrJavaSupport::is_jdk_jfr_module_available() { + return is_jdk_jfr_module_in_readability_graph(); +} + +bool JfrJavaSupport::is_jdk_jfr_module_available(outputStream* stream, TRAPS) { + if (!JfrJavaSupport::is_jdk_jfr_module_available()) { + if (stream != NULL) { + print_module_resolution_error(stream); + } + return false; + } + return true; +} + +jlong JfrJavaSupport::jfr_thread_id(jobject target_thread) { + ThreadsListHandle tlh; + JavaThread* native_thread = NULL; + (void)tlh.cv_internal_thread_to_JavaThread(target_thread, &native_thread, NULL); + return native_thread != NULL ? JFR_THREAD_ID(native_thread) : 0; +} diff --git a/src/hotspot/share/jfr/jni/jfrJavaSupport.hpp b/src/hotspot/share/jfr/jni/jfrJavaSupport.hpp new file mode 100644 index 00000000000..dc71db5adac --- /dev/null +++ b/src/hotspot/share/jfr/jni/jfrJavaSupport.hpp @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_JFR_JNI_JFRJAVASUPPORT_HPP +#define SHARE_VM_JFR_JNI_JFRJAVASUPPORT_HPP + +#include "jfr/jni/jfrJavaCall.hpp" +#include "utilities/exceptions.hpp" + +class Klass; +class JavaThread; +class outputStream; + +class JfrJavaSupport : public AllStatic { + public: + static jobject local_jni_handle(const oop obj, Thread* t); + static jobject local_jni_handle(const jobject handle, Thread* t); + static void destroy_local_jni_handle(const jobject handle); + + static jobject global_jni_handle(const oop obj, Thread* t); + static jobject global_jni_handle(const jobject handle, Thread* t); + static void destroy_global_jni_handle(const jobject handle); + + static oop resolve_non_null(jobject obj); + static void notify_all(jobject obj, TRAPS); + static void set_array_element(jobjectArray arr, jobject element, int index, Thread* t); + + // naked oop result + static void call_static(JfrJavaArguments* args, TRAPS); + static void call_special(JfrJavaArguments* args, TRAPS); + static void call_virtual(JfrJavaArguments* args, TRAPS); + + static void set_field(JfrJavaArguments* args, TRAPS); + static void get_field(JfrJavaArguments* args, TRAPS); + static void new_object(JfrJavaArguments* args, TRAPS); + + // global jni handle result + static void new_object_global_ref(JfrJavaArguments* args, TRAPS); + static void get_field_global_ref(JfrJavaArguments* args, TRAPS); + + // local jni handle result + static void new_object_local_ref(JfrJavaArguments* args, TRAPS); + static void get_field_local_ref(JfrJavaArguments* args, TRAPS); + + static jstring new_string(const char* c_str, TRAPS); + static jobjectArray new_string_array(int length, TRAPS); + + static jobject new_java_lang_Boolean(bool value, TRAPS); + static jobject new_java_lang_Integer(jint value, TRAPS); + static jobject new_java_lang_Long(jlong value, TRAPS); + + // misc + static Klass* klass(const jobject handle); + // caller needs ResourceMark + static const char* c_str(jstring string, Thread* jt); + + // exceptions + static void throw_illegal_state_exception(const char* message, TRAPS); + static void throw_illegal_argument_exception(const char* message, TRAPS); + static void throw_internal_error(const char* message, TRAPS); + static void throw_out_of_memory_error(const char* message, TRAPS); + static void throw_class_format_error(const char* message, TRAPS); + + static bool is_jdk_jfr_module_available(); + static bool is_jdk_jfr_module_available(outputStream* stream, TRAPS); + + static jlong jfr_thread_id(jobject target_thread); + + // critical + static void abort(jstring errorMsg, TRAPS); + static void uncaught_exception(jthrowable throwable, Thread* t); + + // asserts + DEBUG_ONLY(static void check_java_thread_in_vm(Thread* t);) + DEBUG_ONLY(static void check_java_thread_in_native(Thread* t);) + + enum CAUSE { + VM_ERROR, + OUT_OF_MEMORY, + STACK_OVERFLOW, + RUNTIME_EXCEPTION, + UNKNOWN, + NOF_CAUSES + }; + + static CAUSE cause(); + + private: + static CAUSE _cause; + static void set_cause(jthrowable throwable, Thread* t); +}; + +#endif // SHARE_VM_JFR_JNI_JFRJAVASUPPORT_HPP diff --git a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp new file mode 100644 index 00000000000..9f4e819a9d0 --- /dev/null +++ b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp @@ -0,0 +1,310 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "jni.h" +#include "jvm.h" +#include "jfr/jfr.hpp" +#include "jfr/jfrEvents.hpp" +#include "jfr/periodic/sampling/jfrThreadSampler.hpp" +#include "jfr/recorder/jfrEventSetting.hpp" +#include "jfr/recorder/jfrRecorder.hpp" +#include "jfr/recorder/checkpoint/jfrMetadataEvent.hpp" +#include "jfr/recorder/checkpoint/types/traceid/jfrTraceId.inline.hpp" +#include "jfr/recorder/repository/jfrRepository.hpp" +#include "jfr/recorder/repository/jfrChunkSizeNotifier.hpp" +#include "jfr/recorder/repository/jfrChunkWriter.hpp" +#include "jfr/recorder/service/jfrOptionSet.hpp" +#include "jfr/recorder/stacktrace/jfrStackTraceRepository.hpp" +#include "jfr/recorder/stringpool/jfrStringPool.hpp" +#include "jfr/jni/jfrGetAllEventClasses.hpp" +#include "jfr/jni/jfrJavaSupport.hpp" +#include "jfr/jni/jfrJniMethodRegistration.hpp" +#include "jfr/instrumentation/jfrEventClassTransformer.hpp" +#include "jfr/instrumentation/jfrJvmtiAgent.hpp" +#include "jfr/leakprofiler/leakProfiler.hpp" +#include "jfr/utilities/jfrJavaLog.hpp" +#include "jfr/utilities/jfrTimeConverter.hpp" +#include "jfr/utilities/jfrTime.hpp" +#include "jfr/writers/jfrJavaEventWriter.hpp" +#include "jfrfiles/jfrPeriodic.hpp" +#include "logging/log.hpp" +#include "memory/resourceArea.hpp" +#include "runtime/interfaceSupport.inline.hpp" +#include "runtime/mutexLocker.hpp" +#include "runtime/os.hpp" +#include "runtime/thread.hpp" +#include "utilities/debug.hpp" + +#define NO_TRANSITION(result_type, header) extern "C" { result_type JNICALL header { +#define NO_TRANSITION_END } } + +/* + * NO_TRANSITION entries + * + * Thread remains _thread_in_native + */ + +NO_TRANSITION(void, jfr_register_natives(JNIEnv* env, jclass jvmclass)) + JfrJniMethodRegistration register_native_methods(env); +NO_TRANSITION_END + +NO_TRANSITION(jboolean, jfr_is_enabled()) + return Jfr::is_enabled() ? JNI_TRUE : JNI_FALSE; +NO_TRANSITION_END + +NO_TRANSITION(jboolean, jfr_is_disabled()) + return Jfr::is_disabled() ? JNI_TRUE : JNI_FALSE; +NO_TRANSITION_END + +NO_TRANSITION(jboolean, jfr_is_started()) + return JfrRecorder::is_created() ? JNI_TRUE : JNI_FALSE; +NO_TRANSITION_END + +NO_TRANSITION(jstring, jfr_get_pid(JNIEnv* env, jobject jvm)) + char pid_buf[32] = { 0 }; + jio_snprintf(pid_buf, sizeof(pid_buf), "%d", os::current_process_id()); + jstring pid_string = env->NewStringUTF(pid_buf); + return pid_string; // exception pending if NULL +NO_TRANSITION_END + +NO_TRANSITION(jlong, jfr_elapsed_frequency(JNIEnv* env, jobject jvm)) + return JfrTime::frequency(); +NO_TRANSITION_END + +NO_TRANSITION(jlong, jfr_elapsed_counter(JNIEnv* env, jobject jvm)) + return JfrTicks::now(); +NO_TRANSITION_END + +NO_TRANSITION(void, jfr_retransform_classes(JNIEnv* env, jobject jvm, jobjectArray classes)) + JfrJvmtiAgent::retransform_classes(env, classes, JavaThread::thread_from_jni_environment(env)); +NO_TRANSITION_END + +NO_TRANSITION(void, jfr_set_enabled(JNIEnv* env, jobject jvm, jlong event_type_id, jboolean enabled)) + JfrEventSetting::set_enabled(event_type_id, JNI_TRUE == enabled); + if (EventOldObjectSample::eventId == event_type_id) { + ThreadInVMfromNative transition(JavaThread::thread_from_jni_environment(env)); + if (JNI_TRUE == enabled) { + LeakProfiler::start(JfrOptionSet::old_object_queue_size()); + } else { + LeakProfiler::stop(); + } + } +NO_TRANSITION_END + +NO_TRANSITION(void, jfr_set_file_notification(JNIEnv* env, jobject jvm, jlong threshold)) + JfrChunkSizeNotifier::set_chunk_size_threshold((size_t)threshold); +NO_TRANSITION_END + +NO_TRANSITION(void, jfr_set_sample_threads(JNIEnv* env, jobject jvm, jboolean sampleThreads)) + JfrOptionSet::set_sample_threads(sampleThreads); +NO_TRANSITION_END + +NO_TRANSITION(void, jfr_set_stack_depth(JNIEnv* env, jobject jvm, jint depth)) + JfrOptionSet::set_stackdepth((jlong)depth); +NO_TRANSITION_END + +NO_TRANSITION(void, jfr_set_stacktrace_enabled(JNIEnv* env, jobject jvm, jlong event_type_id, jboolean enabled)) + JfrEventSetting::set_stacktrace(event_type_id, JNI_TRUE == enabled); +NO_TRANSITION_END + +NO_TRANSITION(void, jfr_set_global_buffer_count(JNIEnv* env, jobject jvm, jlong count)) + JfrOptionSet::set_num_global_buffers(count); +NO_TRANSITION_END + +NO_TRANSITION(void, jfr_set_global_buffer_size(JNIEnv* env, jobject jvm, jlong size)) +JfrOptionSet::set_global_buffer_size(size); +NO_TRANSITION_END + +NO_TRANSITION(void, jfr_set_thread_buffer_size(JNIEnv* env, jobject jvm, jlong size)) + JfrOptionSet::set_thread_buffer_size(size); +NO_TRANSITION_END + +NO_TRANSITION(void, jfr_set_memory_size(JNIEnv* env, jobject jvm, jlong size)) + JfrOptionSet::set_memory_size(size); +NO_TRANSITION_END + +NO_TRANSITION(jboolean, jfr_set_threshold(JNIEnv* env, jobject jvm, jlong event_type_id, jlong thresholdTicks)) + return JfrEventSetting::set_threshold(event_type_id, thresholdTicks) ? JNI_TRUE : JNI_FALSE; +NO_TRANSITION_END + +NO_TRANSITION(jboolean, jfr_allow_event_retransforms(JNIEnv* env, jobject jvm)) + return JfrOptionSet::allow_event_retransforms() ? JNI_TRUE : JNI_FALSE; +NO_TRANSITION_END + +NO_TRANSITION(jboolean, jfr_is_available(JNIEnv* env, jclass jvm)) + return !Jfr::is_disabled() ? JNI_TRUE : JNI_FALSE; +NO_TRANSITION_END + +NO_TRANSITION(jlong, jfr_get_epoch_address(JNIEnv* env, jobject jvm)) + return JfrTraceIdEpoch::epoch_address(); +NO_TRANSITION_END + +NO_TRANSITION(jlong, jfr_get_unloaded_event_classes_count(JNIEnv* env, jobject jvm)) + return JfrEventClasses::unloaded_event_classes_count(); +NO_TRANSITION_END + +NO_TRANSITION(jdouble, jfr_time_conv_factor(JNIEnv* env, jobject jvm)) + return (jdouble)JfrTimeConverter::nano_to_counter_multiplier(); +NO_TRANSITION_END + +NO_TRANSITION(jboolean, jfr_set_cutoff(JNIEnv* env, jobject jvm, jlong event_type_id, jlong cutoff_ticks)) + return JfrEventSetting::set_cutoff(event_type_id, cutoff_ticks) ? JNI_TRUE : JNI_FALSE; +NO_TRANSITION_END + + +/* + * JVM_ENTRY_NO_ENV entries + * + * Transitions: + * Entry: _thread_in_native -> _thread_in_vm + * Exit: _thread_in_vm -> _thread_in_native + * + * Current JavaThread available as "thread" variable + */ + +JVM_ENTRY_NO_ENV(jboolean, jfr_create_jfr(JNIEnv* env, jobject jvm, jboolean simulate_failure)) + if (JfrRecorder::is_created()) { + return JNI_TRUE; + } + if (!JfrRecorder::create(simulate_failure == JNI_TRUE)) { + JfrJavaSupport::throw_illegal_state_exception("Unable to start Jfr", thread); + return JNI_FALSE; + } + return JNI_TRUE; +JVM_END + +JVM_ENTRY_NO_ENV(jboolean, jfr_destroy_jfr(JNIEnv* env, jobject jvm)) + JfrRecorder::destroy(); + return JNI_TRUE; +JVM_END + +JVM_ENTRY_NO_ENV(void, jfr_begin_recording(JNIEnv* env, jobject jvm)) + if (JfrRecorder::is_recording()) { + return; + } + JfrRecorder::start_recording(); +JVM_END + +JVM_ENTRY_NO_ENV(void, jfr_end_recording(JNIEnv* env, jobject jvm)) + if (!JfrRecorder::is_recording()) { + return; + } + JfrRecorder::stop_recording(); +JVM_END + + +JVM_ENTRY_NO_ENV(jboolean, jfr_emit_event(JNIEnv* env, jobject jvm, jlong eventTypeId, jlong timeStamp, jlong when)) + JfrPeriodicEventSet::requestEvent((JfrEventId)eventTypeId); + return thread->has_pending_exception() ? JNI_FALSE : JNI_TRUE; +JVM_END + +JVM_ENTRY_NO_ENV(jobject, jfr_get_all_event_classes(JNIEnv* env, jobject jvm)) + return JfrEventClasses::get_all_event_classes(thread); +JVM_END + +JVM_ENTRY_NO_ENV(jlong, jfr_class_id(JNIEnv* env, jclass jvm, jclass jc)) + return JfrTraceId::use(jc); +JVM_END + +JVM_ENTRY_NO_ENV(jlong, jfr_stacktrace_id(JNIEnv* env, jobject jvm, jint skip)) + return JfrStackTraceRepository::record(thread, skip); +JVM_END + +JVM_ENTRY_NO_ENV(void, jfr_log(JNIEnv* env, jobject jvm, jint tag_set, jint level, jstring message)) + JfrJavaLog::log(tag_set, level, message, thread); +JVM_END + +JVM_ENTRY_NO_ENV(void, jfr_subscribe_log_level(JNIEnv* env, jobject jvm, jobject log_tag, jint id)) + JfrJavaLog::subscribe_log_level(log_tag, id, thread); +JVM_END + +JVM_ENTRY_NO_ENV(void, jfr_set_output(JNIEnv* env, jobject jvm, jstring path)) + JfrRepository::set_chunk_path(path, thread); +JVM_END + +JVM_ENTRY_NO_ENV(void, jfr_set_method_sampling_interval(JNIEnv* env, jobject jvm, jlong type, jlong intervalMillis)) + if (intervalMillis < 0) { + intervalMillis = 0; + } + JfrEventId typed_event_id = (JfrEventId)type; + assert(EventExecutionSample::eventId == typed_event_id || EventNativeMethodSample::eventId == typed_event_id, "invariant"); + if (intervalMillis > 0) { + JfrEventSetting::set_enabled(typed_event_id, true); // ensure sampling event is enabled + } + if (EventExecutionSample::eventId == type) { + JfrThreadSampling::set_java_sample_interval(intervalMillis); + } else { + JfrThreadSampling::set_native_sample_interval(intervalMillis); + } +JVM_END + +JVM_ENTRY_NO_ENV(void, jfr_store_metadata_descriptor(JNIEnv* env, jobject jvm, jbyteArray descriptor)) + JfrMetadataEvent::update(descriptor); +JVM_END + +// trace thread id for a thread object +JVM_ENTRY_NO_ENV(jlong, jfr_id_for_thread(JNIEnv* env, jobject jvm, jobject t)) + return JfrJavaSupport::jfr_thread_id(t); +JVM_END + +JVM_ENTRY_NO_ENV(jobject, jfr_get_event_writer(JNIEnv* env, jclass cls)) + return JfrJavaEventWriter::event_writer(thread); +JVM_END + +JVM_ENTRY_NO_ENV(jobject, jfr_new_event_writer(JNIEnv* env, jclass cls)) + return JfrJavaEventWriter::new_event_writer(thread); +JVM_END + +JVM_ENTRY_NO_ENV(jboolean, jfr_event_writer_flush(JNIEnv* env, jclass cls, jobject writer, jint used_size, jint requested_size)) + return JfrJavaEventWriter::flush(writer, used_size, requested_size, thread); +JVM_END + +JVM_ENTRY_NO_ENV(void, jfr_set_repository_location(JNIEnv* env, jobject repo, jstring location)) + return JfrRepository::set_path(location, thread); +JVM_END + +JVM_ENTRY_NO_ENV(void, jfr_uncaught_exception(JNIEnv* env, jobject jvm, jobject t, jthrowable throwable)) + JfrJavaSupport::uncaught_exception(throwable, thread); +JVM_END + +JVM_ENTRY_NO_ENV(void, jfr_abort(JNIEnv* env, jobject jvm, jstring errorMsg)) + JfrJavaSupport::abort(errorMsg, thread); +JVM_END + +JVM_ENTRY_NO_ENV(jlong, jfr_type_id(JNIEnv* env, jobject jvm, jclass jc)) + return JfrTraceId::get(jc); +JVM_END + +JVM_ENTRY_NO_ENV(jboolean, jfr_add_string_constant(JNIEnv* env, jclass jvm, jboolean epoch, jlong id, jstring string)) + return JfrStringPool::add(epoch == JNI_TRUE, id, string, thread); +JVM_END + +JVM_ENTRY_NO_ENV(void, jfr_set_force_instrumentation(JNIEnv* env, jobject jvm, jboolean force_instrumentation)) + JfrEventClassTransformer::set_force_instrumentation(force_instrumentation == JNI_TRUE ? true : false); +JVM_END + +JVM_ENTRY_NO_ENV(void, jfr_emit_old_object_samples(JNIEnv* env, jobject jvm, jlong cutoff_ticks, jboolean emit_all)) + LeakProfiler::emit_events(cutoff_ticks, emit_all == JNI_TRUE); +JVM_END diff --git a/src/hotspot/share/jfr/jni/jfrJniMethod.hpp b/src/hotspot/share/jfr/jni/jfrJniMethod.hpp new file mode 100644 index 00000000000..87b6e50d3c2 --- /dev/null +++ b/src/hotspot/share/jfr/jni/jfrJniMethod.hpp @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_JFR_JNI_JFRJNIMETHOD_HPP +#define SHARE_VM_JFR_JNI_JFRJNIMETHOD_HPP + +#include "jni.h" + +/* + * Native methods for jdk.jfr.internal.JVM + */ + +#ifdef __cplusplus +extern "C" { +#endif + +jboolean JNICALL jfr_is_enabled(); + +jboolean JNICALL jfr_is_disabled(); + +jboolean JNICALL jfr_is_started(); + +jlong JNICALL jfr_elapsed_counter(JNIEnv* env, jobject jvm); + +jboolean JNICALL jfr_create_jfr(JNIEnv* env, jobject jvm, jboolean simulate_failure); + +jboolean JNICALL jfr_destroy_jfr(JNIEnv* env, jobject jvm); + +void JNICALL jfr_begin_recording(JNIEnv* env, jobject jvm); + +void JNICALL jfr_end_recording(JNIEnv* env, jobject jvm); + +jboolean JNICALL jfr_emit_event(JNIEnv* env, jobject jvm, jlong eventTypeId, jlong timeStamp, jlong when); + +jobject JNICALL jfr_get_all_event_classes(JNIEnv* env, jobject jvm); + +jlong JNICALL jfr_class_id(JNIEnv* env, jclass jvm, jclass jc); + +jstring JNICALL jfr_get_pid(JNIEnv* env, jobject jvm); + +jlong JNICALL jfr_stacktrace_id(JNIEnv* env, jobject jvm, jint skip); + +jlong JNICALL jfr_elapsed_frequency(JNIEnv* env, jobject jvm); + +void JNICALL jfr_subscribe_log_level(JNIEnv* env, jobject jvm, jobject log_tag, jint id); + +void JNICALL jfr_log(JNIEnv* env, jobject jvm, jint tag_set, jint level, jstring message); + +void JNICALL jfr_retransform_classes(JNIEnv* env, jobject jvm, jobjectArray classes); + +void JNICALL jfr_set_enabled(JNIEnv* env, jobject jvm, jlong event_type_id, jboolean enabled); + +void JNICALL jfr_set_file_notification(JNIEnv* env, jobject jvm, jlong delta); + +void JNICALL jfr_set_global_buffer_count(JNIEnv* env, jobject jvm, jlong count); + +void JNICALL jfr_set_global_buffer_size(JNIEnv* env, jobject jvm, jlong size); + +void JNICALL jfr_set_method_sampling_interval(JNIEnv* env, jobject jvm, jlong type, jlong intervalMillis); + +void JNICALL jfr_set_output(JNIEnv* env, jobject jvm, jstring path); + +void JNICALL jfr_set_sample_threads(JNIEnv* env, jobject jvm, jboolean sampleThreads); + +void JNICALL jfr_set_stack_depth(JNIEnv* env, jobject jvm, jint depth); + +void JNICALL jfr_set_stacktrace_enabled(JNIEnv* env, jobject jvm, jlong event_type_id, jboolean enabled); + +void JNICALL jfr_set_thread_buffer_size(JNIEnv* env, jobject jvm, jlong size); + +void JNICALL jfr_set_memory_size(JNIEnv* env, jobject jvm, jlong size); + +jboolean JNICALL jfr_set_threshold(JNIEnv* env, jobject jvm, jlong event_type_id, jlong thresholdTicks); + +void JNICALL jfr_store_metadata_descriptor(JNIEnv* env, jobject jvm, jbyteArray descriptor); + +jlong JNICALL jfr_id_for_thread(JNIEnv* env, jobject jvm, jobject t); + +jboolean JNICALL jfr_allow_event_retransforms(JNIEnv* env, jobject jvm); + +jboolean JNICALL jfr_is_available(JNIEnv* env, jclass jvm); + +jdouble JNICALL jfr_time_conv_factor(JNIEnv* env, jobject jvm); + +jlong JNICALL jfr_type_id(JNIEnv* env, jobject jvm, jclass jc); + +void JNICALL jfr_set_repository_location(JNIEnv* env, jobject repo, jstring location); + +jobject JNICALL jfr_get_event_writer(JNIEnv* env, jclass cls); + +jobject JNICALL jfr_new_event_writer(JNIEnv* env, jclass cls); + +jboolean JNICALL jfr_event_writer_flush(JNIEnv* env, jclass cls, jobject writer, jint used_size, jint requested_size); + +void JNICALL jfr_abort(JNIEnv* env, jobject jvm, jstring errorMsg); + +jlong JNICALL jfr_get_epoch_address(JNIEnv* env, jobject jvm); + +jlong JNICALL jfr_add_string_constant(JNIEnv* env, jclass jvm, jlong gen, jlong id, jstring string); + +void JNICALL jfr_uncaught_exception(JNIEnv* env, jobject jvm, jobject thread, jthrowable throwable); + +void JNICALL jfr_set_force_instrumentation(JNIEnv* env, jobject jvm, jboolean force); + +jlong JNICALL jfr_get_unloaded_event_classes_count(JNIEnv* env, jobject jvm); + +jboolean JNICALL jfr_set_cutoff(JNIEnv* env, jobject jvm, jlong event_type_id, jlong cutoff_ticks); + +void JNICALL jfr_emit_old_object_samples(JNIEnv* env, jobject jvm, jlong cutoff_ticks, jboolean); + +#ifdef __cplusplus +} +#endif + +#endif // SHARE_VM_JFR_JNI_JFRJNIMETHOD_HPP diff --git a/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp b/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp new file mode 100644 index 00000000000..e737f290293 --- /dev/null +++ b/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "jfr/jni/jfrJniMethod.hpp" +#include "jfr/jni/jfrJniMethodRegistration.hpp" +#include "logging/log.hpp" +#include "runtime/interfaceSupport.inline.hpp" +#include "runtime/thread.hpp" +#include "utilities/exceptions.hpp" + +JfrJniMethodRegistration::JfrJniMethodRegistration(JNIEnv* env) { + assert(env != NULL, "invariant"); + jclass jfr_clz = env->FindClass("jdk/jfr/internal/JVM"); + if (jfr_clz != NULL) { + JNINativeMethod method[] = { + (char*)"beginRecording", (char*)"()V", (void*)jfr_begin_recording, + (char*)"endRecording", (char*)"()V", (void*)jfr_end_recording, + (char*)"counterTime", (char*)"()J", (void*)jfr_elapsed_counter, + (char*)"createJFR", (char*)"(Z)Z", (void*)jfr_create_jfr, + (char*)"destroyJFR", (char*)"()Z", (void*)jfr_destroy_jfr, + (char*)"emitEvent", (char*)"(JJJ)Z", (void*)jfr_emit_event, + (char*)"getAllEventClasses", (char*)"()Ljava/util/List;", (void*)jfr_get_all_event_classes, + (char*)"getClassIdNonIntrinsic", (char*)"(Ljava/lang/Class;)J", (void*)jfr_class_id, + (char*)"getPid", (char*)"()Ljava/lang/String;", (void*)jfr_get_pid, + (char*)"getStackTraceId", (char*)"(I)J", (void*)jfr_stacktrace_id, + (char*)"getThreadId", (char*)"(Ljava/lang/Thread;)J", (void*)jfr_id_for_thread, + (char*)"getTicksFrequency", (char*)"()J", (void*)jfr_elapsed_frequency, + (char*)"subscribeLogLevel", (char*)"(Ljdk/jfr/internal/LogTag;I)V", (void*)jfr_subscribe_log_level, + (char*)"log", (char*)"(IILjava/lang/String;)V", (void*)jfr_log, + (char*)"retransformClasses", (char*)"([Ljava/lang/Class;)V", (void*)jfr_retransform_classes, + (char*)"setEnabled", (char*)"(JZ)V", (void*)jfr_set_enabled, + (char*)"setFileNotification", (char*)"(J)V", (void*)jfr_set_file_notification, + (char*)"setGlobalBufferCount", (char*)"(J)V", (void*)jfr_set_global_buffer_count, + (char*)"setGlobalBufferSize", (char*)"(J)V", (void*)jfr_set_global_buffer_size, + (char*)"setMethodSamplingInterval", (char*)"(JJ)V", (void*)jfr_set_method_sampling_interval, + (char*)"setOutput", (char*)"(Ljava/lang/String;)V", (void*)jfr_set_output, + (char*)"setSampleThreads", (char*)"(Z)V", (void*)jfr_set_sample_threads, + (char*)"setStackDepth", (char*)"(I)V", (void*)jfr_set_stack_depth, + (char*)"setStackTraceEnabled", (char*)"(JZ)V", (void*)jfr_set_stacktrace_enabled, + (char*)"setThreadBufferSize", (char*)"(J)V", (void*)jfr_set_thread_buffer_size, + (char*)"setMemorySize", (char*)"(J)V", (void*)jfr_set_memory_size, + (char*)"setThreshold", (char*)"(JJ)Z", (void*)jfr_set_threshold, + (char*)"storeMetadataDescriptor", (char*)"([B)V", (void*)jfr_store_metadata_descriptor, + (char*)"getAllowedToDoEventRetransforms", (char*)"()Z", (void*)jfr_allow_event_retransforms, + (char*)"isAvailable", (char*)"()Z", (void*)jfr_is_available, + (char*)"getTimeConversionFactor", (char*)"()D", (void*)jfr_time_conv_factor, + (char*)"getTypeId", (char*)"(Ljava/lang/Class;)J", (void*)jfr_type_id, + (char*)"getEventWriter", (char*)"()Ljava/lang/Object;", (void*)jfr_get_event_writer, + (char*)"newEventWriter", (char*)"()Ljdk/jfr/internal/EventWriter;", (void*)jfr_new_event_writer, + (char*)"flush", (char*)"(Ljdk/jfr/internal/EventWriter;II)Z", (void*)jfr_event_writer_flush, + (char*)"setRepositoryLocation", (char*)"(Ljava/lang/String;)V", (void*)jfr_set_repository_location, + (char*)"abort", (char*)"(Ljava/lang/String;)V", (void*)jfr_abort, + (char*)"getEpochAddress", (char*)"()J",(void*)jfr_get_epoch_address, + (char*)"addStringConstant", (char*)"(ZJLjava/lang/String;)Z", (void*)jfr_add_string_constant, + (char*)"uncaughtException", (char*)"(Ljava/lang/Thread;Ljava/lang/Throwable;)V", (void*)jfr_uncaught_exception, + (char*)"setForceInstrumentation", (char*)"(Z)V", (void*)jfr_set_force_instrumentation, + (char*)"getUnloadedEventClassCount", (char*)"()J", (void*)jfr_get_unloaded_event_classes_count, + (char*)"setCutoff", (char*)"(JJ)Z", (void*)jfr_set_cutoff, + (char*)"emitOldObjectSamples", (char*)"(JZ)V", (void*)jfr_emit_old_object_samples + }; + + const size_t method_array_length = sizeof(method) / sizeof(JNINativeMethod); + if (env->RegisterNatives(jfr_clz, method, (jint)method_array_length) != JNI_OK) { + JavaThread* jt = JavaThread::thread_from_jni_environment(env); + assert(jt != NULL, "invariant"); + assert(jt->thread_state() == _thread_in_native, "invariant"); + ThreadInVMfromNative transition(jt); + log_error(jfr, system)("RegisterNatives for JVM class failed!"); + } + env->DeleteLocalRef(jfr_clz); + } +} diff --git a/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.hpp b/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.hpp new file mode 100644 index 00000000000..f0a25f69ccf --- /dev/null +++ b/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.hpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_JFR_JNI_JFRJNIMETHODREGISTRATION_HPP +#define SHARE_VM_JFR_JNI_JFRJNIMETHODREGISTRATION_HPP + +#include "jni.h" +#include "memory/allocation.hpp" + +// +// RegisterNatives for jdk.jfr.internal.JVM +// +class JfrJniMethodRegistration : public StackObj { + public: + JfrJniMethodRegistration(JNIEnv* env); +}; + +#endif // SHARE_VM_JFR_JNI_JFRJNIMETHODREGISTRATION_HPP diff --git a/src/hotspot/share/jfr/jni/jfrUpcalls.cpp b/src/hotspot/share/jfr/jni/jfrUpcalls.cpp new file mode 100644 index 00000000000..9ac3d0ee214 --- /dev/null +++ b/src/hotspot/share/jfr/jni/jfrUpcalls.cpp @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "classfile/javaClasses.hpp" +#include "classfile/symbolTable.hpp" +#include "classfile/systemDictionary.hpp" +#include "jfr/jni/jfrJavaSupport.hpp" +#include "jfr/jni/jfrUpcalls.hpp" +#include "jfr/support/jfrEventClass.hpp" +#include "logging/log.hpp" +#include "memory/oopFactory.hpp" +#include "oops/oop.inline.hpp" +#include "oops/typeArrayKlass.hpp" +#include "oops/typeArrayOop.inline.hpp" +#include "runtime/handles.inline.hpp" +#include "runtime/os.hpp" +#include "runtime/thread.inline.hpp" +#include "utilities/exceptions.hpp" + +static Symbol* jvm_upcalls_class_sym = NULL; +static Symbol* on_retransform_method_sym = NULL; +static Symbol* on_retransform_signature_sym = NULL; +static Symbol* bytes_for_eager_instrumentation_sym = NULL; +static Symbol* bytes_for_eager_instrumentation_sig_sym = NULL; + +static bool initialize(TRAPS) { + static bool initialized = false; + if (!initialized) { + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD)); + jvm_upcalls_class_sym = SymbolTable::new_permanent_symbol("jdk/jfr/internal/JVMUpcalls", CHECK_false); + on_retransform_method_sym = SymbolTable::new_permanent_symbol("onRetransform", CHECK_false); + on_retransform_signature_sym = SymbolTable::new_permanent_symbol("(JZLjava/lang/Class;[B)[B", CHECK_false); + bytes_for_eager_instrumentation_sym = SymbolTable::new_permanent_symbol("bytesForEagerInstrumentation", CHECK_false); + bytes_for_eager_instrumentation_sig_sym = SymbolTable::new_permanent_symbol("(JZLjava/lang/Class;[B)[B", THREAD); + initialized = bytes_for_eager_instrumentation_sig_sym != NULL; + } + return initialized; +} + +static const typeArrayOop invoke(jlong trace_id, + jboolean force_instrumentation, + jclass class_being_redefined, + jint class_data_len, + const unsigned char* class_data, + Symbol* method_sym, + Symbol* signature_sym, + jint& new_bytes_length, + TRAPS) { + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD)); + const Klass* klass = SystemDictionary::resolve_or_fail(jvm_upcalls_class_sym, true, CHECK_NULL); + assert(klass != NULL, "invariant"); + typeArrayOop old_byte_array = oopFactory::new_byteArray(class_data_len, CHECK_NULL); + memcpy(old_byte_array->byte_at_addr(0), class_data, class_data_len); + JavaValue result(T_OBJECT); + JfrJavaArguments args(&result, klass, method_sym, signature_sym); + args.push_long(trace_id); + args.push_int(force_instrumentation); + args.push_jobject(class_being_redefined); + args.push_oop(old_byte_array); + JfrJavaSupport::call_static(&args, THREAD); + if (HAS_PENDING_EXCEPTION) { + log_error(jfr, system)("JfrUpcall failed"); + return NULL; + } + // The result should be a [B + const oop res = (oop)result.get_jobject(); + assert(res != NULL, "invariant"); + assert(res->is_typeArray(), "invariant"); + assert(TypeArrayKlass::cast(res->klass())->element_type() == T_BYTE, "invariant"); + const typeArrayOop new_byte_array = typeArrayOop(res); + new_bytes_length = (jint)new_byte_array->length(); + return new_byte_array; +} + +static const size_t ERROR_MSG_BUFFER_SIZE = 256; +static void log_error_and_throw_oom(jint new_bytes_length, TRAPS) { + char error_buffer[ERROR_MSG_BUFFER_SIZE]; + jio_snprintf(error_buffer, ERROR_MSG_BUFFER_SIZE, + "Thread local allocation (native) for " SIZE_FORMAT " bytes failed in JfrUpcalls", (size_t)new_bytes_length); + log_error(jfr, system)("%s", error_buffer); + JfrJavaSupport::throw_out_of_memory_error(error_buffer, CHECK); +} + +void JfrUpcalls::on_retransform(jlong trace_id, + jclass class_being_redefined, + jint class_data_len, + const unsigned char* class_data, + jint* new_class_data_len, + unsigned char** new_class_data, + TRAPS) { + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD)); + assert(class_being_redefined != NULL, "invariant"); + assert(class_data != NULL, "invariant"); + assert(new_class_data_len != NULL, "invariant"); + assert(new_class_data != NULL, "invariant"); + if (!JdkJfrEvent::is_visible(class_being_redefined)) { + return; + } + jint new_bytes_length = 0; + initialize(THREAD); + const typeArrayOop new_byte_array = invoke(trace_id, + false, + class_being_redefined, + class_data_len, + class_data, + on_retransform_method_sym, + on_retransform_signature_sym, + new_bytes_length, + CHECK); + assert(new_byte_array != NULL, "invariant"); + assert(new_bytes_length > 0, "invariant"); + // memory space must be malloced as mtInternal + // as it will be deallocated by JVMTI routines + unsigned char* const new_bytes = (unsigned char* const)os::malloc(new_bytes_length, mtInternal); + if (new_bytes == NULL) { + log_error_and_throw_oom(new_bytes_length, THREAD); // unwinds + } + assert(new_bytes != NULL, "invariant"); + memcpy(new_bytes, new_byte_array->byte_at_addr(0), (size_t)new_bytes_length); + *new_class_data_len = new_bytes_length; + *new_class_data = new_bytes; +} + +void JfrUpcalls::new_bytes_eager_instrumentation(jlong trace_id, + jboolean force_instrumentation, + jclass super, + jint class_data_len, + const unsigned char* class_data, + jint* new_class_data_len, + unsigned char** new_class_data, + TRAPS) { + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD)); + assert(super != NULL, "invariant"); + assert(class_data != NULL, "invariant"); + assert(new_class_data_len != NULL, "invariant"); + assert(new_class_data != NULL, "invariant"); + jint new_bytes_length = 0; + initialize(THREAD); + const typeArrayOop new_byte_array = invoke(trace_id, + force_instrumentation, + super, + class_data_len, + class_data, + bytes_for_eager_instrumentation_sym, + bytes_for_eager_instrumentation_sig_sym, + new_bytes_length, + CHECK); + assert(new_byte_array != NULL, "invariant"); + assert(new_bytes_length > 0, "invariant"); + unsigned char* const new_bytes = NEW_RESOURCE_ARRAY_IN_THREAD_RETURN_NULL(THREAD, unsigned char, new_bytes_length); + if (new_bytes == NULL) { + log_error_and_throw_oom(new_bytes_length, THREAD); // this unwinds + } + assert(new_bytes != NULL, "invariant"); + memcpy(new_bytes, new_byte_array->byte_at_addr(0), (size_t)new_bytes_length); + *new_class_data_len = new_bytes_length; + *new_class_data = new_bytes; +} diff --git a/src/hotspot/share/jfr/jni/jfrUpcalls.hpp b/src/hotspot/share/jfr/jni/jfrUpcalls.hpp new file mode 100644 index 00000000000..1f1a910d680 --- /dev/null +++ b/src/hotspot/share/jfr/jni/jfrUpcalls.hpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_JFR_JNI_JFRUPCALLS_HPP +#define SHARE_VM_JFR_JNI_JFRUPCALLS_HPP + +#include "jni.h" +#include "jfr/utilities/jfrAllocation.hpp" +#include "utilities/exceptions.hpp" + +class JavaThread; + +// +// Upcalls to Java for instrumentation purposes. +// Targets are located in jdk.jfr.internal.JVMUpcalls. +// +class JfrUpcalls : AllStatic { + public: + static void new_bytes_eager_instrumentation(jlong trace_id, + jboolean force_instrumentation, + jclass super, + jint class_data_len, + const unsigned char* class_data, + jint* new_class_data_len, + unsigned char** new_class_data, + TRAPS); + + static void on_retransform(jlong trace_id, + jclass class_being_redefined, + jint class_data_len, + const unsigned char* class_data, + jint* new_class_data_len, + unsigned char** new_class_data, + TRAPS); +}; + +#endif // SHARE_VM_JFR_JNI_JFRUPCALLS_HPP diff --git a/src/hotspot/share/jfr/leakprofiler/chains/bfsClosure.cpp b/src/hotspot/share/jfr/leakprofiler/chains/bfsClosure.cpp new file mode 100644 index 00000000000..9922729f189 --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/chains/bfsClosure.cpp @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +#include "precompiled.hpp" +#include "jfr/leakprofiler/chains/bitset.hpp" +#include "jfr/leakprofiler/chains/bfsClosure.hpp" +#include "jfr/leakprofiler/chains/dfsClosure.hpp" +#include "jfr/leakprofiler/chains/edge.hpp" +#include "jfr/leakprofiler/chains/edgeStore.hpp" +#include "jfr/leakprofiler/chains/edgeQueue.hpp" +#include "jfr/leakprofiler/utilities/granularTimer.hpp" +#include "jfr/leakprofiler/utilities/unifiedOop.hpp" +#include "logging/log.hpp" +#include "memory/resourceArea.hpp" +#include "oops/access.inline.hpp" +#include "oops/oop.inline.hpp" +#include "utilities/align.hpp" + +BFSClosure::BFSClosure(EdgeQueue* edge_queue, EdgeStore* edge_store, BitSet* mark_bits) : + _edge_queue(edge_queue), + _edge_store(edge_store), + _mark_bits(mark_bits), + _current_parent(NULL), + _current_frontier_level(0), + _next_frontier_idx(0), + _prev_frontier_idx(0), + _dfs_fallback_idx(0), + _use_dfs(false) { +} + +static void log_frontier_level_summary(size_t level, + size_t high_idx, + size_t low_idx, + size_t edge_size) { + const size_t nof_edges_in_frontier = high_idx - low_idx; + log_trace(jfr, system)( + "BFS front: " SIZE_FORMAT " edges: " SIZE_FORMAT " size: " SIZE_FORMAT " [KB]", + level, + nof_edges_in_frontier, + (nof_edges_in_frontier * edge_size) / K + ); +} + +void BFSClosure::log_completed_frontier() const { + log_frontier_level_summary(_current_frontier_level, + _next_frontier_idx, + _prev_frontier_idx, + _edge_queue->sizeof_edge()); +} + +void BFSClosure::log_dfs_fallback() const { + const size_t edge_size = _edge_queue->sizeof_edge(); + // first complete summary for frontier in progress + log_frontier_level_summary(_current_frontier_level, + _next_frontier_idx, + _prev_frontier_idx, + edge_size); + + // and then also complete the last frontier + log_frontier_level_summary(_current_frontier_level + 1, + _edge_queue->bottom(), + _next_frontier_idx, + edge_size); + + // additional information about DFS fallover + log_trace(jfr, system)( + "BFS front: " SIZE_FORMAT " filled edge queue at edge: " SIZE_FORMAT, + _current_frontier_level, + _dfs_fallback_idx + ); + + const size_t nof_dfs_completed_edges = _edge_queue->bottom() - _dfs_fallback_idx; + log_trace(jfr, system)( + "DFS to complete " SIZE_FORMAT " edges size: " SIZE_FORMAT " [KB]", + nof_dfs_completed_edges, + (nof_dfs_completed_edges * edge_size) / K + ); +} + +void BFSClosure::process() { + + process_root_set(); + process_queue(); +} + +void BFSClosure::process_root_set() { + for (size_t idx = _edge_queue->bottom(); idx < _edge_queue->top(); ++idx) { + const Edge* edge = _edge_queue->element_at(idx); + assert(edge->parent() == NULL, "invariant"); + process(edge->reference(), edge->pointee()); + } +} + +void BFSClosure::process(const oop* reference, const oop pointee) { + closure_impl(reference, pointee); +} +void BFSClosure::closure_impl(const oop* reference, const oop pointee) { + assert(reference != NULL, "invariant"); + assert(UnifiedOop::dereference(reference) == pointee, "invariant"); + + if (GranularTimer::is_finished()) { + return; + } + + if (_use_dfs) { + assert(_current_parent != NULL, "invariant"); + DFSClosure::find_leaks_from_edge(_edge_store, _mark_bits, _current_parent); + return; + } + + if (!_mark_bits->is_marked(pointee)) { + _mark_bits->mark_obj(pointee); + // is the pointee a sample object? + if (NULL == pointee->mark()) { + add_chain(reference, pointee); + } + + // if we are processinig initial root set, don't add to queue + if (_current_parent != NULL) { + assert(_current_parent->distance_to_root() == _current_frontier_level, "invariant"); + _edge_queue->add(_current_parent, reference); + } + + if (_edge_queue->is_full()) { + dfs_fallback(); + } + } +} + +void BFSClosure::add_chain(const oop* reference, const oop pointee) { + assert(pointee != NULL, "invariant"); + assert(NULL == pointee->mark(), "invariant"); + + const size_t length = _current_parent == NULL ? 1 : _current_parent->distance_to_root() + 2; + ResourceMark rm; + Edge* const chain = NEW_RESOURCE_ARRAY(Edge, length); + size_t idx = 0; + chain[idx++] = Edge(NULL, reference); + // aggregate from breadth-first search + const Edge* current = _current_parent; + while (current != NULL) { + chain[idx++] = Edge(NULL, current->reference()); + current = current->parent(); + } + assert(length == idx, "invariant"); + _edge_store->add_chain(chain, length); +} + +void BFSClosure::dfs_fallback() { + assert(_edge_queue->is_full(), "invariant"); + _use_dfs = true; + _dfs_fallback_idx = _edge_queue->bottom(); + while (!_edge_queue->is_empty()) { + const Edge* edge = _edge_queue->remove(); + if (edge->pointee() != NULL) { + DFSClosure::find_leaks_from_edge(_edge_store, _mark_bits, edge); + } + } +} + +void BFSClosure::process_queue() { + assert(_current_frontier_level == 0, "invariant"); + assert(_next_frontier_idx == 0, "invariant"); + assert(_prev_frontier_idx == 0, "invariant"); + + _next_frontier_idx = _edge_queue->top(); + while (!is_complete()) { + iterate(_edge_queue->remove()); // edge_queue.remove() increments bottom + } +} + +void BFSClosure::step_frontier() const { + log_completed_frontier(); + ++_current_frontier_level; + _prev_frontier_idx = _next_frontier_idx; + _next_frontier_idx = _edge_queue->top(); +} + +bool BFSClosure::is_complete() const { + if (_edge_queue->bottom() < _next_frontier_idx) { + return false; + } + if (_edge_queue->bottom() > _next_frontier_idx) { + // fallback onto DFS as part of processing the frontier + assert(_dfs_fallback_idx >= _prev_frontier_idx, "invariant"); + assert(_dfs_fallback_idx < _next_frontier_idx, "invariant"); + log_dfs_fallback(); + return true; + } + assert(_edge_queue->bottom() == _next_frontier_idx, "invariant"); + if (_edge_queue->is_empty()) { + return true; + } + step_frontier(); + return false; +} + +void BFSClosure::iterate(const Edge* parent) { + assert(parent != NULL, "invariant"); + const oop pointee = parent->pointee(); + assert(pointee != NULL, "invariant"); + _current_parent = parent; + pointee->oop_iterate(this); +} + +void BFSClosure::do_oop(oop* ref) { + assert(ref != NULL, "invariant"); + assert(is_aligned(ref, HeapWordSize), "invariant"); + const oop pointee = *ref; + if (pointee != NULL) { + closure_impl(ref, pointee); + } +} + +void BFSClosure::do_oop(narrowOop* ref) { + assert(ref != NULL, "invariant"); + assert(is_aligned(ref, sizeof(narrowOop)), "invariant"); + const oop pointee = RawAccess<>::oop_load(ref); + if (pointee != NULL) { + closure_impl(UnifiedOop::encode(ref), pointee); + } +} diff --git a/src/hotspot/share/jfr/leakprofiler/chains/bfsClosure.hpp b/src/hotspot/share/jfr/leakprofiler/chains/bfsClosure.hpp new file mode 100644 index 00000000000..25347b16a96 --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/chains/bfsClosure.hpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_JFR_LEAKPROFILER_CHAINS_BFSCLOSURE_HPP +#define SHARE_VM_JFR_LEAKPROFILER_CHAINS_BFSCLOSURE_HPP + +#include "memory/iterator.hpp" +#include "oops/oop.hpp" + +class BitSet; +class Edge; +class EdgeStore; +class EdgeQueue; + +// Class responsible for iterating the heap breadth-first +class BFSClosure : public ExtendedOopClosure { + private: + EdgeQueue* _edge_queue; + EdgeStore* _edge_store; + BitSet* _mark_bits; + const Edge* _current_parent; + mutable size_t _current_frontier_level; + mutable size_t _next_frontier_idx; + mutable size_t _prev_frontier_idx; + size_t _dfs_fallback_idx; + bool _use_dfs; + + void log_completed_frontier() const; + void log_dfs_fallback() const; + + bool is_complete() const; + void step_frontier() const; + + void closure_impl(const oop* reference, const oop pointee); + void add_chain(const oop* reference, const oop pointee); + void dfs_fallback(); + + void iterate(const Edge* parent); + void process(const oop* reference, const oop pointee); + + void process_root_set(); + void process_queue(); + + public: + BFSClosure(EdgeQueue* edge_queue, EdgeStore* edge_store, BitSet* mark_bits); + void process(); + + virtual void do_oop(oop* ref); + virtual void do_oop(narrowOop* ref); +}; + +#endif // SHARE_VM_JFR_LEAKPROFILER_CHAINS_BFSCLOSURE_HPP diff --git a/src/hotspot/share/jfr/leakprofiler/chains/bitset.cpp b/src/hotspot/share/jfr/leakprofiler/chains/bitset.cpp new file mode 100644 index 00000000000..67df3d94d52 --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/chains/bitset.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +#include "precompiled.hpp" +#include "jfr/leakprofiler/chains/bitset.hpp" +#include "jfr/recorder/storage/jfrVirtualMemory.hpp" +#include "memory/memRegion.hpp" + +BitSet::BitSet(const MemRegion& covered_region) : + _vmm(NULL), + _region_start(covered_region.start()), + _region_size(covered_region.word_size()) { +} + +BitSet::~BitSet() { + delete _vmm; +} + +bool BitSet::initialize() { + assert(_vmm == NULL, "invariant"); + _vmm = new JfrVirtualMemory(); + if (_vmm == NULL) { + return false; + } + + const BitMap::idx_t bits = _region_size >> LogMinObjAlignment; + const size_t words = bits / BitsPerWord; + const size_t raw_bytes = words * sizeof(BitMap::idx_t); + + // the virtual memory invocation will reserve and commit the entire space + BitMap::bm_word_t* map = (BitMap::bm_word_t*)_vmm->initialize(raw_bytes, raw_bytes); + if (map == NULL) { + return false; + } + _bits = BitMapView(map, bits); + return true; +} + diff --git a/src/hotspot/share/jfr/leakprofiler/chains/bitset.hpp b/src/hotspot/share/jfr/leakprofiler/chains/bitset.hpp new file mode 100644 index 00000000000..565b2d7e41a --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/chains/bitset.hpp @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_JFR_LEAKPROFILER_CHAINS_BITSET_HPP +#define SHARE_VM_JFR_LEAKPROFILER_CHAINS_BITSET_HPP + +#include "memory/allocation.hpp" +#include "oops/oopsHierarchy.hpp" +#include "utilities/bitMap.inline.hpp" + +class JfrVirtualMemory; +class MemRegion; + +class BitSet : public CHeapObj { + private: + JfrVirtualMemory* _vmm; + const HeapWord* const _region_start; + BitMapView _bits; + const size_t _region_size; + + public: + BitSet(const MemRegion& covered_region); + ~BitSet(); + + bool initialize(); + + BitMap::idx_t mark_obj(const HeapWord* addr) { + const BitMap::idx_t bit = addr_to_bit(addr); + _bits.par_set_bit(bit); + return bit; + } + + BitMap::idx_t mark_obj(oop obj) { + return mark_obj((HeapWord*)obj); + } + + bool is_marked(const HeapWord* addr) const { + return is_marked(addr_to_bit(addr)); + } + + bool is_marked(oop obj) const { + return is_marked((HeapWord*)obj); + } + + BitMap::idx_t size() const { + return _bits.size(); + } + + BitMap::idx_t addr_to_bit(const HeapWord* addr) const { + return pointer_delta(addr, _region_start) >> LogMinObjAlignment; + } + + bool is_marked(const BitMap::idx_t bit) const { + return _bits.at(bit); + } +}; + +#endif // SHARE_VM_JFR_LEAKPROFILER_CHAINS_BITSET_HPP diff --git a/src/hotspot/share/jfr/leakprofiler/chains/dfsClosure.cpp b/src/hotspot/share/jfr/leakprofiler/chains/dfsClosure.cpp new file mode 100644 index 00000000000..3599ceb1557 --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/chains/dfsClosure.cpp @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "jfr/leakprofiler/chains/dfsClosure.hpp" +#include "jfr/leakprofiler/chains/edge.hpp" +#include "jfr/leakprofiler/chains/edgeStore.hpp" +#include "jfr/leakprofiler/utilities/granularTimer.hpp" +#include "jfr/leakprofiler/chains/bitset.hpp" +#include "jfr/leakprofiler/utilities/unifiedOop.hpp" +#include "jfr/leakprofiler/utilities/rootType.hpp" +#include "jfr/leakprofiler/chains/rootSetClosure.hpp" +#include "memory/resourceArea.hpp" +#include "oops/access.inline.hpp" +#include "oops/oop.inline.hpp" +#include "utilities/align.hpp" + +// max dfs depth should not exceed size of stack +static const size_t max_dfs_depth = 5000; + +EdgeStore* DFSClosure::_edge_store = NULL; +BitSet* DFSClosure::_mark_bits = NULL; +const Edge* DFSClosure::_start_edge = NULL; +size_t DFSClosure::_max_depth = max_dfs_depth; +bool DFSClosure::_ignore_root_set = false; + +DFSClosure::DFSClosure() : + _parent(NULL), + _reference(NULL), + _depth(0) { +} + +DFSClosure::DFSClosure(DFSClosure* parent, size_t depth) : + _parent(parent), + _reference(NULL), + _depth(depth) { +} + +void DFSClosure::find_leaks_from_edge(EdgeStore* edge_store, + BitSet* mark_bits, + const Edge* start_edge) { + assert(edge_store != NULL, "invariant"); + assert(mark_bits != NULL," invariant"); + assert(start_edge != NULL, "invariant"); + + _edge_store = edge_store; + _mark_bits = mark_bits; + _start_edge = start_edge; + _ignore_root_set = false; + assert(_max_depth == max_dfs_depth, "invariant"); + + // Depth-first search, starting from a BFS egde + DFSClosure dfs; + start_edge->pointee()->oop_iterate(&dfs); +} + +void DFSClosure::find_leaks_from_root_set(EdgeStore* edge_store, + BitSet* mark_bits) { + assert(edge_store != NULL, "invariant"); + assert(mark_bits != NULL, "invariant"); + + _edge_store = edge_store; + _mark_bits = mark_bits; + _start_edge = NULL; + + // Mark root set, to avoid going sideways + _max_depth = 1; + _ignore_root_set = false; + DFSClosure dfs1; + RootSetClosure::process_roots(&dfs1); + + // Depth-first search + _max_depth = max_dfs_depth; + _ignore_root_set = true; + assert(_start_edge == NULL, "invariant"); + DFSClosure dfs2; + RootSetClosure::process_roots(&dfs2); +} + +void DFSClosure::closure_impl(const oop* reference, const oop pointee) { + assert(pointee != NULL, "invariant"); + assert(reference != NULL, "invariant"); + + if (GranularTimer::is_finished()) { + return; + } + if (_depth == 0 && _ignore_root_set) { + // Root set is already marked, but we want + // to continue, so skip is_marked check. + assert(_mark_bits->is_marked(pointee), "invariant"); + } else { + if (_mark_bits->is_marked(pointee)) { + return; + } + } + + _reference = reference; + _mark_bits->mark_obj(pointee); + assert(_mark_bits->is_marked(pointee), "invariant"); + + // is the pointee a sample object? + if (NULL == pointee->mark()) { + add_chain(); + } + + assert(_max_depth >= 1, "invariant"); + if (_depth < _max_depth - 1) { + DFSClosure next_level(this, _depth + 1); + pointee->oop_iterate(&next_level); + } +} + +void DFSClosure::add_chain() { + const size_t length = _start_edge == NULL ? _depth + 1 : + _start_edge->distance_to_root() + 1 + _depth + 1; + + ResourceMark rm; + Edge* const chain = NEW_RESOURCE_ARRAY(Edge, length); + size_t idx = 0; + + // aggregate from depth-first search + const DFSClosure* c = this; + while (c != NULL) { + chain[idx++] = Edge(NULL, c->reference()); + c = c->parent(); + } + + assert(idx == _depth + 1, "invariant"); + + // aggregate from breadth-first search + const Edge* current = _start_edge; + while (current != NULL) { + chain[idx++] = Edge(NULL, current->reference()); + current = current->parent(); + } + assert(idx == length, "invariant"); + _edge_store->add_chain(chain, length); +} + +void DFSClosure::do_oop(oop* ref) { + assert(ref != NULL, "invariant"); + assert(is_aligned(ref, HeapWordSize), "invariant"); + const oop pointee = *ref; + if (pointee != NULL) { + closure_impl(ref, pointee); + } +} + +void DFSClosure::do_oop(narrowOop* ref) { + assert(ref != NULL, "invariant"); + assert(is_aligned(ref, sizeof(narrowOop)), "invariant"); + const oop pointee = RawAccess<>::oop_load(ref); + if (pointee != NULL) { + closure_impl(UnifiedOop::encode(ref), pointee); + } +} diff --git a/src/hotspot/share/jfr/leakprofiler/chains/dfsClosure.hpp b/src/hotspot/share/jfr/leakprofiler/chains/dfsClosure.hpp new file mode 100644 index 00000000000..d1beb088992 --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/chains/dfsClosure.hpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_JFR_LEAKPROFILER_CHAINS_DFSCLOSURE_HPP +#define SHARE_VM_JFR_LEAKPROFILER_CHAINS_DFSCLOSURE_HPP + +#include "memory/iterator.hpp" +#include "oops/oop.hpp" + +class BitSet; +class Edge; +class EdgeStore; +class EdgeQueue; + +// Class responsible for iterating the heap depth-first +class DFSClosure: public ExtendedOopClosure { + private: + static EdgeStore* _edge_store; + static BitSet* _mark_bits; + static const Edge*_start_edge; + static size_t _max_depth; + static bool _ignore_root_set; + DFSClosure* _parent; + const oop* _reference; + size_t _depth; + + void add_chain(); + void closure_impl(const oop* reference, const oop pointee); + + DFSClosure* parent() const { return _parent; } + const oop* reference() const { return _reference; } + + DFSClosure(DFSClosure* parent, size_t depth); + DFSClosure(); + + public: + static void find_leaks_from_edge(EdgeStore* edge_store, BitSet* mark_bits, const Edge* start_edge); + static void find_leaks_from_root_set(EdgeStore* edge_store, BitSet* mark_bits); + + virtual void do_oop(oop* ref); + virtual void do_oop(narrowOop* ref); +}; + +#endif // SHARE_VM_JFR_LEAKPROFILER_CHAINS_DFSCLOSURE_HPP diff --git a/src/hotspot/share/jfr/leakprofiler/chains/edge.cpp b/src/hotspot/share/jfr/leakprofiler/chains/edge.cpp new file mode 100644 index 00000000000..9adaa6a5143 --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/chains/edge.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +#include "precompiled.hpp" +#include "classfile/javaClasses.inline.hpp" +#include "jfr/leakprofiler/chains/edge.hpp" +#include "jfr/leakprofiler/utilities/unifiedOop.hpp" + +Edge::Edge() : _parent(NULL), _reference(NULL) {} + +Edge::Edge(const Edge* parent, const oop* reference) : _parent(parent), + _reference(reference) {} + +const oop Edge::pointee() const { + return UnifiedOop::dereference(_reference); +} + +const oop Edge::reference_owner() const { + return is_root() ? (oop)NULL : UnifiedOop::dereference(_parent->reference()); +} + +static const Klass* resolve_klass(const oop obj) { + assert(obj != NULL, "invariant"); + return java_lang_Class::is_instance(obj) ? + java_lang_Class::as_Klass(obj) : obj->klass(); +} + +const Klass* Edge::pointee_klass() const { + return resolve_klass(pointee()); +} + +const Klass* Edge::reference_owner_klass() const { + const oop ref_owner = reference_owner(); + return ref_owner != NULL ? resolve_klass(ref_owner) : NULL; +} + +size_t Edge::distance_to_root() const { + size_t depth = 0; + const Edge* current = _parent; + while (current != NULL) { + depth++; + current = current->parent(); + } + return depth; +} diff --git a/src/hotspot/share/trace/traceBackend.hpp b/src/hotspot/share/jfr/leakprofiler/chains/edge.hpp similarity index 55% rename from src/hotspot/share/trace/traceBackend.hpp rename to src/hotspot/share/jfr/leakprofiler/chains/edge.hpp index 57b80ffda34..1315abd1494 100644 --- a/src/hotspot/share/trace/traceBackend.hpp +++ b/src/hotspot/share/jfr/leakprofiler/chains/edge.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -21,43 +21,39 @@ * questions. * */ -#ifndef SHARE_VM_TRACE_TRACEBACKEND_HPP -#define SHARE_VM_TRACE_TRACEBACKEND_HPP -#include "utilities/macros.hpp" -#if INCLUDE_TRACE -#include "runtime/globals.hpp" -#include "runtime/os.hpp" -#include "trace/traceTime.hpp" -#include "tracefiles/traceEventIds.hpp" +#ifndef SHARE_VM_JFR_LEAKPROFILER_CHAINS_EDGE_HPP +#define SHARE_VM_JFR_LEAKPROFILER_CHAINS_EDGE_HPP -class TraceBackend { -public: - static bool enabled(void) { - return EnableTracing; +#include "memory/allocation.hpp" +#include "oops/oopsHierarchy.hpp" + +class Edge { + private: + const Edge* _parent; + const oop* _reference; + public: + Edge(); + Edge(const Edge* parent, const oop* reference); + + const oop* reference() const { + return _reference; } - - static bool is_event_enabled(TraceEventId id) { - return enabled(); + const Edge* parent() const { + return _parent; } - - static TracingTime time() { - return os::elapsed_counter(); + bool is_root() const { + return _parent == NULL; } + const oop pointee() const; + const Klass* pointee_klass() const; + const oop reference_owner() const; + const Klass* reference_owner_klass() const; + size_t distance_to_root() const; - static void on_unloading_classes(void) { + void* operator new (size_t sz, void* here) { + return here; } - }; -class TraceThreadData { -public: - TraceThreadData() {} -}; - -typedef TraceBackend Tracing; - -#else // !INCLUDE_TRACE -#include "trace/noTraceBackend.hpp" -#endif // INCLUDE_TRACE -#endif // SHARE_VM_TRACE_TRACEBACKEND_HPP +#endif // SHARE_VM_JFR_LEAKPROFILER_CHAINS_EDGE_HPP diff --git a/src/hotspot/share/jfr/leakprofiler/chains/edgeQueue.cpp b/src/hotspot/share/jfr/leakprofiler/chains/edgeQueue.cpp new file mode 100644 index 00000000000..f3abf573fc9 --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/chains/edgeQueue.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "jfr/leakprofiler/chains/edgeQueue.hpp" +#include "jfr/recorder/storage/jfrVirtualMemory.hpp" + +EdgeQueue::EdgeQueue(size_t reservation_size_bytes, size_t commit_block_size_bytes) : + _vmm(NULL), + _reservation_size_bytes(reservation_size_bytes), + _commit_block_size_bytes(commit_block_size_bytes), + _top_index(0), + _bottom_index(0) { +} + +bool EdgeQueue::initialize() { + assert(_reservation_size_bytes >= _commit_block_size_bytes, "invariant"); + assert(_vmm == NULL, "invariant"); + _vmm = new JfrVirtualMemory(); + return _vmm != NULL && _vmm->initialize(_reservation_size_bytes, _commit_block_size_bytes, sizeof(Edge)); +} + +EdgeQueue::~EdgeQueue() { + delete _vmm; +} + +void EdgeQueue::add(const Edge* parent, const oop* ref) { + assert(ref != NULL, "Null objects not allowed in EdgeQueue"); + assert(!is_full(), "EdgeQueue is full. Check is_full before adding another Edge"); + assert(!_vmm->is_full(), "invariant"); + void* const allocation = _vmm->new_datum(); + assert(allocation != NULL, "invariant"); + new (allocation)Edge(parent, ref); + _top_index++; + assert(_vmm->count() == _top_index, "invariant"); +} + +size_t EdgeQueue::top() const { + return _top_index; +} + +size_t EdgeQueue::bottom() const { + return EdgeQueue::_bottom_index; +} + +bool EdgeQueue::is_empty() const { + return _top_index == _bottom_index; +} + +bool EdgeQueue::is_full() const { + return _vmm->is_full(); +} + +const Edge* EdgeQueue::remove() const { + assert(!is_empty(), "EdgeQueue is empty. Check if empty before removing Edge"); + assert(!_vmm->is_empty(), "invariant"); + return (const Edge*)_vmm->get(_bottom_index++); +} + +const Edge* EdgeQueue::element_at(size_t index) const { + assert(index >= _bottom_index, "invariant"); + assert(index <_top_index, "invariant"); + return (Edge*)_vmm->get(index); +} + +size_t EdgeQueue::reserved_size() const { + assert(_vmm != NULL, "invariant"); + return _vmm->reserved_size(); +} + +size_t EdgeQueue::live_set() const { + assert(_vmm != NULL, "invariant"); + return _vmm->live_set(); +} + +size_t EdgeQueue::sizeof_edge() const { + assert(_vmm != NULL, "invariant"); + return _vmm->aligned_datum_size_bytes(); +} diff --git a/src/hotspot/share/jfr/leakprofiler/chains/edgeQueue.hpp b/src/hotspot/share/jfr/leakprofiler/chains/edgeQueue.hpp new file mode 100644 index 00000000000..8211624616a --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/chains/edgeQueue.hpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_JFR_LEAKPROFILER_CHAINS_EDGEQUEUE_HPP +#define SHARE_VM_JFR_LEAKPROFILER_CHAINS_EDGEQUEUE_HPP + +#include "memory/allocation.hpp" +#include "jfr/leakprofiler/chains/edge.hpp" + +class JfrVirtualMemory; + +class EdgeQueue : public CHeapObj { + private: + JfrVirtualMemory* _vmm; + const size_t _reservation_size_bytes; + const size_t _commit_block_size_bytes; + mutable size_t _top_index; + mutable size_t _bottom_index; + public: + EdgeQueue(size_t reservation_size_bytes, size_t commit_block_size_bytes); + ~EdgeQueue(); + + bool initialize(); + + void add(const Edge* parent, const oop* ref); + const Edge* remove() const; + const Edge* element_at(size_t index) const; + + size_t top() const; + size_t bottom() const; + bool is_empty() const; + bool is_full() const; + + size_t reserved_size() const; + size_t live_set() const; + size_t sizeof_edge() const; // with alignments +}; + +#endif // SHARE_VM_JFR_LEAKPROFILER_CHAINS_EDGEQUEUE_HPP diff --git a/src/hotspot/share/jfr/leakprofiler/chains/edgeStore.cpp b/src/hotspot/share/jfr/leakprofiler/chains/edgeStore.cpp new file mode 100644 index 00000000000..cab1c70491c --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/chains/edgeStore.cpp @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "jfr/leakprofiler/chains/edgeStore.hpp" +#include "jfr/leakprofiler/chains/edgeUtils.hpp" +#include "oops/oop.inline.hpp" + +RoutableEdge::RoutableEdge() : Edge() {} +RoutableEdge::RoutableEdge(const Edge* parent, const oop* reference) : Edge(parent, reference), + _skip_edge(NULL), + _skip_length(0), + _processed(false) {} + +RoutableEdge::RoutableEdge(const Edge& edge) : Edge(edge), + _skip_edge(NULL), + _skip_length(0), + _processed(false) {} + +RoutableEdge::RoutableEdge(const RoutableEdge& edge) : Edge(edge), + _skip_edge(edge._skip_edge), + _skip_length(edge._skip_length), + _processed(edge._processed) {} + +void RoutableEdge::operator=(const RoutableEdge& edge) { + Edge::operator=(edge); + _skip_edge = edge._skip_edge; + _skip_length = edge._skip_length; + _processed = edge._processed; +} + +size_t RoutableEdge::logical_distance_to_root() const { + size_t depth = 0; + const RoutableEdge* current = logical_parent(); + while (current != NULL) { + depth++; + current = current->logical_parent(); + } + return depth; +} + +traceid EdgeStore::_edge_id_counter = 0; + +EdgeStore::EdgeStore() : _edges(NULL) { + _edges = new EdgeHashTable(this); +} + +EdgeStore::~EdgeStore() { + assert(_edges != NULL, "invariant"); + delete _edges; + _edges = NULL; +} + +const Edge* EdgeStore::get_edge(const Edge* edge) const { + assert(edge != NULL, "invariant"); + EdgeEntry* const entry = _edges->lookup_only(*edge, (uintptr_t)edge->reference()); + return entry != NULL ? entry->literal_addr() : NULL; +} + +const Edge* EdgeStore::put(const Edge* edge) { + assert(edge != NULL, "invariant"); + const RoutableEdge e = *edge; + assert(NULL == _edges->lookup_only(e, (uintptr_t)e.reference()), "invariant"); + EdgeEntry& entry = _edges->put(e, (uintptr_t)e.reference()); + return entry.literal_addr(); +} + +traceid EdgeStore::get_id(const Edge* edge) const { + assert(edge != NULL, "invariant"); + EdgeEntry* const entry = _edges->lookup_only(*edge, (uintptr_t)edge->reference()); + assert(entry != NULL, "invariant"); + return entry->id(); +} + +traceid EdgeStore::get_root_id(const Edge* edge) const { + assert(edge != NULL, "invariant"); + const Edge* root = EdgeUtils::root(*edge); + assert(root != NULL, "invariant"); + return get_id(root); +} + +void EdgeStore::add_chain(const Edge* chain, size_t length) { + assert(chain != NULL, "invariant"); + assert(length > 0, "invariant"); + + size_t bottom_index = length - 1; + const size_t top_index = 0; + + const Edge* stored_parent_edge = NULL; + + // determine level of shared ancestry + for (; bottom_index > top_index; --bottom_index) { + const Edge* stored_edge = get_edge(&chain[bottom_index]); + if (stored_edge != NULL) { + stored_parent_edge = stored_edge; + continue; + } + break; + } + + // insertion of new Edges + for (int i = (int)bottom_index; i >= (int)top_index; --i) { + Edge edge(stored_parent_edge, chain[i].reference()); + stored_parent_edge = put(&edge); + } + + const oop sample_object = stored_parent_edge->pointee(); + assert(sample_object != NULL, "invariant"); + assert(NULL == sample_object->mark(), "invariant"); + + // Install the "top" edge of the chain into the sample object mark oop. + // This associates the sample object with its navigable reference chain. + sample_object->set_mark(markOop(stored_parent_edge)); +} + +bool EdgeStore::is_empty() const { + return !_edges->has_entries(); +} + +size_t EdgeStore::number_of_entries() const { + return _edges->cardinality(); +} + +void EdgeStore::assign_id(EdgeEntry* entry) { + assert(entry != NULL, "invariant"); + assert(entry->id() == 0, "invariant"); + entry->set_id(++_edge_id_counter); +} + +bool EdgeStore::equals(const Edge& query, uintptr_t hash, const EdgeEntry* entry) { + assert(entry != NULL, "invariant"); + assert(entry->hash() == hash, "invariant"); + return true; +} diff --git a/src/hotspot/share/jfr/leakprofiler/chains/edgeStore.hpp b/src/hotspot/share/jfr/leakprofiler/chains/edgeStore.hpp new file mode 100644 index 00000000000..a504964cdd1 --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/chains/edgeStore.hpp @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_LEAKPROFILER_CHAINS_EDGESTORE_HPP +#define SHARE_VM_LEAKPROFILER_CHAINS_EDGESTORE_HPP + +#include "jfr/utilities/jfrHashtable.hpp" +#include "jfr/leakprofiler/chains/edge.hpp" +#include "memory/allocation.hpp" + +typedef u8 traceid; + +class RoutableEdge : public Edge { + private: + mutable const RoutableEdge* _skip_edge; + mutable size_t _skip_length; + mutable bool _processed; + + public: + RoutableEdge(); + RoutableEdge(const Edge* parent, const oop* reference); + RoutableEdge(const Edge& edge); + RoutableEdge(const RoutableEdge& edge); + void operator=(const RoutableEdge& edge); + + const RoutableEdge* skip_edge() const { return _skip_edge; } + size_t skip_length() const { return _skip_length; } + + bool is_skip_edge() const { return _skip_edge != NULL; } + bool processed() const { return _processed; } + bool is_sentinel() const { + return _skip_edge == NULL && _skip_length == 1; + } + + void set_skip_edge(const RoutableEdge* edge) const { + assert(!is_skip_edge(), "invariant"); + assert(edge != this, "invariant"); + _skip_edge = edge; + } + + void set_skip_length(size_t length) const { + _skip_length = length; + } + + void set_processed() const { + assert(!_processed, "invariant"); + _processed = true; + } + + // true navigation according to physical tree representation + const RoutableEdge* physical_parent() const { + return static_cast(parent()); + } + + // logical navigation taking skip levels into account + const RoutableEdge* logical_parent() const { + return is_skip_edge() ? skip_edge() : physical_parent(); + } + + size_t logical_distance_to_root() const; +}; + +class EdgeStore : public CHeapObj { + typedef HashTableHost EdgeHashTable; + typedef EdgeHashTable::HashEntry EdgeEntry; + template class, + typename, + size_t> + friend class HashTableHost; + private: + static traceid _edge_id_counter; + EdgeHashTable* _edges; + + // Hash table callbacks + void assign_id(EdgeEntry* entry); + bool equals(const Edge& query, uintptr_t hash, const EdgeEntry* entry); + + const Edge* get_edge(const Edge* edge) const; + const Edge* put(const Edge* edge); + + public: + EdgeStore(); + ~EdgeStore(); + + void add_chain(const Edge* chain, size_t length); + bool is_empty() const; + size_t number_of_entries() const; + + traceid get_id(const Edge* edge) const; + traceid get_root_id(const Edge* edge) const; + + template + void iterate_edges(T& functor) const { _edges->iterate_value(functor); } +}; + +#endif // SHARE_VM_LEAKPROFILER_CHAINS_EDGESTORE_HPP diff --git a/src/hotspot/share/jfr/leakprofiler/chains/edgeUtils.cpp b/src/hotspot/share/jfr/leakprofiler/chains/edgeUtils.cpp new file mode 100644 index 00000000000..9074ebbe499 --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/chains/edgeUtils.cpp @@ -0,0 +1,312 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "classfile/javaClasses.hpp" +#include "jfr/leakprofiler/chains/edge.hpp" +#include "jfr/leakprofiler/chains/edgeStore.hpp" +#include "jfr/leakprofiler/chains/edgeUtils.hpp" +#include "jfr/leakprofiler/utilities/unifiedOop.hpp" +#include "oops/fieldStreams.hpp" +#include "oops/instanceKlass.hpp" +#include "oops/objArrayOop.inline.hpp" +#include "oops/oopsHierarchy.hpp" +#include "runtime/handles.inline.hpp" + +bool EdgeUtils::is_leak_edge(const Edge& edge) { + return (const Edge*)edge.pointee()->mark() == &edge; +} + +bool EdgeUtils::is_root(const Edge& edge) { + return edge.is_root(); +} + +static int field_offset(const Edge& edge) { + assert(!edge.is_root(), "invariant"); + const oop ref_owner = edge.reference_owner(); + assert(ref_owner != NULL, "invariant"); + const oop* reference = UnifiedOop::decode(edge.reference()); + assert(reference != NULL, "invariant"); + assert(!UnifiedOop::is_narrow(reference), "invariant"); + assert(!ref_owner->is_array(), "invariant"); + assert(ref_owner->is_instance(), "invariant"); + const int offset = (int)pointer_delta(reference, ref_owner, sizeof(char)); + assert(offset < (ref_owner->size() * HeapWordSize), "invariant"); + return offset; +} + +static const InstanceKlass* field_type(const Edge& edge) { + assert(!edge.is_root() || !EdgeUtils::is_array_element(edge), "invariant"); + return (const InstanceKlass*)edge.reference_owner_klass(); +} + +const Symbol* EdgeUtils::field_name_symbol(const Edge& edge) { + assert(!edge.is_root(), "invariant"); + assert(!is_array_element(edge), "invariant"); + const int offset = field_offset(edge); + const InstanceKlass* ik = field_type(edge); + while (ik != NULL) { + JavaFieldStream jfs(ik); + while (!jfs.done()) { + if (offset == jfs.offset()) { + return jfs.name(); + } + jfs.next(); + } + ik = (InstanceKlass*)ik->super(); + } + return NULL; +} + +jshort EdgeUtils::field_modifiers(const Edge& edge) { + const int offset = field_offset(edge); + const InstanceKlass* ik = field_type(edge); + + while (ik != NULL) { + JavaFieldStream jfs(ik); + while (!jfs.done()) { + if (offset == jfs.offset()) { + return jfs.access_flags().as_short(); + } + jfs.next(); + } + ik = (InstanceKlass*)ik->super(); + } + return 0; +} + +bool EdgeUtils::is_array_element(const Edge& edge) { + assert(!edge.is_root(), "invariant"); + const oop ref_owner = edge.reference_owner(); + assert(ref_owner != NULL, "invariant"); + return ref_owner->is_objArray(); +} + +static int array_offset(const Edge& edge) { + assert(!edge.is_root(), "invariant"); + const oop ref_owner = edge.reference_owner(); + assert(ref_owner != NULL, "invariant"); + const oop* reference = UnifiedOop::decode(edge.reference()); + assert(reference != NULL, "invariant"); + assert(!UnifiedOop::is_narrow(reference), "invariant"); + assert(ref_owner->is_array(), "invariant"); + const objArrayOop ref_owner_array = static_cast(ref_owner); + const int offset = (int)pointer_delta(reference, ref_owner_array->base(), heapOopSize); + assert(offset >= 0 && offset < ref_owner_array->length(), "invariant"); + return offset; +} + +int EdgeUtils::array_index(const Edge& edge) { + return is_array_element(edge) ? array_offset(edge) : 0; +} + +int EdgeUtils::array_size(const Edge& edge) { + if (is_array_element(edge)) { + const oop ref_owner = edge.reference_owner(); + assert(ref_owner != NULL, "invariant"); + assert(ref_owner->is_objArray(), "invariant"); + return ((objArrayOop)(ref_owner))->length(); + } + return 0; +} + +const Edge* EdgeUtils::root(const Edge& edge) { + const Edge* current = &edge; + const Edge* parent = current->parent(); + while (parent != NULL) { + current = parent; + parent = current->parent(); + } + return current; +} + +// The number of references associated with the leak node; +// can be viewed as the leak node "context". +// Used to provide leak context for a "capped/skipped" reference chain. +static const size_t leak_context = 100; + +// The number of references associated with the root node; +// can be viewed as the root node "context". +// Used to provide root context for a "capped/skipped" reference chain. +static const size_t root_context = 100; + +// A limit on the reference chain depth to be serialized, +static const size_t max_ref_chain_depth = leak_context + root_context; + +const RoutableEdge* skip_to(const RoutableEdge& edge, size_t skip_length) { + const RoutableEdge* current = &edge; + const RoutableEdge* parent = current->physical_parent(); + size_t seek = 0; + while (parent != NULL && seek != skip_length) { + seek++; + current = parent; + parent = parent->physical_parent(); + } + return current; +} + +#ifdef ASSERT +static void validate_skip_target(const RoutableEdge* skip_target) { + assert(skip_target != NULL, "invariant"); + assert(skip_target->distance_to_root() + 1 == root_context, "invariant"); + assert(skip_target->is_sentinel(), "invariant"); +} + +static void validate_new_skip_edge(const RoutableEdge* new_skip_edge, const RoutableEdge* last_skip_edge, size_t adjustment) { + assert(new_skip_edge != NULL, "invariant"); + assert(new_skip_edge->is_skip_edge(), "invariant"); + if (last_skip_edge != NULL) { + const RoutableEdge* const target = skip_to(*new_skip_edge->logical_parent(), adjustment); + validate_skip_target(target->logical_parent()); + return; + } + assert(last_skip_edge == NULL, "invariant"); + // only one level of logical indirection + validate_skip_target(new_skip_edge->logical_parent()); +} +#endif // ASSERT + +static void install_logical_route(const RoutableEdge* new_skip_edge, size_t skip_target_distance) { + assert(new_skip_edge != NULL, "invariant"); + assert(!new_skip_edge->is_skip_edge(), "invariant"); + assert(!new_skip_edge->processed(), "invariant"); + const RoutableEdge* const skip_target = skip_to(*new_skip_edge, skip_target_distance); + assert(skip_target != NULL, "invariant"); + new_skip_edge->set_skip_edge(skip_target); + new_skip_edge->set_skip_length(skip_target_distance); + assert(new_skip_edge->is_skip_edge(), "invariant"); + assert(new_skip_edge->logical_parent() == skip_target, "invariant"); +} + +static const RoutableEdge* find_last_skip_edge(const RoutableEdge& edge, size_t& distance) { + assert(distance == 0, "invariant"); + const RoutableEdge* current = &edge; + while (current != NULL) { + if (current->is_skip_edge() && current->skip_edge()->is_sentinel()) { + return current; + } + current = current->physical_parent(); + ++distance; + } + return current; +} + +static void collapse_overlapping_chain(const RoutableEdge& edge, + const RoutableEdge* first_processed_edge, + size_t first_processed_distance) { + assert(first_processed_edge != NULL, "invariant"); + // first_processed_edge is already processed / written + assert(first_processed_edge->processed(), "invariant"); + assert(first_processed_distance + 1 <= leak_context, "invariant"); + + // from this first processed edge, attempt to fetch the last skip edge + size_t last_skip_edge_distance = 0; + const RoutableEdge* const last_skip_edge = find_last_skip_edge(*first_processed_edge, last_skip_edge_distance); + const size_t distance_discovered = first_processed_distance + last_skip_edge_distance + 1; + + if (distance_discovered <= leak_context || (last_skip_edge == NULL && distance_discovered <= max_ref_chain_depth)) { + // complete chain can be accommodated without modification + return; + } + + // backtrack one edge from existing processed edge + const RoutableEdge* const new_skip_edge = skip_to(edge, first_processed_distance - 1); + assert(new_skip_edge != NULL, "invariant"); + assert(!new_skip_edge->processed(), "invariant"); + assert(new_skip_edge->parent() == first_processed_edge, "invariant"); + + size_t adjustment = 0; + if (last_skip_edge != NULL) { + assert(leak_context - 1 > first_processed_distance - 1, "invariant"); + adjustment = leak_context - first_processed_distance - 1; + assert(last_skip_edge_distance + 1 > adjustment, "invariant"); + install_logical_route(new_skip_edge, last_skip_edge_distance + 1 - adjustment); + } else { + install_logical_route(new_skip_edge, last_skip_edge_distance + 1 - root_context); + new_skip_edge->logical_parent()->set_skip_length(1); // sentinel + } + + DEBUG_ONLY(validate_new_skip_edge(new_skip_edge, last_skip_edge, adjustment);) +} + +static void collapse_non_overlapping_chain(const RoutableEdge& edge, + const RoutableEdge* first_processed_edge, + size_t first_processed_distance) { + assert(first_processed_edge != NULL, "invariant"); + assert(!first_processed_edge->processed(), "invariant"); + // this implies that the first "processed" edge is the leak context relative "leaf" + assert(first_processed_distance + 1 == leak_context, "invariant"); + + const size_t distance_to_root = edge.distance_to_root(); + if (distance_to_root + 1 <= max_ref_chain_depth) { + // complete chain can be accommodated without constructing a skip edge + return; + } + + install_logical_route(first_processed_edge, distance_to_root + 1 - first_processed_distance - root_context); + first_processed_edge->logical_parent()->set_skip_length(1); // sentinel + + DEBUG_ONLY(validate_new_skip_edge(first_processed_edge, NULL, 0);) +} + +static const RoutableEdge* processed_edge(const RoutableEdge& edge, size_t& distance) { + assert(distance == 0, "invariant"); + const RoutableEdge* current = &edge; + while (current != NULL && distance < leak_context - 1) { + if (current->processed()) { + return current; + } + current = current->physical_parent(); + ++distance; + } + assert(distance <= leak_context - 1, "invariant"); + return current; +} + +/* + * Some vocabulary: + * ----------- + * "Context" is an interval in the chain, it is associcated with an edge and it signifies a number of connected edges. + * "Processed / written" means an edge that has already been serialized. + * "Skip edge" is an edge that contains additional information for logical routing purposes. + * "Skip target" is an edge used as a destination for a skip edge + */ +void EdgeUtils::collapse_chain(const RoutableEdge& edge) { + assert(is_leak_edge(edge), "invariant"); + + // attempt to locate an already processed edge inside current leak context (if any) + size_t first_processed_distance = 0; + const RoutableEdge* const first_processed_edge = processed_edge(edge, first_processed_distance); + if (first_processed_edge == NULL) { + return; + } + + if (first_processed_edge->processed()) { + collapse_overlapping_chain(edge, first_processed_edge, first_processed_distance); + } else { + collapse_non_overlapping_chain(edge, first_processed_edge, first_processed_distance); + } + + assert(edge.logical_distance_to_root() + 1 <= max_ref_chain_depth, "invariant"); +} diff --git a/src/hotspot/share/jfr/leakprofiler/chains/edgeUtils.hpp b/src/hotspot/share/jfr/leakprofiler/chains/edgeUtils.hpp new file mode 100644 index 00000000000..730b2fdd4db --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/chains/edgeUtils.hpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_LEAKPROFILER_CHAINS_EDGEUTILS_HPP +#define SHARE_VM_LEAKPROFILER_CHAINS_EDGEUTILS_HPP + +#include "memory/allocation.hpp" + +class Edge; +class RoutableEdge; +class Symbol; + +class EdgeUtils : public AllStatic { + public: + static bool is_leak_edge(const Edge& edge); + + static const Edge* root(const Edge& edge); + static bool is_root(const Edge& edge); + + static bool is_array_element(const Edge& edge); + static int array_index(const Edge& edge); + static int array_size(const Edge& edge); + + static const Symbol* field_name_symbol(const Edge& edge); + static jshort field_modifiers(const Edge& edge); + + static void collapse_chain(const RoutableEdge& edge); +}; + +#endif // SHARE_VM_LEAKPROFILER_CHAINS_EDGEUTILS_HPP diff --git a/src/hotspot/share/jfr/leakprofiler/chains/objectSampleMarker.hpp b/src/hotspot/share/jfr/leakprofiler/chains/objectSampleMarker.hpp new file mode 100644 index 00000000000..9a97a373fbc --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/chains/objectSampleMarker.hpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_JFR_LEAKPROFILER_CHAINS_OBJECTSAMPLEMARKER_HPP +#define SHARE_VM_JFR_LEAKPROFILER_CHAINS_OBJECTSAMPLEMARKER_HPP + +#include "memory/allocation.hpp" +#include "oops/markOop.hpp" +#include "utilities/growableArray.hpp" +// +// This class will save the original mark oop of a object sample object. +// It will then install an "identifier" mark oop to be used for +// identification purposes in the search for reference chains. +// The destructor will restore each modified oop with its original mark oop. +// +class ObjectSampleMarker : public StackObj { + private: + class ObjectSampleMarkOop : public ResourceObj { + friend class ObjectSampleMarker; + private: + oop _obj; + markOop _mark_oop; + ObjectSampleMarkOop(const oop obj, + const markOop mark_oop) : _obj(obj), + _mark_oop(mark_oop) {} + public: + ObjectSampleMarkOop() : _obj(NULL), _mark_oop(NULL) {} + }; + + GrowableArray* _store; + + public: + ObjectSampleMarker() : + _store(new GrowableArray(16)) {} + ~ObjectSampleMarker() { + assert(_store != NULL, "invariant"); + // restore the saved, original, markOop for sample objects + while (_store->is_nonempty()) { + ObjectSampleMarkOop sample_oop = _store->pop(); + sample_oop._obj->set_mark(sample_oop._mark_oop); + assert(sample_oop._obj->mark() == sample_oop._mark_oop, "invariant"); + } + } + + void mark(oop obj) { + assert(obj != NULL, "invariant"); + // save the original markOop + _store->push(ObjectSampleMarkOop(obj, obj->mark())); + // now we will "poison" the mark word of the sample object + // to the intermediate monitor INFLATING state. + // This is an "impossible" state during a safepoint, + // hence we will use it to quickly identify sample objects + // during the reachability search from gc roots. + assert(NULL == markOopDesc::INFLATING(), "invariant"); + obj->set_mark(markOopDesc::INFLATING()); + assert(NULL == obj->mark(), "invariant"); + } +}; + +#endif // SHARE_VM_JFR_LEAKPROFILER_CHAINS_OBJECTSAMPLEMARKER_HPP diff --git a/src/hotspot/share/jfr/leakprofiler/chains/rootSetClosure.cpp b/src/hotspot/share/jfr/leakprofiler/chains/rootSetClosure.cpp new file mode 100644 index 00000000000..fab6ada696e --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/chains/rootSetClosure.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "aot/aotLoader.hpp" +#include "classfile/classLoaderData.hpp" +#include "classfile/stringTable.hpp" +#include "classfile/systemDictionary.hpp" +#include "gc/shared/strongRootsScope.hpp" +#include "jfr/leakprofiler/chains/edgeQueue.hpp" +#include "jfr/leakprofiler/chains/rootSetClosure.hpp" +#include "jfr/leakprofiler/utilities/saveRestore.hpp" +#include "jfr/leakprofiler/utilities/unifiedOop.hpp" +#include "memory/universe.hpp" +#include "oops/access.inline.hpp" +#include "prims/jvmtiExport.hpp" +#include "runtime/jniHandles.inline.hpp" +#include "runtime/synchronizer.hpp" +#include "runtime/thread.hpp" +#include "services/management.hpp" +#include "utilities/align.hpp" + +RootSetClosure::RootSetClosure(EdgeQueue* edge_queue) : + _edge_queue(edge_queue) { +} + +void RootSetClosure::do_oop(oop* ref) { + assert(ref != NULL, "invariant"); + // We discard unaligned root references because + // our reference tagging scheme will use + // the lowest bit in a represented reference + // to indicate the reference is narrow. + // It is mainly roots delivered via nmethods::do_oops() + // that come in unaligned. It should be ok to duck these + // since they are supposedly weak. + if (!is_aligned(ref, HeapWordSize)) { + return; + } + + assert(is_aligned(ref, HeapWordSize), "invariant"); + const oop pointee = *ref; + if (pointee != NULL) { + closure_impl(ref, pointee); + } +} + +void RootSetClosure::do_oop(narrowOop* ref) { + assert(ref != NULL, "invariant"); + assert(is_aligned(ref, sizeof(narrowOop)), "invariant"); + const oop pointee = RawAccess<>::oop_load(ref); + if (pointee != NULL) { + closure_impl(UnifiedOop::encode(ref), pointee); + } +} + +void RootSetClosure::closure_impl(const oop* reference, const oop pointee) { + if (!_edge_queue->is_full()) { + _edge_queue->add(NULL, reference); + } +} + +void RootSetClosure::add_to_queue(EdgeQueue* edge_queue) { + RootSetClosure rs(edge_queue); + process_roots(&rs); +} + +class RootSetClosureMarkScope : public MarkScope { +}; + +void RootSetClosure::process_roots(OopClosure* closure) { + SaveRestoreCLDClaimBits save_restore_cld_claim_bits; + RootSetClosureMarkScope mark_scope; + + CLDToOopClosure cldt_closure(closure); + ClassLoaderDataGraph::always_strong_cld_do(&cldt_closure); + CodeBlobToOopClosure blobs(closure, false); + Threads::oops_do(closure, &blobs); + ObjectSynchronizer::oops_do(closure); + Universe::oops_do(closure); + JNIHandles::oops_do(closure); + JvmtiExport::oops_do(closure); + SystemDictionary::always_strong_oops_do(closure); + Management::oops_do(closure); + StringTable::oops_do(closure); + AOTLoader::oops_do(closure); +} diff --git a/src/hotspot/share/jfr/leakprofiler/chains/rootSetClosure.hpp b/src/hotspot/share/jfr/leakprofiler/chains/rootSetClosure.hpp new file mode 100644 index 00000000000..41cb8ba2a4f --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/chains/rootSetClosure.hpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_JFR_LEAKPROFILER_CHAINS_ROOTSETCLOSURE_HPP +#define SHARE_VM_JFR_LEAKPROFILER_CHAINS_ROOTSETCLOSURE_HPP + +#include "memory/iterator.hpp" +#include "oops/oop.hpp" + +class EdgeQueue; + +class RootSetClosure: public ExtendedOopClosure { + private: + RootSetClosure(EdgeQueue* edge_queue); + EdgeQueue* _edge_queue; + void closure_impl(const oop* reference, const oop pointee); + public: + static void add_to_queue(EdgeQueue* edge_queue); + static void process_roots(OopClosure* closure); + + virtual void do_oop(oop* reference); + virtual void do_oop(narrowOop* reference); +}; + +#endif // SHARE_VM_JFR_LEAKPROFILER_CHAINS_ROOTSETCLOSURE_HPP diff --git a/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleCheckpoint.cpp b/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleCheckpoint.cpp new file mode 100644 index 00000000000..ab24853463f --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleCheckpoint.cpp @@ -0,0 +1,320 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "jfr/jfrEvents.hpp" +#include "jfr/recorder/jfrRecorder.hpp" +#include "jfr/recorder/checkpoint/jfrCheckpointWriter.hpp" +#include "jfr/recorder/checkpoint/types/traceid/jfrTraceId.inline.hpp" +#include "jfr/recorder/stacktrace/jfrStackTraceRepository.hpp" +#include "jfr/leakprofiler/chains/edgeStore.hpp" +#include "jfr/leakprofiler/chains/objectSampleMarker.hpp" +#include "jfr/leakprofiler/checkpoint/objectSampleCheckpoint.hpp" +#include "jfr/leakprofiler/checkpoint/objectSampleWriter.hpp" +#include "jfr/leakprofiler/leakProfiler.hpp" +#include "jfr/leakprofiler/sampling/objectSample.hpp" +#include "jfr/leakprofiler/sampling/objectSampler.hpp" +#include "jfr/leakprofiler/utilities/rootType.hpp" +#include "jfr/metadata/jfrSerializer.hpp" +#include "runtime/interfaceSupport.inline.hpp" +#include "runtime/mutexLocker.hpp" +#include "runtime/thread.inline.hpp" + +template +static void do_samples(ObjectSample* sample, const ObjectSample* const end, SampleProcessor& processor) { + assert(sample != NULL, "invariant"); + while (sample != end) { + processor.sample_do(sample); + sample = sample->next(); + } +} + +class RootSystemType : public JfrSerializer { + public: + void serialize(JfrCheckpointWriter& writer) { + const u4 nof_root_systems = OldObjectRoot::_number_of_systems; + writer.write_count(nof_root_systems); + for (u4 i = 0; i < nof_root_systems; ++i) { + writer.write_key(i); + writer.write(OldObjectRoot::system_description((OldObjectRoot::System)i)); + } + } +}; + +class RootType : public JfrSerializer { + public: + void serialize(JfrCheckpointWriter& writer) { + const u4 nof_root_types = OldObjectRoot::_number_of_types; + writer.write_count(nof_root_types); + for (u4 i = 0; i < nof_root_types; ++i) { + writer.write_key(i); + writer.write(OldObjectRoot::type_description((OldObjectRoot::Type)i)); + } + } +}; + +class CheckpointInstall { + private: + const JfrCheckpointBlobHandle& _cp; + public: + CheckpointInstall(const JfrCheckpointBlobHandle& cp) : _cp(cp) {} + void sample_do(ObjectSample* sample) { + assert(sample != NULL, "invariant"); + if (!sample->is_dead()) { + sample->set_klass_checkpoint(_cp); + } + } +}; + +class CheckpointWrite { + private: + JfrCheckpointWriter& _writer; + const jlong _last_sweep; + public: + CheckpointWrite(JfrCheckpointWriter& writer, jlong last_sweep) : _writer(writer), _last_sweep(last_sweep) {} + void sample_do(ObjectSample* sample) { + assert(sample != NULL, "invariant"); + if (sample->is_alive_and_older_than(_last_sweep)) { + if (sample->has_thread_checkpoint()) { + const JfrCheckpointBlobHandle& thread_cp = sample->thread_checkpoint(); + thread_cp->exclusive_write(_writer); + } + if (sample->has_klass_checkpoint()) { + const JfrCheckpointBlobHandle& klass_cp = sample->klass_checkpoint(); + klass_cp->exclusive_write(_writer); + } + } + } +}; + +class CheckpointStateReset { + private: + const jlong _last_sweep; + public: + CheckpointStateReset(jlong last_sweep) : _last_sweep(last_sweep) {} + void sample_do(ObjectSample* sample) { + assert(sample != NULL, "invariant"); + if (sample->is_alive_and_older_than(_last_sweep)) { + if (sample->has_thread_checkpoint()) { + const JfrCheckpointBlobHandle& thread_cp = sample->thread_checkpoint(); + thread_cp->reset_write_state(); + } + if (sample->has_klass_checkpoint()) { + const JfrCheckpointBlobHandle& klass_cp = sample->klass_checkpoint(); + klass_cp->reset_write_state(); + } + } + } +}; + +class StackTraceWrite { + private: + JfrStackTraceRepository& _stack_trace_repo; + JfrCheckpointWriter& _writer; + int _count; + public: + StackTraceWrite(JfrStackTraceRepository& stack_trace_repo, JfrCheckpointWriter& writer) : + _stack_trace_repo(stack_trace_repo), _writer(writer), _count(0) { + JfrStacktrace_lock->lock(); + } + ~StackTraceWrite() { + assert(JfrStacktrace_lock->owned_by_self(), "invariant"); + JfrStacktrace_lock->unlock(); + } + + void sample_do(ObjectSample* sample) { + assert(sample != NULL, "invariant"); + if (!sample->is_dead()) { + if (sample->has_stack_trace()) { + JfrTraceId::use(sample->klass(), true); + _stack_trace_repo.write(_writer, sample->stack_trace_id(), sample->stack_trace_hash()); + ++_count; + } + } + } + + int count() const { + return _count; + } +}; + +class SampleMark { + private: + ObjectSampleMarker& _marker; + jlong _last_sweep; + int _count; + public: + SampleMark(ObjectSampleMarker& marker, jlong last_sweep) : _marker(marker), + _last_sweep(last_sweep), + _count(0) {} + void sample_do(ObjectSample* sample) { + assert(sample != NULL, "invariant"); + if (sample->is_alive_and_older_than(_last_sweep)) { + _marker.mark(sample->object()); + ++_count; + } + } + + int count() const { + return _count; + } +}; + +void ObjectSampleCheckpoint::install(JfrCheckpointWriter& writer, bool class_unload, bool resume) { + assert(class_unload ? SafepointSynchronize::is_at_safepoint() : LeakProfiler::is_suspended(), "invariant"); + + if (!writer.has_data()) { + if (!class_unload) { + LeakProfiler::resume(); + } + assert(LeakProfiler::is_running(), "invariant"); + return; + } + + assert(writer.has_data(), "invariant"); + const JfrCheckpointBlobHandle h_cp = writer.checkpoint_blob(); + + const ObjectSampler* const object_sampler = LeakProfiler::object_sampler(); + assert(object_sampler != NULL, "invariant"); + + ObjectSample* const last = const_cast(object_sampler->last()); + const ObjectSample* const last_resolved = object_sampler->last_resolved(); + CheckpointInstall install(h_cp); + + if (class_unload) { + if (last != NULL) { + // all samples need the class unload information + do_samples(last, NULL, install); + } + assert(LeakProfiler::is_running(), "invariant"); + return; + } + + // only new samples since last resolved checkpoint + if (last != last_resolved) { + do_samples(last, last_resolved, install); + if (resume) { + const_cast(object_sampler)->set_last_resolved(last); + } + } + assert(LeakProfiler::is_suspended(), "invariant"); + if (resume) { + LeakProfiler::resume(); + assert(LeakProfiler::is_running(), "invariant"); + } +} + +void ObjectSampleCheckpoint::write(const EdgeStore* edge_store, bool emit_all, Thread* thread) { + assert(edge_store != NULL, "invariant"); + assert(thread != NULL, "invariant"); + static bool types_registered = false; + if (!types_registered) { + JfrSerializer::register_serializer(TYPE_OLDOBJECTROOTSYSTEM, false, true, new RootSystemType()); + JfrSerializer::register_serializer(TYPE_OLDOBJECTROOTTYPE, false, true, new RootType()); + types_registered = true; + } + const ObjectSampler* const object_sampler = LeakProfiler::object_sampler(); + assert(object_sampler != NULL, "invariant"); + const jlong last_sweep = emit_all ? max_jlong : object_sampler->last_sweep().value(); + ObjectSample* const last = const_cast(object_sampler->last()); + { + JfrCheckpointWriter writer(false, false, thread); + CheckpointWrite checkpoint_write(writer, last_sweep); + do_samples(last, NULL, checkpoint_write); + } + CheckpointStateReset state_reset(last_sweep); + do_samples(last, NULL, state_reset); + if (!edge_store->is_empty()) { + // java object and chain representations + JfrCheckpointWriter writer(false, true, thread); + ObjectSampleWriter osw(writer, edge_store); + edge_store->iterate_edges(osw); + } +} + +WriteObjectSampleStacktrace::WriteObjectSampleStacktrace(JfrStackTraceRepository& repo) : + _stack_trace_repo(repo) { +} + +bool WriteObjectSampleStacktrace::process() { + assert(SafepointSynchronize::is_at_safepoint(), "invariant"); + if (!LeakProfiler::is_running()) { + return true; + } + // Suspend the LeakProfiler subsystem + // to ensure stable samples even + // after we return from the safepoint. + LeakProfiler::suspend(); + assert(!LeakProfiler::is_running(), "invariant"); + assert(LeakProfiler::is_suspended(), "invariant"); + + const ObjectSampler* object_sampler = LeakProfiler::object_sampler(); + assert(object_sampler != NULL, "invariant"); + assert(LeakProfiler::is_suspended(), "invariant"); + + ObjectSample* const last = const_cast(object_sampler->last()); + const ObjectSample* const last_resolved = object_sampler->last_resolved(); + if (last == last_resolved) { + assert(LeakProfiler::is_suspended(), "invariant"); + return true; + } + + JfrCheckpointWriter writer(false, true, Thread::current()); + const JfrCheckpointContext ctx = writer.context(); + + writer.write_type(TYPE_STACKTRACE); + const jlong count_offset = writer.reserve(sizeof(u4)); + + int count = 0; + { + StackTraceWrite stack_trace_write(_stack_trace_repo, writer); // JfrStacktrace_lock + do_samples(last, last_resolved, stack_trace_write); + count = stack_trace_write.count(); + } + if (count == 0) { + writer.set_context(ctx); + assert(LeakProfiler::is_suspended(), "invariant"); + return true; + } + assert(count > 0, "invariant"); + writer.write_count((u4)count, count_offset); + JfrStackTraceRepository::write_metadata(writer); + + ObjectSampleCheckpoint::install(writer, false, false); + assert(LeakProfiler::is_suspended(), "invariant"); + return true; +} + +int ObjectSampleCheckpoint::mark(ObjectSampleMarker& marker, bool emit_all) { + const ObjectSampler* object_sampler = LeakProfiler::object_sampler(); + assert(object_sampler != NULL, "invariant"); + ObjectSample* const last = const_cast(object_sampler->last()); + if (last == NULL) { + return 0; + } + const jlong last_sweep = emit_all ? max_jlong : object_sampler->last_sweep().value(); + SampleMark mark(marker, last_sweep); + do_samples(last, NULL, mark); + return mark.count(); +} diff --git a/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleCheckpoint.hpp b/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleCheckpoint.hpp new file mode 100644 index 00000000000..8735e3aae1c --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleCheckpoint.hpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_LEAKPROFILER_CHECKPOINT_OBJECTSAMPLECHECKPOINT_HPP +#define SHARE_VM_LEAKPROFILER_CHECKPOINT_OBJECTSAMPLECHECKPOINT_HPP + +#include "memory/allocation.hpp" +#include "utilities/exceptions.hpp" + +class EdgeStore; +class JfrStackTraceRepository; +class JfrCheckpointWriter; +class ObjectSampleMarker; + +class ObjectSampleCheckpoint : AllStatic { + public: + static void install(JfrCheckpointWriter& writer, bool class_unload, bool resume); + static void write(const EdgeStore* edge_store, bool emit_all, Thread* thread); + static int mark(ObjectSampleMarker& marker, bool emit_all); +}; + +class WriteObjectSampleStacktrace : public StackObj { + private: + JfrStackTraceRepository& _stack_trace_repo; + public: + WriteObjectSampleStacktrace(JfrStackTraceRepository& repo); + bool process(); +}; + +#endif // SHARE_VM_LEAKPROFILER_CHECKPOINT_OBJECTSAMPLECHECKPOINT_HPP diff --git a/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleDescription.cpp b/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleDescription.cpp new file mode 100644 index 00000000000..916d36f4e6a --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleDescription.cpp @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "classfile/javaClasses.hpp" +#include "classfile/symbolTable.hpp" +#include "classfile/systemDictionary.hpp" +#include "jfr/leakprofiler/checkpoint/objectSampleDescription.hpp" +#include "jfr/recorder/checkpoint/jfrCheckpointWriter.hpp" +#include "oops/oop.inline.hpp" +#include "runtime/thread.hpp" +#include "utilities/ostream.hpp" + +static Symbol* symbol_size = NULL; + +ObjectDescriptionBuilder::ObjectDescriptionBuilder() { + reset(); +} + +void ObjectDescriptionBuilder::write_int(jint value) { + char buf[20]; + jio_snprintf(buf, sizeof(buf), "%d", value); + write_text(buf); +} + +void ObjectDescriptionBuilder::write_text(const char* text) { + if (_index == sizeof(_buffer) - 2) { + return; + } + while (*text != '\0' && _index < sizeof(_buffer) - 2) { + _buffer[_index] = *text; + _index++; + text++; + } + assert(_index < sizeof(_buffer) - 1, "index should not exceed buffer size"); + // add ellipsis if we reached end + if (_index == sizeof(_buffer) - 2) { + _buffer[_index-3] = '.'; + _buffer[_index-2] = '.'; + _buffer[_index-1] = '.'; + } + // terminate string + _buffer[_index] = '\0'; +} + +void ObjectDescriptionBuilder::reset() { + _index = 0; + _buffer[0] = '\0'; +} + +void ObjectDescriptionBuilder::print_description(outputStream* out) { + out->print("%s", (const char*)_buffer); +} + +const char* ObjectDescriptionBuilder::description() { + if (_buffer[0] == '\0') { + return NULL; + } + const size_t len = strlen(_buffer); + char* copy = NEW_RESOURCE_ARRAY(char, len + 1); + assert(copy != NULL, "invariant"); + strncpy(copy, _buffer, len + 1); + return copy; +} + +ObjectSampleDescription::ObjectSampleDescription(oop object) : + _object(object) { +} + +void ObjectSampleDescription::ensure_initialized() { + if (symbol_size == NULL) { + symbol_size = SymbolTable::new_permanent_symbol("size", Thread::current()); + } +} + +void ObjectSampleDescription::print_description(outputStream* out) { + write_object_to_buffer(); + _description.print_description(out); +} + +const char* ObjectSampleDescription::description() { + write_object_to_buffer(); + return _description.description(); +} + +void ObjectSampleDescription::write_text(const char* text) { + _description.write_text(text); +} + +void ObjectSampleDescription::write_int(jint value) { + _description.write_int(value); +} + +void ObjectSampleDescription::write_object_to_buffer() { + ensure_initialized(); + _description.reset(); + write_object_details(); +} + +void ObjectSampleDescription::write_object_details() { + Klass* klass = _object->klass(); + Symbol* class_name = klass->name(); + jint size; + + if (_object->is_a(SystemDictionary::Class_klass())) { + write_class_name(); + return; + } + + if (_object->is_a(SystemDictionary::Thread_klass())) { + write_thread_name(); + return; + } + + if (_object->is_a(SystemDictionary::ThreadGroup_klass())) { + write_thread_group_name(); + return; + } + + if (read_int_size(&size)) { + write_size(size); + return; + } +} + +void ObjectSampleDescription::write_class_name() { + assert(_object->is_a(SystemDictionary::Class_klass()), "invariant"); + const Klass* const k = java_lang_Class::as_Klass(_object); + if (k == NULL) { + // might represent a primitive + const Klass* const ak = java_lang_Class::array_klass_acquire(_object); + // If ak is NULL, this is most likely a mirror associated with a + // jvmti redefine/retransform scratch klass. We can't get any additional + // information from it. + if (ak != NULL) { + write_text(type2name(java_lang_Class::primitive_type(_object))); + } + return; + } + + if (k->is_instance_klass()) { + const InstanceKlass* ik = InstanceKlass::cast(k); + if (ik->is_anonymous()) { + return; + } + assert(!ik->is_anonymous(), "invariant"); + const Symbol* name = ik->name(); + if (name != NULL) { + write_text("Class Name: "); + write_text(name->as_klass_external_name()); + } + } +} + +void ObjectSampleDescription::write_thread_group_name() { + assert(_object->is_a(SystemDictionary::ThreadGroup_klass()), "invariant"); + const char* tg_name = java_lang_ThreadGroup::name(_object); + if (tg_name != NULL) { + write_text("Thread Group: "); + write_text(tg_name); + } +} + +void ObjectSampleDescription::write_thread_name() { + assert(_object->is_a(SystemDictionary::Thread_klass()), "invariant"); + oop name = java_lang_Thread::name(_object); + if (name != NULL) { + char* p = java_lang_String::as_utf8_string(name); + if (p != NULL) { + write_text("Thread Name: "); + write_text(p); + } + } +} + +void ObjectSampleDescription::write_size(jint size) { + if (size >= 0) { + write_text("Size: "); + write_int(size); + } +} + +bool ObjectSampleDescription::read_int_size(jint* result_size) { + fieldDescriptor fd; + Klass* klass = _object->klass(); + if (klass->is_instance_klass()) { + InstanceKlass* ik = InstanceKlass::cast(klass); + if (ik->find_field(symbol_size, vmSymbols::int_signature(), false, &fd) != NULL) { + jint size = _object->int_field(fd.offset()); + *result_size = size; + return true; + } + } + return false; +} diff --git a/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleDescription.hpp b/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleDescription.hpp new file mode 100644 index 00000000000..691e2fe551e --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleDescription.hpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_LEAKPROFILER_CHECKPOINT_OBJECTSAMPLEDESCRIPTION_HPP +#define SHARE_VM_LEAKPROFILER_CHECKPOINT_OBJECTSAMPLEDESCRIPTION_HPP + +#define OBJECT_SAMPLE_DESCRIPTION_BUFFER_SIZE 100 + +#include "memory/allocation.hpp" + +class outputStream; + +class ObjectDescriptionBuilder : public StackObj { +private: + char _buffer[OBJECT_SAMPLE_DESCRIPTION_BUFFER_SIZE]; + size_t _index; + +public: + ObjectDescriptionBuilder(); + + void write_text(const char* text); + void write_int(jint value); + void reset(); + + void print_description(outputStream* out); + const char* description(); +}; + +class ObjectSampleDescription : public StackObj { +private: + ObjectDescriptionBuilder _description; + oop _object; + + void write_text(const char* text); + void write_int(jint value); + + void write_object_details(); + void write_size(jint size); + void write_thread_name(); + void write_thread_group_name(); + void write_class_name(); + void write_object_to_buffer(); + bool is_class(Symbol* s1, const char* s2); + void ensure_initialized(); + bool read_int_size(jint* result); + +public: + ObjectSampleDescription(oop object); + void print_description(outputStream* out); + const char* description(); +}; + +#endif // SHARE_VM_LEAKPROFILER_CHECKPOINT_OBJECTSAMPLEDESCRIPTION_HPP diff --git a/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleWriter.cpp b/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleWriter.cpp new file mode 100644 index 00000000000..bd3319e035c --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleWriter.cpp @@ -0,0 +1,615 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "jfrfiles/jfrTypes.hpp" +#include "jfr/leakprofiler/chains/edge.hpp" +#include "jfr/leakprofiler/chains/edgeStore.hpp" +#include "jfr/leakprofiler/chains/edgeUtils.hpp" +#include "jfr/leakprofiler/checkpoint/objectSampleDescription.hpp" +#include "jfr/leakprofiler/checkpoint/objectSampleWriter.hpp" +#include "jfr/leakprofiler/checkpoint/rootResolver.hpp" +#include "jfr/leakprofiler/sampling/objectSampler.hpp" +#include "jfr/leakprofiler/utilities/rootType.hpp" +#include "jfr/leakprofiler/utilities/unifiedOop.hpp" +#include "jfr/recorder/checkpoint/types/jfrTypeSetUtils.hpp" +#include "jfr/recorder/checkpoint/types/jfrTypeSetWriter.hpp" +#include "oops/oop.inline.hpp" +#include "oops/symbol.hpp" +#include "utilities/growableArray.hpp" + +template +class ObjectSampleAuxInfo : public ResourceObj { + public: + Data _data; + traceid _id; + ObjectSampleAuxInfo() : _data(), _id(0) {} +}; + +class ObjectSampleArrayData { + public: + int _array_size; + int _array_index; + ObjectSampleArrayData() : _array_size(0), _array_index(0) {} +}; + +class ObjectSampleFieldInfo : public ResourceObj { + public: + const Symbol* _field_name_symbol; + jshort _field_modifiers; + ObjectSampleFieldInfo() : _field_name_symbol(NULL), _field_modifiers(0) {} +}; + +class ObjectSampleRootDescriptionData { + public: + const Edge* _root_edge; + const char* _description; + OldObjectRoot::System _system; + OldObjectRoot::Type _type; + ObjectSampleRootDescriptionData() : _root_edge(NULL), + _description(NULL), + _system(OldObjectRoot::_system_undetermined), + _type(OldObjectRoot::_type_undetermined) {} +}; + +class OldObjectSampleData { + public: + oop _object; + traceid _reference_id; +}; + +class ReferenceData { + public: + traceid _field_info_id; + traceid _array_info_id; + traceid _old_object_sample_id; + size_t _skip; +}; + +static int initial_storage_size = 16; + +template +class SampleSet : public ResourceObj { + private: + GrowableArray* _storage; + public: + SampleSet() : _storage(NULL) {} + + traceid store(Data data) { + assert(data != NULL, "invariant"); + if (_storage == NULL) { + _storage = new GrowableArray(initial_storage_size); + } + assert(_storage != NULL, "invariant"); + assert(_storage->find(data) == -1, "invariant"); + _storage->append(data); + return data->_id; + } + + size_t size() const { + return _storage != NULL ? (size_t)_storage->length() : 0; + } + + template + void iterate(Functor& functor) { + if (_storage != NULL) { + for (int i = 0; i < _storage->length(); ++i) { + functor(_storage->at(i)); + } + } + } + + const GrowableArray& storage() const { + return *_storage; + } +}; + +typedef ObjectSampleAuxInfo ObjectSampleArrayInfo; +typedef ObjectSampleAuxInfo ObjectSampleRootDescriptionInfo; +typedef ObjectSampleAuxInfo OldObjectSampleInfo; +typedef ObjectSampleAuxInfo ReferenceInfo; + +class FieldTable : public ResourceObj { + template class, + typename, + size_t> + friend class HashTableHost; + typedef HashTableHost FieldInfoTable; + public: + typedef FieldInfoTable::HashEntry FieldInfoEntry; + + private: + static traceid _field_id_counter; + FieldInfoTable* _table; + + void assign_id(FieldInfoEntry* entry) { + assert(entry != NULL, "invariant"); + entry->set_id(++_field_id_counter); + } + + bool equals(const ObjectSampleFieldInfo* query, uintptr_t hash, const FieldInfoEntry* entry) { + assert(hash == entry->hash(), "invariant"); + assert(query != NULL, "invariant"); + const ObjectSampleFieldInfo* stored = entry->literal(); + assert(stored != NULL, "invariant"); + assert(stored->_field_name_symbol->identity_hash() == query->_field_name_symbol->identity_hash(), "invariant"); + return stored->_field_modifiers == query->_field_modifiers; + } + + public: + FieldTable() : _table(new FieldInfoTable(this)) {} + ~FieldTable() { + assert(_table != NULL, "invariant"); + delete _table; + } + + traceid store(const ObjectSampleFieldInfo* field_info) { + assert(field_info != NULL, "invariant"); + const FieldInfoEntry& entry =_table->lookup_put(field_info, + field_info->_field_name_symbol->identity_hash()); + return entry.id(); + } + + size_t size() const { + return _table->cardinality(); + } + + template + void iterate(T& functor) const { + _table->iterate_entry(functor); + } +}; + +traceid FieldTable::_field_id_counter = 0; + +typedef SampleSet SampleInfo; +typedef SampleSet RefInfo; +typedef SampleSet ArrayInfo; +typedef SampleSet RootDescriptionInfo; + +static SampleInfo* sample_infos = NULL; +static RefInfo* ref_infos = NULL; +static ArrayInfo* array_infos = NULL; +static FieldTable* field_infos = NULL; +static RootDescriptionInfo* root_infos = NULL; + +int __write_sample_info__(JfrCheckpointWriter* writer, JfrArtifactSet* unused, const void* si) { + assert(writer != NULL, "invariant"); + assert(si != NULL, "invariant"); + const OldObjectSampleInfo* const oosi = (const OldObjectSampleInfo*)si; + oop object = oosi->_data._object; + assert(object != NULL, "invariant"); + writer->write(oosi->_id); + writer->write((u8)(const HeapWord*)object); + writer->write(const_cast(object->klass())); + ObjectSampleDescription od(object); + writer->write(od.description()); + writer->write(oosi->_data._reference_id); + return 1; +} + +typedef JfrArtifactWriterImplHost SampleWriterImpl; +typedef JfrArtifactWriterHost SampleWriter; + +static void write_sample_infos(JfrCheckpointWriter& writer) { + if (sample_infos != NULL) { + SampleWriter sw(&writer, NULL, false); + sample_infos->iterate(sw); + } +} + +int __write_reference_info__(JfrCheckpointWriter* writer, JfrArtifactSet* unused, const void* ri) { + assert(writer != NULL, "invariant"); + assert(ri != NULL, "invariant"); + const ReferenceInfo* const ref_info = (const ReferenceInfo*)ri; + writer->write(ref_info->_id); + writer->write(ref_info->_data._array_info_id); + writer->write(ref_info->_data._field_info_id); + writer->write(ref_info->_data._old_object_sample_id); + writer->write((s4)ref_info->_data._skip); + return 1; +} + +typedef JfrArtifactWriterImplHost ReferenceWriterImpl; +typedef JfrArtifactWriterHost ReferenceWriter; + +static void write_reference_infos(JfrCheckpointWriter& writer) { + if (ref_infos != NULL) { + ReferenceWriter rw(&writer, NULL, false); + ref_infos->iterate(rw); + } +} + +int __write_array_info__(JfrCheckpointWriter* writer, JfrArtifactSet* unused, const void* ai) { + assert(writer != NULL, "invariant"); + assert(ai != NULL, "invariant"); + const ObjectSampleArrayInfo* const osai = (const ObjectSampleArrayInfo*)ai; + writer->write(osai->_id); + writer->write(osai->_data._array_size); + writer->write(osai->_data._array_index); + return 1; +} + +static traceid get_array_info_id(const Edge& edge, traceid id) { + if (edge.is_root() || !EdgeUtils::is_array_element(edge)) { + return 0; + } + if (array_infos == NULL) { + array_infos = new ArrayInfo(); + } + assert(array_infos != NULL, "invariant"); + + ObjectSampleArrayInfo* const osai = new ObjectSampleArrayInfo(); + assert(osai != NULL, "invariant"); + osai->_id = id; + osai->_data._array_size = EdgeUtils::array_size(edge); + osai->_data._array_index = EdgeUtils::array_index(edge); + return array_infos->store(osai); +} + +typedef JfrArtifactWriterImplHost ArrayWriterImpl; +typedef JfrArtifactWriterHost ArrayWriter; + +static void write_array_infos(JfrCheckpointWriter& writer) { + if (array_infos != NULL) { + ArrayWriter aw(&writer, NULL, false); + array_infos->iterate(aw); + } +} + +int __write_field_info__(JfrCheckpointWriter* writer, JfrArtifactSet* unused, const void* fi) { + assert(writer != NULL, "invariant"); + assert(fi != NULL, "invariant"); + const FieldTable::FieldInfoEntry* field_info_entry = (const FieldTable::FieldInfoEntry*)fi; + writer->write(field_info_entry->id()); + const ObjectSampleFieldInfo* const osfi = field_info_entry->literal(); + writer->write(osfi->_field_name_symbol->as_C_string()); + writer->write(osfi->_field_modifiers); + return 1; +} + +static traceid get_field_info_id(const Edge& edge) { + if (edge.is_root()) { + return 0; + } + + assert(!EdgeUtils::is_array_element(edge), "invariant"); + const Symbol* const field_name_symbol = EdgeUtils::field_name_symbol(edge); + if (field_name_symbol == NULL) { + return 0; + } + + if (field_infos == NULL) { + field_infos = new FieldTable(); + } + assert(field_infos != NULL, "invariant"); + + ObjectSampleFieldInfo* const osfi = new ObjectSampleFieldInfo(); + assert(osfi != NULL, "invariant"); + osfi->_field_name_symbol = field_name_symbol; + osfi->_field_modifiers = EdgeUtils::field_modifiers(edge); + return field_infos->store(osfi); +} + +typedef JfrArtifactWriterImplHost FieldWriterImpl; +typedef JfrArtifactWriterHost FieldWriter; + +static void write_field_infos(JfrCheckpointWriter& writer) { + if (field_infos != NULL) { + FieldWriter fw(&writer, NULL, false); + field_infos->iterate(fw); + } +} + +static const char* description(const ObjectSampleRootDescriptionInfo* osdi) { + assert(osdi != NULL, "invariant"); + + if (osdi->_data._description == NULL) { + return NULL; + } + + ObjectDescriptionBuilder description; + if (osdi->_data._system == OldObjectRoot::_threads) { + description.write_text("Thread Name: "); + } + description.write_text(osdi->_data._description); + return description.description(); +} + +int __write_root_description_info__(JfrCheckpointWriter* writer, JfrArtifactSet* unused, const void* di) { + assert(writer != NULL, "invariant"); + assert(di != NULL, "invariant"); + const ObjectSampleRootDescriptionInfo* const osdi = (const ObjectSampleRootDescriptionInfo*)di; + writer->write(osdi->_id); + writer->write(description(osdi)); + writer->write(osdi->_data._system); + writer->write(osdi->_data._type); + return 1; +} + +static traceid get_root_description_info_id(const Edge& edge, traceid id) { + assert(edge.is_root(), "invariant"); + if (EdgeUtils::is_leak_edge(edge)) { + return 0; + } + + if (root_infos == NULL) { + root_infos = new RootDescriptionInfo(); + } + assert(root_infos != NULL, "invariant"); + ObjectSampleRootDescriptionInfo* const oodi = new ObjectSampleRootDescriptionInfo(); + oodi->_id = id; + oodi->_data._root_edge = &edge; + return root_infos->store(oodi); +} + +typedef JfrArtifactWriterImplHost RootDescriptionWriterImpl; +typedef JfrArtifactWriterHost RootDescriptionWriter; + + +int _edge_reference_compare_(uintptr_t lhs, uintptr_t rhs) { + return lhs > rhs ? 1 : (lhs < rhs) ? -1 : 0; +} + +int _root_desc_compare_(const ObjectSampleRootDescriptionInfo*const & lhs, const ObjectSampleRootDescriptionInfo* const& rhs) { + const uintptr_t lhs_ref = (uintptr_t)lhs->_data._root_edge->reference(); + const uintptr_t rhs_ref = (uintptr_t)rhs->_data._root_edge->reference(); + return _edge_reference_compare_(lhs_ref, rhs_ref); +} + +static int find_sorted(const RootCallbackInfo& callback_info, + const GrowableArray* arr, + int length, + bool& found) { + assert(arr != NULL, "invariant"); + assert(length >= 0, "invariant"); + assert(length <= arr->length(), "invariant"); + + found = false; + int min = 0; + int max = length; + while (max >= min) { + const int mid = (int)(((uint)max + min) / 2); + int diff = _edge_reference_compare_((uintptr_t)callback_info._high, + (uintptr_t)arr->at(mid)->_data._root_edge->reference()); + if (diff > 0) { + min = mid + 1; + } else if (diff < 0) { + max = mid - 1; + } else { + found = true; + return mid; + } + } + return min; +} + +class RootResolutionSet : public ResourceObj, public RootCallback { + private: + GrowableArray* _unresolved_roots; + + const uintptr_t high() const { + return (uintptr_t)_unresolved_roots->last()->_data._root_edge->reference(); + } + + const uintptr_t low() const { + return (uintptr_t)_unresolved_roots->first()->_data._root_edge->reference(); + } + + bool in_set_address_range(const RootCallbackInfo& callback_info) const { + assert(callback_info._low == NULL, "invariant"); + const uintptr_t addr = (uintptr_t)callback_info._high; + return low() <= addr && high() >= addr; + } + + int compare_to_range(const RootCallbackInfo& callback_info) const { + assert(callback_info._high != NULL, "invariant"); + assert(callback_info._low != NULL, "invariant"); + + for (int i = 0; i < _unresolved_roots->length(); ++i) { + const uintptr_t ref_addr = (uintptr_t)_unresolved_roots->at(i)->_data._root_edge->reference(); + if ((uintptr_t)callback_info._low <= ref_addr && (uintptr_t)callback_info._high >= ref_addr) { + return i; + } + } + return -1; + } + + int exact(const RootCallbackInfo& callback_info) const { + assert(callback_info._high != NULL, "invariant"); + assert(in_set_address_range(callback_info), "invariant"); + + bool found; + const int idx = find_sorted(callback_info, _unresolved_roots, _unresolved_roots->length(), found); + return found ? idx : -1; + } + + bool resolve_root(const RootCallbackInfo& callback_info, int idx) const { + assert(idx >= 0, "invariant"); + assert(idx < _unresolved_roots->length(), "invariant"); + + ObjectSampleRootDescriptionInfo* const desc = + const_cast(_unresolved_roots->at(idx)); + assert(desc != NULL, "invariant"); + assert((uintptr_t)callback_info._high == (uintptr_t)desc->_data._root_edge->reference(), "invariant"); + + desc->_data._system = callback_info._system; + desc->_data._type = callback_info._type; + + if (callback_info._system == OldObjectRoot::_threads) { + const JavaThread* jt = (const JavaThread*)callback_info._context; + assert(jt != NULL, "invariant"); + desc->_data._description = jt->name(); + } + + _unresolved_roots->remove_at(idx); + return _unresolved_roots->is_empty(); + } + + public: + RootResolutionSet(RootDescriptionInfo* info) : _unresolved_roots(NULL) { + assert(info != NULL, "invariant"); + // construct a sorted copy + const GrowableArray& info_storage = info->storage(); + const int length = info_storage.length(); + _unresolved_roots = new GrowableArray(length); + assert(_unresolved_roots != NULL, "invariant"); + + for (int i = 0; i < length; ++i) { + _unresolved_roots->insert_sorted<_root_desc_compare_>(info_storage.at(i)); + } + } + + bool process(const RootCallbackInfo& callback_info) { + if (NULL == callback_info._low) { + if (in_set_address_range(callback_info)) { + const int idx = exact(callback_info); + return idx == -1 ? false : resolve_root(callback_info, idx); + } + return false; + } + assert(callback_info._low != NULL, "invariant"); + const int idx = compare_to_range(callback_info); + return idx == -1 ? false : resolve_root(callback_info, idx); + } + + int entries() const { + return _unresolved_roots->length(); + } + + const void* at(int idx) const { + assert(idx >= 0, "invariant"); + assert(idx < _unresolved_roots->length(), "invariant"); + return _unresolved_roots->at(idx)->_data._root_edge->reference(); + } +}; + +static void write_root_descriptors(JfrCheckpointWriter& writer) { + if (root_infos != NULL) { + // resolve roots + RootResolutionSet rrs(root_infos); + RootResolver::resolve(rrs); + // write roots + RootDescriptionWriter rw(&writer, NULL, false); + root_infos->iterate(rw); + } +} + +static void add_old_object_sample_info(const Edge* current, traceid id) { + assert(current != NULL, "invariant"); + if (sample_infos == NULL) { + sample_infos = new SampleInfo(); + } + assert(sample_infos != NULL, "invariant"); + OldObjectSampleInfo* const oosi = new OldObjectSampleInfo(); + assert(oosi != NULL, "invariant"); + oosi->_id = id; + oosi->_data._object = current->pointee(); + oosi->_data._reference_id = current->is_root() ? (traceid)0 : id; + sample_infos->store(oosi); +} + +static void add_reference_info(const RoutableEdge* current, traceid id, traceid parent_id) { + assert(current != NULL, "invariant"); + if (ref_infos == NULL) { + ref_infos = new RefInfo(); + } + + assert(ref_infos != NULL, "invariant"); + ReferenceInfo* const ri = new ReferenceInfo(); + assert(ri != NULL, "invariant"); + + ri->_id = id; + ri->_data._array_info_id = !current->is_skip_edge() ? get_array_info_id(*current, id) : 0; + ri->_data._field_info_id = ri->_data._array_info_id == 0 && !current->is_skip_edge() ? + get_field_info_id(*current) : (traceid)0; + ri->_data._old_object_sample_id = parent_id; + ri->_data._skip = current->skip_length(); + ref_infos->store(ri); +} + +static traceid add_root_info(const Edge* root, traceid id) { + assert(root != NULL, "invariant"); + assert(root->is_root(), "invariant"); + return get_root_description_info_id(*root, id); +} + +void ObjectSampleWriter::write(const RoutableEdge* edge) { + assert(edge != NULL, "invariant"); + const traceid id = _store->get_id(edge); + add_old_object_sample_info(edge, id); + const RoutableEdge* parent = edge->logical_parent(); + if (parent != NULL) { + add_reference_info(edge, id, _store->get_id(parent)); + } else { + assert(edge->is_root(), "invariant"); + add_root_info(edge, id); + } +} + +ObjectSampleWriter::ObjectSampleWriter(JfrCheckpointWriter& writer, const EdgeStore* store) : + _writer(writer), + _store(store) { + assert(store != NULL, "invariant"); + assert(store->number_of_entries() > 0, "invariant"); + sample_infos = NULL; + ref_infos = NULL; + array_infos = NULL; + field_infos = NULL; + root_infos = NULL; +} + +ObjectSampleWriter::~ObjectSampleWriter() { + write_sample_infos(_writer); + write_reference_infos(_writer); + write_array_infos(_writer); + write_field_infos(_writer); + write_root_descriptors(_writer); +} + +void ObjectSampleWriter::write_chain(const RoutableEdge& edge) { + assert(EdgeUtils::is_leak_edge(edge), "invariant"); + if (edge.processed()) { + return; + } + EdgeUtils::collapse_chain(edge); + const RoutableEdge* current = &edge; + while (current != NULL) { + if (current->processed()) { + return; + } + write(current); + current->set_processed(); + current = current->logical_parent(); + } +} + +bool ObjectSampleWriter::operator()(const RoutableEdge& edge) { + if (EdgeUtils::is_leak_edge(edge)) { + write_chain(edge); + } + return true; +} diff --git a/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleWriter.hpp b/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleWriter.hpp new file mode 100644 index 00000000000..92ccb0301e2 --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleWriter.hpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_LEAKPROFILER_CHECKPOINT_OBJECTSAMPLEWRITER_HPP +#define SHARE_VM_LEAKPROFILER_CHECKPOINT_OBJECTSAMPLEWRITER_HPP + +#include "memory/allocation.hpp" + +class Edge; +class EdgeStore; +class JfrCheckpointWriter; +class RoutableEdge; + +class ObjectSampleWriter : public StackObj { + private: + JfrCheckpointWriter& _writer; + const EdgeStore* const _store; + + void write(const RoutableEdge* edge); + void write_chain(const RoutableEdge& edge); + + public: + ObjectSampleWriter(JfrCheckpointWriter& writer, const EdgeStore* store); + ~ObjectSampleWriter(); + + bool operator()(const RoutableEdge& edge); +}; + +#endif // SHARE_VM_LEAKPROFILER_CHECKPOINT_OBJECTSAMPLEWRITER_HPP diff --git a/src/hotspot/share/jfr/leakprofiler/checkpoint/rootResolver.cpp b/src/hotspot/share/jfr/leakprofiler/checkpoint/rootResolver.cpp new file mode 100644 index 00000000000..b7045adbd8a --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/checkpoint/rootResolver.cpp @@ -0,0 +1,437 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "aot/aotLoader.hpp" +#include "classfile/stringTable.hpp" +#include "gc/shared/strongRootsScope.hpp" +#include "jfr/leakprofiler/utilities/unifiedOop.hpp" +#include "jfr/leakprofiler/checkpoint/rootResolver.hpp" +#include "memory/iterator.hpp" +#include "oops/klass.hpp" +#include "oops/markOop.hpp" +#include "oops/oop.hpp" +#include "prims/jvmtiThreadState.hpp" +#include "prims/privilegedStack.hpp" +#include "runtime/frame.inline.hpp" +#include "runtime/mutexLocker.hpp" +#include "runtime/threadSMR.inline.hpp" +#include "runtime/vframe_hp.hpp" +#include "services/management.hpp" +#include "utilities/growableArray.hpp" + +class ReferenceLocateClosure : public OopClosure { + protected: + RootCallback& _callback; + RootCallbackInfo _info; + bool _complete; + + void do_oop_shared(const void* ref); + + public: + ReferenceLocateClosure(RootCallback& callback, + OldObjectRoot::System system, + OldObjectRoot::Type type, + const void* context) : _callback(callback), + _info(), + _complete(false) { + _info._high = NULL; + _info._low = NULL; + _info._system = system; + _info._type = type; + _info._context = context; + } + + virtual void do_oop(oop* ref); + virtual void do_oop(narrowOop* ref); + + bool complete() const { + return _complete; + } +}; + +void ReferenceLocateClosure::do_oop_shared(const void* ref) { + assert(ref != NULL, "invariant"); + if (!_complete) { + _info._high = ref; + _complete = _callback.process(_info); + } +} + +void ReferenceLocateClosure::do_oop(oop* ref) { + do_oop_shared(ref); +} + +void ReferenceLocateClosure::do_oop(narrowOop* ref) { + do_oop_shared(ref); +} + +class ReferenceToRootClosure : public StackObj { + private: + RootCallback& _callback; + RootCallbackInfo _info; + bool _complete; + + bool do_cldg_roots(); + bool do_object_synchronizer_roots(); + bool do_universe_roots(); + bool do_jni_handle_roots(); + bool do_jvmti_roots(); + bool do_system_dictionary_roots(); + bool do_management_roots(); + bool do_string_table_roots(); + bool do_aot_loader_roots(); + + bool do_roots(); + + public: + ReferenceToRootClosure(RootCallback& callback) : _callback(callback), + _info(), + _complete(false) { + _info._high = NULL; + _info._low = NULL; + _info._context = NULL; + _info._system = OldObjectRoot::_system_undetermined; + _info._type = OldObjectRoot::_type_undetermined; + + assert_locked_or_safepoint(Threads_lock); + do_roots(); + } + + bool complete() const { + return _complete; + } +}; + +bool ReferenceToRootClosure::do_cldg_roots() { + assert(!complete(), "invariant"); + ReferenceLocateClosure rlc(_callback, OldObjectRoot::_class_loader_data, OldObjectRoot::_type_undetermined, NULL); + CLDToOopClosure cldt_closure(&rlc); + ClassLoaderDataGraph::always_strong_cld_do(&cldt_closure); + return rlc.complete(); +} + +bool ReferenceToRootClosure::do_object_synchronizer_roots() { + assert(!complete(), "invariant"); + ReferenceLocateClosure rlc(_callback, OldObjectRoot::_object_synchronizer, OldObjectRoot::_type_undetermined, NULL); + ObjectSynchronizer::oops_do(&rlc); + return rlc.complete(); +} + +bool ReferenceToRootClosure::do_universe_roots() { + assert(!complete(), "invariant"); + ReferenceLocateClosure rlc(_callback, OldObjectRoot::_universe, OldObjectRoot::_type_undetermined, NULL); + Universe::oops_do(&rlc); + return rlc.complete(); +} + +bool ReferenceToRootClosure::do_jni_handle_roots() { + assert(!complete(), "invariant"); + ReferenceLocateClosure rlc(_callback, OldObjectRoot::_global_jni_handles, OldObjectRoot::_global_jni_handle, NULL); + JNIHandles::oops_do(&rlc); + return rlc.complete(); +} + +bool ReferenceToRootClosure::do_jvmti_roots() { + assert(!complete(), "invariant"); + ReferenceLocateClosure rlc(_callback, OldObjectRoot::_jvmti, OldObjectRoot::_global_jni_handle, NULL); + JvmtiExport::oops_do(&rlc); + return rlc.complete(); +} + +bool ReferenceToRootClosure::do_system_dictionary_roots() { + assert(!complete(), "invariant"); + ReferenceLocateClosure rlc(_callback, OldObjectRoot::_system_dictionary, OldObjectRoot::_type_undetermined, NULL); + SystemDictionary::oops_do(&rlc); + return rlc.complete(); +} + +bool ReferenceToRootClosure::do_management_roots() { + assert(!complete(), "invariant"); + ReferenceLocateClosure rlc(_callback, OldObjectRoot::_management, OldObjectRoot::_type_undetermined, NULL); + Management::oops_do(&rlc); + return rlc.complete(); +} + +bool ReferenceToRootClosure::do_string_table_roots() { + assert(!complete(), "invariant"); + ReferenceLocateClosure rlc(_callback, OldObjectRoot::_string_table, OldObjectRoot::_type_undetermined, NULL); + StringTable::oops_do(&rlc); + return rlc.complete(); +} + +bool ReferenceToRootClosure::do_aot_loader_roots() { + assert(!complete(), "invariant"); + ReferenceLocateClosure rcl(_callback, OldObjectRoot::_aot, OldObjectRoot::_type_undetermined, NULL); + AOTLoader::oops_do(&rcl); + return rcl.complete(); +} + +bool ReferenceToRootClosure::do_roots() { + assert(!complete(), "invariant"); + assert(OldObjectRoot::_system_undetermined == _info._system, "invariant"); + assert(OldObjectRoot::_type_undetermined == _info._type, "invariant"); + + if (do_cldg_roots()) { + _complete = true; + return true; + } + + if (do_object_synchronizer_roots()) { + _complete = true; + return true; + } + + if (do_universe_roots()) { + _complete = true; + return true; + } + + if (do_jni_handle_roots()) { + _complete = true; + return true; + } + + if (do_jvmti_roots()) { + _complete = true; + return true; + } + + if (do_system_dictionary_roots()) { + _complete = true; + return true; + } + + if (do_management_roots()) { + _complete = true; + return true; + } + + if (do_string_table_roots()) { + _complete = true; + return true; + } + + if (do_aot_loader_roots()) { + _complete = true; + return true; + } + + return false; +} + +class ReferenceToThreadRootClosure : public StackObj { + private: + RootCallback& _callback; + bool _complete; + + bool do_java_threads_oops(JavaThread* jt); + bool do_thread_roots(JavaThread* jt); + bool do_thread_stack_fast(JavaThread* jt); + bool do_thread_stack_detailed(JavaThread* jt); + bool do_thread_jni_handles(JavaThread* jt); + bool do_thread_handle_area(JavaThread* jt); + + public: + ReferenceToThreadRootClosure(RootCallback& callback) :_callback(callback), _complete(false) { + assert_locked_or_safepoint(Threads_lock); + for (JavaThreadIteratorWithHandle jtiwh; JavaThread *jt = jtiwh.next(); ) { + if (do_thread_roots(jt)) { + return; + } + } + } + + bool complete() const { + return _complete; + } +}; + +bool ReferenceToThreadRootClosure::do_thread_handle_area(JavaThread* jt) { + assert(jt != NULL, "invariant"); + assert(!complete(), "invariant"); + ReferenceLocateClosure rcl(_callback, OldObjectRoot::_threads, OldObjectRoot::_handle_area, jt); + jt->handle_area()->oops_do(&rcl); + return rcl.complete(); +} + +bool ReferenceToThreadRootClosure::do_thread_jni_handles(JavaThread* jt) { + assert(jt != NULL, "invariant"); + assert(!complete(), "invariant"); + + ReferenceLocateClosure rcl(_callback, OldObjectRoot::_threads, OldObjectRoot::_local_jni_handle, jt); + jt->active_handles()->oops_do(&rcl); + return rcl.complete(); +} + +bool ReferenceToThreadRootClosure::do_thread_stack_fast(JavaThread* jt) { + assert(jt != NULL, "invariant"); + assert(!complete(), "invariant"); + + if (_callback.entries() == 0) { + _complete = true; + return true; + } + + RootCallbackInfo info; + info._high = NULL; + info._low = NULL; + info._context = jt; + info._system = OldObjectRoot::_threads; + info._type = OldObjectRoot::_stack_variable; + + for (int i = 0; i < _callback.entries(); ++i) { + const address adr = (address)_callback.at(i); + if (jt->is_in_usable_stack(adr)) { + info._high = adr; + _complete = _callback.process(info); + if (_complete) { + return true; + } + } + } + assert(!complete(), "invariant"); + return false; +} + +bool ReferenceToThreadRootClosure::do_thread_stack_detailed(JavaThread* jt) { + assert(jt != NULL, "invariant"); + assert(!complete(), "invariant"); + + ReferenceLocateClosure rcl(_callback, OldObjectRoot::_threads, OldObjectRoot::_stack_variable, jt); + + if (jt->has_last_Java_frame()) { + PrivilegedElement* const pelem = jt->privileged_stack_top(); + if (pelem != NULL) { + pelem->oops_do(&rcl); + if (rcl.complete()) { + return true; + } + } + + // traverse the registered growable array gc_array + // can't do this as it is not reachable from outside + + // Traverse the monitor chunks + MonitorChunk* chunk = jt->monitor_chunks(); + for (; chunk != NULL; chunk = chunk->next()) { + chunk->oops_do(&rcl); + } + + if (rcl.complete()) { + return true; + } + + // Traverse the execution stack + for (StackFrameStream fst(jt); !fst.is_done(); fst.next()) { + fst.current()->oops_do(&rcl, NULL, fst.register_map()); + } + + } // last java frame + + if (rcl.complete()) { + return true; + } + + GrowableArray* const list = jt->deferred_locals(); + if (list != NULL) { + for (int i = 0; i < list->length(); i++) { + list->at(i)->oops_do(&rcl); + } + } + + if (rcl.complete()) { + return true; + } + + // Traverse instance variables at the end since the GC may be moving things + // around using this function + /* + * // can't reach these oop* from the outside + f->do_oop((oop*) &_threadObj); + f->do_oop((oop*) &_vm_result); + f->do_oop((oop*) &_exception_oop); + f->do_oop((oop*) &_pending_async_exception); + */ + + JvmtiThreadState* const jvmti_thread_state = jt->jvmti_thread_state(); + if (jvmti_thread_state != NULL) { + jvmti_thread_state->oops_do(&rcl); + } + + return rcl.complete(); +} + +bool ReferenceToThreadRootClosure::do_java_threads_oops(JavaThread* jt) { + assert(jt != NULL, "invariant"); + assert(!complete(), "invariant"); + + ReferenceLocateClosure rcl(_callback, OldObjectRoot::_threads, OldObjectRoot::_global_jni_handle, jt); + jt->oops_do(&rcl, NULL); + return rcl.complete(); +} + +bool ReferenceToThreadRootClosure::do_thread_roots(JavaThread* jt) { + assert(jt != NULL, "invariant"); + + if (do_thread_stack_fast(jt)) { + _complete = true; + return true; + } + + if (do_thread_jni_handles(jt)) { + _complete = true; + return true; + } + + if (do_thread_handle_area(jt)) { + _complete = true; + return true; + } + + if (do_thread_stack_detailed(jt)) { + _complete = true; + return true; + } + + return false; +} + +class RootResolverMarkScope : public MarkScope { +}; + +void RootResolver::resolve(RootCallback& callback) { + + // Need to clear cld claim bit before starting + ClassLoaderDataGraph::clear_claimed_marks(); + RootResolverMarkScope mark_scope; + + // thread local roots + ReferenceToThreadRootClosure rtrc(callback); + if (rtrc.complete()) { + return; + } + // system global roots + ReferenceToRootClosure rrc(callback); +} diff --git a/src/hotspot/share/jfr/leakprofiler/checkpoint/rootResolver.hpp b/src/hotspot/share/jfr/leakprofiler/checkpoint/rootResolver.hpp new file mode 100644 index 00000000000..395838faa25 --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/checkpoint/rootResolver.hpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_JFR_LEAKPROFILER_CHECKPOINT_ROOTRESOLVER_HPP +#define SHARE_VM_JFR_LEAKPROFILER_CHECKPOINT_ROOTRESOLVER_HPP + +#include "memory/allocation.hpp" +#include "jfr/leakprofiler/utilities/rootType.hpp" +#include "oops/oopsHierarchy.hpp" + +struct RootCallbackInfo { + const void* _high; + const void* _low; + const void* _context; + OldObjectRoot::System _system; + OldObjectRoot::Type _type; +}; + +class RootCallback { + public: + virtual bool process(const RootCallbackInfo& info) = 0; + virtual int entries() const = 0; + virtual const void* at(int idx) const = 0; +}; + +class RootResolver : public AllStatic { + public: + static void resolve(RootCallback& callback); +}; + +#endif // SHARE_VM_JFR_LEAKPROFILER_CHECKPOINT_ROOTRESOLVER_HPP diff --git a/src/hotspot/share/jfr/leakprofiler/emitEventOperation.cpp b/src/hotspot/share/jfr/leakprofiler/emitEventOperation.cpp new file mode 100644 index 00000000000..940cd78190e --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/emitEventOperation.cpp @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +#include "precompiled.hpp" +#include "gc/shared/collectedHeap.hpp" +#include "jfr/jfrEvents.hpp" +#include "jfr/leakprofiler/utilities/granularTimer.hpp" +#include "jfr/leakprofiler/chains/rootSetClosure.hpp" +#include "jfr/leakprofiler/chains/edge.hpp" +#include "jfr/leakprofiler/chains/edgeQueue.hpp" +#include "jfr/leakprofiler/chains/edgeStore.hpp" +#include "jfr/leakprofiler/chains/bitset.hpp" +#include "jfr/leakprofiler/sampling/objectSample.hpp" +#include "jfr/leakprofiler/leakProfiler.hpp" +#include "jfr/leakprofiler/checkpoint/objectSampleCheckpoint.hpp" +#include "jfr/leakprofiler/sampling/objectSampler.hpp" +#include "jfr/leakprofiler/emitEventOperation.hpp" +#include "jfr/leakprofiler/chains/bfsClosure.hpp" +#include "jfr/leakprofiler/chains/dfsClosure.hpp" +#include "jfr/leakprofiler/chains/objectSampleMarker.hpp" +#include "jfr/recorder/checkpoint/jfrCheckpointWriter.hpp" +#include "jfr/support/jfrThreadId.hpp" +#include "logging/log.hpp" +#include "memory/resourceArea.hpp" +#include "memory/universe.hpp" +#include "oops/markOop.hpp" +#include "oops/oop.inline.hpp" +#include "runtime/safepoint.hpp" +#include "runtime/vmThread.hpp" +#include "utilities/globalDefinitions.hpp" + +/* The EdgeQueue is backed by directly managed virtual memory. + * We will attempt to dimension an initial reservation + * in proportion to the size of the heap (represented by heap_region). + * Initial memory reservation: 5% of the heap OR at least 32 Mb + * Commit ratio: 1 : 10 (subject to allocation granularties) + */ +static size_t edge_queue_memory_reservation(const MemRegion& heap_region) { + const size_t memory_reservation_bytes = MAX2(heap_region.byte_size() / 20, 32*M); + assert(memory_reservation_bytes >= (size_t)32*M, "invariant"); + return memory_reservation_bytes; +} + +static size_t edge_queue_memory_commit_size(size_t memory_reservation_bytes) { + const size_t memory_commit_block_size_bytes = memory_reservation_bytes / 10; + assert(memory_commit_block_size_bytes >= (size_t)3*M, "invariant"); + return memory_commit_block_size_bytes; +} + +static void log_edge_queue_summary(const EdgeQueue& edge_queue) { + log_trace(jfr, system)("EdgeQueue reserved size total: " SIZE_FORMAT " [KB]", edge_queue.reserved_size() / K); + log_trace(jfr, system)("EdgeQueue edges total: " SIZE_FORMAT, edge_queue.top()); + log_trace(jfr, system)("EdgeQueue liveset total: " SIZE_FORMAT " [KB]", edge_queue.live_set() / K); + if (edge_queue.reserved_size() > 0) { + log_trace(jfr, system)("EdgeQueue commit reserve ratio: %f\n", + ((double)edge_queue.live_set() / (double)edge_queue.reserved_size())); + } +} + +void EmitEventOperation::doit() { + assert(LeakProfiler::is_running(), "invariant"); + _object_sampler = LeakProfiler::object_sampler(); + assert(_object_sampler != NULL, "invariant"); + + _vm_thread = VMThread::vm_thread(); + assert(_vm_thread == Thread::current(), "invariant"); + _vm_thread_local = _vm_thread->jfr_thread_local(); + assert(_vm_thread_local != NULL, "invariant"); + assert(_vm_thread->jfr_thread_local()->thread_id() == JFR_THREAD_ID(_vm_thread), "invariant"); + + // The VM_Operation::evaluate() which invoked doit() + // contains a top level ResourceMark + + // save the original markWord for the potential leak objects + // to be restored on function exit + ObjectSampleMarker marker; + if (ObjectSampleCheckpoint::mark(marker, _emit_all) == 0) { + return; + } + + EdgeStore edge_store; + + GranularTimer::start(_cutoff_ticks, 1000000); + if (_cutoff_ticks <= 0) { + // no chains + write_events(&edge_store); + return; + } + + assert(_cutoff_ticks > 0, "invariant"); + + // The bitset used for marking is dimensioned as a function of the heap size + const MemRegion heap_region = Universe::heap()->reserved_region(); + BitSet mark_bits(heap_region); + + // The edge queue is dimensioned as a fraction of the heap size + const size_t edge_queue_reservation_size = edge_queue_memory_reservation(heap_region); + EdgeQueue edge_queue(edge_queue_reservation_size, edge_queue_memory_commit_size(edge_queue_reservation_size)); + + // The initialize() routines will attempt to reserve and allocate backing storage memory. + // Failure to accommodate will render root chain processing impossible. + // As a fallback on failure, just write out the existing samples, flat, without chains. + if (!(mark_bits.initialize() && edge_queue.initialize())) { + log_warning(jfr)("Unable to allocate memory for root chain processing"); + write_events(&edge_store); + return; + } + + // necessary condition for attempting a root set iteration + Universe::heap()->ensure_parsability(false); + + RootSetClosure::add_to_queue(&edge_queue); + if (edge_queue.is_full()) { + // Pathological case where roots don't fit in queue + // Do a depth-first search, but mark roots first + // to avoid walking sideways over roots + DFSClosure::find_leaks_from_root_set(&edge_store, &mark_bits); + } else { + BFSClosure bfs(&edge_queue, &edge_store, &mark_bits); + bfs.process(); + } + GranularTimer::stop(); + write_events(&edge_store); + log_edge_queue_summary(edge_queue); +} + +int EmitEventOperation::write_events(EdgeStore* edge_store) { + assert(_object_sampler != NULL, "invariant"); + assert(edge_store != NULL, "invariant"); + assert(_vm_thread != NULL, "invariant"); + assert(_vm_thread_local != NULL, "invariant"); + assert(SafepointSynchronize::is_at_safepoint(), "invariant"); + + // save thread id in preparation for thread local trace data manipulations + const traceid vmthread_id = _vm_thread_local->thread_id(); + assert(_vm_thread_local->thread_id() == JFR_THREAD_ID(_vm_thread), "invariant"); + + const jlong last_sweep = _emit_all ? max_jlong : _object_sampler->last_sweep().value(); + int count = 0; + + for (int i = 0; i < _object_sampler->item_count(); ++i) { + const ObjectSample* sample = _object_sampler->item_at(i); + if (sample->is_alive_and_older_than(last_sweep)) { + write_event(sample, edge_store); + ++count; + } + } + + // restore thread local stack trace and thread id + _vm_thread_local->set_thread_id(vmthread_id); + _vm_thread_local->clear_cached_stack_trace(); + assert(_vm_thread_local->thread_id() == JFR_THREAD_ID(_vm_thread), "invariant"); + + if (count > 0) { + // serialize assoicated checkpoints + ObjectSampleCheckpoint::write(edge_store, _emit_all, _vm_thread); + } + return count; +} + +static int array_size(const oop object) { + assert(object != NULL, "invariant"); + if (object->is_array()) { + return arrayOop(object)->length(); + } + return -1; +} + +void EmitEventOperation::write_event(const ObjectSample* sample, EdgeStore* edge_store) { + assert(sample != NULL, "invariant"); + assert(!sample->is_dead(), "invariant"); + assert(edge_store != NULL, "invariant"); + assert(_vm_thread_local != NULL, "invariant"); + const oop* object_addr = sample->object_addr(); + assert(*object_addr != NULL, "invariant"); + + const Edge* edge = (const Edge*)(*object_addr)->mark(); + traceid gc_root_id = 0; + if (edge == NULL) { + // In order to dump out a representation of the event + // even though it was not reachable / too long to reach, + // we need to register a top level edge for this object + Edge e(NULL, object_addr); + edge_store->add_chain(&e, 1); + edge = (const Edge*)(*object_addr)->mark(); + } else { + gc_root_id = edge_store->get_root_id(edge); + } + + assert(edge != NULL, "invariant"); + assert(edge->pointee() == *object_addr, "invariant"); + const traceid object_id = edge_store->get_id(edge); + assert(object_id != 0, "invariant"); + + EventOldObjectSample e(UNTIMED); + e.set_starttime(GranularTimer::start_time()); + e.set_endtime(GranularTimer::end_time()); + e.set_allocationTime(sample->allocation_time()); + e.set_object(object_id); + e.set_arrayElements(array_size(*object_addr)); + e.set_root(gc_root_id); + + // Temporarily assigning both the stack trace id and thread id + // onto the thread local data structure of the VMThread (for the duration + // of the commit() call). This trick provides a means to override + // the event generation mechanism by injecting externally provided id's. + // Here, in particular, this allows us to emit an old object event + // supplying information from where the actual sampling occurred. + _vm_thread_local->set_cached_stack_trace_id(sample->stack_trace_id()); + assert(sample->has_thread(), "invariant"); + _vm_thread_local->set_thread_id(sample->thread_id()); + e.commit(); +} diff --git a/src/hotspot/share/jfr/leakprofiler/emitEventOperation.hpp b/src/hotspot/share/jfr/leakprofiler/emitEventOperation.hpp new file mode 100644 index 00000000000..05e37299e10 --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/emitEventOperation.hpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_LEAKPROFILER_EMITEVENTOPERATION_HPP +#define SHARE_VM_LEAKPROFILER_EMITEVENTOPERATION_HPP + +#include "runtime/vm_operations.hpp" + +class BFSClosure; +class EdgeStore; +class EdgeQueue; +class JfrThreadData; +class ObjectSample; +class ObjectSampler; + +// Safepoint operation for emitting object sample events +class EmitEventOperation : public VM_Operation { + private: + jlong _cutoff_ticks; + bool _emit_all; + VMThread* _vm_thread; + JfrThreadLocal* _vm_thread_local; + ObjectSampler* _object_sampler; + + void write_event(const ObjectSample* sample, EdgeStore* edge_store); + int write_events(EdgeStore* edge_store); + + public: + EmitEventOperation(jlong cutoff_ticks, bool emit_all) : + _cutoff_ticks(cutoff_ticks), + _emit_all(emit_all), + _vm_thread(NULL), + _vm_thread_local(NULL), + _object_sampler(NULL) { + } + + VMOp_Type type() const { + return VMOp_GC_HeapInspection; + } + + Mode evaluation_mode() const { + return _safepoint; + } + + virtual void doit(); +}; + +#endif // SHARE_VM_LEAKPROFILER_EMITEVENTOPERATION_HPP diff --git a/src/hotspot/share/jfr/leakprofiler/leakProfiler.cpp b/src/hotspot/share/jfr/leakprofiler/leakProfiler.cpp new file mode 100644 index 00000000000..c5744460f13 --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/leakProfiler.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "jfr/leakprofiler/emitEventOperation.hpp" +#include "jfr/leakprofiler/leakProfiler.hpp" +#include "jfr/leakprofiler/startOperation.hpp" +#include "jfr/leakprofiler/stopOperation.hpp" +#include "jfr/leakprofiler/sampling/objectSampler.hpp" +#include "jfr/recorder/service/jfrOptionSet.hpp" +#include "memory/iterator.hpp" +#include "oops/oop.hpp" +#include "runtime/atomic.hpp" +#include "runtime/orderAccess.inline.hpp" +#include "runtime/thread.hpp" +#include "runtime/vmThread.hpp" +#include "utilities/ostream.hpp" + +// Only to be updated during safepoint +ObjectSampler* LeakProfiler::_object_sampler = NULL; + +static volatile jbyte suspended = 0; +bool LeakProfiler::start(jint sample_count) { + if (_object_sampler != NULL) { + // already started + return true; + } + // Allows user to disable leak profiler on command line by setting queue size to zero. + if (sample_count > 0) { + StartOperation op(sample_count); + VMThread::execute(&op); + return _object_sampler != NULL; + } + return false; +} + +bool LeakProfiler::stop() { + if (_object_sampler == NULL) { + // already stopped/not started + return true; + } + StopOperation op; + VMThread::execute(&op); + return _object_sampler == NULL; +} + +void LeakProfiler::emit_events(jlong cutoff_ticks, bool emit_all) { + if (!is_running()) { + return; + } + EmitEventOperation op(cutoff_ticks, emit_all); + VMThread::execute(&op); +} + +void LeakProfiler::oops_do(BoolObjectClosure* is_alive, OopClosure* f) { + assert(SafepointSynchronize::is_at_safepoint(), + "Leak Profiler::oops_do(...) may only be called during safepoint"); + + if (_object_sampler != NULL) { + _object_sampler->oops_do(is_alive, f); + } +} + +void LeakProfiler::sample(HeapWord* object, + size_t size, + JavaThread* thread) { + assert(is_running(), "invariant"); + assert(thread != NULL, "invariant"); + assert(thread->thread_state() == _thread_in_vm, "invariant"); + + // exclude compiler threads and code sweeper thread + if (thread->is_hidden_from_external_view()) { + return; + } + + _object_sampler->add(object, size, thread); +} + +ObjectSampler* LeakProfiler::object_sampler() { + assert(is_suspended() || SafepointSynchronize::is_at_safepoint(), + "Leak Profiler::object_sampler() may only be called during safepoint"); + return _object_sampler; +} + +void LeakProfiler::set_object_sampler(ObjectSampler* object_sampler) { + assert(SafepointSynchronize::is_at_safepoint(), + "Leak Profiler::set_object_sampler() may only be called during safepoint"); + _object_sampler = object_sampler; +} + +bool LeakProfiler::is_running() { + return _object_sampler != NULL && !suspended; +} + +bool LeakProfiler::is_suspended() { + return _object_sampler != NULL && suspended; +} + +void LeakProfiler::resume() { + assert(is_suspended(), "invariant"); + OrderAccess::storestore(); + Atomic::store((jbyte)0, &suspended); + assert(is_running(), "invariant"); +} + +void LeakProfiler::suspend() { + assert(SafepointSynchronize::is_at_safepoint(), "invariant"); + assert(_object_sampler != NULL, "invariant"); + assert(!is_suspended(), "invariant"); + suspended = (jbyte)1; // safepoint visible +} diff --git a/src/hotspot/share/jfr/leakprofiler/leakProfiler.hpp b/src/hotspot/share/jfr/leakprofiler/leakProfiler.hpp new file mode 100644 index 00000000000..c5de82e82b0 --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/leakProfiler.hpp @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_JFR_LEAKPROFILER_LEAKPROFILER_HPP +#define SHARE_VM_JFR_LEAKPROFILER_LEAKPROFILER_HPP + +#include "memory/allocation.hpp" + +class BoolObjectClosure; +class ObjectSampler; +class OopClosure; +class Thread; + +class LeakProfiler : public AllStatic { + friend class ClassUnloadTypeSet; + friend class EmitEventOperation; + friend class ObjectSampleCheckpoint; + friend class StartOperation; + friend class StopOperation; + friend class TypeSet; + friend class WriteObjectSampleStacktrace; + + private: + static ObjectSampler* _object_sampler; + + static void set_object_sampler(ObjectSampler* object_sampler); + static ObjectSampler* object_sampler(); + + static void suspend(); + static void resume(); + static bool is_suspended(); + + public: + static bool start(jint sample_count); + static bool stop(); + static void emit_events(jlong cutoff_ticks, bool emit_all); + static bool is_running(); + + static void sample(HeapWord* object, size_t size, JavaThread* thread); + + // Called by GC + static void oops_do(BoolObjectClosure* is_alive, OopClosure* f); +}; + +#endif // SHARE_VM_JFR_LEAKPROFILER_LEAKPROFILER_HPP diff --git a/src/hotspot/share/jfr/leakprofiler/sampling/objectSample.hpp b/src/hotspot/share/jfr/leakprofiler/sampling/objectSample.hpp new file mode 100644 index 00000000000..038dba30962 --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/sampling/objectSample.hpp @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_JFR_LEAKPROFILER_SAMPLING_OBJECTSAMPLE_HPP +#define SHARE_VM_JFR_LEAKPROFILER_SAMPLING_OBJECTSAMPLE_HPP + +#include "jfr/recorder/checkpoint/jfrCheckpointBlob.hpp" +#include "jfr/utilities/jfrAllocation.hpp" +#include "jfr/utilities/jfrTime.hpp" +#include "jfr/utilities/jfrTypes.hpp" +#include "memory/allocation.hpp" +#include "oops/oop.hpp" +#include "utilities/ticks.hpp" +/* + * Handle for diagnosing Java memory leaks. + * + * The class tracks the time the object was + * allocated, the thread and the stack trace. + */ +class ObjectSample : public JfrCHeapObj { + friend class ObjectSampler; + friend class SampleList; + private: + ObjectSample* _next; + ObjectSample* _previous; + JfrCheckpointBlobHandle _thread_cp; + JfrCheckpointBlobHandle _klass_cp; + oop _object; + Ticks _allocation_time; + traceid _stack_trace_id; + traceid _thread_id; + int _index; + size_t _span; + size_t _allocated; + unsigned int _stack_trace_hash; + bool _dead; + + void set_dead() { + _dead = true; + } + + void release_references() { + if (_thread_cp.valid()) { + _thread_cp.~JfrCheckpointBlobHandle(); + } + if (_klass_cp.valid()) { + _klass_cp.~JfrCheckpointBlobHandle(); + } + } + + void reset() { + set_stack_trace_id(0); + set_stack_trace_hash(0), + release_references(); + _dead = false; + } + + public: + ObjectSample() : _next(NULL), + _previous(NULL), + _thread_cp(), + _klass_cp(), + _object(NULL), + _allocation_time(), + _stack_trace_id(0), + _thread_id(0), + _index(0), + _span(0), + _allocated(0), + _stack_trace_hash(0), + _dead(false) {} + + ObjectSample* next() const { + return _next; + } + + void set_next(ObjectSample* next) { + _next = next; + } + + ObjectSample* prev() const { + return _previous; + } + + void set_prev(ObjectSample* prev) { + _previous = prev; + } + + bool is_dead() const { + return _dead; + } + + const oop object() const { + return _object; + } + + const oop* object_addr() const { + return &_object; + } + + void set_object(oop object) { + _object = object; + } + + const Klass* klass() const { + assert(_object != NULL, "invariant"); + return _object->klass(); + } + + int index() const { + return _index; + } + + void set_index(int index) { + _index = index; + } + + size_t span() const { + return _span; + } + + void set_span(size_t span) { + _span = span; + } + + void add_span(size_t span) { + _span += span; + } + + size_t allocated() const { + return _allocated; + } + + void set_allocated(size_t size) { + _allocated = size; + } + + const Ticks& allocation_time() const { + return _allocation_time; + } + + const void set_allocation_time(const JfrTicks& time) { + _allocation_time = Ticks(time.value()); + } + + bool has_stack_trace() const { + return stack_trace_id() != 0; + } + + traceid stack_trace_id() const { + return _stack_trace_id; + } + + void set_stack_trace_id(traceid id) { + _stack_trace_id = id; + } + + unsigned int stack_trace_hash() const { + return _stack_trace_hash; + } + + void set_stack_trace_hash(unsigned int hash) { + _stack_trace_hash = hash; + } + + bool has_thread() const { + return _thread_id != 0; + } + + traceid thread_id() const { + return _thread_id; + } + + void set_thread_id(traceid id) { + _thread_id = id; + } + + bool is_alive_and_older_than(jlong time_stamp) const { + return !is_dead() && (JfrTime::is_ft_enabled() ? + _allocation_time.ft_value() : _allocation_time.value()) < time_stamp; + } + + const JfrCheckpointBlobHandle& thread_checkpoint() const { + return _thread_cp; + } + + bool has_thread_checkpoint() const { + return _thread_cp.valid(); + } + + // JfrCheckpointBlobHandle assignment operator + // maintains proper reference counting + void set_thread_checkpoint(const JfrCheckpointBlobHandle& ref) { + if (_thread_cp != ref) { + _thread_cp = ref; + } + } + + const JfrCheckpointBlobHandle& klass_checkpoint() const { + return _klass_cp; + } + + bool has_klass_checkpoint() const { + return _klass_cp.valid(); + } + + void set_klass_checkpoint(const JfrCheckpointBlobHandle& ref) { + if (_klass_cp != ref) { + if (_klass_cp.valid()) { + _klass_cp->set_next(ref); + return; + } + _klass_cp = ref; + } + } +}; + +#endif // SHARE_VM_JFR_LEAKPROFILER_SAMPLING_OBJECTSAMPLE_HPP diff --git a/src/hotspot/share/jfr/leakprofiler/sampling/objectSampler.cpp b/src/hotspot/share/jfr/leakprofiler/sampling/objectSampler.cpp new file mode 100644 index 00000000000..ba179ec2f05 --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/sampling/objectSampler.cpp @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +#include "precompiled.hpp" +#include "jfr/jfrEvents.hpp" +#include "jfr/leakprofiler/sampling/objectSample.hpp" +#include "jfr/leakprofiler/sampling/objectSampler.hpp" +#include "jfr/leakprofiler/sampling/sampleList.hpp" +#include "jfr/leakprofiler/sampling/samplePriorityQueue.hpp" +#include "jfr/recorder/jfrEventSetting.inline.hpp" +#include "jfr/recorder/checkpoint/jfrCheckpointManager.hpp" +#include "jfr/recorder/stacktrace/jfrStackTraceRepository.hpp" +#include "jfr/support/jfrThreadLocal.hpp" +#include "jfr/utilities/jfrTryLock.hpp" +#include "oops/oop.inline.hpp" +#include "runtime/thread.hpp" +#include "logging/log.hpp" + +ObjectSampler::ObjectSampler(size_t size) : + _priority_queue(new SamplePriorityQueue(size)), + _list(new SampleList(size)), + _last_sweep(JfrTicks::now()), + _total_allocated(0), + _threshold(0), + _size(size), + _tryLock(0), + _dead_samples(false) {} + +ObjectSampler::~ObjectSampler() { + delete _priority_queue; + _priority_queue = NULL; + delete _list; + _list = NULL; +} + +void ObjectSampler::add(HeapWord* obj, size_t allocated, JavaThread* thread) { + assert(thread != NULL, "invariant"); + const traceid thread_id = thread->threadObj() != NULL ? thread->jfr_thread_local()->thread_id() : 0; + if (thread_id == 0) { + return; + } + assert(thread_id != 0, "invariant"); + + if (!thread->jfr_thread_local()->has_thread_checkpoint()) { + JfrCheckpointManager::create_thread_checkpoint(thread); + assert(thread->jfr_thread_local()->has_thread_checkpoint(), "invariant"); + } + + traceid stack_trace_id = 0; + unsigned int stack_trace_hash = 0; + if (JfrEventSetting::has_stacktrace(EventOldObjectSample::eventId)) { + stack_trace_id = JfrStackTraceRepository::record(thread, 0, &stack_trace_hash); + thread->jfr_thread_local()->set_cached_stack_trace_id(stack_trace_id, stack_trace_hash); + } + + const JfrTicks allocation_time = JfrTicks::now(); + + JfrTryLock tryLock(&_tryLock); + if (!tryLock.has_lock()) { + log_trace(jfr, oldobject, sampling)("Skipping old object sample due to lock contention"); + return; + } + + if (_dead_samples) { + scavenge(); + assert(!_dead_samples, "invariant"); + } + + _total_allocated += allocated; + const size_t span = _total_allocated - _priority_queue->total(); + ObjectSample* sample; + if ((size_t)_priority_queue->count() == _size) { + assert(_list->count() == _size, "invariant"); + const ObjectSample* peek = _priority_queue->peek(); + if (peek->span() > span) { + // quick reject, will not fit + return; + } + sample = _list->reuse(_priority_queue->pop()); + } else { + sample = _list->get(); + } + + assert(sample != NULL, "invariant"); + assert(thread_id != 0, "invariant"); + sample->set_thread_id(thread_id); + sample->set_thread_checkpoint(thread->jfr_thread_local()->thread_checkpoint()); + + if (stack_trace_id != 0) { + sample->set_stack_trace_id(stack_trace_id); + sample->set_stack_trace_hash(stack_trace_hash); + } + + sample->set_span(allocated); + sample->set_object((oop)obj); + sample->set_allocated(allocated); + sample->set_allocation_time(allocation_time); + _priority_queue->push(sample); +} + +const ObjectSample* ObjectSampler::last() const { + return _list->last(); +} + +const ObjectSample* ObjectSampler::last_resolved() const { + return _list->last_resolved(); +} + +void ObjectSampler::set_last_resolved(const ObjectSample* sample) { + _list->set_last_resolved(sample); +} + +void ObjectSampler::oops_do(BoolObjectClosure* is_alive, OopClosure* f) { + ObjectSample* current = _list->last(); + while (current != NULL) { + ObjectSample* next = current->next(); + if (!current->is_dead()) { + if (is_alive->do_object_b(current->object())) { + // The weakly referenced object is alive, update pointer + f->do_oop(const_cast(current->object_addr())); + } else { + current->set_dead(); + _dead_samples = true; + } + } + current = next; + } + _last_sweep = JfrTicks::now(); +} + +void ObjectSampler::remove_dead(ObjectSample* sample) { + assert(sample != NULL, "invariant"); + assert(sample->is_dead(), "invariant"); + ObjectSample* const previous = sample->prev(); + // push span on to previous + if (previous != NULL) { + _priority_queue->remove(previous); + previous->add_span(sample->span()); + _priority_queue->push(previous); + } + _priority_queue->remove(sample); + _list->release(sample); +} + +void ObjectSampler::scavenge() { + ObjectSample* current = _list->last(); + while (current != NULL) { + ObjectSample* next = current->next(); + if (current->is_dead()) { + remove_dead(current); + } + current = next; + } + _dead_samples = false; +} + +int ObjectSampler::item_count() const { + return _priority_queue->count(); +} + +const ObjectSample* ObjectSampler::item_at(int index) const { + return _priority_queue->item_at(index); +} + +ObjectSample* ObjectSampler::item_at(int index) { + return const_cast( + const_cast(this)->item_at(index) + ); +} + +const JfrTicks& ObjectSampler::last_sweep() const { + return _last_sweep; +} diff --git a/src/hotspot/share/jfr/leakprofiler/sampling/objectSampler.hpp b/src/hotspot/share/jfr/leakprofiler/sampling/objectSampler.hpp new file mode 100644 index 00000000000..b8cea323e02 --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/sampling/objectSampler.hpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_LEAKPROFILER_SAMPLING_OBJECTSAMPLER_HPP +#define SHARE_VM_LEAKPROFILER_SAMPLING_OBJECTSAMPLER_HPP + +#include "memory/allocation.hpp" +#include "jfr/utilities/jfrTime.hpp" + +class BoolObjectClosure; +class OopClosure; +class ObjectSample; +class ObjectSampler; +class SampleList; +class SamplePriorityQueue; +class Thread; + +// Class reponsible for holding samples and +// making sure the samples are evenly distributed as +// new entries are added and removed. +class ObjectSampler : public CHeapObj { + friend class LeakProfiler; + friend class ObjectSampleCheckpoint; + friend class StartOperation; + friend class StopOperation; + friend class EmitEventOperation; + private: + SamplePriorityQueue* _priority_queue; + SampleList* _list; + JfrTicks _last_sweep; + size_t _total_allocated; + size_t _threshold; + size_t _size; + volatile int _tryLock; + bool _dead_samples; + + explicit ObjectSampler(size_t size); + ~ObjectSampler(); + + void add(HeapWord* object, size_t size, JavaThread* thread); + void remove_dead(ObjectSample* sample); + void scavenge(); + + // Called by GC + void oops_do(BoolObjectClosure* is_alive, OopClosure* f); + + public: + const ObjectSample* item_at(int index) const; + ObjectSample* item_at(int index); + int item_count() const; + const ObjectSample* last() const; + const ObjectSample* last_resolved() const; + void set_last_resolved(const ObjectSample* sample); + const JfrTicks& last_sweep() const; +}; + +#endif // SHARE_VM_LEAKPROFILER_SAMPLING_OBJECTSAMPLER_HPP diff --git a/src/hotspot/share/jfr/leakprofiler/sampling/sampleList.cpp b/src/hotspot/share/jfr/leakprofiler/sampling/sampleList.cpp new file mode 100644 index 00000000000..04fffa8dc96 --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/sampling/sampleList.cpp @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "jfr/leakprofiler/sampling/objectSample.hpp" +#include "jfr/leakprofiler/sampling/sampleList.hpp" +#include "oops/oop.inline.hpp" + +SampleList::SampleList(size_t limit, size_t cache_size) : + _free_list(), + _in_use_list(), + _last_resolved(NULL), + _limit(limit), + _cache_size(cache_size), + _allocated(0) { +} + +SampleList::~SampleList() { + deallocate_samples(_free_list); + deallocate_samples(_in_use_list); +} + +ObjectSample* SampleList::last() const { + return _in_use_list.head(); +} + +const ObjectSample* SampleList::last_resolved() const { + return _last_resolved; +} + +void SampleList::set_last_resolved(const ObjectSample* sample) { + assert(last() == sample, "invariant"); + _last_resolved = sample; +} + +void SampleList::link(ObjectSample* sample) { + assert(sample != NULL, "invariant"); + _in_use_list.prepend(sample); +} + +void SampleList::unlink(ObjectSample* sample) { + assert(sample != NULL, "invariant"); + if (_last_resolved == sample) { + _last_resolved = sample->next(); + } + reset(_in_use_list.remove(sample)); +} + +ObjectSample* SampleList::reuse(ObjectSample* sample) { + assert(sample != NULL, "invariant"); + unlink(sample); + link(sample); + return sample; +} + +void SampleList::populate_cache() { + if (_free_list.count() < _cache_size) { + const size_t cache_delta = _cache_size - _free_list.count(); + for (size_t i = 0; i < cache_delta; ++i) { + ObjectSample* sample = newSample(); + if (sample != NULL) { + _free_list.append(sample); + } + } + } +} + +ObjectSample* SampleList::newSample() const { + if (_limit == _allocated) { + return NULL; + } + ++_allocated; + return new ObjectSample(); +} + +ObjectSample* SampleList::get() { + ObjectSample* sample = _free_list.head(); + if (sample != NULL) { + link(_free_list.remove(sample)); + } else { + sample = newSample(); + if (sample != NULL) { + _in_use_list.prepend(sample); + } + } + if (_cache_size > 0 && sample != NULL) { + populate_cache(); + } + return sample; +} + +void SampleList::release(ObjectSample* sample) { + assert(sample != NULL, "invariant"); + unlink(sample); + _free_list.append(sample); +} + +void SampleList::deallocate_samples(List& list) { + if (list.count() > 0) { + ObjectSample* sample = list.head(); + while (sample != NULL) { + list.remove(sample); + delete sample; + sample = list.head(); + } + } + assert(list.count() == 0, "invariant"); +} + +void SampleList::reset(ObjectSample* sample) { + assert(sample != NULL, "invariant"); + sample->reset(); +} + +bool SampleList::is_full() const { + return _in_use_list.count() == _limit; +} + +size_t SampleList::count() const { + return _in_use_list.count(); +} diff --git a/src/hotspot/share/jfr/leakprofiler/sampling/sampleList.hpp b/src/hotspot/share/jfr/leakprofiler/sampling/sampleList.hpp new file mode 100644 index 00000000000..9d7d27fd0d9 --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/sampling/sampleList.hpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_JFR_LEAKPROFILER_SAMPLING_SAMPLELIST_HPP +#define SHARE_VM_JFR_LEAKPROFILER_SAMPLING_SAMPLELIST_HPP +#include "jfr/utilities/jfrAllocation.hpp" +#include "jfr/utilities/jfrDoublyLinkedList.hpp" + +class ObjectSample; + +class SampleList : public JfrCHeapObj { + typedef JfrDoublyLinkedList List; + private: + List _free_list; + List _in_use_list; + const ObjectSample* _last_resolved; + mutable size_t _allocated; + const size_t _limit; + const size_t _cache_size; + + void populate_cache(); + ObjectSample* newSample() const; + void link(ObjectSample* sample); + void unlink(ObjectSample* sample); + void deallocate_samples(List& list); + void reset(ObjectSample* sample); + + public: + SampleList(size_t limit, size_t cache_size = 0); + ~SampleList(); + + void set_last_resolved(const ObjectSample* sample); + ObjectSample* get(); + ObjectSample* last() const; + void release(ObjectSample* sample); + const ObjectSample* last_resolved() const; + ObjectSample* reuse(ObjectSample* sample); + bool is_full() const; + size_t count() const; +}; + +#endif // SHARE_VM_JFR_LEAKPROFILER_SAMPLING_SAMPLELIST_HPP diff --git a/src/hotspot/share/jfr/leakprofiler/sampling/samplePriorityQueue.cpp b/src/hotspot/share/jfr/leakprofiler/sampling/samplePriorityQueue.cpp new file mode 100644 index 00000000000..5760dc7bfbd --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/sampling/samplePriorityQueue.cpp @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "jfr/leakprofiler/sampling/objectSample.hpp" +#include "jfr/leakprofiler/sampling/samplePriorityQueue.hpp" +#include "memory/allocation.inline.hpp" +#include "oops/oop.inline.hpp" + +SamplePriorityQueue::SamplePriorityQueue(size_t size) : + _allocated_size(size), + _count(0), + _total(0) { + _items = NEW_C_HEAP_ARRAY(ObjectSample*, size, mtTracing); + memset(_items, 0, sizeof(ObjectSample*) * size); +} + +SamplePriorityQueue::~SamplePriorityQueue() { + FREE_C_HEAP_ARRAY(ObjectSample*, _items); + _items = NULL; +} + +void SamplePriorityQueue::push(ObjectSample* item) { + assert(item != NULL, "invariant"); + assert(_items[_count] == NULL, "invariant"); + + _items[_count] = item; + _items[_count]->set_index(_count); + _count++; + moveUp(_count - 1); + _total += item->span(); +} + +size_t SamplePriorityQueue::total() const { + return _total; +} + +ObjectSample* SamplePriorityQueue::pop() { + if (_count == 0) { + return NULL; + } + + ObjectSample* const s = _items[0]; + assert(s != NULL, "invariant"); + swap(0, _count - 1); + _count--; + assert(s == _items[_count], "invariant"); + // clear from heap + _items[_count] = NULL; + moveDown(0); + _total -= s->span(); + return s; +} + +void SamplePriorityQueue::swap(int i, int j) { + ObjectSample* tmp = _items[i]; + _items[i] = _items[j]; + _items[j] = tmp; + _items[i]->set_index(i); + _items[j]->set_index(j); +} + +static int left(int i) { + return 2 * i + 1; +} + +static int right(int i) { + return 2 * i + 2; +} + +static int parent(int i) { + return (i - 1) / 2; +} + +void SamplePriorityQueue::moveDown(int i) { + do { + int j = -1; + int r = right(i); + if (r < _count && _items[r]->span() < _items[i]->span()) { + int l = left(i); + if (_items[l]->span() < _items[r]->span()) { + j = l; + } else { + j = r; + } + } else { + int l = left(i); + if (l < _count && _items[l]->span() < _items[i]->span()) { + j = l; + } + } + if (j >= 0) { + swap(i, j); + } + i = j; + } while (i >= 0); + +} + +void SamplePriorityQueue::moveUp(int i) { + int p = parent(i); + while (i > 0 && _items[i]->span() < _items[p]->span()) { + swap(i,p); + i = p; + p = parent(i); + } +} + +void SamplePriorityQueue::remove(ObjectSample* s) { + assert(s != NULL, "invariant"); + const size_t realSpan = s->span(); + s->set_span(0); + moveUp(s->index()); + s->set_span(realSpan); + pop(); +} + +int SamplePriorityQueue::count() const { + return _count; +} + +const ObjectSample* SamplePriorityQueue::peek() const { + return _count == 0 ? NULL : _items[0]; +} + +ObjectSample* SamplePriorityQueue::item_at(int index) { + assert(index >= 0 && index < _count, "out of range"); + return _items[index]; +} diff --git a/src/hotspot/share/jfr/leakprofiler/sampling/samplePriorityQueue.hpp b/src/hotspot/share/jfr/leakprofiler/sampling/samplePriorityQueue.hpp new file mode 100644 index 00000000000..1189e6d7310 --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/sampling/samplePriorityQueue.hpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_JFR_LEAKPROFILER_SAMPLING_SAMPLEPRIORITYQUEUE_HPP +#define SHARE_VM_JFR_LEAKPROFILER_SAMPLING_SAMPLEPRIORITYQUEUE_HPP + +#include "memory/allocation.hpp" + +class ObjectSample; + +// Priority queue that keeps object samples ordered +// by the amount of allocation they span. +class SamplePriorityQueue : public CHeapObj { + private: + ObjectSample** _items; + size_t _allocated_size; + int _count; + size_t _total; + + void swap(int i, int j); + void moveDown(int index); + void moveUp(int index); + + public: + SamplePriorityQueue(size_t size); + ~SamplePriorityQueue(); + + void push(ObjectSample* sample); + ObjectSample* pop(); + const ObjectSample* peek() const; + void remove(ObjectSample* sample); + ObjectSample* item_at(int index); + size_t total() const; + int count() const; +}; + +#endif // SHARE_VM_JFR_LEAKPROFILER_SAMPLING_SAMPLEPRIORITYQUEUE_HPP diff --git a/src/hotspot/share/jfr/leakprofiler/startOperation.hpp b/src/hotspot/share/jfr/leakprofiler/startOperation.hpp new file mode 100644 index 00000000000..051a044cee0 --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/startOperation.hpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_LEAKPROFILER_STARTOPERATION_HPP +#define SHARE_VM_LEAKPROFILER_STARTOPERATION_HPP + +#include "jfr/recorder/jfrRecorder.hpp" +#include "jfr/leakprofiler/leakProfiler.hpp" +#include "jfr/leakprofiler/sampling/objectSampler.hpp" +#include "jfr/recorder/service/jfrOptionSet.hpp" +#include "logging/log.hpp" +#include "runtime/vm_operations.hpp" + +// Safepoint operation for starting leak profiler object sampler +class StartOperation : public VM_Operation { + private: + jlong _sample_count; + public: + StartOperation(jlong sample_count) : + _sample_count(sample_count) { + } + + Mode evaluation_mode() const { + return _safepoint; + } + + VMOp_Type type() const { + return VMOp_GC_HeapInspection; + } + + virtual void doit() { + assert(!LeakProfiler::is_running(), "invariant"); + jint queue_size = JfrOptionSet::old_object_queue_size(); + LeakProfiler::set_object_sampler(new ObjectSampler(queue_size)); + log_trace(jfr, system)( "Object sampling started"); + } +}; + +#endif // SHARE_VM_LEAKPROFILER_STARTOPERATION_HPP diff --git a/src/hotspot/share/jfr/leakprofiler/stopOperation.hpp b/src/hotspot/share/jfr/leakprofiler/stopOperation.hpp new file mode 100644 index 00000000000..64318d9c6be --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/stopOperation.hpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_LEAKPROFILER_STOPOPERATION_HPP +#define SHARE_VM_LEAKPROFILER_STOPOPERATION_HPP + +#include "jfr/leakprofiler/leakProfiler.hpp" +#include "jfr/leakprofiler/sampling/objectSampler.hpp" +#include "jfr/recorder/service/jfrOptionSet.hpp" +#include "logging/log.hpp" +#include "runtime/vm_operations.hpp" + +// Safepoint operation for stopping leak profiler object sampler +class StopOperation : public VM_Operation { + public: + StopOperation() {} + + Mode evaluation_mode() const { + return _safepoint; + } + + VMOp_Type type() const { + return VMOp_GC_HeapInspection; + } + + virtual void doit() { + assert(LeakProfiler::is_running(), "invariant"); + ObjectSampler* object_sampler = LeakProfiler::object_sampler(); + delete object_sampler; + LeakProfiler::set_object_sampler(NULL); + log_trace(jfr, system)( "Object sampling stopped"); + } +}; + +#endif // SHARE_VM_LEAKPROFILER_STOPOPERATION_HPP diff --git a/src/hotspot/share/jfr/leakprofiler/utilities/granularTimer.cpp b/src/hotspot/share/jfr/leakprofiler/utilities/granularTimer.cpp new file mode 100644 index 00000000000..7de70616667 --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/utilities/granularTimer.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "jfr/leakprofiler/utilities/granularTimer.hpp" + +long GranularTimer::_granularity = 0; +long GranularTimer::_counter = 0; +JfrTicks GranularTimer::_finish_time_ticks = 0; +JfrTicks GranularTimer::_start_time_ticks = 0; +bool GranularTimer::_finished = false; + +void GranularTimer::start(jlong duration_ticks, long granularity) { + assert(granularity > 0, "granularity must be at least 1"); + _granularity = granularity; + _counter = granularity; + _start_time_ticks = JfrTicks::now(); + const jlong end_time_ticks = _start_time_ticks.value() + duration_ticks; + _finish_time_ticks = end_time_ticks < 0 ? JfrTicks(max_jlong) : JfrTicks(end_time_ticks); + _finished = _finish_time_ticks == _start_time_ticks; + assert(_finish_time_ticks.value() >= 0, "invariant"); + assert(_finish_time_ticks >= _start_time_ticks, "invariant"); +} +void GranularTimer::stop() { + if (!_finished) { + _finish_time_ticks = JfrTicks::now(); + } +} +const JfrTicks& GranularTimer::start_time() { + return _start_time_ticks; +} + +const JfrTicks& GranularTimer::end_time() { + return _finish_time_ticks; +} + +bool GranularTimer::is_finished() { + assert(_granularity != 0, "GranularTimer::is_finished must be called after GranularTimer::start"); + if (--_counter == 0) { + if (_finished) { + // reset so we decrease to zero at next iteration + _counter = 1; + return true; + } + if (JfrTicks::now() > _finish_time_ticks) { + _finished = true; + _counter = 1; + return true; + } + assert(_counter == 0, "invariant"); + _counter = _granularity; // restore next batch + } + return false; +} diff --git a/src/hotspot/share/jfr/leakprofiler/utilities/granularTimer.hpp b/src/hotspot/share/jfr/leakprofiler/utilities/granularTimer.hpp new file mode 100644 index 00000000000..aa046da4ec9 --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/utilities/granularTimer.hpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_LEAKPROFILER_UTILITIES_GRANULARTIMER_HPP +#define SHARE_VM_LEAKPROFILER_UTILITIES_GRANULARTIMER_HPP + +#include "jfr/utilities/jfrTime.hpp" +#include "memory/allocation.hpp" + +class GranularTimer : public AllStatic { + private: + static JfrTicks _finish_time_ticks; + static JfrTicks _start_time_ticks; + static long _counter; + static long _granularity; + static bool _finished; + public: + static void start(jlong duration_ticks, long granularity); + static void stop(); + static const JfrTicks& start_time(); + static const JfrTicks& end_time(); + static bool is_finished(); +}; + +#endif // SHARE_VM_LEAKPROFILER_UTILITIES_GRANULARTIMER_HPP diff --git a/src/hotspot/share/jfr/leakprofiler/utilities/rootType.hpp b/src/hotspot/share/jfr/leakprofiler/utilities/rootType.hpp new file mode 100644 index 00000000000..c8cd633293f --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/utilities/rootType.hpp @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_LEAKPROFILER_UTILITIES_ROOTTYPE_HPP +#define SHARE_VM_LEAKPROFILER_UTILITIES_ROOTTYPE_HPP + +#include "memory/allocation.hpp" +#include "utilities/debug.hpp" + +class OldObjectRoot : public AllStatic { + public: + enum System { + _system_undetermined, + _universe, + _global_jni_handles, + _threads, + _object_synchronizer, + _system_dictionary, + _class_loader_data, + _management, + _jvmti, + _code_cache, + _string_table, + _aot, + _number_of_systems + }; + + enum Type { + _type_undetermined, + _stack_variable, + _local_jni_handle, + _global_jni_handle, + _handle_area, + _number_of_types + }; + + static const char* system_description(System system) { + switch (system) { + case _system_undetermined: + return ""; + case _universe: + return "Universe"; + case _global_jni_handles: + return "Global JNI Handles"; + case _threads: + return "Threads"; + case _object_synchronizer: + return "Object Monitor"; + case _system_dictionary: + return "System Dictionary"; + case _class_loader_data: + return "Class Loader Data"; + case _management: + return "Management"; + case _jvmti: + return "JVMTI"; + case _code_cache: + return "Code Cache"; + case _string_table: + return "String Table"; + case _aot: + return "AOT"; + default: + ShouldNotReachHere(); + } + return NULL; + } + + static const char* type_description(Type type) { + switch (type) { + case _type_undetermined: + return ""; + case _stack_variable: + return "Stack Variable"; + case _local_jni_handle: + return "Local JNI Handle"; + case _global_jni_handle: + return "Global JNI Handle"; + case _handle_area: + return "Handle Area"; + default: + ShouldNotReachHere(); + } + return NULL; + } +}; + +#endif // SHARE_VM_LEAKPROFILER_UTILITIES_ROOTTYPE_HPP diff --git a/src/hotspot/share/jfr/leakprofiler/utilities/saveRestore.cpp b/src/hotspot/share/jfr/leakprofiler/utilities/saveRestore.cpp new file mode 100644 index 00000000000..c265e973449 --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/utilities/saveRestore.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "classfile/classLoaderData.hpp" +#include "jfr/leakprofiler/utilities/saveRestore.hpp" +#include "oops/oop.inline.hpp" + +MarkOopContext::MarkOopContext() : _obj(NULL), _mark_oop(NULL) {} + +MarkOopContext::MarkOopContext(const oop obj) : _obj(obj), _mark_oop(obj->mark()) { + assert(_obj->mark() == _mark_oop, "invariant"); + // now we will "poison" the mark word of the object + // to the intermediate monitor INFLATING state. + // This is an "impossible" state during a safepoint, + // hence we will use it to quickly identify objects + // during the reachability search from gc roots. + assert(NULL == markOopDesc::INFLATING(), "invariant"); + _obj->set_mark(markOopDesc::INFLATING()); + assert(NULL == obj->mark(), "invariant"); +} + +MarkOopContext::~MarkOopContext() { + if (_obj != NULL) { + _obj->set_mark(_mark_oop); + assert(_obj->mark() == _mark_oop, "invariant"); + } +} + +MarkOopContext::MarkOopContext(const MarkOopContext& rhs) : _obj(NULL), _mark_oop(NULL) { + swap(const_cast(rhs)); +} + +void MarkOopContext::operator=(MarkOopContext rhs) { + swap(rhs); +} + +void MarkOopContext::swap(MarkOopContext& rhs) { + oop temp_obj = rhs._obj; + markOop temp_mark_oop = rhs._mark_oop; + rhs._obj = _obj; + rhs._mark_oop = _mark_oop; + _obj = temp_obj; + _mark_oop = temp_mark_oop; +} + +CLDClaimContext::CLDClaimContext() : _cld(NULL) {} + +CLDClaimContext::CLDClaimContext(ClassLoaderData* cld) : _cld(cld) { + assert(_cld->claimed(), "invariant"); + _cld->clear_claimed(); +} + +CLDClaimContext::~CLDClaimContext() { + if (_cld != NULL) { + assert(!_cld->claimed(), "invariant"); + _cld->claim(); + assert(_cld->claimed(), "invariant"); + } +} + +CLDClaimContext::CLDClaimContext(const CLDClaimContext& rhs) : _cld(NULL) { + swap(const_cast(rhs)); +} + +void CLDClaimContext::operator=(CLDClaimContext rhs) { + swap(rhs); +} + +void CLDClaimContext::swap(CLDClaimContext& rhs) { + ClassLoaderData* temp_cld = rhs._cld; + rhs._cld = _cld; + _cld = temp_cld; +} + +CLDClaimStateClosure::CLDClaimStateClosure() : CLDClosure(), _state() {} + +void CLDClaimStateClosure::do_cld(ClassLoaderData* cld) { + assert(cld != NULL, "invariant"); + if (cld->claimed()) { + _state.save(cld); + } +} + +SaveRestoreCLDClaimBits::SaveRestoreCLDClaimBits() : _claim_state_closure() { + ClassLoaderDataGraph::cld_do(&_claim_state_closure); +} + +SaveRestoreCLDClaimBits::~SaveRestoreCLDClaimBits() { + ClassLoaderDataGraph::clear_claimed_marks(); +} diff --git a/src/hotspot/share/jfr/leakprofiler/utilities/saveRestore.hpp b/src/hotspot/share/jfr/leakprofiler/utilities/saveRestore.hpp new file mode 100644 index 00000000000..abc40dbd588 --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/utilities/saveRestore.hpp @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_JFR_LEAKPROFILER_UTILITIES_SAVERESTORE_HPP +#define SHARE_VM_JFR_LEAKPROFILER_UTILITIES_SAVERESTORE_HPP + +#include "memory/allocation.hpp" +#include "memory/iterator.hpp" +#include "oops/markOop.hpp" +#include "utilities/growableArray.hpp" + +template +class SaveRestore { + private: + Impl _impl; + public: + SaveRestore() : _impl() { + _impl.setup(); + } + + void save(T const& value) { + _impl.save(value); + } + + ~SaveRestore() { + _impl.restore(); + } +}; + +template +class ContextStore { +private: + GrowableArray* _storage; +public: + ContextStore() : _storage(NULL) {} + + void setup() { + assert(_storage == NULL, "invariant"); + _storage = new GrowableArray(16); + } + + void save(T const& value) { + _storage->push(Context(value)); + } + + void restore() { + for (int i = 0; i < _storage->length(); ++i) { + _storage->at(i).~Context(); + } + } +}; + +/* +* This class will save the original mark oop of an object sample object. +* It will then install an "identifier" mark oop to be used for +* identification purposes in the search for reference chains. +* The destructor will restore the original mark oop. +*/ + +class MarkOopContext { + private: + oop _obj; + markOop _mark_oop; + void swap(MarkOopContext& rhs); + public: + MarkOopContext(); + MarkOopContext(const oop obj); + MarkOopContext(const MarkOopContext& rhs); + void operator=(MarkOopContext rhs); + ~MarkOopContext(); +}; + +typedef SaveRestore > SaveRestoreMarkOops; + +class ClassLoaderData; + +class CLDClaimContext { + private: + ClassLoaderData* _cld; + void swap(CLDClaimContext& rhs); + public: + CLDClaimContext(); + CLDClaimContext(ClassLoaderData* cld); + CLDClaimContext(const CLDClaimContext& rhs); + void operator=(CLDClaimContext rhs); + ~CLDClaimContext(); +}; + +typedef SaveRestore > SaveRestoreCLDClaimState; + +class CLDClaimStateClosure : public CLDClosure { + private: + SaveRestoreCLDClaimState _state; + public: + CLDClaimStateClosure(); + void do_cld(ClassLoaderData* cld); +}; + +class SaveRestoreCLDClaimBits : public StackObj { + private: + CLDClaimStateClosure _claim_state_closure; + public: + SaveRestoreCLDClaimBits(); + ~SaveRestoreCLDClaimBits(); +}; + +#endif // SHARE_VM_JFR_LEAKPROFILER_UTILITIES_SAVERESTORE_HPP diff --git a/src/hotspot/share/jfr/leakprofiler/utilities/unifiedOop.hpp b/src/hotspot/share/jfr/leakprofiler/utilities/unifiedOop.hpp new file mode 100644 index 00000000000..b92a182212e --- /dev/null +++ b/src/hotspot/share/jfr/leakprofiler/utilities/unifiedOop.hpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_JFR_LEAKPROFILER_UTILITIES_UNIFIEDOOP_HPP +#define SHARE_VM_JFR_LEAKPROFILER_UTILITIES_UNIFIEDOOP_HPP + +#include "oops/oop.inline.hpp" + +class UnifiedOop : public AllStatic { + public: + static const bool is_narrow(const oop* ref) { + assert(ref != NULL, "invariant"); + return 1 == (((u8)ref) & 1); + } + + static const oop* decode(const oop* ref) { + assert(ref != NULL, "invariant"); + return is_narrow(ref) ? (const oop*)(((u8)ref) & ~1) : ref; + } + + static const oop* encode(narrowOop* ref) { + assert(ref != NULL, "invariant"); + return (const oop*)((u8)ref | 1); + } + + static oop dereference(const oop* ref) { + assert(ref != NULL, "invariant"); + return is_narrow(ref) ? + (oop)RawAccess<>::oop_load((narrowOop*)decode(ref)) : + (oop)RawAccess<>::oop_load(const_cast(ref)); + + } +}; + +#endif // SHARE_VM_JFR_LEAKPROFILER_UTILITIES_UNIFIEDOOP_HPP diff --git a/src/hotspot/share/jfr/metadata/GenerateJfrFiles.java b/src/hotspot/share/jfr/metadata/GenerateJfrFiles.java new file mode 100644 index 00000000000..4fec760b692 --- /dev/null +++ b/src/hotspot/share/jfr/metadata/GenerateJfrFiles.java @@ -0,0 +1,692 @@ +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.StringJoiner; +import java.util.function.Predicate; + +import javax.xml.XMLConstants; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.validation.SchemaFactory; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +import org.xml.sax.helpers.DefaultHandler; + +public class GenerateJfrFiles { + + public static void main(String... args) throws Exception { + if (args.length != 3) { + System.err.println("Incorrect number of command line arguments."); + System.err.println("Usage:"); + System.err.println("java GenerateJfrFiles[.java] "); + System.exit(1); + } + try { + File metadataXml = new File(args[0]); + File metadataSchema = new File(args[1]); + File outputDirectory = new File(args[2]); + + Metadata metadata = new Metadata(metadataXml, metadataSchema); + metadata.verify(); + metadata.wireUpTypes(); + + printJfrPeriodicHpp(metadata, outputDirectory); + printJfrEventIdsHpp(metadata, outputDirectory); + printJfrEventControlHpp(metadata, outputDirectory); + printJfrTypesHpp(metadata, outputDirectory); + printJfrEventClassesHpp(metadata, outputDirectory); + + } catch (Exception e) { + e.printStackTrace(); + System.exit(1); + } + } + + static class XmlType { + final String fieldType; + final String parameterType; + XmlType(String fieldType, String parameterType) { + this.fieldType = fieldType; + this.parameterType = parameterType; + } + } + + static class TypeElement { + List fields = new ArrayList<>(); + String name; + String fieldType; + String parameterType; + boolean supportStruct; + } + + static class Metadata { + final Map types = new LinkedHashMap<>(); + final Map xmlTypes = new HashMap<>(); + Metadata(File metadataXml, File metadataSchema) throws ParserConfigurationException, SAXException, FileNotFoundException, IOException { + SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + SAXParserFactory factory = SAXParserFactory.newInstance(); + factory.setSchema(schemaFactory.newSchema(metadataSchema)); + SAXParser sp = factory.newSAXParser(); + sp.parse(metadataXml, new MetadataHandler(this)); + } + + List getEvents() { + return getList(t -> t.getClass() == EventElement.class); + } + + List getEventsAndStructs() { + return getList(t -> t.getClass() == EventElement.class || t.supportStruct); + } + + List getTypesAndStructs() { + return getList(t -> t.getClass() == TypeElement.class || t.supportStruct); + } + + @SuppressWarnings("unchecked") + List getList(Predicate pred) { + List result = new ArrayList<>(types.size()); + for (TypeElement t : types.values()) { + if (pred.test(t)) { + result.add((T) t); + } + } + return result; + } + + List getPeriodicEvents() { + return getList(t -> t.getClass() == EventElement.class && ((EventElement) t).periodic); + } + + List getNonEventsAndNonStructs() { + return getList(t -> t.getClass() != EventElement.class && !t.supportStruct); + } + + List getTypes() { + return getList(t -> t.getClass() == TypeElement.class && !t.supportStruct); + } + + List getStructs() { + return getList(t -> t.getClass() == TypeElement.class && t.supportStruct); + } + + void verify() { + for (TypeElement t : types.values()) { + for (FieldElement f : t.fields) { + if (!xmlTypes.containsKey(f.typeName)) { // ignore primitives + if (!types.containsKey(f.typeName)) { + throw new IllegalStateException("Could not find definition of type '" + f.typeName + "' used by " + t.name + "#" + f.name); + } + } + } + } + } + + void wireUpTypes() { + for (TypeElement t : types.values()) { + for (FieldElement f : t.fields) { + TypeElement type = types.get(f.typeName); + if (f.struct) { + type.supportStruct = true; + } + f.type = type; + } + } + } + } + + static class EventElement extends TypeElement { + String representation; + boolean thread; + boolean stackTrace; + boolean startTime; + boolean periodic; + boolean cutoff; + } + + static class FieldElement { + final Metadata metadata; + TypeElement type; + String name; + String typeName; + boolean struct; + + FieldElement(Metadata metadata) { + this.metadata = metadata; + } + + String getParameterType() { + if (struct) { + return "const JfrStruct" + typeName + "&"; + } + XmlType xmlType = metadata.xmlTypes.get(typeName); + if (xmlType != null) { + return xmlType.parameterType; + } + return type != null ? "u8" : typeName; + } + + String getParameterName() { + return struct ? "value" : "new_value"; + } + + String getFieldType() { + if (struct) { + return "JfrStruct" + typeName; + } + XmlType xmlType = metadata.xmlTypes.get(typeName); + if (xmlType != null) { + return xmlType.fieldType; + } + return type != null ? "u8" : typeName; + } + } + + static class MetadataHandler extends DefaultHandler { + final Metadata metadata; + FieldElement currentField; + TypeElement currentType; + MetadataHandler(Metadata metadata) { + this.metadata = metadata; + } + @Override + public void error(SAXParseException e) throws SAXException { + throw e; + } + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + switch (qName) { + case "XmlType": + String name = attributes.getValue("name"); + String parameterType = attributes.getValue("parameterType"); + String fieldType = attributes.getValue("fieldType"); + metadata.xmlTypes.put(name, new XmlType(fieldType, parameterType)); + break; + case "Type": + currentType = new TypeElement(); + currentType.name = attributes.getValue("name"); + break; + case "Event": + EventElement eventtType = new EventElement(); + eventtType.name = attributes.getValue("name"); + eventtType.thread = getBoolean(attributes, "thread", false); + eventtType.stackTrace = getBoolean(attributes, "stackTrace", false); + eventtType.startTime = getBoolean(attributes, "startTime", true); + eventtType.periodic = attributes.getValue("period") != null; + eventtType.cutoff = getBoolean(attributes, "cutoff", false); + currentType = eventtType; + break; + case "Field": + currentField = new FieldElement(metadata); + currentField.struct = getBoolean(attributes, "struct", false); + currentField.name = attributes.getValue("name"); + currentField.typeName = attributes.getValue("type"); + break; + } + } + + private boolean getBoolean(Attributes attributes, String name, boolean defaultValue) { + String value = attributes.getValue(name); + return value == null ? defaultValue : Boolean.valueOf(value); + } + + @Override + public void endElement(String uri, String localName, String qName) { + switch (qName) { + case "Type": + case "Event": + metadata.types.put(currentType.name, currentType); + currentType = null; + break; + case "Field": + currentType.fields.add(currentField); + currentField = null; + break; + } + } + } + + static class Printer implements AutoCloseable { + final PrintStream out; + Printer(File outputDirectory, String filename) throws FileNotFoundException { + out = new PrintStream(new BufferedOutputStream(new FileOutputStream(new File(outputDirectory, filename)))); + write("/* AUTOMATICALLY GENERATED FILE - DO NOT EDIT */"); + write(""); + } + + void write(String text) { + out.print(text); + out.print("\n"); // Don't use Windows line endings + } + + @Override + public void close() throws Exception { + out.close(); + } + } + + private static void printJfrPeriodicHpp(Metadata metadata, File outputDirectory) throws Exception { + try (Printer out = new Printer(outputDirectory, "jfrPeriodic.hpp")) { + out.write("#ifndef JFRFILES_JFRPERIODICEVENTSET_HPP"); + out.write("#define JFRFILES_JFRPERIODICEVENTSET_HPP"); + out.write(""); + out.write("#include \"utilities/macros.hpp\""); + out.write("#if INCLUDE_JFR"); + out.write("#include \"jfrfiles/jfrEventIds.hpp\""); + out.write("#include \"memory/allocation.hpp\""); + out.write(""); + out.write("class JfrPeriodicEventSet : public AllStatic {"); + out.write(" public:"); + out.write(" static void requestEvent(JfrEventId id) {"); + out.write(" switch(id) {"); + out.write(" "); + for (EventElement e : metadata.getPeriodicEvents()) { + out.write(" case Jfr" + e.name + "Event:"); + out.write(" request" + e.name + "();"); + out.write(" break;"); + out.write(" "); + } + out.write(" default:"); + out.write(" break;"); + out.write(" }"); + out.write(" }"); + out.write(""); + out.write(" private:"); + out.write(""); + for (EventElement e : metadata.getPeriodicEvents()) { + out.write(" static void request" + e.name + "(void);"); + out.write(""); + } + out.write("};"); + out.write(""); + out.write("#endif // INCLUDE_JFR"); + out.write("#endif // JFRFILES_JFRPERIODICEVENTSET_HPP"); + } + } + + private static void printJfrEventControlHpp(Metadata metadata, File outputDirectory) throws Exception { + try (Printer out = new Printer(outputDirectory, "jfrEventControl.hpp")) { + out.write("#ifndef JFRFILES_JFR_NATIVE_EVENTSETTING_HPP"); + out.write("#define JFRFILES_JFR_NATIVE_EVENTSETTING_HPP"); + out.write(""); + out.write("#include \"utilities/macros.hpp\""); + out.write("#if INCLUDE_JFR"); + out.write("#include \"jfrfiles/jfrEventIds.hpp\""); + out.write(""); + out.write("/**"); + out.write(" * Event setting. We add some padding so we can use our"); + out.write(" * event IDs as indexes into this."); + out.write(" */"); + out.write(""); + out.write("struct jfrNativeEventSetting {"); + out.write(" jlong threshold_ticks;"); + out.write(" jlong cutoff_ticks;"); + out.write(" u1 stacktrace;"); + out.write(" u1 enabled;"); + out.write(" u1 pad[6]; // Because GCC on linux ia32 at least tries to pack this."); + out.write("};"); + out.write(""); + out.write("union JfrNativeSettings {"); + out.write(" // Array version."); + out.write(" jfrNativeEventSetting bits[MaxJfrEventId];"); + out.write(" // Then, to make it easy to debug,"); + out.write(" // add named struct members also."); + out.write(" struct {"); + out.write(" jfrNativeEventSetting pad[NUM_RESERVED_EVENTS];"); + for (TypeElement t : metadata.getEventsAndStructs()) { + out.write(" jfrNativeEventSetting " + t.name + ";"); + } + out.write(" } ev;"); + out.write("};"); + out.write(""); + out.write("#endif // INCLUDE_JFR"); + out.write("#endif // JFRFILES_JFR_NATIVE_EVENTSETTING_HPP"); + } + } + + private static void printJfrEventIdsHpp(Metadata metadata, File outputDirectory) throws Exception { + try (Printer out = new Printer(outputDirectory, "jfrEventIds.hpp")) { + out.write("#ifndef JFRFILES_JFREVENTIDS_HPP"); + out.write("#define JFRFILES_JFREVENTIDS_HPP"); + out.write(""); + out.write("#include \"utilities/macros.hpp\""); + out.write("#if INCLUDE_JFR"); + out.write("#include \"jfrfiles/jfrTypes.hpp\""); + out.write(""); + out.write("/**"); + out.write(" * Enum of the event types in the JVM"); + out.write(" */"); + out.write("enum JfrEventId {"); + out.write(" _jfreventbase = (NUM_RESERVED_EVENTS-1), // Make sure we start at right index."); + out.write(" "); + out.write(" // Events -> enum entry"); + for (TypeElement t : metadata.getEventsAndStructs()) { + out.write(" Jfr" + t.name + "Event,"); + } + out.write(""); + out.write(" MaxJfrEventId"); + out.write("};"); + out.write(""); + out.write("/**"); + out.write(" * Struct types in the JVM"); + out.write(" */"); + out.write("enum JfrStructId {"); + for (TypeElement t : metadata.getNonEventsAndNonStructs()) { + out.write(" Jfr" + t.name + "Struct,"); + } + for (TypeElement t : metadata.getEventsAndStructs()) { + out.write(" Jfr" + t.name + "Struct,"); + } + out.write(""); + out.write(" MaxJfrStructId"); + out.write("};"); + out.write(""); + out.write("typedef enum JfrEventId JfrEventId;"); + out.write("typedef enum JfrStructId JfrStructId;"); + out.write(""); + out.write("#endif // INCLUDE_JFR"); + out.write("#endif // JFRFILES_JFREVENTIDS_HPP"); + } + } + + private static void printJfrTypesHpp(Metadata metadata, File outputDirectory) throws Exception { + List knownTypes = List.of("Thread", "StackTrace", "Class", "StackFrame"); + try (Printer out = new Printer(outputDirectory, "jfrTypes.hpp")) { + out.write("#ifndef JFRFILES_JFRTYPES_HPP"); + out.write("#define JFRFILES_JFRTYPES_HPP"); + out.write(""); + out.write("#include \"utilities/macros.hpp\""); + out.write("#if INCLUDE_JFR"); + out.write(""); + out.write("enum JfrTypeId {"); + out.write(" TYPE_NONE = 0,"); + out.write(" TYPE_CLASS = 20,"); + out.write(" TYPE_STRING = 21,"); + out.write(" TYPE_THREAD = 22,"); + out.write(" TYPE_STACKTRACE = 23,"); + out.write(" TYPE_BYTES = 24,"); + out.write(" TYPE_EPOCHMILLIS = 25,"); + out.write(" TYPE_MILLIS = 26,"); + out.write(" TYPE_NANOS = 27,"); + out.write(" TYPE_TICKS = 28,"); + out.write(" TYPE_ADDRESS = 29,"); + out.write(" TYPE_PERCENTAGE = 30,"); + out.write(" TYPE_DUMMY,"); + out.write(" TYPE_DUMMY_1,"); + for (TypeElement type : metadata.getTypes()) { + if (!knownTypes.contains(type.name)) { + out.write(" TYPE_" + type.name.toUpperCase() + ","); + } + } + out.write(""); + out.write(" NUM_JFR_TYPES,"); + out.write(" TYPES_END = 255"); + out.write("};"); + out.write(""); + out.write("enum ReservedEvent {"); + out.write(" EVENT_METADATA,"); + out.write(" EVENT_CHECKPOINT,"); + out.write(" EVENT_BUFFERLOST,"); + out.write(" NUM_RESERVED_EVENTS = TYPES_END"); + out.write("};"); + out.write(""); + out.write("#endif // INCLUDE_JFR"); + out.write("#endif // JFRFILES_JFRTYPES_HPP"); + }; + } + + private static void printJfrEventClassesHpp(Metadata metadata, File outputDirectory) throws Exception { + try (Printer out = new Printer(outputDirectory, "jfrEventClasses.hpp")) { + out.write("#ifndef JFRFILES_JFREVENTCLASSES_HPP"); + out.write("#define JFRFILES_JFREVENTCLASSES_HPP"); + out.write(""); + out.write("#include \"jfrfiles/jfrTypes.hpp\""); + out.write("#include \"jfr/utilities/jfrTypes.hpp\""); + out.write("#include \"utilities/macros.hpp\""); + out.write("#include \"utilities/ticks.hpp\""); + out.write("#if INCLUDE_JFR"); + out.write("#include \"jfr/recorder/service/jfrEvent.hpp\""); + out.write("/*"); + out.write(" * Each event class has an assert member function verify() which is invoked"); + out.write(" * just before the engine writes the event and its fields to the data stream."); + out.write(" * The purpose of verify() is to ensure that all fields in the event are initialized"); + out.write(" * and set before attempting to commit."); + out.write(" *"); + out.write(" * We enforce this requirement because events are generally stack allocated and therefore"); + out.write(" * *not* initialized to default values. This prevents us from inadvertently committing"); + out.write(" * uninitialized values to the data stream."); + out.write(" *"); + out.write(" * The assert message contains both the index (zero based) as well as the name of the field."); + out.write(" */"); + out.write(""); + printTypes(out, metadata, false); + out.write(""); + out.write(""); + out.write("#else // !INCLUDE_JFR"); + out.write(""); + out.write("class JfrEvent {"); + out.write(" public:"); + out.write(" JfrEvent() {}"); + out.write(" void set_starttime(const Ticks&) const {}"); + out.write(" void set_endtime(const Ticks&) const {}"); + out.write(" bool should_commit() const { return false; }"); + out.write(" static bool is_enabled() { return false; }"); + out.write(" void commit() {}"); + out.write("};"); + out.write(""); + printTypes(out, metadata, true); + out.write(""); + out.write(""); + out.write("#endif // INCLUDE_JFR"); + out.write("#endif // JFRFILES_JFREVENTCLASSES_HPP"); + } + } + + private static void printTypes(Printer out, Metadata metadata, boolean empty) { + for (TypeElement t : metadata.getStructs()) { + if (empty) { + out.write(""); + printEmptyType(out, t); + } else { + printType(out, t); + } + out.write(""); + } + for (EventElement e : metadata.getEvents()) { + if (empty) { + printEmptyEvent(out, e); + } else { + printEvent(out, e); + } + out.write(""); + } + } + + private static void printEmptyEvent(Printer out, EventElement event) { + out.write("class Event" + event.name + " : public JfrEvent"); + out.write("{"); + out.write(" public:"); + out.write(" Event" + event.name + "(EventStartTime ignore=TIMED) {}"); + if (event.startTime) { + StringJoiner sj = new StringJoiner(",\n "); + for (FieldElement f : event.fields) { + sj.add(f.getParameterType()); + } + out.write(" Event" + event.name + "("); + out.write(" " + sj.toString() + ") { }"); + } + for (FieldElement f : event.fields) { + out.write(" void set_" + f.name + "(" + f.getParameterType() + ") { }"); + } + out.write("};"); + } + + private static void printEmptyType(Printer out, TypeElement t) { + out.write("struct JfrStruct" + t.name); + out.write("{"); + out.write(" public:"); + for (FieldElement f : t.fields) { + out.write(" void set_" + f.name + "(" + f.getParameterType() + ") { }"); + } + out.write("};"); + } + + private static void printType(Printer out, TypeElement t) { + out.write("struct JfrStruct" + t.name); + out.write("{"); + out.write(" private:"); + for (FieldElement f : t.fields) { + printField(out, f); + } + out.write(""); + out.write(" public:"); + for (FieldElement f : t.fields) { + printTypeSetter(out, f); + } + out.write(""); + printWriteData(out, t.fields); + out.write("};"); + out.write(""); + } + + private static void printEvent(Printer out, EventElement event) { + out.write("class Event" + event.name + " : public JfrEvent"); + out.write("{"); + out.write(" private:"); + for (FieldElement f : event.fields) { + printField(out, f); + } + out.write(""); + out.write(" public:"); + out.write(" static const bool hasThread = " + event.thread + ";"); + out.write(" static const bool hasStackTrace = " + event.stackTrace + ";"); + out.write(" static const bool isInstant = " + !event.startTime + ";"); + out.write(" static const bool hasCutoff = " + event.cutoff + ";"); + out.write(" static const bool isRequestable = " + event.periodic + ";"); + out.write(" static const JfrEventId eventId = Jfr" + event.name + "Event;"); + out.write(""); + out.write(" Event" + event.name + "(EventStartTime timing=TIMED) : JfrEvent(timing) {}"); + out.write(""); + int index = 0; + for (FieldElement f : event.fields) { + out.write(" void set_" + f.name + "(" + f.getParameterType() + " " + f.getParameterName() + ") {"); + out.write(" this->_" + f.name + " = " + f.getParameterName() + ";"); + out.write(" DEBUG_ONLY(set_field_bit(" + index++ + "));"); + out.write(" }"); + } + out.write(""); + printWriteData(out, event.fields); + out.write(""); + out.write(" using JfrEvent::commit; // else commit() is hidden by overloaded versions in this class"); + printConstructor2(out, event); + printCommitMethod(out, event); + printVerify(out, event.fields); + out.write("};"); + } + + private static void printWriteData(Printer out, List fields) { + out.write(" template "); + out.write(" void writeData(Writer& w) {"); + for (FieldElement field : fields) { + if (field.struct) { + out.write(" _" + field.name + ".writeData(w);"); + } else { + out.write(" w.write(_" + field.name + ");"); + } + } + out.write(" }"); + } + + private static void printTypeSetter(Printer out, FieldElement field) { + out.write(" void set_" + field.name + "(" + field.getParameterType() + " new_value) { this->_" + field.name + " = new_value; }"); + } + + private static void printVerify(Printer out, List fields) { + out.write(""); + out.write("#ifdef ASSERT"); + out.write(" void verify() const {"); + int index = 0; + for (FieldElement f : fields) { + out.write(" assert(verify_field_bit(" + index++ + "), \"Attempting to write an uninitialized event field: %s\", \"_" + f.name + "\");"); + } + out.write(" }"); + out.write("#endif"); + } + + private static void printCommitMethod(Printer out, EventElement event) { + if (event.startTime) { + StringJoiner sj = new StringJoiner(",\n "); + for (FieldElement f : event.fields) { + sj.add(f.getParameterType() + " " + f.name); + } + out.write(""); + out.write(" void commit(" + sj.toString() + ") {"); + out.write(" if (should_commit()) {"); + for (FieldElement f : event.fields) { + out.write(" set_" + f.name + "(" + f.name + ");"); + } + out.write(" commit();"); + out.write(" }"); + out.write(" }"); + } + out.write(""); + StringJoiner sj = new StringJoiner(",\n "); + if (event.startTime) { + sj.add("const Ticks& startTicks"); + sj.add("const Ticks& endTicks"); + } + for (FieldElement f : event.fields) { + sj.add(f.getParameterType() + " " + f.name); + } + out.write(" static void commit(" + sj.toString() + ") {"); + out.write(" Event" + event.name + " me(UNTIMED);"); + out.write(""); + out.write(" if (me.should_commit()) {"); + if (event.startTime) { + out.write(" me.set_starttime(startTicks);"); + out.write(" me.set_endtime(endTicks);"); + } + for (FieldElement f : event.fields) { + out.write(" me.set_" + f.name + "(" + f.name + ");"); + } + out.write(" me.commit();"); + out.write(" }"); + out.write(" }"); + } + + private static void printConstructor2(Printer out, EventElement event) { + if (!event.startTime) { + out.write(""); + out.write(""); + } + if (event.startTime) { + out.write(""); + out.write(" Event" + event.name + "("); + StringJoiner sj = new StringJoiner(",\n "); + for (FieldElement f : event.fields) { + sj.add(f.getParameterType() + " " + f.name); + } + out.write(" " + sj.toString() + ") : JfrEvent(TIMED) {"); + out.write(" if (should_commit()) {"); + for (FieldElement f : event.fields) { + out.write(" set_" + f.name + "(" + f.name + ");"); + } + out.write(" }"); + out.write(" }"); + } + } + + private static void printField(Printer out, FieldElement field) { + out.write(" " + field.getFieldType() + " _" + field.name + ";"); + } +} \ No newline at end of file diff --git a/src/hotspot/share/jfr/metadata/jfrSerializer.hpp b/src/hotspot/share/jfr/metadata/jfrSerializer.hpp new file mode 100644 index 00000000000..f5a4d7d9f24 --- /dev/null +++ b/src/hotspot/share/jfr/metadata/jfrSerializer.hpp @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_JFR_METADATA_JFRSERIALIZER_HPP +#define SHARE_VM_JFR_METADATA_JFRSERIALIZER_HPP + +#include "memory/allocation.hpp" +#include "jfr/recorder/checkpoint/jfrCheckpointWriter.hpp" +#include "jfrfiles/jfrTypes.hpp" + +/* + * A "type" in Jfr is a binary relation defined by enumerating a set of ordered pairs: + * + * { <1, myvalue>, <2, mysecondvalue>, ... } + * + * The key should be a type relative unique id. A value is an instance of the type. + * + * By defining and registering a type, keys can be written to event fields and the + * framework will maintain the mapping to the corresponding value (if you register as below). + * + * Inherit JfrSerializer, create a CHeapObj instance and then use JfrSerializer::register_serializer(...) to register. + * Once registered, the ownership of the serializer instance is transferred to Jfr. + * + * How to register: + * + * bool register_serializer(JfrTypeId id, bool require_safepoint, bool permit_cache, JfrSerializer* serializer) + * + * The type identifiers are machine generated into an enum located in jfrfiles/jfrTypes.hpp (included). + * + * enum JfrTypeId { + * ... + * TYPE_THREADGROUP, + * TYPE_CLASSLOADER, + * TYPE_METHOD, + * TYPE_SYMBOL, + * TYPE_THREADSTATE, + * TYPE_INFLATECAUSE, + * ... + * + * id this is the id of the type your are defining (see the enum above). + * require_safepoint indicate if your type need to be evaluated and serialized under a safepoint. + * permit_cache indicate if your type constants are stable to be cached. + * (implies the callback is invoked only once and the contents will be cached. Set this to true for static information). + * serializer the serializer instance. + * + * See below for guidance about how to implement serialize(). + * + */ +class JfrSerializer : public CHeapObj { + public: + virtual ~JfrSerializer() {} + static bool register_serializer(JfrTypeId id, bool require_safepoint, bool permit_cache, JfrSerializer* serializer); + virtual void serialize(JfrCheckpointWriter& writer) = 0; +}; + +/* + * Defining serialize(JfrCheckpointWriter& writer): + * + * Invoke writer.write_count(N) for the number of ordered pairs (cardinality) to be defined. + * + * You then write each individual ordered pair, ... + * + * Here is a simple example, describing a type defining string constants: + * + * void MyType::serialize(JfrCheckpointWriter& writer) { + * const int nof_causes = ObjectSynchronizer::inflate_cause_nof; + * writer.write_count(nof_causes); // write number of ordered pairs (mappings) to follow + * for (int i = 0; i < nof_causes; i++) { + * writer.write_key(i); // write key + * writer.write(ObjectSynchronizer::inflate_cause_name((ObjectSynchronizer::InflateCause)i)); // write value + * } + * } + * + * Note that values can be complex, and can also referer to other types. + * + * Please see jfr/recorder/checkpoint/types/jfrType.cpp for reference. + */ + +#endif // SHARE_VM_JFR_METADATA_JFRSERIALIZER_HPP diff --git a/src/hotspot/share/jfr/metadata/metadata.xml b/src/hotspot/share/jfr/metadata/metadata.xml new file mode 100644 index 00000000000..70ca651b9d1 --- /dev/null +++ b/src/hotspot/share/jfr/metadata/metadata.xml @@ -0,0 +1,1092 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/hotspot/share/jfr/metadata/metadata.xsd b/src/hotspot/share/jfr/metadata/metadata.xsd new file mode 100644 index 00000000000..aad3ec4cbbc --- /dev/null +++ b/src/hotspot/share/jfr/metadata/metadata.xsd @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/hotspot/share/jfr/periodic/jfrModuleEvent.cpp b/src/hotspot/share/jfr/periodic/jfrModuleEvent.cpp new file mode 100644 index 00000000000..bc6712507ee --- /dev/null +++ b/src/hotspot/share/jfr/periodic/jfrModuleEvent.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "classfile/classLoaderData.hpp" +#include "classfile/moduleEntry.hpp" +#include "classfile/packageEntry.hpp" +#include "jfr/jfrEvents.hpp" +#include "jfr/periodic/jfrModuleEvent.hpp" +#include "jfr/utilities/jfrTime.hpp" +#include "runtime/mutexLocker.hpp" + +// we want all periodic module events to have the same timestamp +static JfrTicks invocation_time; + +typedef void (*EventFunc)(const void* iterated_address, const ModuleEntry* module); +class ModuleEventCallbackClosure : public ModuleClosure { + protected: + const EventFunc _event_func; + ModuleEventCallbackClosure(EventFunc ef) : _event_func(ef) {} +}; + +class ModuleDependencyClosure : public ModuleEventCallbackClosure { + private: + const ModuleEntry* const _module; + public: + ModuleDependencyClosure(const ModuleEntry* module, EventFunc ef) : ModuleEventCallbackClosure(ef), _module(module) {} + void do_module(ModuleEntry* entry); +}; + +class ModuleExportClosure : public ModuleEventCallbackClosure { + private: + const PackageEntry* const _package; + public: + ModuleExportClosure(const PackageEntry* pkg, EventFunc ef) : ModuleEventCallbackClosure(ef), _package(pkg) {} + void do_module(ModuleEntry* entry); +}; + +static void write_module_dependency_event(const void* from_module, const ModuleEntry* to_module) { + EventModuleRequire event(UNTIMED); + event.set_endtime(invocation_time); + event.set_source((const ModuleEntry* const)from_module); + event.set_requiredModule(to_module); + event.commit(); +} + +static void write_module_export_event(const void* package, const ModuleEntry* qualified_export) { + EventModuleExport event(UNTIMED); + event.set_endtime(invocation_time); + event.set_exportedPackage((const PackageEntry*)package); + event.set_targetModule(qualified_export); + event.commit(); +} + +void ModuleDependencyClosure::do_module(ModuleEntry* to_module) { + assert_locked_or_safepoint(Module_lock); + assert(to_module != NULL, "invariant"); + assert(_module != NULL, "invariant"); + assert(_event_func != NULL, "invariant"); + _event_func(_module, to_module); +} + +void ModuleExportClosure::do_module(ModuleEntry* qualified_export) { + assert_locked_or_safepoint(Module_lock); + assert(qualified_export != NULL, "invariant"); + assert(_package != NULL, "invariant"); + assert(_event_func != NULL, "invariant"); + _event_func(_package, qualified_export); +} + +static void module_dependency_event_callback(ModuleEntry* module) { + assert_locked_or_safepoint(Module_lock); + assert(module != NULL, "invariant"); + if (module->has_reads_list()) { + // create an individual event for each directed edge + ModuleDependencyClosure directed_edges(module, &write_module_dependency_event); + module->module_reads_do(&directed_edges); + } +} + +static void module_export_event_callback(PackageEntry* package) { + assert_locked_or_safepoint(Module_lock); + assert(package != NULL, "invariant"); + if (package->is_exported()) { + if (package->has_qual_exports_list()) { + // package is qualifiedly exported to a set of modules, + // create an event for each module in the qualified exported list + ModuleExportClosure qexports(package, &write_module_export_event); + package->package_exports_do(&qexports); + return; + } + + assert(!package->is_qual_exported() || package->is_exported_allUnnamed(), "invariant"); + // no qualified exports + // only create a single event with NULL + // for the qualified_exports module + write_module_export_event(package, NULL); + } +} + +void JfrModuleEvent::generate_module_dependency_events() { + invocation_time = JfrTicks::now(); + MutexLockerEx module_lock(Module_lock); + ClassLoaderDataGraph::modules_do(&module_dependency_event_callback); +} + +void JfrModuleEvent::generate_module_export_events() { + invocation_time = JfrTicks::now(); + MutexLockerEx module_lock(Module_lock); + ClassLoaderDataGraph::packages_do(&module_export_event_callback); +} diff --git a/src/hotspot/share/jfr/periodic/jfrModuleEvent.hpp b/src/hotspot/share/jfr/periodic/jfrModuleEvent.hpp new file mode 100644 index 00000000000..42e9d3c6d4d --- /dev/null +++ b/src/hotspot/share/jfr/periodic/jfrModuleEvent.hpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_JFR_EVENT_JFRMODULEEVENT_HPP +#define SHARE_VM_JFR_EVENT_JFRMODULEEVENT_HPP + +#include "memory/allocation.hpp" + +class JfrModuleEvent : AllStatic { + public: + static void generate_module_dependency_events(); + static void generate_module_export_events(); +}; + +#endif // SHARE_VM_JFR_EVENT_JFRMODULEEVENT_HPP diff --git a/src/hotspot/share/jfr/periodic/jfrOSInterface.cpp b/src/hotspot/share/jfr/periodic/jfrOSInterface.cpp new file mode 100644 index 00000000000..15abd526c98 --- /dev/null +++ b/src/hotspot/share/jfr/periodic/jfrOSInterface.cpp @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "jfr/jfrEvents.hpp" +#include "jfr/periodic/jfrOSInterface.hpp" +#include "memory/allocation.inline.hpp" +#include "memory/resourceArea.hpp" +#include "runtime/os.hpp" +#include "runtime/os_perf.hpp" +#include "utilities/ostream.hpp" + +#include // for environment variables +#ifdef __APPLE__ +#include +#define environ (*_NSGetEnviron()) +#endif + +#ifndef environ +extern char** environ; +#endif + +static JfrOSInterface* _instance = NULL; + +JfrOSInterface& JfrOSInterface::instance() { + return *_instance; +} + +JfrOSInterface* JfrOSInterface::create() { + assert(_instance == NULL, "invariant"); + _instance = new JfrOSInterface(); + return _instance; +} + +void JfrOSInterface::destroy() { + if (_instance != NULL) { + delete _instance; + _instance = NULL; + } +} + +class JfrOSInterface::JfrOSInterfaceImpl : public JfrCHeapObj { + friend class JfrOSInterface; + private: + CPUInformationInterface* _cpu_info_interface; + CPUPerformanceInterface* _cpu_perf_interface; + SystemProcessInterface* _system_process_interface; + + // stub helper + void functionality_not_implemented(char** str) const; + + JfrOSInterfaceImpl(); + bool initialize(); + ~JfrOSInterfaceImpl(); + + // cpu info + int cpu_information(CPUInformation& cpu_info); + int cpu_load(int which_logical_cpu, double* cpu_load); + int context_switch_rate(double* rate); + int cpu_load_total_process(double* cpu_load); + int cpu_loads_process(double* pjvmUserLoad, double* pjvmKernelLoad, double* psystemTotal); + + // os information + int os_version(char** os_version) const; + + // environment information + void generate_environment_variables_events(); + + // system processes information + int system_processes(SystemProcess** system_processes, int* no_of_sys_processes); +}; + +JfrOSInterface::JfrOSInterfaceImpl::JfrOSInterfaceImpl() : _cpu_info_interface(NULL), + _cpu_perf_interface(NULL), + _system_process_interface(NULL) {} + +bool JfrOSInterface::JfrOSInterfaceImpl::initialize() { + _cpu_info_interface = new CPUInformationInterface(); + bool success = _cpu_info_interface != NULL && _cpu_info_interface->initialize(); + if (!success) { + return false; + } + _cpu_perf_interface = new CPUPerformanceInterface(); + success = _cpu_perf_interface != NULL && _cpu_perf_interface->initialize(); + if (!success) { + return false; + } + _system_process_interface = new SystemProcessInterface(); + success = _system_process_interface != NULL && _system_process_interface->initialize(); + return success; +} + +JfrOSInterface::JfrOSInterfaceImpl::~JfrOSInterfaceImpl(void) { + if (_cpu_info_interface != NULL) { + delete _cpu_info_interface; + _cpu_info_interface = NULL; + } + if (_cpu_perf_interface != NULL) { + delete _cpu_perf_interface; + _cpu_perf_interface = NULL; + } + if (_system_process_interface != NULL) { + delete _system_process_interface; + _system_process_interface = NULL; + } +} + +int JfrOSInterface::JfrOSInterfaceImpl::cpu_load(int which_logical_cpu, double* cpu_load) { + return _cpu_perf_interface->cpu_load(which_logical_cpu, cpu_load); +} + +int JfrOSInterface::JfrOSInterfaceImpl::context_switch_rate(double* rate) { + return _cpu_perf_interface->context_switch_rate(rate); +} + +int JfrOSInterface::JfrOSInterfaceImpl::cpu_load_total_process(double* cpu_load) { + return _cpu_perf_interface->cpu_load_total_process(cpu_load); +} + +int JfrOSInterface::JfrOSInterfaceImpl::cpu_loads_process(double* pjvmUserLoad, + double* pjvmKernelLoad, + double* psystemTotal) { + return _cpu_perf_interface->cpu_loads_process(pjvmUserLoad, pjvmKernelLoad, psystemTotal); +} + +int JfrOSInterface::JfrOSInterfaceImpl::cpu_information(CPUInformation& cpu_info) { + return _cpu_info_interface->cpu_information(cpu_info); +} + +int JfrOSInterface::JfrOSInterfaceImpl::system_processes(SystemProcess** system_processes, int* no_of_sys_processes) { + assert(system_processes != NULL, "system_processes pointer is NULL!"); + assert(no_of_sys_processes != NULL, "no_of_sys_processes pointer is NULL!"); + return _system_process_interface->system_processes(system_processes, no_of_sys_processes); +} + +// assigned char* is RESOURCE_HEAP_ALLOCATED +// caller need to ensure proper ResourceMark placement. +int JfrOSInterface::JfrOSInterfaceImpl::os_version(char** os_version) const { + assert(os_version != NULL, "os_version pointer is NULL!"); + stringStream os_ver_info; + os::print_os_info_brief(&os_ver_info); + *os_version = os_ver_info.as_string(); + return OS_OK; +} + +void JfrOSInterface::JfrOSInterfaceImpl::functionality_not_implemented(char** str) const { + assert(str != NULL, "address to string is NULL!"); + const char* not_impl = "Functionality_not_implemented"; + const size_t not_impl_len = strlen(not_impl); + *str = NEW_C_HEAP_ARRAY(char, not_impl_len+1, mtTracing); + strncpy(*str, not_impl, not_impl_len); + (*str)[not_impl_len] = '\0'; +} + +JfrOSInterface::JfrOSInterface() { + _impl = NULL; +} + +bool JfrOSInterface::initialize() { + _impl = new JfrOSInterface::JfrOSInterfaceImpl(); + return _impl != NULL && _impl->initialize(); +} + +JfrOSInterface::~JfrOSInterface() { + if (_impl != NULL) { + delete _impl; + _impl = NULL; + } +} + +int JfrOSInterface::cpu_information(CPUInformation& cpu_info) { + return instance()._impl->cpu_information(cpu_info); +} + +int JfrOSInterface::cpu_load(int which_logical_cpu, double* cpu_load) { + return instance()._impl->cpu_load(which_logical_cpu, cpu_load); +} + +int JfrOSInterface::context_switch_rate(double* rate) { + return instance()._impl->context_switch_rate(rate); +} + +int JfrOSInterface::cpu_load_total_process(double* cpu_load) { + return instance()._impl->cpu_load_total_process(cpu_load); +} + +int JfrOSInterface::cpu_loads_process(double* jvm_user_load, double* jvm_kernel_load, double* system_total_load){ + return instance()._impl->cpu_loads_process(jvm_user_load, jvm_kernel_load, system_total_load); +} + +int JfrOSInterface::os_version(char** os_version) { + return instance()._impl->os_version(os_version); +} + +int JfrOSInterface::generate_initial_environment_variable_events() { + if (environ == NULL) { + return OS_ERR; + } + + if (EventInitialEnvironmentVariable::is_enabled()) { + // One time stamp for all events, so they can be grouped together + JfrTicks time_stamp = JfrTicks::now(); + for (char** p = environ; *p != NULL; p++) { + char* variable = *p; + char* equal_sign = strchr(variable, '='); + if (equal_sign != NULL) { + // Extract key/value + ResourceMark rm; + ptrdiff_t key_length = equal_sign - variable; + char* key = NEW_RESOURCE_ARRAY(char, key_length + 1); + char* value = equal_sign + 1; + strncpy(key, variable, key_length); + key[key_length] = '\0'; + EventInitialEnvironmentVariable event(UNTIMED); + event.set_endtime(time_stamp); + event.set_key(key); + event.set_value(value); + event.commit(); + } + } + } + return OS_OK; +} + +int JfrOSInterface::system_processes(SystemProcess** sys_processes, int* no_of_sys_processes) { + return instance()._impl->system_processes(sys_processes, no_of_sys_processes); +} diff --git a/src/hotspot/share/jfr/periodic/jfrOSInterface.hpp b/src/hotspot/share/jfr/periodic/jfrOSInterface.hpp new file mode 100644 index 00000000000..803633da409 --- /dev/null +++ b/src/hotspot/share/jfr/periodic/jfrOSInterface.hpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_JFR_PERIODIC_JFROSINTERFACE_HPP +#define SHARE_VM_JFR_PERIODIC_JFROSINTERFACE_HPP + +#include "jfr/utilities/jfrAllocation.hpp" +#include "utilities/globalDefinitions.hpp" + +class CPUInformation; +class EnvironmentVariable; +class SystemProcess; + +class JfrOSInterface: public JfrCHeapObj { + friend class JfrRecorder; + private: + class JfrOSInterfaceImpl; + JfrOSInterfaceImpl* _impl; + + JfrOSInterface(); + ~JfrOSInterface(); + bool initialize(); + static JfrOSInterface& instance(); + static JfrOSInterface* create(); + static void destroy(); + + public: + static int cpu_information(CPUInformation& cpu_info); + static int cpu_load(int which_logical_cpu, double* cpu_load); + static int context_switch_rate(double* rate); + static int cpu_load_total_process(double* cpu_load); + static int cpu_loads_process(double* pjvmUserLoad, double* pjvmKernelLoad, double* psystemTotalLoad); + static int os_version(char** os_version); + static int generate_initial_environment_variable_events(); + static int system_processes(SystemProcess** system_processes, int* no_of_sys_processes); +}; + +#endif // SHARE_VM_JFR_PERIODIC_JFROSINTERFACE_HPP diff --git a/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp b/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp new file mode 100644 index 00000000000..5543f1cfe95 --- /dev/null +++ b/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp @@ -0,0 +1,572 @@ +/* + * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "jvm.h" +#include "classfile/classLoaderStats.hpp" +#include "classfile/javaClasses.hpp" +#include "code/codeCache.hpp" +#include "compiler/compileBroker.hpp" +#include "gc/g1/g1HeapRegionEventSender.hpp" +#include "gc/shared/gcConfiguration.hpp" +#include "gc/shared/gcTrace.hpp" +#include "gc/shared/objectCountEventSender.hpp" +#include "gc/shared/vmGCOperations.hpp" +#include "jfr/jfrEvents.hpp" +#include "jfr/periodic/jfrModuleEvent.hpp" +#include "jfr/periodic/jfrOSInterface.hpp" +#include "jfr/periodic/jfrThreadCPULoadEvent.hpp" +#include "jfr/periodic/jfrThreadDumpEvent.hpp" +#include "jfr/recorder/jfrRecorder.hpp" +#include "jfr/support/jfrThreadId.hpp" +#include "jfr/utilities/jfrTime.hpp" +#include "jfrfiles/jfrPeriodic.hpp" +#include "logging/log.hpp" +#include "memory/heapInspection.hpp" +#include "memory/resourceArea.hpp" +#include "oops/oop.inline.hpp" +#include "runtime/arguments.hpp" +#include "runtime/flags/jvmFlag.hpp" +#include "runtime/globals.hpp" +#include "runtime/os.hpp" +#include "runtime/os_perf.hpp" +#include "runtime/thread.inline.hpp" +#include "runtime/threadSMR.hpp" +#include "runtime/sweeper.hpp" +#include "runtime/vmThread.hpp" +#include "services/classLoadingService.hpp" +#include "services/management.hpp" +#include "services/threadService.hpp" +#include "utilities/exceptions.hpp" +#include "utilities/globalDefinitions.hpp" + +/** + * JfrPeriodic class + * Implementation of declarations in + * xsl generated traceRequestables.hpp + */ +#define TRACE_REQUEST_FUNC(id) void JfrPeriodicEventSet::request##id(void) + +TRACE_REQUEST_FUNC(JVMInformation) { + ResourceMark rm; + EventJVMInformation event; + event.set_jvmName(VM_Version::vm_name()); + event.set_jvmVersion(VM_Version::internal_vm_info_string()); + event.set_javaArguments(Arguments::java_command()); + event.set_jvmArguments(Arguments::jvm_args()); + event.set_jvmFlags(Arguments::jvm_flags()); + event.set_jvmStartTime(Management::vm_init_done_time()); + event.commit(); + } + +TRACE_REQUEST_FUNC(OSInformation) { + ResourceMark rm; + char* os_name = NEW_RESOURCE_ARRAY(char, 2048); + JfrOSInterface::os_version(&os_name); + EventOSInformation event; + event.set_osVersion(os_name); + event.commit(); +} + +TRACE_REQUEST_FUNC(ModuleRequire) { + JfrModuleEvent::generate_module_dependency_events(); +} + +TRACE_REQUEST_FUNC(ModuleExport) { + JfrModuleEvent::generate_module_export_events(); +} + +/* + * This is left empty on purpose, having ExecutionSample as a requestable + * is a way of getting the period. The period is passed to ThreadSampling::update_period. + * Implementation in jfrSamples.cpp + */ +TRACE_REQUEST_FUNC(ExecutionSample) { +} +TRACE_REQUEST_FUNC(NativeMethodSample) { +} + +TRACE_REQUEST_FUNC(ThreadDump) { + ResourceMark rm; + EventThreadDump event; + event.set_result(JfrDcmdEvent::thread_dump()); + event.commit(); +} + +static int _native_library_callback(const char* name, address base, address top, void *param) { + EventNativeLibrary event(UNTIMED); + event.set_name(name); + event.set_baseAddress((u8)base); + event.set_topAddress((u8)top); + event.set_endtime(*(JfrTicks*) param); + event.commit(); + return 0; +} + +TRACE_REQUEST_FUNC(NativeLibrary) { + JfrTicks ts= JfrTicks::now(); + os::get_loaded_modules_info(&_native_library_callback, (void *)&ts); +} + +TRACE_REQUEST_FUNC(InitialEnvironmentVariable) { + JfrOSInterface::generate_initial_environment_variable_events(); +} + +TRACE_REQUEST_FUNC(CPUInformation) { + CPUInformation cpu_info; + int ret_val = JfrOSInterface::cpu_information(cpu_info); + if (ret_val == OS_ERR) { + log_debug(jfr, system)( "Unable to generate requestable event CPUInformation"); + return; + } + if (ret_val == FUNCTIONALITY_NOT_IMPLEMENTED) { + return; + } + if (ret_val == OS_OK) { + EventCPUInformation event; + event.set_cpu(cpu_info.cpu_name()); + event.set_description(cpu_info.cpu_description()); + event.set_sockets(cpu_info.number_of_sockets()); + event.set_cores(cpu_info.number_of_cores()); + event.set_hwThreads(cpu_info.number_of_hardware_threads()); + event.commit(); + } +} + +TRACE_REQUEST_FUNC(CPULoad) { + double u = 0; // user time + double s = 0; // kernel time + double t = 0; // total time + int ret_val = JfrOSInterface::cpu_loads_process(&u, &s, &t); + if (ret_val == OS_ERR) { + log_debug(jfr, system)( "Unable to generate requestable event CPULoad"); + return; + } + if (ret_val == OS_OK) { + EventCPULoad event; + event.set_jvmUser((float)u); + event.set_jvmSystem((float)s); + event.set_machineTotal((float)t); + event.commit(); + } +} + +TRACE_REQUEST_FUNC(ThreadCPULoad) { + JfrThreadCPULoadEvent::send_events(); +} + +TRACE_REQUEST_FUNC(CPUTimeStampCounter) { + EventCPUTimeStampCounter event; + event.set_fastTimeEnabled(JfrTime::is_ft_enabled()); + event.set_fastTimeAutoEnabled(JfrTime::is_ft_supported()); + event.set_osFrequency(os::elapsed_frequency()); + event.set_fastTimeFrequency(JfrTime::frequency()); + event.commit(); +} + +TRACE_REQUEST_FUNC(SystemProcess) { + char pid_buf[16]; + SystemProcess* processes = NULL; + int num_of_processes = 0; + JfrTicks start_time = JfrTicks::now(); + int ret_val = JfrOSInterface::system_processes(&processes, &num_of_processes); + if (ret_val == OS_ERR) { + log_debug(jfr, system)( "Unable to generate requestable event SystemProcesses"); + return; + } + JfrTicks end_time = JfrTicks::now(); + if (ret_val == FUNCTIONALITY_NOT_IMPLEMENTED) { + return; + } + if (ret_val == OS_OK) { + // feature is implemented, write real event + while (processes != NULL) { + SystemProcess* tmp = processes; + const char* info = processes->command_line(); + if (info == NULL) { + info = processes->path(); + } + if (info == NULL) { + info = processes->name(); + } + if (info == NULL) { + info = "?"; + } + jio_snprintf(pid_buf, sizeof(pid_buf), "%d", processes->pid()); + EventSystemProcess event(UNTIMED); + event.set_pid(pid_buf); + event.set_commandLine(info); + event.set_starttime(start_time); + event.set_endtime(end_time); + event.commit(); + processes = processes->next(); + delete tmp; + } + } +} + +TRACE_REQUEST_FUNC(ThreadContextSwitchRate) { + double rate = 0.0; + int ret_val = JfrOSInterface::context_switch_rate(&rate); + if (ret_val == OS_ERR) { + log_debug(jfr, system)( "Unable to generate requestable event ThreadContextSwitchRate"); + return; + } + if (ret_val == FUNCTIONALITY_NOT_IMPLEMENTED) { + return; + } + if (ret_val == OS_OK) { + EventThreadContextSwitchRate event; + event.set_switchRate((float)rate + 0.0f); + event.commit(); + } +} + +#define SEND_FLAGS_OF_TYPE(eventType, flagType) \ + do { \ + JVMFlag *flag = JVMFlag::flags; \ + while (flag->_name != NULL) { \ + if (flag->is_ ## flagType()) { \ + if (flag->is_unlocked()) { \ + Event ## eventType event; \ + event.set_name(flag->_name); \ + event.set_value(flag->get_ ## flagType()); \ + event.set_origin(flag->get_origin()); \ + event.commit(); \ + } \ + } \ + ++flag; \ + } \ + } while (0) + +TRACE_REQUEST_FUNC(IntFlag) { + SEND_FLAGS_OF_TYPE(IntFlag, int); +} + +TRACE_REQUEST_FUNC(UnsignedIntFlag) { + SEND_FLAGS_OF_TYPE(UnsignedIntFlag, uint); +} + +TRACE_REQUEST_FUNC(LongFlag) { + SEND_FLAGS_OF_TYPE(LongFlag, intx); +} + +TRACE_REQUEST_FUNC(UnsignedLongFlag) { + SEND_FLAGS_OF_TYPE(UnsignedLongFlag, uintx); + SEND_FLAGS_OF_TYPE(UnsignedLongFlag, uint64_t); + SEND_FLAGS_OF_TYPE(UnsignedLongFlag, size_t); +} + +TRACE_REQUEST_FUNC(DoubleFlag) { + SEND_FLAGS_OF_TYPE(DoubleFlag, double); +} + +TRACE_REQUEST_FUNC(BooleanFlag) { + SEND_FLAGS_OF_TYPE(BooleanFlag, bool); +} + +TRACE_REQUEST_FUNC(StringFlag) { + SEND_FLAGS_OF_TYPE(StringFlag, ccstr); +} + +class VM_GC_SendObjectCountEvent : public VM_GC_HeapInspection { + public: + VM_GC_SendObjectCountEvent() : VM_GC_HeapInspection(NULL, true) {} + virtual void doit() { + ObjectCountEventSender::enable_requestable_event(); + collect(); + ObjectCountEventSender::disable_requestable_event(); + } +}; + +TRACE_REQUEST_FUNC(ObjectCount) { + VM_GC_SendObjectCountEvent op; + VMThread::execute(&op); +} + +class VM_G1SendHeapRegionInfoEvents : public VM_Operation { + virtual void doit() { + G1HeapRegionEventSender::send_events(); + } + virtual VMOp_Type type() const { return VMOp_HeapIterateOperation; } +}; + +TRACE_REQUEST_FUNC(G1HeapRegionInformation) { + if (UseG1GC) { + VM_G1SendHeapRegionInfoEvents op; + VMThread::execute(&op); + } +} + +// Java Mission Control (JMC) uses (Java) Long.MIN_VALUE to describe that a +// long value is undefined. +static jlong jmc_undefined_long = min_jlong; + +TRACE_REQUEST_FUNC(GCConfiguration) { + GCConfiguration conf; + jlong pause_target = conf.has_pause_target_default_value() ? jmc_undefined_long : conf.pause_target(); + EventGCConfiguration event; + event.set_youngCollector(conf.young_collector()); + event.set_oldCollector(conf.old_collector()); + event.set_parallelGCThreads(conf.num_parallel_gc_threads()); + event.set_concurrentGCThreads(conf.num_concurrent_gc_threads()); + event.set_usesDynamicGCThreads(conf.uses_dynamic_gc_threads()); + event.set_isExplicitGCConcurrent(conf.is_explicit_gc_concurrent()); + event.set_isExplicitGCDisabled(conf.is_explicit_gc_disabled()); + event.set_gcTimeRatio(conf.gc_time_ratio()); + event.set_pauseTarget((s8)pause_target); + event.commit(); +} + +TRACE_REQUEST_FUNC(GCTLABConfiguration) { + GCTLABConfiguration conf; + EventGCTLABConfiguration event; + event.set_usesTLABs(conf.uses_tlabs()); + event.set_minTLABSize(conf.min_tlab_size()); + event.set_tlabRefillWasteLimit(conf.tlab_refill_waste_limit()); + event.commit(); +} + +TRACE_REQUEST_FUNC(GCSurvivorConfiguration) { + GCSurvivorConfiguration conf; + EventGCSurvivorConfiguration event; + event.set_maxTenuringThreshold(conf.max_tenuring_threshold()); + event.set_initialTenuringThreshold(conf.initial_tenuring_threshold()); + event.commit(); +} + +TRACE_REQUEST_FUNC(GCHeapConfiguration) { + GCHeapConfiguration conf; + EventGCHeapConfiguration event; + event.set_minSize(conf.min_size()); + event.set_maxSize(conf.max_size()); + event.set_initialSize(conf.initial_size()); + event.set_usesCompressedOops(conf.uses_compressed_oops()); + event.set_compressedOopsMode(conf.narrow_oop_mode()); + event.set_objectAlignment(conf.object_alignment_in_bytes()); + event.set_heapAddressBits(conf.heap_address_size_in_bits()); + event.commit(); +} + +TRACE_REQUEST_FUNC(YoungGenerationConfiguration) { + GCYoungGenerationConfiguration conf; + jlong max_size = conf.has_max_size_default_value() ? jmc_undefined_long : conf.max_size(); + EventYoungGenerationConfiguration event; + event.set_maxSize((u8)max_size); + event.set_minSize(conf.min_size()); + event.set_newRatio(conf.new_ratio()); + event.commit(); +} + +TRACE_REQUEST_FUNC(InitialSystemProperty) { + SystemProperty* p = Arguments::system_properties(); + JfrTicks time_stamp = JfrTicks::now(); + while (p != NULL) { + if (!p->internal()) { + EventInitialSystemProperty event(UNTIMED); + event.set_key(p->key()); + event.set_value(p->value()); + event.set_endtime(time_stamp); + event.commit(); + } + p = p->next(); + } +} + +TRACE_REQUEST_FUNC(ThreadAllocationStatistics) { + ResourceMark rm; + int initial_size = Threads::number_of_threads(); + GrowableArray allocated(initial_size); + GrowableArray thread_ids(initial_size); + JfrTicks time_stamp = JfrTicks::now(); + { + // Collect allocation statistics while holding threads lock + MutexLockerEx ml(Threads_lock); + for (JavaThreadIteratorWithHandle jtiwh; JavaThread *jt = jtiwh.next(); ) { + allocated.append(jt->cooked_allocated_bytes()); + thread_ids.append(JFR_THREAD_ID(jt)); + } + } + + // Write allocation statistics to buffer. + for(int i = 0; i < thread_ids.length(); i++) { + EventThreadAllocationStatistics event(UNTIMED); + event.set_allocated(allocated.at(i)); + event.set_thread(thread_ids.at(i)); + event.set_endtime(time_stamp); + event.commit(); + } +} + +/** + * PhysicalMemory event represents: + * + * @totalSize == The amount of physical memory (hw) installed and reported by the OS, in bytes. + * @usedSize == The amount of physical memory currently in use in the system (reserved/committed), in bytes. + * + * Both fields are systemwide, i.e. represents the entire OS/HW environment. + * These fields do not include virtual memory. + * + * If running inside a guest OS on top of a hypervisor in a virtualized environment, + * the total memory reported is the amount of memory configured for the guest OS by the hypervisor. + */ +TRACE_REQUEST_FUNC(PhysicalMemory) { + u8 totalPhysicalMemory = os::physical_memory(); + EventPhysicalMemory event; + event.set_totalSize(totalPhysicalMemory); + event.set_usedSize(totalPhysicalMemory - os::available_memory()); + event.commit(); +} + +TRACE_REQUEST_FUNC(JavaThreadStatistics) { + EventJavaThreadStatistics event; + event.set_activeCount(ThreadService::get_live_thread_count()); + event.set_daemonCount(ThreadService::get_daemon_thread_count()); + event.set_accumulatedCount(ThreadService::get_total_thread_count()); + event.set_peakCount(ThreadService::get_peak_thread_count()); + event.commit(); +} + +TRACE_REQUEST_FUNC(ClassLoadingStatistics) { + EventClassLoadingStatistics event; + event.set_loadedClassCount(ClassLoadingService::loaded_class_count()); + event.set_unloadedClassCount(ClassLoadingService::unloaded_class_count()); + event.commit(); +} + +class JfrClassLoaderStatsClosure : public ClassLoaderStatsClosure { +public: + JfrClassLoaderStatsClosure() : ClassLoaderStatsClosure(NULL) {} + + bool do_entry(oop const& key, ClassLoaderStats* const& cls) { + const ClassLoaderData* this_cld = cls->_class_loader != NULL ? + java_lang_ClassLoader::loader_data(cls->_class_loader) : (ClassLoaderData*)NULL; + const ClassLoaderData* parent_cld = cls->_parent != NULL ? + java_lang_ClassLoader::loader_data(cls->_parent) : (ClassLoaderData*)NULL; + EventClassLoaderStatistics event; + event.set_classLoader(this_cld); + event.set_parentClassLoader(parent_cld); + event.set_classLoaderData((intptr_t)cls->_cld); + event.set_classCount(cls->_classes_count); + event.set_chunkSize(cls->_chunk_sz); + event.set_blockSize(cls->_block_sz); + event.set_anonymousClassCount(cls->_anon_classes_count); + event.set_anonymousChunkSize(cls->_anon_chunk_sz); + event.set_anonymousBlockSize(cls->_anon_block_sz); + event.commit(); + return true; + } + + void createEvents(void) { + _stats->iterate(this); + } +}; + +class JfrClassLoaderStatsVMOperation : public ClassLoaderStatsVMOperation { + public: + JfrClassLoaderStatsVMOperation() : ClassLoaderStatsVMOperation(NULL) { } + + void doit() { + JfrClassLoaderStatsClosure clsc; + ClassLoaderDataGraph::cld_do(&clsc); + clsc.createEvents(); + } +}; + +TRACE_REQUEST_FUNC(ClassLoaderStatistics) { + JfrClassLoaderStatsVMOperation op; + VMThread::execute(&op); +} + +TRACE_REQUEST_FUNC(CompilerStatistics) { + EventCompilerStatistics event; + event.set_compileCount(CompileBroker::get_total_compile_count()); + event.set_bailoutCount(CompileBroker::get_total_bailout_count()); + event.set_invalidatedCount(CompileBroker::get_total_invalidated_count()); + event.set_osrCompileCount(CompileBroker::get_total_osr_compile_count()); + event.set_standardCompileCount(CompileBroker::get_total_standard_compile_count()); + event.set_osrBytesCompiled(CompileBroker::get_sum_osr_bytes_compiled()); + event.set_standardBytesCompiled(CompileBroker::get_sum_standard_bytes_compiled()); + event.set_nmetodsSize(CompileBroker::get_sum_nmethod_size()); + event.set_nmetodCodeSize(CompileBroker::get_sum_nmethod_code_size()); + event.set_peakTimeSpent(CompileBroker::get_peak_compilation_time()); + event.set_totalTimeSpent(CompileBroker::get_total_compilation_time()); + event.commit(); +} + +TRACE_REQUEST_FUNC(CompilerConfiguration) { + EventCompilerConfiguration event; + event.set_threadCount(CICompilerCount); + event.set_tieredCompilation(TieredCompilation); + event.commit(); +} + +TRACE_REQUEST_FUNC(CodeCacheStatistics) { + // Emit stats for all available code heaps + for (int bt = 0; bt < CodeBlobType::NumTypes; ++bt) { + if (CodeCache::heap_available(bt)) { + EventCodeCacheStatistics event; + event.set_codeBlobType((u1)bt); + event.set_startAddress((u8)CodeCache::low_bound(bt)); + event.set_reservedTopAddress((u8)CodeCache::high_bound(bt)); + event.set_entryCount(CodeCache::blob_count(bt)); + event.set_methodCount(CodeCache::nmethod_count(bt)); + event.set_adaptorCount(CodeCache::adapter_count(bt)); + event.set_unallocatedCapacity(CodeCache::unallocated_capacity(bt)); + event.set_fullCount(CodeCache::get_codemem_full_count(bt)); + event.commit(); + } + } +} + +TRACE_REQUEST_FUNC(CodeCacheConfiguration) { + EventCodeCacheConfiguration event; + event.set_initialSize(InitialCodeCacheSize); + event.set_reservedSize(ReservedCodeCacheSize); + event.set_nonNMethodSize(NonNMethodCodeHeapSize); + event.set_profiledSize(ProfiledCodeHeapSize); + event.set_nonProfiledSize(NonProfiledCodeHeapSize); + event.set_expansionSize(CodeCacheExpansionSize); + event.set_minBlockLength(CodeCacheMinBlockLength); + event.set_startAddress((u8)CodeCache::low_bound()); + event.set_reservedTopAddress((u8)CodeCache::high_bound()); + event.commit(); +} + +TRACE_REQUEST_FUNC(CodeSweeperStatistics) { + EventCodeSweeperStatistics event; + event.set_sweepCount(NMethodSweeper::traversal_count()); + event.set_methodReclaimedCount(NMethodSweeper::total_nof_methods_reclaimed()); + event.set_totalSweepTime(NMethodSweeper::total_time_sweeping()); + event.set_peakFractionTime(NMethodSweeper::peak_sweep_fraction_time()); + event.set_peakSweepTime(NMethodSweeper::peak_sweep_time()); + event.commit(); +} + +TRACE_REQUEST_FUNC(CodeSweeperConfiguration) { + EventCodeSweeperConfiguration event; + event.set_sweeperEnabled(MethodFlushing); + event.set_flushingEnabled(UseCodeCacheFlushing); + event.commit(); +} diff --git a/src/hotspot/share/jfr/periodic/jfrThreadCPULoadEvent.cpp b/src/hotspot/share/jfr/periodic/jfrThreadCPULoadEvent.cpp new file mode 100644 index 00000000000..86be9806264 --- /dev/null +++ b/src/hotspot/share/jfr/periodic/jfrThreadCPULoadEvent.cpp @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "logging/log.hpp" +#include "jfr/jfrEvents.hpp" +#include "jfr/periodic/jfrThreadCPULoadEvent.hpp" +#include "jfr/support/jfrThreadId.hpp" +#include "jfr/support/jfrThreadLocal.hpp" +#include "jfr/utilities/jfrTime.hpp" +#include "utilities/globalDefinitions.hpp" +#include "runtime/os.hpp" +#include "runtime/thread.inline.hpp" +#include "runtime/threadSMR.inline.hpp" + +jlong JfrThreadCPULoadEvent::get_wallclock_time() { + return os::javaTimeNanos(); +} + +int JfrThreadCPULoadEvent::_last_active_processor_count = 0; + +int JfrThreadCPULoadEvent::get_processor_count() { + int cur_processor_count = os::active_processor_count(); + int last_processor_count = _last_active_processor_count; + _last_active_processor_count = cur_processor_count; + + // If the number of processors decreases, we don't know at what point during + // the sample interval this happened, so use the largest number to try + // to avoid percentages above 100% + return MAX2(cur_processor_count, last_processor_count); +} + +// Returns false if the thread has not been scheduled since the last call to updateEvent +// (i.e. the delta for both system and user time is 0 milliseconds) +bool JfrThreadCPULoadEvent::update_event(EventThreadCPULoad& event, JavaThread* thread, jlong cur_wallclock_time, int processor_count) { + JfrThreadLocal* const tl = thread->jfr_thread_local(); + + jlong cur_cpu_time = os::thread_cpu_time(thread, true); + jlong prev_cpu_time = tl->get_cpu_time(); + + jlong prev_wallclock_time = tl->get_wallclock_time(); + tl->set_wallclock_time(cur_wallclock_time); + + // Threshold of 1 ms + if (cur_cpu_time - prev_cpu_time < 1 * NANOSECS_PER_MILLISEC) { + return false; + } + + jlong cur_user_time = os::thread_cpu_time(thread, false); + jlong prev_user_time = tl->get_user_time(); + + jlong cur_system_time = cur_cpu_time - cur_user_time; + jlong prev_system_time = prev_cpu_time - prev_user_time; + + // The user and total cpu usage clocks can have different resolutions, which can + // make us see decreasing system time. Ensure time doesn't go backwards. + if (prev_system_time > cur_system_time) { + cur_cpu_time += prev_system_time - cur_system_time; + cur_system_time = prev_system_time; + } + + jlong user_time = cur_user_time - prev_user_time; + jlong system_time = cur_system_time - prev_system_time; + jlong wallclock_time = cur_wallclock_time - prev_wallclock_time; + jlong total_available_time = wallclock_time * processor_count; + + // Avoid reporting percentages above the theoretical max + if (user_time + system_time > wallclock_time) { + jlong excess = user_time + system_time - wallclock_time; + if (user_time > excess) { + user_time -= excess; + cur_user_time -= excess; + cur_cpu_time -= excess; + } else { + cur_cpu_time -= excess; + excess -= user_time; + user_time = 0; + cur_user_time = 0; + system_time -= excess; + } + } + event.set_user(total_available_time > 0 ? (double)user_time / total_available_time : 0); + event.set_system(total_available_time > 0 ? (double)system_time / total_available_time : 0); + tl->set_user_time(cur_user_time); + tl->set_cpu_time(cur_cpu_time); + return true; +} + +void JfrThreadCPULoadEvent::send_events() { + Thread* periodic_thread = Thread::current(); + JfrThreadLocal* const periodic_thread_tl = periodic_thread->jfr_thread_local(); + traceid periodic_thread_id = periodic_thread_tl->thread_id(); + const int processor_count = JfrThreadCPULoadEvent::get_processor_count(); + JfrTicks event_time = JfrTicks::now(); + jlong cur_wallclock_time = JfrThreadCPULoadEvent::get_wallclock_time(); + + JavaThreadIteratorWithHandle jtiwh; + while (JavaThread* jt = jtiwh.next()) { + EventThreadCPULoad event(UNTIMED); + if (JfrThreadCPULoadEvent::update_event(event, jt, cur_wallclock_time, processor_count)) { + event.set_starttime(event_time); + if (jt != periodic_thread) { + // Commit reads the thread id from this thread's trace data, so put it there temporarily + periodic_thread_tl->set_thread_id(JFR_THREAD_ID(jt)); + } else { + periodic_thread_tl->set_thread_id(periodic_thread_id); + } + event.commit(); + } + } + log_trace(jfr)("Measured CPU usage for %d threads in %.3f milliseconds", jtiwh.length(), + (double)(JfrTicks::now() - event_time).milliseconds()); + // Restore this thread's thread id + periodic_thread_tl->set_thread_id(periodic_thread_id); +} + +void JfrThreadCPULoadEvent::send_event_for_thread(JavaThread* jt) { + EventThreadCPULoad event; + if (event.should_commit()) { + if (update_event(event, jt, get_wallclock_time(), get_processor_count())) { + event.commit(); + } + } +} diff --git a/src/hotspot/share/jfr/periodic/jfrThreadCPULoadEvent.hpp b/src/hotspot/share/jfr/periodic/jfrThreadCPULoadEvent.hpp new file mode 100644 index 00000000000..99b3fa7fe9b --- /dev/null +++ b/src/hotspot/share/jfr/periodic/jfrThreadCPULoadEvent.hpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_JFR_PERIODIC_JFRTHREADCPULOAD_HPP +#define SHARE_VM_JFR_PERIODIC_JFRTHREADCPULOAD_HPP + +#include "jni.h" +#include "memory/allocation.hpp" + +class JavaThread; +class EventThreadCPULoad; + +class JfrThreadCPULoadEvent : public AllStatic { + static int _last_active_processor_count; + public: + static jlong get_wallclock_time(); + static int get_processor_count(); + static bool update_event(EventThreadCPULoad& event, JavaThread* thread, jlong cur_wallclock_time, int processor_count); + static void send_events(); + static void send_event_for_thread(JavaThread* jt); +}; + +#endif // SHARE_VM_JFR_PERIODIC_JFRTHREADCPULOAD_HPP + diff --git a/src/hotspot/share/jfr/periodic/jfrThreadDumpEvent.cpp b/src/hotspot/share/jfr/periodic/jfrThreadDumpEvent.cpp new file mode 100644 index 00000000000..42c0bf07079 --- /dev/null +++ b/src/hotspot/share/jfr/periodic/jfrThreadDumpEvent.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "jfr/dcmd/jfrDcmds.hpp" +#include "jfr/jfrEvents.hpp" +#include "jfr/periodic/jfrThreadDumpEvent.hpp" +#include "logging/log.hpp" +#include "utilities/exceptions.hpp" +#include "utilities/ostream.hpp" + +/** +* Worker impl for generating and writing dcmd commands +* as jfr events. +* dispatch to diagnosticcommands "parse_and_execute" +* +* param: cmd = the DCMD to execute (including options) +*/ +static bool execute_dcmd(bufferedStream& st, const char* const cmd) { + Thread* THREAD = Thread::current(); + assert(!HAS_PENDING_EXCEPTION, "dcmd does not expect pending exceptions on entry!"); + // delegate to DCmd execution + DCmd::parse_and_execute(DCmd_Source_Internal, &st, cmd, ' ', THREAD); + if (HAS_PENDING_EXCEPTION) { + log_debug(jfr, system)("unable to create jfr event for DCMD %s", cmd); + log_debug(jfr, system)("exception type: %s", PENDING_EXCEPTION->klass()->external_name()); + // don't unwind this exception + CLEAR_PENDING_EXCEPTION; + // if exception occurred, + // reset stream. + st.reset(); + return false; + } + return true; +} + +// caller needs ResourceMark +const char* JfrDcmdEvent::thread_dump() { + assert(EventThreadDump::is_enabled(), "invariant"); + bufferedStream st; + execute_dcmd(st, "Thread.print"); + return st.as_string(); +} diff --git a/src/hotspot/share/jfr/periodic/jfrThreadDumpEvent.hpp b/src/hotspot/share/jfr/periodic/jfrThreadDumpEvent.hpp new file mode 100644 index 00000000000..d1b90644ebe --- /dev/null +++ b/src/hotspot/share/jfr/periodic/jfrThreadDumpEvent.hpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_JFR_PERIODIC_JFRDCMDEVENT_HPP +#define SHARE_VM_JFR_PERIODIC_JFRDCMDEVENT_HPP + +#include "memory/allocation.hpp" + +/* + * Helper for generating jfr events using output data from Dcmd's. + */ +class JfrDcmdEvent : public AllStatic { + public: + // caller needs ResourceMark + static const char* thread_dump(); +}; + +#endif // SHARE_VM_JFR_PERIODIC_JFRDCMDEVENT_HPP diff --git a/src/hotspot/share/jfr/periodic/sampling/jfrCallTrace.cpp b/src/hotspot/share/jfr/periodic/sampling/jfrCallTrace.cpp new file mode 100644 index 00000000000..66095f05dfe --- /dev/null +++ b/src/hotspot/share/jfr/periodic/sampling/jfrCallTrace.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "code/debugInfoRec.hpp" +#include "code/nmethod.hpp" +#include "code/pcDesc.hpp" +#include "jfr/periodic/sampling/jfrCallTrace.hpp" +#include "oops/method.hpp" +#include "runtime/javaCalls.hpp" +#include "runtime/frame.inline.hpp" +#include "runtime/registerMap.hpp" +#include "runtime/thread.inline.hpp" + +bool JfrGetCallTrace::find_top_frame(frame& top_frame, Method** method, frame& first_frame) { + assert(top_frame.cb() != NULL, "invariant"); + RegisterMap map(_thread, false); + frame candidate = top_frame; + for (int i = 0; i < MaxJavaStackTraceDepth * 2; ++i) { + if (candidate.is_entry_frame()) { + JavaCallWrapper *jcw = candidate.entry_frame_call_wrapper_if_safe(_thread); + if (jcw == NULL || jcw->is_first_frame()) { + return false; + } + } + + if (candidate.is_interpreted_frame()) { + JavaThreadState state = _thread->thread_state(); + const bool known_valid = (state == _thread_in_native || state == _thread_in_vm || state == _thread_blocked); + if (known_valid || candidate.is_interpreted_frame_valid(_thread)) { + Method* im = candidate.interpreter_frame_method(); + if (known_valid && !im->is_valid_method()) { + return false; + } + *method = im; + first_frame = candidate; + return true; + } + } + + if (candidate.cb()->is_nmethod()) { + // first check to make sure that we have a sane stack, + // the PC is actually inside the code part of the codeBlob, + // and we are past is_frame_complete_at (stack has been setup) + if (!candidate.safe_for_sender(_thread)) { + return false; + } + nmethod* nm = (nmethod*)candidate.cb(); + *method = nm->method(); + + if (_in_java) { + PcDesc* pc_desc = nm->pc_desc_near(candidate.pc() + 1); + if (pc_desc == NULL || pc_desc->scope_decode_offset() == DebugInformationRecorder::serialized_null) { + return false; + } + candidate.set_pc(pc_desc->real_pc(nm)); + assert(nm->pc_desc_at(candidate.pc()) != NULL, "invalid pc"); + } + first_frame = candidate; + return true; + } + + if (!candidate.safe_for_sender(_thread) || + candidate.is_stub_frame() || + candidate.cb()->frame_size() <= 0) { + return false; + } + + candidate = candidate.sender(&map); + if (candidate.cb() == NULL) { + return false; + } + } + return false; +} + +bool JfrGetCallTrace::get_topframe(void* ucontext, frame& topframe) { + if (!_thread->pd_get_top_frame_for_profiling(&topframe, ucontext, _in_java)) { + return false; + } + + if (topframe.cb() == NULL) { + return false; + } + + frame first_java_frame; + Method* method = NULL; + if (find_top_frame(topframe, &method, first_java_frame)) { + if (method == NULL) { + return false; + } + topframe = first_java_frame; + return true; + } + return false; +} diff --git a/src/hotspot/share/jfr/periodic/sampling/jfrCallTrace.hpp b/src/hotspot/share/jfr/periodic/sampling/jfrCallTrace.hpp new file mode 100644 index 00000000000..768778b6b0b --- /dev/null +++ b/src/hotspot/share/jfr/periodic/sampling/jfrCallTrace.hpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_JFR_ENGINE_SAMPLING_JFRCALLTRACE_HPP +#define SHARE_VM_JFR_ENGINE_SAMPLING_JFRCALLTRACE_HPP + +#include "memory/allocation.hpp" + +class frame; +class Method; +class JavaThread; + +class JfrGetCallTrace : public StackObj { + private: + JavaThread* _thread; + bool _in_java; + + public: + JfrGetCallTrace(bool in_java, JavaThread* thread) : _in_java(in_java), _thread(thread) {} + bool find_top_frame(frame& topframe, Method** method, frame& first_frame); + bool get_topframe(void* ucontext, frame& top); +}; + +#endif // SHARE_VM_JFR_ENGINE_SAMPLING_JFRCALLTRACE_HPP diff --git a/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampler.cpp b/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampler.cpp new file mode 100644 index 00000000000..c682dc9318c --- /dev/null +++ b/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampler.cpp @@ -0,0 +1,637 @@ +/* + * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "jfr/jfrEvents.hpp" +#include "jfr/recorder/jfrRecorder.hpp" +#include "jfr/periodic/sampling/jfrCallTrace.hpp" +#include "jfr/periodic/sampling/jfrThreadSampler.hpp" +#include "jfr/recorder/service/jfrOptionSet.hpp" +#include "jfr/recorder/stacktrace/jfrStackTraceRepository.hpp" +#include "jfr/support/jfrThreadId.hpp" +#include "jfr/utilities/jfrTime.hpp" +#include "logging/log.hpp" +#include "runtime/frame.inline.hpp" +#include "runtime/os.hpp" +#include "runtime/semaphore.hpp" +#include "runtime/thread.inline.hpp" +#include "runtime/threadSMR.hpp" + +enum JfrSampleType { + NO_SAMPLE = 0, + JAVA_SAMPLE = 1, + NATIVE_SAMPLE = 2 +}; + +static bool in_java_sample(JavaThread* thread) { + switch (thread->thread_state()) { + case _thread_new: + case _thread_uninitialized: + case _thread_new_trans: + case _thread_in_vm_trans: + case _thread_blocked_trans: + case _thread_in_native_trans: + case _thread_blocked: + case _thread_in_vm: + case _thread_in_native: + case _thread_in_Java_trans: + break; + case _thread_in_Java: + return true; + default: + ShouldNotReachHere(); + break; + } + return false; +} + +static bool in_native_sample(JavaThread* thread) { + switch (thread->thread_state()) { + case _thread_new: + case _thread_uninitialized: + case _thread_new_trans: + case _thread_blocked_trans: + case _thread_blocked: + case _thread_in_vm: + case _thread_in_vm_trans: + case _thread_in_Java_trans: + case _thread_in_Java: + case _thread_in_native_trans: + break; + case _thread_in_native: + return true; + default: + ShouldNotReachHere(); + break; + } + return false; +} + +class JfrThreadSampleClosure { + public: + JfrThreadSampleClosure(EventExecutionSample* events, EventNativeMethodSample* events_native); + ~JfrThreadSampleClosure() {} + EventExecutionSample* next_event() { return &_events[_added_java++]; } + EventNativeMethodSample* next_event_native() { return &_events_native[_added_native++]; } + void commit_events(); + int added() const { return _added_java; } + JfrSampleType do_sample_thread(JavaThread* thread, JfrStackFrame* frames, u4 max_frames, bool java_sample, bool native_sample); + int java_entries() { return _added_java; } + int native_entries() { return _added_native; } + + private: + bool sample_thread_in_java(JavaThread* thread, JfrStackFrame* frames, u4 max_frames); + bool sample_thread_in_native(JavaThread* thread, JfrStackFrame* frames, u4 max_frames); + EventExecutionSample* _events; + EventNativeMethodSample* _events_native; + Thread* _self; + int _added_java; + int _added_native; +}; + +class OSThreadSampler : public os::SuspendedThreadTask { + public: + OSThreadSampler(JavaThread* thread, + JfrThreadSampleClosure& closure, + JfrStackFrame *frames, + u4 max_frames) : os::SuspendedThreadTask((Thread*)thread), + _success(false), + _stacktrace(frames, max_frames), + _closure(closure), + _suspend_time() {} + + void take_sample(); + void do_task(const os::SuspendedThreadTaskContext& context); + void protected_task(const os::SuspendedThreadTaskContext& context); + bool success() const { return _success; } + const JfrStackTrace& stacktrace() const { return _stacktrace; } + + private: + bool _success; + JfrStackTrace _stacktrace; + JfrThreadSampleClosure& _closure; + JfrTicks _suspend_time; +}; + +class OSThreadSamplerCallback : public os::CrashProtectionCallback { + public: + OSThreadSamplerCallback(OSThreadSampler& sampler, const os::SuspendedThreadTaskContext &context) : + _sampler(sampler), _context(context) { + } + virtual void call() { + _sampler.protected_task(_context); + } + private: + OSThreadSampler& _sampler; + const os::SuspendedThreadTaskContext& _context; +}; + +void OSThreadSampler::do_task(const os::SuspendedThreadTaskContext& context) { +#ifndef ASSERT + guarantee(JfrOptionSet::sample_protection(), "Sample Protection should be on in product builds"); +#endif + assert(_suspend_time.value() == 0, "already timestamped!"); + _suspend_time = JfrTicks::now(); + + if (JfrOptionSet::sample_protection()) { + OSThreadSamplerCallback cb(*this, context); + os::ThreadCrashProtection crash_protection; + if (!crash_protection.call(cb)) { + log_error(jfr)("Thread method sampler crashed"); + } + } else { + protected_task(context); + } +} + +/* +* From this method and down the call tree we attempt to protect against crashes +* using a signal handler / __try block. Don't take locks, rely on destructors or +* leave memory (in case of signal / exception) in an inconsistent state. */ +void OSThreadSampler::protected_task(const os::SuspendedThreadTaskContext& context) { + JavaThread* jth = (JavaThread*)context.thread(); + // Skip sample if we signaled a thread that moved to other state + if (!in_java_sample(jth)) { + return; + } + JfrGetCallTrace trace(true, jth); + frame topframe; + if (trace.get_topframe(context.ucontext(), topframe)) { + if (_stacktrace.record_thread(*jth, topframe)) { + /* If we managed to get a topframe and a stacktrace, create an event + * and put it into our array. We can't call Jfr::_stacktraces.add() + * here since it would allocate memory using malloc. Doing so while + * the stopped thread is inside malloc would deadlock. */ + _success = true; + EventExecutionSample *ev = _closure.next_event(); + ev->set_starttime(_suspend_time); + ev->set_endtime(_suspend_time); // fake to not take an end time + ev->set_sampledThread(JFR_THREAD_ID(jth)); + ev->set_state(java_lang_Thread::get_thread_status(jth->threadObj())); + } + } +} + +void OSThreadSampler::take_sample() { + run(); +} + +class JfrNativeSamplerCallback : public os::CrashProtectionCallback { + public: + JfrNativeSamplerCallback(JfrThreadSampleClosure& closure, JavaThread* jt, JfrStackFrame* frames, u4 max_frames) : + _closure(closure), _jt(jt), _stacktrace(frames, max_frames), _success(false) { + } + virtual void call(); + bool success() { return _success; } + JfrStackTrace& stacktrace() { return _stacktrace; } + + private: + JfrThreadSampleClosure& _closure; + JavaThread* _jt; + JfrStackTrace _stacktrace; + bool _success; +}; + +static void write_native_event(JfrThreadSampleClosure& closure, JavaThread* jt) { + EventNativeMethodSample *ev = closure.next_event_native(); + ev->set_starttime(JfrTicks::now()); + ev->set_sampledThread(JFR_THREAD_ID(jt)); + ev->set_state(java_lang_Thread::get_thread_status(jt->threadObj())); +} + +void JfrNativeSamplerCallback::call() { + // When a thread is only attach it will be native without a last java frame + if (!_jt->has_last_Java_frame()) { + return; + } + + frame topframe = _jt->last_frame(); + frame first_java_frame; + Method* method = NULL; + JfrGetCallTrace gct(false, _jt); + if (!gct.find_top_frame(topframe, &method, first_java_frame)) { + return; + } + if (method == NULL) { + return; + } + topframe = first_java_frame; + _success = _stacktrace.record_thread(*_jt, topframe); + if (_success) { + write_native_event(_closure, _jt); + } +} + +bool JfrThreadSampleClosure::sample_thread_in_java(JavaThread* thread, JfrStackFrame* frames, u4 max_frames) { + OSThreadSampler sampler(thread, *this, frames, max_frames); + sampler.take_sample(); + /* We don't want to allocate any memory using malloc/etc while the thread + * is stopped, so everything is stored in stack allocated memory until this + * point where the thread has been resumed again, if the sampling was a success + * we need to store the stacktrace in the stacktrace repository and update + * the event with the id that was returned. */ + if (!sampler.success()) { + return false; + } + EventExecutionSample *event = &_events[_added_java - 1]; + traceid id = JfrStackTraceRepository::add(sampler.stacktrace()); + assert(id != 0, "Stacktrace id should not be 0"); + event->set_stackTrace(id); + return true; +} + +bool JfrThreadSampleClosure::sample_thread_in_native(JavaThread* thread, JfrStackFrame* frames, u4 max_frames) { + JfrNativeSamplerCallback cb(*this, thread, frames, max_frames); + if (JfrOptionSet::sample_protection()) { + os::ThreadCrashProtection crash_protection; + if (!crash_protection.call(cb)) { + log_error(jfr)("Thread method sampler crashed for native"); + } + } else { + cb.call(); + } + if (!cb.success()) { + return false; + } + EventNativeMethodSample *event = &_events_native[_added_native - 1]; + traceid id = JfrStackTraceRepository::add(cb.stacktrace()); + assert(id != 0, "Stacktrace id should not be 0"); + event->set_stackTrace(id); + return true; +} + +void JfrThreadSampleClosure::commit_events() { + for (int i = 0; i < _added_java; ++i) { + _events[i].commit(); + } + for (int i = 0; i < _added_native; ++i) { + _events_native[i].commit(); + } +} + +JfrThreadSampleClosure::JfrThreadSampleClosure(EventExecutionSample* events, EventNativeMethodSample* events_native) : + _events(events), + _events_native(events_native), + _self(Thread::current()), + _added_java(0), + _added_native(0) { +} + +class JfrThreadSampler : public Thread { + friend class JfrThreadSampling; + private: + Semaphore _sample; + Thread* _sampler_thread; + JfrStackFrame* const _frames; + JavaThread* _last_thread_java; + JavaThread* _last_thread_native; + size_t _interval_java; + size_t _interval_native; + int _cur_index; + const u4 _max_frames; + volatile bool _disenrolled; + static Monitor* _transition_block_lock; + + JavaThread* next_thread(ThreadsList* t_list, JavaThread* first_sampled, JavaThread* current); + void task_stacktrace(JfrSampleType type, JavaThread** last_thread); + JfrThreadSampler(size_t interval_java, size_t interval_native, u4 max_frames); + ~JfrThreadSampler(); + + void start_thread(); + + void enroll(); + void disenroll(); + void set_java_interval(size_t interval) { _interval_java = interval; }; + void set_native_interval(size_t interval) { _interval_native = interval; }; + size_t get_java_interval() { return _interval_java; }; + size_t get_native_interval() { return _interval_native; }; + + public: + void run(); + static Monitor* transition_block() { return _transition_block_lock; } + static void on_javathread_suspend(JavaThread* thread); +}; + +Monitor* JfrThreadSampler::_transition_block_lock = new Monitor(Mutex::leaf, "Trace block", true, Monitor::_safepoint_check_never); + +static void clear_transition_block(JavaThread* jt) { + jt->clear_trace_flag(); + JfrThreadLocal* const tl = jt->jfr_thread_local(); + if (tl->is_trace_block()) { + MutexLockerEx ml(JfrThreadSampler::transition_block(), Mutex::_no_safepoint_check_flag); + JfrThreadSampler::transition_block()->notify_all(); + } +} + +JfrSampleType JfrThreadSampleClosure::do_sample_thread(JavaThread* thread, JfrStackFrame* frames, u4 max_frames, bool java_sample, bool native_sample) { + assert(Threads_lock->owned_by_self(), "Holding the thread table lock."); + if (thread->is_hidden_from_external_view()) { + return NO_SAMPLE; + } + if (thread->in_deopt_handler()) { + return NO_SAMPLE; + } + JfrSampleType ret = NO_SAMPLE; + thread->set_trace_flag(); + if (!UseMembar) { + os::serialize_thread_states(); + } + if (in_java_sample(thread) && java_sample) { + ret = sample_thread_in_java(thread, frames, max_frames) ? JAVA_SAMPLE : NO_SAMPLE; + } else if (in_native_sample(thread) && native_sample) { + ret = sample_thread_in_native(thread, frames, max_frames) ? NATIVE_SAMPLE : NO_SAMPLE; + } + clear_transition_block(thread); + return ret; +} + +JfrThreadSampler::JfrThreadSampler(size_t interval_java, size_t interval_native, u4 max_frames) : + _sample(), + _sampler_thread(NULL), + _frames(JfrCHeapObj::new_array(max_frames)), + _last_thread_java(NULL), + _last_thread_native(NULL), + _interval_java(interval_java), + _interval_native(interval_native), + _cur_index(-1), + _max_frames(max_frames), + _disenrolled(true) { +} + +JfrThreadSampler::~JfrThreadSampler() { + JfrCHeapObj::free(_frames, sizeof(JfrStackFrame) * _max_frames); +} + +void JfrThreadSampler::on_javathread_suspend(JavaThread* thread) { + JfrThreadLocal* const tl = thread->jfr_thread_local(); + tl->set_trace_block(); + { + MutexLockerEx ml(transition_block(), Mutex::_no_safepoint_check_flag); + while (thread->is_trace_suspend()) { + transition_block()->wait(true); + } + tl->clear_trace_block(); + } +} + +JavaThread* JfrThreadSampler::next_thread(ThreadsList* t_list, JavaThread* first_sampled, JavaThread* current) { + assert(Threads_lock->owned_by_self(), "Holding the thread table lock."); + if (current == NULL) { + _cur_index = 0; + return t_list->thread_at(_cur_index); + } + + if (_cur_index == -1 || t_list->thread_at(_cur_index) != current) { + // 'current' is not at '_cur_index' so find it: + _cur_index = t_list->find_index_of_JavaThread(current); + assert(_cur_index != -1, "current JavaThread should be findable."); + } + _cur_index++; + + JavaThread* next = NULL; + // wrap + if ((uint)_cur_index >= t_list->length()) { + _cur_index = 0; + } + next = t_list->thread_at(_cur_index); + + // sample wrap + if (next == first_sampled) { + return NULL; + } + return next; +} + +void JfrThreadSampler::start_thread() { + if (os::create_thread(this, os::os_thread)) { + os::start_thread(this); + } else { + log_error(jfr)("Failed to create thread for thread sampling"); + } +} + +void JfrThreadSampler::enroll() { + if (_disenrolled) { + log_info(jfr)("Enrolling thread sampler"); + _sample.signal(); + _disenrolled = false; + } +} + +void JfrThreadSampler::disenroll() { + if (!_disenrolled) { + _sample.wait(); + _disenrolled = true; + log_info(jfr)("Disenrolling thread sampler"); + } +} + +static jlong get_monotonic_ms() { + return os::javaTimeNanos() / 1000000; +} + +void JfrThreadSampler::run() { + assert(_sampler_thread == NULL, "invariant"); + _sampler_thread = this; + + jlong last_java_ms = get_monotonic_ms(); + jlong last_native_ms = last_java_ms; + while (true) { + if (!_sample.trywait()) { + // disenrolled + _sample.wait(); + last_java_ms = get_monotonic_ms(); + last_native_ms = last_java_ms; + } + _sample.signal(); + jlong java_interval = _interval_java == 0 ? max_jlong : MAX2(_interval_java, 10); + jlong native_interval = _interval_native == 0 ? max_jlong : MAX2(_interval_native, 10); + + jlong now_ms = get_monotonic_ms(); + + jlong next_j = java_interval + last_java_ms - now_ms; + jlong next_n = native_interval + last_native_ms - now_ms; + + jlong sleep_to_next = MIN2(next_j, next_n); + + if (sleep_to_next > 0) { + os::naked_short_sleep(sleep_to_next); + } + + if ((next_j - sleep_to_next) <= 0) { + task_stacktrace(JAVA_SAMPLE, &_last_thread_java); + last_java_ms = get_monotonic_ms(); + } + if ((next_n - sleep_to_next) <= 0) { + task_stacktrace(NATIVE_SAMPLE, &_last_thread_native); + last_native_ms = get_monotonic_ms(); + } + } + delete this; +} + +static const int MAX_NR_OF_SAMPLES = 5; + +void JfrThreadSampler::task_stacktrace(JfrSampleType type, JavaThread** last_thread) { + ResourceMark rm; + EventExecutionSample samples[MAX_NR_OF_SAMPLES]; + EventNativeMethodSample samples_native[MAX_NR_OF_SAMPLES]; + JfrThreadSampleClosure sample_task(samples, samples_native); + + int num_samples = 0; + { + elapsedTimer sample_time; + sample_time.start(); + + { + MonitorLockerEx tlock(Threads_lock, Mutex::_allow_vm_block_flag); + ThreadsListHandle tlh; + JavaThread* current = tlh.includes(*last_thread) ? *last_thread : NULL; + JavaThread* start = NULL; + + while (num_samples < MAX_NR_OF_SAMPLES) { + current = next_thread(tlh.list(), start, current); + if (current == NULL) { + break; + } + if (start == NULL) { + start = current; // remember thread where we started sampling + } + if (current->is_Compiler_thread()) { + continue; + } + *last_thread = current; // remember thread we last sampled + JfrSampleType ret = sample_task.do_sample_thread(current, _frames, _max_frames, type == JAVA_SAMPLE, type == NATIVE_SAMPLE); + switch (type) { + case JAVA_SAMPLE: + case NATIVE_SAMPLE: + ++num_samples; + break; + default: + break; + } + } + } + sample_time.stop(); + log_trace(jfr)("JFR thread sampling done in %3.7f secs with %d java %d native samples", + sample_time.seconds(), sample_task.java_entries(), sample_task.native_entries()); + } + if (num_samples > 0) { + sample_task.commit_events(); + } +} + +static JfrThreadSampling* _instance = NULL; + +JfrThreadSampling& JfrThreadSampling::instance() { + return *_instance; +} + +JfrThreadSampling* JfrThreadSampling::create() { + assert(_instance == NULL, "invariant"); + _instance = new JfrThreadSampling(); + return _instance; +} + +void JfrThreadSampling::destroy() { + if (_instance != NULL) { + delete _instance; + _instance = NULL; + } +} + +JfrThreadSampling::JfrThreadSampling() : _sampler(NULL) {} + +JfrThreadSampling::~JfrThreadSampling() { + if (_sampler != NULL) { + _sampler->disenroll(); + } +} + +static void log(size_t interval_java, size_t interval_native) { + log_info(jfr)("Updated thread sampler for java: " SIZE_FORMAT" ms, native " SIZE_FORMAT " ms", interval_java, interval_native); +} + +void JfrThreadSampling::start_sampler(size_t interval_java, size_t interval_native) { + assert(_sampler == NULL, "invariant"); + log_info(jfr)("Enrolling thread sampler"); + _sampler = new JfrThreadSampler(interval_java, interval_native, JfrOptionSet::stackdepth()); + _sampler->start_thread(); + _sampler->enroll(); +} + +void JfrThreadSampling::set_sampling_interval(bool java_interval, size_t period) { + size_t interval_java = 0; + size_t interval_native = 0; + if (_sampler != NULL) { + interval_java = _sampler->get_java_interval(); + interval_native = _sampler->get_native_interval(); + } + + if (java_interval) { + interval_java = period; + } else { + interval_native = period; + } + + if (interval_java > 0 || interval_native > 0) { + if (_sampler == NULL) { + log_info(jfr)("Creating thread sampler for java:%zu ms, native %zu ms", interval_java, interval_native); + start_sampler(interval_java, interval_native); + } else { + _sampler->set_java_interval(interval_java); + _sampler->set_native_interval(interval_native); + _sampler->enroll(); + } + assert(_sampler != NULL, "invariant"); + log(interval_java, interval_native); + } else if (_sampler != NULL) { + _sampler->disenroll(); + } +} + +void JfrThreadSampling::set_java_sample_interval(size_t period) { + if (_instance == NULL && 0 == period) { + return; + } + instance().set_sampling_interval(true, period); +} + +void JfrThreadSampling::set_native_sample_interval(size_t period) { + if (_instance == NULL && 0 == period) { + return; + } + instance().set_sampling_interval(false, period); +} + +void JfrThreadSampling::on_javathread_suspend(JavaThread* thread) { + JfrThreadSampler::on_javathread_suspend(thread); +} + +Thread* JfrThreadSampling::sampler_thread() { + if (_instance == NULL) { + return NULL; + } + return _instance->_sampler != NULL ? _instance->_sampler->_sampler_thread : NULL; +} diff --git a/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampler.hpp b/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampler.hpp new file mode 100644 index 00000000000..092c40971e0 --- /dev/null +++ b/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampler.hpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_JFR_PERIODIC_SAMPLING_JFRTHREADSAMPLER_HPP +#define SHARE_VM_JFR_PERIODIC_SAMPLING_JFRTHREADSAMPLER_HPP + +#include "jfr/utilities/jfrAllocation.hpp" + +class Monitor; +class JavaThread; +class JfrStackFrame; +class JfrThreadSampler; +class Thread; + +class JfrThreadSampling : public JfrCHeapObj { + friend class JfrRecorder; + private: + JfrThreadSampler* _sampler; + void start_sampler(size_t interval_java, size_t interval_native); + void set_sampling_interval(bool java_interval, size_t period); + + JfrThreadSampling(); + ~JfrThreadSampling(); + + static JfrThreadSampling& instance(); + static JfrThreadSampling* create(); + static void destroy(); + + public: + static void set_java_sample_interval(size_t period); + static void set_native_sample_interval(size_t period); + static void on_javathread_suspend(JavaThread* thread); + static Thread* sampler_thread(); +}; + +#endif // SHARE_VM_JFR_PERIODIC_SAMPLING_JFRTHREADSAMPLER_HPP diff --git a/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointBlob.cpp b/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointBlob.cpp new file mode 100644 index 00000000000..aaf3352f3e3 --- /dev/null +++ b/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointBlob.cpp @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "jfr/recorder/checkpoint/jfrCheckpointBlob.hpp" +#include "jfr/recorder/checkpoint/jfrCheckpointWriter.hpp" + +JfrCheckpointBlob::JfrCheckpointBlob(const u1* checkpoint, size_t size) : + _checkpoint(JfrCHeapObj::new_array(size)), + _size(size), + _next(), + _written(false) { + assert(checkpoint != NULL, "invariant"); + assert(_checkpoint != NULL, "invariant"); + memcpy(const_cast(_checkpoint), checkpoint, size); +} + +JfrCheckpointBlob::~JfrCheckpointBlob() { + JfrCHeapObj::free(const_cast(_checkpoint), _size); +} + +const JfrCheckpointBlobHandle& JfrCheckpointBlob::next() const { + return _next; +} + +void JfrCheckpointBlob::write_this(JfrCheckpointWriter& writer) const { + writer.bytes(_checkpoint, _size); +} + +void JfrCheckpointBlob::exclusive_write(JfrCheckpointWriter& writer) const { + if (!_written) { + write_this(writer); + _written = true; + } + if (_next.valid()) { + _next->exclusive_write(writer); + } +} + +void JfrCheckpointBlob::write(JfrCheckpointWriter& writer) const { + write_this(writer); + if (_next.valid()) { + _next->write(writer); + } +} + +void JfrCheckpointBlob::reset_write_state() const { + if (_written) { + _written = false; + } + if (_next.valid()) { + _next->reset_write_state(); + } +} + +void JfrCheckpointBlob::set_next(const JfrCheckpointBlobHandle& ref) { + if (_next == ref) { + return; + } + assert(_next != ref, "invariant"); + if (_next.valid()) { + _next->set_next(ref); + return; + } + _next = ref; +} + +JfrCheckpointBlobHandle JfrCheckpointBlob::make(const u1* checkpoint, size_t size) { + const JfrCheckpointBlob* cp_blob = new JfrCheckpointBlob(checkpoint, size); + assert(cp_blob != NULL, "invariant"); + return JfrCheckpointBlobReference::make(cp_blob); +} diff --git a/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointBlob.hpp b/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointBlob.hpp new file mode 100644 index 00000000000..50617ecb684 --- /dev/null +++ b/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointBlob.hpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_JFR_RECORDER_CHECKPOINT_JFRCHECKPOINTBLOB_HPP +#define SHARE_VM_JFR_RECORDER_CHECKPOINT_JFRCHECKPOINTBLOB_HPP + +#include "jfr/utilities/jfrAllocation.hpp" +#include "jfr/utilities/jfrRefCountPointer.hpp" + +class JfrCheckpointBlob; +class JfrCheckpointWriter; + +typedef RefCountPointer JfrCheckpointBlobReference; +typedef RefCountHandle JfrCheckpointBlobHandle; + +class JfrCheckpointBlob : public JfrCHeapObj { + template + friend class RefCountPointer; + private: + const u1* _checkpoint; + const size_t _size; + JfrCheckpointBlobHandle _next; + mutable bool _written; + + JfrCheckpointBlob(const u1* checkpoint, size_t size); + ~JfrCheckpointBlob(); + const JfrCheckpointBlobHandle& next() const; + void write_this(JfrCheckpointWriter& writer) const; + + public: + void write(JfrCheckpointWriter& writer) const; + void exclusive_write(JfrCheckpointWriter& writer) const; + void reset_write_state() const; + void set_next(const JfrCheckpointBlobHandle& ref); + static JfrCheckpointBlobHandle make(const u1* checkpoint, size_t size); +}; + +#endif // SHARE_VM_JFR_RECORDER_CHECKPOINT_JFRCHECKPOINTBLOB_HPP diff --git a/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointManager.cpp b/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointManager.cpp new file mode 100644 index 00000000000..4a1ede97bda --- /dev/null +++ b/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointManager.cpp @@ -0,0 +1,389 @@ +/* + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "classfile/javaClasses.inline.hpp" +#include "jfr/recorder/jfrRecorder.hpp" +#include "jfr/recorder/checkpoint/jfrCheckpointManager.hpp" +#include "jfr/recorder/checkpoint/jfrCheckpointWriter.hpp" +#include "jfr/recorder/checkpoint/types/jfrTypeManager.hpp" +#include "jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.hpp" +#include "jfr/recorder/service/jfrOptionSet.hpp" +#include "jfr/recorder/storage/jfrMemorySpace.inline.hpp" +#include "jfr/recorder/storage/jfrStorageUtils.inline.hpp" +#include "jfr/recorder/repository/jfrChunkWriter.hpp" +#include "jfr/utilities/jfrBigEndian.hpp" +#include "jfr/utilities/jfrTypes.hpp" +#include "logging/log.hpp" +#include "memory/resourceArea.hpp" +#include "runtime/mutexLocker.hpp" +#include "runtime/orderAccess.inline.hpp" +#include "runtime/safepoint.hpp" + +typedef JfrCheckpointManager::Buffer* BufferPtr; + +static JfrCheckpointManager* _instance = NULL; + +JfrCheckpointManager& JfrCheckpointManager::instance() { + return *_instance; +} + +JfrCheckpointManager* JfrCheckpointManager::create(JfrChunkWriter& cw) { + assert(_instance == NULL, "invariant"); + _instance = new JfrCheckpointManager(cw); + return _instance; +} + +void JfrCheckpointManager::destroy() { + assert(_instance != NULL, "invariant"); + delete _instance; + _instance = NULL; +} + +JfrCheckpointManager::JfrCheckpointManager(JfrChunkWriter& cw) : + _free_list_mspace(NULL), + _epoch_transition_mspace(NULL), + _lock(NULL), + _type_manager(NULL), + _service_thread(NULL), + _chunkwriter(cw), + _checkpoint_epoch_state(JfrTraceIdEpoch::epoch()) {} + +JfrCheckpointManager::~JfrCheckpointManager() { + if (_free_list_mspace != NULL) { + delete _free_list_mspace; + } + if (_epoch_transition_mspace != NULL) { + delete _epoch_transition_mspace; + } + if (_lock != NULL) { + delete _lock; + } + if (_type_manager) { + delete _type_manager; + } +} + +static const size_t unlimited_mspace_size = 0; +static const size_t checkpoint_buffer_cache_count = 2; +static const size_t checkpoint_buffer_size = 512 * K; + +static JfrCheckpointMspace* create_mspace(size_t buffer_size, size_t limit, size_t cache_count, JfrCheckpointManager* system) { + JfrCheckpointMspace* mspace = new JfrCheckpointMspace(buffer_size, limit, cache_count, system); + if (mspace != NULL) { + mspace->initialize(); + } + return mspace; +} + +bool JfrCheckpointManager::initialize() { + assert(_free_list_mspace == NULL, "invariant"); + _free_list_mspace = create_mspace(checkpoint_buffer_size, unlimited_mspace_size, checkpoint_buffer_cache_count, this); + if (_free_list_mspace == NULL) { + return false; + } + assert(_epoch_transition_mspace == NULL, "invariant"); + _epoch_transition_mspace = create_mspace(checkpoint_buffer_size, unlimited_mspace_size, checkpoint_buffer_cache_count, this); + if (_epoch_transition_mspace == NULL) { + return false; + } + assert(_type_manager == NULL, "invariant"); + _type_manager = new JfrTypeManager(); + if (_type_manager == NULL || !_type_manager->initialize()) { + return false; + } + assert(_lock == NULL, "invariant"); + _lock = new Mutex(Monitor::leaf - 1, "Checkpoint mutex", Mutex::_allow_vm_block_flag, Monitor::_safepoint_check_never); + return _lock != NULL; +} + +bool JfrCheckpointManager::use_epoch_transition_mspace(const Thread* thread) const { + return _service_thread != thread && OrderAccess::load_acquire(&_checkpoint_epoch_state) != JfrTraceIdEpoch::epoch(); +} + +void JfrCheckpointManager::synchronize_epoch() { + assert(_checkpoint_epoch_state != JfrTraceIdEpoch::epoch(), "invariant"); + OrderAccess::storestore(); + _checkpoint_epoch_state = JfrTraceIdEpoch::epoch(); +} + +void JfrCheckpointManager::shift_epoch() { + debug_only(const u1 current_epoch = JfrTraceIdEpoch::current();) + JfrTraceIdEpoch::shift_epoch(); + assert(current_epoch != JfrTraceIdEpoch::current(), "invariant"); +} + +void JfrCheckpointManager::register_service_thread(const Thread* thread) { + _service_thread = thread; +} + +void JfrCheckpointManager::register_full(BufferPtr t, Thread* thread) { + // nothing here at the moment + assert(t->retired(), "invariant"); +} + +void JfrCheckpointManager::lock() { + assert(!_lock->owned_by_self(), "invariant"); + _lock->lock_without_safepoint_check(); +} + +void JfrCheckpointManager::unlock() { + _lock->unlock(); +} + +#ifdef ASSERT + +bool JfrCheckpointManager::is_locked() const { + return _lock->owned_by_self(); +} + +static void assert_free_lease(const BufferPtr buffer) { + assert(buffer != NULL, "invariant"); + assert(buffer->acquired_by_self(), "invariant"); + assert(buffer->lease(), "invariant"); +} + +static void assert_release(const BufferPtr buffer) { + assert(buffer != NULL, "invariant"); + assert(buffer->lease(), "invariant"); + assert(buffer->acquired_by_self(), "invariant"); +} + +#endif // ASSERT + +static BufferPtr lease_free(size_t size, JfrCheckpointMspace* mspace, size_t retry_count, Thread* thread) { + static const size_t max_elem_size = mspace->min_elem_size(); // min is max + BufferPtr buffer; + if (size <= max_elem_size) { + BufferPtr buffer = mspace_get_free_lease_with_retry(size, mspace, retry_count, thread); + if (buffer != NULL) { + DEBUG_ONLY(assert_free_lease(buffer);) + return buffer; + } + } + buffer = mspace_allocate_transient_lease_to_free(size, mspace, thread); + DEBUG_ONLY(assert_free_lease(buffer);) + return buffer; +} + +static const size_t lease_retry = 10; + +BufferPtr JfrCheckpointManager::lease_buffer(Thread* thread, size_t size /* 0 */) { + JfrCheckpointManager& manager = instance(); + if (manager.use_epoch_transition_mspace(thread)) { + return lease_free(size, manager._epoch_transition_mspace, lease_retry, thread); + } + return lease_free(size, manager._free_list_mspace, lease_retry, thread); +} + +/* +* If the buffer was a "lease" from the free list, release back. +* +* The buffer is effectively invalidated for the thread post-return, +* and the caller should take means to ensure that it is not referenced. +*/ +static void release(BufferPtr const buffer, Thread* thread) { + DEBUG_ONLY(assert_release(buffer);) + buffer->clear_lease(); + buffer->release(); +} + +BufferPtr JfrCheckpointManager::flush(BufferPtr old, size_t used, size_t requested, Thread* thread) { + assert(old != NULL, "invariant"); + assert(old->lease(), "invariant"); + if (0 == requested) { + // indicates a lease is being returned + release(old, thread); + return NULL; + } + // migration of in-flight information + BufferPtr const new_buffer = lease_buffer(thread, used + requested); + if (new_buffer != NULL) { + migrate_outstanding_writes(old, new_buffer, used, requested); + } + release(old, thread); + return new_buffer; // might be NULL +} + +// offsets into the JfrCheckpointEntry +static const juint starttime_offset = sizeof(jlong); +static const juint duration_offset = starttime_offset + sizeof(jlong); +static const juint flushpoint_offset = duration_offset + sizeof(jlong); +static const juint types_offset = flushpoint_offset + sizeof(juint); +static const juint payload_offset = types_offset + sizeof(juint); + +template +static Return read_data(const u1* data) { + return JfrBigEndian::read(data); +} + +static jlong total_size(const u1* data) { + return read_data(data); +} + +static jlong starttime(const u1* data) { + return read_data(data + starttime_offset); +} + +static jlong duration(const u1* data) { + return read_data(data + duration_offset); +} + +static bool is_flushpoint(const u1* data) { + return read_data(data + flushpoint_offset) == (juint)1; +} + +static juint number_of_types(const u1* data) { + return read_data(data + types_offset); +} + +static void write_checkpoint_header(JfrChunkWriter& cw, intptr_t offset_prev_cp_event, const u1* data) { + cw.reserve(sizeof(u4)); + cw.write((u8)EVENT_CHECKPOINT); + cw.write(starttime(data)); + cw.write(duration(data)); + cw.write((jlong)offset_prev_cp_event); + cw.write(is_flushpoint(data)); + cw.write(number_of_types(data)); +} + +static void write_checkpoint_content(JfrChunkWriter& cw, const u1* data, size_t size) { + assert(data != NULL, "invariant"); + cw.write_unbuffered(data + payload_offset, size); +} + +static size_t write_checkpoint_event(JfrChunkWriter& cw, const u1* data) { + assert(data != NULL, "invariant"); + const intptr_t previous_checkpoint_event = cw.previous_checkpoint_offset(); + const intptr_t event_begin = cw.current_offset(); + const intptr_t offset_to_previous_checkpoint_event = 0 == previous_checkpoint_event ? 0 : previous_checkpoint_event - event_begin; + const jlong total_checkpoint_size = total_size(data); + write_checkpoint_header(cw, offset_to_previous_checkpoint_event, data); + write_checkpoint_content(cw, data, total_checkpoint_size - sizeof(JfrCheckpointEntry)); + const jlong checkpoint_event_size = cw.current_offset() - event_begin; + cw.write_padded_at_offset(checkpoint_event_size, event_begin); + cw.set_previous_checkpoint_offset(event_begin); + return (size_t)total_checkpoint_size; +} + +static size_t write_checkpoints(JfrChunkWriter& cw, const u1* data, size_t size) { + assert(cw.is_valid(), "invariant"); + assert(data != NULL, "invariant"); + assert(size > 0, "invariant"); + const u1* const limit = data + size; + const u1* next_entry = data; + size_t processed = 0; + while (next_entry < limit) { + const size_t checkpoint_size = write_checkpoint_event(cw, next_entry); + processed += checkpoint_size; + next_entry += checkpoint_size; + } + assert(next_entry == limit, "invariant"); + return processed; +} + +template +class CheckpointWriteOp { + private: + JfrChunkWriter& _writer; + size_t _processed; + public: + typedef T Type; + CheckpointWriteOp(JfrChunkWriter& writer) : _writer(writer), _processed(0) {} + bool write(Type* t, const u1* data, size_t size) { + _processed += write_checkpoints(_writer, data, size); + return true; + } + size_t processed() const { return _processed; } +}; + +typedef CheckpointWriteOp WriteOperation; +typedef MutexedWriteOp MutexedWriteOperation; +typedef ReleaseOp CheckpointReleaseOperation; +typedef CompositeOperation CheckpointWriteOperation; + +static size_t write_mspace_exclusive(JfrCheckpointMspace* mspace, JfrChunkWriter& chunkwriter) { + Thread* const thread = Thread::current(); + WriteOperation wo(chunkwriter); + MutexedWriteOperation mwo(wo); + CheckpointReleaseOperation cro(mspace, thread, false); + CheckpointWriteOperation cpwo(&mwo, &cro); + assert(mspace->is_full_empty(), "invariant"); + process_free_list(cpwo, mspace); + return wo.processed(); +} + +size_t JfrCheckpointManager::write() { + const size_t processed = write_mspace_exclusive(_free_list_mspace, _chunkwriter); + synchronize_epoch(); + return processed; +} + +size_t JfrCheckpointManager::write_epoch_transition_mspace() { + return write_mspace_exclusive(_epoch_transition_mspace, _chunkwriter); +} + +typedef DiscardOp > DiscardOperation; +size_t JfrCheckpointManager::clear() { + DiscardOperation discarder(mutexed); // mutexed discard mode + process_free_list(discarder, _free_list_mspace); + process_free_list(discarder, _epoch_transition_mspace); + synchronize_epoch(); + return discarder.processed(); +} + +bool JfrCheckpointManager::register_serializer(JfrTypeId id, bool require_safepoint, bool permit_cache, JfrSerializer* cs) { + assert(cs != NULL, "invariant"); + return instance()._type_manager->register_serializer(id, require_safepoint, permit_cache, cs); +} + +size_t JfrCheckpointManager::write_types() { + JfrCheckpointWriter writer(false, true, Thread::current()); + _type_manager->write_types(writer); + return writer.used_size(); +} + +size_t JfrCheckpointManager::write_safepoint_types() { + // this is also a "flushpoint" + JfrCheckpointWriter writer(true, true, Thread::current()); + _type_manager->write_safepoint_types(writer); + return writer.used_size(); +} + +void JfrCheckpointManager::write_type_set() { + _type_manager->write_type_set(); +} + +void JfrCheckpointManager::write_type_set_for_unloaded_classes() { + assert(SafepointSynchronize::is_at_safepoint(), "must be at safepoint!"); + instance()._type_manager->write_type_set_for_unloaded_classes(); +} + +void JfrCheckpointManager::create_thread_checkpoint(JavaThread* jt) { + instance()._type_manager->create_thread_checkpoint(jt); +} + +void JfrCheckpointManager::write_thread_checkpoint(JavaThread* jt) { + instance()._type_manager->write_thread_checkpoint(jt); +} diff --git a/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointManager.hpp b/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointManager.hpp new file mode 100644 index 00000000000..2bde91eec53 --- /dev/null +++ b/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointManager.hpp @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_JFR_RECORDER_CHECKPOINT_JFRCHECKPOINTMANAGER_HPP +#define SHARE_VM_JFR_RECORDER_CHECKPOINT_JFRCHECKPOINTMANAGER_HPP + +#include "jfr/recorder/storage/jfrBuffer.hpp" +#include "jfr/recorder/storage/jfrMemorySpace.hpp" +#include "jfr/recorder/storage/jfrMemorySpaceRetrieval.hpp" +#include "jfr/utilities/jfrTypes.hpp" + +class JfrCheckpointManager; +class JfrChunkWriter; +class JfrSerializer; +class JfrTypeManager; +class Mutex; +class Thread; + +struct JfrCheckpointEntry { + jlong size; + jlong start_time; + jlong duration; + juint flushpoint; + juint nof_segments; +}; + +typedef JfrMemorySpace JfrCheckpointMspace; + +// +// Responsible for maintaining checkpoints and by implication types. +// A checkpoint is an event that has a payload consisting of constant types. +// A constant type is a binary relation, a set of key-value pairs. +// +class JfrCheckpointManager : public JfrCHeapObj { + public: + typedef JfrCheckpointMspace::Type Buffer; + private: + JfrCheckpointMspace* _free_list_mspace; + JfrCheckpointMspace* _epoch_transition_mspace; + Mutex* _lock; + JfrTypeManager* _type_manager; + const Thread* _service_thread; + JfrChunkWriter& _chunkwriter; + bool _checkpoint_epoch_state; + + // mspace callback + void register_full(Buffer* t, Thread* thread); + void lock(); + void unlock(); + DEBUG_ONLY(bool is_locked() const;) + + static Buffer* lease_buffer(Thread* t, size_t size = 0); + static Buffer* flush(Buffer* old, size_t used, size_t requested, Thread* t); + + size_t clear(); + size_t write(); + size_t write_epoch_transition_mspace(); + size_t write_types(); + size_t write_safepoint_types(); + void write_type_set(); + void shift_epoch(); + void synchronize_epoch(); + bool use_epoch_transition_mspace(const Thread* t) const; + + JfrCheckpointManager(JfrChunkWriter& cw); + ~JfrCheckpointManager(); + + static JfrCheckpointManager& instance(); + static JfrCheckpointManager* create(JfrChunkWriter& cw); + bool initialize(); + static void destroy(); + + static bool register_serializer(JfrTypeId id, bool require_safepoint, bool permit_cache, JfrSerializer* serializer); + + public: + void register_service_thread(const Thread* t); + static void write_type_set_for_unloaded_classes(); + static void create_thread_checkpoint(JavaThread* jt); + static void write_thread_checkpoint(JavaThread* jt); + + friend class JfrRecorder; + friend class JfrRecorderService; + friend class JfrCheckpointFlush; + friend class JfrCheckpointWriter; + friend class JfrSerializer; + friend class JfrStackTraceRepository; + template class, typename> + friend class JfrMemorySpace; +}; + +#endif //SHARE_VM_JFR_RECORDER_CHECKPOINT_JFRCHECKPOINTMANAGER_HPP diff --git a/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointWriter.cpp b/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointWriter.cpp new file mode 100644 index 00000000000..1fc884780b2 --- /dev/null +++ b/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointWriter.cpp @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "jfr/recorder/checkpoint/jfrCheckpointManager.hpp" +#include "jfr/recorder/checkpoint/jfrCheckpointWriter.hpp" +#include "jfr/writers/jfrBigEndianWriter.hpp" + +JfrCheckpointFlush::JfrCheckpointFlush(Type* old, size_t used, size_t requested, Thread* t) : + _result(JfrCheckpointManager::flush(old, used, requested, t)) {} + +JfrCheckpointWriter::JfrCheckpointWriter(bool flushpoint, bool header, Thread* thread) : + JfrCheckpointWriterBase(JfrCheckpointManager::lease_buffer(thread), thread), + _time(JfrTicks::now()), + _offset(0), + _count(0), + _flushpoint(flushpoint), + _header(header) { + assert(this->is_acquired(), "invariant"); + assert(0 == this->current_offset(), "invariant"); + if (_header) { + reserve(sizeof(JfrCheckpointEntry)); + } +} + +static void write_checkpoint_header(u1* pos, jlong size, jlong time, bool flushpoint, juint type_count) { + assert(pos != NULL, "invariant"); + JfrBigEndianWriter be_writer(pos, sizeof(JfrCheckpointEntry)); + be_writer.write(size); + be_writer.write(time); + be_writer.write(JfrTicks::now().value() - time); + be_writer.write(flushpoint ? (juint)1 : (juint)0); + be_writer.write(type_count); + assert(be_writer.is_valid(), "invariant"); +} + +JfrCheckpointWriter::~JfrCheckpointWriter() { + assert(this->is_acquired(), "invariant"); + if (!this->is_valid() || !_header) { + release(); + return; + } + if (0 == count()) { + assert(this->used_size() == sizeof(JfrCheckpointEntry), "invariant"); + this->seek(_offset); + release(); + return; + } + assert(_header, "invariant"); + assert(this->is_valid(), "invariant"); + assert(count() > 0, "invariant"); + assert(this->used_size() > sizeof(JfrCheckpointEntry), "invariant"); + const jlong size = this->current_offset(); + assert(size + this->start_pos() == this->current_pos(), "invariant"); + write_checkpoint_header(const_cast(this->start_pos()), size, _time, is_flushpoint(), count()); + release(); +} + +void JfrCheckpointWriter::set_flushpoint(bool flushpoint) { + _flushpoint = flushpoint; +} + +bool JfrCheckpointWriter::is_flushpoint() const { + return _flushpoint; +} + +juint JfrCheckpointWriter::count() const { + return _count; +} + +void JfrCheckpointWriter::set_count(juint count) { + _count = count; +} + +void JfrCheckpointWriter::release() { + assert(this->is_acquired(), "invariant"); + if (!this->is_valid() || this->used_size() == 0) { + return; + } + assert(this->used_size() > 0, "invariant"); + // write through to backing storage + this->commit(); + assert(0 == this->current_offset(), "invariant"); +} + +void JfrCheckpointWriter::write_type(JfrTypeId type_id) { + assert(type_id < TYPES_END, "invariant"); + write(type_id); + increment(); +} + +void JfrCheckpointWriter::write_key(u8 key) { + write(key); +} + +void JfrCheckpointWriter::increment() { + ++_count; +} + +void JfrCheckpointWriter::write_count(u4 nof_entries) { + write((u4)nof_entries); +} + +void JfrCheckpointWriter::write_count(u4 nof_entries, jlong offset) { + write_padded_at_offset(nof_entries, offset); +} + +const u1* JfrCheckpointWriter::session_data(size_t* size, const JfrCheckpointContext* ctx /* 0 */) { + assert(this->is_acquired(), "wrong state!"); + if (!this->is_valid()) { + *size = 0; + return NULL; + } + if (ctx != NULL) { + const u1* session_start_pos = this->start_pos() + ctx->offset; + *size = this->current_pos() - session_start_pos; + return session_start_pos; + } + *size = this->used_size(); + assert(this->start_pos() + *size == this->current_pos(), "invariant"); + write_checkpoint_header(const_cast(this->start_pos()), this->used_offset(), _time, is_flushpoint(), count()); + this->seek(_offset + (_header ? sizeof(JfrCheckpointEntry) : 0)); + set_count(0); + return this->start_pos(); +} + +const JfrCheckpointContext JfrCheckpointWriter::context() const { + JfrCheckpointContext ctx; + ctx.offset = this->current_offset(); + ctx.count = this->count(); + return ctx; +} + +void JfrCheckpointWriter::set_context(const JfrCheckpointContext ctx) { + this->seek(ctx.offset); + set_count(ctx.count); +} +bool JfrCheckpointWriter::has_data() const { + return this->used_size() > sizeof(JfrCheckpointEntry); +} + +JfrCheckpointBlobHandle JfrCheckpointWriter::checkpoint_blob() { + size_t size = 0; + const u1* data = session_data(&size); + return JfrCheckpointBlob::make(data, size); +} + +JfrCheckpointBlobHandle JfrCheckpointWriter::copy(const JfrCheckpointContext* ctx /* 0 */) { + if (ctx == NULL) { + return checkpoint_blob(); + } + size_t size = 0; + const u1* data = session_data(&size, ctx); + return JfrCheckpointBlob::make(data, size); +} + +JfrCheckpointBlobHandle JfrCheckpointWriter::move(const JfrCheckpointContext* ctx /* 0 */) { + JfrCheckpointBlobHandle data = copy(ctx); + if (ctx != NULL) { + const_cast(ctx)->count = 0; + set_context(*ctx); + } + return data; +} diff --git a/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointWriter.hpp b/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointWriter.hpp new file mode 100644 index 00000000000..bb01cb56e4d --- /dev/null +++ b/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointWriter.hpp @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_JFR_RECORDER_CHECKPOINT_JFRCHECKPOINTWRITER_HPP +#define SHARE_VM_JFR_RECORDER_CHECKPOINT_JFRCHECKPOINTWRITER_HPP + +#include "jfr/recorder/checkpoint/jfrCheckpointBlob.hpp" +#include "jfr/recorder/storage/jfrBuffer.hpp" +#include "jfr/utilities/jfrTime.hpp" +#include "jfr/utilities/jfrTypes.hpp" +#include "jfr/writers/jfrEventWriterHost.inline.hpp" +#include "jfr/writers/jfrMemoryWriterHost.inline.hpp" +#include "jfr/writers/jfrStorageAdapter.hpp" + +class Thread; + +class JfrCheckpointFlush : public StackObj { + public: + typedef JfrBuffer Type; + JfrCheckpointFlush(Type* old, size_t used, size_t requested, Thread* t); + Type* result() { return _result; } + private: + Type* _result; +}; + +typedef Adapter JfrCheckpointAdapter; +typedef AcquireReleaseMemoryWriterHost JfrTransactionalCheckpointWriter; +typedef EventWriterHost JfrCheckpointWriterBase; + +struct JfrCheckpointContext { + jlong offset; + juint count; +}; + +class JfrCheckpointWriter : public JfrCheckpointWriterBase { + friend class JfrSerializerRegistration; + private: + JfrTicks _time; + jlong _offset; + juint _count; + bool _flushpoint; + bool _header; + + juint count() const; + void set_count(juint count); + void increment(); + void set_flushpoint(bool flushpoint); + bool is_flushpoint() const; + const u1* session_data(size_t* size, const JfrCheckpointContext* ctx = NULL); + void release(); + + public: + JfrCheckpointWriter(bool flushpoint, bool header, Thread* thread); + ~JfrCheckpointWriter(); + void write_type(JfrTypeId type_id); + void write_count(u4 nof_entries); + void write_count(u4 nof_entries, jlong offset); + void write_key(u8 key); + const JfrCheckpointContext context() const; + void set_context(const JfrCheckpointContext ctx); + bool has_data() const; + JfrCheckpointBlobHandle checkpoint_blob(); + JfrCheckpointBlobHandle copy(const JfrCheckpointContext* ctx = NULL); + JfrCheckpointBlobHandle move(const JfrCheckpointContext* ctx = NULL); +}; + +#endif // SHARE_VM_JFR_RECORDER_CHECKPOINT_JFRCHECKPOINTWRITER_HPP diff --git a/src/hotspot/share/jfr/recorder/checkpoint/jfrMetadataEvent.cpp b/src/hotspot/share/jfr/recorder/checkpoint/jfrMetadataEvent.cpp new file mode 100644 index 00000000000..4b5d88c4e8e --- /dev/null +++ b/src/hotspot/share/jfr/recorder/checkpoint/jfrMetadataEvent.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "jfr/jni/jfrJavaSupport.hpp" +#include "jfr/recorder/checkpoint/jfrMetadataEvent.hpp" +#include "jfr/recorder/repository/jfrChunkWriter.hpp" +#include "oops/klass.inline.hpp" +#include "oops/oop.inline.hpp" +#include "oops/typeArrayOop.inline.hpp" +#include "runtime/semaphore.hpp" +#include "runtime/thread.inline.hpp" + +static jbyteArray _metadata_blob = NULL; +static Semaphore metadata_mutex_semaphore(1); + +void JfrMetadataEvent::lock() { + metadata_mutex_semaphore.wait(); +} + +void JfrMetadataEvent::unlock() { + metadata_mutex_semaphore.signal(); +} + +static void write_metadata_blob(JfrChunkWriter& chunkwriter, jbyteArray metadata_blob) { + if (metadata_blob != NULL) { + const typeArrayOop arr = (typeArrayOop)JfrJavaSupport::resolve_non_null(metadata_blob); + assert(arr != NULL, "invariant"); + const int length = arr->length(); + const Klass* const k = arr->klass(); + assert(k != NULL && k->is_array_klass(), "invariant"); + const TypeArrayKlass* const byte_arr_klass = TypeArrayKlass::cast(k); + const jbyte* const data_address = arr->byte_at_addr(0); + chunkwriter.write_unbuffered(data_address, length); + } +} + +// the semaphore is assumed to be locked (was locked previous safepoint) +size_t JfrMetadataEvent::write(JfrChunkWriter& chunkwriter, jlong metadata_offset) { + assert(chunkwriter.is_valid(), "invariant"); + assert(chunkwriter.current_offset() == metadata_offset, "invariant"); + // header + chunkwriter.reserve(sizeof(u4)); + chunkwriter.write(EVENT_METADATA); // ID 0 + // time data + chunkwriter.write(JfrTicks::now()); + chunkwriter.write((u8)0); // duration + chunkwriter.write((u8)0); // metadata id + write_metadata_blob(chunkwriter, _metadata_blob); // payload + unlock(); // open up for java to provide updated metadata + // fill in size of metadata descriptor event + const jlong size_written = chunkwriter.current_offset() - metadata_offset; + chunkwriter.write_padded_at_offset((u4)size_written, metadata_offset); + return size_written; +} + +void JfrMetadataEvent::update(jbyteArray metadata) { + JavaThread* thread = (JavaThread*)Thread::current(); + assert(thread->is_Java_thread(), "invariant"); + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(thread)); + lock(); + if (_metadata_blob != NULL) { + JfrJavaSupport::destroy_global_jni_handle(_metadata_blob); + } + const oop new_desc_oop = JfrJavaSupport::resolve_non_null(metadata); + _metadata_blob = new_desc_oop != NULL ? (jbyteArray)JfrJavaSupport::global_jni_handle(new_desc_oop, thread) : NULL; + unlock(); +} diff --git a/src/hotspot/share/jfr/recorder/checkpoint/jfrMetadataEvent.hpp b/src/hotspot/share/jfr/recorder/checkpoint/jfrMetadataEvent.hpp new file mode 100644 index 00000000000..09845069df6 --- /dev/null +++ b/src/hotspot/share/jfr/recorder/checkpoint/jfrMetadataEvent.hpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_JFR_RECORDER_CHECKPOINT_JFRMETADATAEVENT_HPP +#define SHARE_VM_JFR_RECORDER_CHECKPOINT_JFRMETADATAEVENT_HPP + +#include "jni.h" +#include "memory/allocation.hpp" + +class JfrChunkWriter; + +// +// Metadata is continuously updated in Java as event classes are loaded / unloaded. +// Using update(), Java stores a binary representation back to native. +// This is for easy access on chunk finalization as well as having it readily available in the case of fatal error. +// +class JfrMetadataEvent : AllStatic { + public: + static void lock(); + static void unlock(); + static size_t write(JfrChunkWriter& writer, jlong metadata_offset); + static void update(jbyteArray metadata); +}; + +#endif // SHARE_VM_JFR_RECORDER_CHECKPOINT_JFRMETADATAEVENT_HPP diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroup.cpp b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroup.cpp new file mode 100644 index 00000000000..129319c87c3 --- /dev/null +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroup.cpp @@ -0,0 +1,412 @@ +/* + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "jfr/recorder/checkpoint/jfrCheckpointWriter.hpp" +#include "jfr/recorder/checkpoint/types/jfrThreadGroup.hpp" +#include "jfr/utilities/jfrResourceManager.hpp" +#include "jfr/utilities/jfrTypes.hpp" +#include "runtime/handles.inline.hpp" +#include "runtime/jniHandles.inline.hpp" +#include "runtime/safepoint.hpp" +#include "runtime/semaphore.hpp" +#include "utilities/growableArray.hpp" + +class ThreadGroupExclusiveAccess : public StackObj { + private: + static Semaphore _mutex_semaphore; + public: + ThreadGroupExclusiveAccess() { _mutex_semaphore.wait(); } + ~ThreadGroupExclusiveAccess() { _mutex_semaphore.signal(); } +}; + +Semaphore ThreadGroupExclusiveAccess::_mutex_semaphore(1); +JfrThreadGroup* JfrThreadGroup::_instance = NULL; + +class JfrThreadGroupPointers : public ResourceObj { + private: + const Handle _thread_group_handle; + jweak _thread_group_weak_ref; + public: + JfrThreadGroupPointers(Handle thread_group_handle, jweak thread_group_weak_ref); + Handle thread_group_handle() const; + jweak thread_group_weak_ref() const; + oopDesc* const thread_group_oop() const; + jweak transfer_weak_global_handle_ownership(); + void clear_weak_ref(); +}; + +JfrThreadGroupPointers::JfrThreadGroupPointers(Handle thread_group_handle, jweak thread_group_weak_ref) : + _thread_group_handle(thread_group_handle), + _thread_group_weak_ref(thread_group_weak_ref) {} + +Handle JfrThreadGroupPointers::thread_group_handle() const { + return _thread_group_handle; +} + +jweak JfrThreadGroupPointers::thread_group_weak_ref() const { + return _thread_group_weak_ref; +} + +oopDesc* const JfrThreadGroupPointers::thread_group_oop() const { + assert(_thread_group_weak_ref == NULL || + JNIHandles::resolve_non_null(_thread_group_weak_ref) == _thread_group_handle(), "invariant"); + return _thread_group_handle(); +} + +jweak JfrThreadGroupPointers::transfer_weak_global_handle_ownership() { + jweak temp = _thread_group_weak_ref; + _thread_group_weak_ref = NULL; + return temp; +} + +void JfrThreadGroupPointers::clear_weak_ref() { + if (NULL != _thread_group_weak_ref) { + JNIHandles::destroy_weak_global(_thread_group_weak_ref); + } +} + +class JfrThreadGroupsHelper : public ResourceObj { + private: + static const int invalid_iterator_pos = -1; + GrowableArray* _thread_group_hierarchy; + int _current_iterator_pos; + + int populate_thread_group_hierarchy(const JavaThread* jt, Thread* current); + JfrThreadGroupPointers& at(int index); + + public: + JfrThreadGroupsHelper(const JavaThread* jt, Thread* current); + ~JfrThreadGroupsHelper(); + JfrThreadGroupPointers& next(); + bool is_valid() const; + bool has_next() const; +}; + +JfrThreadGroupsHelper::JfrThreadGroupsHelper(const JavaThread* jt, Thread* current) { + _thread_group_hierarchy = new GrowableArray(10, false, mtTracing); + _current_iterator_pos = populate_thread_group_hierarchy(jt, current) - 1; +} + +JfrThreadGroupsHelper::~JfrThreadGroupsHelper() { + assert(_current_iterator_pos == invalid_iterator_pos, "invariant"); + for (int i = 0; i < _thread_group_hierarchy->length(); ++i) { + _thread_group_hierarchy->at(i)->clear_weak_ref(); + } +} + +JfrThreadGroupPointers& JfrThreadGroupsHelper::at(int index) { + assert(_thread_group_hierarchy != NULL, "invariant"); + assert(index > invalid_iterator_pos && index < _thread_group_hierarchy->length(), "invariant"); + return *(_thread_group_hierarchy->at(index)); +} + +bool JfrThreadGroupsHelper::has_next() const { + return _current_iterator_pos > invalid_iterator_pos; +} + +bool JfrThreadGroupsHelper::is_valid() const { + return (_thread_group_hierarchy != NULL && _thread_group_hierarchy->length() > 0); +} + +JfrThreadGroupPointers& JfrThreadGroupsHelper::next() { + assert(is_valid(), "invariant"); + return at(_current_iterator_pos--); +} + +/* + * If not at a safepoint, we create global weak references for + * all reachable threadgroups for this thread. + * If we are at a safepoint, the caller is the VMThread during + * JFR checkpointing. It can use naked oops, because nothing + * will move before the list of threadgroups is cleared and + * mutator threads restarted. The threadgroup list is cleared + * later by the VMThread as one of the final steps in JFR checkpointing + * (not here). + */ +int JfrThreadGroupsHelper::populate_thread_group_hierarchy(const JavaThread* jt, Thread* current) { + assert(jt != NULL && jt->is_Java_thread(), "invariant"); + assert(current != NULL, "invariant"); + assert(_thread_group_hierarchy != NULL, "invariant"); + + // immediate thread group + Handle thread_group_handle(current, java_lang_Thread::threadGroup(jt->threadObj())); + if (thread_group_handle == NULL) { + return 0; + } + + const bool use_weak_handles = !SafepointSynchronize::is_at_safepoint(); + jweak thread_group_weak_ref = use_weak_handles ? JNIHandles::make_weak_global(thread_group_handle) : NULL; + + JfrThreadGroupPointers* thread_group_pointers = new JfrThreadGroupPointers(thread_group_handle, thread_group_weak_ref); + _thread_group_hierarchy->append(thread_group_pointers); + // immediate parent thread group + oop parent_thread_group_obj = java_lang_ThreadGroup::parent(thread_group_handle()); + Handle parent_thread_group_handle(current, parent_thread_group_obj); + + // and check parents parents... + while (!(parent_thread_group_handle == NULL)) { + const jweak parent_group_weak_ref = use_weak_handles ? JNIHandles::make_weak_global(parent_thread_group_handle) : NULL; + thread_group_pointers = new JfrThreadGroupPointers(parent_thread_group_handle, parent_group_weak_ref); + _thread_group_hierarchy->append(thread_group_pointers); + parent_thread_group_obj = java_lang_ThreadGroup::parent(parent_thread_group_handle()); + parent_thread_group_handle = Handle(current, parent_thread_group_obj); + } + return _thread_group_hierarchy->length(); +} + +static traceid next_id() { + static traceid _current_threadgroup_id = 0; + return ++_current_threadgroup_id; +} + +class JfrThreadGroup::JfrThreadGroupEntry : public JfrCHeapObj { + friend class JfrThreadGroup; + private: + traceid _thread_group_id; + traceid _parent_group_id; + char* _thread_group_name; // utf8 format + // If an entry is created during a safepoint, the + // _thread_group_oop contains a direct oop to + // the java.lang.ThreadGroup object. + // If an entry is created on javathread exit time (not at safepoint), + // _thread_group_weak_ref contains a JNI weak global handle + // indirection to the java.lang.ThreadGroup object. + // Note: we cannot use a union here since CHECK_UNHANDLED_OOPS makes oop have + // a ctor which isn't allowed in a union by the SunStudio compiler + oop _thread_group_oop; + jweak _thread_group_weak_ref; + + JfrThreadGroupEntry(const char* tgstr, JfrThreadGroupPointers& ptrs); + ~JfrThreadGroupEntry(); + + traceid thread_group_id() const { return _thread_group_id; } + void set_thread_group_id(traceid tgid) { _thread_group_id = tgid; } + + const char* const thread_group_name() const { return _thread_group_name; } + void set_thread_group_name(const char* tgname); + + traceid parent_group_id() const { return _parent_group_id; } + void set_parent_group_id(traceid pgid) { _parent_group_id = pgid; } + + void set_thread_group(JfrThreadGroupPointers& ptrs); + bool is_equal(const JfrThreadGroupPointers& ptrs) const; + const oop thread_group() const; +}; + +JfrThreadGroup::JfrThreadGroupEntry::JfrThreadGroupEntry(const char* tgname, JfrThreadGroupPointers& ptrs) : + _thread_group_id(0), + _parent_group_id(0), + _thread_group_name(NULL), + _thread_group_oop(NULL), + _thread_group_weak_ref(NULL) { + set_thread_group_name(tgname); + set_thread_group(ptrs); +} + +JfrThreadGroup::JfrThreadGroupEntry::~JfrThreadGroupEntry() { + if (_thread_group_name != NULL) { + JfrCHeapObj::free(_thread_group_name, strlen(_thread_group_name) + 1); + } + if (_thread_group_weak_ref != NULL) { + JNIHandles::destroy_weak_global(_thread_group_weak_ref); + } +} + +void JfrThreadGroup::JfrThreadGroupEntry::set_thread_group_name(const char* tgname) { + assert(_thread_group_name == NULL, "invariant"); + if (tgname != NULL) { + size_t len = strlen(tgname); + _thread_group_name = JfrCHeapObj::new_array(len+1); + strncpy(_thread_group_name, tgname, len); + _thread_group_name[len] = '\0'; + } +} + +const oop JfrThreadGroup::JfrThreadGroupEntry::thread_group() const { + return _thread_group_weak_ref != NULL ? JNIHandles::resolve(_thread_group_weak_ref) : _thread_group_oop; +} + +void JfrThreadGroup::JfrThreadGroupEntry::set_thread_group(JfrThreadGroupPointers& ptrs) { + _thread_group_weak_ref = ptrs.transfer_weak_global_handle_ownership(); + if (_thread_group_weak_ref == NULL) { + _thread_group_oop = ptrs.thread_group_oop(); + assert(_thread_group_oop != NULL, "invariant"); + } else { + _thread_group_oop = NULL; + } +} + +JfrThreadGroup::JfrThreadGroup() : _list(NULL) { + _list = new (ResourceObj::C_HEAP, mtTracing) GrowableArray(30, true); +} + +JfrThreadGroup::~JfrThreadGroup() { + assert(SafepointSynchronize::is_at_safepoint(), "invariant"); + if (_list != NULL) { + for (int i = 0; i < _list->length(); i++) { + JfrThreadGroupEntry* e = _list->at(i); + delete e; + } + delete _list; + } +} + +JfrThreadGroup* JfrThreadGroup::instance() { + return _instance; +} + +void JfrThreadGroup::set_instance(JfrThreadGroup* new_instance) { + _instance = new_instance; +} + +traceid JfrThreadGroup::thread_group_id(const JavaThread* jt, Thread* current) { + ResourceMark rm(current); + HandleMark hm(current); + JfrThreadGroupsHelper helper(jt, current); + return helper.is_valid() ? thread_group_id_internal(helper) : 0; +} + +traceid JfrThreadGroup::thread_group_id(JavaThread* const jt) { + assert(!JfrStream_lock->owned_by_self(), "holding stream lock but should not hold it here"); + return thread_group_id(jt, jt); +} + +traceid JfrThreadGroup::thread_group_id_internal(JfrThreadGroupsHelper& helper) { + ThreadGroupExclusiveAccess lock; + JfrThreadGroup* tg_instance = instance(); + if (tg_instance == NULL) { + tg_instance = new JfrThreadGroup(); + if (tg_instance == NULL) { + return 0; + } + set_instance(tg_instance); + } + + JfrThreadGroupEntry* tge = NULL; + int parent_thread_group_id = 0; + while (helper.has_next()) { + JfrThreadGroupPointers& ptrs = helper.next(); + tge = tg_instance->find_entry(ptrs); + if (NULL == tge) { + tge = tg_instance->new_entry(ptrs); + assert(tge != NULL, "invariant"); + tge->set_parent_group_id(parent_thread_group_id); + } + parent_thread_group_id = tge->thread_group_id(); + } + // the last entry in the hierarchy is the immediate thread group + return tge->thread_group_id(); +} + +bool JfrThreadGroup::JfrThreadGroupEntry::is_equal(const JfrThreadGroupPointers& ptrs) const { + return ptrs.thread_group_oop() == thread_group(); +} + +JfrThreadGroup::JfrThreadGroupEntry* +JfrThreadGroup::find_entry(const JfrThreadGroupPointers& ptrs) const { + for (int index = 0; index < _list->length(); ++index) { + JfrThreadGroupEntry* curtge = _list->at(index); + if (curtge->is_equal(ptrs)) { + return curtge; + } + } + return (JfrThreadGroupEntry*) NULL; +} + +// Assumes you already searched for the existence +// of a corresponding entry in find_entry(). +JfrThreadGroup::JfrThreadGroupEntry* +JfrThreadGroup::new_entry(JfrThreadGroupPointers& ptrs) { + JfrThreadGroupEntry* const tge = new JfrThreadGroupEntry(java_lang_ThreadGroup::name(ptrs.thread_group_oop()), ptrs); + add_entry(tge); + return tge; +} + +int JfrThreadGroup::add_entry(JfrThreadGroupEntry* tge) { + assert(tge != NULL, "attempting to add a null entry!"); + assert(0 == tge->thread_group_id(), "id must be unassigned!"); + tge->set_thread_group_id(next_id()); + return _list->append(tge); +} + +void JfrThreadGroup::write_thread_group_entries(JfrCheckpointWriter& writer) const { + assert(_list != NULL && !_list->is_empty(), "should not need be here!"); + const int number_of_tg_entries = _list->length(); + writer.write_count(number_of_tg_entries); + for (int index = 0; index < number_of_tg_entries; ++index) { + const JfrThreadGroupEntry* const curtge = _list->at(index); + writer.write_key(curtge->thread_group_id()); + writer.write(curtge->parent_group_id()); + writer.write(curtge->thread_group_name()); + } +} + +void JfrThreadGroup::write_selective_thread_group(JfrCheckpointWriter* writer, traceid thread_group_id) const { + assert(writer != NULL, "invariant"); + assert(_list != NULL && !_list->is_empty(), "should not need be here!"); + const int number_of_tg_entries = _list->length(); + + // save context + const JfrCheckpointContext ctx = writer->context(); + writer->write_type(TYPE_THREADGROUP); + const jlong count_offset = writer->reserve(sizeof(u4)); // Don't know how many yet + int number_of_entries_written = 0; + for (int index = number_of_tg_entries - 1; index >= 0; --index) { + const JfrThreadGroupEntry* const curtge = _list->at(index); + if (thread_group_id == curtge->thread_group_id()) { + writer->write_key(curtge->thread_group_id()); + writer->write(curtge->parent_group_id()); + writer->write(curtge->thread_group_name()); + ++number_of_entries_written; + thread_group_id = curtge->parent_group_id(); + } + } + if (number_of_entries_written == 0) { + // nothing to write, restore context + writer->set_context(ctx); + return; + } + assert(number_of_entries_written > 0, "invariant"); + writer->write_count(number_of_entries_written, count_offset); +} + +// Write out JfrThreadGroup instance and then delete it +void JfrThreadGroup::serialize(JfrCheckpointWriter& writer) { + ThreadGroupExclusiveAccess lock; + JfrThreadGroup* tg_instance = instance(); + assert(tg_instance != NULL, "invariant"); + ResourceManager tg_handle(tg_instance); + set_instance(NULL); + tg_handle->write_thread_group_entries(writer); +} + +// for writing a particular thread group +void JfrThreadGroup::serialize(JfrCheckpointWriter* writer, traceid thread_group_id) { + assert(writer != NULL, "invariant"); + ThreadGroupExclusiveAccess lock; + JfrThreadGroup* const tg_instance = instance(); + assert(tg_instance != NULL, "invariant"); + tg_instance->write_selective_thread_group(writer, thread_group_id); +} diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroup.hpp b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroup.hpp new file mode 100644 index 00000000000..e11dd75da41 --- /dev/null +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroup.hpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_JFR_RECORDER_CHECKPOINT_TYPES_JFRTHREADGROUP_HPP +#define SHARE_VM_JFR_RECORDER_CHECKPOINT_TYPES_JFRTHREADGROUP_HPP + +#include "jni.h" +#include "jfr/utilities/jfrAllocation.hpp" +#include "jfr/utilities/jfrTypes.hpp" + +class JfrCheckpointWriter; +template +class GrowableArray; +class JfrThreadGroupsHelper; +class JfrThreadGroupPointers; + +class JfrThreadGroup : public JfrCHeapObj { + friend class JfrCheckpointThreadClosure; + private: + static JfrThreadGroup* _instance; + class JfrThreadGroupEntry; + GrowableArray* _list; + + JfrThreadGroup(); + JfrThreadGroupEntry* find_entry(const JfrThreadGroupPointers& ptrs) const; + JfrThreadGroupEntry* new_entry(JfrThreadGroupPointers& ptrs); + int add_entry(JfrThreadGroupEntry* const tge); + + void write_thread_group_entries(JfrCheckpointWriter& writer) const; + void write_selective_thread_group(JfrCheckpointWriter* writer, traceid thread_group_id) const; + + static traceid thread_group_id_internal(JfrThreadGroupsHelper& helper); + static JfrThreadGroup* instance(); + static void set_instance(JfrThreadGroup* new_instance); + + public: + ~JfrThreadGroup(); + static void serialize(JfrCheckpointWriter& w); + static void serialize(JfrCheckpointWriter* w, traceid thread_group_id); + static traceid thread_group_id(JavaThread* thread); + static traceid thread_group_id(const JavaThread* thread, Thread* current); +}; + +#endif // SHARE_VM_JFR_RECORDER_CHECKPOINT_TYPES_JFRTHREADGROUP_HPP diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadState.cpp b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadState.cpp new file mode 100644 index 00000000000..50e213b3ce5 --- /dev/null +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadState.cpp @@ -0,0 +1,82 @@ +/* +* Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* This code is free software; you can redistribute it and/or modify it +* under the terms of the GNU General Public License version 2 only, as +* published by the Free Software Foundation. +* +* This code is distributed in the hope that it will be useful, but WITHOUT +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +* version 2 for more details (a copy is included in the LICENSE file that +* accompanied this code). +* +* You should have received a copy of the GNU General Public License version +* 2 along with this work; if not, write to the Free Software Foundation, +* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +* +* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +* or visit www.oracle.com if you need additional information or have any +* questions. +* +*/ + +#include "precompiled.hpp" +#include "jfr/recorder/checkpoint/types/jfrThreadState.hpp" +#include "jfr/recorder/checkpoint/jfrCheckpointWriter.hpp" +#include "jvmtifiles/jvmti.h" + +struct jvmti_thread_state { + u8 id; + const char* description; +}; + +static jvmti_thread_state states[] = { + { + JVMTI_JAVA_LANG_THREAD_STATE_NEW, + "STATE_NEW" + }, + { + JVMTI_THREAD_STATE_TERMINATED, + "STATE_TERMINATED" + }, + { + JVMTI_JAVA_LANG_THREAD_STATE_RUNNABLE, + "STATE_RUNNABLE" + }, + { + (JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_WAITING | JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT | JVMTI_THREAD_STATE_SLEEPING), + "STATE_SLEEPING" + }, + { + (JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_WAITING | JVMTI_THREAD_STATE_WAITING_INDEFINITELY | JVMTI_THREAD_STATE_IN_OBJECT_WAIT), + "STATE_IN_OBJECT_WAIT" + }, + { + (JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_WAITING | JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT | JVMTI_THREAD_STATE_IN_OBJECT_WAIT), + "STATE_IN_OBJECT_WAIT_TIMED" + }, + { + (JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_WAITING | JVMTI_THREAD_STATE_WAITING_INDEFINITELY | JVMTI_THREAD_STATE_PARKED), + "STATE_PARKED" + }, + { + (JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_WAITING | JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT | JVMTI_THREAD_STATE_PARKED), + "STATE_PARKED_TIMED" + }, + { + JVMTI_JAVA_LANG_THREAD_STATE_BLOCKED, + "STATE_BLOCKED_ON_MONITOR_ENTER" + } +}; + +void JfrThreadState::serialize(JfrCheckpointWriter& writer) { + const u4 number_of_states = sizeof(states) / sizeof(jvmti_thread_state); + writer.write_count(number_of_states); + for (u4 i = 0; i < number_of_states; ++i) { + writer.write_key(states[i].id); + writer.write(states[i].description); + } +} + diff --git a/src/hotspot/share/trace/traceBackend.cpp b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadState.hpp similarity index 69% rename from src/hotspot/share/trace/traceBackend.cpp rename to src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadState.hpp index 0a629848101..bcedaa7949e 100644 --- a/src/hotspot/share/trace/traceBackend.cpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadState.hpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved. +* Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,7 +22,16 @@ * */ -#include "precompiled.hpp" -#include "jni.h" +#ifndef SHARE_VM_JFR_RECORDER_CHECKPOINT_TYPES_JFRTHREADSTATE_HPP +#define SHARE_VM_JFR_RECORDER_CHECKPOINT_TYPES_JFRTHREADSTATE_HPP -extern "C" void JNICALL trace_register_natives(JNIEnv*, jclass) {} +#include "memory/allocation.hpp" + +class JfrCheckpointWriter; + +class JfrThreadState : public AllStatic { + public: + static void serialize(JfrCheckpointWriter& writer); +}; + +#endif // SHARE_VM_JFR_RECORDER_CHECKPOINT_TYPES_JFRTHREADSTATE_HPP diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.cpp b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.cpp new file mode 100644 index 00000000000..3de0a2690d3 --- /dev/null +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.cpp @@ -0,0 +1,348 @@ +/* + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "classfile/javaClasses.inline.hpp" +#include "code/codeBlob.hpp" +#include "code/codeCache.hpp" +#include "gc/shared/gcCause.hpp" +#include "gc/shared/gcName.hpp" +#include "gc/shared/gcTrace.hpp" +#include "gc/shared/gcWhen.hpp" +#include "jfr/leakprofiler/checkpoint/objectSampleCheckpoint.hpp" +#include "jfr/leakprofiler/leakProfiler.hpp" +#include "jfr/recorder/checkpoint/jfrCheckpointManager.hpp" +#include "jfr/recorder/checkpoint/types/jfrType.hpp" +#include "jfr/recorder/jfrRecorder.hpp" +#include "jfr/recorder/checkpoint/types/jfrThreadGroup.hpp" +#include "jfr/recorder/checkpoint/types/jfrThreadState.hpp" +#include "jfr/recorder/checkpoint/types/jfrTypeSet.hpp" +#include "jfr/support/jfrThreadLocal.hpp" +#include "jfr/writers/jfrJavaEventWriter.hpp" +#include "memory/metaspaceGCThresholdUpdater.hpp" +#include "memory/referenceType.hpp" +#include "memory/universe.hpp" +#include "runtime/flags/jvmFlag.hpp" +#include "runtime/mutexLocker.hpp" +#include "runtime/osThread.hpp" +#include "runtime/safepoint.hpp" +#include "runtime/synchronizer.hpp" +#include "runtime/thread.inline.hpp" +#include "runtime/vm_operations.hpp" + +#ifdef COMPILER2 +#include "opto/compile.hpp" +#include "opto/node.hpp" +#endif +#if INCLUDE_G1GC +#include "gc/g1/g1HeapRegionTraceType.hpp" +#include "gc/g1/g1YCTypes.hpp" +#endif + +// implementation for the static registration function exposed in the api +bool JfrSerializer::register_serializer(JfrTypeId id, bool require_safepoint, bool permit_cache, JfrSerializer* cs) { + assert(cs != NULL, "invariant"); + return JfrCheckpointManager::register_serializer(id, require_safepoint, permit_cache, cs); +} + +class JfrCheckpointThreadCountClosure : public ThreadClosure { +private: + u4 _total_threads; +public: + JfrCheckpointThreadCountClosure() : _total_threads(0) {} + u4 total_threads() { return _total_threads; } + void do_thread(Thread *t) { _total_threads++; } +}; + +// Requires a ResourceMark for get_thread_name/as_utf8 +class JfrCheckpointThreadClosure : public ThreadClosure { + private: + JfrCheckpointWriter& _writer; + Thread* _curthread; + public: + JfrCheckpointThreadClosure(JfrCheckpointWriter& writer) : _writer(writer), _curthread(Thread::current()) {} + void do_thread(Thread* t); +}; + +// Requires a ResourceMark for get_thread_name/as_utf8 +void JfrCheckpointThreadClosure::do_thread(Thread* t) { + assert(t != NULL, "invariant"); + assert_locked_or_safepoint(Threads_lock); + _writer.write_key(t->jfr_thread_local()->thread_id()); + _writer.write(t->name()); + const OSThread* const os_thread = t->osthread(); + _writer.write(os_thread != NULL ? os_thread->thread_id() : (u8)0); + if (t->is_Java_thread()) { + JavaThread* const jt = (JavaThread*)t; + _writer.write(jt->name()); + _writer.write(java_lang_Thread::thread_id(jt->threadObj())); + _writer.write(JfrThreadGroup::thread_group_id(jt, _curthread)); + // since we are iterating threads during a safepoint, also issue notification + JfrJavaEventWriter::notify(jt); + return; + } + _writer.write((const char*)NULL); // java name + _writer.write((traceid)0); // java thread id + _writer.write((traceid)0); // java thread group +} + +void JfrThreadConstantSet::serialize(JfrCheckpointWriter& writer) { + assert(SafepointSynchronize::is_at_safepoint(), "invariant"); + JfrCheckpointThreadCountClosure tcc; + Threads::threads_do(&tcc); + const u4 total_threads = tcc.total_threads(); + // THREADS + writer.write_count(total_threads); + JfrCheckpointThreadClosure tc(writer); + Threads::threads_do(&tc); +} + +void JfrThreadGroupConstant::serialize(JfrCheckpointWriter& writer) { + assert(SafepointSynchronize::is_at_safepoint(), "invariant"); + JfrThreadGroup::serialize(writer); +} + +static const char* flag_value_origin_to_string(JVMFlag::Flags origin) { + switch (origin) { + case JVMFlag::DEFAULT: return "Default"; + case JVMFlag::COMMAND_LINE: return "Command line"; + case JVMFlag::ENVIRON_VAR: return "Environment variable"; + case JVMFlag::CONFIG_FILE: return "Config file"; + case JVMFlag::MANAGEMENT: return "Management"; + case JVMFlag::ERGONOMIC: return "Ergonomic"; + case JVMFlag::ATTACH_ON_DEMAND: return "Attach on demand"; + case JVMFlag::INTERNAL: return "Internal"; + default: ShouldNotReachHere(); return ""; + } +} + +void FlagValueOriginConstant::serialize(JfrCheckpointWriter& writer) { + static const u4 nof_entries = JVMFlag::LAST_VALUE_ORIGIN + 1; + writer.write_count(nof_entries); + for (u4 i = 0; i < nof_entries; ++i) { + writer.write_key(i); + writer.write(flag_value_origin_to_string((JVMFlag::Flags)i)); + } +} + +void MonitorInflateCauseConstant::serialize(JfrCheckpointWriter& writer) { + static const u4 nof_entries = ObjectSynchronizer::inflate_cause_nof; + writer.write_count(nof_entries); + for (u4 i = 0; i < nof_entries; ++i) { + writer.write_key(i); + writer.write(ObjectSynchronizer::inflate_cause_name((ObjectSynchronizer::InflateCause)i)); + } +} + +void GCCauseConstant::serialize(JfrCheckpointWriter& writer) { + static const u4 nof_entries = GCCause::_last_gc_cause; + writer.write_count(nof_entries); + for (u4 i = 0; i < nof_entries; ++i) { + writer.write_key(i); + writer.write(GCCause::to_string((GCCause::Cause)i)); + } +} + +void GCNameConstant::serialize(JfrCheckpointWriter& writer) { + static const u4 nof_entries = GCNameEndSentinel; + writer.write_count(nof_entries); + for (u4 i = 0; i < nof_entries; ++i) { + writer.write_key(i); + writer.write(GCNameHelper::to_string((GCName)i)); + } +} + +void GCWhenConstant::serialize(JfrCheckpointWriter& writer) { + static const u4 nof_entries = GCWhen::GCWhenEndSentinel; + writer.write_count(nof_entries); + for (u4 i = 0; i < nof_entries; ++i) { + writer.write_key(i); + writer.write(GCWhen::to_string((GCWhen::Type)i)); + } +} + +void G1HeapRegionTypeConstant::serialize(JfrCheckpointWriter& writer) { + static const u4 nof_entries = G1HeapRegionTraceType::G1HeapRegionTypeEndSentinel; + writer.write_count(nof_entries); + for (u4 i = 0; i < nof_entries; ++i) { + writer.write_key(i); + writer.write(G1HeapRegionTraceType::to_string((G1HeapRegionTraceType::Type)i)); + } +} + +void GCThresholdUpdaterConstant::serialize(JfrCheckpointWriter& writer) { + static const u4 nof_entries = MetaspaceGCThresholdUpdater::Last; + writer.write_count(nof_entries); + for (u4 i = 0; i < nof_entries; ++i) { + writer.write_key(i); + writer.write(MetaspaceGCThresholdUpdater::to_string((MetaspaceGCThresholdUpdater::Type)i)); + } +} + +void MetadataTypeConstant::serialize(JfrCheckpointWriter& writer) { + static const u4 nof_entries = Metaspace::MetadataTypeCount; + writer.write_count(nof_entries); + for (u4 i = 0; i < nof_entries; ++i) { + writer.write_key(i); + writer.write(Metaspace::metadata_type_name((Metaspace::MetadataType)i)); + } +} + +void MetaspaceObjectTypeConstant::serialize(JfrCheckpointWriter& writer) { + static const u4 nof_entries = MetaspaceObj::_number_of_types; + writer.write_count(nof_entries); + for (u4 i = 0; i < nof_entries; ++i) { + writer.write_key(i); + writer.write(MetaspaceObj::type_name((MetaspaceObj::Type)i)); + } +} + +void G1YCTypeConstant::serialize(JfrCheckpointWriter& writer) { +#if INCLUDE_G1GC + static const u4 nof_entries = G1YCTypeEndSentinel; + writer.write_count(nof_entries); + for (u4 i = 0; i < nof_entries; ++i) { + writer.write_key(i); + writer.write(G1YCTypeHelper::to_string((G1YCType)i)); + } +#endif +} + +static const char* reference_type_to_string(ReferenceType rt) { + switch (rt) { + case REF_NONE: return "None reference"; + case REF_OTHER: return "Other reference"; + case REF_SOFT: return "Soft reference"; + case REF_WEAK: return "Weak reference"; + case REF_FINAL: return "Final reference"; + case REF_PHANTOM: return "Phantom reference"; + default: + ShouldNotReachHere(); + return NULL; + } +} + +void ReferenceTypeConstant::serialize(JfrCheckpointWriter& writer) { + static const u4 nof_entries = REF_PHANTOM + 1; + writer.write_count(nof_entries); + for (u4 i = 0; i < nof_entries; ++i) { + writer.write_key(i); + writer.write(reference_type_to_string((ReferenceType)i)); + } +} + +void NarrowOopModeConstant::serialize(JfrCheckpointWriter& writer) { + static const u4 nof_entries = Universe::HeapBasedNarrowOop + 1; + writer.write_count(nof_entries); + for (u4 i = 0; i < nof_entries; ++i) { + writer.write_key(i); + writer.write(Universe::narrow_oop_mode_to_string((Universe::NARROW_OOP_MODE)i)); + } +} + +void CompilerPhaseTypeConstant::serialize(JfrCheckpointWriter& writer) { +#ifdef COMPILER2 + static const u4 nof_entries = PHASE_NUM_TYPES; + writer.write_count(nof_entries); + for (u4 i = 0; i < nof_entries; ++i) { + writer.write_key(i); + writer.write(CompilerPhaseTypeHelper::to_string((CompilerPhaseType)i)); + } +#endif +} + +void CodeBlobTypeConstant::serialize(JfrCheckpointWriter& writer) { + static const u4 nof_entries = CodeBlobType::NumTypes; + writer.write_count(nof_entries); + for (u4 i = 0; i < nof_entries; ++i) { + writer.write_key(i); + writer.write(CodeCache::get_code_heap_name(i)); + } +}; + +void VMOperationTypeConstant::serialize(JfrCheckpointWriter& writer) { + static const u4 nof_entries = VM_Operation::VMOp_Terminating; + writer.write_count(nof_entries); + for (u4 i = 0; i < nof_entries; ++i) { + writer.write_key(i); + writer.write(VM_Operation::name(VM_Operation::VMOp_Type(i))); + } +} + +class TypeSetSerialization { + private: + bool _class_unload; + public: + explicit TypeSetSerialization(bool class_unload) : _class_unload(class_unload) {} + void write(JfrCheckpointWriter& writer, JfrCheckpointWriter* leakp_writer) { + JfrTypeSet::serialize(&writer, leakp_writer, _class_unload); + } +}; + +void ClassUnloadTypeSet::serialize(JfrCheckpointWriter& writer) { + TypeSetSerialization type_set(true); + if (LeakProfiler::is_running()) { + JfrCheckpointWriter leakp_writer(false, true, Thread::current()); + type_set.write(writer, &leakp_writer); + ObjectSampleCheckpoint::install(leakp_writer, true, true); + return; + } + type_set.write(writer, NULL); +}; + +void TypeSet::serialize(JfrCheckpointWriter& writer) { + TypeSetSerialization type_set(false); + if (LeakProfiler::is_suspended()) { + JfrCheckpointWriter leakp_writer(false, true, Thread::current()); + type_set.write(writer, &leakp_writer); + ObjectSampleCheckpoint::install(leakp_writer, false, true); + return; + } + type_set.write(writer, NULL); +}; + +void ThreadStateConstant::serialize(JfrCheckpointWriter& writer) { + JfrThreadState::serialize(writer); +} + +void JfrThreadConstant::serialize(JfrCheckpointWriter& writer) { + assert(_thread != NULL, "invariant"); + assert(_thread == Thread::current(), "invariant"); + assert(_thread->is_Java_thread(), "invariant"); + assert(!_thread->jfr_thread_local()->has_thread_checkpoint(), "invariant"); + ResourceMark rm(_thread); + const oop threadObj = _thread->threadObj(); + assert(threadObj != NULL, "invariant"); + const u8 java_lang_thread_id = java_lang_Thread::thread_id(threadObj); + const char* const thread_name = _thread->name(); + const traceid thread_group_id = JfrThreadGroup::thread_group_id(_thread); + writer.write_count(1); + writer.write_key(_thread->jfr_thread_local()->thread_id()); + writer.write(thread_name); + writer.write((u8)_thread->osthread()->thread_id()); + writer.write(thread_name); + writer.write(java_lang_thread_id); + writer.write(thread_group_id); + JfrThreadGroup::serialize(&writer, thread_group_id); +} diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.hpp b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.hpp new file mode 100644 index 00000000000..5f91819b9e1 --- /dev/null +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.hpp @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_JFR_CHECKPOINT_TYPES_JFRTYPE_HPP +#define SHARE_VM_JFR_CHECKPOINT_TYPES_JFRTYPE_HPP + +#include "jfr/metadata/jfrSerializer.hpp" + +class JfrThreadConstantSet : public JfrSerializer { + public: + void serialize(JfrCheckpointWriter& writer); +}; + +class JfrThreadGroupConstant : public JfrSerializer { + public: + void serialize(JfrCheckpointWriter& writer); +}; + +class ClassUnloadTypeSet : public JfrSerializer { + public: + void serialize(JfrCheckpointWriter& writer); +}; + +class FlagValueOriginConstant : public JfrSerializer { + public: + void serialize(JfrCheckpointWriter& writer); +}; + +class MonitorInflateCauseConstant : public JfrSerializer { + public: + void serialize(JfrCheckpointWriter& writer); +}; + +class GCCauseConstant : public JfrSerializer { + public: + void serialize(JfrCheckpointWriter& writer); +}; + +class GCNameConstant : public JfrSerializer { + public: + void serialize(JfrCheckpointWriter& writer); +}; + +class GCWhenConstant : public JfrSerializer { + public: + void serialize(JfrCheckpointWriter& writer); +}; + +class G1HeapRegionTypeConstant : public JfrSerializer { + public: + void serialize(JfrCheckpointWriter& writer); +}; + +class GCThresholdUpdaterConstant : public JfrSerializer { + public: + void serialize(JfrCheckpointWriter& writer); +}; + +class MetadataTypeConstant : public JfrSerializer { + public: + void serialize(JfrCheckpointWriter& writer); +}; + +class MetaspaceObjectTypeConstant : public JfrSerializer { + public: + void serialize(JfrCheckpointWriter& writer); +}; + +class G1YCTypeConstant : public JfrSerializer { + public: + void serialize(JfrCheckpointWriter& writer); +}; + +class ReferenceTypeConstant : public JfrSerializer { + public: + void serialize(JfrCheckpointWriter& writer); +}; + +class NarrowOopModeConstant : public JfrSerializer { + public: + void serialize(JfrCheckpointWriter& writer); +}; + +class CompilerPhaseTypeConstant : public JfrSerializer { + public: + void serialize(JfrCheckpointWriter& writer); +}; + +class CodeBlobTypeConstant : public JfrSerializer { + public: + void serialize(JfrCheckpointWriter& writer); +}; + +class VMOperationTypeConstant : public JfrSerializer { + public: + void serialize(JfrCheckpointWriter& writer); +}; + +class TypeSet : public JfrSerializer { + public: + void serialize(JfrCheckpointWriter& writer); +}; + +class ThreadStateConstant : public JfrSerializer { + public: + void serialize(JfrCheckpointWriter& writer); +}; + +class JfrThreadConstant : public JfrSerializer { + private: + JavaThread* _thread; + public: + JfrThreadConstant(JavaThread* jt) : _thread(jt) {} + void serialize(JfrCheckpointWriter& writer); +}; + +#endif // SHARE_VM_JFR_CHECKPOINT_CONSTANT_JFRCONSTANT_HPP diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeManager.cpp b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeManager.cpp new file mode 100644 index 00000000000..02613d2e204 --- /dev/null +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeManager.cpp @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "jfr/recorder/checkpoint/jfrCheckpointWriter.hpp" +#include "jfr/recorder/checkpoint/types/jfrType.hpp" +#include "jfr/recorder/checkpoint/types/jfrTypeManager.hpp" +#include "jfr/utilities/jfrIterator.hpp" +#include "runtime/safepoint.hpp" +#include "runtime/thread.inline.hpp" +#include "utilities/exceptions.hpp" + +JfrSerializerRegistration::JfrSerializerRegistration(JfrTypeId id, bool permit_cache, JfrSerializer* cs) : + _next(NULL), + _prev(NULL), + _serializer(cs), + _cache(), + _id(id), + _permit_cache(permit_cache) {} + +JfrSerializerRegistration::~JfrSerializerRegistration() { + delete _serializer; +} + +JfrSerializerRegistration* JfrSerializerRegistration::next() const { + return _next; +} + +void JfrSerializerRegistration::set_next(JfrSerializerRegistration* next) { + _next = next; +} + +JfrSerializerRegistration* JfrSerializerRegistration::prev() const { + return _prev; +} + +void JfrSerializerRegistration::set_prev(JfrSerializerRegistration* prev) { + _prev = prev; +} + +JfrTypeId JfrSerializerRegistration::id() const { + return _id; +} + +void JfrSerializerRegistration::invoke_serializer(JfrCheckpointWriter& writer) const { + if (_cache.valid()) { + writer.increment(); + _cache->write(writer); + return; + } + const JfrCheckpointContext ctx = writer.context(); + writer.write_type(_id); + _serializer->serialize(writer); + if (_permit_cache) { + _cache = writer.copy(&ctx); + } +} + +JfrTypeManager::~JfrTypeManager() { + Iterator iter(_types); + JfrSerializerRegistration* registration; + while (iter.has_next()) { + registration = _types.remove(iter.next()); + assert(registration != NULL, "invariant"); + delete registration; + } + Iterator sp_type_iter(_safepoint_types); + while (sp_type_iter.has_next()) { + registration = _safepoint_types.remove(sp_type_iter.next()); + assert(registration != NULL, "invariant"); + delete registration; + } +} + +size_t JfrTypeManager::number_of_registered_types() const { + size_t count = 0; + const Iterator iter(_types); + while (iter.has_next()) { + ++count; + iter.next(); + } + const Iterator sp_type_iter(_safepoint_types); + while (sp_type_iter.has_next()) { + ++count; + sp_type_iter.next(); + } + return count; +} + +void JfrTypeManager::write_types(JfrCheckpointWriter& writer) const { + const Iterator iter(_types); + while (iter.has_next()) { + iter.next()->invoke_serializer(writer); + } +} + +void JfrTypeManager::write_safepoint_types(JfrCheckpointWriter& writer) const { + assert(SafepointSynchronize::is_at_safepoint(), "invariant"); + const Iterator iter(_safepoint_types); + while (iter.has_next()) { + iter.next()->invoke_serializer(writer); + } +} + +void JfrTypeManager::write_type_set() const { + assert(!SafepointSynchronize::is_at_safepoint(), "invariant"); + // can safepoint here because of Module_lock + MutexLockerEx lock(Module_lock); + JfrCheckpointWriter writer(true, true, Thread::current()); + TypeSet set; + set.serialize(writer); +} + +void JfrTypeManager::write_type_set_for_unloaded_classes() const { + assert(SafepointSynchronize::is_at_safepoint(), "invariant"); + JfrCheckpointWriter writer(false, true, Thread::current()); + ClassUnloadTypeSet class_unload_set; + class_unload_set.serialize(writer); +} + +void JfrTypeManager::create_thread_checkpoint(JavaThread* jt) const { + assert(jt != NULL, "invariant"); + JfrThreadConstant type_thread(jt); + JfrCheckpointWriter writer(false, true, jt); + writer.write_type(TYPE_THREAD); + type_thread.serialize(writer); + // create and install a checkpoint blob + jt->jfr_thread_local()->set_thread_checkpoint(writer.checkpoint_blob()); + assert(jt->jfr_thread_local()->has_thread_checkpoint(), "invariant"); +} + +void JfrTypeManager::write_thread_checkpoint(JavaThread* jt) const { + assert(jt != NULL, "JavaThread is NULL!"); + ResourceMark rm(jt); + if (jt->jfr_thread_local()->has_thread_checkpoint()) { + JfrCheckpointWriter writer(false, false, jt); + jt->jfr_thread_local()->thread_checkpoint()->write(writer); + } else { + JfrThreadConstant type_thread(jt); + JfrCheckpointWriter writer(false, true, jt); + writer.write_type(TYPE_THREAD); + type_thread.serialize(writer); + } +} + +#ifdef ASSERT +static void assert_not_registered_twice(JfrTypeId id, JfrTypeManager::List& list) { + const JfrTypeManager::Iterator iter(list); + while (iter.has_next()) { + assert(iter.next()->id() != id, "invariant"); + } +} +#endif + +bool JfrTypeManager::register_serializer(JfrTypeId id, bool require_safepoint, bool permit_cache, JfrSerializer* cs) { + assert(cs != NULL, "invariant"); + JfrSerializerRegistration* const registration = new JfrSerializerRegistration(id, permit_cache, cs); + if (registration == NULL) { + delete cs; + return false; + } + if (require_safepoint) { + assert(!_safepoint_types.in_list(registration), "invariant"); + DEBUG_ONLY(assert_not_registered_twice(id, _safepoint_types);) + _safepoint_types.prepend(registration); + } + else { + assert(!_types.in_list(registration), "invariant"); + DEBUG_ONLY(assert_not_registered_twice(id, _types);) + _types.prepend(registration); + } + return true; +} + +bool JfrTypeManager::initialize() { + // register non-safepointing type serialization + for (size_t i = 0; i < 16; ++i) { + switch (i) { + case 0: register_serializer(TYPE_FLAGVALUEORIGIN, false, true, new FlagValueOriginConstant()); break; + case 1: register_serializer(TYPE_INFLATECAUSE, false, true, new MonitorInflateCauseConstant()); break; + case 2: register_serializer(TYPE_GCCAUSE, false, true, new GCCauseConstant()); break; + case 3: register_serializer(TYPE_GCNAME, false, true, new GCNameConstant()); break; + case 4: register_serializer(TYPE_GCWHEN, false, true, new GCWhenConstant()); break; + case 5: register_serializer(TYPE_G1HEAPREGIONTYPE, false, true, new G1HeapRegionTypeConstant()); break; + case 6: register_serializer(TYPE_GCTHRESHOLDUPDATER, false, true, new GCThresholdUpdaterConstant()); break; + case 7: register_serializer(TYPE_METADATATYPE, false, true, new MetadataTypeConstant()); break; + case 8: register_serializer(TYPE_METASPACEOBJECTTYPE, false, true, new MetaspaceObjectTypeConstant()); break; + case 9: register_serializer(TYPE_G1YCTYPE, false, true, new G1YCTypeConstant()); break; + case 10: register_serializer(TYPE_REFERENCETYPE, false, true, new ReferenceTypeConstant()); break; + case 11: register_serializer(TYPE_NARROWOOPMODE, false, true, new NarrowOopModeConstant()); break; + case 12: register_serializer(TYPE_COMPILERPHASETYPE, false, true, new CompilerPhaseTypeConstant()); break; + case 13: register_serializer(TYPE_CODEBLOBTYPE, false, true, new CodeBlobTypeConstant()); break; + case 14: register_serializer(TYPE_VMOPERATIONTYPE, false, true, new VMOperationTypeConstant()); break; + case 15: register_serializer(TYPE_THREADSTATE, false, true, new ThreadStateConstant()); break; + default: + guarantee(false, "invariant"); + } + } + + // register safepointing type serialization + for (size_t i = 0; i < 2; ++i) { + switch (i) { + case 0: register_serializer(TYPE_THREADGROUP, true, false, new JfrThreadGroupConstant()); break; + case 1: register_serializer(TYPE_THREAD, true, false, new JfrThreadConstantSet()); break; + default: + guarantee(false, "invariant"); + } + } + return true; +} + + diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeManager.hpp b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeManager.hpp new file mode 100644 index 00000000000..7aa111f6c09 --- /dev/null +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeManager.hpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +#ifndef SHARE_VM_JFR_CHECKPOINT_TYPES_JFRTYPEMANAGER_HPP +#define SHARE_VM_JFR_CHECKPOINT_TYPES_JFRTYPEMANAGER_HPP + +#include "jfr/metadata/jfrSerializer.hpp" +#include "jfr/utilities/jfrAllocation.hpp" +#include "jfr/utilities/jfrDoublyLinkedList.hpp" +#include "jfr/utilities/jfrIterator.hpp" + +class JfrSerializerRegistration : public JfrCHeapObj { + private: + JfrSerializerRegistration* _next; + JfrSerializerRegistration* _prev; + JfrSerializer* _serializer; + mutable JfrCheckpointBlobHandle _cache; + JfrTypeId _id; + bool _permit_cache; + + public: + JfrSerializerRegistration(JfrTypeId id, bool permit_cache, JfrSerializer* serializer); + ~JfrSerializerRegistration(); + JfrSerializerRegistration* next() const; + void set_next(JfrSerializerRegistration* next); + JfrSerializerRegistration* prev() const; + void set_prev(JfrSerializerRegistration* prev); + void invoke_serializer(JfrCheckpointWriter& writer) const; + JfrTypeId id() const; +}; + +class JfrTypeManager : public JfrCHeapObj { + friend class JfrCheckpointManager; + public: + typedef JfrDoublyLinkedList List; + typedef StopOnNullIterator Iterator; + private: + List _types; + List _safepoint_types; + + ~JfrTypeManager(); + bool initialize(); + size_t number_of_registered_types() const; + void write_types(JfrCheckpointWriter& writer) const; + void write_safepoint_types(JfrCheckpointWriter& writer) const; + void write_type_set() const; + void write_type_set_for_unloaded_classes() const; + void create_thread_checkpoint(JavaThread* jt) const; + void write_thread_checkpoint(JavaThread* jt) const; + bool register_serializer(JfrTypeId id, bool require_safepoint, bool permit_cache, JfrSerializer* serializer); +}; +#endif // SHARE_VM_JFR_CHECKPOINT_TYPES_JFRTYPEMANAGER_HPP diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.cpp b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.cpp new file mode 100644 index 00000000000..7da04514058 --- /dev/null +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.cpp @@ -0,0 +1,988 @@ +/* + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "classfile/classLoaderData.inline.hpp" +#include "classfile/javaClasses.inline.hpp" +#include "classfile/moduleEntry.hpp" +#include "classfile/packageEntry.hpp" +#include "classfile/symbolTable.hpp" +#include "classfile/systemDictionary.hpp" +#include "jfr/jfr.hpp" +#include "jfr/jni/jfrGetAllEventClasses.hpp" +#include "jfr/recorder/checkpoint/jfrCheckpointWriter.hpp" +#include "jfr/recorder/checkpoint/types/jfrTypeSet.hpp" +#include "jfr/recorder/checkpoint/types/jfrTypeSetUtils.hpp" +#include "jfr/recorder/checkpoint/types/jfrTypeSetWriter.hpp" +#include "jfr/recorder/checkpoint/types/traceid/jfrTraceId.inline.hpp" +#include "jfr/recorder/storage/jfrBuffer.hpp" +#include "jfr/utilities/jfrHashtable.hpp" +#include "jfr/utilities/jfrTypes.hpp" +#include "memory/iterator.hpp" +#include "memory/resourceArea.hpp" +#include "oops/instanceKlass.hpp" +#include "oops/objArrayKlass.hpp" +#include "oops/oop.inline.hpp" +#include "memory/resourceArea.hpp" +#include "utilities/accessFlags.hpp" + +// incremented on each checkpoint +static u8 checkpoint_id = 0; + +// creates a unique id by combining a checkpoint relative symbol id (2^24) +// with the current checkpoint id (2^40) +#define CREATE_SYMBOL_ID(sym_id) (((u8)((checkpoint_id << 24) | sym_id))) + +typedef const Klass* KlassPtr; +typedef const PackageEntry* PkgPtr; +typedef const ModuleEntry* ModPtr; +typedef const ClassLoaderData* CldPtr; +typedef const Method* MethodPtr; +typedef const Symbol* SymbolPtr; +typedef const JfrSymbolId::SymbolEntry* SymbolEntryPtr; +typedef const JfrSymbolId::CStringEntry* CStringEntryPtr; + +static traceid module_id(PkgPtr pkg) { + assert(pkg != NULL, "invariant"); + ModPtr module_entry = pkg->module(); + return module_entry != NULL && module_entry->is_named() ? TRACE_ID(module_entry) : 0; +} + +static traceid package_id(KlassPtr klass) { + assert(klass != NULL, "invariant"); + PkgPtr pkg_entry = klass->package(); + return pkg_entry == NULL ? 0 : TRACE_ID(pkg_entry); +} + +static traceid cld_id(CldPtr cld) { + assert(cld != NULL, "invariant"); + return cld->is_anonymous() ? 0 : TRACE_ID(cld); +} + +static void tag_leakp_klass_artifacts(KlassPtr k, bool class_unload) { + assert(k != NULL, "invariant"); + PkgPtr pkg = k->package(); + if (pkg != NULL) { + tag_leakp_artifact(pkg, class_unload); + ModPtr module = pkg->module(); + if (module != NULL) { + tag_leakp_artifact(module, class_unload); + } + } + CldPtr cld = k->class_loader_data(); + assert(cld != NULL, "invariant"); + if (!cld->is_anonymous()) { + tag_leakp_artifact(cld, class_unload); + } +} + +class TagLeakpKlassArtifact { + bool _class_unload; + public: + TagLeakpKlassArtifact(bool class_unload) : _class_unload(class_unload) {} + bool operator()(KlassPtr klass) { + if (_class_unload) { + if (LEAKP_USED_THIS_EPOCH(klass)) { + tag_leakp_klass_artifacts(klass, _class_unload); + } + } else { + if (LEAKP_USED_PREV_EPOCH(klass)) { + tag_leakp_klass_artifacts(klass, _class_unload); + } + } + return true; + } +}; + +/* + * In C++03, functions used as template parameters must have external linkage; + * this restriction was removed in C++11. Change back to "static" and + * rename functions when C++11 becomes available. + * + * The weird naming is an effort to decrease the risk of name clashes. + */ + +int write__artifact__klass(JfrCheckpointWriter* writer, JfrArtifactSet* artifacts, const void* k) { + assert(writer != NULL, "invariant"); + assert(artifacts != NULL, "invariant"); + assert(k != NULL, "invariant"); + KlassPtr klass = (KlassPtr)k; + traceid pkg_id = 0; + KlassPtr theklass = klass; + if (theklass->is_objArray_klass()) { + const ObjArrayKlass* obj_arr_klass = ObjArrayKlass::cast(klass); + theklass = obj_arr_klass->bottom_klass(); + } + if (theklass->is_instance_klass()) { + pkg_id = package_id(theklass); + } else { + assert(theklass->is_typeArray_klass(), "invariant"); + } + const traceid symbol_id = artifacts->mark(klass); + assert(symbol_id > 0, "need to have an address for symbol!"); + writer->write(TRACE_ID(klass)); + writer->write(cld_id(klass->class_loader_data())); + writer->write((traceid)CREATE_SYMBOL_ID(symbol_id)); + writer->write(pkg_id); + writer->write((s4)klass->access_flags().get_flags()); + return 1; +} + +typedef LeakPredicate LeakKlassPredicate; +typedef JfrPredicatedArtifactWriterImplHost LeakKlassWriterImpl; +typedef JfrArtifactWriterHost LeakKlassWriter; +typedef JfrArtifactWriterImplHost KlassWriterImpl; +typedef JfrArtifactWriterHost KlassWriter; + +int write__artifact__method(JfrCheckpointWriter* writer, JfrArtifactSet* artifacts, const void* m) { + assert(writer != NULL, "invariant"); + assert(artifacts != NULL, "invariant"); + assert(m != NULL, "invariant"); + MethodPtr method = (MethodPtr)m; + const traceid method_name_symbol_id = artifacts->mark(method->name()); + assert(method_name_symbol_id > 0, "invariant"); + const traceid method_sig_symbol_id = artifacts->mark(method->signature()); + assert(method_sig_symbol_id > 0, "invariant"); + KlassPtr klass = method->method_holder(); + assert(klass != NULL, "invariant"); + assert(METHOD_USED_ANY_EPOCH(klass), "invariant"); + writer->write((u8)METHOD_ID(klass, method)); + writer->write((u8)TRACE_ID(klass)); + writer->write((u8)CREATE_SYMBOL_ID(method_name_symbol_id)); + writer->write((u8)CREATE_SYMBOL_ID(method_sig_symbol_id)); + writer->write((u2)method->access_flags().get_flags()); + writer->write(const_cast(method)->is_hidden() ? (u1)1 : (u1)0); + return 1; +} + +typedef JfrArtifactWriterImplHost MethodWriterImplTarget; +typedef JfrArtifactWriterHost MethodWriterImpl; + +int write__artifact__package(JfrCheckpointWriter* writer, JfrArtifactSet* artifacts, const void* p) { + assert(writer != NULL, "invariant"); + assert(artifacts != NULL, "invariant"); + assert(p != NULL, "invariant"); + PkgPtr pkg = (PkgPtr)p; + Symbol* const pkg_name = pkg->name(); + const traceid package_name_symbol_id = pkg_name != NULL ? artifacts->mark(pkg_name) : 0; + assert(package_name_symbol_id > 0, "invariant"); + writer->write((traceid)TRACE_ID(pkg)); + writer->write((traceid)CREATE_SYMBOL_ID(package_name_symbol_id)); + writer->write(module_id(pkg)); + writer->write((bool)pkg->is_exported()); + return 1; +} + +typedef LeakPredicate LeakPackagePredicate; +int _compare_pkg_ptr_(PkgPtr const& lhs, PkgPtr const& rhs) { return lhs > rhs ? 1 : (lhs < rhs) ? -1 : 0; } +typedef UniquePredicate PackagePredicate; +typedef JfrPredicatedArtifactWriterImplHost LeakPackageWriterImpl; +typedef JfrPredicatedArtifactWriterImplHost PackageWriterImpl; +typedef JfrArtifactWriterHost LeakPackageWriter; +typedef JfrArtifactWriterHost PackageWriter; + +int write__artifact__module(JfrCheckpointWriter* writer, JfrArtifactSet* artifacts, const void* m) { + assert( m != NULL, "invariant"); + ModPtr entry = (ModPtr)m; + Symbol* const module_name = entry->name(); + const traceid module_name_symbol_id = module_name != NULL ? artifacts->mark(module_name) : 0; + Symbol* const module_version = entry->version(); + const traceid module_version_symbol_id = module_version != NULL ? artifacts->mark(module_version) : 0; + Symbol* const module_location = entry->location(); + const traceid module_location_symbol_id = module_location != NULL ? artifacts->mark(module_location) : 0; + writer->write((traceid)TRACE_ID(entry)); + writer->write(module_name_symbol_id == 0 ? (traceid)0 : (traceid)CREATE_SYMBOL_ID(module_name_symbol_id)); + writer->write(module_version_symbol_id == 0 ? (traceid)0 : (traceid)CREATE_SYMBOL_ID(module_version_symbol_id)); + writer->write(module_location_symbol_id == 0 ? (traceid)0 : (traceid)CREATE_SYMBOL_ID(module_location_symbol_id)); + writer->write(cld_id(entry->loader_data())); + return 1; +} + +typedef LeakPredicate LeakModulePredicate; +int _compare_mod_ptr_(ModPtr const& lhs, ModPtr const& rhs) { return lhs > rhs ? 1 : (lhs < rhs) ? -1 : 0; } +typedef UniquePredicate ModulePredicate; +typedef JfrPredicatedArtifactWriterImplHost LeakModuleWriterImpl; +typedef JfrPredicatedArtifactWriterImplHost ModuleWriterImpl; +typedef JfrArtifactWriterHost LeakModuleWriter; +typedef JfrArtifactWriterHost ModuleWriter; + +int write__artifact__classloader(JfrCheckpointWriter* writer, JfrArtifactSet* artifacts, const void* c) { + assert(c != NULL, "invariant"); + CldPtr cld = (CldPtr)c; + assert(!cld->is_anonymous(), "invariant"); + const traceid cld_id = TRACE_ID(cld); + // class loader type + const Klass* class_loader_klass = cld->class_loader_klass(); + if (class_loader_klass == NULL) { + // (primordial) boot class loader + writer->write(cld_id); // class loader instance id + writer->write((traceid)0); // class loader type id (absence of) + writer->write((traceid)CREATE_SYMBOL_ID(1)); // 1 maps to synthetic name -> "boot" + } else { + Symbol* symbol_name = cld->class_loader_name(); + const traceid symbol_name_id = symbol_name != NULL ? artifacts->mark(symbol_name) : 0; + writer->write(cld_id); // class loader instance id + writer->write(TRACE_ID(class_loader_klass)); // class loader type id + writer->write(symbol_name_id == 0 ? (traceid)0 : + (traceid)CREATE_SYMBOL_ID(symbol_name_id)); // class loader instance name + } + return 1; +} + +typedef LeakPredicate LeakCldPredicate; +int _compare_cld_ptr_(CldPtr const& lhs, CldPtr const& rhs) { return lhs > rhs ? 1 : (lhs < rhs) ? -1 : 0; } +typedef UniquePredicate CldPredicate; +typedef JfrPredicatedArtifactWriterImplHost LeakCldWriterImpl; +typedef JfrPredicatedArtifactWriterImplHost CldWriterImpl; +typedef JfrArtifactWriterHost LeakCldWriter; +typedef JfrArtifactWriterHost CldWriter; + +typedef const JfrSymbolId::SymbolEntry* SymbolEntryPtr; + +static int write__artifact__symbol__entry__(JfrCheckpointWriter* writer, + SymbolEntryPtr entry) { + assert(writer != NULL, "invariant"); + assert(entry != NULL, "invariant"); + ResourceMark rm; + writer->write(CREATE_SYMBOL_ID(entry->id())); + writer->write(entry->value()->as_C_string()); + return 1; +} + +int write__artifact__symbol__entry(JfrCheckpointWriter* writer, JfrArtifactSet* artifacts, const void* e) { + assert(e != NULL, "invariant"); + return write__artifact__symbol__entry__(writer, (SymbolEntryPtr)e); +} + +typedef JfrArtifactWriterImplHost SymbolEntryWriterImpl; +typedef JfrArtifactWriterHost SymbolEntryWriter; + +typedef const JfrSymbolId::CStringEntry* CStringEntryPtr; + +static int write__artifact__cstring__entry__(JfrCheckpointWriter* writer, CStringEntryPtr entry) { + assert(writer != NULL, "invariant"); + assert(entry != NULL, "invariant"); + writer->write(CREATE_SYMBOL_ID(entry->id())); + writer->write(entry->value()); + return 1; +} + +int write__artifact__cstring__entry(JfrCheckpointWriter* writer, JfrArtifactSet* artifacts, const void* e) { + assert(e != NULL, "invariant"); + return write__artifact__cstring__entry__(writer, (CStringEntryPtr)e); +} + +typedef JfrArtifactWriterImplHost CStringEntryWriterImpl; +typedef JfrArtifactWriterHost CStringEntryWriter; + +int write__artifact__klass__symbol(JfrCheckpointWriter* writer, JfrArtifactSet* artifacts, const void* k) { + assert(writer != NULL, "invariant"); + assert(artifacts != NULL, "invaiant"); + assert(k != NULL, "invariant"); + const InstanceKlass* const ik = (const InstanceKlass*)k; + if (ik->is_anonymous()) { + CStringEntryPtr entry = + artifacts->map_cstring(JfrSymbolId::anonymous_klass_name_hash_code(ik)); + assert(entry != NULL, "invariant"); + return write__artifact__cstring__entry__(writer, entry); + } + + SymbolEntryPtr entry = artifacts->map_symbol(JfrSymbolId::regular_klass_name_hash_code(ik)); + return write__artifact__symbol__entry__(writer, entry); +} + +int _compare_traceid_(const traceid& lhs, const traceid& rhs) { + return lhs > rhs ? 1 : (lhs < rhs) ? -1 : 0; +} + +template