defmodule Pleroma.LoadTesting.Users do @moduledoc """ Module for generating users with friends. """ import Ecto.Query import Pleroma.LoadTesting.Helper, only: [to_sec: 1] alias Pleroma.Repo alias Pleroma.User alias Pleroma.User.Query @defaults [ users: 20_000, friends: 100 ] @max_concurrency 10 @spec generate(keyword()) :: User.t() def generate(opts \\ []) do opts = Keyword.merge(@defaults, opts) generate_users(opts[:users]) main_user = Repo.one(from(u in User, where: u.local == true, order_by: fragment("RANDOM()"), limit: 1)) make_friends(main_user, opts[:friends]) User.get_by_id(main_user.id) end def generate_users(max) do IO.puts("Starting generating #{max} users...") {time, users} = :timer.tc(fn -> Task.async_stream( 1..max, &generate_user(&1), max_concurrency: @max_concurrency, timeout: 30_000 ) |> Enum.to_list() end) IO.puts("Generating users took #{to_sec(time)} sec.\n") users end defp generate_user(i) do remote = Enum.random([true, false]) %User{ name: "Test ใƒ†ใ‚นใƒˆ User #{i}", email: "user#{i}@example.com", nickname: "nick#{i}", password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt("test"), bio: "Tester Number #{i}", local: !remote } |> user_urls() |> Repo.insert!() end defp user_urls(%{local: true} = user) do urls = %{ ap_id: User.ap_id(user), follower_address: User.ap_followers(user), following_address: User.ap_following(user) } Map.merge(user, urls) end defp user_urls(%{local: false} = user) do base_domain = Enum.random(["domain1.com", "domain2.com", "domain3.com"]) ap_id = "https://#{base_domain}/users/#{user.nickname}" urls = %{ ap_id: ap_id, follower_address: ap_id <> "/followers", following_address: ap_id <> "/following" } Map.merge(user, urls) end def make_friends(main_user, max) when is_integer(max) do IO.puts("Starting making friends for #{max} users...") {time, _} = :timer.tc(fn -> number_of_users = (max / 2) |> Kernel.trunc() main_user |> get_users(%{limit: number_of_users, local: :local}) |> run_stream(main_user) main_user |> get_users(%{limit: number_of_users, local: :external}) |> run_stream(main_user) end) IO.puts("Making friends took #{to_sec(time)} sec.\n") end def make_friends(%User{} = main_user, %User{} = user) do {:ok, _, _} = User.follow(main_user, user) {:ok, _, _} = User.follow(user, main_user) end @spec get_users(User.t(), keyword()) :: [User.t()] def get_users(user, opts) do criteria = %{limit: opts[:limit]} criteria = if opts[:local] do Map.put(criteria, opts[:local], true) else criteria end criteria = if opts[:friends?] do Map.put(criteria, :friends, user) else criteria end query = criteria |> Query.build() |> random_without_user(user) query = if opts[:friends?] == false do friends_ids = %{friends: user} |> Query.build() |> Repo.all() |> Enum.map(& &1.id) from(u in query, where: u.id not in ^friends_ids) else query end Repo.all(query) end defp random_without_user(query, user) do from(u in query, where: u.id != ^user.id, order_by: fragment("RANDOM()") ) end defp run_stream(users, main_user) do Task.async_stream(users, &make_friends(main_user, &1), max_concurrency: @max_concurrency, timeout: 30_000 ) |> Stream.run() end @spec prepare_users(User.t(), keyword()) :: map() def prepare_users(user, opts) do friends_limit = opts[:friends_used] non_friends_limit = opts[:non_friends_used] %{ user: user, friends_local: fetch_users(user, friends_limit, :local, true), friends_remote: fetch_users(user, friends_limit, :external, true), non_friends_local: fetch_users(user, non_friends_limit, :local, false), non_friends_remote: fetch_users(user, non_friends_limit, :external, false) } end defp fetch_users(user, limit, local, friends?) do user |> get_users(limit: limit, local: local, friends?: friends?) |> Enum.shuffle() end end