From 2b2f246731c4052c4052e2f2d56416352132114f Mon Sep 17 00:00:00 2001 From: Matteo Mara Date: Thu, 11 Jul 2024 12:11:11 +0200 Subject: [PATCH] SONAR-22479 added new telemetry metric "ncloc" for language dimension --- .../telemetry/TelemetryNclocProvider.java | 107 ++++++++++++++++++ .../telemetry/TelemetryNclocProviderTest.java | 71 ++++++++++++ .../platformlevel/PlatformLevel4.java | 2 + 3 files changed, 180 insertions(+) create mode 100644 server/sonar-telemetry/src/main/java/org/sonar/telemetry/TelemetryNclocProvider.java create mode 100644 server/sonar-telemetry/src/test/java/org/sonar/telemetry/TelemetryNclocProviderTest.java diff --git a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/TelemetryNclocProvider.java b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/TelemetryNclocProvider.java new file mode 100644 index 00000000000..f3f72316c9f --- /dev/null +++ b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/TelemetryNclocProvider.java @@ -0,0 +1,107 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.telemetry; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.measure.ProjectLocDistributionDto; +import org.sonar.db.metric.MetricDto; + +import static java.util.Arrays.asList; +import static java.util.stream.Collectors.toMap; +import static org.sonar.api.measures.CoreMetrics.NCLOC_KEY; +import static org.sonar.api.measures.CoreMetrics.NCLOC_LANGUAGE_DISTRIBUTION_KEY; + +public class TelemetryNclocProvider implements TelemetryDataProvider { + + public static final String METRIC_KEY = "ncloc_per_language"; + + private final DbClient dbClient; + + public TelemetryNclocProvider(DbClient dbClient) { + this.dbClient = dbClient; + } + + @Override + public String getMetricKey() { + return METRIC_KEY; + } + + @Override + public Dimension getDimension() { + return Dimension.LANGUAGE; + } + + @Override + public Granularity getGranularity() { + return Granularity.DAILY; + } + + @Override + public TelemetryDataType getType() { + return TelemetryDataType.INTEGER; + } + + @Override + public Map getUuidValues() { + try (DbSession dbSession = dbClient.openSession(false)) { + return getNclocDistribution(dbSession); + } + } + + private Map getNclocDistribution(DbSession dbSession) { + Map metricUuidMap = getNclocMetricUuidMap(dbSession); + String nclocUuid = metricUuidMap.get(NCLOC_KEY); + String nclocDistributionUuid = metricUuidMap.get(NCLOC_LANGUAGE_DISTRIBUTION_KEY); + List branchesWithLargestNcloc = dbClient.liveMeasureDao().selectLargestBranchesLocDistribution(dbSession, nclocUuid, nclocDistributionUuid); + List languageDistributions = getLanguageDistributionList(branchesWithLargestNcloc); + return getNclocDistributionPerLanguage(languageDistributions); + } + + private Map getNclocMetricUuidMap(DbSession dbSession) { + return dbClient.metricDao().selectByKeys(dbSession, asList(NCLOC_KEY, NCLOC_LANGUAGE_DISTRIBUTION_KEY)) + .stream() + .collect(toMap(MetricDto::getKey, MetricDto::getUuid)); + } + + private static List getLanguageDistributionList(List branchesWithLargestNcloc) { + return branchesWithLargestNcloc.stream() + .flatMap(measure -> Arrays.stream(measure.locDistribution().split(";")) + .map(languageAndLoc -> languageAndLoc.split("=")) + .map(languageAndLoc -> new LanguageDistribution( + languageAndLoc[0], + Long.parseLong(languageAndLoc[1])))) + .toList(); + } + + private static Map getNclocDistributionPerLanguage(List languageDistributions) { + // a Map that contains the sum of ncloc per language + Map nclocPerLanguage = new HashMap<>(); + languageDistributions.forEach(languageDistribution -> nclocPerLanguage.merge(languageDistribution.language, languageDistribution.ncloc, Long::sum)); + return nclocPerLanguage; + } + + private record LanguageDistribution(String language, long ncloc) { + } +} diff --git a/server/sonar-telemetry/src/test/java/org/sonar/telemetry/TelemetryNclocProviderTest.java b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/TelemetryNclocProviderTest.java new file mode 100644 index 00000000000..00aeb98086e --- /dev/null +++ b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/TelemetryNclocProviderTest.java @@ -0,0 +1,71 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.telemetry; + +import java.util.Arrays; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.measure.ProjectLocDistributionDto; +import org.sonar.db.metric.MetricDto; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.api.measures.CoreMetrics.NCLOC_KEY; +import static org.sonar.api.measures.CoreMetrics.NCLOC_LANGUAGE_DISTRIBUTION_KEY; +import static org.sonar.telemetry.TelemetryNclocProvider.METRIC_KEY; + +class TelemetryNclocProviderTest { + + DbClient dbClient = mock(DbClient.class, Mockito.RETURNS_DEEP_STUBS); + private final DbSession dbSession = mock(DbSession.class); + + @Test + void getUuidValues_returnsTheRightLanguageDistribution() { + TelemetryNclocProvider telemetryNclocProvider = new TelemetryNclocProvider(dbClient); + + when(dbClient.openSession(false)).thenReturn(dbSession); + + when(dbClient.metricDao().selectByKeys(dbSession, asList(NCLOC_KEY, NCLOC_LANGUAGE_DISTRIBUTION_KEY))).thenReturn(Arrays.asList( + new MetricDto().setKey(NCLOC_KEY).setUuid("ncloc_uuid"), + new MetricDto().setKey(NCLOC_LANGUAGE_DISTRIBUTION_KEY).setUuid("ncloc_distribution_uuid") + )); + + when(dbClient.liveMeasureDao().selectLargestBranchesLocDistribution(dbSession, "ncloc_uuid", "ncloc_distribution_uuid")).thenReturn(Arrays.asList( + new ProjectLocDistributionDto("project1", "branch1-p1", "java=5000;xml=1000;js=1000"), + new ProjectLocDistributionDto("project2", "branch1-p2", "java=10000;csharp=2000"), + new ProjectLocDistributionDto("project3", "branch1-p3", "java=7000;js=500") + )); + + assertEquals(METRIC_KEY, telemetryNclocProvider.getMetricKey()); + assertEquals(Granularity.DAILY, telemetryNclocProvider.getGranularity()); + assertEquals(Dimension.LANGUAGE, telemetryNclocProvider.getDimension()); + + assertThat(telemetryNclocProvider.getUuidValues()).containsOnlyKeys("java", "xml", "csharp", "js"); + assertThat(telemetryNclocProvider.getUuidValues()).containsEntry("java", 22000L); + assertThat(telemetryNclocProvider.getUuidValues()).containsEntry("xml", 1000L); + assertThat(telemetryNclocProvider.getUuidValues()).containsEntry("csharp", 2000L); + assertThat(telemetryNclocProvider.getUuidValues()).containsEntry("js", 1500L); + } +} \ No newline at end of file diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java index c37fa753d5e..59d0d908787 100644 --- a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java +++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java @@ -282,6 +282,7 @@ import org.sonar.server.webhook.WebhookQGChangeEventListener; import org.sonar.server.webhook.ws.WebhooksWsModule; import org.sonar.server.ws.WebServiceEngine; import org.sonar.server.ws.ws.WebServicesWsModule; +import org.sonar.telemetry.TelemetryNclocProvider; import org.sonar.telemetry.legacy.CloudUsageDataProvider; import org.sonar.telemetry.legacy.QualityProfileDataProvider; import org.sonar.telemetry.legacy.TelemetryClient; @@ -667,6 +668,7 @@ public class PlatformLevel4 extends PlatformLevel { //new telemetry metrics TelemetryVersionProvider.class, + TelemetryNclocProvider.class, // monitoring ServerMonitoringMetrics.class, -- 2.39.5