Telemetry WIP

This commit is contained in:
Mark Felder 2024-08-18 09:45:26 -04:00
parent 63663ac88b
commit ba3280fc16
41 changed files with 1121 additions and 198 deletions

View file

@ -132,18 +132,20 @@ config :pleroma, Pleroma.Web.Endpoint,
] ]
# Configures Elixir's Logger # Configures Elixir's Logger
config :logger, backends: [:console] config :logger, backends: [:console, RingLogger]
config :logger, :console, config :logger, :console,
level: :debug, level: :debug,
format: "\n$time $metadata[$level] $message\n", format: "\n$time $metadata[$level] $message\n",
metadata: [:actor, :path, :type, :user] metadata: []
config :logger, RingLogger, max_size: 1024
config :logger, :ex_syslogger, config :logger, :ex_syslogger,
level: :debug, level: :debug,
ident: "pleroma", ident: "pleroma",
format: "$metadata[$level] $message", format: "$metadata[$level] $message",
metadata: [:actor, :path, :type, :user] metadata: []
config :mime, :types, %{ config :mime, :types, %{
"application/xml" => ["xml"], "application/xml" => ["xml"],
@ -950,6 +952,8 @@ config :pleroma, Pleroma.Search.QdrantSearch,
vectors: %{size: 384, distance: "Cosine"} vectors: %{size: 384, distance: "Cosine"}
} }
config :pleroma, Pleroma.Telemetry, phoenix_logs: true
# Import environment specific config. This must remain at the bottom # Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above. # of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs" import_config "#{Mix.env()}.exs"

View file

@ -24,6 +24,8 @@ config :logger, Logger.Backends.Console, level: :info
config :logger, :console, level: :info config :logger, :console, level: :info
config :logger, :ex_syslogger, level: :info config :logger, :ex_syslogger, level: :info
config :pleroma, Pleroma.Telemetry, phoenix_logs: false
# ## SSL Support # ## SSL Support
# #
# To get SSL working, you will need to add the `https` key # To get SSL working, you will need to add the `https` key

View file

