From ffb8e4af38b27af60d9ba3127f7925580c59ca3f Mon Sep 17 00:00:00 2001 From: Alain Kermis Date: Wed, 10 Jul 2024 15:37:38 +0200 Subject: [PATCH] SONAR-22479 Implement sending new telemetry metrics --- .../java/org/sonar/db/version/SqTables.java | 1 + .../telemetry/TelemetryMetricsSentDaoIT.java | 79 ++++++ .../src/main/java/org/sonar/db/DaoModule.java | 2 + .../src/main/java/org/sonar/db/DbClient.java | 7 + .../src/main/java/org/sonar/db/MyBatis.java | 2 + .../db/telemetry/TelemetryMetricsSentDao.java | 56 ++++ .../db/telemetry/TelemetryMetricsSentDto.java | 52 ++++ .../telemetry/TelemetryMetricsSentMapper.java | 30 +++ .../org/sonar/db/telemetry/package-info.java | 23 ++ .../telemetry/TelemetryMetricsSentMapper.xml | 73 ++++++ .../TelemetryMetricsSentDtoTest.java | 39 +++ .../TelemetryMetricsSentTesting.java | 40 +++ .../org/sonar/process/ProcessProperties.java | 1 + server/sonar-server-common/build.gradle | 1 + server/sonar-telemetry/build.gradle | 4 +- .../legacy/TelemetryDataLoaderImplIT.java | 2 + .../metrics/TelemetryMetricsLoaderIT.java | 69 +++++ ...ojectCppAutoconfigTelemetryProviderIT.java | 2 +- .../user/TelemetryUserEnabledProviderIT.java | 34 ++- .../java/org/sonar/telemetry/Dimension.java | 27 +- .../java/org/sonar/telemetry/Granularity.java | 17 +- .../{legacy => }/TelemetryClient.java | 6 +- .../{legacy => }/TelemetryDaemon.java | 42 ++- .../sonar/telemetry/TelemetryDataType.java | 18 +- .../sonar/telemetry/legacy/TelemetryData.java | 20 +- .../legacy/TelemetryDataLoaderImpl.java | 2 +- .../metrics/TelemetryMetricsLoader.java | 127 +++++++++ .../metrics/TelemetryMetricsMapper.java | 93 +++++++ .../sonar/telemetry/metrics/package-info.java | 23 ++ .../telemetry/metrics/schema/BaseMessage.java | 98 +++++++ .../metrics/schema/InstallationMetric.java | 34 +++ .../metrics/schema/LanguageMetric.java | 46 ++++ .../telemetry/metrics/schema/Metric.java | 70 +++++ .../metrics/schema/ProjectMetric.java | 47 ++++ .../telemetry/metrics/schema/UserMetric.java | 47 ++++ .../metrics/schema/package-info.java | 23 ++ .../metrics/util/MessageSerializer.java | 42 +++ .../metrics/util/SentMetricsStorage.java | 78 ++++++ .../telemetry/metrics/util/package-info.java | 23 ++ .../user/TelemetryUserEnabledProvider.java | 6 +- .../org/sonar/telemetry/DimensionTest.java | 56 ++++ .../telemetry/{legacy => }/FakeServer.java | 6 +- .../org/sonar/telemetry/GranularityTest.java | 35 +++ .../TelemetryClientCompressionTest.java | 8 +- .../{legacy => }/TelemetryClientTest.java | 16 +- .../{legacy => }/TelemetryDaemonTest.java | 58 ++-- .../telemetry/TelemetryDataTypeTest.java | 35 +++ .../legacy/CloudUsageDataProviderTest.java | 50 ++-- .../legacy/TelemetryDataJsonWriterTest.java | 78 +++--- .../metrics/TelemetryMetricsMapperTest.java | 110 ++++++++ .../telemetry/metrics/TestTelemetryBean.java | 72 +++++ .../metrics/schema/BaseMessageTest.java | 105 ++++++++ .../schema/InstallationMetricTest.java | 45 ++++ .../metrics/schema/LanguageMetricTest.java | 41 +++ .../metrics/schema/ProjectMetricTest.java | 47 ++++ .../metrics/schema/UserMetricTest.java | 47 ++++ .../metrics/util/MessageSerializerTest.java | 247 ++++++++++++++++++ .../metrics/util/SentMetricsStorageTest.java | 95 +++++++ .../platformlevel/PlatformLevel4.java | 19 +- .../telemetry/TelemetryNclocProvider.java | 6 +- .../telemetry/TelemetryNclocProviderTest.java | 6 +- 61 files changed, 2437 insertions(+), 151 deletions(-) create mode 100644 server/sonar-db-dao/src/it/java/org/sonar/db/telemetry/TelemetryMetricsSentDaoIT.java create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/telemetry/TelemetryMetricsSentDao.java create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/telemetry/TelemetryMetricsSentDto.java create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/telemetry/TelemetryMetricsSentMapper.java create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/telemetry/package-info.java create mode 100644 server/sonar-db-dao/src/main/resources/org/sonar/db/telemetry/TelemetryMetricsSentMapper.xml create mode 100644 server/sonar-db-dao/src/test/java/org/sonar/db/telemetry/TelemetryMetricsSentDtoTest.java create mode 100644 server/sonar-db-dao/src/testFixtures/java/org/sonar/db/telemetry/TelemetryMetricsSentTesting.java create mode 100644 server/sonar-telemetry/src/it/java/org/sonar/telemetry/metrics/TelemetryMetricsLoaderIT.java rename server/sonar-telemetry/src/it/java/org/sonar/telemetry/{legacy => }/project/ProjectCppAutoconfigTelemetryProviderIT.java (99%) rename server/sonar-telemetry/src/it/java/org/sonar/telemetry/{legacy => }/user/TelemetryUserEnabledProviderIT.java (68%) rename server/sonar-telemetry/src/main/java/org/sonar/telemetry/{legacy => }/TelemetryClient.java (99%) rename server/sonar-telemetry/src/main/java/org/sonar/telemetry/{legacy => }/TelemetryDaemon.java (82%) create mode 100644 server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/TelemetryMetricsLoader.java create mode 100644 server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/TelemetryMetricsMapper.java create mode 100644 server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/package-info.java create mode 100644 server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/BaseMessage.java create mode 100644 server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/InstallationMetric.java create mode 100644 server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/LanguageMetric.java create mode 100644 server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/Metric.java create mode 100644 server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/ProjectMetric.java create mode 100644 server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/UserMetric.java create mode 100644 server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/package-info.java create mode 100644 server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/util/MessageSerializer.java create mode 100644 server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/util/SentMetricsStorage.java create mode 100644 server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/util/package-info.java create mode 100644 server/sonar-telemetry/src/test/java/org/sonar/telemetry/DimensionTest.java rename server/sonar-telemetry/src/test/java/org/sonar/telemetry/{legacy => }/FakeServer.java (93%) create mode 100644 server/sonar-telemetry/src/test/java/org/sonar/telemetry/GranularityTest.java rename server/sonar-telemetry/src/test/java/org/sonar/telemetry/{legacy => }/TelemetryClientCompressionTest.java (92%) rename server/sonar-telemetry/src/test/java/org/sonar/telemetry/{legacy => }/TelemetryClientTest.java (87%) rename server/sonar-telemetry/src/test/java/org/sonar/telemetry/{legacy => }/TelemetryDaemonTest.java (82%) create mode 100644 server/sonar-telemetry/src/test/java/org/sonar/telemetry/TelemetryDataTypeTest.java create mode 100644 server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/TelemetryMetricsMapperTest.java create mode 100644 server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/TestTelemetryBean.java create mode 100644 server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/schema/BaseMessageTest.java create mode 100644 server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/schema/InstallationMetricTest.java create mode 100644 server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/schema/LanguageMetricTest.java create mode 100644 server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/schema/ProjectMetricTest.java create mode 100644 server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/schema/UserMetricTest.java create mode 100644 server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/util/MessageSerializerTest.java create mode 100644 server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/util/SentMetricsStorageTest.java rename server/{sonar-telemetry/src/main/java/org/sonar => sonar-webserver/src/main/java/org/sonar/server/platform}/telemetry/TelemetryNclocProvider.java (94%) rename server/{sonar-telemetry/src/test/java/org/sonar => sonar-webserver/src/test/java/org/sonar/server/platform}/telemetry/TelemetryNclocProviderTest.java (93%) 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 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 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 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 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 @@ + + + + + + + m.metric_key as metricKey, + m.dimension as dimension, + m.last_sent as lastSent + + + + + + 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 + + + + 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) + + + + 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) + + + + 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); + + + 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> 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 similarity index 99% rename from server/sonar-telemetry/src/it/java/org/sonar/telemetry/legacy/project/ProjectCppAutoconfigTelemetryProviderIT.java rename to 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 similarity index 68% rename from server/sonar-telemetry/src/it/java/org/sonar/telemetry/legacy/user/TelemetryUserEnabledProviderIT.java rename to 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 uuidValues = underTest.getUuidValues(); - Assertions.assertThat(uuidValues).isEmpty(); + assertThat(uuidValues).isEmpty(); } @Test @@ -59,9 +61,9 @@ class TelemetryUserEnabledProviderIT { Map 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 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 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 similarity index 99% rename from server/sonar-telemetry/src/main/java/org/sonar/telemetry/legacy/TelemetryClient.java rename to 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 similarity index 82% rename from server/sonar-telemetry/src/main/java/org/sonar/telemetry/legacy/TelemetryDaemon.java rename to 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 { + 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 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 plugins) { + public Builder setPlugins(Map 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> providers; + + public TelemetryMetricsLoader(Server server, DbClient dbClient, UuidFactory uuidFactory, List> 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 metricsSentDtos = dbClient.telemetryMetricsSentDao().selectAll(dbSession); + SentMetricsStorage storage = new SentMetricsStorage(metricsSentDtos); + + Map> telemetryDataMap = new LinkedHashMap<>(); + for (TelemetryDataProvider provider : this.providers) { + boolean shouldSendMetric = storage.shouldSendMetric(provider.getDimension(), provider.getMetricKey(), provider.getGranularity()); + if (shouldSendMetric) { + Set newMetrics = TelemetryMetricsMapper.mapFromDataProvider(provider); + telemetryDataMap.computeIfAbsent(provider.getDimension(), k -> new LinkedHashSet<>()).addAll(newMetrics); + + Optional 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 baseMessages = retrieveBaseMessages(telemetryDataMap); + context.setBaseMessages(baseMessages); + return context; + } + } + + private Set retrieveBaseMessages(Map> 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 baseMessages; + List metricsSentDtos; + + public Context() { + baseMessages = new LinkedHashSet<>(); + metricsSentDtos = new ArrayList<>(); + } + + protected void addDto(TelemetryMetricsSentDto dto) { + this.metricsSentDtos.add(dto); + } + + protected void setBaseMessages(Set baseMessages){ + this.baseMessages = baseMessages; + } + + public Set getMessages() { + return baseMessages; + } + + public List 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 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 mapInstallationMetric(TelemetryDataProvider provider) { + return Collections.singleton(new InstallationMetric( + provider.getMetricKey(), + provider.getValue(), + provider.getType(), + provider.getGranularity() + )); + } + + private static Set 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 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 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 metrics; + + protected BaseMessage(String messageUuid, String installationId, Dimension dimension, Set 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 getMetrics() { + return metrics; + } + + public static class Builder { + private String messageUuid; + private String installationId; + private Dimension dimension; + private Set 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 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> dimensionMetricKeyMap = new EnumMap<>(Dimension.class); + + public SentMetricsStorage(List dtoList) { + dtoList.forEach(dto -> dimensionMetricKeyMap + .computeIfAbsent(Dimension.fromValue(dto.getDimension()), k -> new HashMap<>()) + .put(dto.getMetricKey(), dto)); + } + + public Optional getMetricsSentDto(Dimension dimension, String metricKey) { + Map 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 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 userDtos = null; + List 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 similarity index 93% rename from server/sonar-telemetry/src/test/java/org/sonar/telemetry/legacy/FakeServer.java rename to 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 similarity index 92% rename from server/sonar-telemetry/src/test/java/org/sonar/telemetry/legacy/TelemetryClientCompressionTest.java rename to 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 similarity index 87% rename from server/sonar-telemetry/src/test/java/org/sonar/telemetry/legacy/TelemetryClientTest.java rename to 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 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 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 similarity index 82% rename from server/sonar-telemetry/src/test/java/org/sonar/telemetry/legacy/TelemetryDaemonTest.java rename to 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 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 provider = new TestTelemetryBean(Dimension.INSTALLATION); + + Set metrics = TelemetryMetricsMapper.mapFromDataProvider(provider); + List 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 provider = new TestTelemetryBean(Dimension.USER); + + Set metrics = TelemetryMetricsMapper.mapFromDataProvider(provider); + List list = retrieveList(metrics); + + assertThat(list) + .extracting(UserMetric::getKey, UserMetric::getType, UserMetric::getUserUuid, UserMetric::getValue, UserMetric::getGranularity) + .containsExactlyInAnyOrder( + expected() + ); + } + + @Test + void mapFromDataProvider_whenLanguageProvider() { + TelemetryDataProvider provider = new TestTelemetryBean(Dimension.LANGUAGE); + + Set metrics = TelemetryMetricsMapper.mapFromDataProvider(provider); + List list = retrieveList(metrics); + + assertThat(list) + .extracting(LanguageMetric::getKey, LanguageMetric::getType, LanguageMetric::getLanguage, LanguageMetric::getValue, LanguageMetric::getGranularity) + .containsExactlyInAnyOrder( + expected() + ); + } + + @Test + void mapFromDataProvider_whenProjectProvider() { + TelemetryDataProvider provider = new TestTelemetryBean(Dimension.PROJECT); + + Set metrics = TelemetryMetricsMapper.mapFromDataProvider(provider); + List 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 List retrieveList(Set metrics) { + return new ArrayList<>((Set) 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 { + + 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 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 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 installationMetrics = (Set) (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 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 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 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 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 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 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 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 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 similarity index 94% rename from server/sonar-telemetry/src/main/java/org/sonar/telemetry/TelemetryNclocProvider.java rename to 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 similarity index 93% rename from server/sonar-telemetry/src/test/java/org/sonar/telemetry/TelemetryNclocProviderTest.java rename to 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 { -- 2.39.5