LDAS logo
TclDOC logo

The procfs.tcl Script

Modification Date: 11/04/2009

Table of Procedures

red ball getPid
red ball killChild
red ball procfs.tcl
red ball ps::all
red ball ps::children
red ball ps::duration
red ball ps::jiffieOverflow
red ball ps::kill
red ball ps::linuxTime
red ball ps::memOverflow
red ball ps::os
red ball ps::procfiles
red ball ps::procfsValidate
red ball ps::procids
red ball ps::ps
red ball ps::self
red ball ps::server
red ball ps::servercfg
red ball ps::serverhandler
red ball ps::standalone
red ball ps::trace
red ball ps::type
red ball ps::userPids
red ball ps::zombies
red ball sysData


Name:
procfs.tcl

Description:
Tcl package for parsing the /proc filesystem on Linux and Solaris.

Parameters:

Usage:
On Linux, the proc filesystem is text, so easily parsed:

/proc/<pid>/stat contains:
pid (fname) state ppid pgrp session tty tpgid flags minflt cminflt majflt cmajflt utime stime cutime cstime pri nice 0 itreadv stt vsz rss rlim startcode endcode startstack kstkesp kstkeip signal blocked sigignore sigcatch wchan nswap cnswap exit_signal processor We noted a discrepancy with the adoption of RedHat 9.0:
pid (fname) state ppid pgrp session tty tpgid flags minflt cminflt majflt cmajflt utime stime cutime cstime pri (15) nice 0 itreadv rss vsz ??...
With RedHat 9 there are 46 values in stat!!
Here is a sample /proc/<pid>/stat:
28829 (mpiAPI) S 28828 28511 26870 34820 4711 64 15745 0 4 0 673496 1234363 0 0 15 0 0 0 17383534 15630336 1417 4294967295 134512640 134515032 3221222416 3212835016 1076040017 0 0 4096 0 3222461309 0 0 33 1 0 0 673496 1234363 0 0 jiffie counts for utime and stime seem completely wrong, or maybe /proc/uptime has changed??
There seems to be a mystery field somewhere between rss and processor pre RedHat 9.0.
state: R - run S - sleep D - disk Z - zombie T - stop W - swap On Solaris, the parse the /proc/<pid>/psinfo files are binary:
/* process ps(1) information file. /proc/<pid>/psinfo */ typedef struct psinfo { int pr_flag; /* process flags */ int pr_nlwp; /* number of lwps in process */ pid_t pr_pid; /* unique process id */ pid_t pr_ppid; /* process id of parent */ pid_t pr_pgid; /* pid of process group leader */ pid_t pr_sid; /* session id */ uid_t pr_uid; /* real user id */ uid_t pr_euid; /* effective user id */ gid_t pr_gid; /* real group id */ gid_t pr_egid; /* effective group id */ uintptr_t pr_addr; /* address of process */ size_t pr_size; /* size of process image in Kbytes */ size_t pr_rssize; /* resident set size in Kbytes */ size_t pr_pad1; /* padding */ dev_t pr_ttydev; /* controlling tty device (or PRNODEV) */ /* The following percent numbers are 16-bit binary */ /* fractions [0 .. 1] with the binary point to the */ /* right of the high-order bit (1.0 == 0x8000) */ ushort_t pr_pctcpu; /* % of recent cpu time used by all lwps */ ushort_t pr_pctmem; /* % of system memory used by process */ timestruc_t pr_start; /* process start time, from the epoch */ timestruc_t pr_time; /* usr+sys cpu time for this process */ timestruc_t pr_ctime; /* usr+sys cpu time for reaped children */ char pr_fname[PRFNSZ]; /* name of execed file (16 chars) */ char pr_psargs[PRARGSZ]; /* initial 80 characters of arg list */ int pr_wstat; /* if zombie, the wait() status */ int pr_argc; /* initial argument count */ uintptr_t pr_argv; /* address of initial argument vector */ uintptr_t pr_envp; /* address of initial environment vector */ char pr_dmodel; /* data model of the process */ char pr_pad2[3]; /* 3 bytes padding */ taskid_t pr_taskid; /* task id */ projid_t pr_projid; /* project id */ int pr_filler[5]; /* reserved for future use */ lwpsinfo_t pr_lwp; /* information for representative lwp */ } psinfo_t;
Comments:
package provide procfs 1.0
package require genericAPI
namespace eval ps {}

