X-Git-Url: http://git.annexia.org/?a=blobdiff_plain;f=virt-top%2Fvirt_top.ml;h=0aae24e54bc3904bd5fb042b73ab0119b1286cfd;hb=0e47961395eec78b1ee9f6ae48520f1d95d84fdf;hp=75042c130c904a92ba518eb014ff4bc4f2f95958;hpb=daf79631f3eff904ba89297148c4125ac7dfee39;p=virt-top.git diff --git a/virt-top/virt_top.ml b/virt-top/virt_top.ml index 75042c1..0aae24e 100644 --- a/virt-top/virt_top.ml +++ b/virt-top/virt_top.ml @@ -126,11 +126,13 @@ let uri = ref None let debug_file = ref "" let csv_enabled = ref false let csv_cpu = ref true +let csv_mem = ref true let csv_block = ref true let csv_net = ref true let init_file = ref DefaultInitFile 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 = @@ -170,13 +172,15 @@ let start_up () = "-b", Arg.Set batch_mode, " " ^ s_"Batch mode"; "-c", Arg.String set_uri, - "uri " ^ s_"Connect to URI (default: Xen)"; + "uri " ^ s_"Connect to libvirt URI"; "--connect", Arg.String set_uri, - "uri " ^ s_"Connect to URI (default: Xen)"; + "uri " ^ s_"Connect to libvirt URI"; "--csv", Arg.String set_csv, "file " ^ s_"Log statistics to CSV file"; "--no-csv-cpu", Arg.Clear csv_cpu, " " ^ s_"Disable CPU stats in CSV"; + "--no-csv-mem", Arg.Clear csv_mem, + " " ^ s_"Disable memory stats in CSV"; "--no-csv-block", Arg.Clear csv_block, " " ^ s_"Disable block device stats in CSV"; "--no-csv-net", Arg.Clear csv_net, @@ -196,13 +200,16 @@ let start_up () = "-n", Arg.Set_int iterations, "iterations " ^ s_"Number of iterations to run"; "-o", Arg.String set_sort, - "sort " ^ sprintf (f_"Set sort order (%s)") "cpu|mem|time|id|name"; + "sort " ^ sprintf (f_"Set sort order (%s)") + "cpu|mem|time|id|name|netrx|nettx|blockrdrq|blockwrrq"; "-s", Arg.Set secure_mode, " " ^ s_"Secure (\"kiosk\") mode"; "--script", Arg.Set script_mode, " " ^ s_"Run from a script (no user interface)"; "--stream", Arg.Set stream_mode, " " ^ s_"dump output to stdout (no userinterface)"; + "--block-in-bytes", Arg.Set block_in_bytes, + " " ^ s_"show block device load in bytes rather than reqs"; "--version", Arg.Unit display_version, " " ^ s_"Display version number and exit"; ] in @@ -219,6 +226,24 @@ OPTIONS" in (* Read the init file. *) let try_to_read_init_file filename = let config = read_config_file filename in + (* Replacement functions that raise better errors when + * parsing the init file. + *) + let int_of_string s = + try int_of_string s + with Invalid_argument _ -> + failwithf (f_"%s: could not parse '%s' in init file: expecting an integer") + filename s in + let float_of_string s = + try float_of_string s + with Invalid_argument _ -> + failwithf (f_"%s: could not parse '%s' in init file: expecting a number") + filename s in + let bool_of_string s = + try bool_of_string s + with Invalid_argument _ -> + failwithf (f_"%s: could not parse '%s' in init file: expecting %s") + filename s "true|false" in List.iter ( function | _, "display", mode -> display_mode := display_of_cli mode @@ -230,12 +255,14 @@ OPTIONS" in | _, "debug", filename -> debug_file := filename | _, "csv", filename -> set_csv filename | _, "csv-cpu", b -> csv_cpu := bool_of_string b + | _, "csv-mem", b -> csv_mem := bool_of_string b | _, "csv-block", b -> csv_block := bool_of_string b | _, "csv-net", b -> csv_net := bool_of_string b | _, "batch", b -> batch_mode := bool_of_string b | _, "secure", b -> secure_mode := bool_of_string b | _, "script", b -> script_mode := bool_of_string b | _, "stream", b -> stream_mode := bool_of_string b + | _, "block-in-bytes", b -> block_in_bytes := bool_of_string b | _, "end-time", t -> set_end_time t | _, "overwrite-init-file", "false" -> no_init_file () | lineno, key, _ -> @@ -264,7 +291,7 @@ OPTIONS" in prerr_endline (Libvirt.Virterror.to_string err); (* If non-root and no explicit connection URI, print a warning. *) if Unix.geteuid () <> 0 && name = None then ( - print_endline (s_"NB: If you want to monitor a local Xen hypervisor, you usually need to be root"); + print_endline (s_"NB: If you want to monitor a local hypervisor, you usually need to be root"); ); exit 1 in @@ -398,9 +425,17 @@ and rd_active = { (* 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. *) } @@ -430,14 +465,14 @@ let collect, clear_pcpu_display_data = let last_info = Hashtbl.create 13 in let last_time = ref (Unix.gettimeofday ()) in - (* Save vcpuinfo structures across redraws too (only for pCPU display). *) - let last_vcpu_info = Hashtbl.create 13 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 vcpu_info used by PCPUDisplay display_mode + (* Clear out pcpu_usages used by PCPUDisplay display_mode * when we switch back to TaskDisplay mode. *) - Hashtbl.clear last_vcpu_info + Hashtbl.clear last_pcpu_usages in let collect (conn, _, _, _, _, node_info, _, _) = @@ -500,7 +535,10 @@ let collect, clear_pcpu_display_data = 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 @@ -532,9 +570,14 @@ let collect, clear_pcpu_display_data = 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 } in + 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 @@ -562,10 +605,23 @@ let collect, clear_pcpu_display_data = 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 } in + 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 @@ -615,22 +671,46 @@ let collect, clear_pcpu_display_data = (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 vcpu_infos for this domain? *) - let prev_vcpu_infos = - try Some (Hashtbl.find last_vcpu_info domid) + (* 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_vcpu_info. *) - Hashtbl.replace last_vcpu_info domid vcpu_infos; - - (match prev_vcpu_infos with - | Some prev_vcpu_infos - when Array.length prev_vcpu_infos = Array.length vcpu_infos -> - Some (domid, name, nr_vcpus, vcpu_infos, prev_vcpu_infos, - cpumaps, maplen) + (* 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 @@ -642,47 +722,32 @@ let collect, clear_pcpu_display_data = (* Rearrange the data into a matrix. Major axis (down) is * pCPUs. Minor axis (right) is domains. At each node we store: - * cpu_time (on this pCPU only, nanosecs), - * average? (if set, then cpu_time is an average because the - * vCPU is pinned to more than one pCPU) - * running? (if set, we were instantaneously running on this pCPU) + * cpu_time hypervisor + domain (on this pCPU only, nanosecs), + * vcpu_time domain only (on this pCPU only, nanosecs). *) - let empty_node = (0L, false, false) in - let pcpus = Array.make_matrix nr_pcpus nr_doms empty_node in + 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, prev_vcpu_infos, - cpumaps, maplen) -> + fun di (domid, name, nr_vcpus, vcpu_infos, pcpu_usages, + prev_pcpu_usages, cpumaps, maplen) -> (* Which pCPUs can this dom run on? *) - for v = 0 to nr_vcpus-1 do - let pcpu = vcpu_infos.(v).D.cpu in (* instantaneous pCPU *) - let nr_poss_pcpus = ref 0 in (* how many pcpus can it run on? *) - for p = 0 to nr_pcpus-1 do - (* vcpu v can reside on pcpu p *) - if C.cpu_usable cpumaps maplen v p then - incr nr_poss_pcpus - done; - let nr_poss_pcpus = Int64.of_int !nr_poss_pcpus in - for p = 0 to nr_pcpus-1 do - (* vcpu v can reside on pcpu p *) - if C.cpu_usable cpumaps maplen v p then - let vcpu_time_on_pcpu = - vcpu_infos.(v).D.vcpu_time - -^ prev_vcpu_infos.(v).D.vcpu_time in - let vcpu_time_on_pcpu = - vcpu_time_on_pcpu /^ nr_poss_pcpus in - pcpus.(p).(di) <- - (vcpu_time_on_pcpu, nr_poss_pcpus > 1L, p = pcpu) - done - done + 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 CPU time used by each pCPU, for the %CPU column. *) + (* 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) in + let t = row.(di).(0) in cpu_time := !cpu_time +^ t done; Int64.to_float !cpu_time @@ -849,8 +914,12 @@ let redraw = (* Print domains. *) attron A.reverse; - mvaddstr header_lineno 0 - (pad cols " ID S RDRQ WRRQ RXBY TXBY %CPU %MEM TIME NAME"); + 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 @@ -858,14 +927,12 @@ let redraw = | (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_reqs in - let wr_req = Show.int64_option rd.rd_block_wr_reqs 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 = - 100L *^ rd.rd_info.D.memory /^ node_info.C.memory in - let percent_mem = Int64.to_float percent_mem 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 @@ -899,9 +966,9 @@ let redraw = let dom_names = String.concat "" ( List.map ( - fun (_, name, _, _, _, _, _) -> + fun (_, name, _, _, _, _, _, _) -> let len = String.length name in - let width = max (len+1) 7 in + let width = max (len+1) 12 in pad width name ) doms ) in @@ -918,20 +985,28 @@ let redraw = addch ' '; List.iteri ( - fun di (domid, name, _, _, _, _, _) -> - let t, is_average, is_running = pcpus.(p).(di) in + 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) 7 in - let str = + 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 - sprintf "%s%c%c " (Show.percent percent) - (if is_average then '=' else ' ') - (if is_running then '#' else ' ') + Show.percent percent ) in - addstr (pad width str); + 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; @@ -1203,7 +1278,11 @@ let write_csv_header () = (* These fields are repeated for each domain: *) [ "Domain ID"; "Domain name"; ] @ (if !csv_cpu then [ "CPU (ns)"; "%CPU"; ] else []) @ - (if !csv_block then [ "Block RDRQ"; "Block WRRQ"; ] 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 []) ) @@ -1257,9 +1336,12 @@ let append_csv (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_reqs; - string_of_int64_option rd.rd_block_wr_reqs; + 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; @@ -1283,7 +1365,10 @@ let dump_stdout 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 *) - printf " ID S RDRQ WRRQ RXBY TXBY %%CPU %%MEM TIME NAME\n"; + 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 = @@ -1300,18 +1385,16 @@ let dump_stdout 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_reqs = None then " 0" - else Show.int64_option rd.rd_block_rd_reqs in - let wr_req = if rd.rd_block_wr_reqs = None then " 0" - else Show.int64_option rd.rd_block_wr_reqs 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 = - 100L *^ rd.rd_info.D.memory /^ node_info.C.memory in - let percent_mem = Int64.to_float percent_mem 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" @@ -1401,6 +1484,7 @@ and get_key_press setup delay = else if k = Char.code '2' then toggle_net_display () else if k = Char.code '3' then toggle_block_display () else if k = Char.code 'W' then write_init_file () + else if k = Char.code 'B' then toggle_block_in_bytes_mode () else unknown_command k ) @@ -1546,6 +1630,12 @@ and toggle_block_display () = (* key 3 *) | TaskDisplay | NetDisplay -> BlockDisplay | BlockDisplay -> TaskDisplay +and toggle_block_in_bytes_mode () = (* key B *) + block_in_bytes := + match !block_in_bytes with + | false -> true + | true -> false + (* Write an init file. *) and write_init_file () = match !init_file with @@ -1674,6 +1764,7 @@ and show_help (_, _, _, _, _, _, hostname, key "q" (s_"Quit"); key "d s" (s_"Set update interval"); key "h" (s_"Help"); + key "B" (s_"toggle block info req/bytes"); (* Sort order. *) ignore (get_lineno ());