All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.rabbitmq.stream.perf.Utils Maven / Gradle / Ivy

// Copyright (c) 2020-2023 VMware, Inc. or its affiliates.  All rights reserved.
//
// This software, the RabbitMQ Stream Java client library, is dual-licensed under the
// Mozilla Public License 2.0 ("MPL"), and the Apache License version 2 ("ASL").
// For the MPL, please see LICENSE-MPL-RabbitMQ. For the ASL,
// please see LICENSE-APACHE2.
//
// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,
// either express or implied. See the LICENSE file for specific language governing
// rights and limitations of this software.
//
// If you have any questions regarding licensing, please contact us at
// [email protected].
package com.rabbitmq.stream.perf;

import static java.time.Duration.ofSeconds;

import com.codahale.metrics.MetricRegistry;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.SocketConfigurator;
import com.rabbitmq.client.SocketConfigurators;
import com.rabbitmq.stream.BackOffDelayPolicy;
import com.rabbitmq.stream.ByteCapacity;
import com.rabbitmq.stream.OffsetSpecification;
import com.rabbitmq.stream.StreamCreator.LeaderLocator;
import com.rabbitmq.stream.compression.Compression;
import com.rabbitmq.stream.metrics.MicrometerMetricsCollector;
import com.sun.management.OperatingSystemMXBean;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.DistributionSummary;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.dropwizard.DropwizardConfig;
import io.micrometer.core.instrument.dropwizard.DropwizardMeterRegistry;
import io.micrometer.core.instrument.util.HierarchicalNameMapper;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.time.Duration;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.TemporalAccessor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.LongSupplier;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SNIHostName;
import javax.net.ssl.SNIServerName;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.ITypeConverter;
import picocli.CommandLine.Model.CommandSpec;
import picocli.CommandLine.Model.OptionSpec;
import picocli.CommandLine.Option;
import picocli.CommandLine.TypeConversionException;

class Utils {

  static final X509TrustManager TRUST_EVERYTHING_TRUST_MANAGER = new TrustEverythingTrustManager();
  static final Function OPTION_TO_ENVIRONMENT_VARIABLE =
      option -> {
        if (option.startsWith("--")) {
          return option.replace("--", "").replace('-', '_').toUpperCase(Locale.ENGLISH);
        } else if (option.startsWith("-")) {
          return option.substring(1).replace('-', '_').toUpperCase(Locale.ENGLISH);
        } else {
          return option.replace('-', '_').toUpperCase(Locale.ENGLISH);
        }
      };
  static final Function ENVIRONMENT_VARIABLE_PREFIX =
      name -> {
        String prefix = System.getenv("RABBITMQ_STREAM_PERF_TEST_ENV_PREFIX");
        if (prefix == null || prefix.trim().isEmpty()) {
          return name;
        }
        if (prefix.endsWith("_")) {
          return prefix + name;
        } else {
          return prefix + "_" + name;
        }
      };
  static final Function ENVIRONMENT_VARIABLE_LOOKUP = name -> System.getenv(name);
  private static final LongSupplier TOTAL_MEMORY_SIZE_SUPPLIER;
  private static final Logger LOGGER = LoggerFactory.getLogger(Utils.class);
  private static final String RANGE_SEPARATOR_1 = "-";
  private static final String RANGE_SEPARATOR_2 = "..";

  // this trick avoids a deprecation warning when compiling on Java 14+
  static {
    Method method;
    try {
      // Java 14+
      method = OperatingSystemMXBean.class.getDeclaredMethod("getTotalMemorySize");
    } catch (NoSuchMethodException nsme) {
      try {
        method = OperatingSystemMXBean.class.getDeclaredMethod("getTotalPhysicalMemorySize");
      } catch (Exception e) {
        throw new RuntimeException("Error while computing method to get total memory size");
      }
    }
    Method m = method;
    TOTAL_MEMORY_SIZE_SUPPLIER =
        () -> {
          OperatingSystemMXBean os =
              (OperatingSystemMXBean)
                  java.lang.management.ManagementFactory.getOperatingSystemMXBean();
          try {
            return (long) m.invoke(os);
          } catch (Exception e) {
            throw new RuntimeException("Could not retrieve total memory size", e);
          }
        };
  }