§   §   §

Name: sysData

Description:
Returns useful information about the current running program.

Parameters: Usage:
      foreach [ list name pid pcpu pmem vsz rss updt ] \
         [ sysData ] { break }
  returns:

name pid pcpu pmem vsz rss updt where:
name - name of the process as reported by ps pid - process i.d.
pcpu - percent of cpu cycles used by process pmem - percent of system memory used by process vsz - total allocated memory for process rss - current memory in use updt - elapsed run time of process
Comments:
Will return the data for the main thread of the current API.
This procedure was formerly in
genericAPI.tcl
proc sysData { args } {
     if { [ catch {
	   set data [ ps::self ]
     } err ] } {
	   addLogEntry $err red
	   set data [ list ${::API}API [ pid ] 0.01 0.01 1 1 "0 sec" ]
	}
	return $data
}

§   §   §

Name: getPid

Description:
Given a program name, return the pid if the program is running. Given a pid, returns the program name!
Usage:

Comments:

Used to get information needed to be able to shutdown erstwhile processes.
Depends on the existence of the "pgrep" system call.
If the manager is trying to get the pid of a remote API then execssh call will be used to get the pids.
proc getPidX { { prog "" } { host "" } } {
     set tmp  [ list ]
     set data [ list ]
     set pids [ list ]
     set api_rx "([ join $::API_LIST | ])"
     if { [ catch {
        if { [ string length $host ] } {
           ;## no problem
        } elseif { [ regexp -nocase $api_rx $prog -> api ] } {
           set host [ set ::${api}(host) ]
        } else {
           set host $::env(HOST)
        }
        ;## make sure to use * here instead of [pid] as we need API's pid
        if { [ info exists api ] } {
           set lockfile \
            [ glob -nocomplain [ file join $::env(RUNDIR) ${api}API .$api.*.lock ] ]
           if { [ file exists $lockfile ] } {
              set fid [ open $lockfile r ]
              set pids [ read $fid [ file size $lockfile ] ]
              addLogEntry "getting pids '$pids' from lockfile $lockfile" purple
              ::close $fid
           }
        }
        ;## should never get here
	   if { [ llength $pids ] == 0 } {
           if { ! [ string equal localhost $host ] && \
                ! [ string equal $host $::env(HOST) ] } {
              set data [ execssh $host /bin/ps -Ao fname,pid ]
           } else {
              set data [ exec /bin/ps -Ao fname,pid ]
           }
		
           ;## ps only returns first 8 chars of program name
           set pattern [ string range $prog 0 7 ]
           foreach line [ split $data "\n" ] {
              if { [ string equal $pattern [ lindex $line 1 ] ] } {
                 set pids [ lindex $line 0 ]
                 addLogEntry "pids $pids obtained from ps" purple
                 break
              } elseif { [ string match ${pattern}* [ lindex $line 0 ] ] } {
                 lappend pids [ lindex $line 1 ]
                 addLogEntry "appending pids $pids obtained from ps" purple
              }
           }
           set pids [ lsort -dictionary $pids ]
        }
	
     } err ] } {
     	addLogEntry $err red
        catch { ::close $fid }
        set pids [ list ]
     }
     return $pids
}

