суббота, 18 марта 2017 г.

Script to run jobs suite remotely via screen

Often in needed to run some jobs remotely, for example, tests on remote test environment. I used different custom "daemon"-like scripts for this, monitoring the jobs/tests, but another and simple solution for Linux is to use screen tool. It supports sessions, detaching, so is good for such kind of tasks. Here is the script which runs multiple jobs on remote host (setted via $RMHOST env. var). Its syntax is:

  -cls
  -run JOB-NUMBER 'JOB-COMMAND'
  -runall < FILE-WITH-COMMANDS
  -submit < FILE-WITH-COMMANDS
Used env. vars: \$USER (login), \$RMHOST (hostname)

so to run it you must:

  • set $RMHOST env. var
  • create file with commands which will be ran remotely: each command is on own line and can have any $ variables for substitution: they will be substituted locally
  • then call script as V1=a V2=b script -submit < created_file. This will substitute V1 and V2 in created_file and send resulting file over scp to remote host $RMHOST (you must have configured public-key access to host), then will send itself and call itself remotely with command -runall.

After it you can go to host, call screen -r to see your created session, to switch between windows (each job is ran on separate window). You will have local $LOG file with decscription of running commands and remote log file per job with info about what and when was run (job-<DATE>-<JOB_NUMBER>.log). Yes, each job has number/index related to line number of job suite file. If you want you can include generating of screen log file too (with -L option IMO).

While you are on remote host, you can run this script (which was scp-ed) with -cls command: it will remove all log files there.

That's it.

#!/bin/sh
SCR=`basename "$0"`
SESSION=${USER}_screen
LOG=rmjobs.log

_wrapjob() {
    _wrapjob_f=job-`date +%s`-$1.log
    echo "Started $1 at `date`: $2" > $_wrapjob_f
    echo '===============================================================================' >> $_wrapjob_f
    echo >> $_wrapjob_f
    $2|tee -a $_wrapjob_f
}

_runjob() {
    [ $1 = "0" ] && {
        # add -L to both if screelog.* logs are needed
        screen -dmS $SESSION -t "job$1" sh -c "~/$SCR -run $1 \"$2\";sh"
    } || {
        screen -S $SESSION -p 0 -X screen -t "job$1" sh -c "~/$SCR -run $1 \"$2\";sh"
    }
}

case "$1" in
    -cls) rm -f screenlog.*; rm -f job*.log;;
    -run) _wrapjob "$2" "$3";;
    -runall)
        i=0
        rm -f $LOG 2>/dev/null
        while read -r c; do
            c=`echo $c|tr -d '[:cntrl:]'`
            echo $c|egrep '^-' >/dev/null && continue
            _runjob $i "$c"
            echo "Ran $c;" >> $LOG
            i=`expr $i + 1`
            sleep 2
        done;;
    -submit)
        rm -f .jobs-suite.tmp 2>/dev/null
        while read -r c; do
            echo $c|egrep '^-' >/dev/null && continue
            eval "echo $c" >> .jobs-suite.tmp
        done
        scp "$0" "$USER@$RMHOST:$SCR"
        scp ".jobs-suite.tmp" $USER@$RMHOST:".jobs-suite.tmp"
        ssh -n -l $USER $RMHOST "sh $SCR -runall < .jobs-suite.tmp";;
    *)
        echo "Syntax:"
        echo "  -cls"
        echo "  -run JOB-NUMBER 'JOB-COMMAND'"
        echo "  -runall < FILE-WITH-COMMANDS"
        echo "  -submit < FILE-WITH-COMMANDS"
        echo "Used env. vars: \$USER (login), \$RMHOST (hostname)"
        exit 1;;
esac
:

четверг, 2 марта 2017 г.

Redirect file descriptors in Python for current and children processes

This is a Python trick with supressing of console output. Actually implementation does not supresses anything, but maps (dups) "source" file descriptors to "destination" file descriptors, so you can continue to use already existing descriptor, but it will point out to another one. "Another one" are got from list, but if dstfd list in lesser than srcfd list, then missing will be filled with "devnull", which allows us to suppress console output - this is the one way to use this class.

It is OS independent and was tested in Linux, Windows. Such "magia" happens only in context, after exit from context old values of descriptors will be restored. Note, that this works for children process too, sure. So, if you start some child process, you can "mask" its descriptors (redirect them somewhere).

import os, sys
from itertools import zip_longest

class FdRedir:
    def __init__(self, srcfd, dstfd):
        self.src = srcfd
        self.dst = dstfd
        self.null = os.open(os.devnull, os.O_RDWR)
        self.dups = [os.dup(f) for f in self.src]
    def __enter__(self):
        homo = zip_longest(self.src, self.dst, fillvalue=self.null)
        for t, f in homo:
            os.dup2(f, t)
        return self
    def __exit__(self, xtyp, xval, xtb):
        iso = zip(self.dups, self.src)
        for f, t in iso:
            os.dup2(f, t)
        for f in self.dups:
            os.close(f)
        os.close(self.null)

