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
config :logger, backends: [:console]
config :logger, backends: [:console, RingLogger]
config :logger, :console,
level: :debug,
format: "\n$time $metadata[$level] $message\n",
metadata: [:actor, :path, :type, :user]
metadata: []
config :logger, RingLogger, max_size: 1024
config :logger, :ex_syslogger,
level: :debug,
ident: "pleroma",
format: "$metadata[$level] $message",
metadata: [:actor, :path, :type, :user]
metadata: []
config :mime, :types, %{
"application/xml" => ["xml"],
@ -950,6 +952,8 @@ config :pleroma, Pleroma.Search.QdrantSearch,
vectors: %{size: 384, distance: "Cosine"}
}
config :pleroma, Pleroma.Telemetry, phoenix_logs: true
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
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, :ex_syslogger, level: :info
config :pleroma, Pleroma.Telemetry, phoenix_logs: false
# ## SSL Support
#
# 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(
fn {upload, root_path} ->
case Upload.store(upload, uploader: uploader, filters: [], size_limit: nil) do
{:ok, _} ->
{:ok, _, _} ->
if delete?, do: File.rm_rf!(root_path)
Logger.debug("uploaded: #{inspect(upload.path)} #{inspect(upload)}")
:ok

View file

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

View file

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

View file

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

View file

@ -26,7 +26,10 @@ defmodule Pleroma.Gun.ConnectionPool.WorkerSupervisor do
def start_worker(opts, true) do
case DynamicSupervisor.start_child(__MODULE__, {Worker, opts}) do
{: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}
res ->

View file

