Skip to content

Timing breakdown #588

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

Merged
merged 26 commits into from
Jul 19, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
953c909
Recycle via reference counting
felixbarny Apr 16, 2019
847aecd
Revert unrelated changes
felixbarny Apr 16, 2019
8bc8dd1
Introduce Labels
felixbarny Apr 16, 2019
d0e5d51
Add breakdown
felixbarny Apr 16, 2019
dffbba0
Make tracking breakdown metrics garbage free
felixbarny Apr 23, 2019
576f2da
Merge remote-tracking branch 'origin/master' into timing-breakdown
felixbarny May 3, 2019
790f2e2
Update to new metric names
felixbarny May 3, 2019
2c8d6d2
Merge remote-tracking branch 'origin/master' into timing-breakdown
felixbarny Jun 3, 2019
33bfcae
Thread safe publication of immutable labels
felixbarny Jun 3, 2019
b5395a1
Add span.subtype to breakdown metrics
felixbarny Jun 3, 2019
a609d71
Disable all breakdown code paths when disable_metrics=span.self_time
felixbarny Jun 4, 2019
dc701f7
Document metrics
felixbarny Jun 4, 2019
280f663
Clarify docs
felixbarny Jun 5, 2019
a336827
Another docs clarification
felixbarny Jun 5, 2019
833bbe1
Fix tests
felixbarny Jun 5, 2019
5639197
Guard agains concurrent reads/writes
felixbarny Jun 5, 2019
a0d167d
Limit number of metricsets
felixbarny Jun 5, 2019
9aca16f
Add some Javadocs
felixbarny Jun 5, 2019
253ad87
Eliminate allocations
felixbarny Jun 5, 2019
dbf5884
Addressed some comments
felixbarny Jun 21, 2019
6549546
Merge remote-tracking branch 'origin/master' into timing-breakdown
felixbarny Jun 21, 2019
d8394c3
Report timers in microseconds and use .us suffix
felixbarny Jun 21, 2019
308f519
Merge remote-tracking branch 'origin/master' into timing-breakdown
felixbarny Jul 12, 2019
580bf1d
Merge remote-tracking branch 'origin/master' into timing-breakdown
felixbarny Jul 17, 2019
bf6c221
Fix race condition between trackMetrics and incrementTimer
felixbarny Jul 18, 2019
f84852a
Renaming and add comments
felixbarny Jul 19, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
259 changes: 259 additions & 0 deletions apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/Labels.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
/*-
* #%L
* Elastic APM Java agent
* %%
* Copyright (C) 2018 - 2019 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.objectpool.Recyclable;

import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;

public class Labels implements Recyclable {

private static final Labels EMPTY = Labels.of().immutableCopy();
private final List<String> keys = new ArrayList<>();
private final List<CharSequence> values = new ArrayList<>();
private final boolean immutable;
@Nullable
private CharSequence transactionName;
@Nullable
private String transactionType;
@Nullable
private String spanType;
private int cachedHash;

public Labels() {
this(Collections.<String>emptyList(), Collections.<CharSequence>emptyList(), false);
}

private Labels(List<String> keys, List<? extends CharSequence> values, boolean immutable) {
this.keys.addAll(keys);
this.values.addAll(values);
this.immutable = immutable;
}

public static Labels of() {
return new Labels();
}

public static Labels of(String key, CharSequence value) {
final Labels labels = new Labels();
labels.add(key, value);
return labels;
}

public static Labels of(Map<String, ? extends CharSequence> labelMap) {
Labels labels = new Labels();
for (Map.Entry<String, ? extends CharSequence> entry : labelMap.entrySet()) {
labels.add(entry.getKey(), entry.getValue());
}
return labels;
}

public static Labels empty() {
return EMPTY;
}

public Labels add(String key, CharSequence value) {
assertMutable();
keys.add(key);
values.add(value);
return this;
}

public Labels transactionName(CharSequence transactionName) {
assertMutable();
this.transactionName = transactionName;
return this;
}

public Labels transactionType(String transactionType) {
assertMutable();
this.transactionType = transactionType;
return this;
}

public Labels spanType(String spanType) {
assertMutable();
this.spanType = spanType;
return this;
}

private void assertMutable() {
if (immutable) {
throw new UnsupportedOperationException("This Labels instance is immutable");
}
}

@Nullable
public CharSequence getTransactionName() {
return transactionName;
}

@Nullable
public String getTransactionType() {
return transactionType;
}

@Nullable
public String getSpanType() {
return spanType;
}

public Labels immutableCopy() {
List<String> immutableValues = new ArrayList<>(values.size());
for (int i = 0; i < keys.size(); i++) {
immutableValues.add(values.get(i).toString());
}
final Labels labels = new Labels(keys, immutableValues, true);
labels.transactionName = this.transactionName != null ? this.transactionName.toString() : null;
labels.transactionType = this.transactionType;
labels.spanType = this.spanType;
labels.cachedHash = labels.hashCode();
return labels;
}

public List<String> getKeys() {
return keys;
}

public List<CharSequence> getValues() {
return values;
}

public boolean isEmpty() {
return keys.isEmpty();
}

public int size() {
return keys.size();
}

public String getKey(int i) {
return keys.get(i);
}

public CharSequence getValue(int i) {
return values.get(i);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Labels labels = (Labels) o;
return keys.equals(labels.keys) &&
isEqual(values, labels.values) &&
contentEquals(transactionName, labels.transactionName) &&
Objects.equals(transactionType, labels.transactionType) &&
Objects.equals(spanType, labels.spanType);
}

@Override
public int hashCode() {
if (cachedHash != 0) {
return cachedHash;
}
int h = 0;
for (int i = 0; i < values.size(); i++) {
h = 31 * h + hash(i);
}
h = 31 * h + hash(transactionName);
h = 31 * h + Objects.hashCode(transactionType);
h = 31 * h + Objects.hashCode(spanType);
return h;
}

@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < keys.size(); i++) {
if (i > 0) {
sb.append(", ");
}
sb.append(keys.get(i)).append("=").append(values.get(i));

}
return sb.toString();
}

private int hash(int i) {
return keys.get(i).hashCode() * 31 + hash(values.get(i));
}

private static boolean isEqual(List<CharSequence> values, List<CharSequence> otherValues) {
if (values.size() != otherValues.size()) {
return false;
}
for (int i = 0; i < values.size(); i++) {
if (!contentEquals(values.get(i), otherValues.get(i))) {
return false;
}
}
return true;
}

private static boolean contentEquals(@Nullable CharSequence cs1, @Nullable CharSequence cs2) {
if (cs1 == null || cs2 == null) {
return cs1 == cs2;
}
if (cs1 instanceof String) {
return ((String) cs1).contentEquals(cs2);
} else if (cs2 instanceof String) {
return ((String) cs2).contentEquals(cs1);
} else {
if (cs1.length() == cs2.length()) {
for (int i = 0; i < cs1.length(); i++) {
if (cs1.charAt(i) != cs2.charAt(i)) {
return false;
}
}
return true;
}
}
return false;
}

static int hash(@Nullable CharSequence cs) {
if (cs == null) {
return 0;
}
// this is safe as the hash code calculation is well defined
// (see javadoc for String.hashCode())
if (cs instanceof String) return cs.hashCode();
int h = 0;
for (int i = 0; i < cs.length(); i++) {
h = 31 * h + cs.charAt(i);
}
return h;
}

@Override
public void resetState() {
keys.clear();
values.clear();
transactionName = null;
transactionType = null;
spanType = null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,92 +36,98 @@
public class MetricRegistry {

/**
* Groups {@link MetricSet}s by their unique tags.
* Groups {@link MetricSet}s by their unique labels.
*/
private final ConcurrentMap<Map<String, String>, MetricSet> metricSets = new ConcurrentHashMap<>();
private final ConcurrentMap<Labels, MetricSet> metricSets = new ConcurrentHashMap<>();
private final ReporterConfiguration config;

