Blob Blame History Raw
From 74971ed8c85400929714a7adff5b61c2ebe9fcee Mon Sep 17 00:00:00 2001
From: "Richard W.M. Jones" <rjones@redhat.com>
Date: Tue, 28 Mar 2017 13:30:07 +0100
Subject: [PATCH 17/23] Use virConnectGetAllDomainStats API to collect domain
 stats (RHBZ#1422795).

This is much faster than using the basic libvirt APIs to collect
stats for each domain individually.

Note this will not work unless you have the latest ocaml-libvirt
package which includes this new API binding.
---
 src/collect.ml  | 242 +++++++++++++++++++++++++++++++++++-------------
 src/collect.mli |   1 +
 src/utils.ml    |   6 ++
 src/utils.mli   |   3 +
 4 files changed, 190 insertions(+), 62 deletions(-)

diff --git a/src/collect.ml b/src/collect.ml
index 448ce8c..a1e50a1 100644
--- a/src/collect.ml
+++ b/src/collect.ml
@@ -38,6 +38,7 @@ let parse_device_xml : (int -> [>`R] D.t -> string list * string list) ref =
 type rd_domain = Inactive | Active of rd_active
 and rd_active = {
   rd_domid : int;			(* Domain ID. *)
+  rd_domuuid : Libvirt.uuid;            (* Domain UUID. *)
   rd_dom : [`R] D.t;			(* Domain object. *)
   rd_info : D.info;			(* Domain CPU info now. *)
   rd_block_stats : (string * D.block_stats) list;
@@ -110,6 +111,16 @@ let last_pcpu_usages = Hashtbl.create 13
 let clear_pcpu_display_data () =
   Hashtbl.clear last_pcpu_usages
 
+(* What to get from virConnectGetAllDomainStats. *)
+let what = [
+  D.StatsState; D.StatsCpuTotal; D.StatsBalloon; D.StatsVcpu;
+  D.StatsInterface; D.StatsBlock
+]
+(* Which domains to get.  Empty list means return all domains:
+ * active, inactive, persistent, transient etc.
+ *)
+let who = []
+
 let collect (conn, _, _, _, _, node_info, _, _) =
   (* Number of physical CPUs (some may be disabled). *)
   let nr_pcpus = C.maxcpus_of_node_info node_info in
@@ -129,72 +140,179 @@ let collect (conn, _, _, _, _, node_info, _, _) =
 
   (* 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
+    let doms = D.get_all_domain_stats conn what who in
+    let doms = Array.to_list doms in
+    List.map (
+      fun { D.dom_uuid = uuid; D.params = params } ->
+        let nr_params = Array.length params in
+        let get_param name =
+          let rec loop i =
+            if i = nr_params then None
+            else if fst params.(i) = name then Some (snd params.(i))
+            else loop (i+1)
+          in
+          loop 0
+        in
+        let get_param_int name default =
+          match get_param name with
+          | None -> None
+          | Some (D.TypedFieldInt32 i)
+          | Some (D.TypedFieldUInt32 i) -> Some (Int32.to_int i)
+          | Some (D.TypedFieldInt64 i)
+          | Some (D.TypedFieldUInt64 i) -> Some (Int64.to_int i)
+          | _ -> default
+        in
+        let get_param_int64 name default =
+          match get_param name with
+          | None -> None
+          | Some (D.TypedFieldInt32 i)
+          | Some (D.TypedFieldUInt32 i) -> Some (Int64.of_int32 i)
+          | Some (D.TypedFieldInt64 i)
+          | Some (D.TypedFieldUInt64 i) -> Some i
+          | _ -> default
+        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 dom = D.lookup_by_uuid conn uuid in
+        let id = D.get_id dom in
+        let name = D.get_name dom in
+        let state = get_param_int "state.state" None 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
+        if state = Some 5 (* VIR_DOMAIN_SHUTOFF *) then
+          (name, Inactive)
+        else (
+          (* Active domain. *)
 
-	    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_net_rx_bytes = None; rd_net_tx_bytes = None;
-		    })
-	  with
-	    Libvirt.Virterror _ -> None (* ignore transient error *)
-      ) ids in
+          (* Synthesize a D.info struct out of the data we have
+           * from virConnectGetAllDomainStats.  Doing this is an
+           * artifact from the old APIs we used to use to fetch
+           * stats, we could simplify here, and also return the
+           * RSS memory. XXX
+           *)
+          let state =
+            match state with
+            | None | Some 0 -> D.InfoNoState
+            | Some 1 -> D.InfoRunning
+            | Some 2 -> D.InfoBlocked
+            | Some 3 -> D.InfoPaused
+            | Some 4 -> D.InfoShutdown
+            | Some 5 -> D.InfoShutoff
+            | Some 6 -> D.InfoCrashed
+            | Some 7 -> D.InfoPaused (* XXX really VIR_DOMAIN_PMSUSPENDED *)
+            | _ -> D.InfoNoState in
+          let memory =
+            match get_param_int64 "balloon.current" None with
+            | None -> 0_L
+            | Some m -> m in
+          let nr_virt_cpu =
+            match get_param_int "vcpu.current" None with
+            | None -> 1
+            | Some v -> v in
+          let cpu_time =
+            (* NB: libvirt does not return cpu.time for non-root domains. *)
+            match get_param_int64 "cpu.time" None with
+            | None -> 0_L
+            | Some ns -> ns in
+          let info = {
+            D.state = state;
+            max_mem = -1_L; (* not used anywhere in virt-top *)
+            memory = memory;
+            nr_virt_cpu = nr_virt_cpu;
+            cpu_time = cpu_time
+          } 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
+          let nr_block_devs =
+            match get_param_int "block.count" None with
+            | None -> 0
+            | Some i -> i in
+          let block_stats =
+            List.map (
+              fun i ->
+              let dev =
+                match get_param (sprintf "block.%d.name" i) with
+                | None -> sprintf "blk%d" i
+                | Some (D.TypedFieldString s) -> s
+                | _ -> assert false in
+              dev, {
+                D.rd_req =
+                  (match get_param_int64 (sprintf "block.%d.rd.reqs" i) None
+                   with None -> 0_L | Some v -> v);
+                rd_bytes =
+                  (match get_param_int64 (sprintf "block.%d.rd.bytes" i) None
+                   with None -> 0_L | Some v -> v);
+                wr_req =
+                  (match get_param_int64 (sprintf "block.%d.wr.reqs" i) None
+                   with None -> 0_L | Some v -> v);
+                wr_bytes =
+                  (match get_param_int64 (sprintf "block.%d.wr.bytes" i) None
+                   with None -> 0_L | Some v -> v);
+                errs = 0_L
+              }
+            ) (range 0 (nr_block_devs-1)) in
 
-    doms @ doms_inactive in
+          let nr_interface_devs =
+            match get_param_int "net.count" None with
+            | None -> 0
+            | Some i -> i in
+          let interface_stats =
+            List.map (
+              fun i ->
+              let dev =
+                match get_param (sprintf "net.%d.name" i) with
+                | None -> sprintf "net%d" i
+                | Some (D.TypedFieldString s) -> s
+                | _ -> assert false in
+              dev, {
+                D.rx_bytes =
+                  (match get_param_int64 (sprintf "net.%d.rx.bytes" i) None
+                   with None -> 0_L | Some v -> v);
+                rx_packets =
+                  (match get_param_int64 (sprintf "net.%d.rx.pkts" i) None
+                   with None -> 0_L | Some v -> v);
+                rx_errs =
+                  (match get_param_int64 (sprintf "net.%d.rx.errs" i) None
+                   with None -> 0_L | Some v -> v);
+                rx_drop =
+                  (match get_param_int64 (sprintf "net.%d.rx.drop" i) None
+                   with None -> 0_L | Some v -> v);
+                tx_bytes =
+                  (match get_param_int64 (sprintf "net.%d.tx.bytes" i) None
+                   with None -> 0_L | Some v -> v);
+                tx_packets =
+                  (match get_param_int64 (sprintf "net.%d.tx.pkts" i) None
+                   with None -> 0_L | Some v -> v);
+                tx_errs =
+                  (match get_param_int64 (sprintf "net.%d.tx.errs" i) None
+                   with None -> 0_L | Some v -> v);
+                tx_drop =
+                  (match get_param_int64 (sprintf "net.%d.tx.drop" i) None
+                   with None -> 0_L | Some v -> v);
+              }
+            ) (range 0 (nr_interface_devs-1)) in
+
+	  let prev_info, prev_block_stats, prev_interface_stats =
+	    try
+	      let prev_info, prev_block_stats, prev_interface_stats =
+		Hashtbl.find last_info uuid in
+	      Some prev_info, prev_block_stats, prev_interface_stats
+	    with Not_found -> None, [], [] in
+
+	  (name,
+           Active {
+	     rd_domid = id; rd_domuuid = uuid; 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_net_rx_bytes = None; rd_net_tx_bytes = None;
+	   })
+        )
+    ) doms in
 
   (* Calculate the CPU time (ns) and %CPU used by each domain. *)
   let doms =
