diff --git a/README.md b/README.md index bfccf47..a643d81 100755 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # LSF Python API -These python wrappers allow customers to submit and control jobs and obtain status of queues, hosts and other LSF attributes from Python directly. They work with various versions of LSF and are maintained by LSF developement, though we take contributions from the Open Source community. +These python wrappers allow customers to submit and control jobs and obtain status of queues, hosts and other LSF attributes from Python directly. They work with various versions of LSF and are maintained by LSF development, though we take contributions from the Open Source community. If you plan or would like to contribute to the library, you must follow the DCO process in the attached [DCO Readme file](https://github.com/IBMSpectrumComputing/platform-python-lsf-api/blob/master/IBMDCO.md) in the root of this repository. It essentially requires you to provide a Sign Off line in the notes of your pull request stating that the work is clear of infinging work by others. Again, for more details, please see the DCO Readme file. @@ -42,23 +42,26 @@ Please note you must use lsf.lsb_init before any other LSBLIB library routine in Supported operating systems: Linux 2.6 glibc 2.3 x86 64 bit: RHEL 6.2, RHEL6.4, RHEL6.5, RHEL6.8 - Linux 3.10 glibc 2.17 x86 64 bit: Red Hat 7.4, 7.5 + Linux 3.10 glibc 2.17 x86 64 bit: Red Hat 7.4, 7.5, 8.8, 8.9 Linux for Power Systems Servers 8 Little Endian (Linux 3.10, glibc 2.17): RHEL 7.4 Linux for Power Systems Servers 9 Little Endian (Linux 4.14, glibc 2.17): RHEL 7.5 - SWIG - SWIG version 2.0, or later + SWIG version 2.0, or 3.0 The following versions are tested: SWIG: 2.0.10, 3.0.12 + + If installing SWIG with pip or pip3, please specify 3.0.12 version. + `$ pip3 install swig==3.0.12` - Python Python2 and Python3 are all supported. The following versions are tested: - Python 2.6.6, 2.7.15, 3.0, 3.6.0, 3.7.0 + Python 2.6.6, 2.7.15, 3.0, 3.6.0, 3.7.0, 3.12.2 ## Compatibility @@ -74,6 +77,10 @@ Before compiling the library, set the LSF environment variables: `$ source profile.lsf` +If using python version higher than 3.10, distutils has been deprecated, install setuptools: + +`$ pip3 install setuptools` + To compile and install the library, go to the main source directory and type: @@ -90,6 +97,7 @@ or `$ python3 setup.py bdist_rpm` Resulting RPMs will be placed in the dist directory + ## Release Notes ### Release 1.0.6 @@ -171,3 +179,4 @@ IBM(R), the IBM logo and ibm.com(R) are trademarks of International Business Mac registered in many jurisdictions worldwide. Other product and service names might be trademarks of IBM or other companies. A current list of IBM trademarks is available on the Web at "Copyright and trademark information" at www.ibm.com/legal/copytrade.shtml. + diff --git a/examples/disp_gpu.py b/examples/disp_gpu.py index 096d711..892e1e2 100644 --- a/examples/disp_gpu.py +++ b/examples/disp_gpu.py @@ -7,11 +7,14 @@ num_hosts = lsf.new_intp() lsf.intp_assign(num_hosts, 0) host_data = lsf.lsb_hostinfo_ex(host_names, num_hosts, "", 0) - all_host_data = lsf.hostInfoEntArray_frompointer(host_data) + all_host_data = lsf.hostInfoEntArray(lsf.intp_value(num_hosts)) + all_host_data = all_host_data.frompointer(host_data) for i in range(0, lsf.intp_value(num_hosts)): hostname = all_host_data[i].host print(hostname) gpudata = all_host_data[i].gpuData + if gpudata is None: + continue print("ngpus avail_shared_gpus avail_excl_gpus") print(" {} {} {}". \ format(gpudata.ngpus, gpudata.avail_shared_ngpus, gpudata.avail_excl_ngpus)) diff --git a/examples/disp_gpu_host.py b/examples/disp_gpu_host.py new file mode 100644 index 0000000..cae2e3c --- /dev/null +++ b/examples/disp_gpu_host.py @@ -0,0 +1,27 @@ +from pythonlsf import lsf +if __name__ == '__main__': + if lsf.lsb_init("test") > 0: + exit -1; + num_hosts = lsf.new_intp() + lsf.intp_assign(num_hosts, 0) + hosts = lsf.ls_gethostgpuinfo(None, num_hosts, None, 0, 0) + + hostGpuInfos = lsf.hostGpuInfoArray(lsf.intp_value(num_hosts)) + hostGpuInfos = hostGpuInfos.frompointer(hosts) + for i in range(0, lsf.intp_value(num_hosts)): + hostGpuInfo = hostGpuInfos[i] + print("Host: {}". format(hostGpuInfo.hostName)) + numGpus = hostGpuInfos[i].gpuC + if numGpus <= 0: + continue + gpuAttrData = hostGpuInfo.gpuAttrV + gpuLoadData = hostGpuInfo.gpuLoadV + gpuAttrs = lsf.hostGpuAttrArray(numGpus) + gpuAttrs = gpuAttrs.frompointer(gpuAttrData) + gpuLoads = lsf.hostGpuLoadArray(numGpus) + gpuLoads = gpuLoads.frompointer(gpuLoadData) + print(" gBrand gModel gBusId gMode gUsedMem gStatus") + for j in range(0, numGpus): + print(" {} {} {} {} {} {} ". \ + format(gpuAttrs[j].gBrand, gpuAttrs[j].gModel, gpuAttrs[j].gBusId, + gpuLoads[j].gMode, gpuLoads[j].gUsedMem, gpuLoads[j].gStatus)) diff --git a/examples/disp_limit.py b/examples/disp_limit.py index c7bcead..ab715e8 100644 --- a/examples/disp_limit.py +++ b/examples/disp_limit.py @@ -28,4 +28,5 @@ def printLimit(): if __name__ == '__main__': print("LSF Clustername is : {}".format(lsf.ls_getclustername())) + lsf.set_limit_filter_flag(True) printLimit() diff --git a/examples/disp_limit_all.py b/examples/disp_limit_all.py index cc2fa2a..56ea773 100644 --- a/examples/disp_limit_all.py +++ b/examples/disp_limit_all.py @@ -3,12 +3,14 @@ def printLimitItem(name, item): print(name+' :') print(' consumerC : {}'.format(item.consumerC)) - consumers = lsf.limitConsumerArray_frompointer(item.consumerV) + consumers = lsf.limitConsumerArray(item.consumerC) + consumers = consumers.frompointer(item.consumerV) for j in range (item.consumerC) : print(' [{}] type : {}'.format(j, consumers[j].type)) print(' [{}] name : {}'.format(j, consumers[j].name)) print(' resourceC : {}'.format(item.resourceC)) - resources = lsf.limitResourceArray_frompointer(item.resourceV) + resources = lsf.limitResourceArray(item.resourceC) + resources= resources.frompointer(item.resourceV) for j in range (item.resourceC) : print(' [{}] name : {}'.format(j, resources[j].name)) print(' [{}] type : {}'.format(j, resources[j].type)) @@ -47,7 +49,8 @@ def printLimit(): # print usageC in the limit print('usageC : {}'.format(ent.usageC)) # print usageInfo in the limit - all_usageInfo = lsf.limitItemArray_frompointer(ent.usageInfo) + all_usageInfo = lsf.limitItemArray(ent.usageC) + all_usageInfo = all_usageInfo.frompointer(ent.usageInfo) for j in range (ent.usageC) : printLimitItem('usageInfo', all_usageInfo[j]) diff --git a/examples/readstream.py b/examples/readstream.py index 5ba9b57..60a4722 100644 --- a/examples/readstream.py +++ b/examples/readstream.py @@ -27,7 +27,17 @@ def display(eventrec): for i in range(0,numhosts): hoststr += lsf.stringArray_getitem(exechosts, i) + "" print("EVENT_JOB_FORCE jobid<%d>, execHost<%s>, username<%s>" %(jobid, hoststr, username)) - + elif eventrec.type == lsf.EVENT_JOB_RUN_RUSAGE: + jobid = eventrec.eventLog.jobRunRusageLog.jobid; + numpgids = eventrec.eventLog.jobRunRusageLog.jrusage.npgids; + for i in range(0,numpgids): + pgids = str(lsf.intArray_getitem(eventrec.eventLog.jobRunRusageLog.jrusage.pgid, i)) + " " + pgids = pgids[:-1] + numpids = eventrec.eventLog.jobRunRusageLog.jrusage.npids; + for i in range(0,numpids): + pids = str(lsf.pidInfoArray_getitem(eventrec.eventLog.jobRunRusageLog.jrusage.pidInfo, i).pid) + " "; + pids = pids[:-1] + print("EVENT_JOB_RUN_RUSAGE jobid<%d> pgids<%s> pids<%s>" %(jobid, pgids, pids)) else: print("event type is %d" %(eventrec.type)) diff --git a/pythonlsf/Makefile b/pythonlsf/Makefile index b48b893..30c8e6a 100755 --- a/pythonlsf/Makefile +++ b/pythonlsf/Makefile @@ -13,8 +13,8 @@ LSF_INCLUDE = $(LSF_LIBDIR)/../../include/lsf/ PROJECT = _lsf.so OBJECTS = lsf_wrap.o -CFLAGS = -m64 -fPIC -I$(PYTHON_INCLUDE) -I$(LSF_INCLUDE) -LDFLAGS = $(LSF_LIBDIR)/liblsf.a $(LSF_LIBDIR)/libbat.a $(LSF_LIBDIR)/libfairshareadjust.so $(LSF_LIBDIR)/liblsbstream.so -lc -lnsl +CFLAGS = -fPIC -I$(PYTHON_INCLUDE) -I$(LSF_INCLUDE) +LDFLAGS = $(LSF_LIBDIR)/liblsf.a $(LSF_LIBDIR)/libbat.a $(LSF_LIBDIR)/libfairshareadjust.so $(LSF_LIBDIR)/liblsbstream.so -lc -lnsl -lz all: $(PROJECT) diff --git a/pythonlsf/lsf.i b/pythonlsf/lsf.i index d380025..a18028d 100644 --- a/pythonlsf/lsf.i +++ b/pythonlsf/lsf.i @@ -25,7 +25,11 @@ int fclose(FILE *f); #include "lsf.h" #include "lsbatch.h" #include "lib.table.h" +extern struct gpuJobData* str2GpuJobData(char *str); +typedef int bool_t; +extern void setbConfigInfoFlag4Lib(bool_t bConfigInfo); %} +typedef long off_t; %pointer_functions(int, intp) %pointer_functions(float, floatp) @@ -47,6 +51,11 @@ int fclose(FILE *f); %array_functions(struct shareAcctInfoEnt, shareAcctInfoEntArray) #ifdef LSF_VERSION_101 %array_functions(struct gpuRusage, gpuRusageArray) +%array_functions(struct gpuJobHostData, gpuJobHostDataArray) +%array_functions(struct gpuTaskData, gpuTaskDataArray) +%array_functions(struct gpuData *, gpuDataArray) +%array_functions(struct migData, migDataArray) +%array_functions(struct pidInfo, pidInfoArray) #endif %array_functions(LS_LONG_INT, LS_LONG_INTArray) %array_functions(guaranteedResourcePoolEnt, guaranteedResourcePoolEntArray) @@ -193,6 +202,11 @@ static void stringArray_setitem(char * *ary, size_t index, char * value) { %array_class(struct _limitItem, limitItemArray) %array_class(struct _limitConsumer, limitConsumerArray) %array_class(struct _limitResource, limitResourceArray) +#ifdef LSF_VERSION_101 +%array_class(struct hostGpuInfo, hostGpuInfoArray) +%array_class(struct hostGpuAttr, hostGpuAttrArray) +%array_class(struct hostGpuLoad, hostGpuLoadArray) +#endif // handle int arrays %typemap(in) int [ANY] (int temp[$1_dim0]) { @@ -235,6 +249,16 @@ static void stringArray_setitem(char * *ary, size_t index, char * value) { $1 = 0; } +%typemap(in) bool_t { + if (PyBool_Check($input)) { + $1 = (bool_t)(($input == Py_True) ? 1 : 0); + } else if (PyLong_Check($input)) { + $1 = (bool_t)PyLong_AsLong($input); + } else { + $1 = (bool_t)0; + } +} + /* The following routines are not wrapped because SWIG has issues generating proper code for them @@ -703,6 +727,9 @@ int get_lsb_errno() { char * get_lsb_sysmsg() { return lsb_sysmsg(); } +struct gpuJobData* get_str2GpuJobData(char *str) { + return str2GpuJobData(str); +} PyObject * get_pids_from_stream(struct jRusage * jrusage) { struct pidInfo *pidInfo; @@ -750,4 +777,8 @@ PyObject * get_host_info_all() { return result; } +void set_limit_filter_flag(bool_t bConfigInfo) { + setbConfigInfoFlag4Lib(bConfigInfo); +} + %} diff --git a/setup.py b/setup.py index ef5e503..b3da42e 100755 --- a/setup.py +++ b/setup.py @@ -8,8 +8,12 @@ # import os, sys, re import time -from distutils.core import setup, Extension +if sys.version_info >= (3,10): + from setuptools import setup, Extension +else: + from distutils.core import setup, Extension from distutils.command.bdist_rpm import bdist_rpm +from distutils.command.build import build from distutils.command.install import INSTALL_SCHEMES class bdist_rpm_custom(bdist_rpm): @@ -25,6 +29,21 @@ def finalize_package_data (self): self.no_autoreq = 1 bdist_rpm.finalize_package_data(self) +# Build extensions before python modules, so the generated pythonlsf/lsf.py +# file is created before attempting to install it. This makes building with +# "pip" easier. +# See: +# https://bugs.python.org/issue2624 +# https://bugs.python.org/issue1016626 +# https://stackoverflow.com/questions/12491328/python-distutils-not-include-the-swig-generated-module +# https://stackoverflow.com/questions/50239473/building-a-module-with-setuptools-and-swig + +class build_ext_first(build): + def finalize_options(self): + super().finalize_options() + new_order = list(filter(lambda x: x[0] == 'build_ext', self.sub_commands)) + list(filter(lambda x: x[0] != 'build_ext', self.sub_commands)) + self.sub_commands[:] = new_order + def get_lsf_libdir(): try: _lsf_envdir = os.environ['LSF_ENVDIR'] @@ -63,7 +82,7 @@ def set_gccflag_lsf_version(): xlc_path = os.path.join(path, 'xlc') if os.access(xlc_path, os.F_OK): found_xlc = True - os.environ["LDSHARED"] = "%s -pthread -shared -Wl,-z,relro" % xlc_path + os.environ["LDSHARED"] = "%s -pthread -shared -Wl,-z,-lz,relro" % xlc_path break if found_xlc == False: print(''' @@ -87,11 +106,11 @@ def set_gccflag_lsf_version(): if os.access(LSF_LIBDIR + "/liblsbstream.a", os.F_OK): lsf_static_lib = [ LSF_LIBDIR + '/liblsbstream.a'] - lsf_dynamic_lib = ['c', 'nsl', 'rt'] + lsf_dynamic_lib = ['c', 'nsl', 'rt', 'z'] warning_msg = "" else: lsf_static_lib = [] - lsf_dynamic_lib = ['c', 'nsl', 'lsbstream', 'lsf', 'bat', 'rt'] + lsf_dynamic_lib = ['c', 'nsl', 'lsbstream', 'lsf', 'bat', 'rt', 'z'] warning_msg = ''' Warning: The compatibility of the LSF Python API package is not guaranteed if you update LSF at a later time. This is because your current @@ -131,18 +150,18 @@ def set_gccflag_lsf_version(): # '-DLSF_SIMULATOR', '-DOS_HAS_THREAD -D_REENTRANT', gccflag_keyvaluet, gccflag_lsfversion], - extra_compile_args=['-m64', + extra_compile_args=[ '-I' + LSF_LIBDIR + '/../../include/lsf/', '-Wno-strict-prototypes', gccflag_keyvaluet, gccflag_lsfversion, '-DOS_HAS_THREAD -D_REENTRANT', #For multi-thread lib, lserrno '-Wp,-U_FORTIFY_SOURCE', #The flag needs -O option. Undefine it for warning. '-O0'], - extra_link_args=['-m64'], extra_objects=lsf_static_lib, libraries=lsf_dynamic_lib)], py_modules=['pythonlsf.lsf'], - cmdclass = { 'bdist_rpm': bdist_rpm_custom }, + cmdclass = { 'bdist_rpm': bdist_rpm_custom, + 'build': build_ext_first }, classifiers=["Development Status :: 2 - Pre-Alpha", "License :: OSI Approved :: Eclipse Public License", "Operating System :: OS Independent",