From fc96d842a5276a27f528fbfb382b8a4afaeb2819 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Thu, 1 Jan 1970 00:00:00 +0000 Subject: [PATCH] Support for writing output in CSV format. --- MANIFEST | 2 + configure.ac | 5 ++- virt-df/.depend | 2 + virt-df/Makefile.in | 35 ++++++++++----- virt-df/virt-df.1 | 6 +++ virt-df/virt-df.pod | 9 +++- virt-df/virt-df.txt | 6 +++ virt-df/virt_df.ml | 5 +++ virt-df/virt_df.mli | 6 +++ virt-df/virt_df_csv.ml | 32 ++++++++++++++ virt-df/virt_df_csv.mli | 23 ++++++++++ virt-df/virt_df_main.ml | 115 +++++++++++++++++++++++++++++++++++------------- 12 files changed, 203 insertions(+), 43 deletions(-) create mode 100644 virt-df/virt_df_csv.ml create mode 100644 virt-df/virt_df_csv.mli diff --git a/MANIFEST b/MANIFEST index 037a688..d7742b5 100644 --- 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 diff --git a/configure.ac b/configure.ac index 8f655f2..f875ffa 100644 --- a/configure.ac +++ b/configure.ac @@ -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) diff --git a/virt-df/.depend b/virt-df/.depend index e7a7f26..ae927d2 100644 --- a/virt-df/.depend +++ b/virt-df/.depend @@ -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 \ diff --git a/virt-df/Makefile.in b/virt-df/Makefile.in index 0cc51a0..0bc9139 100644 --- a/virt-df/Makefile.in +++ b/virt-df/Makefile.in @@ -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) diff --git a/virt-df/virt-df.1 b/virt-df/virt-df.1 index 17f2e46..59dd6fc 100644 --- a/virt-df/virt-df.1 +++ b/virt-df/virt-df.1 @@ -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 diff --git a/virt-df/virt-df.pod b/virt-df/virt-df.pod index cb43588..50ed7fe 100644 --- a/virt-df/virt-df.pod +++ b/virt-df/virt-df.pod @@ -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. diff --git a/virt-df/virt-df.txt b/virt-df/virt-df.txt index aa02a8f..9ee6243 100644 --- a/virt-df/virt-df.txt +++ b/virt-df/virt-df.txt @@ -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. diff --git a/virt-df/virt_df.ml b/virt-df/virt_df.ml index c02c8e3..2310b1c 100644 --- a/virt-df/virt_df.ml +++ b/virt-df/virt_df.ml @@ -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) diff --git a/virt-df/virt_df.mli b/virt-df/virt_df.mli index f35e0db..2815d9b 100644 --- a/virt-df/virt_df.mli +++ b/virt-df/virt_df.mli @@ -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 index 0000000..b2bf9f6 --- /dev/null +++ b/virt-df/virt_df_csv.ml @@ -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 index 0000000..59caece --- /dev/null +++ b/virt-df/virt_df_csv.mli @@ -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. +*) diff --git a/virt-df/virt_df_main.ml b/virt-df/virt_df_main.ml index 1895aee..2a97d5e 100644 --- a/virt-df/virt_df_main.ml +++ b/virt-df/virt_df_main.ml @@ -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) -- 1.8.3.1