From 50b7ac6a22799f16511d21c2e3a385dedb1abb67 Mon Sep 17 00:00:00 2001 From: JianBo He Date: Tue, 3 Jan 2023 12:55:07 +0800 Subject: [PATCH 1/5] feat(prom): support headers for pushing --- .../i18n/emqx_prometheus_schema_i18n.conf | 10 +++++++++ apps/emqx_prometheus/src/emqx_prometheus.erl | 21 ++++++++++++++----- .../src/emqx_prometheus_api.erl | 1 + .../src/emqx_prometheus_schema.erl | 9 ++++++++ .../test/emqx_prometheus_SUITE.erl | 20 ++++++++++++++++++ 5 files changed, 56 insertions(+), 5 deletions(-) diff --git a/apps/emqx_prometheus/i18n/emqx_prometheus_schema_i18n.conf b/apps/emqx_prometheus/i18n/emqx_prometheus_schema_i18n.conf index 7f251ff4b..391637570 100644 --- a/apps/emqx_prometheus/i18n/emqx_prometheus_schema_i18n.conf +++ b/apps/emqx_prometheus/i18n/emqx_prometheus_schema_i18n.conf @@ -24,6 +24,16 @@ emqx_prometheus_schema { zh: """数据推送间隔""" } } + + headers { + desc { + en: """A list of HTTP Headers when pushing to Push Gateway.
+For example, { Authorization = "some-authz-tokens"}""" + zh: """推送到 Push Gateway 的 HTTP Headers 列表。
+例如, { Authorization = "some-authz-tokens"}""" + } + } + enable { desc { en: """Turn Prometheus data pushing on or off""" diff --git a/apps/emqx_prometheus/src/emqx_prometheus.erl b/apps/emqx_prometheus/src/emqx_prometheus.erl index 5424c4e24..623ae43f0 100644 --- a/apps/emqx_prometheus/src/emqx_prometheus.erl +++ b/apps/emqx_prometheus/src/emqx_prometheus.erl @@ -98,8 +98,12 @@ handle_cast(_Msg, State) -> {noreply, State}. handle_info({timeout, Timer, ?TIMER_MSG}, State = #{timer := Timer}) -> - #{interval := Interval, push_gateway_server := Server} = opts(), - PushRes = push_to_push_gateway(Server), + #{ + interval := Interval, + headers := Headers, + push_gateway_server := Server + } = opts(), + PushRes = push_to_push_gateway(Server, Headers), NewTimer = ensure_timer(Interval), NewState = maps:update_with(PushRes, fun(C) -> C + 1 end, 1, State#{timer => NewTimer}), %% Data is too big, hibernate for saving memory and stop system monitor warning. @@ -107,12 +111,19 @@ handle_info({timeout, Timer, ?TIMER_MSG}, State = #{timer := Timer}) -> handle_info(_Msg, State) -> {noreply, State}. -push_to_push_gateway(Uri) -> +push_to_push_gateway(Uri, Headers0) when is_map(Headers0) -> [Name, Ip] = string:tokens(atom_to_list(node()), "@"), Url = lists:concat([Uri, "/metrics/job/", Name, "/instance/", Name, "~", Ip]), Data = prometheus_text_format:format(), - case httpc:request(post, {Url, [], "text/plain", Data}, ?HTTP_OPTIONS, []) of - {ok, {{"HTTP/1.1", 200, "OK"}, _Headers, _Body}} -> + Headers = maps:fold( + fun(K, V, Acc) -> + [{atom_to_list(K), binary_to_list(V)} | Acc] + end, + [], + Headers0 + ), + case httpc:request(post, {Url, Headers, "text/plain", Data}, ?HTTP_OPTIONS, []) of + {ok, {{"HTTP/1.1", 200, _}, _Headers, _Body}} -> ok; Error -> ?SLOG(error, #{ diff --git a/apps/emqx_prometheus/src/emqx_prometheus_api.erl b/apps/emqx_prometheus/src/emqx_prometheus_api.erl index 7466a1fd1..1acdf99a7 100644 --- a/apps/emqx_prometheus/src/emqx_prometheus_api.erl +++ b/apps/emqx_prometheus/src/emqx_prometheus_api.erl @@ -121,6 +121,7 @@ prometheus_config_example() -> enable => true, interval => "15s", push_gateway_server => <<"http://127.0.0.1:9091">>, + headers => #{'header-name' => 'header-value'}, vm_dist_collector => enabled, mnesia_collector => enabled, vm_statistics_collector => enabled, diff --git a/apps/emqx_prometheus/src/emqx_prometheus_schema.erl b/apps/emqx_prometheus/src/emqx_prometheus_schema.erl index 688c9be58..40b9ed6a1 100644 --- a/apps/emqx_prometheus/src/emqx_prometheus_schema.erl +++ b/apps/emqx_prometheus/src/emqx_prometheus_schema.erl @@ -52,6 +52,15 @@ fields("prometheus") -> desc => ?DESC(interval) } )}, + {headers, + ?HOCON( + map(), + #{ + default => #{}, + required => false, + desc => ?DESC(headers) + } + )}, {enable, ?HOCON( boolean(), diff --git a/apps/emqx_prometheus/test/emqx_prometheus_SUITE.erl b/apps/emqx_prometheus/test/emqx_prometheus_SUITE.erl index b9df1103b..effdfc914 100644 --- a/apps/emqx_prometheus/test/emqx_prometheus_SUITE.erl +++ b/apps/emqx_prometheus/test/emqx_prometheus_SUITE.erl @@ -27,6 +27,7 @@ "prometheus {\n" " push_gateway_server = \"http://127.0.0.1:9091\"\n" " interval = \"1s\"\n" + " headers = { Authorization = \"some-authz-tokens\"}\n" " enable = true\n" " vm_dist_collector = enabled\n" " mnesia_collector = enabled\n" @@ -85,6 +86,25 @@ t_collector_no_crash_test(_) -> prometheus_text_format:format(), ok. +t_assert_push(_) -> + meck:new(httpc, [passthrough]), + Self = self(), + AssertPush = fun(Method, Req = {Url, Headers, ContentType, _Data}, HttpOpts, Opts) -> + ?assertEqual(post, Method), + ?assertMatch("http://127.0.0.1:9091/metrics/job/" ++ _, Url), + ?assertEqual([{"Authorization", "some-authz-tokens"}], Headers), + ?assertEqual("text/plain", ContentType), + Self ! pass, + meck:passthrough([Method, Req, HttpOpts, Opts]) + end, + meck:expect(httpc, request, AssertPush), + ?assertMatch(ok, emqx_prometheus_sup:start_child(emqx_prometheus)), + receive + pass -> ok + after 2000 -> + ct:fail(assert_push_request_failed) + end. + t_only_for_coverage(_) -> ?assertEqual("5.0.0", emqx_prometheus_proto_v1:introduced_in()), ok. From b1292d6d4f610e6a455c1665fc157a1381a5768c Mon Sep 17 00:00:00 2001 From: JianBo He Date: Tue, 3 Jan 2023 18:27:41 +0800 Subject: [PATCH 2/5] feat(prom): support configuring job_name for pushing mode --- .../i18n/emqx_prometheus_schema_i18n.conf | 19 +++++++++++++++++++ apps/emqx_prometheus/src/emqx_prometheus.erl | 15 ++++++++++++--- .../src/emqx_prometheus_api.erl | 1 + .../src/emqx_prometheus_schema.erl | 10 ++++++++++ .../test/emqx_prometheus_SUITE.erl | 3 ++- 5 files changed, 44 insertions(+), 4 deletions(-) diff --git a/apps/emqx_prometheus/i18n/emqx_prometheus_schema_i18n.conf b/apps/emqx_prometheus/i18n/emqx_prometheus_schema_i18n.conf index 391637570..f25e35219 100644 --- a/apps/emqx_prometheus/i18n/emqx_prometheus_schema_i18n.conf +++ b/apps/emqx_prometheus/i18n/emqx_prometheus_schema_i18n.conf @@ -34,6 +34,25 @@ For example, { Authorization = "some-authz-tokens"}""" } } + job_name { + desc { + en: """Job Name that is pushed to the Push Gateway. Available variables:
+- ${name}: Name of EMQX node.
+- ${host}: Host name of EMQX node.
+For example, when the EMQX node name is emqx@127.0.0.1 then the name variable takes value emqx and the host variable takes value 127.0.0.1.
+ +Default value is: ${name}/instance/${name}~${host} +""" + zh: """推送到 Push Gateway 的 Job 名称。可用变量为:
+- ${name}: EMQX 节点的名称。 +- ${host}: EMQX 节点主机名。 + +例如,当 EMQX 节点名为 emqx@127.0.0.1 则 name 变量的值为 emqx,host 变量的值为 127.0.0.1
+ +默认值为: ${name}/instance/${name}~${host}""" + } + } + enable { desc { en: """Turn Prometheus data pushing on or off""" diff --git a/apps/emqx_prometheus/src/emqx_prometheus.erl b/apps/emqx_prometheus/src/emqx_prometheus.erl index 623ae43f0..91c1f22fc 100644 --- a/apps/emqx_prometheus/src/emqx_prometheus.erl +++ b/apps/emqx_prometheus/src/emqx_prometheus.erl @@ -101,9 +101,10 @@ handle_info({timeout, Timer, ?TIMER_MSG}, State = #{timer := Timer}) -> #{ interval := Interval, headers := Headers, + job_name := JobName, push_gateway_server := Server } = opts(), - PushRes = push_to_push_gateway(Server, Headers), + PushRes = push_to_push_gateway(Server, Headers, JobName), NewTimer = ensure_timer(Interval), NewState = maps:update_with(PushRes, fun(C) -> C + 1 end, 1, State#{timer => NewTimer}), %% Data is too big, hibernate for saving memory and stop system monitor warning. @@ -111,9 +112,17 @@ handle_info({timeout, Timer, ?TIMER_MSG}, State = #{timer := Timer}) -> handle_info(_Msg, State) -> {noreply, State}. -push_to_push_gateway(Uri, Headers0) when is_map(Headers0) -> +push_to_push_gateway(Uri, Headers0, JobName) when is_map(Headers0) -> [Name, Ip] = string:tokens(atom_to_list(node()), "@"), - Url = lists:concat([Uri, "/metrics/job/", Name, "/instance/", Name, "~", Ip]), + JobName1 = emqx_placeholder:preproc_tmpl(JobName), + JobName2 = binary_to_list( + emqx_placeholder:proc_tmpl( + JobName1, + #{<<"name">> => Name, <<"host">> => Ip} + ) + ), + + Url = lists:concat([Uri, "/metrics/job/", JobName2]), Data = prometheus_text_format:format(), Headers = maps:fold( fun(K, V, Acc) -> diff --git a/apps/emqx_prometheus/src/emqx_prometheus_api.erl b/apps/emqx_prometheus/src/emqx_prometheus_api.erl index 1acdf99a7..945c6eba9 100644 --- a/apps/emqx_prometheus/src/emqx_prometheus_api.erl +++ b/apps/emqx_prometheus/src/emqx_prometheus_api.erl @@ -122,6 +122,7 @@ prometheus_config_example() -> interval => "15s", push_gateway_server => <<"http://127.0.0.1:9091">>, headers => #{'header-name' => 'header-value'}, + job_name => <<"${name}/instance/${name}~${host}">>, vm_dist_collector => enabled, mnesia_collector => enabled, vm_statistics_collector => enabled, diff --git a/apps/emqx_prometheus/src/emqx_prometheus_schema.erl b/apps/emqx_prometheus/src/emqx_prometheus_schema.erl index 40b9ed6a1..da65e02c4 100644 --- a/apps/emqx_prometheus/src/emqx_prometheus_schema.erl +++ b/apps/emqx_prometheus/src/emqx_prometheus_schema.erl @@ -61,6 +61,16 @@ fields("prometheus") -> desc => ?DESC(headers) } )}, + {job_name, + ?HOCON( + binary(), + #{ + default => <<"${name}/instance/${name}~${host}">>, + required => true, + desc => ?DESC(job_name) + } + )}, + {enable, ?HOCON( boolean(), diff --git a/apps/emqx_prometheus/test/emqx_prometheus_SUITE.erl b/apps/emqx_prometheus/test/emqx_prometheus_SUITE.erl index effdfc914..77d9902a2 100644 --- a/apps/emqx_prometheus/test/emqx_prometheus_SUITE.erl +++ b/apps/emqx_prometheus/test/emqx_prometheus_SUITE.erl @@ -28,6 +28,7 @@ " push_gateway_server = \"http://127.0.0.1:9091\"\n" " interval = \"1s\"\n" " headers = { Authorization = \"some-authz-tokens\"}\n" + " job_name = \"${name}~${host}\"\n" " enable = true\n" " vm_dist_collector = enabled\n" " mnesia_collector = enabled\n" @@ -91,7 +92,7 @@ t_assert_push(_) -> Self = self(), AssertPush = fun(Method, Req = {Url, Headers, ContentType, _Data}, HttpOpts, Opts) -> ?assertEqual(post, Method), - ?assertMatch("http://127.0.0.1:9091/metrics/job/" ++ _, Url), + ?assertMatch("http://127.0.0.1:9091/metrics/job/test~127.0.0.1", Url), ?assertEqual([{"Authorization", "some-authz-tokens"}], Headers), ?assertEqual("text/plain", ContentType), Self ! pass, From 371b42eef295ebcb08a9c9fd88e9430ecc187fbb Mon Sep 17 00:00:00 2001 From: JianBo He Date: Wed, 11 Jan 2023 17:46:00 +0800 Subject: [PATCH 3/5] chore: update changes --- changes/v5.0.15/feat-9722.en.md | 3 +++ changes/v5.0.15/feat-9722.zh.md | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 changes/v5.0.15/feat-9722.en.md create mode 100644 changes/v5.0.15/feat-9722.zh.md diff --git a/changes/v5.0.15/feat-9722.en.md b/changes/v5.0.15/feat-9722.en.md new file mode 100644 index 000000000..b86f37b83 --- /dev/null +++ b/changes/v5.0.15/feat-9722.en.md @@ -0,0 +1,3 @@ +Add the following configuration options for Pushing metrics to Prometheus Push Gateway: +- `headers`: Allows custom HTTP request headers. +- `job_name`: allows to customize the name of the Job pushed to Push Gateway. diff --git a/changes/v5.0.15/feat-9722.zh.md b/changes/v5.0.15/feat-9722.zh.md new file mode 100644 index 000000000..a806cb1de --- /dev/null +++ b/changes/v5.0.15/feat-9722.zh.md @@ -0,0 +1,3 @@ +为 Prometheus 推送到 Push Gateway 新增以下配置项: +- `headers`:允许自定义 HTTP 请求头。 +- `job_name`:允许自定义推送到 Push Gateway 的 Job 名称。 From 9f6df2775960ac096a00d573962a17427938a537 Mon Sep 17 00:00:00 2001 From: JianBo He Date: Wed, 11 Jan 2023 17:46:57 +0800 Subject: [PATCH 4/5] chore: bump app.src vsn --- apps/emqx_prometheus/src/emqx_prometheus.app.src | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/emqx_prometheus/src/emqx_prometheus.app.src b/apps/emqx_prometheus/src/emqx_prometheus.app.src index d95c89c3b..31f8cbfaf 100644 --- a/apps/emqx_prometheus/src/emqx_prometheus.app.src +++ b/apps/emqx_prometheus/src/emqx_prometheus.app.src @@ -2,7 +2,7 @@ {application, emqx_prometheus, [ {description, "Prometheus for EMQX"}, % strict semver, bump manually! - {vsn, "5.0.3"}, + {vsn, "5.0.4"}, {modules, []}, {registered, [emqx_prometheus_sup]}, {applications, [kernel, stdlib, prometheus, emqx]}, From 1194d07a07598d602a4597c61623231051ceb470 Mon Sep 17 00:00:00 2001 From: Zhongwen Deng Date: Thu, 12 Jan 2023 12:48:26 +0800 Subject: [PATCH 5/5] refactor: update emqx_prometheus's headers from map() to list() --- .../src/emqx_dashboard_swagger.erl | 2 ++ apps/emqx_prometheus/src/emqx_prometheus.erl | 14 ++++---------- .../src/emqx_prometheus_schema.erl | 17 +++++++++++++++-- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/apps/emqx_dashboard/src/emqx_dashboard_swagger.erl b/apps/emqx_dashboard/src/emqx_dashboard_swagger.erl index 102b95f4e..1b5b6ca9c 100644 --- a/apps/emqx_dashboard/src/emqx_dashboard_swagger.erl +++ b/apps/emqx_dashboard/src/emqx_dashboard_swagger.erl @@ -708,6 +708,8 @@ typename_to_spec("qos()", _Mod) -> #{type => integer, minimum => 0, maximum => 2, example => 0}; typename_to_spec("{binary(), binary()}", _Mod) -> #{type => object, example => #{}}; +typename_to_spec("{string(), string()}", _Mod) -> + #{type => object, example => #{}}; typename_to_spec("comma_separated_list()", _Mod) -> #{type => string, example => <<"item1,item2">>}; typename_to_spec("comma_separated_binary()", _Mod) -> diff --git a/apps/emqx_prometheus/src/emqx_prometheus.erl b/apps/emqx_prometheus/src/emqx_prometheus.erl index 91c1f22fc..a66f275f8 100644 --- a/apps/emqx_prometheus/src/emqx_prometheus.erl +++ b/apps/emqx_prometheus/src/emqx_prometheus.erl @@ -112,7 +112,7 @@ handle_info({timeout, Timer, ?TIMER_MSG}, State = #{timer := Timer}) -> handle_info(_Msg, State) -> {noreply, State}. -push_to_push_gateway(Uri, Headers0, JobName) when is_map(Headers0) -> +push_to_push_gateway(Uri, Headers, JobName) when is_list(Headers) -> [Name, Ip] = string:tokens(atom_to_list(node()), "@"), JobName1 = emqx_placeholder:preproc_tmpl(JobName), JobName2 = binary_to_list( @@ -124,21 +124,15 @@ push_to_push_gateway(Uri, Headers0, JobName) when is_map(Headers0) -> Url = lists:concat([Uri, "/metrics/job/", JobName2]), Data = prometheus_text_format:format(), - Headers = maps:fold( - fun(K, V, Acc) -> - [{atom_to_list(K), binary_to_list(V)} | Acc] - end, - [], - Headers0 - ), case httpc:request(post, {Url, Headers, "text/plain", Data}, ?HTTP_OPTIONS, []) of - {ok, {{"HTTP/1.1", 200, _}, _Headers, _Body}} -> + {ok, {{"HTTP/1.1", 200, _}, _RespHeaders, _RespBody}} -> ok; Error -> ?SLOG(error, #{ msg => "post_to_push_gateway_failed", error => Error, - url => Url + url => Url, + headers => Headers }), failed end. diff --git a/apps/emqx_prometheus/src/emqx_prometheus_schema.erl b/apps/emqx_prometheus/src/emqx_prometheus_schema.erl index da65e02c4..c13d198a2 100644 --- a/apps/emqx_prometheus/src/emqx_prometheus_schema.erl +++ b/apps/emqx_prometheus/src/emqx_prometheus_schema.erl @@ -25,7 +25,8 @@ roots/0, fields/1, desc/1, - translation/1 + translation/1, + convert_headers/1 ]). namespace() -> "prometheus". @@ -54,10 +55,11 @@ fields("prometheus") -> )}, {headers, ?HOCON( - map(), + list({string(), string()}), #{ default => #{}, required => false, + converter => fun ?MODULE:convert_headers/1, desc => ?DESC(headers) } )}, @@ -145,6 +147,17 @@ fields("prometheus") -> desc("prometheus") -> ?DESC(prometheus); desc(_) -> undefined. +convert_headers(Headers) when is_map(Headers) -> + maps:fold( + fun(K, V, Acc) -> + [{binary_to_list(K), binary_to_list(V)} | Acc] + end, + [], + Headers + ); +convert_headers(Headers) when is_list(Headers) -> + Headers. + %% for CI test, CI don't load the whole emqx_conf_schema. translation(Name) -> emqx_conf_schema:translation(Name).