diff --git a/config/config.exs b/config/config.exs index 07e98011d..6fe292b16 100644 --- a/config/config.exs +++ b/config/config.exs @@ -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" diff --git a/config/prod.exs b/config/prod.exs index 2d252bf02..625f7db9c 100644 --- a/config/prod.exs +++ b/config/prod.exs @@ -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 diff --git a/lib/mix/tasks/pleroma/uploads.ex b/lib/mix/tasks/pleroma/uploads.ex index bf02912fa..c9b7c2d4a 100644 --- a/lib/mix/tasks/pleroma/uploads.ex +++ b/lib/mix/tasks/pleroma/uploads.ex @@ -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 diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 3f199c002..5bdaef5c7 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -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, diff --git a/lib/pleroma/gun/connection_pool/reclaimer.ex b/lib/pleroma/gun/connection_pool/reclaimer.ex index 3580d38f5..d02f7c61c 100644 --- a/lib/pleroma/gun/connection_pool/reclaimer.ex +++ b/lib/pleroma/gun/connection_pool/reclaimer.ex @@ -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} ) diff --git a/lib/pleroma/gun/connection_pool/worker.ex b/lib/pleroma/gun/connection_pool/worker.ex index 38527ec1d..beaf54bb5 100644 --- a/lib/pleroma/gun/connection_pool/worker.ex +++ b/lib/pleroma/gun/connection_pool/worker.ex @@ -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 diff --git a/lib/pleroma/gun/connection_pool/worker_supervisor.ex b/lib/pleroma/gun/connection_pool/worker_supervisor.ex index b9dedf61e..1dcb8302c 100644 --- a/lib/pleroma/gun/connection_pool/worker_supervisor.ex +++ b/lib/pleroma/gun/connection_pool/worker_supervisor.ex @@ -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 -> diff --git a/lib/pleroma/http.ex b/lib/pleroma/http.ex index c11317850..50d963b72 100644 --- a/lib/pleroma/http.ex +++ b/lib/pleroma/http.ex @@ -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 diff --git a/lib/pleroma/instances/instance.ex b/lib/pleroma/instances/instance.ex index 33f1229d0..199d3812f 100644 --- a/lib/pleroma/instances/instance.ex +++ b/lib/pleroma/instances/instance.ex @@ -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} diff --git a/lib/pleroma/stats.ex b/lib/pleroma/stats.ex index 47b30b951..58e607057 100644 --- a/lib/pleroma/stats.ex +++ b/lib/pleroma/stats.ex @@ -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 diff --git a/lib/pleroma/telemetry.ex b/lib/pleroma/telemetry.ex new file mode 100644 index 000000000..942516e79 --- /dev/null +++ b/lib/pleroma/telemetry.ex @@ -0,0 +1,55 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# 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 diff --git a/lib/pleroma/telemetry/logger.ex b/lib/pleroma/telemetry/logger.ex index 31ce3cc20..24ec2e591 100644 --- a/lib/pleroma/telemetry/logger.ex +++ b/lib/pleroma/telemetry/logger.ex @@ -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 diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex index b0aef2592..cd52f7a1a 100644 --- a/lib/pleroma/upload.ex +++ b/lib/pleroma/upload.ex @@ -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, diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index d9da9ede1..cf2443f7d 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -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 diff --git a/lib/pleroma/user/import.ex b/lib/pleroma/user/import.ex index ab6bdb8d4..7626c0c94 100644 --- a/lib/pleroma/user/import.ex +++ b/lib/pleroma/user/import.ex @@ -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)}, diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 62c7a7b31..24b2b3537 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -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 diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 7ac0bbab4..5135c86b6 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -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} <- diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index 0de3a0d43..b76d37e71 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -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 diff --git a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex index 0f22dd538..aabbebefe 100644 --- a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex @@ -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 diff --git a/lib/pleroma/web/auth/ldap_authenticator.ex b/lib/pleroma/web/auth/ldap_authenticator.ex index ec6601fb9..230cbdb14 100644 --- a/lib/pleroma/web/auth/ldap_authenticator.ex +++ b/lib/pleroma/web/auth/ldap_authenticator.ex @@ -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) diff --git a/lib/pleroma/web/auth/pleroma_authenticator.ex b/lib/pleroma/web/auth/pleroma_authenticator.ex index 0da3f19fc..7bdc13776 100644 --- a/lib/pleroma/web/auth/pleroma_authenticator.ex +++ b/lib/pleroma/web/auth/pleroma_authenticator.ex @@ -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 diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index bab3c9fd0..76163c647 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -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], diff --git a/lib/pleroma/web/federator.ex b/lib/pleroma/web/federator.ex index 58260afa8..ff72ead1b 100644 --- a/lib/pleroma/web/federator.ex +++ b/lib/pleroma/web/federator.ex @@ -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 diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 68157b0c4..21d353117 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -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}) diff --git a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex index fbb54a171..0aba01dc8 100644 --- a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex @@ -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, "") diff --git a/lib/pleroma/web/metrics_storage.ex b/lib/pleroma/web/metrics_storage.ex new file mode 100644 index 000000000..4113495a6 --- /dev/null +++ b/lib/pleroma/web/metrics_storage.ex @@ -0,0 +1,70 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# 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 diff --git a/lib/pleroma/web/o_auth/o_auth_controller.ex b/lib/pleroma/web/o_auth/o_auth_controller.ex index 0b3de5481..3f0f44391 100644 --- a/lib/pleroma/web/o_auth/o_auth_controller.ex +++ b/lib/pleroma/web/o_auth/o_auth_controller.ex @@ -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 diff --git a/lib/pleroma/web/o_auth/token.ex b/lib/pleroma/web/o_auth/token.ex index d96425094..3f5e38a6c 100644 --- a/lib/pleroma/web/o_auth/token.ex +++ b/lib/pleroma/web/o_auth/token.ex @@ -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 diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index ca76427ac..14002436b 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -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 diff --git a/lib/pleroma/web/telemetry.ex b/lib/pleroma/web/telemetry.ex new file mode 100644 index 000000000..de2cb10bc --- /dev/null +++ b/lib/pleroma/web/telemetry.ex @@ -0,0 +1,201 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# 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 diff --git a/lib/pleroma/web/twitter_api/controllers/password_controller.ex b/lib/pleroma/web/twitter_api/controllers/password_controller.ex index e5482de9d..8f735893d 100644 --- a/lib/pleroma/web/twitter_api/controllers/password_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/password_controller.ex @@ -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, "") diff --git a/lib/pleroma/workers/user_refresh_worker.ex b/lib/pleroma/workers/user_refresh_worker.ex index ee276774b..881a522e2 100644 --- a/lib/pleroma/workers/user_refresh_worker.ex +++ b/lib/pleroma/workers/user_refresh_worker.ex @@ -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 diff --git a/mix.exs b/mix.exs index d8b7c1e2f..07c4079c2 100644 --- a/mix.exs +++ b/mix.exs @@ -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 diff --git a/mix.lock b/mix.lock index 9b53ede62..599e260d1 100644 --- a/mix.lock +++ b/mix.lock @@ -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"}, diff --git a/test/pleroma/counter_cache_test.exs b/test/pleroma/counter_cache_test.exs new file mode 100644 index 000000000..90e287f35 --- /dev/null +++ b/test/pleroma/counter_cache_test.exs @@ -0,0 +1,113 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# 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 diff --git a/test/pleroma/stats_test.exs b/test/pleroma/stats_test.exs index c70603ab9..e446a0452 100644 --- a/test/pleroma/stats_test.exs +++ b/test/pleroma/stats_test.exs @@ -2,13 +2,12 @@ # Copyright © 2017-2022 Pleroma Authors # 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 diff --git a/test/pleroma/upload_test.exs b/test/pleroma/upload_test.exs index 5fd62fa43..2f808cc2b 100644 --- a/test/pleroma/upload_test.exs +++ b/test/pleroma/upload_test.exs @@ -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 diff --git a/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs b/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs index 6614d1409..faa164008 100644 --- a/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs +++ b/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs @@ -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} = diff --git a/test/pleroma/web/auth/pleroma_authenticator_test.exs b/test/pleroma/web/auth/pleroma_authenticator_test.exs index 7f6d63ff3..b8e20b9c9 100644 --- a/test/pleroma/web/auth/pleroma_authenticator_test.exs +++ b/test/pleroma/web/auth/pleroma_authenticator_test.exs @@ -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 diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs index a4bca6cf9..f77a6beab 100644 --- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs @@ -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") diff --git a/test/pleroma/web/plugs/uploaded_media_plug_test.exs b/test/pleroma/web/plugs/uploaded_media_plug_test.exs index 6a9366e28..fad847fc2 100644 --- a/test/pleroma/web/plugs/uploaded_media_plug_test.exs +++ b/test/pleroma/web/plugs/uploaded_media_plug_test.exs @@ -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