Activity.Search: fallback on status resolution on DB Timeout
[akkoma] / benchmarks / load_testing / users.ex
1 defmodule Pleroma.LoadTesting.Users do
2 @moduledoc """
3 Module for generating users with friends.
4 """
5 import Ecto.Query
6 import Pleroma.LoadTesting.Helper, only: [to_sec: 1]
7
8 alias Pleroma.Repo
9 alias Pleroma.User
10 alias Pleroma.User.Query
11
12 @defaults [
13 users: 20_000,
14 friends: 100
15 ]
16
17 @max_concurrency 10
18
19 @spec generate(keyword()) :: User.t()
20 def generate(opts \\ []) do
21 opts = Keyword.merge(@defaults, opts)
22
23 generate_users(opts[:users])
24
25 main_user =
26 Repo.one(from(u in User, where: u.local == true, order_by: fragment("RANDOM()"), limit: 1))
27
28 make_friends(main_user, opts[:friends])
29
30 User.get_by_id(main_user.id)
31 end
32
33 def generate_users(max) do
34 IO.puts("Starting generating #{max} users...")
35
36 {time, users} =
37 :timer.tc(fn ->
38 Task.async_stream(
39 1..max,
40 &generate_user(&1),
41 max_concurrency: @max_concurrency,
42 timeout: 30_000
43 )
44 |> Enum.to_list()
45 end)
46
47 IO.puts("Generating users took #{to_sec(time)} sec.\n")
48 users
49 end
50
51 defp generate_user(i) do
52 remote = Enum.random([true, false])
53
54 %User{
55 name: "Test ใƒ†ใ‚นใƒˆ User #{i}",
56 email: "user#{i}@example.com",
57 nickname: "nick#{i}",
58 password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt("test"),
59 bio: "Tester Number #{i}",
60 local: !remote
61 }
62 |> user_urls()
63 |> Repo.insert!()
64 end
65
66 defp user_urls(%{local: true} = user) do
67 urls = %{
68 ap_id: User.ap_id(user),
69 follower_address: User.ap_followers(user),
70 following_address: User.ap_following(user)
71 }
72
73 Map.merge(user, urls)
74 end
75
76 defp user_urls(%{local: false} = user) do
77 base_domain = Enum.random(["domain1.com", "domain2.com", "domain3.com"])
78
79 ap_id = "https://#{base_domain}/users/#{user.nickname}"
80
81 urls = %{
82 ap_id: ap_id,
83 follower_address: ap_id <> "/followers",
84 following_address: ap_id <> "/following"
85 }
86
87 Map.merge(user, urls)
88 end
89
90 def make_friends(main_user, max) when is_integer(max) do
91 IO.puts("Starting making friends for #{max} users...")
92
93 {time, _} =
94 :timer.tc(fn ->
95 number_of_users =
96 (max / 2)
97 |> Kernel.trunc()
98
99 main_user
100 |> get_users(%{limit: number_of_users, local: :local})
101 |> run_stream(main_user)
102
103 main_user
104 |> get_users(%{limit: number_of_users, local: :external})
105 |> run_stream(main_user)
106 end)
107
108 IO.puts("Making friends took #{to_sec(time)} sec.\n")
109 end
110
111 def make_friends(%User{} = main_user, %User{} = user) do
112 {:ok, _, _} = User.follow(main_user, user)
113 {:ok, _, _} = User.follow(user, main_user)
114 end
115
116 @spec get_users(User.t(), keyword()) :: [User.t()]
117 def get_users(user, opts) do
118 criteria = %{limit: opts[:limit]}
119
120 criteria =
121 if opts[:local] do
122 Map.put(criteria, opts[:local], true)
123 else
124 criteria
125 end
126
127 criteria =
128 if opts[:friends?] do
129 Map.put(criteria, :friends, user)
130 else
131 criteria
132 end
133
134 query =
135 criteria
136 |> Query.build()
137 |> random_without_user(user)
138
139 query =
140 if opts[:friends?] == false do
141 friends_ids =
142 %{friends: user}
143 |> Query.build()
144 |> Repo.all()
145 |> Enum.map(& &1.id)
146
147 from(u in query, where: u.id not in ^friends_ids)
148 else
149 query
150 end
151
152 Repo.all(query)
153 end
154
155 defp random_without_user(query, user) do
156 from(u in query,
157 where: u.id != ^user.id,
158 order_by: fragment("RANDOM()")
159 )
160 end
161
162 defp run_stream(users, main_user) do
163 Task.async_stream(users, &make_friends(main_user, &1),
164 max_concurrency: @max_concurrency,
165 timeout: 30_000
166 )
167 |> Stream.run()
168 end
169
170 @spec prepare_users(User.t(), keyword()) :: map()
171 def prepare_users(user, opts) do
172 friends_limit = opts[:friends_used]
173 non_friends_limit = opts[:non_friends_used]
174
175 %{
176 user: user,
177 friends_local: fetch_users(user, friends_limit, :local, true),
178 friends_remote: fetch_users(user, friends_limit, :external, true),
179 non_friends_local: fetch_users(user, non_friends_limit, :local, false),
180 non_friends_remote: fetch_users(user, non_friends_limit, :external, false)
181 }
182 end
183
184 defp fetch_users(user, limit, local, friends?) do
185 user
186 |> get_users(limit: limit, local: local, friends?: friends?)
187 |> Enum.shuffle()
188 end
189 end