@@ -329,7 +447,7 @@ let collect (conn, _, _, _, _, node_info, _, _) =
     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
+       Hashtbl.add last_info rd.rd_domuuid info
     | _ -> ()
   ) doms;
 
diff --git a/src/collect.mli b/src/collect.mli
index 9ad3dcb..3c5492f 100644
--- a/src/collect.mli
+++ b/src/collect.mli
@@ -27,6 +27,7 @@ val parse_device_xml :
 type rd_domain = Inactive | Active of rd_active
 and rd_active = {
   rd_domid : int;			(* Domain ID. *)
+  rd_domuuid : Libvirt.uuid;            (* Domain UUID. *)
   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;
diff --git a/src/utils.ml b/src/utils.ml
index 5fcc905..4332ff7 100644
--- a/src/utils.ml
+++ b/src/utils.ml
@@ -32,6 +32,12 @@ let (/^) = Int64.div
 (* failwithf is a printf-like version of failwith. *)
 let failwithf fs = ksprintf failwith fs
 
+let rec range a b =
+  if a <= b then
+    a :: range (a+1) b
+  else
+    []
+
 (* Input a whole file as a list of lines. *)
 let input_all_lines chan =
   let lines = ref [] in
diff --git a/src/utils.mli b/src/utils.mli
index 6e81215..3c966f8 100644
--- a/src/utils.mli
+++ b/src/utils.mli
@@ -25,6 +25,9 @@ val (//) : string -> string -> string
 (* failwithf is a printf-like version of failwith. *)
 val failwithf : ('a, unit, string, 'b) format4 -> 'a
 
+(* Return the list of integers [a..b] (inclusive). *)
+val range : int -> int -> int list
+
 (* Read a configuration file as a list of (lineno, key, value) pairs.
  * If the config file is missing this returns an empty list.
  *)
-- 
2.31.1