From 9600e08dae0b9cfdf97b96a06f0d98d69431d63c Mon Sep 17 00:00:00 2001 From: vsoch Date: Tue, 25 Apr 2023 22:24:45 -0600 Subject: [PATCH 1/3] 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 --- Makefile | 3 +- auto_examples/auto_examples_jupyter.zip | Bin 4225 -> 0 bytes auto_examples/auto_examples_python.zip | Bin 1596 -> 0 bytes auto_examples/example_job_info_api.ipynb | 259 ++++++++++++ auto_examples/example_job_info_api.py | 98 +++++ auto_examples/example_job_info_api.py.md5 | 1 + auto_examples/example_job_info_api.rst | 388 ++++++++++++++++++ .../example_job_info_api_codeobj.pickle | Bin 0 -> 5842 bytes .../sphx_glr_example_job_info_api_thumb.png | Bin 0 -> 26677 bytes auto_examples/index.rst | 18 + auto_examples/sg_execution_times.rst | 7 +- examples/example_job_info_api.py | 98 +++++ 12 files changed, 869 insertions(+), 3 deletions(-) delete mode 100644 auto_examples/auto_examples_jupyter.zip delete mode 100644 auto_examples/auto_examples_python.zip create mode 100644 auto_examples/example_job_info_api.ipynb create mode 100644 auto_examples/example_job_info_api.py create mode 100644 auto_examples/example_job_info_api.py.md5 create mode 100644 auto_examples/example_job_info_api.rst create mode 100644 auto_examples/example_job_info_api_codeobj.pickle create mode 100644 auto_examples/images/thumb/sphx_glr_example_job_info_api_thumb.png create mode 100644 examples/example_job_info_api.py diff --git a/Makefile b/Makefile index 41c270bb..bad63742 100644 --- a/Makefile +++ b/Makefile @@ -17,4 +17,5 @@ help: # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + cp _build/html/_images/sphx_glr_example_job_submit_api_thumb.png _build/html/_images/sphx_glr_example_job_info_api_thumb.png \ No newline at end of file diff --git a/auto_examples/auto_examples_jupyter.zip b/auto_examples/auto_examples_jupyter.zip deleted file mode 100644 index 13b1be2ae51f829c333edc7d382e2f8db3ef981b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4225 zcmd^D&2Aev5Kfx5hg{l&k2y@XDB=Lp#(#&xfDXL`b<%@@7AcBsAW*v_i8Ytpl3dGb zkVDUXoxVvQqOZ|!xRmUqKY$UyNKzT{?hd)bnfX2rXLS1X&c{2qF=x42tQ`PBGcWXO)d6%fD*_coq({nr!iAX+4JdNv)F;vv9 z;bOEI;tQ_(sGXhGuD1~rY;VkVxaFuC6&2(gZKBT^qq@Ki#B*f=XKk;x@3}s9eN3jj z*b0N|eJwT&<|Wg2#Tca%(S=ozp%_=vGzwSz_{8{5x_R^iJ_Fh`^PYYM} z3p%mUyQZ(B^J~h%r7=BmGx{!tm%ctdscdDxU+S=(u+qj*Sh^L-H#!=iAVt8XvVG8Y zL2=3S*z^~4x{ga{X{K!hc_=G;4F}&XtaRHsAIXKv=G!?utnA#omgKfX04NV3h@s>9 z+5R=$eH%Q#Vb8Q;kSIiDqgIiDh*(8SWgEjKB@`2SwX>G%DBuZDzPDCDOS1ew@)7&<(G)JYq)da;aTnHw>-af6L;5kQQ`^6EI~UwCpPG`&Ws-z{bh2+?F< z(PMzN`|ujhybW&>DFCTLkXN#Ug|#Dc9um*B?wIE3A&~>)CH)ALkpirXx=x%E6g%(E zwQO-=hHG1JuELgwifGm&kilUnQ6;Ky7DjlzVO%f|^F0UZz$F<=3qaPJ2CoH|RSTU% z?R6Jv2|^C^1mP1q8z(DoxG9{@Hh_$lN2DXIl0uM?fA00UGHet7S4$NM{x8_F$EIm$ zwm!z5mW`7W+jwk~&66WgE}146Da73Q;i39qsSE*Hp|502j9ztdsy)bi*iKeBVY!931=Vt{?V#-($_3*jl_2~N0Y%4@x~<}B z8TrcX-0P7 zN+d7sFAmGE%Kg0@FF2DNDeMe|CfZ>va3nqRRxoX~b)t3e?yJ+Mw?Eps^Y1?qq~8wk c@AqHj?!SI8IDPubr!wa@{+{4^|8sfz3)=;$rvLx| diff --git a/auto_examples/auto_examples_python.zip b/auto_examples/auto_examples_python.zip deleted file mode 100644 index 6ef22f8a13e585eb679201c7d4160554fee920c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1596 zcma)7O^@6}5S0?)ErC zxbScI0sJ5S3a{FpRYVjiUd>FqKVH3huex@9bN|za3wgeDZ(n}?_Lo2Zey~``=L^j7 z8{PFSZ+>^%O&GSFi5uOUvOk_t@uZ;2)yD3h(GYjVH!`ELEK}DudRGrsbpA*)0u}Uw z+tN=7gT7r~tJTLmO1)@IxC0qY+fr!UNb-q}CQhUY1l15ghGN6?ydB=q`WPE$X=|*I z3}xb`l<9isBe`%I?XX$xyz59tC6H*|1ID4}>gAgXI&p@3ntr^5e<-Y=<Jjf}63$worM~M{1z^Yy+H4F4Rt9pF9xrW)=|La!w}Fm=Ga4zaGp%w``Nq zW1}NMfjwLHsE(Sp$7kT45}u2-C~RWAdJ1V1@zoVZiFX0sg{)#Kw9GwSUZ`0}Xnt3a zrPS+YX0=p=XMS1znx%tTPTA!}_8YVry3HJLdYNP9lT;goOzW#F^`4V*&7ewPWCt0t zYVclC9i6`wjV4cU&of$9V|||dm7U;x;$30^++8U595H^sUj7#aXNm2>cxO8VyTf^B z4tTYx;u4_q>>c$VIX(`j#xPU)ieAB5X&#`EGLaajE>-W1jZ3jdE`B*#w491OKb7z~ zz~>H-N_;A(Mjv3m4hFYy%Pg05Dyf{f+?P3(U&E7rh>U(omsc041C%>e+Cq7B4?95F z)DY9fMEoGPFWtQ{Ak|mn|q%;y#L__pE^Ij{`pHrA8hyQn+KoC QhI{z?4!{56|HESOA6P;KCjbBd diff --git a/auto_examples/example_job_info_api.ipynb b/auto_examples/example_job_info_api.ipynb new file mode 100644 index 00000000..3df22dbc --- /dev/null +++ b/auto_examples/example_job_info_api.ipynb @@ -0,0 +1,259 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n# Introductory example - Job Info API\n\nThis example will show how to get information\nabout a job after the fact.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import json\nimport time\nimport os\nimport flux\nfrom flux.job import JobspecV1\nimport subprocess" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "handle = flux.Flux()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "jobspec = JobspecV1.from_command(\n command=[\"hostname\"], num_tasks=1, num_nodes=1, cores_per_task=1\n)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is how we set the \"current working directory\" (cwd) for the job\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "jobspec.cwd = os.getcwd()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is how we set the job environment\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "jobspec.environment = dict(os.environ)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's submit the job! We will get the job id.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "jobid = flux.job.submit(handle, jobspec)\ntime.sleep(2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "res = subprocess.getoutput('flux jobs -a')\nprint(res)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "res = subprocess.getoutput(f'flux job info {jobid} | true')\nprint(res)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "res = subprocess.getoutput(f'flux job info {jobid} jobspec')\nprint(res)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "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))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can get less commonly used (and thus exposed) metadata like this\nsuch as the emoji state!\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "info = rpc.get_jobinfo()\nprint(info.__dict__)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "kvs = flux.job.job_kvs(handle, jobid)\njobspec = kvs.get('jobspec')\nprint(json.dumps(jobspec))\ntime.sleep(2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "for line in flux.job.event_watch(handle, jobid, \"guest.output\"):\n print(line)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.10" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/auto_examples/example_job_info_api.py b/auto_examples/example_job_info_api.py new file mode 100644 index 00000000..4ba3d335 --- /dev/null +++ b/auto_examples/example_job_info_api.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +""" +Introductory example - Job Info API +=================================== + +This example will show how to get information +about a job after the fact. +""" + + +import json +import time +import os +import flux +from flux.job import JobspecV1 +import subprocess + +#%% +# Here we instantiate a flux handle. This will connect to the running flux instance. +# If you were running this on a cluster with Flux, you'd likely already be able to +# connect. If you are testing out on your own, you might need to do flux start --test-size=4 +handle = flux.Flux() + +#%% +# This is a new jobspec, or a recipe for a flux job. You'll notice we are providing a command +# directly, along with tasks, nodes, and cores per task. You could also provide a script here. +# If we were doing this on the command line, it would be equivalent to what is generated by: +# flux submit --ntasks=4 --nodes=2 --cores-per-task=2 sleep 10 +jobspec = JobspecV1.from_command( + command=["hostname"], num_tasks=1, num_nodes=1, cores_per_task=1 +) + +#%% +# This is how we set the "current working directory" (cwd) for the job +jobspec.cwd = os.getcwd() + +#%% +# This is how we set the job environment +jobspec.environment = dict(os.environ) + +#%% +# Let's submit the job! We will get the job id. +jobid = flux.job.submit(handle, jobspec) +time.sleep(2) + +#%% +# Now let's say we store that jobid somewhere how do we get info later? +# We know that if we ran flux jobs -a on the command line, we'd see the job +res = subprocess.getoutput('flux jobs -a') +print(res) + +#%% +# And if you are an expert user, you know that you can see metadata for a job +# This command, without a key, will show the keys available to you +res = subprocess.getoutput(f'flux job info {jobid} | true') +print(res) + +#%% +# And since the underlying logic here is pinging the flux KVS or key value store, +# we can select one of those keys to view. For example, here is the jobspec +res = subprocess.getoutput(f'flux job info {jobid} jobspec') +print(res) + +#%% +# This is great, but ideally we can get this metadata directly from Python. +# First, here is a way to get basic jobinfo. Given we start with a string jobid, +# we will first want to parse it back into a Flux JobID, and then prepare +# a payload to the Job List RPC to say "give me all the attributes back" +fluxjob = flux.job.JobID(jobid) +payload = {"id": fluxjob, "attrs": ["all"]} +rpc = flux.job.list.JobListIdRPC(handle, "job-list.list-id", payload) +jobinfo = rpc.get_job() +print(json.dumps(jobinfo, indent=4)) + +#%% +# You can get less commonly used (and thus exposed) metadata like this +# such as the emoji state! +info = rpc.get_jobinfo() +print(info.__dict__) + +#%% +# But for either of the above approaches, we aren't getting anything back about our +# original jobspec! That's because we need to query the KVS for that. Notice here we +# have metadata like the current working directory (cwd) +kvs = flux.job.job_kvs(handle, jobid) +jobspec = kvs.get('jobspec') +print(json.dumps(jobspec)) +time.sleep(2) + +#%% +# Finally, to watch (or stream) output, you can do the following. +# Each line here is a json structure that you can further parse. +# As an example, if "data" is present as a key, this usually is output +for line in flux.job.event_watch(handle, jobid, "guest.output"): + print(line) + + + diff --git a/auto_examples/example_job_info_api.py.md5 b/auto_examples/example_job_info_api.py.md5 new file mode 100644 index 00000000..c06d4931 --- /dev/null +++ b/auto_examples/example_job_info_api.py.md5 @@ -0,0 +1 @@ +553b1d62e9c0adeedf1804adf3134c46 \ No newline at end of file diff --git a/auto_examples/example_job_info_api.rst b/auto_examples/example_job_info_api.rst new file mode 100644 index 00000000..5a8f0448 --- /dev/null +++ b/auto_examples/example_job_info_api.rst @@ -0,0 +1,388 @@ + +.. DO NOT EDIT. +.. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. +.. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: +.. "auto_examples/example_job_info_api.py" +.. LINE NUMBERS ARE GIVEN BELOW. + +.. only:: html + + .. note:: + :class: sphx-glr-download-link-note + + :ref:`Go to the end ` + to download the full example code + +.. rst-class:: sphx-glr-example-title + +.. _sphx_glr_auto_examples_example_job_info_api.py: + + +Introductory example - Job Info API +=================================== + +This example will show how to get information +about a job after the fact. + +.. GENERATED FROM PYTHON SOURCE LINES 9-18 + +.. code-block:: default + + + + import json + import time + import os + import flux + from flux.job import JobspecV1 + import subprocess + + + + + + + + +.. GENERATED FROM PYTHON SOURCE LINES 19-22 + +Here we instantiate a flux handle. This will connect to the running flux instance. +If you were running this on a cluster with Flux, you'd likely already be able to +connect. If you are testing out on your own, you might need to do flux start --test-size=4 + +.. GENERATED FROM PYTHON SOURCE LINES 22-24 + +.. code-block:: default + + handle = flux.Flux() + + + + + + + + +.. GENERATED FROM PYTHON SOURCE LINES 25-29 + +This is a new jobspec, or a recipe for a flux job. You'll notice we are providing a command +directly, along with tasks, nodes, and cores per task. You could also provide a script here. +If we were doing this on the command line, it would be equivalent to what is generated by: +flux submit --ntasks=4 --nodes=2 --cores-per-task=2 sleep 10 + +.. GENERATED FROM PYTHON SOURCE LINES 29-33 + +.. code-block:: default + + jobspec = JobspecV1.from_command( + command=["hostname"], num_tasks=1, num_nodes=1, cores_per_task=1 + ) + + + + + + + + +.. GENERATED FROM PYTHON SOURCE LINES 34-35 + +This is how we set the "current working directory" (cwd) for the job + +.. GENERATED FROM PYTHON SOURCE LINES 35-37 + +.. code-block:: default + + jobspec.cwd = os.getcwd() + + + + + + + + +.. GENERATED FROM PYTHON SOURCE LINES 38-39 + +This is how we set the job environment + +.. GENERATED FROM PYTHON SOURCE LINES 39-41 + +.. code-block:: default + + jobspec.environment = dict(os.environ) + + + + + + + + +.. GENERATED FROM PYTHON SOURCE LINES 42-43 + +Let's submit the job! We will get the job id. + +.. GENERATED FROM PYTHON SOURCE LINES 43-46 + +.. code-block:: default + + jobid = flux.job.submit(handle, jobspec) + time.sleep(2) + + + + + + + + +.. GENERATED FROM PYTHON SOURCE LINES 47-49 + +Now let's say we store that jobid somewhere how do we get info later? +We know that if we ran flux jobs -a on the command line, we'd see the job + +.. GENERATED FROM PYTHON SOURCE LINES 49-52 + +.. code-block:: default + + res = subprocess.getoutput('flux jobs -a') + print(res) + + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + JOBID USER NAME ST NTASKS NNODES TIME INFO + ƒkFU3pT vscode hostname CD 1 1 0.028s 610b9ad16799 + + + + +.. GENERATED FROM PYTHON SOURCE LINES 53-55 + +And if you are an expert user, you know that you can see metadata for a job +This command, without a key, will show the keys available to you + +.. GENERATED FROM PYTHON SOURCE LINES 55-58 + +.. code-block:: default + + res = subprocess.getoutput(f'flux job info {jobid} | true') + print(res) + + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + Missing lookup key(s), common keys: + J + R + eventlog + jobspec + guest.exec.eventlog + guest.input + guest.output + + + + +.. GENERATED FROM PYTHON SOURCE LINES 59-61 + +And since the underlying logic here is pinging the flux KVS or key value store, +we can select one of those keys to view. For example, here is the jobspec + +.. GENERATED FROM PYTHON SOURCE LINES 61-64 + +.. code-block:: default + + res = subprocess.getoutput(f'flux job info {jobid} jobspec') + print(res) + + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + {"resources":[{"type":"node","count":1,"with":[{"type":"slot","count":1,"with":[{"type":"core","count":1}],"label":"task"}]}],"tasks":[{"command":["hostname"],"slot":"task","count":{"per_slot":1}}],"attributes":{"system":{"duration":0,"cwd":"/workspaces/flux-docs/examples"}},"version":1} + + + + +.. GENERATED FROM PYTHON SOURCE LINES 65-69 + +This is great, but ideally we can get this metadata directly from Python. +First, here is a way to get basic jobinfo. Given we start with a string jobid, +we will first want to parse it back into a Flux JobID, and then prepare +a payload to the Job List RPC to say "give me all the attributes back" + +.. GENERATED FROM PYTHON SOURCE LINES 69-75 + +.. code-block:: default + + fluxjob = flux.job.JobID(jobid) + payload = {"id": fluxjob, "attrs": ["all"]} + rpc = flux.job.list.JobListIdRPC(handle, "job-list.list-id", payload) + jobinfo = rpc.get_job() + print(json.dumps(jobinfo, indent=4)) + + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + { + "id": 28387049472, + "userid": 1000, + "urgency": 16, + "priority": 16, + "t_submit": 1682482856.185918, + "t_depend": 1682482856.1989403, + "t_run": 1682482856.2132757, + "t_cleanup": 1682482856.2413428, + "t_inactive": 1682482856.2439826, + "state": 64, + "name": "hostname", + "ntasks": 1, + "ncores": 1, + "duration": 0.0, + "nnodes": 1, + "ranks": "0", + "nodelist": "610b9ad16799", + "success": true, + "exception_occurred": false, + "result": 1, + "expiration": 4836082856.0, + "waitstatus": 0 + } + + + + +.. GENERATED FROM PYTHON SOURCE LINES 76-78 + +You can get less commonly used (and thus exposed) metadata like this +such as the emoji state! + +.. GENERATED FROM PYTHON SOURCE LINES 78-81 + +.. code-block:: default + + info = rpc.get_jobinfo() + print(info.__dict__) + + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + {'_t_depend': 1682482856.1989403, '_t_run': 1682482856.2132757, '_t_cleanup': 1682482856.2413428, '_t_inactive': 1682482856.2439826, '_duration': 0.0, '_expiration': 4836082856.0, '_name': 'hostname', '_queue': '', '_ntasks': 1, '_ncores': 1, '_nnodes': 1, '_priority': 16, '_ranks': '0', '_nodelist': '610b9ad16799', '_success': True, '_waitstatus': 0, '_id': JobID(28387049472), '_userid': 1000, '_urgency': 16, '_t_submit': 1682482856.185918, '_exception_occurred': False, '_state_id': 64, '_result_id': 1, '_exception': , '_annotations': , '_sched': , '_user': , '_dependencies': []} + + + + +.. GENERATED FROM PYTHON SOURCE LINES 82-85 + +But for either of the above approaches, we aren't getting anything back about our +original jobspec! That's because we need to query the KVS for that. Notice here we +have metadata like the current working directory (cwd) + +.. GENERATED FROM PYTHON SOURCE LINES 85-90 + +.. code-block:: default + + kvs = flux.job.job_kvs(handle, jobid) + jobspec = kvs.get('jobspec') + print(json.dumps(jobspec)) + time.sleep(2) + + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + {"resources": [{"type": "node", "count": 1, "with": [{"type": "slot", "count": 1, "with": [{"type": "core", "count": 1}], "label": "task"}]}], "tasks": [{"command": ["hostname"], "slot": "task", "count": {"per_slot": 1}}], "attributes": {"system": {"duration": 0, "cwd": "/workspaces/flux-docs/examples"}}, "version": 1} + + + + +.. GENERATED FROM PYTHON SOURCE LINES 91-94 + +Finally, to watch (or stream) output, you can do the following. +Each line here is a json structure that you can further parse. +As an example, if "data" is present as a key, this usually is output + +.. GENERATED FROM PYTHON SOURCE LINES 94-99 + +.. code-block:: default + + for line in flux.job.event_watch(handle, jobid, "guest.output"): + print(line) + + + + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + 1682482856.23028: header {'version': 1, 'encoding': {'stdout': 'UTF-8', 'stderr': 'UTF-8'}, 'count': {'stdout': 1, 'stderr': 1}, 'options': {}} + 1682482856.23757: data {'stream': 'stderr', 'rank': '0', 'eof': True} + 1682482856.23760: data {'stream': 'stdout', 'rank': '0', 'data': '610b9ad16799\n'} + 1682482856.23763: data {'stream': 'stdout', 'rank': '0', 'eof': True} + + + + + +.. rst-class:: sphx-glr-timing + + **Total running time of the script:** ( 0 minutes 4.381 seconds) + + +.. _sphx_glr_download_auto_examples_example_job_info_api.py: + +.. only:: html + + .. container:: sphx-glr-footer sphx-glr-footer-example + + + + + .. container:: sphx-glr-download sphx-glr-download-python + + :download:`Download Python source code: example_job_info_api.py ` + + .. container:: sphx-glr-download sphx-glr-download-jupyter + + :download:`Download Jupyter notebook: example_job_info_api.ipynb ` + + +.. only:: html + + .. rst-class:: sphx-glr-signature + + `Gallery generated by Sphinx-Gallery `_ diff --git a/auto_examples/example_job_info_api_codeobj.pickle b/auto_examples/example_job_info_api_codeobj.pickle new file mode 100644 index 0000000000000000000000000000000000000000..1d5d5c4f3449938a7cc92b6ca1bf44df99a049f3 GIT binary patch literal 5842 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 +.. raw:: html + +
+ +.. only:: html + + .. image:: /auto_examples/images/thumb/sphx_glr_example_job_info_api_thumb.png + :alt: + + :ref:`sphx_glr_auto_examples_example_job_info_api.py` + +.. raw:: html + +
Introductory example - Job Info API
+
+ + .. raw:: html @@ -49,6 +66,7 @@ of using the Flux Python API. :hidden: /auto_examples/example_job_submit_api + /auto_examples/example_job_info_api .. only:: html diff --git a/auto_examples/sg_execution_times.rst b/auto_examples/sg_execution_times.rst index edc588df..97e10eb0 100644 --- a/auto_examples/sg_execution_times.rst +++ b/auto_examples/sg_execution_times.rst @@ -3,10 +3,13 @@ .. _sphx_glr_auto_examples_sg_execution_times: + Computation times ================= -**00:00.232** total execution time for **auto_examples** files: +**00:04.381** total execution time for **auto_examples** files: +-----------------------------------------------------------------------------------------+-----------+--------+ -| :ref:`sphx_glr_auto_examples_example_job_submit_api.py` (``example_job_submit_api.py``) | 00:00.232 | 0.0 MB | +| :ref:`sphx_glr_auto_examples_example_job_info_api.py` (``example_job_info_api.py``) | 00:04.381 | 0.0 MB | ++-----------------------------------------------------------------------------------------+-----------+--------+ +| :ref:`sphx_glr_auto_examples_example_job_submit_api.py` (``example_job_submit_api.py``) | 00:00.000 | 0.0 MB | +-----------------------------------------------------------------------------------------+-----------+--------+ diff --git a/examples/example_job_info_api.py b/examples/example_job_info_api.py new file mode 100644 index 00000000..4ba3d335 --- /dev/null +++ b/examples/example_job_info_api.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +""" +Introductory example - Job Info API +=================================== + +This example will show how to get information +about a job after the fact. +""" + + +import json +import time +import os +import flux +from flux.job import JobspecV1 +import subprocess + +#%% +# Here we instantiate a flux handle. This will connect to the running flux instance. +# If you were running this on a cluster with Flux, you'd likely already be able to +# connect. If you are testing out on your own, you might need to do flux start --test-size=4 +handle = flux.Flux() + +#%% +# This is a new jobspec, or a recipe for a flux job. You'll notice we are providing a command +# directly, along with tasks, nodes, and cores per task. You could also provide a script here. +# If we were doing this on the command line, it would be equivalent to what is generated by: +# flux submit --ntasks=4 --nodes=2 --cores-per-task=2 sleep 10 +jobspec = JobspecV1.from_command( + command=["hostname"], num_tasks=1, num_nodes=1, cores_per_task=1 +) + +#%% +# This is how we set the "current working directory" (cwd) for the job +jobspec.cwd = os.getcwd() + +#%% +# This is how we set the job environment +jobspec.environment = dict(os.environ) + +#%% +# Let's submit the job! We will get the job id. +jobid = flux.job.submit(handle, jobspec) +time.sleep(2) + +#%% +# Now let's say we store that jobid somewhere how do we get info later? +# We know that if we ran flux jobs -a on the command line, we'd see the job +res = subprocess.getoutput('flux jobs -a') +print(res) + +#%% +# And if you are an expert user, you know that you can see metadata for a job +# This command, without a key, will show the keys available to you +res = subprocess.getoutput(f'flux job info {jobid} | true') +print(res) + +#%% +# And since the underlying logic here is pinging the flux KVS or key value store, +# we can select one of those keys to view. For example, here is the jobspec +res = subprocess.getoutput(f'flux job info {jobid} jobspec') +print(res) + +#%% +# This is great, but ideally we can get this metadata directly from Python. +# First, here is a way to get basic jobinfo. Given we start with a string jobid, +# we will first want to parse it back into a Flux JobID, and then prepare +# a payload to the Job List RPC to say "give me all the attributes back" +fluxjob = flux.job.JobID(jobid) +payload = {"id": fluxjob, "attrs": ["all"]} +rpc = flux.job.list.JobListIdRPC(handle, "job-list.list-id", payload) +jobinfo = rpc.get_job() +print(json.dumps(jobinfo, indent=4)) + +#%% +# You can get less commonly used (and thus exposed) metadata like this +# such as the emoji state! +info = rpc.get_jobinfo() +print(info.__dict__) + +#%% +# But for either of the above approaches, we aren't getting anything back about our +# original jobspec! That's because we need to query the KVS for that. Notice here we +# have metadata like the current working directory (cwd) +kvs = flux.job.job_kvs(handle, jobid) +jobspec = kvs.get('jobspec') +print(json.dumps(jobspec)) +time.sleep(2) + +#%% +# Finally, to watch (or stream) output, you can do the following. +# Each line here is a json structure that you can further parse. +# As an example, if "data" is present as a key, this usually is output +for line in flux.job.event_watch(handle, jobid, "guest.output"): + print(line) + + + From e71be2e95a14fb95e3adfc9b31690ff52fe3dbbf Mon Sep 17 00:00:00 2001 From: vsoch Date: Fri, 9 Jun 2023 18:14:26 -0600 Subject: [PATCH 2/3] bug with rst rendering in flux job tutorials Signed-off-by: vsoch --- tutorials/commands/flux-jobs.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/tutorials/commands/flux-jobs.rst b/tutorials/commands/flux-jobs.rst index d14372b0..41390936 100644 --- a/tutorials/commands/flux-jobs.rst +++ b/tutorials/commands/flux-jobs.rst @@ -302,6 +302,7 @@ By default, ``flux jobs`` will not list jobs that are running under subinstances this with an example. Submit the following script to ``flux batch``. .. code-block:: sh + #!/bin/sh # filename: batchjob.sh From da924eb7263829c8fbe4fdaefd906b985e3be2ae Mon Sep 17 00:00:00 2001 From: vsoch Date: Fri, 9 Jun 2023 18:14:42 -0600 Subject: [PATCH 3/3] add auto examples to build Signed-off-by: vsoch --- auto_examples/auto_examples_jupyter.zip | Bin 0 -> 11761 bytes auto_examples/auto_examples_python.zip | Bin 0 -> 4990 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 auto_examples/auto_examples_jupyter.zip create mode 100644 auto_examples/auto_examples_python.zip diff --git a/auto_examples/auto_examples_jupyter.zip b/auto_examples/auto_examples_jupyter.zip new file mode 100644 index 0000000000000000000000000000000000000000..1b2b11e7cf30e8b4437cdf62afce51b843985fc6 GIT binary patch literal 11761 zcmeHNO>f-D5!LP{IqW4DpMnHvGYB*SX1JE@LtsD%oDXNUHWorQa1bnwK@Zt8(-zsy zWj8gNI6mw-x7_kS_MUrA0rDeq%}>aC-6UuHvDs)Hj4ekpWX&|2#qO%tRj;ae`rw1_ z+}PvK(R+V;wEp|*FaP@C-X4E{fH8F;tGZOvCw4Z~W^Sjl)~T-7X7=<$A(C8`rAzLL z&+$bF4)G@pPyM=9xRg|~IVarMxVpm$>@$*)ZyC+vL0>3JZcV^J8&hd}E6? z_qJJ!ZarcmKCv@gnp<)2^dvKx`R!7>;qpqCrEp8T68!a6ER+{qe^W`XEvCzvZM~2J z8xwNwRU`aTiMh;unwg|q>ErOXTjHyCm)k;ZcBU>=-tqy{+_uJJGo~%eVT;=v?;b&I z*|Ln}wQhY-^y5L;tMsL-+BRN1aol3}#p_B9U#uHG&dc^9GxNq) zf*(_AqPqpUb+yVL9S)OSJF6R;E9Wkuo7b`Y$Es0crC>DYrSV#N1#5AkVku2gs#FAv z2TSMH7?pb(npSLDW3*Ys*-=2QVAhklSlbp-A-bRA2^(u5J1<*D9IUioiu-ubn6vi_ zQR*{Qu7xZcC5yF~VIi|p;bEES9-N9!w1i}@oafVtF9?Q=MgaannyB<*>4j0M;9?6K z*N82`YLf|XojCmmb$o}AzJ#jZE*M4&iQ@T+~&iOswYZUQJ^QVAumc)AJXv9JwB z8kOrBhdg`KFHRJ?R& zZj9Rtg+G{w`Hga-rcc4O@BoapWr5qA?L-sKE^l=0#S(yw@C4ukIt$Au_t+$+yEOoe zMvaB`Vig1ejOvS4pUYC2fdAD}!qYi8Jfj*MnnKLhcX7;laBzq#+&P(;u;$}C{K#b= zAK_D2^#rS)@G6E}sZ=cvZ(V69b_f6#2d9?O=fOb-qmDAeNYi~g{ya;T)_Ei0Gn2=# zYFkaCvJT^ys4sj8>Y74b;rcOL<*p&l&<4Obgf=21OY*j92!oYv&WN7~SA@+h5uC`=3F<{R4~Llq2jImK-GoL{RsN*Mt24I zhsy7}-hXufzA1hOZ%Lv@a@P$CUBHuG;m`>PX=0iVo0Ke#PJ0wX>bQ?+2N^g~rPAP| zM@PG|!>4dLWweD{2j>G;4$8xz6IbqRrB)1Rfq8@Az(e)qTa*Y$&CfCe!<`ut^AN1& zoYP1Xxe&xSF_ENBwCVG4Z>7M;oZ=I&z7r!!I3}n&W=$s+nf_?MXD3v}K8#&A+V~Of z*p;R4fyd~`mq~|U9d&_V=SAyO6S?fhgk;q@rqD8dj?>XBn6t~xI!lfNYl_L&nF5vC zW$gmX#*lmYqX|NO}QqS zDD6V$fvS>T))&8Ju2niuCTOo3E6`7fIz!X7`(o8CTtx02Gp&nas~{m@p(m5>MKH^2?Z)0Zazjm%?OJL?munmsW;M zoH)Zw@lZqPZ%%(fnGOrhk_8rpO(JzG!;z34>w!Yxy!oswxzf1g*@gbM2}e-a2!py{ z;OTU9ES_eGE;81r;yq(1hstmqL*4`hA!T}&nWoOS<&{ur3ZYc(NdessTqaBKlu5YG z|0Wba?yZ7na}BKU9SewLbYvNFG(j8e*vNT!4=OG6h4h-u`xTaB(b8Ws9(glF2%0AIRJ-E`%D~+v} zZO4&~{lcN2PMK0nr@OM(&*92g08*&(p+FO25es@2r(D)xVlrQ{jsrIbFJbmQ*=wlh zNduQgPZV5CtRJJcml@k=#6ns2-25gglal$$Oyv?Npsc>)_eHBv#iqAmz~n;6fm88m zEItJ`O2g&soOGDZCFDIg>Qf92&VtT>4her57BN-e zGQGlRzZ=_x%I94J=O5tKH<2;jCo)(uL_k?dk7GW9(Gk;>tPWTsMqxkFX|g2bOT@+; z9Ra%n)(}5`1we+X-&P3a_9z2Qk+x62dJaBsn}F;P5pn-L&}owQ(8qy;oWpIi`GT4d zc^ud&OGfZLlH?8RWI?=jZ3y=G7?*gZk;P-ix+$W=244zX!%u@rp2ryD+M-1zKJB)i z9H4;8B4JFzDCtFSg^o>V%p|>YM@WqQqU209MwNu%FovE_p)Q_A}WdsC3iVW?Hdp)i04-OL~#$?UA?* z&%&j1)i|`YF#q;TovFD-b5LD)Wx6RzTCazjlL|G8z7Oaz?W2py3-X5tzcfBWa|Ms~ z5YMKTYN)3dXVvQkD{XFCqV*PLbQ{>xMpY-8h<)eSxz451iet&O|zi5^2ZTCrI#pYXdO5d&~&QG9`y0pI3 z=nER6en%~wZ)o~{7P}b#Roge30+P$#*zCUz8co6_`09F(>L;B&Nx&xA|c&Q zIAd{QM^r4-_2a8Zp~9q4fJdovrcfB~Uq>1Xl`hh;NIycy4%G>Tj7}UL;WePX)``Rn z6*2aB0u>L{F~W;A4wNBK-auB{y`~3gEb5bVhf&M}(Z0e|``Z~5NBX)z5NZhTaDm{x z_iudg^{-O%6>I){^(Ft`E*P*7z9|=+KG?{4RjGk@^XIECM?ZRAPCSm=lD_*r&U_#L L{*1pT6Mp+QAVEo# literal 0 HcmV?d00001 diff --git a/auto_examples/auto_examples_python.zip b/auto_examples/auto_examples_python.zip new file mode 100644 index 0000000000000000000000000000000000000000..8b168963194520e587fff9f4e5552cf88025c788 GIT binary patch literal 4990 zcmeHLU615O6`dr62P;ny3GsHS?=Nb?e-(b8q?L;XB`X|A?Qn zxBmM0>`xzm^q2RJj`;ln#`IIQZ)&}I;?^r;H*TdGQ#6O$viQ+LN>>@Xy(eAREPld~ zEYI`o+=kXwT^U?^kYT+n&RSSB%}xSJT|nshMh>onSjZkH1)9fM2rui-Q{mo(ojkx!bIv}lq&Anj zK8UJYt*V2pfn;54>;{p;*2O4WL3GeQaDQ47k|CrO*V`yg_GY^aVzsUaw{i(3l!Q$e z3oc!F^LxF#n426#4lp>o`N^Z}hWpn%b>o{I_6M~N`H{-QHis@66-y5A{8 zu15e{-6A?FSs(5JdsG5g| zdD*os_3T}HNdqN^fC+M$m3?&*oy(a3Jc2Ip0KlCukkhz+IaprU1#3myE7Ll=$7Yk9 zYh#dCnKH~EbQqnWKhojY%V6jM$}RZ~{7Q-;tqg-=s)Di&(a}3(f+8)Pr5K-&5qy%7 zN{Z-@+1a$$CooT)_EU#wD6k^(j3JODd$-p;<16wSvI^E5sZDBxNBdL62GCtv@?MOc z4JWnArjUUgFAK$lCGeYJ*lW;$*W5DMoPocj_5*Fv=1BLZ3$x=fBx_n@!wd^1 zb?zf~Orj#@AQ0%Mh>jqg*KJ~n=P*fFC8luqy$-5U0X!IU8<3$?q=EtNJf>=5@Jo&S zea`npjv-LSl`{1}VC1IkpXjogl!-+`o<(baC0|NtJN;Vays@Ad?%3H%xAlQBT)V9) zBL^^T8w{n?2UC&NA3pw+^aY55B=3+6^zj@D!b`~jH96i{Ep7wh&JPNKMz4(S3weO` zL#3Qg8x0;KKmI40GAyw4m8y;;yhU9Ib6JD#O{G`@3ESeKj<&lBZUtRp1LOr17 zTcCoPp#C~~UIhSxNsuVqE6?a<=y06`;+``MnhJR`Y_gPRxvBDdF)Z+GE;(g+jC__u zdj9!WS=*G?<w_?Z6|kUJl>IfY zx_#p#LQF(CLO9#7yn8a`GOB8fPI|gV{}PGAS%d(Rxsn;{WY~4=z#2p>sCxwPUdvD_LVj00hMP$qE&S z>Tg_KyB_vrihl&F$F}BLmUDj!L7(4^6>VhvP1i>E#hj&p`yg-Dig=blFm5DgHOM2M zg2mY}Acho46gb(~cD{>zgv$gmqc_-FA(o$8P=iszDyntUib%d~hn}=ZtMik4u>(pO zLNSQBEW4NvKac*b9)JJY@4o!wyGKX-{_usvPb&NW-SOws)ZG1x2cYHhO-G=F31&lZ zWF2Z38&aKEF%h(YdD$fU7tTQc-dX3{lg+m$8pg0H+QceZq8mh z+?@R%9&QpFPUR@^$#otb>dKA7dpan`0;tj7puNeBAH{-r|_}1I+ zzw`RHTYT%q&o^K5|7+fLT^I7ID#4}veDn3+@zU(Nkfh#FGcF#!`&~|Z8-IVm@87=1 GyMF*kQF{6S literal 0 HcmV?d00001