§   §   §
handle cases where ps -Ao fname,pid cannot find prog because it was started by script e.g. valgrind
proc getPid { { prog "" } { host "" } } {
     set tmp  [ list ]
     set data [ list ]
     set pids [ list ]
     set api_rx "([ join $::API_LIST | ])"
     if { [ catch {
        if { [ string length $host ] } {
           ;## no problem
        } elseif { [ regexp -nocase $api_rx $prog -> api ] } {
           set host [ set ::${api}(host) ]
        } else {
           set host $::env(HOST)
        }
        if { [ info exists api ] } {
           set lockfile \
            [ glob -nocomplain [ file join $::env(RUNDIR) ${api}API .$api.*.lock ] ]
           if { [ file exists $lockfile ] } {
              set fid [ open $lockfile r ]
              set pids [ read $fid [ file size $lockfile ] ]
              addLogEntry "getting pids '$pids' from lockfile $lockfile" purple
              ::close $fid
           }
        }
        ;## should never get here
        ;## ps -Ao fname,pid cannot pick up $prog if it was invoked with some script
        ;## e.g. valgrind
        if  { ![ regexp {^\d+$} $pids ] } {
            set pids {}
        }
  	    if { [ llength $pids ] == 0 } {
           if { ! [ string equal localhost $host ] && \
                ! [ string equal $host $::env(HOST) ] } {
              set data [ execssh $host /bin/ps -fu ldas | grep $prog ]
           } else {
              set data [ exec /bin/ps -fu ldas | grep $prog ]
           }
		
           ;## skip grep or tcsh parent line
           set pattern "^ldas\\s+(\\d+).+\\s+(\\S*$prog)"
           foreach line [ split $data "\n" ] {
              set rc [ regsub -all {\s+} $line " " line ]
              if    { [ regexp "grep $prog|tcsh.+$prog" $line ] } {
                    continue
              }
              if { [ regexp $pattern $line -> pids progname ] } {
                 addLogEntry "pids $pids obtained from ps for $progname" purple
                 break
              }
           }
        }
	
     } err ] } {
     	addLogEntry $err red
        catch { ::close $fid }
        set pids [ list ]
     }
     return $pids
}

§   §   §

Name: killChild

Description:
Kill child processes by names or pids.

Parameters: Usage:
      killChild $pidlist
Comments:
Depends on the existence of the "kill" system call.
The manager can kill remote API's through ssh!
proc killChild { pids_or_names { host "" } } {
     set errs [ list ]
     set api_rx "([ join $::API_LIST | ])"
     if { [ uplevel info exists jobid ] } {
        set jobid [ uplevel set jobid ]
     }
     set caller [ uplevel myName ]
     if { [ string equal manager $::API ] } {
        set manager 1
     } else {
        set manager 0
     }
     if { [ string equal cntlmon $::API ] } {
        set cntlmon 1
     } else {
        set cntlmon 0
     }
     set localhost $::env(HOST)
     if { ! [ string length $host ] } {
        set host $localhost
     }
     if { [ string equal $localhost $host ] } {
        set localhost 1
     } else {
        set localhost 0
     }
     foreach item $pids_or_names {
        if { [ catch {
           if { ! [ regexp {^[0-9]+$} $item pid ] } {
              set pid 0
           }
           if { [ regexp $api_rx $item -> api ]} {
              set host [ set ::${api}(host) ]
           } else {
              set api [ list ]
           }
           if { $localhost } {
              if { [ string length $api ] } {
                 if { $manager } {
                    exec /usr/bin/pkill -9 ${api}API
                 } else {
                    set err "Only the manager API can kill other API's, "
                    append err "sorry!"
                    return -code error $err
                 }
              } else {
                 if { $pid } {
                    kill -9 $pid
                 } else {
                    exec /usr/bin/pkill -9 $item
                 }
              }
           } else {
              if { [ string length $api ] } {
                 if { $manager } {
                    execssh $host /usr/bin/pkill -9 ${api}API
                 } else {
                    set err "Only the manager API can kill other API's, "
                    append err "sorry!"
                    return -code error $err
                 }
              } else {
                 if { $manager || $cntlmon } {
                    if { $pid } {
                       execssh $host kill -9 $pid
                    } else {
                       execssh $host /usr/bin/pkill -9 $item
                    }
                 } else {
                    set err "Only the manager and cntmon API's "
                    append err "can kill processes on remote hosts, "
                    append err "sorry!"
                    return -code error $err
                 }
              }
           }
        } err ] } {
           if { ! [ regexp {exited\s+abnormally} $err ] } {
              lappend errs "error while killing '$item': $err"
           }
        }
     } ;## end of foreach item
     if { [ llength $errs ] } {
        addLogEntry "$errs (called by $caller)" red
     }
}

