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