  static void writeLong(byte[] array, long value) {
    // from Guava Longs
    for (int i = 7; i >= 0; i--) {
      array[i] = (byte) (value & 0xffL);
      value >>= 8;
    }
  }

  static long readLong(byte[] array) {
    // from Guava Longs
    return (array[0] & 0xFFL) << 56
        | (array[1] & 0xFFL) << 48
        | (array[2] & 0xFFL) << 40
        | (array[3] & 0xFFL) << 32
        | (array[4] & 0xFFL) << 24
        | (array[5] & 0xFFL) << 16
        | (array[6] & 0xFFL) << 8
        | (array[7] & 0xFFL);
  }

  static List streams(String range, List streams) {
    if (range.contains(RANGE_SEPARATOR_2)) {
      range = range.replace(RANGE_SEPARATOR_2, RANGE_SEPARATOR_1);
    }
    int from, to;
    if (range.contains(RANGE_SEPARATOR_1)) {
      String[] fromTo = range.split(RANGE_SEPARATOR_1);
      from = Integer.parseInt(fromTo[0]);
      to = Integer.parseInt(fromTo[1]) + 1;
    } else {
      int count = Integer.parseInt(range);
      from = 1;
      to = count + 1;
    }
    if (from == 1 && to == 2) {
      return streams;
    } else {
      if (streams.size() != 1) {
        throw new IllegalArgumentException("Enter only 1 stream when --stream-count is specified");
      }
      String format = streams.get(0);
      String streamFormat;
      if (!format.contains("%")) {
        int digits = String.valueOf(to - 1).length();
        streamFormat = format + "-%0" + digits + "d";
      } else {
        streamFormat = format;
      }
      return IntStream.range(from, to)
          .mapToObj(i -> String.format(streamFormat, i))
          .collect(Collectors.toList());
    }
  }

  static String formatByte(double bytes) {
    // based on
    // https://stackoverflow.com/questions/3758606/how-can-i-convert-byte-size-into-a-human-readable-format-in-java
    if (-1000 < bytes && bytes < 1000) {
      return String.valueOf(bytes);
    }
    CharacterIterator ci = new StringCharacterIterator("kMGTPE");
    while (bytes <= -999_950 || bytes >= 999_950) {
      bytes /= 1000;
      ci.next();
    }
    return String.format("%.1f %cB", bytes / 1000.0, ci.current());
  }

  static long physicalMemory() {
    try {
      return TOTAL_MEMORY_SIZE_SUPPLIER.getAsLong();
    } catch (Throwable e) {
      // we can get NoClassDefFoundError, so we catch from Throwable and below
      LOGGER.warn("Could not get physical memory", e);
      return 0;
    }
  }

  private static void throwConversionException(String format, String... arguments) {
    throw new CommandLine.TypeConversionException(String.format(format, (Object[]) arguments));
  }

  static void assignValuesToCommand(Object command, Function optionMapping)
      throws Exception {
    LOGGER.debug("Assigning values to command {}", command.getClass());
    Collection arguments = new ArrayList<>();
    Collection fieldsToAssign = new ArrayList<>();
    for (Field field : command.getClass().getDeclaredFields()) {
      Option option = field.getAnnotation(Option.class);
      if (option == null) {
        LOGGER.debug("No option annotation for field {}", field.getName());
        continue;
      }
      String longOption =
          Arrays.stream(option.names())
              .sorted(Comparator.comparingInt(String::length).reversed())
              .findFirst()
              .get();
      LOGGER.debug("Looking up new value for option {}", longOption);
      String newValue = optionMapping.apply(longOption);

      LOGGER.debug(
          "New value found for option {} (field {}): {}", longOption, field.getName(), newValue);
      if (newValue == null) {
        continue;
      }
      fieldsToAssign.add(field);
      if (field.getType().equals(boolean.class) || field.getType().equals(Boolean.class)) {
        if (Boolean.parseBoolean(newValue)) {
          arguments.add(longOption);
        }
      } else {
        arguments.add(longOption + " " + newValue);
      }
    }
    if (fieldsToAssign.size() > 0) {
      Constructor defaultConstructor = command.getClass().getConstructor();
      Object commandBuffer = defaultConstructor.newInstance();
      String argumentsLine = String.join(" ", arguments);
      LOGGER.debug("Arguments line with extra values: {}", argumentsLine);
      String[] args = argumentsLine.split(" ");
      commandBuffer = CommandLine.populateCommand(commandBuffer, args);
      for (Field field : fieldsToAssign) {
        field.setAccessible(true);
        field.set(command, field.get(commandBuffer));
      }
    }
  }

