Skip to content

Commit 9600e08

Browse files
committed
add example for job info
Problem: We do not have good examples for replicating flux job info in Python Solution: Add an interactive demo Signed-off-by: vsoch <[email protected]>
1 parent ab797ae commit 9600e08

12 files changed

+869
-3
lines changed

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ help:
1717
# Catch-all target: route all unknown targets to Sphinx using the new
1818
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
1919
%: Makefile
20-
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
20+
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
21+
cp _build/html/_images/sphx_glr_example_job_submit_api_thumb.png _build/html/_images/sphx_glr_example_job_info_api_thumb.png
-4.13 KB
Binary file not shown.
-1.56 KB
Binary file not shown.
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"\n# Introductory example - Job Info API\n\nThis example will show how to get information\nabout a job after the fact.\n"
8+
]
9+
},
10+
{
11+
"cell_type": "code",
12+
"execution_count": null,
13+
"metadata": {
14+
"collapsed": false
15+
},
16+
"outputs": [],
17+
"source": [
18+
"import json\nimport time\nimport os\nimport flux\nfrom flux.job import JobspecV1\nimport subprocess"
19+
]
20+
},
21+
{
22+
"cell_type": "markdown",
23+
"metadata": {},
24+
"source": [
25+
"Here we instantiate a flux handle. This will connect to the running flux instance.\nIf you were running this on a cluster with Flux, you'd likely already be able to\nconnect. If you are testing out on your own, you might need to do flux start --test-size=4\n\n"
26+
]
27+
},
28+
{
29+
"cell_type": "code",
30+
"execution_count": null,
31+
"metadata": {
32+
"collapsed": false
33+
},
34+
"outputs": [],
35+
"source": [
36+
"handle = flux.Flux()"
37+
]
38+
},
39+
{
40+
"cell_type": "markdown",
41+
"metadata": {},
42+
"source": [
43+
"This is a new jobspec, or a recipe for a flux job. You'll notice we are providing a command\ndirectly, along with tasks, nodes, and cores per task. You could also provide a script here.\nIf we were doing this on the command line, it would be equivalent to what is generated by:\nflux submit --ntasks=4 --nodes=2 --cores-per-task=2 sleep 10\n\n"
44+
]
45+
},
46+
{
47+
"cell_type": "code",
48+
"execution_count": null,
49+
"metadata": {
50+
"collapsed": false
51+
},
52+
"outputs": [],
53+
"source": [
54+
"jobspec = JobspecV1.from_command(\n command=[\"hostname\"], num_tasks=1, num_nodes=1, cores_per_task=1\n)"
55+
]
56+
},
57+
{
58+
"cell_type": "markdown",
59+
"metadata": {},
60+
"source": [
61+
"This is how we set the \"current working directory\" (cwd) for the job\n\n"
62+
]
63+
},
64+
{
65+
"cell_type": "code",
66+
"execution_count": null,
67+
"metadata": {
68+
"collapsed": false
69+
},
70+
"outputs": [],
71+
"source": [
72+
"jobspec.cwd = os.getcwd()"
73+
]
74+
},
75+
{
76+
"cell_type": "markdown",
77+
"metadata": {},
78+
"source": [
79+
"This is how we set the job environment\n\n"
80+
]
81+
},
82+
{
83+
"cell_type": "code",
84+
"execution_count": null,
85+
"metadata": {
86+
"collapsed": false
87+
},
88+
"outputs": [],
89+
"source": [
90+
"jobspec.environment = dict(os.environ)"
91+
]
92+
},
93+
{
94+
"cell_type": "markdown",
95+
"metadata": {},
96+
"source": [
97+
"Let's submit the job! We will get the job id.\n\n"
98+
]
99+
},
100+
{
101+
"cell_type": "code",
102+
"execution_count": null,
103+
"metadata": {
104+
"collapsed": false
105+
},
106+
"outputs": [],
107+
"source": [
108+
"jobid = flux.job.submit(handle, jobspec)\ntime.sleep(2)"
109+
]
110+
},
111+
{
112+
"cell_type": "markdown",
113+
"metadata": {},
114+
"source": [
115+
"Now let's say we store that jobid somewhere how do we get info later?\nWe know that if we ran flux jobs -a on the command line, we'd see the job\n\n"
116+
]
117+
},
118+
{
119+
"cell_type": "code",
120+
"execution_count": null,
121+
"metadata": {
122+
"collapsed": false
123+
},
124+
"outputs": [],
125+
"source": [
126+
"res = subprocess.getoutput('flux jobs -a')\nprint(res)"
127+
]
128+
},
129+
{
130+
"cell_type": "markdown",
131+
"metadata": {},
132+
"source": [
133+
"And if you are an expert user, you know that you can see metadata for a job\nThis command, without a key, will show the keys available to you\n\n"
134+
]
135+
},
136+
{
137+
"cell_type": "code",
138+
"execution_count": null,
139+
"metadata": {
140+
"collapsed": false
141+
},
142+
"outputs": [],
143+
"source": [
144+
"res = subprocess.getoutput(f'flux job info {jobid} | true')\nprint(res)"
145+
]
146+
},
147+
{
148+
"cell_type": "markdown",
149+
"metadata": {},
150+
"source": [
151+
"And since the underlying logic here is pinging the flux KVS or key value store,\nwe can select one of those keys to view. For example, here is the jobspec\n\n"
152+
]
153+
},
154+
{
155+
"cell_type": "code",
156+
"execution_count": null,
157+
"metadata": {
158+
"collapsed": false
159+
},
160+
"outputs": [],
161+
"source": [
162+
"res = subprocess.getoutput(f'flux job info {jobid} jobspec')\nprint(res)"
163+
]
164+
},
165+
{
166+
"cell_type": "markdown",
167+
"metadata": {},
168+
"source": [
169+
"This is great, but ideally we can get this metadata directly from Python.\nFirst, here is a way to get basic jobinfo. Given we start with a string jobid,\nwe will first want to parse it back into a Flux JobID, and then prepare\na payload to the Job List RPC to say \"give me all the attributes back\"\n\n"
170+
]
171+
},
172+
{
173+
"cell_type": "code",
174+
"execution_count": null,
175+
"metadata": {
176+
"collapsed": false
177+
},
178+
"outputs": [],
179+
"source": [
180+
"fluxjob = flux.job.JobID(jobid)\npayload = {\"id\": fluxjob, \"attrs\": [\"all\"]}\nrpc = flux.job.list.JobListIdRPC(handle, \"job-list.list-id\", payload)\njobinfo = rpc.get_job()\nprint(json.dumps(jobinfo, indent=4))"
181+
]
182+
},
183+
{
184+
"cell_type": "markdown",
185+
"metadata": {},
186+
"source": [
187+
"You can get less commonly used (and thus exposed) metadata like this\nsuch as the emoji state!\n\n"
188+
]
189+
},
190+
{
191+
"cell_type": "code",
192+
"execution_count": null,
193+
"metadata": {
194+
"collapsed": false
195+
},
196+
"outputs": [],
197+
"source": [
198+
"info = rpc.get_jobinfo()\nprint(info.__dict__)"
199+
]
200+
},
201+
{
202+
"cell_type": "markdown",
203+
"metadata": {},
204+
"source": [
205+
"But for either of the above approaches, we aren't getting anything back about our\noriginal jobspec! That's because we need to query the KVS for that. Notice here we\nhave metadata like the current working directory (cwd)\n\n"
206+
]
207+
},
208+
{
209+
"cell_type": "code",
210+
"execution_count": null,
211+
"metadata": {
212+
"collapsed": false
213+
},
214+
"outputs": [],
215+
"source": [
216+
"kvs = flux.job.job_kvs(handle, jobid)\njobspec = kvs.get('jobspec')\nprint(json.dumps(jobspec))\ntime.sleep(2)"
217+
]
218+
},
219+
{
220+
"cell_type": "markdown",
221+
"metadata": {},
222+
"source": [
223+
"Finally, to watch (or stream) output, you can do the following.\nEach line here is a json structure that you can further parse.\nAs an example, if \"data\" is present as a key, this usually is output\n\n"
224+
]
225+
},
226+
{
227+
"cell_type": "code",
228+
"execution_count": null,
229+
"metadata": {
230+
"collapsed": false
231+
},
232+
"outputs": [],
233+
"source": [
234+
"for line in flux.job.event_watch(handle, jobid, \"guest.output\"):\n print(line)"
235+
]
236+
}
237+
],
238+
"metadata": {
239+
"kernelspec": {
240+
"display_name": "Python 3",
241+
"language": "python",
242+
"name": "python3"
243+
},
244+
"language_info": {
245+
"codemirror_mode": {
246+
"name": "ipython",
247+
"version": 3
248+
},
249+
"file_extension": ".py",
250+
"mimetype": "text/x-python",
251+
"name": "python",
252+
"nbconvert_exporter": "python",
253+
"pygments_lexer": "ipython3",
254+
"version": "3.8.10"
255+
}
256+
},
257+
"nbformat": 4,
258+
"nbformat_minor": 0
259+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
Introductory example - Job Info API
4+
===================================
5+
6+
This example will show how to get information
7+
about a job after the fact.
8+
"""
9+
10+
11+
import json
12+
import time
13+
import os
14+
import flux
15+
from flux.job import JobspecV1
16+
import subprocess
17+
18+
#%%
19+
# Here we instantiate a flux handle. This will connect to the running flux instance.
20+
# If you were running this on a cluster with Flux, you'd likely already be able to
21+
# connect. If you are testing out on your own, you might need to do flux start --test-size=4
22+
handle = flux.Flux()
23+
24+
#%%
25+
# This is a new jobspec, or a recipe for a flux job. You'll notice we are providing a command
26+
# directly, along with tasks, nodes, and cores per task. You could also provide a script here.
27+
# If we were doing this on the command line, it would be equivalent to what is generated by:
28+
# flux submit --ntasks=4 --nodes=2 --cores-per-task=2 sleep 10
29+
jobspec = JobspecV1.from_command(
30+
command=["hostname"], num_tasks=1, num_nodes=1, cores_per_task=1
31+
)
32+
33+
#%%
34+
# This is how we set the "current working directory" (cwd) for the job
35+
jobspec.cwd = os.getcwd()
36+
37+
#%%
38+
# This is how we set the job environment
39+
jobspec.environment = dict(os.environ)
40+
41+
#%%
42+
# Let's submit the job! We will get the job id.
43+
jobid = flux.job.submit(handle, jobspec)
44+
time.sleep(2)
45+
46+
#%%
47+
# Now let's say we store that jobid somewhere how do we get info later?
48+
# We know that if we ran flux jobs -a on the command line, we'd see the job
49+
res = subprocess.getoutput('flux jobs -a')
50+
print(res)
51+
52+
#%%
53+
# And if you are an expert user, you know that you can see metadata for a job
54+
# This command, without a key, will show the keys available to you
55+
res = subprocess.getoutput(f'flux job info {jobid} | true')
56+
print(res)
57+
58+
#%%
59+
# And since the underlying logic here is pinging the flux KVS or key value store,
60+
# we can select one of those keys to view. For example, here is the jobspec
61+
res = subprocess.getoutput(f'flux job info {jobid} jobspec')
62+
print(res)
63+
64+
#%%
65+
# This is great, but ideally we can get this metadata directly from Python.
66+
# First, here is a way to get basic jobinfo. Given we start with a string jobid,
67+
# we will first want to parse it back into a Flux JobID, and then prepare
68+
# a payload to the Job List RPC to say "give me all the attributes back"
69+
fluxjob = flux.job.JobID(jobid)
70+
payload = {"id": fluxjob, "attrs": ["all"]}
71+
rpc = flux.job.list.JobListIdRPC(handle, "job-list.list-id", payload)
72+
jobinfo = rpc.get_job()
73+
print(json.dumps(jobinfo, indent=4))
74+
75+
#%%
76+
# You can get less commonly used (and thus exposed) metadata like this
77+
# such as the emoji state!
78+
info = rpc.get_jobinfo()
79+
print(info.__dict__)
80+
81+
#%%
82+
# But for either of the above approaches, we aren't getting anything back about our
83+
# original jobspec! That's because we need to query the KVS for that. Notice here we
84+
# have metadata like the current working directory (cwd)
85+
kvs = flux.job.job_kvs(handle, jobid)
86+
jobspec = kvs.get('jobspec')
87+
print(json.dumps(jobspec))
88+
time.sleep(2)
89+
90+
#%%
91+
# Finally, to watch (or stream) output, you can do the following.
92+
# Each line here is a json structure that you can further parse.
93+
# As an example, if "data" is present as a key, this usually is output
94+
for line in flux.job.event_watch(handle, jobid, "guest.output"):
95+
print(line)
96+
97+
98+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
553b1d62e9c0adeedf1804adf3134c46

0 commit comments

Comments
 (0)