Skip to content

Commit 01ef368

Browse files
committed
profiles: add abstractions to assist dictionary table assembly.
1 parent 9c70ffe commit 01ef368

File tree

4 files changed

+409
-0
lines changed

4 files changed

+409
-0
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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+
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
25+
* at any time.
26+
*
27+
* @param <T> the type of elements maintained by this table. The type should implement equals and
28+
* hashCode in a manner consistent with Set/Map key expectations.
29+
*/
30+
public class DictionaryTable<T> {
31+
32+
// Whilst it's possible to compute either from the other, we keep two views on the data,
33+
// prioritising access efficiency over memory footprint.
34+
private final List<T> table = new ArrayList<>();
35+
private final Map<T, Integer> map = new HashMap<>();
36+
37+
/**
38+
* Stores the provided element if an equivalent is not already present, and returns its index.
39+
*
40+
* <p>Note that whilst the update semantics of this method are consistent with Map.putIfAbsent,
41+
* the return value is always the index, i.e. reflects the post-update state, not the prior state,
42+
* and therefore does not allow for determining if the method had an effect or not.
43+
*
44+
* @param value an element to store.
45+
* @return the index of the added or existing element.
46+
*/
47+
public Integer putIfAbsent(T value) {
48+
Integer index = map.computeIfAbsent(value, k -> map.size());
49+
if (map.size() != table.size()) {
50+
table.add(value);
51+
}
52+
return index;
53+
}
54+
55+
/**
56+
* Provides a view of the Table in List form.
57+
*
58+
* @return an immutable List containing the table entries in index position.
59+
*/
60+
public List<T> getTable() {
61+
return Collections.unmodifiableList(table);
62+
}
63+
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
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+
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
31+
* at any time.
32+
*/
33+
public class ProfileDictionaryCompositor {
34+
35+
// This implementation relies on the *Data interface impls having equals/hashCode that works.
36+
// The provided AutoValue_* ones do, but it's potentially fragile if used externally.
37+
38+
private final DictionaryTable<MappingData> mappingTable = new DictionaryTable<>();
39+
private final DictionaryTable<LocationData> locationTable = new DictionaryTable<>();
40+
private final DictionaryTable<FunctionData> functionTable = new DictionaryTable<>();
41+
private final DictionaryTable<LinkData> linkTable = new DictionaryTable<>();
42+
private final DictionaryTable<String> stringTable = new DictionaryTable<>();
43+
private final DictionaryTable<KeyValueAndUnitData> attributeTable = new DictionaryTable<>();
44+
private final DictionaryTable<StackData> stackTable = new DictionaryTable<>();
45+
46+
public ProfileDictionaryCompositor() {
47+
48+
// The [0] element of each table should be a 'null' element, such that a 0 value pointer can be
49+
// used to indicate null / not set, in preference to requiring an 'optional' modifier on the
50+
// pointer declarations. The value of this placeholder element is one with all default field
51+
// values, such that it encodes to the minimal number of bytes.
52+
// These values are inlined here, as there is no other point of use.
53+
// They could be public static constants on this class or the corresponding Immutable*Data
54+
// classes if other use cases require them.
55+
56+
mappingTable.putIfAbsent(ImmutableMappingData.create(0, 0, 0, 0, Collections.emptyList()));
57+
locationTable.putIfAbsent(
58+
ImmutableLocationData.create(0, 0, Collections.emptyList(), Collections.emptyList()));
59+
functionTable.putIfAbsent(ImmutableFunctionData.create(0, 0, 0, 0));
60+
linkTable.putIfAbsent(ImmutableLinkData.create("", ""));
61+
stringTable.putIfAbsent("");
62+
attributeTable.putIfAbsent(
63+
ImmutableKeyValueAndUnitData.create(0, Value.of(""), 0)); // TODO api spec
64+
stackTable.putIfAbsent(ImmutableStackData.create(Collections.emptyList()));
65+
}
66+
67+
/**
68+
* Provides the contents of the dictionary tables as a ProfileDictionaryData.
69+
*
70+
* <p>The return value is a view, not a copy. No further elements should be inserted after this is
71+
* called as it would compromise the intended immutability of the result.
72+
*
73+
* @return a ProfileDictionaryData with the contents of the tables.
74+
*/
75+
public ProfileDictionaryData getProfileDictionaryData() {
76+
return ImmutableProfileDictionaryData.create(
77+
mappingTable.getTable(),
78+
locationTable.getTable(),
79+
functionTable.getTable(),
80+
linkTable.getTable(),
81+
stringTable.getTable(),
82+
attributeTable.getTable(),
83+
stackTable.getTable());
84+
}
85+
86+
/**
87+
* Stores the provided element if an equivalent is not already present, and returns its index.
88+
*
89+
* @param mappingData an element to store.
90+
* @return the index of the added or existing element.
91+
*/
92+
public int putIfAbsent(MappingData mappingData) {
93+
return mappingTable.putIfAbsent(mappingData);
94+
}
95+
96+
/**
97+
* Stores the provided element if an equivalent is not already present, and returns its index.
98+
*
99+
* @param locationData an element to store.
100+
* @return the index of the added or existing element.
101+
*/
102+
public int putIfAbsent(LocationData locationData) {
103+
return locationTable.putIfAbsent(locationData);
104+
}
105+
106+
/**
107+
* Stores the provided element if an equivalent is not already present, and returns its index.
108+
*
109+
* @param functionData an element to store.
110+
* @return the index of the added or existing element.
111+
*/
112+
public int putIfAbsent(FunctionData functionData) {
113+
return functionTable.putIfAbsent(functionData);
114+
}
115+
116+
/**
117+
* Stores the provided element if an equivalent is not already present, and returns its index.
118+
*
119+
* @param linkData an element to store.
120+
* @return the index of the added or existing element.
121+
*/
122+
public int putIfAbsent(LinkData linkData) {
123+
return linkTable.putIfAbsent(linkData);
124+
}
125+
126+
/**
127+
* Stores the provided element if an equivalent is not already present, and returns its index.
128+
*
129+
* @param string an element to store.
130+
* @return the index of the added or existing element.
131+
*/
132+
public int putIfAbsent(String string) {
133+
return stringTable.putIfAbsent(string);
134+
}
135+
136+
/**
137+
* Stores the provided element if an equivalent is not already present, and returns its index.
138+
*
139+
* @param keyValueAndUnitData an element to store.
140+
* @return the index of the added or existing element.
141+
*/
142+
public int putIfAbsent(KeyValueAndUnitData keyValueAndUnitData) {
143+
return attributeTable.putIfAbsent(keyValueAndUnitData);
144+
}
145+
146+
/**
147+
* Stores the provided element if an equivalent is not already present, and returns its index.
148+
*
149+
* @param stackData an element to store.
150+
* @return the index of the added or existing element.
151+
*/
152+
public int putIfAbsent(StackData stackData) {
153+
return stackTable.putIfAbsent(stackData);
154+
}
155+
}
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)