source: branches/1.7/lang/tcl/scripts/task.tcl @ 6705

Last change on this file since 6705 was 6705, checked in by clarksm, 5 years ago

Enable "ionhelper" to execute jobs and immediately put results in cache.
This includes reporting run.xml location with absolute path.

File size: 39.9 KB
Line 
1# -*- mode: tcl; indent-tabs-mode: nil -*-
2# ----------------------------------------------------------------------
3#  COMPONENT: task - represents the executable part of a tool
4#
5#  This object is an executable version of a Rappture xml file.
6#  A tool is a task plus its graphical user interface.  Each task
7#  resides in an installation directory with other tool resources
8#  (libraries, examples, etc.).  Each task is defined by its inputs
9#  and outputs, and understands the context in which it executes
10#  (via exec, submit, mx, etc.).
11# ======================================================================
12#  AUTHOR:  Michael McLennan, Purdue University
13#  Copyright (c) 2004-2014  HUBzero Foundation, LLC
14#
15#  See the file "license.terms" for information on usage and
16#  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
17# ======================================================================
18package require BLT
19package require uuid
20
21itcl::class Rappture::Task {
22    private method CheckForCachedRunFile { driverFile }
23    private method CollectUQResults {}
24    private method ExecuteSimulationCommand { cmd }
25    private method GetCommand {}
26    private method GetDriverFile {}
27    private method GetSignal { signal }
28    private method GetCacheHelperCommand { driverFile }
29    private method GetSimulationCommand { driverFile }
30    private method GetUQErrors {}
31    private method GetUQSimulationCommand { driverFile }
32    private method GetUQTemplateFile {}
33    private method IsCacheable {}
34    private method IsCacheHelperEligible {}
35    private method LogCachedSimulationUsage {}
36    private method LogSimulationUsage {}
37    private method LogSubmittedSimulationUsage {}
38    private method LogUQSimulationUsage {}
39    private method SetCpuResourceLimit {}
40
41    public variable logger ""
42    public variable jobstats Rappture::Task::MiddlewareTime
43    public variable resultdir "@default"
44    public variable xmlSource ""
45
46    constructor {xmlobj installdir args} { # defined below }
47    destructor { # defined below }
48
49    public method installdir {} { return $_installdir }
50
51    public method run {args}
52    public method get_uq {args}
53    public method abort {}
54    public method reset {}
55    public method xml {args}
56    public method save {xmlobj {name ""}}
57
58    protected method OnError {data}
59    protected method OnOutput {data}
60    protected method Log {args}
61    protected method BuildSubmitBoincCommand {tFile toolparamFile params_file}
62    protected method BuildSubmitLocalCommand {cmd tFile params_file}
63    protected method GetParamsForUQ {}
64
65    private variable _xmlobj ""      ;# XML object with inputs/outputs
66    private variable _origxml ""     ;# copy of original XML (for reset)
67    private variable _installdir ""  ;# installation directory for this tool
68    private variable _errorcb ""     ;# callback for tool error
69    private variable _outputcb ""    ;# callback for tool output
70    private common jobnum 0          ;# counter for unique job number
71    private variable _uq
72
73    private variable _job
74
75    # get global resources for this tool session
76    public proc resources {{option ""}}
77
78    public common _resources
79    public proc setAppName {name}         { set _resources(-appname) $name }
80    public proc setHubName {name}         { set _resources(-hubname) $name }
81    public proc setHubURL {name}          { set _resources(-huburl) $name }
82    public proc setSession {name}         { set _resources(-session) $name }
83    public proc setJobPrt {name}          { set _resources(-jobprotocol) $name }
84    public proc setResultDir {name}       { set _resources(-resultdir) $name }
85    public proc setCacheHosts {name}      { set _resources(-cachehosts) $name }
86    public proc setCacheUser {name}       { set _resources(-cacheuser) $name }
87    public proc setCacheWriteHost {name}  { set _resources(-cachewritehost) $name }
88
89    # default method for -jobstats control
90    public proc MiddlewareTime {args}
91}
92
93# must use this name -- plugs into Rappture::resources::load
94proc task_init_resources {} {
95    Rappture::resources::register \
96        application_name  Rappture::Task::setAppName \
97        application_id    Rappture::Task::setAppId \
98        hub_name          Rappture::Task::setHubName \
99        hub_url           Rappture::Task::setHubURL \
100        session_token     Rappture::Task::setSession \
101        job_protocol      Rappture::Task::setJobPrt \
102        results_directory Rappture::Task::setResultDir \
103        cache_hosts       Rappture::Task::setCacheHosts \
104        cache_user        Rappture::Task::setCacheUser \
105        cache_write_host  Rappture::Task::setCacheWriteHost
106}
107
108# ----------------------------------------------------------------------
109# CONSTRUCTOR
110# ----------------------------------------------------------------------
111itcl::body Rappture::Task::constructor {xmlobj installdir args} {
112    if {![Rappture::library isvalid $xmlobj]} {
113        error "bad value \"$xmlobj\": should be Rappture::Library"
114    }
115    set _xmlobj $xmlobj
116
117    # stash a copy of the original XML for later "reset" operations
118    set _origxml [Rappture::LibraryObj ::#auto "<?xml version=\"1.0\"?><run/>"]
119    $_origxml copy "" from $_xmlobj ""
120
121    if {![file exists $installdir]} {
122        error "directory \"$installdir\" doesn't exist"
123    }
124    set _installdir $installdir
125    package require http
126    package require tls
127    http::register https 443 [list ::tls::socket -tls1 0 -ssl2 0 -ssl3 0]
128
129    eval configure $args
130}
131
132# ----------------------------------------------------------------------
133# DESTRUCTOR
134# ----------------------------------------------------------------------
135itcl::body Rappture::Task::destructor {} {
136    itcl::delete object $_origxml
137}
138
139# ----------------------------------------------------------------------
140# USAGE: resources ?-option?
141#
142# Clients use this to query information about the tool.
143# ----------------------------------------------------------------------
144itcl::body Rappture::Task::resources {{option ""}} {
145    if {$option == ""} {
146        return [array get _resources]
147    }
148    if {[info exists _resources($option)]} {
149        return $_resources($option)
150    }
151    return ""
152}
153
154itcl::body Rappture::Task::GetSignal {code} {
155    set signals {
156        xxx HUP INT QUIT ILL TRAP ABRT BUS FPE KILL USR1 SEGV
157        USR2 PIPE ALRM TERM STKFLT CHLD CONT STOP TSTP TTIN
158        TTOU URG XCPU XFSZ VTALRM PROF WINCH POLL PWR SYS
159        RTMIN RTMIN+1 RTMIN+2 RTMIN+3 RTMAX-3 RTMAX-2 RTMAX-1 RTMAX
160    }
161    set sigNum [expr $code - 128]
162    if { $sigNum > 0 && $sigNum < [llength $signals] } {
163        return [lindex $signals $sigNum]
164    }
165    return "unknown exit code \"$code\""
166}
167
168itcl::body Rappture::Task::get_uq {args} {
169    foreach {path val} $args {
170        if {$path == "-uq_type"} {
171            set _uq(type) $val
172        } elseif {$path == "-uq_args"} {
173            set _uq(args) $val
174        }
175    }
176    #set varlist [$_xmlobj uq_get_vars]
177    foreach {varlist num} [$_xmlobj uq_get_vars] break
178    return [Rappture::UQ ::#auto $varlist $num $_uq(type) $_uq(args)]
179}
180
181# ----------------------------------------------------------------------
182# USAGE: run ?<path1> <value1> <path2> <value2> ...? ?-output <callbk>?
183#
184# This method causes the tool to run.  A "driver.xml" file is created
185# as the input for the run.  That file is fed to the executable
186# according to the <tool><command> string, and the job is executed.
187#
188# Any "<path> <value>" arguments are used to override the current
189# settings from the GUI.  This is useful, for example, when filling
190# in missing simulation results from the analyzer.
191#
192# If the -output argument is included, then the next arg is a
193# callback command for output messages.  Any output that comes in
194# while the tool is running is sent back to the caller, so the user
195# can see progress running the tool.
196#
197# Returns a list of the form {status result}, where status is an
198# integer status code (0=success) and result is the output from the
199# simulator.  Successful output is something like {0 run1293921.xml},
200# where 0=success and run1293921.xml is the name of the file containing
201# results.
202# ----------------------------------------------------------------------
203itcl::body Rappture::Task::run {args} {
204    global env errorInfo
205
206    #
207    # Make sure that we save the proper application name.  Actually, the
208    # best place to get this information is straight from the "installtool"
209    # script, but just in case we have an older tool, we should insert the
210    # tool name from the resources config file.
211    #
212    if {[info exists _resources(-appname)] && $_resources(-appname) ne "" &&
213        [$_xmlobj get tool.name] eq ""} {
214        $_xmlobj put tool.name $_resources(-appname)
215    }
216
217    # if there are any args, use them to override parameters
218    set _errorcb ""
219    set _outputcb ""
220    set _uq(type) ""
221    set _uq(tFile) ""
222    set _uq(toolparamFile) ""
223    set _uq(paramsFile) ""
224    foreach {path val} $args {
225        if {$path == "-stdout"} {
226            set _outputcb $val
227        } elseif {$path == "-stderr"} {
228            set _errorcb $val
229        } elseif {$path == "-uq_type"} {
230            set _uq(type) $val
231        } elseif {$path == "-uq_args"} {
232            set _uq(args) $val
233        } elseif {$path != "-output"} {
234            $_xmlobj put $path.current $val
235        }
236    }
237    foreach {path val} $args {
238        if {$path == "-output"} {
239            if {$_outputcb == ""} {
240                set _outputcb $val
241            }
242        }
243    }
244
245    # Initialize job array variables
246    array set _job {
247        control  ""
248        exitcode 0
249        mesg     ""
250        runfile  ""
251        stderr   ""
252        stdout   ""
253        success  0
254        xmlobj   ""
255    }
256
257    SetCpuResourceLimit
258
259    set helperEligible [IsCacheHelperEligible]
260
261    set driverFile [GetDriverFile]
262    set cached 0
263    if { [IsCacheable] } {
264puts stderr "Cache checking: [time {
265        set cached [CheckForCachedRunFile $driverFile]
266     } ]"
267puts stderr "checking cached=$cached"
268    }
269    if { !$cached } {
270        if { $_uq(type) != "" } {
271            set _uq(tFile) [GetUQTemplateFile]
272        }
273        global env
274        if { $_uq(type) == "" } {
275            if { $helperEligible } {
276                set cmd [GetCacheHelperCommand $driverFile]
277            } else {
278                set cmd [GetSimulationCommand $driverFile]
279            }
280            set ::env(RAPPTURE_UQ) False
281        } else {
282            set cmd [GetUQSimulationCommand $driverFile]
283            set ::env(RAPPTURE_UQ) True
284        }
285        if { $cmd == "" } {
286            puts stderr "cmd is empty"
287            append mesg "There is no command specified by\n\n"
288            append mesg "    <command>\n"
289            append mesg "    </command>\n\n"
290            append mesg "in the tool.xml file."
291            return [list 1 $mesg]
292        }
293        Rappture::rusage mark
294        if { ![ExecuteSimulationCommand $cmd] } {
295            return [list 1 $_job(mesg)]
296        }
297        if { $_uq(type) != "" } {
298            LogUQSimulationUsage
299        } elseif { [resources -jobprotocol] == "submit" } {
300            LogSubmittedSimulationUsage
301        } else {
302            LogSimulationUsage
303        }
304    } else {
305        LogCachedSimulationUsage
306    }
307    if { $_uq(tFile) ne "" } {
308        file delete -force -- $_uq(tFile)
309    }
310    if { $_uq(toolparamFile) ne "" } {
311        file delete -force -- $_uq(toolparamFile)
312    }
313    if { $_uq(paramsFile) ne "" } {
314        file delete -force -- $_uq(paramsFile)
315    }
316    if { $_job(success) } {
317        file delete -force -- $driverFile
318        Log run finished
319        return [list 0 $_job(xmlobj)]
320    } else {
321        # See if the job was aborted.
322        if {[regexp {^KILLED} $_job(control)]} {
323            Log run aborted
324            return [list 1 "ABORT"]
325        }
326        Log run failed [list 0 $_job(mesg)]
327        return [list 1 $_job(mesg)]
328    }
329}
330
331# ----------------------------------------------------------------------
332#  Turn the command string from tool.xml into the proper syntax to use
333#  with a submit parameter sweep with a temlate file.  Proper quoting
334#  of the template file is necessary to prevent submit from being too smart
335#  and converting it to a full pathname.
336# ----------------------------------------------------------------------
337itcl::body Rappture::Task::BuildSubmitBoincCommand {tFile toolparamFile params_file} {
338    set toolId   [$_xmlobj get tool.id]
339    set toolVers [$_xmlobj get tool.version.application.revision]
340    set newcmd "submit --venue boinc --progress submit --runName=puq --inputfile @:$tFile --data $params_file --env TOOL_PARAMETERS=$toolparamFile ${toolId}_r${toolVers} -w headless"
341
342    return $newcmd
343}
344
345itcl::body Rappture::Task::BuildSubmitLocalCommand {cmd tFile params_file} {
346    set quote_next 0
347    set newcmd "submit --local --progress submit --runName=puq --inputfile @:$tFile --data $params_file"
348    set cmds [split $cmd " "]
349    for {set i 0} {$i < [llength $cmds]} {incr i} {
350        set arg [lindex $cmds $i]
351        if {$quote_next == 1} {
352            set nc [string range $arg 0 0]
353            if {$nc != "\""} {
354                set arg "\"\\\"$arg\\\"\""
355            }
356        }
357        if {$arg == "--eval"} {
358            set quote_next 1
359        } else {
360            set quote_next 0
361        }
362        if {$arg == "@driver"} {
363            set arg "\"\\\"$tFile\\\"\""
364        }
365        append newcmd " " $arg
366    }
367    regsub -all @driver $newcmd $tFile newcmd
368
369    return $newcmd
370}
371
372# ----------------------------------------------------------------------
373# USAGE: abort
374#
375# Clients use this during a "run" to abort the current job.
376# Kills the job and forces the "run" method to return.
377# ----------------------------------------------------------------------
378itcl::body Rappture::Task::abort {} {
379    Log run abort
380    set _job(control) "abort"
381}
382
383# ----------------------------------------------------------------------
384# USAGE: reset
385#
386# Resets all input values to their defaults.  Sometimes used just
387# before a run to reset to a clean state.
388# ----------------------------------------------------------------------
389itcl::body Rappture::Task::reset {} {
390    $_xmlobj copy "" from $_origxml ""
391    foreach path [Rappture::entities -as path $_xmlobj input] {
392        if {[$_xmlobj element -as type $path.default] ne ""} {
393            set defval [$_xmlobj get $path.default]
394            $_xmlobj put $path.current $defval
395        }
396    }
397}
398
399# ----------------------------------------------------------------------
400# USAGE: xml <subcommand> ?<arg> <arg> ...?
401# USAGE: xml object
402#
403# Used by clients to manipulate the underlying XML data for this
404# tool.  The <subcommand> can be any operation supported by a
405# Rappture::library object.  Clients can also request the XML object
406# directly by using the "object" subcommand.
407# ----------------------------------------------------------------------
408itcl::body Rappture::Task::xml {args} {
409    if {"object" == $args} {
410        return $_xmlobj
411    }
412    return [eval $_xmlobj $args]
413}
414
415# ----------------------------------------------------------------------
416# USAGE: save <xmlobj> ?<filename>?
417#
418# Used by clients to save the contents of an <xmlobj> representing
419# a run out to the given file.  If <filename> is not specified, then
420# it uses the -resultsdir and other settings to do what Rappture
421# would normally do with the output.
422# ----------------------------------------------------------------------
423itcl::body Rappture::Task::save {xmlobj {filename ""}} {
424    if {$filename eq ""} {
425
426        # If there's a results_directory defined in the resources file,
427        # then move the run.xml file there for storage.
428
429        set rdir ""
430        if {$resultdir eq "@default"} {
431            if {[info exists _resources(-resultdir)]} {
432                set rdir $_resources(-resultdir)
433            } else {
434                global rapptureInfo
435                set rdir $rapptureInfo(cwd)
436            }
437        } elseif {$resultdir ne ""} {
438            set rdir $resultdir
439        }
440
441        # use the runfile name generated by the last run
442        if {$_job(runfile) ne ""} {
443            set filename [file join $rdir [file tail $_job(runfile)]]
444        } else {
445            set filename [file join $rdir run.xml]
446        }
447    }
448
449    # add any last-minute metadata
450    $xmlobj put output.time [clock format [clock seconds]]
451
452    $xmlobj put tool.version.rappture.version $::Rappture::version
453    $xmlobj put tool.version.rappture.revision $::Rappture::build
454    $xmlobj put output.filename $filename
455    $xmlobj put output.version $Rappture::version
456
457    if {[info exists ::tcl_platform(user)]} {
458        $xmlobj put output.user $::tcl_platform(user)
459    }
460
461    # save the output
462    set rdir [file dirname $filename]
463    file mkdir $rdir
464
465    set fid [open $filename w]
466    puts $fid "<?xml version=\"1.0\"?>"
467    puts $fid [$xmlobj xml]
468    close $fid
469
470    Log output saved in $filename
471}
472
473# ----------------------------------------------------------------------
474# USAGE: OnOutput <data>
475#
476# Used internally to send each bit of output <data> coming from the
477# tool onto the caller, so the user can see progress.
478# ----------------------------------------------------------------------
479itcl::body Rappture::Task::OnOutput {data} {
480    if {[string length $_outputcb] > 0} {
481        uplevel #0 $_outputcb [list $data]
482    }
483}
484
485# ----------------------------------------------------------------------
486# USAGE: OnError <data>
487#
488# Used internally to send each bit of error <data> coming from the
489# tool onto the caller, so the user can see progress.
490# ----------------------------------------------------------------------
491itcl::body Rappture::Task::OnError {data} {
492    if {[string length $_errorcb] > 0} {
493        uplevel #0 $_errorcb [list $data]
494    }
495}
496
497# ----------------------------------------------------------------------
498# USAGE: Log <cmd> <arg> <arg> ...
499#
500# Used internally to log interesting events during the run.  If the
501# -logger option is set (to Rappture::Logger::log, or something like
502# that), then the arguments to this method are passed along to the
503# logger and written out to a log file.  Logging is off by default,
504# so this method does nothing unless -logger is set.
505# ----------------------------------------------------------------------
506itcl::body Rappture::Task::Log {args} {
507    if {[string length $logger] > 0} {
508        uplevel #0 $logger [list $args]
509    }
510}
511
512# ----------------------------------------------------------------------
513# USAGE: MiddlewareTime <key> <value> ...
514#
515# Used as the default method for reporting job status information.
516# Implements the old HUBzero method of reporting job status info to
517# stderr, which can then be picked up by the tool session container.
518# Most tools use the "submit" command, which talks directly to a
519# database to log job information, so this isn't really needed.  But
520# it doesn't hurt to have this and it can be useful in some cases.
521# ----------------------------------------------------------------------
522itcl::body Rappture::Task::MiddlewareTime {args} {
523    set line "MiddlewareTime:"
524    foreach {key val} $args {
525        append line " $key=$val"
526    }
527    puts stderr $line
528}
529
530itcl::body Rappture::Task::IsCacheable {} {
531    if { ![info exists _resources(-cachehosts)] ||
532         $_resources(-cachehosts) == "" } {
533        puts stderr cachehosts=[info exists _resources(-cachehosts)]
534        return 0
535    }
536    global env
537    if { [info exists env(RAPPTURE_CACHE_OVERRIDE)] } {
538        set state $env(RAPPTURE_CACHE_OVERRIDE)
539    } else {
540        set state [$_xmlobj get "tool.cache"]
541    }
542    if { $state ne "" } {
543        puts stderr "cache tag is \"$state\""
544    }
545    if { $state eq "" || ![string is boolean $state] } {
546        return 1;                       # Default is to allow caching.
547    }
548    return $state
549}
550
551itcl::body Rappture::Task::IsCacheHelperEligible {} {
552    global env
553    if { ![info exists env(IONHELPER_ALLOWED)] } {
554        set helperEligible 0
555    } else {
556        if { $env(IONHELPER_ALLOWED) ne "1" } {
557            set helperEligible 0
558        } else {
559            if { $_uq(type) == "" } {
560#               puts stderr "cache_user exists       = [info exists _resources(-cacheuser)]"
561#               puts stderr "cache_write_host exists = [info exists _resources(-cachewritehost)]"
562                if { ![info exists _resources(-cacheuser)] || ![info exists _resources(-cachewritehost)] } {
563                    set helperEligible 0
564                } else {
565                    if { ![info exists env(USER)] } {
566                        set helperEligible 0
567                    } else {
568#                       puts stderr "env(USER)  = $env(USER)"
569#                       puts stderr "cache_user = $_resources(-cacheuser)"
570                        if { $env(USER) eq $_resources(-cacheuser) } {
571                            set helperEligible 0
572                        } else {
573                            set toolId    [$_xmlobj get tool.id]
574                            set toolVers  [$_xmlobj get tool.version.application.revision]
575                            set toolDir   [$_xmlobj get tool.version.application.directory(top)]
576                            set verifyDir [file join / apps ${toolId} r${toolVers}]
577#                           puts stderr "toolDir     = $toolDir"
578#                           puts stderr "verifyDir   = $verifyDir"
579                            if { $toolDir eq $verifyDir } {
580                                if { [ catch { file readlink [file join / apps ${toolId} current] } currentVers ] != 0 } {
581                                    set helperEligible 0
582                                } else {
583#                                   puts stderr "currentVers = $currentVers"
584                                    if { "r$toolVers" eq $currentVers } {
585                                        set helperEligible 1
586                                    } else {
587                                        set helperEligible 0
588                                    }
589                                }
590                            } else {
591                               set helperEligible 0
592                            }
593                        }
594                    }
595                }
596            } else {
597                set helperEligible 0
598            }
599        }
600    }
601#   puts stderr "helperEligible = $helperEligible"
602
603    return $helperEligible
604}
605
606#
607# Send the list of parameters to a python program so it can call PUQ
608# and get a CSV file containing the parameter values to use for the runs.
609itcl::body Rappture::Task::GetParamsForUQ {} {
610    set pid [pid]
611    # puts "puq.sh get_params $pid $_uq(varlist) $_uq(type) $_uq(args)"
612    if {[catch {
613        exec puq.sh get_params $pid $_uq(varlist) $_uq(type) $_uq(args)
614    } errs] != 0 } {
615        error "get_params.py failed: $errs\n[GetUQErrors]"
616    }
617    return "params${pid}.csv"
618}
619
620itcl::body Rappture::Task::SetCpuResourceLimit {} {
621    # Set limits for cpu time
622    set limit [$_xmlobj get tool.limits.cputime]
623    if { $limit == "unlimited" } {
624        set limit 43200;                # 12 hours
625    } else {
626        if { [scan $limit "%d" dum] != 1 } {
627            set limit 14400;            # 4 hours by default
628        } elseif { $limit > 43200 } {
629            set limit 43200;            # limit to 12 hrs.
630        } elseif { $limit < 10 } {
631            set limit 10;               # lower bound is 10 seconds.
632        }
633    }
634    Rappture::rlimit set cputime $limit
635}
636
637# Write out the driver.xml file for the tool
638itcl::body Rappture::Task::GetDriverFile {} {
639    global rapptureInfo
640    set fileName [file join $rapptureInfo(cwd) "driver[pid].xml"]
641#
642# Remove existing <meta> section
643    $_xmlobj remove "meta"
644# Copy original <meta> section
645    $_xmlobj copy "meta" from $_origxml "meta"
646# Add new <meta> entry
647    if { $xmlSource != "" } {
648        set identifier [uuid::uuid generate]
649        $_xmlobj put meta.driver($identifier).source $xmlSource
650        $_xmlobj put meta.driver($identifier).version $::Rappture::build
651        $_xmlobj put meta.driver($identifier).time [clock format [clock seconds]]
652        if { $xmlSource == "rapptureUI" } {
653            $_xmlobj put meta.generated human
654        }
655        set generated [$_xmlobj get meta.generated]
656        if { $generated == "" } {
657            $_xmlobj put meta.generated human
658        }
659        set generated [$_xmlobj get meta.generated]
660        global env
661        set ::env(RAPPTURE_GENERATED) $generated
662        if {[info exists env(RAPPTURE_GENERATED_FILE)]} {
663            set f [open $env(RAPPTURE_GENERATED_FILE) w]
664            puts $f "generated $generated"
665            close $f
666        }
667    }
668#
669    if { [catch {
670        set f [open $fileName w]
671        puts $f "<?xml version=\"1.0\"?>"
672        puts $f [$_xmlobj xml]
673        close $f
674    } errs] != 0 } {
675        error "can't create driver file \"$fileName\": $errs"
676    }
677    return $fileName
678}
679
680itcl::body Rappture::Task::GetCacheHelperCommand { driverFile } {
681    set cmd ""
682    set helperDriverDir [file join / var ion drivers]
683    if { [file exists $helperDriverDir] } {
684        set cacheHelperCommand [file join / apps bin iondrive]
685        if { [file exists $cacheHelperCommand] } {
686            file copy -force $driverFile $helperDriverDir
687            set cmd $cacheHelperCommand
688        }
689    }
690
691    return $cmd
692}
693
694itcl::body Rappture::Task::GetCommand { } {
695    set cmd [$_xmlobj get tool.command]
696    regsub -all @tool $cmd $_installdir cmd
697    set cmd [string trimleft $cmd " "]
698    return $cmd
699}
700
701itcl::body Rappture::Task::GetSimulationCommand { driverFile } {
702    set cmd [GetCommand]
703    if { $cmd == "" } {
704        return ""
705    }
706    regsub -all @driver $cmd $driverFile cmd
707
708    switch -glob -- [resources -jobprotocol] {
709        "submit*" {
710            # if job_protocol is "submit", then use use submit command
711            set cmd "submit --local $cmd"
712        }
713        "mx" {
714            # metachory submission
715            set cmd "mx $cmd"
716        }
717        "exec" {
718            # default -- nothing special
719        }
720    }
721    return $cmd
722}
723
724itcl::body Rappture::Task::GetUQSimulationCommand { driverFile } {
725    set cmd [GetCommand]
726    if { $cmd == "" } {
727        return ""
728    }
729    set _uq(paramsFile) [GetParamsForUQ]
730#   set cmd [BuildSubmitBoincCommand $_uq(tFile) $_uq(toolparamFile) $_uq(paramsFile)]
731    set cmd [BuildSubmitLocalCommand $cmd $_uq(tFile) $_uq(paramsFile)]
732
733    file delete -force puq
734
735    return $cmd
736}
737
738itcl::body Rappture::Task::GetUQTemplateFile {} {
739    global rapptureInfo
740    # Copy xml into a new file
741    set templateFile "template[pid].xml"
742    set f [open $templateFile w]
743    puts $f "<?xml version=\"1.0\"?>"
744    puts $f [$_xmlobj xml]
745    close $f
746
747    # Return a list of the UQ variables and their PDFs.
748    # Also turns $uq(tFile) into a template file.
749    set _uq(varlist) [lindex [$_xmlobj uq_get_vars $templateFile] 0]
750    set _uq(tFile) $templateFile
751
752    # Create toolparameter file
753    set toolParameterFile "toolParameter[pid].hz"
754    set f [open $toolParameterFile w]
755    puts $f "file(execute):$templateFile"
756    close $f
757
758    set _uq(toolparamFile) $toolParameterFile
759
760    return $templateFile
761}
762
763itcl::body Rappture::Task::ExecuteSimulationCommand { cmd } {
764
765    set _job(runfile) ""
766    set _job(success) 0
767    set _job(exitcode) 0
768
769    # Step 1.  Write the command into the run file.
770    $_xmlobj put tool.execute $cmd
771
772    Log run started
773    Rappture::rusage mark
774
775    # Step 2.  Check if it is a special case "ECHO" command which always
776    #          succeeds.
777    if { [string compare -nocase -length 5 $cmd "ECHO "] == 0 } {
778        set _job(stdout) [string range $cmd 5 end]
779        set _job(success) 1
780        set _job(exitcode) 0
781        set _job(mesg) ""
782        return 1;                       # Success
783    }
784
785    # Step 3. Execute the command, collecting its stdout and stderr.
786    catch {
787        eval blt::bgexec [list [itcl::scope _job(control)]] \
788            -keepnewline yes \
789            -killsignal  SIGTERM \
790            -onerror     [list [itcl::code $this OnError]] \
791            -onoutput    [list [itcl::code $this OnOutput]] \
792            -output      [list [itcl::scope _job(stdout)]] \
793            -error       [list [itcl::scope _job(stderr)]] \
794            $cmd
795    } result
796
797    # Step 4. Check the token and the exit code.
798    set logmesg $result
799    foreach { token _job(pid) _job(exitcode) mesg } $_job(control) break
800    if { $token == "EXITED" } {
801        if { $_job(exitcode) != 0 } {
802            # This means that the program exited normally but returned a
803            # non-zero exitcode.  Consider this an invalid result from the
804            # program.  Append the stderr from the program to the message.
805            if {$_job(exitcode) > 128} {
806                set logmesg "Program signaled: signal was [GetSignal $_job(exitcode)]"
807            } else {
808                set logmesg "Program finished: non-zero exit code is $_job(exitcode)"
809            }
810            set _job(mesg) "$logmesg\n\n$_job(stderr)"
811            Log run failed [list $logmesg]
812            return 0;                   # Fail.
813        }
814        # Successful program termination with exit code of 0.
815    } elseif { $token == "abort" }  {
816        # The user pressed the abort button.
817
818        set logmesg "Program terminated by user."
819        Log run failed [list $logmesg]
820        set _job(mesg) "$logmesg\n\n$_job(stdout)"
821        return 0;                       # Fail
822    } else {
823        # Abnormal termination
824
825        set logmesg "Abnormal program termination:"
826        Log run failed [list $logmesg]
827        set _job(mesg) "$logmesg\n\n$_job(stdout)"
828        return 0;                       # Fail
829    }
830    if { $_uq(type) != "" } {
831        CollectUQResults
832    }
833
834    # Step 5. Look in stdout for the name of the run file.
835    set pattern {=RAPPTURE-RUN=>([^\n]+)}
836    if {![regexp $pattern $_job(stdout) match fileName]} {
837        set _job(mesg) "Can't find result file in output.\n"
838        append _job(mesg) "Did you call Rappture::result in your simulator?"
839        return 0;                       # Fail
840    }
841    set _job(runfile) $fileName
842    set _job(success) 1
843    set _job(mesg) $_job(stdout)
844    return 1;                           # Success
845}
846
847itcl::body Rappture::Task::LogSimulationUsage {} {
848    array set times [Rappture::rusage measure]
849
850    set toolId     [$_xmlobj get tool.id]
851    set toolVers   [$_xmlobj get tool.version.application.revision]
852    set simulation "simulation"
853    if { $toolId ne "" && $toolVers ne "" } {
854        set simulation "[pid]_${toolId}_r${toolVers}"
855    }
856
857    # Need to save job info? then invoke the callback
858    if { [string length $jobstats] > 0} {
859        lappend args "job"      [incr jobnum] \
860                     "event"    $simulation \
861                     "start"    $times(start) \
862                     "walltime" $times(walltime) \
863                     "cputime"  $times(cputime) \
864                     "status"   $_job(exitcode)
865        uplevel #0 $jobstats $args
866    }
867
868    #
869    # Scan through stderr channel and look for statements that
870    # represent grid jobs that were executed.  The statements look
871    # like this:
872    #
873    # MiddlewareTime: job=1 event=simulation start=3.001094 ...
874    #
875
876    set subjobs 0
877    set pattern {(^|\n)MiddlewareTime:( +[a-z]+=[^ \n]+)+(\n|$)}
878    while { [regexp -indices $pattern $_job(stderr) match] } {
879        foreach {p0 p1} $match break
880        if { [string index $_job(stderr) $p0] == "\n" } {
881            incr p0
882        }
883        array unset data
884        array set data {
885            job 1
886            event simulation
887            start 0
888            walltime 0
889            cputime 0
890            status 0
891        }
892        foreach arg [lrange [string range $_job(stderr) $p0 $p1] 1 end] {
893            foreach {key val} [split $arg =] break
894            set data($key) $val
895        }
896        set data(job)   [expr { $jobnum + $data(job) }]
897        set data(event) "subsimulation"
898        set data(start) [expr { $times(start) + $data(start) }]
899
900        set details ""
901        foreach key {job event start walltime cputime status} {
902            # Add required keys in a particular order
903            lappend details $key $data($key)
904            unset data($key)
905        }
906        foreach key [array names data] {
907            # Add anything else that the client gave -- venue, etc.
908            lappend details $key $data($key)
909        }
910
911        if {[string length $jobstats] > 0} {
912            uplevel #0 $jobstats $details
913        }
914
915        incr subjobs
916
917        # Done -- remove this statement
918        set _job(stderr) [string replace $_job(stderr) $p0 $p1]
919    }
920    incr jobnum $subjobs
921
922    # Add cputime info to run.xml file
923    if { [catch {
924        Rappture::library $_job(runfile)
925    } xmlobj] != 0 } {
926        error "Can't create rappture library: $xmlobj"
927    }
928    $xmlobj put output.walltime $times(walltime)
929    $xmlobj put output.cputime $times(cputime)
930    global env
931    if {[info exists env(SESSION)]} {
932        $xmlobj put output.session $env(SESSION)
933    }
934    set _job(xmlobj) $xmlobj
935}
936
937itcl::body Rappture::Task::LogSubmittedSimulationUsage {} {
938    array set times [Rappture::rusage measure]
939
940    set toolId     [$_xmlobj get tool.id]
941    set toolVers   [$_xmlobj get tool.version.application.revision]
942    set simulation "simulation"
943    if { $toolId ne "" && $toolVers ne "" } {
944        set simulation "[pid]_${toolId}_r${toolVers}"
945    }
946
947#   job info is not required because jobprotocol = submit
948#   if { [string length $jobstats] > 0} {
949#       lappend args \
950#           "job"      [incr jobnum] \
951#           "event"    $simulation \
952#           "start"    $times(start) \
953#           "walltime" $times(walltime) \
954#           "cputime"  $times(cputime) \
955#           "status"   $_job(exitcode)
956#       uplevel #0 $jobstats $args
957#   }
958
959# [click] messages go here
960    if { [string length $jobstats] > 0} {
961        set recordJobstats 1
962        if { [info exists _resources(-cacheuser)] } {
963            global env
964            if { $env(USER) eq $_resources(-cacheuser) } {
965                set recordJobstats 0
966            }
967        }
968        if { $recordJobstats } {
969            lappend args "job"      [incr jobnum] \
970                         "event"    "\[click\]" \
971                         "start"    $times(start) \
972                         "walltime" 0 \
973                         "cputime"  0 \
974                         "status"   0
975            uplevel #0 $jobstats $args
976        }
977    }
978
979    #
980    # Scan through stderr channel and look for statements that
981    # represent grid jobs that were executed.  The statements look
982    # like this:
983    #
984    # MiddlewareTime: job=1 event=simulation start=3.001094 ...
985    #
986
987    set subjobs 0
988    set pattern {(^|\n)MiddlewareTime:( +[a-z]+=[^ \n]+)+(\n|$)}
989    while { [regexp -indices $pattern $_job(stderr) match] } {
990        foreach {p0 p1} $match break
991        if { [string index $_job(stderr) $p0] == "\n" } {
992            incr p0
993        }
994        array unset data
995        array set data {
996            job 1
997            event simulation
998            start 0
999            walltime 0
1000            cputime 0
1001            status 0
1002        }
1003        foreach arg [lrange [string range $_job(stderr) $p0 $p1] 1 end] {
1004            foreach {key val} [split $arg =] break
1005            set data($key) $val
1006        }
1007        set data(job)   [expr { $jobnum + $data(job) }]
1008        set data(event) "subsimulation"
1009        set data(start) [expr { $times(start) + $data(start) }]
1010
1011#       puts stderr "event subsimulation start = $data(start)"
1012
1013        set details ""
1014        foreach key {job event start walltime cputime status} {
1015            # Add required keys in a particular order
1016            lappend details $key $data($key)
1017            unset data($key)
1018        }
1019        foreach key [array names data] {
1020            # Add anything else that the client gave -- venue, etc.
1021            lappend details $key $data($key)
1022        }
1023
1024#       if {[string length $jobstats] > 0} {
1025#           uplevel #0 $jobstats $details
1026#       }
1027
1028        incr subjobs
1029
1030        # Done -- remove this statement
1031        set _job(stderr) [string replace $_job(stderr) $p0 $p1]
1032    }
1033    incr jobnum $subjobs
1034
1035    # Add session info to run.xml file
1036    if { [catch {
1037        Rappture::library $_job(runfile)
1038    } xmlobj] != 0 } {
1039        error "Can't create rappture library: $xmlobj"
1040    }
1041    global env
1042    if {[info exists env(SESSION)]} {
1043        $xmlobj put output.session $env(SESSION)
1044    }
1045    set _job(xmlobj) $xmlobj
1046}
1047
1048itcl::body Rappture::Task::LogUQSimulationUsage {} {
1049    array set times [Rappture::rusage measure]
1050
1051    if { [string length $jobstats] > 0} {
1052        lappend args "job"      [incr jobnum] \
1053                     "event"    "\[click-uq\]" \
1054                     "start"    $times(start) \
1055                     "walltime" 0 \
1056                     "cputime"  0 \
1057                     "status"   0
1058        uplevel #0 $jobstats $args
1059    }
1060
1061    # Add session info to run.xml file
1062    if { [catch { Rappture::library $_job(runfile) } xmlobj] != 0 } {
1063        error "Can't create rappture library: $xmlobj"
1064    }
1065    global env
1066    if {[info exists env(SESSION)]} {
1067        $xmlobj put output.session $env(SESSION)
1068    }
1069    set _job(xmlobj) $xmlobj
1070}
1071
1072itcl::body Rappture::Task::LogCachedSimulationUsage {} {
1073    if { [catch {
1074        Rappture::library $_job(runfile)
1075    } xmlobj] != 0 } {
1076        error "Can't create rappture library: $xmlobj"
1077    }
1078    set _job(xmlobj) $xmlobj
1079}
1080
1081
1082itcl::body Rappture::Task::CheckForCachedRunFile { driverFile } {
1083
1084    # Read the driver file and collect its contents as the query.
1085    set url http://$_resources(-cachehosts)/cache/request
1086    set f [open $driverFile "r"]
1087    set query [read $f]
1088    close $f
1089
1090    # Make the query
1091    if { [catch {
1092        http::geturl $url -query $query -timeout 6000 -binary yes
1093    } token] != 0 } {
1094        puts stderr "error performing cache query: driverFile=$driverFile url=$url token=$token"
1095        return 0
1096    }
1097
1098#   puts stderr "ncode  = [http::ncode $token]"
1099#   puts stderr "code   = [::http::code $token]"
1100#   puts stderr "status = [::http::status $token]"
1101#   puts stderr "meta   = [::http::meta $token]"
1102
1103    set squid ""
1104    foreach {key value} [::http::meta $token] {
1105        set headers([string tolower $key]) $value
1106        if { [string tolower $key] == "etag" } {
1107            set squid $value
1108        }
1109    }
1110#   puts stderr "SQUID = $headers(etag)"
1111#   puts stderr "SQUID = $squid"
1112    if { [resources -jobprotocol] == "submit" } {
1113        if { $squid != "" } {
1114            # If the code is 200, we'll assume it's a cache hit.
1115            if { [http::ncode $token] == 200} {
1116                if { [catch {exec submit --cacheHit $squid} result] != 0 } {
1117                    puts stderr "submit --cacheHit $squid failed: $result"
1118                }
1119#               puts stderr "submit --cacheHit $squid"
1120            } else {
1121                if { [catch {exec submit --cacheMiss $squid} result] != 0 } {
1122                    puts stderr "submit --cacheMiss $squid failed: $result"
1123                }
1124#               puts stderr "submit --cacheMiss $squid"
1125            }
1126        } else {
1127            puts stderr "cache squid could not be determined."
1128        }
1129    }
1130
1131    # If the code isn't 200, we'll assume it's a cache miss.
1132    if { [http::ncode $token] != 200} {
1133        return 0
1134    }
1135    # Get contents of the run file.
1136    set contents [http::data $token]
1137    if { $contents == "" } {
1138        return 0
1139    }
1140
1141    # Create a new run.xml file and write the results into it.
1142    set secs [clock seconds]
1143    set millisecs [expr [clock clicks -milliseconds] % 1000]
1144    set timestamp [format %d%03d%03d $secs $millisecs 0]
1145
1146    global rapptureInfo
1147    set fileName [file join $rapptureInfo(cwd) "run${timestamp}.xml"]
1148    set f [open $fileName "w"]
1149    puts $f $contents
1150    close $f
1151    set _job(runfile) $fileName
1152    set _job(success) 1
1153    set _job(stderr) "Loading cached results\n"
1154    OnOutput "Loading cached results\n"
1155    update
1156    return 1
1157}
1158
1159itcl::body Rappture::Task::GetUQErrors {} {
1160    set contents {}
1161    if { [file exists "uq_debug.err"] } {
1162        set f [open "uq_debug.err" r]
1163        set contents [read $f]
1164        close $f
1165    }
1166    return $contents
1167}
1168
1169# UQ. Collect data from all jobs and put it in one xml run file.
1170itcl::body Rappture::Task::CollectUQResults {} {
1171    file delete -force -- "run_uq.xml"
1172    set hdfFile puq_[pid].hdf5
1173    if { [catch {
1174        exec puq.sh analyze $hdfFile
1175    } results] != 0 } {
1176        error "UQ analysis failed: $results\n[GetUQErrors]"
1177    } else {
1178        set _job(stdout) $results
1179    }
1180}
Note: See TracBrowser for help on using the repository browser.