diff --git a/common/network/GetAPI.h b/common/network/GetAPI.h index c3eae8e..0771640 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,17 @@ 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, + 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); + void mainUpdateUserInfo(const uint8_t ownerConn, const uint8_t numUsers); // from network threads uint8_t *netGetScreenshot(uint16_t w, uint16_t h, @@ -42,13 +55,25 @@ 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); + 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; @@ -68,6 +93,40 @@ namespace network { std::vector cachedJpeg; uint16_t cachedW, cachedH; uint8_t cachedQ; + + 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 enc; + uint16_t scale; + uint16_t shot; + 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 b06fabd..8ecfb67 100644 --- a/common/network/GetAPIMessager.cxx +++ b/common/network/GetAPIMessager.cxx @@ -56,10 +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 @@ -95,6 +101,78 @@ 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); +} + +void GetAPIMessager::mainClearBottleneckStats(const char userid[]) { + if (pthread_mutex_lock(&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, + 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)) + 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.enc = enc; + serverFrameStats.scale = scale; + serverFrameStats.shot = shot; + 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, @@ -286,3 +364,271 @@ 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); +} + +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" : [ + { + "client": "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\": \"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, + 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\{\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" + "\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 4262be5..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 @@ -459,6 +461,67 @@ 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); +} + +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, bool sslonly, const char *cert, const char *certkey, @@ -503,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 @@ -513,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); } @@ -548,6 +614,18 @@ WebsocketListener::WebsocketListener(const struct sockaddr *listenaddr, settings.adduserCb = adduserCb; 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 72a101c..3d3111d 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"); @@ -1074,6 +1074,89 @@ 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; + } 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 fd00987..5cb8c0e 100644 --- a/common/network/websocket.h +++ b/common/network/websocket.h @@ -84,6 +84,18 @@ 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); + 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/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)); 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/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/EncodeManager.cxx b/common/rfb/EncodeManager.cxx index fb9b8a6..db3add5 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,9 +1081,13 @@ 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 + struct timeval scalestart; + gettimeofday(&scalestart, NULL); + const PixelBuffer *scaledpb = NULL; if (videoDetected && (maxVideoX < pb->getRect().width() || maxVideoY < pb->getRect().height())) { @@ -1129,15 +1136,25 @@ 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) { 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 +1195,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 +1249,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 +1295,8 @@ uint8_t EncodeManager::getEncoderType(const Rect& rect, const PixelBuffer *pb, compressed, videoDetected); } + + ms = msSince(&start); } delete ppb; @@ -1292,10 +1315,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..60f7d21 100644 --- a/common/rfb/EncodeManager.h +++ b/common/rfb/EncodeManager.h @@ -68,9 +68,24 @@ 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; + uint32_t area; + uint32_t rects; + }; + + codecstats_t jpegstats, webpstats; protected: void doUpdate(bool allowLossy, const Region& changed, @@ -105,7 +120,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, @@ -183,6 +199,7 @@ namespace rfb { bool webpTookTooLong; unsigned encodingTime; unsigned maxEncodingTime, framesSinceEncPrint; + unsigned scalingTime; EncCache *encCache; diff --git a/common/rfb/SMsgHandler.h b/common/rfb/SMsgHandler.h index 8b7a9ad..d2fe4af 100644 --- a/common/rfb/SMsgHandler.h +++ b/common/rfb/SMsgHandler.h @@ -63,7 +63,8 @@ namespace rfb { const size_t* lengths, const rdr::U8* const* data); - virtual void sendStats() = 0; + 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..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) { @@ -208,6 +209,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) @@ -315,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)) @@ -349,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; @@ -405,6 +422,8 @@ void SMsgWriter::writeFramebufferUpdateStart(int nRects) nRects++; if (needSetCursorWithAlpha) nRects++; + if (needSetVMWareCursor) + nRects++; if (needCursorPos) nRects++; if (needLEDState) @@ -522,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(); @@ -712,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 f561136..0313bcc 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[]); @@ -90,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(); @@ -149,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(); @@ -165,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 1d34c11..bd24229 100644 --- a/common/rfb/VNCSConnectionST.cxx +++ b/common/rfb/VNCSConnectionST.cxx @@ -17,6 +17,7 @@ * USA. */ +#include #include #include @@ -61,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(); @@ -98,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()); } @@ -128,6 +132,11 @@ VNCSConnectionST::~VNCSConnectionST() server->clients.remove(this); delete [] fenceData; + + if (server->apimessager) { + server->apimessager->mainUpdateUserInfo(checkOwnerConn(), server->clients.size()); + server->apimessager->mainClearBottleneckStats(peerEndpoint.buf); + } } @@ -567,6 +576,7 @@ bool VNCSConnectionST::needRenderedCursor() return false; if (!cp.supportsLocalCursorWithAlpha && + !cp.supportsVMWareCursor && !cp.supportsLocalCursor && !cp.supportsLocalXCursor) return true; if (!server->cursorPos.equals(pointerEventPos) && @@ -1184,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 @@ -1229,6 +1240,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(); @@ -1459,7 +1473,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 +1512,28 @@ 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); + } +} + +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. @@ -1520,11 +1554,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; + } } } } @@ -1611,3 +1647,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 0a3bcdc..86c99c6 100644 --- a/common/rfb/VNCSConnectionST.h +++ b/common/rfb/VNCSConnectionST.h @@ -164,6 +164,35 @@ namespace rfb { void setStatus(int status); 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; + } + + unsigned getEncodingTime() const { + return encodeManager.getEncodingTime(); + } + unsigned getScalingTime() const { + return encodeManager.getScalingTime(); + } + private: // SConnection callbacks @@ -191,7 +220,6 @@ namespace rfb { virtual void supportsContinuousUpdates(); virtual void supportsLEDState(); - virtual void sendStats(); virtual bool canChangeKasmSettings() const { return (accessRights & (AccessPtrEvents | AccessKeyEvents)) == (AccessPtrEvents | AccessKeyEvents); @@ -219,6 +247,8 @@ namespace rfb { bool getPerms(bool &write, bool &owner) const; + bool checkOwnerConn() const; + // Congestion control void writeRTTPing(); bool isCongested(); @@ -294,6 +324,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 44f1a25..fd13cf3 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; @@ -981,11 +1006,22 @@ void VNCServerST::writeUpdate() } } + unsigned shottime = 0; if (apimessager) { + struct timeval shotstart; + gettimeofday(&shotstart, NULL); apimessager->mainUpdateScreen(pb); + shottime = msSince(&shotstart); - checkAPIMessages(apimessager); + 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)); for (ci = clients.begin(); ci != clients.end(); ci = ci_next) { ci_next = ci; ci_next++; @@ -993,10 +1029,68 @@ void VNCServerST::writeUpdate() 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) { + (*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; + + enctime += (*ci)->getEncodingTime(); + scaletime += (*ci)->getScalingTime(); + } + } + + if (trackingFrameStats) { + 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, + 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); + } + } } } 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/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 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; diff --git a/kasmweb b/kasmweb index 6746607..ba40cac 160000 --- a/kasmweb +++ b/kasmweb @@ -1 +1 @@ -Subproject commit 67466077c07377178599315b0cba01440ce6fb53 +Subproject commit ba40cacce068fa35fc706c41605db14c04348170