Skip to content

Commit

Permalink
Merge pull request #3 from pilino1234/chore/black
Browse files Browse the repository at this point in the history
Format with Black
  • Loading branch information
j4cobgarby authored Feb 26, 2024
2 parents 7bb0333 + 196cd74 commit d8cab76
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 89 deletions.
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()

0 comments on commit d8cab76

Please sign in to comment.