Project

General

Profile

root / branches / 1.1 / src / haizea / cli / commands.py @ 847

1
# -------------------------------------------------------------------------- #
2
# Copyright 2006-2009, University of Chicago                                 #
3
# Copyright 2008-2009, Distributed Systems Architecture Group, Universidad   #
4
# Complutense de Madrid (dsa-research.org)                                   #
5
#                                                                            #
6
# Licensed under the Apache License, Version 2.0 (the "License"); you may    #
7
# not use this file except in compliance with the License. You may obtain    #
8
# a copy of the License at                                                   #
9
#                                                                            #
10
# http://www.apache.org/licenses/LICENSE-2.0                                 #
11
#                                                                            #
12
# Unless required by applicable law or agreed to in writing, software        #
13
# distributed under the License is distributed on an "AS IS" BASIS,          #
14
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #
15
# See the License for the specific language governing permissions and        #
16
# limitations under the License.                                             #
17
# -------------------------------------------------------------------------- #
18

    
19
from haizea.core.manager import Manager
20
from haizea.common.utils import generate_config_name, unpickle
21
from haizea.core.configfile import HaizeaConfig, HaizeaMultiConfig
22
from haizea.core.accounting import AccountingDataCollection
23
from haizea.common.config import ConfigException
24
from haizea.common.stats import percentile, print_percentiles
25
from haizea.cli.optionparser import Option
26
from haizea.cli import Command
27
from haizea.lwf.generators import LWFGenerator, LWFAnnotationGenerator
28
from haizea.lwf.analysis import LWFAnalyser
29
from mx.DateTime import TimeDelta, Parser
30
import haizea.common.defaults as defaults
31
import sys
32
import os
33
import errno
34
import signal
35
from time import sleep
36

    
37
try:
38
    import xml.etree.ElementTree as ET
39
except ImportError:
40
    # Compatibility with Python <=2.4
41
    import elementtree.ElementTree as ET 
42

    
43

    
44
class haizea(Command):
45
    """
46
    This is the main Haizea command. By default, it will start Haizea as a daemon, which
47
    can receive requests via RPC or interact with other components such as OpenNebula. It can
48
    also start as a foreground process, and write all log messages to the console. All
49
    Haizea options are specified through the configuration file."""
50
    
51
    name = "haizea"
52
    
53
    def __init__(self, argv):
54
        Command.__init__(self, argv)
55
        
56
        self.optparser.add_option(Option("-c", "--conf", action="store", type="string", dest="conf",
57
                                         help = """
58
                                         The location of the Haizea configuration file. If not
59
                                         specified, Haizea will first look for it in
60
                                         /etc/haizea/haizea.conf and then in ~/.haizea/haizea.conf.
61
                                         """))
62
        self.optparser.add_option(Option("-f", "--fg", action="store_true",  dest="foreground",
63
                                         help = """
64
                                         Runs Haizea in the foreground.
65
                                         """))
66
        self.optparser.add_option(Option("--stop", action="store_true",  dest="stop",
67
                                         help = """
68
                                         Stops the Haizea daemon.
69
                                         """))
70
                
71
    def run(self):
72
        self.parse_options()
73

    
74
        pidfile = defaults.DAEMON_PIDFILE # TODO: Make configurable
75

    
76
        if self.opt.stop == None:
77
            # Start Haizea
78
             
79
            # Check if a daemon is already running
80
            if os.path.exists(pidfile):
81
                pf  = file(pidfile,'r')
82
                pid = int(pf.read().strip())
83
                pf.close()
84
     
85
                try:
86
                    os.kill(pid, signal.SIG_DFL)
87
                except OSError, (err, msg):
88
                    if err == errno.ESRCH:
89
                        # Pidfile is stale. Remove it.
90
                        os.remove(pidfile)
91
                    else:
92
                        msg = "Unexpected error when checking pid file '%s'.\n%s\n" %(pidfile, msg)
93
                        sys.stderr.write(msg)
94
                        sys.exit(1)
95
                else:
96
                    msg = "Haizea seems to be already running (pid %i)\n" % pid
97
                    sys.stderr.write(msg)
98
                    sys.exit(1)
99
     
100
            try:
101
                configfile=self.opt.conf
102
                if configfile == None:
103
                    # Look for config file in default locations
104
                    for loc in defaults.CONFIG_LOCATIONS:
105
                        if os.path.exists(loc):
106
                            config = HaizeaConfig.from_file(loc)
107
                            break
108
                    else:
109
                        print >> sys.stdout, "No configuration file specified, and none found at default locations."
