Skip to content

Commit

Permalink
Merge pull request #66 from glotzerlab/jobs
Browse files Browse the repository at this point in the history
Add `show jobs`
  • Loading branch information
joaander authored Dec 5, 2024
2 parents 0425472 + 2616e20 commit 5eec351
Show file tree
Hide file tree
Showing 10 changed files with 290 additions and 13 deletions.
5 changes: 1 addition & 4 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,7 @@ Row is yet another workflow engine that automates the process of executing **act
* **Waiting** on previous actions.
* List directories and show completion status, submitted job ID, and user-defined keys from the
value.

Ideas:
* List scheduler jobs and show useful information.
* Cancel scheduler jobs specific to actions and/or directories.
* List submitted jobs.

## Overview

Expand Down
1 change: 1 addition & 0 deletions doc/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
- [show](row/show/index.md)
- [show status](row/show/status.md)
- [show directories](row/show/directories.md)
- [show jobs](row/show/jobs.md)
- [show cluster](row/show/cluster.md)
- [show launchers](row/show/launchers.md)
- [scan](row/scan.md)
Expand Down
24 changes: 16 additions & 8 deletions doc/src/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,25 @@

*Highlights:*

**Row** 0.4 expands the `command` templating functionality to improve support for
command line applications as actions. This removes the need for _shim_ scripts that
access the workspace path and/or directory values before invoking a subprocess.
`{workspace_path}` expands to the current project's workspace path and `{/JSON pointer}`
expands to the value of the given JSON pointer for the directory acted on.

**Row** 0.4 also adds _shell autocompletion_. To enable, execute the appropriate
command in your shell's profile:
**Row** 0.4 expands the `command` templating functionality, adds shell autocompletion,
and the `show jobs` subcommand.

The expanded _templating_ functionality improves support for command line applications
as actions. This removes the need for _shim_ scripts that access the workspace path and/
or directory values before invoking a subprocess. `{workspace_path}` expands to the
current project's workspace path and `{/JSON pointer}` expands to the value of the given
JSON pointer for the directory acted on.

_Shell autocompletion_ allows users to autocomplete all parameter names and
workspace dependent values for `cluster`, `action`, and `directories`. To enable,
execute the appropriate command in your shell's profile:
* Bash: `source <(COMPLETE=bash row)`
* Fish: `source (COMPLETE=fish row | psub)`
* Zsh: `source <(COMPLETE=zsh row)`

`show jobs` prints a table summarizing all currently submitted jobs that match given
action and directory criteria.

*Added:*

* In job scripts, set the environment variable `ACTION_WORKSPACE_PATH` to the _relative_
Expand All @@ -25,6 +32,7 @@ command in your shell's profile:
* `{/JSON pointer}` template parameter in `action.command` - replaced with the portion
of the directory's value referenced by the given JSON pointer.
* Shell autocomplete.
* `show jobs` subcommand.

*Fixed:*

Expand Down
59 changes: 59 additions & 0 deletions doc/src/row/show/jobs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# show jobs

Usage:
```bash
row show jobs [OPTIONS] [DIRECTORIES]
```

`row show jobs` lists submitted jobs that execute a matching action on any of the
provided directories.

## `[DIRECTORIES]`

List jobs that execute an action on one or more of the given directories. By default,
**row** shows jobs executing an action on any directory.

Pass a single `-` to read the directories from stdin (separated by newlines):
```bash
echo "dir1" | row show jobs [OPTIONS] -
```

## `[OPTIONS]`

### `--action`

(also: `-a`)

Set `--action <pattern>` to show only jobs that mach the given pattern by name.
By default, **row** shows jobs executing any action. `<pattern>` is a wildcard pattern.

### `--no-header`

Hide the header in the output.

### `--short`

Show only the job IDs.

## Examples

* Show all jobs:
```bash
row show jobs
```
* Show jobs that execute actions on any of the given directories:
```bash
row show jobs directory1 directory
```
* Show jobs that execute the action 'one':
```bash
row show jobs --action one
```
* Show jobs that execute an action starting with 'analyze':
```bash
row show jobs --action 'analyze*'
```
* Cancel SLURM jobs executing action 'two':
```bash
row show jobs --action two --short | xargs scancel
```
2 changes: 1 addition & 1 deletion doc/src/row/submit.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ the entire workspace.

(also: `-a`)

Set `--action <pattern>` to choose which actions to display by name. By default, **row**
Set `--action <pattern>` to choose which actions to submit by name. By default, **row**
submits the eligible jobs of all actions. `<pattern>` is a wildcard pattern.

### `--dry-run`
Expand Down
30 changes: 30 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub mod clean;
pub mod cluster;
pub mod directories;
pub mod init;
pub mod jobs;
pub mod launchers;
pub mod scan;
pub mod status;
Expand Down Expand Up @@ -190,6 +191,35 @@ pub enum ShowCommands {
///
/// row show launchers --all --short
Launchers(launchers::Arguments),

/** Show submitted jobs.
`row show jobs` lists submitted jobs that execute a matching action on
any of the provided directories.
EXAMPLES
* Show all jobs:
row show jobs
* Show jobs that execute actions on any of the given directories:
row show jobs directory1 directory
* Show jobs that execute the action 'one':
row show jobs --action one
* Show jobs that execute an action starting with 'analyze':
row show jobs --action 'analyze*'
* Cancel SLURM jobs executing action 'two':
row show jobs --action two --short | xargs scancel
*/
Jobs(jobs::Arguments),
}

#[derive(Subcommand, Debug)]
Expand Down
147 changes: 147 additions & 0 deletions src/cli/jobs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// Copyright (c) 2024 The Regents of the University of Michigan.
// Part of row, released under the BSD 3-Clause License.

