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

Last change on this file since 726 was 726, checked in by mmc, 17 years ago

Added a fix so that tools running without a $SESSIONDIR/resources file
won't choke on the missing -appname resource.

File size: 10.3 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    public method installdir {} { return $_installdir }
27
28    public method run {args}
29    public method abort {}
30
31    protected method _output {data}
32
33    private variable _installdir ""  ;# installation directory for this tool
34    private variable _outputcb ""    ;# callback for tool output
35    private common job               ;# array var used for blt::bgexec jobs
36    private common jobnum 0          ;# counter for unique job number
37
38    # get global resources for this tool session
39    public proc resources {{option ""}}
40
41    public common _resources
42    public proc setAppName {name} { set _resources(-appname) $name }
43    public proc setHubName {name} { set _resources(-hubname) $name }
44    public proc setHubURL {name}  { set _resources(-huburl) $name }
45    public proc setSession {name} { set _resources(-session) $name }
46}
47
48# must use this name -- plugs into Rappture::resources::load
49proc tool_init_resources {} {
50    Rappture::resources::register \
51        application_name Rappture::Tool::setAppName \
52        application_id   Rappture::Tool::setAppId \
53        hub_name         Rappture::Tool::setHubName \
54        hub_url          Rappture::Tool::setHubURL \
55        session_token    Rappture::Tool::setSession
56}
57                                                                               
58# ----------------------------------------------------------------------
59# CONSTRUCTOR
60# ----------------------------------------------------------------------
61itcl::body Rappture::Tool::constructor {xmlobj installdir args} {
62    if {![Rappture::library isvalid $xmlobj]} {
63        error "bad value \"$xmlobj\": should be Rappture::Library"
64    }
65    set _xmlobj $xmlobj
66
67    if {![file exists $installdir]} {
68        error "directory \"$installdir\" doesn't exist"
69    }
70    set _installdir $installdir
71
72    eval configure $args
73}
74
75# ----------------------------------------------------------------------
76# USAGE: resources ?-option?
77#
78# Clients use this to query information about the tool.
79# ----------------------------------------------------------------------
80itcl::body Rappture::Tool::resources {{option ""}} {
81    if {$option == ""} {
82        return [array get _resources]
83    }
84    if {![info exists _resources($option)]} {
85        error "bad option \"$option\": should be [join [array names _resources] {, }]"
86    }
87    return $_resources($option)
88}
89
90# ----------------------------------------------------------------------
91# USAGE: run ?<path1> <value1> <path2> <value2> ...? ?-output <callbk>?
92#
93# This method causes the tool to run.  All widgets are synchronized
94# to the current XML representation, and a "driver.xml" file is
95# created as the input for the run.  That file is fed to the tool
96# according to the <tool><command> string, and the job is executed.
97#
98# Any "<path> <value>" arguments are used to override the current
99# settings from the GUI.  This is useful, for example, when filling
100# in missing simulation results from the analyzer.
101#
102# If the -output argument is included, then the next arg is a
103# callback command for output messages.  Any output that comes in
104# while the tool is running is sent back to the caller, so the user
105# can see progress running the tool.
106#
107# Returns a list of the form {status result}, where status is an
108# integer status code (0=success) and result is the output from the
109# simulator.  Successful output is something like {0 run1293921.xml},
110# where 0=success and run1293921.xml is the name of the file containing
111# results.
112# ----------------------------------------------------------------------
113itcl::body Rappture::Tool::run {args} {
114    global errorInfo
115
116    #
117    # Make sure that we save the proper application name.
118    # Actually, the best place to get this information is
119    # straight from the "installtool" script, but just in
120    # case we have an older tool, we should insert the
121    # tool name from the resources config file.
122    #
123    if {[info exists _resources(-appname)]
124          && "" != $_resources(-appname)
125          && "" == [$_xmlobj get tool.name]} {
126        $_xmlobj put tool.name $_resources(-appname)
127    }
128
129    # sync all widgets to the XML tree
130    sync
131
132    # if there are any args, use them to override parameters
133    set _outputcb ""
134    foreach {path val} $args {
135        if {$path == "-output"} {
136            set _outputcb $val
137        } else {
138            $_xmlobj put $path.current $val
139        }
140    }
141
142    foreach item {control output error} { set job($item) "" }
143
144    # write out the driver.xml file for the tool
145    set file "driver[pid].xml"
146    set status [catch {
147        set fid [open $file w]
148        puts $fid "<?xml version=\"1.0\"?>"
149        puts $fid [$_xmlobj xml]
150        close $fid
151    } result]
152
153    # set limits for cpu time and file size
154    set limit [$_xmlobj get tool.limits.cputime]
155    if {"" == $limit || [catch {Rappture::rlimit set cputime $limit}]} {
156        Rappture::rlimit set cputime 900  ;# 15 mins by default
157    }
158
159    set limit [$_xmlobj get tool.limits.filesize]
160    if {"" == $limit || [catch {Rappture::rlimit set filesize $limit}]} {
161        Rappture::rlimit set filesize 1000000  ;# 1MB by default
162    }
163
164    # execute the tool using the path from the tool description
165    if {$status == 0} {
166        set cmd [$_xmlobj get tool.command]
167        regsub -all @tool $cmd $_installdir cmd
168        regsub -all @driver $cmd $file cmd
169        regsub -all {\\} $cmd {\\\\} cmd
170        set cmd [string trimleft $cmd " "]
171
172        # starting job...
173        Rappture::rusage mark
174
175        if {0 == [string compare -nocase -length 5 $cmd "ECHO "] } {
176            set status 0;
177            set job(output) [string range $cmd 5 end]
178        } else {
179        set status [catch {eval blt::bgexec \
180            ::Rappture::Tool::job(control) \
181            -keepnewline yes \
182            -killsignal SIGTERM \
183            -onoutput [list [itcl::code $this _output]] \
184            -output ::Rappture::Tool::job(output) \
185            -error ::Rappture::Tool::job(error) $cmd} result]
186        }
187        # ...job is finished
188        array set times [Rappture::rusage measure]
189        puts stderr "MiddlewareTime: job=[incr jobnum] event=simulation start=$times(start) walltime=$times(walltime) cputime=$times(cputime) status=$status"
190
191        #
192        # Scan through stderr channel and look for statements that
193        # represent grid jobs that were executed.  The statements
194        # look like this:
195        #
196        # MiddlewareTime: job=1 event=simulation start=3.001094 ...
197        #
198        set subjobs 0
199        while {[regexp -indices {(^|\n)MiddlewareTime:( +[a-z]+=[^ \n]+)+(\n|$)} $job(error) match]} {
200            foreach {p0 p1} $match break
201            if {[string index $job(error) $p0] == "\n"} { incr p0 }
202
203            catch {unset data}
204            array set data {
205                job 1
206                event simulation
207                start 0
208                walltime 0
209                cputime 0
210                status 0
211            }
212            foreach arg [lrange [string range $job(error) $p0 $p1] 1 end] {
213                foreach {key val} [split $arg =] break
214                set data($key) $val
215            }
216            set data(job) [expr {$jobnum+$data(job)}]
217            set data(event) "subsimulation"
218            set data(start) [expr {$times(start)+$data(start)}]
219
220            set stmt "MiddlewareTime:"
221            foreach key {job event start walltime cputime status} {
222                # add required keys in a particular order
223                append stmt " $key=$data($key)"
224                unset data($key)
225            }
226            foreach key [array names data] {
227                # add anything else that the client gave -- venue, etc.
228                append stmt " $key=$data($key)"
229            }
230            puts stderr $stmt
231            incr subjobs
232
233            # done -- remove this statement
234            set job(error) [string replace $job(error) $p0 $p1]
235        }
236        incr jobnum $subjobs
237
238    } else {
239        set job(error) "$result\n$errorInfo"
240    }
241    if {$status == 0} {
242        file delete -force -- $file
243    }
244
245    # see if the job was aborted
246    if {[regexp {^KILLED} $job(control)]} {
247        return [list 0 "ABORT"]
248    }
249
250    #
251    # If successful, return the output, which should include
252    # a reference to the run.xml file containing results.
253    #
254    if {$status == 0} {
255        set file [string trim $job(output)]
256        return [list $status $file]
257    } elseif {"" != $job(output) || "" != $job(error)} {
258        return [list $status [string trim "$job(output)\n$job(error)"]]
259    }
260    return [list $status $result]
261}
262
263# ----------------------------------------------------------------------
264# USAGE: abort
265#
266# Clients use this during a "run" to abort the current job.
267# Kills the job and forces the "run" method to return.
268# ----------------------------------------------------------------------
269itcl::body Rappture::Tool::abort {} {
270    set job(control) "abort"
271}
272
273# ----------------------------------------------------------------------
274# USAGE: _output <data>
275#
276# Used internally to send each bit of output <data> coming from the
277# tool onto the caller, so the user can see progress.
278# ----------------------------------------------------------------------
279itcl::body Rappture::Tool::_output {data} {
280    if {[string length $_outputcb] > 0} {
281        uplevel #0 [list $_outputcb $data]
282    }
283}
Note: See TracBrowser for help on using the repository browser.