110
                        print >> sys.stdout, "Make sure a config file exists at:\n  -> %s" % "\n  -> ".join(defaults.CONFIG_LOCATIONS)
111
                        print >> sys.stdout, "Or specify a configuration file with the --conf option."
112
                        exit(1)
113
                else:
114
                    config = HaizeaConfig.from_file(configfile)
115
            except ConfigException, msg:
116
                print >> sys.stderr, "Error in configuration file:"
117
                print >> sys.stderr, msg
118
                exit(1)
119
                
120
            daemon = not self.opt.foreground
121
        
122
            manager = Manager(config, daemon, pidfile)
123
        
124
            manager.start()
125
        elif self.opt.stop: # Stop Haizea
126
            # Based on code in:  http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66012
127
            try:
128
                pf  = file(pidfile,'r')
129
                pid = int(pf.read().strip())
130
                pf.close()
131
            except IOError:
132
                msg = "Could not stop, pid file '%s' missing.\n"
133
                sys.stderr.write(msg % pidfile)
134
                sys.exit(1)
135
            try:
136
                while 1:
137
                    os.kill(pid, signal.SIGTERM)
138
                    sleep(1)
139
            except OSError, err:
140
                err = str(err)
141
                if err.find("No such process") > 0:
142
                    os.remove(pidfile)
143
                else:
144
                    print str(err)
145
                    sys.exit(1)
146

    
147
class haizea_generate_configs(Command):
148
    """
149
    Takes an Haizea multiconfiguration file and generates the individual
150
    configuration files. See the Haizea manual for more details on multiconfiguration
151
    files."""
152
    
153
    name = "haizea-generate-configs"
154

    
155
    def __init__(self, argv):
156
        Command.__init__(self, argv)
157
        
158
        self.optparser.add_option(Option("-c", "--conf", action="store", type="string", dest="conf", required=True,
159
                                         help = """
160
                                         Multiconfiguration file.
161
                                         """))
162
        self.optparser.add_option(Option("-d", "--dir", action="store", type="string", dest="dir", required=True,
163
                                         help = """
164
                                         Directory where the individual configuration files
165
                                         must be created.
166
                                         """))
167
                
168
    def run(self):    
169
        self.parse_options()
170
        
171
        configfile=self.opt.conf
172
        multiconfig = HaizeaMultiConfig.from_file(configfile)
173
        
174
        etcdir = self.opt.dir
175
        
176
        configs = multiconfig.get_configs()
177
        
178
        etcdir = os.path.abspath(etcdir)    
179
        if not os.path.exists(etcdir):
180
            os.makedirs(etcdir)
181
            
182
        for c in configs:
183
            profile = c.get_attr("profile")
184
            tracefile = c.get("tracefile")
185
            injfile = c.get("injectionfile")
186
            annotationfile = c.get("annotationfile")
187
            configname = generate_config_name(profile, tracefile, annotationfile, injfile)
188
            configfile = etcdir + "/%s.conf" % configname
189
            fc = open(configfile, "w")
190
            c.config.write(fc)
191
            fc.close()
192

    
193
class haizea_generate_scripts(Command):
194
    """
195
    Generates a script, based on a script template, to run all the individual 
196
    configuration files generated by haizea-generate-configs. This command 
197
    requires Mako Templates for Python (http://www.makotemplates.org/)."""
198
    
199
    name = "haizea-generate-scripts"
200

    
201
    def __init__(self, argv):
202
        Command.__init__(self, argv)
203
        
204
        self.optparser.add_option(Option("-c", "--conf", action="store", type="string", dest="conf", required=True,
205
                                         help = """
206
                                         Multiconfiguration file used in haizea-generate-configs.
207
                                         """))
208
        self.optparser.add_option(Option("-d", "--confdir", action="store", type="string", dest="confdir", required=True,
209
                                         help = """
210
                                         Directory containing the individual configuration files.
211
                                         """))
212
        self.optparser.add_option(Option("-t", "--template", action="store", type="string", dest="template", required=True,
213
                                         help = """
214
                                         Script template (sample templates are included in /usr/share/haizea/etc)
215
                                         """))
216
        self.optparser.add_option(Option("-m", "--only-missing", action="store_true",  dest="onlymissing",
217
                                         help = """
218
                                         If specified, the generated script will only run the configurations
219
                                         that have not already produced a datafile. This is useful when some simulations
220
                                         fail, and you don't want to have to rerun them all.
221
                                         """))
222

    
223
        self.optparser.add_option(Option("-x", "--exclude", action="store", type="string", dest="exclude",
224
                                         help = """
225
                                         ...
226
                                         """))