  static CommandSpec buildCommandSpec(Object... commands) {
    Object mainCommand = commands[0];
    Command commandAnnotation = mainCommand.getClass().getAnnotation(Command.class);
    CommandSpec spec = CommandSpec.create();
    spec.name(commandAnnotation.name());
    spec.mixinStandardHelpOptions(commandAnnotation.mixinStandardHelpOptions());
    for (Object command : commands) {
      for (Field f : command.getClass().getDeclaredFields()) {
        Option annotation = f.getAnnotation(Option.class);
        if (annotation == null) {
          continue;
        }
        String name =
            Arrays.stream(annotation.names())
                .sorted(Comparator.comparingInt(String::length).reversed())
                .findFirst()
                .map(OPTION_TO_ENVIRONMENT_VARIABLE::apply)
                .get();
        spec.addOption(
            OptionSpec.builder(name)
                .type(f.getType())
                .description(annotation.description())
                .paramLabel("<" + name.replace("_", "-") + ">")
                .defaultValue(annotation.defaultValue())
                .showDefaultValue(annotation.showDefaultValue())
                .build());
      }
    }

    return spec;
  }

  static int downSamplingDivisor(int rate) {
    int divisor;
    if (rate > 0) {
      divisor = rate > 100 ? 100 : 1; // no downsampling for small rates
    } else {
      // no rate limitation, downsampling
      divisor = 100;
    }
    return divisor;
  }

  static void declareSuperStreamExchangeAndBindings(
      Channel channel, String superStream, List streams) throws Exception {
    channel.exchangeDeclare(
        superStream,
        BuiltinExchangeType.DIRECT,
        true,
        false,
        Collections.singletonMap("x-super-stream", true));

    for (int i = 0; i < streams.size(); i++) {
      channel.queueBind(
          streams.get(i),
          superStream,
          String.valueOf(i),
          Collections.singletonMap("x-stream-partition-order", i));
    }
  }

  static void deleteSuperStreamExchange(Channel channel, String superStream) throws Exception {
    channel.exchangeDelete(superStream);
  }

  static List superStreamPartitions(String superStream, int partitionCount) {
    int digits = String.valueOf(partitionCount - 1).length();
    String format = superStream + "-%0" + digits + "d";
    return IntStream.range(0, partitionCount)
        .mapToObj(i -> String.format(format, i))
        .collect(Collectors.toList());
  }

  static Connection amqpConnection(
      String amqpUri, List streamUris, boolean isTls, List sniServerNames)
      throws Exception {
    ConnectionFactory connectionFactory = new ConnectionFactory();
    if (amqpUri == null || amqpUri.trim().isEmpty()) {
      String streamUriString = streamUris.get(0);
      if (isTls) {
        streamUriString = streamUriString.replaceFirst("rabbitmq-stream\\+tls", "amqps");
      } else {
        streamUriString = streamUriString.replaceFirst("rabbitmq-stream", "amqp");
      }
      URI streamUri = new URI(streamUriString);
      int streamPort = streamUri.getPort();
      if (streamPort != -1) {
        int defaultAmqpPort =
            isTls
                ? ConnectionFactory.DEFAULT_AMQP_OVER_SSL_PORT
                : ConnectionFactory.DEFAULT_AMQP_PORT;
        streamUriString = streamUriString.replaceFirst(":" + streamPort, ":" + defaultAmqpPort);
      }
      connectionFactory.setUri(streamUriString);
    } else {
      connectionFactory.setUri(amqpUri);
    }
    if (isTls) {
      SSLContext sslContext = SSLContext.getInstance("TLS");
      sslContext.init(
          new KeyManager[] {},
          new TrustManager[] {TRUST_EVERYTHING_TRUST_MANAGER},
          new SecureRandom());
      connectionFactory.useSslProtocol(sslContext);
      if (!sniServerNames.isEmpty()) {
        SocketConfigurator socketConfigurator =
            socket -> {
              if (socket instanceof SSLSocket) {
                SSLSocket sslSocket = (SSLSocket) socket;
                SSLParameters sslParameters =
                    sslSocket.getSSLParameters() == null
                        ? new SSLParameters()
                        : sslSocket.getSSLParameters();
                sslParameters.setServerNames(sniServerNames);
                sslSocket.setSSLParameters(sslParameters);
              } else {
                LOGGER.warn("SNI parameter set on a non-TLS connection");
              }
            };
        connectionFactory.setSocketConfigurator(
            SocketConfigurators.defaultConfigurator().andThen(socketConfigurator));
      }
    }
    return connectionFactory.newConnection("stream-perf-test-amqp-connection");
  }

