source: trunk/gui/scripts/tool.tcl @ 2505

Last change on this file since 2505 was 2164, checked in by mmc, 14 years ago

Fixed the way the Tool object resets itself before a run. Each Tool takes
a snapshot of the original XML file, and copies that clean snapshot back
into the working XML whenever the tool is reset. This cleans out any junk
from previous runs. This was an issue in the regression tester, where one
test case might have an extra input and all test cases that ran after that
would keep that extra input junk around. Each test starts cleanly now.

File size: 13.5 KB
Line 
1# ----------------------------------------------------------------------
2#  COMPONENT: tool - represents an entire tool
3#
4#  This object represents an entire tool defined by Rappture.
5#  Each tool resides in an installation directory with other tool
6#  resources (libraries, examples, etc.).  Each tool is defined by
7#  its inputs and outputs, which are tied to various widgets in the
8#  GUI.  Each tool tracks the inputs, knows when they're changed,
9#  and knows how to run itself to produce new results.
10# ======================================================================
11#  AUTHOR:  Michael McLennan, Purdue University
12#  Copyright (c) 2004-2005  Purdue Research Foundation
13#
14#  See the file "license.terms" for information on usage and
15#  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
16# ======================================================================
17package require BLT
18
19itcl::class Rappture::Tool {
20    inherit Rappture::ControlOwner
21
22    constructor {xmlobj installdir args} {
23        Rappture::ControlOwner::constructor ""
24    } { # defined below }
25
26    destructor { # defined below }
27
28    public method installdir {} { return $_installdir }
29
30    public method run {args}
31    public method abort {}
32    public method reset {}
33
34    protected method _mkdir {dir}
35    protected method _output {data}
36
37    private variable _origxml ""     ;# copy of original XML (for reset)
38    private variable _installdir ""  ;# installation directory for this tool
39    private variable _outputcb ""    ;# callback for tool output
40    private common job               ;# array var used for blt::bgexec jobs
41    private common jobnum 0          ;# counter for unique job number
42
43    # get global resources for this tool session
44    public proc resources {{option ""}}
45
46    public common _resources
47    public proc setAppName {name}   { set _resources(-appname) $name }
48    public proc setHubName {name}   { set _resources(-hubname) $name }
49    public proc setHubURL {name}    { set _resources(-huburl) $name }
50    public proc setSession {name}   { set _resources(-session) $name }
51    public proc setJobPrt {name}    { set _resources(-jobprotocol) $name }
52    public proc setResultDir {name} { set _resources(-resultdir) $name }
53}
54
55# must use this name -- plugs into Rappture::resources::load
56proc tool_init_resources {} {
57    Rappture::resources::register \
58        application_name  Rappture::Tool::setAppName \
59        application_id    Rappture::Tool::setAppId \
60        hub_name          Rappture::Tool::setHubName \
61        hub_url           Rappture::Tool::setHubURL \
62        session_token     Rappture::Tool::setSession \
63        job_protocol      Rappture::Tool::setJobPrt \
64        results_directory Rappture::Tool::setResultDir
65}
66
67# ----------------------------------------------------------------------
68# CONSTRUCTOR
69# ----------------------------------------------------------------------
70itcl::body Rappture::Tool::constructor {xmlobj installdir args} {
71    if {![Rappture::library isvalid $xmlobj]} {
72        error "bad value \"$xmlobj\": should be Rappture::Library"
73    }
74    set _xmlobj $xmlobj
75
76    # stash a copy of the original XML for later "reset" operations
77    set _origxml [Rappture::LibraryObj ::#auto "<?xml version=\"1.0\"?><run/>"]
78    $_origxml copy "" from $_xmlobj ""
79
80    if {![file exists $installdir]} {
81        error "directory \"$installdir\" doesn't exist"
82    }
83    set _installdir $installdir
84
85    eval configure $args
86}
87
88# ----------------------------------------------------------------------
89# DESTRUCTOR
90# ----------------------------------------------------------------------
91itcl::body Rappture::Tool::destructor {} {
92    itcl::delete object $_origxml
93}
94
95# ----------------------------------------------------------------------
96# USAGE: resources ?-option?
97#
98# Clients use this to query information about the tool.
99# ----------------------------------------------------------------------
100itcl::body Rappture::Tool::resources {{option ""}} {
101    if {$option == ""} {
102        return [array get _resources]
103    }
104    if {[info exists _resources($option)]} {
105        return $_resources($option)
106    }
107    return ""
108}
109
110# ----------------------------------------------------------------------
111# USAGE: run ?<path1> <value1> <path2> <value2> ...? ?-output <callbk>?
112#
113# This method causes the tool to run.  All widgets are synchronized
114# to the current XML representation, and a "driver.xml" file is
115# created as the input for the run.  That file is fed to the tool
116# according to the <tool><command> string, and the job is executed.
117#
118# Any "<path> <value>" arguments are used to override the current
119# settings from the GUI.  This is useful, for example, when filling
120# in missing simulation results from the analyzer.
121#
122# If the -output argument is included, then the next arg is a
123# callback command for output messages.  Any output that comes in
124# while the tool is running is sent back to the caller, so the user
125# can see progress running the tool.
126#
127# Returns a list of the form {status result}, where status is an
128# integer status code (0=success) and result is the output from the
129# simulator.  Successful output is something like {0 run1293921.xml},
130# where 0=success and run1293921.xml is the name of the file containing
131# results.
132# ----------------------------------------------------------------------
133itcl::body Rappture::Tool::run {args} {
134    global errorInfo
135
136    #
137    # Make sure that we save the proper application name.
138    # Actually, the best place to get this information is
139    # straight from the "installtool" script, but just in
140    # case we have an older tool, we should insert the
141    # tool name from the resources config file.
142    #
143    if {[info exists _resources(-appname)]
144          && "" != $_resources(-appname)
145          && "" == [$_xmlobj get tool.name]} {
146        $_xmlobj put tool.name $_resources(-appname)
147    }
148
149    # sync all widgets to the XML tree
150    sync
151
152    # if there are any args, use them to override parameters
153    set _outputcb ""
154    foreach {path val} $args {
155        if {$path == "-output"} {
156            set _outputcb $val
157        } else {
158            $_xmlobj put $path.current $val
159        }
160    }
161
162    foreach item {control output error} { set job($item) "" }
163
164    # write out the driver.xml file for the tool
165    set file "driver[pid].xml"
166    set status [catch {
167        set fid [open $file w]
168        puts $fid "<?xml version=\"1.0\"?>"
169        puts $fid [$_xmlobj xml]
170        close $fid
171    } result]
172
173    # set limits for cpu time
174    set limit [$_xmlobj get tool.limits.cputime]
175    if {"" == $limit || [catch {Rappture::rlimit set cputime $limit}]} {
176        Rappture::rlimit set cputime 900  ;# 15 mins by default
177    }
178
179    # execute the tool using the path from the tool description
180    if {$status == 0} {
181        set cmd [$_xmlobj get tool.command]
182        regsub -all @tool $cmd $_installdir cmd
183        regsub -all @driver $cmd $file cmd
184        regsub -all {\\} $cmd {\\\\} cmd
185        set cmd [string trimleft $cmd " "]
186
187        # if job_protocol is "submit", then use use submit command
188        if {[resources -jobprotocol] == "submit"} {
189            set cmd [linsert $cmd 0 submit --local]
190        }
191
192        # starting job...
193        Rappture::rusage mark
194
195        if {0 == [string compare -nocase -length 5 $cmd "ECHO "] } {
196            set status 0;
197            set job(output) [string range $cmd 5 end]
198        } else {
199            set status [catch {eval blt::bgexec \
200                ::Rappture::Tool::job(control) \
201                -keepnewline yes \
202                -killsignal SIGTERM \
203                -onoutput [list [itcl::code $this _output]] \
204                -output ::Rappture::Tool::job(output) \
205                -error ::Rappture::Tool::job(error) $cmd} result]
206        }
207        # ...job is finished
208        array set times [Rappture::rusage measure]
209
210        if {[resources -jobprotocol] != "submit"} {
211            set id [$_xmlobj get tool.id]
212            set vers [$_xmlobj get tool.version.application.revision]
213            set simulation simulation
214            if { $id != "" && $vers != "" } {
215                set pid [pid]
216                set simulation ${pid}_${id}_r${vers}
217            }
218            puts stderr "MiddlewareTime: job=[incr jobnum] event=$simulation start=$times(start) walltime=$times(walltime) cputime=$times(cputime) status=$status"
219
220            #
221            # Scan through stderr channel and look for statements that
222            # represent grid jobs that were executed.  The statements
223            # look like this:
224            #
225            # MiddlewareTime: job=1 event=simulation start=3.001094 ...
226            #
227            set subjobs 0
228            while {[regexp -indices {(^|\n)MiddlewareTime:( +[a-z]+=[^ \n]+)+(\n|$)} $job(error) match]} {
229                foreach {p0 p1} $match break
230                if {[string index $job(error) $p0] == "\n"} { incr p0 }
231
232                catch {unset data}
233                array set data {
234                    job 1
235                    event simulation
236                    start 0
237                    walltime 0
238                    cputime 0
239                    status 0
240                }
241                foreach arg [lrange [string range $job(error) $p0 $p1] 1 end] {
242                    foreach {key val} [split $arg =] break
243                    set data($key) $val
244                }
245                set data(job) [expr {$jobnum+$data(job)}]
246                set data(event) "subsimulation"
247                set data(start) [expr {$times(start)+$data(start)}]
248
249                set stmt "MiddlewareTime:"
250                foreach key {job event start walltime cputime status} {
251                    # add required keys in a particular order
252                    append stmt " $key=$data($key)"
253                    unset data($key)
254                }
255                foreach key [array names data] {
256                    # add anything else that the client gave -- venue, etc.
257                    append stmt " $key=$data($key)"
258                }
259                puts stderr $stmt
260                incr subjobs
261
262                # done -- remove this statement
263                set job(error) [string replace $job(error) $p0 $p1]
264            }
265            incr jobnum $subjobs
266        }
267
268    } else {
269        set job(error) "$result\n$errorInfo"
270    }
271    if {$status == 0} {
272        file delete -force -- $file
273    }
274
275    # see if the job was aborted
276    if {[regexp {^KILLED} $job(control)]} {
277        return [list 0 "ABORT"]
278    }
279
280    #
281    # If successful, return the output, which should include
282    # a reference to the run.xml file containing results.
283    #
284    if {$status == 0} {
285        set result [string trim $job(output)]
286        if {[regexp {=RAPPTURE-RUN=>([^\n]+)} $result match file]} {
287            set status [catch {Rappture::library $file} result]
288            if {$status != 0} {
289                global errorInfo
290                set result "$result\n$errorInfo"
291            }
292
293            # if there's a results_directory defined in the resources
294            # file, then move the run.xml file there for storage
295            if {[info exists _resources(-resultdir)]
296                  && "" != $_resources(-resultdir)} {
297                catch {
298                    if {![file exists $_resources(-resultdir)]} {
299                        _mkdir $_resources(-resultdir)
300                    }
301                    file rename -force -- $file $_resources(-resultdir)
302                }
303            }
304        } else {
305            set status 1
306            set result "Can't find result file in output.\nDid you call Rappture
307::result in your simulator?"
308        }
309        return [list $status $result]
310    } elseif {"" != $job(output) || "" != $job(error)} {
311        return [list $status [string trim "$job(output)\n$job(error)"]]
312    }
313    return [list $status $result]
314}
315
316# ----------------------------------------------------------------------
317# USAGE: _mkdir <directory>
318#
319# Used internally to create the <directory> in the file system.
320# The parent directory is also created, as needed.
321# ----------------------------------------------------------------------
322itcl::body Rappture::Tool::_mkdir {dir} {
323    set parent [file dirname $dir]
324    if {"." != $parent && "/" != $parent} {
325        if {![file exists $parent]} {
326            _mkdir $parent
327        }
328    }
329    file mkdir $dir
330}
331
332
333# ----------------------------------------------------------------------
334# USAGE: abort
335#
336# Clients use this during a "run" to abort the current job.
337# Kills the job and forces the "run" method to return.
338# ----------------------------------------------------------------------
339itcl::body Rappture::Tool::abort {} {
340    set job(control) "abort"
341}
342
343# ----------------------------------------------------------------------
344# USAGE: reset
345#
346# Resets all input values to their defaults.  Sometimes used just
347# before a run to reset to a clean state.
348# ----------------------------------------------------------------------
349itcl::body Rappture::Tool::reset {} {
350    $_xmlobj copy "" from $_origxml ""
351    foreach path [Rappture::entities -as path $_xmlobj input] {
352        if {[$_xmlobj element -as type $path.default] ne ""} {
353            set defval [$_xmlobj get $path.default]
354            $_xmlobj put $path.current $defval
355        }
356    }
357}
358
359# ----------------------------------------------------------------------
360# USAGE: _output <data>
361#
362# Used internally to send each bit of output <data> coming from the
363# tool onto the caller, so the user can see progress.
364# ----------------------------------------------------------------------
365itcl::body Rappture::Tool::_output {data} {
366    if {[string length $_outputcb] > 0} {
367        uplevel #0 $_outputcb [list $data]
368    }
369}
Note: See TracBrowser for help on using the repository browser.