Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic metrics support #360

Merged
merged 11 commits into from
Dec 19, 2018
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
* 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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<Double>() {
@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<Integer> transactionMaxSpans = ConfigurationOption.integerOption()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class TimeDuration {
public class TimeDuration implements Comparable<TimeDuration> {

public static final Pattern DURATION_PATTERN = Pattern.compile("^(-)?(\\d+)(ms|s|m)$");
private final String durationString;
Expand Down Expand Up @@ -68,4 +68,9 @@ public long getMillis() {
public String toString() {
return durationString;
}

@Override
public int compareTo(TimeDuration o) {
return Long.compare(durationMs, o.durationMs);
}
}
Original file line number Diff line number Diff line change
@@ -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<T extends Comparable> implements ConfigurationOption.Validator<T> {

@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 <T extends Comparable> RangeValidator<T> isInRange(T min, T max) {
return new RangeValidator<>(min, max, true);
}

public static <T extends Comparable> RangeValidator<T> isNotInRange(T min, T max) {
return new RangeValidator<>(min, max, false);
}

public static <T extends Comparable> RangeValidator<T> min(T min) {
return new RangeValidator<>(min, null, true);
}

public static <T extends Comparable> RangeValidator<T> 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 + "]");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -75,6 +76,7 @@ protected Deque<Object> initialValue() {
};
private final CoreConfiguration coreConfiguration;
private final List<SpanListener> spanListeners;
private final MetricRegistry metricRegistry = new MetricRegistry();
private Sampler sampler;

ElasticApmTracer(ConfigurationRegistry configurationRegistry, Reporter reporter, Iterable<LifecycleListener> lifecycleListeners, List<SpanListener> spanListeners) {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -396,4 +399,8 @@ private void assertIsActive(Object span, @Nullable Object currentlyActive) {
}
assert span == currentlyActive;
}

public MetricRegistry getMetricRegistry() {
return metricRegistry;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,9 @@
* limitations under the License.
* #L%
*/
package co.elastic.apm.agent.report;
package co.elastic.apm.agent.metrics;

import co.elastic.apm.agent.impl.payload.Payload;
public interface DoubleSupplier {

public interface PayloadSender {
void sendPayload(Payload payload);

long getReported();

long getDropped();
double get();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*-
* #%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.MetricRegistrySerializer;
import com.dslplatform.json.DslJson;
import com.dslplatform.json.JsonWriter;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
* A registry for metrics.
* <p>
* Currently only holds gauges.
* There are plans to add support for histogram-based timers.
* </p>
*/
public class MetricRegistry {

/**
* Groups {@link MetricSet}s by their unique tags.
*/
private final ConcurrentMap<Map<String, String>, MetricSet> metricSets = new ConcurrentHashMap<>();
felixbarny marked this conversation as resolved.
Show resolved Hide resolved

/**
* 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<String, String> 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<String, String> 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<String, String> 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 double get(String name, Map<String, String> tags) {
final MetricSet metricSet = metricSets.get(tags);
if (metricSet != null) {
return metricSet.get(name).get();
}
return Double.NaN;
}

public Map<Map<String, String>, MetricSet> getMetricSets() {
return metricSets;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*-
* #%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 java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
* A metric set is a collection of metrics which have the same tags.
* <p>
* 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.
* </p>
* Example of some serialized metric sets:
* <pre>
* {"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}}}}
* </pre>
*/
public class MetricSet {
felixbarny marked this conversation as resolved.
Show resolved Hide resolved
private final Map<String, String> tags;
private final ConcurrentMap<String, DoubleSupplier> samples = new ConcurrentHashMap<>();

public MetricSet(Map<String, String> tags) {
this.tags = tags;
}

public void add(String name, DoubleSupplier metric) {
samples.putIfAbsent(name, metric);
}

DoubleSupplier get(String name) {
return samples.get(name);
}

public Map<String, String> getTags() {
return tags;
}

public Map<String, DoubleSupplier> getSamples() {
return samples;
}
}
Loading