@ -70,7 +70,9 @@ defmodule Pleroma.HTTP do
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(
fn ->
@ -104,21 +106,20 @@ defmodule Pleroma.HTTP do
fun.()
end
defp adapter_middlewares(Tesla.Adapter.Gun, extra_middleware) do
[Tesla.Middleware.FollowRedirects, Pleroma.Tesla.Middleware.ConnectionPool] ++
extra_middleware
defp adapter_middlewares(Tesla.Adapter.Gun) do
[Tesla.Middleware.FollowRedirects, Pleroma.Tesla.Middleware.ConnectionPool]
end
defp adapter_middlewares({Tesla.Adapter.Finch, _}, extra_middleware) do
[Tesla.Middleware.FollowRedirects] ++ extra_middleware
defp adapter_middlewares({Tesla.Adapter.Finch, _}) do
[Tesla.Middleware.FollowRedirects]
end
defp adapter_middlewares(_, extra_middleware) do
defp adapter_middlewares(_) do
if Pleroma.Config.get(:env) == :test do
# Emulate redirects in test env, which are handled by adapters in other environments
[Tesla.Middleware.FollowRedirects]
else
extra_middleware
[]
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 set_reachable(url_or_host) when is_binary(url_or_host) do
%Instance{host: host(url_or_host)}
|> changeset(%{unreachable_since: nil})
|> Repo.insert(on_conflict: {:replace, [:unreachable_since]}, conflict_target: :host)
host = host(url_or_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
def set_reachable(_), do: {:error, nil}
@ -113,21 +137,30 @@ defmodule Pleroma.Instances.Instance do
changes = %{unreachable_since: unreachable_since}
cond do
is_nil(existing_record) ->
%Instance{}
|> changeset(Map.put(changes, :host, host))
|> Repo.insert()
result =
cond do
is_nil(existing_record) ->
%Instance{}
|> changeset(Map.put(changes, :host, host))
|> Repo.insert()
existing_record.unreachable_since &&
NaiveDateTime.compare(existing_record.unreachable_since, unreachable_since) != :gt ->
{:ok, existing_record}
existing_record.unreachable_since &&
NaiveDateTime.compare(existing_record.unreachable_since, unreachable_since) != :gt ->
{:ok, existing_record}
true ->
existing_record
|> changeset(changes)
|> Repo.update()
end
true ->
existing_record
|> changeset(changes)
|> Repo.update()
end
:telemetry.execute(
[:pleroma, :instance, :unreachable],
%{count: 1},
%{host: host, instance: existing_record}
)
result
end
def set_unreachable(_, _), do: {:error, nil}

View file

@ -11,6 +11,8 @@ defmodule Pleroma.Stats do
alias Pleroma.Repo
alias Pleroma.User
require Logger
@interval :timer.seconds(60)
def start_link(_) do
@ -55,6 +57,14 @@ defmodule Pleroma.Stats do
peers
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() :: %{
peers: list(),
stats: %{
@ -63,6 +73,11 @@ defmodule Pleroma.Stats do
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
peers =
from(
@ -87,6 +102,21 @@ defmodule Pleroma.Stats do
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,
stats: %{
@ -97,13 +127,19 @@ defmodule Pleroma.Stats do
}
end
@spec get_status_visibility_count(String.t() | nil) :: map()
def get_status_visibility_count(instance \\ nil) do
if is_nil(instance) do
CounterCache.get_sum()
else
CounterCache.get_by_instance(instance)
end
@spec get_status_visibility_count :: map()
def get_status_visibility_count do
result = CounterCache.get_sum()
Enum.each(result, fn {k, v} ->
:telemetry.execute(
[:pleroma, :stats, :global, :activities],
%{count: v},
%{visibility: k}
)
end)
result
end
@impl true
@ -134,4 +170,32 @@ defmodule Pleroma.Stats do
Process.send_after(self(), :run_update, @interval)
{:noreply, new_stats}
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

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
@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, :stop],
[:pleroma, :connection_pool, :provision_failure],
[:pleroma, :connection_pool, :client, :dead],
[:pleroma, :connection_pool, :client, :add]
[:pleroma, :upload, :success],
[:pleroma, :user, :account, :confirm],
[: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
:telemetry.attach_many("pleroma-logger", @events, &handle_event/4, [])
def setup do
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
# 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
# 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(
[:pleroma, :connection_pool, :reclaim, :start],
_,
_measurements,
%{max_connections: max_connections, reclaim_max: reclaim_max},
_
) do
@ -35,8 +67,8 @@ defmodule Pleroma.Telemetry.Logger do
def handle_event(
[:pleroma, :connection_pool, :reclaim, :stop],
%{reclaimed_count: 0},
_,
%{count: 0},
_measurements,
_
) do
Logger.debug(fn ->
@ -46,8 +78,8 @@ defmodule Pleroma.Telemetry.Logger do
def handle_event(
[:pleroma, :connection_pool, :reclaim, :stop],
%{reclaimed_count: reclaimed_count},
_,
%{count: reclaimed_count},
_measurements,
_
) do
Logger.debug(fn -> "Connection pool cleaned up #{reclaimed_count} idle connections" end)
@ -55,8 +87,8 @@ defmodule Pleroma.Telemetry.Logger do
def handle_event(
[:pleroma, :connection_pool, :provision_failure],
_measurements,
%{opts: [key | _]},
_,
_
) do
Logger.debug(fn ->
@ -66,8 +98,8 @@ defmodule Pleroma.Telemetry.Logger do
def handle_event(
[:pleroma, :connection_pool, :client, :dead],
%{client_pid: client_pid, reason: reason},
%{key: key},
_measurements,
%{client_pid: client_pid, key: key, reason: reason},
_
) do
Logger.debug(fn ->
@ -77,8 +109,8 @@ defmodule Pleroma.Telemetry.Logger do
def handle_event(
[:pleroma, :connection_pool, :client, :add],
%{clients: [_, _ | _] = clients},
%{key: key, protocol: :http},
_measurements,
%{clients: [_, _ | _] = clients, key: key, protocol: :http},
_
) do
Logger.debug(fn ->
@ -87,4 +119,254 @@ defmodule Pleroma.Telemetry.Logger do
end
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

View file

@ -87,7 +87,7 @@ defmodule Pleroma.Upload do
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."
def store(upload, opts \\ []) do
opts = get_opts(opts)
@ -100,7 +100,7 @@ defmodule Pleroma.Upload do
{:description_limit,
String.length(description) <= Pleroma.Config.get([:instance, :description_limit])},
{:ok, url_spec} <- Pleroma.Uploaders.Uploader.put_file(opts.uploader, upload) do
{:ok,
{:ok, upload,
%{
"id" => Utils.generate_object_id(),
"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.)"
def register(%Ecto.Changeset{} = changeset) do
with {:ok, user} <- Repo.insert(changeset) do
with {:ok, user} <- create(changeset) do
post_register_action(user)
end
end
@ -1276,6 +1276,12 @@ defmodule Pleroma.User do
|> Oban.insert()
end
:telemetry.execute(
[:pleroma, :user, :update],
%{count: 1},
%{nickname: user.nickname, user: user}
)
set_cache(user)
end
end
@ -1952,6 +1958,12 @@ defmodule Pleroma.User do
def confirm(%User{is_confirmed: false} = user) do
with chg <- confirmation_changeset(user, set_confirmation: true),
{:ok, user} <- update_and_set_cache(chg) do
:telemetry.execute(
[:pleroma, :user, :account, :confirm],
%{count: 1},
%{user: user}
)
post_register_action(user)
{:ok, user}
end
@ -1983,6 +1995,20 @@ defmodule Pleroma.User do
|> update_and_set_cache()
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()
def purge_user_changeset(user) do
# "Right to be forgotten"
@ -2040,6 +2066,12 @@ defmodule Pleroma.User do
# Purge the user immediately
purge(user)
:telemetry.execute(
[:pleroma, :user, :delete],
%{count: 1},
%{nickname: user.nickname, user: user}
)
DeleteWorker.new(%{"op" => "delete_user", "user_id" => user.id})
|> Oban.insert()
end
@ -2275,8 +2307,7 @@ defmodule Pleroma.User do
|> change
|> put_private_key()
|> unique_constraint(:nickname)
|> Repo.insert()
|> set_cache()
|> create()
end
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
@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
with {:ok, %User{} = muted_user} <- User.get_or_fetch(actor),
{_, 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()}
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])
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})
end
@ -1860,8 +1879,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
data
|> User.remote_user_changeset()
|> Repo.insert()
|> User.set_cache()
|> User.create()
end
end
end

View file

@ -52,7 +52,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
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(:relay_active? when action in [:relay])
@ -529,12 +529,20 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
conn
end
defp log_inbox_metadata(%{params: %{"actor" => actor, "type" => type}} = conn, _) do
Logger.metadata(actor: actor, type: type)
defp inbox_telemetry(%{params: %{"actor" => actor, "id" => ap_id, "type" => type}} = conn, _) do
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
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
with {:ok, object} <-

View file

@ -306,6 +306,18 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|> Instances.filter_reachable()
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 ->
Enum.each(inboxes, fn inboxes ->
Enum.each(inboxes, fn {inbox, unreachable_since} ->
@ -348,6 +360,18 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
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}]
|> Enum.each(fn {inboxes, priority} ->
inboxes

View file

@ -11,7 +11,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
alias Pleroma.Config
alias Pleroma.MFA
alias Pleroma.ModerationLog
alias Pleroma.Stats
alias Pleroma.CounterCache
alias Pleroma.User
alias Pleroma.User.Backup
alias Pleroma.Web.ActivityPub.ActivityPub
@ -423,7 +423,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
end
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})
end

View file

@ -20,9 +20,26 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do
def get_user(%Plug.Conn{} = conn) do
with {:ldap, true} <- {:ldap, Pleroma.Config.get([:ldap, :enabled])},
{: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}
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, _} ->
@base.get_user(conn)

View file

@ -18,10 +18,27 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do
{_, %User{} = user} <- {:user, fetch_user(name)},
{_, true} <- {:checkpw, AuthenticationPlug.checkpw(password, user.password_hash)},
{: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}
else
{:error, _reason} = error -> error
error -> {:error, error}
{:checkpw, false} ->
oauth_telemetry(conn)
{:error, :invalid_password}
{:user, nil} ->
oauth_telemetry(conn)
{:error, :invalid_user}
{:error, _reason} = error ->
error
error ->
{:error, error}
end
end
@ -121,4 +138,14 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do
end
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

View file

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

View file

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

View file

@ -113,6 +113,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
with :ok <- validate_email_param(params),
:ok <- TwitterAPI.validate_captcha(app, 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}} <-
{:login, OAuthController.login(user, app, app.scopes)} do
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
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)
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
with {:ok, %Token{} = oauth_token} <- Token.get_by_token(token),
{: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 =
with session_token = AuthHelper.get_session_token(conn),
%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
token
|> Query.get_by_token()
|> Query.preload([:user])
|> Repo.find_resource()
end
@ -44,6 +45,7 @@ defmodule Pleroma.Web.OAuth.Token do
def get_by_token(%App{id: app_id} = _app, token) do
Query.get_by_app(app_id)
|> Query.get_by_token(token)
|> Query.preload([:user])
|> Repo.find_resource()
end

View file

@ -996,7 +996,12 @@ defmodule Pleroma.Web.Router do
scope "/" do
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
# 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
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)
json_response(conn, :no_content, "")

View file

@ -9,7 +9,11 @@ defmodule Pleroma.Workers.UserRefreshWorker do
@impl true
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
@impl true

View file

@ -207,6 +207,8 @@ defmodule Pleroma.Mixfile do
{:oban_live_dashboard, "~> 0.1.1"},
{:multipart, "~> 0.4.0", optional: true},
{:argon2_elixir, "~> 4.0"},
{:ring_logger, "~> 0.11.3"},
{:circular_buffer, "~> 0.4.0"},
## dev & test
{:phoenix_live_reload, "~> 1.3.3", only: :dev},
@ -220,7 +222,8 @@ defmodule Pleroma.Mixfile do
{:mox, "~> 1.0", only: :test},
{:websockex, "~> 0.4.3", only: :test},
{: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()
end

View file

@ -14,6 +14,7 @@
"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"},
"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"},
"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"},
@ -127,6 +128,7 @@
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
"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"]},
"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"},
"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"},
@ -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_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_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"},
"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"},

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/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.StatsTest do
defmodule Pleroma.CounterCacheTest do
use Pleroma.DataCase, async: true
import Pleroma.Factory
alias Pleroma.Stats
alias Pleroma.Web.CommonAPI
describe "user count" 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())
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

