The old approach required adding a special virtual field to any table potentially needing such foreign-id pagination and also still required manually sorting according to pagiantion settings since the pagination helper does not know whether this virtual field was set or not. Using lists with each entry containing the pagination id and the actual entry insterad allows any table to use this mechanism unchanged and does not require manually sorting. Since it was unused, this also drops the pagination mode paramter from fetch_favourited_with_fav_id. Furthermore, as a side effect of this change a bug in the favourite benchmark is fixed. It used to incorrectly attempt to use IDs of the liked objects for pagination instead of the like IDs as advertised in Link headers.
625 lines
17 KiB
Elixir
625 lines
17 KiB
Elixir
defmodule Pleroma.LoadTesting.Fetcher do
|
|
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
|
|
|
|
@spec run_benchmarks(User.t()) :: any()
|
|
def run_benchmarks(user) do
|
|
fetch_user(user)
|
|
fetch_timelines(user)
|
|
render_views(user)
|
|
end
|
|
|
|
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 create_filter(user) do
|
|
Pleroma.Filter.create(%{
|
|
user_id: user.id,
|
|
phrase: "must be filtered",
|
|
hide: true,
|
|
context: ["home"]
|
|
})
|
|
end
|
|
|
|
defp delete_filter(filter), do: Repo.delete(filter)
|
|
|
|
defp fetch_timelines(user) do
|
|
fetch_home_timeline(user)
|
|
fetch_home_timeline_with_filter(user)
|
|
fetch_direct_timeline(user)
|
|
fetch_public_timeline(user)
|
|
fetch_public_timeline_with_filter(user)
|
|
fetch_public_timeline(user, :with_blocks)
|
|
fetch_public_timeline(user, :local)
|
|
fetch_public_timeline(user, :tag)
|
|
fetch_notifications(user)
|
|
fetch_favourited_with_fav_id(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,
|
|
type: ["Create", "Announce"],
|
|
user: user,
|
|
with_muted: true
|
|
}
|
|
end
|
|
|
|
defp fetch_home_timeline(user, title_end \\ "") 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()
|
|
|
|
title = "home timeline " <> title_end
|
|
|
|
Benchee.run(
|
|
%{
|
|
title => 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 fetch_home_timeline_with_filter(user) do
|
|
{:ok, filter} = create_filter(user)
|
|
|
|
fetch_home_timeline(user, "with filters")
|
|
|
|
delete_filter(filter)
|
|
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"],
|
|
local_only: false,
|
|
blocking_user: user,
|
|
muting_user: user
|
|
}
|
|
end
|
|
|
|
defp opts_for_public_timeline(user, :local) do
|
|
%{
|
|
type: ["Create", "Announce"],
|
|
local_only: true,
|
|
blocking_user: user,
|
|
muting_user: user
|
|
}
|
|
end
|
|
|
|
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
|
|
|
|
defp fetch_public_timeline(user) do
|
|
opts = opts_for_public_timeline(user)
|
|
|
|
fetch_public_timeline(opts, "public timeline")
|
|
end
|
|
|
|
defp fetch_public_timeline_with_filter(user) do
|
|
{:ok, filter} = create_filter(user)
|
|
opts = opts_for_public_timeline(user)
|
|
|
|
fetch_public_timeline(opts, "public timeline with filters")
|
|
delete_filter(filter)
|
|
end
|
|
|
|
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(user, :with_blocks) do
|
|
opts = opts_for_public_timeline(user)
|
|
|
|
remote_non_friends = Agent.get(:non_friends_remote, & &1)
|
|
|
|
Benchee.run(%{
|
|
"public timeline without blocks" => fn ->
|
|
ActivityPub.fetch_public_activities(opts)
|
|
end
|
|
})
|
|
|
|
Enum.each(remote_non_friends, fn non_friend ->
|
|
{:ok, _} = User.block(user, non_friend)
|
|
end)
|
|
|
|
user = User.get_by_id(user.id)
|
|
|
|
opts = Map.put(opts, :blocking_user, user)
|
|
|
|
Benchee.run(%{
|
|
"public timeline with user block" => fn ->
|
|
ActivityPub.fetch_public_activities(opts)
|
|
end
|
|
})
|
|
|
|
domains =
|
|
Enum.reduce(remote_non_friends, [], fn non_friend, domains ->
|
|
{:ok, _user} = User.unblock(user, non_friend)
|
|
%{host: host} = URI.parse(non_friend.ap_id)
|
|
[host | domains]
|
|
end)
|
|
|
|
domains = Enum.uniq(domains)
|
|
|
|
Enum.each(domains, fn domain ->
|
|
{:ok, _} = User.block_domain(user, domain)
|
|
end)
|
|
|
|
user = User.get_by_id(user.id)
|
|
opts = Map.put(opts, :blocking_user, user)
|
|
|
|
Benchee.run(%{
|
|
"public timeline with domain block" => fn ->
|
|
ActivityPub.fetch_public_activities(opts)
|
|
end
|
|
})
|
|
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_favourited_with_fav_id(user) |> List.last()
|
|
|
|
second_page_last =
|
|
ActivityPub.fetch_favourited_with_fav_id(user, %{:max_id => first_page_last.id}) |> List.last()
|
|
|
|
third_page_last =
|
|
ActivityPub.fetch_favourited_with_fav_id(user, %{:max_id => second_page_last.id}) |> List.last()
|
|
|
|
forth_page_last =
|
|
ActivityPub.fetch_favourited_with_fav_id(user, %{:max_id => third_page_last.id}) |> List.last()
|
|
|
|
Benchee.run(
|
|
%{
|
|
"Favourites" => fn opts ->
|
|
ActivityPub.fetch_favourited_with_fav_id(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
|
|
}
|
|
end
|
|
|
|
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_keyed = ActivityPub.fetch_favourited_with_fav_id(user)
|
|
favourites = Pagiation.unwrap(favourites_keyed)
|
|
|
|
Benchee.run(
|
|
%{
|
|
"Rendering home timeline" => fn ->
|
|
StatusView.render("index.json", %{
|
|
activities: home_activities,
|
|
for: user,
|
|
as: :activity
|
|
})
|
|
end,
|
|
"Rendering direct timeline" => fn ->
|
|
StatusView.render("index.json", %{
|
|
activities: direct_activities,
|
|
for: user,
|
|
as: :activity
|
|
})
|
|
end,
|
|
"Rendering public timeline" => fn ->
|
|
StatusView.render("index.json", %{
|
|
activities: public_activities,
|
|
for: user,
|
|
as: :activity
|
|
})
|
|
end,
|
|
"Rendering tag timeline" => fn ->
|
|
StatusView.render("index.json", %{
|
|
activities: tag_activities,
|
|
for: user,
|
|
as: :activity
|
|
})
|
|
end,
|
|
"Rendering notifications" => fn ->
|
|
Pleroma.Web.MastodonAPI.NotificationView.render("index.json", %{
|
|
notifications: notifications,
|
|
for: user
|
|
})
|
|
end,
|
|
"Rendering favourites timeline" => fn ->
|
|
StatusView.render("index.json", %{
|
|
activities: favourites,
|
|
for: user,
|
|
as: :activity
|
|
})
|
|
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)
|
|
)
|
|
|
|
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
|