contrib: Visualizing block device access and alignment.
authorRichard W.M. Jones <rjones@redhat.com>
Tue, 5 Oct 2010 21:19:15 +0000 (22:19 +0100)
committerRichard W.M. Jones <rjones@redhat.com>
Tue, 5 Oct 2010 21:19:15 +0000 (22:19 +0100)
contrib/visualize-alignment/.gitignore [new file with mode: 0644]
contrib/visualize-alignment/README [new file with mode: 0644]
contrib/visualize-alignment/guestfish-N-fs-10M-aligned-part-disk.qtr [new file with mode: 0644]
contrib/visualize-alignment/guestfish-N-fs-10M.qtr [new file with mode: 0644]
contrib/visualize-alignment/guestfish-add-mount.qtr [new file with mode: 0644]
contrib/visualize-alignment/guestfish-write-hello.qtr [new file with mode: 0644]
contrib/visualize-alignment/qemu-0.13-trace-block-device-access.patch [new file with mode: 0644]
contrib/visualize-alignment/tracetops.ml [new file with mode: 0755]

diff --git a/contrib/visualize-alignment/.gitignore b/contrib/visualize-alignment/.gitignore
new file mode 100644 (file)
index 0000000..68a2009
--- /dev/null
@@ -0,0 +1,2 @@
+*.eps
+*.png
diff --git a/contrib/visualize-alignment/README b/contrib/visualize-alignment/README
new file mode 100644 (file)
index 0000000..917bd77
--- /dev/null
@@ -0,0 +1,19 @@
+- guestfish-N-fs-10M.qtr
+
+  The command 'guestfish -N fs:ext2:10' before we modified the
+  part-disk API to align the partition to 64 sectors.
+
+- guestfish-N-fs-10M-aligned-part-disk.qtr
+
+  The command 'guestfish -N fs:ext2:10' after we modified the
+  part-disk API to align the partition to 64 sectors.
+
+- guestfish-add-mount.qtr
+
+  $ guestfish -a test1.img -m /dev/sda1
+  where test1.img was created by the previous command.
+
+- guestfish-write-hello.qtr
+
+  $ guestfish -a test1.img -m /dev/sda1 write /hello "hello, world."
+  Note that the trace includes the adding and mounting operations.
diff --git a/contrib/visualize-alignment/guestfish-N-fs-10M-aligned-part-disk.qtr b/contrib/visualize-alignment/guestfish-N-fs-10M-aligned-part-disk.qtr
new file mode 100644 (file)
index 0000000..d99c24d
--- /dev/null
@@ -0,0 +1,122 @@
+S 20480
+R 0 1
+R 0 8
+R 8 8
+R 20352 8
+R 20464 8
+R 0 8
+R 8 8
+R 20472 8
+R 20216 8
+R 20416 8
+R 20224 8
+R 20080 8
+R 2048 8
+R 24 8
+R 56 8
+R 120 8
+R 16 8
+R 128 8
+R 64 8
+R 512 8
+R 32 8
+R 4096 8
+R 0 32
+R 20352 8
+R 20464 8
+R 0 8
+R 8 8
+R 0 8
+R 0 8
+R 0 32
+R 32 64
+R 128 8
+R 20456 8
+W 0 24
+W 20456 24
+R 0 8
+R 8 24
+W 0 8
+R 20352 8
+R 20464 8
+R 0 32
+R 32 64
+W 0 8
+R 20472 8
+R 20216 8
+R 20472 8
+R 20416 8
+R 20224 8
+R 20080 8
+R 20464 8
+R 8 8
+R 0 8
+R 2048 8
+R 24 8
+R 56 8
+R 120 8
+R 16 8
+R 128 8
+R 64 8
+R 512 8
+R 32 8
+R 4096 8
+R 20288 8
+R 20400 8
+R 64 8
+R 72 8
+R 20416 1
+R 20160 8
+R 20408 8
+R 2112 8
+R 88 8
+R 120 8
+R 184 8
+R 80 8
+R 192 8
+R 128 8
+R 576 8
+R 96 8
+R 4160 8
+R 0 8
+R 64 2
+W 64 379
+W 443 55
+W 16450 4
+W 16532 92
+R 20416 1
+R 20160 1
+R 20161 1
+R 20162 1
+R 20163 5
+R 2112 1
+R 2113 1
+R 2114 6
+R 576 1
+R 577 1
+R 578 6
+R 4160 1
+R 4161 1
+R 4162 6
+R 0 8
+W 16624 230
+W 20288 128
+W 66 2
+R 20288 8
+R 20400 8
+R 64 8
+R 72 8
+R 20416 1
+R 20160 8
+R 20408 8
+R 2112 8
+R 88 8
+R 120 8
+R 184 8
+R 80 8
+R 192 8
+R 128 8
+R 576 8
+R 96 8
+R 4160 8
+R 0 8
diff --git a/contrib/visualize-alignment/guestfish-N-fs-10M.qtr b/contrib/visualize-alignment/guestfish-N-fs-10M.qtr
new file mode 100644 (file)
index 0000000..4b44e4b
--- /dev/null
@@ -0,0 +1,122 @@
+S 20480
+R 0 1
+R 0 8
+R 8 8
+R 20352 8
+R 20464 8
+R 0 8
+R 8 8
+R 20472 8
+R 20216 8
+R 20416 8
+R 20224 8
+R 20080 8
+R 2048 8
+R 24 8
+R 56 8
+R 120 8
+R 16 8
+R 128 8
+R 64 8
+R 512 8
+R 32 8
+R 4096 8
+R 0 32
+R 20352 8
+R 20464 8
+R 0 8
+R 8 8
+R 0 8
+R 0 8
+R 0 32
+R 32 64
+R 128 8
+R 20456 8
+W 0 24
+W 20456 24
+R 0 8
+R 8 24
+W 0 8
+R 20352 8
+R 20464 8
+R 0 32
+R 32 64
+W 0 8
+R 20472 8
+R 20216 8
+R 20416 8
+R 20224 8
+R 20080 8
+R 20464 8
+R 8 8
+R 0 8
+R 2048 8
+R 24 8
+R 56 8
+R 120 8
+R 16 8
+R 128 8
+R 64 8
+R 512 8
+R 32 8
+R 4096 8
+R 20225 8
+R 20457 8
+R 1 8
+R 9 8
+R 20473 7
+R 20217 8
+R 20465 8
+R 2049 8
+R 25 8
+R 57 8
+R 121 8
+R 17 8
+R 129 8
+R 65 8
+R 513 8
+R 33 8
+R 4097 8
+R 0 8
+R 1 2
+R 20225 8
+W 1 436
+W 16387 1
+W 16388 3
+W 16469 324
+W 20225 248
+R 20473 7
+R 20217 8
+R 2049 1
+R 2050 1
+R 2051 6
+R 513 1
+R 514 1
+R 515 1
+R 516 1
+R 517 1
+R 518 1
+R 519 1
+R 520 1
+R 4097 1
+R 4098 7
+R 0 8
+W 3 2
+R 20225 8
+R 20457 8
+R 1 8
+R 9 8
+R 20473 7
+R 20217 8
+R 20465 8
+R 2049 8
+R 25 8
+R 57 8
+R 121 8
+R 17 8
+R 129 8
+R 65 8
+R 513 8
+R 33 8
+R 4097 8
+R 0 8
diff --git a/contrib/visualize-alignment/guestfish-add-mount.qtr b/contrib/visualize-alignment/guestfish-add-mount.qtr
new file mode 100644 (file)
index 0000000..2c99524
--- /dev/null
@@ -0,0 +1,72 @@
+S 20480
+R 0 1
+R 0 8
+R 20352 8
+R 20464 8
+R 0 8
+R 8 8
+R 20472 8
+R 20216 8
+R 20416 8
+R 20224 8
+R 20080 8
+R 2048 8
+R 24 8
+R 56 8
+R 120 8
+R 16 8
+R 128 8
+R 64 8
+R 512 8
+R 32 8
+R 4096 8
+R 20288 8
+R 20400 8
+R 64 8
+R 72 8
+R 20416 1
+R 20160 8
+R 20408 8
+R 2112 8
+R 88 8
+R 120 8
+R 184 8
+R 80 8
+R 192 8
+R 128 8
+R 576 8
+R 96 8
+R 4160 8
+R 0 8
+R 0 32
+R 20288 1
+R 20400 1
+R 64 1
+R 72 1
+R 64 4
+R 64 4
+R 20288 8
+R 20400 8
+R 64 8
+R 72 8
+R 20416 1
+R 20160 8
+R 20408 8
+R 2112 8
+R 88 8
+R 120 8
+R 184 8
+R 80 8
+R 192 8
+R 128 8
+R 576 8
+R 96 8
+R 4160 8
+R 66 2
+R 68 2
+R 152 2
+W 66 2
+W 66 2
+W 66 2
+W 66 2
+W 66 2
diff --git a/contrib/visualize-alignment/guestfish-write-hello.qtr b/contrib/visualize-alignment/guestfish-write-hello.qtr
new file mode 100644 (file)
index 0000000..b561d6d
--- /dev/null
@@ -0,0 +1,81 @@
+S 20480
+R 0 1
+R 0 8
+R 20352 8
+R 20464 8
+R 0 8
+R 8 8
+R 20472 8
+R 20216 8
+R 20416 8
+R 20224 8
+R 20080 8
+R 2048 8
+R 24 8
+R 56 8
+R 120 8
+R 16 8
+R 128 8
+R 64 8
+R 512 8
+R 32 8
+R 4096 8
+R 20288 8
+R 20400 8
+R 64 8
+R 72 8
+R 20416 1
+R 20160 1
+R 20161 7
+R 20408 8
+R 2112 8
+R 88 8
+R 120 8
+R 184 8
+R 80 8
+R 192 8
+R 128 8
+R 576 8
+R 96 8
+R 4160 8
+R 0 8
+R 0 32
+R 20288 1
+R 20400 1
+R 64 1
+R 72 1
+R 64 4
+R 64 4
+R 20288 8
+R 20400 8
+R 64 8
+R 72 8
+R 20416 1
+R 20160 8
+R 20408 8
+R 2112 8
+R 88 8
+R 120 8
+R 184 8
+R 80 8
+R 192 8
+R 128 8
+R 576 8
+R 96 8
+R 4160 8
+R 66 2
+R 68 2
+R 152 2
+W 66 2
+R 470 2
+R 150 2
+R 154 2
+R 148 2
+W 66 4
+W 148 2
+W 470 2
+W 13378 2
+W 150 6
+W 66 2
+W 66 2
+W 66 2
diff --git a/contrib/visualize-alignment/qemu-0.13-trace-block-device-access.patch b/contrib/visualize-alignment/qemu-0.13-trace-block-device-access.patch
new file mode 100644 (file)
index 0000000..96904b3
--- /dev/null
@@ -0,0 +1,104 @@
+From e04ef476fd330485e5a88c7018d29c55cf411fe2 Mon Sep 17 00:00:00 2001
+From: Richard W.M. Jones <rjones@redhat.com>
+Date: Tue, 5 Oct 2010 09:54:10 +0100
+Subject: [PATCH] Trace reads and writes to qemu block devices.
+
+NB: This patch is not suitable for nor intended to go upstream in
+its current form.
+
+When qemu opens a block device, this patch creates a trace file
+in /tmp with a name related to the block device.  Reads and writes
+to the device cause lines to be written to the trace file:
+
+  S <total_sectors>
+  W <sector> <nb_sectors>
+  R <sector> <nb_sectors>
+
+'S' is the summary line, printed first which just tells you how
+many sectors are on the device.
+
+'W' and 'R' are writes and reads, for the range <sector> through
+to <sector> + <nb_sectors> - 1.
+---
+ block.c     |   29 +++++++++++++++++++++++++++++
+ block_int.h |    3 +++
+ 2 files changed, 32 insertions(+), 0 deletions(-)
+
+diff --git a/block.c b/block.c
+index ebbc376..26ead5b 100644
+--- a/block.c
++++ b/block.c
+@@ -474,6 +474,23 @@ static int bdrv_open_common(BlockDriverState *bs, const char *filename,
+         goto free_and_fail;
+     }
++    /* Open trace file in /tmp based on filename. XXX */
++    size_t len = strlen (filename);
++    char *trace_file = qemu_malloc (10 + len);
++    snprintf (trace_file, 10 + len, "/tmp/%s.qtr", filename);
++    size_t i;
++    for (i = 5; i < 5 + len; ++i) {
++        if (trace_file[i] == '/')
++            trace_file[i] = '_';
++    }
++    bs->trace_fp = fopen (trace_file, "w");
++    if (bs->trace_fp) {
++        setlinebuf (bs->trace_fp);
++        fprintf (bs->trace_fp, "S %" PRIi64 "\n", bs->total_sectors);
++    } else {
++        perror (trace_file);
++    }
++
+ #ifndef _WIN32
+     if (bs->is_temporary) {
+         unlink(filename);
+@@ -665,6 +682,10 @@ void bdrv_close(BlockDriverState *bs)
+             bdrv_close(bs->file);
+         }
++        if (bs->trace_fp)
++            fclose (bs->trace_fp);
++        bs->trace_fp = NULL;
++
+         /* call the change callback */
+         bs->media_changed = 1;
+         if (bs->change_cb)
+@@ -1995,6 +2016,10 @@ BlockDriverAIOCB *bdrv_aio_readv(BlockDriverState *bs, int64_t sector_num,
+       /* Update stats even though technically transfer has not happened. */
+       bs->rd_bytes += (unsigned) nb_sectors * BDRV_SECTOR_SIZE;
+       bs->rd_ops ++;
++
++        if (bs->trace_fp)
++            fprintf (bs->trace_fp,
++                     "R %" PRIi64 " %d\n", sector_num, nb_sectors);
+     }
+     return ret;
+@@ -2028,6 +2053,10 @@ BlockDriverAIOCB *bdrv_aio_writev(BlockDriverState *bs, int64_t sector_num,
+         if (bs->wr_highest_sector < sector_num + nb_sectors - 1) {
+             bs->wr_highest_sector = sector_num + nb_sectors - 1;
+         }
++
++        if (bs->trace_fp)
++            fprintf (bs->trace_fp,
++                     "W %" PRIi64 " %d\n", sector_num, nb_sectors);
+     }
+     return ret;
+diff --git a/block_int.h b/block_int.h
+index e8e7156..03e7c9b 100644
+--- a/block_int.h
++++ b/block_int.h
+@@ -178,6 +178,9 @@ struct BlockDriverState {
+     uint64_t wr_ops;
+     uint64_t wr_highest_sector;
++    /* Trace to file. */
++    FILE *trace_fp;
++
+     /* Whether the disk can expand beyond total_sectors */
+     int growable;
+-- 
+1.7.3.1
+
diff --git a/contrib/visualize-alignment/tracetops.ml b/contrib/visualize-alignment/tracetops.ml
new file mode 100755 (executable)
index 0000000..6600793
--- /dev/null
@@ -0,0 +1,354 @@
+#!/usr/bin/ocamlrun /usr/bin/ocaml
+
+#use "topfind";;
+#require "extlib";;
+
+(* Convert *.qtr (qemu block device trace) to Postscript.  By Richard
+ * W.M. Jones <rjones@redhat.com>.
+ * 
+ * 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.
+ *)
+
+open ExtList
+open Scanf
+open Printf
+
+type op = Read | Write
+
+(* If 'true' then print debug messages. *)
+let debug = false
+
+(* 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
+
+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"