]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-18472 Resolve telemetry performance issue
authorAlain Kermis <alain.kermis@sonarsource.com>
Fri, 17 Feb 2023 10:44:37 +0000 (11:44 +0100)
committersonartech <sonartech@sonarsource.com>
Wed, 15 Mar 2023 20:03:02 +0000 (20:03 +0000)
Co-authored-by: Jacek Poreda <jacek.poreda@sonarsource.com>
(cherry picked from commit a20f2bce3cc9111152aa810030253db6acbd4af8)

12 files changed:
server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java
server/sonar-db-dao/src/main/java/org/sonar/db/measure/LiveMeasureDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/measure/LiveMeasureMapper.java
server/sonar-db-dao/src/main/java/org/sonar/db/measure/MeasureDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/measure/MeasureMapper.java
server/sonar-db-dao/src/main/java/org/sonar/db/measure/ProjectLocDistributionDto.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/measure/ProjectMeasureDto.java [deleted file]
server/sonar-db-dao/src/main/resources/org/sonar/db/measure/LiveMeasureMapper.xml
server/sonar-db-dao/src/main/resources/org/sonar/db/measure/MeasureMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/measure/LiveMeasureDaoTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/measure/MeasureDaoTest.java
server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryDataLoaderImpl.java

index dc6070aa9e734411204f9a2b4eb60bce2d40d740..171467e652c79214ae65102836967315eece89f6 100644 (file)
@@ -81,7 +81,7 @@ import org.sonar.db.measure.LargestBranchNclocDto;
 import org.sonar.db.measure.LiveMeasureMapper;
 import org.sonar.db.measure.MeasureDto;
 import org.sonar.db.measure.MeasureMapper;
-import org.sonar.db.measure.ProjectMeasureDto;
+import org.sonar.db.measure.ProjectLocDistributionDto;
 import org.sonar.db.metric.MetricMapper;
 import org.sonar.db.newcodeperiod.NewCodePeriodMapper;
 import org.sonar.db.notification.NotificationQueueDto;