227

    
228
                
229
    def run(self):        
230
        self.parse_options()
231
        
232
        configfile=self.opt.conf
233
        multiconfig = HaizeaMultiConfig.from_file(configfile)
234
                
235
        try:
236
            from mako.template import Template
237
        except Exception, e:
238
            print "You need Mako Templates for Python to run this command."
239
            print "You can download them at http://www.makotemplates.org/"
240
            exit(1)
241
    
242
        configs = multiconfig.get_configs()
243
        
244
        etcdir = os.path.abspath(self.opt.confdir)    
245
        if not os.path.exists(etcdir):
246
            os.makedirs(etcdir)
247

    
248
        if self.opt.exclude != None:
249
            exclude = self.opt.exclude.split()
250
        else:
251
            exclude = []
252
            
253
        templatedata = []    
254
        for c in configs:
255
            profile = c.get_attr("profile")
256
            tracefile = c.get("tracefile")
257
            injfile = c.get("injectionfile")
258
            datafile = c.get("datafile")
259
            annotationfile = c.get("annotationfile")            
260
            configname = generate_config_name(profile, tracefile, annotationfile, injfile)
261
            configfile = etcdir + "/%s.conf" % configname
262
            if (not self.opt.onlymissing or not os.path.exists(datafile)) and not configfile in exclude:
263
                templatedata.append((configname, configfile))
264
    
265
        template = Template(filename=self.opt.template)
266
        print template.render(configs=templatedata, etcdir=etcdir)
267

    
268

    
269
class haizea_convert_data(Command):
270
    """
271
    Converts Haizea datafiles into another (easier to process) format.
272
    """
273
    
274
    name = "haizea-convert-data"
275

    
276
    def __init__(self, argv):
277
        Command.__init__(self, argv)
278
        
279
        self.optparser.add_option(Option("-t", "--type", action="store",  dest="type",
280
                                         choices = ["per-run", "per-lease", "counter"],
281
                                         help = """
282
                                         Type of data to produce.
283
                                         """))
284
        self.optparser.add_option(Option("-c", "--counter", action="store",  dest="counter",
285
                                         help = """
286
                                         Counter to print out when using '--type counter'.
287
                                         """))
288
        self.optparser.add_option(Option("-f", "--format", action="store", type="string", dest="format",
289
                                         help = """
290
                                         Output format. Currently supported: csv
291
                                         """))
292
        self.optparser.add_option(Option("-l", "--list-counters", action="store_true",  dest="list_counters",
293
                                         help = """
294
                                         If specified, the command will just print out the names of counters
295
                                         stored in the data file and then exit, regardless of other parameters.
296
                                         """))
297
                
298
    def run(self):            
299
        self.parse_options()
300

    
301
        datafiles=self.args[1:]
302
        if len(datafiles) == 0:
303
            print "Please specify at least one datafile to convert"
304
            exit(1)
305
        
306
        counter_names = set()
307
        attr_names = set()
308
        lease_stats_names = set()
309
        stats_names = set()
310
        for datafile in datafiles:
311
            try:
312
                data = unpickle(datafile)
313
                counter_names.update(data.counters.keys())
314
                attr_names.update(data.attrs.keys())
315
                lease_stats_names.update(data.lease_stats_names)
316
                stats_names.update(data.stats_names)
317
            except:
318
                print >> sys.stderr, "Error reading file %s" % (datafile)
319

    
320
        counter_names = list(counter_names)
321
        attr_names = list(attr_names)
322
        lease_stats_names = list(lease_stats_names)
323
        stats_names = list(stats_names)
324

    
325
        if self.opt.list_counters:
326
            for counter in counter_names:
327
                print counter
328
            exit(0)
329
        
330
        if self.opt.type == "per-run":
331
            header_fields = attr_names + stats_names
332
        elif self.opt.type == "per-lease":
333
            header_fields = attr_names + ["lease_id"] + lease_stats_names
334
        elif self.opt.type == "counter":
335
            counter = self.opt.counter
336
            data = unpickle(datafiles[0])
337
            if not data.counters.has_key(counter):
338
                print "The specified datafile does not have a counter called '%s'" % counter
339
                exit(1)
340
            header_fields = attr_names + ["time", "value"]
341
            if data.counter_avg_type[counter] != AccountingDataCollection.AVERAGE_NONE:
342
                header_fields.append("average")                
343

    
344
        header = ",".join(header_fields)
345
            
346
        print header
347
        
348
        for datafile in datafiles:
349
            try:
350
                data = unpickle(datafile)
351
            
352
                attrs = [data.attrs.get(attr_name, "") for attr_name in attr_names]
