vmmd:
- fix fd leak (always close socket) - send first message (login) after renegotiation vmm_stats: - remove unneeded functionality (keeping old statistics around) - translate internal tap names to bridge names - gather statistics from vmmapi as well vmm_prometheus_stats: - new exporter of statistics to prometheus *: - fix typo in README - style
This commit is contained in:
parent
02be3f4528
commit
bdedadf689
|
@ -12,7 +12,7 @@ is used on top to (more gracefully) handle multiple connection, and to have a
|
||||||
watching thread (in `waitpid(2)`) for every virtual machine started by vmmd.
|
watching thread (in `waitpid(2)`) for every virtual machine started by vmmd.
|
||||||
|
|
||||||
It requires some pinned packages:
|
It requires some pinned packages:
|
||||||
- `asn1-combinators https://github.com/hannesm/ocaml-asn-combinators.git#enum`
|
- `asn1-combinators https://github.com/hannesm/ocaml-asn1-combinators.git#enum`
|
||||||
- `x509 https://github.com/hannesm/ocaml-x509.git#crl`
|
- `x509 https://github.com/hannesm/ocaml-x509.git#crl`
|
||||||
- `tls https://github.com/hannesm/ocaml-tls.git#changes`
|
- `tls https://github.com/hannesm/ocaml-tls.git#changes`
|
||||||
- on FreeBSD, `solo5-kernel-ukvm https://github.com/solo5/solo5.git`
|
- on FreeBSD, `solo5-kernel-ukvm https://github.com/solo5/solo5.git`
|
||||||
|
@ -146,7 +146,7 @@ And deploying (watch the output of the processes started on the server above!):
|
||||||
|
|
||||||
```
|
```
|
||||||
DEV> vmm_client cacert.pem hello.bundle hello.key SRV:1025
|
DEV> vmm_client cacert.pem hello.bundle hello.key SRV:1025
|
||||||
DEV> vmm_client cacert.pem admin.bundle hello.key SRV:1025 --db dev.db
|
DEV> vmm_client cacert.pem admin.bundle admin.key SRV:1025 --db dev.db
|
||||||
```
|
```
|
||||||
|
|
||||||
Commands are at the moment `info`, `statistics`, `destroy`, `attach`, `detach`,
|
Commands are at the moment `info`, `statistics`, `destroy`, `attach`, `detach`,
|
||||||
|
|
3
_tags
3
_tags
|
@ -1,4 +1,4 @@
|
||||||
true : bin_annot, safe_string, principal
|
true : bin_annot, safe_string, principal, color(always)
|
||||||
true : warn(+A-44)
|
true : warn(+A-44)
|
||||||
true : package(rresult logs ipaddr x509 tls bos hex ptime ptime.clock.os astring duration)
|
true : package(rresult logs ipaddr x509 tls bos hex ptime ptime.clock.os astring duration)
|
||||||
"src" : include
|
"src" : include
|
||||||
|
@ -11,6 +11,7 @@ true : package(rresult logs ipaddr x509 tls bos hex ptime ptime.clock.os astring
|
||||||
<app/*>: package(lwt.unix cmdliner logs.fmt fmt.cli logs.cli fmt.tty lwt ipaddr.unix)
|
<app/*>: package(lwt.unix cmdliner logs.fmt fmt.cli logs.cli fmt.tty lwt ipaddr.unix)
|
||||||
<app/vmm_client.{ml,native,byte}>: package(nocrypto tls.lwt nocrypto.lwt)
|
<app/vmm_client.{ml,native,byte}>: package(nocrypto tls.lwt nocrypto.lwt)
|
||||||
<app/vmmd.{ml,native,byte}>: package(tls.lwt)
|
<app/vmmd.{ml,native,byte}>: package(tls.lwt)
|
||||||
|
<app/vmm_prometheus_stats.{ml,native,byte}>: package(nocrypto tls.lwt nocrypto.lwt)
|
||||||
|
|
||||||
<provision/*>: package(cmdliner logs.fmt fmt.cli logs.cli fmt.tty asn1-combinators nocrypto.unix lwt)
|
<provision/*>: package(cmdliner logs.fmt fmt.cli logs.cli fmt.tty asn1-combinators nocrypto.unix lwt)
|
||||||
|
|
||||||
|
|
|
@ -16,9 +16,11 @@ let process db hdr data =
|
||||||
let r =
|
let r =
|
||||||
match hdr.tag with
|
match hdr.tag with
|
||||||
| x when x = Client.stat_msg_tag ->
|
| x when x = Client.stat_msg_tag ->
|
||||||
Client.decode_stat data >>= fun (ru, ifd) ->
|
Client.decode_stat data >>= fun (ru, vmm, ifd) ->
|
||||||
Logs.app (fun m -> m "statistics: %a %a"
|
Logs.app (fun m -> m "statistics: %a %a %a"
|
||||||
pp_rusage ru Fmt.(list ~sep:(unit ", ") pp_ifdata) ifd) ;
|
pp_rusage ru
|
||||||
|
Fmt.(list ~sep:(unit ", ") (pair ~sep:(unit ": ") string uint64)) vmm
|
||||||
|
Fmt.(list ~sep:(unit ", ") pp_ifdata) ifd) ;
|
||||||
Ok ()
|
Ok ()
|
||||||
| x when x = Client.log_msg_tag ->
|
| x when x = Client.log_msg_tag ->
|
||||||
Client.decode_log data >>= fun log ->
|
Client.decode_log data >>= fun log ->
|
||||||
|
@ -56,12 +58,12 @@ let rec read_tls_write_cons db t =
|
||||||
Logs.err (fun m -> m "error while reading %s" msg) ;
|
Logs.err (fun m -> m "error while reading %s" msg) ;
|
||||||
read_tls_write_cons db t
|
read_tls_write_cons db t
|
||||||
| Ok (hdr, data) ->
|
| Ok (hdr, data) ->
|
||||||
Logs.debug (fun m -> m "read from tls id %d %a tag %d data %a"
|
|
||||||
hdr.Vmm_wire.id Vmm_wire.pp_version hdr.Vmm_wire.version
|
|
||||||
hdr.Vmm_wire.tag Cstruct.hexdump_pp (Cstruct.of_string data)) ;
|
|
||||||
process db hdr data ;
|
process db hdr data ;
|
||||||
read_tls_write_cons db t)
|
read_tls_write_cons db t)
|
||||||
(fun _ -> Lwt.return_unit)
|
(fun e ->
|
||||||
|
Logs.err (fun m -> m "exception reading TLS stream %s"
|
||||||
|
(Printexc.to_string e)) ;
|
||||||
|
Tls_lwt.Unix.close t)
|
||||||
|
|
||||||
let rec read_cons_write_tls db t =
|
let rec read_cons_write_tls db t =
|
||||||
Lwt.catch (fun () ->
|
Lwt.catch (fun () ->
|
||||||
|
@ -99,9 +101,7 @@ let client cas host port cert priv_key db =
|
||||||
Tls_lwt.Unix.client_of_fd client (* ~host *) fd >>= fun t ->
|
Tls_lwt.Unix.client_of_fd client (* ~host *) fd >>= fun t ->
|
||||||
|
|
||||||
if Vmm_asn.contains_vm leaf || Vmm_asn.contains_crl leaf then
|
if Vmm_asn.contains_vm leaf || Vmm_asn.contains_crl leaf then
|
||||||
Vmm_tls.read_tls t >|= function
|
read_tls_write_cons db t
|
||||||
| Error (`Msg msg) -> Logs.err (fun m -> m "error while reading %s" msg)
|
|
||||||
| Ok (hdr, data) -> process db hdr data
|
|
||||||
else
|
else
|
||||||
(Logs.debug (fun m -> m "read/write games!") ;
|
(Logs.debug (fun m -> m "read/write games!") ;
|
||||||
Lwt.join [ read_tls_write_cons db t ; read_cons_write_tls db t ]))
|
Lwt.join [ read_tls_write_cons db t ; read_cons_write_tls db t ]))
|
||||||
|
|
|
@ -170,7 +170,7 @@ let setup_log =
|
||||||
|
|
||||||
let socket =
|
let socket =
|
||||||
let doc = "Socket to listen onto" in
|
let doc = "Socket to listen onto" in
|
||||||
Arg.(value & pos 0 string "" & info [] ~doc)
|
Arg.(required & pos 0 (some string) None & info [] ~doc)
|
||||||
|
|
||||||
let cmd =
|
let cmd =
|
||||||
Term.(ret (const jump $ setup_log $ socket)),
|
Term.(ret (const jump $ setup_log $ socket)),
|
||||||
|
|
344
app/vmm_prometheus_stats.ml
Normal file
344
app/vmm_prometheus_stats.ml
Normal file
|
@ -0,0 +1,344 @@
|
||||||
|
(* (c) 2017 Hannes Mehnert, all rights reserved *)
|
||||||
|
|
||||||
|
open Lwt.Infix
|
||||||
|
|
||||||
|
open Astring
|
||||||
|
|
||||||
|
open Vmm_core
|
||||||
|
|
||||||
|
let my_version = `WV0
|
||||||
|
|
||||||
|
let command = ref 1
|
||||||
|
|
||||||
|
let t : (Lwt_unix.file_descr * Lwt_unix.sockaddr * string) IM.t ref = ref IM.empty
|
||||||
|
|
||||||
|
module S = struct
|
||||||
|
type t = Lwt_unix.sockaddr
|
||||||
|
let compare : Lwt_unix.sockaddr -> Lwt_unix.sockaddr -> int = compare
|
||||||
|
end
|
||||||
|
|
||||||
|
module SM = Map.Make(S)
|
||||||
|
|
||||||
|
let count : int SM.t ref = ref SM.empty
|
||||||
|
|
||||||
|
let dec s =
|
||||||
|
match SM.find s !count with
|
||||||
|
| exception Not_found -> `Not_found
|
||||||
|
| 1 -> count := SM.remove s !count ; `Close
|
||||||
|
| x -> count := SM.add s (pred x) !count ; `Continue
|
||||||
|
|
||||||
|
let known_vms : string list ref = ref []
|
||||||
|
|
||||||
|
module P = struct
|
||||||
|
let p vm ?(typ = `Counter) name help value =
|
||||||
|
let t_s = function `Counter -> "counter" | `Gauge -> "gauge" in
|
||||||
|
let name = vm ^ "_" ^ name in
|
||||||
|
let p a v = String.concat ~sep:" " [ "#" ; a ; name ; v ] in
|
||||||
|
String.concat ~sep:"\n"
|
||||||
|
[ p "HELP" help ; p "TYPE" (t_s typ) ; name ^ " " ^ value ]
|
||||||
|
|
||||||
|
let tv (sec, usec) = Printf.sprintf "%Lu.%06d" sec usec
|
||||||
|
let i64 = Int64.to_string
|
||||||
|
|
||||||
|
let encode_ru vm ru =
|
||||||
|
let p = p vm in
|
||||||
|
String.concat ~sep:"\n"
|
||||||
|
[ p "utime" "user time used" (tv ru.utime) ;
|
||||||
|
p "stime" "system time used" (tv ru.stime) ;
|
||||||
|
p "maxrss" "maximum resident set" (i64 ru.maxrss) ;
|
||||||
|
p ~typ:`Gauge "ixrss" "shared memory" (i64 ru.ixrss) ;
|
||||||
|
p ~typ:`Gauge "idrss" "unshared data" (i64 ru.idrss) ;
|
||||||
|
p ~typ:`Gauge "isrss" "unshared stack" (i64 ru.isrss) ;
|
||||||
|
p "minflt" "page reclaims" (i64 ru.minflt) ;
|
||||||
|
p "maxflt" "page faults" (i64 ru.majflt) ;
|
||||||
|
p "nswap" "swaps" (i64 ru.nswap) ;
|
||||||
|
p "inblock" "block input ops" (i64 ru.inblock) ;
|
||||||
|
p "outblock" "block output ops" (i64 ru.outblock) ;
|
||||||
|
p "msgsnd" "messages send" (i64 ru.msgsnd) ;
|
||||||
|
p "msgrcv" "messages received" (i64 ru.msgrcv) ;
|
||||||
|
p "nsignals" "signals received" (i64 ru.nsignals) ;
|
||||||
|
p "nvcsw" "voluntary context switches" (i64 ru.nvcsw) ;
|
||||||
|
p "nivcsw" "involuntary context switches" (i64 ru.nivcsw)
|
||||||
|
]
|
||||||
|
|
||||||
|
let encode_vmm vm xs =
|
||||||
|
let p = p vm in
|
||||||
|
let massage s =
|
||||||
|
let cutted = match String.cut ~sep:"umber of " s with
|
||||||
|
| Some (_, r) -> r
|
||||||
|
| None -> s
|
||||||
|
in
|
||||||
|
let cutted = match String.cut ~sep:"[" cutted with
|
||||||
|
| None -> cutted
|
||||||
|
| Some (l, r) -> match String.cut ~sep:"]" r with
|
||||||
|
| None -> cutted
|
||||||
|
| Some (l', r) when r = "" -> l ^ "_" ^ l'
|
||||||
|
| Some (l', r') -> l ^ "_" ^ l' ^ "_" ^ r'
|
||||||
|
in
|
||||||
|
let cutted =
|
||||||
|
List.fold_left (fun str sep ->
|
||||||
|
match String.cut ~sep str with
|
||||||
|
| None -> str
|
||||||
|
| Some (l, r) -> l ^ r)
|
||||||
|
cutted [ "%" ; "/" ; "-" ]
|
||||||
|
in
|
||||||
|
String.concat ~sep:"_" (String.cuts ~sep:" " cutted)
|
||||||
|
in
|
||||||
|
String.concat ~sep:"\n"
|
||||||
|
(List.map (fun (k, v) -> p (massage k) k (i64 v)) xs)
|
||||||
|
|
||||||
|
let i32 = Int32.to_string
|
||||||
|
|
||||||
|
let encode_if vm ifd =
|
||||||
|
let p = p (vm ^ "_" ^ ifd.name) in
|
||||||
|
String.concat ~sep:"\n"
|
||||||
|
(* TODO: flags *)
|
||||||
|
[ p ~typ:`Gauge "send_length" "length of send queue" (i32 ifd.send_length) ;
|
||||||
|
p "max_send_length" "maximum length of send queue" (i32 ifd.max_send_length) ;
|
||||||
|
p "send_drops" "drops in send queue" (i32 ifd.send_drops) ;
|
||||||
|
p ~typ:`Gauge "mtu" "maximum transmission unit" (i32 ifd.mtu) ;
|
||||||
|
p ~typ:`Gauge "baudrate" "linespeed" (i64 ifd.baudrate) ;
|
||||||
|
p "vm_to_host_packets" "packets from vm" (i64 ifd.input_packets) ;
|
||||||
|
p "vm_to_host_errors" "packet errors from vm" (i64 ifd.input_errors) ;
|
||||||
|
p "vm_to_host_bytes" "bytes from vm" (i64 ifd.input_bytes) ;
|
||||||
|
p "vm_to_host_mcast" "packets from vm via multicast" (i64 ifd.input_mcast) ;
|
||||||
|
p "vm_to_host_dropped" "packets dropped from vm" (i64 ifd.input_dropped) ;
|
||||||
|
p "collisions" "collisions on csma interface" (i64 ifd.collisions) ;
|
||||||
|
p "host_to_vm_packets" "packets to vm" (i64 ifd.output_packets) ;
|
||||||
|
p "host_to_vm_errors" "packet errors to vm" (i64 ifd.output_errors) ;
|
||||||
|
p "host_to_vm_bytes" "bytes to vm" (i64 ifd.output_bytes) ;
|
||||||
|
p "host_to_vm_mcast" "packets to vm via multicast" (i64 ifd.output_mcast) ;
|
||||||
|
p "host_to_vm_dropped" "packets dropped to vm" (i64 ifd.output_dropped)
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
(* just a reminder whether we already sent the initial "info" or not *)
|
||||||
|
let f_done = ref false
|
||||||
|
|
||||||
|
let process db tls hdr data =
|
||||||
|
let open Vmm_wire in
|
||||||
|
let open Rresult.R.Infix in
|
||||||
|
if not (version_eq hdr.version my_version) then begin
|
||||||
|
Logs.err (fun m -> m "unknown wire protocol version") ; Lwt.return_unit
|
||||||
|
end else
|
||||||
|
match hdr.tag with
|
||||||
|
| x when x = Client.log_msg_tag && not !f_done ->
|
||||||
|
f_done := true ;
|
||||||
|
(* issue initial "info" to get all the vm names *)
|
||||||
|
let out = Vmm_wire.Client.cmd `Info !command my_version in
|
||||||
|
command := succ !command ;
|
||||||
|
Logs.debug (fun m -> m "writing %a over TLS" Cstruct.hexdump_pp (Cstruct.of_string out)) ;
|
||||||
|
Vmm_tls.write_tls tls out
|
||||||
|
| _ ->
|
||||||
|
let r =
|
||||||
|
match hdr.tag with
|
||||||
|
| x when x = Client.log_msg_tag ->
|
||||||
|
Client.decode_log data >>= fun (hdr, event) ->
|
||||||
|
let nam = translate_serial db hdr.Vmm_core.Log.name in
|
||||||
|
begin match event with
|
||||||
|
| `VM_start _ -> known_vms := nam :: !known_vms
|
||||||
|
| `VM_stop _ -> known_vms := List.filter (fun m -> m <> nam) !known_vms
|
||||||
|
| _ -> ()
|
||||||
|
end ;
|
||||||
|
Ok `None
|
||||||
|
| x when x = Client.info_msg_tag ->
|
||||||
|
Client.decode_info data >>= fun vms ->
|
||||||
|
let vms = List.map (fun (name, _, _, _) -> translate_serial db name) vms in
|
||||||
|
known_vms := vms ;
|
||||||
|
Ok `None
|
||||||
|
| x when x = Client.stat_msg_tag ->
|
||||||
|
Client.decode_stat data >>= fun (ru, vmm, ifd) ->
|
||||||
|
begin match IM.find hdr.id !t with
|
||||||
|
| exception Not_found -> Logs.err (fun m -> m "unexpected reply") ; Ok `None
|
||||||
|
| (fd, s, vm) ->
|
||||||
|
t := IM.remove hdr.id !t ;
|
||||||
|
let out = String.concat ~sep:"\n" (P.encode_ru vm ru :: P.encode_vmm vm vmm :: List.map (P.encode_if vm) ifd @ [""]) in
|
||||||
|
Ok (`Stat (fd, s, out))
|
||||||
|
end
|
||||||
|
| x when x = fail_tag ->
|
||||||
|
let res =
|
||||||
|
match IM.find hdr.id !t with
|
||||||
|
| exception Not_found -> `None
|
||||||
|
| (fd, s, _) -> `Sockaddr (fd, s)
|
||||||
|
in
|
||||||
|
t := IM.remove hdr.id !t ;
|
||||||
|
decode_str data >>= fun (msg, _) ->
|
||||||
|
Logs.err (fun m -> m "failed %s" msg) ;
|
||||||
|
Ok res
|
||||||
|
| x -> Rresult.R.error_msgf "ignoring header tag %02X" x
|
||||||
|
in
|
||||||
|
let d (fd, s) = match dec s with
|
||||||
|
| `Continue -> Lwt.return_unit
|
||||||
|
| `Close -> Lwt_unix.close fd
|
||||||
|
| `Not_found -> Logs.err (fun m -> m "sockaddr not found") ; Lwt.return_unit
|
||||||
|
in
|
||||||
|
let open Lwt.Infix in
|
||||||
|
match r with
|
||||||
|
| Ok `None -> Lwt.return_unit
|
||||||
|
| Ok (`Sockaddr s) -> d s
|
||||||
|
| Ok (`Stat (fd, s, out)) -> Vmm_lwt.write_raw fd out >>= fun () -> d (fd, s)
|
||||||
|
| Error (`Msg msg) -> Logs.err (fun m -> m "error while processing: %s" msg) ; Lwt.return_unit
|
||||||
|
|
||||||
|
let rec tls_listener db tls =
|
||||||
|
Lwt.catch (fun () ->
|
||||||
|
Vmm_tls.read_tls tls >>= function
|
||||||
|
| Error (`Msg msg) ->
|
||||||
|
Logs.err (fun m -> m "error while reading %s" msg) ;
|
||||||
|
Lwt.return (Ok ())
|
||||||
|
| Ok (hdr, data) ->
|
||||||
|
process db tls hdr data >>= fun () ->
|
||||||
|
Lwt.return (Ok ()))
|
||||||
|
(fun e ->
|
||||||
|
Logs.err (fun m -> m "received exception in read_tls: %s" (Printexc.to_string e)) ;
|
||||||
|
Lwt.return (Error ())) >>= function
|
||||||
|
| Ok () -> tls_listener db tls
|
||||||
|
| Error () -> Lwt.return_unit
|
||||||
|
|
||||||
|
let hdr =
|
||||||
|
String.concat ~sep:"\r\n"
|
||||||
|
[ "HTTP/1.1 200 OK" ;
|
||||||
|
"Content-Type: text/plain; version=0.0.4" ;
|
||||||
|
"\r\n" ]
|
||||||
|
|
||||||
|
(* wait for TCP connection, once received request stats from vmmd, and loop *)
|
||||||
|
let rec tcp_listener db tcp tls =
|
||||||
|
Lwt_unix.accept tcp >>= fun (cs, sockaddr) ->
|
||||||
|
Vmm_lwt.write_raw cs hdr >>= fun () ->
|
||||||
|
let l = List.length !known_vms in
|
||||||
|
let ip, port = match sockaddr with Lwt_unix.ADDR_INET (ip, port) -> ip, port | _ -> invalid_arg "unexpected" in
|
||||||
|
Logs.info (fun m -> m "connection from %s:%d with %d known" (Unix.string_of_inet_addr ip) port l) ;
|
||||||
|
(if l = 0 then
|
||||||
|
Lwt_unix.close cs
|
||||||
|
else begin
|
||||||
|
count := SM.add sockaddr (List.length !known_vms) !count ;
|
||||||
|
Lwt_list.iter_s
|
||||||
|
(fun vm ->
|
||||||
|
let vm_id = translate_name db vm in
|
||||||
|
let out = Vmm_wire.Client.cmd `Statistics ~arg:vm_id !command my_version in
|
||||||
|
t := IM.add !command (cs, sockaddr, vm) !t ;
|
||||||
|
command := succ !command ;
|
||||||
|
Vmm_tls.write_tls tls out)
|
||||||
|
!known_vms
|
||||||
|
end) >>= fun () ->
|
||||||
|
tcp_listener db tcp tls
|
||||||
|
|
||||||
|
let client cas host port cert priv_key db listen_ip listen_port =
|
||||||
|
Nocrypto_entropy_lwt.initialize () >>= fun () ->
|
||||||
|
let auth = if Sys.is_directory cas then `Ca_dir cas else `Ca_file cas in
|
||||||
|
X509_lwt.authenticator auth >>= fun authenticator ->
|
||||||
|
Lwt.catch (fun () ->
|
||||||
|
(* start TCP listening socket *)
|
||||||
|
let tcp = Lwt_unix.(socket PF_INET SOCK_STREAM 0) in
|
||||||
|
Lwt_unix.(setsockopt tcp SO_REUSEADDR true) ;
|
||||||
|
let addr = Lwt_unix.ADDR_INET (Ipaddr_unix.V4.to_inet_addr listen_ip, listen_port) in
|
||||||
|
Lwt_unix.bind tcp addr >>= fun () ->
|
||||||
|
Lwt_unix.listen tcp 1 ;
|
||||||
|
|
||||||
|
(* setup remote connection to VMMD *)
|
||||||
|
Lwt_unix.gethostbyname host >>= fun host_entry ->
|
||||||
|
let host_inet_addr = Array.get host_entry.Lwt_unix.h_addr_list 0 in
|
||||||
|
let fd = Lwt_unix.socket host_entry.Lwt_unix.h_addrtype Lwt_unix.SOCK_STREAM 0 in
|
||||||
|
|
||||||
|
Lwt_unix.connect fd (Lwt_unix.ADDR_INET (host_inet_addr, port)) >>= fun _ ->
|
||||||
|
X509_lwt.private_of_pems ~cert ~priv_key >>= fun cert ->
|
||||||
|
let certificates = `Single cert in
|
||||||
|
let client = Tls.Config.client ~reneg:true ~certificates ~authenticator () in
|
||||||
|
Tls_lwt.Unix.client_of_fd client (* ~host *) fd >>= fun tls ->
|
||||||
|
|
||||||
|
(* loop on both tcp and tls connections *)
|
||||||
|
Lwt.join [ tcp_listener db tcp tls ; tls_listener db tls ])
|
||||||
|
(fun exn ->
|
||||||
|
Logs.err (fun m -> m "failed to establish TLS connection: %s"
|
||||||
|
(Printexc.to_string exn)) ;
|
||||||
|
Lwt.return_unit)
|
||||||
|
|
||||||
|
let run_client _ cas cert key (host, port) db listen_ip listen_port =
|
||||||
|
Printexc.register_printer (function
|
||||||
|
| Tls_lwt.Tls_alert x -> Some ("TLS alert: " ^ Tls.Packet.alert_type_to_string x)
|
||||||
|
| Tls_lwt.Tls_failure f -> Some ("TLS failure: " ^ Tls.Engine.string_of_failure f)
|
||||||
|
| _ -> None) ;
|
||||||
|
Sys.(set_signal sigpipe Signal_ignore) ;
|
||||||
|
let db =
|
||||||
|
let open Rresult.R.Infix in
|
||||||
|
match db with
|
||||||
|
| None -> []
|
||||||
|
| Some db ->
|
||||||
|
match Bos.OS.File.read_lines (Fpath.v db) >>= parse_db with
|
||||||
|
| Ok db -> db
|
||||||
|
| Error (`Msg m) -> Logs.warn (fun f -> f "couldn't parse database %s" m) ; []
|
||||||
|
in
|
||||||
|
Lwt_main.run (client cas host port cert key db listen_ip listen_port)
|
||||||
|
|
||||||
|
let setup_log style_renderer level =
|
||||||
|
Fmt_tty.setup_std_outputs ?style_renderer ();
|
||||||
|
Logs.set_level level;
|
||||||
|
Logs.set_reporter (Logs_fmt.reporter ~dst:Format.std_formatter ())
|
||||||
|
|
||||||
|
open Cmdliner
|
||||||
|
|
||||||
|
let setup_log =
|
||||||
|
Term.(const setup_log
|
||||||
|
$ Fmt_cli.style_renderer ()
|
||||||
|
$ Logs_cli.level ())
|
||||||
|
|
||||||
|
let host_port : (string * int) Arg.converter =
|
||||||
|
let parse s =
|
||||||
|
match String.cut ~sep:":" s with
|
||||||
|
| None -> `Error "broken: no port specified"
|
||||||
|
| Some (hostname, port) ->
|
||||||
|
try
|
||||||
|
`Ok (hostname, int_of_string port)
|
||||||
|
with
|
||||||
|
Not_found -> `Error "failed to parse port"
|
||||||
|
in
|
||||||
|
parse, fun ppf (h, p) -> Format.fprintf ppf "%s:%d" h p
|
||||||
|
|
||||||
|
let cas =
|
||||||
|
let doc = "The full path to PEM encoded certificate authorities. Can either be a FILE or a DIRECTORY." in
|
||||||
|
Arg.(required & pos 0 (some string) None & info [] ~docv:"FILE" ~doc)
|
||||||
|
|
||||||
|
let client_cert =
|
||||||
|
let doc = "Use a client certificate chain" in
|
||||||
|
Arg.(required & pos 1 (some file) None & info [] ~doc)
|
||||||
|
|
||||||
|
let client_key =
|
||||||
|
let doc = "Use a client key" in
|
||||||
|
Arg.(required & pos 2 (some file) None & info [] ~doc)
|
||||||
|
|
||||||
|
let destination =
|
||||||
|
Arg.(required & pos 3 (some host_port) None & info [] ~docv:"destination"
|
||||||
|
~doc:"the destination hostname:port to connect to")
|
||||||
|
|
||||||
|
let ip : Ipaddr.V4.t Arg.converter =
|
||||||
|
let parse s =
|
||||||
|
try
|
||||||
|
`Ok (Ipaddr.V4.of_string_exn s)
|
||||||
|
with
|
||||||
|
Not_found -> `Error "broken"
|
||||||
|
in
|
||||||
|
parse, Ipaddr.V4.pp_hum
|
||||||
|
|
||||||
|
let address =
|
||||||
|
let doc = "Address to listen on" in
|
||||||
|
Arg.(value & opt ip (Ipaddr.V4.of_string_exn "127.0.0.1") & info [ "address" ] ~doc)
|
||||||
|
|
||||||
|
let port =
|
||||||
|
let doc = "TCP port to listen on" in
|
||||||
|
Arg.(value & opt int 9080 & info [ "port" ] ~doc)
|
||||||
|
|
||||||
|
let db =
|
||||||
|
let doc = "Certificate database" in
|
||||||
|
Arg.(value & opt (some file) None & info [ "db" ] ~doc)
|
||||||
|
|
||||||
|
let cmd =
|
||||||
|
let doc = "VMM TLS client" in
|
||||||
|
let man = [
|
||||||
|
`S "DESCRIPTION" ;
|
||||||
|
`P "$(tname) connects to a server and initiates a TLS handshake" ]
|
||||||
|
in
|
||||||
|
Term.(pure run_client $ setup_log $ cas $ client_cert $ client_key $ destination $ db $ address $ port),
|
||||||
|
Term.info "vmm_client" ~version:"%%VERSION_NUM%%" ~doc ~man
|
||||||
|
|
||||||
|
let () =
|
||||||
|
match Term.eval cmd
|
||||||
|
with `Error _ -> exit 1 | _ -> exit 0
|
22
app/vmmd.ml
22
app/vmmd.ml
|
@ -8,6 +8,7 @@ let write_tls state t data =
|
||||||
let state', out = Vmm_engine.handle_disconnect !state t in
|
let state', out = Vmm_engine.handle_disconnect !state t in
|
||||||
state := state' ;
|
state := state' ;
|
||||||
Lwt_list.iter_s (fun (s, data) -> Vmm_lwt.write_raw s data) out >>= fun () ->
|
Lwt_list.iter_s (fun (s, data) -> Vmm_lwt.write_raw s data) out >>= fun () ->
|
||||||
|
Tls_lwt.Unix.close (fst t) >>= fun () ->
|
||||||
raise e)
|
raise e)
|
||||||
|
|
||||||
let to_ipaddr (_, sa) = match sa with
|
let to_ipaddr (_, sa) = match sa with
|
||||||
|
@ -31,17 +32,20 @@ let handle ca state t =
|
||||||
let time = Unix.gettimeofday () in
|
let time = Unix.gettimeofday () in
|
||||||
X509.Authenticator.chain_of_trust ~time ~crls:!state.Vmm_engine.crls [ca]
|
X509.Authenticator.chain_of_trust ~time ~crls:!state.Vmm_engine.crls [ca]
|
||||||
in
|
in
|
||||||
Lwt.catch (fun () ->
|
Lwt.catch
|
||||||
Tls_lwt.Unix.reneg ~authenticator (fst t))
|
(fun () -> Tls_lwt.Unix.reneg ~authenticator (fst t))
|
||||||
(fun e ->
|
(fun e ->
|
||||||
(match e with
|
(match e with
|
||||||
| Tls_lwt.Tls_alert a -> Logs.err (fun m -> m "TLS ALERT %s" (Tls.Packet.alert_type_to_string a))
|
| Tls_lwt.Tls_alert a -> Logs.err (fun m -> m "TLS ALERT %s" (Tls.Packet.alert_type_to_string a))
|
||||||
| Tls_lwt.Tls_failure f -> Logs.err (fun m -> m "TLS FAILURE %s" (Tls.Engine.string_of_failure f))
|
| Tls_lwt.Tls_failure f -> Logs.err (fun m -> m "TLS FAILURE %s" (Tls.Engine.string_of_failure f))
|
||||||
| exn -> Logs.err (fun m -> m "%s" (Printexc.to_string exn))) ;
|
| exn -> Logs.err (fun m -> m "%s" (Printexc.to_string exn))) ;
|
||||||
|
Tls_lwt.Unix.close (fst t) >>= fun () ->
|
||||||
Lwt.fail e) >>= fun () ->
|
Lwt.fail e) >>= fun () ->
|
||||||
(match Tls_lwt.Unix.epoch (fst t) with
|
(match Tls_lwt.Unix.epoch (fst t) with
|
||||||
| `Ok epoch -> Lwt.return epoch.Tls.Core.peer_certificate_chain
|
| `Ok epoch -> Lwt.return epoch.Tls.Core.peer_certificate_chain
|
||||||
| `Error -> Lwt.fail_with "error while getting epoch") >>= fun chain ->
|
| `Error ->
|
||||||
|
Tls_lwt.Unix.close (fst t) >>= fun () ->
|
||||||
|
Lwt.fail_with "error while getting epoch") >>= fun chain ->
|
||||||
match Vmm_engine.handle_initial !state t (to_ipaddr t) chain ca with
|
match Vmm_engine.handle_initial !state t (to_ipaddr t) chain ca with
|
||||||
| Ok (state', outs, next) ->
|
| Ok (state', outs, next) ->
|
||||||
state := state' ;
|
state := state' ;
|
||||||
|
@ -75,11 +79,13 @@ let handle ca state t =
|
||||||
process state out >>= fun () ->
|
process state out >>= fun () ->
|
||||||
loop ()
|
loop ()
|
||||||
in
|
in
|
||||||
Lwt.catch loop (fun e ->
|
Lwt.catch loop
|
||||||
let state', cons = Vmm_engine.handle_disconnect !state t in
|
(fun e ->
|
||||||
state := state' ;
|
let state', cons = Vmm_engine.handle_disconnect !state t in
|
||||||
Lwt_list.iter_s (fun (s, data) -> Vmm_lwt.write_raw s data) cons >>= fun () ->
|
state := state' ;
|
||||||
raise e)
|
Lwt_list.iter_s (fun (s, data) -> Vmm_lwt.write_raw s data) cons >>= fun () ->
|
||||||
|
Tls_lwt.Unix.close (fst t) >>= fun () ->
|
||||||
|
raise e)
|
||||||
| `Close socks ->
|
| `Close socks ->
|
||||||
Logs.debug (fun m -> m "closing session with %d active ones" (List.length socks)) ;
|
Logs.debug (fun m -> m "closing session with %d active ones" (List.length socks)) ;
|
||||||
Lwt_list.iter_s (fun (t, _) -> Tls_lwt.Unix.close t) socks >>= fun () ->
|
Lwt_list.iter_s (fun (t, _) -> Tls_lwt.Unix.close t) socks >>= fun () ->
|
||||||
|
|
|
@ -3,6 +3,13 @@ open Ocamlbuild_plugin
|
||||||
let to_opt = List.fold_left (fun acc x -> [A "-ccopt"; A x] @ acc) []
|
let to_opt = List.fold_left (fun acc x -> [A "-ccopt"; A x] @ acc) []
|
||||||
let ccopt = to_opt [ "-O3" ; "-Wall" ]
|
let ccopt = to_opt [ "-O3" ; "-Wall" ]
|
||||||
|
|
||||||
|
let os = Ocamlbuild_pack.My_unix.run_and_read "uname -s"
|
||||||
|
|
||||||
|
let vmm_lib = match os with
|
||||||
|
| "FreeBSD\n" -> [A "-cclib"; A "-lvmmapi"]
|
||||||
|
| _ -> []
|
||||||
|
|
||||||
|
|
||||||
let () =
|
let () =
|
||||||
dispatch begin function
|
dispatch begin function
|
||||||
| After_rules ->
|
| After_rules ->
|
||||||
|
@ -11,7 +18,8 @@ let () =
|
||||||
(S ([A "-dllib"; A "-lvmm_stats_stubs"]));
|
(S ([A "-dllib"; A "-lvmm_stats_stubs"]));
|
||||||
flag ["link"; "library"; "ocaml"; "native"; "use_vmm_stats"]
|
flag ["link"; "library"; "ocaml"; "native"; "use_vmm_stats"]
|
||||||
(S ([A "-cclib"; A "-lvmm_stats_stubs"]));
|
(S ([A "-cclib"; A "-lvmm_stats_stubs"]));
|
||||||
flag ["link"; "ocaml"; "link_vmm_stats"] (A "stats/libvmm_stats_stubs.a");
|
flag ["link"; "ocaml"; "link_vmm_stats"]
|
||||||
|
(S ([A "stats/libvmm_stats_stubs.a"] @ vmm_lib));
|
||||||
dep ["link"; "ocaml"; "use_vmm_stats"] ["stats/libvmm_stats_stubs.a"];
|
dep ["link"; "ocaml"; "use_vmm_stats"] ["stats/libvmm_stats_stubs.a"];
|
||||||
dep ["link"; "ocaml"; "link_vmm_stats"] ["stats/libvmm_stats_stubs.a"];
|
dep ["link"; "ocaml"; "link_vmm_stats"] ["stats/libvmm_stats_stubs.a"];
|
||||||
| _ -> ()
|
| _ -> ()
|
||||||
|
|
|
@ -18,4 +18,5 @@ let () =
|
||||||
Pkg.bin "provision/vmm_gen_ca" ;
|
Pkg.bin "provision/vmm_gen_ca" ;
|
||||||
Pkg.clib "stats/libvmm_stats_stubs.clib" ;
|
Pkg.clib "stats/libvmm_stats_stubs.clib" ;
|
||||||
Pkg.bin "stats/vmm_stats_lwt" ;
|
Pkg.bin "stats/vmm_stats_lwt" ;
|
||||||
|
Pkg.bin "app/vmm_prometheus_stats" ;
|
||||||
]
|
]
|
||||||
|
|
|
@ -177,6 +177,14 @@ let exec dir vm fifo vmimage taps =
|
||||||
Logs.debug (fun m -> m "creating process");
|
Logs.debug (fun m -> m "creating process");
|
||||||
let pid = create_process prog line stdout stdout in
|
let pid = create_process prog line stdout stdout in
|
||||||
Logs.debug (fun m -> m "created process %d: %a" pid Bos.Cmd.pp cmd) ;
|
Logs.debug (fun m -> m "created process %d: %a" pid Bos.Cmd.pp cmd) ;
|
||||||
|
(* on FreeBSD we need to chmod g+rw /dev/vmm/ukvm$pid to run
|
||||||
|
bhyvectl --get-stats --vm=ukvm$pid as non-priviliged user *)
|
||||||
|
(Lazy.force (uname ()) >>= fun (sys, _) ->
|
||||||
|
match sys with
|
||||||
|
| x when x = "FreeBSD" ->
|
||||||
|
let dev = "/dev/vmm/ukvm" ^ string_of_int pid in
|
||||||
|
Bos.OS.Cmd.run Bos.Cmd.(v "chmod" % "g+rw" % dev)
|
||||||
|
| _ -> Ok ()) >>= fun () ->
|
||||||
Ok { config = vm ; cmd ; pid ; taps ; stdout }
|
Ok { config = vm ; cmd ; pid ; taps ; stdout }
|
||||||
with
|
with
|
||||||
Unix.Unix_error (e, _, _) ->
|
Unix.Unix_error (e, _, _) ->
|
||||||
|
|
|
@ -227,6 +227,11 @@ let pp_vm ppf vm =
|
||||||
vm.pid Fmt.(list ~sep:(unit ", ") string) vm.taps
|
vm.pid Fmt.(list ~sep:(unit ", ") string) vm.taps
|
||||||
Bos.Cmd.pp vm.cmd
|
Bos.Cmd.pp vm.cmd
|
||||||
|
|
||||||
|
let translate_tap vm tap =
|
||||||
|
match List.filter (fun (t, b) -> tap = t) (List.combine vm.taps vm.config.network) with
|
||||||
|
| [ (_, b) ] -> Some b
|
||||||
|
| _ -> None
|
||||||
|
|
||||||
let identifier serial =
|
let identifier serial =
|
||||||
match Hex.of_cstruct @@ Nocrypto.Hash.SHA256.digest @@
|
match Hex.of_cstruct @@ Nocrypto.Hash.SHA256.digest @@
|
||||||
Nocrypto.Numeric.Z.to_cstruct_be @@ serial
|
Nocrypto.Numeric.Z.to_cstruct_be @@ serial
|
||||||
|
|
|
@ -17,7 +17,7 @@ type ('a, 'b) t = {
|
||||||
console_version : Vmm_wire.version ;
|
console_version : Vmm_wire.version ;
|
||||||
stats_socket : 'a option ;
|
stats_socket : 'a option ;
|
||||||
stats_counter : int ;
|
stats_counter : int ;
|
||||||
stats_requests : ('b * int) IM.t ;
|
stats_requests : ('b * int * (string -> string option)) IM.t ;
|
||||||
stats_version : Vmm_wire.version ;
|
stats_version : Vmm_wire.version ;
|
||||||
log_socket : 'a ;
|
log_socket : 'a ;
|
||||||
log_counter : int ;
|
log_counter : int ;
|
||||||
|
@ -256,8 +256,9 @@ let handle_command t s prefix perms hdr buf =
|
||||||
| None -> Error (`Msg "no statistics available")
|
| None -> Error (`Msg "no statistics available")
|
||||||
| Some _ -> match Vmm_resources.find_vm t.resources arg with
|
| Some _ -> match Vmm_resources.find_vm t.resources arg with
|
||||||
| Some vm ->
|
| Some vm ->
|
||||||
let stat_out = Vmm_wire.Stats.stat t.stats_counter t.stats_version vm.Vmm_core.pid in
|
let stat_out = Vmm_wire.Stats.stat t.stats_counter t.stats_version vm.pid in
|
||||||
let stats_requests = IM.add t.stats_counter (s, hdr.Vmm_wire.id) t.stats_requests in
|
let d = (s, hdr.Vmm_wire.id, translate_tap vm) in
|
||||||
|
let stats_requests = IM.add t.stats_counter d t.stats_requests in
|
||||||
Ok ({ t with stats_counter = succ t.stats_counter ; stats_requests },
|
Ok ({ t with stats_counter = succ t.stats_counter ; stats_requests },
|
||||||
stat t stat_out)
|
stat t stat_out)
|
||||||
| _ -> Error (`Msg ("statistics: not found " ^ buf))
|
| _ -> Error (`Msg ("statistics: not found " ^ buf))
|
||||||
|
@ -373,31 +374,33 @@ let handle_initial t s addr chain ca =
|
||||||
(* TODO here: inspect top-level-cert of chain.
|
(* TODO here: inspect top-level-cert of chain.
|
||||||
may need to create bridges and/or block device subdirectory (zfs create) *)
|
may need to create bridges and/or block device subdirectory (zfs create) *)
|
||||||
let prefix = List.map id chain in
|
let prefix = List.map id chain in
|
||||||
let t, out = log t (Log.hdr prefix (id leaf), `Login addr) in
|
let login_hdr, login_ev = Log.hdr prefix (id leaf), `Login addr in
|
||||||
|
let t, out = log t (login_hdr, login_ev) in
|
||||||
|
let initial_out = `Tls (s, Vmm_wire.Client.log login_hdr login_ev t.client_version) in
|
||||||
Vmm_asn.permissions_of_cert asn_version leaf >>= fun perms ->
|
Vmm_asn.permissions_of_cert asn_version leaf >>= fun perms ->
|
||||||
if List.mem `Image perms && Vmm_asn.contains_vm leaf then
|
(if List.mem `Image perms && Vmm_asn.contains_vm leaf then
|
||||||
handle_create t prefix chain leaf >>= fun (file, cont) ->
|
handle_create t prefix chain leaf >>= fun (file, cont) ->
|
||||||
let cons = Vmm_wire.Console.add t.console_counter t.console_version file in
|
let cons = Vmm_wire.Console.add t.console_counter t.console_version file in
|
||||||
Ok ({ t with console_counter = succ t.console_counter },
|
Ok ({ t with console_counter = succ t.console_counter },
|
||||||
`Raw (t.console_socket, cons) :: out,
|
[ `Raw (t.console_socket, cons) ],
|
||||||
`Create cont)
|
`Create cont)
|
||||||
else if List.mem `Crl perms && Vmm_asn.contains_crl leaf then
|
else if List.mem `Crl perms && Vmm_asn.contains_crl leaf then
|
||||||
handle_revocation t s leaf chain ca prefix
|
handle_revocation t s leaf chain ca prefix
|
||||||
else
|
else
|
||||||
let log_attached =
|
let log_attached =
|
||||||
if cmd_allowed perms `Log then
|
if cmd_allowed perms `Log then
|
||||||
let pre = string_of_id prefix in
|
let pre = string_of_id prefix in
|
||||||
let v = match String.Map.find pre t.log_attached with
|
let v = match String.Map.find pre t.log_attached with
|
||||||
| None -> []
|
| None -> []
|
||||||
| Some xs -> xs
|
| Some xs -> xs
|
||||||
in
|
in
|
||||||
String.Map.add pre ((s, id leaf) :: v) t.log_attached
|
String.Map.add pre ((s, id leaf) :: v) t.log_attached
|
||||||
else
|
else
|
||||||
t.log_attached
|
t.log_attached
|
||||||
in
|
in
|
||||||
Ok ({ t with log_attached },
|
Ok ({ t with log_attached }, [], `Loop (prefix, perms))
|
||||||
out,
|
) >>= fun (t, outs, res) ->
|
||||||
`Loop (prefix, perms))
|
Ok (t, initial_out :: out @ outs, res)
|
||||||
|
|
||||||
let handle_stat state hdr data =
|
let handle_stat state hdr data =
|
||||||
let open Vmm_wire in
|
let open Vmm_wire in
|
||||||
|
@ -411,14 +414,30 @@ let handle_stat state hdr data =
|
||||||
| exception Not_found ->
|
| exception Not_found ->
|
||||||
Logs.err (fun m -> m "couldn't find stat request") ;
|
Logs.err (fun m -> m "couldn't find stat request") ;
|
||||||
state, []
|
state, []
|
||||||
| (s, req_id) ->
|
| (s, req_id, f) ->
|
||||||
let stats_requests = IM.remove hdr.id state.stats_requests in
|
let stats_requests = IM.remove hdr.id state.stats_requests in
|
||||||
let state = { state with stats_requests } in
|
let state = { state with stats_requests } in
|
||||||
let out =
|
let out =
|
||||||
match Stats.int_to_op hdr.tag with
|
match Stats.int_to_op hdr.tag with
|
||||||
| Some Stats.StatReply ->
|
| Some Stats.StatReply ->
|
||||||
let out = Client.stat data req_id state.client_version in
|
begin match Stats.decode_stats (Cstruct.of_string data) with
|
||||||
[ `Tls (s, out) ]
|
| Ok (ru, vmm, ifs) ->
|
||||||
|
let ifs =
|
||||||
|
List.map
|
||||||
|
(fun x ->
|
||||||
|
match f x.name with
|
||||||
|
| Some name -> { x with name }
|
||||||
|
| None -> x)
|
||||||
|
ifs
|
||||||
|
in
|
||||||
|
let data = Cstruct.to_string (Stats.encode_stats (ru, vmm, ifs)) in
|
||||||
|
let out = Client.stat data req_id state.client_version in
|
||||||
|
[ `Tls (s, out) ]
|
||||||
|
| Error (`Msg msg) ->
|
||||||
|
Logs.err (fun m -> m "error %s while decode statistics" msg) ;
|
||||||
|
let out = fail req_id state.client_version in
|
||||||
|
[ `Tls (s, out) ]
|
||||||
|
end
|
||||||
| None when hdr.tag = fail_tag ->
|
| None when hdr.tag = fail_tag ->
|
||||||
let out = fail ~msg:data req_id state.client_version in
|
let out = fail ~msg:data req_id state.client_version in
|
||||||
[ `Tls (s, out) ]
|
[ `Tls (s, out) ]
|
||||||
|
|
|
@ -13,7 +13,6 @@ let ret = function
|
||||||
| Unix.WSTOPPED s -> `Stop s
|
| Unix.WSTOPPED s -> `Stop s
|
||||||
|
|
||||||
let wait_and_clear pid stdout =
|
let wait_and_clear pid stdout =
|
||||||
let open Lwt.Infix in
|
|
||||||
Lwt_unix.waitpid [] pid >>= fun (_, s) ->
|
Lwt_unix.waitpid [] pid >>= fun (_, s) ->
|
||||||
Logs.debug (fun m -> m "pid %d exited: %a" pid pp_process_status s) ;
|
Logs.debug (fun m -> m "pid %d exited: %a" pid pp_process_status s) ;
|
||||||
Vmm_commands.close_no_err stdout ;
|
Vmm_commands.close_no_err stdout ;
|
||||||
|
|
|
@ -326,7 +326,7 @@ module Stats = struct
|
||||||
|
|
||||||
let decode_ifdata buf =
|
let decode_ifdata buf =
|
||||||
decode_string buf >>= fun (name, l) ->
|
decode_string buf >>= fun (name, l) ->
|
||||||
check_exact buf (l + 116) >>= fun () ->
|
check_len buf (l + 116) >>= fun () ->
|
||||||
let cs = Cstruct.shift buf l in
|
let cs = Cstruct.shift buf l in
|
||||||
let flags = Cstruct.BE.get_uint32 cs 0
|
let flags = Cstruct.BE.get_uint32 cs 0
|
||||||
and send_length = Cstruct.BE.get_uint32 cs 4
|
and send_length = Cstruct.BE.get_uint32 cs 4
|
||||||
|
@ -369,23 +369,53 @@ module Stats = struct
|
||||||
in
|
in
|
||||||
Cstruct.to_string r
|
Cstruct.to_string r
|
||||||
|
|
||||||
let encode_stats (ru, ifd) =
|
let encode_int64 i =
|
||||||
|
let cs = Cstruct.create 8 in
|
||||||
|
Cstruct.BE.set_uint64 cs 0 i ;
|
||||||
|
cs
|
||||||
|
|
||||||
|
let decode_int64 ?(off = 0) cs =
|
||||||
|
check_len cs (8 + off) >>= fun () ->
|
||||||
|
Ok (Cstruct.BE.get_uint64 cs off)
|
||||||
|
|
||||||
|
let encode_vmm_stats xs =
|
||||||
|
encode_int (List.length xs) ::
|
||||||
|
List.flatten
|
||||||
|
(List.map (fun (k, v) -> [ fst (encode_string k) ; encode_int64 v ]) xs)
|
||||||
|
|
||||||
|
let decode_vmm_stats cs =
|
||||||
|
let rec go acc ctr buf =
|
||||||
|
if ctr = 0 then
|
||||||
|
Ok (List.rev acc, buf)
|
||||||
|
else
|
||||||
|
decode_string buf >>= fun (str, off) ->
|
||||||
|
decode_int64 ~off buf >>= fun v ->
|
||||||
|
go ((str, v) :: acc) (pred ctr) (Cstruct.shift buf (off + 8))
|
||||||
|
in
|
||||||
|
decode_int cs >>= fun stat_num ->
|
||||||
|
go [] stat_num (Cstruct.shift cs 8)
|
||||||
|
|
||||||
|
let encode_stats (ru, vmm, ifd) =
|
||||||
Cstruct.concat
|
Cstruct.concat
|
||||||
(encode_rusage ru :: List.map encode_ifdata ifd)
|
(encode_rusage ru ::
|
||||||
|
encode_vmm_stats vmm @
|
||||||
|
encode_int (List.length ifd) :: List.map encode_ifdata ifd)
|
||||||
|
|
||||||
let decode_stats cs =
|
let decode_stats cs =
|
||||||
check_len cs 144 >>= fun () ->
|
check_len cs 144 >>= fun () ->
|
||||||
let ru, ifd = Cstruct.split cs 144 in
|
let ru, rest = Cstruct.split cs 144 in
|
||||||
decode_rusage ru >>= fun ru ->
|
decode_rusage ru >>= fun ru ->
|
||||||
let rec go acc buf =
|
decode_vmm_stats rest >>= fun (vmm, rest) ->
|
||||||
if Cstruct.len buf = 0 then
|
let rec go acc ctr buf =
|
||||||
Ok (List.rev acc)
|
if ctr = 0 then
|
||||||
|
Ok (List.rev acc, buf)
|
||||||
else
|
else
|
||||||
decode_ifdata buf >>= fun (this, used) ->
|
decode_ifdata buf >>= fun (this, used) ->
|
||||||
go (this :: acc) (Cstruct.shift buf used)
|
go (this :: acc) (pred ctr) (Cstruct.shift buf used)
|
||||||
in
|
in
|
||||||
go [] ifd >>= fun ifs ->
|
decode_int rest >>= fun num_if ->
|
||||||
Ok (ru, ifs)
|
go [] num_if (Cstruct.shift rest 8) >>= fun (ifs, _rest) ->
|
||||||
|
Ok (ru, vmm, ifs)
|
||||||
|
|
||||||
let decode_pid_taps data =
|
let decode_pid_taps data =
|
||||||
decode_pid data >>= fun pid ->
|
decode_pid data >>= fun pid ->
|
||||||
|
|
|
@ -7,29 +7,32 @@ open Vmm_core
|
||||||
external sysctl_rusage : int -> rusage = "vmmanage_sysctl_rusage"
|
external sysctl_rusage : int -> rusage = "vmmanage_sysctl_rusage"
|
||||||
external sysctl_ifcount : unit -> int = "vmmanage_sysctl_ifcount"
|
external sysctl_ifcount : unit -> int = "vmmanage_sysctl_ifcount"
|
||||||
external sysctl_ifdata : int -> ifdata = "vmmanage_sysctl_ifdata"
|
external sysctl_ifdata : int -> ifdata = "vmmanage_sysctl_ifdata"
|
||||||
|
external vmmapi_stats : string -> (string * int64) list = "vmmanage_vmmapi_stats"
|
||||||
|
|
||||||
let my_version = `WV0
|
let my_version = `WV0
|
||||||
|
|
||||||
type t = {
|
type t = {
|
||||||
pid_nic : (int * string) list IM.t ;
|
pid_nic : (int * string) list IM.t ;
|
||||||
pid_rusage : rusage IM.t ;
|
pid_rusage : rusage IM.t ;
|
||||||
old_pid_rusage : rusage IM.t ;
|
pid_vmmapi : (string * int64) list IM.t ;
|
||||||
nic_ifdata : ifdata String.Map.t ;
|
nic_ifdata : ifdata String.Map.t ;
|
||||||
old_nic_ifdata : ifdata String.Map.t ;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let empty () =
|
let empty () =
|
||||||
{ pid_nic = IM.empty ;
|
{ pid_nic = IM.empty ; pid_rusage = IM.empty ; pid_vmmapi = IM.empty ; nic_ifdata = String.Map.empty }
|
||||||
pid_rusage = IM.empty ; nic_ifdata = String.Map.empty ;
|
|
||||||
old_pid_rusage = IM.empty ; old_nic_ifdata = String.Map.empty }
|
|
||||||
|
|
||||||
let rec safe_sysctl f arg =
|
let rec safe_sysctl f arg =
|
||||||
try Some (f arg) with
|
try Some (f arg) with
|
||||||
| Unix.Unix_error (Unix.EINTR, _, _) -> safe_sysctl f arg
|
| Unix.Unix_error (Unix.EINTR, _, _) -> safe_sysctl f arg
|
||||||
| _ -> None
|
| _ -> None
|
||||||
|
|
||||||
|
let vm_vmmapi_stats pid =
|
||||||
|
let name = "ukvm" ^ string_of_int pid in
|
||||||
|
vmmapi_stats name
|
||||||
|
|
||||||
let gather pid nics =
|
let gather pid nics =
|
||||||
safe_sysctl sysctl_rusage pid,
|
safe_sysctl sysctl_rusage pid,
|
||||||
|
vm_vmmapi_stats pid,
|
||||||
List.fold_left (fun ifd (nic, _) ->
|
List.fold_left (fun ifd (nic, _) ->
|
||||||
match safe_sysctl sysctl_ifdata nic with
|
match safe_sysctl sysctl_ifdata nic with
|
||||||
| None -> ifd
|
| None -> ifd
|
||||||
|
@ -37,17 +40,17 @@ let gather pid nics =
|
||||||
String.Map.empty nics
|
String.Map.empty nics
|
||||||
|
|
||||||
let tick t =
|
let tick t =
|
||||||
let pid_rusage, nic_ifdata =
|
let pid_rusage, pid_vmmapi, nic_ifdata =
|
||||||
IM.fold (fun pid nics (rus, ifds) ->
|
IM.fold (fun pid nics (rus, vmms, ifds) ->
|
||||||
let ru, ifd = gather pid nics in
|
let ru, vmm, ifd = gather pid nics in
|
||||||
(match ru with
|
(match ru with
|
||||||
| None -> rus
|
| None -> rus
|
||||||
| Some ru -> IM.add pid ru rus),
|
| Some ru -> IM.add pid ru rus),
|
||||||
|
IM.add pid vmm vmms,
|
||||||
String.Map.union (fun _k a _b -> Some a) ifd ifds)
|
String.Map.union (fun _k a _b -> Some a) ifd ifds)
|
||||||
t.pid_nic (IM.empty, String.Map.empty)
|
t.pid_nic (IM.empty, IM.empty, String.Map.empty)
|
||||||
in
|
in
|
||||||
let old_pid_rusage, old_nic_ifdata = t.pid_rusage, t.nic_ifdata in
|
{ t with pid_rusage ; pid_vmmapi ; nic_ifdata }
|
||||||
{ t with pid_rusage ; nic_ifdata ; old_pid_rusage ; old_nic_ifdata }
|
|
||||||
|
|
||||||
let add_pid t pid nics =
|
let add_pid t pid nics =
|
||||||
match safe_sysctl sysctl_ifcount () with
|
match safe_sysctl sysctl_ifcount () with
|
||||||
|
@ -64,18 +67,14 @@ let add_pid t pid nics =
|
||||||
in
|
in
|
||||||
let nic_ids = go (List.length nics) [] max_nic in
|
let nic_ids = go (List.length nics) [] max_nic in
|
||||||
let pid_nic = IM.add pid nic_ids t.pid_nic in
|
let pid_nic = IM.add pid nic_ids t.pid_nic in
|
||||||
let ru, ifd = gather pid nic_ids in
|
|
||||||
(match ru with
|
|
||||||
| None -> ()
|
|
||||||
| Some ru -> Logs.info (fun m -> m "RU %a" pp_rusage ru)) ;
|
|
||||||
Logs.info (fun m -> m "interfaces: %a" Fmt.(list ~sep:(unit ",@ ") pp_ifdata) (snd (List.split (String.Map.bindings ifd)))) ;
|
|
||||||
Ok { t with pid_nic }
|
Ok { t with pid_nic }
|
||||||
|
|
||||||
(* TODO: we can now compute deltas: t contains also old ru & ifdata *)
|
|
||||||
let stats t pid =
|
let stats t pid =
|
||||||
try
|
try
|
||||||
let nics = IM.find pid t.pid_nic in
|
let nics = IM.find pid t.pid_nic
|
||||||
let ru = IM.find pid t.pid_rusage in
|
and ru = IM.find pid t.pid_rusage
|
||||||
|
and vmm = IM.find pid t.pid_vmmapi
|
||||||
|
in
|
||||||
match
|
match
|
||||||
List.fold_left (fun acc nic ->
|
List.fold_left (fun acc nic ->
|
||||||
match String.Map.find nic t.nic_ifdata, acc with
|
match String.Map.find nic t.nic_ifdata, acc with
|
||||||
|
@ -85,7 +84,7 @@ let stats t pid =
|
||||||
(Some []) (snd (List.split nics))
|
(Some []) (snd (List.split nics))
|
||||||
with
|
with
|
||||||
| None -> Error (`Msg "failed to find interface statistics")
|
| None -> Error (`Msg "failed to find interface statistics")
|
||||||
| Some ifd -> Ok (ru, ifd)
|
| Some ifd -> Ok (ru, vmm, ifd)
|
||||||
with
|
with
|
||||||
| _ -> Error (`Msg "failed to find resource usage")
|
| _ -> Error (`Msg "failed to find resource usage")
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ let handle s addr () =
|
||||||
|
|
||||||
let rec timer () =
|
let rec timer () =
|
||||||
t := Vmm_stats.tick !t ;
|
t := Vmm_stats.tick !t ;
|
||||||
Lwt_unix.sleep Duration.(to_f (of_min 5)) >>= fun () ->
|
Lwt_unix.sleep Duration.(to_f (of_sec 15)) >>= fun () ->
|
||||||
timer ()
|
timer ()
|
||||||
|
|
||||||
let jump _ file =
|
let jump _ file =
|
||||||
|
|
|
@ -12,14 +12,13 @@
|
||||||
#include <sys/sysctl.h>
|
#include <sys/sysctl.h>
|
||||||
#include <net/if.h>
|
#include <net/if.h>
|
||||||
|
|
||||||
#ifdef __FreeBSD__
|
|
||||||
#include <net/if_mib.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define Val32 caml_copy_int32
|
#define Val32 caml_copy_int32
|
||||||
#define Val64 caml_copy_int64
|
#define Val64 caml_copy_int64
|
||||||
|
|
||||||
#ifdef __FreeBSD__
|
#ifdef __FreeBSD__
|
||||||
|
#include <net/if_mib.h>
|
||||||
|
#include <vmmapi.h>
|
||||||
|
|
||||||
CAMLprim value vmmanage_sysctl_rusage (value pid_r) {
|
CAMLprim value vmmanage_sysctl_rusage (value pid_r) {
|
||||||
CAMLparam1(pid_r);
|
CAMLparam1(pid_r);
|
||||||
CAMLlocal3(res, utime, stime);
|
CAMLlocal3(res, utime, stime);
|
||||||
|
@ -71,6 +70,38 @@ CAMLprim value vmmanage_sysctl_rusage (value pid_r) {
|
||||||
CAMLreturn(res);
|
CAMLreturn(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CAMLprim value vmmanage_vmmapi_stats (value name) {
|
||||||
|
CAMLparam1(name);
|
||||||
|
CAMLlocal3(res, tmp, t);
|
||||||
|
int i, num_stats;
|
||||||
|
uint64_t *stats;
|
||||||
|
const char *desc;
|
||||||
|
struct vmctx *ctx;
|
||||||
|
const char *devname;
|
||||||
|
|
||||||
|
if (! caml_string_is_c_safe(name)) caml_raise_not_found();
|
||||||
|
|
||||||
|
devname = String_val(name);
|
||||||
|
ctx = vm_open(devname);
|
||||||
|
if (ctx == NULL) uerror("vm_open", Nothing);
|
||||||
|
|
||||||
|
stats = vm_get_stats(ctx, 0, NULL, &num_stats);
|
||||||
|
if (stats != NULL) {
|
||||||
|
for (i = 0; i < num_stats; i++) {
|
||||||
|
tmp = caml_alloc(2, 0);
|
||||||
|
desc = vm_get_stat_desc(ctx, i);
|
||||||
|
Store_field (tmp, 0, caml_copy_string(desc));
|
||||||
|
Store_field (tmp, 1, Val64(stats[i]));
|
||||||
|
t = caml_alloc(2, 0);
|
||||||
|
Store_field (t, 0, tmp);
|
||||||
|
Store_field (t, 1, res);
|
||||||
|
res = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CAMLreturn(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
CAMLprim value vmmanage_sysctl_ifcount (value unit) {
|
CAMLprim value vmmanage_sysctl_ifcount (value unit) {
|
||||||
CAMLparam1(unit);
|
CAMLparam1(unit);
|
||||||
int data = 0;
|
int data = 0;
|
||||||
|
@ -136,17 +167,21 @@ CAMLprim value vmmanage_sysctl_ifdata (value num) {
|
||||||
|
|
||||||
CAMLprim value vmmanage_sysctl_rusage (value pid_r) {
|
CAMLprim value vmmanage_sysctl_rusage (value pid_r) {
|
||||||
CAMLparam1(pid_r);
|
CAMLparam1(pid_r);
|
||||||
uerror("sysctl", Nothing);
|
uerror("sysctl_rusage", Nothing);
|
||||||
}
|
}
|
||||||
|
|
||||||
CAMLprim value vmmanage_sysctl_ifcount (value unit) {
|
CAMLprim value vmmanage_sysctl_ifcount (value unit) {
|
||||||
CAMLparam1(unit);
|
CAMLparam1(unit);
|
||||||
uerror("sysctl", Nothing);
|
uerror("sysctl_ifcount", Nothing);
|
||||||
}
|
}
|
||||||
|
|
||||||
CAMLprim value vmmanage_sysctl_ifdata (value num) {
|
CAMLprim value vmmanage_sysctl_ifdata (value num) {
|
||||||
CAMLparam1(num);
|
CAMLparam1(num);
|
||||||
uerror("sysctl", Nothing);
|
uerror("sysctl_ifdata", Nothing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CAMLprim value vmmanage_vmmapi_stats (value name) {
|
||||||
|
CAMLparam1(name);
|
||||||
|
uerror("vm_stat", Nothing);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in a new issue