From c513d05fd4e85953701b1023bef71af62613cf79 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Tue, 28 Mar 2017 13:30:07 +0100 Subject: [PATCH] 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 | 252 +++++++++++++++++++++++++++++++++++++++++--------------- src/collect.mli | 1 + src/utils.ml | 6 ++ src/utils.mli | 3 + 4 files changed, 195 insertions(+), 67 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 - - (* 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_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 + 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 + + 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 + + if state = Some 5 (* VIR_DOMAIN_SHUTOFF *) then + (name, Inactive) + else ( + (* Active domain. *) + + (* 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 + + 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 + + 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. *) -- 1.8.3.1