diff options
author | Calvin Morrison <calvin@pobox.com> | 2025-09-03 21:15:36 -0400 |
---|---|---|
committer | Calvin Morrison <calvin@pobox.com> | 2025-09-03 21:15:36 -0400 |
commit | 49fa5aa2a127bdf8924d02bf77e5086b39c7a447 (patch) | |
tree | 61d86a7705dacc9fddccc29fa79d075d83ab8059 /server/test/jchat_perf_SUITE.erl |
Diffstat (limited to 'server/test/jchat_perf_SUITE.erl')
-rw-r--r-- | server/test/jchat_perf_SUITE.erl | 185 |
1 files changed, 185 insertions, 0 deletions
diff --git a/server/test/jchat_perf_SUITE.erl b/server/test/jchat_perf_SUITE.erl new file mode 100644 index 0000000..5feccbb --- /dev/null +++ b/server/test/jchat_perf_SUITE.erl @@ -0,0 +1,185 @@ +-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). |