Completed
Push — develop ( 373c1f...cf816b )
by James
01:27
created

detect_scheduling_sys()   D

Complexity

Conditions 8

Size

Total Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 1 Features 0
Metric Value
cc 8
c 4
b 1
f 0
dl 0
loc 34
rs 4
1
import sys
2
import os
3
from subprocess import Popen, PIPE, check_output
0 ignored issues
show
Unused Code introduced by
Unused PIPE imported from subprocess
Loading history...
Unused Code introduced by
Unused Popen imported from subprocess
Loading history...
4
import time
5
import uuid
6
from fabric.api import env, run, cd, get, hide, settings, remote_tunnel, show
0 ignored issues
show
Configuration introduced by
The import fabric.api could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
Unused Code introduced by
Unused remote_tunnel imported from fabric.api
Loading history...
Unused Code introduced by
Unused cd imported from fabric.api
Loading history...
Unused Code introduced by
Unused show imported from fabric.api
Loading history...
7
from fabric.tasks import execute
0 ignored issues
show
Configuration introduced by
The import fabric.tasks could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
8
from fabric.decorators import with_settings
0 ignored issues
show
Configuration introduced by
The import fabric.decorators could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
9
from datetime import timedelta
10
from os.path import join as pj
11
12
JOB_SCHEDULERS = ('SGE', 'SLURM', 'LSF',
13
                  'PBS', 'TORQUE', 'MAUI', 'LOADLEVELER')
14
15
scheduler = None
16
job_db = None
17
18
19
def get_data(filename):
20
    packagedir = os.path.dirname(__file__)
21
    dirname = pj(packagedir, '..', 'share', 'MyCluster')
22
    fullname = os.path.join(dirname, filename)
23
    # Need to check if file exists as
24
    # share location may also be sys.prefix/share
25
    if not os.path.isfile(fullname):
26
        dirname = pj(sys.prefix, 'share', 'MyCluster')
27
        fullname = os.path.join(dirname, filename)
28
29
    return fullname
30
31
32
def detect_scheduling_sys():
33
34
    # Test for SLURM
35
    if os.getenv('SLURMHOME') is not None:
36
        return my_import('mycluster.slurm')
37
38
    try:
39
        line = check_output(['scontrol', 'ping'])
40
        if line.split('(')[0] == 'Slurmctld':
41
            return my_import('mycluster.slurm')
42
    except:
0 ignored issues
show
Coding Style Best Practice introduced by
General except handlers without types should be used sparingly.

Typically, you would use general except handlers when you intend to specifically handle all types of errors, f.e. when logging. Otherwise, such general error handlers can mask errors in your application that you want to know of.

Loading history...
43
        pass
44
45
46
    # Test for PBS
47
    try:
48
        line = check_output(['pbsnodes', '-a'])
49
        return my_import('mycluster.pbs')
50
    except:
0 ignored issues
show
Coding Style Best Practice introduced by
General except handlers without types should be used sparingly.

Typically, you would use general except handlers when you intend to specifically handle all types of errors, f.e. when logging. Otherwise, such general error handlers can mask errors in your application that you want to know of.

Loading history...
51
        pass
52
53
    # Test for SGE
54
    if os.getenv('SGE_CLUSTER_NAME') is not None:
55
        return my_import('mycluster.sge')
56
57
    # Test for lsf
58
    try:
59
        line = check_output('lsid')
60
        if line.split(' ')[0] == 'Platform':
61
            return my_import('mycluster.lsf')
62
    except:
0 ignored issues
show
Coding Style Best Practice introduced by
General except handlers without types should be used sparingly.

Typically, you would use general except handlers when you intend to specifically handle all types of errors, f.e. when logging. Otherwise, such general error handlers can mask errors in your application that you want to know of.

Loading history...
63
        pass
64
65
    return None
66
67
68
def queues():
69
    if scheduler is not None:
70
        return scheduler.queues()
71
    else:
72
        return []
73
74
75
def remote_sites():
76
    if job_db is not None:
77
        return job_db.remote_site_db
78
    else:
79
        return []