use clap::Args;
use clap_complete::ArgValueCandidates;
use console::Style;
use log::{debug, trace};
use std::collections::{BTreeMap, HashSet};
use std::error::Error;
use std::io::Write;
use std::path::PathBuf;
use wildmatch::WildMatch;

use crate::cli::{self, autocomplete, GlobalOptions};
use crate::ui::{Alignment, Item, Row, Table};
use row::project::Project;
use row::MultiProgressContainer;

#[derive(Args, Debug)]
#[allow(clippy::struct_excessive_bools)]
pub struct Arguments {
/// Show jobs running on these directories (defaults to all). Use 'show jobs -' to read from stdin.
#[arg(add=ArgValueCandidates::new(autocomplete::get_directory_candidates))]
directories: Vec<PathBuf>,

/// Show jobs running actions that match a wildcard pattern.
#[arg(short, long, value_name = "pattern", default_value_t=String::from("*"), display_order=0,
add=ArgValueCandidates::new(autocomplete::get_action_candidates))]
action: String,

/// Hide the table header.
#[arg(long, display_order = 0)]
no_header: bool,

/// Show only job IDs.
#[arg(long, default_value_t = false, display_order = 0)]
short: bool,
}

struct JobDetails {
action: String,
n: u64,
}

/** Find jobs that match the given directories and the action wildcard on the selected cluster.
*/
fn find(
directories: Vec<PathBuf>,
action: &str,
project: &Project,
) -> Result<BTreeMap<u32, JobDetails>, Box<dyn Error>> {
debug!("Finding matching jobs.");
let mut result: BTreeMap<u32, JobDetails> = BTreeMap::new();

let action_matcher = WildMatch::new(action);

let query_directories: HashSet<PathBuf> =
HashSet::from_iter(cli::parse_directories(directories, || {
Ok(project.state().list_directories())
})?);

for (action_name, jobs_by_directory) in project.state().submitted() {
if !action_matcher.matches(action_name) {
trace!(
"Skipping action '{}'. It does not match the pattern '{}'.",
action_name,
action
);
continue;
}

for (directory_name, (cluster_name, job_id)) in jobs_by_directory {
if cluster_name != project.cluster_name() {
trace!(
"Skipping cluster '{cluster_name}'. It does not match selected cluster '{}'.",
project.cluster_name()
);
continue;
}

if query_directories.contains(directory_name) {
result
.entry(*job_id)
.and_modify(|e| e.n += 1)
.or_insert(JobDetails {
action: action_name.clone(),
n: 1,
});
}
}
}

Ok(result)
}

/** Show jobs running on given directories where the action also matches a wildcard.
Print a human-readable list of job IDs, the action they are running, and the number of
directories that the job acts on.
*/
pub fn show<W: Write>(
options: &GlobalOptions,
args: Arguments,
multi_progress: &mut MultiProgressContainer,
output: &mut W,
) -> Result<(), Box<dyn Error>> {
debug!("Showing jobs.");

let mut project = Project::open(options.io_threads, &options.cluster, multi_progress)?;

let jobs = find(args.directories, &args.action, &project)?;

let mut table = Table::new().with_hide_header(if args.short { true } else { args.no_header });
table.header = vec![
Item::new("ID".to_string(), Style::new().underlined()),
Item::new("Action".to_string(), Style::new().underlined()),
Item::new("Directories".to_string(), Style::new().underlined()),
];

for (job_id, job_details) in jobs {
let mut row = Vec::new();

row.push(
Item::new(job_id.to_string(), Style::new().bold()).with_alignment(Alignment::Right),
);

// Only show job IDs when user requests short output.
if args.short {
table.rows.push(Row::Items(row));
continue;
}

row.push(Item::new(job_details.action, Style::new()));
row.push(
Item::new(job_details.n.to_string(), Style::new()).with_alignment(Alignment::Right),
);

table.rows.push(Row::Items(row));
}

table.write(output)?;
output.flush()?;

project.close(multi_progress)?;

Ok(())
}
8 changes: 8 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,14 @@ fn main_detail() -> Result<(), Box<dyn Error>> {
ShowCommands::Launchers(args) => {
cli::launchers::launchers(&options.global, &args, &mut output)?;
}
ShowCommands::Jobs(args) => {
cli::jobs::show(
&options.global,
args,
&mut multi_progress_container,
&mut output,
)?;
}
},
Some(Commands::Scan(args)) => {
cli::scan::scan(&options.global, args, &mut multi_progress_container)?;
Expand Down
5 changes: 5 additions & 0 deletions src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,11 @@ impl Project {
&self.state
}

/// Get the currently active cluster name.
pub fn cluster_name(&self) -> &String {
&self.cluster_name
}

/// Find the directories that are included by the action.
///
/// # Parameters:
Expand Down
22 changes: 22 additions & 0 deletions tests/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -894,3 +894,25 @@ fn init() -> Result<(), Box<dyn std::error::Error>> {

Ok(())
}

#[test]
#[parallel]
fn show_jobs() -> Result<(), Box<dyn std::error::Error>> {
let temp = TempDir::new()?;
let _ = setup_sample_workflow(&temp, 4);

Command::cargo_bin("row")?
.args(["show", "jobs"])
.args(["--cluster", "none"])
.env_remove("ROW_COLOR")
.env_remove("CLICOLOR")
.env("ROW_HOME", "/not/a/path")
.current_dir(temp.path())
.assert()
.success();

// It is not possible to automatically check the output of show jobs. This unit test
// must run on systems that do not have SLURM or where developers have no SLURM account.

Ok(())
}

0 comments on commit 5eec351

Please sign in to comment.