Feature/826 healthcheck endpoint Closes #826 See merge request pleroma/pleroma!1083tags/v1.1.4
@@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). | |||||
- Configuration: `fetch_initial_posts` option | - Configuration: `fetch_initial_posts` option | ||||
- Configuration: `notify_email` option | - Configuration: `notify_email` option | ||||
- Pleroma API: User subscribtions | - Pleroma API: User subscribtions | ||||
- Pleroma API: Healthcheck endpoint | |||||
- Admin API: Endpoints for listing/revoking invite tokens | - Admin API: Endpoints for listing/revoking invite tokens | ||||
- Admin API: Endpoints for making users follow/unfollow each other | - Admin API: Endpoints for making users follow/unfollow each other | ||||
- Mastodon API: [Scheduled statuses](https://docs.joinmastodon.org/api/rest/scheduled-statuses/) | - Mastodon API: [Scheduled statuses](https://docs.joinmastodon.org/api/rest/scheduled-statuses/) | ||||
@@ -197,3 +197,20 @@ See [Admin-API](Admin-API.md) | |||||
* `remote`: BOOLEAN field, receives notifications from people on remote instances | * `remote`: BOOLEAN field, receives notifications from people on remote instances | ||||
* `local`: BOOLEAN field, receives notifications from people on the local instance | * `local`: BOOLEAN field, receives notifications from people on the local instance | ||||
* Response: JSON. Returns `{"status": "success"}` if the update was successful, otherwise returns `{"error": "error_msg"}` | * Response: JSON. Returns `{"status": "success"}` if the update was successful, otherwise returns `{"error": "error_msg"}` | ||||
## `/api/pleroma/healthcheck` | |||||
### Healthcheck endpoint with additional system data. | |||||
* Method `GET` | |||||
* Authentication: not required | |||||
* Params: none | |||||
* Response: JSON, statuses (200 - healthy, 503 unhealthy). | |||||
* Example response: | |||||
```json | |||||
{ | |||||
"pool_size": 0, # database connection pool | |||||
"active": 0, # active processes | |||||
"idle": 0, # idle processes | |||||
"memory_used": 0.00, # Memory used | |||||
"healthy": true # Instance state | |||||
} | |||||
``` |
@@ -0,0 +1,60 @@ | |||||
defmodule Pleroma.Healthcheck do | |||||
@moduledoc """ | |||||
Module collects metrics about app and assign healthy status. | |||||
""" | |||||
alias Pleroma.Healthcheck | |||||
alias Pleroma.Repo | |||||
defstruct pool_size: 0, | |||||
active: 0, | |||||
idle: 0, | |||||
memory_used: 0, | |||||
healthy: true | |||||
@type t :: %__MODULE__{ | |||||
pool_size: non_neg_integer(), | |||||
active: non_neg_integer(), | |||||
idle: non_neg_integer(), | |||||
memory_used: number(), | |||||
healthy: boolean() | |||||
} | |||||
@spec system_info() :: t() | |||||
def system_info do | |||||
%Healthcheck{ | |||||
memory_used: Float.round(:erlang.memory(:total) / 1024 / 1024, 2) | |||||
} | |||||
|> assign_db_info() | |||||
|> check_health() | |||||
end | |||||
defp assign_db_info(healthcheck) do | |||||
database = Application.get_env(:pleroma, Repo)[:database] | |||||
query = | |||||
"select state, count(pid) from pg_stat_activity where datname = '#{database}' group by state;" | |||||
result = Repo.query!(query) | |||||
pool_size = Application.get_env(:pleroma, Repo)[:pool_size] | |||||
db_info = | |||||
Enum.reduce(result.rows, %{active: 0, idle: 0}, fn [state, cnt], states -> | |||||
if state == "active" do | |||||
Map.put(states, :active, states.active + cnt) | |||||
else | |||||
Map.put(states, :idle, states.idle + cnt) | |||||
end | |||||
end) | |||||
|> Map.put(:pool_size, pool_size) | |||||
Map.merge(healthcheck, db_info) | |||||
end | |||||
@spec check_health(Healthcheck.t()) :: Healthcheck.t() | |||||
def check_health(%{pool_size: pool_size, active: active} = check) | |||||
when active >= pool_size do | |||||
%{check | healthy: false} | |||||
end | |||||
def check_health(check), do: check | |||||
end |
@@ -135,6 +135,7 @@ defmodule Pleroma.Web.Router do | |||||
post("/password_reset", UtilController, :password_reset) | post("/password_reset", UtilController, :password_reset) | ||||
get("/emoji", UtilController, :emoji) | get("/emoji", UtilController, :emoji) | ||||
get("/captcha", UtilController, :captcha) | get("/captcha", UtilController, :captcha) | ||||
get("/healthcheck", UtilController, :healthcheck) | |||||
end | end | ||||
scope "/api/pleroma", Pleroma.Web do | scope "/api/pleroma", Pleroma.Web do | ||||
@@ -363,4 +363,17 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do | |||||
def captcha(conn, _params) do | def captcha(conn, _params) do | ||||
json(conn, Pleroma.Captcha.new()) | json(conn, Pleroma.Captcha.new()) | ||||
end | end | ||||
def healthcheck(conn, _params) do | |||||
info = Pleroma.Healthcheck.system_info() | |||||
conn = | |||||
if info.healthy do | |||||
conn | |||||
else | |||||
Plug.Conn.put_status(conn, :service_unavailable) | |||||
end | |||||
json(conn, info) | |||||
end | |||||
end | end |
@@ -0,0 +1,22 @@ | |||||
defmodule Pleroma.HealthcheckTest do | |||||
use Pleroma.DataCase | |||||
alias Pleroma.Healthcheck | |||||
test "system_info/0" do | |||||
result = Healthcheck.system_info() |> Map.from_struct() | |||||
assert Map.keys(result) == [:active, :healthy, :idle, :memory_used, :pool_size] | |||||
end | |||||
describe "check_health/1" do | |||||
test "pool size equals active connections" do | |||||
result = Healthcheck.check_health(%Healthcheck{pool_size: 10, active: 10}) | |||||
refute result.healthy | |||||
end | |||||
test "chech_health/1" do | |||||
result = Healthcheck.check_health(%Healthcheck{pool_size: 10, active: 9}) | |||||
assert result.healthy | |||||
end | |||||
end | |||||
end |
@@ -245,4 +245,10 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do | |||||
assert html_response(response, 200) =~ "Log in to follow" | assert html_response(response, 200) =~ "Log in to follow" | ||||
end | end | ||||
end | end | ||||
test "GET /api/pleroma/healthcheck", %{conn: conn} do | |||||
conn = get(conn, "/api/pleroma/healthcheck") | |||||
assert conn.status in [200, 503] | |||||
end | |||||
end | end |