使用loadrunner、loadspace等,首先是付费的,其次对机器的要求较高。loadspace的并非能力本身不怎样。
如果你需要在现网测试,为了避免网络影响,想找一个离业务服务器较近的服务器运行测试,安装loadrunner、loadspace也很麻烦,特别是在linux、unix操作系统中。
所以使用erlang来实现,基于gen_server。
基本常见的所有操作系统都可以运行erlang,编译安装也很简单,如果使用ssl,就需要你的机器上有openssl的lib。
从www.erlang.org下载一个最新版本,在linux、unix上编译,编译很简单,windows下一般不用编译。
./configure --prefix=你的路径 --with-ssl
make & make install
下面代码不多总共不到300行,我实测时,进程数使用过5000都没有问题,不过服务端受不了了:)。测试时服务器能达到8000多并发,所以这个客户端的并发、性能还是比较强的,不过要看运行的服务器、网络情况、服务端的能力几个方面,性能测试是暴露问题,并不能解决问题。
将下面的代码拷贝保存到hpt.erl中,然后在erlang的bin目录下运行erl,进入erlang命令行
在命令行中输入
cd("hpt.erl所在目录"). %% 切换目录
c(hpt, [load, {outdir, "./"}, native, {hipe, [o3]}]). %% 编译
hpt:start("测试url","参数...\"test~$count28.10.0B\"...\"~$time32.10.0B\"...", "成功结果中需包含的字符串\"resultcode\":0").
%%设置测试url及参数,支持两个可变的参数,~$count是当前发送的个数,~$time是当前的时间戳,其他的格式化请参考erlang的io:format
hpt:post(300, 10000). %% 启动一次测试,并指定模拟客户端数量、每个客户端发送次数,也可以使用get方法
hpt:stop(). %% 终止测试
测试结果会5秒中输出一次,同时会记录到当前路径的log文件中,使用简单的查找替换,将这些数字用tab键分隔,就可以用excel打开,利用excel的功能输出图形化的测试报告。
start、post、get还有更多可选参数,请参照代码,
不懂也没关系,找到这几个函数,看参数列表也能看出来:)
======================================================================================================
-module(hpt).
-author('[email protected]').
-vsn('1.0').
-behaviour(gen_server).
-export([
init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
code_change/3,
terminate/2
]).
-export([
start/5,
start/4,
start/3,
start/2,
get/3,
get/2,
post/3,
post/2,
stop/0
]).
-record(state, {
url, %% request url
para, %% in post is request body, and in get is parameters after url
logFile, %% log file name, default is "./log"
log, %% log file handler
search, %% confirm whether the response is ok or not
testers = array:new(), %% all sub processes that send request
report = 5000, %% interval of report, default is 5 seconds
printTime = 0, %% time when print fore-time
start, %% time when start to send request
min = 1000000000000, %% minimal interval of request
max = 0, %% maximal interval of request
interval = 0, %% sum(interval of each request)
curNum = 0, %% number of request which in report interval
total = 0, %% number of all request
curFailed = 0, %% number of request which failed in report interval
failed = 0 %% number of all failed request
}).
start(Url, Para) -> start(Url, Para, null).
start(Url, Para, Search) -> start(Url, Para, Search, 5000).
start(Url, Para, Search, ReportInterval) -> start(Url, Para, Search, ReportInterval, "./log").
start(Url, Para, Search, ReportInterval, LogFile) ->
State = #state{url=Url, para=Para, search=Search, logFile=LogFile, report=ReportInterval},
gen_server:start_link({local, ?MODULE}, ?MODULE, State, [])
.
stop() ->
gen_server:cast(?MODULE, stop)
.
post(ClientNum, TryTimes) -> post(ClientNum, TryTimes, 10000).
post(ClientNum, TryTimes, Timeout) ->
gen_server:cast(?MODULE, {post, ClientNum, TryTimes, Timeout})
.
get(ClientNum, TryTimes) -> get(ClientNum, TryTimes, 10000).
get(ClientNum, TryTimes, Timeout) ->
gen_server:cast(?MODULE, {get, ClientNum, TryTimes, Timeout})
.
%%-----------------------------------------------------------------------------
init(State) ->
inets:start(),
case file:open(State#state.logFile, [write, binary, raw, append, delayed_write]) of
{ok, FP} ->
Now = calcTime(),
{ok, State#state{log=FP, start=Now, printTime=Now}};
{error, Reason} ->
io:format("Fail to open log file,~p ~n", [Reason]),
{stop, {error, Reason}}
end
.
handle_cast({report, -1, _IsOK, Result}, State=#state{printTime=PrintTime}) ->
Now = calcTime(),
printState(State, Now - PrintTime, Now, Result),
{noreply, State}
;
handle_cast({report, Interval, IsOK, Result}, State=#state{printTime=PrintTime}) ->
Max = if Interval > State#state.max -> Interval; true -> State#state.max end,
Min = if Interval < State#state.min -> Interval; true -> State#state.min end,
TotalInterval = State#state.interval + Interval,
Total = State#state.total + 1,
CurNum = State#state.curNum + 1,
Failed = State#state.failed + (1 - IsOK),
CurFailed = State#state.curFailed + (1 - IsOK),
Now = calcTime(),
Time = Now - PrintTime,
{PrintTime1, CurNum1, CurFailed1} =
if
Time > State#state.report ->
printState(State, Time, Now, Result),
{Now, 0, 0};
true ->
{PrintTime, CurNum, CurFailed}
end,
State1 = State#state{
max = Max,
min = Min,
total = Total,
curNum = CurNum1,
printTime = PrintTime1,
interval = TotalInterval,
failed = Failed,
curFailed = CurFailed1
},
{noreply, State1}
;
handle_cast({post, ClientNum, TryTimes, Timeout}, State) ->
io:format("post:start ~p clients and each try ~p times~n", [ClientNum, TryTimes]),
State1 = resetState(State),
Testers1 = postClient(ClientNum, TryTimes, State1, Timeout, array:new()),
{noreply, State1#state{testers=Testers1}}
;
handle_cast({get, ClientNum, TryTimes, Timeout}, State) ->
io:format("get:start ~p clients and each try ~p times~n", [ClientNum, TryTimes]),
State1 = resetState(State),
Testers1 = getClient(ClientNum, TryTimes, State, Timeout, array:new()),
{noreply, State1#state{testers=Testers1}}
;
handle_cast(stop, State=#state{testers=Testers}) ->
io:format("stop all clients~n"),
array:map(fun(_I, Pid) -> exit(Pid, normal) end, Testers),
{stop, normal, State}
;
handle_cast(_Request, State) ->
{noreply, State}
.
handle_call(_Request, _From, State) ->
{noreply, State}
.
handle_info(_Info, State) ->
{noreply, State}
.
code_change(_OldVsn, State, _Extra) ->
{ok, State}
.
terminate(_Reason, #state{log=FP}) ->
file:close(FP),
ok
.
%%-----------------------------------------------------------------------------
postClient(0, _TryTimes, _State, _Timeout, Testers) ->
io:format("post started~n"),
Testers
;
postClient(ClientNum, TryTimes, State, Timeout, Testers) ->
Pid = spawn_link(fun()->
#state{url=Url, para=Para, search=Search} = State,
put(start, ClientNum * TryTimes),
sendPost(TryTimes, Url, Para, Timeout, Search)
end),
postClient(ClientNum - 1, TryTimes, State, Timeout, array:set(ClientNum - 1, Pid, Testers))
.
sendPost(0, _Url, _Para, _Timeout, _Search) ->
gen_server:cast(?MODULE, {report, -1, 1, ""})
;
sendPost(TryTimes, Url, Para, Timeout, Search) ->
Now = calcTime(),
Para1 = formatPara(Para, Now, get(start) + TryTimes, [], []),
Result = httpc:request(post,
{Url, [], "text/plain", Para1},
[{timeout, Timeout}],
[]),
reportResult(Result, Search, Now),
sendPost(TryTimes - 1, Url, Para, Timeout, Search)
.
getClient(0, _TryTimes, _State, _Timeout, Testers) ->
io:format("get started~n"),
Testers
;
getClient(ClientNum, TryTimes, State, Timeout, Testers) ->
Pid = spawn_link(fun()->
#state{url=Url, para=Para, search=Search} = State,
put(start, ClientNum * TryTimes),
sendGet(TryTimes, Url, Para, Timeout, Search)
end),
getClient(ClientNum - 1, TryTimes, State, Timeout, array:set(ClientNum - 1, Pid, Testers))
.
sendGet(0, _Url, _Para, _Timeout, _Search) ->
gen_server:cast(?MODULE, {report, -1, 1, ""})
;
sendGet(TryTimes, Url, Para, Timeout, Search) ->
Now = calcTime(),
Para1 = formatPara(Para, Now, get(start) + TryTimes, [], []),
Result = httpc:request(get,
{Url++Para1, [{"content-type","application/json;charset=utf-8"}]},
[{timeout, Timeout}],
[]),
reportResult(Result, Search, Now),
sendGet(TryTimes - 1, Url, Para, Timeout, Search)
.
reportResult(Result, Search, Start) ->
Interval = calcTime() - Start,
case Result of
{ok, {{_, 200, _},_, Body}} ->
Pos = if Search /= null -> string:str(Body, Search); true -> 1 end,
if
Pos =:= 0 ->
gen_server:cast(?MODULE, {report, Interval, 0, Body});
true ->
gen_server:cast(?MODULE, {report, Interval, 1, Body})
end;
_Error ->
gen_server:cast(?MODULE, {report, Interval, 0, Result})
end
.
formatPara([], _Now, _Count, Para, List) ->
S = lists:flatten(io_lib:format(lists:flatten(lists:reverse(Para)), lists:reverse(List))),
lists:flatten(S)
;
formatPara("$time"++Para, Now, Count, NewPara, List) ->
formatPara(Para, Now, Count, NewPara, [Now|List])
;
formatPara("$count"++Para, Now, Count, NewPara, List) ->
formatPara(Para, Now, Count, NewPara, [Count|List])
;
formatPara([C|Para], Now, Count, NewPara, List) ->
formatPara(Para, Now, Count, [C|NewPara], List)
.
printState(State, 0, Now, Result) -> printState(State, 1, Now, Result);
printState(State, Interval, Now, Result) ->
TotalInterval = if Now - State#state.start > 0 -> Now - State#state.start; true -> 1 end,
Total = if State#state.total > 0 -> State#state.total; true -> 1 end,
Str = io_lib:format("Speed[Cur:~p,Avg:~p],Total:~p,Fail[Cur:~p,All:~p],Interval[Max:~p,Min:~p,Avg:~p],Time:~p~n",
[
(State#state.curNum * 1000) div Interval,
(State#state.total * 1000) div TotalInterval,
State#state.total,
State#state.curFailed,
State#state.failed,
State#state.max,
State#state.min,
State#state.interval div Total,
Interval
]
),
file:write(State#state.log, list_to_binary(Str)),
io:format(Str),
Str1 = io_lib:format("Result:~p~n", [Result]),
file:write(State#state.log, list_to_binary(Str1))
.
resetState(State=#state{testers=Testers}) ->
array:map(fun(_I, Pid) -> exit(Pid, normal) end, Testers),
Now = calcTime(),
State#state{
start = Now,
min = 1000000000000,
max = 0,
interval = 0,
curNum = 0,
total = 0,
curFailed = 0,
failed = 0
}
.
calcTime() ->
{Ms, S, MMs} = now(),
(Ms * 1000000 + S) * 1000 + (MMs div 1000)
.
%% c(hpt, [load, {outdir, "./"}, native, {hipe, [o3]}]).
%% hpt:start("http://192.168.9.145:8080/xxxx/SingleMsgServlet","{\"nsp_ProviderID\":\"1\",\"deviceToken\":\"test~$count12.10.0B0000000001000001\",\"message\":\"~$time32.10.0B\",\"priority\":1,\"cacheMode\":1,\"msgType\":1}", "\"resultcode\":0").
%% hpt:start("http://192.168.9.149:8080/xxx/test","?n=100", "\"resultcode\":0").
%% hpt:start("http://192.168.9.149:8080/xxx/SingleMsgServlet","{\"nsp_ProviderID\":\"1\",\"requestID\":\"1001_1349770334054\",\"deviceToken\":\"test~$count12.10.0B0000001001000001\",\"message\":\"caimessi\",\"priority\":1,\"cacheMode\":1,\"msgType\":1}", "\"resultcode\":\"0\"").
%% hpt:start("http://localhost:5224/PushCRS/AcceptDataServlet","{\"nsp_ProviderID\":\"1\",\"nsp_key\":\"DAECF160C8BEC4AFC3EBFE9E2FE0724F88AD9CA47829F54A2951718C14E1C89D\",\"deviceToken\":\"1a1a1a1a1a1a1a1a0000000000000000\",\"message\":\"caimessi\",\"priority\":1,\"cacheMode\":1}", "\"resultcode\":0").
%% hpt:post(30, 1000).
%% hpt:stop().