View file

@ -58,7 +58,7 @@ defmodule Pleroma.UploadTest do
test "it returns file" do
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 ==
%{
@ -140,7 +140,7 @@ defmodule Pleroma.UploadTest do
filename: "image.jpg"
}
{:ok, data} = Upload.store(file)
{:ok, %Upload{}, data} = Upload.store(file)
assert %{"url" => [%{"href" => url}]} = data
@ -159,7 +159,7 @@ defmodule Pleroma.UploadTest do
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"] ==
Path.join([Pleroma.Upload.base_url(), expected_path])
@ -174,7 +174,7 @@ defmodule Pleroma.UploadTest do
filename: "an [image.jpg"
}
{:ok, data} = Upload.store(file)
{:ok, %Upload{}, data} = Upload.store(file)
assert data["name"] == "an [image.jpg"
end
@ -183,7 +183,7 @@ defmodule Pleroma.UploadTest do
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"
end
@ -194,7 +194,7 @@ defmodule Pleroma.UploadTest do
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")
end
@ -207,7 +207,8 @@ defmodule Pleroma.UploadTest do
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"
end
@ -221,7 +222,7 @@ defmodule Pleroma.UploadTest do
filename: "an… image.jpg"
}
{:ok, data} = Upload.store(file)
{:ok, %Upload{}, data} = Upload.store(file)
[attachment_url | _] = data["url"]
assert Path.basename(attachment_url["href"]) == "an%E2%80%A6%20image.jpg"
@ -236,7 +237,7 @@ defmodule Pleroma.UploadTest do
filename: ":?#[]@!$&\\'()*+,;=.jpg"
}
{:ok, data} = Upload.store(file)
{:ok, %Upload{}, data} = Upload.store(file)
[attachment_url | _] = data["url"]
assert Path.basename(attachment_url["href"]) ==
@ -260,7 +261,7 @@ defmodule Pleroma.UploadTest do
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

