diff --git a/litmus-client/pom.xml b/litmus-client/pom.xml
index e936919..dd4be6d 100644
--- a/litmus-client/pom.xml
+++ b/litmus-client/pom.xml
@@ -75,6 +75,13 @@
jackson-datatype-jsr310
2.13.0
+
+
+ io.micrometer
+ micrometer-registry-prometheus
+ 1.8.4
+
+
diff --git a/litmus-client/src/main/java/com/navi/medici/client/ClickStreamClient.java b/litmus-client/src/main/java/com/navi/medici/client/ClickStreamClient.java
index ef596d5..ed14b31 100644
--- a/litmus-client/src/main/java/com/navi/medici/client/ClickStreamClient.java
+++ b/litmus-client/src/main/java/com/navi/medici/client/ClickStreamClient.java
@@ -3,7 +3,9 @@ package com.navi.medici.client;
import com.navi.medici.clickstream.ClickStreamPayload;
import com.navi.medici.config.LitmusConfig;
+import com.navi.medici.response.LitmusExperimentResponse;
import com.navi.medici.util.JacksonUtils;
+import io.micrometer.core.instrument.Counter;
import lombok.extern.log4j.Log4j2;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
@@ -24,7 +26,7 @@ public class ClickStreamClient {
this.client = new OkHttpClient();
}
- public void publish(ClickStreamPayload payload) {
+ public void publish(String experimentName, ClickStreamPayload payload) {
String requestBody = JacksonUtils.objectToString(payload);
Request request = new Request.Builder()
.url(this.litmusConfig.getClickStreamAPI())
@@ -32,6 +34,13 @@ public class ClickStreamClient {
.build();
try(Response response = client.newCall(request).execute()) {
+ Counter.builder("litmus_client_click_stream_event_ingestion")
+ .tag("vertical", litmusConfig.getVertical())
+ .tag("status", String.valueOf(response.code()))
+ .tag("experiment_name", experimentName)
+ .tag("app_name", litmusConfig.getAppName())
+ .register(this.litmusConfig.getMeterRegistry())
+ .increment();
if (response.code() < 300) {
var responseBody = response.body();
assert responseBody != null;
@@ -41,6 +50,14 @@ public class ClickStreamClient {
log.error("clickstream ingestion received non-2xx. status: {}", response.code());
}
} catch (Exception e) {
+ Counter.builder("litmus_client_click_stream_event_ingestion")
+ .tag("vertical", litmusConfig.getVertical())
+ .tag("experiment_name", experimentName)
+ .tag("app_name", litmusConfig.getAppName())
+ .tag("exception", e.getMessage())
+ .register(this.litmusConfig.getMeterRegistry())
+ .increment();
+
log.error("clickstream event ingestion failed. ", e);
}
}
diff --git a/litmus-client/src/main/java/com/navi/medici/client/ExperimentFetcher.java b/litmus-client/src/main/java/com/navi/medici/client/ExperimentFetcher.java
index a6ebc91..f8dc6f9 100644
--- a/litmus-client/src/main/java/com/navi/medici/client/ExperimentFetcher.java
+++ b/litmus-client/src/main/java/com/navi/medici/client/ExperimentFetcher.java
@@ -5,5 +5,5 @@ import com.navi.medici.response.LitmusExperimentResponse;
public interface ExperimentFetcher {
- LitmusExperimentResponse fetchExperiments() throws LitmusException;
+ LitmusExperimentResponse fetchExperiments(String vertical, Long pollingTime) throws LitmusException;
}
diff --git a/litmus-client/src/main/java/com/navi/medici/client/HttpExperimentFetcher.java b/litmus-client/src/main/java/com/navi/medici/client/HttpExperimentFetcher.java
index a2b6b8d..ff8f17b 100644
--- a/litmus-client/src/main/java/com/navi/medici/client/HttpExperimentFetcher.java
+++ b/litmus-client/src/main/java/com/navi/medici/client/HttpExperimentFetcher.java
@@ -6,6 +6,7 @@ import com.navi.medici.response.LitmusExperimentCollection;
import com.navi.medici.response.LitmusExperimentResponse;
import com.navi.medici.response.LitmusResponse;
import com.navi.medici.util.JacksonUtils;
+import io.micrometer.core.instrument.Counter;
import java.net.URL;
import lombok.extern.log4j.Log4j2;
import okhttp3.OkHttpClient;
@@ -14,10 +15,10 @@ import okhttp3.Response;
@Log4j2
public class HttpExperimentFetcher implements ExperimentFetcher {
-
private final OkHttpClient client;
private final URL experimentUrl;
private final URL segmentIdUrl;
+ private final LitmusConfig litmusConfig;
public HttpExperimentFetcher(LitmusConfig litmusConfig) {
this.client = new OkHttpClient();
@@ -25,29 +26,64 @@ public class HttpExperimentFetcher implements ExperimentFetcher {
litmusConfig.getLitmusURLs()
.getLitmusExperimentsURL(litmusConfig.getProjectName(), litmusConfig.getNamePrefix());
this.segmentIdUrl = litmusConfig.getLitmusURLs().getSegmentIdURL();
+ this.litmusConfig = litmusConfig;
}
@Override
- public LitmusExperimentResponse fetchExperiments() throws LitmusException {
+ public LitmusExperimentResponse fetchExperiments(String vertical, Long pollingTime) throws LitmusException {
Request request = new Request.Builder()
- .url(this.experimentUrl)
+ .url(String.format("%s?vertical=%s&polling_time=%s", this.experimentUrl, vertical, pollingTime))
.build();
try (Response response = client.newCall(request).execute()) {
+ Counter.builder("litmus_client_fetch_experiment_polling")
+ .tag("vertical", vertical)
+ .tag("app_name", litmusConfig.getAppName())
+ .register(this.litmusConfig.getMeterRegistry())
+ .increment();
+
if (response.code() < 300) {
var responseBody = response.body();
assert responseBody != null;
LitmusExperimentCollection experiments = JacksonUtils.stringToObject(responseBody.string(), LitmusExperimentCollection.class);
+ Counter.builder("litmus_client_fetch_experiment_polling_total_request")
+ .tag("vertical", vertical)
+ .tag("status", String.valueOf(response.code()))
+ .tag("experiment_state", LitmusExperimentResponse.Status.CHANGED.name())
+ .tag("app_name", litmusConfig.getAppName())
+ .register(this.litmusConfig.getMeterRegistry())
+ .increment();
return new LitmusExperimentResponse(LitmusExperimentResponse.Status.CHANGED, experiments);
- } else if(response.code() == 304) {
+ } else if (response.code() == 304) {
+ Counter.builder("litmus_client_fetch_experiment_polling_request_status")
+ .tag("vertical", vertical)
+ .tag("app_name", litmusConfig.getAppName())
+ .tag("status", String.valueOf(response.code()))
+ .tag("experiment_state", LitmusExperimentResponse.Status.NOT_CHANGED.name())
+ .register(this.litmusConfig.getMeterRegistry())
+ .increment();
+
return new LitmusExperimentResponse(LitmusExperimentResponse.Status.NOT_CHANGED, response.code());
} else {
+ Counter.builder("litmus_client_fetch_experiment_polling_request_status")
+ .tag("vertical", vertical)
+ .tag("app_name", litmusConfig.getAppName())
+ .tag("status", String.valueOf(response.code()))
+ .tag("experiment_state", LitmusExperimentResponse.Status.NOT_CHANGED.name())
+ .register(this.litmusConfig.getMeterRegistry())
+ .increment();
+
return new LitmusExperimentResponse(LitmusExperimentResponse.Status.UNAVAILABLE, response.code());
}
} catch (Exception e) {
+ Counter.builder("litmus_client_fetch_experiment_polling_request_failed")
+ .tag("vertical", vertical)
+ .tag("app_name", litmusConfig.getAppName())
+ .register(this.litmusConfig.getMeterRegistry())
+ .increment();
throw new LitmusException("fetch experiments failed.", e);
}
}
@@ -57,12 +93,26 @@ public class HttpExperimentFetcher implements ExperimentFetcher {
.url(String.format("%s?segment_name=%s&id=%s", this.segmentIdUrl, segmentName, id))
.build();
- try(Response response = client.newCall(request).execute()) {
+ try (Response response = client.newCall(request).execute()) {
var responseBody = response.body();
assert responseBody != null;
+ Counter.builder("litmus_client_segment_id_exist_request")
+ .tag("vertical", litmusConfig.getVertical())
+ .tag("app_name", litmusConfig.getAppName())
+ .tag("status", String.valueOf(response.code()))
+ .register(this.litmusConfig.getMeterRegistry())
+ .increment();
return JacksonUtils.stringToObject(responseBody.string(), LitmusResponse.class);
} catch (Exception e) {
+ Counter.builder("litmus_client_segment_id_exist_request_failed")
+ .tag("vertical", litmusConfig.getVertical())
+ .tag("segment_name", segmentName)
+ .tag("app_name", litmusConfig.getAppName())
+ .tag("exception", e.getMessage())
+ .register(this.litmusConfig.getMeterRegistry())
+ .increment();
+
throw new LitmusException("segment name to id matching exists.", e);
}
}
diff --git a/litmus-client/src/main/java/com/navi/medici/config/LitmusConfig.java b/litmus-client/src/main/java/com/navi/medici/config/LitmusConfig.java
index a9901d6..2cd29e6 100644
--- a/litmus-client/src/main/java/com/navi/medici/config/LitmusConfig.java
+++ b/litmus-client/src/main/java/com/navi/medici/config/LitmusConfig.java
@@ -8,6 +8,7 @@ import com.navi.medici.scheduler.LitmusScheduledExecutorImpl;
import com.navi.medici.strategy.Strategy;
import com.navi.medici.strategy.UnknownStrategy;
import com.navi.medici.util.LitmusURLs;
+import io.micrometer.core.instrument.MeterRegistry;
import java.io.File;
import java.net.InetAddress;
import java.net.URI;
@@ -37,6 +38,8 @@ public class LitmusConfig {
private final Strategy fallbackStrategy;
private final LitmusExperimentBootstrapProvider litmusExperimentBootstrapProvider;
private final String clickStreamAPI;
+ private final String vertical;
+ private final MeterRegistry meterRegistry;
private LitmusConfig(
@@ -52,7 +55,9 @@ public class LitmusConfig {
LitmusScheduledExecutor litmusScheduledExecutor,
Strategy fallbackStrategy,
LitmusExperimentBootstrapProvider litmusBootstrapProvider,
- String clickStreamAPI) {
+ String clickStreamAPI,
+ String vertical,
+ MeterRegistry meterRegistry) {
if (appName == null) {
throw new IllegalStateException("You are required to specify the litmus appName");
@@ -70,6 +75,19 @@ public class LitmusConfig {
throw new IllegalStateException("You are required to specify a scheduler");
}
+ if (clickStreamAPI == null) {
+ throw new IllegalStateException("You are required to specify a scheduler");
+ }
+
+ if (vertical == null) {
+ throw new IllegalStateException("You are required to specify vertical");
+ }
+
+ if (meterRegistry == null) {
+ throw new IllegalStateException("You are required to specify meter registry");
+ }
+
+
this.fallbackStrategy = Objects.requireNonNullElseGet(fallbackStrategy, UnknownStrategy::new);
this.litmusAPI = litmusAPI;
@@ -85,6 +103,8 @@ public class LitmusConfig {
this.litmusScheduledExecutor = litmusScheduledExecutor;
this.litmusExperimentBootstrapProvider = litmusBootstrapProvider;
this.clickStreamAPI = clickStreamAPI;
+ this.vertical = vertical;
+ this.meterRegistry = meterRegistry;
}
public static Builder builder() {
@@ -150,6 +170,12 @@ public class LitmusConfig {
return clickStreamAPI;
}
+ public String getVertical() {return vertical;}
+
+ public MeterRegistry getMeterRegistry() {
+ return meterRegistry;
+ }
+
public static class Builder {
private URI litmusAPI;
@@ -165,6 +191,8 @@ public class LitmusConfig {
private @Nullable Strategy fallbackStrategy;
private @Nullable LitmusExperimentBootstrapProvider litmusExperimentBootstrapProvider;
private @Nullable String clickStreamAPI;
+ private String vertical;
+ private MeterRegistry meterRegistry;
private static String getHostname() {
String hostName = System.getProperty("hostname");
@@ -252,6 +280,17 @@ public class LitmusConfig {
return this;
}
+ public Builder vertical(String vertical) {
+ this.vertical = vertical;
+ return this;
+ }
+
+ public Builder meterRegistry(MeterRegistry meterRegistry) {
+ this.meterRegistry = meterRegistry;
+ return this;
+ }
+
+
private String getBackupFile() {
if (backupFile != null) {
return backupFile;
@@ -277,7 +316,9 @@ public class LitmusConfig {
.orElseGet(LitmusScheduledExecutorImpl::getInstance),
fallbackStrategy,
litmusExperimentBootstrapProvider,
- clickStreamAPI);
+ clickStreamAPI,
+ vertical,
+ meterRegistry);
}
public String getDefaultSdkVersion() {
diff --git a/litmus-client/src/main/java/com/navi/medici/event/EventDispatcher.java b/litmus-client/src/main/java/com/navi/medici/event/EventDispatcher.java
index 1dac79c..164ffe2 100644
--- a/litmus-client/src/main/java/com/navi/medici/event/EventDispatcher.java
+++ b/litmus-client/src/main/java/com/navi/medici/event/EventDispatcher.java
@@ -11,6 +11,7 @@ import com.navi.medici.request.v1.LitmusExperiment;
import com.navi.medici.util.JacksonUtils;
import com.navi.medici.variants.Variant;
import java.util.List;
+import java.util.concurrent.CompletableFuture;
public class EventDispatcher {
private final ClickStreamClient clickStreamClient;
@@ -47,11 +48,11 @@ public class EventDispatcher {
.customerReferenceId(litmusContext.getUserId().orElse(""))
.build());
- clickStreamClient.publish(clickStreamPayload);
+ CompletableFuture.supplyAsync(() -> {
+ clickStreamClient.publish(litmusExperiment.getName(), clickStreamPayload);
+ return null;
+ });
+
}
}
-
- private void clickstreamIngestion() {
-
- }
}
diff --git a/litmus-client/src/main/java/com/navi/medici/litmus/DefaultLitmus.java b/litmus-client/src/main/java/com/navi/medici/litmus/DefaultLitmus.java
index 9be9153..c839409 100644
--- a/litmus-client/src/main/java/com/navi/medici/litmus/DefaultLitmus.java
+++ b/litmus-client/src/main/java/com/navi/medici/litmus/DefaultLitmus.java
@@ -23,6 +23,8 @@ import com.navi.medici.strategy.UserWithIdStrategy;
import com.navi.medici.util.JacksonUtils;
import com.navi.medici.util.VariantUtil;
import com.navi.medici.variants.Variant;
+
+import io.micrometer.core.instrument.Counter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
@@ -33,9 +35,12 @@ import lombok.extern.log4j.Log4j2;
@Log4j2
public class DefaultLitmus implements Litmus {
+
+
public static final UnknownStrategy UNKNOWN_STRATEGY = new UnknownStrategy();
public static final Variant DISABLED_VARIANT = new Variant("disabled", null, false, null);
+
private final ExperimentRepository experimentRepository;
private final Map strategyMap;
private final LitmusContextProvider contextProvider;
@@ -149,6 +154,12 @@ public class DefaultLitmus implements Litmus {
});
}
+ Counter.builder("litmus_client_experiment_check_enabled")
+ .tag("experiment_name", experimentName)
+ .tag("result", String.valueOf(enabled))
+ .register(this.config.getMeterRegistry())
+ .increment();
+
this.eventDispatcher.publish(context, litmusExperiment, enabled, null);
log.info("experiment_name: {}, result: {}", experimentName, enabled);
return enabled;
@@ -204,6 +215,14 @@ public class DefaultLitmus implements Litmus {
this.eventDispatcher.publish(context, litmusExperiment, enabled, variant);
log.info("experiment_name: {}, result: {}", experimentName, enabled);
+
+ Counter.builder("litmus_client_experiment_variant")
+ .tag("experiment_name", experimentName)
+ .tag("result", String.valueOf(enabled))
+ .tag("variant", variant.getName())
+ .register(this.config.getMeterRegistry())
+ .increment();
+
return variant;
}
@@ -217,4 +236,7 @@ public class DefaultLitmus implements Litmus {
return getVariant(experimentName, contextProvider.getContext(), defaultValue);
}
+
+
+
}
diff --git a/litmus-client/src/main/java/com/navi/medici/repository/LitmusExperimentRepository.java b/litmus-client/src/main/java/com/navi/medici/repository/LitmusExperimentRepository.java
index 591a9a7..4a839c6 100644
--- a/litmus-client/src/main/java/com/navi/medici/repository/LitmusExperimentRepository.java
+++ b/litmus-client/src/main/java/com/navi/medici/repository/LitmusExperimentRepository.java
@@ -51,18 +51,18 @@ public class LitmusExperimentRepository implements ExperimentRepository {
}
if (litmusConfig.isSynchronousFetchOnInitialisation()) {
- updateExperiments().run();
+ updateExperiments(litmusConfig.getVertical(), litmusConfig.getFetchLitmusExperimentsInterval()).run();
}
this.inMemoryCache = new InMemoryCache(this.experimentCollection);
- executor.setInterval(updateExperiments(), 0, litmusConfig.getFetchLitmusExperimentsInterval());
+ executor.setInterval(updateExperiments(litmusConfig.getVertical(), litmusConfig.getFetchLitmusExperimentsInterval()), 0, litmusConfig.getFetchLitmusExperimentsInterval());
}
- private Runnable updateExperiments() {
+ private Runnable updateExperiments(String vertical, Long pollingTime) {
return () -> {
try {
- LitmusExperimentResponse response = experimentFetcher.fetchExperiments();
- if (response.getStatus() == LitmusExperimentResponse.Status.CHANGED) {
+ LitmusExperimentResponse response = experimentFetcher.fetchExperiments(vertical, pollingTime);
+ if (response != null && LitmusExperimentResponse.Status.CHANGED == response.getStatus()) {
experimentCollection = response.getExperimentCollection();
experimentBackupHandler.write(response.getExperimentCollection());
this.inMemoryCache = new InMemoryCache(experimentCollection);
diff --git a/litmus-client/src/main/java/com/navi/medici/strategy/DeviceWithIdStrategy.java b/litmus-client/src/main/java/com/navi/medici/strategy/DeviceWithIdStrategy.java
index 14603af..8956e1e 100644
--- a/litmus-client/src/main/java/com/navi/medici/strategy/DeviceWithIdStrategy.java
+++ b/litmus-client/src/main/java/com/navi/medici/strategy/DeviceWithIdStrategy.java
@@ -1,9 +1,15 @@
package com.navi.medici.strategy;
+import static com.navi.medici.strategy.FlexibleRolloutStrategy.GROUP_ID;
+import static com.navi.medici.strategy.FlexibleRolloutStrategy.PERCENTAGE;
+
import com.navi.medici.client.HttpExperimentFetcher;
import com.navi.medici.context.LitmusContext;
+import com.navi.medici.util.StrategyUtils;
import java.util.Map;
+import java.util.Optional;
+import org.apache.commons.lang3.StringUtils;
public class DeviceWithIdStrategy implements Strategy {
@@ -28,10 +34,30 @@ public class DeviceWithIdStrategy implements Strategy {
@Override
public boolean isEnabled(Map parameters, LitmusContext litmusContext) {
+ if (StringUtils.isBlank(parameters.get(PERCENTAGE))) {
+ return deviceWithIdSegmentCheck(parameters, litmusContext);
+ } else {
+ return segmentCheckWithRollout(parameters, litmusContext);
+ }
+ }
+
+ private boolean deviceWithIdSegmentCheck(Map parameters, LitmusContext litmusContext) {
+ var deviceId = litmusContext.getDeviceId().orElse(null);
var segmentName = parameters.get(SEGMENT);
- var result = experimentFetcher.segmentIdExists(segmentName, litmusContext.getDeviceId().orElse(null));
+ var result = experimentFetcher.segmentIdExists(segmentName, deviceId);
return result.getData() != null && (Boolean) result.getData();
+ }
+ private boolean segmentCheckWithRollout(Map parameters, LitmusContext litmusContext) {
+ var deviceId = litmusContext.getDeviceId().orElse(null);
+ var percentage = StrategyUtils.getPercentage(parameters.get(PERCENTAGE));
+ var groupId = parameters.getOrDefault(GROUP_ID, "");
+
+ var norm = StrategyUtils.getNormalizedNumber(deviceId, groupId);
+
+ return Optional.of(deviceWithIdSegmentCheck(parameters, litmusContext))
+ .map(r -> r && percentage > 0 && norm <= percentage)
+ .orElse(false);
}
}
diff --git a/litmus-client/src/main/java/com/navi/medici/strategy/FlexibleRolloutStrategy.java b/litmus-client/src/main/java/com/navi/medici/strategy/FlexibleRolloutStrategy.java
index f2e8974..f862e6f 100644
--- a/litmus-client/src/main/java/com/navi/medici/strategy/FlexibleRolloutStrategy.java
+++ b/litmus-client/src/main/java/com/navi/medici/strategy/FlexibleRolloutStrategy.java
@@ -15,13 +15,6 @@ public class FlexibleRolloutStrategy implements Strategy {
protected static final String PERCENTAGE = "rollout";
protected static final String GROUP_ID = "groupId";
- private final Supplier randomGenerator;
-
- public FlexibleRolloutStrategy() {
- this.randomGenerator = () -> Math.random() * 100 + "";
- }
-
-
@Override
public String getName() {
return "flexibleRollout";
@@ -32,32 +25,12 @@ public class FlexibleRolloutStrategy implements Strategy {
return false;
}
- private Optional resolveStickiness(String stickiness, LitmusContext context) {
- switch (stickiness) {
- case "userId":
- return context.getUserId();
- case "sessionId":
- return context.getSessionId();
- case "deviceId":
- return context.getDeviceId();
- case "appVersionCode":
- return context.getAppVersionCode();
- case "osType":
- return context.getOsType();
- case "random":
- return Optional.of(randomGenerator.get());
- case "default":
- return Optional.of(context.getUserId()
- .orElse(context.getSessionId().orElse(this.randomGenerator.get())));
- default:
- return context.getByName(stickiness);
- }
- }
+
@Override
public boolean isEnabled(Map parameters, LitmusContext litmusContext) {
final String stickiness = getStickiness(parameters);
- final Optional stickinessId = resolveStickiness(stickiness, litmusContext);
+ Optional stickinessId = StrategyUtils.resolveStickiness(stickiness, litmusContext);
final int percentage = StrategyUtils.getPercentage(parameters.get(PERCENTAGE));
final String groupId = parameters.getOrDefault(GROUP_ID, "");
diff --git a/litmus-client/src/main/java/com/navi/medici/strategy/UserWithIdStrategy.java b/litmus-client/src/main/java/com/navi/medici/strategy/UserWithIdStrategy.java
index b25953e..2e4348d 100644
--- a/litmus-client/src/main/java/com/navi/medici/strategy/UserWithIdStrategy.java
+++ b/litmus-client/src/main/java/com/navi/medici/strategy/UserWithIdStrategy.java
@@ -1,9 +1,15 @@
package com.navi.medici.strategy;
+import static com.navi.medici.strategy.FlexibleRolloutStrategy.GROUP_ID;
+import static com.navi.medici.strategy.FlexibleRolloutStrategy.PERCENTAGE;
+
import com.navi.medici.client.HttpExperimentFetcher;
import com.navi.medici.context.LitmusContext;
+import com.navi.medici.util.StrategyUtils;
import java.util.Map;
+import java.util.Optional;
+import org.apache.commons.lang3.StringUtils;
public class UserWithIdStrategy implements Strategy {
@@ -28,10 +34,31 @@ public class UserWithIdStrategy implements Strategy {
@Override
public boolean isEnabled(Map parameters, LitmusContext litmusContext) {
+ if (StringUtils.isBlank(parameters.get(PERCENTAGE))) {
+ return userWithIdSegmentCheck(parameters, litmusContext);
+ } else {
+ return segmentCheckWithRollout(parameters, litmusContext);
+ }
+ }
+
+ private boolean userWithIdSegmentCheck(Map parameters, LitmusContext litmusContext) {
+ var userId = litmusContext.getUserId().orElse(null);
var segmentName = parameters.get(SEGMENT);
- var result = experimentFetcher.segmentIdExists(segmentName, litmusContext.getUserId().orElse(null));
+ var result = experimentFetcher.segmentIdExists(segmentName, userId);
return result.getData() != null && (Boolean) result.getData();
}
+ private boolean segmentCheckWithRollout(Map parameters, LitmusContext litmusContext) {
+ var userId = litmusContext.getUserId().orElse(null);
+ var percentage = StrategyUtils.getPercentage(parameters.get(PERCENTAGE));
+ var groupId = parameters.getOrDefault(GROUP_ID, "");
+
+ var norm = StrategyUtils.getNormalizedNumber(userId, groupId);
+
+ return Optional.of(userWithIdSegmentCheck(parameters, litmusContext))
+ .map(r -> r && percentage > 0 && norm <= percentage)
+ .orElse(false);
+ }
+
}
diff --git a/litmus-client/src/main/java/com/navi/medici/util/StrategyUtils.java b/litmus-client/src/main/java/com/navi/medici/util/StrategyUtils.java
index 57f57f2..634dc36 100644
--- a/litmus-client/src/main/java/com/navi/medici/util/StrategyUtils.java
+++ b/litmus-client/src/main/java/com/navi/medici/util/StrategyUtils.java
@@ -1,7 +1,10 @@
package com.navi.medici.util;
import com.navi.medici.annotation.Nullable;
+import com.navi.medici.context.LitmusContext;
import com.sangupta.murmur.Murmur3;
+import java.util.Optional;
+import java.util.function.Supplier;
public class StrategyUtils {
@@ -47,4 +50,27 @@ public class StrategyUtils {
}
}
+ public static Optional resolveStickiness(String stickiness, LitmusContext context) {
+ Supplier randomGenerator = () -> Math.random() * 100 + "";
+ switch (stickiness) {
+ case "userId":
+ return context.getUserId();
+ case "sessionId":
+ return context.getSessionId();
+ case "deviceId":
+ return context.getDeviceId();
+ case "appVersionCode":
+ return context.getAppVersionCode();
+ case "osType":
+ return context.getOsType();
+ case "random":
+ return Optional.of(randomGenerator.get());
+ case "default":
+ return Optional.of(context.getUserId()
+ .orElse(context.getSessionId().orElse(randomGenerator.get())));
+ default:
+ return context.getByName(stickiness);
+ }
+ }
+
}
diff --git a/litmus-core/src/main/java/com/navi/medici/controller/v1/ExperimentController.java b/litmus-core/src/main/java/com/navi/medici/controller/v1/ExperimentController.java
index bed7c75..222177e 100644
--- a/litmus-core/src/main/java/com/navi/medici/controller/v1/ExperimentController.java
+++ b/litmus-core/src/main/java/com/navi/medici/controller/v1/ExperimentController.java
@@ -8,13 +8,16 @@ import io.micrometer.core.annotation.Timed;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
+import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@@ -37,6 +40,19 @@ public class ExperimentController {
return experimentService.fetchAllExperiments();
}
+ @GetMapping(value = "/vertical")
+ @Timed(value = "litmus.fetch.vertical.experiment", percentiles = {0.95, 0.99})
+ public ResponseEntity fetchExperimentsForAVertical(@RequestParam("vertical") String vertical,
+ @RequestParam("polling_time") Long pollingTime) {
+
+ var collection = experimentService.fetchAllExperimentsForVerticals(vertical, pollingTime);
+ if (collection.getLitmusExperiments().isEmpty()) {
+ return ResponseEntity.status(HttpStatus.NOT_MODIFIED).body(collection);
+ }
+
+ return ResponseEntity.ok(collection);
+ }
+
@PutMapping(value = "/attach/variants/{experiment_id}", consumes = MediaType.APPLICATION_JSON_VALUE)
@Timed(value = "litmus.attach.variants", percentiles = {0.95, 0.99})
public void attachVariants(@PathVariable("experiment_id") String experimentId,
diff --git a/litmus-core/src/main/java/com/navi/medici/service/experiment/ExperimentService.java b/litmus-core/src/main/java/com/navi/medici/service/experiment/ExperimentService.java
index 66738eb..2d35089 100644
--- a/litmus-core/src/main/java/com/navi/medici/service/experiment/ExperimentService.java
+++ b/litmus-core/src/main/java/com/navi/medici/service/experiment/ExperimentService.java
@@ -6,9 +6,12 @@ import com.navi.medici.variants.VariantDefinition;
import java.util.List;
public interface ExperimentService {
+
void create(LitmusExperiment litmusExperimentRequest);
LitmusExperimentCollection fetchAllExperiments();
void attachVariants(String experimentId, List variantDefinitions);
+
+ LitmusExperimentCollection fetchAllExperimentsForVerticals(String vertical, Long pollingTime);
}
diff --git a/litmus-core/src/main/java/com/navi/medici/service/experiment/ExperimentServiceImpl.java b/litmus-core/src/main/java/com/navi/medici/service/experiment/ExperimentServiceImpl.java
index b6691bd..f0f9fb4 100644
--- a/litmus-core/src/main/java/com/navi/medici/service/experiment/ExperimentServiceImpl.java
+++ b/litmus-core/src/main/java/com/navi/medici/service/experiment/ExperimentServiceImpl.java
@@ -1,5 +1,8 @@
package com.navi.medici.service.experiment;
+import static com.navi.medici.enums.ExperimentType.KILL_SWITCH;
+import static com.navi.medici.enums.ExperimentType.RELEASE;
+
import com.navi.medici.entity.ExperimentEntity;
import com.navi.medici.exceptions.BadRequestException;
import com.navi.medici.query.experiment.IExperimentQuery;
@@ -41,9 +44,16 @@ public record ExperimentServiceImpl(IExperimentQuery experimentQuery,
@Override
public LitmusExperimentCollection fetchAllExperiments() {
List experimentEntities = experimentQuery.findByEnabled(true);
+
List litmusExperiments = experimentEntities.stream()
.map(this::build)
- .collect(Collectors.toList());
+ .peek(le -> {
+ //This is to reduce sending strategy string over network
+ if (RELEASE.equals(le.getType()) || KILL_SWITCH.equals(le.getType())) {
+ le.setStrategies(null);
+ }
+ }).collect(Collectors.toList());
+
return LitmusExperimentCollection.builder()
.litmusExperiments(litmusExperiments)
.build();
@@ -63,6 +73,19 @@ public record ExperimentServiceImpl(IExperimentQuery experimentQuery,
experimentQuery.save(experiment);
}
+ @Override
+ public LitmusExperimentCollection fetchAllExperimentsForVerticals(String vertical, Long pollingTime) {
+ var experimentEntities = experimentQuery.findByVertical(vertical, pollingTime);
+
+ List litmusExperiments = experimentEntities.stream()
+ .map(this::build)
+ .collect(Collectors.toList());
+
+ return LitmusExperimentCollection.builder()
+ .litmusExperiments(litmusExperiments)
+ .build();
+ }
+
private LitmusExperiment build(ExperimentEntity experimentEntity) {
return LitmusExperiment.builder()
.experimentId(experimentEntity.getExperimentId())
@@ -75,6 +98,7 @@ public record ExperimentServiceImpl(IExperimentQuery experimentQuery,
.type(experimentEntity.getType())
.startTime(experimentEntity.getStartTime())
.endTime(experimentEntity.getEndTime())
+ .vertical(experimentEntity.getVertical())
.build();
}
}
diff --git a/litmus-core/src/main/resources/application.properties b/litmus-core/src/main/resources/application.properties
index a6f8a9d..c722155 100644
--- a/litmus-core/src/main/resources/application.properties
+++ b/litmus-core/src/main/resources/application.properties
@@ -14,7 +14,7 @@ spring.jpa.hibernate.ddl-auto=none
#spring.jpa.properties.hibernate.use_sql_comments=true
#spring.jpa.properties.hibernate.format_sql=true
-management.server.port=4001
+management.server.port=4000
management.endpoints.web.exposure.include=prometheus,health,info,metric,heapdump,threaddump
server.tomcat.mbeanregistry.enabled=true
spring.jmx.enabled=true
diff --git a/litmus-db/src/main/java/com/navi/medici/entity/ExperimentEntity.java b/litmus-db/src/main/java/com/navi/medici/entity/ExperimentEntity.java
index 2ea3845..3ba52fe 100644
--- a/litmus-db/src/main/java/com/navi/medici/entity/ExperimentEntity.java
+++ b/litmus-db/src/main/java/com/navi/medici/entity/ExperimentEntity.java
@@ -65,6 +65,9 @@ public class ExperimentEntity {
@Column(name = "end_time")
LocalDateTime endTime;
+ @Column(name = "vertical")
+ String vertical;
+
@Version
private Integer version;
diff --git a/litmus-db/src/main/java/com/navi/medici/query/experiment/ExperimentQueryImpl.java b/litmus-db/src/main/java/com/navi/medici/query/experiment/ExperimentQueryImpl.java
index 910bf9c..658c4db 100644
--- a/litmus-db/src/main/java/com/navi/medici/query/experiment/ExperimentQueryImpl.java
+++ b/litmus-db/src/main/java/com/navi/medici/query/experiment/ExperimentQueryImpl.java
@@ -2,6 +2,7 @@ package com.navi.medici.query.experiment;
import com.navi.medici.entity.ExperimentEntity;
import com.navi.medici.repository.ExperimentRepository;
+import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
@@ -10,6 +11,7 @@ import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class ExperimentQueryImpl implements IExperimentQuery {
+
private final ExperimentRepository experimentRepository;
@Override
@@ -27,6 +29,13 @@ public class ExperimentQueryImpl implements IExperimentQuery {
return experimentRepository.findByExperimentId(experimentId);
}
+ @Override
+ public List findByVertical(String vertical, Long pollingTime) {
+ var lastPollingTime = LocalDateTime.now().minusSeconds(pollingTime);
+
+ return experimentRepository.findByVerticalAndUpdatedTime(vertical, lastPollingTime, LocalDateTime.now());
+ }
+
@Override
public void save(ExperimentEntity experiment) {
experimentRepository.save(experiment);
diff --git a/litmus-db/src/main/java/com/navi/medici/query/experiment/IExperimentQuery.java b/litmus-db/src/main/java/com/navi/medici/query/experiment/IExperimentQuery.java
index 0a05d53..a6b260c 100644
--- a/litmus-db/src/main/java/com/navi/medici/query/experiment/IExperimentQuery.java
+++ b/litmus-db/src/main/java/com/navi/medici/query/experiment/IExperimentQuery.java
@@ -11,5 +11,7 @@ public interface IExperimentQuery {
Optional findByExperimentId(String experimentId);
+ List findByVertical(String vertical, Long pollingTime);
+
void save(ExperimentEntity experiment);
}
diff --git a/litmus-db/src/main/java/com/navi/medici/repository/ExperimentRepository.java b/litmus-db/src/main/java/com/navi/medici/repository/ExperimentRepository.java
index 141d2d1..2169d31 100644
--- a/litmus-db/src/main/java/com/navi/medici/repository/ExperimentRepository.java
+++ b/litmus-db/src/main/java/com/navi/medici/repository/ExperimentRepository.java
@@ -1,16 +1,26 @@
package com.navi.medici.repository;
import com.navi.medici.entity.ExperimentEntity;
+import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
+import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
+import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface ExperimentRepository extends CrudRepository {
+
List findByEnabled(Boolean enabled);
Optional findByName(String name);
Optional findByExperimentId(String experimentId);
+
+ @Query(value = "select * from experiments e where e.vertical = :vertical and e.updated_at >= :lastPollingTime and e.updated_at <= :currentTime ",
+ nativeQuery = true)
+ List findByVerticalAndUpdatedTime(@Param("vertical") String vertical,
+ @Param("lastPollingTime") LocalDateTime lastPollingTime,
+ @Param("currentTime") LocalDateTime currentTime);
}
diff --git a/litmus-liquibase/src/main/resources/db/changelog/202204011642-add-vertical-column.sql b/litmus-liquibase/src/main/resources/db/changelog/202204011642-add-vertical-column.sql
new file mode 100644
index 0000000..6e213e5
--- /dev/null
+++ b/litmus-liquibase/src/main/resources/db/changelog/202204011642-add-vertical-column.sql
@@ -0,0 +1,6 @@
+--liquibase formatted sql
+
+--changeset author:chandresh id:202204011642
+
+ALTER TABLE experiments
+ADD COLUMN vertical VARCHAR;
\ No newline at end of file
diff --git a/litmus-mock/pom.xml b/litmus-mock/pom.xml
index 38311c8..899759f 100644
--- a/litmus-mock/pom.xml
+++ b/litmus-mock/pom.xml
@@ -27,7 +27,18 @@
org.springframework.boot
- spring-boot-starter-webflux
+ spring-boot-starter-actuator
+
+
+ com.squareup.okhttp3
+ okhttp
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
org.springframework.boot
@@ -36,6 +47,18 @@
+
+ io.micrometer
+ micrometer-registry-prometheus
+ 1.8.4
+
+
+
+ bouncycastle
+ bcprov-jdk16
+ 140
+
+
javax.servlet
javax.servlet-api
diff --git a/litmus-mock/src/main/java/com/navi/medici/container/MockContainer.java b/litmus-mock/src/main/java/com/navi/medici/container/MockContainer.java
index 7b39e5a..dff6cb2 100644
--- a/litmus-mock/src/main/java/com/navi/medici/container/MockContainer.java
+++ b/litmus-mock/src/main/java/com/navi/medici/container/MockContainer.java
@@ -3,6 +3,7 @@ package com.navi.medici.container;
import com.navi.medici.config.LitmusConfig;
import com.navi.medici.litmus.DefaultLitmus;
import com.navi.medici.litmus.Litmus;
+import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@@ -10,13 +11,15 @@ import org.springframework.stereotype.Component;
public class MockContainer {
@Bean
- public Litmus litmus() {
+ public Litmus litmus(MeterRegistry meterRegistry) {
var litmusConfig = LitmusConfig.builder()
.litmusAPI("http://localhost:12000/litmus-core/v1")
.appName("litmus-mock")
.instanceId("test-instance")
.litmusContextProvider(new CustomLitmusContextProvider())
.clickStreamAPI("https://dev-janus.np.navi-tech.in/events/json")
+ .vertical("PL")
+ .meterRegistry(meterRegistry)
.build();
Litmus litmus = new DefaultLitmus(litmusConfig);
diff --git a/litmus-mock/src/main/resources/application.properties b/litmus-mock/src/main/resources/application.properties
index e97e724..a689ab2 100644
--- a/litmus-mock/src/main/resources/application.properties
+++ b/litmus-mock/src/main/resources/application.properties
@@ -1 +1,4 @@
-server.port=11000
\ No newline at end of file
+server.port=11000
+
+management.server.port=4001
+management.endpoints.web.exposure.include=prometheus,health,info,metric,heapdump,threaddump
\ No newline at end of file
diff --git a/litmus-proxy/pom.xml b/litmus-proxy/pom.xml
index b56c5a8..a43f228 100644
--- a/litmus-proxy/pom.xml
+++ b/litmus-proxy/pom.xml
@@ -31,6 +31,8 @@
spring-boot-starter-web
+
+
org.springframework.boot
spring-boot-starter-actuator
diff --git a/litmus-proxy/src/main/java/com/navi/medici/container/LitmusProxyContainer.java b/litmus-proxy/src/main/java/com/navi/medici/container/LitmusProxyContainer.java
index af5975d..a58f241 100644
--- a/litmus-proxy/src/main/java/com/navi/medici/container/LitmusProxyContainer.java
+++ b/litmus-proxy/src/main/java/com/navi/medici/container/LitmusProxyContainer.java
@@ -20,6 +20,7 @@ public record LitmusProxyContainer(LitmusProxyConfig litmusProxyConfig, LitmusCo
.instanceId("test-instance")
.litmusContextProvider(litmusContextProvider)
.clickStreamAPI(litmusProxyConfig.getClickStreamEndpoint())
+ .vertical("PL")
.build();
Litmus litmus = new DefaultLitmus(litmusConfig);