public MetricRegistry(ReporterConfiguration config) {
this.config = config;
}

/**
* Same as {@link #add(String, Map, DoubleSupplier)} but only adds the metric
* Same as {@link #add(String, Labels, 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.
* @param labels labels 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.
* Note that there will be a {@link MetricSet} created for each distinct set of labels.
* @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)
* @see #add(String, Labels, DoubleSupplier)
*/
public void addUnlessNan(String name, Map<String, String> tags, DoubleSupplier metric) {
public void addUnlessNan(String name, Labels labels, DoubleSupplier metric) {
if (isDisabled(name)) {
return;
}
if (!Double.isNaN(metric.get())) {
add(name, tags, metric);
add(name, labels, metric);
}
}

/**
* Same as {@link #add(String, Map, DoubleSupplier)} but only adds the metric
* Same as {@link #add(String, Labels, 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.
* @param labels labels 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.
* Note that there will be a {@link MetricSet} created for each distinct set of labels.
* @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)
* @see #add(String, Labels, DoubleSupplier)
*/
public void addUnlessNegative(String name, Map<String, String> tags, DoubleSupplier metric) {
public void addUnlessNegative(String name, Labels labels, DoubleSupplier metric) {
if (isDisabled(name)) {
return;
}
if (metric.get() >= 0) {
add(name, tags, metric);
add(name, labels, metric);
}
}

/**
* Adds a gauge to the metric registry.
*
* @param name the name of the metric
* @param tags tags for the metric.
* @param labels labels 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.
* Note that there will be a {@link MetricSet} created for each distinct set of labels.
* @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) {
public void add(String name, Labels labels, DoubleSupplier metric) {
if (isDisabled(name)) {
return;
}
MetricSet metricSet = metricSets.get(tags);
if (metricSet == null) {
metricSets.putIfAbsent(tags, new MetricSet(tags));
metricSet = metricSets.get(tags);
}
MetricSet metricSet = getOrCreateMetricSet(labels);
metricSet.add(name, metric);
}

private boolean isDisabled(String name) {
return WildcardMatcher.anyMatch(config.getDisableMetrics(), name) != null;
}

public double get(String name, Map<String, String> tags) {
final MetricSet metricSet = metricSets.get(tags);
public double get(String name, Labels labels) {
final MetricSet metricSet = metricSets.get(labels);
if (metricSet != null) {
return metricSet.get(name).get();
}
return Double.NaN;
}

public Map<Map<String, String>, MetricSet> getMetricSets() {
public Map<Labels, MetricSet> getMetricSets() {
return metricSets;
}

private MetricSet getOrCreateMetricSet(Labels labels) {
MetricSet metricSet = metricSets.get(labels);
if (metricSet == null) {
final Labels copy = labels.immutableCopy();
metricSets.putIfAbsent(copy, new MetricSet(copy));
metricSet = metricSets.get(copy);
}
return metricSet;
}
}
Loading