Source file process_stats.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
open! Core
module Sample = struct
module State = struct
type t =
| Running
| Sleeping
| Stopped
| Zombie
| Other
[@@deriving bin_io, sexp]
end
type t =
{ timestamp : Time_ns.t
; cpu_percent : float
; : Int64.t
; memory_virt_bytes : Int64.t
; num_threads : int
; state : State.t
}
[@@deriving bin_io, sexp]
end
module Tree_sample = struct
type process_info =
{ pid : Pid.t
; command : string
; sample : Sample.t
}
[@@deriving bin_io, sexp]
type t =
{ root : process_info
; children : process_info list
; total_cpu_percent : float
; : Int64.t
}
[@@deriving bin_io, sexp]
end
module History = struct
type t = Tree_sample.t Circular_buffer.t [@@deriving sexp_of]
let create ~max_samples = Circular_buffer.create ~capacity:max_samples
let add t sample = Circular_buffer.push t sample
let to_list t = Circular_buffer.to_list t
let is_empty t = Circular_buffer.length t = 0
let latest t = Circular_buffer.last t
let cpu_percents t =
Circular_buffer.to_list_map t ~f:(fun (s : Tree_sample.t) -> s.total_cpu_percent)
;;
let t =
Circular_buffer.to_list_map t ~f:(fun (s : Tree_sample.t) -> s.total_memory_rss_bytes)
;;
let t =
Circular_buffer.fold t ~init:0L ~f:(fun acc (s : Tree_sample.t) ->
if Int64.( > ) s.total_memory_rss_bytes acc then s.total_memory_rss_bytes else acc)
;;
end
let sparkline_chars =
[| "\xe2\x96\x81"
; "\xe2\x96\x82"
; "\xe2\x96\x83"
; "\xe2\x96\x84"
; "\xe2\x96\x85"
; "\xe2\x96\x86"
; "\xe2\x96\x87"
; "\xe2\x96\x88"
|]
;;
let render_sparkline values ~max_width ?min_bound ?max_bound ?(right_align = false) () =
if List.is_empty values
then if right_align then String.make max_width ' ' else ""
else (
let values = List.take (List.rev values) max_width |> List.rev in
let min_val =
match min_bound with
| Some m -> m
| None -> List.fold values ~init:Float.infinity ~f:Float.min
in
let max_val =
match max_bound with
| Some m -> m
| None -> List.fold values ~init:Float.neg_infinity ~f:Float.max
in
let range = max_val -. min_val in
let strs =
List.map values ~f:(fun v ->
let normalized = if Float.( > ) range 0.0 then (v -. min_val) /. range else 0.5 in
let idx =
Float.to_int (normalized *. Float.of_int (Array.length sparkline_chars - 1))
in
let idx = Int.max 0 (Int.min idx (Array.length sparkline_chars - 1)) in
sparkline_chars.(idx))
in
let sparkline = String.concat strs in
if right_align
then (
let num_chars = List.length strs in
let padding = max_width - num_chars in
if padding > 0 then String.make padding ' ' ^ sparkline else sparkline)
else sparkline)
;;