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;