~james/scope

ref: 6e057d536590a73e00a473ec6176185a088491b2 scope/lib/watch.ml -rw-r--r-- 4.5 KiB
6e057d53 — james chore(import): snapshot james.dad.town/scope at 034e6b67d44c 14 hours ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
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