From 36fdca821380b5500fcfacab245b9609a58e65f8 Mon Sep 17 00:00:00 2001 From: Steve Shaw Date: Tue, 12 Oct 2021 18:49:57 +0100 Subject: [PATCH 1/3] Add CLI to Web Service with SQLite repository --- hammerdbcli | 2 +- hammerdbws | 31 +- modules/{rest-1.3.1.tm => rest-1.4.tm} | 65 +- modules/tclreadline-1.2.tm | 5 + modules/wapp-1.0.tm | 98 +- modules/xtprof-1.0.tm | 54 +- src/generic/gencli.tcl | 164 ---- src/generic/genhelp.tcl | 291 ++++++ src/generic/gentcws.tcl | 111 +++ src/generic/genws.tcl | 1186 ++++++++++++++++-------- 10 files changed, 1408 insertions(+), 599 deletions(-) rename modules/{rest-1.3.1.tm => rest-1.4.tm} (96%) create mode 100755 src/generic/genhelp.tcl create mode 100755 src/generic/gentcws.tcl diff --git a/hammerdbcli b/hammerdbcli index 2c340da9..a1aa2175 100755 --- a/hammerdbcli +++ b/hammerdbcli @@ -62,7 +62,7 @@ for { set modcount 0 } { $modcount < [llength $modulelist] } { incr modcount } { } } -append loadlist { genvu.tcl gentpcc.tcl gentpch.tcl gengen.tcl genxml.tcl genmodes.tcl gentccmn.tcl gentccli.tcl geninitcli.tcl gencli.tcl genstep.tcl } +append loadlist { genvu.tcl gentpcc.tcl gentpch.tcl gengen.tcl genxml.tcl genmodes.tcl gentccmn.tcl gentccli.tcl geninitcli.tcl gencli.tcl genhelp.tcl genstep.tcl } for { set loadcount 0 } { $loadcount < [llength $loadlist] } { incr loadcount } { set f [lindex $loadlist $loadcount] set loadtext $f diff --git a/hammerdbws b/hammerdbws index 007a2bcb..f86f07d5 100755 --- a/hammerdbws +++ b/hammerdbws @@ -32,7 +32,26 @@ puts "Type \"help\" for a list of commands" set UserDefaultDir [ file dirname [ info script ] ] ::tcl::tm::path add "$UserDefaultDir/modules" -append modulelist { Thread msgcat sqlite3 xml wapp huddle } +namespace eval autostart { + set autostartap "false" + if {$argc == 0} { ; } else { + if {$argc != 2 || [lindex $argv 0] != "auto" } { +puts {Usage: hammerdbws [ auto [ script_to_autoload.tcl ] ]} +exit + } else { + set autostartap "true" + set autoloadscript [lindex $argv 1] +if { [ file exists $autoloadscript ] && [ file isfile $autoloadscript ] && [ file extension $autoloadscript ] eq ".tcl" } { +;# autostart selected and tcl file exists + } else { +puts {Usage: hammerdbws [ auto [ script_to_autoload.tcl ] ]} +exit + } + } + } +} + +append modulelist { Thread msgcat sqlite3 xml comm tclreadline task wapp rest huddle } for { set modcount 0 } { $modcount < [llength $modulelist] } { incr modcount } { set m [lindex $modulelist $modcount] set loadtext $m @@ -43,7 +62,7 @@ for { set modcount 0 } { $modcount < [llength $modulelist] } { incr modcount } { } } -append loadlist { genvu.tcl gentpcc.tcl gentpch.tcl gengen.tcl genxml.tcl geninitws.tcl genws.tcl } +append loadlist { genvu.tcl gentpcc.tcl gentpch.tcl gengen.tcl genxml.tcl gentccmn.tcl gentcws.tcl geninitws.tcl genws.tcl genhelp.tcl } for { set loadcount 0 } { $loadcount < [llength $loadlist] } { incr loadcount } { set f [lindex $loadlist $loadcount] set loadtext $f @@ -61,4 +80,10 @@ for { set dbsrccount 0 } { $dbsrccount < [llength $dbsrclist] } { incr dbsrccoun puts stderr "Error loading database source files/$f" } } -start_webservice +if { $autostart::autostartap == "true" } { + start_webservice -nowait + source $autostart::autoloadscript + } else { + start_webservice -nowait + TclReadLine::interactws + } diff --git a/modules/rest-1.3.1.tm b/modules/rest-1.4.tm similarity index 96% rename from modules/rest-1.3.1.tm rename to modules/rest-1.4.tm index 59095712..bc9b1967 100755 --- a/modules/rest-1.3.1.tm +++ b/modules/rest-1.4.tm @@ -3,8 +3,6 @@ # A framework for RESTful web services # # Copyright (c) 2009 Aaron Faupell -# -# RCS: @(#) $Id: rest.tcl,v 1.7 2009/10/14 16:28:18 afaupell Exp $ package require Tcl 8.5 package require http 2.7 @@ -12,7 +10,7 @@ package require json #package require tdom #package require base64 -package provide rest 1.3.1 +package provide rest 1.4 namespace eval ::rest { namespace export create_interface parameters parse_opts save \ @@ -74,7 +72,7 @@ proc ::rest::simple {url query args} { } else { set error_body 0 } - + set result [::rest::_call {} $headers $url $query $body $error_body] # if a format was specified then convert the data, but dont do any auto formatting @@ -92,11 +90,33 @@ interp alias {} ::rest::patch {} ::rest::simple interp alias {} ::rest::post {} ::rest::simple interp alias {} ::rest::put {} ::rest::simple +proc ::rest::CheckLevel {loc} { +#if called with autoload script frame level needs to be -2 at first command +#if called from prompt frame level needs to be -4 +#This proc checks first call and hard codes frame level +#Incorrect level gives unable to determine rest::simple method +global flevel +if {![info exists flevel]} { +if {[dict get $loc cmd] eq {source $autostart::autoloadscript}} { + set flevel -2 + } else { + set flevel -4 + } + } +return $flevel +} + proc ::rest::DetermineMethod {cv} { + global flevel upvar 1 $cv config if {[dict exists $config method]} return - - set loc [info frame -2] +if { ![info exists flevel] } { + set loc [info frame -4] +set loclev [ ::rest::CheckLevel $loc ] + set loc [info frame $loclev] + } else { +set loc [info frame $flevel] + } if {![dict exists $loc cmd]} { return -code error "Unable to determine rest::simple method in the current context ([dict get $loc type]). Please specify it explicitly." } @@ -109,7 +129,7 @@ proc ::rest::DetermineMethod {cv} { # TODO: Quoted literal. regexp {^([^ ]+).*$} $cmd -> cmd } - set cmd [namespace tail $cmd] + set cmd [namespace tail $cmd] if {$cmd eq "simple"} { set cmd get } if {$cmd ni {get delete head post put patch}} { return -code error "Unable to determine rest::simple method, found \"$cmd\". Please specify it explicitly." @@ -134,7 +154,7 @@ proc ::rest::DetermineMethod {cv} { # proc ::rest::create_interface {name} { upvar $name in - + # check if any defined calls have https urls and automatically load and register tls #if {[catch {package present tls}]} { # foreach x [array names in] { @@ -160,7 +180,7 @@ proc ::rest::create_interface {name} { if {[dict exists $config content-type]} { dict set config headers content-type [dict get $config content-type] } - + lappend proc "set config \{$config\}" lappend proc "set headers \{\}" @@ -192,8 +212,13 @@ proc ::rest::create_interface {name} { } elseif {[string match mime_multi* [lindex [dict get $config body] 0]]} { lappend proc {if {$body == ""} { return -code error "wrong # args: should be \"[lindex [info level 0] 0] ?options? string\"" }} lappend proc {set b [::rest::mime_multipart body $body]} - lappend proc {dict set config headers content-type "multipart/related; boundary=$b"} - } + if {[regexp {/(.+)$} [lindex [dict get $config body] 0] dummy match]} { + set content_type "multipart/$match" + } else { + set content_type "multipart/related" + } + lappend proc "dict set config headers content-type \"$content_type; boundary=\$b\"" + } } if {[dict exists $config error-body]} { set error_body [dict get $config error-body] @@ -226,9 +251,9 @@ proc ::rest::create_interface {name} { if {[dict exists $config auth] && [lindex [dict get $config auth] 0] == "sign"} { lappend proc "set query \[::${name}::[lindex [dict get $config auth] 1] \$query]" } - + lappend proc {set query [::http::formatQuery {*}$query]} - + # if this is an async call (has defined a callback) # then end the main proc here by returning the http token # the rest of the normal result processing will be put in a _callback_NAME @@ -242,7 +267,7 @@ proc ::rest::create_interface {name} { } else { lappend proc {set result [::rest::_call {} $headers $url $query $body $error_body]} } - + # process results _transform $name $call $config proc pre_transform result if {[dict exists $config result]} { @@ -275,9 +300,9 @@ proc ::rest::create_interface {name} { variable static_args set static_args $args } - + set ::${name}::static_args {} - + # print the contents of all the dynamic generated procs if {0} { foreach x [info commands ::${name}::*] { @@ -373,7 +398,7 @@ package require tls ::http::register https 443 [list ::tls::socket] } } - + puts $fh "namespace eval ::$name \{\}\n" foreach x {_call _callback parse_opts _addopts substitute _check_result \ format_auto format_raw format_xml format_json format_discard \ @@ -436,7 +461,7 @@ proc ::rest::_call {callback headers url query body error_body} { #puts "_call [list $callback $headers $url $query $body $error_body]" # get the settings from the calling proc upvar config config - + set method GET if {[dict exists $config method]} { set method [string toupper [dict get $config method]] } @@ -595,7 +620,7 @@ proc ::rest::parse_opts {static required optional options} { break } set opt [string range $opt 1 end] - + if {[set i [lsearch $optional $opt:*]] > -1} { lappend query $opt [lindex $args 1] set args [lreplace $args 0 1] @@ -613,7 +638,7 @@ proc ::rest::parse_opts {static required optional options} { return -code error "bad option \"$opt\"" } } - + foreach opt $optional { if {[set i [lsearch -regexp $static ^-?$opt$]] >= 0} { lappend query $opt [lindex $static [expr {$i+1}]] diff --git a/modules/tclreadline-1.2.tm b/modules/tclreadline-1.2.tm index 8b172918..b9ca7452 100755 --- a/modules/tclreadline-1.2.tm +++ b/modules/tclreadline-1.2.tm @@ -776,6 +776,11 @@ proc TclReadLine::restore {} { rename ::_unknown ::unknown } +proc TclReadLine::interactws {} { +variable PROMPT "hammerws>" +TclReadLine::interact +} + proc TclReadLine::interact {} { rename ::unknown ::_unknown rename TclReadLine::unknown ::unknown diff --git a/modules/wapp-1.0.tm b/modules/wapp-1.0.tm index e0d33dcc..07bc6405 100755 --- a/modules/wapp-1.0.tm +++ b/modules/wapp-1.0.tm @@ -137,19 +137,19 @@ proc wappInt-enc-unsafe {txt} { return $txt } proc wappInt-enc-url {s} { - if {[regsub -all {[^-{}@~?=#_.:/a-zA-Z0-9]} $s {[wappInt-%HHchar {&}]} s]} { + if {[regsub -all {[^-{}\\@~?=#_.:/a-zA-Z0-9]} $s {[wappInt-%HHchar {&}]} s]} { set s [subst -novar -noback $s] } - if {[regsub -all {[{}]} $s {[wappInt-%HHchar \\&]} s]} { + if {[regsub -all {[\\{}]} $s {[wappInt-%HHchar \\&]} s]} { set s [subst -novar -noback $s] } return $s } proc wappInt-enc-qp {s} { - if {[regsub -all {[^-{}_.a-zA-Z0-9]} $s {[wappInt-%HHchar {&}]} s]} { + if {[regsub -all {[^-{}\\_.a-zA-Z0-9]} $s {[wappInt-%HHchar {&}]} s]} { set s [subst -novar -noback $s] } - if {[regsub -all {[{}]} $s {[wappInt-%HHchar \\&]} s]} { + if {[regsub -all {[\\{}]} $s {[wappInt-%HHchar \\&]} s]} { set s [subst -novar -noback $s] } return $s @@ -331,7 +331,7 @@ proc wapp-content-security-policy {val} { # proc wapp-safety-check {} { set res {} - foreach p [info procs] { + foreach p [info command] { set ln 0 foreach x [split [info body $p] \n] { incr ln @@ -476,7 +476,11 @@ proc wappInt-http-readable-unsafe {chan} { } elseif {$n==0} { # We have reached the blank line that terminates the header. global argv0 - set a0 [file normalize $argv0] + if {[info exists ::argv0]} { + set a0 [file normalize $argv0] + } else { + set a0 / + } dict set W SCRIPT_FILENAME $a0 dict set W DOCUMENT_ROOT [file dir $a0] if {[wappInt-parse-header $chan]} { @@ -493,7 +497,7 @@ proc wappInt-http-readable-unsafe {chan} { } else { # There is no query content, so handle the request immediately set wapp $W - wappInt-handle-request $chan 0 + wappInt-handle-request $chan } } } else { @@ -505,7 +509,7 @@ proc wappInt-http-readable-unsafe {chan} { if {[dict get $W .toread]<=0} { # Handle the request as soon as all the query content is received set wapp $W - wappInt-handle-request $chan 0 + wappInt-handle-request $chan } } } @@ -611,10 +615,35 @@ proc wappInt-decode-query-params {} { # Invoke application-supplied methods to generate a reply to # a single HTTP request. # -# This routine always runs within [catch], so handle exceptions by -# invoking [error]. -# -proc wappInt-handle-request {chan useCgi} { +# This routine uses the global variable ::wapp and so must not be nested. +# It must run to completion before the next instance runs. If a recursive +# instances of this routine starts while another is running, the the +# recursive instance is added to a queue to be invoked after the current +# instance finishes. Yes, this means that WAPP IS SINGLE THREADED. Only +# a single page rendering instance my be running at a time. There can +# be multiple HTTP requests inbound at once, but only one my be processed +# at a time once the request is full read and parsed. +# +set wappIntPending {} +set wappIntLock 0 +proc wappInt-handle-request {chan} { + global wappIntPending wappIntLock + fileevent $chan readable {} + if {$wappIntLock} { + # Another instance of request is already running, so defer this one + lappend wappIntPending [list wappInt-handle-request $chan] + return + } + set wappIntLock 1 + catch [list wappInt-handle-request-unsafe $chan] + set wappIntLock 0 + if {[llength $wappIntPending]>0} { + # If there are deferred requests, then launch the oldest one + after idle [lindex $wappIntPending 0] + set wappIntPending [lrange $wappIntPending 1 end] + } +} +proc wappInt-handle-request-unsafe {chan} { global wapp dict set wapp .reply {} dict set wapp .mimetype {text/html; charset=utf-8} @@ -691,7 +720,7 @@ proc wappInt-handle-request {chan useCgi} { wappInt-trace set mname [dict get $wapp PATH_HEAD] if {[catch { - if {$mname!="" && [llength [info proc wapp-page-$mname]]>0} { + if {$mname!="" && [llength [info command wapp-page-$mname]]>0} { wapp-page-$mname } else { wapp-default @@ -709,6 +738,7 @@ proc wappInt-handle-request {chan useCgi} { } dict unset wapp .new-cookies } + wapp-before-reply-hook # Transmit the HTTP reply # @@ -766,36 +796,20 @@ proc wappInt-handle-request {chan useCgi} { # proc wapp-before-dispatch-hook {} {return} +# This routine runs after the request-handler dispatch and just +# before the reply is generated. The default implementation is +# a no-op, but applications can override to do validation and security +# checks on the reply, such as verifying that no sensitive information +# such as an API key or password is accidentally included in the +# reply text. +# +proc wapp-before-reply-hook {} {return} + # Process a single CGI request # proc wappInt-handle-cgi-request {} { global wapp env - foreach key { - CONTENT_LENGTH - CONTENT_TYPE - DOCUMENT_ROOT - HTTP_ACCEPT_ENCODING - HTTP_COOKIE - HTTP_HOST - HTTP_REFERER - HTTP_USER_AGENT - HTTPS - PATH_INFO - QUERY_STRING - REMOTE_ADDR - REQUEST_METHOD - REQUEST_URI - REMOTE_USER - SCRIPT_FILENAME - SCRIPT_NAME - SERVER_NAME - SERVER_PORT - SERVER_PROTOCOL - } { - if {[info exists env($key)]} { - dict set wapp $key $env($key) - } - } + foreach key [array names env {[A-Z]*}] {dict set wapp $key $env($key)} set len 0 if {[dict exists $wapp CONTENT_LENGTH]} { set len [dict get $wapp CONTENT_LENGTH] @@ -806,7 +820,7 @@ proc wappInt-handle-cgi-request {} { } dict set wapp WAPP_MODE cgi fconfigure stdout -translation binary - wappInt-handle-request stdout 1 + wappInt-handle-request-unsafe stdout } # Process new text received on an inbound SCGI request @@ -847,7 +861,7 @@ proc wappInt-scgi-readable-unsafe {chan} { # There is no query content, so handle the request immediately dict set W SERVER_ADDR [dict get $W .remove_addr] set wapp $W - wappInt-handle-request $chan 0 + wappInt-handle-request $chan } } else { # If .toread is set, that means we are reading the query content. @@ -859,7 +873,7 @@ proc wappInt-scgi-readable-unsafe {chan} { # Handle the request as soon as all the query content is received dict set W SERVER_ADDR [dict get $W .remove_addr] set wapp $W - wappInt-handle-request $chan 0 + wappInt-handle-request $chan } } } diff --git a/modules/xtprof-1.0.tm b/modules/xtprof-1.0.tm index fc8e475d..fadf6d5a 100755 --- a/modules/xtprof-1.0.tm +++ b/modules/xtprof-1.0.tm @@ -304,6 +304,10 @@ set lev2uniquekeys [ lsort -unique [concat {*}[lmap k1 [dict keys $monitortiming if { ![ string equal "delivery neword ostat payment slev" $lev2uniquekeys ]} { puts "WARNING:Timing data returned values for functions different than expected delivery neword ostat payment slev: $lev2uniquekeys" } + +if ![ tsv::exists webservice wsport ] { +#Not working in webservice mode so open file for writing timing data +set using_webservice "false" set tmpdir [ findtempdir ] if { $tmpdir != "notmpdir" } { if { $xtunique_log_name eq 1 } { @@ -323,6 +327,25 @@ puts $fd "$dbtoreport Hammerdb Time Profile Report @ [clock format [clock second } } else { error "Could not open tempfile for Time Profile Report" + } +} else { +#set local variable using_webservice +set using_webservice "true" +if [catch {package require sqlite3} message ] { +puts "Error loading SQLite : $message" +return + } +set sqlite_db [ tsv::get webservice sqldb ] +if [catch {sqlite3 hdb $sqlite_db} message ] { +puts "Error initializing SQLite database for Job Timings : $message" +return + } else { +catch {hdb timeout 30000} +#hdb eval {PRAGMA foreign_keys=ON} +#Select most recent jobid +unset -nocomplain jobid +set jobid [ hdb eval {select jobid from JOBMAIN order by datetime(timestamp) DESC LIMIT 1} ] + } } set vustoreport [ dict keys $monitortimings ] for { set vutri 0 } { $vutri < [llength $vustoreport] } { incr vutri } { @@ -339,9 +362,23 @@ set sprocorder [ lsort -stride 2 -index 1 -real -decreasing $sprocratio ] for { set so 0 } { $so < [llength $sprocorder] } { incr so } { set sprocorder [ lreplace $sprocorder [ expr $so + 1 ] [ expr $so + 1 ] ] } - puts $fd "+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+" - puts $fd [format ">>>>> VIRTUAL USER %s : ELAPSED TIME : %.0fms" $vutr [dict get $monitortimings $vutr [lindex $sprocorder 1] elapsed]] -foreach sproc $sprocorder { +if { $using_webservice } { + foreach sproc $sprocorder { +#DEBUG insert into JOBTIMINGS TABLE +#puts [ subst {INSERT INTO JOBTIMING(jobid,vu,procname,calls,min,avg,max,total,p99,p95,p50,sd,ratio,summary,elapsed) VALUES($jobid,$vutr,[format "%s" [ string toupper $sproc]],[format "%d" [dict get $monitortimings $vutr $sproc calls]],[format "%.3f" [dict get $monitortimings $vutr $sproc min]],[format "%.3f" [dict get $monitortimings $vutr $sproc avgms]],[format "%.3f" [dict get $monitortimings $vutr $sproc max]],[format "%.3f" [dict get $monitortimings $vutr $sproc totalms]],[format "%.3f" [dict get $monitortimings $vutr $sproc p99]],[format "%.3f" [dict get $monitortimings $vutr $sproc p95]],[format "%.3f" [dict get $monitortimings $vutr $sproc p50]],[format "%.3f" [dict get $monitortimings $vutr $sproc sd]],[format "%.3f" [dict get $monitortimings $vutr $sproc ratio] 37],0,[dict get $monitortimings $vutr [lindex $sprocorder 1] elapsed])} ] +#Insert XTprof timing data into JobTiming table for each Virtual User +hdb eval [ subst {INSERT INTO JOBTIMING(jobid,vu,procname,calls,min_ms,avg_ms,max_ms,total_ms,p99_ms,p95_ms,p50_ms,sd,ratio_pct,summary,elapsed_ms) VALUES('$jobid',$vutr,'[format "%s" [ string toupper $sproc]]',[format "%d" [dict get $monitortimings $vutr $sproc calls]],[format "%.3f" [dict get $monitortimings $vutr $sproc min]],[format "%.3f" [dict get $monitortimings $vutr $sproc avgms]],[format "%.3f" [dict get $monitortimings $vutr $sproc max]],[format "%.3f" [dict get $monitortimings $vutr $sproc totalms]],[format "%.3f" [dict get $monitortimings $vutr $sproc p99]],[format "%.3f" [dict get $monitortimings $vutr $sproc p95]],[format "%.3f" [dict get $monitortimings $vutr $sproc p50]],[format "%.3f" [dict get $monitortimings $vutr $sproc sd]],[format "%.3f" [dict get $monitortimings $vutr $sproc ratio] 37],0,[dict get $monitortimings $vutr [lindex $sprocorder 1] elapsed])} ] +#Add the timings to a list of timings for the same stored proc for all virtual users +#At this point [dict get $monitortimings $vutr $sproc clickslist] will return all unsorted data points for vuser $vutr for stored proc $sproc +#To record all individual data points for a virtual user write the output of this command to a file +#Preceed with {*} to expand the list into individual space separated values +#The msperclick per user is in [ dict get $clicktimings $vutr ] clicks need to be multiplied by this value for timings + lappend $sproc-clickslist {*}[dict get $monitortimings $vutr $sproc clickslist] + } + } else { + puts $fd "+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+" + puts $fd [format ">>>>> VIRTUAL USER %s : ELAPSED TIME : %.0fms" $vutr [dict get $monitortimings $vutr [lindex $sprocorder 1] elapsed]] + foreach sproc $sprocorder { puts $fd [format ">>>>> PROC: %s" [ string toupper $sproc]] puts -nonewline $fd [format "CALLS: %d\t" [dict get $monitortimings $vutr $sproc calls]] puts -nonewline $fd [format "MIN: %.3fms\t" [dict get $monitortimings $vutr $sproc min]] @@ -353,13 +390,13 @@ foreach sproc $sprocorder { puts -nonewline $fd [format "P50: %.3fms\t" [dict get $monitortimings $vutr $sproc p50]] puts -nonewline $fd [format "SD: %.3f\t" [dict get $monitortimings $vutr $sproc sd]] puts $fd [format "RATIO: %.3f%c" [dict get $monitortimings $vutr $sproc ratio] 37] - #Add the timings to a list of timings for the same stored proc for all virtual users #At this point [dict get $monitortimings $vutr $sproc clickslist] will return all unsorted data points for vuser $vutr for stored proc $sproc #To record all individual data points for a virtual user write the output of this command to a file #Preceed with {*} to expand the list into individual space separated values #The msperclick per user is in [ dict get $clicktimings $vutr ] clicks need to be multiplied by this value for timings lappend $sproc-clickslist {*}[dict get $monitortimings $vutr $sproc clickslist] + } } } #Calculate Summary for All Virtual Users @@ -401,6 +438,12 @@ set sprocorder [ lsort -stride 2 -index 1 -real -decreasing $sprocratio ] for { set so 0 } { $so < [llength $sprocorder] } { incr so } { set sprocorder [ lreplace $sprocorder [ expr $so + 1 ] [ expr $so + 1 ] ] } +if { $using_webservice } { +foreach sproc $sprocorder { +#Insert summary timings into JOBTIMING table, summary identified by summary column eq 1 +hdb eval [ subst {INSERT INTO JOBTIMING(jobid,vu,procname,calls,min_ms,avg_ms,max_ms,total_ms,p99_ms,p95_ms,p50_ms,sd,ratio_pct,summary,elapsed_ms) VALUES('$jobid',[llength $vustoreport],'[format "%s" [ string toupper $sproc]]',[format "%d" [dict get $monitortimings $vutr $sproc calls]],[format "%.3f" [dict get $monitortimings $vutr $sproc min]],[format "%.3f" [dict get $monitortimings $vutr $sproc avgms]],[format "%.3f" [dict get $monitortimings $vutr $sproc max]],[format "%.3f" [dict get $monitortimings $vutr $sproc totalms]],[format "%.3f" [dict get $monitortimings $vutr $sproc p99]],[format "%.3f" [dict get $monitortimings $vutr $sproc p95]],[format "%.3f" [dict get $monitortimings $vutr $sproc p50]],[format "%.3f" [dict get $monitortimings $vutr $sproc sd]],[format "%.3f" [dict get $monitortimings $vutr $sproc ratio] 37],1,$medianendms)} ] + } + } else { puts $fd "+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+" puts $fd [format ">>>>> SUMMARY OF [llength $vustoreport] ACTIVE VIRTUAL USERS : MEDIAN ELAPSED TIME : %.0fms" $medianendms] foreach sproc $sprocorder { @@ -415,9 +458,10 @@ foreach sproc $sprocorder { puts -nonewline $fd [format "P50: %.3fms\t" [dict get $sumtimings $sproc p50]] puts -nonewline $fd [format "SD: %.3f\t" [dict get $sumtimings $sproc sd]] puts $fd [format "RATIO: %.3f%c" [dict get $sumtimings $sproc ratio] 37] -} + } puts $fd "+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+" close $fd + } } proc xttimeprofdump {myposition} { diff --git a/src/generic/gencli.tcl b/src/generic/gencli.tcl index 6fbcbb08..45264331 100755 --- a/src/generic/gencli.tcl +++ b/src/generic/gencli.tcl @@ -1411,167 +1411,3 @@ puts "Unknown tcset option" puts {Usage: tcset [refreshrate|logtotemp|unique|timestamps] value} } }}} - -proc help { args } { -global hdb_version -if {[ llength $args ] != 1} { - puts "HammerDB $hdb_version CLI Help Index\n -Type \"help command\" for more details on specific commands below" - puts { - buildschema - clearscript - customscript - datagenrun - dbset - dgset - diset - distributescript - librarycheck - loadscript - print - quit - runtimer - steprun - switchmode - tcset - tcstart - tcstatus - tcstop - vucomplete - vucreate - vudestroy - vurun - vuset - vustatus - waittocomplete - } -} else { -set option [ lindex [ split $args ] 0 ] -set ind [ lsearch {print librarycheck dbset diset distributescript buildschema vuset vucreate vurun vudestroy vustatus vucomplete quit loadscript clearscript customscript dgset datagenrun steprun switchmode runtimer waittocomplete tcset tcstart tcstatus tcstop} $option ] -if { $ind eq -1 } { -putscli "Error: invalid option" -putscli {Usage: help [print|librarycheck|dbset|diset|distributescript|buildschema|vuset|vucreate|vurun|vudestroy|vustatus|vucomplete|quit|loadscript|clearscript|customscript|dgset|datagenrun|steprun|switchmode|runtimer|waittocomplete|tcset|tcstart|tcstatus|tcstop]} -return -} else { -switch $option { -print { -putscli {print - Usage: print [db|bm|dict|script|vuconf|vucreated|vustatus|datagen|tcconf]} -putscli "prints the current configuration: -db: database -bm: benchmark -dict: the dictionary for the current database ie all active variables -script: the loaded script -vuconf: the virtual user configuration -vucreated: the number of virtual users created -vustatus: the status of the virtual users -datagen: the datagen configuration -tcconf: the transaction counter configuration" -} -quit { -putscli "quit - Usage: quit" -putscli "Shuts down the HammerDB CLI." -} -librarycheck { -putscli "librarycheck - Usage: librarycheck" -putscli "Attempts to load the vendor provided 3rd party library for all databases and reports whether the attempt was successful." -} -dbset { -putscli "dbset - Usage: dbset \[db|bm\] value" -putscli "Sets the database (db) or benchmark (bm). Equivalent to the Benchmark Menu in the graphical interface. Database value is set by the database prefix in the XML configuration." -} -diset { -putscli "diset - Usage: diset dict key value" -putscli "Set the dictionary variables for the current database. Equivalent to the Schema Build and Driver Options windows in the graphical interface. Use \"print dict\" to see what these variables are and diset to change: -Example: -hammerdb>diset tpcc count_ware 10 -Changed tpcc:count_ware from 1 to 10 for Oracle" -} -distributescript { -putscli "distributescript" -putscli "In Primary mode distributes the script loaded by Primary to the connected Replicas." -} -steprun { -putscli "steprun - Usage: steprun" -putscli "Automatically switches into Primary mode, creates and connects the multiple Replicas defined in config/steps.xml and starts the Primary and Replica Virtual Users at the defined intervals creating a step workload. Both Primary and Replicas will exit on completion." -} -switchmode { -putscli "switchmode - Usage: switchmode \[mode\] ?PrimaryID? ?PrimaryHostname?" -putscli "Equivalent to the Mode option in the graphical interface. Mode to switch to must be one of Local, Primary or Replica. If Mode is Replica then the ID and Hostname of the Primary to connect to must be given." -} -buildschema { -putscli "buildschema - Usage: buildschema" -putscli "Runs the schema build for the database and benchmark selected with dbset and variables selected with diset. Equivalent to the Build command in the graphical interface." -} -vuset { -putscli "vuset - Usage: vuset \[vu|delay|repeat|iterations|showoutput|logtotemp|unique|nobuff|timestamps\]" -putscli "Configure the virtual user options. Equivalent to the Virtual User Options window in the graphical interface." -} -vucreate { -putscli "vucreate - Usage: vucreate" -putscli "Create the virtual users. Equivalent to the Virtual User Create option in the graphical interface. Use \"print vucreated\" to see the number created, vustatus to see the status and vucomplete to see whether all active virtual users have finished the workload. A script must be loaded before virtual users can be created." -} -vurun { -putscli "vurun - Usage: vurun" -putscli "Send the loaded script to the created virtual users for execution. Equivalent to the Run command in the graphical interface." -} -vudestroy { -putscli "vudestroy - Usage: vudestroy" -putscli "Destroy the virtual users. Equivalent to the Destroy Virtual Users button in the graphical interface that replaces the Create Virtual Users button after virtual user creation." -} -vustatus { -putscli "vustatus - Usage: vustatus" -putscli "Show the status of virtual users. Status will be \"WAIT IDLE\" for virtual users that are created but not running a workload,\"RUNNING\" for virtual users that are running a workload, \"FINISH SUCCESS\" for virtual users that completed successfully or \"FINISH FAILED\" for virtual users that encountered an error." -} -vucomplete { -putscli "vucomplete - Usage: vucomplete" -putscli "Returns \"true\" or \"false\" depending on whether all virtual users that started a workload have completed regardless of whether the status was \"FINISH SUCCESS\" or \"FINISH FAILED\"." -} -loadscript { -putscli "loadscript - Usage: loadscript" -putscli "Load the script for the database and benchmark set with dbset and the dictionary variables set with diset. Use \"print script\" to see the script that is loaded. Equivalent to loading a Driver Script in the Script Editor window in the graphical interface." -} -clearscript { -putscli "clearscript - Usage: clearscript" -putscli "Clears the script. Equivalent to the \"Clear the Screen\" button in the graphical interface." -} -customscript { -putscli "customscript - Usage: customscript scriptname.tcl" -putscli "Load an external script. Equivalent to the \"Open Existing File\" button in the graphical interface." -} -dgset { -putscli "dgset - Usage: dgset \[vu|ware|directory\]" -putscli "Set the Datagen options. Equivalent to the Datagen Options dialog in the graphical interface." -} -datagenrun { -putscli "datagenrun - Usage: datagenrun" -putscli "Run Data Generation. Equivalent to the Generate option in the graphical interface." -} -runtimer { -putscli "runtimer - Usage: runtimer seconds" -putscli "Helper routine to run a timer in the main hammerdbcli thread to keep it busy for a period of time whilst the virtual users run a workload. The timer will return when vucomplete returns true or the timer reaches the seconds value. Usually followed by vudestroy." - -} -waittocomplete { -putscli "waittocomplete - Usage: waittocomplete" -putscli "Helper routine to enable the main hammerdbcli thread to keep it busy until vucomplete is detected. When vucomplete is detected exit is called causing all virtual users and the main hammerdblci thread to terminate. Often used when calling hammerdb from external scripting commands." -} -tcset { -putscli "tcset - Usage: tcset \[refreshrate|logtotemp|unique|timestamps\]" -putscli "Configure the transaction counter options. Equivalent to the Transaction Counter Options window in the graphical interface." -} -tcstart { -putscli "tcstart - Usage: tcstart" -putscli "Starts the Transaction Counter." -} -tcstatus { -putscli "status - Usage: tcstatus" -putscli "Checks the status of the Transaction Counter." -} -tcstop { -putscli "tcstop - Usage: tcstop" -putscli "Stops the Transaction Counter." -} -} -} -} -} diff --git a/src/generic/genhelp.tcl b/src/generic/genhelp.tcl new file mode 100755 index 00000000..91d3101f --- /dev/null +++ b/src/generic/genhelp.tcl @@ -0,0 +1,291 @@ +proc help { args } { +#Modified help procedure for both CLI and Web Service. +global hdb_version +if [ llength [ info commands wapp-default ]] { set wsmode 1 } else { set wsmode 0 } +if $wsmode { set helpbanner "HammerDB $hdb_version WS Help Index\n +Type \"help command\" for more details on specific commands below\n" +set helpcmds [ list buildschema clearscript customscript datagenrun dbset dgset diset jobs librarycheck loadscript print quit runtimer tcset tcstart tcstatus tcstop vucomplete vucreate vudestroy vurun vuset vustatus waittocomplete ] + } else { +set helpbanner "HammerDB $hdb_version CLI Help Index\n +Type \"help command\" for more details on specific commands below\n" +set helpcmds [ list buildschema clearscript customscript datagenrun dbset dgset diset distributescript librarycheck loadscript print quit runtimer steprun switchmode tcset tcstart tcstatus tcstop vucomplete vucreate vudestroy vurun vuset vustatus waittocomplete ] + } +if {[ llength $args ] != 1} { +puts $helpbanner +foreach helpcmd $helpcmds { puts "\t$helpcmd" } +} else { +set option [ lindex [ split $args ] 0 ] +set ind [ lsearch $helpcmds $option ] +if { $ind eq -1 } { +putscli "Error: invalid option" +set helpusage "Usage: help \[[ join $helpcmds "|" ]\]" +putscli $helpusage +return +} else { +switch $option { +jobs { +putscli "jobs - Usage: jobs" +putscli "list all jobs.\n" +putscli {jobs - Usage: jobs [jobid|result|timestamp]} +putscli "jobid: list VU output for jobid." +putscli "result: list result for all jobs." +putscli "timestamp: list starting timestamp for all jobs.\n" +putscli {jobs jobid - Usage: jobs jobid [bm|db|delete|dict|result|status|tcount|timestamp|timing|vuid]} +putscli "bm: list benchmark for jobid." +putscli "db: list database for jobid." +putscli "delete: delete jobid." +putscli "dict: list dict for jobid." +putscli "result: list result for jobid ." +putscli "status: list status for jobid." +putscli "tcount: list count for jobid." +putscli "timestamp: list starting timestamp for jobid." +putscli "timing: list xtprof summary timings for jobid." +putscli "vuid: list VU output for VU with vuid for jobid.\n" +putscli {jobs jobid timing - Usage: jobs jobid timing vuid} +putscli "timing vuid: list xtprof timings for vuid for jobid.\n" +} +print { +putscli {print - Usage: print [db|bm|dict|script|vuconf|vucreated|vustatus|datagen|tcconf]} +putscli "prints the current configuration: +db: database +bm: benchmark +dict: the dictionary for the current database ie all active variables +script: the loaded script +vuconf: the virtual user configuration +vucreated: the number of virtual users created +vustatus: the status of the virtual users +datagen: the datagen configuration +tcconf: the transaction counter configuration" +} +quit { +putscli "quit - Usage: quit" +if $wsmode { +putscli "Shuts down the HammerDB Web Service." + } else { +putscli "Shuts down the HammerDB CLI." + } +} +librarycheck { +putscli "librarycheck - Usage: librarycheck" +putscli "Attempts to load the vendor provided 3rd party library for all databases and reports whether the attempt was successful." +} +dbset { +putscli "dbset - Usage: dbset \[db|bm\] value" +putscli "Sets the database (db) or benchmark (bm). Equivalent to the Benchmark Menu in the graphical interface. Database value is set by the database prefix in the XML configuration." +} +diset { +putscli "diset - Usage: diset dict key value" +putscli "Set the dictionary variables for the current database. Equivalent to the Schema Build and Driver Options windows in the graphical interface. Use \"print dict\" to see what these variables are and diset to change: +Example: +hammerdb>diset tpcc count_ware 10 +Changed tpcc:count_ware from 1 to 10 for Oracle" +} +distributescript { +putscli "distributescript - Usage: distributescript" +putscli "In Primary mode distributes the script loaded by Primary to the connected Replicas." +} +steprun { +putscli "steprun - Usage: steprun" +putscli "Automatically switches into Primary mode, creates and connects the multiple Replicas defined in config/steps.xml and starts the Primary and Replica Virtual Users at the defined intervals creating a step workload. Both Primary and Replicas will exit on completion." +} +switchmode { +putscli "switchmode - Usage: switchmode \[mode\] ?PrimaryID? ?PrimaryHostname?" +putscli "Equivalent to the Mode option in the graphical interface. Mode to switch to must be one of Local, Primary or Replica. If Mode is Replica then the ID and Hostname of the Primary to connect to must be given." +} +buildschema { +putscli "buildschema - Usage: buildschema" +putscli "Runs the schema build for the database and benchmark selected with dbset and variables selected with diset. Equivalent to the Build command in the graphical interface." +} +vuset { +putscli "vuset - Usage: vuset \[vu|delay|repeat|iterations|showoutput|logtotemp|unique|nobuff|timestamps\]" +putscli "Configure the virtual user options. Equivalent to the Virtual User Options window in the graphical interface." +} +vucreate { +putscli "vucreate - Usage: vucreate" +putscli "Create the virtual users. Equivalent to the Virtual User Create option in the graphical interface. Use \"print vucreated\" to see the number created, vustatus to see the status and vucomplete to see whether all active virtual users have finished the workload. A script must be loaded before virtual users can be created." +} +vurun { +putscli "vurun - Usage: vurun" +putscli "Send the loaded script to the created virtual users for execution. Equivalent to the Run command in the graphical interface." +} +vudestroy { +putscli "vudestroy - Usage: vudestroy" +putscli "Destroy the virtual users. Equivalent to the Destroy Virtual Users button in the graphical interface that replaces the Create Virtual Users button after virtual user creation." +} +vustatus { +putscli "vustatus - Usage: vustatus" +putscli "Show the status of virtual users. Status will be \"WAIT IDLE\" for virtual users that are created but not running a workload,\"RUNNING\" for virtual users that are running a workload, \"FINISH SUCCESS\" for virtual users that completed successfully or \"FINISH FAILED\" for virtual users that encountered an error." +} +vucomplete { +putscli "vucomplete - Usage: vucomplete" +putscli "Returns \"true\" or \"false\" depending on whether all virtual users that started a workload have completed regardless of whether the status was \"FINISH SUCCESS\" or \"FINISH FAILED\"." +} +loadscript { +putscli "loadscript - Usage: loadscript" +putscli "Load the script for the database and benchmark set with dbset and the dictionary variables set with diset. Use \"print script\" to see the script that is loaded. Equivalent to loading a Driver Script in the Script Editor window in the graphical interface." +} +clearscript { +putscli "clearscript - Usage: clearscript" +putscli "Clears the script. Equivalent to the \"Clear the Screen\" button in the graphical interface." +} +customscript { +putscli "customscript - Usage: customscript scriptname.tcl" +putscli "Load an external script. Equivalent to the \"Open Existing File\" button in the graphical interface." +} +dgset { +putscli "dgset - Usage: dgset \[vu|ware|directory\]" +putscli "Set the Datagen options. Equivalent to the Datagen Options dialog in the graphical interface." +} +datagenrun { +putscli "datagenrun - Usage: datagenrun" +putscli "Run Data Generation. Equivalent to the Generate option in the graphical interface." +} +runtimer { +putscli "runtimer - Usage: runtimer seconds" +putscli "Helper routine to run a timer in the main hammerdbcli thread to keep it busy for a period of time whilst the virtual users run a workload. The timer will return when vucomplete returns true or the timer reaches the seconds value. Usually followed by vudestroy." + +} +waittocomplete { +putscli "waittocomplete - Usage: waittocomplete" +putscli "Helper routine to enable the main hammerdbcli thread to keep it busy until vucomplete is detected. When vucomplete is detected exit is called causing all virtual users and the main hammerdblci thread to terminate. Often used when calling hammerdb from external scripting commands." +} +tcset { +if $wsmode { +putscli "tcset - Usage: tcset refreshrate seconds" + } else { +putscli "tcset - Usage: tcset \[refreshrate|logtotemp|unique|timestamps\]" + } +putscli "Configure the transaction counter options. Equivalent to the Transaction Counter Options window in the graphical interface." +} +tcstart { +putscli "tcstart - Usage: tcstart" +putscli "Starts the Transaction Counter." +} +tcstatus { +putscli "status - Usage: tcstatus" +putscli "Checks the status of the Transaction Counter." +} +tcstop { +putscli "tcstop - Usage: tcstop" +putscli "Stops the Transaction Counter." +} +} +} +} +} + +proc helpws {} { +global ws_port +set res [rest::get http://localhost:$ws_port/help "" ] +putscli [ strip_html $res ] +} + +proc wapp-page-help {} { + set B [wapp-param BASE_URL] + wapp-trim { + + + + HammerDB Web Service +

HammerDB Web Service

+ + +

HammerDB API

+
GET db: Show the configured database.
+get http://localhost:8080/print?db / get http://localhost:8080/db
+    
+GET bm: Show the configured benchmark. +get http://localhost:8080/print?bm / get http://localhost:8080/bm +{\"benchmark\": \"TPC-C\"} +
+GET dict: Show the dictionary for the current database ie all active variables. +get http://localhost:8080/print?dict / http://localhost:8080/dict +
+GET script: Show the loaded script. +get http://localhost:8080/print?script / http://localhost:8080/script +
+GET vuconf: Show the virtual user configuration. +get http://localhost:8080/print?vuconf / http://localhost:8080/vuconf +
+GET vucreate: Create the virtual users. Equivalent to the Virtual User Create option in the graphical interface. Use vucreated to see the number created, vustatus to see the status and vucomplete to see whether all active virtual users have finished the workload. A script must be loaded before virtual users can be created. +get http://localhost:8080/vucreate +
+GET vucreated: Show the number of virtual users created. +get http://localhost:8080/print?vucreated / get http://localhost:8080/vucreated +
+GET vustatus: Show the status of virtual users, status will be \"WAIT IDLE\" for virtual users that are created but not running a workload,\"RUNNING\" for virtual users that are running a workload, \"FINISH SUCCESS\" for virtual users that completed successfully or \"FINISH FAILED\" for virtual users that encountered an error. +get http://localhost:8080/print?vustatus / get http://localhost:8080/vustatus +
+GET datagen: Show the datagen configuration +get http://localhost:8080/print?datagen / get http://localhost:8080/datagen +
+GET vucomplete: Show if virtual users have completed. returns \"true\" or \"false\" depending on whether all virtual users that started a workload have completed regardless of whether the status was \"FINISH SUCCESS\" or \"FINISH FAILED\". +get http://localhost:8080/vucomplete +
+GET vudestroy: Destroy the virtual users. Equivalent to the Destroy Virtual Users button in the graphical interface that replaces the Create Virtual Users button after virtual user creation. +get http://localhost:8080/vudestroy +
+GET loadscript: Load the script for the database and benchmark set with dbset and the dictionary variables set with diset. Use print?script to see the script that is loaded. Equivalent to loading a Driver Script in the Script Editor window in the graphical interface. Driver script must be set to timed for the script to be loaded. Test scripts should be run in the GUI environment. +get http://localhost:8080/loadscript +
+GET clearscript: Clears the script. Equivalent to the \"Clear the Screen\" button in the graphical interface. +get http://localhost:8080/clearscript +
+GET vurun: Send the loaded script to the created virtual users for execution. Equivalent to the Run command in the graphical interface. Creates a job id associated with all output. +get http://localhost:8080/vurun +
+GET buildschema: Runs the schema build for the database and benchmark selected with dbset and variables selected with diset. Equivalent to the Build command in the graphical interface. Creates a job id associated with all output. +get http://localhost:8080/buildschema +
+GET jobs: Show the job ids, configuration, output, status, results and timings of jobs created by buildschema and vurun. Job output is equivalent to the output viewed in the graphical interface or command line. +get http://localhost:8080/jobs +get http://localhost:8080/jobs?jobid=TEXT +get http://localhost:8080/jobs?result +get http://localhost:8080/jobs?timestamp +get http://localhost:8080/jobs?jobid=TEXT&bm +get http://localhost:8080/jobs?jobid=TEXT&db +get http://localhost:8080/jobs?jobid=TEXT&delete +get http://localhost:8080/jobs?jobid=TEXT&dict +get http://localhost:8080/jobs?jobid=TEXT&result +get http://localhost:8080/jobs?jobid=TEXT&status +get http://localhost:8080/jobs?jobid=TEXT&tcount +get http://localhost:8080/jobs?jobid=TEXT&timestamp +get http://localhost:8080/jobs?jobid=TEXT&timing +get http://localhost:8080/jobs?jobid=TEXT&timing&vuid=INTEGER +get http://localhost:8080/jobs?jobid=TEXT&vu=INTEGER +
+GET librarycheck: Attempts to load the vendor provided 3rd party library for all databases and reports whether the attempt was successful. +get http://localhost:8080/librarycheck +
+GET tcstart: Starts the Transaction Counter. +get http://localhost:8080/tcstart +
+GET tcstop: Stops the Transaction Counter. +get http://localhost:8080/tcstop +
+GET tcstatus: Checks the status of the Transaction Counter. +get http://localhost:8080/tcstatus +
+GET quit: Terminates the webservice and reports message to the console. +get http://localhost:8080/quit +
+POST dbset: Usage: dbset \[db|bm\] value. Sets the database (db) or benchmark (bm). Equivalent to the Benchmark Menu in the graphical interface. Database value is set by the database prefix in the XML configuration. +post http://localhost:8080/dbset { \"db\": \"ora\" } +
+POST diset: Usage: diset dict key value. Set the dictionary variables for the current database. Equivalent to the Schema Build and Driver Options windows in the graphical interface. Use print?dict to see what these variables are and diset to change. +post http://localhost:8080/diset { \"dict\": \"tpcc\", \"key\": \"duration\", \"value\": \"1\" } +
+POST vuset: Usage: vuset \[vu|delay|repeat|iterations|showoutput|logtotemp|unique|nobuff|timestamps\]. Configure the virtual user options. Equivalent to the Virtual User Options window in the graphical interface. +post http://localhost:8080/vuset { \"vu\": \"4\" } +
+POST tcset: Usage: tcset \[refreshrate\] +post http://localhost:8080/tcset { \"refreshrate\": \"20\" } +
+POST dgset: Usage: dgset \[vu|ware|directory\]. Set the Datagen options. Equivalent to the Datagen Options dialog in the graphical interface. +post http://localhost:8080/dgset { \"directory\": \"/home/oracle\" } +
+POST customscript: Load an external script. Equivalent to the \"Open Existing File\" button in the graphical interface. Script must be converted to JSON format before post. +post http://localhost:8080/customscript { \"script\": \"customscript\"} + + +}} diff --git a/src/generic/gentcws.tcl b/src/generic/gentcws.tcl new file mode 100755 index 00000000..e773c2e9 --- /dev/null +++ b/src/generic/gentcws.tcl @@ -0,0 +1,111 @@ +#Generic CLI Transaction Counter +proc .ed_mainFrame.tc.g {args} {;} +proc tce {} {;} + +proc showLCD {number} { +global bm rdbms jobid +if { $bm eq "TPC-C" } { set metric "tpm" } else { set metric "qph" } +#TCOUNT may run without a job so only insert TCOUNT DATA into Database if a job is running +#If no job is running no data is inserted, by using global var once job is created data is inserted +#debug output by uncomment next line +#putscli "$number $rdbms $metric" +if { $jobid != "" } { +hdb eval {INSERT INTO JOBTCOUNT(jobid,counter,metric) VALUES($jobid,$number,$metric)} + } +} + +proc transcount { } { +global tcl_platform masterthread tc_threadID bm rdbms afval tc_flog ws_port +upvar #0 genericdict genericdict +tsv::set application tc_errmsg "" +#Only set interval in web service +dict with genericdict { dict with transaction_counter { +set interval $tc_refresh_rate +}} +set tclist [ thread::names ] +if { [ info exists tc_threadID ] } { +set idx [ lsearch $tclist $tc_threadID ] +if { $idx != -1 } { +dict set jsondict success message "Transaction Counter Stopping" +wapp-2-json 2 $jsondict +return 1 + } +unset -nocomplain tc_threadID +} +tsv::set application timeout 0 +if { ![ info exists bm ] } { set bm "TPC-C" } +if { ![ info exists rdbms ] } { set rdbms "Oracle" } +if { [ info exists afval ] } { + after cancel $afval + unset afval +} + +set old 0 +set tcdata {} +set timedata {} + +#Call Database specific transaction counter +upvar #0 dbdict dbdict +foreach { key } [ dict keys $dbdict ] { +if { [ dict get $dbdict $key name ] eq $rdbms } { +set prefix [ dict get $dbdict $key prefix ] +set command [ concat [subst {tcount_$prefix $bm $interval $masterthread}]] +eval $command +break + } + } +if { [ info exists tc_threadID ] } { +tsv::set application thecount $tc_threadID +dict set jsondict success message "Transaction Counter Thread Started" +wapp-2-json 2 $jsondict +return 0 + } else { +dict set jsondict error message "Transaction Counter Failed to Start" +wapp-2-json 2 $jsondict +return 1 + } +} + +proc ed_kill_transcount {args} { +global _ED ws_port +tsv::set application timeout 1 +dict set jsondict success message "Stopping Transaction Counter" +wapp-2-json 2 $jsondict +return +} + +proc show_tc_errmsg {} { +global jobid +if { ![ info exists jobid ] } { set jobid 0 } +set tc_errmsg [ tsv::get application tc_errmsg ] +if { $tc_errmsg != "" } { +if [catch {set joinedmsg [ join $tc_errmsg ]} message ] { +#error in join show unjoined message +putscli [ subst {{"error": {"message": "Transaction Counter Error: $tc_errmsg"}}} ] +hdb eval {INSERT INTO JOBTCOUNT(jobid,counter,metric) VALUES($jobid,0,$tc_errmsg)} +} else { +#show joined message +putscli [ subst {{"error": {"message": "Transaction Counter Error: $joinedmsg"}}} ] +hdb eval {INSERT INTO JOBTCOUNT(jobid,counter,metric) VALUES($jobid,0,$joinedmsg)} + } + } else { +#message is empty +putscli {"error": {"message": "Transaction Counter Error"}} + } +#error message is always followed by thread release before loop enter +#so remove tc_threadID to prevent false positive on startup +post_kill_transcount_cleanup +} + +proc threadnames_without_tcthread {} { +global tc_threadID +set thlist [ thread::names ] +if { [ info exists tc_threadID ] } { +set idx [ lsearch $thlist $tc_threadID ] +if { $idx != -1 } { +set thlist [ lreplace $thlist $idx $idx ] +return $thlist + } +} +return $thlist +} diff --git a/src/generic/genws.tcl b/src/generic/genws.tcl index b9f3f52b..359e36ab 100755 --- a/src/generic/genws.tcl +++ b/src/generic/genws.tcl @@ -8,6 +8,10 @@ namespace eval ttk { variable currentTheme "black" proc scrollbar { args } { ; } } +proc putscli { output } { +puts $output +TclReadLine::print "\r" + } # Pure Tcl implementation of [string insert] command. proc ::tcl::string::insert {string index insertString} { # Convert end-relative and TIP 176 indexes to simple integers. @@ -41,8 +45,9 @@ proc ::tcl::string::insert {string index insertString} { namespace ensemble configure string -map [dict replace\ [namespace ensemble configure string -map]\ insert ::tcl::string::insert] + proc {} { args } { ; } -proc canvas { args } { ; } +proc canvas { args } { ; } proc pack { args } { ; } proc .ed_mainFrame { args } { ; } proc .ed_mainFrame.notebook { args } { ; } @@ -56,10 +61,28 @@ proc .ed_mainFrame.buttons.test { args } { ; } proc .ed_mainFrame.buttons.runworld { args } { ; } proc ed_lvuser_button { args } { ; } proc .ed_mainFrame.editbuttons.test { args } { ; } +proc .ed_mainFrame.editbuttons.distribute { args } { ; } +proc destroy { args } { ; } +proc ed_edit { args } { ; } +proc applyctexthighlight { args } { ; } proc winfo { args } { return "false" } proc even x {expr {($x % 2) == 0}} proc odd x {expr {($x % 2) != 0}} +proc strip_html { htmlText } { + regsub -all {<[^>]+>} $htmlText "" newText + return $newText + } + +proc bgerror {{message ""}} { + global errorInfo + if {[string match {*threadscreated*} $errorInfo]} { + #puts stderr "Background Error ignored - Threads Killed" + } else { + puts stderr "Unmatched Background Error - $errorInfo" + } +} + proc configtable {} { global vustatus threadscreated virtual_users maxvuser table ntimes thvnum totrun AVUC set AVUC "idle" @@ -80,11 +103,47 @@ dict set vustatus [ expr $vuser + 1 ] "WAIT IDLE" set totrun [ expr $maxvuser * $ntimes ] } +proc find_current_dict {} { +global rdbms bm +upvar #0 dbdict dbdict +foreach { key } [ dict keys $dbdict ] { +set dictname config$key +if { [ dict get $dbdict $key name ] eq $rdbms } { + upvar #0 config$key config$key +set posswkl [ split [ dict get $dbdict $key workloads ]] +set ind [lsearch $posswkl $bm] +if { $ind != -1 } { set wkltoremove [lreplace $posswkl $ind $ind ] +if { [ llength $wkltoremove ] > 1 } { +putscli "Error printing dict format more than 2 workloads" +return +} else { +set bmdct [ string tolower [ join [ split $wkltoremove - ] "" ]] +set tmpdictforpt [ dict remove [ subst \$config$key ] $bmdct ] + } + } +return $tmpdictforpt + } + } +} + +proc jobmain { jobid } { +global rdbms bm +set query [ hdb eval {SELECT COUNT(*) FROM JOBMAIN WHERE JOBID=$jobid} ] +if { $query eq 0 } { +set tmpdictforpt [ find_current_dict ] +hdb eval {INSERT INTO JOBMAIN(jobid,db,bm,jobdict) VALUES($jobid,$rdbms,$bm,$tmpdictforpt)} +return 0 + } else { +return 1 + } + +} + proc runninguser { threadid } { global table threadscreated thvnum inrun AVUC vustatus jobid set AVUC "run" set message [ join " Vuser\ [ expr $thvnum($threadid) + 1]:RUNNING" ] -hdb eval {INSERT INTO JOBS VALUES($jobid, 0, $message)} +hdb eval {INSERT INTO JOBOUTPUT VALUES($jobid, 0, $message)} dict set vustatus [ expr $thvnum($threadid) + 1 ] "RUNNING" } @@ -93,17 +152,17 @@ global vustatus table threadscreated thvnum succ fail totrun totcount inrun AVUC incr totcount if { $result == 0 } { set message [ join " Vuser\ [expr $thvnum($threadid) + 1]:FINISHED SUCCESS" ] -hdb eval {INSERT INTO JOBS VALUES($jobid, 0, $message)} +hdb eval {INSERT INTO JOBOUTPUT VALUES($jobid, 0, $message)} dict set vustatus [ expr $thvnum($threadid) + 1 ] "FINISH SUCCESS" } else { set message [ join " Vuser\ [expr $thvnum($threadid) + 1]:FINISHED FAILED" ] -hdb eval {INSERT INTO JOBS VALUES($jobid, 0, $message)} +hdb eval {INSERT INTO JOBOUTPUT VALUES($jobid, 0, $message)} dict set vustatus [ expr $thvnum($threadid) + 1 ] "FINISH FAILED" } if { $totrun == $totcount } { set AVUC "complete" if { [ info exists inrun ] } { unset inrun } -hdb eval {INSERT INTO JOBS VALUES($jobid, 0, "ALL VIRTUAL USERS COMPLETE")} +hdb eval {INSERT INTO JOBOUTPUT VALUES($jobid, 0, "ALL VIRTUAL USERS COMPLETE")} refreshscript } } @@ -116,7 +175,7 @@ set message "tk_messageBox with unknown message" } else { set message [ lindex $args [expr $messind + 1] ] } -hdb eval {INSERT INTO JOBS VALUES($jobid, 0, $message)} +hdb eval {INSERT INTO JOBOUTPUT VALUES($jobid, 0, $message)} set typeind [ lsearch $args yesno ] if { $typeind eq -1 } { set yesno "false" } else { @@ -131,19 +190,23 @@ return rename myerrorproc _myerrorproc proc myerrorproc { id info } { global threadsbytid jobid +if { ![ info exists jobid ] } { set jobid 0 } if { ![string match {*index*} $info] } { if { [ string length $info ] == 0 } { set message "Warning: a running Virtual User was terminated, any pending output has been discarded" -hdb eval {INSERT INTO JOBS VALUES($jobid, 0, $message)} +putscli [ subst {{"warning": {"message": "a running Virtual User was terminated, any pending output has been discarded"}}} ] +hdb eval {INSERT INTO JOBOUTPUT VALUES($jobid, 0, $message)} } else { if { [ info exists threadsbytid($id) ] } { set vuser [expr $threadsbytid($id) + 1] set info "Error: $info" -hdb eval {INSERT INTO JOBS VALUES($jobid, $vuser, $info)} +putscli [ subst {{"error": {"message": "$info"}}} ] +hdb eval {INSERT INTO JOBOUTPUT VALUES($jobid, $vuser, $info)} } else { if {[string match {*.tc*} $info]} { -set message "Warning: Transaction Counter stopped, connection message not displayed" -hdb eval {INSERT INTO JOBS VALUES($jobid, 0, $message)} +#set message "Warning: Transaction Counter stopped, connection message not displayed" +putscli [ subst {{"error": {"message": "$info"}}} ] +hdb eval {INSERT INTO JOBOUTPUT VALUES($jobid, 0, $info)} } else { ; #Background Error from Virtual User suppressed @@ -158,12 +221,12 @@ proc Log {id msg lastline} { global tids threadsbytid jobid set vuser [expr $threadsbytid($id) + 1] set lastline [ string trimright $lastline ] -hdb eval {INSERT INTO JOBS VALUES($jobid, $vuser, $lastline)} +hdb eval {INSERT INTO JOBOUTPUT VALUES($jobid, $vuser, $lastline)} } rename logtofile _logtofile proc logtofile { id msg } { -puts "Warning: File Logging disabled in web service mode" +puts "Warning: File Logging disabled in web service mode, use jobs command to reretrieve output" } proc ed_edit_clear {} { @@ -298,7 +361,7 @@ return false } }} -proc clearscript {} { +proc clearscript2 {} { global bm _ED set _ED(package) "" if { [ string length $_ED(package) ] eq 0 } { @@ -341,17 +404,6 @@ run_virtual } } -proc vurun {} { - global _ED -if { [ string length $_ED(package) ] > 0 } { - if { [ catch {run_virtual} message ] } { - puts "Error: $message" - } - } else { -puts "Error: There is no workload to run because the Script is empty" - } -} - proc loadtpcc {} { upvar #0 dbdict dbdict global _ED rdbms lprefix @@ -378,11 +430,15 @@ set db_async_scale "false" } if { $db_allwarehouse } { shared_tpcc_functions "allwarehouse" $db_async_scale } } +upvar #0 genericdict genericdict +if {[dict exists $genericdict timeprofile profiler]} { +set profiler [ dict get $genericdict timeprofile profiler] + } +if { $profiler eq "xtprof" } { set profile_func "xttimeprofile" } else { set profile_func "ettimeprofile" } set timep [ lsearch -inline [ dict get [ set $dictname ] tpcc ] *timeprofile ] if { $timep != "" } { set db_timeprofile [ dict get [ set $dictname ] tpcc $timep ] -#Always run ettimeprofile in WS as the extended timeprofile uses a separate log -if { $db_timeprofile } { shared_tpcc_functions "ettimeprofile" "false" } +if { $db_timeprofile } { shared_tpcc_functions $profile_func "false" } } break } @@ -450,263 +506,21 @@ proc wapp-default {} { HammerDB Web Service

HammerDB Web Service

-

See the HammerDB Web Service Environment

+

HammerDB Web Service Environment

+

HammerDB Web Service API

-

HAMMERDB REST/HTTP API

-
GET db: Show the configured database.
-get http://localhost:8080/print?db / get http://localhost:8080/db
-{
-  \"current\": \"Oracle\",
-  \"ora\": \"Oracle\",
-  \"mssqls\": \"MSSQLServer\",
-  \"db2\": \"Db2\",
-  \"mysql\": \"MySQL\",
-  \"pg\": \"PostgreSQL\",
-  \"redis\": \"Redis\"
-}
-    
-GET bm: Show the configured benchmark. -get http://localhost:8080/print?bm / get http://localhost:8080/bm -{\"benchmark\": \"TPC-C\"} -
-GET dict: Show the dictionary for the current database ie all active variables. -get http://localhost:8080/print?dict / http://localhost:8080/dict -{ - \"connection\": { - \"system_user\": \"system\", - \"system_password\": \"manager\", - \"instance\": \"oracle\", - \"rac\": \"0\" - }, - \"tpcc\": { - \"count_ware\": \"1\", - \"num_vu\": \"1\", - \"tpcc_user\": \"tpcc\", - \"tpcc_pass\": \"tpcc\", - \"tpcc_def_tab\": \"tpcctab\", - \"tpcc_ol_tab\": \"tpcctab\", - \"tpcc_def_temp\": \"temp\", - \"partition\": \"false\", - \"hash_clusters\": \"false\", - \"tpcc_tt_compat\": \"false\", - \"total_iterations\": \"1000000\", - \"raiseerror\": \"false\", - \"keyandthink\": \"false\", - \"checkpoint\": \"false\", - \"ora_driver\": \"test\", - \"rampup\": \"2\", - \"duration\": \"5\", - \"allwarehouse\": \"false\", - \"timeprofile\": \"false\" - } -} -
-GET script: Show the loaded script. -get http://localhost:8080/print?script / http://localhost:8080/script -{\"script\": \"#!\/usr\/local\/bin\/tclsh8.6\n#EDITABLE OPTIONS##################################################\nset library Oratcl ;# Oracle OCI Library\nset total_iterations 1000000 ;# Number of transactions before logging off\nset RAISEERROR \\"false\\" ;# Exit script on Oracle error (true or false)\nset KEYANDTHINK \\"false\\" ;# Time for user thinking and keying (true or false)\nset CHECKPOINT \\"false\\" ;# Perform Oracle checkpoint when complete (true or false)\nset rampup 2; # Rampup time in minutes before first snapshot is taken\nset duration 5; # Duration in minutes before second AWR snapshot is taken\nset mode \\"Local\\" ;# HammerDB operational mode\nset timesten \\"false\\" ;# Database is TimesTen\nset systemconnect system\/manager@oracle ;# Oracle connect string for system user\nset connect tpcc\/new_password@oracle ;# Oracle connect string for tpc-c user\n#EDITABLE OPTIONS##################################################\n#LOAD LIBRARIES AND MODULES …. \n\"} -
-GET vuconf: Show the virtual user configuration. -get http://localhost:8080/print?vuconf / http://localhost:8080/vuconf -{ - \"Virtual Users\": \"1\", - \"User Delay(ms)\": \"500\", - \"Repeat Delay(ms)\": \"500\", - \"Iterations\": \"1\", - \"Show Output\": \"1\", - \"Log Output\": \"0\", - \"Unique Log Name\": \"0\", - \"No Log Buffer\": \"0\", - \"Log Timestamps\": \"0\" -} -
-GET vucreate: Create the virtual users. Equivalent to the Virtual User Create option in the graphical interface. Use vucreated to see the number created, vustatus to see the status and vucomplete to see whether all active virtual users have finished the workload. A script must be loaded before virtual users can be created. -get http://localhost:8080/vucreate -{\"success\": {\"message\": \"4 Virtual Users Created\"}} -
-GET vucreated: Show the number of virtual users created. -get http://localhost:8080/print?vucreated / get http://localhost:8080/vucreated -{\"Virtual Users created\": \"10\"} -
-GET vustatus: Show the status of virtual users, status will be \"WAIT IDLE\" for virtual users that are created but not running a workload,\"RUNNING\" for virtual users that are running a workload, \"FINISH SUCCESS\" for virtual users that completed successfully or \"FINISH FAILED\" for virtual users that encountered an error. -get http://localhost:8080/print?vustatus / get http://localhost:8080/vustatus -{\"Virtual User status\": \"1 {WAIT IDLE} 2 {WAIT IDLE} 3 {WAIT IDLE} 4 {WAIT IDLE} 5 {WAIT IDLE} 6 {WAIT IDLE} 7 {WAIT IDLE} 8 {WAIT IDLE} 9 {WAIT IDLE} 10 {WAIT IDLE}\"} -
-GET datagen: Show the datagen configuration -get http://localhost:8080/print?datagen / get http://localhost:8080/datagen -{ - \"schema\": \"TPC-C\", - \"database\": \"Oracle\", - \"warehouses\": \"1\", - \"vu\": \"1\", - \"directory\": \"\/tmp\\"\" -} -
-GET vucomplete: Show if virtual users have completed. returns \"true\" or \"false\" depending on whether all virtual users that started a workload have completed regardless of whether the status was \"FINISH SUCCESS\" or \"FINISH FAILED\". -get http://localhost:8080/vucomplete -{\"Virtual Users complete\": \"true\"} -
-GET vudestroy: Destroy the virtual users. Equivalent to the Destroy Virtual Users button in the graphical interface that replaces the Create Virtual Users button after virtual user creation. -get http://localhost:8080/vudestroy -{\"success\": {\"message\": \"vudestroy success\"}} -
-GET loadscript: Load the script for the database and benchmark set with dbset and the dictionary variables set with diset. Use print?script to see the script that is loaded. Equivalent to loading a Driver Script in the Script Editor window in the graphical interface. Driver script must be set to timed for the script to be loaded. Test scripts should be run in the GUI environment. -get http://localhost:8080/loadscript -{\"success\": {\"message\": \"script loaded\"}} -
-GET clearscript: Clears the script. Equivalent to the \"Clear the Screen\" button in the graphical interface. -get http://localhost:8080/clearscript -{\"success\": {\"message\": \"Script cleared\"}} -
-GET vurun: Send the loaded script to the created virtual users for execution. Equivalent to the Run command in the graphical interface. Creates a job id associated with all output. -get http://localhost:8080/vurun -{\"success\": {\"message\": \"Running Virtual Users: JOBID=5CEFBFE658A103E253238363\"}} -
-GET datagenrun: Run Data Generation. Equivalent to the Generate option in the graphical interface. Not supported in web service. Generate data using GUI or CLI. -
-GET buildschema: Runs the schema build for the database and benchmark selected with dbset and variables selected with diset. Equivalent to the Build command in the graphical interface. Creates a job id associated with all output. -get http://localhost:8080/buildschema -{\"success\": {\"message\": \"Building 6 Warehouses with 4 Virtual Users, 3 active + 1 Monitor VU(dict value num_vu is set to 3): JOBID=5CEFA68458A103E273433333\"}} -
-GET jobs: Show the job ids, output, status and results of jobs created by buildschema and vurun. Job output is equivalent to the output viewed in the graphical interface or command line. -GET http://localhost:8080/jobs: Show all job ids -get http://localhost:8080/jobs -\[ - \"5CEE889958A003E203838313\", - \"5CEFA68458A103E273433333\" -\] -GET http://localhost:8080/jobs?jobid=TEXT: Show output for the specified job id. -get http://localhost:8080/jobs?jobid=5CEFA68458A103E273433333 -\[ - \"0\", - \"Ready to create a 6 Warehouse Oracle TPC-C schema\nin database VULPDB1 under user TPCC in tablespace TPCCTAB?\", - \"0\", - \"Vuser 1:RUNNING\", - \"1\", - \"Monitor Thread\", - \"1\", - \"CREATING TPCC SCHEMA\", -... - \"1\", - \"TPCC SCHEMA COMPLETE\", - \"0\", - \"Vuser 1:FINISHED SUCCESS\", - \"0\", - \"ALL VIRTUAL USERS COMPLETE\" -\] -GET http://localhost:8080/jobs?jobid=TEXT&vu=INTEGER: Show output for the specified job id and virtual user. -get http://localhost:8080/jobs?jobid=5CEFA68458A103E273433333&vu=1 -\[ - \"1\", - \"Monitor Thread\", - \"1\", - \"CREATING TPCC SCHEMA\", - \"1\", - \"CREATING USER tpcc\", - \"1\", - \"CREATING TPCC TABLES\", - \"1\", - \"Loading Item\", - \"1\", - \"Loading Items - 50000\", - \"1\", - \"Loading Items - 100000\", - \"1\", - \"Item done\", - \"1\", - \"Monitoring Workers...\", - \"1\", - \"Workers: 3 Active 0 Done\" -\] -GET http://localhost:8080/jobs?jobid=TEXT&status: Show status for the specified job id. Equivalent to virtual user 0. -get http://localhost:8080/jobs?jobid=5CEFA68458A103E273433333&status -\[ - \"0\", - \"Ready to create a 6 Warehouse Oracle TPC-C schema\nin database VULPDB1 under user TPCC in tablespace TPCCTAB?\", - \"0\", - \"Vuser 1:RUNNING\", - \"0\", - \"Vuser 2:RUNNING\", - \"0\", - \"Vuser 3:RUNNING\", - \"0\", - \"Vuser 4:RUNNING\", - \"0\", - \"Vuser 4:FINISHED SUCCESS\", - \"0\", - \"Vuser 3:FINISHED SUCCESS\", - \"0\", - \"Vuser 2:FINISHED SUCCESS\", - \"0\", - \"Vuser 1:FINISHED SUCCESS\", - \"0\", - \"ALL VIRTUAL USERS COMPLETE\" -\] -GET http://localhost:8080/jobs?jobid=TEXT&result: Show the test result for the specified job id. If job is not a test job such as build job then no result will be reported. -get http://localhost:8080/jobs?jobid=5CEFA68458A103E273433333&result -\[ - \"5CEFA68458A103E273433333\", - \"Jobid has no test result\" -\] -GET http://localhost:8080/jobs?jobid=TEXT&delete: Delete all output for the specified jobid. -get http://localhost:8080/jobs?jobid=5CEFA68458A103E273433333&delete -{\"success\": {\"message\": \"Deleted Jobid 5CEFA68458A103E273433333\"}} -
-GET killws: Terminates the webservice and reports message to the console. -get http://localhost:8080/killws -Shutting down HammerDB Web Service -
-POST dbset: Usage: dbset \[db|bm\] value. Sets the database (db) or benchmark (bm). Equivalent to the Benchmark Menu in the graphical interface. Database value is set by the database prefix in the XML configuration. -set body { \"db\": \"ora\" } -rest::post http://localhost:8080/dbset $body -
-POST diset: Usage: diset dict key value. Set the dictionary variables for the current database. Equivalent to the Schema Build and Driver Options windows in the graphical interface. Use print?dict to see what these variables are and diset to change. -set body { \"dict\": \"tpcc\", \"key\": \"rampup\", \"value\": \"0\" } -rest::post http://localhost:8080/diset $body -set body { \"dict\": \"tpcc\", \"key\": \"duration\", \"value\": \"1\" } -rest::post http://localhost:8080/diset $body -
-POST vuset: Usage: vuset \[vu|delay|repeat|iterations|showoutput|logtotemp|unique|nobuff|timestamps\]. Configure the virtual user options. Equivalent to the Virtual User Options window in the graphical interface. -set body { \"vu\": \"4\" } -rest::post http://localhost:8080/vuset $body -
-POST customscript: Load an external script. Equivalent to the \"Open Existing File\" button in the graphical interface. Script must be converted to JSON format before post as shown in the example: -set customscript \"testscript.tcl\" -set _ED(file) $customscript -if {$_ED(file) == \"\"} {return} - if {!\[file readable $_ED(file)\]} { - puts \"File \[$_ED(file)\] is not readable.\" - return - } -if {\[catch \"open \\"$_ED(file)\\" r\" fd\]} { - puts \"Error while opening $_ED(file): \[$fd\]\" - } else { - set _ED(package) \"\[read $fd\]\" - close $fd - } -set huddleobj \[ huddle compile {string} \"$_ED(package)\" \] -set jsonobj \[ huddle jsondump $huddleobj \] -set body \[ subst { {\"script\": $jsonobj}} \] -set res \[ rest::post http://localhost:8080/customscript $body \] -
-POST dgset: Usage: dgset \[vu|ware|directory\]. Set the Datagen options. Equivalent to the Datagen Options dialog in the graphical interface. -set body { \"directory\": \"/home/oracle\" } -rest::post http://localhost:8080/dgset $body -
-DEBUG -GET dumpdb: Dumps output of the SQLite database to the console. -GET http://localhost:8080/dumpdb -***************DEBUG*************** -5CEE889958A003E203838313 0 {Ready to create a 6 Warehouse Oracle TPC-C schema -in database VULPDB1 under user TPCC in tablespace TPCCTAB?} 5CEE889958A003E203838313 0 {Vuser 1:RUNNING} 5CEE889958A003E203838313 1 {Monitor Thread} 5CEE889958A003E203838313 1 {CREATING TPCC SCHEMA} 5CEE889958A003E203838313 0 {Vuser 2:RUNNING} 5CEE889958A003E203838313 2 {Worker Thread} 5CEE889958A003E203838313 2 {Waiting for Monitor Thread...} 5CEE889958A003E203838313 1 {Error: ORA-12541: TNS:no listener} 5CEE889958A003E203838313 0 {Vuser 1:FINISHED FAILED} 5CEE889958A003E203838313 0 {Vuser 3:RUNNING} 5CEE889958A003E203838313 3 {Worker Thread} 5CEE889958A003E203838313 3 {Waiting for Monitor Thread...} 5CEE889958A003E203838313 0 {Vuser 4:RUNNING} 5CEE889958A003E203838313 4 {Worker Thread} 5CEE889958A003E203838313 4 {Waiting for Monitor Thread...} 5CEE889958A003E203838313 2 {Monitor failed to notify ready state} 5CEE889958A003E203838313 0 {Vuser 2:FINISHED SUCCESS} 5CEE889958A003E203838313 3 {Monitor failed to notify ready state} 5CEE889958A003E203838313 0 {Vuser 3:FINISHED SUCCESS} 5CEE889958A003E203838313 4 {Monitor failed to notify ready state} 5CEE889958A003E203838313 0 {Vuser 4:FINISHED SUCCESS} 5CEE889958A003E203838313 0 {ALL VIRTUAL USERS COMPLETE} -***************DEBUG***************
-
- } } +proc env {} { +global ws_port +set res [rest::get http://localhost:$ws_port/env "" ] +putscli [ strip_html $res ] +} + proc wapp-page-env {} { wapp-allow-xorigin-params wapp-trim { @@ -734,23 +548,15 @@ wapp-2-json 1 $bmdict } proc wapp-page-dict {} { -global rdbms bm wapp -upvar #0 dbdict dbdict -foreach { key } [ dict keys $dbdict ] { -set dictname config$key -if { [ dict get $dbdict $key name ] eq $rdbms } { - upvar #0 config$key config$key -set posswkl [ split [ dict get $dbdict $key workloads ]] -set ind [lsearch $posswkl $bm] -if { $ind != -1 } { set wkltoremove [lreplace $posswkl $ind $ind ] -if { [ llength $wkltoremove ] > 1 } { puts "Error printing dict format more than 2 workloads" } else { -set bmdct [ string tolower [ join [ split $wkltoremove - ] "" ]] -set tmpdictforpt [ dict remove [ subst \$config$key ] $bmdct ] - } - } +global wapp +set tmpdictforpt [ find_current_dict ] wapp-2-json 2 $tmpdictforpt - } - } +} + +proc loadscript {} { +global ws_port +set res [rest::get http://localhost:$ws_port/loadscript "" ] +putscli $res } proc wapp-page-loadscript {} { @@ -792,6 +598,32 @@ wapp-2-json 1 $scriptdict } } +proc print { args } { +global ws_port +set res [rest::get http://localhost:$ws_port/print $args ] +putscli $res +} + +proc wapp-page-echo {} { +if [catch {set huddlecontent [ huddle::json2huddle [list [wapp-param CONTENT]]]} message ] { +dict set jsondict error message [ subst {$message} ] +wapp-2-json 2 $jsondict +} else { +if {[ huddle llength $huddlecontent ] != 6} { +dict set jsondict error message "Incorrect number of parameters to echo" +wapp-2-json 2 $jsondict +return +} +foreach {type key value} [ huddle keys $huddlecontent ] break + set type2 [ huddle get_stripped $huddlecontent $type ] + set key2 [ huddle get_stripped $huddlecontent $key ] + set val [ huddle get_stripped $huddlecontent $value ] +dict set jsondict $type2 $key2 "$val" +wapp-2-json 2 $jsondict + } +} + + proc wapp-page-print {} { switch [ wapp-param QUERY_STRING ] { db { wapp-page-db } @@ -802,6 +634,7 @@ vuconf { wapp-page-vuconf } vucreated { wapp-page-vucreated } vustatus { wapp-page-vustatus } datagen { wapp-page-datagen } +tcconf { wapp-page-tcconf } default { dict set scriptdict usage message "print?option" wapp-2-json 2 $scriptdict @@ -822,6 +655,10 @@ dict append vucreateddict "Virtual Users created" [expr [ llength [ thread::name wapp-2-json 1 $vucreateddict } +proc vustatus {} { +print vustatus +} + proc wapp-page-vustatus {} { global vustatus if { ![info exists vustatus] } { @@ -837,6 +674,11 @@ dict append vucompletedict "Virtual Users complete" [ vucomplete ] wapp-2-json 1 $vucompletedict } +proc wapp-page-tcconf {} { +upvar #0 genericdict genericdict +dict with genericdict { wapp-2-json 1 $transaction_counter } +} + proc wapp-page-datagen {} { global rdbms gen_count_ware gen_scale_fact gen_directory gen_num_vu bm if { ![ info exists gen_count_ware ] } { set gen_count_ware "1" } @@ -852,6 +694,56 @@ set vudgendict [ dict create schema $bm database $rdbms scale_factor $gen_scale_ wapp-2-json 1 $vudgendict } +proc librarycheck {} { +global ws_port +set res [rest::get http://localhost:$ws_port/librarycheck "" ] +putscli $res +} + +proc wapp-page-librarycheck {} { +upvar #0 dbdict dbdict +dict for {database attributes} $dbdict { +dict with attributes { +lappend dbl $name +lappend prefixl $prefix +lappend libl $library + } +} +foreach db $dbl library $libl { +if { [ llength $library ] > 1 } { + set version [ lindex $library 1 ] + set library [ lindex $library 0 ] +set cmd "package require $library $version" + } else { +set cmd "package require $library" + } +if [catch {eval $cmd} message] { +lappend joboutput "error: failed to load $library for $db - $message" +} else { +lappend joboutput "success ... loaded library $library for $db" + } + } + set huddleobj [ huddle compile {list} $joboutput ] + wapp-mimetype application/json + wapp-trim { %unsafe([huddle jsondump $huddleobj]) } +} + +proc diset { args } { +global ws_port rdbms opmode +if {[ llength $args ] != 3} { +set body { "type": "error", "key": "message", "value": "Incorrect number of parameters to diset dict key value" } +set res [rest::post http://localhost:$ws_port/echo $body ] +putscli $res +} else { + set dct [ lindex $args 0 ] + set key2 [ lindex $args 1 ] + set val [ lindex $args 2 ] +set body [ subst { "dict": "$dct", "key": "$key2", "value": "$val" } ] +set res [rest::post http://localhost:$ws_port/diset $body ] +putscli $res + } +} + proc wapp-page-diset {} { global wapp rdbms if [catch {set huddlecontent [ huddle::json2huddle [list [wapp-param CONTENT]]]} message ] { @@ -884,7 +776,7 @@ if { [ dict get $dbdict $key name ] eq $rdbms } { if { $val != "test" && $val != "timed" } { dict set jsondict error message "Error: Driver script must be either \"test\" or \"timed\"" } else { - if { [ clearscript ] } { + if { [ clearscript2 ] } { if { [catch {dict set $dictname $dct $key2 $val } message]} { dict set jsondict error message "Failed to set Dictionary value: $message" } else { @@ -913,9 +805,83 @@ dict set jsondict success message "Set driver script to $val, clearing Script, r } }}}}} +proc tcset { args } { +global ws_port +if {[ llength $args ] != 2} { +set body { "type": "error", "key": "message", "value": "Incorrect number of parameters to tcset key value" } +set res [rest::post http://localhost:$ws_port/echo $body ] +putscli $res +} else { +set opt [ lindex [ split $args ] 0 ] +set val [ lindex [ split $args ] 1 ] +set body [ subst { "$opt": "$val" } ] +set res [rest::post http://localhost:$ws_port/tcset $body ] +putscli $res + } +} + +proc wapp-page-tcset {} { +if [catch {set huddlecontent [ huddle::json2huddle [list [wapp-param CONTENT]]]} message ] { +dict set jsondict error message [ subst {$message} ] + wapp-2-json 2 $jsondict +} else { +if {[ huddle llength $huddlecontent ] != 2} { +dict set jsondict error message "Incorrect number of parameters to tcset key value" + wapp-2-json 2 $jsondict +return +} else { +set key [ huddle keys $huddlecontent ] +set val [ huddle get_stripped $huddlecontent $key ] +set ind [ lsearch {refreshrate} $key ] +if { $ind eq -1 } { +dict set jsondict error message "Invalid option to tcset key value" + wapp-2-json 2 $jsondict +return + } +upvar #0 genericdict genericdict +switch $key { +refreshrate { +set refreshrate $val +if { ![string is integer -strict $refreshrate] } { +dict set jsondict error message "Refresh rate must be an integer more than 0 secs and less than 60 secs" + wapp-2-json 2 $jsondict + set refreshrate 10 + return + } else { + if { ($refreshrate >= 60) || ($refreshrate <= 0) } { +dict set jsondict error message "Refresh rate must be more than 0 secs and less than 60 secs" + wapp-2-json 2 $jsondict + set refreshrate 10 + return + } + } +if { [catch {dict set genericdict transaction_counter tc_refresh_rate $refreshrate}] } { +dict set jsondict error message "Failed to set Transaction Counter refresh rate" + wapp-2-json 2 $jsondict + return +} else { +dict set jsondict success message "Transaction Counter refresh rate set to $refreshrate" + wapp-2-json 2 $jsondict + return +} +} +default { +#default option should not be reached as earlier index lsearch only looks for refreshrate +#switch statement retained in case of adding additional parameters in future +dict set jsondict error message "Invalid option to tcset key value" + wapp-2-json 2 $jsondict + return + } +}}}} + +proc clearscript {} { +global ws_port +set res [rest::get http://localhost:$ws_port/clearscript "" ] +putscli $res +} proc wapp-page-clearscript {} { -if { [ clearscript ] } { +if { [ clearscript2 ] } { dict set jsondict success message "Script cleared" } else { dict set jsondict error message "Error:script failed to clear" @@ -923,6 +889,30 @@ dict set jsondict error message "Error:script failed to clear" wapp-2-json 2 $jsondict } +proc dbset { args } { +global ws_port rdbms bm opmode +if {[ llength $args ] != 2} { +set body { "type": "error", "key": "message", "value": "Usage: dbset [db|bm] value" } +set res [rest::post http://localhost:$ws_port/echo $body ] +putscli $res +return +} else { +set option [ lindex [ split $args ] 0 ] +set ind [ lsearch {db bm} $option ] +if { $ind eq -1 } { +set body { "type": "error", "key": "message", "value": "Usage: dbset [db|bm] value" } +set res [rest::post http://localhost:$ws_port/echo $body ] +putscli $res +return + } +set opt [ lindex [ split $args ] 0 ] +set val [ lindex [ split $args ] 1 ] +set body [ subst { "$opt": "$val" } ] +set res [rest::post http://localhost:$ws_port/dbset $body ] +putscli $res + } +} + proc wapp-page-dbset {} { global rdbms bm if [catch {set huddlecontent [ huddle::json2huddle [list [wapp-param CONTENT]]]} message ] { @@ -994,6 +984,21 @@ puts {Usage: dbset [db|bm|config] value} } }}} +proc vuset { args } { +global ws_port +if {[ llength $args ] != 2} { +set body { "type": "error", "key": "message", "value": "Incorrect number of parameters to vuset key value" } +set res [rest::post http://localhost:$ws_port/echo $body ] +putscli $res +} else { +set opt [ lindex [ split $args ] 0 ] +set val [ lindex [ split $args ] 1 ] +set body [ subst { "$opt": "$val" } ] +set res [rest::post http://localhost:$ws_port/vuset $body ] +putscli $res + } +} + proc wapp-page-vuset {} { global virtual_users conpause delayms ntimes suppo optlog unique_log_name no_log_buffer log_timestamps if [catch {set huddlecontent [ huddle::json2huddle [list [wapp-param CONTENT]]]} message ] { @@ -1161,6 +1166,21 @@ dict set jsondict error message "Invalid option to vuset key value" } }}}} +proc dgset { args } { +global ws_port +if {[ llength $args ] != 2} { +set body { "type": "error", "key": "message", "value": "Incorrect number of parameters to dgset key value" } +set res [rest::post http://localhost:$ws_port/echo $body ] +putscli $res +} else { +set opt [ lindex [ split $args ] 0 ] +set val [ lindex [ split $args ] 1 ] +set body [ subst { "$opt": "$val" } ] +set res [rest::post http://localhost:$ws_port/dgset $body ] +putscli $res + } +} + proc wapp-page-dgset {} { global rdbms bm gen_count_ware gen_scale_fact gen_directory gen_num_vu maxvuser virtual_users lprefix if { ![ info exists gen_count_ware ] } { set gen_count_ware "1" } @@ -1271,6 +1291,29 @@ set gen_directory $tmp wapp-2-json 2 $jsondict }}}}} +proc customscript { scriptname } { +global ws_port +set _ED(file) $scriptname +if {$_ED(file) == ""} {return} + if {![file readable $_ED(file)]} { +set body [ subst { "type": "error", "key": "message", "value": "File $scriptname is not readable." } ] +set res [rest::post http://localhost:$ws_port/echo $body ] +putscli $res + return + } +if {[catch "open \"$_ED(file)\" r" fd]} { +set body [subst { "type": "error", "key": "message", "value": "Error while opening $scriptname: $fd" } ] + } else { + set _ED(package) "[read $fd]" + close $fd + } +set huddleobj [ huddle compile {string} "$_ED(package)" ] +set jsonobj [ huddle jsondump $huddleobj ] +set body [ subst { {"script": $jsonobj}} ] +set res [ rest::post http://localhost:$ws_port/customscript $body ] +puts $res +} + proc wapp-page-customscript {} { global _ED if [catch {set huddlecontent [ huddle::json2huddle [wapp-param CONTENT]]} message ] { @@ -1289,40 +1332,61 @@ dict set jsondict success message "Set custom script" wapp-2-json 2 $jsondict }}} +proc vudestroy {} { +global ws_port +set res [rest::get http://localhost:$ws_port/vudestroy "" ] +putscli $res +} + proc wapp-page-vudestroy {} { - global threadscreated vustatus AVUC - if {[expr [ llength [ thread::names ] ] - 1 ] > 0} { - tsv::set application abort 1 - if { [catch {ed_kill_vusers} message]} { -dict set jsondict error message "Virtual Users remain running in background or shutting down, retry" + global threadscreated threadsbytid vustatus AVUC opmode + if {[expr [ llength [ threadnames_without_tcthread ] ] - 1 ] > 0} { + tsv::set application abort 1 + if { [catch {ed_kill_vusers} message]} { + dict set jsondict error message "Virtual Users remain running in background or shutting down, retry" wapp-2-json 2 $jsondict - } else { - set x 0 - set checkstop 0 - while {!$checkstop} { - incr x - after 1000 - update - if {[expr [ llength [ thread::names ] ] - 1 ] eq 0} { - set checkstop 1 -dict set jsondict success message "vudestroy success" + } else { + #remote_command [ concat vudestroy ] + set x 0 + set checkstop 0 + while {!$checkstop} { + incr x + after 1000 + update + if {[expr [ llength [ threadnames_without_tcthread ] ] - 1 ] eq 0} { + set checkstop 1 + dict set jsondict success message "vudestroy success" wapp-2-json 2 $jsondict - unset -nocomplain AVUC - unset -nocomplain vustatus - } - if { $x eq 20 } { - set checkstop 1 -dict set jsondict error message "Virtual Users remain running in background or shutting down, retry" + unset -nocomplain AVUC + unset -nocomplain vustatus + } + if { $x eq 20 } { + set checkstop 1 + dict set jsondict error message "Virtual Users remain running in background or shutting down, retry" wapp-2-json 2 $jsondict - } - } - } + } + } + } } else { -dict set jsondict error message "No virtual users found to destroy" + if { $opmode eq "Replica" } { +#In Primary Replica Mode ed_kill_vusers may have already been called from Primary so thread::names is 1 + unset -nocomplain AVUC + unset -nocomplain vustatus + dict set jsondict error message "vudestroy from Primary, Replica status clean up" wapp-2-json 2 $jsondict + } else { + dict set jsondict error message "No virtual users found to destroy" + wapp-2-json 2 $jsondict + } } } +proc vucreate {} { +global ws_port +set res [rest::get http://localhost:$ws_port/vucreate "" ] +putscli $res +} + proc wapp-page-vucreate {} { global _ED lprefix vustatus if { [ string length $_ED(package) ] eq 0 } { @@ -1351,6 +1415,12 @@ dict set jsondict success message "[expr [ llength [ thread::names ] ] - 1 ] Vir } } +proc buildschema {} { +global ws_port +set res [rest::get http://localhost:$ws_port/buildschema "" ] +putscli $res +} + proc wapp-page-buildschema {} { global virtual_users maxvuser rdbms bm threadscreated jobid if { [ info exists threadscreated ] } { @@ -1359,6 +1429,11 @@ dict set jsondict error message "Cannot build schema with Virtual Users active, return } set jobid [guid] +if { [jobmain $jobid] eq 1 } { +dict set jsondict error message "Jobid already exists or error in creating jobid in JOBMAIN table" + wapp-2-json 2 $jsondict +return + } upvar #0 dbdict dbdict foreach { key } [ dict keys $dbdict ] { if { [ dict get $dbdict $key name ] eq $rdbms } { @@ -1386,7 +1461,7 @@ dict set jsondict error message "Build virtual users must be less than or equal if { $buildvu eq 1 } { set maxvuser 1 set virtual_users 1 - clearscript + clearscript2 if { [ catch {build_schema} message ] } { dict set jsondict error message "$message" wapp-2-json 2 $jsondict @@ -1399,7 +1474,7 @@ dict set jsondict success message "Building $buildcw Warehouses(s) with 1 Virtua } else { set maxvuser [ expr $buildvu + 1 ] set virtual_users $maxvuser - clearscript + clearscript2 if { [ catch {build_schema} message ] } { dict set jsondict error message "$message" wapp-2-json 2 $jsondict @@ -1432,7 +1507,7 @@ if { $buildvu eq 1 } { set maxvuser 1 } else { set maxvuser [ expr $buildvu + 1 ] } set virtual_users $maxvuser - clearscript + clearscript2 if { [ catch {build_schema} message ] } { dict set jsondict error message "$message" wapp-2-json 2 $jsondict @@ -1443,6 +1518,80 @@ dict set jsondict success message "Building Scale Factor $buildsf with $maxvuser } } +proc jobs { args } { +global ws_port +switch [ llength $args ] { +0 { +#Query all jobs +set res [rest::get http://localhost:$ws_port/jobs "" ] +return $res +} +1 { +set param [ lindex [ split $args ] 0 ] +#List results for all jobs +if [ string equal $param "result" ] { + set alljobs [ rest::format_json [ jobs ]] + foreach jobres $alljobs { +set res [rest::get http://localhost:$ws_port/jobs?jobid=$jobres&result "" ] +putscli $res + } + } elseif [ string equal $param "timestamp" ] { + set alljobs [ rest::format_json [ jobs ]] + foreach jobres $alljobs { +set res [rest::get http://localhost:$ws_port/jobs?jobid=$jobres×tamp "" ] +putscli $res + } +} else { +#Query one jobid +set jobid $param +set res [rest::get http://localhost:$ws_port/jobs?jobid=$jobid "" ] +putscli $res + } +} +2 { +#Query status, result, vu number or delete job data for one jobid +#jobs?jobid=TEXT&status param is status +#iobs?jobid=TEXT&result param is result +#jobs?jobid=TEXT&delete param is delete +#jobs?jobid=TEXT×tamp param is timestamp +#jobs?jobid=TEXT&dict param is dict +#jobs?jobid=TEXT&timing param is timing +#jobs?jobid=TEXT&db param is db +#jobs?jobid=TEXT&bm param is bm +#jobs?jobid=TEXT&tcount param is tcount +#jobs?jobid=TEXT&vu=INTEGER param is an INTEGER identifying the vu number +set jobid [ lindex [ split $args ] 0 ] +set cmd [ lindex [ split $args ] 1 ] +if [ string is entier $cmd ] { set cmd "vu=$cmd" } +set res [rest::get http://localhost:$ws_port/jobs?jobid=$jobid&$cmd "" ] +putscli $res +} +3 { +#jobs?jobid=TEXT&timing&vu param is timing +set jobid [ lindex [ split $args ] 0 ] +set cmd [ lindex [ split $args ] 1 ] +set vusel [ lindex [ split $args ] 2 ] +if { $cmd != "timing" } { +set body { "type": "error", "key": "message", "value": "Jobs Three Parameter Usage: jobs?jobid=JOBID&timing&vu=VUID" } +set res [rest::post http://localhost:$ws_port/echo $body ] +putscli $res + } else { +#Three arguments 2nd parameter is timing +if [ string is entier $vusel ] { set vusel "vu=$vusel" } +set res [rest::get http://localhost:$ws_port/jobs?jobid=$jobid&$cmd&$vusel "" ] +putscli $res + } +} +default { +set body { "type": "error", "key": "message", "value": "Usage: jobs?query=parameter" } +set res [rest::post http://localhost:$ws_port/echo $body ] +putscli $res + } + } +} + +interp alias {} job {} jobs + proc wapp-page-jobs {} { global bm set query [ wapp-param QUERY_STRING ] @@ -1450,34 +1599,65 @@ set params [ split $query & ] set paramlen [ llength $params ] #No parameters list jobids if { $paramlen eq 0 } { -set joboutput [ hdb eval {SELECT DISTINCT JOBID FROM JOBS} ] +set joboutput [ hdb eval {SELECT DISTINCT JOBID FROM JOBMAIN} ] set huddleobj [ huddle compile {list} $joboutput ] wapp-mimetype application/json wapp-trim { %unsafe([huddle jsondump $huddleobj]) } return } else { -if { $paramlen >= 1 && $paramlen <= 2 } { +if { $paramlen >= 1 && $paramlen <= 3 } { foreach a $params { lassign [split $a =] key value dict append paramdict $key $value } - } else { + } else { dict set jsondict error message "Usage: jobs?query=parameter" wapp-2-json 2 $jsondict return - } + } +if { $paramlen eq 3 } { +if { [ dict keys $paramdict ] != "jobid timing vu" } { +dict set jsondict error message "Jobs Three Parameter Usage: jobs?jobid=JOBID&timing&vu=VUID" +wapp-2-json 2 $jsondict +return + } else { +#3 parameter case of 1-jobid 2-timing 3-vu +set jobid [ dict get $paramdict jobid ] +set vuid [ dict get $paramdict vu ] +if [ string is entier $vuid ] { +unset -nocomplain jobtiming +set jobtiming [ dict create ] +hdb eval {SELECT procname,elapsed_ms,calls,min_ms,avg_ms,max_ms,total_ms,p99_ms,p95_ms,p50_ms,sd,ratio_pct FROM JOBTIMING WHERE JOBID=$jobid and VU=$vuid and SUMMARY=0 ORDER BY RATIO_PCT DESC} { +set timing "elapsed_ms $elapsed_ms calls $calls min_ms $min_ms avg_ms $avg_ms max_ms $max_ms total_ms $total_ms p99_ms $p99_ms p95_ms $p95_ms p50_ms $p50_ms sd $sd ratio_pct $ratio_pct" +dict append jobtiming $procname $timing +} +if { ![ dict size $jobtiming ] eq 0 } { +wapp-2-json 2 $jobtiming +return +} else { +dict set jsondict error message "No Timing Data for VU $vuid for JOB $jobid: jobs?jobid=JOBID&timing&vu=VUID" +wapp-2-json 2 $jsondict +return +} + } else { +dict set jsondict error message "Jobs Three Parameter Usage: jobs?jobid=JOBID&timing&vu=VUID" +wapp-2-json 2 $jsondict +return + } + } + } } #1 parameter if { $paramlen eq 1 } { if { [ dict keys $paramdict ] eq "jobid" } { set jobid [ dict get $paramdict jobid ] -set query [ hdb eval {SELECT COUNT(*) FROM JOBS WHERE JOBID=$jobid} ] +set query [ hdb eval {SELECT COUNT(*) FROM JOBOUTPUT WHERE JOBID=$jobid} ] if { $query eq 0 } { -dict set jsondict error message "Jobid $jobid for jobstatus does not exist" +dict set jsondict error message "Jobid $jobid does not exist" wapp-2-json 2 $jsondict return } else { -set joboutput [ hdb eval {SELECT VU,OUTPUT FROM JOBS WHERE JOBID=$jobid} ] +set joboutput [ hdb eval {SELECT VU,OUTPUT FROM JOBOUTPUT WHERE JOBID=$jobid} ] set huddleobj [ huddle compile {list} $joboutput ] wapp-mimetype application/json wapp-trim { %unsafe([huddle jsondump $huddleobj]) } @@ -1487,9 +1667,9 @@ dict set jsondict error message "Jobs One Parameter Usage: jobs?jobid=TEXT" wapp-2-json 2 $jsondict return } -#2 parameters +#2 or more parameters } else { -if { [ dict keys $paramdict ] eq "jobid vu" || [ dict keys $paramdict ] eq "jobid status" || [ dict keys $paramdict ] eq "jobid result" || [ dict keys $paramdict ] eq "jobid delete" } { +if { [ dict keys $paramdict ] eq "jobid vu" || [ dict keys $paramdict ] eq "jobid status" || [ dict keys $paramdict ] eq "jobid result" || [ dict keys $paramdict ] eq "jobid delete" || [ dict keys $paramdict ] eq "jobid timestamp" || [ dict keys $paramdict ] eq "jobid dict" || [ dict keys $paramdict ] eq "jobid timing" || [ dict keys $paramdict ] eq "jobid db" || [ dict keys $paramdict ] eq "jobid bm" || [ dict keys $paramdict ] eq "jobid tcount" } { set jobid [ dict get $paramdict jobid ] if { [ dict keys $paramdict ] eq "jobid vu" } { set vuid [ dict get $paramdict vu ] @@ -1500,56 +1680,125 @@ set vuid 1 set vuid 0 } } -set query [ hdb eval {SELECT COUNT(*) FROM JOBS WHERE JOBID=$jobid AND VU=$vuid} ] +set query [ hdb eval {SELECT COUNT(*) FROM JOBOUTPUT WHERE JOBID=$jobid AND VU=$vuid} ] if { $query eq 0 } { dict set jsondict error message "Jobid $jobid for virtual user $vuid does not exist" wapp-2-json 2 $jsondict return } else { if { [ dict keys $paramdict ] eq "jobid vu" || [ dict keys $paramdict ] eq "jobid status" } { -set joboutput [ hdb eval {SELECT VU,OUTPUT FROM JOBS WHERE JOBID=$jobid AND VU=$vuid} ] +set joboutput [ hdb eval {SELECT VU,OUTPUT FROM JOBOUTPUT WHERE JOBID=$jobid AND VU=$vuid} ] set huddleobj [ huddle compile {list} $joboutput ] wapp-mimetype application/json wapp-trim { %unsafe([huddle jsondump $huddleobj]) } return } if { [ dict keys $paramdict ] eq "jobid delete" } { -set joboutput [ hdb eval {DELETE FROM JOBS WHERE JOBID=$jobid} ] +set joboutput [ hdb eval {DELETE FROM JOBMAIN WHERE JOBID=$jobid} ] +set joboutput [ hdb eval {DELETE FROM JOBTIMING WHERE JOBID=$jobid} ] +set joboutput [ hdb eval {DELETE FROM JOBTCOUNT WHERE JOBID=$jobid} ] +set joboutput [ hdb eval {DELETE FROM JOBOUTPUT WHERE JOBID=$jobid} ] dict set jsondict success message "Deleted Jobid $jobid" wapp-2-json 2 $jsondict } else { if { [ dict keys $paramdict ] eq "jobid result" } { if { $bm eq "TPC-C" } { -set joboutput [ hdb eval {SELECT VU,OUTPUT FROM JOBS WHERE JOBID=$jobid AND VU=$vuid} ] +set tstamp "" +set tstamp [ join [ hdb eval {SELECT timestamp FROM JOBMAIN WHERE JOBID=$jobid} ]] +set joboutput [ hdb eval {SELECT VU,OUTPUT FROM JOBOUTPUT WHERE JOBID=$jobid AND VU=$vuid} ] +set activevu [ lsearch -glob -inline $joboutput "*Active Virtual Users*" ] set result [ lsearch -glob -inline $joboutput "TEST RESULT*" ] } else { -set joboutput [ hdb eval {SELECT VU,OUTPUT FROM JOBS WHERE JOBID=$jobid} ] +set joboutput [ hdb eval {SELECT VU,OUTPUT FROM JOBOUTPUT WHERE JOBID=$jobid} ] set result [ lsearch -all -glob -inline $joboutput "Completed*" ] } if { $result eq {} } { set joboutput [ list $jobid "Jobid has no test result" ] } else { -set joboutput [ list $jobid $result ] +if { $activevu eq {} } { +set joboutput [ list $jobid $tstamp $result ] + } else { +set joboutput [ list $jobid $tstamp $activevu $result ] + } } } else { +if { [ dict keys $paramdict ] eq "jobid timing" } { +unset -nocomplain jobtiming +set jobtiming [ dict create ] +hdb eval {SELECT procname,elapsed_ms,calls,min_ms,avg_ms,max_ms,total_ms,p99_ms,p95_ms,p50_ms,sd,ratio_pct FROM JOBTIMING WHERE JOBID=$jobid and SUMMARY=1 ORDER BY RATIO_PCT DESC} { +set timing "elapsed_ms $elapsed_ms calls $calls min_ms $min_ms avg_ms $avg_ms max_ms $max_ms total_ms $total_ms p99_ms $p99_ms p95_ms $p95_ms p50_ms $p50_ms sd $sd ratio_pct $ratio_pct" +dict append jobtiming $procname $timing +} +if { ![ dict size $jobtiming ] eq 0 } { +wapp-2-json 2 $jobtiming +return +} else { +dict set jsondict error message "No Timing Data for JOB $jobid: jobs?jobid=JOBID&timing" +wapp-2-json 2 $jsondict +return + } + } else { +if { [ dict keys $paramdict ] eq "jobid timestamp" } { +set joboutput [ hdb eval {SELECT jobid, timestamp FROM JOBMAIN WHERE JOBID=$jobid} ] +wapp-2-json 2 $joboutput +return + } else { +if { [ dict keys $paramdict ] eq "jobid dict" } { +set joboutput [ join [ hdb eval {SELECT jobdict FROM JOBMAIN WHERE JOBID=$jobid} ]] +wapp-2-json 2 $joboutput +return + } else { +if { [ dict keys $paramdict ] eq "jobid tcount" } { +set jobheader [ hdb eval {select distinct(db), metric from JOBTCOUNT, JOBMAIN WHERE JOBTCOUNT.JOBID=$jobid AND JOBMAIN.JOBID=$jobid} ] +set joboutput [ hdb eval {select counter, JOBTCOUNT.timestamp from JOBTCOUNT WHERE JOBTCOUNT.JOBID=$jobid order by JOBTCOUNT.timestamp asc} ] +dict append jsondict $jobheader $joboutput +wapp-2-json 2 $jsondict +return + } else { +if { [ dict keys $paramdict ] eq "jobid db" } { +set joboutput [ join [ hdb eval {SELECT db FROM JOBMAIN WHERE JOBID=$jobid} ]] + } else { +if { [ dict keys $paramdict ] eq "jobid bm" } { +set joboutput [ join [ hdb eval {SELECT bm FROM JOBMAIN WHERE JOBID=$jobid} ]] + } else { set joboutput [ list $jobid "Cannot find Jobid output" ] - } + } + }}}}}} set huddleobj [ huddle compile {list} $joboutput ] wapp-mimetype application/json wapp-trim { %unsafe([huddle jsondump $huddleobj]) } - } + } } } else { -dict set jsondict error message "Jobs Two Parameter Usage: jobs?jobid=TEXT&vu=INTEGER or jobs?jobid=TEXT&status or jobs?jobid=TEXT&result or jobs?jobid=TEXT&delete" +dict set jsondict error message "Jobs Two Parameter Usage: jobs?jobid=TEXT&status or jobs?jobid=TEXT&db or jobs?jobid=TEXT&bm or jobs?jobid=TEXT×tamp or jobs?jobid=TEXT&dict or jobs?jobid=TEXT&vu=INTEGER or jobs?jobid=TEXT&result or jobs?jobid=TEXT&timing or jobs?jobid=TEXT&delete" wapp-2-json 2 $jsondict return } } } +interp alias {} wapp-page-job {} wapp-page-jobs + +proc vurun {} { +global ws_port jobid +unset -nocomplain jobid +set res [rest::get http://localhost:$ws_port/vurun "" ] +putscli $res +if { [ info exists jobid ] } { +return $jobid + } else { +return + } +} + proc wapp-page-vurun {} { global _ED jobid set jobid [guid] +if { [jobmain $jobid] eq 1 } { +dict set jsondict error message "Jobid already exists or error in creating jobid in JOBMAIN table" + wapp-2-json 2 $jsondict +return + } if { [ string length $_ED(package) ] > 0 } { if { [ catch {run_virtual} message ] } { dict set jsondict error message "Error running virtual users: $message" @@ -1566,9 +1815,10 @@ unset -nocomplain jobid } } -proc wapp-page-librarycheck {} { -dict set jsondict error message "librarycheck is not supported in hammerdbws, verify libraries with hammerdbcli" - wapp-2-json 2 $jsondict +proc datagenrun {} { +global ws_port +set res [rest::get http://localhost:$ws_port/datagenrun "" ] +putscli $res } proc wapp-page-datagenrun {} { @@ -1576,19 +1826,193 @@ dict set jsondict error message "datagenrun is not supported in hammerdbws, run wapp-2-json 2 $jsondict } -proc wapp-page-dumpdb {} { -set dbdump [ hdb eval {SELECT * FROM JOBS} ] -puts "***************DEBUG***************" -puts $dbdump -puts "***************DEBUG***************" +proc switchmode {} { +global ws_port +set res [rest::get http://localhost:$ws_port/switchmode "" ] +putscli $res +} + +proc wapp-page-switchmode {} { +dict set jsondict error message "Primary, replica modes not supported in hammerdbws, run remote modes with hammerdb or hammerdbcli" + wapp-2-json 2 $jsondict +} + +proc steprun {} { +global ws_port +set res [rest::get http://localhost:$ws_port/steprun "" ] +putscli $res +} + +proc wapp-page-steprun {} { +dict set jsondict error message "Steprun not supported in hammerdbws, run steprun with hammerdbcli" + wapp-2-json 2 $jsondict +} + +proc _dumpdb {} { +global ws_port +set res [rest::get http://localhost:$ws_port/_dumpdb "" ] +putscli $res +} + +proc wapp-page-_dumpdb {} { +set jmdump [ concat [ hdb eval {SELECT * FROM JOBMAIN} ] ]] +set jtdump [ concat [ hdb eval {SELECT * FROM JOBTIMING} ]] +set jcdump [ concat [ hdb eval {SELECT * FROM JOBTCOUNT} ]] +set jodump [ concat [ hdb eval {SELECT * FROM JOBOUTPUT} ]] +set joboutput [ list $jmdump $jtdump $jcdump $jodump ] + set huddleobj [ huddle compile {list} $joboutput ] + wapp-mimetype application/json + wapp-trim { %unsafe([huddle jsondump $huddleobj]) } } -proc wapp-page-killws {} { -puts "Shutting down HammerDB Web Service" +proc quit {} { +global ws_port +set res [rest::get http://localhost:$ws_port/quit "" ] +putscli $res +} + +proc wapp-page-quit {} { +putscli "Shutting down HammerDB Web Service" exit } -proc start_webservice {} { +proc runtimer { seconds } { +global ws_port +upvar elapsed elapsed +upvar timevar timevar +proc runtimer_loop { seconds } { +global ws_port +upvar elapsed elapsed +incr elapsed +upvar timevar timevar +set rcomplete [vucomplete] + if { ![ expr {$elapsed % 60} ] } { + set y [ expr $elapsed / 60 ] +set body [ subst { "type": "success", "key": "message", "value": "Timer: $y minutes elapsed" } ] +set res [rest::post http://localhost:$ws_port/echo $body ] +putscli $res + } +if {!$rcomplete && $elapsed < $seconds } { +;#Neither vucomplete or time reached, reschedule loop +catch {after 1000 runtimer_loop $seconds }} else { +set body [ subst { "type": "success", "key": "message", "value": "runtimer returned after $elapsed seconds" } ] +set res [rest::post http://localhost:$ws_port/echo $body ] +putscli $res +set elapsed 0 +set timevar 1 + } +} +set elapsed 0 +set timevar 0 +runtimer_loop $seconds +vwait timevar +return +} + +proc tcstart {} { +global ws_port +set res [rest::get http://localhost:$ws_port/tcstart "" ] +putscli $res +} + +proc wapp-page-tcstart {} { +global tc_threadID +set tclist [ thread::names ] +if { [ info exists tc_threadID ] } { +set idx [ lsearch $tclist $tc_threadID ] +if { $idx != -1 } { +dict set jsondict error message "Transaction Counter thread already running with threadid:$tc_threadID" +wapp-2-json 2 $jsondict +return +} else { +dict set jsondict error message "Transaction Counter thread already running with threadid" +wapp-2-json 2 $jsondict +return +} +} else { +#Start transaction counter +transcount +return +} +} + +proc tcstatus {} { +global ws_port +set res [rest::get http://localhost:$ws_port/tcstatus "" ] +putscli $res +} + +proc wapp-page-tcstatus {} { +global ws_port +global tc_threadID +set tclist [ thread::names ] +if { [ info exists tc_threadID ] } { +set idx [ lsearch $tclist $tc_threadID ] +if { $idx != -1 } { +dict set jsondict success message "Transaction Counter thread running with threadid:$tc_threadID" +wapp-2-json 2 $jsondict +return +} else { +dict set jsondict success message "Transaction Counter thread running" +wapp-2-json 2 $jsondict +return +} +} else { +dict set jsondict success message "Transaction Counter is not running" +wapp-2-json 2 $jsondict +return +} +} + +proc tcstop {} { +global ws_port +set res [rest::get http://localhost:$ws_port/tcstop "" ] +putscli $res +} + +proc wapp-page-tcstop {} { +global tc_threadID ws_port +set tclist [ thread::names ] +if { [ info exists tc_threadID ] } { +set idx [ lsearch $tclist $tc_threadID ] +if { $idx != -1 } { +dict set jsondict success message "Transaction Counter thread running with threadid:$tc_threadID" +wapp-2-json 2 $jsondict +ed_kill_transcount +return +} else { +dict set jsondict success message "Transaction Counter thread running" +wapp-2-json 2 $jsondict +ed_kill_transcount +return +} +} else { +dict set jsondict success message "Transaction Counter is not running" +wapp-2-json 2 $jsondict +return +} +} + +proc waittocomplete {} { +global ws_port +proc wait_to_complete_loop {} { +global ws_port +upvar wcomplete wcomplete +set wcomplete [vucomplete] +if {!$wcomplete} { catch {after 5000 wait_to_complete_loop} } else { +set body { "type": "success", "key": "message", "value": "waittocomplete called script exit" } +set res [rest::post http://localhost:$ws_port/echo $body ] +putscli $res +exit +} +} +set wcomplete "false" +wait_to_complete_loop +vwait forever +} + +proc start_webservice { args } { +global ws_port upvar #0 genericdict genericdict if {[dict exists $genericdict webservice ws_port ]} { set ws_port [ dict get $genericdict webservice ws_port ] @@ -1607,7 +2031,7 @@ set tmpdir [ findtempdir ] if { $tmpdir != "notmpdir" } { set sqlite_db [ file join $tmpdir hammerdb.DB ] } else { -puts "Error Database Directory set to TMP but coundn't find temp directory" +puts "Error Database Directory set to TMP but couldn't find temp directory" } } } else { @@ -1617,34 +2041,68 @@ if [catch {sqlite3 hdb $sqlite_db} message ] { puts "Error initializing SQLite database : $message" return } else { +catch {hdb timeout 30000} +#hdb eval {PRAGMA foreign_keys=ON} if { $sqlite_db eq ":memory:" } { -catch {hdb eval {DROP TABLE JOBS}} -if [catch {hdb eval {CREATE TABLE JOBS(jobid TEXT, vu INTEGER, output TEXT)}} message ] { -puts "Error creating JOBS table in SQLite in-memory database : $message" +catch {hdb eval {DROP TABLE JOBMAIN}} +catch {hdb eval {DROP TABLE JOBTIMING}} +catch {hdb eval {DROP TABLE JOBTCOUNT}} +catch {hdb eval {DROP TABLE JOBOUTPUT}} +if [catch {hdb eval {CREATE TABLE JOBMAIN(jobid TEXT, db TEXT, bm TEXT, jobdict TEXT, timestamp DATETIME NOT NULL DEFAULT (datetime(CURRENT_TIMESTAMP, 'localtime')))}} message ] { +puts "Error creating JOBMAIN table in SQLite in-memory database : $message" return - } else { + } elseif [ catch {hdb eval {CREATE TABLE JOBTIMING(jobid TEXT, vu INTEGER, procname TEXT, calls INTEGER, min_ms REAL, avg_ms REAL, max_ms REAL, total_ms REAL, p99_ms REAL, p95_ms REAL, p50_ms REAL, sd REAL, ratio_pct REAL, summary INTEGER, elapsed_ms REAL, FOREIGN KEY(jobid) REFERENCES JOBMAIN(jobid))}} message ] { +puts "Error creating JOBTIMING table in SQLite in-memory database : $message" + } elseif [ catch {hdb eval {CREATE TABLE JOBTCOUNT(jobid TEXT, counter INTEGER, metric TEXT, timestamp DATETIME NOT NULL DEFAULT (datetime(CURRENT_TIMESTAMP, 'localtime')), FOREIGN KEY(jobid) REFERENCES JOBMAIN(jobid))}} message ] { +puts "Error creating JOBTCOUNT table in SQLite in-memory database : $message" +return + } elseif [ catch {hdb eval {CREATE TABLE JOBOUTPUT(jobid TEXT, vu INTEGER, output TEXT, FOREIGN KEY(jobid) REFERENCES JOBMAIN(jobid))}} message ] { +puts "Error creating JOBOUTPUT table in SQLite in-memory database : $message" +return + } else { +catch {hdb eval {CREATE INDEX JOBMAIN_IDX ON JOBMAIN(jobid)}} +catch {hdb eval {CREATE INDEX JOBTIMING_IDX ON JOBTIMING(jobid)}} +catch {hdb eval {CREATE INDEX JOBTCOUNT_IDX ON JOBTCOUNT(jobid)}} +catch {hdb eval {CREATE INDEX JOBOUTPUT_IDX ON JOBOUTPUT(jobid)}} puts "Initialized new SQLite in-memory database" - } + } } else { -if [catch {set tblname [ hdb eval {SELECT name FROM sqlite_master WHERE type='table' AND name='JOBS'}]} message ] { -puts "Error querying JOBS table in SQLite on-disk database : $message" +if [catch {set tblname [ hdb eval {SELECT name FROM sqlite_master WHERE type='table' AND name='JOBMAIN'}]} message ] { +puts "Error querying JOBOUTPUT table in SQLite on-disk database : $message" return - } else { + } else { if { $tblname eq "" } { -if [catch {hdb eval {CREATE TABLE JOBS(jobid TEXT, vu INTEGER, output TEXT)}} message ] { -puts "Error creating JOBS table in SQLite on-disk database : $message" + if [catch {hdb eval {CREATE TABLE JOBMAIN(jobid TEXT, db TEXT, bm TEXT, jobdict TEXT, timestamp DATETIME NOT NULL DEFAULT (datetime(CURRENT_TIMESTAMP, 'localtime')))}} message ] { +puts "Error creating JOBMAIN table in SQLite in-memory database : $message" +return + } elseif [ catch {hdb eval {CREATE TABLE JOBTIMING(jobid TEXT, vu INTEGER, procname TEXT, calls INTEGER, min_ms REAL, avg_ms REAL, max_ms REAL, total_ms REAL, p99_ms REAL, p95_ms REAL, p50_ms REAL, sd REAL, ratio_pct REAL, summary INTEGER, elapsed_ms REAL, FOREIGN KEY(jobid) REFERENCES JOBMAIN(jobid))}} message ] { +puts "Error creating JOBTIMING table in SQLite in-memory database : $message" +return + } elseif [ catch {hdb eval {CREATE TABLE JOBTCOUNT(jobid TEXT, counter INTEGER, metric TEXT, timestamp DATETIME NOT NULL DEFAULT (datetime(CURRENT_TIMESTAMP, 'localtime')), FOREIGN KEY(jobid) REFERENCES JOBMAIN(jobid))}} message ] { +puts "Error creating JOBTCOUNT table in SQLite in-memory database : $message" +return + } elseif [catch {hdb eval {CREATE TABLE JOBOUTPUT(jobid TEXT, vu INTEGER, output TEXT)}} message ] { +puts "Error creating JOBOUTPUT table in SQLite on-disk database : $message" return } else { +catch {hdb eval {CREATE INDEX JOBMAIN_IDX ON JOBMAIN(jobid)}} +catch {hdb eval {CREATE INDEX JOBTIMING_IDX ON JOBTIMING(jobid)}} +catch {hdb eval {CREATE INDEX JOBTCOUNT_IDX ON JOBTCOUNT(jobid)}} +catch {hdb eval {CREATE INDEX JOBOUTPUT_IDX ON JOBOUTPUT(jobid)}} puts "Initialized new SQLite on-disk database" } } else { -puts "Initialized SQLite on-disk database using existing JOBS table" +puts "Initialized SQLite on-disk database using existing tables" } } } +tsv::set webservice wsport $ws_port +tsv::set webservice sqldb $sqlite_db puts "Starting HammerDB Web Service on port $ws_port" -if [catch {wapp-start [ list --server $ws_port ]} message ] { +if [catch {wapp-start [ list --server $ws_port $args ]} message ] { puts "Error starting HammerDB webservice on port $ws_port : $message" + } else { +#Readline::interactws called from main script } } } From 0d4b47f95c9f934a7f16420e1ee1a3e3547136bd Mon Sep 17 00:00:00 2001 From: Steve Shaw Date: Wed, 13 Oct 2021 11:41:44 +0100 Subject: [PATCH 2/3] Remove invalid WS jobs help commands --- src/generic/genhelp.tcl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/generic/genhelp.tcl b/src/generic/genhelp.tcl index 91d3101f..aa66b1ea 100755 --- a/src/generic/genhelp.tcl +++ b/src/generic/genhelp.tcl @@ -240,8 +240,6 @@ get http://localhost:8080/buildschema GET jobs: Show the job ids, configuration, output, status, results and timings of jobs created by buildschema and vurun. Job output is equivalent to the output viewed in the graphical interface or command line. get http://localhost:8080/jobs get http://localhost:8080/jobs?jobid=TEXT -get http://localhost:8080/jobs?result -get http://localhost:8080/jobs?timestamp get http://localhost:8080/jobs?jobid=TEXT&bm get http://localhost:8080/jobs?jobid=TEXT&db get http://localhost:8080/jobs?jobid=TEXT&delete From 6af22d9613e8b750cfd0c397f4ce2b76edd9d257 Mon Sep 17 00:00:00 2001 From: Steve Shaw Date: Wed, 13 Oct 2021 17:34:42 +0100 Subject: [PATCH 3/3] Update Windows console color for Web Service --- hammerdbws.bat | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hammerdbws.bat b/hammerdbws.bat index 948ded62..375f3bd8 100755 --- a/hammerdbws.bat +++ b/hammerdbws.bat @@ -1,5 +1,5 @@ @echo off +COLOR 07 set path=.\bin;%PATH% -START tclsh86t hammerdbws +CALL tclsh86t hammerdbws %1 %2 exit -