§   §   §

Name: ps::memOverflow

Description:
Handles 64-bit values that are being used for memory size by truncating if the value looks very large.

Parameters: Usage:

Comments:

proc ps::memOverflow { val } {
     if { [ catch {
        if { [ string length $val ] > 9 } {
           set val [ string range $val 0 end-3 ]
           set val [ expr int($val * (1000.0/1024)) ]
        } else {
           set val [ expr { $val / 1024 } ]
        }
     } err ] } {
        return -code error "[ myName ]: $err"
     }
     return $val
}

§   §   §

Name: ps::linuxTime

Description:
Calculates percent cpu based on relative jiffies given to the current process.

Parameters: Usage:

Comments:

proc ps::linuxTime { pid uptime etime utime } {
     if { [ catch {
        ;## everything to seconds
        set etime [ ps::jiffieOverflow $etime ]
        set utime [ ps::jiffieOverflow $utime ]
        set etime [ expr { int($uptime) - $etime } ]
        if { [ info exists ::ps::pcpu($pid) ] } {
           foreach [ list x0 y0 ] $::ps::pcpu($pid) { break }
           if { [ catch {
              set pcpu [ expr { double($utime - $y0) / ($etime - $x0) } ]
              set pcpu [ expr { $pcpu * 100 } ]
           } err ] } {
              set pcpu 0
           }
           set ::ps::pcpu($pid) [ list $etime $utime ]
        } else {
           set ::ps::pcpu($pid) [ list $etime $utime ]
           set pcpu 0
        }
     } err ] } {
        return -code error "[ myName ]: $err"
     }
     return [ list $etime $pcpu ]
}

§   §   §

Name: ps::jiffieOverflow

Description:
Handles 64-bit values that are being used for jiffies by truncating if the value is very large.

Parameters: Usage:

Comments:

proc ps::jiffieOverflow { val } {
     if { [ catch {
        if { [ string length $val ] > 9 } {
           set val [ string range $val 0 end-2 ]
        } else {
           set val [ expr { $val / 100 } ]
        }
     } err ] } {
        return -code error "[ myName ]: $err"
     }
     return $val
}

§   §   §

Name: ps::procfsValidate

Description:
Since there is always the possibility that the proc filesystem structure will change, it would be nice to be able to validate the state of the procfs that is being queried.

Parameters: Usage:

Comments:

Pending investigation of usable validation modes. I can validate the Solaris procfs.h by version, but the linux procfs.h does not use a version id.
proc ps::procfsValidate { args } {
     if { [ catch {
        set os [ lindex [ ps::os ] 0 ]
        set fname /usr/include/sys/procfs.h
        set sol_rx {pragma\s+ident.+procfs.h\s+([\d\.]+)}
        set mtime [ file mtime $fname ]
        set size [ file size $fname ]
        set fid [ open $fname r ]
        set data [ read $fid $size ]
        close $fid
        switch -exact -- $os {
           solaris {
                     regexp $sol_rx $data -> version
                   }
           linux   {
                     set version unknown
                   }
           default {
                     set version unknown
                   }
        }
        set retval [ list $size $mtime $version ]
     } err ] } {
        return -code error "[ myName ]: $err"
     }
     return $retval
}

§   §   §

Name: ps::os

Description:
Get the OS and the filename under /proc/<pid> that we are going to parse.

Parameters: Usage:

Comments:

proc ps::os { args } {
     set os $::tcl_platform(os)
     switch -exact $os {
          SunOS {
                 set os solaris
                 set filename psinfo
                }
          Linux {
                 set os linux
                 set filename stat
                }
        default {
                 return -code error "unsupported OS: $os"
                }
     }
     return [ list $os $filename ]
}

§   §   §

Name: ps::type