353
                            
354
                if self.opt.type == "per-run":
355
                    fields = attrs + [str(data.stats.get(stats_name,"")) for stats_name in stats_names]
356
                    print ",".join(fields)
357
                elif self.opt.type == "per-lease":
358
                    leases = data.lease_stats
359
                    for lease_id, lease_stat in leases.items():
360
                        fields = attrs + [`lease_id`] + [str(lease_stat.get(lease_stat_name,"")) for lease_stat_name in lease_stats_names]
361
                        print ",".join(fields)
362
                elif self.opt.type == "counter":
363
                    for (time, lease_id, value, avg) in data.counters[counter]:
364
                        fields = attrs + [`time`, `value`]
365
                        if data.counter_avg_type[counter] != AccountingDataCollection.AVERAGE_NONE:
366
                            fields.append(`avg`)
367
                        print ",".join(fields)
368
            except:
369
                print >> sys.stderr, "Error reading file %s" % (datafile)
370

    
371
class haizea_lwf_generate(Command):
372
    
373
    name = "haizea-lwf-generate"
374
    
375
    def __init__(self, argv):
376
        Command.__init__(self, argv)
377
        self.optparser.add_option(Option("-o", "--out", action="store", type="string", dest="outf", required=True,
378
                                         help = """
379
                                         LWF file
380
                                         """))
381
        self.optparser.add_option(Option("-c", "--conf", action="store", type="string", dest="conf",
382
                                         help = """
383
                                         ...
384
                                         """))      
385
        
386
    def run(self):
387
        self.parse_options()      
388
        
389
        outfile = self.opt.outf
390
        conffile = self.opt.conf    
391

    
392
        generator = LWFGenerator(outfile, conffile)
393
        
394
        generator.generate()
395

    
396
class haizea_lwf_stats(Command):
397
    
398
    name = "haizea-lwf-stats"
399
    
400
    def __init__(self, argv):
401
        Command.__init__(self, argv)
402
        self.optparser.add_option(Option("-i", "--in", action="store",  type="string", dest="inf", required=True,
403
                                         help = """
404
                                         Input file
405
                                         """))
406
        self.optparser.add_option(Option("-a", "--annotation", action="store",  type="string", dest="annotationf",
407
                                         help = """
408
                                         Annotation file
409
                                         """))
410
        self.optparser.add_option(Option("-v", "--verbose", action="store_true", dest="verbose",
411
                                         help = """
412
                                         Verbose
413
                                         """))
414
        self.optparser.add_option(Option("-l", "--utilization-length", action="store", type="string", dest="utilization_length",
415
                                         help = """
416
                                         Length of the utilization interval in format DD:HH:MM:SS. Default is until
417
                                         the time the last lease is requested.
418
                                         """))          
419
        
420
    def run(self):
421
        self.parse_options()      
422
        
423
        infile = self.opt.inf
424
        annotationfile = self.opt.annotationf
425
        utilization_length = self.opt.utilization_length
426
        if utilization_length != None:
427
            utilization_length = Parser.DateTimeDeltaFromString(utilization_length)
428

    
429
        analyser = LWFAnalyser(infile, utilization_length, annotationfile, self.opt.verbose)
430
        
431
        analyser.analyse()
432

    
433
class haizea_lwf_annotate(Command):
434
    
435
    name = "haizea-lwf-annotate"
436
    
437
    def __init__(self, argv):
438
        Command.__init__(self, argv)
439
        self.optparser.add_option(Option("-i", "--in", action="store",  type="string", dest="inf",
440
                                         help = """
441
                                         LWF file
442
                                         """))
443
        self.optparser.add_option(Option("-n", "--num-annotations", action="store",  type="int", dest="nleases",
444
                                         help = """
445
                                         Number of annotations
446
                                         """))        
447
        self.optparser.add_option(Option("-o", "--out", action="store", type="string", dest="outf", required=True,
448
                                         help = """
449
                                         Annotation file
450
                                         """))
451
        self.optparser.add_option(Option("-c", "--conf", action="store", type="string", dest="conf",
452
                                         help = """
453
                                         ...
454
                                         """))
455
        
456
    def run(self):
457
        self.parse_options()      
458
        
459
        infile = self.opt.inf
460
        nleases = self.opt.nleases
461
        outfile = self.opt.outf
462
        conffile = self.opt.conf    
463
        
464
        generator = LWFAnnotationGenerator(infile, nleases, outfile, conffile)
465
        
466
        generator.generate()
467
        
