I have a Free_F1 Azure SignalR, which should accept between 20 and 25 open connections at a time
I have 2 backend servers using this SignalR plan, dev and testing environments.
Now, I know for a fact that each server using SignalR reserves 5 connections by default. So we have 10 server connections.
Each time we access the website, the number of client connections increases by 1. (see attached graph)
Currently, I am using the website so only 1 client is connected.
Now, in order to test out SignalR performance. I want to increase the number of clients connected using JMeter.
We are going to upgrade to a paid version with 10000 connections so I cannot perform a stress test manually.
Connection is established via 2 api calls (negotiate handshakes)
I am replicating these api calls in JMeter.
In my chat/negotiate call, I send a deviceId param.
On my BE hub configuration, I am accessing this param in order to save the connectionId in the database for this specific device.
So, I know that the negotiate sceam is functioning properly because the connectionIds are in fact getting stored in the database
The issue is, I ran the test on 200 devices, all of them finished successfully on JMeter, 200 new connection Ids where stored in the database and no sign of client connections increasing on Azure signalR metrics. It still shows 1 client connected.
I accessed my BE logs, and indeed the apis all went through successfully.
What is going on? Azure is not even giving a 429 error for too many attempts like it usually did when I was using the ReactJs library with a loop.
I am using the plugin “JMeter WebSocket Samplers by Peter Doornbosch” and I tried WebSocket Single Write sampler using both the existing connection and new connection settings but same output.
I will attach the JMeter test. (Copy code into a a file and set extension to .jmx to open it on JMeter)
<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.6">
<hashTree>
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan">
<elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<boolProp name="TestPlan.serialize_threadgroups">true</boolProp>
</TestPlan>
<hashTree>
<Arguments guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables">
<collectionProp name="Arguments.arguments">
<elementProp name="protocol" elementType="Argument">
<stringProp name="Argument.name">protocol</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<stringProp name="Argument.value">https</stringProp>
</elementProp>
<elementProp name="baseURL" elementType="Argument">
<stringProp name="Argument.name">baseURL</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<stringProp name="Argument.value">my-app.azurewebsites.net</stringProp>
</elementProp>
<elementProp name="loopCount" elementType="Argument">
<stringProp name="Argument.name">loopCount</stringProp>
<stringProp name="Argument.value">1</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="azureBaseURL" elementType="Argument">
<stringProp name="Argument.name">azureBaseURL</stringProp>
<stringProp name="Argument.value">my-app.service.signalr.net</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="deviceIdsFilePath" elementType="Argument">
<stringProp name="Argument.name">deviceIdsFilePath</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<stringProp name="Argument.value">path-to-ids</stringProp>
</elementProp>
</collectionProp>
</Arguments>
<hashTree/>
<ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree">
<boolProp name="ResultCollector.error_logging">false</boolProp>
<objProp>
<name>saveConfig</name>
<value class="SampleSaveConfiguration">
<time>true</time>
<latency>true</latency>
<timestamp>true</timestamp>
<success>true</success>
<label>true</label>
<code>true</code>
<message>true</message>
<threadName>true</threadName>
<dataType>true</dataType>
<encoding>false</encoding>
<assertions>true</assertions>
<subresults>true</subresults>
<responseData>false</responseData>
<samplerData>false</samplerData>
<xml>false</xml>
<fieldNames>true</fieldNames>
<responseHeaders>false</responseHeaders>
<requestHeaders>false</requestHeaders>
<responseDataOnError>false</responseDataOnError>
<saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
<assertionsResultsToSave>0</assertionsResultsToSave>
<bytes>true</bytes>
<sentBytes>true</sentBytes>
<url>true</url>
<threadCounts>true</threadCounts>
<idleTime>true</idleTime>
<connectTime>true</connectTime>
</value>
</objProp>
<stringProp name="filename"></stringProp>
</ResultCollector>
<hashTree/>
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Azure SignalR Authentication">
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller">
<stringProp name="LoopController.loops">${loopCount}</stringProp>
</elementProp>
<stringProp name="ThreadGroup.num_threads">200</stringProp>
<stringProp name="ThreadGroup.ramp_time">1</stringProp>
<boolProp name="ThreadGroup.scheduler">false</boolProp>
<stringProp name="ThreadGroup.duration"></stringProp>
<stringProp name="ThreadGroup.delay">5</stringProp>
</ThreadGroup>
<hashTree>
<CSVDataSet guiclass="TestBeanGUI" testclass="CSVDataSet" testname="Device Ids Scanner">
<stringProp name="delimiter">;</stringProp>
<stringProp name="fileEncoding"></stringProp>
<stringProp name="filename">${deviceIdsFilePath}</stringProp>
<boolProp name="ignoreFirstLine">true</boolProp>
<boolProp name="quotedData">false</boolProp>
<boolProp name="recycle">true</boolProp>
<stringProp name="shareMode">shareMode.all</stringProp>
<boolProp name="stopThread">false</boolProp>
<stringProp name="variableNames">deviceId</stringProp>
</CSVDataSet>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Negotiate">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables">
<collectionProp name="Arguments.arguments">
<elementProp name="negotiateVersion" elementType="HTTPArgument">
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">negotiateVersion</stringProp>
<stringProp name="Argument.value">1</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="HTTPSampler.domain">${baseURL}</stringProp>
<stringProp name="HTTPSampler.protocol">${protocol}</stringProp>
<stringProp name="HTTPSampler.path">/chat/negotiate?deviceId=${__urlencode(${deviceId})}</stringProp>
<stringProp name="HTTPSampler.method">POST</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
</HTTPSamplerProxy>
<hashTree>
<JSONPostProcessor guiclass="JSONPostProcessorGui" testclass="JSONPostProcessor" testname="JSON Extractor">
<stringProp name="JSONPostProcessor.referenceNames">accessToken</stringProp>
<stringProp name="JSONPostProcessor.jsonPathExprs">$.accessToken</stringProp>
<stringProp name="JSONPostProcessor.match_numbers">0</stringProp>
<stringProp name="JSONPostProcessor.defaultValues">no jwt</stringProp>
</JSONPostProcessor>
<hashTree/>
<JSONPostProcessor guiclass="JSONPostProcessorGui" testclass="JSONPostProcessor" testname="JSON Extractor">
<stringProp name="JSONPostProcessor.referenceNames">url</stringProp>
<stringProp name="JSONPostProcessor.jsonPathExprs">$.url</stringProp>
<stringProp name="JSONPostProcessor.match_numbers">0</stringProp>
<stringProp name="JSONPostProcessor.defaultValues">no jwt</stringProp>
</JSONPostProcessor>
<hashTree/>
<RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Regular Expression Extractor">
<stringProp name="RegexExtractor.refname">urlParams</stringProp>
<stringProp name="RegexExtractor.regex">([^?]+)$</stringProp>
<stringProp name="RegexExtractor.template"> $1$</stringProp>
<stringProp name="Sample.scope">variable</stringProp>
<stringProp name="Scope.variable">url</stringProp>
<stringProp name="RegexExtractor.default">nour</stringProp>
</RegexExtractor>
<hashTree/>
</hashTree>
<eu.luminis.jmeter.wssampler.OpenWebSocketSampler guiclass="eu.luminis.jmeter.wssampler.OpenWebSocketSamplerGui" testclass="eu.luminis.jmeter.wssampler.OpenWebSocketSampler" testname="Chat">
<boolProp name="TLS">true</boolProp>
<stringProp name="server">${baseURL}</stringProp>
<stringProp name="port">443</stringProp>
<stringProp name="path">/chat?${__groovy(vars.get("urlParams").trim(),)}</stringProp>
<stringProp name="connectTimeout">20000</stringProp>
<stringProp name="readTimeout">6000</stringProp>
</eu.luminis.jmeter.wssampler.OpenWebSocketSampler>
<hashTree/>
<eu.luminis.jmeter.wssampler.SingleWriteWebSocketSampler guiclass="eu.luminis.jmeter.wssampler.SingleWriteWebSocketSamplerGui" testclass="eu.luminis.jmeter.wssampler.SingleWriteWebSocketSampler" testname="Handshake">
<boolProp name="TLS">true</boolProp>
<stringProp name="server"></stringProp>
<stringProp name="port"></stringProp>
<stringProp name="path"></stringProp>
<stringProp name="connectTimeout">20000</stringProp>
<stringProp name="payloadType">Text</stringProp>
<stringProp name="requestData">{
"protocol": "json",
"version": 1
}</stringProp>
<boolProp name="createNewConnection">false</boolProp>
<boolProp name="loadDataFromFile">false</boolProp>
<stringProp name="dataFile"></stringProp>
</eu.luminis.jmeter.wssampler.SingleWriteWebSocketSampler>
<hashTree>
<HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager">
<collectionProp name="HeaderManager.headers">
<elementProp name="" elementType="Header">
<stringProp name="Header.name">authorization</stringProp>
<stringProp name="Header.value">Bearer ${accessToken}</stringProp>
</elementProp>
<elementProp name="" elementType="Header">
<stringProp name="Header.name">host</stringProp>
<stringProp name="Header.value">${azureBaseURL}</stringProp>
</elementProp>
</collectionProp>
</HeaderManager>
<hashTree/>
</hashTree>
</hashTree>
</hashTree>
</hashTree>
</jmeterTestPlan>