summaryrefslogtreecommitdiff
path: root/src/couch_prometheus/src/couch_prometheus_http.erl
blob: c123a38922dc3f5aaf9f7661f6385c554ab8703c (plain)
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
-module(couch_prometheus_http).

-compile(tuple_calls).

-export([
    start_link/0,
    handle_request/1
]).

-include("couch_prometheus.hrl").
-include_lib("couch/include/couch_db.hrl").

start_link() ->
    IP = case config:get("prometheus", "bind_address", "any") of
        "any" -> any;
        Else -> Else
    end,
    Port = config:get("prometheus", "port"),
    ok = couch_httpd:validate_bind_address(IP),

    Options = [
        {name, ?MODULE},
        {loop, fun ?MODULE:handle_request/1},
        {ip, IP},
        {port, Port}
    ],
    case mochiweb_http:start(Options) of
        {ok, Pid} ->
            {ok, Pid};
        {error, Reason} ->
            io:format("Failure to start Mochiweb: ~s~n", [Reason]),
            {error, Reason}
    end.

handle_request(MochiReq) ->
    RawUri = MochiReq:get(raw_path),
    {"/" ++ Path, _, _} = mochiweb_util:urlsplit_path(RawUri),
    PathParts =  string:tokens(Path, "/"),    try
        case PathParts of
            ["_node", Node, "_prometheus"] ->
                send_prometheus(MochiReq, Node);
            _ ->
                send_error(MochiReq, 404, <<"not_found">>, <<>>)
        end
    catch T:R ->
        Body = list_to_binary(io_lib:format("~p:~p", [T, R])),
        send_error(MochiReq, 500, <<"server_error">>, Body)
    end.

send_prometheus(MochiReq, Node) ->
    Type = "text/plain; version=" ++ ?PROMETHEUS_VERSION,
    Headers = couch_httpd:server_header() ++ [
        {<<"Content-Type">>, ?l2b(Type)}
    ],
    Body = call_node(Node, couch_prometheus_server, scrape, []),
    send_resp(MochiReq, 200, Headers, Body).

send_resp(MochiReq, Status, ExtraHeaders, Body) ->
    Headers = couch_httpd:server_header() ++ ExtraHeaders,
    MochiReq:respond({Status, Headers, Body}).

send_error(MochiReq, Code, Error, Reason) ->
    Headers = couch_httpd:server_header() ++ [
        {<<"Content-Type">>, <<"application/json">>}
    ],
    JsonError = {[{<<"error">>,  Error},
        {<<"reason">>, Reason}]},
    Body = ?JSON_ENCODE(JsonError),
    MochiReq:respond({Code, Headers, Body}).

call_node("_local", Mod, Fun, Args) ->
    call_node(node(), Mod, Fun, Args);
call_node(Node0, Mod, Fun, Args) when is_list(Node0) ->
    Node1 = try
        list_to_existing_atom(Node0)
    catch
        error:badarg ->
            NoNode = list_to_binary(Node0),
            throw({not_found, <<"no such node: ", NoNode/binary>>})
        end,
    call_node(Node1, Mod, Fun, Args);
call_node(Node, Mod, Fun, Args) when is_atom(Node) ->
    case rpc:call(Node, Mod, Fun, Args) of
        {badrpc, nodedown} ->
            Reason = list_to_binary(io_lib:format("~s is down", [Node])),
            throw({error, {nodedown, Reason}});
        Else ->
            Else
    end.