468

    
469
# TODO: Has to be moved to the haizea.lwf package, or removed altogether,
470
# given that this was a one-time thing to convert the old ad hoc LWF files
471
class haizea_lwf2xml(Command):
472
    """
473
    Converts old Haizea LWF file into new XML-based LWF format
474
    """
475
    
476
    name = "haizea-lwf2xml"
477

    
478
    def __init__(self, argv):
479
        Command.__init__(self, argv)
480
        
481
        self.optparser.add_option(Option("-i", "--in", action="store",  type="string", dest="inf",
482
                                         help = """
483
                                         Input file
484
                                         """))
485
        self.optparser.add_option(Option("-o", "--out", action="store", type="string", dest="outf",
486
                                         help = """
487
                                         Output file
488
                                         """))
489
                
490
    def run(self):            
491
        self.parse_options()
492

    
493
        infile = self.opt.inf
494
        outfile = self.opt.outf
495
        
496
        root = ET.Element("lease-workload")
497
        root.set("name", infile)
498
        description = ET.SubElement(root, "description")
499
        time = TimeDelta(seconds=0)
500
        lease_id = 1
501
        requests = ET.SubElement(root, "lease-requests")
502
        
503
        
504
        infile = open(infile, "r")
505
        for line in infile:
506
            if line[0]!='#' and len(line.strip()) != 0:
507
                fields = line.split()
508
                submit_time = int(fields[0])
509
                start_time = int(fields[1])
510
                duration = int(fields[2])
511
                real_duration = int(fields[3])
512
                num_nodes = int(fields[4])
513
                cpu = int(fields[5])
514
                mem = int(fields[6])
515
                disk = int(fields[7])
516
                vm_image = fields[8]
517
                vm_imagesize = int(fields[9])
518
                
519
                
520
        
521
                lease_request = ET.SubElement(requests, "lease-request")
522
                lease_request.set("arrival", str(TimeDelta(seconds=submit_time)))
523
                if real_duration != duration:
524
                    realduration = ET.SubElement(lease_request, "realduration")
525
                    realduration.set("time", str(TimeDelta(seconds=real_duration)))
526
                
527
                lease = ET.SubElement(lease_request, "lease")
528
                lease.set("id", `lease_id`)
529

    
530
                
531
                nodes = ET.SubElement(lease, "nodes")
532
                node_set = ET.SubElement(nodes, "node-set")
533
                node_set.set("numnodes", `num_nodes`)
534
                res = ET.SubElement(node_set, "res")
535
                res.set("type", "CPU")
536
                if cpu == 1:
537
                    res.set("amount", "100")
538
                else:
539
                    pass
540
                res = ET.SubElement(node_set, "res")
541
                res.set("type", "Memory")
542
                res.set("amount", `mem`)
543
                
544
                start = ET.SubElement(lease, "start")
545
                if start_time == -1:
546
                    lease.set("preemptible", "true")
547
                else:
548
                    lease.set("preemptible", "false")
549
                    exact = ET.SubElement(start, "exact")
550
                    exact.set("time", str(TimeDelta(seconds=start_time)))
551

    
552
                duration_elem = ET.SubElement(lease, "duration")
553
                duration_elem.set("time", str(TimeDelta(seconds=duration)))
554

    
555
                software = ET.SubElement(lease, "software")
556
                diskimage = ET.SubElement(software, "disk-image")
557
                diskimage.set("id", vm_image)
558
                diskimage.set("size", `vm_imagesize`)
559
                
560
                    
561
                lease_id += 1
562
        tree = ET.ElementTree(root)
563
        print ET.tostring(root)
564

    
565

    
566

    
567

    
568
# TODO: Has to be moved to the haizea.lwf package
569
class haizea_swf2lwf(Command):
570
    """
571
    Converts Standard Workload Format (SWF, used in the Parallel Workloads Archive at
572
    http://www.cs.huji.ac.il/labs/parallel/workload/) to Lease Workload Format
573
    """
574
    
575
    name = "haizea-swf2lwf"
576

    
577
    def __init__(self, argv):
578
        Command.__init__(self, argv)
579
        
580
        self.optparser.add_option(Option("-i", "--in", action="store",  type="string", dest="inf", required=True,
581
                                         help = """
582
                                         Input file
583
                                         """))
584
        self.optparser.add_option(Option("-o", "--out", action="store", type="string", dest="outf", required=True,
585
                                         help = """
586
                                         Output file
587
                                         """))
588
        self.optparser.add_option(Option("-p", "--preemptible", action="store", type="string", dest="preemptible", required=True,
589
                                         help = """
590
                                         Should the leases be preemptable or not?
591
                                         """))
