You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3275 lines
113 KiB
3275 lines
113 KiB
2 years ago
|
From 127dfdfc52926f8a337fcc50eddb51cf4f64371f Mon Sep 17 00:00:00 2001
|
||
|
From: "Richard W.M. Jones" <rjones@redhat.com>
|
||
|
Date: Mon, 27 Mar 2017 15:29:23 +0100
|
||
|
Subject: [PATCH 14/23] Split up huge Top module into smaller modules.
|
||
|
|
||
|
This change is hopefully pure refactoring, splitting up the very large
|
||
|
and highly interlinked module into more manageable modules with
|
||
|
well-defined (or at least *better*-defined) interfaces between them.
|
||
|
---
|
||
|
MANIFEST | 12 +
|
||
|
po/POTFILES | 6 +
|
||
|
src/.depend | 36 +-
|
||
|
src/Makefile.in | 6 +
|
||
|
src/README | 38 +-
|
||
|
src/collect.ml | 455 ++++++++++++++++
|
||
|
src/collect.mli | 86 ++++
|
||
|
src/csv_output.ml | 118 +++++
|
||
|
src/csv_output.mli | 27 +
|
||
|
src/opt_csv.ml | 2 +-
|
||
|
src/opt_xml.ml | 2 +-
|
||
|
src/redraw.ml | 506 ++++++++++++++++++
|
||
|
src/redraw.mli | 20 +
|
||
|
src/screen.ml | 52 ++
|
||
|
src/screen.mli | 41 ++
|
||
|
src/stream_output.ml | 84 +++
|
||
|
src/stream_output.mli | 22 +
|
||
|
src/top.ml | 1139 +----------------------------------------
|
||
|
src/top.mli | 20 +-
|
||
|
src/types.ml | 147 ++++++
|
||
|
src/types.mli | 49 ++
|
||
|
src/utils.ml | 65 ---
|
||
|
src/utils.mli | 9 -
|
||
|
23 files changed, 1719 insertions(+), 1223 deletions(-)
|
||
|
create mode 100644 src/collect.ml
|
||
|
create mode 100644 src/collect.mli
|
||
|
create mode 100644 src/csv_output.ml
|
||
|
create mode 100644 src/csv_output.mli
|
||
|
create mode 100644 src/redraw.ml
|
||
|
create mode 100644 src/redraw.mli
|
||
|
create mode 100644 src/screen.ml
|
||
|
create mode 100644 src/screen.mli
|
||
|
create mode 100644 src/stream_output.ml
|
||
|
create mode 100644 src/stream_output.mli
|
||
|
create mode 100644 src/types.ml
|
||
|
create mode 100644 src/types.mli
|
||
|
|
||
|
diff --git a/MANIFEST b/MANIFEST
|
||
|
index 26e87b2..4e4014b 100644
|
||
|
--- a/MANIFEST
|
||
|
+++ b/MANIFEST
|
||
|
@@ -54,12 +54,24 @@ TODO
|
||
|
src/.depend
|
||
|
src/Makefile.in
|
||
|
src/README
|
||
|
+src/collect.ml
|
||
|
+src/collect.mli
|
||
|
+src/csv_output.ml
|
||
|
+src/csv_output.mli
|
||
|
src/main.ml
|
||
|
src/opt_calendar.ml
|
||
|
src/opt_csv.ml
|
||
|
src/opt_xml.ml
|
||
|
+src/redraw.ml
|
||
|
+src/redraw.mli
|
||
|
+src/screen.ml
|
||
|
+src/screen.mli
|
||
|
+src/stream_output.ml
|
||
|
+src/stream_output.mli
|
||
|
src/top.ml
|
||
|
src/top.mli
|
||
|
+src/types.ml
|
||
|
+src/types.mli
|
||
|
src/utils.ml
|
||
|
src/utils.mli
|
||
|
src/version.ml.in
|
||
|
diff --git a/po/POTFILES b/po/POTFILES
|
||
|
index b826a2a..6150703 100644
|
||
|
--- a/po/POTFILES
|
||
|
+++ b/po/POTFILES
|
||
|
@@ -1,8 +1,14 @@
|
||
|
+../src/collect.ml
|
||
|
+../src/csv_output.ml
|
||
|
../src/main.ml
|
||
|
../src/opt_calendar.ml
|
||
|
../src/opt_csv.ml
|
||
|
../src/opt_gettext.ml
|
||
|
../src/opt_xml.ml
|
||
|
+../src/redraw.ml
|
||
|
+../src/screen.ml
|
||
|
+../src/stream.ml
|
||
|
../src/top.ml
|
||
|
+../src/types.ml
|
||
|
../src/utils.ml
|
||
|
../src/version.ml
|
||
|
diff --git a/src/.depend b/src/.depend
|
||
|
index f487c18..1075f36 100644
|
||
|
--- a/src/.depend
|
||
|
+++ b/src/.depend
|
||
|
@@ -1,18 +1,36 @@
|
||
|
+collect.cmi: types.cmi
|
||
|
+collect.cmo: utils.cmi types.cmi collect.cmi
|
||
|
+collect.cmx: utils.cmx types.cmx collect.cmi
|
||
|
+csv_output.cmi: types.cmi collect.cmi
|
||
|
+csv_output.cmo: collect.cmi csv_output.cmi
|
||
|
+csv_output.cmx: collect.cmx csv_output.cmi
|
||
|
main.cmo: top.cmi opt_gettext.cmo
|
||
|
main.cmx: top.cmx opt_gettext.cmx
|
||
|
opt_calendar.cmo: top.cmi opt_gettext.cmo
|
||
|
opt_calendar.cmx: top.cmx opt_gettext.cmx
|
||
|
-opt_csv.cmo: top.cmi opt_gettext.cmo
|
||
|
-opt_csv.cmx: top.cmx opt_gettext.cmx
|
||
|
+opt_csv.cmo: top.cmi opt_gettext.cmo csv_output.cmi
|
||
|
+opt_csv.cmx: top.cmx opt_gettext.cmx csv_output.cmx
|
||
|
opt_gettext.cmo:
|
||
|
opt_gettext.cmx:
|
||
|
-opt_xml.cmo: top.cmi opt_gettext.cmo
|
||
|
-opt_xml.cmx: top.cmx opt_gettext.cmx
|
||
|
-top.cmi:
|
||
|
-top.cmo: version.cmo utils.cmi opt_gettext.cmo top.cmi
|
||
|
-top.cmx: version.cmx utils.cmx opt_gettext.cmx top.cmi
|
||
|
+opt_xml.cmo: opt_gettext.cmo collect.cmi
|
||
|
+opt_xml.cmx: opt_gettext.cmx collect.cmx
|
||
|
+redraw.cmi: types.cmi collect.cmi
|
||
|
+redraw.cmo: utils.cmi types.cmi screen.cmi opt_gettext.cmo collect.cmi redraw.cmi
|
||
|
+redraw.cmx: utils.cmx types.cmx screen.cmx opt_gettext.cmx collect.cmx redraw.cmi
|
||
|
+screen.cmi:
|
||
|
+screen.cmo: screen.cmi
|
||
|
+screen.cmx: screen.cmi
|
||
|
+stream_output.cmi: types.cmi collect.cmi
|
||
|
+stream_output.cmo: utils.cmi screen.cmi collect.cmi stream_output.cmi
|
||
|
+stream_output.cmx: utils.cmx screen.cmx collect.cmx stream_output.cmi
|
||
|
+top.cmi: types.cmi
|
||
|
+top.cmo: version.cmo utils.cmi types.cmi stream_output.cmi screen.cmi redraw.cmi opt_gettext.cmo csv_output.cmi collect.cmi top.cmi
|
||
|
+top.cmx: version.cmx utils.cmx types.cmx stream_output.cmx screen.cmx redraw.cmx opt_gettext.cmx csv_output.cmx collect.cmx top.cmi
|
||
|
+types.cmi:
|
||
|
+types.cmo: utils.cmi opt_gettext.cmo types.cmi
|
||
|
+types.cmx: utils.cmx opt_gettext.cmx types.cmi
|
||
|
utils.cmi:
|
||
|
-utils.cmo: opt_gettext.cmo utils.cmi
|
||
|
-utils.cmx: opt_gettext.cmx utils.cmi
|
||
|
+utils.cmo: utils.cmi
|
||
|
+utils.cmx: utils.cmi
|
||
|
version.cmo:
|
||
|
version.cmx:
|
||
|
diff --git a/src/Makefile.in b/src/Makefile.in
|
||
|
index ae896cb..64f431e 100644
|
||
|
--- a/src/Makefile.in
|
||
|
+++ b/src/Makefile.in
|
||
|
@@ -42,6 +42,12 @@ OBJS := \
|
||
|
version.cmo \
|
||
|
opt_gettext.cmo \
|
||
|
utils.cmo \
|
||
|
+ types.cmo \
|
||
|
+ collect.cmo \
|
||
|
+ screen.cmo \
|
||
|
+ redraw.cmo \
|
||
|
+ csv_output.cmo \
|
||
|
+ stream_output.cmo \
|
||
|
top.cmo
|
||
|
ifneq ($(OCAML_PKG_xml_light),no)
|
||
|
OBJS += opt_xml.cmo
|
||
|
diff --git a/src/README b/src/README
|
||
|
index 8aa2348..1fd4be3 100644
|
||
|
--- a/src/README
|
||
|
+++ b/src/README
|
||
|
@@ -5,19 +5,37 @@ The code is structured into these files:
|
||
|
String functions and other small utility functions. This is
|
||
|
included directly into virt_top.ml.
|
||
|
|
||
|
+ types.mli, types.ml
|
||
|
+
|
||
|
+ Various internally used types and functions operating on those
|
||
|
+ types.
|
||
|
+
|
||
|
+ collect.mli, collect.ml
|
||
|
+
|
||
|
+ Stats information is collected in these functions.
|
||
|
+
|
||
|
+ screen.mli, screen.ml
|
||
|
+
|
||
|
+ Various useful functions for drawing to the curses screen.
|
||
|
+
|
||
|
+ redraw.mli, redraw.ml
|
||
|
+
|
||
|
+ Redraw the main display.
|
||
|
+
|
||
|
+ csv_output.mli, csv_output.ml
|
||
|
+
|
||
|
+ Functions which implement --csv mode.
|
||
|
+
|
||
|
+ stream_output.mli, stream_output.ml
|
||
|
+
|
||
|
+ Functions which implement --stream mode.
|
||
|
+
|
||
|
top.mli, top.ml
|
||
|
|
||
|
This is the virt-top program.
|
||
|
|
||
|
- The two interesting functions are called 'collect' and 'redraw'.
|
||
|
-
|
||
|
- 'collect' collects all the information about domains, etc.
|
||
|
-
|
||
|
- 'redraw' updates the display on each frame.
|
||
|
-
|
||
|
- Another interesting function is 'start_up' which handles all
|
||
|
- start-up stuff, eg. command line arguments, connecting to the
|
||
|
- hypervisor, enabling curses.
|
||
|
+ 'start_up' handles all start-up stuff, eg. command line arguments,
|
||
|
+ connecting to the hypervisor, enabling curses.
|
||
|
|
||
|
The function 'main_loop' runs the main loop and has sub-functions
|
||
|
to deal with keypresses, help screens and so on.
|
||
|
@@ -38,7 +56,7 @@ The code is structured into these files:
|
||
|
opt_csv.ml
|
||
|
|
||
|
Any code which needs the optional ocaml-csv library goes
|
||
|
- in here. This implements the --csv command line option.
|
||
|
+ in here.
|
||
|
|
||
|
opt_calendar.ml
|
||
|
|
||
|
diff --git a/src/collect.ml b/src/collect.ml
|
||
|
new file mode 100644
|
||
|
index 0000000..f856067
|
||
|
--- /dev/null
|
||
|
+++ b/src/collect.ml
|
||
|
@@ -0,0 +1,455 @@
|
||
|
+(* 'top'-like tool for libvirt domains.
|
||
|
+ (C) Copyright 2007-2017 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.
|
||
|
+*)
|
||
|
+
|
||
|
+module C = Libvirt.Connect
|
||
|
+module D = Libvirt.Domain
|
||
|
+
|
||
|
+open Printf
|
||
|
+open ExtList
|
||
|
+
|
||
|
+open Utils
|
||
|
+open Types
|
||
|
+
|
||
|
+(* Hook for XML support (see [opt_xml.ml]). *)
|
||
|
+let parse_device_xml : (int -> [>`R] D.t -> string list * string list) ref =
|
||
|
+ ref (
|
||
|
+ fun _ _ -> [], []
|
||
|
+ )
|
||
|
+
|
||
|
+(* Intermediate "domain + stats" structure that we use to collect
|
||
|
+ * everything we know about a domain within the collect function.
|
||
|
+ *)
|
||
|
+type rd_domain = Inactive | Active of rd_active
|
||
|
+and rd_active = {
|
||
|
+ rd_domid : int; (* Domain ID. *)
|
||
|
+ rd_dom : [`R] D.t; (* Domain object. *)
|
||
|
+ rd_info : D.info; (* Domain CPU info now. *)
|
||
|
+ rd_block_stats : (string * D.block_stats) list;
|
||
|
+ (* Domain block stats now. *)
|
||
|
+ rd_interface_stats : (string * D.interface_stats) list;
|
||
|
+ (* Domain net stats now. *)
|
||
|
+ rd_prev_info : D.info option; (* Domain CPU info previously. *)
|
||
|
+ rd_prev_block_stats : (string * D.block_stats) list;
|
||
|
+ (* Domain block stats prev. *)
|
||
|
+ rd_prev_interface_stats : (string * D.interface_stats) list;
|
||
|
+ (* Domain interface stats prev. *)
|
||
|
+ (* The following are since the last slice, or 0 if cannot be calculated: *)
|
||
|
+ rd_cpu_time : float; (* CPU time used in nanoseconds. *)
|
||
|
+ rd_percent_cpu : float; (* CPU time as percent of total. *)
|
||
|
+ rd_mem_bytes : int64; (* Memory usage in bytes *)
|
||
|
+ rd_mem_percent: int64; (* Memory usage as percent of total *)
|
||
|
+ (* The following are since the last slice, or None if cannot be calc'd: *)
|
||
|
+ rd_block_rd_reqs : int64 option; (* Number of block device read rqs. *)
|
||
|
+ rd_block_wr_reqs : int64 option; (* Number of block device write rqs. *)
|
||
|
+ rd_block_rd_bytes : int64 option; (* Number of bytes block device read *)
|
||
|
+ rd_block_wr_bytes : int64 option; (* Number of bytes block device write *)
|
||
|
+ (* _info fields includes the number considering --block_in_bytes option *)
|
||
|
+ rd_block_rd_info : int64 option; (* Block device read info for user *)
|
||
|
+ rd_block_wr_info : int64 option; (* Block device read info for user *)
|
||
|
+
|
||
|
+ rd_net_rx_bytes : int64 option; (* Number of bytes received. *)
|
||
|
+ rd_net_tx_bytes : int64 option; (* Number of bytes transmitted. *)
|
||
|
+}
|
||
|
+
|
||
|
+type stats = {
|
||
|
+ rd_doms : (string * rd_domain) list; (* List of domains. *)
|
||
|
+ rd_time : float;
|
||
|
+ rd_printable_time : string;
|
||
|
+ rd_nr_pcpus : int;
|
||
|
+ rd_total_cpu : float;
|
||
|
+ rd_total_cpu_per_pcpu : float;
|
||
|
+ rd_totals : (int * int * int * int * int * int * int * int * int * float *
|
||
|
+ int64 * int64);
|
||
|
+}
|
||
|
+
|
||
|
+type pcpu_stats = {
|
||
|
+ rd_pcpu_doms : (int * string * int *
|
||
|
+ Libvirt.Domain.vcpu_info array * int64 array array *
|
||
|
+ int64 array array * string * int) list;
|
||
|
+ rd_pcpu_pcpus : int64 array array array;
|
||
|
+ rd_pcpu_pcpus_cpu_time : float array
|
||
|
+}
|
||
|
+
|
||
|
+(* We cache the list of block devices and interfaces for each domain
|
||
|
+ * here, so we don't need to reparse the XML each time.
|
||
|
+ *)
|
||
|
+let devices = Hashtbl.create 13
|
||
|
+
|
||
|
+(* Function to get the list of block devices, network interfaces for
|
||
|
+ * a particular domain. Get it from the devices cache, and if not
|
||
|
+ * there then parse the domain XML.
|
||
|
+ *)
|
||
|
+let get_devices id dom =
|
||
|
+ try Hashtbl.find devices id
|
||
|
+ with Not_found ->
|
||
|
+ let blkdevs, netifs = (!parse_device_xml) id dom in
|
||
|
+ Hashtbl.replace devices id (blkdevs, netifs);
|
||
|
+ blkdevs, netifs
|
||
|
+
|
||
|
+(* We save the state of domains across redraws here, which allows us
|
||
|
+ * to deduce %CPU usage from the running total.
|
||
|
+ *)
|
||
|
+let last_info = Hashtbl.create 13
|
||
|
+let last_time = ref (Unix.gettimeofday ())
|
||
|
+
|
||
|
+(* Save pcpu_usages structures across redraws too (only for pCPU display). *)
|
||
|
+let last_pcpu_usages = Hashtbl.create 13
|
||
|
+
|
||
|
+let clear_pcpu_display_data () =
|
||
|
+ Hashtbl.clear last_pcpu_usages
|
||
|
+
|
||
|
+let collect (conn, _, _, _, _, node_info, _, _) block_in_bytes =
|
||
|
+ (* Number of physical CPUs (some may be disabled). *)
|
||
|
+ let nr_pcpus = C.maxcpus_of_node_info node_info in
|
||
|
+
|
||
|
+ (* Get the current time. *)
|
||
|
+ let time = Unix.gettimeofday () in
|
||
|
+ let tm = Unix.localtime time in
|
||
|
+ let printable_time =
|
||
|
+ sprintf "%02d:%02d:%02d" tm.Unix.tm_hour tm.Unix.tm_min tm.Unix.tm_sec in
|
||
|
+
|
||
|
+ (* What's the total CPU time elapsed since we were last called? (ns) *)
|
||
|
+ let total_cpu_per_pcpu = 1_000_000_000. *. (time -. !last_time) in
|
||
|
+ (* Avoid division by zero. *)
|
||
|
+ let total_cpu_per_pcpu =
|
||
|
+ if total_cpu_per_pcpu <= 0. then 1. else total_cpu_per_pcpu in
|
||
|
+ let total_cpu = float node_info.C.cpus *. total_cpu_per_pcpu in
|
||
|
+
|
||
|
+ (* Get the domains. Match up with their last_info (if any). *)
|
||
|
+ let doms =
|
||
|
+ (* Active domains. *)
|
||
|
+ let n = C.num_of_domains conn in
|
||
|
+ let ids =
|
||
|
+ if n > 0 then Array.to_list (C.list_domains conn n)
|
||
|
+ else [] in
|
||
|
+ let doms =
|
||
|
+ List.filter_map (
|
||
|
+ fun id ->
|
||
|
+ try
|
||
|
+ let dom = D.lookup_by_id conn id in
|
||
|
+ let name = D.get_name dom in
|
||
|
+ let blkdevs, netifs = get_devices id dom in
|
||
|
+
|
||
|
+ (* Get current CPU, block and network stats. *)
|
||
|
+ let info = D.get_info dom in
|
||
|
+ let block_stats =
|
||
|
+ try List.map (fun dev -> dev, D.block_stats dom dev) blkdevs
|
||
|
+ with
|
||
|
+ | Libvirt.Not_supported "virDomainBlockStats"
|
||
|
+ | Libvirt.Virterror _ -> [] in
|
||
|
+ let interface_stats =
|
||
|
+ try List.map (fun dev -> dev, D.interface_stats dom dev) netifs
|
||
|
+ with
|
||
|
+ | Libvirt.Not_supported "virDomainInterfaceStats"
|
||
|
+ | Libvirt.Virterror _ -> [] in
|
||
|
+
|
||
|
+ let prev_info, prev_block_stats, prev_interface_stats =
|
||
|
+ try
|
||
|
+ let prev_info, prev_block_stats, prev_interface_stats =
|
||
|
+ Hashtbl.find last_info id in
|
||
|
+ Some prev_info, prev_block_stats, prev_interface_stats
|
||
|
+ with Not_found -> None, [], [] in
|
||
|
+
|
||
|
+ Some (name,
|
||
|
+ Active {
|
||
|
+ rd_domid = id; rd_dom = dom; rd_info = info;
|
||
|
+ rd_block_stats = block_stats;
|
||
|
+ rd_interface_stats = interface_stats;
|
||
|
+ rd_prev_info = prev_info;
|
||
|
+ rd_prev_block_stats = prev_block_stats;
|
||
|
+ rd_prev_interface_stats = prev_interface_stats;
|
||
|
+ rd_cpu_time = 0.; rd_percent_cpu = 0.;
|
||
|
+ rd_mem_bytes = 0L; rd_mem_percent = 0L;
|
||
|
+ rd_block_rd_reqs = None; rd_block_wr_reqs = None;
|
||
|
+ rd_block_rd_bytes = None; rd_block_wr_bytes = None;
|
||
|
+ rd_block_rd_info = None; rd_block_wr_info = None;
|
||
|
+ rd_net_rx_bytes = None; rd_net_tx_bytes = None;
|
||
|
+ })
|
||
|
+ with
|
||
|
+ Libvirt.Virterror _ -> None (* ignore transient error *)
|
||
|
+ ) ids in
|
||
|
+
|
||
|
+ (* Inactive domains. *)
|
||
|
+ let doms_inactive =
|
||
|
+ try
|
||
|
+ let n = C.num_of_defined_domains conn in
|
||
|
+ let names =
|
||
|
+ if n > 0 then Array.to_list (C.list_defined_domains conn n)
|
||
|
+ else [] in
|
||
|
+ List.map (fun name -> name, Inactive) names
|
||
|
+ with
|
||
|
+ (* Ignore transient errors, in particular errors from
|
||
|
+ * num_of_defined_domains if it cannot contact xend.
|
||
|
+ *)
|
||
|
+ | Libvirt.Virterror _ -> [] in
|
||
|
+
|
||
|
+ doms @ doms_inactive in
|
||
|
+
|
||
|
+ (* Calculate the CPU time (ns) and %CPU used by each domain. *)
|
||
|
+ let doms =
|
||
|
+ List.map (
|
||
|
+ function
|
||
|
+ (* We have previous CPU info from which to calculate it? *)
|
||
|
+ | name, Active ({ rd_prev_info = Some prev_info } as rd) ->
|
||
|
+ let cpu_time =
|
||
|
+ Int64.to_float (rd.rd_info.D.cpu_time -^ prev_info.D.cpu_time) in
|
||
|
+ let percent_cpu = 100. *. cpu_time /. total_cpu in
|
||
|
+ let mem_usage = rd.rd_info.D.memory in
|
||
|
+ let mem_percent =
|
||
|
+ 100L *^ rd.rd_info.D.memory /^ node_info.C.memory in
|
||
|
+ let rd = { rd with
|
||
|
+ rd_cpu_time = cpu_time;
|
||
|
+ rd_percent_cpu = percent_cpu;
|
||
|
+ rd_mem_bytes = mem_usage;
|
||
|
+ rd_mem_percent = mem_percent} in
|
||
|
+ name, Active rd
|
||
|
+ (* For all other domains we can't calculate it, so leave as 0 *)
|
||
|
+ | rd -> rd
|
||
|
+ ) doms in
|
||
|
+
|
||
|
+ (* Calculate the number of block device read/write requests across
|
||
|
+ * all block devices attached to a domain.
|
||
|
+ *)
|
||
|
+ let doms =
|
||
|
+ List.map (
|
||
|
+ function
|
||
|
+ (* Do we have stats from the previous slice? *)
|
||
|
+ | name, Active ({ rd_prev_block_stats = ((_::_) as prev_block_stats) }
|
||
|
+ as rd) ->
|
||
|
+ let block_stats = rd.rd_block_stats in (* stats now *)
|
||
|
+
|
||
|
+ (* Add all the devices together. Throw away device names. *)
|
||
|
+ let prev_block_stats =
|
||
|
+ sum_block_stats (List.map snd prev_block_stats) in
|
||
|
+ let block_stats =
|
||
|
+ sum_block_stats (List.map snd block_stats) in
|
||
|
+
|
||
|
+ (* Calculate increase in read & write requests. *)
|
||
|
+ let read_reqs =
|
||
|
+ block_stats.D.rd_req -^ prev_block_stats.D.rd_req in
|
||
|
+ let write_reqs =
|
||
|
+ block_stats.D.wr_req -^ prev_block_stats.D.wr_req in
|
||
|
+ let read_bytes =
|
||
|
+ block_stats.D.rd_bytes -^ prev_block_stats.D.rd_bytes in
|
||
|
+ let write_bytes =
|
||
|
+ block_stats.D.wr_bytes -^ prev_block_stats.D.wr_bytes in
|
||
|
+
|
||
|
+ let rd = { rd with
|
||
|
+ rd_block_rd_reqs = Some read_reqs;
|
||
|
+ rd_block_wr_reqs = Some write_reqs;
|
||
|
+ rd_block_rd_bytes = Some read_bytes;
|
||
|
+ rd_block_wr_bytes = Some write_bytes;
|
||
|
+ } in
|
||
|
+ let rd = { rd with
|
||
|
+ rd_block_rd_info =
|
||
|
+ if block_in_bytes then
|
||
|
+ rd.rd_block_rd_bytes else rd.rd_block_rd_reqs;
|
||
|
+ rd_block_wr_info =
|
||
|
+ if block_in_bytes then
|
||
|
+ rd.rd_block_wr_bytes else rd.rd_block_wr_reqs;
|
||
|
+ } in
|
||
|
+ name, Active rd
|
||
|
+ (* For all other domains we can't calculate it, so leave as None. *)
|
||
|
+ | rd -> rd
|
||
|
+ ) doms in
|
||
|
+
|
||
|
+ (* Calculate the same as above for network interfaces across
|
||
|
+ * all network interfaces attached to a domain.
|
||
|
+ *)
|
||
|
+ let doms =
|
||
|
+ List.map (
|
||
|
+ function
|
||
|
+ (* Do we have stats from the previous slice? *)
|
||
|
+ | name, Active ({ rd_prev_interface_stats =
|
||
|
+ ((_::_) as prev_interface_stats) }
|
||
|
+ as rd) ->
|
||
|
+ let interface_stats = rd.rd_interface_stats in (* stats now *)
|
||
|
+
|
||
|
+ (* Add all the devices together. Throw away device names. *)
|
||
|
+ let prev_interface_stats =
|
||
|
+ sum_interface_stats (List.map snd prev_interface_stats) in
|
||
|
+ let interface_stats =
|
||
|
+ sum_interface_stats (List.map snd interface_stats) in
|
||
|
+
|
||
|
+ (* Calculate increase in rx & tx bytes. *)
|
||
|
+ let rx_bytes =
|
||
|
+ interface_stats.D.rx_bytes -^ prev_interface_stats.D.rx_bytes in
|
||
|
+ let tx_bytes =
|
||
|
+ interface_stats.D.tx_bytes -^ prev_interface_stats.D.tx_bytes in
|
||
|
+
|
||
|
+ let rd = { rd with
|
||
|
+ rd_net_rx_bytes = Some rx_bytes;
|
||
|
+ rd_net_tx_bytes = Some tx_bytes } in
|
||
|
+ name, Active rd
|
||
|
+ (* For all other domains we can't calculate it, so leave as None. *)
|
||
|
+ | rd -> rd
|
||
|
+ ) doms in
|
||
|
+
|
||
|
+ (* Calculate totals. *)
|
||
|
+ let totals =
|
||
|
+ List.fold_left (
|
||
|
+ fun (count, running, blocked, paused, shutdown, shutoff,
|
||
|
+ crashed, active, inactive,
|
||
|
+ total_cpu_time, total_memory, total_domU_memory) ->
|
||
|
+ function
|
||
|
+ | (name, Active rd) ->
|
||
|
+ let test state orig =
|
||
|
+ if rd.rd_info.D.state = state then orig+1 else orig
|
||
|
+ in
|
||
|
+ let running = test D.InfoRunning running in
|
||
|
+ let blocked = test D.InfoBlocked blocked in
|
||
|
+ let paused = test D.InfoPaused paused in
|
||
|
+ let shutdown = test D.InfoShutdown shutdown in
|
||
|
+ let shutoff = test D.InfoShutoff shutoff in
|
||
|
+ let crashed = test D.InfoCrashed crashed in
|
||
|
+
|
||
|
+ let total_cpu_time = total_cpu_time +. rd.rd_cpu_time in
|
||
|
+ let total_memory = total_memory +^ rd.rd_info.D.memory in
|
||
|
+ let total_domU_memory =
|
||
|
+ total_domU_memory +^
|
||
|
+ if rd.rd_domid > 0 then rd.rd_info.D.memory else 0L in
|
||
|
+
|
||
|
+ (count+1, running, blocked, paused, shutdown, shutoff,
|
||
|
+ crashed, active+1, inactive,
|
||
|
+ total_cpu_time, total_memory, total_domU_memory)
|
||
|
+
|
||
|
+ | (name, Inactive) -> (* inactive domain *)
|
||
|
+ (count+1, running, blocked, paused, shutdown, shutoff,
|
||
|
+ crashed, active, inactive+1,
|
||
|
+ total_cpu_time, total_memory, total_domU_memory)
|
||
|
+ ) (0,0,0,0,0,0,0,0,0, 0.,0L,0L) doms in
|
||
|
+
|
||
|
+ (* Update last_time, last_info. *)
|
||
|
+ last_time := time;
|
||
|
+ Hashtbl.clear last_info;
|
||
|
+ List.iter (
|
||
|
+ function
|
||
|
+ | (_, Active rd) ->
|
||
|
+ let info = rd.rd_info, rd.rd_block_stats, rd.rd_interface_stats in
|
||
|
+ Hashtbl.add last_info rd.rd_domid info
|
||
|
+ | _ -> ()
|
||
|
+ ) doms;
|
||
|
+
|
||
|
+ { rd_doms = doms;
|
||
|
+ rd_time = time;
|
||
|
+ rd_printable_time = printable_time;
|
||
|
+ rd_nr_pcpus = nr_pcpus;
|
||
|
+ rd_total_cpu = total_cpu;
|
||
|
+ rd_total_cpu_per_pcpu = total_cpu_per_pcpu;
|
||
|
+ rd_totals = totals }
|
||
|
+
|
||
|
+(* Collect some extra information in PCPUDisplay display_mode. *)
|
||
|
+let collect_pcpu { rd_doms = doms; rd_nr_pcpus = nr_pcpus } =
|
||
|
+ (* Get the VCPU info and VCPU->PCPU mappings for active domains.
|
||
|
+ * Also cull some data we don't care about.
|
||
|
+ *)
|
||
|
+ let doms =
|
||
|
+ List.filter_map (
|
||
|
+ function
|
||
|
+ | (name, Active rd) ->
|
||
|
+ (try
|
||
|
+ let domid = rd.rd_domid in
|
||
|
+ let maplen = C.cpumaplen nr_pcpus in
|
||
|
+ let cpu_stats = D.get_cpu_stats rd.rd_dom in
|
||
|
+
|
||
|
+ (* Note the terminology is confusing.
|
||
|
+ *
|
||
|
+ * In libvirt, cpu_time is the total time (hypervisor +
|
||
|
+ * vCPU). vcpu_time is the time only taken by the vCPU,
|
||
|
+ * excluding time taken inside the hypervisor.
|
||
|
+ *
|
||
|
+ * For each pCPU, libvirt may return either "cpu_time"
|
||
|
+ * or "vcpu_time" or neither or both. This function
|
||
|
+ * returns an array pair [|cpu_time, vcpu_time|];
|
||
|
+ * if either is missing it is returned as 0.
|
||
|
+ *)
|
||
|
+ let find_cpu_usages params =
|
||
|
+ let rec find_uint64_field name = function
|
||
|
+ | (n, D.TypedFieldUInt64 usage) :: _ when n = name ->
|
||
|
+ usage
|
||
|
+ | _ :: params -> find_uint64_field name params
|
||
|
+ | [] -> 0L
|
||
|
+ in
|
||
|
+ [| find_uint64_field "cpu_time" params;
|
||
|
+ find_uint64_field "vcpu_time" params |]
|
||
|
+ in
|
||
|
+
|
||
|
+ let pcpu_usages = Array.map find_cpu_usages cpu_stats in
|
||
|
+ let maxinfo = rd.rd_info.D.nr_virt_cpu in
|
||
|
+ let nr_vcpus, vcpu_infos, cpumaps =
|
||
|
+ D.get_vcpus rd.rd_dom maxinfo maplen in
|
||
|
+
|
||
|
+ (* Got previous pcpu_usages for this domain? *)
|
||
|
+ let prev_pcpu_usages =
|
||
|
+ try Some (Hashtbl.find last_pcpu_usages domid)
|
||
|
+ with Not_found -> None in
|
||
|
+ (* Update last_pcpu_usages. *)
|
||
|
+ Hashtbl.replace last_pcpu_usages domid pcpu_usages;
|
||
|
+
|
||
|
+ (match prev_pcpu_usages with
|
||
|
+ | Some prev_pcpu_usages
|
||
|
+ when Array.length prev_pcpu_usages = Array.length pcpu_usages ->
|
||
|
+ Some (domid, name, nr_vcpus, vcpu_infos, pcpu_usages,
|
||
|
+ prev_pcpu_usages, cpumaps, maplen)
|
||
|
+ | _ -> None (* ignore missing / unequal length prev_vcpu_infos *)
|
||
|
+ );
|
||
|
+ with
|
||
|
+ Libvirt.Virterror _ -> None (* ignore transient libvirt errors *)
|
||
|
+ )
|
||
|
+ | (_, Inactive) -> None (* ignore inactive doms *)
|
||
|
+ ) doms in
|
||
|
+ let nr_doms = List.length doms in
|
||
|
+
|
||
|
+ (* Rearrange the data into a matrix. Major axis (down) is
|
||
|
+ * pCPUs. Minor axis (right) is domains. At each node we store:
|
||
|
+ * cpu_time hypervisor + domain (on this pCPU only, nanosecs),
|
||
|
+ * vcpu_time domain only (on this pCPU only, nanosecs).
|
||
|
+ *)
|
||
|
+ let make_3d_array dimx dimy dimz e =
|
||
|
+ Array.init dimx (fun _ -> Array.make_matrix dimy dimz e)
|
||
|
+ in
|
||
|
+ let pcpus = make_3d_array nr_pcpus nr_doms 2 0L in
|
||
|
+
|
||
|
+ List.iteri (
|
||
|
+ fun di (domid, name, nr_vcpus, vcpu_infos, pcpu_usages,
|
||
|
+ prev_pcpu_usages, cpumaps, maplen) ->
|
||
|
+ (* Which pCPUs can this dom run on? *)
|
||
|
+ for p = 0 to Array.length pcpu_usages - 1 do
|
||
|
+ pcpus.(p).(di).(0) <-
|
||
|
+ pcpu_usages.(p).(0) -^ prev_pcpu_usages.(p).(0);
|
||
|
+ pcpus.(p).(di).(1) <-
|
||
|
+ pcpu_usages.(p).(1) -^ prev_pcpu_usages.(p).(1)
|
||
|
+ done
|
||
|
+ ) doms;
|
||
|
+
|
||
|
+ (* Sum the total CPU time used by each pCPU, for the %CPU column. *)
|
||
|
+ let pcpus_cpu_time =
|
||
|
+ Array.map (
|
||
|
+ fun row ->
|
||
|
+ let cpu_time = ref 0L in
|
||
|
+ for di = 0 to Array.length row-1 do
|
||
|
+ let t = row.(di).(0) in
|
||
|
+ cpu_time := !cpu_time +^ t
|
||
|
+ done;
|
||
|
+ Int64.to_float !cpu_time
|
||
|
+ ) pcpus in
|
||
|
+
|
||
|
+ { rd_pcpu_doms = doms;
|
||
|
+ rd_pcpu_pcpus = pcpus;
|
||
|
+ rd_pcpu_pcpus_cpu_time = pcpus_cpu_time }
|
||
|
diff --git a/src/collect.mli b/src/collect.mli
|
||
|
new file mode 100644
|
||
|
index 0000000..440859b
|
||
|
--- /dev/null
|
||
|
+++ b/src/collect.mli
|
||
|
@@ -0,0 +1,86 @@
|
||
|
+(* 'top'-like tool for libvirt domains.
|
||
|
+ (C) Copyright 2007-2017 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.
|
||
|
+*)
|
||
|
+
|
||
|
+(* Hook for [Opt_xml] to override (if present). *)
|
||
|
+val parse_device_xml :
|
||
|
+ (int -> [ `R ] Libvirt.Domain.t -> string list * string list) ref
|
||
|
+
|
||
|
+(* Intermediate "domain + stats" structure that we use to collect
|
||
|
+ * everything we know about a domain within the collect function.
|
||
|
+ *)
|
||
|
+type rd_domain = Inactive | Active of rd_active
|
||
|
+and rd_active = {
|
||
|
+ rd_domid : int; (* Domain ID. *)
|
||
|
+ rd_dom : [`R] Libvirt.Domain.t; (* Domain object. *)
|
||
|
+ rd_info : Libvirt.Domain.info; (* Domain CPU info now. *)
|
||
|
+ rd_block_stats : (string * Libvirt.Domain.block_stats) list;
|
||
|
+ (* Domain block stats now. *)
|
||
|
+ rd_interface_stats : (string * Libvirt.Domain.interface_stats) list;
|
||
|
+ (* Domain net stats now. *)
|
||
|
+ rd_prev_info : Libvirt.Domain.info option; (* Domain CPU info previously. *)
|
||
|
+ rd_prev_block_stats : (string * Libvirt.Domain.block_stats) list;
|
||
|
+ (* Domain block stats prev. *)
|
||
|
+ rd_prev_interface_stats : (string * Libvirt.Domain.interface_stats) list;
|
||
|
+ (* Domain interface stats prev. *)
|
||
|
+ (* The following are since the last slice, or 0 if cannot be calculated: *)
|
||
|
+ rd_cpu_time : float; (* CPU time used in nanoseconds. *)
|
||
|
+ rd_percent_cpu : float; (* CPU time as percent of total. *)
|
||
|
+ rd_mem_bytes : int64; (* Memory usage in bytes *)
|
||
|
+ rd_mem_percent: int64; (* Memory usage as percent of total *)
|
||
|
+ (* The following are since the last slice, or None if cannot be calc'd: *)
|
||
|
+ rd_block_rd_reqs : int64 option; (* Number of block device read rqs. *)
|
||
|
+ rd_block_wr_reqs : int64 option; (* Number of block device write rqs. *)
|
||
|
+ rd_block_rd_bytes : int64 option; (* Number of bytes block device read *)
|
||
|
+ rd_block_wr_bytes : int64 option; (* Number of bytes block device write *)
|
||
|
+ (* _info fields includes the number considering --block_in_bytes option *)
|
||
|
+ rd_block_rd_info : int64 option; (* Block device read info for user *)
|
||
|
+ rd_block_wr_info : int64 option; (* Block device read info for user *)
|
||
|
+
|
||
|
+ rd_net_rx_bytes : int64 option; (* Number of bytes received. *)
|
||
|
+ rd_net_tx_bytes : int64 option; (* Number of bytes transmitted. *)
|
||
|
+}
|
||
|
+
|
||
|
+type stats = {
|
||
|
+ rd_doms : (string * rd_domain) list; (* List of domains. *)
|
||
|
+ rd_time : float;
|
||
|
+ rd_printable_time : string;
|
||
|
+ rd_nr_pcpus : int;
|
||
|
+ rd_total_cpu : float;
|
||
|
+ rd_total_cpu_per_pcpu : float;
|
||
|
+ rd_totals : (int * int * int * int * int * int * int * int * int * float *
|
||
|
+ int64 * int64);
|
||
|
+}
|
||
|
+
|
||
|
+type pcpu_stats = {
|
||
|
+ rd_pcpu_doms : (int * string * int *
|
||
|
+ Libvirt.Domain.vcpu_info array * int64 array array *
|
||
|
+ int64 array array * string * int) list;
|
||
|
+ rd_pcpu_pcpus : int64 array array array;
|
||
|
+ rd_pcpu_pcpus_cpu_time : float array
|
||
|
+}
|
||
|
+
|
||
|
+val collect : Types.setup -> bool -> stats
|
||
|
+(** Collect statistics. *)
|
||
|
+
|
||
|
+val collect_pcpu : stats -> pcpu_stats
|
||
|
+(** Used in PCPUDisplay mode only, this returns extra per-PCPU stats. *)
|
||
|
+
|
||
|
+val clear_pcpu_display_data : unit -> unit
|
||
|
+(** Clear the cache of pcpu_usages used by PCPUDisplay display_mode
|
||
|
+ when we switch back to TaskDisplay mode. *)
|
||
|
diff --git a/src/csv_output.ml b/src/csv_output.ml
|
||
|
new file mode 100644
|
||
|
index 0000000..9496ca8
|
||
|
--- /dev/null
|
||
|
+++ b/src/csv_output.ml
|
||
|
@@ -0,0 +1,118 @@
|
||
|
+(* 'top'-like tool for libvirt domains.
|
||
|
+ (C) Copyright 2007-2017 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.
|
||
|
+*)
|
||
|
+
|
||
|
+(* CSV output functions. *)
|
||
|
+
|
||
|
+open Printf
|
||
|
+open ExtList
|
||
|
+
|
||
|
+open Collect
|
||
|
+
|
||
|
+module C = Libvirt.Connect
|
||
|
+
|
||
|
+(* Hook for CSV support (see [opt_csv.ml]). *)
|
||
|
+let csv_write : (string list -> unit) ref =
|
||
|
+ ref (
|
||
|
+ fun _ -> ()
|
||
|
+ )
|
||
|
+
|
||
|
+(* Write CSV header row. *)
|
||
|
+let write_csv_header (csv_cpu, csv_mem, csv_block, csv_net) block_in_bytes =
|
||
|
+ (!csv_write) (
|
||
|
+ [ "Hostname"; "Time"; "Arch"; "Physical CPUs";
|
||
|
+ "Count"; "Running"; "Blocked"; "Paused"; "Shutdown";
|
||
|
+ "Shutoff"; "Crashed"; "Active"; "Inactive";
|
||
|
+ "%CPU";
|
||
|
+ "Total hardware memory (KB)";
|
||
|
+ "Total memory (KB)"; "Total guest memory (KB)";
|
||
|
+ "Total CPU time (ns)" ] @
|
||
|
+ (* These fields are repeated for each domain: *)
|
||
|
+ [ "Domain ID"; "Domain name"; ] @
|
||
|
+ (if csv_cpu then [ "CPU (ns)"; "%CPU"; ] else []) @
|
||
|
+ (if csv_mem then [ "Mem (bytes)"; "%Mem";] else []) @
|
||
|
+ (if csv_block && not block_in_bytes
|
||
|
+ then [ "Block RDRQ"; "Block WRRQ"; ] else []) @
|
||
|
+ (if csv_block && block_in_bytes
|
||
|
+ then [ "Block RDBY"; "Block WRBY"; ] else []) @
|
||
|
+ (if csv_net then [ "Net RXBY"; "Net TXBY" ] else [])
|
||
|
+ )
|
||
|
+
|
||
|
+(* Write summary data to CSV file. *)
|
||
|
+let append_csv (_, _, _, _, _, node_info, hostname, _) (* setup *)
|
||
|
+ (csv_cpu, csv_mem, csv_block, csv_net)
|
||
|
+ { rd_doms = doms;
|
||
|
+ rd_printable_time = printable_time;
|
||
|
+ rd_nr_pcpus = nr_pcpus; rd_total_cpu = total_cpu;
|
||
|
+ rd_totals = totals } (* state *) =
|
||
|
+ (* The totals / summary fields. *)
|
||
|
+ let (count, running, blocked, paused, shutdown, shutoff,
|
||
|
+ crashed, active, inactive,
|
||
|
+ total_cpu_time, total_memory, total_domU_memory) = totals in
|
||
|
+
|
||
|
+ let percent_cpu = 100. *. total_cpu_time /. total_cpu in
|
||
|
+
|
||
|
+ let summary_fields = [
|
||
|
+ hostname; printable_time; node_info.C.model; string_of_int nr_pcpus;
|
||
|
+ string_of_int count; string_of_int running; string_of_int blocked;
|
||
|
+ string_of_int paused; string_of_int shutdown; string_of_int shutoff;
|
||
|
+ string_of_int crashed; string_of_int active; string_of_int inactive;
|
||
|
+ sprintf "%2.1f" percent_cpu;
|
||
|
+ Int64.to_string node_info.C.memory;
|
||
|
+ Int64.to_string total_memory; Int64.to_string total_domU_memory;
|
||
|
+ Int64.to_string (Int64.of_float total_cpu_time)
|
||
|
+ ] in
|
||
|
+
|
||
|
+ (* The domains.
|
||
|
+ *
|
||
|
+ * Sort them by ID so that the list of relatively stable. Ignore
|
||
|
+ * inactive domains.
|
||
|
+ *)
|
||
|
+ let doms = List.filter_map (
|
||
|
+ function
|
||
|
+ | _, Inactive -> None (* Ignore inactive domains. *)
|
||
|
+ | name, Active rd -> Some (name, rd)
|
||
|
+ ) doms in
|
||
|
+ let cmp (_, { rd_domid = rd_domid1 }) (_, { rd_domid = rd_domid2 }) =
|
||
|
+ compare rd_domid1 rd_domid2
|
||
|
+ in
|
||
|
+ let doms = List.sort ~cmp doms in
|
||
|
+
|
||
|
+ let string_of_int64_option = Option.map_default Int64.to_string "" in
|
||
|
+
|
||
|
+ let domain_fields = List.map (
|
||
|
+ fun (domname, rd) ->
|
||
|
+ [ string_of_int rd.rd_domid; domname ] @
|
||
|
+ (if csv_cpu then [
|
||
|
+ string_of_float rd.rd_cpu_time; string_of_float rd.rd_percent_cpu
|
||
|
+ ] else []) @
|
||
|
+ (if csv_mem then [
|
||
|
+ Int64.to_string rd.rd_mem_bytes; Int64.to_string rd.rd_mem_percent
|
||
|
+ ] else []) @
|
||
|
+ (if csv_block then [
|
||
|
+ string_of_int64_option rd.rd_block_rd_info;
|
||
|
+ string_of_int64_option rd.rd_block_wr_info;
|
||
|
+ ] else []) @
|
||
|
+ (if csv_net then [
|
||
|
+ string_of_int64_option rd.rd_net_rx_bytes;
|
||
|
+ string_of_int64_option rd.rd_net_tx_bytes;
|
||
|
+ ] else [])
|
||
|
+ ) doms in
|
||
|
+ let domain_fields = List.flatten domain_fields in
|
||
|
+
|
||
|
+ (!csv_write) (summary_fields @ domain_fields)
|
||
|
diff --git a/src/csv_output.mli b/src/csv_output.mli
|
||
|
new file mode 100644
|
||
|
index 0000000..d5eab0f
|
||
|
--- /dev/null
|
||
|
+++ b/src/csv_output.mli
|
||
|
@@ -0,0 +1,27 @@
|
||
|
+(* 'top'-like tool for libvirt domains.
|
||
|
+ (C) Copyright 2007-2017 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.
|
||
|
+*)
|
||
|
+
|
||
|
+(** CSV output functions. *)
|
||
|
+
|
||
|
+(* Hook for [Opt_csv] to override (if present). *)
|
||
|
+val csv_write : (string list -> unit) ref
|
||
|
+
|
||
|
+val write_csv_header : bool * bool * bool * bool -> bool -> unit
|
||
|
+
|
||
|
+val append_csv : Types.setup -> bool * bool * bool * bool -> Collect.stats -> unit
|
||
|
diff --git a/src/opt_csv.ml b/src/opt_csv.ml
|
||
|
index 6c3b2be..6625c61 100644
|
||
|
--- a/src/opt_csv.ml
|
||
|
+++ b/src/opt_csv.ml
|
||
|
@@ -28,7 +28,7 @@ Top.csv_start :=
|
||
|
fun filename ->
|
||
|
chan := Some (open_out filename) ;;
|
||
|
|
||
|
-Top.csv_write :=
|
||
|
+Csv_output.csv_write :=
|
||
|
fun row ->
|
||
|
match !chan with
|
||
|
| None -> () (* CSV output not enabled. *)
|
||
|
diff --git a/src/opt_xml.ml b/src/opt_xml.ml
|
||
|
index bb83780..1037b85 100644
|
||
|
--- a/src/opt_xml.ml
|
||
|
+++ b/src/opt_xml.ml
|
||
|
@@ -27,7 +27,7 @@ module C = Libvirt.Connect
|
||
|
module D = Libvirt.Domain
|
||
|
module N = Libvirt.Network ;;
|
||
|
|
||
|
-Top.parse_device_xml :=
|
||
|
+Collect.parse_device_xml :=
|
||
|
fun id dom ->
|
||
|
try
|
||
|
let xml = D.get_xml_desc dom in
|
||
|
diff --git a/src/redraw.ml b/src/redraw.ml
|
||
|
new file mode 100644
|
||
|
index 0000000..9ce889b
|
||
|
--- /dev/null
|
||
|
+++ b/src/redraw.ml
|
||
|
@@ -0,0 +1,506 @@
|
||
|
+(* 'top'-like tool for libvirt domains.
|
||
|
+ (C) Copyright 2007-2017 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.
|
||
|
+*)
|
||
|
+
|
||
|
+open ExtList
|
||
|
+open Curses
|
||
|
+open Printf
|
||
|
+
|
||
|
+open Opt_gettext.Gettext
|
||
|
+open Utils
|
||
|
+open Types
|
||
|
+open Screen
|
||
|
+open Collect
|
||
|
+
|
||
|
+module C = Libvirt.Connect
|
||
|
+module D = Libvirt.Domain
|
||
|
+
|
||
|
+(* Keep a historical list of %CPU usages. *)
|
||
|
+let historical_cpu = ref []
|
||
|
+let historical_cpu_last_time = ref (Unix.gettimeofday ())
|
||
|
+
|
||
|
+(* Redraw the display. *)
|
||
|
+let redraw display_mode sort_order
|
||
|
+ (_, _, _, _, _, node_info, _, _) (* setup *)
|
||
|
+ block_in_bytes
|
||
|
+ historical_cpu_delay
|
||
|
+ { rd_doms = doms;
|
||
|
+ rd_time = time; rd_printable_time = printable_time;
|
||
|
+ rd_nr_pcpus = nr_pcpus;
|
||
|
+ rd_total_cpu = total_cpu;
|
||
|
+ rd_total_cpu_per_pcpu = total_cpu_per_pcpu;
|
||
|
+ rd_totals = totals } (* state *)
|
||
|
+ pcpu_display =
|
||
|
+ clear ();
|
||
|
+
|
||
|
+ (* Get the screen/window size. *)
|
||
|
+ let lines, cols = get_size () in
|
||
|
+
|
||
|
+ (* Time. *)
|
||
|
+ mvaddstr top_lineno 0 (sprintf "virt-top %s - " printable_time);
|
||
|
+
|
||
|
+ (* Basic node_info. *)
|
||
|
+ addstr
|
||
|
+ (sprintf "%s %d/%dCPU %dMHz %LdMB "
|
||
|
+ node_info.C.model node_info.C.cpus nr_pcpus node_info.C.mhz
|
||
|
+ (node_info.C.memory /^ 1024L));
|
||
|
+ (* Save the cursor position for when we come to draw the
|
||
|
+ * historical CPU times (down in this function).
|
||
|
+ *)
|
||
|
+ let stdscr = stdscr () in
|
||
|
+ let historical_cursor = getyx stdscr in
|
||
|
+
|
||
|
+ (match display_mode with
|
||
|
+
|
||
|
+ (*---------- Showing domains ----------*)
|
||
|
+ | TaskDisplay ->
|
||
|
+ (* Sort domains on current sort_order. *)
|
||
|
+ let doms =
|
||
|
+ let cmp =
|
||
|
+ match sort_order with
|
||
|
+ | DomainName ->
|
||
|
+ (fun _ -> 0) (* fallthrough to default name compare *)
|
||
|
+ | Processor ->
|
||
|
+ (function
|
||
|
+ | Active rd1, Active rd2 ->
|
||
|
+ compare rd2.rd_percent_cpu rd1.rd_percent_cpu
|
||
|
+ | Active _, Inactive -> -1
|
||
|
+ | Inactive, Active _ -> 1
|
||
|
+ | Inactive, Inactive -> 0)
|
||
|
+ | Memory ->
|
||
|
+ (function
|
||
|
+ | Active { rd_info = info1 }, Active { rd_info = info2 } ->
|
||
|
+ compare info2.D.memory info1.D.memory
|
||
|
+ | Active _, Inactive -> -1
|
||
|
+ | Inactive, Active _ -> 1
|
||
|
+ | Inactive, Inactive -> 0)
|
||
|
+ | Time ->
|
||
|
+ (function
|
||
|
+ | Active { rd_info = info1 }, Active { rd_info = info2 } ->
|
||
|
+ compare info2.D.cpu_time info1.D.cpu_time
|
||
|
+ | Active _, Inactive -> -1
|
||
|
+ | Inactive, Active _ -> 1
|
||
|
+ | Inactive, Inactive -> 0)
|
||
|
+ | DomainID ->
|
||
|
+ (function
|
||
|
+ | Active { rd_domid = id1 }, Active { rd_domid = id2 } ->
|
||
|
+ compare id1 id2
|
||
|
+ | Active _, Inactive -> -1
|
||
|
+ | Inactive, Active _ -> 1
|
||
|
+ | Inactive, Inactive -> 0)
|
||
|
+ | NetRX ->
|
||
|
+ (function
|
||
|
+ | Active { rd_net_rx_bytes = r1 }, Active { rd_net_rx_bytes = r2 } ->
|
||
|
+ compare r2 r1
|
||
|
+ | Active _, Inactive -> -1
|
||
|
+ | Inactive, Active _ -> 1
|
||
|
+ | Inactive, Inactive -> 0)
|
||
|
+ | NetTX ->
|
||
|
+ (function
|
||
|
+ | Active { rd_net_tx_bytes = r1 }, Active { rd_net_tx_bytes = r2 } ->
|
||
|
+ compare r2 r1
|
||
|
+ | Active _, Inactive -> -1
|
||
|
+ | Inactive, Active _ -> 1
|
||
|
+ | Inactive, Inactive -> 0)
|
||
|
+ | BlockRdRq ->
|
||
|
+ (function
|
||
|
+ | Active { rd_block_rd_reqs = r1 }, Active { rd_block_rd_reqs = r2 } ->
|
||
|
+ compare r2 r1
|
||
|
+ | Active _, Inactive -> -1
|
||
|
+ | Inactive, Active _ -> 1
|
||
|
+ | Inactive, Inactive -> 0)
|
||
|
+ | BlockWrRq ->
|
||
|
+ (function
|
||
|
+ | Active { rd_block_wr_reqs = r1 }, Active { rd_block_wr_reqs = r2 } ->
|
||
|
+ compare r2 r1
|
||
|
+ | Active _, Inactive -> -1
|
||
|
+ | Inactive, Active _ -> 1
|
||
|
+ | Inactive, Inactive -> 0)
|
||
|
+ in
|
||
|
+ let cmp (name1, dom1) (name2, dom2) =
|
||
|
+ let r = cmp (dom1, dom2) in
|
||
|
+ if r <> 0 then r
|
||
|
+ else compare name1 name2
|
||
|
+ in
|
||
|
+ List.sort ~cmp doms in
|
||
|
+
|
||
|
+ (* Print domains. *)
|
||
|
+ attron A.reverse;
|
||
|
+ let header_string =
|
||
|
+ if block_in_bytes
|
||
|
+ then " ID S RDBY WRBY RXBY TXBY %CPU %MEM TIME NAME"
|
||
|
+ else " ID S RDRQ WRRQ RXBY TXBY %CPU %MEM TIME NAME"
|
||
|
+ in
|
||
|
+ mvaddstr header_lineno 0
|
||
|
+ (pad cols header_string);
|
||
|
+ attroff A.reverse;
|
||
|
+
|
||
|
+ let rec loop lineno = function
|
||
|
+ | [] -> ()
|
||
|
+ | (name, Active rd) :: doms ->
|
||
|
+ if lineno < lines then (
|
||
|
+ let state = show_state rd.rd_info.D.state in
|
||
|
+ let rd_req = Show.int64_option rd.rd_block_rd_info in
|
||
|
+ let wr_req = Show.int64_option rd.rd_block_wr_info in
|
||
|
+ let rx_bytes = Show.int64_option rd.rd_net_rx_bytes in
|
||
|
+ let tx_bytes = Show.int64_option rd.rd_net_tx_bytes in
|
||
|
+ let percent_cpu = Show.percent rd.rd_percent_cpu in
|
||
|
+ let percent_mem = Int64.to_float rd.rd_mem_percent in
|
||
|
+ let percent_mem = Show.percent percent_mem in
|
||
|
+ let time = Show.time rd.rd_info.D.cpu_time in
|
||
|
+
|
||
|
+ let line =
|
||
|
+ sprintf "%5d %c %s %s %s %s %s %s %s %s"
|
||
|
+ rd.rd_domid state rd_req wr_req rx_bytes tx_bytes
|
||
|
+ percent_cpu percent_mem time name in
|
||
|
+ let line = pad cols line in
|
||
|
+ mvaddstr lineno 0 line;
|
||
|
+ loop (lineno+1) doms
|
||
|
+ )
|
||
|
+ | (name, Inactive) :: doms -> (* inactive domain *)
|
||
|
+ if lineno < lines then (
|
||
|
+ let line =
|
||
|
+ sprintf
|
||
|
+ " - (%s)"
|
||
|
+ name in
|
||
|
+ let line = pad cols line in
|
||
|
+ mvaddstr lineno 0 line;
|
||
|
+ loop (lineno+1) doms
|
||
|
+ )
|
||
|
+ in
|
||
|
+ loop domains_lineno doms
|
||
|
+
|
||
|
+ (*---------- Showing physical CPUs ----------*)
|
||
|
+ | PCPUDisplay ->
|
||
|
+ let { rd_pcpu_doms = doms;
|
||
|
+ rd_pcpu_pcpus = pcpus;
|
||
|
+ rd_pcpu_pcpus_cpu_time = pcpus_cpu_time } =
|
||
|
+ match pcpu_display with
|
||
|
+ | Some p -> p
|
||
|
+ | None -> failwith "internal error: no pcpu_display data" in
|
||
|
+
|
||
|
+ (* Display the pCPUs. *)
|
||
|
+ let dom_names =
|
||
|
+ String.concat "" (
|
||
|
+ List.map (
|
||
|
+ fun (_, name, _, _, _, _, _, _) ->
|
||
|
+ let len = String.length name in
|
||
|
+ let width = max (len+1) 12 in
|
||
|
+ pad width name
|
||
|
+ ) doms
|
||
|
+ ) in
|
||
|
+ attron A.reverse;
|
||
|
+ mvaddstr header_lineno 0 (pad cols ("PHYCPU %CPU " ^ dom_names));
|
||
|
+ attroff A.reverse;
|
||
|
+
|
||
|
+ Array.iteri (
|
||
|
+ fun p row ->
|
||
|
+ mvaddstr (p+domains_lineno) 0 (sprintf "%4d " p);
|
||
|
+ let cpu_time = pcpus_cpu_time.(p) in (* ns used on this CPU *)
|
||
|
+ let percent_cpu = 100. *. cpu_time /. total_cpu_per_pcpu in
|
||
|
+ addstr (Show.percent percent_cpu);
|
||
|
+ addch ' ';
|
||
|
+
|
||
|
+ List.iteri (
|
||
|
+ fun di (domid, name, _, _, _, _, _, _) ->
|
||
|
+ let t = pcpus.(p).(di).(0) in (* hypervisor + domain *)
|
||
|
+ let t_only = pcpus.(p).(di).(1) in (* domain only *)
|
||
|
+ let len = String.length name in
|
||
|
+ let width = max (len+1) 12 in
|
||
|
+ let str_t =
|
||
|
+ if t <= 0L then ""
|
||
|
+ else (
|
||
|
+ let t = Int64.to_float t in
|
||
|
+ let percent = 100. *. t /. total_cpu_per_pcpu in
|
||
|
+ Show.percent percent
|
||
|
+ ) in
|
||
|
+ let str_t_only =
|
||
|
+ if t_only <= 0L then ""
|
||
|
+ else (
|
||
|
+ let t_only = Int64.to_float t_only in
|
||
|
+ let percent = 100. *. t_only /. total_cpu_per_pcpu in
|
||
|
+ Show.percent percent
|
||
|
+ ) in
|
||
|
+ addstr (pad 5 str_t);
|
||
|
+ addstr (pad 5 str_t_only);
|
||
|
+ addstr (pad (width-10) " ");
|
||
|
+ ()
|
||
|
+ ) doms
|
||
|
+ ) pcpus;
|
||
|
+
|
||
|
+ (*---------- Showing network interfaces ----------*)
|
||
|
+ | NetDisplay ->
|
||
|
+ (* Only care about active domains. *)
|
||
|
+ let doms =
|
||
|
+ List.filter_map (
|
||
|
+ function
|
||
|
+ | (name, Active rd) -> Some (name, rd)
|
||
|
+ | (_, Inactive) -> None
|
||
|
+ ) doms in
|
||
|
+
|
||
|
+ (* For each domain we have a list of network interfaces seen
|
||
|
+ * this slice, and seen in the previous slice, which we now
|
||
|
+ * match up to get a list of (domain, interface) for which
|
||
|
+ * we have current & previous knowledge. (And ignore the rest).
|
||
|
+ *)
|
||
|
+ let devs =
|
||
|
+ List.map (
|
||
|
+ fun (name, rd) ->
|
||
|
+ List.filter_map (
|
||
|
+ fun (dev, stats) ->
|
||
|
+ try
|
||
|
+ (* Have prev slice stats for this device? *)
|
||
|
+ let prev_stats =
|
||
|
+ List.assoc dev rd.rd_prev_interface_stats in
|
||
|
+ Some (dev, name, rd, stats, prev_stats)
|
||
|
+ with Not_found -> None
|
||
|
+ ) rd.rd_interface_stats
|
||
|
+ ) doms in
|
||
|
+
|
||
|
+ (* Finally we have a list of:
|
||
|
+ * device name, domain name, rd_* stuff, curr stats, prev stats.
|
||
|
+ *)
|
||
|
+ let devs : (string * string * rd_active *
|
||
|
+ D.interface_stats * D.interface_stats) list =
|
||
|
+ List.flatten devs in
|
||
|
+
|
||
|
+ (* Difference curr slice & prev slice. *)
|
||
|
+ let devs =
|
||
|
+ List.map (
|
||
|
+ fun (dev, name, rd, curr, prev) ->
|
||
|
+ dev, name, rd, diff_interface_stats curr prev
|
||
|
+ ) devs in
|
||
|
+
|
||
|
+ (* Sort by current sort order, but map some of the standard
|
||
|
+ * sort orders into ones which makes sense here.
|
||
|
+ *)
|
||
|
+ let devs =
|
||
|
+ let cmp =
|
||
|
+ match sort_order with
|
||
|
+ | DomainName ->
|
||
|
+ (fun _ -> 0) (* fallthrough to default name compare *)
|
||
|
+ | DomainID ->
|
||
|
+ (fun (_, { rd_domid = id1 }, _, { rd_domid = id2 }) ->
|
||
|
+ compare id1 id2)
|
||
|
+ | Processor | Memory | Time
|
||
|
+ | BlockRdRq | BlockWrRq
|
||
|
+ (* fallthrough to RXBY comparison. *)
|
||
|
+ | NetRX ->
|
||
|
+ (fun ({ D.rx_bytes = b1 }, _, { D.rx_bytes = b2 }, _) ->
|
||
|
+ compare b2 b1)
|
||
|
+ | NetTX ->
|
||
|
+ (fun ({ D.tx_bytes = b1 }, _, { D.tx_bytes = b2 }, _) ->
|
||
|
+ compare b2 b1)
|
||
|
+ in
|
||
|
+ let cmp (dev1, name1, rd1, stats1) (dev2, name2, rd2, stats2) =
|
||
|
+ let r = cmp (stats1, rd1, stats2, rd2) in
|
||
|
+ if r <> 0 then r
|
||
|
+ else compare (dev1, name1) (dev2, name2)
|
||
|
+ in
|
||
|
+ List.sort ~cmp devs in
|
||
|
+
|
||
|
+ (* Print the header for network devices. *)
|
||
|
+ attron A.reverse;
|
||
|
+ mvaddstr header_lineno 0
|
||
|
+ (pad cols " ID S RXBY TXBY RXPK TXPK DOMAIN INTERFACE");
|
||
|
+ attroff A.reverse;
|
||
|
+
|
||
|
+ (* Print domains and devices. *)
|
||
|
+ let rec loop lineno = function
|
||
|
+ | [] -> ()
|
||
|
+ | (dev, name, rd, stats) :: devs ->
|
||
|
+ if lineno < lines then (
|
||
|
+ let state = show_state rd.rd_info.D.state in
|
||
|
+ let rx_bytes =
|
||
|
+ if stats.D.rx_bytes >= 0L
|
||
|
+ then Show.int64 stats.D.rx_bytes
|
||
|
+ else " " in
|
||
|
+ let tx_bytes =
|
||
|
+ if stats.D.tx_bytes >= 0L
|
||
|
+ then Show.int64 stats.D.tx_bytes
|
||
|
+ else " " in
|
||
|
+ let rx_packets =
|
||
|
+ if stats.D.rx_packets >= 0L
|
||
|
+ then Show.int64 stats.D.rx_packets
|
||
|
+ else " " in
|
||
|
+ let tx_packets =
|
||
|
+ if stats.D.tx_packets >= 0L
|
||
|
+ then Show.int64 stats.D.tx_packets
|
||
|
+ else " " in
|
||
|
+
|
||
|
+ let line = sprintf "%5d %c %s %s %s %s %-12s %s"
|
||
|
+ rd.rd_domid state
|
||
|
+ rx_bytes tx_bytes
|
||
|
+ rx_packets tx_packets
|
||
|
+ (pad 12 name) dev in
|
||
|
+ let line = pad cols line in
|
||
|
+ mvaddstr lineno 0 line;
|
||
|
+ loop (lineno+1) devs
|
||
|
+ )
|
||
|
+ in
|
||
|
+ loop domains_lineno devs
|
||
|
+
|
||
|
+ (*---------- Showing block devices ----------*)
|
||
|
+ | BlockDisplay ->
|
||
|
+ (* Only care about active domains. *)
|
||
|
+ let doms =
|
||
|
+ List.filter_map (
|
||
|
+ function
|
||
|
+ | (name, Active rd) -> Some (name, rd)
|
||
|
+ | (_, Inactive) -> None
|
||
|
+ ) doms in
|
||
|
+
|
||
|
+ (* For each domain we have a list of block devices seen
|
||
|
+ * this slice, and seen in the previous slice, which we now
|
||
|
+ * match up to get a list of (domain, device) for which
|
||
|
+ * we have current & previous knowledge. (And ignore the rest).
|
||
|
+ *)
|
||
|
+ let devs =
|
||
|
+ List.map (
|
||
|
+ fun (name, rd) ->
|
||
|
+ List.filter_map (
|
||
|
+ fun (dev, stats) ->
|
||
|
+ try
|
||
|
+ (* Have prev slice stats for this device? *)
|
||
|
+ let prev_stats =
|
||
|
+ List.assoc dev rd.rd_prev_block_stats in
|
||
|
+ Some (dev, name, rd, stats, prev_stats)
|
||
|
+ with Not_found -> None
|
||
|
+ ) rd.rd_block_stats
|
||
|
+ ) doms in
|
||
|
+
|
||
|
+ (* Finally we have a list of:
|
||
|
+ * device name, domain name, rd_* stuff, curr stats, prev stats.
|
||
|
+ *)
|
||
|
+ let devs : (string * string * rd_active *
|
||
|
+ D.block_stats * D.block_stats) list =
|
||
|
+ List.flatten devs in
|
||
|
+
|
||
|
+ (* Difference curr slice & prev slice. *)
|
||
|
+ let devs =
|
||
|
+ List.map (
|
||
|
+ fun (dev, name, rd, curr, prev) ->
|
||
|
+ dev, name, rd, diff_block_stats curr prev
|
||
|
+ ) devs in
|
||
|
+
|
||
|
+ (* Sort by current sort order, but map some of the standard
|
||
|
+ * sort orders into ones which makes sense here.
|
||
|
+ *)
|
||
|
+ let devs =
|
||
|
+ let cmp =
|
||
|
+ match sort_order with
|
||
|
+ | DomainName ->
|
||
|
+ (fun _ -> 0) (* fallthrough to default name compare *)
|
||
|
+ | DomainID ->
|
||
|
+ (fun (_, { rd_domid = id1 }, _, { rd_domid = id2 }) ->
|
||
|
+ compare id1 id2)
|
||
|
+ | Processor | Memory | Time
|
||
|
+ | NetRX | NetTX
|
||
|
+ (* fallthrough to RDRQ comparison. *)
|
||
|
+ | BlockRdRq ->
|
||
|
+ (fun ({ D.rd_req = b1 }, _, { D.rd_req = b2 }, _) ->
|
||
|
+ compare b2 b1)
|
||
|
+ | BlockWrRq ->
|
||
|
+ (fun ({ D.wr_req = b1 }, _, { D.wr_req = b2 }, _) ->
|
||
|
+ compare b2 b1)
|
||
|
+ in
|
||
|
+ let cmp (dev1, name1, rd1, stats1) (dev2, name2, rd2, stats2) =
|
||
|
+ let r = cmp (stats1, rd1, stats2, rd2) in
|
||
|
+ if r <> 0 then r
|
||
|
+ else compare (dev1, name1) (dev2, name2)
|
||
|
+ in
|
||
|
+ List.sort ~cmp devs in
|
||
|
+
|
||
|
+ (* Print the header for block devices. *)
|
||
|
+ attron A.reverse;
|
||
|
+ mvaddstr header_lineno 0
|
||
|
+ (pad cols " ID S RDBY WRBY RDRQ WRRQ DOMAIN DEVICE");
|
||
|
+ attroff A.reverse;
|
||
|
+
|
||
|
+ (* Print domains and devices. *)
|
||
|
+ let rec loop lineno = function
|
||
|
+ | [] -> ()
|
||
|
+ | (dev, name, rd, stats) :: devs ->
|
||
|
+ if lineno < lines then (
|
||
|
+ let state = show_state rd.rd_info.D.state in
|
||
|
+ let rd_bytes =
|
||
|
+ if stats.D.rd_bytes >= 0L
|
||
|
+ then Show.int64 stats.D.rd_bytes
|
||
|
+ else " " in
|
||
|
+ let wr_bytes =
|
||
|
+ if stats.D.wr_bytes >= 0L
|
||
|
+ then Show.int64 stats.D.wr_bytes
|
||
|
+ else " " in
|
||
|
+ let rd_req =
|
||
|
+ if stats.D.rd_req >= 0L
|
||
|
+ then Show.int64 stats.D.rd_req
|
||
|
+ else " " in
|
||
|
+ let wr_req =
|
||
|
+ if stats.D.wr_req >= 0L
|
||
|
+ then Show.int64 stats.D.wr_req
|
||
|
+ else " " in
|
||
|
+
|
||
|
+ let line = sprintf "%5d %c %s %s %s %s %-12s %s"
|
||
|
+ rd.rd_domid state
|
||
|
+ rd_bytes wr_bytes
|
||
|
+ rd_req wr_req
|
||
|
+ (pad 12 name) dev in
|
||
|
+ let line = pad cols line in
|
||
|
+ mvaddstr lineno 0 line;
|
||
|
+ loop (lineno+1) devs
|
||
|
+ )
|
||
|
+ in
|
||
|
+ loop domains_lineno devs
|
||
|
+ ); (* end of display_mode conditional section *)
|
||
|
+
|
||
|
+ let (count, running, blocked, paused, shutdown, shutoff,
|
||
|
+ crashed, active, inactive,
|
||
|
+ total_cpu_time, total_memory, total_domU_memory) = totals in
|
||
|
+
|
||
|
+ mvaddstr summary_lineno 0
|
||
|
+ (sprintf
|
||
|
+ (f_"%d domains, %d active, %d running, %d sleeping, %d paused, %d inactive D:%d O:%d X:%d")
|
||
|
+ count active running blocked paused inactive shutdown shutoff crashed);
|
||
|
+
|
||
|
+ (* Total %CPU used, and memory summary. *)
|
||
|
+ let percent_cpu = 100. *. total_cpu_time /. total_cpu in
|
||
|
+ mvaddstr (summary_lineno+1) 0
|
||
|
+ (sprintf
|
||
|
+ (f_"CPU: %2.1f%% Mem: %Ld MB (%Ld MB by guests)")
|
||
|
+ percent_cpu (total_memory /^ 1024L) (total_domU_memory /^ 1024L));
|
||
|
+
|
||
|
+ (* Time to grab another historical %CPU for the list? *)
|
||
|
+ if time >= !historical_cpu_last_time +. float historical_cpu_delay
|
||
|
+ then (
|
||
|
+ historical_cpu := percent_cpu :: List.take 10 !historical_cpu;
|
||
|
+ historical_cpu_last_time := time
|
||
|
+ );
|
||
|
+
|
||
|
+ (* Display historical CPU time. *)
|
||
|
+ let () =
|
||
|
+ let y, x = historical_cursor in
|
||
|
+ let maxwidth = cols - x in
|
||
|
+ let line =
|
||
|
+ String.concat " "
|
||
|
+ (List.map (sprintf "%2.1f%%") !historical_cpu) in
|
||
|
+ let line = pad maxwidth line in
|
||
|
+ mvaddstr y x line;
|
||
|
+ () in
|
||
|
+
|
||
|
+ move message_lineno 0; (* Park cursor in message area, as with top. *)
|
||
|
+ refresh () (* Refresh the display. *)
|
||
|
diff --git a/src/redraw.mli b/src/redraw.mli
|
||
|
new file mode 100644
|
||
|
index 0000000..2ea97c3
|
||
|
--- /dev/null
|
||
|
+++ b/src/redraw.mli
|
||
|
@@ -0,0 +1,20 @@
|
||
|
+(* 'top'-like tool for libvirt domains.
|
||
|
+ (C) Copyright 2007-2017 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.
|
||
|
+*)
|
||
|
+
|
||
|
+val redraw : Types.display -> Types.sort_order -> Types.setup -> bool -> int -> Collect.stats -> Collect.pcpu_stats option -> unit
|
||
|
diff --git a/src/screen.ml b/src/screen.ml
|
||
|
new file mode 100644
|
||
|
index 0000000..0d847a2
|
||
|
--- /dev/null
|
||
|
+++ b/src/screen.ml
|
||
|
@@ -0,0 +1,52 @@
|
||
|
+(* 'top'-like tool for libvirt domains.
|
||
|
+ (C) Copyright 2007-2017 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.
|
||
|
+*)
|
||
|
+
|
||
|
+(* The virt-top screen layout. *)
|
||
|
+
|
||
|
+open Curses
|
||
|
+
|
||
|
+module D = Libvirt.Domain
|
||
|
+
|
||
|
+(* Line numbers. *)
|
||
|
+let top_lineno = 0
|
||
|
+let summary_lineno = 1 (* this takes 2 lines *)
|
||
|
+let message_lineno = 3
|
||
|
+let header_lineno = 4
|
||
|
+let domains_lineno = 5
|
||
|
+
|
||
|
+(* Easier to use versions of curses functions addstr, mvaddstr, etc. *)
|
||
|
+let move y x = ignore (move y x)
|
||
|
+let refresh () = ignore (refresh ())
|
||
|
+let addch c = ignore (addch (int_of_char c))
|
||
|
+let addstr s = ignore (addstr s)
|
||
|
+let mvaddstr y x s = ignore (mvaddstr y x s)
|
||
|
+
|
||
|
+(* Print in the "message area". *)
|
||
|
+let clear_msg () = move message_lineno 0; clrtoeol ()
|
||
|
+let print_msg str = clear_msg (); mvaddstr message_lineno 0 str
|
||
|
+
|
||
|
+(* Show a libvirt domain state (the 'S' column). *)
|
||
|
+let show_state = function
|
||
|
+ | D.InfoNoState -> '?'
|
||
|
+ | D.InfoRunning -> 'R'
|
||
|
+ | D.InfoBlocked -> 'S'
|
||
|
+ | D.InfoPaused -> 'P'
|
||
|
+ | D.InfoShutdown -> 'D'
|
||
|
+ | D.InfoShutoff -> 'O'
|
||
|
+ | D.InfoCrashed -> 'X'
|
||
|
diff --git a/src/screen.mli b/src/screen.mli
|
||
|
new file mode 100644
|
||
|
index 0000000..a8a23a0
|
||
|
--- /dev/null
|
||
|
+++ b/src/screen.mli
|
||
|
@@ -0,0 +1,41 @@
|
||
|
+(* 'top'-like tool for libvirt domains.
|
||
|
+ (C) Copyright 2007-2017 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.
|
||
|
+*)
|
||
|
+
|
||
|
+(** The virt-top screen layout. *)
|
||
|
+
|
||
|
+(* Line numbers. *)
|
||
|
+val top_lineno : int
|
||
|
+val summary_lineno : int (** this takes 2 lines *)
|
||
|
+val message_lineno : int
|
||
|
+val header_lineno : int
|
||
|
+val domains_lineno : int
|
||
|
+
|
||
|
+(* Easier to use versions of curses functions addstr, mvaddstr, etc. *)
|
||
|
+val move : int -> int -> unit
|
||
|
+val refresh : unit -> unit
|
||
|
+val addch : char -> unit
|
||
|
+val addstr : string -> unit
|
||
|
+val mvaddstr : int -> int -> string -> unit
|
||
|
+
|
||
|
+(* Print in the "message area". *)
|
||
|
+val clear_msg : unit -> unit
|
||
|
+val print_msg : string -> unit
|
||
|
+
|
||
|
+(* Show a libvirt domain state (the 'S' column). *)
|
||
|
+val show_state : Libvirt.Domain.state -> char
|
||
|
diff --git a/src/stream_output.ml b/src/stream_output.ml
|
||
|
new file mode 100644
|
||
|
index 0000000..bf7b114
|
||
|
--- /dev/null
|
||
|
+++ b/src/stream_output.ml
|
||
|
@@ -0,0 +1,84 @@
|
||
|
+(* 'top'-like tool for libvirt domains.
|
||
|
+ (C) Copyright 2007-2017 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.
|
||
|
+*)
|
||
|
+
|
||
|
+(* [--stream] mode output functions. *)
|
||
|
+
|
||
|
+open Printf
|
||
|
+open ExtList
|
||
|
+
|
||
|
+open Utils
|
||
|
+open Collect
|
||
|
+
|
||
|
+module C = Libvirt.Connect
|
||
|
+module D = Libvirt.Domain
|
||
|
+
|
||
|
+let append_stream (_, _, _, _, _, node_info, hostname, _) (* setup *)
|
||
|
+ block_in_bytes
|
||
|
+ { rd_doms = doms;
|
||
|
+ rd_printable_time = printable_time;
|
||
|
+ rd_nr_pcpus = nr_pcpus; rd_total_cpu = total_cpu;
|
||
|
+ rd_totals = totals } (* state *) =
|
||
|
+ (* Header for this iteration *)
|
||
|
+ printf "virt-top time %s Host %s %s %d/%dCPU %dMHz %LdMB \n"
|
||
|
+ printable_time hostname node_info.C.model node_info.C.cpus nr_pcpus
|
||
|
+ node_info.C.mhz (node_info.C.memory /^ 1024L);
|
||
|
+ (* dump domain information one by one *)
|
||
|
+ let rd, wr = if block_in_bytes then "RDBY", "WRBY" else "RDRQ", "WRRQ"
|
||
|
+ in
|
||
|
+ printf " ID S %s %s RXBY TXBY %%CPU %%MEM TIME NAME\n" rd wr;
|
||
|
+
|
||
|
+ (* sort by ID *)
|
||
|
+ let doms =
|
||
|
+ let compare =
|
||
|
+ (function
|
||
|
+ | Active {rd_domid = id1 }, Active {rd_domid = id2} ->
|
||
|
+ compare id1 id2
|
||
|
+ | Active _, Inactive -> -1
|
||
|
+ | Inactive, Active _ -> 1
|
||
|
+ | Inactive, Inactive -> 0)
|
||
|
+ in
|
||
|
+ let cmp (name1, dom1) (name2, dom2) = compare(dom1, dom2) in
|
||
|
+ List.sort ~cmp doms in
|
||
|
+ (*Print domains *)
|
||
|
+ let dump_domain = fun name rd
|
||
|
+ -> begin
|
||
|
+ let state = Screen.show_state rd.rd_info.D.state in
|
||
|
+ let rd_req = if rd.rd_block_rd_info = None then " 0"
|
||
|
+ else Show.int64_option rd.rd_block_rd_info in
|
||
|
+ let wr_req = if rd.rd_block_wr_info = None then " 0"
|
||
|
+ else Show.int64_option rd.rd_block_wr_info in
|
||
|
+ let rx_bytes = if rd.rd_net_rx_bytes = None then " 0"
|
||
|
+ else Show.int64_option rd.rd_net_rx_bytes in
|
||
|
+ let tx_bytes = if rd.rd_net_tx_bytes = None then " 0"
|
||
|
+ else Show.int64_option rd.rd_net_tx_bytes in
|
||
|
+ let percent_cpu = Show.percent rd.rd_percent_cpu in
|
||
|
+ let percent_mem = Int64.to_float rd.rd_mem_percent in
|
||
|
+ let percent_mem = Show.percent percent_mem in
|
||
|
+ let time = Show.time rd.rd_info.D.cpu_time in
|
||
|
+ printf "%5d %c %s %s %s %s %s %s %s %s\n"
|
||
|
+ rd.rd_domid state rd_req wr_req rx_bytes tx_bytes
|
||
|
+ percent_cpu percent_mem time name;
|
||
|
+ end
|
||
|
+ in
|
||
|
+ List.iter (
|
||
|
+ function
|
||
|
+ | name, Active dom -> dump_domain name dom
|
||
|
+ | name, Inactive -> ()
|
||
|
+ ) doms;
|
||
|
+ flush stdout
|
||
|
diff --git a/src/stream_output.mli b/src/stream_output.mli
|
||
|
new file mode 100644
|
||
|
index 0000000..c45e548
|
||
|
--- /dev/null
|
||
|
+++ b/src/stream_output.mli
|
||
|
@@ -0,0 +1,22 @@
|
||
|
+(* 'top'-like tool for libvirt domains.
|
||
|
+ (C) Copyright 2007-2017 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.
|
||
|
+*)
|
||
|
+
|
||
|
+(** [--stream] mode output functions. *)
|
||
|
+
|
||
|
+val append_stream : Types.setup -> bool -> Collect.stats -> unit
|
||
|
diff --git a/src/top.ml b/src/top.ml
|
||
|
index f50e6a8..204f3b6 100644
|
||
|
--- a/src/top.ml
|
||
|
+++ b/src/top.ml
|
||
|
@@ -1,5 +1,5 @@
|
||
|
(* 'top'-like tool for libvirt domains.
|
||
|
- (C) Copyright 2007-2009 Richard W.M. Jones, Red Hat Inc.
|
||
|
+ (C) Copyright 2007-2017 Richard W.M. Jones, Red Hat Inc.
|
||
|
http://libvirt.org/
|
||
|
|
||
|
This program is free software; you can redistribute it and/or modify
|
||
|
@@ -23,6 +23,9 @@ open Curses
|
||
|
|
||
|
open Opt_gettext.Gettext
|
||
|
open Utils
|
||
|
+open Types
|
||
|
+open Collect
|
||
|
+open Screen
|
||
|
|
||
|
module C = Libvirt.Connect
|
||
|
module D = Libvirt.Domain
|
||
|
@@ -30,21 +33,11 @@ module N = Libvirt.Network
|
||
|
|
||
|
let rcfile = ".virt-toprc"
|
||
|
|
||
|
-(* Hook for XML support (see [opt_xml.ml]). *)
|
||
|
-let parse_device_xml : (int -> [>`R] D.t -> string list * string list) ref =
|
||
|
- ref (
|
||
|
- fun _ _ -> [], []
|
||
|
- )
|
||
|
-
|
||
|
(* Hooks for CSV support (see [opt_csv.ml]). *)
|
||
|
let csv_start : (string -> unit) ref =
|
||
|
ref (
|
||
|
fun _ -> failwith (s_"virt-top was compiled without support for CSV files")
|
||
|
)
|
||
|
-let csv_write : (string list -> unit) ref =
|
||
|
- ref (
|
||
|
- fun _ -> ()
|
||
|
- )
|
||
|
|
||
|
(* Hook for calendar support (see [opt_calendar.ml]). *)
|
||
|
let parse_date_time : (string -> float) ref =
|
||
|
@@ -53,62 +46,6 @@ let parse_date_time : (string -> float) ref =
|
||
|
failwith (s_"virt-top was compiled without support for dates and times")
|
||
|
)
|
||
|
|
||
|
-(* Sort order. *)
|
||
|
-type sort_order =
|
||
|
- | DomainID | DomainName | Processor | Memory | Time
|
||
|
- | NetRX | NetTX | BlockRdRq | BlockWrRq
|
||
|
-let all_sort_fields = [
|
||
|
- DomainID; DomainName; Processor; Memory; Time;
|
||
|
- NetRX; NetTX; BlockRdRq; BlockWrRq
|
||
|
-]
|
||
|
-let printable_sort_order = function
|
||
|
- | Processor -> s_"%CPU"
|
||
|
- | Memory -> s_"%MEM"
|
||
|
- | Time -> s_"TIME (CPU time)"
|
||
|
- | DomainID -> s_"Domain ID"
|
||
|
- | DomainName -> s_"Domain name"
|
||
|
- | NetRX -> s_"Net RX bytes"
|
||
|
- | NetTX -> s_"Net TX bytes"
|
||
|
- | BlockRdRq -> s_"Block read reqs"
|
||
|
- | BlockWrRq -> s_"Block write reqs"
|
||
|
-let sort_order_of_cli = function
|
||
|
- | "cpu" | "processor" -> Processor
|
||
|
- | "mem" | "memory" -> Memory
|
||
|
- | "time" -> Time
|
||
|
- | "id" -> DomainID
|
||
|
- | "name" -> DomainName
|
||
|
- | "netrx" -> NetRX | "nettx" -> NetTX
|
||
|
- | "blockrdrq" -> BlockRdRq | "blockwrrq" -> BlockWrRq
|
||
|
- | str ->
|
||
|
- failwithf (f_"%s: sort order should be: %s")
|
||
|
- str "cpu|mem|time|id|name|netrx|nettx|blockrdrq|blockwrrq"
|
||
|
-let cli_of_sort_order = function
|
||
|
- | Processor -> "cpu"
|
||
|
- | Memory -> "mem"
|
||
|
- | Time -> "time"
|
||
|
- | DomainID -> "id"
|
||
|
- | DomainName -> "name"
|
||
|
- | NetRX -> "netrx"
|
||
|
- | NetTX -> "nettx"
|
||
|
- | BlockRdRq -> "blockrdrq"
|
||
|
- | BlockWrRq -> "blockwrrq"
|
||
|
-
|
||
|
-(* Current major display mode: TaskDisplay is the normal display. *)
|
||
|
-type display = TaskDisplay | PCPUDisplay | BlockDisplay | NetDisplay
|
||
|
-
|
||
|
-let display_of_cli = function
|
||
|
- | "task" -> TaskDisplay
|
||
|
- | "pcpu" -> PCPUDisplay
|
||
|
- | "block" -> BlockDisplay
|
||
|
- | "net" -> NetDisplay
|
||
|
- | str ->
|
||
|
- failwithf (f_"%s: display should be %s") str "task|pcpu|block|net"
|
||
|
-let cli_of_display = function
|
||
|
- | TaskDisplay -> "task"
|
||
|
- | PCPUDisplay -> "pcpu"
|
||
|
- | BlockDisplay -> "block"
|
||
|
- | NetDisplay -> "net"
|
||
|
-
|
||
|
(* Init file. *)
|
||
|
type init_file = NoInitFile | DefaultInitFile | InitFile of string
|
||
|
|
||
|
@@ -134,11 +71,6 @@ let script_mode = ref false
|
||
|
let stream_mode = ref false
|
||
|
let block_in_bytes = ref false
|
||
|
|
||
|
-(* Tuple of never-changing data returned by start_up function. *)
|
||
|
-type setup =
|
||
|
- Libvirt.ro C.t * bool * bool * bool * bool * C.node_info * string *
|
||
|
- (int * int * int)
|
||
|
-
|
||
|
(* Function to read command line arguments and go into curses mode. *)
|
||
|
let start_up () =
|
||
|
(* Read command line arguments. *)
|
||
|
@@ -352,16 +284,6 @@ OPTIONS" in
|
||
|
node_info, hostname, libvirt_version (* info that doesn't change *)
|
||
|
)
|
||
|
|
||
|
-(* Show a domain state (the 'S' column). *)
|
||
|
-let show_state = function
|
||
|
- | D.InfoNoState -> '?'
|
||
|
- | D.InfoRunning -> 'R'
|
||
|
- | D.InfoBlocked -> 'S'
|
||
|
- | D.InfoPaused -> 'P'
|
||
|
- | D.InfoShutdown -> 'D'
|
||
|
- | D.InfoShutoff -> 'O'
|
||
|
- | D.InfoCrashed -> 'X'
|
||
|
-
|
||
|
(* Sleep in seconds. *)
|
||
|
let sleep = Unix.sleep
|
||
|
|
||
|
@@ -387,1039 +309,33 @@ let get_string maxlen =
|
||
|
Not_found -> str (* it is full maxlen bytes *)
|
||
|
)
|
||
|
|
||
|
-(* Line numbers. *)
|
||
|
-let top_lineno = 0
|
||
|
-let summary_lineno = 1 (* this takes 2 lines *)
|
||
|
-let message_lineno = 3
|
||
|
-let header_lineno = 4
|
||
|
-let domains_lineno = 5
|
||
|
-
|
||
|
-(* Easier to use versions of curses functions addstr, mvaddstr, etc. *)
|
||
|
-let move y x = ignore (move y x)
|
||
|
-let refresh () = ignore (refresh ())
|
||
|
-let addch c = ignore (addch (int_of_char c))
|
||
|
-let addstr s = ignore (addstr s)
|
||
|
-let mvaddstr y x s = ignore (mvaddstr y x s)
|
||
|
-
|
||
|
-(* Print in the "message area". *)
|
||
|
-let clear_msg () = move message_lineno 0; clrtoeol ()
|
||
|
-let print_msg str = clear_msg (); mvaddstr message_lineno 0 str
|
||
|
-
|
||
|
-(* Intermediate "domain + stats" structure that we use to collect
|
||
|
- * everything we know about a domain within the collect function.
|
||
|
- *)
|
||
|
-type rd_domain = Inactive | Active of rd_active
|
||
|
-and rd_active = {
|
||
|
- rd_domid : int; (* Domain ID. *)
|
||
|
- rd_dom : [`R] D.t; (* Domain object. *)
|
||
|
- rd_info : D.info; (* Domain CPU info now. *)
|
||
|
- rd_block_stats : (string * D.block_stats) list;
|
||
|
- (* Domain block stats now. *)
|
||
|
- rd_interface_stats : (string * D.interface_stats) list;
|
||
|
- (* Domain net stats now. *)
|
||
|
- rd_prev_info : D.info option; (* Domain CPU info previously. *)
|
||
|
- rd_prev_block_stats : (string * D.block_stats) list;
|
||
|
- (* Domain block stats prev. *)
|
||
|
- rd_prev_interface_stats : (string * D.interface_stats) list;
|
||
|
- (* Domain interface stats prev. *)
|
||
|
- (* The following are since the last slice, or 0 if cannot be calculated: *)
|
||
|
- rd_cpu_time : float; (* CPU time used in nanoseconds. *)
|
||
|
- rd_percent_cpu : float; (* CPU time as percent of total. *)
|
||
|
- rd_mem_bytes : int64; (* Memory usage in bytes *)
|
||
|
- rd_mem_percent: int64; (* Memory usage as percent of total *)
|
||
|
- (* The following are since the last slice, or None if cannot be calc'd: *)
|
||
|
- rd_block_rd_reqs : int64 option; (* Number of block device read rqs. *)
|
||
|
- rd_block_wr_reqs : int64 option; (* Number of block device write rqs. *)
|
||
|
- rd_block_rd_bytes : int64 option; (* Number of bytes block device read *)
|
||
|
- rd_block_wr_bytes : int64 option; (* Number of bytes block device write *)
|
||
|
- (* _info fields includes the number considering --block_in_bytes option *)
|
||
|
- rd_block_rd_info : int64 option; (* Block device read info for user *)
|
||
|
- rd_block_wr_info : int64 option; (* Block device read info for user *)
|
||
|
-
|
||
|
- rd_net_rx_bytes : int64 option; (* Number of bytes received. *)
|
||
|
- rd_net_tx_bytes : int64 option; (* Number of bytes transmitted. *)
|
||
|
-}
|
||
|
-
|
||
|
-(* Collect stats. *)
|
||
|
-let collect, clear_pcpu_display_data =
|
||
|
- (* We cache the list of block devices and interfaces for each domain
|
||
|
- * here, so we don't need to reparse the XML each time.
|
||
|
- *)
|
||
|
- let devices = Hashtbl.create 13 in
|
||
|
-
|
||
|
- (* Function to get the list of block devices, network interfaces for
|
||
|
- * a particular domain. Get it from the devices cache, and if not
|
||
|
- * there then parse the domain XML.
|
||
|
- *)
|
||
|
- let get_devices id dom =
|
||
|
- try Hashtbl.find devices id
|
||
|
- with Not_found ->
|
||
|
- let blkdevs, netifs = (!parse_device_xml) id dom in
|
||
|
- Hashtbl.replace devices id (blkdevs, netifs);
|
||
|
- blkdevs, netifs
|
||
|
- in
|
||
|
-
|
||
|
- (* We save the state of domains across redraws here, which allows us
|
||
|
- * to deduce %CPU usage from the running total.
|
||
|
- *)
|
||
|
- let last_info = Hashtbl.create 13 in
|
||
|
- let last_time = ref (Unix.gettimeofday ()) in
|
||
|
-
|
||
|
- (* Save pcpu_usages structures across redraws too (only for pCPU display). *)
|
||
|
- let last_pcpu_usages = Hashtbl.create 13 in
|
||
|
-
|
||
|
- let clear_pcpu_display_data () =
|
||
|
- (* Clear out pcpu_usages used by PCPUDisplay display_mode
|
||
|
- * when we switch back to TaskDisplay mode.
|
||
|
- *)
|
||
|
- Hashtbl.clear last_pcpu_usages
|
||
|
- in
|
||
|
-
|
||
|
- let collect (conn, _, _, _, _, node_info, _, _) =
|
||
|
- (* Number of physical CPUs (some may be disabled). *)
|
||
|
- let nr_pcpus = C.maxcpus_of_node_info node_info in
|
||
|
-
|
||
|
- (* Get the current time. *)
|
||
|
- let time = Unix.gettimeofday () in
|
||
|
- let tm = Unix.localtime time in
|
||
|
- let printable_time =
|
||
|
- sprintf "%02d:%02d:%02d" tm.Unix.tm_hour tm.Unix.tm_min tm.Unix.tm_sec in
|
||
|
-
|
||
|
- (* What's the total CPU time elapsed since we were last called? (ns) *)
|
||
|
- let total_cpu_per_pcpu = 1_000_000_000. *. (time -. !last_time) in
|
||
|
- (* Avoid division by zero. *)
|
||
|
- let total_cpu_per_pcpu =
|
||
|
- if total_cpu_per_pcpu <= 0. then 1. else total_cpu_per_pcpu in
|
||
|
- let total_cpu = float node_info.C.cpus *. total_cpu_per_pcpu in
|
||
|
-
|
||
|
- (* Get the domains. Match up with their last_info (if any). *)
|
||
|
- let doms =
|
||
|
- (* Active domains. *)
|
||
|
- let n = C.num_of_domains conn in
|
||
|
- let ids =
|
||
|
- if n > 0 then Array.to_list (C.list_domains conn n)
|
||
|
- else [] in
|
||
|
- let doms =
|
||
|
- List.filter_map (
|
||
|
- fun id ->
|
||
|
- try
|
||
|
- let dom = D.lookup_by_id conn id in
|
||
|
- let name = D.get_name dom in
|
||
|
- let blkdevs, netifs = get_devices id dom in
|
||
|
-
|
||
|
- (* Get current CPU, block and network stats. *)
|
||
|
- let info = D.get_info dom in
|
||
|
- let block_stats =
|
||
|
- try List.map (fun dev -> dev, D.block_stats dom dev) blkdevs
|
||
|
- with
|
||
|
- | Libvirt.Not_supported "virDomainBlockStats"
|
||
|
- | Libvirt.Virterror _ -> [] in
|
||
|
- let interface_stats =
|
||
|
- try List.map (fun dev -> dev, D.interface_stats dom dev) netifs
|
||
|
- with
|
||
|
- | Libvirt.Not_supported "virDomainInterfaceStats"
|
||
|
- | Libvirt.Virterror _ -> [] in
|
||
|
-
|
||
|
- let prev_info, prev_block_stats, prev_interface_stats =
|
||
|
- try
|
||
|
- let prev_info, prev_block_stats, prev_interface_stats =
|
||
|
- Hashtbl.find last_info id in
|
||
|
- Some prev_info, prev_block_stats, prev_interface_stats
|
||
|
- with Not_found -> None, [], [] in
|
||
|
-
|
||
|
- Some (name, Active {
|
||
|
- rd_domid = id; rd_dom = dom; rd_info = info;
|
||
|
- rd_block_stats = block_stats;
|
||
|
- rd_interface_stats = interface_stats;
|
||
|
- rd_prev_info = prev_info;
|
||
|
- rd_prev_block_stats = prev_block_stats;
|
||
|
- rd_prev_interface_stats = prev_interface_stats;
|
||
|
- rd_cpu_time = 0.; rd_percent_cpu = 0.;
|
||
|
- rd_mem_bytes = 0L; rd_mem_percent = 0L;
|
||
|
- rd_block_rd_reqs = None; rd_block_wr_reqs = None;
|
||
|
- rd_block_rd_bytes = None; rd_block_wr_bytes = None;
|
||
|
- rd_block_rd_info = None; rd_block_wr_info = None;
|
||
|
- rd_net_rx_bytes = None; rd_net_tx_bytes = None;
|
||
|
- })
|
||
|
- with
|
||
|
- Libvirt.Virterror _ -> None (* ignore transient error *)
|
||
|
- ) ids in
|
||
|
-
|
||
|
- (* Inactive domains. *)
|
||
|
- let doms_inactive =
|
||
|
- try
|
||
|
- let n = C.num_of_defined_domains conn in
|
||
|
- let names =
|
||
|
- if n > 0 then Array.to_list (C.list_defined_domains conn n)
|
||
|
- else [] in
|
||
|
- List.map (fun name -> name, Inactive) names
|
||
|
- with
|
||
|
- (* Ignore transient errors, in particular errors from
|
||
|
- * num_of_defined_domains if it cannot contact xend.
|
||
|
- *)
|
||
|
- | Libvirt.Virterror _ -> [] in
|
||
|
-
|
||
|
- doms @ doms_inactive in
|
||
|
-
|
||
|
- (* Calculate the CPU time (ns) and %CPU used by each domain. *)
|
||
|
- let doms =
|
||
|
- List.map (
|
||
|
- function
|
||
|
- (* We have previous CPU info from which to calculate it? *)
|
||
|
- | name, Active ({ rd_prev_info = Some prev_info } as rd) ->
|
||
|
- let cpu_time =
|
||
|
- Int64.to_float (rd.rd_info.D.cpu_time -^ prev_info.D.cpu_time) in
|
||
|
- let percent_cpu = 100. *. cpu_time /. total_cpu in
|
||
|
- let mem_usage = rd.rd_info.D.memory in
|
||
|
- let mem_percent =
|
||
|
- 100L *^ rd.rd_info.D.memory /^ node_info.C.memory in
|
||
|
- let rd = { rd with
|
||
|
- rd_cpu_time = cpu_time;
|
||
|
- rd_percent_cpu = percent_cpu;
|
||
|
- rd_mem_bytes = mem_usage;
|
||
|
- rd_mem_percent = mem_percent} in
|
||
|
- name, Active rd
|
||
|
- (* For all other domains we can't calculate it, so leave as 0 *)
|
||
|
- | rd -> rd
|
||
|
- ) doms in
|
||
|
-
|
||
|
- (* Calculate the number of block device read/write requests across
|
||
|
- * all block devices attached to a domain.
|
||
|
- *)
|
||
|
- let doms =
|
||
|
- List.map (
|
||
|
- function
|
||
|
- (* Do we have stats from the previous slice? *)
|
||
|
- | name, Active ({ rd_prev_block_stats = ((_::_) as prev_block_stats) }
|
||
|
- as rd) ->
|
||
|
- let block_stats = rd.rd_block_stats in (* stats now *)
|
||
|
-
|
||
|
- (* Add all the devices together. Throw away device names. *)
|
||
|
- let prev_block_stats =
|
||
|
- sum_block_stats (List.map snd prev_block_stats) in
|
||
|
- let block_stats =
|
||
|
- sum_block_stats (List.map snd block_stats) in
|
||
|
-
|
||
|
- (* Calculate increase in read & write requests. *)
|
||
|
- let read_reqs =
|
||
|
- block_stats.D.rd_req -^ prev_block_stats.D.rd_req in
|
||
|
- let write_reqs =
|
||
|
- block_stats.D.wr_req -^ prev_block_stats.D.wr_req in
|
||
|
- let read_bytes =
|
||
|
- block_stats.D.rd_bytes -^ prev_block_stats.D.rd_bytes in
|
||
|
- let write_bytes =
|
||
|
- block_stats.D.wr_bytes -^ prev_block_stats.D.wr_bytes in
|
||
|
-
|
||
|
- let rd = { rd with
|
||
|
- rd_block_rd_reqs = Some read_reqs;
|
||
|
- rd_block_wr_reqs = Some write_reqs;
|
||
|
- rd_block_rd_bytes = Some read_bytes;
|
||
|
- rd_block_wr_bytes = Some write_bytes;
|
||
|
- } in
|
||
|
- let rd = { rd with
|
||
|
- rd_block_rd_info = if !block_in_bytes then
|
||
|
- rd.rd_block_rd_bytes else rd.rd_block_rd_reqs;
|
||
|
- rd_block_wr_info = if !block_in_bytes then
|
||
|
- rd.rd_block_wr_bytes else rd.rd_block_wr_reqs;
|
||
|
- } in
|
||
|
- name, Active rd
|
||
|
- (* For all other domains we can't calculate it, so leave as None. *)
|
||
|
- | rd -> rd
|
||
|
- ) doms in
|
||
|
-
|
||
|
- (* Calculate the same as above for network interfaces across
|
||
|
- * all network interfaces attached to a domain.
|
||
|
- *)
|
||
|
- let doms =
|
||
|
- List.map (
|
||
|
- function
|
||
|
- (* Do we have stats from the previous slice? *)
|
||
|
- | name, Active ({ rd_prev_interface_stats =
|
||
|
- ((_::_) as prev_interface_stats) }
|
||
|
- as rd) ->
|
||
|
- let interface_stats = rd.rd_interface_stats in (* stats now *)
|
||
|
-
|
||
|
- (* Add all the devices together. Throw away device names. *)
|
||
|
- let prev_interface_stats =
|
||
|
- sum_interface_stats (List.map snd prev_interface_stats) in
|
||
|
- let interface_stats =
|
||
|
- sum_interface_stats (List.map snd interface_stats) in
|
||
|
-
|
||
|
- (* Calculate increase in rx & tx bytes. *)
|
||
|
- let rx_bytes =
|
||
|
- interface_stats.D.rx_bytes -^ prev_interface_stats.D.rx_bytes in
|
||
|
- let tx_bytes =
|
||
|
- interface_stats.D.tx_bytes -^ prev_interface_stats.D.tx_bytes in
|
||
|
-
|
||
|
- let rd = { rd with
|
||
|
- rd_net_rx_bytes = Some rx_bytes;
|
||
|
- rd_net_tx_bytes = Some tx_bytes } in
|
||
|
- name, Active rd
|
||
|
- (* For all other domains we can't calculate it, so leave as None. *)
|
||
|
- | rd -> rd
|
||
|
- ) doms in
|
||
|
-
|
||
|
- (* Collect some extra information in PCPUDisplay display_mode. *)
|
||
|
- let pcpu_display =
|
||
|
- if !display_mode = PCPUDisplay then (
|
||
|
- (* Get the VCPU info and VCPU->PCPU mappings for active domains.
|
||
|
- * Also cull some data we don't care about.
|
||
|
- *)
|
||
|
- let doms = List.filter_map (
|
||
|
- function
|
||
|
- | (name, Active rd) ->
|
||
|
- (try
|
||
|
- let domid = rd.rd_domid in
|
||
|
- let maplen = C.cpumaplen nr_pcpus in
|
||
|
- let cpu_stats = D.get_cpu_stats rd.rd_dom in
|
||
|
-
|
||
|
- (* Note the terminology is confusing.
|
||
|
- *
|
||
|
- * In libvirt, cpu_time is the total time (hypervisor + vCPU).
|
||
|
- * vcpu_time is the time only taken by the vCPU,
|
||
|
- * excluding time taken inside the hypervisor.
|
||
|
- *
|
||
|
- * For each pCPU, libvirt may return either "cpu_time"
|
||
|
- * or "vcpu_time" or neither or both. This function
|
||
|
- * returns an array pair [|cpu_time, vcpu_time|];
|
||
|
- * if either is missing it is returned as 0.
|
||
|
- *)
|
||
|
- let find_cpu_usages params =
|
||
|
- let rec find_uint64_field name = function
|
||
|
- | (n, D.TypedFieldUInt64 usage) :: _ when n = name -> usage
|
||
|
- | _ :: params -> find_uint64_field name params
|
||
|
- | [] -> 0L
|
||
|
- in
|
||
|
- [| find_uint64_field "cpu_time" params;
|
||
|
- find_uint64_field "vcpu_time" params |]
|
||
|
- in
|
||
|
-
|
||
|
- let pcpu_usages = Array.map find_cpu_usages cpu_stats in
|
||
|
- let maxinfo = rd.rd_info.D.nr_virt_cpu in
|
||
|
- let nr_vcpus, vcpu_infos, cpumaps =
|
||
|
- D.get_vcpus rd.rd_dom maxinfo maplen in
|
||
|
-
|
||
|
- (* Got previous pcpu_usages for this domain? *)
|
||
|
- let prev_pcpu_usages =
|
||
|
- try Some (Hashtbl.find last_pcpu_usages domid)
|
||
|
- with Not_found -> None in
|
||
|
- (* Update last_pcpu_usages. *)
|
||
|
- Hashtbl.replace last_pcpu_usages domid pcpu_usages;
|
||
|
-
|
||
|
- (match prev_pcpu_usages with
|
||
|
- | Some prev_pcpu_usages
|
||
|
- when Array.length prev_pcpu_usages = Array.length pcpu_usages ->
|
||
|
- Some (domid, name, nr_vcpus, vcpu_infos, pcpu_usages,
|
||
|
- prev_pcpu_usages, cpumaps, maplen)
|
||
|
- | _ -> None (* ignore missing / unequal length prev_vcpu_infos *)
|
||
|
- );
|
||
|
- with
|
||
|
- Libvirt.Virterror _ -> None(* ignore transient libvirt errs *)
|
||
|
- )
|
||
|
- | (_, Inactive) -> None (* ignore inactive doms *)
|
||
|
- ) doms in
|
||
|
- let nr_doms = List.length doms in
|
||
|
-
|
||
|
- (* Rearrange the data into a matrix. Major axis (down) is
|
||
|
- * pCPUs. Minor axis (right) is domains. At each node we store:
|
||
|
- * cpu_time hypervisor + domain (on this pCPU only, nanosecs),
|
||
|
- * vcpu_time domain only (on this pCPU only, nanosecs).
|
||
|
- *)
|
||
|
- let make_3d_array dimx dimy dimz e =
|
||
|
- Array.init dimx (fun _ -> Array.make_matrix dimy dimz e)
|
||
|
- in
|
||
|
- let pcpus = make_3d_array nr_pcpus nr_doms 2 0L in
|
||
|
-
|
||
|
- List.iteri (
|
||
|
- fun di (domid, name, nr_vcpus, vcpu_infos, pcpu_usages,
|
||
|
- prev_pcpu_usages, cpumaps, maplen) ->
|
||
|
- (* Which pCPUs can this dom run on? *)
|
||
|
- for p = 0 to Array.length pcpu_usages - 1 do
|
||
|
- pcpus.(p).(di).(0) <-
|
||
|
- pcpu_usages.(p).(0) -^ prev_pcpu_usages.(p).(0);
|
||
|
- pcpus.(p).(di).(1) <-
|
||
|
- pcpu_usages.(p).(1) -^ prev_pcpu_usages.(p).(1)
|
||
|
- done
|
||
|
- ) doms;
|
||
|
-
|
||
|
- (* Sum the total CPU time used by each pCPU, for the %CPU column. *)
|
||
|
- let pcpus_cpu_time = Array.map (
|
||
|
- fun row ->
|
||
|
- let cpu_time = ref 0L in
|
||
|
- for di = 0 to Array.length row-1 do
|
||
|
- let t = row.(di).(0) in
|
||
|
- cpu_time := !cpu_time +^ t
|
||
|
- done;
|
||
|
- Int64.to_float !cpu_time
|
||
|
- ) pcpus in
|
||
|
-
|
||
|
- Some (doms, pcpus, pcpus_cpu_time)
|
||
|
- ) else
|
||
|
- None in
|
||
|
-
|
||
|
- (* Calculate totals. *)
|
||
|
- let totals = List.fold_left (
|
||
|
- fun (count, running, blocked, paused, shutdown, shutoff,
|
||
|
- crashed, active, inactive,
|
||
|
- total_cpu_time, total_memory, total_domU_memory) ->
|
||
|
- function
|
||
|
- | (name, Active rd) ->
|
||
|
- let test state orig =
|
||
|
- if rd.rd_info.D.state = state then orig+1 else orig
|
||
|
- in
|
||
|
- let running = test D.InfoRunning running in
|
||
|
- let blocked = test D.InfoBlocked blocked in
|
||
|
- let paused = test D.InfoPaused paused in
|
||
|
- let shutdown = test D.InfoShutdown shutdown in
|
||
|
- let shutoff = test D.InfoShutoff shutoff in
|
||
|
- let crashed = test D.InfoCrashed crashed in
|
||
|
-
|
||
|
- let total_cpu_time = total_cpu_time +. rd.rd_cpu_time in
|
||
|
- let total_memory = total_memory +^ rd.rd_info.D.memory in
|
||
|
- let total_domU_memory = total_domU_memory +^
|
||
|
- if rd.rd_domid > 0 then rd.rd_info.D.memory else 0L in
|
||
|
-
|
||
|
- (count+1, running, blocked, paused, shutdown, shutoff,
|
||
|
- crashed, active+1, inactive,
|
||
|
- total_cpu_time, total_memory, total_domU_memory)
|
||
|
-
|
||
|
- | (name, Inactive) -> (* inactive domain *)
|
||
|
- (count+1, running, blocked, paused, shutdown, shutoff,
|
||
|
- crashed, active, inactive+1,
|
||
|
- total_cpu_time, total_memory, total_domU_memory)
|
||
|
- ) (0,0,0,0,0,0,0,0,0, 0.,0L,0L) doms in
|
||
|
-
|
||
|
- (* Update last_time, last_info. *)
|
||
|
- last_time := time;
|
||
|
- Hashtbl.clear last_info;
|
||
|
- List.iter (
|
||
|
- function
|
||
|
- | (_, Active rd) ->
|
||
|
- let info = rd.rd_info, rd.rd_block_stats, rd.rd_interface_stats in
|
||
|
- Hashtbl.add last_info rd.rd_domid info
|
||
|
- | _ -> ()
|
||
|
- ) doms;
|
||
|
-
|
||
|
- (doms,
|
||
|
- time, printable_time,
|
||
|
- nr_pcpus, total_cpu, total_cpu_per_pcpu,
|
||
|
- totals,
|
||
|
- pcpu_display)
|
||
|
- in
|
||
|
-
|
||
|
- collect, clear_pcpu_display_data
|
||
|
-
|
||
|
-(* Redraw the display. *)
|
||
|
-let redraw =
|
||
|
- (* Keep a historical list of %CPU usages. *)
|
||
|
- let historical_cpu = ref [] in
|
||
|
- let historical_cpu_last_time = ref (Unix.gettimeofday ()) in
|
||
|
- fun
|
||
|
- (_, _, _, _, _, node_info, _, _) (* setup *)
|
||
|
- (doms,
|
||
|
- time, printable_time,
|
||
|
- nr_pcpus, total_cpu, total_cpu_per_pcpu,
|
||
|
- totals,
|
||
|
- pcpu_display) (* state *) ->
|
||
|
- clear ();
|
||
|
-
|
||
|
- (* Get the screen/window size. *)
|
||
|
- let lines, cols = get_size () in
|
||
|
-
|
||
|
- (* Time. *)
|
||
|
- mvaddstr top_lineno 0 (sprintf "virt-top %s - " printable_time);
|
||
|
-
|
||
|
- (* Basic node_info. *)
|
||
|
- addstr
|
||
|
- (sprintf "%s %d/%dCPU %dMHz %LdMB "
|
||
|
- node_info.C.model node_info.C.cpus nr_pcpus node_info.C.mhz
|
||
|
- (node_info.C.memory /^ 1024L));
|
||
|
- (* Save the cursor position for when we come to draw the
|
||
|
- * historical CPU times (down in this function).
|
||
|
- *)
|
||
|
- let stdscr = stdscr () in
|
||
|
- let historical_cursor = getyx stdscr in
|
||
|
-
|
||
|
- (match !display_mode with
|
||
|
- | TaskDisplay -> (*---------- Showing domains ----------*)
|
||
|
- (* Sort domains on current sort_order. *)
|
||
|
- let doms =
|
||
|
- let cmp =
|
||
|
- match !sort_order with
|
||
|
- | DomainName ->
|
||
|
- (fun _ -> 0) (* fallthrough to default name compare *)
|
||
|
- | Processor ->
|
||
|
- (function
|
||
|
- | Active rd1, Active rd2 ->
|
||
|
- compare rd2.rd_percent_cpu rd1.rd_percent_cpu
|
||
|
- | Active _, Inactive -> -1
|
||
|
- | Inactive, Active _ -> 1
|
||
|
- | Inactive, Inactive -> 0)
|
||
|
- | Memory ->
|
||
|
- (function
|
||
|
- | Active { rd_info = info1 }, Active { rd_info = info2 } ->
|
||
|
- compare info2.D.memory info1.D.memory
|
||
|
- | Active _, Inactive -> -1
|
||
|
- | Inactive, Active _ -> 1
|
||
|
- | Inactive, Inactive -> 0)
|
||
|
- | Time ->
|
||
|
- (function
|
||
|
- | Active { rd_info = info1 }, Active { rd_info = info2 } ->
|
||
|
- compare info2.D.cpu_time info1.D.cpu_time
|
||
|
- | Active _, Inactive -> -1
|
||
|
- | Inactive, Active _ -> 1
|
||
|
- | Inactive, Inactive -> 0)
|
||
|
- | DomainID ->
|
||
|
- (function
|
||
|
- | Active { rd_domid = id1 }, Active { rd_domid = id2 } ->
|
||
|
- compare id1 id2
|
||
|
- | Active _, Inactive -> -1
|
||
|
- | Inactive, Active _ -> 1
|
||
|
- | Inactive, Inactive -> 0)
|
||
|
- | NetRX ->
|
||
|
- (function
|
||
|
- | Active { rd_net_rx_bytes = r1 }, Active { rd_net_rx_bytes = r2 } ->
|
||
|
- compare r2 r1
|
||
|
- | Active _, Inactive -> -1
|
||
|
- | Inactive, Active _ -> 1
|
||
|
- | Inactive, Inactive -> 0)
|
||
|
- | NetTX ->
|
||
|
- (function
|
||
|
- | Active { rd_net_tx_bytes = r1 }, Active { rd_net_tx_bytes = r2 } ->
|
||
|
- compare r2 r1
|
||
|
- | Active _, Inactive -> -1
|
||
|
- | Inactive, Active _ -> 1
|
||
|
- | Inactive, Inactive -> 0)
|
||
|
- | BlockRdRq ->
|
||
|
- (function
|
||
|
- | Active { rd_block_rd_reqs = r1 }, Active { rd_block_rd_reqs = r2 } ->
|
||
|
- compare r2 r1
|
||
|
- | Active _, Inactive -> -1
|
||
|
- | Inactive, Active _ -> 1
|
||
|
- | Inactive, Inactive -> 0)
|
||
|
- | BlockWrRq ->
|
||
|
- (function
|
||
|
- | Active { rd_block_wr_reqs = r1 }, Active { rd_block_wr_reqs = r2 } ->
|
||
|
- compare r2 r1
|
||
|
- | Active _, Inactive -> -1
|
||
|
- | Inactive, Active _ -> 1
|
||
|
- | Inactive, Inactive -> 0)
|
||
|
- in
|
||
|
- let cmp (name1, dom1) (name2, dom2) =
|
||
|
- let r = cmp (dom1, dom2) in
|
||
|
- if r <> 0 then r
|
||
|
- else compare name1 name2
|
||
|
- in
|
||
|
- List.sort ~cmp doms in
|
||
|
-
|
||
|
- (* Print domains. *)
|
||
|
- attron A.reverse;
|
||
|
- let header_string = if !block_in_bytes
|
||
|
- then " ID S RDBY WRBY RXBY TXBY %CPU %MEM TIME NAME"
|
||
|
- else " ID S RDRQ WRRQ RXBY TXBY %CPU %MEM TIME NAME"
|
||
|
- in
|
||
|
- mvaddstr header_lineno 0
|
||
|
- (pad cols header_string);
|
||
|
- attroff A.reverse;
|
||
|
-
|
||
|
- let rec loop lineno = function
|
||
|
- | [] -> ()
|
||
|
- | (name, Active rd) :: doms ->
|
||
|
- if lineno < lines then (
|
||
|
- let state = show_state rd.rd_info.D.state in
|
||
|
- let rd_req = Show.int64_option rd.rd_block_rd_info in
|
||
|
- let wr_req = Show.int64_option rd.rd_block_wr_info in
|
||
|
- let rx_bytes = Show.int64_option rd.rd_net_rx_bytes in
|
||
|
- let tx_bytes = Show.int64_option rd.rd_net_tx_bytes in
|
||
|
- let percent_cpu = Show.percent rd.rd_percent_cpu in
|
||
|
- let percent_mem = Int64.to_float rd.rd_mem_percent in
|
||
|
- let percent_mem = Show.percent percent_mem in
|
||
|
- let time = Show.time rd.rd_info.D.cpu_time in
|
||
|
-
|
||
|
- let line = sprintf "%5d %c %s %s %s %s %s %s %s %s"
|
||
|
- rd.rd_domid state rd_req wr_req rx_bytes tx_bytes
|
||
|
- percent_cpu percent_mem time name in
|
||
|
- let line = pad cols line in
|
||
|
- mvaddstr lineno 0 line;
|
||
|
- loop (lineno+1) doms
|
||
|
- )
|
||
|
- | (name, Inactive) :: doms -> (* inactive domain *)
|
||
|
- if lineno < lines then (
|
||
|
- let line =
|
||
|
- sprintf
|
||
|
- " - (%s)"
|
||
|
- name in
|
||
|
- let line = pad cols line in
|
||
|
- mvaddstr lineno 0 line;
|
||
|
- loop (lineno+1) doms
|
||
|
- )
|
||
|
- in
|
||
|
- loop domains_lineno doms
|
||
|
-
|
||
|
- | PCPUDisplay -> (*---------- Showing physical CPUs ----------*)
|
||
|
- let doms, pcpus, pcpus_cpu_time =
|
||
|
- match pcpu_display with
|
||
|
- | Some p -> p
|
||
|
- | None -> failwith "internal error: no pcpu_display data" in
|
||
|
-
|
||
|
- (* Display the pCPUs. *)
|
||
|
- let dom_names =
|
||
|
- String.concat "" (
|
||
|
- List.map (
|
||
|
- fun (_, name, _, _, _, _, _, _) ->
|
||
|
- let len = String.length name in
|
||
|
- let width = max (len+1) 12 in
|
||
|
- pad width name
|
||
|
- ) doms
|
||
|
- ) in
|
||
|
- attron A.reverse;
|
||
|
- mvaddstr header_lineno 0 (pad cols ("PHYCPU %CPU " ^ dom_names));
|
||
|
- attroff A.reverse;
|
||
|
-
|
||
|
- Array.iteri (
|
||
|
- fun p row ->
|
||
|
- mvaddstr (p+domains_lineno) 0 (sprintf "%4d " p);
|
||
|
- let cpu_time = pcpus_cpu_time.(p) in (* ns used on this CPU *)
|
||
|
- let percent_cpu = 100. *. cpu_time /. total_cpu_per_pcpu in
|
||
|
- addstr (Show.percent percent_cpu);
|
||
|
- addch ' ';
|
||
|
-
|
||
|
- List.iteri (
|
||
|
- fun di (domid, name, _, _, _, _, _, _) ->
|
||
|
- let t = pcpus.(p).(di).(0) in (* hypervisor + domain *)
|
||
|
- let t_only = pcpus.(p).(di).(1) in (* domain only *)
|
||
|
- let len = String.length name in
|
||
|
- let width = max (len+1) 12 in
|
||
|
- let str_t =
|
||
|
- if t <= 0L then ""
|
||
|
- else (
|
||
|
- let t = Int64.to_float t in
|
||
|
- let percent = 100. *. t /. total_cpu_per_pcpu in
|
||
|
- Show.percent percent
|
||
|
- ) in
|
||
|
- let str_t_only =
|
||
|
- if t_only <= 0L then ""
|
||
|
- else (
|
||
|
- let t_only = Int64.to_float t_only in
|
||
|
- let percent = 100. *. t_only /. total_cpu_per_pcpu in
|
||
|
- Show.percent percent
|
||
|
- ) in
|
||
|
- addstr (pad 5 str_t);
|
||
|
- addstr (pad 5 str_t_only);
|
||
|
- addstr (pad (width-10) " ");
|
||
|
- ()
|
||
|
- ) doms
|
||
|
- ) pcpus;
|
||
|
-
|
||
|
- | NetDisplay -> (*---------- Showing network interfaces ----------*)
|
||
|
- (* Only care about active domains. *)
|
||
|
- let doms = List.filter_map (
|
||
|
- function
|
||
|
- | (name, Active rd) -> Some (name, rd)
|
||
|
- | (_, Inactive) -> None
|
||
|
- ) doms in
|
||
|
-
|
||
|
- (* For each domain we have a list of network interfaces seen
|
||
|
- * this slice, and seen in the previous slice, which we now
|
||
|
- * match up to get a list of (domain, interface) for which
|
||
|
- * we have current & previous knowledge. (And ignore the rest).
|
||
|
- *)
|
||
|
- let devs =
|
||
|
- List.map (
|
||
|
- fun (name, rd) ->
|
||
|
- List.filter_map (
|
||
|
- fun (dev, stats) ->
|
||
|
- try
|
||
|
- (* Have prev slice stats for this device? *)
|
||
|
- let prev_stats =
|
||
|
- List.assoc dev rd.rd_prev_interface_stats in
|
||
|
- Some (dev, name, rd, stats, prev_stats)
|
||
|
- with Not_found -> None
|
||
|
- ) rd.rd_interface_stats
|
||
|
- ) doms in
|
||
|
-
|
||
|
- (* Finally we have a list of:
|
||
|
- * device name, domain name, rd_* stuff, curr stats, prev stats.
|
||
|
- *)
|
||
|
- let devs : (string * string * rd_active *
|
||
|
- D.interface_stats * D.interface_stats) list =
|
||
|
- List.flatten devs in
|
||
|
-
|
||
|
- (* Difference curr slice & prev slice. *)
|
||
|
- let devs = List.map (
|
||
|
- fun (dev, name, rd, curr, prev) ->
|
||
|
- dev, name, rd, diff_interface_stats curr prev
|
||
|
- ) devs in
|
||
|
-
|
||
|
- (* Sort by current sort order, but map some of the standard
|
||
|
- * sort orders into ones which makes sense here.
|
||
|
- *)
|
||
|
- let devs =
|
||
|
- let cmp =
|
||
|
- match !sort_order with
|
||
|
- | DomainName ->
|
||
|
- (fun _ -> 0) (* fallthrough to default name compare *)
|
||
|
- | DomainID ->
|
||
|
- (fun (_, { rd_domid = id1 }, _, { rd_domid = id2 }) ->
|
||
|
- compare id1 id2)
|
||
|
- | Processor | Memory | Time | BlockRdRq | BlockWrRq
|
||
|
- (* fallthrough to RXBY comparison. *)
|
||
|
- | NetRX ->
|
||
|
- (fun ({ D.rx_bytes = b1 }, _, { D.rx_bytes = b2 }, _) ->
|
||
|
- compare b2 b1)
|
||
|
- | NetTX ->
|
||
|
- (fun ({ D.tx_bytes = b1 }, _, { D.tx_bytes = b2 }, _) ->
|
||
|
- compare b2 b1)
|
||
|
- in
|
||
|
- let cmp (dev1, name1, rd1, stats1) (dev2, name2, rd2, stats2) =
|
||
|
- let r = cmp (stats1, rd1, stats2, rd2) in
|
||
|
- if r <> 0 then r
|
||
|
- else compare (dev1, name1) (dev2, name2)
|
||
|
- in
|
||
|
- List.sort ~cmp devs in
|
||
|
-
|
||
|
- (* Print the header for network devices. *)
|
||
|
- attron A.reverse;
|
||
|
- mvaddstr header_lineno 0
|
||
|
- (pad cols " ID S RXBY TXBY RXPK TXPK DOMAIN INTERFACE");
|
||
|
- attroff A.reverse;
|
||
|
-
|
||
|
- (* Print domains and devices. *)
|
||
|
- let rec loop lineno = function
|
||
|
- | [] -> ()
|
||
|
- | (dev, name, rd, stats) :: devs ->
|
||
|
- if lineno < lines then (
|
||
|
- let state = show_state rd.rd_info.D.state in
|
||
|
- let rx_bytes =
|
||
|
- if stats.D.rx_bytes >= 0L
|
||
|
- then Show.int64 stats.D.rx_bytes
|
||
|
- else " " in
|
||
|
- let tx_bytes =
|
||
|
- if stats.D.tx_bytes >= 0L
|
||
|
- then Show.int64 stats.D.tx_bytes
|
||
|
- else " " in
|
||
|
- let rx_packets =
|
||
|
- if stats.D.rx_packets >= 0L
|
||
|
- then Show.int64 stats.D.rx_packets
|
||
|
- else " " in
|
||
|
- let tx_packets =
|
||
|
- if stats.D.tx_packets >= 0L
|
||
|
- then Show.int64 stats.D.tx_packets
|
||
|
- else " " in
|
||
|
-
|
||
|
- let line = sprintf "%5d %c %s %s %s %s %-12s %s"
|
||
|
- rd.rd_domid state
|
||
|
- rx_bytes tx_bytes
|
||
|
- rx_packets tx_packets
|
||
|
- (pad 12 name) dev in
|
||
|
- let line = pad cols line in
|
||
|
- mvaddstr lineno 0 line;
|
||
|
- loop (lineno+1) devs
|
||
|
- )
|
||
|
- in
|
||
|
- loop domains_lineno devs
|
||
|
-
|
||
|
- | BlockDisplay -> (*---------- Showing block devices ----------*)
|
||
|
- (* Only care about active domains. *)
|
||
|
- let doms = List.filter_map (
|
||
|
- function
|
||
|
- | (name, Active rd) -> Some (name, rd)
|
||
|
- | (_, Inactive) -> None
|
||
|
- ) doms in
|
||
|
-
|
||
|
- (* For each domain we have a list of block devices seen
|
||
|
- * this slice, and seen in the previous slice, which we now
|
||
|
- * match up to get a list of (domain, device) for which
|
||
|
- * we have current & previous knowledge. (And ignore the rest).
|
||
|
- *)
|
||
|
- let devs =
|
||
|
- List.map (
|
||
|
- fun (name, rd) ->
|
||
|
- List.filter_map (
|
||
|
- fun (dev, stats) ->
|
||
|
- try
|
||
|
- (* Have prev slice stats for this device? *)
|
||
|
- let prev_stats =
|
||
|
- List.assoc dev rd.rd_prev_block_stats in
|
||
|
- Some (dev, name, rd, stats, prev_stats)
|
||
|
- with Not_found -> None
|
||
|
- ) rd.rd_block_stats
|
||
|
- ) doms in
|
||
|
-
|
||
|
- (* Finally we have a list of:
|
||
|
- * device name, domain name, rd_* stuff, curr stats, prev stats.
|
||
|
- *)
|
||
|
- let devs : (string * string * rd_active *
|
||
|
- D.block_stats * D.block_stats) list =
|
||
|
- List.flatten devs in
|
||
|
-
|
||
|
- (* Difference curr slice & prev slice. *)
|
||
|
- let devs = List.map (
|
||
|
- fun (dev, name, rd, curr, prev) ->
|
||
|
- dev, name, rd, diff_block_stats curr prev
|
||
|
- ) devs in
|
||
|
-
|
||
|
- (* Sort by current sort order, but map some of the standard
|
||
|
- * sort orders into ones which makes sense here.
|
||
|
- *)
|
||
|
- let devs =
|
||
|
- let cmp =
|
||
|
- match !sort_order with
|
||
|
- | DomainName ->
|
||
|
- (fun _ -> 0) (* fallthrough to default name compare *)
|
||
|
- | DomainID ->
|
||
|
- (fun (_, { rd_domid = id1 }, _, { rd_domid = id2 }) ->
|
||
|
- compare id1 id2)
|
||
|
- | Processor | Memory | Time | NetRX | NetTX
|
||
|
- (* fallthrough to RDRQ comparison. *)
|
||
|
- | BlockRdRq ->
|
||
|
- (fun ({ D.rd_req = b1 }, _, { D.rd_req = b2 }, _) ->
|
||
|
- compare b2 b1)
|
||
|
- | BlockWrRq ->
|
||
|
- (fun ({ D.wr_req = b1 }, _, { D.wr_req = b2 }, _) ->
|
||
|
- compare b2 b1)
|
||
|
- in
|
||
|
- let cmp (dev1, name1, rd1, stats1) (dev2, name2, rd2, stats2) =
|
||
|
- let r = cmp (stats1, rd1, stats2, rd2) in
|
||
|
- if r <> 0 then r
|
||
|
- else compare (dev1, name1) (dev2, name2)
|
||
|
- in
|
||
|
- List.sort ~cmp devs in
|
||
|
-
|
||
|
- (* Print the header for block devices. *)
|
||
|
- attron A.reverse;
|
||
|
- mvaddstr header_lineno 0
|
||
|
- (pad cols " ID S RDBY WRBY RDRQ WRRQ DOMAIN DEVICE");
|
||
|
- attroff A.reverse;
|
||
|
-
|
||
|
- (* Print domains and devices. *)
|
||
|
- let rec loop lineno = function
|
||
|
- | [] -> ()
|
||
|
- | (dev, name, rd, stats) :: devs ->
|
||
|
- if lineno < lines then (
|
||
|
- let state = show_state rd.rd_info.D.state in
|
||
|
- let rd_bytes =
|
||
|
- if stats.D.rd_bytes >= 0L
|
||
|
- then Show.int64 stats.D.rd_bytes
|
||
|
- else " " in
|
||
|
- let wr_bytes =
|
||
|
- if stats.D.wr_bytes >= 0L
|
||
|
- then Show.int64 stats.D.wr_bytes
|
||
|
- else " " in
|
||
|
- let rd_req =
|
||
|
- if stats.D.rd_req >= 0L
|
||
|
- then Show.int64 stats.D.rd_req
|
||
|
- else " " in
|
||
|
- let wr_req =
|
||
|
- if stats.D.wr_req >= 0L
|
||
|
- then Show.int64 stats.D.wr_req
|
||
|
- else " " in
|
||
|
-
|
||
|
- let line = sprintf "%5d %c %s %s %s %s %-12s %s"
|
||
|
- rd.rd_domid state
|
||
|
- rd_bytes wr_bytes
|
||
|
- rd_req wr_req
|
||
|
- (pad 12 name) dev in
|
||
|
- let line = pad cols line in
|
||
|
- mvaddstr lineno 0 line;
|
||
|
- loop (lineno+1) devs
|
||
|
- )
|
||
|
- in
|
||
|
- loop domains_lineno devs
|
||
|
- ); (* end of display_mode conditional section *)
|
||
|
-
|
||
|
- let (count, running, blocked, paused, shutdown, shutoff,
|
||
|
- crashed, active, inactive,
|
||
|
- total_cpu_time, total_memory, total_domU_memory) = totals in
|
||
|
-
|
||
|
- mvaddstr summary_lineno 0
|
||
|
- (sprintf
|
||
|
- (f_"%d domains, %d active, %d running, %d sleeping, %d paused, %d inactive D:%d O:%d X:%d")
|
||
|
- count active running blocked paused inactive shutdown shutoff crashed);
|
||
|
-
|
||
|
- (* Total %CPU used, and memory summary. *)
|
||
|
- let percent_cpu = 100. *. total_cpu_time /. total_cpu in
|
||
|
- mvaddstr (summary_lineno+1) 0
|
||
|
- (sprintf
|
||
|
- (f_"CPU: %2.1f%% Mem: %Ld MB (%Ld MB by guests)")
|
||
|
- percent_cpu (total_memory /^ 1024L) (total_domU_memory /^ 1024L));
|
||
|
-
|
||
|
- (* Time to grab another historical %CPU for the list? *)
|
||
|
- if time >= !historical_cpu_last_time +. float !historical_cpu_delay
|
||
|
- then (
|
||
|
- historical_cpu := percent_cpu :: List.take 10 !historical_cpu;
|
||
|
- historical_cpu_last_time := time
|
||
|
- );
|
||
|
-
|
||
|
- (* Display historical CPU time. *)
|
||
|
- let () =
|
||
|
- let y, x = historical_cursor in
|
||
|
- let maxwidth = cols - x in
|
||
|
- let line =
|
||
|
- String.concat " "
|
||
|
- (List.map (sprintf "%2.1f%%") !historical_cpu) in
|
||
|
- let line = pad maxwidth line in
|
||
|
- mvaddstr y x line;
|
||
|
- () in
|
||
|
-
|
||
|
- move message_lineno 0; (* Park cursor in message area, as with top. *)
|
||
|
- refresh () (* Refresh the display. *)
|
||
|
-
|
||
|
-(* Write CSV header row. *)
|
||
|
-let write_csv_header () =
|
||
|
- (!csv_write) (
|
||
|
- [ "Hostname"; "Time"; "Arch"; "Physical CPUs";
|
||
|
- "Count"; "Running"; "Blocked"; "Paused"; "Shutdown";
|
||
|
- "Shutoff"; "Crashed"; "Active"; "Inactive";
|
||
|
- "%CPU";
|
||
|
- "Total hardware memory (KB)";
|
||
|
- "Total memory (KB)"; "Total guest memory (KB)";
|
||
|
- "Total CPU time (ns)" ] @
|
||
|
- (* These fields are repeated for each domain: *)
|
||
|
- [ "Domain ID"; "Domain name"; ] @
|
||
|
- (if !csv_cpu then [ "CPU (ns)"; "%CPU"; ] else []) @
|
||
|
- (if !csv_mem then [ "Mem (bytes)"; "%Mem";] else []) @
|
||
|
- (if !csv_block && not !block_in_bytes
|
||
|
- then [ "Block RDRQ"; "Block WRRQ"; ] else []) @
|
||
|
- (if !csv_block && !block_in_bytes
|
||
|
- then [ "Block RDBY"; "Block WRBY"; ] else []) @
|
||
|
- (if !csv_net then [ "Net RXBY"; "Net TXBY" ] else [])
|
||
|
- )
|
||
|
-
|
||
|
-(* Write summary data to CSV file. *)
|
||
|
-let append_csv
|
||
|
- (_, _, _, _, _, node_info, hostname, _) (* setup *)
|
||
|
- (doms,
|
||
|
- _, printable_time,
|
||
|
- nr_pcpus, total_cpu, _,
|
||
|
- totals,
|
||
|
- _) (* state *) =
|
||
|
-
|
||
|
- (* The totals / summary fields. *)
|
||
|
- let (count, running, blocked, paused, shutdown, shutoff,
|
||
|
- crashed, active, inactive,
|
||
|
- total_cpu_time, total_memory, total_domU_memory) = totals in
|
||
|
-
|
||
|
- let percent_cpu = 100. *. total_cpu_time /. total_cpu in
|
||
|
-
|
||
|
- let summary_fields = [
|
||
|
- hostname; printable_time; node_info.C.model; string_of_int nr_pcpus;
|
||
|
- string_of_int count; string_of_int running; string_of_int blocked;
|
||
|
- string_of_int paused; string_of_int shutdown; string_of_int shutoff;
|
||
|
- string_of_int crashed; string_of_int active; string_of_int inactive;
|
||
|
- sprintf "%2.1f" percent_cpu;
|
||
|
- Int64.to_string node_info.C.memory;
|
||
|
- Int64.to_string total_memory; Int64.to_string total_domU_memory;
|
||
|
- Int64.to_string (Int64.of_float total_cpu_time)
|
||
|
- ] in
|
||
|
-
|
||
|
- (* The domains.
|
||
|
- *
|
||
|
- * Sort them by ID so that the list of relatively stable. Ignore
|
||
|
- * inactive domains.
|
||
|
- *)
|
||
|
- let doms = List.filter_map (
|
||
|
- function
|
||
|
- | _, Inactive -> None (* Ignore inactive domains. *)
|
||
|
- | name, Active rd -> Some (name, rd)
|
||
|
- ) doms in
|
||
|
- let cmp (_, { rd_domid = rd_domid1 }) (_, { rd_domid = rd_domid2 }) =
|
||
|
- compare rd_domid1 rd_domid2
|
||
|
- in
|
||
|
- let doms = List.sort ~cmp doms in
|
||
|
-
|
||
|
- let string_of_int64_option = Option.map_default Int64.to_string "" in
|
||
|
-
|
||
|
- let domain_fields = List.map (
|
||
|
- fun (domname, rd) ->
|
||
|
- [ string_of_int rd.rd_domid; domname ] @
|
||
|
- (if !csv_cpu then [
|
||
|
- string_of_float rd.rd_cpu_time; string_of_float rd.rd_percent_cpu
|
||
|
- ] else []) @
|
||
|
- (if !csv_mem then [
|
||
|
- Int64.to_string rd.rd_mem_bytes; Int64.to_string rd.rd_mem_percent
|
||
|
- ] else []) @
|
||
|
- (if !csv_block then [
|
||
|
- string_of_int64_option rd.rd_block_rd_info;
|
||
|
- string_of_int64_option rd.rd_block_wr_info;
|
||
|
- ] else []) @
|
||
|
- (if !csv_net then [
|
||
|
- string_of_int64_option rd.rd_net_rx_bytes;
|
||
|
- string_of_int64_option rd.rd_net_tx_bytes;
|
||
|
- ] else [])
|
||
|
- ) doms in
|
||
|
- let domain_fields = List.flatten domain_fields in
|
||
|
-
|
||
|
- (!csv_write) (summary_fields @ domain_fields)
|
||
|
-
|
||
|
-let dump_stdout
|
||
|
- (_, _, _, _, _, node_info, hostname, _) (* setup *)
|
||
|
- (doms,
|
||
|
- _, printable_time,
|
||
|
- nr_pcpus, total_cpu, _,
|
||
|
- totals,
|
||
|
- _) (* state *) =
|
||
|
-
|
||
|
- (* Header for this iteration *)
|
||
|
- printf "virt-top time %s Host %s %s %d/%dCPU %dMHz %LdMB \n"
|
||
|
- printable_time hostname node_info.C.model node_info.C.cpus nr_pcpus
|
||
|
- node_info.C.mhz (node_info.C.memory /^ 1024L);
|
||
|
- (* dump domain information one by one *)
|
||
|
- let rd, wr = if !block_in_bytes then "RDBY", "WRBY" else "RDRQ", "WRRQ"
|
||
|
- in
|
||
|
- printf " ID S %s %s RXBY TXBY %%CPU %%MEM TIME NAME\n" rd wr;
|
||
|
-
|
||
|
- (* sort by ID *)
|
||
|
- let doms =
|
||
|
- let compare =
|
||
|
- (function
|
||
|
- | Active {rd_domid = id1 }, Active {rd_domid = id2} ->
|
||
|
- compare id1 id2
|
||
|
- | Active _, Inactive -> -1
|
||
|
- | Inactive, Active _ -> 1
|
||
|
- | Inactive, Inactive -> 0)
|
||
|
- in
|
||
|
- let cmp (name1, dom1) (name2, dom2) = compare(dom1, dom2) in
|
||
|
- List.sort ~cmp doms in
|
||
|
- (*Print domains *)
|
||
|
- let dump_domain = fun name rd
|
||
|
- -> begin
|
||
|
- let state = show_state rd.rd_info.D.state in
|
||
|
- let rd_req = if rd.rd_block_rd_info = None then " 0"
|
||
|
- else Show.int64_option rd.rd_block_rd_info in
|
||
|
- let wr_req = if rd.rd_block_wr_info = None then " 0"
|
||
|
- else Show.int64_option rd.rd_block_wr_info in
|
||
|
- let rx_bytes = if rd.rd_net_rx_bytes = None then " 0"
|
||
|
- else Show.int64_option rd.rd_net_rx_bytes in
|
||
|
- let tx_bytes = if rd.rd_net_tx_bytes = None then " 0"
|
||
|
- else Show.int64_option rd.rd_net_tx_bytes in
|
||
|
- let percent_cpu = Show.percent rd.rd_percent_cpu in
|
||
|
- let percent_mem = Int64.to_float rd.rd_mem_percent in
|
||
|
- let percent_mem = Show.percent percent_mem in
|
||
|
- let time = Show.time rd.rd_info.D.cpu_time in
|
||
|
- printf "%5d %c %s %s %s %s %s %s %s %s\n"
|
||
|
- rd.rd_domid state rd_req wr_req rx_bytes tx_bytes
|
||
|
- percent_cpu percent_mem time name;
|
||
|
- end
|
||
|
- in
|
||
|
- List.iter (
|
||
|
- function
|
||
|
- | name, Active dom -> dump_domain name dom
|
||
|
- | name, Inactive -> ()
|
||
|
- ) doms;
|
||
|
- flush stdout
|
||
|
-
|
||
|
(* Main loop. *)
|
||
|
let rec main_loop ((_, batch_mode, script_mode, csv_enabled, stream_mode, _, _, _)
|
||
|
as setup) =
|
||
|
- if csv_enabled then write_csv_header ();
|
||
|
+ let csv_flags = !csv_cpu, !csv_mem, !csv_block, !csv_net in
|
||
|
+
|
||
|
+ if csv_enabled then
|
||
|
+ Csv_output.write_csv_header csv_flags !block_in_bytes;
|
||
|
|
||
|
while not !quit do
|
||
|
- let state = collect setup in (* Collect stats. *)
|
||
|
+ (* Collect stats. *)
|
||
|
+ let state = collect setup !block_in_bytes in
|
||
|
+ let pcpu_display =
|
||
|
+ if !display_mode = PCPUDisplay then Some (collect_pcpu state)
|
||
|
+ else None in
|
||
|
(* Redraw display. *)
|
||
|
- if not script_mode && not stream_mode then redraw setup state;
|
||
|
- if csv_enabled then append_csv setup state; (* Update CSV file. *)
|
||
|
- if stream_mode then dump_stdout setup state; (* dump to stdout *)
|
||
|
+ if not script_mode && not stream_mode then
|
||
|
+ Redraw.redraw !display_mode !sort_order
|
||
|
+ setup !block_in_bytes !historical_cpu_delay
|
||
|
+ state pcpu_display;
|
||
|
+
|
||
|
+ (* Update CSV file. *)
|
||
|
+ if csv_enabled then
|
||
|
+ Csv_output.append_csv setup csv_flags state;
|
||
|
+
|
||
|
+ (* Append to stream output file. *)
|
||
|
+ if stream_mode then
|
||
|
+ Stream_output.append_stream setup !block_in_bytes state;
|
||
|
|
||
|
(* Clear up unused virDomainPtr objects. *)
|
||
|
Gc.compact ();
|
||
|
@@ -1440,11 +356,10 @@ let rec main_loop ((_, batch_mode, script_mode, csv_enabled, stream_mode, _, _,
|
||
|
(* No --end-time option, so use the current delay. *)
|
||
|
!delay
|
||
|
| Some end_time ->
|
||
|
- let (_, time, _, _, _, _, _, _) = state in
|
||
|
let delay_secs = float !delay /. 1000. in
|
||
|
- if end_time <= time +. delay_secs then (
|
||
|
+ if end_time <= state.rd_time +. delay_secs then (
|
||
|
quit := true;
|
||
|
- let delay = int_of_float (1000. *. (end_time -. time)) in
|
||
|
+ let delay = int_of_float (1000. *. (end_time -. state.rd_time)) in
|
||
|
if delay >= 0 then delay else 0
|
||
|
) else
|
||
|
!delay in
|
||
|
diff --git a/src/top.mli b/src/top.mli
|
||
|
index b0953dd..b625910 100644
|
||
|
--- a/src/top.mli
|
||
|
+++ b/src/top.mli
|
||
|
@@ -1,5 +1,5 @@
|
||
|
(* 'top'-like tool for libvirt domains.
|
||
|
- (C) Copyright 2007-2009 Richard W.M. Jones, Red Hat Inc.
|
||
|
+ (C) Copyright 2007-2017 Richard W.M. Jones, Red Hat Inc.
|
||
|
http://libvirt.org/
|
||
|
|
||
|
This program is free software; you can redistribute it and/or modify
|
||
|
@@ -17,23 +17,11 @@
|
||
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||
|
*)
|
||
|
|
||
|
-(* Hook for [Opt_xml] to override (if present). *)
|
||
|
-val parse_device_xml :
|
||
|
- (int -> [ `R ] Libvirt.Domain.t -> string list * string list) ref
|
||
|
-
|
||
|
-(* Hooks for [Opt_csv] to override (if present). *)
|
||
|
+(* Hook for [Opt_csv] to override (if present). *)
|
||
|
val csv_start : (string -> unit) ref
|
||
|
-val csv_write : (string list -> unit) ref
|
||
|
|
||
|
(* Hook for [Opt_calendar] to override (if present). *)
|
||
|
val parse_date_time : (string -> float) ref
|
||
|
|
||
|
-type setup =
|
||
|
- Libvirt.ro Libvirt.Connect.t (* connection *)
|
||
|
- * bool * bool * bool * bool (* batch, script, csv, stream mode *)
|
||
|
- * Libvirt.Connect.node_info (* node_info *)
|
||
|
- * string (* hostname *)
|
||
|
- * (int * int * int) (* libvirt version *)
|
||
|
-
|
||
|
-val start_up : unit -> setup
|
||
|
-val main_loop : setup -> unit
|
||
|
+val start_up : unit -> Types.setup
|
||
|
+val main_loop : Types.setup -> unit
|
||
|
diff --git a/src/types.ml b/src/types.ml
|
||
|
new file mode 100644
|
||
|
index 0000000..2fdd49b
|
||
|
--- /dev/null
|
||
|
+++ b/src/types.ml
|
||
|
@@ -0,0 +1,147 @@
|
||
|
+(* 'top'-like tool for libvirt domains.
|
||
|
+ (C) Copyright 2007-2017 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.
|
||
|
+*)
|
||
|
+
|
||
|
+open Opt_gettext.Gettext
|
||
|
+open Utils
|
||
|
+
|
||
|
+module C = Libvirt.Connect
|
||
|
+module D = Libvirt.Domain
|
||
|
+
|
||
|
+(* XXX We should get rid of this type. *)
|
||
|
+type setup =
|
||
|
+ Libvirt.ro C.t (* connection *)
|
||
|
+ * bool * bool * bool * bool (* batch, script, csv, stream mode *)
|
||
|
+ * C.node_info (* node_info *)
|
||
|
+ * string (* hostname *)
|
||
|
+ * (int * int * int) (* libvirt version *)
|
||
|
+
|
||
|
+(* Sort order. *)
|
||
|
+type sort_order =
|
||
|
+ | DomainID | DomainName | Processor | Memory | Time
|
||
|
+ | NetRX | NetTX | BlockRdRq | BlockWrRq
|
||
|
+let all_sort_fields = [
|
||
|
+ DomainID; DomainName; Processor; Memory; Time;
|
||
|
+ NetRX; NetTX; BlockRdRq; BlockWrRq
|
||
|
+]
|
||
|
+let printable_sort_order = function
|
||
|
+ | Processor -> s_"%CPU"
|
||
|
+ | Memory -> s_"%MEM"
|
||
|
+ | Time -> s_"TIME (CPU time)"
|
||
|
+ | DomainID -> s_"Domain ID"
|
||
|
+ | DomainName -> s_"Domain name"
|
||
|
+ | NetRX -> s_"Net RX bytes"
|
||
|
+ | NetTX -> s_"Net TX bytes"
|
||
|
+ | BlockRdRq -> s_"Block read reqs"
|
||
|
+ | BlockWrRq -> s_"Block write reqs"
|
||
|
+let sort_order_of_cli = function
|
||
|
+ | "cpu" | "processor" -> Processor
|
||
|
+ | "mem" | "memory" -> Memory
|
||
|
+ | "time" -> Time
|
||
|
+ | "id" -> DomainID
|
||
|
+ | "name" -> DomainName
|
||
|
+ | "netrx" -> NetRX | "nettx" -> NetTX
|
||
|
+ | "blockrdrq" -> BlockRdRq | "blockwrrq" -> BlockWrRq
|
||
|
+ | str ->
|
||
|
+ failwithf (f_"%s: sort order should be: %s")
|
||
|
+ str "cpu|mem|time|id|name|netrx|nettx|blockrdrq|blockwrrq"
|
||
|
+let cli_of_sort_order = function
|
||
|
+ | Processor -> "cpu"
|
||
|
+ | Memory -> "mem"
|
||
|
+ | Time -> "time"
|
||
|
+ | DomainID -> "id"
|
||
|
+ | DomainName -> "name"
|
||
|
+ | NetRX -> "netrx"
|
||
|
+ | NetTX -> "nettx"
|
||
|
+ | BlockRdRq -> "blockrdrq"
|
||
|
+ | BlockWrRq -> "blockwrrq"
|
||
|
+
|
||
|
+(* Current major display mode: TaskDisplay is the normal display. *)
|
||
|
+type display = TaskDisplay | PCPUDisplay | BlockDisplay | NetDisplay
|
||
|
+
|
||
|
+let display_of_cli = function
|
||
|
+ | "task" -> TaskDisplay
|
||
|
+ | "pcpu" -> PCPUDisplay
|
||
|
+ | "block" -> BlockDisplay
|
||
|
+ | "net" -> NetDisplay
|
||
|
+ | str ->
|
||
|
+ failwithf (f_"%s: display should be %s") str "task|pcpu|block|net"
|
||
|
+let cli_of_display = function
|
||
|
+ | TaskDisplay -> "task"
|
||
|
+ | PCPUDisplay -> "pcpu"
|
||
|
+ | BlockDisplay -> "block"
|
||
|
+ | NetDisplay -> "net"
|
||
|
+
|
||
|
+(* Sum Domain.block_stats structures together. Missing fields
|
||
|
+ * get forced to 0. Empty list returns all 0.
|
||
|
+ *)
|
||
|
+let zero_block_stats =
|
||
|
+ { D.rd_req = 0L; rd_bytes = 0L; wr_req = 0L; wr_bytes = 0L; errs = 0L }
|
||
|
+let add_block_stats bs1 bs2 =
|
||
|
+ let add f1 f2 = if f1 >= 0L && f2 >= 0L then f1 +^ f2 else 0L in
|
||
|
+ { D.rd_req = add bs1.D.rd_req bs2.D.rd_req;
|
||
|
+ rd_bytes = add bs1.D.rd_bytes bs2.D.rd_bytes;
|
||
|
+ wr_req = add bs1.D.wr_req bs2.D.wr_req;
|
||
|
+ wr_bytes = add bs1.D.wr_bytes bs2.D.wr_bytes;
|
||
|
+ errs = add bs1.D.errs bs2.D.errs }
|
||
|
+let sum_block_stats =
|
||
|
+ List.fold_left add_block_stats zero_block_stats
|
||
|
+
|
||
|
+(* Get the difference between two block_stats structures. Missing data
|
||
|
+ * forces the difference to -1.
|
||
|
+ *)
|
||
|
+let diff_block_stats curr prev =
|
||
|
+ let sub f1 f2 = if f1 >= 0L && f2 >= 0L then f1 -^ f2 else -1L in
|
||
|
+ { D.rd_req = sub curr.D.rd_req prev.D.rd_req;
|
||
|
+ rd_bytes = sub curr.D.rd_bytes prev.D.rd_bytes;
|
||
|
+ wr_req = sub curr.D.wr_req prev.D.wr_req;
|
||
|
+ wr_bytes = sub curr.D.wr_bytes prev.D.wr_bytes;
|
||
|
+ errs = sub curr.D.errs prev.D.errs }
|
||
|
+
|
||
|
+(* Sum Domain.interface_stats structures together. Missing fields
|
||
|
+ * get forced to 0. Empty list returns all 0.
|
||
|
+ *)
|
||
|
+let zero_interface_stats =
|
||
|
+ { D.rx_bytes = 0L; rx_packets = 0L; rx_errs = 0L; rx_drop = 0L;
|
||
|
+ tx_bytes = 0L; tx_packets = 0L; tx_errs = 0L; tx_drop = 0L }
|
||
|
+let add_interface_stats is1 is2 =
|
||
|
+ let add f1 f2 = if f1 >= 0L && f2 >= 0L then f1 +^ f2 else 0L in
|
||
|
+ { D.rx_bytes = add is1.D.rx_bytes is2.D.rx_bytes;
|
||
|
+ rx_packets = add is1.D.rx_packets is2.D.rx_packets;
|
||
|
+ rx_errs = add is1.D.rx_errs is2.D.rx_errs;
|
||
|
+ rx_drop = add is1.D.rx_drop is2.D.rx_drop;
|
||
|
+ tx_bytes = add is1.D.tx_bytes is2.D.tx_bytes;
|
||
|
+ tx_packets = add is1.D.tx_packets is2.D.tx_packets;
|
||
|
+ tx_errs = add is1.D.tx_errs is2.D.tx_errs;
|
||
|
+ tx_drop = add is1.D.tx_drop is2.D.tx_drop }
|
||
|
+let sum_interface_stats =
|
||
|
+ List.fold_left add_interface_stats zero_interface_stats
|
||
|
+
|
||
|
+(* Get the difference between two interface_stats structures.
|
||
|
+ * Missing data forces the difference to -1.
|
||
|
+ *)
|
||
|
+let diff_interface_stats curr prev =
|
||
|
+ let sub f1 f2 = if f1 >= 0L && f2 >= 0L then f1 -^ f2 else -1L in
|
||
|
+ { D.rx_bytes = sub curr.D.rx_bytes prev.D.rx_bytes;
|
||
|
+ rx_packets = sub curr.D.rx_packets prev.D.rx_packets;
|
||
|
+ rx_errs = sub curr.D.rx_errs prev.D.rx_errs;
|
||
|
+ rx_drop = sub curr.D.rx_drop prev.D.rx_drop;
|
||
|
+ tx_bytes = sub curr.D.tx_bytes prev.D.tx_bytes;
|
||
|
+ tx_packets = sub curr.D.tx_packets prev.D.tx_packets;
|
||
|
+ tx_errs = sub curr.D.tx_errs prev.D.tx_errs;
|
||
|
+ tx_drop = sub curr.D.tx_drop prev.D.tx_drop }
|
||
|
diff --git a/src/types.mli b/src/types.mli
|
||
|
new file mode 100644
|
||
|
index 0000000..6297482
|
||
|
--- /dev/null
|
||
|
+++ b/src/types.mli
|
||
|
@@ -0,0 +1,49 @@
|
||
|
+(* 'top'-like tool for libvirt domains.
|
||
|
+ (C) Copyright 2007-2017 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.
|
||
|
+*)
|
||
|
+
|
||
|
+(* XXX We should get rid of this type. *)
|
||
|
+type setup =
|
||
|
+ Libvirt.ro Libvirt.Connect.t (* connection *)
|
||
|
+ * bool * bool * bool * bool (* batch, script, csv, stream mode *)
|
||
|
+ * Libvirt.Connect.node_info (* node_info *)
|
||
|
+ * string (* hostname *)
|
||
|
+ * (int * int * int) (* libvirt version *)
|
||
|
+
|
||
|
+(* Sort order. *)
|
||
|
+type sort_order =
|
||
|
+ | DomainID | DomainName | Processor | Memory | Time
|
||
|
+ | NetRX | NetTX | BlockRdRq | BlockWrRq
|
||
|
+
|
||
|
+val all_sort_fields : sort_order list
|
||
|
+val printable_sort_order : sort_order -> string
|
||
|
+val sort_order_of_cli : string -> sort_order
|
||
|
+val cli_of_sort_order : sort_order -> string
|
||
|
+
|
||
|
+(* Current major display mode: TaskDisplay is the normal display. *)
|
||
|
+type display = TaskDisplay | PCPUDisplay | BlockDisplay | NetDisplay
|
||
|
+
|
||
|
+val display_of_cli : string -> display
|
||
|
+val cli_of_display : display -> string
|
||
|
+
|
||
|
+(* Helpers for manipulating block_stats & interface_stats. *)
|
||
|
+val sum_block_stats : Libvirt.Domain.block_stats list -> Libvirt.Domain.block_stats
|
||
|
+val diff_block_stats : Libvirt.Domain.block_stats -> Libvirt.Domain.block_stats -> Libvirt.Domain.block_stats
|
||
|
+
|
||
|
+val sum_interface_stats : Libvirt.Domain.interface_stats list -> Libvirt.Domain.interface_stats
|
||
|
+val diff_interface_stats : Libvirt.Domain.interface_stats -> Libvirt.Domain.interface_stats -> Libvirt.Domain.interface_stats
|
||
|
diff --git a/src/utils.ml b/src/utils.ml
|
||
|
index 3dc637d..5fcc905 100644
|
||
|
--- a/src/utils.ml
|
||
|
+++ b/src/utils.ml
|
||
|
@@ -21,12 +21,6 @@
|
||
|
|
||
|
open Printf
|
||
|
|
||
|
-open Opt_gettext.Gettext
|
||
|
-
|
||
|
-module C = Libvirt.Connect
|
||
|
-module D = Libvirt.Domain
|
||
|
-module N = Libvirt.Network
|
||
|
-
|
||
|
let (//) = Filename.concat
|
||
|
|
||
|
(* Int64 operators for convenience. *)
|
||
|
@@ -166,62 +160,3 @@ module Show = struct
|
||
|
sprintf "%3Ldd%02Ld:%02Ld" days hours mins
|
||
|
)
|
||
|
end
|
||
|
-
|
||
|
-(* Sum Domain.block_stats structures together. Missing fields
|
||
|
- * get forced to 0. Empty list returns all 0.
|
||
|
- *)
|
||
|
-let zero_block_stats =
|
||
|
- { D.rd_req = 0L; rd_bytes = 0L; wr_req = 0L; wr_bytes = 0L; errs = 0L }
|
||
|
-let add_block_stats bs1 bs2 =
|
||
|
- let add f1 f2 = if f1 >= 0L && f2 >= 0L then f1 +^ f2 else 0L in
|
||
|
- { D.rd_req = add bs1.D.rd_req bs2.D.rd_req;
|
||
|
- rd_bytes = add bs1.D.rd_bytes bs2.D.rd_bytes;
|
||
|
- wr_req = add bs1.D.wr_req bs2.D.wr_req;
|
||
|
- wr_bytes = add bs1.D.wr_bytes bs2.D.wr_bytes;
|
||
|
- errs = add bs1.D.errs bs2.D.errs }
|
||
|
-let sum_block_stats =
|
||
|
- List.fold_left add_block_stats zero_block_stats
|
||
|
-
|
||
|
-(* Get the difference between two block_stats structures. Missing data
|
||
|
- * forces the difference to -1.
|
||
|
- *)
|
||
|
-let diff_block_stats curr prev =
|
||
|
- let sub f1 f2 = if f1 >= 0L && f2 >= 0L then f1 -^ f2 else -1L in
|
||
|
- { D.rd_req = sub curr.D.rd_req prev.D.rd_req;
|
||
|
- rd_bytes = sub curr.D.rd_bytes prev.D.rd_bytes;
|
||
|
- wr_req = sub curr.D.wr_req prev.D.wr_req;
|
||
|
- wr_bytes = sub curr.D.wr_bytes prev.D.wr_bytes;
|
||
|
- errs = sub curr.D.errs prev.D.errs }
|
||
|
-
|
||
|
-(* Sum Domain.interface_stats structures together. Missing fields
|
||
|
- * get forced to 0. Empty list returns all 0.
|
||
|
- *)
|
||
|
-let zero_interface_stats =
|
||
|
- { D.rx_bytes = 0L; rx_packets = 0L; rx_errs = 0L; rx_drop = 0L;
|
||
|
- tx_bytes = 0L; tx_packets = 0L; tx_errs = 0L; tx_drop = 0L }
|
||
|
-let add_interface_stats is1 is2 =
|
||
|
- let add f1 f2 = if f1 >= 0L && f2 >= 0L then f1 +^ f2 else 0L in
|
||
|
- { D.rx_bytes = add is1.D.rx_bytes is2.D.rx_bytes;
|
||
|
- rx_packets = add is1.D.rx_packets is2.D.rx_packets;
|
||
|
- rx_errs = add is1.D.rx_errs is2.D.rx_errs;
|
||
|
- rx_drop = add is1.D.rx_drop is2.D.rx_drop;
|
||
|
- tx_bytes = add is1.D.tx_bytes is2.D.tx_bytes;
|
||
|
- tx_packets = add is1.D.tx_packets is2.D.tx_packets;
|
||
|
- tx_errs = add is1.D.tx_errs is2.D.tx_errs;
|
||
|
- tx_drop = add is1.D.tx_drop is2.D.tx_drop }
|
||
|
-let sum_interface_stats =
|
||
|
- List.fold_left add_interface_stats zero_interface_stats
|
||
|
-
|
||
|
-(* Get the difference between two interface_stats structures.
|
||
|
- * Missing data forces the difference to -1.
|
||
|
- *)
|
||
|
-let diff_interface_stats curr prev =
|
||
|
- let sub f1 f2 = if f1 >= 0L && f2 >= 0L then f1 -^ f2 else -1L in
|
||
|
- { D.rx_bytes = sub curr.D.rx_bytes prev.D.rx_bytes;
|
||
|
- rx_packets = sub curr.D.rx_packets prev.D.rx_packets;
|
||
|
- rx_errs = sub curr.D.rx_errs prev.D.rx_errs;
|
||
|
- rx_drop = sub curr.D.rx_drop prev.D.rx_drop;
|
||
|
- tx_bytes = sub curr.D.tx_bytes prev.D.tx_bytes;
|
||
|
- tx_packets = sub curr.D.tx_packets prev.D.tx_packets;
|
||
|
- tx_errs = sub curr.D.tx_errs prev.D.tx_errs;
|
||
|
- tx_drop = sub curr.D.tx_drop prev.D.tx_drop }
|
||
|
diff --git a/src/utils.mli b/src/utils.mli
|
||
|
index 5b71b31..6e81215 100644
|
||
|
--- a/src/utils.mli
|
||
|
+++ b/src/utils.mli
|
||
|
@@ -46,12 +46,3 @@ module Show : sig
|
||
|
val int64 : int64 -> string
|
||
|
val time : int64 -> string
|
||
|
end
|
||
|
-
|
||
|
-(* Helpers for manipulating block_stats & interface_stats. *)
|
||
|
-open Libvirt.Domain
|
||
|
-
|
||
|
-val sum_block_stats : block_stats list -> block_stats
|
||
|
-val diff_block_stats : block_stats -> block_stats -> block_stats
|
||
|
-
|
||
|
-val sum_interface_stats : interface_stats list -> interface_stats
|
||
|
-val diff_interface_stats : interface_stats -> interface_stats -> interface_stats
|
||
|
--
|
||
|
2.31.1
|
||
|
|