Skip to content

Commit f8a4d81

Browse files
authored
Optimize log hot path (#4913)
* ComponentRegistry accepts name, version, schemaUrl instead of InstrumentationScopeInfo * Fix comment
1 parent 112b891 commit f8a4d81

File tree

6 files changed

+143
-109
lines changed

6 files changed

+143
-109
lines changed

sdk/common/src/main/java/io/opentelemetry/sdk/internal/ComponentRegistry.java

+80-14
Original file line numberDiff line numberDiff line change
@@ -5,46 +5,112 @@
55

66
package io.opentelemetry.sdk.internal;
77

8+
import io.opentelemetry.api.common.Attributes;
89
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
9-
import java.util.ArrayList;
1010
import java.util.Collection;
1111
import java.util.Collections;
12+
import java.util.IdentityHashMap;
13+
import java.util.Map;
14+
import java.util.Set;
1215
import java.util.concurrent.ConcurrentHashMap;
13-
import java.util.concurrent.ConcurrentMap;
1416
import java.util.function.Function;
17+
import javax.annotation.Nullable;
1518

1619
/**
1720
* Component (tracer, meter, etc) registry class for all the provider classes (TracerProvider,
1821
* MeterProvider, etc.).
1922
*
23+
* <p>Components are identified by name, version, and schema. Name is required, but version and
24+
* schema are optional. Therefore, we have 4 possible scenarios for component keys:
25+
*
26+
* <ol>
27+
* <li>Only name is provided, represented by {@link #componentByName}
28+
* <li>Name and version are provided, represented by {@link #componentByNameAndVersion}
29+
* <li>Name and schema are provided, represented by {@link #componentByNameAndSchema}
30+
* <li>Name, version and schema are provided, represented by {@link
31+
* #componentByNameVersionAndSchema}
32+
* </ol>
33+
*
2034
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
2135
* at any time.
2236
*
2337
* @param <V> the type of the registered value.
2438
*/
2539
public final class ComponentRegistry<V> {
2640

27-
private final ConcurrentMap<InstrumentationScopeInfo, V> registry = new ConcurrentHashMap<>();
41+
private final Map<String, V> componentByName = new ConcurrentHashMap<>();
42+
private final Map<String, Map<String, V>> componentByNameAndVersion = new ConcurrentHashMap<>();
43+
private final Map<String, Map<String, V>> componentByNameAndSchema = new ConcurrentHashMap<>();
44+
private final Map<String, Map<String, Map<String, V>>> componentByNameVersionAndSchema =
45+
new ConcurrentHashMap<>();
46+
47+
private final Set<V> allComponents = Collections.newSetFromMap(new IdentityHashMap<>());
48+
2849
private final Function<InstrumentationScopeInfo, V> factory;
2950

3051
public ComponentRegistry(Function<InstrumentationScopeInfo, V> factory) {
3152
this.factory = factory;
3253
}
3354

3455
/**
35-
* Returns the registered value associated with this {@link InstrumentationScopeInfo scope} if
36-
* any, otherwise creates a new instance and associates it with the given scope.
56+
* Returns the component associated with the {@code name}, {@code version}, and {@code schemaUrl}.
57+
* {@link Attributes} are not part of component identity. Behavior is undefined when different
58+
* {@link Attributes} are provided where {@code name}, {@code version}, and {@code schemaUrl} are
59+
* identical.
3760
*/
38-
public V get(InstrumentationScopeInfo instrumentationScopeInfo) {
39-
// Optimistic lookup, before creating the new component.
40-
V component = registry.get(instrumentationScopeInfo);
41-
if (component != null) {
42-
return component;
61+
public V get(
62+
String name, @Nullable String version, @Nullable String schemaUrl, Attributes attributes) {
63+
if (version != null && schemaUrl != null) {
64+
Map<String, Map<String, V>> componentByVersionAndSchema =
65+
componentByNameVersionAndSchema.computeIfAbsent(
66+
name, unused -> new ConcurrentHashMap<>());
67+
Map<String, V> componentBySchema =
68+
componentByVersionAndSchema.computeIfAbsent(version, unused -> new ConcurrentHashMap<>());
69+
return componentBySchema.computeIfAbsent(
70+
schemaUrl,
71+
schemaUrl1 ->
72+
buildComponent(
73+
InstrumentationScopeInfo.builder(name)
74+
.setVersion(version)
75+
.setSchemaUrl(schemaUrl1)
76+
.setAttributes(attributes)
77+
.build()));
78+
} else if (version != null) { // schemaUrl == null
79+
Map<String, V> componentByVersion =
80+
componentByNameAndVersion.computeIfAbsent(name, unused -> new ConcurrentHashMap<>());
81+
return componentByVersion.computeIfAbsent(
82+
version,
83+
version1 ->
84+
buildComponent(
85+
InstrumentationScopeInfo.builder(name)
86+
.setVersion(version1)
87+
.setAttributes(attributes)
88+
.build()));
89+
}
90+
if (schemaUrl != null) { // version == null
91+
Map<String, V> componentBySchema =
92+
componentByNameAndSchema.computeIfAbsent(name, unused -> new ConcurrentHashMap<>());
93+
return componentBySchema.computeIfAbsent(
94+
schemaUrl,
95+
schemaUrl1 ->
96+
buildComponent(
97+
InstrumentationScopeInfo.builder(name)
98+
.setSchemaUrl(schemaUrl1)
99+
.setAttributes(attributes)
100+
.build()));
101+
} else { // schemaUrl == null && version == null
102+
return componentByName.computeIfAbsent(
103+
name,
104+
name1 ->
105+
buildComponent(
106+
InstrumentationScopeInfo.builder(name1).setAttributes(attributes).build()));
43107
}
108+
}
44109

45-
V newComponent = factory.apply(instrumentationScopeInfo);
46-
V oldComponent = registry.putIfAbsent(instrumentationScopeInfo, newComponent);
47-
return oldComponent != null ? oldComponent : newComponent;
110+
private V buildComponent(InstrumentationScopeInfo instrumentationScopeInfo) {
111+
V component = factory.apply(instrumentationScopeInfo);
112+
allComponents.add(component);
113+
return component;
48114
}
49115

50116
/**
@@ -53,6 +119,6 @@ public V get(InstrumentationScopeInfo instrumentationScopeInfo) {
53119
* @return a {@code Collection} view of the registered components.
54120
*/
55121
public Collection<V> getComponents() {
56-
return Collections.unmodifiableCollection(new ArrayList<>(registry.values()));
122+
return Collections.unmodifiableCollection(allComponents);
57123
}
58124
}

sdk/common/src/test/java/io/opentelemetry/sdk/internal/ComponentRegistryTest.java

+22-71
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import static org.assertj.core.api.Assertions.assertThat;
99

1010
import io.opentelemetry.api.common.Attributes;
11-
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
1211
import org.junit.jupiter.api.Test;
1312

1413
class ComponentRegistryTest {
@@ -22,83 +21,35 @@ class ComponentRegistryTest {
2221

2322
@Test
2423
void get_SameInstance() {
25-
assertThat(registry.get(InstrumentationScopeInfo.builder(NAME).build()))
26-
.isSameAs(registry.get(InstrumentationScopeInfo.builder(NAME).build()));
27-
assertThat(registry.get(InstrumentationScopeInfo.builder(NAME).setVersion(VERSION).build()))
28-
.isSameAs(registry.get(InstrumentationScopeInfo.builder(NAME).setVersion(VERSION).build()));
29-
assertThat(
30-
registry.get(InstrumentationScopeInfo.builder(NAME).setSchemaUrl(SCHEMA_URL).build()))
24+
assertThat(registry.get(NAME, null, null, Attributes.empty()))
25+
.isSameAs(registry.get(NAME, null, null, Attributes.empty()))
26+
.isSameAs(registry.get(NAME, null, null, Attributes.builder().put("k1", "v2").build()));
27+
28+
assertThat(registry.get(NAME, VERSION, null, Attributes.empty()))
29+
.isSameAs(registry.get(NAME, VERSION, null, Attributes.empty()))
30+
.isSameAs(registry.get(NAME, VERSION, null, Attributes.builder().put("k1", "v2").build()));
31+
assertThat(registry.get(NAME, null, SCHEMA_URL, Attributes.empty()))
32+
.isSameAs(registry.get(NAME, null, SCHEMA_URL, Attributes.empty()))
3133
.isSameAs(
32-
registry.get(InstrumentationScopeInfo.builder(NAME).setSchemaUrl(SCHEMA_URL).build()));
33-
assertThat(
34-
registry.get(InstrumentationScopeInfo.builder(NAME).setAttributes(ATTRIBUTES).build()))
34+
registry.get(NAME, null, SCHEMA_URL, Attributes.builder().put("k1", "v2").build()));
35+
assertThat(registry.get(NAME, VERSION, SCHEMA_URL, Attributes.empty()))
36+
.isSameAs(registry.get(NAME, VERSION, SCHEMA_URL, Attributes.empty()))
3537
.isSameAs(
36-
registry.get(InstrumentationScopeInfo.builder(NAME).setAttributes(ATTRIBUTES).build()));
37-
assertThat(
38-
registry.get(
39-
InstrumentationScopeInfo.builder(NAME)
40-
.setVersion(VERSION)
41-
.setSchemaUrl(SCHEMA_URL)
42-
.setAttributes(ATTRIBUTES)
43-
.build()))
44-
.isSameAs(
45-
registry.get(
46-
InstrumentationScopeInfo.builder(NAME)
47-
.setVersion(VERSION)
48-
.setSchemaUrl(SCHEMA_URL)
49-
.setAttributes(ATTRIBUTES)
50-
.build()));
38+
registry.get(NAME, VERSION, SCHEMA_URL, Attributes.builder().put("k1", "v2").build()));
5139
}
5240

5341
@Test
5442
void get_DifferentInstance() {
55-
InstrumentationScopeInfo allFields =
56-
InstrumentationScopeInfo.builder(NAME)
57-
.setVersion(VERSION)
58-
.setSchemaUrl(SCHEMA_URL)
59-
.setAttributes(ATTRIBUTES)
60-
.build();
43+
assertThat(registry.get(NAME, VERSION, SCHEMA_URL, ATTRIBUTES))
44+
.isNotSameAs(registry.get(NAME + "_1", VERSION, SCHEMA_URL, ATTRIBUTES))
45+
.isNotSameAs(registry.get(NAME, VERSION + "_1", SCHEMA_URL, ATTRIBUTES))
46+
.isNotSameAs(registry.get(NAME, VERSION, SCHEMA_URL + "_1", ATTRIBUTES));
47+
48+
assertThat(registry.get(NAME, VERSION, null, Attributes.empty()))
49+
.isNotSameAs(registry.get(NAME, null, null, Attributes.empty()));
6150

62-
assertThat(registry.get(allFields))
63-
.isNotSameAs(
64-
registry.get(
65-
InstrumentationScopeInfo.builder(NAME + "_1")
66-
.setVersion(VERSION)
67-
.setSchemaUrl(SCHEMA_URL)
68-
.setAttributes(ATTRIBUTES)
69-
.build()));
70-
assertThat(registry.get(allFields))
71-
.isNotSameAs(
72-
registry.get(
73-
InstrumentationScopeInfo.builder(NAME)
74-
.setVersion(VERSION + "_1")
75-
.setSchemaUrl(SCHEMA_URL)
76-
.setAttributes(ATTRIBUTES)
77-
.build()));
78-
assertThat(registry.get(allFields))
79-
.isNotSameAs(
80-
registry.get(
81-
InstrumentationScopeInfo.builder(NAME)
82-
.setVersion(VERSION)
83-
.setSchemaUrl(SCHEMA_URL + "_1")
84-
.setAttributes(ATTRIBUTES)
85-
.build()));
86-
assertThat(registry.get(allFields))
87-
.isNotSameAs(
88-
registry.get(
89-
InstrumentationScopeInfo.builder(NAME)
90-
.setVersion(VERSION)
91-
.setSchemaUrl(SCHEMA_URL)
92-
.setAttributes(Attributes.builder().put("k1", "v2").build())
93-
.build()));
94-
assertThat(registry.get(InstrumentationScopeInfo.builder(NAME).setVersion(VERSION).build()))
95-
.isNotSameAs(registry.get(InstrumentationScopeInfo.builder(NAME).build()));
96-
assertThat(
97-
registry.get(InstrumentationScopeInfo.builder(NAME).setSchemaUrl(SCHEMA_URL).build()))
98-
.isNotSameAs(registry.get(InstrumentationScopeInfo.builder(NAME).build()));
99-
assertThat(
100-
registry.get(InstrumentationScopeInfo.builder(NAME).setAttributes(ATTRIBUTES).build()))
101-
.isNotSameAs(registry.get(InstrumentationScopeInfo.builder(NAME).build()));
51+
assertThat(registry.get(NAME, null, SCHEMA_URL, Attributes.empty()))
52+
.isNotSameAs(registry.get(NAME, null, null, Attributes.empty()));
10253
}
10354

10455
private static final class TestComponent {}

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerBuilder.java

+10-7
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,22 @@
55

66
package io.opentelemetry.sdk.logs;
77

8+
import io.opentelemetry.api.common.Attributes;
89
import io.opentelemetry.api.logs.LoggerBuilder;
9-
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
10-
import io.opentelemetry.sdk.common.InstrumentationScopeInfoBuilder;
1110
import io.opentelemetry.sdk.internal.ComponentRegistry;
1211
import javax.annotation.Nullable;
1312

1413
final class SdkLoggerBuilder implements LoggerBuilder {
1514

1615
private final ComponentRegistry<SdkLogger> registry;
17-
private final InstrumentationScopeInfoBuilder scopeBuilder;
16+
private final String instrumentationScopeName;
17+
@Nullable private String instrumentationScopeVersion;
18+
@Nullable private String schemaUrl;
1819
@Nullable private String eventDomain;
1920

2021
SdkLoggerBuilder(ComponentRegistry<SdkLogger> registry, String instrumentationScopeName) {
2122
this.registry = registry;
22-
this.scopeBuilder = InstrumentationScopeInfo.builder(instrumentationScopeName);
23+
this.instrumentationScopeName = instrumentationScopeName;
2324
}
2425

2526
@Override
@@ -30,19 +31,21 @@ public LoggerBuilder setEventDomain(String eventDomain) {
3031

3132
@Override
3233
public SdkLoggerBuilder setSchemaUrl(String schemaUrl) {
33-
scopeBuilder.setSchemaUrl(schemaUrl);
34+
this.schemaUrl = schemaUrl;
3435
return this;
3536
}
3637

3738
@Override
3839
public SdkLoggerBuilder setInstrumentationVersion(String instrumentationScopeVersion) {
39-
scopeBuilder.setVersion(instrumentationScopeVersion);
40+
this.instrumentationScopeVersion = instrumentationScopeVersion;
4041
return this;
4142
}
4243

4344
@Override
4445
public SdkLogger build() {
45-
SdkLogger logger = registry.get(scopeBuilder.build());
46+
SdkLogger logger =
47+
registry.get(
48+
instrumentationScopeName, instrumentationScopeVersion, schemaUrl, Attributes.empty());
4649
return eventDomain == null ? logger : logger.withEventDomain(eventDomain);
4750
}
4851
}

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerProvider.java

+11-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
package io.opentelemetry.sdk.logs;
77

8+
import io.opentelemetry.api.common.Attributes;
89
import io.opentelemetry.api.logs.Logger;
910
import io.opentelemetry.api.logs.LoggerBuilder;
1011
import io.opentelemetry.api.logs.LoggerProvider;
@@ -17,6 +18,7 @@
1718
import java.util.concurrent.TimeUnit;
1819
import java.util.function.Supplier;
1920
import java.util.logging.Level;
21+
import javax.annotation.Nullable;
2022

2123
/** SDK implementation for {@link LoggerProvider}. */
2224
public final class SdkLoggerProvider implements LoggerProvider, Closeable {
@@ -61,7 +63,8 @@ public static SdkLoggerProviderBuilder builder() {
6163
*/
6264
@Override
6365
public Logger get(String instrumentationScopeName) {
64-
return loggerBuilder(instrumentationScopeName).build();
66+
return loggerComponentRegistry.get(
67+
instrumentationNameOrDefault(instrumentationScopeName), null, null, Attributes.empty());
6568
}
6669

6770
/**
@@ -75,11 +78,16 @@ public LoggerBuilder loggerBuilder(String instrumentationScopeName) {
7578
if (isNoopLogRecordProcessor) {
7679
return LoggerProvider.noop().loggerBuilder(instrumentationScopeName);
7780
}
81+
return new SdkLoggerBuilder(
82+
loggerComponentRegistry, instrumentationNameOrDefault(instrumentationScopeName));
83+
}
84+
85+
private static String instrumentationNameOrDefault(@Nullable String instrumentationScopeName) {
7886
if (instrumentationScopeName == null || instrumentationScopeName.isEmpty()) {
7987
LOGGER.fine("Logger requested without instrumentation scope name.");
80-
instrumentationScopeName = DEFAULT_LOGGER_NAME;
88+
return DEFAULT_LOGGER_NAME;
8189
}
82-
return new SdkLoggerBuilder(loggerComponentRegistry, instrumentationScopeName);
90+
return instrumentationScopeName;
8391
}
8492

8593
/**

sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeterBuilder.java

+10-7
Original file line numberDiff line numberDiff line change
@@ -5,36 +5,39 @@
55

66
package io.opentelemetry.sdk.metrics;
77

8+
import io.opentelemetry.api.common.Attributes;
89
import io.opentelemetry.api.metrics.Meter;
910
import io.opentelemetry.api.metrics.MeterBuilder;
10-
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
11-
import io.opentelemetry.sdk.common.InstrumentationScopeInfoBuilder;
1211
import io.opentelemetry.sdk.internal.ComponentRegistry;
12+
import javax.annotation.Nullable;
1313

1414
class SdkMeterBuilder implements MeterBuilder {
1515

1616
private final ComponentRegistry<SdkMeter> registry;
17-
private final InstrumentationScopeInfoBuilder scopeBuilder;
17+
private final String instrumentationScopeName;
18+
@Nullable private String instrumentationScopeVersion;
19+
@Nullable private String schemaUrl;
1820

1921
SdkMeterBuilder(ComponentRegistry<SdkMeter> registry, String instrumentationScopeName) {
2022
this.registry = registry;
21-
this.scopeBuilder = InstrumentationScopeInfo.builder(instrumentationScopeName);
23+
this.instrumentationScopeName = instrumentationScopeName;
2224
}
2325

2426
@Override
2527
public MeterBuilder setSchemaUrl(String schemaUrl) {
26-
scopeBuilder.setSchemaUrl(schemaUrl);
28+
this.schemaUrl = schemaUrl;
2729
return this;
2830
}
2931

3032
@Override
3133
public MeterBuilder setInstrumentationVersion(String instrumentationScopeVersion) {
32-
scopeBuilder.setVersion(instrumentationScopeVersion);
34+
this.instrumentationScopeVersion = instrumentationScopeVersion;
3335
return this;
3436
}
3537

3638
@Override
3739
public Meter build() {
38-
return registry.get(scopeBuilder.build());
40+
return registry.get(
41+
instrumentationScopeName, instrumentationScopeVersion, schemaUrl, Attributes.empty());
3942
}
4043
}

0 commit comments

Comments
 (0)