Index: Makefile ================================================================== --- Makefile +++ Makefile @@ -1,7 +1,7 @@ -PREFIX=. +PREFIX=$(PWD) CSCOPTS= INSTALL=install SRCFILES = common.scm items.scm launch.scm \ ods.scm runconfig.scm server.scm configf.scm \ db.scm keys.scm margs.scm megatest-version.scm \ @@ -10,19 +10,28 @@ GUISRCF = dashboard.scm dashboard-tests.scm dashboard-guimonitor.scm dashboard-main.scm OFILES = $(SRCFILES:%.scm=%.o) GOFILES = $(GUISRCF:%.scm=%.o) -HELPERS=$(addprefix $(PREFIX)/bin/,mt_laststep mt_runstep mt_ezstep) +ADTLSCR=mt_laststep mt_runstep mt_ezstep +HELPERS=$(addprefix $(PREFIX)/bin/,$(ADTLSCR)) +DEPLOYHELPERS=$(addprefix $(DEPLOYTARG)/,$(ADTLSCR)) MTESTHASH=$(shell fossil info|grep checkout:| awk '{print $$2}') all : mtest dboard mtest: $(OFILES) megatest.o csc $(CSCOPTS) $(OFILES) megatest.o -o mtest dboard : $(OFILES) $(GOFILES) csc $(OFILES) $(GOFILES) -o dboard + +$(DEPLOYTARG)/megatest : $(OFILES) megatest.o + csc -deployed $(CSCOPTS) $(OFILES) megatest.o -o $(DEPLOYTARG)/megatest + +$(DEPLOYTARG)/dashboard : $(OFILES) $(GOFILES) + csc -deployed $(OFILES) $(GOFILES) -o $(DEPLOYTARG)/dashboard + # Special dependencies for the includes tests.o db.o launch.o runs.o dashboard-tests.o dashboard-guimonitor.o dashboard-main.o monitor.o dashboard.o megatest.o : db_records.scm tests.o runs.o dashboard.o dashboard-tests.o dashboard-main.o : run_records.scm db.o ezsteps.o keys.o launch.o megatest.o monitor.o runs-for-ref.o runs.o tests.o : key_records.scm @@ -46,30 +55,46 @@ chmod a+x $(PREFIX)/bin/megatest $(HELPERS) : utils/mt_* $(INSTALL) $< $@ chmod a+x $@ + +$(DEPLOYHELPERS) : utils/mt_* + $(INSTALL) $< $@ + chmod a+X $@ $(PREFIX)/bin/nbfake : utils/nbfake $(INSTALL) $< $@ chmod a+x $@ $(PREFIX)/bin/nbfind : utils/nbfind $(INSTALL) $< $@ chmod a+x $@ + +$(DEPLOYTARG)/nbfake : utils/nbfake + $(INSTALL) $< $@ + chmod a+x $@ + +$(DEPLOYTARG)/nbfind : utils/nbfind + $(INSTALL) $< $@ + chmod a+x $@ + # install dashboard as dboard so wrapper script can be called dashboard $(PREFIX)/bin/dboard : dboard $(FILES) $(INSTALL) dboard $(PREFIX)/bin/dboard utils/mk_wrapper $(PREFIX) dboard > $(PREFIX)/bin/dashboard chmod a+x $(PREFIX)/bin/dashboard install : bin $(PREFIX)/bin/mtest $(PREFIX)/bin/megatest $(PREFIX)/bin/dboard $(PREFIX)/bin/dashboard $(HELPERS) $(PREFIX)/bin/nbfake $(PREFIX)/bin/nbfind + +deploy : $(DEPLOYTARG)/megatest $(DEPLOYTARG)/dashboard $(DEPLOYHELPERS) $(DEPLOYTARG)/nbfake $(DEPLOYTARG)/nbfind + bin : mkdir -p $(PREFIX)/bin test: tests/tests.scm cd tests;csi -I .. -b -n tests.scm clean : rm -f $(OFILES) $(GOFILES) megatest dboard dboard.o megatest.o Index: common.scm ================================================================== --- common.scm +++ common.scm @@ -38,23 +38,53 @@ (define *waiting-queue* (make-hash-table)) (define *test-meta-updated* (make-hash-table)) (define *globalexitstatus* 0) ;; attempt to work around possible thread issues (define *passnum* 0) ;; when running track calls to run-tests or similar +;; SERVER +(define *my-client-signature* #f) (define *rpc:listener* #f) ;; if set up for server communication this will hold the tcp port (define *runremote* #f) ;; if set up for server communication this will hold (define *last-db-access* (current-seconds)) ;; update when db is accessed via server (define *max-cache-size* 0) +(define *logged-in-clients* (make-hash-table)) +(define *client-non-blocking-mode* #f) +(define *server-id* #f) +(define *server-info* #f) +(define *time-to-exit* #f) +(define *received-response* #f) +(define *default-numtries* 10) + (define *target* (make-hash-table)) ;; cache the target here; target is keyval1/keyval2/.../keyvalN (define *keys* (make-hash-table)) ;; cache the keys here (define *keyvals* (make-hash-table)) (define *toptest-paths* (make-hash-table)) ;; cache toptest path settings here (define *test-paths* (make-hash-table)) ;; cache test-id to test run paths here (define *test-ids* (make-hash-table)) ;; cache run-id, testname, and item-path => test-id (define *test-info* (make-hash-table)) ;; cache the test info records, update the state, status, run_duration etc. from testdat.db -(define *run-info-cache* (make-hash-table)) ;; run info is stable, no need to reget +(define *run-info-cache* (make-hash-table)) ;; run info is stable, no need to reget + +;; Debugging stuff +(define *verbosity* 1) +(define *logging* #f) + +;; Awful. Please FIXME +(define *env-vars-by-run-id* (make-hash-table)) +(define *current-run-name* #f) + +(define (common:clear-caches) + (set! *target* (make-hash-table)) + (set! *keys* (make-hash-table)) + (set! *keyvals* (make-hash-table)) + (set! *toptest-paths* (make-hash-table)) + (set! *test-paths* (make-hash-table)) + (set! *test-ids* (make-hash-table)) + (set! *test-info* (make-hash-table)) + (set! *run-info-cache* (make-hash-table)) + (set! *env-vars-by-run-id* (make-hash-table)) + (set! *test-id-cache* (make-hash-table))) ;; Debugging stuff (define *verbosity* 1) (define *logging* #f) @@ -67,10 +97,19 @@ (if res (cadr res)(if (null? default) #f (car default))))) ;;====================================================================== ;; Misc utils ;;====================================================================== + +;; one-of args defined +(define (args-defined? . param) + (let ((res #f)) + (for-each + (lambda (arg) + (if (args:get-arg arg)(set! res #t))) + param) + res)) ;; convert stuff to a number if possible (define (any->number val) (cond ((number? val) val) @@ -81,22 +120,34 @@ (define (any->number-if-possible val) (let ((num (any->number val))) (if num num val))) (define (patt-list-match item patts) - (debug:print 8 "INFO: patt-list-match item=" item " patts=" patts) + (debug:print-info 8 "patt-list-match item=" item " patts=" patts) (if (and item patts) ;; here we are filtering for matches with -itempatt (let ((res #f)) ;; look through all the item-patts if defined, format is patt1,patt2,patt3 ... wildcard is % (for-each (lambda (patt) (let ((modpatt (string-substitute "%" ".*" patt #t))) - (debug:print 10 "INFO: patt " patt " modpatt " modpatt) + (debug:print-info 10 "patt " patt " modpatt " modpatt) (if (string-match (regexp modpatt) item) (set! res #t)))) (string-split patts ",")) res) #t)) + +;; (map print (map car (hash-table->alist (read-config "runconfigs.config" #f #t)))) +(define (common:get-runconfig-targets) + (sort (map car (hash-table->alist + (read-config "runconfigs.config" + #f #t))) string (length debugvals) 1) + (map string->number debugvals) + (string->number (car debugvals))))) + ((args:get-arg "-v") 2) + ((args:get-arg "-q") 0) + (else 1))) + +;; check verbosity, #t is ok +(define (debug:check-verbosity verbosity vstr) + (if (not (or (number? verbosity) + (list? verbosity))) + (begin + (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*)))) + +(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) + (if (or (args:get-arg "-debug") + (not (getenv "MT_DEBUG_MODE"))) + (setenv "MT_DEBUG_MODE" (if (list? *verbosity*) + (string-intersperse (map conc *verbosity*) ",") + (conc *verbosity*)))))) + + +(define (debug:print n . params) + (if (debug:debug-mode n) + (with-output-to-port (current-error-port) + (lambda () + (apply print params) + (if *logging* (apply db:log-event params)))))) + +(define (debug:print-info n . params) + (if (debug:debug-mode n) + (with-output-to-port (current-error-port) + (lambda () + (let ((res #f));; (format#format #f "INFO:~2d ~a" n (apply conc params)))) + (apply print "INFO: (" n ") " params) ;; res) + (if *logging* (db:log-event res))))))) ;; if a value is printable (i.e. string or number) return the value ;; else return an empty string (define-inline (printable val) (if (or (number? val)(string? val)) val "")) Index: configf.scm ================================================================== --- configf.scm +++ configf.scm @@ -99,11 +99,11 @@ (status (cadr output))) (if (equal? status 0) (let ((outres (string-intersperse res "\n"))) - (debug:print 4 "INFO: shell result:\n" outres) + (debug:print-info 4 "shell result:\n" outres) outres) (begin (with-output-to-port (current-error-port) (print "ERROR: " cmd " returned bad exit code " status)) "")))) @@ -113,49 +113,52 @@ (let ((targ (or (args:get-arg "-reqtarg")(args:get-arg "-target")))) (if targ (config-lookup config targ var) #f))) -(define-inline (configf:read-line p ht) - (configf:process-line (read-line p) ht)) +(define-inline (configf:read-line p ht allow-processing) + (if (and allow-processing + (not (eq? allow-processing 'return-string))) + (configf:process-line (read-line p) ht) + (read-line p))) ;; 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)) - (debug:print 4 "INFO: read-config " path " allow-system " allow-system " environ-patt " environ-patt " curr-section: " curr-section " sections: " sections) + (debug:print-info 4 "read-config " path " allow-system " allow-system " environ-patt " environ-patt " curr-section: " curr-section " sections: " sections) (if (not (file-exists? path)) (begin - (debug:print 4 "INFO: read-config - file not found " path " current path: " (current-directory)) + (debug:print-info 4 "read-config - file not found " path " current path: " (current-directory)) (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)) ;; (read-line inp)) + (let loop ((inl (configf:read-line inp res allow-system)) ;; (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 8 "INFO: curr-section-name: " curr-section-name " var-flag: " var-flag "\n inl: \"" inl "\"") + (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 res) (regex-case inl - (configf:comment-rx _ (loop (configf:read-line inp res) curr-section-name #f #f)) - (configf:blank-l-rx _ (loop (configf:read-line inp res) curr-section-name #f #f)) + (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:include-rx ( x include-file ) (let ((curr-dir (current-directory)) (conf-dir (pathname-directory path))) (if conf-dir (change-directory conf-dir)) (read-config include-file res allow-system environ-patt: environ-patt curr-section: curr-section-name sections: sections) (change-directory curr-dir) - (loop (configf:read-line inp res) curr-section-name #f #f))) - (configf:section-rx ( x section-name ) (loop (configf:read-line inp res) + (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) ;; 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)) @@ -163,11 +166,11 @@ (let ((alist (hash-table-ref/default res curr-section-name '())) (val-proc (lambda () (let* ((cmdres (cmd-run->list cmd)) (status (cadr cmdres)) (res (car cmdres))) - (debug:print 4 "INFO: " inl "\n => " (string-intersperse res "\n")) + (debug:print-info 4 "" inl "\n => " (string-intersperse res "\n")) (if (not (eq? status 0)) (begin (debug:print 0 "ERROR: problem with " inl ", return code " status " output: " cmdres) (exit 1))) @@ -179,25 +182,25 @@ key (case allow-system ((return-procs) val-proc) ((return-string) cmd) (else (val-proc))))) - (loop (configf:read-line inp res) curr-section-name #f #f)) - (loop (configf:read-line inp res) 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) 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 6 "INFO: read-config env setting, envar: " envar " realval: " realval " val: " val " key: " key " curr-section-name: " curr-section-name) + (debug:print-info 6 "read-config env setting, envar: " envar " realval: " realval " val: " val " key: " key " curr-section-name: " curr-section-name) (if envar (begin - ;; (debug:print 4 "INFO: read-config key=" key ", val=" val ", realval=" realval) + ;; (debug:print-info 4 "read-config key=" key ", val=" val ", realval=" realval) (setenv key realval))) (hash-table-set! res curr-section-name (config:assoc-safe-add alist key realval)) - (loop (configf:read-line inp res) curr-section-name key #f))) + (loop (configf:read-line inp res allow-system) 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" @@ -207,15 +210,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) curr-section-name var-flag (if lead lead whsp))) - (loop (configf:read-line inp res) curr-section-name #f #f)))) + (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)))) (else (debug:print 0 "ERROR: problem parsing " path ",\n \"" inl "\"") (set! var-flag #f) - (loop (configf:read-line inp res) curr-section-name #f #f)))))))) + (loop (configf:read-line inp res allow-system) 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-guimonitor.scm ================================================================== --- dashboard-guimonitor.scm +++ dashboard-guimonitor.scm @@ -172,18 +172,19 @@ (iup:attribute-set! tabtop "TABTITLE1" "Collateral") (iup:attribute-set! tabtop "TABTITLE2" "Fossil") (iup:attribute-set! tabtop "TABTITLE3" "Tools") tabtop)))) -(on-exit (lambda () - (let ((tdb (tasks:open-db))) - ;; (print "On-exit called") - (tasks:remove-monitor-record tdb) - (sqlite3:finalize! tdb)))) +;; BUG: Remember to re-instate this!!!! +;; (on-exit (lambda () +;; (let ((tdb (tasks:open-db))) +;; ;; (print "On-exit called") +;; (tasks:remove-monitor-record tdb) +;; (sqlite3:finalize! tdb)))) (define (gui-monitor db) - (let ((keys (rdb:get-keys db)) + (let ((keys (db:get-keys db)) (tdb (tasks:open-db))) (tasks:register-monitor db tdb) ;;; let the other monitors know we are here (control-panel db tdb keys) ;(tasks:remove-monitor-record db) ;(sqlite3:finalize! db) Index: dashboard-main.scm ================================================================== --- dashboard-main.scm +++ dashboard-main.scm @@ -210,11 +210,16 @@ (define (main-panel) (iup:dialog #:title "Menu Test" #:menu (main-menu) - (let ((tabtop (iup:tabs (mtest) (rconfig) (tests) (runs)))) - (iup:attribute-set! tabtop "TABTITLE0" "Megatest") - (iup:attribute-set! tabtop "TABTITLE1" "Runconfigs") - (iup:attribute-set! tabtop "TABTITLE2" "Tests") - (iup:attribute-set! tabtop "TABTITLE3" "Runs") + (let ((tabtop (iup:tabs + (runs) + (mtest) + (rconfig) + (tests) + ))) + (iup:attribute-set! tabtop "TABTITLE0" "Runs") + (iup:attribute-set! tabtop "TABTITLE3" "Tests") + (iup:attribute-set! tabtop "TABTITLE1" "megatest.config") + (iup:attribute-set! tabtop "TABTITLE2" "runconfigs.config") tabtop))) Index: dashboard-tests.scm ================================================================== --- dashboard-tests.scm +++ dashboard-tests.scm @@ -11,11 +11,11 @@ ;;====================================================================== ;; Test info panel ;;====================================================================== -(use format) +(use format fmt) (require-library iup) (import (prefix iup iup:)) (use canvas-draw) @@ -81,10 +81,14 @@ ))))) ;;====================================================================== ;; Test meta panel ;;====================================================================== + +(define (test-meta-panel-get-description testmeta) + (fmt #f (with-width 40 (wrap-lines (db:testmeta-get-description testmeta))))) + (define (test-meta-panel testmeta store-meta) (iup:frame #:title "Test Meta Data" ; #:expand "YES" (iup:hbox ; #:expand "YES" (apply iup:vbox ; #:expand "YES" @@ -111,12 +115,13 @@ (lambda (testmeta)(db:testmeta-get-reviewed testmeta))) (store-meta "tags" (iup:label (db:testmeta-get-tags testmeta) #:expand "HORIZONTAL") (lambda (testmeta)(db:testmeta-get-tags testmeta))) (store-meta "description" - (iup:label (db:testmeta-get-description testmeta) #:size "x50"); #:expand "HORIZONTAL") - (lambda (testmeta)(db:testmeta-get-description testmeta))) + (iup:label (test-meta-panel-get-description testmeta) #:size "x50"); #:expand "HORIZONTAL") + (lambda (testmeta) + (test-meta-panel-get-description testmeta))) ))))) ;;====================================================================== ;; Run info panel @@ -128,16 +133,18 @@ (apply iup:vbox ; #:expand "YES" (append (map (lambda (keyval) (iup:label (conc (car keyval) " ") ; #:expand "HORIZONTAL" )) keydat) - (list (iup:label "runname ")))) + (list (iup:label "runname ")(iup:label "run-id")))) (apply iup:vbox (append (map (lambda (keyval) (iup:label (cadr keyval) #:expand "HORIZONTAL")) keydat) - (list (iup:label runname)(iup:label "" #:expand "VERTICAL"))))))) + (list (iup:label runname) + (iup:label (conc (db:test-get-run_id testdat))) + (iup:label "" #:expand "VERTICAL"))))))) ;;====================================================================== ;; Host info panel ;;====================================================================== (define (host-info-panel testdat store-label) @@ -197,21 +204,21 @@ (iup:frame #:title "Set fields" (iup:vbox (iup:hbox (iup:label "Comment:") (iup:textbox #:action (lambda (val a b) - (open-run-close db:test-set-state-status-by-id *db* test-id #f #f b) + (open-run-close db:test-set-state-status-by-id #f test-id #f #f b) (set! newcomment b)) #:value (db:test-get-comment testdat) #:expand "HORIZONTAL")) (apply iup:hbox (iup:label "STATE:" #:size "30x") (let* ((btns (map (lambda (state) (let ((btn (iup:button state #:expand "HORIZONTAL" #:size "50x" #:font "Courier New, -10" #:action (lambda (x) - (open-run-close db:test-set-state-status-by-id *db* test-id state #f #f) + (open-run-close db:test-set-state-status-by-id #f test-id state #f #f) (db:test-set-state! testdat state))))) btn)) (list "COMPLETED" "NOT_STARTED" "RUNNING" "REMOTEHOSTSTART" "KILLED" "KILLREQ")))) (vector-set! *state-status* 0 (lambda (state color) @@ -227,11 +234,11 @@ (iup:label "STATUS:" #:size "30x") (let* ((btns (map (lambda (status) (let ((btn (iup:button status #:expand "HORIZONTAL" #:size "50x" #:font "Courier New, -10" #:action (lambda (x) - (open-run-close db:test-set-state-status-by-id *db* test-id #f status #f) + (open-run-close db:test-set-state-status-by-id #f test-id #f status #f) (db:test-set-status! testdat status))))) btn)) (list "PASS" "WARN" "FAIL" "CHECK" "n/a" "WAIVED")))) (vector-set! *state-status* 1 (lambda (status color) @@ -258,22 +265,22 @@ (if (not testdat) (begin (debug:print 0 "ERROR: No test data found for test " test-id ", exiting") (exit 1)) (let* ((run-id (if testdat (db:test-get-run_id testdat) #f)) - (keydat (if testdat (open-run-close db:get-key-val-pairs db run-id) #f)) - (rundat (if testdat (open-run-close db:get-run-info db run-id) #f)) + (keydat (if testdat (open-run-close db:get-key-val-pairs #f run-id) #f)) + (rundat (if testdat (open-run-close db:get-run-info #f run-id) #f)) (runname (if testdat (db:get-value-by-header (db:get-row rundat) (db:get-header rundat) "runname") #f)) ;(teststeps (if testdat (db:get-steps-for-test db test-id) #f)) (logfile "/this/dir/better/not/exist") (rundir logfile) (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 (open-run-close db:testmeta-get-record db testname))) + (let ((tm (open-run-close db:testmeta-get-record #f testname))) (if tm tm (make-db:testmeta))) (make-db:testmeta))) (keystring (string-intersperse (map (lambda (keyval) @@ -298,11 +305,11 @@ (refreshdat (lambda () (let* ((curr-mod-time (file-modification-time db-path)) (need-update (or (and (> curr-mod-time db-mod-time) (> (current-seconds) (+ last-update 2))) ;; every two seconds if db touched request-update)) - (newtestdat (if need-update (open-run-close db:get-test-info-by-id db test-id)))) + (newtestdat (if need-update (open-run-close db:get-test-info-by-id #f test-id)))) (cond ((and need-update newtestdat) (set! testdat newtestdat) (set! teststeps (open-run-close db:get-steps-for-test db test-id)) (set! logfile (conc (db:test-get-rundir testdat) "/" (db:test-get-final_logf testdat))) @@ -343,22 +350,22 @@ (let ((cmd (iup:attribute command-text-box "VALUE"))) (system (conc cmd " &")))))) (run-test (lambda (x) (iup:attribute-set! command-text-box "VALUE" - (conc "xterm -geometry 180x20 -e \"megatest -runtests " testname " -target " keystring " :runname " runname - " -itempatt " (if (equal? item-path "") - "%" - item-path) + (conc "xterm -geometry 180x20 -e \"megatest -target " keystring " :runname " runname + " -runtests " (conc testname "/" (if (equal? item-path "") + "%" + item-path)) ";echo Press any key to continue;bash -c 'read -n 1 -s'\"")))) (remove-test (lambda (x) (iup:attribute-set! command-text-box "VALUE" - (conc "xterm -geometry 180x20 -e \"megatest -remove-runs -target " keystring " :runname " runname " -testpatt " testname " -itempatt " - (if (equal? item-path "") - "%" - item-path) + (conc "xterm -geometry 180x20 -e \"megatest -remove-runs -target " keystring " :runname " runname + " -testpatt " (conc testname "/" (if (equal? item-path "") + "%" + item-path)) " -v;echo Press any key to continue;bash -c 'read -n 1 -s'\""))))) (cond ((not testdat)(begin (print "ERROR: bad test info for " test-id)(exit 1))) ((not rundat)(begin (print "ERROR: found test info but there is a problem with the run info for " run-id)(exit 1))) (else @@ -423,12 +430,17 @@ (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)) - (< time-a time-b) - #t)))))) + (if (< time-a time-b) + #t + (if (eq? time-a time-b) + (string (define *alltestnamelst* '()) (define *searchpatts* (make-hash-table)) (define *num-runs* 8) -(define *tot-run-count* (open-run-close db:get-num-runs *db* "%")) +(define *tot-run-count* (open-run-close db:get-num-runs #f "%")) ;; (define *tot-run-count* (db:get-num-runs *db* "%")) (define *last-update* (current-seconds)) (define *num-tests* 15) (define *start-run-offset* 0) (define *start-test-offset* 0) @@ -114,20 +115,11 @@ (define *db-file-path* (conc *toppath* "/megatest.db")) (define *tests-sort-reverse* #f) (define *hide-empty-runs* #f) -(define *verbosity* (cond - ((string? (args:get-arg "-debug"))(string->number (args:get-arg "-debug"))) - ((args:get-arg "-v") 2) - ((args:get-arg "-q") 0) - (else 1))) - -(if (not (number? *verbosity*)) - (begin - (print "ERROR: Invalid debug value " (args:get-arg "-debug")) - (exit))) +(debug:setup) (define uidat #f) (define-inline (dboard:uidat-get-keycol vec)(vector-ref vec 0)) (define-inline (dboard:uidat-get-lftcol vec)(vector-ref vec 1)) @@ -161,38 +153,39 @@ (c2 (map string->number (string-split color2))) (delta (map (lambda (a b)(abs (- a b))) c1 c2))) (null? (filter (lambda (x)(> x 3)) delta)))) ;; keypatts: ( (KEY1 "abc%def")(KEY2 "%") ) -(define (update-rundat runnamepatt numruns testnamepatt itemnamepatt keypatts) +(define (update-rundat runnamepatt numruns testnamepatt keypatts) (let ((modtime (file-modification-time *db-file-path*))) (if (or (and (> modtime *last-db-update-time*) (> (current-seconds)(+ *last-db-update-time* 5))) (> *delayed-update* 0)) (begin - (debug:print 4 "INFO: update-rundat runnamepatt: " runnamepatt " numruns: " numruns " testnamepatt: " testnamepatt " itemnamepatt: " itemnamepatt " keypatts: " keypatts) + (debug:print-info 4 "update-rundat runnamepatt: " runnamepatt " numruns: " numruns " testnamepatt: " testnamepatt " keypatts: " keypatts) (set! *please-update-buttons* #t) (set! *last-db-update-time* modtime) (set! *delayed-update* (- *delayed-update* 1)) - (let* ((allruns (open-run-close db:get-runs *db* runnamepatt numruns ;; (+ numruns 1) ;; (/ numruns 2)) + (let* ((allruns (open-run-close db:get-runs #f runnamepatt numruns ;; (+ numruns 1) ;; (/ numruns 2)) *start-run-offset* keypatts)) (header (db:get-header allruns)) (runs (db:get-rows allruns)) (result '()) (maxtests 0) (states (hash-table-keys *state-ignore-hash*)) (statuses (hash-table-keys *status-ignore-hash*))) + ;; (thread-sleep! 0.1) ;; give some time to other threads (debug:print 6 "update-rundat, got " (length runs) " runs") (if (> (+ *last-update* 300) (current-seconds)) ;; every five minutes (begin (set! *last-update* (current-seconds)) (set! *tot-run-count* (length runs)))) ;; (rdb:get-num-runs *db* runnamepatt)))) (for-each (lambda (run) (let* ((run-id (db:get-value-by-header run header "id")) - (tests (let ((tsts (open-run-close db:get-tests-for-run *db* run-id testnamepatt itemnamepatt states statuses))) + (tests (let ((tsts (open-run-close db:get-tests-for-run #f run-id testnamepatt states statuses))) (if *tests-sort-reverse* (reverse tsts) tsts))) - (key-vals (open-run-close db:get-key-vals *db* run-id))) + (key-vals (open-run-close db:get-key-vals #f run-id))) (if (> (length tests) maxtests) (set! maxtests (length tests))) (if (or (not *hide-empty-runs*) ;; this reduces the data burden when set (not (null? tests))) (set! result (cons (vector run tests key-vals) result))))) @@ -429,18 +422,19 @@ (iup:hbox (iup:vbox (iup:frame #:title "filter test and items" (iup:hbox - (iup:textbox #:size "60x15" #:fontsize "10" #:value "%" + (iup:textbox #:size "120x15" #:fontsize "10" #:value "%" #:action (lambda (obj unk val) (mark-for-update) (update-search "test-name" val))) - (iup:textbox #:size "60x15" #:fontsize "10" #:value "%" - #:action (lambda (obj unk val) - (mark-for-update) - (update-search "item-name" val))))) + ;;(iup:textbox #:size "60x15" #:fontsize "10" #:value "%" + ;; #:action (lambda (obj unk val) + ;; (mark-for-update) + ;; (update-search "item-name" val)) + )) (iup:vbox (iup:hbox (iup:button "Sort" #:action (lambda (obj) (set! *tests-sort-reverse* (not *tests-sort-reverse*)) (iup:attribute-set! obj "TITLE" (if *tests-sort-reverse* "+Sort" "-Sort")) @@ -498,13 +492,13 @@ ;; create the left most column for the run key names and the test names (set! lftlst (list (iup:hbox (iup:label) ;; (iup:valuator) (apply iup:vbox (map (lambda (x) - (let ((res (iup:hbox - (iup:label x #:size "40x15" #:fontsize "10") ;; #:expand "HORIZONTAL") - (iup:textbox #:size "60x15" #:fontsize "10" #:value "%" ;; #:expand "HORIZONTAL" + (let ((res (iup:hbox #:expand "HORIZONTAL" + (iup:label x #:size "x15" #:fontsize "10" #:expand "HORIZONTAL") + (iup:textbox #:size "x15" #:fontsize "10" #:value "%" #:expand "HORIZONTAL" #:action (lambda (obj unk val) (mark-for-update) (update-search x val)))))) (set! i (+ i 1)) res)) @@ -512,11 +506,11 @@ (let loop ((testnum 0) (res '())) (cond ((>= testnum ntests) ;; now lftlst will be an hbox with the test keys and the test name labels - (set! lftlst (append lftlst (list (iup:hbox + (set! lftlst (append lftlst (list (iup:hbox #:expand "HORIZONTAL" (iup:valuator #:valuechanged_cb (lambda (obj) (let ((val (string->number (iup:attribute obj "VALUE"))) (oldmax (string->number (iup:attribute obj "MAX"))) (newmax (* 10 (length *alltestnamelst*)))) (set! *please-update-buttons* #t) @@ -523,20 +517,21 @@ (set! *start-test-offset* (inexact->exact (round (/ val 10)))) (debug:print 6 "*start-test-offset* " *start-test-offset* " val: " val " newmax: " newmax " oldmax: " oldmax) (if (< val 10) (iup:attribute-set! obj "MAX" newmax)) )) - #:expand "YES" + #:expand "VERTICAL" #:orientation "VERTICAL") (apply iup:vbox (reverse res))))))) (else (let ((labl (iup:button "" #:flat "YES" #:alignment "ALEFT" ; #:image img1 ; #:impress img2 - #:size "100x15" + #:size "x15" + #:expand "HORIZONTAL" #:fontsize "10" #:action (lambda (obj) (mark-for-update) (toggle-hide testnum))))) ;; (iup:attribute obj "TITLE")))) (vector-set! lftcol testnum labl) @@ -551,11 +546,11 @@ ((>= keynum nkeys) (vector-set! header runnum keyvec) (set! hdrlst (cons (apply iup:vbox (reverse res)) hdrlst)) (loop (+ runnum 1) 0 (make-vector nkeys) '())) (else - (let ((labl (iup:label "" #:size "60x15" #:fontsize "10"))) ;; #:expand "HORIZONTAL" + (let ((labl (iup:label "" #:size "60x15" #:fontsize "10" #:expand "HORIZONTAL"))) ;; #:expand "HORIZONTAL" (vector-set! keyvec keynum labl) (loop runnum (+ keynum 1) keyvec (cons labl res)))))) ;; By here the hdrlst contains a list of vboxes containing nkeys labels (let loop ((runnum 0) (testnum 0) @@ -569,11 +564,11 @@ (loop (+ runnum 1) 0 (make-vector ntests) '())) (else (let* ((button-key (mkstr runnum testnum)) (butn (iup:button "" ;; button-key #:size "60x15" - ;; #:expand "HORIZONTAL" + #:expand "HORIZONTAL" #:fontsize "10" #:action (lambda (x) (let* ((toolpath (car (argv))) (buttndat (hash-table-ref *buttondat* button-key)) (test-id (db:test-get-id (vector-ref buttndat 3))) @@ -601,12 +596,12 @@ (if (or (args:get-arg "-rows") (get-environment-variable "DASHBOARDROWS" )) (begin (set! *num-tests* (string->number (or (args:get-arg "-rows") (get-environment-variable "DASHBOARDROWS")))) - (update-rundat "%" *num-runs* "%" "%" '())) - (set! *num-tests* (min (max (update-rundat "%" *num-runs* "%" "%" '()) 8) 20))) + (update-rundat "%" *num-runs* "%/%" '())) + (set! *num-tests* (min (max (update-rundat "%" *num-runs* "%/%" '()) 8) 20))) (define *tim* (iup:timer)) (define *ord* #f) (iup:attribute-set! *tim* "TIME" 300) (iup:attribute-set! *tim* "RUN" "YES") @@ -622,12 +617,12 @@ (define (run-update x) (update-buttons uidat *num-runs* *num-tests*) ;; (if (db:been-changed) (begin (update-rundat (hash-table-ref/default *searchpatts* "runname" "%") *num-runs* - (hash-table-ref/default *searchpatts* "test-name" "%") - (hash-table-ref/default *searchpatts* "item-name" "%") + (hash-table-ref/default *searchpatts* "test-name" "%/%") + ;; (hash-table-ref/default *searchpatts* "item-name" "%") (let ((res '())) (for-each (lambda (key) (if (not (equal? key "runname")) (let ((val (hash-table-ref/default *searchpatts* key #f))) (if val (set! res (cons (list key val) res)))))) @@ -641,11 +636,11 @@ (let ((runid (string->number (args:get-arg "-run")))) (if runid (begin (lambda (x) (on-exit (lambda () - (sqlite3:finalize! *db*))) + (if *db* (sqlite3:finalize! *db*)))) (open-run-close examine-run *db* runid))) (begin (print "ERROR: runid is not a number " (args:get-arg "-run")) (exit 1))))) ((args:get-arg "-test") Index: db.scm ================================================================== --- db.scm +++ db.scm @@ -11,15 +11,16 @@ ;;====================================================================== ;; Database access ;;====================================================================== -(require-extension (srfi 18) extras tcp rpc) -(import (prefix rpc rpc:)) +(require-extension (srfi 18) extras tcp) ;; rpc) +;; (import (prefix rpc rpc:)) -(use sqlite3 srfi-1 posix regex regex-case srfi-69 csv-xml) +(use sqlite3 srfi-1 posix regex regex-case srfi-69 csv-xml s11n md5 message-digest base64) (import (prefix sqlite3 sqlite3:)) +(import (prefix base64 base64:)) (declare (unit db)) (declare (uses common)) (declare (uses keys)) (declare (uses ods)) @@ -49,77 +50,85 @@ (else (debug:print 0 "ERROR: synchronous must be 0,1,2,OFF,NORMAL or FULL, you provided: " syncval) #f)))) (if val (begin - (debug:print 2 "INFO: Setting pragma synchronous to " val) + (debug:print-info 11 "db:set-sync, setting pragma synchronous to " val) (sqlite3:execute db (conc "PRAGMA synchronous = '" val "';")))))) (define (open-db) ;; (conc *toppath* "/megatest.db") (car *configinfo*))) + (if (not *toppath*) + (if (not (setup-for-run)) + (begin + (debug:print 0 "ERROR: Attempted to open db when not in megatest area. Exiting.") + (exit)))) (let* ((dbpath (conc *toppath* "/megatest.db")) ;; fname) (dbexists (file-exists? dbpath)) (db (sqlite3:open-database dbpath)) ;; (never-give-up-open-db dbpath)) (handler (make-busy-timeout (if (args:get-arg "-override-timeout") (string->number (args:get-arg "-override-timeout")) - 36000)))) ;; 136000))) - (debug:print 4 "INFO: dbpath=" dbpath) + 136000)))) ;; 136000))) ;; 136000 = 2.2 minutes + (debug:print-info 11 "open-db, dbpath=" dbpath " argv=" (argv)) (sqlite3:set-busy-handler! db handler) (if (not dbexists) (db:initialize db)) (db:set-sync db) db)) ;; keeping it around for debugging purposes only (define (open-run-close-no-exception-handling proc idb . params) - (let* ((db (if idb idb (open-db))) + (debug:print-info 11 "open-run-close-no-exception-handling START given a db=" (if idb "yes " "no ") ", params=" params) + (let* ((db (if idb + (if (procedure? idb) + (idb) + idb) + (open-db))) (res #f)) (set! res (apply proc db params)) (if (not idb)(sqlite3:finalize! db)) + (debug:print-info 11 "open-run-close-no-exception-handling END" ) res)) (define (open-run-close-exception-handling proc idb . params) - (let ((runner (lambda () - (let* ((db (if idb idb (open-db))) - (res #f)) - (set! res (apply proc db params)) - (if (not idb)(sqlite3:finalize! db)) - res)))) - (handle-exceptions - exn - (begin - (debug:print 0 "EXCEPTION: database probably overloaded?") - (debug:print 0 " " ((condition-property-accessor 'exn 'message) exn)) - (print-call-chain) - (thread-sleep! (random 120)) - (debug:print 0 "trying db call one more time....") - (runner)) - (runner)))) - -(define open-run-close open-run-close-exception-handling) + (handle-exceptions + exn + (begin + (debug:print 0 "EXCEPTION: database probably overloaded?") + (debug:print 0 " " ((condition-property-accessor 'exn 'message) exn)) + (print-call-chain) + (thread-sleep! (random 120)) + (debug:print-info 0 "trying db call one more time....") + (apply open-run-close-no-exception-handling proc idb params)) + (apply open-run-close-no-exception-handling proc idb params))) + +;; (define open-run-close open-run-close-exception-handling) +(define open-run-close open-run-close-no-exception-handling) (define *global-delta* 0) (define *last-global-delta-printed* 0) (define (open-run-close-measure proc idb . params) + (debug:print-info 11 "open-run-close-measure START, idb=" idb ", params=" params) (let* ((start-ms (current-milliseconds)) (db (if idb idb (open-db))) (throttle (string->number (config-lookup *configdat* "setup" "throttle")))) - (db:set-sync db) (set! res (apply proc db params)) (if (not idb)(sqlite3:finalize! db)) ;; scale by 10, average with current value. (set! *global-delta* (/ (+ *global-delta* (* (- (current-milliseconds) start-ms) (if throttle throttle 0.01))) 2)) (if (> (abs (- *last-global-delta-printed* *global-delta*)) 0.08) ;; don't print all the time, only if it changes a bit (begin - (debug:print 1 "INFO: launch throttle factor=" *global-delta*) + (debug:print-info 1 "launch throttle factor=" *global-delta*) (set! *last-global-delta-printed* *global-delta*))) + (debug:print-info 11 "open-run-close-measure END" ) res)) (define (db:initialize db) + (debug:print-info 11 "db:initialize START") (let* ((configdat (car *configinfo*)) ;; tut tut, global warning... (keys (config-get-fields configdat)) (havekeys (> (length keys) 0)) (keystr (keys->keystr keys)) (fieldstr (keys->key/field keys))) @@ -216,43 +225,54 @@ status TEXT DEFAULT 'n/a', type TEXT DEFAULT '', CONSTRAINT test_data_constraint UNIQUE (test_id,category,variable));") ;; Must do this *after* running patch db !! No more. (db:set-var db "MEGATEST_VERSION" megatest-version) + (debug:print-info 11 "db:initialize END") )) + +;;====================================================================== +;; T E S T S P E C I F I C D B +;;====================================================================== ;;====================================================================== ;; T E S T S P E C I F I C D B ;;====================================================================== ;; Create the sqlite db for the individual test(s) (define (open-test-db testpath) - (if (and (directory? testpath) + (debug:print-info 11 "open-test-db " testpath) + (if (and testpath + (directory? testpath) (file-read-access? testpath)) (let* ((dbpath (conc testpath "/testdat.db")) (dbexists (file-exists? dbpath)) (db (sqlite3:open-database dbpath)) ;; (never-give-up-open-db dbpath)) (handler (make-busy-timeout (if (args:get-arg "-override-timeout") (string->number (args:get-arg "-override-timeout")) - 36000)))) - (debug:print 4 "INFO: test dbpath=" dbpath) + 136000)))) (sqlite3:set-busy-handler! db handler) (if (not dbexists) (begin (sqlite3:execute db "PRAGMA synchronous = FULL;") - (debug:print 0 "Initialized test database " dbpath) + (debug:print-info 11 "Initialized test database " dbpath) (db:testdb-initialize db))) ;; (sqlite3:execute db "PRAGMA synchronous = 0;") + (debug:print-info 11 "open-test-db END (sucessful)" testpath) db) - #f)) + (begin + (debug:print-info 11 "open-test-db END (unsucessful)" testpath) + #f))) ;; find and open the testdat.db file for an existing test (define (db:open-test-db-by-test-id db test-id) (let* ((test-path (db:test-get-rundir-from-test-id db test-id))) + (debug:print 3 "TEST PATH: " test-path) (open-test-db test-path))) (define (db:testdb-initialize db) + (debug:print 11 "db:testdb-initialize START") (for-each (lambda (sqlcmd) (sqlite3:execute db sqlcmd)) (list "CREATE TABLE IF NOT EXISTS test_rundat ( id INTEGER PRIMARY KEY, @@ -290,11 +310,43 @@ "CREATE TABLE IF NOT EXISTS test_meta ( id INTEGER PRIMARY KEY, var TEXT, val TEXT, ackstate INTEGER DEFAULT 0, - CONSTRAINT metadat_constraint UNIQUE (var));"))) + CONSTRAINT metadat_constraint UNIQUE (var));")) + (debug:print 11 "db:testdb-initialize END")) + +;;====================================================================== +;; L O G G I N G D B +;;====================================================================== + +(define (open-logging-db) ;; (conc *toppath* "/megatest.db") (car *configinfo*))) + (let* ((dbpath (conc (if *toppath* (conc *toppath* "/") "") "logging.db")) ;; fname) + (dbexists (file-exists? dbpath)) + (db (sqlite3:open-database dbpath)) ;; (never-give-up-open-db dbpath)) + (handler (make-busy-timeout (if (args:get-arg "-override-timeout") + (string->number (args:get-arg "-override-timeout")) + 136000)))) ;; 136000))) + (sqlite3:set-busy-handler! db handler) + (if (not dbexists) + (begin + (sqlite3:execute db "CREATE TABLE IF NOT EXISTS log (id INTEGER PRIMARY KEY,event_time TIMESTAMP DEFAULT (strftime('%s','now')),logline TEXT,pwd TEXT,cmdline TEXT,pid INTEGER);") + (sqlite3:execute db (conc "PRAGMA synchronous = 0;")))) + db)) + +(define (db:log-local-event . loglst) + (let ((logline (apply conc loglst)) + (pwd (current-directory)) + (cmdline (string-intersperse (argv) " ")) + (pid (current-process-id))) + (db:log-event logline pwd cmdline pid))) + +(define (db:log-event logline pwd cmdline pid) + (let ((db (open-logging-db))) + (sqlite3:execute db "INSERT INTO log (logline,pwd,cmdline,pid) VALUES (?,?,?,?);" logline (current-directory)(string-intersperse (argv) " ")(current-process-id)) + (sqlite3:finalize! db) + logline)) ;;====================================================================== ;; L O G G I N G D B ;;====================================================================== @@ -409,10 +461,11 @@ ;;====================================================================== ;; returns number if string->number is successful, string otherwise ;; also updates *global-delta* (define (db:get-var db var) + (debug:print-info 11 "db:get-var START " var) (let* ((start-ms (current-milliseconds)) (throttle (let ((t (config-lookup *configdat* "setup" "throttle"))) (if t (string->number t) t))) (res #f)) (sqlite3:for-each-row @@ -427,33 +480,43 @@ (set! *global-delta* (/ (+ *global-delta* (* (- (current-milliseconds) start-ms) (if throttle throttle 0.01))) 2)) (if (> (abs (- *last-global-delta-printed* *global-delta*)) 0.08) ;; don't print all the time, only if it changes a bit (begin - (debug:print 4 "INFO: launch throttle factor=" *global-delta*) + (debug:print-info 4 "launch throttle factor=" *global-delta*) (set! *last-global-delta-printed* *global-delta*))) + (debug:print-info 11 "db:get-var END " var " val=" res) res)) (define (db:set-var db var val) - (sqlite3:execute db "INSERT OR REPLACE INTO metadat (var,val) VALUES (?,?);" var val)) + (debug:print-info 11 "db:set-var START " var " " val) + (sqlite3:execute db "INSERT OR REPLACE INTO metadat (var,val) VALUES (?,?);" var val) + (debug:print-info 11 "db:set-var END " var " " val)) + +(define (db:del-var db var) + (debug:print-info 11 "db:del-var START " var) + (sqlite3:execute db "DELETE FROM metadat WHERE var=?;" var) + (debug:print-info 11 "db:del-var END " var)) ;; use a global for some primitive caching, it is just silly to re-read the db ;; over and over again for the keys since they never change (define (db:get-keys db) (if *db-keys* *db-keys* (let ((res '())) + (debug:print-info 11 "db:get-keys START (cache miss)") (sqlite3:for-each-row (lambda (key keytype) (set! res (cons (vector key keytype) res))) db "SELECT fieldname,fieldtype FROM keys ORDER BY id DESC;") (set! *db-keys* res) + (debug:print-info 11 "db:get-keys END (cache miss)") res))) (define (db:get-value-by-header row header field) - ;; (debug:print 2 "db:get-value-by-header row: " row " header: " header " field: " field) + (debug:print-info 4 "db:get-value-by-header row: " row " header: " header " field: " field) (if (null? header) #f (let loop ((hed (car header)) (tal (cdr header)) (n 0)) (if (equal? hed field) @@ -514,70 +577,79 @@ (conc " LIMIT " count) "") (if (number? offset) (conc " OFFSET " offset) "")))) - (debug:print 8 "INFO: db:get-runs qrystr: " qrystr "\nkeypatts: " keypatts "\n offset: " offset " limit: " count) + (debug:print-info 11 "db:get-runs START qrystr: " qrystr " keypatts: " keypatts " offset: " offset " limit: " count) (sqlite3:for-each-row (lambda (a . x) (set! res (cons (apply vector a x) res))) db qrystr ) + (debug:print-info 11 "db:get-runs END qrystr: " qrystr " keypatts: " keypatts " offset: " offset " limit: " count) (vector header res))) ;; just get count of runs (define (db:get-num-runs db runpatt) (let ((numruns 0)) + (debug:print-info 11 "db:get-num-runs START " runpatt) (sqlite3:for-each-row (lambda (count) (set! numruns count)) db "SELECT COUNT(id) FROM runs WHERE runname LIKE ?;" runpatt) + (debug:print-info 11 "db:get-num-runs END " runpatt) numruns)) ;; use (get-value-by-header (db:get-header runinfo)(db:get-row runinfo)) (define (db:get-run-info db run-id) - (if (hash-table-ref/default *run-info-cache* run-id #f) - (hash-table-ref *run-info-cache* run-id) + ;;(if (hash-table-ref/default *run-info-cache* run-id #f) + ;; (hash-table-ref *run-info-cache* run-id) (let* ((res #f) (keys (db:get-keys db)) (remfields (list "id" "runname" "state" "status" "owner" "event_time")) (header (append (map key:get-fieldname keys) remfields)) (keystr (conc (keys->keystr keys) "," (string-intersperse remfields ",")))) - ;; (debug:print 0 "db:get-run-info run-id: " run-id " header: " header " keystr: " keystr) + (debug:print-info 11 "db:get-run-info run-id: " run-id " header: " header " keystr: " keystr) (sqlite3:for-each-row (lambda (a . x) (set! res (apply vector a x))) db (conc "SELECT " keystr " FROM runs WHERE id=?;") run-id) + (debug:print-info 11 "db:get-run-info run-id: " run-id " header: " header " keystr: " keystr) (let ((finalres (vector header res))) - (hash-table-set! *run-info-cache* run-id finalres) - finalres)))) + ;; (hash-table-set! *run-info-cache* run-id finalres) + finalres))) (define (db:set-comment-for-run db run-id comment) - (sqlite3:execute db "UPDATE runs SET comment=? WHERE id=?;" comment run-id)) + (debug:print-info 11 "db:set-comment-for-run START run-id: " run-id " comment: " comment) + (sqlite3:execute db "UPDATE runs SET comment=? WHERE id=?;" comment run-id) + (debug:print-info 11 "db:set-comment-for-run END run-id: " run-id " comment: " comment)) ;; does not (obviously!) removed dependent data. But why not!!? (define (db:delete-run db run-id) + (common:clear-caches) ;; don't trust caches after doing any deletion (sqlite3:execute db "DELETE FROM runs WHERE id=?;" run-id)) (define (db:update-run-event_time db run-id) - (sqlite3:execute db "UPDATE runs SET event_time=strftime('%s','now') WHERE id=?;" run-id)) + (debug:print-info 11 "db:update-run-event_time START run-id: " run-id) + (sqlite3:execute db "UPDATE runs SET event_time=strftime('%s','now') WHERE id=?;" run-id) + (debug:print-info 11 "db:update-run-event_time END run-id: " run-id)) (define (db:lock/unlock-run db run-id lock unlock user) (let ((newlockval (if lock "locked" (if unlock "unlocked" "locked")))) ;; semi-failsafe (sqlite3:execute db "UPDATE runs SET state=? WHERE id=?;" newlockval run-id) (sqlite3:execute db "INSERT INTO access_log (user,accessed,args) VALUES(?,strftime('%s','now'),?);" user (conc newlockval " " run-id)) - (debug:print 1 "INFO: " newlockval " run number " run-id))) + (debug:print-info 1 "" newlockval " run number " run-id))) ;;====================================================================== ;; K E Y S ;;====================================================================== @@ -584,39 +656,41 @@ ;; get key val pairs for a given run-id ;; ( (FIELDNAME1 keyval1) (FIELDNAME2 keyval2) ... ) (define (db:get-key-val-pairs db run-id) (let* ((keys (get-keys db)) (res '())) - (debug:print 6 "keys: " keys " run-id: " run-id) + (debug:print-info 11 "db:get-key-val-pairs START keys: " keys " run-id: " run-id) (for-each (lambda (key) (let ((qry (conc "SELECT " (key:get-fieldname key) " FROM runs WHERE id=?;"))) ;; (debug:print 0 "qry: " qry) (sqlite3:for-each-row (lambda (key-val) (set! res (cons (list (key:get-fieldname key) key-val) res))) db qry run-id))) keys) + (debug:print-info 11 "db:get-key-val-pairs END keys: " keys " run-id: " run-id) (reverse res))) ;; get key vals for a given run-id (define (db:get-key-vals db run-id) (let ((mykeyvals (hash-table-ref/default *keyvals* run-id #f))) (if mykeyvals mykeyvals (let* ((keys (get-keys db)) (res '())) - (debug:print 6 "keys: " keys " run-id: " run-id) + (debug:print-info 11 "db:get-key-vals START keys: " keys " run-id: " run-id) (for-each (lambda (key) (let ((qry (conc "SELECT " (key:get-fieldname key) " FROM runs WHERE id=?;"))) ;; (debug:print 0 "qry: " qry) (sqlite3:for-each-row (lambda (key-val) (set! res (cons key-val res))) db qry run-id))) keys) + (debug:print-info 11 "db:get-key-vals END keys: " keys " run-id: " run-id) (let ((final-res (reverse res))) (hash-table-set! *keyvals* run-id final-res) final-res))))) ;; The target is keyval1/keyval2..., cached in *target* as it is used often @@ -632,11 +706,11 @@ ;;====================================================================== ;; T E S T S ;;====================================================================== (define (db:tests-register-test db run-id test-name item-path) - (debug:print 4 "INFO: db:tests-register-test db=" db ", run-id=" run-id ", test-name=" test-name ", item-path=\"" item-path "\"") + (debug:print-info 11 "db:tests-register-test START db=" db ", run-id=" run-id ", test-name=" test-name ", item-path=\"" item-path "\"") (let ((item-paths (if (equal? item-path "") (list item-path) (list item-path "")))) (for-each (lambda (pth) @@ -643,50 +717,61 @@ (sqlite3:execute db "INSERT OR IGNORE INTO tests (run_id,testname,event_time,item_path,state,status) VALUES (?,?,strftime('%s','now'),?,'NOT_STARTED','n/a');" run-id test-name pth)) item-paths) + (debug:print-info 11 "db:tests-register-test END db=" db ", run-id=" run-id ", test-name=" test-name ", item-path=\"" item-path "\"") #f)) ;; states and statuses are lists, turn them into ("PASS","FAIL"...) and use NOT IN ;; i.e. these lists define what to NOT show. ;; states and statuses are required to be lists, empty is ok ;; not-in #t = above behaviour, #f = must match -(define (db:get-tests-for-run db run-id testpatt itempatt states statuses +(define (db:get-tests-for-run db run-id testpatt states statuses #!key (not-in #t) (sort-by #f) ;; 'rundir 'event_time ) + (debug:print-info 11 "db:get-tests-for-run START run-id=" run-id ", testpatt=" testpatt ", states=" states ", statuses=" statuses ", not-in=" not-in ", sort-by=" sort-by) (let* ((res '()) ;; if states or statuses are null then assume match all when not-in is false - (states-str (conc " state in ('" (string-intersperse states "','") "')")) - (statuses-str (conc " status in ('" (string-intersperse statuses "','") "')")) - (state-status-qry (if (or (not (null? states)) - (not (null? states))) - (conc " AND " (if not-in "NOT" "") " (" states-str " AND " statuses-str ") ") - "")) - (qry (conc "SELECT id,run_id,testname,state,status,event_time,host,cpuload,diskfree,uname,rundir,item_path,run_duration,final_logf,comment " - " FROM tests WHERE run_id=? AND " - ;; testname like ? AND item_path LIKE ? " - (db:patt->like "testname" testpatt) " AND " - (db:patt->like "item_path" itempatt) - state-status-qry - (case sort-by - ((rundir) " ORDER BY length(rundir) DESC;") - ((event_time) " ORDER BY event_time ASC;") - (else ";")) + (states-qry (if (null? states) + #f + (conc " state " + (if not-in "NOT" "") + " IN ('" + (string-intersperse states "','") + "')"))) + (statuses-qry (if (null? statuses) + #f + (conc " status " + (if not-in "NOT" "") + " IN ('" + (string-intersperse statuses "','") + "')"))) + (tests-match-qry (tests:match->sqlqry testpatt)) + (qry (conc "SELECT id,run_id,testname,state,status,event_time,host,cpuload,diskfree,uname,rundir,item_path,run_duration,final_logf,comment " + " FROM tests WHERE run_id=? " + (if states-qry (conc " AND " states-qry) "") + (if statuses-qry (conc " AND " statuses-qry) "") + (if tests-match-qry (conc " AND (" tests-match-qry ") ") "") + (case sort-by + ((rundir) " ORDER BY length(rundir) DESC;") + ((event_time) " ORDER BY event_time ASC;") + (else ";")) ))) - (debug:print 8 "INFO: db:get-tests-for-run qry=" qry) + (debug:print-info 8 "db:get-tests-for-run qry=" qry) (sqlite3:for-each-row (lambda (a . b) ;; id run-id testname state status event-time host cpuload diskfree uname rundir item-path run-duration final-logf comment) (set! res (cons (apply vector a b) res))) ;; id run-id testname state status event-time host cpuload diskfree uname rundir item-path run-duration final-logf comment) res))) db qry run-id ;; (if testpatt testpatt "%") ;; (if itempatt itempatt "%")) ) + (debug:print-info 11 "db:get-tests-for-run START run-id=" run-id ", testpatt=" testpatt ", states=" states ", statuses=" statuses ", not-in=" not-in ", sort-by=" sort-by) res)) ;; this one is a bit broken BUG FIXME (define (db:delete-test-step-records db test-id) ;; Breaking it into two queries for better file access interleaving @@ -698,10 +783,11 @@ (sqlite3:execute tdb "DELETE FROM test_data;") (sqlite3:finalize! tdb))))) ;; (define (db:delete-test-records db tdb test-id #!key (force #f)) + (common:clear-caches) (if tdb (begin (sqlite3:execute tdb "DELETE FROM test_steps;") (sqlite3:execute tdb "DELETE FROM test_data;"))) ;; (sqlite3:execute db "DELETE FROM tests WHERE id=?;" test-id)) @@ -712,10 +798,11 @@ (if force (sqlite3:execute db "DELETE FROM tests WHERE id=?;" test-id) (sqlite3:execute db "UPDATE tests SET state='DELETED',status='n/a' WHERE id=?;" test-id))))) (define (db:delete-tests-for-run db run-id) + (common:clear-caches) (sqlite3:execute db "DELETE FROM tests WHERE run_id=?;" run-id)) (define (db:delete-old-deleted-test-records db) (let ((targtime (- (current-seconds)(* 30 24 60 60)))) ;; one month in the past (sqlite3:execute db "DELETE FROM tests WHERE state='DELETED' AND event_time last-delete *last-test-cache-delete*)) (begin (set! *test-info* (make-hash-table)) (set! *test-id-cache* (make-hash-table)) (set! *last-test-cache-delete* last-delete) - (debug:print 4 "INFO: Clearing test data cache")))) + (debug:print-info 4 "Clearing test data cache")))) (if (not test-id) (begin - (debug:print 4 "INFO: db:get-test-info-by-id called with test-id=" test-id) + (debug:print-info 4 "db:get-test-info-by-id called with test-id=" test-id) #f) (let* ((res (hash-table-ref/default *test-info* test-id #f))) (if (and res (member (db:test-get-state res) '("RUNNING" "COMPLETED"))) (db:patch-tdb-data-into-test-info db test-id res) @@ -887,11 +974,11 @@ ;; Get test data using test_id (define (db:get-test-info-not-cached-by-id db test-id) (if (not test-id) (begin - (debug:print 4 "INFO: db:get-test-info-by-id called with test-id=" test-id) + (debug:print-info 4 "db:get-test-info-by-id called with test-id=" test-id) #f) (let ((res #f)) (sqlite3:for-each-row (lambda (id run-id testname state status event-time host cpuload diskfree uname rundir item-path run_duration final_logf comment) ;; 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 @@ -910,64 +997,53 @@ (sqlite3:execute db "UPDATE tests SET comment=? WHERE id=?;" comment test-id)) -;; -(define (db:test-set-rundir! db run-id test-name item-path rundir) - (sqlite3:execute - db - "UPDATE tests SET rundir=? WHERE run_id=? AND testname=? AND item_path=?;" - rundir run-id test-name item-path)) - -(define (db:test-set-rundir-by-test-id! db test-id rundir) - (sqlite3:execute - db - "UPDATE tests SET rundir=? WHERE id=?" - rundir test-id)) - -;; +(define (cdb:test-set-rundir! serverdat run-id test-name item-path rundir) + (cdb:client-call serverdat 'test-set-rundir #t *default-numtries* rundir run-id test-name item-path)) + +(define (cdb:test-set-rundir-by-test-id serverdat test-id rundir) + (cdb:client-call serverdat 'test-set-rundir-by-test-id #t *default-numtries* rundir test-id)) + (define (db:test-get-rundir-from-test-id db test-id) - (let ((res (hash-table-ref/default *test-paths* test-id #f))) - (if res - res - (begin - (sqlite3:for-each-row - (lambda (tpath) - (set! res tpath)) - db - "SELECT rundir FROM tests WHERE id=?;" - test-id) - (hash-table-set! *test-paths* test-id res) - res)))) - -(define (db:test-set-log! db test-id logf) - (if (string? logf) - (sqlite3:execute db "UPDATE tests SET final_logf=? WHERE id=?;" - logf test-id) - (debug:print 0 "ERROR: db:test-set-log! called with non-string log file name " logf))) + (let ((res #f)) ;; (hash-table-ref/default *test-paths* test-id #f))) + ;; (if res + ;; res + ;; (begin + (sqlite3:for-each-row + (lambda (tpath) + (set! res tpath)) + db + "SELECT rundir FROM tests WHERE id=?;" + test-id) + ;; (hash-table-set! *test-paths* test-id res) + res)) ;; )) + +(define (cdb:test-set-log! serverdat test-id logf) + (if (string? logf)(cdb:client-call serverdat 'test-set-log #f *default-numtries* logf test-id))) ;;====================================================================== ;; Misc. test related queries ;;====================================================================== (define (db:test-get-paths-matching db keynames target fnamepatt #!key (res '())) - (let* ((itempatt (if (args:get-arg "-itempatt")(args:get-arg "-itempatt") "%")) - (testpatt (if (args:get-arg "-testpatt")(args:get-arg "-testpatt") "%")) + (let* ((testpatt (if (args:get-arg "-testpatt")(args:get-arg "-testpatt") "%")) (statepatt (if (args:get-arg ":state") (args:get-arg ":state") "%")) (statuspatt (if (args:get-arg ":status") (args:get-arg ":status") "%")) (runname (if (args:get-arg ":runname") (args:get-arg ":runname") "%")) (keystr (string-intersperse (map (lambda (key val) (conc "r." key " like '" val "'")) keynames (string-split target "/")) " AND ")) + (testqry (tests:match->sqlqry testpatt)) (qrystr (conc "SELECT t.rundir FROM tests AS t INNER JOIN runs AS r ON t.run_id=r.id WHERE " - keystr " AND r.runname LIKE '" runname "' AND item_path LIKE '" itempatt "' AND testname LIKE '" - testpatt "' AND t.state LIKE '" statepatt "' AND t.status LIKE '" statuspatt - "'ORDER BY t.event_time ASC;"))) + keystr " AND r.runname LIKE '" runname "' AND " testqry + " AND t.state LIKE '" statepatt "' AND t.status LIKE '" statuspatt + "' ORDER BY t.event_time ASC;"))) (debug:print 3 "qrystr: " qrystr) (sqlite3:for-each-row (lambda (p) (set! res (cons p res))) db @@ -989,11 +1065,11 @@ (if (not (null? res)) (car res) ;; return first found (if path (let* ((db (open-db path: (cadr pathdat))) (newres (db:test-get-paths-matching db keynames target fname))) - (debug:print 4 "INFO: Trying " (car pathdat) " at " (cadr pathdat)) + (debug:print-info 4 "Trying " (car pathdat) " at " (cadr pathdat)) (sqlite3:finalize! db) (if (not (null? newres)) (car newres) (if (null? tal) #f @@ -1033,14 +1109,11 @@ t.comment t.event_time t.fail_count t.pass_count t.archived - - - - FROM tests AS t INNER JOIN runs AS r ON t.run_id=r.id WHERE " + FROM tests AS t INNER JOIN runs AS r ON t.run_id=r.id WHERE " keystr " AND r.runname LIKE '" runname "' AND item_path LIKE '" itempatt "' AND testname LIKE '" testpatt "' AND t.state LIKE '" statepatt "' AND t.status LIKE '" statuspatt "'ORDER BY t.event_time ASC;"))) (debug:print 3 "qrystr: " qrystr) (sqlite3:for-each-row @@ -1049,155 +1122,268 @@ db qrystr) res)) ;;====================================================================== -;; QUEUE UP META, TEST STATUS AND STEPS -;;====================================================================== - -(define (db:updater) - (debug:print 4 "INFO: Starting cache processing") - (let loop ((start-time (current-time))) - (thread-sleep! 10) ;; move save time around to minimize regular collisions? - (db:write-cached-data) - (loop start-time))) - -(define (cdb:test-set-status-state test-id status state msg) - (debug:print 4 "INFO: cdb:test-set-status-state test-id=" test-id ", status=" status ", state=" state ", msg=" msg) - (mutex-lock! *incoming-mutex*) - (set! *last-db-access* (current-seconds)) - (if msg - (set! *incoming-data* (cons (vector 'state-status-msg - (current-milliseconds) - (list state status msg test-id)) - *incoming-data*)) - (set! *incoming-data* (cons (vector 'state-status - (current-milliseconds) - (list state status test-id)) ;; run-id test-name item-path minutes cpuload diskfree tmpfree) - *incoming-data*))) - (mutex-unlock! *incoming-mutex*) - (if *cache-on* - (debug:print 6 "INFO: *cache-on* is " *cache-on* ", skipping cache write") - (db:write-cached-data))) - -(define (cdb:test-rollup-test_data-pass-fail test-id) - (debug:print 4 "INFO: Adding " test-id " for test_data rollup to the queue") - (mutex-lock! *incoming-mutex*) - (set! *last-db-access* (current-seconds)) - (set! *incoming-data* (cons (vector 'test_data-pf-rollup - (current-milliseconds) - (list test-id test-id test-id test-id)) - *incoming-data*)) - (mutex-unlock! *incoming-mutex*) - (if *cache-on* - (debug:print 6 "INFO: *cache-on* is " *cache-on* ", skipping cache write") - (db:write-cached-data))) - -(define (cdb:pass-fail-counts test-id fail-count pass-count) - (debug:print 4 "INFO: Adding " test-id " for setting pass/fail counts to the queue") - (mutex-lock! *incoming-mutex*) - (set! *last-db-access* (current-seconds)) - (set! *incoming-data* (cons (vector 'pass-fail-counts - (current-milliseconds) - (list fail-count pass-count test-id)) - *incoming-data*)) - (mutex-unlock! *incoming-mutex*) - (if *cache-on* - (debug:print 6 "INFO: *cache-on* is " *cache-on* ", skipping cache write") - (db:write-cached-data))) - -(define (cdb:tests-register-test db run-id test-name item-path #!key (force-write #f)) +;; QUEUE UP META, TEST STATUS AND STEPS REMOTE ACCESS +;;====================================================================== + +;; db:updater is run in a thread to write out the cached data periodically +;; (define (db:updater) +;; (debug:print-info 4 "Starting cache processing") +;; (let loop () +;; (thread-sleep! 10) ;; move save time around to minimize regular collisions? +;; (db:write-cached-data) +;; (loop))) + +(define (db:obj->string obj) + (string-substitute + (regexp "=") "_" + (base64:base64-encode (with-output-to-string (lambda ()(serialize obj)))) + #t)) + +(define (db:string->obj msg) + (with-input-from-string + (base64:base64-decode + (string-substitute + (regexp "_") "=" msg #t)) + (lambda ()(deserialize)))) + +(define (cdb:use-non-blocking-mode proc) + (set! *client-non-blocking-mode* #t) + (let ((res (proc))) + (set! *client-non-blocking-mode* #f) + res)) + +;; params = 'target cached remparams +;; +;; make-vector-record cdb packet client-sig qtype immediate query-sig params qtime +;; +(define (cdb:client-call serverdat qtype immediate numretries . params) + (debug:print-info 11 "cdb:client-call serverdat=" serverdat ", qtype=" qtype ", immediate=" immediate ", numretries=" numretries ", params=" params) + ;; (handle-exceptions + ;; exn + ;; (begin + ;; (thread-sleep! 5) + ;; (if (> numretries 0)(apply cdb:client-call serverdat qtype immediate (- numretries 1) params))) + (let* ((client-sig (server:get-client-signature)) + (query-sig (message-digest-string (md5-primitive) (conc qtype immediate params))) + (zdat (db:obj->string (vector client-sig qtype immediate query-sig params (current-seconds)))) ;; (with-output-to-string (lambda ()(serialize params)))) + ) + (debug:print-info 11 "zdat=" zdat) + (let* ( + (res #f) + (rawdat (server:client-send-receive serverdat zdat)) + (tmp #f)) + (debug:print-info 11 "Sent " zdat ", received " rawdat) + (set! tmp (db:string->obj rawdat)) + ;; (if (equal? query-sig (vector-ref myres 1)) + ;; (set! res + (vector-ref tmp 2) + ;; (loop (server:client-send-receive serverdat zdat))))))) + ;; (timeout (lambda () + ;; (let loop ((n numretries)) + ;; (thread-sleep! 15) + ;; (if (not res) + ;; (if (> numretries 0) + ;; (begin + ;; (debug:print 2 "WARNING: no reply to query " params ", trying resend") + ;; (debug:print-info 11 "re-sending message") + ;; (apply cdb:client-call serverdat qtype immediate numretries params) + ;; (debug:print-info 11 "message re-sent") + ;; (loop (- n 1))) + ;; ;; (apply cdb:client-call serverdats qtype immediate (- numretries 1) params)) + ;; (begin + ;; (debug:print 0 "ERROR: cdb:client-call timed out " params ", exiting.") + ;; (exit 5)))))))) + ;; (send-receive) + ))) + ;; (debug:print-info 11 "Starting threads") + ;; (let ((th1 (make-thread send-receive "send receive")) + ;; (th2 (make-thread timeout "timeout"))) + ;; (thread-start! th1) + ;; (thread-start! th2) + ;; (thread-join! th1) + ;; (debug:print-info 11 "cdb:client-call returning res=" res) + ;; res)))) + +(define (cdb:set-verbosity serverdat val) + (cdb:client-call serverdat 'set-verbosity #f *default-numtries* val)) + +(define (cdb:login serverdat keyval signature) + (cdb:client-call serverdat 'login #t *default-numtries* keyval megatest-version signature)) + +(define (cdb:logout serverdat keyval signature) + (cdb:client-call serverdat 'logout #t *default-numtries* keyval signature)) + +(define (cdb:num-clients serverdat) + (cdb:client-call serverdat 'numclients #t *default-numtries*)) + +(define (cdb:test-set-status-state serverdat test-id status state msg) + (if msg + (cdb:client-call serverdat 'state-status-msg #t *default-numtries* state status msg test-id) + (cdb:client-call serverdat 'state-status #t *default-numtries* state status test-id))) ;; run-id test-name item-path minutes cpuload diskfree tmpfree) + +(define (cdb:test-rollup-test_data-pass-fail serverdat test-id) + (cdb:client-call serverdat 'test_data-pf-rollup #t *default-numtries* test-id test-id test-id test-id)) + +(define (cdb:pass-fail-counts serverdat test-id fail-count pass-count) + (cdb:client-call serverdat 'pass-fail-counts #t *default-numtries* fail-count pass-count test-id)) + +(define (cdb:tests-register-test serverdat run-id test-name item-path) (let ((item-paths (if (equal? item-path "") (list item-path) (list item-path "")))) - (debug:print 4 "INFO: Adding " run-id ", " test-name "/" item-path " for setting pass/fail counts to the queue") - (mutex-lock! *incoming-mutex*) - (set! *last-db-access* (current-seconds)) - (set! *incoming-data* (cons (vector 'register-test - (current-milliseconds) - (list run-id test-name item-path)) ;; fail-count pass-count test-id)) - *incoming-data*)) - (mutex-unlock! *incoming-mutex*) - (if (and (not force-write) *cache-on*) - (debug:print 6 "INFO: *cache-on* is " *cache-on* ", skipping cache write") - (db:write-cached-data)))) + (cdb:client-call serverdat 'register-test #t *default-numtries* run-id test-name item-path))) + +(define (cdb:flush-queue serverdat) + (cdb:client-call serverdat 'flush #f *default-numtries*)) + +(define (cdb:kill-server serverdat) + (cdb:client-call serverdat 'killserver #f *default-numtries*)) + +(define (cdb:roll-up-pass-fail-counts serverdat run-id test-name item-path status) + (cdb:client-call serverdat 'immediate #f *default-numtries* open-run-close db:roll-up-pass-fail-counts #f run-id test-name item-path status)) + +(define (cdb:get-test-info serverdat run-id test-name item-path) + (cdb:client-call serverdat 'immediate #f *default-numtries* open-run-close db:get-test-info #f run-id test-name item-path)) + +(define (cdb:get-test-info-by-id serverdat test-id) + (cdb:client-call serverdat 'immediate #f *default-numtries* open-run-close db:get-test-info-by-id #f test-id)) + +;; db should be db open proc or #f +(define (cdb:remote-run proc db . params) + (apply cdb:client-call *runremote* 'immediate #f *default-numtries* open-run-close proc #f params)) + +(define (db:test-get-logfile-info db run-id test-name) + (let ((res #f)) + (sqlite3:for-each-row + (lambda (path final_logf) + (set! logf final_logf) + (set! res (list path final_logf)) + (if (directory? path) + (debug:print 2 "Found path: " path) + (debug:print 2 "No such path: " path))) + db + "SELECT rundir,final_logf FROM tests WHERE run_id=? AND testname=? AND item_path='';" + run-id test-name) + res)) + +(define db:queries + (list '(register-test "INSERT OR IGNORE INTO tests (run_id,testname,event_time,item_path,state,status) VALUES (?,?,strftime('%s','now'),?,'NOT_STARTED','n/a');") + '(state-status "UPDATE tests SET state=?,status=? WHERE id=?;") + '(state-status-msg "UPDATE tests SET state=?,status=?,comment=? WHERE id=?;") + '(pass-fail-counts "UPDATE tests SET fail_count=?,pass_count=? WHERE id=?;") + ;; test_data-pf-rollup is used to set a tests PASS/FAIL based on the pass/fail info from the steps + '(test_data-pf-rollup "UPDATE tests + SET status=CASE WHEN (SELECT fail_count FROM tests WHERE id=?) > 0 + THEN 'FAIL' + WHEN (SELECT pass_count FROM tests WHERE id=?) > 0 AND + (SELECT status FROM tests WHERE id=?) NOT IN ('WARN','FAIL') + THEN 'PASS' + ELSE status + END WHERE id=?;") + '(test-set-log "UPDATE tests SET final_logf=? WHERE id=?;") + '(test-set-rundir-by-test-id "UPDATE tests SET rundir=? WHERE id=?") + '(test-set-rundir "UPDATE tests SET rundir=? WHERE run_id=? AND testname=? AND item_path=?;") + '(delete-tests-in-state "DELETE FROM tests WHERE state=? AND run_id=?;") + '(tests:test-set-toplog "UPDATE tests SET final_logf=? WHERE run_id=? AND testname=? AND item_path='';") + )) + +;; do not run these as part of the transaction +(define db:special-queries '(rollup-tests-pass-fail + db:roll-up-pass-fail-counts + login + immediate + flush + sync + set-verbosity + killserver)) + +;; not used, intended to indicate to run in calling process +(define db:run-local-queries '()) ;; rollup-tests-pass-fail)) ;; The queue is a list of vectors where the zeroth slot indicates the type of query to ;; apply and the second slot is the time of the query and the third entry is a list of ;; values to be applied ;; -(define (db:write-cached-data) - (open-run-close - (lambda (db . params) - (let ((register-test-stmt (sqlite3:prepare db "INSERT OR IGNORE INTO tests (run_id,testname,event_time,item_path,state,status) VALUES (?,?,strftime('%s','now'),?,'NOT_STARTED','n/a');")) - (state-status-stmt (sqlite3:prepare db "UPDATE tests SET state=?,status=? WHERE id=?;")) - (state-status-msg-stmt (sqlite3:prepare db "UPDATE tests SET state=?,status=?,comment=? WHERE id=?;")) - (pass-fail-counts-stmt (sqlite3:prepare db "UPDATE tests SET fail_count=?,pass_count=? WHERE id=?;")) - (test_data-rollup-stmt (sqlite3:prepare db "UPDATE tests - SET status=CASE WHEN (SELECT fail_count FROM tests WHERE id=?) > 0 - THEN 'FAIL' - WHEN (SELECT pass_count FROM tests WHERE id=?) > 0 AND - (SELECT status FROM tests WHERE id=?) NOT IN ('WARN','FAIL') - THEN 'PASS' - ELSE status - END WHERE id=?;")) - (data #f) - (rollups (make-hash-table))) - (mutex-lock! *incoming-mutex*) - (set! data (sort *incoming-data* (lambda (a b)(< (vector-ref a 1)(vector-ref b 1))))) - (set! *incoming-data* '()) - (mutex-unlock! *incoming-mutex*) - (if (> (length data) 0) - (debug:print 4 "INFO: Writing cached data " data)) - (sqlite3:with-transaction - db - (lambda () - (debug:print 4 "INFO: flushing " data " to db") - (for-each (lambda (entry) - (let ((params (vector-ref entry 2))) - (debug:print 4 "INFO: Applying " entry " to params " params) - (case (vector-ref entry 0) - ((state-status) - (apply sqlite3:execute state-status-stmt params)) - ((state-status-msg) - (apply sqlite3:execute state-status-msg-stmt params)) - ((test_data-pf-rollup) - ;; (hash-table-set! rollups (car params) params)) - (apply sqlite3:execute test_data-rollup-stmt params)) - ((pass-fail-counts) - (apply sqlite3:execute pass-fail-counts-stmt params)) - ((register-test) - (apply sqlite3:execute register-test-stmt params)) - (else - (debug:print 0 "ERROR: Queued entry not recognised " entry))))) - data))) - ;; now do any rollups - ;; (for-each - ;; (lambda (test-id) - ;; (apply sqlite3:execute test_data-rollup-stmt (hash-table-ref rollups test-id))) - ;; (hash-table-keys rollups)) - (sqlite3:finalize! state-status-stmt) - (sqlite3:finalize! state-status-msg-stmt) - (sqlite3:finalize! test_data-rollup-stmt) - (sqlite3:finalize! pass-fail-counts-stmt) - (sqlite3:finalize! register-test-stmt) - (let ((cache-size (length data))) - (if (> cache-size *max-cache-size*) - (set! *max-cache-size* cache-size))) - )) - #f)) - -(define cdb:flush-queue db:write-cached-data) - +(define (db:process-queue db pubsock indata) + (let* ((data (sort indata (lambda (a b) + (< (cdb:packet-get-qtime a)(cdb:packet-get-qtime b)))))) + (for-each + (lambda (item) + (db:process-queue-item db pubsock item)) + data))) + +(define (db:process-queue-item db item) + (let* ((stmt-key (cdb:packet-get-qtype item)) + (qry-sig (cdb:packet-get-query-sig item)) + (return-address (cdb:packet-get-client-sig item)) + (params (cdb:packet-get-params item)) + (query (let ((q (alist-ref stmt-key db:queries))) + (if q (car q) #f)))) + (debug:print-info 11 "Special queries/requests stmt-key=" stmt-key ", return-address=" return-address ", query=" query ", params=" params) + (cond + (query + (apply sqlite3:execute db query params) + (server:reply return-address qry-sig #t #t)) + ((member stmt-key db:special-queries) + (debug:print-info 11 "Handling special statement " stmt-key) + (case stmt-key + ((immediate) + (let ((proc (car params)) + (remparams (cdr params))) + ;; we are being handed a procedure so call it + (debug:print-info 11 "Running (apply " proc " " remparams ")") + (server:reply return-address qry-sig #t (apply proc remparams)))) + ((login) + (if (< (length params) 3) ;; should get toppath, version and signature + (server:reply return-address qry-sig '(#f "login failed due to missing params")) ;; missing params + (let ((calling-path (car params)) + (calling-vers (cadr params)) + (client-key (caddr params))) + (if (and (equal? calling-path *toppath*) + (equal? megatest-version calling-vers)) + (begin + (hash-table-set! *logged-in-clients* client-key (current-seconds)) + (server:reply return-address qry-sig #t '(#t "successful login"))) ;; path matches - pass! Should vet the caller at this time ... + (server:reply return-address qry-sig #f (list #f (conc "Login failed due to mismatch paths: " calling-path ", " *toppath*))))))) + ((flush sync) + (server:reply return-address qry-sig #t 1)) ;; (length data))) + ((set-verbosity) + (set! *verbosity* (car params)) + (server:reply return-address qry-sig #t '(#t *verbosity*))) + ((killserver) + (debug:print 0 "WARNING: Server going down in 15 seconds by user request!") + (open-run-close tasks:server-deregister tasks:open-db + (car *runremote*) + pullport: (cadr *runremote*)) + (thread-start! (make-thread (lambda ()(thread-sleep! 15)(exit)))) + (server:reply return-address qry-sig #t '(#t "exit process started"))) + (else ;; not a command, i.e. is a query + (debug:print 0 "ERROR: Unrecognised query/command " stmt-key) + (server:reply pubsock return-address qry-sig #f 'failed)))) + (else + (debug:print-info 11 "Executing " stmt-key " for " params) + (apply sqlite3:execute (hash-table-ref queries stmt-key) params) + (server:reply return-address qry-sig #t #t))))) + +(define (db:test-get-records-for-index-file db run-id test-name) + (let ((res '())) + (sqlite3:for-each-row + (lambda (id itempath state status run_duration logf comment) + (set! res (cons (vector id itempath state status run_duration logf comment) res))) + db + "SELECT id,item_path,state,status,run_duration,final_logf,comment FROM tests WHERE run_id=? AND testname=? AND item_path != '';" + run-id test-name) + res)) + +;; Rollup the pass/fail counts from itemized tests into fail_count and pass_count (define (db:roll-up-pass-fail-counts db run-id test-name item-path status) - (rdb:flush-queue) + ;; (cdb:flush-queue *runremote*) (if (and (not (equal? item-path "")) - (or (equal? status "PASS") - (equal? status "WARN") - (equal? status "FAIL") - (equal? status "WAIVED") - (equal? status "RUNNING"))) + (member status '("PASS" "WARN" "FAIL" "WAIVED" "RUNNING" "CHECK"))) (begin (sqlite3:execute db "UPDATE tests SET fail_count=(SELECT count(id) FROM tests WHERE run_id=? AND testname=? AND item_path != '' AND status='FAIL'), @@ -1208,14 +1394,20 @@ (if (equal? status "RUNNING") ;; running takes priority over all other states, force the test state to RUNNING (sqlite3:execute db "UPDATE tests SET state=? WHERE run_id=? AND testname=? AND item_path='';" "RUNNING" run-id test-name) (sqlite3:execute db "UPDATE tests - SET state=CASE WHEN (SELECT count(id) FROM tests WHERE run_id=? AND testname=? AND item_path != '' AND state in ('RUNNING','NOT_STARTED')) > 0 THEN - 'RUNNING' - ELSE 'COMPLETED' END, - status=CASE WHEN fail_count > 0 THEN 'FAIL' WHEN pass_count > 0 AND fail_count=0 THEN 'PASS' ELSE 'UNKNOWN' END + SET state=CASE + WHEN (SELECT count(id) FROM tests + WHERE run_id=? AND testname=? + AND item_path != '' + AND state in ('RUNNING','NOT_STARTED')) > 0 THEN 'RUNNING' + ELSE 'COMPLETED' END, + status=CASE + WHEN fail_count > 0 THEN 'FAIL' + WHEN pass_count > 0 AND fail_count=0 THEN 'PASS' + ELSE 'UNKNOWN' END WHERE run_id=? AND testname=? AND item_path='';" run-id test-name run-id test-name)) #f) #f)) @@ -1350,18 +1542,18 @@ (SELECT count(id) FROM test_data WHERE test_id=? AND status like 'pass') AS pass_count;" test-id test-id) (sqlite3:finalize! tdb) ;; Now rollup the counts to the central megatest.db - (rdb:pass-fail-counts test-id fail-count pass-count) + (cdb:pass-fail-counts *runremote* test-id fail-count pass-count) ;; (sqlite3:execute db "UPDATE tests SET fail_count=?,pass_count=? WHERE id=?;" ;; fail-count pass-count test-id) - - (thread-sleep! 10) ;; play nice with the queue by ensuring the rollup is at least 10s later than the set + (cdb:flush-queue *runremote*) + ;; (thread-sleep! 1) ;; play nice with the queue by ensuring the rollup is at least 10ms later than the set ;; if the test is not FAIL then set status based on the fail and pass counts. - (rdb:test-rollup-test_data-pass-fail test-id) + (cdb:test-rollup-test_data-pass-fail *runremote* test-id) ;; (sqlite3:execute ;; db ;;; NOTE: Should this be WARN,FAIL? A WARN is not a FAIL????? BUG FIXME ;; "UPDATE tests ;; SET status=CASE WHEN (SELECT fail_count FROM tests WHERE id=?) > 0 ;; THEN 'FAIL' @@ -1450,11 +1642,16 @@ "\nstepname: " (db:step-get-stepname step) "\nstate: " (db:step-get-state step) "\nstatus: " (db:step-get-status step) "\ntime: " (db:step-get-event_time step)))) ;; (else (vector-set! record 1 (db:step-get-event_time step))) - (sort steps (lambda (a b)(< (db:step-get-event_time a)(db:step-get-event_time b))))) + (sort steps (lambda (a b) + (cond + ((< (db:step-get-event_time a)(db:step-get-event_time b)) #t) + ((eq? (db:step-get-event_time a)(db:step-get-event_time b)) + (< (db:step-get-id a) (db:step-get-id b))) + (else #f))))) res))) ;;====================================================================== ;; M I S C M A N A G E M E N T I T E M S ;;====================================================================== @@ -1477,11 +1674,11 @@ (result '())) (for-each (lambda (waitontest-name) ;; by getting the tests with matching name we are looking only at the matching test ;; and related sub items - (let ((tests (db:get-tests-for-run db run-id waitontest-name #f '() '())) + (let ((tests (db:get-tests-for-run db run-id waitontest-name '() '())) (ever-seen #f) (parent-waiton-met #f) (item-waiton-met #f)) (for-each (lambda (test) @@ -1514,14 +1711,14 @@ (delete-duplicates result)))) (define (db:teststep-set-status! db test-id teststep-name state-in status-in comment logfile) (debug:print 4 "test-id: " test-id " teststep-name: " teststep-name) (let* ((tdb (db:open-test-db-by-test-id db test-id)) - (state (check-valid-items "state" state-in)) - (status (check-valid-items "status" status-in))) + (state (items:check-valid-items "state" state-in)) + (status (items:check-valid-items "status" status-in))) (if (or (not state)(not status)) - (debug:print 0 "WARNING: Invalid " (if status "status" "state") + (debug:print 3 "WARNING: Invalid " (if status "status" "state") " value \"" (if status state-in status-in) "\", update your validvalues section in megatest.config")) (if tdb (begin (sqlite3:execute tdb @@ -1612,11 +1809,11 @@ (if (string=? item-path "") "" (conc "/" item-path)) final-log))) ;; for now throw away newpath and use the log-fpath conc'd with pathmod (set! newpath (conc pathmod log-fpath)) (if windows (string-translate newpath "/" "\\") newpath)) - (if (> *verbosity* 1) + (if (debug:debug-mode 1) (conc final-log " not-found") ""))) (vector->list vb)) b))))) db @@ -1653,61 +1850,5 @@ results) ;; brutal clean up (system "rm -rf tempdir"))) ;; (db:extract-ods-file db "outputfile.ods" '(("sysname" "%")("fsname" "%")("datapath" "%")) "%") - - -;;====================================================================== -;; REMOTE DB ACCESS VIA RPC -;;====================================================================== - -(define (rdb:open-run-close procname . remargs) - (if *runremote* - (let ((host (vector-ref *runremote* 0)) - (port (vector-ref *runremote* 1))) - (apply (rpc:procedure 'rdb:open-run-close host port) procname remargs)) - (apply open-run-close (eval procname) remargs))) - -(define (rdb:test-set-status-state test-id status state msg) - (if *runremote* - (let ((host (vector-ref *runremote* 0)) - (port (vector-ref *runremote* 1))) - (handle-exceptions - exn - (begin - (debug:print 0 "EXCEPTION: rpc call failed?") - (debug:print 0 " " ((condition-property-accessor 'exn 'message) exn)) - (print-call-chain) - (cdb:test-set-status-state test-id status state msg)) - ((rpc:procedure 'cdb:test-set-status-state host port) test-id status state msg))) - (cdb:test-set-status-state test-id status state msg))) - -(define (rdb:test-rollup-test_data-pass-fail test-id) - (if *runremote* - (let ((host (vector-ref *runremote* 0)) - (port (vector-ref *runremote* 1))) - ((rpc:procedure 'cdb:test-rollup-test_data-pass-fail host port) test-id)) - (cdb:test-rollup-test_data-pass-fail test-id))) - -(define (rdb:pass-fail-counts test-id fail-count pass-count) - (if *runremote* - (let ((host (vector-ref *runremote* 0)) - (port (vector-ref *runremote* 1))) - ((rpc:procedure 'cdb:pass-fail-counts host port) test-id fail-count pass-count)) - (cdb:pass-fail-counts test-id fail-count pass-count))) - -;; currently forces a flush of the queue -(define (rdb:tests-register-test db run-id test-name item-path) - (if *runremote* - (let ((host (vector-ref *runremote* 0)) - (port (vector-ref *runremote* 1))) - ((rpc:procedure 'cdb:tests-register-test host port) db run-id test-name item-path force-write: #t)) - (cdb:tests-register-test db run-id test-name item-path force-write: #t))) - -(define (rdb:flush-queue) - (if *runremote* - (let ((host (vector-ref *runremote* 0)) - (port (vector-ref *runremote* 1))) - ((rpc:procedure 'cdb:flush-queue host port))) - (cdb:flush-queue))) - Index: db_records.scm ================================================================== --- db_records.scm +++ db_records.scm @@ -118,5 +118,19 @@ (define-inline (db:step-stable-set-runtime! vec val)(vector-set! vec 4 val)) ;; use this one for db-get-run-info (define-inline (db:get-row vec)(vector-ref vec 1)) +;; The data structure for handing off requests via wire +(define (make-cdb:packet)(make-vector 6)) +(define-inline (cdb:packet-get-client-sig vec) (vector-ref vec 0)) +(define-inline (cdb:packet-get-qtype vec) (vector-ref vec 1)) +(define-inline (cdb:packet-get-immediate vec) (vector-ref vec 2)) +(define-inline (cdb:packet-get-query-sig vec) (vector-ref vec 3)) +(define-inline (cdb:packet-get-params vec) (vector-ref vec 4)) +(define-inline (cdb:packet-get-qtime vec) (vector-ref vec 5)) +(define-inline (cdb:packet-set-client-sig! vec val)(vector-set! vec 0 val)) +(define-inline (cdb:packet-set-qtype! vec val)(vector-set! vec 1 val)) +(define-inline (cdb:packet-set-immediate! vec val)(vector-set! vec 2 val)) +(define-inline (cdb:packet-set-query-sig! vec val)(vector-set! vec 3 val)) +(define-inline (cdb:packet-set-params! vec val)(vector-set! vec 4 val)) +(define-inline (cdb:packet-set-qtime! vec val)(vector-set! vec 5 val)) Index: items.scm ================================================================== --- items.scm +++ items.scm @@ -75,11 +75,11 @@ (items (cadr x))) (list name (string-split items))))) itemsdat)))) (let ((debuglevel 5)) (debug:print 5 "item-assoc->item-list: itemsdat => itemlst ") - (if (>= *verbosity* 5) + (if (debug:debug-mode 5) (begin (pp itemsdat) (print " => ") (pp itemlst)))) (if (> (length itemlst) 0) @@ -122,11 +122,11 @@ '() #f))) res))) ;; Nope, not now, return null as of 6/6/2011 -(define (check-valid-items class item) +(define (items:check-valid-items class item) (let ((valid-values (let ((s (config-lookup *configdat* "validvalues" class))) (if s (string-split s) #f)))) (if valid-values (if (member item valid-values) item #f) Index: launch.scm ================================================================== --- launch.scm +++ launch.scm @@ -53,11 +53,11 @@ (define (launch:execute encoded-cmd) (let* ((cmdinfo (read (open-input-string (base64:base64-decode encoded-cmd))))) (setenv "MT_CMDINFO" encoded-cmd) (if (list? cmdinfo) ;; ((testpath /tmp/mrwellan/jazzmind/src/example_run/tests/sqlitespeed) ;; (test-name sqlitespeed) (runscript runscript.rb) (db-host localhost) (run-id 1)) - (let* ((testpath (assoc/default 'testpath cmdinfo)) + (let* ((testpath (assoc/default 'testpath cmdinfo)) ;; How is testpath different from work-area ?? (top-path (assoc/default 'toppath cmdinfo)) (work-area (assoc/default 'work-area cmdinfo)) (test-name (assoc/default 'test-name cmdinfo)) (runscript (assoc/default 'runscript cmdinfo)) (ezsteps (assoc/default 'ezsteps cmdinfo)) @@ -69,11 +69,19 @@ (env-ovrd (assoc/default 'env-ovrd cmdinfo)) (set-vars (assoc/default 'set-vars cmdinfo)) ;; pre-overrides from -setvar (runname (assoc/default 'runname cmdinfo)) (megatest (assoc/default 'megatest cmdinfo)) (mt-bindir-path (assoc/default 'mt-bindir-path cmdinfo)) - (fullrunscript (if runscript (conc testpath "/" runscript) #f)) + (fullrunscript (if (not runscript) + #f + (if (substring-index "/" runscript) + runscript ;; use unadultered if contains slashes + (let ((fulln (conc testpath "/" runscript))) + (if (and (file-exists? fulln) + (file-execute-access? fulln)) + fulln + runscript))))) ;; assume it is on the path (rollup-status 0)) (debug:print 2 "Exectuing " test-name " (id: " test-id ") on " (get-host-name)) ;; apply pre-overrides before other variables. The pre-override vars must not ;; clobbers things from the official sources such as megatest.config and runconfigs.config @@ -104,17 +112,17 @@ (exit 1))) ;; Can setup as client for server mode now (server:client-setup) (change-directory *toppath*) - (open-run-close set-megatest-env-vars #f run-id) ;; these may be needed by the launching process + (set-megatest-env-vars run-id) ;; these may be needed by the launching process (change-directory work-area) (open-run-close set-run-config-vars #f run-id) ;; environment overrides are done *before* the remaining critical envars. (alist->env-vars env-ovrd) - (open-run-close set-megatest-env-vars #f run-id) + (set-megatest-env-vars run-id) (set-item-env-vars itemdat) (save-environment-as-files "megatest") (open-run-close test-set-meta-info #f test-id run-id test-name itemdat 0) (tests:test-set-status! test-id "REMOTEHOSTSTART" "n/a" (args:get-arg "-m") #f) (if (args:get-arg "-xterm") @@ -188,11 +196,11 @@ ;; call the command using mt_ezstep (set! script (conc "mt_ezstep " stepname " " (if prevstep prevstep "-") " " stepcmd)) (debug:print 4 "script: " script) - (open-run-close db:teststep-set-status! #f test-id stepname "start" "-" #f #f) + (cdb:remote-run db:teststep-set-status! #f test-id stepname "start" "-" #f #f) ;; now launch (let ((pid (process-run script))) (let processloop ((i 0)) (let-values (((pid-val exit-status exit-code)(process-wait pid #t))) (mutex-lock! m) @@ -206,13 +214,13 @@ (processloop (+ i 1)))) )) (let ((exinfo (vector-ref exit-info 2)) (logfna (if logpro-used (conc stepname ".html") ""))) ;; testing if procedures called in a remote call cause problems (ans: no or so I suspect) - (open-run-close db:teststep-set-status! #f test-id stepname "end" exinfo #f logfna)) + (cdb:remote-run db:teststep-set-status! #f test-id stepname "end" exinfo #f logfna)) (if logpro-used - (open-run-close db:test-set-log! #f test-id (conc stepname ".html"))) + (cdb:test-set-log! *runremote* test-id (conc stepname ".html"))) ;; set the test final status (let* ((this-step-status (cond ((and (eq? (vector-ref exit-info 2) 2) logpro-used) 'warn) ((eq? (vector-ref exit-info 2) 0) 'pass) (else 'fail))) @@ -254,11 +262,11 @@ (current-seconds) start-seconds))))) (kill-tries 0)) (let loop ((minutes (calc-minutes))) (begin - (set! kill-job? (open-run-close test-get-kill-request #f test-id)) ;; run-id test-name itemdat)) + (set! kill-job? (test-get-kill-request test-id)) ;; run-id test-name itemdat)) (open-run-close test-set-meta-info #f test-id run-id test-name itemdat minutes) (if kill-job? (begin (mutex-lock! m) (let* ((pid (vector-ref exit-info 0))) @@ -275,11 +283,11 @@ (if p-id (begin (debug:print 0 "Killing " (cadr parts) "; kill -9 " p-id) (system (conc "kill -9 " p-id)))))) (car processes)) - (system (conc "kill -9 " pid)))) + (system (conc "kill -9 -" pid)))) (begin (debug:print 0 "WARNING: Request received to kill job but problem with process, attempting to kill manager process") (tests:test-set-status! test-id "KILLED" "FAIL" (args:get-arg "-m") #f) (sqlite3:finalize! tdb) @@ -295,11 +303,12 @@ (thread-start! th1) (thread-start! th2) (thread-join! th2) (mutex-lock! m) (let* ((item-path (item-list->path itemdat)) - (testinfo (open-run-close db:get-test-info-by-id #f test-id))) ;; )) ;; run-id test-name item-path))) + (testinfo (cdb:get-test-info-by-id *runremote* test-id))) ;; )) ;; run-id test-name item-path))) + ;; Am I completed? (if (not (equal? (db:test-get-state testinfo) "COMPLETED")) (begin (debug:print 2 "Test NOT logged as COMPLETED, (state=" (db:test-get-state testinfo) "), updating result, rollup-status is " rollup-status) (tests:test-set-status! test-id (if kill-job? "KILLED" "COMPLETED") @@ -385,17 +394,17 @@ ;; All log file links should be stored relative to the top of link path ;; ;; - [ - ] ;; (define (create-work-area db run-id test-id test-src-path disk-path testname itemdat) - (let* ((run-info (db:get-run-info db run-id)) + (let* ((run-info (cdb:remote-run db:get-run-info #f run-id)) (item-path (item-list->path itemdat)) (runname (db:get-value-by-header (db:get-row run-info) (db:get-header run-info) "runname")) ;; convert back to db: from rdb: - this is always run at server end - (key-vals (db:get-key-vals db run-id)) + (key-vals (cdb:remote-run db:get-key-vals #f run-id)) (target (string-intersperse key-vals "/")) (not-iterated (equal? "" item-path)) ;; all tests are found at /test-base or /test-base @@ -413,11 +422,11 @@ (lnkbase (conc linktree "/" target "/" runname)) (lnkpath (conc lnkbase "/" testname)) (lnkpathf (conc lnkpath (if not-iterated "" "/") item-path))) ;; Update the rundir path in the test record for all - (db:test-set-rundir-by-test-id! db test-id lnkpathf) + (cdb:test-set-rundir-by-test-id *runremote* test-id lnkpathf) (debug:print 2 "INFO:\n lnkbase=" lnkbase "\n lnkpath=" lnkpath "\n toptest-path=" toptest-path "\n test-path=" test-path) (if (not (file-exists? linktree)) (begin (debug:print 0 "WARNING: linktree did not exist! Creating it now at " linktree) @@ -432,28 +441,29 @@ ;; again. ;; NB - This is not working right - some top tests are not getting the path set!!! (if (not (hash-table-ref/default *toptest-paths* testname #f)) - (let* ((testinfo (db:get-test-info-by-id db test-id)) ;; run-id testname item-path)) + (let* ((testinfo (cdb:get-test-info-by-id *runremote* test-id)) ;; run-id testname item-path)) (curr-test-path (if testinfo (db:test-get-rundir testinfo) #f))) (hash-table-set! *toptest-paths* testname curr-test-path) - (db:test-set-rundir! db run-id testname "" lnkpath) ;; toptest-path) + ;; NB// Was this for the test or for the parent in an iterated test? + (cdb:test-set-rundir! *runremote* run-id testname "" lnkpath) ;; toptest-path) (if (or (not curr-test-path) (not (directory-exists? toptest-path))) (begin - (debug:print 2 "INFO: Creating " toptest-path " and link " lnkpath) + (debug:print-info 2 "Creating " toptest-path " and link " lnkpath) (create-directory toptest-path #t) (hash-table-set! *toptest-paths* testname toptest-path))))) ;; Now create the link from the test path to the link tree, however ;; if the test is iterated it is necessary to create the parent path ;; to the iteration. use pathname-directory to trim the path by one ;; level (if (not not-iterated) ;; i.e. iterated (let ((iterated-parent (pathname-directory (conc lnkpath "/" item-path)))) - (debug:print 2 "INFO: Creating iterated parent " iterated-parent) + (debug:print-info 2 "Creating iterated parent " iterated-parent) (create-directory iterated-parent #t))) (if (symbolic-link? lnkpath) (delete-file lnkpath)) (if (not (or (file-exists? lnkpath) (symbolic-link? lnkpath))) @@ -484,11 +494,11 @@ ;; (symbolic-link? testlink))) ;; (system (conc "rm -f " testlink))) ;; (system (conc "ln -sf " test-path " " testlink))) (if (directory? test-path) (begin - (let* ((cmd (conc "rsync -av" (if (> *verbosity* 1) "" "q") " " test-src-path "/ " test-path "/")) + (let* ((cmd (conc "rsync -av" (if (debug:debug-mode 1) "" "q") " " test-src-path "/ " test-path "/")) (status (system cmd))) (if (not (eq? status 0)) (debug:print 2 "ERROR: problem with running \"" cmd "\""))) (list lnkpathf lnkpath )) (list #f #f)))) @@ -537,12 +547,12 @@ (diskpath #f) (cmdparms #f) (fullcmd #f) ;; (define a (with-output-to-string (lambda ()(write x)))) (mt-bindir-path #f) (item-path (item-list->path itemdat)) - (test-id (open-run-close db:get-test-id db run-id test-name item-path)) - (testinfo (open-run-close db:get-test-info-by-id db test-id)) + (test-id (cdb:remote-run db:get-test-id #f run-id test-name item-path)) + (testinfo (cdb:get-test-info-by-id *runremote* test-id)) (mt_target (string-intersperse (map cadr keyvallst) "/")) (debug-param (append (if (args:get-arg "-debug") (list "-debug" (args:get-arg "-debug")) '()) (if (args:get-arg "-logging")(list "-logging") '())))) (if hosts (set! hosts (string-split hosts))) ;; set the megatest to be called on the remote host @@ -553,11 +563,11 @@ (set! diskpath (get-best-disk *configdat*)) (if diskpath (let ((dat (open-run-close create-work-area db run-id test-id test-path diskpath test-name itemdat))) (set! work-area (car dat)) (set! toptest-work-area (cadr dat)) - (debug:print 2 "INFO: Using work area " work-area)) + (debug:print-info 2 "Using work area " work-area)) (begin (set! work-area (conc test-path "/tmp_run")) (create-directory work-area #t) (debug:print 0 "WARNING: No disk work area specified - running in the test directory under tmp_run"))) (set! cmdparms (base64:base64-encode (with-output-to-string @@ -576,12 +586,12 @@ (list 'env-ovrd (hash-table-ref/default *configdat* "env-override" '())) (list 'set-vars (if params (hash-table-ref/default params "-setvars" #f))) (list 'runname runname) (list 'mt-bindir-path mt-bindir-path))))))) ;; (string-intersperse keyvallst " ")))) ;; clean out step records from previous run if they exist - (debug:print 4 "INFO: FIXMEEEEE!!!! This can be removed some day, perhaps move all test records to the test db?") - (open-run-close db:delete-test-step-records db test-id) + ;; (debug:print-info 4 "FIXMEEEEE!!!! This can be removed some day, perhaps move all test records to the test db?") + ;; (open-run-close db:delete-test-step-records db test-id) (change-directory work-area) ;; so that log files from the launch process don't clutter the test dir (tests:test-set-status! test-id "LAUNCHED" "n/a" #f #f) ;; (if launch-results launch-results "FAILED")) (cond ((and launcher hosts) ;; must be using ssh hostname (set! fullcmd (append launcher (car hosts)(list remote-megatest test-sig "-execute" cmdparms) debug-param))) Index: megatest-version.scm ================================================================== --- megatest-version.scm +++ megatest-version.scm @@ -1,7 +1,7 @@ ;; Always use two digit decimal ;; 1.01, 1.02...1.10,1.11 ... 1.99,2.00.. (declare (unit megatest-version)) -(define megatest-version 1.4611) +(define megatest-version 1.5209) Index: megatest.scm ================================================================== --- megatest.scm +++ megatest.scm @@ -8,13 +8,15 @@ ;; PURPOSE. ;; (include "common.scm") ;; (include "megatest-version.scm") -(use sqlite3 srfi-1 posix regex regex-case srfi-69 base64 format readline apropos) ;; (srfi 18) extras) +(use sqlite3 srfi-1 posix regex regex-case srfi-69 base64 format readline apropos ) ;; (srfi 18) extras) (import (prefix sqlite3 sqlite3:)) (import (prefix base64 base64:)) + +(use zmq) (declare (uses common)) (declare (uses megatest-version)) (declare (uses margs)) (declare (uses runs)) @@ -35,29 +37,29 @@ version " megatest-version " license GPL, Copyright Matt Welland 2006-2012 Usage: megatest [options] -h : this help + -version : print megatest version (currently " megatest-version ") Launching and managing runs -runall : run all tests that are not state COMPLETED and status PASS, CHECK or KILLED -runtests tst1,tst2 ... : run tests - -remove-runs : remove the data for a run, requires :runname, -testpatt and - -itempatt be set. Optionally use :state and :status + -remove-runs : remove the data for a run, requires :runname and -testpatt + Optionally use :state and :status -set-state-status X,Y : set state to X and status to Y, requires controls per -remove-runs -rerun FAIL,WARN... : force re-run for tests with specificed status(s) - -rollup : fill run (set by :runname) with latest test(s) from - prior runs with same keys + -rollup : (currently disabled) fill run (set by :runname) with latest test(s) + from prior runs with same keys -lock : lock run specified by target and runname -unlock : unlock run specified by target and runname Selectors (e.g. use for -runtests, -remove-runs, -set-state-status, -list-runs etc.) -target key1/key2/... : run for key1, key2, etc. -reqtarg key1/key2/... : run for key1, key2, etc. but key1/key2 must be in runconfig - -testpatt patt : % is wildcard - -itempatt patt : % is wildcard + -testpatt patt1/patt2,patt3/... : % is wildcard :runname : required, name for this particular test run :state : Applies to runs, tests or steps depending on context :status : Applies to runs, tests or steps depending on context Test helpers (for use inside tests) @@ -95,10 +97,11 @@ -env2file fname : write the environment to fname.csh and fname.sh -setvars VAR1=val1,VAR2=val2 : Add environment variables to a run NB// these are overwritten by values set in config files. -server -|hostname : start the server (reduces contention on megatest.db), use - to automatically figure out hostname + -list-servers : list the servers -repl : start a repl (useful for extending megatest) -debug N : increase verbosity to N. (try 10 for lots of noise) -logging : turn on logging all debug output to logging.db Spreadsheet generation @@ -115,14 +118,15 @@ # Get test path, use '.' to get a single path or a specific path/file pattern megatest -test-files 'logs/*.log' -target ubuntu/n%/no% :runname w49% -testpatt test_mt% Called as " (string-intersperse (argv) " ") " -Built from " megatest-fossil-hash )) +Version " megatest-version ", built from " megatest-fossil-hash )) ;; -gui : start a gui interface ;; -config fname : override the runconfig file with fname +;; -kill-server host:port|pid : kill server specified by host:port or pid ;; process args (define remargs (args:get-args (argv) (list "-runtests" ;; run a specific test @@ -155,10 +159,12 @@ ":expected" ":tol" ":units" ;; misc "-server" + "-kill-server" + "-port" "-extract-ods" "-pathmod" "-env2file" "-setvars" "-set-state-status" @@ -166,10 +172,11 @@ "-gen-megatest-test" "-override-timeout" "-test-files" ;; -test-paths is for listing all ) (list "-h" + "-version" "-force" "-xterm" "-showkeys" "-test-status" "-set-values" @@ -179,10 +186,16 @@ ;; misc "-archive" "-repl" "-lock" "-unlock" + "-list-servers" + ;; mist queries + "-list-disks" + "-list-targets" + "-list-db-targets" + "-show-runconfig" ;; queries "-test-paths" ;; get path(s) to a test, ordered by youngest first "-runall" ;; run all tests "-remove-runs" @@ -190,10 +203,11 @@ "-rebuild-db" "-rollup" "-update-meta" "-gen-megatest-area" + "-logging" "-v" ;; verbose 2, more than normal (normal is 1) "-q" ;; quiet 0, errors/warnings only "-logging" ) args:arg-hash @@ -201,27 +215,53 @@ (if (args:get-arg "-h") (begin (print help) (exit))) + +(if (args:get-arg "-version") + (begin + (print megatest-version) + (exit))) (define *didsomething* #f) ;;====================================================================== ;; Misc setup stuff ;;====================================================================== -(set! *verbosity* (cond - ((string? (args:get-arg "-debug"))(string->number (args:get-arg "-debug"))) - ((args:get-arg "-v") 2) - ((args:get-arg "-q") 0) - (else 1))) - -(if (not (number? *verbosity*)) - (begin - (print "ERROR: Invalid debug value " (args:get-arg "-debug")) - (exit))) +(debug:setup) + +(if (args:get-arg "-logging")(set! *logging* #t)) + +(if (debug:debug-mode 3) ;; we are obviously debugging + (set! open-run-close open-run-close-no-exception-handling)) + +;; a,b,c % => a/%,b/%,c/% +(define (tack-on-patt srcstr patt) + (let ((strlst (string-split srcstr ","))) + (string-intersperse + (map (lambda (str) + (if (not (substring-index "/" str)) + (conc str "/" patt) + str)) + strlst) + ","))) + +;; to try and not burden Kim too much... +(if (args:get-arg "-itempatt") + (let ((old-testpatt (args:get-arg "-testpatt"))) + ;; (debug:print 0 "ERROR: parameter \"-itempatt\" has been deprecated. For now I will tweak your -testpatt for you") + (if (args:get-arg "-testpatt") + (hash-table-set! args:arg-hash "-testpatt" (tack-on-patt old-testpatt (args:get-arg "-itempatt")))) + ;; (debug:print 0 " old: " old-testpatt ", new: " (args:get-arg "-testpatt")) + (if (args:get-arg "-runtests") + (begin + ;; (debug:print 0 "NOTE: Also modifying -runtests") + (hash-table-set! args:arg-hash "-runtests" (tack-on-patt (args:get-arg "-runtests") + (args:get-arg "-itempatt"))))) + )) (if (args:get-arg "-logging")(set! *logging* #t)) ;;====================================================================== ;; Misc general calls @@ -229,10 +269,101 @@ (if (args:get-arg "-env2file") (begin (save-environment-as-files (args:get-arg "-env2file")) (set! *didsomething* #t))) + +(if (args:get-arg "-list-disks") + (begin + (print + (string-intersperse + (map (lambda (x) + (string-intersperse + x + " => ")) + (common:get-disks) ) + "\n")) + (set! *didsomething* #t))) + +;;====================================================================== +;; Start the server - can be done in conjunction with -runall or -runtests (one day...) +;; we start the server if not running else start the client thread +;;====================================================================== + +(if (args:get-arg "-server") + (begin + (debug:print 2 "Launching server...") + (server:launch))) + +(if (args:get-arg "-list-servers") + ;; (args:get-arg "-kill-server")) + (let ((tl (setup-for-run))) + (if tl + (let ((servers (open-run-close tasks:get-all-servers tasks:open-db)) + (fmtstr "~5a~8a~8a~20a~20a~10a~10a~10a~10a\n") + (servers-to-kill '())) + (format #t fmtstr "Id" "MTver" "Pid" "Host" "Interface" "OutPort" "InPort" "LastBeat" "State") + (format #t fmtstr "==" "=====" "===" "====" "=========" "=======" "======" "========" "=====") + (for-each + (lambda (server) + (let* (;; (killinfo (args:get-arg "-kill-server")) + ;; (khost-port (if killinfo (if (substring-index ":" killinfo)(string-split ":") #f) #f)) + ;; (kpid (if killinfo (if (substring-index ":" killinfo) #f (string->number killinfo)) #f)) + (id (vector-ref server 0)) + (pid (vector-ref server 1)) + (hostname (vector-ref server 2)) + (interface (vector-ref server 3)) + (pullport (vector-ref server 4)) + (pubport (vector-ref server 5)) + (start-time (vector-ref server 6)) + (priority (vector-ref server 7)) + (state (vector-ref server 8)) + (mt-ver (vector-ref server 9)) + (last-update (vector-ref server 10)) ;; (open-run-close tasks:server-alive? tasks:open-db #f hostname: hostname port: port)) + (killed #f) + (status (< last-update 20))) + ;; (zmq-sockets (if status (server:client-connect hostname port) #f))) + ;; no need to login as status of #t indicates we are connecting to correct + ;; server + (if (equal? state "dead") + (if (> last-update (* 25 60 60)) ;; keep records around for slighly over a day. + (open-run-close tasks:server-deregister tasks:open-db hostname pullport: pullport pid: pid action: 'delete)) + (if (> last-update 20) ;; Mark as dead if not updated in last 20 seconds + (open-run-close tasks:server-deregister tasks:open-db hostname pullport: pullport pid: pid))) + + (format #t fmtstr id mt-ver pid hostname interface pullport pubport last-update + (if status "alive" "dead")))) + servers) + (debug:print-info 1 "Done with listservers") + (set! *didsomething* #t) + (exit) ;; must do, would have to add checks to many/all calls below + ) + (exit))) + ;; if not list or kill then start a client (if appropriate) + (if (or (args-defined? "-h" "-version" "-gen-megatest-area" "-gen-megatest-test") + (eq? (length (hash-table-keys args:arg-hash)) 0)) + (debug:print-info 1 "Server connection not needed") + + (server:client-launch))) + +;;====================================================================== +;; Weird special calls that need to run *after* the server has started? +;;====================================================================== + +(if (args:get-arg "-list-targets") + (let ((targets (common:get-runconfig-targets))) + (print "Found "(length targets) " targets") + (for-each (lambda (x) + ;; (print "[" x "]")) + (print x)) + targets) + (set! *didsomething* #t))) + +(if (args:get-arg "-show-runconfig") + (begin + (pp (hash-table->alist (open-run-close setup-env-defaults #f "runconfigs.config" #f #f change-env: #f))) + (set! *didsomething* #t))) ;;====================================================================== ;; Remove old run(s) ;;====================================================================== @@ -244,23 +375,19 @@ (debug:print 0 "ERROR: Missing required parameter for " action ", you must specify the run name pattern with :runname patt") (exit 2)) ((not (args:get-arg "-testpatt")) (debug:print 0 "ERROR: Missing required parameter for " action ", you must specify the test pattern with -testpatt") (exit 3)) - ((not (args:get-arg "-itempatt")) - (print "ERROR: Missing required parameter for " action ", you must specify the items with -itempatt") - (exit 4)) (else (if (not (car *configinfo*)) (begin (debug:print 0 "ERROR: Attempted " action "on test(s) but run area config file not found") (exit 1)) ;; put test parameters into convenient variables (runs:operate-on action (args:get-arg ":runname") (args:get-arg "-testpatt") - (args:get-arg "-itempatt") state: (args:get-arg ":state") status: (args:get-arg ":status") new-state-status: (args:get-arg "-set-state-status"))) (set! *didsomething* #t)))) @@ -280,88 +407,78 @@ ;;====================================================================== ;; Query runs ;;====================================================================== -(if (args:get-arg "-list-runs") +(if (or (args:get-arg "-list-runs") + (args:get-arg "-list-db-targets")) (if (setup-for-run) (let* ((db #f) (runpatt (args:get-arg "-list-runs")) - (testpatt (args:get-arg "-testpatt")) - (itempatt (args:get-arg "-itempatt")) + (testpatt (if (args:get-arg "-testpatt") + (args:get-arg "-testpatt") + "%")) (runsdat (open-run-close db:get-runs db runpatt #f #f '())) (runs (db:get-rows runsdat)) (header (db:get-header runsdat)) (keys (open-run-close db:get-keys db)) - (keynames (map key:get-fieldname keys))) + (keynames (map key:get-fieldname keys)) + (db-targets (args:get-arg "-list-db-targets")) + (seen (make-hash-table))) ;; Each run (for-each (lambda (run) - (debug:print 1 "Run: " - (string-intersperse (map (lambda (x) - (db:get-value-by-header run header x)) - keynames) "/") - "/" - (db:get-value-by-header run header "runname") - " status: " (db:get-value-by-header run header "state")) - (let ((run-id (open-run-close db:get-value-by-header run header "id"))) - (let ((tests (open-run-close db:get-tests-for-run db run-id testpatt itempatt '() '()))) - ;; Each test - (for-each - (lambda (test) - (format #t - " Test: ~25a State: ~15a Status: ~15a Runtime: ~5@as Time: ~22a Host: ~10a\n" - (conc (db:test-get-testname test) - (if (equal? (db:test-get-item-path test) "") - "" - (conc "(" (db:test-get-item-path test) ")"))) - (db:test-get-state test) - (db:test-get-status test) - (db:test-get-run_duration test) - (db:test-get-event_time test) - (db:test-get-host test)) - (if (not (or (equal? (db:test-get-status test) "PASS") - (equal? (db:test-get-status test) "WARN") - (equal? (db:test-get-state test) "NOT_STARTED"))) - (begin - (print " cpuload: " (db:test-get-cpuload test) - "\n diskfree: " (db:test-get-diskfree test) - "\n uname: " (db:test-get-uname test) - "\n rundir: " (db:test-get-rundir test) - ) - ;; Each test - (let ((steps (open-run-close db:get-steps-for-test db (db:test-get-id test)))) - (for-each - (lambda (step) - (format #t - " Step: ~20a State: ~10a Status: ~10a Time ~22a\n" - (db:step-get-stepname step) - (db:step-get-state step) - (db:step-get-status step) - (db:step-get-event_time step))) - steps))))) - tests)))) - runs) - (set! *didsomething* #t) - ))) - -;;====================================================================== -;; Start the server - can be done in conjunction with -runall or -runtests (one day...) -;;====================================================================== -(if (args:get-arg "-server") - (let* ((toppath (setup-for-run)) - (db (if toppath (open-db) #f))) - (debug:print 0 "INFO: Starting the standalone server") - (if db - (let* ((host:port (db:get-var db "SERVER")) ;; this doen't support multiple servers BUG!!!! - (th2 (server:start db (args:get-arg "-server"))) - (th3 (make-thread (lambda () - (server:keep-running db host:port))))) - (thread-start! th3) - (thread-join! th3) - (set! *didsomething* #t)) - (debug:print 0 "ERROR: Failed to setup for megatest")))) + (let ((targetstr (string-intersperse (map (lambda (x) + (db:get-value-by-header run header x)) + keynames) "/"))) + (if db-targets + (if (not (hash-table-ref/default seen targetstr #f)) + (begin + (hash-table-set! seen targetstr #t) + ;; (print "[" targetstr "]")))) + (print targetstr)))) + (if (not db-targets) + (let* ((run-id (open-run-close db:get-value-by-header run header "id")) + (tests (open-run-close db:get-tests-for-run db run-id testpatt '() '()))) + (debug:print 1 "Run: " targetstr " status: " (db:get-value-by-header run header "state") + " run-id: " run-id ", number tests: " (length tests)) + (for-each + (lambda (test) + (format #t + " Test: ~25a State: ~15a Status: ~15a Runtime: ~5@as Time: ~22a Host: ~10a\n" + (conc (db:test-get-testname test) + (if (equal? (db:test-get-item-path test) "") + "" + (conc "(" (db:test-get-item-path test) ")"))) + (db:test-get-state test) + (db:test-get-status test) + (db:test-get-run_duration test) + (db:test-get-event_time test) + (db:test-get-host test)) + (if (not (or (equal? (db:test-get-status test) "PASS") + (equal? (db:test-get-status test) "WARN") + (equal? (db:test-get-state test) "NOT_STARTED"))) + (begin + (print " cpuload: " (db:test-get-cpuload test) + "\n diskfree: " (db:test-get-diskfree test) + "\n uname: " (db:test-get-uname test) + "\n rundir: " (db:test-get-rundir test) + ) + ;; Each test + (let ((steps (open-run-close db:get-steps-for-test db (db:test-get-id test)))) + (for-each + (lambda (step) + (format #t + " Step: ~20a State: ~10a Status: ~10a Time ~22a\n" + (db:step-get-stepname step) + (db:step-get-state step) + (db:step-get-status step) + (db:step-get-event_time step))) + steps))))) + tests))))) + runs) + (set! *didsomething* #t)))) ;;====================================================================== ;; full run ;;====================================================================== @@ -383,15 +500,16 @@ (if (args:get-arg "-runall") (general-run-call "-runall" "run all tests" (lambda (target runname keys keynames keyvallst) - (runs:run-tests target - runname - (args:get-arg "-runtests") - user - args:arg-hash)))) ;; ) + (runs:run-tests target + runname + "%" + (args:get-arg "-testpatt") + user + args:arg-hash)))) ;;====================================================================== ;; run one test ;;====================================================================== @@ -414,26 +532,30 @@ "run a test" (lambda (target runname keys keynames keyvallst) (runs:run-tests target runname (args:get-arg "-runtests") + (args:get-arg "-testpatt") user args:arg-hash)))) ;;====================================================================== ;; Rollup into a run ;;====================================================================== (if (args:get-arg "-rollup") - (general-run-call - "-rollup" - "rollup tests" - (lambda (target runname keys keynames keyvallst) - (runs:rollup-run keys - (keys->alist keys "na") - (args:get-arg ":runname") - user)))) + (begin + (debug:print 0 "ERROR: Rollup is currently not working. If you need it please submit a ticket at http://www.kiatoa.com/fossils/megatest") + (exit 4))) +;; (general-run-call +;; "-rollup" +;; "rollup tests" +;; (lambda (target runname keys keynames keyvallst) +;; (runs:rollup-run keys +;; (keys->alist keys "na") +;; (args:get-arg ":runname") +;; user)))) ;;====================================================================== ;; Lock or unlock a run ;;====================================================================== @@ -477,12 +599,11 @@ (exit 1))) (if (not (setup-for-run)) (begin (debug:print 0 "Failed to setup, giving up on -test-paths or -test-files, exiting") (exit 1))) - (let* ((itempatt (args:get-arg "-itempatt")) - (keys (open-run-close db:get-keys db)) + (let* ((keys (open-run-close db:get-keys db)) (keynames (map key:get-fieldname keys)) (paths (open-run-close db:test-get-paths-matching db keynames target (args:get-arg "-test-files")))) (set! *didsomething* #t) (for-each (lambda (path) (print path)) @@ -647,11 +768,11 @@ (if (args:get-arg "-load-test-data") ;; has sub commands that are rdb: (open-run-close db:load-test-data db test-id)) (if (args:get-arg "-setlog") (let ((logfname (args:get-arg "-setlog"))) - (open-run-close db:test-set-log! db test-id logfname))) + (cdb:test-set-log! *runremote* test-id logfname))) (if (args:get-arg "-set-toplog") (open-run-close tests:test-set-toplog! db run-id test-name (args:get-arg "-set-toplog"))) (if (args:get-arg "-summarize-items") (open-run-close tests:summarize-items db run-id test-name #t)) ;; do force here (if (args:get-arg "-runstep") @@ -675,26 +796,26 @@ (cons cmd params) " ") ") " redir " " logfile))) ;; mark the start of the test (open-run-close db:teststep-set-status! db test-id stepname "start" "n/a" (args:get-arg "-m") logfile) ;; run the test step - (debug:print 2 "INFO: Running \"" fullcmd "\"") + (debug:print-info 2 "Running \"" fullcmd "\"") (change-directory startingdir) (set! exitstat (system fullcmd)) ;; cmd params)) (set! *globalexitstatus* exitstat) (change-directory testpath) ;; run logpro if applicable ;; (process-run "ls" (list "/foo" "2>&1" "blah.log")) (if logprofile (let* ((htmllogfile (conc stepname ".html")) (oldexitstat exitstat) (cmd (string-intersperse (list "logpro" logprofile htmllogfile "<" logfile ">" (conc stepname "_logpro.log")) " "))) - (debug:print 2 "INFO: running \"" cmd "\"") + (debug:print-info 2 "running \"" cmd "\"") (change-directory startingdir) (set! exitstat (system cmd)) (set! *globalexitstatus* exitstat) ;; no necessary (change-directory testpath) - (open-run-close db:test-set-log! db test-id htmllogfile))) + (cdb:test-set-log! *runremote* test-id htmllogfile))) (let ((msg (args:get-arg "-m"))) (open-run-close db:teststep-set-status! db test-id stepname "end" exitstat msg logfile)) ))) (if (or (args:get-arg "-test-status") (args:get-arg "-set-values")) @@ -792,24 +913,30 @@ (let* ((toppath (setup-for-run)) (db (if toppath (open-db) #f))) (if db (begin (set! *db* db) - (if (not (args:get-arg "-server")) - (server:client-setup)) + (set! *client-non-blocking-mode* #t) + (server:client-setup) (import readline) (import apropos) (gnu-history-install-file-manager (string-append (or (get-environment-variable "HOME") ".") "/.megatest_history")) (current-input-port (make-gnu-readline-port "megatest> ")) - (repl))) + (repl)) + (exit)) (set! *didsomething* #t))) ;;====================================================================== ;; Exit and clean up ;;====================================================================== + +;; this is the socket if we are a client +;; (if (and *runremote* +;; (socket? *runremote*)) +;; (close-socket *runremote*)) (if (not *didsomething*) (debug:print 0 help)) ;; (if *runremote* (rpc:close-all-connections!)) Index: runconfig.scm ================================================================== --- runconfig.scm +++ runconfig.scm @@ -10,45 +10,57 @@ (include "common_records.scm") -(define (setup-env-defaults db fname run-id already-seen #!key (environ-patt #f)) +(define (setup-env-defaults db fname run-id already-seen #!key (environ-patt #f)(change-env #t)) (let* ((keys (db:get-keys db)) - (keyvals (db:get-key-vals db run-id)) - (thekey (string-intersperse (map (lambda (x)(if x x "-na-")) keyvals) "/")) + (keyvals (if run-id (db:get-key-vals db run-id) #f)) + (thekey (if keyvals (string-intersperse (map (lambda (x)(if x x "-na-")) keyvals) "/") + (if (args:get-arg "-reqtarg") + (args:get-arg "-reqtarg") + (if (args:get-arg "-target") + (args:get-arg "-target") + (begin + (debug:print 0 "ERROR: setup-env-defaults called with no run-id or -target or -reqtarg") + "nothing matches this I hope"))))) ;; Why was system disallowed in the reading of the runconfigs file? ;; NOTE: Should be setting env vars based on (target|default) (confdat (read-config fname #f #t environ-patt: environ-patt sections: (list "default" thekey))) (whatfound (make-hash-table)) + (finaldat (make-hash-table)) (sections (list "default" thekey))) (if (not *target*)(set! *target* thekey)) ;; may save a db access or two but repeats db:get-target code (debug:print 4 "Using key=\"" thekey "\"") - (for-each - (lambda (key val) - (setenv (vector-ref key 0) val)) - keys keyvals) - + (if change-env + (for-each + (lambda (key val) + (setenv (vector-ref key 0) val)) + keys keyvals)) + (for-each (lambda (section) (let ((section-dat (hash-table-ref/default confdat section #f))) (if section-dat (for-each (lambda (envvar) + (let ((val (cadr (assoc envvar section-dat)))) (hash-table-set! whatfound section (+ (hash-table-ref/default whatfound section 0) 1)) - (setenv envvar (cadr (assoc envvar section-dat)))) + (if change-env (setenv envvar val)) + (hash-table-set! finaldat envvar val))) (map car section-dat))))) sections) (if already-seen (begin (debug:print 2 "Key settings found in runconfig.config:") (for-each (lambda (fullkey) (debug:print 2 (format #f "~20a ~a\n" fullkey (hash-table-ref/default whatfound fullkey 0)))) sections) (debug:print 2 "---") - (set! *already-seen-runconfig-info* #t))))) + (set! *already-seen-runconfig-info* #t))) + finaldat)) (define (set-run-config-vars db run-id) (let ((runconfigf (conc *toppath* "/runconfigs.config")) (targ (or (args:get-arg "-target") (args:get-arg "-reqtarg") Index: runs.scm ================================================================== --- runs.scm +++ runs.scm @@ -1,7 +1,7 @@ -;; Copyright 2006-2011, Matthew Welland. +;; Copyright 2006-2012, Matthew Welland. ;; ;; This program is made available under the GNU GPL version 2.0 or ;; greater. See the accompanying file COPYING for details. ;; ;; This program is distributed WITHOUT ANY WARRANTY; without even the @@ -39,11 +39,12 @@ (tmp (runs:get-std-run-fields keys '("id" "runname" "state" "status" "owner" "event_time"))) (keystr (car tmp)) (header (cadr tmp)) (res '()) (key-patt "") - (runwildtype (if (substring-index "%" runnamepatt) "like" "glob"))) + (runwildtype (if (substring-index "%" runnamepatt) "like" "glob")) + (qry-str #f)) (for-each (lambda (keyval) (let* ((key (vector-ref keyval 0)) (fulkey (conc ":" key)) (patt (args:get-arg fulkey)) (wildtype (if (substring-index "%" patt) "like" "glob"))) @@ -51,61 +52,66 @@ (set! key-patt (conc key-patt " AND " key " " wildtype " '" patt "'")) (begin (debug:print 0 "ERROR: searching for runs with no pattern set for " fulkey) (exit 6))))) keys) + (set! qry-str (conc "SELECT " keystr " FROM runs WHERE runname " runwildtype " ? " key-patt ";")) + (debug:print-info 4 "runs:get-runs-by-patt qry=" qry-str " " runnamepatt) (sqlite3:for-each-row (lambda (a . r) (set! res (cons (list->vector (cons a r)) res))) db - (conc "SELECT " keystr " FROM runs WHERE runname " runwildtype " ? " key-patt ";") + qry-str runnamepatt) (vector header res))) (define (runs:test-get-full-path test) (let* ((testname (db:test-get-testname test)) (itempath (db:test-get-item-path test))) (conc testname (if (equal? itempath "") "" (conc "(" itempath ")"))))) -;; Awful. Please FIXME -(define *env-vars-by-run-id* (make-hash-table)) -(define *current-run-name* #f) +(define (db:get-run-key-val db run-id key) + (let ((res #f)) + (sqlite3:for-each-row + (lambda (val) + (set! res val)) + db + (conc "SELECT " (key:get-fieldname key) " FROM runs WHERE id=?;") + run-id) + res)) + +(define (db:get-run-name-from-id db run-id) + (let ((res #f)) + (sqlite3:for-each-row + (lambda (runname) + (set! res runname)) + db + "SELECT runname FROM runs WHERE id=?;" + run-id) + res)) -(define (set-megatest-env-vars db run-id) - (let ((keys (db:get-keys db)) +(define (set-megatest-env-vars run-id) + (let ((keys (cdb:remote-run db:get-keys #f)) (vals (hash-table-ref/default *env-vars-by-run-id* run-id #f))) ;; get the info from the db and put it in the cache (if (not vals) (let ((ht (make-hash-table))) (hash-table-set! *env-vars-by-run-id* run-id ht) (set! vals ht) (for-each (lambda (key) - (sqlite3:for-each-row - (lambda (val) - (hash-table-set! vals key val)) - db - (conc "SELECT " (key:get-fieldname key) " FROM runs WHERE id=?;") - run-id)) + (hash-table-set! vals key (cdb:remote-run db:get-run-key-val #f run-id key))) keys))) ;; from the cached data set the vars (hash-table-for-each vals (lambda (key val) (debug:print 2 "setenv " (key:get-fieldname key) " " val) (setenv (key:get-fieldname key) val))) (alist->env-vars (hash-table-ref/default *configdat* "env-override" '())) ;; Lets use this as an opportunity to put MT_RUNNAME in the environment - (if (not *current-run-name*) - (sqlite3:for-each-row - (lambda (runname) - (set! *current-run-name* runname)) - - db - "SELECT runname FROM runs WHERE id=?;" - run-id)) - (setenv "MT_RUNNAME" *current-run-name*) + (setenv "MT_RUNNAME" (cdb:remote-run db:get-run-name-from-id #f run-id)) (setenv "MT_RUN_AREA_HOME" *toppath*) )) (define (set-item-env-vars itemdat) (for-each (lambda (item) @@ -112,19 +118,19 @@ (debug:print 2 "setenv " (car item) " " (cadr item)) (setenv (car item) (cadr item))) itemdat)) (define *last-num-running-tests* 0) -(define (runs:can-run-more-tests db test-record) +(define (runs:can-run-more-tests test-record) (let* ((tconfig (tests:testqueue-get-testconfig test-record)) (jobgroup (config-lookup tconfig "requirements" "jobgroup")) - (num-running (db:get-count-tests-running db)) - (num-running-in-jobgroup (db:get-count-tests-running-in-jobgroup db jobgroup)) + (num-running (cdb:remote-run db:get-count-tests-running #f)) + (num-running-in-jobgroup (cdb:remote-run db:get-count-tests-running-in-jobgroup #f jobgroup)) (max-concurrent-jobs (let ((mcj (config-lookup *configdat* "setup" "max_concurrent_jobs"))) (if (and mcj (string->number mcj)) (string->number mcj) - #f))) + 1))) (job-group-limit (config-lookup *configdat* "jobgroups" jobgroup))) (if (not (eq? *last-num-running-tests* num-running)) (begin (debug:print 2 "max-concurrent-jobs: " max-concurrent-jobs ", num-running: " num-running) (set! *last-num-running-tests* num-running))) @@ -183,65 +189,61 @@ (begin (debug:print 0 "ERROR: Called without all necessary keys") #f)))) ;; This is a duplicate of run-tests (which has been deprecated). Use this one instead of run tests. -;; keyvals -(define (runs:run-tests target runname test-patts user flags) +;; keyvals. +;; +;; test-names: Comma separated patterns same as test-patts but used in selection +;; of tests to run. The item portions are not respected. +;; FIXME: error out if /patt specified +;; +(define (runs:run-tests target runname test-names test-patts user flags) + (common:clear-caches) ;; clear all caches (let* ((db #f) - (keys (open-run-close db:get-keys db)) + (keys (cdb:remote-run db:get-keys #f)) (keyvallst (keys:target->keyval keys target)) - (run-id (open-run-close runs:register-run db keys keyvallst runname "new" "n/a" user)) ;; test-name))) + (run-id (cdb:remote-run runs:register-run #f keys keyvallst runname "new" "n/a" user)) ;; test-name))) (deferred '()) ;; delay running these since they have a waiton clause ;; keepgoing is the defacto modality now, will add hit-n-run a bit later ;; (keepgoing (hash-table-ref/default flags "-keepgoing" #f)) - (test-names '()) (runconfigf (conc *toppath* "/runconfigs.config")) (required-tests '()) (test-records (make-hash-table))) - (open-run-close set-megatest-env-vars db run-id) ;; these may be needed by the launching process + (set-megatest-env-vars run-id) ;; these may be needed by the launching process (if (file-exists? runconfigf) (open-run-close setup-env-defaults db runconfigf run-id *already-seen-runconfig-info* "pre-launch-env-vars") (debug:print 0 "WARNING: You do not have a run config file: " runconfigf)) ;; look up all tests matching the comma separated list of globs in ;; test-patts (using % as wildcard) - (for-each - (lambda (patt) - (let ((tests (glob (conc *toppath* "/tests/" (string-translate patt "%" "*"))))) - (set! tests (filter (lambda (test)(file-exists? (conc test "/testconfig"))) tests)) - (set! test-names (append test-names - (map (lambda (testp) - (last (string-split testp "/"))) - tests))))) - (if test-patts (string-split test-patts ",")(list "%"))) - - ;; now remove duplicates + + (set! test-names (tests:get-valid-tests *toppath* test-names)) (set! test-names (delete-duplicates test-names)) - (debug:print 0 "INFO: test names " test-names) + (debug:print-info 0 "test names " test-names) ;; on the first pass or call to run-tests set FAILS to NOT_STARTED if ;; -keepgoing is specified (if (eq? *passnum* 0) (begin ;; have to delete test records where NOT_STARTED since they can cause -keepgoing to ;; get stuck due to becoming inaccessible from a failed test. I.e. if test B depends ;; on test A but test B reached the point on being registered as NOT_STARTED and test ;; A failed for some reason then on re-run using -keepgoing the run can never complete. - (open-run-close db:delete-tests-in-state db run-id "NOT_STARTED") - (open-run-close db:set-tests-state-status db run-id test-names #f "FAIL" "NOT_STARTED" "FAIL"))) + (cdb:delete-tests-in-state *runremote* run-id "NOT_STARTED") + (cdb:remote-run db:set-tests-state-status #f run-id test-names #f "FAIL" "NOT_STARTED" "FAIL"))) ;; from here on out the db will be opened and closed on every call runs:run-tests-queue ;; (sqlite3:finalize! db) ;; now add non-directly referenced dependencies (i.e. waiton) (if (not (null? test-names)) (let loop ((hed (car test-names)) (tal (cdr test-names))) ;; 'return-procs tells the config reader to prep running system but return a proc - (debug:print 4 "INFO: hed=" hed " at top of loop") + (debug:print-info 4 "hed=" hed " at top of loop") (let* ((config (tests:get-testconfig hed 'return-procs)) (waitons (if config (string-split (let ((w (config-lookup config "requirements" "waiton"))) (if w w ""))) (begin (debug:print 0 "ERROR: non-existent required test \"" hed "\"") @@ -267,23 +269,23 @@ ;; process can know to call items:get-items-from-config ;; if either is a list and none is a proc go ahead and call get-items ;; otherwise return #f - this is not an iterated test (cond ((procedure? items) - (debug:print 4 "INFO: items is a procedure, will calc later") + (debug:print-info 4 "items is a procedure, will calc later") items) ;; calc later ((procedure? itemstable) - (debug:print 4 "INFO: itemstable is a procedure, will calc later") + (debug:print-info 4 "itemstable is a procedure, will calc later") itemstable) ;; calc later ((filter (lambda (x) (let ((val (car x))) (if (procedure? val) val #f))) (append (if (list? items) items '()) (if (list? itemstable) itemstable '()))) 'have-procedure) ((or (list? items)(list? itemstable)) ;; calc now - (debug:print 4 "INFO: items and itemstable are lists, calc now\n" + (debug:print-info 4 "items and itemstable are lists, calc now\n" " items: " items " itemstable: " itemstable) (items:get-items-from-config config)) (else #f))) ;; not iterated #f ;; itemsdat 5 #f ;; spare - used for item-path @@ -298,15 +300,15 @@ (let ((remtests (delete-duplicates (append waitons tal)))) (if (not (null? remtests)) (loop (car remtests)(cdr remtests))))))) (if (not (null? required-tests)) - (debug:print 1 "INFO: Adding " required-tests " to the run queue")) + (debug:print-info 1 "Adding " required-tests " to the run queue")) ;; NOTE: these are all parent tests, items are not expanded yet. - (debug:print 4 "INFO: test-records=" (hash-table->alist test-records)) - (runs:run-tests-queue run-id runname test-records keyvallst flags) - (debug:print 4 "INFO: All done by here"))) + (debug:print-info 4 "test-records=" (hash-table->alist test-records)) + (runs:run-tests-queue run-id runname test-records keyvallst flags test-patts) + (debug:print-info 4 "All done by here"))) (define (runs:calc-fails prereqs-not-met) (filter (lambda (test) (and (vector? test) ;; not (string? test)) (equal? (db:test-get-state test) "COMPLETED") @@ -330,25 +332,24 @@ (define (runs:make-full-test-name testname itempath) (if (equal? itempath "") testname (conc testname "/" itempath))) ;; test-records is a hash table testname:item_path => vector < testname testconfig waitons priority items-info ... > -(define (runs:run-tests-queue run-id runname test-records keyvallst flags) +(define (runs:run-tests-queue run-id runname test-records keyvallst flags test-patts) ;; At this point the list of parent tests is expanded ;; NB// Should expand items here and then insert into the run queue. (debug:print 5 "test-records: " test-records ", keyvallst: " keyvallst " flags: " (hash-table->alist flags)) (let ((sorted-test-names (tests:sort-by-priority-and-waiton test-records)) - (item-patts (hash-table-ref/default flags "-itempatt" #f)) (test-registery (make-hash-table)) (num-retries 0) (max-retries (config-lookup *configdat* "setup" "maxretries"))) (set! max-retries (if (and max-retries (string->number max-retries))(string->number max-retries) 100)) (if (not (null? sorted-test-names)) (let loop ((hed (car sorted-test-names)) (tal (cdr sorted-test-names)) (reruns '())) - (if (not (null? reruns))(debug:print 4 "INFO: reruns=" reruns)) + (if (not (null? reruns))(debug:print-info 4 "reruns=" reruns)) ;; (print "Top of loop, hed=" hed ", tal=" tal " ,reruns=" reruns) (let* ((test-record (hash-table-ref test-records hed)) (test-name (tests:testqueue-get-testname test-record)) (tconfig (tests:testqueue-get-testconfig test-record)) (testmode (let ((m (config-lookup tconfig "requirements" "mode"))) @@ -378,50 +379,51 @@ (debug:print 0 "ERROR: test " test-name " has listed itself as a waiton, please correct this!") (set! waiton (filter (lambda (x)(not (equal? x hed))) waitons)))) (cond ;; OUTER COND ((not items) ;; when false the test is ok to be handed off to launch (but not before) - (let* ((run-limits-info (open-run-close runs:can-run-more-tests #f test-record)) ;; look at the test jobgroup and tot jobs running + (let* ((run-limits-info (open-run-close runs:can-run-more-tests test-record)) ;; look at the test jobgroup and tot jobs running (have-resources (car run-limits-info)) (num-running (list-ref run-limits-info 1)) (num-running-in-jobgroup (list-ref run-limits-info 2)) (max-concurrent-jobs (list-ref run-limits-info 3)) (job-group-limit (list-ref run-limits-info 4)) (prereqs-not-met (open-run-close db:get-prereqs-not-met #f run-id waitons item-path mode: testmode)) (fails (runs:calc-fails prereqs-not-met)) (non-completed (runs:calc-not-completed prereqs-not-met))) - (debug:print 8 "INFO: have-resources: " have-resources " prereqs-not-met: " + (debug:print-info 8 "have-resources: " have-resources " prereqs-not-met: " (string-intersperse (map (lambda (t) (if (vector? t) (conc (db:test-get-state t) "/" (db:test-get-status t)) (conc " WARNING: t is not a vector=" t ))) prereqs-not-met) ", ") " fails: " fails) - (debug:print 4 "INFO: hed=" hed) + (debug:print-info 4 "hed=" hed "\n test-record=" test-record "\n test-name: " test-name "\n item-path: " item-path "\n test-patts: " test-patts) ;; Don't know at this time if the test have been launched at some time in the past ;; i.e. is this a re-launch? - (debug:print 4 "INFO: run-limits-info = " run-limits-info) + (debug:print-info 4 "run-limits-info = " run-limits-info) (cond ;; INNER COND #1 for a launchable test ;; Check item path against item-patts - ((and (not (patt-list-match item-path item-patts)) - (not (equal? item-path ""))) + ((not (tests:match test-patts (tests:testqueue-get-testname test-record) item-path)) ;; This test/itempath is not to be run ;; else the run is stuck, temporarily or permanently ;; but should check if it is due to lack of resources vs. prerequisites - (debug:print 1 "INFO: Skipping " (tests:testqueue-get-testname test-record) " " item-path " as it doesn't match " item-patts) + (debug:print-info 1 "Skipping " (tests:testqueue-get-testname test-record) " " item-path " as it doesn't match " test-patts) (thread-sleep! *global-delta*) (if (not (null? tal)) (loop (car tal)(cdr tal) reruns))) - ((and (not (hash-table-ref/default test-registery (runs:make-full-test-name test-name item-path) #f)) - (and max-concurrent-jobs (> (- max-concurrent-jobs num-running) 5))) + ( ;; (and + (not (hash-table-ref/default test-registery (runs:make-full-test-name test-name item-path) #f)) + ;; (and max-concurrent-jobs (> (- max-concurrent-jobs num-running) 5))) + (debug:print-info 4 "Pre-registering test " test-name "/" item-path " to create placeholder" ) (open-run-close db:tests-register-test #f run-id test-name item-path) (hash-table-set! test-registery (runs:make-full-test-name test-name item-path) #t) (thread-sleep! *global-delta*) (loop (car newtal)(cdr newtal) reruns)) ((not have-resources) ;; simply try again after waiting a second - (thread-sleep! (+ 1 *global-delta*)) - (debug:print 1 "INFO: no resources to run new tests, waiting ...") + (debug:print-info 1 "no resources to run new tests, waiting ...") + (thread-sleep! (+ 2 *global-delta*)) ;; could have done hed tal here but doing car/cdr of newtal to rotate tests (loop (car newtal)(cdr newtal) reruns)) ((and have-resources (or (null? prereqs-not-met) (and (eq? testmode 'toplevel) @@ -435,12 +437,12 @@ ;; If one or more of the prereqs-not-met are FAIL then we can issue ;; a message and drop hed from the items to be processed. (if (null? fails) (begin ;; couldn't run, take a breather - (debug:print 4 "INFO: Shouldn't really get here, race condition? Unable to launch more tests at this moment, killing time ...") - (thread-sleep! (+ 1 *global-delta*)) ;; long sleep here - no resources, may as well be patient + (debug:print-info 4 "Shouldn't really get here, race condition? Unable to launch more tests at this moment, killing time ...") + (thread-sleep! (+ 0.01 *global-delta*)) ;; long sleep here - no resources, may as well be patient ;; we made new tal by sticking hed at the back of the list (loop (car newtal)(cdr newtal) reruns)) ;; the waiton is FAIL so no point in trying to run hed ever again (if (not (null? tal)) (if (vector? hed) @@ -448,68 +450,71 @@ " from the launch list as it has prerequistes that are FAIL") (thread-sleep! *global-delta*) (loop (car tal)(cdr tal) (cons hed reruns))) (begin (debug:print 1 "WARN: Test not processed correctly. Could be a race condition in your test implementation? " hed) ;; " as it has prerequistes that are FAIL. (NOTE: hed is not a vector)") - (thread-sleep! *global-delta*) + (thread-sleep! (+ 0.01 *global-delta*)) (loop hed tal reruns))))))))) ;; END OF INNER COND ;; case where an items came in as a list been processed ((and (list? items) ;; thus we know our items are already calculated (not itemdat)) ;; and not yet expanded into the list of things to be done - (if (and (>= *verbosity* 1) + (if (and (debug:debug-mode 1) ;; (>= *verbosity* 1) (> (length items) 0) (> (length (car items)) 0)) (pp items)) (for-each (lambda (my-itemdat) (let* ((new-test-record (let ((newrec (make-tests:testqueue))) (vector-copy! test-record newrec) newrec)) (my-item-path (item-list->path my-itemdat))) - (if (patt-list-match my-item-path item-patts) ;; yes, we want to process this item, NOTE: Should not need this check here! + (if (tests:match test-patts hed my-item-path) ;; (patt-list-match my-item-path item-patts) ;; yes, we want to process this item, NOTE: Should not need this check here! (let ((newtestname (runs:make-full-test-name hed my-item-path))) ;; test names are unique on testname/item-path (tests:testqueue-set-items! new-test-record #f) (tests:testqueue-set-itemdat! new-test-record my-itemdat) (tests:testqueue-set-item_path! new-test-record my-item-path) (hash-table-set! test-records newtestname new-test-record) (set! tal (cons newtestname tal)))))) ;; since these are itemized create new test names testname/itempath items) (if (not (null? tal)) (begin - (thread-sleep! *global-delta*) - (debug:print 4 "INFO: End of items list, looping with next") + (debug:print-info 4 "End of items list, looping with next after short delay") + (thread-sleep! (+ 0.01 *global-delta*)) (loop (car tal)(cdr tal) reruns)))) ;; if items is a proc then need to run items:get-items-from-config, get the list and loop ;; - but only do that if resources exist to kick off the job ((or (procedure? items)(eq? items 'have-procedure)) - (let ((can-run-more (open-run-close runs:can-run-more-tests #f test-record))) - (if can-run-more + (let ((can-run-more (runs:can-run-more-tests test-record))) + (if (and (list? can-run-more) + (car can-run-more)) (let* ((prereqs-not-met (open-run-close db:get-prereqs-not-met #f run-id waitons item-path mode: testmode)) (fails (runs:calc-fails prereqs-not-met)) (non-completed (runs:calc-not-completed prereqs-not-met))) - (debug:print 8 "INFO: can-run-more: " can-run-more + (debug:print-info 8 "can-run-more: " can-run-more "\n testname: " hed "\n prereqs-not-met: " (runs:pretty-string prereqs-not-met) "\n non-completed: " (runs:pretty-string non-completed) "\n fails: " (runs:pretty-string fails) "\n testmode: " testmode "\n num-retries: " num-retries "\n (eq? testmode 'toplevel): " (eq? testmode 'toplevel) "\n (null? non-completed): " (null? non-completed) - "\n reruns: " reruns) - + "\n reruns: " reruns + "\n items: " items + "\n can-run-more: " can-run-more) + ;; (thread-sleep! (+ 0.01 *global-delta*)) (cond ;; INNER COND #2 ((or (null? prereqs-not-met) ;; all prereqs met, fire off the test ;; or, if it is a 'toplevel test and all prereqs not met are COMPLETED then launch (and (eq? testmode 'toplevel) (null? non-completed))) (let ((test-name (tests:testqueue-get-testname test-record))) (setenv "MT_TEST_NAME" test-name) ;; (setenv "MT_RUNNAME" runname) - (open-run-close set-megatest-env-vars #f run-id) ;; these may be needed by the launching process + (set-megatest-env-vars run-id) ;; these may be needed by the launching process (let ((items-list (items:get-items-from-config tconfig))) (if (list? items-list) (begin (tests:testqueue-set-items! test-record items-list) (thread-sleep! *global-delta*) @@ -516,57 +521,66 @@ (loop hed tal reruns)) (begin (debug:print 0 "ERROR: The proc from reading the setup did not yield a list - please report this") (exit 1)))))) ((null? fails) - (debug:print 4 "INFO: fails is null, moving on in the queue but keeping " hed " for now") - (thread-sleep! *global-delta*) - (loop (car newtal)(cdr newtal) reruns)) ;; an issue with prereqs not yet met? + (debug:print-info 4 "fails is null, moving on in the queue but keeping " hed " for now") + ;; only increment num-retries when there are no tests runing + (if (eq? 0 (list-ref can-run-more 1)) + (begin + (if (> num-retries 100) ;; first 100 retries are low time cost + (thread-sleep! (+ 2 *global-delta*)) + (thread-sleep! (+ 0.01 *global-delta*))) + (set! num-retries (+ num-retries 1)))) + (if (> num-retries max-retries) + (if (not (null? tal)) + (loop (car tal)(cdr tal) reruns)) + (loop (car newtal)(cdr newtal) reruns))) ;; an issue with prereqs not yet met? ((and (not (null? fails))(eq? testmode 'normal)) - (debug:print 1 "INFO: test " hed " (mode=" testmode ") has failed prerequisite(s); " + (debug:print-info 1 "test " hed " (mode=" testmode ") has failed prerequisite(s); " (string-intersperse (map (lambda (t)(conc (db:test-get-testname t) ":" (db:test-get-state t)"/"(db:test-get-status t))) fails) ", ") ", removing it from to-do list") (if (not (null? tal)) (begin (thread-sleep! *global-delta*) (loop (car tal)(cdr tal)(cons hed reruns))))) (else (debug:print 8 "ERROR: No handler for this condition.") - (thread-sleep! *global-delta*) + (thread-sleep! (+ 1 *global-delta*)) (loop (car newtal)(cdr newtal) reruns)))) ;; END OF IF CAN RUN MORE ;; if can't run more just loop with next possible test (begin - (debug:print 4 "INFO: processing the case with a lambda for items or 'have-procedure. Moving through the queue without dropping " hed) - (thread-sleep! (+ 1 *global-delta*)) + (debug:print-info 4 "processing the case with a lambda for items or 'have-procedure. Moving through the queue without dropping " hed) + (thread-sleep! (+ 2 *global-delta*)) (loop (car newtal)(cdr newtal) reruns))))) ;; END OF (or (procedure? items)(eq? items 'have-procedure)) ;; this case should not happen, added to help catch any bugs ((and (list? items) itemdat) (debug:print 0 "ERROR: Should not have a list of items in a test and the itemspath set - please report this") (exit 1)) ((not (null? reruns)) - (let* ((newlst (open-run-close tests:filter-non-runnable #f run-id tal test-records)) ;; i.e. not FAIL, WAIVED, INCOMPLETE, PASS, KILLED, + (let* ((newlst (tests:filter-non-runnable run-id tal test-records)) ;; i.e. not FAIL, WAIVED, INCOMPLETE, PASS, KILLED, (junked (lset-difference equal? tal newlst))) - (debug:print 4 "INFO: full drop through, if reruns is less than 100 we will force retry them, reruns=" reruns ", tal=" tal) + (debug:print-info 4 "full drop through, if reruns is less than 100 we will force retry them, reruns=" reruns ", tal=" tal) (if (< num-retries max-retries) (set! newlst (append reruns newlst))) (set! num-retries (+ num-retries 1)) - (thread-sleep! *global-delta*) + (thread-sleep! (+ 1 *global-delta*)) (if (not (null? newlst)) ;; since reruns have been tacked on to newlst create new reruns from junked (loop (car newlst)(cdr newlst)(delete-duplicates junked))))) ((not (null? tal)) - (debug:print 4 "INFO: I'm pretty sure I shouldn't get here.")) + (debug:print-info 4 "I'm pretty sure I shouldn't get here.")) (else - (debug:print 4 "INFO: Exiting loop with...\n hed=" hed "\n tal=" tal "\n reruns=" reruns)) + (debug:print-info 4 "Exiting loop with...\n hed=" hed "\n tal=" tal "\n reruns=" reruns)) )))) ;; LET* ((test-record ;; we get here on "drop through" - loop for next test in queue ;; FIXME!!!! THIS SHOULD NOT REQUIRE AN EXIT!!!!!!! - (debug:print 1 "INFO: All tests launched") + (debug:print-info 1 "All tests launched") (thread-sleep! 0.5) ;; FIXME! This harsh exit should not be necessary.... ;; (if (not *runremote*)(exit)) ;; #f)) ;; return a #f as a hint that we are done ;; Here we need to check that all the tests remaining to be run are eligible to run @@ -594,11 +608,11 @@ (if (not itemdat)(set! itemdat '())) (set! item-path (item-list->path itemdat)) (debug:print 2 "Attempting to launch test " test-name (if (equal? item-path "/") "/" item-path)) (setenv "MT_TEST_NAME" test-name) ;; (setenv "MT_RUNNAME" runname) - (open-run-close set-megatest-env-vars db run-id) ;; these may be needed by the launching process + (set-megatest-env-vars run-id) ;; these may be needed by the launching process (change-directory *toppath*) ;; Here is where the test_meta table is best updated ;; Yes, another use of a global for caching. Need a better way? (if (not (hash-table-ref/default *test-meta-updated* test-name #f)) @@ -607,12 +621,12 @@ (open-run-close runs:update-test_meta db test-name test-conf))) ;; (lambda (itemdat) ;;; ((ripeness "overripe") (temperature "cool") (season "summer")) (let* ((new-test-path (string-intersperse (cons test-path (map cadr itemdat)) "/")) (new-test-name (if (equal? item-path "") test-name (conc test-name "/" item-path))) ;; just need it to be unique - (test-id (open-run-close db:get-test-id db run-id test-name item-path)) - (testdat (open-run-close db:get-test-info-by-id db test-id))) + (test-id (cdb:remote-run db:get-test-id #f run-id test-name item-path)) + (testdat (cdb:get-test-info-by-id *runremote* test-id))) (if (not testdat) (begin ;; ensure that the path exists before registering the test ;; NOPE: Cannot! Don't know yet which disk area will be assigned.... ;; (system (conc "mkdir -p " new-test-path)) @@ -625,12 +639,12 @@ (if (not test-id) (begin (debug:print 2 "WARN: Test not pre-created? test-name=" test-name ", item-path=" item-path ", run-id=" run-id) (open-run-close db:tests-register-test #f run-id test-name item-path) (set! test-id (open-run-close db:get-test-id db run-id test-name item-path)))) - (debug:print 4 "INFO: test-id=" test-id ", run-id=" run-id ", test-name=" test-name ", item-path=\"" item-path "\"") - (set! testdat (open-run-close db:get-test-info-by-id db test-id)))) + (debug:print-info 4 "test-id=" test-id ", run-id=" run-id ", test-name=" test-name ", item-path=\"" item-path "\"") + (set! testdat (cdb:get-test-info-by-id *runremote* test-id)))) (set! test-id (db:test-get-id testdat)) (change-directory test-path) (case (if force ;; (args:get-arg "-force") 'NOT_STARTED (if testdat @@ -649,19 +663,19 @@ ((and (or (not rerun) keepgoing) ;; Require to force re-run for COMPLETED or *anything* + PASS,WARN or CHECK (or (member (test:get-status testdat) '("PASS" "WARN" "CHECK")) (member (test:get-state testdat) '("COMPLETED")))) - (debug:print 2 "INFO: running test " test-name "/" item-path " suppressed as it is " (test:get-state testdat) " and " (test:get-status testdat)) + (debug:print-info 2 "running test " test-name "/" item-path " suppressed as it is " (test:get-state testdat) " and " (test:get-status testdat)) (set! runflag #f)) ;; -rerun and status is one of the specifed, run it ((and rerun (let* ((rerunlst (string-split rerun ",")) (must-rerun (member (test:get-status testdat) rerunlst))) - (debug:print 3 "INFO: -rerun list: " rerun ", test-status: " (test:get-status testdat)", must-rerun: " must-rerun) + (debug:print-info 3 "-rerun list: " rerun ", test-status: " (test:get-status testdat)", must-rerun: " must-rerun) must-rerun)) - (debug:print 2 "INFO: Rerun forced for test " test-name "/" item-path) + (debug:print-info 2 "Rerun forced for test " test-name "/" item-path) (set! runflag #t)) ;; -keepgoing, do not rerun FAIL ((and keepgoing (member (test:get-status testdat) '("FAIL"))) (set! runflag #f)) @@ -710,86 +724,104 @@ ;; 'remove-runs ;; 'set-state-status ;; ;; NB// should pass in keys? ;; -(define (runs:operate-on action runnamepatt testpatt itempatt #!key (state #f)(status #f)(new-state-status #f)) +(define (runs:operate-on action runnamepatt testpatt #!key (state #f)(status #f)(new-state-status #f)) + (common:clear-caches) ;; clear all caches (let* ((db #f) (keys (open-run-close db:get-keys db)) (rundat (open-run-close runs:get-runs-by-patt db keys runnamepatt)) (header (vector-ref rundat 0)) (runs (vector-ref rundat 1)) (states (if state (string-split state ",") '())) (statuses (if status (string-split status ",") '())) (state-status (if (string? new-state-status) (string-split new-state-status ",") '(#f #f)))) - (debug:print 2 "Header: " header " action: " action " new-state-status: " new-state-status) + (debug:print-info 4 "runs:operate-on => Header: " header " action: " action " new-state-status: " new-state-status) + (if (> 2 (length state-status)) + (begin + (debug:print 0 "ERROR: the parameter to -set-state-status is a comma delimited string. E.g. COMPLETED,FAIL") + (exit))) (for-each (lambda (run) (let ((runkey (string-intersperse (map (lambda (k) (db:get-value-by-header run header (vector-ref k 0))) keys) "/")) (dirs-to-remove (make-hash-table))) (let* ((run-id (db:get-value-by-header run header "id")) (run-state (db:get-value-by-header run header "state")) (tests (if (not (equal? run-state "locked")) - (open-run-close db:get-tests-for-run db (db:get-value-by-header run header "id") - testpatt itempatt states statuses + (open-run-close db:get-tests-for-run db run-id + testpatt states statuses not-in: #f sort-by: (case action ((remove-runs) 'rundir) (else 'event_time))) '())) (lasttpath "/does/not/exist/I/hope")) - (debug:print 4 "INFO: runs:operate-on run=" run ", header=" header) + (debug:print-info 4 "runs:operate-on run=" run ", header=" header) (if (not (null? tests)) (begin (case action ((remove-runs) (debug:print 1 "Removing tests for run: " runkey " " (db:get-value-by-header run header "runname"))) ((set-state-status) (debug:print 1 "Modifying state and staus for tests for run: " runkey " " (db:get-value-by-header run header "runname"))) ((print-run) - (debug:print 1 "Printing info for run " runkey ", run=" run ", tests=" tests ", header=" header)) + (debug:print 1 "Printing info for run " runkey ", run=" run ", tests=" tests ", header=" header) + action) (else - (print "INFO: action not recognised " action))) + (debug:print-info 0 "action not recognised " action))) (for-each (lambda (test) (let* ((item-path (db:test-get-item-path test)) (test-name (db:test-get-testname test)) - (run-dir (db:test-get-rundir test)) + (run-dir (db:test-get-rundir test)) ;; run dir is from the link tree + (real-dir (if (file-exists? run-dir) + (resolve-pathname run-dir) + #f)) (test-id (db:test-get-id test))) ;; (tdb (db:open-test-db run-dir))) - (debug:print 1 " " (db:test-get-testname test) " id: " (db:test-get-id test) " " item-path " action: " action) + (debug:print-info 4 "test=" test) ;; " (db:test-get-testname test) " id: " (db:test-get-id test) " " item-path " action: " action) (case action ((remove-runs) ;; the tdb is for future possible. (open-run-close db:delete-test-records db #f (db:test-get-id test)) - (debug:print 1 "INFO: Attempting to remove dir " run-dir) - (if (and (> (string-length run-dir) 5) - (file-exists? run-dir)) ;; bad heuristic but should prevent /tmp /home etc. - (let* ((realpath (resolve-pathname run-dir))) - (debug:print 1 "INFO: Real path of is " realpath) - (if (file-exists? realpath) - (if (> (system (conc "rm -rf " realpath)) 0) - (debug:print 0 "ERROR: There was a problem removing " realpath " with rm -f")) - (debug:print 0 "WARNING: test run dir " realpath " appears to not exist")) - (if (file-exists? run-dir) ;; the link - (if (symbolic-link? run-dir) - (delete-file run-dir) - (if (directory? run-dir) - (if (> (directory-fold (lambda (f x)(+ 1 x)) 0 run-dir) 0) - (debug:print 0 "WARNING: refusing to remove " run-dir " as it is not empty") - (delete-directory run-dir)) ;; it should be empty by here BUG BUG, add error catch - (debug:print 0 "ERROR: refusing to remove " run-dir " as it is neither a symlink nor a directory") - )))) - (debug:print 0 "WARNING: directory already removed " run-dir))) + (debug:print-info 1 "Attempting to remove " (if real-dir (conc " dir " real-dir " and ") "") " link " run-dir) + (if (and real-dir + (> (string-length real-dir) 5) + (file-exists? real-dir)) ;; bad heuristic but should prevent /tmp /home etc. + (begin ;; let* ((realpath (resolve-pathname run-dir))) + (debug:print-info 1 "Recursively removing " real-dir) + (if (file-exists? real-dir) + (if (> (system (conc "rm -rf " real-dir)) 0) + (debug:print 0 "ERROR: There was a problem removing " real-dir " with rm -f")) + (debug:print 0 "WARNING: test dir " real-dir " appears to not exist or is not readable"))) + (if real-dir + (debug:print 0 "WARNING: directory " real-dir " does not exist") + (debug:print 0 "WARNING: no real directory corrosponding to link " run-dir ", nothing done"))) + (if (symbolic-link? run-dir) + (begin + (debug:print-info 1 "Removing symlink " run-dir) + (delete-file run-dir)) + (if (directory? run-dir) + (if (> (directory-fold (lambda (f x)(+ 1 x)) 0 run-dir) 0) + (debug:print 0 "WARNING: refusing to remove " run-dir " as it is not empty") + (delete-directory run-dir)) ;; it should be empty by here BUG BUG, add error catch + (if run-dir + (debug:print 0 "WARNING: not removing " run-dir " as it either doesn't exist or is not a symlink") + (debug:print 0 "NOTE: the run dir for this test is undefined. Test may have already been deleted.")) + ))) ((set-state-status) - (debug:print 2 "INFO: new state " (car state-status) ", new status " (cadr state-status)) + (debug:print-info 2 "new state " (car state-status) ", new status " (cadr state-status)) (open-run-close db:test-set-state-status-by-id db (db:test-get-id test) (car state-status)(cadr state-status) #f))))) - tests))) - + (sort tests (lambda (a b)(let ((dira (db:test-get-rundir a)) + (dirb (db:test-get-rundir b))) + (if (and (string? dira)(string? dirb)) + (> (string-length dira)(string-length dirb)) + #f))))))) ;; remove the run if zero tests remain (if (eq? action 'remove-runs) - (let ((remtests (open-run-close db:get-tests-for-run db (db:get-value-by-header run header "id") #f #f '("DELETED") '("n/a") not-in: #t))) + (let ((remtests (open-run-close db:get-tests-for-run db (db:get-value-by-header run header "id") #f '("DELETED") '("n/a") not-in: #t))) (if (null? remtests) ;; no more tests remaining (let* ((dparts (string-split lasttpath "/")) (runpath (conc "/" (string-intersperse (take dparts (- (length dparts) 1)) "/")))) @@ -804,11 +836,12 @@ ;; (begin ;; (debug:print 1 "Removing run dir " runpath) ;; (system (conc "rmdir -p " runpath)))) ))))) )) - runs))) + runs)) + #t) ;;====================================================================== ;; Routines for manipulating runs ;;====================================================================== @@ -836,11 +869,12 @@ (exit 1))) (if (args:get-arg "-server") (open-run-close server:start db (args:get-arg "-server")) (if (not (or (args:get-arg "-runall") ;; runall and runtests are allowed to be servers (args:get-arg "-runtests"))) - (server:client-setup))) + (server:client-setup) ;; This is a duplicate startup!!!??? BUG? + )) (set! keys (open-run-close db:get-keys db)) ;; have enough to process -target or -reqtarg here (if (args:get-arg "-reqtarg") (let* ((runconfigf (conc *toppath* "/runconfigs.config")) ;; DO NOT EVALUATE ALL (runconfig (read-config runconfigf #f #f environ-patt: #f))) @@ -880,11 +914,11 @@ (and unlock (begin (print "Do you really wish to unlock run " run-id "?\n y/n: ") (equal? "y" (read-line))))) (open-run-close db:lock/unlock-run db run-id lock unlock user) - (debug:print 0 "INFO: Skipping lock/unlock on " run-id)))) + (debug:print-info 0 "Skipping lock/unlock on " run-id)))) runs))) ;;====================================================================== ;; Rollup runs ;;====================================================================== @@ -925,11 +959,11 @@ (define (runs:rollup-run keys keyvallst runname user) ;; was target, now keyvallst (debug:print 4 "runs:rollup-run, keys: " keys " keyvallst: " keyvallst " :runname " runname " user: " user) (let* ((db #f) ;; (keyvalllst (keys:target->keyval keys target)) (new-run-id (open-run-close runs:register-run db keys keyvallst runname "new" "n/a" user)) (prev-tests (open-run-close test:get-matching-previous-test-run-records db new-run-id "%" "%")) - (curr-tests (open-run-close db:get-tests-for-run db new-run-id "%" "%" '() '())) + (curr-tests (open-run-close db:get-tests-for-run db new-run-id "%/%" '() '())) (curr-tests-hash (make-hash-table))) (open-run-close db:update-run-event_time db new-run-id) ;; index the already saved tests by testname and itemdat in curr-tests-hash (for-each (lambda (testdat) @@ -953,11 +987,11 @@ (apply sqlite3:execute db (conc "INSERT OR REPLACE INTO tests (run_id,testname,state,status,event_time,host,cpuload,diskfree,uname,rundir,item_path,run_duration,final_logf,comment) " "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?);") new-run-id (cddr (vector->list testdat))) - (set! new-testdat (car (open-run-close db:get-tests-for-run db new-run-id testname item-path '() '()))) + (set! new-testdat (car (open-run-close db:get-tests-for-run db new-run-id (conc testname "/" item-path) '() '()))) (hash-table-set! curr-tests-hash full-name new-testdat) ;; this could be confusing, which record should go into the lookup table? ;; Now duplicate the test steps (debug:print 4 "Copying records in test_steps from test_id=" (db:test-get-id testdat) " to " (db:test-get-id new-testdat)) (open-run-close (lambda () Index: server.scm ================================================================== --- server.scm +++ server.scm @@ -6,207 +6,408 @@ ;; ;; This program is distributed WITHOUT ANY WARRANTY; without even the ;; implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR ;; PURPOSE. -(require-extension (srfi 18) extras tcp rpc) -(import (prefix rpc rpc:)) +(require-extension (srfi 18) extras tcp s11n) -(use sqlite3 srfi-1 posix regex regex-case srfi-69 hostinfo) +(use sqlite3 srfi-1 posix regex regex-case srfi-69 hostinfo md5 message-digest) (import (prefix sqlite3 sqlite3:)) + +(use spiffy uri-common intarweb http-client spiffy-request-vars) + +(tcp-buffer-size 2048) (declare (unit server)) (declare (uses common)) (declare (uses db)) (declare (uses tests)) +(declare (uses tasks)) ;; tasks are where stuff is maintained about what is running. (include "common_records.scm") (include "db_records.scm") -;; procstr is the name of the procedure to be called as a string -(define (server:autoremote procstr params) - (handle-exceptions - exn - (begin - (debug:print 1 "Remote failed for " proc " " params) - (apply (eval (string->symbol procstr)) params)) - ;; (if *runremote* - ;; (apply (eval (string->symbol (conc "remote:" procstr))) params) - (apply (eval (string->symbol procstr)) params))) - -(define (server:start db hostn) - (debug:print 0 "Attempting to start the server ...") - (let ((host:port (db:get-var db "SERVER"))) ;; do whe already have a server running? - (if host:port - (set! *runremote* (let* ((lst (string-split host:port ":")) - (port (if (> (length lst) 1) - (string->number (cadr lst)) - #f))) - (if port (vector (car lst) port) #f))) - (let* ((rpc:listener (server:find-free-port-and-open (rpc:default-server-port))) - (th1 (make-thread - (cute (rpc:make-server rpc:listener) "rpc:server") - 'rpc:server)) - (th2 (make-thread (lambda ()(db:updater)))) - (hostname (if (string=? "-" hostn) - (get-host-name) - hostn)) - (ipaddrstr (if (string=? "-" hostn) - (string-intersperse (map number->string (u8vector->list (hostname->ip hostname))) ".") - #f)) - (host:port (conc (if ipaddrstr ipaddrstr hostname) ":" (rpc:default-server-port)))) - (debug:print 0 "Server started on " host:port) - (db:set-var db "SERVER" host:port) - (set! *cache-on* #t) - - ;; can use this to run most anything at the remote - (rpc:publish-procedure! - 'remote:run - (lambda (procstr . params) - (server:autoremote procstr params))) - - (rpc:publish-procedure! - 'server:login - (lambda (toppath) - (set! *last-db-access* (current-seconds)) - (if (equal? *toppath* toppath) - (begin - (debug:print 2 "INFO: login successful") - #t) - #f))) - - ;;====================================================================== - ;; db specials here - ;;====================================================================== - ;; remote call to open-run-close - (rpc:publish-procedure! - 'rdb:open-run-close - (lambda (procname . remargs) - (debug:print 4 "INFO: Remote call of rdb:open-run-close " procname " " remargs) - (set! *last-db-access* (current-seconds)) - (apply open-run-close (eval procname) remargs))) - - (rpc:publish-procedure! - 'cdb:test-set-status-state - (lambda (test-id status state msg) - (debug:print 4 "INFO: Remote call of cdb:test-set-status-state test-id=" test-id ", status=" status ", state=" state ", msg=" msg) - (cdb:test-set-status-state test-id status state msg))) - - (rpc:publish-procedure! - 'cdb:test-rollup-test_data-pass-fail - (lambda (test-id) - (debug:print 4 "INFO: Remote call of cdb:test-rollup-test_data-pass-fail " test-id) - (cdb:test-rollup-test_data-pass-fail test-id))) - - (rpc:publish-procedure! - 'cdb:pass-fail-counts - (lambda (test-id fail-count pass-count) - (debug:print 4 "INFO: Remote call of cdb:pass-fail-counts " test-id " passes: " pass-count " fails: " fail-count) - (cdb:pass-fail-counts test-id fail-count pass-count))) - - (rpc:publish-procedure! - 'cdb:tests-register-test - (lambda (db run-id test-name item-path) - (debug:print 4 "INFO: Remote call of cdb:tests-register-test " run-id " testname: " test-name " item-path: " item-path) - (cdb:tests-register-test db run-id test-name item-path))) - - (rpc:publish-procedure! - 'cdb:flush-queue - (lambda () - (debug:print 4 "INFO: Remote call of cdb:flush-queue") - (cdb:flush-queue))) - - ;;====================================================================== - ;; end of publish-procedure section - ;;====================================================================== - - (set! *rpc:listener* rpc:listener) - (on-exit (lambda () - (open-run-close - (lambda (db . params) - (sqlite3:execute db "DELETE FROM metadat WHERE var='SERVER' and val=?;" host:port)) - #f ;; for db - #f) ;; for a param - (let loop ((n 0)) - (let ((queue-len 0)) - (thread-sleep! (random 5)) - (mutex-lock! *incoming-mutex*) - (set! queue-len (length *incoming-data*)) - (mutex-unlock! *incoming-mutex*) - (if (> queue-len 0) - (begin - (debug:print 0 "INFO: Queue not flushed, waiting ...") - (loop (+ n 1))))) - ))) - (thread-start! th1) - ;; (debug:print 0 "Server started on port " (rpc:default-server-port) "...") - (thread-start! th2) - ;; (thread-join! th2) - ;; return th2 for the calling process to do a join with - th2 - )))) ;; rpc:server))) - -(define (server:keep-running db host:port) - ;; if none running or if > 20 seconds since - ;; server last used then start shutdown - (let loop ((count 0)) - (thread-sleep! 20) ;; no need to do this very often - (let ((numrunning (db:get-count-tests-running db))) - (if (or (> numrunning 0) - (> (+ *last-db-access* 60)(current-seconds))) - (begin - (debug:print 0 "INFO: Server continuing, tests running: " numrunning ", seconds since last db access: " (- (current-seconds) *last-db-access*)) - (loop (+ 1 count))) - (begin - (debug:print 0 "INFO: Starting to shutdown the server side") - ;; need to delete only *my* server entry (future use) - (sqlite3:execute db "DELETE FROM metadat WHERE var='SERVER' AND val like ?;" host:port) - (thread-sleep! 10) - (debug:print 0 "INFO: Max cached queries was " *max-cache-size*) - (debug:print 0 "INFO: Server shutdown complete. Exiting") - ;; (exit))) - ))))) - -(define (server:find-free-port-and-open port) - (handle-exceptions - exn - (begin - (print "Failed to bind to port " (rpc:default-server-port) ", trying next port") - (server:find-free-port-and-open (+ port 1))) - (rpc:default-server-port port) - (tcp-read-timeout 240000) - (tcp-listen (rpc:default-server-port) 10000))) - -(define (server:client-setup) - (if *runremote* - (begin - (debug:print 0 "ERROR: Attempt to connect to server but already connected") - #f) - (let* ((hostinfo (open-run-close db:get-var #f "SERVER")) - (hostdat (if hostinfo (string-split hostinfo ":") #f)) - (host (if hostinfo (car hostdat) #f)) - (port (if (and hostinfo (> (length hostdat) 1))(cadr hostdat) #f))) - (if (and port - (string->number port)) - (let ((portn (string->number port))) - (debug:print 2 "INFO: Setting up to connect to host " host ":" port) - (handle-exceptions - exn - (begin - (debug:print 0 "ERROR: Failed to open a connection to the server at host: " host " port: " port) - (debug:print 0 " EXCEPTION: " ((condition-property-accessor 'exn 'message) exn)) - ;; (open-run-close - ;; (lambda (db . param) - ;; (sqlite3:execute db "DELETE FROM metadat WHERE var='SERVER'")) - ;; #f) - (set! *runremote* #f)) - (if (and (not (args:get-arg "-server")) ;; no point in the server using the server using the server - ((rpc:procedure 'server:login host portn) *toppath*)) - (begin - (debug:print 2 "INFO: Logged in and connected to " host ":" port) - (set! *runremote* (vector host portn))) - (begin - (debug:print 2 "INFO: Failed to login or connect to " host ":" port) - (set! *runremote* #f))))) - (debug:print 2 "INFO: no server available"))))) +(define (server:make-server-url hostport) + (if (not hostport) + #f + (conc "http://" (car hostport) ":" (cadr hostport)))) + +(define *server-loop-heart-beat* (current-seconds)) +(define *heartbeat-mutex* (make-mutex)) + (debug:print 0 "Server started on " host:port) + +;;====================================================================== +;; S E R V E R +;;====================================================================== + +;; Call this to start the actual server +;; + +(define *db:process-queue-mutex* (make-mutex)) + +(define (server:run hostn) + (debug:print 2 "Attempting to start the server ...") + (if (not *toppath*) + (if (not (setup-for-run)) + (begin + (debug:print 0 "ERROR: cannot find megatest.config, cannot start server, exiting") + (exit)))) + (let* (;; (iface (if (string=? "-" hostn) + ;; #f ;; (get-host-name) + ;; hostn)) + (db #f) ;; (open-db)) ;; we don't want the server to be opening and closing the db unnecesarily + (hostname (get-host-name)) + (ipaddrstr (let ((ipstr (if (string=? "-" hostn) + (string-intersperse (map number->string (u8vector->list (hostname->ip hostname))) ".") + #f))) + (if ipstr ipstr hostn))) ;; hostname))) + (start-port (if (args:get-arg "-port") + (string->number (args:get-arg "-port")) + (+ 5000 (random 1001)))) + (link-tree-path (config-lookup *configdat* "setup" "linktree"))) + (set! *cache-on* #t) + (root-path (if link-tree-path + link-tree-path + (current-directory))) ;; WARNING: SECURITY HOLE. FIX ASAP! + + ;; Setup the web server and a /ctrl interface + ;; + (vhost-map `(((* any) . ,(lambda (continue) + ;; open the db on the first call + (if (not db)(set! db (open-db))) + (let* (($ (request-vars source: 'both)) + (dat ($ 'dat)) + (res #f)) + (cond + ((equal? (uri-path (request-uri (current-request))) + '(/ "hey")) + (send-response body: "hey there!\n" + headers: '((content-type text/plain)))) + ;; This is the /ctrl path where data is handed to the server and + ;; responses + ((equal? (uri-path (request-uri (current-request))) + '(/ "ctrl")) + (let* ((packet (db:string->obj dat)) + (qtype (cdb:packet-get-qtype packet))) + (debug:print-info 12 "server=> received packet=" packet) + (if (not (member qtype '(sync ping))) + (begin + (mutex-lock! *heartbeat-mutex*) + (set! *last-db-access* (current-seconds)) + (mutex-unlock! *heartbeat-mutex*))) + ;; (mutex-lock! *db:process-queue-mutex*) ;; trying a mutex + ;; (set! res (open-run-close db:process-queue-item open-db packet)) + (set! res (db:process-queue-item db packet)) + ;; (mutex-unlock! *db:process-queue-mutex*) + (debug:print-info 11 "Return value from db:process-queue-item is " res) + (send-response body: (conc "ctrl data\n" + res + "") + headers: '((content-type text/plain))))) + (else (continue)))))))) + (server:try-start-server ipaddrstr start-port) + ;; lite3:finalize! db))) + )) + + + +;; (define (server:main-loop) +;; (print "INFO: Exectuing main server loop") +;; (access-log "megatest-http.log") +;; (server-bind-address #f) +;; (define-page (main-page-path) +;; (lambda () +;; (let ((dat ($ "dat"))) +;; ;; (with-request-variables (dat) +;; (debug:print-info 12 "Got dat=" dat) +;; (let* ((packet (db:string->obj dat)) +;; (qtype (cdb:packet-get-qtype packet))) +;; (debug:print-info 12 "server=> received packet=" packet) +;; (if (not (member qtype '(sync ping))) +;; (begin +;; (mutex-lock! *heartbeat-mutex*) +;; (set! *last-db-access* (current-seconds)) +;; (mutex-unlock! *heartbeat-mutex*))) +;; (let ((res (open-run-close db:process-queue-item open-db packet))) +;; (debug:print-info 11 "Return value from db:process-queue-item is " res) +;; res)))))) + +;;; (use spiffy uri-common intarweb) +;;; +;;; (root-path "/var/www") +;;; +;;; (vhost-map `(((* any) . ,(lambda (continue) +;;; (if (equal? (uri-path (request-uri (current-request))) +;;; '(/ "hey")) +;;; (send-response body: "hey there!\n" +;;; headers: '((content-type text/plain))) +;;; (continue)))))) +;;; +;;; (start-server port: 12345) + +;; This is recursively run by server:run until sucessful +;; +(define (server:try-start-server ipaddrstr portnum) + (handle-exceptions + exn + (begin + (print-error-message exn) + (if (< portnum 9000) + (begin + (print "WARNING: failed to start on portnum: " portnum ", trying next port") + (thread-sleep! 0.1) + (open-run-close tasks:remove-server-records tasks:open-db) + (server:try-start-server ipaddrstr (+ portnum 1))) + (print "ERROR: Tried and tried but could not start the server"))) + (set! *runremote* (list ipaddrstr portnum)) + (open-run-close tasks:remove-server-records tasks:open-db) + (open-run-close tasks:server-register + tasks:open-db + (current-process-id) + ipaddrstr portnum 0 'live) + (print "INFO: Trying to start server on " ipaddrstr ":" portnum) + ;; This starts the spiffy server + (start-server port: portnum) + (print "INFO: server has been stopped"))) + +(define (server:mk-signature) + (message-digest-string (md5-primitive) + (with-output-to-string + (lambda () + (write (list (current-directory) + (argv))))))) + +;;====================================================================== +;; S E R V E R U T I L I T I E S +;;====================================================================== + +;; When using zmq this would send the message back (two step process) +;; with spiffy or rpc this simply returns the return data to be returned +;; +(define (server:reply return-addr query-sig success/fail result) + (debug:print-info 11 "server:reply return-addr=" return-addr ", result=" result) + ;; (send-message pubsock target send-more: #t) + ;; (send-message pubsock + (db:obj->string (vector success/fail query-sig result))) + +;;====================================================================== +;; C L I E N T S +;;====================================================================== + +(define (server:get-client-signature) + (if *my-client-signature* *my-client-signature* + (let ((sig (server:mk-signature))) + (set! *my-client-signature* sig) + *my-client-signature*))) + +;; +;; +;; 1 Hello, world! Goodbye Dolly +;; Send msg to serverdat and receive result +(define (server:client-send-receive serverdat msg) + (let* ((url (server:make-server-url serverdat)) + (fullurl (conc url "/ctrl")) ;; (conc url "/?dat=" msg))) + (numretries 0)) + (handle-exceptions + exn + (if (< numretries 200) + (server:client-send-receive serverdat msg)) + (begin + (debug:print-info 11 "fullurl=" fullurl "\n") + ;; set up the http-client here + (max-retry-attempts 100) + (retry-request? (lambda (request) + (thread-sleep! (/ (if (> numretries 100) 100 numretries) 10)) + (set! numretries (+ numretries 1)) + #t)) + ;; send the data and get the response + ;; extract the needed info from the http data and + ;; process and return it. + (let* ((res (with-input-from-request fullurl + ;; #f + ;; msg + (list (cons 'dat msg)) + read-string))) + (debug:print-info 11 "got res=" res) + (let ((match (string-search (regexp "(.*)<.body>") res))) + (debug:print-info 11 "match=" match) + (let ((final (cadr match))) + (debug:print-info 11 "final=" final) + final))))))) + +(define (server:client-login serverdat) + (max-retry-attempts 100) + (cdb:login serverdat *toppath* (server:get-client-signature))) + +;; Not currently used! But, I think it *should* be used!!! +(define (server:client-logout serverdat) + (let ((ok (and (socket? serverdat) + (cdb:logout serverdat *toppath* (server:get-client-signature))))) + ;; (close-socket serverdat) + ok)) + +(define (server:client-connect iface port) + (let* ((login-res #f) + (serverdat (list iface port))) + (set! login-res (server:client-login serverdat)) + (if (and (not (null? login-res)) + (car login-res)) + (begin + (debug:print-info 2 "Logged in and connected to " iface ":" port) + (set! *runremote* serverdat) + serverdat) + (begin + (debug:print-info 2 "Failed to login or connect to " iface ":" port) + (set! *runremote* #f) + #f)))) + +;; Do all the connection work, start a server if not already running +(define (server:client-setup #!key (numtries 50)) + (if (not *toppath*) + (if (not (setup-for-run)) + (begin + (debug:print 0 "ERROR: failed to find megatest.config, exiting") + (exit)))) + (let ((hostinfo (open-run-close tasks:get-best-server tasks:open-db))) + (if hostinfo + (let ((host (list-ref hostinfo 0)) + (iface (list-ref hostinfo 1)) + (port (list-ref hostinfo 2)) + (pid (list-ref hostinfo 3))) + (debug:print-info 2 "Setting up to connect to " hostinfo) + (server:client-connect iface port)) ;; ) + (if (> numtries 0) + (let ((exe (car (argv))) + (pid #f)) + (debug:print-info 0 "No server available, attempting to start one...") + (set! pid (process-run exe (list "-server" "-" "-debug" (if (list? *verbosity*) + (string-intersperse *verbosity* ",") + (conc *verbosity*))))) + ;; (set! pid (process-fork (lambda () + ;; (current-input-port (open-input-file "/dev/null")) + ;; (current-output-port (open-output-file "/dev/null")) + ;; (current-error-port (open-output-file "/dev/null")) + ;; (server:launch)))) + (let loop ((count 0)) + (let ((hostinfo (open-run-close tasks:get-best-server tasks:open-db))) + (if (not hostinfo) + (begin + (debug:print-info 0 "Waiting for server pid=" pid " to start") + (sleep 2) ;; give server time to start + (if (< count 5) + (loop (+ count 1))))))) + ;; we are starting a server, do not try again! That can lead to + ;; recursively starting many processes!!! + (server:client-setup numtries: 0)) + (debug:print-info 1 "Too many attempts, giving up"))))) + +;; run server:keep-running in a parallel thread to monitor that the db is being +;; used and to shutdown after sometime if it is not. +;; +(define (server:keep-running) + ;; if none running or if > 20 seconds since + ;; server last used then start shutdown + ;; This thread waits for the server to come alive + (let* ((server-info (let loop () + (let ((sdat #f)) + (mutex-lock! *heartbeat-mutex*) + (set! sdat *runremote*) + (mutex-unlock! *heartbeat-mutex*) + (if sdat sdat + (begin + (sleep 4) + (loop)))))) + (iface (car server-info)) + (port (cadr server-info)) + (last-access 0) + (tdb (tasks:open-db)) + (spid (tasks:server-get-server-id tdb #f iface port #f))) + (print "Keep-running got server pid " spid ", using iface " iface " and port " port) + (let loop ((count 0)) + (thread-sleep! 4) ;; no need to do this very often + ;; NB// sync currently does NOT return queue-length + (let () ;; (queue-len (cdb:client-call server-info 'sync #t 1))) + ;; (print "Server running, count is " count) + (if (< count 1) ;; 3x3 = 9 secs aprox + (loop (+ count 1))) + + ;; NOTE: Get rid of this mechanism! It really is not needed... + (tasks:server-update-heartbeat tdb spid) + + ;; (if ;; (or (> numrunning 0) ;; stay alive for two days after last access + (mutex-lock! *heartbeat-mutex*) + (set! last-access *last-db-access*) + (mutex-unlock! *heartbeat-mutex*) + (if (> (+ last-access + ;; (* 50 60 60) ;; 48 hrs + ;; 60 ;; one minute + ;; (* 60 60) ;; one hour + (* 45 60) ;; 45 minutes, until the db deletion bug is fixed. + ) + (current-seconds)) + (begin + (debug:print-info 2 "Server continuing, seconds since last db access: " (- (current-seconds) last-access)) + (loop 0)) + (begin + (debug:print-info 0 "Starting to shutdown the server.") + ;; need to delete only *my* server entry (future use) + (set! *time-to-exit* #t) + (tasks:server-deregister-self tdb (get-host-name)) + (thread-sleep! 1) + (debug:print-info 0 "Max cached queries was " *max-cache-size*) + (debug:print-info 0 "Server shutdown complete. Exiting") + (exit))))))) + +;; all routes though here end in exit ... +(define (server:launch) + (if (not *toppath*) + (if (not (setup-for-run)) + (begin + (debug:print 0 "ERROR: cannot find megatest.config, exiting") + (exit)))) + (debug:print-info 2 "Starting the standalone server") + (let ((hostinfo (open-run-close tasks:get-best-server tasks:open-db))) + (debug:print 11 "server:launch hostinfo=" hostinfo) + (if hostinfo + (debug:print-info 2 "NOT starting new server, one is already running on " (car hostinfo) ":" (cadr hostinfo)) + (if *toppath* + (let* ((th2 (make-thread (lambda () + (server:run + (if (args:get-arg "-server") + (args:get-arg "-server") + "-"))) "Server run")) + (th3 (make-thread (lambda ()(server:keep-running)) "Keep running")) + ) + (thread-start! th2) + (thread-start! th3) + (set! *didsomething* #t) + (thread-join! th2) + ) + (debug:print 0 "ERROR: Failed to setup for megatest"))) + (exit))) + +(define (server:client-signal-handler signum) + (handle-exceptions + exn + (debug:print " ... exiting ...") + (let ((th1 (make-thread (lambda () + "") ;; do nothing for now (was flush out last call if applicable) + "eat response")) + (th2 (make-thread (lambda () + (debug:print 0 "ERROR: Received ^C, attempting clean exit. Please be patient and wait a few seconds before hitting ^C again.") + (thread-sleep! 1) ;; give the flush one second to do it's stuff + (debug:print 0 " Done.") + (exit 4)) + "exit on ^C timer"))) + (thread-start! th2) + (thread-start! th1) + (thread-join! th2)))) + +(define (server:client-launch) + (set-signal-handler! signal/int server:client-signal-handler) + (if (server:client-setup) + (debug:print-info 2 "connected as client") + (begin + (debug:print 0 "ERROR: Failed to connect as client") + (exit)))) Index: tasks.scm ================================================================== --- tasks.scm +++ tasks.scm @@ -23,16 +23,25 @@ ;;====================================================================== (define (tasks:open-db) (let* ((dbpath (conc *toppath* "/monitor.db")) (exists (file-exists? dbpath)) - (tdb (sqlite3:open-database dbpath)) ;; (never-give-up-open-db dbpath)) + ;; ;; BUGGISHNESS: Remove this code in six months. Today is 11/13/2012 + ;; (if (< (file-change-time dbpath) 1352851396.0) + ;; (begin + ;; (debug:print 0 "NOTE: removing old db file " dbpath) + ;; (delete-file dbpath) + ;; #f) + ;; #t) + ;; #f)) + (mdb (sqlite3:open-database dbpath)) ;; (never-give-up-open-db dbpath)) (handler (make-busy-timeout 36000))) - (sqlite3:set-busy-handler! tdb handler) + (sqlite3:set-busy-handler! mdb handler) + (sqlite3:execute mdb (conc "PRAGMA synchronous = 0;")) (if (not exists) (begin - (sqlite3:execute tdb "CREATE TABLE IF NOT EXISTS tasks_queue (id INTEGER PRIMARY KEY, + (sqlite3:execute mdb "CREATE TABLE IF NOT EXISTS tasks_queue (id INTEGER PRIMARY KEY, action TEXT DEFAULT '', owner TEXT, state TEXT DEFAULT 'new', target TEXT DEFAULT '', name TEXT DEFAULT '', @@ -40,19 +49,220 @@ item TEXT DEFAULT '', keylock TEXT, params TEXT, creation_time TIMESTAMP, execution_time TIMESTAMP);") - (sqlite3:execute tdb "CREATE TABLE IF NOT EXISTS monitors (id INTEGER PRIMARY KEY, + (sqlite3:execute mdb "CREATE TABLE IF NOT EXISTS monitors (id INTEGER PRIMARY KEY, pid INTEGER, start_time TIMESTAMP, last_update TIMESTAMP, hostname TEXT, username TEXT, - CONSTRAINT monitors_constraint UNIQUE (pid,hostname));"))) - tdb)) + CONSTRAINT monitors_constraint UNIQUE (pid,hostname));") + (sqlite3:execute mdb "CREATE TABLE IF NOT EXISTS servers (id INTEGER PRIMARY KEY, + pid INTEGER, + interface TEXT, + hostname TEXT, + port INTEGER, + start_time TIMESTAMP, + priority INTEGER, + state TEXT, + mt_version TEXT, + heartbeat TIMESTAMP, + CONSTRAINT servers_constraint UNIQUE (pid,hostname,port));") + (sqlite3:execute mdb "CREATE TABLE IF NOT EXISTS clients (id INTEGER PRIMARY KEY, + server_id INTEGER, + pid INTEGER, + hostname TEXT, + cmdline TEXT, + login_time TIMESTAMP, + logout_time TIMESTAMP DEFAULT -1, + CONSTRAINT clients_constraint UNIQUE (pid,hostname));") + + )) + mdb)) +;;====================================================================== +;; Server and client management +;;====================================================================== + +;; state: 'live, 'shutting-down, 'dead +(define (tasks:server-register mdb pid interface port priority state) + (debug:print-info 11 "tasks:server-register " pid " " interface " " port " " priority " " state) + (sqlite3:execute + mdb + "INSERT OR REPLACE INTO servers (pid,hostname,port,start_time,priority,state,mt_version,heartbeat,interface) + VALUES(?, ?, ?, strftime('%s','now'), ?, ?, ?, strftime('%s','now'),?);" + pid (get-host-name) port priority (conc state) megatest-version interface) + (list + (tasks:server-get-server-id mdb (get-host-name) interface port pid) + interface + port + )) + +;; NB// two servers with same pid on different hosts will be removed from the list if pid: is used! +(define (tasks:server-deregister mdb hostname #!key (port #f)(pid #f)(action 'markdead)) + (debug:print-info 11 "server-deregister " hostname ", port " port ", pid " pid) + (if pid + (case action + ((delete)(sqlite3:execute mdb "DELETE FROM servers WHERE pid=?;" pid)) + (else (sqlite3:execute mdb "UPDATE servers SET state='dead' WHERE pid=?;" pid))) + (if port + (case action + ((delete)(sqlite3:execute mdb "DELETE FROM servers WHERE hostname=? AND port=?;" hostname port)) + (else (sqlite3:execute mdb "UPDATE servers SET state='dead' WHERE hostname=? AND port=?;" hostname port))) + (debug:print 0 "ERROR: tasks:server-deregister called with neither pid nor port specified")))) + +(define (tasks:server-deregister-self mdb hostname) + (tasks:server-deregister mdb hostname pid: (current-process-id))) + +(define (tasks:server-get-server-id mdb hostname iface port pid) + (let ((res #f)) + (sqlite3:for-each-row + (lambda (id) + (set! res id)) + mdb + (cond + ((and hostname pid) "SELECT id FROM servers WHERE hostname=? AND pid=?;") + ((and iface port) "SELECT id FROM servers WHERE interface=? AND port=?;") + ((and hostname port) "SELECT id FROM servers WHERE hostname=? AND port=?;") + (else + (begin + (debug:print 0 "ERROR: tasks:server-get-server-id needs (hostname and pid) OR (iface and port) OR (hostname and port)") + "SELECT id FROM servers WHERE pid=-999;"))) + (if hostname hostname iface)(if pid pid port)) + res)) + +(define (tasks:server-update-heartbeat mdb server-id) + (sqlite3:execute mdb "UPDATE servers SET heartbeat=strftime('%s','now') WHERE id=?;" server-id)) + +;; alive servers keep the heartbeat field upto date with seconds every 6 or so seconds +(define (tasks:server-alive? mdb server-id #!key (iface #f)(hostname #f)(port #f)(pid #f)) + (let* ((server-id (if server-id + server-id + (tasks:server-get-server-id mdb hostname iface port pid))) + (heartbeat-delta 99e9)) + (sqlite3:for-each-row + (lambda (delta) + (set! heartbeat-delta delta)) + mdb "SELECT strftime('%s','now')-heartbeat FROM servers WHERE id=?;" server-id) + (< heartbeat-delta 10))) + +(define (tasks:client-register mdb pid hostname cmdline) + (sqlite3:execute + mdb + "INSERT OR REPLACE INTO clients (server_id,pid,hostname,cmdline,login_time) VALUES(?,?,?,?,strftime('%s','now'));") + (tasks:server-get-server-id mdb hostname #f #f pid) + pid hostname cmdline) + +(define (tasks:client-logout mdb pid hostname cmdline) + (sqlite3:execute + mdb + "UPDATE clients SET logout_time=strftime('%s','now') WHERE pid=? AND hostname=? AND cmdline=?;" + pid hostname cmdline)) + +(define (tasks:get-logged-in-clients mdb server-id) + (let ((res '())) + (sqlite3:for-each-row + (lambda (id server-id pid hostname cmdline login-time logout-time) + (set! res (cons (vector id server-id pid hostname cmdline login-time lougout-time) res))) + mdb + "SELECT id,server_id,pid,hostname,cmdline,login_time,logout_time FROM clients WHERE server_id=?;" + server-id))) + +(define (tasks:have-clients? mdb server-id) + (null? (tasks:get-logged-in-clients mdb server-id))) + +;; ping each server in the db and return first found that responds. +;; remove any others. will not necessarily remove all! +(define (tasks:get-best-server mdb) + (let ((res '()) + (best #f)) + (sqlite3:for-each-row + (lambda (id hostname interface port pid) + (set! res (cons (list hostname interface port pid id) res)) + (debug:print-info 2 "Found existing server " hostname ":" port " registered in db")) + mdb + "SELECT id,hostname,interface,port,pid FROM servers + WHERE strftime('%s','now')-heartbeat < 10 + AND mt_version=? ORDER BY start_time ASC LIMIT 1;" megatest-version) + ;; for now we are keeping only one server registered in the db, return #f or first server found + (if (null? res) #f (car res)))) + +;; BUG: This logic is probably needed unless methodology changes completely... +;; +;; (if (null? res) #f +;; (let loop ((hed (car res)) +;; (tal (cdr res))) +;; ;; (print "hed=" hed ", tal=" tal) +;; (let* ((host (list-ref hed 0)) +;; (iface (list-ref hed 1)) +;; (port (list-ref hed 2)) +;; (pid (list-ref hed 4)) +;; (alive (open-run-close tasks:server-alive? tasks:open-db #f hostname: host port: port))) +;; (if alive +;; (begin +;; (debug:print-info 2 "Found an existing, alive, server " host ", " port ".") +;; (list host iface port)) +;; (begin +;; (debug:print-info 1 "Marking " host ":" port " as dead in server registry.") +;; (if port +;; (open-run-close tasks:server-deregister tasks:open-db host port: port) +;; (open-run-close tasks:server-deregister tasks:open-db host pid: pid)) +;; (if (null? tal) +;; #f +;; (loop (car tal)(cdr tal)))))))))) + +(define (tasks:remove-server-records mdb) + (sqlite3:execute mdb "DELETE FROM servers;")) + +(define (tasks:mark-server hostname port pid state) + (if port + (open-run-close tasks:server-deregister tasks:open-db hostname port: port) + (open-run-close tasks:server-deregister tasks:open-db hostname pid: pid))) + + +(define (tasks:kill-server status hostname port pid) + (debug:print-info 1 "Removing defunct server record for " hostname ":" port) + (if port + (open-run-close tasks:server-deregister tasks:open-db hostname port: port) + (open-run-close tasks:server-deregister tasks:open-db hostname pid: pid)) + (if status ;; #t means alive + (begin + (if (equal? hostname (get-host-name)) + (handle-exceptions + exn + (debug:print-info 0 "server may or may not be dead, check for megatest -server running as pid " pid "\n" + " EXCEPTION: " ((condition-property-accessor 'exn 'message) exn)) + (debug:print 1 "Sending signal/term to " pid " on " hostname) + (process-signal pid signal/term) + (thread-sleep! 5) ;; give it five seconds to die peacefully then do a brutal kill + (process-signal pid signal/kill)) ;; local machine, send sig term + (begin + (debug:print-info 1 "Telling alive server on " hostname ":" port " to commit servercide") + (cdb:kill-server zmq-socket)))) ;; remote machine, try telling server to commit suicide + (begin + (if status + (if (equal? hostname (get-host-name)) + (begin + (debug:print-info 1 "Sending signal/term to " pid " on " hostname) + (process-signal pid signal/term) ;; local machine, send sig term + (thread-sleep! 5) ;; give it five seconds to die peacefully then do a brutal kill + (process-signal pid signal/kill)) + (debug:print 0 "WARNING: Can't kill frozen server on remote host " hostname)))))) + + + +(define (tasks:get-all-servers mdb) + (let ((res '())) + (sqlite3:for-each-row + (lambda (id pid hostname interface port start-time priority state mt-version last-update) + (set! res (cons (vector id pid hostname interface port start-time priority state mt-version last-update) res))) + mdb + "SELECT id,pid,hostname,interface,port,start_time,priority,state,mt_version,strftime('%s','now')-heartbeat AS last_update FROM servers ORDER BY start_time DESC;") + res)) + ;;====================================================================== ;; Tasks and Task monitors ;;====================================================================== @@ -65,32 +275,32 @@ ;;====================================================================== ;; Task Monitors ;;====================================================================== -(define (tasks:register-monitor db tdb) +(define (tasks:register-monitor db mdb) (let* ((pid (current-process-id)) (hostname (get-host-name)) (userinfo (user-information (current-user-id))) (username (car userinfo))) (print "Register monitor, pid: " pid ", hostname: " hostname ", username: " username) - (sqlite3:execute tdb "INSERT INTO monitors (pid,start_time,last_update,hostname,username) VALUES (?,strftime('%s','now'),strftime('%s','now'),?,?);" + (sqlite3:execute mdb "INSERT INTO monitors (pid,start_time,last_update,hostname,username) VALUES (?,strftime('%s','now'),strftime('%s','now'),?,?);" pid hostname username))) -(define (tasks:get-num-alive-monitors tdb) +(define (tasks:get-num-alive-monitors mdb) (let ((res 0)) (sqlite3:for-each-row (lambda (count) (set! res count)) - tdb + mdb "SELECT count(id) FROM monitors WHERE last_update < (strftime('%s','now') - 300) AND username=?;" (car (user-information (current-user-id)))) res)) ;; register a task -(define (tasks:add tdb action owner target runname test item params) - (sqlite3:execute tdb "INSERT INTO tasks_queue (action,owner,state,target,name,test,item,params,creation_time,execution_time) +(define (tasks:add mdb action owner target runname test item params) + (sqlite3:execute mdb "INSERT INTO tasks_queue (action,owner,state,target,name,test,item,params,creation_time,execution_time) VALUES (?,?,'new',?,?,?,?,?,strftime('%s','now'),0);" action owner target runname @@ -105,28 +315,28 @@ (set! tmp (conc tmp "/" (hash-table-ref/default key-params (vector-ref key 0) "")))) (cdr keys))) tmp)) ;; for use from the gui -(define (tasks:add-from-params tdb action keys key-params var-params) +(define (tasks:add-from-params mdb action keys key-params var-params) (let ((target (keys:key-vals-hash->target keys key-params)) (owner (car (user-information (current-user-id)))) (runname (hash-table-ref/default var-params "runname" #f)) (testpatts (hash-table-ref/default var-params "testpatts" "%")) (itempatts (hash-table-ref/default var-params "itempatts" "%")) (params (hash-table-ref/default var-params "params" ""))) - (tasks:add tdb action owner target runname testpatts itempatts params))) + (tasks:add mdb action owner target runname testpatts itempatts params))) ;; return one task from those who are 'new' OR 'waiting' AND more than 10sec old ;; -(define (tasks:snag-a-task tdb) +(define (tasks:snag-a-task mdb) (let ((res #f) (keytxt (conc (current-process-id) "-" (get-host-name) "-" (car (user-information (current-user-id)))))) ;; first randomly set a new to pid-hostname-hostname (sqlite3:execute - tdb + mdb "UPDATE tasks_queue SET keylock=? WHERE id IN (SELECT id FROM tasks_queue WHERE state='new' OR (state='waiting' AND (strftime('%s','now')-execution_time) > 10) OR state='reset' @@ -133,92 +343,92 @@ ORDER BY RANDOM() LIMIT 1);" keytxt) (sqlite3:for-each-row (lambda (id . rem) (set! res (apply vector id rem))) - tdb + mdb "SELECT id,action,owner,state,target,name,test,item,params,creation_time,execution_time FROM tasks_queue WHERE keylock=? ORDER BY execution_time ASC LIMIT 1;" keytxt) (if res ;; yep, have work to be done (begin - (sqlite3:execute tdb "UPDATE tasks_queue SET state='inprogress',execution_time=strftime('%s','now') WHERE id=?;" + (sqlite3:execute mdb "UPDATE tasks_queue SET state='inprogress',execution_time=strftime('%s','now') WHERE id=?;" (tasks:task-get-id res)) res) #f))) -(define (tasks:reset-stuck-tasks tdb) +(define (tasks:reset-stuck-tasks mdb) (let ((res '())) (sqlite3:for-each-row (lambda (id delta) (set! res (cons id res))) - tdb + mdb "SELECT id,strftime('%s','now')-execution_time AS delta FROM tasks_queue WHERE state='inprogress' AND delta>700 ORDER BY delta DESC LIMIT 2;") (sqlite3:execute - tdb + mdb (conc "UPDATE tasks_queue SET state='reset' WHERE id IN ('" (string-intersperse (map conc res) "','") "');")))) ;; return all tasks in the tasks_queue table ;; -(define (tasks:get-tasks tdb types states) +(define (tasks:get-tasks mdb types states) (let ((res '())) (sqlite3:for-each-row (lambda (id . rem) (set! res (cons (apply vector id rem) res))) - tdb + mdb (conc "SELECT id,action,owner,state,target,name,test,item,params,creation_time,execution_time FROM tasks_queue " ;; WHERE ;; state IN " statesstr " AND ;; action IN " actionsstr " ORDER BY creation_time DESC;")) res)) ;; remove tasks given by a string of numbers comma separated -(define (tasks:remove-queue-entries tdb task-ids) - (sqlite3:execute tdb (conc "DELETE FROM tasks_queue WHERE id IN (" task-ids ");"))) +(define (tasks:remove-queue-entries mdb task-ids) + (sqlite3:execute mdb (conc "DELETE FROM tasks_queue WHERE id IN (" task-ids ");"))) ;; -(define (tasks:start-monitor db tdb) - (if (> (tasks:get-num-alive-monitors tdb) 2) ;; have two running, no need for more - (debug:print 1 "INFO: Not starting monitor, already have more than two running") +(define (tasks:start-monitor db mdb) + (if (> (tasks:get-num-alive-monitors mdb) 2) ;; have two running, no need for more + (debug:print-info 1 "Not starting monitor, already have more than two running") (let* ((megatestdb (conc *toppath* "/megatest.db")) (monitordbf (conc *toppath* "/monitor.db")) (last-db-update 0)) ;; (file-modification-time megatestdb))) - (task:register-monitor tdb) + (task:register-monitor mdb) (let loop ((count 0) (next-touch 0)) ;; next-touch is the time where we need to update last_update ;; if the db has been modified we'd best look at the task queue (let ((modtime (file-modification-time megatestdbpath ))) (if (> modtime last-db-update) - (tasks:process-queue db tdb last-db-update megatestdb next-touch)) + (tasks:process-queue db mdb last-db-update megatestdb next-touch)) ;; WARNING: Possible race conditon here!! ;; should this update be immediately after the task-get-action call above? (if (> (current-seconds) next-touch) (begin - (tasks:monitors-update tdb) + (tasks:monitors-update mdb) (loop (+ count 1)(+ (current-seconds) 240))) (loop (+ count 1) next-touch))))))) -(define (tasks:process-queue db tdb) - (let* ((task (tasks:snag-a-task tdb)) +(define (tasks:process-queue db mdb) + (let* ((task (tasks:snag-a-task mdb)) (action (if task (tasks:task-get-action task) #f))) (if action (print "tasks:process-queue task: " task)) (if action (case (string->symbol action) - ((run) (tasks:start-run db tdb task)) - ((remove) (tasks:remove-runs db tdb task)) - ((lock) (tasks:lock-runs db tdb task)) + ((run) (tasks:start-run db mdb task)) + ((remove) (tasks:remove-runs db mdb task)) + ((lock) (tasks:lock-runs db mdb task)) ;; ((monitor) (tasks:start-monitor db task)) - ((rollup) (tasks:rollup-runs db tdb task)) - ((updatemeta)(tasks:update-meta db tdb task)) - ((kill) (tasks:kill-monitors db tdb task)))))) + ((rollup) (tasks:rollup-runs db mdb task)) + ((updatemeta)(tasks:update-meta db mdb task)) + ((kill) (tasks:kill-monitors db mdb task)))))) -(define (tasks:get-monitors tdb) +(define (tasks:get-monitors mdb) (let ((res '())) (sqlite3:for-each-row (lambda (a . rem) (set! res (cons (apply vector a rem) res))) - tdb + mdb "SELECT id,pid,strftime('%m/%d/%Y %H:%M',datetime(start_time,'unixepoch'),'localtime'),strftime('%m/%d/%Y %H:%M:%S',datetime(last_update,'unixepoch'),'localtime'),hostname,username FROM monitors ORDER BY last_update ASC;") (reverse res) )) (define (tasks:tasks->text tasks) @@ -253,31 +463,31 @@ monitors) "\n")))) ;; update the last_update field with the current time and ;; if any monitors appear dead, remove them -(define (tasks:monitors-update tdb) - (sqlite3:execute tdb "UPDATE monitors SET last_update=strftime('%s','now') WHERE pid=? AND hostname=?;" +(define (tasks:monitors-update mdb) + (sqlite3:execute mdb "UPDATE monitors SET last_update=strftime('%s','now') WHERE pid=? AND hostname=?;" (current-process-id) (get-host-name)) (let ((deadlist '())) (sqlite3:for-each-row (lambda (id pid host last-update delta) (print "Going to delete stale record for monitor with pid " pid " on host " host " last updated " delta " seconds ago") (set! deadlist (cons id deadlist))) - tdb + mdb "SELECT id,pid,hostname,last_update,strftime('%s','now')-last_update AS delta FROM monitors WHERE delta > 700;") - (sqlite3:execute tdb (conc "DELETE FROM monitors WHERE id IN ('" (string-intersperse (map conc deadlist) "','") "');"))) + (sqlite3:execute mdb (conc "DELETE FROM monitors WHERE id IN ('" (string-intersperse (map conc deadlist) "','") "');"))) ) -(define (tasks:remove-monitor-record tdb) - (sqlite3:execute tdb "DELETE FROM monitors WHERE pid=? AND hostname=?;" +(define (tasks:remove-monitor-record mdb) + (sqlite3:execute mdb "DELETE FROM monitors WHERE pid=? AND hostname=?;" (current-process-id) (get-host-name))) -(define (tasks:set-state tdb task-id state) - (sqlite3:execute tdb "UPDATE tasks_queue SET state=? WHERE id=?;" +(define (tasks:set-state mdb task-id state) + (sqlite3:execute mdb "UPDATE tasks_queue SET state=? WHERE id=?;" state task-id)) ;;====================================================================== ;; The routines to process tasks @@ -284,11 +494,11 @@ ;;====================================================================== ;; NOTE: It might be good to add one more layer of checking to ensure ;; that no task gets run in parallel. -(define (tasks:start-run db tdb task) +(define (tasks:start-run db mdb task) (let ((flags (make-hash-table))) (hash-table-set! flags "-rerun" "NOT_STARTED") (if (not (string=? (tasks:task-get-params task) "")) (hash-table-set! flags "-setvars" (tasks:task-get-params task))) (print "Starting run " task) @@ -298,13 +508,13 @@ (tasks:task-get-name task) (tasks:task-get-test task) (tasks:task-get-item task) (tasks:task-get-owner task) flags) - (tasks:set-state tdb (tasks:task-get-id task) "waiting"))) + (tasks:set-state mdb (tasks:task-get-id task) "waiting"))) -(define (tasks:rollup-runs db tdb task) +(define (tasks:rollup-runs db mdb task) (let* ((flags (make-hash-table)) (keys (db:get-keys db)) (keyvallst (keys:target->keyval keys (tasks:task-get-target task)))) ;; (hash-table-set! flags "-rerun" "NOT_STARTED") (print "Starting rollup " task) @@ -312,6 +522,6 @@ (runs:rollup-run db keys keyvallst (tasks:task-get-name task) (tasks:task-get-owner task)) - (tasks:set-state tdb (tasks:task-get-id task) "waiting"))) + (tasks:set-state mdb (tasks:task-get-id task) "waiting"))) ADDED testhttp/example-client.scm Index: testhttp/example-client.scm ================================================================== --- /dev/null +++ testhttp/example-client.scm @@ -0,0 +1,6 @@ +(use regex http-client) + +(print (with-input-from-request "http://localhost:8083/?foo=1" #f + (lambda () + (let ((match (string-search (regexp "(.*)<.body>") (caddr (string-split (read-string) "\n"))))) + (cadr match))))) ADDED testhttp/example-server.scm Index: testhttp/example-server.scm ================================================================== --- /dev/null +++ testhttp/example-server.scm @@ -0,0 +1,26 @@ +(use spiffy awful) + +(tcp-buffer-size 2048) +(enable-sxml #t) + +(define (hello-world) + (define-page (main-page-path) + (lambda () + (with-request-variables (foo) + foo)))) + +(define (start-server #!key (portnum 8080)) + (handle-exceptions + exn + (begin + (print-error-message exn) + (if (< portnum 9000) + (begin + (print "WARNING: failed to start on portnum: " portnum ", trying next port") + (sleep 1) + (start-server portnum: (+ portnum 1))) + (print "ERROR: Tried and tried but could not start the server"))) + (print "INFO: Trying to start server on portnum: " portnum) + (awful-start hello-world port: portnum))) + +(start-server) ADDED testhttp/mockupclient.scm Index: testhttp/mockupclient.scm ================================================================== --- /dev/null +++ testhttp/mockupclient.scm @@ -0,0 +1,35 @@ +(use posix) + +(define cname "Bob") +(define runtime 10) +(let ((args (argv))) + (if (< (length args) 3) + (begin + (print "Usage: mockupclient clientname runtime") + (exit)) + (begin + (set! cname (cadr args)) + (set! runtime (string->number (caddr args)))))) + +;; (define start-delay (/ (random 100) 9)) +;; (define runtime (+ 1 (/ (random 200) 2))) + +(print "Starting client " cname " with runtime " runtime) + +(include "mockupclientlib.scm") + +(set! endtime (+ (current-seconds) runtime)) + +(let loop () + (let ((x (random 15)) + (varname (list-ref (list "hello" "goodbye" "saluton" "kiaorana")(random 4)))) + (case x + ;; ((1)(dbaccess cname 'sync "nodat" #f)) + ((2 3 4 5)(dbaccess cname 'set varname (random 999))) + ((6 7 8 9 10)(print cname ": Get \"" varname "\" " (dbaccess cname 'get varname #f))) + (else + (thread-sleep! 0.011))) + (if (< (current-seconds) endtime) + (loop)))) + +(print "Client " cname " all done!!") ADDED testhttp/mockupclientlib.scm Index: testhttp/mockupclientlib.scm ================================================================== --- /dev/null +++ testhttp/mockupclientlib.scm @@ -0,0 +1,33 @@ +(define sub (make-socket 'sub)) +(define push (make-socket 'push)) +(socket-option-set! sub 'subscribe cname) +(connect-socket sub "tcp://localhost:5563") +(connect-socket push "tcp://localhost:5564") + +(define (dbaccess cname cmd var val #!key (numtries 1)) + (let* ((msg (conc cname ":" cmd ":" (if val (conc var " " val) var))) + (res #f) + (do-access (lambda () + (print "Sending msg: " msg) + (send-message push msg) + (print "Message " msg " sent") + (print "Client " cname " waiting for response to " msg) + (print "Client " cname " received address " (receive-message* sub)) + (set! res (receive-message* sub))))) + (let ((th1 (make-thread do-access "do access")) + (th2 (make-thread (lambda () + (thread-sleep! 5) + (if (not res) + (if (> numtries 0) + (begin + (print "WARNING: access timed out for " cname ", trying again. Trys remaining=" numtries) + (dbaccess cname cmd var val numtries: (- numtries 1))) + (begin + (print "ERROR: dbaccess timed out. Exiting") + (exit))))) + "timeout thread"))) + (thread-start! th1) + (thread-start! th2) + (thread-join! th1) + res))) + ADDED testhttp/mockupserver.scm Index: testhttp/mockupserver.scm ================================================================== --- /dev/null +++ testhttp/mockupserver.scm @@ -0,0 +1,140 @@ +;; pub/sub with envelope address +;; Note that if you don't insert a sleep, the server will crash with SIGPIPE as soon +;; as a client disconnects. Also a remaining client may receive tons of +;; messages afterward. + +(use srfi-18 sqlite3 spiffy) + +(define cname "server") +(define total-db-accesses 0) +(define start-time (current-seconds)) + +;; setup the server here +(tcp-buffer-size 2048) +(server-port 5563) + +(define (open-db) + (let* ((dbpath "mockup.db") + (dbexists (file-exists? dbpath)) + (db (open-database dbpath)) ;; (never-give-up-open-db dbpath)) + (handler (make-busy-timeout 10))) + (set-busy-handler! db handler) + (if (not dbexists) + (for-each + (lambda (stmt) + (execute db stmt)) + (list + "PRAGMA SYNCHRONOUS=0;" + "CREATE TABLE clients (id INTEGER PRIMARY KEY,name TEXT,num_accesses INTEGER DEFAULT 0);" + "CREATE TABLE vars (var TEXT,val TEXT,CONSTRAINT vars_constraint UNIQUE (var));"))) + db)) + +(define cid-cache (make-hash-table)) + +(define (get-client-id db cname) + (let ((cid (hash-table-ref/default cid-cache cname #f))) + (if cid + cid + (begin + (execute db "INSERT OR REPLACE INTO clients (name) VALUES(?);" cname) + (for-each-row + (lambda (id) + (set! cid id)) + db + "SELECT id FROM clients WHERE name=?;" cname) + (hash-table-set! cid-cache cname cid) + (set! total-db-accesses (+ total-db-accesses 2)) + cid)))) + +(define (count-client db cname) + (let ((cid (get-client-id db cname))) + (execute db "UPDATE clients SET num_accesses=num_accesses+1 WHERE id=?;" cid) + (set! total-db-accesses (+ total-db-accesses 1)) + )) + +(define db (open-db)) +;; (define queuelst '()) +;; (define mx1 (make-mutex)) + +(define max-queue-len 0) + +(define (process-queue queuelst) + (let ((queuelen (length queuelst))) + (if (> queuelen max-queue-len) + (set! max-queue-len queuelen)) + (for-each + (lambda (item) + (let ((cname (vector-ref item 1)) + (clcmd (vector-ref item 2)) + (cdata (vector-ref item 3))) + (send-message pub cname send-more: #t) + (send-message pub (case clcmd + ((sync) + (conc queuelen)) + ((set) + (set! total-db-accesses (+ total-db-accesses 1)) + (apply execute db "INSERT OR REPLACE INTO vars (var,val) VALUES (?,?);" (string-split cdata)) + "ok") + ((get) + (set! total-db-accesses (+ total-db-accesses 1)) + (let ((res "noval")) + (for-each-row + (lambda (val) + (set! res val)) + db + "SELECT val FROM vars WHERE var=?;" cdata) + res)) + (else (conc "unk cmd: " clcmd)))))) + queuelst))) + +(define th1 (make-thread + (lambda () + (let ((last-run 0)) ;; current-seconds when run last + (let loop ((queuelst '())) + (let* ((indat (receive-message* pull)) + (parts (string-split indat ":")) + (cname (car parts)) ;; client name + (clcmd (string->symbol (cadr parts))) ;; client cmd + (cdata (caddr parts)) ;; client data + (svect (vector (current-seconds) cname clcmd cdata))) ;; record for the queue + (count-client db cname) + (case clcmd + ((sync) ;; just process the queue + (print "Got sync from " cname) + (process-queue (cons svect queuelst)) + (loop '())) + ((get) + (process-queue (cons svect queuelst)) + (loop '())) + (else + (loop (cons svect queuelst)))))))) + "server thread")) + +(include "mockupclientlib.scm") + +;; ;; send a sync to the pull port +;; (define th2 (make-thread +;; (lambda () +;; (let ((last-action-time (current-seconds))) +;; (let loop () +;; (thread-sleep! 5) +;; (let ((queuelen (string->number (dbaccess "server" 'sync "nada" #f))) +;; (last-action-delta #f)) +;; (if (> queuelen 1)(set! last-action-time (current-seconds))) +;; (set! last-action-delta (- (current-seconds) last-action-time)) +;; (print "Server: Got queuelen=" queuelen ", last-action-delta=" last-action-delta) +;; (if (< last-action-delta 60) +;; (loop) +;; (print "Server exiting, 25 seconds since last access")))))) +;; "sync thread")) + +(handle-not-found + + +(thread-start! th1) +(thread-start! th2) +(thread-join! th2) + +(let* ((run-time (- (current-seconds) start-time)) + (queries/second (/ total-db-accesses run-time))) + (print "Server exited! Total db accesses=" total-db-accesses " in " run-time " seconds for " queries/second " queries/second with max queue length of: " max-queue-len)) ADDED testhttp/testclient.scm Index: testhttp/testclient.scm ================================================================== --- /dev/null +++ testhttp/testclient.scm @@ -0,0 +1,8 @@ +(use http-client) + +(with-input-from-request "http://localhost:12345/hey" + ;; #f + ;; msg + (list (cons 'dat "Testing eh")) + read-string) + ADDED testhttp/testserver.scm Index: testhttp/testserver.scm ================================================================== --- /dev/null +++ testhttp/testserver.scm @@ -0,0 +1,16 @@ +(use spiffy uri-common intarweb spiffy-request-vars) + +(root-path "/var/www") + +(vhost-map `(((* any) . ,(lambda (continue) + (let (($ (request-vars source: 'both))) + (print ($ 'dat)) + (if (equal? (uri-path (request-uri (current-request))) + '(/ "hey")) + (send-response body: "hey there!\n" + headers: '((content-type text/plain))) + (continue))))))) + +(start-server port: 12345) + + Index: tests.scm ================================================================== --- tests.scm +++ tests.scm @@ -11,13 +11,12 @@ ;;====================================================================== ;; Tests ;;====================================================================== -(use sqlite3 srfi-1 posix regex regex-case srfi-69 dot-locking tcp rpc) +(use sqlite3 srfi-1 posix regex regex-case srfi-69 dot-locking tcp) (import (prefix sqlite3 sqlite3:)) -(import (prefix rpc rpc:)) (declare (unit tests)) (declare (uses db)) (declare (uses common)) (declare (uses items)) @@ -26,15 +25,92 @@ (include "common_records.scm") (include "key_records.scm") (include "db_records.scm") (include "run_records.scm") (include "test_records.scm") + +(define (tests:get-valid-tests testsdir test-patts) ;; #!key (test-names '())) + (let ((tests (glob (conc testsdir "/tests/*")))) ;; " (string-translate patt "%" "*"))))) + (set! tests (filter (lambda (test)(file-exists? (conc test "/testconfig"))) tests)) + (delete-duplicates + (filter (lambda (testname) + (tests:match test-patts testname #f)) + (map (lambda (testp) + (last (string-split testp "/"))) + tests))))) + +;; tests:glob-like-match +(define (tests:glob-like-match patt str) + (let ((like (substring-index "%" patt))) + (let* ((notpatt (equal? (substring-index "~" patt) 0)) + (newpatt (if notpatt (substring patt 1) patt)) + (finpatt (if like + (string-substitute (regexp "%") ".*" newpatt) + (string-substitute (regexp "\\*") ".*" newpatt))) + (res #f)) + ;; (print "tests:glob-like-match => notpatt: " notpatt ", newpatt: " newpatt ", finpatt: " finpatt) + (set! res (string-match (regexp finpatt (if like #t #f)) str)) + (if notpatt (not res) res)))) + +;; if itempath is #f then look only at the testname part +;; +(define (tests:match patterns testname itempath) + (if (string? patterns) + (let ((patts (string-split patterns ","))) + (if (null? patts) ;;; no pattern(s) means no match + #f + (let loop ((patt (car patts)) + (tal (cdr patts))) + ;; (print "loop: patt: " patt ", tal " tal) + (if (string=? patt "") + #f ;; nothing ever matches empty string - policy + (let* ((patt-parts (string-match (regexp "^([^\\/]*)(\\/(.*)|)$") patt)) + (test-patt (cadr patt-parts)) + (item-patt (cadddr patt-parts))) + ;; special case: test vs. test/ + ;; test => "test" "%" + ;; test/ => "test" "" + (if (and (not (substring-index "/" patt)) ;; no slash in the original + (or (not item-patt) + (equal? item-patt ""))) ;; should always be true that item-patt is "" + (set! item-patt "%")) + ;; (print "tests:match => patt-parts: " patt-parts ", test-patt: " test-patt ", item-patt: " item-patt) + (if (and (tests:glob-like-match test-patt testname) + (or (not itempath) + (tests:glob-like-match (if item-patt item-patt "") itempath))) + #t + (if (null? tal) + #f + (loop (car tal)(cdr tal))))))))))) + +;; if itempath is #f then look only at the testname part +;; +(define (tests:match->sqlqry patterns) + (if (string? patterns) + (let ((patts (string-split patterns ","))) + (if (null? patts) ;;; no pattern(s) means no match, we will do no query + #f + (let loop ((patt (car patts)) + (tal (cdr patts)) + (res '())) + ;; (print "loop: patt: " patt ", tal " tal) + (let* ((patt-parts (string-match (regexp "^([^\\/]*)(\\/(.*)|)$") patt)) + (test-patt (cadr patt-parts)) + (item-patt (cadddr patt-parts)) + (test-qry (db:patt->like "testname" test-patt)) + (item-qry (db:patt->like "item_path" item-patt)) + (qry (conc "(" test-qry " AND " item-qry ")"))) + ;; (print "tests:match => patt-parts: " patt-parts ", test-patt: " test-patt ", item-patt: " item-patt) + (if (null? tal) + (string-intersperse (append (reverse res)(list qry)) " OR ") + (loop (car tal)(cdr tal)(cons qry res))))))) + #f)) ;; get the previous record for when this test was run where all keys match but runname ;; returns #f if no such test found, returns a single test record if found (define (test:get-previous-test-run-record db run-id test-name item-path) - (let* ((keys (db:get-keys db)) + (let* ((keys (cdb:remote-run db:get-keys #f)) (selstr (string-intersperse (map (lambda (x)(vector-ref x 0)) keys) ",")) (qrystr (string-intersperse (map (lambda (x)(conc (vector-ref x 0) "=?")) keys) " AND ")) (keyvals #f)) ;; first look up the key values from the run selected by run-id (sqlite3:for-each-row @@ -54,11 +130,11 @@ ;; if found then return that matching test record (debug:print 4 "selstr: " selstr ", qrystr: " qrystr ", keyvals: " keyvals ", previous run ids found: " prev-run-ids) (if (null? prev-run-ids) #f (let loop ((hed (car prev-run-ids)) (tal (cdr prev-run-ids))) - (let ((results (db:get-tests-for-run db hed test-name item-path '() '()))) + (let ((results (cdb:remote-run db:get-tests-for-run #f hed (conc test-name "/" item-path)'() '()))) (debug:print 4 "Got tests for run-id " run-id ", test-name " test-name ", item-path " item-path ": " results) (if (and (null? results) (not (null? tal))) (loop (car tal)(cdr tal)) (if (null? results) #f @@ -66,11 +142,11 @@ ;; get the previous records for when these tests were run where all keys match but runname ;; NB// Merge this with test:get-previous-test-run-records? This one looks for all matching tests ;; can use wildcards. Also can likely be factored in with get test paths? (define (test:get-matching-previous-test-run-records db run-id test-name item-path) - (let* ((keys (db:get-keys db)) + (let* ((keys (cdb:remote-run db:get-keys #f)) (selstr (string-intersperse (map (lambda (x)(vector-ref x 0)) keys) ",")) (qrystr (string-intersperse (map (lambda (x)(conc (vector-ref x 0) "=?")) keys) " AND ")) (keyvals #f) (tests-hash (make-hash-table))) ;; first look up the key values from the run selected by run-id @@ -92,11 +168,11 @@ (debug:print 4 "selstr: " selstr ", qrystr: " qrystr ", keyvals: " keyvals ", previous run ids found: " prev-run-ids) (if (null? prev-run-ids) '() ;; no previous runs? return null (let loop ((hed (car prev-run-ids)) (tal (cdr prev-run-ids))) - (let ((results (db:get-tests-for-run db hed test-name item-path '() '()))) + (let ((results (cdb:remote-run db:get-tests-for-run #f hed (conc test-name "/" item-path) '() '()))) (debug:print 4 "Got tests for run-id " run-id ", test-name " test-name ", item-path " item-path " results: " (intersperse results "\n")) ;; Keep only the youngest of any test/item combination (for-each (lambda (testdat) @@ -112,15 +188,15 @@ (map cdr (hash-table->alist tests-hash)) ;; return a list of the most recent tests (loop (car tal)(cdr tal)))))))))) ;; Do not rpc this one, do the underlying calls!!! (define (tests:test-set-status! test-id state status comment dat) - (debug:print 4 "INFO: tests:test-set-status! test-id=" test-id ", state=" state ", status=" status ", dat=" dat) + (debug:print-info 4 "tests:test-set-status! test-id=" test-id ", state=" state ", status=" status ", dat=" dat) (let* ((db #f) (real-status status) (otherdat (if dat dat (make-hash-table))) - (testdat (open-run-close db:get-test-info-by-id db test-id)) + (testdat (cdb:get-test-info-by-id *runremote* test-id)) (run-id (db:test-get-run_id testdat)) (test-name (db:test-get-testname testdat)) (item-path (db:test-get-item-path testdat)) ;; before proceeding we must find out if the previous test (where all keys matched except runname) ;; was WAIVED if this test is FAIL @@ -140,14 +216,14 @@ (if waived (set! real-status "WAIVED")) (debug:print 4 "real-status " real-status ", waived " waived ", status " status) ;; update the primary record IF state AND status are defined (if (and state status) - (rdb:test-set-status-state test-id real-status state #f)) + (cdb:test-set-status-state *runremote* test-id real-status state #f)) ;; if status is "AUTO" then call rollup (note, this one modifies data in test - ;; run area, do not rpc it (yet) + ;; run area, it does remote calls under the hood. (if (and test-id state status (equal? status "AUTO")) (db:test-data-rollup #f test-id status)) ;; add metadata (need to do this way to avoid SQL injection issues) @@ -179,48 +255,46 @@ expected "," tol "," units "," dcomment ",," ;; extra comma for status type ))) - (open-run-close db:csv->test-data db test-id + (cdb:remote-run db:csv->test-data #f test-id dat)))) ;; need to update the top test record if PASS or FAIL and this is a subtest - (open-run-close db:roll-up-pass-fail-counts db run-id test-name item-path status) + (if (not (equal? item-path "")) + (cdb:roll-up-pass-fail-counts *runremote* run-id test-name item-path status)) (if (or (and (string? comment) (string-match (regexp "\\S+") comment)) waived) (let ((cmt (if waived waived comment))) - (open-run-close db:test-set-comment db test-id cmt))) + (cdb:remote-run db:test-set-comment #f test-id cmt))) )) + (define (tests:test-set-toplog! db run-id test-name logf) - (sqlite3:execute db "UPDATE tests SET final_logf=? WHERE run_id=? AND testname=? AND item_path='';" - logf run-id test-name)) + (cdb:client-call *runremote* 'tests:test-set-toplog #t 2 logf run-id test-name)) (define (tests:summarize-items db run-id test-name force) ;; if not force then only update the record if one of these is true: ;; 1. logf is "log/final.log ;; 2. logf is same as outputfilename - (let ((outputfilename (conc "megatest-rollup-" test-name ".html")) - (orig-dir (current-directory)) - (logf #f)) + (let* ((outputfilename (conc "megatest-rollup-" test-name ".html")) + (orig-dir (current-directory)) + (logf-info (cdb:remote-run db:test-get-logfile-info #f run-id test-name)) + (logf (if logf-info (cadr logf-info) #f)) + (path (if logf-info (car logf-info) #f))) ;; This query finds the path and changes the directory to it for the test - (sqlite3:for-each-row - (lambda (path final_logf) - (set! logf final_logf) - (if (directory? path) - (begin - (print "Found path: " path) - (change-directory path)) - ;; (set! outputfilename (conc path "/" outputfilename))) - (print "No such path: " path))) - db - "SELECT rundir,final_logf FROM tests WHERE run_id=? AND testname=? AND item_path='';" - run-id test-name) - (print "summarize-items with logf " logf) + (set! logf (car logf-info)) + (if (directory? path) + (begin + (debug:print 4 "Found path: " path) + (change-directory path)) + ;; (set! outputfilename (conc path "/" outputfilename))) + (print "No such path: " path)) + (debug:print 1 "summarize-items with logf " logf) (if (or (equal? logf "logs/final.log") (equal? logf outputfilename) force) (begin (if (obtain-dot-lock outputfilename 1 20 30) ;; retry every second for 20 seconds, call it dead after 30 seconds and steal the lock @@ -228,33 +302,38 @@ (print "Failed to obtain lock for " outputfilename)) (let ((oup (open-output-file outputfilename)) (counts (make-hash-table)) (statecounts (make-hash-table)) (outtxt "") - (tot 0)) + (tot 0) + (testdat (cdb:remote-run db:test-get-records-for-index-file #f run-id test-name))) (with-output-to-port oup (lambda () (set! outtxt (conc outtxt "Summary: " test-name "

