1 /* 2 * Copyright 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.conscrypt; 18 19 import static org.conscrypt.TestUtils.getProtocols; 20 import static org.conscrypt.TestUtils.newTextMessage; 21 import static org.junit.Assert.assertEquals; 22 23 import java.io.IOException; 24 import java.io.OutputStream; 25 import java.net.SocketException; 26 import java.util.concurrent.ExecutorService; 27 import java.util.concurrent.Executors; 28 import java.util.concurrent.Future; 29 import java.util.concurrent.TimeUnit; 30 import java.util.concurrent.atomic.AtomicBoolean; 31 import java.util.concurrent.atomic.AtomicLong; 32 import org.conscrypt.ServerEndpoint.MessageProcessor; 33 34 /** 35 * Benchmark for comparing performance of server socket implementations. 36 */ 37 public final class ServerSocketBenchmark { 38 /** 39 * Provider for the benchmark configuration 40 */ 41 interface Config { 42 EndpointFactory clientFactory(); 43 EndpointFactory serverFactory(); 44 int messageSize(); 45 String cipher(); 46 ChannelType channelType(); 47 } 48 49 private ClientEndpoint client; 50 private ServerEndpoint server; 51 private ExecutorService executor; 52 private Future<?> receivingFuture; 53 private volatile boolean stopping; 54 private static final AtomicLong bytesCounter = new AtomicLong(); 55 private AtomicBoolean recording = new AtomicBoolean(); 56 57 ServerSocketBenchmark(final Config config) throws Exception { 58 recording.set(false); 59 60 byte[] message = newTextMessage(config.messageSize()); 61 62 final ChannelType channelType = config.channelType(); 63 64 server = config.serverFactory().newServer( 65 channelType, config.messageSize(), getProtocols(), ciphers(config)); 66 server.setMessageProcessor(new MessageProcessor() { 67 @Override 68 public void processMessage(byte[] inMessage, int numBytes, OutputStream os) { 69 try { 70 try { 71 while (!stopping) { 72 os.write(inMessage, 0, numBytes); 73 } 74 } finally { 75 os.flush(); 76 } 77 } catch (SocketException e) { 78 // Just ignore. 79 } catch (IOException e) { 80 throw new RuntimeException(e); 81 } 82 } 83 }); 84 85 Future<?> connectedFuture = server.start(); 86 87 // Always use the same client for consistency across the benchmarks. 88 client = config.clientFactory().newClient( 89 ChannelType.CHANNEL, server.port(), getProtocols(), ciphers(config)); 90 client.start(); 91 92 // Wait for the initial connection to complete. 93 connectedFuture.get(5, TimeUnit.SECONDS); 94 95 // Start the server-side streaming by sending a message to the server. 96 client.sendMessage(message); 97 client.flush(); 98 99 executor = Executors.newSingleThreadExecutor(); 100 receivingFuture = executor.submit(new Runnable() { 101 @Override 102 public void run() { 103 Thread thread = Thread.currentThread(); 104 byte[] buffer = new byte[config.messageSize()]; 105 while (!stopping && !thread.isInterrupted()) { 106 int numBytes = client.readMessage(buffer); 107 if (numBytes < 0) { 108 return; 109 } 110 assertEquals(config.messageSize(), numBytes); 111 112 // Increment the message counter if we're recording. 113 if (recording.get()) { 114 bytesCounter.addAndGet(numBytes); 115 } 116 } 117 } 118 }); 119 } 120 121 void close() throws Exception { 122 stopping = true; 123 // Stop and wait for sending to complete. 124 server.stop(); 125 client.stop(); 126 executor.shutdown(); 127 receivingFuture.get(5, TimeUnit.SECONDS); 128 executor.awaitTermination(5, TimeUnit.SECONDS); 129 } 130 131 void throughput() throws Exception { 132 recording.set(true); 133 // Send as many messages as we can in a second. 134 Thread.sleep(1001); 135 recording.set(false); 136 } 137 138 static void reset() { 139 bytesCounter.set(0); 140 } 141 142 static long bytesPerSecond() { 143 return bytesCounter.get(); 144 } 145 146 private String[] ciphers(Config config) { 147 return new String[] {config.cipher()}; 148 } 149 } 150