From 9a67028ce617de67c650af541cfc508a3be3e1e9 Mon Sep 17 00:00:00 2001 From: ana espinoza Date: Wed, 29 Apr 2026 11:34:49 -0600 Subject: [PATCH 1/4] Use --split-string in hashbang The `--split-string` or `-S` option allows the argument called by `/usr/bin/env` to be interpreted as multiple arguments. I.e., without the `-S` option, `/usr/bin/env`, the system will try and run the executable at the path `conda run -n usage-monitoring python`, instead of `conda` with the rest as arguments to `conda`. --- usage-monitoring/usage_monitoring.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usage-monitoring/usage_monitoring.py b/usage-monitoring/usage_monitoring.py index 4ff485a..7d1cf48 100755 --- a/usage-monitoring/usage_monitoring.py +++ b/usage-monitoring/usage_monitoring.py @@ -1,4 +1,4 @@ -#! /usr/bin/env conda run -n usage-monitoring python +#! /usr/bin/env -S conda run -n usage-monitoring python from subprocess import run from os.path import isfile, expanduser From cd1d8784782dc4f62aa2f29d5add40e30064970c Mon Sep 17 00:00:00 2001 From: ana espinoza Date: Wed, 29 Apr 2026 13:46:44 -0600 Subject: [PATCH 2/4] Basic error handing for token creation --- usage-monitoring/usage_monitoring.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/usage-monitoring/usage_monitoring.py b/usage-monitoring/usage_monitoring.py index 7d1cf48..3168f43 100755 --- a/usage-monitoring/usage_monitoring.py +++ b/usage-monitoring/usage_monitoring.py @@ -1,6 +1,6 @@ #! /usr/bin/env -S conda run -n usage-monitoring python -from subprocess import run +from subprocess import run, CalledProcessError from os.path import isfile, expanduser import argparse @@ -25,11 +25,20 @@ def load_config(config_path): exit(1) def create_os_token(token_file): - token = run( - ['openstack', 'token', 'issue', '-f', 'json'], - capture_output=True, - check=True - ) + try: + token = run( + ['openstack', 'token', 'issue', '-f', 'json'], + capture_output=True, + check=True + ) + except CalledProcessError as ex: + print( + f'{ex}\n', + f'STDOUT: {ex.stdout.decode()}\n', + f'STDERR: {ex.stderr.decode()}\n' + 'It is possible that a valid openrc.sh was not sourced. Exiting ...' + ) + exit(1) with open(token_file, 'w') as f: f.write(token.stdout.decode()) From fbd0879e092f14e40829b2992d51a3afcbea66cf Mon Sep 17 00:00:00 2001 From: ana espinoza Date: Wed, 29 Apr 2026 13:52:29 -0600 Subject: [PATCH 3/4] print() error messages to stderr --- usage-monitoring/usage_monitoring.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/usage-monitoring/usage_monitoring.py b/usage-monitoring/usage_monitoring.py index 3168f43..1c59532 100755 --- a/usage-monitoring/usage_monitoring.py +++ b/usage-monitoring/usage_monitoring.py @@ -2,6 +2,7 @@ from subprocess import run, CalledProcessError from os.path import isfile, expanduser +from sys import stderr import argparse @@ -21,7 +22,7 @@ def load_config(config_path): c['test_csv_file'] = expanduser(c['test_csv_file']) return c except FileNotFoundError: - print(f'File {config_path} not found! Exiting ...') + print(f'File {config_path} not found! Exiting ...', file=stderr) exit(1) def create_os_token(token_file): @@ -36,7 +37,8 @@ def create_os_token(token_file): f'{ex}\n', f'STDOUT: {ex.stdout.decode()}\n', f'STDERR: {ex.stderr.decode()}\n' - 'It is possible that a valid openrc.sh was not sourced. Exiting ...' + 'It is possible that a valid openrc.sh was not sourced. Exiting ...', + file=stderr ) exit(1) with open(token_file, 'w') as f: @@ -63,7 +65,7 @@ def query_accounting_api(token): try: response.raise_for_status() except Exception as ex: - print(ex) + print(ex, file=stderr) exit(1) query = json.loads(response.text) return query @@ -274,7 +276,7 @@ def generate_usage_plot(resources, analyses, allocation_resources): for resource_type in allocation_resources: data = get_data_by_resource(resources, resource_type) if data.empty: - print(f'No available data for {resource_type}') + print(f'No available data for {resource_type}', file=stderr) continue timestamps = pd.array(data['timestamp']) @@ -333,10 +335,10 @@ def main(): for resource_type in c['allocation_resources']: data = get_data_by_resource(resources, resource_type) if data.empty: - print(f'No available data for {resource_type}. Skipping ...') + print(f'No available data for {resource_type}. Skipping ...', file=stderr) continue if len(data) < 2: - print(f'Not enough data for {resource_type}: len(data) = {len(data)}. Skipping ...') + print(f'Not enough data for {resource_type}: len(data) = {len(data)}. Skipping ...', file=stderr) # Perform analysis (usage rates, "forecast", ) analyses.append(usage_analysis(data,args['analysis_days'])) From bd1f3c55f37cd45f51d9974740af5fe2dcf96480 Mon Sep 17 00:00:00 2001 From: ana espinoza Date: Wed, 29 Apr 2026 14:06:16 -0600 Subject: [PATCH 4/4] Install instructions; running as executable --- usage-monitoring/README.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/usage-monitoring/README.md b/usage-monitoring/README.md index 19d3986..7e608d3 100644 --- a/usage-monitoring/README.md +++ b/usage-monitoring/README.md @@ -9,10 +9,16 @@ Create a conda environment with the necessary additional dependencies with: ## Usage For usage run `python usage_monitoring.py`. When running this script, you will -need to `source` a valid `openrc.sh`. +need to `source` a valid `openrc.sh` or have a valid `clouds.yaml` in `~/.config/openstack/`. See the [Jetstream2 docs](https://docs.jetstream-cloud.org/ui/cli/auth/) for -information on how to acquire an `openrc.sh` file. +information on how to acquire an `openrc.sh` or `clouds.yaml` file. + +### Installing + +Run the included `install.sh` script. This will create the appropriate directories if necessary and soft/symlink the appropriate files. To "uninstall" the script, simply remove the symlinks. + +Finally, ensure that `~/.local/bin` is in your `PATH`. ### Activating the Environment @@ -26,6 +32,10 @@ environment, use `conda run`, as in `usage_monitoring.sh`: `conda run -n usage-monitoring python usage_monitoring.py [options]` +Additionally, if you've configured your shell to include `~/.local/bin` in your `PATH`, you can run `usage_monitoring.py` as an executable, as the script includes the appropriate "hashbang" (a.k.a. "shebang" `#!`): + +`usage_monitoring.py [options]` + # Cron To collect usage data on a daily basis, run the wrapper script (after setting