[configf:settings trim-trailing-spaces yes]
+Index: Makefile ================================================================== --- Makefile +++ Makefile @@ -34,16 +34,13 @@ # BASH_MACHTYPE=$(shell bash -c "echo \$$MACHTYPE") # ARCHSTR=$(BASH_MACHTYPE)_$(shell lsb_release -sr) ARCHSTR=$(shell lsb_release -sr) # ARCHSTR=$(shell bash -c "echo \$$MACHTYPE") -all : $(PREFIX)/bin/.$(ARCHSTR) mtest dboard txtdb +all : $(PREFIX)/bin/.$(ARCHSTR) mtest dboard -refdb : txtdb/txtdb.scm - csc -I txtdb txtdb/txtdb.scm -o refdb - -mtest: $(OFILES) megatest.o +mtest: $(OFILES) megatest.o readline-fix.scm csc $(CSCOPTS) $(OFILES) megatest.o -o mtest dboard : $(OFILES) $(GOFILES) dashboard.scm csc $(OFILES) dashboard.scm $(GOFILES) -o dboard @@ -87,11 +84,23 @@ $(PREFIX)/bin/newdashboard : $(PREFIX)/bin/.$(ARCHSTR)/ndboard utils/mk_wrapper $(PREFIX) ndboard $(PREFIX)/bin/newdashboard chmod a+x $(PREFIX)/bin/newdashboard -$(HELPERS) : utils/mt_* +# $(HELPERS) : utils/% +# $(INSTALL) $< $@ +# chmod a+x $@ + +$(PREFIX)/bin/mt_laststep : utils/mt_laststep + $(INSTALL) $< $@ + chmod a+x $@ + +$(PREFIX)/bin/mt_runstep : utils/mt_runstep + $(INSTALL) $< $@ + chmod a+x $@ + +$(PREFIX)/bin/mt_ezstep : utils/mt_ezstep $(INSTALL) $< $@ chmod a+x $@ $(PREFIX)/bin/mt_xterm : utils/mt_xterm $(INSTALL) $< $@ @@ -107,13 +116,13 @@ $(PREFIX)/bin/loadrunner : utils/loadrunner $(INSTALL) $< $@ chmod a+x $@ -$(PREFIX)/bin/refdb : refdb - $(INSTALL) $< $@ - chmod a+x $@ +# $(PREFIX)/bin/refdb : refdb +# $(INSTALL) $< $@ +# chmod a+x $@ deploytarg/nbfake : utils/nbfake $(INSTALL) $< $@ chmod a+x $@ @@ -128,11 +137,11 @@ chmod a+x $(PREFIX)/bin/dashboard $(INSTALL) dboard $(PREFIX)/bin/.$(ARCHSTR)/dboard install : $(PREFIX)/bin/.$(ARCHSTR) $(PREFIX)/bin/.$(ARCHSTR)/mtest $(PREFIX)/bin/megatest \ $(PREFIX)/bin/.$(ARCHSTR)/dboard $(PREFIX)/bin/dashboard $(HELPERS) $(PREFIX)/bin/nbfake \ - $(PREFIX)/bin/nbfind $(PREFIX)/bin/loadrunner $(PREFIX)/bin/refdb $(PREFIX)/bin/mt_xterm \ + $(PREFIX)/bin/nbfind $(PREFIX)/bin/loadrunner $(PREFIX)/bin/mt_xterm \ $(PREFIX)/bin/newdashboard $(PREFIX)/bin/.$(ARCHSTR) : mkdir -p $(PREFIX)/bin/.$(ARCHSTR) @@ -183,5 +192,12 @@ mkdir -p /tmp/$(USER)/datashare/disk1 /tmp/$(USER)/basepath xterm : sd (export BASEPATH=/tmp/$(USER)/basepath ; export PATH="$(PWD)/datashare-testing:$(PATH)" ; xterm &) +# "(define (toplevel-command . a) #f)" +readline-fix.scm : + if egrep 'version.*3.0' $(shell dirname $(shell dirname $(shell which csi)))/lib/chicken/7/readline.setup-info;then \ + echo "(use-legacy-bindings)" > readline-fix.scm; \ + else \ + echo "" > readline-fix.scm;\ + fi Index: api.scm ================================================================== --- api.scm +++ api.scm @@ -20,10 +20,11 @@ '(get-key-val-pairs get-keys test-toplevel-num-items get-test-info-by-id test-get-rundir-from-test-id + get-count-tests-running-for-testname get-count-tests-running get-count-tests-running-in-jobgroup get-previous-test-run-record get-matching-previous-test-run-records test-get-logfile-info @@ -44,10 +45,11 @@ get-all-run-ids get-prev-run-ids get-run-ids-matching-target get-runs-by-patt get-steps-data + get-steps-for-test login testmeta-get-record have-incompletes? synchash-get )) @@ -180,10 +182,11 @@ ;; TESTS ((test-toplevel-num-items) (apply db:test-toplevel-num-items dbstruct params)) ((get-test-info-by-id) (apply db:get-test-info-by-id dbstruct params)) ((test-get-rundir-from-test-id) (apply db:test-get-rundir-from-test-id dbstruct params)) + ((get-count-tests-running-for-testname) (apply db:get-count-tests-running-for-testname dbstruct params)) ((get-count-tests-running) (apply db:get-count-tests-running dbstruct params)) ((get-count-tests-running-in-jobgroup) (apply db:get-count-tests-running-in-jobgroup dbstruct params)) ;; ((delete-test-step-records) (apply db:delete-test-step-records dbstruct params)) ((get-previous-test-run-record) (apply db:get-previous-test-run-record dbstruct params)) ((get-matching-previous-test-run-records)(apply db:get-matching-previous-test-run-records dbstruct params)) @@ -210,10 +213,11 @@ ((get-runs-by-patt) (apply db:get-runs-by-patt dbstruct params)) ((get-run-name-from-id) (apply db:get-run-name-from-id dbstruct params)) ;; STEPS ((get-steps-data) (apply db:get-steps-data dbstruct params)) + ((get-steps-for-test) (apply db:get-steps-for-test dbstruct params)) ;; MISC ((have-incompletes?) (apply db:have-incompletes? dbstruct params)) ((login) (apply db:login dbstruct params)) ((general-call) (let ((stmtname (car params)) Index: archive.scm ================================================================== --- archive.scm +++ archive.scm @@ -133,11 +133,11 @@ (run-id (db:test-get-run_id test-dat)) (target (string-intersperse (map cadr (rmt:get-key-val-pairs run-id)) "/")) (toplevel/children (and (db:test-get-is-toplevel test-dat) (> (rmt:test-toplevel-num-items run-id test-name) 0))) - (test-partial-path (conc target "/" run-name "/" (runs:make-full-test-name test-name item-path))) + (test-partial-path (conc target "/" run-name "/" (db:test-make-full-name test-name item-path))) ;; note the trailing slash to get the dir inspite of it being a link (test-path (conc linktree "/" test-partial-path)) (test-physical-path (if (file-exists? test-path) (read-symbolic-link test-path #t) #f)) (partial-path-index (if test-physical-path (substring-index test-partial-path test-physical-path) #f)) (test-base (if (and partial-path-index @@ -217,11 +217,11 @@ (keyvals (rmt:get-key-val-pairs run-id)) (target (string-intersperse (map cadr keyvals) "/")) (toplevel/children (and (db:test-get-is-toplevel test-dat) (> (rmt:test-toplevel-num-items run-id test-name) 0))) - (test-partial-path (conc target "/" run-name "/" (runs:make-full-test-name test-name item-path))) + (test-partial-path (conc target "/" run-name "/" (db:test-make-full-name test-name item-path))) ;; note the trailing slash to get the dir inspite of it being a link (test-path (conc linktree "/" test-partial-path)) ;; if the old path was not deleted then prev-test-physical-path will end up pointing to a real directory (prev-test-physical-path (if (file-exists? test-path) (read-symbolic-link test-path #t) #f)) ADDED batchsim/Makefile Index: batchsim/Makefile ================================================================== --- /dev/null +++ batchsim/Makefile @@ -0,0 +1,8 @@ +RUN=default.scm + +all : batchsim + ./batchsim $(RUN) + +batchsim : batchsim.scm + csc batchsim.scm + ADDED batchsim/batchsim.scm Index: batchsim/batchsim.scm ================================================================== --- /dev/null +++ batchsim/batchsim.scm @@ -0,0 +1,417 @@ +(use ezxdisp srfi-18) + +(define *ezx* (ezx-init 650 650 "Batch simulator")) +(require-library ezxgui) +(define *green* (make-ezx-color 0 1 0)) +(define *black* (make-ezx-color 0 0 0)) +(define *grey* (make-ezx-color 0.1 0.1 0.1)) +(define *blue* (make-ezx-color 0 0 1)) +(define *cyan* (make-ezx-color 0 1 1)) +(define *green* (make-ezx-color 0 1 0)) +(define *purple* (make-ezx-color 1 0 1)) +(define *red* (make-ezx-color 1 0 0)) +(define *white* (make-ezx-color 1 1 1)) +(define *yellow* (make-ezx-color 1 1 0)) + +(define *user-colors-palette* + (list + *green* + *blue* + *cyan* + *purple* + *red* + *yellow* + *black*)) + +(define *dark-green* (get-color "dark-green")) +(define *brown* (get-color "brown")) + +(ezx-select-layer *ezx* 1) +(ezx-wipe-layer *ezx* 1) + +;; (ezx-str-2d *ezx* 30 30 "Hello" *white*) +;; (ezx-fillrect-2d *ezx* 100 100 120 120 *brown*) +(ezx-redraw *ezx*) + +(define *last-draw* (current-milliseconds)) +(define *draw-delta* 40) ;; milliseconds between drawing + +(define (wait-for-next-draw-time) + (let* ((cm (current-milliseconds)) + (delta (- *draw-delta* (- cm *last-draw*)))) + (if (> delta 0) + (thread-sleep! (/ delta 1000))) + (set! *last-draw* (current-milliseconds)))) + +(include "events.scm") + +;; System spec (to be moved into loaded file) +;; +;; x y w gap x-min x-max +(define *cpu-grid* (vector 500 50 15 2 500 600)) +(define (make-cpu:grid)(make-vector 6)) +(define *queues* (make-hash-table)) ;; name -> (list (list user duration num-cpus num-gigs) ... ) +(define *cpus* (make-hash-table)) ;; cpu-name => (vector user job-len num-cpu mem x-loc y-loc) +(define *obj-locations* (make-hash-table)) ;; name -> (x y layer) +(define *queue-spec* + (vector + 80 ;; start-x + 300 ;; start-y + 300 ;; delta-y how far to next queue + 15 ;; height + 400 ;; length + )) +(define *use-log* #f) +(define *job-log-scale* 10) + +;;====================================================================== +;; CPU +;;====================================================================== + +(define-record cpu name num-cores mem job x y) + +;;====================================================================== +;; CPU Pool +;;====================================================================== + +(define-record pool name x y w h gap boxw cpus delta nrows ncols cpunum) + +(define (new-pool name x y nrows ncols gap boxw) + (let* ((delta (+ gap boxw)) + ;; (nrows (quotient h (+ gap delta))) + ;; (ncols (quotient w (+ gap delta))) + (w (+ gap (* nrows delta))) + (h (+ gap (* ncols delta))) + (cpus (make-vector (* nrows ncols) #f)) + (npool (make-pool name x y w h gap boxw cpus delta nrows ncols 0))) + npool)) + +(define (pool:add-cpu pool name num-cores mem) + (let* ((cpu (make-cpu name num-cores mem #f #f #f))) + (vector-set! (pool-cpus pool)(pool-cpunum pool) cpu) + (pool-cpunum-set! pool (+ 1 (pool-cpunum pool))) + cpu)) + +(define (pool:draw ezx pool) + (let ((nrows (pool-nrows pool)) + (ncols (pool-ncols pool)) + (x (pool-x pool)) + (y (pool-y pool)) + (w (pool-w pool)) + (h (pool-h pool)) + (gap (pool-gap pool)) + (boxw (pool-boxw pool)) + (delta (pool-delta pool)) + (cpus (pool-cpus pool))) + (ezx-select-layer ezx 1) + ;(ezx-wipe-layer ezx 1) + ;; draw time at upper right + (ezx-str-2d ezx x y (pool-name pool) *black*) + (ezx-rect-2d ezx x y (+ x w)(+ y h) *black* 1) + (let loop ((row 0) + (col 0) + (cpunum 0)) + (let* ((cpu (vector-ref cpus cpunum)) + (xval (+ x gap (* row delta))) + (yval (+ y gap (* col delta)))) + (if cpu + (begin + (cpu-x-set! cpu xval) + (cpu-y-set! cpu yval)) + (vector-set! cpus cpunum (make-cpu (conc cpunum) 1 1 #f xval yval))) + ;; (print "box at " xval ", " yval) + (ezx-rect-2d ezx xval yval (+ xval boxw) (+ yval boxw) *grey* 1) + (if (< col (- ncols 1)) + (loop row (+ col 1)(+ cpunum 1)) + (if (< row (- nrows 1)) + (loop (+ row 1) 0 (+ cpunum 1)))))) + (ezx-redraw ezx))) + + +;;====================================================================== +;; Users +;;====================================================================== + +(define *user-colors* (make-hash-table)) + +(define (get-user-color user) + (let ((color (hash-table-ref/default *user-colors* user #f))) + (if color + color + (let* ((color-num (+ (length (hash-table-keys *user-colors*)) 1)) + (color (list-ref *user-colors-palette* color-num))) + (hash-table-set! *user-colors* user color) + color)))) + +;;====================================================================== +;; Job Queues +;;====================================================================== + +;; jobs + +(define (make-queue:job)(make-vector 4)) +(define-inline (queue:job-get-user vec) (vector-ref vec 0)) +(define-inline (queue:job-get-duration vec) (vector-ref vec 1)) +(define-inline (queue:job-get-num-cpu vec) (vector-ref vec 2)) +(define-inline (queue:job-get-num-gigs vec) (vector-ref vec 3)) +(define-inline (queue:job-set-user! vec val)(vector-set! vec 0 val)) +(define-inline (queue:job-set-duration! vec val)(vector-set! vec 1 val)) +(define-inline (queue:job-set-num-cpu! vec val)(vector-set! vec 2 val)) +(define-inline (queue:job-set-num-gigs! vec val)(vector-set! vec 3 val)) + +;; add a job to the queue +;; +(define (add-job queue-name user duration num-cpu num-gigs) + (let* ((queue-dat (hash-table-ref/default *queues* queue-name '())) + (new-queue (append + queue-dat + (list (vector user duration num-cpu num-gigs))))) + (hash-table-set! *queues* queue-name new-queue) + (draw-queue-jobs queue-name))) + +;; peek for jobs to do in queue +;; +(define (peek-job queue-name) + (let ((queue (hash-table-ref/default *queues* queue-name '()))) + (if (null? queue) + #f + (car queue)))) + +;; take job from queue +;; +(define (take-job queue-name) + (let ((queue (hash-table-ref/default *queues* queue-name '()))) + (if (null? queue) + #f + (begin + (hash-table-set! *queues* queue-name (cdr queue)) + (draw-queue-jobs queue-name) + (car queue))))) + +;;====================================================================== +;; CPUs +;;====================================================================== + +(define (make-cpu:dat)(make-vector 6 #f)) +(define-inline (cpu:dat-get-user vec) (vector-ref vec 0)) +(define-inline (cpu:dat-get-job-len vec) (vector-ref vec 1)) +(define-inline (cpu:dat-get-num-cpu vec) (vector-ref vec 2)) +(define-inline (cpu:dat-get-mem vec) (vector-ref vec 3)) +(define-inline (cpu:dat-get-x vec) (vector-ref vec 4)) +(define-inline (cpu:dat-get-y vec) (vector-ref vec 5)) +(define-inline (cpu:dat-set-user! vec val)(vector-set! vec 0 val)) +(define-inline (cpu:dat-set-job-len! vec val)(vector-set! vec 1 val)) +(define-inline (cpu:dat-set-num-cpu! vec val)(vector-set! vec 2 val)) +(define-inline (cpu:dat-set-mem! vec val)(vector-set! vec 3 val)) +(define-inline (cpu:dat-set-x! vec val)(vector-set! vec 4 val)) +(define-inline (cpu:dat-set-y! vec val)(vector-set! vec 5 val)) + +(define-inline (cpu:grid-get-x vec) (vector-ref vec 0)) +(define-inline (cpu:grid-get-y vec) (vector-ref vec 1)) +(define-inline (cpu:grid-get-w vec) (vector-ref vec 2)) +(define-inline (cpu:grid-get-gap vec) (vector-ref vec 3)) +(define-inline (cpu:grid-get-x-min vec) (vector-ref vec 4)) +(define-inline (cpu:grid-get-x-max vec) (vector-ref vec 5)) +(define-inline (cpu:grid-set-x! vec val)(vector-set! vec 0 val)) +(define-inline (cpu:grid-set-y! vec val)(vector-set! vec 1 val)) +(define-inline (cpu:grid-set-w! vec val)(vector-set! vec 2 val)) +(define-inline (cpu:grid-set-gap! vec val)(vector-set! vec 3 val)) +(define-inline (cpu:grid-set-x-min! vec val)(vector-set! vec 4 val)) +(define-inline (cpu:grid-set-x-max! vec val)(vector-set! vec 5 val)) + +(define (add-cpu name num-cores mem) + (let ((x (cpu:grid-get-x *cpu-grid*)) + (y (cpu:grid-get-y *cpu-grid*)) + (delta (+ (cpu:grid-get-w *cpu-grid*)(cpu:grid-get-gap *cpu-grid*))) + (x-max (cpu:grid-get-x-max *cpu-grid*))) + (hash-table-set! *cpus* name (vector #f #f num-cores mem x y)) + (if (> x x-max) + (begin + (cpu:grid-set-x! *cpu-grid* (cpu:grid-get-x-min *cpu-grid*)) + (cpu:grid-set-y! *cpu-grid* (+ y delta))) + (cpu:grid-set-x! *cpu-grid* (+ x delta))))) + +;; draw grey box for each cpu on layer 2 +;; jobs are drawn on layer 1 +;; +(define (draw-cpus) ;; call once after init'ing all cpus + (ezx-select-layer *ezx* 1) + (ezx-wipe-layer *ezx* 1) + ;; draw time at upper right + (ezx-str-2d *ezx* 20 20 (seconds->h:m:s *now*) *black*) + (for-each + (lambda (cpu) + (let ((x (cpu:dat-get-x cpu)) + (y (cpu:dat-get-y cpu)) + (w (cpu:grid-get-w *cpu-grid*))) + (ezx-rect-2d *ezx* x y (+ x w) (+ y w) *grey* 1))) + (hash-table-values *cpus*)) + (ezx-redraw *ezx*)) + +(define (draw-jobs) + ;; (draw-cpus) + (ezx-select-layer *ezx* 2) + (ezx-wipe-layer *ezx* 2) + (for-each + (lambda (cpu) + (let* ((x (cpu:dat-get-x cpu)) + (y (cpu:dat-get-y cpu)) + (w (cpu:grid-get-w *cpu-grid*)) + (u (cpu:dat-get-user cpu))) + (if u ;; job running if not #f + (let ((color (get-user-color u))) + (ezx-fillrect-2d *ezx* (+ x 2)(+ 2 y)(+ x 9) (+ y 9) color))))) + (hash-table-values *cpus*)) + (ezx-redraw *ezx*)) + +(define (end-job cpu-name user) + (let ((cpu (hash-table-ref/default *cpus* cpu-name #f))) + (if cpu + (let ((curr-user (cpu:dat-get-user cpu))) ;; if it is a user name then job is not done - error + (if (or (not curr-user) + (not (equal? curr-user user))) + (print "ERROR: cpu " cpu-name " not running job for " user "!") + (begin + (cpu:dat-set-user! cpu #f) + (cpu:dat-set-job-len! cpu #f) + (draw-jobs)))) ;; hash-table-set! *cpus* cpu-name (make-cpu:dat)))) + (print "ERROR: no cpu " cpu-name " found. Ensure it is registered before addressing it.")))) + +(define (run-job cpu-name job) + (let* ((user (queue:job-get-user job)) + (job-len (queue:job-get-duration job)) + (cpu (hash-table-ref/default *cpus* cpu-name #f))) + (if cpu + (let ((curr-user (cpu:dat-get-user cpu))) ;; if it is a user name then job is not done - error + (if curr-user + (begin + (print "ERROR: cpu already busy! Adding more jobs not supported yet. " cpu-name) + #f) + (begin + (cpu:dat-set-user! cpu user) + (cpu:dat-set-job-len! cpu job-len) + (draw-jobs) + (hash-table-set! *cpus* cpu-name cpu) + (event (+ *now* job-len) (lambda ()(end-job cpu-name user))) + #t))) + #f))) + +(define (get-cpu) + (let ((all-cpus (hash-table-keys *cpus*))) + (if (null? all-cpus) + #f + (let loop ((hed (car all-cpus)) + (tal (cdr all-cpus))) + (if (cpu:dat-get-user (hash-table-ref/default *cpus* hed '(#f #f))) ;; if user is #f then cpu is available + (if (null? tal) + #f + (loop (car tal)(cdr tal))) + hed))))) + +;;====================================================================== +;; Animation +;;====================================================================== + +;; make-vector-record queue spec x y delta-y height length +(define (make-queue:spec)(make-vector 5)) +(define-inline (queue:spec-get-x vec) (vector-ref vec 0)) +(define-inline (queue:spec-get-y vec) (vector-ref vec 1)) +(define-inline (queue:spec-get-delta-y vec) (vector-ref vec 2)) +(define-inline (queue:spec-get-height vec) (vector-ref vec 3)) +(define-inline (queue:spec-get-length vec) (vector-ref vec 4)) +(define-inline (queue:spec-set-x! vec val)(vector-set! vec 0 val)) +(define-inline (queue:spec-set-y! vec val)(vector-set! vec 1 val)) +(define-inline (queue:spec-set-delta-y! vec val)(vector-set! vec 2 val)) +(define-inline (queue:spec-set-height! vec val)(vector-set! vec 3 val)) +(define-inline (queue:spec-set-length! vec val)(vector-set! vec 4 val)) + +;; queues are drawn on layer 3 but boxes (jobs) are drawn on the numbered layer +;; +(define (draw-queues) + (let* ((text-offset 3) + (queue-names (sort (hash-table-keys *queues*) string>=?)) + (start-x (vector-ref *queue-spec* 0)) + (text-x (+ start-x text-offset)) + (delta-y (vector-ref *queue-spec* 1)) + (delta-x (vector-ref *queue-spec* 2)) + (height (vector-ref *queue-spec* 3)) + (length (vector-ref *queue-spec* 4)) + (end-x (+ start-x length))) + (ezx-select-layer *ezx* 3) + (ezx-wipe-layer *ezx* 3) + (let loop ((y (vector-ref *queue-spec* 1)) + (qname (car queue-names)) + (tail (cdr queue-names)) + (layer 4)) + (print "Drawing queue at x=" start-x ", y=" y) + (ezx-fillrect-2d *ezx* start-x y end-x (+ y height) *brown*) + (ezx-str-2d *ezx* text-x (- (+ y height) text-offset) qname *white*) + (hash-table-set! *obj-locations* qname (list start-x y layer)) + (if (not (null? tail)) + (loop (+ y height delta-y) + (car tail) + (cdr tail) + (+ layer 1)))) + (ezx-redraw *ezx*))) + +;; compress queue data to (vector user count) list +;; +(define (draw-queue-compress-queue-data queue-dat) + (let loop ((hed (car queue-dat)) + (tal (cdr queue-dat)) + (curr #f) ;; (vector name count) + (res '())) + (let ((user (queue:job-get-user hed))) + (cond + ((not curr) ;; first time through only? + (if (null? tal) + (append res (list (vector user 1))) + (loop (car tal)(cdr tal)(vector user 1) res))) + ((equal? (vector-ref curr 0) user) + (vector-set! curr 1 (+ (vector-ref curr 1) 1)) + (if (null? tal) + (append res (list curr)) + (loop (car tal)(cdr tal) curr res))) + (else ;; names are different, add curr to queue and create new curr + (let ((newcurr (vector user 1))) + (if (null? tal) + (append res (list newcurr)) + (loop (car tal)(cdr tal) newcurr (append res (list curr)))))))))) + +;; draw jobs for a given queue +;; +(define (draw-queue-jobs queue-name) + (let* ((queue-dat (hash-table-ref/default *queues* queue-name #f)) ;; list of jobs in the queue + (obj-spec (hash-table-ref/default *obj-locations* queue-name #f))) ;; x, y etc. of the drawn queue + (if obj-spec + (let ((origin-x (list-ref obj-spec 0)) + (origin-y (list-ref obj-spec 1)) + (bar-width 10) + (queue-len (queue:spec-get-length *queue-spec*)) + (layer (list-ref obj-spec 2))) + (ezx-select-layer *ezx* layer) + (ezx-wipe-layer *ezx* layer) + (if (not (null? queue-dat)) + (let ((res (draw-queue-compress-queue-data queue-dat))) + (if (not (null? res)) + (let loop ((hed (car res)) + (tal (cdr res)) + (x2 (+ origin-x queue-len))) + (let* ((user (vector-ref hed 0)) + (h (let ((numjobs (vector-ref hed 1))) + (if *use-log* + (inexact->exact (round (log (+ 1 (* *job-log-scale* numjobs))))) + numjobs))) + (x1 (- x2 bar-width)) + (y2 (- origin-y h))) + ;; (print "x1 " x1 ", origin-y " origin-y ", x2 " x2 ", y2 " y2) + (ezx-fillrect-2d *ezx* x1 y2 x2 origin-y (get-user-color user)) + (if (not (null? tal)) + (loop (car tal)(cdr tal) x1))))) + (ezx-redraw *ezx*))))))) + +(let* ((args (argv)) + (fname (if (> (length args) 1) + (cadr args) + "default.scm"))) + (load (if (file-exists? fname) fname "default.scm"))) ADDED batchsim/default.scm Index: batchsim/default.scm ================================================================== --- /dev/null +++ batchsim/default.scm @@ -0,0 +1,133 @@ +;; run sim for four hours +;; +(define *end-time* (* 60 50)) + +;; create the cpus +;; +(let loop ((count 200)) + (add-cpu (conc "cpu_" count) 1 1) + (if (>= count 0)(loop (- count 1)))) + +(draw-cpus) + +(define *pool1* (new-pool "generic" 100 100 100 100 2 10)) +(let loop ((count 10)) + (pool:add-cpu *pool1* (conc count) 1 1) + (if (> count 0) + (loop (- count 1)))) + +(pool:draw *ezx* *pool1*) + +;; init the queues +;; +(hash-table-set! *queues* "normal" '()) +(hash-table-set! *queues* "quick" '()) +(draw-queues) + +;; user k adds 200 jobs at time zero +;; +(event *start-time* + (lambda () + (let loop ((count 300)) ;; add 500 jobs + (add-job "normal" "k" 600 1 1) + (if (>= count 0)(loop (- count 1)))))) + +;; one minute in user m runs ten jobs +;; +(event (+ 600 *start-time*) + (lambda () + (let loop ((count 300)) ;; add 100 jobs + (add-job "normal" "m" 600 1 1) + (if (> count 0)(loop (- count 1)))))) + +;; every minute user j runs ten jobs +;; +(define *user-j-jobs* 300) +(event (+ 600 *start-time*) + (lambda () + (let f () + (schedule 60) + (if (> *user-j-jobs* 0) + (begin + (let loop ((count 5)) ;; add 100 jobs + (add-job "quick" "j" 600 1 1) + (if (> count 0)(loop (- count 1)))) + (set! *user-j-jobs* (- *user-j-jobs* 5)))) + (if (and (not *done*) + (> *user-j-jobs* 0)) + (f))))) ;; Megatest user running 200 jobs + +;; every minute user j runs ten jobs +;; +(define *user-j-jobs* 300) +(event (+ 630 *start-time*) + (lambda () + (let f () + (schedule 60) + (if (> *user-j-jobs* 0) + (begin + (let loop ((count 5)) ;; add 100 jobs + (add-job "quick" "n" 600 1 1) + (if (> count 0)(loop (- count 1)))) + (set! *user-j-jobs* (- *user-j-jobs* 5)))) + (if (and (not *done*) + (> *user-j-jobs* 0)) + (f))))) ;; Megatest user running 200 jobs + +;; ;; +;; (event *start-time* +;; (lambda () +;; (let f ((count 200)) +;; (schedule 10) +;; (add-job "normal" "t" 60 1 1) +;; (if (and (not *done*) +;; (>= count 0)) +;; (f (- count 1)))))) + +;; every 3 seconds check for available machines and launch a job +;; +(event *start-time* + (lambda () + (let f () + (schedule 3) + (let ((queue-names (random-sort (hash-table-keys *queues*)))) + (let loop ((cpu (get-cpu)) + (count (+ (length queue-names) 4)) + (qname (car queue-names)) + (remq (cdr queue-names))) + (if (and cpu + (> count 0)) + (begin + (if (peek-job qname) ;; any jobs to do in normal queue + (let ((job (take-job qname))) + (run-job cpu job))) + (loop (get-cpu) + (- count 1) + (if (null? remq) + (car queue-names) + (car remq)) + (if (null? remq) + (cdr queue-names) + (cdr remq))))))) + (if (not *done*)(f))))) + +;; screen updates +;; +(event *start-time* (lambda () + (let f () + (schedule 60) ;; update the screen every 60 seconds of sim time + (draw-cpus) ;; (print "Now: " *now* " queue: " (hash-table->alist *queues*)) + (wait-for-next-draw-time) + (if (not *done*) (f))))) + + +;; end the simulation +;; +(event *end-time* + (lambda () + (set! *event-list* '()) + (set! *done* #t))) + +(start) +;; (exit 0) + ADDED batchsim/events.scm Index: batchsim/events.scm ================================================================== --- /dev/null +++ batchsim/events.scm @@ -0,0 +1,79 @@ + +;;====================================================================== +;; Event Processing and Simulator +;;====================================================================== + +;; The global event list +(define *event-list* '()) +(define *start-time* 0) +(define *end-time* (* 60 60 4)) ;; four hours +(define *now* *start-time*) +(define *done* #f) + +(define (random-sort l) + (sort l + (lambda (x y) + (equal? 0 (random 2))))) + +;; Each item in the event list is a list of a scheduled time and the thunk +;; (time thunk). Sort the list so that the next event is the earliest. +;; +(define event-sort + (lambda (@a @b) + (< (car @a)(car @b)))) + +(define event + (lambda ($time $thunk) ;; add a sort based on scheduled time here -- improve later + ;; to use an insert algorythm. + (set! *event-list* (sort (cons (list $time $thunk) *event-list*) event-sort)))) + +(define start + (lambda () + (let ((next (car *event-list*))) + (set! *event-list* (cdr *event-list*)) + (set! *now* (car next)) + (if (not *done*) ;; note that the second item in the list is the thunk + ((car (cdr next))))))) + +(define pause + (lambda () + (call/cc + (lambda (k) + (event (lambda () (k #f))) + (start))))) + +(define schedule + (lambda ($time) + (call/cc + (lambda (k) + (event (+ *now* $time) (lambda () (k #f))) + (start))))) + +;; (event (lambda () (let f () (pause) (display "h") (f)))) + +(define years->seconds + (lambda ($yrs) + (* $yrs 365 24 3600))) + +(define weeks->seconds + (lambda ($wks) + (* $wks 7 24 3600))) + +(define days->seconds + (lambda ($days) + (* $days 24 3600))) + +(define months->seconds + (lambda ($months) + (* $months (/ 365 12) 24 3600))) + +(define seconds->date + (lambda ($seconds) + (posix-strftime "%D" (posix-localtime (inexact->exact $seconds))))) + +(define (seconds->h:m:s seconds) + (let* ((hours (quotient seconds 3600)) + (rem1 (- seconds (* hours 3600))) + (minutes (quotient rem1 60)) + (rem-sec (- rem1 (* minutes 60)))) + (conc hours "h " minutes "m " rem-sec "s"))) ADDED batchsim/testing.scm Index: batchsim/testing.scm ================================================================== --- /dev/null +++ batchsim/testing.scm @@ -0,0 +1,135 @@ +;; run sim for four hours +;; +(define *end-time* (* 60 50)) + +;; create the cpus +;; +(let loop ((count 200)) + (add-cpu (conc "cpu_" count) 1 1) + (if (>= count 0)(loop (- count 1)))) + +;; (draw-cpus) + +(define *pool1* (new-pool "generic" 20 20 12 80 2 4)) +(let loop ((count 10)) + (pool:add-cpu *pool1* (conc count) 1 1) + (if (> count 0) + (loop (- count 1)))) + +(pool:draw *ezx* *pool1*) + +;; ;; init the queues +;; ;; +;; (hash-table-set! *queues* "normal" '()) +;; (hash-table-set! *queues* "quick" '()) +;; (draw-queues) +;; +;; ;; user k adds 200 jobs at time zero +;; ;; +;; (event *start-time* +;; (lambda () +;; (let loop ((count 300)) ;; add 500 jobs +;; (add-job "normal" "k" 600 1 1) +;; (if (>= count 0)(loop (- count 1)))))) +;; +;; ;; one minute in user m runs ten jobs +;; ;; +;; (event (+ 600 *start-time*) +;; (lambda () +;; (let loop ((count 300)) ;; add 100 jobs +;; (add-job "normal" "m" 600 1 1) +;; (if (> count 0)(loop (- count 1)))))) +;; +;; ;; every minute user j runs ten jobs +;; ;; +;; (define *user-j-jobs* 300) +;; (event (+ 600 *start-time*) +;; (lambda () +;; (let f () +;; (schedule 60) +;; (if (> *user-j-jobs* 0) +;; (begin +;; (let loop ((count 5)) ;; add 100 jobs +;; (add-job "quick" "j" 600 1 1) +;; (if (> count 0)(loop (- count 1)))) +;; (set! *user-j-jobs* (- *user-j-jobs* 5)))) +;; (if (and (not *done*) +;; (> *user-j-jobs* 0)) +;; (f))))) ;; Megatest user running 200 jobs +;; +;; ;; every minute user j runs ten jobs +;; ;; +;; (define *user-j-jobs* 300) +;; (event (+ 630 *start-time*) +;; (lambda () +;; (let f () +;; (schedule 60) +;; (if (> *user-j-jobs* 0) +;; (begin +;; (let loop ((count 5)) ;; add 100 jobs +;; (add-job "quick" "n" 600 1 1) +;; (if (> count 0)(loop (- count 1)))) +;; (set! *user-j-jobs* (- *user-j-jobs* 5)))) +;; (if (and (not *done*) +;; (> *user-j-jobs* 0)) +;; (f))))) ;; Megatest user running 200 jobs +;; +;; ;; ;; +;; ;; (event *start-time* +;; ;; (lambda () +;; ;; (let f ((count 200)) +;; ;; (schedule 10) +;; ;; (add-job "normal" "t" 60 1 1) +;; ;; (if (and (not *done*) +;; ;; (>= count 0)) +;; ;; (f (- count 1)))))) +;; +;; ;; every 3 seconds check for available machines and launch a job +;; ;; +;; (event *start-time* +;; (lambda () +;; (let f () +;; (schedule 3) +;; (let ((queue-names (random-sort (hash-table-keys *queues*)))) +;; (let loop ((cpu (get-cpu)) +;; (count (+ (length queue-names) 4)) +;; (qname (car queue-names)) +;; (remq (cdr queue-names))) +;; (if (and cpu +;; (> count 0)) +;; (begin +;; (if (peek-job qname) ;; any jobs to do in normal queue +;; (let ((job (take-job qname))) +;; (run-job cpu job))) +;; (loop (get-cpu) +;; (- count 1) +;; (if (null? remq) +;; (car queue-names) +;; (car remq)) +;; (if (null? remq) +;; (cdr queue-names) +;; (cdr remq))))))) +;; (if (not *done*)(f))))) +;; +;; ;; screen updates +;; ;; +(event *start-time* (lambda () + (let f () + (schedule 60) ;; update the screen every 60 seconds of sim time + ;; (draw-cpus) ;; (print "Now: " *now* " queue: " (hash-table->alist *queues*)) + (pool:draw *ezx* *pool1*) + + (wait-for-next-draw-time) + (if (not *done*) (f))))) +;; +;; +;; ;; end the simulation +;; ;; +(event *end-time* + (lambda () + (set! *event-list* '()) + (set! *done* #t))) +;; +(start) +;; ;; (exit 0) +;; ADDED bin/sleeprunner Index: bin/sleeprunner ================================================================== --- /dev/null +++ bin/sleeprunner @@ -0,0 +1,7 @@ +#!/bin/bash + +if [[ $SLEEPRUNNER == "" ]];then +SLEEPRUNNER=1 +fi + +echo "nbfake $@ &> /dev/null" | at now + $SLEEPRUNNER minutes &> /dev/null Index: common.scm ================================================================== --- common.scm +++ common.scm @@ -164,10 +164,33 @@ (debug:print 0 "ERROR: received bad encoded string \"" instr "\", message: " ((condition-property-accessor 'exn 'message) exn)) (print-call-chain (current-error-port)) #f) (read (open-input-string (base64:base64-decode instr)))) (read (open-input-string (z3:decode-buffer (base64:base64-decode instr)))))) + +;; dot-locking egg seems not to work, using this for now +;; if lock is older than expire-time then remove it and try again +;; to get the lock +;; +(define (common:simple-file-lock fname #!key (expire-time 300)) + (if (file-exists? fname) + (if (> (- (current-seconds)(file-modification-time fname)) expire-time) + (begin + (delete-file* fname) + (common:simple-file-lock fname expire-time: expire-time)) + #f) + (let ((key-string (conc (get-host-name) "-" (current-process-id)))) + (with-output-to-file fname + (lambda () + (print key-string))) + (thread-sleep! 0.25) + (with-input-from-file fname + (lambda () + (equal? key-string (read-line))))))) + +(define (common:simple-file-release-lock fname) + (delete-file* fname)) ;;====================================================================== ;; S T A T E S A N D S T A T U S E S ;;====================================================================== Index: common_records.scm ================================================================== --- common_records.scm +++ common_records.scm @@ -52,15 +52,24 @@ (print "ERROR: Invalid debug value \"" vstr "\"") #f) #t)) (define (debug:debug-mode n) - (or (and (number? *verbosity*) - (<= n *verbosity*)) - (and (list? *verbosity*) - (member n *verbosity*)))) - + (cond + ((and (number? *verbosity*) ;; number number + (number? n)) + (<= n *verbosity*)) + ((and (list? *verbosity*) ;; list number + (number? n)) + (member n *verbosity*)) + ((and (list? *verbosity*) ;; list list + (list? n)) + (not (null? (lset-intersection! eq? *verbosity* n)))) + ((and (number? *verbosity*) + (list? n)) + (member *verbosity* n)))) + (define (debug:setup) (let ((debugstr (or (args:get-arg "-debug") (getenv "MT_DEBUG_MODE")))) (set! *verbosity* (debug:calc-verbosity debugstr)) (debug:check-verbosity *verbosity* debugstr) @@ -85,11 +94,11 @@ (define (debug:print-info n . params) (if (debug:debug-mode n) (with-output-to-port (current-error-port) (lambda () - (let ((res (format#format #f "INFO: (~2d) ~a" n (apply conc params)))) + (let ((res (format#format #f "INFO: (~a) ~a" n (apply conc params)))) (if *logging* (db:log-event res) ;; (apply print "pid:" (current-process-id) " " "INFO: (" n ") " params) ;; res) (apply print "INFO: (" n ") " params) ;; res) )))))) Index: configf.scm ================================================================== --- configf.scm +++ configf.scm @@ -57,10 +57,11 @@ (define configf:key-sys-pr (regexp "^(\\S+)\\s+\\[system\\s+(\\S+.*)\\]\\s*$")) (define configf:key-val-pr (regexp "^(\\S+)(\\s+(.*)|())$")) (define configf:key-no-val (regexp "^(\\S+)(\\s*)$")) (define configf:comment-rx (regexp "^\\s*#.*")) (define configf:cont-ln-rx (regexp "^(\\s+)(\\S+.*)$")) +(define configf:settings (regexp "^\\[configf:settings\\s+(\\S+)\\s+(\\S+)]\\s*$")) ;; read a line and process any #{ ... } constructs (define configf:var-expand-regex (regexp "^(.*)#\\{(scheme|system|shell|getenv|get|runconfigs-get|rget)\\s+([^\\}\\{]*)\\}(.*)")) (define (configf:process-line l ht allow-system) @@ -85,16 +86,20 @@ (conc "(lambda (ht)(config-lookup ht \"" sect "\" \"" var "\"))"))) ((runconfigs-get) (conc "(lambda (ht)(runconfigs-get ht \"" cmd "\"))")) ((rget) (conc "(lambda (ht)(runconfigs-get ht \"" cmd "\"))")) (else "(lambda (ht)(print \"ERROR\") \"ERROR\")")))) ;; (print "fullcmd=" fullcmd) - (if (or allow-system - (not (member cmdtype '("system" "shell")))) - (with-input-from-string fullcmd - (lambda () - (set! result ((eval (read)) ht)))) - (set! result (conc "#{(" cmdtype ") " cmd "}"))) (loop (conc prestr result poststr))) + (handle-exceptions + exn + (debug:print 0 "ERROR: failed to process config input \"" l "\"") + (if (or allow-system + (not (member cmdtype '("system" "shell")))) + (with-input-from-string fullcmd + (lambda () + (set! result ((eval (read)) ht)))) + (set! result (conc "#{(" cmdtype ") " cmd "}")))) + (loop (conc prestr result poststr))) res)) res))) ;; Run a shell command and return the output as a string (define (shell cmd) @@ -121,11 +126,11 @@ (configf:lookup config "default" var)) (configf:lookup config "default" var)))) ;; this was inline but I'm pretty sure that is a hold over from when it was *very* simple ... ;; -(define (configf:read-line p ht allow-processing) +(define (configf:read-line p ht allow-processing settings) (let loop ((inl (read-line p))) (let ((cont-line (and (string? inl) (not (string-null? inl)) (equal? "\\" (string-take-right inl 1))))) (if cont-line ;; last character is \ @@ -133,49 +138,58 @@ (if (not (eof-object? nextl)) (loop (string-append (if cont-line (string-take inl (- (string-length inl) 1)) inl) nextl)))) - (case allow-processing ;; if (and allow-processing - ;; (not (eq? allow-processing 'return-string))) - ((#t #f) - (configf:process-line inl ht allow-processing)) - ((return-string) - inl) - (else - (configf:process-line inl ht allow-processing))))))) - + (let ((res (case allow-processing ;; if (and allow-processing + ;; (not (eq? allow-processing 'return-string))) + ((#t #f) + (configf:process-line inl ht allow-processing)) + ((return-string) + inl) + (else + (configf:process-line inl ht allow-processing))))) + (if (and (string? res) + (not (equal? (hash-table-ref/default settings "trim-trailing-spaces" "no") "no"))) + (string-substitute "\\s+$" "" res) + res)))))) + ;; read a config file, returns hash table of alists ;; read a config file, returns hash table of alists ;; adds to ht if given (must be #f otherwise) ;; envion-patt is a regex spec that identifies sections that will be eval'd ;; in the environment on the fly ;; sections: #f => get all, else list of sections to gather -(define (read-config path ht allow-system #!key (environ-patt #f)(curr-section #f)(sections #f)) +(define (read-config path ht allow-system #!key (environ-patt #f)(curr-section #f)(sections #f)(settings (make-hash-table))) (debug:print-info 5 "read-config " path " allow-system " allow-system " environ-patt " environ-patt " curr-section: " curr-section " sections: " sections " pwd: " (current-directory)) + (debug:print 9 "START: " path) (if (not (file-exists? path)) (begin (debug:print-info 1 "read-config - file not found " path " current path: " (current-directory)) ;; WARNING: This is a risky change but really, we should not return an empty hash table if no file read? #f) ;; (if (not ht)(make-hash-table) ht)) (let ((inp (open-input-file path)) (res (if (not ht)(make-hash-table) ht))) - (let loop ((inl (configf:read-line inp res allow-system)) ;; (read-line inp)) + (let loop ((inl (configf:read-line inp res allow-system settings)) ;; (read-line inp)) (curr-section-name (if curr-section curr-section "default")) (var-flag #f);; turn on for key-var-pr and cont-ln-rx, turn off elsewhere (lead #f)) (debug:print-info 8 "curr-section-name: " curr-section-name " var-flag: " var-flag "\n inl: \"" inl "\"") (if (eof-object? inl) (begin (close-input-port inp) (hash-table-delete! res "") ;; we are using "" as a dumping ground and must remove it before returning the ht + (debug:print 9 "END: " path) res) (regex-case inl - (configf:comment-rx _ (loop (configf:read-line inp res allow-system) curr-section-name #f #f)) - (configf:blank-l-rx _ (loop (configf:read-line inp res allow-system) curr-section-name #f #f)) + (configf:comment-rx _ (loop (configf:read-line inp res allow-system settings) curr-section-name #f #f)) + (configf:blank-l-rx _ (loop (configf:read-line inp res allow-system settings) curr-section-name #f #f)) + (configf:settings ( x setting val ) (begin + (hash-table-set! settings setting val) + (loop (configf:read-line inp res allow-system settings) curr-section-name #f #f))) (configf:include-rx ( x include-file ) (let* ((curr-conf-dir (pathname-directory path)) (full-conf (if (absolute-pathname? include-file) include-file (nice-path (conc (if curr-conf-dir @@ -183,18 +197,19 @@ ".") "/" include-file))))) (if (file-exists? full-conf) (begin ;; (push-directory conf-dir) - (read-config full-conf res allow-system environ-patt: environ-patt curr-section: curr-section-name sections: sections) + (debug:print 9 "Including: " full-conf) + (read-config full-conf res allow-system environ-patt: environ-patt curr-section: curr-section-name sections: sections settings: settings) ;; (pop-directory) - (loop (configf:read-line inp res allow-system) curr-section-name #f #f)) + (loop (configf:read-line inp res allow-system settings) curr-section-name #f #f)) (begin - (debug:print 2 "INFO: include file " include-file " not found (called from " path ")") + (debug:print '(2 9) "INFO: include file " include-file " not found (called from " path ")") (debug:print 2 " " full-conf) - (loop (configf:read-line inp res allow-system) curr-section-name #f #f))))) - (configf:section-rx ( x section-name ) (loop (configf:read-line inp res allow-system) + (loop (configf:read-line inp res allow-system settings) curr-section-name #f #f))))) + (configf:section-rx ( x section-name ) (loop (configf:read-line inp res allow-system settings) ;; if we have the sections list then force all settings into "" and delete it later? (if (or (not sections) (member section-name sections)) section-name "") ;; stick everything into "" #f #f)) @@ -218,26 +233,28 @@ key (case allow-system ((return-procs) val-proc) ((return-string) cmd) (else (val-proc))))) - (loop (configf:read-line inp res allow-system) curr-section-name #f #f)) - (loop (configf:read-line inp res allow-system) curr-section-name #f #f))) + (loop (configf:read-line inp res allow-system settings) curr-section-name #f #f)) + (loop (configf:read-line inp res allow-system settings) curr-section-name #f #f))) (configf:key-val-pr ( x key unk1 val unk2 ) (let* ((alist (hash-table-ref/default res curr-section-name '())) (envar (and environ-patt (string-search (regexp environ-patt) curr-section-name))) (realval (if envar (config:eval-string-in-environment val) val))) (debug:print-info 6 "read-config env setting, envar: " envar " realval: " realval " val: " val " key: " key " curr-section-name: " curr-section-name) (if envar (safe-setenv key realval)) + (debug:print 10 " setting: [" curr-section-name "] " key " = " val) (hash-table-set! res curr-section-name (config:assoc-safe-add alist key realval)) - (loop (configf:read-line inp res allow-system) curr-section-name key #f))) + (loop (configf:read-line inp res allow-system settings) curr-section-name key #f))) (configf:key-no-val ( x key val) (let* ((alist (hash-table-ref/default res curr-section-name '()))) + (debug:print 10 " setting: [" curr-section-name "] " key " = #t") (hash-table-set! res curr-section-name (config:assoc-safe-add alist key #t)) - (loop (configf:read-line inp res allow-system) curr-section-name key #f))) + (loop (configf:read-line inp res allow-system settings) curr-section-name key #f))) ;; if a continued line (configf:cont-ln-rx ( x whsp val ) (let ((alist (hash-table-ref/default res curr-section-name '()))) (if var-flag ;; if set to a string then we have a continued var (let ((newval (conc (config-lookup res curr-section-name var-flag) "\n" @@ -247,15 +264,15 @@ "") val))) ;; (print "val: " val "\nnewval: \"" newval "\"\nvarflag: " var-flag) (hash-table-set! res curr-section-name (config:assoc-safe-add alist var-flag newval)) - (loop (configf:read-line inp res allow-system) curr-section-name var-flag (if lead lead whsp))) - (loop (configf:read-line inp res allow-system) curr-section-name #f #f)))) + (loop (configf:read-line inp res allow-system settings) curr-section-name var-flag (if lead lead whsp))) + (loop (configf:read-line inp res allow-system settings) curr-section-name #f #f)))) (else (debug:print 0 "ERROR: problem parsing " path ",\n \"" inl "\"") (set! var-flag #f) - (loop (configf:read-line inp res allow-system) curr-section-name #f #f)))))))) + (loop (configf:read-line inp res allow-system settings) curr-section-name #f #f)))))))) ;; pathenvvar will set the named var to the path of the config (define (find-and-read-config fname #!key (environ-patt #f)(given-toppath #f)(pathenvvar #f)) (let* ((curr-dir (current-directory)) (configinfo (find-config fname toppath: given-toppath)) Index: dashboard-tests.scm ================================================================== --- dashboard-tests.scm +++ dashboard-tests.scm @@ -401,11 +401,12 @@ ;;====================================================================== ;; ;;====================================================================== (define (examine-test run-id test-id) ;; run-id run-key origtest) (let* ((db-path (db:dbfile-path run-id)) ;; (conc (configf:lookup *configdat* "setup" "linktree") "/db/" run-id ".db")) - (dbstruct (make-dbr:dbstruct path: (configf:lookup *configdat* "setup" "linktree") local: #t)) + (dbstruct (make-dbr:dbstruct path: (db:dbfile-path #f) ;; (configf:lookup *configdat* "setup" "linktree") + local: #t)) (testdat (db:get-test-info-by-id dbstruct run-id test-id)) (db-mod-time 0) ;; (file-modification-time db-path)) (last-update 0) ;; (current-seconds)) (request-update #t)) (if (not testdat) @@ -424,11 +425,11 @@ (logfile "/this/dir/better/not/exist") (rundir (if testdat (db:test-get-rundir testdat) logfile)) (testdat-path (conc rundir "/testdat.db")) ;; this gets recalculated until found - (teststeps (if testdat (dcommon:get-compressed-steps dbstruct run-id test-id) '())) + (teststeps (if testdat (tests:get-compressed-steps dbstruct run-id test-id) '())) (testfullname (if testdat (db:test-get-fullname testdat) "Gathering data ...")) (testname (if testdat (db:test-get-testname testdat) "n/a")) (testmeta (if testdat (let ((tm (db:testmeta-get-record dbstruct testname))) (if tm tm (make-db:testmeta))) @@ -481,11 +482,11 @@ (db:get-test-info-by-id dbstruct run-id test-id ))))) ;; (debug:print-info 0 "need-update= " need-update " curr-mod-time = " curr-mod-time) (cond ((and need-update newtestdat) (set! testdat newtestdat) - (set! teststeps (dcommon:get-compressed-steps dbstruct run-id test-id)) + (set! teststeps (tests:get-compressed-steps dbstruct run-id test-id)) (set! logfile (conc (db:test-get-rundir testdat) "/" (db:test-get-final_logf testdat))) (set! rundir ;; (filedb:get-path *fdb* (db:test-get-rundir testdat)) ;; ) (set! testfullname (db:test-get-fullname testdat)) ;; (debug:print 0 "INFO: teststeps=" (intersperse teststeps "\n ")) Index: dashboard.scm ================================================================== --- dashboard.scm +++ dashboard.scm @@ -85,11 +85,11 @@ (if (not (launch:setup-for-run)) (begin (print "Failed to find megatest.config, exiting") (exit 1))) -(define *dbdir* (conc (configf:lookup *configdat* "setup" "linktree") "/.db")) +(define *dbdir* (db:dbfile-path #f)) ;; (conc (configf:lookup *configdat* "setup" "linktree") "/.db")) (define *dbstruct-local* (make-dbr:dbstruct path: *dbdir* local: #t)) (define *db-file-path* (db:dbfile-path 0)) ;; HACK ALERT: this is a hack, please fix. Index: db.scm ================================================================== --- db.scm +++ db.scm @@ -61,18 +61,15 @@ ;; (define (db:get-db dbstruct run-id) (if (sqlite3:database? dbstruct) ;; pass sqlite3 databases on through dbstruct (begin - (mutex-lock! *rundb-mutex*) (let ((dbdat (if (or (not run-id) (eq? run-id 0)) (db:open-main dbstruct) (db:open-rundb dbstruct run-id) ))) - ;; db prunning would go here - (mutex-unlock! *rundb-mutex*) dbdat)))) (define (db:dbdat-get-db dbdat) (if (pair? dbdat) (car dbdat) @@ -137,24 +134,28 @@ ;; ;; ;; (define (db:get-path dbstruct id) ;; (let ((fdb (db:get-filedb dbstruct))) ;; (filedb:get-path db id))) -;; NB// #f => zeroth db with name=main.db +;; NB// #f => return dbdir only +;; (was planned to be; zeroth db with name=main.db) ;; (define (db:dbfile-path run-id) - (let* (;; (toppath (dbr:dbstruct-get-path dbstruct)) - (link-tree-path (configf:lookup *configdat* "setup" "linktree")) - (fname (if (eq? run-id 0) "main.db" (conc run-id ".db"))) - (dbdir (conc link-tree-path "/.db/"))) + (let* ((dbdir (or (configf:lookup *configdat* "setup" "dbdir") + (conc (configf:lookup *configdat* "setup" "linktree") "/.db"))) + (fname (if run-id + (if (eq? run-id 0) "main.db" (conc run-id ".db")) + #f))) (handle-exceptions exn (begin (debug:print 0 "ERROR: Couldn't create path to " dbdir) (exit 1)) (if (not (directory? dbdir))(create-directory dbdir #t))) - (conc dbdir fname))) + (if fname + (conc dbdir "/" fname) + dbdir))) (define (db:set-sync db) (let ((syncprag (configf:lookup *configdat* "setup" "sychronous"))) (sqlite3:execute db (conc "PRAGMA synchronous = " (or syncprag 1) ";")))) @@ -191,86 +192,95 @@ (dbr:dbstruct-get-localdb dbstruct run-id) (dbr:dbstruct-get-inmem dbstruct)))) ;; (dbr:dbstruct-get-runrec dbstruct run-id 'inmem))) (if (or rdb do-not-open) rdb - (let* ((dbpath (db:dbfile-path run-id)) ;; (conc toppath "/db/" run-id ".db")) - (dbexists (file-exists? dbpath)) - (inmem (if local #f (db:open-inmem-db))) - (refdb (if local #f (db:open-inmem-db))) - (db (db:lock-create-open dbpath ;; this is the database physically on disk - (lambda (db) - (handle-exceptions - exn - (begin - (release-dot-lock dbpath) - (if (> attemptnum 2) - (debug:print 0 "ERROR: tried twice, cannot create/initialize db for run-id " run-id ", at path " dbpath) - (db:open-rundb dbstruct run-id attemptnum (+ attemptnum 1)))) - (db:initialize-run-id-db db) - (sqlite3:execute - db - "INSERT OR IGNORE INTO tests (id,run_id,testname,event_time,item_path,state,status) VALUES (?,?,'bogustest',strftime('%s','now'),'nowherepath','DELETED','n/a');" - (* run-id 30000) ;; allow for up to 30k tests per run - run-id) - ;; do a dummy query to test that the table exists and the db is truly readable - (sqlite3:execute db "SELECT * FROM tests WHERE id=?;" (* run-id 30000)) - )))) ;; add strings db to rundb, not in use yet - ;; )) ;; (sqlite3:open-database dbpath)) - (olddb (if *megatest-db* - *megatest-db* - (let ((db (db:open-megatest-db))) - (set! *megatest-db* db) - db))) - (write-access (file-write-access? dbpath)) - ;; (handler (make-busy-timeout 136000)) - ) - (if (and dbexists (not write-access)) - (set! *db-write-access* #f)) ;; only unset so other db's also can use this control - (dbr:dbstruct-set-rundb! dbstruct (cons db dbpath)) - (dbr:dbstruct-set-inuse! dbstruct #t) - (dbr:dbstruct-set-olddb! dbstruct olddb) - ;; (dbr:dbstruct-set-run-id! dbstruct run-id) - (if local - (begin - (dbr:dbstruct-set-localdb! dbstruct run-id db) ;; (dbr:dbstruct-set-inmem! dbstruct db) ;; direct access ... - db) - (begin - (dbr:dbstruct-set-inmem! dbstruct inmem) - ;; dec 14, 2014 - keep deleted records available. hunch is that they are needed for id placeholders - ;; (sqlite3:execute db "DELETE FROM tests WHERE state='DELETED';") ;; they just slow us down in this context - (db:sync-tables db:sync-tests-only db inmem) - (db:delay-if-busy refdb) ;; dbpath: (db:dbdat-get-path refdb)) ;; What does delaying here achieve? - (dbr:dbstruct-set-refdb! dbstruct refdb) - (db:sync-tables db:sync-tests-only inmem refdb) ;; use inmem as the reference, don't read again from db - ;; sync once more to deal with delays? - ;; (db:sync-tables db:sync-tests-only db inmem) - ;; (db:sync-tables db:sync-tests-only inmem refdb) - inmem)))))) + (begin + (mutex-lock! *rundb-mutex*) + (let* ((dbpath (db:dbfile-path run-id)) ;; (conc toppath "/db/" run-id ".db")) + (dbexists (file-exists? dbpath)) + (inmem (if local #f (db:open-inmem-db))) + (refdb (if local #f (db:open-inmem-db))) + (db (db:lock-create-open dbpath ;; this is the database physically on disk + (lambda (db) + (handle-exceptions + exn + (begin + (release-dot-lock dbpath) + (if (> attemptnum 2) + (debug:print 0 "ERROR: tried twice, cannot create/initialize db for run-id " run-id ", at path " dbpath) + (db:open-rundb dbstruct run-id attemptnum (+ attemptnum 1)))) + (db:initialize-run-id-db db) + (sqlite3:execute + db + "INSERT OR IGNORE INTO tests (id,run_id,testname,event_time,item_path,state,status) VALUES (?,?,'bogustest',strftime('%s','now'),'nowherepath','DELETED','n/a');" + (* run-id 30000) ;; allow for up to 30k tests per run + run-id) + ;; do a dummy query to test that the table exists and the db is truly readable + (sqlite3:execute db "SELECT * FROM tests WHERE id=?;" (* run-id 30000)) + )))) ;; add strings db to rundb, not in use yet + ;; )) ;; (sqlite3:open-database dbpath)) + (olddb (if *megatest-db* + *megatest-db* + (let ((db (db:open-megatest-db))) + (set! *megatest-db* db) + db))) + (write-access (file-write-access? dbpath)) + ;; (handler (make-busy-timeout 136000)) + ) + (if (and dbexists (not write-access)) + (set! *db-write-access* #f)) ;; only unset so other db's also can use this control + (dbr:dbstruct-set-rundb! dbstruct (cons db dbpath)) + (dbr:dbstruct-set-inuse! dbstruct #t) + (dbr:dbstruct-set-olddb! dbstruct olddb) + ;; (dbr:dbstruct-set-run-id! dbstruct run-id) + (mutex-unlock! *rundb-mutex*) + (if local + (begin + (dbr:dbstruct-set-localdb! dbstruct run-id db) ;; (dbr:dbstruct-set-inmem! dbstruct db) ;; direct access ... + db) + (begin + (dbr:dbstruct-set-inmem! dbstruct inmem) + ;; dec 14, 2014 - keep deleted records available. hunch is that they are needed for id placeholders + ;; (sqlite3:execute db "DELETE FROM tests WHERE state='DELETED';") ;; they just slow us down in this context + (db:sync-tables db:sync-tests-only db inmem) + (db:delay-if-busy refdb) ;; dbpath: (db:dbdat-get-path refdb)) ;; What does delaying here achieve? + (dbr:dbstruct-set-refdb! dbstruct refdb) + (db:sync-tables db:sync-tests-only inmem refdb) ;; use inmem as the reference, don't read again from db + ;; sync once more to deal with delays? + ;; (db:sync-tables db:sync-tests-only db inmem) + ;; (db:sync-tables db:sync-tests-only inmem refdb) + inmem))))))) ;; This routine creates the db. It is only called if the db is not already ls opened ;; (define (db:open-main dbstruct) ;; (conc *toppath* "/megatest.db") (car *configinfo*))) (let ((mdb (dbr:dbstruct-get-main dbstruct))) (if mdb mdb - (let* ((dbpath (db:dbfile-path 0)) - (dbexists (file-exists? dbpath)) - (db (db:lock-create-open dbpath db:initialize-main-db)) - (olddb (db:open-megatest-db)) - (write-access (file-write-access? dbpath)) - (dbdat (cons db dbpath))) - (if (and dbexists (not write-access)) - (set! *db-write-access* #f)) - (dbr:dbstruct-set-main! dbstruct dbdat) - (dbr:dbstruct-set-olddb! dbstruct olddb) ;; olddb is already a (cons db path) - dbdat)))) + (begin + (mutex-lock! *rundb-mutex*) + (let* ((dbpath (db:dbfile-path 0)) + (dbexists (file-exists? dbpath)) + (db (db:lock-create-open dbpath db:initialize-main-db)) + (olddb (db:open-megatest-db)) + (write-access (file-write-access? dbpath)) + (dbdat (cons db dbpath))) + (if (and dbexists (not write-access)) + (set! *db-write-access* #f)) + (dbr:dbstruct-set-main! dbstruct dbdat) + (dbr:dbstruct-set-olddb! dbstruct olddb) ;; olddb is already a (cons db path) + (mutex-unlock! *rundb-mutex*) + (if (and (not dbexists) + *db-write-access*) ;; did not have a prior db and do have write access + (db:multi-db-sync #f 'old2new)) ;; migrate data from megatest.db automatically + dbdat))))) ;; Make the dbstruct, setup up auxillary db's and call for main db at least once ;; (define (db:setup run-id #!key (local #f)) - (let* ((dbdir (conc (configf:lookup *configdat* "setup" "linktree") "/.db")) + (let* ((dbdir (db:dbfile-path #f)) ;; (conc (configf:lookup *configdat* "setup" "linktree") "/.db")) (dbstruct (make-dbr:dbstruct path: dbdir local: local))) dbstruct)) ;; Open the classic megatest.db file in toppath ;; @@ -2072,11 +2082,11 @@ "SELECT run_id,testname,item_path,state,status FROM tests WHERE id=?;" test-id))) res)) ;; get a useful subset of the tests data (used in dashboard -;; use db:mintests-get-{id ,run_id,testname ...} +;; use db:mintest-get-{id ,run_id,testname ...} ;; (define (db:get-tests-for-runs-mindata dbstruct run-ids testpatt states statuses not-in) (debug:print 0 "ERROR: BROKN!") ;; (db:get-tests-for-runs dbstruct run-ids testpatt states statuses not-in: not-in qryvals: "id,run_id,testname,state,status,event_time,item_path")) ) @@ -2211,10 +2221,23 @@ (lambda (db) (sqlite3:first-result db "SELECT count(id) FROM tests WHERE state in ('RUNNING','LAUNCHED','REMOTEHOSTSTART') AND run_id=? AND NOT (uname = 'n/a' AND item_path = '');" run-id)))) +;; For a given testname how many items are running? Used to determine +;; probability for regenerating html +;; +(define (db:get-count-tests-running-for-testname dbstruct run-id testname) + (db:with-db + dbstruct + run-id + #f + (lambda (db) + (sqlite3:first-result + db + "SELECT count(id) FROM tests WHERE state in ('RUNNING','LAUNCHED','REMOTEHOSTSTART') AND run_id=? AND NOT (uname = 'n/a' AND item_path = '') AND testname=?;" run-id testname)))) + (define (db:get-count-tests-running-in-jobgroup dbstruct run-id jobgroup) (let* ((dbdat (db:get-db dbstruct #f)) (db (db:dbdat-get-db dbdat))) (if (not jobgroup) 0 ;; @@ -2237,11 +2260,11 @@ (sqlite3:first-result db (conc "SELECT count(id) FROM tests WHERE state in ('RUNNING','LAUNCHED','REMOTEHOSTSTART') AND testname in ('" (string-intersperse testnames "','") "') AND NOT (uname = 'n/a' AND item_path='');")) ;; should this include the (uname = 'n/a' ...) ??? - 0))))))) + ))))))) ;; DEBUG FIXME - need to merge this v.155 query correctly ;; AND testname in (SELECT testname FROM test_meta WHERE jobgroup=?) ;; AND NOT (uname = 'n/a' AND item_path = '');" ;; done with run when: Index: db_records.scm ================================================================== --- db_records.scm +++ db_records.scm @@ -89,10 +89,14 @@ ;; (define-inline (db:test-get-pass_count vec) (vector-ref vec 15)) ;; (define-inline (db:test-get-fail_count vec) (vector-ref vec 16)) (define-inline (db:test-get-fullname vec) (conc (db:test-get-testname vec) "/" (db:test-get-item-path vec))) +;; replace runs:make-full-test-name with this routine +(define (db:test-make-full-name testname itempath) + (if (equal? itempath "") testname (conc testname "/" itempath))) + (define-inline (db:test-get-first_err vec) (printable (vector-ref vec 15))) (define-inline (db:test-get-first_warn vec) (printable (vector-ref vec 16))) (define-inline (db:test-set-cpuload! vec val)(vector-set! vec 7 val)) (define-inline (db:test-set-diskfree! vec val)(vector-set! vec 8 val)) @@ -198,10 +202,12 @@ (define-inline (tdb:steps-table-get-stepname vec) (vector-ref vec 0)) (define-inline (tdb:steps-table-get-start vec) (vector-ref vec 1)) (define-inline (tdb:steps-table-get-end vec) (vector-ref vec 2)) (define-inline (tdb:steps-table-get-status vec) (vector-ref vec 3)) (define-inline (tdb:steps-table-get-runtime vec) (vector-ref vec 4)) +(define-inline (tdb:steps-table-get-log-file vec) (vector-ref vec 5)) + (define-inline (tdb:step-stable-set-stepname! vec val)(vector-set! vec 0 val)) (define-inline (tdb:step-stable-set-start! vec val)(vector-set! vec 1 val)) (define-inline (tdb:step-stable-set-end! vec val)(vector-set! vec 2 val)) (define-inline (tdb:step-stable-set-status! vec val)(vector-set! vec 3 val)) (define-inline (tdb:step-stable-set-runtime! vec val)(vector-set! vec 4 val)) Index: dcommon.scm ================================================================== --- dcommon.scm +++ dcommon.scm @@ -98,10 +98,19 @@ ;; Look up test-ids by (key1 key2 ... testname [itempath]) (dboard:data-set-path-test-ids! *data* (make-hash-table)) ;; Look up run-ids by ?? (dboard:data-set-path-run-ids! *data* (make-hash-table)) + +;;====================================================================== +;; D O T F I L E +;;====================================================================== + +(define (dcommon:write-dotfile fname dat) + (with-output-to-file fname + (lambda () + (pp dat)))) ;;====================================================================== ;; TARGET AND PATTERN MANIPULATIONS ;;====================================================================== @@ -543,25 +552,32 @@ ;; The main menu (define (dcommon:main-menu) (iup:menu ;; a menu is a special attribute to a dialog (think Gnome putting the menu at screen top) (iup:menu-item "Files" (iup:menu ;; Note that you can use either #:action or action: for options - (iup:menu-item "Open" action: (lambda (obj) - (iup:show (iup:file-dialog)) - (print "File->open " obj))) - (iup:menu-item "Save" #:action (lambda (obj)(print "File->save " obj))) - (iup:menu-item "Exit" #:action (lambda (obj)(exit))))) + (iup:menu-item "Open" action: (lambda (obj) + (let* ((area-name (iup:textbox #:expand "HORIZONTAL")) + (fd (iup:file-dialog #:dialogtype "DIR")) + (top (iup:show fd #:modal? "YES"))) + (iup:attribute-set! source-tb "VALUE" + (iup:attribute fd "VALUE")) + (iup:destroy! fd)))) + ;; (lambda (obj) + ;; (iup:show (iup:file-dialog)) + ;; (print "File->open " obj))) + (iup:menu-item "Save" #:action (lambda (obj)(print "File->save " obj))) + (iup:menu-item "Exit" #:action (lambda (obj)(exit))))) (iup:menu-item "Tools" (iup:menu - (iup:menu-item "Create new blah" #:action (lambda (obj)(print "Tools->new blah"))) - ;; (iup:menu-item "Show dialog" #:action (lambda (obj) - ;; (show message-window - ;; #:modal? #t - ;; ;; set positon using coordinates or center, start, top, left, end, bottom, right, parent-center, current - ;; ;; #:x 'mouse - ;; ;; #:y 'mouse - ;; ) - )))) + (iup:menu-item "Create new blah" #:action (lambda (obj)(print "Tools->new blah"))) + ;; (iup:menu-item "Show dialog" #:action (lambda (obj) + ;; (show message-window + ;; #:modal? #t + ;; ;; set positon using coordinates or center, start, top, left, end, bottom, right, parent-center, current + ;; ;; #:x 'mouse + ;; ;; #:y 'mouse + ;; ) + )))) ;;====================================================================== ;; CANVAS STUFF FOR TESTS ;;====================================================================== @@ -648,99 +664,10 @@ ;;====================================================================== ;; S T E P S ;;====================================================================== -;; CHECK - WAS THIS ADDED OR REMOVED? MANUAL MERGE WITH API STUFF!!! -;; -;; get a pretty table to summarize steps -;; -(define (dcommon:process-steps-table steps);; db test-id #!key (work-area #f)) -;; (let ((steps (db:get-steps-for-test db test-id work-area: work-area))) - ;; organise the steps for better readability - (let ((res (make-hash-table))) - (for-each - (lambda (step) - (debug:print 6 "step=" step) - (let ((record (hash-table-ref/default - res - (tdb:step-get-stepname step) - ;; stepname start end status Duration Logfile - (vector (tdb:step-get-stepname step) "" "" "" "" "")))) - (debug:print 6 "record(before) = " record - "\nid: " (tdb:step-get-id step) - "\nstepname: " (tdb:step-get-stepname step) - "\nstate: " (tdb:step-get-state step) - "\nstatus: " (tdb:step-get-status step) - "\ntime: " (tdb:step-get-event_time step)) - (case (string->symbol (tdb:step-get-state step)) - ((start)(vector-set! record 1 (tdb:step-get-event_time step)) - (vector-set! record 3 (if (equal? (vector-ref record 3) "") - (tdb:step-get-status step))) - (if (> (string-length (tdb:step-get-logfile step)) - 0) - (vector-set! record 5 (tdb:step-get-logfile step)))) - ((end) - (vector-set! record 2 (any->number (tdb:step-get-event_time step))) - (vector-set! record 3 (tdb:step-get-status step)) - (vector-set! record 4 (let ((startt (any->number (vector-ref record 1))) - (endt (any->number (vector-ref record 2)))) - (debug:print 4 "record[1]=" (vector-ref record 1) - ", startt=" startt ", endt=" endt - ", get-status: " (tdb:step-get-status step)) - (if (and (number? startt)(number? endt)) - (seconds->hr-min-sec (- endt startt)) "-1"))) - (if (> (string-length (tdb:step-get-logfile step)) - 0) - (vector-set! record 5 (tdb:step-get-logfile step)))) - (else - (vector-set! record 2 (tdb:step-get-state step)) - (vector-set! record 3 (tdb:step-get-status step)) - (vector-set! record 4 (tdb:step-get-event_time step)))) - (hash-table-set! res (tdb:step-get-stepname step) record) - (debug:print 6 "record(after) = " record - "\nid: " (tdb:step-get-id step) - "\nstepname: " (tdb:step-get-stepname step) - "\nstate: " (tdb:step-get-state step) - "\nstatus: " (tdb:step-get-status step) - "\ntime: " (tdb:step-get-event_time step)))) - ;; (else (vector-set! record 1 (tdb:step-get-event_time step))) - (sort steps (lambda (a b) - (cond - ((< (tdb:step-get-event_time a)(tdb:step-get-event_time b)) #t) - ((eq? (tdb:step-get-event_time a)(tdb:step-get-event_time b)) - (< (tdb:step-get-id a) (tdb:step-get-id b))) - (else #f))))) - res)) - -(define (dcommon:get-compressed-steps dbstruct run-id test-id) - (let* ((steps-data (db:get-steps-for-test dbstruct run-id test-id)) - (comprsteps (dcommon:process-steps-table steps-data))) ;; (open-run-close db:get-steps-table #f test-id work-area: work-area))) - (map (lambda (x) - ;; take advantage of the \n on time->string - (vector - (vector-ref x 0) - (let ((s (vector-ref x 1))) - (if (number? s)(seconds->time-string s) s)) - (let ((s (vector-ref x 2))) - (if (number? s)(seconds->time-string s) s)) - (vector-ref x 3) ;; status - (vector-ref x 4) - (vector-ref x 5))) ;; time delta - (sort (hash-table-values comprsteps) - (lambda (a b) - (let ((time-a (vector-ref a 1)) - (time-b (vector-ref b 1))) - (if (and (number? time-a)(number? time-b)) - (if (< time-a time-b) - #t - (if (eq? time-a time-b) - (string (conc (vector-ref a 2)) - (conc (vector-ref b 2))) - #f)) - (string (conc time-a)(conc time-b))))))))) - (define (dcommon:populate-steps teststeps steps-matrix) (let ((max-row 0)) (if (null? teststeps) (iup:attribute-set! steps-matrix "CLEARVALUE" "CONTENTS") (let loop ((hed (car teststeps)) Index: docs/manual/megatest_manual.html ================================================================== --- docs/manual/megatest_manual.html +++ docs/manual/megatest_manual.html @@ -1012,10 +1012,22 @@
[configf:settings trim-trailing-spaces yes]
+