├── .gitignore ├── Cargo.toml ├── src ├── tcp_connection.rs ├── fake_data_generator.rs ├── main.rs └── console.rs ├── README.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | /target 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fake-log-generator" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | fake = "2.6" 10 | rand = "0.8" 11 | serde = { version = "1.0.193", features = ["derive"] } 12 | toml = "0.8.8" 13 | -------------------------------------------------------------------------------- /src/tcp_connection.rs: -------------------------------------------------------------------------------- 1 | use std::net::{Ipv4Addr, TcpStream}; 2 | 3 | pub fn create_connections(ip: Ipv4Addr, ports: Vec) -> Vec { 4 | let mut data: Vec = Vec::new(); 5 | 6 | for port in ports { 7 | let stream = TcpStream::connect((ip, port)).expect("Failed to connect to server"); 8 | data.push(stream); 9 | } 10 | 11 | data 12 | } 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fake Log Generator 2 | 3 | > [!NOTE] 4 | > 5 | > It's a project for me to practice Rust. 6 | 7 | This is a fake json log generator for testing purposes. 8 | It generates fake logs in the following format: 9 | 10 | ```json 11 | { 12 | "id": "1", 13 | "company": "example inc.", 14 | "name": "John Doe", 15 | "age": 24, 16 | "address": "Amsterdam, Netherlands", 17 | "phone": "+31 6 12345678", 18 | "email": "example@email.com", 19 | "introduction": "Hello, my name is John Doe and I'm a software engineer.", 20 | "timestamp": "2020-01-01T00:00:00.000Z" 21 | } 22 | ``` 23 | 24 | > [!NOTE] 25 | > 26 | > Currently, it can only be used to send fixed-format logs. 27 | 28 | ## Usage 29 | 30 | ```text 31 | This is a simple TCP client that sends fake data to a TCP server. 32 | 33 | Usage: 34 | --host, -h . Default is 127.0.0.1 35 | --ports, -p . Default is 80 36 | --number, -n . Default is 10,000 37 | ``` 38 | -------------------------------------------------------------------------------- /src/fake_data_generator.rs: -------------------------------------------------------------------------------- 1 | use fake::Fake; 2 | use fake::faker::address::en::*; 3 | use fake::faker::company::en::CompanyName; 4 | use fake::faker::internet::en::SafeEmail; 5 | use fake::faker::lorem::en::Paragraph; 6 | use fake::faker::name::en::*; 7 | use fake::faker::phone_number::raw::PhoneNumber; 8 | use fake::locales::*; 9 | 10 | pub struct Employee { 11 | id: u32, 12 | company: String, 13 | name: String, 14 | age: u8, 15 | address: String, 16 | phone: String, 17 | email: String, 18 | introduction: String, 19 | } 20 | 21 | pub fn generate_fake_data() -> String { 22 | let employee = Employee { 23 | id: (1..1_000_000_000).fake(), 24 | company: CompanyName().fake(), 25 | name: Name().fake(), 26 | age: (1..65).fake(), 27 | address: CityName().fake(), 28 | phone: PhoneNumber(EN).fake(), 29 | email: SafeEmail().fake(), 30 | introduction: Paragraph(30..31).fake(), 31 | }; 32 | 33 | // 產生 JSON 格式的資料 34 | let message = format!("{{ \ 35 | \"id\":\"{}\", \ 36 | \"company\":\"{}\", \ 37 | \"name\":\"{}\", \ 38 | \"age\":\"{}\", \ 39 | \"address\":\"{}\", \ 40 | \"phone\":\"{}\", \ 41 | \"email\":\"{}\", \ 42 | \"introduction\":\"{}\" \ 43 | }}", 44 | employee.id, 45 | employee.company, 46 | employee.name, 47 | employee.age, 48 | employee.address, 49 | employee.phone, 50 | employee.email, 51 | employee.introduction 52 | ); 53 | 54 | message 55 | } -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::io::Write; 3 | use std::net::Ipv4Addr; 4 | use std::time::Instant; 5 | 6 | use rand::Rng; 7 | 8 | pub mod tcp_connection; 9 | pub mod fake_data_generator; 10 | mod console; 11 | 12 | fn main() { 13 | // get the current time, used to calculate elapsed time 14 | let instant = Instant::now(); 15 | 16 | let args: Vec = env::args().collect(); 17 | 18 | let (host, ports, total_requests) = console::handler(args); 19 | 20 | let total_bytes = send_logs(host, ports, total_requests); 21 | 22 | execute_information(instant, total_requests, total_bytes); 23 | } 24 | 25 | fn send_logs(host: Ipv4Addr, ports: Vec, total_request: usize) -> usize { 26 | let mut streams = tcp_connection::create_connections( 27 | host, 28 | ports, 29 | ); 30 | 31 | let mut rng = rand::thread_rng(); 32 | 33 | let mut total_bytes = 0; 34 | 35 | for i in 0..total_request { 36 | let message = fake_data_generator::generate_fake_data(); 37 | 38 | // randomize which stream to write to 39 | let random_index = rng.gen_range(0..streams.len()); 40 | streams[random_index].write(message.as_bytes()).expect("Failed to write to stream"); 41 | 42 | total_bytes += message.len(); 43 | 44 | // print every 1000 requests 45 | if i % 1000 == 0 && i != 0 { 46 | println!("Requests sent: {}", i); 47 | } 48 | } 49 | 50 | total_bytes 51 | } 52 | 53 | fn execute_information(instance: Instant, total_requests: usize, total_bytes: usize) { 54 | // print total requests 55 | println!("Total requests: {}", total_requests); 56 | // print elapsed time 57 | println!("Elapsed time: {:.2?}", instance.elapsed()); 58 | // print total mb sent 59 | println!("Total sent: {} MB", total_bytes / 1_000_000); 60 | 61 | if instance.elapsed().as_secs() > 0 { 62 | // print request per second 63 | println!("Requests per second: {}", total_requests / (instance.elapsed().as_secs() as usize)); 64 | } 65 | } 66 | 67 | -------------------------------------------------------------------------------- /src/console.rs: -------------------------------------------------------------------------------- 1 | use std::net::Ipv4Addr; 2 | 3 | pub fn print_help_message() { 4 | println!("This is a simple TCP client that sends fake data to a TCP server.\n"); 5 | println!("Usage:"); 6 | println!("--host, -h . Required."); 7 | println!("--ports, -p . Default is 80."); 8 | println!("--number, -n . Default is 10000."); 9 | } 10 | 11 | pub fn handler(args: Vec) -> (Ipv4Addr, Vec, usize) { 12 | let mut ip4v_address = Ipv4Addr::new(0, 0, 0, 0); 13 | let mut target_ports = vec![80]; 14 | let mut number_of_requests: usize = 10000; 15 | 16 | // if there are no arguments, print the help message 17 | if args.len() == 1 { 18 | print_help_message(); 19 | 20 | std::process::exit(1); 21 | } 22 | 23 | // remove the first argument, which is the program itself 24 | let args: Vec = args[1..].to_vec(); 25 | 26 | // for loop all the arguments 27 | for (i, arg) in args.iter().enumerate() { 28 | if arg == "--host" || arg == "-h" { 29 | let host = args.get(i + 1); 30 | 31 | let host = match host { 32 | Some(host) => host, 33 | None => { 34 | print_help_message(); 35 | 36 | panic!("There is no host after the --host flag"); 37 | } 38 | }; 39 | 40 | match host.parse::() { 41 | Ok(_) => { 42 | ip4v_address = host.parse().unwrap(); 43 | } 44 | Err(_) => { 45 | print_help_message(); 46 | 47 | panic!("Host is not a valid IPv4 address"); 48 | } 49 | } 50 | } 51 | 52 | if arg == "--ports" || arg == "-p" { 53 | let ports = args.get(i + 1); 54 | 55 | let ports = match ports { 56 | Some(ports) => ports, 57 | None => { 58 | print_help_message(); 59 | 60 | panic!("There is no ports number after the --port flag"); 61 | } 62 | }; 63 | 64 | target_ports = ports.split(",") 65 | .map(|port| { 66 | let port = port.parse::(); 67 | 68 | let port = match port { 69 | Ok(port) => port, 70 | Err(_) => { 71 | print_help_message(); 72 | 73 | panic!("Port is not a valid number"); 74 | } 75 | }; 76 | 77 | // port must be between 1 and 65535 78 | // u16 max value is 65535, we don't have to check for that 79 | if port < 1 { 80 | println!("Port {} is not valid", port); 81 | std::process::exit(1); 82 | } 83 | 84 | port 85 | }) 86 | .collect(); 87 | } 88 | 89 | if arg == "--number" || arg == "-n" { 90 | let number = args.get(i + 1); 91 | 92 | let number = match number { 93 | Some(number) => number, 94 | None => { 95 | print_help_message(); 96 | 97 | panic!("There is no number after the --number flag"); 98 | } 99 | }; 100 | 101 | match number.parse::() { 102 | Ok(_) => { 103 | number_of_requests = number.parse().unwrap(); 104 | } 105 | Err(_) => { 106 | print_help_message(); 107 | 108 | panic!("Number is not a valid number"); 109 | } 110 | } 111 | } 112 | } 113 | 114 | if ip4v_address == Ipv4Addr::new(0, 0, 0, 0) { 115 | print_help_message(); 116 | 117 | panic!("There is no host after the --host flag"); 118 | } 119 | 120 | 121 | return (ip4v_address, target_ports, number_of_requests); 122 | } 123 | 124 | // A test for arguments_handler 125 | #[cfg(test)] 126 | mod tests { 127 | use std::net::Ipv4Addr; 128 | 129 | use crate::console::handler; 130 | 131 | #[test] 132 | #[should_panic(expected = "There is no host after the --host flag")] 133 | fn it_should_have_a_args_after_host_flag() { 134 | let args: Vec = vec![ 135 | String::from("program_itself"), 136 | String::from("--host"), 137 | ]; 138 | 139 | handler(args); 140 | } 141 | 142 | #[test] 143 | #[should_panic(expected = "There is no host after the --host flag")] 144 | fn it_should_have_a_args_after_host_flag_2() { 145 | let args: Vec = vec![ 146 | String::from("program_itself"), 147 | String::from("-h"), 148 | ]; 149 | 150 | handler(args); 151 | } 152 | 153 | #[test] 154 | fn it_should_return_the_host() { 155 | let args: Vec = vec![ 156 | String::from("program_itself"), 157 | String::from("--host"), 158 | String::from("127.0.0.1"), 159 | ]; 160 | 161 | let (hosts, ports, total_requests) = handler(args); 162 | 163 | assert_eq!(hosts, Ipv4Addr::new(127, 0, 0, 1)); 164 | } 165 | 166 | #[test] 167 | #[should_panic] 168 | fn it_should_be_a_valid_ip() { 169 | let args: Vec = vec![ 170 | String::from("program_itself"), 171 | String::from("--host"), 172 | String::from("hello!"), 173 | ]; 174 | 175 | handler(args); 176 | } 177 | 178 | #[test] 179 | #[should_panic(expected = "There is no ports number after the --port flag")] 180 | fn it_should_have_a_args_after_ports_flag() { 181 | let args: Vec = vec![ 182 | String::from("program_itself"), 183 | String::from("--ports"), 184 | ]; 185 | 186 | handler(args); 187 | } 188 | 189 | #[test] 190 | #[should_panic(expected = "There is no ports number after the --port flag")] 191 | fn it_should_have_a_args_after_ports_flag_2() { 192 | let args: Vec = vec![ 193 | String::from("program_itself"), 194 | String::from("-p"), 195 | ]; 196 | 197 | handler(args); 198 | } 199 | 200 | #[test] 201 | fn it_should_return_the_port() { 202 | let args: Vec = vec![ 203 | String::from("program_itself"), 204 | String::from("--host"), 205 | String::from("127.0.0.1"), 206 | String::from("--ports"), 207 | String::from("80,443"), 208 | ]; 209 | 210 | let (hosts, ports, total_requests) = handler(args); 211 | 212 | assert_eq!(ports, vec![80, 443]); 213 | } 214 | } -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "byteorder" 7 | version = "1.5.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 10 | 11 | [[package]] 12 | name = "cfg-if" 13 | version = "1.0.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 16 | 17 | [[package]] 18 | name = "deunicode" 19 | version = "1.6.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "339544cc9e2c4dc3fc7149fd630c5f22263a4fdf18a98afd0075784968b5cf00" 22 | 23 | [[package]] 24 | name = "equivalent" 25 | version = "1.0.1" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 28 | 29 | [[package]] 30 | name = "fake" 31 | version = "2.9.2" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "1c25829bde82205da46e1823b2259db6273379f626fc211f126f65654a2669be" 34 | dependencies = [ 35 | "deunicode", 36 | "rand", 37 | ] 38 | 39 | [[package]] 40 | name = "fake-log-generator" 41 | version = "0.1.0" 42 | dependencies = [ 43 | "fake", 44 | "rand", 45 | "serde", 46 | "toml", 47 | ] 48 | 49 | [[package]] 50 | name = "getrandom" 51 | version = "0.2.15" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 54 | dependencies = [ 55 | "cfg-if", 56 | "libc", 57 | "wasi", 58 | ] 59 | 60 | [[package]] 61 | name = "hashbrown" 62 | version = "0.14.5" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 65 | 66 | [[package]] 67 | name = "indexmap" 68 | version = "2.3.0" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" 71 | dependencies = [ 72 | "equivalent", 73 | "hashbrown", 74 | ] 75 | 76 | [[package]] 77 | name = "libc" 78 | version = "0.2.155" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" 81 | 82 | [[package]] 83 | name = "memchr" 84 | version = "2.7.4" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 87 | 88 | [[package]] 89 | name = "ppv-lite86" 90 | version = "0.2.18" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "dee4364d9f3b902ef14fab8a1ddffb783a1cb6b4bba3bfc1fa3922732c7de97f" 93 | dependencies = [ 94 | "zerocopy", 95 | ] 96 | 97 | [[package]] 98 | name = "proc-macro2" 99 | version = "1.0.86" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 102 | dependencies = [ 103 | "unicode-ident", 104 | ] 105 | 106 | [[package]] 107 | name = "quote" 108 | version = "1.0.36" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 111 | dependencies = [ 112 | "proc-macro2", 113 | ] 114 | 115 | [[package]] 116 | name = "rand" 117 | version = "0.8.5" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 120 | dependencies = [ 121 | "libc", 122 | "rand_chacha", 123 | "rand_core", 124 | ] 125 | 126 | [[package]] 127 | name = "rand_chacha" 128 | version = "0.3.1" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 131 | dependencies = [ 132 | "ppv-lite86", 133 | "rand_core", 134 | ] 135 | 136 | [[package]] 137 | name = "rand_core" 138 | version = "0.6.4" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 141 | dependencies = [ 142 | "getrandom", 143 | ] 144 | 145 | [[package]] 146 | name = "serde" 147 | version = "1.0.204" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" 150 | dependencies = [ 151 | "serde_derive", 152 | ] 153 | 154 | [[package]] 155 | name = "serde_derive" 156 | version = "1.0.204" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" 159 | dependencies = [ 160 | "proc-macro2", 161 | "quote", 162 | "syn", 163 | ] 164 | 165 | [[package]] 166 | name = "serde_spanned" 167 | version = "0.6.7" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" 170 | dependencies = [ 171 | "serde", 172 | ] 173 | 174 | [[package]] 175 | name = "syn" 176 | version = "2.0.72" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" 179 | dependencies = [ 180 | "proc-macro2", 181 | "quote", 182 | "unicode-ident", 183 | ] 184 | 185 | [[package]] 186 | name = "toml" 187 | version = "0.8.19" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" 190 | dependencies = [ 191 | "serde", 192 | "serde_spanned", 193 | "toml_datetime", 194 | "toml_edit", 195 | ] 196 | 197 | [[package]] 198 | name = "toml_datetime" 199 | version = "0.6.8" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" 202 | dependencies = [ 203 | "serde", 204 | ] 205 | 206 | [[package]] 207 | name = "toml_edit" 208 | version = "0.22.20" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" 211 | dependencies = [ 212 | "indexmap", 213 | "serde", 214 | "serde_spanned", 215 | "toml_datetime", 216 | "winnow", 217 | ] 218 | 219 | [[package]] 220 | name = "unicode-ident" 221 | version = "1.0.12" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 224 | 225 | [[package]] 226 | name = "wasi" 227 | version = "0.11.0+wasi-snapshot-preview1" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 230 | 231 | [[package]] 232 | name = "winnow" 233 | version = "0.6.18" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" 236 | dependencies = [ 237 | "memchr", 238 | ] 239 | 240 | [[package]] 241 | name = "zerocopy" 242 | version = "0.6.6" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "854e949ac82d619ee9a14c66a1b674ac730422372ccb759ce0c39cabcf2bf8e6" 245 | dependencies = [ 246 | "byteorder", 247 | "zerocopy-derive", 248 | ] 249 | 250 | [[package]] 251 | name = "zerocopy-derive" 252 | version = "0.6.6" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "125139de3f6b9d625c39e2efdd73d41bdac468ccd556556440e322be0e1bbd91" 255 | dependencies = [ 256 | "proc-macro2", 257 | "quote", 258 | "syn", 259 | ] 260 | --------------------------------------------------------------------------------