Skip to content

Commit 3c59e03

Browse files
authored
Add GitCommitSha to the client stats (#10833)
Add GitCommitSha to the client stats changes fix merge issues Co-authored-by: andrea.marziali <andrea.marziali@datadoghq.com>
1 parent 9a2b34d commit 3c59e03

File tree

2 files changed

+149
-5
lines changed

2 files changed

+149
-5
lines changed

dd-trace-core/src/main/java/datadog/trace/common/metrics/SerializingMetricWriter.java

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
package datadog.trace.common.metrics;
22

3+
import static datadog.trace.bootstrap.instrumentation.api.UTF8BytesString.EMPTY;
34
import static java.nio.charset.StandardCharsets.ISO_8859_1;
45

56
import datadog.communication.serialization.GrowableBuffer;
67
import datadog.communication.serialization.WritableFormatter;
78
import datadog.communication.serialization.msgpack.MsgPackWriter;
89
import datadog.trace.api.ProcessTags;
910
import datadog.trace.api.WellKnownTags;
11+
import datadog.trace.api.cache.DDCache;
12+
import datadog.trace.api.cache.DDCaches;
13+
import datadog.trace.api.git.GitInfo;
14+
import datadog.trace.api.git.GitInfoProvider;
1015
import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString;
1116
import java.util.List;
17+
import java.util.function.Function;
1218

1319
public final class SerializingMetricWriter implements MetricWriter {
1420

@@ -39,33 +45,57 @@ public final class SerializingMetricWriter implements MetricWriter {
3945
private static final byte[] HTTP_ENDPOINT = "HTTPEndpoint".getBytes(ISO_8859_1);
4046
private static final byte[] GRPC_STATUS_CODE = "GRPCStatusCode".getBytes(ISO_8859_1);
4147
private static final byte[] SERVICE_SOURCE = "srv_src".getBytes(ISO_8859_1);
48+
private static final byte[] GIT_COMMIT_SHA = "GitCommitSha".getBytes(ISO_8859_1);
4249

4350
// Constant declared here for compile-time folding
4451
public static final int TRISTATE_TRUE = TriState.TRUE.serialValue;
4552
public static final int TRISTATE_FALSE = TriState.FALSE.serialValue;
4653

54+
private static final Function<GitInfo, UTF8BytesString> SHA_COMMIT_GETTER =
55+
gitInfo ->
56+
gitInfo.getCommit() != null && gitInfo.getCommit().getSha() != null
57+
? UTF8BytesString.create(gitInfo.getCommit().getSha())
58+
: EMPTY;
59+
4760
private final WellKnownTags wellKnownTags;
4861
private final WritableFormatter writer;
4962
private final Sink sink;
5063
private final GrowableBuffer buffer;
64+
private final DDCache<GitInfo, UTF8BytesString> gitInfoCache =
65+
DDCaches.newFixedSizeWeakKeyCache(4);
5166
private long sequence = 0;
67+
private final GitInfoProvider gitInfoProvider;
5268

5369
public SerializingMetricWriter(WellKnownTags wellKnownTags, Sink sink) {
5470
this(wellKnownTags, sink, 512 * 1024);
5571
}
5672

5773
public SerializingMetricWriter(WellKnownTags wellKnownTags, Sink sink, int initialCapacity) {
74+
this(wellKnownTags, sink, initialCapacity, GitInfoProvider.INSTANCE);
75+
}
76+
77+
public SerializingMetricWriter(
78+
WellKnownTags wellKnownTags,
79+
Sink sink,
80+
int initialCapacity,
81+
final GitInfoProvider gitInfoProvider) {
5882
this.wellKnownTags = wellKnownTags;
5983
this.buffer = new GrowableBuffer(initialCapacity);
6084
this.writer = new MsgPackWriter(buffer);
6185
this.sink = sink;
86+
this.gitInfoProvider = new GitInfoProvider();
6287
}
6388

6489
@Override
6590
public void startBucket(int metricCount, long start, long duration) {
6691
final UTF8BytesString processTags = ProcessTags.getTagsForSerialization();
6792
final boolean writeProcessTags = processTags != null;
68-
writer.startMap(7 + (writeProcessTags ? 1 : 0));
93+
// the gitinfo can be rebuild in time and initialized after this class is created. So that a
94+
// cacheable is needed
95+
final UTF8BytesString gitSha =
96+
gitInfoCache.computeIfAbsent(gitInfoProvider.getGitInfo(), SHA_COMMIT_GETTER);
97+
final boolean writeGitCommitSha = gitSha != EMPTY;
98+
writer.startMap(7 + (writeProcessTags ? 1 : 0) + (writeGitCommitSha ? 1 : 0));
6999

70100
writer.writeUTF8(RUNTIME_ID);
71101
writer.writeUTF8(wellKnownTags.getRuntimeId());
@@ -90,6 +120,11 @@ public void startBucket(int metricCount, long start, long duration) {
90120
writer.writeUTF8(processTags);
91121
}
92122

123+
if (writeGitCommitSha) {
124+
writer.writeUTF8(GIT_COMMIT_SHA);
125+
writer.writeUTF8(gitSha);
126+
}
127+
93128
writer.writeUTF8(STATS);
94129

95130
writer.startArray(1);

dd-trace-core/src/test/groovy/datadog/trace/common/metrics/SerializingMetricWriterTest.groovy

Lines changed: 113 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import datadog.trace.api.Config
1010
import datadog.trace.api.Pair
1111
import datadog.trace.api.ProcessTags
1212
import datadog.trace.api.WellKnownTags
13+
import datadog.trace.api.git.CommitInfo
14+
import datadog.trace.api.git.GitInfo
15+
import datadog.trace.api.git.GitInfoProvider
1316
import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString
1417
import datadog.trace.test.util.DDSpecification
1518
import java.nio.ByteBuffer
@@ -25,8 +28,8 @@ class SerializingMetricWriterTest extends DDSpecification {
2528

2629
def "should produce correct message #iterationIndex with process tags enabled #withProcessTags" () {
2730
setup:
28-
if (withProcessTags) {
29-
injectSysConfig(EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED, "true")
31+
if (!withProcessTags) {
32+
injectSysConfig(EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED, "false")
3033
}
3134
ProcessTags.reset()
3235
long startTime = MILLISECONDS.toNanos(System.currentTimeMillis())
@@ -45,6 +48,9 @@ class SerializingMetricWriterTest extends DDSpecification {
4548
then:
4649
sink.validatedInput()
4750

51+
cleanup:
52+
removeSysConfig(EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED)
53+
ProcessTags.reset()
4854

4955
where:
5056
content << [
@@ -136,8 +142,105 @@ class SerializingMetricWriterTest extends DDSpecification {
136142
withProcessTags << [true, false]
137143
}
138144

145+
def "ServiceSource optional in the payload"() {
146+
setup:
147+
long startTime = MILLISECONDS.toNanos(System.currentTimeMillis())
148+
long duration = SECONDS.toNanos(10)
149+
WellKnownTags wellKnownTags = new WellKnownTags("runtimeid", "hostname", "env", "service", "version", "language")
150+
151+
// Create keys with different combinations of HTTP fields
152+
def keyWithNoSource = new MetricKey("resource", "service", "operation", null, "type", 200, false, false, "server", [], "GET", "/api/users", null)
153+
def keyWithSource = new MetricKey("resource", "service", "operation", "source", "type", 200, false, false, "server", [], "POST", null, null)
154+
155+
def content = [
156+
Pair.of(keyWithNoSource, new AggregateMetric().recordDurations(1, new AtomicLongArray(1L))),
157+
Pair.of(keyWithSource, new AggregateMetric().recordDurations(1, new AtomicLongArray(1L))),
158+
]
159+
160+
ValidatingSink sink = new ValidatingSink(wellKnownTags, startTime, duration, content)
161+
SerializingMetricWriter writer = new SerializingMetricWriter(wellKnownTags, sink, 128)
162+
163+
when:
164+
writer.startBucket(content.size(), startTime, duration)
165+
for (Pair<MetricKey, AggregateMetric> pair : content) {
166+
writer.add(pair.getLeft(), pair.getRight())
167+
}
168+
writer.finishBucket()
169+
170+
then:
171+
sink.validatedInput()
172+
}
173+
174+
def "HTTPMethod and HTTPEndpoint fields are optional in payload"() {
175+
setup:
176+
long startTime = MILLISECONDS.toNanos(System.currentTimeMillis())
177+
long duration = SECONDS.toNanos(10)
178+
WellKnownTags wellKnownTags = new WellKnownTags("runtimeid", "hostname", "env", "service", "version", "language")
179+
180+
// Create keys with different combinations of HTTP fields
181+
def keyWithBoth = new MetricKey("resource", "service", "operation", null, "type", 200, false, false, "server", [], "GET", "/api/users", null)
182+
def keyWithMethodOnly = new MetricKey("resource", "service", "operation", null, "type", 200, false, false, "server", [], "POST", null,null)
183+
def keyWithEndpointOnly = new MetricKey("resource", "service", "operation", null, "type", 200, false, false, "server", [], null, "/api/orders",null)
184+
def keyWithNeither = new MetricKey("resource", "service", "operation", null, "type", 200, false, false, "client", [], null, null, null)
185+
186+
def content = [
187+
Pair.of(keyWithBoth, new AggregateMetric().recordDurations(1, new AtomicLongArray(1L))),
188+
Pair.of(keyWithMethodOnly, new AggregateMetric().recordDurations(1, new AtomicLongArray(1L))),
189+
Pair.of(keyWithEndpointOnly, new AggregateMetric().recordDurations(1, new AtomicLongArray(1L))),
190+
Pair.of(keyWithNeither, new AggregateMetric().recordDurations(1, new AtomicLongArray(1L)))
191+
]
192+
193+
ValidatingSink sink = new ValidatingSink(wellKnownTags, startTime, duration, content)
194+
SerializingMetricWriter writer = new SerializingMetricWriter(wellKnownTags, sink, 128)
195+
196+
when:
197+
writer.startBucket(content.size(), startTime, duration)
198+
for (Pair<MetricKey, AggregateMetric> pair : content) {
199+
writer.add(pair.getLeft(), pair.getRight())
200+
}
201+
writer.finishBucket()
202+
203+
then:
204+
sink.validatedInput()
205+
// Test passes if validation in ValidatingSink succeeds
206+
// ValidatingSink verifies that map size matches actual number of fields
207+
// and that HTTPMethod/HTTPEndpoint are only present when non-empty
208+
}
209+
210+
def "add git sha commit info when sha commit is #shaCommit"() {
211+
setup:
212+
GitInfoProvider gitInfoProvider = Mock(GitInfoProvider)
213+
gitInfoProvider.getGitInfo() >> new GitInfo(null, null, null, new CommitInfo(shaCommit))
139214

140-
class ValidatingSink implements Sink {
215+
long startTime = MILLISECONDS.toNanos(System.currentTimeMillis())
216+
long duration = SECONDS.toNanos(10)
217+
WellKnownTags wellKnownTags = new WellKnownTags("runtimeid", "hostname", "env", "service", "version", "language")
218+
219+
// Create keys with different combinations of HTTP fields
220+
def key = new MetricKey("resource", "service", "operation", null, "type", 200, false, false, "server", [], "GET", "/api/users", null)
221+
222+
def content = [Pair.of(key, new AggregateMetric().recordDurations(1, new AtomicLongArray(1L))),]
223+
224+
ValidatingSink sink = new ValidatingSink(wellKnownTags, startTime, duration, content)
225+
SerializingMetricWriter writer = new SerializingMetricWriter(wellKnownTags, sink, 128, gitInfoProvider)
226+
227+
when:
228+
229+
writer.startBucket(content.size(), startTime, duration)
230+
for (Pair<MetricKey, AggregateMetric> pair : content) {
231+
writer.add(pair.getLeft(), pair.getRight())
232+
}
233+
writer.finishBucket()
234+
235+
then:
236+
237+
sink.validatedInput()
238+
239+
where:
240+
shaCommit << [null, "123456"]
241+
}
242+
243+
static class ValidatingSink implements Sink {
141244

142245
private final WellKnownTags wellKnownTags
143246
private final long startTimeNanos
@@ -161,7 +264,9 @@ class SerializingMetricWriterTest extends DDSpecification {
161264
void accept(int messageCount, ByteBuffer buffer) {
162265
MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(buffer)
163266
int mapSize = unpacker.unpackMapHeader()
164-
assert mapSize == (7 + (Config.get().isExperimentalPropagateProcessTagsEnabled() ? 1 : 0))
267+
String gitCommitSha = GitInfoProvider.INSTANCE.getGitInfo()?.getCommit()?.getSha()
268+
assert mapSize == (7 + (Config.get().isExperimentalPropagateProcessTagsEnabled() ? 1 : 0)
269+
+ (gitCommitSha != null ? 1 : 0))
165270
assert unpacker.unpackString() == "RuntimeID"
166271
assert unpacker.unpackString() == wellKnownTags.getRuntimeId() as String
167272
assert unpacker.unpackString() == "Sequence"
@@ -178,6 +283,10 @@ class SerializingMetricWriterTest extends DDSpecification {
178283
assert unpacker.unpackString() == "ProcessTags"
179284
assert unpacker.unpackString() == ProcessTags.tagsForSerialization as String
180285
}
286+
if (gitCommitSha != null) {
287+
assert unpacker.unpackString() == "GitCommitSha"
288+
assert unpacker.unpackString() == gitCommitSha
289+
}
181290
assert unpacker.unpackString() == "Stats"
182291
int outerLength = unpacker.unpackArrayHeader()
183292
assert outerLength == 1

0 commit comments

Comments
 (0)