Description:
Discriminate between pid's, executable programs, and valid users.
Note that the determination of valid users via the local password file is NOT reliable when users are mounted via nfs, so we at least look for a directory named /home/$user. This is not a robust method of identifying valid users.
users.

Parameters: Usage:

Comments:

proc ps::type { program } {
     if { [ catch {
	   if { [ regexp {^\d+$} $program ] } {
           set type pid
        } elseif { [ string length [ auto_execok $program ] ] } {
           set type prog
        } elseif { [ file exists /home/$program ] } {
           set type user
        } else {
           set fid [ open /etc/passwd r ]
           set data [ read $fid [ file size /etc/passwd ] ]
           ::close $fid
           set data [ split $data "\n" ]
           set type unknown
           foreach user $data {
              set user [ lindex [ split $user : ] 0 ]
              if { [ string equal $program $user ] } {
                 set type user
                 break
              }
           }
        }
     } err ] } {
	   return -code error "[ myName ]: $err"
	}
	return $type
}

§   §   §

Name: ps::ps

Description:

Parameters: Usage:
  to see the pids of all rxvt processes:

ps::ps rxvt to see the process info for pid 123:
ps::ps 123 a user may be specified as well
Comments:
proc ps::ps { program { user all } } {
     if { [ catch {
        set pids   [ list ]
        set noperm [ list ]
        foreach [ list os filename ] [ ps::os ] { break }
        set type [ ps::type $program ]
        foreach file [ ps::procfiles $user ] {
           foreach [ list owner file ] $file { break }
           set pid [ lindex [ split $file / ] end-1 ]
           if { [ file readable $file ] } {
              set data [ getPSInfo $pid ]
              if { [ string equal all $program ] && \
                   [ string equal all $user ] } {
                 lappend pids $data
                 continue
              }
              ;## return all data for the pid
              if { [ string equal $program $pid ] } {
                 set pids $data
                 break
              ;## collect all pids for the given program name
              } elseif { ! [ string equal self $pid ] } {
                 set fname [ lindex $data end-1 ]
                 if { [ string length $program ] < 9 } {
                    if { [ string equal $fname $program ] } {
                       lappend pids [ lindex $data 0 ]
                    }
                 } elseif { [ string length $fname ] && \
                            [ string match $fname* $program ] } {
                    lappend pids [ lindex $data 0 ]
                 }
              }
           ;## what if file is owned by 'program' and is not
           ;## readable?
           } elseif { [ string equal user $type ] } {
              lappend noperm $file
           }
        }
     } err ] } {
        return -code error "[ myName ]: $err"
     }
     return $pids
}

§   §   §

Name: ps::userPids

Description:
Return all process i.d.'s owned by a user.

Parameters: Usage:

Comments:

proc ps::userPids { user } {
     if { [ catch {
        set pids [ list ]
        set files [ ps::procfiles $user ]
        foreach file $files {
           foreach [ list owner file ] $file { break }
           set pid [ lindex [ split $file / ] end-1 ]
           lappend pids $pid
        }
     } err ] } {
        return -code error "[ myName ]: $err"
     }
     return $pids
}

§   §   §

Name: ps::procfiles

Description:
Return the full list of proc files on the box, or a list of all proc files owned by a user.

Parameters: Usage:

Comments:

Typically takes 250 usec * number of processes running.
proc ps::procfiles { { user all } } {
     if { [ catch {
        set temp [ list ]
        foreach [ list os filename ] [ ps::os ] { break }
	 set files [ lsort -dictionary [ glob /proc/{0,1,2,3,4,5,6,7,8,9}*/$filename ] ]
        if { [ string length $user ] } {
           foreach file $files {
              if { [ file exists $file ] } {
	            set owner [ file attributes $file -owner ]
                 if { [ string equal all $user ] || \
                      [ string equal $user $owner ] } {
                    lappend temp [ list $owner $file ]
                 }
              }
              set files $temp
           }
        }		
     } err ] } {
        return -code error "[ myName ]: $err"
     }
     return $files
}

§   §   §

