summaryrefslogtreecommitdiffstats
path: root/contrib/visualize-alignment/tracetops.ml
blob: 3ea23270179c8e1213aee5023ffbaf94cd0a6b54 (plain)
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
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
#!/usr/bin/ocamlrun /usr/bin/ocaml

(* Convert *.qtr (qemu block device trace) to Postscript.
 * Copyright (C) 2009-2010 Red Hat Inc.
 * By Richard W.M. Jones <rjones@redhat.com>.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *)

(* Note that we use ordinary OCaml ints, which means this program is
 * limited to: ~1TB disks for 32 bit machines, or effectively unlimited
 * for 64 bit machines.  Also we make several 512 byte sector
 * assumptions.
 *)

#use "topfind";;
#require "extlib";;

open ExtList
open Scanf
open Printf

type op = Read | Write

(* If 'true' then print debug messages. *)
let debug = true

(* Width of each row (in sectors) in the output. *)
let row_size = 64

(* Desirable alignment (sectors). *)
let alignment = 8

(* Height (in 1/72 inch) of the final image. *)
let height = 6.*.72.

(* Width (in 1/72 inch) of the final image. *)
let width = 6.*.72.

(* Reserve at left for the sector number (comes out of width). *)
let sn_width = 36.

let input =
  if Array.length Sys.argv = 2 then
    Sys.argv.(1)
  else
    failwith "usage: tracetops filename.qtr"

(* Read the input file. *)
let nb_sectors, accesses =
  let chan = open_in input in
  let nb_sectors =
    let summary = input_line chan in
    if String.length summary < 1 || summary.[0] <> 'S' then
      failwith (sprintf "%s: input is not a qemu block device trace file"
                  input);
    sscanf summary "S %d" (fun x -> x) in

  if nb_sectors mod row_size <> 0 then
    failwith (sprintf "input nb_sectors (%d) not divisible by row size (%d)"
                nb_sectors row_size);

  (* Read the reads and writes from the remainder of the file. *)
  let accesses = ref [] in
  let rec loop () =
    let line = input_line chan in
    let rw, s, n = sscanf line "%c %d %d" (fun rw s n -> (rw, s, n)) in
    let rw =
      match rw with
      | 'R' -> Read | 'W' -> Write
      | c -> failwith
          (sprintf "%s: error reading input: got '%c', expecting 'R' or 'W'"
             input c) in
    if n < 0 || s < 0 || s+n > nb_sectors then
      failwith (sprintf "%s: s (%d), n (%d) out of range" input s n);
    let aligned = s mod alignment = 0 && n mod alignment = 0 in
    accesses := (rw, aligned, s, n) :: !accesses;
    loop ()
  in
  (try loop () with
   | End_of_file -> ()
   | Scan_failure msg ->
       failwith (sprintf "%s: error reading input: %s" input msg)
  );
  close_in chan;

  let accesses = List.rev !accesses in

  if debug then (
    eprintf "%s: nb_sectors = %d, accesses = %d\n"
      input nb_sectors (List.length accesses)
  );

  nb_sectors, accesses

(* If the accesses list contains any qtrace on/off patterns (in
 * guestfish: debug "qtrace /dev/vda (on|off)") then filter out the
 * things we want to display.  Otherwise leave the whole trace.
 *)
let accesses =
  let contains_qtrace_patterns =
    let rec loop = function
      | [] -> false
      | (Read, _, 2, 1) :: (Read, _, 21, 1) :: (Read, _, 15, 1) ::
          (Read, _, 2, 1) :: _ -> true
      | (Read, _, 2, 1) :: (Read, _, 15, 1) :: (Read, _, 21, 1) ::
          (Read, _, 2, 1) :: _ -> true
      | _ :: rest -> loop rest
    in
    loop accesses in

  if contains_qtrace_patterns then (
    if debug then eprintf "%s: contains qtrace on/off patterns\n%!" input;

    let rec find_qtrace_on = function
      | [] -> []
      | (Read, _, 2, 1) :: (Read, _, 21, 1) :: (Read, _, 15, 1) ::
          (Read, _, 2, 1) :: rest -> rest
      | (Read, _, 2, 1) :: (Read, _, 15, 1) :: (Read, _, 21, 1) ::
          (Read, _, 2, 1) :: rest ->
          eprintf "ignored 'qtrace off' pattern when expecting 'qtrace on'\n";
          find_qtrace_on rest
      | _ :: rest -> find_qtrace_on rest
    and split_until_qtrace_off = function
      | [] -> [], []
      | (Read, _, 2, 1) :: (Read, _, 15, 1) :: (Read, _, 21, 1) ::
          (Read, _, 2, 1) :: rest -> [], rest
      | (Read, _, 2, 1) :: (Read, _, 21, 1) :: (Read, _, 15, 1) ::
          (Read, _, 2, 1) :: rest ->
          eprintf "found 'qtrace on' pattern when expecting 'qtrace off'\n";
          split_until_qtrace_off rest
      | x :: ys ->
          let xs, ys = split_until_qtrace_off ys in
          x :: xs, ys
    and filter_accesses xs =
      let xs = find_qtrace_on xs in
      if xs <> [] then (
        let xs, ys = split_until_qtrace_off xs in
        let ys = filter_accesses ys in
        xs @ ys
      ) else
        []
    in
    filter_accesses accesses
  ) else
    accesses

