sync with develop
[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 30
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 Repo.get(User, main_user.id)
31 end
32
33 def generate_users(max) do
34 IO.puts("Starting generating #{opts[:users]} users...")
35
36 {time, _} =
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 |> Stream.run()
45 end)
46
47 IO.puts("Generating users take #{to_sec(time)} sec.\n")
48 end
49
50 defp generate_user(i) do
51 remote = Enum.random([true, false])
52
53 %User{
54 name: "Test ใƒ†ใ‚นใƒˆ User #{i}",
55 email: "user#{i}@example.com",
56 nickname: "nick#{i}",
57 password_hash: Comeonin.Pbkdf2.hashpwsalt("test"),
58 bio: "Tester Number #{i}",
59 local: !remote
60 }
61 |> user_urls()
62 |> Repo.insert!()
63 end
64
65 defp user_urls(%{local: true} = user) do
66 urls = %{
67 ap_id: User.ap_id(user),
68 follower_address: User.ap_followers(user),
69 following_address: User.ap_following(user)
70 }
71
72 Map.merge(user, urls)
73 end
74
75 defp user_urls(%{local: false} = user) do
76 base_domain = Enum.random(["domain1.com", "domain2.com", "domain3.com"])
77
78 ap_id = "https://#{base_domain}/users/#{user.nickname}"
79
80 urls = %{
81 ap_id: ap_id,
82 follower_address: ap_id <> "/followers",
83 following_address: ap_id <> "/following"
84 }
85
86 Map.merge(user, urls)
87 end
88
89 def make_friends(main_user, max) when is_integer(max) do
90 IO.puts("Starting making friends for #{opts[:friends]} users...")
91
92 {time, _} =
93 :timer.tc(fn ->
94 number_of_users =
95 (max / 2)
96 |> Kernel.trunc()
97
98 main_user
99 |> get_users(%{limit: number_of_users, local: :local})
100 |> run_stream(main_user)
101
102 main_user
103 |> get_users(%{limit: number_of_users, local: :external})
104 |> run_stream(main_user)
105 end)
106
107 IO.puts("Making friends take #{to_sec(time)} sec.\n")
108 end
109
110 defp make_friends(%User{} = main_user, %User{} = user) do
111 {:ok, _} = User.follow(main_user, user)
112 {:ok, _} = User.follow(user, main_user)
113 end
114
115 @spec get_users(User.t(), keyword()) :: [User.t()]
116 def get_users(user, opts) do
117 criteria = %{limit: opts[:limit]}
118
119 criteria =
120 if opts[:local] do
121 Map.put(criteria, opts[:local], true)
122 else
123 criteria
124 end
125
126 criteria =
127 if opts[:friends?] do
128 Map.put(criteria, :friends, user)
129 else
130 criteria
131 end
132
133 query =
134 criteria
135 |> Query.build()
136 |> random_without_user(user)
137
138 query =
139 if opts[:friends?] == false do
140 friends_ids =
141 %{friends: user}
142 |> Query.build()
143 |> Repo.all()
144 |> Enum.map(& &1.id)
145
146 from(u in query, where: u.id not in ^friends_ids)
147 else
148 query
149 end
150
151 Repo.all(query)
152 end
153
154 defp random_without_user(query, user) do
155 from(u in query,
156 where: u.id != ^user.id,
157 order_by: fragment("RANDOM()")
158 )
159 end
160
161 defp run_stream(users, main_user) do
162 Task.async_stream(users, &make_friends(main_user, &1),
163 max_concurrency: @max_concurrency,
164 timeout: 30_000
165 )
166 |> Stream.run()
167 end
168 end