Migrate remaining custom metrics from Dropwizard to Micrometer
And remove some that are obsolete or duplicative.
This commit is contained in:
parent
419ec6e308
commit
a38bf25e68
|
@ -8,11 +8,9 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import io.dropwizard.core.Configuration;
|
import io.dropwizard.core.Configuration;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
import org.whispersystems.textsecuregcm.attachments.TusConfiguration;
|
import org.whispersystems.textsecuregcm.attachments.TusConfiguration;
|
||||||
|
@ -170,11 +168,6 @@ public class WhisperServerConfiguration extends Configuration {
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private RedisClusterConfiguration clientPresenceCluster;
|
private RedisClusterConfiguration clientPresenceCluster;
|
||||||
|
|
||||||
@Valid
|
|
||||||
@NotNull
|
|
||||||
@JsonProperty
|
|
||||||
private Set<String> testDevices = new HashSet<>();
|
|
||||||
|
|
||||||
@Valid
|
@Valid
|
||||||
@NotNull
|
@NotNull
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
|
@ -461,10 +454,6 @@ public class WhisperServerConfiguration extends Configuration {
|
||||||
return unidentifiedDelivery;
|
return unidentifiedDelivery;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<String> getTestDevices() {
|
|
||||||
return testDevices;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, Integer> getMaxDevices() {
|
public Map<String, Integer> getMaxDevices() {
|
||||||
Map<String, Integer> results = new HashMap<>();
|
Map<String, Integer> results = new HashMap<>();
|
||||||
|
|
||||||
|
|
|
@ -671,8 +671,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||||
environment.lifecycle().manage(clientReleaseManager);
|
environment.lifecycle().manage(clientReleaseManager);
|
||||||
environment.lifecycle().manage(virtualThreadPinEventMonitor);
|
environment.lifecycle().manage(virtualThreadPinEventMonitor);
|
||||||
|
|
||||||
final RegistrationCaptchaManager registrationCaptchaManager = new RegistrationCaptchaManager(captchaChecker,
|
final RegistrationCaptchaManager registrationCaptchaManager = new RegistrationCaptchaManager(captchaChecker);
|
||||||
rateLimiters, config.getTestDevices(), dynamicConfigurationManager);
|
|
||||||
|
|
||||||
StaticCredentialsProvider cdnCredentialsProvider = StaticCredentialsProvider
|
StaticCredentialsProvider cdnCredentialsProvider = StaticCredentialsProvider
|
||||||
.create(AwsBasicCredentials.create(
|
.create(AwsBasicCredentials.create(
|
||||||
|
|
|
@ -5,50 +5,15 @@
|
||||||
|
|
||||||
package org.whispersystems.textsecuregcm.captcha;
|
package org.whispersystems.textsecuregcm.captcha;
|
||||||
|
|
||||||
import static com.codahale.metrics.MetricRegistry.name;
|
|
||||||
|
|
||||||
import com.codahale.metrics.Meter;
|
|
||||||
import com.codahale.metrics.MetricRegistry;
|
|
||||||
import com.codahale.metrics.SharedMetricRegistries;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicCaptchaConfiguration;
|
|
||||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
|
||||||
import org.whispersystems.textsecuregcm.controllers.AccountController;
|
|
||||||
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
|
|
||||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
|
||||||
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
|
|
||||||
import org.whispersystems.textsecuregcm.util.Constants;
|
|
||||||
import org.whispersystems.textsecuregcm.util.Util;
|
|
||||||
|
|
||||||
public class RegistrationCaptchaManager {
|
public class RegistrationCaptchaManager {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(RegistrationCaptchaManager.class);
|
|
||||||
|
|
||||||
private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME);
|
|
||||||
private final Meter countryFilteredHostMeter = metricRegistry.meter(
|
|
||||||
name(AccountController.class, "country_limited_host"));
|
|
||||||
private final Meter rateLimitedHostMeter = metricRegistry.meter(name(AccountController.class, "rate_limited_host"));
|
|
||||||
private final Meter rateLimitedPrefixMeter = metricRegistry.meter(
|
|
||||||
name(AccountController.class, "rate_limited_prefix"));
|
|
||||||
|
|
||||||
private final CaptchaChecker captchaChecker;
|
private final CaptchaChecker captchaChecker;
|
||||||
private final RateLimiters rateLimiters;
|
|
||||||
private final Set<String> testDevices;
|
|
||||||
private final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager;
|
|
||||||
|
|
||||||
|
public RegistrationCaptchaManager(final CaptchaChecker captchaChecker) {
|
||||||
public RegistrationCaptchaManager(final CaptchaChecker captchaChecker, final RateLimiters rateLimiters,
|
|
||||||
final Set<String> testDevices,
|
|
||||||
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager) {
|
|
||||||
this.captchaChecker = captchaChecker;
|
this.captchaChecker = captchaChecker;
|
||||||
this.rateLimiters = rateLimiters;
|
|
||||||
this.testDevices = testDevices;
|
|
||||||
this.dynamicConfigurationManager = dynamicConfigurationManager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2013-2020 Signal Messenger, LLC
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.whispersystems.textsecuregcm.metrics;
|
|
||||||
|
|
||||||
import static com.codahale.metrics.MetricRegistry.name;
|
|
||||||
|
|
||||||
import io.micrometer.core.instrument.Metrics;
|
|
||||||
import io.micrometer.core.instrument.Tag;
|
|
||||||
import java.lang.management.BufferPoolMXBean;
|
|
||||||
import java.lang.management.ManagementFactory;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class BufferPoolGauges {
|
|
||||||
|
|
||||||
private BufferPoolGauges() {}
|
|
||||||
|
|
||||||
public static void registerMetrics() {
|
|
||||||
for (final BufferPoolMXBean bufferPoolMXBean : ManagementFactory.getPlatformMXBeans(BufferPoolMXBean.class)) {
|
|
||||||
final List<Tag> tags = List.of(Tag.of("bufferPoolName", bufferPoolMXBean.getName()));
|
|
||||||
|
|
||||||
Metrics.gauge(name(BufferPoolGauges.class, "count"), tags, bufferPoolMXBean, BufferPoolMXBean::getCount);
|
|
||||||
Metrics.gauge(name(BufferPoolGauges.class, "memory_used"), tags, bufferPoolMXBean, BufferPoolMXBean::getMemoryUsed);
|
|
||||||
Metrics.gauge(name(BufferPoolGauges.class, "total_capacity"), tags, bufferPoolMXBean, BufferPoolMXBean::getTotalCapacity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2013-2020 Signal Messenger, LLC
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.whispersystems.textsecuregcm.metrics;
|
|
||||||
|
|
||||||
import com.codahale.metrics.CachedGauge;
|
|
||||||
import com.sun.management.OperatingSystemMXBean;
|
|
||||||
|
|
||||||
import java.lang.management.ManagementFactory;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
public class CpuUsageGauge extends CachedGauge<Integer> {
|
|
||||||
|
|
||||||
private final OperatingSystemMXBean operatingSystemMXBean;
|
|
||||||
|
|
||||||
public CpuUsageGauge(final long timeout, final TimeUnit timeoutUnit) {
|
|
||||||
super(timeout, timeoutUnit);
|
|
||||||
|
|
||||||
this.operatingSystemMXBean = (com.sun.management.OperatingSystemMXBean)
|
|
||||||
ManagementFactory.getOperatingSystemMXBean();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Integer loadValue() {
|
|
||||||
return (int) Math.ceil(operatingSystemMXBean.getCpuLoad() * 100);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2013-2020 Signal Messenger, LLC
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.whispersystems.textsecuregcm.metrics;
|
|
||||||
|
|
||||||
|
|
||||||
import com.codahale.metrics.Gauge;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
public class FileDescriptorGauge implements Gauge<Integer> {
|
|
||||||
@Override
|
|
||||||
public Integer getValue() {
|
|
||||||
File file = new File("/proc/self/fd");
|
|
||||||
|
|
||||||
if (file.isDirectory() && file.exists()) {
|
|
||||||
return file.list().length;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,21 +5,20 @@
|
||||||
|
|
||||||
package org.whispersystems.textsecuregcm.metrics;
|
package org.whispersystems.textsecuregcm.metrics;
|
||||||
|
|
||||||
import com.codahale.metrics.Gauge;
|
|
||||||
import com.sun.management.OperatingSystemMXBean;
|
import com.sun.management.OperatingSystemMXBean;
|
||||||
import java.lang.management.ManagementFactory;
|
import java.lang.management.ManagementFactory;
|
||||||
|
|
||||||
public class FreeMemoryGauge implements Gauge<Long> {
|
public class FreeMemoryGauge implements Gauge {
|
||||||
|
|
||||||
private final OperatingSystemMXBean operatingSystemMXBean;
|
private final OperatingSystemMXBean operatingSystemMXBean;
|
||||||
|
|
||||||
public FreeMemoryGauge() {
|
public FreeMemoryGauge() {
|
||||||
this.operatingSystemMXBean = (com.sun.management.OperatingSystemMXBean)
|
this.operatingSystemMXBean = (com.sun.management.OperatingSystemMXBean)
|
||||||
ManagementFactory.getOperatingSystemMXBean();
|
ManagementFactory.getOperatingSystemMXBean();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Long getValue() {
|
public double getValue() {
|
||||||
return operatingSystemMXBean.getFreeMemorySize();
|
return operatingSystemMXBean.getFreeMemorySize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
package org.whispersystems.textsecuregcm.metrics;
|
package org.whispersystems.textsecuregcm.metrics;
|
||||||
|
|
||||||
import static com.codahale.metrics.MetricRegistry.name;
|
import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name;
|
||||||
|
|
||||||
import io.micrometer.core.instrument.Metrics;
|
import io.micrometer.core.instrument.Metrics;
|
||||||
import io.micrometer.core.instrument.Tag;
|
import io.micrometer.core.instrument.Tag;
|
||||||
|
@ -15,14 +15,17 @@ import java.util.List;
|
||||||
|
|
||||||
public class GarbageCollectionGauges {
|
public class GarbageCollectionGauges {
|
||||||
|
|
||||||
private GarbageCollectionGauges() {}
|
private GarbageCollectionGauges() {
|
||||||
|
}
|
||||||
|
|
||||||
public static void registerMetrics() {
|
public static void registerMetrics() {
|
||||||
for (final GarbageCollectorMXBean garbageCollectorMXBean : ManagementFactory.getGarbageCollectorMXBeans()) {
|
for (final GarbageCollectorMXBean garbageCollectorMXBean : ManagementFactory.getGarbageCollectorMXBeans()) {
|
||||||
final List<Tag> tags = List.of(Tag.of("memoryManagerName", garbageCollectorMXBean.getName()));
|
final List<Tag> tags = List.of(Tag.of("memoryManagerName", garbageCollectorMXBean.getName()));
|
||||||
|
|
||||||
Metrics.gauge(name(GarbageCollectionGauges.class, "collection_count"), tags, garbageCollectorMXBean, GarbageCollectorMXBean::getCollectionCount);
|
Metrics.gauge(name(GarbageCollectionGauges.class, "collectionCount"), tags, garbageCollectorMXBean,
|
||||||
Metrics.gauge(name(GarbageCollectionGauges.class, "collection_time"), tags, garbageCollectorMXBean, GarbageCollectorMXBean::getCollectionTime);
|
GarbageCollectorMXBean::getCollectionCount);
|
||||||
}
|
Metrics.gauge(name(GarbageCollectionGauges.class, "collectionTime"), tags, garbageCollectorMXBean,
|
||||||
|
GarbageCollectorMXBean::getCollectionTime);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.whispersystems.textsecuregcm.metrics;
|
||||||
|
|
||||||
|
interface Gauge {
|
||||||
|
|
||||||
|
double getValue();
|
||||||
|
}
|
|
@ -1,28 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2013-2020 Signal Messenger, LLC
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.whispersystems.textsecuregcm.metrics;
|
|
||||||
|
|
||||||
import com.codahale.metrics.Gauge;
|
|
||||||
import com.sun.management.UnixOperatingSystemMXBean;
|
|
||||||
|
|
||||||
import java.lang.management.ManagementFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A gauge that reports the maximum number of file descriptors allowed by the operating system.
|
|
||||||
*/
|
|
||||||
public class MaxFileDescriptorGauge implements Gauge<Long> {
|
|
||||||
|
|
||||||
private final UnixOperatingSystemMXBean unixOperatingSystemMXBean;
|
|
||||||
|
|
||||||
public MaxFileDescriptorGauge() {
|
|
||||||
this.unixOperatingSystemMXBean = (UnixOperatingSystemMXBean)ManagementFactory.getOperatingSystemMXBean();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Long getValue() {
|
|
||||||
return unixOperatingSystemMXBean.getMaxFileDescriptorCount();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -12,10 +12,13 @@ import io.micrometer.core.instrument.Meter;
|
||||||
import io.micrometer.core.instrument.MeterRegistry;
|
import io.micrometer.core.instrument.MeterRegistry;
|
||||||
import io.micrometer.core.instrument.Metrics;
|
import io.micrometer.core.instrument.Metrics;
|
||||||
import io.micrometer.core.instrument.Tags;
|
import io.micrometer.core.instrument.Tags;
|
||||||
|
import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics;
|
||||||
|
import io.micrometer.core.instrument.binder.jvm.JvmThreadMetrics;
|
||||||
|
import io.micrometer.core.instrument.binder.system.FileDescriptorMetrics;
|
||||||
|
import io.micrometer.core.instrument.binder.system.ProcessorMetrics;
|
||||||
import io.micrometer.core.instrument.config.MeterFilter;
|
import io.micrometer.core.instrument.config.MeterFilter;
|
||||||
import io.micrometer.core.instrument.distribution.DistributionStatisticConfig;
|
import io.micrometer.core.instrument.distribution.DistributionStatisticConfig;
|
||||||
import io.micrometer.statsd.StatsdMeterRegistry;
|
import io.micrometer.statsd.StatsdMeterRegistry;
|
||||||
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.configuration.dynamic.DynamicConfiguration;
|
||||||
|
@ -101,19 +104,20 @@ public class MetricsUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void registerSystemResourceMetrics(final Environment environment) {
|
public static void registerSystemResourceMetrics(final Environment environment) {
|
||||||
environment.metrics().register(name(CpuUsageGauge.class, "cpu"), new CpuUsageGauge(3, TimeUnit.SECONDS));
|
new ProcessorMetrics().bindTo(Metrics.globalRegistry);
|
||||||
environment.metrics().register(name(FreeMemoryGauge.class, "free_memory"), new FreeMemoryGauge());
|
registerGauge(name(FreeMemoryGauge.class, "freeMemory"), new FreeMemoryGauge());
|
||||||
environment.metrics().register(name(NetworkSentGauge.class, "bytes_sent"), new NetworkSentGauge());
|
new FileDescriptorMetrics().bindTo(Metrics.globalRegistry);
|
||||||
environment.metrics().register(name(NetworkReceivedGauge.class, "bytes_received"), new NetworkReceivedGauge());
|
registerGauge(name(OperatingSystemMemoryGauge.class, "buffers"), new OperatingSystemMemoryGauge("Buffers"));
|
||||||
environment.metrics().register(name(FileDescriptorGauge.class, "fd_count"), new FileDescriptorGauge());
|
registerGauge(name(OperatingSystemMemoryGauge.class, "cached"), new OperatingSystemMemoryGauge("Cached"));
|
||||||
environment.metrics().register(name(MaxFileDescriptorGauge.class, "max_fd_count"), new MaxFileDescriptorGauge());
|
|
||||||
environment.metrics()
|
new JvmMemoryMetrics().bindTo(Metrics.globalRegistry);
|
||||||
.register(name(OperatingSystemMemoryGauge.class, "buffers"), new OperatingSystemMemoryGauge("Buffers"));
|
new JvmThreadMetrics().bindTo(Metrics.globalRegistry);
|
||||||
environment.metrics()
|
|
||||||
.register(name(OperatingSystemMemoryGauge.class, "cached"), new OperatingSystemMemoryGauge("Cached"));
|
|
||||||
|
|
||||||
BufferPoolGauges.registerMetrics();
|
|
||||||
GarbageCollectionGauges.registerMetrics();
|
GarbageCollectionGauges.registerMetrics();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void registerGauge(final String name, final Gauge gauge) {
|
||||||
|
Metrics.gauge(name, gauge, Gauge::getValue);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2013-2020 Signal Messenger, LLC
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.whispersystems.textsecuregcm.metrics;
|
|
||||||
|
|
||||||
|
|
||||||
import com.codahale.metrics.Gauge;
|
|
||||||
import org.whispersystems.textsecuregcm.util.Pair;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public abstract class NetworkGauge implements Gauge<Double> {
|
|
||||||
|
|
||||||
protected Pair<Long, Long> getSentReceived() throws IOException {
|
|
||||||
File proc = new File("/proc/net/dev");
|
|
||||||
BufferedReader reader = new BufferedReader(new FileReader(proc));
|
|
||||||
String header = reader.readLine();
|
|
||||||
String header2 = reader.readLine();
|
|
||||||
|
|
||||||
long bytesSent = 0;
|
|
||||||
long bytesReceived = 0;
|
|
||||||
|
|
||||||
String interfaceStats;
|
|
||||||
|
|
||||||
while ((interfaceStats = reader.readLine()) != null) {
|
|
||||||
String[] stats = interfaceStats.split("\\s+");
|
|
||||||
|
|
||||||
if (!stats[1].equals("lo:")) {
|
|
||||||
bytesReceived += Long.parseLong(stats[2]);
|
|
||||||
bytesSent += Long.parseLong(stats[10]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Pair<>(bytesSent, bytesReceived);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2013-2020 Signal Messenger, LLC
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.whispersystems.textsecuregcm.metrics;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.whispersystems.textsecuregcm.util.Pair;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class NetworkReceivedGauge extends NetworkGauge {
|
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(NetworkReceivedGauge.class);
|
|
||||||
|
|
||||||
private long lastTimestamp;
|
|
||||||
private long lastReceived;
|
|
||||||
|
|
||||||
public NetworkReceivedGauge() {
|
|
||||||
try {
|
|
||||||
this.lastTimestamp = System.currentTimeMillis();
|
|
||||||
this.lastReceived = getSentReceived().second();
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.warn(NetworkReceivedGauge.class.getSimpleName(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Double getValue() {
|
|
||||||
try {
|
|
||||||
long timestamp = System.currentTimeMillis();
|
|
||||||
Pair<Long, Long> sentAndReceived = getSentReceived();
|
|
||||||
double bytesReceived = sentAndReceived.second() - lastReceived;
|
|
||||||
double secondsElapsed = (timestamp - this.lastTimestamp) / 1000;
|
|
||||||
double result = bytesReceived / secondsElapsed;
|
|
||||||
|
|
||||||
this.lastTimestamp = timestamp;
|
|
||||||
this.lastReceived = sentAndReceived.second();
|
|
||||||
|
|
||||||
return result;
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.warn("NetworkReceivedGauge", e);
|
|
||||||
return -1D;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2013-2020 Signal Messenger, LLC
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.whispersystems.textsecuregcm.metrics;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.whispersystems.textsecuregcm.util.Pair;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class NetworkSentGauge extends NetworkGauge {
|
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(NetworkSentGauge.class);
|
|
||||||
|
|
||||||
private long lastTimestamp;
|
|
||||||
private long lastSent;
|
|
||||||
|
|
||||||
public NetworkSentGauge() {
|
|
||||||
try {
|
|
||||||
this.lastTimestamp = System.currentTimeMillis();
|
|
||||||
this.lastSent = getSentReceived().first();
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.warn(NetworkSentGauge.class.getSimpleName(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Double getValue() {
|
|
||||||
try {
|
|
||||||
long timestamp = System.currentTimeMillis();
|
|
||||||
Pair<Long, Long> sentAndReceived = getSentReceived();
|
|
||||||
double bytesTransmitted = sentAndReceived.first() - lastSent;
|
|
||||||
double secondsElapsed = (timestamp - this.lastTimestamp) / 1000;
|
|
||||||
double result = bytesTransmitted / secondsElapsed;
|
|
||||||
|
|
||||||
this.lastSent = sentAndReceived.first();
|
|
||||||
this.lastTimestamp = timestamp;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.warn("NetworkSentGauge", e);
|
|
||||||
return -1D;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,9 +5,7 @@
|
||||||
|
|
||||||
package org.whispersystems.textsecuregcm.metrics;
|
package org.whispersystems.textsecuregcm.metrics;
|
||||||
|
|
||||||
import com.codahale.metrics.Gauge;
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileReader;
|
import java.io.FileReader;
|
||||||
|
@ -16,33 +14,33 @@ import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public class OperatingSystemMemoryGauge implements Gauge<Long> {
|
public class OperatingSystemMemoryGauge implements Gauge {
|
||||||
|
|
||||||
private final String metricName;
|
private final String metricName;
|
||||||
|
|
||||||
private static final File MEMINFO_FILE = new File("/proc/meminfo");
|
private static final File MEMINFO_FILE = new File("/proc/meminfo");
|
||||||
private static final Pattern MEMORY_METRIC_PATTERN = Pattern.compile("^([^:]+):\\s+([0-9]+).*$");
|
private static final Pattern MEMORY_METRIC_PATTERN = Pattern.compile("^([^:]+):\\s+([0-9]+).*$");
|
||||||
|
|
||||||
public OperatingSystemMemoryGauge(final String metricName) {
|
public OperatingSystemMemoryGauge(final String metricName) {
|
||||||
this.metricName = metricName;
|
this.metricName = metricName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getValue() {
|
||||||
|
try (final BufferedReader bufferedReader = new BufferedReader(new FileReader(MEMINFO_FILE))) {
|
||||||
|
return getValue(bufferedReader.lines());
|
||||||
|
} catch (final IOException e) {
|
||||||
|
return 0L;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@VisibleForTesting
|
||||||
public Long getValue() {
|
double getValue(final Stream<String> lines) {
|
||||||
try (final BufferedReader bufferedReader = new BufferedReader(new FileReader(MEMINFO_FILE))) {
|
return lines.map(MEMORY_METRIC_PATTERN::matcher)
|
||||||
return getValue(bufferedReader.lines());
|
.filter(Matcher::matches)
|
||||||
} catch (final IOException e) {
|
.filter(matcher -> this.metricName.equalsIgnoreCase(matcher.group(1)))
|
||||||
return 0L;
|
.map(matcher -> Double.parseDouble(matcher.group(2)))
|
||||||
}
|
.findFirst()
|
||||||
}
|
.orElse(0d);
|
||||||
|
}
|
||||||
@VisibleForTesting
|
|
||||||
long getValue(final Stream<String> lines) {
|
|
||||||
return lines.map(MEMORY_METRIC_PATTERN::matcher)
|
|
||||||
.filter(Matcher::matches)
|
|
||||||
.filter(matcher -> this.metricName.equalsIgnoreCase(matcher.group(1)))
|
|
||||||
.map(matcher -> Long.parseLong(matcher.group(2), 10))
|
|
||||||
.findFirst()
|
|
||||||
.orElse(0L);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,10 +7,6 @@ package org.whispersystems.textsecuregcm.push;
|
||||||
|
|
||||||
import static com.codahale.metrics.MetricRegistry.name;
|
import static com.codahale.metrics.MetricRegistry.name;
|
||||||
|
|
||||||
import com.codahale.metrics.Meter;
|
|
||||||
import com.codahale.metrics.MetricRegistry;
|
|
||||||
import com.codahale.metrics.SharedMetricRegistries;
|
|
||||||
import com.codahale.metrics.Timer;
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import io.dropwizard.lifecycle.Managed;
|
import io.dropwizard.lifecycle.Managed;
|
||||||
import io.lettuce.core.LettuceFutures;
|
import io.lettuce.core.LettuceFutures;
|
||||||
|
@ -21,6 +17,7 @@ import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands;
|
||||||
import io.lettuce.core.cluster.models.partitions.RedisClusterNode;
|
import io.lettuce.core.cluster.models.partitions.RedisClusterNode;
|
||||||
import io.lettuce.core.cluster.pubsub.RedisClusterPubSubAdapter;
|
import io.lettuce.core.cluster.pubsub.RedisClusterPubSubAdapter;
|
||||||
import io.micrometer.core.instrument.Counter;
|
import io.micrometer.core.instrument.Counter;
|
||||||
|
import io.micrometer.core.instrument.Timer;
|
||||||
import io.micrometer.core.instrument.Metrics;
|
import io.micrometer.core.instrument.Metrics;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
@ -41,7 +38,6 @@ import org.whispersystems.textsecuregcm.redis.ClusterLuaScript;
|
||||||
import org.whispersystems.textsecuregcm.redis.FaultTolerantPubSubConnection;
|
import org.whispersystems.textsecuregcm.redis.FaultTolerantPubSubConnection;
|
||||||
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster;
|
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster;
|
||||||
import org.whispersystems.textsecuregcm.storage.Device;
|
import org.whispersystems.textsecuregcm.storage.Device;
|
||||||
import org.whispersystems.textsecuregcm.util.Constants;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The client presence manager keeps track of which clients are actively connected and "present" to receive messages.
|
* The client presence manager keeps track of which clients are actively connected and "present" to receive messages.
|
||||||
|
@ -72,9 +68,9 @@ public class ClientPresenceManager extends RedisClusterPubSubAdapter<String, Str
|
||||||
private final Timer setPresenceTimer;
|
private final Timer setPresenceTimer;
|
||||||
private final Timer clearPresenceTimer;
|
private final Timer clearPresenceTimer;
|
||||||
private final Timer prunePeersTimer;
|
private final Timer prunePeersTimer;
|
||||||
private final Meter pruneClientMeter;
|
private final Counter pruneClientMeter;
|
||||||
private final Meter remoteDisplacementMeter;
|
private final Counter remoteDisplacementMeter;
|
||||||
private final Meter pubSubMessageMeter;
|
private final Counter pubSubMessageMeter;
|
||||||
private final Counter displacementListenerAlreadyRemovedCounter;
|
private final Counter displacementListenerAlreadyRemovedCounter;
|
||||||
|
|
||||||
private static final int PRUNE_PEERS_INTERVAL_SECONDS = (int) Duration.ofSeconds(30).toSeconds();
|
private static final int PRUNE_PEERS_INTERVAL_SECONDS = (int) Duration.ofSeconds(30).toSeconds();
|
||||||
|
@ -96,16 +92,15 @@ public class ClientPresenceManager extends RedisClusterPubSubAdapter<String, Str
|
||||||
this.scheduledExecutorService = scheduledExecutorService;
|
this.scheduledExecutorService = scheduledExecutorService;
|
||||||
this.keyspaceNotificationExecutorService = keyspaceNotificationExecutorService;
|
this.keyspaceNotificationExecutorService = keyspaceNotificationExecutorService;
|
||||||
|
|
||||||
final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME);
|
Metrics.gauge(name(getClass(), "localClientCount"), this, ignored -> displacementListenersByPresenceKey.size());
|
||||||
metricRegistry.gauge(name(getClass(), "localClientCount"), () -> displacementListenersByPresenceKey::size);
|
|
||||||
|
|
||||||
this.checkPresenceTimer = metricRegistry.timer(name(getClass(), "checkPresence"));
|
this.checkPresenceTimer = Metrics.timer(name(getClass(), "checkPresence"));
|
||||||
this.setPresenceTimer = metricRegistry.timer(name(getClass(), "setPresence"));
|
this.setPresenceTimer = Metrics.timer(name(getClass(), "setPresence"));
|
||||||
this.clearPresenceTimer = metricRegistry.timer(name(getClass(), "clearPresence"));
|
this.clearPresenceTimer = Metrics.timer(name(getClass(), "clearPresence"));
|
||||||
this.prunePeersTimer = metricRegistry.timer(name(getClass(), "prunePeers"));
|
this.prunePeersTimer = Metrics.timer(name(getClass(), "prunePeers"));
|
||||||
this.pruneClientMeter = metricRegistry.meter(name(getClass(), "pruneClient"));
|
this.pruneClientMeter = Metrics.counter(name(getClass(), "pruneClient"));
|
||||||
this.remoteDisplacementMeter = metricRegistry.meter(name(getClass(), "remoteDisplacement"));
|
this.remoteDisplacementMeter = Metrics.counter(name(getClass(), "remoteDisplacement"));
|
||||||
this.pubSubMessageMeter = metricRegistry.meter(name(getClass(), "pubSubMessage"));
|
this.pubSubMessageMeter = Metrics.counter(name(getClass(), "pubSubMessage"));
|
||||||
this.displacementListenerAlreadyRemovedCounter = Metrics.counter(
|
this.displacementListenerAlreadyRemovedCounter = Metrics.counter(
|
||||||
name(getClass(), "displacementListenerAlreadyRemoved"));
|
name(getClass(), "displacementListenerAlreadyRemoved"));
|
||||||
}
|
}
|
||||||
|
@ -165,7 +160,7 @@ public class ClientPresenceManager extends RedisClusterPubSubAdapter<String, Str
|
||||||
public void setPresent(final UUID accountUuid, final byte deviceId,
|
public void setPresent(final UUID accountUuid, final byte deviceId,
|
||||||
final DisplacedPresenceListener displacementListener) {
|
final DisplacedPresenceListener displacementListener) {
|
||||||
|
|
||||||
try (final Timer.Context ignored = setPresenceTimer.time()) {
|
setPresenceTimer.record(() -> {
|
||||||
final String presenceKey = getPresenceKey(accountUuid, deviceId);
|
final String presenceKey = getPresenceKey(accountUuid, deviceId);
|
||||||
|
|
||||||
displacePresence(presenceKey, true);
|
displacePresence(presenceKey, true);
|
||||||
|
@ -180,7 +175,7 @@ public class ClientPresenceManager extends RedisClusterPubSubAdapter<String, Str
|
||||||
});
|
});
|
||||||
|
|
||||||
subscribeForRemotePresenceChanges(presenceKey);
|
subscribeForRemotePresenceChanges(presenceKey);
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void renewPresence(final UUID accountUuid, final byte deviceId) {
|
public void renewPresence(final UUID accountUuid, final byte deviceId) {
|
||||||
|
@ -224,10 +219,9 @@ public class ClientPresenceManager extends RedisClusterPubSubAdapter<String, Str
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isPresent(final UUID accountUuid, final byte deviceId) {
|
public boolean isPresent(final UUID accountUuid, final byte deviceId) {
|
||||||
try (final Timer.Context ignored = checkPresenceTimer.time()) {
|
return checkPresenceTimer.record(() ->
|
||||||
return presenceCluster.withCluster(connection ->
|
presenceCluster.withCluster(connection ->
|
||||||
connection.sync().exists(getPresenceKey(accountUuid, deviceId))) == 1;
|
connection.sync().exists(getPresenceKey(accountUuid, deviceId))) == 1);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isLocallyPresent(final UUID accountUuid, final byte deviceId) {
|
public boolean isLocallyPresent(final UUID accountUuid, final byte deviceId) {
|
||||||
|
@ -245,7 +239,7 @@ public class ClientPresenceManager extends RedisClusterPubSubAdapter<String, Str
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean clearPresence(final String presenceKey) {
|
private boolean clearPresence(final String presenceKey) {
|
||||||
try (final Timer.Context ignored = clearPresenceTimer.time()) {
|
return clearPresenceTimer.record(() -> {
|
||||||
displacementListenersByPresenceKey.remove(presenceKey);
|
displacementListenersByPresenceKey.remove(presenceKey);
|
||||||
unsubscribeFromRemotePresenceChanges(presenceKey);
|
unsubscribeFromRemotePresenceChanges(presenceKey);
|
||||||
|
|
||||||
|
@ -253,7 +247,7 @@ public class ClientPresenceManager extends RedisClusterPubSubAdapter<String, Str
|
||||||
presenceCluster.useCluster(connection -> connection.sync().srem(connectedClientSetKey, presenceKey));
|
presenceCluster.useCluster(connection -> connection.sync().srem(connectedClientSetKey, presenceKey));
|
||||||
|
|
||||||
return removed;
|
return removed;
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void subscribeForRemotePresenceChanges(final String presenceKey) {
|
private void subscribeForRemotePresenceChanges(final String presenceKey) {
|
||||||
|
@ -277,7 +271,7 @@ public class ClientPresenceManager extends RedisClusterPubSubAdapter<String, Str
|
||||||
}
|
}
|
||||||
|
|
||||||
void pruneMissingPeers() {
|
void pruneMissingPeers() {
|
||||||
try (final Timer.Context ignored = prunePeersTimer.time()) {
|
prunePeersTimer.record(() -> {
|
||||||
final Set<String> peerIds = presenceCluster.withCluster(
|
final Set<String> peerIds = presenceCluster.withCluster(
|
||||||
connection -> connection.sync().smembers(MANAGER_SET_KEY));
|
connection -> connection.sync().smembers(MANAGER_SET_KEY));
|
||||||
peerIds.remove(managerId);
|
peerIds.remove(managerId);
|
||||||
|
@ -296,7 +290,7 @@ public class ClientPresenceManager extends RedisClusterPubSubAdapter<String, Str
|
||||||
while ((presenceKey = presenceCluster.withCluster(connection -> connection.sync().spop(connectedClientsKey)))
|
while ((presenceKey = presenceCluster.withCluster(connection -> connection.sync().spop(connectedClientsKey)))
|
||||||
!= null) {
|
!= null) {
|
||||||
clearPresenceScript.execute(List.of(presenceKey), List.of(peerId));
|
clearPresenceScript.execute(List.of(presenceKey), List.of(peerId));
|
||||||
pruneClientMeter.mark();
|
pruneClientMeter.increment();
|
||||||
}
|
}
|
||||||
|
|
||||||
presenceCluster.useCluster(connection -> {
|
presenceCluster.useCluster(connection -> {
|
||||||
|
@ -305,12 +299,12 @@ public class ClientPresenceManager extends RedisClusterPubSubAdapter<String, Str
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void message(final RedisClusterNode node, final String channel, final String message) {
|
public void message(final RedisClusterNode node, final String channel, final String message) {
|
||||||
pubSubMessageMeter.mark();
|
pubSubMessageMeter.increment();
|
||||||
|
|
||||||
if (channel.startsWith("__keyspace@0__:presence::{")) {
|
if (channel.startsWith("__keyspace@0__:presence::{")) {
|
||||||
if ("set".equals(message) || "del".equals(message)) {
|
if ("set".equals(message) || "del".equals(message)) {
|
||||||
|
@ -323,7 +317,7 @@ public class ClientPresenceManager extends RedisClusterPubSubAdapter<String, Str
|
||||||
keyspaceNotificationExecutorService.execute(() -> {
|
keyspaceNotificationExecutorService.execute(() -> {
|
||||||
try {
|
try {
|
||||||
displacePresence(channel.substring("__keyspace@0__:".length()), connectedElsewhere);
|
displacePresence(channel.substring("__keyspace@0__:".length()), connectedElsewhere);
|
||||||
remoteDisplacementMeter.mark();
|
remoteDisplacementMeter.increment();
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
log.warn("Error displacing presence", e);
|
log.warn("Error displacing presence", e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,6 @@ package org.whispersystems.textsecuregcm.storage;
|
||||||
import static com.codahale.metrics.MetricRegistry.name;
|
import static com.codahale.metrics.MetricRegistry.name;
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
import com.codahale.metrics.MetricRegistry;
|
|
||||||
import com.codahale.metrics.SharedMetricRegistries;
|
|
||||||
import com.codahale.metrics.Timer;
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import com.fasterxml.jackson.databind.ObjectWriter;
|
import com.fasterxml.jackson.databind.ObjectWriter;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
@ -18,6 +15,7 @@ import com.google.common.base.Preconditions;
|
||||||
import io.lettuce.core.RedisException;
|
import io.lettuce.core.RedisException;
|
||||||
import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands;
|
import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands;
|
||||||
import io.micrometer.core.instrument.Metrics;
|
import io.micrometer.core.instrument.Metrics;
|
||||||
|
import io.micrometer.core.instrument.Timer;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.UncheckedIOException;
|
import java.io.UncheckedIOException;
|
||||||
import java.time.Clock;
|
import java.time.Clock;
|
||||||
|
@ -60,7 +58,6 @@ import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster;
|
||||||
import org.whispersystems.textsecuregcm.redis.RedisOperation;
|
import org.whispersystems.textsecuregcm.redis.RedisOperation;
|
||||||
import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient;
|
import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient;
|
||||||
import org.whispersystems.textsecuregcm.securevaluerecovery.SecureValueRecovery2Client;
|
import org.whispersystems.textsecuregcm.securevaluerecovery.SecureValueRecovery2Client;
|
||||||
import org.whispersystems.textsecuregcm.util.Constants;
|
|
||||||
import org.whispersystems.textsecuregcm.util.DestinationDeviceValidator;
|
import org.whispersystems.textsecuregcm.util.DestinationDeviceValidator;
|
||||||
import org.whispersystems.textsecuregcm.util.ExceptionUtils;
|
import org.whispersystems.textsecuregcm.util.ExceptionUtils;
|
||||||
import org.whispersystems.textsecuregcm.util.Pair;
|
import org.whispersystems.textsecuregcm.util.Pair;
|
||||||
|
@ -72,19 +69,19 @@ import software.amazon.awssdk.services.dynamodb.model.TransactWriteItem;
|
||||||
|
|
||||||
public class AccountsManager {
|
public class AccountsManager {
|
||||||
|
|
||||||
private static final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME);
|
private static final Timer createTimer = Metrics.timer(name(AccountsManager.class, "create"));
|
||||||
private static final Timer createTimer = metricRegistry.timer(name(AccountsManager.class, "create"));
|
private static final Timer updateTimer = Metrics.timer(name(AccountsManager.class, "update"));
|
||||||
private static final Timer updateTimer = metricRegistry.timer(name(AccountsManager.class, "update"));
|
private static final Timer getByNumberTimer = Metrics.timer(name(AccountsManager.class, "getByNumber"));
|
||||||
private static final Timer getByNumberTimer = metricRegistry.timer(name(AccountsManager.class, "getByNumber"));
|
private static final Timer getByUsernameHashTimer = Metrics.timer(name(AccountsManager.class, "getByUsernameHash"));
|
||||||
private static final Timer getByUsernameHashTimer = metricRegistry.timer(name(AccountsManager.class, "getByUsernameHash"));
|
private static final Timer getByUsernameLinkHandleTimer = Metrics.timer(
|
||||||
private static final Timer getByUsernameLinkHandleTimer = metricRegistry.timer(name(AccountsManager.class, "getByUsernameLinkHandle"));
|
name(AccountsManager.class, "getByUsernameLinkHandle"));
|
||||||
private static final Timer getByUuidTimer = metricRegistry.timer(name(AccountsManager.class, "getByUuid"));
|
private static final Timer getByUuidTimer = Metrics.timer(name(AccountsManager.class, "getByUuid"));
|
||||||
private static final Timer deleteTimer = metricRegistry.timer(name(AccountsManager.class, "delete"));
|
private static final Timer deleteTimer = Metrics.timer(name(AccountsManager.class, "delete"));
|
||||||
|
|
||||||
private static final Timer redisSetTimer = metricRegistry.timer(name(AccountsManager.class, "redisSet"));
|
private static final Timer redisSetTimer = Metrics.timer(name(AccountsManager.class, "redisSet"));
|
||||||
private static final Timer redisPniGetTimer = metricRegistry.timer(name(AccountsManager.class, "redisPniGet"));
|
private static final Timer redisPniGetTimer = Metrics.timer(name(AccountsManager.class, "redisPniGet"));
|
||||||
private static final Timer redisUuidGetTimer = metricRegistry.timer(name(AccountsManager.class, "redisUuidGet"));
|
private static final Timer redisUuidGetTimer = Metrics.timer(name(AccountsManager.class, "redisUuidGet"));
|
||||||
private static final Timer redisDeleteTimer = metricRegistry.timer(name(AccountsManager.class, "redisDelete"));
|
private static final Timer redisDeleteTimer = Metrics.timer(name(AccountsManager.class, "redisDelete"));
|
||||||
|
|
||||||
private static final String CREATE_COUNTER_NAME = name(AccountsManager.class, "createCounter");
|
private static final String CREATE_COUNTER_NAME = name(AccountsManager.class, "createCounter");
|
||||||
private static final String DELETE_COUNTER_NAME = name(AccountsManager.class, "deleteCounter");
|
private static final String DELETE_COUNTER_NAME = name(AccountsManager.class, "deleteCounter");
|
||||||
|
@ -172,7 +169,7 @@ public class AccountsManager {
|
||||||
|
|
||||||
final Account account = new Account();
|
final Account account = new Account();
|
||||||
|
|
||||||
try (Timer.Context ignoredTimerContext = createTimer.time()) {
|
return createTimer.record(() -> {
|
||||||
accountLockManager.withLock(List.of(number), () -> {
|
accountLockManager.withLock(List.of(number), () -> {
|
||||||
final Optional<UUID> maybeRecentlyDeletedAccountIdentifier =
|
final Optional<UUID> maybeRecentlyDeletedAccountIdentifier =
|
||||||
accounts.findRecentlyDeletedAccountIdentifier(number);
|
accounts.findRecentlyDeletedAccountIdentifier(number);
|
||||||
|
@ -259,7 +256,7 @@ public class AccountsManager {
|
||||||
}, accountLockExecutor);
|
}, accountLockExecutor);
|
||||||
|
|
||||||
return account;
|
return account;
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<Pair<Account, Device>> addDevice(final Account account, final DeviceSpec deviceSpec) {
|
public CompletableFuture<Pair<Account, Device>> addDevice(final Account account, final DeviceSpec deviceSpec) {
|
||||||
|
@ -689,29 +686,27 @@ public class AccountsManager {
|
||||||
*/
|
*/
|
||||||
private Account update(Account account, Function<Account, Boolean> updater) {
|
private Account update(Account account, Function<Account, Boolean> updater) {
|
||||||
|
|
||||||
final Account updatedAccount;
|
return updateTimer.record(() -> {
|
||||||
|
|
||||||
try (Timer.Context ignored = updateTimer.time()) {
|
|
||||||
|
|
||||||
redisDelete(account);
|
redisDelete(account);
|
||||||
|
|
||||||
final UUID uuid = account.getUuid();
|
final UUID uuid = account.getUuid();
|
||||||
|
|
||||||
updatedAccount = updateWithRetries(account,
|
final Account updatedAccount = updateWithRetries(account,
|
||||||
updater,
|
updater,
|
||||||
accounts::update,
|
accounts::update,
|
||||||
() -> accounts.getByAccountIdentifier(uuid).orElseThrow(),
|
() -> accounts.getByAccountIdentifier(uuid).orElseThrow(),
|
||||||
AccountChangeValidator.GENERAL_CHANGE_VALIDATOR);
|
AccountChangeValidator.GENERAL_CHANGE_VALIDATOR);
|
||||||
|
|
||||||
redisSet(updatedAccount);
|
redisSet(updatedAccount);
|
||||||
}
|
|
||||||
|
|
||||||
return updatedAccount;
|
return updatedAccount;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private CompletableFuture<Account> updateAsync(final Account account, final Function<Account, Boolean> updater) {
|
private CompletableFuture<Account> updateAsync(final Account account, final Function<Account, Boolean> updater) {
|
||||||
|
|
||||||
final Timer.Context timerContext = updateTimer.time();
|
final Timer.Sample timerSample = Timer.start();
|
||||||
|
|
||||||
return redisDeleteAsync(account)
|
return redisDeleteAsync(account)
|
||||||
.thenCompose(ignored -> {
|
.thenCompose(ignored -> {
|
||||||
|
@ -725,7 +720,7 @@ public class AccountsManager {
|
||||||
MAX_UPDATE_ATTEMPTS);
|
MAX_UPDATE_ATTEMPTS);
|
||||||
})
|
})
|
||||||
.thenCompose(updatedAccount -> redisSetAsync(updatedAccount).thenApply(ignored -> updatedAccount))
|
.thenCompose(updatedAccount -> redisSetAsync(updatedAccount).thenApply(ignored -> updatedAccount))
|
||||||
.whenComplete((ignored, throwable) -> timerContext.close());
|
.whenComplete((ignored, throwable) -> timerSample.stop(updateTimer));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Account updateWithRetries(Account account,
|
private Account updateWithRetries(Account account,
|
||||||
|
@ -859,13 +854,13 @@ public class AccountsManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<Account> getByE164(final String number) {
|
public Optional<Account> getByE164(final String number) {
|
||||||
return getByNumberTimer.timeSupplier(() -> accounts.getByE164(number));
|
return getByNumberTimer.record(() -> accounts.getByE164(number));
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<Optional<Account>> getByE164Async(final String number) {
|
public CompletableFuture<Optional<Account>> getByE164Async(final String number) {
|
||||||
final Timer.Context context = getByNumberTimer.time();
|
Timer.Sample sample = Timer.start();
|
||||||
return accounts.getByE164Async(number)
|
return accounts.getByE164Async(number)
|
||||||
.whenComplete((ignoredResult, ignoredThrowable) -> context.close());
|
.whenComplete((ignoredResult, ignoredThrowable) -> sample.stop(getByNumberTimer));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<Account> getByPhoneNumberIdentifier(final UUID pni) {
|
public Optional<Account> getByPhoneNumberIdentifier(final UUID pni) {
|
||||||
|
@ -885,15 +880,15 @@ public class AccountsManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<Optional<Account>> getByUsernameLinkHandle(final UUID usernameLinkHandle) {
|
public CompletableFuture<Optional<Account>> getByUsernameLinkHandle(final UUID usernameLinkHandle) {
|
||||||
final Timer.Context context = getByUsernameLinkHandleTimer.time();
|
final Timer.Sample sample = Timer.start();
|
||||||
return accounts.getByUsernameLinkHandle(usernameLinkHandle)
|
return accounts.getByUsernameLinkHandle(usernameLinkHandle)
|
||||||
.whenComplete((ignoredResult, ignoredThrowable) -> context.close());
|
.whenComplete((ignoredResult, ignoredThrowable) -> sample.stop(getByUsernameLinkHandleTimer));
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<Optional<Account>> getByUsernameHash(final byte[] usernameHash) {
|
public CompletableFuture<Optional<Account>> getByUsernameHash(final byte[] usernameHash) {
|
||||||
final Timer.Context context = getByUsernameHashTimer.time();
|
final Timer.Sample sample = Timer.start();
|
||||||
return accounts.getByUsernameHash(usernameHash)
|
return accounts.getByUsernameHash(usernameHash)
|
||||||
.whenComplete((ignoredResult, ignoredThrowable) -> context.close());
|
.whenComplete((ignoredResult, ignoredThrowable) -> sample.stop(getByUsernameHashTimer));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<Account> getByServiceIdentifier(final ServiceIdentifier serviceIdentifier) {
|
public Optional<Account> getByServiceIdentifier(final ServiceIdentifier serviceIdentifier) {
|
||||||
|
@ -943,11 +938,11 @@ public class AccountsManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<Void> delete(final Account account, final DeletionReason deletionReason) {
|
public CompletableFuture<Void> delete(final Account account, final DeletionReason deletionReason) {
|
||||||
@SuppressWarnings("resource") final Timer.Context timerContext = deleteTimer.time();
|
final Timer.Sample sample = Timer.start();
|
||||||
|
|
||||||
return accountLockManager.withLockAsync(List.of(account.getNumber()), () -> delete(account), accountLockExecutor)
|
return accountLockManager.withLockAsync(List.of(account.getNumber()), () -> delete(account), accountLockExecutor)
|
||||||
.whenComplete((ignored, throwable) -> {
|
.whenComplete((ignored, throwable) -> {
|
||||||
timerContext.close();
|
sample.stop(deleteTimer);
|
||||||
|
|
||||||
if (throwable == null) {
|
if (throwable == null) {
|
||||||
Metrics.counter(DELETE_COUNTER_NAME,
|
Metrics.counter(DELETE_COUNTER_NAME,
|
||||||
|
@ -984,10 +979,6 @@ public class AccountsManager {
|
||||||
clientPresenceManager.disconnectPresence(account.getUuid(), device.getId()))), clientPresenceExecutor);
|
clientPresenceManager.disconnectPresence(account.getUuid(), device.getId()))), clientPresenceExecutor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getUsernameHashAccountMapKey(byte[] usernameHash) {
|
|
||||||
return "UAccountMap::" + Base64.getUrlEncoder().withoutPadding().encodeToString(usernameHash);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getAccountMapKey(String key) {
|
private String getAccountMapKey(String key) {
|
||||||
return "AccountMap::" + key;
|
return "AccountMap::" + key;
|
||||||
}
|
}
|
||||||
|
@ -997,18 +988,21 @@ public class AccountsManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void redisSet(Account account) {
|
private void redisSet(Account account) {
|
||||||
try (Timer.Context ignored = redisSetTimer.time()) {
|
redisSetTimer.record(() -> {
|
||||||
final String accountJson = writeRedisAccountJson(account);
|
try {
|
||||||
|
final String accountJson = writeRedisAccountJson(account);
|
||||||
|
|
||||||
cacheCluster.useCluster(connection -> {
|
cacheCluster.useCluster(connection -> {
|
||||||
final RedisAdvancedClusterCommands<String, String> commands = connection.sync();
|
final RedisAdvancedClusterCommands<String, String> commands = connection.sync();
|
||||||
|
|
||||||
commands.setex(getAccountMapKey(account.getPhoneNumberIdentifier().toString()), CACHE_TTL_SECONDS, account.getUuid().toString());
|
commands.setex(getAccountMapKey(account.getPhoneNumberIdentifier().toString()), CACHE_TTL_SECONDS,
|
||||||
commands.setex(getAccountEntityKey(account.getUuid()), CACHE_TTL_SECONDS, accountJson);
|
account.getUuid().toString());
|
||||||
});
|
commands.setex(getAccountEntityKey(account.getUuid()), CACHE_TTL_SECONDS, accountJson);
|
||||||
} catch (JsonProcessingException e) {
|
});
|
||||||
throw new IllegalStateException(e);
|
} catch (JsonProcessingException e) {
|
||||||
}
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private CompletableFuture<Void> redisSetAsync(final Account account) {
|
private CompletableFuture<Void> redisSetAsync(final Account account) {
|
||||||
|
@ -1033,14 +1027,14 @@ public class AccountsManager {
|
||||||
final Timer overallTimer,
|
final Timer overallTimer,
|
||||||
final Supplier<Optional<Account>> resolveFromRedis,
|
final Supplier<Optional<Account>> resolveFromRedis,
|
||||||
final Supplier<Optional<Account>> resolveFromAccounts) {
|
final Supplier<Optional<Account>> resolveFromAccounts) {
|
||||||
try (final Timer.Context ignored = overallTimer.time()) {
|
return overallTimer.record(() -> {
|
||||||
Optional<Account> account = resolveFromRedis.get();
|
Optional<Account> account = resolveFromRedis.get();
|
||||||
if (account.isEmpty()) {
|
if (account.isEmpty()) {
|
||||||
account = resolveFromAccounts.get();
|
account = resolveFromAccounts.get();
|
||||||
account.ifPresent(this::redisSet);
|
account.ifPresent(this::redisSet);
|
||||||
}
|
}
|
||||||
return account;
|
return account;
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private CompletableFuture<Optional<Account>> checkRedisThenAccountsAsync(
|
private CompletableFuture<Optional<Account>> checkRedisThenAccountsAsync(
|
||||||
|
@ -1048,7 +1042,7 @@ public class AccountsManager {
|
||||||
final Supplier<CompletableFuture<Optional<Account>>> resolveFromRedis,
|
final Supplier<CompletableFuture<Optional<Account>>> resolveFromRedis,
|
||||||
final Supplier<CompletableFuture<Optional<Account>>> resolveFromAccounts) {
|
final Supplier<CompletableFuture<Optional<Account>>> resolveFromAccounts) {
|
||||||
|
|
||||||
@SuppressWarnings("resource") final Timer.Context timerContext = overallTimer.time();
|
final Timer.Sample sample = Timer.start();
|
||||||
|
|
||||||
return resolveFromRedis.get()
|
return resolveFromRedis.get()
|
||||||
.thenCompose(maybeAccountFromRedis -> maybeAccountFromRedis
|
.thenCompose(maybeAccountFromRedis -> maybeAccountFromRedis
|
||||||
|
@ -1057,11 +1051,12 @@ public class AccountsManager {
|
||||||
.thenCompose(maybeAccountFromAccounts -> maybeAccountFromAccounts
|
.thenCompose(maybeAccountFromAccounts -> maybeAccountFromAccounts
|
||||||
.map(account -> redisSetAsync(account).thenApply(ignored -> maybeAccountFromAccounts))
|
.map(account -> redisSetAsync(account).thenApply(ignored -> maybeAccountFromAccounts))
|
||||||
.orElseGet(() -> CompletableFuture.completedFuture(maybeAccountFromAccounts)))))
|
.orElseGet(() -> CompletableFuture.completedFuture(maybeAccountFromAccounts)))))
|
||||||
.whenComplete((ignored, throwable) -> timerContext.close());
|
.whenComplete((ignored, throwable) -> sample.stop(overallTimer));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<Account> redisGetBySecondaryKey(final String secondaryKey, final Timer timer) {
|
private Optional<Account> redisGetBySecondaryKey(final String secondaryKey, final Timer timer) {
|
||||||
try (final Timer.Context ignored = timer.time()) {
|
return timer.record(() -> {
|
||||||
|
try {
|
||||||
return Optional.ofNullable(cacheCluster.withCluster(connection -> connection.sync().get(secondaryKey)))
|
return Optional.ofNullable(cacheCluster.withCluster(connection -> connection.sync().get(secondaryKey)))
|
||||||
.map(UUID::fromString)
|
.map(UUID::fromString)
|
||||||
.flatMap(this::getByAccountIdentifier);
|
.flatMap(this::getByAccountIdentifier);
|
||||||
|
@ -1072,10 +1067,11 @@ public class AccountsManager {
|
||||||
logger.warn("Redis failure", e);
|
logger.warn("Redis failure", e);
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private CompletableFuture<Optional<Account>> redisGetBySecondaryKeyAsync(final String secondaryKey, final Timer timer) {
|
private CompletableFuture<Optional<Account>> redisGetBySecondaryKeyAsync(final String secondaryKey, final Timer timer) {
|
||||||
@SuppressWarnings("resource") final Timer.Context timerContext = timer.time();
|
final Timer.Sample sample = Timer.start();
|
||||||
|
|
||||||
return cacheCluster.withCluster(connection -> connection.async().get(secondaryKey))
|
return cacheCluster.withCluster(connection -> connection.async().get(secondaryKey))
|
||||||
.thenCompose(nullableUuid -> {
|
.thenCompose(nullableUuid -> {
|
||||||
|
@ -1089,19 +1085,21 @@ public class AccountsManager {
|
||||||
logger.warn("Failed to retrieve account from Redis", throwable);
|
logger.warn("Failed to retrieve account from Redis", throwable);
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
})
|
})
|
||||||
.whenComplete((ignored, throwable) -> timerContext.close())
|
.whenComplete((ignored, throwable) -> sample.stop(timer))
|
||||||
.toCompletableFuture();
|
.toCompletableFuture();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<Account> redisGetByAccountIdentifier(UUID uuid) {
|
private Optional<Account> redisGetByAccountIdentifier(UUID uuid) {
|
||||||
try (Timer.Context ignored = redisUuidGetTimer.time()) {
|
return redisUuidGetTimer.record(() -> {
|
||||||
final String json = cacheCluster.withCluster(connection -> connection.sync().get(getAccountEntityKey(uuid)));
|
try {
|
||||||
|
final String json = cacheCluster.withCluster(connection -> connection.sync().get(getAccountEntityKey(uuid)));
|
||||||
|
|
||||||
return parseAccountJson(json, uuid);
|
return parseAccountJson(json, uuid);
|
||||||
} catch (final RedisException e) {
|
} catch (final RedisException e) {
|
||||||
logger.warn("Redis failure", e);
|
logger.warn("Redis failure", e);
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private CompletableFuture<Optional<Account>> redisGetByAccountIdentifierAsync(final UUID uuid) {
|
private CompletableFuture<Optional<Account>> redisGetByAccountIdentifierAsync(final UUID uuid) {
|
||||||
|
@ -1141,17 +1139,14 @@ public class AccountsManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void redisDelete(final Account account) {
|
private void redisDelete(final Account account) {
|
||||||
try (final Timer.Context ignored = redisDeleteTimer.time()) {
|
redisDeleteTimer.record(() ->
|
||||||
cacheCluster.useCluster(connection -> {
|
cacheCluster.useCluster(connection ->
|
||||||
connection.sync().del(
|
connection.sync().del(getAccountMapKey(account.getPhoneNumberIdentifier().toString()),
|
||||||
getAccountMapKey(account.getPhoneNumberIdentifier().toString()),
|
getAccountEntityKey(account.getUuid()))));
|
||||||
getAccountEntityKey(account.getUuid()));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private CompletableFuture<Void> redisDeleteAsync(final Account account) {
|
private CompletableFuture<Void> redisDeleteAsync(final Account account) {
|
||||||
@SuppressWarnings("resource") final Timer.Context timerContext = redisDeleteTimer.time();
|
final Timer.Sample sample = Timer.start();
|
||||||
|
|
||||||
final String[] keysToDelete = new String[]{
|
final String[] keysToDelete = new String[]{
|
||||||
getAccountMapKey(account.getPhoneNumberIdentifier().toString()),
|
getAccountMapKey(account.getPhoneNumberIdentifier().toString()),
|
||||||
|
@ -1160,7 +1155,7 @@ public class AccountsManager {
|
||||||
|
|
||||||
return cacheCluster.withCluster(connection -> connection.async().del(keysToDelete))
|
return cacheCluster.withCluster(connection -> connection.async().del(keysToDelete))
|
||||||
.toCompletableFuture()
|
.toCompletableFuture()
|
||||||
.whenComplete((ignoredResult, ignoredException) -> timerContext.close())
|
.whenComplete((ignoredResult, ignoredException) -> sample.stop(redisDeleteTimer))
|
||||||
.thenRun(Util.NOOP);
|
.thenRun(Util.NOOP);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,17 +5,14 @@
|
||||||
|
|
||||||
package org.whispersystems.textsecuregcm.storage;
|
package org.whispersystems.textsecuregcm.storage;
|
||||||
|
|
||||||
import static com.codahale.metrics.MetricRegistry.name;
|
import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name;
|
||||||
import static io.micrometer.core.instrument.Metrics.counter;
|
|
||||||
|
|
||||||
import com.codahale.metrics.Histogram;
|
|
||||||
import com.codahale.metrics.Meter;
|
|
||||||
import com.codahale.metrics.MetricRegistry;
|
|
||||||
import com.codahale.metrics.SharedMetricRegistries;
|
|
||||||
import com.codahale.metrics.Timer;
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import io.dropwizard.lifecycle.Managed;
|
import io.dropwizard.lifecycle.Managed;
|
||||||
import io.micrometer.core.instrument.Counter;
|
import io.micrometer.core.instrument.Counter;
|
||||||
|
import io.micrometer.core.instrument.DistributionSummary;
|
||||||
|
import io.micrometer.core.instrument.Metrics;
|
||||||
|
import io.micrometer.core.instrument.Timer;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
@ -30,7 +27,6 @@ import org.slf4j.LoggerFactory;
|
||||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
||||||
import org.whispersystems.textsecuregcm.entities.MessageProtos;
|
import org.whispersystems.textsecuregcm.entities.MessageProtos;
|
||||||
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
|
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
|
||||||
import org.whispersystems.textsecuregcm.util.Constants;
|
|
||||||
import org.whispersystems.textsecuregcm.util.Util;
|
import org.whispersystems.textsecuregcm.util.Util;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.util.function.Tuple2;
|
import reactor.util.function.Tuple2;
|
||||||
|
@ -47,18 +43,24 @@ public class MessagePersister implements Managed {
|
||||||
|
|
||||||
private final Duration persistDelay;
|
private final Duration persistDelay;
|
||||||
|
|
||||||
private final boolean dedicatedProcess;
|
|
||||||
private final Thread[] workerThreads;
|
private final Thread[] workerThreads;
|
||||||
private volatile boolean running;
|
private volatile boolean running;
|
||||||
|
|
||||||
private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME);
|
private final Timer getQueuesTimer = Metrics.timer(name(MessagePersister.class, "getQueues"));
|
||||||
private final Timer getQueuesTimer = metricRegistry.timer(name(MessagePersister.class, "getQueues"));
|
private final Timer persistQueueTimer = Metrics.timer(name(MessagePersister.class, "persistQueue"));
|
||||||
private final Timer persistQueueTimer = metricRegistry.timer(name(MessagePersister.class, "persistQueue"));
|
private final Counter persistQueueExceptionMeter = Metrics.counter(
|
||||||
private final Meter persistQueueExceptionMeter = metricRegistry.meter(
|
|
||||||
name(MessagePersister.class, "persistQueueException"));
|
name(MessagePersister.class, "persistQueueException"));
|
||||||
private final Counter oversizedQueueCounter = counter(name(MessagePersister.class, "persistQueueOversized"));
|
private final Counter oversizedQueueCounter = Metrics.counter(name(MessagePersister.class, "persistQueueOversized"));
|
||||||
private final Histogram queueCountHistogram = metricRegistry.histogram(name(MessagePersister.class, "queueCount"));
|
private final DistributionSummary queueCountDistributionSummery = DistributionSummary.builder(
|
||||||
private final Histogram queueSizeHistogram = metricRegistry.histogram(name(MessagePersister.class, "queueSize"));
|
name(MessagePersister.class, "queueCount"))
|
||||||
|
.publishPercentiles(0.5, 0.75, 0.95, 0.99, 0.999)
|
||||||
|
.distributionStatisticExpiry(Duration.ofMinutes(10))
|
||||||
|
.register(Metrics.globalRegistry);
|
||||||
|
private final DistributionSummary queueSizeDistributionSummery = DistributionSummary.builder(
|
||||||
|
name(MessagePersister.class, "queueSize"))
|
||||||
|
.publishPercentiles(0.5, 0.75, 0.95, 0.99, 0.999)
|
||||||
|
.distributionStatisticExpiry(Duration.ofMinutes(10))
|
||||||
|
.register(Metrics.globalRegistry);
|
||||||
private final ExecutorService executor;
|
private final ExecutorService executor;
|
||||||
|
|
||||||
static final int QUEUE_BATCH_LIMIT = 100;
|
static final int QUEUE_BATCH_LIMIT = 100;
|
||||||
|
@ -86,7 +88,6 @@ public class MessagePersister implements Managed {
|
||||||
this.keysManager = keysManager;
|
this.keysManager = keysManager;
|
||||||
this.persistDelay = persistDelay;
|
this.persistDelay = persistDelay;
|
||||||
this.workerThreads = new Thread[dedicatedProcessWorkerThreadCount];
|
this.workerThreads = new Thread[dedicatedProcessWorkerThreadCount];
|
||||||
this.dedicatedProcess = true;
|
|
||||||
this.executor = executor;
|
this.executor = executor;
|
||||||
|
|
||||||
for (int i = 0; i < workerThreads.length; i++) {
|
for (int i = 0; i < workerThreads.length; i++) {
|
||||||
|
@ -96,7 +97,7 @@ public class MessagePersister implements Managed {
|
||||||
.isPersistenceEnabled()) {
|
.isPersistenceEnabled()) {
|
||||||
try {
|
try {
|
||||||
final int queuesPersisted = persistNextQueues(Instant.now());
|
final int queuesPersisted = persistNextQueues(Instant.now());
|
||||||
queueCountHistogram.update(queuesPersisted);
|
queueCountDistributionSummery.record(queuesPersisted);
|
||||||
|
|
||||||
if (queuesPersisted == 0) {
|
if (queuesPersisted == 0) {
|
||||||
Util.sleep(100);
|
Util.sleep(100);
|
||||||
|
@ -148,9 +149,8 @@ public class MessagePersister implements Managed {
|
||||||
int queuesPersisted = 0;
|
int queuesPersisted = 0;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try (final Timer.Context ignored = getQueuesTimer.time()) {
|
queuesToPersist = getQueuesTimer.record(
|
||||||
queuesToPersist = messagesCache.getQueuesToPersist(slot, currentTime.minus(persistDelay), QUEUE_BATCH_LIMIT);
|
() -> messagesCache.getQueuesToPersist(slot, currentTime.minus(persistDelay), QUEUE_BATCH_LIMIT));
|
||||||
}
|
|
||||||
|
|
||||||
for (final String queue : queuesToPersist) {
|
for (final String queue : queuesToPersist) {
|
||||||
final UUID accountUuid = MessagesCache.getAccountUuidFromQueueName(queue);
|
final UUID accountUuid = MessagesCache.getAccountUuidFromQueueName(queue);
|
||||||
|
@ -164,7 +164,7 @@ public class MessagePersister implements Managed {
|
||||||
try {
|
try {
|
||||||
persistQueue(maybeAccount.get(), deviceId);
|
persistQueue(maybeAccount.get(), deviceId);
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
persistQueueExceptionMeter.mark();
|
persistQueueExceptionMeter.increment();
|
||||||
logger.warn("Failed to persist queue {}::{}; will schedule for retry", accountUuid, deviceId, e);
|
logger.warn("Failed to persist queue {}::{}; will schedule for retry", accountUuid, deviceId, e);
|
||||||
|
|
||||||
messagesCache.addQueueToPersist(accountUuid, deviceId);
|
messagesCache.addQueueToPersist(accountUuid, deviceId);
|
||||||
|
@ -182,41 +182,44 @@ public class MessagePersister implements Managed {
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
void persistQueue(final Account account, final byte deviceId) throws MessagePersistenceException {
|
void persistQueue(final Account account, final byte deviceId) throws MessagePersistenceException {
|
||||||
final UUID accountUuid = account.getUuid();
|
final UUID accountUuid = account.getUuid();
|
||||||
try (final Timer.Context ignored = persistQueueTimer.time()) {
|
|
||||||
messagesCache.lockQueueForPersistence(accountUuid, deviceId);
|
|
||||||
|
|
||||||
try {
|
final Timer.Sample sample = Timer.start();
|
||||||
int messageCount = 0;
|
|
||||||
List<MessageProtos.Envelope> messages;
|
|
||||||
|
|
||||||
int consecutiveEmptyCacheRemovals = 0;
|
messagesCache.lockQueueForPersistence(accountUuid, deviceId);
|
||||||
|
|
||||||
do {
|
try {
|
||||||
messages = messagesCache.getMessagesToPersist(accountUuid, deviceId, MESSAGE_BATCH_LIMIT);
|
int messageCount = 0;
|
||||||
|
List<MessageProtos.Envelope> messages;
|
||||||
|
|
||||||
int messagesRemovedFromCache = messagesManager.persistMessages(accountUuid, deviceId, messages);
|
int consecutiveEmptyCacheRemovals = 0;
|
||||||
messageCount += messages.size();
|
|
||||||
|
|
||||||
if (messagesRemovedFromCache == 0) {
|
do {
|
||||||
consecutiveEmptyCacheRemovals += 1;
|
messages = messagesCache.getMessagesToPersist(accountUuid, deviceId, MESSAGE_BATCH_LIMIT);
|
||||||
} else {
|
|
||||||
consecutiveEmptyCacheRemovals = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (consecutiveEmptyCacheRemovals > CONSECUTIVE_EMPTY_CACHE_REMOVAL_LIMIT) {
|
int messagesRemovedFromCache = messagesManager.persistMessages(accountUuid, deviceId, messages);
|
||||||
throw new MessagePersistenceException("persistence failure loop detected");
|
messageCount += messages.size();
|
||||||
}
|
|
||||||
|
|
||||||
} while (!messages.isEmpty());
|
if (messagesRemovedFromCache == 0) {
|
||||||
|
consecutiveEmptyCacheRemovals += 1;
|
||||||
|
} else {
|
||||||
|
consecutiveEmptyCacheRemovals = 0;
|
||||||
|
}
|
||||||
|
|
||||||
queueSizeHistogram.update(messageCount);
|
if (consecutiveEmptyCacheRemovals > CONSECUTIVE_EMPTY_CACHE_REMOVAL_LIMIT) {
|
||||||
} catch (ItemCollectionSizeLimitExceededException e) {
|
throw new MessagePersistenceException("persistence failure loop detected");
|
||||||
oversizedQueueCounter.increment();
|
}
|
||||||
unlinkLeastActiveDevice(account, deviceId); // this will either do a deferred reschedule for retry or throw
|
|
||||||
} finally {
|
} while (!messages.isEmpty());
|
||||||
messagesCache.unlockQueueForPersistence(accountUuid, deviceId);
|
|
||||||
}
|
queueSizeDistributionSummery.record(messageCount);
|
||||||
|
} catch (ItemCollectionSizeLimitExceededException e) {
|
||||||
|
oversizedQueueCounter.increment();
|
||||||
|
unlinkLeastActiveDevice(account, deviceId); // this will either do a deferred reschedule for retry or throw
|
||||||
|
} finally {
|
||||||
|
messagesCache.unlockQueueForPersistence(accountUuid, deviceId);
|
||||||
|
sample.stop(persistQueueTimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
|
|
|
@ -4,11 +4,9 @@
|
||||||
*/
|
*/
|
||||||
package org.whispersystems.textsecuregcm.storage;
|
package org.whispersystems.textsecuregcm.storage;
|
||||||
|
|
||||||
import static com.codahale.metrics.MetricRegistry.name;
|
import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name;
|
||||||
|
|
||||||
import com.codahale.metrics.Meter;
|
import io.micrometer.core.instrument.Counter;
|
||||||
import com.codahale.metrics.MetricRegistry;
|
|
||||||
import com.codahale.metrics.SharedMetricRegistries;
|
|
||||||
import io.micrometer.core.instrument.Metrics;
|
import io.micrometer.core.instrument.Metrics;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
@ -24,8 +22,6 @@ import org.reactivestreams.Publisher;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope;
|
import org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope;
|
||||||
import org.whispersystems.textsecuregcm.metrics.MetricsUtil;
|
|
||||||
import org.whispersystems.textsecuregcm.util.Constants;
|
|
||||||
import org.whispersystems.textsecuregcm.util.Pair;
|
import org.whispersystems.textsecuregcm.util.Pair;
|
||||||
import reactor.core.observability.micrometer.Micrometer;
|
import reactor.core.observability.micrometer.Micrometer;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
|
@ -34,15 +30,12 @@ import reactor.core.publisher.Mono;
|
||||||
public class MessagesManager {
|
public class MessagesManager {
|
||||||
|
|
||||||
private static final int RESULT_SET_CHUNK_SIZE = 100;
|
private static final int RESULT_SET_CHUNK_SIZE = 100;
|
||||||
final String GET_MESSAGES_FOR_DEVICE_FLUX_NAME = MetricsUtil.name(MessagesManager.class, "getMessagesForDevice");
|
final String GET_MESSAGES_FOR_DEVICE_FLUX_NAME = name(MessagesManager.class, "getMessagesForDevice");
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(MessagesManager.class);
|
private static final Logger logger = LoggerFactory.getLogger(MessagesManager.class);
|
||||||
|
|
||||||
private static final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME);
|
private static final Counter PERSIST_MESSAGE_COUNTER = Metrics.counter(
|
||||||
private static final Meter cacheHitByGuidMeter = metricRegistry.meter(name(MessagesManager.class, "cacheHitByGuid"));
|
name(MessagesManager.class, "persistMessage"));
|
||||||
private static final Meter cacheMissByGuidMeter = metricRegistry.meter(
|
|
||||||
name(MessagesManager.class, "cacheMissByGuid"));
|
|
||||||
private static final Meter persistMessageMeter = metricRegistry.meter(name(MessagesManager.class, "persistMessage"));
|
|
||||||
|
|
||||||
private final MessagesDynamoDb messagesDynamoDb;
|
private final MessagesDynamoDb messagesDynamoDb;
|
||||||
private final MessagesCache messagesCache;
|
private final MessagesCache messagesCache;
|
||||||
|
@ -124,12 +117,9 @@ public class MessagesManager {
|
||||||
.thenComposeAsync(removed -> {
|
.thenComposeAsync(removed -> {
|
||||||
|
|
||||||
if (removed.isPresent()) {
|
if (removed.isPresent()) {
|
||||||
cacheHitByGuidMeter.mark();
|
|
||||||
return CompletableFuture.completedFuture(removed);
|
return CompletableFuture.completedFuture(removed);
|
||||||
}
|
}
|
||||||
|
|
||||||
cacheMissByGuidMeter.mark();
|
|
||||||
|
|
||||||
if (serverTimestamp == null) {
|
if (serverTimestamp == null) {
|
||||||
return messagesDynamoDb.deleteMessageByDestinationAndGuid(destinationUuid, guid);
|
return messagesDynamoDb.deleteMessageByDestinationAndGuid(destinationUuid, guid);
|
||||||
} else {
|
} else {
|
||||||
|
@ -159,7 +149,7 @@ public class MessagesManager {
|
||||||
try {
|
try {
|
||||||
messagesRemovedFromCache = messagesCache.remove(destinationUuid, destinationDeviceId, messageGuids)
|
messagesRemovedFromCache = messagesCache.remove(destinationUuid, destinationDeviceId, messageGuids)
|
||||||
.get(30, TimeUnit.SECONDS).size();
|
.get(30, TimeUnit.SECONDS).size();
|
||||||
persistMessageMeter.mark(nonEphemeralMessages.size());
|
PERSIST_MESSAGE_COUNTER.increment(nonEphemeralMessages.size());
|
||||||
|
|
||||||
} catch (InterruptedException | ExecutionException | TimeoutException e) {
|
} catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||||
logger.warn("Failed to remove messages from cache", e);
|
logger.warn("Failed to remove messages from cache", e);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2013-2022 Signal Messenger, LLC
|
* Copyright 2013 Signal Messenger, LLC
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -7,11 +7,9 @@ package org.whispersystems.textsecuregcm.websocket;
|
||||||
|
|
||||||
import static com.codahale.metrics.MetricRegistry.name;
|
import static com.codahale.metrics.MetricRegistry.name;
|
||||||
|
|
||||||
import com.codahale.metrics.Histogram;
|
|
||||||
import com.codahale.metrics.Meter;
|
|
||||||
import com.codahale.metrics.MetricRegistry;
|
|
||||||
import com.codahale.metrics.SharedMetricRegistries;
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import io.micrometer.core.instrument.Counter;
|
||||||
|
import io.micrometer.core.instrument.DistributionSummary;
|
||||||
import io.micrometer.core.instrument.Metrics;
|
import io.micrometer.core.instrument.Metrics;
|
||||||
import io.micrometer.core.instrument.Tag;
|
import io.micrometer.core.instrument.Tag;
|
||||||
import io.micrometer.core.instrument.Tags;
|
import io.micrometer.core.instrument.Tags;
|
||||||
|
@ -50,7 +48,6 @@ import org.whispersystems.textsecuregcm.storage.ClientReleaseManager;
|
||||||
import org.whispersystems.textsecuregcm.storage.Device;
|
import org.whispersystems.textsecuregcm.storage.Device;
|
||||||
import org.whispersystems.textsecuregcm.storage.MessageAvailabilityListener;
|
import org.whispersystems.textsecuregcm.storage.MessageAvailabilityListener;
|
||||||
import org.whispersystems.textsecuregcm.storage.MessagesManager;
|
import org.whispersystems.textsecuregcm.storage.MessagesManager;
|
||||||
import org.whispersystems.textsecuregcm.util.Constants;
|
|
||||||
import org.whispersystems.textsecuregcm.util.HeaderUtils;
|
import org.whispersystems.textsecuregcm.util.HeaderUtils;
|
||||||
import org.whispersystems.websocket.WebSocketClient;
|
import org.whispersystems.websocket.WebSocketClient;
|
||||||
import org.whispersystems.websocket.WebSocketResourceProvider;
|
import org.whispersystems.websocket.WebSocketResourceProvider;
|
||||||
|
@ -63,18 +60,17 @@ import reactor.core.scheduler.Scheduler;
|
||||||
|
|
||||||
public class WebSocketConnection implements MessageAvailabilityListener, DisplacedPresenceListener {
|
public class WebSocketConnection implements MessageAvailabilityListener, DisplacedPresenceListener {
|
||||||
|
|
||||||
private static final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME);
|
private static final DistributionSummary messageTime = Metrics.summary(
|
||||||
private static final Histogram messageTime = metricRegistry.histogram(
|
name(MessageController.class, "messageDeliveryDuration"));
|
||||||
name(MessageController.class, "message_delivery_duration"));
|
private static final DistributionSummary primaryDeviceMessageTime = Metrics.summary(
|
||||||
private static final Histogram primaryDeviceMessageTime = metricRegistry.histogram(
|
name(MessageController.class, "primaryDeviceMessageDeliveryDuration"));
|
||||||
name(MessageController.class, "primary_device_message_delivery_duration"));
|
private static final Counter sendMessageCounter = Metrics.counter(name(WebSocketConnection.class, "sendMessage"));
|
||||||
private static final Meter sendMessageMeter = metricRegistry.meter(name(WebSocketConnection.class, "send_message"));
|
private static final Counter messageAvailableCounter = Metrics.counter(
|
||||||
private static final Meter messageAvailableMeter = metricRegistry.meter(
|
|
||||||
name(WebSocketConnection.class, "messagesAvailable"));
|
name(WebSocketConnection.class, "messagesAvailable"));
|
||||||
private static final Meter messagesPersistedMeter = metricRegistry.meter(
|
private static final Counter messagesPersistedCounter = Metrics.counter(
|
||||||
name(WebSocketConnection.class, "messagesPersisted"));
|
name(WebSocketConnection.class, "messagesPersisted"));
|
||||||
private static final Meter bytesSentMeter = metricRegistry.meter(name(WebSocketConnection.class, "bytes_sent"));
|
private static final Counter bytesSentCounter = Metrics.counter(name(WebSocketConnection.class, "bytesSent"));
|
||||||
private static final Meter sendFailuresMeter = metricRegistry.meter(name(WebSocketConnection.class, "send_failures"));
|
private static final Counter sendFailuresCounter = Metrics.counter(name(WebSocketConnection.class, "sendFailures"));
|
||||||
|
|
||||||
private static final String INITIAL_QUEUE_LENGTH_DISTRIBUTION_NAME = name(WebSocketConnection.class,
|
private static final String INITIAL_QUEUE_LENGTH_DISTRIBUTION_NAME = name(WebSocketConnection.class,
|
||||||
"initialQueueLength");
|
"initialQueueLength");
|
||||||
|
@ -209,9 +205,9 @@ public class WebSocketConnection implements MessageAvailabilityListener, Displac
|
||||||
// clear ephemeral field from the envelope
|
// clear ephemeral field from the envelope
|
||||||
final Optional<byte[]> body = Optional.ofNullable(message.toBuilder().clearEphemeral().build().toByteArray());
|
final Optional<byte[]> body = Optional.ofNullable(message.toBuilder().clearEphemeral().build().toByteArray());
|
||||||
|
|
||||||
sendMessageMeter.mark();
|
sendMessageCounter.increment();
|
||||||
sentMessageCounter.increment();
|
sentMessageCounter.increment();
|
||||||
bytesSentMeter.mark(body.map(bytes -> bytes.length).orElse(0));
|
bytesSentCounter.increment(body.map(bytes -> bytes.length).orElse(0));
|
||||||
MessageMetrics.measureAccountEnvelopeUuidMismatches(auth.getAccount(), message);
|
MessageMetrics.measureAccountEnvelopeUuidMismatches(auth.getAccount(), message);
|
||||||
|
|
||||||
// X-Signal-Key: false must be sent until Android stops assuming it missing means true
|
// X-Signal-Key: false must be sent until Android stops assuming it missing means true
|
||||||
|
@ -219,7 +215,7 @@ public class WebSocketConnection implements MessageAvailabilityListener, Displac
|
||||||
List.of(HeaderUtils.X_SIGNAL_KEY + ": false", HeaderUtils.getTimestampHeader()), body)
|
List.of(HeaderUtils.X_SIGNAL_KEY + ": false", HeaderUtils.getTimestampHeader()), body)
|
||||||
.whenComplete((ignored, throwable) -> {
|
.whenComplete((ignored, throwable) -> {
|
||||||
if (throwable != null) {
|
if (throwable != null) {
|
||||||
sendFailuresMeter.mark();
|
sendFailuresCounter.increment();
|
||||||
} else {
|
} else {
|
||||||
MessageMetrics.measureOutgoingMessageLatency(message.getServerTimestamp(), "websocket", client.getUserAgent(), clientReleaseManager);
|
MessageMetrics.measureOutgoingMessageLatency(message.getServerTimestamp(), "websocket", client.getUserAgent(), clientReleaseManager);
|
||||||
}
|
}
|
||||||
|
@ -258,9 +254,9 @@ public class WebSocketConnection implements MessageAvailabilityListener, Displac
|
||||||
|
|
||||||
public static void recordMessageDeliveryDuration(long timestamp, Device messageDestinationDevice) {
|
public static void recordMessageDeliveryDuration(long timestamp, Device messageDestinationDevice) {
|
||||||
final long messageDeliveryDuration = System.currentTimeMillis() - timestamp;
|
final long messageDeliveryDuration = System.currentTimeMillis() - timestamp;
|
||||||
messageTime.update(messageDeliveryDuration);
|
messageTime.record(messageDeliveryDuration);
|
||||||
if (messageDestinationDevice.isPrimary()) {
|
if (messageDestinationDevice.isPrimary()) {
|
||||||
primaryDeviceMessageTime.update(messageDeliveryDuration);
|
primaryDeviceMessageTime.record(messageDeliveryDuration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -429,7 +425,7 @@ public class WebSocketConnection implements MessageAvailabilityListener, Displac
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
messageAvailableMeter.mark();
|
messageAvailableCounter.increment();
|
||||||
|
|
||||||
storedMessageState.compareAndSet(StoredMessageState.EMPTY, StoredMessageState.CACHED_NEW_MESSAGES_AVAILABLE);
|
storedMessageState.compareAndSet(StoredMessageState.EMPTY, StoredMessageState.CACHED_NEW_MESSAGES_AVAILABLE);
|
||||||
|
|
||||||
|
@ -445,7 +441,7 @@ public class WebSocketConnection implements MessageAvailabilityListener, Displac
|
||||||
Metrics.counter(CLIENT_CLOSED_MESSAGE_AVAILABLE_COUNTER_NAME).increment();
|
Metrics.counter(CLIENT_CLOSED_MESSAGE_AVAILABLE_COUNTER_NAME).increment();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
messagesPersistedMeter.mark();
|
messagesPersistedCounter.increment();
|
||||||
|
|
||||||
storedMessageState.set(StoredMessageState.PERSISTED_NEW_MESSAGES_AVAILABLE);
|
storedMessageState.set(StoredMessageState.PERSISTED_NEW_MESSAGES_AVAILABLE);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue