diff --git a/build.go b/build.go index 4df72ac..8ede01f 100644 --- a/build.go +++ b/build.go @@ -127,7 +127,7 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { if IsLaunchContribution(jrePlanEntry.Metadata) { helpers := []string{"active-processor-count", "java-opts", "jvm-heap", "link-local-dns", "memory-calculator", - "openssl-certificate-loader", "security-providers-configurer", "jmx"} + "openssl-certificate-loader", "security-providers-configurer", "jmx", "nmt"} if IsBeforeJava9(depJRE.Version) { helpers = append(helpers, "security-providers-classpath-8") diff --git a/build_test.go b/build_test.go index ec396ba..3e0d285 100644 --- a/build_test.go +++ b/build_test.go @@ -113,6 +113,7 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { "openssl-certificate-loader", "security-providers-configurer", "jmx", + "nmt", "security-providers-classpath-8", "debug-8", })) @@ -143,6 +144,7 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { "openssl-certificate-loader", "security-providers-configurer", "jmx", + "nmt", "security-providers-classpath-9", "debug-9", })) diff --git a/cmd/helper/main.go b/cmd/helper/main.go index c2c804b..f67f57c 100644 --- a/cmd/helper/main.go +++ b/cmd/helper/main.go @@ -54,6 +54,7 @@ func main() { d8 = helper.Debug8{Logger: l} d9 = helper.Debug9{Logger: l} jm = helper.JMX{Logger: l} + n = helper.NMT{Logger: l} ) file := "/etc/resolv.conf" @@ -75,6 +76,7 @@ func main() { "debug-8": d8, "debug-9": d9, "jmx": jm, + "nmt": n, }) }) } diff --git a/helper/init_test.go b/helper/init_test.go index 4ef670e..df360bf 100644 --- a/helper/init_test.go +++ b/helper/init_test.go @@ -37,5 +37,6 @@ func TestUnit(t *testing.T) { suite("Debug8", testDebug8) suite("Debug9", testDebug9) suite("JMX", testJMX) + suite("NMT", testNMT) suite.Run(t) } diff --git a/helper/nmt.go b/helper/nmt.go new file mode 100644 index 0000000..70f2783 --- /dev/null +++ b/helper/nmt.go @@ -0,0 +1,37 @@ +package helper + +import ( + "fmt" + "os" + "strings" + + "github.com/paketo-buildpacks/libpak/bard" +) + +type NMT struct { + Logger bard.Logger +} + +func (n NMT) Execute() (map[string]string, error) { + + if s, ok := os.LookupEnv("BPL_JAVA_NMT_ENABLED"); ok && strings.ToLower(s) == "false" { + n.Logger.Info("Disabling Java Native Memory Tracking") + return nil, nil + } + level := "summary" + if s, ok := os.LookupEnv("BPL_JAVA_NMT_LEVEL"); ok && strings.ToLower(s) == "detail" { + level = "detail" + } + + n.Logger.Info("Enabling Java Native Memory Tracking") + var values []string + if s, ok := os.LookupEnv("JAVA_TOOL_OPTIONS"); ok { + values = append(values, s) + } + values = append(values, "-XX:+UnlockDiagnosticVMOptions", fmt.Sprintf("-XX:NativeMemoryTracking=%s", level), "-XX:+PrintNMTStatistics") + + // NMT_LEVEL_1 Required for Java Native Memory Tracking to work due to bug which is not fixed until Java v18 (https://bugs.openjdk.java.net/browse/JDK-8256844) + // '1' = PID of Java process in the container. Value for NMT level should match that passed to '-XX:NativeMemoryTracking' in the NMT helper. + return map[string]string{"NMT_LEVEL_1": level, "JAVA_TOOL_OPTIONS": strings.Join(values, " ")}, nil + +} diff --git a/helper/nmt_test.go b/helper/nmt_test.go new file mode 100644 index 0000000..f2f766f --- /dev/null +++ b/helper/nmt_test.go @@ -0,0 +1,69 @@ +package helper_test + +import ( + "os" + "testing" + + . "github.com/onsi/gomega" + "github.com/paketo-buildpacks/libjvm/helper" + "github.com/sclevine/spec" +) + +func testNMT(t *testing.T, context spec.G, it spec.S) { + var ( + Expect = NewWithT(t).Expect + + n = helper.NMT{} + ) + + it("returns if $BPL_JAVA_NMT_ENABLED is set to false", func() { + Expect(os.Setenv("BPL_JAVA_NMT_ENABLED", "false")).To(Succeed()) + Expect(n.Execute()).To(BeNil()) + }) + + context("$BPL_JAVA_NMT_ENABLED", func() { + it.Before(func() { + Expect(os.Setenv("BPL_JAVA_NMT_ENABLED", "true")).To(Succeed()) + }) + + it.After(func() { + Expect(os.Unsetenv("BPL_JAVA_NMT_ENABLED")).To(Succeed()) + Expect(os.Unsetenv("NMT_LEVEL_1")).To(Succeed()) + }) + + it("contributes configuration for summary level", func() { + + Expect(os.Setenv("BPL_JAVA_NMT_LEVEL", "summary")).To(Succeed()) + Expect(n.Execute()).To(Equal(map[string]string{"NMT_LEVEL_1": "summary", + "JAVA_TOOL_OPTIONS": "-XX:+UnlockDiagnosticVMOptions -XX:NativeMemoryTracking=summary -XX:+PrintNMTStatistics", + })) + Expect(os.Unsetenv("BPL_JAVA_NMT_LEVEL")).To(Succeed()) + }) + + it("contributes configuration for detail level", func() { + + Expect(os.Setenv("BPL_JAVA_NMT_LEVEL", "detail")).To(Succeed()) + Expect(n.Execute()).To(Equal(map[string]string{"NMT_LEVEL_1": "detail", + "JAVA_TOOL_OPTIONS": "-XX:+UnlockDiagnosticVMOptions -XX:NativeMemoryTracking=detail -XX:+PrintNMTStatistics", + })) + Expect(os.Unsetenv("BPL_JAVA_NMT_LEVEL")).To(Succeed()) + }) + + context("$JAVA_TOOL_OPTIONS", func() { + it.Before(func() { + Expect(os.Setenv("JAVA_TOOL_OPTIONS", "test-java-tool-options")).To(Succeed()) + }) + + it.After(func() { + Expect(os.Unsetenv("JAVA_TOOL_OPTIONS")).To(Succeed()) + }) + + it("contributes configuration appended to existing $JAVA_TOOL_OPTIONS - level defaults to summary", func() { + Expect(n.Execute()).To(Equal(map[string]string{"NMT_LEVEL_1": "summary", + "JAVA_TOOL_OPTIONS": "test-java-tool-options -XX:+UnlockDiagnosticVMOptions -XX:NativeMemoryTracking=summary -XX:+PrintNMTStatistics", + })) + }) + }) + }) + +}