Add dynamic configuration to enable detailed Lettuce metrics

This commit is contained in:
Chris Eager 2024-02-09 09:22:51 -06:00 committed by Chris Eager
parent ff59ef8094
commit 699b0c775a
9 changed files with 76 additions and 20 deletions

View File

@ -312,7 +312,13 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
UncaughtExceptionHandler.register(); UncaughtExceptionHandler.register();
MetricsUtil.configureRegistries(config, environment); DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager =
new DynamicConfigurationManager<>(config.getAppConfig().getApplication(),
config.getAppConfig().getEnvironment(),
config.getAppConfig().getConfigurationName(),
DynamicConfiguration.class);
MetricsUtil.configureRegistries(config, environment, dynamicConfigurationManager);
final boolean useRemoteAddress = Optional.ofNullable( final boolean useRemoteAddress = Optional.ofNullable(
System.getenv("SIGNAL_USE_REMOTE_ADDRESS")) System.getenv("SIGNAL_USE_REMOTE_ADDRESS"))
@ -342,12 +348,6 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
DynamoDbClient dynamoDbClient = DynamoDbFromConfig.client(config.getDynamoDbClientConfiguration(), DynamoDbClient dynamoDbClient = DynamoDbFromConfig.client(config.getDynamoDbClientConfiguration(),
AWSSDK_CREDENTIALS_PROVIDER); AWSSDK_CREDENTIALS_PROVIDER);
DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager =
new DynamicConfigurationManager<>(config.getAppConfig().getApplication(),
config.getAppConfig().getEnvironment(),
config.getAppConfig().getConfigurationName(),
DynamicConfiguration.class);
BlockingQueue<Runnable> messageDeletionQueue = new LinkedBlockingQueue<>(); BlockingQueue<Runnable> messageDeletionQueue = new LinkedBlockingQueue<>();
Metrics.gaugeCollectionSize(name(getClass(), "messageDeletionQueueSize"), Collections.emptyList(), Metrics.gaugeCollectionSize(name(getClass(), "messageDeletionQueueSize"), Collections.emptyList(),
messageDeletionQueue); messageDeletionQueue);

View File

@ -63,6 +63,10 @@ public class DynamicConfiguration {
@Valid @Valid
DynamicVirtualThreadConfiguration virtualThreads = new DynamicVirtualThreadConfiguration(Collections.emptySet()); DynamicVirtualThreadConfiguration virtualThreads = new DynamicVirtualThreadConfiguration(Collections.emptySet());
@JsonProperty
@Valid
DynamicMetricsConfiguration metricsConfiguration = new DynamicMetricsConfiguration(false);
public Optional<DynamicExperimentEnrollmentConfiguration> getExperimentEnrollmentConfiguration( public Optional<DynamicExperimentEnrollmentConfiguration> getExperimentEnrollmentConfiguration(
final String experimentName) { final String experimentName) {
return Optional.ofNullable(experiments.get(experimentName)); return Optional.ofNullable(experiments.get(experimentName));
@ -113,4 +117,8 @@ public class DynamicConfiguration {
return virtualThreads; return virtualThreads;
} }
public DynamicMetricsConfiguration getMetricsConfiguration() {
return metricsConfiguration;
}
} }

View File

@ -0,0 +1,13 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration.dynamic;
/**
* @param enableLettuceRemoteTag - whether the `remote` tag should be added. Note: although this is dynamic, meters are
* cached after creation, so changes will only affect servers launched after the change.
*/
public record DynamicMetricsConfiguration(boolean enableLettuceRemoteTag) {
}

View File

