Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Format with Black #3

Merged
merged 5 commits into from
Feb 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/workflows/black.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
name: black

on: [push, pull_request]

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/[email protected]
- uses: psf/black@stable
with:
options: "--check --diff --verbose"
src: "resman.py"
11 changes: 0 additions & 11 deletions .github/workflows/main.yml

This file was deleted.

18 changes: 17 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,17 @@
__pycache__
# Byte-compiled / optimized / DLL files
__pycache__/

# Distribution / packaging
*.egg-info/

# Virtual environments
.env
.venv
env/
venv/

# Pycharm
.idea/

# KDE directory preferences
.directory
20 changes: 18 additions & 2 deletions readme.md → README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
### Usage:
resman
======

A simple system resource reservation manager.

# Usage:

`resman [-h] [-d DURATION] [-x [COMMAND ...]] [-r [REASON ...]] [-R] [-c] [-u USER]`

Expand All @@ -8,7 +13,7 @@ which user (if any) has laid claim to it for the time being, and if applicable h
have it reserved for. The purpose of this is to prevent two people accidentally running
experiments at the same time.

### Options:
# Options:
| Flag(s) | Description |
|---------|-------------|
|-h, --help| Show this **help message** and exit |
Expand All @@ -20,3 +25,14 @@ experiments at the same time.

Can also be run with **no arguments**, to check status. Exits with 0 if not reserved, or 1 if
currently reserved. Just `echo $?` afterwards.

# Development

Install development dependencies inside a virtual environment:

```shell
$ python3 -m venv venv
$ source venv/bin/activate

(venv)$ pip install -e .[dev]
```
11 changes: 11 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[project]
name = "resman"
version = "1.0.20240216"

[project.optional-dependencies]
dev = [
"black"
]

[tool.black]
line-length = 120
173 changes: 98 additions & 75 deletions resman.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,27 @@
#!/usr/bin/env python3

import time, datetime
import json
import argparse
import os, sys
import datetime
import json
import os
import sys
import time

LOG_FILE = "/etc/res_log"
LOCK_FILE = "/etc/res_lock"

BLANK_DAT = {'username': '', 'reason': [''], 'start_time': 0, 'duration': 0}
BLANK_DAT = {"username": "", "reason": [""], "start_time": 0, "duration": 0}


class cols:
WARNING = '\033[91m'
ENDC = '\033[0m'
OKGREEN = '\033[92m'
WARNING = "\033[91m"
ENDC = "\033[0m"
OKGREEN = "\033[92m"


# Write reservation info into a given file, and also return the data as a
# dictionary.
def dumpdat(file, username, start, duration, reason):
dat = {
'start_time': start,
'duration': duration,
'username': username,
'reason': reason
}
dat = {"start_time": start, "duration": duration, "username": username, "reason": reason}

json.dump(dat, file)
return dat
Expand All @@ -39,10 +35,10 @@ def is_locked():
file = open(LOCK_FILE, "r")
dat = json.load(file)

if dat['duration'] == -1:
if dat["duration"] == -1:
return True, dat

end_time = dat['start_time'] + dat['duration']
end_time = dat["start_time"] + dat["duration"]
now = time.time()

return now <= end_time, dat
Expand All @@ -60,13 +56,13 @@ def try_lock(username, start, duration=3600, reason=""):
if locked:
return False, dat
else:
with open(LOCK_FILE, 'w') as file:
with open(LOCK_FILE, "w") as file:
return True, dumpdat(file, username, start, duration, reason)


# Force unlock the lock file, by clearing it
def release():
with open(LOCK_FILE, 'w') as file:
with open(LOCK_FILE, "w") as file:
file.truncate(0)


Expand All @@ -75,16 +71,16 @@ def release():
# '25m' = 0 hours, 25 minutes
# '10h' = 10 hours, 0 minutes
def timestring2secs(ts: str):
if 'h' not in ts:
ts = '0h' + ts
if 'm' not in ts:
ts = ts + '0m'
if "h" not in ts:
ts = "0h" + ts
if "m" not in ts:
ts = ts + "0m"

h_ind = ts.index('h')
h_ind = ts.index("h")

try:
h = int(ts[:h_ind])
m = int(ts[h_ind + 1:-1])
m = int(ts[h_ind + 1 : -1])

return h * 3600 + m * 60
except ValueError:
Expand All @@ -111,15 +107,14 @@ def explain_failure(dat):
print(f"{cols.WARNING}Already locked by {dat['username']}!{cols.ENDC}")
print(f"Reason: '{dat['reason']}'")

if dat['duration'] == -1:
print("Reservation is until further notice, likely waiting for a \
command to finish running.")
if dat["duration"] == -1:
print("Reservation is until further notice, likely waiting for a command to finish running.")
else:
snds = int(dat['start_time'] + dat['duration'] - time.time())
snds = int(dat["start_time"] + dat["duration"] - time.time())
print(f"{secs2timestring(snds)} left on reservation")


def log_lock(user, reason, dur = "", cmd = ""):
def log_lock(user, reason, dur="", cmd=""):
try:
with open(LOG_FILE, "a") as log_file:
now = datetime.datetime.now().strftime("%x %X")
Expand All @@ -136,69 +131,98 @@ def log_lock(user, reason, dur = "", cmd = ""):