@ -76,7 +76,7 @@ defmodule Mix.Tasks.Pleroma.Uploads do
|> Task.async_stream( |> Task.async_stream(
fn {upload, root_path} -> fn {upload, root_path} ->
case Upload.store(upload, uploader: uploader, filters: [], size_limit: nil) do case Upload.store(upload, uploader: uploader, filters: [], size_limit: nil) do
{:ok, _} -> {:ok, _, _} ->
if delete?, do: File.rm_rf!(root_path) if delete?, do: File.rm_rf!(root_path)
Logger.debug("uploaded: #{inspect(upload.path)} #{inspect(upload)}") Logger.debug("uploaded: #{inspect(upload.path)} #{inspect(upload)}")
:ok :ok

View file

@ -46,7 +46,7 @@ defmodule Pleroma.Application do
# Disable warnings_as_errors at runtime, it breaks Phoenix live reload # Disable warnings_as_errors at runtime, it breaks Phoenix live reload
# due to protocol consolidation warnings # due to protocol consolidation warnings
Code.compiler_options(warnings_as_errors: false) Code.compiler_options(warnings_as_errors: false)
Pleroma.Telemetry.Logger.attach() Pleroma.Telemetry.Logger.setup()
Config.Holder.save_default() Config.Holder.save_default()
Pleroma.HTML.compile_scrubbers() Pleroma.HTML.compile_scrubbers()
Pleroma.Config.Oban.warn() Pleroma.Config.Oban.warn()
@ -93,6 +93,8 @@ defmodule Pleroma.Application do
# Define workers and child supervisors to be supervised # Define workers and child supervisors to be supervised
children = children =
[ [
Pleroma.Web.Telemetry,
{Pleroma.Web.MetricsStorage, Pleroma.Web.Telemetry.metrics()},
Pleroma.PromEx, Pleroma.PromEx,
Pleroma.LDAP, Pleroma.LDAP,
Pleroma.Repo, Pleroma.Repo,

View file

@ -36,7 +36,7 @@ defmodule Pleroma.Gun.ConnectionPool.Reclaimer do
|> round |> round
|> max(1) |> max(1)
:telemetry.execute([:pleroma, :connection_pool, :reclaim, :start], %{}, %{ :telemetry.execute([:pleroma, :connection_pool, :reclaim, :start], %{count: 1}, %{
max_connections: max_connections, max_connections: max_connections,
reclaim_max: reclaim_max reclaim_max: reclaim_max
}) })
@ -56,7 +56,7 @@ defmodule Pleroma.Gun.ConnectionPool.Reclaimer do
[] -> [] ->
:telemetry.execute( :telemetry.execute(
[:pleroma, :connection_pool, :reclaim, :stop], [:pleroma, :connection_pool, :reclaim, :stop],
%{reclaimed_count: 0}, %{count: 0},
%{ %{
max_connections: max_connections max_connections: max_connections
} }
@ -79,7 +79,7 @@ defmodule Pleroma.Gun.ConnectionPool.Reclaimer do
:telemetry.execute( :telemetry.execute(
[:pleroma, :connection_pool, :reclaim, :stop], [:pleroma, :connection_pool, :reclaim, :stop],
%{reclaimed_count: Enum.count(reclaimed)}, %{count: Enum.count(reclaimed)},
%{max_connections: max_connections} %{max_connections: max_connections}
) )

View file

@ -71,8 +71,8 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do
:telemetry.execute( :telemetry.execute(
[:pleroma, :connection_pool, :client, :add], [:pleroma, :connection_pool, :client, :add],
%{client_pid: client_pid, clients: used_by}, %{count: 1, start_time: time},
%{key: state.key, protocol: protocol} %{client_pid: client_pid, clients: used_by, key: state.key, protocol: protocol}
) )
state = state =
@ -115,23 +115,27 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do
def handle_info(:idle_close, state) do def handle_info(:idle_close, state) do
# Gun monitors the owner process, and will close the connection automatically # Gun monitors the owner process, and will close the connection automatically
# when it's terminated # when it's terminated
update_telemetry_worker_count()
{:stop, :normal, state} {:stop, :normal, state}
end end
@impl true @impl true
def handle_info({:gun_up, _pid, _protocol}, state) do def handle_info({:gun_up, _pid, _protocol}, state) do
update_telemetry_worker_count()
{:noreply, state, :hibernate} {:noreply, state, :hibernate}
end end
# Gracefully shutdown if the connection got closed without any streams left # Gracefully shutdown if the connection got closed without any streams left
@impl true @impl true
def handle_info({:gun_down, _pid, _protocol, _reason, []}, state) do def handle_info({:gun_down, _pid, _protocol, _reason, []}, state) do
update_telemetry_worker_count()
{:stop, :normal, state} {:stop, :normal, state}
end end
# Otherwise, wait for retry # Otherwise, wait for retry
@impl true @impl true
def handle_info({:gun_down, _pid, _protocol, _reason, _killed_streams}, state) do def handle_info({:gun_down, _pid, _protocol, _reason, _killed_streams}, state) do
update_telemetry_worker_count()
{:noreply, state, :hibernate} {:noreply, state, :hibernate}
end end
@ -139,10 +143,12 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do
def handle_info({:DOWN, _ref, :process, pid, reason}, state) do def handle_info({:DOWN, _ref, :process, pid, reason}, state) do
:telemetry.execute( :telemetry.execute(
[:pleroma, :connection_pool, :client, :dead], [:pleroma, :connection_pool, :client, :dead],
%{client_pid: pid, reason: reason}, %{count: 1},
%{key: state.key} %{client_pid: pid, reason: reason, key: state.key}
) )
update_telemetry_worker_count()
handle_cast({:remove_client, pid}, state) handle_cast({:remove_client, pid}, state)
end end
@ -150,4 +156,12 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do
defp crf(time_delta, prev_crf) do defp crf(time_delta, prev_crf) do
1 + :math.pow(0.5, 0.0001 * time_delta) * prev_crf 1 + :math.pow(0.5, 0.0001 * time_delta) * prev_crf
end end
def update_telemetry_worker_count do
:telemetry.execute(
[:pleroma, :connection_pool, :worker],
%{count: Registry.count(Pleroma.Gun.ConnectionPool)},
%{}
)
end
end end

View file

@ -26,7 +26,10 @@ defmodule Pleroma.Gun.ConnectionPool.WorkerSupervisor do
def start_worker(opts, true) do def start_worker(opts, true) do
case DynamicSupervisor.start_child(__MODULE__, {Worker, opts}) do case DynamicSupervisor.start_child(__MODULE__, {Worker, opts}) do
{:error, :max_children} -> {:error, :max_children} ->
:telemetry.execute([:pleroma, :connection_pool, :provision_failure], %{opts: opts}) :telemetry.execute([:pleroma, :connection_pool, :provision_failure], %{count: 1}, %{
opts: opts
})
{:error, :pool_full} {:error, :pool_full}
res -> res ->

View file

@ -70,7 +70,9 @@ defmodule Pleroma.HTTP do
extra_middleware = options[:tesla_middleware] || [] extra_middleware = options[:tesla_middleware] || []
client = Tesla.client(adapter_middlewares(adapter, extra_middleware), adapter) middlewares = [Tesla.Middleware.Telemetry] ++ extra_middleware ++ adapter_middlewares(adapter)
client = Tesla.client(middlewares, adapter)
maybe_limit( maybe_limit(
fn -> fn ->
@ -104,21 +106,20 @@ defmodule Pleroma.HTTP do
fun.() fun.()
end end
defp adapter_middlewares(Tesla.Adapter.Gun, extra_middleware) do defp adapter_middlewares(Tesla.Adapter.Gun) do
[Tesla.Middleware.FollowRedirects, Pleroma.Tesla.Middleware.ConnectionPool] ++ [Tesla.Middleware.FollowRedirects, Pleroma.Tesla.Middleware.ConnectionPool]
extra_middleware
end end
defp adapter_middlewares({Tesla.Adapter.Finch, _}, extra_middleware) do defp adapter_middlewares({Tesla.Adapter.Finch, _}) do
[Tesla.Middleware.FollowRedirects] ++ extra_middleware [Tesla.Middleware.FollowRedirects]
end end
defp adapter_middlewares(_, extra_middleware) do defp adapter_middlewares(_) do
if Pleroma.Config.get(:env) == :test do if Pleroma.Config.get(:env) == :test do
# Emulate redirects in test env, which are handled by adapters in other environments # Emulate redirects in test env, which are handled by adapters in other environments
[Tesla.Middleware.FollowRedirects] [Tesla.Middleware.FollowRedirects]
else else
extra_middleware []
end end
end end
end end

View file

@ -97,9 +97,33 @@ defmodule Pleroma.Instances.Instance do
def reachable?(url_or_host) when is_binary(url_or_host), do: true def reachable?(url_or_host) when is_binary(url_or_host), do: true
def set_reachable(url_or_host) when is_binary(url_or_host) do def set_reachable(url_or_host) when is_binary(url_or_host) do
%Instance{host: host(url_or_host)} host = host(url_or_host)
|> changeset(%{unreachable_since: nil})
|> Repo.insert(on_conflict: {:replace, [:unreachable_since]}, conflict_target: :host) existing_record = Repo.get_by(Instance, %{host: host})
cond do
is_nil(existing_record) ->
%Instance{}
|> changeset(%{host: host})
|> Repo.insert()
is_nil(existing_record.unreachable_since) ->
{:ok, existing_record}
true ->
result =
%Instance{host: host}
|> changeset(%{unreachable_since: nil})
|> Repo.insert(on_conflict: {:replace, [:unreachable_since]}, conflict_target: :host)
:telemetry.execute(
[:pleroma, :instance, :reachable],
%{count: 1},
%{host: host, instance: existing_record}
)
result
end
end end
def set_reachable(_), do: {:error, nil} def set_reachable(_), do: {:error, nil}
@ -113,21 +137,30 @@ defmodule Pleroma.Instances.Instance do
changes = %{unreachable_since: unreachable_since} changes = %{unreachable_since: unreachable_since}
cond do result =
is_nil(existing_record) -> cond do
%Instance{} is_nil(existing_record) ->
|> changeset(Map.put(changes, :host, host)) %Instance{}
|> Repo.insert() |> changeset(Map.put(changes, :host, host))
|> Repo.insert()
existing_record.unreachable_since && existing_record.unreachable_since &&
NaiveDateTime.compare(existing_record.unreachable_since, unreachable_since) != :gt -> NaiveDateTime.compare(existing_record.unreachable_since, unreachable_since) != :gt ->
{:ok, existing_record} {:ok, existing_record}
true -> true ->
existing_record existing_record
|> changeset(changes) |> changeset(changes)
|> Repo.update() |> Repo.update()
end end
:telemetry.execute(
[:pleroma, :instance, :unreachable],
%{count: 1},
%{host: host, instance: existing_record}
)
result
end end
def set_unreachable(_, _), do: {:error, nil} def set_unreachable(_, _), do: {:error, nil}

View file

@ -11,6 +11,8 @@ defmodule Pleroma.Stats do
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
require Logger
@interval :timer.seconds(60) @interval :timer.seconds(60)
def start_link(_) do def start_link(_) do
@ -55,6 +57,14 @@ defmodule Pleroma.Stats do
peers peers
end end
@doc "Returns Oban queue counts for available, executing, and retryable states"
@spec get_oban() :: map()
def get_oban do
%{oban: oban_counts} = GenServer.call(__MODULE__, :get_state)
oban_counts
end
@spec calculate_stat_data() :: %{ @spec calculate_stat_data() :: %{
peers: list(), peers: list(),
stats: %{ stats: %{
@ -63,6 +73,11 @@ defmodule Pleroma.Stats do
user_count: non_neg_integer() user_count: non_neg_integer()
} }
} }
@doc """
Calculates stat data and exports some metrics through telemetry.
Executes automatically on a 60 second interval by default.
"""
def calculate_stat_data do def calculate_stat_data do
peers = peers =
from( from(
@ -87,6 +102,21 @@ defmodule Pleroma.Stats do
user_count = Repo.aggregate(users_query, :count, :id) user_count = Repo.aggregate(users_query, :count, :id)
# Logs Oban counts
get_oban_counts()
# Logs / Telemetry for local visibility counts
get_status_visibility_count()
# Telemetry for local stats
%{domains: domain_count, notes: status_count, users: user_count}
|> Enum.each(fn {k, v} ->
:telemetry.execute(
[:pleroma, :stats, :local, k],
%{count: v}
)
end)
%{ %{
peers: peers, peers: peers,
stats: %{ stats: %{
@ -97,13 +127,19 @@ defmodule Pleroma.Stats do
} }
end end
@spec get_status_visibility_count(String.t() | nil) :: map() @spec get_status_visibility_count :: map()
def get_status_visibility_count(instance \\ nil) do def get_status_visibility_count do
if is_nil(instance) do result = CounterCache.get_sum()
CounterCache.get_sum()
else Enum.each(result, fn {k, v} ->
CounterCache.get_by_instance(instance) :telemetry.execute(
end [:pleroma, :stats, :global, :activities],
%{count: v},
%{visibility: k}
)
end)
result
end end
@impl true @impl true
@ -134,4 +170,32 @@ defmodule Pleroma.Stats do
Process.send_after(self(), :run_update, @interval) Process.send_after(self(), :run_update, @interval)
{:noreply, new_stats} {:noreply, new_stats}
end end
def get_oban_counts do
result =
from(j in Oban.Job,
where: j.state in ^["executing", "available", "retryable"],
group_by: [j.queue, j.state],
select: {j.queue, j.state, count(j.id)}
)
|> Pleroma.Repo.all()
|> Enum.group_by(fn {key, _, _} -> key end)
|> Enum.reduce(%{}, fn {k, v}, acc ->
queues = Map.new(v, fn {_, state, count} -> {state, count} end)
Map.put(acc, k, queues)
end)
for {queue, states} <- result do
queue_text =
for {state, count} <- states do
"#{state}: #{count}"
end
|> Enum.join(" || ")
Logger.info("Oban Queue Stats for :#{queue} || #{queue_text}")
end
result
end
end end

55
lib/pleroma/telemetry.ex Normal file
View file

@ -0,0 +1,55 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Telemetry do
@phoenix_handlers %{
[:phoenix, :endpoint, :start] => &Phoenix.Logger.phoenix_endpoint_start/4,
[:phoenix, :endpoint, :stop] => &Phoenix.Logger.phoenix_endpoint_stop/4,
[:phoenix, :router_dispatch, :start] => &Phoenix.Logger.phoenix_router_dispatch_start/4,
[:phoenix, :error_rendered] => &Phoenix.Logger.phoenix_error_rendered/4,
[:phoenix, :socket_connected] => &Phoenix.Logger.phoenix_socket_connected/4,
[:phoenix, :channel_joined] => &Phoenix.Logger.phoenix_channel_joined/4,
[:phoenix, :channel_handled_in] => &Phoenix.Logger.phoenix_channel_handled_in/4
}
@live_view_handlers %{
[:phoenix, :live_view, :mount, :start] => &Phoenix.LiveView.Logger.lv_mount_start/4,
[:phoenix, :live_view, :mount, :stop] => &Phoenix.LiveView.Logger.lv_mount_stop/4,
[:phoenix, :live_view, :handle_params, :start] =>
&Phoenix.LiveView.Logger.lv_handle_params_start/4,
[:phoenix, :live_view, :handle_params, :stop] =>
&Phoenix.LiveView.Logger.lv_handle_params_stop/4,
[:phoenix, :live_view, :handle_event, :start] =>
&Phoenix.LiveView.Logger.lv_handle_event_start/4,
[:phoenix, :live_view, :handle_event, :stop] =>
&Phoenix.LiveView.Logger.lv_handle_event_stop/4,
[:phoenix, :live_component, :handle_event, :start] =>
&Phoenix.LiveView.Logger.lc_handle_event_start/4,
[:phoenix, :live_component, :handle_event, :stop] =>
&Phoenix.LiveView.Logger.lc_handle_event_stop/4
}
def disable_phoenix_logs() do
:telemetry.list_handlers([])
|> Enum.filter(fn x ->
match?(%{id: {Phoenix.Logger, _}}, x) or match?(%{id: {Phoenix.LiveView.Logger, _}}, x)
end)
|> Enum.map(& &1.id)
|> Enum.each(&:telemetry.detach(&1))
:ok
end
def enable_phoenix_logs() do
for {key, fun} <- @phoenix_handlers do
:telemetry.attach({Phoenix.Logger, key}, key, fun, :ok)
end
for {key, fun} <- @live_view_handlers do
:telemetry.attach({Phoenix.LiveView.Logger, key}, key, fun, :ok)
end
:ok
end
end

View file

@ -8,23 +8,55 @@ defmodule Pleroma.Telemetry.Logger do
require Logger require Logger
@events [ @events [
[:oban, :job, :exception],
[:pleroma, :activity, :mrf, :filter],
[:pleroma, :activity, :mrf, :pass],
[:pleroma, :activity, :mrf, :reject],
[:pleroma, :activitypub, :inbox],
[:pleroma, :activitypub, :publisher],
[:pleroma, :instance, :reachable],
[:pleroma, :instance, :unreachable],
[:pleroma, :connection_pool, :client, :add],
[:pleroma, :connection_pool, :client, :dead],
[:pleroma, :connection_pool, :provision_failure],
[:pleroma, :connection_pool, :reclaim, :start], [:pleroma, :connection_pool, :reclaim, :start],
[:pleroma, :connection_pool, :reclaim, :stop], [:pleroma, :connection_pool, :reclaim, :stop],
[:pleroma, :connection_pool, :provision_failure], [:pleroma, :upload, :success],
[:pleroma, :connection_pool, :client, :dead], [:pleroma, :user, :account, :confirm],
[:pleroma, :connection_pool, :client, :add] [:pleroma, :user, :account, :password_reset],
[:pleroma, :user, :account, :register],
[:pleroma, :user, :o_auth, :failure],
[:pleroma, :user, :o_auth, :revoke],
[:pleroma, :user, :o_auth, :success],
[:pleroma, :user, :create],
[:pleroma, :user, :delete],
[:pleroma, :user, :update],
[:tesla, :request, :exception],
[:tesla, :request, :stop]
] ]
def attach do def setup do
:telemetry.attach_many("pleroma-logger", @events, &handle_event/4, []) if not Pleroma.Config.get([Pleroma.Telemetry, :phoenix_logs]) do
Pleroma.Telemetry.disable_phoenix_logs()
end
Enum.each(@events, fn event ->
:telemetry.attach({__MODULE__, event}, event, &__MODULE__.handle_event/4, [])
end)
end end
# Passing anonymous functions instead of strings to logger is intentional, # Passing anonymous functions instead of strings to logger is intentional,
# that way strings won't be concatenated if the message is going to be thrown # that way strings won't be concatenated if the message is going to be thrown
# out anyway due to higher log level configured # out anyway due to higher log level configured
def handle_event([:oban, :job, :exception], _measure, meta, _) do
Logger.error(fn ->
"[Oban] #{meta.worker} error: #{inspect(meta.error)} args: #{inspect(meta.args)}"
end)
end
def handle_event( def handle_event(
[:pleroma, :connection_pool, :reclaim, :start], [:pleroma, :connection_pool, :reclaim, :start],
_, _measurements,
%{max_connections: max_connections, reclaim_max: reclaim_max}, %{max_connections: max_connections, reclaim_max: reclaim_max},
_ _
) do ) do
@ -35,8 +67,8 @@ defmodule Pleroma.Telemetry.Logger do
def handle_event( def handle_event(
[:pleroma, :connection_pool, :reclaim, :stop], [:pleroma, :connection_pool, :reclaim, :stop],
%{reclaimed_count: 0}, %{count: 0},
_, _measurements,
_ _
) do ) do
Logger.debug(fn -> Logger.debug(fn ->
@ -46,8 +78,8 @@ defmodule Pleroma.Telemetry.Logger do
def handle_event( def handle_event(
[:pleroma, :connection_pool, :reclaim, :stop], [:pleroma, :connection_pool, :reclaim, :stop],
%{reclaimed_count: reclaimed_count}, %{count: reclaimed_count},
_, _measurements,
_ _
) do ) do
Logger.debug(fn -> "Connection pool cleaned up #{reclaimed_count} idle connections" end) Logger.debug(fn -> "Connection pool cleaned up #{reclaimed_count} idle connections" end)
@ -55,8 +87,8 @@ defmodule Pleroma.Telemetry.Logger do
def handle_event( def handle_event(
[:pleroma, :connection_pool, :provision_failure], [:pleroma, :connection_pool, :provision_failure],
_measurements,
%{opts: [key | _]}, %{opts: [key | _]},
_,
_ _
) do ) do
Logger.debug(fn -> Logger.debug(fn ->
@ -66,8 +98,8 @@ defmodule Pleroma.Telemetry.Logger do
def handle_event( def handle_event(
[:pleroma, :connection_pool, :client, :dead], [:pleroma, :connection_pool, :client, :dead],
%{client_pid: client_pid, reason: reason}, _measurements,
%{key: key}, %{client_pid: client_pid, key: key, reason: reason},
_ _
) do ) do
Logger.debug(fn -> Logger.debug(fn ->
@ -77,8 +109,8 @@ defmodule Pleroma.Telemetry.Logger do
def handle_event( def handle_event(
[:pleroma, :connection_pool, :client, :add], [:pleroma, :connection_pool, :client, :add],
%{clients: [_, _ | _] = clients}, _measurements,
%{key: key, protocol: :http}, %{clients: [_, _ | _] = clients, key: key, protocol: :http},
_ _
) do ) do
Logger.debug(fn -> Logger.debug(fn ->
@ -87,4 +119,254 @@ defmodule Pleroma.Telemetry.Logger do
end end
def handle_event([:pleroma, :connection_pool, :client, :add], _, _, _), do: :ok def handle_event([:pleroma, :connection_pool, :client, :add], _, _, _), do: :ok
def handle_event(
[:pleroma, :activitypub, :inbox],
_measurements,
%{ap_id: ap_id, type: type},
_
) do
Logger.info(fn ->
"Inbox: received #{type} of #{ap_id}"
end)
end
def handle_event(
[:pleroma, :activity, :mrf, :pass],
_measurements,
%{activity: activity, mrf: mrf},
_
) do
type = activity["type"]
ap_id = activity["id"]
Logger.debug(fn ->
"#{mrf}: passed #{inspect(type)} of #{inspect(ap_id)}"
end)
end
def handle_event(
[:pleroma, :activity, :mrf, :filter],
_measurements,
%{activity: activity, mrf: mrf},
_
) do
type = activity["type"]
ap_id = activity["id"]
Logger.debug(fn ->
"#{mrf}: filtered #{inspect(type)} of #{inspect(ap_id)}"
end)
end
def handle_event(
[:pleroma, :activity, :mrf, :reject],
_measurements,
%{activity: activity, mrf: mrf, reason: reason},
_
) do
type = activity["type"]
ap_id = activity["id"]
Logger.info(fn ->
"#{mrf}: rejected #{inspect(type)} of #{inspect(ap_id)}: #{inspect(reason)}"
end)
end
def handle_event(
[:pleroma, :upload, :success],
%{size: size, duration: duration} = _measurements,
%{nickname: nickname, filename: filename},
_
) do
Logger.info(fn ->
upload_size = (size / 1_024_000) |> :erlang.float_to_binary(decimals: 2)
seconds = duration / 1000
"Upload: #{nickname} uploaded #{filename} [#{upload_size}MB in #{seconds}s]"
end)
end
def handle_event(
[:pleroma, :user, :create],
_measurements,
%{nickname: nickname},
_
) do
Logger.info(fn ->
"User: created #{nickname}"
end)
end
def handle_event(
[:pleroma, :user, :delete],
_measurements,
%{nickname: nickname},
_
) do
Logger.info(fn ->
"User: deleted #{nickname}"
end)
end
def handle_event(
[:pleroma, :user, :disable],
_measurements,
%{nickname: nickname},
_
) do
Logger.info(fn ->
"User: disabled #{nickname}"
end)
end
def handle_event(
[:pleroma, :user, :update],
_measurements,
%{nickname: nickname},
_
) do
Logger.debug(fn ->
"User: updated #{nickname}"
end)
end
def handle_event(
[:pleroma, :instance, :reachable],
_measurements,
%{host: host, instance: instance},
_
) do
unreachable_since = get_in(instance, [Access.key(:unreachable_since)]) || "UNDEFINED"
Logger.info(fn ->
"Instance: #{host} is set reachable (was unreachable since: #{unreachable_since})"
end)
end
def handle_event(
[:pleroma, :instance, :unreachable],
_measurements,
%{host: host, instance: _instance},
_
) do
Logger.info(fn ->
"Instance: #{host} is set unreachable"
end)
end
def handle_event(
[:pleroma, :user, :o_auth, :failure],
_measurements,
%{ip: ip, nickname: name},
_
) do
Logger.error(fn ->
"OAuth: authentication failure for #{name} from #{ip}"
end)
end
def handle_event(
[:pleroma, :user, :o_auth, :revoke],
_measurements,
%{ip: ip, nickname: name},
_
) do
Logger.info(fn ->
"OAuth: token revoked for #{name} from #{ip}"
end)
end
def handle_event(
[:pleroma, :user, :o_auth, :success],
_measurements,
%{ip: ip, nickname: name},
_
) do
Logger.info(fn ->
"OAuth: authentication success for #{name} from #{ip}"
end)
end
def handle_event(
[:pleroma, :user, :account, :confirm],
_measurements,
%{user: user},
_
) do
Logger.info(fn ->
"Account: #{user.nickname} account confirmed"
end)
end
def handle_event(
[:pleroma, :user, :account, :password_reset],
_measurements,
%{for: for, ip: ip},
_
) do
Logger.info(fn ->
"Account: password reset requested for #{for} from #{ip}"
end)
end
def handle_event(
[:pleroma, :user, :account, :register],
_measurements,
%{ip: ip, user: user},
_
) do
Logger.info(fn ->
"Account: #{user.nickname} registered from #{ip}"
end)
end
def handle_event(
[:pleroma, :activitypub, :publisher],
%{sum: sum},
%{nickname: nickname, type: type},
_
) do
Logger.info(fn ->
"Publisher: delivering #{type} for #{nickname} to #{sum} inboxes"
end)
end
def handle_event([:tesla, :request, :exception], _measure, meta, _) do
Logger.error(fn -> "Pleroma.HTTP exception #{inspect(meta.stacktrace)}" end)
end
def handle_event(
[:tesla, :request, :stop],
_measurements,
%{env: %Tesla.Env{method: method, url: url}, error: error} = _meta,
_
) do
Logger.warning(fn ->
"Pleroma.HTTP :#{method} failed for #{url} with error #{inspect(error)}"
end)
end
def handle_event(
[:tesla, :request, :stop],
_measurements,
%{env: %Tesla.Env{method: method, status: status, url: url}} = _meta,
_
) do
cond do
status in 200..299 ->
:ok
true ->
Logger.warning(fn -> "Pleroma.HTTP :#{method} status #{status} for #{url}" end)
end
end
# Catchall
def handle_event(event, measurements, metadata, _) do
Logger.debug(fn ->
"Unhandled telemetry event #{inspect(event)} with measurements #{inspect(measurements)} and metadata #{inspect(metadata)}"
end)
:ok
end
end end

View file

@ -87,7 +87,7 @@ defmodule Pleroma.Upload do
end end
end end
@spec store(source, options :: [option()]) :: {:ok, map()} | {:error, any()} @spec store(source, options :: [option()]) :: {:ok, Upload.t(), map()} | {:error, any()}
@doc "Store a file. If using a `Plug.Upload{}` as the source, be sure to use `Majic.Plug` to ensure its content_type and filename is correct." @doc "Store a file. If using a `Plug.Upload{}` as the source, be sure to use `Majic.Plug` to ensure its content_type and filename is correct."
def store(upload, opts \\ []) do def store(upload, opts \\ []) do
opts = get_opts(opts) opts = get_opts(opts)
@ -100,7 +100,7 @@ defmodule Pleroma.Upload do
{:description_limit, {:description_limit,
String.length(description) <= Pleroma.Config.get([:instance, :description_limit])}, String.length(description) <= Pleroma.Config.get([:instance, :description_limit])},
{:ok, url_spec} <- Pleroma.Uploaders.Uploader.put_file(opts.uploader, upload) do {:ok, url_spec} <- Pleroma.Uploaders.Uploader.put_file(opts.uploader, upload) do
{:ok, {:ok, upload,
%{ %{
"id" => Utils.generate_object_id(), "id" => Utils.generate_object_id(),
"type" => opts.activity_type, "type" => opts.activity_type,

View file

@ -986,7 +986,7 @@ defmodule Pleroma.User do
@doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)" @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
def register(%Ecto.Changeset{} = changeset) do def register(%Ecto.Changeset{} = changeset) do
with {:ok, user} <- Repo.insert(changeset) do with {:ok, user} <- create(changeset) do
post_register_action(user) post_register_action(user)
end end
end end
@ -1276,6 +1276,12 @@ defmodule Pleroma.User do
|> Oban.insert() |> Oban.insert()
end end
:telemetry.execute(
[:pleroma, :user, :update],
%{count: 1},
%{nickname: user.nickname, user: user}
)
set_cache(user) set_cache(user)
end end
end end
@ -1952,6 +1958,12 @@ defmodule Pleroma.User do
def confirm(%User{is_confirmed: false} = user) do def confirm(%User{is_confirmed: false} = user) do
with chg <- confirmation_changeset(user, set_confirmation: true), with chg <- confirmation_changeset(user, set_confirmation: true),
{:ok, user} <- update_and_set_cache(chg) do {:ok, user} <- update_and_set_cache(chg) do
:telemetry.execute(
[:pleroma, :user, :account, :confirm],
%{count: 1},
%{user: user}
)
post_register_action(user) post_register_action(user)
{:ok, user} {:ok, user}
end end
@ -1983,6 +1995,20 @@ defmodule Pleroma.User do
|> update_and_set_cache() |> update_and_set_cache()
end end
@spec create(Ecto.Changeset.t()) :: {:ok, User.t()} | {:error, any()}
def create(%Ecto.Changeset{} = changeset) do
with {:ok, %User{} = user} <- Repo.insert(changeset),
{:ok, user} <- set_cache(user) do
:telemetry.execute(
[:pleroma, :user, :create],
%{count: 1},
%{nickname: user.nickname, user: user}
)
{:ok, user}
end
end
@spec purge_user_changeset(User.t()) :: Ecto.Changeset.t() @spec purge_user_changeset(User.t()) :: Ecto.Changeset.t()
def purge_user_changeset(user) do def purge_user_changeset(user) do
# "Right to be forgotten" # "Right to be forgotten"
@ -2040,6 +2066,12 @@ defmodule Pleroma.User do
# Purge the user immediately # Purge the user immediately
purge(user) purge(user)
:telemetry.execute(
[:pleroma, :user, :delete],
%{count: 1},
%{nickname: user.nickname, user: user}
)
DeleteWorker.new(%{"op" => "delete_user", "user_id" => user.id}) DeleteWorker.new(%{"op" => "delete_user", "user_id" => user.id})
|> Oban.insert() |> Oban.insert()
end end
@ -2275,8 +2307,7 @@ defmodule Pleroma.User do
|> change |> change
|> put_private_key() |> put_private_key()
|> unique_constraint(:nickname) |> unique_constraint(:nickname)
|> Repo.insert() |> create()
|> set_cache()
end end
def public_key(%{public_key: public_key_pem}) when is_binary(public_key_pem) do def public_key(%{public_key: public_key_pem}) when is_binary(public_key_pem) do

View file

@ -12,7 +12,7 @@ defmodule Pleroma.User.Import do
require Logger require Logger
@spec perform(atom(), User.t(), String.t()) :: :ok | {:error, any()} @spec perform(atom(), User.t(), String.t()) :: {:ok, User.t()} | {:error, any()}
def perform(:mute_import, %User{} = user, actor) do def perform(:mute_import, %User{} = user, actor) do
with {:ok, %User{} = muted_user} <- User.get_or_fetch(actor), with {:ok, %User{} = muted_user} <- User.get_or_fetch(actor),
{_, false} <- {:existing_mute, User.mutes_user?(user, muted_user)}, {_, false} <- {:existing_mute, User.mutes_user?(user, muted_user)},

View file

@ -1539,8 +1539,27 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
@spec upload(Upload.source(), keyword()) :: {:ok, Object.t()} | {:error, any()} @spec upload(Upload.source(), keyword()) :: {:ok, Object.t()} | {:error, any()}
def upload(file, opts \\ []) do def upload(file, opts \\ []) do
with {:ok, data} <- Upload.store(sanitize_upload_file(file), opts) do start_time = :erlang.monotonic_time(:millisecond)
with {:ok, upload, data} <- Upload.store(sanitize_upload_file(file), opts) do
obj_data = Maps.put_if_present(data, "actor", opts[:actor]) obj_data = Maps.put_if_present(data, "actor", opts[:actor])
stop_time = :erlang.monotonic_time(:millisecond)
nickname =
with true <- is_binary(opts[:actor]),
%User{nickname: nickname} <- User.get_cached_by_ap_id(opts[:actor]) do
nickname
else
_ -> "UNDEFINED"
end
{:ok, %{size: upload_size}} = File.stat(upload.tempfile)
:telemetry.execute(
[:pleroma, :upload, :success],
%{count: 1, duration: stop_time - start_time, size: upload_size},
%{nickname: nickname, filename: upload.path}
)
Repo.insert(%Object{data: obj_data}) Repo.insert(%Object{data: obj_data})
end end
@ -1860,8 +1879,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
data data
|> User.remote_user_changeset() |> User.remote_user_changeset()
|> Repo.insert() |> User.create()
|> User.set_cache()
end end
end end
end end

View file

@ -52,7 +52,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
when action in [:activity, :object] when action in [:activity, :object]
) )
plug(:log_inbox_metadata when action in [:inbox]) plug(:inbox_telemetry when action in [:inbox])
plug(:set_requester_reachable when action in [:inbox]) plug(:set_requester_reachable when action in [:inbox])
plug(:relay_active? when action in [:relay]) plug(:relay_active? when action in [:relay])
@ -529,12 +529,20 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
conn conn
end end
defp log_inbox_metadata(%{params: %{"actor" => actor, "type" => type}} = conn, _) do defp inbox_telemetry(%{params: %{"actor" => actor, "id" => ap_id, "type" => type}} = conn, _) do
Logger.metadata(actor: actor, type: type) actor_host = URI.parse(actor).host
Logger.metadata(actor: actor, ap_id: ap_id, type: type, actor_host: actor_host)
:telemetry.execute(
[:pleroma, :activitypub, :inbox],
%{count: 1},
%{host: actor_host, ap_id: ap_id, type: type}
)
conn conn
end end
defp log_inbox_metadata(conn, _), do: conn defp inbox_telemetry(conn, _), do: conn
def upload_media(%{assigns: %{user: %User{} = user}} = conn, %{"file" => file} = data) do def upload_media(%{assigns: %{user: %User{} = user}} = conn, %{"file" => file} = data) do
with {:ok, object} <- with {:ok, object} <-

View file

@ -306,6 +306,18 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|> Instances.filter_reachable() |> Instances.filter_reachable()
end) end)
:telemetry.execute(
[:pleroma, :activitypub, :publisher],
%{count: 1, sum: Enum.count(inboxes)},
%{
activity: activity,
actor: actor,
nickname: Map.get(actor, :nickname),
inboxes: inboxes,
type: Map.get(activity, :data)["type"]
}
)
Repo.checkout(fn -> Repo.checkout(fn ->
Enum.each(inboxes, fn inboxes -> Enum.each(inboxes, fn inboxes ->
Enum.each(inboxes, fn {inbox, unreachable_since} -> Enum.each(inboxes, fn {inbox, unreachable_since} ->
@ -348,6 +360,18 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
inboxes = inboxes -- priority_inboxes inboxes = inboxes -- priority_inboxes
:telemetry.execute(
[:pleroma, :activitypub, :publisher],
%{count: 1, sum: Enum.count(inboxes ++ priority_inboxes)},
%{
activity: activity,
actor: actor,
nickname: Map.get(actor, :nickname),
inboxes: inboxes ++ priority_inboxes,
type: Map.get(activity, :data)["type"]
}
)
[{priority_inboxes, 0}, {inboxes, 1}] [{priority_inboxes, 0}, {inboxes, 1}]
|> Enum.each(fn {inboxes, priority} -> |> Enum.each(fn {inboxes, priority} ->
inboxes inboxes

View file

@ -11,7 +11,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
alias Pleroma.Config alias Pleroma.Config
alias Pleroma.MFA alias Pleroma.MFA
alias Pleroma.ModerationLog alias Pleroma.ModerationLog
alias Pleroma.Stats alias Pleroma.CounterCache
alias Pleroma.User alias Pleroma.User
alias Pleroma.User.Backup alias Pleroma.User.Backup
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
@ -423,7 +423,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
end end
def stats(conn, params) do def stats(conn, params) do
counters = Stats.get_status_visibility_count(params["instance"]) counters = CounterCache.get_by_instance(params["instance"])
json(conn, %{"status_visibility" => counters}) json(conn, %{"status_visibility" => counters})
end end

View file

@ -20,9 +20,26 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do
def get_user(%Plug.Conn{} = conn) do def get_user(%Plug.Conn{} = conn) do
with {:ldap, true} <- {:ldap, Pleroma.Config.get([:ldap, :enabled])}, with {:ldap, true} <- {:ldap, Pleroma.Config.get([:ldap, :enabled])},
{:ok, {name, password}} <- fetch_credentials(conn), {:ok, {name, password}} <- fetch_credentials(conn),
%User{} = user <- LDAP.bind_user(name, password) do {:checkpw, %User{} = user} <- {:checkpw, LDAP.bind_user(name, password)} do
:telemetry.execute(
[:pleroma, :user, :o_auth, :success],
%{count: 1},
%{ip: :inet.ntoa(conn.remote_ip), nickname: name}
)
{:ok, user} {:ok, user}
else else
{:checkpw, _} = e ->
{:ok, {name, _password}} = fetch_credentials(conn)
:telemetry.execute(
[:pleroma, :user, :o_auth, :failure],
%{count: 1},
%{ip: :inet.ntoa(conn.remote_ip), nickname: name}
)
{:error, e}
{:ldap, _} -> {:ldap, _} ->
@base.get_user(conn) @base.get_user(conn)

View file

@ -18,10 +18,27 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do
{_, %User{} = user} <- {:user, fetch_user(name)}, {_, %User{} = user} <- {:user, fetch_user(name)},
{_, true} <- {:checkpw, AuthenticationPlug.checkpw(password, user.password_hash)}, {_, true} <- {:checkpw, AuthenticationPlug.checkpw(password, user.password_hash)},
{:ok, user} <- AuthenticationPlug.maybe_update_password(user, password) do {:ok, user} <- AuthenticationPlug.maybe_update_password(user, password) do
:telemetry.execute(
[:pleroma, :user, :o_auth, :success],
%{count: 1},
%{ip: :inet.ntoa(conn.remote_ip), nickname: name}
)
{:ok, user} {:ok, user}
else else
{:error, _reason} = error -> error {:checkpw, false} ->
error -> {:error, error} oauth_telemetry(conn)
{:error, :invalid_password}
{:user, nil} ->
oauth_telemetry(conn)
{:error, :invalid_user}
{:error, _reason} = error ->
error
error ->
{:error, error}
end end
end end
@ -121,4 +138,14 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do
end end
def change_password(_, _, _, _), do: {:error, :password_confirmation} def change_password(_, _, _, _), do: {:error, :password_confirmation}
defp oauth_telemetry(conn) do
{:ok, {name, _password}} = fetch_credentials(conn)
:telemetry.execute(
[:pleroma, :user, :o_auth, :failure],
%{count: 1},
%{ip: :inet.ntoa(conn.remote_ip), nickname: name}
)
end
end end

View file

@ -136,7 +136,6 @@ defmodule Pleroma.Web.Endpoint do
plug(Pleroma.Web.Plugs.TrailingFormatPlug) plug(Pleroma.Web.Plugs.TrailingFormatPlug)
plug(Plug.RequestId) plug(Plug.RequestId)
plug(Plug.Logger, log: :debug)
plug(Plug.Parsers, plug(Plug.Parsers,
parsers: [:urlencoded, Pleroma.Web.Multipart, :json], parsers: [:urlencoded, Pleroma.Web.Multipart, :json],

View file

@ -118,9 +118,9 @@ defmodule Pleroma.Web.Federator do
Logger.debug("Already had #{params["id"]}") Logger.debug("Already had #{params["id"]}")
{:error, :already_present} {:error, :already_present}
{:actor, e} -> {:actor, {:error, _} = e} ->
Logger.debug("Unhandled actor #{actor}, #{inspect(e)}") Logger.debug("Unhandled actor #{actor}, #{inspect(e)}")
{:error, e} e
e -> e ->
# Just drop those for now # Just drop those for now

View file

@ -113,6 +113,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
with :ok <- validate_email_param(params), with :ok <- validate_email_param(params),
:ok <- TwitterAPI.validate_captcha(app, params), :ok <- TwitterAPI.validate_captcha(app, params),
{:ok, user} <- TwitterAPI.register_user(params), {:ok, user} <- TwitterAPI.register_user(params),
:ok <-
:telemetry.execute(
[:pleroma, :user, :account, :register],
%{count: 1},
%{ip: conn.remote_ip, nickname: user.nickname}
),
{_, {:ok, token}} <- {_, {:ok, token}} <-
{:login, OAuthController.login(user, app, app.scopes)} do {:login, OAuthController.login(user, app, app.scopes)} do
OAuthController.after_token_exchange(conn, %{user: user, token: token}) OAuthController.after_token_exchange(conn, %{user: user, token: token})

View file

@ -17,6 +17,12 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do
def password_reset(conn, params) do def password_reset(conn, params) do
nickname_or_email = params["email"] || params["nickname"] nickname_or_email = params["email"] || params["nickname"]
:telemetry.execute(
[:pleroma, :user, :account, :password_reset],
%{count: 1},
%{for: nickname_or_email, ip: :inet.ntoa(conn.remote_ip)}
)
TwitterAPI.password_reset(nickname_or_email) TwitterAPI.password_reset(nickname_or_email)
json_response(conn, :no_content, "") json_response(conn, :no_content, "")

View file

@ -0,0 +1,70 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MetricsStorage do
use GenServer
@history_buffer_size 50
def metrics_history(metric) do
GenServer.call(__MODULE__, {:data, metric})
end
def start_link(args) do
GenServer.start_link(__MODULE__, args, name: __MODULE__)
end
@impl true
def init(metrics) do
Process.flag(:trap_exit, true)
metric_histories_map =
metrics
|> Enum.map(fn metric ->
attach_handler(metric)
{metric, CircularBuffer.new(@history_buffer_size)}
end)
|> Map.new()
{:ok, metric_histories_map}
end
@impl true
def terminate(_, metrics) do
for metric <- metrics do
:telemetry.detach({__MODULE__, metric, self()})
end
:ok
end
defp attach_handler(%{event_name: name_list} = metric) do
:telemetry.attach(
{__MODULE__, metric, self()},
name_list,
&__MODULE__.handle_event/4,
metric
)
end
def handle_event(_event_name, data, metadata, metric) do
if data = Phoenix.LiveDashboard.extract_datapoint_for_metric(metric, data, metadata) do
GenServer.cast(__MODULE__, {:telemetry_metric, data, metric})
end
end
@impl true
def handle_cast({:telemetry_metric, data, metric}, state) do
{:noreply, update_in(state[metric], &CircularBuffer.insert(&1, data))}
end
@impl true
def handle_call({:data, metric}, _from, state) do
if history = state[metric] do
{:reply, CircularBuffer.to_list(history), state}
else
{:reply, [], state}
end
end
end

View file

@ -381,6 +381,12 @@ defmodule Pleroma.Web.OAuth.OAuthController do
def token_revoke(%Plug.Conn{} = conn, %{"token" => token}) do def token_revoke(%Plug.Conn{} = conn, %{"token" => token}) do
with {:ok, %Token{} = oauth_token} <- Token.get_by_token(token), with {:ok, %Token{} = oauth_token} <- Token.get_by_token(token),
{:ok, oauth_token} <- RevokeToken.revoke(oauth_token) do {:ok, oauth_token} <- RevokeToken.revoke(oauth_token) do
:telemetry.execute(
[:pleroma, :user, :o_auth, :revoke],
%{count: 1},
%{ip: :inet.ntoa(conn.remote_ip), nickname: oauth_token.user.nickname}
)
conn = conn =
with session_token = AuthHelper.get_session_token(conn), with session_token = AuthHelper.get_session_token(conn),
%Token{token: ^session_token} <- oauth_token do %Token{token: ^session_token} <- oauth_token do

View file

@ -36,6 +36,7 @@ defmodule Pleroma.Web.OAuth.Token do
def get_by_token(token) do def get_by_token(token) do
token token
|> Query.get_by_token() |> Query.get_by_token()
|> Query.preload([:user])
|> Repo.find_resource() |> Repo.find_resource()
end end
@ -44,6 +45,7 @@ defmodule Pleroma.Web.OAuth.Token do
def get_by_token(%App{id: app_id} = _app, token) do def get_by_token(%App{id: app_id} = _app, token) do
Query.get_by_app(app_id) Query.get_by_app(app_id)
|> Query.get_by_token(token) |> Query.get_by_token(token)
|> Query.preload([:user])
|> Repo.find_resource() |> Repo.find_resource()
end end

View file

@ -996,7 +996,12 @@ defmodule Pleroma.Web.Router do
scope "/" do scope "/" do
pipe_through([:pleroma_html, :authenticate, :require_admin]) pipe_through([:pleroma_html, :authenticate, :require_admin])
live_dashboard("/phoenix/live_dashboard", additional_pages: [oban: Oban.LiveDashboard])
live_dashboard("/phoenix/live_dashboard",
metrics: Pleroma.Web.Telemetry,
metrics_history: {Pleroma.Web.MetricsStorage, :metrics_history, []},
additional_pages: [oban: Oban.LiveDashboard]
)
end end
# Test-only routes needed to test action dispatching and plug chain execution # Test-only routes needed to test action dispatching and plug chain execution

View file

@ -0,0 +1,201 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Telemetry do
use Supervisor
import Telemetry.Metrics
def start_link(arg) do
Supervisor.start_link(__MODULE__, arg, name: __MODULE__)
end
@impl true
def init(_arg) do
children = [
# Telemetry poller will execute the given period measurements
# every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics
{:telemetry_poller, measurements: periodic_measurements(), period: 10_000}
# Add reporters as children of your supervision tree.
# {Telemetry.Metrics.ConsoleReporter, metrics: metrics()}
]
Supervisor.init(children, strategy: :one_for_one)
end
def metrics do
[
# Phoenix Metrics
summary("phoenix.endpoint.start.system_time",
unit: {:native, :millisecond}
),
summary("phoenix.endpoint.stop.duration",
unit: {:native, :millisecond}
),
summary("phoenix.router_dispatch.start.system_time",
tags: [:route],
unit: {:native, :millisecond}
),
summary("phoenix.router_dispatch.exception.duration",
tags: [:route],
unit: {:native, :millisecond}
),
summary("phoenix.router_dispatch.stop.duration",
tags: [:route],
unit: {:native, :millisecond}
),
summary("phoenix.socket_connected.duration",
unit: {:native, :millisecond}
),
summary("phoenix.channel_joined.duration",
unit: {:native, :millisecond}
),
summary("phoenix.channel_handled_in.duration",
tags: [:event],
unit: {:native, :millisecond}
),
# Database Metrics
summary("pleroma.repo.query.total_time",
reporter_options: [nav: "Repo"],
unit: {:native, :millisecond},
description: "The sum of the other measurements"
),
summary("pleroma.repo.query.decode_time",
reporter_options: [nav: "Repo"],
unit: {:native, :millisecond},
description: "The time spent decoding the data received from the database"
),
summary("pleroma.repo.query.query_time",
reporter_options: [nav: "Repo"],
unit: {:native, :millisecond},
description: "The time spent executing the query"
),
summary("pleroma.repo.query.queue_time",
reporter_options: [nav: "Repo"],
unit: {:native, :millisecond},
description: "The time spent waiting for a database connection"
),
summary("pleroma.repo.query.idle_time",
reporter_options: [nav: "Repo"],
unit: {:native, :millisecond},
description:
"The time the connection spent waiting before being checked out for the query"
),
# VM Metrics
summary("vm.memory.total", unit: {:byte, :megabyte}),
summary("vm.total_run_queue_lengths.total"),
summary("vm.total_run_queue_lengths.cpu"),
summary("vm.total_run_queue_lengths.io"),
# Pleroma Metrics
# ActivityPub
sum("pleroma.activitypub.inbox.count",
tags: [:type],
reporter_options: [nav: "ActivityPub"],
description: "Sum of activities received by type"
),
sum("pleroma.activitypub.publisher.sum",
tags: [:nickname],
reporter_options: [nav: "ActivityPub"],
description: "Sum of published activities by actor"
),
sum("pleroma.activitypub.publisher.sum",
tags: [:type],
reporter_options: [nav: "ActivityPub"],
description: "Sum of published activities by type"
),
counter("pleroma.activitypub.publisher.count",
tags: [:nickname],
reporter_options: [nav: "ActivityPub"],
description: "Counter of publishing events by activity actor"
),
counter("pleroma.activitypub.publisher.count",
tags: [:type],
reporter_options: [nav: "ActivityPub"],
description: "Counter of publishing events by activity type"
),
# OAuth
sum("pleroma.user.o_auth.success.count",
tags: [:nickname],
reporter_options: [nav: "OAuth"],
description: "Sum of successful login attempts by user"
),
# this graph is at risk of high cardinality as even incorrect
# usernames are passed through as the nickname
sum("pleroma.user.o_auth.failure.count",
tags: [:nickname],
reporter_options: [nav: "OAuth"],
description: "Sum of failed login attempts by user"
),
# Gun
last_value("pleroma.connection_pool.worker.count",
reporter_options: [nav: "Gun"],
description: "Number of connection pool workers"
),
counter("pleroma.connection_pool.client.add.count",
reporter_options: [nav: "Gun"],
description: "Counter of new connection events"
),
counter("pleroma.connection_pool.client.dead.count",
reporter_options: [nav: "Gun"],
description: "Counter of dead connection events"
),
counter("pleroma.connection_pool.provision_failure.count",
reporter_options: [nav: "Gun"],
description: "Counter of connection provisioning failure events"
),
counter("pleroma.connection_pool.reclaim.stop.count",
reporter_options: [nav: "Gun"],
description: "Counter of idle connection reclaimation events"
),
# Uploads
last_value("pleroma.upload.success.size",
reporter_options: [nav: "Uploads"],
description: "Size of media uploads in bytes",
unit: {:byte, :megabyte}
),
counter("pleroma.upload.success.count",
reporter_options: [nav: "Uploads"],
description: "Count of media uploads by nickname",
tags: [:nickname]
),
summary("pleroma.upload.success.duration",
reporter_options: [nav: "Uploads"],
description: "Time spent receiving and processing uploads",
unit: {:millisecond, :second}
),
# Stats
last_value("pleroma.stats.global.activities.count",
reporter_options: [nav: "Stats"],
description: "Total number of known activities per visibility type",
tags: [:visibility]
),
last_value("pleroma.stats.local.domains.count",
reporter_options: [nav: "Stats"],
description: "Total number of known domains"
),
last_value("pleroma.stats.local.notes.count",
reporter_options: [nav: "Stats"],
description: "Total number of notes by local users"
),
last_value("pleroma.stats.local.users.count",
reporter_options: [nav: "Stats"],
description: "Total number of local user accounts"
)
]
end
defp periodic_measurements do
[
# A module, function and arguments to be invoked periodically.
# This function must call :telemetry.execute/3 and a metric must be added above.
# {Pleroma.Web, :count_users, []}
]
end
end

View file

@ -24,6 +24,12 @@ defmodule Pleroma.Web.TwitterAPI.PasswordController do
def request(conn, params) do def request(conn, params) do
nickname_or_email = params["email"] || params["nickname"] nickname_or_email = params["email"] || params["nickname"]
:telemetry.execute(
[:pleroma, :user, :account, :password_reset],
%{count: 1},
%{for: nickname_or_email, ip: :inet.ntoa(conn.remote_ip)}
)
TwitterAPI.password_reset(nickname_or_email) TwitterAPI.password_reset(nickname_or_email)
json_response(conn, :no_content, "") json_response(conn, :no_content, "")

View file

@ -9,7 +9,11 @@ defmodule Pleroma.Workers.UserRefreshWorker do
@impl true @impl true
def perform(%Job{args: %{"ap_id" => ap_id}}) do def perform(%Job{args: %{"ap_id" => ap_id}}) do
User.fetch_by_ap_id(ap_id) case User.fetch_by_ap_id(ap_id) do
{:error, :not_found} -> {:cancel, :not_found}
{:error, :forbidden} -> {:cancel, :forbidden}
result -> result
end
end end
@impl true @impl true

View file

@ -207,6 +207,8 @@ defmodule Pleroma.Mixfile do
{:oban_live_dashboard, "~> 0.1.1"}, {:oban_live_dashboard, "~> 0.1.1"},
{:multipart, "~> 0.4.0", optional: true}, {:multipart, "~> 0.4.0", optional: true},
{:argon2_elixir, "~> 4.0"}, {:argon2_elixir, "~> 4.0"},
{:ring_logger, "~> 0.11.3"},
{:circular_buffer, "~> 0.4.0"},
## dev & test ## dev & test
{:phoenix_live_reload, "~> 1.3.3", only: :dev}, {:phoenix_live_reload, "~> 1.3.3", only: :dev},
@ -220,7 +222,8 @@ defmodule Pleroma.Mixfile do
{:mox, "~> 1.0", only: :test}, {:mox, "~> 1.0", only: :test},
{:websockex, "~> 0.4.3", only: :test}, {:websockex, "~> 0.4.3", only: :test},
{:benchee, "~> 1.0", only: :benchmark}, {:benchee, "~> 1.0", only: :benchmark},
{:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false} {:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false},
{:telemetry_test, "~> 0.1.0", only: :test}
] ++ oauth_deps() ++ logger_deps() ] ++ oauth_deps() ++ logger_deps()
end end

View file

@ -14,6 +14,7 @@
"castore": {:hex, :castore, "1.0.8", "dedcf20ea746694647f883590b82d9e96014057aff1d44d03ec90f36a5c0dc6e", [:mix], [], "hexpm", "0b2b66d2ee742cb1d9cb8c8be3b43c3a70ee8651f37b75a8b982e036752983f1"}, "castore": {:hex, :castore, "1.0.8", "dedcf20ea746694647f883590b82d9e96014057aff1d44d03ec90f36a5c0dc6e", [:mix], [], "hexpm", "0b2b66d2ee742cb1d9cb8c8be3b43c3a70ee8651f37b75a8b982e036752983f1"},
"cc_precompiler": {:hex, :cc_precompiler, "0.1.9", "e8d3364f310da6ce6463c3dd20cf90ae7bbecbf6c5203b98bf9b48035592649b", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "9dcab3d0f3038621f1601f13539e7a9ee99843862e66ad62827b0c42b2f58a54"}, "cc_precompiler": {:hex, :cc_precompiler, "0.1.9", "e8d3364f310da6ce6463c3dd20cf90ae7bbecbf6c5203b98bf9b48035592649b", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "9dcab3d0f3038621f1601f13539e7a9ee99843862e66ad62827b0c42b2f58a54"},
"certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
"circular_buffer": {:hex, :circular_buffer, "0.4.1", "477f370fd8cfe1787b0a1bade6208bbd274b34f1610e41f1180ba756a7679839", [:mix], [], "hexpm", "633ef2e059dde0d7b89bbab13b1da9d04c6685e80e68fbdf41282d4fae746b72"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
"comeonin": {:hex, :comeonin, "5.4.0", "246a56ca3f41d404380fc6465650ddaa532c7f98be4bda1b4656b3a37cc13abe", [:mix], [], "hexpm", "796393a9e50d01999d56b7b8420ab0481a7538d0caf80919da493b4a6e51faf1"}, "comeonin": {:hex, :comeonin, "5.4.0", "246a56ca3f41d404380fc6465650ddaa532c7f98be4bda1b4656b3a37cc13abe", [:mix], [], "hexpm", "796393a9e50d01999d56b7b8420ab0481a7538d0caf80919da493b4a6e51faf1"},
"concurrent_limiter": {:hex, :concurrent_limiter, "0.1.1", "43ae1dc23edda1ab03dd66febc739c4ff710d047bb4d735754909f9a474ae01c", [:mix], [{:telemetry, "~> 0.3", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "53968ff238c0fbb4d7ed76ddb1af0be6f3b2f77909f6796e249e737c505a16eb"}, "concurrent_limiter": {:hex, :concurrent_limiter, "0.1.1", "43ae1dc23edda1ab03dd66febc739c4ff710d047bb4d735754909f9a474ae01c", [:mix], [{:telemetry, "~> 0.3", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "53968ff238c0fbb4d7ed76ddb1af0be6f3b2f77909f6796e249e737c505a16eb"},
@ -127,6 +128,7 @@
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
"recon": {:hex, :recon, "2.5.4", "05dd52a119ee4059fa9daa1ab7ce81bc7a8161a2f12e9d42e9d551ffd2ba901c", [:mix, :rebar3], [], "hexpm", "e9ab01ac7fc8572e41eb59385efeb3fb0ff5bf02103816535bacaedf327d0263"}, "recon": {:hex, :recon, "2.5.4", "05dd52a119ee4059fa9daa1ab7ce81bc7a8161a2f12e9d42e9d551ffd2ba901c", [:mix, :rebar3], [], "hexpm", "e9ab01ac7fc8572e41eb59385efeb3fb0ff5bf02103816535bacaedf327d0263"},
"remote_ip": {:git, "https://git.pleroma.social/pleroma/remote_ip.git", "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8", [ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"]}, "remote_ip": {:git, "https://git.pleroma.social/pleroma/remote_ip.git", "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8", [ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"]},
"ring_logger": {:hex, :ring_logger, "0.11.3", "08a423e5d088d5bf41bf1bdf23b804f96279624ef09ca6b5b5012c32014b38a8", [:mix], [{:circular_buffer, "~> 0.4.0", [hex: :circular_buffer, repo: "hexpm", optional: false]}], "hexpm", "b870a23b8f8329aeadcbee3429e5e43942e4134d810a71e9a5a2f0567b9ce78d"},
"rustler": {:hex, :rustler, "0.30.0", "cefc49922132b072853fa9b0ca4dc2ffcb452f68fb73b779042b02d545e097fb", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:toml, "~> 0.6", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "9ef1abb6a7dda35c47cfc649e6a5a61663af6cf842a55814a554a84607dee389"}, "rustler": {:hex, :rustler, "0.30.0", "cefc49922132b072853fa9b0ca4dc2ffcb452f68fb73b779042b02d545e097fb", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:toml, "~> 0.6", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "9ef1abb6a7dda35c47cfc649e6a5a61663af6cf842a55814a554a84607dee389"},
"sleeplocks": {:hex, :sleeplocks, "1.1.2", "d45aa1c5513da48c888715e3381211c859af34bee9b8290490e10c90bb6ff0ca", [:rebar3], [], "hexpm", "9fe5d048c5b781d6305c1a3a0f40bb3dfc06f49bf40571f3d2d0c57eaa7f59a5"}, "sleeplocks": {:hex, :sleeplocks, "1.1.2", "d45aa1c5513da48c888715e3381211c859af34bee9b8290490e10c90bb6ff0ca", [:rebar3], [], "hexpm", "9fe5d048c5b781d6305c1a3a0f40bb3dfc06f49bf40571f3d2d0c57eaa7f59a5"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
@ -139,6 +141,7 @@
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.2", "2caabe9344ec17eafe5403304771c3539f3b6e2f7fb6a6f602558c825d0d0bfb", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b43db0dc33863930b9ef9d27137e78974756f5f198cae18409970ed6fa5b561"}, "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.2", "2caabe9344ec17eafe5403304771c3539f3b6e2f7fb6a6f602558c825d0d0bfb", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b43db0dc33863930b9ef9d27137e78974756f5f198cae18409970ed6fa5b561"},
"telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.2.0", "b583c3f18508f5c5561b674d16cf5d9afd2ea3c04505b7d92baaeac93c1b8260", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "9cba950e1c4733468efbe3f821841f34ac05d28e7af7798622f88ecdbbe63ea3"}, "telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.2.0", "b583c3f18508f5c5561b674d16cf5d9afd2ea3c04505b7d92baaeac93c1b8260", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "9cba950e1c4733468efbe3f821841f34ac05d28e7af7798622f88ecdbbe63ea3"},
"telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"}, "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"},
"telemetry_test": {:hex, :telemetry_test, "0.1.2", "122d927567c563cf57773105fa8104ae4299718ec2cbdddcf6776562c7488072", [:mix], [{:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7bd41a49ecfd33ecd82d2c7edae19a5736f0d2150206d0ee290dcf3885d0e14d"},
"tesla": {:hex, :tesla, "1.11.0", "81b2b10213dddb27105ec6102d9eb0cc93d7097a918a0b1594f2dfd1a4601190", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "b83ab5d4c2d202e1ea2b7e17a49f788d49a699513d7c4f08f2aef2c281be69db"}, "tesla": {:hex, :tesla, "1.11.0", "81b2b10213dddb27105ec6102d9eb0cc93d7097a918a0b1594f2dfd1a4601190", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "b83ab5d4c2d202e1ea2b7e17a49f788d49a699513d7c4f08f2aef2c281be69db"},
"thousand_island": {:hex, :thousand_island, "1.3.5", "6022b6338f1635b3d32406ff98d68b843ba73b3aa95cfc27154223244f3a6ca5", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2be6954916fdfe4756af3239fb6b6d75d0b8063b5df03ba76fd8a4c87849e180"}, "thousand_island": {:hex, :thousand_island, "1.3.5", "6022b6338f1635b3d32406ff98d68b843ba73b3aa95cfc27154223244f3a6ca5", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2be6954916fdfe4756af3239fb6b6d75d0b8063b5df03ba76fd8a4c87849e180"},
"timex": {:hex, :timex, "3.7.7", "3ed093cae596a410759104d878ad7b38e78b7c2151c6190340835515d4a46b8a", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "0ec4b09f25fe311321f9fc04144a7e3affe48eb29481d7a5583849b6c4dfa0a7"}, "timex": {:hex, :timex, "3.7.7", "3ed093cae596a410759104d878ad7b38e78b7c2151c6190340835515d4a46b8a", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "0ec4b09f25fe311321f9fc04144a7e3affe48eb29481d7a5583849b6c4dfa0a7"},

View file

@ -0,0 +1,113 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.CounterCacheTest do
use Pleroma.DataCase, async: true
import Pleroma.Factory
alias Pleroma.CounterCache
alias Pleroma.Web.CommonAPI
@local_instance Pleroma.Web.Endpoint.url() |> String.split("//") |> Enum.at(1)
describe "status visibility sum count" do
test "on new status" do
instance2 = "instance2.tld"
user = insert(:user)
other_user = insert(:user, %{ap_id: "https://#{instance2}/@actor"})
CommonAPI.post(user, %{visibility: "public", status: "hey"})
Enum.each(0..1, fn _ ->
CommonAPI.post(user, %{
visibility: "unlisted",
status: "hey"
})
end)
Enum.each(0..2, fn _ ->
CommonAPI.post(user, %{
visibility: "direct",
status: "hey @#{other_user.nickname}"
})
end)
Enum.each(0..3, fn _ ->
CommonAPI.post(user, %{
visibility: "private",
status: "hey"
})
end)
assert %{"direct" => 3, "private" => 4, "public" => 1, "unlisted" => 2} =
CounterCache.get_by_instance(@local_instance)
end
test "on status delete" do
user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{visibility: "public", status: "hey"})
assert %{"public" => 1} = CounterCache.get_by_instance(@local_instance)
CommonAPI.delete(activity.id, user)
assert %{"public" => 0} = CounterCache.get_by_instance(@local_instance)
end
test "on status visibility update" do
user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{visibility: "public", status: "hey"})
assert %{"public" => 1, "private" => 0} = CounterCache.get_by_instance(@local_instance)
{:ok, _} = CommonAPI.update_activity_scope(activity.id, %{visibility: "private"})
assert %{"public" => 0, "private" => 1} = CounterCache.get_by_instance(@local_instance)
end
test "doesn't count unrelated activities" do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{visibility: "public", status: "hey"})
_ = CommonAPI.follow(other_user, user)
CommonAPI.favorite(activity.id, other_user)
CommonAPI.repeat(activity.id, other_user)
assert %{"direct" => 0, "private" => 0, "public" => 1, "unlisted" => 0} =
CounterCache.get_by_instance(@local_instance)
end
end
describe "status visibility by instance count" do
test "single instance" do
instance2 = "instance2.tld"
user1 = insert(:user)
user2 = insert(:user, %{ap_id: "https://#{instance2}/@actor"})
CommonAPI.post(user1, %{visibility: "public", status: "hey"})
Enum.each(1..5, fn _ ->
CommonAPI.post(user1, %{
visibility: "unlisted",
status: "hey"
})
end)
Enum.each(1..10, fn _ ->
CommonAPI.post(user1, %{
visibility: "direct",
status: "hey @#{user2.nickname}"
})
end)
Enum.each(1..20, fn _ ->
CommonAPI.post(user2, %{
visibility: "private",
status: "hey"
})
end)
assert %{"direct" => 10, "private" => 0, "public" => 1, "unlisted" => 5} =
CounterCache.get_by_instance(@local_instance)
assert %{"direct" => 0, "private" => 20, "public" => 0, "unlisted" => 0} =
CounterCache.get_by_instance(instance2)
end
end
end

View file

@ -2,13 +2,12 @@
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.StatsTest do defmodule Pleroma.CounterCacheTest do
use Pleroma.DataCase, async: true use Pleroma.DataCase, async: true
import Pleroma.Factory import Pleroma.Factory
alias Pleroma.Stats alias Pleroma.Stats
alias Pleroma.Web.CommonAPI
describe "user count" do describe "user count" do
test "it ignores internal users" do test "it ignores internal users" do
@ -19,104 +18,4 @@ defmodule Pleroma.StatsTest do
assert match?(%{stats: %{user_count: 1}}, Stats.calculate_stat_data()) assert match?(%{stats: %{user_count: 1}}, Stats.calculate_stat_data())
end end
end end
describe "status visibility sum count" do
test "on new status" do
instance2 = "instance2.tld"
user = insert(:user)
other_user = insert(:user, %{ap_id: "https://#{instance2}/@actor"})
CommonAPI.post(user, %{visibility: "public", status: "hey"})
Enum.each(0..1, fn _ ->
CommonAPI.post(user, %{
visibility: "unlisted",
status: "hey"
})
end)
Enum.each(0..2, fn _ ->
CommonAPI.post(user, %{
visibility: "direct",
status: "hey @#{other_user.nickname}"
})
end)
Enum.each(0..3, fn _ ->
CommonAPI.post(user, %{
visibility: "private",
status: "hey"
})
end)
assert %{"direct" => 3, "private" => 4, "public" => 1, "unlisted" => 2} =
Stats.get_status_visibility_count()
end
test "on status delete" do
user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{visibility: "public", status: "hey"})
assert %{"public" => 1} = Stats.get_status_visibility_count()
CommonAPI.delete(activity.id, user)
assert %{"public" => 0} = Stats.get_status_visibility_count()
end
test "on status visibility update" do
user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{visibility: "public", status: "hey"})
assert %{"public" => 1, "private" => 0} = Stats.get_status_visibility_count()
{:ok, _} = CommonAPI.update_activity_scope(activity.id, %{visibility: "private"})
assert %{"public" => 0, "private" => 1} = Stats.get_status_visibility_count()
end
test "doesn't count unrelated activities" do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{visibility: "public", status: "hey"})
_ = CommonAPI.follow(other_user, user)
CommonAPI.favorite(activity.id, other_user)
CommonAPI.repeat(activity.id, other_user)
assert %{"direct" => 0, "private" => 0, "public" => 1, "unlisted" => 0} =
Stats.get_status_visibility_count()
end
end
describe "status visibility by instance count" do
test "single instance" do
local_instance = Pleroma.Web.Endpoint.url() |> String.split("//") |> Enum.at(1)
instance2 = "instance2.tld"
user1 = insert(:user)
user2 = insert(:user, %{ap_id: "https://#{instance2}/@actor"})
CommonAPI.post(user1, %{visibility: "public", status: "hey"})
Enum.each(1..5, fn _ ->
CommonAPI.post(user1, %{
visibility: "unlisted",
status: "hey"
})
end)
Enum.each(1..10, fn _ ->
CommonAPI.post(user1, %{
visibility: "direct",
status: "hey @#{user2.nickname}"
})
end)
Enum.each(1..20, fn _ ->
CommonAPI.post(user2, %{
visibility: "private",
status: "hey"
})
end)
assert %{"direct" => 10, "private" => 0, "public" => 1, "unlisted" => 5} =
Stats.get_status_visibility_count(local_instance)
assert %{"direct" => 0, "private" => 20, "public" => 0, "unlisted" => 0} =
Stats.get_status_visibility_count(instance2)
end
end
end end

View file

@ -58,7 +58,7 @@ defmodule Pleroma.UploadTest do
test "it returns file" do test "it returns file" do
File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg") File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
assert {:ok, result} = Upload.store(@upload_file) assert {:ok, %Upload{}, result} = Upload.store(@upload_file)
assert result == assert result ==
%{ %{
@ -140,7 +140,7 @@ defmodule Pleroma.UploadTest do
filename: "image.jpg" filename: "image.jpg"
} }
{:ok, data} = Upload.store(file) {:ok, %Upload{}, data} = Upload.store(file)
assert %{"url" => [%{"href" => url}]} = data assert %{"url" => [%{"href" => url}]} = data
@ -159,7 +159,7 @@ defmodule Pleroma.UploadTest do
filename: "an [image.jpg" filename: "an [image.jpg"
} }
{:ok, data} = Upload.store(file, filters: [Pleroma.Upload.Filter.Dedupe]) {:ok, %Upload{}, data} = Upload.store(file, filters: [Pleroma.Upload.Filter.Dedupe])
assert List.first(data["url"])["href"] == assert List.first(data["url"])["href"] ==
Path.join([Pleroma.Upload.base_url(), expected_path]) Path.join([Pleroma.Upload.base_url(), expected_path])
@ -174,7 +174,7 @@ defmodule Pleroma.UploadTest do
filename: "an [image.jpg" filename: "an [image.jpg"
} }
{:ok, data} = Upload.store(file) {:ok, %Upload{}, data} = Upload.store(file)
assert data["name"] == "an [image.jpg" assert data["name"] == "an [image.jpg"
end end
@ -183,7 +183,7 @@ defmodule Pleroma.UploadTest do
img: "data:image/png;base64,#{Base.encode64(File.read!("test/fixtures/image.jpg"))}" img: "data:image/png;base64,#{Base.encode64(File.read!("test/fixtures/image.jpg"))}"
} }
{:ok, data} = Upload.store(params) {:ok, %Upload{}, data} = Upload.store(params)
assert hd(data["url"])["mediaType"] == "image/jpeg" assert hd(data["url"])["mediaType"] == "image/jpeg"
end end
@ -194,7 +194,7 @@ defmodule Pleroma.UploadTest do
img: "data:image/png;base64,#{Base.encode64(File.read!("test/fixtures/image.jpg"))}" img: "data:image/png;base64,#{Base.encode64(File.read!("test/fixtures/image.jpg"))}"
} }
{:ok, data} = Upload.store(params) {:ok, %Upload{}, data} = Upload.store(params)
assert String.ends_with?(data["name"], ".jpg") assert String.ends_with?(data["name"], ".jpg")
end end
@ -207,7 +207,8 @@ defmodule Pleroma.UploadTest do
filename: "an [image.jpg" filename: "an [image.jpg"
} }
{:ok, data} = Upload.store(file, filters: [Pleroma.Upload.Filter.AnonymizeFilename]) {:ok, %Upload{}, data} =
Upload.store(file, filters: [Pleroma.Upload.Filter.AnonymizeFilename])
refute data["name"] == "an [image.jpg" refute data["name"] == "an [image.jpg"
end end
@ -221,7 +222,7 @@ defmodule Pleroma.UploadTest do
filename: "an… image.jpg" filename: "an… image.jpg"
} }
{:ok, data} = Upload.store(file) {:ok, %Upload{}, data} = Upload.store(file)
[attachment_url | _] = data["url"] [attachment_url | _] = data["url"]
assert Path.basename(attachment_url["href"]) == "an%E2%80%A6%20image.jpg" assert Path.basename(attachment_url["href"]) == "an%E2%80%A6%20image.jpg"
@ -236,7 +237,7 @@ defmodule Pleroma.UploadTest do
filename: ":?#[]@!$&\\'()*+,;=.jpg" filename: ":?#[]@!$&\\'()*+,;=.jpg"
} }
{:ok, data} = Upload.store(file) {:ok, %Upload{}, data} = Upload.store(file)
[attachment_url | _] = data["url"] [attachment_url | _] = data["url"]
assert Path.basename(attachment_url["href"]) == assert Path.basename(attachment_url["href"]) ==
@ -260,7 +261,7 @@ defmodule Pleroma.UploadTest do
filename: "image.jpg" filename: "image.jpg"
} }
{:ok, data} = Upload.store(file, base_url: base_url) {:ok, %Upload{}, data} = Upload.store(file, base_url: base_url)
assert %{"url" => [%{"href" => url}]} = data assert %{"url" => [%{"href" => url}]} = data

View file

@ -1036,13 +1036,14 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
test "status visibility count", %{conn: conn} do test "status visibility count", %{conn: conn} do
user = insert(:user) user = insert(:user)
instance = Pleroma.Web.Endpoint.url() |> String.split("//") |> Enum.at(1)
CommonAPI.post(user, %{visibility: "public", status: "hey"}) CommonAPI.post(user, %{visibility: "public", status: "hey"})
CommonAPI.post(user, %{visibility: "unlisted", status: "hey"}) CommonAPI.post(user, %{visibility: "unlisted", status: "hey"})
CommonAPI.post(user, %{visibility: "unlisted", status: "hey"}) CommonAPI.post(user, %{visibility: "unlisted", status: "hey"})
response = response =
conn conn
|> get("/api/pleroma/admin/stats") |> get("/api/pleroma/admin/stats", %{"instance" => instance})
|> json_response(200) |> json_response(200)
assert %{"direct" => 0, "private" => 0, "public" => 1, "unlisted" => 2} = assert %{"direct" => 0, "private" => 0, "public" => 1, "unlisted" => 2} =

View file

@ -37,7 +37,7 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticatorTest do
params = %{"authorization" => %{"name" => name, "password" => "password"}} params = %{"authorization" => %{"name" => name, "password" => "password"}}
res = PleromaAuthenticator.get_user(%Plug.Conn{params: params}) res = PleromaAuthenticator.get_user(%Plug.Conn{params: params})
assert {:error, {:checkpw, false}} == res assert {:error, :invalid_password} == res
end end
test "get_user/grant_type_password", %{user: user, name: name, password: password} do test "get_user/grant_type_password", %{user: user, name: name, password: password} do

View file

@ -22,6 +22,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
import Mox import Mox
import Pleroma.Factory import Pleroma.Factory
import TelemetryTest
setup [:telemetry_listen]
setup do: clear_config([:instance, :federating]) setup do: clear_config([:instance, :federating])
setup do: clear_config([:instance, :allow_relay]) setup do: clear_config([:instance, :allow_relay])
@ -200,6 +203,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|> json_response_and_validate_schema(422) |> json_response_and_validate_schema(422)
end end
@tag telemetry_listen: [:pleroma, :upload, :success]
test "posting an undefined status with an attachment", %{user: user, conn: conn} do test "posting an undefined status with an attachment", %{user: user, conn: conn} do
file = %Plug.Upload{ file = %Plug.Upload{
content_type: "image/jpeg", content_type: "image/jpeg",
@ -209,6 +213,15 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
{:ok, upload} = ActivityPub.upload(file, actor: user.ap_id) {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
nickname = user.nickname
assert_receive {:telemetry_event,
%{
event: [:pleroma, :upload, :success],
measurements: %{count: 1, duration: _, size: _},
metadata: %{filename: _, nickname: ^nickname}
}}
conn = conn =
conn conn
|> put_req_header("content-type", "application/json") |> put_req_header("content-type", "application/json")

View file

@ -24,7 +24,7 @@ defmodule Pleroma.Web.Plugs.UploadedMediaPlugTest do
filename: "nice_tf.jpg" filename: "nice_tf.jpg"
} }
{:ok, data} = Upload.store(file) {:ok, %Upload{}, data} = Upload.store(file)
[%{"href" => attachment_url} | _] = data["url"] [%{"href" => attachment_url} | _] = data["url"]
[attachment_url: attachment_url] [attachment_url: attachment_url]
end end