Skip to content

Commit 111bd5a

Browse files
Merge branch 'master' into alexeyk/ci-memory-percentage
2 parents f5b98a0 + cd456d2 commit 111bd5a

File tree

40 files changed

+583
-127
lines changed

40 files changed

+583
-127
lines changed

buildSrc/src/main/kotlin/datadog/gradle/plugin/config/ConfigInversionLinter.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ private fun registerLogEnvVarUsages(target: Project, extension: SupportedTracerC
8282
val javaFiles = target.fileTree(target.projectDir) {
8383
include("**/src/main/java/**/*.java")
8484
exclude("**/build/**", "**/dd-smoke-tests/**")
85+
// Undertow uses DD_UNDERTOW_CONTINUATION as a legacy key to store an AgentScope. It is not related to an environment variable
86+
exclude("dd-java-agent/instrumentation/undertow/src/main/java/datadog/trace/instrumentation/undertow/UndertowDecorator.java")
8587
}
8688
inputs.files(javaFiles)
8789
outputs.upToDateWhen { true }
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
package datadog.gradle.plugin.config
2+
3+
import org.gradle.api.DefaultTask
4+
import org.gradle.kotlin.dsl.property
5+
import org.gradle.api.model.ObjectFactory
6+
import org.gradle.api.tasks.Input
7+
import org.gradle.api.tasks.InputFile
8+
import org.gradle.api.tasks.OutputDirectory
9+
import org.gradle.api.tasks.TaskAction
10+
import com.fasterxml.jackson.core.type.TypeReference
11+
import com.fasterxml.jackson.databind.ObjectMapper
12+
import org.gradle.api.tasks.CacheableTask
13+
import org.gradle.api.tasks.PathSensitive
14+
import org.gradle.api.tasks.PathSensitivity
15+
import java.io.File
16+
import java.io.FileInputStream
17+
import java.io.PrintWriter
18+
import javax.inject.Inject
19+
20+
@CacheableTask
21+
abstract class ParseV2SupportedConfigurationsTask @Inject constructor(
22+
private val objects: ObjectFactory
23+
) : DefaultTask() {
24+
@InputFile
25+
@PathSensitive(PathSensitivity.NONE)
26+
val jsonFile = objects.fileProperty()
27+
28+
@get:OutputDirectory
29+
val destinationDirectory = objects.directoryProperty()
30+
31+
@Input
32+
val className = objects.property<String>()
33+
34+
@TaskAction
35+
fun generate() {
36+
val input = jsonFile.get().asFile
37+
val outputDir = destinationDirectory.get().asFile
38+
val finalClassName = className.get()
39+
outputDir.mkdirs()
40+
41+
// Read JSON (directly from the file, not classpath)
42+
val mapper = ObjectMapper()
43+
val fileData: Map<String, Any?> = FileInputStream(input).use { inStream ->
44+
mapper.readValue(inStream, object : TypeReference<Map<String, Any?>>() {})
45+
}
46+
47+
// Fetch top-level keys of JSON file
48+
@Suppress("UNCHECKED_CAST")
49+
val supportedRaw = fileData["supportedConfigurations"] as Map<String, List<Map<String, Any?>>>
50+
@Suppress("UNCHECKED_CAST")
51+
val deprecated = (fileData["deprecations"] as? Map<String, String>) ?: emptyMap()
52+
53+
// Parse supportedConfigurations key to into a V2 format
54+
val supported: Map<String, List<SupportedConfigurationItem>> = supportedRaw.mapValues { (_, configList) ->
55+
configList.map { configMap ->
56+
SupportedConfigurationItem(
57+
configMap["version"] as? String,
58+
configMap["type"] as? String,
59+
configMap["default"] as? String,
60+
(configMap["aliases"] as? List<String>) ?: emptyList(),
61+
(configMap["propertyKeys"] as? List<String>) ?: emptyList()
62+
)
63+
}
64+
}
65+
66+
// Generate top-level mapping from config -> list of aliases and reverse alias mapping from alias -> top-level config
67+
// Note: This top-level alias mapping will be deprecated once Config Registry is mature enough to understand which version of a config a customer is using
68+
val aliases: Map<String, List<String>> = supported.mapValues { (_, configList) ->
69+
configList.flatMap { it.aliases }.distinct()
70+
}
71+
72+
val aliasMapping = mutableMapOf<String, String>()
73+
for ((canonical, alist) in aliases) {
74+
for (alias in alist) aliasMapping[alias] = canonical
75+
}
76+
77+
val reversePropertyKeysMap: Map<String, String> = supported.flatMap { (canonical, configList) ->
78+
configList.flatMap { config ->
79+
config.propertyKeys.map { propertyKey -> propertyKey to canonical }
80+
}
81+
}.toMap()
82+
83+
// Build the output .java path from the fully-qualified class name
84+
val pkgName = finalClassName.substringBeforeLast('.', "")
85+
val pkgPath = pkgName.replace('.', File.separatorChar)
86+
val simpleName = finalClassName.substringAfterLast('.')
87+
val pkgDir = if (pkgPath.isEmpty()) outputDir else File(outputDir, pkgPath).also { it.mkdirs() }
88+
val generatedFile = File(pkgDir, "$simpleName.java").absolutePath
89+
90+
// Call your existing generator (same signature as in your Java code)
91+
generateJavaFile(
92+
generatedFile,
93+
simpleName,
94+
pkgName,
95+
supported,
96+
aliases,
97+
aliasMapping,
98+
deprecated,
99+
reversePropertyKeysMap
100+
)
101+
}
102+
103+
private fun generateJavaFile(
104+
outputPath: String,
105+
className: String,
106+
packageName: String,
107+
supported: Map<String, List<SupportedConfigurationItem>>,
108+
aliases: Map<String, List<String>>,
109+
aliasMapping: Map<String, String>,
110+
deprecated: Map<String, String>,
111+
reversePropertyKeysMap: Map<String, String>
112+
) {
113+
val outFile = File(outputPath)
114+
outFile.parentFile?.mkdirs()
115+
116+
PrintWriter(outFile).use { out ->
117+
out.println("package $packageName;")
118+
out.println()
119+
out.println("import java.util.*;")
120+
out.println()
121+
out.println("public final class $className {")
122+
out.println()
123+
out.println(" public static final Map<String, List<SupportedConfiguration>> SUPPORTED;")
124+
out.println()
125+
out.println(" public static final Map<String, List<String>> ALIASES;")
126+
out.println()
127+
out.println(" public static final Map<String, String> ALIAS_MAPPING;")
128+
out.println()
129+
out.println(" public static final Map<String, String> DEPRECATED;")
130+
out.println()
131+
out.println(" public static final Map<String, String> REVERSE_PROPERTY_KEYS_MAP;")
132+
out.println()
133+
out.println(" static {")
134+
out.println()
135+
136+
// SUPPORTED
137+
out.println(" Map<String, List<SupportedConfiguration>> supportedMap = new HashMap<>();")
138+
for ((key, configList) in supported.toSortedMap()) {
139+
out.print(" supportedMap.put(\"${esc(key)}\", Collections.unmodifiableList(Arrays.asList(")
140+
val configIter = configList.iterator()
141+
while (configIter.hasNext()) {
142+
val config = configIter.next()
143+
out.print("new SupportedConfiguration(")
144+
out.print("${escNullableString(config.version)}, ")
145+
out.print("${escNullableString(config.type)}, ")
146+
out.print("${escNullableString(config.default)}, ")
147+
out.print("Arrays.asList(${quoteList(config.aliases)}), ")
148+
out.print("Arrays.asList(${quoteList(config.propertyKeys)})")
149+
out.print(")")
150+
if (configIter.hasNext()) out.print(", ")
151+
}
152+
out.println(")));")
153+
}
154+
out.println(" SUPPORTED = Collections.unmodifiableMap(supportedMap);")
155+
out.println()
156+
157+
// ALIASES
158+
out.println(" // Note: This top-level alias mapping will be deprecated once Config Registry is mature enough to understand which version of a config a customer is using")
159+
out.println(" Map<String, List<String>> aliasesMap = new HashMap<>();")
160+
for ((canonical, list) in aliases.toSortedMap()) {
161+
out.printf(
162+
" aliasesMap.put(\"%s\", Collections.unmodifiableList(Arrays.asList(%s)));\n",
163+
esc(canonical),
164+
quoteList(list)
165+
)
166+
}
167+
out.println(" ALIASES = Collections.unmodifiableMap(aliasesMap);")
168+
out.println()
169+
170+
// ALIAS_MAPPING
171+
out.println(" Map<String, String> aliasMappingMap = new HashMap<>();")
172+
for ((alias, target) in aliasMapping.toSortedMap()) {
173+
out.printf(" aliasMappingMap.put(\"%s\", \"%s\");\n", esc(alias), esc(target))
174+
}
175+
out.println(" ALIAS_MAPPING = Collections.unmodifiableMap(aliasMappingMap);")
176+
out.println()
177+
178+
// DEPRECATED
179+
out.println(" Map<String, String> deprecatedMap = new HashMap<>();")
180+
for ((oldKey, note) in deprecated.toSortedMap()) {
181+
out.printf(" deprecatedMap.put(\"%s\", \"%s\");\n", esc(oldKey), esc(note))
182+
}
183+
out.println(" DEPRECATED = Collections.unmodifiableMap(deprecatedMap);")
184+
out.println()
185+
186+
// REVERSE_PROPERTY_KEYS_MAP
187+
out.println(" Map<String, String> reversePropertyKeysMapping = new HashMap<>();")
188+
for ((propertyKey, config) in reversePropertyKeysMap.toSortedMap()) {
189+
out.printf(" reversePropertyKeysMapping.put(\"%s\", \"%s\");\n", esc(propertyKey), esc(config))
190+
}
191+
out.println(" REVERSE_PROPERTY_KEYS_MAP = Collections.unmodifiableMap(reversePropertyKeysMapping);")
192+
out.println()
193+
194+
out.println(" }")
195+
out.println("}")
196+
}
197+
}
198+
199+
private fun quoteList(list: List<String>): String =
200+
list.joinToString(", ") { "\"${esc(it)}\"" }
201+
202+
private fun esc(s: String): String =
203+
s.replace("\\", "\\\\").replace("\"", "\\\"")
204+
205+
private fun escNullableString(s: String?): String =
206+
if (s == null) "null" else "\"${esc(s)}\""
207+
}
208+
209+
private data class SupportedConfigurationItem(
210+
val version: String?,
211+
val type: String?,
212+
val default: String?,
213+
val aliases: List<String>,
214+
val propertyKeys: List<String>
215+
)

buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleTask.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ abstract class MuzzleTask @Inject constructor(
9898
// See https://github.com/gradle/gradle/issues/33987
9999
workerExecutor.processIsolation {
100100
forkOptions {
101+
// datadog.trace.agent.tooling.muzzle.MuzzleVersionScanPlugin needs reflective access to ClassLoader.findLoadedClass
102+
if(javaLauncher.metadata.languageVersion > JavaLanguageVersion.of(9)) {
103+
jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED")
104+
}
101105
executable(javaLauncher.executablePath)
102106
}
103107
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package datadog.communication.ddagent;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
6+
public class AgentVersion {
7+
8+
private static final Logger log = LoggerFactory.getLogger(AgentVersion.class);
9+
10+
/**
11+
* Checks if the given version string represents a version that is below the specified major,
12+
* minor, and patch version.
13+
*
14+
* @param version the version string to check (e.g., "7.64.0")
15+
* @param maxMajor maximum major version (exclusive)
16+
* @param maxMinor maximum minor version (exclusive)
17+
* @param maxPatch maximum patch version (exclusive)
18+
* @return true if version is below the specified maximum (or if not parseable), false otherwise
19+
*/
20+
public static boolean isVersionBelow(String version, int maxMajor, int maxMinor, int maxPatch) {
21+
if (version == null || version.isEmpty()) {
22+
return true;
23+
}
24+
25+
try {
26+
// Parse version string in format "major.minor.patch" (e.g., "7.65.0")
27+
// Assumes the 'version' is below if it can't be parsed.
28+
int majorDot = version.indexOf('.');
29+
if (majorDot == -1) {
30+
return true;
31+
}
32+
33+
int major = Integer.parseInt(version.substring(0, majorDot));
34+
35+
if (major < maxMajor) {
36+
return true;
37+
} else if (major > maxMajor) {
38+
return false;
39+
}
40+
41+
// major == maxMajor
42+
int minorDot = version.indexOf('.', majorDot + 1);
43+
if (minorDot == -1) {
44+
return true;
45+
}
46+
47+
int minor = Integer.parseInt(version.substring(majorDot + 1, minorDot));
48+
if (minor < maxMinor) {
49+
return true;
50+
} else if (minor > maxMinor) {
51+
return false;
52+
}
53+
54+
// major == maxMajor && minor == maxMinor
55+
// Find end of patch version (may have suffix like "-rc.1")
56+
int patchEnd = minorDot + 1;
57+
while (patchEnd < version.length() && Character.isDigit(version.charAt(patchEnd))) {
58+
patchEnd++;
59+
}
60+
61+
int patch = Integer.parseInt(version.substring(minorDot + 1, patchEnd));
62+
if (patch != maxPatch) {
63+
return patch < maxPatch;
64+
} else {
65+
// If there's a suffix (like "-rc.1"), consider it below the non-suffixed version
66+
return patchEnd < version.length();
67+
}
68+
} catch (NumberFormatException | IndexOutOfBoundsException e) {
69+
return true;
70+
}
71+
}
72+
}

communication/src/main/java/datadog/communication/ddagent/DDAgentFeaturesDiscovery.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ private static class State {
8787
String dataStreamsEndpoint;
8888
boolean supportsLongRunning;
8989
boolean supportsDropping;
90+
boolean supportsClientSideStats;
9091
String state;
9192
String configEndpoint;
9293
String debuggerLogEndpoint;
@@ -306,6 +307,7 @@ private boolean processInfoResponse(State newState, String response) {
306307
Boolean.TRUE.equals(map.getOrDefault("long_running_spans", false));
307308

308309
if (metricsEnabled) {
310+
newState.supportsClientSideStats = !AgentVersion.isVersionBelow(newState.version, 7, 65, 0);
309311
Object canDrop = map.get("client_drop_p0s");
310312
newState.supportsDropping =
311313
null != canDrop
@@ -358,7 +360,8 @@ private static void discoverStatsDPort(final Map<String, Object> info) {
358360
public boolean supportsMetrics() {
359361
return metricsEnabled
360362
&& null != discoveryState.metricsEndpoint
361-
&& discoveryState.supportsDropping;
363+
&& discoveryState.supportsDropping
364+
&& discoveryState.supportsClientSideStats;
362365
}
363366

364367
public boolean supportsDebugger() {

0 commit comments

Comments
 (0)