1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2010-2016. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19%% 20%% 21%%---------------------------------------------------------------------- 22%% Purpose: The HDLT client module. 23%% This is the traffic generator 24%%---------------------------------------------------------------------- 25 26-module(hdlt_client). 27 28-export([ 29 start/1, 30 stop/0, 31 start_inets/0, 32 start_service/1, 33 release/0, 34 node_info/0 35 ]). 36 37-export([ 38 proxy/1 39 ]). 40 41-include("hdlt_logger.hrl"). 42 43-define(CTRL, hdlt_ctrl). 44-define(PROXY, hdlt_proxy). 45 46-record(state, 47 { 48 mode = initial, 49 send_rate, 50 time, 51 stop_time, 52 url, 53 nof_reqs = 0, 54 nof_reps = 0, 55 last_req, 56 sizes, 57 socket_type, 58 cert_file 59 }). 60 61 62 63start(Debug) -> 64 proc_lib:start_link(?MODULE, proxy, [Debug]). 65 66stop() -> 67 (catch erlang:send(?PROXY, stop)), 68 ok. 69 70start_inets() -> 71 ?PROXY ! start_inets. 72 73start_service(Args) -> 74 ?PROXY ! {start_client, Args, self()}, 75 receive 76 client_started -> 77 %% ?LOG("client service started"), 78 ok 79 end. 80 81release() -> 82 ?PROXY ! release. 83 84node_info() -> 85 ?PROXY ! {node_info, self()}, 86 receive 87 {node_info, NodeInfo} -> 88 NodeInfo 89 end. 90 91 92%% --------------------------------------------------------------------- 93%% 94%% The proxy process 95%% 96 97proxy(Debug) -> 98 process_flag(trap_exit, true), 99 erlang:register(?PROXY, self()), 100 SName = lists:flatten( 101 io_lib:format("HDLT PROXY[~p,~p]", [self(), node()])), 102 ?SET_NAME(SName), 103 ?SET_LEVEL(Debug), 104 ?LOG("starting", []), 105 Ref = await_for_controller(10), 106 CtrlNode = node(Ref), 107 erlang:monitor_node(CtrlNode, true), 108 proc_lib:init_ack({ok, self()}), 109 ?DEBUG("started", []), 110 proxy_loop(Ref, CtrlNode, undefined). 111 112await_for_controller(N) when N > 0 -> 113 case global:whereis_name(hdlt_ctrl) of 114 Pid when is_pid(Pid) -> 115 erlang:monitor(process, Pid); 116 _ -> 117 timer:sleep(1000), 118 await_for_controller(N-1) 119 end; 120await_for_controller(_) -> 121 proc_lib:init_ack({error, controller_not_found, nodes()}), 122 timer:sleep(500), 123 init:stop(). 124 125 126proxy_loop(Ref, CtrlNode, Client) -> 127 ?DEBUG("await command", []), 128 receive 129 stop -> 130 ?LOG("stop", []), 131 timer:sleep(1000), 132 halt(); 133 134 start_inets -> 135 ?LOG("start the inets service framework", []), 136 %% inets:enable_trace(max, "/tmp/inets-httpc-trace.log", all), 137 case (catch inets:start()) of 138 ok -> 139 ?LOG("framework started", []), 140 proxy_loop(Ref, CtrlNode, Client); 141 Error -> 142 ?LOG("failed starting inets service framework: " 143 "~n Error: ~p", [Error]), 144 timer:sleep(1000), 145 halt() 146 end; 147 148 {start_client, Args, From} -> 149 ?LOG("start client with" 150 "~n Args: ~p", [Args]), 151 Client2 = spawn_link(fun() -> client(Args) end), 152 From ! client_started, 153 proxy_loop(Ref, CtrlNode, Client2); 154 155 release -> 156 ?LOG("release", []), 157 Client ! go, 158 proxy_loop(Ref, CtrlNode, Client); 159 160 {node_info, Pid} -> 161 ?LOG("received requets for node info", []), 162 NodeInfo = get_node_info(), 163 Pid ! {node_info, NodeInfo}, 164 proxy_loop(Ref, CtrlNode, Client); 165 166 {'EXIT', Client, normal} -> 167 ?LOG("received normal exit message from client (~p)", 168 [Client]), 169 exit(normal); 170 171 {'EXIT', Client, Reason} -> 172 ?INFO("received exit message from client (~p)" 173 "~n Reason: ~p", [Client, Reason]), 174 %% Unexpected client termination, inform the controller and die 175 global:send(hdlt_ctrl, {client_exit, Client, node(), Reason}), 176 exit({client_exit, Reason}); 177 178 {nodedown, CtrlNode} -> 179 ?LOG("received nodedown for controller node - terminate", []), 180 halt(); 181 182 {'DOWN', Ref, process, _, _} -> 183 ?INFO("received DOWN message for controller - terminate", []), 184 %% The controller has terminated, dont care why, time to die 185 halt() 186 187 end. 188 189 190 191%% --------------------------------------------------------------------- 192%% 193%% The client process 194%% 195 196client([SocketType, CertFile, URLBase, Sizes, Time, SendRate, Debug]) -> 197 SName = lists:flatten( 198 io_lib:format("HDLT CLIENT[~p,~p]", [self(), node()])), 199 ?SET_NAME(SName), 200 ?SET_LEVEL(Debug), 201 ?LOG("starting with" 202 "~n SocketType: ~p" 203 "~n Time: ~p" 204 "~n SendRate: ~p", [SocketType, Time, SendRate]), 205 httpc:set_options([{max_pipeline_length, 0}]), 206 if 207 (SocketType =:= ssl) orelse 208 (SocketType =:= ossl) orelse 209 (SocketType =:= essl) -> 210 %% Ensure crypto and ssl started: 211 crypto:start(), 212 ssl:start(); 213 true -> 214 ok 215 end, 216 State = #state{mode = idle, 217 url = URLBase, 218 time = Time, 219 send_rate = SendRate, 220 sizes = Sizes, 221 socket_type = SocketType, 222 cert_file = CertFile}, 223 ?DEBUG("started", []), 224 client_loop(State). 225 226%% The point is to first start all client nodes and then this 227%% process. Then, when they are all started, the go-ahead, go, 228%% message is sent to let them lose at the same time. 229client_loop(#state{mode = idle, 230 time = Time, 231 send_rate = SendRate} = State) -> 232 ?DEBUG("[idle] awaiting the go command", []), 233 receive 234 go -> 235 ?LOG("[idle] received go", []), 236 erlang:send_after(Time, self(), stop), 237 NewState = send_requests(State, SendRate), 238 client_loop(NewState#state{mode = generating, 239 nof_reqs = SendRate}) 240 end; 241 242%% In this mode the client is generating traffic. 243%% It will continue to do so until the stop message 244%% is received. 245client_loop(#state{mode = generating} = State) -> 246 receive 247 stop -> 248 ?LOG("[generating] received stop", []), 249 StopTime = timestamp(), 250 req_reply(State), 251 client_loop(State#state{mode = stopping, stop_time = StopTime}); 252 253 {http, {_, {{_, 200, _}, _, _}}} -> 254 %% ?DEBUG("[generating] received reply - send another request", []), 255 NewState = send_requests(State, 1), 256 client_loop(NewState#state{nof_reps = NewState#state.nof_reps + 1, 257 nof_reqs = NewState#state.nof_reqs + 1}); 258 259 {http, {ReqId, {error, Reason}}} -> 260 ?INFO("[generating] request ~p failed: " 261 "~n Reason: ~p" 262 "~n NofReqs: ~p" 263 "~n NofReps: ~p", 264 [ReqId, Reason, State#state.nof_reqs, State#state.nof_reps]), 265 exit({Reason, generating, State#state.nof_reqs, State#state.nof_reps}); 266 267 Else -> 268 ?LOG("[generating] received unexpected message: " 269 "~n~p", [Else]), 270 unexpected_data(Else), 271 client_loop(State) 272 end; 273 274%% The client no longer issues any new requests, instead it 275%% waits for replies for all the oustanding requests to 276%% arrive. 277client_loop(#state{mode = stopping, 278 time = Time, 279 last_req = LastReqId} = State) -> 280 receive 281 {http, {LastReqId, {{_, 200, _}, _, _}}} -> 282 ?DEBUG("[stopping] received reply for last request (~p)", [LastReqId]), 283 time_to_complete(State), 284 ok; 285 286 {http, {ReqId, {{_, 200, _}, _, _}}} -> 287 ?DEBUG("[stopping] received reply ~p", [ReqId]), 288 client_loop(State); 289 290 {http, {ReqId, {error, Reason}}} -> 291 ?INFO("[stopping] request ~p failed: " 292 "~n Reason: ~p" 293 "~n NofReqs: ~p" 294 "~n NofReps: ~p", 295 [ReqId, Reason, State#state.nof_reqs, State#state.nof_reps]), 296 exit({Reason, stopping, State#state.nof_reqs, State#state.nof_reps}); 297 298 Else -> 299 ?LOG("[stopping] received unexpected message: " 300 "~n~p", [Else]), 301 unexpected_data(Else), 302 client_loop(State) 303 304 after Time -> 305 ?INFO("timeout when" 306 "~n Number of requests: ~p" 307 "~n Number of replies: ~p", 308 [State#state.nof_reqs, State#state.nof_reps]), 309 exit({timeout, State#state.nof_reqs, State#state.nof_reps}) 310 end. 311 312req_reply(#state{nof_reqs = NofReqs, nof_reps = NofReps}) -> 313 load_data({req_reply, node(), NofReqs, NofReps}). 314 315time_to_complete(#state{stop_time = StopTime}) -> 316 StoppedTime = os:timestamp(), 317 load_data({time_to_complete, node(), StopTime, StoppedTime}). 318 319load_data(Data) -> 320 global:send(?CTRL, {load_data, Data}). 321 322unexpected_data(Else) -> 323 global:send(?CTRL, {unexpected_data, Else}). 324 325 326send_requests(#state{sizes = Sizes} = State, N) -> 327 send_requests(State, N, Sizes). 328 329send_requests(State, 0, Sizes) -> 330 State#state{sizes = Sizes}; 331send_requests(#state{socket_type = SocketType, 332 cert_file = CertFile} = State, N, [Sz | Sizes]) -> 333 URL = lists:flatten(io_lib:format("~s~w", [State#state.url, Sz])), 334 Method = get, 335 Request = {URL, []}, 336 HTTPOptions = 337 case SocketType of 338 ip_comm -> 339 []; 340 _ -> 341 SslOpts = [{verify, 0}, 342 {certfile, CertFile}, 343 {keyfile, CertFile}], 344 case SocketType of 345 ssl -> 346 [{ssl, SslOpts}]; 347 ossl -> 348 [{ssl, {ossl, SslOpts}}]; 349 essl -> 350 [{ssl, {essl, SslOpts}}] 351 end 352 end, 353 Options = [{sync, false}], 354 {ok, Ref} = httpc:request(Method, Request, HTTPOptions, Options), 355 send_requests(State#state{last_req = Ref}, N-1, lists:append(Sizes, [Sz])). 356 357 358timestamp() -> 359 os:timestamp(). 360 361 362get_node_info() -> 363 [{cpu_topology, erlang:system_info(cpu_topology)}, 364 {heap_type, erlang:system_info(heap_type)}, 365 {nof_schedulers, erlang:system_info(schedulers)}, 366 {otp_release, erlang:system_info(otp_release)}, 367 {version, erlang:system_info(version)}, 368 {system_version, erlang:system_info(system_version)}, 369 {system_architecture, erlang:system_info(system_architecture)}]. 370 371 372