aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java1
-rw-r--r--server/sonar-db-dao/src/it/java/org/sonar/db/telemetry/TelemetryMetricsSentDaoIT.java79
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java2
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java7
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java2
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/telemetry/TelemetryMetricsSentDao.java56
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/telemetry/TelemetryMetricsSentDto.java52
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/telemetry/TelemetryMetricsSentMapper.java30
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/telemetry/package-info.java23
-rw-r--r--server/sonar-db-dao/src/main/resources/org/sonar/db/telemetry/TelemetryMetricsSentMapper.xml73
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/telemetry/TelemetryMetricsSentDtoTest.java39
-rw-r--r--server/sonar-db-dao/src/testFixtures/java/org/sonar/db/telemetry/TelemetryMetricsSentTesting.java40
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java1
-rw-r--r--server/sonar-server-common/build.gradle1
-rw-r--r--server/sonar-telemetry/build.gradle4
-rw-r--r--server/sonar-telemetry/src/it/java/org/sonar/telemetry/legacy/TelemetryDataLoaderImplIT.java2
-rw-r--r--server/sonar-telemetry/src/it/java/org/sonar/telemetry/metrics/TelemetryMetricsLoaderIT.java69
-rw-r--r--server/sonar-telemetry/src/it/java/org/sonar/telemetry/project/ProjectCppAutoconfigTelemetryProviderIT.java (renamed from server/sonar-telemetry/src/it/java/org/sonar/telemetry/legacy/project/ProjectCppAutoconfigTelemetryProviderIT.java)2
-rw-r--r--server/sonar-telemetry/src/it/java/org/sonar/telemetry/user/TelemetryUserEnabledProviderIT.java (renamed from server/sonar-telemetry/src/it/java/org/sonar/telemetry/legacy/user/TelemetryUserEnabledProviderIT.java)34
-rw-r--r--server/sonar-telemetry/src/main/java/org/sonar/telemetry/Dimension.java27
-rw-r--r--server/sonar-telemetry/src/main/java/org/sonar/telemetry/Granularity.java17
-rw-r--r--server/sonar-telemetry/src/main/java/org/sonar/telemetry/TelemetryClient.java (renamed from server/sonar-telemetry/src/main/java/org/sonar/telemetry/legacy/TelemetryClient.java)6
-rw-r--r--server/sonar-telemetry/src/main/java/org/sonar/telemetry/TelemetryDaemon.java (renamed from server/sonar-telemetry/src/main/java/org/sonar/telemetry/legacy/TelemetryDaemon.java)42
-rw-r--r--server/sonar-telemetry/src/main/java/org/sonar/telemetry/TelemetryDataType.java18
-rw-r--r--server/sonar-telemetry/src/main/java/org/sonar/telemetry/legacy/TelemetryData.java20
-rw-r--r--server/sonar-telemetry/src/main/java/org/sonar/telemetry/legacy/TelemetryDataLoaderImpl.java2
-rw-r--r--server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/TelemetryMetricsLoader.java127
-rw-r--r--server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/TelemetryMetricsMapper.java93
-rw-r--r--server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/package-info.java23
-rw-r--r--server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/BaseMessage.java98
-rw-r--r--server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/InstallationMetric.java34
-rw-r--r--server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/LanguageMetric.java46
-rw-r--r--server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/Metric.java70
-rw-r--r--server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/ProjectMetric.java47
-rw-r--r--server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/UserMetric.java47
-rw-r--r--server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/package-info.java23
-rw-r--r--server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/util/MessageSerializer.java42
-rw-r--r--server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/util/SentMetricsStorage.java78
-rw-r--r--server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/util/package-info.java23
-rw-r--r--server/sonar-telemetry/src/main/java/org/sonar/telemetry/user/TelemetryUserEnabledProvider.java6
-rw-r--r--server/sonar-telemetry/src/test/java/org/sonar/telemetry/DimensionTest.java56
-rw-r--r--server/sonar-telemetry/src/test/java/org/sonar/telemetry/FakeServer.java (renamed from server/sonar-telemetry/src/test/java/org/sonar/telemetry/legacy/FakeServer.java)6
-rw-r--r--server/sonar-telemetry/src/test/java/org/sonar/telemetry/GranularityTest.java35
-rw-r--r--server/sonar-telemetry/src/test/java/org/sonar/telemetry/TelemetryClientCompressionTest.java (renamed from server/sonar-telemetry/src/test/java/org/sonar/telemetry/legacy/TelemetryClientCompressionTest.java)8
-rw-r--r--server/sonar-telemetry/src/test/java/org/sonar/telemetry/TelemetryClientTest.java (renamed from server/sonar-telemetry/src/test/java/org/sonar/telemetry/legacy/TelemetryClientTest.java)16
-rw-r--r--server/sonar-telemetry/src/test/java/org/sonar/telemetry/TelemetryDaemonTest.java (renamed from server/sonar-telemetry/src/test/java/org/sonar/telemetry/legacy/TelemetryDaemonTest.java)58
-rw-r--r--server/sonar-telemetry/src/test/java/org/sonar/telemetry/TelemetryDataTypeTest.java35
-rw-r--r--server/sonar-telemetry/src/test/java/org/sonar/telemetry/legacy/CloudUsageDataProviderTest.java50
-rw-r--r--server/sonar-telemetry/src/test/java/org/sonar/telemetry/legacy/TelemetryDataJsonWriterTest.java78
-rw-r--r--server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/TelemetryMetricsMapperTest.java110
-rw-r--r--server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/TestTelemetryBean.java72
-rw-r--r--server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/schema/BaseMessageTest.java105
-rw-r--r--server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/schema/InstallationMetricTest.java45
-rw-r--r--server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/schema/LanguageMetricTest.java41
-rw-r--r--server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/schema/ProjectMetricTest.java47
-rw-r--r--server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/schema/UserMetricTest.java47
-rw-r--r--server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/util/MessageSerializerTest.java247
-rw-r--r--server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/util/SentMetricsStorageTest.java95
-rw-r--r--server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java19
-rw-r--r--server/sonar-webserver/src/main/java/org/sonar/server/platform/telemetry/TelemetryNclocProvider.java (renamed from server/sonar-telemetry/src/main/java/org/sonar/telemetry/TelemetryNclocProvider.java)6
-rw-r--r--server/sonar-webserver/src/test/java/org/sonar/server/platform/telemetry/TelemetryNclocProviderTest.java (renamed from server/sonar-telemetry/src/test/java/org/sonar/telemetry/TelemetryNclocProviderTest.java)6
61 files changed, 2437 insertions, 151 deletions
diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java b/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java
index 6a33f544b46..32bd5f7e330 100644
--- a/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java
+++ b/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java
@@ -112,6 +112,7 @@ public final class SqTables {
"scm_accounts",
"session_tokens",
"snapshots",
+ "telemetry_metrics_sent",
"users",
"user_dismissed_messages",
"user_roles",
diff --git a/server/sonar-db-dao/src/it/java/org/sonar/db/telemetry/TelemetryMetricsSentDaoIT.java b/server/sonar-db-dao/src/it/java/org/sonar/db/telemetry/TelemetryMetricsSentDaoIT.java
new file mode 100644
index 00000000000..cab1919d82f
--- /dev/null
+++ b/server/sonar-db-dao/src/it/java/org/sonar/db/telemetry/TelemetryMetricsSentDaoIT.java
@@ -0,0 +1,79 @@
+/*
+ * 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.db.telemetry;
+
+import java.util.List;
+import java.util.stream.IntStream;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.sonar.api.impl.utils.TestSystem2;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.groups.Tuple.tuple;
+
+class TelemetryMetricsSentDaoIT {
+
+ @RegisterExtension
+ private final DbTester db = DbTester.create(System2.INSTANCE);
+ private static final long NOW = 1L;
+ private final TestSystem2 system2 = new TestSystem2().setNow(NOW);
+ private final DbSession dbSession = db.getSession();
+ private final TelemetryMetricsSentDao underTest = new TelemetryMetricsSentDao(system2);
+
+ @Test
+ void selectAll_shouldReturnAllInsertedData() {
+ List<TelemetryMetricsSentDto> dtos = IntStream.range(0, 10)
+ .mapToObj(i -> TelemetryMetricsSentTesting.newTelemetryMetricsSentDto())
+ .toList();
+ dtos.forEach(metricDto -> db.getDbClient().telemetryMetricsSentDao().insert(db.getSession(), metricDto));
+ db.getSession().commit();
+
+ assertThat(underTest.selectAll(dbSession))
+ .extracting(TelemetryMetricsSentDto::getMetricKey, TelemetryMetricsSentDto::getDimension, TelemetryMetricsSentDto::getLastSent)
+ .containsExactlyInAnyOrderElementsOf(
+ dtos.stream()
+ .map(metricDto -> tuple(
+ metricDto.getMetricKey(),
+ metricDto.getDimension(),
+ metricDto.getLastSent()))
+ .toList()
+ );
+ }
+
+ @Test
+ void upsert_shouldUpdateOnly() {
+ TelemetryMetricsSentDto dto = TelemetryMetricsSentTesting.newTelemetryMetricsSentDto();
+ underTest.insert(dbSession, dto);
+
+ system2.setNow(NOW + 1);
+ underTest.update(dbSession, dto);
+ List<TelemetryMetricsSentDto> dtos = underTest.selectAll(dbSession);
+
+ assertThat(dtos).hasSize(1);
+ TelemetryMetricsSentDto result = dtos.get(0);
+ assertThat(result)
+ .extracting(TelemetryMetricsSentDto::getMetricKey, TelemetryMetricsSentDto::getDimension, TelemetryMetricsSentDto::getLastSent)
+ .containsExactly(dto.getMetricKey(), dto.getDimension(), NOW + 1);
+ }
+
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java b/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java
index 83ef19a5185..26e1187218d 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java
@@ -92,6 +92,7 @@ import org.sonar.db.schemamigration.SchemaMigrationDao;
import org.sonar.db.scim.ScimGroupDao;
import org.sonar.db.scim.ScimUserDao;
import org.sonar.db.source.FileSourceDao;
+import org.sonar.db.telemetry.TelemetryMetricsSentDao;
import org.sonar.db.user.ExternalGroupDao;
import org.sonar.db.user.GroupDao;
import org.sonar.db.user.GroupMembershipDao;
@@ -186,6 +187,7 @@ public class DaoModule extends Module {
ScimUserDao.class,
SnapshotDao.class,
SessionTokensDao.class,
+ TelemetryMetricsSentDao.class,
UserDao.class,
UserDismissedMessagesDao.class,
UserGroupDao.class,
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java b/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java
index a670f4fbca1..c336b040b8c 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java
@@ -92,6 +92,7 @@ import org.sonar.db.schemamigration.SchemaMigrationDao;
import org.sonar.db.scim.ScimGroupDao;
import org.sonar.db.scim.ScimUserDao;
import org.sonar.db.source.FileSourceDao;
+import org.sonar.db.telemetry.TelemetryMetricsSentDao;
import org.sonar.db.user.ExternalGroupDao;
import org.sonar.db.user.GroupDao;
import org.sonar.db.user.GroupMembershipDao;
@@ -195,6 +196,7 @@ public class DbClient {
private final RuleChangeDao ruleChangeDao;
private final ProjectExportDao projectExportDao;
private final IssueFixedDao issueFixedDao;
+ private final TelemetryMetricsSentDao telemetryMetricsSentDao;
public DbClient(Database database, MyBatis myBatis, DBSessions dbSessions, Dao... daos) {
this.database = database;
@@ -288,6 +290,7 @@ public class DbClient {
ruleChangeDao = getDao(map, RuleChangeDao.class);
projectExportDao = getDao(map, ProjectExportDao.class);
issueFixedDao = getDao(map, IssueFixedDao.class);
+ telemetryMetricsSentDao = getDao(map, TelemetryMetricsSentDao.class);
}
public DbSession openSession(boolean batch) {
@@ -342,6 +345,10 @@ public class DbClient {
return issueFixedDao;
}
+ public TelemetryMetricsSentDao telemetryMetricsSentDao() {
+ return telemetryMetricsSentDao;
+ }
+
public QualityProfileDao qualityProfileDao() {
return qualityProfileDao;
}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java
index 28b941519ce..35162ba115e 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java
@@ -157,6 +157,7 @@ import org.sonar.db.schemamigration.SchemaMigrationMapper;
import org.sonar.db.scim.ScimGroupMapper;
import org.sonar.db.scim.ScimUserMapper;
import org.sonar.db.source.FileSourceMapper;
+import org.sonar.db.telemetry.TelemetryMetricsSentMapper;
import org.sonar.db.user.ExternalGroupDto;
import org.sonar.db.user.ExternalGroupMapper;
import org.sonar.db.user.GroupDto;
@@ -343,6 +344,7 @@ public class MyBatis {
ScimUserMapper.class,
SessionTokenMapper.class,
SnapshotMapper.class,
+ TelemetryMetricsSentMapper.class,
UserDismissedMessagesMapper.class,
UserGroupMapper.class,
UserMapper.class,
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/telemetry/TelemetryMetricsSentDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/telemetry/TelemetryMetricsSentDao.java
new file mode 100644
index 00000000000..5c78c7df88e
--- /dev/null
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/telemetry/TelemetryMetricsSentDao.java
@@ -0,0 +1,56 @@
+/*
+ * 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.db.telemetry;
+
+import java.util.List;
+import org.sonar.api.utils.System2;
+import org.sonar.db.Dao;
+import org.sonar.db.DbSession;
+
+public class TelemetryMetricsSentDao implements Dao {
+
+ private final System2 system2;
+
+ public TelemetryMetricsSentDao(System2 system2) {
+ this.system2 = system2;
+ }
+
+ public List<TelemetryMetricsSentDto> selectAll(DbSession session) {
+ return mapper(session).selectAll();
+ }
+
+ public TelemetryMetricsSentDto insert(DbSession session, TelemetryMetricsSentDto dto) {
+ mapper(session).upsert(dto);
+
+ return dto;
+ }
+
+ public void update(DbSession dbSession, TelemetryMetricsSentDto telemetryMetricsSentDto) {
+ long now = system2.now();
+ telemetryMetricsSentDto.setLastSent(now);
+ mapper(dbSession).upsert(telemetryMetricsSentDto);
+ }
+
+ private static TelemetryMetricsSentMapper mapper(DbSession session) {
+ return session.getMapper(TelemetryMetricsSentMapper.class);
+ }
+
+
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/telemetry/TelemetryMetricsSentDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/telemetry/TelemetryMetricsSentDto.java
new file mode 100644
index 00000000000..4a6fb5b8b9c
--- /dev/null
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/telemetry/TelemetryMetricsSentDto.java
@@ -0,0 +1,52 @@
+/*
+ * 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.db.telemetry;
+
+public class TelemetryMetricsSentDto {
+
+ private final String metricKey;
+
+ private final String dimension;
+
+ private long lastSent;
+
+ public TelemetryMetricsSentDto(String metricKey, String dimension) {
+ this.metricKey = metricKey;
+ this.dimension = dimension;
+ }
+
+ public String getMetricKey() {
+ return metricKey;
+ }
+
+ public String getDimension() {
+ return dimension;
+ }
+
+ public Long getLastSent() {
+ return lastSent;
+ }
+
+ public TelemetryMetricsSentDto setLastSent(long lastSent) {
+ this.lastSent = lastSent;
+ return this;
+ }
+
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/telemetry/TelemetryMetricsSentMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/telemetry/TelemetryMetricsSentMapper.java
new file mode 100644
index 00000000000..cd670c3d982
--- /dev/null
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/telemetry/TelemetryMetricsSentMapper.java
@@ -0,0 +1,30 @@
+/*
+ * 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.db.telemetry;
+
+import java.util.List;
+
+public interface TelemetryMetricsSentMapper {
+
+ List<TelemetryMetricsSentDto> selectAll();
+
+ void upsert(TelemetryMetricsSentDto dto);
+
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/telemetry/package-info.java b/server/sonar-db-dao/src/main/java/org/sonar/db/telemetry/package-info.java
new file mode 100644
index 00000000000..3aaddff89e3
--- /dev/null
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/telemetry/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.db.telemetry;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/telemetry/TelemetryMetricsSentMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/telemetry/TelemetryMetricsSentMapper.xml
new file mode 100644
index 00000000000..40ca25232d4
--- /dev/null
+++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/telemetry/TelemetryMetricsSentMapper.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.telemetry.TelemetryMetricsSentMapper">
+
+ <sql id="columns">
+ m.metric_key as metricKey,
+ m.dimension as dimension,
+ m.last_sent as lastSent
+ </sql>
+
+ <select id="selectAll" resultType="org.sonar.db.telemetry.TelemetryMetricsSentDto">
+ SELECT
+ <include refid="columns"/>
+ FROM telemetry_metrics_sent m
+ </select>
+
+ <insert id="upsert" parameterType="org.sonar.db.telemetry.TelemetryMetricsSentDto" useGeneratedKeys="false" databaseId="postgresql">
+ INSERT INTO telemetry_metrics_sent (
+ metric_key, dimension, last_sent
+ ) VALUES (
+ #{metricKey, jdbcType=VARCHAR}, #{dimension, jdbcType=VARCHAR}, #{lastSent, jdbcType=BIGINT}
+ )
+ ON CONFLICT (metric_key, dimension) DO UPDATE SET
+ last_sent = EXCLUDED.last_sent
+ </insert>
+
+ <insert id="upsert" parameterType="org.sonar.db.telemetry.TelemetryMetricsSentDto" useGeneratedKeys="false" databaseId="h2">
+ MERGE INTO telemetry_metrics_sent AS target
+ USING (VALUES (
+ #{metricKey, jdbcType=VARCHAR}, #{dimension, jdbcType=VARCHAR}, #{lastSent, jdbcType=BIGINT}
+ )) AS source (metric_key, dimension, last_sent)
+ ON target.metric_key = source.metric_key AND target.dimension = source.dimension
+ WHEN MATCHED THEN
+ UPDATE SET last_sent = source.last_sent
+ WHEN NOT MATCHED THEN
+ INSERT (metric_key, dimension, last_sent)
+ VALUES (source.metric_key, source.dimension, source.last_sent)
+ </insert>
+
+ <insert id="upsert" parameterType="org.sonar.db.telemetry.TelemetryMetricsSentDto" useGeneratedKeys="false" databaseId="oracle">
+ MERGE INTO telemetry_metrics_sent target
+ USING (
+ SELECT #{metricKey} AS metric_key,
+ #{dimension} AS dimension,
+ #{lastSent} AS last_sent
+ FROM dual
+ ) source
+ ON (target.metric_key = source.metric_key AND target.dimension = source.dimension)
+ WHEN MATCHED THEN
+ UPDATE SET target.last_sent = source.last_sent
+ WHEN NOT MATCHED THEN
+ INSERT (metric_key, dimension, last_sent)
+ VALUES (source.metric_key, source.dimension, source.last_sent)
+ </insert>
+
+ <insert id="upsert" parameterType="org.sonar.db.telemetry.TelemetryMetricsSentDto" useGeneratedKeys="false" databaseId="mssql">
+ MERGE INTO telemetry_metrics_sent AS target
+ USING (
+ SELECT
+ #{metricKey} AS metric_key,
+ #{dimension} AS dimension,
+ #{lastSent} AS last_sent
+ ) AS source (metric_key, dimension, last_sent)
+ ON (target.metric_key = source.metric_key AND target.dimension = source.dimension)
+ WHEN MATCHED THEN
+ UPDATE SET target.last_sent = source.last_sent
+ WHEN NOT MATCHED THEN
+ INSERT (metric_key, dimension, last_sent)
+ VALUES (source.metric_key, source.dimension, source.last_sent);
+ </insert>
+
+</mapper>
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/telemetry/TelemetryMetricsSentDtoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/telemetry/TelemetryMetricsSentDtoTest.java
new file mode 100644
index 00000000000..362de2eb635
--- /dev/null
+++ b/server/sonar-db-dao/src/test/java/org/sonar/db/telemetry/TelemetryMetricsSentDtoTest.java
@@ -0,0 +1,39 @@
+/*
+ * 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.db.telemetry;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class TelemetryMetricsSentDtoTest {
+
+ @Test
+ void testGetters() {
+ TelemetryMetricsSentDto metricDto = new TelemetryMetricsSentDto(
+ "key", "installation"
+ ).setLastSent(1L);
+
+ assertThat(metricDto.getMetricKey()).isEqualTo("key");
+ assertThat(metricDto.getDimension()).isEqualTo("installation");
+ assertThat(metricDto.getLastSent()).isEqualTo(1L);
+ }
+
+}
diff --git a/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/telemetry/TelemetryMetricsSentTesting.java b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/telemetry/TelemetryMetricsSentTesting.java
new file mode 100644
index 00000000000..2475d46f778
--- /dev/null
+++ b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/telemetry/TelemetryMetricsSentTesting.java
@@ -0,0 +1,40 @@
+/*
+ * 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.db.telemetry;
+
+import java.security.SecureRandom;
+import java.util.Random;
+import org.apache.commons.lang3.RandomStringUtils;
+
+public class TelemetryMetricsSentTesting {
+
+ private static final Random RANDOM = new SecureRandom();
+
+ private TelemetryMetricsSentTesting() {
+ // static stuff only
+ }
+
+ public static TelemetryMetricsSentDto newTelemetryMetricsSentDto() {
+ return new TelemetryMetricsSentDto(
+ RandomStringUtils.randomAlphanumeric(40), // key
+ RandomStringUtils.randomAlphanumeric(30) // dimension
+ ).setLastSent(RANDOM.nextLong());
+ }
+}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java b/server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java
index 3a49ed4b8e1..c3f4063ffb6 100644
--- a/server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java
+++ b/server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java
@@ -175,6 +175,7 @@ public class ProcessProperties {
SONAR_TELEMETRY_ENABLE("sonar.telemetry.enable", "true"),
SONAR_TELEMETRY_URL("sonar.telemetry.url", "https://telemetry.sonarsource.com/sonarqube"),
+ SONAR_TELEMETRY_METRICS_URL("sonar.telemetry.metrics.url", "https://telemetry.sonarsource.com/sonarqube/metrics"),
SONAR_TELEMETRY_FREQUENCY_IN_SECONDS("sonar.telemetry.frequencyInSeconds", "10800"),
SONAR_TELEMETRY_COMPRESSION("sonar.telemetry.compression", "true"),
diff --git a/server/sonar-server-common/build.gradle b/server/sonar-server-common/build.gradle
index 3ad43d2a9c0..083eb70991d 100644
--- a/server/sonar-server-common/build.gradle
+++ b/server/sonar-server-common/build.gradle
@@ -8,6 +8,7 @@ sonar {
dependencies {
// please keep the list grouped by configuration and ordered by name
+ implementation 'com.fasterxml.jackson.core:jackson-databind'
api 'commons-io:commons-io'
api 'com.google.guava:guava'
diff --git a/server/sonar-telemetry/build.gradle b/server/sonar-telemetry/build.gradle
index 8bbc9e29e4c..4ab65454207 100644
--- a/server/sonar-telemetry/build.gradle
+++ b/server/sonar-telemetry/build.gradle
@@ -11,7 +11,8 @@ dependencies {
testImplementation 'com.squareup.okhttp3:mockwebserver'
testImplementation 'com.tngtech.java:junit-dataprovider'
testImplementation 'org.assertj:assertj-core'
- testImplementation("org.junit.jupiter:junit-jupiter")
+ testImplementation 'org.junit.jupiter:junit-jupiter-api'
+ testImplementation 'org.junit.jupiter:junit-jupiter-params'
testImplementation 'org.mockito:mockito-core'
testImplementation 'org.sonarsource.api.plugin:sonar-plugin-api-test-fixtures'
testImplementation project(':sonar-testing-harness')
@@ -24,7 +25,6 @@ dependencies {
api project(':sonar-core')
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
- testRuntimeOnly 'org.junit.vintage:junit-vintage-engine'
}
tasks.test {
diff --git a/server/sonar-telemetry/src/it/java/org/sonar/telemetry/legacy/TelemetryDataLoaderImplIT.java b/server/sonar-telemetry/src/it/java/org/sonar/telemetry/legacy/TelemetryDataLoaderImplIT.java
index 4fa82ad1b97..fa51a39d5a1 100644
--- a/server/sonar-telemetry/src/it/java/org/sonar/telemetry/legacy/TelemetryDataLoaderImplIT.java
+++ b/server/sonar-telemetry/src/it/java/org/sonar/telemetry/legacy/TelemetryDataLoaderImplIT.java
@@ -67,6 +67,8 @@ import org.sonar.server.property.MapInternalProperties;
import org.sonar.server.qualitygate.QualityGateCaycChecker;
import org.sonar.server.qualitygate.QualityGateFinder;
import org.sonar.server.qualityprofile.QProfileComparison;
+import org.sonar.telemetry.FakeServer;
+import org.sonar.telemetry.TelemetryDaemon;
import org.sonar.telemetry.legacy.TelemetryData.Branch;
import org.sonar.telemetry.legacy.TelemetryData.CloudUsage;
import org.sonar.telemetry.legacy.TelemetryData.NewCodeDefinition;
diff --git a/server/sonar-telemetry/src/it/java/org/sonar/telemetry/metrics/TelemetryMetricsLoaderIT.java b/server/sonar-telemetry/src/it/java/org/sonar/telemetry/metrics/TelemetryMetricsLoaderIT.java
new file mode 100644
index 00000000000..91743dc5fb0
--- /dev/null
+++ b/server/sonar-telemetry/src/it/java/org/sonar/telemetry/metrics/TelemetryMetricsLoaderIT.java
@@ -0,0 +1,69 @@
+/*
+ * 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.metrics;
+
+import java.util.List;
+import org.junit.Rule;
+import org.junit.jupiter.api.Test;
+import org.sonar.api.impl.utils.TestSystem2;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.db.DbTester;
+import org.sonar.telemetry.Dimension;
+import org.sonar.telemetry.TelemetryDataProvider;
+import org.sonar.telemetry.FakeServer;
+import org.sonar.telemetry.metrics.schema.BaseMessage;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class TelemetryMetricsLoaderIT {
+
+ private static final String SOME_UUID = "some-uuid";
+ private static final Long NOW = 100_000_000L;
+ private static final String SERVER_ID = "AU-TpxcB-iU5OvuD2FL7";
+ private final TestSystem2 system2 = new TestSystem2().setNow(NOW);
+
+ @Rule
+ public DbTester db = DbTester.create(system2);
+ private final FakeServer server = new FakeServer();
+ private final UuidFactory uuidFactory = mock(UuidFactory.class);
+ private final List<TelemetryDataProvider<?>> providers = List.of(new TestTelemetryBean(Dimension.INSTALLATION), new TestTelemetryBean(Dimension.USER));
+ private final TelemetryMetricsLoader underTest = new TelemetryMetricsLoader(server, db.getDbClient(), uuidFactory, providers);
+
+ @Test
+ void sendTelemetryData() {
+ when(uuidFactory.create()).thenReturn(SOME_UUID);
+
+ server.setId(SERVER_ID);
+ TelemetryMetricsLoader.Context context = underTest.loadData();
+
+ assertThat(context.getMessages()).hasSize(2);
+
+ assertThat(context.getMessages())
+ .extracting(BaseMessage::getMessageUuid, BaseMessage::getInstallationId, BaseMessage::getDimension)
+ .containsExactlyInAnyOrder(
+ tuple(SOME_UUID, SERVER_ID, Dimension.INSTALLATION),
+ tuple(SOME_UUID, SERVER_ID, Dimension.USER)
+ );
+ }
+
+}
diff --git a/server/sonar-telemetry/src/it/java/org/sonar/telemetry/legacy/project/ProjectCppAutoconfigTelemetryProviderIT.java b/server/sonar-telemetry/src/it/java/org/sonar/telemetry/project/ProjectCppAutoconfigTelemetryProviderIT.java
index aa53b737069..1b9e7e2d0b3 100644
--- a/server/sonar-telemetry/src/it/java/org/sonar/telemetry/legacy/project/ProjectCppAutoconfigTelemetryProviderIT.java
+++ b/server/sonar-telemetry/src/it/java/org/sonar/telemetry/project/ProjectCppAutoconfigTelemetryProviderIT.java
@@ -17,7 +17,7 @@
* 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.legacy.project;
+package org.sonar.telemetry.project;
import java.util.Map;
import java.util.function.Consumer;
diff --git a/server/sonar-telemetry/src/it/java/org/sonar/telemetry/legacy/user/TelemetryUserEnabledProviderIT.java b/server/sonar-telemetry/src/it/java/org/sonar/telemetry/user/TelemetryUserEnabledProviderIT.java
index 255ebefec23..aa8c502f110 100644
--- a/server/sonar-telemetry/src/it/java/org/sonar/telemetry/legacy/user/TelemetryUserEnabledProviderIT.java
+++ b/server/sonar-telemetry/src/it/java/org/sonar/telemetry/user/TelemetryUserEnabledProviderIT.java
@@ -17,17 +17,19 @@
* 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.legacy.user;
+package org.sonar.telemetry.user;
import java.util.Map;
-import org.assertj.core.api.Assertions;
import org.junit.Rule;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.sonar.api.impl.utils.AlwaysIncreasingSystem2;
import org.sonar.api.utils.System2;
import org.sonar.db.DbTester;
-import org.sonar.telemetry.user.TelemetryUserEnabledProvider;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.util.DigestUtil;
+
+import static org.assertj.core.api.Assertions.assertThat;
class TelemetryUserEnabledProviderIT {
@@ -48,7 +50,7 @@ class TelemetryUserEnabledProviderIT {
void getUuidValues_whenNoUsersInDatabase_shouldReturnEmptyMap() {
Map<String, Boolean> uuidValues = underTest.getUuidValues();
- Assertions.assertThat(uuidValues).isEmpty();
+ assertThat(uuidValues).isEmpty();
}
@Test
@@ -59,9 +61,9 @@ class TelemetryUserEnabledProviderIT {
Map<String, Boolean> uuidValues = underTest.getUuidValues();
- Assertions.assertThat(uuidValues).hasSize(2);
- Assertions.assertThat(uuidValues.values().stream().filter(Boolean::booleanValue)).hasSize(1);
- Assertions.assertThat(uuidValues.values().stream().filter(b -> !b)).hasSize(1);
+ assertThat(uuidValues).hasSize(2);
+ assertThat(uuidValues.values().stream().filter(Boolean::booleanValue)).hasSize(1);
+ assertThat(uuidValues.values().stream().filter(b -> !b)).hasSize(1);
}
@Test
@@ -73,7 +75,21 @@ class TelemetryUserEnabledProviderIT {
Map<String, Boolean> uuidValues = underTest.getUuidValues();
- Assertions.assertThat(uuidValues).hasSize(10);
- Assertions.assertThat(uuidValues.values().stream().filter(Boolean::booleanValue)).hasSize(10);
+ assertThat(uuidValues).hasSize(10);
+ assertThat(uuidValues.values().stream().filter(Boolean::booleanValue)).hasSize(10);
}
+
+ @Test
+ void getUuidValues_shouldAnonymizeUserUuids() {
+ UserDto userDto1 = db.users().insertUser();
+ UserDto userDto2 = db.users().insertUser();
+ db.getSession().commit();
+
+ Map<String, Boolean> uuidValues = underTest.getUuidValues();
+
+ String anonymizedUser1 = DigestUtil.sha3_224Hex(userDto1.getUuid());
+ String anonymizedUser2 = DigestUtil.sha3_224Hex(userDto2.getUuid());
+ assertThat(uuidValues.keySet()).containsExactlyInAnyOrder(anonymizedUser1, anonymizedUser2);
+ }
+
}
diff --git a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/Dimension.java b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/Dimension.java
index d301b49e14b..b30efed0fca 100644
--- a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/Dimension.java
+++ b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/Dimension.java
@@ -19,11 +19,36 @@
*/
package org.sonar.telemetry;
+import com.fasterxml.jackson.annotation.JsonValue;
+
/**
* Represents the dimension of the data provided by a {@link TelemetryDataProvider}.
* {@link Dimension#PROJECT}, {@link Dimension#LANGUAGE} and {@link Dimension#USER} should not provide aggregated data.
* For aggregated data (i.e. average number of lines of code per project), use #INSTALLATION.
*/
public enum Dimension {
- INSTALLATION, PROJECT, USER, LANGUAGE
+ INSTALLATION("installation"),
+ USER("user"),
+ PROJECT("project"),
+ LANGUAGE("language");
+
+ private final String value;
+
+ Dimension(String value) {
+ this.value = value;
+ }
+
+ @JsonValue
+ public String getValue() {
+ return value;
+ }
+
+ public static Dimension fromValue(String value) {
+ for (Dimension dimension : Dimension.values()) {
+ if (dimension.value.equalsIgnoreCase(value)) {
+ return dimension;
+ }
+ }
+ throw new IllegalArgumentException("Unknown dimension value: " + value);
+ }
}
diff --git a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/Granularity.java b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/Granularity.java
index c342ef2f285..7c350868406 100644
--- a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/Granularity.java
+++ b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/Granularity.java
@@ -19,11 +19,26 @@
*/
package org.sonar.telemetry;
+import com.fasterxml.jackson.annotation.JsonValue;
+
/**
* Represent the granularity of the data provided by a {@link TelemetryDataProvider}. This both defines the time period between to pushes to
* telemetry server for a given metric and the time period that the data represents.
* Modifying this enum needs to be discussed beforehand with Data Platform team.
*/
public enum Granularity {
- DAILY, WEEKLY, MONTHLY;
+ DAILY("daily"),
+ WEEKLY("weekly"),
+ MONTHLY("monthly");
+
+ private final String value;
+
+ Granularity(String value) {
+ this.value = value;
+ }
+
+ @JsonValue
+ public String getValue() {
+ return value;
+ }
}
diff --git a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/legacy/TelemetryClient.java b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/TelemetryClient.java
index 7b4b85a9da0..92fa3599f3a 100644
--- a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/legacy/TelemetryClient.java
+++ b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/TelemetryClient.java
@@ -17,7 +17,7 @@
* 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.legacy;
+package org.sonar.telemetry;
import java.io.IOException;
import okhttp3.Call;
@@ -29,11 +29,11 @@ import okhttp3.Response;
import okio.BufferedSink;
import okio.GzipSink;
import okio.Okio;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.sonar.api.Startable;
import org.sonar.api.config.Configuration;
import org.sonar.api.server.ServerSide;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_COMPRESSION;
import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_URL;
diff --git a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/legacy/TelemetryDaemon.java b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/TelemetryDaemon.java
index 8868744eb53..83b605e7a10 100644
--- a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/legacy/TelemetryDaemon.java
+++ b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/TelemetryDaemon.java
@@ -17,7 +17,7 @@
* 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.legacy;
+package org.sonar.telemetry;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.io.IOException;
@@ -27,15 +27,23 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.sonar.api.config.Configuration;
import org.sonar.api.server.ServerSide;
import org.sonar.api.utils.System2;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
import org.sonar.server.property.InternalProperties;
import org.sonar.server.util.AbstractStoppableScheduledExecutorServiceImpl;
import org.sonar.server.util.GlobalLockManager;
+import org.sonar.telemetry.legacy.TelemetryData;
+import org.sonar.telemetry.legacy.TelemetryDataJsonWriter;
+import org.sonar.telemetry.legacy.TelemetryDataLoader;
+import org.sonar.telemetry.metrics.TelemetryMetricsLoader;
+import org.sonar.telemetry.metrics.schema.BaseMessage;
+import org.sonar.telemetry.metrics.util.MessageSerializer;
import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_ENABLE;
import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_FREQUENCY_IN_SECONDS;
@@ -43,6 +51,8 @@ import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_URL;
@ServerSide
public class TelemetryDaemon extends AbstractStoppableScheduledExecutorServiceImpl<ScheduledExecutorService> {
+ public static final String I_PROP_MESSAGE_SEQUENCE = "telemetry.messageSeq";
+
private static final String THREAD_NAME_PREFIX = "sq-telemetry-service-";
private static final int ONE_DAY = 24 * 60 * 60 * 1_000;
private static final String I_PROP_LAST_PING = "telemetry.lastPing";
@@ -50,7 +60,6 @@ public class TelemetryDaemon extends AbstractStoppableScheduledExecutorServiceIm
private static final String LOCK_NAME = "TelemetryStat";
private static final Logger LOG = LoggerFactory.getLogger(TelemetryDaemon.class);
private static final String LOCK_DELAY_SEC = "sonar.telemetry.lock.delay";
- static final String I_PROP_MESSAGE_SEQUENCE = "telemetry.messageSeq";
private final TelemetryDataLoader dataLoader;
private final TelemetryDataJsonWriter dataJsonWriter;
@@ -59,9 +68,11 @@ public class TelemetryDaemon extends AbstractStoppableScheduledExecutorServiceIm
private final Configuration config;
private final InternalProperties internalProperties;
private final System2 system2;
+ private final TelemetryMetricsLoader telemetryMetricsLoader;
+ private final DbClient dbClient;
public TelemetryDaemon(TelemetryDataLoader dataLoader, TelemetryDataJsonWriter dataJsonWriter, TelemetryClient telemetryClient, Configuration config,
- InternalProperties internalProperties, GlobalLockManager lockManager, System2 system2) {
+ InternalProperties internalProperties, GlobalLockManager lockManager, System2 system2, TelemetryMetricsLoader telemetryMetricsLoader, DbClient dbClient) {
super(Executors.newSingleThreadScheduledExecutor(newThreadFactory()));
this.dataLoader = dataLoader;
this.dataJsonWriter = dataJsonWriter;
@@ -70,6 +81,8 @@ public class TelemetryDaemon extends AbstractStoppableScheduledExecutorServiceIm
this.internalProperties = internalProperties;
this.lockManager = lockManager;
this.system2 = system2;
+ this.telemetryMetricsLoader = telemetryMetricsLoader;
+ this.dbClient = dbClient;
}
@Override
@@ -110,7 +123,9 @@ public class TelemetryDaemon extends AbstractStoppableScheduledExecutorServiceIm
long now = system2.now();
if (shouldUploadStatistics(now)) {
- uploadStatistics();
+ uploadMetrics();
+ uploadLegacyTelemetry();
+
updateTelemetryProps(now);
}
} catch (Exception e) {
@@ -143,7 +158,20 @@ public class TelemetryDaemon extends AbstractStoppableScheduledExecutorServiceIm
telemetryClient.optOut(json.toString());
}
- private void uploadStatistics() throws IOException {
+ private void uploadMetrics() throws IOException {
+ TelemetryMetricsLoader.Context context = telemetryMetricsLoader.loadData();
+ for (BaseMessage message : context.getMessages()) {
+ String jsonString = MessageSerializer.serialize(message);
+ telemetryClient.upload(jsonString);
+ }
+
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ context.getMetricsToUpdate().forEach(toUpdate -> dbClient.telemetryMetricsSentDao().update(dbSession, toUpdate));
+ dbSession.commit();
+ }
+ }
+
+ private void uploadLegacyTelemetry() throws IOException {
TelemetryData statistics = dataLoader.load();
StringWriter jsonString = new StringWriter();
try (JsonWriter json = JsonWriter.of(jsonString)) {
diff --git a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/TelemetryDataType.java b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/TelemetryDataType.java
index 99ddd8c3768..a59bd03e34e 100644
--- a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/TelemetryDataType.java
+++ b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/TelemetryDataType.java
@@ -19,10 +19,26 @@
*/
package org.sonar.telemetry;
+import com.fasterxml.jackson.annotation.JsonValue;
+
/**
* Represents the type of the data provided by a {@link TelemetryDataProvider}.
* Modifying this enum needs to be discussed beforehand with Data Platform team.
*/
public enum TelemetryDataType {
- BOOLEAN, STRING, INTEGER, FLOAT
+ BOOLEAN("boolean"),
+ STRING("string"),
+ INTEGER("integer"),
+ FLOAT("float");
+
+ private final String value;
+
+ TelemetryDataType(String value) {
+ this.value = value;
+ }
+
+ @JsonValue
+ public String getValue() {
+ return value;
+ }
}
diff --git a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/legacy/TelemetryData.java b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/legacy/TelemetryData.java
index 188df1427ca..fdd4f70223a 100644
--- a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/legacy/TelemetryData.java
+++ b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/legacy/TelemetryData.java
@@ -172,7 +172,7 @@ public class TelemetryData {
return qualityProfiles;
}
- static Builder builder() {
+ public static Builder builder() {
return new Builder();
}
@@ -188,7 +188,7 @@ public class TelemetryData {
return newCodeDefinitions;
}
- static class Builder {
+ public static class Builder {
private String serverId;
private String version;
private Long messageSequenceNumber;
@@ -215,31 +215,31 @@ public class TelemetryData {
private List<QualityProfile> qualityProfiles;
private int ncdId;
- private Builder() {
+ public Builder() {
// enforce static factory method
}
- Builder setServerId(String serverId) {
+ public Builder setServerId(String serverId) {
this.serverId = serverId;
return this;
}
- Builder setVersion(String version) {
+ public Builder setVersion(String version) {
this.version = version;
return this;
}
- Builder setMessageSequenceNumber(@Nullable Long messageSequenceNumber) {
+ public Builder setMessageSequenceNumber(@Nullable Long messageSequenceNumber) {
this.messageSequenceNumber = messageSequenceNumber;
return this;
}
- Builder setPlugins(Map<String, String> plugins) {
+ public Builder setPlugins(Map<String, String> plugins) {
this.plugins = plugins;
return this;
}
- Builder setDatabase(Database database) {
+ public Builder setDatabase(Database database) {
this.database = database;
return this;
}
@@ -309,7 +309,7 @@ public class TelemetryData {
return this;
}
- TelemetryData build() {
+ public TelemetryData build() {
requireNonNullValues(serverId, version, plugins, database, messageSequenceNumber);
return new TelemetryData(this);
}
@@ -349,7 +349,7 @@ public class TelemetryData {
}
}
- record Database(String name, String version) {
+ public record Database(String name, String version) {
}
record NewCodeDefinition(String type, @Nullable String value, String scope) {
diff --git a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/legacy/TelemetryDataLoaderImpl.java b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/legacy/TelemetryDataLoaderImpl.java
index 65432e4048e..112ea8a4c13 100644
--- a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/legacy/TelemetryDataLoaderImpl.java
+++ b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/legacy/TelemetryDataLoaderImpl.java
@@ -89,7 +89,7 @@ import static org.sonar.db.newcodeperiod.NewCodePeriodType.REFERENCE_BRANCH;
import static org.sonar.server.metric.UnanalyzedLanguageMetrics.UNANALYZED_CPP_KEY;
import static org.sonar.server.metric.UnanalyzedLanguageMetrics.UNANALYZED_C_KEY;
import static org.sonar.server.qualitygate.Condition.Operator.fromDbValue;
-import static org.sonar.telemetry.legacy.TelemetryDaemon.I_PROP_MESSAGE_SEQUENCE;
+import static org.sonar.telemetry.TelemetryDaemon.I_PROP_MESSAGE_SEQUENCE;
@ServerSide
public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
diff --git a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/TelemetryMetricsLoader.java b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/TelemetryMetricsLoader.java
new file mode 100644
index 00000000000..e7cf201a73a
--- /dev/null
+++ b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/TelemetryMetricsLoader.java
@@ -0,0 +1,127 @@
+/*
+ * 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.metrics;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.sonar.api.platform.Server;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.telemetry.TelemetryMetricsSentDto;
+import org.sonar.telemetry.Dimension;
+import org.sonar.telemetry.TelemetryDataProvider;
+import org.sonar.telemetry.metrics.schema.BaseMessage;
+import org.sonar.telemetry.metrics.schema.Metric;
+import org.sonar.telemetry.metrics.util.SentMetricsStorage;
+
+public class TelemetryMetricsLoader {
+ private final Server server;
+ private final DbClient dbClient;
+ private final UuidFactory uuidFactory;
+ private final List<TelemetryDataProvider<?>> providers;
+
+ public TelemetryMetricsLoader(Server server, DbClient dbClient, UuidFactory uuidFactory, List<TelemetryDataProvider<?>> providers) {
+ this.server = server;
+ this.dbClient = dbClient;
+ this.providers = providers;
+ this.uuidFactory = uuidFactory;
+ }
+
+ public Context loadData() {
+ Context context = new Context();
+ if (this.providers.isEmpty()) {
+ return context;
+ }
+
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ List<TelemetryMetricsSentDto> metricsSentDtos = dbClient.telemetryMetricsSentDao().selectAll(dbSession);
+ SentMetricsStorage storage = new SentMetricsStorage(metricsSentDtos);
+
+ Map<Dimension, Set<Metric>> telemetryDataMap = new LinkedHashMap<>();
+ for (TelemetryDataProvider<?> provider : this.providers) {
+ boolean shouldSendMetric = storage.shouldSendMetric(provider.getDimension(), provider.getMetricKey(), provider.getGranularity());
+ if (shouldSendMetric) {
+ Set<Metric> newMetrics = TelemetryMetricsMapper.mapFromDataProvider(provider);
+ telemetryDataMap.computeIfAbsent(provider.getDimension(), k -> new LinkedHashSet<>()).addAll(newMetrics);
+
+ Optional<TelemetryMetricsSentDto> dto = storage.getMetricsSentDto(provider.getDimension(), provider.getMetricKey());
+ if (dto.isPresent()) {
+ context.addDto(dto.get());
+ } else {
+ TelemetryMetricsSentDto newDto = new TelemetryMetricsSentDto(
+ provider.getMetricKey(), provider.getDimension().getValue()
+ );
+ context.addDto(newDto);
+ }
+ }
+ }
+
+ Set<BaseMessage> baseMessages = retrieveBaseMessages(telemetryDataMap);
+ context.setBaseMessages(baseMessages);
+ return context;
+ }
+ }
+
+ private Set<BaseMessage> retrieveBaseMessages(Map<Dimension, Set<Metric>> metrics) {
+ return metrics.entrySet().stream()
+ .map(entry -> new BaseMessage.Builder()
+ .setMessageUuid(uuidFactory.create())
+ .setInstallationId(server.getId())
+ .setDimension(entry.getKey())
+ .setMetrics(entry.getValue())
+ .build())
+ .collect(Collectors.toSet());
+ }
+
+ public static class Context {
+
+ Set<BaseMessage> baseMessages;
+ List<TelemetryMetricsSentDto> metricsSentDtos;
+
+ public Context() {
+ baseMessages = new LinkedHashSet<>();
+ metricsSentDtos = new ArrayList<>();
+ }
+
+ protected void addDto(TelemetryMetricsSentDto dto) {
+ this.metricsSentDtos.add(dto);
+ }
+
+ protected void setBaseMessages(Set<BaseMessage> baseMessages){
+ this.baseMessages = baseMessages;
+ }
+
+ public Set<BaseMessage> getMessages() {
+ return baseMessages;
+ }
+
+ public List<TelemetryMetricsSentDto> getMetricsToUpdate() {
+ return metricsSentDtos;
+ }
+ }
+
+}
diff --git a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/TelemetryMetricsMapper.java b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/TelemetryMetricsMapper.java
new file mode 100644
index 00000000000..645798bf721
--- /dev/null
+++ b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/TelemetryMetricsMapper.java
@@ -0,0 +1,93 @@
+/*
+ * 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.metrics;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.sonar.telemetry.TelemetryDataProvider;
+import org.sonar.telemetry.metrics.schema.InstallationMetric;
+import org.sonar.telemetry.metrics.schema.LanguageMetric;
+import org.sonar.telemetry.metrics.schema.Metric;
+import org.sonar.telemetry.metrics.schema.ProjectMetric;
+import org.sonar.telemetry.metrics.schema.UserMetric;
+
+public class TelemetryMetricsMapper {
+
+ private TelemetryMetricsMapper() {
+ }
+
+ public static Set<Metric> mapFromDataProvider(TelemetryDataProvider<?> provider) {
+ switch (provider.getDimension()) {
+ case INSTALLATION -> {
+ return mapInstallationMetric(provider);
+ } case PROJECT -> {
+ return mapProjectMetric(provider);
+ } case USER -> {
+ return mapUserMetric(provider);
+ } case LANGUAGE -> {
+ return mapLanguageMetric(provider);
+ } default -> throw new IllegalArgumentException("Dimension: " + provider.getDimension() + " not yet implemented.");
+ }
+ }
+
+ private static Set<Metric> mapInstallationMetric(TelemetryDataProvider<?> provider) {
+ return Collections.singleton(new InstallationMetric(
+ provider.getMetricKey(),
+ provider.getValue(),
+ provider.getType(),
+ provider.getGranularity()
+ ));
+ }
+
+ private static Set<Metric> mapUserMetric(TelemetryDataProvider<?> provider) {
+ return provider.getUuidValues().entrySet().stream()
+ .map(entry -> new UserMetric(
+ provider.getMetricKey(),
+ entry.getValue(),
+ entry.getKey(),
+ provider.getType(),
+ provider.getGranularity()
+ )).collect(Collectors.toSet());
+ }
+
+ private static Set<Metric> mapProjectMetric(TelemetryDataProvider<?> provider) {
+ return provider.getUuidValues().entrySet().stream()
+ .map(entry -> new ProjectMetric(
+ provider.getMetricKey(),
+ entry.getValue(),
+ entry.getKey(),
+ provider.getType(),
+ provider.getGranularity()
+ )).collect(Collectors.toSet());
+ }
+
+ private static Set<Metric> mapLanguageMetric(TelemetryDataProvider<?> provider) {
+ return provider.getUuidValues().entrySet().stream()
+ .map(entry -> new LanguageMetric(
+ provider.getMetricKey(),
+ entry.getValue(),
+ entry.getKey(),
+ provider.getType(),
+ provider.getGranularity()
+ )).collect(Collectors.toSet());
+ }
+
+}
diff --git a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/package-info.java b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/package-info.java
new file mode 100644
index 00000000000..fb756a49d33
--- /dev/null
+++ b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.telemetry.metrics;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/BaseMessage.java b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/BaseMessage.java
new file mode 100644
index 00000000000..2bbbbf2753f
--- /dev/null
+++ b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/BaseMessage.java
@@ -0,0 +1,98 @@
+/*
+ * 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.metrics.schema;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.Objects;
+import java.util.Set;
+import org.sonar.telemetry.Dimension;
+
+public class BaseMessage {
+ @JsonProperty("message_uuid")
+ private String messageUuid;
+
+ @JsonProperty("installation_id")
+ private String installationId;
+
+ @JsonProperty("dimension")
+ private Dimension dimension;
+
+ @JsonProperty("metric_values")
+ private Set<Metric> metrics;
+
+ protected BaseMessage(String messageUuid, String installationId, Dimension dimension, Set<Metric> metrics) {
+ this.messageUuid = messageUuid;
+ this.installationId = installationId;
+ this.dimension = dimension;
+ this.metrics = metrics;
+ }
+
+ public String getMessageUuid() {
+ return messageUuid;
+ }
+
+ public String getInstallationId() {
+ return installationId;
+ }
+
+ public Dimension getDimension() {
+ return dimension;
+ }
+
+ public Set<Metric> getMetrics() {
+ return metrics;
+ }
+
+ public static class Builder {
+ private String messageUuid;
+ private String installationId;
+ private Dimension dimension;
+ private Set<Metric> metrics;
+
+ public Builder setMessageUuid(String messageUuid) {
+ this.messageUuid = messageUuid;
+ return this;
+ }
+
+ public Builder setInstallationId(String installationId) {
+ this.installationId = installationId;
+ return this;
+ }
+
+ public Builder setDimension(Dimension dimension) {
+ this.dimension = dimension;
+ return this;
+ }
+
+ public Builder setMetrics(Set<Metric> metrics) {
+ this.metrics = metrics;
+ return this;
+ }
+
+ public BaseMessage build() {
+ Objects.requireNonNull(messageUuid, "messageUuid must be specified");
+ Objects.requireNonNull(installationId, "installationId must be specified");
+ Objects.requireNonNull(dimension, "dimension must be specified");
+ Objects.requireNonNull(metrics, "metrics must be specified");
+
+ return new BaseMessage(messageUuid, installationId, dimension, metrics);
+ }
+ }
+} \ No newline at end of file
diff --git a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/InstallationMetric.java b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/InstallationMetric.java
new file mode 100644
index 00000000000..0c3eea43c09
--- /dev/null
+++ b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/InstallationMetric.java
@@ -0,0 +1,34 @@
+/*
+ * 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.metrics.schema;
+
+import org.sonar.telemetry.Granularity;
+import org.sonar.telemetry.TelemetryDataType;
+
+public class InstallationMetric extends Metric {
+
+ public InstallationMetric(String key, Object value, TelemetryDataType type, Granularity granularity) {
+ this.key = key;
+ this.value = value;
+ this.type = type;
+ this.granularity = granularity;
+ }
+
+}
diff --git a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/LanguageMetric.java b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/LanguageMetric.java
new file mode 100644
index 00000000000..766dcf9cbe6
--- /dev/null
+++ b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/LanguageMetric.java
@@ -0,0 +1,46 @@
+/*
+ * 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.metrics.schema;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.sonar.telemetry.Granularity;
+import org.sonar.telemetry.TelemetryDataType;
+
+public class LanguageMetric extends Metric {
+
+ @JsonProperty("language")
+ private String language;
+
+ public LanguageMetric(String key, Object value, String language, TelemetryDataType type, Granularity granularity) {
+ this.key = key;
+ this.value = value;
+ this.language = language;
+ this.type = type;
+ this.granularity = granularity;
+ }
+
+ public String getLanguage() {
+ return language;
+ }
+
+ public void setLanguage(String language) {
+ this.language = language;
+ }
+}
diff --git a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/Metric.java b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/Metric.java
new file mode 100644
index 00000000000..efdf43d0dd7
--- /dev/null
+++ b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/Metric.java
@@ -0,0 +1,70 @@
+/*
+ * 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.metrics.schema;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.sonar.telemetry.Granularity;
+import org.sonar.telemetry.TelemetryDataType;
+
+public abstract class Metric {
+ @JsonProperty("key")
+ protected String key;
+
+ @JsonProperty("value")
+ protected Object value;
+
+ @JsonProperty("type")
+ protected TelemetryDataType type;
+
+ @JsonProperty("granularity")
+ protected Granularity granularity;
+
+ public String getKey() {
+ return key;
+ }
+
+ public void setKey(String key) {
+ this.key = key;
+ }
+
+ public Object getValue() {
+ return value;
+ }
+
+ public void setValue(Object value) {
+ this.value = value;
+ }
+
+ public TelemetryDataType getType() {
+ return type;
+ }
+
+ public void setType(TelemetryDataType type) {
+ this.type = type;
+ }
+
+ public Granularity getGranularity() {
+ return granularity;
+ }
+
+ public void setGranularity(Granularity granularity) {
+ this.granularity = granularity;
+ }
+}
diff --git a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/ProjectMetric.java b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/ProjectMetric.java
new file mode 100644
index 00000000000..421d2e79ca6
--- /dev/null
+++ b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/ProjectMetric.java
@@ -0,0 +1,47 @@
+/*
+ * 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.metrics.schema;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.sonar.telemetry.Granularity;
+import org.sonar.telemetry.TelemetryDataType;
+
+public class ProjectMetric extends Metric {
+
+ @JsonProperty("project_uuid")
+ private String projectUuid;
+
+ public ProjectMetric(String key, Object value, String projectUuid, TelemetryDataType type, Granularity granularity) {
+ this.key = key;
+ this.value = value;
+ this.projectUuid = projectUuid;
+ this.type = type;
+ this.granularity = granularity;
+ }
+
+ public String getProjectUuid() {
+ return projectUuid;
+ }
+
+ public void setProjectUuid(String projectUuid) {
+ this.projectUuid = projectUuid;
+ }
+
+}
diff --git a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/UserMetric.java b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/UserMetric.java
new file mode 100644
index 00000000000..a9ff05657df
--- /dev/null
+++ b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/UserMetric.java
@@ -0,0 +1,47 @@
+/*
+ * 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.metrics.schema;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.sonar.telemetry.Granularity;
+import org.sonar.telemetry.TelemetryDataType;
+
+public class UserMetric extends Metric {
+
+ @JsonProperty("user_uuid")
+ private String userUuid;
+
+ public UserMetric(String key, Object value, String userUuid, TelemetryDataType type, Granularity granularity) {
+ this.key = key;
+ this.value = value;
+ this.userUuid = userUuid;
+ this.type = type;
+ this.granularity = granularity;
+ }
+
+ public String getUserUuid() {
+ return userUuid;
+ }
+
+ public void setUserUuid(String userUuid) {
+ this.userUuid = userUuid;
+ }
+
+}
diff --git a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/package-info.java b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/package-info.java
new file mode 100644
index 00000000000..d1b9bb184e6
--- /dev/null
+++ b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.telemetry.metrics.schema;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/util/MessageSerializer.java b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/util/MessageSerializer.java
new file mode 100644
index 00000000000..a55c771552c
--- /dev/null
+++ b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/util/MessageSerializer.java
@@ -0,0 +1,42 @@
+/*
+ * 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.metrics.util;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import org.sonar.telemetry.metrics.schema.BaseMessage;
+
+public class MessageSerializer {
+
+ private MessageSerializer() {
+ throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
+ }
+
+ public static String serialize(BaseMessage message) {
+ ObjectMapper mapper = new ObjectMapper();
+ try {
+ return mapper.writeValueAsString(message);
+ } catch (IOException ioException) {
+ throw new UncheckedIOException(ioException);
+ }
+ }
+
+}
diff --git a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/util/SentMetricsStorage.java b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/util/SentMetricsStorage.java
new file mode 100644
index 00000000000..36ab85505b1
--- /dev/null
+++ b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/util/SentMetricsStorage.java
@@ -0,0 +1,78 @@
+/*
+ * 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.metrics.util;
+
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.temporal.ChronoUnit;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import org.sonar.db.telemetry.TelemetryMetricsSentDto;
+import org.sonar.telemetry.Dimension;
+import org.sonar.telemetry.Granularity;
+
+public class SentMetricsStorage {
+ private final Map<Dimension, Map<String, TelemetryMetricsSentDto>> dimensionMetricKeyMap = new EnumMap<>(Dimension.class);
+
+ public SentMetricsStorage(List<TelemetryMetricsSentDto> dtoList) {
+ dtoList.forEach(dto -> dimensionMetricKeyMap
+ .computeIfAbsent(Dimension.fromValue(dto.getDimension()), k -> new HashMap<>())
+ .put(dto.getMetricKey(), dto));
+ }
+
+ public Optional<TelemetryMetricsSentDto> getMetricsSentDto(Dimension dimension, String metricKey) {
+ Map<String, TelemetryMetricsSentDto> metricKeyMap = dimensionMetricKeyMap.get(dimension);
+ if (metricKeyMap != null && metricKeyMap.containsKey(metricKey)) {
+ return Optional.of(metricKeyMap.get(metricKey));
+ }
+
+ return Optional.empty();
+ }
+
+ public boolean shouldSendMetric(Dimension dimension, String metricKey, Granularity granularity) {
+ Map<String, TelemetryMetricsSentDto> metricKeyMap = dimensionMetricKeyMap.get(dimension);
+ boolean exists = metricKeyMap != null && metricKeyMap.containsKey(metricKey);
+
+ if (!exists) {
+ return true;
+ }
+
+ TelemetryMetricsSentDto dto = metricKeyMap.get(metricKey);
+ Instant lastSentTime = Instant.ofEpochMilli(dto.getLastSent());
+ Instant now = Instant.now();
+
+ LocalDateTime lastSentDateTime = LocalDateTime.ofInstant(lastSentTime, ZoneId.systemDefault());
+ LocalDateTime nowDateTime = LocalDateTime.ofInstant(now, ZoneId.systemDefault());
+
+ switch (granularity) {
+ case DAILY -> {
+ return ChronoUnit.DAYS.between(lastSentDateTime, nowDateTime) > 0;
+ } case WEEKLY -> {
+ return ChronoUnit.WEEKS.between(lastSentDateTime, nowDateTime) > 0;
+ } case MONTHLY -> {
+ return ChronoUnit.MONTHS.between(lastSentDateTime, nowDateTime) > 0;
+ } default -> throw new IllegalArgumentException("Unknown granularity: " + granularity);
+ }
+ }
+}
diff --git a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/util/package-info.java b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/util/package-info.java
new file mode 100644
index 00000000000..0e0db280dda
--- /dev/null
+++ b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/util/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.telemetry.metrics.util;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/user/TelemetryUserEnabledProvider.java b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/user/TelemetryUserEnabledProvider.java
index aa7d715d949..fb7358e1ab6 100644
--- a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/user/TelemetryUserEnabledProvider.java
+++ b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/user/TelemetryUserEnabledProvider.java
@@ -26,6 +26,7 @@ import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.user.UserDto;
import org.sonar.db.user.UserQuery;
+import org.sonar.server.util.DigestUtil;
import org.sonar.telemetry.Dimension;
import org.sonar.telemetry.Granularity;
import org.sonar.telemetry.TelemetryDataProvider;
@@ -65,11 +66,12 @@ public class TelemetryUserEnabledProvider implements TelemetryDataProvider<Boole
int pageSize = 1000;
int page = 1;
try (DbSession dbSession = dbClient.openSession(false)) {
- List<UserDto> userDtos = null;
+ List<UserDto> userDtos;
do {
userDtos = dbClient.userDao().selectUsers(dbSession, UserQuery.builder().build(), page, pageSize);
for (UserDto userDto : userDtos) {
- result.put(userDto.getUuid(), userDto.isActive());
+ String anonymizedUuid = DigestUtil.sha3_224Hex(userDto.getUuid());
+ result.put(anonymizedUuid, userDto.isActive());
}
page++;
} while (!userDtos.isEmpty());
diff --git a/server/sonar-telemetry/src/test/java/org/sonar/telemetry/DimensionTest.java b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/DimensionTest.java
new file mode 100644
index 00000000000..6c30ec72758
--- /dev/null
+++ b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/DimensionTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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 org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class DimensionTest {
+ @Test
+ void getValue() {
+ assertEquals("installation", Dimension.INSTALLATION.getValue());
+ assertEquals("user", Dimension.USER.getValue());
+ assertEquals("project", Dimension.PROJECT.getValue());
+ assertEquals("language", Dimension.LANGUAGE.getValue());
+ }
+
+ @Test
+ void fromValue() {
+ assertEquals(Dimension.INSTALLATION, Dimension.fromValue("installation"));
+ assertEquals(Dimension.USER, Dimension.fromValue("user"));
+ assertEquals(Dimension.PROJECT, Dimension.fromValue("project"));
+ assertEquals(Dimension.LANGUAGE, Dimension.fromValue("language"));
+
+ assertEquals(Dimension.INSTALLATION, Dimension.fromValue("INSTALLATION"));
+ assertEquals(Dimension.USER, Dimension.fromValue("USER"));
+ assertEquals(Dimension.PROJECT, Dimension.fromValue("PROJECT"));
+ assertEquals(Dimension.LANGUAGE, Dimension.fromValue("LANGUAGE"));
+ }
+
+ @Test
+ void fromValue_whenInvalid() {
+ Exception exception = assertThrows(IllegalArgumentException.class, () -> {
+ Dimension.fromValue("invalid");
+ });
+ assertEquals("Unknown dimension value: invalid", exception.getMessage());
+ }
+}
diff --git a/server/sonar-telemetry/src/test/java/org/sonar/telemetry/legacy/FakeServer.java b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/FakeServer.java
index 2e2060834f4..5fa8a71ed47 100644
--- a/server/sonar-telemetry/src/test/java/org/sonar/telemetry/legacy/FakeServer.java
+++ b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/FakeServer.java
@@ -17,14 +17,14 @@
* 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.legacy;
+package org.sonar.telemetry;
import java.util.Date;
import org.sonar.api.platform.Server;
import static org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric;
-class FakeServer extends Server {
+public class FakeServer extends Server {
private String id;
private String version;
@@ -38,7 +38,7 @@ class FakeServer extends Server {
return id;
}
- FakeServer setId(String id) {
+ public FakeServer setId(String id) {
this.id = id;
return this;
}
diff --git a/server/sonar-telemetry/src/test/java/org/sonar/telemetry/GranularityTest.java b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/GranularityTest.java
new file mode 100644
index 00000000000..dc3946d8a57
--- /dev/null
+++ b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/GranularityTest.java
@@ -0,0 +1,35 @@
+/*
+ * 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 org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class GranularityTest {
+
+ @Test
+ void getValue() {
+ assertEquals("daily", Granularity.DAILY.getValue());
+ assertEquals("weekly", Granularity.WEEKLY.getValue());
+ assertEquals("monthly", Granularity.MONTHLY.getValue());
+ }
+
+}
diff --git a/server/sonar-telemetry/src/test/java/org/sonar/telemetry/legacy/TelemetryClientCompressionTest.java b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/TelemetryClientCompressionTest.java
index a9343df8303..29121b50377 100644
--- a/server/sonar-telemetry/src/test/java/org/sonar/telemetry/legacy/TelemetryClientCompressionTest.java
+++ b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/TelemetryClientCompressionTest.java
@@ -17,7 +17,7 @@
* 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.legacy;
+package org.sonar.telemetry;
import java.io.IOException;
import java.util.Objects;
@@ -29,19 +29,19 @@ import okhttp3.mockwebserver.RecordedRequest;
import okio.GzipSource;
import okio.Okio;
import org.assertj.core.api.Assertions;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import org.sonar.api.config.internal.MapSettings;
import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_URL;
-public class TelemetryClientCompressionTest {
+class TelemetryClientCompressionTest {
private final OkHttpClient okHttpClient = new OkHttpClient();
private final MockWebServer telemetryServer = new MockWebServer();
@Test
- public void payload_is_gzip_encoded() throws IOException, InterruptedException {
+ void payload_is_gzip_encoded() throws IOException, InterruptedException {
telemetryServer.enqueue(new MockResponse().setResponseCode(200));
MapSettings settings = new MapSettings();
settings.setProperty(SONAR_TELEMETRY_URL.getKey(), telemetryServer.url("/").toString());
diff --git a/server/sonar-telemetry/src/test/java/org/sonar/telemetry/legacy/TelemetryClientTest.java b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/TelemetryClientTest.java
index a2bf58cdc91..2323ee4e0e8 100644
--- a/server/sonar-telemetry/src/test/java/org/sonar/telemetry/legacy/TelemetryClientTest.java
+++ b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/TelemetryClientTest.java
@@ -17,14 +17,14 @@
* 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.legacy;
+package org.sonar.telemetry;
import java.io.IOException;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okio.Buffer;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.sonar.api.config.internal.MapSettings;
@@ -35,18 +35,18 @@ import static org.mockito.Mockito.verify;
import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_COMPRESSION;
import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_URL;
-public class TelemetryClientTest {
+class TelemetryClientTest {
private static final String JSON = "{\"key\":\"value\"}";
private static final String TELEMETRY_URL = "https://telemetry.com/url";
- private OkHttpClient okHttpClient = mock(OkHttpClient.class, RETURNS_DEEP_STUBS);
- private MapSettings settings = new MapSettings();
+ private final OkHttpClient okHttpClient = mock(OkHttpClient.class, RETURNS_DEEP_STUBS);
+ private final MapSettings settings = new MapSettings();
- private TelemetryClient underTest = new TelemetryClient(okHttpClient, settings.asConfig());
+ private final TelemetryClient underTest = new TelemetryClient(okHttpClient, settings.asConfig());
@Test
- public void upload() throws IOException {
+ void upload() throws IOException {
ArgumentCaptor<Request> requestCaptor = ArgumentCaptor.forClass(Request.class);
settings.setProperty(SONAR_TELEMETRY_URL.getKey(), TELEMETRY_URL);
settings.setProperty(SONAR_TELEMETRY_COMPRESSION.getKey(), false);
@@ -65,7 +65,7 @@ public class TelemetryClientTest {
}
@Test
- public void opt_out() throws IOException {
+ void opt_out() throws IOException {
ArgumentCaptor<Request> requestCaptor = ArgumentCaptor.forClass(Request.class);
settings.setProperty(SONAR_TELEMETRY_URL.getKey(), TELEMETRY_URL);
underTest.start();
diff --git a/server/sonar-telemetry/src/test/java/org/sonar/telemetry/legacy/TelemetryDaemonTest.java b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/TelemetryDaemonTest.java
index 62d9e397feb..bcee58e5c6f 100644
--- a/server/sonar-telemetry/src/test/java/org/sonar/telemetry/legacy/TelemetryDaemonTest.java
+++ b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/TelemetryDaemonTest.java
@@ -17,23 +17,28 @@
* 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.legacy;
+package org.sonar.telemetry;
import java.io.IOException;
import java.util.Collections;
-import org.junit.After;
-import org.junit.Rule;
-import org.junit.Test;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
import org.slf4j.event.Level;
import org.sonar.api.config.internal.MapSettings;
import org.sonar.api.impl.utils.TestSystem2;
-import org.sonar.api.testfixtures.log.LogTester;
-import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.api.testfixtures.log.LogTesterJUnit5;
import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.db.DbTester;
import org.sonar.server.property.InternalProperties;
import org.sonar.server.property.MapInternalProperties;
import org.sonar.server.util.GlobalLockManager;
import org.sonar.server.util.GlobalLockManagerImpl;
+import org.sonar.telemetry.legacy.TelemetryData;
+import org.sonar.telemetry.legacy.TelemetryDataJsonWriter;
+import org.sonar.telemetry.legacy.TelemetryDataLoader;
+import org.sonar.telemetry.metrics.TelemetryMetricsLoader;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -54,9 +59,14 @@ import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_ENABL
import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_FREQUENCY_IN_SECONDS;
import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_URL;
-public class TelemetryDaemonTest {
- @Rule
- public LogTester logger = new LogTester().setLevel(LoggerLevel.DEBUG);
+class TelemetryDaemonTest {
+
+ protected final TestSystem2 system2 = new TestSystem2().setNow(System.currentTimeMillis());
+
+ @RegisterExtension
+ private final LogTesterJUnit5 logTester = new LogTesterJUnit5().setLevel(Level.DEBUG);
+ @RegisterExtension
+ public DbTester db = DbTester.create(system2);
private static final long ONE_HOUR = 60 * 60 * 1_000L;
private static final long ONE_DAY = 24 * ONE_HOUR;
@@ -71,20 +81,24 @@ public class TelemetryDaemonTest {
private final TelemetryClient client = mock(TelemetryClient.class);
private final InternalProperties internalProperties = spy(new MapInternalProperties());
private final GlobalLockManager lockManager = mock(GlobalLockManagerImpl.class);
- private final TestSystem2 system2 = new TestSystem2().setNow(System.currentTimeMillis());
private final MapSettings settings = new MapSettings();
-
private final TelemetryDataLoader dataLoader = mock(TelemetryDataLoader.class);
private final TelemetryDataJsonWriter dataJsonWriter = mock(TelemetryDataJsonWriter.class);
- private final TelemetryDaemon underTest = new TelemetryDaemon(dataLoader, dataJsonWriter, client, settings.asConfig(), internalProperties, lockManager, system2);
+ private final TelemetryMetricsLoader metricsLoader = mock(TelemetryMetricsLoader.class);
+ private final TelemetryDaemon underTest = new TelemetryDaemon(dataLoader, dataJsonWriter, client, settings.asConfig(), internalProperties, lockManager, system2, metricsLoader, db.getDbClient());
+
+ @BeforeEach
+ void setUp() {
+ when(metricsLoader.loadData()).thenReturn(new TelemetryMetricsLoader.Context());
+ }
- @After
- public void tearDown() {
+ @AfterEach
+ void tearDown() {
underTest.stop();
}
@Test
- public void send_data_via_client_at_startup_after_initial_delay() throws IOException {
+ void send_data_via_client_at_startup_after_initial_delay() throws IOException {
initTelemetrySettingsToDefaultValues();
when(lockManager.tryLock(any(), anyInt())).thenReturn(true);
settings.setProperty("sonar.telemetry.frequencyInSeconds", "1");
@@ -108,7 +122,7 @@ public class TelemetryDaemonTest {
}
@Test
- public void check_if_should_send_data_periodically() throws IOException {
+ void check_if_should_send_data_periodically() throws IOException {
initTelemetrySettingsToDefaultValues();
when(lockManager.tryLock(any(), anyInt())).thenReturn(true);
long now = system2.now();
@@ -131,7 +145,7 @@ public class TelemetryDaemonTest {
}
@Test
- public void do_not_send_data_if_last_ping_earlier_than_one_day_ago() throws IOException {
+ void do_not_send_data_if_last_ping_earlier_than_one_day_ago() throws IOException {
initTelemetrySettingsToDefaultValues();
when(lockManager.tryLock(any(), anyInt())).thenReturn(true);
settings.setProperty("sonar.telemetry.frequencyInSeconds", "1");
@@ -146,7 +160,7 @@ public class TelemetryDaemonTest {
}
@Test
- public void send_data_if_last_ping_is_over_one_day_ago() throws IOException {
+ void send_data_if_last_ping_is_over_one_day_ago() throws IOException {
initTelemetrySettingsToDefaultValues();
when(lockManager.tryLock(any(), anyInt())).thenReturn(true);
settings.setProperty("sonar.telemetry.frequencyInSeconds", "1");
@@ -165,7 +179,7 @@ public class TelemetryDaemonTest {
}
@Test
- public void opt_out_sent_once() throws IOException {
+ void opt_out_sent_once() throws IOException {
initTelemetrySettingsToDefaultValues();
when(lockManager.tryLock(any(), anyInt())).thenReturn(true);
settings.setProperty("sonar.telemetry.frequencyInSeconds", "1");
@@ -177,11 +191,11 @@ public class TelemetryDaemonTest {
verify(client, after(2_000).never()).upload(anyString());
verify(client, timeout(2_000).times(1)).optOut(anyString());
- assertThat(logger.logs(Level.INFO)).contains("Sharing of SonarQube statistics is disabled.");
+ assertThat(logTester.logs(Level.INFO)).contains("Sharing of SonarQube statistics is disabled.");
}
@Test
- public void write_sequence_as_one_if_not_previously_present() {
+ void write_sequence_as_one_if_not_previously_present() {
initTelemetrySettingsToDefaultValues();
when(lockManager.tryLock(any(), anyInt())).thenReturn(true);
settings.setProperty("sonar.telemetry.frequencyInSeconds", "1");
@@ -193,7 +207,7 @@ public class TelemetryDaemonTest {
}
@Test
- public void write_sequence_correctly_incremented() {
+ void write_sequence_correctly_incremented() {
initTelemetrySettingsToDefaultValues();
when(lockManager.tryLock(any(), anyInt())).thenReturn(true);
settings.setProperty("sonar.telemetry.frequencyInSeconds", "1");
diff --git a/server/sonar-telemetry/src/test/java/org/sonar/telemetry/TelemetryDataTypeTest.java b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/TelemetryDataTypeTest.java
new file mode 100644
index 00000000000..7d3a3cc19a5
--- /dev/null
+++ b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/TelemetryDataTypeTest.java
@@ -0,0 +1,35 @@
+/*
+ * 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 org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class TelemetryDataTypeTest {
+ @Test
+ void getValue() {
+ assertEquals("integer", TelemetryDataType.INTEGER.getValue());
+ assertEquals("string", TelemetryDataType.STRING.getValue());
+ assertEquals("boolean", TelemetryDataType.BOOLEAN.getValue());
+ assertEquals("float", TelemetryDataType.FLOAT.getValue());
+ }
+
+}
diff --git a/server/sonar-telemetry/src/test/java/org/sonar/telemetry/legacy/CloudUsageDataProviderTest.java b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/legacy/CloudUsageDataProviderTest.java
index dc14c8725ad..dde35593dd5 100644
--- a/server/sonar-telemetry/src/test/java/org/sonar/telemetry/legacy/CloudUsageDataProviderTest.java
+++ b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/legacy/CloudUsageDataProviderTest.java
@@ -31,8 +31,8 @@ import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
-import org.junit.Before;
-import org.junit.Test;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import org.sonar.api.utils.System2;
import org.sonar.server.platform.ContainerSupport;
import org.sonar.server.util.Paths2;
@@ -49,7 +49,7 @@ import static org.sonar.telemetry.legacy.CloudUsageDataProvider.KUBERNETES_SERVI
import static org.sonar.telemetry.legacy.CloudUsageDataProvider.KUBERNETES_SERVICE_PORT;
import static org.sonar.telemetry.legacy.CloudUsageDataProvider.SONAR_HELM_CHART_VERSION;
-public class CloudUsageDataProviderTest {
+class CloudUsageDataProviderTest {
private final System2 system2 = mock(System2.class);
private final Paths2 paths2 = mock(Paths2.class);
@@ -59,8 +59,8 @@ public class CloudUsageDataProviderTest {
private final CloudUsageDataProvider underTest = new CloudUsageDataProvider(containerSupport, system2, paths2, () -> processBuilder,
httpClient);
- @Before
- public void setUp() throws Exception {
+ @BeforeEach
+ void setUp() throws Exception {
when(system2.envVariable(KUBERNETES_SERVICE_HOST)).thenReturn("localhost");
when(system2.envVariable(KUBERNETES_SERVICE_PORT)).thenReturn("443");
@@ -92,64 +92,64 @@ public class CloudUsageDataProviderTest {
}
@Test
- public void containerRuntime_whenContainerSupportContextExists_shouldNotBeNull() {
+ void containerRuntime_whenContainerSupportContextExists_shouldNotBeNull() {
when(containerSupport.getContainerContext()).thenReturn("docker");
assertThat(underTest.getCloudUsage().containerRuntime()).isEqualTo("docker");
}
@Test
- public void containerRuntime_whenContainerSupportContextMissing_shouldBeNull() {
+ void containerRuntime_whenContainerSupportContextMissing_shouldBeNull() {
when(containerSupport.getContainerContext()).thenReturn(null);
assertThat(underTest.getCloudUsage().containerRuntime()).isNull();
}
@Test
- public void kubernetes_whenEnvVarExists_shouldReturnTrue() {
+ void kubernetes_whenEnvVarExists_shouldReturnTrue() {
assertThat(underTest.getCloudUsage().kubernetes()).isTrue();
}
@Test
- public void kubernetes_whenEnvVarDoesNotExist_shouldReturnFalse() {
+ void kubernetes_whenEnvVarDoesNotExist_shouldReturnFalse() {
when(system2.envVariable(KUBERNETES_SERVICE_HOST)).thenReturn(null);
assertThat(underTest.getCloudUsage().kubernetes()).isFalse();
}
@Test
- public void kubernetesVersion_whenOnKubernetes_shouldReturnValue() {
+ void kubernetesVersion_whenOnKubernetes_shouldReturnValue() {
assertThat(underTest.getCloudUsage().kubernetesVersion()).isEqualTo("1.25");
}
@Test
- public void kubernetesVersion_whenNotOnKubernetes_shouldReturnNull() {
+ void kubernetesVersion_whenNotOnKubernetes_shouldReturnNull() {
when(system2.envVariable(KUBERNETES_SERVICE_HOST)).thenReturn(null);
assertThat(underTest.getCloudUsage().kubernetesVersion()).isNull();
}
@Test
- public void kubernetesVersion_whenApiCallFails_shouldReturnNull() throws IOException {
+ void kubernetesVersion_whenApiCallFails_shouldReturnNull() throws IOException {
mockHttpClientCall(404, "not found", null);
assertThat(underTest.getCloudUsage().kubernetesVersion()).isNull();
}
@Test
- public void kubernetesPlatform_whenOnKubernetes_shouldReturnValue() {
+ void kubernetesPlatform_whenOnKubernetes_shouldReturnValue() {
assertThat(underTest.getCloudUsage().kubernetesPlatform()).isEqualTo("linux/arm64");
}
@Test
- public void kubernetesPlatform_whenNotOnKubernetes_shouldReturnNull() {
+ void kubernetesPlatform_whenNotOnKubernetes_shouldReturnNull() {
when(system2.envVariable(KUBERNETES_SERVICE_HOST)).thenReturn(null);
assertThat(underTest.getCloudUsage().kubernetesPlatform()).isNull();
}
@Test
- public void kubernetesPlatform_whenApiCallFails_shouldReturnNull() throws IOException {
+ void kubernetesPlatform_whenApiCallFails_shouldReturnNull() throws IOException {
mockHttpClientCall(404, "not found", null);
assertThat(underTest.getCloudUsage().kubernetesPlatform()).isNull();
}
@Test
- public void kubernetesProvider_shouldReturnValue() throws IOException {
+ void kubernetesProvider_shouldReturnValue() throws IOException {
Process processMock = mock(Process.class);
when(processMock.getInputStream()).thenReturn(new ByteArrayInputStream("some-provider".getBytes()));
when(processBuilder.command(any(String[].class))).thenReturn(processBuilder);
@@ -159,7 +159,7 @@ public class CloudUsageDataProviderTest {
}
@Test
- public void kubernetesProvider_whenValueContainsNullChars_shouldReturnValueWithoutNullChars() throws IOException {
+ void kubernetesProvider_whenValueContainsNullChars_shouldReturnValueWithoutNullChars() throws IOException {
Process processMock = mock(Process.class);
when(processMock.getInputStream()).thenReturn(new ByteArrayInputStream("so\u0000me-prov\u0000ider".getBytes()));
when(processBuilder.command(any(String[].class))).thenReturn(processBuilder);
@@ -169,37 +169,37 @@ public class CloudUsageDataProviderTest {
}
@Test
- public void officialHelmChart_whenEnvVarExists_shouldReturnValue() {
+ void officialHelmChart_whenEnvVarExists_shouldReturnValue() {
when(system2.envVariable(SONAR_HELM_CHART_VERSION)).thenReturn("10.1.0");
assertThat(underTest.getCloudUsage().officialHelmChart()).isEqualTo("10.1.0");
}
@Test
- public void officialHelmChart_whenEnvVarDoesNotExist_shouldReturnNull() {
+ void officialHelmChart_whenEnvVarDoesNotExist_shouldReturnNull() {
when(system2.envVariable(SONAR_HELM_CHART_VERSION)).thenReturn(null);
assertThat(underTest.getCloudUsage().officialHelmChart()).isNull();
}
@Test
- public void officialImage_whenEnvVarTrue_shouldReturnTrue() {
+ void officialImage_whenEnvVarTrue_shouldReturnTrue() {
when(system2.envVariable(DOCKER_RUNNING)).thenReturn("True");
assertThat(underTest.getCloudUsage().officialImage()).isTrue();
}
@Test
- public void officialImage_whenEnvVarFalse_shouldReturnFalse() {
+ void officialImage_whenEnvVarFalse_shouldReturnFalse() {
when(system2.envVariable(DOCKER_RUNNING)).thenReturn("False");
assertThat(underTest.getCloudUsage().officialImage()).isFalse();
}
@Test
- public void officialImage_whenEnvVarDoesNotExist_shouldReturnFalse() {
+ void officialImage_whenEnvVarDoesNotExist_shouldReturnFalse() {
when(system2.envVariable(DOCKER_RUNNING)).thenReturn(null);
assertThat(underTest.getCloudUsage().officialImage()).isFalse();
}
@Test
- public void initHttpClient_whenValidCertificate_shouldCreateClient() throws URISyntaxException {
+ void initHttpClient_whenValidCertificate_shouldCreateClient() throws URISyntaxException {
when(paths2.get(anyString())).thenReturn(Paths.get(requireNonNull(getClass().getResource("dummy.crt")).toURI()));
CloudUsageDataProvider provider = new CloudUsageDataProvider(containerSupport, system2, paths2);
@@ -207,7 +207,7 @@ public class CloudUsageDataProviderTest {
}
@Test
- public void initHttpClient_whenNotOnKubernetes_shouldNotCreateClient() throws URISyntaxException {
+ void initHttpClient_whenNotOnKubernetes_shouldNotCreateClient() throws URISyntaxException {
when(paths2.get(anyString())).thenReturn(Paths.get(requireNonNull(getClass().getResource("dummy.crt")).toURI()));
when(system2.envVariable(KUBERNETES_SERVICE_HOST)).thenReturn(null);
@@ -216,7 +216,7 @@ public class CloudUsageDataProviderTest {
}
@Test
- public void initHttpClient_whenCertificateNotFound_shouldFail() {
+ void initHttpClient_whenCertificateNotFound_shouldFail() {
when(paths2.get(any())).thenReturn(Paths.get("dummy.crt"));
CloudUsageDataProvider provider = new CloudUsageDataProvider(containerSupport, system2, paths2);
diff --git a/server/sonar-telemetry/src/test/java/org/sonar/telemetry/legacy/TelemetryDataJsonWriterTest.java b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/legacy/TelemetryDataJsonWriterTest.java
index 4534fa3ec17..2592a7fedb3 100644
--- a/server/sonar-telemetry/src/test/java/org/sonar/telemetry/legacy/TelemetryDataJsonWriterTest.java
+++ b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/legacy/TelemetryDataJsonWriterTest.java
@@ -20,8 +20,6 @@
package org.sonar.telemetry.legacy;
import com.tngtech.java.junit.dataprovider.DataProvider;
-import com.tngtech.java.junit.dataprovider.DataProviderRunner;
-import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Collections;
@@ -33,8 +31,9 @@ import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.jetbrains.annotations.NotNull;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
import org.sonar.api.utils.System2;
import org.sonar.api.utils.text.JsonWriter;
import org.sonar.core.platform.EditionProvider;
@@ -54,8 +53,7 @@ import static org.sonar.db.newcodeperiod.NewCodePeriodType.PREVIOUS_VERSION;
import static org.sonar.server.qualitygate.Condition.Operator.fromDbValue;
import static org.sonar.test.JsonAssert.assertJson;
-@RunWith(DataProviderRunner.class)
-public class TelemetryDataJsonWriterTest {
+class TelemetryDataJsonWriterTest {
private final Random random = new Random();
@@ -71,7 +69,7 @@ public class TelemetryDataJsonWriterTest {
private static final TelemetryData.NewCodeDefinition NCD_PROJECT = new TelemetryData.NewCodeDefinition(NUMBER_OF_DAYS.name(), "30", "project");
@Test
- public void write_server_id_version_and_sequence() {
+ void write_server_id_version_and_sequence() {
TelemetryData data = telemetryBuilder().build();
String json = writeTelemetryData(data);
@@ -85,7 +83,7 @@ public class TelemetryDataJsonWriterTest {
}
@Test
- public void does_not_write_edition_if_null() {
+ void does_not_write_edition_if_null() {
TelemetryData data = telemetryBuilder().build();
String json = writeTelemetryData(data);
@@ -93,9 +91,9 @@ public class TelemetryDataJsonWriterTest {
assertThat(json).doesNotContain("edition");
}
- @Test
- @UseDataProvider("allEditions")
- public void writes_edition_if_non_null(EditionProvider.Edition edition) {
+ @ParameterizedTest
+ @MethodSource("allEditions")
+ void writes_edition_if_non_null(EditionProvider.Edition edition) {
TelemetryData data = telemetryBuilder()
.setEdition(edition)
.build();
@@ -109,7 +107,7 @@ public class TelemetryDataJsonWriterTest {
}
@Test
- public void writes_default_qg() {
+ void writes_default_qg() {
TelemetryData data = telemetryBuilder()
.setDefaultQualityGate("default-qg")
.build();
@@ -123,7 +121,7 @@ public class TelemetryDataJsonWriterTest {
}
@Test
- public void writes_sonarWay_qg() {
+ void writes_sonarWay_qg() {
TelemetryData data = telemetryBuilder()
.setSonarWayQualityGate("sonarWayUUID")
.build();
@@ -137,7 +135,7 @@ public class TelemetryDataJsonWriterTest {
}
@Test
- public void writes_database() {
+ void writes_database() {
String name = randomAlphabetic(12);
String version = randomAlphabetic(10);
TelemetryData data = telemetryBuilder()
@@ -156,7 +154,7 @@ public class TelemetryDataJsonWriterTest {
}
@Test
- public void writes_no_plugins() {
+ void writes_no_plugins() {
TelemetryData data = telemetryBuilder()
.setPlugins(Collections.emptyMap())
.build();
@@ -171,7 +169,7 @@ public class TelemetryDataJsonWriterTest {
}
@Test
- public void writes_all_plugins() {
+ void writes_all_plugins() {
Map<String, String> plugins = IntStream.range(0, 1 + random.nextInt(10))
.boxed()
.collect(Collectors.toMap(i -> "P" + i, i1 -> "V" + i1));
@@ -188,7 +186,7 @@ public class TelemetryDataJsonWriterTest {
}
@Test
- public void does_not_write_installation_date_if_null() {
+ void does_not_write_installation_date_if_null() {
TelemetryData data = telemetryBuilder()
.setInstallationDate(null)
.build();
@@ -199,7 +197,7 @@ public class TelemetryDataJsonWriterTest {
}
@Test
- public void write_installation_date_in_utc_format() {
+ void write_installation_date_in_utc_format() {
TelemetryData data = telemetryBuilder()
.setInstallationDate(1_000L)
.build();
@@ -214,7 +212,7 @@ public class TelemetryDataJsonWriterTest {
}
@Test
- public void does_not_write_installation_version_if_null() {
+ void does_not_write_installation_version_if_null() {
TelemetryData data = telemetryBuilder()
.setInstallationVersion(null)
.build();
@@ -225,7 +223,7 @@ public class TelemetryDataJsonWriterTest {
}
@Test
- public void write_installation_version() {
+ void write_installation_version() {
String installationVersion = randomAlphabetic(5);
TelemetryData data = telemetryBuilder()
.setInstallationVersion(installationVersion)
@@ -239,9 +237,9 @@ public class TelemetryDataJsonWriterTest {
""".formatted(installationVersion));
}
- @Test
- @UseDataProvider("getFeatureFlagEnabledStates")
- public void write_container_flag(boolean isIncontainer) {
+ @ParameterizedTest
+ @MethodSource("getFeatureFlagEnabledStates")
+ void write_container_flag(boolean isIncontainer) {
TelemetryData data = telemetryBuilder()
.setInContainer(isIncontainer)
.build();
@@ -264,9 +262,9 @@ public class TelemetryDataJsonWriterTest {
};
}
- @Test
- @UseDataProvider("getManagedInstanceData")
- public void writeTelemetryData_encodesCorrectlyManagedInstanceInformation(boolean isManaged, String provider) {
+ @ParameterizedTest
+ @MethodSource("getManagedInstanceData")
+ void writeTelemetryData_encodesCorrectlyManagedInstanceInformation(boolean isManaged, String provider) {
TelemetryData data = telemetryBuilder()
.setManagedInstanceInformation(new TelemetryData.ManagedInstanceInformation(isManaged, provider))
.build();
@@ -294,7 +292,7 @@ public class TelemetryDataJsonWriterTest {
}
@Test
- public void writeTelemetryData_shouldWriteCloudUsage() {
+ void writeTelemetryData_shouldWriteCloudUsage() {
TelemetryData data = telemetryBuilder().build();
String json = writeTelemetryData(data);
@@ -314,7 +312,7 @@ public class TelemetryDataJsonWriterTest {
}
@Test
- public void writes_has_unanalyzed_languages() {
+ void writes_has_unanalyzed_languages() {
TelemetryData data = telemetryBuilder()
.setHasUnanalyzedC(true)
.setHasUnanalyzedCpp(false)
@@ -331,7 +329,7 @@ public class TelemetryDataJsonWriterTest {
}
@Test
- public void writes_security_custom_config() {
+ void writes_security_custom_config() {
TelemetryData data = telemetryBuilder()
.setCustomSecurityConfigs(Set.of("php", "java"))
.build();
@@ -346,7 +344,7 @@ public class TelemetryDataJsonWriterTest {
}
@Test
- public void writes_local_timestamp() {
+ void writes_local_timestamp() {
when(system2.now()).thenReturn(1000L);
TelemetryData data = telemetryBuilder().build();
@@ -360,7 +358,7 @@ public class TelemetryDataJsonWriterTest {
}
@Test
- public void writes_all_users_with_anonymous_md5_uuids() {
+ void writes_all_users_with_anonymous_md5_uuids() {
TelemetryData data = telemetryBuilder()
.setUsers(attachUsers())
.build();
@@ -401,7 +399,7 @@ public class TelemetryDataJsonWriterTest {
}
@Test
- public void writes_all_projects() {
+ void writes_all_projects() {
TelemetryData data = telemetryBuilder()
.setProjects(attachProjects())
.build();
@@ -438,7 +436,7 @@ public class TelemetryDataJsonWriterTest {
}
@Test
- public void writeTelemetryData_whenAnalyzedLanguages_shouldwriteAllProjectsStats() {
+ void writeTelemetryData_whenAnalyzedLanguages_shouldwriteAllProjectsStats() {
TelemetryData data = telemetryBuilder()
.setProjectStatistics(attachProjectStatsWithMetrics())
.build();
@@ -508,7 +506,7 @@ public class TelemetryDataJsonWriterTest {
}
@Test
- public void writes_all_projects_stats_with_unanalyzed_languages() {
+ void writes_all_projects_stats_with_unanalyzed_languages() {
TelemetryData data = telemetryBuilder()
.setProjectStatistics(attachProjectStats())
.build();
@@ -518,7 +516,7 @@ public class TelemetryDataJsonWriterTest {
}
@Test
- public void writes_all_projects_stats_without_missing_metrics() {
+ void writes_all_projects_stats_without_missing_metrics() {
TelemetryData data = telemetryBuilder()
.setProjectStatistics(attachProjectStats())
.build();
@@ -527,7 +525,7 @@ public class TelemetryDataJsonWriterTest {
}
@Test
- public void writes_all_quality_gates() {
+ void writes_all_quality_gates() {
TelemetryData data = telemetryBuilder()
.setQualityGates(attachQualityGates())
.build();
@@ -590,7 +588,7 @@ public class TelemetryDataJsonWriterTest {
}
@Test
- public void writeTelemetryData_shouldWriteQualityProfiles() {
+ void writeTelemetryData_shouldWriteQualityProfiles() {
TelemetryData data = telemetryBuilder()
.setQualityProfiles(List.of(
new TelemetryData.QualityProfile("uuid-1", "parent-uuid-1", "js", true, false, true, 2, 3, 4),
@@ -623,7 +621,7 @@ public class TelemetryDataJsonWriterTest {
}
@Test
- public void writes_all_branches() {
+ void writes_all_branches() {
TelemetryData data = telemetryBuilder()
.setBranches(attachBranches())
.build();
@@ -654,7 +652,7 @@ public class TelemetryDataJsonWriterTest {
}
@Test
- public void writes_new_code_definitions() {
+ void writes_new_code_definitions() {
TelemetryData data = telemetryBuilder()
.setNewCodeDefinitions(attachNewCodeDefinitions())
.build();
@@ -683,7 +681,7 @@ public class TelemetryDataJsonWriterTest {
}
@Test
- public void writes_instance_new_code_definition() {
+ void writes_instance_new_code_definition() {
TelemetryData data = telemetryBuilder().build();
String json = writeTelemetryData(data);
diff --git a/server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/TelemetryMetricsMapperTest.java b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/TelemetryMetricsMapperTest.java
new file mode 100644
index 00000000000..276948bd360
--- /dev/null
+++ b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/TelemetryMetricsMapperTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.metrics;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import org.assertj.core.groups.Tuple;
+import org.junit.jupiter.api.Test;
+import org.sonar.telemetry.Dimension;
+import org.sonar.telemetry.Granularity;
+import org.sonar.telemetry.TelemetryDataProvider;
+import org.sonar.telemetry.TelemetryDataType;
+import org.sonar.telemetry.metrics.schema.InstallationMetric;
+import org.sonar.telemetry.metrics.schema.LanguageMetric;
+import org.sonar.telemetry.metrics.schema.Metric;
+import org.sonar.telemetry.metrics.schema.ProjectMetric;
+import org.sonar.telemetry.metrics.schema.UserMetric;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+
+class TelemetryMetricsMapperTest {
+
+ @Test
+ void mapFromDataProvider_whenInstallationProvider() {
+ TelemetryDataProvider<String> provider = new TestTelemetryBean(Dimension.INSTALLATION);
+
+ Set<Metric> metrics = TelemetryMetricsMapper.mapFromDataProvider(provider);
+ List<InstallationMetric> userMetrics = retrieveList(metrics);
+
+ assertThat(userMetrics)
+ .extracting(InstallationMetric::getKey, InstallationMetric::getType, InstallationMetric::getValue, InstallationMetric::getGranularity)
+ .containsExactlyInAnyOrder(
+ tuple("telemetry-bean-a", TelemetryDataType.STRING, "value", Granularity.DAILY)
+ );
+ }
+
+ @Test
+ void mapFromDataProvider_whenUserProvider() {
+ TelemetryDataProvider<String> provider = new TestTelemetryBean(Dimension.USER);
+
+ Set<Metric> metrics = TelemetryMetricsMapper.mapFromDataProvider(provider);
+ List<UserMetric> list = retrieveList(metrics);
+
+ assertThat(list)
+ .extracting(UserMetric::getKey, UserMetric::getType, UserMetric::getUserUuid, UserMetric::getValue, UserMetric::getGranularity)
+ .containsExactlyInAnyOrder(
+ expected()
+ );
+ }
+
+ @Test
+ void mapFromDataProvider_whenLanguageProvider() {
+ TelemetryDataProvider<String> provider = new TestTelemetryBean(Dimension.LANGUAGE);
+
+ Set<Metric> metrics = TelemetryMetricsMapper.mapFromDataProvider(provider);
+ List<LanguageMetric> list = retrieveList(metrics);
+
+ assertThat(list)
+ .extracting(LanguageMetric::getKey, LanguageMetric::getType, LanguageMetric::getLanguage, LanguageMetric::getValue, LanguageMetric::getGranularity)
+ .containsExactlyInAnyOrder(
+ expected()
+ );
+ }
+
+ @Test
+ void mapFromDataProvider_whenProjectProvider() {
+ TelemetryDataProvider<String> provider = new TestTelemetryBean(Dimension.PROJECT);
+
+ Set<Metric> metrics = TelemetryMetricsMapper.mapFromDataProvider(provider);
+ List<ProjectMetric> list = retrieveList(metrics);
+
+ assertThat(list)
+ .extracting(ProjectMetric::getKey, ProjectMetric::getType, ProjectMetric::getProjectUuid, ProjectMetric::getValue, ProjectMetric::getGranularity)
+ .containsExactlyInAnyOrder(
+ expected()
+ );
+ }
+
+ private static Tuple[] expected() {
+ return new Tuple[]
+ {
+ tuple("telemetry-bean-a", TelemetryDataType.STRING, "key-1", "value-1", Granularity.DAILY),
+ tuple("telemetry-bean-a", TelemetryDataType.STRING, "key-2", "value-2", Granularity.DAILY)
+ };
+ }
+
+ private static <T extends Metric> List<T> retrieveList(Set<Metric> metrics) {
+ return new ArrayList<>((Set<T>) metrics);
+ }
+
+}
diff --git a/server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/TestTelemetryBean.java b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/TestTelemetryBean.java
new file mode 100644
index 00000000000..876f6b025c1
--- /dev/null
+++ b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/TestTelemetryBean.java
@@ -0,0 +1,72 @@
+/*
+ * 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.metrics;
+
+import java.util.Map;
+import org.sonar.telemetry.Dimension;
+import org.sonar.telemetry.Granularity;
+import org.sonar.telemetry.TelemetryDataProvider;
+import org.sonar.telemetry.TelemetryDataType;
+
+public class TestTelemetryBean implements TelemetryDataProvider<String> {
+
+ private static final String METRIC_KEY = "telemetry-bean-a";
+ private static final Granularity METRIC_GRANULARITY = Granularity.DAILY;
+ private static final TelemetryDataType METRIC_TYPE = TelemetryDataType.STRING;
+ private static final String METRIC_VALUE = "value";
+ private static final Map<String, String> METRIC_UUID_VALUES = Map.of("key-1", "value-1", "key-2", "value-2");
+
+ private final Dimension dimension;
+
+ public TestTelemetryBean(Dimension dimension) {
+ this.dimension = dimension;
+ }
+
+ @Override
+ public Dimension getDimension() {
+ return dimension;
+ }
+
+ @Override
+ public String getMetricKey() {
+ return METRIC_KEY;
+ }
+
+ @Override
+ public Granularity getGranularity() {
+ return METRIC_GRANULARITY;
+ }
+
+ @Override
+ public TelemetryDataType getType() {
+ return METRIC_TYPE;
+ }
+
+ @Override
+ public String getValue() {
+ return METRIC_VALUE;
+ }
+
+ @Override
+ public Map<String, String> getUuidValues() {
+ return METRIC_UUID_VALUES;
+ }
+
+}
diff --git a/server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/schema/BaseMessageTest.java b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/schema/BaseMessageTest.java
new file mode 100644
index 00000000000..dd50af1b46a
--- /dev/null
+++ b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/schema/BaseMessageTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.metrics.schema;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.sonar.telemetry.Dimension;
+import org.sonar.telemetry.Granularity;
+import org.sonar.telemetry.TelemetryDataType;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class BaseMessageTest {
+
+ @Test
+ void build() {
+ BaseMessage message = new BaseMessage.Builder()
+ .setMessageUuid("123e4567-e89b-12d3-a456-426614174000")
+ .setInstallationId("installation-id")
+ .setDimension(Dimension.INSTALLATION)
+ .setMetrics(installationMetrics())
+ .build();
+
+ assertThat(message.getMessageUuid()).isEqualTo("123e4567-e89b-12d3-a456-426614174000");
+ assertThat(message.getInstallationId()).isEqualTo("installation-id");
+ assertThat(message.getDimension()).isEqualTo(Dimension.INSTALLATION);
+ Set<InstallationMetric> installationMetrics = (Set<InstallationMetric>) (Set<?>) message.getMetrics();
+ assertThat(installationMetrics)
+ .extracting(InstallationMetric::getKey, InstallationMetric::getGranularity, InstallationMetric::getType, InstallationMetric::getValue)
+ .containsExactlyInAnyOrder(
+ tuple("key-0", Granularity.DAILY, TelemetryDataType.INTEGER, 0),
+ tuple("key-1", Granularity.DAILY, TelemetryDataType.INTEGER, 1),
+ tuple("key-2", Granularity.DAILY, TelemetryDataType.INTEGER, 2)
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("invalidBaseMessageProvider")
+ void build_invalidCases(BaseMessage.Builder builder, String expectedErrorMessage) {
+ Exception exception = assertThrows(NullPointerException.class, builder::build);
+ assertEquals(expectedErrorMessage, exception.getMessage());
+ }
+
+ private static Stream<Arguments> invalidBaseMessageProvider() {
+ return Stream.of(
+ Arguments.of(
+ new BaseMessage.Builder()
+ .setInstallationId("installation-id")
+ .setDimension(Dimension.INSTALLATION)
+ .setMetrics(installationMetrics()),
+ "messageUuid must be specified"
+ ),
+ Arguments.of(
+ new BaseMessage.Builder()
+ .setMessageUuid("some-uuid")
+ .setInstallationId("installation-id")
+ .setMetrics(installationMetrics()),
+ "dimension must be specified"
+ ),
+ Arguments.of(
+ new BaseMessage.Builder()
+ .setMessageUuid("some-uuid")
+ .setDimension(Dimension.INSTALLATION)
+ .setMetrics(installationMetrics()),
+ "installationId must be specified"
+ )
+ );
+ }
+
+ private static Set<Metric> installationMetrics() {
+ return IntStream.range(0, 3)
+ .mapToObj(i -> new InstallationMetric(
+ "key-" + i,
+ i,
+ TelemetryDataType.INTEGER,
+ Granularity.DAILY
+ )).collect(Collectors.toSet());
+ }
+}
diff --git a/server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/schema/InstallationMetricTest.java b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/schema/InstallationMetricTest.java
new file mode 100644
index 00000000000..2a1215626e3
--- /dev/null
+++ b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/schema/InstallationMetricTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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.metrics.schema;
+
+import org.junit.jupiter.api.Test;
+import org.sonar.telemetry.Granularity;
+import org.sonar.telemetry.TelemetryDataType;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class InstallationMetricTest {
+
+ @Test
+ void gettersAndSetters() {
+ InstallationMetric metric = new InstallationMetric(
+ "installation-key-1",
+ "value",
+ TelemetryDataType.STRING,
+ Granularity.WEEKLY
+ );
+
+ assertThat(metric.getValue()).isEqualTo("value");
+ assertThat(metric.getKey()).isEqualTo("installation-key-1");
+ assertThat(metric.getGranularity()).isEqualTo(Granularity.WEEKLY);
+ assertThat(metric.getType()).isEqualTo(TelemetryDataType.STRING);
+ }
+
+}
diff --git a/server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/schema/LanguageMetricTest.java b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/schema/LanguageMetricTest.java
new file mode 100644
index 00000000000..61c72956255
--- /dev/null
+++ b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/schema/LanguageMetricTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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.metrics.schema;
+
+import org.junit.jupiter.api.Test;
+import org.sonar.telemetry.Granularity;
+import org.sonar.telemetry.TelemetryDataType;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class LanguageMetricTest {
+
+ @Test
+ void gettersAndSetters() {
+ LanguageMetric metric = new LanguageMetric("ncloc", 100, "java", TelemetryDataType.INTEGER, Granularity.MONTHLY);
+
+ assertThat(metric.getLanguage()).isEqualTo("java");
+ assertThat(metric.getValue()).isEqualTo(100);
+ assertThat(metric.getKey()).isEqualTo("ncloc");
+ assertThat(metric.getGranularity()).isEqualTo(Granularity.MONTHLY);
+ assertThat(metric.getType()).isEqualTo(TelemetryDataType.INTEGER);
+ }
+
+}
diff --git a/server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/schema/ProjectMetricTest.java b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/schema/ProjectMetricTest.java
new file mode 100644
index 00000000000..cbcdb2c8180
--- /dev/null
+++ b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/schema/ProjectMetricTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.metrics.schema;
+
+import org.junit.jupiter.api.Test;
+import org.sonar.telemetry.Granularity;
+import org.sonar.telemetry.TelemetryDataType;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class ProjectMetricTest {
+
+ @Test
+ void gettersAndSetters() {
+ ProjectMetric metric = new ProjectMetric(
+ "project-key-1",
+ 1.0998,
+ "project-uuid",
+ TelemetryDataType.FLOAT,
+ Granularity.DAILY
+ );
+
+ assertThat(metric.getValue()).isEqualTo(1.0998);
+ assertThat(metric.getKey()).isEqualTo("project-key-1");
+ assertThat(metric.getGranularity()).isEqualTo(Granularity.DAILY);
+ assertThat(metric.getType()).isEqualTo(TelemetryDataType.FLOAT);
+ assertThat(metric.getProjectUuid()).isEqualTo("project-uuid");
+ }
+
+}
diff --git a/server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/schema/UserMetricTest.java b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/schema/UserMetricTest.java
new file mode 100644
index 00000000000..2f171714b8e
--- /dev/null
+++ b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/schema/UserMetricTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.metrics.schema;
+
+import org.junit.jupiter.api.Test;
+import org.sonar.telemetry.Granularity;
+import org.sonar.telemetry.TelemetryDataType;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class UserMetricTest {
+
+ @Test
+ void gettersAndSetters() {
+ UserMetric metric = new UserMetric(
+ "user-key-1",
+ true,
+ "user-uuid",
+ TelemetryDataType.BOOLEAN,
+ Granularity.DAILY
+ );
+
+ assertThat(metric.getValue()).isEqualTo(true);
+ assertThat(metric.getKey()).isEqualTo("user-key-1");
+ assertThat(metric.getGranularity()).isEqualTo(Granularity.DAILY);
+ assertThat(metric.getType()).isEqualTo(TelemetryDataType.BOOLEAN);
+ assertThat(metric.getUserUuid()).isEqualTo("user-uuid");
+ }
+
+}
diff --git a/server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/util/MessageSerializerTest.java b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/util/MessageSerializerTest.java
new file mode 100644
index 00000000000..098bb2dc82f
--- /dev/null
+++ b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/util/MessageSerializerTest.java
@@ -0,0 +1,247 @@
+/*
+ * 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.metrics.util;
+
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.sonar.telemetry.Dimension;
+import org.sonar.telemetry.Granularity;
+import org.sonar.telemetry.TelemetryDataType;
+import org.sonar.telemetry.metrics.schema.BaseMessage;
+import org.sonar.telemetry.metrics.schema.InstallationMetric;
+import org.sonar.telemetry.metrics.schema.LanguageMetric;
+import org.sonar.telemetry.metrics.schema.Metric;
+import org.sonar.telemetry.metrics.schema.ProjectMetric;
+import org.sonar.telemetry.metrics.schema.UserMetric;
+
+import static org.sonar.test.JsonAssert.assertJson;
+
+public class MessageSerializerTest {
+
+ @ParameterizedTest
+ @MethodSource("data")
+ void serialize(Dimension dimension, String expectedJson) {
+ BaseMessage message = getMessage(dimension);
+
+ String json = MessageSerializer.serialize(message);
+
+ assertJson(json).isSimilarTo(expectedJson);
+ }
+
+ @DataProvider
+ public static Object[][] data() {
+ return new Object[][]{
+ {Dimension.INSTALLATION, expectedInstallationJson()},
+ {Dimension.PROJECT, expectedProjectJson()},
+ {Dimension.USER, expectedUserJson()},
+ {Dimension.LANGUAGE, expectedLanguageJson()}
+ };
+ }
+
+ private BaseMessage getMessage(Dimension dimension) {
+ Set<Metric> metrics = null;
+ switch (dimension) {
+ case INSTALLATION -> metrics = installationMetrics();
+ case PROJECT -> metrics = projectMetrics();
+ case LANGUAGE -> metrics = languageMetrics();
+ case USER -> metrics = userMetrics();
+ }
+
+ return new BaseMessage.Builder()
+ .setMessageUuid("message-uuid")
+ .setInstallationId("installation-id")
+ .setDimension(dimension)
+ .setMetrics(metrics)
+ .build();
+ }
+
+ private Set<Metric> userMetrics() {
+ return IntStream.range(0, 3)
+ .mapToObj(i -> new UserMetric(
+ "key-" + i,
+ 1.06f * i,
+ "user-" + i,
+ TelemetryDataType.FLOAT,
+ Granularity.MONTHLY
+ )).collect(Collectors.toSet());
+ }
+
+ private Set<Metric> languageMetrics() {
+ return IntStream.range(0, 3)
+ .mapToObj(i -> new LanguageMetric(
+ "key-" + i,
+ i % 2 == 0,
+ "java",
+ TelemetryDataType.BOOLEAN,
+ Granularity.MONTHLY
+ )).collect(Collectors.toSet());
+ }
+
+ private Set<Metric> projectMetrics() {
+ return IntStream.range(0, 3)
+ .mapToObj(i -> new ProjectMetric(
+ "key-" + i,
+ "value-" + i,
+ "project-" + i,
+ TelemetryDataType.STRING,
+ Granularity.WEEKLY
+ )).collect(Collectors.toSet());
+ }
+
+ private Set<Metric> installationMetrics() {
+ return IntStream.range(0, 3)
+ .mapToObj(i -> new InstallationMetric(
+ "key-" + i,
+ i,
+ TelemetryDataType.INTEGER,
+ Granularity.DAILY
+ )).collect(Collectors.toSet());
+ }
+
+ private static String expectedInstallationJson() {
+ return """
+ {
+ "message_uuid": "message-uuid",
+ "installation_id": "installation-id",
+ "dimension": "installation",
+ "metric_values": [
+ {
+ "key": "key-0",
+ "value": 0,
+ "type": "integer",
+ "granularity": "daily"
+ },
+ {
+ "key": "key-2",
+ "value": 2,
+ "type": "integer",
+ "granularity": "daily"
+ },
+ {
+ "key": "key-1",
+ "value": 1,
+ "type": "integer",
+ "granularity": "daily"
+ }
+ ]
+ }""";
+ }
+
+ private static String expectedProjectJson() {
+ return """
+ {
+ "message_uuid": "message-uuid",
+ "installation_id": "installation-id",
+ "dimension": "project",
+ "metric_values": [
+ {
+ "key": "key-0",
+ "value": "value-0",
+ "type": "string",
+ "granularity": "weekly",
+ "project_uuid": "project-0"
+ },
+ {
+ "key": "key-1",
+ "value": "value-1",
+ "type": "string",
+ "granularity": "weekly",
+ "project_uuid": "project-1"
+ },
+ {
+ "key": "key-2",
+ "value": "value-2",
+ "type": "string",
+ "granularity": "weekly",
+ "project_uuid": "project-2"
+ }
+ ]
+ }""";
+ }
+
+ private static String expectedUserJson() {
+ return """
+ {
+ "message_uuid": "message-uuid",
+ "installation_id": "installation-id",
+ "dimension": "user",
+ "metric_values": [
+ {
+ "key": "key-0",
+ "value": 0.0,
+ "type": "float",
+ "granularity": "monthly",
+ "user_uuid": "user-0"
+ },
+ {
+ "key": "key-1",
+ "value": 1.06,
+ "type": "float",
+ "granularity": "monthly",
+ "user_uuid": "user-1"
+ },
+ {
+ "key": "key-2",
+ "value": 2.12,
+ "type": "float",
+ "granularity": "monthly",
+ "user_uuid": "user-2"
+ }
+ ]
+ }""";
+ }
+
+ private static String expectedLanguageJson() {
+ return """
+ {
+ "message_uuid": "message-uuid",
+ "installation_id": "installation-id",
+ "dimension": "language",
+ "metric_values": [
+ {
+ "key": "key-0",
+ "value": true,
+ "type": "boolean",
+ "granularity": "monthly",
+ "language": "java"
+ },
+ {
+ "key": "key-2",
+ "value": true,
+ "type": "boolean",
+ "granularity": "monthly",
+ "language": "java"
+ },
+ {
+ "key": "key-1",
+ "value": false,
+ "type": "boolean",
+ "granularity": "monthly",
+ "language": "java"
+ }
+ ]
+ }""";
+ }
+
+}
diff --git a/server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/util/SentMetricsStorageTest.java b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/util/SentMetricsStorageTest.java
new file mode 100644
index 00000000000..8bced415452
--- /dev/null
+++ b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/util/SentMetricsStorageTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.metrics.util;
+
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.sonar.db.telemetry.TelemetryMetricsSentDto;
+import org.sonar.telemetry.Dimension;
+import org.sonar.telemetry.Granularity;
+
+public class SentMetricsStorageTest {
+
+ public static final String METRIC_1 = "metric-1";
+ public static final String METRIC_2 = "metric-2";
+ public static final String METRIC_3 = "metric-3";
+ public static final String METRIC_4 = "metric-4";
+
+ @DataProvider
+ public static Object[][] data() {
+ return new Object[][]{
+ // Dimension: INSTALLATION
+ {Dimension.INSTALLATION, METRIC_1, Granularity.DAILY, false}, // 1 minute ago
+ {Dimension.INSTALLATION, METRIC_1, Granularity.WEEKLY, false}, // 1 minute ago
+ {Dimension.INSTALLATION, METRIC_1, Granularity.MONTHLY, false}, // 1 minute ago
+
+ // Dimension: USER
+ {Dimension.USER, METRIC_2, Granularity.DAILY, true}, // 2 days ago
+ {Dimension.USER, METRIC_2, Granularity.WEEKLY, false}, // 2 days ago
+ {Dimension.USER, METRIC_2, Granularity.MONTHLY, false}, // 2 days ago
+
+ // Dimension: PROJECT
+ {Dimension.PROJECT, METRIC_3, Granularity.DAILY, true}, // 10 days ago
+ {Dimension.PROJECT, METRIC_3, Granularity.WEEKLY, true}, // 10 days ago
+ {Dimension.PROJECT, METRIC_3, Granularity.MONTHLY, false}, // 10 days ago
+
+ // Dimension: LANGUAGE
+ {Dimension.LANGUAGE, METRIC_4, Granularity.DAILY, true}, // 40 days ago
+ {Dimension.LANGUAGE, METRIC_4, Granularity.WEEKLY, true}, // 40 days ago
+ {Dimension.LANGUAGE, METRIC_4, Granularity.MONTHLY, true}, // 40 days ago
+
+ // Non-existing metrics that should be sent, as they are sent for the first time
+ {Dimension.INSTALLATION, "metric-5", Granularity.DAILY, true},
+ {Dimension.USER, "metric-6", Granularity.WEEKLY, true},
+ {Dimension.PROJECT, "metric-7", Granularity.MONTHLY, true}
+ };
+ }
+
+ @ParameterizedTest
+ @MethodSource("data")
+ void shouldSendMetric(Dimension dimension, String metricKey, Granularity granularity, boolean expectedResult) {
+ SentMetricsStorage storage = new SentMetricsStorage(getDtos());
+ boolean actualResult = storage.shouldSendMetric(dimension, metricKey, granularity);
+ Assertions.assertEquals(expectedResult, actualResult);
+ }
+
+ private List<TelemetryMetricsSentDto> getDtos() {
+ TelemetryMetricsSentDto dto1 = new TelemetryMetricsSentDto(METRIC_1, Dimension.INSTALLATION.getValue());
+ dto1.setLastSent(Instant.now().minus(1, ChronoUnit.MINUTES).toEpochMilli()); // 1 minute ago
+
+ TelemetryMetricsSentDto dto2 = new TelemetryMetricsSentDto(METRIC_2, Dimension.USER.getValue());
+ dto2.setLastSent(Instant.now().minus(2, ChronoUnit.DAYS).toEpochMilli()); // 2 days ago
+
+ TelemetryMetricsSentDto dto3 = new TelemetryMetricsSentDto(METRIC_3, Dimension.PROJECT.getValue());
+ dto3.setLastSent(Instant.now().minus(10, ChronoUnit.DAYS).toEpochMilli()); // 10 days ago
+
+ TelemetryMetricsSentDto dto4 = new TelemetryMetricsSentDto(METRIC_4, Dimension.LANGUAGE.getValue());
+ dto4.setLastSent(Instant.now().minus(40, ChronoUnit.DAYS).toEpochMilli()); // 40 days ago
+
+ return Arrays.asList(dto1, dto2, dto3, dto4);
+ }
+
+}
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 631e9f88854..aa63a07edd1 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
@@ -184,6 +184,7 @@ import org.sonar.server.platform.PersistentSettings;
import org.sonar.server.platform.SystemInfoWriterModule;
import org.sonar.server.platform.WebCoreExtensionsInstaller;
import org.sonar.server.platform.db.CheckAnyonePermissionsAtStartup;
+import org.sonar.server.platform.telemetry.TelemetryNclocProvider;
import org.sonar.server.platform.telemetry.TelemetryVersionProvider;
import org.sonar.server.platform.web.ActionDeprecationLoggerInterceptor;
import org.sonar.server.platform.web.SonarLintConnectionFilter;
@@ -282,13 +283,13 @@ 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.TelemetryClient;
+import org.sonar.telemetry.TelemetryDaemon;
import org.sonar.telemetry.legacy.CloudUsageDataProvider;
import org.sonar.telemetry.legacy.QualityProfileDataProvider;
-import org.sonar.telemetry.legacy.TelemetryClient;
-import org.sonar.telemetry.legacy.TelemetryDaemon;
import org.sonar.telemetry.legacy.TelemetryDataJsonWriter;
import org.sonar.telemetry.legacy.TelemetryDataLoaderImpl;
+import org.sonar.telemetry.metrics.TelemetryMetricsLoader;
import org.sonar.telemetry.project.ProjectCppAutoconfigTelemetryProvider;
import org.sonar.telemetry.user.TelemetryUserEnabledProvider;
@@ -660,7 +661,14 @@ public class PlatformLevel4 extends PlatformLevel {
RecoveryIndexer.class,
IndexersImpl.class,
+ //new telemetry metrics
+ ProjectCppAutoconfigTelemetryProvider.class,
+ TelemetryVersionProvider.class,
+ TelemetryNclocProvider.class,
+ TelemetryUserEnabledProvider.class,
+
// telemetry
+ TelemetryMetricsLoader.class,
TelemetryDataLoaderImpl.class,
TelemetryDataJsonWriter.class,
TelemetryDaemon.class,
@@ -668,11 +676,6 @@ public class PlatformLevel4 extends PlatformLevel {
CloudUsageDataProvider.class,
QualityProfileDataProvider.class,
- //new telemetry metrics
- TelemetryVersionProvider.class,
- TelemetryNclocProvider.class,
- ProjectCppAutoconfigTelemetryProvider.class,
- TelemetryUserEnabledProvider.class,
// monitoring
ServerMonitoringMetrics.class,
diff --git a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/TelemetryNclocProvider.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/telemetry/TelemetryNclocProvider.java
index f3f72316c9f..5e3fa11b544 100644
--- a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/TelemetryNclocProvider.java
+++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/telemetry/TelemetryNclocProvider.java
@@ -17,7 +17,7 @@
* 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;
+package org.sonar.server.platform.telemetry;
import java.util.Arrays;
import java.util.HashMap;
@@ -27,6 +27,10 @@ import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.measure.ProjectLocDistributionDto;
import org.sonar.db.metric.MetricDto;
+import org.sonar.telemetry.Dimension;
+import org.sonar.telemetry.Granularity;
+import org.sonar.telemetry.TelemetryDataProvider;
+import org.sonar.telemetry.TelemetryDataType;
import static java.util.Arrays.asList;
import static java.util.stream.Collectors.toMap;
diff --git a/server/sonar-telemetry/src/test/java/org/sonar/telemetry/TelemetryNclocProviderTest.java b/server/sonar-webserver/src/test/java/org/sonar/server/platform/telemetry/TelemetryNclocProviderTest.java
index 00aeb98086e..7c9752a8088 100644
--- a/server/sonar-telemetry/src/test/java/org/sonar/telemetry/TelemetryNclocProviderTest.java
+++ b/server/sonar-webserver/src/test/java/org/sonar/server/platform/telemetry/TelemetryNclocProviderTest.java
@@ -17,7 +17,7 @@
* 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;
+package org.sonar.server.platform.telemetry;
import java.util.Arrays;
import org.junit.jupiter.api.Test;
@@ -26,6 +26,8 @@ import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.measure.ProjectLocDistributionDto;
import org.sonar.db.metric.MetricDto;
+import org.sonar.telemetry.Dimension;
+import org.sonar.telemetry.Granularity;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
@@ -34,7 +36,7 @@ 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;
+import static org.sonar.server.platform.telemetry.TelemetryNclocProvider.METRIC_KEY;
class TelemetryNclocProviderTest {