def main():
parser = argparse.ArgumentParser(
prog='resman',
description=f'A simple system resource allocation program. Persistent \
state stored in {LOCK_FILE}. \
This program does not impose any actual lock on system resource usage; it only\
keeps track of which user (if any) has laid claim to it for the time being, \
and if applicable how long they have it reserved for. \
The purpose of this is to prevent two people accidentally running experiments \
at the same time.',
epilog='Can also be run with no arguments, to check status. \
Exits with 0 if not reserved, or 1 if currently reserved. Just \
`echo $?` afterwards.'
prog="resman",
description=(
f"A simple system resource allocation program. Persistent state stored in {LOCK_FILE}. This "
"program does not impose any actual lock on system resource usage; it only keeps track of which "
"user (if any) has laid claim to it for the time being, and if applicable how long they have it "
"reserved for. The purpose of this is to prevent two people accidentally running experiments at "
"the same time."
),
epilog=(
"Can also be run with no arguments, to check status. Exits with 0 if not reserved, or 1 if currently "
"reserved. Just `echo $?` afterwards."
),
)

parser.add_argument('-d', '--duration', help='how long to reserve server \
use for (e.g. 1h30m, 25m, 4h)', default=-1)
parser.add_argument(
"-d",
"--duration",
help="how long to reserve server use for (e.g. 1h30m, 25m, 4h)",
default=-1,
)

parser.add_argument('-x', '--run', metavar='COMMAND', help='reserve the \
server until COMMAND finishes. quotes (\') are needed around COMMAND if it \
contains \'-\'s, otherwise they\'re optional.', nargs='*')
parser.add_argument(
"-x",
"--run",
metavar="COMMAND",
help=(
"reserve the server until COMMAND finishes. quotes (') are needed around COMMAND if it contains '-'s, "
"otherwise they're optional."
),
nargs="*",
)

parser.add_argument('-r', '--reason', help='what experiment are you \
running? default is blank. completely optional.', default=['<no reason given>'],
nargs='*')
parser.add_argument(
"-r",
"--reason",
help="what experiment are you running? default is blank. completely optional.",
default=["<no reason given>"],
nargs="*",
)

parser.add_argument('-R', '--release', help='unlocks an existing \
reservation. you should probably only use this if you\'re the person who made \
the reservation in the first place, and you want to free the server earlier \
than your allocated time slot. may also be used if this script crashes :)',
action='store_true')
parser.add_argument(
"-R",
"--release",
help=(
"unlocks an existing reservation. you should probably only use this if you're the person who made the "
"reservation in the first place, and you want to free the server earlier than your allocated time slot. "
"may also be used if this script crashes :)"
),
action="store_true",
)

parser.add_argument('-c', '--confirm', help='interactively tell the user \
the current status of things. if the server is reserved, they are prompted \
to press enter to confirm that they understand this.', action='store_true')
parser.add_argument(
"-c",
"--confirm",
help=(
"interactively tell the user the current status of things. if the server is reserved, they are prompted to "
"press enter to confirm that they understand this."
),
action="store_true",
)

parser.add_argument('-u', '--user', help=f'who is using the server during \
the reservation? Default is {os.getlogin()}.', default=os.getlogin())
parser.add_argument(
"-u",
"--user",
help=f"who is using the server during the reservation? Default is {os.getlogin()}.",
default=os.getlogin(),
)

args = parser.parse_args()
args.reason = ' '.join(args.reason)
args.reason = " ".join(args.reason)

locked, dat = is_locked()

if args.confirm:
if locked:
print(cols.WARNING + "Be careful!" + cols.ENDC + " Someone is \
running an experiment right now.\nPlease avoid any significant CPU or memory \
usage for the time being.")
print(
cols.WARNING + "Be careful!" + cols.ENDC + " Someone is running an experiment right now.\n"
"Please avoid any significant CPU or memory usage for the time being."
)
print(f"User: {dat['username']}")
print(f"Reason: {dat['reason']}")
if dat['duration'] == -1:
print("Time remaining: indeterminate (waiting for command to \
terminate)")
runtime = int(time.time()) - dat['start_time']
if dat["duration"] == -1:
print("Time remaining: indeterminate (waiting for command to terminate)")
runtime = int(time.time()) - dat["start_time"]
print(f"Time since job started: {secs2timestring(runtime)}")
else:
snds = int(dat['start_time'] + dat['duration'] - time.time())
snds = int(dat["start_time"] + dat["duration"] - time.time())
print(f"Time remaining: {secs2timestring(snds)}")
input(cols.WARNING + "Please press ENTER" + cols.ENDC + " to \
confirm that you've read this :) ")
input(cols.WARNING + "Please press ENTER" + cols.ENDC + " to confirm that you've read this :) ")
return
else:
print(cols.OKGREEN + "No one is running any experiment right now, \
do what you like :)" + cols.ENDC)
print(cols.OKGREEN + "No one is running any experiment right now, do what you like :)" + cols.ENDC)
return

if args.release:
Expand All @@ -223,7 +247,7 @@ def main():
print("Server locked by someone else. Race condition edge case.")
sys.exit(1)
else:
cmd = ' '.join(args.run)
cmd = " ".join(args.run)
log_lock(args.user, args.reason, "", cmd)

res = os.system(cmd)
Expand All @@ -237,8 +261,7 @@ def main():
print("Failed to pass time string", file=sys.stderr)
sys.exit(os.EX_DATAERR)

print(f"Allocating server for {args.user} for {args.duration} \
({dur_secs}s):\nReason: {args.reason}")
print(f"Allocating server for {args.user} for {args.duration} ({dur_secs}s):\nReason: {args.reason}")

successful, _ = try_lock(args.user, int(time.time()), dur_secs, args.reason)

Expand All @@ -248,5 +271,5 @@ def main():
log_lock(args.user, args.reason, args.duration, "")


if __name__ == '__main__':
if __name__ == "__main__":
main()