@@ -225,7 +225,7 @@ public class MyBatis {
     confBuilder.loadAlias("ProjectAlmKeyAndProject", ProjectAlmKeyAndProject.class);
     confBuilder.loadAlias("PrAndBranchCountByProjectDto", PrBranchAnalyzedLanguageCountByProjectDto.class);
     confBuilder.loadAlias("ProjectMapping", ProjectMappingDto.class);
-    confBuilder.loadAlias("ProjectMeasure", ProjectMeasureDto.class);
+    confBuilder.loadAlias("ProjectLocDistribution", ProjectLocDistributionDto.class);
     confBuilder.loadAlias("PurgeableAnalysis", PurgeableAnalysisDto.class);
     confBuilder.loadAlias("PushEvent", PushEventDto.class);
     confBuilder.loadAlias("QualityGateCondition", QualityGateConditionDto.class);
index 7e5547fc2926d9167501afcf3b4e12e8ca294b74..17441f112367f1e4153129793d0f7cb07eccc82f 100644 (file)
@@ -109,6 +109,10 @@ public class LiveMeasureDao implements Dao {
     return mapper(dbSession).getLargestBranchNclocPerProject();
   }
 
+  public List<ProjectLocDistributionDto> selectLargestBranchesLocDistribution(DbSession session, String nclocUuid, String nclocDistributionUuid) {
+    return mapper(session).selectLargestBranchesLocDistribution(nclocUuid, nclocDistributionUuid);
+  }
+
   public long countProjectsHavingMeasure(DbSession dbSession, String metric) {
     return mapper(dbSession).countProjectsHavingMeasure(metric);
   }
index 9d2765d89e35ade122e0c650f189a7f33cb90a68..5891b136fd76e6457215611254e49f45e786bca4 100644 (file)
@@ -64,6 +64,8 @@ public interface LiveMeasureMapper {
 
   List<LargestBranchNclocDto> getLargestBranchNclocPerProject();
 
+  List<ProjectLocDistributionDto> selectLargestBranchesLocDistribution(@Param("nclocUuid") String nclocUuid, @Param("nclocDistributionUuid") String nclocDistributionUuid);
+
   Long countProjectsHavingMeasure(
     @Param("metric") String metric);
 
index df552e3aeb61486a56422fa83a5d194a26e24301..2c9fb0a195e10ccf8d275730f3c4f288be0e0b06 100644 (file)
@@ -76,8 +76,4 @@ public class MeasureDao implements Dao {
     return session.getMapper(MeasureMapper.class);
   }
 
-  public List<ProjectMeasureDto> selectLastMeasureForAllProjects(DbSession session, String metricKey) {
-    return mapper(session).selectLastMeasureForAllProjects(metricKey);
-
-  }
 }
index fc395469f8935782eb27ef93b19fc47f5444e18e..5b11e84467785d802cba0363c491b8d7000459e0 100644 (file)
@@ -42,5 +42,4 @@ public interface MeasureMapper {
 
   void insert(MeasureDto measureDto);
 
-  List<ProjectMeasureDto> selectLastMeasureForAllProjects(@Param("metricKey") String metricKey);
 }
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/measure/ProjectLocDistributionDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/measure/ProjectLocDistributionDto.java
new file mode 100644 (file)
index 0000000..bd31009
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.db.measure;
+
+/**
+ * Loc distribution per language for the largest branch in a project.
+ */
+public record ProjectLocDistributionDto(String projectUuid, String branchUuid, String locDistribution) {
+
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/measure/ProjectMeasureDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/measure/ProjectMeasureDto.java
deleted file mode 100644 (file)
index 15c320a..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.db.measure;
-
-public class ProjectMeasureDto {
-
-  private String projectUuid;
-  private Long lastAnalysis;
-  private long loc;
-  private String textValue;
-
-  public String getProjectUuid() {
-    return projectUuid;
-  }
-
-  public ProjectMeasureDto setProjectUuid(String projectUuid) {
-    this.projectUuid = projectUuid;
-    return this;
-  }
-
-  public String getTextValue() {
-    return textValue;
-  }
-
-  public ProjectMeasureDto setTextValue(String textValue) {
-    this.textValue = textValue;
-    return this;
-  }
-
-  public long getLoc() {
-    return loc;
-  }
-
-  public ProjectMeasureDto setLoc(long loc) {
-    this.loc = loc;
-    return this;
-  }
-
-  public Long getLastAnalysis() {
-    return lastAnalysis;
-  }
-
-  public ProjectMeasureDto setLastAnalysis(Long lastAnalysis) {
-    this.lastAnalysis = lastAnalysis;
-    return this;
-  }
-
-}
index 2612e083b46ce8ffda8a6703d04f4f7bf5265dc0..00819f44716f2002372abdbdbd040f1754c3c6d2 100644 (file)
       order by ncloc desc
   </select>
 
+  <select id="selectLargestBranchesLocDistribution" parameterType="map" resultType="ProjectLocDistribution">
+    select top_branches.project_uuid as projectUuid,
+    top_branches.uuid as branchUuid,
+    lm2.text_value as locDistribution from (
+    SELECT loc_grouped_branches.uuid, loc_grouped_branches.project_uuid
+      FROM (
+       SELECT b.uuid, b.project_uuid, ROW_NUMBER() OVER (PARTITION BY
+       b.project_uuid ORDER BY lm.value desc, b.uuid asc) row_number
+       from live_measures lm
+          inner join project_branches b on b.uuid = lm.component_uuid
+          inner join projects p on p.uuid = b.project_uuid
+          where lm.metric_uuid = #{nclocUuid, jdbcType=VARCHAR}
+          and p.qualifier ='TRK'
+      ) loc_grouped_branches
+    WHERE loc_grouped_branches.row_number = 1) top_branches
+    inner join live_measures lm2 on lm2.component_uuid = top_branches.uuid
+    where lm2.metric_uuid = #{nclocDistributionUuid, jdbcType=VARCHAR}
+  </select>
 
   <select id="countProjectsHavingMeasure" parameterType="map" resultType="long">
     select count(1)
index 3071c777ecd642eba3e804f9eb3904f165401fc0..a864b092452cfbdb85f07afe11653a232e3e678d 100644 (file)
     s.islast= ${_true}
   </select>
 
-
-  <select id="selectLastMeasureForAllProjects" parameterType="map" resultType="ProjectMeasure">
-    select tie_breaker.projectUuid as projectUuid,
-    s.build_date as lastAnalysis,
-    ncloc as loc,
-    pm.text_value as textValue
-      from
-        (select counter.projectUuid as projectUuid,
-          counter.maxncloc ncloc,
-          min(br.uuid) as branchUuid,
-          counter.projectName,
-          counter.projectKey
-          from
-            (select b.project_uuid as projectUuid,
-              p.name as projectName,
-              p.kee as projectKey,
-              max(lm.value) as maxncloc
-                from live_measures lm
-                inner join metrics m on m.uuid = lm.metric_uuid
-                inner join project_branches b on b.uuid = lm.component_uuid
-                inner join projects p on p.uuid = b.project_uuid and p.qualifier = 'TRK'
-                where m.name = 'ncloc'
-                group by b.project_uuid, p.name, p.kee) counter
-          inner join live_measures lmo on lmo.value = counter.maxncloc
-          inner join project_branches br on br.project_uuid = counter.projectUuid and br.uuid = lmo.component_uuid
-          inner join components c on c.uuid = br.uuid
-          group by counter.projectUuid, counter.maxncloc, counter.projectName, counter.projectKey) tie_breaker
-      inner join project_branches pb on tie_breaker.branchUuid = pb.uuid
-      inner join project_measures pm on pb.uuid = pm.component_uuid
-      inner join snapshots s on s.component_uuid = pb.uuid and s.islast = ${_true}
-      inner join metrics m on m.name = #{metricKey,jdbcType=VARCHAR} and m.uuid = pm.metric_uuid
-      where pm.analysis_uuid = s.uuid
-      order by ncloc desc
-  </select>
-
   <select id="selectMeasure" parameterType="map" resultType="Measure">
     select <include refid="measureColumns"/>
     from project_measures pm
index 04f12e74729b4c83369f203707d8aa14a621a85c..13cf0c87affb5748de8e477b0112bd64a5f7c46e 100644 (file)
@@ -24,6 +24,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 import java.util.stream.IntStream;
 import org.apache.commons.lang.RandomStringUtils;
@@ -43,6 +44,7 @@ import static java.util.Collections.singleton;
 import static java.util.Collections.singletonList;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.groups.Tuple.tuple;
+import static org.sonar.api.measures.Metric.ValueType.DATA;
 import static org.sonar.api.measures.Metric.ValueType.INT;
 import static org.sonar.db.component.BranchDto.DEFAULT_MAIN_BRANCH_NAME;
 import static org.sonar.db.component.ComponentTesting.newFileDto;
@@ -373,7 +375,8 @@ public class LiveMeasureDaoTest {
 
   @Test
   public void get_branch_with_max_ncloc_per_project() {
-    setupProjectsWithLoc();
+    Map<String, MetricDto> metrics = setupMetrics();
+    setupProjectsWithLoc(metrics.get("ncloc"), metrics.get("ncloc_language_distribution"), metrics.get("lines"));
 
     List<LargestBranchNclocDto> results = underTest.getLargestBranchNclocPerProject(db.getSession());
 
@@ -385,6 +388,24 @@ public class LiveMeasureDaoTest {
     assertLocForProject(results.get(4), "projectWithLinesButNoLoc", DEFAULT_MAIN_BRANCH_NAME, 0);
   }
 
+  @Test
+  public void get_loc_language_distribution() {
+    Map<String, MetricDto> metrics = setupMetrics();
+    MetricDto ncloc = metrics.get("ncloc");
+    MetricDto nclocLanguageDistribution = metrics.get("ncloc_language_distribution");
+    Map<String, ComponentDto> components = setupProjectsWithLoc(ncloc, nclocLanguageDistribution, metrics.get("lines"));
+
+    List<ProjectLocDistributionDto> results = underTest.selectLargestBranchesLocDistribution(db.getSession(), ncloc.getUuid(), nclocLanguageDistribution.getUuid());
+
+    assertThat(results)
+      .containsExactlyInAnyOrder(
+        new ProjectLocDistributionDto(components.get("projectWithTieOnBranchSize").uuid(), components.get("projectWithTieOnBranchSize").uuid(), "java=250;js=0"),
+        new ProjectLocDistributionDto(components.get("projectWithTieOnOtherBranches").uuid(), components.get("tieBranch1").uuid(), "java=230;js=0"),
+        new ProjectLocDistributionDto(components.get("projectWithBranchBiggerThanMaster").uuid(), components.get("notMasterBranch").uuid(), "java=100;js=100"),
+        new ProjectLocDistributionDto(components.get("simpleProject").uuid(), components.get("simpleProject").uuid(), "java=10;js=0"),
+        new ProjectLocDistributionDto(components.get("projectWithLinesButNoLoc").uuid(), components.get("projectWithLinesButNoLoc").uuid(), "java=0;js=0"));
+  }
+
   @Test
   public void countNcloc_empty() {
     db.measures().insertMetric(m -> m.setKey("ncloc").setValueType(INT.toString()));
@@ -696,29 +717,55 @@ public class LiveMeasureDaoTest {
       "componentUuid", "projectUuid", "metricUuid", "value", "textValue", "data");
   }
 
-  private void setupProjectsWithLoc() {
+  private Map<String, MetricDto> setupMetrics() {
     MetricDto ncloc = db.measures().insertMetric(m -> m.setKey("ncloc").setValueType(INT.toString()));
+    MetricDto nclocDistribution = db.measures().insertMetric(m -> m.setKey("ncloc_language_distribution").setValueType(DATA.toString()));
     MetricDto lines = db.measures().insertMetric(m -> m.setKey("lines").setValueType(INT.toString()));
+    return Map.of("ncloc", ncloc,
+      "ncloc_language_distribution", nclocDistribution,
+      "lines", lines);
+  }
 
-    addProjectWithMeasure("simpleProject", ncloc, 10d);
+  private Map<String, ComponentDto> setupProjectsWithLoc(MetricDto ncloc, MetricDto nclocDistribution, MetricDto lines) {
+    ComponentDto simpleProject = addProjectWithMeasure("simpleProject", ncloc, 10d);
+    addMeasureToComponent(simpleProject, nclocDistribution, "java=10;js=0");
 
     ComponentDto projectWithBranchBiggerThanMaster = addProjectWithMeasure("projectWithBranchBiggerThanMaster", ncloc, 100d);
-    addBranchToProjectWithMeasure(projectWithBranchBiggerThanMaster,"notMasterBranch", ncloc, 200d);
+    addMeasureToComponent(projectWithBranchBiggerThanMaster, nclocDistribution, "java=100;js=0");
+
+    ComponentDto notMasterBranch = addBranchToProjectWithMeasure(projectWithBranchBiggerThanMaster, "notMasterBranch", ncloc, 200d);
+    addMeasureToComponent(notMasterBranch, nclocDistribution, "java=100;js=100");
 
     ComponentDto projectWithLinesButNoLoc = addProjectWithMeasure("projectWithLinesButNoLoc", lines, 365d);
-    addMeasureToComponent(projectWithLinesButNoLoc,ncloc,0d,false);
+    addMeasureToComponent(projectWithLinesButNoLoc, nclocDistribution, "java=0;js=0");
+    addMeasureToComponent(projectWithLinesButNoLoc, ncloc, 0d, false);
 
     ComponentDto projectWithTieOnBranchSize = addProjectWithMeasure("projectWithTieOnBranchSize", ncloc, 250d);
-    addBranchToProjectWithMeasure(projectWithTieOnBranchSize,"tieBranch", ncloc, 250d);
+    addMeasureToComponent(projectWithTieOnBranchSize, nclocDistribution, "java=250;js=0");
+    ComponentDto tieBranch = addBranchToProjectWithMeasure(projectWithTieOnBranchSize, "tieBranch", ncloc, 250d);
+    addMeasureToComponent(tieBranch, nclocDistribution, "java=250;js=0");
 
     ComponentDto projectWithTieOnOtherBranches = addProjectWithMeasure("projectWithTieOnOtherBranches", ncloc, 220d);
-    addBranchToProjectWithMeasure(projectWithTieOnOtherBranches,"tieBranch1", ncloc, 230d);
-    addBranchToProjectWithMeasure(projectWithTieOnOtherBranches,"tieBranch2", ncloc, 230d);
+    addMeasureToComponent(projectWithTieOnOtherBranches, nclocDistribution, "java=220;js=0");
+    ComponentDto tieBranch1 = addBranchToProjectWithMeasure(projectWithTieOnOtherBranches, "tieBranch1", ncloc, 230d);
+    addMeasureToComponent(tieBranch1, nclocDistribution, "java=230;js=0");
+    ComponentDto tieBranch2 = addBranchToProjectWithMeasure(projectWithTieOnOtherBranches, "tieBranch2", ncloc, 230d);
+    addMeasureToComponent(tieBranch2, nclocDistribution, "java=230;js=0");
+
+    return Map.of("simpleProject", simpleProject,
+      "projectWithBranchBiggerThanMaster", projectWithBranchBiggerThanMaster,
+      "notMasterBranch", notMasterBranch,
+      "projectWithLinesButNoLoc", projectWithLinesButNoLoc,
+      "projectWithTieOnBranchSize", projectWithTieOnBranchSize,
+      "tieBranch", tieBranch,
+      "projectWithTieOnOtherBranches", projectWithTieOnOtherBranches,
+      "tieBranch1", tieBranch1,
+      "tieBranch2", tieBranch2);
   }
 
   private ComponentDto addProjectWithMeasure(String projectKey, MetricDto metric, double metricValue) {
     ComponentDto project = db.components().insertPublicProject(p -> p.setKey(projectKey));
-    addMeasureToComponent(project, metric, metricValue,true);
+    addMeasureToComponent(project, metric, metricValue, true);
     return project;
   }
 
@@ -729,9 +776,14 @@ public class LiveMeasureDaoTest {
     }
   }
 
-  private void addBranchToProjectWithMeasure(ComponentDto project, String branchKey, MetricDto metric, double metricValue) {
+  private void addMeasureToComponent(ComponentDto component, MetricDto metric, String metricValue) {
+    db.measures().insertLiveMeasure(component, metric, m -> m.setData(metricValue));
+  }
+
+  private ComponentDto addBranchToProjectWithMeasure(ComponentDto project, String branchKey, MetricDto metric, double metricValue) {
     ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.BRANCH).setKey(branchKey));
-    addMeasureToComponent(branch, metric, metricValue,true);
+    addMeasureToComponent(branch, metric, metricValue, true);
+    return branch;
   }
 
   private void assertLocForProject(LargestBranchNclocDto result, String projectKey, String branchKey, long linesOfCode) {
index 2ba89f513bcacbc6dc5f57dd642311ec08506af5..4e7e97c0cdd948a82fc80ee94bcedaf0ca446ace 100644 (file)
@@ -46,6 +46,7 @@ public class MeasureDaoTest {
   private MetricDto coverage;
   private MetricDto complexity;
   private MetricDto ncloc;
+  private MetricDto nclocDistribution;
 
 
   @Rule
@@ -60,6 +61,7 @@ public class MeasureDaoTest {
     coverage =db.measures().insertMetric(m -> m.setKey("coverage"));
     complexity = db.measures().insertMetric(m -> m.setKey( "complexity"));
     ncloc = db.measures().insertMetric(m -> m.setKey("ncloc"));
+    nclocDistribution = db.measures().insertMetric(m -> m.setKey("ncloc_language_distribution"));
   }
 
   @Test
index a24ec92656f7a3320ac93a2ed40d23c4eda519a6..98251d34a7fd3d102427cd4acba3dea726d78e66 100644 (file)
@@ -23,6 +23,7 @@ import com.google.common.annotations.VisibleForTesting;
 import java.sql.DatabaseMetaData;
 import java.sql.SQLException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -46,8 +47,9 @@ import org.sonar.db.alm.setting.ALM;
 import org.sonar.db.alm.setting.ProjectAlmKeyAndProject;
 import org.sonar.db.component.AnalysisPropertyValuePerProject;
 import org.sonar.db.component.PrBranchAnalyzedLanguageCountByProjectDto;
+import org.sonar.db.component.SnapshotDto;
 import org.sonar.db.measure.LiveMeasureDto;
-import org.sonar.db.measure.ProjectMeasureDto;
+import org.sonar.db.measure.ProjectLocDistributionDto;
 import org.sonar.db.metric.MetricDto;
 import org.sonar.db.qualitygate.ProjectQgateAssociationDto;
 import org.sonar.db.qualitygate.QualityGateDto;
@@ -64,6 +66,7 @@ import static java.util.stream.Collectors.toMap;
 import static org.sonar.api.internal.apachecommons.lang.StringUtils.startsWithIgnoreCase;
 import static org.sonar.api.measures.CoreMetrics.BUGS_KEY;
 import static org.sonar.api.measures.CoreMetrics.DEVELOPMENT_COST_KEY;
+import static org.sonar.api.measures.CoreMetrics.NCLOC_KEY;
 import static org.sonar.api.measures.CoreMetrics.NCLOC_LANGUAGE_DISTRIBUTION_KEY;
 import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_KEY;
 import static org.sonar.api.measures.CoreMetrics.TECHNICAL_DEBT_KEY;
@@ -222,17 +225,35 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
   }
 
   private void resolveProjects(TelemetryData.Builder data, DbSession dbSession) {
-    List<ProjectMeasureDto> measures = dbClient.measureDao().selectLastMeasureForAllProjects(dbSession, NCLOC_LANGUAGE_DISTRIBUTION_KEY);
-    List<TelemetryData.Project> projects = new ArrayList<>();
-    for (ProjectMeasureDto measure : measures) {
-      for (String measureTextValue : measure.getTextValue().split(";")) {
-        String[] languageAndLoc = measureTextValue.split("=");
-        String language = languageAndLoc[0];
-        Long loc = Long.parseLong(languageAndLoc[1]);
-        projects.add(new TelemetryData.Project(measure.getProjectUuid(), measure.getLastAnalysis(), language, loc));
-      }
-    }
-    data.setProjects(projects);
+    Map<String, String> metricUuidMap = getNclocMetricUuidMap(dbSession);
+    String nclocUuid = metricUuidMap.get(NCLOC_KEY);
+    String nclocDistributionUuid = metricUuidMap.get(NCLOC_LANGUAGE_DISTRIBUTION_KEY);
+    List<ProjectLocDistributionDto> branchesWithLargestNcloc = dbClient.liveMeasureDao().selectLargestBranchesLocDistribution(dbSession, nclocUuid, nclocDistributionUuid);
+    List<String> branchUuids = branchesWithLargestNcloc.stream().map(ProjectLocDistributionDto::branchUuid).toList();
+    Map<String, Long> latestSnapshotMap = dbClient.snapshotDao().selectLastAnalysesByRootComponentUuids(dbSession, branchUuids)
+      .stream()
+      .collect(toMap(SnapshotDto::getComponentUuid, SnapshotDto::getBuildDate));
+    data.setProjects(buildProjectsList(branchesWithLargestNcloc, latestSnapshotMap));
+  }
+
+  private static List<TelemetryData.Project> buildProjectsList(List<ProjectLocDistributionDto> branchesWithLargestNcloc,
+    Map<String, Long> latestSnapshotMap) {
+    return branchesWithLargestNcloc.stream()
+      .flatMap(measure -> Arrays.stream(measure.locDistribution().split(";"))
+        .map(languageAndLoc -> languageAndLoc.split("="))
+        .map(languageAndLoc -> new TelemetryData.Project(
+          measure.projectUuid(),
+          latestSnapshotMap.get(measure.branchUuid()),
+          languageAndLoc[0],
+          Long.parseLong(languageAndLoc[1])
+        ))
+      ).toList();
+  }
+
+  private Map<String, String> getNclocMetricUuidMap(DbSession dbSession) {
+    return dbClient.metricDao().selectByKeys(dbSession, asList(NCLOC_KEY, NCLOC_LANGUAGE_DISTRIBUTION_KEY))
+      .stream()
+      .collect(toMap(MetricDto::getKey, MetricDto::getUuid));
   }
 
   private void resolveQualityGates(TelemetryData.Builder data, DbSession dbSession) {