You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by to...@apache.org on 2021/04/14 00:23:45 UTC

[couchdb] 02/05: add configurable http server

This is an automated email from the ASF dual-hosted git repository.

tonysun83 pushed a commit to branch port-prometheus-3.x
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit 4849f24c8448b4a04e4e8bd36de01ef9764f4bb8
Author: Tony Sun <to...@gmail.com>
AuthorDate: Wed Mar 10 22:03:34 2021 -0800

    add configurable http server
    
    We add an option to spawn a new mochiweb_http server to allow for an
    additional port for scraping which does not require authentication.
    The default ports are 17986, 27986, 37986 across 3 nodes.
---
 dev/run                                            |  7 +-
 rel/overlay/etc/default.ini                        |  5 ++
 setup_eunit.template                               |  1 +
 src/couch_prometheus/src/couch_prometheus.app.src  |  2 +-
 src/couch_prometheus/src/couch_prometheus.hrl      |  3 +-
 src/couch_prometheus/src/couch_prometheus_http.erl | 88 ++++++++++++++++++++++
 .../src/couch_prometheus_server.erl                | 12 +--
 src/couch_prometheus/src/couch_prometheus_sup.erl  |  8 +-
 8 files changed, 116 insertions(+), 10 deletions(-)

diff --git a/dev/run b/dev/run
index 66d990a..04c0268 100755
--- a/dev/run
+++ b/dev/run
@@ -280,7 +280,8 @@ def check_boot_script(ctx):
 @log("Prepare configuration files")
 def setup_configs(ctx):
     for idx, node in enumerate(ctx["nodes"]):
-        cluster_port, backend_port = get_ports(ctx, idx + ctx["node_number"])
+        cluster_port, backend_port, prometheus_port = get_ports(ctx,
+            idx + ctx["node_number"])
         env = {
             "prefix": toposixpath(ctx["rootdir"]),
             "package_author_name": "The Apache Software Foundation",
@@ -293,6 +294,7 @@ def setup_configs(ctx):
             "node_name": "-name %s@127.0.0.1" % node,
             "cluster_port": cluster_port,
             "backend_port": backend_port,
+            "prometheus_port": prometheus_port,
             "uuid": "fake_uuid_for_dev",
             "_default": "",
         }
@@ -353,7 +355,8 @@ def apply_config_overrides(ctx, content):
 def get_ports(ctx, idnode):
     assert idnode
     if idnode <= 5 and not ctx["auto_ports"]:
-        return ((10000 * idnode) + 5984, (10000 * idnode) + 5986)
+        return ((10000 * idnode) + 5984, (10000 * idnode) + 5986,
+            (10000 * idnode) + 7986)
     else:
         return tuple(get_available_ports(2))
 
diff --git a/rel/overlay/etc/default.ini b/rel/overlay/etc/default.ini
index 9dafaba..b8852b1 100644
--- a/rel/overlay/etc/default.ini
+++ b/rel/overlay/etc/default.ini
@@ -682,3 +682,8 @@ compaction = false
 ;source_close_timeout_sec = 600
 ;require_node_param = false
 ;require_range_param = false
+
+[prometheus]
+additional_port = true
+bind_address = 127.0.0.1
+port = {{prometheus_port}}
diff --git a/setup_eunit.template b/setup_eunit.template
index 97bee46..3625441 100644
--- a/setup_eunit.template
+++ b/setup_eunit.template
@@ -2,6 +2,7 @@
     {package_author_name, "The Apache Software Foundation"},
     {cluster_port, 5984},
     {backend_port, 5986},
+    {prometheus_port, 17986},
     {node_name, "-name couchdbtest@127.0.0.1"},
 
     {data_dir, "/tmp"},
