Support for writing output in CSV format.
authorRichard W.M. Jones <rjones@redhat.com>
Thu, 17 Apr 2008 15:06:23 +0000 (16:06 +0100)
committerRichard W.M. Jones <rjones@redhat.com>
Thu, 17 Apr 2008 15:06:23 +0000 (16:06 +0100)
12 files changed:
MANIFEST
configure.ac
virt-df/.depend
virt-df/Makefile.in
virt-df/virt-df.1
virt-df/virt-df.pod
virt-df/virt-df.txt
virt-df/virt_df.ml
virt-df/virt_df.mli
virt-df/virt_df_csv.ml [new file with mode: 0644]
virt-df/virt_df_csv.mli [new file with mode: 0644]
virt-df/virt_df_main.ml

index 037a688..d7742b5 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -26,6 +26,8 @@ virt-df/virt-df.pod
 virt-df/virt-df.txt
 virt-df/virt_df.ml
 virt-df/virt_df.mli
+virt-df/virt_df_csv.ml
+virt-df/virt_df_csv.mli
 virt-df/virt_df_ext2.ml
 virt-df/virt_df_ext2.mli
 virt-df/virt_df_linux_swap.ml
index 8f655f2..f875ffa 100644 (file)
@@ -59,12 +59,15 @@ fi
 
 dnl Check for optional OCaml packages.
 AC_CHECK_OCAML_PKG(gettext)
+AC_CHECK_OCAML_PKG(csv)
 
 AC_SUBST(pkg_unix)
 AC_SUBST(pkg_extlib)
-AC_SUBST(pkg_gettext)
+AC_SUBST(pkg_libvirt)
 AC_SUBST(pkg_xml_light)
 AC_SUBST(pkg_bitmatch)
+AC_SUBST(pkg_gettext)
+AC_SUBST(pkg_csv)
 
 dnl Check for optional perldoc (for building manual pages).
 AC_CHECK_PROG(HAVE_PERLDOC,perldoc,perldoc)
index e7a7f26..ae927d2 100644 (file)
@@ -1,4 +1,6 @@
 virt_df_lvm2_parser.cmi: virt_df_lvm2_metadata.cmi 
+virt_df_csv.cmo: virt_df.cmi virt_df_csv.cmi 
+virt_df_csv.cmx: virt_df.cmx virt_df_csv.cmi 
 virt_df_ext2.cmo: virt_df_gettext.cmo virt_df.cmi \
     /usr/lib64/ocaml/bitmatch/bitmatch.cmi virt_df_ext2.cmi 
 virt_df_ext2.cmx: virt_df_gettext.cmx virt_df.cmx \
index 0cc51a0..0bc9139 100644 (file)
@@ -34,20 +34,33 @@ ifneq ($(pkg_gettext),no)
 OCAMLCPACKAGES  += -package gettext-stub
 endif
 
-OBJS           := \
-       virt_df_gettext.cmo \
-       virt_df.cmo \
-       virt_df_ext2.cmo \
-       virt_df_linux_swap.cmo \
-       virt_df_lvm2_metadata.cmo \
-       virt_df_lvm2_parser.cmo \
-       virt_df_lvm2_lexer.cmo \
-       virt_df_lvm2.cmo \
-       virt_df_mbr.cmo \
-       virt_df_main.cmo
+#----------------------------------------------------------------------
+# Build up the list of object files.
+
+# Library objects.
+OBJS           := virt_df_gettext.cmo virt_df.cmo
+
+# Plugin objects.
+OBJS           += virt_df_ext2.cmo \
+                  virt_df_linux_swap.cmo \
+                  virt_df_lvm2_metadata.cmo \
+                  virt_df_lvm2_parser.cmo \
+                  virt_df_lvm2_lexer.cmo \
+                  virt_df_lvm2.cmo \
+                  virt_df_mbr.cmo
+
+ifneq ($(pkg_csv),no)
+OCAMLCPACKAGES  += -package csv
+OBJS           += virt_df_csv.cmo
+endif
+
+# Main program.
+OBJS           += virt_df_main.cmo
 
 XOBJS          := $(OBJS:.cmo=.cmx)
 
+#----------------------------------------------------------------------
+
 SYNTAX         := -pp "camlp4o -I`ocamlc -where`/bitmatch pa_bitmatch.cmo"
 
 OCAMLCFLAGS    := -g -w s $(SYNTAX)