  static String commandLineMetrics(String[] args) {
    Map filteredOptions = new HashMap<>();
    filteredOptions.put("--uris", true);
    filteredOptions.put("-u", true);
    filteredOptions.put("--prometheus", false);
    filteredOptions.put("--amqp-uri", true);
    filteredOptions.put("--au", true);
    filteredOptions.put("--metrics-command-line-arguments", false);
    filteredOptions.put("-mcla", false);
    filteredOptions.put("--metrics-tags", true);
    filteredOptions.put("-mt", true);

    Collection filtered = new ArrayList<>();
    Iterator iterator = Arrays.stream(args).iterator();
    while (iterator.hasNext()) {
      String option = iterator.next();
      if (filteredOptions.containsKey(option)) {
        if (filteredOptions.get(option)) {
          iterator.next();
        }
      } else {
        filtered.add(option);
      }
    }
    return String.join(" ", filtered);
  }

  static class ByteCapacityTypeConverter implements CommandLine.ITypeConverter {

    @Override
    public ByteCapacity convert(String value) {
      try {
        return ByteCapacity.from(value);
      } catch (IllegalArgumentException e) {
        throw new CommandLine.TypeConversionException(
            "'" + value + "' is not valid, valid example values: 100gb, 50mb");
      }
    }
  }

  static class MetricsTagsTypeConverter implements CommandLine.ITypeConverter> {

    @Override
    public Collection convert(String value) {
      if (value == null || value.trim().isEmpty()) {
        return Collections.emptyList();
      } else {
        try {
          Collection tags = new ArrayList<>();
          for (String tag : value.split(",")) {
            String[] keyValue = tag.split("=", 2);
            tags.add(Tag.of(keyValue[0], keyValue[1]));
          }
          return tags;
        } catch (Exception e) {
          throw new TypeConversionException(
              String.format("'%s' is not valid, use key/value pairs separated by commas"));
        }
      }
    }
  }

  static class NameStrategyConverter
      implements CommandLine.ITypeConverter> {

    @Override
    public BiFunction convert(String input) {
      if ("uuid".equals(input)) {
        return (stream, index) -> UUID.randomUUID().toString();
      } else {
        return new PatternNameStrategy(input);
      }
    }
  }

  static class FilterValueSetConverter implements ITypeConverter> {

    @Override
    public List convert(String value) {
      if (value == null || value.trim().isEmpty()) {
        return Collections.emptyList();
      }
      if (value.contains("..")) {
        String[] range = value.split("\\.\\.");
        String errorMessage = "'" + value + "' is not valid, valid example values: 1..10, 1..20";
        if (range.length != 2) {
          throw new CommandLine.TypeConversionException(errorMessage);
        }
        int start, end;
        try {
          start = Integer.parseInt(range[0]);
          end = Integer.parseInt(range[1]) + 1;
          return IntStream.range(start, end).mapToObj(String::valueOf).collect(Collectors.toList());
        } catch (NumberFormatException e) {
          throw new CommandLine.TypeConversionException(errorMessage);
        }
      } else {
        return Arrays.stream(value.split(",")).collect(Collectors.toList());
      }
    }
  }

  static class SniServerNamesConverter implements ITypeConverter> {