let ranges =
  (* Given the number of sectors, make the row array. *)
  let nr_rows = nb_sectors / row_size in
  let rows = Array.make nr_rows false in

  List.iter (
    fun (_, _, s, n) ->
      let i0 = s / row_size in
      let i1 = (s+n-1) / row_size in
      for i = i0 to i1 do rows.(i) <- true done;
  ) accesses;

  (* Coalesce rows into a list of ranges of rows we will draw. *)
  let rows = Array.to_list rows in
  let rows = List.mapi (fun i v -> (v, i)) rows in
  let ranges =
    (* When called, we are in the middle of a range which started at i0. *)
    let rec loop i0 = function
      | (false, _) :: (false, _) :: (true, i1) :: []
      | _ :: (_, i1) :: []
      | (_, i1) :: [] ->
          [i0, i1]
      | (false, _) :: (false, _) :: (true, _) :: rest
      | (false, _) :: (true, _) :: rest
      | (true, _) :: rest ->
          loop i0 rest
      | (false, i1) :: rest ->
          let i1 = i1 - 1 in
          let rest = List.dropwhile (function (v, _) -> not v) rest in
          (match rest with
           | [] -> [i0, i1]
           | (_, i2) :: rest -> (i0, i1) :: loop i2 rest)
      | [] -> assert false
    in
    loop 0 (List.tl rows) in

  if debug then (
    eprintf "%s: rows = %d (ranges = %d)\n" input nr_rows (List.length ranges);
    List.iter (
      fun (i0, i1) ->
        eprintf "  %d - %d (rows %d - %d)\n"
          (i0 * row_size) ((i1 + 1) * row_size - 1) i0 i1
    ) ranges
  );

  ranges

(* Locate where we will draw the rows and cells in the final image. *)
let iter_rows, mapxy, row_height, cell_width =
  let nr_ranges = List.length ranges in
  let nr_breaks = nr_ranges - 1 in
  let nr_rows =
    List.fold_left (+) 0 (List.map (fun (i0,i1) -> i1-i0+1) ranges) in
  let nr_rnb = nr_rows + nr_breaks in
  let row_height = height /. float nr_rnb in
  let cell_width = (width -. sn_width) /. float row_size in

  if debug then (
    eprintf "number of rows and breaks = %d\n" nr_rnb;
    eprintf "row_height x cell_width = %g x %g\n" row_height cell_width
  );

  (* Create a higher-order function to iterate over the rows. *)
  let rec iter_rows f =
    let rec loop row = function
      | [] -> ()
      | (i0,i1) :: rows ->
          for i = i0 to i1 do
            let y = float (row+i-i0) *. row_height in
            f y (Some i)
          done;
          (* Call an extra time for the break. *)
          let y = float (row+i1-i0+1) *. row_height in
          if rows <> [] then f y None;
          (* extra +1 here is to skip the break *)
          loop (row+i1-i0+1+1) rows
    in
    loop 0 ranges
  in

  (* Create a hash which maps from the row number to the position
   * where we draw the row.  If the row is not drawn, the hash value
   * is missing.
   *)
  let row_y = Hashtbl.create nr_rows in
  iter_rows (
    fun y ->
      function
      | Some i -> Hashtbl.replace row_y i y
      | None -> ()
  );

  (* Create a function which maps from the sector number to the final
   * position that we will draw it.
   *)
  let mapxy s =
    let r = s / row_size in
    let y = try Hashtbl.find row_y r with Not_found -> assert false in
    let x = sn_width +. cell_width *. float (s mod row_size) in
    x, y
  in

  iter_rows, mapxy, row_height, cell_width

