diff --git a/.gitignore b/.gitignore index 53c8081..5d1a04c 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ .omakedb.lock distro rage +_build +.merlin +*.install diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..99f3b26 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,12 @@ +language: c +sudo: required +service: docker +install: wget https://raw.githubusercontent.com/ocaml/ocaml-ci-scripts/master/.travis-docker.sh +script: + - bash -ex .travis-docker.sh +env: + global: + - OCAML_VERSION="4.08" + - DISTRO="debian-unstable" + - PACKAGE="rage" + - PINS="rage:." diff --git a/OMakefile b/Makefile similarity index 55% rename from OMakefile rename to Makefile index 362de3a..2dee165 100644 --- a/OMakefile +++ b/Makefile @@ -1,42 +1,32 @@ -SHELL=/bin/bash -SRC=src -PROGRAM=rage CONFIG=/usr/groups/perfeng/rage/config RAGE_DB=$(shell grep "^rage_db=" $(CONFIG) | awk -F '=' '{print $$2}') RAGE_HOST=$(shell grep "^rage_host=" $(CONFIG) | awk -F '=' '{print $$2}') RAGE_USER=$(shell grep "^rage_user=" $(CONFIG) | awk -F '=' '{print $$2}') RAGE_PASS=$(shell grep "^rage_pass=" $(CONFIG) | awk -F '=' '{print $$2}') -SETTINGS="host=$(RAGE_HOST) user=$(RAGE_USER) password=$(RAGE_PASS) dbname=$(RAGE_DB)" -RUN_CMD=OCAMLRUNPARAM='b1' ./$(PROGRAM) "$(SETTINGS)" +SETTINGS=host=$(RAGE_HOST) user=$(RAGE_USER) password=$(RAGE_PASS) dbname=$(RAGE_DB) +PROGRAM=rage +RUN_CMD=OCAMLRUNPARAM='b1' ./$(PROGRAM) "$(SETTINGS)" /etc/rage_passwd WWW_DIR=/var/www CGI_SCRIPT=index.cgi STATIC_DIR=static -README=README.markdown DISTRO_DIR=distro MODE=775 INSTALL=install -m $(MODE) -.PHONY: readme build distro run install log clean -.DEFAULT: build +.PHONY: build clean distro install log +build: + dune build --profile=release @install -.SUBDIRS: $(SRC) +clean: + dune clean distro: build - rsync -avpL $(STATIC_DIR)/ $(SRC)/$(PROGRAM) $(DISTRO_DIR) - printf '#!/bin/bash\n\n$(RUN_CMD)' > $(DISTRO_DIR)/$(CGI_SCRIPT) + rsync -avpL $(STATIC_DIR)/ _build/install/default/bin/$(PROGRAM) $(DISTRO_DIR) + printf '#!/bin/bash\n\n$(RUN_CMD)\n' > $(DISTRO_DIR)/$(CGI_SCRIPT) chmod $(MODE) $(DISTRO_DIR)/$(CGI_SCRIPT) -run: distro - $(DISTRO_DIR)/$(RUN_CMD) - install: distro cp $(DISTRO_DIR)/* $(WWW_DIR) -clean: - rm -rf $(DISTRO_DIR) *.omc - -readme: - markdown $(README) - log: sudo tail -F /var/log/apache2/error.log diff --git a/OMakeroot b/OMakeroot deleted file mode 100644 index 8b6ba9e..0000000 --- a/OMakeroot +++ /dev/null @@ -1,14 +0,0 @@ -include $(STDLIB)/build/Common -include $(STDLIB)/build/OCaml -DefineCommandVars(.) - -# Use Camlp4 -UseCamlp4(packs, files) = - OCAMLPACKS += $(packs) - OCAMLFINDFLAGS += -syntax camlp4o - $(addsuffix .cmx, $(files)): - $(addsuffix .o, $(files)): - $(addsuffix .cmi, $(files)): - $(addsuffix .cmo, $(files)): - -.SUBDIRS: . diff --git a/dune-project b/dune-project new file mode 100644 index 0000000..f1fddc2 --- /dev/null +++ b/dune-project @@ -0,0 +1,17 @@ +(lang dune 2.0) +(generate_opam_files) +(name rage) +(source (github perf101/rage)) +(license BSD3) +(package + (name rage) + (synopsis "RAGE, Results And Graphing Engine") + (depends + (core (>= v0.13)) + (async (>= v0.13)) + (postgresql (>= 4.5.2)) + (ocurl (>= 0.9.0)) + (ssl (= 0.5.7)) + ppx_sexp_conv + re + uri)) diff --git a/rage.opam b/rage.opam new file mode 100644 index 0000000..852228c --- /dev/null +++ b/rage.opam @@ -0,0 +1,32 @@ +# This file is generated by dune, edit dune-project instead +opam-version: "2.0" +synopsis: "RAGE, Results And Graphing Engine" +license: "BSD3" +homepage: "https://github.com/perf101/rage" +bug-reports: "https://github.com/perf101/rage/issues" +depends: [ + "dune" {>= "2.0"} + "core" {>= "v0.13"} + "async" {>= "v0.13"} + "postgresql" {>= "4.5.2"} + "ocurl" {>= "0.9.0"} + "ssl" {= "0.5.7"} + "ppx_sexp_conv" + "re" + "uri" +] +build: [ + ["dune" "subst"] {pinned} + [ + "dune" + "build" + "-p" + name + "-j" + jobs + "@install" + "@runtest" {with-test} + "@doc" {with-doc} + ] +] +dev-repo: "git+https://github.com/perf101/rage.git" diff --git a/sql/schema.sql b/sql/schema.sql index 3cdbd75..61bc6ca 100755 --- a/sql/schema.sql +++ b/sql/schema.sql @@ -23,11 +23,13 @@ create table builds ( product varchar(128) not null, branch varchar(128) not null, build_number integer not null, - build_tag varchar(128) null, + build_tag varchar(2048) null, build_date varchar(32) null, + build_is_release boolean null, + patches_applied varchar(1024) not null, primary key (build_id), - constraint builds_unique_keys unique (product, branch, build_number, build_tag) + constraint builds_unique_keys unique (product, branch, build_number, build_tag, build_is_release, patches_applied) ); grant select on builds to "www-data"; @@ -108,6 +110,7 @@ create table tc_config ( dom0_vcpus integer not null, host_pcpus integer not null, host_type varchar(16) not null, + bootmode_precedence varchar(32) not null, foreign key (job_id) references jobs(job_id), foreign key (tc_fqn) references test_cases(tc_fqn), diff --git a/src/OMakefile b/src/OMakefile deleted file mode 100644 index 662a7d1..0000000 --- a/src/OMakefile +++ /dev/null @@ -1,41 +0,0 @@ -NATIVE_ENABLED=true -USE_OCAMLFIND=true -OCAMLINCLUDES+=ocaml-sql -OCAMLPACKS=core,postgresql,str,curl,uri -OCAMLFLAGS=-g -thread -linkpkg -w +a-4-7-13-27 -principal -HANDLERS=\ - create_tiny_url_handler \ - default_handler \ - handler \ - html_handler \ - javascript_only_handler \ - json_handler \ - redirect_tiny_url_handler \ - soms_handler \ - som_data_handler \ - som_page_handler \ - std_axes_handler \ - brief_handler \ - import_page_handler \ - import_jobs_handler -FILES=\ - ocaml-sql/sql \ - $(HANDLERS) \ - main \ - place \ - utils - -.SUBDIRS: ocaml-sql - OCAMLINCLUDES=. - UseCamlp4(sexplib.syntax, sql) - clean: - make -C $(ROOT)/src/ocaml-sql clean - -UseCamlp4(sexplib.syntax, brief_handler) - -OCamlProgram($(PROGRAM), $(FILES)) - -build: $(PROGRAM) - -clean: - rm -f $(PROGRAM) *.{cmi,cmx,o,omc,opt} diff --git a/src/brief_handler.ml b/src/brief_handler.ml index d161bae..2010b4f 100644 --- a/src/brief_handler.ml +++ b/src/brief_handler.ml @@ -1,15 +1,38 @@ -open Core.Std +open Core +open Async open Utils +let () = Ssl_threads.init () +let () = + Shutdown.at_shutdown (fun () -> + Curl.global_cleanup(); + return ()) + +let config_file = Sys.(get_argv ()).(2) + +let config = + In_channel.(with_file config_file ~f:input_lines) + |> List.map ~f:(fun line -> Scanf.sscanf line "%s@=%s" (fun k v -> (k,v)) ) + |> String.Table.of_alist_exn + +let get_config key = + match String.Table.find config key with + | None -> debug (sprintf "Fatal error: Could not find config key '%s' in %s" key config_file); raise Not_found + | Some x -> x + +let rage_username = get_config "rage_username" +let rage_password = get_config "rage_password" +let product_version = get_config "product_version" + (* types of the url input arguments *) -type cols_t = (string * string list) list list with sexp -type rows_t = (string * string list) list list with sexp -type base_t = (string * string list) list with sexp -type baseline_t = int with sexp -type ctx_t = (string * string list) list with sexp -type str_lst_t = string list with sexp -type out_t = [`Html | `Wiki] with sexp -type sort_by_col_t = int with sexp +type cols_t = (string * string list) list list [@@deriving sexp] +type rows_t = (string * string list) list list [@@deriving sexp] +type base_t = (string * string list) list [@@deriving sexp] +type baseline_t = int [@@deriving sexp] +type ctx_t = (string * string list) list [@@deriving sexp] +type str_lst_t = string list [@@deriving sexp] +type out_t = [`Html | `Wiki] [@@deriving sexp] +type sort_by_col_t = int [@@deriving sexp] type result_t = Avg of float | Range of float * float * float @@ -28,29 +51,31 @@ let t ~args = object (self) method private write_body = let page_start_time = Unix.gettimeofday () in - let show_jobids = try bool_of_string (List.Assoc.find_exn params "show_jobids") with _ -> false in - let no_rounding = try bool_of_string (List.Assoc.find_exn params "no_rounding") with _ -> false in + let show_jobids = try bool_of_string (List.Assoc.find_exn ~equal:String.equal params "show_jobids") with _ -> false in + let no_rounding = try bool_of_string (List.Assoc.find_exn ~equal:String.equal params "no_rounding") with _ -> false in let progress str = debug str in (* === input === *) - let brief_id = try List.Assoc.find_exn params "id" with |_->"" in + let brief_id = try List.Assoc.find_exn ~equal:String.equal params "id" with |_->"" in let url_decode url0 = (* todo: find a more complete version in some lib *) let rec loop url_in = let decode_once_more = Str.string_match (Str.regexp "%25") url_in 0 in let url_out = List.fold_left - [ - ("%20"," ");("%22","\""); ("%28","("); ("%29",")"); (* unescape http params *) - ("%2B"," ");("%2C",","); - ("%2F","/");("%3F","?" ); ("%3D","="); ("%26","&"); - ("%25","%");("+"," "); ("%3E",">"); ("%3C","<"); - ("%3A",":");("&","&");(""","\""); - (">",">");("<","<"); - ] - ~init:url_in - ~f:(fun acc (f,t)->(Str.global_replace (Str.regexp f) t acc)) (* f->t *) + [ + ("%20"," ");("%22","\""); ("%28","("); ("%29",")"); (* unescape http params *) + ("%2B"," ");("%2C",","); + ("%2F","/");("%3F","?" ); ("%3D","="); ("%26","&"); + ("%25","%");("+"," "); ("%3E",">"); ("%3C","<"); + ("%3A",":");("&","&");(""","\""); + ("%2d","-"); + (">",">");("<","<"); + ("&45;","-");("+","%2b") + ] + ~init:url_in + ~f:(fun acc (f,t)->(Str.global_replace (Str.regexp f) t acc)) (* f->t *) in (* loop once more if a %25 was found *) if decode_once_more then loop url_out else url_out @@ -59,61 +84,108 @@ let t ~args = object (self) in let html_encode html = (* todo: find a more complete version in some lib *) List.fold_left - [ - (">",">");("<","<"); (* escape html text *) - ] - ~init:html + [ + (">",">");("<","<"); (* escape html text *) + ] + ~init:html ~f:(fun acc (f,t)->(Str.global_replace (Str.regexp f) t acc)) (* f->t *) in let parse_url args = let key k = Str.replace_first (Str.regexp "/\\?") "" k in List.map ~f:(fun p -> - match String.split ~on:'=' p with - | k::vs -> (key k), (String.concat ~sep:"=" vs) - | [] -> failwith "k should be present") - (String.split ~on:'&' (url_decode args)) + match String.split ~on:'=' p with + | k::vs -> (key k), (String.concat ~sep:"=" vs) + | [] -> failwith "k should be present") + (String.split ~on:'&' (url_decode args)) in (* extra input from urls *) let is_digit id = Str.string_match (Str.regexp "[0-9]+") id 0 in let html_of_url url = - try - let conn = Curl.init() and write_buff = Buffer.create 16384 in - Curl.set_writefunction conn (fun x->Buffer.add_string write_buff x; String.length x); - Curl.set_url conn url; - Curl.perform conn; - Curl.global_cleanup(); - Buffer.contents write_buff; - with _ -> sprintf "error fetching url %s" url + In_thread.run ~name:"Fetch url" (fun () -> + try + let conn = Curl.init() and write_buff = Buffer.create 16384 in + Curl.set_writefunction conn (fun x->Buffer.add_string write_buff x; String.length x); + Curl.set_url conn url; + Curl.set_username conn rage_username; + Curl.set_password conn rage_password; + Curl.perform conn; + Curl.cleanup conn; + Buffer.contents write_buff; + with _ -> sprintf "error fetching url %s" url) in let fetch_brief_params_from_url url = (* simple fetch using confluence page with brief_params inside the "code block" macro in the page *) - let html = html_of_url url in + let%map html = html_of_url url in let html = Str.global_replace (Str.regexp "\n") "" html in (*remove newlines from html*) let has_match = Str.string_match (Str.regexp ".*
]*>\\([^<]+\\)<") html 0 in (*find the "code block" in the page*)
if not has_match
- then (Printf.printf "Error: no '{code}' block found in %s" url; raise Not_found)
- else
- try Str.matched_group 1 html
- with Not_found -> (debug "not found"; raise Not_found)
+ then (printf "Error: no '{code}' block found in %s" url; raise Not_found)
+ else
+ try Str.matched_group 1 html
+ with Not_found -> (debug "not found"; raise Not_found)
in
let fetch_brief_params_from_db id =
let query = sprintf "select brief_params from briefs where brief_id='%s'" id in
- (Sql.exec_exn ~conn ~query)#get_all.(0).(0)
+ let%map r = Postgresql_async.exec_exn_get_all ~conn ~query in r.(0).(0)
in
- let fetch_brief_params_from_suite ?(branch="refs/heads/master") id =
+ let fetch_suite id branch =
let url = sprintf "https://code.citrite.net/projects/XRT/repos/xenrt/raw/suites/%s?at=%s" id (Uri.pct_encode branch) in
debug (sprintf "Fetching from suite %s" url);
- let html = html_of_url url in
- let html = Str.global_replace (Str.regexp "\n") "" html in (*remove newlines from html*)
- (* Look for comments and concatenate their contents *)
+ let%map r = html_of_url url in r, url in
+ let pattern = Str.regexp "\\([^=]+\\)=\\([^<]+\\)" in
+ let include_rex = Str.regexp "]*\\)-->" in
+ ignore (Str.global_substitute rex f html);
+ List.rev !rage_str
+ in
+ let rec fetch_parameters_from inc ~branch =
+ let b = Buffer.create 80 in
+ let lookup var =
+ debug ("Lookup include variable: " ^ var);
+ match var with
+ | "PRODUCT_VERSION" -> product_version
+ | other -> "_"
+ in
+ Caml.Buffer.add_substitute b lookup inc;
+ let inc = Buffer.contents b in
+ let%bind html, _ = fetch_suite inc branch in
+ let%map includes = includes html ~branch in
+ let rage_str = ref [] in
+ let f str = rage_str := (Str.matched_group 1 str, Str.matched_group 2 str) :: !rage_str; "" in
ignore (Str.global_substitute pattern f html);
- let rows = List.rev !rage_str |> String.concat ~sep:"\n" in
- "rows=(" ^ rows ^ ")"
+ List.rev !rage_str |> List.append includes
+ and includes html ~branch =
+ let%map r = find_matches html include_rex
+ |> Deferred.List.concat_map ~how:`Parallel ~f:(fetch_parameters_from ~branch) in
+ debug (sprintf "include parameters: %s"
+ (List.map ~f:(fun (k,v) -> sprintf "%s=%s" k v) r |> String.concat ~sep:","));
+ r
+ in
+ let fetch_brief_params_from_suite ?(branch="refs/heads/master") id =
+ let%bind html, url = fetch_suite id branch in
+ let html = Str.global_replace (Str.regexp "\n") "" html in (*remove newlines from html*)
+ let find_matches = find_matches html in
+ (* Look for comments and concatenate their contents *)
+ let pattern = Str.regexp "" in
+ let rows = find_matches pattern |> String.concat ~sep:"\n" in
+ let%map includes = includes html ~branch in
+ let lookup k =
+ if String.(uppercase k = k) then
+ match List.Assoc.find ~equal:String.equal includes k with
+ | Some v -> v
+ | None ->
+ failwith (Printf.sprintf "Cannot resolve variable '%s' in %s" k url)
+ else "$" ^ k
+ in
+ let b = Buffer.create (String.length rows) in
+ Buffer.add_string b "rows=(";
+ Caml.Buffer.add_substitute b lookup rows;
+ Buffer.add_string b ")";
+ Buffer.contents b
in
let fetch_brief_params_from id =
let xs = if is_digit id then fetch_brief_params_from_db id
@@ -130,27 +202,27 @@ let t ~args = object (self)
let title_of_id id =
if is_digit id then
let query = sprintf "select brief_desc from briefs where brief_id='%s'" id in
- (Sql.exec_exn ~conn ~query)#get_all.(0).(0)
+ let%map r = Postgresql_async.exec_exn_get_all ~conn ~query in r.(0).(0)
else
- ""
+ return ""
in
let get_input_rows_from_id id fn =
- let brief_params_from = fetch_brief_params_from id in
+ let%bind brief_params_from = fetch_brief_params_from id in
let args = parse_url brief_params_from in
- let _,_input_rows,_,_,_,_ = fn args in
+ let%map _,_input_rows,_,_,_,_ = fn args in
_input_rows
in
let rec get_input_values args =
- let params_cols=(try url_decode (List.Assoc.find_exn args "cols") with |_-> "") in
- let params_rows=(try url_decode (List.Assoc.find_exn args "rows") with |_-> "") in
- let params_base=(try url_decode (List.Assoc.find_exn args "base") with |_-> "") in
- let params_baseline=(try url_decode (List.Assoc.find_exn args "baseline") with |_-> "") in
- let params_out=(try url_decode (List.Assoc.find_exn args "out") with |_-> "") in
- let params_sort_by_col=(try url_decode (List.Assoc.find_exn args "sort_by_col") with |_-> "") in
- let params_add_rows_from=(try url_decode (List.Assoc.find_exn args k_add_rows_from) with |_-> "") in
+ let params_cols=(try url_decode (List.Assoc.find_exn ~equal:String.equal args "cols") with |_-> "") in
+ let params_rows=(try url_decode (List.Assoc.find_exn ~equal:String.equal args "rows") with |_-> "") in
+ let params_base=(try url_decode (List.Assoc.find_exn ~equal:String.equal args "base") with |_-> "") in
+ let params_baseline=(try url_decode (List.Assoc.find_exn ~equal:String.equal args "baseline") with |_-> "") in
+ let params_out=(try url_decode (List.Assoc.find_exn ~equal:String.equal args "out") with |_-> "") in
+ let params_sort_by_col=(try url_decode (List.Assoc.find_exn ~equal:String.equal args "sort_by_col") with |_-> "") in
+ let params_add_rows_from=(try url_decode (List.Assoc.find_exn ~equal:String.equal args k_add_rows_from) with |_-> "") in
let attempt ~f a =
try f()
@@ -163,7 +235,7 @@ let t ~args = object (self)
(* eg.: input_cols_sexp="(((machine_name(xrtuk-08-02 xrtuk-08-04))(active_session_count(1)))((machine_name(xrtuk-08-02 xrtuk-08-04)))((machine_name(xrtuk-08-02 xrtuk-08-04))(active_session_count(2 3)))((machine_name(xrtuk-08-02 xrtuk-08-04))(active_session_count(1 2 3))(soms(288))))" *)
let input_cols =
- if params_cols <> "" then
+ if String.(params_cols <> "") then
attempt ~f:(fun ()->cols_t_of_sexp (Sexp.of_string params_cols) ) "cols"
else (*default value *)
[]
@@ -172,31 +244,31 @@ let t ~args = object (self)
printf " \n" (html_encode params_rows);
let input_rows =
- if params_rows <> "" then
+ if String.(params_rows <> "") then
attempt ~f:(fun ()->rows_t_of_sexp (Sexp.of_string params_rows)) "rows"
else (*default value *)
[]
in
printf " \n" (html_encode (Sexp.to_string (sexp_of_rows_t input_rows)));
- let extra_input_rows_from = (* list of rows_t *)
+ let%map extra_input_rows_from = (* list of rows_t *)
let ids = Str.split (Str.regexp ",") params_add_rows_from in
- List.map ids ~f:(fun id-> get_input_rows_from_id id get_input_values)
+ Deferred.List.map ~how:`Parallel ids ~f:(fun id-> get_input_rows_from_id id get_input_values)
in
-(*
+ (*
printf " \n" (html_encode (List.fold_left extra_input_rows_from ~init:"" ~f:(fun extra_input_row->(Sexp.to_string (sexp_of_rows_t extra_input_row)))));
-*)
+ *)
let input_rows = List.concat (input_rows :: extra_input_rows_from) in
printf " \n" (html_encode (Sexp.to_string (sexp_of_rows_t input_rows)));
(* base context is used to fill any context gap not expressed in row and column contexts
eg. [("build_number",[44543;55432]);("job_id",[1000;4000]);("number_of_cpus",[1]);...]
-- append (OR) the results of each element in the list
- TODO: is base context restrictive or conjuntive, ie does it restrict possible contexts in
- the cells or does it contribute to them with lower-priority than rows and col contexts?
- TODO: use intersection between base_context and input_cols and input_rows
- *)
+ TODO: is base context restrictive or conjuntive, ie does it restrict possible contexts in
+ the cells or does it contribute to them with lower-priority than rows and col contexts?
+ TODO: use intersection between base_context and input_cols and input_rows
+ *)
let input_base_context =
- if params_base <> "" then
+ if String.(params_base <> "") then
attempt ~f:(fun ()->base_t_of_sexp (Sexp.of_string params_base)) "base"
else (*default value *)
[]
@@ -204,48 +276,49 @@ let t ~args = object (self)
printf " \n" (Sexp.to_string (sexp_of_base_t input_base_context));
let baseline_col_idx =
- if params_baseline <> "" then
- attempt ~f:(fun ()->baseline_t_of_sexp (Sexp.of_string params_baseline)) "baseline"
- else (*default value *)
- 0
+ if String.(params_baseline <> "") then
+ attempt ~f:(fun ()->baseline_t_of_sexp (Sexp.of_string params_baseline)) "baseline"
+ else (*default value *)
+ 0
in
printf " \n" (Sexp.to_string (sexp_of_baseline_t baseline_col_idx));
let out =
- if params_out <> "" then
- attempt ~f:(fun ()->out_t_of_sexp (Sexp.of_string (String.capitalize params_out))) "out"
- else (*default value *)
- `Html
+ if String.(params_out <> "") then
+ attempt ~f:(fun ()->out_t_of_sexp (Sexp.of_string (String.capitalize params_out))) "out"
+ else (*default value *)
+ `Html
in
printf " \n" (params_out) (Sexp.to_string (sexp_of_out_t out));
let sort_by_col =
- if params_sort_by_col <> "" then
- Some (attempt ~f:(fun ()->sort_by_col_t_of_sexp (Sexp.of_string (String.capitalize params_sort_by_col))) "sort_by_col")
- else (*default value *)
- None
+ if String.(params_sort_by_col <> "") then
+ Some (attempt ~f:(fun ()->sort_by_col_t_of_sexp (Sexp.of_string (String.capitalize params_sort_by_col))) "sort_by_col")
+ else (*default value *)
+ None
in
(input_cols, input_rows, input_base_context, baseline_col_idx, out, sort_by_col)
in
- let args =
- if brief_id = "" then params
+ let%bind args =
+ if String.(brief_id = "") then return params
else
let replace params default_params=
List.fold_left (* if params present, use it preferrably over the default params *)
(parse_url default_params)
~init:[]
- ~f:(fun acc (k,v)->match List.find params ~f:(fun (ko,_)->k=ko) with|None->(k,v)::acc|Some o->o::acc)
+ ~f:(fun acc (k,v)->match List.find params ~f:(fun (ko,_)->String.(k=ko)) with|None->(k,v)::acc|Some o->o::acc)
in
- List.fold_left params ~init:(replace params (fetch_brief_params_from brief_id)) ~f:(fun acc (k,v)->
- match List.find acc ~f:(fun (ka,_)->k=ka) with
- |None->(k,v)::acc (* if params contains a k not in the db, add this k to args *)
- |Some _->acc
- )
+ let%map brief_params = fetch_brief_params_from brief_id in
+ List.fold_left params ~init:(replace params brief_params) ~f:(fun acc (k,v)->
+ match List.find acc ~f:(fun (ka,_)->String.(k=ka)) with
+ |None->(k,v)::acc (* if params contains a k not in the db, add this k to args *)
+ |Some _->acc
+ )
in
(* === process === *)
- let input_cols, input_rows, input_base_context, baseline_col_idx, out, sort_by_col =
+ let%bind input_cols, input_rows, input_base_context, baseline_col_idx, out, sort_by_col =
get_input_values args
in
@@ -258,230 +331,233 @@ let t ~args = object (self)
match sort_by_col with
| None -> ()
| Some sort_by_col ->
- if sort_by_col >= List.length input_cols then
- failwith (sprintf "Sort-by column is %d but there are only %d columns" sort_by_col (List.length input_cols));
+ if sort_by_col >= List.length input_cols then
+ failwith (sprintf "Sort-by column is %d but there are only %d columns" sort_by_col (List.length input_cols));
end;
let soms_of_tc tc_fqn =
let query = sprintf "select som_id from soms where tc_fqn='%s'" tc_fqn in
- Array.to_list (Array.map (Sql.exec_exn ~conn ~query)#get_all ~f:(fun x->x.(0)))
+ let%map a = Postgresql_async.exec_exn_get_all ~conn ~query in
+ Array.to_list (Array.map a ~f:(fun x->x.(0)))
in
+ let soms_of_tc = Deferred.Memo.general (module String) soms_of_tc in
let rec_of_som som_id =
let query = sprintf "select som_name,tc_fqn,more_is_better,units,positive from soms where som_id='%s'" som_id in
- (Sql.exec_exn ~conn ~query)#get_all.(0)
+ let%map r = Postgresql_async.exec_exn_get_all ~conn ~query in r.(0)
in
- let name_of_som som_id = (rec_of_som som_id).(0) in
- let tc_of_som som_id = (rec_of_som som_id).(1) in
- let more_is_better_of_som som_id = (rec_of_som som_id).(2) in
- let unit_of_som som_id = (rec_of_som som_id).(3) in
+ let rec_of_som = Deferred.Memo.general (module String) rec_of_som in
+ let rec_of_som_id_n som_id n =
+ let%map r = unstage(rec_of_som) som_id in r.(n) in
+ let name_of_som som_id = rec_of_som_id_n som_id 0 in
+ let tc_of_som som_id = rec_of_som_id_n som_id 1 in
+ let more_is_better_of_som som_id = rec_of_som_id_n som_id 2 in
+ let unit_of_som som_id = rec_of_som_id_n som_id 3 in
let has_table table_name =
let query = sprintf "select table_name from information_schema.tables where table_schema='public' and table_name='%s'" table_name in
- (Array.to_list (Sql.exec_exn ~conn ~query)#get_all) <> []
+ let%map a = Postgresql_async.exec_exn_get_all ~conn ~query in
+ not @@ List.is_empty (Array.to_list a)
in
+ let has_table = Deferred.Memo.general (module String) has_table in
let columns_of_table table_name =
let query = sprintf "select column_name from information_schema.columns where table_name='%s'" table_name in
- Array.to_list (Array.map (Sql.exec_exn ~conn ~query)#get_all ~f:(fun x->x.(0)))
+ let%map a = Postgresql_async.exec_exn_get_all ~conn ~query in
+ Array.to_list (Array.map a ~f:(fun x->x.(0)))
in
+ let columns_of_table = Deferred.Memo.general (module String) columns_of_table in
let contexts_of_som_id som_id =
- (List.filter
- (columns_of_table (sprintf "som_config_%s" som_id))
- ~f:(fun e->e<>"som_config_id")
+ let%map cols = unstage(columns_of_table) (sprintf "som_config_%s" som_id) in
+ (List.filter cols
+ ~f:(fun e->String.(e<>"som_config_id"))
)
in
let contexts_of_tc_fqn tc_fqn =
- (List.filter
- (columns_of_table (sprintf "tc_config_%s" tc_fqn))
- ~f:(fun e->e<>"tc_config_id")
- )
- in
- let contexts_of_tc =
- (List.filter
- (columns_of_table "tc_config")
- ~f:(fun e->not (List.mem ["tc_fqn";"tc_config_id";"machine_id"] e))
+ let%map cols = unstage(columns_of_table) (sprintf "tc_config_%s" tc_fqn) in
+ (List.filter cols
+ ~f:(fun e->String.(e<>"tc_config_id"))
)
in
let url_of_t t =
let query = sprintf "select url from tiny_urls where key=%s" t in
- (Sql.exec_exn ~conn ~query)#get_all.(0).(0)
- in
- (*
- let all_contexts_of_tc tc_fqn =
- let tc_contexts =
- (List.filter
- (columns_of_table "tc_config")
- ~f:(fun e->not (List.mem e ["tc_fqn";"tc_config_id";"machine_id"]))
- )@
- (List.filter
- (columns_of_table (sprintf "tc_config_%d" tc_fqn))
- ~f:(fun e->e<>"tc_config_id")
- )
- in
- List.map
- (List.map (soms_of_tc tc_id) ~f:contexts_of_som)
- ~f:(fun som_contexts->tc_contexts @ som_contexts)
+ let%map r = Postgresql_async.exec_exn_get_all ~conn ~query in r.(0).(0)
in
- *)
- let contexts_of_machine = List.filter (columns_of_table "machines") ~f:(fun e->e<>"machine_id") in
- let contexts_of_build = List.filter (columns_of_table "builds") ~f:(fun e->e<>"build_id") in
- let values_of cs ~at:cs_f = List.filter cs ~f:(fun (k,v)->List.mem cs_f k) in
+ let%bind contexts_of_tc =
+ let%map cols = unstage(columns_of_table) "tc_config" in
+ (List.filter cols
+ ~f:(fun e->not (List.mem ~equal:String.equal ["tc_fqn";"tc_config_id";"machine_id"] e))
+ )
+ and contexts_of_machine =
+ let%map cols = unstage(columns_of_table) "machines" in
+ List.filter cols
+ ~f:(fun e->String.(e<>"machine_id"))
+ and contexts_of_build =
+ let%map cols = unstage(columns_of_table) "builds" in
+ List.filter cols
+ ~f:(fun e->String.(e<>"build_id")) in
+ let values_of cs ~at:cs_f = List.filter cs ~f:(fun (k,v)->List.mem ~equal:String.equal cs_f k) in
-(*
+ (*
let latest_build_of_branch branch =
let query = sprintf "select max(build_number) from builds where branch='%s'" branch in
- (Sql.exec_exn ~conn ~query)#get_all.(0).(0)
- in
+(Postgresql_async.exec_exn_get_all ~conn ~query).(0).(0)
+in
*)
let builds_of_branch branch =
let query = sprintf "select distinct build_number from builds where branch='%s' order by build_number desc" branch in
- List.map (Array.to_list ((Sql.exec_exn ~conn ~query)#get_all)) ~f:(fun x->x.(0))
- in
-(* this query is better than builds_of_branch but it is too slow so cannot be used
- let latest_build_in_branch branch =
- let query = sprintf "select max(build_number) from builds,measurements,jobs where branch='%s' and measurements.job_id = jobs.job_id and jobs.build_id = builds.build_id" branch in
- (Sql.exec_exn ~conn ~query)#get_all.(0).(0)
+ let%map a = Postgresql_async.exec_exn_get_all ~conn ~query in
+ List.map (Array.to_list a) ~f:(fun x->x.(0))
in
-*)
+ (* this query is better than builds_of_branch but it is too slow so cannot be used
+ let latest_build_in_branch branch =
+ let query = sprintf "select max(build_number) from builds,measurements,jobs where branch='%s' and measurements.job_id = jobs.job_id and jobs.build_id = builds.build_id" branch in
+ (Postgresql_async.exec_exn_get_all ~conn ~query).(0).(0)
+ in
+ *)
(*TODO: touch each element of the context when it is used; if an element is not used at the end of this function,
- then raise an error indicating that probably there's a typo in the context element
- *)
+ then raise an error indicating that probably there's a typo in the context element
+ *)
let measurements_of_cell context =
- let get e ctx = match List.find_exn ctx ~f:(fun (k,v)->e=k) with |k,v->v in
- let measurements_of_som som_id =
- let has_table_som_id som_id = has_table (sprintf "som_config_%s" som_id) in
- let tc_fqn = tc_of_som som_id in
- let query = "select sj.job_id, m.result from measurements_2 m join soms_jobs sj on m.som_job_id=sj.id "
- ^(sprintf "join tc_config_%s on m.tc_config_id=tc_config_%s.tc_config_id " tc_fqn tc_fqn)
- ^(if has_table_som_id som_id then
- (sprintf "join som_config_%s on m.som_config_id=som_config_%s.som_config_id " som_id som_id)
- else ""
- )
- ^"join tc_config on sj.job_id=tc_config.job_id "
- ^"join machines on tc_config.machine_id=machines.machine_id "
- ^"join jobs on tc_config.job_id=jobs.job_id "
- ^"join builds on jobs.build_id=builds.build_id "
- ^"where "
- ^(sprintf "sj.som_id=%s " som_id)
- ^(List.fold_left (values_of context ~at:contexts_of_machine) ~init:"" ~f:(fun acc (k,vs)->
- match vs with []->acc|_->
- sprintf "%s and (%s) " acc
- (List.fold_left vs ~init:"" ~f:(fun acc2 v->
- sprintf "%s%smachines.%s='%s' " acc2 (match acc2 with |""->""|_->"or ") k v
- ))
- ))
- ^(if has_table_som_id som_id then
- (List.fold_left (values_of context ~at:(contexts_of_som_id som_id)) ~init:"" ~f:(fun acc (k,vs)->
- match vs with []->acc|_->
- sprintf "%s and (%s) " acc
- (List.fold_left vs ~init:"" ~f:(fun acc2 v->
- sprintf "%s%ssom_config_%s.%s='%s' " acc2 (match acc2 with |""->""|_->"or ") som_id k v
- ))
- ))
- else ""
- )
- ^(List.fold_left (values_of context ~at:(contexts_of_tc_fqn tc_fqn)) ~init:"" ~f:(fun acc (k,vs)->
- match vs with []->acc|_->
- sprintf "%s and (%s) " acc
- (List.fold_left vs ~init:"" ~f:(fun acc2 v->
- sprintf "%s%stc_config_%s.%s='%s' " acc2 (match acc2 with |""->""|_->"or ") tc_fqn k v
- ))
- ))
- ^(List.fold_left (values_of context ~at:(contexts_of_tc)) ~init:"" ~f:(fun acc (k,vs)->
- match vs with []->acc|_->
- sprintf "%s and (%s) " acc
- (List.fold_left vs ~init:"" ~f:(fun acc2 v->
- sprintf "%s%stc_config.%s='%s' " acc2 (match acc2 with |""->""|_->"or ") k v
- ))
- ))
- ^(List.fold_left (values_of context ~at:(contexts_of_build)) ~init:"" ~f:(fun acc (k,vs)->
- match vs with []->acc|_->
- sprintf "%s and (%s) " acc
- (List.fold_left vs ~init:"" ~f:(fun acc2 v->
- sprintf "%s%sbuilds.%s='%s' " acc2 (match acc2 with |""->""|_->"or ") k v
- ))
- ))
- in
- Array.to_list (Array.map (Sql.exec_exn ~conn ~query)#get_all ~f:(fun x->{job=int_of_string x.(0); value=x.(1)}))
+ let get e ctx = List.Assoc.find ~equal:String.equal ctx e in
+ let measurements_of_som som_id =
+ let%bind has_table_som_id = unstage(has_table) (sprintf "som_config_%s" som_id)
+ and tc_fqn = tc_of_som som_id
+ and contexts_of_this_som_id = contexts_of_som_id som_id in
+ let%bind contexts_of_this_tc_fqn = contexts_of_tc_fqn tc_fqn in
+ let query = "select sj.job_id, m.result from measurements_2 m join soms_jobs sj on m.som_job_id=sj.id "
+ ^(sprintf "join tc_config_%s on m.tc_config_id=tc_config_%s.tc_config_id " tc_fqn tc_fqn)
+ ^(if has_table_som_id then
+ (sprintf "join som_config_%s on m.som_config_id=som_config_%s.som_config_id " som_id som_id)
+ else ""
+ )
+ ^"join tc_config on sj.job_id=tc_config.job_id "
+ ^"join machines on tc_config.machine_id=machines.machine_id "
+ ^"join jobs on tc_config.job_id=jobs.job_id "
+ ^"join builds on jobs.build_id=builds.build_id "
+ ^"where "
+ ^(sprintf "sj.som_id=%s " som_id)
+ ^(List.fold_left (values_of context ~at:contexts_of_machine) ~init:"" ~f:(fun acc (k,vs)->
+ match vs with []->acc|_->
+ sprintf "%s and (%s) " acc
+ (List.fold_left vs ~init:"" ~f:(fun acc2 v->
+ sprintf "%s%smachines.%s='%s' " acc2 (match acc2 with |""->""|_->"or ") k v
+ ))
+ ))
+ ^(if has_table_som_id then
+ (List.fold_left (values_of context ~at:contexts_of_this_som_id) ~init:"" ~f:(fun acc (k,vs)->
+ match vs with []->acc|_->
+ sprintf "%s and (%s) " acc
+ (List.fold_left vs ~init:"" ~f:(fun acc2 v->
+ sprintf "%s%ssom_config_%s.%s='%s' " acc2 (match acc2 with |""->""|_->"or ") som_id k v
+ ))
+ ))
+ else ""
+ )
+ ^(List.fold_left (values_of context ~at:contexts_of_this_tc_fqn) ~init:"" ~f:(fun acc (k,vs)->
+ match vs with []->acc|_->
+ sprintf "%s and (%s) " acc
+ (List.fold_left vs ~init:"" ~f:(fun acc2 v->
+ sprintf "%s%stc_config_%s.%s='%s' " acc2 (match acc2 with |""->""|_->"or ") tc_fqn k v
+ ))
+ ))
+ ^(List.fold_left (values_of context ~at:(contexts_of_tc)) ~init:"" ~f:(fun acc (k,vs)->
+ match vs with []->acc|_->
+ sprintf "%s and (%s) " acc
+ (List.fold_left vs ~init:"" ~f:(fun acc2 v->
+ sprintf "%s%stc_config.%s='%s' " acc2 (match acc2 with |""->""|_->"or ") k v
+ ))
+ ))
+ ^(List.fold_left (values_of context ~at:(contexts_of_build)) ~init:"" ~f:(fun acc (k,vs)->
+ match vs with []->acc|_->
+ sprintf "%s and (%s) " acc
+ (List.fold_left vs ~init:"" ~f:(fun acc2 v->
+ sprintf "%s%sbuilds.%s='%s' " acc2 (match acc2 with |""->""|_->"or ") k v
+ ))
+ ))
in
- (* add measurements for each one of the soms in the cell *)
- try
- List.concat (List.map ~f:measurements_of_som (get "soms" context))
- with Not_found ->
- failwith (sprintf "Could not find 'soms' in context; keys are [%s]" (List.map ~f:fst context |> String.concat ~sep:", "));
+ let%map a = Postgresql_async.exec_exn_get_all ~conn ~query in
+ Array.to_list (Array.map a ~f:(fun x->{job=int_of_string x.(0); value=x.(1)}))
+ in
+ (* add measurements for each one of the soms in the cell *)
+ match get "soms" context with
+ | Some som ->
+ Deferred.List.concat_map ~how:`Parallel ~f:measurements_of_som som
+ | None ->
+ failwith (sprintf "Could not find 'soms' in context; keys are [%s]" (List.map ~f:fst context |> String.concat ~sep:", "));
in
let context_of base row col =
(* we use intersection to obtain the result when the same context is present in more than one input source *)
List.fold_left (base @ row @ col) ~init:[] ~f:(fun acc (ck,cv)->
- let x,ys = List.partition_tf ~f:(fun (k,v)->k=ck) acc in
- match x with
+ let x,ys = List.partition_tf ~f:(fun (k,v)->String.(k=ck)) acc in
+ match x with
|(k,v)::[]->(* context already in acc, intersect the values *)
- if k<>ck then (failwith (sprintf "k=%s <> ck=%s" k ck));
- (k, List.filter cv ~f:(fun x->List.mem v x))::ys
+ if String.(k<>ck) then (failwith (sprintf "k=%s <> ck=%s" k ck));
+ (k, List.filter cv ~f:(fun x->List.mem ~equal:String.equal v x))::ys
|[]->(* context not in acc, just add it *)
(ck,cv)::ys
|x->(* error *)
failwith (sprintf "More than one element with the same context")
- )
+ )
in
let expand_latest_build_of_branch c_kvs =
let k_branch = "branch" in
let k_build_number = "build_number" in
let v_latest_in_branch = "latest_in_branch" in
- match List.find ~f:(fun (k,vs)->k=k_branch) c_kvs with
- | None -> [c_kvs]
+ match List.find ~f:(fun (k,vs)->String.(k=k_branch)) c_kvs with
+ | None -> return [c_kvs]
| Some (_,branches) ->
- if List.length branches < 1
- then
- [] (* no branches provided, no results *)
- else
- (* list of all builds in all branches provided *)
+ if List.length branches < 1
+ then
+ return [] (* no branches provided, no results *)
+ else
+ (* list of all builds in all branches provided *)
- (* this is the most straightforward way of obtaining the max build of a branch but this query is too slow and cannot be used
- let builds_of_branches = [latest_build_in_branch (List.nth_exn branches 0)] in (*TODO: handle >1 branches in context*)
- *)
+ (* this is the most straightforward way of obtaining the max build of a branch but this query is too slow and cannot be used
+ let builds_of_branches = [latest_build_in_branch (List.nth_exn branches 0)] in (*TODO: handle >1 branches in context*)
+ *)
- let has_v_latest_in_branch =
- List.exists c_kvs ~f:(fun (k,vs) -> k=k_build_number && List.exists vs ~f:(fun v->v=v_latest_in_branch))
- in
- (* if 'latest_in_branch' value is present, expand ctx into many ctxs, one for each build; otherwise, return the ctx intact *)
- if not has_v_latest_in_branch then [c_kvs]
- else (
- (* brute-force way to find the max build with measurements, to work around the slowness in the query in latest_build_in_branch *)
- let builds = builds_of_branch (List.nth_exn branches 0) in (*TODO: handle >1 branch in context*)
- let builds_of_branches = List.slice builds 0 (min 100 (List.length builds)) in (* take up to 100 elements in the list *)
- debug (sprintf "builds_of_branches=%s" (List.fold_left ~init:"" builds_of_branches ~f:(fun acc b->acc ^","^b)));
-
- List.map builds_of_branches ~f:(fun bs->
- List.map c_kvs ~f:(fun (k,vs) ->
- if k<>k_build_number then (k,vs)
- else k,(List.map vs ~f:(fun v->
- if v<>v_latest_in_branch then v else bs
- ))
- )
- ))
+ let has_v_latest_in_branch =
+ List.exists c_kvs ~f:(fun (k,vs) -> String.(k=k_build_number) && List.exists vs ~f:(fun v->String.(v=v_latest_in_branch)))
+ in
+ (* if 'latest_in_branch' value is present, expand ctx into many ctxs, one for each build; otherwise, return the ctx intact *)
+ if not has_v_latest_in_branch then return [c_kvs]
+ else (
+ (* brute-force way to find the max build with measurements, to work around the slowness in the query in latest_build_in_branch *)
+ let%map builds = builds_of_branch (List.nth_exn branches 0) in (*TODO: handle >1 branch in context*)
+ let builds_of_branches = List.slice builds 0 (min 100 (List.length builds)) in (* take up to 100 elements in the list *)
+ debug (sprintf "builds_of_branches=%s" (List.fold_left ~init:"" builds_of_branches ~f:(fun acc b->acc ^","^b)));
+
+ List.map builds_of_branches ~f:(fun bs->
+ List.map c_kvs ~f:(fun (k,vs) ->
+ if String.(k<>k_build_number) then (k,vs)
+ else k,(List.map vs ~f:(fun v->
+ if String.(v<>v_latest_in_branch) then v else bs
+ ))
+ )
+ ))
in
let c_kvs_of_tiny_url t =
- let url = url_decode (url_of_t t) in
+ let%map url = url_of_t t in
+ let url = url_decode url in
debug (sprintf "expanded tiny url t=%s => %s" t url);
(* parse and add "v_"k=value patterns in url *)
let items = parse_url url in
let kv = List.map
- (List.filter items (*filter special keys*)
- ~f:(fun (k,v)->
- (* starts with "v_" or "som" *)
- ( k="som" ||
- try Str.search_forward (Str.regexp "v_.*") k 0 = 0 with Not_found->false
- )
- && (*and doesn't have 'ALL' as a value*)
- v<>"ALL"
+ (List.filter items (*filter special keys*)
+ ~f:(fun (k,v)->
+ (* starts with "v_" or "som" *)
+ ( String.(k="som") ||
+ try Str.search_forward (Str.regexp "v_.*") k 0 = 0 with Not_found->false
+ )
+ && (*and doesn't have 'ALL' as a value*)
+ String.(v<>"ALL")
+ )
)
- )
- ~f:(fun (k,v)-> (*apply some mappings to remaining keys and values *)
- (* remove "v_" from beginning of k *)
- let new_key = Str.replace_first (Str.regexp "v_") "" k in
- let new_value = url_decode v in
- ((if new_key="som" then "soms" else new_key), new_value)
- )
+ ~f:(fun (k,v)-> (*apply some mappings to remaining keys and values *)
+ (* remove "v_" from beginning of k *)
+ let new_key = Str.replace_first (Str.regexp "v_") "" k in
+ let new_value = url_decode v in
+ ((if String.(new_key="som") then "soms" else new_key), new_value)
+ )
in
(* map (k_i,v_i) and (k_j,v_j) to (k_i,[v_i,v_j,...]) when k_i=k_j *)
@@ -490,40 +566,41 @@ let t ~args = object (self)
let ks_tbl = Hashtbl.create 128 in
List.iter kv
~f:(fun (k,v)->
- if Hashtbl.mem ks_tbl k
+ if Hashtbl.mem ks_tbl k
then Hashtbl.replace ks_tbl k (v::(Hashtbl.find ks_tbl k)) (* add new v to existing k *)
else Hashtbl.add ks_tbl k [v] (* add initial v to non-existing k *)
- );
+ );
Hashtbl.fold (fun k vs acc->(k,vs)::acc) ks_tbl []
in
kvs
in
let expand_tiny_urls c_kvs =
let k_tiny_url = "t" in
- let tiny_url = List.find c_kvs ~f:(fun (k,_) -> k=k_tiny_url) in
+ let tiny_url = List.find c_kvs ~f:(fun (k,_) -> String.(k=k_tiny_url)) in
let x = match tiny_url with
- | None -> [c_kvs]
- | Some (_,[t]) ->
- let x = c_kvs_of_tiny_url t in
- [List.fold_left
- ~init:c_kvs (* c_kvs kvs have priority over the ones in c_kvs_of_tiny_url *)
- x (* obtain url from tiny_url id, parse it and return a c_kvs *)
- ~f:(fun acc (k,vs)->
- if List.exists c_kvs ~f:(fun(_k,_)->k=_k)
- then (*prefer the one already in c_kvs, ie. do not add (k,vs) to acc*)
- acc
- else (*(k,vs) not already in c_kvs, add it *)
- (k,vs)::acc
- )
- ]
- | Some (_,_) ->
- failwith (sprintf "tiny url: only one tiny url value supported for each t")
+ | None -> return [c_kvs]
+ | Some (_,[t]) ->
+ let%map x = c_kvs_of_tiny_url t in
+ [List.fold_left
+ ~init:c_kvs (* c_kvs kvs have priority over the ones in c_kvs_of_tiny_url *)
+ x (* obtain url from tiny_url id, parse it and return a c_kvs *)
+ ~f:(fun acc (k,vs)->
+ if List.exists c_kvs ~f:(fun(_k,_)->String.(k=_k))
+ then (*prefer the one already in c_kvs, ie. do not add (k,vs) to acc*)
+ acc
+ else (*(k,vs) not already in c_kvs, add it *)
+ (k,vs)::acc
+ )
+ ]
+ | Some (_,_) ->
+ failwith (sprintf "tiny url: only one tiny url value supported for each t")
in
x
in
let expand ctx = (*expand cell context into all possible context after expanding ctx templates into values*)
- List.fold_left ~init:[ctx]
- ~f:(fun rets expand_fn->List.fold_left rets ~init:[] ~f:(fun acc ret->acc@(expand_fn ret)))
+ Deferred.List.fold ~init:[ctx]
+ ~f:(fun rets expand_fn ->
+ Deferred.List.concat_map rets ~how:`Parallel ~f:expand_fn)
[
expand_latest_build_of_branch; (* 1. value template: latest_in_branch *)
(* expand_tiny_urls;*) (* 2. key template: t -- to use a tiny link value -- already expanded in row *)
@@ -546,13 +623,13 @@ let t ~args = object (self)
(* Expand any variables defined as lists *)
let apply_definitions row =
List.map row ~f:(fun (k,vs) ->
- let new_vs = List.map vs ~f:(fun v ->
- match List.Assoc.find !deflists v with
- | None -> [v]
- | Some exp -> exp
- ) |> List.concat in
- (k, new_vs)
- ) in
+ let new_vs = List.map vs ~f:(fun v ->
+ match List.Assoc.find ~equal:String.equal!deflists v with
+ | None -> [v]
+ | Some exp -> exp
+ ) |> List.concat in
+ (k, new_vs)
+ ) in
let remove_quotes s =
let quote_re = Str.regexp "'" in
@@ -566,143 +643,138 @@ let t ~args = object (self)
let apply_substitions row =
let all_subs = transform !substitions in
List.map all_subs ~f:(fun sub ->
- (* First expand any compound variables, e.g. ("a,b", "0,A") into [("a","0"); ("b","A")] *)
- let sub = List.map sub ~f:(fun (k,v) ->
- let k_split = String.split ~on:',' k in
- let v_split = String.split ~on:',' v in
- List.mapi k_split ~f:(fun i k' ->
- let v' = List.nth_exn v_split i in
- match has_quotes v' with
- | true -> v' |> remove_quotes |> fun v' -> [(k', v')] (* inside quotes ': use as it is *)
- | false -> v' |> String.split ~on:' ' |> List.map ~f:(fun v'' -> (k', v'')) (* outside quotes ': spaces delimit items *)
- ) |> List.concat
- ) |> List.concat in
-
- progress (sprintf "current substitions: [%s]" (String.concat ~sep:", " (List.map ~f:(fun (k,v) -> sprintf "(%s, %s)" k v) sub)));
-
- (* Create a modified row applying this set of substitutions *)
- List.map row ~f:(fun (k,vs) ->
- let new_vs = List.map vs ~f:(fun v ->
- match List.filter sub ~f:(fun (v',_)->v'=v) |> List.map ~f:(fun (_,v)->v) with
- | [] -> [v]
- | sub_vs -> sub_vs
- ) |> List.concat
- in
- (k, new_vs)
- )
- )
- in
+ (* First expand any compound variables, e.g. ("a,b", "0,A") into [("a","0"); ("b","A")] *)
+ let sub = List.map sub ~f:(fun (k,v) ->
+ let k_split = String.split ~on:',' k in
+ let v_split = String.split ~on:',' v in
+ List.mapi k_split ~f:(fun i k' ->
+ let v' = List.nth_exn v_split i in
+ match has_quotes v' with
+ | true -> v' |> remove_quotes |> fun v' -> [(k', v')] (* inside quotes ': use as it is *)
+ | false -> v' |> String.split ~on:' ' |> List.map ~f:(fun v'' -> (k', v'')) (* outside quotes ': spaces delimit items *)
+ ) |> List.concat
+ ) |> List.concat in
- List.fold_left ~init:[] rows (* expand special row keys *) (*todo: this should also apply to columns *)
- ~f:(fun acc r->
- let resolve_keywords_in_row acc r =
+ progress (sprintf "current substitions: [%s]" (String.concat ~sep:", " (List.map ~f:(fun (k,v) -> sprintf "(%s, %s)" k v) sub)));
- if List.exists r ~f:(fun (k,v)->k="tcs") then (* expand tcs into soms *)
- let r_expanded = List.concat (List.map r
- ~f:(fun (k,v)->match k with
- | _ when k="tcs" -> List.concat (List.map v ~f:(fun tc->List.map (soms_of_tc tc) ~f:(fun som->("soms",[som]))))
- | _ -> (k,v)::[]
- )
- )
- in
- let soms,no_soms = List.partition_tf r_expanded ~f:(fun (k,v)->k="soms") in
- let soms = List.sort soms ~cmp:(fun (xk,xv) (yk,yv)->(int_of_string(List.hd_exn xv)) - (int_of_string(List.hd_exn yv))) in
- acc @ (List.map soms ~f:(fun som->[som] @ no_soms))
-
- else if List.exists r ~f:(fun (k,v)->k="t") then (* expand tiny links into rows kvs *)
- List.hd_exn (expand_tiny_urls r) :: acc
-
- else if List.exists r ~f:(fun (k,_)->k=k_add_rows_from) then (* add rows from other brief ids *)
- let bs = List.filter r ~f:(fun (k,_)->k=k_add_rows_from) in (* use all references. TODO: what to do with non-references in the same row??? *)
- acc @ List.concat (
- List.map bs ~f:(fun (k,vs)-> (* map one r into many potential rows *)
- List.concat (
- List.map vs ~f:(fun v-> (* map one vs into many potential rows *)
- let xs = get_input_rows_from_id v get_input_values in (* resolve each new row recursively if necessary *)
- let ys = resolve_keywords xs in
-(*
- printf "
v=%s
xs=%s
r=%s
acc=%s
ys=%s" v (Sexp.to_string (sexp_of_rows_t xs)) (Sexp.to_string (sexp_of_ctx_t r)) (Sexp.to_string (sexp_of_cols_t acc)) (Sexp.to_string (sexp_of_rows_t ys));
-*)
- ys
- )
+ (* Create a modified row applying this set of substitutions *)
+ List.map row ~f:(fun (k,vs) ->
+ let new_vs = List.map vs ~f:(fun v ->
+ match List.filter sub ~f:(fun (v',_)->String.(v'=v)) |> List.map ~f:(fun (_,v)->v) with
+ | [] -> [v]
+ | sub_vs -> sub_vs
+ ) |> List.concat
+ in
+ (k, new_vs)
)
- )
)
+ in
- else if List.exists r ~f:(fun (k,_)->k=k_for) then (* it's a for-loop! *)
- begin
- let bs = List.filter r ~f:(fun (k,_)->k=k_for) in
- List.iter bs ~f:(fun (_,v) ->
- let key = List.hd_exn v in
- let values = List.tl_exn v in
- progress (sprintf "mapping: key '%s' becomes each of [%s]" key (String.concat ~sep:", " values));
- substitions := (key, values) :: !substitions
- );
- acc
- end
-
- else if List.exists r ~f:(fun (k,_)->k=k_endfor) then (* it's the end of a for-loop! *)
- begin
- let bs = List.filter r ~f:(fun (k,_)->k=k_endfor) in
- List.iter bs ~f:(fun (_,v) ->
- substitions := match v with
- | [] ->
- begin
- progress ("unmapping unspecified variable");
- (* just pop the most recent 'for' variable *)
- match !substitions with
- | _::tl -> tl
- | _ -> failwith ("tried to pop (unspecified) variable from empty stack")
- end
- | [v] ->
- begin
- progress (sprintf "unmapping '%s'" v);
- match !substitions with
- | (hk,hvs)::tl -> if hk=v then tl else failwith (sprintf "tried to pop variable '%s' but top of stack was '%s'" v hk)
- | _ -> failwith (sprintf "tried to pop variable '%s' from empty stack" v)
- (* check the most recent 'for' variable has this name and pop it *)
- end
- | _ ->
- failwith "endfor can have either zero or one parameter"
- );
- acc
- end
-
- else if List.exists r ~f:(fun (k,_)->k=k_deflist) then (* it's a deflist *)
- begin
- let bs = List.filter r ~f:(fun (k,_)->k=k_deflist) in
- List.iter bs ~f:(fun (_,v) ->
- let key = List.hd_exn v in
- let values = List.tl_exn v in
- progress (sprintf "definition: name '%s' means array [%s]" key (String.concat ~sep:", " values));
- deflists := List.Assoc.add !deflists key values
- );
- acc
- end
+ Deferred.List.fold ~init:[] rows (* expand special row keys *) (*todo: this should also apply to columns *)
+ ~f:(fun acc r->
+ let resolve_keywords_in_row acc r =
- else (* nothing to resolve, carry on *)
- (r |> apply_substitions |> List.map ~f:apply_definitions) @ acc
+ if List.exists r ~f:(fun (k,v)->String.(k="tcs")) then (* expand tcs into soms *)
+ let%map r_expanded = Deferred.List.concat_map r
+ ~f:(fun (k,v)->match k with
+ | _ when String.(k="tcs") -> (Deferred.List.concat_map v ~f:(fun tc->
+ let%map r = unstage(soms_of_tc) tc in
+ List.map r ~f:(fun som->("soms",[som]))))
+ | _ -> (k,v)::[] |> return
+ )
+ in
+ let soms,no_soms = List.partition_tf r_expanded ~f:(fun (k,v)->String.(k="soms")) in
+ let soms = List.sort soms ~compare:(fun (xk,xv) (yk,yv)->(int_of_string(List.hd_exn xv)) - (int_of_string(List.hd_exn yv))) in
+ acc @ (List.map soms ~f:(fun som->[som] @ no_soms))
- in
- resolve_keywords_in_row acc r
- )
+ else if List.exists r ~f:(fun (k,v)->String.(k="t")) then (* expand tiny links into rows kvs *)
+ let%map lst = expand_tiny_urls r in
+ List.hd_exn lst :: acc
+
+ else if List.exists r ~f:(fun (k,_)->String.(k=k_add_rows_from)) then (* add rows from other brief ids *)
+ let bs = List.filter r ~f:(fun (k,_)->String.(k=k_add_rows_from)) in (* use all references. TODO: what to do with non-references in the same row??? *)
+ let%map r = Deferred.List.concat_map bs ~f:(fun (k,vs)-> (* map one r into many potential rows *)
+ Deferred.List.concat_map vs ~f:(fun v-> (* map one vs into many potential rows *)
+ let%bind xs = get_input_rows_from_id v get_input_values in (* resolve each new row recursively if necessary *)
+ resolve_keywords xs
+ )
+ ) in
+ acc @ r
+
+ else if List.exists r ~f:(fun (k,_)->String.(k=k_for)) then (* it's a for-loop! *)
+ begin
+ let bs = List.filter r ~f:(fun (k,_)->String.(k=k_for)) in
+ List.iter bs ~f:(fun (_,v) ->
+ let key = List.hd_exn v in
+ let values = List.tl_exn v in
+ progress (sprintf "mapping: key '%s' becomes each of [%s]" key (String.concat ~sep:", " values));
+ substitions := (key, values) :: !substitions
+ );
+ return acc
+ end
+
+ else if List.exists r ~f:(fun (k,_)->String.(k=k_endfor)) then (* it's the end of a for-loop! *)
+ begin
+ let bs = List.filter r ~f:(fun (k,_)->String.(k=k_endfor)) in
+ List.iter bs ~f:(fun (_,v) ->
+ substitions := match v with
+ | [] ->
+ begin
+ progress ("unmapping unspecified variable");
+ (* just pop the most recent 'for' variable *)
+ match !substitions with
+ | _::tl -> tl
+ | _ -> failwith ("tried to pop (unspecified) variable from empty stack")
+ end
+ | [v] ->
+ begin
+ progress (sprintf "unmapping '%s'" v);
+ match !substitions with
+ | (hk,hvs)::tl -> if String.(hk=v) then tl else failwith (sprintf "tried to pop variable '%s' but top of stack was '%s'" v hk)
+ | _ -> failwith (sprintf "tried to pop variable '%s' from empty stack" v)
+ (* check the most recent 'for' variable has this name and pop it *)
+ end
+ | _ ->
+ failwith "endfor can have either zero or one parameter"
+ );
+ return acc
+ end
+
+ else if List.exists r ~f:(fun (k,_)->String.(k=k_deflist)) then (* it's a deflist *)
+ begin
+ let bs = List.filter r ~f:(fun (k,_)->String.(k=k_deflist)) in
+ List.iter bs ~f:(fun (_,v) ->
+ let key = List.hd_exn v in
+ let values = List.tl_exn v in
+ progress (sprintf "definition: name '%s' means array [%s]" key (String.concat ~sep:", " values));
+ deflists := List.Assoc.add ~equal:String.equal !deflists key values
+ );
+ return acc
+ end
+
+ else (* nothing to resolve, carry on *)
+ return @@ (r |> apply_substitions |> List.map ~f:apply_definitions) @ acc
+
+ in
+ resolve_keywords_in_row acc r
+ )
in
- let rs = resolve_keywords input_rows in
+ let%bind rs = resolve_keywords input_rows in
progress (sprintf "table: %d lines: " (List.length rs));
let ctx_and_measurements_of_1st_cell_with_data expand_f ctx =
- let ctxs = expand_f ctx in
- let measurements_of_cells = List.find_map ctxs ~f:(fun c->let ms=measurements_of_cell c in if ms=[] then None else (Some (c,ms))) in
- match measurements_of_cells with None->ctx,[]|Some (c,ms)->c,ms
+ let%bind ctxs = expand_f ctx in
+ let measurements_of_cells = Deferred.List.find_map ctxs ~f:(fun c->let%map ms=measurements_of_cell c in if List.is_empty ms then None else (Some (c,ms))) in
+ match%map measurements_of_cells with None->ctx,[]|Some (c,ms)->c,ms
in
- let measurements_of_table =
+ let%bind measurements_of_table =
let rs_len = List.length rs in
- List.mapi rs ~f:(fun i r->
- progress (sprintf "row %d of %d..." i rs_len);
- r, (List.map cs ~f:(fun c->
- let ctx, ms = ctx_and_measurements_of_1st_cell_with_data expand (context_of b r c) in
- (r, c, ctx, ms)
- ))
- )
+ Deferred.List.mapi ~how:`Parallel rs ~f:(fun i r->
+ progress (sprintf "row %d of %d..." i rs_len);
+ let%map csr = Deferred.List.map ~how:`Parallel cs ~f:(fun c->
+ let%map ctx, ms = ctx_and_measurements_of_1st_cell_with_data expand (context_of b r c) in
+ (r, c, ctx, ms)
+ ) in r, csr
+ )
in
(* === output === *)
@@ -730,58 +802,58 @@ let t ~args = object (self)
(* round value f to the optimal decimal place according to magnitude of its stddev *)
let round f stddev =
- if Float.abs (Float.(/) stddev f) < 0.00000001 (* stddev = 0.0 doesn't work because of rounding errors in the float representation *)
+ if Float.(abs (Float.(/) stddev f) < 0.00000001) (* stddev = 0.0 doesn't work because of rounding errors in the float representation *)
then (sprintf "%f" f), f
else
- (* 0. compute magnitude of stddev relative to f *)
- let f_abs = Float.abs f in
- let magnitude = (log stddev) /. (log 10.0) in
- let newdotpos = (if is_valid magnitude then Float.to_int (if magnitude < 0.0 then Float.round_down (magnitude) else (Float.round_down magnitude) +. 1.0) else 1) in
- let f_str = sprintf "%f" f_abs in
- let dotpos = (String.index_exn f_str '.') in
- let cutpos = (dotpos - newdotpos) in
- if cutpos < 0
- then ("0",0.0) (* stddev magnitude is larger then value f *)
- else
- (* 1. round for the computed magnitude of stddev *)
- let dig_from s pos = (String.sub s ~pos:(pos+1) ~len:1) in
- let dig=dig_from f_str cutpos in
- let rounddigit,roundpos = (* round last significant value using the next digit value *)
- if dig="."
+ (* 0. compute magnitude of stddev relative to f *)
+ let f_abs = Float.abs f in
+ let magnitude = (log stddev) /. (log 10.0) in
+ let newdotpos = (if is_valid magnitude then Float.to_int (if Float.(magnitude < 0.0) then Float.round_down (magnitude) else (Float.round_down magnitude) +. 1.0) else 1) in
+ let f_str = sprintf "%f" f_abs in
+ let dotpos = (String.index_exn f_str '.') in
+ let cutpos = (dotpos - newdotpos) in
+ if cutpos < 0
+ then ("0",0.0) (* stddev magnitude is larger then value f *)
+ else
+ (* 1. round for the computed magnitude of stddev *)
+ let dig_from s pos = (String.sub s ~pos:(pos+1) ~len:1) in
+ let dig=dig_from f_str cutpos in
+ let rounddigit,roundpos = (* round last significant value using the next digit value *)
+ if String.(dig=".")
then (int_of_string (dig_from f_str (cutpos+1)),newdotpos-1)
else (int_of_string dig,if newdotpos<0 then newdotpos else newdotpos-1)
- in
- let f_rounded = if rounddigit < 5 then f_abs else f_abs +. 10.0 ** (Float.of_int roundpos) in
- (* 2. print only significant digits *)
- let f_result = (
- let f_str_rounded = sprintf "%f" f_rounded in
- let f_abs_str_rounded = (if (f_rounded<1.0)
- then (* print the rounded value up to its last significant digit *)
- String.sub f_str_rounded ~pos:0 ~len:(cutpos+1)
- else (* print the rounded value up to its last significant digit and fill the rest with 0s *)
- let dotposr = String.index_exn f_str_rounded '.' in
- sprintf "%s%s"
- (String.sub f_str_rounded ~pos:0 ~len:(cutpos+1))
- (if dotposr-(cutpos+1)>0 then (String.make (dotposr-(cutpos+1)) '0') else "")
- ) in
- (sprintf "%s%s" (if f<0.0 then if f_abs_str_rounded <> "0" then "-" else "" else "") f_abs_str_rounded)
- )
- in
- (
- (*sprintf "f_str=%s stddev=%f magnitude=%f cutpos=%d dotpos=%d newdotpos=%d dig=%s rounddigit=%d roundpos=%d f_rounded=%f f=%f %s" f_str stddev magnitude cutpos dotpos newdotpos dig rounddigit roundpos f_rounded f*)
- f_result, Float.of_string f_result
- )
+ in
+ let f_rounded = if rounddigit < 5 then f_abs else f_abs +. 10.0 ** (Float.of_int roundpos) in
+ (* 2. print only significant digits *)
+ let f_result = (
+ let f_str_rounded = sprintf "%f" f_rounded in
+ let f_abs_str_rounded = (if Float.(f_rounded<1.0)
+ then (* print the rounded value up to its last significant digit *)
+ String.sub f_str_rounded ~pos:0 ~len:(cutpos+1)
+ else (* print the rounded value up to its last significant digit and fill the rest with 0s *)
+ let dotposr = String.index_exn f_str_rounded '.' in
+ sprintf "%s%s"
+ (String.sub f_str_rounded ~pos:0 ~len:(cutpos+1))
+ (if dotposr-(cutpos+1)>0 then (String.make (dotposr-(cutpos+1)) '0') else "")
+ ) in
+ (sprintf "%s%s" (if Float.(f<0.0) then if String.(f_abs_str_rounded <> "0") then "-" else "" else "") f_abs_str_rounded)
+ )
+ in
+ (
+ (*sprintf "f_str=%s stddev=%f magnitude=%f cutpos=%d dotpos=%d newdotpos=%d dig=%s rounddigit=%d roundpos=%d f_rounded=%f f=%f %s" f_str stddev magnitude cutpos dotpos newdotpos dig rounddigit roundpos f_rounded f*)
+ f_result, Float.of_string f_result
+ )
in
let of_round avg stddev ~f0 ~f1 ~f2 =
if no_rounding then
f1 (Float.to_string avg, avg)
else
- let lower = avg -. 2.0 *. stddev in (* 2-sigma = 95% confidence assuming normal distribution *)
- let upper = avg +. 2.0 *. stddev in
- if (Float.abs avg) < Float.min_value
- then f0 ()
- else if stddev /. avg < 0.05 (* see if the relative std error is <5% *)
+ let lower = avg -. 2.0 *. stddev in (* 2-sigma = 95% confidence assuming normal distribution *)
+ let upper = avg +. 2.0 *. stddev in
+ if Float.(abs avg < min_value)
+ then f0 ()
+ else if Float.(stddev /. avg < 0.05) (* see if the relative std error is <5% *)
then f1 (round avg stddev) (* 95% confidence *)
else f2 (round lower stddev) (round avg stddev) (round upper stddev) (* 95% confidence *)
in
@@ -803,29 +875,29 @@ let t ~args = object (self)
let is_green baseline value more_is_better =
if more_is_better then
match baseline, value with
- |Avg b, Avg v-> (v>=b)
- |Avg b, Range (vl, va, vu)-> (va>=b)
- |Range (bl, ba, bu), Avg v-> (v>=ba)
- |Range (bl, ba, bu), Range (vl,va,vu)-> (va>=ba)
- else (* less is better *)
+ |Avg b, Avg v-> Float.(v>=b)
+ |Avg b, Range (vl, va, vu)-> Float.(va>=b)
+ |Range (bl, ba, bu), Avg v-> Float.(v>=ba)
+ |Range (bl, ba, bu), Range (vl,va,vu)-> Float.(va>=ba)
+ else (* less is better *)
match baseline, value with
- |Avg b, Avg v-> (v<=b)
- |Avg b, Range (vl, va, vu)-> (va<=b)
- |Range (bl, ba, bu), Avg v-> (v<=ba)
- |Range (bl, ba, bu), Range (vl,va,vu)-> (va<=ba)
+ |Avg b, Avg v-> Float.(v<=b)
+ |Avg b, Range (vl, va, vu)-> Float.(va<=b)
+ |Range (bl, ba, bu), Avg v-> Float.(v<=ba)
+ |Range (bl, ba, bu), Range (vl,va,vu)-> Float.(va<=ba)
in
let delta baseline value more_is_better =
match baseline, value with
- |Avg b, Avg v-> v -. b
- |Avg b, Range (vl, va, vu)-> va -. b
- |Range (bl, ba, bu), Avg v-> v -. ba
- |Range (bl, ba, bu), Range (vl,va,vu)-> va -. ba
+ |Avg b, Avg v-> v -. b
+ |Avg b, Range (vl, va, vu)-> va -. b
+ |Range (bl, ba, bu), Avg v-> v -. ba
+ |Range (bl, ba, bu), Range (vl,va,vu)-> va -. ba
in
let proportion baseline value more_is_better =
(delta baseline value more_is_better) /.
(match baseline with
- |Avg b-> Float.abs b
- |Range (bl, ba, bu)-> Float.abs ba)
+ |Avg b-> Float.abs b
+ |Range (bl, ba, bu)-> Float.abs ba)
in
(* pretty print a list of values as average and stddev *)
let str_stddev_of ?f1_fmt ?f2_fmt xs =
@@ -846,22 +918,22 @@ let t ~args = object (self)
|None->mt
|Some compare_col_idx->
let mt_xs, mt_0s = List.partition_tf mt
- ~f:(fun (r,cs)->
- let _,_,_,cmp_ms=List.nth_exn cs compare_col_idx in
- let _,_,_,base_ms=List.nth_exn cs baseline_col_idx in
- (List.length cmp_ms > 0) && (List.length base_ms > 0)
- )
+ ~f:(fun (r,cs)->
+ let _,_,_,cmp_ms=List.nth_exn cs compare_col_idx in
+ let _,_,_,base_ms=List.nth_exn cs baseline_col_idx in
+ (List.length cmp_ms > 0) && (List.length base_ms > 0)
+ )
in
List.sort (mt_xs) (* rows with at least one measurement *)
- ~cmp:(fun (r1,cs1) (r2,cs2) ->
- let ms cs =
- let _,_,_,cmp_ms = List.nth_exn cs compare_col_idx in
- let _,_,_,base_ms = List.nth_exn cs baseline_col_idx in
- proportion (val_stddev_of (vals_of_ms base_ms)) (val_stddev_of (vals_of_ms cmp_ms)) None
- in
- let ms1, ms2 = (Float.abs (ms cs1)),(Float.abs (ms cs2)) in
- if ms1 > ms2 then -1 else if ms2 > ms1 then 1 else 0 (* decreasing order *)
- ) @ mt_0s (* rows with no measurements stay at the end *)
+ ~compare:(fun (r1,cs1) (r2,cs2) ->
+ let ms cs =
+ let _,_,_,cmp_ms = List.nth_exn cs compare_col_idx in
+ let _,_,_,base_ms = List.nth_exn cs baseline_col_idx in
+ proportion (val_stddev_of (vals_of_ms base_ms)) (val_stddev_of (vals_of_ms cmp_ms)) None
+ in
+ let ms1, ms2 = (Float.abs (ms cs1)),(Float.abs (ms cs2)) in
+ if Float.(ms1 > ms2) then -1 else if Float.(ms2 > ms1) then 1 else 0 (* decreasing order *)
+ ) @ mt_0s (* rows with no measurements stay at the end *)
in
(* compute link to rage graph *)
@@ -870,153 +942,157 @@ let t ~args = object (self)
(* eg.: http://perf/?som=41&xaxis=numvms&show_dist=on&f_branch=1&v_build_tag=&v_dom0_memory_static_max=752&v_dom0_memory_target=(NULL)&v_cc_restrictions=f&v_memsize=256&v_vmtype=dom0 *)
let link_ctx_of_row ctxs =
List.fold_left ctxs ~init:[] ~f:(fun acc (ck,cv)->
- let x,ys = List.partition_tf ~f:(fun (k,v)->k=ck) acc in
- match x with
+ let x,ys = List.partition_tf ~f:(fun (k,v)->String.(k=ck)) acc in
+ match x with
|(k,v)::[]->(* context already in acc, union the values *)
- if k<>ck then (failwith (sprintf "link: k=%s <> ck=%s" k ck));
- (k, List.dedup (cv @ v))::ys
+ if String.(k<>ck) then (failwith (sprintf "link: k=%s <> ck=%s" k ck));
+ (k, List.dedup_and_sort ~compare:String.compare (cv @ v))::ys
|[]->(* context not in acc, just add it *)
(ck,cv)::ys
|x->(* error *)
failwith (sprintf "link: More than one element with the same context")
- )
+ )
in
let link_ctxs = (List.map (sort_table measurements_of_table) ~f:(fun (r,cs)->link_ctx_of_row (List.concat (List.map cs ~f:(fun (_,_,ctx,_)->ctx))))) in
- let link_xaxis = List.dedup (List.concat (List.map cs ~f:(fun c-> List.map c ~f:(fun (x,_)->x)))) in
- let link_xaxis = List.filter link_xaxis ~f:(fun x -> x <> "label") in
+ let link_xaxis = List.dedup_and_sort ~compare:String.compare (List.concat (List.map cs ~f:(fun c-> List.map c ~f:(fun (x,_)->x)))) in
+ let link_xaxis = List.filter link_xaxis ~f:(fun x -> String.(x <> "label")) in
(* writers *)
let rage_encode url =
List.fold_left
- [
- (" ","+"); (* escape http params according to what rage expects *)
- ]
- ~init:url
+ [
+ (" ","+"); (* escape http params according to what rage expects *)
+ ]
+ ~init:url
~f:(fun acc (f,t)->(Str.global_replace (Str.regexp f) t acc)) (* f->t *)
in
let html_writer table =
- let str_of_values vs=List.fold_left vs ~init:"" ~f:(fun acc v->if acc="" then "\""^v^"\"" else acc^", \""^v^"\"") in
+ let str_of_values vs=List.fold_left vs ~init:"" ~f:(fun acc v->if String.(acc="") then "\""^v^"\"" else acc^", \""^v^"\"") in
let str_of_ctxs ?(txtonly=false) kvs =
List.fold_left kvs ~init:"" ~f:(fun acc (k,v)->
- (sprintf "%s %s=(%s)%s\n" acc k (str_of_values v) (if txtonly then "" else "
") )
- )
+ (sprintf "%s %s=(%s)%s\n" acc k (str_of_values v) (if txtonly then "" else "
") )
+ )
in
let str_desc_of_ctxs kvs =
- List.fold_left kvs ~init:"" ~f:(fun acc (k,vs)->
- if k<>"soms" then acc else
- (sprintf "%s %s
\n" acc (List.fold_left vs ~init:"" ~f:(fun acc2 som->
- let s=sprintf "%s: %s (%s%s)" (tc_of_som som) (name_of_som som) (let u=unit_of_som som in if u="" then u else u^", ") (sprintf "%s is better" (let mb=more_is_better_of_som som in if mb="" then "none" else if mb="f" then "less" else "more")) in
- if acc="" then s else acc^","^s
- ))
+ Deferred.List.fold kvs ~init:"" ~f:(fun acc (k,vs)->
+ if String.(k<>"soms") then return acc else
+ let%map lst = Deferred.List.map ~how:`Parallel vs ~f:(fun som->
+ let%map tc = tc_of_som som
+ and u = unit_of_som som
+ and mb = more_is_better_of_som som
+ and name = name_of_som som in
+ sprintf "%s: %s (%s%s)" tc name (if String.(u="") then u else u^", ") (sprintf "%s is better" (if String.(mb="") then "none" else if String.(mb="f") then "less" else "more"))) in
+ (sprintf "%s %s
\n" acc (String.concat ~sep:"," lst))
)
- )
in
- let link ctx =
- (* link *)
- (
+ let link ctx =
+ (* link *)
+ (
(* rage is not generic enough to receive an arbirary number of soms in a link, pick just the first one *)
- let som_id=match List.find_exn ctx ~f:(fun (k,_)->k="soms") with |(k,v)->List.hd_exn v in
- (sprintf "graph" (Utils.server_name ()) som_id
- (* xaxis *)
- (List.fold_left link_xaxis ~init:"" ~f:(fun acc x->sprintf "%s%s" acc (sprintf "&xaxis=%s" x)))
- (* preset values *)
- (List.fold_left ctx ~init:"" ~f:(fun acc (k,vs)->sprintf "%s%s" acc
- (List.fold_left vs ~init:"" ~f:(fun acc2 v->sprintf "%s&v_%s=%s" acc2 k (rage_encode v))
- )
- ))
+ let som_id=match List.find_exn ctx ~f:(fun (k,_)->String.(k="soms")) with |(k,v)->List.hd_exn v in
+ (sprintf "graph" (Utils.server_name ()) som_id
+ (* xaxis *)
+ (List.fold_left link_xaxis ~init:"" ~f:(fun acc x->sprintf "%s%s" acc (sprintf "&xaxis=%s" x)))
+ (* preset values *)
+ (List.fold_left ctx ~init:"" ~f:(fun acc (k,vs)->sprintf "%s%s" acc
+ (List.fold_left vs ~init:"" ~f:(fun acc2 v->sprintf "%s&v_%s=%s" acc2 k (rage_encode v))
+ )
+ ))
))
- in
+ in
let is_more_is_better ctx =
- match List.find ctx ~f:(fun (k,_)->k="soms") with
- |None->None
+ match List.find ctx ~f:(fun (k,_)->String.(k="soms")) with
+ |None->return None
|Some (k,_vs)->(
- let rec is_mb acc vs = (match vs with
- |[]->if acc=None then None else acc
- |v::vs->(let mb = more_is_better_of_som v in
- if mb="" then is_mb acc vs (* ignore more_is_better if not defined in db *)
- else
- let mbtf = match mb with m when m="f"->false|_->true in
- match acc with
- |None->is_mb (Some mbtf) vs
- |Some _mbtf->if _mbtf=mbtf
- then is_mb (Some mbtf) vs (* more_is_better values agree between soms *)
- else None (* more_is_better values disagree between soms *)
- )
- ) in
- is_mb None _vs
- )
+ let rec is_mb acc vs = (match vs with
+ |[]-> return @@ if Option.is_none acc then None else acc
+ |v::vs->(let%bind mb = more_is_better_of_som v in
+ if String.(mb="") then is_mb acc vs (* ignore more_is_better if not defined in db *)
+ else
+ let mbtf = match mb with m when String.(m="f")->false|_->true in
+ match acc with
+ |None->is_mb (Some mbtf) vs
+ |Some _mbtf->if Bool.(_mbtf=mbtf)
+ then is_mb (Some mbtf) vs (* more_is_better values agree between soms *)
+ else return None (* more_is_better values disagree between soms *)
+ )
+ ) in
+ is_mb None _vs
+ )
in
let num_columns = (List.length cs) + 3 in
+ let%bind cells = List.map2_exn table link_ctxs ~f:(fun (r,cs) lnkctx ->
+ let%map str_desc = str_desc_of_ctxs r
+ and lst =
+ Deferred.List.mapi ~how:`Parallel cs ~f:(fun i (r,c,ctx,ms)->
+ let _,_,_,baseline_ms = List.nth_exn cs baseline_col_idx in
+ let debug_r = Sexp.to_string (sexp_of_ctx_t r)
+ and debug_c = Sexp.to_string (sexp_of_ctx_t c)
+ and context = str_of_ctxs ctx ~txtonly:true
+ and debug_ms = Sexp.to_string (sexp_of_str_lst_t (vals_of_ms ms)) in
+ let number = List.length ms in
+ let number_str = if show_jobids
+ then
+ sprintf "[%s]" (String.concat ~sep:"; " (List.map ~f:string_of_int (List.dedup_and_sort ~compare:Int.compare (jobs_of_ms ms))))
+ else
+ sprintf "(%d)" number
+ in
+ let%bind colour =
+ (if number = 0 || baseline_col_idx = i then return "" else
+ match%map is_more_is_better ctx with
+ |None->""
+ |Some mb->
+ if (List.length baseline_ms) < 1 then "black" else
+ if is_green (val_stddev_of (vals_of_ms baseline_ms)) (val_stddev_of (vals_of_ms ms)) mb then "green" else "red"
+ ) in
+ let avg = str_stddev_of (vals_of_ms ms) in
+ let%map diff =
+ (if number = 0 || baseline_col_idx = i || (List.length baseline_ms < 1) then return "" else
+ match%map is_more_is_better ctx with
+ |None->""
+ |Some mb->sprintf "(%+.0f%%)" (100.0 *. (proportion (val_stddev_of (vals_of_ms baseline_ms)) (val_stddev_of (vals_of_ms ms)) mb))
+ ) in
+ let text = sprintf "%s
%s %s" colour avg number_str diff in
+ sprintf "%s" debug_r debug_c context debug_ms text
+ ) in
+ let cells = List.fold_left ~init:"" ~f:(fun acc c_ms->(sprintf "%s %s \n" acc c_ms)) lst in
+ sprintf " %s %s %s %s \n\n"
+ (* row id/title *)
+ (str_of_ctxs r)
+ (* row description *)
+ str_desc
+ (* graph link *)
+ (link lnkctx)
+ (* cells to the right *)
+ cells
+ ) |> Deferred.List.all in
let html_table =
- sprintf " %s \n%s%s%s"
- num_columns
- (* print the base context *)
- (str_of_ctxs b)
- (* print the header *)
- (sprintf " id Description View %s "
- (List.foldi cs ~init:"" ~f:(fun i acc _ ->
- sprintf "%s %s " acc (if i=baseline_col_idx then "Baseline" else (sprintf "Comparison %d" i))
- ))
- )
- (* print the columns *)
- (sprintf " %s "
- (List.fold_left ~init:""
- ~f:(fun acc cs->sprintf "%s %s " acc (str_of_ctxs cs)) cs
- )
- )
- (* print the cells *)
- (List.fold_left ~init:"" ~f:(fun acc r_ms->acc^r_ms)
- (List.map2_exn table link_ctxs ~f:(fun (r,cs) lnkctx ->
- sprintf " %s %s %s %s \n\n"
- (* row id/title *)
- (str_of_ctxs r)
- (* row description *)
- (str_desc_of_ctxs r)
- (* graph link *)
- (link lnkctx)
- (* cells to the right *)
- (List.fold_left ~init:"" ~f:(fun acc c_ms->(sprintf "%s %s \n" acc c_ms))
- (List.mapi cs ~f:(fun i (r,c,ctx,ms)->
- let _,_,_,baseline_ms = List.nth_exn cs baseline_col_idx in
- let debug_r = Sexp.to_string (sexp_of_ctx_t r)
- and debug_c = Sexp.to_string (sexp_of_ctx_t c)
- and context = str_of_ctxs ctx ~txtonly:true
- and debug_ms = Sexp.to_string (sexp_of_str_lst_t (vals_of_ms ms)) in
- let number = List.length ms in
- let number_str = if show_jobids
- then
- sprintf "[%s]" (String.concat ~sep:"; " (List.map ~f:string_of_int (List.dedup (jobs_of_ms ms))))
- else
- sprintf "(%d)" number
- in
- let colour =
- (if number = 0 || baseline_col_idx = i then "" else
- match is_more_is_better ctx with
- |None->""
- |Some mb->
- if (List.length baseline_ms) < 1 then "black" else
- if is_green (val_stddev_of (vals_of_ms baseline_ms)) (val_stddev_of (vals_of_ms ms)) mb then "green" else "red"
- ) in
- let avg = str_stddev_of (vals_of_ms ms) in
- let diff =
- (if number = 0 || baseline_col_idx = i || (List.length baseline_ms < 1) then "" else
- match is_more_is_better ctx with
- |None->""
- |Some mb->sprintf "(%+.0f%%)" (100.0 *. (proportion (val_stddev_of (vals_of_ms baseline_ms)) (val_stddev_of (vals_of_ms ms)) mb))
- ) in
- let text = sprintf "%s
%s %s" colour avg number_str diff in
- sprintf "%s" debug_r debug_c context debug_ms text
- ))
+ sprintf " %s \n%s%s%s"
+ num_columns
+ (* print the base context *)
+ (str_of_ctxs b)
+ (* print the header *)
+ (sprintf " id Description View %s "
+ (List.foldi cs ~init:"" ~f:(fun i acc _ ->
+ sprintf "%s %s " acc (if i=baseline_col_idx then "Baseline" else (sprintf "Comparison %d" i))
+ ))
)
- ))
- )
+ (* print the columns *)
+ (sprintf " %s "
+ (List.fold_left ~init:""
+ ~f:(fun acc cs->sprintf "%s %s " acc (str_of_ctxs cs)) cs
+ )
+ )
+ (* print the cells *)
+ (String.concat ~sep:"" cells)
in
- let brief_name = if is_digit brief_id then "jim #"^brief_id else sprintf "from %s" brief_id brief_id in
- printf "Brief RAGE Report %s: %s
\n" brief_name (title_of_id brief_id);
+ let brief_name = if is_digit brief_id then "jim #"^brief_id else sprintf "from %s" brief_id brief_id in
+ let%map title = title_of_id brief_id in
+ printf "Brief RAGE Report %s: %s
\n" brief_name title;
printf "%s" "- Numbers reported at 95% confidence level from the data of existing runs\n";
printf "%s" "
- (x) indicates number of samples\n";
printf "%s" "
- (x%) indicates difference with baseline column\n";
@@ -1049,116 +1125,127 @@ let t ~args = object (self)
let wiki_writer table =
- let str_of_values vs=List.fold_left vs ~init:"" ~f:(fun acc v->if acc="" then "\""^v^"\"" else acc^", \""^v^"\"") in
+ let str_of_values vs=List.fold_left vs ~init:"" ~f:(fun acc v->if String.(acc="") then "\""^v^"\"" else acc^", \""^v^"\"") in
let str_of_ctxs ?(txtonly=false) kvs =
List.fold_left kvs ~init:"" ~f:(fun acc (k,v)->
- (sprintf "%s %s=(%s)%s " acc k (str_of_values v) (if txtonly then "" else "\\\\") )
- )
+ (sprintf "%s %s=(%s)%s " acc k (str_of_values v) (if txtonly then "" else "\\\\") )
+ )
in
let str_desc_of_ctxs kvs =
- List.fold_left kvs ~init:"" ~f:(fun acc (k,vs)->
- if k<>"soms" then acc else
- (sprintf "%s %s \\\\" acc (List.fold_left vs ~init:"" ~f:(fun acc2 som->
- let s=sprintf "%s: *%s* (%s%s)" (tc_of_som som) (name_of_som som) (let u=unit_of_som som in if u="" then u else u^", ") (sprintf "%s is better" (let mb=more_is_better_of_som som in if mb="" then "none" else if mb="f" then "less" else "more")) in
- if acc="" then s else acc^","^s
- ))
+ Deferred.List.fold kvs ~init:"" ~f:(fun acc (k,vs)->
+ if String.(k<>"soms") then return acc else
+ let%map r =
+ Deferred.List.fold vs ~init:"" ~f:(fun acc2 som->
+ let%map tc = tc_of_som som
+ and name = name_of_som som
+ and u = unit_of_som som
+ and mbstr =
+ let%map mb=more_is_better_of_som som in if String.(mb="") then "none" else if String.(mb="f") then "less" else "more"
+ in
+ let s=sprintf "%s: *%s* (%s%s)" tc name (if String.(u="") then u else u^", ") (sprintf "%s is better" mbstr) in
+ if String.(acc="") then s else acc^","^s
+ )
+ in
+ sprintf "%s %s \\\\" acc r
)
- )
in
- let link ctx =
- (* link *)
- (
+ let link ctx =
+ (* link *)
+ (
(* rage is not generic enough to receive an arbirary number of soms in a link, pick just the first one *)
- let som_id=match List.find_exn ctx ~f:(fun (k,_)->k="soms") with |(k,v)->List.hd_exn v in
+ let som_id=match List.find_exn ctx ~f:(fun (k,_)->String.(k="soms")) with |(k,v)->List.hd_exn v in
(sprintf "[graph|http://%s/?som=%s&show_dist=on%s%s]" (Utils.server_name ()) som_id
- (* xaxis *)
- (List.fold_left link_xaxis ~init:"" ~f:(fun acc x->sprintf "%s%s" acc (sprintf "&xaxis=%s" x)))
- (* preset values *)
- (List.fold_left ctx ~init:"" ~f:(fun acc (k,vs)->sprintf "%s%s" acc
- (List.fold_left vs ~init:"" ~f:(fun acc2 v->sprintf "%s&v_%s=%s" acc2 k (rage_encode v))
- )
- ))
+ (* xaxis *)
+ (List.fold_left link_xaxis ~init:"" ~f:(fun acc x->sprintf "%s%s" acc (sprintf "&xaxis=%s" x)))
+ (* preset values *)
+ (List.fold_left ctx ~init:"" ~f:(fun acc (k,vs)->sprintf "%s%s" acc
+ (List.fold_left vs ~init:"" ~f:(fun acc2 v->sprintf "%s&v_%s=%s" acc2 k (rage_encode v))
+ )
+ ))
))
- in
+ in
let is_more_is_better ctx =
- match List.find ctx ~f:(fun (k,_)->k="soms") with
- |None->None
+ match List.find ctx ~f:(fun (k,_)->String.(k="soms")) with
+ |None->return None
|Some (k,_vs)->(
- let rec is_mb acc vs = (match vs with
- |[]->if acc=None then None else acc
- |v::vs->(let mb = more_is_better_of_som v in
- if mb="" then is_mb acc vs (* ignore more_is_better if not defined in db *)
- else
- let mbtf = match mb with m when m="f"->false|_->true in
- match acc with
- |None->is_mb (Some mbtf) vs
- |Some _mbtf->if _mbtf=mbtf
- then is_mb (Some mbtf) vs (* more_is_better values agree between soms *)
- else None (* more_is_better values disagree between soms *)
- )
- ) in
- is_mb None _vs
- )
+ let rec is_mb acc vs = (match vs with
+ |[]-> return @@ if Option.is_none acc then None else acc
+ |v::vs->(let%bind mb = more_is_better_of_som v in
+ if String.(mb="") then is_mb acc vs (* ignore more_is_better if not defined in db *)
+ else
+ let mbtf = match mb with m when String.(m="f")->false|_->true in
+ match acc with
+ |None->is_mb (Some mbtf) vs
+ |Some _mbtf->if Bool.(_mbtf=mbtf)
+ then is_mb (Some mbtf) vs (* more_is_better values agree between soms *)
+ else return None (* more_is_better values disagree between soms *)
+ )
+ ) in
+ is_mb None _vs
+ )
in
- let wiki_table =
- sprintf "| %s|\n%s%s\n%s"
- (* print the base context *)
- (str_of_ctxs b)
- (* print the header *)
- (sprintf "||id|| Description || View || %s \n"
- (List.foldi cs ~init:"" ~f:(fun i acc _ ->
- sprintf "%s %s ||" acc (if i=baseline_col_idx then "Baseline" else (sprintf "Comparison %d" i))
- ))
- )
- (* print the columns *)
- (sprintf "|| || || || %s"
- (List.fold_left ~init:""
- ~f:(fun acc cs->sprintf "%s %s || " acc (str_of_ctxs cs)) cs
- )
- )
- (* print the cells *)
- (List.fold_left ~init:"" ~f:(fun acc r_ms->acc^r_ms)
- (List.map2_exn table link_ctxs ~f:(fun (r,cs) lnkctx ->
- sprintf "| %s | %s | %s | %s \n"
- (* row id/title *)
- (str_of_ctxs r)
- (* row description *)
- (str_desc_of_ctxs r)
- (* graph link *)
- (link lnkctx)
- (* cells to the right *)
- (List.fold_left ~init:"" ~f:(fun acc c_ms->(sprintf "%s %s | " acc c_ms))
- (List.mapi cs ~f:(fun i (r,c,ctx,ms)->
- let _,_,_,baseline_ms = List.nth_exn cs baseline_col_idx in
-(*
+ let%map cells =
+ (List.map2_exn table link_ctxs ~f:(fun (r,cs) lnkctx ->
+ let%bind str_desc = str_desc_of_ctxs r in
+ let%map cells =
+ Deferred.List.mapi ~how:`Parallel cs ~f:(fun i (r,c,ctx,ms)->
+ let _,_,_,baseline_ms = List.nth_exn cs baseline_col_idx in
+ let%map is_mb = is_more_is_better ctx in
+ (*
sprintf "%s"
(Sexp.to_string (sexp_of_ctx_t r))
- (Sexp.to_string (sexp_of_ctx_t c))
- (str_of_ctxs ctx ~txtonly:true)
- (Sexp.to_string (sexp_of_str_lst_t (vals_of_ms ms)))
-*)
- (sprintf "{color:%s} %s %s %s {color}"
- (if baseline_col_idx = i then "" else
- match is_more_is_better ctx with
- |None->""
- |Some mb->if is_green (val_stddev_of (vals_of_ms baseline_ms)) (val_stddev_of (vals_of_ms ms)) mb then "green" else "red"
- )
- (str_stddev_of (vals_of_ms ms) ~f2_fmt:"\\\\[%s, %s, %s\\\\]")
- (sprintf "~(%d)~" (List.length ms))
- (if baseline_col_idx = i then "" else
- match is_more_is_better ctx with
- |None->""
- |Some mb->sprintf "~(%+.0f%%)~" (100.0 *. (proportion (val_stddev_of (vals_of_ms baseline_ms)) (val_stddev_of (vals_of_ms ms)) mb))
- )
- )
- ))
+ (Sexp.to_string (sexp_of_ctx_t c))
+ (str_of_ctxs ctx ~txtonly:true)
+ (Sexp.to_string (sexp_of_str_lst_t (vals_of_ms ms)))
+ *)
+ (sprintf "{color:%s} %s %s %s {color}"
+ (if baseline_col_idx = i then "" else
+ match is_mb with
+ |None->""
+ |Some mb->if is_green (val_stddev_of (vals_of_ms baseline_ms)) (val_stddev_of (vals_of_ms ms)) mb then "green" else "red"
+ )
+ (str_stddev_of (vals_of_ms ms) ~f2_fmt:"\\\\[%s, %s, %s\\\\]")
+ (sprintf "~(%d)~" (List.length ms))
+ (if baseline_col_idx = i then "" else
+ match is_mb with
+ |None->""
+ |Some mb->sprintf "~(%+.0f%%)~" (100.0 *. (proportion (val_stddev_of (vals_of_ms baseline_ms)) (val_stddev_of (vals_of_ms ms)) mb))
+ )))
+ in
+ sprintf "| %s | %s | %s | %s \n"
+ (* row id/title *)
+ (str_of_ctxs r)
+ (* row description *)
+ str_desc
+ (* graph link *)
+ (link lnkctx)
+ (* cells to the right *)
+ (List.fold_left ~init:"" ~f:(fun acc c_ms->(sprintf "%s %s | " acc c_ms)) cells)
+ ))
+ |> Deferred.List.all
+ in
+ let wiki_table =
+ sprintf "| %s|\n%s%s\n%s"
+ (* print the base context *)
+ (str_of_ctxs b)
+ (* print the header *)
+ (sprintf "||id|| Description || View || %s \n"
+ (List.foldi cs ~init:"" ~f:(fun i acc _ ->
+ sprintf "%s %s ||" acc (if i=baseline_col_idx then "Baseline" else (sprintf "Comparison %d" i))
+ ))
)
- ))
- )
+ (* print the columns *)
+ (sprintf "|| || || || %s"
+ (List.fold_left ~init:""
+ ~f:(fun acc cs->sprintf "%s %s || " acc (str_of_ctxs cs)) cs
+ )
+ )
+ (* print the cells *)
+ (String.concat ~sep:"" cells)
in
printf "%s" "
";
printf "%s" "h1. Brief Rage Report\n\n";
- printf "- [live html version, with parameters %s |http://%s/?%s]\n" (List.fold_left params ~init:"" ~f:(fun acc (k,v)->if k="out" then acc else if acc="" then (sprintf "%s=%s" k v) else (sprintf "%s, %s=%s" acc k (url_decode v)))) (Utils.server_name ()) (List.fold_left params ~init:"" ~f:(fun acc (k,v)->if k="out" then acc else sprintf "%s&%s=%s" acc k (url_decode v)));
+ printf "- [live html version, with parameters %s |http://%s/?%s]\n" (List.fold_left params ~init:"" ~f:(fun acc (k,v)->if String.(k="out") then acc else if String.(acc="") then (sprintf "%s=%s" k v) else (sprintf "%s, %s=%s" acc k (url_decode v)))) (Utils.server_name ()) (List.fold_left params ~init:"" ~f:(fun acc (k,v)->if String.(k="out") then acc else sprintf "%s&%s=%s" acc k (url_decode v)));
printf "%s" "- Numbers reported at 95% confidence level from the data of existing runs\n";
printf "%s" "- \\(x) indicates number of samples\n";
printf "%s" "- \\(x%) indicates difference with baseline column\n";
diff --git a/src/create_tiny_url_handler.ml b/src/create_tiny_url_handler.ml
index 8a32105..e922bba 100644
--- a/src/create_tiny_url_handler.ml
+++ b/src/create_tiny_url_handler.ml
@@ -1,4 +1,5 @@
-open! Core.Std
+open Core
+open Async
let t ~args = object (self)
inherit Json_handler.t ~args
@@ -6,6 +7,6 @@ let t ~args = object (self)
method private write_body =
let url = self#get_param_exn "url" in
let tuples = [("url", url)] in
- let id = Sql.ensure_inserted_get_id ~conn ~tbl:"tiny_urls" ~tuples in
+ let%map id = Postgresql_async.wrap_sql ~conn (Sql.ensure_inserted_get_id ~tbl:"tiny_urls" ~tuples) in
printf "{\"id\":%d}" id
end
diff --git a/src/default_handler.ml b/src/default_handler.ml
index f1cbd65..97fa6c5 100644
--- a/src/default_handler.ml
+++ b/src/default_handler.ml
@@ -1,4 +1,5 @@
-open! Core.Std
+open Core
+open Async
let t ~args = object (self)
inherit Html_handler.t ~args
@@ -8,4 +9,5 @@ let t ~args = object (self)
printf " - Scales of Measure
\n";
printf "- Import Jobs
\n";
printf "
\n";
+ return ()
end
diff --git a/src/dune b/src/dune
new file mode 100644
index 0000000..67df187
--- /dev/null
+++ b/src/dune
@@ -0,0 +1,8 @@
+(executable
+ (public_name rage)
+ (name main)
+ (flags
+ (:standard -principal -short-paths))
+ (preprocess
+ (pps ppx_sexp_conv ppx_let))
+ (libraries threads.posix core postgresql curl async sql uri str re ssl))
diff --git a/src/handler.ml b/src/handler.ml
index da11e8a..f05ea77 100644
--- a/src/handler.ml
+++ b/src/handler.ml
@@ -1,8 +1,9 @@
-open! Core.Std
+open Core
+open Async
open Utils
type args = {
- conn : Postgresql.connection;
+ conn : Postgresql_async.t;
params : (string * string) list;
}
@@ -12,20 +13,20 @@ object (self)
val params = args.params
val base_path : string =
- let exe = Sys.argv.(0) in
+ let exe = (Sys.get_argv ()).(0) in
String.sub exe ~pos:0 ~len:((String.rindex_exn exe '/') + 1)
val mutable html_header_written : bool = false
method private write_header = ()
- method private write_body = ()
+ method private write_body = return ()
method private write_footer = ()
method handle =
self#write_header;
- self#write_body;
+ let%map () = self#write_body in
self#write_footer
method private write_html_header =
@@ -37,13 +38,13 @@ object (self)
if not html_header_written then self#write_html_header;
failwith msg
- method private get_param key = List.Assoc.find params key
+ method private get_param key = List.Assoc.find ~equal:String.equal params key
- method private get_param_exn key = List.Assoc.find_exn params key
+ method private get_param_exn key = List.Assoc.find_exn ~equal:String.equal params key
method private get_params_gen ~params key =
List.fold params ~init:[]
- ~f:(fun acc (k, v) -> if k = key then v::acc else acc)
+ ~f:(fun acc (k, v) -> if String.(k = key) then v::acc else acc)
method private get_params key = self#get_params_gen ~params key
diff --git a/src/html_handler.ml b/src/html_handler.ml
index ae2e7fb..e037a03 100644
--- a/src/html_handler.ml
+++ b/src/html_handler.ml
@@ -1,4 +1,5 @@
-open! Core.Std
+open Core
+open Async
class t = fun ~args ->
object (self)
diff --git a/src/import_jobs_handler.ml b/src/import_jobs_handler.ml
index 60c53a7..21b58ec 100644
--- a/src/import_jobs_handler.ml
+++ b/src/import_jobs_handler.ml
@@ -1,4 +1,4 @@
-open! Core.Std
+open Core
open Utils
let importer = "/usr/groups/perfeng/bin/importer-xenrt"
@@ -18,20 +18,14 @@ let import_job job_ids =
if not (Str.string_match (Str.regexp "^[0-9,\\-]*$") job_ids 0) then failwith (sprintf "expected '<n>' or '<n>-<n>' or '<n>,<n>,...'; got '%s'" job_ids);
let cmd = Printf.sprintf "%s -jobs %s -ignoreseenjobs 2>&1" importer job_ids in
- Printf.printf "" cmd;
+ printf "" cmd;
let ic = Unix.open_process_in cmd in
- begin
- try
- while true do
- let input = input_line ic in
- Printf.printf "%s\n" input;
- Printf.eprintf "[import_jobs_handler|%s] %s\n" job_ids input
- done
- with End_of_file ->
- Printf.eprintf "[import_jobs_handler|%s] EOF\n" job_ids;
- ignore (Unix.close_process_in ic)
- end;
- Printf.eprintf "[import_jobs_handler|%s] Finished\n" job_ids
+ In_channel.iter_lines ic ~f:(fun input ->
+ printf "%s\n" input;
+ eprintf "[import_jobs_handler|%s] %s\n" job_ids input);
+ eprintf "[import_jobs_handler|%s] EOF\n" job_ids;
+ ignore (Unix.close_process_in ic);
+ eprintf "[import_jobs_handler|%s] Finished\n" job_ids
let t ~args = object (self)
inherit Html_handler.t ~args
@@ -47,6 +41,7 @@ let t ~args = object (self)
Printf.printf "";
import_job job_ids;
Printf.printf "";
- Printf.printf "Finished."
+ Printf.printf "Finished.";
+ Async.return ()
end
diff --git a/src/import_page_handler.ml b/src/import_page_handler.ml
index 9f1f598..7b27537 100644
--- a/src/import_page_handler.ml
+++ b/src/import_page_handler.ml
@@ -1,4 +1,5 @@
-open! Core.Std
+open Core
+open Async
let t ~args = object (self)
inherit Html_handler.t ~args
@@ -14,5 +15,6 @@ let t ~args = object (self)
printf "\n";
printf "";
printf "";
+ return ()
end
diff --git a/src/javascript_only_handler.ml b/src/javascript_only_handler.ml
index 260f085..d9ee63b 100644
--- a/src/javascript_only_handler.ml
+++ b/src/javascript_only_handler.ml
@@ -1,5 +1,6 @@
+open Async
let t ~args = object (self)
inherit Html_handler.t ~args
- method private write_body = Printf.printf ""
+ method private write_body = printf ""; return ()
end
diff --git a/src/json_handler.ml b/src/json_handler.ml
index ef56007..3b1b22b 100644
--- a/src/json_handler.ml
+++ b/src/json_handler.ml
@@ -1,4 +1,5 @@
-open! Core.Std
+open Core
+open Async
class t = fun ~args ->
object (self)
diff --git a/src/main.ml b/src/main.ml
index 3f0e147..af3f283 100644
--- a/src/main.ml
+++ b/src/main.ml
@@ -1,11 +1,12 @@
-open! Core.Std
+open Core
+open Async
open Utils
(** Combines GET and POST parameters. *)
let get_params_of_request () =
let get_req = Sys.getenv_exn "QUERY_STRING" in
let post_req = In_channel.input_all In_channel.stdin in
- let req = get_req ^ (if post_req = "" then "" else "&" ^ post_req) in
+ let req = get_req ^ (if String.(post_req = "") then "" else "&" ^ post_req) in
let parts = String.split req ~on:'&' in
let opt_split part =
Option.value ~default:(part, "") (String.lsplit2 part ~on:'=') in
@@ -16,6 +17,7 @@ let get_params_of_request () =
let place_of_params ~params =
let open List.Assoc in
let open Place in
+ let find = find ~equal:String.equal in
match find params "p" with Some p -> Place.of_string p | None ->
match find params "t" with Some _ -> RedirectTinyUrl | None ->
match find params "som" with Some _ -> SomPage | None ->
@@ -25,7 +27,7 @@ let handle_request () =
let start_time = Unix.gettimeofday () in
let params = get_params_of_request () in
let place = place_of_params ~params in
- let conn = new Postgresql.connection ~conninfo:Sys.argv.(1) () in
+ let conn = Postgresql_async.connect_pool ~conninfo:Sys.(get_argv()).(1) in
let args = let open Handler in {conn; params} in
let open Place in
let handler = begin match place with
@@ -40,10 +42,12 @@ let handle_request () =
| Brief -> Brief_handler.t
| ImportPage -> Import_page_handler.t
| ImportJobs -> Import_jobs_handler.t
- end in (handler ~args)#handle;
- conn#finish;
+ end in
+ let%bind () = (handler ~args)#handle in
+ let%map () = Postgresql_async.destroy_pool conn in
let elapsed_time = Unix.gettimeofday () -. start_time in
- debug (sprintf "==========> '%s': %fs." (Place.string_of place) elapsed_time)
+ debug (sprintf "==========> '%s': %fs." (Place.string_of place) elapsed_time);
+ Shutdown.shutdown 0
let bind_modules () =
Sql.debug_fn := None; (* Some debug; *)
@@ -52,7 +56,15 @@ let bind_modules () =
Sql.ignore_limit_0 := true;
Sql.mode := Sql.Live
-let _ =
+let () =
bind_modules ();
- try handle_request ()
- with Failure msg -> Printexc.print_backtrace stderr; printf "%s" msg
+ don't_wait_for @@ Monitor.handle_errors handle_request
+ (fun e ->
+ Printexc.print_backtrace stderr;
+ let msg = match e with
+ | Failure msg -> msg
+ | _ -> Exn.to_string e in
+ printf "%s" msg;
+ Shutdown.shutdown 1)
+
+let () = never_returns (Scheduler.go ())
diff --git a/src/ocaml-sql b/src/ocaml-sql
index dba2815..53ab8e3 160000
--- a/src/ocaml-sql
+++ b/src/ocaml-sql
@@ -1 +1 @@
-Subproject commit dba2815b0886f6c902e277860258ff937cccd8bd
+Subproject commit 53ab8e3820b4e3f3d6bb1b3519bc3170c9153525
diff --git a/src/place.ml b/src/place.ml
index 1b59d0a..248444e 100644
--- a/src/place.ml
+++ b/src/place.ml
@@ -1,4 +1,4 @@
-open! Core.Std
+open Core
type t =
| CreateTinyUrl
diff --git a/src/postgresql_async.ml b/src/postgresql_async.ml
new file mode 100644
index 0000000..2ea3964
--- /dev/null
+++ b/src/postgresql_async.ml
@@ -0,0 +1,69 @@
+open Core
+open Async
+
+(* [in_thread ~name f] runs the blocking function [f] in a worker thread *)
+let in_thread ~name f =
+ In_thread.run ~name (fun () -> Or_error.try_with ~backtrace:true f)
+
+let connect ~conninfo =
+ in_thread ~name:"Postgresql connect" (fun () ->
+ new Postgresql.connection ~conninfo ())
+
+let close c =
+ in_thread ~name:"Postgresql close connection" (fun () -> c#finish)
+
+module Lazy_pooled_resource = struct
+ type 'a t = 'a Or_error.t Lazy_deferred.t Throttle.t
+
+ (** [create ~acquire ~release ~limit] creates a lazily initialized resource pool.
+ * This pool has at most [n] resources, acquired on demand.
+ * *)
+ let create ~acquire ~release ~limit : 'a t =
+ let pool =
+ limit
+ |> List.init ~f:(fun _ -> Lazy_deferred.create acquire)
+ |> Throttle.create_with ~continue_on_error:false
+ in
+ Throttle.at_kill pool (fun c ->
+ match c |> Lazy_deferred.peek_exn |> Option.bind ~f:Or_error.ok with
+ | None ->
+ return ()
+ | Some conn ->
+ conn |> release |> Deferred.Or_error.ok_exn) ;
+ pool
+
+ let destroy pool = Throttle.kill pool ; Throttle.cleaned pool
+
+ (** [with_ pool ~f] acquires a resource from [pool] and runs [f].
+ * If all resources in [pool] are in use then a new one is created,
+ * as long as the total number of resources in the pool is below the limit
+ * specified at creation time. *)
+ let with_ (pool : 'a t) ~f =
+ Throttle.enqueue pool (fun conn ->
+ Deferred.Or_error.bind (conn |> Lazy_deferred.force_exn) ~f)
+end
+
+let cores =
+ (Linux_ext.cores |> Result.ok |> Option.value ~default:(fun () -> 1)) ()
+
+type t = Postgresql.connection Lazy_pooled_resource.t
+
+let connect_pool ~conninfo =
+ let acquire () = connect ~conninfo in
+ let release = close in
+ Lazy_pooled_resource.create ~acquire ~release ~limit:cores
+
+let destroy_pool = Lazy_pooled_resource.destroy
+
+let wrap_sql ~(conn:t) f =
+ Lazy_pooled_resource.with_ conn ~f:(fun conn ->
+ in_thread ~name:"Postgresql query" (fun () ->
+ (* previous invocation might've left the connection in a bad state *)
+ conn#try_reset ; f ~conn))
+ |> Deferred.Or_error.ok_exn
+
+let exec_exn ~conn ~query =
+ wrap_sql ~conn (Sql.exec_exn ~query)
+
+let exec_exn_get_all ~conn ~query =
+ exec_exn ~conn ~query >>| fun r -> r#get_all
diff --git a/src/postgresql_async.mli b/src/postgresql_async.mli
new file mode 100644
index 0000000..065860c
--- /dev/null
+++ b/src/postgresql_async.mli
@@ -0,0 +1,7 @@
+open Async
+type t
+val connect_pool: conninfo:string -> t
+val destroy_pool: t -> unit Deferred.t
+val exec_exn: conn:t -> query:string -> Postgresql.result Deferred.t
+val exec_exn_get_all: conn:t -> query:string -> string array array Deferred.t
+val wrap_sql: conn:t -> (conn:Postgresql.connection -> 'a) -> 'a Deferred.t
diff --git a/src/redirect_tiny_url_handler.ml b/src/redirect_tiny_url_handler.ml
index 198dde3..9bbd1e8 100644
--- a/src/redirect_tiny_url_handler.ml
+++ b/src/redirect_tiny_url_handler.ml
@@ -1,4 +1,5 @@
-open! Core.Std
+open Core
+open Async
let t ~args = object (self)
inherit Html_handler.t ~args
@@ -6,7 +7,7 @@ let t ~args = object (self)
method handle =
let id = int_of_string (self#get_param_exn "t") in
let query = sprintf "SELECT url FROM tiny_urls WHERE key=%d" id in
- let result = Sql.exec_exn ~conn ~query in
+ let%map result = Postgresql_async.exec_exn ~conn ~query in
match result#ntuples with
| 1 -> self#javascript_redirect (Sql.get_first_entry_exn ~result)
| _ -> self#write_404
diff --git a/src/som_data_handler.ml b/src/som_data_handler.ml
index 552ed6e..aa03e07 100644
--- a/src/som_data_handler.ml
+++ b/src/som_data_handler.ml
@@ -1,4 +1,5 @@
-open! Core.Std
+open Core
+open Async
open Fn
open Utils
@@ -14,15 +15,15 @@ let t ~args = object (self)
method private values_for_key ?(default=[]) key =
let xs = List.fold params ~init:[]
- ~f:(fun acc (k, v) -> if k = key then v::acc else acc) in
- if xs = [] then default else xs
+ ~f:(fun acc (k, v) -> if String.(k = key) then v::acc else acc) in
+ if List.is_empty xs then default else xs
method private get_first_val k d =
Option.value ~default:d (List.hd (self#values_for_key k))
method private select_params ?(value=None) prefix =
List.filter_map params ~f:(fun (k, v) ->
- if String.is_prefix k ~prefix && (Option.is_none value || Some v = value)
+ if String.is_prefix k ~prefix && (Option.is_none value || Option.equal String.equal (Some v) value)
then String.chop_prefix k ~prefix else None
)
@@ -52,16 +53,16 @@ let t ~args = object (self)
try
compare (int_of_string a) (int_of_string b)
with Failure _ ->
- compare a b
+ String.compare a b
else
- compare a b
+ String.compare a b
in
if not (self#should_sort_alphabetically col_types col_name force_as_seq force_as_num)
then None else
let col_data = Array.to_list (Array.map ~f:(fun row -> row.(col)) rows) in
- let uniques = List.dedup col_data in
- let sorted = List.sort ~cmp:sort_seq_numeric uniques in
+ let uniques = List.dedup_and_sort ~compare:String.compare col_data in
+ let sorted = List.sort ~compare:sort_seq_numeric uniques in
Some (List.mapi sorted ~f:(fun i x -> (i+1, x)))
method private strings_to_numbers rows col col_name col_types label
@@ -74,21 +75,21 @@ let t ~args = object (self)
printf "\"%s\":{%s}," label mapping_str;
let i_to_string_map = List.Assoc.inverse mapping in
let i_from_string row =
- match List.Assoc.find i_to_string_map row.(col) with
+ match List.Assoc.find ~equal:String.equal i_to_string_map row.(col) with
| None -> failwith ("NOT IN TRANSLATION MAP: " ^ row.(col) ^ "\n")
| Some i -> row.(col) <- string_of_int i
in Array.iter rows ~f:i_from_string
method private write_body =
let som_id = int_of_string (self#get_param_exn "id") in
- let tc_fqn, tc_config_tbl = get_tc_config_tbl_name conn som_id in
- let som_config_tbl, som_tbl_exists = som_config_tbl_exists ~conn som_id in
+ let%bind tc_fqn, tc_config_tbl = get_tc_config_tbl_name conn som_id
+ and som_config_tbl, som_tbl_exists = som_config_tbl_exists ~conn som_id in
(* determine filter columns and their types *)
let tbls = ["measurements_2"; "soms_jobs"; "jobs"; "builds"; "tc_config"; "machines";
tc_config_tbl] @
(if som_tbl_exists then [som_config_tbl] else []) in
- let col_fqns = get_column_fqns_many conn tbls in
- let col_types = get_column_types_many conn tbls in
+ let%bind col_fqns = get_column_fqns_many conn tbls
+ and col_types = get_column_types_many conn tbls in
(* Get axes selections. xaxis may be multi-valued; yaxis is single value. *)
let xaxis = self#values_for_key "xaxis" ~default:["branch"] in
(* xaxis could be ["one"; "two"] or ["one%2Ctwo"] -- both are equivalent *)
@@ -96,7 +97,7 @@ let t ~args = object (self)
let yaxis = self#get_first_val "yaxis" "result" in
let compose_keys ~xaxis ~yaxis ~rest =
let deduped = List.stable_dedup rest in
- let filter_cond = non (List.mem (yaxis::xaxis)) in
+ let filter_cond = non (List.mem ~equal:String.equal (yaxis::xaxis)) in
List.filter ~f:filter_cond deduped
in
let restkeys =
@@ -112,38 +113,40 @@ let t ~args = object (self)
let xaxis_str = String.concat ~sep:"," xaxis in
let keys = xaxis_str :: [yaxis] @ xaxis @ restkeys in
let filter = extract_filter col_fqns col_types params values_prefix in
- (* obtain SOM meta-data *)
- let query = sprintf "SELECT positive FROM soms WHERE som_id=%d" som_id in
- let metadata = Sql.exec_exn ~conn ~query in
- let positive = (Sql.get_first_entry_exn ~result:metadata) = "t" in
- (* obtain data from database *)
- let query =
- "SELECT " ^
- (String.concat ~sep:"||','||" xaxisfqns) ^ ", " ^ (* x-axis *)
- yaxisfqns ^ ", " ^ (* y-axis *)
- (String.concat ~sep:", " xaxisfqns) ^ (* components of x-axis, needed in case we split by one of them *)
- (if restfqns = [] then " " else sprintf ", %s " (String.concat ~sep:", " restfqns)) ^
- (sprintf "FROM %s " (String.concat ~sep:", " tbls)) ^
- (sprintf "WHERE measurements_2.tc_config_id=%s.tc_config_id "
- tc_config_tbl) ^
- (sprintf "AND soms_jobs.som_id=%d " som_id) ^
- "AND soms_jobs.job_id=jobs.job_id " ^
- "AND measurements_2.som_job_id=soms_jobs.id "^
- "AND jobs.build_id=builds.build_id " ^
- "AND tc_config.job_id=jobs.job_id " ^
- (sprintf "AND tc_config.tc_fqn='%s' " tc_fqn) ^
- "AND tc_config.tc_config_id=measurements_2.tc_config_id " ^
- "AND tc_config.machine_id=machines.machine_id" ^
- (if som_tbl_exists
- then sprintf " AND measurements_2.som_config_id=%s.som_config_id"
- som_config_tbl else "") ^
- (if not (String.is_empty filter) then sprintf " AND %s" filter else "") ^
- (sprintf " LIMIT %d" limit_rows)
- in
- let data = Sql.exec_exn ~conn ~query in
+ let%bind metadata =
+ (* obtain SOM meta-data *)
+ let query = sprintf "SELECT positive FROM soms WHERE som_id=%d" som_id in
+ Postgresql_async.exec_exn ~conn ~query
+ and data =
+ (* obtain data from database *)
+ let query =
+ "SELECT " ^
+ (String.concat ~sep:"||','||" xaxisfqns) ^ ", " ^ (* x-axis *)
+ yaxisfqns ^ ", " ^ (* y-axis *)
+ (String.concat ~sep:", " xaxisfqns) ^ (* components of x-axis, needed in case we split by one of them *)
+ (if List.is_empty restfqns then " " else sprintf ", %s " (String.concat ~sep:", " restfqns)) ^
+ (sprintf "FROM %s " (String.concat ~sep:", " tbls)) ^
+ (sprintf "WHERE measurements_2.tc_config_id=%s.tc_config_id "
+ tc_config_tbl) ^
+ (sprintf "AND soms_jobs.som_id=%d " som_id) ^
+ "AND soms_jobs.job_id=jobs.job_id " ^
+ "AND measurements_2.som_job_id=soms_jobs.id "^
+ "AND jobs.build_id=builds.build_id " ^
+ "AND tc_config.job_id=jobs.job_id " ^
+ (sprintf "AND tc_config.tc_fqn='%s' " tc_fqn) ^
+ "AND tc_config.tc_config_id=measurements_2.tc_config_id " ^
+ "AND tc_config.machine_id=machines.machine_id" ^
+ (if som_tbl_exists
+ then sprintf " AND measurements_2.som_config_id=%s.som_config_id"
+ som_config_tbl else "") ^
+ (if not (String.is_empty filter) then sprintf " AND %s" filter else "") ^
+ (sprintf " LIMIT %d" limit_rows)
+ in
+ Postgresql_async.exec_exn ~conn ~query in
let rows = data#get_all in
debug (sprintf "The query returned %d rows" (Array.length rows));
(if Array.length rows = limit_rows then debug (sprintf "WARNING: truncation of data -- we are only returning the first %d rows" limit_rows));
+ let positive = String.(Sql.get_first_entry_exn ~result:metadata = "t") in
(* filter data into groups based on "SPLIT BY"-s *)
let split_bys =
self#select_params filter_prefix ~value:(Some filter_by_value) in
@@ -167,9 +170,9 @@ let t ~args = object (self)
printf "\"part\":%s," (self#get_first_val "part" "1");
printf "\"xaxis\":\"%s\"," xaxis_str;
printf "\"yaxis\":\"%s\"," yaxis;
- let x_as_seq = ("on" = self#get_first_val "x_as_seq" "off") in
- let y_as_seq = ("on" = self#get_first_val "y_as_seq" "off") in
- let x_as_num = ("on" = self#get_first_val "x_as_num" "off") in
+ let x_as_seq = String.("on" = self#get_first_val "x_as_seq" "off") in
+ let y_as_seq = String.("on" = self#get_first_val "y_as_seq" "off") in
+ let x_as_num = String.("on" = self#get_first_val "x_as_num" "off") in
self#strings_to_numbers rows 0 xaxis col_types "x_labels" x_as_seq x_as_num;
self#strings_to_numbers rows 1 [yaxis] col_types "y_labels" y_as_seq false;
let num_other_keys = List.length keys - 2 in
@@ -200,5 +203,6 @@ let t ~args = object (self)
printf "]}"
in
List.iteri (Hashtbl.Poly.to_alist all_series) ~f:process_series;
- printf "]}"
+ printf "]}";
+ return ()
end
diff --git a/src/som_page_handler.ml b/src/som_page_handler.ml
index 21a4e5e..7f3e6cc 100644
--- a/src/som_page_handler.ml
+++ b/src/som_page_handler.ml
@@ -1,4 +1,5 @@
-open! Core.Std
+open Core
+open Async
open Utils
let jira_hostname = "jira.uk.xensource.com"
@@ -42,9 +43,9 @@ let t ~args = object (self)
let machine_options_lst = options_lst_of_dbresult machines in
- let config_options_lst = List.map config_column_names ~f:(fun config_name ->
+ let%map config_options_lst = Deferred.List.map ~how:`Parallel config_column_names ~f:(fun config_name ->
let query = sprintf "SELECT DISTINCT %s FROM %s ORDER BY %s" config_name tc_config_tbl config_name in
- let configs = Sql.exec_exn ~conn ~query in
+ let%map configs = Postgresql_async.exec_exn ~conn ~query in
get_options_for_field_once configs 0
) in
@@ -74,40 +75,47 @@ let t ~args = object (self)
List.iter ~f:print_table_for (List.zip_exn labels options_lst)
method private write_body =
- let som_id = int_of_string (List.Assoc.find_exn params "som") in
- let _, tc_config_tbl = get_tc_config_tbl_name conn som_id in
- let query =
- sprintf "SELECT * FROM soms WHERE som_id=%d" som_id in
- let som_info = Sql.exec_exn ~conn ~query in
- let query = "SELECT * FROM " ^ tc_config_tbl ^ " LIMIT 0" in
- let config_columns = Sql.exec_exn ~conn ~query in
- let job_fields = String.concat ~sep:", " Utils.job_fields in
- let query = "SELECT DISTINCT " ^ job_fields ^ " FROM soms_jobs WHERE " ^
+ let som_id = int_of_string (List.Assoc.find_exn ~equal:String.equal params "som") in
+ let%bind _, tc_config_tbl = get_tc_config_tbl_name conn som_id
+ and som_info =
+ let query =
+ sprintf "SELECT * FROM soms WHERE som_id=%d" som_id in
+ Postgresql_async.exec_exn ~conn ~query
+ and job_ids =
+ let job_fields = String.concat ~sep:", " Utils.job_fields in
+ let query = "SELECT DISTINCT " ^ job_fields ^ " FROM soms_jobs WHERE " ^
(sprintf "som_id=%d" som_id) in
- let job_ids = Sql.exec_exn ~conn ~query in
- let build_fields = String.concat ~sep:", " Utils.build_fields in
- let query =
- "SELECT DISTINCT " ^ build_fields ^ " " ^
+ Postgresql_async.exec_exn ~conn ~query
+ and builds =
+ let build_fields = String.concat ~sep:", " Utils.build_fields in
+ let query =
+ "SELECT DISTINCT " ^ build_fields ^ " " ^
(sprintf "FROM builds AS b, jobs AS j, (select distinct job_id from soms_jobs where som_id=%d) AS m " som_id) ^
"WHERE m.job_id=j.job_id AND j.build_id=b.build_id "
- in
- let builds = Sql.exec_exn ~conn ~query in
- let query = "SELECT DISTINCT " ^ (String.concat ~sep:", " Utils.tc_config_fields) ^ " " ^
+ in
+ Postgresql_async.exec_exn ~conn ~query
+ and job_attributes =
+ let query = "SELECT DISTINCT " ^ (String.concat ~sep:", " Utils.tc_config_fields) ^ " " ^
(sprintf "FROM tc_config AS c, jobs AS j, (select distinct job_id from soms_jobs where som_id=%d) AS m " som_id) ^
"WHERE m.job_id=j.job_id AND j.job_id=c.job_id "
- in
- let job_attributes = Sql.exec_exn ~conn ~query in
- let som_config_tbl, som_tbl_exists = som_config_tbl_exists ~conn som_id in
- let som_configs_opt =
- if not som_tbl_exists then None else
- let query = sprintf "SELECT * FROM %s" som_config_tbl in
- Some (Sql.exec_exn ~conn ~query) in
- let query =
- "SELECT DISTINCT machine_name, machine_type, cpu_model, number_of_cpus " ^
+ in
+ Postgresql_async.exec_exn ~conn ~query
+ and som_config_tbl, som_tbl_exists = som_config_tbl_exists ~conn som_id
+ and machines =
+ let query =
+ "SELECT DISTINCT machine_name, machine_type, cpu_model, number_of_cpus " ^
(sprintf "FROM machines AS mn, tc_config AS c, (select distinct job_id from soms_jobs where som_id=%d) AS mr " som_id) ^
"WHERE mn.machine_id=c.machine_id AND c.job_id=mr.job_id "
+ in
+ Postgresql_async.exec_exn ~conn ~query in
+ let%bind som_configs_opt =
+ if not som_tbl_exists then return None else
+ let query = sprintf "SELECT * FROM %s" som_config_tbl in
+ let%map r = Postgresql_async.exec_exn ~conn ~query in Some r
+ and config_columns =
+ let query = "SELECT * FROM " ^ tc_config_tbl ^ " LIMIT 0" in
+ Postgresql_async.exec_exn ~conn ~query
in
- let machines = Sql.exec_exn ~conn ~query in
printf "\n";
printf "\n";
@@ -174,4 +182,5 @@ let t ~args = object (self)
printf "";
printf "";
self#include_javascript;
+ return ()
end
diff --git a/src/soms_handler.ml b/src/soms_handler.ml
index c311d72..46a8eae 100644
--- a/src/soms_handler.ml
+++ b/src/soms_handler.ml
@@ -1,4 +1,5 @@
-open! Core.Std
+open Core
+open Async
open Utils
let t ~args = object (self)
@@ -6,14 +7,15 @@ let t ~args = object (self)
method private write_body =
let query = "SELECT tc_fqn,description FROM test_cases ORDER BY tc_fqn" in
- let tcs = Sql.exec_exn ~conn ~query in
+ let%bind tcs = Postgresql_async.exec_exn ~conn ~query in
let json_of_tc tc =
sprintf "\"%s\":{\"desc\":\"%s\"}" tc.(0) tc.(1) in
let tcs_json = concat_array (Array.map ~f:json_of_tc tcs#get_all) in
let query = "SELECT som_id,som_name,tc_fqn FROM soms ORDER BY som_id" in
- let soms = Sql.exec_exn ~conn ~query in
+ let%bind soms = Postgresql_async.exec_exn ~conn ~query in
let json_of_som som =
sprintf "\"%s\":{\"name\":\"%s\",\"tc\":\"%s\"}" som.(0) som.(1) som.(2) in
let soms_json = concat_array (Array.map ~f:json_of_som soms#get_all) in
- printf "{\"tcs\":{%s},\"soms\":{%s}}" tcs_json soms_json
+ printf "{\"tcs\":{%s},\"soms\":{%s}}" tcs_json soms_json;
+ return ()
end
diff --git a/src/std_axes_handler.ml b/src/std_axes_handler.ml
index 7a982eb..7dff9b6 100644
--- a/src/std_axes_handler.ml
+++ b/src/std_axes_handler.ml
@@ -1,23 +1,27 @@
-open! Core.Std
+open Core
+open Async
let t ~args = object (self)
inherit Json_handler.t ~args
method private get_std_xy_choices =
- let machine_field_lst =
- List.tl_exn (Sql.get_col_names ~conn ~tbl:"machines") in
- "branch" :: "build_number" :: "build_tag" ::
+ let%map machine_field_lst =
+ let%map r = Postgresql_async.wrap_sql ~conn (Sql.get_col_names ~tbl:"machines") in
+ List.tl_exn r in
+ "branch" :: "build_number" :: "build_tag" :: "patches_applied" :: "build_is_release" ::
"dom0_memory_static_max" :: "dom0_memory_target" ::
"cc_restrictions" :: "redo_log" ::
machine_field_lst
method private get_std_x_choices = self#get_std_xy_choices
- method private get_std_y_choices = "result" :: self#get_std_xy_choices
+ method private get_std_y_choices =
+ let%map r = self#get_std_xy_choices in
+ "result" :: r
method private write_body =
- let std_x_axes = self#get_std_x_choices in
- let std_y_axes = self#get_std_y_choices in
+ let%bind std_x_axes = self#get_std_x_choices
+ and std_y_axes = self#get_std_y_choices in
let string_of_axes choices =
let quoted = List.map ~f:(fun c -> "\"" ^ c ^ "\"") choices in
sprintf "[%s]" (String.concat ~sep:"," quoted)
@@ -25,5 +29,6 @@ let t ~args = object (self)
printf "{";
printf "\"std_x_axes\": %s," (string_of_axes std_x_axes);
printf "\"std_y_axes\": %s" (string_of_axes std_y_axes);
- printf "}"
+ printf "}";
+ return ()
end
diff --git a/src/utils.ml b/src/utils.ml
index 2a363ae..9c85ea1 100644
--- a/src/utils.ml
+++ b/src/utils.ml
@@ -1,13 +1,14 @@
-open! Core.Std
+open Core
+open Async
let debug msg =
- output_string stderr (msg ^ "\n");
- flush stderr
+ Out_channel.output_string stderr (msg ^ "\n");
+ Out_channel.flush stderr
let index l x =
let rec aux i = function
| [] -> failwith "index []"
- | x'::xs -> if x = x' then i else aux (i+1) xs
+ | x'::xs -> if String.(x = x') then i else aux (i+1) xs
in aux 0 l
let concat ?(sep = ",") l =
@@ -29,7 +30,7 @@ let concat_array ?(sep = ",") a =
let merge_table_into src dst =
String.Table.merge_into ~src ~dst
~f:(fun ~key:_ src_v dst_v_opt ->
- match dst_v_opt with None -> Some src_v | vo -> vo)
+ match dst_v_opt with None -> Set_to src_v | Some vo -> Set_to vo)
let cat filename =
print_string (In_channel.with_file ~f:In_channel.input_all filename)
@@ -41,20 +42,23 @@ let get_value r row col null_val =
let combine_maps conn tbls f =
let m = String.Table.create () in
- List.iter tbls ~f:(fun t -> merge_table_into (f conn t) m);
+ let%map () = Deferred.List.iter tbls ~f:(fun t ->
+ let%map r = f conn t in
+ merge_table_into r m) in
m
let get_column_types conn tbl =
- String.Table.of_alist_exn (Sql.get_col_types_lst ~conn ~tbl)
+ let%map r = Postgresql_async.wrap_sql ~conn (Sql.get_col_types_lst ~tbl) in
+ String.Table.of_alist_exn r
let get_column_types_many conn tbls = combine_maps conn tbls get_column_types
let get_column_fqns conn tbl =
- let col_names = Sql.get_col_names ~conn ~tbl in
+ let%map col_names = Postgresql_async.wrap_sql ~conn (Sql.get_col_names ~tbl) in
let nameToFqn = String.Table.create () in
let process_column name =
let fqn = tbl ^ "." ^ name in
- String.Table.replace nameToFqn ~key:name ~data:fqn
+ String.Table.set nameToFqn ~key:name ~data:fqn
in List.iter col_names ~f:process_column;
nameToFqn
@@ -89,18 +93,18 @@ let extract_filter col_fqns col_types params key_prefix =
let update_m v vs_opt =
let vs = Option.value vs_opt ~default:[] in Some (v::vs) in
let filter_insert (k, v) =
- if v = "ALL" then () else
+ if String.equal v "ALL" then () else
if String.is_prefix k ~prefix:key_prefix then begin
let k2 = String.chop_prefix_exn k ~prefix:key_prefix in
- String.Table.change m k2 (update_m v)
+ String.Table.change m k2 ~f:(update_m v)
end in
List.iter params ~f:filter_insert;
let l = String.Table.to_alist m in
let conds = List.map l
~f:(fun (k, vs) ->
let vs = List.map vs ~f:decode_html in
- let has_null = List.mem vs "(NULL)" in
- let vs = if has_null then List.filter vs ~f:((<>) "(NULL)") else vs in
+ let has_null = List.mem ~equal:String.equal vs "(NULL)" in
+ let vs = if has_null then List.filter vs ~f:(String.(<>) "(NULL)") else vs in
let ty = String.Table.find_exn col_types k in
let quote = Sql.Type.is_quoted ty in
let vs_oq =
@@ -119,13 +123,13 @@ let extract_filter col_fqns col_types params key_prefix =
let print_select ?(td=false) ?(label="") ?(selected=[]) ?(attrs=[]) options =
if td then printf "\n";
- if label <> "" then printf "%s:\n" label;
+ if String.(label <> "") then printf "%s:\n" label;
printf "\n";
@@ -144,14 +148,14 @@ let get_options_for_field db_result ~data col =
if db_result#getisnull i col then "(NULL)" else data.(i).(col)
in aux (elem::acc) (i-1)
in
- let cmp x y =
+ let compare x y =
try
- if ftype = Postgresql.INT4
+ if Poly.(ftype = Postgresql.INT4)
then compare (int_of_string x) (int_of_string y)
- else compare x y
+ else String.compare x y
with _ -> 0
in
- List.sort ~cmp (List.dedup (aux [] nRows))
+ List.sort ~compare (List.dedup_and_sort ~compare (aux [] nRows))
let get_options_for_field_once db_result col =
let data = db_result#get_all in
@@ -159,7 +163,7 @@ let get_options_for_field_once db_result col =
let get_options_for_field_once_byname db_result col_name =
let col_names = db_result#get_fnames_lst in
- let col = match List.findi ~f:(fun _ c -> c = col_name) col_names with
+ let col = match List.findi ~f:(fun _ c -> String.(c = col_name)) col_names with
| Some (i, _) -> i
| _ -> failwith (sprintf "could not find column '%s' amongst [%s]" col_name (String.concat ~sep:"; " col_names))
in
@@ -185,7 +189,7 @@ let print_options_for_field namespace db_result col =
let print_options_for_fields conn tbl namespace =
let query = "SELECT * FROM " ^ tbl in
- let result = Sql.exec_exn ~conn ~query in
+ let%map result = Postgresql_async.exec_exn ~conn ~query in
List.iter ~f:(print_options_for_field namespace result)
(List.range 1 result#nfields);
printf "
\n"
@@ -216,6 +220,7 @@ let tc_config_fields = [
"host_pcpus";
"live_patching";
"host_type";
+ "bootmode_precedence"
]
let build_fields = [
@@ -224,6 +229,8 @@ let build_fields = [
"build_number";
"build_date";
"build_tag";
+ "patches_applied";
+ "build_is_release"
]
let job_fields = [
@@ -232,18 +239,22 @@ let job_fields = [
let som_config_tbl_exists ~conn som_id =
let som_config_tbl = sprintf "som_config_%d" som_id in
- som_config_tbl, Sql.tbl_exists ~conn ~tbl:som_config_tbl
+ let%map r = Postgresql_async.wrap_sql ~conn (Sql.tbl_exists ~tbl:som_config_tbl) in
+ som_config_tbl, r
let get_std_xy_choices ~conn =
+ let%map colnames = Postgresql_async.wrap_sql ~conn (Sql.get_col_names ~tbl:"machines") in
let machine_field_lst =
- List.tl_exn (Sql.get_col_names ~conn ~tbl:"machines") in
+ List.tl_exn colnames in
job_fields @ build_fields @ tc_config_fields @ machine_field_lst
let get_xy_choices ~conn configs som_configs_opt =
let som_configs_lst = match som_configs_opt with
| None -> []
| Some som_configs -> List.tl_exn som_configs#get_fnames_lst
- in get_std_xy_choices ~conn @ configs#get_fnames_lst @ som_configs_lst
+ in
+ let%map r = get_std_xy_choices ~conn in
+ r @ configs#get_fnames_lst @ som_configs_lst
let print_axis_choice ?(multiselect=false) label id choices =
printf "\n" id;
@@ -252,24 +263,25 @@ let print_axis_choice ?(multiselect=false) label id choices =
print_select_list ~label ~attrs:attrs choices;
printf "\n"
-let print_empty_x_axis_choice ~conn =
+let print_empty_x_axis_choice ~conn:_ =
print_axis_choice "X axis" "xaxis" [] ~multiselect:true
-let print_empty_y_axis_choice ~conn =
+let print_empty_y_axis_choice ~conn:_ =
print_axis_choice "Y axis" "yaxis" []
let print_x_axis_choice ~conn configs som_configs_opt =
- print_axis_choice "X axis" "xaxis" ~multiselect:true
- (get_xy_choices ~conn configs som_configs_opt)
+ let%map r = get_xy_choices ~conn configs som_configs_opt in
+ print_axis_choice "X axis" "xaxis" ~multiselect:true r
let print_y_axis_choice ~conn configs som_configs_opt =
+ let%map r = get_xy_choices ~conn configs som_configs_opt in
print_axis_choice "Y axis" "yaxis"
- ("result" :: (get_xy_choices ~conn configs som_configs_opt))
+ ("result" :: r)
let get_tc_config_tbl_name conn som_id =
let query = "SELECT tc_fqn FROM soms " ^
"WHERE som_id = " ^ (string_of_int som_id) in
- let result = Sql.exec_exn ~conn ~query in
+ let%map result = Postgresql_async.exec_exn ~conn ~query in
let tc_fqn = String.lowercase (result#getvalue 0 0) in
(tc_fqn, "tc_config_" ^ tc_fqn)
diff --git a/static/rage.js b/static/rage.js
index 13d4c16..bc3fa3b 100644
--- a/static/rage.js
+++ b/static/rage.js
@@ -2,7 +2,7 @@
Invariants (also reflected on server side):
- Default value for field "xaxis" is "branch".
- Default value for field "yaxis" is "result".
-- show_points, show_avgs, y_fromto_zero is selected by default.
+- show_points, show_avgs is selected by default.
- All other checkboxes are not selected by default.
- "SHOW FOR" is the first (default) option for filters ("f_").
- "ALL" is the first (default) option for filter values ("v_").
@@ -10,7 +10,7 @@ Invariants (also reflected on server side):
// === GLOBAL VARIABLES --- start ===
var autofetch = false; // if false, the following triggers have no effect
-var checkboxes_on_by_default = ["show_points", "show_avgs", "y_fromto_zero"];
+var checkboxes_on_by_default = ["show_points", "show_avgs"];
//defaults for all drop-down selection options above filter boxes
var graph_selection_defaults = {
xaxis: ["branch"], //multiselect defaults of length > 1 will always show up in the url
@@ -571,11 +571,22 @@ function GraphObject() {
function get_distribution_lines(data) {
// var avgs = [], min_maxs = [], std_devs = [];
- var medians = [], prc40to60s = [], prc25to75s = [], prc15to85s = [];
+ var medians = [], prc25to75s = [], fences = [];
var plus = function(acc, x) {return acc + x;};
var plus_sq = function(acc, x) {return acc + x*x;};
var min = function(acc, x) {return acc < x ? acc : x;};
var max = function(acc, x) {return acc < x ? x : acc;};
+ var interpolate = function(ys, n) {
+ var idx = Math.floor(n);
+ var d = n - idx;
+ if (d < Number.EPSILON)
+ return ys[idx];
+ else
+ return (1-d)*ys[idx] + d*ys[idx+1];
+ };
+ var quantile = function(ys, q) {
+ return interpolate(ys, q * ys.length)
+ };
$.each(group_by_x(data), function(i, x_ys) {
var x = x_ys[0], ys = x_ys[1];
numerical_sort(ys);
@@ -585,14 +596,18 @@ function GraphObject() {
// min_maxs.push([x, ys.reduce(max, -Infinity), ys.reduce(min, Infinity)]);
// var std_dev = Math.sqrt(ys.reduce(plus_sq) / n - avg*avg);
// std_devs.push([x, avg + std_dev, avg - std_dev]);
- medians.push([x, ys[n / 2], ys[n / 2]]);
- prc40to60s.push([x, ys[Math.floor(n * 0.60)], ys[Math.floor(n * 0.40)]]);
- prc25to75s.push([x, ys[Math.floor(n * 0.75)], ys[Math.floor(n * 0.25)]]);
- prc15to85s.push([x, ys[Math.floor(n * 0.85)], ys[Math.floor(n * 0.15)]]);
+ var median = quantile(ys, 0.5);
+ medians.push([x, median, median]);
+ var q1 = quantile(ys, 0.25);
+ var q3 = quantile(ys, 0.75);
+ prc25to75s.push([x, q3, q1]);
+ // Tukey fences
+ var iqr = q3 - q1
+ fences.push([x, q3 + 1.5*iqr, q1-1.5*iqr]);
});
return { // min_max: min_maxs, std_dev: std_devs,
- median: medians, prc40to60: prc40to60s, prc25to75: prc25to75s,
- prc15to85: prc15to85s};
+ median: medians, prc25to75: prc25to75s, fences: fences
+ };
}
function draw_graph(o, cb) { //will call callback function and pass in the time that plotting started
@@ -622,9 +637,8 @@ function GraphObject() {
label: null, points: {show: false},
lines: {show: true, lineWidth: 0, fill: fill}});
};
- add_percentile(i, dist.prc15to85, 0.2);
+ add_percentile(i, dist.fences, 0.2);
add_percentile(i, dist.prc25to75, 0.4);
- add_percentile(i, dist.prc40to60, 0.6);
var label_shown = is_checked("show_points") || is_checked("show_avgs");
series.push({color: point_series[i].color, data: dist.median,
label: label_shown ? null : point_series[i].label,
@@ -830,6 +844,11 @@ const setPresetBriefReport = () => {
// Select SW legend position, our interesting data is usually NE
select('legend_position', 'sw');
+ // Split by build_is_release
+ unselectAll('v_build_is_release');
+ select('v_build_is_release', 'ALL');
+ select('f_build_is_release', 1);
+
// Enable autodraw - jquery to trigger its jquery change event
$('input[name=auto_redraw]').prop('checked', true);
redraw_graph();