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