diff --git a/README.md b/README.md
index 8eb07a199e..67b7d1fb53 100644
--- a/README.md
+++ b/README.md
@@ -80,6 +80,7 @@ To learn how to configure various properties of the buildpack, follow the "Confi
* [Dynatrace SaaS/Managed OneAgent](docs/framework-dynatrace_one_agent.md) ([Configuration](docs/framework-dynatrace_one_agent.md#configuration))
* [Google Stackdriver Debugger](docs/framework-google_stackdriver_debugger.md) ([Configuration](docs/framework-google_stackdriver_debugger.md#configuration))
* [Introscope Agent](docs/framework-introscope_agent.md) ([Configuration](docs/framework-introscope_agent.md#configuration))
+ * [Java Memory Assistant](docs/framework-java_memory_assistant.md) ([Configuration](docs/framework-java_memory_assistant.md#configuration))
* [Java Options](docs/framework-java_opts.md) ([Configuration](docs/framework-java_opts.md#configuration))
* [JRebel Agent](docs/framework-jrebel_agent.md) ([Configuration](docs/framework-jrebel_agent.md#configuration))
* [JMX](docs/framework-jmx.md) ([Configuration](docs/framework-jmx.md#configuration))
diff --git a/config/components.yml b/config/components.yml
index 6d56bc7aff..56e92df82a 100644
--- a/config/components.yml
+++ b/config/components.yml
@@ -45,6 +45,7 @@ frameworks:
- "JavaBuildpack::Framework::DynatraceOneAgent"
- "JavaBuildpack::Framework::GoogleStackdriverDebugger"
# - "JavaBuildpack::Framework::IntroscopeAgent"
+ - "JavaBuildpack::Framework::JavaMemoryAssistant"
- "JavaBuildpack::Framework::Jmx"
- "JavaBuildpack::Framework::JrebelAgent"
- "JavaBuildpack::Framework::LunaSecurityProvider"
diff --git a/config/java_memory_assistant.yml b/config/java_memory_assistant.yml
new file mode 100644
index 0000000000..21fd798c19
--- /dev/null
+++ b/config/java_memory_assistant.yml
@@ -0,0 +1,36 @@
+# Cloud Foundry Java Buildpack
+# Copyright 2013-2017 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+---
+enabled: false
+agent:
+ version: 0.+
+ repository_root: https://raw.githubusercontent.com/SAP/java-memory-assistant/repository
+ heap_dump_folder:
+ check_interval: 5s
+ max_frequency: 1/1m
+ log_level:
+ thresholds:
+ heap:
+ code_cache:
+ metaspace:
+ perm_gen:
+ compressed_class:
+ eden:
+ survivor:
+ old_gen: ">600MB"
+clean_up:
+ version: 0.+
+ repository_root: https://raw.githubusercontent.com/SAP/java-memory-assistant-tools/repository-cu
+ max_dump_count: 1
\ No newline at end of file
diff --git a/docs/framework-java_memory_assistant.md b/docs/framework-java_memory_assistant.md
new file mode 100644
index 0000000000..7e2ad936de
--- /dev/null
+++ b/docs/framework-java_memory_assistant.md
@@ -0,0 +1,115 @@
+# Java Memory Assistant Framework
+The Java Memory Assistant is a Java agent (as in `-javaagent`) that creats heap dumps of your application automatically based on preconfigured conditions of memory usage.
+The heap dumps created by the Java Memory Assistant can be analyzed using Java memory profilers that support the `.hprof` format (i.e., virtually all profilers).
+
+
+
+ | Detection Criterion | enabled set in the config/java_memory_assistant.yml |
+
+
+ | Tags | java-memory-assistant=<version> |
+
+
+Tags are printed to standard output by the buildpack detect script.
+
+## Configuration
+For general information on configuring the buildpack, including how to specify configuration values through environment variables, refer to [Configuration and Extension][].
+
+The framework can be configured by modifying the [``config/java_memory_assistant.yml``][] file in the buildpack fork.
+
+| Name | Description
+| ---- | -----------
+| `enabled` | Whether to enable the Java Memory Assistant framework. By default the agent is turned off.
+| `agent.heap_dump_folder` | The folder on the container's filesystem where heap dumps are created. Default value: `$PWD`
+| `agent.thresholds.` | This configuration allows to define thresholds for every memory area of the JVM. Thresholds can be defined in absolute percentages, e.g., `75%` creates a heap dump at 75% of the selected memory area. It is also possible to specify relative increases and decreases of memory usage: for example, `+5%/2m` will triggera heap dumpo if the particular memory area has increased by `5%` or more over the last two minutes. See below to check which memory areas are supported. Since version `0.3.0`, thresholds can also be specified in terms of absolute values, e.g., `>400MB` (more than 400 MB) or `<=30KB` (30 KB or less); supported memory size units are `KB`, `MB` and `GB`.
+| `agent.check_interval` | The interval between checks. Examples: `1s` (once a second), `3m` (every three minutes), `1h` (once every hour). Default: `5s` (check every five seconds).
+| `agent.max_frequency` | Maximum amount of heap dumps that the Java Memory Assistant is allowed to create in a given amount of time. Examples: `1/30s` (no more than one heap dump every thirty seconds), `2/3m` (up to two heap dumps every three minutes), `1/2h` (one heap dump every two hours). The time interval is checked every time one heap dump *should* be created (based on the specified thresholds), and compared with the timestamps of the previously created heap dumps to make sure that the maximum frequency is not exceeded. Default: `1/1m` (one heap dump per minute). |
+| `agent.log_level` | The log level used by the Java Memory Assistant. Supported values are the same as the Java buildpack's: `DEBUG`, `WARN`, `INFO`, `ERROR` and `FATAL` (the latter is equivalent to `ERROR`). If the `agent.log_level` is not specified, the Java buildpack's log level will be used. |
+| `clean_up.max_dump_count` | Maximum amount of heap dumps that can be stored in the filesystem of the container; when the creation of a new heap dump would cause the threshold to be surpassed, the oldest heap dumps are removed from the file system. Default value: `1` |
+
+### Heap Dump Names
+
+The heap dump filenames will be generated according to the following name pattern:
+
+`-%ts:yyyyMMdd'T'mmssSSSZ%-.hprof`
+
+The timestamp pattern `%ts:yyyyMMdd'T'mmssSSSZ%` is equivalent to the `%FT%T%z` pattern of [strftime](http://www.cplusplus.com/reference/ctime/strftime/) for [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601).
+
+### Supported Memory Areas
+
+| Memory Area | Property Name |
+|------------------------|------------------|
+| Heap | `heap` |
+| Code Cache | `code_cache` |
+| Metaspace | `metaspace` |
+| Compressed Class Space | `compressed_class` |
+| Eden | `eden` |
+| Survivor | `survivor` |
+| Old Generation | `old_gen` |
+
+The default values can be found in the [``config/java_memory_assistant.yml``][] file.
+
+### Examples
+
+Enable the Java Memory Assistant with its default settings:
+
+```yaml
+JBP_CONFIG_JAVA_MEMORY_ASSISTANT: '{enabled : true}'
+```
+
+Create heap dumps when the old generation memory pool exceeds 800 MB:
+
+```yaml
+JBP_CONFIG_JAVA_MEMORY_ASSISTANT: '{enabled : true, agent: { thresholds : { old_gen : ">800MB" } } }'
+```
+
+Create heap dumps when the old generation grows by more than 20% in two minutes:
+
+```yaml
+JBP_CONFIG_JAVA_MEMORY_ASSISTANT: '{enabled : true, agent : { thresholds : { old_gen : +20%/2m } } }'
+```
+
+### What are the right thresholds for your application?
+
+Well, it depends.
+The way applications behave in terms of memory management is a direct result of how they are implemented.
+This is much more then case when the applications are under heavy load.
+Thus, there is no "silver bullet" configuration that will serve all applications equally well, and Java Memory Assistant configurations should result from profiling the application under load and then encode the expected memory usage patterns (plus a margin upwards) to detect anomalies.
+
+Nevertheless, a memory area that tends to be particularly interesting to monitor is the so called "old generation" (`old_gen`).
+When instantiated, bjects in the Java heap are allocated in an area called `eden`.
+As garbage collections occur, objects that are not reclaimed become first "survivors" (and belong to the namesake `survivor` memory area) and then eventually become `old_gen`.
+In other words, `old_gen` objects are those that survived multiple garbage collections.
+In contrast, `eden` and `survivor` objects are collectively called "young generation".
+
+Application-wide singletons and pooled objects (threads, connections) are examples of "legitimate" `old_gen` candidates.
+But memory leaks, by their very nature or surviving multiple garbage collections, end up in `old_gen` too.
+Under load that is not too high for the application (and you should find out what it is with load tests and avoid it via rate limiting, e.g., using [route services](https://docs.cloudfoundry.org/services/route-services.html) in front of your application), Java code that allows the JVM to perform efficient memory management tends to have a rather consistent baseline of `old_gen` objects, with most objects being reclaimed as they are still young generation.
+That is, when the `old_gen` grows large with respect to the overall heap, this often signifies some sort of memory leak or, at the very least, suboptimal memory management.
+Notable exceptions to this rule of thumb are applications that use large local caches.
+
+### Making sure heap dumps can be created
+
+The Java Virtual Machine must create heap dumps on a file.
+Unless you are using a `volume service`, it pretty much means that, even if you are uploading the heap dump somewhere else, the heap dump must first land on the ephemeral disk of the container.
+Ephemeral disks have quotas and, if all the space is taken by heap dumps (even incomplete ones!), horrible things are bound to happen to your app.
+
+The maximum size of a heap dump depends on the maximum size of the heap of the Java Virtual Machine.
+Consider increasing the disk quota of your warden/garden container via the `cf scale -k [new size]` using as `new size` to the outcome of the following calculation:
+
+`[max heap size] * [max heap dump count] + 200MB`
+
+The aditional `200MB` is a rule-of-thumb, generous over-approximation of the amount of disk the buildpack and the application therein needs to run.
+If your application requires more filesystem than just a few tens of megabytes, you must increase the additional portion of the disk amount calculation accordingly.
+
+### Where to best store heap dumps?
+
+Heap dumps are created by the Java Virtual Machine on a file on the filesystem mounted by the garden container.
+Normally, the filesystem of a container is ephemeral.
+That is, if your app crashes or it is shut down, the filesystem of its container is gone with it and so are your heap dumps.
+
+To prevent heap dumps from "going down" with the container, you should consider storing them on a `volume service`.
+
+#### Container-mounted volumes
+
+If you are using a filesystem service that mounts persistent volumes to the container, it is enough to name one of the volume services `heap-dump` or tag one volume with `heap-dump`, and the path specified as the `heap_dump_folder` configuration will be resolved against `/-/-`.
\ No newline at end of file
diff --git a/lib/java_buildpack/framework/java_memory_assistant.rb b/lib/java_buildpack/framework/java_memory_assistant.rb
new file mode 100644
index 0000000000..1e5522ad73
--- /dev/null
+++ b/lib/java_buildpack/framework/java_memory_assistant.rb
@@ -0,0 +1,51 @@
+# Cloud Foundry Java Buildpack
+# Copyright 2013-2017 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'java_buildpack/component/modular_component'
+require 'java_buildpack/framework'
+require 'java_buildpack/framework/java_memory_assistant/agent'
+require 'java_buildpack/framework/java_memory_assistant/clean_up'
+require 'java_buildpack/framework/java_memory_assistant/heap_dump_folder'
+
+module JavaBuildpack
+ module Framework
+
+ # Encapsulates the integraton of the JavaMemoryAssistant.
+ class JavaMemoryAssistant < JavaBuildpack::Component::ModularComponent
+
+ protected
+
+ # (see JavaBuildpack::Component::ModularComponent#command)
+ def command
+ # Nothing to do here, the agent is initialized via java opts
+ end
+
+ # (see JavaBuildpack::Component::ModularComponent#sub_components)
+ def sub_components(context)
+ [
+ JavaMemoryAssistantAgent.new(sub_configuration_context(context, 'agent')),
+ JavaMemoryAssistantHeapDumpFolder.new(sub_configuration_context(context, 'agent')),
+ JavaMemoryAssistantCleanUp.new(sub_configuration_context(context, 'clean_up'))
+ ]
+ end
+
+ # (see JavaBuildpack::Component::ModularComponent#supports?)
+ def supports?
+ @configuration['enabled']
+ end
+
+ end
+ end
+end
diff --git a/lib/java_buildpack/framework/java_memory_assistant/agent.rb b/lib/java_buildpack/framework/java_memory_assistant/agent.rb
new file mode 100644
index 0000000000..363944d038
--- /dev/null
+++ b/lib/java_buildpack/framework/java_memory_assistant/agent.rb
@@ -0,0 +1,102 @@
+# Cloud Foundry Java Buildpack
+# Copyright 2013-2017 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'fileutils'
+require 'java_buildpack/component'
+require 'java_buildpack/component/versioned_dependency_component'
+require 'java_buildpack/component/droplet'
+require 'java_buildpack/component/environment_variables'
+require 'java_buildpack/framework'
+
+module JavaBuildpack
+ module Framework
+
+ # Encapsulates the integraton of the JavaMemoryAssistant to inject the agent in the JVM.
+ class JavaMemoryAssistantAgent < JavaBuildpack::Component::VersionedDependencyComponent
+
+ # (see JavaBuildpack::Component::BaseComponent#compile)
+ def compile
+ download_jar
+ @droplet.copy_resources
+ end
+
+ # (see JavaBuildpack::Component::BaseComponent#release)
+ def release
+ @droplet.java_opts.add_javaagent @droplet.sandbox + jar_name
+
+ @droplet.java_opts.add_system_property 'jma.enabled', 'true'
+ @droplet.java_opts.add_system_property 'jma.heap_dump_name', %("#{name_pattern}")
+
+ add_system_prop_if_config_present 'check_interval', 'jma.check_interval'
+ add_system_prop_if_config_present 'max_frequency', 'jma.max_frequency'
+
+ @droplet.java_opts.add_system_property 'jma.log_level', log_level
+
+ (@configuration['thresholds'] || {}).each do |key, value|
+ @droplet.java_opts.add_system_property "jma.thresholds.#{key}", value.to_s
+ end
+ end
+
+ protected
+
+ def supports?
+ true
+ end
+
+ # (see JavaBuildpack::Component::VersionedDependencyComponent#jar_name)
+ def jar_name
+ "java-memory-assistant-#{@version}.jar"
+ end
+
+ private
+
+ def name_pattern
+ # Double escaping quotes of doom. Nothing less would work.
+ %q(%env:CF_INSTANCE_INDEX%-%ts:yyyy-MM-dd'"'"'T'"'"'mm'"'"':'"'"'ss'"'"':'"'"'SSSZ%-) \
+ '%env:CF_INSTANCE_GUID[,8]%.hprof'
+ end
+
+ def add_system_prop_if_config_present(config_entry, system_property_name, quote_value = false)
+ return unless @configuration[config_entry]
+
+ config_value = @configuration[config_entry]
+ config_value = '"' + config_value + '"' if quote_value
+
+ @droplet.java_opts.add_system_property(system_property_name, config_value)
+ end
+
+ def log_level
+ actual_log_level = @configuration['log_level'] || ENV['JBP_LOG_LEVEL'] || 'ERROR'
+
+ mapped_log_level = log_level_mapping[actual_log_level.upcase]
+
+ raise "Invalid value of the 'log_level' property: '#{actual_log_level}'" unless mapped_log_level
+
+ mapped_log_level
+ end
+
+ def log_level_mapping
+ {
+ 'DEBUG' => 'DEBUG',
+ 'WARN' => 'WARNING',
+ 'INFO' => 'INFO',
+ 'ERROR' => 'ERROR',
+ 'FATAL' => 'ERROR'
+ }
+ end
+
+ end
+ end
+end
diff --git a/lib/java_buildpack/framework/java_memory_assistant/clean_up.rb b/lib/java_buildpack/framework/java_memory_assistant/clean_up.rb
new file mode 100644
index 0000000000..7bd24853aa
--- /dev/null
+++ b/lib/java_buildpack/framework/java_memory_assistant/clean_up.rb
@@ -0,0 +1,55 @@
+# Cloud Foundry Java Buildpack
+# Copyright 2013-2017 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'java_buildpack/component/versioned_dependency_component'
+require 'java_buildpack/component/droplet'
+require 'java_buildpack/framework'
+
+module JavaBuildpack
+ module Framework
+
+ # Encapsulates the integraton of the JavaMemoryAssistant to set up clean up of dumps.
+ class JavaMemoryAssistantCleanUp < JavaBuildpack::Component::VersionedDependencyComponent
+
+ # (see JavaBuildpack::Component::BaseComponent#compile)
+ def compile
+ return unless supports?
+
+ download_zip false
+
+ @droplet.copy_resources
+ end
+
+ # (see JavaBuildpack::Component::BaseComponent#release)
+ def release
+ return unless supports?
+
+ environment_variables = @droplet.environment_variables
+ environment_variables.add_environment_variable 'JMA_MAX_DUMP_COUNT', @configuration['max_dump_count'].to_s
+
+ @droplet.java_opts.add_system_property('jma.command.interpreter', '')
+ @droplet.java_opts.add_system_property('jma.execute.before', @droplet.sandbox + 'cleanup')
+ end
+
+ protected
+
+ # (see JavaBuildpack::Component::VersionedDependencyComponent#supports?)
+ def supports?
+ @configuration['max_dump_count'].to_i > 0
+ end
+
+ end
+ end
+end
diff --git a/lib/java_buildpack/framework/java_memory_assistant/heap_dump_folder.rb b/lib/java_buildpack/framework/java_memory_assistant/heap_dump_folder.rb
new file mode 100644
index 0000000000..1a2316d4c8
--- /dev/null
+++ b/lib/java_buildpack/framework/java_memory_assistant/heap_dump_folder.rb
@@ -0,0 +1,86 @@
+# Cloud Foundry Java Buildpack
+# Copyright 2013-2017 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'java_buildpack/component/base_component'
+require 'java_buildpack/component/droplet'
+require 'java_buildpack/framework'
+require 'java_buildpack/framework/java_memory_assistant'
+
+module JavaBuildpack
+ module Framework
+
+ # Encapsulates the integraton of the JavaMemoryAssistant to store generated heap dumps.
+ class JavaMemoryAssistantHeapDumpFolder < JavaBuildpack::Component::BaseComponent
+
+ # Creates an instance
+ #
+ # @param [Hash] context a collection of utilities used by the component
+ def initialize(context)
+ @logger = JavaBuildpack::Logging::LoggerFactory.instance.get_logger JavaMemoryAssistantHeapDumpFolder
+ super(context)
+ end
+
+ # (see JavaBuildpack::Component::BaseComponent#detect)
+ def detect
+ true
+ end
+
+ # (see JavaBuildpack::Component::BaseComponent#compile)
+ def compile
+ # Nothing to do
+ end
+
+ # (see JavaBuildpack::Component::BaseComponent#release)
+ def release
+ environment_variables = @droplet.environment_variables
+
+ heap_dump_folder = @configuration['heap_dump_folder'] || default_heap_dump_folder
+
+ # If there is a bound volume service, use the heap_dump_folder under the volume's path
+ service = find_heap_dump_volume_service
+ if service
+ volume_mount = service['volume_mounts'][0]
+ container_dir = volume_mount['container_dir']
+ mode = volume_mount['mode']
+
+ raise "Volume mounted under '#{container_dir}' not in write mode" unless mode.to_s.include? 'w'
+
+ heap_dump_folder = "#{container_dir}/#{heap_dump_folder}"
+ @logger.info { "Using volume service mounted under '#{container_dir}' to store heap dumps" }
+ end
+
+ # This is needed by the clean_up module
+ environment_variables.add_environment_variable 'JMA_HEAP_DUMP_FOLDER', heap_dump_folder.to_s
+ @droplet.java_opts.add_system_property 'jma.heap_dump_folder', "\"#{heap_dump_folder}\""
+ @logger.info { "Heap dumps will be stored under '#{heap_dump_folder}'" }
+ end
+
+ private
+
+ # Matcher for service names or tags associated with the Java Memory Assistant
+ FILTER = 'heap-dump'.freeze
+
+ def find_heap_dump_volume_service
+ @application.services.find_service FILTER
+ end
+
+ def default_heap_dump_folder
+ "#{@application.details['space_name']}-#{@application.details['space_id'][0...8]}/" \
+ "#{@application.details['application_name']}-#{@application.details['application_id'][0...8]}"
+ end
+
+ end
+ end
+end
diff --git a/spec/fixtures/stub-java-memory-assistant-cleanup.zip b/spec/fixtures/stub-java-memory-assistant-cleanup.zip
new file mode 100644
index 0000000000..b7a42c4d25
Binary files /dev/null and b/spec/fixtures/stub-java-memory-assistant-cleanup.zip differ
diff --git a/spec/java_buildpack/framework/java_memory_assistant/agent_spec.rb b/spec/java_buildpack/framework/java_memory_assistant/agent_spec.rb
new file mode 100644
index 0000000000..564d3a06ac
--- /dev/null
+++ b/spec/java_buildpack/framework/java_memory_assistant/agent_spec.rb
@@ -0,0 +1,223 @@
+# Cloud Foundry Java Buildpack
+# Copyright 2013-2017 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'spec_helper'
+require 'application_helper'
+require 'component_helper'
+require 'logging_helper'
+require 'java_buildpack/component/environment_variables'
+require 'java_buildpack/framework/java_memory_assistant/agent'
+
+describe JavaBuildpack::Framework::JavaMemoryAssistantAgent do
+ include_context 'application_helper'
+ include_context 'component_helper'
+ include_context 'logging_helper'
+
+ let(:vcap_application) do
+ {
+ 'instance_index' => '42',
+ 'instance_id' => '406beca7-7692-41f4-9482-f32ae0a1da93'
+ }
+ end
+
+ context do
+
+ let(:configuration) do
+ {
+ 'check_interval' => '5s',
+ 'max_frequency' => '1/1m',
+ 'thresholds' => {
+ 'heap' => 90,
+ 'old_gen' => 90
+ }
+ }
+ end
+
+ let(:version) { '1.2.3' }
+
+ it 'updates JAVA_OPTS with default values' do
+ component.release
+
+ expect(java_opts).to include('-javaagent:$PWD/.java-buildpack/java_memory_assistant_agent/' \
+ 'java-memory-assistant-1.2.3.jar')
+ expect(java_opts).to include('-Djma.enabled=true')
+
+ expect(java_opts).to include('-Djma.check_interval=5s')
+ expect(java_opts).to include('-Djma.max_frequency=1/1m')
+
+ expect(java_opts).to include('-Djma.thresholds.heap=90')
+ expect(java_opts).to include('-Djma.thresholds.old_gen=90')
+ end
+
+ end
+
+ context do
+ let(:configuration) do
+ {
+ 'check_interval' => '10m',
+ 'max_frequency' => '4/10h',
+ 'heap_dump_folder' => 'test/folder',
+ 'log_level' => 'DEBUG',
+ 'thresholds' => {
+ 'heap' => 60,
+ 'code_cache' => 30,
+ 'metaspace' => 5,
+ 'perm_gen' => 45.5,
+ 'eden' => 90,
+ 'survivor' => 95.5,
+ 'old_gen' => 30
+ }
+ }
+ end
+
+ let(:version) { '0.1.0' }
+
+ it 'updates JAVA_OPTS with configured values' do
+ component.release
+
+ expect(java_opts).to include('-javaagent:$PWD/.java-buildpack/java_memory_assistant_agent/' \
+ 'java-memory-assistant-0.1.0.jar')
+ expect(java_opts).to include('-Djma.enabled=true')
+ expect(java_opts).to include('-Djma.check_interval=10m')
+ expect(java_opts).to include('-Djma.max_frequency=4/10h')
+ expect(java_opts).to include('-Djma.log_level=DEBUG')
+ expect(java_opts).to include('-Djma.thresholds.heap=60')
+ expect(java_opts).to include('-Djma.thresholds.code_cache=30')
+ expect(java_opts).to include('-Djma.thresholds.metaspace=5')
+ expect(java_opts).to include('-Djma.thresholds.perm_gen=45.5')
+ expect(java_opts).to include('-Djma.thresholds.eden=90')
+ expect(java_opts).to include('-Djma.thresholds.survivor=95.5')
+ expect(java_opts).to include('-Djma.thresholds.old_gen=30')
+ end
+
+ end
+
+ context do
+ let(:configuration) do
+ {
+ 'log_level' => 'debug'
+ }
+ end
+
+ it 'maps log-level case-insensitive' do
+ component.release
+
+ expect(java_opts).to include('-Djma.log_level=DEBUG')
+ end
+
+ end
+
+ context do
+ let(:configuration) do
+ {
+ 'log_level' => 'WARN'
+ }
+ end
+
+ let(:version) { '0.1.0' }
+
+ it 'maps log-level WARN to WARNING' do
+ component.release
+
+ expect(java_opts).to include('-javaagent:$PWD/.java-buildpack/java_memory_assistant_agent/' \
+ 'java-memory-assistant-0.1.0.jar')
+
+ expect(java_opts).to include('-Djma.enabled=true')
+ expect(java_opts).to include('-Djma.log_level=WARNING')
+ end
+
+ end
+
+ context do
+ let(:configuration) do
+ {
+ 'log_level' => 'INFO'
+ }
+ end
+
+ let(:version) { '0.1.0' }
+
+ it 'maps log-level INFO to INFO' do
+ component.release
+
+ expect(java_opts).to include('-javaagent:$PWD/.java-buildpack/java_memory_assistant_agent/' \
+ 'java-memory-assistant-0.1.0.jar')
+
+ expect(java_opts).to include('-Djma.enabled=true')
+ expect(java_opts).to include('-Djma.log_level=INFO')
+ end
+
+ end
+
+ context do
+ let(:configuration) do
+ {
+ 'log_level' => 'ERROR'
+ }
+ end
+
+ it 'maps log-level ERROR to ERROR' do
+ component.release
+
+ expect(java_opts).to include('-Djma.log_level=ERROR')
+ end
+
+ end
+
+ context do
+ let(:configuration) do
+ {
+ 'log_level' => 'FATAL'
+ }
+ end
+
+ it 'maps log-level FATAL to ERROR' do
+ component.release
+
+ expect(java_opts).to include('-Djma.log_level=ERROR')
+ end
+
+ end
+
+ context do
+
+ let(:configuration) do
+ {}
+ end
+
+ it 'falls back on JBP log_level when no log_level specified via configuration',
+ :enable_log_file, log_level: 'WARN' do
+ component.release
+
+ expect(java_opts).to include('-Djma.log_level=WARNING')
+ end
+
+ end
+
+ context do
+ let(:configuration) do
+ {
+ 'log_level' => 'ciao'
+ }
+ end
+
+ it 'fails if log_level is not recognized' do
+ expect { component.release }.to raise_exception 'Invalid value of the \'log_level\'' \
+ ' property: \'ciao\''
+ end
+
+ end
+
+end
diff --git a/spec/java_buildpack/framework/java_memory_assistant/clean_up_spec.rb b/spec/java_buildpack/framework/java_memory_assistant/clean_up_spec.rb
new file mode 100644
index 0000000000..9aeb77a524
--- /dev/null
+++ b/spec/java_buildpack/framework/java_memory_assistant/clean_up_spec.rb
@@ -0,0 +1,97 @@
+# Cloud Foundry Java Buildpack
+# Copyright 2013-2017 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'spec_helper'
+require 'application_helper'
+require 'component_helper'
+require 'java_buildpack/framework/java_memory_assistant/clean_up'
+
+describe JavaBuildpack::Framework::JavaMemoryAssistantCleanUp do
+ include_context 'application_helper'
+ include_context 'component_helper'
+
+ let(:version) { '1.2.3' }
+
+ context do
+
+ let(:configuration) do
+ {
+ 'max_dump_count' => 1
+ }
+ end
+
+ it 'downloads and unpacks the cleanup command',
+ cache_fixture: 'stub-java-memory-assistant-cleanup.zip' do
+
+ component.compile
+
+ expect(sandbox + 'cleanup').to exist
+ end
+
+ end
+
+ context do
+
+ let(:configuration) do
+ {
+ 'max_dump_count' => 1
+ }
+ end
+
+ it 'configures clean up' do
+ component.release
+
+ expect(java_opts).to include('-Djma.command.interpreter=')
+ expect(java_opts).to include('-Djma.execute.before=$PWD/.java-buildpack/java_memory_assistant_clean_up/' \
+ 'cleanup')
+ end
+
+ end
+
+ context do
+
+ let(:configuration) do
+ {
+ 'max_dump_count' => 0
+ }
+ end
+
+ it 'does not configure clean up when max_dump_count is zero' do
+ component.release
+
+ expect(java_opts).not_to include('-Djma.command.interpreter=')
+ expect(java_opts).not_to include('-Djma.execute.before=$PWD/.java-buildpack/java_memory_assistant_clean_up/' \
+ 'cleanup')
+ end
+
+ end
+
+ context do
+
+ let(:configuration) do
+ {}
+ end
+
+ it 'does not configure clean up when max_dump_count is not set' do
+ component.release
+
+ expect(java_opts).not_to include('-Djma.command.interpreter=')
+ expect(java_opts).not_to include('-Djma.execute.before=$PWD/.java-buildpack/java_memory_assistant_clean_up/' \
+ 'cleanup')
+ end
+
+ end
+
+end
diff --git a/spec/java_buildpack/framework/java_memory_assistant/heap_dump_folder_spec.rb b/spec/java_buildpack/framework/java_memory_assistant/heap_dump_folder_spec.rb
new file mode 100644
index 0000000000..992b4eafd0
--- /dev/null
+++ b/spec/java_buildpack/framework/java_memory_assistant/heap_dump_folder_spec.rb
@@ -0,0 +1,131 @@
+# Cloud Foundry Java Buildpack
+# Copyright 2013-2017 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'spec_helper'
+require 'application_helper'
+require 'component_helper'
+require 'logging_helper'
+require 'java_buildpack/framework/java_memory_assistant/heap_dump_folder'
+
+describe JavaBuildpack::Framework::JavaMemoryAssistantHeapDumpFolder do
+ include_context 'application_helper'
+ include_context 'component_helper'
+ include_context 'logging_helper'
+
+ let(:logger) { described_class.instance.get_logger String }
+
+ context do
+ let(:vcap_application) do
+ {
+ 'space_name' => '1234567890',
+ 'space_id' => '0987654321',
+ 'application_name' => 'abcdefghi',
+ 'application_id' => 'ihgfedcba'
+ }
+ end
+
+ let(:configuration) do
+ {
+ 'heap_dump_folder' => nil
+ }
+ end
+
+ it 'uses the default for \'jma.heap_dump_folder\' if no value is specified', :enable_log_file, log_level: 'INFO' do
+
+ component.release
+
+ expect(java_opts).to include('-Djma.heap_dump_folder="1234567890-09876543/abcdefghi-ihgfedcb"')
+ expect(environment_variables).to include('JMA_HEAP_DUMP_FOLDER=1234567890-09876543/abcdefghi-ihgfedcb')
+
+ expect(log_contents).to match(%r{Heap dumps will be stored under '1234567890-09876543/abcdefghi-ihgfedcb'})
+ end
+
+ end
+
+ context do
+ let(:configuration) do
+ {
+ 'heap_dump_folder' => 'test/folder'
+ }
+ end
+
+ it 'adds \'jma.heap_dump_folder\' with verbatim value', :enable_log_file, log_level: 'INFO' do
+
+ component.release
+
+ expect(java_opts).to include('-Djma.heap_dump_folder="test/folder"')
+ expect(environment_variables).to include('JMA_HEAP_DUMP_FOLDER=test/folder')
+
+ expect(log_contents).to match(%r{Heap dumps will be stored under \'test/folder\'})
+ end
+
+ end
+
+ context do
+ let(:configuration) do
+ {
+ 'heap_dump_folder' => 'test/folder'
+ }
+ end
+
+ before do
+ allow(services).to receive(:find_service).with('heap-dump')
+ .and_return('volume_mounts' =>
+ [
+ {
+ 'container_dir' => '/my_volume',
+ 'mode' => 'rw'
+ }
+ ])
+ end
+
+ it 'adds \'jma.heap_dump_folder\' with volume container_dir as path root', :enable_log_file, log_level: 'INFO' do
+
+ component.release
+
+ expect(java_opts).to include('-Djma.heap_dump_folder="/my_volume/test/folder"')
+ expect(environment_variables).to include('JMA_HEAP_DUMP_FOLDER=/my_volume/test/folder')
+
+ expect(log_contents).to match(%r{Heap dumps will be stored under \'/my_volume/test/folder\'})
+ end
+
+ end
+
+ context do
+ let(:configuration) do
+ {
+ 'heap_dump_folder' => 'test/folder'
+ }
+ end
+
+ before do
+ allow(services).to receive(:find_service).with('heap-dump')
+ .and_return('volume_mounts' =>
+ [
+ {
+ 'container_dir' => '/my_volume',
+ 'mode' => 'r'
+ }
+ ])
+ end
+
+ it 'fails if volume does not have write mode active', :enable_log_file, log_level: 'DEBUG' do
+ expect { component.release } .to raise_error 'Volume mounted under \'/my_volume\' not in write mode'
+ expect(log_contents).not_to match(/Heap dumps will be stored under/)
+ end
+
+ end
+
+end
diff --git a/spec/java_buildpack/framework/java_memory_assistant_spec.rb b/spec/java_buildpack/framework/java_memory_assistant_spec.rb
new file mode 100644
index 0000000000..32f73e3933
--- /dev/null
+++ b/spec/java_buildpack/framework/java_memory_assistant_spec.rb
@@ -0,0 +1,73 @@
+# Cloud Foundry Java Buildpack
+# Copyright 2013-2017 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'spec_helper'
+require 'application_helper'
+require 'component_helper'
+require 'java_buildpack/framework/java_memory_assistant'
+require 'java_buildpack/framework/java_memory_assistant/agent'
+require 'java_buildpack/framework/java_memory_assistant/clean_up'
+require 'java_buildpack/framework/java_memory_assistant/heap_dump_folder'
+
+describe JavaBuildpack::Framework::JavaMemoryAssistant do
+ include_context 'component_helper'
+
+ let(:component) { StubJavaMemoryAssistant.new context }
+
+ context do
+
+ let(:configuration) do
+ {
+ 'enabled' => false
+ }
+ end
+
+ it 'does not activate submodules if it is disabled in the configuration' do
+ expect(component.detect).not_to be
+ end
+
+ end
+
+ context do
+
+ let(:configuration) do
+ { 'enabled' => true,
+ 'agent' => agent_configuration,
+ 'clean_up' => clean_up_configuration }
+ end
+
+ let(:agent_configuration) { instance_double('agent_configuration') }
+
+ let(:clean_up_configuration) { instance_double('clean_up_configuration') }
+
+ it 'creates submodules' do
+ allow(JavaBuildpack::Framework::JavaMemoryAssistantAgent)
+ .to receive(:new).with(sub_configuration_context(agent_configuration))
+ allow(JavaBuildpack::Framework::JavaMemoryAssistantHeapDumpFolder)
+ .to receive(:new).with(sub_configuration_context(agent_configuration))
+ allow(JavaBuildpack::Framework::JavaMemoryAssistantCleanUp)
+ .to receive(:new).with(sub_configuration_context(clean_up_configuration))
+
+ component.sub_components context
+ end
+ end
+
+end
+
+class StubJavaMemoryAssistant < JavaBuildpack::Framework::JavaMemoryAssistant
+
+ public :command, :sub_components, :supports?
+
+end