Skip to content

Commit 2a890c8

Browse files
committed
Merge branch '436-java-memory-assistant' into 3.x
2 parents dfb00dd + f5efa6e commit 2a890c8

File tree

13 files changed

+965
-0
lines changed

13 files changed

+965
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ To learn how to configure various properties of the buildpack, follow the "Confi
8181
* [Dynatrace SaaS/Managed OneAgent](docs/framework-dynatrace_one_agent.md) ([Configuration](docs/framework-dynatrace_one_agent.md#configuration))
8282
* [Google Stackdriver Debugger](docs/framework-google_stackdriver_debugger.md) ([Configuration](docs/framework-google_stackdriver_debugger.md#configuration))
8383
* [Introscope Agent](docs/framework-introscope_agent.md) ([Configuration](docs/framework-introscope_agent.md#configuration))
84+
* [Java Memory Assistant](docs/framework-java_memory_assistant.md) ([Configuration](docs/framework-java_memory_assistant.md#configuration))
8485
* [Java Options](docs/framework-java_opts.md) ([Configuration](docs/framework-java_opts.md#configuration))
8586
* [JRebel Agent](docs/framework-jrebel_agent.md) ([Configuration](docs/framework-jrebel_agent.md#configuration))
8687
* [JMX](docs/framework-jmx.md) ([Configuration](docs/framework-jmx.md#configuration))

config/components.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ frameworks:
4848
- "JavaBuildpack::Framework::DynatraceOneAgent"
4949
- "JavaBuildpack::Framework::GoogleStackdriverDebugger"
5050
# - "JavaBuildpack::Framework::IntroscopeAgent"
51+
- "JavaBuildpack::Framework::JavaMemoryAssistant"
5152
- "JavaBuildpack::Framework::Jmx"
5253
- "JavaBuildpack::Framework::JrebelAgent"
5354
- "JavaBuildpack::Framework::LunaSecurityProvider"

config/java_memory_assistant.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Cloud Foundry Java Buildpack
2+
# Copyright 2013-2017 the original author or authors.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
---
16+
enabled: false
17+
agent:
18+
version: 0.+
19+
repository_root: https://raw.githubusercontent.com/SAP/java-memory-assistant/repository
20+
heap_dump_folder:
21+
check_interval: 5s
22+
max_frequency: 1/1m
23+
log_level:
24+
thresholds:
25+
heap:
26+
code_cache:
27+
metaspace:
28+
perm_gen:
29+
compressed_class:
30+
eden:
31+
survivor:
32+
old_gen: ">600MB"
33+
clean_up:
34+
version: 0.+
35+
repository_root: https://raw.githubusercontent.com/SAP/java-memory-assistant-tools/repository-cu
36+
max_dump_count: 1
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# Java Memory Assistant Framework
2+
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.
3+
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).
4+
5+
<table>
6+
<tr>
7+
<td><strong>Detection Criterion</strong></td><td><code>enabled</code> set in the <code>config/java_memory_assistant.yml</code></td>
8+
</tr>
9+
<tr>
10+
<td><strong>Tags</strong></td><td><tt>java-memory-assistant=&lt;version&gt;</tt></td>
11+
</tr>
12+
</table>
13+
Tags are printed to standard output by the buildpack detect script.
14+
15+
## Configuration
16+
For general information on configuring the buildpack, including how to specify configuration values through environment variables, refer to [Configuration and Extension][].
17+
18+
The framework can be configured by modifying the [`config/java_memory_assistant.yml`][] file in the buildpack fork.
19+
20+
| Name | Description
21+
| ---- | -----------
22+
| `enabled` | Whether to enable the Java Memory Assistant framework. By default the agent is turned off.
23+
| `agent.heap_dump_folder` | The folder on the container's filesystem where heap dumps are created. Default value: `$PWD`
24+
| `agent.thresholds.<memory_area>` | 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`.
25+
| `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).
26+
| `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). |
27+
| `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. |
28+
| `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` |
29+
30+
### Heap Dump Names
31+
32+
The heap dump filenames will be generated according to the following name pattern:
33+
34+
`<INSTANCE-INDEX>-%ts:yyyyMMdd'T'mmssSSSZ%-<INSTANCE-ID[0,8]>.hprof`
35+
36+
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).
37+
38+
### Supported Memory Areas
39+
40+
| Memory Area | Property Name |
41+
|------------------------|------------------|
42+
| Heap | `heap` |
43+
| Code Cache | `code_cache` |
44+
| Metaspace | `metaspace` |
45+
| Compressed Class Space | `compressed_class` |
46+
| Eden | `eden` |
47+
| Survivor | `survivor` |
48+
| Old Generation | `old_gen` |
49+
50+
The default values can be found in the [`config/java_memory_assistant.yml`][] file.
51+
52+
### Examples
53+
54+
Enable the Java Memory Assistant with its default settings:
55+
56+
```yaml
57+
JBP_CONFIG_JAVA_MEMORY_ASSISTANT: '{enabled : true}'
58+
```
59+
60+
Create heap dumps when the old generation memory pool exceeds 800 MB:
61+
62+
```yaml
63+
JBP_CONFIG_JAVA_MEMORY_ASSISTANT: '{enabled : true, agent: { thresholds : { old_gen : ">800MB" } } }'
64+
```
65+
66+
Create heap dumps when the old generation grows by more than 20% in two minutes:
67+
68+
```yaml
69+
JBP_CONFIG_JAVA_MEMORY_ASSISTANT: '{enabled : true, agent : { thresholds : { old_gen : +20%/2m } } }'
70+
```
71+
72+
### What are the right thresholds for your application?
73+
74+
Well, it depends.
75+
The way applications behave in terms of memory management is a direct result of how they are implemented.
76+
This is much more then case when the applications are under heavy load.
77+
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.
78+
79+
Nevertheless, a memory area that tends to be particularly interesting to monitor is the so called "old generation" (`old_gen`).
80+
When instantiated, bjects in the Java heap are allocated in an area called `eden`.
81+
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`.
82+
In other words, `old_gen` objects are those that survived multiple garbage collections.
83+
In contrast, `eden` and `survivor` objects are collectively called "young generation".
84+
85+
Application-wide singletons and pooled objects (threads, connections) are examples of "legitimate" `old_gen` candidates.
86+
But memory leaks, by their very nature or surviving multiple garbage collections, end up in `old_gen` too.
87+
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.
88+
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.
89+
Notable exceptions to this rule of thumb are applications that use large local caches.
90+
91+
### Making sure heap dumps can be created
92+
93+
The Java Virtual Machine must create heap dumps on a file.
94+
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.
95+
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.
96+
97+
The maximum size of a heap dump depends on the maximum size of the heap of the Java Virtual Machine.
98+
Consider increasing the disk quota of your warden/garden container via the `cf scale <app-name> -k [new size]` using as `new size` to the outcome of the following calculation:
99+
100+
`[max heap size] * [max heap dump count] + 200MB`
101+
102+
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.
103+
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.
104+
105+
### Where to best store heap dumps?
106+
107+
Heap dumps are created by the Java Virtual Machine on a file on the filesystem mounted by the garden container.
108+
Normally, the filesystem of a container is ephemeral.
109+
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.
110+
111+
To prevent heap dumps from "going down" with the container, you should consider storing them on a `volume service`.
112+
113+
#### Container-mounted volumes
114+
115+
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 `<mount-point>/<space_name>-<space_id[0,8]>/<application_name>-<application_id[0-8]>`.
116+
117+
[`config/java_memory_assistant.yml`]: ../config/java_memory_assistant.yml
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Cloud Foundry Java Buildpack
2+
# Copyright 2013-2017 the original author or authors.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
require 'java_buildpack/component/modular_component'
17+
require 'java_buildpack/framework'
18+
require 'java_buildpack/framework/java_memory_assistant/agent'
19+
require 'java_buildpack/framework/java_memory_assistant/clean_up'
20+
require 'java_buildpack/framework/java_memory_assistant/heap_dump_folder'
21+
22+
module JavaBuildpack
23+
module Framework
24+
25+
# Encapsulates the integraton of the JavaMemoryAssistant.
26+
class JavaMemoryAssistant < JavaBuildpack::Component::ModularComponent
27+
28+
protected
29+
30+
# (see JavaBuildpack::Component::ModularComponent#command)
31+
def command; end
32+
33+
# (see JavaBuildpack::Component::ModularComponent#sub_components)
34+
def sub_components(context)
35+
[
36+
JavaMemoryAssistantAgent.new(sub_configuration_context(context, 'agent')),
37+
JavaMemoryAssistantHeapDumpFolder.new(sub_configuration_context(context, 'agent')),
38+
JavaMemoryAssistantCleanUp.new(sub_configuration_context(context, 'clean_up'))
39+
]
40+
end
41+
42+
# (see JavaBuildpack::Component::ModularComponent#supports?)
43+
def supports?
44+
@configuration['enabled']
45+
end
46+
47+
end
48+
end
49+
end
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Cloud Foundry Java Buildpack
2+
# Copyright 2013-2017 the original author or authors.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
require 'fileutils'
17+
require 'java_buildpack/component'
18+
require 'java_buildpack/component/versioned_dependency_component'
19+
require 'java_buildpack/component/droplet'
20+
require 'java_buildpack/component/environment_variables'
21+
require 'java_buildpack/framework'
22+
23+
module JavaBuildpack
24+
module Framework
25+
26+
# Encapsulates the integraton of the JavaMemoryAssistant to inject the agent in the JVM.
27+
class JavaMemoryAssistantAgent < JavaBuildpack::Component::VersionedDependencyComponent
28+
29+
# (see JavaBuildpack::Component::BaseComponent#compile)
30+
def compile
31+
download_jar
32+
@droplet.copy_resources
33+
end
34+
35+
# (see JavaBuildpack::Component::BaseComponent#release)
36+
def release
37+
@droplet.java_opts
38+
.add_javaagent(@droplet.sandbox + jar_name)
39+
.add_system_property('jma.enabled', 'true')
40+
.add_system_property('jma.heap_dump_name', %("#{name_pattern}"))
41+
.add_system_property 'jma.log_level', normalized_log_level
42+
43+
add_system_prop_if_config_present 'check_interval', 'jma.check_interval'
44+
add_system_prop_if_config_present 'max_frequency', 'jma.max_frequency'
45+
46+
return unless @configuration.key?('thresholds')
47+
48+
@configuration['thresholds'].each do |key, value|
49+
@droplet.java_opts.add_system_property "jma.thresholds.#{key}", value.to_s
50+
end
51+
end
52+
53+
protected
54+
55+
# (see JavaBuildpack::Component::VersionedDependencyComponent#jar_name)
56+
def jar_name
57+
"java-memory-assistant-#{@version}.jar"
58+
end
59+
60+
def supports?
61+
true
62+
end
63+
64+
private
65+
66+
LOG_LEVEL_MAPPING = {
67+
'DEBUG' => 'DEBUG',
68+
'WARN' => 'WARNING',
69+
'INFO' => 'INFO',
70+
'ERROR' => 'ERROR',
71+
'FATAL' => 'ERROR'
72+
}.freeze
73+
74+
private_constant :LOG_LEVEL_MAPPING
75+
76+
def add_system_prop_if_config_present(config_entry, system_property_name)
77+
return unless @configuration.key?(config_entry)
78+
@droplet.java_opts.add_system_property(system_property_name, @configuration[config_entry])
79+
end
80+
81+
def log_level
82+
@configuration['log_level'] || ENV['JBP_LOG_LEVEL'] || 'ERROR'
83+
end
84+
85+
def normalized_log_level
86+
normalized_log_level = LOG_LEVEL_MAPPING[log_level.upcase]
87+
raise "Invalid value of the 'log_level' property: '#{log_level}'" unless normalized_log_level
88+
normalized_log_level
89+
end
90+
91+
def name_pattern
92+
# Double escaping quotes of doom. Nothing less would work.
93+
%q(%env:CF_INSTANCE_INDEX%-%ts:yyyy-MM-dd'"'"'T'"'"'mm'"'"':'"'"'ss'"'"':'"'"'SSSZ%-) \
94+
'%env:CF_INSTANCE_GUID[,8]%.hprof'
95+
end
96+
97+
end
98+
end
99+
end
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Cloud Foundry Java Buildpack
2+
# Copyright 2013-2017 the original author or authors.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
require 'java_buildpack/component/versioned_dependency_component'
17+
require 'java_buildpack/component/droplet'
18+
require 'java_buildpack/framework'
19+
20+
module JavaBuildpack
21+
module Framework
22+
23+
# Encapsulates the integraton of the JavaMemoryAssistant to set up clean up of dumps.
24+
class JavaMemoryAssistantCleanUp < JavaBuildpack::Component::VersionedDependencyComponent
25+
26+
# (see JavaBuildpack::Component::BaseComponent#compile)
27+
def compile
28+
return unless supports?
29+
30+
download_zip false
31+
end
32+
33+
# (see JavaBuildpack::Component::BaseComponent#release)
34+
def release
35+
return unless supports?
36+
37+
@droplet.environment_variables
38+
.add_environment_variable 'JMA_MAX_DUMP_COUNT', @configuration['max_dump_count'].to_s
39+
40+
@droplet.java_opts
41+
.add_system_property('jma.command.interpreter', '')
42+
.add_system_property('jma.execute.before', @droplet.sandbox + 'cleanup')
43+
end
44+
45+
protected
46+
47+
# (see JavaBuildpack::Component::VersionedDependencyComponent#supports?)
48+
def supports?
49+
@configuration['max_dump_count'].to_i > 0
50+
end
51+
52+
end
53+
end
54+
end

0 commit comments

Comments
 (0)