    @Override
    public List convert(String value) {
      if (value == null || value.trim().isEmpty()) {
        return Collections.emptyList();
      } else {
        return Arrays.stream(value.split(","))
            .map(s -> s.trim())
            .map(s -> new SNIHostName(s))
            .collect(Collectors.toList());
      }
    }
  }

  static class RangeTypeConverter implements CommandLine.ITypeConverter {

    @Override
    public String convert(String input) {
      String value;
      if (input.contains(RANGE_SEPARATOR_2)) {
        value = input.replace(RANGE_SEPARATOR_2, RANGE_SEPARATOR_1);
      } else {
        value = input;
      }
      if (value.contains(RANGE_SEPARATOR_1)) {
        String[] fromTo = value.split(RANGE_SEPARATOR_1);
        if (fromTo == null || fromTo.length != 2) {
          throwConversionException("'%s' is not valid, valid examples values: 10, 1-10", input);
        }
        Arrays.stream(fromTo)
            .forEach(
                v -> {
                  try {
                    int i = Integer.parseInt(v);
                    if (i <= 0) {
                      throwConversionException(
                          "'%s' is not valid, the value must be a positive integer", v);
                    }
                  } catch (NumberFormatException e) {
                    throwConversionException(
                        "'%s' is not valid, the value must be a positive integer", v);
                  }
                });
        int from = Integer.parseInt(fromTo[0]);
        int to = Integer.parseInt(fromTo[1]);
        if (from >= to) {
          throwConversionException("'%s' is not valid, valid examples values: 10, 1-10", input);
        }
      } else {
        try {
          int count = Integer.parseInt(value);
          if (count <= 0) {
            throwConversionException(
                "'%s' is not valid, the value must be a positive integer", input);
          }
        } catch (NumberFormatException e) {
          throwConversionException("'%s' is not valid, valid example values: 10, 1-10", input);
        }
      }
      return input;
    }
  }

  static class DurationTypeConverter implements CommandLine.ITypeConverter {

    @Override
    public Duration convert(String value) {
      try {
        Duration duration = Duration.parse(value);
        if (duration.isNegative() || duration.isZero()) {
          throw new CommandLine.TypeConversionException(
              "'" + value + "' is not valid, it must be positive");
        }
        return duration;
      } catch (DateTimeParseException e) {
        throw new CommandLine.TypeConversionException(
            "'" + value + "' is not valid, valid example values: PT15M, PT10H");
      }
    }
  }

  static class LeaderLocatorTypeConverter implements CommandLine.ITypeConverter {

    @Override
    public LeaderLocator convert(String value) {
      try {
        return LeaderLocator.from(value);
      } catch (Exception e) {
        throw new CommandLine.TypeConversionException(
            "'"
                + value
                + "' is not valid, possible values: "
                + Arrays.stream(LeaderLocator.values())
                    .map(ll -> ll.value())
                    .collect(Collectors.joining(", ")));
      }
    }
  }

  static class OffsetSpecificationTypeConverter
      implements CommandLine.ITypeConverter {

    private static final Map SPECS =
        Collections.unmodifiableMap(
            new HashMap() {
              {
                put("first", OffsetSpecification.first());
                put("last", OffsetSpecification.last());
                put("next", OffsetSpecification.next());
              }
            });

    @Override
    public OffsetSpecification convert(String value) throws Exception {
      if (value == null || value.trim().isEmpty()) {
        return OffsetSpecification.first();
      }

      if (SPECS.containsKey(value.toLowerCase())) {
        return SPECS.get(value.toLowerCase());
      }

      try {
        long offset = Long.parseUnsignedLong(value);
        return OffsetSpecification.offset(offset);
      } catch (NumberFormatException e) {
        // trying next
      }

      try {
        TemporalAccessor accessor = DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(value);
        return OffsetSpecification.timestamp(Instant.from(accessor).toEpochMilli());
      } catch (DateTimeParseException e) {
        throw new CommandLine.TypeConversionException(
            "'"
                + value
                + "' is not a valid offset value, valid values are 'first', 'last', 'next', "
                + "an unsigned long, or an ISO 8601 formatted timestamp (eg. 2020-06-03T07:45:54Z)");
      }
    }
  }

