]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-23327 Added support for sending telemetry data in compute engine process
authorlukasz-jarocki-sonarsource <lukasz.jarocki@sonarsource.com>
Fri, 11 Oct 2024 10:13:17 +0000 (12:13 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 11 Oct 2024 20:02:43 +0000 (20:02 +0000)
53 files changed:
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/batch/BatchReportReader.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/batch/BatchReportReaderImpl.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/ReportComputationSteps.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/SendAnalysisTelemetryStep.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/batch/BatchReportReaderRule.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/ReportComputationStepsTest.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/SendAnalysisTelemetryStepTest.java [new file with mode: 0644]
server/sonar-ce/src/main/java/org/sonar/ce/CeTaskCommonsModule.java
server/sonar-telemetry-core/build.gradle
server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/Dimension.java
server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/MessageSerializer.java [new file with mode: 0644]
server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/TelemetryClient.java [new file with mode: 0644]
server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/schema/AnalysisMetric.java [new file with mode: 0644]
server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/schema/BaseMessage.java [new file with mode: 0644]
server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/schema/InstallationMetric.java [new file with mode: 0644]
server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/schema/LanguageMetric.java [new file with mode: 0644]
server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/schema/Metric.java [new file with mode: 0644]
server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/schema/ProjectMetric.java [new file with mode: 0644]
server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/schema/UserMetric.java [new file with mode: 0644]
server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/schema/package-info.java [new file with mode: 0644]
server/sonar-telemetry-core/src/test/java/org/sonar/telemetry/core/TelemetryClientTest.java [new file with mode: 0644]
server/sonar-telemetry-core/src/test/java/org/sonar/telemetry/core/schema/AnalysisMetricTest.java [new file with mode: 0644]
server/sonar-telemetry-core/src/test/java/org/sonar/telemetry/core/schema/BaseMessageTest.java [new file with mode: 0644]
server/sonar-telemetry-core/src/test/java/org/sonar/telemetry/core/schema/InstallationMetricTest.java [new file with mode: 0644]
server/sonar-telemetry-core/src/test/java/org/sonar/telemetry/core/schema/LanguageMetricTest.java [new file with mode: 0644]
server/sonar-telemetry-core/src/test/java/org/sonar/telemetry/core/schema/ProjectMetricTest.java [new file with mode: 0644]
server/sonar-telemetry-core/src/test/java/org/sonar/telemetry/core/schema/UserMetricTest.java [new file with mode: 0644]
server/sonar-telemetry/src/it/java/org/sonar/telemetry/metrics/TelemetryMetricsLoaderIT.java
server/sonar-telemetry/src/main/java/org/sonar/telemetry/TelemetryClient.java [deleted file]
server/sonar-telemetry/src/main/java/org/sonar/telemetry/TelemetryDaemon.java
server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/TelemetryMetricsLoader.java
server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/TelemetryMetricsMapper.java
server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/BaseMessage.java [deleted file]
server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/InstallationMetric.java [deleted file]
server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/LanguageMetric.java [deleted file]
server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/Metric.java [deleted file]
server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/ProjectMetric.java [deleted file]
server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/UserMetric.java [deleted file]
server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/schema/package-info.java [deleted file]
server/sonar-telemetry/src/main/java/org/sonar/telemetry/metrics/util/MessageSerializer.java [deleted file]
server/sonar-telemetry/src/test/java/org/sonar/telemetry/TelemetryClientCompressionTest.java
server/sonar-telemetry/src/test/java/org/sonar/telemetry/TelemetryClientTest.java [deleted file]
server/sonar-telemetry/src/test/java/org/sonar/telemetry/TelemetryDaemonTest.java
server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/TelemetryMetricsMapperTest.java
server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/schema/BaseMessageTest.java [deleted file]
server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/schema/InstallationMetricTest.java [deleted file]
server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/schema/LanguageMetricTest.java [deleted file]
server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/schema/ProjectMetricTest.java [deleted file]
server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/schema/UserMetricTest.java [deleted file]
server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/util/MessageSerializerTest.java
server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
sonar-scanner-protocol/src/it/java/org/sonar/scanner/protocol/output/ScannerReportReaderIT.java
sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportReader.java

index de85e094a67374dd6e87c962ad1d654e523c2571..5b908c4d4a386cbaf703df33e406c1ee2ea49e5d 100644 (file)
@@ -72,4 +72,6 @@ public interface BatchReportReader {
   CloseableIterator<ScannerReport.AnalysisWarning> readAnalysisWarnings();
 
   CloseableIterator<ScannerReport.Cve> readCves();
+
+  CloseableIterator<ScannerReport.TelemetryEntry> readTelemetryEntries();
 }
index df17a5ac89b680183c3e11e72bf166e3f901b16c..740d7a259d7a03658ca8fec5b0a97faf6c113b22 100644 (file)
@@ -233,4 +233,10 @@ public class BatchReportReaderImpl implements BatchReportReader {
     ensureInitialized();
     return delegate.readCves();
   }
+
+  @Override
+  public CloseableIterator<ScannerReport.TelemetryEntry> readTelemetryEntries() {
+    ensureInitialized();
+    return delegate.readTelemetryEntries();
+  }
 }
