057dbbf147
influx may drop topmost label (if --drop-label provided)
441 lines
14 KiB
OCaml
441 lines
14 KiB
OCaml
(* (c) 2017, 2018 Hannes Mehnert, all rights reserved *)
|
|
|
|
let conn_metrics kind =
|
|
let s = ref (0, 0) in
|
|
let open Metrics in
|
|
let doc = "connection statistics" in
|
|
let data () =
|
|
Data.v [
|
|
int "active" (fst !s) ;
|
|
int "total" (snd !s) ;
|
|
] in
|
|
let tags = Tags.string "kind" in
|
|
let src = Src.v ~doc ~tags:Tags.[ tags ] ~data "connections" in
|
|
(fun action ->
|
|
(match action with
|
|
| `Open -> s := (succ (fst !s), succ (snd !s))
|
|
| `Close -> s := (pred (fst !s), snd !s));
|
|
Metrics.add src (fun x -> x kind) (fun d -> d ()))
|
|
|
|
open Astring
|
|
|
|
let tmpdir = Fpath.(v "/var" / "run" / "albatross")
|
|
let sockdir = Fpath.(tmpdir / "util")
|
|
|
|
type service = [ `Console | `Log | `Stats | `Vmmd ]
|
|
|
|
let socket_path t =
|
|
let path = match t with
|
|
| `Console -> "console"
|
|
| `Vmmd -> "vmmd"
|
|
| `Stats -> "stat"
|
|
| `Log -> "log"
|
|
in
|
|
Fpath.to_string Fpath.(sockdir / path + "sock")
|
|
|
|
let pp_socket ppf t =
|
|
let name = socket_path t in
|
|
Fmt.pf ppf "socket: %s" name
|
|
|
|
module I = struct
|
|
type t = int
|
|
let compare : int -> int -> int = compare
|
|
end
|
|
|
|
module IS = Set.Make(I)
|
|
module IM = Map.Make(I)
|
|
|
|
module Name = struct
|
|
type t = string list
|
|
|
|
let root = []
|
|
|
|
let is_root x = x = []
|
|
|
|
let rec equal x y = match x, y with
|
|
| [], [] -> true
|
|
| x::xs, y::ys -> x = y && equal xs ys
|
|
| _ -> false
|
|
|
|
let [@inline always] valid_label s =
|
|
String.length s < 20 &&
|
|
String.length s > 0 &&
|
|
String.get s 0 <> '-' && (* leading may not be '-' *)
|
|
String.for_all (function
|
|
| 'a'..'z' | 'A'..'Z' | '0'..'9' | '-' -> true
|
|
| _ -> false)
|
|
s (* only LDH (letters, digits, hyphen)! *)
|
|
|
|
let to_string = function
|
|
| [] -> "."
|
|
| ids -> String.concat ~sep:"." ids
|
|
|
|
let to_list x = x
|
|
|
|
let drop x = match List.rev x with
|
|
| [] -> []
|
|
| _::tl -> List.rev tl
|
|
|
|
let drop_front = function
|
|
| [] -> []
|
|
| _::tl -> tl
|
|
|
|
let append_exn lbl x =
|
|
if valid_label lbl then
|
|
x @ [ lbl ]
|
|
else
|
|
invalid_arg "label not valid"
|
|
|
|
let append lbl x =
|
|
if valid_label lbl then
|
|
Ok (x @ [ lbl ])
|
|
else
|
|
Error (`Msg "label not valid")
|
|
|
|
let prepend lbl x =
|
|
if valid_label lbl then
|
|
Ok (lbl :: x)
|
|
else
|
|
Error (`Msg "label not valid")
|
|
|
|
let concat a b = a @ b
|
|
|
|
let domain id = match List.rev id with
|
|
| _::prefix -> List.rev prefix
|
|
| [] -> []
|
|
|
|
let image_file name =
|
|
let file = to_string name in
|
|
Fpath.(tmpdir / file + "img")
|
|
|
|
let fifo_file name =
|
|
let file = to_string name in
|
|
Fpath.(tmpdir / "fifo" / file)
|
|
|
|
let block_name vm_name dev =
|
|
List.rev (dev :: List.rev (domain vm_name))
|
|
|
|
let of_string str =
|
|
let id = String.cuts ~sep:"." str in
|
|
if List.for_all valid_label id then
|
|
Ok id
|
|
else
|
|
Error (`Msg "invalid name")
|
|
|
|
let of_list labels =
|
|
if List.for_all valid_label labels then
|
|
Ok labels
|
|
else
|
|
Error (`Msg "invalid name")
|
|
|
|
let drop_super ~super ~sub =
|
|
let rec go sup sub = match sup, sub with
|
|
| [], xs -> Some xs
|
|
| _, [] -> None
|
|
| x::xs, z::zs -> if String.equal x z then go xs zs else None
|
|
in
|
|
go super sub
|
|
|
|
let is_sub ~super ~sub =
|
|
match drop_super ~super ~sub with None -> false | Some _ -> true
|
|
|
|
let pp ppf ids =
|
|
Fmt.(pf ppf "[vm: %a]" (list ~sep:(unit ".") string) ids)
|
|
|
|
let mac name bridge =
|
|
(* deterministic mac address computation: VEB Kombinat Robotron prefix
|
|
vielen dank, liebe genossen! *)
|
|
let prefix = "\x00\x80\x41"
|
|
and ours = Digest.string (to_string (bridge :: name))
|
|
in
|
|
Macaddr.of_octets_exn (prefix ^ String.take ~min:3 ~max:3 ours)
|
|
end
|
|
|
|
module Policy = struct
|
|
let pp_is ppf is = Fmt.pf ppf "%a" Fmt.(list ~sep:(unit ",") int) (IS.elements is)
|
|
|
|
let eq_int (a : int) (b : int) = a = b
|
|
|
|
type t = {
|
|
vms : int ;
|
|
cpuids : IS.t ;
|
|
memory : int ;
|
|
block : int option ;
|
|
bridges : String.Set.t ;
|
|
}
|
|
|
|
let equal p1 p2 =
|
|
let eq_opt a b = match a, b with
|
|
| None, None -> true
|
|
| Some a, Some b -> eq_int a b
|
|
| _ -> false
|
|
in
|
|
eq_int p1.vms p2.vms &&
|
|
IS.equal p1.cpuids p2.cpuids &&
|
|
eq_int p1.memory p2.memory &&
|
|
eq_opt p1.block p2.block &&
|
|
String.Set.equal p1.bridges p2.bridges
|
|
|
|
let pp ppf res =
|
|
Fmt.pf ppf "policy: %d vms %a cpus %d MB memory %a MB block bridges: %a"
|
|
res.vms pp_is res.cpuids res.memory
|
|
Fmt.(option ~none:(unit "no") int) res.block
|
|
(String.Set.pp ~sep:Fmt.(unit ", ") Fmt.string) res.bridges
|
|
end
|
|
|
|
module Unikernel = struct
|
|
type typ = [ `Solo5 ]
|
|
|
|
let pp_typ ppf = function
|
|
| `Solo5 -> Fmt.pf ppf "solo5"
|
|
|
|
type fail_behaviour = [ `Quit | `Restart of IS.t option ]
|
|
|
|
let pp_fail_behaviour ppf = function
|
|
| `Quit -> Fmt.string ppf "quit"
|
|
| `Restart codes ->
|
|
Fmt.pf ppf "restart %a"
|
|
Fmt.(option ~none:(unit "all except 1") (list ~sep:(unit ", ") int))
|
|
(match codes with None -> None | Some x -> Some (IS.elements x))
|
|
|
|
type config = {
|
|
typ : typ ;
|
|
compressed : bool ;
|
|
image : Cstruct.t ;
|
|
fail_behaviour : fail_behaviour;
|
|
cpuid : int ;
|
|
memory : int ;
|
|
block_devices : string list ;
|
|
bridges : string list ;
|
|
argv : string list option ;
|
|
}
|
|
|
|
let pp_config ppf (vm : config) =
|
|
Fmt.pf ppf "typ %a@ compression %B image %d bytes@ fail behaviour %a@ cpu %d@ %d MB memory@ block devices %a@ bridge %a@ argv %a"
|
|
pp_typ vm.typ
|
|
vm.compressed
|
|
(Cstruct.len vm.image)
|
|
pp_fail_behaviour vm.fail_behaviour
|
|
vm.cpuid vm.memory
|
|
Fmt.(list ~sep:(unit ", ") string) vm.block_devices
|
|
Fmt.(list ~sep:(unit ", ") string) vm.bridges
|
|
Fmt.(option ~none:(unit "no") (list ~sep:(unit " ") string)) vm.argv
|
|
|
|
let restart_handler config =
|
|
match config.fail_behaviour with `Quit -> false | `Restart _ -> true
|
|
|
|
type t = {
|
|
config : config ;
|
|
cmd : Bos.Cmd.t ;
|
|
pid : int ;
|
|
taps : string list ;
|
|
}
|
|
|
|
let pp ppf vm =
|
|
Fmt.pf ppf "pid %d@ taps %a (block %a) cmdline %a"
|
|
vm.pid
|
|
Fmt.(list ~sep:(unit ", ") string) vm.taps
|
|
Fmt.(list ~sep:(unit ", ") string) vm.config.block_devices
|
|
Bos.Cmd.pp vm.cmd
|
|
end
|
|
|
|
module Stats = struct
|
|
type rusage = {
|
|
utime : (int64 * int) ;
|
|
stime : (int64 * int) ;
|
|
maxrss : int64 ;
|
|
ixrss : int64 ;
|
|
idrss : int64 ;
|
|
isrss : int64 ;
|
|
minflt : int64 ;
|
|
majflt : int64 ;
|
|
nswap : int64 ;
|
|
inblock : int64 ;
|
|
outblock : int64 ;
|
|
msgsnd : int64 ;
|
|
msgrcv : int64 ;
|
|
nsignals : int64 ;
|
|
nvcsw : int64 ;
|
|
nivcsw : int64 ;
|
|
}
|
|
|
|
let pp_rusage ppf r =
|
|
Fmt.pf ppf "utime %Lu.%d stime %Lu.%d maxrss %Lu ixrss %Lu idrss %Lu isrss %Lu minflt %Lu majflt %Lu nswap %Lu inblock %Lu outblock %Lu msgsnd %Lu msgrcv %Lu signals %Lu nvcsw %Lu nivcsw %Lu"
|
|
(fst r.utime) (snd r.utime) (fst r.stime) (snd r.stime) r.maxrss r.ixrss r.idrss r.isrss r.minflt r.majflt r.nswap r.inblock r.outblock r.msgsnd r.msgrcv r.nsignals r.nvcsw r.nivcsw
|
|
let pp_rusage_mem ppf r =
|
|
Fmt.pf ppf "maxrss %Lu ixrss %Lu idrss %Lu isrss %Lu minflt %Lu majflt %Lu"
|
|
r.maxrss r.ixrss r.idrss r.isrss r.minflt r.majflt
|
|
|
|
type kinfo_mem = {
|
|
vsize : int64 ;
|
|
rss : int64 ;
|
|
tsize : int64 ;
|
|
dsize : int64 ;
|
|
ssize : int64 ;
|
|
runtime : int64 ;
|
|
cow : int ;
|
|
start : (int64 * int) ;
|
|
}
|
|
|
|
let pp_kinfo_mem ppf t =
|
|
Fmt.pf ppf "virtual-size %Lu rss %Lu text-size %Lu data-size %Lu stack-size %Lu runtime %Lu cow %u start %Lu.%d"
|
|
t.vsize t.rss t.tsize t.dsize t.ssize t.runtime t.cow (fst t.start) (snd t.start)
|
|
|
|
type vmm = (string * int64) list
|
|
let pp_vmm ppf vmm =
|
|
Fmt.(list ~sep:(unit "@.") (pair ~sep:(unit ": ") string int64)) ppf vmm
|
|
let pp_vmm_mem ppf vmm =
|
|
Fmt.(list ~sep:(unit "@.") (pair ~sep:(unit ": ") string int64)) ppf
|
|
(List.filter (fun (k, _) -> k = "Resident memory" || k = "Wired memory") vmm)
|
|
|
|
type ifdata = {
|
|
bridge : string ;
|
|
flags : int32 ;
|
|
send_length : int32 ;
|
|
max_send_length : int32 ;
|
|
send_drops : int32 ;
|
|
mtu : int32 ;
|
|
baudrate : int64 ;
|
|
input_packets : int64 ;
|
|
input_errors : int64 ;
|
|
output_packets : int64 ;
|
|
output_errors : int64 ;
|
|
collisions : int64 ;
|
|
input_bytes : int64 ;
|
|
output_bytes : int64 ;
|
|
input_mcast : int64 ;
|
|
output_mcast : int64 ;
|
|
input_dropped : int64 ;
|
|
output_dropped : int64 ;
|
|
}
|
|
|
|
let pp_ifdata ppf i =
|
|
Fmt.pf ppf "bridge %s flags %lX send_length %lu max_send_length %lu send_drops %lu mtu %lu baudrate %Lu input_packets %Lu input_errors %Lu output_packets %Lu output_errors %Lu collisions %Lu input_bytes %Lu output_bytes %Lu input_mcast %Lu output_mcast %Lu input_dropped %Lu output_dropped %Lu"
|
|
i.bridge i.flags i.send_length i.max_send_length i.send_drops i.mtu i.baudrate i.input_packets i.input_errors i.output_packets i.output_errors i.collisions i.input_bytes i.output_bytes i.input_mcast i.output_mcast i.input_dropped i.output_dropped
|
|
|
|
type t = rusage * kinfo_mem option * vmm option * ifdata list
|
|
let pp ppf (ru, mem, vmm, ifs) =
|
|
Fmt.pf ppf "%a@.%a@.%a@.%a"
|
|
pp_rusage ru
|
|
Fmt.(option ~none:(unit "no kinfo_mem stats") pp_kinfo_mem) mem
|
|
Fmt.(option ~none:(unit "no vmm stats") pp_vmm) vmm
|
|
Fmt.(list ~sep:(unit "@.@.") pp_ifdata) ifs
|
|
end
|
|
|
|
type process_exit = [ `Exit of int | `Signal of int | `Stop of int ]
|
|
|
|
let pp_process_exit ppf = function
|
|
| `Exit n -> Fmt.pf ppf "exit %d" n
|
|
| `Signal n -> Fmt.pf ppf "signal %a (numeric %d)" Fmt.Dump.signal n n
|
|
| `Stop n -> Fmt.pf ppf "stop %a (numeric %d)" Fmt.Dump.signal n n
|
|
|
|
let should_restart config name = function
|
|
| (`Signal _ | `Stop _) as r ->
|
|
(* signal 11 is if a kill -TERM was sent (i.e. our destroy) *)
|
|
Logs.warn (fun m -> m "unikernel %a exited with signal %a"
|
|
Name.pp name pp_process_exit r);
|
|
false
|
|
| `Exit i ->
|
|
(* results (and default behaviour) -- solo5-exit allows an arbitrary int
|
|
from sysexits(3), bash tutorial (appendix E), OCaml runtime, solo5:
|
|
0 normal exit (i.e. teardown) -> restart
|
|
1 solo5 internal error (bad image, bad manifest) -> no restart, never
|
|
2 ocaml exceptions (out of memory et al) -> restart
|
|
60 61 62 (unused, not reserved) -> no restart, never
|
|
63 functoria-runtime help/version -> no restart, never
|
|
64 argument parse error - no restart, never
|
|
65 (sysexits, unused) data error
|
|
66 (sysexits, unused) noinput
|
|
67 (sysexits, unused) nouser
|
|
68 (sysexits, unused) nohost
|
|
69 (sysexits, unused) unavailable
|
|
70 (sysexits, unused) software
|
|
71 (sysexits, unused) oserr
|
|
72 (sysexits, unused) osfile
|
|
73 (sysexits, unused) cantcreat
|
|
74 (sysexits, unused) ioerr
|
|
75 (sysexits, unused) tempfail
|
|
76 (sysexits, unused) protocol
|
|
77 (sysexits, unused) noperm
|
|
78 (sysexits, unused) config
|
|
126 (bash, unused) command invoked cannot execute
|
|
127 (bash, unused) command not found
|
|
128+n (bash, unused) fatal error signal n
|
|
255 solo5-abort -> OCaml 4.10: fatal error (instead of 2) -> restart
|
|
|
|
opam exit codes:
|
|
1 False. Returned when a boolean return value is expected, e.g. when running with --check, or for queries like opam lint.
|
|
2 Bad command-line arguments, or command-line arguments pointing to an invalid context (e.g. file not following the expected format).
|
|
5 Not found. You requested something (package, version, repository, etc.) that couldn't be found.
|
|
10 Aborted. The operation required confirmation, which wasn't given.
|
|
15 Could not acquire the locks required for the operation.
|
|
20 There is no solution to the user request. This can be caused by asking to install two incompatible packages, for example.
|
|
30 Error in package definition, or other metadata files. Using --strict raises this error more often.
|
|
31 Package script error. Some package operations were unsuccessful. This may be an error in the packages or an incompatibility with your system. This can be a partial error.
|
|
40 Sync error. Could not fetch some remotes from the network. This can be a partial error.
|
|
50 Configuration error. Opam or system configuration doesn't allow operation, and needs fixing.
|
|
60 Solver failure. The solver failed to return a sound answer. It can be due to a broken external solver, or an error in solver configuration.
|
|
99 Internal error. Something went wrong, likely due to a bug in opam itself.
|
|
130 User interrupt. SIGINT was received, generally due to the user pressing Ctrl-C.
|
|
*)
|
|
let opt_mem i =
|
|
match config.Unikernel.fail_behaviour with
|
|
| `Quit -> assert false
|
|
| `Restart None -> true
|
|
| `Restart (Some c) -> IS.mem i c
|
|
in
|
|
match i with
|
|
| 1 ->
|
|
Logs.warn (fun m -> m "unikernel %a solo5 exit failure (1)"
|
|
Name.pp name);
|
|
false
|
|
| 60 | 61 | 62 | 63 | 64 ->
|
|
Logs.warn (fun m -> m "unikernel %a exited %d, not restarting"
|
|
Name.pp name i);
|
|
false
|
|
| _ when opt_mem i ->
|
|
Logs.info (fun m -> m "unikernel %a exited %d, restarting"
|
|
Name.pp name i);
|
|
true
|
|
| _ ->
|
|
Logs.info (fun m -> m "unikernel %a exited %d, not restarting %a"
|
|
Name.pp name i Unikernel.pp_fail_behaviour config.fail_behaviour);
|
|
false
|
|
|
|
module Log = struct
|
|
type log_event = [
|
|
| `Login of Name.t * Ipaddr.V4.t * int
|
|
| `Logout of Name.t * Ipaddr.V4.t * int
|
|
| `Startup
|
|
| `Unikernel_start of Name.t * int * (string * string) list * (string * Name.t) list
|
|
| `Unikernel_stop of Name.t * int * process_exit
|
|
| `Hup
|
|
]
|
|
|
|
let name = function
|
|
| `Startup -> []
|
|
| `Login (name, _, _) -> name
|
|
| `Logout (name, _, _) -> name
|
|
| `Unikernel_start (name, _, _ ,_) -> name
|
|
| `Unikernel_stop (name, _, _) -> name
|
|
| `Hup -> []
|
|
|
|
let pp_log_event ppf = function
|
|
| `Startup -> Fmt.string ppf "startup"
|
|
| `Login (name, ip, port) -> Fmt.pf ppf "%a login %a:%d" Name.pp name Ipaddr.V4.pp ip port
|
|
| `Logout (name, ip, port) -> Fmt.pf ppf "%a logout %a:%d" Name.pp name Ipaddr.V4.pp ip port
|
|
| `Unikernel_start (name, pid, taps, blocks) ->
|
|
Fmt.pf ppf "%a started %d (taps %a, block %a)"
|
|
Name.pp name pid Fmt.(list ~sep:(unit "; ") (pair ~sep:(unit "=") string string)) taps
|
|
Fmt.(list ~sep:(unit "; ") (pair ~sep:(unit "=") string Name.pp)) blocks
|
|
| `Unikernel_stop (name, pid, code) ->
|
|
Fmt.pf ppf "%a stopped %d with %a" Name.pp name pid pp_process_exit code
|
|
| `Hup -> Fmt.string ppf "hup"
|
|
|
|
|
|
type t = Ptime.t * log_event
|
|
|
|
let pp ppf (ts, ev) =
|
|
Fmt.pf ppf "%a: %a" (Ptime.pp_rfc3339 ()) ts pp_log_event ev
|
|
end
|