From 811e7cde3aed4064a4f8d2c9eb161183c873ffde Mon Sep 17 00:00:00 2001 From: Lauri Kasanen Date: Mon, 12 Jul 2021 15:42:18 +0300 Subject: [PATCH 01/12] Use case-insensitive search for the websocket headers --- common/network/websocket.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/common/network/websocket.c b/common/network/websocket.c index 72a101c..fd88124 100644 --- a/common/network/websocket.c +++ b/common/network/websocket.c @@ -566,7 +566,7 @@ int parse_handshake(ws_ctx_t *ws_ctx, char *handshake) { headers->key3[0] = '\0'; if ((strlen(handshake) < 92) || (bcmp(handshake, "GET ", 4) != 0) || - (!strstr(handshake, "Upgrade: websocket"))) { + (!strcasestr(handshake, "Upgrade: websocket"))) { return 0; } start = handshake+4; @@ -587,7 +587,7 @@ int parse_handshake(ws_ctx_t *ws_ctx, char *handshake) { if (start) { start += 10; } else { - start = strstr(handshake, "\r\nSec-WebSocket-Origin: "); + start = strcasestr(handshake, "\r\nSec-WebSocket-Origin: "); if (!start) { return 0; } start += 24; } @@ -595,7 +595,7 @@ int parse_handshake(ws_ctx_t *ws_ctx, char *handshake) { strncpy(headers->origin, start, end-start); headers->origin[end-start] = '\0'; - start = strstr(handshake, "\r\nSec-WebSocket-Version: "); + start = strcasestr(handshake, "\r\nSec-WebSocket-Version: "); if (start) { // HyBi/RFC 6455 start += 25; @@ -605,7 +605,7 @@ int parse_handshake(ws_ctx_t *ws_ctx, char *handshake) { ws_ctx->hixie = 0; ws_ctx->hybi = strtol(headers->version, NULL, 10); - start = strstr(handshake, "\r\nSec-WebSocket-Key: "); + start = strcasestr(handshake, "\r\nSec-WebSocket-Key: "); if (!start) { return 0; } start += 21; end = strstr(start, "\r\n"); @@ -619,7 +619,7 @@ int parse_handshake(ws_ctx_t *ws_ctx, char *handshake) { strncpy(headers->connection, start, end-start); headers->connection[end-start] = '\0'; - start = strstr(handshake, "\r\nSec-WebSocket-Protocol: "); + start = strcasestr(handshake, "\r\nSec-WebSocket-Protocol: "); if (!start) { return 0; } start += 26; end = strstr(start, "\r\n"); @@ -637,14 +637,14 @@ int parse_handshake(ws_ctx_t *ws_ctx, char *handshake) { strncpy(headers->key3, start, 8); headers->key3[8] = '\0'; - start = strstr(handshake, "\r\nSec-WebSocket-Key1: "); + start = strcasestr(handshake, "\r\nSec-WebSocket-Key1: "); if (!start) { return 0; } start += 22; end = strstr(start, "\r\n"); strncpy(headers->key1, start, end-start); headers->key1[end-start] = '\0'; - start = strstr(handshake, "\r\nSec-WebSocket-Key2: "); + start = strcasestr(handshake, "\r\nSec-WebSocket-Key2: "); if (!start) { return 0; } start += 22; end = strstr(start, "\r\n"); From 32e8d40472247f5a0ab3c07b30c4b9ba1edb5273 Mon Sep 17 00:00:00 2001 From: Lauri Kasanen Date: Mon, 26 Jul 2021 18:58:29 +0300 Subject: [PATCH 02/12] Implement /api/get_bottleneck_stats --- common/network/GetAPI.h | 7 +++ common/network/GetAPIMessager.cxx | 77 +++++++++++++++++++++++++++++++ common/network/TcpSocket.cxx | 8 ++++ common/network/websocket.c | 15 ++++++ common/network/websocket.h | 1 + common/rfb/SMsgHandler.h | 2 +- common/rfb/VNCSConnectionST.cxx | 11 +++-- common/rfb/VNCSConnectionST.h | 3 +- common/rfb/VNCServerST.cxx | 3 ++ 9 files changed, 122 insertions(+), 5 deletions(-) diff --git a/common/network/GetAPI.h b/common/network/GetAPI.h index c3eae8e..2ffbf52 100644 --- a/common/network/GetAPI.h +++ b/common/network/GetAPI.h @@ -24,6 +24,8 @@ #include #include #include +#include +#include #include namespace network { @@ -34,6 +36,7 @@ namespace network { // from main thread void mainUpdateScreen(rfb::PixelBuffer *pb); + void mainUpdateBottleneckStats(const char userid[], const char stats[]); // from network threads uint8_t *netGetScreenshot(uint16_t w, uint16_t h, @@ -42,6 +45,7 @@ namespace network { uint8_t netAddUser(const char name[], const char pw[], const bool write); uint8_t netRemoveUser(const char name[]); uint8_t netGiveControlTo(const char name[]); + void netGetBottleneckStats(char *buf, uint32_t len); enum USER_ACTION { //USER_ADD, - handled locally for interactivity @@ -68,6 +72,9 @@ namespace network { std::vector cachedJpeg; uint16_t cachedW, cachedH; uint8_t cachedQ; + + std::map bottleneckStats; + pthread_mutex_t statMutex; }; } diff --git a/common/network/GetAPIMessager.cxx b/common/network/GetAPIMessager.cxx index b06fabd..4a798fe 100644 --- a/common/network/GetAPIMessager.cxx +++ b/common/network/GetAPIMessager.cxx @@ -60,6 +60,7 @@ GetAPIMessager::GetAPIMessager(const char *passwdfile_): passwdfile(passwdfile_) pthread_mutex_init(&screenMutex, NULL); pthread_mutex_init(&userMutex, NULL); + pthread_mutex_init(&statMutex, NULL); } // from main thread @@ -95,6 +96,15 @@ void GetAPIMessager::mainUpdateScreen(rfb::PixelBuffer *pb) { pthread_mutex_unlock(&screenMutex); } +void GetAPIMessager::mainUpdateBottleneckStats(const char userid[], const char stats[]) { + if (pthread_mutex_trylock(&statMutex)) + return; + + bottleneckStats[userid] = stats; + + pthread_mutex_unlock(&statMutex); +} + // from network threads uint8_t *GetAPIMessager::netGetScreenshot(uint16_t w, uint16_t h, const uint8_t q, const bool dedup, @@ -286,3 +296,70 @@ uint8_t GetAPIMessager::netGiveControlTo(const char name[]) { return 1; } + +void GetAPIMessager::netGetBottleneckStats(char *buf, uint32_t len) { +/* +{ + "username.1": { + "192.168.100.2:14908": [ 100, 100, 100, 100 ], + "192.168.100.3:14918": [ 100, 100, 100, 100 ] + }, + "username.2": { + "192.168.100.5:14904": [ 100, 100, 100, 100 ] + } +} +*/ + std::map::const_iterator it; + const char *prev = NULL; + FILE *f; + + if (pthread_mutex_lock(&statMutex)) { + buf[0] = 0; + return; + } + + // Conservative estimate + if (len < bottleneckStats.size() * 60) { + buf[0] = 0; + goto out; + } + + f = fmemopen(buf, len, "w"); + + fprintf(f, "{\n"); + + for (it = bottleneckStats.begin(); it != bottleneckStats.end(); it++) { + // user@127.0.0.1_1627311208.791752::websocket + const char *id = it->first.c_str(); + const char *data = it->second.c_str(); + + const char *at = strchr(id, '@'); + if (!at) + continue; + + const unsigned userlen = at - id; + if (prev && !strncmp(prev, id, userlen)) { + // Same user + fprintf(f, ",\n\t\t\"%s\": %s", at + 1, data); + } else { + // New one + if (prev) { + fprintf(f, "\n\t},\n"); + } + fprintf(f, "\t\"%.*s\": {\n", userlen, id); + fprintf(f, "\t\t\"%s\": %s", at + 1, data); + } + + prev = id; + } + + if (!bottleneckStats.size()) + fprintf(f, "}\n"); + else + fprintf(f, "\n\t}\n}\n"); + + fclose(f); + +out: + pthread_mutex_unlock(&statMutex); +} diff --git a/common/network/TcpSocket.cxx b/common/network/TcpSocket.cxx index 4262be5..50dc4bf 100644 --- a/common/network/TcpSocket.cxx +++ b/common/network/TcpSocket.cxx @@ -459,6 +459,13 @@ static uint8_t givecontrolCb(void *messager, const char name[]) return msgr->netGiveControlTo(name); } +static void bottleneckStatsCb(void *messager, char *buf, uint32_t len) +{ + GetAPIMessager *msgr = (GetAPIMessager *) messager; + msgr->netGetBottleneckStats(buf, len); +} + + WebsocketListener::WebsocketListener(const struct sockaddr *listenaddr, socklen_t listenaddrlen, bool sslonly, const char *cert, const char *certkey, @@ -548,6 +555,7 @@ WebsocketListener::WebsocketListener(const struct sockaddr *listenaddr, settings.adduserCb = adduserCb; settings.removeCb = removeCb; settings.givecontrolCb = givecontrolCb; + settings.bottleneckStatsCb = bottleneckStatsCb; pthread_t tid; pthread_create(&tid, NULL, start_server, NULL); diff --git a/common/network/websocket.c b/common/network/websocket.c index fd88124..1d96066 100644 --- a/common/network/websocket.c +++ b/common/network/websocket.c @@ -1074,6 +1074,21 @@ static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in) { wserr("Passed give_control request to main thread\n"); ret = 1; + } else entry("/api/get_bottleneck_stats") { + char statbuf[4096]; + settings.bottleneckStatsCb(settings.messager, statbuf, 4096); + + sprintf(buf, "HTTP/1.1 200 OK\r\n" + "Server: KasmVNC/4.0\r\n" + "Connection: close\r\n" + "Content-type: text/plain\r\n" + "Content-length: %lu\r\n" + "\r\n", strlen(statbuf)); + ws_send(ws_ctx, buf, strlen(buf)); + ws_send(ws_ctx, statbuf, strlen(statbuf)); + + wserr("Sent bottleneck stats to API caller\n"); + ret = 1; } #undef entry diff --git a/common/network/websocket.h b/common/network/websocket.h index fd00987..f2802a5 100644 --- a/common/network/websocket.h +++ b/common/network/websocket.h @@ -84,6 +84,7 @@ typedef struct { const uint8_t write); uint8_t (*removeCb)(void *messager, const char name[]); uint8_t (*givecontrolCb)(void *messager, const char name[]); + void (*bottleneckStatsCb)(void *messager, char *buf, uint32_t len); } settings_t; #ifdef __cplusplus diff --git a/common/rfb/SMsgHandler.h b/common/rfb/SMsgHandler.h index 8b7a9ad..b24d112 100644 --- a/common/rfb/SMsgHandler.h +++ b/common/rfb/SMsgHandler.h @@ -63,7 +63,7 @@ namespace rfb { const size_t* lengths, const rdr::U8* const* data); - virtual void sendStats() = 0; + virtual void sendStats(const bool toClient = true) = 0; virtual bool canChangeKasmSettings() const = 0; diff --git a/common/rfb/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx index 1d34c11..215f1e5 100644 --- a/common/rfb/VNCSConnectionST.cxx +++ b/common/rfb/VNCSConnectionST.cxx @@ -17,6 +17,7 @@ * USA. */ +#include #include #include @@ -1459,7 +1460,7 @@ static void pruneStatList(std::list &list, const struct timeval } } -void VNCSConnectionST::sendStats() { +void VNCSConnectionST::sendStats(const bool toClient) { char buf[1024]; struct timeval now; @@ -1498,8 +1499,12 @@ void VNCSConnectionST::sendStats() { #undef ten - vlog.info("Sending client stats:\n%s\n", buf); - writer()->writeStats(buf, strlen(buf)); + if (toClient) { + vlog.info("Sending client stats:\n%s\n", buf); + writer()->writeStats(buf, strlen(buf)); + } else if (server->apimessager) { + server->apimessager->mainUpdateBottleneckStats(peerEndpoint.buf, buf); + } } // setCursor() is called whenever the cursor has changed shape or pixel format. diff --git a/common/rfb/VNCSConnectionST.h b/common/rfb/VNCSConnectionST.h index 0a3bcdc..cd00243 100644 --- a/common/rfb/VNCSConnectionST.h +++ b/common/rfb/VNCSConnectionST.h @@ -164,6 +164,8 @@ namespace rfb { void setStatus(int status); int getStatus(); + virtual void sendStats(const bool toClient = true); + private: // SConnection callbacks @@ -191,7 +193,6 @@ namespace rfb { virtual void supportsContinuousUpdates(); virtual void supportsLEDState(); - virtual void sendStats(); virtual bool canChangeKasmSettings() const { return (accessRights & (AccessPtrEvents | AccessKeyEvents)) == (AccessPtrEvents | AccessKeyEvents); diff --git a/common/rfb/VNCServerST.cxx b/common/rfb/VNCServerST.cxx index 44f1a25..7fe1677 100644 --- a/common/rfb/VNCServerST.cxx +++ b/common/rfb/VNCServerST.cxx @@ -997,6 +997,9 @@ void VNCServerST::writeUpdate() (*ci)->add_copypassed(ui.copypassed); (*ci)->add_changed(ui.changed); (*ci)->writeFramebufferUpdateOrClose(); + + if (apimessager) + (*ci)->sendStats(false); } } From fb9dd567038552479429bbc5bbd5778bf0972d3b Mon Sep 17 00:00:00 2001 From: Lauri Kasanen Date: Tue, 27 Jul 2021 15:33:48 +0300 Subject: [PATCH 03/12] Initial /api/get_frame_stats --- common/network/GetAPI.h | 47 +++++ common/network/GetAPIMessager.cxx | 251 +++++++++++++++++++++++++- common/network/TcpSocket.cxx | 65 +++++++ common/network/websocket.c | 68 +++++++ common/network/websocket.h | 11 ++ common/rfb/ComparingUpdateTracker.cxx | 9 +- common/rfb/ComparingUpdateTracker.h | 2 + common/rfb/Congestion.cxx | 5 + common/rfb/Congestion.h | 2 + common/rfb/EncodeManager.cxx | 32 +++- common/rfb/EncodeManager.h | 11 +- common/rfb/SMsgHandler.h | 1 + common/rfb/SMsgReader.cxx | 11 ++ common/rfb/SMsgReader.h | 1 + common/rfb/SMsgWriter.cxx | 6 + common/rfb/SMsgWriter.h | 2 + common/rfb/VNCSConnectionST.cxx | 39 +++- common/rfb/VNCSConnectionST.h | 24 +++ common/rfb/VNCServerST.cxx | 75 +++++++- common/rfb/VNCServerST.h | 3 + common/rfb/msgTypes.h | 2 + 21 files changed, 655 insertions(+), 12 deletions(-) diff --git a/common/network/GetAPI.h b/common/network/GetAPI.h index 2ffbf52..c7c07b4 100644 --- a/common/network/GetAPI.h +++ b/common/network/GetAPI.h @@ -37,6 +37,14 @@ namespace network { // from main thread void mainUpdateScreen(rfb::PixelBuffer *pb); void mainUpdateBottleneckStats(const char userid[], const char stats[]); + void mainUpdateServerFrameStats(uint8_t changedPerc, uint32_t all, + uint32_t jpeg, uint32_t webp, uint32_t analysis, + uint32_t jpegarea, uint32_t webparea, + uint16_t njpeg, uint16_t nwebp, + uint16_t w, uint16_t h); + void mainUpdateClientFrameStats(const char userid[], uint32_t render, uint32_t all, + uint32_t ping); + void mainUpdateUserInfo(const uint8_t ownerConn, const uint8_t numUsers); // from network threads uint8_t *netGetScreenshot(uint16_t w, uint16_t h, @@ -46,13 +54,24 @@ namespace network { uint8_t netRemoveUser(const char name[]); uint8_t netGiveControlTo(const char name[]); void netGetBottleneckStats(char *buf, uint32_t len); + void netGetFrameStats(char *buf, uint32_t len); + uint8_t netServerFrameStatsReady(); enum USER_ACTION { //USER_ADD, - handled locally for interactivity USER_REMOVE, USER_GIVE_CONTROL, + WANT_FRAME_STATS_SERVERONLY, + WANT_FRAME_STATS_ALL, + WANT_FRAME_STATS_OWNER, + WANT_FRAME_STATS_SPECIFIC, }; + uint8_t netRequestFrameStats(USER_ACTION what, const char *client); + uint8_t netOwnerConnected(); + uint8_t netNumActiveUsers(); + uint8_t netGetClientFrameStatsNum(); + struct action_data { enum USER_ACTION action; kasmpasswd_entry_t data; @@ -75,6 +94,34 @@ namespace network { std::map bottleneckStats; pthread_mutex_t statMutex; + + struct clientFrameStats_t { + uint32_t render; + uint32_t all; + uint32_t ping; + }; + struct serverFrameStats_t { + uint32_t all; + uint32_t jpeg; + uint32_t webp; + uint32_t analysis; + uint32_t jpegarea; + uint32_t webparea; + uint16_t njpeg; + uint16_t nwebp; + uint16_t w; + uint16_t h; + uint8_t changedPerc; + + uint8_t inprogress; + }; + std::map clientFrameStats; + serverFrameStats_t serverFrameStats; + pthread_mutex_t frameStatMutex; + + uint8_t ownerConnected; + uint8_t activeUsers; + pthread_mutex_t userInfoMutex; }; } diff --git a/common/network/GetAPIMessager.cxx b/common/network/GetAPIMessager.cxx index 4a798fe..3ce2675 100644 --- a/common/network/GetAPIMessager.cxx +++ b/common/network/GetAPIMessager.cxx @@ -56,11 +56,16 @@ static const struct TightJPEGConfiguration conf[10] = { GetAPIMessager::GetAPIMessager(const char *passwdfile_): passwdfile(passwdfile_), screenW(0), screenH(0), screenHash(0), - cachedW(0), cachedH(0), cachedQ(0) { + cachedW(0), cachedH(0), cachedQ(0), + ownerConnected(0), activeUsers(0) { pthread_mutex_init(&screenMutex, NULL); pthread_mutex_init(&userMutex, NULL); pthread_mutex_init(&statMutex, NULL); + pthread_mutex_init(&frameStatMutex, NULL); + pthread_mutex_init(&userInfoMutex, NULL); + + serverFrameStats.inprogress = 0; } // from main thread @@ -105,6 +110,56 @@ void GetAPIMessager::mainUpdateBottleneckStats(const char userid[], const char s pthread_mutex_unlock(&statMutex); } +void GetAPIMessager::mainUpdateServerFrameStats(uint8_t changedPerc, + uint32_t all, uint32_t jpeg, uint32_t webp, uint32_t analysis, + uint32_t jpegarea, uint32_t webparea, + uint16_t njpeg, uint16_t nwebp, + uint16_t w, uint16_t h) { + + if (pthread_mutex_lock(&frameStatMutex)) + return; + + serverFrameStats.changedPerc = changedPerc; + serverFrameStats.all = all; + serverFrameStats.jpeg = jpeg; + serverFrameStats.webp = webp; + serverFrameStats.analysis = analysis; + serverFrameStats.jpegarea = jpegarea; + serverFrameStats.webparea = webparea; + serverFrameStats.njpeg = njpeg; + serverFrameStats.nwebp = nwebp; + serverFrameStats.w = w; + serverFrameStats.h = h; + + pthread_mutex_unlock(&frameStatMutex); +} + +void GetAPIMessager::mainUpdateClientFrameStats(const char userid[], uint32_t render, + uint32_t all, uint32_t ping) { + + if (pthread_mutex_lock(&frameStatMutex)) + return; + + clientFrameStats_t s; + s.render = render; + s.all = all; + s.ping = ping; + + clientFrameStats[userid] = s; + + pthread_mutex_unlock(&frameStatMutex); +} + +void GetAPIMessager::mainUpdateUserInfo(const uint8_t ownerConn, const uint8_t numUsers) { + if (pthread_mutex_lock(&userInfoMutex)) + return; + + ownerConnected = ownerConn; + activeUsers = numUsers; + + pthread_mutex_unlock(&userInfoMutex); +} + // from network threads uint8_t *GetAPIMessager::netGetScreenshot(uint16_t w, uint16_t h, const uint8_t q, const bool dedup, @@ -363,3 +418,197 @@ void GetAPIMessager::netGetBottleneckStats(char *buf, uint32_t len) { out: pthread_mutex_unlock(&statMutex); } + +void GetAPIMessager::netGetFrameStats(char *buf, uint32_t len) { +/* +{ + "frame" : { + "resx": 1024, + "resy": 1280, + "changed": 75, + "server_time": 23 + }, + "server_side" : [ + { "process_name": "Analysis", "time": 20 }, + { "process_name": "TightWEBPEncoder", "time": 20, "count": 64, "area": 12 }, + { "process_name": "TightJPEGEncoder", "time": 20, "count": 64, "area": 12 } + ], + "client_side" : [ + "123.1.2.1:1211" : { + "client_time": 20, + "ping": 20, + "processes" : [ + { "process_name": "scanRenderQ", "time": 20 } + ] + } + } +} +*/ + std::map::const_iterator it; + unsigned i = 0; + FILE *f; + + if (pthread_mutex_lock(&frameStatMutex)) { + buf[0] = 0; + return; + } + + const unsigned num = clientFrameStats.size(); + + // Conservative estimate + if (len < 1024) { + buf[0] = 0; + goto out; + } + + f = fmemopen(buf, len, "w"); + + fprintf(f, "{\n"); + + fprintf(f, "\t\"frame\" : {\n" + "\t\t\"resx\": %u,\n" + "\t\t\"resy\": %u,\n" + "\t\t\"changed\": %u,\n" + "\t\t\"server_time\": %u\n" + "\t},\n", + serverFrameStats.w, + serverFrameStats.h, + serverFrameStats.changedPerc, + serverFrameStats.all); + + fprintf(f, "\t\"server_side\" : [\n" + "\t\t{ \"process_name\": \"Analysis\", \"time\": %u },\n" + "\t\t{ \"process_name\": \"TightJPEGEncoder\", \"time\": %u, \"count\": %u, \"area\": %u },\n" + "\t\t{ \"process_name\": \"TightWEBPEncoder\", \"time\": %u, \"count\": %u, \"area\": %u }\n" + "\t],\n", + serverFrameStats.analysis, + serverFrameStats.jpeg, + serverFrameStats.njpeg, + serverFrameStats.jpegarea, + serverFrameStats.webp, + serverFrameStats.nwebp, + serverFrameStats.webparea); + + fprintf(f, "\t\"client_side\" : [\n"); + + for (it = clientFrameStats.begin(); it != clientFrameStats.end(); it++, i++) { + const char *id = it->first.c_str(); + const clientFrameStats_t &s = it->second; + + fprintf(f, "\t\t\"%s\" : {\n" + "\t\t\t\"client_time\": %u,\n" + "\t\t\t\"ping\": %u,\n" + "\t\t\t\"processes\" : [\n" + "\t\t\t\t{ \"process_name\": \"scanRenderQ\", \"time\": %u }\n" + "\t\t\t]\n" + "\t\t}", + id, + s.all, + s.ping, + s.render); + + if (i == num - 1) + fprintf(f, "\n"); + else + fprintf(f, ",\n"); + } + + fprintf(f, "\t]\n}\n"); + + fclose(f); + + serverFrameStats.inprogress = 0; + +out: + pthread_mutex_unlock(&frameStatMutex); +} + +uint8_t GetAPIMessager::netRequestFrameStats(USER_ACTION what, const char *client) { + // Return 1 for success + action_data act; + act.action = what; + if (client) { + strncpy(act.data.password, client, PASSWORD_LEN); + act.data.password[PASSWORD_LEN - 1] = '\0'; + } + + // In progress already? + bool fail = false; + if (pthread_mutex_lock(&frameStatMutex)) + return 0; + + if (serverFrameStats.inprogress) { + fail = true; + vlog.error("Frame stats request already in progress, refusing another"); + } else { + clientFrameStats.clear(); + memset(&serverFrameStats, 0, sizeof(serverFrameStats_t)); + serverFrameStats.inprogress = 1; + } + + pthread_mutex_unlock(&frameStatMutex); + if (fail) + return 0; + + // Send it in + if (pthread_mutex_lock(&userMutex)) + return 0; + + actionQueue.push_back(act); + + pthread_mutex_unlock(&userMutex); + + return 1; +} + +uint8_t GetAPIMessager::netOwnerConnected() { + uint8_t ret; + + if (pthread_mutex_lock(&userInfoMutex)) + return 0; + + ret = ownerConnected; + + pthread_mutex_unlock(&userInfoMutex); + + return ret; +} + +uint8_t GetAPIMessager::netNumActiveUsers() { + uint8_t ret; + + if (pthread_mutex_lock(&userInfoMutex)) + return 0; + + ret = activeUsers; + + pthread_mutex_unlock(&userInfoMutex); + + return ret; +} + +uint8_t GetAPIMessager::netGetClientFrameStatsNum() { + uint8_t ret; + + if (pthread_mutex_lock(&frameStatMutex)) + return 0; + + ret = clientFrameStats.size(); + + pthread_mutex_unlock(&frameStatMutex); + + return ret; +} + +uint8_t GetAPIMessager::netServerFrameStatsReady() { + uint8_t ret; + + if (pthread_mutex_lock(&frameStatMutex)) + return 0; + + ret = serverFrameStats.w != 0; + + pthread_mutex_unlock(&frameStatMutex); + + return ret; +} diff --git a/common/network/TcpSocket.cxx b/common/network/TcpSocket.cxx index 50dc4bf..141bdf7 100644 --- a/common/network/TcpSocket.cxx +++ b/common/network/TcpSocket.cxx @@ -465,6 +465,60 @@ static void bottleneckStatsCb(void *messager, char *buf, uint32_t len) msgr->netGetBottleneckStats(buf, len); } +static void frameStatsCb(void *messager, char *buf, uint32_t len) +{ + GetAPIMessager *msgr = (GetAPIMessager *) messager; + msgr->netGetFrameStats(buf, len); +} + +static uint8_t requestFrameStatsNoneCb(void *messager) +{ + GetAPIMessager *msgr = (GetAPIMessager *) messager; + return msgr->netRequestFrameStats(GetAPIMessager::WANT_FRAME_STATS_SERVERONLY, NULL); +} + +static uint8_t requestFrameStatsOwnerCb(void *messager) +{ + GetAPIMessager *msgr = (GetAPIMessager *) messager; + return msgr->netRequestFrameStats(GetAPIMessager::WANT_FRAME_STATS_OWNER, NULL); +} + +static uint8_t requestFrameStatsAllCb(void *messager) +{ + GetAPIMessager *msgr = (GetAPIMessager *) messager; + return msgr->netRequestFrameStats(GetAPIMessager::WANT_FRAME_STATS_ALL, NULL); +} + +static uint8_t requestFrameStatsOneCb(void *messager, const char *client) +{ + GetAPIMessager *msgr = (GetAPIMessager *) messager; + return msgr->netRequestFrameStats(GetAPIMessager::WANT_FRAME_STATS_SPECIFIC, client); +} + +static uint8_t ownerConnectedCb(void *messager) +{ + GetAPIMessager *msgr = (GetAPIMessager *) messager; + return msgr->netOwnerConnected(); +} + +static uint8_t numActiveUsersCb(void *messager) +{ + GetAPIMessager *msgr = (GetAPIMessager *) messager; + return msgr->netNumActiveUsers(); +} + +static uint8_t getClientFrameStatsNumCb(void *messager) +{ + GetAPIMessager *msgr = (GetAPIMessager *) messager; + return msgr->netGetClientFrameStatsNum(); +} + +static uint8_t serverFrameStatsReadyCb(void *messager) +{ + GetAPIMessager *msgr = (GetAPIMessager *) messager; + return msgr->netServerFrameStatsReady(); +} + WebsocketListener::WebsocketListener(const struct sockaddr *listenaddr, socklen_t listenaddrlen, @@ -556,6 +610,17 @@ WebsocketListener::WebsocketListener(const struct sockaddr *listenaddr, settings.removeCb = removeCb; settings.givecontrolCb = givecontrolCb; settings.bottleneckStatsCb = bottleneckStatsCb; + settings.frameStatsCb = frameStatsCb; + + settings.requestFrameStatsNoneCb = requestFrameStatsNoneCb; + settings.requestFrameStatsOwnerCb = requestFrameStatsOwnerCb; + settings.requestFrameStatsAllCb = requestFrameStatsAllCb; + settings.requestFrameStatsOneCb = requestFrameStatsOneCb; + + settings.ownerConnectedCb = ownerConnectedCb; + settings.numActiveUsersCb = numActiveUsersCb; + settings.getClientFrameStatsNumCb = getClientFrameStatsNumCb; + settings.serverFrameStatsReadyCb = serverFrameStatsReadyCb; pthread_t tid; pthread_create(&tid, NULL, start_server, NULL); diff --git a/common/network/websocket.c b/common/network/websocket.c index 1d96066..3d3111d 100644 --- a/common/network/websocket.c +++ b/common/network/websocket.c @@ -1089,6 +1089,74 @@ static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in) { wserr("Sent bottleneck stats to API caller\n"); ret = 1; + } else entry("/api/get_frame_stats") { + char statbuf[4096], decname[1024]; + unsigned waitfor; + + param = parse_get(args, "client", &len); + if (len) { + memcpy(buf, param, len); + buf[len] = '\0'; + percent_decode(buf, decname); + } else { + wserr("client param required\n"); + goto nope; + } + + if (!decname[0]) + goto nope; + + if (!strcmp(decname, "none")) { + waitfor = 0; + if (!settings.requestFrameStatsNoneCb(settings.messager)) + goto nope; + } else if (!strcmp(decname, "auto")) { + waitfor = settings.ownerConnectedCb(settings.messager); + if (!waitfor) { + if (!settings.requestFrameStatsNoneCb(settings.messager)) + goto nope; + } else { + if (!settings.requestFrameStatsOwnerCb(settings.messager)) + goto nope; + } + } else if (!strcmp(decname, "all")) { + waitfor = settings.numActiveUsersCb(settings.messager); + if (!settings.requestFrameStatsAllCb(settings.messager)) + goto nope; + } else { + waitfor = 1; + if (!settings.requestFrameStatsOneCb(settings.messager, decname)) + goto nope; + } + + while (1) { + usleep(10 * 1000); + if (settings.serverFrameStatsReadyCb(settings.messager)) + break; + } + + if (waitfor) { + unsigned waits; + for (waits = 0; waits < 20; waits++) { // wait up to 2s + if (settings.getClientFrameStatsNumCb(settings.messager) >= waitfor) + break; + usleep(100 * 1000); + } + } + + settings.frameStatsCb(settings.messager, statbuf, 4096); + + sprintf(buf, "HTTP/1.1 200 OK\r\n" + "Server: KasmVNC/4.0\r\n" + "Connection: close\r\n" + "Content-type: text/plain\r\n" + "Content-length: %lu\r\n" + "\r\n", strlen(statbuf)); + ws_send(ws_ctx, buf, strlen(buf)); + ws_send(ws_ctx, statbuf, strlen(statbuf)); + + wserr("Sent frame stats to API caller\n"); + ret = 1; } #undef entry diff --git a/common/network/websocket.h b/common/network/websocket.h index f2802a5..5cb8c0e 100644 --- a/common/network/websocket.h +++ b/common/network/websocket.h @@ -85,6 +85,17 @@ typedef struct { uint8_t (*removeCb)(void *messager, const char name[]); uint8_t (*givecontrolCb)(void *messager, const char name[]); void (*bottleneckStatsCb)(void *messager, char *buf, uint32_t len); + void (*frameStatsCb)(void *messager, char *buf, uint32_t len); + + uint8_t (*requestFrameStatsNoneCb)(void *messager); + uint8_t (*requestFrameStatsOwnerCb)(void *messager); + uint8_t (*requestFrameStatsAllCb)(void *messager); + uint8_t (*requestFrameStatsOneCb)(void *messager, const char *client); + + uint8_t (*ownerConnectedCb)(void *messager); + uint8_t (*numActiveUsersCb)(void *messager); + uint8_t (*getClientFrameStatsNumCb)(void *messager); + uint8_t (*serverFrameStatsReadyCb)(void *messager); } settings_t; #ifdef __cplusplus diff --git a/common/rfb/ComparingUpdateTracker.cxx b/common/rfb/ComparingUpdateTracker.cxx index dc53bd0..da132c3 100644 --- a/common/rfb/ComparingUpdateTracker.cxx +++ b/common/rfb/ComparingUpdateTracker.cxx @@ -695,6 +695,8 @@ bool ComparingUpdateTracker::compare(bool skipScrollDetection, const Region &ski std::vector rects; std::vector::iterator i; + changedPerc = 100; + if (!enabled) return false; @@ -749,8 +751,13 @@ bool ComparingUpdateTracker::compare(bool skipScrollDetection, const Region &ski for (i = rects.begin(); i != rects.end(); i++) totalPixels += i->area(); newChanged.get_rects(&rects); - for (i = rects.begin(); i != rects.end(); i++) + unsigned newchangedarea = 0; + for (i = rects.begin(); i != rects.end(); i++) { missedPixels += i->area(); + newchangedarea += i->area(); + } + + changedPerc = newchangedarea * 100 / fb->area(); if (changed.equals(newChanged)) return false; diff --git a/common/rfb/ComparingUpdateTracker.h b/common/rfb/ComparingUpdateTracker.h index 60c42e2..e649c49 100644 --- a/common/rfb/ComparingUpdateTracker.h +++ b/common/rfb/ComparingUpdateTracker.h @@ -48,6 +48,8 @@ namespace rfb { virtual void getUpdateInfo(UpdateInfo* info, const Region& cliprgn); virtual void clear(); + rdr::U8 changedPerc; + private: void compareRect(const Rect& r, Region* newchanged, const Region &skipCursorArea); PixelBuffer* fb; diff --git a/common/rfb/Congestion.cxx b/common/rfb/Congestion.cxx index 619bd72..1fdf6cc 100644 --- a/common/rfb/Congestion.cxx +++ b/common/rfb/Congestion.cxx @@ -298,6 +298,11 @@ size_t Congestion::getBandwidth() return congWindow * 1000 / safeBaseRTT; } +unsigned Congestion::getPingTime() const +{ + return safeBaseRTT; +} + void Congestion::debugTrace(const char* filename, int fd) { #ifdef CONGESTION_TRACE diff --git a/common/rfb/Congestion.h b/common/rfb/Congestion.h index d293512..e968801 100644 --- a/common/rfb/Congestion.h +++ b/common/rfb/Congestion.h @@ -51,6 +51,8 @@ namespace rfb { // per second. size_t getBandwidth(); + unsigned getPingTime() const; + // debugTrace() writes the current congestion window, as well as the // congestion window of the underlying TCP layer, to the specified // file diff --git a/common/rfb/EncodeManager.cxx b/common/rfb/EncodeManager.cxx index fb9b8a6..d042d4c 100644 --- a/common/rfb/EncodeManager.cxx +++ b/common/rfb/EncodeManager.cxx @@ -359,6 +359,8 @@ void EncodeManager::doUpdate(bool allowLossy, const Region& changed_, changed = changed_; gettimeofday(&start, NULL); + memset(&jpegstats, 0, sizeof(codecstats_t)); + memset(&webpstats, 0, sizeof(codecstats_t)); if (allowLossy && activeEncoders[encoderFullColour] == encoderTightWEBP) { const unsigned rate = 1024 * 1000 / rfb::Server::frameRate; @@ -1014,6 +1016,7 @@ void EncodeManager::writeRects(const Region& changed, const PixelBuffer* pb, std::vector isWebp, fromCache; std::vector palettes; std::vector > compresseds; + std::vector ms; uint32_t i; if (rfb::Server::rectThreads > 0) @@ -1078,6 +1081,7 @@ void EncodeManager::writeRects(const Region& changed, const PixelBuffer* pb, palettes.resize(subrects.size()); compresseds.resize(subrects.size()); scaledrects.resize(subrects.size()); + ms.resize(subrects.size()); // In case the current resolution is above the max video res, and video was detected, // scale to that res, keeping aspect ratio @@ -1134,10 +1138,19 @@ void EncodeManager::writeRects(const Region& changed, const PixelBuffer* pb, for (i = 0; i < subrects.size(); ++i) { encoderTypes[i] = getEncoderType(subrects[i], pb, &palettes[i], compresseds[i], &isWebp[i], &fromCache[i], - scaledpb, scaledrects[i]); + scaledpb, scaledrects[i], ms[i]); checkWebpFallback(start); } + for (i = 0; i < subrects.size(); ++i) { + if (encoderTypes[i] == encoderFullColour) { + if (isWebp[i]) + webpstats.ms += ms[i]; + else + jpegstats.ms += ms[i]; + } + } + if (start) { encodingTime = msSince(start); @@ -1178,7 +1191,8 @@ void EncodeManager::writeRects(const Region& changed, const PixelBuffer* pb, uint8_t EncodeManager::getEncoderType(const Rect& rect, const PixelBuffer *pb, Palette *pal, std::vector &compressed, uint8_t *isWebp, uint8_t *fromCache, - const PixelBuffer *scaledpb, const Rect& scaledrect) const + const PixelBuffer *scaledpb, const Rect& scaledrect, + uint32_t &ms) const { struct RectInfo info; unsigned int maxColours = 256; @@ -1231,9 +1245,12 @@ uint8_t EncodeManager::getEncoderType(const Rect& rect, const PixelBuffer *pb, *isWebp = 0; *fromCache = 0; + ms = 0; if (type == encoderFullColour) { uint32_t len; const void *data; + struct timeval start; + gettimeofday(&start, NULL); if (encCache->enabled && (data = encCache->get(activeEncoders[encoderFullColour], @@ -1274,6 +1291,8 @@ uint8_t EncodeManager::getEncoderType(const Rect& rect, const PixelBuffer *pb, compressed, videoDetected); } + + ms = msSince(&start); } delete ppb; @@ -1292,10 +1311,15 @@ void EncodeManager::writeSubRect(const Rect& rect, const PixelBuffer *pb, encoder = startRect(rect, type, compressed.size() == 0, isWebp); if (compressed.size()) { - if (isWebp) + if (isWebp) { ((TightWEBPEncoder *) encoder)->writeOnly(compressed); - else + webpstats.area += rect.area(); + webpstats.rects++; + } else { ((TightJPEGEncoder *) encoder)->writeOnly(compressed); + jpegstats.area += rect.area(); + jpegstats.rects++; + } } else { if (encoder->flags & EncoderUseNativePF) { ppb = preparePixelBuffer(rect, pb, false); diff --git a/common/rfb/EncodeManager.h b/common/rfb/EncodeManager.h index 9ed5b65..c271845 100644 --- a/common/rfb/EncodeManager.h +++ b/common/rfb/EncodeManager.h @@ -72,6 +72,14 @@ namespace rfb { return encodingTime; }; + struct codecstats_t { + uint32_t ms; + uint32_t area; + uint32_t rects; + }; + + codecstats_t jpegstats, webpstats; + protected: void doUpdate(bool allowLossy, const Region& changed, const Region& copied, const Point& copy_delta, @@ -105,7 +113,8 @@ namespace rfb { uint8_t getEncoderType(const Rect& rect, const PixelBuffer *pb, Palette *pal, std::vector &compressed, uint8_t *isWebp, uint8_t *fromCache, - const PixelBuffer *scaledpb, const Rect& scaledrect) const; + const PixelBuffer *scaledpb, const Rect& scaledrect, + uint32_t &ms) const; virtual bool handleTimeout(Timer* t); bool checkSolidTile(const Rect& r, const rdr::U8* colourValue, diff --git a/common/rfb/SMsgHandler.h b/common/rfb/SMsgHandler.h index b24d112..d2fe4af 100644 --- a/common/rfb/SMsgHandler.h +++ b/common/rfb/SMsgHandler.h @@ -64,6 +64,7 @@ namespace rfb { const rdr::U8* const* data); virtual void sendStats(const bool toClient = true) = 0; + virtual void handleFrameStats(rdr::U32 all, rdr::U32 render) = 0; virtual bool canChangeKasmSettings() const = 0; diff --git a/common/rfb/SMsgReader.cxx b/common/rfb/SMsgReader.cxx index 11655b6..de5e1b3 100644 --- a/common/rfb/SMsgReader.cxx +++ b/common/rfb/SMsgReader.cxx @@ -80,6 +80,9 @@ void SMsgReader::readMsg() case msgTypeRequestStats: readRequestStats(); break; + case msgTypeFrameStats: + readFrameStats(); + break; case msgTypeKeyEvent: readKeyEvent(); break; @@ -346,6 +349,14 @@ void SMsgReader::readRequestStats() handler->sendStats(); } +void SMsgReader::readFrameStats() +{ + is->skip(3); + rdr::U32 all = is->readU32(); + rdr::U32 render = is->readU32(); + handler->handleFrameStats(all, render); +} + void SMsgReader::readQEMUMessage() { int subType = is->readU8(); diff --git a/common/rfb/SMsgReader.h b/common/rfb/SMsgReader.h index ec0035b..a9b09cc 100644 --- a/common/rfb/SMsgReader.h +++ b/common/rfb/SMsgReader.h @@ -57,6 +57,7 @@ namespace rfb { void readClientCutText(); void readExtendedClipboard(rdr::S32 len); void readRequestStats(); + void readFrameStats(); void readQEMUMessage(); void readQEMUKeyEvent(); diff --git a/common/rfb/SMsgWriter.cxx b/common/rfb/SMsgWriter.cxx index 3d46e2d..5d87719 100644 --- a/common/rfb/SMsgWriter.cxx +++ b/common/rfb/SMsgWriter.cxx @@ -208,6 +208,12 @@ void SMsgWriter::writeStats(const char* str, int len) endMsg(); } +void SMsgWriter::writeRequestFrameStats() +{ + startMsg(msgTypeRequestFrameStats); + endMsg(); +} + void SMsgWriter::writeFence(rdr::U32 flags, unsigned len, const char data[]) { if (!cp->supportsFence) diff --git a/common/rfb/SMsgWriter.h b/common/rfb/SMsgWriter.h index f561136..98188d3 100644 --- a/common/rfb/SMsgWriter.h +++ b/common/rfb/SMsgWriter.h @@ -65,6 +65,8 @@ namespace rfb { void writeStats(const char* str, int len); + void writeRequestFrameStats(); + // writeFence() sends a new fence request or response to the client. void writeFence(rdr::U32 flags, unsigned len, const char data[]); diff --git a/common/rfb/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx index 215f1e5..993c14a 100644 --- a/common/rfb/VNCSConnectionST.cxx +++ b/common/rfb/VNCSConnectionST.cxx @@ -62,7 +62,7 @@ VNCSConnectionST::VNCSConnectionST(VNCServerST* server_, network::Socket *s, continuousUpdates(false), encodeManager(this, &server_->encCache), needsPermCheck(false), pointerEventTime(0), clientHasCursor(false), - accessRights(AccessDefault), startTime(time(0)) + accessRights(AccessDefault), startTime(time(0)), frameTracking(false) { setStreams(&sock->inStream(), &sock->outStream()); peerEndpoint.buf = sock->getPeerEndpoint(); @@ -99,6 +99,9 @@ VNCSConnectionST::VNCSConnectionST(VNCServerST* server_, network::Socket *s, gettimeofday(&lastKeyEvent, NULL); server->clients.push_front(this); + + if (server->apimessager) + server->apimessager->mainUpdateUserInfo(checkOwnerConn(), server->clients.size()); } @@ -129,6 +132,9 @@ VNCSConnectionST::~VNCSConnectionST() server->clients.remove(this); delete [] fenceData; + + if (server->apimessager) + server->apimessager->mainUpdateUserInfo(checkOwnerConn(), server->clients.size()); } @@ -1230,6 +1236,9 @@ void VNCSConnectionST::writeFramebufferUpdate() // window. sock->cork(true); + if (frameTracking) + writer()->writeRequestFrameStats(); + // First take care of any updates that cannot contain framebuffer data // changes. writeNoDataUpdate(); @@ -1507,6 +1516,22 @@ void VNCSConnectionST::sendStats(const bool toClient) { } } +void VNCSConnectionST::handleFrameStats(rdr::U32 all, rdr::U32 render) +{ + if (server->apimessager) { + const char *at = strchr(peerEndpoint.buf, '@'); + if (!at) + at = peerEndpoint.buf; + else + at++; + + server->apimessager->mainUpdateClientFrameStats(at, render, all, + congestion.getPingTime()); + } + + frameTracking = false; +} + // setCursor() is called whenever the cursor has changed shape or pixel format. // If the client supports local cursor then it will arrange for the cursor to // be sent to the client. @@ -1616,3 +1641,15 @@ int VNCSConnectionST::getStatus() return 4; } +bool VNCSConnectionST::checkOwnerConn() const +{ + std::list::const_iterator it; + + for (it = server->clients.begin(); it != server->clients.end(); it++) { + bool write, owner; + if ((*it)->getPerms(write, owner) && owner) + return true; + } + + return false; +} diff --git a/common/rfb/VNCSConnectionST.h b/common/rfb/VNCSConnectionST.h index cd00243..043f398 100644 --- a/common/rfb/VNCSConnectionST.h +++ b/common/rfb/VNCSConnectionST.h @@ -165,6 +165,26 @@ namespace rfb { int getStatus(); virtual void sendStats(const bool toClient = true); + virtual void handleFrameStats(rdr::U32 all, rdr::U32 render); + + bool is_owner() const { + bool write, owner; + if (getPerms(write, owner) && owner) + return true; + return false; + } + + void setFrameTracking() { + frameTracking = true; + } + + EncodeManager::codecstats_t getJpegStats() const { + return encodeManager.jpegstats; + } + + EncodeManager::codecstats_t getWebpStats() const { + return encodeManager.webpstats; + } private: // SConnection callbacks @@ -220,6 +240,8 @@ namespace rfb { bool getPerms(bool &write, bool &owner) const; + bool checkOwnerConn() const; + // Congestion control void writeRTTPing(); bool isCongested(); @@ -295,6 +317,8 @@ namespace rfb { time_t startTime; std::vector copypassed; + + bool frameTracking; }; } #endif diff --git a/common/rfb/VNCServerST.cxx b/common/rfb/VNCServerST.cxx index 7fe1677..efaf070 100644 --- a/common/rfb/VNCServerST.cxx +++ b/common/rfb/VNCServerST.cxx @@ -128,7 +128,7 @@ VNCServerST::VNCServerST(const char* name_, SDesktop* desktop_) renderedCursorInvalid(false), queryConnectionHandler(0), keyRemapper(&KeyRemapper::defInstance), lastConnectionTime(0), disableclients(false), - frameTimer(this), apimessager(NULL) + frameTimer(this), apimessager(NULL), trackingFrameStats(0) { lastUserInputTime = lastDisconnectTime = time(0); slog.debug("creating single-threaded server %s", name.buf); @@ -210,6 +210,8 @@ VNCServerST::VNCServerST(const char* name_, SDesktop* desktop_) if (inotify_add_watch(inotifyfd, kasmpasswdpath, IN_CLOSE_WRITE | IN_DELETE_SELF) < 0) slog.error("Failed to set watch"); } + + trackingClient[0] = 0; } VNCServerST::~VNCServerST() @@ -774,7 +776,8 @@ int VNCServerST::msToNextUpdate() return frameTimer.getRemainingMs(); } -static void checkAPIMessages(network::GetAPIMessager *apimessager) +static void checkAPIMessages(network::GetAPIMessager *apimessager, + rdr::U8 &trackingFrameStats, char trackingClient[]) { if (pthread_mutex_lock(&apimessager->userMutex)) return; @@ -827,6 +830,20 @@ static void checkAPIMessages(network::GetAPIMessager *apimessager) slog.error("Tried to give control to nonexistent user %s", act.data.user); } break; + + case network::GetAPIMessager::WANT_FRAME_STATS_SERVERONLY: + trackingFrameStats = act.action; + break; + case network::GetAPIMessager::WANT_FRAME_STATS_ALL: + trackingFrameStats = act.action; + break; + case network::GetAPIMessager::WANT_FRAME_STATS_OWNER: + trackingFrameStats = act.action; + break; + case network::GetAPIMessager::WANT_FRAME_STATS_SPECIFIC: + trackingFrameStats = act.action; + memcpy(trackingClient, act.data.password, 128); + break; } if (set) { @@ -923,6 +940,9 @@ void VNCServerST::writeUpdate() assert(blockCounter == 0); assert(desktopStarted); + struct timeval start; + gettimeofday(&start, NULL); + if (DLPRegion.enabled) { comparer->enable_copyrect(false); blackOut(); @@ -949,6 +969,9 @@ void VNCServerST::writeUpdate() else comparer->disable(); + struct timeval beforeAnalysis; + gettimeofday(&beforeAnalysis, NULL); + // Skip scroll detection if the client is slow, and didn't get the previous one yet if (comparer->compare(clients.size() == 1 && (*clients.begin())->has_copypassed(), cursorReg)) @@ -956,6 +979,8 @@ void VNCServerST::writeUpdate() comparer->clear(); + const unsigned analysisMs = msSince(&beforeAnalysis); + encCache.clear(); encCache.enabled = clients.size() > 1; @@ -984,22 +1009,64 @@ void VNCServerST::writeUpdate() if (apimessager) { apimessager->mainUpdateScreen(pb); - checkAPIMessages(apimessager); + trackingFrameStats = 0; + checkAPIMessages(apimessager, trackingFrameStats, trackingClient); } + EncodeManager::codecstats_t jpegstats, webpstats; + memset(&jpegstats, 0, sizeof(EncodeManager::codecstats_t)); + memset(&webpstats, 0, sizeof(EncodeManager::codecstats_t)); + for (ci = clients.begin(); ci != clients.end(); ci = ci_next) { ci_next = ci; ci_next++; if (permcheck) (*ci)->recheckPerms(); + if (trackingFrameStats == network::GetAPIMessager::WANT_FRAME_STATS_ALL || + (trackingFrameStats == network::GetAPIMessager::WANT_FRAME_STATS_OWNER && + (*ci)->is_owner()) || + (trackingFrameStats == network::GetAPIMessager::WANT_FRAME_STATS_SPECIFIC && + strstr((*ci)->getPeerEndpoint(), trackingClient))) { + + (*ci)->setFrameTracking(); + + // Only one owner + if (trackingFrameStats == network::GetAPIMessager::WANT_FRAME_STATS_OWNER) + trackingFrameStats = network::GetAPIMessager::WANT_FRAME_STATS_SERVERONLY; + } + (*ci)->add_copied(ui.copied, ui.copy_delta); (*ci)->add_copypassed(ui.copypassed); (*ci)->add_changed(ui.changed); (*ci)->writeFramebufferUpdateOrClose(); - if (apimessager) + if (apimessager) { (*ci)->sendStats(false); + const EncodeManager::codecstats_t subjpeg = (*ci)->getJpegStats(); + const EncodeManager::codecstats_t subwebp = (*ci)->getWebpStats(); + + jpegstats.ms += subjpeg.ms; + jpegstats.area += subjpeg.area; + jpegstats.rects += subjpeg.rects; + + webpstats.ms += subwebp.ms; + webpstats.area += subwebp.area; + webpstats.rects += subwebp.rects; + } + } + + if (trackingFrameStats) { + const unsigned totalMs = msSince(&start); + + if (apimessager) + apimessager->mainUpdateServerFrameStats(comparer->changedPerc, totalMs, + jpegstats.ms, webpstats.ms, + analysisMs, + jpegstats.area, webpstats.area, + jpegstats.rects, webpstats.rects, + pb->getRect().width(), + pb->getRect().height()); } } diff --git a/common/rfb/VNCServerST.h b/common/rfb/VNCServerST.h index 97d43f7..26c9ef6 100644 --- a/common/rfb/VNCServerST.h +++ b/common/rfb/VNCServerST.h @@ -267,6 +267,9 @@ namespace rfb { network::GetAPIMessager *apimessager; + rdr::U8 trackingFrameStats; + char trackingClient[128]; + struct { bool enabled; int x1, y1, x2, y2; diff --git a/common/rfb/msgTypes.h b/common/rfb/msgTypes.h index 4bb4ddf..5070c3f 100644 --- a/common/rfb/msgTypes.h +++ b/common/rfb/msgTypes.h @@ -30,6 +30,7 @@ namespace rfb { // kasm const int msgTypeStats = 178; + const int msgTypeRequestFrameStats = 179; const int msgTypeServerFence = 248; @@ -47,6 +48,7 @@ namespace rfb { // kasm const int msgTypeRequestStats = 178; + const int msgTypeFrameStats = 179; const int msgTypeClientFence = 248; From 438271d68b7a731deaf9885b338e5d099d9d499d Mon Sep 17 00:00:00 2001 From: Lauri Kasanen Date: Thu, 29 Jul 2021 18:02:57 +0300 Subject: [PATCH 04/12] Add support for vmware cursor --- common/rfb/ConnParams.cxx | 5 ++++ common/rfb/ConnParams.h | 1 + common/rfb/SMsgWriter.cxx | 46 ++++++++++++++++++++++++++++++++- common/rfb/SMsgWriter.h | 5 ++++ common/rfb/VNCSConnectionST.cxx | 13 ++++++---- common/rfb/encodings.h | 1 + 6 files changed, 65 insertions(+), 6 deletions(-) diff --git a/common/rfb/ConnParams.cxx b/common/rfb/ConnParams.cxx index 848712f..3871037 100644 --- a/common/rfb/ConnParams.cxx +++ b/common/rfb/ConnParams.cxx @@ -37,6 +37,7 @@ ConnParams::ConnParams() width(0), height(0), useCopyRect(false), supportsLocalCursor(false), supportsLocalXCursor(false), supportsLocalCursorWithAlpha(false), + supportsVMWareCursor(false), supportsCursorPosition(false), supportsDesktopResize(false), supportsExtendedDesktopSize(false), supportsDesktopRename(false), supportsLastRect(false), @@ -123,6 +124,7 @@ void ConnParams::setEncodings(int nEncodings, const rdr::S32* encodings) useCopyRect = false; supportsLocalCursor = false; supportsLocalCursorWithAlpha = false; + supportsVMWareCursor = false; supportsDesktopResize = false; supportsExtendedDesktopSize = false; supportsLocalXCursor = false; @@ -153,6 +155,9 @@ void ConnParams::setEncodings(int nEncodings, const rdr::S32* encodings) case pseudoEncodingCursorWithAlpha: supportsLocalCursorWithAlpha = true; break; + case pseudoEncodingVMwareCursor: + supportsVMWareCursor = true; + break; case pseudoEncodingDesktopSize: supportsDesktopResize = true; break; diff --git a/common/rfb/ConnParams.h b/common/rfb/ConnParams.h index 39a0de0..af39641 100644 --- a/common/rfb/ConnParams.h +++ b/common/rfb/ConnParams.h @@ -102,6 +102,7 @@ namespace rfb { bool supportsLocalCursor; bool supportsLocalXCursor; bool supportsLocalCursorWithAlpha; + bool supportsVMWareCursor; bool supportsCursorPosition; bool supportsDesktopResize; bool supportsExtendedDesktopSize; diff --git a/common/rfb/SMsgWriter.cxx b/common/rfb/SMsgWriter.cxx index 5d87719..a7c12f4 100644 --- a/common/rfb/SMsgWriter.cxx +++ b/common/rfb/SMsgWriter.cxx @@ -43,6 +43,7 @@ SMsgWriter::SMsgWriter(ConnParams* cp_, rdr::OutStream* os_) needSetDesktopSize(false), needExtendedDesktopSize(false), needSetDesktopName(false), needSetCursor(false), needSetXCursor(false), needSetCursorWithAlpha(false), + needSetVMWareCursor(false), needCursorPos(false), needLEDState(false), needQEMUKeyEvent(false) { @@ -321,6 +322,16 @@ bool SMsgWriter::writeSetCursorWithAlpha() return true; } +bool SMsgWriter::writeSetVMwareCursor() +{ + if (!cp->supportsVMWareCursor) + return false; + + needSetVMWareCursor = true; + + return true; +} + void SMsgWriter::writeCursorPos() { if (!cp->supportsEncoding(pseudoEncodingVMwareCursorPosition)) @@ -355,7 +366,7 @@ bool SMsgWriter::needFakeUpdate() { if (needSetDesktopName) return true; - if (needSetCursor || needSetXCursor || needSetCursorWithAlpha) + if (needSetCursor || needSetXCursor || needSetCursorWithAlpha || needSetVMWareCursor) return true; if (needCursorPos) return true; @@ -411,6 +422,8 @@ void SMsgWriter::writeFramebufferUpdateStart(int nRects) nRects++; if (needSetCursorWithAlpha) nRects++; + if (needSetVMWareCursor) + nRects++; if (needCursorPos) nRects++; if (needLEDState) @@ -528,6 +541,15 @@ void SMsgWriter::writePseudoRects() needSetCursorWithAlpha = false; } + if (needSetVMWareCursor) { + const Cursor& cursor = cp->cursor(); + + writeSetVMwareCursorRect(cursor.width(), cursor.height(), + cursor.hotspot().x, cursor.hotspot().y, + cursor.getBuffer()); + needSetVMWareCursor = false; + } + if (needCursorPos) { const Point& cursorPos = cp->cursorPos(); @@ -718,6 +740,28 @@ void SMsgWriter::writeSetCursorWithAlphaRect(int width, int height, } } +void SMsgWriter::writeSetVMwareCursorRect(int width, int height, + int hotspotX, int hotspotY, + const rdr::U8* data) +{ + if (!cp->supportsVMWareCursor) + throw Exception("Client does not support local cursors"); + if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader) + throw Exception("SMsgWriter::writeSetVMwareCursorRect: nRects out of sync"); + + os->writeS16(hotspotX); + os->writeS16(hotspotY); + os->writeU16(width); + os->writeU16(height); + os->writeU32(pseudoEncodingVMwareCursor); + + os->writeU8(1); // Alpha cursor + os->pad(1); + + // FIXME: Should alpha be premultiplied? + os->writeBytes(data, width*height*4); +} + void SMsgWriter::writeSetVMwareCursorPositionRect(int hotspotX, int hotspotY) { if (!cp->supportsEncoding(pseudoEncodingVMwareCursorPosition)) diff --git a/common/rfb/SMsgWriter.h b/common/rfb/SMsgWriter.h index 98188d3..0313bcc 100644 --- a/common/rfb/SMsgWriter.h +++ b/common/rfb/SMsgWriter.h @@ -92,6 +92,7 @@ namespace rfb { bool writeSetCursor(); bool writeSetXCursor(); bool writeSetCursorWithAlpha(); + bool writeSetVMwareCursor(); // Notifies the client that the cursor pointer was moved by the server. void writeCursorPos(); @@ -151,6 +152,9 @@ namespace rfb { void writeSetCursorWithAlphaRect(int width, int height, int hotspotX, int hotspotY, const rdr::U8* data); + void writeSetVMwareCursorRect(int width, int height, + int hotspotX, int hotspotY, + const rdr::U8* data); void writeSetVMwareCursorPositionRect(int hotspotX, int hotspotY); void writeLEDStateRect(rdr::U8 state); void writeQEMUKeyEventRect(); @@ -167,6 +171,7 @@ namespace rfb { bool needSetCursor; bool needSetXCursor; bool needSetCursorWithAlpha; + bool needSetVMWareCursor; bool needCursorPos; bool needLEDState; bool needQEMUKeyEvent; diff --git a/common/rfb/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx index 993c14a..aa2314c 100644 --- a/common/rfb/VNCSConnectionST.cxx +++ b/common/rfb/VNCSConnectionST.cxx @@ -574,6 +574,7 @@ bool VNCSConnectionST::needRenderedCursor() return false; if (!cp.supportsLocalCursorWithAlpha && + !cp.supportsVMWareCursor && !cp.supportsLocalCursor && !cp.supportsLocalXCursor) return true; if (!server->cursorPos.equals(pointerEventPos) && @@ -1550,11 +1551,13 @@ void VNCSConnectionST::setCursor() clientHasCursor = true; } - if (!writer()->writeSetCursorWithAlpha()) { - if (!writer()->writeSetCursor()) { - if (!writer()->writeSetXCursor()) { - // No client support - return; + if (!writer()->writeSetVMwareCursor()) { + if (!writer()->writeSetCursorWithAlpha()) { + if (!writer()->writeSetCursor()) { + if (!writer()->writeSetXCursor()) { + // No client support + return; + } } } } diff --git a/common/rfb/encodings.h b/common/rfb/encodings.h index fda4582..d5ce64b 100644 --- a/common/rfb/encodings.h +++ b/common/rfb/encodings.h @@ -86,6 +86,7 @@ namespace rfb { const int pseudoEncodingVideoOutTimeLevel100 = -1887; // VMware-specific + const int pseudoEncodingVMwareCursor = 0x574d5664; const int pseudoEncodingVMwareCursorPosition = 0x574d5666; // UltraVNC-specific From fae8aa9051648473171d84bb4104b3dcc457b210 Mon Sep 17 00:00:00 2001 From: matt Date: Thu, 29 Jul 2021 14:04:39 -0400 Subject: [PATCH 05/12] switched novnc commit --- .gitmodules | 2 +- kasmweb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index e43a13a..a57ba67 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "kasmweb"] path = kasmweb url = https://github.com/kasmtech/noVNC.git - branch = master + branch = apistats diff --git a/kasmweb b/kasmweb index 6746607..f9f5b01 160000 --- a/kasmweb +++ b/kasmweb @@ -1 +1 @@ -Subproject commit 67466077c07377178599315b0cba01440ce6fb53 +Subproject commit f9f5b01cc23f3084f83ccf15c144de130b8b3222 From c1ed769780d740da671812fa87d64752bf8af07e Mon Sep 17 00:00:00 2001 From: Lauri Kasanen Date: Mon, 2 Aug 2021 12:45:51 +0300 Subject: [PATCH 06/12] Clear disconnected clients from the bottleneck stats api --- common/network/GetAPI.h | 1 + common/network/GetAPIMessager.cxx | 9 +++++++++ common/rfb/VNCSConnectionST.cxx | 4 +++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/common/network/GetAPI.h b/common/network/GetAPI.h index c7c07b4..12d07b5 100644 --- a/common/network/GetAPI.h +++ b/common/network/GetAPI.h @@ -37,6 +37,7 @@ namespace network { // from main thread void mainUpdateScreen(rfb::PixelBuffer *pb); void mainUpdateBottleneckStats(const char userid[], const char stats[]); + void mainClearBottleneckStats(const char userid[]); void mainUpdateServerFrameStats(uint8_t changedPerc, uint32_t all, uint32_t jpeg, uint32_t webp, uint32_t analysis, uint32_t jpegarea, uint32_t webparea, diff --git a/common/network/GetAPIMessager.cxx b/common/network/GetAPIMessager.cxx index 3ce2675..9ea19bc 100644 --- a/common/network/GetAPIMessager.cxx +++ b/common/network/GetAPIMessager.cxx @@ -110,6 +110,15 @@ void GetAPIMessager::mainUpdateBottleneckStats(const char userid[], const char s pthread_mutex_unlock(&statMutex); } +void GetAPIMessager::mainClearBottleneckStats(const char userid[]) { + if (pthread_mutex_trylock(&statMutex)) + return; + + bottleneckStats.erase(userid); + + pthread_mutex_unlock(&statMutex); +} + void GetAPIMessager::mainUpdateServerFrameStats(uint8_t changedPerc, uint32_t all, uint32_t jpeg, uint32_t webp, uint32_t analysis, uint32_t jpegarea, uint32_t webparea, diff --git a/common/rfb/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx index aa2314c..a70b9b5 100644 --- a/common/rfb/VNCSConnectionST.cxx +++ b/common/rfb/VNCSConnectionST.cxx @@ -133,8 +133,10 @@ VNCSConnectionST::~VNCSConnectionST() delete [] fenceData; - if (server->apimessager) + if (server->apimessager) { server->apimessager->mainUpdateUserInfo(checkOwnerConn(), server->clients.size()); + server->apimessager->mainClearBottleneckStats(peerEndpoint.buf); + } } From f57e6e644b306c6b2166cb3851e76ce4ae4b8b23 Mon Sep 17 00:00:00 2001 From: Lauri Kasanen Date: Mon, 2 Aug 2021 13:47:48 +0300 Subject: [PATCH 07/12] Add some more frame stats granularity, skip frame stats on no-data calls --- common/network/GetAPI.h | 4 +++ common/network/GetAPIMessager.cxx | 9 +++++++ common/rfb/EncodeManager.cxx | 4 +++ common/rfb/EncodeManager.h | 8 ++++++ common/rfb/VNCSConnectionST.cxx | 1 + common/rfb/VNCSConnectionST.h | 7 ++++++ common/rfb/VNCServerST.cxx | 42 ++++++++++++++++++++++++------- 7 files changed, 66 insertions(+), 9 deletions(-) diff --git a/common/network/GetAPI.h b/common/network/GetAPI.h index 12d07b5..0771640 100644 --- a/common/network/GetAPI.h +++ b/common/network/GetAPI.h @@ -42,6 +42,7 @@ namespace network { uint32_t jpeg, uint32_t webp, uint32_t analysis, uint32_t jpegarea, uint32_t webparea, uint16_t njpeg, uint16_t nwebp, + uint16_t enc, uint16_t scale, uint16_t shot, uint16_t w, uint16_t h); void mainUpdateClientFrameStats(const char userid[], uint32_t render, uint32_t all, uint32_t ping); @@ -110,6 +111,9 @@ namespace network { uint32_t webparea; uint16_t njpeg; uint16_t nwebp; + uint16_t enc; + uint16_t scale; + uint16_t shot; uint16_t w; uint16_t h; uint8_t changedPerc; diff --git a/common/network/GetAPIMessager.cxx b/common/network/GetAPIMessager.cxx index 9ea19bc..e115000 100644 --- a/common/network/GetAPIMessager.cxx +++ b/common/network/GetAPIMessager.cxx @@ -123,6 +123,7 @@ void GetAPIMessager::mainUpdateServerFrameStats(uint8_t changedPerc, uint32_t all, uint32_t jpeg, uint32_t webp, uint32_t analysis, uint32_t jpegarea, uint32_t webparea, uint16_t njpeg, uint16_t nwebp, + uint16_t enc, uint16_t scale, uint16_t shot, uint16_t w, uint16_t h) { if (pthread_mutex_lock(&frameStatMutex)) @@ -137,6 +138,9 @@ void GetAPIMessager::mainUpdateServerFrameStats(uint8_t changedPerc, serverFrameStats.webparea = webparea; serverFrameStats.njpeg = njpeg; serverFrameStats.nwebp = nwebp; + serverFrameStats.enc = enc; + serverFrameStats.scale = scale; + serverFrameStats.shot = shot; serverFrameStats.w = w; serverFrameStats.h = h; @@ -487,10 +491,15 @@ void GetAPIMessager::netGetFrameStats(char *buf, uint32_t len) { fprintf(f, "\t\"server_side\" : [\n" "\t\t{ \"process_name\": \"Analysis\", \"time\": %u },\n" + "\t\t{ \"process_name\": \"Screenshot\", \"time\": %u },\n" + "\t\t{ \"process_name\": \"Encoding_total\", \"time\": %u, \"videoscaling\": %u },\n" "\t\t{ \"process_name\": \"TightJPEGEncoder\", \"time\": %u, \"count\": %u, \"area\": %u },\n" "\t\t{ \"process_name\": \"TightWEBPEncoder\", \"time\": %u, \"count\": %u, \"area\": %u }\n" "\t],\n", serverFrameStats.analysis, + serverFrameStats.shot, + serverFrameStats.enc, + serverFrameStats.scale, serverFrameStats.jpeg, serverFrameStats.njpeg, serverFrameStats.jpegarea, diff --git a/common/rfb/EncodeManager.cxx b/common/rfb/EncodeManager.cxx index d042d4c..db3add5 100644 --- a/common/rfb/EncodeManager.cxx +++ b/common/rfb/EncodeManager.cxx @@ -1085,6 +1085,9 @@ void EncodeManager::writeRects(const Region& changed, const PixelBuffer* pb, // In case the current resolution is above the max video res, and video was detected, // scale to that res, keeping aspect ratio + struct timeval scalestart; + gettimeofday(&scalestart, NULL); + const PixelBuffer *scaledpb = NULL; if (videoDetected && (maxVideoX < pb->getRect().width() || maxVideoY < pb->getRect().height())) { @@ -1133,6 +1136,7 @@ void EncodeManager::writeRects(const Region& changed, const PixelBuffer* pb, } } } + scalingTime = msSince(&scalestart); #pragma omp parallel for schedule(dynamic, 1) for (i = 0; i < subrects.size(); ++i) { diff --git a/common/rfb/EncodeManager.h b/common/rfb/EncodeManager.h index c271845..60f7d21 100644 --- a/common/rfb/EncodeManager.h +++ b/common/rfb/EncodeManager.h @@ -68,9 +68,16 @@ namespace rfb { const RenderedCursor* renderedCursor, size_t maxUpdateSize); + void clearEncodingTime() { + encodingTime = 0; + }; + unsigned getEncodingTime() const { return encodingTime; }; + unsigned getScalingTime() const { + return scalingTime; + }; struct codecstats_t { uint32_t ms; @@ -192,6 +199,7 @@ namespace rfb { bool webpTookTooLong; unsigned encodingTime; unsigned maxEncodingTime, framesSinceEncPrint; + unsigned scalingTime; EncCache *encCache; diff --git a/common/rfb/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx index a70b9b5..bd24229 100644 --- a/common/rfb/VNCSConnectionST.cxx +++ b/common/rfb/VNCSConnectionST.cxx @@ -1194,6 +1194,7 @@ bool VNCSConnectionST::isCongested() void VNCSConnectionST::writeFramebufferUpdate() { congestion.updatePosition(sock->outStream().length()); + encodeManager.clearEncodingTime(); // We're in the middle of processing a command that's supposed to be // synchronised. Allowing an update to slip out right now might violate diff --git a/common/rfb/VNCSConnectionST.h b/common/rfb/VNCSConnectionST.h index 043f398..86c99c6 100644 --- a/common/rfb/VNCSConnectionST.h +++ b/common/rfb/VNCSConnectionST.h @@ -186,6 +186,13 @@ namespace rfb { return encodeManager.webpstats; } + unsigned getEncodingTime() const { + return encodeManager.getEncodingTime(); + } + unsigned getScalingTime() const { + return encodeManager.getScalingTime(); + } + private: // SConnection callbacks diff --git a/common/rfb/VNCServerST.cxx b/common/rfb/VNCServerST.cxx index efaf070..fd13cf3 100644 --- a/common/rfb/VNCServerST.cxx +++ b/common/rfb/VNCServerST.cxx @@ -1006,14 +1006,20 @@ void VNCServerST::writeUpdate() } } + unsigned shottime = 0; if (apimessager) { + struct timeval shotstart; + gettimeofday(&shotstart, NULL); apimessager->mainUpdateScreen(pb); + shottime = msSince(&shotstart); trackingFrameStats = 0; checkAPIMessages(apimessager, trackingFrameStats, trackingClient); } + const rdr::U8 origtrackingFrameStats = trackingFrameStats; EncodeManager::codecstats_t jpegstats, webpstats; + unsigned enctime = 0, scaletime = 0; memset(&jpegstats, 0, sizeof(EncodeManager::codecstats_t)); memset(&webpstats, 0, sizeof(EncodeManager::codecstats_t)); @@ -1053,20 +1059,38 @@ void VNCServerST::writeUpdate() webpstats.ms += subwebp.ms; webpstats.area += subwebp.area; webpstats.rects += subwebp.rects; + + enctime += (*ci)->getEncodingTime(); + scaletime += (*ci)->getScalingTime(); } } if (trackingFrameStats) { - const unsigned totalMs = msSince(&start); + if (enctime) { + const unsigned totalMs = msSince(&start); - if (apimessager) - apimessager->mainUpdateServerFrameStats(comparer->changedPerc, totalMs, - jpegstats.ms, webpstats.ms, - analysisMs, - jpegstats.area, webpstats.area, - jpegstats.rects, webpstats.rects, - pb->getRect().width(), - pb->getRect().height()); + if (apimessager) + apimessager->mainUpdateServerFrameStats(comparer->changedPerc, totalMs, + jpegstats.ms, webpstats.ms, + analysisMs, + jpegstats.area, webpstats.area, + jpegstats.rects, webpstats.rects, + enctime, scaletime, shottime, + pb->getRect().width(), + pb->getRect().height()); + } else { + // Zero encoding time means this was a no-data frame; restore the stats request + if (apimessager && pthread_mutex_lock(&apimessager->userMutex) == 0) { + + network::GetAPIMessager::action_data act; + act.action = (network::GetAPIMessager::USER_ACTION) origtrackingFrameStats; + memcpy(act.data.password, trackingClient, 128); + + apimessager->actionQueue.push_back(act); + + pthread_mutex_unlock(&apimessager->userMutex); + } + } } } From e208d5bb5f99f6d8be20f8b29ec93991443ef59a Mon Sep 17 00:00:00 2001 From: Lauri Kasanen Date: Mon, 2 Aug 2021 13:56:54 +0300 Subject: [PATCH 08/12] Bottleneck clear should wait instead of try --- common/network/GetAPIMessager.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/network/GetAPIMessager.cxx b/common/network/GetAPIMessager.cxx index e115000..b3bc6bb 100644 --- a/common/network/GetAPIMessager.cxx +++ b/common/network/GetAPIMessager.cxx @@ -111,7 +111,7 @@ void GetAPIMessager::mainUpdateBottleneckStats(const char userid[], const char s } void GetAPIMessager::mainClearBottleneckStats(const char userid[]) { - if (pthread_mutex_trylock(&statMutex)) + if (pthread_mutex_lock(&statMutex)) return; bottleneckStats.erase(userid); From a9b66833a9fa08184f1acaedee693fe88581afc4 Mon Sep 17 00:00:00 2001 From: Lauri Kasanen Date: Tue, 3 Aug 2021 14:41:54 +0300 Subject: [PATCH 09/12] Change frame stats API json formatting --- common/network/GetAPIMessager.cxx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/common/network/GetAPIMessager.cxx b/common/network/GetAPIMessager.cxx index b3bc6bb..8ecfb67 100644 --- a/common/network/GetAPIMessager.cxx +++ b/common/network/GetAPIMessager.cxx @@ -447,7 +447,8 @@ void GetAPIMessager::netGetFrameStats(char *buf, uint32_t len) { { "process_name": "TightJPEGEncoder", "time": 20, "count": 64, "area": 12 } ], "client_side" : [ - "123.1.2.1:1211" : { + { + "client": "123.1.2.1:1211", "client_time": 20, "ping": 20, "processes" : [ @@ -513,7 +514,8 @@ void GetAPIMessager::netGetFrameStats(char *buf, uint32_t len) { const char *id = it->first.c_str(); const clientFrameStats_t &s = it->second; - fprintf(f, "\t\t\"%s\" : {\n" + fprintf(f, "\t\t\{\n" + "\t\t\t\"client\": \"%s\",\n" "\t\t\t\"client_time\": %u,\n" "\t\t\t\"ping\": %u,\n" "\t\t\t\"processes\" : [\n" From 544a9fc5928d0082f0527bf10a1970cfd37493d2 Mon Sep 17 00:00:00 2001 From: matt Date: Thu, 5 Aug 2021 11:12:03 -0400 Subject: [PATCH 10/12] KASM-1512 api stats KASM-1790 cursor --- .gitmodules | 2 +- kasmweb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index a57ba67..e43a13a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "kasmweb"] path = kasmweb url = https://github.com/kasmtech/noVNC.git - branch = apistats + branch = master diff --git a/kasmweb b/kasmweb index f9f5b01..519a402 160000 --- a/kasmweb +++ b/kasmweb @@ -1 +1 @@ -Subproject commit f9f5b01cc23f3084f83ccf15c144de130b8b3222 +Subproject commit 519a40213b6e2baec94464c8e65d5b4be07c0536 From ec6bd697a8785e2f3ed82cafd3b6a4c47fa43491 Mon Sep 17 00:00:00 2001 From: matt Date: Thu, 5 Aug 2021 14:48:41 -0400 Subject: [PATCH 11/12] update novnc ref --- kasmweb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kasmweb b/kasmweb index 519a402..ba40cac 160000 --- a/kasmweb +++ b/kasmweb @@ -1 +1 @@ -Subproject commit 519a40213b6e2baec94464c8e65d5b4be07c0536 +Subproject commit ba40cacce068fa35fc706c41605db14c04348170 From 9ca850a108cab215a72e579271dcfccad7df075b Mon Sep 17 00:00:00 2001 From: Lauri Kasanen Date: Fri, 13 Aug 2021 12:15:17 +0300 Subject: [PATCH 12/12] Add pid to the internal socket name, clarify websocketport bind error --- common/network/TcpSocket.cxx | 11 ++++++++--- common/network/websockify.c | 8 ++++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/common/network/TcpSocket.cxx b/common/network/TcpSocket.cxx index 141bdf7..f6f4a5a 100644 --- a/common/network/TcpSocket.cxx +++ b/common/network/TcpSocket.cxx @@ -40,6 +40,8 @@ #include #include #include +#include +#include #include "websocket.h" #include @@ -564,7 +566,7 @@ WebsocketListener::WebsocketListener(const struct sockaddr *listenaddr, if (bind(sock, &sa.u.sa, listenaddrlen) == -1) { int e = errorNumber; closesocket(sock); - throw SocketException("failed to bind socket", e); + throw SocketException("failed to bind socket, is someone else on our -websocketPort?", e); } listen(sock); // sets the internal fd @@ -574,13 +576,16 @@ WebsocketListener::WebsocketListener(const struct sockaddr *listenaddr, // internalSocket = socket(AF_UNIX, SOCK_STREAM, 0); + char sockname[32]; + sprintf(sockname, ".KasmVNCSock%u", getpid()); + struct sockaddr_un addr; addr.sun_family = AF_UNIX; - strcpy(addr.sun_path, ".KasmVNCSock"); + strcpy(addr.sun_path, sockname); addr.sun_path[0] = '\0'; if (bind(internalSocket, (struct sockaddr *) &addr, - sizeof(sa_family_t) + sizeof(".KasmVNCSock"))) { + sizeof(sa_family_t) + strlen(sockname))) { throw SocketException("failed to bind socket", errorNumber); } diff --git a/common/network/websockify.c b/common/network/websockify.c index 08491c9..a97c189 100644 --- a/common/network/websockify.c +++ b/common/network/websockify.c @@ -21,6 +21,7 @@ #include #include #include +#include #include "websocket.h" /* @@ -223,9 +224,12 @@ static void do_proxy(ws_ctx_t *ws_ctx, int target) { void proxy_handler(ws_ctx_t *ws_ctx) { + char sockname[32]; + sprintf(sockname, ".KasmVNCSock%u", getpid()); + struct sockaddr_un addr; addr.sun_family = AF_UNIX; - strcpy(addr.sun_path, ".KasmVNCSock"); + strcpy(addr.sun_path, sockname); addr.sun_path[0] = '\0'; struct timeval tv; @@ -243,7 +247,7 @@ void proxy_handler(ws_ctx_t *ws_ctx) { handler_msg("connecting to VNC target\n"); if (connect(tsock, (struct sockaddr *) &addr, - sizeof(sa_family_t) + sizeof(".KasmVNCSock")) < 0) { + sizeof(sa_family_t) + strlen(sockname)) < 0) { handler_emsg("Could not connect to target: %s\n", strerror(errno));