80
81
82
@with_settings(warn_only=True)
83
def remote_cmd():
84
    output_file = '~/.mycluster/' + str(uuid.uuid4())
85
    with hide('output', 'running', 'warnings'), settings(warn_only=True):
86
        run('mycluster -p >' + output_file, pty=False)
87
        import StringIO
0 ignored issues
show
Configuration introduced by
The import StringIO could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
88
        contents = StringIO.StringIO()
89
        get(output_file, contents)
90
        # operate on 'contents' like a file object here, e.g. 'print
91
        return contents.getvalue()
92
93
94
def remote_job_list(site):
95
    env.use_ssh_config = True
96
    return execute(remote_cmd, hosts=[site])
97
98
99
def print_timedelta(td):
100
    if (td.days > 0):
0 ignored issues
show
Unused Code Coding Style introduced by
There is an unnecessary parenthesis after if.
Loading history...
101
        if td.days > 1:
102
            out = str(td).replace(" days, ", ":")
103
        else:
104
            out = str(td).replace(" day, ", ":")
105
    else:
106
        out = "0:" + str(td)
107
    outAr = out.split(':')
108
    outAr = ["%02d" % (int(float(x))) for x in outAr]
109
    out = ":".join(outAr)
110
    return out
111
112
113
def get_timedelta(date_str):
114
    # Returns timedelta object from string in [DD-[hh:]]mm:ss format
115
    days = 0
116
    hours = 0
117
    minutes = 0
118
    seconds = 0
119
120
    if date_str.count('-') == 1:
121
        days = int(date_str.split('-')[0])
122
        date_str = date_str.partition('-')[2]
123
    if date_str.count(':') == 2:
124
        hours = int(date_str.split(':')[0])
125
        date_str = date_str.partition(':')[2]
126
127
    try:
128
        minutes = int(date_str.split(':')[0])
129
        seconds = int(date_str.split(':')[1])
130
    except:
0 ignored issues
show
Coding Style Best Practice introduced by
General except handlers without types should be used sparingly.

Typically, you would use general except handlers when you intend to specifically handle all types of errors, f.e. when logging. Otherwise, such general error handlers can mask errors in your application that you want to know of.

Loading history...
131
        pass
132
133
    return timedelta(days=days,
134
                     hours=hours,
135
                     minutes=minutes,
136
                     seconds=seconds
137
                     )
138
139
140
def get_stats_time(stats):
141
142
    wallclock = '-' if 'wallclock' not in stats else stats['wallclock']
143
    wallclock_delta = None
144
    cputime_delta = None
145
    if wallclock != '-':
146
        try:
147
            wallclock_delta = wallclock
148
            wallclock = print_timedelta(wallclock_delta)
149
        except:
0 ignored issues
show
Coding Style Best Practice introduced by
General except handlers without types should be used sparingly.

Typically, you would use general except handlers when you intend to specifically handle all types of errors, f.e. when logging. Otherwise, such general error handlers can mask errors in your application that you want to know of.

Loading history...
150
            pass
151
    cputime = '-' if 'cpu' not in stats else stats['cpu']
152
    if cputime != '-':
153
        try:
154
            cputime_delta = cputime
155
            cputime = print_timedelta(cputime_delta)
156
        except:
0 ignored issues
show
Coding Style Best Practice introduced by
General except handlers without types should be used sparingly.

Typically, you would use general except handlers when you intend to specifically handle all types of errors, f.e. when logging. Otherwise, such general error handlers can mask errors in your application that you want to know of.

Loading history...
157
            pass
158
159
    time_ratio = None
160
    if cputime_delta and wallclock_delta:
161
        time_ratio = (float(cputime_delta.total_seconds()) /
162
                      wallclock_delta.total_seconds())
163
164
    return cputime, wallclock, time_ratio
165
166
167
def printjobs(num_lines):
168
    print('User name: {0} {1}'.format(job_db.user_db['user'].first_name,
169
                                      job_db.user_db['user'].last_name))
170
    jobs = job_list()
