Skip to content

Commit

Permalink
SONAR-9721 Extract TelemetryDataLoader from TelemetryDataDaemon
Browse files Browse the repository at this point in the history
  • Loading branch information
teryk committed Aug 30, 2017
1 parent 1c6140e commit 518a2a6
Show file tree
Hide file tree
Showing 8 changed files with 229 additions and 33 deletions.
Expand Up @@ -59,7 +59,7 @@ void optOut(String json) {
try { try {
okHttpClient.newCall(request.build()).execute(); okHttpClient.newCall(request.build()).execute();
} catch (IOException e) { } catch (IOException e) {
LOG.debug("Error when sending opt-out usage statistics: %s", e.getMessage()); LOG.debug("Error when sending opt-out usage statistics: {}", e.getMessage());
} }
} }


Expand Down
Expand Up @@ -29,19 +29,12 @@
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.picocontainer.Startable; import org.picocontainer.Startable;
import org.sonar.api.config.Configuration; import org.sonar.api.config.Configuration;
import org.sonar.api.platform.Server;
import org.sonar.api.server.ServerSide; import org.sonar.api.server.ServerSide;
import org.sonar.api.utils.System2; import org.sonar.api.utils.System2;
import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers; import org.sonar.api.utils.log.Loggers;
import org.sonar.api.utils.text.JsonWriter; import org.sonar.api.utils.text.JsonWriter;
import org.sonar.core.platform.PluginRepository;
import org.sonar.server.es.SearchOptions;
import org.sonar.server.measure.index.ProjectMeasuresIndex;
import org.sonar.server.measure.index.ProjectMeasuresStatistics;
import org.sonar.server.property.InternalProperties; import org.sonar.server.property.InternalProperties;
import org.sonar.server.user.index.UserIndex;
import org.sonar.server.user.index.UserQuery;


import static org.sonar.api.measures.CoreMetrics.LINES_KEY; import static org.sonar.api.measures.CoreMetrics.LINES_KEY;
import static org.sonar.api.measures.CoreMetrics.NCLOC_KEY; import static org.sonar.api.measures.CoreMetrics.NCLOC_KEY;
Expand All @@ -59,27 +52,20 @@ public class TelemetryDaemon implements Startable {
static final String I_PROP_OPT_OUT = "telemetry.optOut"; static final String I_PROP_OPT_OUT = "telemetry.optOut";
private static final Logger LOG = Loggers.get(TelemetryDaemon.class); private static final Logger LOG = Loggers.get(TelemetryDaemon.class);


private final TelemetryDataLoader dataLoader;
private final TelemetryClient telemetryClient; private final TelemetryClient telemetryClient;
private final Configuration config; private final Configuration config;
private final InternalProperties internalProperties; private final InternalProperties internalProperties;
private final Server server;
private final PluginRepository pluginRepository;
private final System2 system2; private final System2 system2;
private final UserIndex userIndex;
private final ProjectMeasuresIndex projectMeasuresIndex;


private ScheduledExecutorService executorService; private ScheduledExecutorService executorService;


public TelemetryDaemon(TelemetryClient telemetryClient, Configuration config, InternalProperties internalProperties, Server server, PluginRepository pluginRepository, public TelemetryDaemon(TelemetryDataLoader dataLoader, TelemetryClient telemetryClient, Configuration config, InternalProperties internalProperties, System2 system2) {
System2 system2, UserIndex userIndex, ProjectMeasuresIndex projectMeasuresIndex) { this.dataLoader = dataLoader;
this.telemetryClient = telemetryClient; this.telemetryClient = telemetryClient;
this.config = config; this.config = config;
this.internalProperties = internalProperties; this.internalProperties = internalProperties;
this.server = server;
this.pluginRepository = pluginRepository;
this.system2 = system2; this.system2 = system2;
this.userIndex = userIndex;
this.projectMeasuresIndex = projectMeasuresIndex;
} }


@Override @Override
Expand Down Expand Up @@ -139,28 +125,22 @@ private void optOut() {
StringWriter json = new StringWriter(); StringWriter json = new StringWriter();
try (JsonWriter writer = JsonWriter.of(json)) { try (JsonWriter writer = JsonWriter.of(json)) {
writer.beginObject(); writer.beginObject();
writer.prop("id", server.getId()); writer.prop("id", dataLoader.loadServerId());
writer.endObject(); writer.endObject();
} }
telemetryClient.optOut(json.toString()); telemetryClient.optOut(json.toString());
} }