  static class PositiveIntegerTypeConverter implements CommandLine.ITypeConverter {

    @Override
    public Integer convert(String input) {
      try {
        Integer value = Integer.valueOf(input);
        if (value <= 0) {
          throw new IllegalArgumentException();
        }
        return value;
      } catch (Exception e) {
        throw new CommandLine.TypeConversionException(input + " is not a positive integer");
      }
    }
  }

  static class GreaterThanOrEqualToZeroIntegerTypeConverter
      implements CommandLine.ITypeConverter {

    @Override
    public Integer convert(String input) {
      try {
        Integer value = Integer.valueOf(input);
        if (value < 0) {
          throw new IllegalArgumentException();
        }
        return value;
      } catch (Exception e) {
        throw new CommandLine.TypeConversionException(input + " is not greater than or equal to 0");
      }
    }
  }

  static class BackOffDelayPolicyTypeConverter
      implements CommandLine.ITypeConverter {

    @Override
    public BackOffDelayPolicy convert(String input) {
      if (input == null || input.trim().isEmpty()) {
        typeConversionException("Value for back-off delay policy cannot be empty");
      }
      String[] values = input.split(":");
      if (values.length != 1 && values.length != 2) {
        typeConversionException("Invalid value for back-off delay policy: " + input);
      }
      int firstAttempt = 0, nextAttempts = 0;
      try {
        firstAttempt = Integer.parseInt(values[0]);
        if (firstAttempt <= 0) {
          throw new IllegalArgumentException();
        }
      } catch (Exception e) {
        typeConversionException("Invalid value for back-off delay policy: " + input);
      }
      if (values.length == 2) {
        try {
          nextAttempts = Integer.parseInt(values[1]);
          if (nextAttempts <= 0) {
            throw new IllegalArgumentException();
          }
        } catch (Exception e) {
          typeConversionException("Invalid value for back-off delay policy: " + input);
        }
      } else {
        nextAttempts = firstAttempt;
      }
      return BackOffDelayPolicy.fixedWithInitialDelay(
          ofSeconds(firstAttempt), ofSeconds(nextAttempts));
    }
  }

  private static void typeConversionException(String message) {
    throw new TypeConversionException(message);
  }

  static class CompressionTypeConverter implements CommandLine.ITypeConverter {

    @Override
    public Compression convert(String input) {
      try {
        return Compression.valueOf(input.toUpperCase(Locale.ENGLISH));
      } catch (Exception e) {
        throw new CommandLine.TypeConversionException(
            input
                + " is not a valid compression value. "
                + "Accepted values are "
                + Arrays.stream(Compression.values())
                    .map(Compression::name)
                    .map(String::toLowerCase)
                    .collect(Collectors.joining(", "))
                + ".");
      }
    }
  }

  private abstract static class RangeIntegerTypeConverter
      implements CommandLine.ITypeConverter {

    private final int min, max;

    private RangeIntegerTypeConverter(int min, int max) {
      this.min = min;
      this.max = max;
    }

    @Override
    public Integer convert(String input) {
      try {
        Integer value = Integer.valueOf(input);
        if (value < this.min || value > this.max) {
          throw new IllegalArgumentException();
        }
        return value;
      } catch (Exception e) {
        throw new CommandLine.TypeConversionException(
            input + " must an integer between " + this.min + " and " + this.max);
      }
    }
  }

  static class OneTo255RangeIntegerTypeConverter extends RangeIntegerTypeConverter {

    OneTo255RangeIntegerTypeConverter() {
      super(1, 255);
    }
  }

  static class NotNegativeIntegerTypeConverter implements CommandLine.ITypeConverter {

    @Override
    public Integer convert(String input) {
      try {
        Integer value = Integer.valueOf(input);
        if (value < 0) {
          throw new IllegalArgumentException();
        }
        return value;
      } catch (Exception e) {
        throw new CommandLine.TypeConversionException(input + " is not a non-negative integer");
      }
    }
  }

  static class NamedThreadFactory implements ThreadFactory {

    private final ThreadFactory backingThreaFactory;

    private final String prefix;

    private final AtomicLong count = new AtomicLong(0);

