Skip to content

Commit abfcb41

Browse files
committed
initial EntityProvider prototype
1 parent cc7d085 commit abfcb41

File tree

6 files changed

+380
-0
lines changed

6 files changed

+380
-0
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package io.opentelemetry.sdk.entities;
2+
3+
import com.google.auto.value.AutoValue;
4+
import io.opentelemetry.api.common.Attributes;
5+
import javax.annotation.concurrent.Immutable;
6+
7+
@Immutable
8+
@AutoValue
9+
abstract public class Entity {
10+
11+
public static Entity create(String id, String name, Attributes attributes) {
12+
return new AutoValue_Entity(id, name, attributes);
13+
}
14+
15+
public abstract String getId();
16+
17+
public abstract String getName();
18+
19+
public abstract Attributes getAttributes();
20+
21+
public Entity withAttributes(Attributes newAttributes) {
22+
return new AutoValue_Entity(getId(), getName(), newAttributes);
23+
}
24+
25+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package io.opentelemetry.sdk.entities;
2+
3+
import io.opentelemetry.sdk.resources.Resource;
4+
5+
public interface EntityListener {
6+
7+
void onEntityState(Entity state, Resource resource);
8+
void onEntityDelete(Entity state, Resource resource);
9+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package io.opentelemetry.sdk.entities;
2+
3+
import io.opentelemetry.api.common.Attributes;
4+
import io.opentelemetry.sdk.resources.Resource;
5+
6+
public interface EntityProvider {
7+
8+
/**
9+
* Returns the current representation of the Resource, as materialized by the
10+
* Entities contained herein.
11+
*/
12+
Resource getResource();
13+
14+
/**
15+
* Adds a listener to the end of the list of EntityListeners. The listener
16+
* will be notified when changes are made to the EntityProvider.
17+
* @param listener - an EntityListener
18+
*/
19+
void addListener(EntityListener listener);
20+
21+
/**
22+
* Adds a new Entity to the EntityProvider. If an Entity with the same id already exists,
23+
* it will be removed and a new instance will be inserted at the end of the list of
24+
* entities. After the entity is added, the resource is rebuilt and the listeners are
25+
* notified.
26+
*/
27+
void addEntity(String id, String name, Attributes attributes);
28+
29+
/**
30+
* Updates an existing Entity with the given id with new attributes. If an Entity
31+
* with the given id does not exist, this is effective a no-op. This method does not
32+
* change the order of ids, so the change is effectively made "in-place". After the
33+
* Entity has been updated, the listeners will be notified.
34+
*/
35+
void updateEntity(String id, Attributes attributes);
36+
37+
/**
38+
* Deletes the Entity with the given id. If the Entity does not exist within
39+
* this EntityProvider, then it is effectively a no-op. If the Entity is removed,
40+
* the Resource will be rebuilt and listeners will then be notified.
41+
*/
42+
void deleteEntity(String id);
43+
44+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package io.opentelemetry.sdk.entities;
2+
3+
import io.opentelemetry.api.common.Attributes;
4+
import io.opentelemetry.api.internal.GuardedBy;
5+
import io.opentelemetry.sdk.resources.Resource;
6+
import io.opentelemetry.sdk.resources.ResourceBuilder;
7+
import java.util.ArrayList;
8+
import java.util.Collections;
9+
import java.util.LinkedHashMap;
10+
import java.util.List;
11+
import java.util.concurrent.atomic.AtomicReference;
12+
13+
public class SdkEntityProvider implements EntityProvider {
14+
15+
private final Object lock = new Object();
16+
private final AtomicReference<Resource> resource = new AtomicReference<>(Resource.empty());
17+
@GuardedBy("lock")
18+
private final LinkedHashMap<String,Entity> entities = new LinkedHashMap<>();
19+
@GuardedBy("lock")
20+
private final List<EntityListener> listeners = new ArrayList<>();
21+
22+
public SdkEntityProvider() {
23+
this(Collections.emptyList());
24+
}
25+
26+
public SdkEntityProvider(List<Entity> initialEntities) {
27+
for (Entity entity : initialEntities) {
28+
entities.put(entity.getId(), entity);
29+
}
30+
rebuildResource();
31+
}
32+
33+
@Override
34+
public Resource getResource() {
35+
Resource result = resource.get();
36+
return result == null ? Resource.empty() : result;
37+
}
38+
39+
@Override
40+
public void addListener(EntityListener listener) {
41+
synchronized(lock){
42+
listeners.add(listener);
43+
}
44+
}
45+
46+
@Override
47+
public void addEntity(String id, String name, Attributes attributes) {
48+
Entity entity = Entity.create(id, name, attributes);
49+
List<EntityListener> listeners;
50+
Resource resource;
51+
synchronized(lock){
52+
entities.remove(id);
53+
entities.put(id, entity);
54+
55+
resource = rebuildResource();
56+
listeners = new ArrayList<>(this.listeners);
57+
}
58+
for (EntityListener listener : listeners) {
59+
listener.onEntityState(entity, resource);
60+
}
61+
62+
}
63+
64+
@Override
65+
public void updateEntity(String id, Attributes attributes) {
66+
List<EntityListener> listeners;
67+
Resource resource;
68+
Entity updatedEntity;
69+
synchronized(lock){
70+
Entity entity = entities.get(id);
71+
if(entity == null){
72+
return;
73+
}
74+
updatedEntity = entity.withAttributes(attributes);
75+
entities.put(id, updatedEntity);
76+
resource = rebuildResource();
77+
listeners = new ArrayList<>(this.listeners);
78+
}
79+
for (EntityListener listener : listeners) {
80+
listener.onEntityState(updatedEntity, resource);
81+
}
82+
}
83+
84+
@Override
85+
public void deleteEntity(String id) {
86+
Entity removedEntity;
87+
Resource resource;
88+
synchronized(lock){
89+
removedEntity = entities.remove(id);
90+
if(removedEntity == null){
91+
return;
92+
}
93+
resource = rebuildResource();
94+
}
95+
for (EntityListener listener : listeners) {
96+
listener.onEntityDelete(removedEntity, resource);
97+
}
98+
}
99+
100+
// private void doMutationInsideLock(){
101+
//
102+
// }
103+
104+
private Resource rebuildResource() {
105+
Resource newResource = doRebuildResource();
106+
resource.set(newResource);
107+
return newResource;
108+
}
109+
110+
private Resource doRebuildResource() {
111+
if (entities.isEmpty()) {
112+
return Resource.empty();
113+
}
114+
ResourceBuilder builder = Resource.builder();
115+
for (Entity entity : entities.values()) {
116+
builder.putAll(entity.getAttributes());
117+
}
118+
return builder.build();
119+
}
120+
}
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
package io.opentelemetry.sdk.entities;
2+
3+
import static io.opentelemetry.api.common.AttributeKey.stringKey;
4+
import static java.util.Collections.singletonList;
5+
import static org.assertj.core.api.Assertions.assertThat;
6+
import static org.assertj.core.api.Assertions.entry;
7+
import static org.mockito.Mockito.doNothing;
8+
import static org.mockito.Mockito.mock;
9+
import static org.mockito.Mockito.verifyNoInteractions;
10+
11+
import io.opentelemetry.api.common.AttributeKey;
12+
import io.opentelemetry.api.common.Attributes;
13+
import io.opentelemetry.sdk.resources.Resource;
14+
import java.util.Arrays;
15+
import java.util.List;
16+
import org.junit.jupiter.api.Test;
17+
import org.mockito.ArgumentCaptor;
18+
19+
class SdkEntityProviderTest {
20+
21+
AttributeKey<String> k1 = stringKey("a1.k");
22+
AttributeKey<String> k2 = stringKey("a2.k");
23+
Attributes a1 = Attributes.of(k1, "boop");
24+
Attributes a2 = Attributes.of(k2, "bleep");
25+
Entity e1 = Entity.create("foo", "fooname", a1);
26+
Entity e2 = Entity.create("bar", "barname", a2);
27+
28+
@Test
29+
void emptyListGivesConstructorGivesResource() {
30+
Resource res = new SdkEntityProvider().getResource();
31+
assertThat(res).isSameAs(Resource.empty());
32+
}
33+
34+
@Test
35+
void constructorGivesResource() {
36+
List<Entity> entities = Arrays.asList(e1, e2);
37+
Resource res = new SdkEntityProvider(entities).getResource();
38+
assertThat(res.getAttributes().asMap()).containsOnly(
39+
entry(k1, "boop"),
40+
entry(k2, "bleep")
41+
);
42+
}
43+
44+
@Test
45+
void constructWithIdCollisions() {
46+
Entity e1 = Entity.create("x", "fooname", a1);
47+
Entity e2 = Entity.create("x", "barname", a2);
48+
List<Entity> entities = Arrays.asList(e1, e2);
49+
50+
Resource res = new SdkEntityProvider(entities).getResource();
51+
assertThat(res.getAttributes().asMap()).containsOnly(
52+
entry(k2, "bleep"));
53+
}
54+
55+
@Test
56+
void addNewEntity() {
57+
Attributes newAttr = Attributes.of(stringKey("new key"), "newval");
58+
EntityListener listener = mock(EntityListener.class);
59+
ArgumentCaptor<Entity> entityCaptor = ArgumentCaptor.forClass(Entity.class);
60+
ArgumentCaptor<Resource> resourceCaptor = ArgumentCaptor.forClass(Resource.class);
61+
62+
doNothing().when(listener).onEntityState(entityCaptor.capture(), resourceCaptor.capture());
63+
64+
EntityProvider entityProvider = new SdkEntityProvider(singletonList(e1));
65+
entityProvider.addListener(listener);
66+
67+
entityProvider.addEntity("new id", "new name", newAttr);
68+
69+
assertThat(entityCaptor.getValue().getId()).isEqualTo("new id");
70+
assertThat(entityCaptor.getValue().getName()).isEqualTo("new name");
71+
assertThat(entityProvider.getResource().getAttributes().asMap()).containsOnly(
72+
entry(k1, "boop"),
73+
entry(stringKey("new key"), "newval")
74+
);
75+
}
76+
77+
@Test
78+
void addWithExistingIdOverridesEntity() {
79+
Attributes newAttr = Attributes.of(stringKey("jibro"), "newval");
80+
81+
EntityListener listener = mock(EntityListener.class);
82+
83+
ArgumentCaptor<Entity> entityCaptor = ArgumentCaptor.forClass(Entity.class);
84+
ArgumentCaptor<Resource> resourceCaptor = ArgumentCaptor.forClass(Resource.class);
85+
doNothing().when(listener).onEntityState(entityCaptor.capture(), resourceCaptor.capture());
86+
87+
EntityProvider entityProvider = new SdkEntityProvider(singletonList(e1));
88+
entityProvider.addListener(listener);
89+
90+
entityProvider.addEntity(e1.getId(), "new name", newAttr);
91+
92+
assertThat(entityCaptor.getValue().getId()).isEqualTo(e1.getId());
93+
assertThat(entityCaptor.getValue().getName()).isEqualTo("new name");
94+
assertThat(resourceCaptor.getValue()).isSameAs(entityProvider.getResource());
95+
assertThat(entityProvider.getResource().getAttributes().asMap()).containsOnly(
96+
entry(stringKey("jibro"), "newval")
97+
);
98+
}
99+
100+
@Test
101+
void updateNotFoundNoListeners() {
102+
EntityListener listener = mock(EntityListener.class);
103+
104+
EntityProvider entityProvider = new SdkEntityProvider(singletonList(e1));
105+
entityProvider.addListener(listener);
106+
107+
Resource resource = entityProvider.getResource();
108+
entityProvider.updateEntity("notfound", Attributes.empty());
109+
verifyNoInteractions(listener);
110+
assertThat(entityProvider.getResource()).isSameAs(resource);
111+
}
112+
113+
@Test
114+
void updateEntity() {
115+
AttributeKey<String> newKey = stringKey("jibro");
116+
Attributes newAttr = Attributes.of(newKey, "newval");
117+
EntityListener listener = mock(EntityListener.class);
118+
119+
ArgumentCaptor<Entity> entityCaptor = ArgumentCaptor.forClass(Entity.class);
120+
ArgumentCaptor<Resource> resourceCaptor = ArgumentCaptor.forClass(Resource.class);
121+
doNothing().when(listener).onEntityState(entityCaptor.capture(), resourceCaptor.capture());
122+
123+
EntityProvider entityProvider = new SdkEntityProvider(singletonList(e1));
124+
entityProvider.addListener(listener);
125+
126+
entityProvider.updateEntity(e1.getId(), newAttr);
127+
128+
assertThat(entityCaptor.getValue().getId()).isEqualTo(e1.getId());
129+
assertThat(entityCaptor.getValue().getName()).isEqualTo(e1.getName());
130+
assertThat(entityCaptor.getValue().getAttributes()).isEqualTo(newAttr);
131+
assertThat(entityProvider.getResource()).isSameAs(resourceCaptor.getValue());
132+
assertThat(resourceCaptor.getValue().getAttributes().asMap()).containsOnly(
133+
entry(newKey, "newval")
134+
);
135+
}
136+
137+
@Test
138+
void deleteEntity() {
139+
EntityListener listener = mock(EntityListener.class);
140+
141+
ArgumentCaptor<Entity> entityCaptor = ArgumentCaptor.forClass(Entity.class);
142+
ArgumentCaptor<Resource> resourceCaptor = ArgumentCaptor.forClass(Resource.class);
143+
doNothing().when(listener).onEntityDelete(entityCaptor.capture(), resourceCaptor.capture());
144+
145+
EntityProvider entityProvider = new SdkEntityProvider(Arrays.asList(e1, e2));
146+
entityProvider.addListener(listener);
147+
148+
entityProvider.deleteEntity(e1.getId());
149+
150+
assertThat(entityCaptor.getValue().getId()).isEqualTo(e1.getId());
151+
assertThat(entityCaptor.getValue().getName()).isEqualTo(e1.getName());
152+
assertThat(entityProvider.getResource()).isSameAs(resourceCaptor.getValue());
153+
assertThat(resourceCaptor.getValue().getAttributes().asMap()).containsOnly(
154+
entry(k2, "bleep")
155+
);
156+
}
157+
158+
@Test
159+
void deleteEntityNotFound() {
160+
EntityListener listener = mock(EntityListener.class);
161+
162+
EntityProvider entityProvider = new SdkEntityProvider(Arrays.asList(e1, e2));
163+
164+
Resource resource = entityProvider.getResource();
165+
entityProvider.addListener(listener);
166+
entityProvider.deleteEntity("NO WAY");
167+
verifyNoInteractions(listener);
168+
assertThat(entityProvider.getResource()).isSameAs(resource);
169+
}
170+
171+
@Test
172+
void deleteLastReturnsEmptyResource() {
173+
EntityProvider entityProvider = new SdkEntityProvider(Arrays.asList(e1, e2));
174+
assertThat(entityProvider.getResource().getAttributes().size()).isPositive();
175+
entityProvider.deleteEntity(e1.getId());
176+
entityProvider.deleteEntity(e2.getId());
177+
assertThat(entityProvider.getResource().getAttributes().size()).isZero();
178+
assertThat(entityProvider.getResource()).isSameAs(Resource.empty());
179+
}
180+
181+
}

sdk/trace/src/main/java/io/opentelemetry/sdk/trace/TracerSharedState.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ final class TracerSharedState {
3333
Clock clock,
3434
IdGenerator idGenerator,
3535
Resource resource,
36+
// xxx entityprovider instead of resource here ??,
3637
Supplier<SpanLimits> spanLimitsSupplier,
3738
Sampler sampler,
3839
List<SpanProcessor> spanProcessors) {

0 commit comments

Comments
 (0)