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

Last change on this file since 6710 was 6710, checked in by clarksm, 23 months ago

Do not use ionhelper if caching is disabled by tool, environment, down.

File size: 40.0 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    } else {
269        set helperEligible 0
270    }
271    if { !$cached } {
272        if { $_uq(type) != "" } {
273            set _uq(tFile) [GetUQTemplateFile]
274        }
275        global env
276        if { $_uq(type) == "" } {
277            if { $helperEligible } {
278                set cmd [GetCacheHelperCommand $driverFile]
279            } else {
280                set cmd [GetSimulationCommand $driverFile]
281            }
282            set ::env(RAPPTURE_UQ) False
283        } else {
284            set cmd [GetUQSimulationCommand $driverFile]
285            set ::env(RAPPTURE_UQ) True
286        }
287        if { $cmd == "" } {
288            puts stderr "cmd is empty"
289            append mesg "There is no command specified by\n\n"
290            append mesg "    <command>\n"
291            append mesg "    </command>\n\n"
292            append mesg "in the tool.xml file."
293            return [list 1 $mesg]
294        }
295        Rappture::rusage mark
296        if { ![ExecuteSimulationCommand $cmd] } {
297            return [list 1 $_job(mesg)]
298        }
299        if { $_uq(type) != "" } {
300            LogUQSimulationUsage
301        } elseif { [resources -jobprotocol] == "submit" } {
302            LogSubmittedSimulationUsage
303        } else {
304            LogSimulationUsage
305        }
306    } else {
307        LogCachedSimulationUsage
308    }
309    if { $_uq(tFile) ne "" } {
310        file delete -force -- $_uq(tFile)
311    }
312    if { $_uq(toolparamFile) ne "" } {
313        file delete -force -- $_uq(toolparamFile)
314    }
315    if { $_uq(paramsFile) ne "" } {
316        file delete -force -- $_uq(paramsFile)
317    }
318    if { $_job(success) } {
319        file delete -force -- $driverFile
320        Log run finished
321        return [list 0 $_job(xmlobj)]
322    } else {
323        # See if the job was aborted.
324        if {[regexp {^KILLED} $_job(control)]} {
325            Log run aborted
326            return [list 1 "ABORT"]
327        }
328        Log run failed [list 0 $_job(mesg)]
329        return [list 1 $_job(mesg)]
330    }
331}
332
333# ----------------------------------------------------------------------
334#  Turn the command string from tool.xml into the proper syntax to use
335#  with a submit parameter sweep with a temlate file.  Proper quoting
336#  of the template file is necessary to prevent submit from being too smart
337#  and converting it to a full pathname.
338# ----------------------------------------------------------------------
339itcl::body Rappture::Task::BuildSubmitBoincCommand {tFile toolparamFile params_file} {
340    set toolId   [$_xmlobj get tool.id]
341    set toolVers [$_xmlobj get tool.version.application.revision]
342    set newcmd "submit --venue boinc --progress submit --runName=puq --inputfile @:$tFile --data $params_file --env TOOL_PARAMETERS=$toolparamFile ${toolId}_r${toolVers} -w headless"
343
344    return $newcmd
345}
346
347itcl::body Rappture::Task::BuildSubmitLocalCommand {cmd tFile params_file} {
348    set quote_next 0
349    set newcmd "submit --local --progress submit --runName=puq --inputfile @:$tFile --data $params_file"
350    set cmds [split $cmd " "]
351    for {set i 0} {$i < [llength $cmds]} {incr i} {
352        set arg [lindex $cmds $i]
353        if {$quote_next == 1} {
354            set nc [string range $arg 0 0]
355            if {$nc != "\""} {
356                set arg "\"\\\"$arg\\\"\""
357            }
358        }
359        if {$arg == "--eval"} {
360            set quote_next 1
361        } else {
362            set quote_next 0
363        }
364        if {$arg == "@driver"} {
365            set arg "\"\\\"$tFile\\\"\""
366        }
367        append newcmd " " $arg
368    }
369    regsub -all @driver $newcmd $tFile newcmd
370
371    return $newcmd
372}
373
374# ----------------------------------------------------------------------
375# USAGE: abort
376#
377# Clients use this during a "run" to abort the current job.
378# Kills the job and forces the "run" method to return.
379# ----------------------------------------------------------------------
380itcl::body Rappture::Task::abort {} {
381    Log run abort
382    set _job(control) "abort"
383}
384
385# ----------------------------------------------------------------------
386# USAGE: reset
387#
388# Resets all input values to their defaults.  Sometimes used just
389# before a run to reset to a clean state.
390# ----------------------------------------------------------------------
391itcl::body Rappture::Task::reset {} {
392    $_xmlobj copy "" from $_origxml ""
393    foreach path [Rappture::entities -as path $_xmlobj input] {
394        if {[$_xmlobj element -as type $path.default] ne ""} {
395            set defval [$_xmlobj get $path.default]
396            $_xmlobj put $path.current $defval
397        }
398    }
399}
400
401# ----------------------------------------------------------------------
402# USAGE: xml <subcommand> ?<arg> <arg> ...?
403# USAGE: xml object
404#
405# Used by clients to manipulate the underlying XML data for this
406# tool.  The <subcommand> can be any operation supported by a
407# Rappture::library object.  Clients can also request the XML object
408# directly by using the "object" subcommand.
409# ----------------------------------------------------------------------
410itcl::body Rappture::Task::xml {args} {
411    if {"object" == $args} {
412        return $_xmlobj
413    }
414    return [eval $_xmlobj $args]
415}
416
417# ----------------------------------------------------------------------
418# USAGE: save <xmlobj> ?<filename>?
419#
420# Used by clients to save the contents of an <xmlobj> representing
421# a run out to the given file.  If <filename> is not specified, then
422# it uses the -resultsdir and other settings to do what Rappture
423# would normally do with the output.
424# ----------------------------------------------------------------------
425itcl::body Rappture::Task::save {xmlobj {filename ""}} {
426    if {$filename eq ""} {
427
428        # If there's a results_directory defined in the resources file,
429        # then move the run.xml file there for storage.
430
431        set rdir ""
432        if {$resultdir eq "@default"} {
433            if {[info exists _resources(-resultdir)]} {
434                set rdir $_resources(-resultdir)
435            } else {
436                global rapptureInfo
437                set rdir $rapptureInfo(cwd)
438            }
439        } elseif {$resultdir ne ""} {
440            set rdir $resultdir
441        }
442
443        # use the runfile name generated by the last run
444        if {$_job(runfile) ne ""} {
445            set filename [file join $rdir [file tail $_job(runfile)]]
446        } else {
447            set filename [file join $rdir run.xml]
448        }
449    }
450
451    # add any last-minute metadata
452    $xmlobj put output.time [clock format [clock seconds]]
453
454    $xmlobj put tool.version.rappture.version $::Rappture::version
455    $xmlobj put tool.version.rappture.revision $::Rappture::build
456    $xmlobj put output.filename $filename
457    $xmlobj put output.version $Rappture::version
458
459    if {[info exists ::tcl_platform(user)]} {
460        $xmlobj put output.user $::tcl_platform(user)
461    }
462
463    # save the output
464    set rdir [file dirname $filename]
465    file mkdir $rdir
466
467    set fid [open $filename w]
468    puts $fid "<?xml version=\"1.0\"?>"
469    puts $fid [$xmlobj xml]
470    close $fid
471
472    Log output saved in $filename
473}
474
475# ----------------------------------------------------------------------
476# USAGE: OnOutput <data>
477#
478# Used internally to send each bit of output <data> coming from the
479# tool onto the caller, so the user can see progress.
480# ----------------------------------------------------------------------
481itcl::body Rappture::Task::OnOutput {data} {
482    if {[string length $_outputcb] > 0} {
483        uplevel #0 $_outputcb [list $data]
484    }
485}
486
487# ----------------------------------------------------------------------
488# USAGE: OnError <data>
489#
490# Used internally to send each bit of error <data> coming from the
491# tool onto the caller, so the user can see progress.
492# ----------------------------------------------------------------------
493itcl::body Rappture::Task::OnError {data} {
494    if {[string length $_errorcb] > 0} {
495        uplevel #0 $_errorcb [list $data]
496    }
497}
498
499# ----------------------------------------------------------------------
500# USAGE: Log <cmd> <arg> <arg> ...
501#
502# Used internally to log interesting events during the run.  If the
503# -logger option is set (to Rappture::Logger::log, or something like
504# that), then the arguments to this method are passed along to the
505# logger and written out to a log file.  Logging is off by default,
506# so this method does nothing unless -logger is set.
507# ----------------------------------------------------------------------
508itcl::body Rappture::Task::Log {args} {
509    if {[string length $logger] > 0} {
510        uplevel #0 $logger [list $args]
511    }
512}
513
514# ----------------------------------------------------------------------
515# USAGE: MiddlewareTime <key> <value> ...
516#
517# Used as the default method for reporting job status information.
518# Implements the old HUBzero method of reporting job status info to
519# stderr, which can then be picked up by the tool session container.
520# Most tools use the "submit" command, which talks directly to a
521# database to log job information, so this isn't really needed.  But
522# it doesn't hurt to have this and it can be useful in some cases.
523# ----------------------------------------------------------------------
524itcl::body Rappture::Task::MiddlewareTime {args} {
525    set line "MiddlewareTime:"
526    foreach {key val} $args {
527        append line " $key=$val"
528    }
529    puts stderr $line
530}
531
532itcl::body Rappture::Task::IsCacheable {} {
533    if { ![info exists _resources(-cachehosts)] ||
534         $_resources(-cachehosts) == "" } {
535        puts stderr cachehosts=[info exists _resources(-cachehosts)]
536        return 0
537    }
538    global env
539    if { [info exists env(RAPPTURE_CACHE_OVERRIDE)] } {
540        set state $env(RAPPTURE_CACHE_OVERRIDE)
541    } else {
542        set state [$_xmlobj get "tool.cache"]
543    }
544    if { $state ne "" } {
545        puts stderr "cache tag is \"$state\""
546    }
547    if { $state eq "" || ![string is boolean $state] } {
548        return 1;                       # Default is to allow caching.
549    }
550    return $state
551}
552
553itcl::body Rappture::Task::IsCacheHelperEligible {} {
554    global env
555    if { ![info exists env(IONHELPER_ALLOWED)] } {
556        set helperEligible 0
557    } else {
558        if { $env(IONHELPER_ALLOWED) ne "1" } {
559            set helperEligible 0
560        } else {
561            if { $_uq(type) == "" } {
562#               puts stderr "cache_user exists       = [info exists _resources(-cacheuser)]"
563#               puts stderr "cache_write_host exists = [info exists _resources(-cachewritehost)]"
564                if { ![info exists _resources(-cacheuser)] || ![info exists _resources(-cachewritehost)] } {
565                    set helperEligible 0
566                } else {
567                    if { ![info exists env(USER)] } {
568                        set helperEligible 0
569                    } else {
570#                       puts stderr "env(USER)  = $env(USER)"
571#                       puts stderr "cache_user = $_resources(-cacheuser)"
572                        if { $env(USER) eq $_resources(-cacheuser) } {
573                            set helperEligible 0
574                        } else {
575                            set toolId    [$_xmlobj get tool.id]
576                            set toolVers  [$_xmlobj get tool.version.application.revision]
577                            set toolDir   [$_xmlobj get tool.version.application.directory(top)]
578                            set verifyDir [file join / apps ${toolId} r${toolVers}]
579#                           puts stderr "toolDir     = $toolDir"
580#                           puts stderr "verifyDir   = $verifyDir"
581                            if { $toolDir eq $verifyDir } {
582                                if { [ catch { file readlink [file join / apps ${toolId} current] } currentVers ] != 0 } {
583                                    set helperEligible 0
584                                } else {
585#                                   puts stderr "currentVers = $currentVers"
586                                    if { "r$toolVers" eq $currentVers } {
587                                        set helperEligible 1
588                                    } else {
589                                        set helperEligible 0
590                                    }
591                                }
592                            } else {
593                               set helperEligible 0
594                            }
595                        }
596                    }
597                }
598            } else {
599                set helperEligible 0
600            }
601        }
602    }
603#   puts stderr "helperEligible = $helperEligible"
604
605    return $helperEligible
606}
607
608#
609# Send the list of parameters to a python program so it can call PUQ
610# and get a CSV file containing the parameter values to use for the runs.
611itcl::body Rappture::Task::GetParamsForUQ {} {
612    set pid [pid]
613    # puts "puq.sh get_params $pid $_uq(varlist) $_uq(type) $_uq(args)"
614    if {[catch {
615        exec puq.sh get_params $pid $_uq(varlist) $_uq(type) $_uq(args)
616    } errs] != 0 } {
617        error "get_params.py failed: $errs\n[GetUQErrors]"
618    }
619    return "params${pid}.csv"
620}
621
622itcl::body Rappture::Task::SetCpuResourceLimit {} {
623    # Set limits for cpu time
624    set limit [$_xmlobj get tool.limits.cputime]
625    if { $limit == "unlimited" } {
626        set limit 43200;                # 12 hours
627    } else {
628        if { [scan $limit "%d" dum] != 1 } {
629            set limit 14400;            # 4 hours by default
630        } elseif { $limit > 43200 } {
631            set limit 43200;            # limit to 12 hrs.
632        } elseif { $limit < 10 } {
633            set limit 10;               # lower bound is 10 seconds.
634        }
635    }
636    Rappture::rlimit set cputime $limit
637}
638
639# Write out the driver.xml file for the tool
640itcl::body Rappture::Task::GetDriverFile {} {
641    global rapptureInfo
642    set fileName [file join $rapptureInfo(cwd) "driver[pid].xml"]
643#
644# Remove existing <meta> section
645    $_xmlobj remove "meta"
646# Copy original <meta> section
647    $_xmlobj copy "meta" from $_origxml "meta"
648# Add new <meta> entry
649    if { $xmlSource != "" } {
650        set identifier [uuid::uuid generate]
651        $_xmlobj put meta.driver($identifier).source $xmlSource
652        $_xmlobj put meta.driver($identifier).version $::Rappture::build
653        $_xmlobj put meta.driver($identifier).time [clock format [clock seconds]]
654        if { $xmlSource == "rapptureUI" } {
655            $_xmlobj put meta.generated human
656        }
657        set generated [$_xmlobj get meta.generated]
658        if { $generated == "" } {
659            $_xmlobj put meta.generated human
660        }
661        set generated [$_xmlobj get meta.generated]
662        global env
663        set ::env(RAPPTURE_GENERATED) $generated
664        if {[info exists env(RAPPTURE_GENERATED_FILE)]} {
665            set f [open $env(RAPPTURE_GENERATED_FILE) w]
666            puts $f "generated $generated"
667            close $f
668        }
669    }
670#
671    if { [catch {
672        set f [open $fileName w]
673        puts $f "<?xml version=\"1.0\"?>"
674        puts $f [$_xmlobj xml]
675        close $f
676    } errs] != 0 } {
677        error "can't create driver file \"$fileName\": $errs"
678    }
679    return $fileName
680}
681
682itcl::body Rappture::Task::GetCacheHelperCommand { driverFile } {
683    set cmd ""
684    set helperDriverDir [file join / var ion drivers]
685    if { [file exists $helperDriverDir] } {
686        set cacheHelperCommand [file join / apps bin iondrive]
687        if { [file exists $cacheHelperCommand] } {
688            file copy -force $driverFile $helperDriverDir
689            set cmd $cacheHelperCommand
690        }
691    }
692
693    return $cmd
694}
695
696itcl::body Rappture::Task::GetCommand { } {
697    set cmd [$_xmlobj get tool.command]
698    regsub -all @tool $cmd $_installdir cmd
699    set cmd [string trimleft $cmd " "]
700    return $cmd
701}
702
703itcl::body Rappture::Task::GetSimulationCommand { driverFile } {
704    set cmd [GetCommand]
705    if { $cmd == "" } {
706        return ""
707    }
708    regsub -all @driver $cmd $driverFile cmd
709
710    switch -glob -- [resources -jobprotocol] {
711        "submit*" {
712            # if job_protocol is "submit", then use use submit command
713            set cmd "submit --local $cmd"
714        }
715        "mx" {
716            # metachory submission
717            set cmd "mx $cmd"
718        }
719        "exec" {
720            # default -- nothing special
721        }
722    }
723    return $cmd
724}
725
726itcl::body Rappture::Task::GetUQSimulationCommand { driverFile } {
727    set cmd [GetCommand]
728    if { $cmd == "" } {
729        return ""
730    }
731    set _uq(paramsFile) [GetParamsForUQ]
732#   set cmd [BuildSubmitBoincCommand $_uq(tFile) $_uq(toolparamFile) $_uq(paramsFile)]
733    set cmd [BuildSubmitLocalCommand $cmd $_uq(tFile) $_uq(paramsFile)]
734
735    file delete -force puq
736
737    return $cmd
738}
739
740itcl::body Rappture::Task::GetUQTemplateFile {} {
741    global rapptureInfo
742    # Copy xml into a new file
743    set templateFile "template[pid].xml"
744    set f [open $templateFile w]
745    puts $f "<?xml version=\"1.0\"?>"
746    puts $f [$_xmlobj xml]
747    close $f
748
749    # Return a list of the UQ variables and their PDFs.
750    # Also turns $uq(tFile) into a template file.
751    set _uq(varlist) [lindex [$_xmlobj uq_get_vars $templateFile] 0]
752    set _uq(tFile) $templateFile
753
754    # Create toolparameter file
755    set toolParameterFile "toolParameter[pid].hz"
756    set f [open $toolParameterFile w]
757    puts $f "file(execute):$templateFile"
758    close $f
759
760    set _uq(toolparamFile) $toolParameterFile
761
762    return $templateFile
763}
764
765itcl::body Rappture::Task::ExecuteSimulationCommand { cmd } {
766
767    set _job(runfile) ""
768    set _job(success) 0
769    set _job(exitcode) 0
770
771    # Step 1.  Write the command into the run file.
772    $_xmlobj put tool.execute $cmd
773
774    Log run started
775    Rappture::rusage mark
776
777    # Step 2.  Check if it is a special case "ECHO" command which always
778    #          succeeds.
779    if { [string compare -nocase -length 5 $cmd "ECHO "] == 0 } {
780        set _job(stdout) [string range $cmd 5 end]
781        set _job(success) 1
782        set _job(exitcode) 0
783        set _job(mesg) ""
784        return 1;                       # Success
785    }
786
787    # Step 3. Execute the command, collecting its stdout and stderr.
788    catch {
789        eval blt::bgexec [list [itcl::scope _job(control)]] \
790            -keepnewline yes \
791            -killsignal  SIGTERM \
792            -onerror     [list [itcl::code $this OnError]] \
793            -onoutput    [list [itcl::code $this OnOutput]] \
794            -output      [list [itcl::scope _job(stdout)]] \
795            -error       [list [itcl::scope _job(stderr)]] \
796            $cmd
797    } result
798
799    # Step 4. Check the token and the exit code.
800    set logmesg $result
801    foreach { token _job(pid) _job(exitcode) mesg } $_job(control) break
802    if { $token == "EXITED" } {
803        if { $_job(exitcode) != 0 } {
804            # This means that the program exited normally but returned a
805            # non-zero exitcode.  Consider this an invalid result from the
806            # program.  Append the stderr from the program to the message.
807            if {$_job(exitcode) > 128} {
808                set logmesg "Program signaled: signal was [GetSignal $_job(exitcode)]"
809            } else {
810                set logmesg "Program finished: non-zero exit code is $_job(exitcode)"
811            }
812            set _job(mesg) "$logmesg\n\n$_job(stderr)"
813            Log run failed [list $logmesg]
814            return 0;                   # Fail.
815        }
816        # Successful program termination with exit code of 0.
817    } elseif { $token == "abort" }  {
818        # The user pressed the abort button.
819
820        set logmesg "Program terminated by user."
821        Log run failed [list $logmesg]
822        set _job(mesg) "$logmesg\n\n$_job(stdout)"
823        return 0;                       # Fail
824    } else {
825        # Abnormal termination
826
827        set logmesg "Abnormal program termination:"
828        Log run failed [list $logmesg]
829        set _job(mesg) "$logmesg\n\n$_job(stdout)"
830        return 0;                       # Fail
831    }
832    if { $_uq(type) != "" } {
833        CollectUQResults
834    }
835
836    # Step 5. Look in stdout for the name of the run file.
837    set pattern {=RAPPTURE-RUN=>([^\n]+)}
838    if {![regexp $pattern $_job(stdout) match fileName]} {
839        set _job(mesg) "Can't find result file in output.\n"
840        append _job(mesg) "Did you call Rappture::result in your simulator?"
841        return 0;                       # Fail
842    }
843    set _job(runfile) $fileName
844    set _job(success) 1
845    set _job(mesg) $_job(stdout)
846    return 1;                           # Success
847}
848
849itcl::body Rappture::Task::LogSimulationUsage {} {
850    array set times [Rappture::rusage measure]
851
852    set toolId     [$_xmlobj get tool.id]
853    set toolVers   [$_xmlobj get tool.version.application.revision]
854    set simulation "simulation"
855    if { $toolId ne "" && $toolVers ne "" } {
856        set simulation "[pid]_${toolId}_r${toolVers}"
857    }
858
859    # Need to save job info? then invoke the callback
860    if { [string length $jobstats] > 0} {
861        lappend args "job"      [incr jobnum] \
862                     "event"    $simulation \
863                     "start"    $times(start) \
864                     "walltime" $times(walltime) \
865                     "cputime"  $times(cputime) \
866                     "status"   $_job(exitcode)
867        uplevel #0 $jobstats $args
868    }
869
870    #
871    # Scan through stderr channel and look for statements that
872    # represent grid jobs that were executed.  The statements look
873    # like this:
874    #
875    # MiddlewareTime: job=1 event=simulation start=3.001094 ...
876    #
877
878    set subjobs 0
879    set pattern {(^|\n)MiddlewareTime:( +[a-z]+=[^ \n]+)+(\n|$)}
880    while { [regexp -indices $pattern $_job(stderr) match] } {
881        foreach {p0 p1} $match break
882        if { [string index $_job(stderr) $p0] == "\n" } {
883            incr p0
884        }
885        array unset data
886        array set data {
887            job 1
888            event simulation
889            start 0
890            walltime 0
891            cputime 0
892            status 0
893        }
894        foreach arg [lrange [string range $_job(stderr) $p0 $p1] 1 end] {
895            foreach {key val} [split $arg =] break
896            set data($key) $val
897        }
898        set data(job)   [expr { $jobnum + $data(job) }]
899        set data(event) "subsimulation"
900        set data(start) [expr { $times(start) + $data(start) }]
901
902        set details ""
903        foreach key {job event start walltime cputime status} {
904            # Add required keys in a particular order
905            lappend details $key $data($key)
906            unset data($key)
907        }
908        foreach key [array names data] {
909            # Add anything else that the client gave -- venue, etc.
910            lappend details $key $data($key)
911        }
912
913        if {[string length $jobstats] > 0} {
914            uplevel #0 $jobstats $details
915        }
916
917        incr subjobs
918
919        # Done -- remove this statement
920        set _job(stderr) [string replace $_job(stderr) $p0 $p1]
921    }
922    incr jobnum $subjobs
923
924    # Add cputime info to run.xml file
925    if { [catch {
926        Rappture::library $_job(runfile)
927    } xmlobj] != 0 } {
928        error "Can't create rappture library: $xmlobj"
929    }
930    $xmlobj put output.walltime $times(walltime)
931    $xmlobj put output.cputime $times(cputime)
932    global env
933    if {[info exists env(SESSION)]} {
934        $xmlobj put output.session $env(SESSION)
935    }
936    set _job(xmlobj) $xmlobj
937}
938
939itcl::body Rappture::Task::LogSubmittedSimulationUsage {} {
940    array set times [Rappture::rusage measure]
941
942    set toolId     [$_xmlobj get tool.id]
943    set toolVers   [$_xmlobj get tool.version.application.revision]
944    set simulation "simulation"
945    if { $toolId ne "" && $toolVers ne "" } {
946        set simulation "[pid]_${toolId}_r${toolVers}"
947    }
948
949#   job info is not required because jobprotocol = submit
950#   if { [string length $jobstats] > 0} {
951#       lappend args \
952#           "job"      [incr jobnum] \
953#           "event"    $simulation \
954#           "start"    $times(start) \
955#           "walltime" $times(walltime) \
956#           "cputime"  $times(cputime) \
957#           "status"   $_job(exitcode)
958#       uplevel #0 $jobstats $args
959#   }
960
961# [click] messages go here
962    if { [string length $jobstats] > 0} {
963        set recordJobstats 1
964        if { [info exists _resources(-cacheuser)] } {
965            global env
966            if { $env(USER) eq $_resources(-cacheuser) } {
967                set recordJobstats 0
968            }
969        }
970        if { $recordJobstats } {
971            lappend args "job"      [incr jobnum] \
972                         "event"    "\[click\]" \
973                         "start"    $times(start) \
974                         "walltime" 0 \
975                         "cputime"  0 \
976                         "status"   0
977            uplevel #0 $jobstats $args
978        }
979    }
980
981    #
982    # Scan through stderr channel and look for statements that
983    # represent grid jobs that were executed.  The statements look
984    # like this:
985    #
986    # MiddlewareTime: job=1 event=simulation start=3.001094 ...
987    #
988
989    set subjobs 0
990    set pattern {(^|\n)MiddlewareTime:( +[a-z]+=[^ \n]+)+(\n|$)}
991    while { [regexp -indices $pattern $_job(stderr) match] } {
992        foreach {p0 p1} $match break
993        if { [string index $_job(stderr) $p0] == "\n" } {
994            incr p0
995        }
996        array unset data
997        array set data {
998            job 1
999            event simulation
1000            start 0
1001            walltime 0
1002            cputime 0
1003            status 0
1004        }
1005        foreach arg [lrange [string range $_job(stderr) $p0 $p1] 1 end] {
1006            foreach {key val} [split $arg =] break
1007            set data($key) $val
1008        }
1009        set data(job)   [expr { $jobnum + $data(job) }]
1010        set data(event) "subsimulation"
1011        set data(start) [expr { $times(start) + $data(start) }]
1012
1013#       puts stderr "event subsimulation start = $data(start)"
1014
1015        set details ""
1016        foreach key {job event start walltime cputime status} {
1017            # Add required keys in a particular order
1018            lappend details $key $data($key)
1019            unset data($key)
1020        }
1021        foreach key [array names data] {
1022            # Add anything else that the client gave -- venue, etc.
1023            lappend details $key $data($key)
1024        }
1025
1026#       if {[string length $jobstats] > 0} {
1027#           uplevel #0 $jobstats $details
1028#       }
1029
1030        incr subjobs
1031
1032        # Done -- remove this statement
1033        set _job(stderr) [string replace $_job(stderr) $p0 $p1]
1034    }
1035    incr jobnum $subjobs
1036
1037    # Add session info to run.xml file
1038    if { [catch {
1039        Rappture::library $_job(runfile)
1040    } xmlobj] != 0 } {
1041        error "Can't create rappture library: $xmlobj"
1042    }
1043    global env
1044    if {[info exists env(SESSION)]} {
1045        $xmlobj put output.session $env(SESSION)
1046    }
1047    set _job(xmlobj) $xmlobj
1048}
1049
1050itcl::body Rappture::Task::LogUQSimulationUsage {} {
1051    array set times [Rappture::rusage measure]
1052
1053    if { [string length $jobstats] > 0} {
1054        lappend args "job"      [incr jobnum] \
1055                     "event"    "\[click-uq\]" \
1056                     "start"    $times(start) \
1057                     "walltime" 0 \
1058                     "cputime"  0 \
1059                     "status"   0
1060        uplevel #0 $jobstats $args
1061    }
1062
1063    # Add session info to run.xml file
1064    if { [catch { Rappture::library $_job(runfile) } xmlobj] != 0 } {
1065        error "Can't create rappture library: $xmlobj"
1066    }
1067    global env
1068    if {[info exists env(SESSION)]} {
1069        $xmlobj put output.session $env(SESSION)
1070    }
1071    set _job(xmlobj) $xmlobj
1072}
1073
1074itcl::body Rappture::Task::LogCachedSimulationUsage {} {
1075    if { [catch {
1076        Rappture::library $_job(runfile)
1077    } xmlobj] != 0 } {
1078        error "Can't create rappture library: $xmlobj"
1079    }
1080    set _job(xmlobj) $xmlobj
1081}
1082
1083
1084itcl::body Rappture::Task::CheckForCachedRunFile { driverFile } {
1085
1086    # Read the driver file and collect its contents as the query.
1087    set url http://$_resources(-cachehosts)/cache/request
1088    set f [open $driverFile "r"]
1089    set query [read $f]
1090    close $f
1091
1092    # Make the query
1093    if { [catch {
1094        http::geturl $url -query $query -timeout 6000 -binary yes
1095    } token] != 0 } {
1096        puts stderr "error performing cache query: driverFile=$driverFile url=$url token=$token"
1097        return 0
1098    }
1099
1100#   puts stderr "ncode  = [http::ncode $token]"
1101#   puts stderr "code   = [::http::code $token]"
1102#   puts stderr "status = [::http::status $token]"
1103#   puts stderr "meta   = [::http::meta $token]"
1104
1105    set squid ""
1106    foreach {key value} [::http::meta $token] {
1107        set headers([string tolower $key]) $value
1108        if { [string tolower $key] == "etag" } {
1109            set squid $value
1110        }
1111    }
1112#   puts stderr "SQUID = $headers(etag)"
1113#   puts stderr "SQUID = $squid"
1114    if { [resources -jobprotocol] == "submit" } {
1115        if { $squid != "" } {
1116            # If the code is 200, we'll assume it's a cache hit.
1117            if { [http::ncode $token] == 200} {
1118                if { [catch {exec submit --cacheHit $squid} result] != 0 } {
1119                    puts stderr "submit --cacheHit $squid failed: $result"
1120                }
1121#               puts stderr "submit --cacheHit $squid"
1122            } else {
1123                if { [catch {exec submit --cacheMiss $squid} result] != 0 } {
1124                    puts stderr "submit --cacheMiss $squid failed: $result"
1125                }
1126#               puts stderr "submit --cacheMiss $squid"
1127            }
1128        } else {
1129            puts stderr "cache squid could not be determined."
1130        }
1131    }
1132
1133    # If the code isn't 200, we'll assume it's a cache miss.
1134    if { [http::ncode $token] != 200} {
1135        return 0
1136    }
1137    # Get contents of the run file.
1138    set contents [http::data $token]
1139    if { $contents == "" } {
1140        return 0
1141    }
1142
1143    # Create a new run.xml file and write the results into it.
1144    set secs [clock seconds]
1145    set millisecs [expr [clock clicks -milliseconds] % 1000]
1146    set timestamp [format %d%03d%03d $secs $millisecs 0]
1147
1148    global rapptureInfo
1149    set fileName [file join $rapptureInfo(cwd) "run${timestamp}.xml"]
1150    set f [open $fileName "w"]
1151    puts $f $contents
1152    close $f
1153    set _job(runfile) $fileName
1154    set _job(success) 1
1155    set _job(stderr) "Loading cached results\n"
1156    OnOutput "Loading cached results\n"
1157    update
1158    return 1
1159}
1160
1161itcl::body Rappture::Task::GetUQErrors {} {
1162    set contents {}
1163    if { [file exists "uq_debug.err"] } {
1164        set f [open "uq_debug.err" r]
1165        set contents [read $f]
1166        close $f
1167    }
1168    return $contents
1169}
1170
1171# UQ. Collect data from all jobs and put it in one xml run file.
1172itcl::body Rappture::Task::CollectUQResults {} {
1173    file delete -force -- "run_uq.xml"
1174    set hdfFile puq_[pid].hdf5
1175    if { [catch {
1176        exec puq.sh analyze $hdfFile
1177    } results] != 0 } {
1178        error "UQ analysis failed: $results\n[GetUQErrors]"
1179    } else {
1180        set _job(stdout) $results
1181    }
1182}
Note: See TracBrowser for help on using the repository browser.