Socket.io: Loadtest using jmeter howto connect to namespace

Created on 26 Nov 2014  Â·  13Comments  Â·  Source: socketio/socket.io

I'm trying to loadtest my solution using jmeter. Since I use namespaces I need to know how I can connect to the specific namespace in order to send my messages.

I have been using google chrome devtools in order to figure out how the connection to a specific namespace is established, but somehow I can't find it.

Our loadtest currently fails as soon as we try to emit on a namespace.
On the server I can see the code for the namespace is never executed, which means we actually never connect.

Does anybody know how to connect to the namespace using httprequest or websocket?
Or is this completely handled in the javascript, which makes this actually completely untestable?

Most helpful comment

OK, after reverse engineering socket.io(v0.9.17), here are the steps to connect:

1: connect to socket in order to get an Socket ID:
sampler: HTTP sampler
method: get
path: /socket.io/1/websocket
2: use a Regular Expression Extractor post processor
field: Body
Reference Name: socketId
Regular Expression: (.+?):60:60:
template: $1$
match num: $1$
3: upgrade to socket using the socketId variable we just created with any params your server expects
sampler: websocket sampler
path: /socket.io/1/websocket/${socketId}
streaming connection: true
4: connect to a namespace(optional)
sampler: websocket sampler
Request Data: 1::/path/to/namespace
5: send payload to server
sampler: websocket sampler
request data: 5::/path/to/namespace:{"name":"tacos","args":["cheese", "salsa", "carne asada"]}
6: ping server to keep connection alive
sampler: websocket sampler
request data: 2::probe

If you are looping this multiple times, make sure to put the connect to namespace in a once only logic controller, otherwise, it will cause your server to send responses multiple times.

All 13 comments

Same issue on my side.