Summary for " test-name "

")) - (sqlite3:for-each-row - (lambda (id itempath state status run_duration logf comment) - (hash-table-set! counts status (+ 1 (hash-table-ref/default counts status 0))) - (hash-table-set! statecounts state (+ 1 (hash-table-ref/default statecounts state 0))) - (set! outtxt (conc outtxt "" - " " itempath "" - "" state "" - "" status "" - "" (if (equal? comment "") - " " - comment) "" - ""))) - db - "SELECT id,item_path,state,status,run_duration,final_logf,comment FROM tests WHERE run_id=? AND testname=? AND item_path != '';" - run-id test-name) - + (for-each + (lambda (testrecord) + (let ((id (vector-ref testrecord 0)) + (itempath (vector-ref testrecord 1)) + (state (vector-ref testrecord 2)) + (status (vector-ref testrecord 3)) + (run_duration (vector-ref testrecord 4)) + (logf (vector-ref testrecord 5)) + (comment (vector-ref testrecord 6))) + (hash-table-set! counts status (+ 1 (hash-table-ref/default counts status 0))) + (hash-table-set! statecounts state (+ 1 (hash-table-ref/default statecounts state 0))) + (set! outtxt (conc outtxt "" + " " itempath "" + "" state "" + "" status "" + "" (if (equal? comment "") + " " + comment) "" + "")))) + testdat) (print "
") ;; Print out stats for status (set! tot 0) (print "") (for-each (lambda (state) @@ -278,17 +357,18 @@ "" outtxt "

