import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.io.StringWriter;
+import java.util.Optional;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.sonar.api.config.Configuration;
import org.sonar.api.platform.Server;
import org.sonar.api.server.ServerSide;
-import org.sonar.api.utils.log.Logger;
-import org.sonar.api.utils.log.Loggers;
+import org.sonar.api.utils.System2;
import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.server.property.InternalProperties;
+
+import static org.sonar.api.utils.DateUtils.formatDate;
+import static org.sonar.api.utils.DateUtils.parseDate;
@ServerSide
public class TelemetryDaemon implements Startable {
private static final String THREAD_NAME_PREFIX = "sq-telemetry-service-";
- private static final Logger LOG = Loggers.get(TelemetryDaemon.class);
+ private static final int SEVEN_DAYS = 7 * 24 * 60 * 60 * 1_000;
+ private static final String I_PROP_LAST_PING = "sonar.telemetry.lastPing";
private final TelemetryClient telemetryClient;
+ private final InternalProperties internalProperties;
private final Server server;
+ private final System2 system2;
private final TelemetryFrequency frequencyInSeconds;
private ScheduledExecutorService executorService;
- public TelemetryDaemon(TelemetryClient telemetryClient, Server server, Configuration config) {
+ public TelemetryDaemon(TelemetryClient telemetryClient, InternalProperties internalProperties, Server server, System2 system2, Configuration config) {
this.telemetryClient = telemetryClient;
+ this.internalProperties = internalProperties;
this.server = server;
this.frequencyInSeconds = new TelemetryFrequency(config);
+ this.system2 = system2;
}
@Override
.build());
executorService.scheduleWithFixedDelay(() -> {
try {
+ Optional<Long> lastPing = internalProperties.read(I_PROP_LAST_PING).map(Long::valueOf);
+ long now = system2.now();
+ if (lastPing.isPresent() && now - lastPing.get() < SEVEN_DAYS) {
+ return;
+ }
+
StringWriter json = new StringWriter();
try (JsonWriter writer = JsonWriter.of(json)) {
writer.beginObject();
writer.endObject();
}
telemetryClient.send(json.toString());
+ internalProperties.write(I_PROP_LAST_PING, String.valueOf(startOfDay(now)));
} catch (Exception e) {
// fail silently
}
Thread.currentThread().interrupt();
}
}
+
+ private static long startOfDay(long now) {
+ return parseDate(formatDate(now)).getTime();
+ }
}
import org.junit.Test;
import org.sonar.api.config.PropertyDefinitions;
import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.utils.internal.TestSystem2;
+import org.sonar.server.property.InternalProperties;
+import org.sonar.server.property.MapInternalProperties;
import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.contains;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
+import static org.sonar.api.utils.DateUtils.parseDate;
public class TelemetryDaemonTest {
+ private static final long ONE_HOUR = 60 * 60 * 1_000L;
+ private static final long ONE_DAY = 24 * ONE_HOUR;
+
private TelemetryClient client = mock(TelemetryClient.class);
+ private InternalProperties internalProperties = new MapInternalProperties();
private FakeServer server = new FakeServer();
+ private TestSystem2 system2 = new TestSystem2();
private MapSettings settings;
private TelemetryDaemon underTest;
@Before
public void setUp() throws Exception {
settings = new MapSettings(new PropertyDefinitions(TelemetryProperties.class));
+ system2.setNow(System.currentTimeMillis());
- underTest = new TelemetryDaemon(client, server, settings.asConfig());
+ underTest = new TelemetryDaemon(client, internalProperties, server, system2, settings.asConfig());
}
@Test
}
@Test
- public void send_data_periodically() {
+ public void check_if_should_send_data_periodically() {
+ long now = system2.now();
+ long sixDaysAgo = now - (ONE_DAY * 6L);
+ long sevenDaysAgo = now - (ONE_DAY * 7L);
+ internalProperties.write("sonar.telemetry.lastPing", String.valueOf(sixDaysAgo));
settings.setProperty("sonar.telemetry.frequency", "1");
- underTest = new TelemetryDaemon(client, server, settings.asConfig());
-
+ underTest = new TelemetryDaemon(client, internalProperties, server, system2, settings.asConfig());
underTest.start();
+ verify(client, timeout(1_000).never()).send(anyString());
+ internalProperties.write("sonar.telemetry.lastPing", String.valueOf(sevenDaysAgo));
- verify(client, timeout(3_000).atLeast(2)).send(anyString());
+ verify(client, timeout(1_000).atLeastOnce()).send(anyString());
}
@Test
verify(client, timeout(2_000).atLeastOnce()).send(contains(id));
}
+
+ @Test
+ public void do_not_send_data_if_last_ping_earlier_than_one_week_ago() {
+ settings.setProperty("sonar.telemetry.frequency", "1");
+ long now = system2.now();
+ long sixDaysAgo = now - (ONE_DAY * 6L);
+
+ internalProperties.write("sonar.telemetry.lastPing", String.valueOf(sixDaysAgo));
+ underTest.start();
+
+ verify(client, timeout(2_000).never()).send(anyString());
+ }
+
+ @Test
+ public void send_data_if_last_ping_is_one_week_ago() {
+ settings.setProperty("sonar.telemetry.frequency", "1");
+ long today = parseDate("2017-08-01").getTime();
+ system2.setNow(today + 15 * ONE_HOUR);
+ long now = system2.now();
+ long sevenDaysAgo = now - (ONE_DAY * 7L);
+ internalProperties.write("sonar.telemetry.lastPing", String.valueOf(sevenDaysAgo));
+
+ underTest.start();
+
+ verify(client, timeout(2_000)).send(anyString());
+ assertThat(internalProperties.read("sonar.telemetry.lastPing").get()).isEqualTo(String.valueOf(today));
+ }
}