From 40b684627df4a8da45924ee9c77fc34b46c93109 Mon Sep 17 00:00:00 2001 From: Valentin Perrelle <valentin.perrelle@cea.fr> Date: Thu, 5 Jan 2023 17:30:19 +0100 Subject: [PATCH] [Eva] Add a module to compute and export stats --- src/plugins/eva/utils/statistics.ml | 184 +++++++++++++++++++++++++++ src/plugins/eva/utils/statistics.mli | 40 ++++++ 2 files changed, 224 insertions(+) create mode 100644 src/plugins/eva/utils/statistics.ml create mode 100644 src/plugins/eva/utils/statistics.mli diff --git a/src/plugins/eva/utils/statistics.ml b/src/plugins/eva/utils/statistics.ml new file mode 100644 index 00000000000..8c7dad25f98 --- /dev/null +++ b/src/plugins/eva/utils/statistics.ml @@ -0,0 +1,184 @@ +(**************************************************************************) +(* *) +(* This file is part of Frama-C. *) +(* *) +(* Copyright (C) 2007-2022 *) +(* CEA (Commissariat à l'énergie atomique et aux énergies *) +(* alternatives) *) +(* *) +(* you can redistribute it and/or modify it under the terms of the GNU *) +(* Lesser General Public License as published by the Free Software *) +(* Foundation, version 2.1. *) +(* *) +(* It is distributed in the hope that it will be useful, *) +(* but WITHOUT ANY WARRANTY; without even the implied warranty of *) +(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *) +(* GNU Lesser General Public License for more details. *) +(* *) +(* See the GNU Lesser General Public License version 2.1 *) +(* for more details (enclosed in the file licenses/LGPLv2.1). *) +(* *) +(**************************************************************************) + +(* Statistics are stored in a dictonary, implemented as an hashtable from + keys to integers. + + [Key] is the representation of the dictionnary keys: a registered + ['a t] statistic accompagnied by the function or the statement the stat is + about + + Statistics must be registered before usage. The registry keeps track of the + registered statistics and allow the reloading of projects by matching the + previous stats to the current ones. +*) + +(* --- Type --- *) + +type _ kind = + | Global : unit kind + | Function : Cil_types.kernel_function kind + | Statement : Cil_types.stmt kind + +type 'a t = { + id: int; + name: string; + kind: 'a kind; +} + + +(* --- Registry --- *) + +type registered_stat = Registered : 'a t -> registered_stat + +let kind_to_string : type a. a kind -> string = function + | Global -> "global" + | Function -> "function" + | Statement -> "statement" + +let registry = Hashtbl.create 13 +let last_id = ref 0 + +let register (type a) (name : string) (kind : a kind) : a t = + try + let Registered stat = Hashtbl.find registry name in + match stat.kind, kind with + | Global, Global -> stat + | Function, Function -> stat + | Statement, Statement -> stat + | _ -> + Self.fatal + "%s statistic \"%s\" was already registered with as a %s statistic" + name (kind_to_string kind) (kind_to_string stat.kind) + with Not_found -> + incr last_id; + let stat = { id = !last_id; name; kind } in + Hashtbl.add registry name (Registered stat); + stat + +let register_global_stat name = + register name Global + +let register_function_stat name = + register name Function + +let register_statement_stat name = + register name Statement + + +(* --- Keys --- *) + +type key = Key : 'a t * 'a -> key + +module Key = Datatype.Make_with_collections ( + struct + include Datatype.Serializable_undefined + + type t = key + let name = "Statistics.Key" + let rehash (Key (s, x)) = + (Key (register s.name s.kind, x)) + let reprs = [] + let equal (Key (s1,x1)) (Key (s2,x2)) = + match s1.kind, s2.kind with + | Global, Global -> true + | Function, Function -> Kernel_function.equal x1 x2 + | Statement, Statement -> Cil_datatype.Stmt.equal x1 x2 + | _, _ -> false + let compare (Key (s1,x1)) (Key (s2,x2)) = + let c = s1.id - s2.id in + if c <> 0 then c else + match s1.kind, s2.kind with + | Global, Global -> 0 + | Function, Function -> Kernel_function.compare x1 x2 + | Statement, Statement -> Cil_datatype.Stmt.compare x1 x2 + | Global, (Function | Statement) -> -1 + | (Function | Statement), Global -> 1 + | Function, Statement -> -1 + | Statement, Function -> 1 + let hash (Key (s,x)) = + let h = match s.kind with + | Global -> 0 + | Function -> Kernel_function.hash x + | Statement -> Cil_datatype.Stmt.hash x + in + Hashtbl.hash (s.id, h) + let copy k = k + let pretty fmt (Key (s,x)) = + match s.kind with + | Global -> + Format.fprintf fmt "%s" s.name + | Function -> + Format.fprintf fmt "%s:%a" s.name Kernel_function.pretty x + | Statement -> + Format.fprintf fmt "%s:%a" s.name Cil_datatype.Stmt.pretty x + end) + + +(* --- Projectified state --- *) + +module State = + State_builder.Hashtbl + (Key.Hashtbl) + (Datatype.Int) + (struct + let name = "Eva.Statistics.State" + let dependencies = [ Self.state ] + let size = 17 + end) + + +(* --- Statistics update --- *) + +let set (type a) (stat : a t) (x : a) value = + let k = Key (stat,x) in + State.replace k value +let update (type a) (stat : a t) (x : a) (f : int -> int) = + let k = Key (stat,x) in + State.replace k (f (State.find_opt k |> Option.value ~default:0)) + +let incr (type a) (stat : a t) (x : a) = + update stat x (fun v -> v + 1) + +let grow (type a) (stat : a t) (x : a) value = + update stat x (fun v -> max v value) + + +(* -- Export --- *) + +let export_as_list () = + State.to_seq () |> List.of_seq |> + List.sort (fun (k1,_v1) (k2,_v2) -> Key.compare k1 k2) + +let export_as_csv_to_channel out_channel = + let fmt = Format.formatter_of_out_channel out_channel in + let l = export_as_list () in + let pp_stat fmt (k,v) = + Format.fprintf fmt "%a\t%d\n" Key.pretty k v + in + List.iter (pp_stat fmt) l + +let export_as_csv ?filename () = + let filename = (filename : Filepath.Normalized.t option :> string option) in + let filename = Option.value ~default:"stats.json" filename in + let out_channel = open_out (filename :> string) in + export_as_csv_to_channel out_channel diff --git a/src/plugins/eva/utils/statistics.mli b/src/plugins/eva/utils/statistics.mli new file mode 100644 index 00000000000..038a57bb0e9 --- /dev/null +++ b/src/plugins/eva/utils/statistics.mli @@ -0,0 +1,40 @@ +(**************************************************************************) +(* *) +(* This file is part of Frama-C. *) +(* *) +(* Copyright (C) 2007-2022 *) +(* CEA (Commissariat à l'énergie atomique et aux énergies *) +(* alternatives) *) +(* *) +(* you can redistribute it and/or modify it under the terms of the GNU *) +(* Lesser General Public License as published by the Free Software *) +(* Foundation, version 2.1. *) +(* *) +(* It is distributed in the hope that it will be useful, *) +(* but WITHOUT ANY WARRANTY; without even the implied warranty of *) +(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *) +(* GNU Lesser General Public License for more details. *) +(* *) +(* See the GNU Lesser General Public License version 2.1 *) +(* for more details (enclosed in the file licenses/LGPLv2.1). *) +(* *) +(**************************************************************************) + +type 'a t + +(* Register a statistic class *) +val register_global_stat : string -> unit t +val register_function_stat : string -> Cil_types.kernel_function t +val register_statement_stat : string -> Cil_types.stmt t + +(* Set the stat to the given value *) +val set : 'a t -> 'a -> int -> unit + +(* Adds 1 to the stat or set it to 1 if undefined *) +val incr : 'a t -> 'a -> unit + +(* Set the stat to the maximum between the current value and the given value *) +val grow : 'a t -> 'a -> int -> unit + +(* Export the computed statistics as CSV *) +val export_as_csv : ?filename:Filepath.Normalized.t -> unit -> unit -- GitLab