package eio-trace

  1. Overview
  2. Docs

Source file render.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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
let rec find_first_aux arr test low high =
  if low = high then high
  else (
    let mid = (low + high) / 2 in
    let diff = test mid in
    if diff >= 0. then find_first_aux arr test low mid
    else find_first_aux arr test (mid + 1) high
  )

(* Binary search. Return the index of the first element where [test i] is true.
   If [test] isn't true for any element, returns the length of the array.
   Assumes that if [test i] then it's true for all later entries. *)
let find_first ?(start = 0) arr test =
  find_first_aux arr test start (Array.length arr)

module type CANVAS = sig
  type context

  type text_extents = {
    x_bearing : float;
    y_bearing : float;
    width : float;
    height : float;
    x_advance : float;
    y_advance : float;
  }

  type rectangle = {
    x : float;
    y : float;
    w : float;
    h : float;
  }

  val set_font_size : context -> float -> unit
  val set_line_width : context -> float -> unit
  val set_source_rgb : context -> r:float -> g:float -> b:float -> unit
  val set_source_rgba : context -> r:float -> g:float -> b:float -> a:float -> unit
  (* (Cairo needs to know the r,g,b too) *)
  val set_source_alpha : context -> r:float -> g:float -> b:float -> float -> unit
  val move_to : context -> x:float -> y:float -> unit
  val line_to : context -> x:float -> y:float -> unit
  val rectangle : context -> x:float -> y:float -> w:float -> h:float -> unit
  val stroke : context -> unit
  val stroke_preserve : context -> unit
  val fill : context -> unit
  val fill_preserve : context -> unit
  val text_extents : context -> string -> text_extents
  val paint_text : context -> ?clip_area:(float * float) -> x:float -> y:float -> string -> unit
  val paint : ?alpha:float -> context -> unit
  val clip_extents : context -> rectangle
end

module Make (C : CANVAS) = struct
  module Style = struct
    let line_spacing = View.pixels_per_row
    let big_text = 12.
    let small_text = 8.

    let fiber_padding_top = 10.0
    let fiber_height = 14.0

    let running_fiber cr =
      C.set_source_rgb cr ~r:0.4 ~g:0.8 ~b:0.4

    let suspended_fiber cr =
      C.set_source_rgb cr ~r:0.4 ~g:0.4 ~b:0.4
  end

  (** Draw [msg] in the area (min_x, max_x) and ideally centred at [x]. *)
  let draw_label (v : View.t) cr ~min_x ~max_x ~x ~y msg =
    let text_width = C.((text_extents cr msg).x_advance) in
    let x =
      x -. (text_width /. 2.)   (* Desired start for centred text *)
      |> min (max_x -. text_width)
      |> max min_x in

    if x +. text_width > max_x then (
      (* Doesn't fit. Draw as much as we can. *)
      C.paint_text cr ~x:min_x ~y ~clip_area:(max_x -. x, v.height) msg
    ) else (
      (* Show label on left margin if the thread starts off-screen *)
      let x =
        if x < 4.0 then min 4.0 (max_x -. text_width)
        else x in
      C.paint_text cr ~x ~y msg
    )

  let bracket_width = 4.

  let draw_l_bracket cr ~x ~y ~w ~h =
    let w = min bracket_width w in
    C.move_to cr ~x:(x +. w) ~y;
    C.line_to cr ~x ~y;
    C.line_to cr ~x ~y:(y +. h);
    C.line_to cr ~x:(x +. w) ~y:(y +. h);
    C.stroke cr

  let draw_r_bracket cr ~x ~y ~w ~h =
    let w = min bracket_width w in
    C.move_to cr ~x:(x -. w) ~y;
    C.line_to cr ~x ~y;
    C.line_to cr ~x ~y:(y +. h);
    C.line_to cr ~x:(x -. w) ~y:(y +. h);
    C.stroke cr

  let y_of_row v row =
    float row *. Style.line_spacing -. v.View.scroll_y

  let iter_spans v fn item =
    Array.iter fn item.Layout.activations;
    let stop = Option.value item.end_time ~default:v.View.layout.duration in
    fn (stop, [])

  let link_fibers v cr ~x a b =
    let upper, lower = if a.Layout.y < b.Layout.y then a, b else b, a in
    C.move_to cr ~x ~y:(y_of_row v upper.y +. Style.fiber_padding_top);
    C.line_to cr ~x ~y:(y_of_row v lower.y +. Style.fiber_padding_top +. Style.fiber_height);
    C.stroke cr

  let rec render_events v cr (item : Layout.item) =
    for i = 0 to Array.length item.events - 1 do
      let (ts, e) = item.events.(i) in
      match (e : Layout.event) with
      | Add_fiber { parent; child } ->
        let parent = Layout.get v.View.layout parent |> Option.value ~default:item in
        render_fiber v cr ts child;
        Style.running_fiber cr;
        let x = View.x_of_time v ts in
        link_fibers v cr ~x parent child
      | Create_cc (ty, cc) -> render_cc v cr ts cc ty
      | Log msg | Error msg ->
        let is_error = match e with Error _ -> true | _ -> false in
        let x = View.x_of_time v ts in
        let y = y_of_row v item.y in
        if is_error then (
          C.set_source_rgb cr ~r:0.8 ~g:0.0 ~b:0.0;
          C.move_to cr ~x ~y;
          C.line_to cr ~x ~y:(y +. float item.height *. View.pixels_per_row);
        ) else (
          C.set_source_rgb cr ~r:0.0 ~g:0.0 ~b:0.0;
          C.move_to cr ~x ~y:(y +. 13.);
          C.line_to cr ~x ~y:(y +. 7.);
        );
        C.stroke cr;
        C.set_font_size cr Style.small_text;
        let rec next i =
          if i < Array.length item.events - 1 then (
            match item.events.(i + 1) with
            | (ts, (Log _ | Error _ | Create_cc _)) -> Some ts
            | (_, Add_fiber _) -> next (i + 1)
          ) else (
            item.end_time
          )
        in
        let clip_area = next i |> Option.map (fun t2 ->
            let x2 = View.x_of_time v t2 in
            (x2 -. x -. 2.0, v.height)
          ) in
        C.paint_text cr ~x:(x +. 2.) ~y:(y +. 8.) msg
          ?clip_area
    done

  and render_fiber v cr start_time (f : Layout.item) =
    let y = y_of_row v f.y in
    if y < v.height then (
(*
    let x = View.x_of_time v start_time in
    let w =
      match f.end_time with
      | None -> v.width -. min x 0.
      | Some stop -> View.x_of_time v stop -. x
    in
*)
      if y +. View.pixels_per_row > 0. then (
        C.set_font_size cr Style.big_text;
        let prev_stack = ref [] in
        let event = ref (start_time, []) in
        f |> iter_spans v (fun event' ->
            let t0, stack = !event in
            event := event';
            let t1 = fst event' in
            let x0 = View.x_of_time v t0 in
            let x1 = View.x_of_time v t1 in
            let w = x1 -. x0 in
            begin match stack with
              | `Suspend _ :: _ -> Style.suspended_fiber cr
              | `Span _ :: _ -> C.set_source_rgb cr ~r:0.5 ~g:0.9 ~b:0.5
              | [] -> Style.running_fiber cr
            end;
            C.rectangle cr ~x:x0 ~y:(y +. 10.) ~w ~h:14.0;
            C.fill cr;
            let label op =
              let clip_area = (w -. 2.0, v.height) in
              C.paint_text cr ~x:(x0 +. 2.) ~y:(y +. 22.) op
                ~clip_area
            in
            begin match stack with
              | `Suspend op :: _ -> C.set_source_rgb cr ~r:1.0 ~g:1.0 ~b:1.0; label op
              | `Span op :: p ->
                C.set_source_rgb cr ~r:0.0 ~g:0.0 ~b:0.0;
                if p == !prev_stack then label op
              | [] -> ()
            end;
            prev_stack := stack
          );
      );
      render_events v cr f
    )

  and render_cc v cr start_time (cc : Layout.item) ty =
    render_events v cr cc;
    let label = Option.value cc.name ~default:ty in
    let x = View.x_of_time v start_time in
    let y = y_of_row v cc.y in
    let w =
      match cc.end_time with
      | None -> v.width -. x +. 100.
      | Some stop -> View.x_of_time v stop -. x
    in
    let h = float cc.height *. Style.line_spacing -. 4. in
    C.set_source_rgb cr ~r:0.0 ~g:0.0 ~b:0.0;
    draw_l_bracket cr ~x ~y ~w ~h; 
    draw_r_bracket cr ~x:(x +. w) ~y ~w ~h; 
    C.set_font_size cr Style.small_text;
    let clip_width =
      match cc.end_cc_label with
      | Some t -> View.x_of_time v t -. x
      | None -> w
    in
    C.paint_text cr ~x:(x +. 2.) ~y:(y +. 8.) ~clip_area:(clip_width -. 2., v.height) label

  let min_render_width = 0.2

  (* Call [fn] for each event in the visible region, plus one on each side (if any).
     Very close events (at the current zoom level) are skipped.
     The caller will typically render the region ending in this event. *)
  let iter_gc_spans v fn ring =
    let arr = ring.Layout.Ring.events in
    let time_of i = fst arr.(i) in
    let start = max 0 (find_first arr (fun i -> time_of i -. v.View.start_time) - 1) in
    let stop_time = View.time_of_x v v.width in
    let stop = min (Array.length arr) (1 + find_first arr (fun i -> time_of i -. stop_time) ~start) in
    let visible_time = View.timespan_of_width v min_render_width in
    let rec visit ~prev i =
      if i < stop then (
        let time, e = arr.(i) in
        let next_useful_time = prev +. visible_time in
        if time < next_useful_time then (
          let i = find_first arr (fun i -> time_of i -. next_useful_time) ~start:(i + 1) - 1 in
          let time, e = arr.(i) in
          fn (i, time, e);
          visit ~prev:time (i + 1)
        ) else (
          fn (i, time, e);
          visit ~prev:time (i + 1)
        )
      )
    in
    visit ~prev:(-.visible_time) start;
    if stop = Array.length arr then
      fn (stop, v.View.layout.duration, [])

  let render_gc_events v cr (ring : Layout.Ring.t) layer =
    let y = y_of_row v ring.y in
    let h = float ring.height *. Style.line_spacing in
    if y <= v.height && y +. h >= 0. then(
      let event = ref (0, 0.0, []) in
      C.set_font_size cr Style.big_text;
      ring |> iter_gc_spans v (fun event' ->
          let i, t0, stack = !event in
          let prev_stack = if i = 0 then [] else snd (ring.events.(i - 1)) in
          event := event';
          let _, t1, _ = event' in
          let x0 = View.x_of_time v t0 in
          let x1 = View.x_of_time v t1 in
          let w = x1 -. x0 in
          begin match stack with
            | [] -> ()
            | Suspend op :: p ->
              begin match layer with
                | `Bg ->
                  let g = 0.9 in
                  C.set_source_rgb cr ~r:g ~g:g ~b:(g /. 2.);
                  C.rectangle cr ~x:x0 ~y ~w ~h;
                  C.fill cr
                | `Fg ->
                  if p == prev_stack then (
                    let clip_area = (w -. 0.2, v.height) in
                    C.set_source_rgb cr ~r:0.0 ~g:0.0 ~b:0.0;
                    C.paint_text cr ~x:(x0 +. 2.) ~y:(y +. 12.) op
                      ~clip_area
                  )
              end
            | Gc op :: p ->
              let g = max 0.1 (0.1 *. float (List.length stack)) in
              match layer with
              | `Bg ->
                C.set_source_rgb cr ~r:1.0 ~g:g ~b:(g /. 2.);
                C.rectangle cr ~x:x0 ~y ~w ~h;
                C.fill cr
              | `Fg ->
                if p == prev_stack then (
                  let clip_area = (w -. 0.2, v.height) in
                  if g < 0.5 then C.set_source_rgb cr ~r:1.0 ~g:1.0 ~b:1.0
                  else C.set_source_rgb cr ~r:0.0 ~g:0.0 ~b:0.0;
                  C.paint_text cr ~x:(x0 +. 2.) ~y:(y +. 12.) op
                    ~clip_area
                )
          end
        )
    )

  let link_domain v cr ~x (fiber : Layout.item) (ring : Layout.Ring.t) =
    let fiber_y = y_of_row v fiber.y +. Style.fiber_padding_top in
    let ring_y = y_of_row v ring.y in
    let (y1, y2) =
      if fiber.y < ring.y then (
        (fiber_y +. Style.fiber_height, ring_y +. float ring.height *. View.pixels_per_row)
      ) else (
        fiber_y, ring_y
      )
    in
    Style.suspended_fiber cr;
    C.move_to cr ~x ~y:y1;
    C.line_to cr ~x ~y:y2;
    C.stroke cr

  let render_ring_bg v cr ring =
    render_gc_events v cr ring `Bg;
    ring.roots |> List.iter @@ fun (root : Layout.Ring.root) ->
    C.set_line_width cr 4.0;
    root.parent |> Option.iter (fun (ts, parent) ->
        Layout.get v.layout parent |> Option.iter @@ fun (parent : Layout.item) ->
        let x = View.x_of_time v ts in
        link_domain v cr ~x parent ring
      )

  let render_ring v cr (ring : Layout.Ring.t) =
    render_gc_events v cr ring `Fg;
    ring.roots |> List.iter @@ fun (root : Layout.Ring.root) ->
    root.cc |> Option.iter (fun (_ts, cc) -> render_events v cr cc)

  let render_grid v cr =
    C.set_line_width cr 1.0;
    C.set_source_rgb cr ~r:0.7 ~g:0.7 ~b:0.7;
    let clip = C.clip_extents cr in
    let grid_step, grid_start_x, grid_step_x = View.grid v clip.x in
    let rec draw x =
      if x < clip.x +. clip.w then (
        C.move_to cr ~x:x ~y:clip.y;
        C.line_to cr ~x:x ~y:(clip.y +. clip.h);
        C.stroke cr;
        draw (x +. grid_step_x)
      ) in
    draw grid_start_x;
    C.set_source_rgb cr ~r:0.4 ~g:0.4 ~b:0.4;
    let msg =
      if grid_step >= 1.0 then Printf.sprintf "Each grid division: %.0f s" grid_step
      else if grid_step >= 0.001 then Printf.sprintf "Each grid division: %.0f ms" (grid_step *. 1000.)
      else if grid_step >= 0.000_001 then Printf.sprintf "Each grid division: %.0f us" (grid_step *. 1_000_000.)
      else if grid_step >= 0.000_000_001 then Printf.sprintf "Each grid division: %.0f ns" (grid_step *. 1_000_000_000.)
      else Printf.sprintf "Each grid division: %.2g s" grid_step in
    let extents = C.text_extents cr msg in
    let y = v.height -. C.(extents.height +. extents.y_bearing) -. 2.0 in
    C.paint_text cr ~x:4.0 ~y msg

  let render (v : View.t) cr =
    C.set_source_rgb cr ~r:0.9 ~g:0.9 ~b:0.9;
    C.paint cr;
    v.layout.rings |> Trace.Rings.iter (fun _id -> render_ring_bg v cr);
    render_grid v cr;
    C.set_source_rgb cr ~r:0.0 ~g:0.0 ~b:0.0;
    v.layout.rings |> Trace.Rings.iter (fun _id -> render_ring v cr)
end
OCaml

Innovation. Community. Security.