private void uploadStatistics() throws IOException { private void uploadStatistics() throws IOException {
TelemetryData statistics = dataLoader.load();
StringWriter json = new StringWriter(); StringWriter json = new StringWriter();
try (JsonWriter writer = JsonWriter.of(json)) { try (JsonWriter writer = JsonWriter.of(json)) {
writer.beginObject(); writer.beginObject();
writer.prop("id", server.getId()); writer.prop("id", statistics.getServerId());
writer.prop("version", server.getVersion()); writer.prop("version", statistics.getVersion());
writer.name("plugins"); writer.name("plugins");
writer.beginObject(); writer.valueObject(statistics.getPlugins());
pluginRepository.getPluginInfos().forEach(plugin -> { writer.prop("userCount", statistics.getUserCount());
String version = plugin.getVersion() == null ? "undefined" : plugin.getVersion().getName();
writer.prop(plugin.getKey(), version);
});
writer.endObject();
long userCount = userIndex.search(UserQuery.builder().build(), new SearchOptions().setLimit(1)).getTotal();
writer.prop("userCount", userCount);
ProjectMeasuresStatistics statistics = projectMeasuresIndex.searchTelemetryStatistics();
writer.prop("projectCount", statistics.getProjectCount()); writer.prop("projectCount", statistics.getProjectCount());
writer.prop(LINES_KEY, statistics.getLines()); writer.prop(LINES_KEY, statistics.getLines());
writer.prop(NCLOC_KEY, statistics.getNcloc()); writer.prop(NCLOC_KEY, statistics.getNcloc());
Expand Down
@@ -0,0 +1,133 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.server.telemetry;

import java.util.Map;
import org.sonar.server.measure.index.ProjectMeasuresStatistics;

import static java.util.Objects.requireNonNull;

public class TelemetryData {
private final String serverId;
private final String version;
private final Map<String, String> plugins;
private final long lines;
private final long ncloc;
private final long userCount;
private final long projectCount;
private final Map<String, Long> projectCountByLanguage;
private final Map<String, Long> nclocByLanguage;

public TelemetryData(Builder builder) {
serverId = builder.serverId;
version = builder.version;
plugins = builder.plugins;
lines = builder.projectMeasuresStatistics.getLines();
ncloc = builder.projectMeasuresStatistics.getNcloc();
userCount = builder.userCount;
projectCount = builder.projectMeasuresStatistics.getProjectCount();
projectCountByLanguage = builder.projectMeasuresStatistics.getProjectCountByLanguage();
nclocByLanguage = builder.projectMeasuresStatistics.getNclocByLanguage();
}

public String getServerId() {
return serverId;
}

public String getVersion() {
return version;
}

public Map<String, String> getPlugins() {
return plugins;
}

public long getLines() {
return lines;
}

public long getNcloc() {
return ncloc;
}

public long getUserCount() {
return userCount;
}

public long getProjectCount() {
return projectCount;
}

public Map<String, Long> getProjectCountByLanguage() {
return projectCountByLanguage;
}

public Map<String, Long> getNclocByLanguage() {
return nclocByLanguage;
}

static Builder builder() {
return new Builder();
}

static class Builder {
private String serverId;
private String version;
private long userCount;
private Map<String, String> plugins;
private ProjectMeasuresStatistics projectMeasuresStatistics;

private Builder() {
// enforce static factory method
}

Builder setServerId(String serverId) {
this.serverId = serverId;
return this;
}

Builder setVersion(String version) {
this.version = version;
return this;
}

void setUserCount(long userCount) {
this.userCount = userCount;
}

void setPlugins(Map<String, String> plugins) {
this.plugins = plugins;
}

void setProjectMeasuresStatistics(ProjectMeasuresStatistics projectMeasuresStatistics) {
this.projectMeasuresStatistics = projectMeasuresStatistics;
}

TelemetryData build() {
requireNonNull(serverId);
requireNonNull(version);
requireNonNull(plugins);
requireNonNull(projectMeasuresStatistics);

return new TelemetryData(this);
}
}
}
@@ -0,0 +1,69 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.server.telemetry;

import java.util.Map;
import java.util.function.Function;
import org.sonar.api.platform.Server;
import org.sonar.api.server.ServerSide;
import org.sonar.core.platform.PluginInfo;
import org.sonar.core.platform.PluginRepository;
import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.server.es.SearchOptions;
import org.sonar.server.measure.index.ProjectMeasuresIndex;
import org.sonar.server.measure.index.ProjectMeasuresStatistics;
import org.sonar.server.user.index.UserIndex;
import org.sonar.server.user.index.UserQuery;

@ServerSide
public class TelemetryDataLoader {
private final Server server;
private final PluginRepository pluginRepository;
private final UserIndex userIndex;
private final ProjectMeasuresIndex projectMeasuresIndex;

public TelemetryDataLoader(Server server, PluginRepository pluginRepository, UserIndex userIndex, ProjectMeasuresIndex projectMeasuresIndex) {
this.server = server;
this.pluginRepository = pluginRepository;
this.userIndex = userIndex;
this.projectMeasuresIndex = projectMeasuresIndex;
}

public TelemetryData load() {
TelemetryData.Builder data = TelemetryData.builder();

data.setServerId(server.getId());
data.setVersion(server.getVersion());
Function<PluginInfo, String> getVersion = plugin -> plugin.getVersion() == null ? "undefined" : plugin.getVersion().getName();
Map<String, String> plugins = pluginRepository.getPluginInfos().stream().collect(MoreCollectors.uniqueIndex(PluginInfo::getKey, getVersion));
data.setPlugins(plugins);
long userCount = userIndex.search(UserQuery.builder().build(), new SearchOptions().setLimit(1)).getTotal();
data.setUserCount(userCount);
ProjectMeasuresStatistics projectMeasuresStatistics = projectMeasuresIndex.searchTelemetryStatistics();
data.setProjectMeasuresStatistics(projectMeasuresStatistics);

return data.build();
}

String loadServerId() {
return server.getId();
}
}
Expand Up @@ -25,6 +25,7 @@ public class TelemetryModule extends Module {
@Override @Override
protected void configureModule() { protected void configureModule() {
add( add(
TelemetryDataLoader.class,
TelemetryDaemon.class, TelemetryDaemon.class,
TelemetryClient.class); TelemetryClient.class);
} }
Expand Down
Expand Up @@ -25,10 +25,17 @@
import javax.annotation.CheckForNull; import javax.annotation.CheckForNull;
import org.sonar.api.platform.Server; import org.sonar.api.platform.Server;


import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;

class FakeServer extends Server { class FakeServer extends Server {
private String id; private String id;
private String version; private String version;


public FakeServer() {
this.id = randomAlphanumeric(20);
this.version = randomAlphanumeric(10);
}

@Override @Override
public String getId() { public String getId() {
return id; return id;
Expand Down
Expand Up @@ -33,6 +33,8 @@
import org.sonar.api.config.PropertyDefinitions; import org.sonar.api.config.PropertyDefinitions;
import org.sonar.api.config.internal.MapSettings; import org.sonar.api.config.internal.MapSettings;
import org.sonar.api.utils.internal.TestSystem2; import org.sonar.api.utils.internal.TestSystem2;
import org.sonar.api.utils.log.LogTester;
import org.sonar.api.utils.log.LoggerLevel;
import org.sonar.core.config.TelemetryProperties; import org.sonar.core.config.TelemetryProperties;
import org.sonar.core.platform.PluginInfo; import org.sonar.core.platform.PluginInfo;
import org.sonar.core.platform.PluginRepository; import org.sonar.core.platform.PluginRepository;
Expand Down Expand Up @@ -71,6 +73,8 @@ public class TelemetryDaemonTest {
public UserSessionRule userSession = UserSessionRule.standalone(); public UserSessionRule userSession = UserSessionRule.standalone();
@Rule @Rule
public EsTester es = new EsTester(new UserIndexDefinition(emptyConfig), new ProjectMeasuresIndexDefinition(emptyConfig)); public EsTester es = new EsTester(new UserIndexDefinition(emptyConfig), new ProjectMeasuresIndexDefinition(emptyConfig));
@Rule
public LogTester logger = new LogTester().setLevel(LoggerLevel.DEBUG);


private TelemetryClient client = mock(TelemetryClient.class); private TelemetryClient client = mock(TelemetryClient.class);
private InternalProperties internalProperties = new MapInternalProperties(); private InternalProperties internalProperties = new MapInternalProperties();
Expand All @@ -86,8 +90,8 @@ public void setUp() throws Exception {
settings = new MapSettings(new PropertyDefinitions(TelemetryProperties.all())); settings = new MapSettings(new PropertyDefinitions(TelemetryProperties.all()));
system2.setNow(System.currentTimeMillis()); system2.setNow(System.currentTimeMillis());


underTest = new TelemetryDaemon(client, settings.asConfig(), internalProperties, server, pluginRepository, system2, new UserIndex(es.client()), underTest = new TelemetryDaemon(new TelemetryDataLoader(server, pluginRepository, new UserIndex(es.client()), new ProjectMeasuresIndex(es.client(), null)), client,
new ProjectMeasuresIndex(es.client(), null)); settings.asConfig(), internalProperties, system2);
} }


@Test @Test
Expand Down Expand Up @@ -121,6 +125,7 @@ public void send_telemetry_data() throws IOException {
String json = jsonCaptor.getValue(); String json = jsonCaptor.getValue();
assertJson(json).isSimilarTo(getClass().getResource("telemetry-example.json")); assertJson(json).isSimilarTo(getClass().getResource("telemetry-example.json"));
assertJson(getClass().getResource("telemetry-example.json")).isSimilarTo(json); assertJson(getClass().getResource("telemetry-example.json")).isSimilarTo(json);
assertThat(logger.logs(LoggerLevel.INFO)).contains("Sharing of SonarQube statistics is enabled.");
} }


@Test @Test
Expand Down Expand Up @@ -194,6 +199,7 @@ public void opt_out_sent_once() throws IOException {


verify(client, timeout(1_000).never()).upload(anyString()); verify(client, timeout(1_000).never()).upload(anyString());
verify(client, timeout(1_000).times(1)).optOut(anyString()); verify(client, timeout(1_000).times(1)).optOut(anyString());
assertThat(logger.logs(LoggerLevel.INFO)).contains("Sharing of SonarQube statistics is disabled.");
} }


private PluginInfo newPlugin(String key, String version) { private PluginInfo newPlugin(String key, String version) {
Expand Down
Expand Up @@ -29,6 +29,6 @@ public class TelemetryModuleTest {
public void verify_count_of_added_components() { public void verify_count_of_added_components() {
ComponentContainer container = new ComponentContainer(); ComponentContainer container = new ComponentContainer();
new TelemetryModule().configure(container); new TelemetryModule().configure(container);
assertThat(container.size()).isEqualTo(2 + 2); assertThat(container.size()).isEqualTo(3 + 2);
} }
} }

0 comments on commit 518a2a6

Please sign in to comment.