CloseableIterator<ScannerReport.AnalysisWarning> readAnalysisWarnings();
CloseableIterator<ScannerReport.Cve> readCves();
+
+ CloseableIterator<ScannerReport.TelemetryEntry> readTelemetryEntries();
}
ensureInitialized();
return delegate.readCves();
}
+
+ @Override
+ public CloseableIterator<ScannerReport.TelemetryEntry> readTelemetryEntries() {
+ ensureInitialized();
+ return delegate.readTelemetryEntries();
+ }
}
private static final List<Class<? extends ComputationStep>> STEPS = Arrays.asList(
ExtractReportStep.class,
+ SendAnalysisTelemetryStep.class,
PersistScannerContextStep.class,
PersistAnalysisWarningsStep.class,
GenerateAnalysisUuid.class,
--- /dev/null
+/*
+ * 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
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) {
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;
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");
}
}
--- /dev/null
+/*
+ * 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);
+ }
+}
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.
add(
DefaultPeriodCleaner.class,
ProjectCleaner.class,
- IndexPurgeListener.class);
+ IndexPurgeListener.class,
+ TelemetryClient.class);
}
}
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'
}
INSTALLATION("installation"),
USER("user"),
PROJECT("project"),
- LANGUAGE("language");
+ LANGUAGE("language"),
+ ANALYSIS("analysis");
private final String value;
--- /dev/null
+/*
+ * 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);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+
+}
--- /dev/null
+/*
+ * 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;
+ }
+
+}
--- /dev/null
+/*
+ * 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;
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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());
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+
+}
--- /dev/null
+/*
+ * 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);
+ }
+
+}
--- /dev/null
+/*
+ * 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");
+ }
+
+}
--- /dev/null
+/*
+ * 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");
+ }
+
+}
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;
+++ /dev/null
-/*
- * 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
- }
-}
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;
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 {
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 {
+++ /dev/null
-/*
- * 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);
- }
- }
-}
+++ /dev/null
-/*
- * 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;
- }
-
-}
+++ /dev/null
-/*
- * 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;
- }
-}
+++ /dev/null
-/*
- * 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;
- }
-}
+++ /dev/null
-/*
- * 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;
- }
-
-}
+++ /dev/null
-/*
- * 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;
- }
-
-}
+++ /dev/null
-/*
- * 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;
+++ /dev/null
-/*
- * 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);
- }
- }
-
-}
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;
+++ /dev/null
-/*
- * 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);
- }
-}
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;
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;
+++ /dev/null
-/*
- * 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());
- }
-}
+++ /dev/null
-/*
- * 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);
- }
-
-}
+++ /dev/null
-/*
- * 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);
- }
-
-}
+++ /dev/null
-/*
- * 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");
- }
-
-}
+++ /dev/null
-/*
- * 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");
- }
-
-}
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;
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;
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();
+ }
}
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());
+ }
}