@ -18,7 +18,9 @@ import io.micrometer.statsd.StatsdMeterRegistry;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.whispersystems.textsecuregcm.WhisperServerConfiguration; import org.whispersystems.textsecuregcm.WhisperServerConfiguration;
import org.whispersystems.textsecuregcm.WhisperServerVersion; import org.whispersystems.textsecuregcm.WhisperServerVersion;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.push.PushLatencyManager; import org.whispersystems.textsecuregcm.push.PushLatencyManager;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
import org.whispersystems.textsecuregcm.util.Constants; import org.whispersystems.textsecuregcm.util.Constants;
public class MetricsUtil { public class MetricsUtil {
@ -41,7 +43,8 @@ public class MetricsUtil {
return sb.toString(); return sb.toString();
} }
public static void configureRegistries(final WhisperServerConfiguration config, final Environment environment) { public static void configureRegistries(final WhisperServerConfiguration config, final Environment environment,
DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager) {
SharedMetricRegistries.add(Constants.METRICS_NAME, environment.metrics()); SharedMetricRegistries.add(Constants.METRICS_NAME, environment.metrics());
{ {
@ -54,7 +57,7 @@ public class MetricsUtil {
"version", WhisperServerVersion.getServerVersion(), "version", WhisperServerVersion.getServerVersion(),
"env", config.getDatadogConfiguration().getEnvironment())); "env", config.getDatadogConfiguration().getEnvironment()));
configureMeterFilters(dogstatsdMeterRegistry.config()); configureMeterFilters(dogstatsdMeterRegistry.config(), dynamicConfigurationManager);
Metrics.addRegistry(dogstatsdMeterRegistry); Metrics.addRegistry(dogstatsdMeterRegistry);
} }
@ -64,7 +67,8 @@ public class MetricsUtil {
} }
@VisibleForTesting @VisibleForTesting
static MeterRegistry.Config configureMeterFilters(MeterRegistry.Config config) { static MeterRegistry.Config configureMeterFilters(MeterRegistry.Config config,
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager) {
final DistributionStatisticConfig defaultDistributionStatisticConfig = DistributionStatisticConfig.builder() final DistributionStatisticConfig defaultDistributionStatisticConfig = DistributionStatisticConfig.builder()
.percentiles(.75, .95, .99, .999) .percentiles(.75, .95, .99, .999)
.build(); .build();
@ -84,6 +88,8 @@ public class MetricsUtil {
return id.withName(PREFIX + "." + id.getName()) return id.withName(PREFIX + "." + id.getName())
.replaceTags(id.getTags().stream() .replaceTags(id.getTags().stream()
.filter(tag -> !"command".equals(tag.getKey())) .filter(tag -> !"command".equals(tag.getKey()))
.filter(tag -> dynamicConfigurationManager.getConfiguration().getMetricsConfiguration().
enableLettuceRemoteTag() || !"remote".equals(tag.getKey()))
.toList()); .toList());
} }

View File

@ -63,11 +63,13 @@ public abstract class AbstractSinglePassCrawlAccountsCommand extends Environment
final WhisperServerConfiguration configuration) throws Exception { final WhisperServerConfiguration configuration) throws Exception {
UncaughtExceptionHandler.register(); UncaughtExceptionHandler.register();
MetricsUtil.configureRegistries(configuration, environment);
this.namespace = namespace; this.namespace = namespace;
this.commandDependencies = CommandDependencies.build(getName(), environment, configuration); this.commandDependencies = CommandDependencies.build(getName(), environment, configuration);
MetricsUtil.configureRegistries(configuration, environment, commandDependencies.dynamicConfigurationManager());
final int segments = Objects.requireNonNull(namespace.getInt(SEGMENT_COUNT)); final int segments = Objects.requireNonNull(namespace.getInt(SEGMENT_COUNT));
logger.info("Crawling accounts with {} segments and {} processors", logger.info("Crawling accounts with {} segments and {} processors",

View File

@ -60,7 +60,8 @@ record CommandDependencies(
ClientPresenceManager clientPresenceManager, ClientPresenceManager clientPresenceManager,
KeysManager keysManager, KeysManager keysManager,
FaultTolerantRedisCluster cacheCluster, FaultTolerantRedisCluster cacheCluster,
ClientResources redisClusterClientResources) { ClientResources redisClusterClientResources,
DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager) {
static CommandDependencies build( static CommandDependencies build(
final String name, final String name,
@ -196,7 +197,8 @@ record CommandDependencies(
clientPresenceManager, clientPresenceManager,
keys, keys,
cacheCluster, cacheCluster,
redisClusterClientResources redisClusterClientResources,
dynamicConfigurationManager
); );
} }

View File

@ -50,7 +50,9 @@ public class MessagePersisterServiceCommand extends ServerCommand<WhisperServerC
UncaughtExceptionHandler.register(); UncaughtExceptionHandler.register();
MetricsUtil.configureRegistries(configuration, environment); final CommandDependencies deps = CommandDependencies.build("message-persister-service", environment, configuration);
MetricsUtil.configureRegistries(configuration, environment, deps.dynamicConfigurationManager());
if (configuration.getServerFactory() instanceof DefaultServerFactory defaultServerFactory) { if (configuration.getServerFactory() instanceof DefaultServerFactory defaultServerFactory) {
defaultServerFactory.getApplicationConnectors() defaultServerFactory.getApplicationConnectors()
@ -61,7 +63,6 @@ public class MessagePersisterServiceCommand extends ServerCommand<WhisperServerC
}); });
} }
final CommandDependencies deps = CommandDependencies.build("message-persister-service", environment, configuration);
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager = new DynamicConfigurationManager<>( final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager = new DynamicConfigurationManager<>(
configuration.getAppConfig().getApplication(), configuration.getAppConfig().getApplication(),

View File

@ -52,7 +52,9 @@ public class ScheduledApnPushNotificationSenderServiceCommand extends ServerComm
UncaughtExceptionHandler.register(); UncaughtExceptionHandler.register();
MetricsUtil.configureRegistries(configuration, environment); final CommandDependencies deps = CommandDependencies.build("scheduled-apn-sender", environment, configuration);
MetricsUtil.configureRegistries(configuration, environment, deps.dynamicConfigurationManager());
if (configuration.getServerFactory() instanceof DefaultServerFactory defaultServerFactory) { if (configuration.getServerFactory() instanceof DefaultServerFactory defaultServerFactory) {
defaultServerFactory.getApplicationConnectors() defaultServerFactory.getApplicationConnectors()
@ -63,7 +65,6 @@ public class ScheduledApnPushNotificationSenderServiceCommand extends ServerComm
}); });
} }
final CommandDependencies deps = CommandDependencies.build("scheduled-apn-sender", environment, configuration);
final FaultTolerantRedisCluster pushSchedulerCluster = new FaultTolerantRedisCluster("push_scheduler", final FaultTolerantRedisCluster pushSchedulerCluster = new FaultTolerantRedisCluster("push_scheduler",
configuration.getPushSchedulerCluster(), deps.redisClusterClientResources()); configuration.getPushSchedulerCluster(), deps.redisClusterClientResources());