diff --git a/src/couch_prometheus/src/couch_prometheus.app.src b/src/couch_prometheus/src/couch_prometheus.app.src
index 3080b29..bf49e59 100644
--- a/src/couch_prometheus/src/couch_prometheus.app.src
+++ b/src/couch_prometheus/src/couch_prometheus.app.src
@@ -14,7 +14,7 @@
     {description, "Aggregated metrics info for Prometheus consumption"},
     {vsn, git},
     {registered, []},
-    {applications, [kernel, stdlib, folsom, couch_stats]},
+    {applications, [kernel, stdlib, folsom, couch_stats, couch_log]},
     {mod, {couch_prometheus_app, []}},
     {env, []}
 ]}.
diff --git a/src/couch_prometheus/src/couch_prometheus.hrl b/src/couch_prometheus/src/couch_prometheus.hrl
index e82fb90..383dbfe 100644
--- a/src/couch_prometheus/src/couch_prometheus.hrl
+++ b/src/couch_prometheus/src/couch_prometheus.hrl
@@ -10,4 +10,5 @@
 % License for the specific language governing permissions and limitations under
 % the License.
 
--define(REFRESH_INTERVAL, 60).
+-define(REFRESH_INTERVAL, 5).
+
diff --git a/src/couch_prometheus/src/couch_prometheus_http.erl b/src/couch_prometheus/src/couch_prometheus_http.erl
new file mode 100644
index 0000000..4edb538
--- /dev/null
+++ b/src/couch_prometheus/src/couch_prometheus_http.erl
@@ -0,0 +1,88 @@
+-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) ->
+    Headers = couch_httpd:server_header() ++ [
+        {<<"Content-Type">>, <<"text/plain">>}
+    ],
+    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.
diff --git a/src/couch_prometheus/src/couch_prometheus_server.erl b/src/couch_prometheus/src/couch_prometheus_server.erl
index a0accba..753e953 100644
--- a/src/couch_prometheus/src/couch_prometheus_server.erl
+++ b/src/couch_prometheus/src/couch_prometheus_server.erl
@@ -21,15 +21,17 @@
 ]).
 
 -export([
+    scrape/0
+]).
+
+-export([
     start_link/0,
     init/1,
     handle_call/3,
     handle_cast/2,
     handle_info/2,
     code_change/3,
-    terminate/2,
-
-    scrape/0
+    terminate/2
 ]).
 
 -include("couch_prometheus.hrl").
@@ -51,7 +53,6 @@ scrape() ->
     {ok, Metrics} = gen_server:call(?MODULE, scrape),
     Metrics.
 
-
 handle_call(scrape, _from, #st{metrics = Metrics}=State) ->
     {reply, {ok, Metrics}, State};
 handle_call(refresh, _from, #st{refresh=OldRT} = State) ->
@@ -67,7 +68,8 @@ handle_cast(Msg, State) ->
 
 handle_info(refresh, State) ->
     Metrics = refresh_metrics(),
-    {noreply, State#st{metrics=Metrics}};
+    RT = update_refresh_timer(),
+    {noreply, State#st{metrics=Metrics, refresh=RT}};
 handle_info(Msg, State) ->
     {stop, {unknown_info, Msg}, State}.
 
diff --git a/src/couch_prometheus/src/couch_prometheus_sup.erl b/src/couch_prometheus/src/couch_prometheus_sup.erl
index 09ed45f..8d8c7e0 100644
--- a/src/couch_prometheus/src/couch_prometheus_sup.erl
+++ b/src/couch_prometheus/src/couch_prometheus_sup.erl
@@ -28,6 +28,12 @@ init([]) ->
     {ok, {
         {one_for_one, 5, 10}, [
             ?CHILD(couch_prometheus_server, worker)
-        ]
+        ] ++ maybe_start_prometheus_http()
     }}.
 
+maybe_start_prometheus_http() ->
+    case config:get("prometheus", "additional_port", "false") of
+        "false" -> [];
+        "true" -> [?CHILD(couch_prometheus_http, worker)];
+        _ -> []
+    end.