open Model type event = { sample : int; time : float; kind : string; before : Capture.t option; after : Capture.t; changes : Analysis.change list; context_changes : string list; aliases : Analysis.alias list; complete : bool; directory : string; mappings : int; bytes : int64; executable : int; writable : int; huge : int; } let run ~capture ~wait ~emit ~directory ~interval ~count ~retain ~max_entries ~max_pairs ~include_ad ~scan_aliases ~selected () = if count < 0 || retain < 1 || not (Float.is_finite interval) || interval <= 0. then invalid_arg "invalid watch sampling or retention settings"; if Sys.file_exists directory then invalid_arg "watch output directory must not already exist"; Unix.mkdir directory 0o700; let retained = Queue.create () in let previous = ref None and identity = ref None in let findings = ref false and incomplete = ref false in let statistics = ref (0, 0L, 0, 0, 0) in let notify sample kind before after changes context_changes aliases complete path = let mappings, bytes, executable, writable, huge = !statistics in emit { sample; time = Unix.gettimeofday (); kind; before; after; changes; context_changes; aliases; complete; directory = path; mappings; bytes; executable; writable; huge } in let publish sample kind before after changes context_changes aliases complete = let path = Filename.concat directory (Printf.sprintf "%08d" sample) in Unix.mkdir path 0o700; Option.iter (Capture.save (Filename.concat path "before")) before; Capture.save (Filename.concat path "after") after; Queue.add path retained; while Queue.length retained > retain do let oldest = Queue.take retained in List.iter (fun name -> let file = Filename.concat oldest name in if Sys.file_exists file then Unix.unlink file) ["before"; "after"]; Unix.rmdir oldest done; notify sample kind before after changes context_changes aliases complete path in let rec loop sample = let after = capture () in let context = Capture.context after in (match !identity with | None -> identity := Some context.identity | Some expected when expected <> context.identity -> failwith "domain identity changed between watch samples" | _ -> ()); let regions = Paging.enumerate ~max_entries context (Capture.read after) |> List.filter selected in statistics := List.fold_left (fun (n, bytes, x, w, huge) region -> match region.Paging.state with | Paging.Present m -> n + 1, Int64.add bytes m.size, x + (if m.permissions.executable then 1 else 0), w + (if m.permissions.writable then 1 else 0), huge + (if m.size > 4096L then 1 else 0) | _ -> n, bytes, x, w, huge) (0, 0L, 0, 0, 0) regions; let complete = List.for_all (fun r -> match r.Paging.state with Paging.Unknown _ -> false | _ -> true) regions in let aliases = if scan_aliases then Analysis.aliases ~max_pairs regions else [] in if aliases <> [] || List.exists (fun r -> match r.Paging.state with Paging.Broken _ -> true | _ -> false) regions then findings := true; if not complete then begin incomplete := true; publish sample "incomplete" !previous after [] [] aliases false end else begin (match !previous with | None -> publish sample "baseline" None after [] [] aliases true | Some before -> let earlier = Capture.context before in let context_changes = Model.context_changes earlier context in if Model.root earlier <> Model.root context then publish sample "address_space" (Some before) after [] context_changes aliases true else begin let before_regions = Paging.enumerate ~max_entries earlier (Capture.read before) in let changes = Analysis.diff ~include_ad before_regions regions |> List.filter (fun (c : Analysis.change) -> selected { Paging.start = c.start; length = c.length; state = Paging.Unknown "" }) in if changes <> [] || context_changes <> [] then begin findings := true; publish sample "change" (Some before) after changes context_changes aliases true end else notify sample "sample" (Some before) after [] [] aliases true "" end); previous := Some after end; if count = 0 || sample < count then begin wait interval; loop (sample + 1) end in (try loop 1 with exn -> if Array.length (Sys.readdir directory) = 0 then Unix.rmdir directory; raise exn); if !incomplete then 2 else if !findings then 1 else 0