sliderule-cli 5.6.1__tar.gz → 5.6.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sliderule_cli-5.6.1/src/sliderule_cli.egg-info → sliderule_cli-5.6.3}/PKG-INFO +1 -1
- {sliderule_cli-5.6.1 → sliderule_cli-5.6.3/src/sliderule_cli.egg-info}/PKG-INFO +1 -1
- sliderule_cli-5.6.3/src/sliderule_cli.egg-info/scm_version.json +8 -0
- {sliderule_cli-5.6.1 → sliderule_cli-5.6.3}/src/sliderule_cluster/main.py +16 -15
- {sliderule_cli-5.6.1 → sliderule_cli-5.6.3}/src/sliderule_provisioner/main.py +16 -4
- {sliderule_cli-5.6.1 → sliderule_cli-5.6.3}/src/sliderule_runner/database.py +1 -0
- {sliderule_cli-5.6.1 → sliderule_cli-5.6.3}/src/sliderule_runner/main.py +90 -41
- sliderule_cli-5.6.1/src/sliderule_cli.egg-info/scm_version.json +0 -8
- {sliderule_cli-5.6.1 → sliderule_cli-5.6.3}/.gitignore +0 -0
- {sliderule_cli-5.6.1 → sliderule_cli-5.6.3}/LICENSE +0 -0
- {sliderule_cli-5.6.1 → sliderule_cli-5.6.3}/README.md +0 -0
- {sliderule_cli-5.6.1 → sliderule_cli-5.6.3}/pyproject.toml +0 -0
- {sliderule_cli-5.6.1 → sliderule_cli-5.6.3}/setup.cfg +0 -0
- {sliderule_cli-5.6.1 → sliderule_cli-5.6.3}/src/sliderule_cli.egg-info/SOURCES.txt +0 -0
- {sliderule_cli-5.6.1 → sliderule_cli-5.6.3}/src/sliderule_cli.egg-info/dependency_links.txt +0 -0
- {sliderule_cli-5.6.1 → sliderule_cli-5.6.3}/src/sliderule_cli.egg-info/entry_points.txt +0 -0
- {sliderule_cli-5.6.1 → sliderule_cli-5.6.3}/src/sliderule_cli.egg-info/requires.txt +0 -0
- {sliderule_cli-5.6.1 → sliderule_cli-5.6.3}/src/sliderule_cli.egg-info/scm_file_list.json +0 -0
- {sliderule_cli-5.6.1 → sliderule_cli-5.6.3}/src/sliderule_cli.egg-info/top_level.txt +0 -0
- {sliderule_cli-5.6.1 → sliderule_cli-5.6.3}/src/sliderule_cluster/__init__.py +0 -0
- {sliderule_cli-5.6.1 → sliderule_cli-5.6.3}/src/sliderule_provisioner/__init__.py +0 -0
- {sliderule_cli-5.6.1 → sliderule_cli-5.6.3}/src/sliderule_recorder/__init__.py +0 -0
- {sliderule_cli-5.6.1 → sliderule_cli-5.6.3}/src/sliderule_recorder/environment.yml +0 -0
- {sliderule_cli-5.6.1 → sliderule_cli-5.6.3}/src/sliderule_recorder/main.py +0 -0
- {sliderule_cli-5.6.1 → sliderule_cli-5.6.3}/src/sliderule_runner/__init__.py +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import sys
|
|
1
2
|
import json
|
|
2
3
|
import uuid
|
|
3
4
|
import tempfile
|
|
@@ -42,10 +43,9 @@ class Tool:
|
|
|
42
43
|
|
|
43
44
|
# whoami
|
|
44
45
|
def whoami(self):
|
|
45
|
-
|
|
46
|
-
result =
|
|
47
|
-
|
|
48
|
-
return result
|
|
46
|
+
result = self.session.source("whoami", parm={}, path="/discovery", retries=0, rethrow=True)
|
|
47
|
+
self.__display_result(result, reformat=True)
|
|
48
|
+
return json.dumps(result)
|
|
49
49
|
|
|
50
50
|
# status
|
|
51
51
|
def status(self):
|
|
@@ -53,22 +53,21 @@ class Tool:
|
|
|
53
53
|
parm = {"service":self.session.service}
|
|
54
54
|
else:
|
|
55
55
|
parm = {}
|
|
56
|
-
|
|
57
|
-
result =
|
|
58
|
-
|
|
59
|
-
return result
|
|
56
|
+
result = self.session.source("status", parm=parm, path="/discovery", retries=0, rethrow=True)
|
|
57
|
+
self.__display_result(result, reformat=True)
|
|
58
|
+
return json.dumps(result)
|
|
60
59
|
|
|
61
60
|
# version
|
|
62
61
|
def version(self):
|
|
63
62
|
result = sliderule.get_version(session=self.session)
|
|
64
63
|
self.__display_result(result, reformat=True)
|
|
65
|
-
return result
|
|
64
|
+
return json.dumps(result)
|
|
66
65
|
|
|
67
66
|
# defaults
|
|
68
67
|
def defaults(self):
|
|
69
68
|
result = sliderule.source("defaults", session=self.session)
|
|
70
69
|
self.__display_result(result, reformat=True)
|
|
71
|
-
return result
|
|
70
|
+
return json.dumps(result)
|
|
72
71
|
|
|
73
72
|
# earthdata
|
|
74
73
|
def earthdata(self):
|
|
@@ -84,13 +83,13 @@ class Tool:
|
|
|
84
83
|
}.items() if v is not None })
|
|
85
84
|
result = sliderule.source("earthdata", parms, rethrow=True, session=self.session)
|
|
86
85
|
self.__display_result(result, reformat=True)
|
|
87
|
-
return result
|
|
86
|
+
return json.dumps(result)
|
|
88
87
|
|
|
89
88
|
# run
|
|
90
89
|
def run(self):
|
|
91
90
|
# get request parameters
|
|
92
|
-
if self.args.parms.startswith('
|
|
93
|
-
parms = self.args.parms
|
|
91
|
+
if self.args.parms.startswith('{'):
|
|
92
|
+
parms = json.loads(self.args.parms)
|
|
94
93
|
else:
|
|
95
94
|
parms = self.__get_parms()
|
|
96
95
|
# build output parameters (if not supplied in request)
|
|
@@ -117,7 +116,7 @@ class Tool:
|
|
|
117
116
|
break
|
|
118
117
|
# set and return results
|
|
119
118
|
result = output_path
|
|
120
|
-
self.__display_result(result, reformat=
|
|
119
|
+
self.__display_result(result, reformat=False)
|
|
121
120
|
return result
|
|
122
121
|
|
|
123
122
|
#########################################
|
|
@@ -157,7 +156,7 @@ def main():
|
|
|
157
156
|
# earthdata
|
|
158
157
|
earthdata = subparsers.add_parser("earthdata", parents=[common], help="earthdata search")
|
|
159
158
|
earthdata.add_argument('--asset', type=str, default="icesat2", help="SlideRule asset name")
|
|
160
|
-
earthdata.add_argument('--poly', type=float, nargs='+', default=None, help="closed counter-clockwise list of latitude
|
|
159
|
+
earthdata.add_argument('--poly', type=float, nargs='+', default=None, help="closed counter-clockwise list of longitude/latitude pairs defining the area of interest; e.g. lon1 lat1 lon2 lat2 lon3 lat3 ... lon1 lat1")
|
|
161
160
|
earthdata.add_argument('--geojson', type=str, default=None, help="geojson filename defining the area of interest")
|
|
162
161
|
earthdata.add_argument('--bbox', type=float, nargs='+', default=None, help="bounding box defining the area of interest; e.g. lon_ll lat_ll lon_ur lat_ur")
|
|
163
162
|
earthdata.add_argument('--t0', type=str, default=None, help="start time as an ISO datetime string YYYY-MM-DDTHH:MM:SSZ") # datetime.fromisoformat(args.t0)
|
|
@@ -166,6 +165,7 @@ def main():
|
|
|
166
165
|
earthdata.add_argument('--short_name', type=str, default=None, help="CMR dataset name")
|
|
167
166
|
earthdata.add_argument('--with_meta', action='store_true', default=False, help="return metadata along with query results")
|
|
168
167
|
earthdata.add_argument('--name_filter', type=str, default=None, help="regular expression to evaluate against resource names returned by query")
|
|
168
|
+
earthdata.add_argument('--parms', type=str, default=None, help="string or filename containing json parameters to use in request")
|
|
169
169
|
earthdata.set_defaults(func=Tool.earthdata)
|
|
170
170
|
|
|
171
171
|
# run
|
|
@@ -189,6 +189,7 @@ def main():
|
|
|
189
189
|
except Exception as e:
|
|
190
190
|
if args.verbose: raise
|
|
191
191
|
print(f"Unhandled error: {e}")
|
|
192
|
+
sys.exit(1) # returns error back to system
|
|
192
193
|
|
|
193
194
|
# running via direct invocation
|
|
194
195
|
if __name__ == "__main__": main()
|
|
@@ -69,6 +69,14 @@ class Tool:
|
|
|
69
69
|
def info(self):
|
|
70
70
|
return self.session.provisioner.info()
|
|
71
71
|
|
|
72
|
+
# S3 Access
|
|
73
|
+
def s3access(self):
|
|
74
|
+
credentials = self.session.provisioner.s3access()
|
|
75
|
+
print(f'export AWS_ACCESS_KEY_ID={credentials["access_key_id"]}')
|
|
76
|
+
print(f'export AWS_SECRET_ACCESS_KEY={credentials["secret_access_key"]}')
|
|
77
|
+
print(f'export AWS_SESSION_TOKEN={credentials["session_token"]}')
|
|
78
|
+
print(f'export AWS_CREDENTIAL_EXPIRATION={credentials["expiration"]}')
|
|
79
|
+
|
|
72
80
|
# Authenticate
|
|
73
81
|
def authenticate(self):
|
|
74
82
|
return self.session.authenticate(force_login=True)
|
|
@@ -81,7 +89,7 @@ class Tool:
|
|
|
81
89
|
def main():
|
|
82
90
|
|
|
83
91
|
# command line arguments
|
|
84
|
-
parser = argparse.ArgumentParser(prog="sliderule-
|
|
92
|
+
parser = argparse.ArgumentParser(prog="sliderule-provisioner", description="""SlideRule Provisioner""")
|
|
85
93
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
86
94
|
|
|
87
95
|
# options shared by every subcommand; parents= lets them appear after the command name
|
|
@@ -89,7 +97,6 @@ def main():
|
|
|
89
97
|
common.add_argument('--domain', type=str, default="slideruleearth.io")
|
|
90
98
|
common.add_argument('--cluster', type=str, default="developers")
|
|
91
99
|
common.add_argument('--user_service', action='store_true', default=False)
|
|
92
|
-
common.add_argument('--concise', action='store_true', default=False)
|
|
93
100
|
common.add_argument('--verbose', action='store_true', default=False)
|
|
94
101
|
common.add_argument('--timezone', type=str, default="America/New_York")
|
|
95
102
|
|
|
@@ -120,7 +127,7 @@ def main():
|
|
|
120
127
|
|
|
121
128
|
# report
|
|
122
129
|
report = subparsers.add_parser("report", parents=[common], help="report metadata of active clusters")
|
|
123
|
-
report.add_argument('--kind', type=str, default="
|
|
130
|
+
report.add_argument('--kind', type=str, default="cluster", help="'cluster' for cluster report, 'test' for test report")
|
|
124
131
|
report.set_defaults(func=Tool.report)
|
|
125
132
|
|
|
126
133
|
# test
|
|
@@ -132,6 +139,10 @@ def main():
|
|
|
132
139
|
info = subparsers.add_parser("info", parents=[common], help="display information about provisioner")
|
|
133
140
|
info.set_defaults(func=Tool.info)
|
|
134
141
|
|
|
142
|
+
# s3access
|
|
143
|
+
s3access = subparsers.add_parser("s3access", parents=[common], help="sets AWS credentials by outputting shell commands to 'eval' into current environment")
|
|
144
|
+
s3access.set_defaults(func=Tool.s3access)
|
|
145
|
+
|
|
135
146
|
# authenticate
|
|
136
147
|
authenticate = subparsers.add_parser("authenticate", parents=[common], help="force re-authentication of user account")
|
|
137
148
|
authenticate.set_defaults(func=Tool.authenticate)
|
|
@@ -145,7 +156,8 @@ def main():
|
|
|
145
156
|
# route command
|
|
146
157
|
try:
|
|
147
158
|
result = args.func(tool)
|
|
148
|
-
|
|
159
|
+
if result:
|
|
160
|
+
print(f'{json.dumps(result, indent=2)}')
|
|
149
161
|
except Exception as e:
|
|
150
162
|
if args.verbose: raise
|
|
151
163
|
print(f"Unhandled error: {e}")
|
|
@@ -4,6 +4,7 @@ import boto3
|
|
|
4
4
|
import random
|
|
5
5
|
import string
|
|
6
6
|
import argparse
|
|
7
|
+
import traceback
|
|
7
8
|
from sliderule import sliderule
|
|
8
9
|
from .database import Database, JobState, QueuePriority, JobStatus
|
|
9
10
|
from pathlib import Path
|
|
@@ -27,7 +28,7 @@ class Tool:
|
|
|
27
28
|
# open database
|
|
28
29
|
self.database = Database(args.database)
|
|
29
30
|
# create sliderule session
|
|
30
|
-
self.session = sliderule.create_session(verbose=args.verbose)
|
|
31
|
+
self.session = sliderule.create_session(domain=args.domain, verbose=args.verbose)
|
|
31
32
|
self.session.authenticate() # gives privileges to access SlideRule Runner
|
|
32
33
|
# aws clients
|
|
33
34
|
self.s3 = boto3.client("s3", region_name="us-west-2")
|
|
@@ -36,7 +37,10 @@ class Tool:
|
|
|
36
37
|
def __load_remote_file(self, bucket, key):
|
|
37
38
|
obj = self.s3.get_object(Bucket=bucket, Key=key)
|
|
38
39
|
contents = obj["Body"].read().decode("utf-8")
|
|
39
|
-
|
|
40
|
+
try:
|
|
41
|
+
return json.loads(contents)
|
|
42
|
+
except Exception:
|
|
43
|
+
return contents
|
|
40
44
|
|
|
41
45
|
# Get Results for a Job Run
|
|
42
46
|
# output of job must include the following fields for this to work:
|
|
@@ -51,15 +55,22 @@ class Tool:
|
|
|
51
55
|
bucket = run_url.split("s3://")[-1].split("/")[0]
|
|
52
56
|
prefix = "/".join(run_url.split("s3://")[-1].split("/")[1:])
|
|
53
57
|
receipt = self.__load_remote_file(bucket, f"{prefix}/receipt.json") # {"name": ..., "username": ... "args": <path to arg file>, "environment": ...}
|
|
54
|
-
|
|
58
|
+
args_bucket = receipt["args"].split("s3://")[-1].split("/")[0]
|
|
59
|
+
args_file = "/".join(receipt["args"].split("s3://")[-1].split("/")[1:])
|
|
60
|
+
args_content = self.__load_remote_file(args_bucket, args_file)
|
|
61
|
+
if isinstance(args_content, list):
|
|
62
|
+
args_list = args_content
|
|
63
|
+
elif isinstance(args_content, dict) or isinstance(args_content, str):
|
|
64
|
+
args_list = [args_content]
|
|
55
65
|
for i in tqdm(range(len(args_list)), total=len(args_list), desc=f"{run_url}", unit="granule"):
|
|
66
|
+
result_file = f"{prefix}/result{len(args_list) > 1 and str(i) or ''}.json"
|
|
56
67
|
try:
|
|
57
68
|
result = {
|
|
58
|
-
"file": f"{
|
|
69
|
+
"file": f"s3://{bucket}/{result_file}",
|
|
59
70
|
"environment": receipt["environment"],
|
|
60
71
|
"arg": args_list[i]
|
|
61
72
|
}
|
|
62
|
-
rsps = self.__load_remote_file(bucket,
|
|
73
|
+
rsps = self.__load_remote_file(bucket, result_file)
|
|
63
74
|
try:
|
|
64
75
|
result |= {
|
|
65
76
|
"status": rsps["status"] and JobStatus.SUCCESS or JobStatus.FAILURE,
|
|
@@ -89,7 +100,8 @@ class Tool:
|
|
|
89
100
|
# pull out arguments
|
|
90
101
|
name = self.args.name
|
|
91
102
|
script_file = self.args.script
|
|
92
|
-
|
|
103
|
+
arguments_input = self.args.arguments
|
|
104
|
+
arg_as_str = self.args.arg_as_str
|
|
93
105
|
batch_size = self.args.batch_size
|
|
94
106
|
vcpus = self.args.vcpus
|
|
95
107
|
memory = self.args.memory
|
|
@@ -99,9 +111,12 @@ class Tool:
|
|
|
99
111
|
with open(script_file, "r") as file:
|
|
100
112
|
script = file.read()
|
|
101
113
|
# read arguments
|
|
102
|
-
|
|
103
|
-
arguments = [
|
|
104
|
-
|
|
114
|
+
if arg_as_str:
|
|
115
|
+
arguments = [str(arguments_input)]
|
|
116
|
+
else:
|
|
117
|
+
with open(arguments_input, "r") as file:
|
|
118
|
+
arguments = [line.strip() for line in file.readlines()]
|
|
119
|
+
# process job in batches
|
|
105
120
|
for i in range(0, len(arguments), batch_size):
|
|
106
121
|
# build and check name
|
|
107
122
|
job_name = f"{name}_{i}"
|
|
@@ -111,29 +126,34 @@ class Tool:
|
|
|
111
126
|
# submit & save job
|
|
112
127
|
args_list = arguments[i:i+batch_size]
|
|
113
128
|
rsps = self.session.runner.submit(name=job_name, script=script, args=args_list, optional_args={"vcpus":vcpus, "memory":memory, "image":image, "queue":queue})
|
|
114
|
-
self.database.submissions[job_name] = rsps | {"complete": False}
|
|
129
|
+
self.database.submissions[job_name] = rsps | {"complete": False, "job_size": len(args_list)}
|
|
115
130
|
print(f"Submitted job {job_name} using script {script_file} with {len(args_list)} entries: {rsps}")
|
|
116
131
|
|
|
117
132
|
# Scrape Submissions
|
|
118
133
|
def scrape_submissions(self):
|
|
119
|
-
|
|
134
|
+
output_list = []
|
|
120
135
|
for name,submission in self.database.submissions.items():
|
|
121
136
|
if self.args.name and self.args.name != name: continue
|
|
122
137
|
try:
|
|
123
138
|
if submission["complete"]:
|
|
124
139
|
print(f"Scraping {name} ...")
|
|
125
|
-
for
|
|
140
|
+
for i in range(len(submission["results"])):
|
|
141
|
+
result = submission["results"][i]
|
|
126
142
|
if result["status"] in self.args.status:
|
|
127
|
-
|
|
128
|
-
|
|
143
|
+
if self.args.verbose:
|
|
144
|
+
print(f'{i:-5d}: {result}\n')
|
|
145
|
+
output_list.append(json.dumps(result))
|
|
146
|
+
else:
|
|
147
|
+
print(f'{i:-5d}: {result["arg"]}')
|
|
148
|
+
output_list.append(result["arg"])
|
|
129
149
|
else:
|
|
130
150
|
print(f"Skipping {name} because it is still pending")
|
|
131
151
|
except Exception as e:
|
|
132
152
|
print(f"Failed to scrape {name}: {e}")
|
|
133
153
|
if self.args.output:
|
|
134
154
|
with open(self.args.output, "w") as file:
|
|
135
|
-
for
|
|
136
|
-
file.write(f"{
|
|
155
|
+
for output in output_list:
|
|
156
|
+
file.write(f"{output}\n")
|
|
137
157
|
|
|
138
158
|
# Get Status
|
|
139
159
|
def get_status(self):
|
|
@@ -142,44 +162,52 @@ class Tool:
|
|
|
142
162
|
complete = job["complete"]
|
|
143
163
|
print(f"Statusing {name} ...")
|
|
144
164
|
if not complete:
|
|
145
|
-
|
|
165
|
+
if job["job_size"] > 1: # array processing, use parent job id
|
|
166
|
+
queue_status = self.session.runner.queue(job_id=job["job_id"], queue=queue, verbose=True)
|
|
167
|
+
child_jobs = queue_status["jobs"]
|
|
168
|
+
self.database.submissions[name]["jobs"] = {}
|
|
169
|
+
for child_job in child_jobs:
|
|
170
|
+
self.database.submissions[name]["jobs"][int(child_job["index"])] = child_job
|
|
171
|
+
else: # single job, use name
|
|
172
|
+
queue_status = self.session.runner.queue(name=name, queue=queue, verbose=False)
|
|
173
|
+
# report
|
|
174
|
+
report = queue_status["report"]
|
|
146
175
|
self.database.submissions[name]["status"] = report
|
|
147
|
-
|
|
176
|
+
jobs_in_progress = sum([report[s] for s in [JobState.SUBMITTED, JobState.PENDING, JobState.RUNNABLE, JobState.STARTING, JobState.RUNNING]])
|
|
177
|
+
jobs_complete = sum([report[s] for s in [JobState.SUCCEEDED, JobState.FAILED]])
|
|
178
|
+
if jobs_in_progress == 0 and jobs_complete > 0:
|
|
148
179
|
print(f"Job {name} complete, reading results ...")
|
|
149
180
|
self.database.submissions[name]["complete"] = True
|
|
150
181
|
self.database.submissions[name]["results"] = self.__get_results(job["run_url"])
|
|
151
182
|
else:
|
|
152
183
|
print(f"Job {name} still pending")
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
184
|
+
if self.args.verbose:
|
|
185
|
+
print(",".join([f"{c:>30}" for c in ["NAME"]] + [f"{c:>10}" for c in ["INDEX", "STATUS"]]))
|
|
186
|
+
for name,job in self.database.submissions.items():
|
|
187
|
+
for index, child_job in job["jobs"].items():
|
|
188
|
+
print(",".join([f"{c:>30}" for c in [name]] + [f"{c:>10}" for c in [index, child_job["status"]]]))
|
|
189
|
+
else:
|
|
190
|
+
print(",".join([f"{c:>30}" for c in ["NAME"]] + [f"{c:>10}" for c in list(JobState)]))
|
|
191
|
+
for name,job in self.database.submissions.items():
|
|
192
|
+
print(",".join([f"{c:>30}" for c in [name]] + [f"{c:>10}" for c in [job["status"][state] for state in list(JobState)]]))
|
|
156
193
|
|
|
157
194
|
# Generate Report
|
|
158
195
|
def generate_report(self):
|
|
159
|
-
|
|
160
|
-
duration = {"avg": 0.0, "total": 0.0}
|
|
161
|
-
processed = 0
|
|
162
|
-
pending = 0
|
|
163
|
-
errors = 0
|
|
196
|
+
print(",".join([f"{c:>30}" for c in ["name"]] + [f"{c:>12}" for c in ["processed"]] + [f"{c:>12}" for c in list(JobStatus)] + [f"{c:>12}" for c in ["duration"]]))
|
|
164
197
|
for name,submission in self.database.submissions.items():
|
|
165
198
|
if self.args.name and self.args.name != name: continue
|
|
199
|
+
stats = {status.value: 0 for status in JobStatus}
|
|
200
|
+
duration = {"avg": 0.0, "total": 0.0}
|
|
201
|
+
processed = 0
|
|
166
202
|
if submission["complete"]:
|
|
167
203
|
for result in submission["results"]:
|
|
168
|
-
|
|
169
|
-
|
|
204
|
+
stats[result["status"]] += 1
|
|
205
|
+
if "duration" in result:
|
|
170
206
|
duration["total"] += result["duration"]
|
|
171
207
|
processed += 1
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
pending += 1
|
|
176
|
-
if processed > 0:
|
|
177
|
-
duration["avg"] = duration["total"] / processed
|
|
178
|
-
print("Processed:", processed)
|
|
179
|
-
print("Pending:", pending)
|
|
180
|
-
print("Errors:", errors)
|
|
181
|
-
print("Status:", json.dumps(stats, indent=2))
|
|
182
|
-
print("Duration:", json.dumps(duration, indent=2))
|
|
208
|
+
if processed > 0:
|
|
209
|
+
duration["avg"] = duration["total"] / processed
|
|
210
|
+
print(",".join([f"{c:>30}" for c in [name]] + [f"{c:>12}" for c in [processed]] + [f"{c:>12}" for c in [stats[status] for status in list(JobStatus)]] + [f"{c:>12.3}" for c in [duration["avg"]]]))
|
|
183
211
|
|
|
184
212
|
# Cancel Job
|
|
185
213
|
def cancel_job(self):
|
|
@@ -189,9 +217,21 @@ class Tool:
|
|
|
189
217
|
rsps = self.session.runner.cancel(job_list=[job_id], queue=queue)
|
|
190
218
|
print(f"Cancelled submission {name}: {rsps}")
|
|
191
219
|
|
|
220
|
+
# Get Logs
|
|
221
|
+
def get_logs(self):
|
|
222
|
+
name = self.args.name
|
|
223
|
+
index = self.args.index
|
|
224
|
+
if index is not None:
|
|
225
|
+
job_id = self.database.submissions[name]["jobs"][str(index)]["job_id"]
|
|
226
|
+
else:
|
|
227
|
+
job_id = self.database.submissions[name]["job_id"]
|
|
228
|
+
events = self.session.runner.logs(job_id=job_id)
|
|
229
|
+
for event in events:
|
|
230
|
+
print("->", event)
|
|
231
|
+
|
|
192
232
|
# Finish
|
|
193
233
|
def finish(self):
|
|
194
|
-
if not self.args.dryrun:
|
|
234
|
+
if not self.args.dryrun and "archive" not in self.args:
|
|
195
235
|
# save database
|
|
196
236
|
self.database.write()
|
|
197
237
|
|
|
@@ -207,6 +247,7 @@ def main():
|
|
|
207
247
|
|
|
208
248
|
# options shared by every subcommand; parents= lets them appear after the command name
|
|
209
249
|
common = argparse.ArgumentParser(add_help=False)
|
|
250
|
+
common.add_argument('--domain', type=str, default="slideruleearth.io")
|
|
210
251
|
common.add_argument('--database', type=Path, default=Path.home() / ".cache" / "sliderule" / "runner_database.json")
|
|
211
252
|
common.add_argument('--queue', type=QueuePriority, default=QueuePriority.DEFAULT, choices=list(QueuePriority))
|
|
212
253
|
common.add_argument('--verbose', action='store_true', default=False)
|
|
@@ -222,6 +263,7 @@ def main():
|
|
|
222
263
|
submit.add_argument('name', metavar="<name>")
|
|
223
264
|
submit.add_argument('script', metavar="<script.lua>", type=Path)
|
|
224
265
|
submit.add_argument('arguments', metavar="<arguments.txt>", type=Path)
|
|
266
|
+
submit.add_argument('--arg_as_str', action='store_true', default=False) # changes arguments to be just a string instead of a file
|
|
225
267
|
submit.add_argument('--vcpus', type=int, default=4)
|
|
226
268
|
submit.add_argument('--memory', type=int, default=16000)
|
|
227
269
|
submit.add_argument('--batch_size', type=int, default=10000)
|
|
@@ -240,7 +282,7 @@ def main():
|
|
|
240
282
|
status.set_defaults(func=Tool.get_status)
|
|
241
283
|
|
|
242
284
|
# report
|
|
243
|
-
report = subparsers.add_parser("report", parents=[common], help="generate report of
|
|
285
|
+
report = subparsers.add_parser("report", parents=[common], help="generate report of completed jobs")
|
|
244
286
|
report.add_argument('--name', type=str, default=None) # name of submission
|
|
245
287
|
report.set_defaults(func=Tool.generate_report)
|
|
246
288
|
|
|
@@ -249,6 +291,12 @@ def main():
|
|
|
249
291
|
cancel.add_argument('--name', type=str, required=True) # name of submission
|
|
250
292
|
cancel.set_defaults(func=Tool.cancel_job)
|
|
251
293
|
|
|
294
|
+
# logs
|
|
295
|
+
logs = subparsers.add_parser("logs", parents=[common], help="get log messages for a child job")
|
|
296
|
+
logs.add_argument('--name', type=str, required=True) # name of submission
|
|
297
|
+
logs.add_argument('--index', type=int, default=None) # job index
|
|
298
|
+
logs.set_defaults(func=Tool.get_logs)
|
|
299
|
+
|
|
252
300
|
# parse command line
|
|
253
301
|
args = parser.parse_args()
|
|
254
302
|
|
|
@@ -262,6 +310,7 @@ def main():
|
|
|
262
310
|
except Exception as e:
|
|
263
311
|
if args.verbose: raise
|
|
264
312
|
print(f"Unhandled error: {e}")
|
|
313
|
+
traceback.print_exc()
|
|
265
314
|
|
|
266
315
|
# running via direct invocation
|
|
267
316
|
if __name__ == "__main__": main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|