Like Marco, I use JMeter with the WebSocketSampler plugin ( https://github.com/maciejzaleski/JMeter-WebSocketSampler ).

Can someone help us?

Socket.IO is not just websocket or http, but has own protocols on top on them, thus I think using JMeter is not easy (or impossible).
Please refer the following for protocols.

Thank you @nkzawa for your answer. What we did is generating exactly the same requests as you will see in the chrome devtools. We are aware of this protocol and also used it to do the actual socket connection. However as soon as I send a message to my namespace our jMeter script disconnects. We know it is most probably caused by the fact that the server never really connects to the namespace when we do exactly the same requests as you can see in the google devtools network tab.

So what we do very simply.

GET HTTP/socket.io/?EIO=3&transport=polling&t=1417083573418-0
OPTIONS HTTP  /socket.io/?EIO=3&transport=polling&t=1417083577725-1&sid=vgBVmF_jpLhlexsQAAAB
GET HTTP /socket.io/?EIO=3&transport=polling&t=1417083577728-2&sid=vgBVmF_jpLhlexsQAAAB
GET WS /socket.io/?EIO=3&transport=websocket&sid=vgBVmF_jpLhlexsQAAAB
POST HTTP /socket.io/?EIO=3&transport=polling&t=1417083577725-1&sid=vgBVmF_jpLhlexsQAAAB

This establishes the websocket connection...

On the socket we have the following messages in Chrome devtools which we do exactly the same in jMeter: (+ send - receive)

+ 3
+ 2
+ 3
+ 2
- 43/notifications,0[{"successfull": true}]
+ 42/notifications,0["listenNotifications", {"userId":6}]
+ 5
- probe3

On the server this result in following when using the webpage:

Websocket connected
Namespace notifications connected
Notifications: Received message listen for notifications

However when we do the same using jMeter the following happens on the server:

Websocket connected
Websocket disconnected **This happens as soon as we sent the message to start listening for notifications**

In the past we have been using socket.io 0.9 which we where perfectly able to test using jmeter by sending the data as defined in the protocol for socket.io 0.9. However at that point in time we didn't had any namespaces.

Hope this clarifies a little bit more. Looking forward to an answer.

Can you enable debug logs (DEBUG=* node server), and post a dump of server logs?

Log when using the webpage

  socket.io:server initializing namespace / +0ms
  socket.io:server initializing namespace /presence +101ms
  socket.io:server initializing namespace /notifications +2ms
  socket.io:server creating http server and binding to 3000 +2ms
  socket.io:server creating engine.io instance with opts {"path":"/socket.io"} +3ms
  socket.io:server attaching client serving req handler +3ms
  engine intercepting request for path "/socket.io/" +0ms
  engine handling "GET" http request "/socket.io/?EIO=3&transport=polling&t=1417090905372-8" +1ms
  engine handshaking client "TYQy83VYbEKgWSc3AAAA" +4ms
  engine:socket sending packet "open" ({"sid":"TYQy83VYbEKgWSc3AAAA","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":60000}) +1ms
  engine:polling setting request +13ms
  engine:socket flushing buffer to transport +0ms
  engine:polling writing "      �0{"sid":"TYQy83VYbEKgWSc3AAAA","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":60000}" +2ms
  engine:socket executing batch send callback +18ms
  socket.io:server incoming connection with id TYQy83VYbEKgWSc3AAAA +62ms
  socket.io:client connecting to namespace / +0ms
  socket.io:namespace adding socket to nsp / +0ms
  engine intercepting request for path "/socket.io/" +31ms
  engine handling "GET" http request "/socket.io/?EIO=3&transport=polling&t=1417090906214-10&sid=TYQy83VYbEKgWSc3AAAA" +0ms
  engine setting new request for existing client +1ms
  engine:polling setting request +0ms
  socket.io:socket socket connected - writing packet +0ms
  socket.io:socket joining room TYQy83VYbEKgWSc3AAAA +0ms
  socket.io:client writing packet {"type":0,"nsp":"/"} +56ms
  socket.io-parser encoding packet {"type":0,"nsp":"/"} +0ms
  socket.io-parser encoded {"type":0,"nsp":"/"} as 0 +0ms
  engine:socket sending packet "message" (0) +43ms
  engine:socket flushing buffer to transport +1ms
  engine:polling writing "�40" +0ms
  socket.io:socket joined room TYQy83VYbEKgWSc3AAAA +2ms
  engine intercepting request for path "/socket.io/" +2ms
  engine handling "OPTIONS" http request "/socket.io/?EIO=3&transport=polling&t=1417090906212-9&sid=TYQy83VYbEKgWSc3AAAA" +1ms
  engine setting new request for existing client +1ms
  engine upgrading existing transport +9ms
  engine:socket might upgrade socket transport from "polling" to "websocket" +1ms
  engine:ws received "2probe" +2ms
  engine:ws writing "3probe" +1ms
  engine intercepting request for path "/socket.io/" +1ms
  engine handling "POST" http request "/socket.io/?EIO=3&transport=polling&t=1417090906212-9&sid=TYQy83VYbEKgWSc3AAAA" +1ms
  engine setting new request for existing client +0ms
  engine:polling received "�40/notifications" +1ms
  engine:socket packet +20ms
  socket.io-parser decoded 0/notifications as {"type":0,"nsp":"/notifications"} +42ms
  socket.io:client connecting to namespace /notifications +42ms
  socket.io:namespace adding socket to nsp /notifications +98ms
  socket.io:socket socket connected - writing packet +44ms
  socket.io:socket joining room TYQy83VYbEKgWSc3AAAA +0ms
  socket.io:client writing packet {"type":0,"nsp":"/notifications"} +3ms
  socket.io-parser encoding packet {"type":0,"nsp":"/notifications"} +3ms
  socket.io-parser encoded {"type":0,"nsp":"/notifications"} as 0/notifications +1ms
  engine:socket sending packet "message" (0/notifications) +5ms
  socket.io:socket joined room TYQy83VYbEKgWSc3AAAA +2ms
  engine intercepting request for path "/socket.io/" +2ms
  engine handling "GET" http request "/socket.io/?EIO=3&transport=polling&t=1417090906303-11&sid=TYQy83VYbEKgWSc3AAAA" +0ms
  engine setting new request for existing client +1ms
  engine:polling setting request +0ms
  engine:socket flushing buffer to transport +0ms
  engine:polling writing "�40/notifications" +0ms
  engine:socket executing batch send callback +0ms
  engine:ws received "5" +20ms
  engine:socket got upgrade packet - upgrading +0ms
  engine:ws received "42/notifications,1["listenNotifications",{"userId":6}]" +1s
  engine:socket packet +0ms
  socket.io-parser decoded 2/notifications,1["listenNotifications",{"userId":6}] as {"type":2,"nsp":"/notifications","id":1,"data":["listenNotifications",{"userId":6}]} +1s
  socket.io:socket got packet {"type":2,"nsp":"/notifications","id":1,"data":["listenNotifications",{"userId":6}]} +1s
  socket.io:socket emitting event ["listenNotifications",{"userId":6}] +0ms
  socket.io:socket attaching ack callback to event +1ms
  socket.io:socket sending ack [{"successfull":true,"userId":6,"count":null}] +5ms
  socket.io:client writing packet {"id":1,"type":3,"data":[{"successfull":true,"userId":6,"count":null}],"nsp":"/notifications"} +1s
  socket.io-parser encoding packet {"id":1,"type":3,"data":[{"successfull":true,"userId":6,"count":null}],"nsp":"/notifications"} +7ms
  socket.io-parser encoded {"id":1,"type":3,"data":[{"successfull":true,"userId":6,"count":null}],"nsp":"/notifications"} as 3/notifications,1[{"successfull":true,"userId":6,"count":null}] +1ms
  engine:socket sending packet "message" (3/notifications,1[{"successfull":true,"userId":6,"count":null}]) +9ms
  engine:socket flushing buffer to transport +0ms
  engine:ws writing "43/notifications,1[{"successfull":true,"userId":6,"count":null}]" +2ms
  engine:socket executing batch send callback +2ms
  socket.io-redis ignore different namespace /notifications <=> /  +0ms
  socket.io-redis ignore different namespace /notifications <=> /presence  +12ms
  socket.io-parser encoding packet {"type":2,"data":["notify",{"userId":6,"count":4}],"nsp":"/notifications"} +0ms
  socket.io-parser encoded {"type":2,"data":["notify",{"userId":6,"count":4}],"nsp":"/notifications"} as 2/notifications,["notify",{"userId":6,"count":4}] +9ms
  socket.io-redis ignore different namespace /notifications <=> /  +11ms
  socket.io-redis ignore different namespace /notifications <=> /presence  +0ms
  socket.io-parser encoding packet {"type":2,"data":["notify",{"userId":6,"count":4}],"nsp":"/notifications"} +1ms
  socket.io-parser encoded {"type":2,"data":["notify",{"userId":6,"count":4}],"nsp":"/notifications"} as 2/notifications,["notify",{"userId":6,"count":4}] +2ms
  socket.io:client writing packet ["2/notifications,[\"notify\",{\"userId\":6,\"count\":4}]"] +39ms
  engine:socket sending packet "message" (2/notifications,["notify",{"userId":6,"count":4}]) +35ms
  engine:socket flushing buffer to transport +0ms
  engine:ws writing "42/notifications,["notify",{"userId":6,"count":4}]" +0ms
  socket.io-redis ignore different namespace /notifications <=> /  +4ms
  socket.io-redis ignore different namespace /notifications <=> /presence  +0ms
  socket.io-parser encoding packet {"type":2,"data":["notify",{"userId":6,"count":4}],"nsp":"/notifications"} +2ms
  socket.io-parser encoded {"type":2,"data":["notify",{"userId":6,"count":4}],"nsp":"/notifications"} as 2/notifications,["notify",{"userId":6,"count":4}] +0ms
  socket.io-redis ignore different namespace /notifications <=> /  +0ms
  socket.io-redis ignore different namespace /notifications <=> /presence  +1ms
  socket.io-parser encoding packet {"type":2,"data":["notify",{"userId":6,"count":4}],"nsp":"/notifications"} +1ms
  socket.io-parser encoded {"type":2,"data":["notify",{"userId":6,"count":4}],"nsp":"/notifications"} as 2/notifications,["notify",{"userId":6,"count":4}] +1ms
  socket.io:client writing packet ["2/notifications,[\"notify\",{\"userId\":6,\"count\":4}]"] +4ms
  engine:socket sending packet "message" (2/notifications,["notify",{"userId":6,"count":4}]) +3ms
  engine:socket flushing buffer to transport +0ms
  engine:ws writing "42/notifications,["notify",{"userId":6,"count":4}]" +0ms
  engine:ws received "2" +22s
  engine:socket packet +1ms
  engine:socket got ping +0ms
  engine:socket sending packet "pong" (undefined) +0ms
  engine:socket flushing buffer to transport +0ms
  engine:ws writing "3" +0ms
  engine:ws received "2" +3s
  engine:socket packet +0ms
  engine:socket got ping +1ms
  engine:socket sending packet "pong" (undefined) +0ms
  engine:socket flushing buffer to transport +0ms
  engine:ws writing "3" +0ms

When using jMeter

  socket.io:server initializing namespace / +0ms
  socket.io:server initializing namespace /presence +98ms
  socket.io:server initializing namespace /notifications +2ms
  socket.io:server creating http server and binding to 3000 +2ms
  socket.io:server creating engine.io instance with opts {"path":"/socket.io"} +2ms
  socket.io:server attaching client serving req handler +6ms
  socket.io-redis ignore different namespace /notifications <=> /  +0ms
  socket.io-redis ignore different namespace /notifications <=> /presence  +0ms
  socket.io-parser encoding packet {"type":2,"data":["notify",{"userId":6,"count":null}],"nsp":"/notifications"} +0ms
  socket.io-parser encoded {"type":2,"data":["notify",{"userId":6,"count":null}],"nsp":"/notifications"} as 2/notifications,["notify",{"userId":6,"count":null}] +1ms
  socket.io-redis ignore different namespace /notifications <=> /  +6ms
  socket.io-redis ignore different namespace /notifications <=> /presence  +0ms
  socket.io-parser encoding packet {"type":2,"data":["notify",{"userId":6,"count":4}],"nsp":"/notifications"} +4ms
  socket.io-parser encoded {"type":2,"data":["notify",{"userId":6,"count":4}],"nsp":"/notifications"} as 2/notifications,["notify",{"userId":6,"count":4}] +0ms
  engine intercepting request for path "/socket.io/" +0ms
  engine handling "GET" http request "/socket.io/?EIO=3&transport=polling&t=1417092200015-0" +1ms
  engine handshaking client "OkhjEQyA3sz4x-rGAAAA" +4ms
  engine:socket sending packet "open" ({"sid":"OkhjEQyA3sz4x-rGAAAA","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":60000}) +0ms
  engine:polling setting request +2ms
  engine:socket flushing buffer to transport +0ms
  engine:polling writing "      �0{"sid":"OkhjEQyA3sz4x-rGAAAA","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":60000}" +2ms
  engine:socket executing batch send callback +3ms
  socket.io:server incoming connection with id OkhjEQyA3sz4x-rGAAAA +3s
  socket.io:client connecting to namespace / +0ms
  socket.io:namespace adding socket to nsp / +0ms
  engine intercepting request for path "/socket.io/" +30ms
  engine handling "OPTIONS" http request "/socket.io/?EIO=3&transport=polling&t=1417092200035-1&sid=OkhjEQyA3sz4x-rGAAAA" +0ms
  engine setting new request for existing client +0ms
  socket.io:socket socket connected - writing packet +0ms
  socket.io:socket joining room OkhjEQyA3sz4x-rGAAAA +1ms
  socket.io:client writing packet {"type":0,"nsp":"/"} +37ms
  socket.io-parser encoding packet {"type":0,"nsp":"/"} +0ms
  socket.io-parser encoded {"type":0,"nsp":"/"} as 0 +1ms
  engine:socket sending packet "message" (0) +11ms
  socket.io:socket joined room OkhjEQyA3sz4x-rGAAAA +2ms
  engine intercepting request for path "/socket.io/" +1ms
  engine handling "GET" http request "/socket.io/?EIO=3&transport=polling&t=1417092200067-2&sid=OkhjEQyA3sz4x-rGAAAA" +0ms
  engine setting new request for existing client +1ms
  engine:polling setting request +0ms
  engine:socket flushing buffer to transport +1ms
  engine:polling writing "�40" +1ms
  engine:socket executing batch send callback +1ms
  engine upgrading existing transport +66ms
  engine:socket might upgrade socket transport from "polling" to "websocket" +0ms
  engine:ws received "2probe" +23ms
  engine:ws writing "3probe" +1ms
  engine intercepting request for path "/socket.io/" +4ms
  engine handling "GET" http request "/socket.io/?EIO=3&transport=polling&t=1417092200174-3&sid=OkhjEQyA3sz4x-rGAAAA" +0ms
  engine setting new request for existing client +1ms
  engine:polling setting request +0ms
  engine:socket writing a noop packet to polling for fast upgrade +97ms
  engine:polling writing "�6" +1ms
  engine:ws received "40/notifications" +3ms
  engine:ws closing +1ms
  engine:socket client did not complete upgrade - closing transport +10s

Whoaaaaa.... Amazing... I just figured it out.... @nkzawa you made my day :-)