View File

@ -7,12 +7,20 @@ package org.whispersystems.textsecuregcm.metrics;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import io.micrometer.core.instrument.Meter; import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import java.util.List; import java.util.List;
import org.assertj.core.api.AbstractStringAssert;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicMetricsConfiguration;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
class MetricsUtilTest { class MetricsUtilTest {
@ -25,10 +33,18 @@ class MetricsUtilTest {
MetricsUtil.name(MetricsUtilTest.class, "namespace", "metric")); MetricsUtil.name(MetricsUtilTest.class, "namespace", "metric"));
} }
@Test @ParameterizedTest
void lettuceTagRejection() { @ValueSource(booleans = {true, false})
void lettuceTagRejection(final boolean enableLettuceRemoteTag) {
DynamicConfiguration dynamicConfiguration = mock(DynamicConfiguration.class);
DynamicMetricsConfiguration metricsConfiguration = new DynamicMetricsConfiguration(enableLettuceRemoteTag);
when(dynamicConfiguration.getMetricsConfiguration()).thenReturn(metricsConfiguration);
DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager =
mock(DynamicConfigurationManager.class);
when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration);
MeterRegistry registry = new SimpleMeterRegistry(); MeterRegistry registry = new SimpleMeterRegistry();
MetricsUtil.configureMeterFilters(registry.config()); MetricsUtil.configureMeterFilters(registry.config(), dynamicConfigurationManager);
registry.counter("lettuce.command.completion.max", "command", "hello", "remote", "world", "allowed", "!").increment(); registry.counter("lettuce.command.completion.max", "command", "hello", "remote", "world", "allowed", "!").increment();
final List<Meter> meters = registry.getMeters(); final List<Meter> meters = registry.getMeters();
@ -37,6 +53,13 @@ class MetricsUtilTest {
Meter meter = meters.get(0); Meter meter = meters.get(0);
assertThat(meter.getId().getName()).isEqualTo("chat.lettuce.command.completion.max"); assertThat(meter.getId().getName()).isEqualTo("chat.lettuce.command.completion.max");
assertThat(meter.getId().getTag("command")).isNull(); assertThat(meter.getId().getTag("command")).isNull();
AbstractStringAssert<?> remoteTag = assertThat(meter.getId().getTag("remote"));
if (enableLettuceRemoteTag) {
remoteTag.isNotNull();
} else {
remoteTag.isNull();
}
assertThat(meter.getId().getTag("allowed")).isNotNull(); assertThat(meter.getId().getTag("allowed")).isNotNull();
} }
} }