diff --git a/VERSION b/VERSION index 05fb6cbb..aa3b9022 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -B1-21-g30e3a43311+1 \ No newline at end of file +B1-27-g2d9f578719+1 \ No newline at end of file diff --git a/client/src/main.c b/client/src/main.c index 9b08f8c1..155d1cc4 100644 --- a/client/src/main.c +++ b/client/src/main.c @@ -401,6 +401,7 @@ static int frameThread(void * unused) updatePositionInfo(); } FrameBuffer frame = (FrameBuffer)((uint8_t *)state.shm + header.dataPos); + if (!state.lgr->on_frame_event(state.lgrData, lgrFormat, frame)) { DEBUG_ERROR("renderer on frame event returned failure"); diff --git a/profile/README.md b/profile/README.md new file mode 100644 index 00000000..01f2a79c --- /dev/null +++ b/profile/README.md @@ -0,0 +1,5 @@ +##This directory contains programs for LG performance profiling. + +###Directories: + +* `client` - dummy client that profiles the host application's performance. diff --git a/profile/client/.gitignore b/profile/client/.gitignore new file mode 100644 index 00000000..0824b6c0 --- /dev/null +++ b/profile/client/.gitignore @@ -0,0 +1,3 @@ +bin/ +build/ +*.swp diff --git a/profile/client/CMakeLists.txt b/profile/client/CMakeLists.txt new file mode 100644 index 00000000..ac57c673 --- /dev/null +++ b/profile/client/CMakeLists.txt @@ -0,0 +1,64 @@ +cmake_minimum_required(VERSION 3.0) +project(profiler-client C) + +set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/") + +include(GNUInstallDirs) +include(CheckCCompilerFlag) +include(FeatureSummary) + +option(OPTIMIZE_FOR_NATIVE "Build with -march=native" ON) +if(OPTIMIZE_FOR_NATIVE) + CHECK_C_COMPILER_FLAG("-march=native" COMPILER_SUPPORTS_MARCH_NATIVE) + if(COMPILER_SUPPORTS_MARCH_NATIVE) + add_compile_options("-march=native") + endif() +endif() + +add_compile_options( + "-Wall" + "-Werror" + "-Wfatal-errors" + "-ffast-math" + "-fdata-sections" + "-ffunction-sections" + "$<$:-O0;-g3;-ggdb>" +) + +set(EXE_FLAGS "-Wl,--gc-sections") +set(CMAKE_C_STANDARD 11) + +execute_process( + COMMAND cat ../../VERSION + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + OUTPUT_VARIABLE BUILD_VERSION + OUTPUT_STRIP_TRAILING_WHITESPACE +) + +add_definitions(-D BUILD_VERSION='"${BUILD_VERSION}"') +get_filename_component(PROJECT_TOP "${PROJECT_SOURCE_DIR}/../.." ABSOLUTE) + +include_directories( + ${PROJECT_SOURCE_DIR}/include + ${CMAKE_BINARY_DIR}/include +) + +link_libraries( + rt + m +) + +set(SOURCES + src/main.c +) + +add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common") + +add_executable(profiler-client ${SOURCES}) +target_compile_options(profiler-client PUBLIC ${PKGCONFIG_CFLAGS_OTHER}) +target_link_libraries(profiler-client + ${EXE_FLAGS} + lg_common +) + +feature_summary(WHAT ENABLED_FEATURES DISABLED_FEATURES) diff --git a/profile/client/src/main.c b/profile/client/src/main.c new file mode 100644 index 00000000..ec54ed9b --- /dev/null +++ b/profile/client/src/main.c @@ -0,0 +1,303 @@ +/* +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; +} \ No newline at end of file