This commit is contained in:
parent
aa2ff7b2f0
commit
2f3113f55d
11742 changed files with 1223940 additions and 0 deletions
295
unikernel/duniverse/dune_/bin/monitor.ml
Normal file
295
unikernel/duniverse/dune_/bin/monitor.ml
Normal file
|
|
@ -0,0 +1,295 @@
|
|||
open Import
|
||||
open Fiber.O
|
||||
module Client = Dune_rpc_client.Client
|
||||
module Version_error = Dune_rpc_private.Version_error
|
||||
|
||||
include struct
|
||||
open Dune_rpc
|
||||
module Diagnostic = Diagnostic
|
||||
module Progress = Progress
|
||||
module Job = Job
|
||||
module Sub = Sub
|
||||
module Conv = Conv
|
||||
end
|
||||
|
||||
(** Utility module for generating [Map] modules for [Diagnostic]s and [Job]s which use
|
||||
their [Id] as keys. *)
|
||||
module Id_map (Id : sig
|
||||
type t
|
||||
|
||||
val compare : t -> t -> Ordering.t
|
||||
val sexp : (t, Conv.values) Conv.t
|
||||
end) =
|
||||
struct
|
||||
include Map.Make (struct
|
||||
include Id
|
||||
|
||||
let to_dyn t = Sexp.to_dyn (Conv.to_sexp Id.sexp t)
|
||||
end)
|
||||
end
|
||||
|
||||
module Diagnostic_id_map = Id_map (Diagnostic.Id)
|
||||
module Job_id_map = Id_map (Job.Id)
|
||||
|
||||
module Event = struct
|
||||
(** Events that the render loop will process. *)
|
||||
type t =
|
||||
| Diagnostics of Diagnostic.Event.t list
|
||||
| Jobs of Job.Event.t list
|
||||
| Progress of Progress.t
|
||||
end
|
||||
|
||||
module State : sig
|
||||
(** Internal state of the render loop. *)
|
||||
type t
|
||||
|
||||
(** Initial empty state. *)
|
||||
val init : unit -> t
|
||||
|
||||
module Update : sig
|
||||
(** Incremental updates to the state. Computes increments of the state that
|
||||
will be used for efficient rendering. *)
|
||||
type t
|
||||
end
|
||||
|
||||
val update : t -> Event.t -> Update.t
|
||||
|
||||
(** Given a state update, render the update. *)
|
||||
val render : t -> Update.t -> unit
|
||||
end = struct
|
||||
type t =
|
||||
{ mutable diagnostics : Diagnostic.t Diagnostic_id_map.t
|
||||
; mutable jobs : Job.t Job_id_map.t
|
||||
; mutable progress : Progress.t
|
||||
}
|
||||
|
||||
let init () =
|
||||
{ diagnostics = Diagnostic_id_map.empty; jobs = Job_id_map.empty; progress = Waiting }
|
||||
;;
|
||||
|
||||
let done_status ~complete ~remaining ~failed state =
|
||||
Pp.textf
|
||||
"Done: %d%% (%d/%d, %d left%s) (jobs: %d)"
|
||||
(if complete + remaining = 0 then 0 else complete * 100 / (complete + remaining))
|
||||
complete
|
||||
(complete + remaining)
|
||||
remaining
|
||||
(match failed with
|
||||
| 0 -> ""
|
||||
| failed -> sprintf ", %d failed" failed)
|
||||
(Job_id_map.cardinal state.jobs)
|
||||
;;
|
||||
|
||||
let waiting_for_file_system_changes message =
|
||||
Pp.seq message (Pp.verbatim ", waiting for filesystem changes...")
|
||||
;;
|
||||
|
||||
let restarting_current_build message =
|
||||
Pp.seq message (Pp.verbatim ", restarting current build...")
|
||||
;;
|
||||
|
||||
let had_errors state =
|
||||
match Diagnostic_id_map.cardinal state.diagnostics with
|
||||
| 1 -> Pp.verbatim "Had 1 error"
|
||||
| n -> Pp.textf "Had %d errors" n
|
||||
;;
|
||||
|
||||
let status (state : t) =
|
||||
Console.Status_line.set
|
||||
(Live
|
||||
(fun () ->
|
||||
match (state.progress : Progress.t) with
|
||||
| Waiting -> Pp.verbatim "Initializing..."
|
||||
| In_progress { complete; remaining; failed } ->
|
||||
done_status ~complete ~remaining ~failed state
|
||||
| Interrupted ->
|
||||
Pp.tag User_message.Style.Error (Pp.verbatim "Source files changed")
|
||||
|> restarting_current_build
|
||||
| Success ->
|
||||
Pp.tag User_message.Style.Success (Pp.verbatim "Success")
|
||||
|> waiting_for_file_system_changes
|
||||
| Failed ->
|
||||
Pp.tag User_message.Style.Error (had_errors state)
|
||||
|> waiting_for_file_system_changes))
|
||||
;;
|
||||
|
||||
module Update = struct
|
||||
type t =
|
||||
| Update_status
|
||||
| Add_diagnostics of Diagnostic.t list
|
||||
| Refresh
|
||||
|
||||
let jobs state jobs =
|
||||
let jobs =
|
||||
List.fold_left jobs ~init:state.jobs ~f:(fun acc job_event ->
|
||||
match (job_event : Job.Event.t) with
|
||||
| Start job -> Job_id_map.add_exn acc job.id job
|
||||
| Stop id -> Job_id_map.remove acc id)
|
||||
in
|
||||
state.jobs <- jobs;
|
||||
Update_status
|
||||
;;
|
||||
|
||||
let progress state progress =
|
||||
state.progress <- progress;
|
||||
Update_status
|
||||
;;
|
||||
|
||||
let diagnostics state diagnostics =
|
||||
let mode, diagnostics =
|
||||
List.fold_left
|
||||
diagnostics
|
||||
~init:(`Add_only [], state.diagnostics)
|
||||
~f:(fun (mode, acc) diag_event ->
|
||||
match (diag_event : Diagnostic.Event.t) with
|
||||
| Remove diag -> `Remove, Diagnostic_id_map.remove acc diag.id
|
||||
| Add diag ->
|
||||
( (match mode with
|
||||
| `Add_only diags -> `Add_only (diag :: diags)
|
||||
| `Remove -> `Remove)
|
||||
, Diagnostic_id_map.add_exn acc diag.id diag ))
|
||||
in
|
||||
state.diagnostics <- diagnostics;
|
||||
match mode with
|
||||
| `Add_only update -> Add_diagnostics (List.rev update)
|
||||
| `Remove -> Refresh
|
||||
;;
|
||||
end
|
||||
|
||||
let update state (event : Event.t) =
|
||||
match event with
|
||||
| Jobs jobs -> Update.jobs state jobs
|
||||
| Progress progress -> Update.progress state progress
|
||||
| Diagnostics diagnostics -> Update.diagnostics state diagnostics
|
||||
;;
|
||||
|
||||
let render =
|
||||
let f d = Console.print_user_message (Diagnostic.to_user_message d) in
|
||||
fun (state : t) (update : Update.t) ->
|
||||
(match (update : Update.t) with
|
||||
| Add_diagnostics diags -> List.iter diags ~f
|
||||
| Update_status -> ()
|
||||
| Refresh ->
|
||||
Console.reset ();
|
||||
Diagnostic_id_map.iter state.diagnostics ~f);
|
||||
status state
|
||||
;;
|
||||
end
|
||||
|
||||
(* A generic loop that continuously fetches events from a [sub] that it opens a
|
||||
poll to and writes them to the [event] bus. *)
|
||||
let fetch_loop ~(event : Event.t Fiber_event_bus.t) ~client ~f sub =
|
||||
Client.poll client sub
|
||||
>>= function
|
||||
| Error version_error ->
|
||||
let* () = Fiber_event_bus.close event in
|
||||
User_error.raise [ Pp.verbatim (Version_error.message version_error) ]
|
||||
| Ok poller ->
|
||||
let rec loop () =
|
||||
Fiber.collect_errors (fun () -> Client.Stream.next poller)
|
||||
>>= (function
|
||||
| Ok (Some payload) -> Fiber_event_bus.push event (f payload)
|
||||
| Error _ | Ok None -> Fiber_event_bus.close event >>> Fiber.return `Closed)
|
||||
>>= function
|
||||
| `Closed -> Fiber.return ()
|
||||
| `Ok -> loop ()
|
||||
in
|
||||
loop ()
|
||||
;;
|
||||
|
||||
(* Main render loop *)
|
||||
let render_loop ~(event : Event.t Fiber_event_bus.t) =
|
||||
Console.reset ();
|
||||
let state = State.init () in
|
||||
let rec loop () =
|
||||
Fiber_event_bus.pop event
|
||||
>>= function
|
||||
| `Closed ->
|
||||
Console.print_user_message
|
||||
(User_error.make [ Pp.textf "Lost connection to server." ]);
|
||||
Fiber.return ()
|
||||
| `Next event ->
|
||||
let update = State.update state event in
|
||||
(* CR-someday alizter: If performance of rendering here on every loop is bad we can
|
||||
instead batch updates. It should be very simple to write a [State.Update.union]
|
||||
function that can combine incremental updates to be done at once. *)
|
||||
State.render state update;
|
||||
loop ()
|
||||
in
|
||||
loop ()
|
||||
;;
|
||||
|
||||
let monitor ~quit_on_disconnect () =
|
||||
Fiber.repeat_while ~init:1 ~f:(fun i ->
|
||||
match Dune_rpc_impl.Where.get () with
|
||||
| Some where ->
|
||||
let* connect = Client.Connection.connect_exn where in
|
||||
let+ () =
|
||||
Dune_rpc_impl.Client.client
|
||||
connect
|
||||
(Dune_rpc.Initialize.Request.create
|
||||
~id:(Dune_rpc.Id.make (Sexp.Atom "monitor_cmd")))
|
||||
~f:(fun client ->
|
||||
let event = Fiber_event_bus.create () in
|
||||
let module Sub = Dune_rpc_private.Public.Sub in
|
||||
Fiber.all_concurrently_unit
|
||||
[ render_loop ~event
|
||||
; fetch_loop ~event ~client ~f:(fun x -> Event.Jobs x) Sub.running_jobs
|
||||
; fetch_loop ~event ~client ~f:(fun x -> Event.Progress x) Sub.progress
|
||||
; fetch_loop ~event ~client ~f:(fun x -> Event.Diagnostics x) Sub.diagnostic
|
||||
])
|
||||
in
|
||||
Some i
|
||||
| None when quit_on_disconnect ->
|
||||
User_error.raise [ Pp.text "RPC server not running." ]
|
||||
| None ->
|
||||
Console.Status_line.set
|
||||
(Console.Status_line.Live
|
||||
(fun () -> Pp.verbatim ("Waiting for RPC server" ^ String.make (i mod 4) '.')));
|
||||
let+ () = Scheduler.sleep ~seconds:0.3 in
|
||||
Some (i + 1))
|
||||
;;
|
||||
|
||||
let man =
|
||||
[ `S "DESCRIPTION"
|
||||
; `P
|
||||
"$(b,dune monitor) connects to an RPC server running in the current workspace and \
|
||||
displays the build progress and diagnostics. If no server is running or it was \
|
||||
disconnected, it will continuously try to reconnect."
|
||||
]
|
||||
;;
|
||||
|
||||
let command =
|
||||
let info =
|
||||
let doc = "Connect to a Dune RPC server and monitor it." in
|
||||
Cmd.info "monitor" ~doc ~man
|
||||
and term =
|
||||
let open Import in
|
||||
let+ builder = Common.Builder.term
|
||||
and+ quit_on_disconnect =
|
||||
Arg.(
|
||||
value
|
||||
& flag
|
||||
& info
|
||||
[ "quit-on-disconnect" ]
|
||||
~doc:"Quit if the connection to the server is lost.")
|
||||
in
|
||||
let builder = Common.Builder.forbid_builds builder in
|
||||
let builder = Common.Builder.disable_log_file builder in
|
||||
let common, config = Common.init builder in
|
||||
let stats = Common.stats common in
|
||||
let config =
|
||||
Dune_config.for_scheduler
|
||||
config
|
||||
stats
|
||||
~print_ctrl_c_warning:true
|
||||
~watch_exclusions:[]
|
||||
in
|
||||
Scheduler.Run.go
|
||||
config
|
||||
~on_event:(fun _ _ -> ())
|
||||
~file_watcher:No_watcher
|
||||
(monitor ~quit_on_disconnect)
|
||||
in
|
||||
Cmd.v info term
|
||||
;;
|
||||
Loading…
Add table
Add a link
Reference in a new issue