592
        self.optparser.add_option(Option("-f", "--from", action="store", type="string", dest="from_time", default="00:00:00:00",
593
                                         help = """
594
                                         This parameter, together with the --amount parameter, allows converting just
595
                                         an interval of the SWF file.
596
                                         
597
                                         This parameter must be a timestamp of the format DD:HH:MM:SS. Only jobs sumitted
598
                                         at or after that time will be converted.
599
                                         
600
                                         Default is 00:00:00:00
601
                                         """))        
602
        self.optparser.add_option(Option("-l", "--interval-length", action="store", type="string", dest="interval_length",
603
                                         help = """
604
                                         Length of the interval in format DD:HH:MM:SS. Default is to convert jobs until the
605
                                         end of the SWF file.
606
                                         """))        
607
        self.optparser.add_option(Option("-q", "--queues", action="store", type="string", dest="queues",
608
                                         help = """
609
                                         Only convert jobs from the specified queues
610
                                         """))
611
        self.optparser.add_option(Option("-m", "--memory", action="store", type="string", dest="mem",
612
                                         help = """
613
                                         Memory requested by jobs.
614
                                         """))
615
        self.optparser.add_option(Option("-s", "--scale", action="store", type="string", dest="scale",
616
                                         help = """
617
                                         Scale number of processors by 1/SCALE.
618
                                         """))        
619
        self.optparser.add_option(Option("-n", "--site", action="store", type="string", dest="site",
620
                                         help = """
621
                                         File containing site description
622
                                         """))        
623
                
624
    def run(self):            
625
        self.parse_options()
626

    
627
        infile = self.opt.inf
628
        outfile = self.opt.outf
629
        
630
        from_time = Parser.DateTimeDeltaFromString(self.opt.from_time)
631
        if self.opt.interval_length == None:
632
            to_time = None
633
        else:
634
            to_time = from_time + Parser.DateTimeDeltaFromString(self.opt.interval_length)
635

    
636
        root = ET.Element("lease-workload")
637
        root.set("name", infile)
638
        description = ET.SubElement(root, "description")
639
        description.text = "Created with haizea-swf2lwf %s" % " ".join(self.argv[1:])
640

    
641
        if self.opt.site != None:
642
            site_elem = ET.parse(self.opt.site).getroot()
643
            site_num_nodes = int(site_elem.find("nodes").find("node-set").get("numnodes"))
644
            root.append(site_elem)
645
        
646
        time = TimeDelta(seconds=0)
647
        requests = ET.SubElement(root, "lease-requests")
648
        
649
        slowdowns = []
650
        users = set()
651
        utilization = 0
652
        utilization_no_ramp = 0
653
        if to_time == None:
654
            swf = open(infile, 'r')
655
            lines = swf.readlines()
656
            lastline = lines[-1]
657
            to_time = TimeDelta(seconds=int(lastline.split()[1]))
658
            swf.close()
659

    
660
        no_ramp_cutoff = from_time + ((to_time - from_time) * 0.05)
661

    
662
        infile = open(infile, "r")
663
        for line in infile:
664
            if line[0]!=';' and len(line.strip()) != 0:
665
                fields = line.split()
666
                
667
                # Unpack the job's attributes. The description of each field is
668
                # taken from the SWF documentation at
669
                # http://www.cs.huji.ac.il/labs/parallel/workload/swf.html
670
                
671
                # Job Number -- a counter field, starting from 1. 
672
                job_number = int(fields[0])
673
                
674
                # Submit Time -- in seconds. The earliest time the log refers to is zero, 
675
                # and is the submittal time the of the first job. The lines in the log are 
676
                # sorted by ascending submittal times. It makes sense for jobs to also be 
677
                # numbered in this order.
678
                submit_time = int(fields[1])
679

    
680
                # Wait Time -- in seconds. The difference between the job's submit time 
681
                # and the time at which it actually began to run. Naturally, this is only 
682
                # relevant to real logs, not to models.
683
                wait_time = int(fields[2])
684

    
685
                # Run Time -- in seconds. The wall clock time the job was running (end 
686
                # time minus start time).
687
                # We decided to use ``wait time'' and ``run time'' instead of the equivalent 
688
                # ``start time'' and ``end time'' because they are directly attributable to 
689
                # the scheduler and application, and are more suitable for models where only 
690
                # the run time is relevant.
691
                # Note that when values are rounded to an integral number of seconds (as 
692
                # often happens in logs) a run time of 0 is possible and means the job ran 
693
                # for less than 0.5 seconds. On the other hand it is permissable to use 
694
                # floating point values for time fields.