Name: ps::procids

Description:
Helper function that strips the numerical process i.d.'s from the data returned by ps::procfiles, returning only the pids.

Parameters: Usage:

Comments:

proc ps::procids { user } {
     if { [ catch {
        set pids [ list ]
        set files [ ps::procfiles $user ]
        foreach file $files {
           foreach [ list owner file ] $file { break }
           lappend pids [ lindex [ split $file / ] end-1 ]
        }
     } err ] } {
        return -code error "[ myName ]: $err"
     }
     return $pids
}

§   §   §

Name: ps::zombies

Description:
Returns the list of all zombies of the named program, or all zombies when the program is 'all'.

Parameters: Usage:

Comments:

proc ps::zombies { { program all } } {
     if { [ catch {
        set data    [ list ]
        set zombies [ list ]
        if { [ string equal all $program ] } {
           set type user
        } else {
           set type [ ps::type $program ]
        }
        foreach [ list os filename ] [ ps::os ] { break }
        switch -exact -- $type {
           pid {
                 set pids $program
               }
          prog {
                 set pids [ ps::ps $program ]
               }
          user {
                 set pids [ ps::procids $program ]
               }
       unknown {
                 return "argument of unknown type: '$program'"
               }
        }
        foreach pid $pids {
	      set file /proc/${pid}/$filename
           if { [ file readable $file ] } {
		    set data [ getPSInfo $pid ]
              set state [ lindex $data 1 ]
              if { [ string equal Z $state ] } {
                 set owner [ lindex $data 2 ]
                 lappend zombies [ list $owner $data ]
              }
           }
        }
     } err ] } {
        return -code error "[ myName ]: $err"
     }
     return $zombies
}

§   §   §

Name: ps::kill

Description:

Parameters: Usage:

Comments:

proc ps::kill { program } {
     if { [ catch {
	
	   foreach pid [ ps::ps $program ] {
           puts stderr "calling kill -9 $pid ($program)"
           catch { kill -9 $pid }
        }
	   after 1000
	   if { [ catch {
           set undead [ ps::zombies $program ]
           if { [ llength $undead ] } {
              put stderr "zombie ${program}'s: '$undead'"
           }
        } err ] } {
           puts stderr "no zombie ${program}'s found."
           set undead [ ps::zombies all ]
           if { [ llength $undead ] } {
              puts stderr "other zombies: '$undead'"
           }
        }
        puts stderr DONE!
     } err ] } {
        return -code error "[ myName ]: $err"
     }
}

§   §   §

Name: ps::self

Description:
Returns lots of useful and ACCURATE information about the current process.

Parameters: Usage:

Comments:

typical runtime is 2 ms.
proc ps::self { } {
     if { [ catch {
        set self [ pid ]
        set me $::argv0
        set data [ getPSInfo $self ]
        foreach \
         [ list pid state usr vsz rsz pcpu pmem etime fname args ] \
         $data { break }
        set etime [ ps::duration $etime ]
        set retval [ list $me $self $pcpu $pmem $vsz $rsz $etime ]
     } err ] } {
        return -code error "[ myName ]: $err"
     }
     return $retval
}

§   §   §

Name: ps::children

Description:
Return ps info for all children of current process.

Parameters: Usage:

Comments:

Need ppid for this to work.
proc ps::children { args } {
     if { [ llength $args ] == 1 } {
        set args [ lindex $args 0 ]
     }
     if { [ catch {
        return -code error UNIMPLEMENTED!
     } err ] } {
        return -code error "[ myName ]: $err"
     }
}

§   §   §

Name: ps::all

Description:
Return a full ps list dump of all procs on the host.
This might be used to drop reports usable by the cntlmonAPI, for example.

Parameters: Usage:

Comments:

Roughly 2 ms per process.
proc ps::all { args } {
     if { [ catch {
        set os [ lindex [ ps::os ] 0 ]
	   set files [ ps::procfiles {} ]
	   foreach file $files {
	      if { [ file readable $file ] } {
		    lappend data \
		       [ getPSInfo [ lindex [ split $file / ] end-1 ] ]
	      }
	   }
	} err ] } {
        return -code error "[ myName ]: $err"
     }
	return $data
}

