Skip to content

Commit 9f091a4

Browse files
authored
profiles: add abstractions to assist sample composition (#7727)
1 parent 2933b8f commit 9f091a4

File tree

4 files changed

+268
-0
lines changed

4 files changed

+268
-0
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.exporter.otlp.profiles;
7+
8+
import io.opentelemetry.exporter.otlp.internal.data.ImmutableSampleData;
9+
import java.util.ArrayList;
10+
import java.util.HashMap;
11+
import java.util.List;
12+
import java.util.Map;
13+
import javax.annotation.Nullable;
14+
15+
/**
16+
* Assembles a collection of state observations into a collection of SampleData objects i.e. proto
17+
* Sample messages.
18+
*
19+
* <p>Observations (samples or traces from a profiler) having the same key can be merged to save
20+
* space without loss of fidelity. On the wire, a single Sample, modelled in the API as a SampleData
21+
* object, comprises shared fields (the key) and per-occurrence fields (the value and timestamp).
22+
* This class maps the raw observations to the aggregations.
23+
*
24+
* <p>This class is not threadsafe and must be externally synchronized.
25+
*/
26+
public class SampleCompositionBuilder {
27+
28+
private final Map<SampleCompositionKey, SampleCompositionValue> map = new HashMap<>();
29+
30+
/**
31+
* Constructs a new collection of SampleData instances based on the builder's value.
32+
*
33+
* @return a new {@code List<SampleData>}
34+
*/
35+
public List<SampleData> build() {
36+
List<SampleData> result = new ArrayList<>(map.size());
37+
for (Map.Entry<SampleCompositionKey, SampleCompositionValue> entry : map.entrySet()) {
38+
SampleCompositionKey key = entry.getKey();
39+
SampleCompositionValue value = entry.getValue();
40+
SampleData sampleData =
41+
ImmutableSampleData.create(
42+
key.getStackIndex(),
43+
value.getValues(),
44+
key.getAttributeIndices(),
45+
key.getLinkIndex(),
46+
value.getTimestamps());
47+
result.add(sampleData);
48+
}
49+
50+
return result;
51+
}
52+
53+
/**
54+
* Adds a new observation to the collection.
55+
*
56+
* @param key the shared ('primary key') fields of the observation.
57+
* @param value the observed data point.
58+
* @param timestamp the time of the observation.
59+
*/
60+
public void add(SampleCompositionKey key, @Nullable Long value, @Nullable Long timestamp) {
61+
SampleCompositionValue v = map.computeIfAbsent(key, key1 -> new SampleCompositionValue());
62+
v.add(value, timestamp);
63+
}
64+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.exporter.otlp.profiles;
7+
8+
import java.util.ArrayList;
9+
import java.util.Collections;
10+
import java.util.List;
11+
import java.util.Objects;
12+
import javax.annotation.concurrent.Immutable;
13+
14+
/**
15+
* A SampleCompositionKey represents the identity portion of an aggregation of observed data.
16+
* Observations (samples) having the same key can be merged to save space without loss of fidelity.
17+
*/
18+
@Immutable
19+
public class SampleCompositionKey {
20+
21+
// on the wire, a Sample's identity (i.e. 'primary key') is the tuple of
22+
// {stack_index, sorted(attribute_indices), link_index}
23+
private final int stackIndex;
24+
private final List<Integer> attributeIndices;
25+
private final int linkIndex;
26+
27+
public SampleCompositionKey(int stackIndex, List<Integer> attributeIndices, int linkIndex) {
28+
this.stackIndex = stackIndex;
29+
List<Integer> tmp = new ArrayList<>(attributeIndices);
30+
Collections.sort(tmp);
31+
this.attributeIndices = Collections.unmodifiableList(tmp);
32+
this.linkIndex = linkIndex;
33+
}
34+
35+
public int getStackIndex() {
36+
return stackIndex;
37+
}
38+
39+
public List<Integer> getAttributeIndices() {
40+
return attributeIndices;
41+
}
42+
43+
public int getLinkIndex() {
44+
return linkIndex;
45+
}
46+
47+
@Override
48+
public boolean equals(Object o) {
49+
if (!(o instanceof SampleCompositionKey)) {
50+
return false;
51+
}
52+
SampleCompositionKey that = (SampleCompositionKey) o;
53+
return stackIndex == that.stackIndex
54+
&& linkIndex == that.linkIndex
55+
&& Objects.equals(attributeIndices, that.attributeIndices);
56+
}
57+
58+
@Override
59+
public int hashCode() {
60+
return Objects.hash(stackIndex, attributeIndices, linkIndex);
61+
}
62+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.exporter.otlp.profiles;
7+
8+
import java.util.ArrayList;
9+
import java.util.Collections;
10+
import java.util.List;
11+
import javax.annotation.Nullable;
12+
13+
/**
14+
* A SampleCompositionValue represents the per-observation parts of an aggregation of observed data.
15+
* Observations (samples) having the same key can be merged by appending their distinct fields to
16+
* the composed value.
17+
*
18+
* <p>This class is not threadsafe and must be externally synchronized.
19+
*/
20+
public class SampleCompositionValue {
21+
22+
private final List<Long> values = new ArrayList<>();
23+
private final List<Long> timestamps = new ArrayList<>();
24+
25+
public List<Long> getValues() {
26+
return Collections.unmodifiableList(values);
27+
}
28+
29+
public List<Long> getTimestamps() {
30+
return Collections.unmodifiableList(timestamps);
31+
}
32+
33+
/**
34+
* Add a new observation to the collection.
35+
*
36+
* <p>Note that, whilst not enforced by the API, it is required that all observations in a
37+
* collection share the same 'shape'. That is, they have either a value without timestamp, a
38+
* timestamp without value, or both timestamp and value. Thus each array (values, timestamps) in
39+
* the collection is either zero length, or the same length as the other.
40+
*
41+
* @param value the observed data point.
42+
* @param timestamp the time of the observation.
43+
*/
44+
public void add(@Nullable Long value, @Nullable Long timestamp) {
45+
if (value != null) {
46+
values.add(value);
47+
}
48+
if (timestamp != null) {
49+
timestamps.add(timestamp);
50+
}
51+
}
52+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.exporter.otlp.profiles;
7+
8+
import static org.assertj.core.api.Assertions.assertThat;
9+
10+
import java.util.ArrayList;
11+
import java.util.Collections;
12+
import java.util.List;
13+
import org.junit.jupiter.api.BeforeEach;
14+
import org.junit.jupiter.api.Test;
15+
16+
class SampleCompositionTest {
17+
18+
SampleCompositionBuilder sampleCompositionBuilder;
19+
20+
@BeforeEach
21+
void setUp() {
22+
sampleCompositionBuilder = new SampleCompositionBuilder();
23+
}
24+
25+
@Test
26+
public void empty() {
27+
assertThat(sampleCompositionBuilder.build()).isEmpty();
28+
}
29+
30+
@Test
31+
public void keyEquality() {
32+
SampleCompositionKey a;
33+
SampleCompositionKey b;
34+
35+
a = new SampleCompositionKey(1, listOf(2, 3), 4);
36+
b = new SampleCompositionKey(1, listOf(2, 3), 4);
37+
assertThat(a).isEqualTo(b);
38+
assertThat(a.hashCode()).isEqualTo(b.hashCode());
39+
40+
b = new SampleCompositionKey(1, listOf(3, 2), 4);
41+
assertThat(a).isEqualTo(b);
42+
assertThat(a.hashCode()).isEqualTo(b.hashCode());
43+
44+
b = new SampleCompositionKey(2, listOf(2, 3), 4);
45+
assertThat(a).isNotEqualTo(b);
46+
}
47+
48+
@Test
49+
public void valueElidesNulls() {
50+
SampleCompositionValue v = new SampleCompositionValue();
51+
v.add(1L, 1L);
52+
v.add(null, 2L);
53+
v.add(2L, null);
54+
assertThat(v.getValues().size()).isEqualTo(2);
55+
assertThat(v.getTimestamps().size()).isEqualTo(2);
56+
}
57+
58+
@Test
59+
public void isAggregatingSameKey() {
60+
SampleCompositionKey sampleCompositionKey =
61+
new SampleCompositionKey(0, Collections.emptyList(), 0);
62+
sampleCompositionBuilder.add(sampleCompositionKey, 1L, 1L);
63+
sampleCompositionBuilder.add(sampleCompositionKey, 2L, 2L);
64+
65+
List<SampleData> sampleDataList = sampleCompositionBuilder.build();
66+
assertThat(sampleDataList).size().isEqualTo(1);
67+
assertThat(sampleDataList.get(0).getTimestamps().size()).isEqualTo(2);
68+
assertThat(sampleDataList.get(0).getValues().size()).isEqualTo(2);
69+
}
70+
71+
@Test
72+
public void isNotAggregatingDifferentKey() {
73+
SampleCompositionKey keyA = new SampleCompositionKey(1, Collections.emptyList(), 0);
74+
sampleCompositionBuilder.add(keyA, 1L, 1L);
75+
SampleCompositionKey keyB = new SampleCompositionKey(2, Collections.emptyList(), 0);
76+
sampleCompositionBuilder.add(keyB, 2L, 2L);
77+
78+
List<SampleData> sampleDataList = sampleCompositionBuilder.build();
79+
assertThat(sampleDataList).size().isEqualTo(2);
80+
assertThat(sampleDataList.get(0).getTimestamps().size()).isEqualTo(1);
81+
assertThat(sampleDataList.get(1).getTimestamps().size()).isEqualTo(1);
82+
}
83+
84+
private static <T> List<T> listOf(T a, T b) {
85+
ArrayList<T> list = new ArrayList<>();
86+
list.add(a);
87+
list.add(b);
88+
return Collections.unmodifiableList(list);
89+
}
90+
}

0 commit comments

Comments
 (0)