├── .gitignore ├── .yamllint ├── .markdownlint.yaml ├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ └── check_deps.yml ├── Makefile ├── src ├── erldns_metrics.app.src ├── erldns_metrics_app.erl ├── erldns_metrics_sup.erl ├── erldns_metrics_root_handler.erl └── erldns_metrics.erl ├── rebar.config ├── test └── erldns_metrics_root_handler_SUITE.erl ├── rebar.lock └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | rebar3 2 | _build 3 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | extends: default 3 | rules: 4 | line-length: 5 | max: 256 6 | level: warning 7 | truthy: 8 | ignore: | 9 | /.github/workflows/*.yml 10 | -------------------------------------------------------------------------------- /.markdownlint.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | default: true 3 | 4 | # no-hard-tabs 5 | MD010: 6 | code_blocks: false 7 | 8 | # no-multiple-blanks 9 | MD012: 10 | maximum: 2 11 | 12 | # line-length 13 | MD013: false 14 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | version: 2 4 | updates: 5 | - package-ecosystem: "github-actions" 6 | directory: "/" 7 | schedule: 8 | interval: "daily" 9 | time: '12:00' 10 | open-pull-requests-limit: 10 11 | labels: 12 | - task 13 | - dependencies 14 | - automerge 15 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: clean build 2 | 3 | .PHONY: build 4 | build: 5 | rebar3 compile 6 | 7 | .PHONY: clean 8 | clean: 9 | rebar3 clean 10 | 11 | .PHONY: wipe 12 | wipe: 13 | rm -Rf _build 14 | rebar3 clean 15 | 16 | .PHONY: fresh 17 | fresh: wipe build 18 | 19 | .PHONY: test 20 | test: 21 | rebar3 fmt --check 22 | rebar3 ct 23 | rebar3 dialyzer 24 | 25 | .PHONY: format 26 | format: 27 | rebar3 fmt 28 | -------------------------------------------------------------------------------- /src/erldns_metrics.app.src: -------------------------------------------------------------------------------- 1 | % -*- mode: Erlang; -*- 2 | {application, erldns_metrics, [ 3 | {description, "Erlang Authoritative DNS Server Metrics API"}, 4 | {vsn, "1.0.0"}, 5 | {licenses, ["MIT"]}, 6 | {mod, {erldns_metrics_app, []}}, 7 | {applications, [ 8 | kernel, 9 | stdlib, 10 | inets, 11 | crypto, 12 | lager, 13 | ssl, 14 | observer, 15 | bear, 16 | folsom, 17 | cowlib, 18 | ranch, 19 | cowboy, 20 | erldns 21 | ]} 22 | ]}. 23 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | %%-*- mode: erlang -*- 2 | {cover_enabled, true}. 3 | {erl_opts, [ 4 | debug_info, 5 | fail_on_warning, 6 | {platform_define, "^[0-9]+", namespaced_types}, 7 | {parse_transform, lager_transform} 8 | ]}. 9 | 10 | {minimum_otp_vsn, "27"}. 11 | 12 | {project_plugins, [ 13 | erlfmt, 14 | rebar3_depup 15 | ]}. 16 | 17 | {deps, [ 18 | {lager, "3.9.2"}, 19 | folsom, 20 | cowboy, 21 | dns_erlang, 22 | erldns 23 | ]}. 24 | 25 | {erlfmt, [ 26 | write, 27 | {print_width, 140} 28 | ]}. 29 | 30 | {dialyzer, [ 31 | {warnings, [no_unknown]} 32 | ]}. 33 | 34 | {shell, [ 35 | {apps, [erldns_metrics]} 36 | ]}. 37 | -------------------------------------------------------------------------------- /src/erldns_metrics_app.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) DNSimple Corporation 2 | %% 3 | %% Permission to use, copy, modify, and/or distribute this software for any 4 | %% purpose with or without fee is hereby granted, provided that the above 5 | %% copyright notice and this permission notice appear in all copies. 6 | %% 7 | %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | %% @doc The erldns OTP application. 16 | -module(erldns_metrics_app). 17 | -behavior(application). 18 | 19 | % Application hooks 20 | -export([start/2, stop/1]). 21 | 22 | start(_Type, _Args) -> 23 | lager:debug("Starting erldns_metrics application"), 24 | erldns_metrics_sup:start_link(). 25 | 26 | stop(_State) -> 27 | lager:info("Stop erldns_metrics application"), 28 | ok. 29 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: CI 4 | 5 | on: 6 | pull_request: 7 | push: 8 | branches: 9 | - main 10 | workflow_dispatch: 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.ref }} 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | markdownlint-cli: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout Code 21 | uses: actions/checkout@v4 22 | - name: Run markdownlint-cli 23 | uses: nosborn/github-action-markdown-cli@v3.4.0 24 | with: 25 | files: . 26 | config_file: ".markdownlint.yaml" 27 | 28 | yamllint: 29 | runs-on: ubuntu-latest 30 | steps: 31 | - name: Checkout Code 32 | uses: actions/checkout@v4 33 | - name: Run YAML Lint 34 | uses: actionshub/yamllint@main 35 | 36 | test: 37 | name: OTP ${{matrix.otp}} 38 | strategy: 39 | matrix: 40 | otp: ['27'] 41 | rebar3: ['3.24'] 42 | runs-on: 'ubuntu-24.04' 43 | steps: 44 | - uses: actions/checkout@v4 45 | - uses: erlef/setup-beam@v1 46 | with: 47 | otp-version: ${{matrix.otp}} 48 | rebar3-version: ${{matrix.rebar3}} 49 | - uses: actions/cache@v4 50 | with: 51 | path: | 52 | ~/.cache/rebar3 53 | _build 54 | key: ${{ runner.os }}-erlang-${{ matrix.otp }}-rebar3-${{ matrix.rebar3 }}-hash-${{hashFiles('rebar.lock')}} 55 | - run: make build 56 | - run: make test 57 | -------------------------------------------------------------------------------- /src/erldns_metrics_sup.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) DNSimple Corporation 2 | %% 3 | %% Permission to use, copy, modify, and/or distribute this software for any 4 | %% purpose with or without fee is hereby granted, provided that the above 5 | %% copyright notice and this permission notice appear in all copies. 6 | %% 7 | %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | %% @doc Application supervisor. Supervises everything except the UDP and TCP 16 | %% listeners and the zone checker. 17 | -module(erldns_metrics_sup). 18 | -behavior(supervisor). 19 | 20 | % API 21 | -export([start_link/0]). 22 | 23 | % Supervisor hooks 24 | -export([init/1]). 25 | 26 | -define(SUPERVISOR, ?MODULE). 27 | 28 | %% Helper macro for declaring children of supervisor 29 | -define(CHILD(I, Type, Args), {I, {I, start_link, Args}, permanent, 5000, Type, [I]}). 30 | 31 | %% Public API 32 | -spec start_link() -> any(). 33 | start_link() -> 34 | supervisor:start_link({local, ?SUPERVISOR}, ?MODULE, []). 35 | 36 | %% Callbacks 37 | init(_Args) -> 38 | SysProcs = [ 39 | ?CHILD(erldns_metrics, worker, []) 40 | ], 41 | 42 | {ok, {{one_for_one, 20, 10}, SysProcs}}. 43 | -------------------------------------------------------------------------------- /test/erldns_metrics_root_handler_SUITE.erl: -------------------------------------------------------------------------------- 1 | -module(erldns_metrics_root_handler_SUITE). 2 | 3 | -include_lib("common_test/include/ct.hrl"). 4 | -include_lib("eunit/include/eunit.hrl"). 5 | 6 | %% CT callbacks 7 | -export([ 8 | suite/0, 9 | all/0, 10 | init_per_suite/1, 11 | end_per_suite/1, 12 | init_per_testcase/2, 13 | end_per_testcase/2 14 | ]). 15 | 16 | %% Tests 17 | -export([to_json_test/1, to_html_test/1, to_text_test/1]). 18 | 19 | all() -> [to_json_test, to_html_test, to_text_test]. 20 | 21 | suite() -> []. 22 | 23 | init_per_suite(Config) -> 24 | {ok, _} = application:ensure_all_started(erldns_metrics), 25 | Config. 26 | 27 | end_per_suite(_Config) -> 28 | application:stop(erldns_metrics), 29 | ok. 30 | 31 | init_per_testcase(_TestCase, Config) -> 32 | Config. 33 | 34 | end_per_testcase(_TestCase, _Config) -> 35 | ok. 36 | 37 | to_json_test(_Config) -> 38 | Req = {}, 39 | State = {}, 40 | {Body, Req, State} = erldns_metrics_root_handler:to_json(Req, State), 41 | DecodedBody = json:decode(Body), 42 | ?assertMatch( 43 | #{ 44 | <<"erldns">> := #{ 45 | <<"metrics">> := _, 46 | <<"stats">> := _, 47 | <<"vm">> := #{<<"memory">> := #{<<"atom">> := _}}, 48 | <<"ets">> := _, 49 | <<"processes">> := _ 50 | } 51 | }, 52 | DecodedBody 53 | ), 54 | ok. 55 | 56 | to_html_test(_Config) -> 57 | ?assertMatch( 58 | {<<"erldns metrics">>, _, _}, 59 | erldns_metrics_root_handler:to_html({}, {}) 60 | ). 61 | 62 | to_text_test(_Config) -> 63 | ?assertMatch( 64 | {<<"erldns metrics">>, _, _}, 65 | erldns_metrics_root_handler:to_text({}, {}) 66 | ). 67 | -------------------------------------------------------------------------------- /.github/workflows/check_deps.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: Check Dependencies 4 | 5 | on: 6 | workflow_dispatch: 7 | schedule: 8 | - cron: '38 8 * * *' 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.ref }} 12 | cancel-in-progress: true 13 | 14 | permissions: write-all 15 | 16 | jobs: 17 | check-deps: 18 | name: Check Dependencies 19 | runs-on: ubuntu-latest 20 | 21 | steps: 22 | - uses: actions/checkout@v4 23 | 24 | - uses: erlef/setup-beam@v1 25 | with: 26 | otp-version: '27.x' 27 | rebar3-version: '3.x' 28 | 29 | - uses: actions/cache@v4 30 | with: 31 | path: | 32 | ~/.cache/rebar3 33 | _build 34 | key: ${{ runner.os }}-erlang-${{ matrix.otp_version }}-${{ hashFiles('**/*rebar.lock') }} 35 | 36 | - name: Build 37 | run: make build 38 | 39 | - name: Update dependencies 40 | id: update-deps 41 | run: | 42 | _update_output="$(TERM=dumb rebar3 update-deps --replace)" 43 | 44 | cat < /tmp/pr-body.md 45 | Output of running \`rebar3 update-deps\`: 46 | 47 | \`\`\` 48 | $_update_output 49 | \`\`\` 50 | EOF 51 | 52 | rebar3 fmt --write 53 | 54 | # We can always run this step because the action will exit silently if there are no changes. 55 | # See: https://github.com/marketplace/actions/create-pull-request#action-behaviour 56 | - name: Create PR 57 | uses: peter-evans/create-pull-request@v7 58 | with: 59 | # By always using the same branch name, we can keep pushing to 60 | # the same branch if there are new changes. 61 | branch: "automatic-dependencies-update" 62 | commit-message: "Update dependencies" 63 | title: "Update dependencies" 64 | body-path: /tmp/pr-body.md 65 | labels: "dependencies,task" 66 | delete-branch: true 67 | -------------------------------------------------------------------------------- /src/erldns_metrics_root_handler.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) DNSimple Corporation 2 | %% 3 | %% Permission to use, copy, modify, and/or distribute this software for any 4 | %% purpose with or without fee is hereby granted, provided that the above 5 | %% copyright notice and this permission notice appear in all copies. 6 | %% 7 | %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | %% @doc Cowboy handler for the Metrics API endpoint / 16 | -module(erldns_metrics_root_handler). 17 | 18 | -export([init/2]). 19 | -export([content_types_provided/2]). 20 | -export([to_html/2, to_json/2, to_text/2]). 21 | 22 | -behaviour(cowboy_rest). 23 | 24 | init(Req, State) -> 25 | {cowboy_rest, Req, State}. 26 | 27 | content_types_provided(Req, State) -> 28 | { 29 | [ 30 | {<<"text/html">>, to_html}, 31 | {<<"text/plain">>, to_text}, 32 | {<<"application/json">>, to_json} 33 | ], 34 | Req, 35 | State 36 | }. 37 | 38 | to_html(Req, State) -> 39 | {<<"erldns metrics">>, Req, State}. 40 | 41 | to_text(Req, State) -> 42 | {<<"erldns metrics">>, Req, State}. 43 | 44 | to_json(Req, State) -> 45 | Metrics = [ 46 | {<<"erldns">>, [ 47 | {<<"metrics">>, erldns_metrics:filtered_metrics()}, 48 | {<<"stats">>, erldns_metrics:filtered_stats()}, 49 | {<<"vm">>, erldns_metrics:filtered_vm()}, 50 | {<<"ets">>, erldns_metrics:filtered_ets()}, 51 | {<<"processes">>, erldns_metrics:filtered_process_metrics()} 52 | ]} 53 | ], 54 | 55 | Body = iolist_to_binary(json:encode(Metrics, fun encode_to_json/2)), 56 | {Body, Req, State}. 57 | 58 | encode_to_json([{_, _} | _] = Value, Encode) -> json:encode_key_value_list(Value, Encode); 59 | encode_to_json(Value, Encode) -> json:encode_value(Value, Encode). 60 | -------------------------------------------------------------------------------- /rebar.lock: -------------------------------------------------------------------------------- 1 | {"1.2.0", 2 | [{<<"base32">>,{pkg,<<"base32">>,<<"0.1.0">>},1}, 3 | {<<"bear">>,{pkg,<<"bear">>,<<"1.0.0">>},1}, 4 | {<<"cowboy">>,{pkg,<<"cowboy">>,<<"2.12.0">>},0}, 5 | {<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.13.0">>},1}, 6 | {<<"dns_erlang">>,{pkg,<<"dns_erlang">>,<<"1.1.0">>},0}, 7 | {<<"erldns">>,{pkg,<<"erldns">>,<<"1.0.0">>},0}, 8 | {<<"folsom">>,{pkg,<<"folsom">>,<<"1.0.0">>},0}, 9 | {<<"goldrush">>,{pkg,<<"goldrush">>,<<"0.1.9">>},1}, 10 | {<<"iso8601">>,{pkg,<<"iso8601">>,<<"1.3.4">>},1}, 11 | {<<"jsx">>,{pkg,<<"jsx">>,<<"2.10.0">>},1}, 12 | {<<"lager">>,{pkg,<<"lager">>,<<"3.9.2">>},0}, 13 | {<<"ranch">>,{pkg,<<"ranch">>,<<"1.8.0">>},1}, 14 | {<<"recon">>,{pkg,<<"recon">>,<<"2.5.5">>},1}]}. 15 | [ 16 | {pkg_hash,[ 17 | {<<"base32">>, <<"044F6DC95709727CA2176F3E97A41DDAA76B5BC690D3536908618C0CB32616A2">>}, 18 | {<<"bear">>, <<"430419C1126B477686CDE843E88BA0F2C7DC5CDF0881C677500074F704339A99">>}, 19 | {<<"cowboy">>, <<"F276D521A1FF88B2B9B4C54D0E753DA6C66DD7BE6C9FCA3D9418B561828A3731">>}, 20 | {<<"cowlib">>, <<"DB8F7505D8332D98EF50A3EF34B34C1AFDDEC7506E4EE4DD4A3A266285D282CA">>}, 21 | {<<"dns_erlang">>, <<"1E3EA9BD45977198F5F1F3B0FBCC52DB3EFE9661669A43DBA4B9F7917460040D">>}, 22 | {<<"erldns">>, <<"58AAECC66CF7C49F511662B120883C6F44F802A1A2F2BAA879B050F99CACDA77">>}, 23 | {<<"folsom">>, <<"50ECC998D2149939F1D5E0AA3E32788F8ED16A58E390D81B5C0BE4CC4EF25589">>}, 24 | {<<"goldrush">>, <<"F06E5D5F1277DA5C413E84D5A2924174182FB108DABB39D5EC548B27424CD106">>}, 25 | {<<"iso8601">>, <<"7B1F095F86F6CF65E1E5A77872E8E8BF69BD58D4C3A415B3F77D9CC9423ECBB9">>}, 26 | {<<"jsx">>, <<"77760560D6AC2B8C51FD4C980E9E19B784016AA70BE354CE746472C33BEB0B1C">>}, 27 | {<<"lager">>, <<"4CAB289120EB24964E3886BD22323CB5FEFE4510C076992A23AD18CF85413D8C">>}, 28 | {<<"ranch">>, <<"8C7A100A139FD57F17327B6413E4167AC559FBC04CA7448E9BE9057311597A1D">>}, 29 | {<<"recon">>, <<"C108A4C406FA301A529151A3BB53158CADC4064EC0C5F99B03DDB8C0E4281BDF">>}]}, 30 | {pkg_hash_ext,[ 31 | {<<"base32">>, <<"10A73951D857D8CB1ECEEA8EB96C6941F6A76E105947AD09C2B73977DEE07638">>}, 32 | {<<"bear">>, <<"157B67901ADF84FF0DA6EAE035CA1292A0AC18AA55148154D8C582B2C68959DB">>}, 33 | {<<"cowboy">>, <<"8A7ABE6D183372CEB21CAA2709BEC928AB2B72E18A3911AA1771639BEF82651E">>}, 34 | {<<"cowlib">>, <<"E1E1284DC3FC030A64B1AD0D8382AE7E99DA46C3246B815318A4B848873800A4">>}, 35 | {<<"dns_erlang">>, <<"DBCC00560CB76C49EB70C40F931BBA1177CFC28463FFED6303200CCF88BF2D6E">>}, 36 | {<<"erldns">>, <<"AB13864D68DA8F28F556D032FD15CF954E8C63A202A835DFACD32CDDF04585CF">>}, 37 | {<<"folsom">>, <<"DD6AB97278E94F9E4CFC43E188224A7B8C7EAEC0DD2E935007005177F3EEBB0E">>}, 38 | {<<"goldrush">>, <<"99CB4128CFFCB3227581E5D4D803D5413FA643F4EB96523F77D9E6937D994CEB">>}, 39 | {<<"iso8601">>, <<"A334469C07F1C219326BC891A95F5EEC8EB12DD8071A3FFF56A7843CB20FAE34">>}, 40 | {<<"jsx">>, <<"9A83E3704807298016968DB506F9FAD0F027DE37546EB838B3AE1064C3A0AD62">>}, 41 | {<<"lager">>, <<"7F904D9E87A8CB7E66156ED31768D1C8E26EBA1D54F4BC85B1AA4AC1F6340C28">>}, 42 | {<<"ranch">>, <<"49FBCFD3682FAB1F5D109351B61257676DA1A2FDBE295904176D5E521A2DDFE5">>}, 43 | {<<"recon">>, <<"632A6F447DF7CCC1A4A10BDCFCE71514412B16660FE59DECA0FCF0AA3C054404">>}]} 44 | ]. 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Archived 2 | 3 | > [!CAUTION] 4 | > This repo has been archived and its contents have been merged into [`erldns`](https://github.com/dnsimple/erldns). 5 | 6 | ## Metrics API for [erldns] 7 | 8 | This app provides an HTTP API for gathering and querying metrics from an [erldns] server and presenting those metrics as JSON. 9 | 10 | [![Build Status](https://github.com/dnsimple/erldns-metrics/actions/workflows/ci.yml/badge.svg)](https://github.com/dnsimple/erldns-metrics/actions/workflows/ci.yml) 11 | [![Module Version](https://img.shields.io/hexpm/v/erldns-metrics.svg)](https://hex.pm/packages/erldns-metrics) 12 | 13 | > [!NOTE] 14 | > erldns_metrics is architected to run in the *same Erlang VM* as erldns, as it reads metrics from the runtime. This is why erldns is a dependency of this library, and gets started in [`erldns_metrics.app.src`](./src/erldns_metrics.app.src): it's useful for local development, so that starting erldns_metrics also starts erldns. 15 | > In general, you might want to run erldns_metrics and erldns both as dependencies of an application that *you* control and deploy. 16 | 17 | Here's an example script that shows how to get the output with `curl` and pass it through Python to format it in a pretty fashion. It assumes you have this API running on port `8082`. 18 | 19 | ```bash 20 | curl -s http://localhost:8082/ -H "Accept: application/json" | python -mjson.tool 21 | ``` 22 | 23 | > [!IMPORTANT] 24 | > Timing stats are given in microseconds. 25 | 26 | ## Configuration 27 | 28 | To run this application and configure erldns, add something like the following to your Erlang configuration section: 29 | 30 | ```erlang 31 | [ 32 | {erldns, [ 33 | {metrics, [ 34 | {port, 8082} 35 | ]}, 36 | ]} 37 | ]. 38 | ``` 39 | 40 | ## Building 41 | 42 | To build: 43 | 44 | ```bash 45 | make 46 | ``` 47 | 48 | To start fresh: 49 | 50 | ```bash 51 | make fresh 52 | ``` 53 | 54 | ## Running 55 | 56 | You'll need [erldns] running. erldns_metrics starts it on startup, so you just need to run this in the erldns_metrics repository: 57 | 58 | ```bash 59 | rebar3 shell 60 | ``` 61 | 62 | If you need to configure erldns (such as to use a different port), you can store configuration such as the one below in a file such as `erldns_metrics.config`: 63 | 64 | ```erlang 65 | [ 66 | {erldns, [ 67 | {servers, [ 68 | [ 69 | {name, inet_localhost_1}, 70 | {address, "127.0.0.1"}, 71 | {port, 8053}, 72 | {family, inet}, 73 | {processes, 2} 74 | ], 75 | [{name, inet6_localhost_1}, {address, "::1"}, {port, 8053}, {family, inet6}] 76 | ]} 77 | ]} 78 | ]. 79 | ``` 80 | 81 | Then, you can run erldns_metrics as: 82 | 83 | ```bash 84 | rebar3 shell --config erldns_metrics.config 85 | ``` 86 | 87 | ## Testing 88 | 89 | ```bash 90 | make test 91 | ``` 92 | 93 | ## Formatting 94 | 95 | If your editor doesn't automatically format Erlang code using [erlfmt](https://github.com/WhatsApp/erlfmt), run: 96 | 97 | ```bash 98 | make format 99 | ``` 100 | 101 | [erldns]: https://github.com/dnsimple/erldns 102 | -------------------------------------------------------------------------------- /src/erldns_metrics.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) DNSimple Corporation 2 | %% 3 | %% Permission to use, copy, modify, and/or distribute this software for any 4 | %% purpose with or without fee is hereby granted, provided that the above 5 | %% copyright notice and this permission notice appear in all copies. 6 | %% 7 | %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | %% ACTION OF T, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | %% @doc gen server that provides access to metrics recorded through Folsom. 16 | -module(erldns_metrics). 17 | 18 | -behavior(gen_server). 19 | 20 | -export([start_link/0]). 21 | 22 | -export([ 23 | metrics/0, 24 | stats/0, 25 | vm/0, 26 | ets/0, 27 | process_metrics/0, 28 | filtered_metrics/0, 29 | filtered_stats/0, 30 | filtered_vm/0, 31 | filtered_ets/0, 32 | filtered_process_metrics/0 33 | ]). 34 | 35 | -define(DEFAULT_PORT, 8082). 36 | 37 | % Gen server hooks 38 | -export([ 39 | init/1, 40 | handle_call/3, 41 | handle_cast/2, 42 | handle_info/2, 43 | terminate/2, 44 | code_change/3 45 | ]). 46 | 47 | -record(state, {}). 48 | 49 | %% Not part of gen server 50 | 51 | metrics() -> 52 | lists:map( 53 | fun(Name) -> 54 | {Name, folsom_metrics:get_metric_value(Name)} 55 | end, 56 | folsom_metrics:get_metrics() 57 | ). 58 | 59 | filtered_metrics() -> 60 | filter_metrics(metrics()). 61 | 62 | stats() -> 63 | Histograms = [udp_handoff_histogram, tcp_handoff_histogram, request_handled_histogram], 64 | lists:map( 65 | fun(Name) -> 66 | {Name, folsom_metrics:get_histogram_statistics(Name)} 67 | end, 68 | Histograms 69 | ). 70 | 71 | filtered_stats() -> 72 | filter_stats(stats()). 73 | 74 | % Functions to clean up metrics so they can be returned as JSON. 75 | filter_metrics(Metrics) -> 76 | filter_metrics(Metrics, []). 77 | 78 | filter_metrics([], FilteredMetrics) -> 79 | FilteredMetrics; 80 | filter_metrics([{Name, History = [{Timestamp, _Values} | _Rest]} | Rest], FilteredMetrics) when is_number(Timestamp) -> 81 | filter_metrics(Rest, FilteredMetrics ++ [{Name, filter_history_entries(History)}]); 82 | filter_metrics([{Name, Metrics} | Rest], FilteredMetrics) -> 83 | filter_metrics(Rest, FilteredMetrics ++ [{Name, Metrics}]). 84 | 85 | filter_history_entries(HistoryEntries) -> 86 | filter_history_entries(HistoryEntries, []). 87 | filter_history_entries([], FilteredHistoryEntries) -> 88 | FilteredHistoryEntries; 89 | filter_history_entries([{Timestamp, Values} | Rest], FilteredHistoryEntries) -> 90 | filter_history_entries(Rest, FilteredHistoryEntries ++ [filter_history_entry(Timestamp, Values)]). 91 | 92 | filter_history_entry(Timestamp, Values) -> 93 | [{<<"timestamp">>, Timestamp}, {<<"values">>, lists:map(fun({event, Value}) -> Value end, Values)}]. 94 | 95 | % Functions to clean up the stats so they can be returned as JSON. 96 | filter_stats(Stats) -> 97 | filter_stats(Stats, []). 98 | 99 | filter_stats([], FilteredStats) -> 100 | FilteredStats; 101 | filter_stats([{Name, Stats} | Rest], FilteredStats) -> 102 | filter_stats(Rest, FilteredStats ++ [{Name, filter_stat_set(Stats)}]). 103 | 104 | filter_stat_set(Stats) -> 105 | filter_stat_set(Stats, []). 106 | 107 | filter_stat_set([], FilteredStatSet) -> 108 | FilteredStatSet; 109 | filter_stat_set([{percentile, Percentiles} | Rest], FilteredStatSet) -> 110 | filter_stat_set(Rest, FilteredStatSet ++ [{percentile, keys_to_strings(Percentiles)}]); 111 | filter_stat_set([{histogram, _} | Rest], FilteredStatSet) -> 112 | filter_stat_set(Rest, FilteredStatSet); 113 | filter_stat_set([Pair | Rest], FilteredStatSet) -> 114 | filter_stat_set(Rest, FilteredStatSet ++ [Pair]). 115 | 116 | vm() -> 117 | [ 118 | {<<"memory">>, folsom_vm_metrics:get_memory()} 119 | ]. 120 | 121 | filtered_vm() -> 122 | vm(). 123 | 124 | ets() -> 125 | [ 126 | {<<"ets">>, ets_metrics()} 127 | ]. 128 | 129 | filtered_ets() -> 130 | lists:map( 131 | fun(TableData) -> 132 | { 133 | proplists:get_value(name, TableData), 134 | [ 135 | {compressed, proplists:get_value(compressed, TableData)}, 136 | {size, proplists:get_value(size, TableData)}, 137 | {type, atom_to_binary(proplists:get_value(type, TableData), latin1)}, 138 | {protection, atom_to_binary(proplists:get_value(protection, TableData), latin1)} 139 | ] 140 | } 141 | end, 142 | ets_metrics() 143 | ). 144 | 145 | ets_metrics() -> 146 | lists:map(fun(Name) -> ets:info(Name) end, ets:all()). 147 | 148 | keys_to_strings(Pairs) -> 149 | lists:map( 150 | fun({K, V}) -> 151 | {list_to_binary(lists:flatten(io_lib:format("~p", [K]))), V} 152 | end, 153 | Pairs 154 | ). 155 | 156 | process_metrics() -> 157 | lists:map( 158 | fun(ProcessName) -> 159 | Pid = whereis(ProcessName), 160 | { 161 | ProcessName, 162 | [ 163 | process_info(Pid, memory), 164 | process_info(Pid, heap_size), 165 | process_info(Pid, stack_size), 166 | process_info(Pid, message_queue_len) 167 | ] 168 | } 169 | end, 170 | registered() 171 | ). 172 | 173 | filtered_process_metrics() -> 174 | process_metrics(). 175 | 176 | %% Gen server 177 | start_link() -> 178 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 179 | 180 | init([]) -> 181 | lager:debug("Starting ~p", [?MODULE]), 182 | 183 | Dispatch = cowboy_router:compile( 184 | [ 185 | {'_', [ 186 | {"/", erldns_metrics_root_handler, []} 187 | ]} 188 | ] 189 | ), 190 | 191 | {ok, _} = cowboy:start_clear(?MODULE, [{port, port()}], #{env => #{dispatch => Dispatch}}), 192 | 193 | {ok, #state{}}. 194 | 195 | handle_call(_Message, _From, State) -> 196 | {reply, ok, State}. 197 | handle_cast(_, State) -> 198 | {noreply, State}. 199 | handle_info(_, State) -> 200 | {noreply, State}. 201 | terminate(_, _) -> 202 | ok. 203 | code_change(_PreviousVersion, State, _Extra) -> 204 | {ok, State}. 205 | 206 | port() -> 207 | proplists:get_value(port, metrics_env(), ?DEFAULT_PORT). 208 | 209 | metrics_env() -> 210 | case application:get_env(erldns, metrics) of 211 | {ok, MetricsEnv} -> MetricsEnv; 212 | _ -> [] 213 | end. 214 | --------------------------------------------------------------------------------