diff --git a/communication/src/main/java/datadog/communication/ddagent/AgentVersion.java b/communication/src/main/java/datadog/communication/ddagent/AgentVersion.java new file mode 100644 index 00000000000..f3b30cb7baf --- /dev/null +++ b/communication/src/main/java/datadog/communication/ddagent/AgentVersion.java @@ -0,0 +1,72 @@ +package datadog.communication.ddagent; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class AgentVersion { + + private static final Logger log = LoggerFactory.getLogger(AgentVersion.class); + + /** + * Checks if the given version string represents a version that is below the specified major, + * minor, and patch version. + * + * @param version the version string to check (e.g., "7.64.0") + * @param maxMajor maximum major version (exclusive) + * @param maxMinor maximum minor version (exclusive) + * @param maxPatch maximum patch version (exclusive) + * @return true if version is below the specified maximum (or if not parseable), false otherwise + */ + public static boolean isVersionBelow(String version, int maxMajor, int maxMinor, int maxPatch) { + if (version == null || version.isEmpty()) { + return true; + } + + try { + // Parse version string in format "major.minor.patch" (e.g., "7.65.0") + // Assumes the 'version' is below if it can't be parsed. + int majorDot = version.indexOf('.'); + if (majorDot == -1) { + return true; + } + + int major = Integer.parseInt(version.substring(0, majorDot)); + + if (major < maxMajor) { + return true; + } else if (major > maxMajor) { + return false; + } + + // major == maxMajor + int minorDot = version.indexOf('.', majorDot + 1); + if (minorDot == -1) { + return true; + } + + int minor = Integer.parseInt(version.substring(majorDot + 1, minorDot)); + if (minor < maxMinor) { + return true; + } else if (minor > maxMinor) { + return false; + } + + // major == maxMajor && minor == maxMinor + // Find end of patch version (may have suffix like "-rc.1") + int patchEnd = minorDot + 1; + while (patchEnd < version.length() && Character.isDigit(version.charAt(patchEnd))) { + patchEnd++; + } + + int patch = Integer.parseInt(version.substring(minorDot + 1, patchEnd)); + if (patch != maxPatch) { + return patch < maxPatch; + } else { + // If there's a suffix (like "-rc.1"), consider it below the non-suffixed version + return patchEnd < version.length(); + } + } catch (NumberFormatException | IndexOutOfBoundsException e) { + return true; + } + } +} diff --git a/communication/src/main/java/datadog/communication/ddagent/DDAgentFeaturesDiscovery.java b/communication/src/main/java/datadog/communication/ddagent/DDAgentFeaturesDiscovery.java index 840a11c0b4c..1da613eada0 100644 --- a/communication/src/main/java/datadog/communication/ddagent/DDAgentFeaturesDiscovery.java +++ b/communication/src/main/java/datadog/communication/ddagent/DDAgentFeaturesDiscovery.java @@ -87,6 +87,7 @@ private static class State { String dataStreamsEndpoint; boolean supportsLongRunning; boolean supportsDropping; + boolean supportsClientSideStats; String state; String configEndpoint; String debuggerLogEndpoint; @@ -306,6 +307,7 @@ private boolean processInfoResponse(State newState, String response) { Boolean.TRUE.equals(map.getOrDefault("long_running_spans", false)); if (metricsEnabled) { + newState.supportsClientSideStats = !AgentVersion.isVersionBelow(newState.version, 7, 65, 0); Object canDrop = map.get("client_drop_p0s"); newState.supportsDropping = null != canDrop @@ -358,7 +360,8 @@ private static void discoverStatsDPort(final Map info) { public boolean supportsMetrics() { return metricsEnabled && null != discoveryState.metricsEndpoint - && discoveryState.supportsDropping; + && discoveryState.supportsDropping + && discoveryState.supportsClientSideStats; } public boolean supportsDebugger() { diff --git a/communication/src/test/groovy/datadog/communication/ddagent/DDAgentFeaturesDiscoveryTest.groovy b/communication/src/test/groovy/datadog/communication/ddagent/DDAgentFeaturesDiscoveryTest.groovy index 5724633e524..ca662d6fd52 100644 --- a/communication/src/test/groovy/datadog/communication/ddagent/DDAgentFeaturesDiscoveryTest.groovy +++ b/communication/src/test/groovy/datadog/communication/ddagent/DDAgentFeaturesDiscoveryTest.groovy @@ -498,6 +498,67 @@ class DDAgentFeaturesDiscoveryTest extends DDSpecification { ) } + def "test metrics disabled for agent version below 7.65"() { + setup: + OkHttpClient client = Mock(OkHttpClient) + DDAgentFeaturesDiscovery features = new DDAgentFeaturesDiscovery(client, monitoring, agentUrl, true, true) + + when: "agent version is below 7.65" + features.discover() + + then: + 1 * client.newCall(_) >> { Request request -> + def response = """ + { + "version": "${version}", + "endpoints": ["/v0.5/traces", "/v0.6/stats"], + "client_drop_p0s": true, + "config": {} + } + """ + infoResponse(request, response) + } + features.getMetricsEndpoint() == V6_METRICS_ENDPOINT + features.supportsDropping() == true + features.supportsMetrics() == expected + + where: + version | expected + "7.64.0" | false + "7.64.9" | false + "7.64.9-rc.1" | false + "7.65.0" | true + "7.65.1" | true + "7.70.0" | true + "8.0.0" | true + } + + def "test metrics disabled for agent with unparseable version"() { + setup: + OkHttpClient client = Mock(OkHttpClient) + DDAgentFeaturesDiscovery features = new DDAgentFeaturesDiscovery(client, monitoring, agentUrl, true, true) + + when: "agent version is unparseable" + features.discover() + + then: + 1 * client.newCall(_) >> { Request request -> + def response = """ + { + "version": "${version}", + "endpoints": ["/v0.5/traces", "/v0.6/stats"], + "client_drop_p0s": true, + "config": {} + } + """ + infoResponse(request, response) + } + !features.supportsMetrics() + + where: + version << ["invalid", "7", "7.65", "", null] + } + def "should send container id as header on the info request and parse the hash in the response"() { setup: OkHttpClient client = Mock(OkHttpClient) diff --git a/communication/src/test/java/datadog/communication/ddagent/AgentVersionTest.java b/communication/src/test/java/datadog/communication/ddagent/AgentVersionTest.java new file mode 100644 index 00000000000..d9a1cd0d966 --- /dev/null +++ b/communication/src/test/java/datadog/communication/ddagent/AgentVersionTest.java @@ -0,0 +1,64 @@ +package datadog.communication.ddagent; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class AgentVersionTest { + + @Test + void testIsVersionBelow_VersionBelowThreshold() { + assertTrue(AgentVersion.isVersionBelow("7.64.0", 7, 65, 0)); + assertTrue(AgentVersion.isVersionBelow("7.64.9", 7, 65, 0)); + assertTrue(AgentVersion.isVersionBelow("6.99.99", 7, 65, 0)); + assertTrue(AgentVersion.isVersionBelow("7.0.0", 7, 65, 0)); + } + + @Test + void testIsVersionBelow_VersionEqualToThreshold() { + assertFalse(AgentVersion.isVersionBelow("7.65.0", 7, 65, 0)); + } + + @Test + void testIsVersionBelow_VersionAboveThreshold() { + assertFalse(AgentVersion.isVersionBelow("7.65.1", 7, 65, 0)); + assertFalse(AgentVersion.isVersionBelow("7.66.0", 7, 65, 0)); + assertFalse(AgentVersion.isVersionBelow("8.0.0", 7, 65, 0)); + assertFalse(AgentVersion.isVersionBelow("7.65.10", 7, 65, 0)); + } + + @Test + void testIsVersionBelow_MajorVersionComparison() { + assertTrue(AgentVersion.isVersionBelow("6.100.100", 7, 0, 0)); + assertFalse(AgentVersion.isVersionBelow("8.0.0", 7, 100, 100)); + } + + @Test + void testIsVersionBelow_MinorVersionComparison() { + assertTrue(AgentVersion.isVersionBelow("7.64.100", 7, 65, 0)); + assertFalse(AgentVersion.isVersionBelow("7.66.0", 7, 65, 100)); + } + + @Test + void testIsVersionBelow_WithSuffix() { + assertTrue(AgentVersion.isVersionBelow("7.64.0-rc.1", 7, 65, 0)); + assertTrue(AgentVersion.isVersionBelow("7.65.0-rc.1", 7, 65, 0)); + assertFalse(AgentVersion.isVersionBelow("7.65.1-snapshot", 7, 65, 0)); + } + + @Test + void testIsVersionBelow_NullOrEmpty() { + assertTrue(AgentVersion.isVersionBelow(null, 7, 65, 0)); + assertTrue(AgentVersion.isVersionBelow("", 7, 65, 0)); + } + + @Test + void testIsVersionBelow_InvalidFormat() { + assertTrue(AgentVersion.isVersionBelow("invalid", 7, 65, 0)); + assertTrue(AgentVersion.isVersionBelow("7", 7, 65, 0)); + assertTrue(AgentVersion.isVersionBelow("7.", 7, 65, 0)); + assertTrue(AgentVersion.isVersionBelow("7.65", 7, 65, 0)); + assertTrue(AgentVersion.isVersionBelow("7.65.", 7, 65, 0)); + assertTrue(AgentVersion.isVersionBelow("a.b.c", 7, 65, 0)); + } +} diff --git a/communication/src/test/resources/agent-features/agent-info-with-client-dropping.json b/communication/src/test/resources/agent-features/agent-info-with-client-dropping.json index 29baa64c04a..18467a1170b 100644 --- a/communication/src/test/resources/agent-features/agent-info-with-client-dropping.json +++ b/communication/src/test/resources/agent-features/agent-info-with-client-dropping.json @@ -1,5 +1,5 @@ { - "version": "0.99.0", + "version": "7.65.0", "git_commit": "fab047e10", "build_date": "2020-12-04 15:57:06.74187 +0200 EET m=+0.029001792", "endpoints": [ diff --git a/dd-trace-core/src/test/groovy/datadog/trace/common/metrics/MetricsReliabilityTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/common/metrics/MetricsReliabilityTest.groovy index 17dba326ff3..777b5e1436f 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/common/metrics/MetricsReliabilityTest.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/common/metrics/MetricsReliabilityTest.groovy @@ -35,7 +35,7 @@ class MetricsReliabilityTest extends DDCoreSpecification { httpServer { handlers { get("/info") { - final def res = '{"endpoints":[' + (state.agentMetricsAvailable ? '"/v0.6/stats", ' : '') + '"/v0.4/traces"], "client_drop_p0s" : true}' + final def res = '{"version":"7.65.0","endpoints":[' + (state.agentMetricsAvailable ? '"/v0.6/stats", ' : '') + '"/v0.4/traces"], "client_drop_p0s" : true}' state.hash = Strings.sha256(res) response.send(res) state.latch.countDown()