Add finer grain rollouts to experiments

This commit is contained in:
ravi-signal
2024-05-06 13:28:32 -05:00
committed by GitHub
parent 7aff81547a
commit 10bb2a6a10
5 changed files with 132 additions and 33 deletions

View File

@@ -6,6 +6,7 @@
package org.whispersystems.textsecuregcm.configuration.dynamic;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.annotations.VisibleForTesting;
import javax.validation.Valid;
import javax.validation.constraints.Max;
@@ -16,21 +17,52 @@ import java.util.UUID;
public class DynamicExperimentEnrollmentConfiguration {
public static class UuidSelector {
@JsonProperty
@Valid
private Set<UUID> enrolledUuids = Collections.emptySet();
private Set<UUID> uuids = Collections.emptySet();
/**
* What percentage of enrolled UUIDs should the experiment be enabled for.
* <p>
* Unlike {@link this#enrollmentPercentage}, this is not stable by UUID. The same UUID may be
* enrolled/unenrolled across calls.
*/
@JsonProperty
@Valid
@Min(0)
@Max(100)
private int enrollmentPercentage = 0;
private int uuidEnrollmentPercentage = 100;
public Set<UUID> getEnrolledUuids() {
return enrolledUuids;
public Set<UUID> getUuids() {
return uuids;
}
public int getEnrollmentPercentage() {
return enrollmentPercentage;
public int getUuidEnrollmentPercentage() {
return uuidEnrollmentPercentage;
}
}
private UuidSelector uuidSelector = new UuidSelector();
/**
* If the UUID is not enrolled via {@link UuidSelector#uuids}, what is the percentage chance it should be enrolled.
* <p>
* This is stable by UUID, for a given configuration if a UUID is enrolled it will always be enrolled on every call.
*/
@JsonProperty
@Valid
@Min(0)
@Max(100)
private int enrollmentPercentage = 0;
public int getEnrollmentPercentage() {
return enrollmentPercentage;
}
public UuidSelector getUuidSelector() {
return uuidSelector;
}
}

View File

@@ -6,7 +6,10 @@
package org.whispersystems.textsecuregcm.experiment;
import java.util.Optional;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
import com.google.common.annotations.VisibleForTesting;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicExperimentEnrollmentConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicPreRegistrationExperimentEnrollmentConfiguration;
@@ -16,9 +19,20 @@ import org.whispersystems.textsecuregcm.util.Util;
public class ExperimentEnrollmentManager {
private final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager;
private final Random random;
public ExperimentEnrollmentManager(final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager) {
public ExperimentEnrollmentManager(
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager) {
this(dynamicConfigurationManager, ThreadLocalRandom.current());
}
@VisibleForTesting
ExperimentEnrollmentManager(
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager,
final Random random) {
this.dynamicConfigurationManager = dynamicConfigurationManager;
this.random = random;
}
public boolean isEnrolled(final UUID accountUuid, final String experimentName) {
@@ -28,8 +42,9 @@ public class ExperimentEnrollmentManager {
return maybeConfiguration.map(config -> {
if (config.getEnrolledUuids().contains(accountUuid)) {
return true;
if (config.getUuidSelector().getUuids().contains(accountUuid)) {
final int r = random.nextInt(100);
return r < config.getUuidSelector().getUuidEnrollmentPercentage();
}
return isEnrolled(accountUuid, config.getEnrollmentPercentage(), experimentName);