add status request counters
Authored by
mfwolffe <wolffemf@dukes.jmu.edu>
- SHA
8060762e00ac3772f5daca7c903fdc48e53f8871- Parents
-
c0fb997 - Tree
736ca2f
8060762
8060762e00ac3772f5daca7c903fdc48e53f8871c0fb997
736ca2f| Status | File | + | - |
|---|---|---|---|
| M |
README.md
|
1 | 1 |
| M |
garwarp-ipc/src/lib.rs
|
34 | 3 |
| M |
garwarp/src/daemon.rs
|
4 | 0 |
| M |
garwarp/src/request.rs
|
32 | 0 |
| M |
garwarpctl/src/main.rs
|
2 | 0 |
README.mdmodified@@ -18,7 +18,7 @@ Current scaffold includes: | |||
| 18 | 18 | ||
| 19 | ## Local Commands | 19 | ## Local Commands |
| 20 | 1. Start daemon: `cargo run -p garwarp -- daemon` | 20 | 1. Start daemon: `cargo run -p garwarp -- daemon` |
| 21 | -2. Check health: `cargo run -p garwarpctl -- status` | 21 | +2. Check health and request counters: `cargo run -p garwarpctl -- status` |
| 22 | 3. Stop daemon: `cargo run -p garwarpctl -- stop` | 22 | 3. Stop daemon: `cargo run -p garwarpctl -- stop` |
| 23 | 4. Verify D-Bus activation: `./scripts/test-dbus-activation.sh` | 23 | 4. Verify D-Bus activation: `./scripts/test-dbus-activation.sh` |
| 24 | 5. Create mock request: `cargo run -p garwarpctl -- begin req-1 :1.2 - x11:0x2a` | 24 | 5. Create mock request: `cargo run -p garwarpctl -- begin req-1 :1.2 - x11:0x2a` |
garwarp-ipc/src/lib.rsmodified@@ -198,6 +198,8 @@ pub struct StatusResponse { | |||
| 198 | pub protocol_version: u16, | 198 | pub protocol_version: u16, |
| 199 | pub health: HealthStatus, | 199 | pub health: HealthStatus, |
| 200 | pub in_flight_requests: usize, | 200 | pub in_flight_requests: usize, |
| 201 | + pub total_requests: usize, | ||
| 202 | + pub terminal_requests: usize, | ||
| 201 | } | 203 | } |
| 202 | 204 | ||
| 203 | impl StatusResponse { | 205 | impl StatusResponse { |
@@ -207,6 +209,8 @@ impl StatusResponse { | |||
| 207 | protocol_version: PROTOCOL_VERSION, | 209 | protocol_version: PROTOCOL_VERSION, |
| 208 | health: HealthStatus::Healthy, | 210 | health: HealthStatus::Healthy, |
| 209 | in_flight_requests: 0, | 211 | in_flight_requests: 0, |
| 212 | + total_requests: 0, | ||
| 213 | + terminal_requests: 0, | ||
| 210 | } | 214 | } |
| 211 | } | 215 | } |
| 212 | } | 216 | } |
@@ -240,10 +244,12 @@ impl ControlResponse { | |||
| 240 | pub fn to_line(&self) -> String { | 244 | pub fn to_line(&self) -> String { |
| 241 | match self { | 245 | match self { |
| 242 | Self::Status(status) => format!( | 246 | Self::Status(status) => format!( |
| 243 | - "status protocol={} health={} in_flight={}\n", | 247 | + "status protocol={} health={} in_flight={} total={} terminal={}\n", |
| 244 | status.protocol_version, | 248 | status.protocol_version, |
| 245 | status.health.as_str(), | 249 | status.health.as_str(), |
| 246 | - status.in_flight_requests | 250 | + status.in_flight_requests, |
| 251 | + status.total_requests, | ||
| 252 | + status.terminal_requests | ||
| 247 | ), | 253 | ), |
| 248 | Self::AckStopping => "ack stopping\n".to_string(), | 254 | Self::AckStopping => "ack stopping\n".to_string(), |
| 249 | Self::RequestList { ids } => { | 255 | Self::RequestList { ids } => { |
@@ -284,6 +290,8 @@ impl ControlResponse { | |||
| 284 | let mut protocol_version = None; | 290 | let mut protocol_version = None; |
| 285 | let mut health = None; | 291 | let mut health = None; |
| 286 | let mut in_flight_requests = None; | 292 | let mut in_flight_requests = None; |
| 293 | + let mut total_requests = None; | ||
| 294 | + let mut terminal_requests = None; | ||
| 287 | 295 | ||
| 288 | for part in parts { | 296 | for part in parts { |
| 289 | let (key, value) = part | 297 | let (key, value) = part |
@@ -310,6 +318,20 @@ impl ControlResponse { | |||
| 310 | .map_err(|_| ParseError::InvalidField(part.to_string()))?, | 318 | .map_err(|_| ParseError::InvalidField(part.to_string()))?, |
| 311 | ); | 319 | ); |
| 312 | } | 320 | } |
| 321 | + "total" => { | ||
| 322 | + total_requests = Some( | ||
| 323 | + value | ||
| 324 | + .parse::<usize>() | ||
| 325 | + .map_err(|_| ParseError::InvalidField(part.to_string()))?, | ||
| 326 | + ); | ||
| 327 | + } | ||
| 328 | + "terminal" => { | ||
| 329 | + terminal_requests = Some( | ||
| 330 | + value | ||
| 331 | + .parse::<usize>() | ||
| 332 | + .map_err(|_| ParseError::InvalidField(part.to_string()))?, | ||
| 333 | + ); | ||
| 334 | + } | ||
| 313 | _ => return Err(ParseError::InvalidField(part.to_string())), | 335 | _ => return Err(ParseError::InvalidField(part.to_string())), |
| 314 | } | 336 | } |
| 315 | } | 337 | } |
@@ -320,6 +342,9 @@ impl ControlResponse { | |||
| 320 | health: health.ok_or(ParseError::MissingField("health"))?, | 342 | health: health.ok_or(ParseError::MissingField("health"))?, |
| 321 | in_flight_requests: in_flight_requests | 343 | in_flight_requests: in_flight_requests |
| 322 | .ok_or(ParseError::MissingField("in_flight"))?, | 344 | .ok_or(ParseError::MissingField("in_flight"))?, |
| 345 | + total_requests: total_requests.ok_or(ParseError::MissingField("total"))?, | ||
| 346 | + terminal_requests: terminal_requests | ||
| 347 | + .ok_or(ParseError::MissingField("terminal"))?, | ||
| 323 | }; | 348 | }; |
| 324 | Ok(Self::Status(status)) | 349 | Ok(Self::Status(status)) |
| 325 | } | 350 | } |
@@ -527,6 +552,8 @@ mod tests { | |||
| 527 | protocol_version: PROTOCOL_VERSION, | 552 | protocol_version: PROTOCOL_VERSION, |
| 528 | health: HealthStatus::Healthy, | 553 | health: HealthStatus::Healthy, |
| 529 | in_flight_requests: 7, | 554 | in_flight_requests: 7, |
| 555 | + total_requests: 10, | ||
| 556 | + terminal_requests: 3, | ||
| 530 | }); | 557 | }); |
| 531 | let line = response.to_line(); | 558 | let line = response.to_line(); |
| 532 | let parsed = ControlResponse::parse_line(&line).expect("response should parse"); | 559 | let parsed = ControlResponse::parse_line(&line).expect("response should parse"); |
@@ -569,11 +596,15 @@ mod tests { | |||
| 569 | assert_eq!(response.protocol_version, PROTOCOL_VERSION); | 596 | assert_eq!(response.protocol_version, PROTOCOL_VERSION); |
| 570 | assert_eq!(response.health, HealthStatus::Healthy); | 597 | assert_eq!(response.health, HealthStatus::Healthy); |
| 571 | assert_eq!(response.in_flight_requests, 0); | 598 | assert_eq!(response.in_flight_requests, 0); |
| 599 | + assert_eq!(response.total_requests, 0); | ||
| 600 | + assert_eq!(response.terminal_requests, 0); | ||
| 572 | } | 601 | } |
| 573 | 602 | ||
| 574 | #[test] | 603 | #[test] |
| 575 | fn malformed_status_is_rejected() { | 604 | fn malformed_status_is_rejected() { |
| 576 | - let parsed = ControlResponse::parse_line("status protocol=one health=healthy in_flight=0"); | 605 | + let parsed = ControlResponse::parse_line( |
| 606 | + "status protocol=one health=healthy in_flight=0 total=0 terminal=0", | ||
| 607 | + ); | ||
| 577 | assert!(parsed.is_err()); | 608 | assert!(parsed.is_err()); |
| 578 | } | 609 | } |
| 579 | 610 | ||
garwarp/src/daemon.rsmodified@@ -117,6 +117,8 @@ fn handle_connection(stream: UnixStream, state: &mut DaemonState) -> io::Result< | |||
| 117 | protocol_version: garwarp_ipc::PROTOCOL_VERSION, | 117 | protocol_version: garwarp_ipc::PROTOCOL_VERSION, |
| 118 | health: state.health, | 118 | health: state.health, |
| 119 | in_flight_requests: state.requests.in_flight_count(), | 119 | in_flight_requests: state.requests.in_flight_count(), |
| 120 | + total_requests: state.requests.total_count(), | ||
| 121 | + terminal_requests: state.requests.terminal_count(), | ||
| 120 | }), | 122 | }), |
| 121 | Some(ControlRequest::Stop) => { | 123 | Some(ControlRequest::Stop) => { |
| 122 | state.health = HealthStatus::Stopping; | 124 | state.health = HealthStatus::Stopping; |
@@ -388,6 +390,8 @@ mod tests { | |||
| 388 | ControlResponse::Status(status) => { | 390 | ControlResponse::Status(status) => { |
| 389 | assert_eq!(status.health, HealthStatus::Healthy); | 391 | assert_eq!(status.health, HealthStatus::Healthy); |
| 390 | assert_eq!(status.in_flight_requests, 1); | 392 | assert_eq!(status.in_flight_requests, 1); |
| 393 | + assert_eq!(status.total_requests, 1); | ||
| 394 | + assert_eq!(status.terminal_requests, 0); | ||
| 391 | } | 395 | } |
| 392 | _ => panic!("expected status response"), | 396 | _ => panic!("expected status response"), |
| 393 | } | 397 | } |
garwarp/src/request.rsmodified@@ -264,6 +264,19 @@ impl RequestRegistry { | |||
| 264 | .count() | 264 | .count() |
| 265 | } | 265 | } |
| 266 | 266 | ||
| 267 | + #[must_use] | ||
| 268 | + pub fn total_count(&self) -> usize { | ||
| 269 | + self.entries.len() | ||
| 270 | + } | ||
| 271 | + | ||
| 272 | + #[must_use] | ||
| 273 | + pub fn terminal_count(&self) -> usize { | ||
| 274 | + self.entries | ||
| 275 | + .values() | ||
| 276 | + .filter(|entry| entry.state.is_terminal()) | ||
| 277 | + .count() | ||
| 278 | + } | ||
| 279 | + | ||
| 267 | #[must_use] | 280 | #[must_use] |
| 268 | pub fn state(&self, id: &str) -> Option<RequestState> { | 281 | pub fn state(&self, id: &str) -> Option<RequestState> { |
| 269 | self.entries.get(id).map(|entry| entry.state) | 282 | self.entries.get(id).map(|entry| entry.state) |
@@ -562,6 +575,25 @@ mod tests { | |||
| 562 | assert_eq!(registry.state("req-1"), Some(RequestState::Pending)); | 575 | assert_eq!(registry.state("req-1"), Some(RequestState::Pending)); |
| 563 | } | 576 | } |
| 564 | 577 | ||
| 578 | + #[test] | ||
| 579 | + fn count_helpers_return_expected_values() { | ||
| 580 | + let now = Instant::now(); | ||
| 581 | + let mut registry = RequestRegistry::new(Duration::from_secs(5)); | ||
| 582 | + registry | ||
| 583 | + .begin_at("req-1", owner(":1.2"), None, now) | ||
| 584 | + .expect("request should be created"); | ||
| 585 | + registry | ||
| 586 | + .begin_at("req-2", owner(":1.3"), None, now) | ||
| 587 | + .expect("request should be created"); | ||
| 588 | + registry | ||
| 589 | + .transition_at("req-2", &owner(":1.3"), RequestState::Cancelled, now) | ||
| 590 | + .expect("request should transition"); | ||
| 591 | + | ||
| 592 | + assert_eq!(registry.total_count(), 2); | ||
| 593 | + assert_eq!(registry.in_flight_count(), 1); | ||
| 594 | + assert_eq!(registry.terminal_count(), 1); | ||
| 595 | + } | ||
| 596 | + | ||
| 565 | #[test] | 597 | #[test] |
| 566 | fn duplicate_cancel_is_idempotent() { | 598 | fn duplicate_cancel_is_idempotent() { |
| 567 | let now = Instant::now(); | 599 | let now = Instant::now(); |
garwarpctl/src/main.rsmodified@@ -173,6 +173,8 @@ fn run(command: Command) -> io::Result<()> { | |||
| 173 | println!("protocol={}", status.protocol_version); | 173 | println!("protocol={}", status.protocol_version); |
| 174 | println!("health={}", status.health.as_str()); | 174 | println!("health={}", status.health.as_str()); |
| 175 | println!("in_flight={}", status.in_flight_requests); | 175 | println!("in_flight={}", status.in_flight_requests); |
| 176 | + println!("total={}", status.total_requests); | ||
| 177 | + println!("terminal={}", status.terminal_requests); | ||
| 176 | Ok(()) | 178 | Ok(()) |
| 177 | } | 179 | } |
| 178 | ControlResponse::Error { code, reason } => Err(io::Error::other(format!( | 180 | ControlResponse::Error { code, reason } => Err(io::Error::other(format!( |