Skip to content
This repository was archived by the owner on May 30, 2024. It is now read-only.

Commit c7c7e3b

Browse files
committed
Merge pull request #44 from launchdarkly/dr/eventSampling
Add event sampling. Name threads.
2 parents b7d87be + c25d1c2 commit c7c7e3b

File tree

7 files changed

+61
-65
lines changed

7 files changed

+61
-65
lines changed

build.gradle

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,15 @@ apply plugin: 'idea'
66
apply plugin: 'com.github.johnrengelman.shadow'
77

88
configurations.all {
9-
// check for updates every build
9+
// check for updates every build for dependencies with: 'changing: true'
1010
resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
1111
}
1212

1313
repositories {
14-
mavenCentral()
1514
mavenLocal()
16-
1715
// Before LaunchDarkly release artifacts get synced to Maven Central they are here along with snapshots:
18-
maven {
19-
url "https://oss.sonatype.org/content/groups/public/"
20-
}
16+
maven { url "https://oss.sonatype.org/content/groups/public/" }
17+
mavenCentral()
2118
}
2219

2320
allprojects {
@@ -38,7 +35,7 @@ dependencies {
3835
compile "redis.clients:jedis:2.8.0"
3936
testCompile "org.easymock:easymock:3.4"
4037
testCompile 'junit:junit:[4.10,)'
41-
testRuntime "org.slf4j:slf4j-simple:1.7.7"
38+
testRuntime "ch.qos.logback:logback-classic:1.1.3"
4239
}
4340

4441
jar {

src/main/java/com/launchdarkly/client/EventProcessor.java

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.launchdarkly.client;
22

3+
import com.google.common.util.concurrent.ThreadFactoryBuilder;
34
import com.google.gson.Gson;
45
import org.apache.http.HttpStatus;
56
import org.apache.http.client.methods.CloseableHttpResponse;
@@ -14,22 +15,34 @@
1415
import java.io.IOException;
1516
import java.util.ArrayList;
1617
import java.util.List;
18+
import java.util.Random;
1719
import java.util.concurrent.*;
1820

1921
class EventProcessor implements Closeable {
20-
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(new DaemonThreadFactory());
22+
private final ScheduledExecutorService scheduler;
23+
private final Random random = new Random();
2124
private final BlockingQueue<Event> queue;
2225
private final String apiKey;
26+
private final LDConfig config;
2327
private final Consumer consumer;
2428

2529
EventProcessor(String apiKey, LDConfig config) {
2630
this.apiKey = apiKey;
2731
this.queue = new ArrayBlockingQueue<>(config.capacity);
2832
this.consumer = new Consumer(config);
33+
this.config = config;
34+
ThreadFactory threadFactory = new ThreadFactoryBuilder()
35+
.setDaemon(true)
36+
.setNameFormat("LaunchDarkly-EventProcessor-%d")
37+
.build();
38+
this.scheduler = Executors.newSingleThreadScheduledExecutor(threadFactory);
2939
this.scheduler.scheduleAtFixedRate(consumer, 0, config.flushInterval, TimeUnit.SECONDS);
3040
}
3141

3242
boolean sendEvent(Event e) {
43+
if (config.samplingInterval > 0 && random.nextInt(config.samplingInterval) != 0) {
44+
return true;
45+
}
3346
return queue.offer(e);
3447
}
3548

@@ -43,18 +56,8 @@ public void flush() {
4356
this.consumer.flush();
4457
}
4558

46-
static class DaemonThreadFactory implements ThreadFactory {
47-
public Thread newThread(Runnable r) {
48-
Thread thread = new Thread(r);
49-
thread.setDaemon(true);
50-
return thread;
51-
}
52-
}
53-
5459
class Consumer implements Runnable {
5560
private final Logger logger = LoggerFactory.getLogger(Consumer.class);
56-
57-
5861
private final CloseableHttpClient client;
5962
private final LDConfig config;
6063

@@ -78,6 +81,7 @@ public void flush() {
7881
}
7982

8083
private void postEvents(List<Event> events) {
84+
logger.debug("Posting " + events.size() + " event(s)..");
8185
CloseableHttpResponse response = null;
8286
Gson gson = new Gson();
8387
String json = gson.toJson(events);
@@ -95,13 +99,11 @@ private void postEvents(List<Event> events) {
9599
if (status >= 300) {
96100
if (status == HttpStatus.SC_UNAUTHORIZED) {
97101
logger.error("Invalid API key");
98-
}
99-
else {
102+
} else {
100103
logger.error("Unexpected status code: " + status);
101104
}
102-
}
103-
else {
104-
logger.debug("Successfully processed events");
105+
} else {
106+
logger.debug("Successfully posted " + events.size() + " event(s).");
105107
}
106108
} catch (IOException e) {
107109
logger.error("Unhandled exception in LaunchDarkly client attempting to connect to URI: " + config.eventsURI, e);
@@ -112,7 +114,6 @@ private void postEvents(List<Event> events) {
112114
logger.error("Unhandled exception in LaunchDarkly client", e);
113115
}
114116
}
115-
116117
}
117118
}
118119
}

src/main/java/com/launchdarkly/client/LDConfig.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public final class LDConfig {
2323
private static final int DEFAULT_FLUSH_INTERVAL = 5;
2424
private static final long DEFAULT_POLLING_INTERVAL_MILLIS = 1000L;
2525
private static final long DEFAULT_START_WAIT_MILLIS = 0L;
26+
private static final int DEFAULT_SAMPLING_INTERVAL = 0;
2627
private static final Logger logger = LoggerFactory.getLogger(LDConfig.class);
2728

2829
protected static final LDConfig DEFAULT = new Builder().build();
@@ -42,6 +43,7 @@ public final class LDConfig {
4243
final boolean offline;
4344
final long pollingIntervalMillis;
4445
final long startWaitMillis;
46+
final int samplingInterval;
4547

4648
protected LDConfig(Builder builder) {
4749
this.baseURI = builder.baseURI;
@@ -63,6 +65,7 @@ protected LDConfig(Builder builder) {
6365
this.pollingIntervalMillis = builder.pollingIntervalMillis;
6466
}
6567
this.startWaitMillis = builder.startWaitMillis;
68+
this.samplingInterval = builder.samplingInterval;
6669
}
6770

6871
/**
@@ -94,6 +97,7 @@ public static class Builder {
9497
private long pollingIntervalMillis = DEFAULT_POLLING_INTERVAL_MILLIS;
9598
private FeatureStore featureStore = new InMemoryFeatureStore();
9699
public long startWaitMillis = DEFAULT_START_WAIT_MILLIS;
100+
public int samplingInterval = DEFAULT_SAMPLING_INTERVAL;
97101

98102
/**
99103
* Creates a builder with all configuration parameters set to the default
@@ -335,6 +339,21 @@ public Builder startWaitMillis(long startWaitMillis) {
335339
return this;
336340
}
337341

342+
/**
343+
* Enable event sampling. When set to the default of zero, sampling is disabled and all events
344+
* are sent back to LaunchDarkly. When set to greater than zero, there is a 1 in
345+
* <code>samplingInterval</code> chance events will be will be sent.
346+
*
347+
* <p>Example: if you want 5% sampling rate, set <code>samplingInterval</code> to 20.
348+
*
349+
* @param samplingInterval the sampling interval.
350+
* @return the builder
351+
*/
352+
public Builder samplingInterval(int samplingInterval) {
353+
this.samplingInterval = samplingInterval;
354+
return this;
355+
}
356+
338357
HttpHost proxyHost() {
339358
if (this.proxyHost == null && this.proxyPort == -1 && this.proxyScheme == null) {
340359
return null;

src/main/java/com/launchdarkly/client/PollingProcessor.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
package com.launchdarkly.client;
22

3+
import com.google.common.util.concurrent.ThreadFactoryBuilder;
34
import org.slf4j.Logger;
45
import org.slf4j.LoggerFactory;
56

67
import java.io.IOException;
7-
import java.util.concurrent.Executors;
8-
import java.util.concurrent.Future;
9-
import java.util.concurrent.ScheduledExecutorService;
10-
import java.util.concurrent.TimeUnit;
8+
import java.util.concurrent.*;
119
import java.util.concurrent.atomic.AtomicBoolean;
1210

1311
public class PollingProcessor implements UpdateProcessor {
@@ -40,7 +38,10 @@ public Future<Void> start() {
4038
logger.info("Starting LaunchDarkly polling client with interval: "
4139
+ config.pollingIntervalMillis + " milliseconds");
4240
final VeryBasicFuture initFuture = new VeryBasicFuture();
43-
scheduler = Executors.newScheduledThreadPool(1);
41+
ThreadFactory threadFactory = new ThreadFactoryBuilder()
42+
.setNameFormat("LaunchDarkly-PollingProcessor-%d")
43+
.build();
44+
scheduler = Executors.newScheduledThreadPool(1, threadFactory);
4445

4546
scheduler.scheduleAtFixedRate(new Runnable() {
4647
@Override

src/main/java/com/launchdarkly/client/StreamProcessor.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,10 @@ else if (name.equals(INDIRECT_PATCH)) {
104104

105105
@Override
106106
public void onError(Throwable throwable) {
107-
logger.error("Encountered exception in LaunchDarkly client: " + throwable.getMessage());
107+
logger.warn("Encountered EventSource error", throwable);
108108
}
109109
};
110110

111-
112111
es = new EventSource.Builder(handler, URI.create(config.streamURI.toASCIIString() + "/features"))
113112
.headers(headers)
114113
.build();

src/test/resources/logback.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<configuration>
2+
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
3+
<!-- encoders are assigned the type
4+
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
5+
<encoder>
6+
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36}:%line - %msg%n</pattern>
7+
</encoder>
8+
</appender>
9+
10+
<root level="debug">
11+
<appender-ref ref="STDOUT" />
12+
</root>
13+
</configuration>

src/test/resources/simplelogger.properties

Lines changed: 0 additions & 34 deletions
This file was deleted.

0 commit comments

Comments
 (0)