    public NamedThreadFactory(String prefix) {
      this(Executors.defaultThreadFactory(), prefix);
    }

    public NamedThreadFactory(ThreadFactory backingThreadFactory, String prefix) {
      this.backingThreaFactory = backingThreadFactory;
      this.prefix = prefix;
    }

    @Override
    public Thread newThread(Runnable r) {
      Thread thread = this.backingThreaFactory.newThread(r);
      thread.setName(prefix + count.getAndIncrement());
      return thread;
    }
  }

  private static class TrustEverythingTrustManager implements X509TrustManager {

    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType) {}

    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType) {}

    @Override
    public X509Certificate[] getAcceptedIssuers() {
      return new X509Certificate[0];
    }
  }

  static final class PatternNameStrategy implements BiFunction {

    private final String pattern;

    PatternNameStrategy(String pattern) {
      this.pattern = pattern;
    }

    @Override
    public String apply(String stream, Integer index) {
      return String.format(pattern, stream, index);
    }
  }

  static class PerformanceMicrometerMetricsCollector extends MicrometerMetricsCollector {

    public PerformanceMicrometerMetricsCollector(MeterRegistry registry, String prefix) {
      super(registry, prefix);
    }

    @Override
    protected Counter createChunkCounter(
        MeterRegistry registry, String prefix, Iterable tags) {
      return null;
    }

    @Override
    protected DistributionSummary createChunkSizeDistributionSummary(
        MeterRegistry registry, String prefix, Iterable tags) {
      return DistributionSummary.builder(prefix + ".chunk_size")
          .tags(tags)
          .description("chunk size")
          .publishPercentiles(0.5, 0.75, 0.95, 0.99)
          .distributionStatisticExpiry(ofSeconds(1))
          .serviceLevelObjectives()
          .register(registry);
    }

    @Override
    public void chunk(int entriesCount) {
      this.chunkSize.record(entriesCount);
    }
  }

  static DropwizardMeterRegistry dropwizardMeterRegistry() {
    DropwizardConfig dropwizardConfig =
        new DropwizardConfig() {
          @Override
          public String prefix() {
            return "";
          }

          @Override
          public String get(String key) {
            return null;
          }
        };
    MetricRegistry metricRegistry = new MetricRegistry();
    DropwizardMeterRegistry dropwizardMeterRegistry =
        new DropwizardMeterRegistry(
            dropwizardConfig,
            metricRegistry,
            HierarchicalNameMapper.DEFAULT,
            io.micrometer.core.instrument.Clock.SYSTEM) {
          @Override
          protected Double nullGaugeValue() {
            return null;
          }
        };
    return dropwizardMeterRegistry;
  }

  @SuppressWarnings("unchecked")
  static InstanceSynchronization defaultInstanceSynchronization(
      String id, int expectedInstances, String namespace, Duration timeout, PrintWriter out) {
    try {
      Class defaultClass =
          (Class)
              Class.forName("com.rabbitmq.stream.perf.DefaultInstanceSynchronization");
      Constructor constructor =
          defaultClass.getDeclaredConstructor(
              String.class, int.class, String.class, Duration.class, PrintWriter.class);
      return constructor.newInstance(id, expectedInstances, namespace, timeout, out);
    } catch (ClassNotFoundException e) {
      return () -> {
        if (expectedInstances > 1) {
          throw new IllegalArgumentException("Multi-instance synchronization is not available");
        }
      };
    } catch (NoSuchMethodException e) {
      throw new RuntimeException(e);
    } catch (InvocationTargetException e) {
      throw new RuntimeException(e);
    } catch (InstantiationException e) {
      throw new RuntimeException(e);
    } catch (IllegalAccessException e) {
      throw new RuntimeException(e);
    }
  }

  static int filteringPublishingCycle(int rate) {
    if (rate == 0) {
      return 100_000;
    } else if (rate <= 10) {
      return 10;
    } else {
      return rate / 10;
    }
  }

  static int filteringSubSetSize(int setSize) {
    if (setSize <= 3) {
      return 1;
    } else if (setSize > 10) {
      return (int) (setSize * 0.70);
    } else {
      return setSize - 3;
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy