/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/debug.h" #include "common/option.h" #include "common/crash.h" #include "common/KVMFR.h" #include "common/locking.h" #include "common/stringutils.h" #include #include #include #include #include #include #include #include #define min(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; }) #define max(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a > _b ? _a : _b; }) struct state { int shmSize; int shmFD; struct KVMFRHeader * shm; bool running; }; struct state state; static struct Option options[] = { { .module = "app", .name = "configFile", .description = "A file to read additional configuration from", .shortopt = 'C', .type = OPTION_TYPE_STRING, .value.x_string = NULL }, { .module = "app", .name = "shmFile", .description = "The path to the shared memory file", .shortopt = 'f', .type = OPTION_TYPE_STRING, .value.x_string = "/dev/shm/looking-glass" }, { .module = "app", .name = "shmSize", .description = "Specify the size in MB of the shared memory file (0 = detect)", .shortopt = 'L', .type = OPTION_TYPE_INT, .value.x_int = 0 }, {0} }; static bool config_load(int argc, char * argv[]) { // load any global options first struct stat st; if (stat("/etc/looking-glass-client.ini", &st) >= 0) { DEBUG_INFO("Loading config from: /etc/looking-glass-client.ini"); if (!option_load("/etc/looking-glass-client.ini")) return false; } // load user's local options struct passwd * pw = getpwuid(getuid()); char * localFile; alloc_sprintf(&localFile, "%s/.looking-glass-client.ini", pw->pw_dir); if (stat(localFile, &st) >= 0) { DEBUG_INFO("Loading config from: %s", localFile); if (!option_load(localFile)) { free(localFile); return false; } } free(localFile); if (!option_parse(argc, argv)) return false; // if a file was specified to also load, do it const char * configFile = option_get_string("app", "configFile"); if (configFile) { DEBUG_INFO("Loading config from: %s", configFile); if (!option_load(configFile)) return false; } if (!option_validate()) return false; return true; } static bool map_memory() { const char * shmFile = option_get_string("app", "shmFile"); int shmSize = option_get_int ("app", "shmSize"); struct stat st; if (stat(shmFile, &st) < 0) { DEBUG_ERROR("Failed to stat the shared memory file: %s", shmFile); return false; } state.shmSize = shmSize ? shmSize : st.st_size; state.shmFD = open(shmFile, O_RDWR, (mode_t)0600); if (state.shmFD < 0) { DEBUG_ERROR("Failed to open the shared memory file: %s", shmFile); return false; } state.shm = (struct KVMFRHeader *)mmap(0, shmSize, PROT_READ | PROT_WRITE, MAP_SHARED, state.shmFD, 0); if (state.shm == MAP_FAILED) { DEBUG_ERROR("Failed to map the shared memory file: %s", shmFile); state.shm = NULL; return false; } DEBUG_INFO("Shared memory mapped @ 0x%p", state.shm); return true; } static void unmap_memory() { if (state.shmFD > -1) { if (state.shm) { munmap(state.shm, state.shmSize); state.shm = NULL; } close(state.shmFD); state.shmFD = -1; } } static inline uint64_t nanotime() { struct timespec time; clock_gettime(CLOCK_MONOTONIC_RAW, &time); return ((uint64_t)time.tv_sec * 1e9) + time.tv_nsec; } static int run() { DEBUG_INFO("Waiting for host to signal it's ready..."); __sync_or_and_fetch(&state.shm->flags, KVMFR_HEADER_FLAG_RESTART); while(state.running && (state.shm->flags & KVMFR_HEADER_FLAG_RESTART)) usleep(1e6); if (!state.running) return -1; DEBUG_INFO("Host ready, starting session"); // check the header's magic and version are valid if (memcmp(state.shm->magic, KVMFR_HEADER_MAGIC, sizeof(KVMFR_HEADER_MAGIC)) != 0) { DEBUG_ERROR("Invalid header magic, is the host running?"); return -1; } DEBUG_INFO("KVMFR Version: %u", state.shm->version); if (state.shm->version != KVMFR_HEADER_VERSION) { DEBUG_ERROR("KVMFR version missmatch, expected %u but got %u", KVMFR_HEADER_VERSION, state.shm->version); DEBUG_ERROR("This is not a bug, ensure you have the right version of looking-glass-host.exe on the guest"); return -1; } struct perf { uint64_t min, max, ttl; unsigned int count; }; unsigned int frameCount = 0; uint64_t lastFrameTime = 0; struct perf p1 = {}; struct perf p5 = {}; struct perf p10 = {}; struct perf p30 = {}; // start accepting frames while(state.running) { // we don't sleep in this loop as we are profiling the host application while(!(state.shm->frame.flags & KVMFR_FRAME_FLAG_UPDATE)) { if (!state.running) break; } uint64_t frameTime = nanotime(); // tell the host that it can continue __sync_and_and_fetch(&state.shm->frame.flags, ~KVMFR_FRAME_FLAG_UPDATE); uint64_t diff = frameTime - lastFrameTime; if (frameCount++ == 0) { lastFrameTime = frameTime; p1.min = p5.min = p10.min = p30.min = diff; continue; } ++p1 .count; ++p5 .count; ++p10.count; ++p30.count; #define UPDATE(p, interval) \ if (p.ttl + diff >= (1e9 * interval)) \ { \ fprintf(stdout, "%02d, min:%9lu ns (%5.2f ms) max:%9lu ns (%5.2f ms) avg:%9lu ns (%5.2f ms)\n", \ interval, \ p.min , ((float)p.min / 1e6f), \ p.max , ((float)p.max / 1e6f), \ p.ttl / p.count, (((float)p.ttl / p.count) / 1e6f)\ ); \ p.min = p.max = p.ttl = diff; p.count = 1; \ } \ else \ { \ p.min = min(p.min, diff); \ p.max = max(p.max, diff); \ p.ttl += diff; \ } UPDATE(p1 , 1 ); UPDATE(p5 , 5 ); UPDATE(p10, 10); UPDATE(p30, 30); lastFrameTime = frameTime; } return 0; } int main(int argc, char * argv[]) { DEBUG_INFO("Looking Glass (" BUILD_VERSION ") - Client Profiler"); if (!installCrashHandler("/proc/self/exe")) DEBUG_WARN("Failed to install the crash handler"); option_register(options); if (!config_load(argc, argv)) { option_free(); return -1; } // init the global state vars state.shmFD = -1; state.shm = NULL; state.running = true; int ret = -1; if (map_memory()) ret = run(); unmap_memory(); option_free(); return ret; }