package prometheus-app

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file prometheus_unix.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
module Logging = Prometheus_reporter_unix.Logging
module Server = Prometheus_app.Cohttp(Cohttp_lwt_unix.Server)

module Listen_address = struct
  type t =
    [ `TCP of [ `Host of string ] * [ `Port of int ]
    | `Unix_domain_socket of [ `File of string ] ]

  let default_host = "0.0.0.0"
  let default_port = 9090

  let syntax = "expected PORT, tcp:HOST[:PORT] or unix:PATH"

  let pp f = function
    | `TCP (`Host host, `Port port) ->
      let host = if String.contains host ':' then "[" ^ host ^ "]" else host in
      Fmt.pf f "tcp:%s:%d" host port
    | `Unix_domain_socket (`File path) -> Fmt.pf f "unix:%s" path

  let error = Fmt.error_msg

  let valid_port port = port > 0 && port <= 65535

  let tcp ?(host = default_host) ?(port = default_port) () =
    if not (valid_port port) then
      invalid_arg (Fmt.str "Listen_address.tcp: port %d is out of range" port);
    `TCP (`Host host, `Port port)

  let unix = function
    | "" -> invalid_arg "Listen_address.unix: the path is empty";
    | path -> `Unix_domain_socket (`File path)

  let parse_tcp s host_port =
    let uri = Uri.of_string ("tcp://" ^ host_port) in
    match Uri.host uri, Uri.port uri, Uri.path uri with
    | Some host, port, ("" | "/") when host <> "" ->
      let port = Option.value port ~default:default_port in
      if valid_port port then Ok (tcp ~host ~port ())
      else error "Port %d is out of range in %S" port s
    | _ -> error "Invalid address %S, expected tcp:HOST[:PORT]" s

  let of_string s =
    match int_of_string_opt s with
    | Some port when valid_port port -> Ok (tcp ~port ())
    | Some _ -> error "Port %S is out of range" s
    | None ->
      match String.index_opt s ':' with
      | None | Some 0 -> error "Missing scheme in %S, %s" s syntax
      | Some i ->
        let rest = String.sub s (i + 1) (String.length s - i - 1) in
        match String.sub s 0 i with
        | "tcp" -> parse_tcp s rest
        | "unix" when rest = "" -> error "Missing path in %S, expected unix:PATH" s
        | "unix" -> Ok (unix rest)
        | scheme -> error "Unsupported scheme %S in %S, %s" scheme s syntax
end

type config = Listen_address.t option

let config ?listen_address () = listen_address

let sockaddr = function
  | `Unix_domain_socket (`File path) -> Unix.ADDR_UNIX path
  | `TCP (`Host host, `Port port) ->
    let hints = Unix.[ AI_SOCKTYPE SOCK_STREAM; AI_PASSIVE ] in
    match Unix.getaddrinfo host (string_of_int port) hints with
    | [] -> Fmt.failwith "Unable to resolve listen address %S" host
    | ai :: _ -> ai.Unix.ai_addr

let listen sockaddr =
  let open Unix in
  let socket = socket ~cloexec:true (domain_of_sockaddr sockaddr) SOCK_STREAM 0 in
  (try
     setsockopt socket SO_REUSEADDR true;
     bind socket sockaddr;
     listen socket 128
   with exn -> close socket; raise exn);
  `TCP (`Socket (Lwt_unix.of_unix_file_descr socket))

let serve ?registry = function
  | None -> []
  | Some conf ->
    let mode = listen (sockaddr conf) in
    let callback = Server.callback ?registry in
    let thread = Cohttp_lwt_unix.Server.create ~mode (Cohttp_lwt_unix.Server.make ~callback ()) in
    [thread]

let listen_prometheus =
  let open! Cmdliner in
  let addr = Arg.conv ~docv:"ADDR" Listen_address.(of_string, pp) in
  let doc =
    Arg.info ~docs:"MONITORING OPTIONS" ~docv:"ADDR" ~doc:
      "Address on which to provide Prometheus metrics over HTTP. This is \
       either a port number, $(b,tcp:HOST[:PORT]) or $(b,unix:PATH), for \
       example $(b,9090), $(b,tcp:127.0.0.1:9090), $(b,tcp:[::1]:9090), \
       $(b,tcp:localhost) or $(b,unix:/run/metrics.sock). The default \
       listen address and port are 0.0.0.0:9090."
      ["listen-prometheus"]
  in
  Arg.(value @@ opt (some addr) None doc)

let opts = listen_prometheus