State stats

ItemStateStatusComment
") (release-dot-lock outputfilename))) (close-output-port oup) (change-directory orig-dir) + ;; NB// tests:test-set-toplog! is remote internal... (tests:test-set-toplog! db run-id test-name outputfilename) ))))) (define (get-all-legal-tests) (let* ((tests (glob (conc *toppath* "/tests/*"))) (res '())) - (debug:print 4 "INFO: Looking at tests " (string-intersperse tests ",")) + (debug:print-info 4 "Looking at tests " (string-intersperse tests ",")) (for-each (lambda (testpath) (if (file-exists? (conc testpath "/testconfig")) (set! res (cons (last (string-split testpath "/")) res)))) tests) res)) @@ -345,22 +425,22 @@ #t ;; if a is a higher priority than b then we are good to go #f)))))))) ;; for each test: ;; -(define (tests:filter-non-runnable db run-id testkeynames testrecordshash) +(define (tests:filter-non-runnable run-id testkeynames testrecordshash) (let ((runnables '())) (for-each (lambda (testkeyname) (let* ((test-record (hash-table-ref testrecordshash testkeyname)) (test-name (tests:testqueue-get-testname test-record)) (itemdat (tests:testqueue-get-itemdat test-record)) (item-path (tests:testqueue-get-item_path test-record)) (waitons (tests:testqueue-get-waitons test-record)) (keep-test #t) - (test-id (db:get-test-id db run-id test-name item-path)) - (tdat (db:get-test-info-by-id db test-id))) + (test-id (cdb:remote-run db:get-test-id #f run-id test-name item-path)) + (tdat (cdb:get-test-info-by-id *runremote* test-id))) (if tdat (begin ;; Look at the test state and status (if (or (member (db:test-get-status tdat) '("PASS" "WARN" "WAIVED" "CHECK")) @@ -371,12 +451,12 @@ ;; examine waitons for any fails. If it is FAIL or INCOMPLETE then eliminate this test ;; from the runnable list (if keep-test (for-each (lambda (waiton) ;; for now we are waiting only on the parent test - (let* ((parent-test-id (db:get-test-id db run-id waiton "")) - (wtdat (db:get-test-info-by-id db test-id))) + (let* ((parent-test-id (cdb:remote-run db:get-test-id #f run-id waiton "")) + (wtdat (cdb:get-test-info-by-id *runremote* test-id))) (if (or (member (db:test-get-status wtdat) '("FAIL" "KILLED")) (member (db:test-get-state wtdat) '("INCOMPETE"))) (set! keep-test #f)))) ;; no point in running this one again @@ -389,13 +469,13 @@ ;; test steps ;;====================================================================== ;; teststep-set-status! used to be here -(define (test-get-kill-request db test-id) ;; run-id test-name itemdat) +(define (test-get-kill-request test-id) ;; run-id test-name itemdat) (let* (;; (item-path (item-list->path itemdat)) - (testdat (db:get-test-info-by-id db test-id))) ;; run-id test-name item-path))) + (testdat (cdb:get-test-info-by-id *runremote* test-id))) ;; run-id test-name item-path))) (equal? (test:get-state testdat) "KILLREQ"))) (define (test:tdb-get-rundat-count tdb) (if tdb (let ((res 0)) Index: tests/Makefile ================================================================== --- tests/Makefile +++ tests/Makefile @@ -18,52 +18,58 @@ NEWTARGET = "-target $(OS)/$(FS)/$(VER)" TARGET = "-target ubuntu/nfs/none" all : test1 test2 test3 test4 test5 +test0 : cleanprep + cd simplerun ; $(MEGATEST) -server - -debug $(DEBUG)& + test1 : cleanprep rm -f simplerun/megatest.db rm -rf simplelinks/ simpleruns/ mkdir -p simplelinks simpleruns cd simplerun;cp ../../*_records.scm .;perl -pi.bak -e 's/define-inline/define/' *_records.scm cd simplerun;echo '(load "../tests.scm")' | $(MEGATEST) -repl -debug $(DEBUG) test2 : fullprep - cd fullrun;$(MEGATEST) -runtests ez_pass,runfirst -reqtarg ubuntu/nfs/none -itempatt a/1 :runname $(RUNNAME)_a $(SERVER) - cd fullrun;sleep 20;megatest -target ubuntu/nfs/none :runname $(RUNNAME) -set-state-status :state COMPLETED :status FORCED -testpatt runfirst -itempatt '' + cd fullrun;$(MEGATEST) -runtests ez_pass,runfirst/a/% -reqtarg ubuntu/nfs/none :runname $(RUNNAME) -debug $(DEBUG) $(LOGGING) + cd fullrun;megatest -runall -target ubunut/nfs/none :runname $(RUNNAME)_01 -testpatt %/,%/ai -debug $(DEBUG) + cd fullrun;megatest -runtests %/,%/ai -target ubunut/nfs/none :runname $(RUNAME)_02 -debug $(DEBUG) + cd fullrun;megatest -runtests runfirst/%,%/ai -target ubunut/nfs/none :runname $(RUNNAME)_02 -debug $(DEBUG) + cd fullrun;megatest -runtests %/,%/winter -target ubunut/nfs/none :runname $(RUNNAME)_03 -debug $(DEBUG) + sleep 40;cd fullrun;megatest -target ubuntu/nfs/none :runname $(RUNNAME) -set-state-status COMPLETED,FORCED :state COMPLETED :status PASS -testpatt ez_p%s,runfirst/ -debug $(DEBUG) $(LOGGING) test3 : fullprep - cd fullrun;$(MEGATEST) -runtests runfirst -reqtarg ubuntu/nfs/none :runname $(RUNNAME)_b $(SERVER) -debug 10 + cd fullrun;$(MEGATEST) -runtests runfirst -reqtarg ubuntu/nfs/none :runname $(RUNNAME)_b -debug 10 test4 : fullprep - cd fullrun;$(MEGATEST) $(SERVER) & - cd fullrun;sleep 5;$(MEGATEST) -debug $(DEBUG) -runall -reqtarg ubuntu/nfs/none :runname $(RUNNAME)_b -m "This is a comment specific to a run" -v $(LOGGING) + cd fullrun;$(MEGATEST) -debug $(DEBUG) -runall -reqtarg ubuntu/nfs/none :runname $(RUNNAME)_b -m "This is a comment specific to a run" -v $(LOGGING) # NOTE: Only one instance can be a server test5 : fullprep - cd fullrun;$(MEGATEST) $(SERVER) & - cd fullrun;sleep 10;$(MEGATEST) -runall $(TARGET) :runname $(RUNNAME)_aa -debug $(DEBUG) $(LOGGING) > aa.log 2> aa.log & + cd fullrun;sleep 0;$(MEGATEST) -runall $(TARGET) :runname $(RUNNAME)_aa -debug $(DEBUG) $(LOGGING) > aa.log 2> aa.log & cd fullrun;sleep 10;$(MEGATEST) -runall $(TARGET) :runname $(RUNNAME)_ab -debug $(DEBUG) $(LOGGING) > ab.log 2> ab.log & cd fullrun;sleep 10;$(MEGATEST) -runall $(TARGET) :runname $(RUNNAME)_ac -debug $(DEBUG) $(LOGGING) > ac.log 2> ac.log & cd fullrun;sleep 10;$(MEGATEST) -runall $(TARGET) :runname $(RUNNAME)_ad -debug $(DEBUG) $(LOGGING) > ad.log 2> ad.log & -# cd fullrun;sleep 10;$(MEGATEST) -runall $(TARGET) :runname $(RUNNAME)_ae -debug $(DEBUG) $(LOGGING) > ae.log 2> ae.log & -# cd fullrun;sleep 10;$(MEGATEST) -runall $(TARGET) :runname $(RUNNAME)_af -debug $(DEBUG) $(LOGGING) > af.log 2> af.log & + # cd fullrun;sleep 10;$(MEGATEST) -runall $(TARGET) :runname $(RUNNAME)_ae -debug $(DEBUG) $(LOGGING) > ae.log 2> ae.log & + # cd fullrun;sleep 10;$(MEGATEST) -runall $(TARGET) :runname $(RUNNAME)_af -debug $(DEBUG) $(LOGGING) > af.log 2> af.log & test6: fullprep - cd fullrun;$(MEGATEST) -runtests runfirst -itempatt %/1 -reqtarg ubuntu/nfs/none :runname $(RUNNAME)_itempatt -v - cd fullrun;$(MEGATEST) -runtests runfirst -itempatt %blahha% -reqtarg ubuntu/nfs/none :runname $(RUNNAME)_itempatt -debug 10 + cd fullrun;$(MEGATEST) -runtests runfirst -testpatt %/1 -reqtarg ubuntu/nfs/none :runname $(RUNNAME)_itempatt -v + cd fullrun;$(MEGATEST) -runtests runfirst -testpatt %blahha% -reqtarg ubuntu/nfs/none :runname $(RUNNAME)_itempatt -debug 10 + cd fullrun;$(MEGATEST) -rollup :runname newrun -target ubuntu/nfs/none -debug 10 + cleanprep : ../*.scm Makefile */*.config - # if [ -e fullrun/megatest.db ]; then sqlite3 fullrun/megatest.db "delete from metadat where var='SERVER';";fi mkdir -p /tmp/mt_runs /tmp/mt_links cd ..;make install - rm -f fullrun/logging.db + rm -f */logging.db touch cleanprep fullprep : cleanprep - cd fullrun;$(MEGATEST) -remove-runs :runname $(RUNNAME)% -target %/%/% -testpatt % -itempatt % + cd fullrun;$(MEGATEST) -remove-runs :runname $(RUNNAME)% -target %/%/% -testpatt %/% cd fullrun;$(BINPATH)/dboard -rows 15 & dashboard : cleanprep cd fullrun && $(BINPATH)/dashboard -rows 25 & @@ -73,10 +79,16 @@ clean : rm cleanprep kill : killall -v mtest main.sh dboard || true - rm -f fullrun/megatest.db fullrun/logging.db || true - killall -v -9 mtest dboard || true + rm -f */megatest.db */logging.db */monitor.db || true + killall -v mtest dboard || true + +hardkill : kill + sleep 5;killall -v mtest main.sh dboard -9 + +listservers : + cd fullrun;$(MEGATEST) -listservers runforever : while(ls); do runname=`date +%F-%R:%S`;(cd fullrun;$(MEGATEST) -runall -target ubuntu/nfs/none :runname $$runname;/home/matt/data/megatest/megatest -runall -target ubuntu/nfs/none :runname $$runname;/home/matt/data/megatest/megatest -runall -target ubuntu/nfs/none :runname $$runname);done Index: tests/fullrun/megatest.config ================================================================== --- tests/fullrun/megatest.config +++ tests/fullrun/megatest.config @@ -15,15 +15,15 @@ synchronous OFF # Throttle roughly scales the db access milliseconds to seconds delay throttle 0.2 # Max retries allows megatest to re-check that a tests status has changed # as tests can have transient FAIL status occasionally -maxretries 500 +maxretries 20 [validvalues] -state start end -status pass fail n/a 0 1 running +state start end 0 1 - 2 +status pass fail n/a 0 1 running - 2 # These are set before all tests, override them # in the testconfig [pre-launch-env-overrides] section [env-override] SPECIAL_ENV_VARS overide them here - should be seen at launch and in the runs Index: tests/fullrun/runconfigs.config ================================================================== --- tests/fullrun/runconfigs.config +++ tests/fullrun/runconfigs.config @@ -4,5 +4,7 @@ WACKYVAR1 #{scheme (args:get-arg "-target")} [default/ubuntu/nfs] WACKYVAR2 #{runconfigs-get CURRENT} +[ubuntu/nfs/none] +WACKYVAR2 #{runconfigs-get CURRENT} Index: tests/fullrun/tests/sqlitespeed/runscript.rb ================================================================== --- tests/fullrun/tests/sqlitespeed/runscript.rb +++ tests/fullrun/tests/sqlitespeed/runscript.rb @@ -16,11 +16,12 @@ num_records=rand(5) # 0000 record_step("add #{num_records}","start","n/a") status=false (0..num_records).each do |i| - randstring="a;lskdfja;sdfj;alsdfj;aslfdj;alsfja;lsfdj;alsfja;lsjfd;lasfjl;asdfja;slfj;alsjf;asljf;alsjf;lasdjf;lasjf;lasjf;alsjf;lashflkashflkerhflkdsvnlasldhlfaldf" + randstring="abc"; + # "a;lskdfja;sdfj;alsdfj;aslfdj;alsfja;lsfdj;alsfja;lsjfd;lasfjl;asdfja;slfj;alsjf;asljf;alsjf;lasdjf;lasjf;lasjf;alsjf;lashflkashflkerhflkdsvnlasldhlfaldf" # status=system "sqlite3 testing.db \"insert into blah (name) values ('#{randstring}');\"" system "megatest -step testing :state wrote_junk :status #{num_records}" sleep(5) puts "i=#{i}" end ADDED tests/fullrun/tests/test_mt_vars/test-path-file.sh Index: tests/fullrun/tests/test_mt_vars/test-path-file.sh ================================================================== --- /dev/null +++ tests/fullrun/tests/test_mt_vars/test-path-file.sh @@ -0,0 +1,28 @@ +#!/bin/bash + + +# get a previous test +export EZFAILPATH=`$MT_MEGATEST -test-files envfile.txt -target $MT_TARGET :runname $MT_RUNNAME -testpatt runfirst/a%` + +echo "Found |$EZFAILPATH|" + +if [ -e $EZFAILPATH ];then + echo All good! +else + echo NOT good! + exit 1 +fi + +export EZFAILPATH2=`$MT_MEGATEST -test-paths -target $MT_TARGET :runname $MT_RUNNAME -testpatt runfirst/a%` + +echo "Found |$EZFAILPATH2|" + +if [ -e $EZFAILPATH2 ];then + echo All good! +else + echo NOT good! + exit 1 +fi + + +exit 0 Index: tests/fullrun/tests/test_mt_vars/testconfig ================================================================== --- tests/fullrun/tests/test_mt_vars/testconfig +++ tests/fullrun/tests/test_mt_vars/testconfig @@ -13,10 +13,13 @@ altvarnotset altvarnotset.sh # EMPTY_VAR should be an empty string empty_var empty_var.sh +# test-path and test-file +test-path test-path-file.sh + [requirements] waiton runfirst priority 0 [items] Index: tests/tests.scm ================================================================== --- tests/tests.scm +++ tests/tests.scm @@ -1,7 +1,22 @@ +;; Copyright 2006-2012, Matthew Welland. +;; +;; This program is made available under the GNU GPL version 2.0 or +;; greater. See the accompanying file COPYING for details. +;; +;; This program is distributed WITHOUT ANY WARRANTY; without even the +;; implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +;; PURPOSE. + +;; strftime('%m/%d/%Y %H:%M:%S','now','localtime') + (require-extension test) (require-extension regex) +(require-extension srfi-18) +(import srfi-18) +(require-extension zmq) +(import zmq) (define test-work-dir (current-directory)) ;; read in all the _record files (let ((files (glob "*_records.scm"))) @@ -8,19 +23,93 @@ (for-each (lambda (file) (print "Loading " file) (load file)) files)) + +(define *runremote* #f) ;;====================================================================== ;; P R O C E S S E S ;;====================================================================== (test "cmd-run-with-stderr->list" '("No such file or directory") (let ((reslst (cmd-run-with-stderr->list "ls" "/tmp/ihadbetternotexist"))) (string-search (regexp "No such file or directory")(car reslst)))) +;;====================================================================== +;; T E S T M A T C H I N G +;;====================================================================== + +;; tests:glob-like-match +(test #f '("abc") (tests:glob-like-match "abc" "abc")) +(for-each + (lambda (patt str expected) + (test (conc patt " " str "=>" expected) expected (tests:glob-like-match patt str))) + (list "abc" "~abc" "~abc" "a*c" "a%c") + (list "abc" "abcd" "abc" "ABC" "ABC") + (list '("abc") #t #f #f '("ABC")) + ) + +;; tests:match +(test #f #t (tests:match "abc/def" "abc" "def")) +(for-each + (lambda (patterns testname itempath expected) + (test (conc patterns " " testname "/" itempath "=>" expected) + expected + (tests:match patterns testname itempath))) + (list "abc" "abc/%" "ab%/c%" "~abc/c%" "abc/~c%" "a,b/c,%/d" "%/,%/a" "%/,%/a" "%/,%/a" "%" "%" "%/" "%/") + (list "abc" "abc" "abcd" "abc" "abc" "a" "abc" "def" "ghi" "a" "a" "a" "a") + (list "" "" "cde" "cde" "cde" "" "" "a" "b" "" "b" "" "b") + (list #t #t #t #f #f #t #t #t #f #t #t #t #f)) + +;; db:patt->like +(test #f "testname LIKE 't%'" (db:patt->like "testname" "t%" comparator: " AND ")) +(test #f "testname LIKE 't%' AND testname LIKE '%t'" (db:patt->like "testname" "t%,%t" comparator: " AND ")) +(test #f "item_path GLOB ''" (db:patt->like "item_path" "")) + +;; test:match->sqlqry +(test #f "(testname GLOB 'a' AND item_path GLOB 'b') OR (testname LIKE 'a%' AND item_path LIKE '%') OR (testname GLOB '' AND item_path LIKE 'b%')" + (tests:match->sqlqry "a/b,a%,/b%")) +(test #f "(testname GLOB 'a' AND item_path GLOB 'b') OR (testname LIKE 'a%' AND item_path LIKE '%') OR (testname LIKE '%' AND item_path LIKE 'b%')" + (tests:match->sqlqry "a/b,a%,%/b%")) + +;;====================================================================== +;; S E R V E R +;;====================================================================== + +(test "setup for run" #t (begin (setup-for-run) + (string? (getenv "MT_RUN_AREA_HOME")))) + +(test "server-register, get-best-server" #t (let ((res #f)) + (open-run-close tasks:server-register tasks:open-db 1 "bob" 1234 100 'live) + (set! res (open-run-close tasks:get-best-server tasks:open-db)) + (number? (cadddr res)))) + +(test "de-register server" #t (let ((res #f)) + (open-run-close tasks:server-deregister tasks:open-db "bob" pullport: 1234) + (list? (open-run-close tasks:get-best-server tasks:open-db)))) + +(define hostinfo #f) +(test "get-best-server" #t (let ((dat (open-run-close tasks:get-best-server tasks:open-db))) + (set! hostinfo dat) ;; host ip pullport pubport + (and (string? (car dat)) + (number? (caddr dat))))) + +(test #f #t (let ((zmq-socket (server:client-connect + (cadr hostinfo) + (caddr hostinfo) + ;; (cadddr hostinfo) + ))) + (set! *runremote* zmq-socket) + (string? (car *runremote*)))) + +(test #f #t (let ((res (server:client-login *runremote*))) + (car res))) + +(test #f #t (car (cdb:login *runremote* *toppath* *my-client-signature*))) + ;;====================================================================== ;; C O N F I G F I L E S ;;====================================================================== (define conffile #f) @@ -39,12 +128,10 @@ (define header (list "col1" "col2" "col3" "col4")) (test "Get row by header" "blah" (db:get-value-by-header row header "col4")) ;; (define *toppath* "tests") (define *db* #f) -(test "setup for run" #t (begin (setup-for-run) - (string? (getenv "MT_RUN_AREA_HOME")))) (test "open-db" #t (begin (set! *db* (open-db)) (if *db* #t #f))) ;; quit wasting time, I'm changing *db* to db @@ -56,61 +143,69 @@ (test "get validvalues as list" (list "start" "end" "completed") (string-split (config-lookup *configdat* "validvalues" "state"))) (for-each (lambda (item) (test (conc "get valid items (" item ")") - item (check-valid-items "state" item))) + item (items:check-valid-items "state" item))) (list "start" "end" "completed")) (for-each (lambda (item) (test (conc "get valid items (" item ")") - item (check-valid-items "status" item))) + item (items:check-valid-items "status" item))) (list "pass" "fail" "n/a")) + +(test #f #f (items:check-valid-items "state" "blahfool")) (test "write env files" "nada.csh" (begin (save-environment-as-files "nada") (and (file-exists? "nada.sh") (file-exists? "nada.csh")))) + +(test #f #t (cdb:client-call *runremote* 'immediate #t 1 (lambda ()(display "Got here eh!?") #t))) + +;; (set! *verbosity* 20) +(test #f *verbosity* (cadr (cdb:set-verbosity *runremote* *verbosity*))) +(test #f #f (cdb:roll-up-pass-fail-counts *runremote* 1 "test1" "" "PASS")) +;; (set! *verbosity* 1) +;; (cdb:set-verbosity *runremote* *verbosity*) (test "get all legal tests" (list "test1" "test2") (sort (get-all-legal-tests) string<=?)) -(test "register-test, test info" "NOT_STARTED" - (begin - (rdb:tests-register-test *db* 1 "nada" "") - ;; (rdb:flush-queue) - (vector-ref (db:get-test-info *db* 1 "nada" "") 3))) - -(test #f "NOT_STARTED" - (begin - (rdb:tests-register-test #f 1 "nada" "") - ;; (rdb:flush-queue) - (vector-ref (open-run-close db:get-test-info #f 1 "nada" "") 3))) (test "get-keys" "SYSTEM" (vector-ref (car (db:get-keys *db*)) 0));; (key:get-fieldname (car (sort (db-get-keys *db*)(lambda (a b)(string>=? (vector-ref a 0)(vector-ref b 0))))))) (define remargs (args:get-args - '("bar" "foo" ":runname" "bob" ":sysname" "ubuntu" ":fsname" "nfs" ":datapath" "blah/foo" "nada") + '("bar" "foo" ":runname" "bob" ":SYSTEM" "ubuntu" ":RELEASE" "v1.2" ":datapath" "blah/foo" "nada") (list ":runname" ":state" ":status") (list "-h") args:arg-hash 0)) (test "register-run" #t (number? (runs:register-run *db* (db:get-keys *db*) - '(("SYSTEM" "key1")("OS" "key2")) + '(("SYSTEM" "key1")("RELEASE" "key2")) "myrun" "new" "n/a" "bob"))) + +(test #f #t (cdb:tests-register-test *runremote* 1 "nada" "")) +(test #f 1 (cdb:remote-run db:get-test-id #f 1 "nada" "")) +(test #f "NOT_STARTED" (vector-ref (open-run-close db:get-test-info #f 1 "nada" "") 3)) +(test #f "NOT_STARTED" (vector-ref (cdb:get-test-info *runremote* 1 "nada" "") 3)) + (define keys (db:get-keys *db*)) ;;====================================================================== ;; D B ;;====================================================================== + (test #f "FOO LIKE 'abc%def'" (db:patt->like "FOO" "abc%def")) +(test #f (vector '("SYSTEM" "RELEASE" "id" "runname" "state" "status" "owner" "event_time") '()) + (runs:get-runs-by-patt db keys "%")) (test #f "SYSTEM,RELEASE,id,runname,state,status,owner,event_time" (car (runs:get-std-run-fields keys '("id" "runname" "state" "status" "owner" "event_time")))) -(test #f #t (list? (runs:operate-on 'print "%" "%" "%"))) +(test #f #t (runs:operate-on 'print "%" "%" "%")) ;;(test "update-test-info" #t (test-update-meta-info *db* 1 "nada" (setenv "BLAHFOO" "1234") (unsetenv "NADAFOO") (test "env temp overrides" "xyz" (let ((prevvals (alist->env-vars '(("BLAHFOO" 4321)("NADAFOO" xyz)))) @@ -191,40 +286,49 @@ (test "Add a step" #t (begin (db:teststep-set-status! db 2 "step1" "start" 0 "This is a comment" "mylogfile.html") (sleep 2) (db:teststep-set-status! db 2 "step1" "end" "pass" "This is a different comment" "finallogfile.html") - (set! test-id (db:test-get-id (car (db:get-tests-for-run db 1 "test1" "" '() '())))) + (set! test-id (db:test-get-id (car (cdb:remote-run db:get-tests-for-run #f 1 "test1" '() '())))) (number? test-id))) -(test "Get rundir" #t (let ((rundir (db:test-get-rundir-from-test-id db test-id))) - (print "Rundir" rundir) +(test "Get rundir" #t (let ((rundir (cdb:remote-run db:test-get-rundir-from-test-id #f test-id))) + (print "Rundir " rundir) + (system (conc "mkdir -p " rundir)) (string? rundir))) -(test "Create a test db" "../simpleruns/key1/key2/myrun/test1/testdat.db" (let ((tdb (db:open-test-db-by-test-id db test-id))) - (sqlite3#finalize! tdb) - (file-exists? "../simpleruns/key1/key2/myrun/test1/testdat.db"))) -(test "Get steps for test" #t (> (length (db:get-steps-for-test db test-id)) 0)) -(test "Get nice table for steps" "2s" +(test #f #t (sqlite3#database? (open-test-db "./"))) +(test "Create a test db" "../simpleruns/key1/key2/myrun/test1/testdat.db" + (let ((tdb (open-run-close db:open-test-db-by-test-id db test-id))) + (if tdb (sqlite3#finalize! tdb)) + (file-exists? "../simpleruns/key1/key2/myrun/test1/testdat.db"))) + +(test "Get steps for test" #t (let ((steps (cdb:remote-run db:get-steps-for-test #f test-id))) + (print steps) + (> (length steps) 0))) +(test "Get nice table for steps" "2.0s" (begin - (vector-ref (hash-table-ref (db:get-steps-table db test-id) "step1") 4))) + (vector-ref (hash-table-ref (open-run-close db:get-steps-table #f test-id) "step1") 4))) + +;; (exit) + +(test #f "myrun" (cdb:remote-run db:get-run-name-from-id #f 1)) + +(test #f #f (cdb:remote-run db:roll-up-pass-fail-counts #f 1 "nada" "" "PASS")) ;;====================================================================== ;; R E M O T E C A L L S ;;====================================================================== -;; start a server process -(set! *verbosity* 10) -(define server-pid (process-run "../../bin/megatest" (list "-server" "-" "-debug" (conc *verbosity*)))) -(sleep 2) (define start-wait (current-seconds)) -(server:client-setup) (print "Starting intensive cache and rpc test") (for-each (lambda (params) - ;;; (rdb:tests-register-test #f 1 (conc "test" (random 20)) "") - (apply rdb:test-set-status-state test-id params) - (rdb:pass-fail-counts test-id (random 100) (random 100)) - (rdb:test-rollup-test_data-pass-fail test-id) + (print "Intensive: params=" params) + (cdb:tests-register-test *runremote* 1 (conc "test" (random 20)) "") + (apply cdb:test-set-status-state *runremote* test-id params) + (cdb:pass-fail-counts *runremote* test-id (random 100) (random 100)) + (cdb:test-rollup-test_data-pass-fail *runremote* test-id) + (cdb:roll-up-pass-fail-counts *runremote* 1 "test1" "" (cadr params)) (thread-sleep! 0.01)) ;; cache ordering granularity is at the second level. Should really be at the ms level '(("COMPLETED" "PASS" #f) ("NOT_STARTED" "FAIL" "Just testing") ("NOT_STARTED" "FAIL" "Just testing") ("NOT_STARTED" "FAIL" "Just testing") @@ -262,30 +366,35 @@ ("COMPLETED" "PASS" #f) ("NOT_STARTED" "FAIL" "Just testing") ("KILLED" "UNKNOWN" "More testing") ("KILLED" "UNKNOWN" "More testing") )) + ;; now set all tests to completed -(rdb:flush-queue) -(let ((tests (open-run-close db:get-tests-for-run #f 1 "%" "%" '() '()))) +(cdb:flush-queue *runremote*) +(let ((tests (cdb:remote-run db:get-tests-for-run #f 1 "%" '() '()))) (print "Setting " (length tests) " to COMPLETED/PASS") (for-each (lambda (test) - (rdb:test-set-status-state (db:test-get-id test) "COMPLETED" "PASS" "Forced pass")) + (cdb:test-set-status-state *runremote* (db:test-get-id test) "COMPLETED" "PASS" "Forced pass")) tests)) -(print "Waiting for server to be done, should be about 20 seconds") -(process-wait server-pid) -(test "Server wait time" #t (let ((run-delta (- (current-seconds) start-wait))) - (print "Server ran for " run-delta " seconds") - (> run-delta 20))) +;; (process-wait server-pid) +;; (test "Server wait time" #t (let ((run-delta (- (current-seconds) start-wait))) +;; (print "Server ran for " run-delta " seconds") +;; (> run-delta 20))) (test "Rollup the run(s)" #t (begin (runs:rollup-run keys (keys->alist keys "na") "rollup" "matt") #t)) (hash-table-set! args:arg-hash ":runname" "%") (test "Remove the rollup run" #t (begin (operate-on 'remove-runs))) + +(print "Waiting for server to be done, should be about 20 seconds") +(cdb:kill-server *runremote*) + +;; (thread-join! th1 th2 th3) ;; ADD ME!!!! (db:get-prereqs-not-met *db* 1 '("runfirst") "" mode: 'normal) ;; ADD ME!!!! (rdb:get-tests-for-run *db* 1 "runfirst" #f '() '()) ADDED testzmq/hwclient.scm Index: testzmq/hwclient.scm ================================================================== --- /dev/null +++ testzmq/hwclient.scm @@ -0,0 +1,16 @@ +(use zmq posix srfi-18) + +(define s (make-socket 'req)) +(connect-socket s "tcp://*:5563") + +(define myname (cadr (argv))) + +(print "Start client...") + +(do ((i 0 (+ i 1))) + ((>= i 1000)) + (print "sending message #" i) + (send-message s (conc "Hello from " myname)) + (print "sent \"Hello\", looking for a reply") + (printf "Received reply ~a [~a]\n" + i (receive-message s))) ADDED testzmq/hwserver.scm Index: testzmq/hwserver.scm ================================================================== --- /dev/null +++ testzmq/hwserver.scm @@ -0,0 +1,28 @@ +(use zmq srfi-18 posix) + +(define th1 (make-thread + (lambda () + (let ((s (make-socket 'rep))) + (bind-socket s "tcp://*:5563") + (print "Start server...") + (let loop () + (let* ((msg (receive-message s)) + (name (caddr (string-split msg " "))) + (resp (conc "World " name))) + (print "Received request: [" msg "]") + (thread-sleep! 0.0001) + (print "Sending response \"" resp "\"") + (send-message s resp) + (loop))))))) +(define th2 (make-thread + (lambda () + (let loop ((count 0)) + (print "count is " count) + (thread-sleep! 0.1) + (if (< count 10000) + (loop (+ count 1))))))) + +(thread-start! th1) +(thread-start! th2) + +(thread-join! th1) ADDED testzmq/hwtest.sh Index: testzmq/hwtest.sh ================================================================== --- /dev/null +++ testzmq/hwtest.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +echo Compiling hwclient and hwserver +csc hwclient.scm +csc hwserver.scm + +./hwserver > hwserver.log & + +sleep 1 +for x in a b c d e f g h i j k l m n o p q r s t u v w x y z;do +./hwclient $x & +done + +# killall -v hwserver hwclient ADDED testzmq/mockupclient.scm Index: testzmq/mockupclient.scm ================================================================== --- /dev/null +++ testzmq/mockupclient.scm @@ -0,0 +1,42 @@ +(use zmq posix numbers) + +(define cname "Bob") +(define runtime 10) +(let ((args (argv))) + (if (< (length args) 3) + (begin + (print "Usage: mockupclient clientname runtime") + (exit)) + (begin + (set! cname (cadr args)) + (set! runtime (string->number (caddr args)))))) + +;; (define start-delay (/ (random 100) 9)) +;; (define runtime (+ 1 (/ (random 200) 2))) + +(print "Starting client " cname " with runtime " runtime) + +(include "mockupclientlib.scm") + +(set! endtime (+ (current-seconds) runtime)) + +;; first ping the server to ensure we have a connection +(if (server-ping cname 5) + (print "SUCCESS: Client " cname " connected to server") + (begin + (print "ERROR: Client " cname " failed ping of server, exiting") + (exit))) + +(let loop () + (let ((x (random 15)) + (varname (list-ref (list "hello" "goodbye" "saluton" "kiaorana")(random 4)))) + (case x + ;; ((1)(dbaccess cname 'sync "nodat" #f)) + ((2 3 4 5)(dbaccess cname 'set varname (random 999))) + ((6 7 8 9 10)(print cname ": Get \"" varname "\" " (dbaccess cname 'get varname #f))) + (else + (thread-sleep! 0.011))) + (if (< (current-seconds) endtime) + (loop)))) + +(print "Client " cname " all done!!") ADDED testzmq/mockupclientlib.scm Index: testzmq/mockupclientlib.scm ================================================================== --- /dev/null +++ testzmq/mockupclientlib.scm @@ -0,0 +1,63 @@ +(define sub (make-socket 'sub)) +(define push (make-socket 'push)) +(socket-option-set! sub 'subscribe cname) +(socket-option-set! sub 'hwm 1000) +(socket-option-set! push 'hwm 1000) + +(connect-socket sub "tcp://localhost:6563") +(connect-socket push "tcp://localhost:6564") + +(thread-sleep! 0.2) + +(define (server-ping cname timeout) + (let ((msg (conc cname ":ping:" timeout)) + (maxtime (+ (current-seconds) timeout))) + (print "pinging server from " cname " with timeout " timeout) + (let loop ((res #f)) + (if (< maxtime (current-seconds)) + #f ;; failed to ping + (if (equal? res "Got ping") + #t + (begin + (print "Ping received from server " res) + (send-message push msg) + (thread-sleep! 0.1) + (loop (receive-message sub non-blocking: #t)))))))) + +(define (dbaccess cname cmd var val #!key (numtries 20)) + (let* ((msg (conc cname ":" cmd ":" (if val (conc var " " val) var))) + (res #f) + (mtx1 (make-mutex)) + (do-access (lambda () + (let ((tmpres #f)) + (print "Sending msg: " msg) + (send-message push msg) + (print "Message " msg " sent") + (print "Client " cname " waiting for response to " msg) + (print "Client " cname " received address " (receive-message* sub)) + (set! tmpres (receive-message* sub)) + (mutex-lock! mtx1) + (set! res tmpres) + (mutex-unlock! mtx1)))) + (th1 (make-thread do-access "do access")) + (th2 (make-thread (lambda () + (let ((result #f)) + (mutex-lock! mtx1) + (set! result res) + (mutex-unlock! mtx1) + (thread-sleep! 5) + (if (not result) + (if (> numtries 0) + (begin + (print "WARNING: access timed out for " cname ", trying again. Trys remaining=" numtries) + (dbaccess cname cmd var val numtries: (- numtries 1))) + (begin + (print "ERROR: dbaccess timed out. Exiting") + (exit))))) + "timeout thread")))) + (thread-start! th1) + (thread-start! th2) + (thread-join! th1) + (if res (print "SUCCESS: received " res " with " numtries " remaining possible attempts")) + res)) + ADDED testzmq/mockupserver.scm Index: testzmq/mockupserver.scm ================================================================== --- /dev/null +++ testzmq/mockupserver.scm @@ -0,0 +1,151 @@ +;; pub/sub with envelope address +;; Note that if you don't insert a sleep, the server will crash with SIGPIPE as soon +;; as a client disconnects. Also a remaining client may receive tons of +;; messages afterward. + +(use zmq srfi-18 sqlite3 numbers) + +(define pub (make-socket 'pub)) +(define pull (make-socket 'pull)) +(define cname "server") +(define total-db-accesses 0) +(define start-time (current-seconds)) + +(socket-option-set! pub 'hwm 1000) +(socket-option-set! pull 'hwm 1000) + +(bind-socket pub "tcp://*:6563") +(bind-socket pull "tcp://*:6564") + +(thread-sleep! 0.2) + +(define (open-db) + (let* ((dbpath "mockup.db") + (dbexists (file-exists? dbpath)) + (db (open-database dbpath)) ;; (never-give-up-open-db dbpath)) + (handler (make-busy-timeout 10))) + (set-busy-handler! db handler) + (if (not dbexists) + (for-each + (lambda (stmt) + (execute db stmt)) + (list + "PRAGMA SYNCHRONOUS=0;" + "CREATE TABLE clients (id INTEGER PRIMARY KEY,name TEXT,num_accesses INTEGER DEFAULT 0);" + "CREATE TABLE vars (var TEXT,val TEXT,CONSTRAINT vars_constraint UNIQUE (var));"))) + db)) + +(define cid-cache (make-hash-table)) + +(define (get-client-id db cname) + (let ((cid (hash-table-ref/default cid-cache cname #f))) + (if cid + cid + (begin + (execute db "INSERT OR REPLACE INTO clients (name) VALUES(?);" cname) + (for-each-row + (lambda (id) + (set! cid id)) + db + "SELECT id FROM clients WHERE name=?;" cname) + (hash-table-set! cid-cache cname cid) + (set! total-db-accesses (+ total-db-accesses 2)) + cid)))) + +(define (count-client db cname) + (let ((cid (get-client-id db cname))) + (execute db "UPDATE clients SET num_accesses=num_accesses+1 WHERE id=?;" cid) + (set! total-db-accesses (+ total-db-accesses 1)) + )) + +(define db (open-db)) +;; (define queuelst '()) +;; (define mx1 (make-mutex)) + +(define max-queue-len 0) + +(define (process-queue queuelst) + (let ((queuelen (length queuelst))) + (if (> queuelen max-queue-len) + (set! max-queue-len queuelen)) + (for-each + (lambda (item) + (let ((cname (vector-ref item 1)) + (clcmd (vector-ref item 2)) + (cdata (vector-ref item 3))) + (send-message pub cname send-more: #t) + (send-message pub (case clcmd + ((sync) + (conc queuelen)) + ((set) + (set! total-db-accesses (+ total-db-accesses 1)) + (apply execute db "INSERT OR REPLACE INTO vars (var,val) VALUES (?,?);" (string-split cdata)) + "ok") + ((get) + (set! total-db-accesses (+ total-db-accesses 1)) + (let ((res "noval")) + (for-each-row + (lambda (val) + (set! res val)) + db + "SELECT val FROM vars WHERE var=?;" cdata) + res)) + (else (conc "unk cmd: " clcmd)))))) + queuelst))) + +;; SERVER THREAD +(define th1 (make-thread + (lambda () + (let ((last-run 0)) ;; current-seconds when run last + (let loop ((queuelst '())) + (let* ((indat (receive-message* pull)) + (parts (string-split indat ":")) + (cname (car parts)) ;; client name + (clcmd (string->symbol (cadr parts))) ;; client cmd + (cdata (caddr parts)) ;; client data + (svect (vector (current-seconds) cname clcmd cdata))) ;; record for the queue + ;; (print "Server received message: " indat) + (count-client db cname) + (case clcmd + ((ping) + (print "Got ping from " cname) + (send-message pub cname send-more: #t) + (send-message pub "Got ping") + (loop queuelst)) + ((sync) ;; just process the queue + (print "Got sync from " cname) + (process-queue (cons svect queuelst)) + (loop '())) + ((get) + (process-queue (cons svect queuelst)) + (loop '())) + (else + (loop (cons svect queuelst)))))))) + "server thread")) + +(include "mockupclientlib.scm") + +;; SYNC THREAD +;; send a sync to the pull port +(define th2 (make-thread + (lambda () + (let ((last-action-time (current-seconds))) + (let loop () + (thread-sleep! 5) + (let ((queuelen (string->number (dbaccess "server" 'sync "nada" #f))) + (last-action-delta #f)) + (if (> queuelen 1)(set! last-action-time (current-seconds))) + (set! last-action-delta (- (current-seconds) last-action-time)) + (print "Server: Got queuelen=" queuelen ", last-action-delta=" last-action-delta) + (if (< last-action-delta 60) + (loop) + (print "Server exiting, 25 seconds since last access")))))) + "sync thread")) + +(thread-start! th1) +(thread-start! th2) +(thread-join! th2) + +(let* ((run-time (- (current-seconds) start-time)) + (queries/second (/ total-db-accesses run-time))) + (print "Server exited! Total db accesses=" total-db-accesses " in " run-time " seconds for " queries/second " queries/second with max queue length of: " max-queue-len)) ADDED testzmq/random.scm Index: testzmq/random.scm ================================================================== --- /dev/null +++ testzmq/random.scm @@ -0,0 +1,8 @@ +(use posix numbers) +(randomize (inexact->exact (current-seconds))) + +(define low (string->number (cadr (argv)))) +(define hi (string->number (caddr (argv)))) + +(print (+ low (random (- hi low)))) + ADDED testzmq/testmockup.sh Index: testzmq/testmockup.sh ================================================================== --- /dev/null +++ testzmq/testmockup.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +rm -f mockup.db + +echo Compiling mockupserver.scm and mockupclient.scm + +# Clean up first +killall mockupserver mockupclient -v + +csc random.scm +csc mockupserver.scm +csc mockupclient.scm + +echo Starting server +./mockupserver & + +sleep 1 + +rm -f mockupclients.log + +echo Starting clients +for i in a b c d e f g h i j k l m n o p q s t u v w x y z; + do + for k in a b; + do + for j in 0 1 2 3 4 5 6 7 8 9; + do + waittime=`./random 0 60` + runtime=`./random 5 120` + echo "Starting client $i$k$j with waittime $waittime and runtime $runtime" + (sleep $waittime;./mockupclient $i$k$j $runtime) & + # >> mockupclients.log & + done + done +done + +wait +echo testmockup.sh script done +# echo "Waiting for 5 seconds then killing all mockupserver and mockupclient processes" +# sleep 30 +# killall -v mockupserver mockupclient ADDED utils/deploy.sh Index: utils/deploy.sh ================================================================== --- /dev/null +++ utils/deploy.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +set -x + +if [[ $DEPLOYTARG == "" ]] ; then + echo Installing into deploytarg + export DEPLOYTARG=$PWD/deploytarg +fi + +# Make the deploy dir +mkdir -p $DEPLOYTARG + +if [[ $proxy == "" ]]; then + echo 'Please set the environment variable "proxy" to host.com:port (e.g. foo.com:1234) if you need to use a proxy' + echo PROX="" +else + export http_proxy=http://$proxy + export PROX="-proxy $proxy" +fi + +export CHICKENINSTDIR=$(dirname $(dirname $(type -p csi))) + +# First copy in the needed iup, sqlite3 and zmq libraries +cp $CHICKENINSTDIR/lib/lib{zmq,uuid}* $DEPLOYTARG +cp $CHICKENINSTDIR/lib/libchicken.* $DEPLOYTARG + +# Then run the deploy for all needed # Some eggs are quoted since they are reserved to Bash +for f in matchable readline apropos base64 regex-literals format "regex-case" "test" coops \ + trace csv dot-locking posix-utils posix-extras directory-utils hostinfo tcp rpc \ + csv-xml fmt json md5 iup canvas-draw ; do + if ! [[ -e $DEPLOYTARG/$f.so ]];then + chicken-install $PROX -deploy $f -prefix $DEPLOYTARG + # chicken-install -deploy -prefix $DEPLOYTARG $PROX $f + else + echo Skipping install of egg $f as it is already installed + fi +done + +export CSC_OPTIONS="-I$CHICKENINSTDIR/include -L$DEPLOYTARG" +chicken-install -deploy zmq -prefix $DEPLOYTARG +chicken-install -deploy sqlite3 -prefix $DEPLOYTARG + +make $DEPLOYTARG/megatest +make $DEPLOYTARG/dashboard Index: utils/installall.sh ================================================================== --- utils/installall.sh +++ utils/installall.sh @@ -1,6 +1,8 @@ -#!/bin/bash +#! /usr/bin/env bash + +set -x # Copyright 2007-2010, Matthew Welland. # # This program is made available under the GNU GPL version 2.0 or # greater. See the accompanying file COPYING for details. @@ -9,11 +11,11 @@ # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. echo You may need to do the following first: echo sudo apt-get install libreadline-dev -echo sudo apt-get install libwebkitgtk-dev +echo sudo apt-get install libwebkitgtk-dev echo sudo apt-get install libmotif3 -OR- set KTYPE=26g4 echo KTYPE can be 26, 26g4, or 32 echo KTYPE=$KTYPE echo You are using PREFIX=$PREFIX echo You are using proxy="$proxy" @@ -21,11 +23,11 @@ echo "Set additional_libpath to help find gtk or other libraries, don't forget a leading :" echo ADDITIONAL_LIBPATH=$ADDITIONAL_LIBPATH echo echo To use previous IUP libraries set USEOLDIUP to yes echo USEOLDIUP=$USEOLDIUP - +echo echo Hit ^C now to do that # A nice way to run this script: # # script -c 'PREFIX=/tmp/delme ./installall.sh ' installall.log @@ -53,45 +55,51 @@ if ! [[ -e chicken-${CHICKEN_VERSION}.tar.gz ]]; then wget http://code.call-cc.org/releases/${CHICKEN_VERSION}/chicken-${CHICKEN_VERSION}.tar.gz fi BUILDHOME=$PWD +DEPLOYTARG=$BUILDHOME/deploy + if [[ $PREFIX == "" ]]; then PREFIX=$PWD/inst fi export PATH=$PREFIX/bin:$PATH export LIBPATH=$PREFIX/lib$ADDITIONAL_LIBPATH export LD_LIBRARY_PATH=$LIBPATH +export CHICKEN_INSTALL=$PREFIX/bin/chicken-install echo "export PATH=$PREFIX/bin:\$PATH" > setup-chicken4x.sh echo "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH" >> setup-chicken4x.sh echo PATH=$PATH echo LD_LIBRARY_PATH=$LD_LIBRARY_PATH if ! [[ -e $PREFIX/bin/csi ]]; then tar xfvz chicken-${CHICKEN_VERSION}.tar.gz cd chicken-${CHICKEN_VERSION} + make PLATFORM=linux PREFIX=$PREFIX clean make PLATFORM=linux PREFIX=$PREFIX make PLATFORM=linux PREFIX=$PREFIX install cd $BUILDHOME fi # Some eggs are quoted since they are reserved to Bash -for f in readline apropos base64 regex-literals format "regex-case" "test" coops trace csv dot-locking posix-utils posix-extras directory-utils hostinfo tcp rpc csv-xml fmt ; do - if ! [[ -e $PREFIX/lib/chicken/6/$f.so ]];then - chicken-install $PROX $f - else - echo Skipping install of egg $f as it is already installed - fi -done +# for f in matchable readline apropos base64 regex-literals format "regex-case" "test" coops trace csv dot-locking posix-utils posix-extras directory-utils hostinfo tcp rpc csv-xml fmt json md5; do +$CHICKEN_INSTALL $PROX -keep-installed matchable readline apropos base64 regex-literals format "regex-case" "test" coops trace csv dot-locking posix-utils posix-extras directory-utils hostinfo tcp rpc csv-xml fmt json md5 awful http-client spiffy uri-common intarweb http-client spiffy-request-vars +# if ! [[ -e $PREFIX/lib/chicken/6/$f.so ]];then +# $CHICKEN_INSTALL $PROX $f +# # $CHICKEN_INSTALL -deploy -prefix $DEPLOYTARG $PROX $f +# else +# echo Skipping install of egg $f as it is already installed +# fi +# done cd $BUILDHOME for a in `ls */*.meta|cut -f1 -d/` ; do echo $a - (cd $a;chicken-install) + (cd $a;$CHICKEN_INSTALL) done export LIBPATH=$PREFIX/lib$ADDITIONAL_LIBPATH export LD_LIBRARY_PATH=$LIBPATH @@ -103,15 +111,16 @@ if ! [[ -e $PREFIX/bin/sqlite3 ]] ; then if [[ -e sqlite-autoconf-$SQLITE3_VERSION.tar.gz ]]; then tar xfz sqlite-autoconf-$SQLITE3_VERSION.tar.gz (cd sqlite-autoconf-$SQLITE3_VERSION;./configure --prefix=$PREFIX;make;make install) - CSC_OPTIONS="-I$PREFIX/include -L$PREFIX/lib" chicken-install $PROX sqlite3 + # CSC_OPTIONS="-I$PREFIX/include -L$PREFIX/lib" $CHICKEN_INSTALL -prefix $DEPLOYTARG -deploy $PROX sqlite3 + CSC_OPTIONS="-I$PREFIX/include -L$PREFIX/lib" $CHICKEN_INSTALL $PROX sqlite3 fi fi -chicken-install $PROX sqlite3 +# $CHICKEN_INSTALL $PROX sqlite3 if [[ `uname -a | grep x86_64` == "" ]]; then export ARCHSIZE='' else export ARCHSIZE=64_ @@ -122,17 +131,18 @@ else echo WARNING: Using old IUP libraries export files="cd-5.4.1_Linux${KTYPE}_${ARCHSIZE}lib.tar.gz im-3.6.3_Linux${KTYPE}_${ARCHSIZE}lib.tar.gz iup-3.5_Linux${KTYPE}_${ARCHSIZE}lib.tar.gz" fi -mkdir $PREFIX/iuplib +mkdir -p $PREFIX/iuplib for a in `echo $files` ; do if ! [[ -e $a ]] ; then wget http://www.kiatoa.com/matt/iup/$a fi echo Untarring $a into $BUILDHOME/lib (cd $PREFIX/lib;tar xfvz $BUILDHOME/$a;mv include/* ../include) + # (cd $DEPLOYTARG;tar xfvz $BUILDHOME/$a) done # ffcall obtained from: # cvs -z3 -d:pserver:anonymous@cvs.savannah.gnu.org:/sources/libffcall co ffcall @@ -148,20 +158,166 @@ make install cd $BUILDHOME export CSCLIBS=`echo $LD_LIBRARY_PATH | sed 's/:/ -L/g'` -CSC_OPTIONS="-I$PREFIX/include -L$CSCLIBS" chicken-install $PROX -D no-library-checks iup:1.0.2 -CSC_OPTIONS="-I$PREFIX/include -L$CSCLIBS" chicken-install $PROX -D no-library-checks canvas-draw +CSC_OPTIONS="-I$PREFIX/include -L$CSCLIBS" $CHICKEN_INSTALL $PROX -D no-library-checks -feature disable-iup-web iup +# CSC_OPTIONS="-I$PREFIX/include -L$CSCLIBS" $CHICKEN_INSTALL $PROX -D no-library-checks -feature disable-iup-web -deploy -prefix $DEPLOYTARG iup +# iup:1.0.2 +CSC_OPTIONS="-I$PREFIX/include -L$CSCLIBS" $CHICKEN_INSTALL $PROX -D no-library-checks canvas-draw +# CSC_OPTIONS="-I$PREFIX/include -L$CSCLIBS" $CHICKEN_INSTALL $PROX -D no-library-checks -deploy -prefix $DEPLOYTARG canvas-draw + +#====================================================================== +# Note uuid needed only for zmq 2.x series +#====================================================================== + +# http://download.zeromq.org/zeromq-3.2.1-rc2.tar.gz +# zpatchlev=-rc2 +# http://download.zeromq.org/zeromq-2.2.0.tar.gz +# ZEROMQ=zeromq-2.2.0 + +ZEROMQ=zeromq-3.2.2 + +# wget http://www.kernel.org/pub/linux/utils/util-linux/v2.22/util-linux-2.22.tar.gz +UTIL_LINUX=2.21 +# UTIL_LINUX=2.20.1 +if ! [[ -e util-linux-${UTIL_LINUX}.tar.gz ]] ; then + # wget http://www.kiatoa.com/matt/util-linux-2.20.1.tar.gz + wget http://www.kernel.org/pub/linux/utils/util-linux/v${UTIL_LINUX}/util-linux-${UTIL_LINUX}.tar.gz +fi + +if [[ -e util-linux-${UTIL_LINUX}.tar.gz ]] ; then + tar xfz util-linux-${UTIL_LINUX}.tar.gz + cd util-linux-${UTIL_LINUX} + mkdir -p build + cd build + if [[ $UTIL_LINUX = "2.22" ]] ; then + ../configure --prefix=$PREFIX \ +--enable-shared \ +--disable-use-tty-group \ +--disable-makeinstall-chown \ +--disable-makeinstall-setuid \ +--disable-libtool-lock \ +--disable-login \ +--disable-sulogin \ +--disable-su \ +--disable-schedutils \ +--disable-libmount \ +--disable-mount \ +--disable-losetup \ +--disable-fsck \ +--disable-partx \ +--disable-mountpoint \ +--disable-fallocate \ +--disable-unshare \ +--disable-eject \ +--disable-agetty \ +--disable-cramfs \ +--disable-switch_root \ +--disable-pivot_root \ +--disable-kill \ +--disable-libblkid \ +--disable-utmpdump \ +--disable-rename \ +--disable-chsh-only-listed \ +--disable-wall \ +--disable-pg-bell \ +--disable-require-password \ +--disable-libtool-lock \ +--disable-nls \ +--disable-dmesg \ +--without-ncurses + else + ../configure --prefix=$PREFIX \ + --enable-shared \ + --disable-mount \ + --disable-fsck \ + --disable-partx \ + --disable-largefile \ + --disable-tls \ + --disable-libmount \ + --disable-mountpoint \ + --disable-nls \ + --disable-rpath \ + --disable-agetty \ + --disable-cramfs \ + --disable-switch_root \ + --disable-pivot_root \ + --disable-fallocate \ + --disable-unshare \ + --disable-rename \ + --disable-schedutils \ + --disable-libblkid \ + --disable-wall CFLAGS='-fPIC' + +# --disable-makeinstall-chown \ +# --disable-makeinstall-setuid \ + +# --disable-chsh-only-listed +# --disable-pg-bell let pg not ring the bell on invalid keys +# --disable-require-password +# --disable-use-tty-group do not install wall and write setgid tty +# --disable-makeinstall-chown +# --disable-makeinstall-setuid + fi + + (cd libuuid;make install) + # make + # make install + cp $PREFIX/include/uuid/uuid.h $PREFIX/include/uuid.h +fi + + +cd $BUILDHOME + +if ! [[ -e ${ZEROMQ}${zpatchlev}.tar.gz ]] ; then + wget http://download.zeromq.org/${ZEROMQ}${zpatchlev}.tar.gz +fi + +if [[ -e ${ZEROMQ}${zpatchlev}.tar.gz ]] ; then + tar xfz ${ZEROMQ}.tar.gz + cd ${ZEROMQ} + ln -s $PREFIX/include/uuid src + # LDFLAGS=-L$PREFIX/lib ./configure --prefix=$PREFIX + + ./configure --enable-static --prefix=$PREFIX --with-uuid=$PREFIX LDFLAGS="-L$PREFIX/lib" CPPFLAGS="-fPIC -I$PREFIX/include" LIBS="-lgcc" + # --disable-shared CPPFLAGS="-fPIC + # LDFLAGS="-L/usr/lib64 -L$PREFIX/lib" ./configure --enable-static --prefix=$PREFIX + make + make install + CSC_OPTIONS="-I$PREFIX/include -L$CSCLIBS" $CHICKEN_INSTALL $PROX zmq + # CSC_OPTIONS="-I$PREFIX/include -L$CSCLIBS" $CHICKEN_INSTALL $PROX -deploy -prefix $DEPLOYTARG zmq +fi + +cd $BUILDHOME + +## WEBKIT=WebKit-r131972 +## if ! [[ -e ${WEBKIT}.tar.bz2 ]] ; then +## # http://builds.nightly.webkit.org/files/trunk/src/WebKit-r131972.tar.bz2 +## wget http://builds.nightly.webkit.org/files/trunk/src/${WEBKIT}.tar.bz2 +## fi +## +## if [[ x$only_it_worked == $I_wish ]] ;then +## if [[ -e ${WEBKIT}.tar.bz2 ]] ; then +## tar xfj ${WEBKIT}.tar.bz2 +## cd $WEBKIT +## ./autogen.sh +## ./configure --prefix=$PREFIX +## make +## make install +## fi +## fi +## +## cd $BUILHOME # export CD_REL=d704525ebe1c6d08 # if ! [[ -e Canvas_Draw-$CD_REL.zip ]]; then # wget http://www.kiatoa.com/matt/iup/Canvas_Draw-$CD_REL.zip # fi # # unzip -o Canvas_Draw-$CD_REL.zip # # cd "Canvas Draw-$CD_REL/chicken" -# CSC_OPTIONS="-I$PREFIX/include -L$LIBPATH" chicken-install $PROX -D no-library-checks +# CSC_OPTIONS="-I$PREFIX/include -L$LIBPATH" $CHICKEN_INSTALL $PROX -D no-library-checks echo You may need to add $LD_LIBRARY_PATH to your LD_LIBRARY_PATH variable, a setup-chicken4x.sh echo file can be found in the current directory which should work for setting up to run chicken4x Index: utils/mk_wrapper ================================================================== --- utils/mk_wrapper +++ utils/mk_wrapper @@ -6,8 +6,8 @@ echo "#!/bin/bash" if [ "$LD_LIBRARY_PATH" != "" ];then echo "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH" fi -fullcmd=`realpath $prefix/bin/$cmd` +fullcmd="$prefix/bin/$cmd" echo "$fullcmd \$*"