PsychichHTTP v2-dev
This commit is contained in:
@@ -8,18 +8,18 @@
|
||||
|
||||
*/
|
||||
#include <Arduino.h>
|
||||
#include <WiFi.h>
|
||||
#include <ArduinoJSON.h>
|
||||
#include <LittleFS.h>
|
||||
#include <MongooseCore.h>
|
||||
#include <MongooseHttpServer.h>
|
||||
#include <LittleFS.h>
|
||||
#include <ArduinoJSON.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
const char *ssid = "";
|
||||
const char *password = "";
|
||||
const char* ssid = "";
|
||||
const char* password = "";
|
||||
|
||||
MongooseHttpServer server;
|
||||
|
||||
const char *htmlContent = R"(
|
||||
const char* htmlContent = R"(
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
@@ -154,25 +154,23 @@ void setup()
|
||||
// To debug, please enable Core Debug Level to Verbose
|
||||
if (connectToWifi())
|
||||
{
|
||||
if(!LittleFS.begin())
|
||||
if (!LittleFS.begin())
|
||||
{
|
||||
Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode");
|
||||
return;
|
||||
}
|
||||
|
||||
//start our server
|
||||
// start our server
|
||||
Mongoose.begin();
|
||||
server.begin(80);
|
||||
|
||||
//index file
|
||||
server.on("/", HTTP_GET, [](MongooseHttpServerRequest *request)
|
||||
{
|
||||
request->send(200, "text/html", htmlContent);
|
||||
});
|
||||
// index file
|
||||
server.on("/", HTTP_GET, [](MongooseHttpServerRequest* request)
|
||||
{ request->send(200, "text/html", htmlContent); });
|
||||
|
||||
//api - parameters passed in via query eg. /api/endpoint?foo=bar
|
||||
server.on("/api", HTTP_GET, [](MongooseHttpServerRequest *request)
|
||||
{
|
||||
// api - parameters passed in via query eg. /api/endpoint?foo=bar
|
||||
server.on("/api", HTTP_GET, [](MongooseHttpServerRequest* request)
|
||||
{
|
||||
//create a response object
|
||||
StaticJsonDocument<128> output;
|
||||
output["msg"] = "status";
|
||||
@@ -189,19 +187,18 @@ void setup()
|
||||
//serialize and return
|
||||
String jsonBuffer;
|
||||
serializeJson(output, jsonBuffer);
|
||||
request->send(200, "application/json", jsonBuffer.c_str());
|
||||
});
|
||||
request->send(200, "application/json", jsonBuffer.c_str()); });
|
||||
|
||||
//websocket
|
||||
server.on("/ws$")->
|
||||
onFrame([](MongooseHttpWebSocketConnection *connection, int flags, uint8_t *data, size_t len) {
|
||||
connection->send(WEBSOCKET_OP_TEXT, data, len);
|
||||
//server.sendAll(connection, (char *)data);
|
||||
});
|
||||
// websocket
|
||||
server.on("/ws$")->onFrame([](MongooseHttpWebSocketConnection* connection, int flags, uint8_t* data, size_t len)
|
||||
{
|
||||
connection->send(WEBSOCKET_OP_TEXT, data, len);
|
||||
// server.sendAll(connection, (char *)data);
|
||||
});
|
||||
|
||||
//hack - no servestatic
|
||||
server.on("/alien.png", HTTP_GET, [](MongooseHttpServerRequest *request)
|
||||
{
|
||||
// hack - no servestatic
|
||||
server.on("/alien.png", HTTP_GET, [](MongooseHttpServerRequest* request)
|
||||
{
|
||||
//open our file
|
||||
File fp = LittleFS.open("/www/alien.png");
|
||||
size_t length = fp.size();
|
||||
@@ -223,8 +220,7 @@ void setup()
|
||||
free(data);
|
||||
}
|
||||
else
|
||||
request->send(503);
|
||||
});
|
||||
request->send(503); });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,12 +11,31 @@
|
||||
[env]
|
||||
platform = espressif32
|
||||
framework = arduino
|
||||
board = esp32dev
|
||||
; board = esp32dev
|
||||
board = esp32-s3-devkitc-1
|
||||
upload_port = /dev/ttyACM0
|
||||
monitor_port = /dev/ttyACM1
|
||||
monitor_speed = 115200
|
||||
monitor_filters = esp32_exception_decoder
|
||||
lib_deps =
|
||||
https://github.com/me-no-dev/ESPAsyncWebServer
|
||||
bblanchon/ArduinoJson
|
||||
|
||||
lib_compat_mode = strict
|
||||
lib_ldf_mode = chain
|
||||
lib_deps =
|
||||
; mathieucarbou/AsyncTCP @ 3.2.10
|
||||
https://github.com/mathieucarbou/AsyncTCPSock/archive/refs/tags/v1.0.3-dev.zip
|
||||
mathieucarbou/ESPAsyncWebServer @ 3.3.16
|
||||
bblanchon/ArduinoJson
|
||||
lib_ignore =
|
||||
AsyncTCP
|
||||
mathieucarbou/AsyncTCP
|
||||
|
||||
board_build.filesystem = littlefs
|
||||
build_flags =
|
||||
-D CONFIG_ASYNC_TCP_MAX_ACK_TIME=3000
|
||||
-D CONFIG_ASYNC_TCP_PRIORITY=10
|
||||
-D CONFIG_ASYNC_TCP_QUEUE_SIZE=128
|
||||
-D CONFIG_ASYNC_TCP_RUNNING_CORE=1
|
||||
-D CONFIG_ASYNC_TCP_STACK_SIZE=4096
|
||||
-D WS_MAX_QUEUED_MESSAGES=128
|
||||
|
||||
[env:default]
|
||||
@@ -7,19 +7,29 @@
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
||||
*/
|
||||
#include "_secret.h"
|
||||
#include <Arduino.h>
|
||||
#include <WiFi.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include <LittleFS.h>
|
||||
#include <ArduinoJSON.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
const char *ssid = "";
|
||||
const char *password = "";
|
||||
#ifndef WIFI_SSID
|
||||
#error "You need to enter your wifi credentials. Copy secret.h to _secret.h and enter your credentials there."
|
||||
#endif
|
||||
|
||||
// Enter your WIFI credentials in secret.h
|
||||
const char* ssid = WIFI_SSID;
|
||||
const char* password = WIFI_PASS;
|
||||
|
||||
// hostname for mdns (psychic.local)
|
||||
const char* local_hostname = "psychic";
|
||||
|
||||
AsyncWebServer server(80);
|
||||
AsyncWebSocket ws("/ws");
|
||||
|
||||
const char *htmlContent = R"(
|
||||
const char* htmlContent = R"(
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
@@ -79,14 +89,16 @@ const char *htmlContent = R"(
|
||||
</html>
|
||||
)";
|
||||
|
||||
const size_t htmlContentLen = strlen(htmlContent);
|
||||
|
||||
bool connectToWifi()
|
||||
{
|
||||
Serial.println();
|
||||
Serial.print("[WiFi] Connecting to ");
|
||||
Serial.println(ssid);
|
||||
|
||||
WiFi.setSleep(false);
|
||||
WiFi.useStaticBuffers(true);
|
||||
// WiFi.setSleep(false);
|
||||
// WiFi.useStaticBuffers(true);
|
||||
|
||||
WiFi.begin(ssid, password);
|
||||
|
||||
@@ -95,10 +107,8 @@ bool connectToWifi()
|
||||
int numberOfTries = 20;
|
||||
|
||||
// Wait for the WiFi event
|
||||
while (true)
|
||||
{
|
||||
switch (WiFi.status())
|
||||
{
|
||||
while (true) {
|
||||
switch (WiFi.status()) {
|
||||
case WL_NO_SSID_AVAIL:
|
||||
Serial.println("[WiFi] SSID not found");
|
||||
break;
|
||||
@@ -128,15 +138,12 @@ bool connectToWifi()
|
||||
}
|
||||
delay(tryDelay);
|
||||
|
||||
if (numberOfTries <= 0)
|
||||
{
|
||||
if (numberOfTries <= 0) {
|
||||
Serial.print("[WiFi] Failed to connect to WiFi!");
|
||||
// Use disconnect function to force stop trying to connect
|
||||
WiFi.disconnect();
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
numberOfTries--;
|
||||
}
|
||||
}
|
||||
@@ -144,28 +151,29 @@ bool connectToWifi()
|
||||
return false;
|
||||
}
|
||||
|
||||
void onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
|
||||
if(type == WS_EVT_CONNECT){
|
||||
//client connected
|
||||
// Serial.printf("ws[%s][%u] connect\n", server->url(), client->id());
|
||||
// client->printf("Hello Client %u :)", client->id());
|
||||
// client->ping();
|
||||
} else if(type == WS_EVT_DISCONNECT){
|
||||
//client disconnected
|
||||
// Serial.printf("ws[%s][%u] disconnect: %u\n", server->url(), client->id());
|
||||
} else if(type == WS_EVT_ERROR){
|
||||
//error was received from the other end
|
||||
// Serial.printf("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t*)arg), (char*)data);
|
||||
} else if(type == WS_EVT_PONG){
|
||||
//pong message was received (in response to a ping request maybe)
|
||||
// Serial.printf("ws[%s][%u] pong[%u]: %s\n", server->url(), client->id(), len, (len)?(char*)data:"");
|
||||
} else if(type == WS_EVT_DATA){
|
||||
//data packet
|
||||
AwsFrameInfo * info = (AwsFrameInfo*)arg;
|
||||
if(info->final && info->index == 0 && info->len == len){
|
||||
//the whole message is in a single frame and we got all of it's data
|
||||
// Serial.printf("ws[%s][%u] %s-message[%llu]: ", server->url(), client->id(), (info->opcode == WS_TEXT)?"text":"binary", info->len);
|
||||
if(info->opcode == WS_TEXT){
|
||||
void onEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len)
|
||||
{
|
||||
if (type == WS_EVT_CONNECT) {
|
||||
// client connected
|
||||
// Serial.printf("ws[%s][%u] connect\n", server->url(), client->id());
|
||||
// client->printf("Hello Client %u :)", client->id());
|
||||
// client->ping();
|
||||
} else if (type == WS_EVT_DISCONNECT) {
|
||||
// client disconnected
|
||||
// Serial.printf("ws[%s][%u] disconnect: %u\n", server->url(), client->id());
|
||||
} else if (type == WS_EVT_ERROR) {
|
||||
// error was received from the other end
|
||||
// Serial.printf("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t*)arg), (char*)data);
|
||||
} else if (type == WS_EVT_PONG) {
|
||||
// pong message was received (in response to a ping request maybe)
|
||||
// Serial.printf("ws[%s][%u] pong[%u]: %s\n", server->url(), client->id(), len, (len)?(char*)data:"");
|
||||
} else if (type == WS_EVT_DATA) {
|
||||
// data packet
|
||||
AwsFrameInfo* info = (AwsFrameInfo*)arg;
|
||||
if (info->final && info->index == 0 && info->len == len) {
|
||||
// the whole message is in a single frame and we got all of it's data
|
||||
// Serial.printf("ws[%s][%u] %s-message[%llu]: ", server->url(), client->id(), (info->opcode == WS_TEXT)?"text":"binary", info->len);
|
||||
if (info->opcode == WS_TEXT) {
|
||||
data[len] = 0;
|
||||
// Serial.printf("%s\n", (char*)data);
|
||||
} else {
|
||||
@@ -174,22 +182,21 @@ void onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
|
||||
// }
|
||||
// Serial.printf("\n");
|
||||
}
|
||||
if(info->opcode == WS_TEXT)
|
||||
{
|
||||
client->text((char *)data, len);
|
||||
if (info->opcode == WS_TEXT) {
|
||||
client->text((char*)data, len);
|
||||
}
|
||||
// else
|
||||
// client->binary("I got your binary message");
|
||||
} else {
|
||||
//message is comprised of multiple frames or the frame is split into multiple packets
|
||||
if(info->index == 0){
|
||||
// message is comprised of multiple frames or the frame is split into multiple packets
|
||||
if (info->index == 0) {
|
||||
// if(info->num == 0)
|
||||
// Serial.printf("ws[%s][%u] %s-message start\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary");
|
||||
// Serial.printf("ws[%s][%u] frame[%u] start[%llu]\n", server->url(), client->id(), info->num, info->len);
|
||||
}
|
||||
|
||||
Serial.printf("ws[%s][%u] frame[%u] %s[%llu - %llu]: ", server->url(), client->id(), info->num, (info->message_opcode == WS_TEXT)?"text":"binary", info->index, info->index + len);
|
||||
if(info->message_opcode == WS_TEXT){
|
||||
Serial.printf("ws[%s][%u] frame[%u] %s[%llu - %llu]: ", server->url(), client->id(), info->num, (info->message_opcode == WS_TEXT) ? "text" : "binary", info->index, info->index + len);
|
||||
if (info->message_opcode == WS_TEXT) {
|
||||
data[len] = 0;
|
||||
// Serial.printf("%s\n", (char*)data);
|
||||
} else {
|
||||
@@ -199,13 +206,12 @@ void onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
|
||||
// Serial.printf("\n");
|
||||
}
|
||||
|
||||
if((info->index + len) == info->len){
|
||||
if ((info->index + len) == info->len) {
|
||||
// Serial.printf("ws[%s][%u] frame[%u] end[%llu]\n", server->url(), client->id(), info->num, info->len);
|
||||
if(info->final){
|
||||
if (info->final) {
|
||||
// Serial.printf("ws[%s][%u] %s-message end\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary");
|
||||
if(info->message_opcode == WS_TEXT)
|
||||
{
|
||||
client->text((char *)data, info->len);
|
||||
if (info->message_opcode == WS_TEXT) {
|
||||
client->text((char*)data, info->len);
|
||||
}
|
||||
// else
|
||||
// client->binary("I got your binary message");
|
||||
@@ -223,40 +229,41 @@ void setup()
|
||||
|
||||
// We start by connecting to a WiFi network
|
||||
// To debug, please enable Core Debug Level to Verbose
|
||||
if (connectToWifi())
|
||||
{
|
||||
if(!LittleFS.begin())
|
||||
{
|
||||
if (connectToWifi()) {
|
||||
// set up our esp32 to listen on the local_hostname.local domain
|
||||
if (!MDNS.begin(local_hostname)) {
|
||||
Serial.println("Error starting mDNS");
|
||||
return;
|
||||
}
|
||||
MDNS.addService("http", "tcp", 80);
|
||||
|
||||
if (!LittleFS.begin()) {
|
||||
Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode");
|
||||
return;
|
||||
}
|
||||
|
||||
//api - parameters passed in via query eg. /api/endpoint?foo=bar
|
||||
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
|
||||
{
|
||||
request->send(200, "text/html", htmlContent);
|
||||
// api - parameters passed in via query eg. /api/endpoint?foo=bar
|
||||
server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||
// ESPAsyncWebServer, sending a char* does a buffer copy, unlike Psychic.
|
||||
// Sending flash data is done with the uint8_t* overload.
|
||||
request->send(200, "text/html", (uint8_t*)htmlContent, htmlContentLen);
|
||||
});
|
||||
|
||||
//serve static files from LittleFS/www on /
|
||||
server.serveStatic("/", LittleFS, "/www/");
|
||||
|
||||
//api - parameters passed in via query eg. /api/endpoint?foo=bar
|
||||
server.on("/api", HTTP_GET, [](AsyncWebServerRequest *request)
|
||||
{
|
||||
//create a response object
|
||||
StaticJsonDocument<128> output;
|
||||
// api - parameters passed in via query eg. /api/endpoint?foo=bar
|
||||
server.on("/api", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||
// create a response object
|
||||
JsonDocument output;
|
||||
output["msg"] = "status";
|
||||
output["status"] = "success";
|
||||
output["millis"] = millis();
|
||||
|
||||
//work with some params
|
||||
if (request->hasParam("foo"))
|
||||
{
|
||||
AsyncWebParameter* foo = request->getParam("foo");
|
||||
// work with some params
|
||||
if (request->hasParam("foo")) {
|
||||
const AsyncWebParameter* foo = request->getParam("foo");
|
||||
output["foo"] = foo->value();
|
||||
}
|
||||
|
||||
//serialize and return
|
||||
// serialize and return
|
||||
String jsonBuffer;
|
||||
serializeJson(output, jsonBuffer);
|
||||
request->send(200, "application/json", jsonBuffer.c_str());
|
||||
@@ -265,6 +272,10 @@ void setup()
|
||||
ws.onEvent(onEvent);
|
||||
server.addHandler(&ws);
|
||||
|
||||
// put this last, otherwise it clogs the other requests
|
||||
// serve static files from LittleFS/www on /
|
||||
server.serveStatic("/", LittleFS, "/www/");
|
||||
|
||||
server.begin();
|
||||
}
|
||||
}
|
||||
@@ -272,5 +283,6 @@ void setup()
|
||||
void loop()
|
||||
{
|
||||
ws.cleanupClients();
|
||||
Serial.printf("Free Heap: %d\n", esp_get_free_heap_size());
|
||||
delay(1000);
|
||||
}
|
||||
2
lib/PsychicHttp/benchmark/espasyncwebserver/src/secret.h
Normal file
2
lib/PsychicHttp/benchmark/espasyncwebserver/src/secret.h
Normal file
@@ -0,0 +1,2 @@
|
||||
#define WIFI_SSID "Your_SSID"
|
||||
#define WIFI_PASS "Your_PASS"
|
||||
@@ -1,7 +1,8 @@
|
||||
#!/usr/bin/env node
|
||||
//stress test the client opening/closing code
|
||||
|
||||
const EventSource = require('eventsource');
|
||||
const url = 'http://192.168.2.131/events';
|
||||
const url = 'http://psychic.local/events';
|
||||
|
||||
async function eventSourceClient() {
|
||||
console.log(`Starting test`);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#!/usr/bin/env node
|
||||
//stress test the http request code
|
||||
|
||||
const axios = require('axios');
|
||||
|
||||
const url = 'http://192.168.2.131/api';
|
||||
const url = 'http://psychic.local/api';
|
||||
const queryParams = {
|
||||
foo: 'bar',
|
||||
foo1: 'bar',
|
||||
|
||||
@@ -1,37 +1,42 @@
|
||||
#!/usr/bin/env bash
|
||||
#Command to install the testers:
|
||||
# npm install -g autocannon
|
||||
# npm install
|
||||
|
||||
TEST_IP="192.168.2.131"
|
||||
TEST_TIME=60
|
||||
LOG_FILE=psychic-http-loadtest.log
|
||||
TEST_IP="psychic.local"
|
||||
TEST_TIME=10
|
||||
#LOG_FILE=psychic-http-loadtest.log
|
||||
LOG_FILE=_psychic-http-loadtest.json
|
||||
RESULTS_FILE=http-loadtest-results.csv
|
||||
TIMEOUT=10000
|
||||
WORKERS=1
|
||||
PROTOCOL=http
|
||||
#PROTOCOL=https
|
||||
|
||||
if test -f "$LOG_FILE"; then
|
||||
rm $LOG_FILE
|
||||
fi
|
||||
echo "url,connections,rps,latency,errors" > $RESULTS_FILE
|
||||
|
||||
for CONCURRENCY in 1 2 3 4 5 6 7 8 9 10 15 20
|
||||
#for CONCURRENCY in 20
|
||||
do
|
||||
printf "\n\nCLIENTS: *** $CONCURRENCY ***\n\n" >> $LOG_FILE
|
||||
echo "Testing $CONCURRENCY clients on $PROTOCOL://$TEST_IP/"
|
||||
#loadtest -c $CONCURRENCY --cores 1 -t $TEST_TIME --timeout $TIMEOUT "$PROTOCOL://$TEST_IP/" --quiet >> $LOG_FILE
|
||||
autocannon -c $CONCURRENCY -w 1 -d $TEST_TIME --renderStatusCodes "$PROTOCOL://$TEST_IP/" >> $LOG_FILE 2>&1
|
||||
printf "\n\n----------------\n\n" >> $LOG_FILE
|
||||
sleep 1
|
||||
autocannon -c $CONCURRENCY -w $WORKERS -d $TEST_TIME -j "$PROTOCOL://$TEST_IP/" > $LOG_FILE
|
||||
node parse-http-test.js $LOG_FILE $RESULTS_FILE
|
||||
sleep 5
|
||||
done
|
||||
|
||||
for CONCURRENCY in 1 2 3 4 5 6 7 8 9 10 15 20
|
||||
do
|
||||
echo "Testing $CONCURRENCY clients on $PROTOCOL://$TEST_IP/api"
|
||||
#loadtest -c $CONCURRENCY --cores 1 -t $TEST_TIME --timeout $TIMEOUT "$PROTOCOL://$TEST_IP/api?foo=bar" --quiet >> $LOG_FILE
|
||||
autocannon -c $CONCURRENCY -w 1 -d $TEST_TIME --renderStatusCodes "$PROTOCOL://$TEST_IP/api?foo=bar" >> $LOG_FILE 2>&1
|
||||
printf "\n\n----------------\n\n" >> $LOG_FILE
|
||||
sleep 1
|
||||
|
||||
autocannon -c $CONCURRENCY -w $WORKERS -d $TEST_TIME -j "$PROTOCOL://$TEST_IP/api?foo=bar" > $LOG_FILE
|
||||
node parse-http-test.js $LOG_FILE $RESULTS_FILE
|
||||
sleep 5
|
||||
done
|
||||
|
||||
for CONCURRENCY in 1 2 3 4 5 6 7 8 9 10 15 20
|
||||
do
|
||||
echo "Testing $CONCURRENCY clients on $PROTOCOL://$TEST_IP/alien.png"
|
||||
#loadtest -c $CONCURRENCY --cores 1 -t $TEST_TIME --timeout $TIMEOUT "$PROTOCOL://$TEST_IP/alien.png" --quiet >> $LOG_FILE
|
||||
autocannon -c $CONCURRENCY -w 1 -d $TEST_TIME --renderStatusCodes "$PROTOCOL://$TEST_IP/alien.png" >> $LOG_FILE 2>&1
|
||||
printf "\n\n----------------\n\n" >> $LOG_FILE
|
||||
sleep 1
|
||||
done
|
||||
autocannon -c $CONCURRENCY -w $WORKERS -d $TEST_TIME -j "$PROTOCOL://$TEST_IP/alien.png" > $LOG_FILE
|
||||
node parse-http-test.js $LOG_FILE $RESULTS_FILE
|
||||
sleep 5
|
||||
done
|
||||
|
||||
rm $LOG_FILE
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
#Command to install the testers:
|
||||
# npm install -g loadtest
|
||||
# npm install
|
||||
|
||||
TEST_IP="192.168.2.131"
|
||||
TEST_IP="psychic.local"
|
||||
TEST_TIME=60
|
||||
LOG_FILE=psychic-websocket-loadtest.log
|
||||
LOG_FILE=psychic-websocket-loadtest.json
|
||||
RESULTS_FILE=websocket-loadtest-results.csv
|
||||
PROTOCOL=ws
|
||||
#PROTOCOL=wss
|
||||
|
||||
@@ -12,20 +13,33 @@ if test -f "$LOG_FILE"; then
|
||||
rm $LOG_FILE
|
||||
fi
|
||||
|
||||
for CONCURRENCY in 1 2 3 4 5 6 7
|
||||
echo "url,clients,rps,latency,errors" > $RESULTS_FILE
|
||||
|
||||
CORES=1
|
||||
for CONCURRENCY in 1 2 3 4 5
|
||||
do
|
||||
printf "\n\nCLIENTS: *** $CONCURRENCY ***\n\n" >> $LOG_FILE
|
||||
echo "Testing $CONCURRENCY clients on $PROTOCOL://$TEST_IP/ws"
|
||||
loadtest -c $CONCURRENCY --cores 1 -t $TEST_TIME --insecure $PROTOCOL://$TEST_IP/ws --quiet 2> /dev/null >> $LOG_FILE
|
||||
sleep 1
|
||||
loadtest -c $CONCURRENCY --cores $CORES -t $TEST_TIME --insecure $PROTOCOL://$TEST_IP/ws --quiet 2> /dev/null >> $LOG_FILE
|
||||
node parse-websocket-test.js $LOG_FILE $RESULTS_FILE
|
||||
sleep 2
|
||||
done
|
||||
|
||||
for CONNECTIONS in 8 10 16 20
|
||||
#for CONNECTIONS in 20
|
||||
CORES=2
|
||||
for CONNECTIONS in 6 8 10 12 14
|
||||
do
|
||||
CONCURRENCY=$((CONNECTIONS / 2))
|
||||
printf "\n\nCLIENTS: *** $CONNECTIONS ***\n\n" >> $LOG_FILE
|
||||
echo "Testing $CONNECTIONS clients on $PROTOCOL://$TEST_IP/ws"
|
||||
loadtest -c $CONCURRENCY --cores 2 -t $TEST_TIME --insecure $PROTOCOL://$TEST_IP/ws --quiet 2> /dev/null >> $LOG_FILE
|
||||
sleep 1
|
||||
done
|
||||
loadtest -c $CONCURRENCY --cores $CORES -t $TEST_TIME --insecure $PROTOCOL://$TEST_IP/ws --quiet 2> /dev/null >> $LOG_FILE
|
||||
node parse-websocket-test.js $LOG_FILE $RESULTS_FILE
|
||||
sleep 2
|
||||
done
|
||||
|
||||
CORES=4
|
||||
for CONNECTIONS in 16 20 24 28 32
|
||||
do
|
||||
CONCURRENCY=$((CONNECTIONS / CORES))
|
||||
echo "Testing $CONNECTIONS clients on $PROTOCOL://$TEST_IP/ws"
|
||||
loadtest -c $CONCURRENCY --cores $CORES -t $TEST_TIME --insecure $PROTOCOL://$TEST_IP/ws --quiet 2> /dev/null >> $LOG_FILE
|
||||
node parse-websocket-test.js $LOG_FILE $RESULTS_FILE
|
||||
sleep 2
|
||||
done
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"autocannon": "^7.15.0",
|
||||
"axios": "^1.6.2",
|
||||
"csv-writer": "^1.6.0",
|
||||
"eventsource": "^2.0.2",
|
||||
"loadtest": "^8.0.9",
|
||||
"ws": "^8.14.2"
|
||||
}
|
||||
}
|
||||
|
||||
55
lib/PsychicHttp/benchmark/parse-http-test.js
Normal file
55
lib/PsychicHttp/benchmark/parse-http-test.js
Normal file
@@ -0,0 +1,55 @@
|
||||
const fs = require('fs');
|
||||
const createCsvWriter = require('csv-writer').createObjectCsvWriter;
|
||||
|
||||
// Get the input and output file paths from the command line arguments
|
||||
const inputFilePath = process.argv[2];
|
||||
const outputFilePath = process.argv[3];
|
||||
|
||||
if (!inputFilePath || !outputFilePath) {
|
||||
console.error('Usage: node script.js <inputFilePath> <outputFilePath>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Read and parse the JSON file
|
||||
fs.readFile(inputFilePath, 'utf8', (err, data) => {
|
||||
if (err) {
|
||||
console.error('Error reading the input file:', err);
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the JSON data
|
||||
const jsonData = JSON.parse(data);
|
||||
|
||||
// Extract the desired fields
|
||||
const { url, connections, latency, requests, errors } = jsonData;
|
||||
const latencyMean = latency.mean;
|
||||
const requestsMean = requests.mean;
|
||||
|
||||
// Set up the CSV writer
|
||||
const csvWriter = createCsvWriter({
|
||||
path: outputFilePath,
|
||||
header: [
|
||||
{id: 'url', title: 'URL'},
|
||||
{id: 'connections', title: 'Connections'},
|
||||
{id: 'requestsMean', title: 'Requests Mean'},
|
||||
{id: 'latencyMean', title: 'Latency Mean'},
|
||||
{id: 'errors', title: 'Errors'},
|
||||
],
|
||||
append: true // this will append to the existing file
|
||||
});
|
||||
|
||||
// Prepare the data to be written
|
||||
const records = [
|
||||
{ url: url, connections: connections, latencyMean: latencyMean, requestsMean: requestsMean, errors: errors }
|
||||
];
|
||||
|
||||
// Write the data to the CSV file
|
||||
csvWriter.writeRecords(records)
|
||||
.then(() => {
|
||||
console.log('Data successfully appended to CSV file.');
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Error writing to the CSV file:', err);
|
||||
});
|
||||
});
|
||||
|
||||
64
lib/PsychicHttp/benchmark/parse-websocket-test.js
Normal file
64
lib/PsychicHttp/benchmark/parse-websocket-test.js
Normal file
@@ -0,0 +1,64 @@
|
||||
const fs = require('fs');
|
||||
const readline = require('readline');
|
||||
|
||||
if (process.argv.length !== 4) {
|
||||
console.error('Usage: node parse-websocket-test.js <input_file> <output_file>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const inputFile = process.argv[2];
|
||||
const outputFile = process.argv[3];
|
||||
|
||||
async function parseFile() {
|
||||
const fileStream = fs.createReadStream(inputFile);
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: fileStream,
|
||||
crlfDelay: Infinity
|
||||
});
|
||||
|
||||
let targetUrl = null;
|
||||
let totalErrors = null;
|
||||
let meanLatency = null;
|
||||
let effectiveRps = null;
|
||||
let concurrentClients = null;
|
||||
|
||||
for await (const line of rl) {
|
||||
if (line.startsWith('Target URL:')) {
|
||||
targetUrl = line.split(':').slice(1).join(':').trim();
|
||||
}
|
||||
if (line.startsWith('Total errors:')) {
|
||||
totalErrors = parseInt(line.split(':')[1].trim(), 10);
|
||||
}
|
||||
if (line.startsWith('Mean latency:')) {
|
||||
meanLatency = parseFloat(line.split(':')[1].trim());
|
||||
}
|
||||
if (line.startsWith('Effective rps:')) {
|
||||
effectiveRps = parseInt(line.split(':')[1].trim(), 10);
|
||||
}
|
||||
if (line.startsWith('Concurrent clients:')) {
|
||||
concurrentClients = parseInt(line.split(':')[1].trim(), 10);
|
||||
}
|
||||
}
|
||||
|
||||
if (targetUrl === null || totalErrors === null || meanLatency === null || effectiveRps === null || concurrentClients === null) {
|
||||
console.error('Failed to extract necessary data from the input file');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const csvLine = `${targetUrl},${concurrentClients},${effectiveRps},${meanLatency},${totalErrors}\n`;
|
||||
|
||||
fs.appendFile(outputFile, csvLine, (err) => {
|
||||
if (err) {
|
||||
console.error('Failed to append to CSV file:', err);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('Data successfully appended to CSV file.');
|
||||
});
|
||||
}
|
||||
|
||||
parseFile().catch(err => {
|
||||
console.error('Error reading file:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@@ -15,8 +15,14 @@ board = esp32dev
|
||||
monitor_speed = 115200
|
||||
monitor_filters = esp32_exception_decoder
|
||||
lib_deps =
|
||||
https://github.com/hoeken/PsychicHttp
|
||||
bblanchon/ArduinoJson
|
||||
board_build.filesystem = littlefs
|
||||
|
||||
[env:default]
|
||||
lib_deps = https://github.com/hoeken/PsychicHttp
|
||||
|
||||
[env:v2-dev]
|
||||
lib_deps = https://github.com/hoeken/PsychicHttp#v2-dev
|
||||
board = esp32-s3-devkitc-1
|
||||
upload_port = /dev/ttyACM0
|
||||
monitor_port = /dev/ttyACM1
|
||||
@@ -7,26 +7,30 @@
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
||||
*/
|
||||
#include <Arduino.h>
|
||||
#include <WiFi.h>
|
||||
#include <PsychicHttp.h>
|
||||
#include <LittleFS.h>
|
||||
#include <ArduinoJSON.h>
|
||||
#include "_secret.h"
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include <LittleFS.h>
|
||||
#include <PsychicHttp.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
#ifndef WIFI_SSID
|
||||
#error "You need to enter your wifi credentials. Copy secret.h to _secret.h and enter your credentials there."
|
||||
#endif
|
||||
|
||||
//Enter your WIFI credentials in secret.h
|
||||
const char *ssid = WIFI_SSID;
|
||||
const char *password = WIFI_PASS;
|
||||
// Enter your WIFI credentials in secret.h
|
||||
const char* ssid = WIFI_SSID;
|
||||
const char* password = WIFI_PASS;
|
||||
|
||||
// hostname for mdns (psychic.local)
|
||||
const char* local_hostname = "psychic";
|
||||
|
||||
PsychicHttpServer server;
|
||||
PsychicWebSocketHandler websocketHandler;
|
||||
PsychicEventSource eventSource;
|
||||
|
||||
const char *htmlContent = R"(
|
||||
const char* htmlContent = R"(
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
@@ -92,8 +96,8 @@ bool connectToWifi()
|
||||
Serial.print("[WiFi] Connecting to ");
|
||||
Serial.println(ssid);
|
||||
|
||||
WiFi.setSleep(false);
|
||||
WiFi.useStaticBuffers(true);
|
||||
// WiFi.setSleep(false);
|
||||
// WiFi.useStaticBuffers(true);
|
||||
|
||||
WiFi.begin(ssid, password);
|
||||
|
||||
@@ -102,10 +106,8 @@ bool connectToWifi()
|
||||
int numberOfTries = 20;
|
||||
|
||||
// Wait for the WiFi event
|
||||
while (true)
|
||||
{
|
||||
switch (WiFi.status())
|
||||
{
|
||||
while (true) {
|
||||
switch (WiFi.status()) {
|
||||
case WL_NO_SSID_AVAIL:
|
||||
Serial.println("[WiFi] SSID not found");
|
||||
break;
|
||||
@@ -135,15 +137,12 @@ bool connectToWifi()
|
||||
}
|
||||
delay(tryDelay);
|
||||
|
||||
if (numberOfTries <= 0)
|
||||
{
|
||||
if (numberOfTries <= 0) {
|
||||
Serial.print("[WiFi] Failed to connect to WiFi!");
|
||||
// Use disconnect function to force stop trying to connect
|
||||
WiFi.disconnect();
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
numberOfTries--;
|
||||
}
|
||||
}
|
||||
@@ -157,47 +156,42 @@ void setup()
|
||||
delay(10);
|
||||
Serial.println("PsychicHTTP Benchmark");
|
||||
|
||||
if (connectToWifi())
|
||||
{
|
||||
if(!LittleFS.begin())
|
||||
{
|
||||
if (connectToWifi()) {
|
||||
// set up our esp32 to listen on the local_hostname.local domain
|
||||
if (!MDNS.begin(local_hostname)) {
|
||||
Serial.println("Error starting mDNS");
|
||||
return;
|
||||
}
|
||||
MDNS.addService("http", "tcp", 80);
|
||||
|
||||
if (!LittleFS.begin()) {
|
||||
Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode");
|
||||
return;
|
||||
}
|
||||
|
||||
//start our server
|
||||
server.listen(80);
|
||||
// our index
|
||||
server.on("/", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { return response->send(200, "text/html", htmlContent); });
|
||||
|
||||
//our index
|
||||
server.on("/", HTTP_GET, [](PsychicRequest *request)
|
||||
{
|
||||
return request->reply(200, "text/html", htmlContent);
|
||||
});
|
||||
|
||||
//serve static files from LittleFS/www on /
|
||||
// serve static files from LittleFS/www on /
|
||||
server.serveStatic("/", LittleFS, "/www/");
|
||||
|
||||
//a websocket echo server
|
||||
websocketHandler.onOpen([](PsychicWebSocketClient *client) {
|
||||
client->sendMessage("Hello!");
|
||||
});
|
||||
websocketHandler.onFrame([](PsychicWebSocketRequest *request, httpd_ws_frame *frame) {
|
||||
request->reply(frame);
|
||||
return ESP_OK;
|
||||
// a websocket echo server
|
||||
websocketHandler.onOpen([](PsychicWebSocketClient* client) {
|
||||
// client->sendMessage("Hello!");
|
||||
});
|
||||
websocketHandler.onFrame([](PsychicWebSocketRequest* request, httpd_ws_frame* frame) {
|
||||
response->send(frame);
|
||||
return ESP_OK; });
|
||||
server.on("/ws", &websocketHandler);
|
||||
|
||||
//EventSource server
|
||||
eventSource.onOpen([](PsychicEventSourceClient *client) {
|
||||
client->send("Hello", NULL, millis(), 1000);
|
||||
});
|
||||
// EventSource server
|
||||
eventSource.onOpen([](PsychicEventSourceClient* client) { client->send("Hello", NULL, millis(), 1000); });
|
||||
server.on("/events", &eventSource);
|
||||
|
||||
//api - parameters passed in via query eg. /api/endpoint?foo=bar
|
||||
server.on("/api", HTTP_GET, [](PsychicRequest *request)
|
||||
{
|
||||
// api - parameters passed in via query eg. /api/endpoint?foo=bar
|
||||
server.on("/api", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) {
|
||||
//create a response object
|
||||
StaticJsonDocument<128> output;
|
||||
JsonDocument output;
|
||||
output["msg"] = "status";
|
||||
output["status"] = "success";
|
||||
output["millis"] = millis();
|
||||
@@ -212,16 +206,16 @@ void setup()
|
||||
//serialize and return
|
||||
String jsonBuffer;
|
||||
serializeJson(output, jsonBuffer);
|
||||
return request->reply(200, "application/json", jsonBuffer.c_str());
|
||||
});
|
||||
return response->send(200, "application/json", jsonBuffer.c_str()); });
|
||||
|
||||
server.begin();
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long last;
|
||||
void loop()
|
||||
{
|
||||
if (millis() - last > 1000)
|
||||
{
|
||||
if (millis() - last > 1000) {
|
||||
Serial.printf("Free Heap: %d\n", esp_get_free_heap_size());
|
||||
last = millis();
|
||||
}
|
||||
|
||||
@@ -7,22 +7,21 @@
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
||||
*/
|
||||
#include <Arduino.h>
|
||||
#include <WiFi.h>
|
||||
#include <PsychicHttp.h>
|
||||
#include <LittleFS.h>
|
||||
#include <ArduinoJSON.h>
|
||||
#include "_secret.h"
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJSON.h>
|
||||
#include <LittleFS.h>
|
||||
#include <PsychicHttp.h>
|
||||
#include <PsychicHttpsServer.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
#ifndef WIFI_SSID
|
||||
#error "You need to enter your wifi credentials. Rename secret.h to _secret.h and enter your credentials there."
|
||||
#endif
|
||||
|
||||
//Enter your WIFI credentials in secret.h
|
||||
const char *ssid = WIFI_SSID;
|
||||
const char *password = WIFI_PASS;
|
||||
// Enter your WIFI credentials in secret.h
|
||||
const char* ssid = WIFI_SSID;
|
||||
const char* password = WIFI_PASS;
|
||||
|
||||
PsychicHttpsServer server;
|
||||
PsychicWebSocketHandler websocketHandler;
|
||||
@@ -30,7 +29,7 @@ PsychicWebSocketHandler websocketHandler;
|
||||
String server_cert;
|
||||
String server_key;
|
||||
|
||||
const char *htmlContent = R"(
|
||||
const char* htmlContent = R"(
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
@@ -162,52 +161,56 @@ void setup()
|
||||
|
||||
if (connectToWifi())
|
||||
{
|
||||
if(!LittleFS.begin())
|
||||
if (!LittleFS.begin())
|
||||
{
|
||||
Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode");
|
||||
return;
|
||||
}
|
||||
|
||||
File fp = LittleFS.open("/server.crt");
|
||||
if (fp) {
|
||||
if (fp)
|
||||
{
|
||||
server_cert = fp.readString();
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println("server.pem not found, SSL not available");
|
||||
return;
|
||||
}
|
||||
fp.close();
|
||||
|
||||
File fp2 = LittleFS.open("/server.key");
|
||||
if (fp2) {
|
||||
if (fp2)
|
||||
{
|
||||
server_key = fp2.readString();
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println("server.key not found, SSL not available");
|
||||
return;
|
||||
}
|
||||
fp2.close();
|
||||
|
||||
//start our server
|
||||
server.listen(443, server_cert.c_str(), server_key.c_str());
|
||||
// start our server
|
||||
server.setCertificate(server_cert.c_str(), server_key.c_str());
|
||||
|
||||
//our index
|
||||
server.on("/", HTTP_GET, [](PsychicRequest *request)
|
||||
{
|
||||
return request->reply(200, "text/html", htmlContent);
|
||||
});
|
||||
// our index
|
||||
server.on("/", HTTP_GET, [](PsychicRequest* request)
|
||||
{ return response->send(200, "text/html", htmlContent); });
|
||||
|
||||
//serve static files from LittleFS/www on /
|
||||
// serve static files from LittleFS/www on /
|
||||
server.serveStatic("/", LittleFS, "/www/");
|
||||
|
||||
//a websocket echo server
|
||||
websocketHandler.onFrame([](PsychicWebSocketRequest *request, httpd_ws_frame *frame) {
|
||||
request->reply(frame);
|
||||
return ESP_OK;
|
||||
});
|
||||
// a websocket echo server
|
||||
websocketHandler.onFrame([](PsychicWebSocketRequest* request, httpd_ws_frame* frame)
|
||||
{
|
||||
response->send(frame);
|
||||
return ESP_OK; });
|
||||
server.on("/ws", &websocketHandler);
|
||||
|
||||
//api - parameters passed in via query eg. /api/endpoint?foo=bar
|
||||
server.on("/api", HTTP_GET, [](PsychicRequest *request)
|
||||
{
|
||||
// api - parameters passed in via query eg. /api/endpoint?foo=bar
|
||||
server.on("/api", HTTP_GET, [](PsychicRequest* request)
|
||||
{
|
||||
//create a response object
|
||||
StaticJsonDocument<128> output;
|
||||
output["msg"] = "status";
|
||||
@@ -224,8 +227,7 @@ void setup()
|
||||
//serialize and return
|
||||
String jsonBuffer;
|
||||
serializeJson(output, jsonBuffer);
|
||||
return request->reply(200, "application/json", jsonBuffer.c_str());
|
||||
});
|
||||
return response->send(200, "application/json", jsonBuffer.c_str()); });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#!/usr/bin/env node
|
||||
//stress test the client open/close for websockets
|
||||
|
||||
const WebSocket = require('ws');
|
||||
|
||||
const uri = 'ws://192.168.2.131/ws';
|
||||
const uri = 'ws://psychic.local/ws';
|
||||
|
||||
async function websocketClient() {
|
||||
console.log(`Starting test`);
|
||||
|
||||
Reference in New Issue
Block a user