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
17package org.conscrypt;
18
19import static org.conscrypt.TestUtils.getProtocols;
20import static org.conscrypt.TestUtils.newTextMessage;
21
22import java.io.OutputStream;
23import java.util.concurrent.ExecutorService;
24import java.util.concurrent.Executors;
25import java.util.concurrent.Future;
26import java.util.concurrent.TimeUnit;
27import java.util.concurrent.atomic.AtomicBoolean;
28import java.util.concurrent.atomic.AtomicLong;
29
30/**
31 * Benchmark for comparing performance of client socket implementations.
32 */
33public final class ClientSocketBenchmark {
34    /**
35     * Provider for the benchmark configuration
36     */
37    interface Config {
38        SocketType socketType();
39        int messageSize();
40        String cipher();
41        ChannelType channelType();
42    }
43
44    private ClientEndpoint client;
45    private ServerEndpoint server;
46    private byte[] message;
47    private ExecutorService executor;
48    private Future<?> sendingFuture;
49    private volatile boolean stopping;
50
51    private static final AtomicLong bytesCounter = new AtomicLong();
52    private AtomicBoolean recording = new AtomicBoolean();
53
54    ClientSocketBenchmark(Config config) throws Exception {
55        recording.set(false);
56
57        message = newTextMessage(config.messageSize());
58
59        // Always use the same server for consistency across the benchmarks.
60        server = SocketType.CONSCRYPT_ENGINE.newServer(
61                ChannelType.CHANNEL, config.messageSize(), getProtocols(), ciphers(config));
62
63        server.setMessageProcessor(new ServerEndpoint.MessageProcessor() {
64            @Override
65            public void processMessage(byte[] inMessage, int numBytes, OutputStream os) {
66                if (recording.get()) {
67                    // Server received a message, increment the count.
68                    bytesCounter.addAndGet(numBytes);
69                }
70            }
71        });
72        Future<?> connectedFuture = server.start();
73
74        client = config.socketType().newClient(
75            config.channelType(), server.port(), getProtocols(), ciphers(config));
76        client.start();
77
78        // Wait for the initial connection to complete.
79        connectedFuture.get(5, TimeUnit.SECONDS);
80
81        executor = Executors.newSingleThreadExecutor();
82        sendingFuture = executor.submit(new Runnable() {
83            @Override
84            public void run() {
85                Thread thread = Thread.currentThread();
86                while (!stopping && !thread.isInterrupted()) {
87                    client.sendMessage(message);
88                }
89            }
90        });
91    }
92
93    void close() throws Exception {
94        stopping = true;
95        client.stop();
96        server.stop();
97        executor.shutdown();
98        executor.awaitTermination(5, TimeUnit.SECONDS);
99        sendingFuture.get(5, TimeUnit.SECONDS);
100    }
101
102    /**
103     * Simple benchmark for throughput.
104     */
105    void throughput() throws Exception {
106        recording.set(true);
107        // Send as many messages as we can in a second.
108        Thread.sleep(1001);
109        recording.set(false);
110    }
111
112    static void reset() {
113        bytesCounter.set(0);
114    }
115
116    static long bytesPerSecond() {
117        return bytesCounter.get();
118    }
119
120    private String[] ciphers(Config config) {
121        return new String[] {config.cipher()};
122    }
123
124    /**
125     * A simple main for profiling.
126     */
127    public static void main(String[] args) throws Exception {
128        ClientSocketBenchmark bm = new ClientSocketBenchmark(new Config() {
129            @Override
130            public SocketType socketType() {
131                return SocketType.CONSCRYPT_ENGINE;
132            }
133
134            @Override
135            public int messageSize() {
136                return 512;
137            }
138
139            @Override
140            public String cipher() {
141                return TestUtils.TEST_CIPHER;
142            }
143
144            @Override
145            public ChannelType channelType() {
146                return ChannelType.CHANNEL;
147            }
148        });
149
150        // Just run forever for profiling.
151        while (true) {
152            bm.throughput();
153        }
154    }
155}
156