Index: Makefile ================================================================== --- Makefile +++ Makefile @@ -10,11 +10,11 @@ process.scm runs.scm tasks.scm tests.scm genexample.scm \ http-transport.scm filedb.scm \ client.scm synchash.scm daemon.scm mt.scm \ ezsteps.scm lock-queue.scm sdb.scm \ rmt.scm api.scm tdb.scm rpc-transport.scm \ - portlogger.scm archive.scm env.scm + portlogger.scm archive.scm env.scm diff-report.scm # Eggs to install (straightforward ones) EGGS=matchable readline apropos base64 regex-literals format regex-case test coops trace csv \ dot-locking posix-utils posix-extras directory-utils hostinfo tcp-server rpc csv-xml fmt \ json md5 awful http-client spiffy uri-common intarweb spiffy-request-vars \ Index: client.scm ================================================================== --- client.scm +++ client.scm @@ -12,11 +12,11 @@ ;; C L I E N T S ;;====================================================================== (require-extension (srfi 18) extras tcp s11n) -(use srfi-1 posix regex regex-case srfi-69 hostinfo md5 message-digest) +(use srfi-1 posix regex regex-case srfi-69 hostinfo md5 message-digest matchable) ;; (use zmq) (use (prefix sqlite3 sqlite3:)) (use spiffy uri-common intarweb http-client spiffy-request-vars uri-common intarweb directory-utils) @@ -48,101 +48,15 @@ ((rpc) (rpc:client-connect iface port)) ((http) (http:client-connect iface port)) ((zmq) (zmq:client-connect iface port)) (else (rpc:client-connect iface port)))) -(define (client:setup run-id #!key (remaining-tries 10) (failed-connects 0)) +(define (client:setup areapath #!key (remaining-tries 100) (failed-connects 0)) (case (server:get-transport) - ((rpc) (rpc-transport:client-setup run-id remaining-tries: remaining-tries failed-connects: failed-connects)) ;;(client:setup-rpc run-id)) - ((http)(client:setup-http run-id remaining-tries: remaining-tries failed-connects: failed-connects)) - (else (rpc-transport:client-setup run-id remaining-tries: remaining-tries failed-connects: failed-connects)))) ;; (client:setup-rpc run-id)))) - -;; (define (client:login-no-auto-setup server-info run-id) -;; (case (server:get-transport) -;; ((rpc) (rpc:login-no-auto-client-setup server-info run-id)) -;; ((http) (rmt:login-no-auto-client-setup server-info run-id)) -;; (else (rpc:login-no-auto-client-setup server-info run-id)))) -;; -;; (define (client:setup-rpc run-id) -;; (debug:print 0 *default-log-port* "INFO: client:setup remaining-tries=" remaining-tries) -;; (if (<= remaining-tries 0) -;; (begin -;; (debug:print-error 0 *default-log-port* "failed to start or connect to server for run-id " run-id) -;; (exit 1)) -;; (let ((host-info (hash-table-ref/default *runremote* run-id #f))) -;; (debug:print-info 0 *default-log-port* "client:setup host-info=" host-info ", remaining-tries=" remaining-tries) -;; (if host-info -;; (let* ((iface (car host-info)) -;; (port (cadr host-info)) -;; (start-res (client:connect iface port)) -;; ;; (ping-res (server:ping-server run-id iface port)) -;; (ping-res (client:login-no-auto-setup start-res run-id))) -;; (if ping-res ;; sucessful login? -;; (begin -;; (hash-table-set! *runremote* run-id start-res) -;; start-res) ;; return the server info -;; (if (member remaining-tries '(3 4 6)) -;; (begin ;; login failed -;; (debug:print 25 *default-log-port* "INFO: client:setup start-res=" start-res ", run-id=" run-id ", server-dat=" host-info) -;; (hash-table-delete! *runremote* run-id) -;; (open-run-close tasks:server-force-clean-run-record -;; tasks:open-db -;; run-id -;; (car host-info) -;; (cadr host-info) -;; " client:setup (host-info=#t)") -;; (thread-sleep! 5) -;; (client:setup run-id remaining-tries: 10)) ;; (- remaining-tries 1))) -;; (begin -;; (debug:print 25 *default-log-port* "INFO: client:setup failed to connect, start-res=" start-res ", run-id=" run-id ", host-info=" host-info) -;; (thread-sleep! 5) -;; (client:setup run-id remaining-tries: (- remaining-tries 1)))))) -;; ;; YUK: rename server-dat here -;; (let* ((server-dat (open-run-close tasks:get-server tasks:open-db run-id))) -;; (debug:print-info 0 *default-log-port* "client:setup server-dat=" server-dat ", remaining-tries=" remaining-tries) -;; (if server-dat -;; (let* ((iface (tasks:hostinfo-get-interface server-dat)) -;; (port (tasks:hostinfo-get-port server-dat)) -;; (start-res (http-transport:client-connect iface port)) -;; ;; (ping-res (server:ping-server run-id iface port)) -;; (ping-res (rmt:login-no-auto-client-setup start-res run-id))) -;; (if start-res -;; (begin -;; (hash-table-set! *runremote* run-id start-res) -;; start-res) -;; (if (member remaining-tries '(2 5)) -;; (begin ;; login failed -;; (debug:print 25 *default-log-port* "INFO: client:setup start-res=" start-res ", run-id=" run-id ", server-dat=" server-dat) -;; (hash-table-delete! *runremote* run-id) -;; (open-run-close tasks:server-force-clean-run-record -;; tasks:open-db -;; run-id -;; (tasks:hostinfo-get-interface server-dat) -;; (tasks:hostinfo-get-port server-dat) -;; " client:setup (server-dat = #t)") -;; (thread-sleep! 2) -;; (server:try-running run-id) -;; (thread-sleep! 10) ;; give server a little time to start up -;; (client:setup run-id remaining-tries: 10)) ;; (- remaining-tries 1))) -;; (begin -;; (debug:print 25 *default-log-port* "INFO: client:setup start-res=" start-res ", run-id=" run-id ", server-dat=" server-dat) -;; (thread-sleep! 5) -;; (client:setup run-id remaining-tries: (- remaining-tries 1)))))) -;; (begin ;; no server registered -;; (if (eq? remaining-tries 2) -;; (begin -;; ;; (open-run-close tasks:server-clean-out-old-records-for-run-id tasks:open-db run-id " client:setup (server-dat=#f)") -;; (client:setup run-id remaining-tries: 10)) -;; (begin -;; (thread-sleep! 2) -;; (debug:print 25 *default-log-port* "INFO: client:setup start-res (not defined here), run-id=" run-id ", server-dat=" server-dat) -;; (if (< (open-run-close tasks:num-in-available-state tasks:open-db run-id) 3) -;; (begin -;; ;; (open-run-close tasks:server-clean-out-old-records-for-run-id tasks:open-db run-id " client:setup (server-dat=#f)") -;; (server:try-running run-id))) -;; (thread-sleep! 10) ;; give server a little time to start up -;; (client:setup run-id remaining-tries: (- remaining-tries 1))))))))))) + ((rpc) (rpc-transport:client-setup remaining-tries: remaining-tries failed-connects: failed-connects)) ;;(client:setup-rpc run-id)) + ((http)(client:setup-http areapath remaining-tries: remaining-tries failed-connects: failed-connects)) + (else (rpc-transport:client-setup remaining-tries: remaining-tries failed-connects: failed-connects)))) ;; (client:setup-rpc run-id)))) ;; Do all the connection work, look up the transport type and set up the ;; connection if required. ;; ;; There are two scenarios. @@ -152,100 +66,50 @@ ;; ;; client:setup ;; ;; lookup_server, need to remove *runremote* stuff ;; -(define (client:setup-http run-id #!key (remaining-tries 10) (failed-connects 0)) + +(define (client:setup-http areapath #!key (remaining-tries 100) (failed-connects 0)) (debug:print-info 2 *default-log-port* "client:setup remaining-tries=" remaining-tries) - (let* ((tdbdat (tasks:open-db))) - (if (<= remaining-tries 0) - (begin - (debug:print-error 0 *default-log-port* "failed to start or connect to server for run-id " run-id) - (exit 1)) - (let* ((server-dat (tasks:get-server (db:delay-if-busy tdbdat) run-id))) - (debug:print-info 4 *default-log-port* "client:setup server-dat=" server-dat ", remaining-tries=" remaining-tries) - (if server-dat - (let* ((iface (tasks:hostinfo-get-interface server-dat)) - (hostname (tasks:hostinfo-get-hostname server-dat)) - (port (tasks:hostinfo-get-port server-dat)) - (start-res (case *transport-type* - ((http)(http-transport:client-connect iface port)) - ;;((nmsg)(nmsg-transport:client-connect hostname port)) - )) - (ping-res (case *transport-type* - ((http)(rmt:login-no-auto-client-setup start-res)) - ;; ((nmsg)(let ((logininfo (rmt:login-no-auto-client-setup start-res run-id))) - ;; (if logininfo - ;; (car (vector-ref logininfo 1)) - ;; #f))) - - ))) - (if (and start-res - ping-res) - (begin - (remote-conndat-set! *runremote* start-res) ;; (hash-table-set! *runremote* run-id start-res) - (debug:print-info 2 *default-log-port* "connected to " (http-transport:server-dat-make-url start-res)) - start-res) - (begin ;; login failed but have a server record, clean out the record and try again - (debug:print-info 0 *default-log-port* "client:setup, login failed, will attempt to start server ... start-res=" start-res ", run-id=" run-id ", server-dat=" server-dat) - (case *transport-type* - ((http)(http-transport:close-connections run-id))) - (remote-conndat-set! *runremote* #f) ;; (hash-table-delete! *runremote* run-id) - (tasks:kill-server-run-id run-id) - (tasks:server-force-clean-run-record (db:delay-if-busy tdbdat) - run-id - (tasks:hostinfo-get-interface server-dat) - (tasks:hostinfo-get-port server-dat) - " client:setup (server-dat = #t)") - (if (> remaining-tries 8) - (thread-sleep! (+ 1 (random 5))) ;; spread out the starts a little - (thread-sleep! (+ 15 (random 20)))) ;; it isn't going well. give it plenty of time - (server:try-running *toppath*) - (thread-sleep! 5) ;; give server a little time to start up - (client:setup run-id remaining-tries: (- remaining-tries 1)) - ))) - (begin ;; no server registered - (let ((num-available (tasks:num-in-available-state (db:dbdat-get-db tdbdat) run-id))) - (debug:print-info 0 *default-log-port* "client:setup, no server registered, remaining-tries=" remaining-tries " num-available=" num-available) - (if (< num-available 2) - (server:try-running *toppath*)) - (thread-sleep! (+ 5 (random (- 20 remaining-tries)))) ;; give server a little time to start up, randomize a little to avoid start storms. - (client:setup run-id remaining-tries: (- remaining-tries 1))))))))) - -;; keep this as a function to ease future -(define (client:start run-id server-info) - (http-transport:client-connect (tasks:hostinfo-get-interface server-info) - (tasks:hostinfo-get-port server-info))) - -;; ;; client:signal-handler -;; (define (client:signal-handler signum) -;; (signal-mask! signum) -;; (set! *time-to-exit* #t) -;; (handle-exceptions -;; exn -;; (debug:print 0 *default-log-port* " ... 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-error 0 *default-log-port* "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 *default-log-port* " Done.") -;; (exit 4)) -;; "exit on ^C timer"))) -;; (thread-start! th2) -;; (thread-start! th1) -;; (thread-join! th2)))) -;; -;; ;; client:launch -;; ;; Need to set the signal handler somewhere other than here as this -;; ;; routine will go away. -;; ;; -;; (define (client:launch run-id) -;; (set-signal-handler! signal/int client:signal-handler) -;; (set-signal-handler! signal/term client:signal-handler) -;; (if (client:setup run-id) -;; (debug:print-info 2 *default-log-port* "connected as client") -;; (begin -;; (debug:print-error 0 *default-log-port* "Failed to connect as client") -;; (exit)))) -;; + (server:start-and-wait areapath) + (if (<= remaining-tries 0) + (begin + (debug:print-error 0 *default-log-port* "failed to start or connect to server") + (exit 1)) + ;; + ;; Alternatively here, we can get the list of candidate servers and work our way + ;; through them searching for a good one. + ;; + (let* ((server-dat (server:get-first-best areapath))) + (if (not server-dat) ;; no server found + (client:setup-http areapath remaining-tries: (- remaining-tries 1)) + (let ((host (cadr server-dat)) + (port (caddr server-dat))) + (debug:print-info 4 *default-log-port* "client:setup server-dat=" server-dat ", remaining-tries=" remaining-tries) + (if (not *runremote*)(set! *runremote* (make-remote))) + (if (and host port) + (let* ((start-res (case *transport-type* + ((http)(http-transport:client-connect host port)))) + (ping-res (case *transport-type* + ((http)(rmt:login-no-auto-client-setup start-res))))) + (if (and start-res + ping-res) + (begin + (remote-conndat-set! *runremote* start-res) ;; (hash-table-set! *runremote* run-id start-res) + (debug:print-info 2 *default-log-port* "connected to " (http-transport:server-dat-make-url start-res)) + start-res) + (begin ;; login failed but have a server record, clean out the record and try again + (debug:print-info 0 *default-log-port* "client:setup, login failed, will attempt to start server ... start-res=" start-res ", run-id=" run-id ", server-dat=" server-dat) + (case *transport-type* + ((http)(http-transport:close-connections))) + (remote-conndat-set! *runremote* #f) ;; (hash-table-delete! *runremote* run-id) + (thread-sleep! 1) + (client:setup-http areapath remaining-tries: (- remaining-tries 1)) + ))) + (begin ;; no server registered + (server:kind-run areapath) + (debug:print-info 0 *default-log-port* "client:setup, no server registered, remaining-tries=" remaining-tries) + (thread-sleep! 1) ;; (+ 5 (random (- 20 remaining-tries)))) ;; give server a little time to start up, randomize a little to avoid start storms. + (server:start-and-wait areapath) + (client:setup-http areapath remaining-tries: (- remaining-tries 1))))))))) + Index: common.scm ================================================================== --- common.scm +++ common.scm @@ -141,11 +141,11 @@ (define *launch-setup-mutex* (make-mutex)) ;; need to be able to call launch:setup often so mutex it and re-call the real deal only if *toppath* not set (define *homehost-mutex* (make-mutex)) (defstruct remote (hh-dat (common:get-homehost)) ;; homehost record ( addr . hhflag ) - (server-url (if *toppath* (server:read-dotserver->url *toppath*))) ;; (server:check-if-running *toppath*) #f)) + (server-url (if *toppath* (server:check-if-running *toppath*))) ;; (server:check-if-running *toppath*) #f)) (last-server-check 0) ;; last time we checked to see if the server was alive (conndat #f) (transport *transport-type*) (server-timeout (or (server:get-timeout) 100))) ;; default to 100 seconds @@ -242,19 +242,32 @@ ;; (define (common:rotate-logs) (if (not (directory-exists? "logs"))(create-directory "logs")) (directory-fold (lambda (file rem) - (if (and (string-match "^.*.log" file) - (> (file-size (conc "logs/" file)) 200000)) - (let ((gzfile (conc "logs/" file ".gz"))) - (if (file-exists? gzfile) - (begin - (debug:print-info 0 *default-log-port* "removing " gzfile) - (delete-file gzfile))) - (debug:print-info 0 *default-log-port* "compressing " file) - (system (conc "gzip logs/" file))))) + (handle-exceptions + exn + (debug:print-info 0 *default-log-port* "failed to rotate log " file ", probably handled by another process.") + (let* ((fullname (conc "logs/" file)) + (file-age (- (current-seconds)(file-modification-time fullname)))) + (if (or (and (string-match "^.*.log" file) + (> (file-size fullname) 200000)) + (and (string-match "^server-.*.log" file) + (> (- (current-seconds) (file-modification-time fullname)) + (* 8 60 60)))) + (let ((gzfile (conc fullname ".gz"))) + (if (file-exists? gzfile) + (begin + (debug:print-info 0 *default-log-port* "removing " gzfile) + (delete-file gzfile))) + (debug:print-info 0 *default-log-port* "compressing " file) + (system (conc "gzip " fullname))) + (if (> file-age (* (string->number (or (configf:lookup *configdat* "setup" "log-expire-days") "30")) 24 3600)) + (handle-exceptions + exn + #f + (delete-file fullname))))))) '() "logs")) ;; Force a megatest cleanup-db if version is changed and skip-version-check not specified ;; @@ -572,33 +585,36 @@ (define *wdnum*mutex (make-mutex)) ;; currently the primary job of the watchdog is to run the sync back to megatest.db from the db in /tmp ;; if we are on the homehost and we are a server (by definition we are on the homehost if we are a server) ;; (define (common:watchdog) - (thread-sleep! 0.05) ;; delay for startup (let ((legacy-sync (common:run-sync?)) (debug-mode (debug:debug-mode 1)) (last-time (current-seconds)) - (this-wd-num (begin (mutex-lock! *wdnum*mutex) (let ((x *wdnum*)) (set! *wdnum* (add1 *wdnum*)) (mutex-unlock! *wdnum*mutex) x))) - ) + (this-wd-num (begin (mutex-lock! *wdnum*mutex) (let ((x *wdnum*)) (set! *wdnum* (add1 *wdnum*)) (mutex-unlock! *wdnum*mutex) x)))) (debug:print-info 0 *default-log-port* "watchdog starting. legacy-sync is " legacy-sync" pid="(current-process-id)" this-wd-num="this-wd-num) (if (and legacy-sync (not *time-to-exit*)) - (let ((dbstruct (db:setup))) + (let* ((dbstruct (db:setup)) + (mtdb (dbr:dbstruct-mtdb dbstruct)) + (mtpath (db:dbdat-get-path mtdb))) (debug:print-info 0 *default-log-port* "Server running, periodic sync started.") (let loop () - ;;(BB> "watchdog loop. pid="(current-process-id)" this-wd-num="this-wd-num" *time-to-exit*="*time-to-exit*) ;; sync for filesystem local db writes ;; (mutex-lock! *db-multi-sync-mutex*) (let* ((need-sync (>= *db-last-access* *db-last-sync*)) ;; no sync since last write (sync-in-progress *db-sync-in-progress*) (should-sync (and (not *time-to-exit*) (> (- (current-seconds) *db-last-sync*) 5))) ;; sync every five seconds minimum + (start-time (current-seconds)) + (mt-mod-time (file-modification-time mtpath)) + (recently-synced (> (- start-time mt-mod-time) 4)) (will-sync (and (or need-sync should-sync) - (not sync-in-progress))) - (start-time (current-seconds))) + (not sync-in-progress) + (not recently-synced)))) + ;; (if recently-synced (debug:print-info 0 *default-log-port* "Skipping sync due to recently-synced flag=" recently-synced)) ;; (debug:print-info 0 *default-log-port* "need-sync: " need-sync " sync-in-progress: " sync-in-progress " should-sync: " should-sync " will-sync: " will-sync) (if will-sync (set! *db-sync-in-progress* #t)) (mutex-unlock! *db-multi-sync-mutex*) (if will-sync (let ((res (common:sync-to-megatest.db dbstruct))) ;; did we sync any data? If so need to set the db touched flag to keep the server alive Index: dashboard.scm ================================================================== --- dashboard.scm +++ dashboard.scm @@ -99,10 +99,14 @@ (if (args:get-arg "-h") (begin (print help) (exit))) +(if (not (common:on-homehost?)) + (begin + (debug:print 0 *default-log-port* "ERROR: Current policy requires running dashboard on homehost: " (common:get-homehost)))) + ;; TODO: Move this inside (main) ;; (if (not (launch:setup)) (begin (print "Failed to find megatest.config, exiting") Index: db.scm ================================================================== --- db.scm +++ db.scm @@ -13,14 +13,14 @@ ;; Database access ;;====================================================================== ;; dbstruct vector containing all the relevant dbs like main.db, megatest.db, run.db etc -(use (srfi 18) extras tcp stack) ;; RADT => use of require-extension? -(use sqlite3 srfi-1 posix regex regex-case srfi-69 csv-xml s11n md5 message-digest base64 format dot-locking z3 typed-records sql-null) +(use (srfi 18) extras tcp stack) +(use sqlite3 srfi-1 posix regex regex-case srfi-69 csv-xml s11n md5 message-digest base64 format dot-locking z3 typed-records sql-null matchable) (import (prefix sqlite3 sqlite3:)) -(import (prefix base64 base64:)) ;; RADT => prefix?? +(import (prefix base64 base64:)) (include "/nfs/site/disks/icf_fdk_cw_gwa002/srehman/fossil/dbi/dbi.scm") (import (prefix dbi dbi:)) (declare (unit db)) (declare (uses common)) @@ -146,11 +146,11 @@ (use-mutex (> *api-process-request-count* 25))) (if (and use-mutex (common:low-noise-print 120 "over-50-parallel-api-requests")) (debug:print-info 0 *default-log-port* *api-process-request-count* " parallel api requests being processed in process " (current-process-id) ", throttling access")) (if (common:low-noise-print 600 (conc "parallel-api-requests" *max-api-process-requests*)) - (debug:print-info 0 *default-log-port* "Parallel api request count: " *api-process-request-count* " max parallel requests: " *max-api-process-requests*)) + (debug:print-info 2 *default-log-port* "Parallel api request count: " *api-process-request-count* " max parallel requests: " *max-api-process-requests*)) (handle-exceptions exn (begin (print-call-chain (current-error-port)) (debug:print-error 0 *default-log-port* "sqlite3 issue in db:with-db, dbstruct=" dbstruct ", run-id=" run-id ", proc=" proc ", params=" params " error: " ((condition-property-accessor 'exn 'message) exn)) @@ -487,13 +487,14 @@ '("pass_count" "INTEGER")) (list "test_meta" '("id" "INTEGER" 'key) '("testname" "TEXT") + '("author" "TEXT") '("owner" "TEXT") '("description" "TEXT") - '("reviewed" "INTEGER") + '("reviewed" "TEXT") '("iterated" "TEXT") '("avg_runtime" "REAL") '("avg_disk" "REAL") '("tags" "TEXT") '("jobgroup" "TEXT"))))) @@ -563,21 +564,21 @@ ) ;; test read/write access to the database (let ((db (dbi:open 'sqlite3 (cons (cons ('dbname dbpath) '()))))) (cond ((equal? fname "megatest.db") - (sqlite3:executeute db "DELETE FROM tests WHERE state='DELETED';")) + (sqlite3:execute db "DELETE FROM tests WHERE state='DELETED';")) ((equal? fname "main.db") - (sqlite3:executeute db "DELETE FROM runs WHERE state='deleted';")) + (sqlite3:execute db "DELETE FROM runs WHERE state='deleted';")) ((string-match "\\d.db" fname) - (sqlite3:executeute db "UPDATE tests SET state='DELETED' WHERE state='DELETED';")) + (sqlite3:execute db "UPDATE tests SET state='DELETED' WHERE state='DELETED';")) ((equal? fname "monitor.db") - (sqlite3:executeute "DELETE FROM servers WHERE state LIKE 'defunct%';")) + (sqlite3:execute "DELETE FROM servers WHERE state LIKE 'defunct%';")) (else - (sqlite3:executeute db "vacuum;"))) + (sqlite3:execute db "vacuum;"))) - (dbi:close db) + (sqlite3:finalize! db) #t)))))) ;; tbls is ( ("tablename" ( "field1" [#f|proc1] ) ( "field2" [#f|proc2] ) .... ) ) ;; db's are dbdat's ;; @@ -910,20 +911,21 @@ (let* ((mtdb (dbr:dbstruct-mtdb dbstruct)) (tmpdb (db:get-db dbstruct)) (refndb (dbr:dbstruct-refndb dbstruct)) (slave-dbs (dbr:dbstruct-slave-dbs dbstruct)) (allow-cleanup #t) ;; (if run-ids #f #t)) - (tdbdat (tasks:open-db)) - (servers (tasks:get-all-servers (db:delay-if-busy tdbdat))) + ;; (tdbdat (tasks:open-db)) + (servers (server:get-list *toppath*)) ;; (tasks:get-all-servers (db:delay-if-busy tdbdat))) (data-synced 0)) ;; count of changed records (I hope) ;; kill servers (if (member 'killservers options) (for-each (lambda (server) - (tasks:server-delete-record (db:delay-if-busy tdbdat) (vector-ref server 0) "dbmigration") - (tasks:kill-server (vector-ref server 2)(vector-ref server 1))) + (match-let (((mod-time host port start-time pid) server)) + (if (and host pid) + (tasks:kill-server host pid)))) servers)) ;; clear out junk records ;; (if (member 'dejunk options) @@ -2157,13 +2159,16 @@ db "SELECT id FROM runs WHERE state != 'deleted' ORDER BY event_time DESC;") (reverse run-ids))))) ;; get some basic run stats +;; +;; data structure: ;; ;; ( (runname (( state count ) ... )) -;; ( ... +;; ( ... +;; (define (db:get-run-stats dbstruct) (let* ((totals (make-hash-table)) (curr (make-hash-table)) (res '()) (runs-info '())) Index: dcommon.scm ================================================================== --- dcommon.scm +++ dcommon.scm @@ -12,11 +12,11 @@ (use format) (require-library iup) (import (prefix iup iup:)) (use canvas-draw) (import canvas-draw-iup) -(use regex typed-records) +(use regex typed-records matchable) (declare (unit dcommon)) (declare (uses megatest-version)) (declare (uses gutils)) @@ -620,11 +620,12 @@ #:numlin-visible 5 )) (colnames (list "Id" "MTver" "Pid" "Host" "Interface:OutPort" "RunTime" "State" "RunId")) (updater (lambda () (if (dashboard:monitor-changed? commondat tabdat) - (let ((servers (tasks:get-all-servers (db:delay-if-busy tdbdat)))) + (let ((servers (server:get-list *toppath* limit: 10))) + ;; (tasks:get-all-servers (db:delay-if-busy tdbdat)))) (iup:attribute-set! servers-matrix "NUMLIN" (length servers)) ;; (set! colnum 0) ;; (for-each (lambda (colname) ;; ;; (print "colnum: " colnum " colname: " colname) ;; (iup:attribute-set! servers-matrix (conc "0:" colnum) colname) @@ -632,36 +633,40 @@ ;; colnames) (set! rownum 1) (for-each (lambda (server) (set! colnum 0) - (let* ((vals (list (vector-ref server 0) ;; Id - (vector-ref server 9) ;; MT-Ver - (vector-ref server 1) ;; Pid - (vector-ref server 2) ;; Hostname - (conc (vector-ref server 3) ":" (vector-ref server 4)) ;; IP:Port - (seconds->hr-min-sec (- (current-seconds)(vector-ref server 6))) - ;; (vector-ref server 5) ;; Pubport - ;; (vector-ref server 10) ;; Last beat - ;; (vector-ref server 6) ;; Start time - ;; (vector-ref server 7) ;; Priority - ;; (vector-ref server 8) ;; State - (vector-ref server 8) ;; State - (vector-ref server 12) ;; RunId - ))) - (for-each (lambda (val) - (let* ((row-col (conc rownum ":" colnum)) - (curr-val (iup:attribute servers-matrix row-col))) - (if (not (equal? (conc val) curr-val)) - (begin - (iup:attribute-set! servers-matrix row-col val) - (iup:attribute-set! servers-matrix "FITTOTEXT" (conc "C" colnum)))) - (set! colnum (+ 1 colnum)))) - vals) - (set! rownum (+ rownum 1))) - (iup:attribute-set! servers-matrix "REDRAW" "ALL")) - servers)))))) + (match-let (((mod-time host port start-time pid) + server)) + (let* ((uptime (- (current-seconds) mod-time)) + (runtime (if start-time + (- mod-time start-time) + 0)) + (vals (list "-" ;; (vector-ref server 0) ;; Id + "-" ;; (vector-ref server 9) ;; MT-Ver + pid ;; (vector-ref server 1) ;; Pid + host ;; (vector-ref server 2) ;; Hostname + (conc host ":" port) ;; (conc (vector-ref server 3) ":" (vector-ref server 4)) ;; IP:Port + (seconds->hr-min-sec runtime) ;; (- (current-seconds) start-time)) ;; (vector-ref server 6))) + (cond + ((< uptime 5) "alive") + ((< uptime 16) "probably alive");; less than 15 seconds since mod, call it alive (vector-ref server 8) ;; State + (else "dead")) + "-" ;; (vector-ref server 12) ;; RunId + ))) + (for-each (lambda (val) + (let* ((row-col (conc rownum ":" colnum)) + (curr-val (iup:attribute servers-matrix row-col))) + (if (not (equal? (conc val) curr-val)) + (begin + (iup:attribute-set! servers-matrix row-col val) + (iup:attribute-set! servers-matrix "FITTOTEXT" (conc "C" colnum)))) + (set! colnum (+ 1 colnum)))) + vals) + (set! rownum (+ rownum 1))) + (iup:attribute-set! servers-matrix "REDRAW" "ALL"))) + (sort servers (lambda (a b)(> (car a)(car b)))))))))) (set! colnum 0) (for-each (lambda (colname) (iup:attribute-set! servers-matrix (conc "0:" colnum) colname) (iup:attribute-set! servers-matrix "FITTOTEXT" (conc "C" colnum)) (set! colnum (+ colnum 1))) ADDED diff-report.scm Index: diff-report.scm ================================================================== --- /dev/null +++ diff-report.scm @@ -0,0 +1,408 @@ + +(declare (unit diff-report)) +(declare (uses common)) +(declare (uses rmt)) + +(include "common_records.scm") +(use matchable) +(use fmt) +(use ducttape-lib) +(define css "") + +(define (diff:tests-mindat->hash tests-mindat) + (let* ((res (make-hash-table))) + (for-each + (lambda (item) + (let* ((test-name+item-path (cons (list-ref item 0) (list-ref item 1))) + (value (list-ref item 2))) + (hash-table-set! res test-name+item-path value))) + tests-mindat) + res)) + +;; return 1 if status1 is better +;; return 0 if status1 and 2 are equally good +;; return -1 if status2 is better +(define (diff:status-compare3 status1 status2) + (let* + ((status-goodness-ranking (list "PASS" "WARN" "WAIVED" "SKIP" "FAIL" "ABORT" #f)) + (mem1 (member status1 status-goodness-ranking)) + (mem2 (member status2 status-goodness-ranking)) + ) + (cond + ((and (not mem1) (not mem2)) 0) + ((not mem1) -1) + ((not mem2) 1) + ((= (length mem1) (length mem2)) 0) + ((> (length mem1) (length mem2)) 1) + (else -1)))) + + +(define (diff:xor-tests-mindat src-tests-mindat dest-tests-mindat #!key (hide-clean #f) (consistent-fail-not-clean #f)) + (let* ((src-hash (diff:tests-mindat->hash src-tests-mindat)) + (dest-hash (diff:tests-mindat->hash dest-tests-mindat)) + (all-keys + (reverse (sort + (delete-duplicates + (append (hash-table-keys src-hash) (hash-table-keys dest-hash))) + + (lambda (a b) + (cond + ((< 0 (string-compare3 (car a) (car b))) #t) + ((> 0 (string-compare3 (car a) (car b))) #f) + ((< 0 (string-compare3 (cdr a) (cdr b))) #t) + (else #f))) + + )))) + (let ((res + (map ;; TODO: rename xor to delta globally in dcommon and dashboard + (lambda (key) + (let* ((test-name (car key)) + (item-path (cdr key)) + + (dest-value (hash-table-ref/default dest-hash key (list 0 "NULL" "NULL"))) ;; (list test-id state status) + (dest-test-id (list-ref dest-value 0)) + (dest-state (list-ref dest-value 1)) + (dest-status (list-ref dest-value 2)) + + (src-value (hash-table-ref/default src-hash key (list 0 "NULL" "NULL"))) ;; (list test-id state status) + (src-test-id (list-ref src-value 0)) + (src-state (list-ref src-value 1)) + (src-status (list-ref src-value 2)) + + (incomplete-statuses '("DELETED" "INCOMPLETE" "STUCK/DEAD" "N/A")) ;; if any of these statuses apply, treat test as incomplete + + (dest-complete + (and dest-value dest-state dest-status + (equal? dest-state "COMPLETED") + (not (member dest-status incomplete-statuses)))) + (src-complete + (and src-value src-state src-status + (equal? src-state "COMPLETED") + (not (member src-status incomplete-statuses)))) + (status-compare-result (diff:status-compare3 src-status dest-status)) + (xor-new-item + (cond + ;; complete, for this case means: state=compelte AND status not in ( deleted uncomplete stuck/dead n/a ) + ;; neither complete -> bad + + ;; src !complete, dest complete -> better + ((and (not dest-complete) (not src-complete)) + (list dest-test-id "BOTH-BAD" "BOTH-INCOMPLETE") src-value dest-value) + ((not dest-complete) + (list src-test-id "NOT-IN-DEST" "DEST-INCOMPLETE") src-value dest-value) + ((not src-complete) + (list dest-test-id "NOT-IN-SRC" "SRC-INCOMPLETE") src-value dest-value) + ((and + (equal? src-state dest-state) + (equal? src-status dest-status)) + (if (and consistent-fail-not-clean (not (member dest-status '("PASS" "SKIP" "WAIVED" "WARN")))) + (list dest-test-id (conc "BOTH-BAD") (conc "CLEAN-" dest-status) src-value dest-value) + (list dest-test-id (conc "CLEAN") (conc "CLEAN-" dest-status) src-value dest-value))) + ;; better or worse: pass > warn > waived > skip > fail > abort + ;; pass > warn > waived > skip > fail > abort + + ((= 1 status-compare-result) ;; src is better, dest is worse + (list dest-test-id "WORSE" (conc src-status "->" dest-status) src-value dest-value)) + (else + (list dest-test-id "BETTER" (conc src-status "->" dest-status) src-value dest-value))))) + (list test-name item-path xor-new-item))) + all-keys))) + + (if hide-clean + (filter + (lambda (item) + (not + (equal? + "CLEAN" + (list-ref (list-ref item 2) 1)))) + res) + res)))) + +(define (diff:run-name->run-id run-name) + (if (number? run-name) + run-name + (let* ((qry-res (rmt:get-runs run-name 1 0 '()))) + (if (eq? 2 (vector-length qry-res)) + (vector-ref (car (vector-ref qry-res 1)) 1) + #f)))) + +(define (diff:target+run-name->run-id target run-name) + (let* ((keys (rmt:get-keys)) + (target-parts (if target (string-split target "/") (map (lambda (x) "%") keys)))) + (if (not (eq? (length keys) (length keys))) + (begin + (print "Error: Target ("target") item count does not match fields count target tokens="target-parts" fields="keys) + #f) + (let* ((target-map (zip keys target-parts)) + (qry-res (rmt:get-runs run-name 1 0 target-map))) + + (if (eq? 2 (vector-length qry-res)) + (let ((first-ent (vector-ref qry-res 1))) + (if (> (length first-ent) 0) + (vector-ref (car first-ent) 1) + #f)) + #f))))) + +(define (diff:run-id->tests-mindat run-id #!key (testpatt "%/%")) + (let* ((states '()) + (statuses '()) + (offset #f) + (limit #f) + (not-in #t) + (sort-by #f) + (sort-order #f) + (qryvals "id,testname,item_path,state,status") + (qryvals "id,testname,item_path,state,status") + (last-update 0) + (mode #f) + ) + (map + ;; (lambda (row) + ;; (match row + ;; ((#(id test-name item-path state status) + ;; (list test-name item-path (list id state status)))) + ;; (else #f))) + (lambda (row) + (let* ((id (vector-ref row 0)) + (test-name (vector-ref row 1)) + (item-path (vector-ref row 2)) + (state (vector-ref row 3)) + (status (vector-ref row 4))) + (list test-name item-path (list id state status)))) + + (rmt:get-tests-for-run run-id + testpatt states statuses + offset limit + not-in sort-by sort-order + qryvals + last-update + mode)))) + + +(define (diff:diff-runs src-run-id dest-run-id) + (let* ((src-tests-mindat (diff:run-id->tests-mindat src-run-id)) + (dest-tests-mindat (diff:run-id->tests-mindat dest-run-id))) + (diff:xor-tests-mindat src-tests-mindat dest-tests-mindat consistent-fail-not-clean: #t))) + + +(define (diff:rundiff-find-by-state run-diff state) + (filter + (lambda (x) + (equal? (list-ref (caddr x) 1) state)) + run-diff)) + +(define (diff:rundiff-clean-breakdown run-diff) + (map + (lambda (run-diff-item) + (match run-diff-item + ((test-name item-path (junk-id diff-state diff-status (src-test-id src-state src-status) (dest-test-id dest-state dest-status))) + (list test-name item-path "CLEAN" src-status)) + (else ""))) + (diff:rundiff-find-by-state run-diff "CLEAN"))) + +(define (diff:summarize-run-diff run-diff) + + (let* ((diff-states (list "CLEAN" "BETTER" "WORSE" "BOTH-BAD" "NOT-IN-DEST" "NOT-IN-SRC" ))) + (map + (lambda (state) + (list state + (length (diff:rundiff-find-by-state run-diff state)))) + diff-states))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Presentation code below, business logic above ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(define (diff:stml->string in-stml) + (with-output-to-string + (lambda () + (s:output-new + (current-output-port) + in-stml)))) + +(define (diff:state-status->bgcolor state status) + (match (list state status) + (("CLEAN" _) "#88ff88") + (("BETTER" _) "#33ff33") + (("WORSE" _) "#ff3333") + (("BOTH-BAD" _) "#ff3333") + ((_ "WARN") "#ffff88") + ((_ "FAIL") "#ff8888") + ((_ "ABORT") "#ff0000") + ((_ "PASS") "#88ff88") + ((_ "SKIP") "#ffff00") + (else "#ffffff"))) + +(define (diff:test-state-status->diff-report-cell state status) + (s:td 'bgcolor (diff:state-status->bgcolor state status) status)) + +(define (diff:diff-state-status->diff-report-cell state status) + (s:td state 'bgcolor (diff:state-status->bgcolor state status))) + + +(define (diff:megatest-html-logo) + + "
+___  ___                 _            _
+|  \\/  | ___  __ _  __ _| |_ ___  ___| |_
+| |\\/| |/ _ \\/ _` |/ _` | __/ _ \\/ __| __|
+| |  | |  __/ (_| | (_| | ||  __/\\__ \\ |_
+|_|  |_|\\___|\\__, |\\__,_|\\__\\___||___/\\__|
+             |___/
+
") + +(define (diff:megatest-html-diff-logo) + "
+___  ___                 _            _
+|  \\/  | ___  __ _  __ _| |_ ___  ___| |_  |  _ \\(_)/ _|/ _|
+| |\\/| |/ _ \\/ _` |/ _` | __/ _ \\/ __| __| | | | | | |_| |_
+| |  | |  __/ (_| | (_| | ||  __/\\__ \\ |_  | |_| | |  _|  _|
+|_|  |_|\\___|\\__, |\\__,_|\\__\\___||___/\\__| |____/|_|_| |_|
+             |___/
+
") + + +(define (diff:run-id->target+run-name+starttime run-id) + (let* ((target (rmt:get-target run-id)) + (runinfo (rmt:get-run-info run-id)) ; vector of header (list) and result (vector) + (info-hash (alist->hash-table + (map (lambda (x) (cons (car x) (cadr x))) ; make it a useful hash + (zip (vector-ref runinfo 0) (vector->list (vector-ref runinfo 1)))))) + (run-name (hash-table-ref/default info-hash "runname" "N/A")) + (start-time (hash-table-ref/default info-hash "event_time" 0))) + (list target run-name start-time))) + +(define (diff:deliver-diff-report src-run-id dest-run-id + #!key + (html-output-file #f) + (email-subject-prefix "[MEGATEST DIFF]") + (email-recipients-list '()) ) + (let* ((src-info (diff:run-id->target+run-name+starttime src-run-id)) + (src-target (car src-info)) + (src-run-name (cadr src-info)) + (src-start (conc (seconds->string (caddr src-info)) " " (local-timezone-abbreviation))) + (dest-info (diff:run-id->target+run-name+starttime dest-run-id)) + (dest-target (car dest-info)) + (dest-run-name (cadr dest-info)) + (dest-start (conc (seconds->string (caddr dest-info)) " " (local-timezone-abbreviation))) + + + (run-diff (diff:diff-runs src-run-id dest-run-id )) + (test-count (length run-diff)) + (summary-table + (apply s:table 'cellspacing "0" 'border "1" + (s:tr + (s:th "Diff type") + (s:th "% share") + (s:th "Count")) + + (map + (lambda (state-count) + (s:tr + (diff:diff-state-status->diff-report-cell (car state-count) #f) + (s:td 'align "right" (fmt #f + (decimal-align 3 + (fix 2 + (num/fit 6 + (* 100 (/ (cadr state-count) test-count))))))) + (s:td 'align "right" (cadr state-count)))) + (diff:summarize-run-diff run-diff)))) + (meta-table + (s:table 'cellspacing "0" 'border "1" + + (s:tr + (s:td 'colspan "2" + (s:table 'cellspacing "0" 'border "1" + (s:tr + (s:th 'align "LEFT" "") (s:th "SOURCE RUN") (s:th "DESTINATION RUN")) + (s:tr + (s:th 'align "LEFT" "Started") (s:td src-start) (s:td dest-start)) + (s:tr + (s:th 'align "LEFT" "TARGET") (s:td src-target) (s:td dest-target)) + (s:tr + (s:th 'align "LEFT" "RUN NAME") (s:td src-run-name) (s:td dest-run-name))))))) + + (main-table + (apply s:table 'cellspacing "0" 'border "1" + (s:tr + (s:th "Test name") + (s:th "Item Path") + (s:th (conc "SOURCE")) + (s:th (conc "DEST")) + (s:th "Diff")) + (map + (lambda (run-diff-item) + (match run-diff-item + ((test-name item-path (junk-id diff-state diff-status (src-test-id src-state src-status) (dest-test-id dest-state dest-status))) + (s:tr + (s:td test-name) + (s:td item-path) + (diff:test-state-status->diff-report-cell src-state src-status) + (diff:test-state-status->diff-report-cell dest-state dest-status) + (diff:diff-state-status->diff-report-cell diff-state diff-status))) + (else ""))) + (filter (lambda (run-diff-item) + (match run-diff-item + ((test-name item-path (junk-id diff-state diff-status (src-test-id src-state src-status) (dest-test-id dest-state dest-status))) + (not (equal? diff-state "CLEAN"))) + (else #f))) + run-diff)))) + (email-subject (conc email-subject-prefix " " src-target "/" src-run-name" vs. "dest-target"/"dest-run-name)) + (html-body (diff:stml->string (s:body + (diff:megatest-html-diff-logo) + (s:h2 "Summary") + (s:table 'border "0" + (s:tr + (s:td "Diff calculated at") + (s:td (conc (seconds->string) " " (local-timezone-abbreviation)))) + (s:tr + (s:td "MT_RUN_AREA_HOME" ) (s:td *toppath*)) + (s:tr 'valign "TOP" + (s:td summary-table) + (s:td meta-table))) + (s:h2 "Diffs + consistently failing tests") + main-table))) + + ) + (if html-output-file + (with-output-to-file html-output-file (lambda () (print html-body)))) + (when (and email-recipients-list (> (length email-recipients-list) 0)) + (sendmail (string-join email-recipients-list ",") email-subject html-body use_html: #t)) + html-body)) + + + + + +;; (let* ((src-run-name "all57") +;; (dest-run-name "all60") +;; (src-run-id (diff:run-name->run-id src-run-name)) +;; (dest-run-id (diff:run-name->run-id dest-run-name)) +;; (to-list (list "bjbarcla"))) +;; (diff:deliver-diff-report src-run-id dest-run-id email-recipients-list: to-list html-output-file: "/tmp/bjbarcla/zippy.html") +;; ) + +(define (do-diff-report src-target src-runname dest-target dest-runname html-file to-list-raw) + (let* (;;(src-target "nope%") + ;;(src-runname "all57") + ;;(dest-target "%") + ;;(dest-runname "all60") + (src-run-id (diff:target+run-name->run-id src-target src-runname)) + (dest-run-id (diff:target+run-name->run-id dest-target dest-runname)) + ;(html-file "/tmp/bjbarcla/zippy.html") + (to-list (if (string? to-list-raw) (string-split to-list-raw ",:") #f)) + ) + + (cond + ((not src-run-id) + (print "No match for source target/runname="src-target"/"src-runname) + (print "Cannot proceed.") + #f) + ((not dest-run-id) + (print "No match for source target/runname="dest-target"/"dest-runname) + (print "Cannot proceed.") + #f) + (else + (diff:deliver-diff-report src-run-id dest-run-id email-recipients-list: to-list html-output-file: html-file))))) + + ADDED ducttape/Makefile Index: ducttape/Makefile ================================================================== --- /dev/null +++ ducttape/Makefile @@ -0,0 +1,33 @@ +help: + @echo "" + @echo "make targets:" + @echo "=============" + @echo "install - build and install general_lib egg as icfadm" + @echo "test - run unit tests on ducttape-lib.scm (tests code, not egg)" + @echo "eggs-info - show chicken-install commands to get eggs upon which ducttape-lib depends" + @echo "test_example - compile an example scm against installed general_lib egg" + @echo "clean - remove binaries and other build artifacts" + @echo "" + +clean: + rm -f *.so *.import.scm test_ducttape test_example foo *.c *.o + +install: + chicken-install + +test: + chicken-install -no-install + csc test_ducttape.scm + + ./test_ducttape + rm -f foo + +test_example: + @csc test_example.scm + @./test_example + @rm test_example + +eggs-info: + @echo chicken-install ansi-escape-sequences + @echo chicken-install slice + @echo chicken-install rfc3339 ADDED ducttape/README Index: ducttape/README ================================================================== --- /dev/null +++ ducttape/README @@ -0,0 +1,8 @@ +This directory holds the "ducttape" chicken scheme egg used by megatest. + +Run "make test" to ensure this egg works on your system. + +Run "make install" as your admin user with chicken on your $PATH to install this egg. + + + ADDED ducttape/ducttape-lib.meta Index: ducttape/ducttape-lib.meta ================================================================== --- /dev/null +++ ducttape/ducttape-lib.meta @@ -0,0 +1,13 @@ +;;; ducttape-lib.meta -*- Hen -*- + +((egg "ducttape-lib.egg") + (synopsis "Miscellaneous tool and standard print routines.") + (category env) + (author "Brandon Barclay") + (doc-from-wiki) + (license "GPL-2") + ;; srfi-69, posix, srfi-18 + (depends regex) + (test-depends test) + ; suspicious - (files "ducttape-lib") + ) ADDED ducttape/ducttape-lib.scm Index: ducttape/ducttape-lib.scm ================================================================== --- /dev/null +++ ducttape/ducttape-lib.scm @@ -0,0 +1,747 @@ +(module ducttape-lib + ( + runs-ok + ducttape-debug-level + ducttape-debug-regex-filter + ducttape-silent-mode + ducttape-quiet-mode + ducttape-log-file + ducttape-color-mode + iputs-preamble + script-name + idbg + ierr + iwarn + inote + iputs + re-match? + ; launch-repl + keyword-skim + skim-cmdline-opts-noarg-by-regex + skim-cmdline-opts-withargs-by-regex + concat-lists + ducttape-process-command-line + ducttape-append-logfile + ducttape-activate-logfile + isys + do-or-die + counter-maker + dir-is-writable? + mktemp + get-tmpdir + sendmail + find-exe + + zeropad + string-leftpad + string-rightpad + seconds->isodate + seconds->wwdate + seconds->wwdate-values + isodate->seconds + isodate->wwdate + wwdate->seconds + wwdate->isodate + current-wwdate + current-isodate + + ) + + (import scheme chicken extras ports data-structures ) + (use posix regex ansi-escape-sequences test srfi-1 irregex slice srfi-13 rfc3339) + ;;scsh-process ;; dropping scsh-process, it was clobbering posix's process and process* + (use directory-utils uuid-lib filepath srfi-19 ) ; linenoise + + (include "mimetypes.scm") ; provides ext->mimetype + (include "workweekdate.scm") + (define ducttape-lib-version 1.00) + (define (toplevel-command sym proc) (lambda () #f)) +;;;; utility procedures + + ;; begin credit: megatest's process.scm + (define (port->list fh ) + (if (eof-object? fh) #f + (let loop ((curr (read-line fh)) + (result '())) + (if (not (eof-object? curr)) + (loop (read-line fh) + (append result (list curr))) + result)))) + + (define (conservative-read port) + (let loop ((res "")) + (if (not (eof-object? (peek-char port))) + (loop (conc res (read-char port))) + res))) + ;; end credit: megatest's process.scm + + (define (counter-maker) + (let ((acc 0)) + (lambda ( #!optional (increment 1) ) + (set! acc (+ increment acc)) + acc))) + + (define (port->string port #!optional ) ; todo - add newline + (let ((linelist (port->list port))) + (if linelist + (string-join linelist "\n") + ""))) + + + (define (outport->foreach outport foreach-thunk) + (let loop ((line (foreach-thunk))) + (if line + (begin + (write-line line outport) + (loop (foreach-thunk)) + ) + (begin + ;;http://bugs.call-cc.org/ticket/766 + ;;close-[input|output]-port implicitly calling process-wait on process pipe ports. This leads to errors like + ;;Error: (process-wait) waiting for child process failed - No child processes: 10872 + (close-output-port outport) + #f)))) + + ;; weird - alist-ref arg order changes signature csc vs. csi... explitly defining. + (define (my-alist-ref key alist) + (let ((res (assoc key alist))) + (if res (cdr res) #f))) + + (define (keyword-skim-alist args alist) + (let loop ((result-alist '()) (result-args args) (rest-alist alist)) + (cond + ((null? rest-alist) (values result-alist result-args)) + (else + (let ((keyword (caar rest-alist)) + (defval (cdar rest-alist))) + (let-values (((kwval result-args2) + (keyword-skim + keyword + defval + result-args))) + (loop + (cons (cons keyword kwval) result-alist) + result-args2 + (cdr rest-alist)))))))) + + (define (isys command . rest-args) + (let-values + (((opt-alist args) + (keyword-skim-alist + rest-args + '( ( foreach-stdout-thunk: . #f ) + ( foreach-stdin-thunk: . #f ) + ( stdin-proc: . #f ) ) ))) + (let* ((foreach-stdout-thunk + (my-alist-ref foreach-stdout-thunk: opt-alist)) + (foreach-stdin-thunk + (my-alist-ref foreach-stdin-thunk: opt-alist)) + (stdin-proc + (if foreach-stdin-thunk + (lambda (port) + (outport->foreach port foreach-stdin-thunk)) + (my-alist-ref stdin-proc: opt-alist)))) + + ;; TODO: support command is list. + + (let-values (((stdout stdin pid stderr) + (if (null? args) + (process* command) + (process* command args)))) + + ;(if foreach-stdin-thunk + ; (set! stdin-proc + ; (lambda (port) + ; (outport->foreach port foreach-stdin-thunk)))) + + (if stdin-proc + (stdin-proc stdin)) + + (let ((stdout-res + (if foreach-stdout-thunk ;; don't accumulate stdout if we have a thunk; probably doing this because stdout is BIG so lets not waste memory + (begin + (port-for-each foreach-stdout-thunk (lambda () (read-line stdout))) + "foreach-stdout-thunk ate stdout" + ) + (if stdin-proc + "foreach-stdin-thunk/stdin-proc blocks stdout" + (port->string stdout)))) + (stderr-res + (if stdin-proc + "foreach-stdin-thunk/stdin-proc blocks stdout" + (port->string stderr)))) + + ;; if we've used a stdin-proc, we've closed stdin port, which unfortunately causes a wait-pid internally, causing stdout and stderr ports to auto-close. don't close them again. (so sad - we lost stdout and stderr contents when we write to stdin) + ;; see - http://bugs.call-cc.org/ticket/766 + (if (not stdin-proc) + (close-input-port stdout) + (close-input-port stderr)) + + (let-values (((anotherpid normalexit? exitstatus) (process-wait pid))) + (values exitstatus stdout-res stderr-res))))))) + + (define (do-or-die command #!key nodie (foreach-stdout #f) (stdin-proc #f)) + (let-values (((exit-code stdout-str stderr-str) (isys command foreach-stdout-thunk: foreach-stdout stdin-proc: stdin-proc ))) + (if (equal? 0 exit-code) + stdout-str + (begin + (ierr (conc "Command > " command " " "< failed with " exit-code " because: \n" stderr-str) ) + (if nodie #f (exit exit-code)))))) + + + ;; runs-ok: evaluate expression while suppressing exceptions. + ; on caught exception, returns #f + ; otherwise, returns expression value + (define (runs-ok thunk) + (handle-exceptions exn #f (begin (thunk) #t))) + + ;; concat-lists: result list = lista + listb + (define (concat-lists lista listb) ;; ok, I just reimplemented append... + (foldr cons listb lista)) + + +;;; setup general_lib env var parameters + + ;; show warning/note/error/debug prefixes using ansi colors + (define ducttape-color-mode + (make-parameter (get-environment-variable "DUCTTAPE_COLORIZE"))) + + ;; if defined, has number value. if number value > 0, show debug messages + ;; value should be decremented in subshells -- idea is raising debug level will show debug messages deeper and deeper in process call stack + (define ducttape-debug-level + (make-parameter + (let ( (raw-debug-level (get-environment-variable "DUCTTAPE_DEBUG_LEVEL")) ) + (if raw-debug-level + (let ((num-debug-level (runs-ok (string->number raw-debug-level)))) + (if (integer? num-debug-level) + (begin + (let ((new-num-debug-level (- num-debug-level 1))) + (if (> new-num-debug-level 0) ;; decrement + (setenv "DUCTTAPE_DEBUG_LEVEL" (number->string new-num-debug-level)) + (unsetenv "DUCTTAPE_DEBUG_LEVEL"))) + num-debug-level) ; it was set and > 0, mode is value + (begin + (unsetenv "DUCTTAPE_DEBUG_LEVEL") ;; value was invalid, unset it + #f))) ; value was invalid, mode is f + #f)))) ; var not set, mode is f + + + (define ducttape-debug-mode (if (ducttape-debug-level) #t #f)) + + ;; ducttape-debug-regex-filter suppresses non-matching debug messages + (define ducttape-debug-regex-filter + (make-parameter + (let ((raw-debug-pattern (get-environment-variable "DUCTTAPE_DEBUG_PATTERN"))) + (if raw-debug-pattern + raw-debug-pattern + ".")))) + + ;; silent mode suppresses Note and Warning type messages + (define ducttape-silent-mode + (make-parameter (get-environment-variable "DUCTTAPE_SILENT_MODE"))) + + ;; quiet mode suppresses Note type messages + (define ducttape-quiet-mode + (make-parameter (get-environment-variable "DUCTTAPE_QUIET_MODE"))) + + ;; if log file is defined, warning/note/error/debug messages are appended + ;; to named logfile. + (define ducttape-log-file + (make-parameter (get-environment-variable "DUCTTAPE_LOG_FILE"))) + + + + + + +;;; standard messages printing implementation + + ; get the name of the current script/binary being run + (define (script-name) + (car (reverse (string-split (car (argv)) "/")))) + + (define (ducttape-timestamp) + (rfc3339->string (time->rfc3339 (seconds->local-time)))) + + + (define (iputs-preamble msg-type #!optional (suppress-color #f)) + (let ((do-color (and + (not suppress-color) + (ducttape-color-mode) + (terminal-port? (current-error-port))))) + (case msg-type + ((note) + (if do-color + (set-text (list 'fg-green 'bg-black 'bold) "Note:") + "Note:" + )) + ((warn) + (if do-color + (set-text (list 'fg-yellow 'bg-black 'bold) "Warning:") + "Warning:" + )) + ((err) + (if do-color + (set-text (list 'fg-red 'bg-black 'bold) "Error:") + "Error:" + )) + ((dbg) + (if do-color + (set-text (list 'fg-blue 'bg-magenta) "Debug:") + "Debug:" + ))))) + + (define (ducttape-append-logfile msg-type message #!optional (suppress-preamble #f)) + (let + ((txt + (string-join + (list + (ducttape-timestamp) + (script-name) + (if suppress-preamble + message + (string-join (list (iputs-preamble msg-type #t) message) " "))) + " | "))) + + (if (ducttape-log-file) + (runs-ok + (call-with-output-file (ducttape-log-file) + (lambda (output-port) + (format output-port "~A ~%" txt) + ) + #:append)) + #t))) + + (define (ducttape-activate-logfile #!optional (logfile #f)) + ;; from python ducttape-lib.py + ; message = "START - pid=%d ppid=%d argv=(%s) pwd=%s user=%s host=%s"%(pid,ppid," ".join("'"+x+"'" for x in sys.argv),os.environ['PWD'],os.getenv('USER','nouser'),os.getenv('HOST','nohost') ) + (let ((pid (number->string (current-process-id))) + (ppid (number->string (parent-process-id))) + (argv + (string-join + (map + (lambda (x) + (string-join (list "\"" x "\"") "" )) + (argv)) + " ")) + (pwd (or (get-environment-variable "PWD") "nopwd")) + (user (or (get-environment-variable "USER") "nouser")) + (host (or (get-environment-variable "HOST") "nohost"))) + (if logfile + (begin + (ducttape-log-file logfile) + (setenv "DUCTTAPE_LOG_FILE" (ducttape-log-file)))) + (ducttape-append-logfile 'note (format #f "START - pid=~A ppid=~A argv=(~A) pwd=~A user=~A host=~A" pid ppid argv pwd user host) #t))) + + + ;; log exit code + (define (set-ducttape-log-exit-handler) + (let ((orig-exit-handler (exit-handler))) + (exit-handler + (lambda (exitcode) + (ducttape-append-logfile 'note (format #f "Exit ~A by sys.exit" exitcode) #t) + (orig-exit-handler exitcode))))) + + + (define (idbg first-message . rest-args) + (let* ((debug-level-threshold + (if (> (length rest-args) 0) (car rest-args) 1)) + (message-list + (if (> (length rest-args) 1) + (cons first-message (cdr rest-args)) + (list first-message)) ) + (message (apply conc + (map ->string message-list)))) + + (ducttape-append-logfile 'dbg message) + (if (ducttape-debug-level) + (if (<= debug-level-threshold (ducttape-debug-level)) + (if (string-search (ducttape-debug-regex-filter) message) + (begin + (format (current-error-port) "~A ~A (~A)~%" (iputs-preamble 'dbg) message (script-name)))))))) + + (define (ierr message-first . message-rest) + (let* ((message + (apply conc + (map ->string (cons message-first message-rest))))) + (ducttape-append-logfile 'err message) + (format (current-error-port) "~A ~A (~A)~%" (iputs-preamble 'err) message (script-name)))) + + (define (iwarn message-first . message-rest) + (let* ((message + (apply conc + (map ->string (cons message-first message-rest))))) + (ducttape-append-logfile 'warn message) + (if (not (ducttape-silent-mode)) + (begin + (format (current-error-port) "~A ~A (~A)~%" (iputs-preamble 'warn) message (script-name)))))) + + (define (inote message-first . message-rest) + (let* ((message + (apply conc + (map ->string (cons message-first message-rest))))) + (ducttape-append-logfile 'note message) + (if (not (or (ducttape-silent-mode) (ducttape-quiet-mode))) + (begin + (format (current-error-port) "~A ~A (~A)~%" (iputs-preamble 'note) message (script-name)))))) + + + (define (iputs kind message #!optional (debug-level-threshold 1)) + (cond + ((member kind (string-split "NOTE/Note/note/n/N" "/")) (inote message)) + ((member kind (string-split "Error/ERROR/error/Err/ERR/err/E/e" "/")) (ierr message)) + ((member kind + (string-split "Warning/WARNING/warning/Warn/WARN/warn/W/w" "/")) + (iwarn message)) + ((member kind (string-split "Debug/DEBUG/debug/Dbg/DBG/dbg/D/d" "/")) + (idbg message debug-level-threshold)))) + + (define (mkdir-recursive path-so-far hier-list-to-create) + (if (null? hier-list-to-create) + path-so-far + (let* ((next-hier-item (car hier-list-to-create)) + (rest-hier-items (cdr hier-list-to-create)) + (path-to-mkdir (string-concatenate (list path-so-far "/" next-hier-item)))) + (if (runs-ok (lambda () (create-directory path-to-mkdir))) + (mkdir-recursive path-to-mkdir rest-hier-items) + #f)))) + + ; ::mkdir-if-not-exists:: + ; make a dir recursively if it does not + ; already exist. + ; on success - returns path + ; on fail - returns #f + (define (mkdirp-if-not-exists the-dir) + (let ( (path-list (string-split the-dir "/"))) + (mkdir-recursive "/" path-list))) + + ; ::mkdir-if-not-exists:: + ; make a dir recursively if it does not + ; already exist. + ; on success - returns path + ; on fail - returns #f + + + (define (mkdirp-if-not-exists the-dir) + (let ( (path-list (string-split the-dir "/"))) + (mkdir-recursive "/" path-list))) + + (define (dir-is-writable? the-dir) + (let ((dummy-file (string-concatenate (list the-dir "/.dummyfile")))) + (and + (file-exists? the-dir) + (cond + ((runs-ok (lambda ()(with-output-to-file dummy-file (lambda () (print "foo"))))) + (begin + (runs-ok (lambda () (delete-file dummy-file) )) + the-dir)) + (else #f))))) + + + (define (get-tmpdir ) + (let* ((tmproot + (dir-is-writable? + (or + (get-environment-variable "TMPDIR") + "/tmp"))) + + (user + (or + (get-environment-variable "USER") + "USER_Envvar_not_set")) + (tmppath + (string-concatenate + (list tmproot "/env21-general-" user )))) + + (dir-is-writable? + (mkdirp-if-not-exists + tmppath)))) + + (define (mktemp + #!optional + (prefix "general_lib_tmpfile") + (dir #f)) + (let-values + (((fd path) + (file-mkstemp + (conc + (if dir dir (get-tmpdir)) + "/" prefix ".XXXXXX")))) + (close-output-port (open-output-file* fd)) + path)) + + + + ;;http://stackoverflow.com/questions/11134857/using-sendmail-for-html-body-and-binary-attachment + ;; write send-email using: + ;; - isys-foreach-stdin-line + ;; - formatting in http://stackoverflow.com/questions/11134857/using-sendmail-for-html-body-and-binary-attachment + (define (sendmail to_addr subject body + #!key + (from_addr "admin") + cc_addr + bcc_addr + more-headers + use_html + (attach-files-list '()) + (images-with-content-id-alist '()) + ) + + (define (sendmail-proc sendmail-port) + (define (wl line-str) + (write-line line-str sendmail-port)) + + (define (get-uuid) + (string-upcase (uuid->string (uuid-generate)))) + + (let ((mailpart-uuid (get-uuid)) + (mailpart-body-uuid (get-uuid))) + + (define (boundary) + (wl (conc "--" mailpart-uuid))) + + (define (body-boundary) + (wl (conc "--" mailpart-body-uuid))) + + + (define (email-mime-header) + (wl (conc "From: " from_addr)) + (wl (conc "To: " to_addr)) + (if cc_addr + (wl (conc "Cc: " cc_addr))) + (if bcc_addr + (wl (conc "Bcc: " bcc_addr))) + (if more-headers + (wl more-headers)) + (wl (conc "Subject: " subject)) + (wl "MIME-Version: 1.0") + (wl (conc "Content-Type: multipart/mixed; boundary=\"" mailpart-uuid "\"")) + (wl "") + (boundary) + (wl (conc "Content-Type: multipart/alternative; boundary=\"" mailpart-body-uuid "\"")) + (wl "") + ) + + + (define (email-text-body) + (body-boundary) + (wl "Content-Type: text/plain; charset=ISO-8859-1") + (wl "Content-Disposition: inline") + (wl "") + (wl body) + (body-boundary)) + + (define (email-html-body) + (body-boundary) + (wl "Content-Type: text/plain; charset=ISO-8859-1") + (wl "") + (wl "You need to enable HTML option for email") + (body-boundary) + (wl "Content-Type: text/html; charset=ISO-8859-1") + (wl "Content-Disposition: inline") + (wl "") + (wl body) + (body-boundary)) + + (define (attach-file file #!key (content-id #f)) + (let* ((filename + (filepath:take-file-name file)) + (ext-with-dot + (filepath:take-extension file)) + (ext (string-take-right + ext-with-dot + (- (string-length ext-with-dot) 1))) + (mimetype (ext->mimetype ext)) + (uuencode-command (conc "uuencode " file " " filename))) + (boundary) + (wl (conc "Content-Type: " mimetype "; name=\"" filename "\"")) + (wl "Content-Transfer-Encoding: uuencode") + (if content-id + (wl (conc "Content-Id: " content-id))) + (wl (conc "Content-Disposition: attachment; filename=\"" filename "\"")) + (wl "") + (do-or-die + uuencode-command + foreach-stdout: + (lambda (line) + (wl line))))) + + (define (embed-image file+content-id) + (let ((file (car file+content-id)) + (content-id (cdr file+content-id))) + (attach-file file content-id: content-id))) + + ;; send the email + (email-mime-header) + (if use_html + (email-html-body) + (email-text-body)) + (for-each attach-file attach-files-list) + (for-each embed-image images-with-content-id-alist) + (boundary) + (close-output-port sendmail-port))) + + (do-or-die "/usr/sbin/sendmail -t" + stdin-proc: sendmail-proc)) + + ;; like shell "which" command + (define (find-exe exe) + (let* ((path-items + (string-split + (or + (get-environment-variable "PATH") "") + ":"))) + + (let loop ((rest-path-items path-items)) + (if (null? rest-path-items) + #f + (let* ((this-dir (car rest-path-items)) + (next-rest (cdr rest-path-items)) + (candidate (conc this-dir "/" exe))) + (if (file-execute-access? candidate) + candidate + (loop next-rest))))))) + + +;;;; process command line options + + ;; get command line switches (have no subsequent arg; eg. [-foo]) + ;; assumes these are switches without arguments + ;; will return list of matches + ;; removes matches from command-line-arguments parameter + (define (skim-cmdline-opts-noarg-by-regex switch-pattern) + (let* ( + (irr (irregex switch-pattern)) + (matches (filter + (lambda (x) + (irregex-match irr x)) + (command-line-arguments))) + (non-matches (filter + (lambda (x) + (not (member x matches))) + (command-line-arguments)))) + + (command-line-arguments non-matches) + matches)) + + (define (keyword-skim keyword default args #!optional (eqpred equal?)) + (let loop ( (kwval default) (args-remaining args) (args-to-return '()) ) + (cond + ((null? args-remaining) + (values + (if (list? kwval) (reverse kwval) kwval) + (reverse args-to-return))) + ((and (> (length args-remaining) 1) (eqpred keyword (car args-remaining))) + (if (list? default) + (if (equal? default kwval) + (loop (list (cadr args-remaining)) (cddr args-remaining) args-to-return) + (loop (cons (cadr args-remaining) kwval) (cddr args-remaining) args-to-return)) + (loop (cadr args-remaining) (cddr args-remaining) args-to-return))) + (else (loop kwval (cdr args-remaining) (cons (car args-remaining) args-to-return)))))) + + + + ;; get command line switches (have a subsequent arg; eg. [-foo bar]) + ;; assumes these are switches without arguments + ;; will return list of arguments to matches + ;; removes matches from command-line-arguments parameter + + (define (re-match? re str) + (irregex-match re str)) + + (define (skim-cmdline-opts-withargs-by-regex switch-pattern) + (let-values + (((result new-cmdline-args) + (keyword-skim switch-pattern + '() + (command-line-arguments) + re-match? + ))) + (command-line-arguments new-cmdline-args) + result)) + + + + ;; recognize ducttape-lib command line switches (--quiet, --silent, --color, -d.., -dp.., -logfile) + ;; - reset parameters; reset DUCTTAPE_* env vars to match user specified intent + ;; - mutate (command-line-arguments) parameter to subtract these recognized and handled switches + ;; * beware -- now (argv) and (command-line-arguments) are inconsistent... cannot mutate (argv) alas. Use (command-line-arguments) + ;; WARNING: this defines command line arguments that may clash with your program. Only call this if you + ;; are sure they can coexist. + (define (ducttape-process-command-line) + + ;; --quiet + (let ((quiet-opts (skim-cmdline-opts-noarg-by-regex "--?quiet"))) + (if (not (null? quiet-opts)) + (begin + (setenv "DUCTTAPE_QUIET_MODE" "1") + (ducttape-quiet-mode "1")))) + + ;; --silent + (let ((silent-opts (skim-cmdline-opts-noarg-by-regex "--?silent"))) + (if (not (null? silent-opts)) + (begin + (setenv "DUCTTAPE_SILENT_MODE" "1") + (ducttape-silent-mode "1")))) + + ;; -color + (let ((color-opts (skim-cmdline-opts-noarg-by-regex "--?colou?r(ize)?"))) + (if (not (null? color-opts)) + (begin + (setenv "DUCTTAPE_COLORIZE" "1") + (ducttape-color-mode "1")))) + + ;; -nocolor + (let ((nocolor-opts (skim-cmdline-opts-noarg-by-regex "--?nocolou?r(ize)?"))) + (if (not (null? nocolor-opts)) + (begin + (unsetenv "DUCTTAPE_COLORIZE" ) + (ducttape-color-mode #f)))) + + ;; -logfile + (let ((logfile-opts (skim-cmdline-opts-withargs-by-regex "--?log(-?file)?"))) + (if (not (null? logfile-opts)) + (begin + (ducttape-log-file (car (reverse logfile-opts))) + (setenv "DUCTTAPE_LOG_FILE" (ducttape-log-file))))) + + ;; -d -dd -d# + (let ((debug-opts (skim-cmdline-opts-noarg-by-regex "-d(d*|\\d+)")) + (initial-debuglevel (if (ducttape-debug-level) (ducttape-debug-level) 0) )) + (if (not (null? debug-opts)) + (begin + (ducttape-debug-level + (let loop ((opts debug-opts) (debuglevel initial-debuglevel)) + (if (null? opts) + debuglevel + (let* + ( (curopt (car opts)) + (restopts (cdr opts)) + (ds (string-match "-(d+)" curopt)) + (dnum (string-match "-d(\\d+)" curopt))) + (cond + (ds (loop restopts (+ debuglevel (string-length (cadr ds))))) + (dnum (loop restopts (string->number (cadr dnum))))))))) + (setenv "DUCTTAPE_DEBUG_LEVEL" (number->string (ducttape-debug-level)))))) + + + ;; -dp / --debug-pattern + (let ((debugpat-opts (skim-cmdline-opts-withargs-by-regex "--?(debug-pattern|dp)"))) + (if (not (null? debugpat-opts)) + (begin + (ducttape-debug-regex-filter (string-join debugpat-opts "|")) + (setenv "DUCTTAPE_DEBUG_PATTERN" (ducttape-debug-regex-filter)))))) + + + ;;; following code commented out; side effects not wanted on startup + ;; immediately activate logfile (will be noop if logfile disabled) + ;;(ducttape-activate-logfile) + ;;(set-ducttape-log-exit-handler) + + ;; TODO: hook exception handler so we can log exception before we sign off. + + ;; handle command line immediately; + ;;(process-command-line) + + + ) ; end module ADDED ducttape/ducttape-lib.setup Index: ducttape/ducttape-lib.setup ================================================================== --- /dev/null +++ ducttape/ducttape-lib.setup @@ -0,0 +1,1 @@ +(standard-extension 'ducttape-lib '1.0.0) ADDED ducttape/mimetypes.scm Index: ducttape/mimetypes.scm ================================================================== --- /dev/null +++ ducttape/mimetypes.scm @@ -0,0 +1,782 @@ +;; gathered from macosx: +;; cat /etc/apache2/mime.types | grep -v '^#' | perl -ne 'print "(\"$2\" . \"$1\")\n" if /(\S+)\s+(\S+)/' > mimetypes.scm +;; + manual manipulation + +(define ducttape_ext2mimetype '(("ez" . "application/andrew-inset") +("aw" . "application/applixware") +("atom" . "application/atom+xml") +("atomcat" . "application/atomcat+xml") +("atomsvc" . "application/atomsvc+xml") +("ccxml" . "application/ccxml+xml") +("cdmia" . "application/cdmi-capability") +("cdmic" . "application/cdmi-container") +("cdmid" . "application/cdmi-domain") +("cdmio" . "application/cdmi-object") +("cdmiq" . "application/cdmi-queue") +("cu" . "application/cu-seeme") +("davmount" . "application/davmount+xml") +("dbk" . "application/docbook+xml") +("dssc" . "application/dssc+der") +("xdssc" . "application/dssc+xml") +("ecma" . "application/ecmascript") +("emma" . "application/emma+xml") +("epub" . "application/epub+zip") +("exi" . "application/exi") +("pfr" . "application/font-tdpfr") +("gml" . "application/gml+xml") +("gpx" . "application/gpx+xml") +("gxf" . "application/gxf") +("stk" . "application/hyperstudio") +("ink" . "application/inkml+xml") +("ipfix" . "application/ipfix") +("jar" . "application/java-archive") +("ser" . "application/java-serialized-object") +("class" . "application/java-vm") +("js" . "application/javascript") +("json" . "application/json") +("jsonml" . "application/jsonml+json") +("lostxml" . "application/lost+xml") +("hqx" . "application/mac-binhex40") +("cpt" . "application/mac-compactpro") +("mads" . "application/mads+xml") +("mrc" . "application/marc") +("mrcx" . "application/marcxml+xml") +("ma" . "application/mathematica") +("mathml" . "application/mathml+xml") +("mbox" . "application/mbox") +("mscml" . "application/mediaservercontrol+xml") +("metalink" . "application/metalink+xml") +("meta4" . "application/metalink4+xml") +("mets" . "application/mets+xml") +("mods" . "application/mods+xml") +("m21" . "application/mp21") +("mp4s" . "application/mp4") +("doc" . "application/msword") +("mxf" . "application/mxf") +("bin" . "application/octet-stream") +("oda" . "application/oda") +("opf" . "application/oebps-package+xml") +("ogx" . "application/ogg") +("omdoc" . "application/omdoc+xml") +("onetoc" . "application/onenote") +("oxps" . "application/oxps") +("xer" . "application/patch-ops-error+xml") +("pdf" . "application/pdf") +("pgp" . "application/pgp-encrypted") +("asc" . "application/pgp-signature") +("prf" . "application/pics-rules") +("p10" . "application/pkcs10") +("p7m" . "application/pkcs7-mime") +("p7s" . "application/pkcs7-signature") +("p8" . "application/pkcs8") +("ac" . "application/pkix-attr-cert") +("cer" . "application/pkix-cert") +("crl" . "application/pkix-crl") +("pkipath" . "application/pkix-pkipath") +("pki" . "application/pkixcmp") +("pls" . "application/pls+xml") +("ai" . "application/postscript") +("cww" . "application/prs.cww") +("pskcxml" . "application/pskc+xml") +("rdf" . "application/rdf+xml") +("rif" . "application/reginfo+xml") +("rnc" . "application/relax-ng-compact-syntax") +("rl" . "application/resource-lists+xml") +("rld" . "application/resource-lists-diff+xml") +("rs" . "application/rls-services+xml") +("gbr" . "application/rpki-ghostbusters") +("mft" . "application/rpki-manifest") +("roa" . "application/rpki-roa") +("rsd" . "application/rsd+xml") +("rss" . "application/rss+xml") +("rtf" . "application/rtf") +("sbml" . "application/sbml+xml") +("scq" . "application/scvp-cv-request") +("scs" . "application/scvp-cv-response") +("spq" . "application/scvp-vp-request") +("spp" . "application/scvp-vp-response") +("sdp" . "application/sdp") +("setpay" . "application/set-payment-initiation") +("setreg" . "application/set-registration-initiation") +("shf" . "application/shf+xml") +("smi" . "application/smil+xml") +("rq" . "application/sparql-query") +("srx" . "application/sparql-results+xml") +("gram" . "application/srgs") +("grxml" . "application/srgs+xml") +("sru" . "application/sru+xml") +("ssdl" . "application/ssdl+xml") +("ssml" . "application/ssml+xml") +("tei" . "application/tei+xml") +("tfi" . "application/thraud+xml") +("tsd" . "application/timestamped-data") +("plb" . "application/vnd.3gpp.pic-bw-large") +("psb" . "application/vnd.3gpp.pic-bw-small") +("pvb" . "application/vnd.3gpp.pic-bw-var") +("tcap" . "application/vnd.3gpp2.tcap") +("pwn" . "application/vnd.3m.post-it-notes") +("aso" . "application/vnd.accpac.simply.aso") +("imp" . "application/vnd.accpac.simply.imp") +("acu" . "application/vnd.acucobol") +("atc" . "application/vnd.acucorp") +("air" . "application/vnd.adobe.air-application-installer-package+zip") +("fcdt" . "application/vnd.adobe.formscentral.fcdt") +("fxp" . "application/vnd.adobe.fxp") +("xdp" . "application/vnd.adobe.xdp+xml") +("xfdf" . "application/vnd.adobe.xfdf") +("ahead" . "application/vnd.ahead.space") +("azf" . "application/vnd.airzip.filesecure.azf") +("azs" . "application/vnd.airzip.filesecure.azs") +("azw" . "application/vnd.amazon.ebook") +("acc" . "application/vnd.americandynamics.acc") +("ami" . "application/vnd.amiga.ami") +("apk" . "application/vnd.android.package-archive") +("cii" . "application/vnd.anser-web-certificate-issue-initiation") +("fti" . "application/vnd.anser-web-funds-transfer-initiation") +("atx" . "application/vnd.antix.game-component") +("mpkg" . "application/vnd.apple.installer+xml") +("m3u8" . "application/vnd.apple.mpegurl") +("swi" . "application/vnd.aristanetworks.swi") +("iota" . "application/vnd.astraea-software.iota") +("aep" . "application/vnd.audiograph") +("mpm" . "application/vnd.blueice.multipass") +("bmi" . "application/vnd.bmi") +("rep" . "application/vnd.businessobjects") +("cdxml" . "application/vnd.chemdraw+xml") +("mmd" . "application/vnd.chipnuts.karaoke-mmd") +("cdy" . "application/vnd.cinderella") +("cla" . "application/vnd.claymore") +("rp9" . "application/vnd.cloanto.rp9") +("c4g" . "application/vnd.clonk.c4group") +("c11amc" . "application/vnd.cluetrust.cartomobile-config") +("c11amz" . "application/vnd.cluetrust.cartomobile-config-pkg") +("csp" . "application/vnd.commonspace") +("cdbcmsg" . "application/vnd.contact.cmsg") +("cmc" . "application/vnd.cosmocaller") +("clkx" . "application/vnd.crick.clicker") +("clkk" . "application/vnd.crick.clicker.keyboard") +("clkp" . "application/vnd.crick.clicker.palette") +("clkt" . "application/vnd.crick.clicker.template") +("clkw" . "application/vnd.crick.clicker.wordbank") +("wbs" . "application/vnd.criticaltools.wbs+xml") +("pml" . "application/vnd.ctc-posml") +("ppd" . "application/vnd.cups-ppd") +("car" . "application/vnd.curl.car") +("pcurl" . "application/vnd.curl.pcurl") +("dart" . "application/vnd.dart") +("rdz" . "application/vnd.data-vision.rdz") +("uvf" . "application/vnd.dece.data") +("uvt" . "application/vnd.dece.ttml+xml") +("uvx" . "application/vnd.dece.unspecified") +("uvz" . "application/vnd.dece.zip") +("fe_launch" . "application/vnd.denovo.fcselayout-link") +("dna" . "application/vnd.dna") +("mlp" . "application/vnd.dolby.mlp") +("dpg" . "application/vnd.dpgraph") +("dfac" . "application/vnd.dreamfactory") +("kpxx" . "application/vnd.ds-keypoint") +("ait" . "application/vnd.dvb.ait") +("svc" . "application/vnd.dvb.service") +("geo" . "application/vnd.dynageo") +("mag" . "application/vnd.ecowin.chart") +("nml" . "application/vnd.enliven") +("esf" . "application/vnd.epson.esf") +("msf" . "application/vnd.epson.msf") +("qam" . "application/vnd.epson.quickanime") +("slt" . "application/vnd.epson.salt") +("ssf" . "application/vnd.epson.ssf") +("es3" . "application/vnd.eszigno3+xml") +("ez2" . "application/vnd.ezpix-album") +("ez3" . "application/vnd.ezpix-package") +("fdf" . "application/vnd.fdf") +("mseed" . "application/vnd.fdsn.mseed") +("seed" . "application/vnd.fdsn.seed") +("gph" . "application/vnd.flographit") +("ftc" . "application/vnd.fluxtime.clip") +("fm" . "application/vnd.framemaker") +("fnc" . "application/vnd.frogans.fnc") +("ltf" . "application/vnd.frogans.ltf") +("fsc" . "application/vnd.fsc.weblaunch") +("oas" . "application/vnd.fujitsu.oasys") +("oa2" . "application/vnd.fujitsu.oasys2") +("oa3" . "application/vnd.fujitsu.oasys3") +("fg5" . "application/vnd.fujitsu.oasysgp") +("bh2" . "application/vnd.fujitsu.oasysprs") +("ddd" . "application/vnd.fujixerox.ddd") +("xdw" . "application/vnd.fujixerox.docuworks") +("xbd" . "application/vnd.fujixerox.docuworks.binder") +("fzs" . "application/vnd.fuzzysheet") +("txd" . "application/vnd.genomatix.tuxedo") +("ggb" . "application/vnd.geogebra.file") +("ggt" . "application/vnd.geogebra.tool") +("gex" . "application/vnd.geometry-explorer") +("gxt" . "application/vnd.geonext") +("g2w" . "application/vnd.geoplan") +("g3w" . "application/vnd.geospace") +("gmx" . "application/vnd.gmx") +("kml" . "application/vnd.google-earth.kml+xml") +("kmz" . "application/vnd.google-earth.kmz") +("gqf" . "application/vnd.grafeq") +("gac" . "application/vnd.groove-account") +("ghf" . "application/vnd.groove-help") +("gim" . "application/vnd.groove-identity-message") +("grv" . "application/vnd.groove-injector") +("gtm" . "application/vnd.groove-tool-message") +("tpl" . "application/vnd.groove-tool-template") +("vcg" . "application/vnd.groove-vcard") +("hal" . "application/vnd.hal+xml") +("zmm" . "application/vnd.handheld-entertainment+xml") +("hbci" . "application/vnd.hbci") +("les" . "application/vnd.hhe.lesson-player") +("hpgl" . "application/vnd.hp-hpgl") +("hpid" . "application/vnd.hp-hpid") +("hps" . "application/vnd.hp-hps") +("jlt" . "application/vnd.hp-jlyt") +("pcl" . "application/vnd.hp-pcl") +("pclxl" . "application/vnd.hp-pclxl") +("sfd-hdstx" . "application/vnd.hydrostatix.sof-data") +("mpy" . "application/vnd.ibm.minipay") +("afp" . "application/vnd.ibm.modcap") +("irm" . "application/vnd.ibm.rights-management") +("sc" . "application/vnd.ibm.secure-container") +("icc" . "application/vnd.iccprofile") +("igl" . "application/vnd.igloader") +("ivp" . "application/vnd.immervision-ivp") +("ivu" . "application/vnd.immervision-ivu") +("igm" . "application/vnd.insors.igm") +("xpw" . "application/vnd.intercon.formnet") +("i2g" . "application/vnd.intergeo") +("qbo" . "application/vnd.intu.qbo") +("qfx" . "application/vnd.intu.qfx") +("rcprofile" . "application/vnd.ipunplugged.rcprofile") +("irp" . "application/vnd.irepository.package+xml") +("xpr" . "application/vnd.is-xpr") +("fcs" . "application/vnd.isac.fcs") +("jam" . "application/vnd.jam") +("rms" . "application/vnd.jcp.javame.midlet-rms") +("jisp" . "application/vnd.jisp") +("joda" . "application/vnd.joost.joda-archive") +("ktz" . "application/vnd.kahootz") +("karbon" . "application/vnd.kde.karbon") +("chrt" . "application/vnd.kde.kchart") +("kfo" . "application/vnd.kde.kformula") +("flw" . "application/vnd.kde.kivio") +("kon" . "application/vnd.kde.kontour") +("kpr" . "application/vnd.kde.kpresenter") +("ksp" . "application/vnd.kde.kspread") +("kwd" . "application/vnd.kde.kword") +("htke" . "application/vnd.kenameaapp") +("kia" . "application/vnd.kidspiration") +("kne" . "application/vnd.kinar") +("skp" . "application/vnd.koan") +("sse" . "application/vnd.kodak-descriptor") +("lasxml" . "application/vnd.las.las+xml") +("lbd" . "application/vnd.llamagraphics.life-balance.desktop") +("lbe" . "application/vnd.llamagraphics.life-balance.exchange+xml") +("123" . "application/vnd.lotus-1-2-3") +("apr" . "application/vnd.lotus-approach") +("pre" . "application/vnd.lotus-freelance") +("nsf" . "application/vnd.lotus-notes") +("org" . "application/vnd.lotus-organizer") +("scm" . "application/vnd.lotus-screencam") +("lwp" . "application/vnd.lotus-wordpro") +("portpkg" . "application/vnd.macports.portpkg") +("mcd" . "application/vnd.mcd") +("mc1" . "application/vnd.medcalcdata") +("cdkey" . "application/vnd.mediastation.cdkey") +("mwf" . "application/vnd.mfer") +("mfm" . "application/vnd.mfmp") +("flo" . "application/vnd.micrografx.flo") +("igx" . "application/vnd.micrografx.igx") +("mif" . "application/vnd.mif") +("daf" . "application/vnd.mobius.daf") +("dis" . "application/vnd.mobius.dis") +("mbk" . "application/vnd.mobius.mbk") +("mqy" . "application/vnd.mobius.mqy") +("msl" . "application/vnd.mobius.msl") +("plc" . "application/vnd.mobius.plc") +("txf" . "application/vnd.mobius.txf") +("mpn" . "application/vnd.mophun.application") +("mpc" . "application/vnd.mophun.certificate") +("xul" . "application/vnd.mozilla.xul+xml") +("cil" . "application/vnd.ms-artgalry") +("cab" . "application/vnd.ms-cab-compressed") +("xls" . "application/vnd.ms-excel") +("xlam" . "application/vnd.ms-excel.addin.macroenabled.12") +("xlsb" . "application/vnd.ms-excel.sheet.binary.macroenabled.12") +("xlsm" . "application/vnd.ms-excel.sheet.macroenabled.12") +("xltm" . "application/vnd.ms-excel.template.macroenabled.12") +("eot" . "application/vnd.ms-fontobject") +("chm" . "application/vnd.ms-htmlhelp") +("ims" . "application/vnd.ms-ims") +("lrm" . "application/vnd.ms-lrm") +("thmx" . "application/vnd.ms-officetheme") +("cat" . "application/vnd.ms-pki.seccat") +("stl" . "application/vnd.ms-pki.stl") +("ppt" . "application/vnd.ms-powerpoint") +("ppam" . "application/vnd.ms-powerpoint.addin.macroenabled.12") +("pptm" . "application/vnd.ms-powerpoint.presentation.macroenabled.12") +("sldm" . "application/vnd.ms-powerpoint.slide.macroenabled.12") +("ppsm" . "application/vnd.ms-powerpoint.slideshow.macroenabled.12") +("potm" . "application/vnd.ms-powerpoint.template.macroenabled.12") +("mpp" . "application/vnd.ms-project") +("docm" . "application/vnd.ms-word.document.macroenabled.12") +("dotm" . "application/vnd.ms-word.template.macroenabled.12") +("wps" . "application/vnd.ms-works") +("wpl" . "application/vnd.ms-wpl") +("xps" . "application/vnd.ms-xpsdocument") +("mseq" . "application/vnd.mseq") +("mus" . "application/vnd.musician") +("msty" . "application/vnd.muvee.style") +("taglet" . "application/vnd.mynfc") +("nlu" . "application/vnd.neurolanguage.nlu") +("ntf" . "application/vnd.nitf") +("nnd" . "application/vnd.noblenet-directory") +("nns" . "application/vnd.noblenet-sealer") +("nnw" . "application/vnd.noblenet-web") +("ngdat" . "application/vnd.nokia.n-gage.data") +("n-gage" . "application/vnd.nokia.n-gage.symbian.install") +("rpst" . "application/vnd.nokia.radio-preset") +("rpss" . "application/vnd.nokia.radio-presets") +("edm" . "application/vnd.novadigm.edm") +("edx" . "application/vnd.novadigm.edx") +("ext" . "application/vnd.novadigm.ext") +("odc" . "application/vnd.oasis.opendocument.chart") +("otc" . "application/vnd.oasis.opendocument.chart-template") +("odb" . "application/vnd.oasis.opendocument.database") +("odf" . "application/vnd.oasis.opendocument.formula") +("odft" . "application/vnd.oasis.opendocument.formula-template") +("odg" . "application/vnd.oasis.opendocument.graphics") +("otg" . "application/vnd.oasis.opendocument.graphics-template") +("odi" . "application/vnd.oasis.opendocument.image") +("oti" . "application/vnd.oasis.opendocument.image-template") +("odp" . "application/vnd.oasis.opendocument.presentation") +("otp" . "application/vnd.oasis.opendocument.presentation-template") +("ods" . "application/vnd.oasis.opendocument.spreadsheet") +("ots" . "application/vnd.oasis.opendocument.spreadsheet-template") +("odt" . "application/vnd.oasis.opendocument.text") +("odm" . "application/vnd.oasis.opendocument.text-master") +("ott" . "application/vnd.oasis.opendocument.text-template") +("oth" . "application/vnd.oasis.opendocument.text-web") +("xo" . "application/vnd.olpc-sugar") +("dd2" . "application/vnd.oma.dd2+xml") +("oxt" . "application/vnd.openofficeorg.extension") +("pptx" . "application/vnd.openxmlformats-officedocument.presentationml.presentation") +("sldx" . "application/vnd.openxmlformats-officedocument.presentationml.slide") +("ppsx" . "application/vnd.openxmlformats-officedocument.presentationml.slideshow") +("potx" . "application/vnd.openxmlformats-officedocument.presentationml.template") +("xlsx" . "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") +("xltx" . "application/vnd.openxmlformats-officedocument.spreadsheetml.template") +("docx" . "application/vnd.openxmlformats-officedocument.wordprocessingml.document") +("dotx" . "application/vnd.openxmlformats-officedocument.wordprocessingml.template") +("mgp" . "application/vnd.osgeo.mapguide.package") +("dp" . "application/vnd.osgi.dp") +("esa" . "application/vnd.osgi.subsystem") +("pdb" . "application/vnd.palm") +("paw" . "application/vnd.pawaafile") +("str" . "application/vnd.pg.format") +("ei6" . "application/vnd.pg.osasli") +("efif" . "application/vnd.picsel") +("wg" . "application/vnd.pmi.widget") +("plf" . "application/vnd.pocketlearn") +("pbd" . "application/vnd.powerbuilder6") +("box" . "application/vnd.previewsystems.box") +("mgz" . "application/vnd.proteus.magazine") +("qps" . "application/vnd.publishare-delta-tree") +("ptid" . "application/vnd.pvi.ptid1") +("qxd" . "application/vnd.quark.quarkxpress") +("bed" . "application/vnd.realvnc.bed") +("mxl" . "application/vnd.recordare.musicxml") +("musicxml" . "application/vnd.recordare.musicxml+xml") +("cryptonote" . "application/vnd.rig.cryptonote") +("cod" . "application/vnd.rim.cod") +("rm" . "application/vnd.rn-realmedia") +("rmvb" . "application/vnd.rn-realmedia-vbr") +("link66" . "application/vnd.route66.link66+xml") +("st" . "application/vnd.sailingtracker.track") +("see" . "application/vnd.seemail") +("sema" . "application/vnd.sema") +("semd" . "application/vnd.semd") +("semf" . "application/vnd.semf") +("ifm" . "application/vnd.shana.informed.formdata") +("itp" . "application/vnd.shana.informed.formtemplate") +("iif" . "application/vnd.shana.informed.interchange") +("ipk" . "application/vnd.shana.informed.package") +("twd" . "application/vnd.simtech-mindmapper") +("mmf" . "application/vnd.smaf") +("teacher" . "application/vnd.smart.teacher") +("sdkm" . "application/vnd.solent.sdkm+xml") +("dxp" . "application/vnd.spotfire.dxp") +("sfs" . "application/vnd.spotfire.sfs") +("sdc" . "application/vnd.stardivision.calc") +("sda" . "application/vnd.stardivision.draw") +("sdd" . "application/vnd.stardivision.impress") +("smf" . "application/vnd.stardivision.math") +("sdw" . "application/vnd.stardivision.writer") +("sgl" . "application/vnd.stardivision.writer-global") +("smzip" . "application/vnd.stepmania.package") +("sm" . "application/vnd.stepmania.stepchart") +("sxc" . "application/vnd.sun.xml.calc") +("stc" . "application/vnd.sun.xml.calc.template") +("sxd" . "application/vnd.sun.xml.draw") +("std" . "application/vnd.sun.xml.draw.template") +("sxi" . "application/vnd.sun.xml.impress") +("sti" . "application/vnd.sun.xml.impress.template") +("sxm" . "application/vnd.sun.xml.math") +("sxw" . "application/vnd.sun.xml.writer") +("sxg" . "application/vnd.sun.xml.writer.global") +("stw" . "application/vnd.sun.xml.writer.template") +("sus" . "application/vnd.sus-calendar") +("svd" . "application/vnd.svd") +("sis" . "application/vnd.symbian.install") +("xsm" . "application/vnd.syncml+xml") +("bdm" . "application/vnd.syncml.dm+wbxml") +("xdm" . "application/vnd.syncml.dm+xml") +("tao" . "application/vnd.tao.intent-module-archive") +("pcap" . "application/vnd.tcpdump.pcap") +("tmo" . "application/vnd.tmobile-livetv") +("tpt" . "application/vnd.trid.tpt") +("mxs" . "application/vnd.triscape.mxs") +("tra" . "application/vnd.trueapp") +("ufd" . "application/vnd.ufdl") +("utz" . "application/vnd.uiq.theme") +("umj" . "application/vnd.umajin") +("unityweb" . "application/vnd.unity") +("uoml" . "application/vnd.uoml+xml") +("vcx" . "application/vnd.vcx") +("vsd" . "application/vnd.visio") +("vis" . "application/vnd.visionary") +("vsf" . "application/vnd.vsf") +("wbxml" . "application/vnd.wap.wbxml") +("wmlc" . "application/vnd.wap.wmlc") +("wmlsc" . "application/vnd.wap.wmlscriptc") +("wtb" . "application/vnd.webturbo") +("nbp" . "application/vnd.wolfram.player") +("wpd" . "application/vnd.wordperfect") +("wqd" . "application/vnd.wqd") +("stf" . "application/vnd.wt.stf") +("xar" . "application/vnd.xara") +("xfdl" . "application/vnd.xfdl") +("hvd" . "application/vnd.yamaha.hv-dic") +("hvs" . "application/vnd.yamaha.hv-script") +("hvp" . "application/vnd.yamaha.hv-voice") +("osf" . "application/vnd.yamaha.openscoreformat") +("osfpvg" . "application/vnd.yamaha.openscoreformat.osfpvg+xml") +("saf" . "application/vnd.yamaha.smaf-audio") +("spf" . "application/vnd.yamaha.smaf-phrase") +("cmp" . "application/vnd.yellowriver-custom-menu") +("zir" . "application/vnd.zul") +("zaz" . "application/vnd.zzazz.deck+xml") +("vxml" . "application/voicexml+xml") +("wgt" . "application/widget") +("hlp" . "application/winhlp") +("wsdl" . "application/wsdl+xml") +("wspolicy" . "application/wspolicy+xml") +("7z" . "application/x-7z-compressed") +("abw" . "application/x-abiword") +("ace" . "application/x-ace-compressed") +("dmg" . "application/x-apple-diskimage") +("aab" . "application/x-authorware-bin") +("aam" . "application/x-authorware-map") +("aas" . "application/x-authorware-seg") +("bcpio" . "application/x-bcpio") +("torrent" . "application/x-bittorrent") +("blb" . "application/x-blorb") +("bz" . "application/x-bzip") +("bz2" . "application/x-bzip2") +("cbr" . "application/x-cbr") +("vcd" . "application/x-cdlink") +("cfs" . "application/x-cfs-compressed") +("chat" . "application/x-chat") +("pgn" . "application/x-chess-pgn") +("nsc" . "application/x-conference") +("cpio" . "application/x-cpio") +("csh" . "application/x-csh") +("deb" . "application/x-debian-package") +("dgc" . "application/x-dgc-compressed") +("dir" . "application/x-director") +("wad" . "application/x-doom") +("ncx" . "application/x-dtbncx+xml") +("dtb" . "application/x-dtbook+xml") +("res" . "application/x-dtbresource+xml") +("dvi" . "application/x-dvi") +("evy" . "application/x-envoy") +("eva" . "application/x-eva") +("bdf" . "application/x-font-bdf") +("gsf" . "application/x-font-ghostscript") +("psf" . "application/x-font-linux-psf") +("otf" . "application/x-font-otf") +("pcf" . "application/x-font-pcf") +("snf" . "application/x-font-snf") +("ttf" . "application/x-font-ttf") +("pfa" . "application/x-font-type1") +("woff" . "application/x-font-woff") +("arc" . "application/x-freearc") +("spl" . "application/x-futuresplash") +("gca" . "application/x-gca-compressed") +("ulx" . "application/x-glulx") +("gnumeric" . "application/x-gnumeric") +("gramps" . "application/x-gramps-xml") +("gtar" . "application/x-gtar") +("hdf" . "application/x-hdf") +("install" . "application/x-install-instructions") +("iso" . "application/x-iso9660-image") +("jnlp" . "application/x-java-jnlp-file") +("latex" . "application/x-latex") +("lzh" . "application/x-lzh-compressed") +("mie" . "application/x-mie") +("prc" . "application/x-mobipocket-ebook") +("m3u8" . "application/x-mpegurl") +("application" . "application/x-ms-application") +("lnk" . "application/x-ms-shortcut") +("wmd" . "application/x-ms-wmd") +("wmz" . "application/x-ms-wmz") +("xbap" . "application/x-ms-xbap") +("mdb" . "application/x-msaccess") +("obd" . "application/x-msbinder") +("crd" . "application/x-mscardfile") +("clp" . "application/x-msclip") +("exe" . "application/x-msdownload") +("mvb" . "application/x-msmediaview") +("wmf" . "application/x-msmetafile") +("mny" . "application/x-msmoney") +("pub" . "application/x-mspublisher") +("scd" . "application/x-msschedule") +("trm" . "application/x-msterminal") +("wri" . "application/x-mswrite") +("nc" . "application/x-netcdf") +("nzb" . "application/x-nzb") +("p12" . "application/x-pkcs12") +("p7b" . "application/x-pkcs7-certificates") +("p7r" . "application/x-pkcs7-certreqresp") +("rar" . "application/x-rar-compressed") +("ris" . "application/x-research-info-systems") +("sh" . "application/x-sh") +("shar" . "application/x-shar") +("swf" . "application/x-shockwave-flash") +("xap" . "application/x-silverlight-app") +("sql" . "application/x-sql") +("sit" . "application/x-stuffit") +("sitx" . "application/x-stuffitx") +("srt" . "application/x-subrip") +("sv4cpio" . "application/x-sv4cpio") +("sv4crc" . "application/x-sv4crc") +("t3" . "application/x-t3vm-image") +("gam" . "application/x-tads") +("tar" . "application/x-tar") +("tcl" . "application/x-tcl") +("tex" . "application/x-tex") +("tfm" . "application/x-tex-tfm") +("texinfo" . "application/x-texinfo") +("obj" . "application/x-tgif") +("ustar" . "application/x-ustar") +("src" . "application/x-wais-source") +("der" . "application/x-x509-ca-cert") +("fig" . "application/x-xfig") +("xlf" . "application/x-xliff+xml") +("xpi" . "application/x-xpinstall") +("xz" . "application/x-xz") +("z1" . "application/x-zmachine") +("xaml" . "application/xaml+xml") +("xdf" . "application/xcap-diff+xml") +("xenc" . "application/xenc+xml") +("xhtml" . "application/xhtml+xml") +("xml" . "application/xml") +("dtd" . "application/xml-dtd") +("xop" . "application/xop+xml") +("xpl" . "application/xproc+xml") +("xslt" . "application/xslt+xml") +("xspf" . "application/xspf+xml") +("mxml" . "application/xv+xml") +("yang" . "application/yang") +("yin" . "application/yin+xml") +("zip" . "application/zip") +("adp" . "audio/adpcm") +("au" . "audio/basic") +("mid" . "audio/midi") +("mp4a" . "audio/mp4") +("m4a" . "audio/mp4a-latm") +("mpga" . "audio/mpeg") +("oga" . "audio/ogg") +("s3m" . "audio/s3m") +("sil" . "audio/silk") +("uva" . "audio/vnd.dece.audio") +("eol" . "audio/vnd.digital-winds") +("dra" . "audio/vnd.dra") +("dts" . "audio/vnd.dts") +("dtshd" . "audio/vnd.dts.hd") +("lvp" . "audio/vnd.lucent.voice") +("pya" . "audio/vnd.ms-playready.media.pya") +("ecelp4800" . "audio/vnd.nuera.ecelp4800") +("ecelp7470" . "audio/vnd.nuera.ecelp7470") +("ecelp9600" . "audio/vnd.nuera.ecelp9600") +("rip" . "audio/vnd.rip") +("weba" . "audio/webm") +("aac" . "audio/x-aac") +("aif" . "audio/x-aiff") +("caf" . "audio/x-caf") +("flac" . "audio/x-flac") +("mka" . "audio/x-matroska") +("m3u" . "audio/x-mpegurl") +("wax" . "audio/x-ms-wax") +("wma" . "audio/x-ms-wma") +("ram" . "audio/x-pn-realaudio") +("rmp" . "audio/x-pn-realaudio-plugin") +("wav" . "audio/x-wav") +("xm" . "audio/xm") +("cdx" . "chemical/x-cdx") +("cif" . "chemical/x-cif") +("cmdf" . "chemical/x-cmdf") +("cml" . "chemical/x-cml") +("csml" . "chemical/x-csml") +("xyz" . "chemical/x-xyz") +("bmp" . "image/bmp") +("cgm" . "image/cgm") +("g3" . "image/g3fax") +("gif" . "image/gif") +("ief" . "image/ief") +("jp2" . "image/jp2") +("jpeg" . "image/jpeg") +("ktx" . "image/ktx") +("pict" . "image/pict") +("png" . "image/png") +("btif" . "image/prs.btif") +("sgi" . "image/sgi") +("svg" . "image/svg+xml") +("tiff" . "image/tiff") +("psd" . "image/vnd.adobe.photoshop") +("uvi" . "image/vnd.dece.graphic") +("sub" . "image/vnd.dvb.subtitle") +("djvu" . "image/vnd.djvu") +("dwg" . "image/vnd.dwg") +("dxf" . "image/vnd.dxf") +("fbs" . "image/vnd.fastbidsheet") +("fpx" . "image/vnd.fpx") +("fst" . "image/vnd.fst") +("mmr" . "image/vnd.fujixerox.edmics-mmr") +("rlc" . "image/vnd.fujixerox.edmics-rlc") +("mdi" . "image/vnd.ms-modi") +("wdp" . "image/vnd.ms-photo") +("npx" . "image/vnd.net-fpx") +("wbmp" . "image/vnd.wap.wbmp") +("xif" . "image/vnd.xiff") +("webp" . "image/webp") +("3ds" . "image/x-3ds") +("ras" . "image/x-cmu-raster") +("cmx" . "image/x-cmx") +("fh" . "image/x-freehand") +("ico" . "image/x-icon") +("pntg" . "image/x-macpaint") +("sid" . "image/x-mrsid-image") +("pcx" . "image/x-pcx") +("pic" . "image/x-pict") +("pnm" . "image/x-portable-anymap") +("pbm" . "image/x-portable-bitmap") +("pgm" . "image/x-portable-graymap") +("ppm" . "image/x-portable-pixmap") +("qtif" . "image/x-quicktime") +("rgb" . "image/x-rgb") +("tga" . "image/x-tga") +("xbm" . "image/x-xbitmap") +("xpm" . "image/x-xpixmap") +("xwd" . "image/x-xwindowdump") +("eml" . "message/rfc822") +("igs" . "model/iges") +("msh" . "model/mesh") +("dae" . "model/vnd.collada+xml") +("dwf" . "model/vnd.dwf") +("gdl" . "model/vnd.gdl") +("gtw" . "model/vnd.gtw") +("mts" . "model/vnd.mts") +("vtu" . "model/vnd.vtu") +("wrl" . "model/vrml") +("x3db" . "model/x3d+binary") +("x3dv" . "model/x3d+vrml") +("x3d" . "model/x3d+xml") +("manifest" . "text/cache-manifest") +("appcache" . "text/cache-manifest") +("ics" . "text/calendar") +("css" . "text/css") +("csv" . "text/csv") +("html" . "text/html") +("n3" . "text/n3") +("txt" . "text/plain") +("dsc" . "text/prs.lines.tag") +("rtx" . "text/richtext") +("sgml" . "text/sgml") +("tsv" . "text/tab-separated-values") +("t" . "text/troff") +("ttl" . "text/turtle") +("uri" . "text/uri-list") +("vcard" . "text/vcard") +("curl" . "text/vnd.curl") +("dcurl" . "text/vnd.curl.dcurl") +("scurl" . "text/vnd.curl.scurl") +("mcurl" . "text/vnd.curl.mcurl") +("sub" . "text/vnd.dvb.subtitle") +("fly" . "text/vnd.fly") +("flx" . "text/vnd.fmi.flexstor") +("gv" . "text/vnd.graphviz") +("3dml" . "text/vnd.in3d.3dml") +("spot" . "text/vnd.in3d.spot") +("jad" . "text/vnd.sun.j2me.app-descriptor") +("wml" . "text/vnd.wap.wml") +("wmls" . "text/vnd.wap.wmlscript") +("s" . "text/x-asm") +("c" . "text/x-c") +("f" . "text/x-fortran") +("java" . "text/x-java-source") +("opml" . "text/x-opml") +("p" . "text/x-pascal") +("nfo" . "text/x-nfo") +("etx" . "text/x-setext") +("sfv" . "text/x-sfv") +("uu" . "text/x-uuencode") +("vcs" . "text/x-vcalendar") +("vcf" . "text/x-vcard") +("3gp" . "video/3gpp") +("3g2" . "video/3gpp2") +("h261" . "video/h261") +("h263" . "video/h263") +("h264" . "video/h264") +("jpgv" . "video/jpeg") +("jpm" . "video/jpm") +("mj2" . "video/mj2") +("ts" . "video/mp2t") +("mp4" . "video/mp4") +("mpeg" . "video/mpeg") +("ogv" . "video/ogg") +("qt" . "video/quicktime") +("uvh" . "video/vnd.dece.hd") +("uvm" . "video/vnd.dece.mobile") +("uvp" . "video/vnd.dece.pd") +("uvs" . "video/vnd.dece.sd") +("uvv" . "video/vnd.dece.video") +("dvb" . "video/vnd.dvb.file") +("fvt" . "video/vnd.fvt") +("mxu" . "video/vnd.mpegurl") +("pyv" . "video/vnd.ms-playready.media.pyv") +("uvu" . "video/vnd.uvvu.mp4") +("viv" . "video/vnd.vivo") +("dv" . "video/x-dv") +("webm" . "video/webm") +("f4v" . "video/x-f4v") +("fli" . "video/x-fli") +("flv" . "video/x-flv") +("m4v" . "video/x-m4v") +("mkv" . "video/x-matroska") +("mng" . "video/x-mng") +("asf" . "video/x-ms-asf") +("vob" . "video/x-ms-vob") +("wm" . "video/x-ms-wm") +("wmv" . "video/x-ms-wmv") +("wmx" . "video/x-ms-wmx") +("wvx" . "video/x-ms-wvx") +("avi" . "video/x-msvideo") +("movie" . "video/x-sgi-movie") +("smv" . "video/x-smv") +("ice" . "x-conference/x-cooltalk"))) + +(define (ext->mimetype ext) + (let ((x (assoc ext ducttape_ext2mimetype))) + (if x (cdr x) "text/plain"))) ADDED ducttape/sample_ducttape.scm Index: ducttape/sample_ducttape.scm ================================================================== --- /dev/null +++ ducttape/sample_ducttape.scm @@ -0,0 +1,4 @@ +(include "ducttape-lib.scm") +(import ducttape-lib) +(inote "hello world") +(exit 0) ADDED ducttape/test_ducttape.scm Index: ducttape/test_ducttape.scm ================================================================== --- /dev/null +++ ducttape/test_ducttape.scm @@ -0,0 +1,355 @@ +#!/usr/bin/env csi -script +(use test) +(include "ducttape-lib.scm") +(import ducttape-lib) +(import ansi-escape-sequences) +(use trace) +(set! systype (do-or-die (if (file-exists? "/bin/uname") "/bin/uname" "/usr/bin/uname"))) +;(trace skim-cmdline-opts-withargs-by-regex) +;(trace keyword-skim) +;(trace re-match?) +(define (reset-ducttape) + (unsetenv "DUCTTAPE_DEBUG_LEVEL") + (ducttape-debug-level #f) + + (unsetenv "DUCTTAPE_DEBUG_PATTERN") + (ducttape-debug-regex-filter ".") + + (unsetenv "DUCTTAPE_LOG_FILE") + (ducttape-log-file #f) + + (unsetenv "DUCTTAPE_SILENT_MODE") + (ducttape-silent-mode #f) + + (unsetenv "DUCTTAPE_QUIET_MODE") + (ducttape-quiet-mode #f) + + (unsetenv "DUCTTAPE_COLOR_MODE") + (ducttape-color-mode #f) +) + +(define (reset-ducttape-with-cmdline-list cmdline-list) + (reset-ducttape) + + (command-line-arguments cmdline-list) + (ducttape-process-command-line) +) + + +(define (direct-iputs-test) + (ducttape-color-mode #f) + (ierr "I'm an error") + (iwarn "I'm a warning") + (inote "I'm a note") + + (ducttape-debug-level 1) + (idbg "I'm a debug statement") + (ducttape-debug-level #f) + (idbg "I'm a hidden debug statement") + + (ducttape-silent-mode #t) + (iwarn "I shouldn't show up") + (inote "I shouldn't show up either") + (ierr "I should show up 1") + (ducttape-silent-mode #f) + + (ducttape-quiet-mode #t) + (iwarn "I should show up 2") + (inote "I shouldn't show up though") + (ierr "I should show up 3") + (ducttape-quiet-mode #f) + + (ducttape-debug-level 1) + (idbg "foo") + (iputs "dbg" "debug message") + (iputs "e" "error message") + (iputs "w" "warning message") + (iputs "n" "note message") + + (ducttape-color-mode #t) + (ierr "I'm an error COLOR") + (iwarn "I'm a warning COLOR") + (inote "I'm a note COLOR") + (idbg "I'm a debug COLOR") + + + ) + +(define (test-argprocessor-funcs) + + (test-group + "Command line processor utility functions" + + (set! testargs1 '( "-d" "-d" "-d3" "-ddd" "-foo" "fooarg" "-foo" "fooarg2" "-lastArgIsDecoy" "-foo")) + (command-line-arguments testargs1) + (set! expected_result '("-d" "-d" "-d3" "-ddd")) + (set! expected_sideeffect '("-foo" "fooarg" "-foo" "fooarg2" "-lastArgIsDecoy" "-foo")) + + (test "skim-cmdline-opts-noarg-by-regex result" expected_result (skim-cmdline-opts-noarg-by-regex "-d(d+|\\d+)?")) + (test "skim-cmdline-opts-noarg-by-regex sideeffect" expected_sideeffect (command-line-arguments)) + + + + (command-line-arguments testargs1) + (set! expected_result '("fooarg" "fooarg2" )) + (set! expected_sideeffect '( "-d" "-d" "-d3" "-ddd" "-lastArgIsDecoy" "-foo")) + (test + "skim-cmdline-opts-withargs-by-regex result" + expected_result + (skim-cmdline-opts-withargs-by-regex "--?foo")) + + (test + "skim-cmdline-opts-withargs-by-regex sideeffect" + expected_sideeffect + (command-line-arguments)) + + )) + +(define (test-misc) + (test-group + "misc" + (let ((tmpfile (mktemp))) + (test-assert "mktemp: temp file created" (file-exists? tmpfile)) + (if (file-exists? tmpfile) + (delete-file tmpfile)) + + ))) + + + +(define (test-systemstuff) + (test-group + "system commands" + + (let-values (((ec o e) (isys (find-exe "true")))) + (test-assert "isys: /bin/true should have exit code 0" (equal? ec 0))) + (let-values (((ec o e) (isys (find-exe "false")))) + (test-assert "isys: /bin/false should have exit code 1" (equal? ec 1))) + + (let-values (((ec o e) (isys "/bin/echo" "foo" "bar" "baz"))) + (test-assert "isys: /bin/echo should have exit code 0" (equal? ec 0)) + (test-assert "isys: /bin/echo should have stdout 'foo bar baz'" (equal? o "foo bar baz"))) + + (let-values (((ec o e) (isys "/bin/ls /zzzzz"))) + (let ((expected-code + (if (equal? systype "Darwin") 1 2)) + (expected-err + (if (equal? systype "Darwin") + "ls: /zzzzz: No such file or directory" + "/bin/ls: cannot access /zzzzz: No such file or directory")) + + ) + (test "isys: /bin/ls /zzzzz should have exit code 2" expected-code ec) + (test "isys: /bin/ls /zzzzz should have empty stdout" "" o) + (test + "isys: /bin/ls /zzzzz should have stderr" + expected-err + e)) + ) + + (let-values (((ec o e) (isys "/bin/ls /etc/passwd"))) + (test "isys: /bin/ls /etc/passwd should have exit code 0" 0 ec) + (test "isys: /bin/ls /etc/passwd should have stdout" "/etc/passwd" o) + (test + "isys: /bin/ls /etc/passwd should have empty stderr" + "" + e)) + + (let ((res (do-or-die "/bin/ls /etc/passwd"))) + (test + "do-or-die: ls /etc/passwd should work" + "/etc/passwd" res )) + + (let ((res (do-or-die "/bin/ls /zzzzz" nodie: #t))) + (test + "do-or-die: ls /zzzzz should die" + #f res )) + + ; test reading from process stdout line at a time + (let* ( + (lineno (counter-maker)) + + ; print each line with an index + (eachline-fn (lambda (line) + (print "GOTLINE " (lineno) "> " line))) + + (res + (do-or-die "/bin/ls -l /etc | head; true" + foreach-stdout: eachline-fn ))) + + (test-assert "ls -l /etc should not be empty" + (not (equal? res "")))) + ;; test writing to process stdout line at a time + + (let* ((tmpfile (mktemp)) + (cmd (conc "cat > " tmpfile))) + (let-values (((c o e) + (isys cmd stdin-proc: + (lambda (myport) + (write-line "hello" myport) + (write-line "hello2" myport) + (close-output-port myport))))) + (test "isys-sp: cat should exit 0" 0 c) + (let ((mycmd (conc "cat " tmpfile))) + (test "isys-sp: cat output should match input" "hello\nhello2" (do-or-die mycmd))) + + (delete-file tmpfile) + )) + + (let* ((tmpfile (mktemp)) + (cmd (conc "cat > " tmpfile))) + (do-or-die cmd stdin-proc: + (lambda (myport) + (write-line "hello" myport) + (write-line "hello2" myport) + (close-output-port myport)) + cmd) + (test "dod-sp: cat output should match input" "hello\nhello2" (do-or-die (conc "cat " tmpfile))) + (delete-file tmpfile)) + + + + + + (let* + ((thefile (conc "/tmp/" (get-environment-variable "USER") "9-lines")) + (counter (counter-maker)) + (stdin-writer + (lambda () + (if (< (counter) 10) + (number->string (counter 0)) + #f))) + (cmd (conc "cat > " thefile))) + (let-values + (((c o e) + (isys cmd foreach-stdin-thunk: stdin-writer))) + + (test-assert "isys-fsl: cat should return 0" (equal? c 0)) + + (test-assert + "isys-fsl: cat should have written a file" + (file-exists? thefile)) + + (if + (file-exists? thefile) + (begin + (test "isys-fsl: cat file should have right contents" "1\n2\n3\n4\n5\n6\n7\n8\n9" (do-or-die (conc "cat " thefile))) + (delete-file thefile))))) + + ) ; end test-group + ) ; end define + + +(define (test-argprocessor ) + (test-group + "Command line processor parameter settings" + + (reset-ducttape-with-cmdline-list '()) + (test-assert "(nil) debug mode should be off" (not (ducttape-debug-level))) + (test-assert "(nil): debug pattern should be '.'" (equal? "." (ducttape-debug-regex-filter))) + (test-assert "(nil): colors should be off" (not (ducttape-color-mode))) + (test-assert "(nil): silent mode should be off" (not (ducttape-silent-mode))) + (test-assert "(nil): quiet mode should be off" (not (ducttape-quiet-mode))) + (test-assert "(nil): logfile should be off" (not (ducttape-log-file))) + + (reset-ducttape-with-cmdline-list '("-d")) + (test-assert "-d: debug mode should be on at level 1" (eq? 1 (ducttape-debug-level))) + + (reset-ducttape-with-cmdline-list '("-dd")) + (test "-dd: debug level should be 2" 2 (ducttape-debug-level)) + + (reset-ducttape-with-cmdline-list '("-ddd")) + (test "-ddd: debug level should be 3" 3 (ducttape-debug-level)) + + (reset-ducttape-with-cmdline-list '("-d2")) + (test "-d2: debug level should be 2" 2 (ducttape-debug-level)) + + (reset-ducttape-with-cmdline-list '("-d3")) + (test "-d3: debug level should be 3" 3 (ducttape-debug-level)) + + (reset-ducttape-with-cmdline-list '("-dp" "foo")) + (test "-dp foo: debug pattern should be 'foo'" "foo" (ducttape-debug-regex-filter)) + + (reset-ducttape-with-cmdline-list '("--debug-pattern" "foo")) + (test "--debug-pattern foo: debug pattern should be 'foo'" "foo" (ducttape-debug-regex-filter)) + + (reset-ducttape-with-cmdline-list '("-dp" "foo" "-dp" "bar")) + (test "-dp foo -dp bar: debug pattern should be 'foo|bar'" "foo|bar" (ducttape-debug-regex-filter)) + + (reset-ducttape-with-cmdline-list '("--quiet")) + (test-assert "-quiet: quiet mode should be active" (ducttape-quiet-mode)) + + (reset-ducttape-with-cmdline-list '("--silent")) + (test-assert "-silent: silent mode should be active" (ducttape-silent-mode)) + + (reset-ducttape-with-cmdline-list '("--color")) + (test-assert "-color: color mode should be active" (ducttape-color-mode)) + + (reset-ducttape-with-cmdline-list '("--log" "foo")) + (test "--log foo: logfile should be 'foo'" "foo" (ducttape-log-file)) + +)) + +(define (test-wwdate) + (test-group + "wwdate conversion tests" + (let ((test-table + '(("16ww01.5" . "2016-01-01") + ("16ww18.5" . "2016-04-29") + ("1999ww33.5" . "1999-08-13") + ("16ww18.4" . "2016-04-28") + ("16ww18.3" . "2016-04-27") + ("13ww01.0" . "2012-12-30") + ("13ww52.6" . "2013-12-28") + ("16ww53.3" . "2016-12-28")))) + (for-each + (lambda (test-pair) + (let ((wwdate (car test-pair)) + (isodate (cdr test-pair))) + (test + (conc "(isodate->wwdate "isodate ") => "wwdate) + wwdate + (isodate->wwdate isodate)) + + (test + (conc "(wwdate->isodate "wwdate ") => "isodate) + isodate + (wwdate->isodate wwdate)))) + test-table)))) + +(define (main) + ;; (test ) + +; (test-group "silly settext group" +; (test #f "\x1b[1mfoo\x1b[0m" (set-text (list 'bold) "foo")) +; (test "settext bold" "\x1b[1mfoo\x1b[0m" (set-text (list 'bold) "foo")) +; ) + + ; visually inspect this + (direct-iputs-test) + + ; following use unit test test-egg + (reset-ducttape) + (test-argprocessor-funcs) + (reset-ducttape) + (test-argprocessor) + (test-systemstuff) + (test-misc) + (test-wwdate) + ) ; end main() + +(main) +(sendmail "brandon.j.barclay@intel.com" "6hello subject" "test body" ) + +;(let* ((image-file "/nfs/site/home/bjbarcla/megatest-logo.png") +; (cid "mtlogo") +; (image-alist (list (cons image-file cid))) +; (body (conc "Hello world
\"test
bye!"))) + +; (sendmail "brandon.j.barclay@intel.com" "7hello subject" body use_html: #t images-with-content-id-alist: image-alist) +; (print "sent image mail")) +;(sendmail "bjbarcla" "2hello subject html" "test body

hello

italics" use_html: #t) +;(sendmail "bb" "4hello attach subject html" "

hmm

" use_html: #t attach-files-list: '( "/Users/bb/Downloads/wdmycloud-manual-4779-705103.pdf" ) ) + +;(launch-repl) +(test-exit) ADDED ducttape/test_example.scm Index: ducttape/test_example.scm ================================================================== --- /dev/null +++ ducttape/test_example.scm @@ -0,0 +1,3 @@ +(use ducttape-lib) + +(inote "Hello world") ADDED ducttape/useargs-example.scm Index: ducttape/useargs-example.scm ================================================================== --- /dev/null +++ ducttape/useargs-example.scm @@ -0,0 +1,19 @@ +(use ducttape-lib) + +(let ( + (customers (skim-cmdline-opts-withargs-by-regex "--cust(omer)?")) + (magicmode (skim-cmdline-opts-noarg-by-regex "--magic")) + ) + (print "your customers are " customers) + (if (null? magicmode) + (print "no unicorns for you") + (print "magic!") + ) + ) + +(idbg "hello") +(idbg "hello2" 2) +(idbg "hello2" 3) +(inote "note") +(iwarn "warn") +(ierr "err") ADDED ducttape/workweekdate.scm Index: ducttape/workweekdate.scm ================================================================== --- /dev/null +++ ducttape/workweekdate.scm @@ -0,0 +1,193 @@ +(use srfi-19) +(use test) +;;(use format) +(use regex) +;(declare (unit wwdate)) +;; utility procedures to convert among +;; different ways to express date (wwdate, seconds since epoch, isodate) +;; +;; samples: +;; isodate -> "2016-01-01" +;; wwdate -> "16ww01.5" +;; seconds -> 1451631600 + +;; procedures provided: +;; ==================== +;; seconds->isodate +;; seconds->wwdate +;; +;; isodate->seconds +;; isodate->wwdate +;; +;; wwdate->seconds +;; wwdate->isodate + +;; srfi-19 used extensively; this doc is better tha the eggref: +;; http://srfi.schemers.org/srfi-19/srfi-19.html + +;; Author: brandon.j.barclay@intel.com 16ww18.6 + +(define (date->seconds date) + (inexact->exact + (string->number + (date->string date "~s")))) + +(define (seconds->isodate seconds) + (let* ((date (seconds->date seconds)) + (result (date->string date "~Y-~m-~d"))) + result)) + +(define (isodate->seconds isodate) + "Takes a string input of the form 'YY-MM-DD' or 'YYYY-MM-DD' and returns epoch time; for YY, assume after Y2K" + (let* ((numlist (map string->number (string-split isodate "-"))) + (raw-year (car numlist)) + (year (if (< raw-year 100) (+ raw-year 2000) raw-year)) + (month (list-ref numlist 1)) + (day (list-ref numlist 2)) + (date (make-date 0 0 0 0 day month year)) + (seconds (date->seconds date))) + + seconds)) + +;; adapted from perl Intel::WorkWeek perl module +;; workweek year consists of numbered weeks starting from week 1 +;; days of week are numbered starting from 0 on sunday +;; weeks begin on sunday- day number 0 and end saturday- day 6 +;; week 1 is defined as the week containing jan 1 of the year +;; workweek year does not match calendar year in workweek 1 +;; since workweek 1 contains jan1 and workweek begins sunday, +;; days prior to jan1 in workweek 1 belong to the next workweek year +(define (seconds->wwdate-values seconds) + (define (date-difference->seconds d1 d2) + (- (date->seconds d1) (date->seconds d2))) + + (let* ((thisdate (seconds->date seconds)) + (thisdow (string->number (date->string thisdate "~w"))) + + (year (date-year thisdate)) + ;; intel workweek 1 begins on sunday of week containing jan1 + (jan1 (make-date 0 0 0 0 1 1 year)) + (jan1dow (date-week-day jan1)) + (ww01 (date-subtract-duration jan1 (seconds->time (* 60 60 24 jan1dow)))) + + (ww01_delta_seconds (date-difference->seconds thisdate ww01)) + (wwnum_initial (inexact->exact (add1 (floor (/ ww01_delta_seconds 24 3600 7) )))) + + ;; we could be in ww1 of next year + (this-saturday (seconds->date + (+ seconds + (* 60 60 24 (- 6 thisdow))))) + (this-week-ends-next-year? + (> (date-year this-saturday) year)) + (intelyear + (if this-week-ends-next-year? + (add1 year) + year)) + (intelweek + (if this-week-ends-next-year? + 1 + wwnum_initial))) + (values intelyear intelweek thisdow))) + +(define (string-leftpad in width pad-char) + (let* ((unpadded-str (->string in)) + (padlen_temp (- width (string-length unpadded-str))) + (padlen (if (< padlen_temp 0) 0 padlen_temp)) + (padding (make-string padlen pad-char))) + (conc padding unpadded-str))) + +(define (string-rightpad in width pad-char) + (let* ((unpadded-str (->string in)) + (padlen_temp (- width (string-length unpadded-str))) + (padlen (if (< padlen_temp 0) 0 padlen_temp)) + (padding (make-string padlen pad-char))) + (conc unpadded-str padding))) + +(define (zeropad num width) + (string-leftpad num width #\0)) + +(define (seconds->wwdate seconds) + + (let-values (((intelyear intelweek day-of-week-num) + (seconds->wwdate-values seconds))) + (let ((intelyear-str + (zeropad + (->string + (if (> intelyear 1999) + (- intelyear 2000) intelyear)) + 2)) + (intelweek-str + (zeropad (->string intelweek) 2)) + (dow-str (->string day-of-week-num))) + (conc intelyear-str "ww" intelweek-str "." dow-str)))) + +(define (isodate->wwdate isodate) + (seconds->wwdate + (isodate->seconds isodate))) + +(define (wwdate->seconds wwdate) + (let ((match (string-match "^(\\d+)ww(\\d+).(\\d)$" wwdate))) + (if + (not match) + #f + (let* ( + (intelyear-raw (string->number (list-ref match 1))) + (intelyear (if (< intelyear-raw 100) + (+ intelyear-raw 2000) + intelyear-raw)) + (intelww (string->number (list-ref match 2))) + (dayofweek (string->number (list-ref match 3))) + + (day-of-seconds (* 60 60 24 )) + (week-of-seconds (* day-of-seconds 7)) + + + ;; get seconds at ww1.0 + (new-years-date (make-date 0 0 0 0 1 1 intelyear)) + (new-years-seconds + (date->seconds new-years-date)) + (new-years-dayofweek (date-week-day new-years-date)) + (ww1.0_seconds (- new-years-seconds + (* day-of-seconds + new-years-dayofweek))) + (workweek-adjustment (* week-of-seconds (sub1 intelww))) + (weekday-adjustment (* dayofweek day-of-seconds)) + + (result (+ ww1.0_seconds workweek-adjustment weekday-adjustment))) + result)))) + +(define (wwdate->isodate wwdate) + (seconds->isodate (wwdate->seconds wwdate))) + +(define (current-wwdate) + (seconds->wwdate (current-seconds))) + +(define (current-isodate) + (seconds->isodate (current-seconds))) + +(define (wwdate-tests) + (test-group + "date conversion tests" + (let ((test-table + '(("16ww01.5" . "2016-01-01") + ("16ww18.5" . "2016-04-29") + ("1999ww33.5" . "1999-08-13") + ("16ww18.4" . "2016-04-28") + ("16ww18.3" . "2016-04-27") + ("13ww01.0" . "2012-12-30") + ("13ww52.6" . "2013-12-28") + ("16ww53.3" . "2016-12-28")))) + (for-each + (lambda (test-pair) + (let ((wwdate (car test-pair)) + (isodate (cdr test-pair))) + (test + (conc "(isodate->wwdate "isodate ") => "wwdate) + wwdate + (isodate->wwdate isodate)) + + (test + (conc "(wwdate->isodate "wwdate ") => "isodate) + isodate + (wwdate->isodate wwdate)))) + test-table)))) Index: http-transport.scm ================================================================== --- http-transport.scm +++ http-transport.scm @@ -8,11 +8,11 @@ ;; implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR ;; PURPOSE. (require-extension (srfi 18) extras tcp s11n) -(use srfi-1 posix regex regex-case srfi-69 hostinfo md5 message-digest) ;; sqlite3 +(use srfi-1 posix regex regex-case srfi-69 hostinfo md5 message-digest posix-extras) ;; sqlite3 ;; (import (prefix sqlite3 sqlite3:)) (use spiffy uri-common intarweb http-client spiffy-request-vars intarweb spiffy-directory-listing) ;; Configurations for server @@ -47,11 +47,11 @@ ;; Call this to start the actual server ;; (define *db:process-queue-mutex* (make-mutex)) -(define (http-transport:run hostn run-id server-id) +(define (http-transport:run hostn) (debug:print 2 *default-log-port* "Attempting to start the server ...") (let* ((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))) ".") @@ -104,18 +104,17 @@ ((equal? (uri-path (request-uri (current-request))) '(/ "hey")) (send-response body: "hey there!\n" headers: '((content-type text/plain)))) (else (continue)))))))) - (http-transport:try-start-server run-id ipaddrstr start-port server-id))) + (http-transport:try-start-server ipaddrstr start-port))) ;; This is recursively run by http-transport:run until sucessful ;; -(define (http-transport:try-start-server run-id ipaddrstr portnum server-id) - (let ((config-hostname (configf:lookup *configdat* "server" "hostname")) - (tdbdat (tasks:open-db))) - (debug:print-info 0 *default-log-port* "http-transport:try-start-server time=" (seconds->time-string (current-seconds)) " run-id=" run-id " ipaddrsstr=" ipaddrstr " portnum=" portnum " server-id=" server-id " config-hostname=" config-hostname) +(define (http-transport:try-start-server ipaddrstr portnum) + (let ((config-hostname (configf:lookup *configdat* "server" "hostname"))) + (debug:print-info 0 *default-log-port* "http-transport:try-start-server time=" (seconds->time-string (current-seconds)) " ipaddrsstr=" ipaddrstr " portnum=" portnum " config-hostname=" config-hostname) (handle-exceptions exn (begin (print-error-message exn) (if (< portnum 64000) @@ -126,34 +125,26 @@ (portlogger:open-run-close portlogger:set-failed portnum) (debug:print 0 *default-log-port* "WARNING: failed to start on portnum: " portnum ", trying next port") (thread-sleep! 0.1) ;; get_next_port goes here - (http-transport:try-start-server run-id - ipaddrstr - (portlogger:open-run-close portlogger:find-port) - server-id)) + (http-transport:try-start-server ipaddrstr + (portlogger:open-run-close portlogger:find-port))) (begin - (tasks:server-force-clean-run-record (db:delay-if-busy tdbdat) run-id ipaddrstr portnum " http-transport:try-start-server") (print "ERROR: Tried and tried but could not start the server")))) ;; any error in following steps will result in a retry (set! *server-info* (list ipaddrstr portnum)) - (tasks:server-set-interface-port - (db:delay-if-busy tdbdat) - server-id - ipaddrstr portnum) (debug:print 0 *default-log-port* "INFO: Trying to start server on " ipaddrstr ":" portnum) ;; This starts the spiffy server ;; NEED WAY TO SET IP TO #f TO BIND ALL ;; (start-server bind-address: ipaddrstr port: portnum) (if config-hostname ;; this is a hint to bind directly (start-server port: portnum bind-address: (if (equal? config-hostname "-") ipaddrstr config-hostname)) (start-server port: portnum)) - ;; (portlogger:open-run-close portlogger:set-port portnum "released") - (tasks:server-force-clean-run-record (db:delay-if-busy tdbdat) run-id ipaddrstr portnum " http-transport:try-start-server") + (portlogger:open-run-close portlogger:set-port portnum "released") (debug:print 1 *default-log-port* "INFO: server has been stopped")))) ;;====================================================================== ;; S E R V E R U T I L I T I E S ;;====================================================================== @@ -292,11 +283,11 @@ 'timeout 'message "nmsg-transport:client-api-send-receive-raw timed out talking to server"))))))) ;; careful closing of connections stored in *runremote* ;; -(define (http-transport:close-connections run-id) +(define (http-transport:close-connections) (let* ((server-dat (if *runremote* (remote-conndat *runremote*) #f))) ;; (hash-table-ref/default *runremote* run-id #f))) (if (vector? server-dat) (let ((api-dat (http-transport:server-dat-get-api-uri server-dat))) @@ -341,17 +332,16 @@ server-dat)) ;; run http-transport:keep-running in a parallel thread to monitor that the db is being ;; used and to shutdown after sometime if it is not. ;; -(define (http-transport:keep-running server-id run-id) +(define (http-transport: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 - (debug:print-info 0 *default-log-port* "Starting the sync-back, keep alive thread in server for run-id=" run-id) - (let* ((tdbdat (tasks:open-db)) - (server-start-time (current-seconds)) + (debug:print-info 0 *default-log-port* "Starting the sync-back, keep alive thread in server") + (let* ((server-start-time (current-seconds)) (server-info (let loop ((start-time (current-seconds)) (changed #t) (last-sdat "not this")) (let ((sdat #f)) (thread-sleep! 0.01) @@ -368,48 +358,33 @@ (begin (debug:print-info 0 *default-log-port* "Still waiting, last-sdat=" last-sdat) (sleep 4) (if (> (- (current-seconds) start-time) 120) ;; been waiting for two minutes (begin - (debug:print-error 0 *default-log-port* "transport appears to have died, exiting server " server-id " for run " run-id) - (tasks:server-delete-record (db:delay-if-busy tdbdat) server-id "failed to start, never received server alive signature") + (debug:print-error 0 *default-log-port* "transport appears to have died, exiting server") (exit)) (loop start-time (equal? sdat last-sdat) sdat))))))) (iface (car server-info)) (port (cadr server-info)) (last-access 0) (server-timeout (server:get-timeout)) - (server-going #f)) + (server-going #f) + (server-log-file (args:get-arg "-log"))) ;; always set when we are a server (let loop ((count 0) (server-state 'available) (bad-sync-count 0) (start-time (current-milliseconds))) - ;;(BB> "http-transport: top of loop; count="count" server-state="server-state" bad-sync-count="bad-sync-count" server-going="server-going) ;; Use this opportunity to sync the tmp db to megatest.db (if (not server-going) ;; *dbstruct-db* - ;; Removed code is pasted below (keeping it around until we are clear it is not needed). - ;; no *dbstruct-db* yet, set running after our first pass through and start the db - (if (eq? server-state 'available) - (let ((new-server-id (tasks:server-am-i-the-server? (db:delay-if-busy tdbdat) run-id))) ;; try to ensure no double registering of servers - (if (equal? new-server-id server-id) - (begin - (tasks:server-set-state! (db:delay-if-busy tdbdat) server-id "dbprep") - ;;(BB> "http-transport: ->dbprep") - (thread-sleep! 0.5) ;; give some margin for queries to complete before switching from file based access to server based access - (set! *dbstruct-db* (db:setup)) ;; run-id)) - (set! server-going #t) - (tasks:server-set-state! (db:delay-if-busy tdbdat) server-id "running") - ;;(BB> "http-transport: ->running") - (server:write-dotserver *toppath* iface port (current-process-id) 'http) - (thread-start! *watchdog*) - (server:complete-attempt *toppath*)) - (begin ;; gotta exit nicely - ;;(BB> "http-transport: ->collision") - (tasks:server-set-state! (db:delay-if-busy tdbdat) server-id "collision") - (http-transport:server-shutdown server-id port)))))) + (begin + (debug:print 0 *default-log-port* "SERVER: dbprep") + (set! *dbstruct-db* (db:setup)) ;; run-id)) + (set! server-going #t) + (debug:print 0 *default-log-port* "SERVER: running, megatest version: " (common:get-full-version)) ;; NOTE: the server is NOT yet marked as running in the log. We do that in the keep-running routine. + (thread-start! *watchdog*))) ;; when things go wrong we don't want to be doing the various queries too often ;; so we strive to run this stuff only every four seconds or so. (let* ((sync-time (- (current-milliseconds) start-time)) (rem-time (quotient (- 4000 sync-time) 1000))) @@ -423,183 +398,111 @@ ;; Check that iface and port have not changed (can happen if server port collides) (mutex-lock! *heartbeat-mutex*) (set! sdat *server-info*) (mutex-unlock! *heartbeat-mutex*) - (if (or (not (equal? sdat (list iface port))) - (not server-id)) - (begin - (debug:print-info 0 *default-log-port* "interface changed, refreshing iface and port info") - (set! iface (car sdat)) - (set! port (cadr sdat)) - (server:write-dotserver *toppath* iface port (current-process-id) 'http))) + (if (not (equal? sdat (list iface port))) + (let ((new-iface (car sdat)) + (new-port (cadr sdat))) + (debug:print-info 0 *default-log-port* "WARNING: interface changed, refreshing iface and port info") + (set! iface new-iface) + (set! port new-port) + (debug:print 0 *default-log-port* "SERVER STARTED: " iface ":" port " AT " (current-seconds)) + (flush-output *default-log-port*))) ;; Transfer *db-last-access* to last-access to use in checking that we are still alive (mutex-lock! *heartbeat-mutex*) (set! last-access *db-last-access*) (mutex-unlock! *heartbeat-mutex*) + + (if (common:low-noise-print 120 (conc "server running on " iface ":" port)) + (begin + (debug:print 0 *default-log-port* "SERVER STARTED: " iface ":" port " AT " (current-seconds)) + (flush-output *default-log-port*))) - ;; (debug:print 11 *default-log-port* "last-access=" last-access ", server-timeout=" server-timeout) - ;; - ;; no_traffic, no running tests, if server 0, no running servers - ;; - ;; (let ((wait-on-running (configf:lookup *configdat* "server" b"wait-on-running"))) ;; wait on running tasks (if not true then exit on time out) - ;; (let* ((hrs-since-start (/ (- (current-seconds) server-start-time) 3600)) (adjusted-timeout (if (> hrs-since-start 1) (- server-timeout (inexact->exact (round (* hrs-since-start 60)))) ;; subtract 60 seconds per hour server-timeout))) (if (common:low-noise-print 120 "server timeout") (debug:print-info 0 *default-log-port* "Adjusted server timeout: " adjusted-timeout)) (cond - ((not (server:confirm-dotserver *toppath* iface port (current-process-id) 'http)) - (debug:print-info 0 *default-log-port* "Server .server file does not exist or contents do not match. Initiate server shutdown.") - (http-transport:server-shutdown server-id port)) ((and *server-run* - (> (+ last-access server-timeout) - (current-seconds))) + (> (+ last-access server-timeout) + (current-seconds)) + (< (- (current-seconds) server-start-time) 3600)) ;; do not update log or touch log if we've been running for more than one hour. (if (common:low-noise-print 120 "server continuing") - (debug:print-info 0 *default-log-port* "Server continuing, seconds since last db access: " (- (current-seconds) last-access))) - ;; - ;; Consider implementing some smarts here to re-insert the record or kill self is - ;; the db indicates so - ;; - ;; (if (tasks:server-am-i-the-server? tdb run-id) - ;; (tasks:server-set-state! tdb server-id "running")) - ;; + (debug:print-info 0 *default-log-port* "Server continuing, seconds since last db access: " (- (current-seconds) last-access)) + (let ((curr-time (current-seconds))) + (change-file-times server-log-file curr-time curr-time))) (loop 0 server-state bad-sync-count (current-milliseconds))) (else - (debug:print-info 0 *default-log-port* "Server timeed out. seconds since last db access: " (- (current-seconds) last-access)) - (http-transport:server-shutdown server-id port))))))) - -;; code cut out from above -;; -;; (condition-case -;; ;; (if (and (member (mutex-state *db-sync-mutex*) '(abandoned not-abandoned)) -;; ;; (> (- (current-seconds) *db-last-sync*) 5)) ;; if not currently being synced nor recently synced -;; (db:sync-touched *dbstruct-db* *run-id* force-sync: #t) ;; usually done in the watchdog, not here. -;; ((sync-failed)(cond -;; ((> bad-sync-count 10) ;; time to give up -;; (http-transport:server-shutdown server-id port)) -;; (else ;; (> bad-sync-count 0) ;; we've had a fail or two, delay and loop -;; (thread-sleep! 5) -;; (loop count server-state (+ bad-sync-count 1))))) -;; ((exn) -;; (debug:print-error 0 *default-log-port* "error from sync code other than 'sync-failed. Attempting to gracefully shutdown the server") -;; (tasks:server-delete-record (db:delay-if-busy tdbdat) server-id " http-transport:keep-running crashed") -;; (exit))) -;; (set! sync-time (- (current-milliseconds) start-time)) -;; (set! rem-time (quotient (- 4000 sync-time) 1000)) -;; (debug:print 4 *default-log-port* "SYNC: time= " sync-time ", rem-time=" rem-time) -;; -;; (if (and (<= rem-time 4) -;; (> rem-time 0)) -;; (thread-sleep! rem-time) -;; (thread-sleep! 4))) ;; fallback for if the math is changed ... - -(define (http-transport:server-shutdown server-id port) + (debug:print-info 0 *default-log-port* "Server timed out. seconds since last db access: " (- (current-seconds) last-access)) + (http-transport:server-shutdown port))))))) + +(define (http-transport:server-shutdown port) (let ((tdbdat (tasks:open-db))) ;;(BB> "http-transport:server-shutdown called") (debug:print-info 0 *default-log-port* "Starting to shutdown the server. pid="(current-process-id)) ;; ;; start_shutdown ;; - (tasks:server-set-state! (db:delay-if-busy tdbdat) server-id "shutting-down") + ;; (tasks:server-set-state! (db:delay-if-busy tdbdat) server-id "shutting-down") (set! *time-to-exit* #t) ;; tell on-exit to be fast as we've already cleaned up (portlogger:open-run-close portlogger:set-port port "released") - (thread-sleep! 5) -;; (debug:print-info 0 *default-log-port* "Max cached queries was " *max-cache-size*) -;; (debug:print-info 0 *default-log-port* "Number of cached writes " *number-of-writes*) -;; (debug:print-info 0 *default-log-port* "Average cached write time " -;; (if (eq? *number-of-writes* 0) -;; "n/a (no writes)" -;; (/ *writes-total-delay* -;; *number-of-writes*)) -;; " ms") -;; (debug:print-info 0 *default-log-port* "Number non-cached queries " *number-non-write-queries*) -;; (debug:print-info 0 *default-log-port* "Average non-cached time " -;; (if (eq? *number-non-write-queries* 0) -;; "n/a (no queries)" -;; (/ *total-non-write-delay* -;; *number-non-write-queries*)) - ;; " ms") - + (thread-sleep! 1) + + ;; (debug:print-info 0 *default-log-port* "Max cached queries was " *max-cache-size*) + ;; (debug:print-info 0 *default-log-port* "Number of cached writes " *number-of-writes*) + ;; (debug:print-info 0 *default-log-port* "Average cached write time " + ;; (if (eq? *number-of-writes* 0) + ;; "n/a (no writes)" + ;; (/ *writes-total-delay* + ;; *number-of-writes*)) + ;; " ms") + ;; (debug:print-info 0 *default-log-port* "Number non-cached queries " *number-non-write-queries*) + ;; (debug:print-info 0 *default-log-port* "Average non-cached time " + ;; (if (eq? *number-non-write-queries* 0) + ;; "n/a (no queries)" + ;; (/ *total-non-write-delay* + ;; *number-non-write-queries*)) + ;; " ms") + (db:print-current-query-stats) (debug:print-info 0 *default-log-port* "Server shutdown complete. Exiting") - (tasks:server-delete-record (db:delay-if-busy tdbdat) server-id " http-transport:keep-running complete") - ;; if the .server file contained :myport then we can remove it - (server:remove-dotserver-file *toppath* port) - ;;(BB> "http-transport:server-shutdown -> exit") (exit))) ;; all routes though here end in exit ... ;; ;; start_server? ;; -(define (http-transport:launch run-id) - (server:attempting-start *toppath*) - (let* ((tdbdat (tasks:open-db))) - (set! *run-id* run-id) - (if (args:get-arg "-daemonize") - (begin - (daemon:ize) - (if *alt-log-file* ;; we should re-connect to this port, I think daemon:ize disrupts it - (begin - (current-error-port *alt-log-file*) - (current-output-port *alt-log-file*))))) - (if (and (server:read-dotserver *toppath*) - (server:check-if-running run-id)) - (begin - (debug:print 0 *default-log-port* "INFO: Server for run-id " run-id " already running") - (exit 0)) - (begin ;; ok, no server detected, clean out any lingering records - (tasks:server-force-clean-running-records-for-run-id (db:delay-if-busy tdbdat) run-id "notresponding"))) - (let loop ((server-id (tasks:server-lock-slot (db:delay-if-busy tdbdat) run-id)) - (remtries 4)) - (if (not server-id) - (if (> remtries 0) - (begin - (thread-sleep! 2) - (loop (tasks:server-lock-slot (db:delay-if-busy tdbdat) run-id) - (- remtries 1))) - (begin - ;; since we didn't get the server lock we are going to clean up and bail out - (debug:print-info 2 *default-log-port* "INFO: server pid=" (current-process-id) ", hostname=" (get-host-name) " not starting due to other candidates ahead in start queue") - (tasks:server-delete-records-for-this-pid (db:delay-if-busy tdbdat) " http-transport:launch") - (server:complete-attempt *toppath*) - )) - (let* ((th2 (make-thread (lambda () - (debug:print-info 0 *default-log-port* "Server run thread started") - (http-transport:run - (if (args:get-arg "-server") - (args:get-arg "-server") - "-") - run-id - server-id)) "Server run")) - (th3 (make-thread (lambda () - (debug:print-info 0 *default-log-port* "Server monitor thread started") - (http-transport:keep-running server-id run-id)) - "Keep running"))) - (thread-start! th2) - (thread-sleep! 0.25) ;; give the server time to settle before starting the keep-running monitor. - (thread-start! th3) - (set! *didsomething* #t) - (thread-join! th2) - (exit)))))) - -;; (define (http:ping run-id host-port) -;; (let* ((server-dat (http-transport:client-connect (car host-port)(cadr host-port))) -;; (login-res (rmt:login-no-auto-client-setup server-dat run-id))) -;; (if (and (list? login-res) -;; (car login-res)) -;; (begin -;; (print "LOGIN_OK") -;; (exit 0)) -;; (begin -;; (print "LOGIN_FAILED") -;; (exit 1))))) +(define (http-transport:launch) + (if (args:get-arg "-daemonize") + (begin + (daemon:ize) + (if *alt-log-file* ;; we should re-connect to this port, I think daemon:ize disrupts it + (begin + (current-error-port *alt-log-file*) + (current-output-port *alt-log-file*))))) + (let* ((th2 (make-thread (lambda () + (debug:print-info 0 *default-log-port* "Server run thread started") + (http-transport:run + (if (args:get-arg "-server") + (args:get-arg "-server") + "-") + )) "Server run")) + (th3 (make-thread (lambda () + (debug:print-info 0 *default-log-port* "Server monitor thread started") + (http-transport:keep-running) + "Keep running")))) + (thread-start! th2) + (thread-sleep! 0.25) ;; give the server time to settle before starting the keep-running monitor. + (thread-start! th3) + (set! *didsomething* #t) + (thread-join! th2) + (exit))) (define (http-transport:server-signal-handler signum) (signal-mask! signum) (handle-exceptions exn Index: megatest-version.scm ================================================================== --- megatest-version.scm +++ megatest-version.scm @@ -1,7 +1,7 @@ ;; Always use two or four digit decimal ;; 1.01, 1.02...1.10,1.11,1.1101 ... 1.99,2.00.. (declare (unit megatest-version)) -(define megatest-version 1.6305) +(define megatest-version 1.6306) Index: megatest.scm ================================================================== --- megatest.scm +++ megatest.scm @@ -43,10 +43,11 @@ (declare (uses tdb)) (declare (uses mt)) (declare (uses api)) (declare (uses tasks)) ;; only used for debugging. (declare (uses env)) +(declare (uses diff-report)) (define *db* #f) ;; this is only for the repl, do not use in general!!!! (include "common_records.scm") (include "key_records.scm") @@ -175,10 +176,18 @@ -archive cmd : archive runs specified by selectors to one of disks specified in the [archive-disks] section. cmd: keep-html, restore, save, save-remove -generate-html : create a simple html tree for browsing your runs +Diff report + -diff-rep : generate diff report (must include -src-target, -src-runname, -target, -runname + and either -diff-email or -diff-html) + -src-target + -src-runname + -diff-email : comma separated list of email addresses to send diff report + -diff-html : path to html file to generate + Spreadsheet generation -extract-ods fname.ods : extract an open document spreadsheet from the database -pathmod path : insert path, i.e. path/runame/itempath/logfile.html will clear the field if no rundir/testname/itempath/logfile if it contains forward slashes the path will be converted @@ -267,10 +276,15 @@ "-fields" "-recover-test" ;; run-id,test-id - used internally to recover a test stuck in RUNNING state "-sort" "-target-db" "-source-db" + + "-src-target" + "-src-runname" + "-diff-email" + "-diff-html" ) (list "-h" "-help" "--help" "-manual" "-version" "-force" @@ -327,11 +341,13 @@ "-sync-to-configdb" "-logging" "-v" ;; verbose 2, more than normal (normal is 1) "-q" ;; quiet 0, errors/warnings only - ) + + "-diff-rep" + ) args:arg-hash 0)) ;; Add args that use remargs here ;; @@ -353,15 +369,32 @@ ;; (define *watchdog* (make-thread common:watchdog "Watchdog thread")) (if (not (args:get-arg "-server")) (thread-start! *watchdog*)) ;; if starting a server; wait till we get to running state before kicking off watchdog -;;(BB> "thread-start! watchdog") -(if (args:get-arg "-log") - (let ((oup (open-output-file (args:get-arg "-log")))) - (debug:print-info 0 *default-log-port* "Sending log output to " (args:get-arg "-log")) +;; bracket open-output-file with code to make leading directory if it does not exist and handle exceptions +(define (open-logfile logpath) + (condition-case + (let* ((log-dir (or (pathname-directory logpath) "."))) + (if (not (directory-exists? log-dir)) + (system (conc "mkdir -p " log-dir))) + (open-output-file logpath)) + (exn () + (debug:print-error 0 *default-log-port* "Could not open log file for write: "logpath) + (define *didsomething* #t) + (exit 1)))) + + +(if (or (args:get-arg "-log")(args:get-arg "-server")) ;; redirect the log always when a server + (let* ((tl (or (args:get-arg "-log")(launch:setup))) ;; run launch:setup if -server + (logf (or (args:get-arg "-log") ;; use -log unless we are a server, then craft a logfile name + (conc tl "/logs/server-" (current-process-id) "-" (get-host-name) ".log"))) + (oup (open-logfile logf))) + (if (not (args:get-arg "-log")) + (hash-table-set! args:arg-hash "-log" logf)) ;; fake out future queries of -log + (debug:print-info 0 *default-log-port* "Sending log output to " logf) (set! *default-log-port* oup))) (if (or (args:get-arg "-h") (args:get-arg "-help") (args:get-arg "--help")) @@ -700,50 +733,17 @@ ;;====================================================================== ;; 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 ;;====================================================================== +;; Server? Start up here. +;; (if (args:get-arg "-server") - - ;; Server? Start up here. - ;; (let ((tl (launch:setup)) - ;; (run-id (and (args:get-arg "-run-id") - ;; (string->number (args:get-arg "-run-id")))) (transport-type (string->symbol (or (args:get-arg "-transport") "http")))) - ;; (if run-id - ;; (begin (server:launch 0 transport-type) (set! *didsomething* #t))) -;; ;; (debug:print-error 0 *default-log-port* "server requires run-id be specified with -run-id"))) -;; -;; ;; Not a server? This section will decide how to communicate -;; ;; -;; ;; Setup client for all expect listed here -;; (if (null? (lset-intersection -;; equal? -;; (hash-table-keys args:arg-hash) -;; '("-list-servers" -;; "-stop-server" -;; "-kill-server" -;; "-show-cmdinfo" -;; "-list-runs" -;; "-ping"))) -;; (if (launch:setup) -;; (let ((run-id (and (args:get-arg "-run-id") -;; (string->number (args:get-arg "-run-id"))))) -;; ;; (set! *fdb* (filedb:open-db (conc *toppath* "/db/paths.db"))) -;; ;; if not list or kill then start a client (if appropriate) -;; (if (or (args-defined? "-h" "-version" "-create-megatest-area" "-create-test") -;; (eq? (length (hash-table-keys args:arg-hash)) 0)) -;; (debug:print-info 1 *default-log-port* "Server connection not needed") -;; (begin -;; ;; (if run-id -;; ;; (client:launch run-id) -;; ;; (client:launch 0) ;; without run-id we'll start a server for "0" -;; #t -;; )))))) (if (or (args:get-arg "-list-servers") (args:get-arg "-stop-server") (args:get-arg "-kill-server")) (let ((tl (launch:setup))) @@ -1869,10 +1869,30 @@ ;;====================================================================== ;; fakeout readline (include "readline-fix.scm") + +(when (args:get-arg "-diff-rep") + (when (and + (not (args:get-arg "-diff-html")) + (not (args:get-arg "-diff-email"))) + (debug:print 0 *default-log-port* "Must specify -diff-html or -diff-email with -diff-rep") + (set! *didsomething* 1) + (exit 1)) + + (let* ((toppath (launch:setup))) + (do-diff-report + (args:get-arg "-src-target") + (args:get-arg "-src-runname") + (args:get-arg "-target") + (args:get-arg "-runname") + (args:get-arg "-diff-html") + (args:get-arg "-diff-email")) + (set! *didsomething* #t) + (exit 0))) + (if (or (getenv "MT_RUNSCRIPT") (args:get-arg "-repl") (args:get-arg "-load")) (let* ((toppath (launch:setup)) (dbstruct (if (and toppath @@ -1992,11 +2012,11 @@ (set! *didsomething* #t))) (if (args:get-arg "-generate-html") (let* ((toppath (launch:setup))) (if (tests:create-html-tree #f) - (debug:print-info 0 *default-log-port* "HTML output created in " toppath "/lt/runs-index.html") + (debug:print-info 0 *default-log-port* "HTML output created in " toppath "/lt/page#.html") (debug:print 0 *default-log-port* "Failed to create HTML output in " toppath "/lt/runs-index.html")) (set! *didsomething* #t))) ;;====================================================================== ;; Exit and clean up Index: rmt.scm ================================================================== --- rmt.scm +++ rmt.scm @@ -36,12 +36,12 @@ (define (rmt:get-connection-info areapath) ;; TODO: push areapath down. (let ((cinfo (remote-conndat *runremote*)) (run-id 0)) (if cinfo cinfo - (if (tasks:server-running-or-starting? (db:delay-if-busy (tasks:open-db)) run-id) - (client:setup run-id) + (if (server:check-if-running areapath) + (client:setup areapath) #f)))) (define *send-receive-mutex* (make-mutex)) ;; should have separate mutex per run-id ;; RA => e.g. usage (rmt:send-receive 'get-var #f (list varname)) @@ -62,11 +62,11 @@ (debug:print 0 *default-log-port* "ERROR: 15 tries to start/connect to server. Giving up.") (exit 1)) ;; reset the connection if it has been unused too long ((and *runremote* (remote-conndat *runremote*) - (let ((expire-time (- start-time (remote-server-timeout *runremote*)))) + (let ((expire-time (+ (- start-time (remote-server-timeout *runremote*))(random 30)))) ;; add 30 seconds of noise so that not all running tests expire at the same time causing a storm of server starts (< (http-transport:server-dat-get-last-access (remote-conndat *runremote*)) expire-time))) (debug:print-info 12 *default-log-port* "rmt:send-receive, case 8") (remote-conndat-set! *runremote* #f) (mutex-unlock! *rmt-mutex*) (rmt:send-receive cmd rid params attemptnum: attemptnum)) @@ -92,12 +92,12 @@ ;; on homehost and this is a write, we already have a server, but server has died ((and (cdr (remote-hh-dat *runremote*)) ;; on homehost (not (member cmd api:read-only-queries)) ;; this is a write (remote-server-url *runremote*) ;; have a server - (not (server:read-dotserver *toppath*))) ;; server has died. - (set! *runremote* #f) + (not (server:check-if-running *toppath*))) ;; server has died. + (set! *runremote* (make-remote)) (mutex-unlock! *rmt-mutex*) (debug:print-info 12 *default-log-port* "rmt:send-receive, case 4.1") (rmt:send-receive cmd rid params attemptnum: attemptnum)) ;; on homehost and this is a write, we already have a server @@ -106,60 +106,28 @@ (remote-server-url *runremote*)) ;; have a server (mutex-unlock! *rmt-mutex*) (debug:print-info 12 *default-log-port* "rmt:send-receive, case 4") (rmt:open-qry-close-locally cmd 0 params)) - ;; commented by bb; this was blocking server passive start on write on homehost (case 5) - ;; ;; on homehost and this is a write, we have a server (we know because case 4 checked) - ;; ((and (cdr (remote-hh-dat *runremote*)) ;; on homehost - ;; (not (member cmd api:read-only-queries))) - ;; (mutex-unlock! *rmt-mutex*) - ;; (debug:print-info 12 *default-log-port* "rmt:send-receive, case 4.1") - ;; (rmt:open-qry-close-locally cmd 0 params)) - - ;; on homehost, no server contact made and this is a write, passively start a server ((and (cdr (remote-hh-dat *runremote*)) ; new (not (remote-server-url *runremote*)) (not (member cmd api:read-only-queries))) (debug:print-info 12 *default-log-port* "rmt:send-receive, case 5") - (let ((server-url (server:read-dotserver->url *toppath*))) ;; (server:check-if-running *toppath*))) ;; Do NOT want to run server:check-if-running - very expensive to do for every write call + (let ((server-url (server:check-if-running *toppath*))) ;; (server:read-dotserver->url *toppath*))) ;; (server:check-if-running *toppath*))) ;; Do NOT want to run server:check-if-running - very expensive to do for every write call (if server-url (remote-server-url-set! *runremote* server-url) ;; the string can be consumed by the client setup if needed - (if (not (server:start-attempted? *toppath*)) - (server:kind-run *toppath*)))) - (mutex-unlock! *rmt-mutex*) - (debug:print-info 12 *default-log-port* "rmt:send-receive, case 5.1") - (rmt:open-qry-close-locally cmd 0 params)) - - - - ;;; - ;; (begin ;; not on homehost, start server and wait - ;; (mutex-unlock! *rmt-mutex*) - ;; (debug:print-info 12 *default-log-port* "rmt:send-receive, case 5.2") - ;; (tasks:start-and-wait-for-server (tasks:open-db) 0 15) - ;; (rmt:send-receive cmd rid params attemptnum: attemptnum)) ;) ;) -;;;; - - ;; if not on homehost ensure we have a connection to a live server - ;; NOTE: we *have* a homehost record by now - - ;; ((and (not (cdr (remote-hh-dat *runremote*))) ;; not on a homehost - ;; (not (remote-conndat *runremote*)) ;; and no connection - ;; (server:read-dotserver *toppath*)) ;; .server file exists - ;; ;; something caused the server entry in tdb to disappear, but the server is still running - ;; (server:remove-dotserver-file *toppath* ".*") - ;; (mutex-unlock! *rmt-mutex*) - ;; (debug:print-info 12 *default-log-port* "rmt:send-receive, case 20") - ;; (rmt:send-receive cmd rid params attemptnum: (add1 attemptnum))) + (server:kind-run *toppath*))) + (mutex-unlock! *rmt-mutex*) + (debug:print-info 12 *default-log-port* "rmt:send-receive, case 5.1") + (rmt:open-qry-close-locally cmd 0 params)) ((and (not (cdr (remote-hh-dat *runremote*))) ;; not on a homehost (not (remote-conndat *runremote*))) ;; and no connection (debug:print-info 12 *default-log-port* "rmt:send-receive, case 6 hh-dat: " (remote-hh-dat *runremote*) " conndat: " (remote-conndat *runremote*)) (mutex-unlock! *rmt-mutex*) - (tasks:start-and-wait-for-server (tasks:open-db) 0 15) + (server:start-and-wait *toppath*) (remote-conndat-set! *runremote* (rmt:get-connection-info *toppath*)) ;; calls client:setup which calls client:setup-http (rmt:send-receive cmd rid params attemptnum: attemptnum)) ;; TODO: add back-off timeout as ;; all set up if get this far, dispatch the query ((cdr (remote-hh-dat *runremote*)) ;; we are on homehost (mutex-unlock! *rmt-mutex*) @@ -198,11 +166,11 @@ (debug:print 0 *default-log-port* "WARNING: communication failed. Trying again, try num: " attemptnum) (remote-conndat-set! *runremote* #f) (remote-server-url-set! *runremote* #f) (debug:print-info 12 *default-log-port* "rmt:send-receive, case 9.1") (mutex-unlock! *rmt-mutex*) - (tasks:start-and-wait-for-server (tasks:open-db) 0 15) + (server:start-and-wait *toppath*) (rmt:send-receive cmd rid params attemptnum: (+ attemptnum 1))))))))) ;; (define (rmt:update-db-stats run-id rawcmd params duration) ;; (mutex-lock! *db-stats-mutex*) ;; (handle-exceptions Index: server.scm ================================================================== --- server.scm +++ server.scm @@ -8,11 +8,11 @@ ;; implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR ;; PURPOSE. (require-extension (srfi 18) extras tcp s11n) -(use srfi-1 posix regex regex-case srfi-69 hostinfo md5 message-digest directory-utils posix-extras) +(use srfi-1 posix regex regex-case srfi-69 hostinfo md5 message-digest directory-utils posix-extras matchable) ;; (use zmq) (use spiffy uri-common intarweb http-client spiffy-request-vars) (declare (unit server)) @@ -47,35 +47,15 @@ ;; all routes though here end in exit ... ;; ;; start_server ;; (define (server:launch run-id transport-type) - ;;(BB> "server:launch fired for run-id="run-id" transport-type="transport-type) - - (let ((attempt-in-progress (server:start-attempted? *toppath*))) ; check for .server-starting - (when attempt-in-progress - (debug:print-info 0 *default-log-port* "Server start attempt in progress in other process (=> "attempt-in-progress"<=). Aborting server launch attempt in this process ("(current-process-id)")") - (exit))) - - (let ((dotserver-url (server:check-if-running *toppath*))) ;; check for .server - (when dotserver-url - (debug:print-info 0 *default-log-port* "Server already running (=> "dotserver-url"<=). Aborting server launch attempt in this process ("(current-process-id)")") - (exit) - )) - (case transport-type - ((http)(http-transport:launch run-id)) + ((http)(http-transport:launch)) ;;((nmsg)(nmsg-transport:launch run-id)) ((rpc) (rpc-transport:launch run-id)) - (else (debug:print-error 0 *default-log-port* "unknown server type " transport-type))) - - ;; is this a good place to print server exit stats? - (debug:print 0 "SERVER: max parallel api requests: " *max-api-process-requests*) - - ) -;; (else (debug:print-error 0 *default-log-port* "No known transport set, transport=" transport ", using rpc") -;; (rpc-transport:launch run-id))))) + (else (debug:print-error 0 *default-log-port* "unknown server type " transport-type)))) ;;====================================================================== ;; S E R V E R U T I L I T I E S ;;====================================================================== @@ -106,14 +86,10 @@ ;; (send-message pubsock target send-more: #t) ;; (send-message pubsock (case (server:get-transport) ((rpc) (db:obj->string (vector success/fail query-sig result))) ((http) (db:obj->string (vector success/fail query-sig result))) - ((zmq) - (let ((pub-socket (vector-ref *runremote* 1))) - (send-message pub-socket return-addr send-more: #t) - (send-message pub-socket (db:obj->string (vector success/fail query-sig result))))) ((fs) result) (else (debug:print-error 0 *default-log-port* "unrecognised transport type: " *transport-type*) result))) @@ -122,241 +98,256 @@ ;; try running on that host ;; incidental: rotate logs in logs/ dir. ;; (define (server:run areapath) ;; areapath is *toppath* for a given testsuite area (let* ((curr-host (get-host-name)) - (attempt-in-progress (server:start-attempted? areapath)) - (dot-server-url (server:check-if-running areapath)) + ;; (attempt-in-progress (server:start-attempted? areapath)) + ;; (dot-server-url (server:check-if-running areapath)) (curr-ip (server:get-best-guess-address curr-host)) (curr-pid (current-process-id)) (homehost (common:get-homehost)) ;; configf:lookup *configdat* "server" "homehost" )) (target-host (car homehost)) (testsuite (common:get-testsuite-name)) - (logfile (conc areapath "/logs/server.log")) + (logfile (conc areapath "/logs/server.log")) ;; -" curr-pid "-" target-host ".log")) (cmdln (conc (common:get-megatest-exe) - " -server " (or target-host "-") " -run-id " 0 (if (equal? (configf:lookup *configdat* "server" "daemonize") "yes") - (conc " -daemonize -log " logfile) - "") + " -server " (or target-host "-") (if (equal? (configf:lookup *configdat* "server" "daemonize") "yes") + " -daemonize " + "") + ;; " -log " logfile " -m testsuite:" testsuite)) ;; (conc " >> " logfile " 2>&1 &"))))) (log-rotate (make-thread common:rotate-logs "server run, rotate logs thread"))) ;; we want the remote server to start in *toppath* so push there (push-directory areapath) - (cond - (attempt-in-progress - (debug:print 0 *default-log-port* "INFO: Not trying to start server because attempt is in progress: "attempt-in-progress)) - (dot-server-url - (debug:print 0 *default-log-port* "INFO: Not trying to start server because one is already running : "dot-server-url)) - (else - (debug:print 0 *default-log-port* "INFO: Trying to start server (" cmdln ") ...") - (thread-start! log-rotate) - - ;; host.domain.tld match host? - (if (and target-host - ;; look at target host, is it host.domain.tld or ip address and does it - ;; match current ip or hostname - (not (string-match (conc "("curr-host "|" curr-host"\\..*)") target-host)) - (not (equal? curr-ip target-host))) - (begin - (debug:print-info 0 *default-log-port* "Starting server on " target-host ", logfile is " logfile) - (setenv "TARGETHOST" target-host))) - - (setenv "TARGETHOST_LOGF" logfile) - (common:wait-for-normalized-load 4 " delaying server start due to load" remote-host: (get-environment-variable "TARGETHOST")) ;; do not try starting servers on an already overloaded machine, just wait forever - (system (conc "nbfake " cmdln)) - (unsetenv "TARGETHOST_LOGF") - (if (get-environment-variable "TARGETHOST")(unsetenv "TARGETHOST")) - (thread-join! log-rotate) - (pop-directory))))) - + (debug:print 0 *default-log-port* "INFO: Trying to start server (" cmdln ") ...") + (thread-start! log-rotate) + + ;; host.domain.tld match host? + (if (and target-host + ;; look at target host, is it host.domain.tld or ip address and does it + ;; match current ip or hostname + (not (string-match (conc "("curr-host "|" curr-host"\\..*)") target-host)) + (not (equal? curr-ip target-host))) + (begin + (debug:print-info 0 *default-log-port* "Starting server on " target-host ", logfile is " logfile) + (setenv "TARGETHOST" target-host))) + + (setenv "TARGETHOST_LOGF" logfile) + (common:wait-for-normalized-load 4 " delaying server start due to load" remote-host: (get-environment-variable "TARGETHOST")) ;; do not try starting servers on an already overloaded machine, just wait forever + (system (conc "nbfake " cmdln)) + (unsetenv "TARGETHOST_LOGF") + (if (get-environment-variable "TARGETHOST")(unsetenv "TARGETHOST")) + (thread-join! log-rotate) + (pop-directory))) + +;; given a path to a server log return: host port startseconds +;; +(define (server:logf-get-start-info logf) + (let ((rx (regexp "^SERVER STARTED: (\\S+):(\\d+) AT ([\\d\\.]+)"))) ;; SERVER STARTED: host:port AT timesecs + (with-input-from-file + logf + (lambda () + (let loop ((inl (read-line)) + (lnum 0)) + (if (not (eof-object? inl)) + (let ((mlst (string-match rx inl))) + (if (not mlst) + (if (< lnum 500) ;; give up if more than 500 lines of server log read + (loop (read-line)(+ lnum 1)) + (list #f #f #f)) + (let ((dat (cdr mlst))) + (list (car dat) ;; host + (string->number (cadr dat)) ;; port + (string->number (caddr dat)))))) + (list #f #f #f))))))) + +;; get a list of servers with all relevant data +;; ( mod-time host port start-time pid ) +;; +(define (server:get-list areapath #!key (limit #f)) + (let ((fname-rx (regexp "^(|.*/)server-(\\d+)-(\\S+).log$")) + (day-seconds (* 24 60 60))) + ;; if the directory exists continue to get the list + ;; otherwise attempt to create the logs dir and then + ;; continue + (if (if (directory-exists? (conc areapath "/logs")) + #t + (if (file-write-access? areapath) + (begin + (condition-case + (create-directory (conc areapath "/logs") #t) + (exn (i/o file)(debug:print 0 *default-log-port* "ERROR: Cannot create directory at " (conc areapath "/logs"))) + (exn ()(debug:print 0 *default-log-port* "ERROR: Unknown error attemtping to get server list."))) + (directory-exists? (conc areapath "/logs"))) + #f)) + (let* ((server-logs (glob (conc areapath "/logs/server-*.log"))) + (num-serv-logs (length server-logs))) + (if (null? server-logs) + '() + (let loop ((hed (car server-logs)) + (tal (cdr server-logs)) + (res '())) + (let* ((mod-time (file-modification-time hed)) + (down-time (- (current-seconds) mod-time)) + (serv-dat (if (or (< num-serv-logs 10) + (< down-time day-seconds)) + (server:logf-get-start-info hed) + '())) ;; don't waste time processing server files not touched in the past day if there are more than ten servers to look at + (serv-rec (cons mod-time serv-dat)) + (fmatch (string-match fname-rx hed)) + (pid (if fmatch (string->number (list-ref fmatch 2)) #f)) + (new-res (if (null? serv-dat) + res + (cons (append serv-rec (list pid)) res)))) + (if (null? tal) + (if (and limit + (> (length new-res) limit)) + new-res ;; (take new-res limit) <= need intelligent sorting before this will work + new-res) + (loop (car tal)(cdr tal) new-res))))))))) + +;; given a list of servers get a list of valid servers, i.e. at least +;; 10 seconds old, has started and is less than 1 hour old and is +;; active (i.e. mod-time < 10 seconds +;; +;; mod-time host port start-time pid +;; +;; sort by start-time descending. I.e. get the oldest first. Young servers will thus drop off +;; and servers should stick around for about two hours or so. +;; +(define (server:get-best srvlst) + (let ((now (current-seconds))) + (sort + (filter (lambda (rec) + (let ((start-time (list-ref rec 3)) + (mod-time (list-ref rec 0))) + ;; (print "start-time: " start-time " mod-time: " mod-time) + (and start-time mod-time + (> (- now start-time) 0) ;; been running at least 0 seconds + (< (- now mod-time) 16) ;; still alive - file touched in last 16 seconds + (< (- now start-time) + (+ (- (string->number (or (configf:lookup *configdat* "server" "runtime") "3600")) + 180) + (random 360))) ;; under one hour running time +/- 180 + ))) + srvlst) + (lambda (a b) + (< (list-ref a 3) + (list-ref b 3)))))) + +(define (server:get-first-best areapath) + (let ((srvrs (server:get-best (server:get-list areapath)))) + (if (and srvrs + (not (null? srvrs))) + (car srvrs) + #f))) + +(define (server:record->url servr) + (match-let (((mod-time host port start-time pid) + servr)) + (if (and host port) + (conc host ":" port) + #f))) + (define (server:get-client-signature) ;; BB> why is this proc named "get-"? it returns nothing -- set! has not return value. (if *my-client-signature* *my-client-signature* (let ((sig (server:mk-signature))) (set! *my-client-signature* sig) *my-client-signature*))) ;; kind start up of servers, wait 40 seconds before allowing another server for a given ;; run-id to be launched (define (server:kind-run areapath) - (let ((last-run-time (hash-table-ref/default *server-kind-run* areapath #f))) - (if (or (not last-run-time) - (> (- (current-seconds) last-run-time) 30)) - (begin - (server:run areapath) - (hash-table-set! *server-kind-run* areapath (current-seconds)))))) + (let* ((last-run-dat (hash-table-ref/default *server-kind-run* areapath '(0 0))) ;; callnum, whenrun + (call-num (car last-run-dat)) + (when-run (cadr last-run-dat)) + (run-delay (+ (case call-num + ((0) 0) + ((1) 20) + ((2) 300) + (else 600)) + (random 5)))) ;; add a small random number just in case a lot of jobs hit the work hosts simultaneously + (if (> (- (current-seconds) when-run) run-delay) + (server:run areapath)) + (hash-table-set! *server-kind-run* areapath (list (+ call-num 1)(current-seconds))))) + +(define (server:start-and-wait areapath #!key (timeout 60)) + (let ((give-up-time (+ (current-seconds) timeout))) + (let loop ((server-url (server:check-if-running areapath))) + (if (or server-url + (> (current-seconds) give-up-time)) ;; server-url will be #f if no server available. + server-url + (let ((num-ok (length (server:get-best (server:get-list areapath))))) + (if (< num-ok 1) ;; if there are no decent candidates for servers then try starting a new one + (server:kind-run areapath)) + (thread-sleep! 5) + (loop (server:check-if-running areapath))))))) (define server:try-running server:run) ;; there is no more per-run servers ;; REMOVE ME. BUG. -(define (server:attempting-start areapath) - (with-output-to-file - (conc areapath "/.starting-server") - (lambda () - (print (current-process-id) " on " (get-host-name))))) - -(define (server:complete-attempt areapath) - (delete-file* (conc areapath "/.starting-server"))) - -(define (server:start-attempted? areapath) - (let ((flagfile (conc areapath "/.starting-server"))) - (handle-exceptions - exn - #f ;; if things go wrong pretend we can't see the file - (cond - ((and (file-exists? flagfile) - (< (- (current-seconds) - (file-modification-time flagfile)) - 15)) ;; exists and less than 15 seconds old - (with-input-from-file flagfile (lambda () (read-line)))) - ((file-exists? flagfile) ;; it is stale. - (server:complete-attempt areapath) - #f) - (else #f))))) - -(define (server:read-dotserver areapath) - (let ((dotfile (conc areapath "/.server"))) - (handle-exceptions - exn - #f ;; if things go wrong pretend we can't see the file - (cond - ((not (file-exists? dotfile)) - #f) - ((not (file-read-access? dotfile)) - #f) - ((> (server:dotserver-age-seconds areapath) (+ 5 (server:get-timeout))) - (server:remove-dotserver-file areapath ".*") - #f) - (else - (let* ((line - (with-input-from-file - dotfile - (lambda () - (read-line)))) - (tokens (if (string? line) (string-split line ":") #f))) - (cond - ((eq? 4 (length tokens)) - tokens) - (else #f)))))))) - -(define (server:read-dotserver->url areapath) - (let ((dotserver-tokens (server:read-dotserver areapath))) - (if dotserver-tokens - (conc (list-ref dotserver-tokens 0) ":" (list-ref dotserver-tokens 1)) - #f))) - -;; write a .server file in *toppath* with hostport -;; return #t on success, #f otherwise -;; -(define (server:write-dotserver areapath host port pid transport) - (let ((lock-file (conc areapath "/.server.lock")) - (server-file (conc areapath "/.server"))) - (if (common:simple-file-lock lock-file) - (let ((res (handle-exceptions - exn - #f ;; failed for some reason, for the moment simply return #f - (with-output-to-file server-file - (lambda () - (print (conc host ":" port ":" pid ":" transport)))) - #t))) - (debug:print-info 0 *default-log-port* "server file " server-file " for " host ":" port " created pid="pid) - (common:simple-file-release-lock lock-file) - res) - #f))) - - -;; this will check that the .server file present matches the server calling this procedure. -;; if parameters match (this-pid and transport) the file will be touched and #t returned -;; otherwise #f will be returned. -(define (server:confirm-dotserver areapath this-iface this-port this-pid this-transport) - (let* ((tokens (server:read-dotserver areapath))) - (cond - ((not tokens) - (debug:print-info 0 *default-log-port* "INFO: .server file does not exist.") - #f) - ((not (eq? 4 (length tokens))) - (debug:print-info 0 *default-log-port* "INFO: .server file is corrupt. There are not 4 tokens as expeted; there are "(length tokens)".") - #f) - ((not (equal? this-iface (list-ref tokens 0))) - (debug:print-info 0 *default-log-port* "INFO: .server file mismatch. for iface, server has value >"(list-ref tokens 0)"< but this server's value is >"this-iface"<") - #f) - ((not (equal? (->string this-port) (list-ref tokens 1))) - (debug:print-info 0 *default-log-port* "INFO: .server file mismatch. for port, .server has value >"(list-ref tokens 1)"< but this server's value is >"(->string this-port)"<") - #f) - ((not (equal? (->string this-pid) (list-ref tokens 2))) - (debug:print-info 0 *default-log-port* "INFO: .server file mismatch. for pid, .server has value >"(list-ref tokens 2)"< but this server's value is >"(->string this-pid)"<") - #f) - ((not (equal? (->string this-transport) (->string (list-ref tokens 3)))) - (debug:print-info 0 *default-log-port* "INFO: .server file mismatch. for transport, .server has value >"(list-ref tokens 3)"< but this server's value is >"this-transport"<") - #f) - (else (server:touch-dotserver areapath) - #t)))) - -(define (server:touch-dotserver areapath) - (let ((server-file (conc areapath "/.server"))) - (change-file-times server-file (current-seconds) (current-seconds)))) - (define (server:dotserver-age-seconds areapath) (let ((server-file (conc areapath "/.server"))) (begin (handle-exceptions exn #f (- (current-seconds) (file-modification-time server-file)))))) -(define (server:remove-dotserver-file areapath hostport) - (let ((dotserver-url (server:read-dotserver->url areapath)) - (server-file (conc areapath "/.server")) - (lock-file (conc areapath "/.server.lock"))) - (if (and dotserver-url (string-match (conc ".*:" hostport "$") dotserver-url)) ;; port matches, good enough info to decide to remove the file - (if (common:simple-file-lock lock-file) - (begin - (handle-exceptions - exn - #f - (delete-file* server-file)) - (debug:print-info 0 *default-log-port* "server file " server-file " for " hostport " removed") - (common:simple-file-release-lock lock-file)) - (debug:print-info 0 *default-log-port* "server file " server-file " for " hostport " NOT removed - could not get lock.")) - (debug:print-info 0 *default-log-port* "server file " server-file " for " hostport " NOT removed - dotserver-url("dotserver-url") did not match hostport pattern ("hostport")")))) - ;; no longer care if multiple servers are started by accident. older servers will drop off in time. ;; (define (server:check-if-running areapath) - (let* ((dotserver-url (server:read-dotserver->url areapath))) ;; tdbdat (tasks:open-db))) - (if dotserver-url - (let* ((res (case *transport-type* - ((http)(server:ping-server dotserver-url)) - ;; ((nmsg)(nmsg-transport:ping (tasks:hostinfo-get-interface server) - ))) - (if res - dotserver-url - (begin - (server:remove-dotserver-file areapath ".*") ;; remove stale dotserver - #f))) - #f))) + (let* ((servers (server:get-best (server:get-list areapath)))) + (if (null? servers) + #f + (let loop ((hed (car servers)) + (tal (cdr servers))) + (let ((res (server:check-server hed))) + (if res + res + (if (null? tal) + #f + (loop (car tal)(cdr tal))))))))) + +;; ping the given server +;; +(define (server:check-server server-record) + (let* ((server-url (server:record->url server-record)) + (res (case *transport-type* + ((http)(server:ping server-url)) + ;; ((nmsg)(nmsg-transport:ping (tasks:hostinfo-get-interface server) + ))) + (if res + server-url + #f))) + +(define (server:kill servr) + (match-let (((mod-time hostname port start-time pid) + servr)) + (tasks:kill-server hostname pid))) ;; called in megatest.scm, host-port is string hostname:port ;; ;; NOTE: This is NOT called directly from clients as not all transports support a client running ;; in the same process as the server. ;; (define (server:ping host-port-in #!key (do-exit #f)) (let ((host:port (if (not host-port-in) ;; use read-dotserver to find - (server:read-dotserver->url *toppath*) - (if (number? host-port-in) ;; we were handed a server-id - (let ((srec (tasks:get-server-by-id (db:delay-if-busy (tasks:open-db)) host-port-in))) - ;; (print "srec: " srec " host-port-in: " host-port-in) - (if srec - (conc (vector-ref srec 3) ":" (vector-ref srec 4)) - (conc "no such server-id " host-port-in))) - host-port-in)))) + #f ;; (server:check-if-running *toppath*) + ;; (if (number? host-port-in) ;; we were handed a server-id + ;; (let ((srec (tasks:get-server-by-id (db:delay-if-busy (tasks:open-db)) host-port-in))) + ;; ;; (print "srec: " srec " host-port-in: " host-port-in) + ;; (if srec + ;; (conc (vector-ref srec 3) ":" (vector-ref srec 4)) + ;; (conc "no such server-id " host-port-in))) + host-port-in))) ;; ) (let* ((host-port (if host:port (let ((slst (string-split host:port ":"))) (if (eq? (length slst) 2) (list (car slst)(string->number (cadr slst))) #f)) - #f)) - (toppath (launch:setup))) + #f))) +;; (toppath (launch:setup))) ;; (print "host-port=" host-port) (if (not host-port) (begin (if host-port-in (debug:print 0 *default-log-port* "ERROR: bad host:port")) @@ -367,15 +358,17 @@ (server-dat (http-transport:client-connect iface port)) (login-res (rmt:login-no-auto-client-setup server-dat))) (if (and (list? login-res) (car login-res)) (begin - (print "LOGIN_OK") - (if do-exit (exit 0))) + ;; (print "LOGIN_OK") + (if do-exit (exit 0)) + #t) (begin - (print "LOGIN_FAILED") - (if do-exit (exit 1))))))))) + ;; (print "LOGIN_FAILED") + (if do-exit (exit 1)) + #f))))))) ;; run ping in separate process, safest way in some cases ;; (define (server:ping-server ifaceport) (with-input-from-pipe Index: tasks.scm ================================================================== --- tasks.scm +++ tasks.scm @@ -170,315 +170,24 @@ (define (tasks:hostinfo-get-pubport vec) (vector-ref vec 3)) (define (tasks:hostinfo-get-transport vec) (vector-ref vec 4)) (define (tasks:hostinfo-get-pid vec) (vector-ref vec 5)) (define (tasks:hostinfo-get-hostname vec) (vector-ref vec 6)) -(define (tasks:server-lock-slot mdb run-id) - (tasks:server-clean-out-old-records-for-run-id mdb run-id " tasks:server-lock-slot") - (if (< (tasks:num-in-available-state mdb run-id) 4) - (begin - (tasks:server-set-available mdb run-id) - (thread-sleep! (/ (random 1500) 1000)) ;; (thread-sleep! 2) ;; Try removing this. It may not be needed. - (tasks:server-am-i-the-server? mdb run-id)) - #f)) - -;; register that this server may come online (first to register goes though with the process) -(define (tasks:server-set-available mdb run-id) - (sqlite3:execute - mdb - "INSERT INTO servers (pid,hostname,port,pubport,start_time, priority,state,mt_version,heartbeat, interface,transport,run_id) - VALUES(?, ?, ?, ?, strftime('%s','now'), ?, ?, ?,-1,?, ?, ?);" - (current-process-id) ;; pid - (get-host-name) ;; hostname - -1 ;; port - -1 ;; pubport - (random 1000) ;; priority (used a tiebreaker on get-available) - "available" ;; state - (common:version-signature) ;; mt_version - -1 ;; interface - ;; (conc (server:get-transport)) ;; transport - (conc *transport-type*) ;; transport - run-id - )) - -(define (tasks:num-in-available-state mdb run-id) - (let ((res 0)) - (sqlite3:for-each-row - (lambda (num-in-queue) - (set! res num-in-queue)) - mdb - "SELECT count(id) FROM servers WHERE run_id=? AND state = 'available' AND (strftime('%s','now') - start_time) < 30 ;" - run-id) - res)) - -(define (tasks:num-servers-non-zero-running mdb) - (let ((res 0)) - (sqlite3:for-each-row - (lambda (num-running) - (set! res num-running)) - mdb - "SELECT count(id) FROM servers WHERE run_id != 0 AND state = 'running';") - res)) - -(define (tasks:server-clean-out-old-records-for-run-id mdb run-id tag) - (sqlite3:execute mdb "UPDATE servers SET state=?,heartbeat=strftime('%s','now') WHERE state in ('available','dbprep','shutting-down') AND (strftime('%s','now') - start_time) > 50 AND run_id=?;" - (conc "defunct" tag) run-id)) - -(define (tasks:server-force-clean-running-records-for-run-id mdb run-id tag) - (sqlite3:execute mdb "UPDATE servers SET state=?,heartbeat=strftime('%s','now') WHERE state = 'running' AND run_id=?;" - (conc "defunct" tag) run-id)) - -(define (tasks:server-force-clean-run-record mdb run-id iface port tag) - (sqlite3:execute mdb "UPDATE servers SET state=?,heartbeat=strftime('%s','now') WHERE state = 'running' AND run_id=? AND interface=? AND port=?;" - (conc "defunct" tag) run-id iface port)) - - -;; BB> adding missing func for --list-servers -(define (tasks:server-deregister mdb hostname #!key (pullport #f) (pid #f) (action #f)) ;;pullport pid: pid action: 'delete)) - (if (eq? action 'delete) - (sqlite3:execute mdb "DELETE FROM servers WHERE pid=? AND port=? AND hostname=?;" pid pullport hostname) - (sqlite3:execute mdb "UPDATE servers SET state='defunct', heartbeat=strftime('%s','now') WHERE hostname=? AND pid=?;" - hostname pid))) - -(define (tasks:server-delete-records-for-this-pid mdb tag) - (sqlite3:execute mdb "UPDATE servers SET state=?,heartbeat=strftime('%s','now') WHERE hostname=? AND pid=?;" - (conc "defunct" tag) (get-host-name) (current-process-id))) - -(define (tasks:server-delete-record mdb server-id tag) - (sqlite3:execute mdb "UPDATE servers SET state=?,heartbeat=strftime('%s','now') WHERE id=?;" - (conc "defunct" tag) server-id) - ;; use this opportuntity to clean out records over one month old or over 10 minutes old with port = -1 (i.e. a never used placeholder) - (sqlite3:execute mdb "DELETE FROM servers WHERE state not in ('running','shutting-down','dbprep') AND (strftime('%s','now') - start_time) > 2628000;") - (sqlite3:execute mdb "DELETE FROM servers WHERE state like 'defunct%' AND port=-1 AND (strftime('%s','now') - start_time) > 600;") - ) - -(define (tasks:server-set-state! mdb server-id state) - (sqlite3:execute mdb "UPDATE servers SET state=?,heartbeat=strftime('%s','now') WHERE id=?;" state server-id)) - -(define (tasks:server-set-interface-port mdb server-id interface port) - (sqlite3:execute mdb "UPDATE servers SET interface=?,port=?,heartbeat=strftime('%s','now') WHERE id=?;" interface port server-id)) - -;; Get random port not used in long time -;; -(define (tasks:server-get-next-port mdb) - (let* ((lownum 30000) - (highnum 64000) - (used-ports '()) - (get-rand-port (lambda () - (+ lownum (random (- highnum lownum))))) - (port-param (if (and (args:get-arg "-port") - (string->number (args:get-arg "-port"))) - (string->number (args:get-arg "-port")) - #f)) - ;; (config-port (if (and (config-lookup *configdat* "server" "port") - ;; (string->number (config-lookup *configdat* "server" "port"))) - ;; (string->number (config-lookup *configdat* "server" "port")) - ;; #f)) - ) - (sqlite3:for-each-row - (lambda (port) - (set! used-ports (cons port used-ports))) - mdb - "SELECT port FROM servers;") - (cond - ((and port-param res) (if (> res port-param) res port-param)) - (port-param port-param) - ;; ((and config-port res) (if (> res config-port) res config-port)) - ;; (config-port config-port) - (else - (let loop ((port (get-rand-port)) - (remtries 100)) - (if (member port used-ports) - (if (> remtries 0) - (loop (get-rand-port)(- remtries 1)) - (get-rand-port)) - port)))))) - -(define (tasks:server-am-i-the-server? mdb run-id) - (let* ((all (tasks:server-get-servers-vying-for-run-id mdb run-id)) - (first (if (null? all) - #f;; (begin (debug:print-error 0 *default-log-port* "no servers listed, should be at least one by now.") - ;; (sqlite3:finalize! mdb) - ;; (exit 1)) - (car (db:get-rows all))))) - (if first - (let* ((header (db:get-header all)) - (id (db:get-value-by-header first header "id")) - (hostname (db:get-value-by-header first header "hostname")) - (pid (db:get-value-by-header first header "pid")) - (priority (db:get-value-by-header first header "priority"))) - ;; (debug:print 0 *default-log-port* "INFO: am-i-the-server got record " first) - ;; for now a basic check. add tiebreaking by priority later - (if (and (equal? hostname (get-host-name)) - (equal? pid (current-process-id))) - id - #f)) - #f))) - -;; Use: (db:get-value-by-header (car (db:get-rows dat)) (db:get-header dat) "fieldname") -;; to extract info from the structure returned -;; -(define (tasks:server-get-servers-vying-for-run-id mdb run-id) - (let* ((header (list "id" "hostname" "pid" "interface" "port" "pubport" "state" "run_id" "priority" "start_time")) - (selstr (string-intersperse header ",")) - (res '())) - (sqlite3:for-each-row - (lambda (a . b) - (set! res (cons (apply vector a b) res))) - mdb - (conc "SELECT " selstr " FROM servers WHERE state in ('available','running','dbprep') ORDER BY start_time DESC;") - ) - (vector header res))) - -(define (tasks:get-server mdb run-id #!key (retries 10)) - (let ((res #f) - (best #f)) - (handle-exceptions - exn - (begin - (print-call-chain (current-error-port)) - (debug:print 0 *default-log-port* "WARNING: tasks:get-server db access error.") - (debug:print 0 *default-log-port* " message: " ((condition-property-accessor 'exn 'message) exn)) - (debug:print 0 *default-log-port* " for run " run-id) - (print-call-chain (current-error-port)) - (if (> retries 0) - (begin - (debug:print 0 *default-log-port* " trying call to tasks:get-server again in 10 seconds") - (thread-sleep! 10) - (tasks:get-server mdb run-id retries: (- retries 0))) - (debug:print 0 *default-log-port* "10 tries of tasks:get-server all crashed and burned. Giving up and returning \"no server found\""))) - (sqlite3:for-each-row - (lambda (id interface port pubport transport pid hostname) - (set! res (vector id interface port pubport transport pid hostname))) - mdb - ;; removed: - ;; strftime('%s','now')-heartbeat < 10 AND mt_version = ? - "SELECT id,interface,port,pubport,transport,pid,hostname FROM servers - WHERE run_id=? AND state='running' - ORDER BY start_time DESC LIMIT 1;" run-id) ;; (common:version-signature) run-id) - res))) - -(define (tasks:server-running-or-starting? mdb run-id) - (let ((res #f)) - (sqlite3:for-each-row - (lambda (id) - (set! res id)) - mdb ;; NEEDS dbprep ADDED - "SELECT id FROM servers WHERE run_id=? AND (state = 'running' OR (state = 'dbprep' AND (strftime('%s','now') - start_time) < 60));" run-id) - res)) - -(define (tasks:server-running? mdb run-id) - (let ((res #f)) - (sqlite3:for-each-row - (lambda (id) - (set! res id)) - mdb ;; NEEDS dbprep ADDED - "SELECT id FROM servers WHERE run_id=? AND state = 'running';" run-id) - res)) - (define (tasks:need-server run-id) (equal? (configf:lookup *configdat* "server" "required") "yes")) -;; (maxqry (cdr (rmt:get-max-query-average run-id))) -;; (threshold (string->number (or (configf:lookup *configdat* "server" "server-query-threshold") "10")))) -;; (cond -;; (forced -;; (if (common:low-noise-print 60 run-id "server required is set") -;; (debug:print-info 0 *default-log-port* "Server required is set, starting server for run-id " run-id ".")) -;; #t) -;; ((> maxqry threshold) -;; (if (common:low-noise-print 60 run-id "Max query time execeeded") -;; (debug:print-info 0 *default-log-port* "Max avg query time of " maxqry "ms exceeds limit of " threshold "ms, server needed for run-id " run-id ".")) -;; #t) -;; (else -;; #f)))) - -;; try to start a server and wait for it to be available -;; -(define (tasks:start-and-wait-for-server tdbdat run-id delay-max-tries) - ;; ensure a server is running for this run - (let loop ((server-dat (tasks:get-server (db:delay-if-busy tdbdat) run-id)) - (delay-time 0)) - (if (and (not server-dat) - (< delay-time delay-max-tries)) - (begin - (if (common:low-noise-print 60 "tasks:start-and-wait-for-server" run-id) - (debug:print 0 *default-log-port* "Try starting server for run-id " run-id)) - (thread-sleep! (/ (random 2000) 1000)) - (server:kind-run *toppath*) - (thread-sleep! (min delay-time 1)) - (if (not (or (server:start-attempted? *toppath*) - (server:read-dotserver *toppath*))) ;; no point in trying - (loop (tasks:get-server (db:delay-if-busy tdbdat) run-id)(+ delay-time 1)) - #f)) - #f))) - -(define (tasks:get-all-servers mdb) - (let ((res '())) - (sqlite3:for-each-row - (lambda (id pid hostname interface port pubport start-time priority state mt-version last-update transport run-id) - ;; 0 1 2 3 4 5 6 7 8 9 10 11 12 - (set! res (cons (vector id pid hostname interface port pubport start-time priority state mt-version last-update transport run-id) res))) - mdb - "SELECT id,pid,hostname,interface,port,pubport,start_time,priority,state,mt_version,strftime('%s','now')-heartbeat AS last_update,transport,run_id - FROM servers WHERE state NOT LIKE 'defunct%' ORDER BY start_time DESC;") - res)) - -(define (tasks:get-server-by-id mdb id) - (let ((res #f)) - (sqlite3:for-each-row - (lambda (id pid hostname interface port pubport start-time priority state mt-version last-update transport run-id) - ;; 0 1 2 3 4 5 6 7 8 9 10 11 12 - (set! res (vector id pid hostname interface port pubport start-time priority state mt-version last-update transport run-id))) - mdb - "SELECT id,pid,hostname,interface,port,pubport,start_time,priority,state,mt_version,strftime('%s','now')-heartbeat AS last_update,transport,run_id - FROM servers WHERE id=?;" - id) - res)) - -(define (tasks:get-server-records mdb run-id) - (let ((res '())) - (sqlite3:for-each-row - (lambda (id pid hostname interface port pubport start-time priority state mt-version last-update transport run-id) - ;; 0 1 2 3 4 5 6 7 8 9 10 11 12 - (set! res (cons (vector id pid hostname interface port pubport start-time priority state mt-version last-update transport run-id) res))) - mdb - "SELECT id,pid,hostname,interface,port,pubport,start_time,priority,state,mt_version,strftime('%s','now')-heartbeat AS last_update,transport,run_id - FROM servers WHERE run_id=? AND state NOT LIKE 'defunct%' ORDER BY start_time DESC;" - run-id) - (reverse res))) - ;; no elegance here ... ;; (define (tasks:kill-server hostname pid #!key (kill-switch "")) - (server:remove-dotserver-file *toppath* ".*") (debug:print-info 0 *default-log-port* "Attempting to kill server process " pid " on host " hostname) (setenv "TARGETHOST" hostname) (setenv "TARGETHOST_LOGF" "server-kills.log") (system (conc "nbfake kill "kill-switch" "pid)) (unsetenv "TARGETHOST_LOGF") (unsetenv "TARGETHOST")) -;; look up a server by run-id and send it a kill, also delete the record for that server -;; -(define (tasks:kill-server-run-id run-id #!key (tag "default")) - (let* ((tdbdat (tasks:open-db)) - (sdat (tasks:get-server (db:delay-if-busy tdbdat) run-id))) - (if sdat - (let ((hostname (vector-ref sdat 6)) - (pid (vector-ref sdat 5)) - (server-id (vector-ref sdat 0))) - (tasks:server-set-state! (db:delay-if-busy tdbdat) server-id "killed") - (debug:print-info 0 *default-log-port* "Killing server " server-id " for run-id " run-id " on host " hostname " with pid " pid) - (server:remove-dotserver-file *toppath* ".*") - (tasks:kill-server hostname pid) - (tasks:server-delete-record (db:delay-if-busy tdbdat) server-id tag) ) - (debug:print-info 0 *default-log-port* "No server found for run-id " run-id ", nothing to kill")) - ;; (sqlite3:finalize! tdb) - )) - ;;====================================================================== ;; M O N I T O R S ;;====================================================================== (define (tasks:remove-monitor-record mdb) Index: tests.scm ================================================================== --- tests.scm +++ tests.scm @@ -569,13 +569,41 @@ #< ul.LinkedList { display: block; } /* ul.LinkedList ul { display: none; } */ .HandCursorStyle { cursor: pointer; cursor: hand; } /* For IE */ +th {background-color: #8c8c8c;} +td.test {background-color: #d9dbdd;} +td.PASS {background-color: #347533;} +td.FAIL {background-color: #cc2812;} + + +