From 49a0efdd926b2823d80fb2e76f922399a5d991a6 Mon Sep 17 00:00:00 2001 From: Felix Barnsteiner Date: Thu, 13 Dec 2018 16:23:28 +0100 Subject: [PATCH 01/10] Add basic metrics --- .../configuration/CoreConfiguration.java | 13 +- .../configuration/converter/TimeDuration.java | 7 +- .../validation/RangeValidator.java | 76 ++++++++ .../apm/agent/impl/ElasticApmTracer.java | 9 +- .../apm/agent/metrics/DoubleSupplier.java | 25 +++ .../apm/agent/metrics/MetricRegistry.java | 62 +++++++ .../elastic/apm/agent/metrics/MetricSet.java | 96 ++++++++++ .../metrics/builtin/JvmMemoryMetrics.java | 70 ++++++++ .../agent/metrics/builtin/SystemMetrics.java | 167 ++++++++++++++++++ .../agent/metrics/builtin/package-info.java | 28 +++ .../apm/agent/metrics/package-info.java | 23 +++ .../apm/agent/report/ApmServerReporter.java | 30 +++- .../report/IntakeV2ReportingEventHandler.java | 6 +- .../co/elastic/apm/agent/report/Reporter.java | 3 + .../agent/report/ReporterConfiguration.java | 15 ++ .../apm/agent/report/ReporterFactory.java | 3 - .../apm/agent/report/ReportingEvent.java | 17 +- .../report/serialize/DslJsonSerializer.java | 42 +++-- .../report/serialize/PayloadSerializer.java | 3 + .../src/main/resources/META-INF/NOTICE | 4 + ...lastic.apm.agent.context.LifecycleListener | 2 + .../co/elastic/apm/agent/MockReporter.java | 5 + .../validation/RangeValidatorTest.java | 73 ++++++++ .../metrics/MetricSetSerializationTest.java | 50 ++++++ .../metrics/builtin/SystemMetricsTest.java | 43 +++++ 25 files changed, 842 insertions(+), 30 deletions(-) create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/validation/RangeValidator.java create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/DoubleSupplier.java create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricRegistry.java create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricSet.java create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/builtin/JvmMemoryMetrics.java create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/builtin/SystemMetrics.java create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/builtin/package-info.java create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/package-info.java create mode 100644 apm-agent-core/src/test/java/co/elastic/apm/agent/configuration/validation/RangeValidatorTest.java create mode 100644 apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/MetricSetSerializationTest.java create mode 100644 apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/builtin/SystemMetricsTest.java diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/CoreConfiguration.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/CoreConfiguration.java index 544258b1d0..9e8fd78be3 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/CoreConfiguration.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/CoreConfiguration.java @@ -36,6 +36,8 @@ import java.util.Set; import java.util.TreeSet; +import static co.elastic.apm.agent.configuration.validation.RangeValidator.isInRange; + public class CoreConfiguration extends ConfigurationOptionProvider { public static final String ACTIVE = "active"; @@ -102,16 +104,7 @@ public class CoreConfiguration extends ConfigurationOptionProvider { "To reduce overhead and storage requirements, you can set the sample rate to a value between 0.0 and 1.0. " + "We still record overall time and the result for unsampled transactions, but no context information, tags, or spans.") .dynamic(true) - .addValidator(new ConfigurationOption.Validator() { - @Override - public void assertValid(Double value) { - if (value != null) { - if (value < 0 || value > 1) { - throw new IllegalArgumentException("The sample rate must be between 0 and 1"); - } - } - } - }) + .addValidator(isInRange(0d, 1d)) .buildWithDefault(1.0); private final ConfigurationOption transactionMaxSpans = ConfigurationOption.integerOption() diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/converter/TimeDuration.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/converter/TimeDuration.java index f7779e11c1..81caf45c28 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/converter/TimeDuration.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/converter/TimeDuration.java @@ -22,7 +22,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -public class TimeDuration { +public class TimeDuration implements Comparable { public static final Pattern DURATION_PATTERN = Pattern.compile("^(-)?(\\d+)(ms|s|m)$"); private final String durationString; @@ -68,4 +68,9 @@ public long getMillis() { public String toString() { return durationString; } + + @Override + public int compareTo(TimeDuration o) { + return Long.compare(durationMs, o.durationMs); + } } diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/validation/RangeValidator.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/validation/RangeValidator.java new file mode 100644 index 0000000000..ce037d131e --- /dev/null +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/validation/RangeValidator.java @@ -0,0 +1,76 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 Elastic and contributors + * %% + * 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. + * #L% + */ +package co.elastic.apm.agent.configuration.validation; + +import org.stagemonitor.configuration.ConfigurationOption; + +import javax.annotation.Nullable; + +public class RangeValidator implements ConfigurationOption.Validator { + + @Nullable + private final T min; + @Nullable + private final T max; + private final boolean mustBeInRange; + + private RangeValidator(@Nullable T min, @Nullable T max, boolean mustBeInRange) { + this.min = min; + this.max = max; + this.mustBeInRange = mustBeInRange; + } + + public static RangeValidator isInRange(T min, T max) { + return new RangeValidator<>(min, max, true); + } + + public static RangeValidator isNotInRange(T min, T max) { + return new RangeValidator<>(min, max, false); + } + + public static RangeValidator min(T min) { + return new RangeValidator<>(min, null, true); + } + + public static RangeValidator max(T max) { + return new RangeValidator<>(null, max, true); + } + + @Override + public void assertValid(@Nullable T value) { + if (value != null) { + boolean isInRange = true; + if (min != null) { + isInRange = min.compareTo(value) <= 0; + } + if (max != null) { + isInRange &= value.compareTo(max) <= 0; + } + + if (!isInRange && mustBeInRange) { + throw new IllegalArgumentException(value + " must be in the range [" + min + "," + max + "]"); + } + + if (isInRange && !mustBeInRange) { + throw new IllegalArgumentException(value + " must not be in the range [" + min + "," + max + "]"); + } + } + } +} diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/ElasticApmTracer.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/ElasticApmTracer.java index 1028b691fd..0d2b65ce02 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/ElasticApmTracer.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/ElasticApmTracer.java @@ -29,8 +29,9 @@ import co.elastic.apm.agent.impl.transaction.Span; import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.Transaction; -import co.elastic.apm.agent.objectpool.ObjectPool; +import co.elastic.apm.agent.metrics.MetricRegistry; import co.elastic.apm.agent.objectpool.Allocator; +import co.elastic.apm.agent.objectpool.ObjectPool; import co.elastic.apm.agent.objectpool.impl.QueueBasedObjectPool; import co.elastic.apm.agent.report.Reporter; import co.elastic.apm.agent.report.ReporterConfiguration; @@ -75,6 +76,7 @@ protected Deque initialValue() { }; private final CoreConfiguration coreConfiguration; private final List spanListeners; + private final MetricRegistry metricRegistry = new MetricRegistry(); private Sampler sampler; ElasticApmTracer(ConfigurationRegistry configurationRegistry, Reporter reporter, Iterable lifecycleListeners, List spanListeners) { @@ -120,6 +122,7 @@ public void onChange(ConfigurationOption configurationOption, Double oldValue for (SpanListener spanListener : spanListeners) { spanListener.init(this); } + reporter.scheduleMetricReporting(metricRegistry, configurationRegistry.getConfig(ReporterConfiguration.class).getMetricsIntervalMs()); } public Transaction startTransaction() { @@ -396,4 +399,8 @@ private void assertIsActive(Object span, @Nullable Object currentlyActive) { } assert span == currentlyActive; } + + public MetricRegistry getMetricRegistry() { + return metricRegistry; + } } diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/DoubleSupplier.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/DoubleSupplier.java new file mode 100644 index 0000000000..6fd088bfa9 --- /dev/null +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/DoubleSupplier.java @@ -0,0 +1,25 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 Elastic and contributors + * %% + * 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. + * #L% + */ +package co.elastic.apm.agent.metrics; + +public interface DoubleSupplier { + + double get(); +} diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricRegistry.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricRegistry.java new file mode 100644 index 0000000000..534c96ed52 --- /dev/null +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricRegistry.java @@ -0,0 +1,62 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 Elastic and contributors + * %% + * 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. + * #L% + */ +package co.elastic.apm.agent.metrics; + +import com.dslplatform.json.JsonWriter; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class MetricRegistry { + + private static final byte NEW_LINE = '\n'; + private final Map, MetricSet> metricSets = new ConcurrentHashMap<>(); + + public void addUnlessNan(String name, Map tags, DoubleSupplier metric) { + if (!Double.isNaN(metric.get())) { + add(name, tags, metric); + } + } + + public void add(String name, Map tags, DoubleSupplier metric) { + MetricSet metricSet = metricSets.get(tags); + if (metricSet == null) { + metricSets.putIfAbsent(tags, new MetricSet(tags)); + metricSet = metricSets.get(tags); + } + metricSet.add(name, metric); + } + + public void serialize(JsonWriter jw, StringBuilder replaceBuilder) { + final long timestamp = System.currentTimeMillis() * 1000; + for (MetricSet metricSet : metricSets.values()) { + metricSet.serialize(timestamp, replaceBuilder, jw); + jw.writeByte(NEW_LINE); + } + } + + public double get(String name, Map tags) { + final MetricSet metricSet = metricSets.get(tags); + if (metricSet != null) { + return metricSet.get(name).get(); + } + return Double.NaN; + } +} diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricSet.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricSet.java new file mode 100644 index 0000000000..2727b684bd --- /dev/null +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricSet.java @@ -0,0 +1,96 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 Elastic and contributors + * %% + * 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. + * #L% + */ +package co.elastic.apm.agent.metrics; + +import co.elastic.apm.agent.report.serialize.DslJsonSerializer; +import com.dslplatform.json.JsonWriter; +import com.dslplatform.json.NumberConverter; + +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class MetricSet { + private final Map tags; + private final Map samples = new ConcurrentHashMap<>(); + + MetricSet(Map tags) { + this.tags = tags; + } + + public void add(String name, DoubleSupplier metric) { + samples.putIfAbsent(name, metric); + } + + DoubleSupplier get(String name) { + return samples.get(name); + } + + public void serialize(long epochMicros, StringBuilder replaceBuilder, JsonWriter jw) { + jw.writeByte(JsonWriter.OBJECT_START); + { + DslJsonSerializer.writeFieldName("metricset", jw); + jw.writeByte(JsonWriter.OBJECT_START); + { + DslJsonSerializer.writeFieldName("timestamp", jw); + NumberConverter.serialize(epochMicros, jw); + jw.writeByte(JsonWriter.COMMA); + + if (!tags.isEmpty()) { + DslJsonSerializer.writeFieldName("tags", jw); + DslJsonSerializer.serializeTags(tags, replaceBuilder, jw); + jw.writeByte(JsonWriter.COMMA); + } + + DslJsonSerializer.writeFieldName("samples", jw); + serializeSamples(samples, jw); + } + jw.writeByte(JsonWriter.OBJECT_END); + } + jw.writeByte(JsonWriter.OBJECT_END); + } + + private void serializeSamples(Map samples, JsonWriter jw) { + jw.writeByte(JsonWriter.OBJECT_START); + final int size = samples.size(); + if (size > 0) { + final Iterator> iterator = samples.entrySet().iterator(); + Map.Entry kv = iterator.next(); + serializeSample(kv.getKey(), kv.getValue().get(), jw); + for (int i = 1; i < size; i++) { + jw.writeByte(JsonWriter.COMMA); + kv = iterator.next(); + serializeSample(kv.getKey(), kv.getValue().get(), jw); + } + } + jw.writeByte(JsonWriter.OBJECT_END); + } + + private void serializeSample(String key, double value, JsonWriter jw) { + jw.writeString(key); + jw.writeByte(JsonWriter.SEMI); + jw.writeByte(JsonWriter.OBJECT_START); + { + DslJsonSerializer.writeFieldName("value", jw); + NumberConverter.serialize(value, jw); + } + jw.writeByte(JsonWriter.OBJECT_END); + } +} diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/builtin/JvmMemoryMetrics.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/builtin/JvmMemoryMetrics.java new file mode 100644 index 0000000000..4eb53ef6ed --- /dev/null +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/builtin/JvmMemoryMetrics.java @@ -0,0 +1,70 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 Elastic and contributors + * %% + * 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. + * #L% + */ +package co.elastic.apm.agent.metrics.builtin; + +import co.elastic.apm.agent.context.LifecycleListener; +import co.elastic.apm.agent.impl.ElasticApmTracer; +import co.elastic.apm.agent.metrics.DoubleSupplier; +import co.elastic.apm.agent.metrics.MetricRegistry; + +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; +import java.lang.management.MemoryUsage; +import java.util.Collections; + +public class JvmMemoryMetrics implements LifecycleListener { + + @Override + public void start(ElasticApmTracer tracer) { + bindTo(tracer.getMetricRegistry()); + } + + private void bindTo(final MetricRegistry registry) { + register(registry, "nonheap", ManagementFactory.getPlatformMXBean(MemoryMXBean.class).getNonHeapMemoryUsage()); + register(registry, "heap", ManagementFactory.getPlatformMXBean(MemoryMXBean.class).getNonHeapMemoryUsage()); + } + + private void register(final MetricRegistry registry, final String area, final MemoryUsage memoryUsage) { + registry.add("jvm.memory." + area + ".used", Collections.emptyMap(), new DoubleSupplier() { + @Override + public double get() { + return memoryUsage.getUsed(); + } + }); + + registry.add("jvm.memory." + area + ".committed", Collections.emptyMap(), new DoubleSupplier() { + @Override + public double get() { + return memoryUsage.getCommitted(); + } + }); + + registry.add("jvm.memory." + area + ".max", Collections.emptyMap(), new DoubleSupplier() { + @Override + public double get() { + return memoryUsage.getMax(); + } + }); + } + + @Override + public void stop() throws Exception { + } +} diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/builtin/SystemMetrics.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/builtin/SystemMetrics.java new file mode 100644 index 0000000000..5add2a7279 --- /dev/null +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/builtin/SystemMetrics.java @@ -0,0 +1,167 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 Elastic and contributors + * %% + * 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. + * #L% + */ +package co.elastic.apm.agent.metrics.builtin; + +import co.elastic.apm.agent.context.LifecycleListener; +import co.elastic.apm.agent.impl.ElasticApmTracer; +import co.elastic.apm.agent.metrics.DoubleSupplier; +import co.elastic.apm.agent.metrics.MetricRegistry; + +import javax.annotation.Nullable; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.management.ManagementFactory; +import java.lang.management.OperatingSystemMXBean; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Record metrics related to the CPU, gathered by the JVM. + *

+ * Supported JVM implementations: + *

    + *
  • HotSpot
  • + *
  • J9
  • + *
+ *

+ * This implementation is based on io.micrometer.core.instrument.binder.system.ProcessorMetrics, + * under Apache License 2.0 + */ +public class SystemMetrics implements LifecycleListener { + + /** + * List of public, exported interface class names from supported JVM implementations. + */ + private static final List OPERATING_SYSTEM_BEAN_CLASS_NAMES = Arrays.asList( + "com.sun.management.OperatingSystemMXBean", // HotSpot + "com.ibm.lang.management.OperatingSystemMXBean" // J9 + ); + + private final OperatingSystemMXBean operatingSystemBean; + + @Nullable + private final Class operatingSystemBeanClass; + + @Nullable + private final MethodHandle systemCpuUsage; + + @Nullable + private final MethodHandle processCpuUsage; + + @Nullable + private final MethodHandle freeMemory; + + @Nullable + private final MethodHandle totalMemory; + + @Nullable + private final MethodHandle virtualProcessMemory; + + public SystemMetrics() { + this.operatingSystemBean = ManagementFactory.getOperatingSystemMXBean(); + this.operatingSystemBeanClass = getFirstClassFound(OPERATING_SYSTEM_BEAN_CLASS_NAMES); + this.systemCpuUsage = detectMethod("getSystemCpuLoad", double.class); + this.processCpuUsage = detectMethod("getProcessCpuLoad", double.class); + this.freeMemory = detectMethod("getFreePhysicalMemorySize", long.class); + this.totalMemory = detectMethod("getTotalPhysicalMemorySize", long.class); + this.virtualProcessMemory = detectMethod("getCommittedVirtualMemorySize", long.class); + } + + @Override + public void start(ElasticApmTracer tracer) { + bindTo(tracer.getMetricRegistry()); + } + + void bindTo(MetricRegistry metricRegistry) { + metricRegistry.addUnlessNan("system.cpu.total.norm.pct", Collections.emptyMap(), new DoubleSupplier() { + @Override + public double get() { + return invoke(systemCpuUsage); + } + }); + + metricRegistry.addUnlessNan("system.process.cpu.total.norm.pct", Collections.emptyMap(), new DoubleSupplier() { + @Override + public double get() { + return invoke(processCpuUsage); + } + }); + + metricRegistry.addUnlessNan("system.memory.total", Collections.emptyMap(), new DoubleSupplier() { + @Override + public double get() { + return invoke(totalMemory); + } + }); + + metricRegistry.addUnlessNan("system.memory.actual.free", Collections.emptyMap(), new DoubleSupplier() { + @Override + public double get() { + return invoke(freeMemory); + } + }); + + metricRegistry.addUnlessNan("system.process.memory.size", Collections.emptyMap(), new DoubleSupplier() { + @Override + public double get() { + return invoke(virtualProcessMemory); + } + }); + } + + private double invoke(@Nullable MethodHandle method) { + try { + return method != null ? (double) method.invoke(operatingSystemBean) : Double.NaN; + } catch (Throwable e) { + return Double.NaN; + } + } + + @Nullable + private MethodHandle detectMethod(String name, Class returnType) { + if (operatingSystemBeanClass == null) { + return null; + } + try { + // ensure the Bean we have is actually an instance of the interface + operatingSystemBeanClass.cast(operatingSystemBean); + return MethodHandles.lookup().findVirtual(operatingSystemBeanClass, name, MethodType.methodType(returnType)); + } catch (ClassCastException | NoSuchMethodException | SecurityException | IllegalAccessException e) { + return null; + } + } + + @Nullable + private Class getFirstClassFound(List classNames) { + for (String className : classNames) { + try { + return Class.forName(className); + } catch (ClassNotFoundException ignore) { + } + } + return null; + } + + @Override + public void stop() throws Exception { + } +} diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/builtin/package-info.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/builtin/package-info.java new file mode 100644 index 0000000000..384ed50bde --- /dev/null +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/builtin/package-info.java @@ -0,0 +1,28 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 Elastic and contributors + * %% + * 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. + * #L% + */ +/** + * We can't use micrometer directly as it does not support Java 7 which we want to support with the Java agent + * Also, micrometer does not have a native concept of {@link co.elastic.apm.agent.metrics.MetricSet}s, + * so converting to metricsets in a reporter (group metrics by tags) would introduce some overhead. + */ +@NonnullApi +package co.elastic.apm.agent.metrics.builtin; + +import co.elastic.apm.agent.annotation.NonnullApi; diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/package-info.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/package-info.java new file mode 100644 index 0000000000..4682641d70 --- /dev/null +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/package-info.java @@ -0,0 +1,23 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 Elastic and contributors + * %% + * 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. + * #L% + */ +@NonnullApi +package co.elastic.apm.agent.metrics; + +import co.elastic.apm.agent.annotation.NonnullApi; diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/ApmServerReporter.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/ApmServerReporter.java index a2089ea1a5..1acf0970a7 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/ApmServerReporter.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/ApmServerReporter.java @@ -22,7 +22,8 @@ import co.elastic.apm.agent.impl.error.ErrorCapture; import co.elastic.apm.agent.impl.transaction.Span; import co.elastic.apm.agent.impl.transaction.Transaction; -import co.elastic.apm.agent.objectpool.Recyclable; +import co.elastic.apm.agent.metrics.MetricRegistry; +import co.elastic.apm.agent.util.ExecutorUtils; import co.elastic.apm.agent.util.MathUtils; import com.lmax.disruptor.EventFactory; import com.lmax.disruptor.EventTranslator; @@ -32,7 +33,9 @@ import com.lmax.disruptor.dsl.Disruptor; import com.lmax.disruptor.dsl.ProducerType; +import javax.annotation.Nullable; import java.util.concurrent.Future; +import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -76,6 +79,8 @@ public void translateTo(ReportingEvent event, long sequence, ErrorCapture error) private final boolean dropTransactionIfQueueFull; private final ReportingEventHandler reportingEventHandler; private final boolean syncReport; + @Nullable + private ScheduledThreadPoolExecutor metricsReportingScheduler; public ApmServerReporter(boolean dropTransactionIfQueueFull, ReporterConfiguration reporterConfiguration, ReportingEventHandler reportingEventHandler) { @@ -203,6 +208,9 @@ private boolean isEventProcessed(long sequence) { public void close() { disruptor.shutdown(); reportingEventHandler.close(); + if (metricsReportingScheduler != null) { + metricsReportingScheduler.shutdown(); + } } @Override @@ -215,7 +223,25 @@ public void report(ErrorCapture error) { } } - private boolean tryAddEventToRingBuffer(E event, EventTranslatorOneArg eventTranslator) { + @Override + public void scheduleMetricReporting(final MetricRegistry metricRegistry, long intervalMs) { + if (intervalMs > 0 && metricsReportingScheduler == null) { + metricsReportingScheduler = ExecutorUtils.createSingleThreadSchedulingDeamonPool("apm-metrics-reporter", 1); + metricsReportingScheduler.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + disruptor.publishEvent(new EventTranslatorOneArg() { + @Override + public void translateTo(ReportingEvent event, long sequence, MetricRegistry metricRegistry) { + event.reportMetrics(metricRegistry); + } + }, metricRegistry); + } + }, intervalMs, intervalMs, TimeUnit.MILLISECONDS); + } + } + + private boolean tryAddEventToRingBuffer(E event, EventTranslatorOneArg eventTranslator) { if (dropTransactionIfQueueFull) { boolean queueFull = !disruptor.getRingBuffer().tryPublishEvent(eventTranslator, event); if (queueFull) { diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/IntakeV2ReportingEventHandler.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/IntakeV2ReportingEventHandler.java index ae29163fb7..a7de06e3c4 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/IntakeV2ReportingEventHandler.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/IntakeV2ReportingEventHandler.java @@ -191,6 +191,8 @@ private void writeEvent(ReportingEvent event) { currentlyTransmitting++; payloadSerializer.serializeErrorNdJson(event.getError()); event.getError().recycle(); + } else if (event.getMetricRegistry() != null) { + payloadSerializer.serializeMetrics(event.getMetricRegistry()); } } @@ -249,8 +251,8 @@ private HttpURLConnection startRequest() { URL getUrl() throws MalformedURLException { URL serverUrl = serverUrlIterator.get(); String path = serverUrl.getPath(); - if(path.endsWith("/")) { - path = path.substring(0, path.length()-1); + if (path.endsWith("/")) { + path = path.substring(0, path.length() - 1); } return new URL(serverUrl, path + INTAKE_V2_URL); } diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/Reporter.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/Reporter.java index ffe07eff35..7575c24efe 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/Reporter.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/Reporter.java @@ -22,6 +22,7 @@ import co.elastic.apm.agent.impl.error.ErrorCapture; import co.elastic.apm.agent.impl.transaction.Span; import co.elastic.apm.agent.impl.transaction.Transaction; +import co.elastic.apm.agent.metrics.MetricRegistry; import java.io.Closeable; import java.util.concurrent.Future; @@ -41,4 +42,6 @@ public interface Reporter extends Closeable { void close(); void report(ErrorCapture error); + + void scheduleMetricReporting(MetricRegistry metricRegistry, long intervalMs); } diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/ReporterConfiguration.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/ReporterConfiguration.java index 27a0c0d9a2..f8220b8fe8 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/ReporterConfiguration.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/ReporterConfiguration.java @@ -32,6 +32,8 @@ import java.util.Collections; import java.util.List; +import static co.elastic.apm.agent.configuration.validation.RangeValidator.isNotInRange; + public class ReporterConfiguration extends ConfigurationOptionProvider { public static final String REPORTER_CATEGORY = "Reporter"; private final ConfigurationOption secretToken = ConfigurationOption.stringOption() @@ -133,6 +135,15 @@ public class ReporterConfiguration extends ConfigurationOptionProvider { "Allowed byte units are `b`, `kb` and `mb`. `1kb` is equal to `1024b`.") .buildWithDefault(ByteValue.of("768kb")); + private final ConfigurationOption metricsInterval = TimeDurationValueConverter.durationOption("s") + .key("metrics_interval") + .configurationCategory(REPORTER_CATEGORY) + .description("The interval at which the agent sends metrics to the APM Server.\n" + + "Must be at least `1s`.\n" + + "Set to `0s` to deactivate.") + .addValidator(isNotInRange(TimeDuration.of("1ms"), TimeDuration.of("999ms"))) + .buildWithDefault(TimeDuration.of("30s")); + @Nullable public String getSecretToken() { return secretToken.get(); @@ -173,4 +184,8 @@ public TimeDuration getApiRequestTime() { public long getApiRequestSize() { return apiRequestSize.get().getBytes(); } + + public long getMetricsIntervalMs() { + return metricsInterval.get().getMillis(); + } } diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/ReporterFactory.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/ReporterFactory.java index d7a6a9df85..e64c196307 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/ReporterFactory.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/ReporterFactory.java @@ -40,9 +40,6 @@ public class ReporterFactory { - private static final Logger logger = LoggerFactory.getLogger(ReporterFactory.class); - private final String userAgent = getUserAgent(); - public Reporter createReporter(ConfigurationRegistry configurationRegistry, @Nullable String frameworkName, @Nullable String frameworkVersion) { final ReporterConfiguration reporterConfiguration = configurationRegistry.getConfig(ReporterConfiguration.class); diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/ReportingEvent.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/ReportingEvent.java index fe85decf76..bd5c23f50e 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/ReportingEvent.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/ReportingEvent.java @@ -22,11 +22,13 @@ import co.elastic.apm.agent.impl.error.ErrorCapture; import co.elastic.apm.agent.impl.transaction.Span; import co.elastic.apm.agent.impl.transaction.Transaction; +import co.elastic.apm.agent.metrics.MetricRegistry; import javax.annotation.Nullable; import static co.elastic.apm.agent.report.ReportingEvent.ReportingEventType.ERROR; import static co.elastic.apm.agent.report.ReportingEvent.ReportingEventType.FLUSH; +import static co.elastic.apm.agent.report.ReportingEvent.ReportingEventType.METRICS; import static co.elastic.apm.agent.report.ReportingEvent.ReportingEventType.SPAN; import static co.elastic.apm.agent.report.ReportingEvent.ReportingEventType.TRANSACTION; @@ -39,12 +41,15 @@ public class ReportingEvent { private ErrorCapture error; @Nullable private Span span; + @Nullable + private MetricRegistry metricRegistry; public void resetState() { this.transaction = null; this.type = null; this.error = null; this.span = null; + this.metricRegistry = null; } @Nullable @@ -86,7 +91,17 @@ public void setSpan(Span span) { this.type = SPAN; } + public void reportMetrics(MetricRegistry metricRegistry) { + this.metricRegistry = metricRegistry; + this.type = METRICS; + } + + @Nullable + public MetricRegistry getMetricRegistry() { + return metricRegistry; + } + enum ReportingEventType { - FLUSH, TRANSACTION, SPAN, ERROR + FLUSH, TRANSACTION, SPAN, ERROR, METRICS } } diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/DslJsonSerializer.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/DslJsonSerializer.java index 91a49a302f..b2a5dea8ad 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/DslJsonSerializer.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/DslJsonSerializer.java @@ -46,6 +46,7 @@ import co.elastic.apm.agent.impl.transaction.SpanCount; import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.Transaction; +import co.elastic.apm.agent.metrics.MetricRegistry; import co.elastic.apm.agent.util.PotentiallyMultiValuedMap; import com.dslplatform.json.BoolConverter; import com.dslplatform.json.DslJson; @@ -190,6 +191,11 @@ public int getBufferSize() { return jw.size(); } + @Override + public void serializeMetrics(MetricRegistry metricRegistry) { + metricRegistry.serialize(jw, replaceBuilder); + } + private void serializeErrorPayload(ErrorPayload payload) { jw.writeByte(JsonWriter.OBJECT_START); serializeService(payload.getService()); @@ -616,35 +622,39 @@ private void serializeContext(final TransactionContext context) { // visible for testing void serializeTags(Map value) { + serializeTags(value, replaceBuilder, jw); + } + + public static void serializeTags(Map value, StringBuilder replaceBuilder, JsonWriter jw) { jw.writeByte(OBJECT_START); final int size = value.size(); if (size > 0) { final Iterator> iterator = value.entrySet().iterator(); Map.Entry kv = iterator.next(); - writeStringValue(sanitizeTagKey(kv.getKey())); + writeStringValue(sanitizeTagKey(kv.getKey(), replaceBuilder), replaceBuilder, jw); jw.writeByte(JsonWriter.SEMI); - writeStringValue(kv.getValue()); + writeStringValue(kv.getValue(), replaceBuilder, jw); for (int i = 1; i < size; i++) { jw.writeByte(COMMA); kv = iterator.next(); - writeStringValue(sanitizeTagKey(kv.getKey())); + writeStringValue(sanitizeTagKey(kv.getKey(), replaceBuilder), replaceBuilder, jw); jw.writeByte(JsonWriter.SEMI); - writeStringValue(kv.getValue()); + writeStringValue(kv.getValue(), replaceBuilder, jw); } } jw.writeByte(OBJECT_END); } - private CharSequence sanitizeTagKey(String key) { + private static CharSequence sanitizeTagKey(String key, StringBuilder replaceBuilder) { for (int i = 0; i < DISALLOWED_IN_TAG_KEY.length; i++) { if (key.contains(DISALLOWED_IN_TAG_KEY[i])) { - return replaceAll(key, DISALLOWED_IN_TAG_KEY, "_"); + return replaceAll(key, DISALLOWED_IN_TAG_KEY, "_", replaceBuilder); } } return key; } - private CharSequence replaceAll(String s, String[] stringsToReplace, String replacement) { + private static CharSequence replaceAll(String s, String[] stringsToReplace, String replacement, StringBuilder replaceBuilder) { // uses a instance variable StringBuilder to avoid allocations replaceBuilder.setLength(0); replaceBuilder.append(s); @@ -654,7 +664,7 @@ private CharSequence replaceAll(String s, String[] stringsToReplace, String repl return replaceBuilder; } - private void replace(StringBuilder replaceBuilder, String toReplace, String replacement) { + private static void replace(StringBuilder replaceBuilder, String toReplace, String replacement) { for (int i = replaceBuilder.indexOf(toReplace); i != -1; i = replaceBuilder.indexOf(toReplace)) { replaceBuilder.replace(i, i + replacement.length(), replacement); } @@ -789,6 +799,10 @@ void writeField(final String fieldName, @Nullable final String value) { } private void writeStringBuilderValue(StringBuilder value) { + writeStringBuilderValue(value, jw); + } + + private static void writeStringBuilderValue(StringBuilder value, JsonWriter jw) { if (value.length() > MAX_VALUE_LENGTH) { value.setLength(MAX_VALUE_LENGTH - 1); value.append('…'); @@ -797,10 +811,14 @@ private void writeStringBuilderValue(StringBuilder value) { } private void writeStringValue(CharSequence value) { + writeStringValue(value, replaceBuilder, jw); + } + + private static void writeStringValue(CharSequence value, StringBuilder replaceBuilder, JsonWriter jw) { if (value.length() > MAX_VALUE_LENGTH) { replaceBuilder.setLength(0); replaceBuilder.append(value, 0, Math.min(value.length(), MAX_VALUE_LENGTH + 1)); - writeStringBuilderValue(replaceBuilder); + writeStringBuilderValue(replaceBuilder, jw); } else { jw.writeString(value); } @@ -867,13 +885,17 @@ void writeLastField(final String fieldName, @Nullable final String value) { } } - private void writeFieldName(final String fieldName) { + public static void writeFieldName(final String fieldName, final JsonWriter jw) { jw.writeByte(JsonWriter.QUOTE); jw.writeAscii(fieldName); jw.writeByte(JsonWriter.QUOTE); jw.writeByte(JsonWriter.SEMI); } + private void writeFieldName(final String fieldName) { + writeFieldName(fieldName, jw); + } + private void writeField(final String fieldName, final List values) { if (values.size() > 0) { writeFieldName(fieldName); diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/PayloadSerializer.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/PayloadSerializer.java index 7a2b059354..b76e92d232 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/PayloadSerializer.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/PayloadSerializer.java @@ -24,6 +24,7 @@ import co.elastic.apm.agent.impl.payload.Payload; import co.elastic.apm.agent.impl.transaction.Span; import co.elastic.apm.agent.impl.transaction.Transaction; +import co.elastic.apm.agent.metrics.MetricRegistry; import java.io.IOException; import java.io.OutputStream; @@ -59,4 +60,6 @@ public interface PayloadSerializer { * @return the number of bytes which are currently buffered */ int getBufferSize(); + + void serializeMetrics(MetricRegistry metricRegistry); } diff --git a/apm-agent-core/src/main/resources/META-INF/NOTICE b/apm-agent-core/src/main/resources/META-INF/NOTICE index f5aa88113d..7fde0822d1 100644 --- a/apm-agent-core/src/main/resources/META-INF/NOTICE +++ b/apm-agent-core/src/main/resources/META-INF/NOTICE @@ -4,3 +4,7 @@ under the Apache License 2.0. See: - co.elastic.apm.agent.configuration.source.SystemPropertyConfigurationSource - co.elastic.apm.agent.configuration.StartupInfo - co.elastic.apm.agent.bci.bytebuddy.MethodHierarchyMatcher + +This product includes software derived from micrometer, +under the Apache License 2.0. See: + - co.elastic.apm.agent.metrics.ProcessorMetrics diff --git a/apm-agent-core/src/main/resources/META-INF/services/co.elastic.apm.agent.context.LifecycleListener b/apm-agent-core/src/main/resources/META-INF/services/co.elastic.apm.agent.context.LifecycleListener index f4ceb91105..5767a46ac2 100644 --- a/apm-agent-core/src/main/resources/META-INF/services/co.elastic.apm.agent.context.LifecycleListener +++ b/apm-agent-core/src/main/resources/META-INF/services/co.elastic.apm.agent.context.LifecycleListener @@ -1,3 +1,5 @@ co.elastic.apm.agent.configuration.StartupInfo co.elastic.apm.agent.bci.OsgiBootDelegationEnabler co.elastic.apm.agent.bci.MatcherTimerLifecycleListener +co.elastic.apm.agent.metrics.builtin.JvmMemoryMetrics +co.elastic.apm.agent.metrics.builtin.SystemMetrics diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/MockReporter.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/MockReporter.java index 8a842de9c6..618efa99b3 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/MockReporter.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/MockReporter.java @@ -26,6 +26,7 @@ import co.elastic.apm.agent.impl.stacktrace.StacktraceConfiguration; import co.elastic.apm.agent.impl.transaction.Span; import co.elastic.apm.agent.impl.transaction.Transaction; +import co.elastic.apm.agent.metrics.MetricRegistry; import co.elastic.apm.agent.report.Reporter; import co.elastic.apm.agent.report.serialize.DslJsonSerializer; import com.fasterxml.jackson.databind.JsonNode; @@ -143,6 +144,10 @@ public void report(ErrorCapture error) { errors.add(error); } + @Override + public void scheduleMetricReporting(MetricRegistry metricRegistry, long intervalMs) { + // noop + } public Span getFirstSpan() { return spans.get(0); diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/configuration/validation/RangeValidatorTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/configuration/validation/RangeValidatorTest.java new file mode 100644 index 0000000000..c6ca6d0eb7 --- /dev/null +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/configuration/validation/RangeValidatorTest.java @@ -0,0 +1,73 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 Elastic and contributors + * %% + * 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. + * #L% + */ +package co.elastic.apm.agent.configuration.validation; + +import co.elastic.apm.agent.configuration.converter.TimeDuration; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class RangeValidatorTest { + + @Test + void testRange() { + final RangeValidator validator = RangeValidator.isInRange(1, 3); + assertThatThrownBy(() -> validator.assertValid(0)).isInstanceOf(IllegalArgumentException.class); + validator.assertValid(1); + validator.assertValid(2); + validator.assertValid(3); + assertThatThrownBy(() -> validator.assertValid(4)).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testNotInRange() { + final RangeValidator validator = RangeValidator.isNotInRange(1, 3); + validator.assertValid(0); + assertThatThrownBy(() -> validator.assertValid(1)).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> validator.assertValid(2)).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> validator.assertValid(3)).isInstanceOf(IllegalArgumentException.class); + validator.assertValid(4); + } + + @Test + void testMin() { + final RangeValidator validator = RangeValidator.min(1); + assertThatThrownBy(() -> validator.assertValid(0)).isInstanceOf(IllegalArgumentException.class); + validator.assertValid(1); + validator.assertValid(2); + } + + @Test + void testMax() { + final RangeValidator validator = RangeValidator.max(3); + validator.assertValid(1); + validator.assertValid(2); + validator.assertValid(3); + assertThatThrownBy(() -> validator.assertValid(4)).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testTimeDuration() { + final RangeValidator validator = RangeValidator.min(TimeDuration.of("1s")); + assertThatThrownBy(() -> validator.assertValid(TimeDuration.of("0s"))).isInstanceOf(IllegalArgumentException.class); + validator.assertValid(TimeDuration.of("1s")); + validator.assertValid(TimeDuration.of("2s")); + } +} diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/MetricSetSerializationTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/MetricSetSerializationTest.java new file mode 100644 index 0000000000..7d12f3ea4c --- /dev/null +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/MetricSetSerializationTest.java @@ -0,0 +1,50 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 Elastic and contributors + * %% + * 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. + * #L% + */ +package co.elastic.apm.agent.metrics; + +import com.dslplatform.json.DslJson; +import com.dslplatform.json.JsonWriter; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; + +class MetricSetSerializationTest { + + private JsonWriter jw = new DslJson<>().newWriter(); + private ObjectMapper objectMapper = new ObjectMapper(); + + @Test + void testSerialization() throws IOException { + final MetricSet metricSet = new MetricSet(Collections.singletonMap("foo.bar", "baz")); + metricSet.add("foo.bar", () -> 42); + metricSet.add("bar.baz", () -> 42); + metricSet.serialize(System.currentTimeMillis() * 1000, new StringBuilder(), jw); + final String metricSetAsString = jw.toString(); + System.out.println(metricSetAsString); + final JsonNode jsonNode = objectMapper.readTree(metricSetAsString); + assertThat(jsonNode.get("metricset").get("samples").get("foo.bar").get("value").doubleValue()).isEqualTo(42); + + } +} diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/builtin/SystemMetricsTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/builtin/SystemMetricsTest.java new file mode 100644 index 0000000000..57e398e9ea --- /dev/null +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/builtin/SystemMetricsTest.java @@ -0,0 +1,43 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 Elastic and contributors + * %% + * 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. + * #L% + */ +package co.elastic.apm.agent.metrics.builtin; + +import co.elastic.apm.agent.metrics.MetricRegistry; +import org.junit.jupiter.api.Test; + +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; + +class SystemMetricsTest { + + private MetricRegistry metricRegistry = new MetricRegistry(); + private SystemMetrics systemMetrics = new SystemMetrics(); + + @Test + void testSystemMetrics() { + systemMetrics.bindTo(metricRegistry); + assertThat(metricRegistry.get("system.cpu.total.norm.pct", Collections.emptyMap())).isBetween(0.0, 1.0); + assertThat(metricRegistry.get("system.process.cpu.total.norm.pct", Collections.emptyMap())).isBetween(0.0, 1.0); + assertThat(metricRegistry.get("system.memory.total", Collections.emptyMap())).isGreaterThan(0.0); + assertThat(metricRegistry.get("system.memory.actual.free", Collections.emptyMap())).isGreaterThan(0.0); + assertThat(metricRegistry.get("system.process.memory.size", Collections.emptyMap())).isGreaterThan(0.0); + } +} From 85c50cb704f282a5aa328cc4c68d6695f32c2f02 Mon Sep 17 00:00:00 2001 From: Felix Barnsteiner Date: Thu, 13 Dec 2018 16:35:13 +0100 Subject: [PATCH 02/10] Make animal sniffer happy --- .../main/java/co/elastic/apm/agent/metrics/MetricRegistry.java | 3 ++- .../src/main/java/co/elastic/apm/agent/metrics/MetricSet.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricRegistry.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricRegistry.java index 534c96ed52..f015be293c 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricRegistry.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricRegistry.java @@ -23,11 +23,12 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; public class MetricRegistry { private static final byte NEW_LINE = '\n'; - private final Map, MetricSet> metricSets = new ConcurrentHashMap<>(); + private final ConcurrentMap, MetricSet> metricSets = new ConcurrentHashMap<>(); public void addUnlessNan(String name, Map tags, DoubleSupplier metric) { if (!Double.isNaN(metric.get())) { diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricSet.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricSet.java index 2727b684bd..a046290edc 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricSet.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricSet.java @@ -26,10 +26,11 @@ import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; public class MetricSet { private final Map tags; - private final Map samples = new ConcurrentHashMap<>(); + private final ConcurrentMap samples = new ConcurrentHashMap<>(); MetricSet(Map tags) { this.tags = tags; From 4f180aceb5ef9081f90f0edcff10d895aa04508b Mon Sep 17 00:00:00 2001 From: Felix Barnsteiner Date: Mon, 17 Dec 2018 12:29:44 +0100 Subject: [PATCH 03/10] Add metrics for GC and allocations --- .../co/elastic/apm/api/AbstractSpanImpl.java | 19 ++++ .../apm/agent/metrics/MetricRegistry.java | 14 +++ .../agent/metrics/builtin/JvmGcMetrics.java | 90 +++++++++++++++++++ .../metrics/builtin/JvmMemoryMetrics.java | 6 +- .../agent/metrics/builtin/SystemMetrics.java | 6 +- ...lastic.apm.agent.context.LifecycleListener | 1 + .../metrics/builtin/JvmGcMetricsTest.java | 52 +++++++++++ .../metrics/builtin/JvmMemoryMetricsTest.java | 45 ++++++++++ .../metrics/builtin/SystemMetricsTest.java | 11 +++ docs/configuration.asciidoc | 37 ++++++++ 10 files changed, 275 insertions(+), 6 deletions(-) create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/builtin/JvmGcMetrics.java create mode 100644 apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/builtin/JvmGcMetricsTest.java create mode 100644 apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/builtin/JvmMemoryMetricsTest.java diff --git a/apm-agent-api/src/main/java/co/elastic/apm/api/AbstractSpanImpl.java b/apm-agent-api/src/main/java/co/elastic/apm/api/AbstractSpanImpl.java index 5635439e64..1828c2cbd9 100644 --- a/apm-agent-api/src/main/java/co/elastic/apm/api/AbstractSpanImpl.java +++ b/apm-agent-api/src/main/java/co/elastic/apm/api/AbstractSpanImpl.java @@ -1,3 +1,22 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 Elastic and contributors + * %% + * 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. + * #L% + */ package co.elastic.apm.api; import javax.annotation.Nonnull; diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricRegistry.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricRegistry.java index f015be293c..4215790a81 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricRegistry.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricRegistry.java @@ -19,6 +19,7 @@ */ package co.elastic.apm.agent.metrics; +import com.dslplatform.json.DslJson; import com.dslplatform.json.JsonWriter; import java.util.Map; @@ -36,6 +37,12 @@ public void addUnlessNan(String name, Map tags, DoubleSupplier m } } + public void addUnlessNegative(String name, Map tags, DoubleSupplier metric) { + if (metric.get() >= 0) { + add(name, tags, metric); + } + } + public void add(String name, Map tags, DoubleSupplier metric) { MetricSet metricSet = metricSets.get(tags); if (metricSet == null) { @@ -60,4 +67,11 @@ public double get(String name, Map tags) { } return Double.NaN; } + + @Override + public String toString() { + final JsonWriter jw = new DslJson<>().newWriter(); + serialize(jw, new StringBuilder()); + return jw.toString(); + } } diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/builtin/JvmGcMetrics.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/builtin/JvmGcMetrics.java new file mode 100644 index 0000000000..302c783e26 --- /dev/null +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/builtin/JvmGcMetrics.java @@ -0,0 +1,90 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 Elastic and contributors + * %% + * 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. + * #L% + */ +package co.elastic.apm.agent.metrics.builtin; + +import co.elastic.apm.agent.context.LifecycleListener; +import co.elastic.apm.agent.impl.ElasticApmTracer; +import co.elastic.apm.agent.metrics.DoubleSupplier; +import co.elastic.apm.agent.metrics.MetricRegistry; +import com.sun.management.ThreadMXBean; + +import java.lang.management.GarbageCollectorMXBean; +import java.lang.management.ManagementFactory; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class JvmGcMetrics implements LifecycleListener { + + private final List garbageCollectorMXBeans = ManagementFactory.getGarbageCollectorMXBeans(); + + @Override + public void start(ElasticApmTracer tracer) { + bindTo(tracer.getMetricRegistry()); + } + + void bindTo(final MetricRegistry registry) { + for (final GarbageCollectorMXBean garbageCollectorMXBean : garbageCollectorMXBeans) { + final Map tags = Collections.singletonMap("name", garbageCollectorMXBean.getName()); + registry.addUnlessNegative("jvm.gc.count", tags, new DoubleSupplier() { + @Override + public double get() { + return garbageCollectorMXBean.getCollectionCount(); + } + }); + registry.addUnlessNegative("jvm.gc.time", tags, new DoubleSupplier() { + @Override + public double get() { + return garbageCollectorMXBean.getCollectionTime(); + } + }); + } + + try { + // only refer to hotspot specific class via reflection to avoid linkage errors + Class.forName("com.sun.management.ThreadMXBean"); + // in reference to JMH's GC profiler (gc.alloc.rate) + registry.add("jvm.gc.alloc", Collections.emptyMap(), + (DoubleSupplier) Class.forName(getClass().getName() + "$HotspotAllocationSupplier").getEnumConstants()[0]); + } catch (ClassNotFoundException ignore) { + } + } + + enum HotspotAllocationSupplier implements DoubleSupplier { + INSTANCE; + + final ThreadMXBean threadMXBean = (ThreadMXBean) ManagementFactory.getThreadMXBean(); + + @Override + public double get() { + long allocatedBytes = 0; + for (final long threadAllocatedBytes : threadMXBean.getThreadAllocatedBytes(threadMXBean.getAllThreadIds())) { + if (threadAllocatedBytes > 0) { + allocatedBytes += threadAllocatedBytes; + } + } + return allocatedBytes; + } + } + + @Override + public void stop() throws Exception { + } +} diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/builtin/JvmMemoryMetrics.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/builtin/JvmMemoryMetrics.java index 4eb53ef6ed..9d15a84763 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/builtin/JvmMemoryMetrics.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/builtin/JvmMemoryMetrics.java @@ -36,9 +36,9 @@ public void start(ElasticApmTracer tracer) { bindTo(tracer.getMetricRegistry()); } - private void bindTo(final MetricRegistry registry) { - register(registry, "nonheap", ManagementFactory.getPlatformMXBean(MemoryMXBean.class).getNonHeapMemoryUsage()); - register(registry, "heap", ManagementFactory.getPlatformMXBean(MemoryMXBean.class).getNonHeapMemoryUsage()); + void bindTo(final MetricRegistry registry) { + register(registry, "non_heap", ManagementFactory.getPlatformMXBean(MemoryMXBean.class).getNonHeapMemoryUsage()); + register(registry, "heap", ManagementFactory.getPlatformMXBean(MemoryMXBean.class).getHeapMemoryUsage()); } private void register(final MetricRegistry registry, final String area, final MemoryUsage memoryUsage) { diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/builtin/SystemMetrics.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/builtin/SystemMetrics.java index 5add2a7279..fe6301a160 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/builtin/SystemMetrics.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/builtin/SystemMetrics.java @@ -92,14 +92,14 @@ public void start(ElasticApmTracer tracer) { } void bindTo(MetricRegistry metricRegistry) { - metricRegistry.addUnlessNan("system.cpu.total.norm.pct", Collections.emptyMap(), new DoubleSupplier() { + metricRegistry.addUnlessNegative("system.cpu.total.norm.pct", Collections.emptyMap(), new DoubleSupplier() { @Override public double get() { return invoke(systemCpuUsage); } }); - metricRegistry.addUnlessNan("system.process.cpu.total.norm.pct", Collections.emptyMap(), new DoubleSupplier() { + metricRegistry.addUnlessNegative("system.process.cpu.total.norm.pct", Collections.emptyMap(), new DoubleSupplier() { @Override public double get() { return invoke(processCpuUsage); @@ -120,7 +120,7 @@ public double get() { } }); - metricRegistry.addUnlessNan("system.process.memory.size", Collections.emptyMap(), new DoubleSupplier() { + metricRegistry.addUnlessNegative("system.process.memory.size", Collections.emptyMap(), new DoubleSupplier() { @Override public double get() { return invoke(virtualProcessMemory); diff --git a/apm-agent-core/src/main/resources/META-INF/services/co.elastic.apm.agent.context.LifecycleListener b/apm-agent-core/src/main/resources/META-INF/services/co.elastic.apm.agent.context.LifecycleListener index 5767a46ac2..10f18aebe7 100644 --- a/apm-agent-core/src/main/resources/META-INF/services/co.elastic.apm.agent.context.LifecycleListener +++ b/apm-agent-core/src/main/resources/META-INF/services/co.elastic.apm.agent.context.LifecycleListener @@ -3,3 +3,4 @@ co.elastic.apm.agent.bci.OsgiBootDelegationEnabler co.elastic.apm.agent.bci.MatcherTimerLifecycleListener co.elastic.apm.agent.metrics.builtin.JvmMemoryMetrics co.elastic.apm.agent.metrics.builtin.SystemMetrics +co.elastic.apm.agent.metrics.builtin.JvmGcMetrics diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/builtin/JvmGcMetricsTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/builtin/JvmGcMetricsTest.java new file mode 100644 index 0000000000..50c7bdb0ef --- /dev/null +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/builtin/JvmGcMetricsTest.java @@ -0,0 +1,52 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 Elastic and contributors + * %% + * 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. + * #L% + */ +package co.elastic.apm.agent.metrics.builtin; + +import co.elastic.apm.agent.metrics.MetricRegistry; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +class JvmGcMetricsTest { + + private final JvmGcMetrics jvmGcMetrics = new JvmGcMetrics(); + private MetricRegistry registry = mock(MetricRegistry.class); + + @Test + void testGcMetrics() { + jvmGcMetrics.bindTo(registry); + verify(registry, atLeastOnce()).addUnlessNegative(eq("jvm.gc.count"), any(), any()); + verify(registry, atLeastOnce()).addUnlessNegative(eq("jvm.gc.time"), any(), any()); + verify(registry, atLeastOnce()).add(eq("jvm.gc.alloc"), any(), any()); + } + + @Test + void testGetAllocatedBytes() { + final double snapshot = JvmGcMetrics.HotspotAllocationSupplier.INSTANCE.get(); + assertThat(snapshot).isPositive(); + new Object(); + assertThat(JvmGcMetrics.HotspotAllocationSupplier.INSTANCE.get()).isGreaterThan(snapshot); + } +} diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/builtin/JvmMemoryMetricsTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/builtin/JvmMemoryMetricsTest.java new file mode 100644 index 0000000000..a6004768ce --- /dev/null +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/builtin/JvmMemoryMetricsTest.java @@ -0,0 +1,45 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 Elastic and contributors + * %% + * 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. + * #L% + */ +package co.elastic.apm.agent.metrics.builtin; + +import co.elastic.apm.agent.metrics.MetricRegistry; +import org.junit.jupiter.api.Test; + +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; + +class JvmMemoryMetricsTest { + + private final JvmMemoryMetrics jvmMemoryMetrics = new JvmMemoryMetrics(); + + @Test + void testMetrics() { + final MetricRegistry registry = new MetricRegistry(); + jvmMemoryMetrics.bindTo(registry); + + assertThat(registry.get("jvm.memory.heap.used", Collections.emptyMap())).isNotZero(); + assertThat(registry.get("jvm.memory.heap.committed", Collections.emptyMap())).isNotZero(); + assertThat(registry.get("jvm.memory.heap.max", Collections.emptyMap())).isNotZero(); + assertThat(registry.get("jvm.memory.non_heap.used", Collections.emptyMap())).isNotZero(); + assertThat(registry.get("jvm.memory.non_heap.committed", Collections.emptyMap())).isNotZero(); + assertThat(registry.get("jvm.memory.non_heap.max", Collections.emptyMap())).isNotZero(); + } +} diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/builtin/SystemMetricsTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/builtin/SystemMetricsTest.java index 57e398e9ea..68a28fd06b 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/builtin/SystemMetricsTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/builtin/SystemMetricsTest.java @@ -34,10 +34,21 @@ class SystemMetricsTest { @Test void testSystemMetrics() { systemMetrics.bindTo(metricRegistry); + // makes sure system.cpu.total.norm.pct does not return NaN + consumeCpu(); assertThat(metricRegistry.get("system.cpu.total.norm.pct", Collections.emptyMap())).isBetween(0.0, 1.0); assertThat(metricRegistry.get("system.process.cpu.total.norm.pct", Collections.emptyMap())).isBetween(0.0, 1.0); assertThat(metricRegistry.get("system.memory.total", Collections.emptyMap())).isGreaterThan(0.0); assertThat(metricRegistry.get("system.memory.actual.free", Collections.emptyMap())).isGreaterThan(0.0); assertThat(metricRegistry.get("system.process.memory.size", Collections.emptyMap())).isGreaterThan(0.0); } + + private void consumeCpu() { + int result = 1; + for (int i = 0; i < 10000; i++) { + result += Math.random() * i; + } + // forces a side-effect so that the JIT can't optimize away this code + System.out.println(result); + } } diff --git a/docs/configuration.asciidoc b/docs/configuration.asciidoc index 6d5005b8f4..c5dca204f2 100644 --- a/docs/configuration.asciidoc +++ b/docs/configuration.asciidoc @@ -768,6 +768,31 @@ Allowed byte units are `b`, `kb` and `mb`. `1kb` is equal to `1024b`. | `elastic.apm.api_request_size` | `api_request_size` | `ELASTIC_APM_API_REQUEST_SIZE` |============ +[float] +[[config-metrics-interval]] +==== `metrics_interval` + +The interval at which the agent sends metrics to the APM Server. +Must be at least `1s`. +Set to `0s` to deactivate. + +Supports the duration suffixes `ms`, `s` and `m`. +Example: `30s`. +The default unit for this option is `s` + +[options="header"] +|============ +| Default | Type | Dynamic +| `30s` | TimeDuration | false +|============ + + +[options="header"] +|============ +| Java System Properties | Property file | Environment +| `elastic.apm.metrics_interval` | `metrics_interval` | `ELASTIC_APM_METRICS_INTERVAL` +|============ + [[config-stacktrace]] === Stacktrace configuration options [float] @@ -1241,6 +1266,18 @@ The default unit for this option is `ms` # # api_request_size=768kb +# The interval at which the agent sends metrics to the APM Server. +# Must be at least `1s`. +# Set to `0s` to deactivate. +# +# This setting can not be changed at runtime. Changes require a restart of the application. +# Type: TimeDuration +# Supports the duration suffixes ms, s and m. Example: 30s. +# The default unit for this option is s. +# Default value: 30s +# +# metrics_interval=30s + ############################################ # Stacktrace # ############################################ From 35f100661808579c10e920f62fc135c4c1991523 Mon Sep 17 00:00:00 2001 From: Felix Barnsteiner Date: Mon, 17 Dec 2018 13:01:38 +0100 Subject: [PATCH 04/10] Add javadoc --- .../apm/agent/metrics/MetricRegistry.java | 45 +++++++++++++++++++ .../elastic/apm/agent/metrics/MetricSet.java | 14 ++++++ 2 files changed, 59 insertions(+) diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricRegistry.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricRegistry.java index 4215790a81..bfe6488d4a 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricRegistry.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricRegistry.java @@ -26,23 +26,68 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +/** + * A registry for metrics. + *

+ * Currently only holds gauges. + * There are plans to add support for histogram-based timers. + *

+ */ public class MetricRegistry { private static final byte NEW_LINE = '\n'; + + /** + * Groups {@link MetricSet}s by their unique tags. + */ private final ConcurrentMap, MetricSet> metricSets = new ConcurrentHashMap<>(); + /** + * Same as {@link #add(String, Map, DoubleSupplier)} but only adds the metric + * if the {@link DoubleSupplier} does not return {@link Double#NaN} + * + * @param name the name of the metric + * @param tags tags for the metric. + * Tags can be used to create different graphs based for each value of a specific tag name, using a terms aggregation. + * Note that there will be a {@link MetricSet} created for each distinct set of tags. + * @param metric this supplier will be called for every reporting cycle + * ({@link co.elastic.apm.agent.report.ReporterConfiguration#metricsInterval metrics_interval)}) + * @see #add(String, Map, DoubleSupplier) + */ public void addUnlessNan(String name, Map tags, DoubleSupplier metric) { if (!Double.isNaN(metric.get())) { add(name, tags, metric); } } + /** + * Same as {@link #add(String, Map, DoubleSupplier)} but only adds the metric + * if the {@link DoubleSupplier} returns a positive number or zero. + * + * @param name the name of the metric + * @param tags tags for the metric. + * Tags can be used to create different graphs based for each value of a specific tag name, using a terms aggregation. + * Note that there will be a {@link MetricSet} created for each distinct set of tags. + * @param metric this supplier will be called for every reporting cycle + * ({@link co.elastic.apm.agent.report.ReporterConfiguration#metricsInterval metrics_interval)}) + * @see #add(String, Map, DoubleSupplier) + */ public void addUnlessNegative(String name, Map tags, DoubleSupplier metric) { if (metric.get() >= 0) { add(name, tags, metric); } } + /** + * Adds a gauge to the metric registry. + * + * @param name the name of the metric + * @param tags tags for the metric. + * Tags can be used to create different graphs based for each value of a specific tag name, using a terms aggregation. + * Note that there will be a {@link MetricSet} created for each distinct set of tags. + * @param metric this supplier will be called for every reporting cycle + * ({@link co.elastic.apm.agent.report.ReporterConfiguration#metricsInterval metrics_interval)}) + */ public void add(String name, Map tags, DoubleSupplier metric) { MetricSet metricSet = metricSets.get(tags); if (metricSet == null) { diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricSet.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricSet.java index a046290edc..77bb8f739a 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricSet.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricSet.java @@ -28,6 +28,20 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +/** + * A metric set is a collection of metrics which have the same tags. + *

+ * A metric set corresponds to one document per + * {@link co.elastic.apm.agent.report.ReporterConfiguration#metricsInterval metrics_interval} in Elasticsearch. + * An alternative would be to have one document per metric but having one document for all metrics with the same tags saves disk space. + *

+ * Example of some serialized metric sets: + *
+ * {"metricset":{"timestamp":1545047730692000,"samples":{"jvm.gc.alloc":{"value":24089200.0}}}}
+ * {"metricset":{"timestamp":1545047730692000,"tags":{"name":"G1 Young Generation"},"samples":{"jvm.gc.time":{"value":0.0},"jvm.gc.count":{"value":0.0}}}}
+ * {"metricset":{"timestamp":1545047730692000,"tags":{"name":"G1 Old Generation"},  "samples":{"jvm.gc.time":{"value":0.0},"jvm.gc.count":{"value":0.0}}}}
+ * 
+ */ public class MetricSet { private final Map tags; private final ConcurrentMap samples = new ConcurrentHashMap<>(); From d012c5900101082bd83a8128b6882761c78d2860 Mon Sep 17 00:00:00 2001 From: Felix Barnsteiner Date: Mon, 17 Dec 2018 13:16:20 +0100 Subject: [PATCH 05/10] Move serialization code out of MetricRegistry/MetricSet --- .../apm/agent/metrics/MetricRegistry.java | 15 ++- .../elastic/apm/agent/metrics/MetricSet.java | 58 +----------- .../report/serialize/DslJsonSerializer.java | 2 +- .../serialize/MetricRegistrySerializer.java | 93 +++++++++++++++++++ .../MetricSetSerializationTest.java | 7 +- 5 files changed, 110 insertions(+), 65 deletions(-) create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/MetricRegistrySerializer.java rename apm-agent-core/src/test/java/co/elastic/apm/agent/{metrics => report/serialize}/MetricSetSerializationTest.java (85%) diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricRegistry.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricRegistry.java index bfe6488d4a..5b4de47728 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricRegistry.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricRegistry.java @@ -19,6 +19,7 @@ */ package co.elastic.apm.agent.metrics; +import co.elastic.apm.agent.report.serialize.MetricRegistrySerializer; import com.dslplatform.json.DslJson; import com.dslplatform.json.JsonWriter; @@ -35,8 +36,6 @@ */ public class MetricRegistry { - private static final byte NEW_LINE = '\n'; - /** * Groups {@link MetricSet}s by their unique tags. */ @@ -98,11 +97,7 @@ public void add(String name, Map tags, DoubleSupplier metric) { } public void serialize(JsonWriter jw, StringBuilder replaceBuilder) { - final long timestamp = System.currentTimeMillis() * 1000; - for (MetricSet metricSet : metricSets.values()) { - metricSet.serialize(timestamp, replaceBuilder, jw); - jw.writeByte(NEW_LINE); - } + MetricRegistrySerializer.serialize(this, replaceBuilder, jw); } public double get(String name, Map tags) { @@ -116,7 +111,11 @@ public double get(String name, Map tags) { @Override public String toString() { final JsonWriter jw = new DslJson<>().newWriter(); - serialize(jw, new StringBuilder()); + MetricRegistrySerializer.serialize(this, new StringBuilder(), jw); return jw.toString(); } + + public Map, MetricSet> getMetricSets() { + return metricSets; + } } diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricSet.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricSet.java index 77bb8f739a..ac2c9d9e1d 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricSet.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricSet.java @@ -19,11 +19,6 @@ */ package co.elastic.apm.agent.metrics; -import co.elastic.apm.agent.report.serialize.DslJsonSerializer; -import com.dslplatform.json.JsonWriter; -import com.dslplatform.json.NumberConverter; - -import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -46,7 +41,7 @@ public class MetricSet { private final Map tags; private final ConcurrentMap samples = new ConcurrentHashMap<>(); - MetricSet(Map tags) { + public MetricSet(Map tags) { this.tags = tags; } @@ -58,54 +53,11 @@ DoubleSupplier get(String name) { return samples.get(name); } - public void serialize(long epochMicros, StringBuilder replaceBuilder, JsonWriter jw) { - jw.writeByte(JsonWriter.OBJECT_START); - { - DslJsonSerializer.writeFieldName("metricset", jw); - jw.writeByte(JsonWriter.OBJECT_START); - { - DslJsonSerializer.writeFieldName("timestamp", jw); - NumberConverter.serialize(epochMicros, jw); - jw.writeByte(JsonWriter.COMMA); - - if (!tags.isEmpty()) { - DslJsonSerializer.writeFieldName("tags", jw); - DslJsonSerializer.serializeTags(tags, replaceBuilder, jw); - jw.writeByte(JsonWriter.COMMA); - } - - DslJsonSerializer.writeFieldName("samples", jw); - serializeSamples(samples, jw); - } - jw.writeByte(JsonWriter.OBJECT_END); - } - jw.writeByte(JsonWriter.OBJECT_END); - } - - private void serializeSamples(Map samples, JsonWriter jw) { - jw.writeByte(JsonWriter.OBJECT_START); - final int size = samples.size(); - if (size > 0) { - final Iterator> iterator = samples.entrySet().iterator(); - Map.Entry kv = iterator.next(); - serializeSample(kv.getKey(), kv.getValue().get(), jw); - for (int i = 1; i < size; i++) { - jw.writeByte(JsonWriter.COMMA); - kv = iterator.next(); - serializeSample(kv.getKey(), kv.getValue().get(), jw); - } - } - jw.writeByte(JsonWriter.OBJECT_END); + public Map getTags() { + return tags; } - private void serializeSample(String key, double value, JsonWriter jw) { - jw.writeString(key); - jw.writeByte(JsonWriter.SEMI); - jw.writeByte(JsonWriter.OBJECT_START); - { - DslJsonSerializer.writeFieldName("value", jw); - NumberConverter.serialize(value, jw); - } - jw.writeByte(JsonWriter.OBJECT_END); + public Map getSamples() { + return samples; } } diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/DslJsonSerializer.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/DslJsonSerializer.java index b2a5dea8ad..3803fa833a 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/DslJsonSerializer.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/DslJsonSerializer.java @@ -193,7 +193,7 @@ public int getBufferSize() { @Override public void serializeMetrics(MetricRegistry metricRegistry) { - metricRegistry.serialize(jw, replaceBuilder); + MetricRegistrySerializer.serialize(metricRegistry, replaceBuilder, jw); } private void serializeErrorPayload(ErrorPayload payload) { diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/MetricRegistrySerializer.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/MetricRegistrySerializer.java new file mode 100644 index 0000000000..e9c982aa97 --- /dev/null +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/MetricRegistrySerializer.java @@ -0,0 +1,93 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 Elastic and contributors + * %% + * 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. + * #L% + */ +package co.elastic.apm.agent.report.serialize; + +import co.elastic.apm.agent.metrics.DoubleSupplier; +import co.elastic.apm.agent.metrics.MetricRegistry; +import co.elastic.apm.agent.metrics.MetricSet; +import com.dslplatform.json.JsonWriter; +import com.dslplatform.json.NumberConverter; + +import java.util.Iterator; +import java.util.Map; + +public class MetricRegistrySerializer { + + private static final byte NEW_LINE = '\n'; + + public static void serialize(MetricRegistry metricRegistry, StringBuilder replaceBuilder, JsonWriter jw) { + final long timestamp = System.currentTimeMillis() * 1000; + for (MetricSet metricSet : metricRegistry.getMetricSets().values()) { + serializeMetricSet(metricSet, timestamp, replaceBuilder, jw); + jw.writeByte(NEW_LINE); + } + } + + static void serializeMetricSet(MetricSet metricSet, long epochMicros, StringBuilder replaceBuilder, JsonWriter jw) { + jw.writeByte(JsonWriter.OBJECT_START); + { + DslJsonSerializer.writeFieldName("metricset", jw); + jw.writeByte(JsonWriter.OBJECT_START); + { + DslJsonSerializer.writeFieldName("timestamp", jw); + NumberConverter.serialize(epochMicros, jw); + jw.writeByte(JsonWriter.COMMA); + + if (!metricSet.getTags().isEmpty()) { + DslJsonSerializer.writeFieldName("tags", jw); + DslJsonSerializer.serializeTags(metricSet.getTags(), replaceBuilder, jw); + jw.writeByte(JsonWriter.COMMA); + } + + DslJsonSerializer.writeFieldName("samples", jw); + serializeSamples(metricSet.getSamples(), jw); + } + jw.writeByte(JsonWriter.OBJECT_END); + } + jw.writeByte(JsonWriter.OBJECT_END); + } + + private static void serializeSamples(Map samples, JsonWriter jw) { + jw.writeByte(JsonWriter.OBJECT_START); + final int size = samples.size(); + if (size > 0) { + final Iterator> iterator = samples.entrySet().iterator(); + Map.Entry kv = iterator.next(); + serializeSample(kv.getKey(), kv.getValue().get(), jw); + for (int i = 1; i < size; i++) { + jw.writeByte(JsonWriter.COMMA); + kv = iterator.next(); + serializeSample(kv.getKey(), kv.getValue().get(), jw); + } + } + jw.writeByte(JsonWriter.OBJECT_END); + } + + private static void serializeSample(String key, double value, JsonWriter jw) { + jw.writeString(key); + jw.writeByte(JsonWriter.SEMI); + jw.writeByte(JsonWriter.OBJECT_START); + { + DslJsonSerializer.writeFieldName("value", jw); + NumberConverter.serialize(value, jw); + } + jw.writeByte(JsonWriter.OBJECT_END); + } +} diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/MetricSetSerializationTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/MetricSetSerializationTest.java similarity index 85% rename from apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/MetricSetSerializationTest.java rename to apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/MetricSetSerializationTest.java index 7d12f3ea4c..374cefab93 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/MetricSetSerializationTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/MetricSetSerializationTest.java @@ -17,8 +17,10 @@ * limitations under the License. * #L% */ -package co.elastic.apm.agent.metrics; +package co.elastic.apm.agent.report.serialize; +import co.elastic.apm.agent.metrics.MetricSet; +import co.elastic.apm.agent.report.serialize.MetricRegistrySerializer; import com.dslplatform.json.DslJson; import com.dslplatform.json.JsonWriter; import com.fasterxml.jackson.databind.JsonNode; @@ -40,11 +42,10 @@ void testSerialization() throws IOException { final MetricSet metricSet = new MetricSet(Collections.singletonMap("foo.bar", "baz")); metricSet.add("foo.bar", () -> 42); metricSet.add("bar.baz", () -> 42); - metricSet.serialize(System.currentTimeMillis() * 1000, new StringBuilder(), jw); + MetricRegistrySerializer.serializeMetricSet(metricSet, System.currentTimeMillis() * 1000, new StringBuilder(), jw); final String metricSetAsString = jw.toString(); System.out.println(metricSetAsString); final JsonNode jsonNode = objectMapper.readTree(metricSetAsString); assertThat(jsonNode.get("metricset").get("samples").get("foo.bar").get("value").doubleValue()).isEqualTo(42); - } } From 472c2ec40d52e8b907bf73446c1e23b4191874d7 Mon Sep 17 00:00:00 2001 From: Felix Barnsteiner Date: Mon, 17 Dec 2018 16:51:02 +0100 Subject: [PATCH 06/10] Add documentation for metrics --- docs/index.asciidoc | 1 + docs/metrics.asciidoc | 195 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 196 insertions(+) create mode 100644 docs/metrics.asciidoc diff --git a/docs/index.asciidoc b/docs/index.asciidoc index 105f8f6be9..b54e0f9f28 100644 --- a/docs/index.asciidoc +++ b/docs/index.asciidoc @@ -12,6 +12,7 @@ ifndef::env-github[] include::./intro.asciidoc[Introduction] include::./application-server-setup.asciidoc[Setup the agent with Application Servers] include::./supported-technologies.asciidoc[Supported Technologies] +include::./metrics.asciidoc[Metrics] include::./configuration.asciidoc[Configuration] include::./faq.asciidoc[Frequently Asked Questions] include::./public-api.asciidoc[API documentation] diff --git a/docs/metrics.asciidoc b/docs/metrics.asciidoc new file mode 100644 index 0000000000..0bfd903a89 --- /dev/null +++ b/docs/metrics.asciidoc @@ -0,0 +1,195 @@ +ifdef::env-github[] +NOTE: For the best reading experience, +please view this documentation at https://www.elastic.co/guide/en/apm/agent/java[elastic.co] +endif::[] + +[[metrics]] +== Metrics + +The Java agent tracks certain system and application metrics. +Some of them are have built-in visualizations and some can only be visualized with custom Kibana dashboards. + +The metrics will be stored in the `apm-*` index and have the `processor.event` property set to `metric`. + +[float] +[[metrics-system]] +=== System metrics + +As of version 6.6, these metrics will be visualized in the APM UI. + +For more system metrics, consider installing {metricbeat-ref}/index.html[metricbeat] on your hosts. + +*`system.cpu.total.norm.pct`*:: ++ +-- +type: scaled_float + +format: percent + +The percentage of CPU time in states other than Idle and IOWait, normalised by the number of cores. +-- + + +*`system.process.cpu.total.norm.pct`*:: ++ +-- +type: scaled_float + +format: percent + +The percentage of CPU time spent by the process since the last event. +This value is normalized by the number of CPU cores and it ranges from 0 to 100%. +-- + + +*`system.memory.total`*:: ++ +-- +type: long + +format: bytes + +Total memory. +-- + + +*`system.memory.actual.free`*:: ++ +-- +type: long + +format: bytes + +Actual free memory in bytes. It is calculated based on the OS. +On Linux it consists of the free memory plus caches and buffers. +On OSX it is a sum of free memory and the inactive memory. +On Windows, this value does not include memory consumed by system caches and buffers. +-- + + +*`system.process.memory.size`*:: ++ +-- +type: long + +format: bytes + +The total virtual memory the process has. +-- + +[float] +[[metrics-jvm]] +=== JVM Metrics + +NOTE: As of now, there are no built-in visualizations for these metrics, +so you will need to create custom Kibana dashboards for them. + +*`jvm.memory.heap.used`*:: ++ +-- +type: long + +format: bytes + +The amount of used heap memory in bytes +-- + + +*`jvm.memory.heap.committed`*:: ++ +-- +type: long + +format: bytes + +The amount of heap memory in bytes that is committed for the Java virtual machine to use. +This amount of memory is guaranteed for the Java virtual machine to use. +-- + + +*`jvm.memory.heap.max`*:: ++ +-- +type: long + +format: bytes + +The maximum amount of heap memory in bytes that can be used for memory management. +If the maximum memory size is undefined, the value is `-1`. +-- + + +*`jvm.memory.non_heap.used`*:: ++ +-- +type: long + +format: bytes + +The amount of used non-heap memory in bytes +-- + + +*`jvm.memory.non_heap.committed`*:: ++ +-- +type: long + +format: bytes + +The amount of non-heap memory in bytes that is committed for the Java virtual machine to use. +This amount of memory is guaranteed for the Java virtual machine to use. +-- + + +*`jvm.memory.non_heap.max`*:: ++ +-- +type: long + +format: bytes + +The maximum amount of non-heap memory in bytes that can be used for memory management. +If the maximum memory size is undefined, the value is `-1`. +-- + + +*`jvm.gc.count`*:: ++ +-- +type: long + +tags + +* name: The name representing this memory manager + +The total number of collections that have occurred. +-- + + +*`jvm.gc.time`*:: ++ +-- +type: long + +format: ms + +tags + +* name: The name representing this memory manager + +The approximate accumulated collection elapsed time in milliseconds. +-- + + +*`jvm.gc.alloc`*:: ++ +-- +type: long + +format: bytes + +An approximation of the total amount of memory, +in bytes, allocated in heap memory. +-- + From 06ae62b3fffbee61630ab168a9aa0c2a3d8447b0 Mon Sep 17 00:00:00 2001 From: Felix Barnsteiner Date: Mon, 17 Dec 2018 16:55:27 +0100 Subject: [PATCH 07/10] Make animal sniffer happy --- .../java/co/elastic/apm/agent/metrics/builtin/JvmGcMetrics.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/builtin/JvmGcMetrics.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/builtin/JvmGcMetrics.java index 302c783e26..a708252f94 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/builtin/JvmGcMetrics.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/builtin/JvmGcMetrics.java @@ -24,6 +24,7 @@ import co.elastic.apm.agent.metrics.DoubleSupplier; import co.elastic.apm.agent.metrics.MetricRegistry; import com.sun.management.ThreadMXBean; +import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement; import java.lang.management.GarbageCollectorMXBean; import java.lang.management.ManagementFactory; @@ -67,6 +68,7 @@ public double get() { } } + @IgnoreJRERequirement enum HotspotAllocationSupplier implements DoubleSupplier { INSTANCE; From 4fd650b6facaf0993090724545710aaf127fe205 Mon Sep 17 00:00:00 2001 From: Felix Barnsteiner Date: Mon, 17 Dec 2018 17:23:50 +0100 Subject: [PATCH 08/10] Add to metrics_interval config from metrics doc --- docs/metrics.asciidoc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/metrics.asciidoc b/docs/metrics.asciidoc index 0bfd903a89..73351b580b 100644 --- a/docs/metrics.asciidoc +++ b/docs/metrics.asciidoc @@ -9,6 +9,9 @@ endif::[] The Java agent tracks certain system and application metrics. Some of them are have built-in visualizations and some can only be visualized with custom Kibana dashboards. +These metrics will be sent regularly to the APM Server and from there to Elasticsearch. +You can adjust the interval with the setting <>. + The metrics will be stored in the `apm-*` index and have the `processor.event` property set to `metric`. [float] From af94b5a04bb2e5947f2e173c80742bd306d124f4 Mon Sep 17 00:00:00 2001 From: Felix Barnsteiner Date: Tue, 18 Dec 2018 14:30:39 +0100 Subject: [PATCH 09/10] Actualy report updated memory metrics --- .../metrics/builtin/JvmMemoryMetrics.java | 39 ++++++++++++------- .../metrics/builtin/JvmMemoryMetricsTest.java | 4 +- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/builtin/JvmMemoryMetrics.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/builtin/JvmMemoryMetrics.java index 9d15a84763..4b099fe143 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/builtin/JvmMemoryMetrics.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/builtin/JvmMemoryMetrics.java @@ -26,7 +26,6 @@ import java.lang.management.ManagementFactory; import java.lang.management.MemoryMXBean; -import java.lang.management.MemoryUsage; import java.util.Collections; public class JvmMemoryMetrics implements LifecycleListener { @@ -37,29 +36,41 @@ public void start(ElasticApmTracer tracer) { } void bindTo(final MetricRegistry registry) { - register(registry, "non_heap", ManagementFactory.getPlatformMXBean(MemoryMXBean.class).getNonHeapMemoryUsage()); - register(registry, "heap", ManagementFactory.getPlatformMXBean(MemoryMXBean.class).getHeapMemoryUsage()); - } - - private void register(final MetricRegistry registry, final String area, final MemoryUsage memoryUsage) { - registry.add("jvm.memory." + area + ".used", Collections.emptyMap(), new DoubleSupplier() { + final MemoryMXBean platformMXBean = ManagementFactory.getPlatformMXBean(MemoryMXBean.class); + registry.add("jvm.memory.heap.used", Collections.emptyMap(), new DoubleSupplier() { @Override public double get() { - return memoryUsage.getUsed(); + return platformMXBean.getHeapMemoryUsage().getUsed(); } }); - - registry.add("jvm.memory." + area + ".committed", Collections.emptyMap(), new DoubleSupplier() { + registry.add("jvm.memory.heap.committed", Collections.emptyMap(), new DoubleSupplier() { @Override public double get() { - return memoryUsage.getCommitted(); + return platformMXBean.getHeapMemoryUsage().getCommitted(); } }); - - registry.add("jvm.memory." + area + ".max", Collections.emptyMap(), new DoubleSupplier() { + registry.add("jvm.memory.heap.max", Collections.emptyMap(), new DoubleSupplier() { + @Override + public double get() { + return platformMXBean.getHeapMemoryUsage().getMax(); + } + }); + registry.add("jvm.memory.non_heap.used", Collections.emptyMap(), new DoubleSupplier() { + @Override + public double get() { + return platformMXBean.getNonHeapMemoryUsage().getUsed(); + } + }); + registry.add("jvm.memory.non_heap.committed", Collections.emptyMap(), new DoubleSupplier() { + @Override + public double get() { + return platformMXBean.getNonHeapMemoryUsage().getCommitted(); + } + }); + registry.add("jvm.memory.non_heap.max", Collections.emptyMap(), new DoubleSupplier() { @Override public double get() { - return memoryUsage.getMax(); + return platformMXBean.getNonHeapMemoryUsage().getMax(); } }); } diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/builtin/JvmMemoryMetricsTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/builtin/JvmMemoryMetricsTest.java index a6004768ce..d309befd52 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/builtin/JvmMemoryMetricsTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/builtin/JvmMemoryMetricsTest.java @@ -34,12 +34,14 @@ class JvmMemoryMetricsTest { void testMetrics() { final MetricRegistry registry = new MetricRegistry(); jvmMemoryMetrics.bindTo(registry); - + System.out.println(registry.toString()); assertThat(registry.get("jvm.memory.heap.used", Collections.emptyMap())).isNotZero(); assertThat(registry.get("jvm.memory.heap.committed", Collections.emptyMap())).isNotZero(); assertThat(registry.get("jvm.memory.heap.max", Collections.emptyMap())).isNotZero(); assertThat(registry.get("jvm.memory.non_heap.used", Collections.emptyMap())).isNotZero(); assertThat(registry.get("jvm.memory.non_heap.committed", Collections.emptyMap())).isNotZero(); assertThat(registry.get("jvm.memory.non_heap.max", Collections.emptyMap())).isNotZero(); + final long[] longs = new long[1000000]; + System.out.println(registry.toString()); } } From 20ffadbe393ce5f1594072f947cfcd4cb01519e6 Mon Sep 17 00:00:00 2001 From: Felix Barnsteiner Date: Wed, 19 Dec 2018 14:21:41 +0100 Subject: [PATCH 10/10] Review comments --- .../apm/agent/metrics/MetricRegistry.java | 11 ------- .../apm/agent/report/PayloadSender.java | 30 ------------------- 2 files changed, 41 deletions(-) delete mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/report/PayloadSender.java diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricRegistry.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricRegistry.java index 5b4de47728..5b2e645fa1 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricRegistry.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricRegistry.java @@ -96,10 +96,6 @@ public void add(String name, Map tags, DoubleSupplier metric) { metricSet.add(name, metric); } - public void serialize(JsonWriter jw, StringBuilder replaceBuilder) { - MetricRegistrySerializer.serialize(this, replaceBuilder, jw); - } - public double get(String name, Map tags) { final MetricSet metricSet = metricSets.get(tags); if (metricSet != null) { @@ -108,13 +104,6 @@ public double get(String name, Map tags) { return Double.NaN; } - @Override - public String toString() { - final JsonWriter jw = new DslJson<>().newWriter(); - MetricRegistrySerializer.serialize(this, new StringBuilder(), jw); - return jw.toString(); - } - public Map, MetricSet> getMetricSets() { return metricSets; } diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/PayloadSender.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/PayloadSender.java deleted file mode 100644 index 5cde3cbc17..0000000000 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/PayloadSender.java +++ /dev/null @@ -1,30 +0,0 @@ -/*- - * #%L - * Elastic APM Java agent - * %% - * Copyright (C) 2018 Elastic and contributors - * %% - * 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. - * #L% - */ -package co.elastic.apm.agent.report; - -import co.elastic.apm.agent.impl.payload.Payload; - -public interface PayloadSender { - void sendPayload(Payload payload); - - long getReported(); - - long getDropped(); -}