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