171
    print('     | {0:^10} | {1:^10} |\
172
          {2:^10} | {3:^12} | {4:^12} |\
173
          {5:^5} | {6:^20} | {7:50}'.format('Job ID',
174
                                            'Status',
175
                                            'NTasks',
176
                                            'CPU Time',
177
                                            'Wallclock',
178
                                            'Util %',
179
                                            'Job Name',
180
                                            'Job Dir',)
181
          )
182
    for i, j in enumerate(jobs):
183
        job_id = jobs[j].job_id
184
        status = jobs[j].status
185
        # queue = jobs[j].queue
186
        # site_name = job_db.queue_db[queue].site_name
187
        # scheduler_type = job_db.site_db[site_name].scheduler_type
188
        cputime, wallclock, time_ratio = get_stats_time(jobs[j].stats)
189
        efficiency = '-'
190
        if time_ratio:
191
            try:
192
                efficiency = (time_ratio / (int(jobs[j].num_tasks) *
193
                              int(jobs[j].threads_per_task)) * 100.0)
194
                efficiency = '{:.1f}'.format(efficiency)
195
            except:
0 ignored issues
show
Coding Style Best Practice introduced by
General except handlers without types should be used sparingly.

Typically, you would use general except handlers when you intend to specifically handle all types of errors, f.e. when logging. Otherwise, such general error handlers can mask errors in your application that you want to know of.

Loading history...
196
                pass
197
198
        if status == 'completed':
199
            print('{0:4} | {1:^10} |\
200
                  {2:^10} | {3:^10} |\
201
                  {4:^12} | {5:^12} |\
202
                  {6:^5} | {7:^20} | {8:50}'.format(i + 1,
203
                                                    job_id,
204
                                                    status,
205
                                                    str(jobs[j].num_tasks) +
206
                                                    ' (' +
207
                                                    str(jobs[j].threads_per_task) +
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (83/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
208
                                                    ')',
209
                                                    cputime,
210
                                                    wallclock,
211
                                                    efficiency,
212
                                                    jobs[j].job_name,
213
                                                    jobs[j].job_dir)
214
                  )
215
        elif status == 'running':
216
            stats = scheduler.running_stats(job_id)
217
            cputime, wallclock, time_ratio = get_stats_time(stats)
218
            efficiency = '-'
219
            if time_ratio:
220
                try:
221
                    efficiency = (time_ratio / (int(jobs[j].num_tasks) *
222
                                  int(jobs[j].threads_per_task)) * 100.0)
223
                    efficiency = '{:.1f}'.format(efficiency)
224
                except:
0 ignored issues
show
Coding Style Best Practice introduced by
General except handlers without types should be used sparingly.

Typically, you would use general except handlers when you intend to specifically handle all types of errors, f.e. when logging. Otherwise, such general error handlers can mask errors in your application that you want to know of.

Loading history...
225
                    pass
226
            print('{0:4} | {1:^10} | {2:^10} |\
227
                   {3:^10} | {4:^12} | {5:^12} |\
228
                   {6:^5} | {7:^20} | {8:50}'.format(i + 1,
229
                                                     job_id,
230
                                                     status,
231
                                                     str(jobs[j].num_tasks) +
232
                                                     ' (' +
233
                                                     str(jobs[j].threads_per_task) +
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (84/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
234
                                                     ')',
235
                                                     cputime,
236
                                                     wallclock,
237
                                                     efficiency,
238
                                                     jobs[j].job_name,
239
                                                     jobs[j].job_dir)
240
                  )
241
        else:
242
            print('{0:4} | {1:^10} | {2:^10} |\
243
                   {3:^10} | {4:^12} | {5:^12} |\
244
                   {6:^5} | {7:^20} | {8:50}'.format(i + 1,
245
                                                     job_id,
246
                                                     status,
247
                                                     str(jobs[j].num_tasks) +
248
                                                     ' (' +
249
                                                     str(jobs[j].threads_per_task) +
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (84/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
250
                                                     ')',
251
                                                     '-',
252
                                                     '-',
253
                                                     efficiency,
254
                                                     jobs[j].job_name,
255
                                                     jobs[j].job_dir)
256
                  )
257
258
    remotes = remote_sites()
