aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-scanner-engine
diff options
context:
space:
mode:
authorJavier García Orduña <javier.garcia@sonarsource.com>2025-02-25 13:39:24 +0100
committerLukasz Jarocki <lukasz.jarocki@sonarsource.com>2025-02-28 09:57:48 +0100
commit86670bc803d6bedaa7703da7f7e0aa072cf6977d (patch)
tree1fa5aea4fdb47438a973eee73067756d9c2374f9 /sonar-scanner-engine
parent20d7a52648033668ee648b3e7f875e3ea831bde1 (diff)
downloadsonarqube-86670bc803d6bedaa7703da7f7e0aa072cf6977d.tar.gz
sonarqube-86670bc803d6bedaa7703da7f7e0aa072cf6977d.zip
SQRP-272 Add scanner-engine telemetry for SCA services
Diffstat (limited to 'sonar-scanner-engine')
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/CliCacheService.java64
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/CliService.java56
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scanner/sca/CliCacheServiceTest.java46
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scanner/sca/CliServiceTest.java20
4 files changed, 131 insertions, 55 deletions
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/CliCacheService.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/CliCacheService.java
index b0cbcf7f721..a8d36093c67 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/CliCacheService.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/CliCacheService.java
@@ -39,6 +39,7 @@ import org.sonar.api.internal.apachecommons.lang3.SystemUtils;
import org.sonar.api.utils.System2;
import org.sonar.scanner.bootstrap.SonarUserHome;
import org.sonar.scanner.http.ScannerWsClient;
+import org.sonar.scanner.repository.TelemetryCache;
import org.sonarqube.ws.client.GetRequest;
import org.sonarqube.ws.client.WsResponse;
@@ -55,11 +56,13 @@ public class CliCacheService {
private static final Logger LOG = LoggerFactory.getLogger(CliCacheService.class);
private final SonarUserHome sonarUserHome;
private final ScannerWsClient wsClient;
+ private final TelemetryCache telemetryCache;
private final System2 system2;
- public CliCacheService(SonarUserHome sonarUserHome, ScannerWsClient wsClient, System2 system2) {
+ public CliCacheService(SonarUserHome sonarUserHome, ScannerWsClient wsClient, TelemetryCache telemetryCache, System2 system2) {
this.sonarUserHome = sonarUserHome;
this.wsClient = wsClient;
+ this.telemetryCache = telemetryCache;
this.system2 = system2;
}
@@ -105,27 +108,36 @@ public class CliCacheService {
}
public File cacheCli() {
- List<CliMetadataResponse> metadataResponses = getLatestMetadata(apiOsName(), apiArch());
+ boolean success = false;
+ try {
+ List<CliMetadataResponse> metadataResponses = getLatestMetadata(apiOsName(), apiArch());
- if (metadataResponses.isEmpty()) {
- throw new IllegalStateException(format("Could not find CLI for %s %s", apiOsName(), apiArch()));
- }
+ if (metadataResponses.isEmpty()) {
+ throw new IllegalStateException(format("Could not find CLI for %s %s", apiOsName(), apiArch()));
+ }
- // We should only be getting one matching CLI for the OS + Arch combination.
- // If we have more than one CLI to choose from then I'm not sure which one to choose.
- if (metadataResponses.size() > 1) {
- throw new IllegalStateException("Multiple CLI matches found. Unable to correctly cache CLI.");
- }
+ // We should only be getting one matching CLI for the OS + Arch combination.
+ // If we have more than one CLI to choose from then I'm not sure which one to choose.
+ if (metadataResponses.size() > 1) {
+ throw new IllegalStateException("Multiple CLI matches found. Unable to correctly cache CLI.");
+ }
- CliMetadataResponse metadataResponse = metadataResponses.get(0);
- String checksum = metadataResponse.sha256();
- // If we have a matching checksum dir with the existing CLI file, then we are up to date.
- if (!cachedCliFile(checksum).exists()) {
- LOG.debug("CLI checksum mismatch");
- downloadCli(metadataResponse.id(), checksum);
- }
+ CliMetadataResponse metadataResponse = metadataResponses.get(0);
+ String checksum = metadataResponse.sha256();
+ // If we have a matching checksum dir with the existing CLI file, then we are up to date.
+ if (!cachedCliFile(checksum).exists()) {
+ LOG.debug("CLI checksum mismatch");
+ downloadCli(metadataResponse.id(), checksum);
+ telemetryCache.put("scanner.sca.get.cli.cache.hit", "false");
+ } else {
+ telemetryCache.put("scanner.sca.get.cli.cache.hit", "true");
+ }
- return cachedCliFile(checksum);
+ success = true;
+ return cachedCliFile(checksum);
+ } finally {
+ telemetryCache.put("scanner.sca.get.cli.success", String.valueOf(success));
+ }
}
Path cacheDir() {
@@ -143,10 +155,12 @@ public class CliCacheService {
private List<CliMetadataResponse> getLatestMetadata(String osName, String arch) {
LOG.info("Requesting CLI for OS {} and arch {}", osName, arch);
GetRequest getRequest = new GetRequest(CLI_WS_URL).setParam("os", osName).setParam("arch", arch);
- try (Reader reader = wsClient.call(getRequest).contentReader()) {
- Type listOfMetadata = new TypeToken<ArrayList<CliMetadataResponse>>() {
- }.getType();
- return new Gson().fromJson(reader, listOfMetadata);
+ try (WsResponse response = wsClient.call(getRequest)) {
+ try (Reader reader = response.contentReader()) {
+ Type listOfMetadata = new TypeToken<ArrayList<CliMetadataResponse>>() {
+ }.getType();
+ return new Gson().fromJson(reader, listOfMetadata);
+ }
} catch (Exception e) {
throw new IllegalStateException("Unable to load CLI metadata", e);
}
@@ -154,6 +168,8 @@ public class CliCacheService {
private void downloadCli(String id, String checksum) {
LOG.info("Downloading cli {}", id);
+ long startTime = system2.now();
+ boolean success = false;
GetRequest getRequest = new GetRequest(CLI_WS_URL + "/" + id).setHeader("Accept", "application/octet-stream");
try (WsResponse response = wsClient.call(getRequest)) {
@@ -170,8 +186,12 @@ public class CliCacheService {
if (!destinationFile.setExecutable(true, false)) {
throw new IllegalStateException("Unable to mark CLI as executable");
}
+ success = true;
} catch (Exception e) {
throw new IllegalStateException("Unable to download CLI executable", e);
+ } finally {
+ telemetryCache.put("scanner.sca.download.cli.duration", String.valueOf(system2.now() - startTime));
+ telemetryCache.put("scanner.sca.download.cli.success", String.valueOf(success));
}
}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/CliService.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/CliService.java
index e07393c01db..3891acb23bb 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/CliService.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/CliService.java
@@ -29,7 +29,9 @@ import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.batch.fs.internal.DefaultInputModule;
+import org.sonar.api.utils.System2;
import org.sonar.core.util.ProcessWrapperFactory;
+import org.sonar.scanner.repository.TelemetryCache;
/**
* The CliService class is meant to serve as the main entrypoint for any commands
@@ -40,35 +42,45 @@ import org.sonar.core.util.ProcessWrapperFactory;
public class CliService {
private static final Logger LOG = LoggerFactory.getLogger(CliService.class);
private final ProcessWrapperFactory processWrapperFactory;
+ private final TelemetryCache telemetryCache;
+ private final System2 system2;
- public CliService(ProcessWrapperFactory processWrapperFactory) {
+ public CliService(ProcessWrapperFactory processWrapperFactory, TelemetryCache telemetryCache, System2 system2) {
this.processWrapperFactory = processWrapperFactory;
+ this.telemetryCache = telemetryCache;
+ this.system2 = system2;
}
public File generateManifestsZip(DefaultInputModule module, File cliExecutable) throws IOException, IllegalStateException {
- String zipName = "dependency-files.zip";
- Path zipPath = module.getWorkDir().resolve(zipName);
- List<String> args = new ArrayList<>();
- args.add(cliExecutable.getAbsolutePath());
- args.add("projects");
- args.add("save-lockfiles");
- args.add("--zip");
- args.add("--zip-filename");
- args.add(zipPath.toAbsolutePath().toString());
- args.add("--directory");
- args.add(module.getBaseDir().toString());
- args.add("--debug");
+ long startTime = system2.now();
+ boolean success = false;
+ try {
+ String zipName = "dependency-files.zip";
+ Path zipPath = module.getWorkDir().resolve(zipName);
+ List<String> args = new ArrayList<>();
+ args.add(cliExecutable.getAbsolutePath());
+ args.add("projects");
+ args.add("save-lockfiles");
+ args.add("--zip");
+ args.add("--zip-filename");
+ args.add(zipPath.toAbsolutePath().toString());
+ args.add("--directory");
+ args.add(module.getBaseDir().toString());
+ args.add("--debug");
- LOG.debug("Calling ProcessBuilder with args: {}", args);
+ LOG.debug("Calling ProcessBuilder with args: {}", args);
- Map<String, String> envProperties = new HashMap<>();
- // sending this will tell the CLI to skip checking for the latest available version on startup
- envProperties.put("TIDELIFT_SKIP_UPDATE_CHECK", "1");
+ Map<String, String> envProperties = new HashMap<>();
+ // sending this will tell the CLI to skip checking for the latest available version on startup
+ envProperties.put("TIDELIFT_SKIP_UPDATE_CHECK", "1");
- processWrapperFactory.create(module.getWorkDir(), LOG::debug, envProperties, args.toArray(new String[0])).execute();
- LOG.info("Generated manifests zip file: {}", zipName);
-
- return zipPath.toFile();
+ processWrapperFactory.create(module.getWorkDir(), LOG::debug, envProperties, args.toArray(new String[0])).execute();
+ LOG.info("Generated manifests zip file: {}", zipName);
+ success = true;
+ return zipPath.toFile();
+ } finally {
+ telemetryCache.put("scanner.sca.execution.cli.duration", String.valueOf(startTime - system2.now()));
+ telemetryCache.put("scanner.sca.execution.cli.success", String.valueOf(success));
+ }
}
-
}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/sca/CliCacheServiceTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/sca/CliCacheServiceTest.java
index 4b26c2a3a9b..b7ee1c0f668 100644
--- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/sca/CliCacheServiceTest.java
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/sca/CliCacheServiceTest.java
@@ -31,15 +31,19 @@ import java.nio.file.StandardCopyOption;
import org.apache.commons.io.FileUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.api.io.TempDir;
+import org.mockito.Mock;
import org.mockito.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
import org.slf4j.event.Level;
import org.sonar.api.testfixtures.log.LogTesterJUnit5;
import org.sonar.api.utils.System2;
import org.sonar.scanner.WsTestUtil;
import org.sonar.scanner.bootstrap.SonarUserHome;
import org.sonar.scanner.http.DefaultScannerWsClient;
+import org.sonar.scanner.repository.TelemetryCache;
import org.sonarqube.ws.client.HttpException;
import org.sonarqube.ws.client.WsResponse;
@@ -47,26 +51,38 @@ import static java.lang.String.format;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.sonar.scanner.sca.CliCacheService.CLI_WS_URL;
+@ExtendWith(MockitoExtension.class)
class CliCacheServiceTest {
- private final SonarUserHome sonarUserHome = mock(SonarUserHome.class);
- private final DefaultScannerWsClient scannerWsClient = mock(DefaultScannerWsClient.class);
- private final System2 system2 = mock(System2.class);
-
- private final CliCacheService underTest = new CliCacheService(sonarUserHome, scannerWsClient, system2);
+ @Mock
+ private SonarUserHome sonarUserHome;
+ @Mock
+ private DefaultScannerWsClient scannerWsClient;
+ @Mock
+ private System2 system2;
+ @Mock
+ private TelemetryCache telemetryCache;
@RegisterExtension
private final LogTesterJUnit5 logTester = new LogTesterJUnit5();
@TempDir
public Path cacheDir;
+ private CliCacheService underTest;
+
@BeforeEach
- void setUp() {
- when(sonarUserHome.getPath()).thenReturn(cacheDir);
+ void setup() {
+ lenient().when(sonarUserHome.getPath()).thenReturn(cacheDir);
+ lenient().when(telemetryCache.put(any(), any())).thenReturn(telemetryCache);
+
+ underTest = new CliCacheService(sonarUserHome, scannerWsClient, telemetryCache, system2);
}
@Test
@@ -92,6 +108,11 @@ class CliCacheServiceTest {
assertThat(generatedFile).exists().isExecutable();
assertThat(cacheDir.resolve("cache").resolve(checksum)).exists().isNotEmptyDirectory();
+
+ verify(telemetryCache).put(eq("scanner.sca.download.cli.duration"), any());
+ verify(telemetryCache).put("scanner.sca.download.cli.success", "true");
+ verify(telemetryCache).put("scanner.sca.get.cli.cache.hit", "false");
+ verify(telemetryCache).put("scanner.sca.get.cli.success", "true");
}
@Test
@@ -116,6 +137,9 @@ class CliCacheServiceTest {
assertThatThrownBy(underTest::cacheCli).isInstanceOf(IllegalStateException.class)
.hasMessageContaining("Multiple CLI matches found. Unable to correctly cache CLI.");
+
+ verify(telemetryCache).put("scanner.sca.get.cli.success", "false");
+
}
@Test
@@ -124,6 +148,9 @@ class CliCacheServiceTest {
assertThatThrownBy(underTest::cacheCli).isInstanceOf(IllegalStateException.class)
.hasMessageMatching("Could not find CLI for .+ .+");
+
+ verify(telemetryCache).put("scanner.sca.get.cli.success", "false");
+
}
@Test
@@ -134,6 +161,8 @@ class CliCacheServiceTest {
assertThatThrownBy(underTest::cacheCli).isInstanceOf(IllegalStateException.class)
.hasMessageContaining("Unable to load CLI metadata");
+
+ verify(telemetryCache).put("scanner.sca.get.cli.success", "false");
}
@Test
@@ -166,6 +195,9 @@ class CliCacheServiceTest {
assertThat(existingFile).exists();
assertThat(existingFile.canExecute()).isFalse();
assertThat(FileUtils.readFileToString(existingFile, Charset.defaultCharset())).isEqualTo(fileContent);
+
+ verify(telemetryCache).put("scanner.sca.get.cli.cache.hit", "true");
+ verify(telemetryCache).put("scanner.sca.get.cli.success", "true");
}
@Test
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/sca/CliServiceTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/sca/CliServiceTest.java
index 422f485bd51..5a491439578 100644
--- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/sca/CliServiceTest.java
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/sca/CliServiceTest.java
@@ -26,27 +26,36 @@ import java.net.URL;
import java.nio.file.Path;
import java.util.List;
import org.apache.commons.lang3.SystemUtils;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.api.io.TempDir;
import org.sonar.api.batch.bootstrap.ProjectDefinition;
import org.sonar.api.batch.fs.internal.DefaultInputModule;
import org.sonar.api.testfixtures.log.LogTesterJUnit5;
+import org.sonar.api.utils.System2;
import org.sonar.core.util.ProcessWrapperFactory;
+import org.sonar.scanner.repository.TelemetryCache;
import static org.assertj.core.api.Assertions.assertThat;
import static org.slf4j.event.Level.DEBUG;
import static org.slf4j.event.Level.INFO;
class CliServiceTest {
- private final ProcessWrapperFactory processWrapperFactory = new ProcessWrapperFactory();
- private final CliService underTest = new CliService(processWrapperFactory);
-
+ private TelemetryCache telemetryCache;
@RegisterExtension
private final LogTesterJUnit5 logTester = new LogTesterJUnit5();
+ private CliService underTest;
+
+ @BeforeEach
+ void setup() {
+ telemetryCache = new TelemetryCache();
+ underTest = new CliService(new ProcessWrapperFactory(), telemetryCache, System2.INSTANCE);
+ }
+
@Test
- void generateZip_shouldCallProcessCorrectly(@TempDir Path rootModuleDir) throws IOException, URISyntaxException {
+ void generateZip_shouldCallProcessCorrectly_andRegisterTelemetry(@TempDir Path rootModuleDir) throws IOException, URISyntaxException {
DefaultInputModule root = new DefaultInputModule(
ProjectDefinition.create().setBaseDir(rootModuleDir.toFile()).setWorkDir(rootModuleDir.toFile()));
@@ -81,5 +90,8 @@ class CliServiceTest {
.contains(argumentOutput)
.contains("TIDELIFT_SKIP_UPDATE_CHECK=1");
assertThat(logTester.logs(INFO)).contains("Generated manifests zip file: " + producedZip.getName());
+
+ assertThat(telemetryCache.getAll()).containsKey("scanner.sca.execution.cli.duration").isNotNull();
+ assertThat(telemetryCache.getAll()).containsEntry("scanner.sca.execution.cli.success", "true");
}
}