(********************************************************************)
(* *)
(* The Why3 Verification Platform / The Why3 Development Team *)
(* Copyright 2010-2015 -- INRIA - CNRS - Paris-Sud University *)
(* *)
(* This software is distributed under the terms of the GNU Lesser *)
(* General Public License version 2.1, with the special exception *)
(* on linking described in file LICENSE. *)
(* *)
(********************************************************************)
open Why3
open Why3session_lib
open Format
module Hprover = Whyconf.Hprover
module S = Session
let orig_dir = Sys.getcwd ()
let opt_output_dir = ref "./"
let opt_provers = ref []
let opt_aggregate = ref false
let opt_print_csv = ref false
let simple_read_opt_prover s =
try
Whyconf.parse_filter_prover s
with Whyconf.ParseFilterProver _ ->
raise (Arg.Bad
"--filter-prover name[,version[,alternative]|,,alternative] \
regexp must start with ^")
let opt_value_not_valid = ref "-1."
type gnuplot =
| PDF
| PNG
| SVG
| QT
| GP
let opt_gnuplot = ref []
let select_gnuplot = function
| "pdf" -> opt_gnuplot := PDF::!opt_gnuplot
| "png" -> opt_gnuplot := PNG::!opt_gnuplot
| "svg" -> opt_gnuplot := SVG::!opt_gnuplot
| "qt" -> opt_gnuplot := QT::!opt_gnuplot
| "gp" -> opt_gnuplot := GP::!opt_gnuplot
| _ -> assert false (** absurd: Arg already filtered bad cases *)
type gnuplot_x =
| Xtime
| Xgoals
let opt_gnuplot_x = ref Xtime
let select_gnuplot_x = function
| "time" -> opt_gnuplot_x := Xtime
| "goals" -> opt_gnuplot_x := Xgoals
| _ -> assert false (** absurd: Arg already filtered bad cases *)
let opt_data = ref false
let opt_by_time = ref false
let spec =
("-o",
Arg.Set_string opt_output_dir,
"
where to produce files (default: session dir)") ::
("--filter-prover",
Arg.String (fun s ->
opt_provers := (simple_read_opt_prover s)::!opt_provers),
" select the provers")::
("--data", Arg.Set opt_data,
" for all the goals give the time taken by each provers that proved it")::
("--aggregate", Arg.Set opt_aggregate,
" aggregate all the input into one file named \
aggregate_data.* and aggregate.*")::
("--value-not-valid", Arg.Set_string opt_value_not_valid,
" value used when the external proof is not valid (only for --data)")::
("--valid_by_time", Arg.Set opt_by_time,
" give the evolution of the number of goal proved \
for each provers (default)")::
("--gnuplot", Arg.Symbol (["pdf";"png";"svg";"qt";"gp"],select_gnuplot),
" run gnuplot on the produced file (currently only with --valid_by_time)\
(gp write the gnuplot script used for generating the other case)")::
("--gnuplot-x", Arg.Symbol (["time";"goals"],select_gnuplot_x),
" select the data used for the x axes time or number of goal proved \
(default time)")::
("--output-csv", Arg.Set opt_print_csv,
" print the csv, set by default when --gnuplot is not set")::
common_options
(** Normal *)
let print_cell fmt pa =
match pa.Session.proof_state with
| Session.Done {Call_provers.pr_answer = Call_provers.Valid;
pr_time = time} -> fprintf fmt "%f" time
| _ -> fprintf fmt "%s" !opt_value_not_valid
let rec print_line fmt provers a =
begin match a with
| Session.Goal a ->
fprintf fmt "\"%s\"" a.Session.goal_name.Ident.id_string;
Whyconf.Sprover.iter (fun p ->
try
let pa = Session.PHprover.find a.Session.goal_external_proofs p in
fprintf fmt ",%a" print_cell pa
with Not_found ->
fprintf fmt ",") provers;
fprintf fmt "\n@?" (* no @\n since we use Pp.wnl *)
| _ -> () end;
Session.iter (print_line fmt provers) a
let run_one_normal filter_provers fmt fname =
let project_dir = Session.get_project_dir fname in
let session,_use_shapes = Session.read_session project_dir in
let provers = Session.get_used_provers session in
let provers =
match filter_provers with
| [] -> provers
| _ ->
Whyconf.Sprover.filter
(fun p ->
List.exists
(fun f -> Whyconf.filter_prover f p) filter_provers) provers in
fprintf fmt ",%a\n@?"
(Pp.print_iter1 Whyconf.Sprover.iter Pp.comma (Pp.asd Whyconf.print_prover))
provers;
Session.session_iter (print_line fmt provers) session
let run_normal dir filter_provers =
if !opt_aggregate then
let out = open_out (Filename.concat dir "aggregate_data.csv") in
let fmt = formatter_of_out_channel out in
Pp.wnl fmt;
iter_files (run_one_normal filter_provers fmt);
close_out out
else
iter_files (fun fname ->
let out = open_out
(Filename.concat dir
((try Filename.chop_extension fname with _ -> fname)^"_data.csv")) in
let fmt = formatter_of_out_channel out in
Pp.wnl fmt;
run_one_normal filter_provers fmt fname;
close_out out)
(** By time *)
let print_float_list =
Pp.print_list_delim ~start:Pp.lsquare ~stop:Pp.rsquare ~sep:Pp.semi Pp.float
let grab_valid_time provers_time provers pa =
let prover = pa.Session.proof_prover in
if Whyconf.Sprover.mem prover provers then
match pa.Session.proof_state with
| Session.Done {Call_provers.pr_answer = Call_provers.Valid;
pr_time = time} ->
let l = Whyconf.Hprover.find_def provers_time [] prover in
Whyconf.Hprover.replace provers_time prover (time::l)
| _ -> ()
let run_one_by_time provers_time filter_provers fname =
let project_dir = Session.get_project_dir fname in
let session,_use_shapes = Session.read_session project_dir in
let provers = Session.get_used_provers session in
let provers =
match filter_provers with
| [] -> provers
| _ ->
Whyconf.Sprover.filter
(fun p ->
List.exists
(fun f -> Whyconf.filter_prover f p) filter_provers) provers in
Session.session_iter_proof_attempt
(grab_valid_time provers_time provers) session
let print_provers_time (provers_time : float list Whyconf.Hprover.t) fmt =
let provers_time =
Whyconf.Hprover.fold (fun p e acc -> Whyconf.Mprover.add p e acc)
provers_time Whyconf.Mprover.empty in
fprintf fmt ",%a\n@?"
(Pp.print_iter2 Whyconf.Mprover.iter Pp.comma Pp.nothing
(Pp.asd Whyconf.print_prover) Pp.nothing)
provers_time;
let l = Whyconf.Mprover.values provers_time in
let l = List.map (fun l ->
let sorted = List.fast_sort Pervasives.compare l in
(ref sorted,ref 0)) l in
let rec print_line (l : (float list ref * int ref) list) =
(* get the minimum *)
let lmin = List.fold_left (fun acc (e,_) ->
match !e with
| [] -> acc
| a::_ -> min a acc) max_float l in
if lmin = max_float then () (* finished *)
else begin
(* remove the minimum and increase the number of proved *)
let rec remove nb = function
| [] -> []
| a::e when a = lmin -> incr nb; remove nb e
| e -> e in
List.iter (fun (e,nb) -> e:=remove nb !e) l;
(* Print the current number of proved *)
fprintf fmt "%f,%a\n@?" lmin
(Pp.print_list Pp.comma (fun fmt (_,nb) -> pp_print_int fmt (!nb)))
l;
print_line l end in
print_line l
let create_gnuplot_by_time nb_provers fmt =
let timeaxe, goalsaxe =
match !opt_gnuplot_x with | Xtime -> "x","y" | Xgoals -> "y","x"
in
Format.pp_print_string fmt
"\
set datafile separator \",\"\n\
set key autotitle columnhead\n\
set object rectangle from screen 0,0 to screen 1,1 behind\n\
set key rmargin width -4 samplen 2\n";
Format.fprintf fmt "set logscale %s 10\n" timeaxe;
Format.fprintf fmt "set %slabel \"time(s)\"\n" timeaxe;
Format.fprintf fmt "set %slabel \"number of solved goals\"\n\n" goalsaxe;
Format.pp_print_string fmt "plot \\\n";
for i=1 to nb_provers do
begin match !opt_gnuplot_x with
| Xtime ->
Format.fprintf fmt
"data_filename using ($1):($%i) with steps title columnhead(%i)"
(i+1) (i+1)
| Xgoals ->
Format.fprintf fmt
"data_filename using ($%i):($1) with fsteps title columnhead(%i)"
(i+1) (i+1)
end;
if i <> nb_provers then Format.pp_print_string fmt ",\\\n";
done
let print_file out f : unit =
let fmt = formatter_of_out_channel out in
Pp.wnl fmt;
f fmt;
close_out out
let print_args fmt args =
(Pp.print_iter1 Array.iter
(fun fmt () -> Format.pp_print_string fmt " ") (* no @\n *)
(fun fmt -> Format.fprintf fmt "%S"))
fmt args
let call_gnuplot arg1 arg2 csv_file gp_file =
let args =
[| "gnuplot"; "-e"; arg1; "-e"; arg2;
"-e"; Printf.sprintf "data_filename = \"%s\"" (Opt.get csv_file);
Opt.get gp_file |] in
Debug.dprintf verbose "exec:%a@." print_args args;
let pid = Unix.create_process "gnuplot" args
Unix.stdin Unix.stdout Unix.stderr in
match Unix.waitpid [] pid with
| _, Unix.WEXITED 0 -> ()
| _ -> Format.eprintf "Command failed:%a@." print_args args
let run_by_time_gen dir canonical_name iter =
let to_remove = Stack.create () in
let canonical_name = Filename.concat dir canonical_name in
(* compute stats *)
let provers_time = Whyconf.Hprover.create 5 in
iter provers_time;
(* print .csv if necessary *)
let csv_file =
if !opt_gnuplot = [] || !opt_print_csv then
let fname = canonical_name ^ ".csv" in
print_file (open_out fname)
(fun fmt -> print_provers_time provers_time fmt);
Some fname
else if !opt_gnuplot <> [] then
let fname,ch = Filename.open_temp_file "why3session" ".csv" in
Stack.push fname to_remove;
print_file ch
(fun fmt -> print_provers_time provers_time fmt);
Some fname
else None
in
(* create .gp if necessary *)
let nb_provers = Whyconf.Hprover.length provers_time in
let gp_file =
if List.mem GP !opt_gnuplot
then let fname = canonical_name ^ ".gp" in
print_file (open_out fname)
(fun fmt -> create_gnuplot_by_time nb_provers fmt);
Some fname
else if !opt_gnuplot <> [] then
let fname,ch = Filename.open_temp_file "why3session" ".gp" in
Stack.push fname to_remove;
print_file ch
(fun fmt -> create_gnuplot_by_time nb_provers fmt);
Some fname
else None
in
(* output .png, .pdf, .csv and run .qt if necessary *)
if List.mem PNG !opt_gnuplot then
call_gnuplot
"set terminal pngcairo size 600, 400"
(Printf.sprintf "set output \"%s.png\"" canonical_name)
csv_file gp_file;
if List.mem PDF !opt_gnuplot then
call_gnuplot
"set terminal pdfcairo"
(Printf.sprintf "set output \"%s.pdf\"" canonical_name)
csv_file gp_file;
if List.mem SVG !opt_gnuplot then
call_gnuplot
"set terminal svg"
(Printf.sprintf "set output \"%s.svg\"" canonical_name)
csv_file gp_file;
if List.mem QT !opt_gnuplot then
call_gnuplot
"set terminal qt persist"
""
csv_file gp_file;
(* Clean up temporary files *)
Stack.iter Sys.remove to_remove
let run_by_time dir filter_provers =
if !opt_aggregate then
run_by_time_gen dir "aggregate.csv"
(fun provers_time ->
iter_files (run_one_by_time provers_time filter_provers))
else
iter_files (fun fname ->
run_by_time_gen dir (try Filename.chop_extension fname with _ -> fname)
(fun provers_time ->
run_one_by_time provers_time filter_provers fname)
)
(* Common *)
let run () =
let _,whyconf,should_exit1 = read_env_spec () in
if should_exit1 then exit 1;
let filter_provers =
List.map (Whyconf.filter_prover_with_shortcut whyconf) !opt_provers in
let dir = Sysutil.absolutize_filename orig_dir (!opt_output_dir) in
if !opt_data then run_normal dir filter_provers;
if !opt_by_time || not (!opt_data) then run_by_time dir filter_provers
let cmd =
{ cmd_spec = spec;
cmd_desc =
"output session as a table or graph for simple processing or viewing";
cmd_name = "csv";
cmd_run = run;
}