OpenTelemetryを使ってJavaのヒープサイズを AWSのCloudWatchMetricsに送信する
Collectorの出力先は環境変数で定義している通りに、コンソールに出てくる。
$ export JAVA_TOOL_OPTIONS="-javaagent:PATH/TO/opentelemetry-javaagent.jar" \
OTEL_TRACES_EXPORTER=logging \
OTEL_METRICS_EXPORTER=logging \
OTEL_LOGS_EXPORTER=logging \
OTEL_METRIC_EXPORT_INTERVAL=15000
起動時に設定している環境変数のうち、Collectorの出力先に関わってくるのは下の3つ。
- OTEL_TRACES_EXPORTER
- OTEL_METRICS_EXPORTER
- OTEL_LOGS_EXPORTER
役割は Environment Variable Specificationにある通り。
- OTEL_TRACES_EXPORTER はトレースの出力先
- OTEL_METRICS_EXPORTER はメトリクスの出力先
- OTEL_LOGS_EXPORTER はログの出力先
値の意味は、ざっくり理解すると、 otlp
は OpenTelemetry Protocol でバックエンドに送信、
console
は 標準出力。 logging
も標準出力だけど、後方互換のために使われている。出力しないなら none
を指定する。
実際に出力されたメトリクスの一つ。長い・・・
[otel.javaagent 2024-11-03 17:49:12:993 +0900] [PeriodicMetricReader-1] INFO io.opentelemetry.exporter.logging.LoggingMetricExporter - metric: ImmutableMetricData{resource=Resource{schemaUrl=https://opentelemetry.io/schemas/1.24.0, attributes={host.arch="amd64", host.name="my-pc-name", os.description="Windows 10 10.0", os.type="windows", process.command_line="E:\amazon-corretto\jdk11.0.3_7\bin\java.exe -javaagent:E:\open-telemetry\opentelemetry-javaagent.jar -Dfile.encoding=UTF-8 otel.DiceApplication", process.executable.path="E:\amazon-corretto\jdk11.0.3_7\bin\java.exe", process.pid=5752, process.runtime.description="Amazon.com Inc. OpenJDK 64-Bit Server VM 11.0.3+7-LTS", process.runtime.name="OpenJDK Runtime Environment", process.runtime.version="11.0.3+7-LTS", service.instance.id="d9287b78-acc2-40fc-a6bb-7da8310978f6", service.name="unknown_service:java", telemetry.distro.name="opentelemetry-java-instrumentation", telemetry.distro.version="2.9.0", telemetry.sdk.language="java", telemetry.sdk.name="opentelemetry", telemetry.sdk.version="1.43.0"}}, instrumentationScopeInfo=InstrumentationScopeInfo{name=io.opentelemetry.runtime-telemetry-java8, version=2.9.0-alpha, schemaUrl=null, attributes={}}, name=jvm.memory.limit, description=Measure of max obtainable memory., unit=By, type=LONG_SUM, data=ImmutableSumData{points=[ImmutableLongPointData{startEpochNanos=1730623707947767900, epochNanos=1730623752967338300, attributes={jvm.memory.pool.name="G1 Old Gen", jvm.memory.type="heap"}, value=17125343232, exemplars=[]}, ImmutableLongPointData{startEpochNanos=1730623707947767900, epochNanos=1730623752967338300, attributes={jvm.memory.pool.name="CodeHeap 'profiled nmethods'", jvm.memory.type="non_heap"}, value=122028032, exemplars=[]}, ImmutableLongPointData{startEpochNanos=1730623707947767900, epochNanos=1730623752967338300, attributes={jvm.memory.pool.name="CodeHeap 'non-nmethods'", jvm.memory.type="non_heap"}, value=7602176, exemplars=[]}, ImmutableLongPointData{startEpochNanos=1730623707947767900, epochNanos=1730623752967338300, attributes={jvm.memory.pool.name="CodeHeap 'non-profiled nmethods'", jvm.memory.type="non_heap"}, value=122028032, exemplars=[]}, ImmutableLongPointData{startEpochNanos=1730623707947767900, epochNanos=1730623752967338300, attributes={jvm.memory.pool.name="Compressed Class Space", jvm.memory.type="non_heap"}, value=1073741824, exemplars=[]}], monotonic=false, aggregationTemporality=CUMULATIVE}}
見ずらいので適宜、改行
ImmutableMetricData {
resource=Resource {
schemaUrl=https://opentelemetry.io/schemas/1.24.0,
attributes={
host.arch="amd64",
host.name="my-pc-name",
os.description="Windows 10 10.0",
os.type="windows",
process.command_line="E:\amazon-corretto\jdk11.0.3_7\bin\java.exe -javaagent:E:\open-telemetry\opentelemetry-javaagent.jar -Dfile.encoding=UTF-8 otel.DiceApplication",
process.executable.path="E:\amazon-corretto\jdk11.0.3_7\bin\java.exe",
process.pid=5752,
process.runtime.description="Amazon.com Inc. OpenJDK 64-Bit Server VM 11.0.3+7-LTS",
process.runtime.name="OpenJDK Runtime Environment",
process.runtime.version="11.0.3+7-LTS",
service.instance.id="d9287b78-acc2-40fc-a6bb-7da8310978f6",
service.name="unknown_service:java",
telemetry.distro.name="opentelemetry-java-instrumentation",
telemetry.distro.version="2.9.0",
telemetry.sdk.language="java",
telemetry.sdk.name="opentelemetry",
telemetry.sdk.version="1.43.0"
}
},
instrumentationScopeInfo=InstrumentationScopeInfo{
name=io.opentelemetry.runtime-telemetry-java8,
version=2.9.0-alpha,
schemaUrl=null,
attributes={}
},
name=jvm.memory.limit,
description=Measure of max obtainable memory.,
unit=By,
type=LONG_SUM,
data=ImmutableSumData{
points=[
ImmutableLongPointData{
startEpochNanos=1730623707947767900,
epochNanos=1730623752967338300,
attributes={
jvm.memory.pool.name="G1 Old Gen",
jvm.memory.type="heap"
},
value=17125343232,
exemplars=[]
},
ImmutableLongPointData{
startEpochNanos=1730623707947767900,
epochNanos=1730623752967338300,
attributes={
jvm.memory.pool.name="CodeHeap 'profiled nmethods'",
jvm.memory.type="non_heap"
},
value=122028032,
exemplars=[]
},
ImmutableLongPointData{
startEpochNanos=1730623707947767900,
epochNanos=1730623752967338300,
attributes={
jvm.memory.pool.name="CodeHeap 'non-nmethods'",
jvm.memory.type="non_heap"
},
value=7602176,
exemplars=[]
},
ImmutableLongPointData{
startEpochNanos=1730623707947767900,
epochNanos=1730623752967338300,
attributes={
jvm.memory.pool.name="CodeHeap 'non-profiled nmethods'",
jvm.memory.type="non_heap"
},
value=122028032,
exemplars=[]
},
ImmutableLongPointData{
startEpochNanos=1730623707947767900,
epochNanos=1730623752967338300,
attributes={
jvm.memory.pool.name="Compressed Class Space",
jvm.memory.type="non_heap"
},
value=1073741824,
exemplars=[]
}
],
monotonic=false,
aggregationTemporality=CUMULATIVE
}
}
1度の出力で得られたメトリクスは下の13個。
https://opentelemetry.io/docs/specs/semconv/runtime/jvm-metrics/ のExperimental を除いた12個 と tomcat-7
JVM Memory
- jvm.memory.used
- jvm.memory.committed
- jvm.memory.limit
- jvm.memory.used_after_last_gc
JVM Garbage Collection
- jvm.gc.duration
JVM Threads
- jvm.thread.count
JVM Classes
- jvm.class.loaded
- jvm.class.unloaded
- jvm.class.count
JVM CPU
- jvm.cpu.time
- jvm.cpu.count
- jvm.cpu.recent_utilization
tomcat-7
- io.opentelemetry.tomcat-7.0
メトリクスのドキュメントは探せなかったけど、ソースは見つかった。 https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/main/instrumentation/tomcat
具体的な値は、これ。
ImmutableMetricData{
resource=Resource{
schemaUrl=https://opentelemetry.io/schemas/1.24.0,
attributes={
host.arch="amd64",
host.name="DESKTOP-O9APPFJ",
os.description="Windows 10 10.0",
os.type="windows",
process.command_line="E:\amazon-corretto\jdk11.0.3_7\bin\java.exe -javaagent:E:\open-telemetry\opentelemetry-javaagent.jar -Dfile.encoding=UTF-8 otel.DiceApplication",
process.executable.path="E:\amazon-corretto\jdk11.0.3_7\bin\java.exe",
process.pid=5752,
process.runtime.description="Amazon.com Inc. OpenJDK 64-Bit Server VM 11.0.3+7-LTS",
process.runtime.name="OpenJDK Runtime Environment",
process.runtime.version="11.0.3+7-LTS",
service.instance.id="d9287b78-acc2-40fc-a6bb-7da8310978f6",
service.name="unknown_service:java",
telemetry.distro.name="opentelemetry-java-instrumentation",
telemetry.distro.version="2.9.0",
telemetry.sdk.language="java",
telemetry.sdk.name="opentelemetry",
telemetry.sdk.version="1.43.0"
}
},
instrumentationScopeInfo=InstrumentationScopeInfo{
name=io.opentelemetry.tomcat-7.0,
version=2.9.0-alpha,
schemaUrl=null,
attributes={}
},
name=http.server.request.duration,
description=Duration of HTTP server requests.,
unit=s,
type=HISTOGRAM,
data=ImmutableHistogramData{
aggregationTemporality=CUMULATIVE,
points=[
ImmutableHistogramPointData{
getStartEpochNanos=1730623707947767900,
getEpochNanos=1730623752967338300,
getAttributes=FilteredAttributes{
http.request.method=GET,http.response.status_code=404,http.route=/**,network.protocol.version=1.1,url.scheme=http
},
getSum=0.0639274,
getCount=2,
hasMin=true,
getMin=0.006074,
hasMax=true,
getMax=0.0578534,
getBoundaries=[0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1.0, 2.5, 5.0, 7.5, 10.0],
getCounts=[0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
getExemplars=[
ImmutableDoubleExemplarData{
filteredAttributes={
client.address="127.0.0.1",
network.peer.address="127.0.0.1",
network.peer.port=62520,
server.address="localhost",
server.port=8080,
url.path="/favicon.ico",
user_agent.original="Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0"
},
epochNanos=1730623749634000000,
spanContext=ImmutableSpanContext{
traceId=5fc5ed7f2be50133264377124a619657,
spanId=0ffcf3c35ea514d7,
traceFlags=01,
traceState=ArrayBasedTraceState{
entries=[]
},
remote=false,
valid=true
},
value=0.006074
}
]
},
ImmutableHistogramPointData{
getStartEpochNanos=1730623707947767900,
getEpochNanos=1730623752967338300,
getAttributes=FilteredAttributes{
http.request.method=GET,
http.response.status_code=200,
http.route=/rolldice,network.protocol.version=1.1,
url.scheme=http
},
getSum=0.1518083,
getCount=11,
hasMin=true,
getMin=0.0021692,
hasMax=true,
getMax=0.1254785,
getBoundaries=[0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1.0, 2.5, 5.0, 7.5, 10.0],
getCounts=[10, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
getExemplars=[
ImmutableDoubleExemplarData{
filteredAttributes={
client.address="127.0.0.1",
network.peer.address="127.0.0.1",
network.peer.port=62521,
server.address="localhost",
server.port=8080,
url.path="/rolldice",
user_agent.original="Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0"
},
epochNanos=1730623752703000000,
spanContext=ImmutableSpanContext{
traceId=19013ea924101886bdb21bc1c74b5c50,
spanId=9bff9f2f5fb72848,
traceFlags=01,
traceState=ArrayBasedTraceState{
entries=[]
},
remote=false,
valid=true
},
value=0.0025629
}
]
}
]
}
}
-javaagent:PATH/TO/opentelemetry-javaagent.jar
で指定したエージェントが Collectorに送信していることを確認できたので、AWsに送信したい。
エージェントは Auto-Instrumentation for Traces and Metrics with the Java agentの Installation の latest version.
に設定されたリンクからダウンロード。リンク先はGitHubの リリースページ。
Collectorは AWS Distro for OpenTelemetry を使う。
ワークショップ「One Observability Workshop」にある Java tracing walkthrough を学ぶのが よさそうだけど、今回はスキップ。
今回は aws-otel-collectorの docker-demo.md を参考にする。
Javaを起動する差分は jar
コマンドの引数 -javaagen
と 環境変数。
-javaagen
オプションのjarファイルを事前にダウンロードした aws-opentelemetry-agent.jar
に変更する。
-javaagent:E:\open-telemetry\aws-opentelemetry-agent.jar
環境変数は下の3つを削除してデフォルトの otlp
で動作させるか、明示的にotlp
を指定する。そうすることで エージェントが メトリクスを Collectorに送信する。
- OTEL_TRACES_EXPORTER
- OTEL_METRICS_EXPORTER
- OTEL_LOGS_EXPORTER
追加する環境変数は1つ。
- OTEL_RESOURCE_ATTRIBUTES="service.name=MyApp,service.namespace=MyTeam "
ここで指定した値はCloudWatchMetricsの名前空間に使われる。 Spring Boot starterを利用しているのなら、 application.yaml に定義することができる。
Collectorの AWS Distro for OpenTelemetry をDcokerで起動するのは、docker-demo.mdを参考にして、下のコマンドで実行した
docker run --rm -p 4317:4317 -p 55680:55680 -p 8889:8888 \
-e "AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}" \
-e "AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}" \
-e "AWS_SESSION_TOKEN=${AWS_SESSION_TOKEN}" \
-e AWS_REGION=ap-northeast-1 \
-v .\otel-local-config.yaml:/otel-local-config.yaml \
--name awscollector public.ecr.aws/aws-observability/aws-otel-collector:latest \
--config /otel-local-config.yaml
otel-local-config.yaml は https://github.com/aws-observability/aws-otel-collector/blob/main/config/ecs/ecs-default-config.yaml を参考にして、 awsemf
の namespaceとlog_group_nameを削除した。
extensions:
health_check:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
awsxray:
endpoint: 0.0.0.0:2000
transport: udp
statsd:
endpoint: 0.0.0.0:8125
aggregation_interval: 60s
processors:
batch/traces:
timeout: 1s
send_batch_size: 50
batch/metrics:
timeout: 60s
exporters:
awsxray:
awsemf:
service:
pipelines:
traces:
receivers: [otlp,awsxray]
processors: [batch/traces]
exporters: [awsxray]
metrics:
receivers: [otlp, statsd]
processors: [batch/metrics]
exporters: [awsemf]
extensions: [health_check]
CloudWatch Metricsに送られてきました。
メトリクス名は 環境変数で設定したサービスのnamespaceとnameが使われています。
OTEL_RESOURCE_ATTRIBUTES="service.name=MyApp,service.namespace=MyTeam"
MyTeam/MyApp を選択すると、いっぱいあります。
今回必要なメトリクスは OTelLib にありました。
グラフにしてみます。1分より細かいメトリクスは取れていない(送られていない?)。これは要調査。
CloudWatch Logs の ロググループ /metrics/MyTeam/MyApp
に メトリクスの値が記録されていた。
{
"OTelLib": "io.opentelemetry.runtime-telemetry-java8",
"Version": "1",
"_aws": {
"CloudWatchMetrics": [
{
"Namespace": "MyTeam/MyApp",
"Dimensions": [
[
"pool",
"type",
"OTelLib"
],
[
"OTelLib"
],
[
"OTelLib",
"pool"
],
[
"OTelLib",
"type"
]
],
"Metrics": [
{
"Name": "process.runtime.jvm.memory.usage_after_last_gc",
"Unit": "Bytes"
},
{
"Name": "process.runtime.jvm.memory.usage",
"Unit": "Bytes"
},
{
"Name": "process.runtime.jvm.memory.init",
"Unit": "Bytes"
},
{
"Name": "process.runtime.jvm.memory.committed",
"Unit": "Bytes"
}
]
}
],
"Timestamp": 1730722219636
},
"pool": "G1 Eden Space",
"process.runtime.jvm.memory.committed": 0,
"process.runtime.jvm.memory.init": 0,
"process.runtime.jvm.memory.usage": 142606336,
"process.runtime.jvm.memory.usage_after_last_gc": 0,
"type": "heap"
}
GCのアルゴリズムが G1GC
なので、メモリプールごとにメトリクスが記録されていた。
メトリクスの Attributeの意味を理解した。
type と pool の組み合わせは下の通りであった。
type | pool |
---|---|
heap | G1 Eden Space |
heap | G1 Old Gen |
heap | G1 Survivor Space |
non_heap | CodeHeap 'non-nmethods' |
non_heap | CodeHeap 'non-profiled nmethods' |
non_heap | CodeHeap 'profiled nmethods' |
non_heap | Compressed Class Space |
non_heap | Metaspace |
メトリクスは量が多いので、CloudWatch Application Signals を使いたい所。
ひとまずは、ここまで。