695
                run_time = int(fields[3])
696
                
697
                # Number of Allocated Processors -- an integer. In most cases this is also 
698
                # the number of processors the job uses; if the job does not use all of them, 
699
                # we typically don't know about it.
700
                num_processors_allocated = int(fields[4])
701
                
702
                # Average CPU Time Used -- both user and system, in seconds. This is the 
703
                # average over all processors of the CPU time used, and may therefore be 
704
                # smaller than the wall clock runtime. If a log contains the total CPU time 
705
                # used by all the processors, it is divided by the number of allocated 
706
                # processors to derive the average.
707
                avg_cpu_time = float(fields[5])
708
                
709
                # Used Memory -- in kilobytes. This is again the average per processor.
710
                used_memory = int(fields[6])
711
                
712
                # Requested Number of Processors.
713
                num_processors_requested = int(fields[7])
714
                
715
                # Requested Time. This can be either runtime (measured in wallclock seconds), 
716
                # or average CPU time per processor (also in seconds) -- the exact meaning 
717
                # is determined by a header comment. In many logs this field is used for 
718
                # the user runtime estimate (or upper bound) used in backfilling. If a log 
719
                # contains a request for total CPU time, it is divided by the number of 
720
                # requested processors.
721
                time_requested = int(fields[8])
722
                
723
                # Requested Memory (again kilobytes per processor).
724
                mem_requested = int(fields[9])
725
                
726
                # Status 1 if the job was completed, 0 if it failed, and 5 if cancelled. 
727
                # If information about chekcpointing or swapping is included, other values 
728
                # are also possible. See usage note below. This field is meaningless for 
729
                # models, so would be -1.
730
                status = int(fields[10])
731
                
732
                # User ID -- a natural number, between one and the number of different users.
733
                user_id = int(fields[11])
734
                
735
                # Group ID -- a natural number, between one and the number of different groups. 
736
                # Some systems control resource usage by groups rather than by individual users.
737
                group_id = int(fields[12])
738
                
739
                # Executable (Application) Number -- a natural number, between one and the number 
740
                # of different applications appearing in the workload. in some logs, this might 
741
                # represent a script file used to run jobs rather than the executable directly; 
742
                # this should be noted in a header comment.
743
                exec_number = int(fields[13])
744
                
745
                # Queue Number -- a natural number, between one and the number of different 
746
                # queues in the system. The nature of the system's queues should be explained 
747
                # in a header comment. This field is where batch and interactive jobs should 
748
                # be differentiated: we suggest the convention of denoting interactive jobs by 0.
749
                queue = int(fields[14])
750
                
751
                # Partition Number -- a natural number, between one and the number of different 
752
                # partitions in the systems. The nature of the system's partitions should be 
753
                # explained in a header comment. For example, it is possible to use partition 
754
                # numbers to identify which machine in a cluster was used.
755
                partition = int(fields[15])
756
                
757
                # Preceding Job Number -- this is the number of a previous job in the workload, 
758
                # such that the current job can only start after the termination of this preceding 
759
                # job. Together with the next field, this allows the workload to include feedback 
760
                # as described below.
761
                prec_job = int(fields[16])
762

    
763
                # Think Time from Preceding Job -- this is the number of seconds that should elapse 
764
                # between the termination of the preceding job and the submittal of this one. 
765
                prec_job_thinktime = int(fields[17])
766

    
767
                                
768
                # Check if we have to skip this job
769
                
770
                submit_time = TimeDelta(seconds=submit_time)
771
                
772
                if submit_time < from_time:
773
                    continue
774
                
775
                if to_time != None and submit_time > to_time:
776
                    break
777
                
778
                if run_time < 0 and status==5:
779
                    # This is a job that got cancelled while waiting in the queue
780
                    continue
781
                
782
                if self.opt.queues != None:
783
                    queues = [int(q) for q in self.opt.queues.split(",")]
784
                    if queue not in queues:
785
                        # Job was submitted to a queue we're filtering out
786
                        continue              
787
                    
788
                if num_processors_requested == -1:
789
                    num_processors = num_processors_allocated
790
                else:
791
                    num_processors = num_processors_requested
792
        
793
                if self.opt.scale != None:
794
                    num_processors = int(num_processors/int(self.opt.scale))
795
                    
796
                lease_request = ET.SubElement(requests, "lease-request")
797
                # Make submission time relative to starting time of trace
798
                lease_request.set("arrival", str(submit_time - from_time))
799

    
800
                if run_time == 0:
801
                    # As specified in the SWF documentation, a runtime of 0 means
802
                    # the job ran for less than a second, so we round up to 1.
