-module(jchat_perf_SUITE). -include_lib("common_test/include/ct.hrl"). -include("../src/jchat.hrl"). %% CT callbacks -export([all/0, init_per_suite/1, end_per_suite/1]). %% Test cases -export([test_message_creation_throughput/1, test_conversation_query_performance/1, test_concurrent_requests/1, test_large_conversation/1]). all() -> [test_message_creation_throughput, test_conversation_query_performance, test_concurrent_requests, test_large_conversation]. init_per_suite(Config) -> application:ensure_all_started(jchat), timer:sleep(1000), Config. end_per_suite(_Config) -> application:stop(jchat), ok. %% Performance tests test_message_creation_throughput(_Config) -> NumMessages = 1000, ConvId = <<"perf-conv-1">>, % Setup {ok, _} = jchat_db:create_conversation(ConvId, #{ title => <<"Performance Test">>, participant_ids => [<<"user1">>] }), % Measure throughput StartTime = erlang:monotonic_time(millisecond), lists:foreach(fun(N) -> MsgId = list_to_binary(io_lib:format("msg-~p", [N])), {ok, _} = jchat_db:create_message(MsgId, #{ conversation_id => ConvId, sender_id => <<"user1">>, body => <<"Performance test message">> }) end, lists:seq(1, NumMessages)), EndTime = erlang:monotonic_time(millisecond), Duration = EndTime - StartTime, Throughput = NumMessages * 1000 / Duration, ct:pal("Created ~p messages in ~p ms (~.2f msg/sec)", [NumMessages, Duration, Throughput]), % Should be able to create at least 100 messages/second true = Throughput > 100.0. test_conversation_query_performance(_Config) -> NumConversations = 100, NumQueries = 1000, % Setup - create conversations lists:foreach(fun(N) -> ConvId = list_to_binary(io_lib:format("query-conv-~p", [N])), {ok, _} = jchat_db:create_conversation(ConvId, #{ title => list_to_binary(io_lib:format("Conversation ~p", [N])), participant_ids => [<<"user1">>] }) end, lists:seq(1, NumConversations)), % Measure query performance StartTime = erlang:monotonic_time(millisecond), lists:foreach(fun(_) -> {ok, _Conversations} = jchat_db:query_conversations(<<"user1">>, #{}) end, lists:seq(1, NumQueries)), EndTime = erlang:monotonic_time(millisecond), Duration = EndTime - StartTime, AvgQueryTime = Duration / NumQueries, ct:pal("Executed ~p queries in ~p ms (~.2f ms/query)", [NumQueries, Duration, AvgQueryTime]), % Each query should take less than 10ms on average true = AvgQueryTime < 10.0. test_concurrent_requests(_Config) -> NumWorkers = 10, RequestsPerWorker = 100, ConvId = <<"concurrent-conv">>, % Setup {ok, _} = jchat_db:create_conversation(ConvId, #{ title => <<"Concurrent Test">>, participant_ids => [<<"user1">>] }), Parent = self(), StartTime = erlang:monotonic_time(millisecond), % Spawn workers Workers = [spawn_link(fun() -> worker_loop(ConvId, RequestsPerWorker, Parent) end) || _ <- lists:seq(1, NumWorkers)], % Wait for all workers to complete lists:foreach(fun(Worker) -> receive {Worker, done} -> ok after 30000 -> error(timeout) end end, Workers), EndTime = erlang:monotonic_time(millisecond), Duration = EndTime - StartTime, TotalRequests = NumWorkers * RequestsPerWorker, Throughput = TotalRequests * 1000 / Duration, ct:pal("Completed ~p concurrent requests in ~p ms (~.2f req/sec)", [TotalRequests, Duration, Throughput]), % Should handle at least 50 concurrent req/sec true = Throughput > 50.0. test_large_conversation(_Config) -> NumMessages = 10000, ConvId = <<"large-conv">>, % Setup {ok, _} = jchat_db:create_conversation(ConvId, #{ title => <<"Large Conversation">>, participant_ids => [<<"user1">>] }), % Create many messages StartTime = erlang:monotonic_time(millisecond), lists:foreach(fun(N) -> MsgId = list_to_binary(io_lib:format("large-msg-~p", [N])), Body = list_to_binary(io_lib:format("Message ~p in large conversation", [N])), {ok, _} = jchat_db:create_message(MsgId, #{ conversation_id => ConvId, sender_id => <<"user1">>, body => Body }) end, lists:seq(1, NumMessages)), EndTime = erlang:monotonic_time(millisecond), Duration = EndTime - StartTime, ct:pal("Created large conversation with ~p messages in ~p ms", [NumMessages, Duration]), % Test querying the large conversation QueryStart = erlang:monotonic_time(millisecond), {ok, Messages} = jchat_db:query_messages(#{in_conversation => ConvId}, #{sort => sent_at}), QueryEnd = erlang:monotonic_time(millisecond), QueryDuration = QueryEnd - QueryStart, ct:pal("Queried ~p messages in ~p ms", [length(Messages), QueryDuration]), % Query should complete reasonably fast even for large conversations true = QueryDuration < 1000. % Less than 1 second %% Helper functions worker_loop(ConvId, 0, Parent) -> Parent ! {self(), done}; worker_loop(ConvId, N, Parent) -> % Create a message MsgId = list_to_binary(io_lib:format("worker-~p-msg-~p", [self(), N])), {ok, _} = jchat_db:create_message(MsgId, #{ conversation_id => ConvId, sender_id => <<"user1">>, body => <<"Concurrent test message">> }), worker_loop(ConvId, N - 1, Parent).