Since we do the 40/notifications on the websocket instead of polling I have to switch this call with the call sending 5.

5 will complete the websocket upgrade. Maybe it is a good to make this more clear in the protocol docs.

So to answer the initial question: How to connect to a namespace...
Simply send 40/namespacename

To do a successfull upgrade make sure the first message sent on the socket equals 5 to complete the upgrade.

Then you are free to send any next messages on the socket using the protocol definition as defined in
https://github.com/Automattic/socket.io-protocol

@marcofranssen Great :smile: Please share what you did if possible. It would be helpful.

I switched the 2 webwsocket calls we had in jmeter.

first send 5 on the websocket
then send 40/namespacename on the websocket

before we had it the other way arround. I figured out when I was examining the log I posted over here.

Also opened a ticket in the repo explaining the protocol since it could be more clarified over there. https://github.com/Automattic/socket.io-protocol/issues/12

I'm not entirely sure what you mean by Simply send 40/namespacename

Is this in the path? If it's the path does should it be /40/namespace?

I'm just not entirely sure what you mean by sending 5 either. I realize this is what is necessary for the socket upgrade. just not sure how you're sending it.

OK, after reverse engineering socket.io(v0.9.17), here are the steps to connect:

1: connect to socket in order to get an Socket ID:
sampler: HTTP sampler
method: get
path: /socket.io/1/websocket
2: use a Regular Expression Extractor post processor
field: Body
Reference Name: socketId
Regular Expression: (.+?):60:60:
template: $1$
match num: $1$
3: upgrade to socket using the socketId variable we just created with any params your server expects
sampler: websocket sampler
path: /socket.io/1/websocket/${socketId}
streaming connection: true
4: connect to a namespace(optional)
sampler: websocket sampler
Request Data: 1::/path/to/namespace
5: send payload to server
sampler: websocket sampler
request data: 5::/path/to/namespace:{"name":"tacos","args":["cheese", "salsa", "carne asada"]}
6: ping server to keep connection alive
sampler: websocket sampler
request data: 2::probe