(* Start the PostScript file. *)
let () =
  printf "%%!PS-Adobe-3.0 EPSF-3.0\n";
  printf "%%%%BoundingBox: -10 -10 %g %g\n"
    (width +. 10.) (height +. row_height +. 20.);
  printf "%%%%Creator: tracetops.ml (part of libguestfs)\n";
  printf "%%%%Title: %s\n" input;
  printf "%%%%LanguageLevel: 2\n";
  printf "%%%%Pages: 1\n";
  printf "%%%%Page: 1 1\n";
  printf "\n";

  printf "/min { 2 copy gt { exch } if pop } def\n";
  printf "/max { 2 copy lt { exch } if pop } def\n";

  (* Function for drawing cells. *)
  printf "/cell {\n";
  printf "  newpath\n";
  printf "    moveto\n";
  printf "    %g 0 rlineto\n" cell_width;
  printf "    0 %g rlineto\n" row_height;
  printf "    -%g 0 rlineto\n" cell_width;
  printf "  closepath\n";
  printf "  gsave fill grestore 0.75 setgray stroke\n";
  printf "} def\n";

  (* Define colours for different cell types. *)
  printf "/unalignedread  { 0.95 0.95 0 setrgbcolor } def\n";
  printf "/unalignedwrite { 0.95 0 0    setrgbcolor } def\n";
  printf "/alignedread    { 0 0.95 0    setrgbcolor } def\n";
  printf "/alignedwrite   { 0 0 0.95    setrgbcolor } def\n";

  (* Get width of text. *)
  printf "/textwidth { stringwidth pop } def\n";

  (* Draw the outline. *)
  printf "/outline {\n";
  printf "  newpath\n";
  printf "    %g 0 moveto\n" sn_width;
  printf "    %g 0 lineto\n" width;
  printf "    %g %g lineto\n" width height;
  printf "    %g %g lineto\n" sn_width height;
  printf "  closepath\n";
  printf "  0.5 setlinewidth 0.3 setgray stroke\n";
  printf "} def\n";

  (* Draw the outline breaks. *)
  printf "/breaks {\n";
  iter_rows (
    fun y ->
      function
      | Some _ -> ()
      | None ->
          let f xmin xmax =
            let yll = y +. row_height /. 3. -. 2. in
            let ylr = y +. row_height /. 2. -. 2. in
            let yur = y +. 2. *. row_height /. 3. in
            let yul = y +. row_height /. 2. in
            printf "  newpath\n";
            printf "    %g %g moveto\n" xmin yll;
            printf "    %g %g lineto\n" xmax ylr;
            printf "    %g %g lineto\n" xmax yur;
            printf "    %g %g lineto\n" xmin yul;
            printf "  closepath\n";
            printf "  1 setgray fill\n";
            printf "  newpath\n";
            printf "    %g %g moveto\n" xmin yll;
            printf "    %g %g lineto\n" xmax ylr;
            printf "    %g %g moveto\n" xmax yur;
            printf "    %g %g lineto\n" xmin yul;
            printf "  closepath\n";
            printf "  0.5 setlinewidth 0.3 setgray stroke\n"
          in
          f (sn_width -. 6.) (sn_width +. 6.);
          f (width -. 6.) (width +. 6.)
  );
  printf "} def\n";

  (* Draw the labels. *)
  printf "/labels {\n";
  printf "  /Courier findfont\n";
  printf "  0.75 %g mul 10 min scalefont\n" row_height;
  printf "  setfont\n";
  iter_rows (
    fun y ->
      function
      | Some i ->
          let sector = i * row_size in
          printf "  newpath\n";
          printf "    /s { (%d) } def\n" sector;
          printf "    %g s textwidth sub 4 sub %g moveto\n" sn_width (y +. 2.);
          printf "  s show\n"
      | None -> ()
  );
  printf "} def\n";

  (* Print the key. *)
  printf "/key {\n";
  printf "  /Times-Roman findfont\n";
  printf "  10. scalefont\n";
  printf "  setfont\n";
  let x = sn_width and y = height +. 10. in
  printf "  unalignedwrite %g %g cell\n" x y;
  let x = x +. cell_width +. 4. in
  printf "  newpath %g %g moveto (unaligned write) 0.3 setgray show\n" x y;
  let x = x +. 72. in
  printf "  unalignedread %g %g cell\n" x y;
  let x = x +. cell_width +. 4. in
  printf "  newpath %g %g moveto (unaligned read) 0.3 setgray show\n" x y;
  let x = x +. 72. in
  printf "  alignedwrite %g %g cell\n" x y;
  let x = x +. cell_width +. 4. in
  printf "  newpath %g %g moveto (aligned write) 0.3 setgray show\n" x y;
  let x = x +. 72. in
  printf "  alignedread %g %g cell\n" x y;
  let x = x +. cell_width +. 4. in
  printf "  newpath %g %g moveto (aligned read) 0.3 setgray show\n" x y;
  printf "} def\n";

  printf "\n"

(* Draw the accesses. *)
let () =
  (* Sort the accesses so unaligned ones are displayed at the end (on
   * top of aligned ones) and writes on top of reads.  This isn't
   * really perfect, but it'll do.
   *)
  let cmp (rw, aligned, s, n) (rw', aligned', s', n') =
    let r = compare rw rw' (* Write later *) in
    if r <> 0 then r else (
      let r = compare aligned' aligned (* unaligned later *) in
      if r <> 0 then r else
        compare (n, s) (n', s')
    )
  in
  let accesses = List.sort ~cmp accesses in

  List.iter (
    fun op ->
      let col, s, n =
        match op with
        | Read, false, s, n ->
            "unalignedread", s, n
        | Write, false, s, n ->
            "unalignedwrite", s, n
        | Read, true, s, n ->
            "alignedread", s, n
        | Write, true, s, n ->
            "alignedwrite", s, n in
      for i = s to s+n-1 do
        let x, y = mapxy i in
        printf "%s %g %g cell\n" col x y
      done;
      printf "\n"
  ) accesses

(* Finish off the PostScript output. *)
let () =
  printf "outline breaks labels key\n";
  printf "%%%%EOF\n"