View file

@ -1036,13 +1036,14 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
test "status visibility count", %{conn: conn} do
user = insert(:user)
instance = Pleroma.Web.Endpoint.url() |> String.split("//") |> Enum.at(1)
CommonAPI.post(user, %{visibility: "public", status: "hey"})
CommonAPI.post(user, %{visibility: "unlisted", status: "hey"})
CommonAPI.post(user, %{visibility: "unlisted", status: "hey"})
response =
conn
|> get("/api/pleroma/admin/stats")
|> get("/api/pleroma/admin/stats", %{"instance" => instance})
|> json_response(200)
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"}}
res = PleromaAuthenticator.get_user(%Plug.Conn{params: params})
assert {:error, {:checkpw, false}} == res
assert {:error, :invalid_password} == res
end
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 Pleroma.Factory
import TelemetryTest
setup [:telemetry_listen]
setup do: clear_config([:instance, :federating])
setup do: clear_config([:instance, :allow_relay])
@ -200,6 +203,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|> json_response_and_validate_schema(422)
end
@tag telemetry_listen: [:pleroma, :upload, :success]
test "posting an undefined status with an attachment", %{user: user, conn: conn} do
file = %Plug.Upload{
content_type: "image/jpeg",
@ -209,6 +213,15 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
{: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
|> put_req_header("content-type", "application/json")

View file

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