index 17f2e46..59dd6fc 100644 (file)
@@ -156,6 +156,12 @@ Show all domains.  The default is show only running (active) domains.
 .IX Item "-c uri, --connect uri"
 Connect to libvirt \s-1URI\s0.  The default is to connect to the default
 libvirt \s-1URI\s0, normally Xen.
+.IP "\fB\-\-csv\fR" 4
+.IX Item "--csv"
+Print the results in \s-1CSV\s0 format, suitable for importing into a
+spreadsheet or database.
+.Sp
+This option is only supported if virt-df was built with \s-1CSV\s0 support.
 .IP "\fB\-\-debug\fR" 4
 .IX Item "--debug"
 Emit debugging information on stderr.  Please supply this if you
index cb43588..50ed7fe 100644 (file)
@@ -32,7 +32,14 @@ Show all domains.  The default is show only running (active) domains.
 Connect to libvirt URI.  The default is to connect to the default
 libvirt URI, normally Xen.
 
-=item  B<--debug>
+=item B<--csv>
+
+Print the results in CSV format, suitable for importing into a
+spreadsheet or database.
+
+This option is only supported if virt-df was built with CSV support.
+
+=item B<--debug>
 
 Emit debugging information on stderr.  Please supply this if you
 report a bug.
index aa02a8f..9ee6243 100644 (file)
@@ -23,6 +23,12 @@ OPTIONS
         Connect to libvirt URI. The default is to connect to the default
         libvirt URI, normally Xen.
 
+    --csv
+        Print the results in CSV format, suitable for importing into a
+        spreadsheet or database.
+
+        This option is only supported if virt-df was built with CSV support.
+
     --debug
         Emit debugging information on stderr. Please supply this if you
         report a bug.
index c02c8e3..2310b1c 100644 (file)
@@ -33,12 +33,17 @@ let ( -^ ) = Int64.sub
 let ( *^ ) = Int64.mul
 let ( /^ ) = Int64.div
 
+(* Command line arguments. *)
 let debug = ref false
 let uri = ref None
 let inodes = ref false
 let human = ref false
 let all = ref false
 let test_files = ref []
+let csv_mode = ref false
+
+(* Support for CSV (overridden by virt_df_csv.ml, if present). *)
+let csv_write = ref None
 
 class virtual device =
 object (self)
index f35e0db..2815d9b 100644 (file)
@@ -37,8 +37,14 @@ val inodes : bool ref                        (** Display inodes. *)
 val human : bool ref                   (** Display human-readable. *)
 val all : bool ref                     (** Show all or just active domains. *)
 val test_files : string list ref       (** In test mode (-t) list of files. *)
+val csv_mode : bool ref                        (** CSV mode. *)
 (** State of command line arguments. *)
 
+val csv_write : (out_channel -> string list -> unit) option ref
+(** If virt_df_csv.ml is compiled in then this hook is overridden with
+    a function to write a single line to a CSV file.
+*)
+
 (**
    {2 Domain/device model}
 
diff --git a/virt-df/virt_df_csv.ml b/virt-df/virt_df_csv.ml
new file mode 100644 (file)
index 0000000..b2bf9f6
--- /dev/null
@@ -0,0 +1,32 @@
+(* 'df' command for virtual domains.
+
+   (C) Copyright 2007 Richard W.M. Jones, Red Hat Inc.
+   http://libvirt.org/
+
+   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.
+
+   Support for writing CSV files.  This is only compiled in if
+   we detect the ocaml-csv module is present at compile time.
+*)
+
+open Printf
+
+open Virt_df
+
+csv_write := Some (
+  fun chan row ->
+    Csv.save_out chan [row];
+    flush chan
+)
diff --git a/virt-df/virt_df_csv.mli b/virt-df/virt_df_csv.mli
new file mode 100644 (file)
index 0000000..59caece
--- /dev/null
@@ -0,0 +1,23 @@
+(* 'df' command for virtual domains.
+
+   (C) Copyright 2007 Richard W.M. Jones, Red Hat Inc.
+   http://libvirt.org/
+
+   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.
+*)
+
+(* This file is empty to stop this plug-in from exporting any
+   symbols to other modules by accident.
+*)
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)