259
    for i, j in enumerate(remotes):
260
        print('Remote Site: ' + remotes[j].name)
261
        remote_list = remote_job_list(remotes[j].user + '@' + remotes[j].name)
262
        for r in remote_list:
263
            print(remote_list[r])
264
265
266
def print_queue_info():
267
    print('{0:25} | {1:^15} | {2:^15} | {3:^15} |\
268
           {4:^15} | {5:^15}'.format('Queue Name', 'Node Max Task',
269
                                     'Node Max Thread', 'Node Max Memory',
270
                                     'Max Task', 'Available Task'))
271
    for q in queues():
272
        nc = scheduler.node_config(q)
273
        tpn = scheduler.tasks_per_node(q)
274
        avail = scheduler.available_tasks(q)
275
        print('{0:25} | {1:^15} | {2:^15} |\
276
               {3:^15} | {4:^15} | {5:^15}'.format(q, tpn,
277
                                                   nc['max thread'],
278
                                                   nc['max memory'],
279
                                                   avail['max tasks'],
280
                                                   avail['available']))
281
282
283
def create_submit(queue_id, script_name=None, **kwargs):
284
285
    if job_db is not None:
286
        if 'user_email' not in kwargs:
287
            email = job_db.user_db['user'].email
288
            if email != 'unknown':
289
                kwargs['user_email'] = email
290
291
    if scheduler is not None:
292
        script = scheduler.create_submit(queue_id, **kwargs)
293
294
        if script_name is not None:
295
            import os.path
0 ignored issues
show
Comprehensibility Bug introduced by
os is re-defining a name which is already available in the outer-scope (previously defined on line 2).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
296
            if not os.path.isfile(script_name):
297
                with open(script_name, 'w') as f:
298
                    f.write(script)
299
            else:
