├── README.md ├── .gitignore ├── Cargo.toml └── src └── main.rs /README.md: -------------------------------------------------------------------------------- 1 | # cgminer_monitor -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | ipaddr.txt 3 | miner_list.csv 4 | .DS_Store -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cgminer_monitor" 3 | version = "0.1.0" 4 | authors = ["wxLite "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | tokio = "0.2.0" 11 | rayon = "1.2.0" 12 | regex = "1.3.1" 13 | csv = "1.1.1" 14 | serde = "1.0.103" 15 | serde_derive = "1.0.103" 16 | chrono = "0.4.10" 17 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate tokio; 2 | extern crate rayon; 3 | extern crate regex; 4 | #[macro_use] 5 | extern crate serde_derive; 6 | extern crate serde; 7 | extern crate chrono; 8 | 9 | use std::fs::File; 10 | use std::io::prelude::*; 11 | use chrono::prelude::*; 12 | use regex::Regex; 13 | use std::io::{BufReader, BufWriter, Read}; 14 | use std::net::TcpStream; 15 | use std::time::Duration; 16 | use std::path::Path; 17 | use std::process::{Command}; 18 | 19 | #[derive(Debug, Deserialize, Serialize)] 20 | struct Miner { 21 | ip: String, 22 | is_online: bool, 23 | model: String, 24 | ghs_5s: String, 25 | ghs_av: String, 26 | pool_url: String, 27 | pool_user: String, 28 | miner_name: String, 29 | miner_version: String, 30 | version: String, 31 | config: String, 32 | summary: String, 33 | pools: String, 34 | stats: String, 35 | created_at: String 36 | } 37 | 38 | fn info_by_response(buffer: &str) -> Result<(String, String, String, String, String), String>{ 39 | // println!("info_by_response: {}", &buffer); 40 | let mut version: String = "".to_string(); 41 | let mut config: String = "".to_string(); 42 | let mut summary: String = "".to_string(); 43 | let mut pools: String = "".to_string(); 44 | let mut stats: String = "".to_string(); 45 | 46 | let splits: Vec<&str> = buffer.split("CMD=").collect(); 47 | for msg in splits{ 48 | if msg.starts_with("version") { version = msg.to_string() }; 49 | if msg.starts_with("config") { config = msg.to_string() }; 50 | if msg.starts_with("summary") { summary = msg.to_string() }; 51 | if msg.starts_with("pools") { pools = msg.to_string() }; 52 | if msg.starts_with("stats") { stats = msg.to_string() }; 53 | } 54 | 55 | Ok((version.to_string(), config.to_string(), summary.to_string(), pools.to_string(), stats.to_string())) 56 | } 57 | 58 | fn get_miner_from_version(version: &str) -> Result<(String, String), String>{ 59 | // println!("get_miner_from_version: {}", &version); 60 | let re = Regex::new(r"(?m)VERSION,(?P\w+?)=(?P.+?)(,[\w\s]+?=.+?)*$").unwrap(); 61 | let caps = re.captures(&version).unwrap(); 62 | let miner_name = caps["miner_name"].to_string(); 63 | let miner_version = caps["miner_version"].to_string(); 64 | 65 | Ok((miner_name, miner_version)) 66 | } 67 | 68 | fn get_config_from_pools(pools: &str) -> Result<(String, String), String>{ 69 | // println!("get_config_from_pools: {}", &pools); 70 | let re_pools = Regex::new(r"(?m)POOL=0,URL=(?P.+?)(,[\w\s%]+=[\w\s%]+)+,User=(?P.+?),").unwrap(); 71 | let caps_pools = re_pools.captures(&pools).unwrap(); 72 | 73 | let pool_url = &caps_pools["pool_url"]; 74 | let pool_user = &caps_pools["pool_user"]; 75 | 76 | Ok((pool_url.to_string(), pool_user.to_string())) 77 | } 78 | 79 | fn get_model_from_config(config: &str) -> Result{ 80 | // println!("get_model_from_config: {}", &config); 81 | let re_config = Regex::new(r"(?m),Device\sCode=(?P[\w\s]+?)(,.+?=.+?)*$").unwrap(); 82 | let caps_config = re_config.captures(&config).unwrap(); 83 | 84 | Ok(caps_config["model"].to_string()) 85 | } 86 | fn get_model_from_version(version: &str) -> Result{ 87 | // println!("get_model_from_version: {}", &version); 88 | let re_version = Regex::new(r"(?m)Type=(?P.+?)(,.+?=.+?)*$").unwrap(); 89 | let caps_version = re_version.captures(&version).unwrap(); 90 | 91 | Ok(caps_version["model"].to_string()) 92 | } 93 | 94 | fn get_ghs_from_summary_bmminer(summary: &str) -> Result<(f64, f64), String>{ 95 | // println!("get_ghs_from_summary_bmminer: {}", &summary); 96 | let re_summary_bm = Regex::new(r"(?m)GHS\s5s=(?P.*?),GHS\sav=(?P.*?)(,[\w\s]+?=.+?)+$").unwrap(); 97 | let caps_summary_bm = re_summary_bm.captures(&summary).unwrap(); 98 | 99 | let ghs_5s = caps_summary_bm["ghs_5s"].parse::().unwrap_or_default(); 100 | let ghs_av = caps_summary_bm["ghs_av"].parse::().unwrap_or_default(); 101 | 102 | Ok((ghs_5s, ghs_av)) 103 | } 104 | 105 | fn get_ghs_from_summary_cgminer(summary: &str) -> Result<(f64, f64), String>{ 106 | // println!("get_ghs_from_summary_cgminer: {}", &summary); 107 | let re_summary_cg = Regex::new(r"(?m)MHS\sav=(?P.+?),MHS\s5s=(?P.+?)(,[\w\s]+?=.+?)+$").unwrap(); 108 | let caps_summary_cg = re_summary_cg.captures(&summary).unwrap(); 109 | 110 | let ghs_5s = caps_summary_cg["mhs_5s"].parse::().unwrap_or_default() / 1024.0; 111 | let ghs_av = caps_summary_cg["mhs_av"].parse::().unwrap_or_default() / 1024.0; 112 | 113 | Ok((ghs_5s, ghs_av)) 114 | } 115 | 116 | fn get_miner_info(ipaddr: &str) -> Result{ 117 | let host = format!("{0}:{1}", ipaddr, 4028); 118 | if let Ok(mut stream) = TcpStream::connect(host){ 119 | println!("Connected to {}!", &ipaddr); 120 | 121 | let _result = stream.set_read_timeout(Some(Duration::new(5, 0))); 122 | let _result = stream.write_all(b"version+config+summary+pools+stats|\n"); 123 | 124 | let mut buffer = String::new(); 125 | loop { 126 | match stream.read_to_string(&mut buffer) { 127 | Ok(_) => break, 128 | Err(_e) => { 129 | return Err("encountered IO error.".to_string()); 130 | } 131 | }; 132 | } 133 | // println!("msg: {:?}", buffer); 134 | let (version, config, summary, pools, stats) = info_by_response(&buffer).expect("info_by_response error"); 135 | 136 | let mut model = String::new(); 137 | let mut ghs_5s: f64 = 0.0; 138 | let mut ghs_av: f64 = 0.0; 139 | 140 | // version 141 | let (miner_name, miner_version) = get_miner_from_version(&version).expect("get_miner_from_version error"); 142 | println!("miner_name: {} miner_version: {}", &miner_name, &miner_version); 143 | 144 | // pools 145 | let (pool_url, pool_user) = get_config_from_pools(&pools).expect("get_config_from_pools error"); 146 | println!("pool_url: {} pool_user: {}", &pool_url, &pool_user); 147 | 148 | match miner_name.as_ref() { 149 | "CGMiner" => { 150 | // 10.71.3.11 Device Code=SM CGMiner=4.9.2-git-41ffdeb 151 | // config 152 | model = get_model_from_config(&config).expect("get_model_from_config error"); 153 | // let re_config = Regex::new(r"(?m),Device\sCode=(?P[\w\s]+?)(,.+?=.+?)*$").unwrap(); 154 | // let caps_config = re_config.captures(&config).unwrap(); 155 | // 156 | // model = caps_config["model"].to_string(); 157 | 158 | // summary 159 | let _ghs = get_ghs_from_summary_cgminer(&summary).expect("get_ghs_from_summary_cgminer error"); 160 | ghs_5s = _ghs.0; 161 | ghs_av = _ghs.1; 162 | 163 | }, 164 | "BMMiner" => { 165 | // 10.71.51.14 Type=Antminer T9+ 166 | model = get_model_from_version(&version).expect("get_model_from_version error"); 167 | 168 | // summary 169 | let _ghs = get_ghs_from_summary_bmminer(&summary).expect("get_ghs_from_summary_bmminer error"); 170 | ghs_5s = _ghs.0; 171 | ghs_av = _ghs.1; 172 | }, 173 | _ =>{} 174 | } 175 | println!("model: {} ghs_5s: {} ghs_av: {}", &model, &ghs_5s, &ghs_av); 176 | 177 | let miner = Miner { 178 | ip: ipaddr.to_string(), 179 | is_online: true, 180 | model: model.to_string(), 181 | ghs_5s: ghs_5s.to_string(), 182 | ghs_av: ghs_av.to_string(), 183 | pool_url: pool_url.to_string(), 184 | pool_user: pool_user.to_string(), 185 | miner_name: miner_name.to_string(), 186 | miner_version: miner_version.to_string(), 187 | version: version.to_string(), 188 | config: config.to_string(), 189 | summary: summary.to_string(), 190 | pools: pools.to_string(), 191 | stats: stats.to_string(), 192 | created_at: Local::now().to_rfc3339() 193 | }; 194 | Ok(miner) 195 | } else { 196 | Err("Error in connectint to the server...".to_string()) 197 | } 198 | } 199 | 200 | /** 201 | fn test_version() 202 | { 203 | let re_version = Regex::new(r"(?m)\|VERSION,(?P\w+?)=(?P.+?)(,.+?=.+?)*\|").unwrap(); 204 | let version = "STATUS=S,When=1574850226,Code=22,Msg=CGMiner versions,Description=cgminer 4.9.2|VERSION,CGMiner=4.9.2-git-41ffdeb,API=3.7|"; 205 | let caps_version = re_version.captures(&version).unwrap(); 206 | 207 | println!("miner_name: {}, miner_version: {}", &caps_version["miner_name"], &caps_version["miner_version"]); 208 | } 209 | 210 | fn test_config() 211 | { 212 | let re_config = Regex::new(r"(?m),Device\sCode=(?P[\w\s]+?)(,.+?=.+?)*\|").unwrap(); 213 | let config = "STATUS=S,When=1574851419,Code=33,Msg=CGMiner config,Description=cgminer 4.9.2|CONFIG,ASC Count=3,PGA Count=0,Pool Count=3,Strategy=Failover,Log Interval=5,Device Code=SM ,OS=Linux,Hotplug=None|%"; 214 | let caps_config = re_config.captures(&config).unwrap(); 215 | 216 | println!("model: {}", &caps_config["model"]); 217 | } 218 | 219 | fn test_summary(){ 220 | let re = Regex::new(r"(?m)(?P[\w\s%]+?)=(?P[^,]+),*?").unwrap(); 221 | let string = "STATUS=S,When=1573642564,Code=11,Msg=Summary,Description=bmminer 1.0.0|SUMMARY,Elapsed=108985,GHS 5s=10339.40,GHS av=10347.63,Found Blocks=0,Getworks=15852,Accepted=16219,Rejected=34,Hardware Errors=2895,Utility=8.93,Discarded=59053,Stale=0,Get Failures=137,Local Work=4161408,Remote Failures=0,Network Blocks=174,Total MH=1127725920870.0000,Work Utility=144802.16,Difficulty Accepted=262578176.00000000,Difficulty Rejected=442880.00000000,Difficulty Stale=0.00000000,Best Share=636351451,Device Hardware%=0.0011,Device Rejected%=0.1684,Pool Rejected%=0.1684,Pool Stale%=0.0000,Last getwork=1573642564"; 222 | 223 | for caps in re.captures_iter(string) { 224 | println!("key: {}, value: {}", &caps["key"], &caps["value"]); 225 | } 226 | } 227 | 228 | fn test_pools(){ 229 | let re = Regex::new(r"(?m)POOL=0,URL=(?P.+?)(,[\w\s%]+=[\w\s%]+)+,User=(?P.+?),").unwrap(); 230 | let string = "STATUS=S,When=1574756025,Code=7,Msg=3 Pool(s),Description=bmminer 1.0.0|POOL=0,URL=stratum+tcp://61.166.56.36:9778,Status=Alive,Priority=0,Quota=1,Long Poll=N,Getworks=9096,Accepted=15390,Rejected=21,Discarded=87895,Stale=36,Get Failures=68,Remote Failures=2,User=bitmory.001.10x71x51x13,Last Share Time=0:00:42,Diff=32.8K,Diff1 Shares=0,Proxy Type=,Proxy=,Difficulty Accepted=392624139.00000000,Difficulty Rejected=507906.00000000,Difficulty Stale=507904.00000000,Last Share Difficulty=32768.00000000,Has Stratum=true,Stratum Active=true,Stratum URL=61.166.56.36,Has GBT=false,Best Share=2584444801,Pool Rejected%=0.1290,Pool Stale%=0.1290|POOL=1,URL=stratum+tcp://btc.ss.poolin.com:443,Status=Alive,Priority=1,Quota=1,Long Poll=N,Getworks=3317,Accepted=6886,Rejected=10,Discarded=48408,Stale=2,Get Failures=0,Remote Failures=0,User=bitmory.001.10x71x51x13,Last Share Time=3:55:14,Diff=32.8K,Diff1 Shares=0,Proxy Type=,Proxy=,Difficulty Accepted=225640448.00000000,Difficulty Rejected=327680.00000000,Difficulty Stale=0.00000000,Last Share Difficulty=32768.00000000,Has Stratum=true,Stratum Active=false,Stratum URL=,Has GBT=false,Best Share=146776122,Pool Rejected%=0.1450,Pool Stale%=0.0000|POOL=2,URL=stratum+tcp://stratum+tls://btc.ss.poolin.com:5222,Status=Dead,Priority=2,Quota=1,Long Poll=N,Getworks=0,Accepted=0,Rejected=0,Discarded=0,Stale=0,Get Failures=0,Remote Failures=0,User=bitmory.001.10x71x51x13,Last Share Time=0,Diff=,Diff1 Shares=0,Proxy Type=,Proxy=,Difficulty Accepted=0.00000000,Difficulty Rejected=0.00000000,Difficulty Stale=0.00000000,Last Share Difficulty=0.00000000,Has Stratum=true,Stratum Active=false,Stratum URL=,Has GBT=false,Best Share=0,Pool Rejected%=0.0000,Pool Stale%=0.0000"; 231 | let caps = re.captures(&string).unwrap(); 232 | 233 | println!("pool_url: {}, pool_user:{}", &caps["pool_url"], &caps["pool_user"]); 234 | } 235 | 236 | fn test_stats(){ 237 | let re = Regex::new(r"(?m)GHS\s5s=(?P.*?),GHS\sav=(?P.*?)(,[\w\s]+?=.+?)+$").unwrap(); 238 | let string = r"STATUS=S,When=1574905855,Code=11,Msg=Summary,Description=bmminer 1.0.0|SUMMARY,Elapsed=146,GHS 5s=,GHS av=3200.63,Found Blocks=0,Getworks=8,Accepted=4,Rejected=0,Hardware Errors=0,Utility=1.64,Discarded=79,Stale=0,Get Failures=0,Local Work=1747,Remote Failures=0,Network Blocks=1,Total MH=467292234.0000,Work Utility=53865.21,Difficulty Accepted=131072.00000000,Difficulty Rejected=0.00000000,Difficulty Stale=0.00000000,Best Share=188624,Device Hardware%=0.0000,Device Rejected%=0.0000,Pool Rejected%=0.0000,Pool Stale%=0.0000,Last getwork=1574905768"; 239 | let caps = re.captures(&string).unwrap(); 240 | 241 | println!("ghs_5s:{}, ghs_av: {}", &caps["ghs_5s"].parse::().unwrap_or_default(), &caps["ghs_av"].parse::().unwrap_or_default()); 242 | } 243 | **/ 244 | 245 | fn main() { 246 | // let mut miner_list: Vec = Vec::new(); 247 | println!("started at {}", Local::now().to_rfc3339()); 248 | 249 | let file_name= "ipaddr.txt"; 250 | if !Path::new(file_name).exists(){ 251 | let f = File::create(&file_name).unwrap(); 252 | { 253 | let mut writer = BufWriter::new(f); 254 | 255 | // nmap -sS 20.15.11.2-16 -p 4028 256 | let output = Command::new("nmap") 257 | // .arg("-PS 10.40.116.2-254").arg("-p 4028") 258 | .arg(r"-v") 259 | .arg(r"-n") 260 | // .arg(r"127.0.0.1") 261 | // .arg(r"10.71.51.12-20") 262 | // .arg(r"10.71.3.11") 263 | // .arg(r"10.71.51.14") 264 | // .arg(r"10.71.7.83") 265 | // .arg(r"10.71.9.19") 266 | // .arg(r"10.81.43.124") 267 | .arg(r"10.81.12.96") 268 | // .arg(r"10.71.1-99.2-254") 269 | // .arg(r"10.81.1-99.2-254") 270 | // .arg(r"10.91.1-99.2-254") 271 | .arg(r"-PS") 272 | .arg(r"-p 4028") 273 | // .arg(r"-p 4028") 274 | .output().unwrap(); 275 | 276 | for line in output.stdout.lines() { 277 | match line { 278 | Ok(msg) => { 279 | // Nmap scan report for 192.168.2.254 [host down] 280 | // Discovered open port 8080/tcp on 192.168.1.4 281 | let re = Regex::new(r"(?m)^Discovered open port (\d+?)/tcp on (?P[\d.]+?)$").unwrap(); 282 | for caps in re.captures_iter(&msg) { 283 | let ipaddr = &caps["ipaddr"]; 284 | writer.write_fmt(format_args!("{}\n",ipaddr)).unwrap(); 285 | } 286 | }, 287 | Err(_e) => panic!("encountered IO error: {}", _e), 288 | }; 289 | } 290 | } 291 | } 292 | 293 | // let mut miner_list: Vec = Vec::new(); 294 | println!("scan finished at {}", Local::now().to_rfc3339()); 295 | 296 | let path = "miner_list.csv"; 297 | let mut writer = csv::Writer::from_path(path).unwrap(); 298 | 299 | let f = File::open(file_name).unwrap(); 300 | { 301 | let mut reader = BufReader::new(f); 302 | let mut buffer = String::new(); 303 | 304 | while reader.read_line(&mut buffer).unwrap() > 0 { 305 | let ipaddr = buffer.trim(); 306 | println!("ipaddr: {}", ipaddr); 307 | 308 | match get_miner_info(&ipaddr) { 309 | Ok(miner) => { 310 | // miner_list.push(miner); 311 | writer.serialize(miner).expect("CSV writer error"); 312 | writer.flush().expect("Flush error"); 313 | } 314 | Err(_e) => {} 315 | } 316 | 317 | buffer.clear(); 318 | // std::thread::sleep( Duration::new(0, 200) ); 319 | } 320 | } 321 | 322 | 323 | println!("ended at {}", Local::now().to_rfc3339()); 324 | } 325 | 326 | --------------------------------------------------------------------------------