Merge branch 'master' into KASM-1609_select_de_to_run

This commit is contained in:
Dmitry Maksyoma 2021-08-17 00:06:36 +12:00
commit b4612d548f
26 changed files with 934 additions and 35 deletions

View File

@ -24,6 +24,8 @@
#include <rfb/PixelBuffer.h> #include <rfb/PixelBuffer.h>
#include <rfb/PixelFormat.h> #include <rfb/PixelFormat.h>
#include <stdint.h> #include <stdint.h>
#include <map>
#include <string>
#include <vector> #include <vector>
namespace network { namespace network {
@ -34,6 +36,17 @@ namespace network {
// from main thread // from main thread
void mainUpdateScreen(rfb::PixelBuffer *pb); 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 // from network threads
uint8_t *netGetScreenshot(uint16_t w, uint16_t h, 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 netAddUser(const char name[], const char pw[], const bool write);
uint8_t netRemoveUser(const char name[]); uint8_t netRemoveUser(const char name[]);
uint8_t netGiveControlTo(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 { enum USER_ACTION {
//USER_ADD, - handled locally for interactivity //USER_ADD, - handled locally for interactivity
USER_REMOVE, USER_REMOVE,
USER_GIVE_CONTROL, 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 { struct action_data {
enum USER_ACTION action; enum USER_ACTION action;
kasmpasswd_entry_t data; kasmpasswd_entry_t data;
@ -68,6 +93,40 @@ namespace network {
std::vector<uint8_t> cachedJpeg; std::vector<uint8_t> cachedJpeg;
uint16_t cachedW, cachedH; uint16_t cachedW, cachedH;
uint8_t cachedQ; uint8_t cachedQ;
std::map<std::string, std::string> 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<std::string, clientFrameStats_t> clientFrameStats;
serverFrameStats_t serverFrameStats;
pthread_mutex_t frameStatMutex;
uint8_t ownerConnected;
uint8_t activeUsers;
pthread_mutex_t userInfoMutex;
}; };
} }

View File

@ -56,10 +56,16 @@ static const struct TightJPEGConfiguration conf[10] = {
GetAPIMessager::GetAPIMessager(const char *passwdfile_): passwdfile(passwdfile_), GetAPIMessager::GetAPIMessager(const char *passwdfile_): passwdfile(passwdfile_),
screenW(0), screenH(0), screenHash(0), 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(&screenMutex, NULL);
pthread_mutex_init(&userMutex, 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 // from main thread
@ -95,6 +101,78 @@ void GetAPIMessager::mainUpdateScreen(rfb::PixelBuffer *pb) {
pthread_mutex_unlock(&screenMutex); 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 // from network threads
uint8_t *GetAPIMessager::netGetScreenshot(uint16_t w, uint16_t h, uint8_t *GetAPIMessager::netGetScreenshot(uint16_t w, uint16_t h,
const uint8_t q, const bool dedup, const uint8_t q, const bool dedup,
@ -286,3 +364,271 @@ uint8_t GetAPIMessager::netGiveControlTo(const char name[]) {
return 1; 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<std::string, std::string>::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<std::string, clientFrameStats_t>::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;
}

View File

@ -40,6 +40,8 @@
#include <unistd.h> #include <unistd.h>
#include <pthread.h> #include <pthread.h>
#include <wordexp.h> #include <wordexp.h>
#include <sys/types.h>
#include <unistd.h>
#include "websocket.h" #include "websocket.h"
#include <network/GetAPI.h> #include <network/GetAPI.h>
@ -459,6 +461,67 @@ static uint8_t givecontrolCb(void *messager, const char name[])
return msgr->netGiveControlTo(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, WebsocketListener::WebsocketListener(const struct sockaddr *listenaddr,
socklen_t listenaddrlen, socklen_t listenaddrlen,
bool sslonly, const char *cert, const char *certkey, 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) { if (bind(sock, &sa.u.sa, listenaddrlen) == -1) {
int e = errorNumber; int e = errorNumber;
closesocket(sock); 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 listen(sock); // sets the internal fd
@ -513,13 +576,16 @@ WebsocketListener::WebsocketListener(const struct sockaddr *listenaddr,
// //
internalSocket = socket(AF_UNIX, SOCK_STREAM, 0); internalSocket = socket(AF_UNIX, SOCK_STREAM, 0);
char sockname[32];
sprintf(sockname, ".KasmVNCSock%u", getpid());
struct sockaddr_un addr; struct sockaddr_un addr;
addr.sun_family = AF_UNIX; addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, ".KasmVNCSock"); strcpy(addr.sun_path, sockname);
addr.sun_path[0] = '\0'; addr.sun_path[0] = '\0';
if (bind(internalSocket, (struct sockaddr *) &addr, 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); throw SocketException("failed to bind socket", errorNumber);
} }
@ -548,6 +614,18 @@ WebsocketListener::WebsocketListener(const struct sockaddr *listenaddr,
settings.adduserCb = adduserCb; settings.adduserCb = adduserCb;
settings.removeCb = removeCb; settings.removeCb = removeCb;
settings.givecontrolCb = givecontrolCb; 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_t tid;
pthread_create(&tid, NULL, start_server, NULL); pthread_create(&tid, NULL, start_server, NULL);

View File

@ -566,7 +566,7 @@ int parse_handshake(ws_ctx_t *ws_ctx, char *handshake) {
headers->key3[0] = '\0'; headers->key3[0] = '\0';
if ((strlen(handshake) < 92) || (bcmp(handshake, "GET ", 4) != 0) || if ((strlen(handshake) < 92) || (bcmp(handshake, "GET ", 4) != 0) ||
(!strstr(handshake, "Upgrade: websocket"))) { (!strcasestr(handshake, "Upgrade: websocket"))) {
return 0; return 0;
} }
start = handshake+4; start = handshake+4;
@ -587,7 +587,7 @@ int parse_handshake(ws_ctx_t *ws_ctx, char *handshake) {
if (start) { if (start) {
start += 10; start += 10;
} else { } else {
start = strstr(handshake, "\r\nSec-WebSocket-Origin: "); start = strcasestr(handshake, "\r\nSec-WebSocket-Origin: ");
if (!start) { return 0; } if (!start) { return 0; }
start += 24; start += 24;
} }
@ -595,7 +595,7 @@ int parse_handshake(ws_ctx_t *ws_ctx, char *handshake) {
strncpy(headers->origin, start, end-start); strncpy(headers->origin, start, end-start);
headers->origin[end-start] = '\0'; headers->origin[end-start] = '\0';
start = strstr(handshake, "\r\nSec-WebSocket-Version: "); start = strcasestr(handshake, "\r\nSec-WebSocket-Version: ");
if (start) { if (start) {
// HyBi/RFC 6455 // HyBi/RFC 6455
start += 25; start += 25;
@ -605,7 +605,7 @@ int parse_handshake(ws_ctx_t *ws_ctx, char *handshake) {
ws_ctx->hixie = 0; ws_ctx->hixie = 0;
ws_ctx->hybi = strtol(headers->version, NULL, 10); 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; } if (!start) { return 0; }
start += 21; start += 21;
end = strstr(start, "\r\n"); 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); strncpy(headers->connection, start, end-start);
headers->connection[end-start] = '\0'; headers->connection[end-start] = '\0';
start = strstr(handshake, "\r\nSec-WebSocket-Protocol: "); start = strcasestr(handshake, "\r\nSec-WebSocket-Protocol: ");
if (!start) { return 0; } if (!start) { return 0; }
start += 26; start += 26;
end = strstr(start, "\r\n"); end = strstr(start, "\r\n");
@ -637,14 +637,14 @@ int parse_handshake(ws_ctx_t *ws_ctx, char *handshake) {
strncpy(headers->key3, start, 8); strncpy(headers->key3, start, 8);
headers->key3[8] = '\0'; headers->key3[8] = '\0';
start = strstr(handshake, "\r\nSec-WebSocket-Key1: "); start = strcasestr(handshake, "\r\nSec-WebSocket-Key1: ");
if (!start) { return 0; } if (!start) { return 0; }
start += 22; start += 22;
end = strstr(start, "\r\n"); end = strstr(start, "\r\n");
strncpy(headers->key1, start, end-start); strncpy(headers->key1, start, end-start);
headers->key1[end-start] = '\0'; headers->key1[end-start] = '\0';
start = strstr(handshake, "\r\nSec-WebSocket-Key2: "); start = strcasestr(handshake, "\r\nSec-WebSocket-Key2: ");
if (!start) { return 0; } if (!start) { return 0; }
start += 22; start += 22;
end = strstr(start, "\r\n"); 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"); wserr("Passed give_control request to main thread\n");
ret = 1; 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 #undef entry

View File

@ -84,6 +84,18 @@ typedef struct {
const uint8_t write); const uint8_t write);
uint8_t (*removeCb)(void *messager, const char name[]); uint8_t (*removeCb)(void *messager, const char name[]);
uint8_t (*givecontrolCb)(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; } settings_t;
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -21,6 +21,7 @@
#include <unistd.h> #include <unistd.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/time.h> #include <sys/time.h>
#include <sys/types.h>
#include "websocket.h" #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) { void proxy_handler(ws_ctx_t *ws_ctx) {
char sockname[32];
sprintf(sockname, ".KasmVNCSock%u", getpid());
struct sockaddr_un addr; struct sockaddr_un addr;
addr.sun_family = AF_UNIX; addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, ".KasmVNCSock"); strcpy(addr.sun_path, sockname);
addr.sun_path[0] = '\0'; addr.sun_path[0] = '\0';
struct timeval tv; struct timeval tv;
@ -243,7 +247,7 @@ void proxy_handler(ws_ctx_t *ws_ctx) {
handler_msg("connecting to VNC target\n"); handler_msg("connecting to VNC target\n");
if (connect(tsock, (struct sockaddr *) &addr, 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", handler_emsg("Could not connect to target: %s\n",
strerror(errno)); strerror(errno));

View File

@ -695,6 +695,8 @@ bool ComparingUpdateTracker::compare(bool skipScrollDetection, const Region &ski
std::vector<Rect> rects; std::vector<Rect> rects;
std::vector<Rect>::iterator i; std::vector<Rect>::iterator i;
changedPerc = 100;
if (!enabled) if (!enabled)
return false; return false;
@ -749,8 +751,13 @@ bool ComparingUpdateTracker::compare(bool skipScrollDetection, const Region &ski
for (i = rects.begin(); i != rects.end(); i++) for (i = rects.begin(); i != rects.end(); i++)
totalPixels += i->area(); totalPixels += i->area();
newChanged.get_rects(&rects); 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(); missedPixels += i->area();
newchangedarea += i->area();
}
changedPerc = newchangedarea * 100 / fb->area();
if (changed.equals(newChanged)) if (changed.equals(newChanged))
return false; return false;

View File

@ -48,6 +48,8 @@ namespace rfb {
virtual void getUpdateInfo(UpdateInfo* info, const Region& cliprgn); virtual void getUpdateInfo(UpdateInfo* info, const Region& cliprgn);
virtual void clear(); virtual void clear();
rdr::U8 changedPerc;
private: private:
void compareRect(const Rect& r, Region* newchanged, const Region &skipCursorArea); void compareRect(const Rect& r, Region* newchanged, const Region &skipCursorArea);
PixelBuffer* fb; PixelBuffer* fb;

View File

@ -298,6 +298,11 @@ size_t Congestion::getBandwidth()
return congWindow * 1000 / safeBaseRTT; return congWindow * 1000 / safeBaseRTT;
} }
unsigned Congestion::getPingTime() const
{
return safeBaseRTT;
}
void Congestion::debugTrace(const char* filename, int fd) void Congestion::debugTrace(const char* filename, int fd)
{ {
#ifdef CONGESTION_TRACE #ifdef CONGESTION_TRACE

View File

@ -51,6 +51,8 @@ namespace rfb {
// per second. // per second.
size_t getBandwidth(); size_t getBandwidth();
unsigned getPingTime() const;
// debugTrace() writes the current congestion window, as well as the // debugTrace() writes the current congestion window, as well as the
// congestion window of the underlying TCP layer, to the specified // congestion window of the underlying TCP layer, to the specified
// file // file

View File

@ -37,6 +37,7 @@ ConnParams::ConnParams()
width(0), height(0), useCopyRect(false), width(0), height(0), useCopyRect(false),
supportsLocalCursor(false), supportsLocalXCursor(false), supportsLocalCursor(false), supportsLocalXCursor(false),
supportsLocalCursorWithAlpha(false), supportsLocalCursorWithAlpha(false),
supportsVMWareCursor(false),
supportsCursorPosition(false), supportsCursorPosition(false),
supportsDesktopResize(false), supportsExtendedDesktopSize(false), supportsDesktopResize(false), supportsExtendedDesktopSize(false),
supportsDesktopRename(false), supportsLastRect(false), supportsDesktopRename(false), supportsLastRect(false),
@ -123,6 +124,7 @@ void ConnParams::setEncodings(int nEncodings, const rdr::S32* encodings)
useCopyRect = false; useCopyRect = false;
supportsLocalCursor = false; supportsLocalCursor = false;
supportsLocalCursorWithAlpha = false; supportsLocalCursorWithAlpha = false;
supportsVMWareCursor = false;
supportsDesktopResize = false; supportsDesktopResize = false;
supportsExtendedDesktopSize = false; supportsExtendedDesktopSize = false;
supportsLocalXCursor = false; supportsLocalXCursor = false;
@ -153,6 +155,9 @@ void ConnParams::setEncodings(int nEncodings, const rdr::S32* encodings)
case pseudoEncodingCursorWithAlpha: case pseudoEncodingCursorWithAlpha:
supportsLocalCursorWithAlpha = true; supportsLocalCursorWithAlpha = true;
break; break;
case pseudoEncodingVMwareCursor:
supportsVMWareCursor = true;
break;
case pseudoEncodingDesktopSize: case pseudoEncodingDesktopSize:
supportsDesktopResize = true; supportsDesktopResize = true;
break; break;

View File

@ -102,6 +102,7 @@ namespace rfb {
bool supportsLocalCursor; bool supportsLocalCursor;
bool supportsLocalXCursor; bool supportsLocalXCursor;
bool supportsLocalCursorWithAlpha; bool supportsLocalCursorWithAlpha;
bool supportsVMWareCursor;
bool supportsCursorPosition; bool supportsCursorPosition;
bool supportsDesktopResize; bool supportsDesktopResize;
bool supportsExtendedDesktopSize; bool supportsExtendedDesktopSize;

View File

@ -359,6 +359,8 @@ void EncodeManager::doUpdate(bool allowLossy, const Region& changed_,
changed = changed_; changed = changed_;
gettimeofday(&start, NULL); gettimeofday(&start, NULL);
memset(&jpegstats, 0, sizeof(codecstats_t));
memset(&webpstats, 0, sizeof(codecstats_t));
if (allowLossy && activeEncoders[encoderFullColour] == encoderTightWEBP) { if (allowLossy && activeEncoders[encoderFullColour] == encoderTightWEBP) {
const unsigned rate = 1024 * 1000 / rfb::Server::frameRate; const unsigned rate = 1024 * 1000 / rfb::Server::frameRate;
@ -1014,6 +1016,7 @@ void EncodeManager::writeRects(const Region& changed, const PixelBuffer* pb,
std::vector<uint8_t> isWebp, fromCache; std::vector<uint8_t> isWebp, fromCache;
std::vector<Palette> palettes; std::vector<Palette> palettes;
std::vector<std::vector<uint8_t> > compresseds; std::vector<std::vector<uint8_t> > compresseds;
std::vector<uint32_t> ms;
uint32_t i; uint32_t i;
if (rfb::Server::rectThreads > 0) if (rfb::Server::rectThreads > 0)
@ -1078,9 +1081,13 @@ void EncodeManager::writeRects(const Region& changed, const PixelBuffer* pb,
palettes.resize(subrects.size()); palettes.resize(subrects.size());
compresseds.resize(subrects.size()); compresseds.resize(subrects.size());
scaledrects.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, // In case the current resolution is above the max video res, and video was detected,
// scale to that res, keeping aspect ratio // scale to that res, keeping aspect ratio
struct timeval scalestart;
gettimeofday(&scalestart, NULL);
const PixelBuffer *scaledpb = NULL; const PixelBuffer *scaledpb = NULL;
if (videoDetected && if (videoDetected &&
(maxVideoX < pb->getRect().width() || maxVideoY < pb->getRect().height())) { (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) #pragma omp parallel for schedule(dynamic, 1)
for (i = 0; i < subrects.size(); ++i) { for (i = 0; i < subrects.size(); ++i) {
encoderTypes[i] = getEncoderType(subrects[i], pb, &palettes[i], compresseds[i], encoderTypes[i] = getEncoderType(subrects[i], pb, &palettes[i], compresseds[i],
&isWebp[i], &fromCache[i], &isWebp[i], &fromCache[i],
scaledpb, scaledrects[i]); scaledpb, scaledrects[i], ms[i]);
checkWebpFallback(start); 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) { if (start) {
encodingTime = msSince(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, uint8_t EncodeManager::getEncoderType(const Rect& rect, const PixelBuffer *pb,
Palette *pal, std::vector<uint8_t> &compressed, Palette *pal, std::vector<uint8_t> &compressed,
uint8_t *isWebp, uint8_t *fromCache, 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; struct RectInfo info;
unsigned int maxColours = 256; unsigned int maxColours = 256;
@ -1231,9 +1249,12 @@ uint8_t EncodeManager::getEncoderType(const Rect& rect, const PixelBuffer *pb,
*isWebp = 0; *isWebp = 0;
*fromCache = 0; *fromCache = 0;
ms = 0;
if (type == encoderFullColour) { if (type == encoderFullColour) {
uint32_t len; uint32_t len;
const void *data; const void *data;
struct timeval start;
gettimeofday(&start, NULL);
if (encCache->enabled && if (encCache->enabled &&
(data = encCache->get(activeEncoders[encoderFullColour], (data = encCache->get(activeEncoders[encoderFullColour],
@ -1274,6 +1295,8 @@ uint8_t EncodeManager::getEncoderType(const Rect& rect, const PixelBuffer *pb,
compressed, compressed,
videoDetected); videoDetected);
} }
ms = msSince(&start);
} }
delete ppb; delete ppb;
@ -1292,10 +1315,15 @@ void EncodeManager::writeSubRect(const Rect& rect, const PixelBuffer *pb,
encoder = startRect(rect, type, compressed.size() == 0, isWebp); encoder = startRect(rect, type, compressed.size() == 0, isWebp);
if (compressed.size()) { if (compressed.size()) {
if (isWebp) if (isWebp) {
((TightWEBPEncoder *) encoder)->writeOnly(compressed); ((TightWEBPEncoder *) encoder)->writeOnly(compressed);
else webpstats.area += rect.area();
webpstats.rects++;
} else {
((TightJPEGEncoder *) encoder)->writeOnly(compressed); ((TightJPEGEncoder *) encoder)->writeOnly(compressed);
jpegstats.area += rect.area();
jpegstats.rects++;
}
} else { } else {
if (encoder->flags & EncoderUseNativePF) { if (encoder->flags & EncoderUseNativePF) {
ppb = preparePixelBuffer(rect, pb, false); ppb = preparePixelBuffer(rect, pb, false);

View File

@ -68,9 +68,24 @@ namespace rfb {
const RenderedCursor* renderedCursor, const RenderedCursor* renderedCursor,
size_t maxUpdateSize); size_t maxUpdateSize);
void clearEncodingTime() {
encodingTime = 0;
};
unsigned getEncodingTime() const { unsigned getEncodingTime() const {
return encodingTime; return encodingTime;
}; };
unsigned getScalingTime() const {
return scalingTime;
};
struct codecstats_t {
uint32_t ms;
uint32_t area;
uint32_t rects;
};
codecstats_t jpegstats, webpstats;
protected: protected:
void doUpdate(bool allowLossy, const Region& changed, void doUpdate(bool allowLossy, const Region& changed,
@ -105,7 +120,8 @@ namespace rfb {
uint8_t getEncoderType(const Rect& rect, const PixelBuffer *pb, Palette *pal, uint8_t getEncoderType(const Rect& rect, const PixelBuffer *pb, Palette *pal,
std::vector<uint8_t> &compressed, uint8_t *isWebp, std::vector<uint8_t> &compressed, uint8_t *isWebp,
uint8_t *fromCache, 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); virtual bool handleTimeout(Timer* t);
bool checkSolidTile(const Rect& r, const rdr::U8* colourValue, bool checkSolidTile(const Rect& r, const rdr::U8* colourValue,
@ -183,6 +199,7 @@ namespace rfb {
bool webpTookTooLong; bool webpTookTooLong;
unsigned encodingTime; unsigned encodingTime;
unsigned maxEncodingTime, framesSinceEncPrint; unsigned maxEncodingTime, framesSinceEncPrint;
unsigned scalingTime;
EncCache *encCache; EncCache *encCache;

View File

@ -63,7 +63,8 @@ namespace rfb {
const size_t* lengths, const size_t* lengths,
const rdr::U8* const* data); 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; virtual bool canChangeKasmSettings() const = 0;

View File

@ -80,6 +80,9 @@ void SMsgReader::readMsg()
case msgTypeRequestStats: case msgTypeRequestStats:
readRequestStats(); readRequestStats();
break; break;
case msgTypeFrameStats:
readFrameStats();
break;
case msgTypeKeyEvent: case msgTypeKeyEvent:
readKeyEvent(); readKeyEvent();
break; break;
@ -346,6 +349,14 @@ void SMsgReader::readRequestStats()
handler->sendStats(); 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() void SMsgReader::readQEMUMessage()
{ {
int subType = is->readU8(); int subType = is->readU8();

View File

@ -57,6 +57,7 @@ namespace rfb {
void readClientCutText(); void readClientCutText();
void readExtendedClipboard(rdr::S32 len); void readExtendedClipboard(rdr::S32 len);
void readRequestStats(); void readRequestStats();
void readFrameStats();
void readQEMUMessage(); void readQEMUMessage();
void readQEMUKeyEvent(); void readQEMUKeyEvent();

View File

@ -43,6 +43,7 @@ SMsgWriter::SMsgWriter(ConnParams* cp_, rdr::OutStream* os_)
needSetDesktopSize(false), needExtendedDesktopSize(false), needSetDesktopSize(false), needExtendedDesktopSize(false),
needSetDesktopName(false), needSetCursor(false), needSetDesktopName(false), needSetCursor(false),
needSetXCursor(false), needSetCursorWithAlpha(false), needSetXCursor(false), needSetCursorWithAlpha(false),
needSetVMWareCursor(false),
needCursorPos(false), needCursorPos(false),
needLEDState(false), needQEMUKeyEvent(false) needLEDState(false), needQEMUKeyEvent(false)
{ {
@ -208,6 +209,12 @@ void SMsgWriter::writeStats(const char* str, int len)
endMsg(); endMsg();
} }
void SMsgWriter::writeRequestFrameStats()
{
startMsg(msgTypeRequestFrameStats);
endMsg();
}
void SMsgWriter::writeFence(rdr::U32 flags, unsigned len, const char data[]) void SMsgWriter::writeFence(rdr::U32 flags, unsigned len, const char data[])
{ {
if (!cp->supportsFence) if (!cp->supportsFence)
@ -315,6 +322,16 @@ bool SMsgWriter::writeSetCursorWithAlpha()
return true; return true;
} }
bool SMsgWriter::writeSetVMwareCursor()
{
if (!cp->supportsVMWareCursor)
return false;
needSetVMWareCursor = true;
return true;
}
void SMsgWriter::writeCursorPos() void SMsgWriter::writeCursorPos()
{ {
if (!cp->supportsEncoding(pseudoEncodingVMwareCursorPosition)) if (!cp->supportsEncoding(pseudoEncodingVMwareCursorPosition))
@ -349,7 +366,7 @@ bool SMsgWriter::needFakeUpdate()
{ {
if (needSetDesktopName) if (needSetDesktopName)
return true; return true;
if (needSetCursor || needSetXCursor || needSetCursorWithAlpha) if (needSetCursor || needSetXCursor || needSetCursorWithAlpha || needSetVMWareCursor)
return true; return true;
if (needCursorPos) if (needCursorPos)
return true; return true;
@ -405,6 +422,8 @@ void SMsgWriter::writeFramebufferUpdateStart(int nRects)
nRects++; nRects++;
if (needSetCursorWithAlpha) if (needSetCursorWithAlpha)
nRects++; nRects++;
if (needSetVMWareCursor)
nRects++;
if (needCursorPos) if (needCursorPos)
nRects++; nRects++;
if (needLEDState) if (needLEDState)
@ -522,6 +541,15 @@ void SMsgWriter::writePseudoRects()
needSetCursorWithAlpha = false; 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) { if (needCursorPos) {
const Point& cursorPos = cp->cursorPos(); 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) void SMsgWriter::writeSetVMwareCursorPositionRect(int hotspotX, int hotspotY)
{ {
if (!cp->supportsEncoding(pseudoEncodingVMwareCursorPosition)) if (!cp->supportsEncoding(pseudoEncodingVMwareCursorPosition))

View File

@ -65,6 +65,8 @@ namespace rfb {
void writeStats(const char* str, int len); void writeStats(const char* str, int len);
void writeRequestFrameStats();
// writeFence() sends a new fence request or response to the client. // writeFence() sends a new fence request or response to the client.
void writeFence(rdr::U32 flags, unsigned len, const char data[]); void writeFence(rdr::U32 flags, unsigned len, const char data[]);
@ -90,6 +92,7 @@ namespace rfb {
bool writeSetCursor(); bool writeSetCursor();
bool writeSetXCursor(); bool writeSetXCursor();
bool writeSetCursorWithAlpha(); bool writeSetCursorWithAlpha();
bool writeSetVMwareCursor();
// Notifies the client that the cursor pointer was moved by the server. // Notifies the client that the cursor pointer was moved by the server.
void writeCursorPos(); void writeCursorPos();
@ -149,6 +152,9 @@ namespace rfb {
void writeSetCursorWithAlphaRect(int width, int height, void writeSetCursorWithAlphaRect(int width, int height,
int hotspotX, int hotspotY, int hotspotX, int hotspotY,
const rdr::U8* data); 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 writeSetVMwareCursorPositionRect(int hotspotX, int hotspotY);
void writeLEDStateRect(rdr::U8 state); void writeLEDStateRect(rdr::U8 state);
void writeQEMUKeyEventRect(); void writeQEMUKeyEventRect();
@ -165,6 +171,7 @@ namespace rfb {
bool needSetCursor; bool needSetCursor;
bool needSetXCursor; bool needSetXCursor;
bool needSetCursorWithAlpha; bool needSetCursorWithAlpha;
bool needSetVMWareCursor;
bool needCursorPos; bool needCursorPos;
bool needLEDState; bool needLEDState;
bool needQEMUKeyEvent; bool needQEMUKeyEvent;

View File

@ -17,6 +17,7 @@
* USA. * USA.
*/ */
#include <network/GetAPI.h>
#include <network/TcpSocket.h> #include <network/TcpSocket.h>
#include <rfb/ComparingUpdateTracker.h> #include <rfb/ComparingUpdateTracker.h>
@ -61,7 +62,7 @@ VNCSConnectionST::VNCSConnectionST(VNCServerST* server_, network::Socket *s,
continuousUpdates(false), encodeManager(this, &server_->encCache), continuousUpdates(false), encodeManager(this, &server_->encCache),
needsPermCheck(false), pointerEventTime(0), needsPermCheck(false), pointerEventTime(0),
clientHasCursor(false), clientHasCursor(false),
accessRights(AccessDefault), startTime(time(0)) accessRights(AccessDefault), startTime(time(0)), frameTracking(false)
{ {
setStreams(&sock->inStream(), &sock->outStream()); setStreams(&sock->inStream(), &sock->outStream());
peerEndpoint.buf = sock->getPeerEndpoint(); peerEndpoint.buf = sock->getPeerEndpoint();
@ -98,6 +99,9 @@ VNCSConnectionST::VNCSConnectionST(VNCServerST* server_, network::Socket *s,
gettimeofday(&lastKeyEvent, NULL); gettimeofday(&lastKeyEvent, NULL);
server->clients.push_front(this); 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); server->clients.remove(this);
delete [] fenceData; 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; return false;
if (!cp.supportsLocalCursorWithAlpha && if (!cp.supportsLocalCursorWithAlpha &&
!cp.supportsVMWareCursor &&
!cp.supportsLocalCursor && !cp.supportsLocalXCursor) !cp.supportsLocalCursor && !cp.supportsLocalXCursor)
return true; return true;
if (!server->cursorPos.equals(pointerEventPos) && if (!server->cursorPos.equals(pointerEventPos) &&
@ -1184,6 +1194,7 @@ bool VNCSConnectionST::isCongested()
void VNCSConnectionST::writeFramebufferUpdate() void VNCSConnectionST::writeFramebufferUpdate()
{ {
congestion.updatePosition(sock->outStream().length()); congestion.updatePosition(sock->outStream().length());
encodeManager.clearEncodingTime();
// We're in the middle of processing a command that's supposed to be // 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 // synchronised. Allowing an update to slip out right now might violate
@ -1229,6 +1240,9 @@ void VNCSConnectionST::writeFramebufferUpdate()
// window. // window.
sock->cork(true); sock->cork(true);
if (frameTracking)
writer()->writeRequestFrameStats();
// First take care of any updates that cannot contain framebuffer data // First take care of any updates that cannot contain framebuffer data
// changes. // changes.
writeNoDataUpdate(); writeNoDataUpdate();
@ -1459,7 +1473,7 @@ static void pruneStatList(std::list<struct timeval> &list, const struct timeval
} }
} }
void VNCSConnectionST::sendStats() { void VNCSConnectionST::sendStats(const bool toClient) {
char buf[1024]; char buf[1024];
struct timeval now; struct timeval now;
@ -1498,8 +1512,28 @@ void VNCSConnectionST::sendStats() {
#undef ten #undef ten
vlog.info("Sending client stats:\n%s\n", buf); if (toClient) {
writer()->writeStats(buf, strlen(buf)); 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. // setCursor() is called whenever the cursor has changed shape or pixel format.
@ -1520,11 +1554,13 @@ void VNCSConnectionST::setCursor()
clientHasCursor = true; clientHasCursor = true;
} }
if (!writer()->writeSetCursorWithAlpha()) { if (!writer()->writeSetVMwareCursor()) {
if (!writer()->writeSetCursor()) { if (!writer()->writeSetCursorWithAlpha()) {
if (!writer()->writeSetXCursor()) { if (!writer()->writeSetCursor()) {
// No client support if (!writer()->writeSetXCursor()) {
return; // No client support
return;
}
} }
} }
} }
@ -1611,3 +1647,15 @@ int VNCSConnectionST::getStatus()
return 4; return 4;
} }
bool VNCSConnectionST::checkOwnerConn() const
{
std::list<VNCSConnectionST*>::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;
}

View File

@ -164,6 +164,35 @@ namespace rfb {
void setStatus(int status); void setStatus(int status);
int getStatus(); 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: private:
// SConnection callbacks // SConnection callbacks
@ -191,7 +220,6 @@ namespace rfb {
virtual void supportsContinuousUpdates(); virtual void supportsContinuousUpdates();
virtual void supportsLEDState(); virtual void supportsLEDState();
virtual void sendStats();
virtual bool canChangeKasmSettings() const { virtual bool canChangeKasmSettings() const {
return (accessRights & (AccessPtrEvents | AccessKeyEvents)) == return (accessRights & (AccessPtrEvents | AccessKeyEvents)) ==
(AccessPtrEvents | AccessKeyEvents); (AccessPtrEvents | AccessKeyEvents);
@ -219,6 +247,8 @@ namespace rfb {
bool getPerms(bool &write, bool &owner) const; bool getPerms(bool &write, bool &owner) const;
bool checkOwnerConn() const;
// Congestion control // Congestion control
void writeRTTPing(); void writeRTTPing();
bool isCongested(); bool isCongested();
@ -294,6 +324,8 @@ namespace rfb {
time_t startTime; time_t startTime;
std::vector<CopyPassRect> copypassed; std::vector<CopyPassRect> copypassed;
bool frameTracking;
}; };
} }
#endif #endif

View File

@ -128,7 +128,7 @@ VNCServerST::VNCServerST(const char* name_, SDesktop* desktop_)
renderedCursorInvalid(false), renderedCursorInvalid(false),
queryConnectionHandler(0), keyRemapper(&KeyRemapper::defInstance), queryConnectionHandler(0), keyRemapper(&KeyRemapper::defInstance),
lastConnectionTime(0), disableclients(false), lastConnectionTime(0), disableclients(false),
frameTimer(this), apimessager(NULL) frameTimer(this), apimessager(NULL), trackingFrameStats(0)
{ {
lastUserInputTime = lastDisconnectTime = time(0); lastUserInputTime = lastDisconnectTime = time(0);
slog.debug("creating single-threaded server %s", name.buf); 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) if (inotify_add_watch(inotifyfd, kasmpasswdpath, IN_CLOSE_WRITE | IN_DELETE_SELF) < 0)
slog.error("Failed to set watch"); slog.error("Failed to set watch");
} }
trackingClient[0] = 0;
} }
VNCServerST::~VNCServerST() VNCServerST::~VNCServerST()
@ -774,7 +776,8 @@ int VNCServerST::msToNextUpdate()
return frameTimer.getRemainingMs(); 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)) if (pthread_mutex_lock(&apimessager->userMutex))
return; return;
@ -827,6 +830,20 @@ static void checkAPIMessages(network::GetAPIMessager *apimessager)
slog.error("Tried to give control to nonexistent user %s", act.data.user); slog.error("Tried to give control to nonexistent user %s", act.data.user);
} }
break; 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) { if (set) {
@ -923,6 +940,9 @@ void VNCServerST::writeUpdate()
assert(blockCounter == 0); assert(blockCounter == 0);
assert(desktopStarted); assert(desktopStarted);
struct timeval start;
gettimeofday(&start, NULL);
if (DLPRegion.enabled) { if (DLPRegion.enabled) {
comparer->enable_copyrect(false); comparer->enable_copyrect(false);
blackOut(); blackOut();
@ -949,6 +969,9 @@ void VNCServerST::writeUpdate()
else else
comparer->disable(); comparer->disable();
struct timeval beforeAnalysis;
gettimeofday(&beforeAnalysis, NULL);
// Skip scroll detection if the client is slow, and didn't get the previous one yet // 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(), if (comparer->compare(clients.size() == 1 && (*clients.begin())->has_copypassed(),
cursorReg)) cursorReg))
@ -956,6 +979,8 @@ void VNCServerST::writeUpdate()
comparer->clear(); comparer->clear();
const unsigned analysisMs = msSince(&beforeAnalysis);
encCache.clear(); encCache.clear();
encCache.enabled = clients.size() > 1; encCache.enabled = clients.size() > 1;
@ -981,11 +1006,22 @@ void VNCServerST::writeUpdate()
} }
} }
unsigned shottime = 0;
if (apimessager) { if (apimessager) {
struct timeval shotstart;
gettimeofday(&shotstart, NULL);
apimessager->mainUpdateScreen(pb); 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) { for (ci = clients.begin(); ci != clients.end(); ci = ci_next) {
ci_next = ci; ci_next++; ci_next = ci; ci_next++;
@ -993,10 +1029,68 @@ void VNCServerST::writeUpdate()
if (permcheck) if (permcheck)
(*ci)->recheckPerms(); (*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_copied(ui.copied, ui.copy_delta);
(*ci)->add_copypassed(ui.copypassed); (*ci)->add_copypassed(ui.copypassed);
(*ci)->add_changed(ui.changed); (*ci)->add_changed(ui.changed);
(*ci)->writeFramebufferUpdateOrClose(); (*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);
}
}
} }
} }

View File

@ -267,6 +267,9 @@ namespace rfb {
network::GetAPIMessager *apimessager; network::GetAPIMessager *apimessager;
rdr::U8 trackingFrameStats;
char trackingClient[128];
struct { struct {
bool enabled; bool enabled;
int x1, y1, x2, y2; int x1, y1, x2, y2;

View File

@ -86,6 +86,7 @@ namespace rfb {
const int pseudoEncodingVideoOutTimeLevel100 = -1887; const int pseudoEncodingVideoOutTimeLevel100 = -1887;
// VMware-specific // VMware-specific
const int pseudoEncodingVMwareCursor = 0x574d5664;
const int pseudoEncodingVMwareCursorPosition = 0x574d5666; const int pseudoEncodingVMwareCursorPosition = 0x574d5666;
// UltraVNC-specific // UltraVNC-specific

View File

@ -30,6 +30,7 @@ namespace rfb {
// kasm // kasm
const int msgTypeStats = 178; const int msgTypeStats = 178;
const int msgTypeRequestFrameStats = 179;
const int msgTypeServerFence = 248; const int msgTypeServerFence = 248;
@ -47,6 +48,7 @@ namespace rfb {
// kasm // kasm
const int msgTypeRequestStats = 178; const int msgTypeRequestStats = 178;
const int msgTypeFrameStats = 179;
const int msgTypeClientFence = 248; const int msgTypeClientFence = 248;

@ -1 +1 @@
Subproject commit 67466077c07377178599315b0cba01440ce6fb53 Subproject commit ba40cacce068fa35fc706c41605db14c04348170