Merge branch 'bugfix/announce-race-condition' into 'develop'
[akkoma] / 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 fetch_timelines(user) do
36 fetch_home_timeline(user)
37 fetch_direct_timeline(user)
38 fetch_public_timeline(user)
39 fetch_public_timeline(user, :local)
40 fetch_public_timeline(user, :tag)
41 fetch_notifications(user)
42 fetch_favourites(user)
43 fetch_long_thread(user)
44 fetch_timelines_with_reply_filtering(user)
45 end
46
47 defp render_views(user) do
48 render_timelines(user)
49 render_long_thread(user)
50 end
51
52 defp opts_for_home_timeline(user) do
53 %{
54 "blocking_user" => user,
55 "count" => "20",
56 "muting_user" => user,
57 "type" => ["Create", "Announce"],
58 "user" => user,
59 "with_muted" => "true"
60 }
61 end
62
63 defp fetch_home_timeline(user) do
64 opts = opts_for_home_timeline(user)
65
66 recipients = [user.ap_id | User.following(user)]
67
68 first_page_last =
69 ActivityPub.fetch_activities(recipients, opts) |> Enum.reverse() |> List.last()
70
71 second_page_last =
72 ActivityPub.fetch_activities(recipients, Map.put(opts, "max_id", first_page_last.id))
73 |> Enum.reverse()
74 |> List.last()
75
76 third_page_last =
77 ActivityPub.fetch_activities(recipients, Map.put(opts, "max_id", second_page_last.id))
78 |> Enum.reverse()
79 |> List.last()
80
81 forth_page_last =
82 ActivityPub.fetch_activities(recipients, Map.put(opts, "max_id", third_page_last.id))
83 |> Enum.reverse()
84 |> List.last()
85
86 Benchee.run(
87 %{
88 "home timeline" => fn opts -> ActivityPub.fetch_activities(recipients, opts) end
89 },
90 inputs: %{
91 "1 page" => opts,
92 "2 page" => Map.put(opts, "max_id", first_page_last.id),
93 "3 page" => Map.put(opts, "max_id", second_page_last.id),
94 "4 page" => Map.put(opts, "max_id", third_page_last.id),
95 "5 page" => Map.put(opts, "max_id", forth_page_last.id),
96 "1 page only media" => Map.put(opts, "only_media", "true"),
97 "2 page only media" =>
98 Map.put(opts, "max_id", first_page_last.id) |> Map.put("only_media", "true"),
99 "3 page only media" =>
100 Map.put(opts, "max_id", second_page_last.id) |> Map.put("only_media", "true"),
101 "4 page only media" =>
102 Map.put(opts, "max_id", third_page_last.id) |> Map.put("only_media", "true"),
103 "5 page only media" =>
104 Map.put(opts, "max_id", forth_page_last.id) |> Map.put("only_media", "true")
105 },
106 formatters: formatters()
107 )
108 end
109
110 defp opts_for_direct_timeline(user) do
111 %{
112 :visibility => "direct",
113 "blocking_user" => user,
114 "count" => "20",
115 "type" => "Create",
116 "user" => user,
117 "with_muted" => "true"
118 }
119 end
120
121 defp fetch_direct_timeline(user) do
122 recipients = [user.ap_id]
123
124 opts = opts_for_direct_timeline(user)
125
126 first_page_last =
127 recipients
128 |> ActivityPub.fetch_activities_query(opts)
129 |> Pagination.fetch_paginated(opts)
130 |> List.last()
131
132 opts2 = Map.put(opts, "max_id", first_page_last.id)
133
134 second_page_last =
135 recipients
136 |> ActivityPub.fetch_activities_query(opts2)
137 |> Pagination.fetch_paginated(opts2)
138 |> List.last()
139
140 opts3 = Map.put(opts, "max_id", second_page_last.id)
141
142 third_page_last =
143 recipients
144 |> ActivityPub.fetch_activities_query(opts3)
145 |> Pagination.fetch_paginated(opts3)
146 |> List.last()
147
148 opts4 = Map.put(opts, "max_id", third_page_last.id)
149
150 forth_page_last =
151 recipients
152 |> ActivityPub.fetch_activities_query(opts4)
153 |> Pagination.fetch_paginated(opts4)
154 |> List.last()
155
156 Benchee.run(
157 %{
158 "direct timeline" => fn opts ->
159 ActivityPub.fetch_activities_query(recipients, opts) |> Pagination.fetch_paginated(opts)
160 end
161 },
162 inputs: %{
163 "1 page" => opts,
164 "2 page" => opts2,
165 "3 page" => opts3,
166 "4 page" => opts4,
167 "5 page" => Map.put(opts4, "max_id", forth_page_last.id)
168 },
169 formatters: formatters()
170 )
171 end
172
173 defp opts_for_public_timeline(user) do
174 %{
175 "type" => ["Create", "Announce"],
176 "local_only" => false,
177 "blocking_user" => user,
178 "muting_user" => user
179 }
180 end
181
182 defp opts_for_public_timeline(user, :local) do
183 %{
184 "type" => ["Create", "Announce"],
185 "local_only" => true,
186 "blocking_user" => user,
187 "muting_user" => user
188 }
189 end
190
191 defp opts_for_public_timeline(user, :tag) do
192 %{
193 "blocking_user" => user,
194 "count" => "20",
195 "local_only" => nil,
196 "muting_user" => user,
197 "tag" => ["tag"],
198 "tag_all" => [],
199 "tag_reject" => [],
200 "type" => "Create",
201 "user" => user,
202 "with_muted" => "true"
203 }
204 end
205
206 defp fetch_public_timeline(user) do
207 opts = opts_for_public_timeline(user)
208
209 fetch_public_timeline(opts, "public timeline")
210 end
211
212 defp fetch_public_timeline(user, :local) do
213 opts = opts_for_public_timeline(user, :local)
214
215 fetch_public_timeline(opts, "public timeline only local")
216 end
217
218 defp fetch_public_timeline(user, :tag) do
219 opts = opts_for_public_timeline(user, :tag)
220
221 fetch_public_timeline(opts, "hashtag timeline")
222 end
223
224 defp fetch_public_timeline(user, :only_media) do
225 opts = opts_for_public_timeline(user) |> Map.put("only_media", "true")
226
227 fetch_public_timeline(opts, "public timeline only media")
228 end
229
230 defp fetch_public_timeline(opts, title) when is_binary(title) do
231 first_page_last = ActivityPub.fetch_public_activities(opts) |> List.last()
232
233 second_page_last =
234 ActivityPub.fetch_public_activities(Map.put(opts, "max_id", first_page_last.id))
235 |> List.last()
236
237 third_page_last =
238 ActivityPub.fetch_public_activities(Map.put(opts, "max_id", second_page_last.id))
239 |> List.last()
240
241 forth_page_last =
242 ActivityPub.fetch_public_activities(Map.put(opts, "max_id", third_page_last.id))
243 |> List.last()
244
245 Benchee.run(
246 %{
247 title => fn opts ->
248 ActivityPub.fetch_public_activities(opts)
249 end
250 },
251 inputs: %{
252 "1 page" => opts,
253 "2 page" => Map.put(opts, "max_id", first_page_last.id),
254 "3 page" => Map.put(opts, "max_id", second_page_last.id),
255 "4 page" => Map.put(opts, "max_id", third_page_last.id),
256 "5 page" => Map.put(opts, "max_id", forth_page_last.id)
257 },
258 formatters: formatters()
259 )
260 end
261
262 defp opts_for_notifications do
263 %{"count" => "20", "with_muted" => "true"}
264 end
265
266 defp fetch_notifications(user) do
267 opts = opts_for_notifications()
268
269 first_page_last = MastodonAPI.get_notifications(user, opts) |> List.last()
270
271 second_page_last =
272 MastodonAPI.get_notifications(user, Map.put(opts, "max_id", first_page_last.id))
273 |> List.last()
274
275 third_page_last =
276 MastodonAPI.get_notifications(user, Map.put(opts, "max_id", second_page_last.id))
277 |> List.last()
278
279 forth_page_last =
280 MastodonAPI.get_notifications(user, Map.put(opts, "max_id", third_page_last.id))
281 |> List.last()
282
283 Benchee.run(
284 %{
285 "Notifications" => fn opts ->
286 MastodonAPI.get_notifications(user, opts)
287 end
288 },
289 inputs: %{
290 "1 page" => opts,
291 "2 page" => Map.put(opts, "max_id", first_page_last.id),
292 "3 page" => Map.put(opts, "max_id", second_page_last.id),
293 "4 page" => Map.put(opts, "max_id", third_page_last.id),
294 "5 page" => Map.put(opts, "max_id", forth_page_last.id)
295 },
296 formatters: formatters()
297 )
298 end
299
300 defp fetch_favourites(user) do
301 first_page_last = ActivityPub.fetch_favourites(user) |> List.last()
302
303 second_page_last =
304 ActivityPub.fetch_favourites(user, %{"max_id" => first_page_last.id}) |> List.last()
305
306 third_page_last =
307 ActivityPub.fetch_favourites(user, %{"max_id" => second_page_last.id}) |> List.last()
308
309 forth_page_last =
310 ActivityPub.fetch_favourites(user, %{"max_id" => third_page_last.id}) |> List.last()
311
312 Benchee.run(
313 %{
314 "Favourites" => fn opts ->
315 ActivityPub.fetch_favourites(user, opts)
316 end
317 },
318 inputs: %{
319 "1 page" => %{},
320 "2 page" => %{"max_id" => first_page_last.id},
321 "3 page" => %{"max_id" => second_page_last.id},
322 "4 page" => %{"max_id" => third_page_last.id},
323 "5 page" => %{"max_id" => forth_page_last.id}
324 },
325 formatters: formatters()
326 )
327 end
328
329 defp opts_for_long_thread(user) do
330 %{
331 "blocking_user" => user,
332 "user" => user
333 }
334 end
335
336 defp fetch_long_thread(user) do
337 %{public_thread: public, private_thread: private} =
338 Agent.get(:benchmark_state, fn state -> state end)
339
340 opts = opts_for_long_thread(user)
341
342 private_input = {private.data["context"], Map.put(opts, "exclude_id", private.id)}
343
344 public_input = {public.data["context"], Map.put(opts, "exclude_id", public.id)}
345
346 Benchee.run(
347 %{
348 "fetch context" => fn {context, opts} ->
349 ActivityPub.fetch_activities_for_context(context, opts)
350 end
351 },
352 inputs: %{
353 "Private long thread" => private_input,
354 "Public long thread" => public_input
355 },
356 formatters: formatters()
357 )
358 end
359
360 defp render_timelines(user) do
361 opts = opts_for_home_timeline(user)
362
363 recipients = [user.ap_id | User.following(user)]
364
365 home_activities = ActivityPub.fetch_activities(recipients, opts) |> Enum.reverse()
366
367 recipients = [user.ap_id]
368
369 opts = opts_for_direct_timeline(user)
370
371 direct_activities =
372 recipients
373 |> ActivityPub.fetch_activities_query(opts)
374 |> Pagination.fetch_paginated(opts)
375
376 opts = opts_for_public_timeline(user)
377
378 public_activities = ActivityPub.fetch_public_activities(opts)
379
380 opts = opts_for_public_timeline(user, :tag)
381
382 tag_activities = ActivityPub.fetch_public_activities(opts)
383
384 opts = opts_for_notifications()
385
386 notifications = MastodonAPI.get_notifications(user, opts)
387
388 favourites = ActivityPub.fetch_favourites(user)
389
390 output_relationships =
391 !!Pleroma.Config.get([:extensions, :output_relationships_in_statuses_by_default])
392
393 Benchee.run(
394 %{
395 "Rendering home timeline" => fn ->
396 StatusView.render("index.json", %{
397 activities: home_activities,
398 for: user,
399 as: :activity,
400 skip_relationships: !output_relationships
401 })
402 end,
403 "Rendering direct timeline" => fn ->
404 StatusView.render("index.json", %{
405 activities: direct_activities,
406 for: user,
407 as: :activity,
408 skip_relationships: !output_relationships
409 })
410 end,
411 "Rendering public timeline" => fn ->
412 StatusView.render("index.json", %{
413 activities: public_activities,
414 for: user,
415 as: :activity,
416 skip_relationships: !output_relationships
417 })
418 end,
419 "Rendering tag timeline" => fn ->
420 StatusView.render("index.json", %{
421 activities: tag_activities,
422 for: user,
423 as: :activity,
424 skip_relationships: !output_relationships
425 })
426 end,
427 "Rendering notifications" => fn ->
428 Pleroma.Web.MastodonAPI.NotificationView.render("index.json", %{
429 notifications: notifications,
430 for: user,
431 skip_relationships: !output_relationships
432 })
433 end,
434 "Rendering favourites timeline" => fn ->
435 StatusView.render("index.json", %{
436 activities: favourites,
437 for: user,
438 as: :activity,
439 skip_relationships: !output_relationships
440 })
441 end
442 },
443 formatters: formatters()
444 )
445 end
446
447 defp render_long_thread(user) do
448 %{public_thread: public, private_thread: private} =
449 Agent.get(:benchmark_state, fn state -> state end)
450
451 opts = %{for: user}
452 public_activity = Activity.get_by_id_with_object(public.id)
453 private_activity = Activity.get_by_id_with_object(private.id)
454
455 Benchee.run(
456 %{
457 "render" => fn opts ->
458 StatusView.render("show.json", opts)
459 end
460 },
461 inputs: %{
462 "Public root" => Map.put(opts, :activity, public_activity),
463 "Private root" => Map.put(opts, :activity, private_activity)
464 },
465 formatters: formatters()
466 )
467
468 fetch_opts = opts_for_long_thread(user)
469
470 public_context =
471 ActivityPub.fetch_activities_for_context(
472 public.data["context"],
473 Map.put(fetch_opts, "exclude_id", public.id)
474 )
475
476 private_context =
477 ActivityPub.fetch_activities_for_context(
478 private.data["context"],
479 Map.put(fetch_opts, "exclude_id", private.id)
480 )
481
482 Benchee.run(
483 %{
484 "render" => fn opts ->
485 StatusView.render("context.json", opts)
486 end
487 },
488 inputs: %{
489 "Public context" => %{user: user, activity: public_activity, activities: public_context},
490 "Private context" => %{
491 user: user,
492 activity: private_activity,
493 activities: private_context
494 }
495 },
496 formatters: formatters()
497 )
498 end
499
500 defp fetch_timelines_with_reply_filtering(user) do
501 public_params = opts_for_public_timeline(user)
502
503 Benchee.run(
504 %{
505 "Public timeline without reply filtering" => fn ->
506 ActivityPub.fetch_public_activities(public_params)
507 end,
508 "Public timeline with reply filtering - following" => fn ->
509 public_params
510 |> Map.put("reply_visibility", "following")
511 |> Map.put("reply_filtering_user", user)
512 |> ActivityPub.fetch_public_activities()
513 end,
514 "Public timeline with reply filtering - self" => fn ->
515 public_params
516 |> Map.put("reply_visibility", "self")
517 |> Map.put("reply_filtering_user", user)
518 |> ActivityPub.fetch_public_activities()
519 end
520 },
521 formatters: formatters()
522 )
523
524 private_params = opts_for_home_timeline(user)
525
526 recipients = [user.ap_id | User.following(user)]
527
528 Benchee.run(
529 %{
530 "Home timeline without reply filtering" => fn ->
531 ActivityPub.fetch_activities(recipients, private_params)
532 end,
533 "Home timeline with reply filtering - following" => fn ->
534 private_params =
535 private_params
536 |> Map.put("reply_filtering_user", user)
537 |> Map.put("reply_visibility", "following")
538
539 ActivityPub.fetch_activities(recipients, private_params)
540 end,
541 "Home timeline with reply filtering - self" => fn ->
542 private_params =
543 private_params
544 |> Map.put("reply_filtering_user", user)
545 |> Map.put("reply_visibility", "self")
546
547 ActivityPub.fetch_activities(recipients, private_params)
548 end
549 },
550 formatters: formatters()
551 )
552 end
553 end