/* * Copyright 2015, Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package io.grpc.benchmarks.qps; import static io.grpc.benchmarks.Utils.parseBoolean; import static java.lang.Integer.parseInt; import static java.util.Arrays.asList; import io.grpc.ManagedChannel; import io.grpc.benchmarks.Transport; import io.grpc.benchmarks.Utils; import io.grpc.benchmarks.proto.Control.RpcType; import io.grpc.benchmarks.proto.Messages; import io.grpc.benchmarks.proto.Messages.PayloadType; import io.grpc.testing.TestUtils; import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; import java.util.Set; /** * Configuration options for benchmark clients. */ public class ClientConfiguration implements Configuration { private static final ClientConfiguration DEFAULT = new ClientConfiguration(); Transport transport = Transport.NETTY_NIO; boolean tls; boolean testca; String authorityOverride = TestUtils.TEST_SERVER_HOST; boolean useDefaultCiphers; boolean directExecutor; SocketAddress address; int channels = 4; int outstandingRpcsPerChannel = 10; int serverPayload; int clientPayload; int flowControlWindow = Utils.DEFAULT_FLOW_CONTROL_WINDOW; // seconds int duration = 60; // seconds int warmupDuration = 10; int targetQps; String histogramFile; RpcType rpcType = RpcType.UNARY; PayloadType payloadType = PayloadType.COMPRESSABLE; private ClientConfiguration() { } public ManagedChannel newChannel() throws IOException { return Utils.newClientChannel(transport, address, tls, testca, authorityOverride, useDefaultCiphers, flowControlWindow, directExecutor); } public Messages.SimpleRequest newRequest() { return Utils.makeRequest(payloadType, clientPayload, serverPayload); } /** * Constructs a builder for configuring a client application with supported parameters. If no * parameters are provided, all parameters are assumed to be supported. */ static Builder newBuilder(ClientParam... supportedParams) { return new Builder(supportedParams); } static final class Builder extends AbstractConfigurationBuilder<ClientConfiguration> { private final Collection<Param> supportedParams; private Builder(ClientParam... supportedParams) { this.supportedParams = supportedOptionsSet(supportedParams); } @Override protected ClientConfiguration newConfiguration() { return new ClientConfiguration(); } @Override protected Collection<Param> getParams() { return supportedParams; } @Override protected ClientConfiguration build0(ClientConfiguration config) { if (config.tls) { if (!config.transport.tlsSupported) { throw new IllegalArgumentException( "Transport " + config.transport.name().toLowerCase() + " does not support TLS."); } if (config.transport != Transport.OK_HTTP && config.testca && config.address instanceof InetSocketAddress) { // Override the socket address with the host from the testca. InetSocketAddress address = (InetSocketAddress) config.address; config.address = TestUtils.testServerAddress(address.getHostName(), address.getPort()); } } // Verify that the address type is correct for the transport type. config.transport.validateSocketAddress(config.address); return config; } private static Set<Param> supportedOptionsSet(ClientParam... supportedParams) { if (supportedParams.length == 0) { // If no options are supplied, default to including all options. supportedParams = ClientParam.values(); } return Collections.unmodifiableSet(new LinkedHashSet<Param>(asList(supportedParams))); } } enum ClientParam implements AbstractConfigurationBuilder.Param { ADDRESS("STR", "Socket address (host:port) or Unix Domain Socket file name " + "(unix:///path/to/file), depending on the transport selected.", null, true) { @Override protected void setClientValue(ClientConfiguration config, String value) { config.address = Utils.parseSocketAddress(value); } }, CHANNELS("INT", "Number of Channels.", "" + DEFAULT.channels) { @Override protected void setClientValue(ClientConfiguration config, String value) { config.channels = parseInt(value); } }, OUTSTANDING_RPCS("INT", "Number of outstanding RPCs per Channel.", "" + DEFAULT.outstandingRpcsPerChannel) { @Override protected void setClientValue(ClientConfiguration config, String value) { config.outstandingRpcsPerChannel = parseInt(value); } }, CLIENT_PAYLOAD("BYTES", "Payload Size of the Request.", "" + DEFAULT.clientPayload) { @Override protected void setClientValue(ClientConfiguration config, String value) { config.clientPayload = parseInt(value); } }, SERVER_PAYLOAD("BYTES", "Payload Size of the Response.", "" + DEFAULT.serverPayload) { @Override protected void setClientValue(ClientConfiguration config, String value) { config.serverPayload = parseInt(value); } }, TLS("", "Enable TLS.", "" + DEFAULT.tls) { @Override protected void setClientValue(ClientConfiguration config, String value) { config.tls = parseBoolean(value); } }, TESTCA("", "Use the provided Test Certificate for TLS.", "" + DEFAULT.testca) { @Override protected void setClientValue(ClientConfiguration config, String value) { config.testca = parseBoolean(value); } }, USE_DEFAULT_CIPHERS("", "Use the default JDK ciphers for TLS (Used to support Java 7).", "" + DEFAULT.useDefaultCiphers) { @Override protected void setClientValue(ClientConfiguration config, String value) { config.useDefaultCiphers = parseBoolean(value); } }, TRANSPORT("STR", Transport.getDescriptionString(), DEFAULT.transport.name().toLowerCase()) { @Override protected void setClientValue(ClientConfiguration config, String value) { config.transport = Transport.valueOf(value.toUpperCase()); } }, DURATION("SECONDS", "Duration of the benchmark.", "" + DEFAULT.duration) { @Override protected void setClientValue(ClientConfiguration config, String value) { config.duration = parseInt(value); } }, WARMUP_DURATION("SECONDS", "Warmup Duration of the benchmark.", "" + DEFAULT.warmupDuration) { @Override protected void setClientValue(ClientConfiguration config, String value) { config.warmupDuration = parseInt(value); } }, DIRECTEXECUTOR("", "Don't use a threadpool for RPC calls, instead execute calls directly " + "in the transport thread.", "" + DEFAULT.directExecutor) { @Override protected void setClientValue(ClientConfiguration config, String value) { config.directExecutor = parseBoolean(value); } }, SAVE_HISTOGRAM("FILE", "Write the histogram with the latency recordings to file.", null) { @Override protected void setClientValue(ClientConfiguration config, String value) { config.histogramFile = value; } }, STREAMING_RPCS("", "Use Streaming RPCs.", "false") { @Override protected void setClientValue(ClientConfiguration config, String value) { config.rpcType = RpcType.STREAMING; } }, FLOW_CONTROL_WINDOW("BYTES", "The HTTP/2 flow control window.", "" + DEFAULT.flowControlWindow) { @Override protected void setClientValue(ClientConfiguration config, String value) { config.flowControlWindow = parseInt(value); } }, TARGET_QPS("INT", "Average number of QPS to shoot for.", "" + DEFAULT.targetQps, true) { @Override protected void setClientValue(ClientConfiguration config, String value) { config.targetQps = parseInt(value); } }; private final String type; private final String description; private final String defaultValue; private final boolean required; ClientParam(String type, String description, String defaultValue) { this(type, description, defaultValue, false); } ClientParam(String type, String description, String defaultValue, boolean required) { this.type = type; this.description = description; this.defaultValue = defaultValue; this.required = required; } @Override public String getName() { return name().toLowerCase(); } @Override public String getType() { return type; } @Override public String getDescription() { return description; } @Override public String getDefaultValue() { return defaultValue; } @Override public boolean isRequired() { return required; } @Override public void setValue(Configuration config, String value) { setClientValue((ClientConfiguration) config, value); } protected abstract void setClientValue(ClientConfiguration config, String value); } }