total rebase
[anni] / 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