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 @@ -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
@@ -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();
}
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 com.dslplatform.json.JsonWriter;

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 ConcurrentMap<Map<String, String>, MetricSet> metricSets = new ConcurrentHashMap<>();
felixbarny marked this conversation as resolved.
Show resolved Hide resolved

public void addUnlessNan(String name, Map<String, String> tags, DoubleSupplier metric) {
if (!Double.isNaN(metric.get())) {
add(name, tags, metric);
}
}

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 void serialize(JsonWriter jw, StringBuilder replaceBuilder) {
felixbarny marked this conversation as resolved.
Show resolved Hide resolved
felixbarny marked this conversation as resolved.
Show resolved Hide resolved
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<String, String> tags) {
final MetricSet metricSet = metricSets.get(tags);
if (metricSet != null) {
return metricSet.get(name).get();
}
return Double.NaN;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*-
* #%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;
import java.util.concurrent.ConcurrentMap;

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<>();

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 void serialize(long epochMicros, StringBuilder replaceBuilder, JsonWriter jw) {
jw.writeByte(JsonWriter.OBJECT_START);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same - serialization better be colocated

{
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<String, DoubleSupplier> samples, JsonWriter jw) {
jw.writeByte(JsonWriter.OBJECT_START);
final int size = samples.size();
if (size > 0) {
final Iterator<Map.Entry<String, DoubleSupplier>> iterator = samples.entrySet().iterator();
Map.Entry<String, DoubleSupplier> 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);
}
}
Loading