Skip to content

Commit 8a8ecf9

Browse files
committed
Add event log export to (ND)JSON
1 parent 02f2ea4 commit 8a8ecf9

18 files changed

Lines changed: 367 additions & 20 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@
5555
$ hq submit --stdin bash
5656
```
5757

58+
* You can now store HyperQueue events into a log file and later export them to JSON for further
59+
processing. You can find more information in the
60+
[documentation](https://it4innovations.github.io/hyperqueue/stable/jobs/directives/).
61+
62+
*Note that this functionality is quite low-level, and it's designed primarily for
63+
tool builders that use HyperQueue programmatically, not regular users. It is also currently
64+
unstable.*
5865
5966
### Worker configuration
6067
* You can now select what should happen when a worker loses its connection to the server using the

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/hyperqueue/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ termion = "1.5"
4747
indicatif = "0.16.2"
4848
textwrap = "0.14"
4949
async-compression = { version = "0.3.8", features = ["tokio", "gzip"] }
50+
flate2 = { version = "1.0.22", features = ["default"] }
5051

5152
# Tako
5253
tako = { path = "../tako" }

crates/hyperqueue/src/bin/hq.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use cli_table::ColorChoice;
88

99
use clap::ArgSettings::HiddenShortHelp;
1010
use hyperqueue::client::commands::autoalloc::{command_autoalloc, AutoAllocOpts};
11+
use hyperqueue::client::commands::event::{command_event_log, EventLogOpts};
1112
use hyperqueue::client::commands::job::{
1213
cancel_job, output_job_cat, output_job_detail, output_job_list, output_job_tasks,
1314
JobCancelOpts, JobCatOpts, JobInfoOpts, JobListOpts, JobTasksOpts,
@@ -111,6 +112,8 @@ enum SubCommand {
111112
/// Automatic allocation management
112113
#[clap(name = "alloc")]
113114
AutoAlloc(AutoAllocOpts),
115+
/// Event log management
116+
EventLog(EventLogOpts),
114117
///Commands for the dashboard
115118
Dashboard(DashboardOpts),
116119
/// Generate shell completion script
@@ -146,7 +149,7 @@ struct ServerStartOpts {
146149

147150
/// Path to a log file where events will be stored.
148151
#[clap(long, hide(true))]
149-
event_log_file: Option<PathBuf>,
152+
event_log_path: Option<PathBuf>,
150153
}
151154

152155
#[derive(Parser)]
@@ -297,7 +300,7 @@ async fn command_server_start(
297300
client_port: opts.client_port,
298301
worker_port: opts.worker_port,
299302
event_buffer_size: opts.event_store_size,
300-
event_log_file: opts.event_log_file,
303+
event_log_path: opts.event_log_path,
301304
};
302305

303306
init_hq_server(gsettings, server_cfg).await
@@ -601,6 +604,7 @@ async fn main() -> hyperqueue::Result<()> {
601604
SubCommand::Dashboard(opts) => command_dashboard_start(&gsettings, opts).await,
602605
SubCommand::Log(opts) => command_log(&gsettings, opts),
603606
SubCommand::AutoAlloc(opts) => command_autoalloc(&gsettings, opts).await,
607+
SubCommand::EventLog(opts) => command_event_log(opts),
604608
SubCommand::GenerateCompletion(opts) => generate_completion(opts),
605609
};
606610

crates/hyperqueue/src/client/commands/autoalloc.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,13 @@ enum AutoAllocCommand {
4242
}
4343

4444
#[derive(Parser)]
45-
pub struct AddQueueOpts {
45+
struct AddQueueOpts {
4646
#[clap(subcommand)]
4747
subcmd: AddQueueCommand,
4848
}
4949

5050
#[derive(Parser)]
51-
pub struct RemoveQueueOpts {
51+
struct RemoveQueueOpts {
5252
/// ID of the allocation queue that should be removed
5353
queue_id: DescriptorId,
5454

@@ -59,7 +59,7 @@ pub struct RemoveQueueOpts {
5959
}
6060

6161
#[derive(Parser)]
62-
pub enum AddQueueCommand {
62+
enum AddQueueCommand {
6363
/// Create a PBS allocation queue
6464
Pbs(SharedQueueOpts),
6565
/// Create a SLURM allocation queue
@@ -68,7 +68,7 @@ pub enum AddQueueCommand {
6868

6969
#[derive(Parser)]
7070
#[clap(setting = clap::AppSettings::TrailingVarArg)]
71-
pub struct SharedQueueOpts {
71+
struct SharedQueueOpts {
7272
/// How many jobs should be waiting in the queue to be started
7373
#[clap(long, short, default_value = "4")]
7474
backlog: u32,
@@ -116,27 +116,27 @@ pub struct SharedQueueOpts {
116116
}
117117

118118
#[derive(Parser)]
119-
pub struct DryRunOpts {
119+
struct DryRunOpts {
120120
#[clap(subcommand)]
121121
subcmd: DryRunCommand,
122122
}
123123

124124
#[derive(Parser)]
125-
pub enum DryRunCommand {
125+
enum DryRunCommand {
126126
/// Try to create a PBS allocation
127127
Pbs(SharedQueueOpts),
128128
/// Try to create a SLURM allocation
129129
Slurm(SharedQueueOpts),
130130
}
131131

132132
#[derive(Parser)]
133-
pub struct EventsOpts {
133+
struct EventsOpts {
134134
/// ID of the allocation queue
135135
queue: u32,
136136
}
137137

138138
#[derive(Parser)]
139-
pub struct AllocationsOpts {
139+
struct AllocationsOpts {
140140
/// ID of the allocation queue
141141
queue: u32,
142142

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
mod output;
2+
3+
use crate::client::commands::event::output::format_event;
4+
use crate::common::strutils::pluralize;
5+
use crate::event::log::EventLogReader;
6+
use anyhow::anyhow;
7+
use clap::{Parser, ValueHint};
8+
use std::io::{BufWriter, Write};
9+
use std::path::PathBuf;
10+
11+
#[derive(Parser)]
12+
pub struct EventLogOpts {
13+
/// Manage event log files.
14+
#[clap(subcommand)]
15+
command: EventCommand,
16+
}
17+
18+
#[derive(Parser)]
19+
enum EventCommand {
20+
/// Export events from a log file to NDJSON (line-delimited JSON).
21+
/// Events will be exported to `stdout`, you can redirect it e.g. to a file.
22+
Export(ExportOpts),
23+
}
24+
25+
#[derive(Parser)]
26+
struct ExportOpts {
27+
/// Path to a file containing the event log.
28+
/// The file had to be created with `hq server start --event-log-path=<PATH>`.
29+
#[clap(value_hint = ValueHint::FilePath)]
30+
logfile: PathBuf,
31+
}
32+
33+
pub fn command_event_log(opts: EventLogOpts) -> anyhow::Result<()> {
34+
match opts.command {
35+
EventCommand::Export(opts) => export_json(opts),
36+
}
37+
}
38+
39+
fn export_json(opts: ExportOpts) -> anyhow::Result<()> {
40+
let file = EventLogReader::open(&opts.logfile).map_err(|error| {
41+
anyhow!(
42+
"Cannot open event log file at `{}`: {error:?}",
43+
opts.logfile.display()
44+
)
45+
})?;
46+
47+
let stdout = std::io::stdout();
48+
let stdout = stdout.lock();
49+
let mut stdout = BufWriter::new(stdout);
50+
51+
let mut count = 0;
52+
for event in file {
53+
match event {
54+
Ok(event) => {
55+
writeln!(stdout, "{}", format_event(event))?;
56+
count += 1;
57+
}
58+
Err(error) => {
59+
log::error!(
60+
"Encountered an error while reading event log file: {error:?}.\n
61+
The file might have been incomplete."
62+
)
63+
}
64+
}
65+
}
66+
67+
log::info!("Outputted {count} {}", pluralize("event", count));
68+
69+
stdout.flush()?;
70+
Ok(())
71+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
use crate::client::output::json::format_datetime;
2+
use crate::event::events::MonitoringEventPayload;
3+
use crate::event::MonitoringEvent;
4+
use serde_json::json;
5+
use tako::messages::worker::WorkerOverview;
6+
7+
pub fn format_event(event: MonitoringEvent) -> serde_json::Value {
8+
json!({
9+
"id": event.id,
10+
"time": format_datetime(event.time),
11+
"event": format_payload(event.payload)
12+
})
13+
}
14+
15+
fn format_payload(event: MonitoringEventPayload) -> serde_json::Value {
16+
match event {
17+
MonitoringEventPayload::WorkerConnected(id, ..) => json!({
18+
"type": "worker-connected",
19+
"id": id
20+
}),
21+
MonitoringEventPayload::WorkerLost(id, reason) => json!({
22+
"type": "worker-lost",
23+
"id": id,
24+
"reason": reason
25+
}),
26+
MonitoringEventPayload::OverviewUpdate(WorkerOverview { id, hw_state, .. }) => json!({
27+
"type": "worker-overview",
28+
"id": id,
29+
"hw_state": hw_state
30+
}),
31+
}
32+
}

crates/hyperqueue/src/client/commands/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod autoalloc;
2+
pub mod event;
23
pub mod job;
34
pub mod log;
45
pub mod stats;

crates/hyperqueue/src/client/output/json.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,6 @@ fn format_duration(duration: Duration) -> serde_json::Value {
472472
let value = duration.as_secs() as f64 + duration.subsec_nanos() as f64 * 1e-9;
473473
json!(value)
474474
}
475-
fn format_datetime<T: Into<DateTime<Utc>>>(time: T) -> serde_json::Value {
475+
pub fn format_datetime<T: Into<DateTime<Utc>>>(time: T) -> serde_json::Value {
476476
json!(time.into())
477477
}
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use std::borrow::Cow;
22

3-
/// Return the input string with an added "s" at the end if `count` is larger than one.
3+
/// Return the input string with an added "s" at the end if `count` is larger than one and non-zero.
44
pub fn pluralize(value: &str, count: usize) -> Cow<str> {
5-
if count > 1 {
6-
Cow::Owned(format!("{}s", value))
7-
} else {
5+
if count == 1 {
86
Cow::Borrowed(value)
7+
} else {
8+
Cow::Owned(format!("{}s", value))
99
}
1010
}

0 commit comments

Comments
 (0)