diff options
| -rw-r--r-- | lib/load_testing/fetcher.ex | 126 | ||||
| -rw-r--r-- | lib/load_testing/generator.ex | 203 | ||||
| -rw-r--r-- | lib/load_testing/helper.ex | 5 | ||||
| -rw-r--r-- | lib/mix/tasks/pleroma/load_testing.ex | 47 | 
4 files changed, 289 insertions, 92 deletions
| diff --git a/lib/load_testing/fetcher.ex b/lib/load_testing/fetcher.ex index ed744db9b..825f921e6 100644 --- a/lib/load_testing/fetcher.ex +++ b/lib/load_testing/fetcher.ex @@ -51,10 +51,52 @@ defmodule Pleroma.LoadTesting.Fetcher do          )        end,        "User mastodon public timeline" => fn -> -        ActivityPub.ActivityPub.fetch_public_activities(mastodon_public_timeline_params) +        Pleroma.Web.ActivityPub.ActivityPub.fetch_public_activities( +          mastodon_public_timeline_params +        )        end,        "User mastodon federated public timeline" => fn -> -        ActivityPub.ActivityPub.fetch_public_activities(mastodon_federated_timeline_params) +        Pleroma.Web.ActivityPub.ActivityPub.fetch_public_activities( +          mastodon_federated_timeline_params +        ) +      end +    }) + +    home_activities = +      Pleroma.Web.ActivityPub.ActivityPub.fetch_activities( +        [user.ap_id | user.following], +        home_timeline_params +      ) + +    public_activities = +      Pleroma.Web.ActivityPub.ActivityPub.fetch_public_activities(mastodon_public_timeline_params) + +    public_federated_activities = +      Pleroma.Web.ActivityPub.ActivityPub.fetch_public_activities( +        mastodon_federated_timeline_params +      ) + +    Benchee.run(%{ +      "Rendering home timeline" => fn -> +        Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{ +          activities: home_activities, +          for: user, +          as: :activity +        }) +      end, +      "Rendering public timeline" => fn -> +        Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{ +          activities: public_activities, +          for: user, +          as: :activity +        }) +      end, +      "Rendering public federated timeline" => fn -> +        Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{ +          activities: public_federated_activities, +          for: user, +          as: :activity +        })        end      })    end @@ -72,6 +114,27 @@ defmodule Pleroma.LoadTesting.Fetcher do          Pleroma.Web.MastodonAPI.MastodonAPI.get_notifications(user, with_muted_params)        end      }) + +    without_muted_notifications = +      Pleroma.Web.MastodonAPI.MastodonAPI.get_notifications(user, without_muted_params) + +    with_muted_notifications = +      Pleroma.Web.MastodonAPI.MastodonAPI.get_notifications(user, with_muted_params) + +    Benchee.run(%{ +      "Render notifications without muted" => fn -> +        Pleroma.Web.MastodonAPI.NotificationView.render("index.json", %{ +          notifications: without_muted_notifications, +          for: user +        }) +      end, +      "Render notifications with muted" => fn -> +        Pleroma.Web.MastodonAPI.NotificationView.render("index.json", %{ +          notifications: with_muted_notifications, +          for: user +        }) +      end +    })    end    def query_dms(user) do @@ -96,13 +159,40 @@ defmodule Pleroma.LoadTesting.Fetcher do          |> Pleroma.Pagination.fetch_paginated(Map.put(params, "with_muted", false))        end      }) + +    dms_with_muted = +      Pleroma.Web.ActivityPub.ActivityPub.fetch_activities_query([user.ap_id], params) +      |> Pleroma.Pagination.fetch_paginated(params) + +    dms_without_muted = +      Pleroma.Web.ActivityPub.ActivityPub.fetch_activities_query([user.ap_id], params) +      |> Pleroma.Pagination.fetch_paginated(Map.put(params, "with_muted", false)) + +    Benchee.run(%{ +      "Rendering dms with muted" => fn -> +        Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{ +          activities: dms_with_muted, +          for: user, +          as: :activity +        }) +      end, +      "Rendering dms without muted" => fn -> +        Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{ +          activities: dms_without_muted, +          for: user, +          as: :activity +        }) +      end +    })    end    def query_long_thread(user, activity) do      IO.puts("\n=================================")      Benchee.run(%{ -      "Fetch main post" => fn -> Activity.get_by_id_with_object(activity.id) end, +      "Fetch main post" => fn -> +        Pleroma.Activity.get_by_id_with_object(activity.id) +      end,        "Fetch context of main post" => fn ->          Pleroma.Web.ActivityPub.ActivityPub.fetch_activities_for_context(            activity.data["context"], @@ -114,5 +204,35 @@ defmodule Pleroma.LoadTesting.Fetcher do          )        end      }) + +    activity = Pleroma.Activity.get_by_id_with_object(activity.id) + +    context = +      Pleroma.Web.ActivityPub.ActivityPub.fetch_activities_for_context( +        activity.data["context"], +        %{ +          "blocking_user" => user, +          "user" => user, +          "exclude_id" => activity.id +        } +      ) + +    Benchee.run(%{ +      "Render status" => fn -> +        Pleroma.Web.MastodonAPI.StatusView.render("status.json", %{ +          activity: activity, +          for: user +        }) +      end, +      "Render context ancestors" => fn -> +        Pleroma.Web.MastodonAPI.StatusView.render( +          "index.json", +          for: user, +          activities: context, +          as: :activity +        ) +        |> Enum.reverse() +      end +    })    end  end diff --git a/lib/load_testing/generator.ex b/lib/load_testing/generator.ex index 7f50ee68e..6df518aba 100644 --- a/lib/load_testing/generator.ex +++ b/lib/load_testing/generator.ex @@ -1,9 +1,11 @@  defmodule Pleroma.LoadTesting.Generator do    use Pleroma.LoadTesting.Helper +  alias Pleroma.Web.CommonAPI    def generate_users(opts) do      IO.puts("Starting generating #{opts[:users_max]} users...")      {time, _} = :timer.tc(fn -> do_generate_users(opts) end) +      IO.puts("Inserting users take #{to_sec(time)} sec.\n")    end @@ -37,34 +39,111 @@ defmodule Pleroma.LoadTesting.Generator do          following: [User.ap_id(user)]      } -    Pleroma.Repo.insert!(user) +    Repo.insert!(user)    end -  def generate_activities(users, opts) do -    IO.puts("Starting generating #{opts[:activities_max]} activities...") -    {time, _} = :timer.tc(fn -> do_generate_activities(users, opts) end) -    IO.puts("Inserting activities take #{to_sec(time)} sec.\n") +  def generate_activities(user, users) do +    do_generate_activities(user, users)    end -  defp do_generate_activities(users, opts) do -    Task.async_stream( -      1..opts[:activities_max], -      fn _ -> -        do_generate_activity(users, opts) -      end, -      max_concurrency: 10, -      timeout: 30_000 -    ) -    |> Stream.run() +  defp do_generate_activities(user, users) do +    IO.puts("Starting generating 20000 common activities...") + +    {time, _} = +      :timer.tc(fn -> +        Task.async_stream( +          1..20_000, +          fn _ -> +            do_generate_activity([user | users]) +          end, +          max_concurrency: 10, +          timeout: 30_000 +        ) +        |> Stream.run() +      end) + +    IO.puts("Inserting common activities take #{to_sec(time)} sec.\n") + +    IO.puts("Starting generating 20000 activities with mentions...") + +    {time, _} = +      :timer.tc(fn -> +        Task.async_stream( +          1..20_000, +          fn _ -> +            do_generate_activity_with_mention(user, users) +          end, +          max_concurrency: 10, +          timeout: 30_000 +        ) +        |> Stream.run() +      end) + +    IO.puts("Inserting activities with menthions take #{to_sec(time)} sec.\n") + +    IO.puts("Starting generating 10000 activities with threads...") + +    {time, _} = +      :timer.tc(fn -> +        Task.async_stream( +          1..10_000, +          fn _ -> +            do_generate_threads([user | users]) +          end, +          max_concurrency: 10, +          timeout: 30_000 +        ) +        |> Stream.run() +      end) + +    IO.puts("Inserting activities with threads take #{to_sec(time)} sec.\n") +  end + +  defp do_generate_activity(users) do +    post = %{ +      "status" => "Some status without mention with random user" +    } + +    CommonAPI.post(Enum.random(users), post) +  end + +  defp do_generate_activity_with_mention(user, users) do +    mentions_cnt = Enum.random([2, 3, 4, 5]) +    with_user = Enum.random([true, false]) +    users = Enum.shuffle(users) +    mentions_users = Enum.take(users, mentions_cnt) +    mentions_users = if with_user, do: [user | mentions_users], else: mentions_users + +    mentions_str = +      Enum.map(mentions_users, fn user -> "@" <> user.nickname end) |> Enum.join(", ") + +    post = %{ +      "status" => mentions_str <> "some status with mentions random users" +    } + +    CommonAPI.post(Enum.random(users), post)    end -  defp do_generate_activity(users, opts) do -    status = -      if opts[:mention], -        do: "some status with @#{opts[:mention].nickname}", -        else: "some status" +  defp do_generate_threads(users) do +    thread_length = Enum.random([2, 3, 4, 5]) +    actor = Enum.random(users) + +    post = %{ +      "status" => "Start of the thread" +    } + +    {:ok, activity} = CommonAPI.post(actor, post) + +    Enum.each(1..thread_length, fn _ -> +      user = Enum.random(users) + +      post = %{ +        "status" => "@#{actor.nickname} reply to thread", +        "in_reply_to_status_id" => activity.id +      } -    Pleroma.Web.CommonAPI.post(Enum.random(users), %{"status" => status}) +      CommonAPI.post(user, post) +    end)    end    def generate_dms(user, users, opts) do @@ -91,22 +170,21 @@ defmodule Pleroma.LoadTesting.Generator do        "visibility" => "direct"      } -    Pleroma.Web.CommonAPI.post(Enum.random(users), post) +    CommonAPI.post(Enum.random(users), post)    end    def generate_long_thread(user, users, opts) do -    IO.puts("Starting generating long thread with #{opts[:long_thread_length]} replies") +    IO.puts("Starting generating long thread with #{opts[:thread_length]} replies")      {time, activity} = :timer.tc(fn -> do_generate_long_thread(user, users, opts) end)      IO.puts("Inserting long thread replies take #{to_sec(time)} sec.\n")      {:ok, activity}    end    defp do_generate_long_thread(user, users, opts) do -    {:ok, %{id: id} = activity} = -      Pleroma.Web.CommonAPI.post(user, %{"status" => "Start of long thread"}) +    {:ok, %{id: id} = activity} = CommonAPI.post(user, %{"status" => "Start of long thread"})      Task.async_stream( -      1..opts[:long_thread_length], +      1..opts[:thread_length],        fn _ -> do_generate_thread(users, id) end,        max_concurrency: 10,        timeout: 30_000 @@ -117,50 +195,63 @@ defmodule Pleroma.LoadTesting.Generator do    end    defp do_generate_thread(users, activity_id) do -    Pleroma.Web.CommonAPI.post(Enum.random(users), %{ +    CommonAPI.post(Enum.random(users), %{        "status" => "reply to main post",        "in_reply_to_status_id" => activity_id      })    end -  def generate_private_thread(users, opts) do -    IO.puts("Starting generating long thread with #{opts[:non_visible_posts_max]} replies") -    {time, _} = :timer.tc(fn -> do_generate_non_visible_posts(users, opts) end) -    IO.puts("Inserting long thread replies take #{to_sec(time)} sec.\n") -  end +  def generate_non_visible_message(user, users) do +    IO.puts("Starting generating 1000 non visible posts") -  defp do_generate_non_visible_posts(users, opts) do -    [user1, user2] = Enum.take(users, 2) -    {:ok, user1} = Pleroma.User.follow(user1, user2) -    {:ok, user2} = Pleroma.User.follow(user2, user1) +    {time, _} = +      :timer.tc(fn -> +        do_generate_non_visible_posts(user, users) +      end) -    {:ok, activity} = -      Pleroma.Web.CommonAPI.post(user1, %{ -        "status" => "Some private post", -        "visibility" => "private" -      }) +    IO.puts("Inserting non visible posts take #{to_sec(time)} sec.\n") +  end -    {:ok, activity_public} = -      Pleroma.Web.CommonAPI.post(user2, %{ -        "status" => "Some public reply", -        "in_reply_to_status_id" => activity.id -      }) +  defp do_generate_non_visible_posts(user, users) do +    [not_friend | users] = users -    Task.async_stream( -      1..opts[:non_visible_posts_max], -      fn _ -> do_generate_non_visible_post(users, activity_public) end, +    make_friends(user, users) + +    Task.async_stream(1..1000, fn _ -> do_generate_non_visible_post(not_friend, users) end,        max_concurrency: 10,        timeout: 30_000      ) +    |> Stream.run()    end -  defp do_generate_non_visible_post(users, activity) do -    visibility = Enum.random(["private", "public"]) +  defp make_friends(_user, []), do: nil -    Pleroma.Web.CommonAPI.post(Enum.random(users), %{ -      "visibility" => visibility, -      "status" => "Some #{visibility} reply", -      "in_reply_to_status_id" => activity.id -    }) +  defp make_friends(user, [friend | users]) do +    {:ok, _} = User.follow(user, friend) +    {:ok, _} = User.follow(friend, user) +    make_friends(user, users) +  end + +  defp do_generate_non_visible_post(not_friend, users) do +    post = %{ +      "status" => "some non visible post", +      "visibility" => "private" +    } + +    {:ok, activity} = CommonAPI.post(not_friend, post) + +    thread_length = Enum.random([2, 3, 4, 5]) + +    Enum.each(1..thread_length, fn _ -> +      user = Enum.random(users) + +      post = %{ +        "status" => "@#{not_friend.nickname} reply to non visible post", +        "in_reply_to_status_id" => activity.id, +        "visibility" => "private" +      } + +      CommonAPI.post(user, post) +    end)    end  end diff --git a/lib/load_testing/helper.ex b/lib/load_testing/helper.ex index 338dba323..47b25c65f 100644 --- a/lib/load_testing/helper.ex +++ b/lib/load_testing/helper.ex @@ -2,13 +2,8 @@ defmodule Pleroma.LoadTesting.Helper do    defmacro __using__(_) do      quote do        import Ecto.Query -      alias Pleroma.Activity -      alias Pleroma.Notification -      alias Pleroma.Object        alias Pleroma.Repo        alias Pleroma.User -      alias Pleroma.Web.ActivityPub -      alias Pleroma.Web.CommonAPI        defp to_sec(microseconds), do: microseconds / 1_000_000      end diff --git a/lib/mix/tasks/pleroma/load_testing.ex b/lib/mix/tasks/pleroma/load_testing.ex index d17cf0b3e..83e531abf 100644 --- a/lib/mix/tasks/pleroma/load_testing.ex +++ b/lib/mix/tasks/pleroma/load_testing.ex @@ -13,46 +13,37 @@ defmodule Mix.Tasks.Pleroma.LoadTesting do    - activities with notifications    ## Generate data -      MIX_ENV=test mix pleroma.load_testing --users 10000 --activities 20000 -      MIX_ENV=test mix pleroma.load_testing -u 10000 -a 20000 +      MIX_ENV=benchmark mix pleroma.load_testing --users 10000 +      MIX_ENV=benchmark mix pleroma.load_testing -u 10000    Options:    - `--users NUMBER` - number of users to generate (default: 10000) -  - `--activities NUMBER` - number of activities to generate (default: 20000)    """ -  @aliases [u: :users, a: :activities] +  @aliases [u: :users, d: :dms, t: :thread_length]    @switches [      users: :integer, -    activities: :integer,      dms: :integer, -    thread_length: :integer, -    non_visible_posts: :integer +    thread_length: :integer    ]    @users_default 20_000 -  @activities_default 50_000 -  @dms_default 50_000 +  @dms_default 20_000    @thread_length_default 2_000 -  @non_visible_posts_default 2_000    def run(args) do      start_pleroma()      {opts, _} = OptionParser.parse!(args, strict: @switches, aliases: @aliases)      users_max = Keyword.get(opts, :users, @users_default) -    activities_max = Keyword.get(opts, :activities, @activities_default)      dms_max = Keyword.get(opts, :dms, @dms_default) -    long_thread_length = Keyword.get(opts, :thread_length, @thread_length_default) -    non_visible_posts = Keyword.get(opts, :non_visible_posts, @non_visible_posts_default) +    thread_length = Keyword.get(opts, :thread_length, @thread_length_default)      clean_tables()      opts =        Keyword.put(opts, :users_max, users_max) -      |> Keyword.put(:activities_max, activities_max)        |> Keyword.put(:dms_max, dms_max) -      |> Keyword.put(:long_thread_length, long_thread_length) -      |> Keyword.put(:non_visible_posts_max, non_visible_posts) +      |> Keyword.put(:thread_length, thread_length)      generate_users(opts) @@ -60,7 +51,9 @@ defmodule Mix.Tasks.Pleroma.LoadTesting do      IO.puts("Fetching main user...")      {time, user} = -      :timer.tc(fn -> Repo.one(from(u in User, order_by: fragment("RANDOM()"), limit: 1)) end) +      :timer.tc(fn -> +        Repo.one(from(u in User, order_by: fragment("RANDOM()"), limit: 1)) +      end)      IO.puts("Fetching main user take #{to_sec(time)} sec.\n") @@ -79,25 +72,23 @@ defmodule Mix.Tasks.Pleroma.LoadTesting do      IO.puts("Fetching users take #{to_sec(time)} sec.\n") -    generate_activities(users, opts) - -    generate_activities(users, Keyword.put(opts, :mention, user)) +    generate_activities(user, users)      generate_dms(user, users, opts)      {:ok, activity} = generate_long_thread(user, users, opts) -    generate_private_thread(users, opts) +    generate_non_visible_message(user, users) -    # generate_replies(user, users, activities) +    IO.puts("Users in DB: #{Repo.aggregate(from(u in User), :count, :id)}") -    # activity = Enum.random(activities) -    # generate_long_thread(user, users, activity) +    IO.puts("Activities in DB: #{Repo.aggregate(from(a in Pleroma.Activity), :count, :id)}") -    IO.puts("Users in DB: #{Repo.aggregate(from(u in User), :count, :id)}") -    IO.puts("Activities in DB: #{Repo.aggregate(from(a in Activity), :count, :id)}") -    IO.puts("Objects in DB: #{Repo.aggregate(from(o in Object), :count, :id)}") -    IO.puts("Notifications in DB: #{Repo.aggregate(from(n in Notification), :count, :id)}") +    IO.puts("Objects in DB: #{Repo.aggregate(from(o in Pleroma.Object), :count, :id)}") + +    IO.puts( +      "Notifications in DB: #{Repo.aggregate(from(n in Pleroma.Notification), :count, :id)}" +    )      fetch_user(user)      query_timelines(user) | 