# Example of usage

with FdRedir((1, 2), ()) as fr:
    print('hidden')
    print('hidden', file=sys.stderr)
    with FdRedir([1], [fr.dups[0]]):
        print('visible tmp')
        print('hidden', file=sys.stderr)
    print('hidden')
print('visible end')

will print

visible tmp
visible end

which is expected: we suppress output to stdout, stderr descriptors in first context, then temporary allow stdout (by map it to fr.dups[0]).

суббота, 25 февраля 2017 г.

Format of table data in Emacs ORG mode

I'm continue to work with Spacemacs/Org/Babel, so... :)

The idea of the task is to use ORG mode some kind of Literate Programming (LP). It's a good known technique in Emacs/ORG but here I want to share little bit of ELisp helping to format table data - kind of LP. Imagine, you have documentation about Finite Automata or some table function, or some data set and you want to create some static data code (in C, Tcl, Python, whatever). So, to do it, you need function to format table data, which consists, eventually, of:

  • selecting data (because you may want to skip some columns/rows)
  • formatting it in custom format
  • and sure save it somewhere...

Suppose, we have table in ORG mode:

#+TBLNAME: t1
| arg0 | arg1 | func |
|------+------+------|
| a00  | a01  | f0   |
| a10  | a11  | f1   |
| a20  | a21  | f2   |
| a30  | a31  | f3   |

And we want to format data from the table to free text. Let's consider output like this:

table = 'a10', 'f1',
        'a20', 'f2',
        'a30', 'f3';

To achieve this we create Babel function table-format which has arguments:

  • tbl - table data (put table name)
  • skip - pair of lists: skipped-rows, skipped-columns, eg. '((1 2) (1))
  • rfmt - format funtion w/ args (row-index max-row-index row)
  • cfmt - format function w/ args: (col-index max-col-index col)

while all have default values (and default formatters, looks like original ORG format for table data). Indexes are indexes of visible columns/rows!

So, our function looks like:

#+NAME: table-format
#+HEADERS: :var tbl='(), skip='(), rfmt='(), cfmt='()
#+BEGIN_SRC elisp :session my
; mr - max row index; mc - max column index;
; skip - ((rows-to-skip) (columns-to-skip))
; vmr - visual max row index, ri - row index, vri - visual row index
  (let* ((rs)
         (mr (- (length tbl) 1))
         (def-rfmt #'(lambda (ri mr row) (format "%s\n" row)))
         (def-cfmt #'(lambda (ci mc col) (format (if (< ci mc) "%s|" "%s") col)))
         (rfmt (if (null rfmt) def-rfmt rfmt))
         (cfmt (if (null cfmt) def-cfmt cfmt))
         (skip (if (null skip) '(() ()) skip))
         (rskp (car skip)) (cskp (cadr skip))
         (vmr (- mr (length rskp)))
         (vri 0))
   (dotimes (ri (length tbl) rs)
    (if (not (member ri rskp))
     (let* ((cs)
            (vci 0)
            (row (nth ri tbl))
            (mc (- (length row) 1))
            (vmc (- mc (length cskp))))
      (dotimes (ci (length row) cs)
       (if (not (member ci cskp))
        (let ((col (nth ci row)))
         (setq cs (concat cs (funcall cfmt vci vmc col)))
         (setq vci (+ 1 vci)))))
      (setq rs (concat rs (funcall rfmt vri vmr cs)))
      (setq vri (+ 1 vri))))))
#+END_SRC

Here is example of usage. Note, that we don't use default formatters of raw/column but custom: mycfmt and myrfmt.

#+NAME: mycfmt
#+BEGIN_SRC elisp :session my
(defun mycfmt (ci mc col)
  (format (if (< ci mc) "'%s', " "'%s'") col)
)
#+END_SRC


#+NAME: myrfmt
#+BEGIN_SRC elisp :session my
(defun myrfmt (ri mr row)
 (format 
  (if (< ri mr)
   (if (> ri 0) "        %s,\n" "%s,\n")
   "        %s;\n") row))
#+END_SRC


#+BEGIN_SRC elisp :var d=table-format(t1, skip='((0) (1)), rfmt='myrfmt, cfmt='mycfmt) :results raw
(prin1 (format "table = %s" d))
#+END_SRC

воскресенье, 29 января 2017 г.

Profiling of Haskell program on Windows

To profile your Haskell application on Windows w/ stack currently (due existing bug?) you must:

C:\prj> stack build --profile
C:\prj> stack exec -- prj-exe --RTS +RTS -p -RTS [other options of app]

This will produce .prof file in current directory. You can add -h and -hy to -p to get info about heap/allocations. To produce .ps file use:

C:\prj> hp2ps -c your-hp-file-name

Then open generated .ps file in SumatraPDF and analize that.. -)