803
                    run_time = 1 
804
                realduration = ET.SubElement(lease_request, "realduration")
805
                realduration.set("time", str(TimeDelta(seconds=run_time)))
806
                
807
                lease = ET.SubElement(lease_request, "lease")
808
                lease.set("id", `job_number`)
809

    
810
                
811
                nodes = ET.SubElement(lease, "nodes")
812
                node_set = ET.SubElement(nodes, "node-set")
813
                node_set.set("numnodes", `num_processors`)
814
                res = ET.SubElement(node_set, "res")
815
                res.set("type", "CPU")
816
                res.set("amount", "100")
817

    
818
                res = ET.SubElement(node_set, "res")
819
                res.set("type", "Memory")
820
                if self.opt.mem != None:
821
                    res.set("amount", self.opt.mem)
822
                elif mem_requested != -1:
823
                    res.set("amount", `mem_requested / 1024`)
824
                else:
825
                    print "Cannot convert this file. Job #%i does not specify requested memory, and --memory parameter not specified" % job_number
826
                    exit(-1)
827
                    
828
                if wait_time != -1:
829
                    if run_time < 10:
830
                        run_time2 = 10.0
831
                    else:
832
                        run_time2 = float(run_time)
833
                    slowdown = (wait_time + run_time2) / run_time2
834
                    slowdowns.append(slowdown)
835
                
836
                if not user_id in users:
837
                    users.add(user_id)
838

    
839
                # Total utilization
840
                utilization += run_time * num_processors
841

    
842
                # Removing ramp-up and ramp-down effects
843
                if wait_time != -1 and submit_time + run_time >= no_ramp_cutoff:
844
                    start_in_interval = max(no_ramp_cutoff, submit_time)
845
                    end_in_interval = min(to_time, submit_time + run_time)
846
                    time_in_interval = end_in_interval - start_in_interval
847
                    utilization_no_ramp += time_in_interval * num_processors
848

    
849
                start = ET.SubElement(lease, "start")
850
                lease.set("preemptible", self.opt.preemptible)
851
                lease.set("user", `user_id`)
852

    
853
                duration_elem = ET.SubElement(lease, "duration")
854
                duration_elem.set("time", str(TimeDelta(seconds=time_requested)))
855

    
856
                # No software environment specified. The annotator would have to be used to
857
                # add one (or an image file when running a simulation).
858
                software = ET.SubElement(lease, "software")
859
                diskimage = ET.SubElement(software, "none")
860
                
861
                # Add unused SWF attributes to the extra section, for future reference.
862
                extra = ET.SubElement(lease, "extra")
863
                attr = ET.SubElement(extra, "attr")
864
                attr.set("name", "SWF_waittime")
865
                attr.set("value", `wait_time`)
866
                attr = ET.SubElement(extra, "attr")
867
                attr.set("name", "SWF_runtime")
868
                attr.set("value", `run_time`)
869
                attr = ET.SubElement(extra, "attr")
870
                attr.set("name", "SWF_avgcputime")
871
                attr.set("value", `avg_cpu_time`)
872
                attr = ET.SubElement(extra, "attr")
873
                attr.set("name", "SWF_queue")
874
                attr.set("value", `queue`)
875
                attr = ET.SubElement(extra, "attr")
876
                attr.set("name", "SWF_group")
877
                attr.set("value", `group_id`)
878
                attr = ET.SubElement(extra, "attr")
879
                attr.set("name", "SWF_execnumber")
880
                attr.set("value", `exec_number`)
881
                    
882
        tree = ET.ElementTree(root)
883
        
884
        outfile = open(outfile, "w")
885
        tree.write(outfile)
886
        
887
        infile.close()
888
        outfile.close()
889
        
890
        slowdowns.sort()
891
        total_capacity = site_num_nodes * (to_time - from_time).seconds
892
        print utilization, total_capacity
893
        utilization = float(utilization) / float(total_capacity)
894
        utilization_no_ramp = float(utilization_no_ramp) / float(total_capacity)
895
        
896
        if len(slowdowns) > 0:
897
            print "SLOWDOWNS"
898
            print "---------"
899
            print_percentiles(slowdowns)
900
            print 
901
        print "USERS"
902
        print "-----"
903
        print "Number of users: %i" % len(users)
904
        print 
905
        print "UTILIZATION"
906
        print "-----------"
907
        print "Utilization: %.2f%%" % (utilization * 100)
908
        if utilization_no_ramp != 0:
909
            print "Utilization (no ramp-up/ramp-down): %.2f%%" % (utilization_no_ramp * 100)
910