§   §   §

Name: ps::trace

Description:
Try to get an strace/truss snapshot of a process based on some conditional state.

Parameters: Usage:

Comments:

proc ps::trace { pid { sec 10 } } {
     if { [ catch {
	
	   set time [ expr { $sec * 1000 } ]
	   switch -exact $::tcl_platform(os) {
             SunOS {
                   set traceprog "/usr/bin/truss -f -p -rall -wall -i"
                   }
             Linux {
                   set traceprog "/usr/bin/strace -f -p"
                   }
           default {
                   return {}
                   }
           } ;## end of switch
		 set file ${::API}APItrace.[ clock seconds]
           set tracepid [ eval ::exec /usr/bin/env LD_PRELOAD= \
                          $traceprog $pid >& $file & ]
		 set traceprog [ lindex [ lindex $traceprog 0 ] end ]
		 after $time [ list ps::kill $traceprog ]
     } err ] } {
        return -code error "[ myName ]: $err"
     }
}

§   §   §

Name: ps::duration

Description:
Apply to etime value to get 'human' uptime reports that look like "2 days 17 hrs 36 mins 25 secs", for example.

Parameters: Usage:

Comments:

proc ps::duration { secs } {
     set timeatoms [ list ]
     if { [ catch {
        foreach div { 86400 3600 60 1 } \
                mod { 0 24 60 60 } \
               name { day hr min sec } {
           set n [ expr {$secs / $div} ]
           if { $mod > 0 } { set n [ expr {$n % $mod} ] }
           if { $n > 1 } {
              lappend timeatoms "$n ${name}s"
           } elseif { $n == 1 } {
             lappend timeatoms "$n $name"
           }
        }
        set timeatoms [ join $timeatoms ]
        if { ! [ string length $timeatoms ] } {
           set timeatoms [ list 0 sec ]
        }
     } err ] } {
        return -code error "duration: $err"
     }
     return $timeatoms
}

§   §   §

Name: ps::server

Description:
Canned server definition so this library can be used as a server on a remote host.

Parameters: Usage:

Comments:

proc ps::server { port } {
     if { [ catch {
	   ps::standalone
        set cid [ socket -server ps::servercfg $port ]
     } err ] } {
        puts stderr "[ myName ]: $err"
     }
}

§   §   §

Name: ps::servercfg

Description:
Boilerplate version of a server configuration procedure.

Parameters: Usage:

Comments:

proc ps::servercfg { cid addr port } {
     if { [ catch {
        fileevent  $cid readable "ps::serverhandler $cid"
     } err ] } {
        return -code error "[ myName ]: $err"
     }
}

§   §   §

Name: ps::serverhandler

Description:
Extremely simplistic handler for running these functions as a server API.

Parameters: Usage:

Comments:

proc ps::serverhandler { cid } {
     if { [ catch {
        ::gets $cid cmd
	   if { ! [ regexp {^ps::[a-z]+(\s+\S+)?$} $cmd ] } {
	      set err "invalid command received: '$cmd'"
		 return -code error $err
	   }
	
        catch { eval $cmd } reply
        ::puts $cid $reply
        ::close $cid
     } err ] } {
        catch { ::close $cid }
        return -code error "[ myName ]: $err"
     }
}

§   §   §

Name: ps::standalone

Description:
Provides missing helper functions useful for debugging this module in a standalone context.
Note that it is called by ps::server.

Parameters: Usage:

Comments:

proc ps::standalone { args } {
     if { [ catch {
        proc myName { args } {
             return [ lindex [ info level -1 ] 0 ]
        }
        proc addLogEntry { args } {
             puts "[ uplevel myName ]: $args"
        }
	   proc bgerror { args } {
             puts stderr $::errorInfo
        }
     } err ] } {
        return -code error "[ myName ]: $err"
     }
}

§   §   §

up arrow Back to Top up arrow