CASSANDRASC-98: Improve logging for traffic shaping / rate limiting configurations
Patch by Francisco Guerrero; Reviewed by Yifan Cai for CASSANDRASC-98
diff --git a/CHANGES.txt b/CHANGES.txt
index 27fba7e..1c3a6f8 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,6 @@
1.0.0
-----
+ * Improve logging for traffic shaping / rate limiting configuration (CASSANDRASC-98)
* Startup Validation Failures when Checking Sidecar Connectivity (CASSANDRASC-86)
* Add support for additional digest validation during SSTable upload (CASSANDRASC-97)
* Add sidecar client changes for restore from S3 (CASSANDRASC-95)
diff --git a/common/src/main/java/org/apache/cassandra/sidecar/common/utils/ByteUtils.java b/common/src/main/java/org/apache/cassandra/sidecar/common/utils/ByteUtils.java
new file mode 100644
index 0000000..8af203a
--- /dev/null
+++ b/common/src/main/java/org/apache/cassandra/sidecar/common/utils/ByteUtils.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.common.utils;
+
+/**
+ * A utility class to perform byte operations
+ */
+public final class ByteUtils
+{
+ /**
+ * {@code 1 Kibibyte} is equivalent to {@code 1,024 bytes}
+ */
+ public static final long ONE_KIB = 1L << 10;
+
+ /**
+ * {@code 1 Mebibyte} is equivalent to {@code 1,048,576 bytes}
+ */
+ public static final long ONE_MIB = ONE_KIB << 10;
+
+ /**
+ * {@code 1 Gibibyte} is equivalent to {@code 1,073,741,824 bytes}
+ */
+ public static final long ONE_GIB = ONE_MIB << 10;
+
+ /**
+ * {@code 1 Tebibyte} is equivalent to {@code 1,099,511,627,776 bytes}
+ */
+ public static final long ONE_TIB = ONE_GIB << 10;
+
+ /**
+ * {@code 1 Pebibyte} is equivalent to {@code 1,125,899,906,842,624 bytes}
+ */
+ public static final long ONE_PIB = ONE_TIB << 10;
+
+ /**
+ * {@code 1 Exbibyte} is equivalent to {@code 1,152,921,504,606,846,976 bytes}
+ */
+ public static final long ONE_EIB = ONE_PIB << 10;
+
+ /**
+ * Returns a human-readable representation of the number of {@code bytes} in the binary prefix format. A
+ * long input can only represent up to exbibytes units.
+ *
+ * @param bytes the non-negative number of bytes
+ * @return a human-readable representation of the number of {@code bytes} in the binary prefix format
+ * @throws IllegalArgumentException when {@code bytes} is a negative value
+ */
+ public static String bytesToHumanReadableBinaryPrefix(long bytes)
+ {
+ Preconditions.checkArgument(bytes >= 0, "bytes cannot be negative");
+
+ if (bytes >= ONE_EIB) return formatHelper(bytes, ONE_EIB, "EiB");
+ if (bytes >= ONE_PIB) return formatHelper(bytes, ONE_PIB, "PiB");
+ if (bytes >= ONE_TIB) return formatHelper(bytes, ONE_TIB, "TiB");
+ if (bytes >= ONE_GIB) return formatHelper(bytes, ONE_GIB, "GiB");
+ if (bytes >= ONE_MIB) return formatHelper(bytes, ONE_MIB, "MiB");
+ if (bytes >= ONE_KIB) return formatHelper(bytes, ONE_KIB, "KiB");
+ return bytes + " B";
+ }
+
+ static String formatHelper(double bytes, long baseUnit, String unitName)
+ {
+ return String.format("%.2f %s", bytes / baseUnit, unitName);
+ }
+}
diff --git a/common/src/test/java/org/apache/cassandra/sidecar/common/utils/ByteUtilsTest.java b/common/src/test/java/org/apache/cassandra/sidecar/common/utils/ByteUtilsTest.java
new file mode 100644
index 0000000..981729e
--- /dev/null
+++ b/common/src/test/java/org/apache/cassandra/sidecar/common/utils/ByteUtilsTest.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.common.utils;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+
+/**
+ * Unit tests for {@link ByteUtils}
+ */
+class ByteUtilsTest
+{
+ @ParameterizedTest
+ @ValueSource(longs = { -1L, -10L, -100L, -1024L, Long.MIN_VALUE })
+ void failsWithNegativeInputs(long bytes)
+ {
+ assertThatIllegalArgumentException().isThrownBy(() -> ByteUtils.bytesToHumanReadableBinaryPrefix(bytes))
+ .withMessage("bytes cannot be negative");
+ }
+
+ @Test
+ void testToHumanReadableBinaryPrefix()
+ {
+ assertThat(ByteUtils.bytesToHumanReadableBinaryPrefix(0)).isEqualTo("0 B");
+ assertThat(ByteUtils.bytesToHumanReadableBinaryPrefix(1152921504606846976L)).isEqualTo("1.00 EiB");
+ assertThat(ByteUtils.bytesToHumanReadableBinaryPrefix(1152921504606846977L)).isEqualTo("1.00 EiB");
+ assertThat(ByteUtils.bytesToHumanReadableBinaryPrefix(Long.MAX_VALUE)).isEqualTo("8.00 EiB");
+ assertThat(ByteUtils.bytesToHumanReadableBinaryPrefix(64)).isEqualTo("64 B");
+ assertThat(ByteUtils.bytesToHumanReadableBinaryPrefix(3221225472L)).isEqualTo("3.00 GiB");
+ assertThat(ByteUtils.bytesToHumanReadableBinaryPrefix(4L * 1024 * 1024)).isEqualTo("4.00 MiB");
+ assertThat(ByteUtils.bytesToHumanReadableBinaryPrefix(4L * 1024 * 1024 * 1024)).isEqualTo("4.00 GiB");
+ assertThat(ByteUtils.bytesToHumanReadableBinaryPrefix(4L * 1024 * 1024 * 1024 * 1024)).isEqualTo("4.00 TiB");
+ assertThat(ByteUtils.bytesToHumanReadableBinaryPrefix(4L * 1024 * 1024 * 1024 * 1024 * 1024))
+ .isEqualTo("4.00 PiB");
+ assertThat(ByteUtils.bytesToHumanReadableBinaryPrefix(4L * 1024 * 1024 * 1024 * 1024 * 1024 * 1024))
+ .isEqualTo("4.00 EiB");
+ }
+}
diff --git a/src/main/java/org/apache/cassandra/sidecar/server/HttpServerOptionsProvider.java b/src/main/java/org/apache/cassandra/sidecar/server/HttpServerOptionsProvider.java
index fad0b81..2de80dd 100644
--- a/src/main/java/org/apache/cassandra/sidecar/server/HttpServerOptionsProvider.java
+++ b/src/main/java/org/apache/cassandra/sidecar/server/HttpServerOptionsProvider.java
@@ -39,6 +39,7 @@
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.apache.cassandra.sidecar.common.utils.ByteUtils.bytesToHumanReadableBinaryPrefix;
/**
* A provider that takes the {@link SidecarConfiguration} and builds {@link HttpServerOptions} from the configured
@@ -143,18 +144,34 @@
/**
* Returns the built {@link TrafficShapingOptions} that are going to be applied to the server.
*
- * @param trafficShapingConfig the configuration for the traffic shaping options.
+ * @param config the configuration for the traffic shaping options.
* @return the built {@link TrafficShapingOptions} from the {@link TrafficShapingConfiguration}
*/
- protected TrafficShapingOptions buildTrafficShapingOptions(TrafficShapingConfiguration trafficShapingConfig)
+ protected TrafficShapingOptions buildTrafficShapingOptions(TrafficShapingConfiguration config)
{
+ long inboundGlobalBandwidthBytesPerSecond = config.inboundGlobalBandwidthBytesPerSecond();
+ long outboundGlobalBandwidthBytesPerSecond = config.outboundGlobalBandwidthBytesPerSecond();
+ long peakOutboundGlobalBandwidthBytesPerSecond = config.peakOutboundGlobalBandwidthBytesPerSecond();
+ LOGGER.info("Configured traffic shaping options. InboundGlobalBandwidth={}/s " +
+ "rawInboundGlobalBandwidth={} B/s OutboundGlobalBandwidth={}/s rawOutboundGlobalBandwidth={} B/s " +
+ "PeakOutboundGlobalBandwidth={}/s rawPeakOutboundGlobalBandwidth={} B/s IntervalForStats={}ms " +
+ "MaxDelayToWait={}ms",
+ bytesToHumanReadableBinaryPrefix(inboundGlobalBandwidthBytesPerSecond),
+ inboundGlobalBandwidthBytesPerSecond,
+ bytesToHumanReadableBinaryPrefix(outboundGlobalBandwidthBytesPerSecond),
+ outboundGlobalBandwidthBytesPerSecond,
+ bytesToHumanReadableBinaryPrefix(peakOutboundGlobalBandwidthBytesPerSecond),
+ peakOutboundGlobalBandwidthBytesPerSecond,
+ config.checkIntervalForStatsMillis(),
+ config.maxDelayToWaitMillis()
+ );
return new TrafficShapingOptions()
- .setInboundGlobalBandwidth(trafficShapingConfig.inboundGlobalBandwidthBytesPerSecond())
- .setOutboundGlobalBandwidth(trafficShapingConfig.outboundGlobalBandwidthBytesPerSecond())
- .setPeakOutboundGlobalBandwidth(trafficShapingConfig.peakOutboundGlobalBandwidthBytesPerSecond())
- .setCheckIntervalForStats(trafficShapingConfig.checkIntervalForStatsMillis())
+ .setInboundGlobalBandwidth(inboundGlobalBandwidthBytesPerSecond)
+ .setOutboundGlobalBandwidth(outboundGlobalBandwidthBytesPerSecond)
+ .setPeakOutboundGlobalBandwidth(peakOutboundGlobalBandwidthBytesPerSecond)
+ .setCheckIntervalForStats(config.checkIntervalForStatsMillis())
.setCheckIntervalForStatsTimeUnit(MILLISECONDS)
- .setMaxDelayToWait(trafficShapingConfig.maxDelayToWaitMillis())
+ .setMaxDelayToWait(config.maxDelayToWaitMillis())
.setMaxDelayToWaitUnit(MILLISECONDS);
}
}
diff --git a/src/main/java/org/apache/cassandra/sidecar/server/MainModule.java b/src/main/java/org/apache/cassandra/sidecar/server/MainModule.java
index 31ab7b1..008fadc 100644
--- a/src/main/java/org/apache/cassandra/sidecar/server/MainModule.java
+++ b/src/main/java/org/apache/cassandra/sidecar/server/MainModule.java
@@ -26,6 +26,8 @@
import java.util.stream.Collectors;
import com.google.common.util.concurrent.SidecarRateLimiter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import com.datastax.driver.core.NettyOptions;
import com.google.inject.AbstractModule;
@@ -98,6 +100,7 @@
import org.apache.cassandra.sidecar.utils.CassandraVersionProvider;
import org.apache.cassandra.sidecar.utils.TimeProvider;
+import static org.apache.cassandra.sidecar.common.utils.ByteUtils.bytesToHumanReadableBinaryPrefix;
import static org.apache.cassandra.sidecar.server.SidecarServerEvents.ON_SERVER_STOP;
/**
@@ -105,6 +108,7 @@
*/
public class MainModule extends AbstractModule
{
+ private static final Logger LOGGER = LoggerFactory.getLogger(MainModule.class);
public static final Map<String, String> OK_STATUS = Collections.singletonMap("status", "OK");
public static final Map<String, String> NOT_OK_STATUS = Collections.singletonMap("status", "NOT_OK");
@@ -383,10 +387,13 @@
@Provides
@Singleton
+ @Named("StreamRequestRateLimiter")
public SidecarRateLimiter streamRequestRateLimiter(ServiceConfiguration config)
{
- return SidecarRateLimiter.create(config.throttleConfiguration()
- .rateLimitStreamRequestsPerSecond());
+ long permitsPerSecond = config.throttleConfiguration().rateLimitStreamRequestsPerSecond();
+ LOGGER.info("Configuring streamRequestRateLimiter. rateLimitStreamRequestsPerSecond={}",
+ permitsPerSecond);
+ return SidecarRateLimiter.create(permitsPerSecond);
}
@Provides
@@ -394,8 +401,12 @@
@Named("IngressFileRateLimiter")
public SidecarRateLimiter ingressFileRateLimiter(ServiceConfiguration config)
{
- return SidecarRateLimiter.create(config.trafficShapingConfiguration()
- .inboundGlobalFileBandwidthBytesPerSecond());
+ long bytesPerSecond = config.trafficShapingConfiguration()
+ .inboundGlobalFileBandwidthBytesPerSecond();
+ LOGGER.info("Configuring ingressFileRateLimiter. inboundGlobalFileBandwidth={}/s " +
+ "rawInboundGlobalFileBandwidth={} B/s", bytesToHumanReadableBinaryPrefix(bytesPerSecond),
+ bytesPerSecond);
+ return SidecarRateLimiter.create(bytesPerSecond);
}
@Provides
diff --git a/src/main/java/org/apache/cassandra/sidecar/utils/FileStreamer.java b/src/main/java/org/apache/cassandra/sidecar/utils/FileStreamer.java
index 77cd16f..2f133af 100644
--- a/src/main/java/org/apache/cassandra/sidecar/utils/FileStreamer.java
+++ b/src/main/java/org/apache/cassandra/sidecar/utils/FileStreamer.java
@@ -28,6 +28,7 @@
import com.google.inject.Inject;
import com.google.inject.Singleton;
+import com.google.inject.name.Named;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.ext.web.handler.HttpException;
@@ -60,7 +61,7 @@
@Inject
public FileStreamer(ExecutorPools executorPools,
ServiceConfiguration config,
- SidecarRateLimiter rateLimiter,
+ @Named("StreamRequestRateLimiter") SidecarRateLimiter rateLimiter,
SidecarStats stats)
{
this.executorPools = executorPools;