607b7d4cb662c01718115f97f5767a13f870cf20
[anni] / benchmarks / load_testing / fetcher.ex
1 defmodule Pleroma.LoadTesting.Fetcher do
2   alias Pleroma.Activity
3   alias Pleroma.Pagination
4   alias Pleroma.Repo
5   alias Pleroma.User
6   alias Pleroma.Web.ActivityPub.ActivityPub
7   alias Pleroma.Web.MastodonAPI.MastodonAPI
8   alias Pleroma.Web.MastodonAPI.StatusView
9
10   @spec run_benchmarks(User.t()) :: any()
11   def run_benchmarks(user) do
12     fetch_user(user)
13     fetch_timelines(user)
14     render_views(user)
15   end
16
17   defp formatters do
18     [
19       Benchee.Formatters.Console
20     ]
21   end
22
23   defp fetch_user(user) do
24     Benchee.run(
25       %{
26         "By id" => fn -> Repo.get_by(User, id: user.id) end,
27         "By ap_id" => fn -> Repo.get_by(User, ap_id: user.ap_id) end,
28         "By email" => fn -> Repo.get_by(User, email: user.email) end,
29         "By nickname" => fn -> Repo.get_by(User, nickname: user.nickname) end
30       },
31       formatters: formatters()
32     )
33   end
34
35   defp create_filter(user) do
36     Pleroma.Filter.create(%{
37       user_id: user.id,
38       phrase: "must be filtered",
39       hide: true,
40       context: ["home"]
41     })
42   end
43
44   defp delete_filter(filter), do: Repo.delete(filter)
45
46   defp fetch_timelines(user) do
47     fetch_home_timeline(user)
48     fetch_home_timeline_with_filter(user)
49     fetch_direct_timeline(user)
50     fetch_public_timeline(user)
51     fetch_public_timeline_with_filter(user)
52     fetch_public_timeline(user, :with_blocks)
53     fetch_public_timeline(user, :local)
54     fetch_public_timeline(user, :tag)
55     fetch_notifications(user)
56     fetch_favourites(user)
57     fetch_long_thread(user)
58     fetch_timelines_with_reply_filtering(user)
59   end
60
61   defp render_views(user) do
62     render_timelines(user)
63     render_long_thread(user)
64   end
65
66   defp opts_for_home_timeline(user) do
67     %{
68       blocking_user: user,
69       count: "20",
70       muting_user: user,
71       type: ["Create", "Announce"],
72       user: user,
73       with_muted: true
74     }
75   end
76
77   defp fetch_home_timeline(user, title_end \\ "") do
78     opts = opts_for_home_timeline(user)
79
80     recipients = [user.ap_id | User.following(user)]
81
82     first_page_last =
83       ActivityPub.fetch_activities(recipients, opts) |> Enum.reverse() |> List.last()
84
85     second_page_last =
86       ActivityPub.fetch_activities(recipients, Map.put(opts, :max_id, first_page_last.id))
87       |> Enum.reverse()
88       |> List.last()
89
90     third_page_last =
91       ActivityPub.fetch_activities(recipients, Map.put(opts, :max_id, second_page_last.id))
92       |> Enum.reverse()
93       |> List.last()
94
95     forth_page_last =
96       ActivityPub.fetch_activities(recipients, Map.put(opts, :max_id, third_page_last.id))
97       |> Enum.reverse()
98       |> List.last()
99
100     title = "home timeline " <> title_end
101
102     Benchee.run(
103       %{
104         title => fn opts -> ActivityPub.fetch_activities(recipients, opts) end
105       },
106       inputs: %{
107         "1 page" => opts,
108         "2 page" => Map.put(opts, :max_id, first_page_last.id),
109         "3 page" => Map.put(opts, :max_id, second_page_last.id),
110         "4 page" => Map.put(opts, :max_id, third_page_last.id),
111         "5 page" => Map.put(opts, :max_id, forth_page_last.id),
112         "1 page only media" => Map.put(opts, :only_media, true),
113         "2 page only media" =>
114           Map.put(opts, :max_id, first_page_last.id) |> Map.put(:only_media, true),
115         "3 page only media" =>
116           Map.put(opts, :max_id, second_page_last.id) |> Map.put(:only_media, true),
117         "4 page only media" =>
118           Map.put(opts, :max_id, third_page_last.id) |> Map.put(:only_media, true),
119         "5 page only media" =>
120           Map.put(opts, :max_id, forth_page_last.id) |> Map.put(:only_media, true)
121       },
122       formatters: formatters()
123     )
124   end
125
126   defp fetch_home_timeline_with_filter(user) do
127     {:ok, filter} = create_filter(user)
128
129     fetch_home_timeline(user, "with filters")
130
131     delete_filter(filter)
132   end
133
134   defp opts_for_direct_timeline(user) do
135     %{
136       visibility: "direct",
137       blocking_user: user,
138       count: "20",
139       type: "Create",
140       user: user,
141       with_muted: true
142     }
143   end
144
145   defp fetch_direct_timeline(user) do
146     recipients = [user.ap_id]
147
148     opts = opts_for_direct_timeline(user)
149
150     first_page_last =
151       recipients
152       |> ActivityPub.fetch_activities_query(opts)
153       |> Pagination.fetch_paginated(opts)
154       |> List.last()
155
156     opts2 = Map.put(opts, :max_id, first_page_last.id)
157
158     second_page_last =
159       recipients
160       |> ActivityPub.fetch_activities_query(opts2)
161       |> Pagination.fetch_paginated(opts2)
162       |> List.last()
163
164     opts3 = Map.put(opts, :max_id, second_page_last.id)
165
166     third_page_last =
167       recipients
168       |> ActivityPub.fetch_activities_query(opts3)
169       |> Pagination.fetch_paginated(opts3)
170       |> List.last()
171
172     opts4 = Map.put(opts, :max_id, third_page_last.id)
173
174     forth_page_last =
175       recipients
176       |> ActivityPub.fetch_activities_query(opts4)
177       |> Pagination.fetch_paginated(opts4)
178       |> List.last()
179
180     Benchee.run(
181       %{
182         "direct timeline" => fn opts ->
183           ActivityPub.fetch_activities_query(recipients, opts) |> Pagination.fetch_paginated(opts)
184         end
185       },
186       inputs: %{
187         "1 page" => opts,
188         "2 page" => opts2,
189         "3 page" => opts3,
190         "4 page" => opts4,
191         "5 page" => Map.put(opts4, :max_id, forth_page_last.id)
192       },
193       formatters: formatters()
194     )
195   end
196
197   defp opts_for_public_timeline(user) do
198     %{
199       type: ["Create", "Announce"],
200       local_only: false,
201       blocking_user: user,
202       muting_user: user
203     }
204   end
205
206   defp opts_for_public_timeline(user, :local) do
207     %{
208       type: ["Create", "Announce"],
209       local_only: true,
210       blocking_user: user,
211       muting_user: user
212     }
213   end
214
215   defp opts_for_public_timeline(user, :tag) do
216     %{
217       blocking_user: user,
218       count: "20",
219       local_only: nil,
220       muting_user: user,
221       tag: ["tag"],
222       tag_all: [],
223       tag_reject: [],
224       type: "Create",
225       user: user,
226       with_muted: true
227     }
228   end
229
230   defp fetch_public_timeline(user) do
231     opts = opts_for_public_timeline(user)
232
233     fetch_public_timeline(opts, "public timeline")
234   end
235
236   defp fetch_public_timeline_with_filter(user) do
237     {:ok, filter} = create_filter(user)
238     opts = opts_for_public_timeline(user)
239
240     fetch_public_timeline(opts, "public timeline with filters")
241     delete_filter(filter)
242   end
243
244   defp fetch_public_timeline(user, :local) do
245     opts = opts_for_public_timeline(user, :local)
246
247     fetch_public_timeline(opts, "public timeline only local")
248   end
249
250   defp fetch_public_timeline(user, :tag) do
251     opts = opts_for_public_timeline(user, :tag)
252
253     fetch_public_timeline(opts, "hashtag timeline")
254   end
255
256   defp fetch_public_timeline(user, :only_media) do
257     opts = opts_for_public_timeline(user) |> Map.put(:only_media, true)
258
259     fetch_public_timeline(opts, "public timeline only media")
260   end
261
262   defp fetch_public_timeline(user, :with_blocks) do
263     opts = opts_for_public_timeline(user)
264
265     remote_non_friends = Agent.get(:non_friends_remote, & &1)
266
267     Benchee.run(%{
268       "public timeline without blocks" => fn ->
269         ActivityPub.fetch_public_activities(opts)
270       end
271     })
272
273     Enum.each(remote_non_friends, fn non_friend ->
274       {:ok, _} = User.block(user, non_friend)
275     end)
276
277     user = User.get_by_id(user.id)
278
279     opts = Map.put(opts, :blocking_user, user)
280
281     Benchee.run(%{
282       "public timeline with user block" => fn ->
283         ActivityPub.fetch_public_activities(opts)
284       end
285     })
286
287     domains =
288       Enum.reduce(remote_non_friends, [], fn non_friend, domains ->
289         {:ok, _user} = User.unblock(user, non_friend)
290         %{host: host} = URI.parse(non_friend.ap_id)
291         [host | domains]
292       end)
293
294     domains = Enum.uniq(domains)
295
296     Enum.each(domains, fn domain ->
297       {:ok, _} = User.block_domain(user, domain)
298     end)
299
300     user = User.get_by_id(user.id)
301     opts = Map.put(opts, :blocking_user, user)
302
303     Benchee.run(%{
304       "public timeline with domain block" => fn ->
305         ActivityPub.fetch_public_activities(opts)
306       end
307     })
308   end
309
310   defp fetch_public_timeline(opts, title) when is_binary(title) do
311     first_page_last = ActivityPub.fetch_public_activities(opts) |> List.last()
312
313     second_page_last =
314       ActivityPub.fetch_public_activities(Map.put(opts, :max_id, first_page_last.id))
315       |> List.last()
316
317     third_page_last =
318       ActivityPub.fetch_public_activities(Map.put(opts, :max_id, second_page_last.id))
319       |> List.last()
320
321     forth_page_last =
322       ActivityPub.fetch_public_activities(Map.put(opts, :max_id, third_page_last.id))
323       |> List.last()
324
325     Benchee.run(
326       %{
327         title => fn opts ->
328           ActivityPub.fetch_public_activities(opts)
329         end
330       },
331       inputs: %{
332         "1 page" => opts,
333         "2 page" => Map.put(opts, :max_id, first_page_last.id),
334         "3 page" => Map.put(opts, :max_id, second_page_last.id),
335         "4 page" => Map.put(opts, :max_id, third_page_last.id),
336         "5 page" => Map.put(opts, :max_id, forth_page_last.id)
337       },
338       formatters: formatters()
339     )
340   end
341
342   defp opts_for_notifications do
343     %{count: "20", with_muted: true}
344   end
345
346   defp fetch_notifications(user) do
347     opts = opts_for_notifications()
348
349     first_page_last = MastodonAPI.get_notifications(user, opts) |> List.last()
350
351     second_page_last =
352       MastodonAPI.get_notifications(user, Map.put(opts, :max_id, first_page_last.id))
353       |> List.last()
354
355     third_page_last =
356       MastodonAPI.get_notifications(user, Map.put(opts, :max_id, second_page_last.id))
357       |> List.last()
358
359     forth_page_last =
360       MastodonAPI.get_notifications(user, Map.put(opts, :max_id, third_page_last.id))
361       |> List.last()
362
363     Benchee.run(
364       %{
365         "Notifications" => fn opts ->
366           MastodonAPI.get_notifications(user, opts)
367         end
368       },
369       inputs: %{
370         "1 page" => opts,
371         "2 page" => Map.put(opts, :max_id, first_page_last.id),
372         "3 page" => Map.put(opts, :max_id, second_page_last.id),
373         "4 page" => Map.put(opts, :max_id, third_page_last.id),
374         "5 page" => Map.put(opts, :max_id, forth_page_last.id)
375       },
376       formatters: formatters()
377     )
378   end
379
380   defp fetch_favourites(user) do
381     first_page_last = ActivityPub.fetch_favourites(user) |> List.last()
382
383     second_page_last =
384       ActivityPub.fetch_favourites(user, %{:max_id => first_page_last.id}) |> List.last()
385
386     third_page_last =
387       ActivityPub.fetch_favourites(user, %{:max_id => second_page_last.id}) |> List.last()
388
389     forth_page_last =
390       ActivityPub.fetch_favourites(user, %{:max_id => third_page_last.id}) |> List.last()
391
392     Benchee.run(
393       %{
394         "Favourites" => fn opts ->
395           ActivityPub.fetch_favourites(user, opts)
396         end
397       },
398       inputs: %{
399         "1 page" => %{},
400         "2 page" => %{:max_id => first_page_last.id},
401         "3 page" => %{:max_id => second_page_last.id},
402         "4 page" => %{:max_id => third_page_last.id},
403         "5 page" => %{:max_id => forth_page_last.id}
404       },
405       formatters: formatters()
406     )
407   end
408
409   defp opts_for_long_thread(user) do
410     %{
411       blocking_user: user,
412       user: user
413     }
414   end
415
416   defp fetch_long_thread(user) do
417     %{public_thread: public, private_thread: private} =
418       Agent.get(:benchmark_state, fn state -> state end)
419
420     opts = opts_for_long_thread(user)
421
422     private_input = {private.data["context"], Map.put(opts, :exclude_id, private.id)}
423
424     public_input = {public.data["context"], Map.put(opts, :exclude_id, public.id)}
425
426     Benchee.run(
427       %{
428         "fetch context" => fn {context, opts} ->
429           ActivityPub.fetch_activities_for_context(context, opts)
430         end
431       },
432       inputs: %{
433         "Private long thread" => private_input,
434         "Public long thread" => public_input
435       },
436       formatters: formatters()
437     )
438   end
439
440   defp render_timelines(user) do
441     opts = opts_for_home_timeline(user)
442
443     recipients = [user.ap_id | User.following(user)]
444
445     home_activities = ActivityPub.fetch_activities(recipients, opts) |> Enum.reverse()
446
447     recipients = [user.ap_id]
448
449     opts = opts_for_direct_timeline(user)
450
451     direct_activities =
452       recipients
453       |> ActivityPub.fetch_activities_query(opts)
454       |> Pagination.fetch_paginated(opts)
455
456     opts = opts_for_public_timeline(user)
457
458     public_activities = ActivityPub.fetch_public_activities(opts)
459
460     opts = opts_for_public_timeline(user, :tag)
461
462     tag_activities = ActivityPub.fetch_public_activities(opts)
463
464     opts = opts_for_notifications()
465
466     notifications = MastodonAPI.get_notifications(user, opts)
467
468     favourites = ActivityPub.fetch_favourites(user)
469
470     Benchee.run(
471       %{
472         "Rendering home timeline" => fn ->
473           StatusView.render("index.json", %{
474             activities: home_activities,
475             for: user,
476             as: :activity
477           })
478         end,
479         "Rendering direct timeline" => fn ->
480           StatusView.render("index.json", %{
481             activities: direct_activities,
482             for: user,
483             as: :activity
484           })
485         end,
486         "Rendering public timeline" => fn ->
487           StatusView.render("index.json", %{
488             activities: public_activities,
489             for: user,
490             as: :activity
491           })
492         end,
493         "Rendering tag timeline" => fn ->
494           StatusView.render("index.json", %{
495             activities: tag_activities,
496             for: user,
497             as: :activity
498           })
499         end,
500         "Rendering notifications" => fn ->
501           Pleroma.Web.MastodonAPI.NotificationView.render("index.json", %{
502             notifications: notifications,
503             for: user
504           })
505         end,
506         "Rendering favourites timeline" => fn ->
507           StatusView.render("index.json", %{
508             activities: favourites,
509             for: user,
510             as: :activity
511           })
512         end
513       },
514       formatters: formatters()
515     )
516   end
517
518   defp render_long_thread(user) do
519     %{public_thread: public, private_thread: private} =
520       Agent.get(:benchmark_state, fn state -> state end)
521
522     opts = %{for: user}
523     public_activity = Activity.get_by_id_with_object(public.id)
524     private_activity = Activity.get_by_id_with_object(private.id)
525
526     Benchee.run(
527       %{
528         "render" => fn opts ->
529           StatusView.render("show.json", opts)
530         end
531       },
532       inputs: %{
533         "Public root" => Map.put(opts, :activity, public_activity),
534         "Private root" => Map.put(opts, :activity, private_activity)
535       },
536       formatters: formatters()
537     )
538
539     fetch_opts = opts_for_long_thread(user)
540
541     public_context =
542       ActivityPub.fetch_activities_for_context(
543         public.data["context"],
544         Map.put(fetch_opts, :exclude_id, public.id)
545       )
546
547     private_context =
548       ActivityPub.fetch_activities_for_context(
549         private.data["context"],
550         Map.put(fetch_opts, :exclude_id, private.id)
551       )
552
553     Benchee.run(
554       %{
555         "render" => fn opts ->
556           StatusView.render("context.json", opts)
557         end
558       },
559       inputs: %{
560         "Public context" => %{user: user, activity: public_activity, activities: public_context},
561         "Private context" => %{
562           user: user,
563           activity: private_activity,
564           activities: private_context
565         }
566       },
567       formatters: formatters()
568     )
569   end
570
571   defp fetch_timelines_with_reply_filtering(user) do
572     public_params = opts_for_public_timeline(user)
573
574     Benchee.run(
575       %{
576         "Public timeline without reply filtering" => fn ->
577           ActivityPub.fetch_public_activities(public_params)
578         end,
579         "Public timeline with reply filtering - following" => fn ->
580           public_params
581           |> Map.put(:reply_visibility, "following")
582           |> Map.put(:reply_filtering_user, user)
583           |> ActivityPub.fetch_public_activities()
584         end,
585         "Public timeline with reply filtering - self" => fn ->
586           public_params
587           |> Map.put(:reply_visibility, "self")
588           |> Map.put(:reply_filtering_user, user)
589           |> ActivityPub.fetch_public_activities()
590         end
591       },
592       formatters: formatters()
593     )
594
595     private_params = opts_for_home_timeline(user)
596
597     recipients = [user.ap_id | User.following(user)]
598
599     Benchee.run(
600       %{
601         "Home timeline without reply filtering" => fn ->
602           ActivityPub.fetch_activities(recipients, private_params)
603         end,
604         "Home timeline with reply filtering - following" => fn ->
605           private_params =
606             private_params
607             |> Map.put(:reply_filtering_user, user)
608             |> Map.put(:reply_visibility, "following")
609
610           ActivityPub.fetch_activities(recipients, private_params)
611         end,
612         "Home timeline with reply filtering - self" => fn ->
613           private_params =
614             private_params
615             |> Map.put(:reply_filtering_user, user)
616             |> Map.put(:reply_visibility, "self")
617
618           ActivityPub.fetch_activities(recipients, private_params)
619         end
620       },
621       formatters: formatters()
622     )
623   end
624 end