Skip to content

Commit 7070577

Browse files
authored
Profiling instrumentation source (#2186)
* Add the InstrumentationSource enum which will be used to differentiate between callstacks collected by the two profiling extensions present in the agent. * Add an attribute specifying the instrumentation source of 'continuous' to profiling otel log messages. * Include a lowercase name value on InstrumentationSource for use when exporting otel logs. * Add missing test for including the frame count attribute in the profiling otel log message attributes. * Configure the continuous CPU and Memory profiler log exporter with an InstrumentationSource of 'CONTINUOUS'. * Apply spotless code formatting. * Remove usage of List method 'getFirst' which is not available in JDK17. * Add 'INSTRUMENTATION_SOURCE' constant in ProfilingSemanticAttributes. * Add test asserting the expected number of attributes are included on every profilng log message. * Use random log messages in PprofLogDataExporterTest.
1 parent b166b08 commit 7070577

File tree

8 files changed

+426
-3
lines changed

8 files changed

+426
-3
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright Splunk Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.splunk.opentelemetry.profiler;
18+
19+
import java.util.Locale;
20+
21+
public enum InstrumentationSource {
22+
CONTINUOUS,
23+
SNAPSHOT;
24+
25+
private final String value;
26+
27+
InstrumentationSource() {
28+
this.value = name().toLowerCase(Locale.ROOT);
29+
}
30+
31+
public String value() {
32+
return value;
33+
}
34+
}

profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilingSemanticAttributes.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ public class ProfilingSemanticAttributes {
3939
public static final AttributeKey<String> DATA_FORMAT = stringKey("profiling.data.format");
4040
public static final String PPROF_GZIP_BASE64 = "pprof-gzip-base64";
4141
public static final AttributeKey<Long> FRAME_COUNT = longKey("profiling.data.total.frame.count");
42+
public static final AttributeKey<String> INSTRUMENTATION_SOURCE =
43+
stringKey("profiling.instrumentation.source");
4244

4345
public static final AttributeKey<Long> THREAD_ID = longKey("thread.id");
4446
public static final AttributeKey<String> THREAD_NAME = stringKey("thread.name");

profiler/src/main/java/com/splunk/opentelemetry/profiler/allocation/exporter/PprofAllocationEventExporter.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import com.google.perftools.profiles.ProfileProto.Profile;
3030
import com.google.perftools.profiles.ProfileProto.Sample;
3131
import com.splunk.opentelemetry.profiler.EventReader;
32+
import com.splunk.opentelemetry.profiler.InstrumentationSource;
3233
import com.splunk.opentelemetry.profiler.ProfilingDataType;
3334
import com.splunk.opentelemetry.profiler.allocation.sampler.AllocationEventSampler;
3435
import com.splunk.opentelemetry.profiler.exporter.PprofLogDataExporter;
@@ -51,7 +52,8 @@ private PprofAllocationEventExporter(Builder builder) {
5152
this.eventReader = builder.eventReader;
5253
this.stackDepth = builder.stackDepth;
5354
this.pprofLogDataExporter =
54-
new PprofLogDataExporter(builder.otelLogger, ProfilingDataType.ALLOCATION);
55+
new PprofLogDataExporter(
56+
builder.otelLogger, ProfilingDataType.ALLOCATION, InstrumentationSource.CONTINUOUS);
5557
}
5658

5759
@Override

profiler/src/main/java/com/splunk/opentelemetry/profiler/exporter/PprofCpuEventExporter.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import static com.splunk.opentelemetry.profiler.ProfilingSemanticAttributes.TRACE_ID;
2828

2929
import com.google.perftools.profiles.ProfileProto.Sample;
30+
import com.splunk.opentelemetry.profiler.InstrumentationSource;
3031
import com.splunk.opentelemetry.profiler.ProfilingDataType;
3132
import com.splunk.opentelemetry.profiler.context.StackToSpanLinkage;
3233
import com.splunk.opentelemetry.profiler.events.EventPeriods;
@@ -46,7 +47,9 @@ public class PprofCpuEventExporter implements CpuEventExporter {
4647
private PprofCpuEventExporter(Builder builder) {
4748
this.eventPeriods = builder.eventPeriods;
4849
this.stackDepth = builder.stackDepth;
49-
this.pprofLogDataExporter = new PprofLogDataExporter(builder.otelLogger, ProfilingDataType.CPU);
50+
this.pprofLogDataExporter =
51+
new PprofLogDataExporter(
52+
builder.otelLogger, ProfilingDataType.CPU, InstrumentationSource.CONTINUOUS);
5053
}
5154

5255
@Override

profiler/src/main/java/com/splunk/opentelemetry/profiler/exporter/PprofLogDataExporter.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@
1919
import static com.splunk.opentelemetry.profiler.ProfilingSemanticAttributes.DATA_FORMAT;
2020
import static com.splunk.opentelemetry.profiler.ProfilingSemanticAttributes.DATA_TYPE;
2121
import static com.splunk.opentelemetry.profiler.ProfilingSemanticAttributes.FRAME_COUNT;
22+
import static com.splunk.opentelemetry.profiler.ProfilingSemanticAttributes.INSTRUMENTATION_SOURCE;
2223
import static com.splunk.opentelemetry.profiler.ProfilingSemanticAttributes.PPROF_GZIP_BASE64;
2324
import static com.splunk.opentelemetry.profiler.ProfilingSemanticAttributes.PROFILING_SOURCE;
2425
import static com.splunk.opentelemetry.profiler.ProfilingSemanticAttributes.SOURCE_TYPE;
2526
import static java.util.logging.Level.FINE;
2627

28+
import com.splunk.opentelemetry.profiler.InstrumentationSource;
2729
import com.splunk.opentelemetry.profiler.ProfilingDataType;
2830
import io.opentelemetry.api.common.Attributes;
2931
import io.opentelemetry.api.logs.Logger;
@@ -37,14 +39,16 @@ public class PprofLogDataExporter {
3739
private final ProfilingDataType dataType;
3840
private final Attributes commonAttributes;
3941

40-
public PprofLogDataExporter(Logger otelLogger, ProfilingDataType dataType) {
42+
public PprofLogDataExporter(
43+
Logger otelLogger, ProfilingDataType dataType, InstrumentationSource instrumentationSource) {
4144
this.otelLogger = otelLogger;
4245
this.dataType = dataType;
4346
this.commonAttributes =
4447
Attributes.builder()
4548
.put(SOURCE_TYPE, PROFILING_SOURCE)
4649
.put(DATA_TYPE, dataType.value())
4750
.put(DATA_FORMAT, PPROF_GZIP_BASE64)
51+
.put(INSTRUMENTATION_SOURCE, instrumentationSource.value())
4852
.build();
4953
}
5054

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright Splunk Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.splunk.opentelemetry.profiler;
18+
19+
import static org.junit.jupiter.api.Assertions.assertEquals;
20+
21+
import java.util.Locale;
22+
import org.junit.jupiter.params.ParameterizedTest;
23+
import org.junit.jupiter.params.provider.EnumSource;
24+
25+
class InstrumentationSourceTest {
26+
@ParameterizedTest
27+
@EnumSource(InstrumentationSource.class)
28+
void valueIsLowerCaseOfEnumName(InstrumentationSource source) {
29+
var value = source.value();
30+
assertEquals(source.name().toLowerCase(Locale.ROOT), value);
31+
}
32+
}
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
/*
2+
* Copyright Splunk Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.splunk.opentelemetry.profiler.exporter;
18+
19+
import io.opentelemetry.api.common.AttributeKey;
20+
import io.opentelemetry.api.common.Attributes;
21+
import io.opentelemetry.api.common.AttributesBuilder;
22+
import io.opentelemetry.api.logs.LogRecordBuilder;
23+
import io.opentelemetry.api.logs.Logger;
24+
import io.opentelemetry.api.logs.Severity;
25+
import io.opentelemetry.api.trace.Span;
26+
import io.opentelemetry.api.trace.SpanContext;
27+
import io.opentelemetry.context.Context;
28+
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
29+
import io.opentelemetry.sdk.logs.data.Body;
30+
import io.opentelemetry.sdk.logs.data.LogRecordData;
31+
import io.opentelemetry.sdk.resources.Resource;
32+
import java.time.Instant;
33+
import java.util.ArrayList;
34+
import java.util.Collections;
35+
import java.util.List;
36+
import java.util.Objects;
37+
import java.util.concurrent.TimeUnit;
38+
import org.junit.jupiter.api.extension.AfterEachCallback;
39+
import org.junit.jupiter.api.extension.ExtensionContext;
40+
41+
/**
42+
* In memory implementation of the OpenTelemetry {@link Logger} interface that allows for direct
43+
* access to the collected logs. Intended for testing use only.
44+
*/
45+
class InMemoryOtelLogger implements Logger, AfterEachCallback {
46+
private final List<LogRecordData> records = new ArrayList<>();
47+
48+
@Override
49+
public LogRecordBuilder logRecordBuilder() {
50+
return new Builder(this);
51+
}
52+
53+
@Override
54+
public void afterEach(ExtensionContext extensionContext) {
55+
records.clear();
56+
}
57+
58+
List<LogRecordData> records() {
59+
return Collections.unmodifiableList(records);
60+
}
61+
62+
static class Builder implements LogRecordBuilder {
63+
private final InMemoryOtelLogger logger;
64+
private long timestampEpochNanos;
65+
private long observedTimestampEpochNanos;
66+
private Context context = Context.current();
67+
private Severity severity = Severity.UNDEFINED_SEVERITY_NUMBER;
68+
private String severityText = "";
69+
private String body = "";
70+
private final AttributesBuilder attributes = Attributes.builder();
71+
72+
private Builder(InMemoryOtelLogger logger) {
73+
this.logger = logger;
74+
}
75+
76+
@Override
77+
public LogRecordBuilder setTimestamp(Instant instant) {
78+
return setTimestamp(instant.toEpochMilli(), TimeUnit.MILLISECONDS);
79+
}
80+
81+
@Override
82+
public LogRecordBuilder setTimestamp(long timestamp, TimeUnit unit) {
83+
this.timestampEpochNanos = unit.toNanos(timestamp);
84+
return this;
85+
}
86+
87+
@Override
88+
public LogRecordBuilder setObservedTimestamp(Instant instant) {
89+
return setObservedTimestamp(instant.toEpochMilli(), TimeUnit.MILLISECONDS);
90+
}
91+
92+
@Override
93+
public LogRecordBuilder setObservedTimestamp(long timestamp, TimeUnit unit) {
94+
this.observedTimestampEpochNanos = unit.toNanos(timestamp);
95+
return this;
96+
}
97+
98+
@Override
99+
public LogRecordBuilder setContext(Context context) {
100+
this.context = Objects.requireNonNull(context);
101+
return this;
102+
}
103+
104+
@Override
105+
public LogRecordBuilder setSeverity(Severity severity) {
106+
this.severity = Objects.requireNonNull(severity);
107+
return this;
108+
}
109+
110+
@Override
111+
public LogRecordBuilder setSeverityText(String severityText) {
112+
this.severityText = Objects.requireNonNull(severityText);
113+
return this;
114+
}
115+
116+
@Override
117+
public LogRecordBuilder setBody(String body) {
118+
this.body = Objects.requireNonNull(body);
119+
return this;
120+
}
121+
122+
@Override
123+
public <T> LogRecordBuilder setAttribute(AttributeKey<T> key, T value) {
124+
attributes.put(Objects.requireNonNull(key), Objects.requireNonNull(value));
125+
return this;
126+
}
127+
128+
@Override
129+
public void emit() {
130+
logger.records.add(
131+
new LogRecord(
132+
null,
133+
null,
134+
timestampEpochNanos,
135+
observedTimestampEpochNanos,
136+
Span.fromContext(context).getSpanContext(),
137+
severity,
138+
severityText,
139+
Body.string(body),
140+
attributes.build()));
141+
}
142+
}
143+
144+
static class LogRecord implements LogRecordData {
145+
private final Resource resource;
146+
private final InstrumentationScopeInfo instrumentationScopeInfo;
147+
private final long timestampEpochNanos;
148+
private final long observedTimestampEpochNanos;
149+
private final SpanContext spanContext;
150+
private final Severity severity;
151+
private final String severityText;
152+
private final Body body;
153+
private final Attributes attributes;
154+
155+
LogRecord(
156+
Resource resource,
157+
InstrumentationScopeInfo instrumentationScopeInfo,
158+
long timestampEpochNanos,
159+
long observedTimestampEpochNanos,
160+
SpanContext spanContext,
161+
Severity severity,
162+
String severityText,
163+
Body body,
164+
Attributes attributes) {
165+
this.resource = resource;
166+
this.instrumentationScopeInfo = instrumentationScopeInfo;
167+
this.timestampEpochNanos = timestampEpochNanos;
168+
this.observedTimestampEpochNanos = observedTimestampEpochNanos;
169+
this.spanContext = spanContext;
170+
this.severity = severity;
171+
this.severityText = severityText;
172+
this.body = body;
173+
this.attributes = attributes;
174+
}
175+
176+
@Override
177+
public Resource getResource() {
178+
return resource;
179+
}
180+
181+
@Override
182+
public InstrumentationScopeInfo getInstrumentationScopeInfo() {
183+
return instrumentationScopeInfo;
184+
}
185+
186+
@Override
187+
public long getTimestampEpochNanos() {
188+
return timestampEpochNanos;
189+
}
190+
191+
@Override
192+
public long getObservedTimestampEpochNanos() {
193+
return observedTimestampEpochNanos;
194+
}
195+
196+
@Override
197+
public SpanContext getSpanContext() {
198+
return spanContext;
199+
}
200+
201+
@Override
202+
public Severity getSeverity() {
203+
return severity;
204+
}
205+
206+
@Override
207+
public String getSeverityText() {
208+
return severityText;
209+
}
210+
211+
@Override
212+
public Body getBody() {
213+
return body;
214+
}
215+
216+
@Override
217+
public Attributes getAttributes() {
218+
return attributes;
219+
}
220+
221+
@Override
222+
public int getTotalAttributeCount() {
223+
return attributes.size();
224+
}
225+
}
226+
}

0 commit comments

Comments
 (0)