diff options
author | Javier García Orduña <javier.garcia@sonarsource.com> | 2025-02-25 13:39:24 +0100 |
---|---|---|
committer | Lukasz Jarocki <lukasz.jarocki@sonarsource.com> | 2025-02-28 09:57:48 +0100 |
commit | 86670bc803d6bedaa7703da7f7e0aa072cf6977d (patch) | |
tree | 1fa5aea4fdb47438a973eee73067756d9c2374f9 /sonar-scanner-engine | |
parent | 20d7a52648033668ee648b3e7f875e3ea831bde1 (diff) | |
download | sonarqube-86670bc803d6bedaa7703da7f7e0aa072cf6977d.tar.gz sonarqube-86670bc803d6bedaa7703da7f7e0aa072cf6977d.zip |
SQRP-272 Add scanner-engine telemetry for SCA services
Diffstat (limited to 'sonar-scanner-engine')
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"); } } |