├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md └── src └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "echo_bench" 3 | version = "0.2.0" 4 | authors = ["Harald Hoyer "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | getopts = "0" 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Red Hat Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rust_echo_bench 2 | A simple rust echo server benchmark. 3 | 4 | ``` 5 | $ cargo run --release -- --help 6 | Compiling echo_bench v0.1.0 (…) 7 | Finished release [optimized] target(s) in 1.40 secs 8 | Running `target/release/echo_bench --help` 9 | Echo benchmark. 10 | 11 | Usage: 12 | echo_bench [ -a
] [ -l ] [ -c ] [ -t ] 13 | echo_bench (-h | --help) 14 | echo_bench --version 15 | 16 | Options: 17 | -h, --help Show this screen. 18 | -a, --address
Target echo server address. 19 | -l, --lenght Test message length. 20 | -t, --duration Test duration in seconds. 21 | -c, --number Test connection number. 22 | ``` 23 | 24 | Run it against a server: 25 | ``` 26 | $ cargo run --release -- --address "127.0.0.1:12345" --number 1000 --duration 60 --length 512 27 | Finished release [optimized] target(s) in 0.0 secs 28 | Running `target/release/echo_bench --address 127.0.0.1:12345 --number 1000 --duration 60 --length 512` 29 | Benchmarking: 127.0.0.1:12345 30 | 1000 clients, running 512 bytes, 60 sec. 31 | 32 | Speed: 670864 request/sec, 670864 response/sec 33 | Requests: 40251881 34 | Responses: 40251872 35 | ``` 36 | 37 | A simple rust echo server can be found at: https://github.com/haraldh/rust_echo_server 38 | 39 | This benchmark can also be used to run it against various other echo servers, e.g. those found at https://gist.github.com/idada/9342414 40 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::io::{Read, Write}; 3 | use std::net::TcpStream; 4 | use std::sync::atomic::{AtomicBool, Ordering}; 5 | use std::sync::{mpsc, Arc}; 6 | use std::thread; 7 | use std::time::Duration; 8 | 9 | fn print_usage(program: &str, opts: &getopts::Options) { 10 | let brief = format!( 11 | r#"Echo benchmark. 12 | 13 | Usage: 14 | {program} [ -a
] [ -l ] [ -c ] [ -t ] 15 | {program} (-h | --help) 16 | {program} --version"#, 17 | program = program 18 | ); 19 | print!("{}", opts.usage(&brief)); 20 | } 21 | 22 | struct Count { 23 | inb: u64, 24 | outb: u64, 25 | } 26 | 27 | fn main() { 28 | let args: Vec<_> = env::args().collect(); 29 | let program = args[0].clone(); 30 | 31 | let mut opts = getopts::Options::new(); 32 | opts.optflag("h", "help", "Print this help."); 33 | opts.optopt( 34 | "a", 35 | "address", 36 | "Target echo server address. Default: 127.0.0.1:12345", 37 | "
", 38 | ); 39 | opts.optopt( 40 | "l", 41 | "length", 42 | "Test message length. Default: 512", 43 | "", 44 | ); 45 | opts.optopt( 46 | "t", 47 | "duration", 48 | "Test duration in seconds. Default: 60", 49 | "", 50 | ); 51 | opts.optopt( 52 | "c", 53 | "number", 54 | "Test connection number. Default: 50", 55 | "", 56 | ); 57 | 58 | let matches = match opts.parse(&args[1..]) { 59 | Ok(m) => m, 60 | Err(f) => { 61 | eprintln!("{}", f.to_string()); 62 | print_usage(&program, &opts); 63 | return; 64 | } 65 | }; 66 | 67 | if matches.opt_present("h") { 68 | print_usage(&program, &opts); 69 | return; 70 | } 71 | 72 | let length = matches 73 | .opt_str("length") 74 | .unwrap_or_default() 75 | .parse::() 76 | .unwrap_or(512); 77 | let duration = matches 78 | .opt_str("duration") 79 | .unwrap_or_default() 80 | .parse::() 81 | .unwrap_or(60); 82 | let number = matches 83 | .opt_str("number") 84 | .unwrap_or_default() 85 | .parse::() 86 | .unwrap_or(50); 87 | let address = matches 88 | .opt_str("address") 89 | .unwrap_or_else(|| "127.0.0.1:12345".to_string()); 90 | 91 | let (tx, rx) = mpsc::channel(); 92 | 93 | let stop = Arc::new(AtomicBool::new(false)); 94 | let control = Arc::downgrade(&stop); 95 | 96 | for _ in 0..number { 97 | let tx = tx.clone(); 98 | let address = address.clone(); 99 | let stop = stop.clone(); 100 | let length = length; 101 | 102 | thread::spawn(move || { 103 | let mut sum = Count { inb: 0, outb: 0 }; 104 | let mut out_buf: Vec = vec![0; length]; 105 | out_buf[length - 1] = b'\n'; 106 | let mut in_buf: Vec = vec![0; length]; 107 | let mut stream = TcpStream::connect(&*address).unwrap(); 108 | 109 | loop { 110 | if (*stop).load(Ordering::Relaxed) { 111 | break; 112 | } 113 | 114 | match stream.write_all(&out_buf) { 115 | Err(_) => { 116 | println!("Write error!"); 117 | break; 118 | } 119 | Ok(_) => sum.outb += 1, 120 | } 121 | 122 | if (*stop).load(Ordering::Relaxed) { 123 | break; 124 | } 125 | 126 | match stream.read(&mut in_buf) { 127 | Err(_) => break, 128 | Ok(m) => { 129 | if m == 0 || m != length { 130 | println!("Read error! length={}", m); 131 | break; 132 | } 133 | } 134 | }; 135 | sum.inb += 1; 136 | } 137 | tx.send(sum).unwrap(); 138 | }); 139 | } 140 | 141 | thread::sleep(Duration::from_secs(duration)); 142 | 143 | match control.upgrade() { 144 | Some(stop) => (*stop).store(true, Ordering::Relaxed), 145 | None => println!("Sorry, but all threads died already."), 146 | } 147 | 148 | let mut sum = Count { inb: 0, outb: 0 }; 149 | for _ in 0..number { 150 | let c: Count = rx.recv().unwrap(); 151 | sum.inb += c.inb; 152 | sum.outb += c.outb; 153 | } 154 | println!("Benchmarking: {}", address); 155 | println!( 156 | "{} clients, running {} bytes, {} sec.", 157 | number, length, duration 158 | ); 159 | println!(); 160 | println!( 161 | "Speed: {} request/sec, {} response/sec", 162 | sum.outb / duration, 163 | sum.inb / duration 164 | ); 165 | println!("Requests: {}", sum.outb); 166 | println!("Responses: {}", sum.inb); 167 | } 168 | --------------------------------------------------------------------------------