-
Notifications
You must be signed in to change notification settings - Fork 10
/
memory.ml
153 lines (132 loc) · 5.46 KB
/
memory.ml
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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
(** Memory reporting - GC and OS, optionally malloc
General background:
- VSZ is not very intersting, this is the amount of memory which is mapped to the process address space.
It's not really memory use, only the amount of memory the process can access without triggering a segfault.
- RSS is resident set size: this is the real world data. It's tracked by kernel and is the amount of memory
currently allocated to this process. Most of the time this is what you want to look at.
- Malloc stats: those are metrics tracked by C malloc (jemalloc, tcmalloc, glibc, etc).
- size is basically like VSZ but from malloc point of view.
That is it does not include mmap files for instance.
- used is basically RSS from malloc point of view.
- heap is the sum of all currently malloced values for which [free] had not been called.
So this is what application requested, not including metadata, cache, etc
- Gc stats are one level above and are tracked by ocaml gc.
e.g. heap is the total size allocate for ocaml program. See [Gc] module documentation for more details.
*)
open Prelude
open ExtLib
open Printf
let log = Log.from "memory"
type t = {
rss : int; (** resident set size *)
vsize : int; (** virtual memory size *)
nr_maps : int; (** number of VM mappings *)
swap_used : int; (** used swap size *)
}
let get_num = int_of_string $ String.replace_chars (fun c -> if Stre.ASCII.is_digit c then String.of_char c else "")
let pagesize = Int64.to_int ExtUnix.Specific.(sysconf PAGESIZE)
(**
@param swap whether to compute swap used, can be slow (many seconds), default [false]
@return virtual memory info
*)
let get_vm_info ?(swap=false) () =
let (vsize,rss) =
match Action.file_lines "/proc/self/statm" with
| [] -> Log.self #warn "cannot read /proc/self/statm, no VM info"; (0,0)
| s::_ -> Scanf.sscanf s "%d %d" (fun vsize rss -> (pagesize * vsize, pagesize * rss))
in
let nr_maps = List.length @@ Action.file_lines ("/proc/self/maps") in (* FIXME deleted *)
(* process smaps *)
let swap_used =
match swap with
| false -> 0
| true ->
Action.file_lines ("/proc/self/smaps") |>
List.fold_left (fun acc s -> if Stre.starts_with s "Swap:" then acc + get_num s else acc) 0
in
{ rss; vsize; nr_maps; swap_used = swap_used * 1024; }
let show_vm_info () =
let bytes = Action.bytes_string in
let { rss; vsize; nr_maps; swap_used } = get_vm_info () in
let swap = if swap_used > 0 then sprintf " %s," (bytes swap_used) else "" in
sprintf "VM: rss %s, vsz %s,%s maps %d" (bytes rss) (bytes vsize) swap nr_maps
let show_gc_heap ?(st=Gc.quick_stat ()) () =
let open Action in
sprintf "%s (max %s, chunks %d)"
(caml_words st.Gc.heap_words)
(caml_words st.Gc.top_heap_words)
st.Gc.heap_chunks
let show_gc_info () =
let open Action in
let st = Gc.quick_stat () in
let gc_heap = show_gc_heap ~st () in
let gc_ctrs =
sprintf "%s %s %s"
(caml_words_f st.Gc.minor_words)
(caml_words_f st.Gc.promoted_words)
(caml_words_f st.Gc.major_words)
in
let gc_coll =
sprintf "%u %u %u"
st.Gc.compactions
st.Gc.major_collections
st.Gc.minor_collections
in
sprintf "GC: Heap: %s Counters(mi,pr,ma): %s Collections(mv,ma,mi): %s" gc_heap gc_ctrs gc_coll
let show_lwt_info () =
let (r, w, t) = Lwt_engine.(readable_count (), writable_count (), timer_count ()) in
sprintf "lwt readable %d, writable %d, timer %d" r w t
(* hooks for Memory_gperftools *)
let show_crt_info = ref (fun () -> "MALLOC: ?")
let malloc_release = ref (ignore : unit -> unit)
let reclaim_s () =
let module A = Action in
let st1 = Gc.stat () in
let { rss; _ } = get_vm_info () in
let t1 = Time.now () in
Gc.compact ();
let t2 = Time.now () in
!malloc_release ();
let t3 = Time.now () in
let st3 = Gc.stat () in
let { rss=rss'; _ } = get_vm_info () in
let changed f a b =
if a = b then sprintf "= %s" (f a) else sprintf "%s -> %s" (f a) (f b)
in
sprintf "Memory.reclaim: heap %s live %s freelist %s (%s), rss %s"
(changed A.caml_words st1.heap_words st3.heap_words)
(changed A.caml_words st1.live_words st3.live_words)
(changed string_of_int st1.free_blocks st3.free_blocks)
(Time.duration_str @@ t2 -. t1)
(if !malloc_release == ignore then A.bytes_string rss
else sprintf "%s (%s)" (changed A.bytes_string rss rss') (Time.duration_str @@ t3 -. t2))
let reclaim () = log #info "%s" @@ reclaim_s ()
let reclaim_silent () =
Gc.compact ();
!malloc_release ()
let (add_stats,new_stats,log_stats,get_stats) =
let f_print = ref [] in (* called in reverse - and it is fine *)
let f_get = ref [] in
let log_stats () =
List.iter (fun f -> f ()) !f_print;
List.iter (fun f -> log #info_s @@ f ()) !f_get
in
let get_stats () = List.map (fun f -> f ()) !f_get in
(tuck f_print), (tuck f_get), log_stats, get_stats
let track_global = ref []
let show_global_reachable () =
let l = List.map (fun (name,repr) -> sprintf "%s %s" name (Action.caml_words @@ Obj.reachable_words repr)) !track_global in
sprintf "reachable: %s" (String.concat " " l)
let track_global name var = tuck track_global (name,Obj.repr var)
let show_c_info () = sprintf "%s. %s" (show_vm_info ()) (!show_crt_info ())
let show_all_info () =
[
show_c_info ();
show_gc_info ();
show_lwt_info ();
]
let log_all_info () = show_all_info () |> List.iter log#info_s
let () = new_stats show_c_info
let () = new_stats show_gc_info
let () = new_stats show_lwt_info
let () = new_stats show_global_reachable