Merge branch 'develop' into 'remove-twitter-api'
[akkoma] / benchmarks / load_testing / fetcher.ex
index cdc073b2ebea0f64f704c44a620f0f3e67c4398a..12c30f6f55a1b71f88070a83b7106b7667b4da43 100644 (file)
 defmodule Pleroma.LoadTesting.Fetcher do
-  use Pleroma.LoadTesting.Helper
+  alias Pleroma.Activity
+  alias Pleroma.Pagination
+  alias Pleroma.Repo
+  alias Pleroma.User
+  alias Pleroma.Web.ActivityPub.ActivityPub
+  alias Pleroma.Web.MastodonAPI.MastodonAPI
+  alias Pleroma.Web.MastodonAPI.StatusView
 
-  def fetch_user(user) do
-    Benchee.run(%{
-      "By id" => fn -> Repo.get_by(User, id: user.id) end,
-      "By ap_id" => fn -> Repo.get_by(User, ap_id: user.ap_id) end,
-      "By email" => fn -> Repo.get_by(User, email: user.email) end,
-      "By nickname" => fn -> Repo.get_by(User, nickname: user.nickname) end
-    })
+  @spec run_benchmarks(User.t()) :: any()
+  def run_benchmarks(user) do
+    fetch_user(user)
+    fetch_timelines(user)
+    render_views(user)
   end
 