If you are looping this multiple times, make sure to put the connect to namespace in a once only logic controller, otherwise, it will cause your server to send responses multiple times.

Hi bjoshuanoah ,
I was using the steps same what you just mentioned. My http requets is going fine an di am able to retrieve the id. but the connection is not made. and i get the following error.
[Execution Flow]

  • Opening new connection
  • Using response message pattern ".*"
  • Using disconnect pattern ""
  • Waiting for the server connection for 5000 MILLISECONDS
  • Cannot connect to the remote server

[Variables]

  • Message count: 0

[Problems]

  • Unexpected error: null
    JMeter.plugins.functional.samplers.websocket.ServiceSocket.sendMessage(ServiceSocket.java:156)
    JMeter.plugins.functional.samplers.websocket.WebSocketSampler.sample(WebSocketSampler.java:136)
    org.apache.jmeter.threads.JMeterThread.process_sampler(JMeterThread.java:434)
    org.apache.jmeter.threads.JMeterThread.run(JMeterThread.java:261)
    java.lang.Thread.run(Unknown Source)

Response headers:

SampleResult fields:
ContentType:
DataEncoding: UTF-8

Alls well that ends well. I figured out every puzzel and its working for me. created a test with the plugin and all errors are not gone. sending 3 requests and in between 1 ping lifesign request to keep the connection alive.

Hello @bjoshuanoah ,

I need to know about send payload to server.
I sent request data 2probe , it response 3probe
Now ,
I tried to sent event , but not response
Do you have example other than 5::/path/to/namespace:{"name":"tacos","args":["cheese", "salsa", "carne asada"]}

Thank you

Was this page helpful?
0 / 5 - 0 ratings

Related issues

doughsay picture doughsay  Â·  4Comments

Elliot9 picture Elliot9  Â·  4Comments

Aweather picture Aweather  Â·  4Comments

adammw picture adammw  Â·  4Comments

jloa picture jloa  Â·  4Comments