300
                print('Warning file: {0} already exists.\
301
                       Please choose a different name'.format(script_name))
302
        return script
303
    else:
304
        print('Warning job scheduler not detected')
305
        return None
306
307
308
def submit(script_name, immediate, depends=None):
309
310
    if scheduler is None:
311
        return None
312
313
    job_id = -1
314
    import os.path
0 ignored issues
show
Comprehensibility Bug introduced by
os is re-defining a name which is already available in the outer-scope (previously defined on line 2).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
315
    if os.path.isfile(script_name):
316
        job_id = scheduler.submit(script_name, immediate, depends)
317
        if job_id is not None:
318
            print('Job submitted with ID {0}'.format(job_id))
319
        if job_db is not None and job_id is not None:
320
            from persist import Job
0 ignored issues
show
Configuration introduced by
The import persist could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
321
            job = Job(job_id, time.time())
322
            with open(script_name, 'r') as f:
323
                for line in f:
324
                    if line.split('=')[0] == 'export NUM_TASKS':
325
                        job.num_tasks = line.split('=')[1].strip()
326
                    if line.split('=')[0] == 'export TASKS_PER_NODE':
327
                        job.tasks_per_node = line.split('=')[1].strip()
328
                    if line.split('=')[0] == 'export THREADS_PER_TASK':
329
                        job.threads_per_task = line.split('=')[1].strip()
330
                    if line.split('=')[0] == 'export NUM_NODES':
331
                        job.num_nodes = line.split('=')[1].strip()
332
                    if line.split('=')[0] == 'export MYCLUSTER_QUEUE':
333
                        job.queue = line.split('=')[1].strip()
334
                    if line.split('=')[0] == 'export MYCLUSTER_JOB_NAME':
335
                        job.job_name = line.split('=')[1].strip()
336
337
            job.script_name = script_name
338
            job.job_dir = os.path.dirname(os.path.abspath(script_name))
339
            job_db.add_job(job)
340
            job_db.add_queue(job.queue, scheduler.name())
341
    else:
342
        print('Error file: {0} does not exist.'.format(script_name))
343
344
    return job_id
345
346
347
def delete(job_id):
348
    # Add check
349
    job = job_db.get(job_id)
350
    site_name = job.queue.site_name
351
    scheduler_type = job_db.site_db[site_name].scheduler_type
352
353
    if (scheduler.name() == site_name and
354
       scheduler.scheduler_type() == scheduler_type):
355
        scheduler.delete(job_id)
356
    else:
357
        print('JobID: ' + str(job_id) + ' not found at current site')
358
359
360
def add_remote(remote_site):
361
    if job_db is not None:
362
        job_db.add_remote(remote_site)
363
364
365
def export(job_id):
366
    pass
367
368
369
def job_list():
370
    if job_db is not None:
371
        return job_db.job_db
372
    return []
373
374
375
def get_job(job_id):
376
    if job_db is not None:
377
        return job_db.get(job_id)
378
    return None
379
380
381
def my_import(name):
382
    mod = __import__(name)
383
    components = name.split('.')
384
    for comp in components[1:]:
385
        mod = getattr(mod, comp)
386
    return mod
387
388
389
def get_directory():
390
    from os.path import expanduser
391
    home = expanduser("~")
392
    directory = home + '/.mycluster/'
393
    return directory
394
395
396
def create_directory():
397
    directory = get_directory()
398
    if not os.path.exists(directory):
399
        os.makedirs(directory)
400
        return True
401
    else:
402
        return False
403
404
405
def create_db():
406
    global job_db
407
    try:
408
        from persist import JobDB
0 ignored issues
show
Configuration introduced by
The import persist could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
409
        job_db = JobDB()
410
    except Exception as e:
411
        print('Database failed to initialise. Error Message: ' + str(e))
412
413
    return job_db
414
415
416
def update_db():
417
    try:
418
        if scheduler is not None:
419
            status_dict = scheduler.status()
420
            jobs = job_list()
421
            for j in jobs:
422
                if jobs[j].status != 'completed':
423
                    job_id = jobs[j].job_id
424
                    if job_id in status_dict:
425
                        state = status_dict[job_id]
426
                        if state == 'r':
427
                            jobs[j].update_status('running')
428
                    else:
429
                        jobs[j].update_status('completed')
430
                        jobs[j].update_stats(scheduler.job_stats(job_id))
431
    except Exception as e:
432
        print('Database failed to update. Error Message: ' + str(e))
433
434
435
def sysscribe_update(job_id):
436
    if job_db is not None:
437
        from sysscribe import system
0 ignored issues
show
Configuration introduced by
The import sysscribe could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
438
        job_db.get(job_id).update_sysscribe(system.system_dict())
439
440
441
def email_update(email):
442
    if job_db is not None:
443
        job_db.user_db['user'].update_email(email)
444
445
446
def firstname_update(name):
447
    if job_db is not None:
448
        job_db.user_db['user'].firstname(name)
449
450
451
def lastname_update(name):
452
    if job_db is not None:
453
        job_db.user_db['user'].lastname(name)
454
455
456
def get_user():
457
    if job_db is not None:
458
        return (job_db.user_db['user'].first_name + ' ' +
459
                job_db.user_db['user'].last_name)
460
    else:
461
        return 'unknown'
462
463
464
def get_email():
465
    if job_db is not None:
466
        return job_db.user_db['user'].email
467
    else:
468
        return 'unknown'
469
470
471
def get_site():
472
    return 'unknown'
473
474
475
def appname_update(job_id, appname):
476
    if job_db is not None:
477
        job_db.get(job_id).appname(appname)
478
479
480
def appdata_update(job_id, appdata):
481
    if job_db is not None:
482
        job_db.get(job_id).appdata(appdata)
483
484
485
def init():
486
    global scheduler
487
    scheduler = detect_scheduling_sys()
488
    created = create_directory()
489
    if create_db() is not None:
490
        update_db()
491
492
    print('MyCluster Initialisation Info')
493
    print('-----------------------------')
494
    print('Local database in: ' + get_directory())
495
    print('User: ' + get_user())
496
    print('Email: ' + get_email())
497
    if not scheduler:
498
        print('Local job scheduler: None')
499
    else:
500
        print('Local job scheduler: ' + scheduler.scheduler_type())
501
        print('Site name: ' + get_site())
502
    print('')
503