-  def query_timelines(user) do
-    home_timeline_params = %{
-      "count" => 20,
-      "with_muted" => true,
-      "type" => ["Create", "Announce"],
+  defp formatters do
+    [
+      Benchee.Formatters.Console
+    ]
+  end
+
+  defp fetch_user(user) do
+    Benchee.run(
+      %{
+        "By id" => fn -> Repo.get_by(User, id: user.id) end,
+        "By ap_id" => fn -> Repo.get_by(User, ap_id: user.ap_id) end,
+        "By email" => fn -> Repo.get_by(User, email: user.email) end,
+        "By nickname" => fn -> Repo.get_by(User, nickname: user.nickname) end
+      },
+      formatters: formatters()
+    )
+  end
+
+  defp fetch_timelines(user) do
+    fetch_home_timeline(user)
+    fetch_direct_timeline(user)
+    fetch_public_timeline(user)
+    fetch_public_timeline(user, :local)
+    fetch_public_timeline(user, :tag)
+    fetch_notifications(user)
+    fetch_favourites(user)
+    fetch_long_thread(user)
+    fetch_timelines_with_reply_filtering(user)
+  end
+
+  defp render_views(user) do
+    render_timelines(user)
+    render_long_thread(user)
+  end
+
+  defp opts_for_home_timeline(user) do
+    %{
       "blocking_user" => user,
+      "count" => "20",
       "muting_user" => user,
-      "user" => user
+      "type" => ["Create", "Announce"],
+      "user" => user,
+      "with_muted" => "true"
     }
+  end
 
-    mastodon_public_timeline_params = %{
-      "count" => 20,
-      "local_only" => true,
-      "only_media" => "false",
+  defp fetch_home_timeline(user) do
+    opts = opts_for_home_timeline(user)
+
+    recipients = [user.ap_id | User.following(user)]
+
+    first_page_last =
+      ActivityPub.fetch_activities(recipients, opts) |> Enum.reverse() |> List.last()
+
+    second_page_last =
+      ActivityPub.fetch_activities(recipients, Map.put(opts, "max_id", first_page_last.id))
+      |> Enum.reverse()
+      |> List.last()
+
+    third_page_last =
+      ActivityPub.fetch_activities(recipients, Map.put(opts, "max_id", second_page_last.id))
+      |> Enum.reverse()
+      |> List.last()
+
+    forth_page_last =
+      ActivityPub.fetch_activities(recipients, Map.put(opts, "max_id", third_page_last.id))
+      |> Enum.reverse()
+      |> List.last()
+
+    Benchee.run(
+      %{
+        "home timeline" => fn opts -> ActivityPub.fetch_activities(recipients, opts) end
+      },
+      inputs: %{
+        "1 page" => opts,
+        "2 page" => Map.put(opts, "max_id", first_page_last.id),
+        "3 page" => Map.put(opts, "max_id", second_page_last.id),
+        "4 page" => Map.put(opts, "max_id", third_page_last.id),
+        "5 page" => Map.put(opts, "max_id", forth_page_last.id),
+        "1 page only media" => Map.put(opts, "only_media", "true"),
+        "2 page only media" =>
+          Map.put(opts, "max_id", first_page_last.id) |> Map.put("only_media", "true"),
+        "3 page only media" =>
+          Map.put(opts, "max_id", second_page_last.id) |> Map.put("only_media", "true"),
+        "4 page only media" =>
+          Map.put(opts, "max_id", third_page_last.id) |> Map.put("only_media", "true"),
+        "5 page only media" =>
+          Map.put(opts, "max_id", forth_page_last.id) |> Map.put("only_media", "true")
+      },
+      formatters: formatters()
+    )
+  end
+
+  defp opts_for_direct_timeline(user) do
+    %{
+      :visibility => "direct",
+      "blocking_user" => user,
+      "count" => "20",
+      "type" => "Create",
+      "user" => user,
+      "with_muted" => "true"
+    }
+  end
+
+  defp fetch_direct_timeline(user) do
+    recipients = [user.ap_id]
+
+    opts = opts_for_direct_timeline(user)
+
+    first_page_last =
+      recipients
+      |> ActivityPub.fetch_activities_query(opts)
+      |> Pagination.fetch_paginated(opts)
+      |> List.last()
+
+    opts2 = Map.put(opts, "max_id", first_page_last.id)
+
+    second_page_last =
+      recipients
+      |> ActivityPub.fetch_activities_query(opts2)
+      |> Pagination.fetch_paginated(opts2)
+      |> List.last()
+
+    opts3 = Map.put(opts, "max_id", second_page_last.id)
+
+    third_page_last =
+      recipients
+      |> ActivityPub.fetch_activities_query(opts3)
+      |> Pagination.fetch_paginated(opts3)
+      |> List.last()
+
+    opts4 = Map.put(opts, "max_id", third_page_last.id)
+
+    forth_page_last =
+      recipients
+      |> ActivityPub.fetch_activities_query(opts4)
+      |> Pagination.fetch_paginated(opts4)
+      |> List.last()
+
+    Benchee.run(
+      %{
+        "direct timeline" => fn opts ->
+          ActivityPub.fetch_activities_query(recipients, opts) |> Pagination.fetch_paginated(opts)
+        end
+      },
+      inputs: %{
+        "1 page" => opts,
+        "2 page" => opts2,
+        "3 page" => opts3,
+        "4 page" => opts4,
+        "5 page" => Map.put(opts4, "max_id", forth_page_last.id)
+      },
+      formatters: formatters()
+    )
+  end
+
+  defp opts_for_public_timeline(user) do
+    %{
       "type" => ["Create", "Announce"],
-      "with_muted" => "true",
+      "local_only" => false,
       "blocking_user" => user,
       "muting_user" => user
     }
+  end
 
-    mastodon_federated_timeline_params = %{
-      "count" => 20,
-      "only_media" => "false",
+  defp opts_for_public_timeline(user, :local) do
+    %{
       "type" => ["Create", "Announce"],
-      "with_muted" => "true",
+      "local_only" => true,
       "blocking_user" => user,
       "muting_user" => user
     }
+  end
 
-    following = User.following(user)
-
-    Benchee.run(%{
-      "User home timeline" => fn ->
-        Pleroma.Web.ActivityPub.ActivityPub.fetch_activities(
-          following,
-          home_timeline_params
-        )
-      end,
-      "User mastodon public timeline" => fn ->
-        Pleroma.Web.ActivityPub.ActivityPub.fetch_public_activities(
-          mastodon_public_timeline_params
-        )
-      end,
-      "User mastodon federated public timeline" => fn ->
-        Pleroma.Web.ActivityPub.ActivityPub.fetch_public_activities(
-          mastodon_federated_timeline_params
-        )
-      end
-    })
-
-    home_activities =
-      Pleroma.Web.ActivityPub.ActivityPub.fetch_activities(
-        following,
-        home_timeline_params
-      )
+  defp opts_for_public_timeline(user, :tag) do
+    %{
+      "blocking_user" => user,
+      "count" => "20",
+      "local_only" => nil,
+      "muting_user" => user,
+      "tag" => ["tag"],
+      "tag_all" => [],
+      "tag_reject" => [],
+      "type" => "Create",
+      "user" => user,
+      "with_muted" => "true"
+    }
+  end
 
-    public_activities =
-      Pleroma.Web.ActivityPub.ActivityPub.fetch_public_activities(mastodon_public_timeline_params)
+  defp fetch_public_timeline(user) do
+    opts = opts_for_public_timeline(user)
 
-    public_federated_activities =
-      Pleroma.Web.ActivityPub.ActivityPub.fetch_public_activities(
-        mastodon_federated_timeline_params
-      )
+    fetch_public_timeline(opts, "public timeline")
+  end
 
-    Benchee.run(%{
-      "Rendering home timeline" => fn ->
-        Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{
-          activities: home_activities,
-          for: user,
-          as: :activity
-        })
-      end,
-      "Rendering public timeline" => fn ->
-        Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{
-          activities: public_activities,
-          for: user,
-          as: :activity
-        })
-      end,
-      "Rendering public federated timeline" => fn ->
-        Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{
-          activities: public_federated_activities,
-          for: user,
-          as: :activity
-        })
-      end
-    })
-  end
-
-  def query_notifications(user) do
-    without_muted_params = %{"count" => "20", "with_muted" => "false"}
-    with_muted_params = %{"count" => "20", "with_muted" => "true"}
-
-    Benchee.run(%{
-      "Notifications without muted" => fn ->
-        Pleroma.Web.MastodonAPI.MastodonAPI.get_notifications(user, without_muted_params)
-      end,
-      "Notifications with muted" => fn ->
-        Pleroma.Web.MastodonAPI.MastodonAPI.get_notifications(user, with_muted_params)
-      end
-    })
-
-    without_muted_notifications =
-      Pleroma.Web.MastodonAPI.MastodonAPI.get_notifications(user, without_muted_params)
-
-    with_muted_notifications =
-      Pleroma.Web.MastodonAPI.MastodonAPI.get_notifications(user, with_muted_params)
-
-    Benchee.run(%{
-      "Render notifications without muted" => fn ->
-        Pleroma.Web.MastodonAPI.NotificationView.render("index.json", %{
-          notifications: without_muted_notifications,
-          for: user
-        })
-      end,
-      "Render notifications with muted" => fn ->
-        Pleroma.Web.MastodonAPI.NotificationView.render("index.json", %{
-          notifications: with_muted_notifications,
-          for: user
-        })
-      end
-    })
-  end
-
-  def query_dms(user) do
-    params = %{
-      "count" => "20",
-      "with_muted" => "true",
-      "type" => "Create",
+  defp fetch_public_timeline(user, :local) do
+    opts = opts_for_public_timeline(user, :local)
+
+    fetch_public_timeline(opts, "public timeline only local")
+  end
+
+  defp fetch_public_timeline(user, :tag) do
+    opts = opts_for_public_timeline(user, :tag)
+
+    fetch_public_timeline(opts, "hashtag timeline")
+  end
+
+  defp fetch_public_timeline(user, :only_media) do
+    opts = opts_for_public_timeline(user) |> Map.put("only_media", "true")
+
+    fetch_public_timeline(opts, "public timeline only media")
+  end
+
+  defp fetch_public_timeline(opts, title) when is_binary(title) do
+    first_page_last = ActivityPub.fetch_public_activities(opts) |> List.last()
+
+    second_page_last =
+      ActivityPub.fetch_public_activities(Map.put(opts, "max_id", first_page_last.id))
+      |> List.last()
+
+    third_page_last =
+      ActivityPub.fetch_public_activities(Map.put(opts, "max_id", second_page_last.id))
+      |> List.last()
+
+    forth_page_last =
+      ActivityPub.fetch_public_activities(Map.put(opts, "max_id", third_page_last.id))
+      |> List.last()
+
+    Benchee.run(
+      %{
+        title => fn opts ->
+          ActivityPub.fetch_public_activities(opts)
+        end
+      },
+      inputs: %{
+        "1 page" => opts,
+        "2 page" => Map.put(opts, "max_id", first_page_last.id),
+        "3 page" => Map.put(opts, "max_id", second_page_last.id),
+        "4 page" => Map.put(opts, "max_id", third_page_last.id),
+        "5 page" => Map.put(opts, "max_id", forth_page_last.id)
+      },
+      formatters: formatters()
+    )
+  end
+
+  defp opts_for_notifications do
+    %{"count" => "20", "with_muted" => "true"}
+  end
+
+  defp fetch_notifications(user) do
+    opts = opts_for_notifications()
+
+    first_page_last = MastodonAPI.get_notifications(user, opts) |> List.last()
+
+    second_page_last =
+      MastodonAPI.get_notifications(user, Map.put(opts, "max_id", first_page_last.id))
+      |> List.last()
+
+    third_page_last =
+      MastodonAPI.get_notifications(user, Map.put(opts, "max_id", second_page_last.id))
+      |> List.last()
+
+    forth_page_last =
+      MastodonAPI.get_notifications(user, Map.put(opts, "max_id", third_page_last.id))
+      |> List.last()
+
+    Benchee.run(
+      %{
+        "Notifications" => fn opts ->
+          MastodonAPI.get_notifications(user, opts)
+        end
+      },
+      inputs: %{
+        "1 page" => opts,
+        "2 page" => Map.put(opts, "max_id", first_page_last.id),
+        "3 page" => Map.put(opts, "max_id", second_page_last.id),
+        "4 page" => Map.put(opts, "max_id", third_page_last.id),
+        "5 page" => Map.put(opts, "max_id", forth_page_last.id)
+      },
+      formatters: formatters()
+    )
+  end
+
+  defp fetch_favourites(user) do
+    first_page_last = ActivityPub.fetch_favourites(user) |> List.last()
+
+    second_page_last =
+      ActivityPub.fetch_favourites(user, %{"max_id" => first_page_last.id}) |> List.last()
+
+    third_page_last =
+      ActivityPub.fetch_favourites(user, %{"max_id" => second_page_last.id}) |> List.last()
+
+    forth_page_last =
+      ActivityPub.fetch_favourites(user, %{"max_id" => third_page_last.id}) |> List.last()
+
+    Benchee.run(
+      %{
+        "Favourites" => fn opts ->
+          ActivityPub.fetch_favourites(user, opts)
+        end
+      },
+      inputs: %{
+        "1 page" => %{},
+        "2 page" => %{"max_id" => first_page_last.id},
+        "3 page" => %{"max_id" => second_page_last.id},
+        "4 page" => %{"max_id" => third_page_last.id},
+        "5 page" => %{"max_id" => forth_page_last.id}
+      },
+      formatters: formatters()
+    )
+  end
+
+  defp opts_for_long_thread(user) do
+    %{
       "blocking_user" => user,
-      "user" => user,
-      visibility: "direct"
+      "user" => user
     }
+  end
 
-    Benchee.run(%{
-      "Direct messages with muted" => fn ->
-        Pleroma.Web.ActivityPub.ActivityPub.fetch_activities_query([user.ap_id], params)
-        |> Pleroma.Pagination.fetch_paginated(params)
-      end,
-      "Direct messages without muted" => fn ->
-        Pleroma.Web.ActivityPub.ActivityPub.fetch_activities_query([user.ap_id], params)
-        |> Pleroma.Pagination.fetch_paginated(Map.put(params, "with_muted", false))
-      end
-    })
-
-    dms_with_muted =
-      Pleroma.Web.ActivityPub.ActivityPub.fetch_activities_query([user.ap_id], params)
-      |> Pleroma.Pagination.fetch_paginated(params)
-
-    dms_without_muted =
-      Pleroma.Web.ActivityPub.ActivityPub.fetch_activities_query([user.ap_id], params)
-      |> Pleroma.Pagination.fetch_paginated(Map.put(params, "with_muted", false))
-
-    Benchee.run(%{
-      "Rendering dms with muted" => fn ->
-        Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{
-          activities: dms_with_muted,
-          for: user,
-          as: :activity
-        })
-      end,
-      "Rendering dms without muted" => fn ->
-        Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{
-          activities: dms_without_muted,
-          for: user,
-          as: :activity
-        })
-      end
-    })
-  end
-
-  def query_long_thread(user, activity) do
-    Benchee.run(%{
-      "Fetch main post" => fn ->
-        Pleroma.Activity.get_by_id_with_object(activity.id)
-      end,
-      "Fetch context of main post" => fn ->
-        Pleroma.Web.ActivityPub.ActivityPub.fetch_activities_for_context(
-          activity.data["context"],
-          %{
-            "blocking_user" => user,
-            "user" => user,
-            "exclude_id" => activity.id
-          }
-        )
-      end
-    })
-
-    activity = Pleroma.Activity.get_by_id_with_object(activity.id)
-
-    context =
-      Pleroma.Web.ActivityPub.ActivityPub.fetch_activities_for_context(
-        activity.data["context"],
-        %{
-          "blocking_user" => user,
-          "user" => user,
-          "exclude_id" => activity.id
-        }
+  defp fetch_long_thread(user) do
+    %{public_thread: public, private_thread: private} =
+      Agent.get(:benchmark_state, fn state -> state end)
+
+    opts = opts_for_long_thread(user)
+
+    private_input = {private.data["context"], Map.put(opts, "exclude_id", private.id)}
+
+    public_input = {public.data["context"], Map.put(opts, "exclude_id", public.id)}
+
+    Benchee.run(
+      %{
+        "fetch context" => fn {context, opts} ->
+          ActivityPub.fetch_activities_for_context(context, opts)
+        end
+      },
+      inputs: %{
+        "Private long thread" => private_input,
+        "Public long thread" => public_input
+      },
+      formatters: formatters()
+    )
+  end
+
+  defp render_timelines(user) do
+    opts = opts_for_home_timeline(user)
+
+    recipients = [user.ap_id | User.following(user)]
+
+    home_activities = ActivityPub.fetch_activities(recipients, opts) |> Enum.reverse()
+
+    recipients = [user.ap_id]
+
+    opts = opts_for_direct_timeline(user)
+
+    direct_activities =
+      recipients
+      |> ActivityPub.fetch_activities_query(opts)
+      |> Pagination.fetch_paginated(opts)
+
+    opts = opts_for_public_timeline(user)
+
+    public_activities = ActivityPub.fetch_public_activities(opts)
+
+    opts = opts_for_public_timeline(user, :tag)
+
+    tag_activities = ActivityPub.fetch_public_activities(opts)
+
+    opts = opts_for_notifications()
+
+    notifications = MastodonAPI.get_notifications(user, opts)
+
+    favourites = ActivityPub.fetch_favourites(user)
+
+    output_relationships =
+      !!Pleroma.Config.get([:extensions, :output_relationships_in_statuses_by_default])
+
+    Benchee.run(
+      %{
+        "Rendering home timeline" => fn ->
+          StatusView.render("index.json", %{
+            activities: home_activities,
+            for: user,
+            as: :activity,
+            skip_relationships: !output_relationships
+          })
+        end,
+        "Rendering direct timeline" => fn ->
+          StatusView.render("index.json", %{
+            activities: direct_activities,
+            for: user,
+            as: :activity,
+            skip_relationships: !output_relationships
+          })
+        end,
+        "Rendering public timeline" => fn ->
+          StatusView.render("index.json", %{
+            activities: public_activities,
+            for: user,
+            as: :activity,
+            skip_relationships: !output_relationships
+          })
+        end,
+        "Rendering tag timeline" => fn ->
+          StatusView.render("index.json", %{
+            activities: tag_activities,
+            for: user,
+            as: :activity,
+            skip_relationships: !output_relationships
+          })
+        end,
+        "Rendering notifications" => fn ->
+          Pleroma.Web.MastodonAPI.NotificationView.render("index.json", %{
+            notifications: notifications,
+            for: user,
+            skip_relationships: !output_relationships
+          })
+        end,
+        "Rendering favourites timeline" => fn ->
+          StatusView.render("index.json", %{
+            activities: favourites,
+            for: user,
+            as: :activity,
+            skip_relationships: !output_relationships
+          })
+        end
+      },
+      formatters: formatters()
+    )
+  end
+
+  defp render_long_thread(user) do
+    %{public_thread: public, private_thread: private} =
+      Agent.get(:benchmark_state, fn state -> state end)
+
+    opts = %{for: user}
+    public_activity = Activity.get_by_id_with_object(public.id)
+    private_activity = Activity.get_by_id_with_object(private.id)
+
+    Benchee.run(
+      %{
+        "render" => fn opts ->
+          StatusView.render("show.json", opts)
+        end
+      },
+      inputs: %{
+        "Public root" => Map.put(opts, :activity, public_activity),
+        "Private root" => Map.put(opts, :activity, private_activity)
+      },
+      formatters: formatters()
+    )
+
+    fetch_opts = opts_for_long_thread(user)
+
+    public_context =
+      ActivityPub.fetch_activities_for_context(
+        public.data["context"],
+        Map.put(fetch_opts, "exclude_id", public.id)
       )
 
-    Benchee.run(%{
-      "Render status" => fn ->
-        Pleroma.Web.MastodonAPI.StatusView.render("show.json", %{
-          activity: activity,
-          for: user
-        })
-      end,
-      "Render context" => fn ->
-        Pleroma.Web.MastodonAPI.StatusView.render(
-          "index.json",
-          for: user,
-          activities: context,
-          as: :activity
-        )
-        |> Enum.reverse()
-      end
-    })
+    private_context =
+      ActivityPub.fetch_activities_for_context(
+        private.data["context"],
+        Map.put(fetch_opts, "exclude_id", private.id)
+      )
+
+    Benchee.run(
+      %{
+        "render" => fn opts ->
+          StatusView.render("context.json", opts)
+        end
+      },
+      inputs: %{
+        "Public context" => %{user: user, activity: public_activity, activities: public_context},
+        "Private context" => %{
+          user: user,
+          activity: private_activity,
+          activities: private_context
+        }
+      },
+      formatters: formatters()
+    )
+  end
+
+  defp fetch_timelines_with_reply_filtering(user) do
+    public_params = opts_for_public_timeline(user)
+
+    Benchee.run(
+      %{
+        "Public timeline without reply filtering" => fn ->
+          ActivityPub.fetch_public_activities(public_params)
+        end,
+        "Public timeline with reply filtering - following" => fn ->
+          public_params
+          |> Map.put("reply_visibility", "following")
+          |> Map.put("reply_filtering_user", user)
+          |> ActivityPub.fetch_public_activities()
+        end,
+        "Public timeline with reply filtering - self" => fn ->
+          public_params
+          |> Map.put("reply_visibility", "self")
+          |> Map.put("reply_filtering_user", user)
+          |> ActivityPub.fetch_public_activities()
+        end
+      },
+      formatters: formatters()
+    )
+
+    private_params = opts_for_home_timeline(user)
+
+    recipients = [user.ap_id | User.following(user)]
+
+    Benchee.run(
+      %{
+        "Home timeline without reply filtering" => fn ->
+          ActivityPub.fetch_activities(recipients, private_params)
+        end,
+        "Home timeline with reply filtering - following" => fn ->
+          private_params =
+            private_params
+            |> Map.put("reply_filtering_user", user)
+            |> Map.put("reply_visibility", "following")
+
+          ActivityPub.fetch_activities(recipients, private_params)
+        end,
+        "Home timeline with reply filtering - self" => fn ->
+          private_params =
+            private_params
+            |> Map.put("reply_filtering_user", user)
+            |> Map.put("reply_visibility", "self")
+
+          ActivityPub.fetch_activities(recipients, private_params)
+        end
+      },
+      formatters: formatters()
+    )
   end
 end