[#1427] Reworked admin scopes support.
[akkoma] / benchmarks / load_testing / generator.ex
1 defmodule Pleroma.LoadTesting.Generator do
2 use Pleroma.LoadTesting.Helper
3 alias Pleroma.Web.CommonAPI
4
5 def generate_users(opts) do
6 IO.puts("Starting generating #{opts[:users_max]} users...")
7 {time, _} = :timer.tc(fn -> do_generate_users(opts) end)
8
9 IO.puts("Inserting users take #{to_sec(time)} sec.\n")
10 end
11
12 defp do_generate_users(opts) do
13 max = Keyword.get(opts, :users_max)
14
15 Task.async_stream(
16 1..max,
17 &generate_user_data(&1),
18 max_concurrency: 10,
19 timeout: 30_000
20 )
21 |> Enum.to_list()
22 end
23
24 defp generate_user_data(i) do
25 remote = Enum.random([true, false])
26
27 user = %User{
28 name: "Test ใƒ†ใ‚นใƒˆ User #{i}",
29 email: "user#{i}@example.com",
30 nickname: "nick#{i}",
31 password_hash:
32 "$pbkdf2-sha512$160000$bU.OSFI7H/yqWb5DPEqyjw$uKp/2rmXw12QqnRRTqTtuk2DTwZfF8VR4MYW2xMeIlqPR/UX1nT1CEKVUx2CowFMZ5JON8aDvURrZpJjSgqXrg",
33 bio: "Tester Number #{i}",
34 info: %{},
35 local: remote
36 }
37
38 user_urls =
39 if remote do
40 base_url =
41 Enum.random(["https://domain1.com", "https://domain2.com", "https://domain3.com"])
42
43 ap_id = "#{base_url}/users/#{user.nickname}"
44
45 %{
46 ap_id: ap_id,
47 follower_address: ap_id <> "/followers",
48 following_address: ap_id <> "/following"
49 }
50 else
51 %{
52 ap_id: User.ap_id(user),
53 follower_address: User.ap_followers(user),
54 following_address: User.ap_following(user)
55 }
56 end
57
58 user = Map.merge(user, user_urls)
59
60 Repo.insert!(user)
61 end
62
63 def generate_activities(user, users) do
64 do_generate_activities(user, users)
65 end
66
67 defp do_generate_activities(user, users) do
68 IO.puts("Starting generating 20000 common activities...")
69
70 {time, _} =
71 :timer.tc(fn ->
72 Task.async_stream(
73 1..20_000,
74 fn _ ->
75 do_generate_activity([user | users])
76 end,
77 max_concurrency: 10,
78 timeout: 30_000
79 )
80 |> Stream.run()
81 end)
82
83 IO.puts("Inserting common activities take #{to_sec(time)} sec.\n")
84
85 IO.puts("Starting generating 20000 activities with mentions...")
86
87 {time, _} =
88 :timer.tc(fn ->
89 Task.async_stream(
90 1..20_000,
91 fn _ ->
92 do_generate_activity_with_mention(user, users)
93 end,
94 max_concurrency: 10,
95 timeout: 30_000
96 )
97 |> Stream.run()
98 end)
99
100 IO.puts("Inserting activities with menthions take #{to_sec(time)} sec.\n")
101
102 IO.puts("Starting generating 10000 activities with threads...")
103
104 {time, _} =
105 :timer.tc(fn ->
106 Task.async_stream(
107 1..10_000,
108 fn _ ->
109 do_generate_threads([user | users])
110 end,
111 max_concurrency: 10,
112 timeout: 30_000
113 )
114 |> Stream.run()
115 end)
116
117 IO.puts("Inserting activities with threads take #{to_sec(time)} sec.\n")
118 end
119
120 defp do_generate_activity(users) do
121 post = %{
122 "status" => "Some status without mention with random user"
123 }
124
125 CommonAPI.post(Enum.random(users), post)
126 end
127
128 defp do_generate_activity_with_mention(user, users) do
129 mentions_cnt = Enum.random([2, 3, 4, 5])
130 with_user = Enum.random([true, false])
131 users = Enum.shuffle(users)
132 mentions_users = Enum.take(users, mentions_cnt)
133 mentions_users = if with_user, do: [user | mentions_users], else: mentions_users
134
135 mentions_str =
136 Enum.map(mentions_users, fn user -> "@" <> user.nickname end) |> Enum.join(", ")
137
138 post = %{
139 "status" => mentions_str <> "some status with mentions random users"
140 }
141
142 CommonAPI.post(Enum.random(users), post)
143 end
144
145 defp do_generate_threads(users) do
146 thread_length = Enum.random([2, 3, 4, 5])
147 actor = Enum.random(users)
148
149 post = %{
150 "status" => "Start of the thread"
151 }
152
153 {:ok, activity} = CommonAPI.post(actor, post)
154
155 Enum.each(1..thread_length, fn _ ->
156 user = Enum.random(users)
157
158 post = %{
159 "status" => "@#{actor.nickname} reply to thread",
160 "in_reply_to_status_id" => activity.id
161 }
162
163 CommonAPI.post(user, post)
164 end)
165 end
166
167 def generate_remote_activities(user, users) do
168 do_generate_remote_activities(user, users)
169 end
170
171 defp do_generate_remote_activities(user, users) do
172 IO.puts("Starting generating 10000 remote activities...")
173
174 {time, _} =
175 :timer.tc(fn ->
176 Task.async_stream(
177 1..10_000,
178 fn i ->
179 do_generate_remote_activity(i, user, users)
180 end,
181 max_concurrency: 10,
182 timeout: 30_000
183 )
184 |> Stream.run()
185 end)
186
187 IO.puts("Inserting remote activities take #{to_sec(time)} sec.\n")
188 end
189
190 defp do_generate_remote_activity(i, user, users) do
191 actor = Enum.random(users)
192 %{host: host} = URI.parse(actor.ap_id)
193 date = Date.utc_today()
194 datetime = DateTime.utc_now()
195
196 map = %{
197 "actor" => actor.ap_id,
198 "cc" => [actor.follower_address, user.ap_id],
199 "context" => "tag:mastodon.example.org,#{date}:objectId=#{i}:objectType=Conversation",
200 "id" => actor.ap_id <> "/statuses/#{i}/activity",
201 "object" => %{
202 "actor" => actor.ap_id,
203 "atomUri" => actor.ap_id <> "/statuses/#{i}",
204 "attachment" => [],
205 "attributedTo" => actor.ap_id,
206 "bcc" => [],
207 "bto" => [],
208 "cc" => [actor.follower_address, user.ap_id],
209 "content" =>
210 "<p><span class=\"h-card\"><a href=\"" <>
211 user.ap_id <>
212 "\" class=\"u-url mention\">@<span>" <> user.nickname <> "</span></a></span></p>",
213 "context" => "tag:mastodon.example.org,#{date}:objectId=#{i}:objectType=Conversation",
214 "conversation" =>
215 "tag:mastodon.example.org,#{date}:objectId=#{i}:objectType=Conversation",
216 "emoji" => %{},
217 "id" => actor.ap_id <> "/statuses/#{i}",
218 "inReplyTo" => nil,
219 "inReplyToAtomUri" => nil,
220 "published" => datetime,
221 "sensitive" => true,
222 "summary" => "cw",
223 "tag" => [
224 %{
225 "href" => user.ap_id,
226 "name" => "@#{user.nickname}@#{host}",
227 "type" => "Mention"
228 }
229 ],
230 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
231 "type" => "Note",
232 "url" => "http://#{host}/@#{actor.nickname}/#{i}"
233 },
234 "published" => datetime,
235 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
236 "type" => "Create"
237 }
238
239 Pleroma.Web.ActivityPub.ActivityPub.insert(map, false)
240 end
241
242 def generate_dms(user, users, opts) do
243 IO.puts("Starting generating #{opts[:dms_max]} DMs")
244 {time, _} = :timer.tc(fn -> do_generate_dms(user, users, opts) end)
245 IO.puts("Inserting dms take #{to_sec(time)} sec.\n")
246 end
247
248 defp do_generate_dms(user, users, opts) do
249 Task.async_stream(
250 1..opts[:dms_max],
251 fn _ ->
252 do_generate_dm(user, users)
253 end,
254 max_concurrency: 10,
255 timeout: 30_000
256 )
257 |> Stream.run()
258 end
259
260 defp do_generate_dm(user, users) do
261 post = %{
262 "status" => "@#{user.nickname} some direct message",
263 "visibility" => "direct"
264 }
265
266 CommonAPI.post(Enum.random(users), post)
267 end
268
269 def generate_long_thread(user, users, opts) do
270 IO.puts("Starting generating long thread with #{opts[:thread_length]} replies")
271 {time, activity} = :timer.tc(fn -> do_generate_long_thread(user, users, opts) end)
272 IO.puts("Inserting long thread replies take #{to_sec(time)} sec.\n")
273 {:ok, activity}
274 end
275
276 defp do_generate_long_thread(user, users, opts) do
277 {:ok, %{id: id} = activity} = CommonAPI.post(user, %{"status" => "Start of long thread"})
278
279 Task.async_stream(
280 1..opts[:thread_length],
281 fn _ -> do_generate_thread(users, id) end,
282 max_concurrency: 10,
283 timeout: 30_000
284 )
285 |> Stream.run()
286
287 activity
288 end
289
290 defp do_generate_thread(users, activity_id) do
291 CommonAPI.post(Enum.random(users), %{
292 "status" => "reply to main post",
293 "in_reply_to_status_id" => activity_id
294 })
295 end
296
297 def generate_non_visible_message(user, users) do
298 IO.puts("Starting generating 1000 non visible posts")
299
300 {time, _} =
301 :timer.tc(fn ->
302 do_generate_non_visible_posts(user, users)
303 end)
304
305 IO.puts("Inserting non visible posts take #{to_sec(time)} sec.\n")
306 end
307
308 defp do_generate_non_visible_posts(user, users) do
309 [not_friend | users] = users
310
311 make_friends(user, users)
312
313 Task.async_stream(1..1000, fn _ -> do_generate_non_visible_post(not_friend, users) end,
314 max_concurrency: 10,
315 timeout: 30_000
316 )
317 |> Stream.run()
318 end
319
320 defp make_friends(_user, []), do: nil
321
322 defp make_friends(user, [friend | users]) do
323 {:ok, _} = User.follow(user, friend)
324 {:ok, _} = User.follow(friend, user)
325 make_friends(user, users)
326 end
327
328 defp do_generate_non_visible_post(not_friend, users) do
329 post = %{
330 "status" => "some non visible post",
331 "visibility" => "private"
332 }
333
334 {:ok, activity} = CommonAPI.post(not_friend, post)
335
336 thread_length = Enum.random([2, 3, 4, 5])
337
338 Enum.each(1..thread_length, fn _ ->
339 user = Enum.random(users)
340
341 post = %{
342 "status" => "@#{not_friend.nickname} reply to non visible post",
343 "in_reply_to_status_id" => activity.id,
344 "visibility" => "private"
345 }
346
347 CommonAPI.post(user, post)
348 end)
349 end
350 end