index 466be8f175f15c917fe0965f4a4d2f96a2c2461b..00c21bd5357b07ebf559e90f80a6570c7f6594ad 100644 (file)
@@ -41,6 +41,7 @@ public class ReportComputationSteps extends AbstractComputationSteps {
 
   private static final List<Class<? extends ComputationStep>> STEPS = Arrays.asList(
     ExtractReportStep.class,
+    SendAnalysisTelemetryStep.class,
     PersistScannerContextStep.class,
     PersistAnalysisWarningsStep.class,
     GenerateAnalysisUuid.class,
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/SendAnalysisTelemetryStep.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/SendAnalysisTelemetryStep.java
new file mode 100644 (file)
index 0000000..62700fa
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * 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.ce.task.projectanalysis.step;
+
+import java.util.HashSet;
+import java.util.Set;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.platform.Server;
+import org.sonar.ce.task.projectanalysis.batch.BatchReportReader;
+import org.sonar.ce.task.step.ComputationStep;
+import org.sonar.core.util.CloseableIterator;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.scanner.protocol.output.ScannerReport;
+import org.sonar.telemetry.core.Dimension;
+import org.sonar.telemetry.core.MessageSerializer;
+import org.sonar.telemetry.core.TelemetryClient;
+import org.sonar.telemetry.core.schema.AnalysisMetric;
+import org.sonar.telemetry.core.schema.BaseMessage;
+import org.sonar.telemetry.core.schema.Metric;
+
+import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_ENABLE;
+
+public class SendAnalysisTelemetryStep implements ComputationStep {
+
+  private final TelemetryClient telemetryClient;
+  private final BatchReportReader batchReportReader;
+  private final Server server;
+  private final UuidFactory uuidFactory;
+  private final Configuration config;
+
+  public SendAnalysisTelemetryStep(TelemetryClient telemetryClient, BatchReportReader batchReportReader,
+    UuidFactory uuidFactory, Server server, Configuration configuration) {
+    this.telemetryClient = telemetryClient;
+    this.batchReportReader = batchReportReader;
+    this.server = server;
+    this.uuidFactory = uuidFactory;
+    this.config = configuration;
+  }
+
+  @Override
+  public void execute(Context context) {
+    if (!config.getBoolean(SONAR_TELEMETRY_ENABLE.getKey()).orElse(false)) {
+      return;
+    }
+    try (CloseableIterator<ScannerReport.TelemetryEntry> it = batchReportReader.readTelemetryEntries()) {
+      Set<Metric> metrics = new HashSet<>();
+      // it was agreed to limit the number of telemetry entries to 1000 per one analysis
+      final int limit = 1000;
+      int count = 0;
+      while (it.hasNext() && count++ < limit) {
+        ScannerReport.TelemetryEntry telemetryEntry = it.next();
+        metrics.add(new AnalysisMetric(telemetryEntry.getKey(), telemetryEntry.getValue()));
+      }
+
+      if (metrics.isEmpty()) {
+        return;
+      }
+      BaseMessage baseMessage = new BaseMessage.Builder()
+        .setMessageUuid(uuidFactory.create())
+        .setInstallationId(server.getId())
+        .setDimension(Dimension.ANALYSIS)
+        .setMetrics(metrics)
+        .build();
+
+      String jsonString = MessageSerializer.serialize(baseMessage);
+      telemetryClient.uploadMetricAsync(jsonString);
+    }
+
+  }
+
+  @Override
+  public String getDescription() {
+    return "This step pushes telemetry data from the Sonar analyzers to Telemetry V2 server in case telemetry is enabled.";
+  }
+}
\ No newline at end of file
index b960803b18a3d0c35d68098fb54469241efb05cd..9eda82d4f2c40bda2872bcf58c3c5ca8c01ec0ea 100644 (file)
@@ -63,6 +63,7 @@ public class BatchReportReaderRule implements TestRule, BatchReportReader, After
   private List<ScannerReport.AnalysisWarning> analysisWarnings = Collections.emptyList();
   private byte[] analysisCache;
   private List<ScannerReport.Cve> cves = new ArrayList<>();
+  private List<ScannerReport.TelemetryEntry> telemetryEntries = new ArrayList<>();
 
   @Override
   public Statement apply(final Statement statement, Description description) {
@@ -333,6 +334,16 @@ public class BatchReportReaderRule implements TestRule, BatchReportReader, After
     return CloseableIterator.from(cves.iterator());
   }
 
+  @Override
+  public CloseableIterator<ScannerReport.TelemetryEntry> readTelemetryEntries() {
+    return CloseableIterator.from(telemetryEntries.iterator());
+  }
+
+  public BatchReportReaderRule putTelemetry(List<ScannerReport.TelemetryEntry> telemetryEntries) {
+    this.telemetryEntries = telemetryEntries;
+    return this;
+  }
+
   public BatchReportReaderRule putCves(List<ScannerReport.Cve> cves) {
     this.cves = cves;
     return this;
index cdbd274b5f689e9a9f469b9d064a821eb9ffde53..e060a69ba47232ef11d0e4f5505bcd8ca6328fe5 100644 (file)
@@ -56,6 +56,6 @@ public class ReportComputationStepsTest {
     Iterable<ComputationStep> instances = new ReportComputationSteps(computeEngineContainer).instances();
     assertThatThrownBy(() -> newArrayList(instances))
       .isInstanceOf(IllegalStateException.class)
-      .hasMessageContaining("org.sonar.ce.task.projectanalysis.step.PersistScannerContextStep");
+      .hasMessageContaining("org.sonar.ce.task.projectanalysis.step.SendAnalysisTelemetryStep");
   }
 }
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/SendAnalysisTelemetryStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/SendAnalysisTelemetryStepTest.java
new file mode 100644 (file)
index 0000000..7e0c196
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * 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.ce.task.projectanalysis.step;
+
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.platform.Server;
+import org.sonar.ce.task.projectanalysis.batch.BatchReportReader;
+import org.sonar.ce.task.step.ComputationStep;
+import org.sonar.core.util.CloseableIterator;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.scanner.protocol.output.ScannerReport;
+import org.sonar.telemetry.core.TelemetryClient;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+class SendAnalysisTelemetryStepTest {
+
+  private final TelemetryClient telemetryClient = mock();
+  private final BatchReportReader batchReportReader = mock();
+  private final UuidFactory uuidFactory = mock();
+  private final Server server = mock();
+  private final ComputationStep.Context context = mock();
+  private final Configuration configuration = mock();
+  private final SendAnalysisTelemetryStep underTest = new SendAnalysisTelemetryStep(telemetryClient, batchReportReader, uuidFactory,
+    server, configuration);
+
+  {
+    when(uuidFactory.create()).thenReturn("uuid");
+    when(server.getId()).thenReturn("serverId");
+    when(configuration.getBoolean("sonar.telemetry.enable")).thenReturn(Optional.of(true));
+  }
+
+  @Test
+  void execute_whenNoMetrics_dontSendAnything() {
+    when(batchReportReader.readTelemetryEntries()).thenReturn(CloseableIterator.emptyCloseableIterator());
+
+    underTest.execute(context);
+
+    verifyNoInteractions(telemetryClient);
+  }
+
+  @Test
+  void execute_whenTwoMetrics_callTelemetryClientOnce() {
+    Set<ScannerReport.TelemetryEntry> telemetryEntries = Set.of(
+      ScannerReport.TelemetryEntry.newBuilder().setKey("key1").setValue("value1").build(),
+      ScannerReport.TelemetryEntry.newBuilder().setKey("key2").setValue("value2").build());
+    when(batchReportReader.readTelemetryEntries()).thenReturn(CloseableIterator.from(telemetryEntries.iterator()));
+
+    underTest.execute(context);
+
+    verify(telemetryClient, times(1)).uploadMetricAsync(anyString());
+  }
+
+  @Test
+  void execute_whenMetricsPresentAndTelemetryNotEnabled_dontCallTelemetryClient() {
+    when(configuration.getBoolean("sonar.telemetry.enable")).thenReturn(Optional.of(false));
+    Set<ScannerReport.TelemetryEntry> telemetryEntries = Set.of(
+      ScannerReport.TelemetryEntry.newBuilder().setKey("key1").setValue("value1").build(),
+      ScannerReport.TelemetryEntry.newBuilder().setKey("key2").setValue("value2").build());
+    when(batchReportReader.readTelemetryEntries()).thenReturn(CloseableIterator.from(telemetryEntries.iterator()));
+
+    underTest.execute(context);
+
+    verifyNoInteractions(telemetryClient);
+  }
+
+  @Test
+  void execute_when2000entries_sendOnly1000entries() {
+    Set<ScannerReport.TelemetryEntry> telemetryEntries = new HashSet<>();
+    for (int i = 0; i < 2000; i++) {
+      telemetryEntries.add(ScannerReport.TelemetryEntry.newBuilder().setKey(String.valueOf(i)).setValue("value" + i).build());
+    }
+    when(batchReportReader.readTelemetryEntries()).thenReturn(CloseableIterator.from(telemetryEntries.iterator()));
+
+    underTest.execute(context);
+
+    ArgumentCaptor<String> argumentCaptor = ArgumentCaptor.forClass(String.class);
+    verify(telemetryClient, times(1)).uploadMetricAsync(argumentCaptor.capture());
+
+    String capturedArgument = argumentCaptor.getValue();
+    assertEquals(1000 + 1, capturedArgument.split("key").length);
+  }
+}
index 7a8a110c856048639c305402b85ba323991b498e..396a50eb5fd475b50840deaa256eb4db6a97784a 100644 (file)
@@ -23,6 +23,7 @@ import org.sonar.ce.task.projectanalysis.purge.IndexPurgeListener;
 import org.sonar.ce.task.projectanalysis.purge.ProjectCleaner;
 import org.sonar.core.platform.Module;
 import org.sonar.db.purge.period.DefaultPeriodCleaner;
+import org.sonar.telemetry.core.TelemetryClient;
 
 /**
  * Globally available components in CE for tasks to use.
@@ -33,6 +34,7 @@ public class CeTaskCommonsModule extends Module {
     add(
       DefaultPeriodCleaner.class,
       ProjectCleaner.class,
-      IndexPurgeListener.class);
+      IndexPurgeListener.class,
+      TelemetryClient.class);
   }
 }
index 47330b978214a815ef6a9ea9948286bb7dced85e..7ae81051fd70e7d56ebdd414fb670885c684fe3b 100644 (file)
@@ -9,12 +9,16 @@ sonar {
 dependencies {
     compileOnlyApi 'com.github.spotbugs:spotbugs-annotations'
 
+    implementation project(':sonar-core')
+    implementation project(':server:sonar-process')
+
     implementation 'com.fasterxml.jackson.core:jackson-databind'
 
     testImplementation(platform("org.junit:junit-bom"))
     testImplementation 'org.assertj:assertj-core'
     testImplementation 'org.junit.jupiter:junit-jupiter-api'
     testImplementation 'org.junit.jupiter:junit-jupiter-params'
+    testImplementation 'org.mockito:mockito-core'
 
     testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
 }
index 96f9ce5b5fd8c2d8a2603066631c4f90f7125241..e3362120f22c059a882264da472a05cb90175cb2 100644 (file)
@@ -30,7 +30,8 @@ public enum Dimension {
   INSTALLATION("installation"),
   USER("user"),
   PROJECT("project"),
-  LANGUAGE("language");
+  LANGUAGE("language"),
+  ANALYSIS("analysis");
 
   private final String value;
 
diff --git a/server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/MessageSerializer.java b/server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/MessageSerializer.java
new file mode 100644 (file)
index 0000000..fef42ad
--- /dev/null
@@ -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.core;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import org.sonar.telemetry.core.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-core/src/main/java/org/sonar/telemetry/core/TelemetryClient.java b/server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/TelemetryClient.java
new file mode 100644 (file)
index 0000000..d17a103
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ * 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.core;
+
+import java.io.IOException;
+import okhttp3.Call;
+import okhttp3.MediaType;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+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.ce.ComputeEngineSide;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.server.ServerSide;
+
+import static org.sonar.process.ProcessProperties.Property;
+
+@ComputeEngineSide
+@ServerSide
+public class TelemetryClient implements Startable {
+  private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
+  private static final Logger LOG = LoggerFactory.getLogger(TelemetryClient.class);
+
+  private final OkHttpClient okHttpClient;
+  private final Configuration config;
+  private String serverUrl;
+  private String metricsServerUrl;
+  private boolean compression;
+
+  public TelemetryClient(OkHttpClient okHttpClient, Configuration config) {
+    this.config = config;
+    this.okHttpClient = okHttpClient;
+  }
+
+  public void upload(String json) throws IOException {
+    Request request = buildHttpRequest(serverUrl, json);
+    execute(okHttpClient.newCall(request));
+  }
+
+  public void uploadMetric(String json) throws IOException {
+    Request request = buildHttpRequest(metricsServerUrl, json);
+    execute(okHttpClient.newCall(request));
+  }
+
+  public void optOut(String json) {
+    Request.Builder request = new Request.Builder();
+    request.url(serverUrl);
+    RequestBody body = RequestBody.create(JSON, json);
+    request.delete(body);
+    try {
+      execute(okHttpClient.newCall(request.build()));
+    } catch (IOException e) {
+      LOG.debug("Error when sending opt-out usage statistics: {}", e.getMessage());
+    }
+  }
+
+  private Request buildHttpRequest(String serverUrl, String json) {
+    Request.Builder request = new Request.Builder();
+    request.addHeader("Content-Encoding", "gzip");
+    request.addHeader("Content-Type", "application/json");
+    request.url(serverUrl);
+    RequestBody body = RequestBody.create(JSON, json);
+    if (compression) {
+      request.post(gzip(body));
+    } else {
+      request.post(body);
+    }
+    return request.build();
+  }
+
+  private static RequestBody gzip(final RequestBody body) {
+    return new RequestBody() {
+      @Override
+      public MediaType contentType() {
+        return body.contentType();
+      }
+
+      @Override
+      public long contentLength() {
+        // We don't know the compressed length in advance!
+        return -1;
+      }
+
+      @Override
+      public void writeTo(BufferedSink sink) throws IOException {
+        BufferedSink gzipSink = Okio.buffer(new GzipSink(sink));
+        body.writeTo(gzipSink);
+        gzipSink.close();
+      }
+    };
+  }
+
+  private static void execute(Call call) throws IOException {
+    try (Response ignored = call.execute()) {
+      // auto close connection to avoid leaked connection
+    }
+  }
+
+  @Override
+  public void start() {
+    this.serverUrl = config.get(Property.SONAR_TELEMETRY_URL.getKey())
+      .orElseThrow(() -> new IllegalStateException(String.format("Setting '%s' must be provided.", Property.SONAR_TELEMETRY_URL)));
+    this.metricsServerUrl = config.get(Property.SONAR_TELEMETRY_METRICS_URL.getKey())
+      .orElseThrow(() -> new IllegalStateException(String.format("Setting '%s' must be provided.", Property.SONAR_TELEMETRY_METRICS_URL)));
+    this.compression = config.getBoolean(Property.SONAR_TELEMETRY_COMPRESSION.getKey()).orElse(true);
+  }
+
+  @Override
+  public void stop() {
+    // Nothing to do
+  }
+
+  public void uploadMetricAsync(String jsonString) {
+    Thread thread = new Thread(() -> {
+      try {
+        uploadMetric(jsonString);
+      } catch (IOException e) {
+        LOG.debug("Sending telemetry messages has failed", e);
+      }
+    });
+    thread.setDaemon(true);
+    thread.start();
+  }
+}
diff --git a/server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/schema/AnalysisMetric.java b/server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/schema/AnalysisMetric.java
new file mode 100644 (file)
index 0000000..66372e8
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * 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.core.schema;
+
+import org.sonar.telemetry.core.Granularity;
+import org.sonar.telemetry.core.TelemetryDataType;
+
+public class AnalysisMetric extends Metric {
+
+  public AnalysisMetric(String key, String value) {
+    this.key = key;
+    this.value = value;
+    this.type = TelemetryDataType.STRING;
+    this.granularity = Granularity.ADHOC;
+  }
+}
diff --git a/server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/schema/BaseMessage.java b/server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/schema/BaseMessage.java
new file mode 100644 (file)
index 0000000..11d57b4
--- /dev/null
@@ -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.core.schema;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.Objects;
+import java.util.Set;
+import org.sonar.telemetry.core.Dimension;
+
+public class BaseMessage {
+  @JsonProperty("message_uuid")
+  private String messageUuid;
+
+  @JsonProperty("installation_id")
+  private String installationId;
+
+  @JsonProperty("dimension")
+  private Dimension dimension;
+
+  @JsonProperty("metric_values")
+  private Set<Metric> metrics;
+
+  protected BaseMessage(String messageUuid, String installationId, Dimension dimension, Set<Metric> metrics) {
+    this.messageUuid = messageUuid;
+    this.installationId = installationId;
+    this.dimension = dimension;
+    this.metrics = metrics;
+  }
+
+  public String getMessageUuid() {
+    return messageUuid;
+  }
+
+  public String getInstallationId() {
+    return installationId;
+  }
+
+  public Dimension getDimension() {
+    return dimension;
+  }
+
+  public Set<Metric> getMetrics() {
+    return metrics;
+  }
+
+  public static class Builder {
+    private String messageUuid;
+    private String installationId;
+    private Dimension dimension;
+    private Set<Metric> metrics;
+
+    public Builder setMessageUuid(String messageUuid) {
+      this.messageUuid = messageUuid;
+      return this;
+    }
+
+    public Builder setInstallationId(String installationId) {
+      this.installationId = installationId;
+      return this;
+    }
+
+    public Builder setDimension(Dimension dimension) {
+      this.dimension = dimension;
+      return this;
+    }
+
+    public Builder setMetrics(Set<Metric> metrics) {
+      this.metrics = metrics;
+      return this;
+    }
+
+    public BaseMessage build() {
+      Objects.requireNonNull(messageUuid, "messageUuid must be specified");
+      Objects.requireNonNull(installationId, "installationId must be specified");
+      Objects.requireNonNull(dimension, "dimension must be specified");
+      Objects.requireNonNull(metrics, "metrics must be specified");
+
+      return new BaseMessage(messageUuid, installationId, dimension, metrics);
+    }
+  }
+}
diff --git a/server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/schema/InstallationMetric.java b/server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/schema/InstallationMetric.java
new file mode 100644 (file)
index 0000000..583711d
--- /dev/null
@@ -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.core.schema;
+
+import javax.annotation.Nullable;
+import org.sonar.telemetry.core.Granularity;
+import org.sonar.telemetry.core.TelemetryDataType;
+
+public class InstallationMetric extends Metric {
+
+  public InstallationMetric(String key, @Nullable Object value, TelemetryDataType type, Granularity granularity) {
+    this.key = key;
+    this.value = value;
+    this.type = type;
+    this.granularity = granularity;
+  }
+
+}
diff --git a/server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/schema/LanguageMetric.java b/server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/schema/LanguageMetric.java
new file mode 100644 (file)
index 0000000..0f1974b
--- /dev/null
@@ -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.core.schema;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.sonar.telemetry.core.Granularity;
+import org.sonar.telemetry.core.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-core/src/main/java/org/sonar/telemetry/core/schema/Metric.java b/server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/schema/Metric.java
new file mode 100644 (file)
index 0000000..dfe8b7f
--- /dev/null
@@ -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.core.schema;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.sonar.telemetry.core.Granularity;
+import org.sonar.telemetry.core.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-core/src/main/java/org/sonar/telemetry/core/schema/ProjectMetric.java b/server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/schema/ProjectMetric.java
new file mode 100644 (file)
index 0000000..83eb95b
--- /dev/null
@@ -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.core.schema;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.sonar.telemetry.core.Granularity;
+import org.sonar.telemetry.core.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-core/src/main/java/org/sonar/telemetry/core/schema/UserMetric.java b/server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/schema/UserMetric.java
new file mode 100644 (file)
index 0000000..a9e49e0
--- /dev/null
@@ -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.core.schema;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.sonar.telemetry.core.Granularity;
+import org.sonar.telemetry.core.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-core/src/main/java/org/sonar/telemetry/core/schema/package-info.java b/server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/schema/package-info.java
new file mode 100644 (file)
index 0000000..4656410
--- /dev/null
@@ -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.core.schema;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-telemetry-core/src/test/java/org/sonar/telemetry/core/TelemetryClientTest.java b/server/sonar-telemetry-core/src/test/java/org/sonar/telemetry/core/TelemetryClientTest.java
new file mode 100644 (file)
index 0000000..3e3c7a0
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.telemetry.core;
+
+import java.io.IOException;
+import okhttp3.MediaType;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okio.Buffer;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+import org.sonar.api.config.internal.MapSettings;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_COMPRESSION;
+import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_METRICS_URL;
+import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_URL;
+
+class TelemetryClientTest {
+
+  private static final String JSON = "{\"key\":\"value\"}";
+  private static final String TELEMETRY_URL = "https://telemetry.com/url";
+  private static final String METRICS_TELEMETRY_URL = "https://telemetry.com/url/metrics";
+
+  private final OkHttpClient okHttpClient = Mockito.mock(OkHttpClient.class, Mockito.RETURNS_DEEP_STUBS);
+  private final MapSettings settings = new MapSettings();
+
+  private final TelemetryClient underTest = new TelemetryClient(okHttpClient, settings.asConfig());
+
+  @BeforeEach
+  void setProperties() {
+    settings.setProperty(SONAR_TELEMETRY_URL.getKey(), TELEMETRY_URL);
+    settings.setProperty(SONAR_TELEMETRY_METRICS_URL.getKey(), METRICS_TELEMETRY_URL);
+  }
+
+  @Test
+  void upload() throws IOException {
+    ArgumentCaptor<Request> requestCaptor = ArgumentCaptor.forClass(Request.class);
+    settings.setProperty(SONAR_TELEMETRY_COMPRESSION.getKey(), false);
+    underTest.start();
+
+    underTest.upload(JSON);
+
+    Mockito.verify(okHttpClient).newCall(requestCaptor.capture());
+    Request request = requestCaptor.getValue();
+    assertThat(request.method()).isEqualTo("POST");
+    assertThat(request.body().contentType()).isEqualTo(MediaType.parse("application/json; charset=utf-8"));
+    Buffer body = new Buffer();
+    request.body().writeTo(body);
+    assertThat(body.readUtf8()).isEqualTo(JSON);
+    assertThat(request.url()).hasToString(TELEMETRY_URL);
+  }
+
+  @Test
+  void uploadMetric() throws IOException {
+    ArgumentCaptor<Request> requestCaptor = ArgumentCaptor.forClass(Request.class);
+    settings.setProperty(SONAR_TELEMETRY_COMPRESSION.getKey(), false);
+    underTest.start();
+
+    underTest.uploadMetric(JSON);
+
+    Mockito.verify(okHttpClient).newCall(requestCaptor.capture());
+    Request request = requestCaptor.getValue();
+    assertThat(request.method()).isEqualTo("POST");
+    assertThat(request.body().contentType()).isEqualTo(MediaType.parse("application/json; charset=utf-8"));
+    Buffer body = new Buffer();
+    request.body().writeTo(body);
+    assertThat(body.readUtf8()).isEqualTo(JSON);
+    assertThat(request.url()).hasToString(METRICS_TELEMETRY_URL);
+  }
+
+  @Test
+  void opt_out() throws IOException {
+    ArgumentCaptor<Request> requestCaptor = ArgumentCaptor.forClass(Request.class);
+    underTest.start();
+
+    underTest.optOut(JSON);
+
+    Mockito.verify(okHttpClient).newCall(requestCaptor.capture());
+    Request request = requestCaptor.getValue();
+    assertThat(request.method()).isEqualTo("DELETE");
+    assertThat(request.body().contentType()).isEqualTo(MediaType.parse("application/json; charset=utf-8"));
+    Buffer body = new Buffer();
+    request.body().writeTo(body);
+    assertThat(body.readUtf8()).isEqualTo(JSON);
+    assertThat(request.url()).hasToString(TELEMETRY_URL);
+  }
+}
diff --git a/server/sonar-telemetry-core/src/test/java/org/sonar/telemetry/core/schema/AnalysisMetricTest.java b/server/sonar-telemetry-core/src/test/java/org/sonar/telemetry/core/schema/AnalysisMetricTest.java
new file mode 100644 (file)
index 0000000..13e0ee0
--- /dev/null
@@ -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.telemetry.core.schema;
+
+import org.junit.jupiter.api.Test;
+import org.sonar.telemetry.core.Granularity;
+import org.sonar.telemetry.core.TelemetryDataType;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class AnalysisMetricTest {
+
+  @Test
+  void getters() {
+    AnalysisMetric metric = new AnalysisMetric("memory", "100");
+
+    assertThat(metric.getKey()).isEqualTo("memory");
+    assertThat(metric.getValue()).isEqualTo("100");
+    assertThat(metric.getGranularity()).isEqualTo(Granularity.ADHOC);
+    assertThat(metric.getType()).isEqualTo(TelemetryDataType.STRING);
+  }
+}
diff --git a/server/sonar-telemetry-core/src/test/java/org/sonar/telemetry/core/schema/BaseMessageTest.java b/server/sonar-telemetry-core/src/test/java/org/sonar/telemetry/core/schema/BaseMessageTest.java
new file mode 100644 (file)
index 0000000..0710eb8
--- /dev/null
@@ -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.core.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.core.Dimension;
+import org.sonar.telemetry.core.Granularity;
+import org.sonar.telemetry.core.TelemetryDataType;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class BaseMessageTest {
+
+  @Test
+  void build() {
+    BaseMessage message = new BaseMessage.Builder()
+      .setMessageUuid("123e4567-e89b-12d3-a456-426614174000")
+      .setInstallationId("installation-id")
+      .setDimension(Dimension.INSTALLATION)
+      .setMetrics(installationMetrics())
+      .build();
+
+    assertThat(message.getMessageUuid()).isEqualTo("123e4567-e89b-12d3-a456-426614174000");
+    assertThat(message.getInstallationId()).isEqualTo("installation-id");
+    assertThat(message.getDimension()).isEqualTo(Dimension.INSTALLATION);
+    Set<InstallationMetric> installationMetrics = (Set<InstallationMetric>) (Set<?>) message.getMetrics();
+    assertThat(installationMetrics)
+      .extracting(InstallationMetric::getKey, InstallationMetric::getGranularity, InstallationMetric::getType, InstallationMetric::getValue)
+      .containsExactlyInAnyOrder(
+        tuple("key-0", Granularity.DAILY, TelemetryDataType.INTEGER, 0),
+        tuple("key-1", Granularity.DAILY, TelemetryDataType.INTEGER, 1),
+        tuple("key-2", Granularity.DAILY, TelemetryDataType.INTEGER, 2)
+      );
+  }
+
+  @ParameterizedTest
+  @MethodSource("invalidBaseMessageProvider")
+  void build_invalidCases(BaseMessage.Builder builder, String expectedErrorMessage) {
+    Exception exception = assertThrows(NullPointerException.class, builder::build);
+    assertEquals(expectedErrorMessage, exception.getMessage());
+  }
+
+  private static Stream<Arguments> invalidBaseMessageProvider() {
+    return Stream.of(
+      Arguments.of(
+        new BaseMessage.Builder()
+          .setInstallationId("installation-id")
+          .setDimension(Dimension.INSTALLATION)
+          .setMetrics(installationMetrics()),
+        "messageUuid must be specified"
+      ),
+      Arguments.of(
+        new BaseMessage.Builder()
+          .setMessageUuid("some-uuid")
+          .setInstallationId("installation-id")
+          .setMetrics(installationMetrics()),
+        "dimension must be specified"
+      ),
+      Arguments.of(
+        new BaseMessage.Builder()
+          .setMessageUuid("some-uuid")
+          .setDimension(Dimension.INSTALLATION)
+          .setMetrics(installationMetrics()),
+        "installationId must be specified"
+      )
+    );
+  }
+
+  private static Set<Metric> installationMetrics() {
+    return IntStream.range(0, 3)
+      .mapToObj(i -> new InstallationMetric(
+        "key-" + i,
+        i,
+        TelemetryDataType.INTEGER,
+        Granularity.DAILY
+      )).collect(Collectors.toSet());
+  }
+}
diff --git a/server/sonar-telemetry-core/src/test/java/org/sonar/telemetry/core/schema/InstallationMetricTest.java b/server/sonar-telemetry-core/src/test/java/org/sonar/telemetry/core/schema/InstallationMetricTest.java
new file mode 100644 (file)
index 0000000..7d0b11c
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * 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.core.schema;
+
+import org.junit.jupiter.api.Test;
+import org.sonar.telemetry.core.Granularity;
+import org.sonar.telemetry.core.TelemetryDataType;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class InstallationMetricTest {
+
+  @Test
+  void constructor() {
+    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);
+  }
+
+  @Test
+  void constructor_shouldAcceptNullValue() {
+    InstallationMetric metric = new InstallationMetric(
+      "installation-key-1",
+      null,
+      TelemetryDataType.STRING,
+      Granularity.WEEKLY
+    );
+
+    assertThat(metric.getValue()).isNull();
+    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-core/src/test/java/org/sonar/telemetry/core/schema/LanguageMetricTest.java b/server/sonar-telemetry-core/src/test/java/org/sonar/telemetry/core/schema/LanguageMetricTest.java
new file mode 100644 (file)
index 0000000..1b10608
--- /dev/null
@@ -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.core.schema;
+
+import org.junit.jupiter.api.Test;
+import org.sonar.telemetry.core.Granularity;
+import org.sonar.telemetry.core.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-core/src/test/java/org/sonar/telemetry/core/schema/ProjectMetricTest.java b/server/sonar-telemetry-core/src/test/java/org/sonar/telemetry/core/schema/ProjectMetricTest.java
new file mode 100644 (file)
index 0000000..636b1fb
--- /dev/null
@@ -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.core.schema;
+
+import org.junit.jupiter.api.Test;
+import org.sonar.telemetry.core.Granularity;
+import org.sonar.telemetry.core.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-core/src/test/java/org/sonar/telemetry/core/schema/UserMetricTest.java b/server/sonar-telemetry-core/src/test/java/org/sonar/telemetry/core/schema/UserMetricTest.java
new file mode 100644 (file)
index 0000000..dca676a
--- /dev/null
@@ -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.core.schema;
+
+import org.junit.jupiter.api.Test;
+import org.sonar.telemetry.core.Granularity;
+import org.sonar.telemetry.core.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");
+  }
+
+}
index 67e2ea872e8fa460aa6be3693456f7c112bbf1af..7587e7c31117da00061c9739b90e98c868590bfe 100644 (file)
@@ -40,11 +40,11 @@ import org.sonar.telemetry.core.Dimension;
 import org.sonar.telemetry.core.Granularity;
 import org.sonar.telemetry.core.TelemetryDataProvider;
 import org.sonar.telemetry.core.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.ProjectMetric;
-import org.sonar.telemetry.metrics.schema.UserMetric;
+import org.sonar.telemetry.core.schema.BaseMessage;
+import org.sonar.telemetry.core.schema.InstallationMetric;
+import org.sonar.telemetry.core.schema.LanguageMetric;
+import org.sonar.telemetry.core.schema.ProjectMetric;
+import org.sonar.telemetry.core.schema.UserMetric;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.tuple;
diff --git a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/TelemetryClient.java b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/TelemetryClient.java
deleted file mode 100644 (file)
index c1a0a94..0000000
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.telemetry;
-
-import java.io.IOException;
-import okhttp3.Call;
-import okhttp3.MediaType;
-import okhttp3.OkHttpClient;
-import okhttp3.Request;
-import okhttp3.RequestBody;
-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 static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_COMPRESSION;
-import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_URL;
-import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_METRICS_URL;
-
-@ServerSide
-public class TelemetryClient implements Startable {
-  private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
-  private static final Logger LOG = LoggerFactory.getLogger(TelemetryClient.class);
-
-  private final OkHttpClient okHttpClient;
-  private final Configuration config;
-  private String serverUrl;
-  private String metricsServerUrl;
-  private boolean compression;
-
-  public TelemetryClient(OkHttpClient okHttpClient, Configuration config) {
-    this.config = config;
-    this.okHttpClient = okHttpClient;
-  }
-
-  void upload(String json) throws IOException {
-    Request request = buildHttpRequest(serverUrl, json);
-    execute(okHttpClient.newCall(request));
-  }
-
-  void uploadMetric(String json) throws IOException {
-    Request request = buildHttpRequest(metricsServerUrl, json);
-    execute(okHttpClient.newCall(request));
-  }
-
-  void optOut(String json) {
-    Request.Builder request = new Request.Builder();
-    request.url(serverUrl);
-    RequestBody body = RequestBody.create(JSON, json);
-    request.delete(body);
-    try {
-      execute(okHttpClient.newCall(request.build()));
-    } catch (IOException e) {
-      LOG.debug("Error when sending opt-out usage statistics: {}", e.getMessage());
-    }
-  }
-
-  private Request buildHttpRequest(String serverUrl, String json) {
-    Request.Builder request = new Request.Builder();
-    request.addHeader("Content-Encoding", "gzip");
-    request.addHeader("Content-Type", "application/json");
-    request.url(serverUrl);
-    RequestBody body = RequestBody.create(JSON, json);
-    if (compression) {
-      request.post(gzip(body));
-    } else {
-      request.post(body);
-    }
-    return request.build();
-  }
-
-  private static RequestBody gzip(final RequestBody body) {
-    return new RequestBody() {
-      @Override
-      public MediaType contentType() {
-        return body.contentType();
-      }
-
-      @Override
-      public long contentLength() {
-        // We don't know the compressed length in advance!
-        return -1;
-      }
-
-      @Override
-      public void writeTo(BufferedSink sink) throws IOException {
-        BufferedSink gzipSink = Okio.buffer(new GzipSink(sink));
-        body.writeTo(gzipSink);
-        gzipSink.close();
-      }
-    };
-  }
-
-  private static void execute(Call call) throws IOException {
-    try (Response ignored = call.execute()) {
-      // auto close connection to avoid leaked connection
-    }
-  }
-
-  @Override
-  public void start() {
-    this.serverUrl = config.get(SONAR_TELEMETRY_URL.getKey())
-      .orElseThrow(() -> new IllegalStateException(String.format("Setting '%s' must be provided.", SONAR_TELEMETRY_URL)));
-    this.metricsServerUrl = config.get(SONAR_TELEMETRY_METRICS_URL.getKey())
-      .orElseThrow(() -> new IllegalStateException(String.format("Setting '%s' must be provided.", SONAR_TELEMETRY_METRICS_URL)));
-    this.compression = config.getBoolean(SONAR_TELEMETRY_COMPRESSION.getKey()).orElse(true);
-  }
-
-  @Override
-  public void stop() {
-    // Nothing to do
-  }
-}
index 4f2c42689a0b7e833092e721c6db7a488dfaf03b..821b0bbede8b514f459d808d09fbf26745cd84ca 100644 (file)
@@ -38,12 +38,13 @@ 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.core.TelemetryClient;
 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 org.sonar.telemetry.core.schema.BaseMessage;
+import org.sonar.telemetry.core.MessageSerializer;
 
 import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_ENABLE;
 import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_FREQUENCY_IN_SECONDS;
index aeb5f3c3074b17a442f0002945218d22daa888fb..7909baaaa98e1038c1b1d76ac937970a5bc8e6d3 100644 (file)
@@ -35,8 +35,8 @@ import org.sonar.db.DbSession;
 import org.sonar.db.telemetry.TelemetryMetricsSentDto;
 import org.sonar.telemetry.core.Dimension;
 import org.sonar.telemetry.core.TelemetryDataProvider;
-import org.sonar.telemetry.metrics.schema.BaseMessage;
-import org.sonar.telemetry.metrics.schema.Metric;
+import org.sonar.telemetry.core.schema.BaseMessage;
+import org.sonar.telemetry.core.schema.Metric;
 import org.sonar.telemetry.metrics.util.SentMetricsStorage;
 
 public class TelemetryMetricsLoader {
index d282608d9c2425acdca6a0fc9072ee056abaa2af..b005743172c45a3e4eb9c854479359d2a2a81a69 100644 (file)
@@ -25,11 +25,11 @@ import java.util.Set;
 import java.util.stream.Collectors;
 import org.sonar.telemetry.core.Granularity;
 import org.sonar.telemetry.core.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;
+import org.sonar.telemetry.core.schema.InstallationMetric;
+import org.sonar.telemetry.core.schema.LanguageMetric;
+import org.sonar.telemetry.core.schema.Metric;
+import org.sonar.telemetry.core.schema.ProjectMetric;
+import org.sonar.telemetry.core.schema.UserMetric;
 
 public class TelemetryMetricsMapper {
 
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
deleted file mode 100644 (file)
index 451480a..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * 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.core.Dimension;
-
-public class BaseMessage {
-  @JsonProperty("message_uuid")
-  private String messageUuid;
-
-  @JsonProperty("installation_id")
-  private String installationId;
-
-  @JsonProperty("dimension")
-  private Dimension dimension;
-
-  @JsonProperty("metric_values")
-  private Set<Metric> metrics;
-
-  protected BaseMessage(String messageUuid, String installationId, Dimension dimension, Set<Metric> metrics) {
-    this.messageUuid = messageUuid;
-    this.installationId = installationId;
-    this.dimension = dimension;
-    this.metrics = metrics;
-  }
-
-  public String getMessageUuid() {
-    return messageUuid;
-  }
-
-  public String getInstallationId() {
-    return installationId;
-  }
-
-  public Dimension getDimension() {
-    return dimension;
-  }
-
-  public Set<Metric> getMetrics() {
-    return metrics;
-  }
-
-  public static class Builder {
-    private String messageUuid;
-    private String installationId;
-    private Dimension dimension;
-    private Set<Metric> metrics;
-
-    public Builder setMessageUuid(String messageUuid) {
-      this.messageUuid = messageUuid;
-      return this;
-    }
-
-    public Builder setInstallationId(String installationId) {
-      this.installationId = installationId;
-      return this;
-    }
-
-    public Builder setDimension(Dimension dimension) {
-      this.dimension = dimension;
-      return this;
-    }
-
-    public Builder setMetrics(Set<Metric> metrics) {
-      this.metrics = metrics;
-      return this;
-    }
-
-    public BaseMessage build() {
-      Objects.requireNonNull(messageUuid, "messageUuid must be specified");
-      Objects.requireNonNull(installationId, "installationId must be specified");
-      Objects.requireNonNull(dimension, "dimension must be specified");
-      Objects.requireNonNull(metrics, "metrics must be specified");
-
-      return new BaseMessage(messageUuid, installationId, dimension, metrics);
-    }
-  }
-}
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
deleted file mode 100644 (file)
index 68dc2e8..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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 javax.annotation.Nullable;
-import org.sonar.telemetry.core.Granularity;
-import org.sonar.telemetry.core.TelemetryDataType;
-
-public class InstallationMetric extends Metric {
-
-  public InstallationMetric(String key, @Nullable 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
deleted file mode 100644 (file)
index 236c31d..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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.core.Granularity;
-import org.sonar.telemetry.core.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
deleted file mode 100644 (file)
index ad5c17f..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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.core.Granularity;
-import org.sonar.telemetry.core.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
deleted file mode 100644 (file)
index fc9ff8e..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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.core.Granularity;
-import org.sonar.telemetry.core.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
deleted file mode 100644 (file)
index 2af08ca..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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.core.Granularity;
-import org.sonar.telemetry.core.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
deleted file mode 100644 (file)
index d1b9bb1..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * 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
deleted file mode 100644 (file)
index a55c771..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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);
-    }
-  }
-
-}
index 6ac5d708553fbee2206c405c096148206e941934..6297d2e8cea8679d5668c51db49b958d4b8d41bb 100644 (file)
@@ -31,6 +31,7 @@ import okio.Okio;
 import org.assertj.core.api.Assertions;
 import org.junit.jupiter.api.Test;
 import org.sonar.api.config.internal.MapSettings;
+import org.sonar.telemetry.core.TelemetryClient;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_METRICS_URL;
diff --git a/server/sonar-telemetry/src/test/java/org/sonar/telemetry/TelemetryClientTest.java b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/TelemetryClientTest.java
deleted file mode 100644 (file)
index ffb94d7..0000000
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.telemetry;
-
-import java.io.IOException;
-import okhttp3.MediaType;
-import okhttp3.OkHttpClient;
-import okhttp3.Request;
-import okio.Buffer;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.mockito.ArgumentCaptor;
-import org.sonar.api.config.internal.MapSettings;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
-import static org.mockito.Mockito.mock;
-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_METRICS_URL;
-import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_URL;
-
-class TelemetryClientTest {
-
-  private static final String JSON = "{\"key\":\"value\"}";
-  private static final String TELEMETRY_URL = "https://telemetry.com/url";
-  private static final String METRICS_TELEMETRY_URL = "https://telemetry.com/url/metrics";
-
-  private final OkHttpClient okHttpClient = mock(OkHttpClient.class, RETURNS_DEEP_STUBS);
-  private final MapSettings settings = new MapSettings();
-
-  private final TelemetryClient underTest = new TelemetryClient(okHttpClient, settings.asConfig());
-
-  @BeforeEach
-  void setProperties() {
-    settings.setProperty(SONAR_TELEMETRY_URL.getKey(), TELEMETRY_URL);
-    settings.setProperty(SONAR_TELEMETRY_METRICS_URL.getKey(), METRICS_TELEMETRY_URL);
-  }
-
-  @Test
-  void upload() throws IOException {
-    ArgumentCaptor<Request> requestCaptor = ArgumentCaptor.forClass(Request.class);
-    settings.setProperty(SONAR_TELEMETRY_COMPRESSION.getKey(), false);
-    underTest.start();
-
-    underTest.upload(JSON);
-
-    verify(okHttpClient).newCall(requestCaptor.capture());
-    Request request = requestCaptor.getValue();
-    assertThat(request.method()).isEqualTo("POST");
-    assertThat(request.body().contentType()).isEqualTo(MediaType.parse("application/json; charset=utf-8"));
-    Buffer body = new Buffer();
-    request.body().writeTo(body);
-    assertThat(body.readUtf8()).isEqualTo(JSON);
-    assertThat(request.url()).hasToString(TELEMETRY_URL);
-  }
-
-  @Test
-  void uploadMetric() throws IOException {
-    ArgumentCaptor<Request> requestCaptor = ArgumentCaptor.forClass(Request.class);
-    settings.setProperty(SONAR_TELEMETRY_COMPRESSION.getKey(), false);
-    underTest.start();
-
-    underTest.uploadMetric(JSON);
-
-    verify(okHttpClient).newCall(requestCaptor.capture());
-    Request request = requestCaptor.getValue();
-    assertThat(request.method()).isEqualTo("POST");
-    assertThat(request.body().contentType()).isEqualTo(MediaType.parse("application/json; charset=utf-8"));
-    Buffer body = new Buffer();
-    request.body().writeTo(body);
-    assertThat(body.readUtf8()).isEqualTo(JSON);
-    assertThat(request.url()).hasToString(METRICS_TELEMETRY_URL);
-  }
-
-  @Test
-  void opt_out() throws IOException {
-    ArgumentCaptor<Request> requestCaptor = ArgumentCaptor.forClass(Request.class);
-    underTest.start();
-
-    underTest.optOut(JSON);
-
-    verify(okHttpClient).newCall(requestCaptor.capture());
-    Request request = requestCaptor.getValue();
-    assertThat(request.method()).isEqualTo("DELETE");
-    assertThat(request.body().contentType()).isEqualTo(MediaType.parse("application/json; charset=utf-8"));
-    Buffer body = new Buffer();
-    request.body().writeTo(body);
-    assertThat(body.readUtf8()).isEqualTo(JSON);
-    assertThat(request.url()).hasToString(TELEMETRY_URL);
-  }
-}
index bcee58e5c6f3bbd424423cc820dea8e4e65d7971..0ae19fc5b24e4284ca90c190848ec19268bfbade 100644 (file)
@@ -35,6 +35,7 @@ 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.core.TelemetryClient;
 import org.sonar.telemetry.legacy.TelemetryData;
 import org.sonar.telemetry.legacy.TelemetryDataJsonWriter;
 import org.sonar.telemetry.legacy.TelemetryDataLoader;
index d822623d1678aef59ad95ba9413bc1e886f66ec7..d311a0e88a7a5b3f14b4da711eb688d963b586da 100644 (file)
@@ -28,11 +28,11 @@ import org.sonar.telemetry.core.Dimension;
 import org.sonar.telemetry.core.Granularity;
 import org.sonar.telemetry.core.TelemetryDataProvider;
 import org.sonar.telemetry.core.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 org.sonar.telemetry.core.schema.InstallationMetric;
+import org.sonar.telemetry.core.schema.LanguageMetric;
+import org.sonar.telemetry.core.schema.Metric;
+import org.sonar.telemetry.core.schema.ProjectMetric;
+import org.sonar.telemetry.core.schema.UserMetric;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.tuple;
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
deleted file mode 100644 (file)
index e702018..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * 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.core.Dimension;
-import org.sonar.telemetry.core.Granularity;
-import org.sonar.telemetry.core.TelemetryDataType;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.tuple;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
-class BaseMessageTest {
-
-  @Test
-  void build() {
-    BaseMessage message = new BaseMessage.Builder()
-      .setMessageUuid("123e4567-e89b-12d3-a456-426614174000")
-      .setInstallationId("installation-id")
-      .setDimension(Dimension.INSTALLATION)
-      .setMetrics(installationMetrics())
-      .build();
-
-    assertThat(message.getMessageUuid()).isEqualTo("123e4567-e89b-12d3-a456-426614174000");
-    assertThat(message.getInstallationId()).isEqualTo("installation-id");
-    assertThat(message.getDimension()).isEqualTo(Dimension.INSTALLATION);
-    Set<InstallationMetric> installationMetrics = (Set<InstallationMetric>) (Set<?>) message.getMetrics();
-    assertThat(installationMetrics)
-      .extracting(InstallationMetric::getKey, InstallationMetric::getGranularity, InstallationMetric::getType, InstallationMetric::getValue)
-      .containsExactlyInAnyOrder(
-        tuple("key-0", Granularity.DAILY, TelemetryDataType.INTEGER, 0),
-        tuple("key-1", Granularity.DAILY, TelemetryDataType.INTEGER, 1),
-        tuple("key-2", Granularity.DAILY, TelemetryDataType.INTEGER, 2)
-      );
-  }
-
-  @ParameterizedTest
-  @MethodSource("invalidBaseMessageProvider")
-  void build_invalidCases(BaseMessage.Builder builder, String expectedErrorMessage) {
-    Exception exception = assertThrows(NullPointerException.class, builder::build);
-    assertEquals(expectedErrorMessage, exception.getMessage());
-  }
-
-  private static Stream<Arguments> invalidBaseMessageProvider() {
-    return Stream.of(
-      Arguments.of(
-        new BaseMessage.Builder()
-          .setInstallationId("installation-id")
-          .setDimension(Dimension.INSTALLATION)
-          .setMetrics(installationMetrics()),
-        "messageUuid must be specified"
-      ),
-      Arguments.of(
-        new BaseMessage.Builder()
-          .setMessageUuid("some-uuid")
-          .setInstallationId("installation-id")
-          .setMetrics(installationMetrics()),
-        "dimension must be specified"
-      ),
-      Arguments.of(
-        new BaseMessage.Builder()
-          .setMessageUuid("some-uuid")
-          .setDimension(Dimension.INSTALLATION)
-          .setMetrics(installationMetrics()),
-        "installationId must be specified"
-      )
-    );
-  }
-
-  private static Set<Metric> installationMetrics() {
-    return IntStream.range(0, 3)
-      .mapToObj(i -> new InstallationMetric(
-        "key-" + i,
-        i,
-        TelemetryDataType.INTEGER,
-        Granularity.DAILY
-      )).collect(Collectors.toSet());
-  }
-}
diff --git a/server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/schema/InstallationMetricTest.java b/server/sonar-telemetry/src/test/java/org/sonar/telemetry/metrics/schema/InstallationMetricTest.java
deleted file mode 100644 (file)
index b3ecd01..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * 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.core.Granularity;
-import org.sonar.telemetry.core.TelemetryDataType;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-class InstallationMetricTest {
-
-  @Test
-  void constructor() {
-    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);
-  }
-
-  @Test
-  void constructor_shouldAcceptNullValue() {
-    InstallationMetric metric = new InstallationMetric(
-      "installation-key-1",
-      null,
-      TelemetryDataType.STRING,
-      Granularity.WEEKLY
-    );
-
-    assertThat(metric.getValue()).isNull();
-    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
deleted file mode 100644 (file)
index 832ec3d..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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.core.Granularity;
-import org.sonar.telemetry.core.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
deleted file mode 100644 (file)
index 7eb23fc..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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.core.Granularity;
-import org.sonar.telemetry.core.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
deleted file mode 100644 (file)
index 04466d0..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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.core.Granularity;
-import org.sonar.telemetry.core.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");
-  }
-
-}
index 57aa2e858e05fc418222c66376995cefa8ff5453..b3563e543684bf262ce78229a4abf6f27411d2e6 100644 (file)
@@ -27,13 +27,14 @@ import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.MethodSource;
 import org.sonar.telemetry.core.Dimension;
 import org.sonar.telemetry.core.Granularity;
+import org.sonar.telemetry.core.MessageSerializer;
 import org.sonar.telemetry.core.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 org.sonar.telemetry.core.schema.BaseMessage;
+import org.sonar.telemetry.core.schema.InstallationMetric;
+import org.sonar.telemetry.core.schema.LanguageMetric;
+import org.sonar.telemetry.core.schema.Metric;
+import org.sonar.telemetry.core.schema.ProjectMetric;
+import org.sonar.telemetry.core.schema.UserMetric;
 
 import static org.sonar.test.JsonAssert.assertJson;
 
index 95e617507d11df31690a855ec03e2c9e954cd9d3..a947a254e31fd04dd392a92e3ebb64ffdf950311 100644 (file)
@@ -291,7 +291,7 @@ import org.sonar.server.webhook.WebhookQGChangeEventListener;
 import org.sonar.server.webhook.ws.WebhooksWsModule;
 import org.sonar.server.ws.WebServiceEngine;
 import org.sonar.server.ws.ws.WebServicesWsModule;
-import org.sonar.telemetry.TelemetryClient;
+import org.sonar.telemetry.core.TelemetryClient;
 import org.sonar.telemetry.TelemetryDaemon;
 import org.sonar.telemetry.legacy.CloudUsageDataProvider;
 import org.sonar.telemetry.legacy.ProjectLocDistributionDataProvider;
index b2bf317afa7ac1902a1d44789a7979bbefa9c057..7aa0556e55a8b7ef3399083fcc4ed2c0312a98c9 100644 (file)
@@ -373,4 +373,20 @@ public class ScannerReportReaderIT {
   public void return_null_when_no_file_source() {
     assertThat(underTest.readFileSource(UNKNOWN_COMPONENT_REF)).isNull();
   }
+
+  @Test
+  public void readTelemetryEntries_whenFileExists() {
+    ScannerReportWriter writer = new ScannerReportWriter(fileStructure);
+    ScannerReport.TelemetryEntry.Builder telemetry = ScannerReport.TelemetryEntry.newBuilder()
+      .setKey("key")
+      .setValue("value");
+    writer.writeTelemetry(List.of(telemetry.build()));
+
+    assertThat(underTest.readTelemetryEntries()).toIterable().hasSize(1);
+  }
+
+  @Test
+  public void readTelemetryEntries_whenFileDoesntExists() {
+    assertThat(underTest.readTelemetryEntries()).toIterable().isEmpty();
+  }
 }
index 4569a34789101fc30d3753b0ebadd1988e4851c9..807509a83fbe8e279699d4842e6d0dc5e5285319 100644 (file)
@@ -233,4 +233,12 @@ public class ScannerReportReader {
   public FileStructure getFileStructure() {
     return fileStructure;
   }
+
+  public CloseableIterator<ScannerReport.TelemetryEntry> readTelemetryEntries() {
+    File file = fileStructure.telemetryEntries();
+    if (!fileExists(file)) {
+      return emptyCloseableIterator();
+    }
+    return Protobuf.readStream(file, ScannerReport.TelemetryEntry.parser());
+  }
 }