Support for writing output in CSV format.
[virt-df.git] / virt-df / virt_df_main.ml
index 1895aee..2a97d5e 100644 (file)
@@ -19,7 +19,6 @@
 
 open Printf
 open ExtList
-open Unix
 
 module C = Libvirt.Connect
 module D = Libvirt.Domain
@@ -41,9 +40,7 @@ let () =
     exit 0
   in
 
-  let test_mode filename =
-    test_files := filename :: !test_files
-  in
+  let test_mode filename = test_files := filename :: !test_files in
 
   let argspec = Arg.align [
     "-a", Arg.Set all,
@@ -54,6 +51,8 @@ let () =
       "uri " ^ s_ "Connect to URI (default: Xen)";
     "--connect", Arg.String set_uri,
       "uri " ^ s_ "Connect to URI (default: Xen)";
+    "--csv", Arg.Set csv_mode,
+      " " ^ s_ "Write results in CSV format";
     "--debug", Arg.Set debug,
       " " ^ s_ "Debug mode (default: false)";
     "-h", Arg.Set human,
@@ -81,6 +80,19 @@ OPTIONS" in
 
   Arg.parse argspec anon_fun usage_msg;
 
+  (* Set up CSV support. *)
+  let csv_write =
+    if not !csv_mode then
+      fun _ -> assert false (* Should never happen. *)
+    else
+      match !csv_write with
+      | None ->
+         prerr_endline (s_ "CSV is not supported in this build of virt-df");
+         exit 1
+      | Some csv_write ->
+         csv_write stdout
+  in
+
   let doms : domain list =
     if !test_files = [] then (
       let xmls =
@@ -92,7 +104,7 @@ OPTIONS" in
            Libvirt.Virterror err ->
              prerr_endline (Libvirt.Virterror.to_string err);
              (* If non-root and no explicit connection URI, print a warning. *)
-             if geteuid () <> 0 && name = None then (
+             if Unix.geteuid () <> 0 && name = None then (
                print_endline (s_ "NB: If you want to monitor a local Xen hypervisor, you usually need to be root");
              );
              exit 1 in
@@ -215,8 +227,9 @@ OPTIONS" in
                            d_dev = dev; d_content = `Unknown
                          }
                        with
-                         Unix_error (err, func, param) ->
-                           eprintf "%s:%s: %s" func param (error_message err);
+                         Unix.Unix_error (err, func, param) ->
+                           eprintf "%s:%s: %s" func param
+                             (Unix.error_message err);
                            None
                       )
                   | _ -> None (* ignore anything else *)
@@ -381,18 +394,21 @@ OPTIONS" in
       { dom with dom_lv_filesystems = filesystems }
   ) doms in
 
-  (* Now print the results.
-   *
-   * Print the title.
-   *)
+  (*----------------------------------------------------------------------*)
+  (* Now print the results. *)
+
+  (* Print the title. *)
   let () =
     let total, used, avail =
       match !inodes, !human with
       | false, false -> s_ "1K-blocks", s_ "Used", s_ "Available"
       | false, true -> s_ "Size", s_ "Used", s_ "Available"
       | true, _ -> s_ "Inodes", s_ "IUse", s_ "IFree" in
-    printf "%-32s %10s %10s %10s %s\n%!"
-      (s_ "Filesystem") total used avail (s_ "Type") in
+    if not !csv_mode then
+      printf "%-32s %10s %10s %10s %s\n%!"
+       (s_ "Filesystem") total used avail (s_ "Type")
+    else
+      csv_write [ "Filesystem"; total; used; avail; "Type" ] in
 
   let printable_size bytes =
     if bytes < 1024L *^ 1024L then
@@ -429,24 +445,27 @@ OPTIONS" in
     ) doms
   in
 
+  (* Printable name is like "domain:hda" or "domain:hda1". *)
+  let printable_name dom ?disk ?partno dev =
+    let dom_name = dom.dom_name in
+    (* Get the disk name (eg. "hda") from the domain XML, if
+     * we have it, otherwise use the device name (eg. for LVM).
+     *)
+    let disk_name =
+      match disk with
+      | None -> dev#name
+      | Some disk -> disk.d_target
+    in
+    match partno with
+    | None ->
+       dom_name ^ ":" ^ disk_name
+    | Some partno ->
+       dom_name ^ ":" ^ disk_name ^ string_of_int partno
+  in
+
   (* Print stats for each recognized filesystem. *)
   let print_stats dom ?disk ?partno dev fs =
-    (* Printable name is like "domain:hda" or "domain:hda1". *)
-    let name =
-      let dom_name = dom.dom_name in
-      (* Get the disk name (eg. "hda") from the domain XML, if
-       * we have it, otherwise use the device name (eg. for LVM).
-       *)
-      let disk_name =
-       match disk with
-       | None -> dev#name
-       | Some disk -> disk.d_target
-      in
-      match partno with
-      | None ->
-         dom_name ^ ":" ^ disk_name
-      | Some partno ->
-         dom_name ^ ":" ^ disk_name ^ string_of_int partno in
+    let name = printable_name dom ?disk ?partno dev in
     printf "%-32s " name;
 
     if fs.fs_is_swap then (
@@ -485,4 +504,40 @@ OPTIONS" in
       )
     )
   in
-  iter_over_filesystems doms print_stats
+
+  (* Alternate version of print_stats which writes to a CSV file.
+   * We ignore the human-readable option because we assume that
+   * the data will be post-processed by something.
+   *)
+  let print_stats_csv dom ?disk ?partno dev fs =
+    let name = printable_name dom ?disk ?partno dev in
+
+    let row =
+      if fs.fs_is_swap then
+       (* Swap partition. *)
+       [ Int64.to_string (fs.fs_block_size *^ fs.fs_blocks_total /^ 1024L);
+         ""; "" ]
+      else (
+       (* Ordinary filesystem. *)
+       if not !inodes then (           (* Block display. *)
+         (* 'df' doesn't count the restricted blocks. *)
+         let blocks_total = fs.fs_blocks_total -^ fs.fs_blocks_reserved in
+         let blocks_avail = fs.fs_blocks_avail -^ fs.fs_blocks_reserved in
+         let blocks_avail = if blocks_avail < 0L then 0L else blocks_avail in
+
+         [ Int64.to_string (blocks_total *^ fs.fs_block_size /^ 1024L);
+           Int64.to_string (fs.fs_blocks_used *^ fs.fs_block_size /^ 1024L);
+           Int64.to_string (blocks_avail *^ fs.fs_block_size /^ 1024L) ]
+       ) else (                        (* Inodes display. *)
+         [ Int64.to_string fs.fs_inodes_total;
+           Int64.to_string fs.fs_inodes_used;
+           Int64.to_string fs.fs_inodes_avail ]
+       )
+      ) in
+
+    let row = name :: row @ [fs.fs_name] in
+    csv_write row
+  in
+
+  iter_over_filesystems doms
+    (if not !csv_mode then print_stats else print_stats_csv)