sunet/scheme/httpd/surflets/web-server/root/surflets/admin-profiling.scm

216 lines
7.2 KiB
Scheme

(define-structure servlet servlet-interface
(open scheme-with-scsh
servlets
servlet-handler/admin
profiling
handle-fatal-error
httpd-responses
(subset srfi-1 (iota))
define-record-types
locks
let-opt
)
(begin
;; This uses the filesystem heavily to not influence the
;; profiling. Note to get the resulting picture, gnuplot must be
;; installed.
(define-record-type state :state
(make-state file-name file-names-to-delete counter)
state?
(file-name state:file-name set-state:file-name)
(file-names-to-delete state:file-names-to-delete set-state:file-names-to-delete)
(counter state:counter set-state:counter))
(define (state-file-name)
(state:file-name (get-servlet-data)))
(define (state-file-names-to-delete)
(state:file-names-to-delete (get-servlet-data)))
(define (state-counter)
(state:counter (get-servlet-data)))
;; Leave this global. Server is running on a single system.
(define gnuplot "/usr/bin/gnuplot")
(define lock (make-lock))
(define (profile req . maybe-update-text)
(let* ((update-text (:optional maybe-update-text ""))
(input-field (make-text-input-field gnuplot '(@ (size 20))))
(change-button (make-submit-button "Change"))
(new-profile-address (make-address))
(result-address (make-address))
(reset-address (make-address))
(return-address (make-address))
(reset-return-address (make-address))
(req
(send-html/suspend
(lambda (new-url)
`(html
(title "Servlet Administration -- Profiling")
(body (h1 "Serlvet Administration")
(h2 "Profiling")
(font (@ (color "red")) ,update-text)
(p "Currently, there are " ,(state-counter) " profiles saved.")
(ul
(li (URL ,(new-profile-address new-url)
"Create new profile")
(li (URL ,(result-address new-url)
"Show profile results")
(br)
(servlet-form
,new-url
POST
(p "This uses " (var "gnuplot") " that is searched at "
,input-field ,change-button)))
(li (URL ,(reset-address new-url)
"Delete files and reset profile state."))))
(hr)
(URL ,(return-address new-url)
"Return to administration menu leaving files and state untouched.")
(br)
(URL ,(reset-return-address new-url)
"Return to administration menu removing files and reseting state.")
(br)
(URL "/" "Return to main menu."))))))
(bindings (get-bindings req)))
(cond
((returned-via? new-profile-address bindings)
(new-profile req))
((returned-via? result-address bindings)
(if (zero? (state-counter))
(profile req "Now profiles created, currently. Select 'Create new profile' to create one.")
(result req)))
((returned-via? reset-address bindings)
(reset req))
((returned-via? return-address bindings)
(return-to-main-page req))
((returned-via? reset-return-address bindings)
(reset-and-return-to-main-page req))
(else
(let ((new-gnuplot-location (input-field-value input-field bindings)))
(if (and new-gnuplot-location
(file-executable? new-gnuplot-location))
(begin
(set! gnuplot new-gnuplot-location)
(profile req (format #f "Gnuplot is now searched at ~a." gnuplot)))
(profile req "Please enter a file name of an existing executable.")))))))
(define (new-profile req)
(let ((state (get-servlet-data)))
(format #t "profiling...~%")
(obtain-lock lock)
(profile-space (state:file-name state))
(release-lock lock)
(format #t "profile recorded in ~s~%" (state:file-name state))
(set-state:counter state (+ 1 (state:counter state)))
(profile req (format #f "Profile #~a generated" (state:counter state)))))
(define (result req)
(let ((results (profile-results (state-file-name)))
(gnuplot-data-file-name (create-temp-file "servlet-profiling.data"))
(picture-file (string-append (create-temp-file
"../img/servlet-profiling.picture")
".pbm"))
(get-total-bytes (lambda (space-info)
(total-bytes (space-info-total space-info))))
(return-address (make-address))
(reset-return-address (make-address)))
(write-gnuplot-data-file gnuplot-data-file-name
get-total-bytes
results)
(let ((status
(run (,gnuplot -)
(<< ,(format #f "set terminal pbm color
set output '~a'
set size 0.7,0.7
plot '~a' title 'Servlet Profiling ~a' with lines"
picture-file
gnuplot-data-file-name
(format-date "~c" (date))
)))))
(delete-file gnuplot-data-file-name)
(add-file-name-to-delete! picture-file)
(let* ((req (show-results status picture-file get-total-bytes results
return-address reset-return-address))
(bindings (get-bindings req)))
(cond
((returned-via? return-address bindings)
(profile req "You may continue to make profiles."))
((returned-via? reset-return-address bindings)
(reset-and-return-to-main-page req))
(else
(error "unexpected return address")))))))
(define (show-results status picture-file get-total-bytes results
return-address reset-return-address)
(send-html/suspend
(lambda (new-url)
`(html
(title "Servlet Administration -- Profiling Results")
(body
(h1 "Servlet-Administration")
(h2 "Profiling Results")
(h3 "Picture")
,(if (zero? status)
`(image (@ (src ,picture-file)))
`(p "An error occured while generating the profiling results picture."
(br)
"Are you sure, you have " (q "gnuplot")
" installed at " (q ,gnuplot) "?"))
(hr)
(h3 "Data")
(table
(tr (th "#") (th "Total bytes occupied"))
,@(map (lambda (num space-info)
`(tr (td ,(+ 1 num))
(td ,(get-total-bytes space-info))))
(iota (length results))
results))
(hr)
(p (URL ,(return-address new-url) "Return to previous page") (br)
(URL ,(reset-return-address new-url)
"Delete files, reset state and return to main menu.")))))))
(define (reset req)
(reset-profiling-state!)
(profile req "Profiling state reseted."))
(define (add-file-name-to-delete! file-name)
(let ((state (get-servlet-data)))
(set-state:file-names-to-delete
state
(cons file-name
(state:file-names-to-delete state)))))
(define (delete-files state)
(let ((file-names-to-delete (state:file-names-to-delete state)))
(if file-names-to-delete
(for-each delete-filesys-object file-names-to-delete))))
(define (reset-profiling-state!)
(let ((state (get-servlet-data)))
(set-state:counter state 0)
(delete-files state)
(set-state:file-name state
(absolute-file-name (create-temp-file "servlet-profiling")))
(set-state:file-names-to-delete state
(list (state:file-name state)))))
(define (reset-and-return-to-main-page req)
;; Overhead included :-|
(reset-profiling-state!)
(delete-files (get-servlet-data))
(return-to-main-page req))
(define (return-to-main-page req)
(send/finish (make-error-response (status-code moved-perm) req
"admin.scm" "admin.scm")))
(define (main req)
;; We'll fill this out soon.
(set-servlet-data! (make-state #f #f 0))
(reset-profiling-state!)
(profile req))
))