Skip to content

Commit 15442cd

Browse files
authored
profiles: add abstractions to assist dictionary table assembly. (#7717)
1 parent a3b635d commit 15442cd

File tree

4 files changed

+402
-0
lines changed

4 files changed

+402
-0
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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.HashMap;
11+
import java.util.List;
12+
import java.util.Map;
13+
14+
/**
15+
* This data structure is effectively an indexed Set.
16+
*
17+
* <p>It stores each offered element without duplication (like a Set, not a List), but supports
18+
* reference to elements by their position (like a List index).
19+
*
20+
* <p>This class is not threadsafe and must be externally synchronized.
21+
*
22+
* <p>For a given Object o, after i = putIfAbsent(o), then getTable().get(i).equals(o);
23+
*
24+
* @param <T> the type of elements maintained by this table. The type should implement equals and
25+
* hashCode in a manner consistent with Set/Map key expectations.
26+
*/
27+
public class DictionaryTable<T> {
28+
29+
// Whilst it's possible to compute either from the other, we keep two views on the data,
30+
// prioritising access efficiency over memory footprint.
31+
private final List<T> table = new ArrayList<>();
32+
private final Map<T, Integer> map = new HashMap<>();
33+
34+
/**
35+
* Stores the provided element if an equivalent is not already present, and returns its index.
36+
*
37+
* <p>Note that whilst the update semantics of this method are consistent with Map.putIfAbsent,
38+
* the return value is always the index, i.e. reflects the post-update state, not the prior state,
39+
* and therefore does not allow for determining if the method had an effect or not.
40+
*
41+
* @param value an element to store.
42+
* @return the index of the added or existing element.
43+
*/
44+
public Integer putIfAbsent(T value) {
45+
Integer index = map.computeIfAbsent(value, k -> map.size());
46+
if (map.size() != table.size()) {
47+
table.add(value);
48+
}
49+
return index;
50+
}
51+
52+
/**
53+
* Provides a view of the Table in List form.
54+
*
55+
* @return an immutable List containing the table entries in index position.
56+
*/
57+
public List<T> getTable() {
58+
return Collections.unmodifiableList(table);
59+
}
60+
}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
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.api.common.Value;
9+
import io.opentelemetry.exporter.otlp.internal.data.ImmutableFunctionData;
10+
import io.opentelemetry.exporter.otlp.internal.data.ImmutableKeyValueAndUnitData;
11+
import io.opentelemetry.exporter.otlp.internal.data.ImmutableLinkData;
12+
import io.opentelemetry.exporter.otlp.internal.data.ImmutableLocationData;
13+
import io.opentelemetry.exporter.otlp.internal.data.ImmutableMappingData;
14+
import io.opentelemetry.exporter.otlp.internal.data.ImmutableProfileDictionaryData;
15+
import io.opentelemetry.exporter.otlp.internal.data.ImmutableStackData;
16+
import java.util.Collections;
17+
18+
/**
19+
* This class allows for the assembly of the reference tables which form a ProfileDictionaryData.
20+
*
21+
* <p>It's effectively a builder, though without the fluent API. Instead, mutation methods return
22+
* the index of the offered element, this information being required to construct any element that
23+
* references into the tables.
24+
*
25+
* <p>Note this class does not enforce referential integrity, i.e. will accept an element which
26+
* references a non-existing element.
27+
*
28+
* <p>This class is not threadsafe and must be externally synchronized.
29+
*/
30+
public class ProfileDictionaryCompositor {
31+
32+
// This implementation relies on the *Data interface impls having equals/hashCode that works.
33+
// The provided AutoValue_* ones do, but it's potentially fragile if used externally.
34+
35+
private final DictionaryTable<MappingData> mappingTable = new DictionaryTable<>();
36+
private final DictionaryTable<LocationData> locationTable = new DictionaryTable<>();
37+
private final DictionaryTable<FunctionData> functionTable = new DictionaryTable<>();
38+
private final DictionaryTable<LinkData> linkTable = new DictionaryTable<>();
39+
private final DictionaryTable<String> stringTable = new DictionaryTable<>();
40+
private final DictionaryTable<KeyValueAndUnitData> attributeTable = new DictionaryTable<>();
41+
private final DictionaryTable<StackData> stackTable = new DictionaryTable<>();
42+
43+
public ProfileDictionaryCompositor() {
44+
45+
// The [0] element of each table should be a 'null' element, such that a 0 value pointer can be
46+
// used to indicate null / not set, in preference to requiring an 'optional' modifier on the
47+
// pointer declarations. The value of this placeholder element is one with all default field
48+
// values, such that it encodes to the minimal number of bytes.
49+
// These values are inlined here, as there is no other point of use.
50+
// They could be public static constants on this class or the corresponding Immutable*Data
51+
// classes if other use cases require them.
52+
53+
mappingTable.putIfAbsent(ImmutableMappingData.create(0, 0, 0, 0, Collections.emptyList()));
54+
locationTable.putIfAbsent(
55+
ImmutableLocationData.create(0, 0, Collections.emptyList(), Collections.emptyList()));
56+
functionTable.putIfAbsent(ImmutableFunctionData.create(0, 0, 0, 0));
57+
linkTable.putIfAbsent(ImmutableLinkData.create("", ""));
58+
stringTable.putIfAbsent("");
59+
attributeTable.putIfAbsent(ImmutableKeyValueAndUnitData.create(0, Value.of(""), 0));
60+
stackTable.putIfAbsent(ImmutableStackData.create(Collections.emptyList()));
61+
}
62+
63+
/**
64+
* Provides the contents of the dictionary tables as a ProfileDictionaryData.
65+
*
66+
* <p>The return value is a view, not a copy. No further elements should be inserted after this is
67+
* called as it would compromise the intended immutability of the result.
68+
*
69+
* @return a ProfileDictionaryData with the contents of the tables.
70+
*/
71+
public ProfileDictionaryData getProfileDictionaryData() {
72+
return ImmutableProfileDictionaryData.create(
73+
mappingTable.getTable(),
74+
locationTable.getTable(),
75+
functionTable.getTable(),
76+
linkTable.getTable(),
77+
stringTable.getTable(),
78+
attributeTable.getTable(),
79+
stackTable.getTable());
80+
}
81+
82+
/**
83+
* Stores the provided element if an equivalent is not already present, and returns its index.
84+
*
85+
* @param mappingData an element to store.
86+
* @return the index of the added or existing element.
87+
*/
88+
public int putIfAbsent(MappingData mappingData) {
89+
return mappingTable.putIfAbsent(mappingData);
90+
}
91+
92+
/**
93+
* Stores the provided element if an equivalent is not already present, and returns its index.
94+
*
95+
* @param locationData an element to store.
96+
* @return the index of the added or existing element.
97+
*/
98+
public int putIfAbsent(LocationData locationData) {
99+
return locationTable.putIfAbsent(locationData);
100+
}
101+
102+
/**
103+
* Stores the provided element if an equivalent is not already present, and returns its index.
104+
*
105+
* @param functionData an element to store.
106+
* @return the index of the added or existing element.
107+
*/
108+
public int putIfAbsent(FunctionData functionData) {
109+
return functionTable.putIfAbsent(functionData);
110+
}
111+
112+
/**
113+
* Stores the provided element if an equivalent is not already present, and returns its index.
114+
*
115+
* @param linkData an element to store.
116+
* @return the index of the added or existing element.
117+
*/
118+
public int putIfAbsent(LinkData linkData) {
119+
return linkTable.putIfAbsent(linkData);
120+
}
121+
122+
/**
123+
* Stores the provided element if an equivalent is not already present, and returns its index.
124+
*
125+
* @param string an element to store.
126+
* @return the index of the added or existing element.
127+
*/
128+
public int putIfAbsent(String string) {
129+
return stringTable.putIfAbsent(string);
130+
}
131+
132+
/**
133+
* Stores the provided element if an equivalent is not already present, and returns its index.
134+
*
135+
* @param keyValueAndUnitData an element to store.
136+
* @return the index of the added or existing element.
137+
*/
138+
public int putIfAbsent(KeyValueAndUnitData keyValueAndUnitData) {
139+
return attributeTable.putIfAbsent(keyValueAndUnitData);
140+
}
141+
142+
/**
143+
* Stores the provided element if an equivalent is not already present, and returns its index.
144+
*
145+
* @param stackData an element to store.
146+
* @return the index of the added or existing element.
147+
*/
148+
public int putIfAbsent(StackData stackData) {
149+
return stackTable.putIfAbsent(stackData);
150+
}
151+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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 org.junit.jupiter.api.BeforeEach;
11+
import org.junit.jupiter.api.Test;
12+
13+
class DictionaryTableTest {
14+
15+
DictionaryTable<String> table;
16+
17+
@BeforeEach
18+
void setUp() {
19+
table = new DictionaryTable<>();
20+
}
21+
22+
@Test
23+
void isInitiallyEmpty() {
24+
assertThat(table.getTable()).isEmpty();
25+
}
26+
27+
@Test
28+
void isDeduplicating() {
29+
assertThat(table.putIfAbsent("foo")).isEqualTo(0);
30+
assertThat(table.putIfAbsent("foo")).isEqualTo(0);
31+
assertThat(table.getTable()).size().isEqualTo(1);
32+
}
33+
34+
@Test
35+
void isSequencing() {
36+
assertThat(table.putIfAbsent("foo")).isEqualTo(0);
37+
assertThat(table.putIfAbsent("bar")).isEqualTo(1);
38+
assertThat(table.getTable()).size().isEqualTo(2);
39+
assertThat(table.getTable().get(0)).isEqualTo("foo");
40+
assertThat(table.getTable().get(1)).isEqualTo("bar");
41+
}
42+
}

0 commit comments

Comments
 (0)