Skip to content
Browse files

First commit.

Only connecting to the WebSocket server works at the moment.
  • Loading branch information...
0 parents commit e15719247ef25485abb65baa42c558dd60f7c7e3 @wulczer committed Mar 20, 2012
Showing with 590 additions and 0 deletions.
  1. +14 −0 include/ts_websocket.hrl
  2. +117 −0 src/tsung/ts_websocket.erl
  3. +37 −0 src/tsung_controller/ts_config_websocket.erl
  4. +389 −0 tsung-1.0.dtd
  5. +33 −0 websocket.xml
14 include/ts_websocket.hrl
@@ -0,0 +1,14 @@
+-record(websocket_request, {
+ type, % connect | send | ping | close
+ url % string() (only used in connect requests)
+ }).
+
+
+-record(websocket_dyndata, {
+ fixme
+ }).
+
+-record(websocket, {
+ state, % initial | connected
+ accept % binary()
+ }).
117 src/tsung/ts_websocket.erl
@@ -0,0 +1,117 @@
+-module(ts_websocket).
+
+-behavior(ts_plugin).
+
+-include("ts_profile.hrl").
+-include("ts_websocket.hrl").
+
+-export([init_dynparams/0,
+ add_dynparams/4,
+ get_message/2,
+ session_defaults/0,
+ dump/2,
+ parse/2,
+ parse_bidi/2,
+ parse_config/2,
+ decode_buffer/2,
+ new_session/0]).
+
+
+-define(ACCEPT_GUID, << "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" >>).
+
+-define(OPCODE_TEXT, 1).
+-define(OPCODE_CLOSE, 8).
+-define(OPCODE_PING, 9).
+-define(OPCODE_PONG, 10).
+
+
+session_defaults() ->
+ {ok, true}.
+
+
+decode_buffer(Buffer,#websocket{}) ->
+ Buffer.
+
+
+new_session() ->
+ #websocket{state = initial}.
+
+
+get_message(#websocket_request{type=connect, url=Url}, State=#state_rcv{session=Session}) ->
+ Nonce = base64:encode(crypto:rand_bytes(16)),
+ Accept = base64:encode(crypto:sha(<< Nonce/binary, ?ACCEPT_GUID/binary >>)),
+ Handshake = list_to_binary(string:join(["GET " ++ Url ++ " HTTP/1.1",
+ "Host: " ++ State#state_rcv.host,
+ "Upgrade: websocket",
+ "Connection: Upgrade",
+ "Sec-WebSocket-Key: " ++ binary_to_list(Nonce),
+ "Sec-WebSocket-Version: 13",
+ "", ""], "\r\n")),
+ {Handshake, Session#websocket{accept = Accept}}.
+
+
+extract_header(Name, [Header | Tail]) ->
+ case binary:split(Header, << ": " >>) of
+ [Name, Val] ->
+ % found it
+ Val;
+ _ ->
+ % not the header we're looking for
+ extract_header(Name, Tail)
+ end;
+extract_header(Name, []) ->
+ % the header was not found
+ ?LOGF("WEBSOCKET: Header ~p not found ~n", [Name], ?NOTICE),
+ << >>.
+
+extract_accept(Data) ->
+ Suffix = binary:longest_common_suffix([Data, << "\r\n\r\n" >>]),
+ if Suffix == 4 ->
+ % got all headers
+ Headers = binary:split(Data, << "\r\n" >>, [global]),
+ extract_header(<< "Sec-WebSocket-Accept" >>, Headers);
+ true ->
+ % still getting the headers
+ more
+ end.
+
+
+parse(closed, State) ->
+ {State#state_rcv{ack_done = true, datasize = 0}, [], true};
+parse(Data, State=#state_rcv{acc=[], datasize=0}) ->
+ parse(Data, State#state_rcv{datasize = size(Data)});
+parse(Data, State=#state_rcv{acc=[], session=Session})
+ when Session#websocket.state == initial ->
+ Expected = Session#websocket.accept,
+ case extract_accept(Data) of
+ Expected ->
+ {State#state_rcv{ack_done = true}, [], false};
+ more ->
+ {State#state_rcv{ack_done = false, acc = Data}, [], false};
+ Wrong ->
+ ts_mon:add({count, error_websocket}),
+ {State#state_rcv{ack_done = true}, [], true}
+ end;
+parse(Data, State=#state_rcv{acc=Acc, datasize=DataSize}) ->
+ NewSize= DataSize + size(Data),
+ parse(<< Acc/binary, Data/binary >>, State#state_rcv{acc = [], datasize = NewSize}).
+
+
+parse_bidi(Data, State) ->
+ ts_plugin:parse_bidi(Data,State).
+
+
+init_dynparams() ->
+ #dyndata{proto = #websocket_dyndata{}}.
+
+
+add_dynparams(_Subst, _DynData, Param, _HostData) ->
+ Param#websocket_request{}.
+
+
+parse_config(Element, Config) ->
+ ts_config_websocket:parse_config(Element, Config).
+
+
+dump(A, B) ->
+ ts_plugin:dump(A, B).
37 src/tsung_controller/ts_config_websocket.erl
@@ -0,0 +1,37 @@
+-module(ts_config_websocket).
+
+-export([parse_config/2]).
+
+-include("ts_profile.hrl").
+-include("ts_websocket.hrl").
+-include("ts_config.hrl").
+
+-include("xmerl.hrl").
+
+
+parse_config(Element = #xmlElement{name=dyn_variable}, Conf = #config{}) ->
+ ts_config:parse(Element,Conf);
+parse_config(Element = #xmlElement{name=websocket},
+ Config=#config{curid= Id, session_tab = Tab,
+ match=MatchRegExp, dynvar=DynVar,
+ subst= SubstFlag, sessions = [CurS |_]}) ->
+
+ Type = ts_config:getAttr(atom, Element#xmlElement.attributes, type),
+ Url = ts_config:getAttr(string, Element#xmlElement.attributes, url, "/"),
+ Request = #websocket_request{type = Type, url = Url},
+ Msg = #ts_request{ack = parse,
+ endpage = true,
+ dynvar_specs = DynVar,
+ subst = SubstFlag,
+ match = MatchRegExp,
+ param = Request},
+
+ ts_config:mark_prev_req(Id-1, Tab, CurS),
+ ets:insert(Tab,{{CurS#session.id, Id}, Msg }),
+ lists:foldl( fun(A,B)->ts_config:parse(A,B) end,
+ Config#config{dynvar=[]},
+ Element#xmlElement.content);
+parse_config(Element = #xmlElement{}, Conf = #config{}) ->
+ ts_config:parse(Element,Conf);
+parse_config(_, Conf = #config{}) ->
+ Conf.
389 tsung-1.0.dtd
@@ -0,0 +1,389 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!ELEMENT tsung (information?, clients, servers, monitoring?, load, options?, sessions)>
+
+<!ELEMENT information (name|description|username|organisation)*>
+
+
+<!ELEMENT name (#PCDATA)>
+<!ELEMENT description (#PCDATA)>
+<!ELEMENT username (#PCDATA)>
+<!ELEMENT organisation (#PCDATA)>
+
+<!ATTLIST tsung
+ dumptraffic (true | false | light | protocol) "false"
+ backend (text | json| rrdtool | fullstats) "text"
+ loglevel (emergency|critical|error|warning|notice|info|debug) "notice"
+ version NMTOKEN #IMPLIED>
+
+<!ELEMENT servers (server+)>
+<!ELEMENT server EMPTY>
+<!ATTLIST server
+ host NMTOKEN #REQUIRED
+ port NMTOKEN #REQUIRED
+ type (ssl | tcp | udp | erlang | ssl6 | tcp6 | udp6) #REQUIRED>
+
+<!ELEMENT clients (client+)>
+<!ELEMENT client (ip*) >
+<!ATTLIST client
+ cpu NMTOKEN "1"
+ type (machine | batch) "machine"
+ host NMTOKEN #IMPLIED
+ batch (torque | pbs | lsf | oar) #IMPLIED
+ scan_intf NMTOKEN #IMPLIED
+ maxusers NMTOKEN "800"
+ use_controller_vm (true | false) "false"
+ weight NMTOKEN "1">
+
+<!ELEMENT ip EMPTY>
+<!ATTLIST ip
+ value NMTOKEN #REQUIRED
+ scan (true| false) "false"
+>
+
+<!ELEMENT monitoring ( monitor+ )>
+<!ELEMENT monitor ( snmp? | munin?)>
+<!ATTLIST monitor
+ host NMTOKEN #REQUIRED
+ batch (true | false) "false"
+ type (snmp | erlang | munin) "erlang">
+
+<!ELEMENT snmp (oid)*>
+<!ATTLIST snmp
+ version (v1 | v2) "v1"
+ community NMTOKEN "public"
+ port NMTOKEN "161">
+
+<!ELEMENT oid EMPTY>
+<!ATTLIST oid
+ value NMTOKEN #REQUIRED
+ name NMTOKEN #REQUIRED
+ type NMTOKEN "sample"
+ eval CDATA #IMPLIED>
+
+<!ELEMENT munin EMPTY>
+<!ATTLIST munin
+ port NMTOKEN "4949">
+
+<!ELEMENT load (arrivalphase | user)+>
+<!ATTLIST load
+ duration NMTOKEN #IMPLIED
+ unit (hour | minute | second) "second"
+ loop NMTOKEN "0"
+ >
+
+<!ELEMENT arrivalphase (users)>
+<!ATTLIST arrivalphase
+ duration NMTOKEN #REQUIRED
+ phase NMTOKEN #REQUIRED
+ unit (hour | minute | second | millisecond) #REQUIRED>
+
+<!ELEMENT users EMPTY>
+<!ATTLIST users
+ interarrival NMTOKEN #IMPLIED
+ arrivalrate NMTOKEN #IMPLIED
+ unit (hour | minute | second) #REQUIRED
+ maxnumber NMTOKEN #IMPLIED>
+
+<!ELEMENT user EMPTY>
+<!ATTLIST user
+ start_time NMTOKEN #IMPLIED
+ unit (hour | minute | second | millisecond) "second"
+ session NMTOKEN #REQUIRED>
+
+<!ELEMENT options (option*)>
+<!ELEMENT option (user_agent*)>
+<!ATTLIST option
+ name NMTOKEN #REQUIRED
+ override (true | false) #IMPLIED
+ random (true | false) #IMPLIED
+ id NMTOKEN #IMPLIED
+ min NMTOKEN #IMPLIED
+ max NMTOKEN #IMPLIED
+ type (ts_http | ts_jabber | ts_pgsql) #IMPLIED
+ value CDATA #IMPLIED>
+
+<!ELEMENT set_option (user_agent*)>
+<!ATTLIST set_option
+ name NMTOKEN #REQUIRED
+ id NMTOKEN #IMPLIED
+ min NMTOKEN #IMPLIED
+ max NMTOKEN #IMPLIED
+ type (ts_http | ts_jabber | ts_pgsql) #IMPLIED
+ value CDATA #IMPLIED>
+
+<!ELEMENT sessions (session+)>
+<!ELEMENT session ( request | thinktime | transaction | setdynvars | for |
+repeat | if | change_type | foreach | set_option)*>
+<!ATTLIST session
+ name CDATA #REQUIRED
+ bidi CDATA #IMPLIED
+ persistent (true | false) #IMPLIED
+ probability NMTOKEN #REQUIRED
+ type (ts_jabber | ts_http | ts_raw | ts_pgsql | ts_ldap | ts_webdav |ts_mysql| ts_fs |ts_shell|ts_job|ts_websocket) #REQUIRED>
+
+<!ELEMENT change_type EMPTY>
+<!ATTLIST change_type
+ new_type (ts_jabber | ts_http | ts_raw | ts_pgsql | ts_ldap | ts_webdav |ts_mysql| ts_fs | ts_shell|ts_job | ts_websocket) #REQUIRED
+ host NMTOKEN #REQUIRED
+ port NMTOKEN #REQUIRED
+ server_type NMTOKEN #REQUIRED
+ store ( true | false ) "false"
+ restore ( true | false ) "false"
+ >
+
+<!ELEMENT request ( match*, dyn_variable*, ( http | jabber | raw |
+ pgsql | ldap | mysql |fs | shell | job | websocket ) )>
+<!ATTLIST request
+ subst (true|false) "false"
+ >
+
+<!ELEMENT match (#PCDATA)>
+<!ATTLIST match
+ do (continue|loop|abort|restart|log|dump) "continue"
+ when (match|nomatch) "match"
+ subst (true|false) "false"
+ loop_back NMTOKEN "0"
+ max_loop NMTOKEN "20"
+ max_restart NMTOKEN "3"
+ sleep_loop NMTOKEN "5"
+ apply_to_content NMTOKEN "undefined"
+ skip_headers NMTOKEN "no"
+ >
+
+<!ELEMENT thinktime EMPTY>
+<!ATTLIST thinktime
+ random (true|false) "false"
+ value CDATA #IMPLIED
+ min NMTOKEN #IMPLIED
+ max NMTOKEN #IMPLIED
+ >
+
+<!ELEMENT user_agent (#PCDATA)*>
+<!ATTLIST user_agent
+ probability NMTOKEN #REQUIRED
+ >
+
+<!ELEMENT transaction (request | setdynvars | thinktime | for | repeat
+ | if | foreach)+>
+<!ATTLIST transaction name NMTOKEN #REQUIRED>
+
+<!ELEMENT http ( www_authenticate?, soap?, http_header*, add_cookie*)>
+<!ATTLIST http
+ contents CDATA #IMPLIED
+ contents_from_file CDATA #IMPLIED
+ content_type CDATA #IMPLIED
+ if_modified_since CDATA #IMPLIED
+ method (GET | POST | PUT | DELETE | HEAD | PROPFIND | PROPPATCH | COPY | MOVE | LOCK | UNLOCK | MKCOL | MKACTIVITY | OPTIONS | REPORT | VERSION-CONTROL | MERGE | CHECKOUT) "GET"
+ url CDATA #REQUIRED
+ version (1.0 | 1.1) "1.1" >
+
+<!ELEMENT soap EMPTY >
+<!ATTLIST soap action CDATA #REQUIRED >
+
+<!ELEMENT dyn_variable EMPTY >
+<!ATTLIST dyn_variable
+ name CDATA #REQUIRED
+ xpath CDATA #IMPLIED
+ re CDATA #IMPLIED
+ jsonpath CDATA #IMPLIED
+ pgsql_expr CDATA #IMPLIED
+ regexp CDATA #IMPLIED >
+
+<!ELEMENT http_header EMPTY >
+<!ATTLIST http_header
+ name CDATA #REQUIRED
+ encoding CDATA #IMPLIED
+ value CDATA #IMPLIED >
+
+<!ELEMENT add_cookie EMPTY >
+<!ATTLIST add_cookie
+ key CDATA #REQUIRED
+ domain CDATA #IMPLIED
+ path CDATA #IMPLIED
+ value CDATA #REQUIRED >
+
+<!ELEMENT www_authenticate EMPTY >
+<!ATTLIST www_authenticate
+ passwd CDATA #REQUIRED
+ userid CDATA #REQUIRED >
+
+<!ELEMENT jabber (xmpp_authenticate?) >
+<!ATTLIST jabber
+ ack (global | local | no_ack | parse) #REQUIRED
+ destination (online | offline | random | unique | previous) "random"
+ id NMTOKEN #IMPLIED
+ size NMTOKEN "0"
+ data CDATA #IMPLIED
+ type NMTOKEN #REQUIRED
+ show (away|chat|dnd|xa) "chat"
+ status CDATA "Available"
+ nick CDATA #IMPLIED
+ room CDATA #IMPLIED
+ group CDATA #IMPLIED
+ node CDATA #IMPLIED
+ regexp CDATA #IMPLIED
+ node_type CDATA #IMPLIED >
+
+<!ELEMENT xmpp_authenticate EMPTY >
+<!ATTLIST xmpp_authenticate
+ passwd CDATA #REQUIRED
+ username CDATA #REQUIRED >
+
+
+<!ELEMENT fs EMPTY >
+<!ATTLIST fs
+ cmd (read|write|open|delete|stats|copy|read_chunk|write_chunk|close|make_dir|del_dir) "write"
+ path CDATA #IMPLIED
+ size CDATA "1024"
+ position CDATA #IMPLIED
+ mode (read | write | append ) #IMPLIED
+ dest CDATA #IMPLIED
+>
+
+<!ELEMENT shell EMPTY >
+<!ATTLIST shell
+ cmd CDATA #REQUIRED
+ args CDATA ""
+>
+
+<!ELEMENT job EMPTY >
+<!ATTLIST job
+ type (oar|torque) "oar"
+ req (submit|delete|stat|suspend|resume|wait_jobs) #REQUIRED
+ script CDATA #IMPLIED
+ walltime CDATA #IMPLIED
+ duration CDATA #IMPLIED
+ jobid CDATA #IMPLIED
+ resources CDATA #IMPLIED
+ nodes CDATA #IMPLIED
+ queue CDATA #IMPLIED
+ options CDATA #IMPLIED
+ user CDATA #IMPLIED
+ name CDATA "tsung"
+ notify_port CDATA #IMPLIED
+ notify_script CDATA #IMPLIED
+>
+
+<!ELEMENT websocket (#PCDATA) >
+<!ATTLIST websocket
+ type (connect | send | ping | close) #REQUIRED
+ url CDATA "/"
+ ack (no_ack | parse) #IMPLIED
+>
+
+<!ELEMENT pgsql (#PCDATA) >
+<!ATTLIST pgsql
+ password CDATA #IMPLIED
+ database CDATA #IMPLIED
+ username CDATA #IMPLIED
+ name_portal CDATA #IMPLIED
+ name_prepared CDATA #IMPLIED
+ query CDATA #IMPLIED
+ parameters CDATA #IMPLIED
+ max_rows CDATA "0"
+ formats CDATA #IMPLIED
+ formats_results CDATA #IMPLIED
+ contents_from_file CDATA #IMPLIED
+ type (connect | authenticate | sql | close | bind | parse | cancel|call| sync | execute | describe | flush | copy | copydone| copyfail) #REQUIRED >
+
+<!ELEMENT mysql (#PCDATA) >
+<!ATTLIST mysql
+ password CDATA #IMPLIED
+ database CDATA #IMPLIED
+ username CDATA #IMPLIED
+ type (connect | authenticate | sql | close) #REQUIRED >
+
+<!ELEMENT raw EMPTY >
+<!ATTLIST raw
+ ack (global | local | no_ack) #REQUIRED
+ datasize CDATA #IMPLIED
+ data CDATA #IMPLIED>
+
+<!ELEMENT ldap (attr* | modification*) >
+<!ATTLIST ldap
+ password CDATA #IMPLIED
+ user CDATA #IMPLIED
+ type (bind | unbind | search | start_tls | add | modify ) #REQUIRED
+ result_var CDATA #IMPLIED
+ filter CDATA #IMPLIED
+ base CDATA #IMPLIED
+ scope (singleLevel | baseObject | wholeSubtree) #IMPLIED
+ cacertfile CDATA #IMPLIED
+ keyfile CDATA #IMPLIED
+ certfile CDATA #IMPLIED
+ dn CDATA #IMPLIED
+ >
+
+<!ELEMENT modification (attr*) >
+<!ATTLIST modification
+ type CDATA #REQUIRED>
+
+
+<!ELEMENT attr (value+) >
+<!ATTLIST attr
+ type CDATA #REQUIRED>
+
+<!ELEMENT value (#PCDATA) >
+
+
+<!ELEMENT setdynvars (var*) >
+<!ATTLIST setdynvars
+ sourcetype (random_string | urandom_string | random_number |
+ file | erlang | eval| jsonpath) #REQUIRED
+ callback CDATA #IMPLIED
+ code CDATA #IMPLIED
+ fileid CDATA #IMPLIED
+ order (iter | random ) #IMPLIED
+ delimiter CDATA #IMPLIED
+ length CDATA #IMPLIED
+ start CDATA #IMPLIED
+ end CDATA #IMPLIED
+ from CDATA #IMPLIED
+ jsonpath CDATA #IMPLIED
+ >
+<!ELEMENT var (#PCDATA) >
+<!ATTLIST var
+ name CDATA #REQUIRED>
+
+<!ELEMENT for (request | thinktime | transaction | setdynvars | for |
+ if | repeat | change_type | foreach )+>
+<!ATTLIST for
+ var CDATA #REQUIRED
+ from CDATA #REQUIRED
+ to CDATA #REQUIRED
+ incr NMTOKEN "1">
+
+<!ELEMENT foreach (request | thinktime | transaction | setdynvars | foreach |
+ if | repeat | change_type | for )+>
+<!ATTLIST foreach
+ name NMTOKEN #REQUIRED
+ in NMTOKEN #REQUIRED
+ include CDATA #IMPLIED
+ exclude CDATA #IMPLIED
+>
+
+<!ELEMENT repeat (request | thinktime | transaction | setdynvars | for | repeat
+| while | if | until | change_type | foreach )+>
+<!ATTLIST repeat
+ name NMTOKEN #REQUIRED
+ max_repeat NMTOKEN "20">
+
+<!ELEMENT if (request | thinktime | transaction | setdynvars | for | repeat
+| while | if | until | change_type | foreach)+>
+<!ATTLIST if
+ var CDATA #REQUIRED
+ eq CDATA #IMPLIED
+ neq CDATA #IMPLIED >
+
+<!ELEMENT while EMPTY>
+<!ATTLIST while
+ var CDATA #REQUIRED
+ eq CDATA #IMPLIED
+ neq CDATA #IMPLIED >
+
+<!ELEMENT until EMPTY>
+<!ATTLIST until
+ var CDATA #REQUIRED
+ eq CDATA #IMPLIED
+ neq CDATA #IMPLIED >
+
33 websocket.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+<!DOCTYPE tsung SYSTEM "tsung-1.0.dtd">
+<tsung loglevel="debug">
+
+ <clients>
+ <client host="localhost" use_controller_vm="true"/>
+ </clients>
+
+ <!-- Server side setup -->
+ <servers>
+ <server host="127.0.0.1" port="8081" type="ssl"/>
+ </servers>
+
+ <monitoring>
+ <monitor host="localhost"/>
+ </monitoring>
+
+ <load>
+ <arrivalphase phase="1" duration="1" unit="second">
+ <users interarrival="1" unit="second"></users>
+ </arrivalphase>
+ </load>
+
+ <sessions>
+ <session probability="100" name="ws" type="ts_websocket">
+
+ <request>
+ <websocket type="connect" url="/websocket" />
+ </request>
+
+ </session>
+ </sessions>
+</tsung>

0 comments on commit e157192

Please sign in to comment.
Something went wrong with that request. Please try again.