|
|
Line 276: |
Line 276: |
|
| |
|
| == Proof of concept (patch) == | | == Proof of concept (patch) == |
| {{Note|The following patch [[Howto:Create new subsystems|implements a simple SGSubsystem]] by inheriting from the base class and wrapping access to <code>/proc/pid/smaps</code> (on Linux). | | {{Note|http://codepad.org/27VcoMSX/raw.txt}} |
| This should probably be updated to use the [http://api-docs.freeflightsim.org/simgear/classSGTimeStamp.html SGTimeStamp] and [[Howto:Use Property Tree Objects|SGPropertyObject]] APIs respectively. For the time being, the patch should be considered out of date - it's been updated to also include VRAM utilization stats, and will probably evolve to also provide other metrics over time (e.g. Nasal GC and active timers/listeners).}}
| |
|
| |
|
| Here's a first stab at a simple subsystem to monitor FlightGear memory usage on Linux at 5 second intervals, consider it a "proof of concept" prototype now, as this would need to be cleaned up and implemented for Mac/Windows respectively - on Linux it simply works such that it merely fopen()s /proc/pid/smaps and copies two metrics to the property tree: | | Here's a first stab at a simple subsystem to monitor FlightGear memory usage on Linux at 5 second intervals, consider it a "proof of concept" prototype now, as this would need to be cleaned up and implemented for Mac/Windows respectively - on Linux it simply works such that it merely fopen()s /proc/pid/smaps and copies two metrics to the property tree[http://codepad.org/27VcoMSX/raw.txt]: |
| <syntaxhighlight lang="diff">
| | <syntaxhighlight lang="diff"></syntaxhighlight> |
| diff --git a/CMakeLists.txt b/CMakeLists.txt
| |
| index 4808f9d..5b45fa6 100644
| |
| --- a/CMakeLists.txt
| |
| +++ b/CMakeLists.txt
| |
| @@ -194,6 +194,7 @@ option(ENABLE_TESTS "Set to ON to build test applications (default)" ON)
| |
| option(ENABLE_FGCOM "Set to ON to build the FGCom application (default)" ON)
| |
| option(ENABLE_FLITE "Set to ON to build the Flite text-to-speech module" ON)
| |
| option(ENABLE_QT "Set to ON to build the internal Qt launcher" ON)
| |
| +#option(ENABLE_SIGAR "Set to ON to build with SIGAR support (experimental)" OFF)
| |
|
| |
| if(LOGGING)
| |
| # nothing
| |
| @@ -328,6 +329,13 @@ if(ENABLE_PROFILE)
| |
| message(STATUS "Built-in profiler using gperftools available")
| |
| endif()
| |
|
| |
| +if(ENABLE_SIGAR)
| |
| + # find_package(SIGAR REQUIRED)
| |
| + set(FG_HAVE_SIGAR 1)
| |
| + message(STATUS "Built-in process monitoring using SIGAR available")
| |
| +endif(ENABLE_SIGAR)
| |
| +
| |
| +
| |
| if(ENABLE_RTI)
| |
| message(STATUS "RTI: ENABLED")
| |
| find_package(RTI)
| |
| diff --git a/CMakeModules/FindSIGAR.cmake b/CMakeModules/FindSIGAR.cmake
| |
| new file mode 100644
| |
| index 0000000..4c890a7
| |
| --- /dev/null
| |
| +++ b/CMakeModules/FindSIGAR.cmake
| |
| @@ -0,0 +1,52 @@
| |
| +# - Find SIGAR: https://support.hyperic.com/display/SIGAR/Home
| |
| +# Find the native SIGAR includes and library
| |
| +#
| |
| +# SIGAR_INCLUDE_DIR - where to find SIGAR.h, etc.
| |
| +# SIGAR_LIBRARIES - List of libraries when using SIGAR.
| |
| +# SIGAR_FOUND - True if SIGAR found.
| |
| +#
| |
| +# Redistribution and use is allowed according to the terms of the BSD license.
| |
| +# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
| |
| +
| |
| +
| |
| +if (SIGAR_INCLUDE_DIR)
| |
| + # Already in cache, be silent
| |
| + set(SIGAR_FIND_QUIETLY TRUE)
| |
| +endif ()
| |
| +
| |
| +find_path(SIGAR_INCLUDE_DIR sigar.h
| |
| + /opt/local/include
| |
| + /usr/local/include
| |
| + /usr/include
| |
| +)
| |
| +
| |
| +# SIGAR support a lot more platforms than listed here.
| |
| +# cf. sigar.hyperic.com
| |
| +set(SIGAR_NAMES sigar-x86-linux sigar-x86_64-linux sigar-amd64-linux sigar-universal-macosx sigar)
| |
| +find_library(SIGAR_LIBRARY
| |
| + NAMES ${SIGAR_NAMES}
| |
| + PATHS /usr/lib /usr/local/lib /opt/local/lib
| |
| +)
| |
| +
| |
| +if (SIGAR_INCLUDE_DIR AND SIGAR_LIBRARY)
| |
| + set(SIGAR_FOUND TRUE)
| |
| + set(SIGAR_LIBRARIES ${SIGAR_LIBRARY} ${CMAKE_DL_LIBS})
| |
| +else ()
| |
| + set(SIGAR_FOUND FALSE)
| |
| + set(SIGAR_LIBRARIES)
| |
| +endif ()
| |
| +
| |
| +if (SIGAR_FOUND)
| |
| + message(STATUS "Found SIGAR: ${SIGAR_LIBRARIES}")
| |
| +else ()
| |
| + message(STATUS "Not Found SIGAR: ${SIGAR_LIBRARY}")
| |
| + if (SIGAR_FIND_REQUIRED)
| |
| + message(STATUS "Looked for SIGAR libraries named ${SIGAR_NAMES}.")
| |
| + message(FATAL_ERROR "Could NOT find SIGAR library")
| |
| + endif ()
| |
| +endif ()
| |
| +
| |
| +mark_as_advanced(
| |
| + SIGAR_LIBRARY
| |
| + SIGAR_INCLUDE_DIR
| |
| +)
| |
| diff --git a/src/Main/CMakeLists.txt b/src/Main/CMakeLists.txt
| |
| index 4b6926e..31d3a8f 100644
| |
| --- a/src/Main/CMakeLists.txt
| |
| +++ b/src/Main/CMakeLists.txt
| |
| @@ -17,12 +17,17 @@ set(SOURCES
| |
| main.cxx
| |
| options.cxx
| |
| util.cxx
| |
| + ram_usage.cxx
| |
| positioninit.cxx
| |
| subsystemFactory.cxx
| |
| screensaver_control.cxx
| |
| ${RESOURCE_FILE}
| |
| )
| |
|
| |
| +IF(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
| |
| + list(APPEND SOURCES ram_usage_linux.cxx)
| |
| +ENDIF(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
| |
| +
| |
| set(HEADERS
| |
| fg_commands.hxx
| |
| fg_init.hxx
| |
| @@ -35,12 +40,19 @@ set(HEADERS
| |
| main.hxx
| |
| options.hxx
| |
| util.hxx
| |
| + ram_usage.hxx
| |
| positioninit.hxx
| |
| subsystemFactory.hxx
| |
| AircraftDirVisitorBase.hxx
| |
| screensaver_control.hxx
| |
| )
| |
|
| |
| +IF(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
| |
| + list(APPEND HEADERS ram_usage_linux.hxx)
| |
| +ENDIF(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
| |
| +
| |
| +
| |
| +
| |
| get_property(FG_SOURCES GLOBAL PROPERTY FG_SOURCES)
| |
| get_property(FG_HEADERS GLOBAL PROPERTY FG_HEADERS)
| |
|
| |
| diff --git a/src/Main/fg_init.cxx b/src/Main/fg_init.cxx
| |
| index 30ffa0e..ae936ab 100644
| |
| --- a/src/Main/fg_init.cxx
| |
| +++ b/src/Main/fg_init.cxx
| |
| @@ -141,6 +141,7 @@
| |
| #include "globals.hxx"
| |
| #include "logger.hxx"
| |
| #include "main.hxx"
| |
| +#include "ram_usage.hxx"
| |
| #include "positioninit.hxx"
| |
| #include "util.hxx"
| |
| #include "AircraftDirVisitorBase.hxx"
| |
| @@ -715,6 +716,10 @@ void fgCreateSubsystems(bool duringReset) {
| |
| ////////////////////////////////////////////////////////////////////
| |
| globals->add_subsystem("properties", new FGProperties);
| |
|
| |
| + ////////////////////////////////////////////////////////////////////
| |
| + // Add the RAM/VRAM utilization statistics system (SIGAR)
| |
| + ////////////////////////////////////////////////////////////////////
| |
| + globals->add_subsystem("process-stats", new MemoryUsageStats, SGSubsystemMgr::INIT, 1.00);
| |
|
| |
| ////////////////////////////////////////////////////////////////////
| |
| // Add the performance monitoring system.
| |
| diff --git a/src/Main/globals.cxx b/src/Main/globals.cxx
| |
| index d41e00d..c8805b6 100644
| |
| --- a/src/Main/globals.cxx
| |
| +++ b/src/Main/globals.cxx
| |
| @@ -63,6 +63,7 @@
| |
|
| |
| #include "fg_props.hxx"
| |
| #include "fg_io.hxx"
| |
| +#include "ram_usage.hxx"
| |
|
| |
| class AircraftResourceProvider : public simgear::ResourceProvider
| |
| {
| |
| @@ -503,7 +504,12 @@ FGGlobals::add_subsystem (const char * name,
| |
| SGSubsystemMgr::GroupType type,
| |
| double min_time_sec)
| |
| {
| |
| + MemoryUsageStats* stats = (MemoryUsageStats*) subsystem_mgr->get_subsystem("process-stats");
| |
| subsystem_mgr->add(name, subsystem, type, min_time_sec);
| |
| + if(stats) {
| |
| + SG_LOG(SG_GENERAL, SG_ALERT, "Process snapshot after adding subsystem:"<< name);
| |
| + stats->snapshot();
| |
| + }
| |
| }
| |
|
| |
| SGSoundMgr *
| |
| diff --git a/src/Main/ram_usage.cxx b/src/Main/ram_usage.cxx
| |
| new file mode 100644
| |
| index 0000000..a9da94d
| |
| --- /dev/null
| |
| +++ b/src/Main/ram_usage.cxx
| |
| @@ -0,0 +1,17 @@
| |
| +#include "ram_usage_linux.hxx"
| |
| +
| |
| +MemoryUsageStats::MemoryUsageStats() {
| |
| + _ram = new LinuxMemoryInterface(); //FIXME: should be implemented for Win/Mac & Linux (use SIGAR)
| |
| +}
| |
| +
| |
| +MemoryUsageStats::~MemoryUsageStats() {
| |
| + delete _ram;
| |
| +}
| |
| +
| |
| +void
| |
| +MemoryUsageStats::update(double dt) {
| |
| +update();
| |
| +}
| |
| +
| |
| +
| |
| +
| |
| diff --git a/src/Main/ram_usage.hxx b/src/Main/ram_usage.hxx
| |
| new file mode 100644
| |
| index 0000000..58abdf5
| |
| --- /dev/null
| |
| +++ b/src/Main/ram_usage.hxx
| |
| @@ -0,0 +1,186 @@
| |
| +#ifndef __RAM_USAGE
| |
| +#define __RAM_USAGE
| |
| +
| |
| +#include<stdio.h>
| |
| +#include<stdlib.h>
| |
| +#include<X11/X.h>
| |
| +#include<X11/Xlib.h>
| |
| +#include<GL/gl.h>
| |
| +#include<GL/glx.h>
| |
| +#include<GL/glu.h>
| |
| +
| |
| +
| |
| +// http://wiki.flightgear.org/Howto:Use_Property_Tree_Objects
| |
| +#include <simgear/props/propertyObject.hxx>
| |
| +
| |
| +#include <simgear/timing/timestamp.hxx>
| |
| +
| |
| +// http://wiki.flightgear.org/Howto:Create_new_subsystems
| |
| +#include <simgear/structure/subsystem_mgr.hxx>
| |
| +
| |
| +#include <Main/globals.hxx>
| |
| +#include <Main/fg_props.hxx>
| |
| +
| |
| +#include <string>
| |
| +#include <map>
| |
| +
| |
| +using std::map;
| |
| +
| |
| + // this is the base class, with the only method being a virtual method
| |
| + // which needs to be implemented by any child classes
| |
| + // a pointer of this class will be added to the LinuxMemoryInterface class
| |
| + class GPUInfo {
| |
| + private:
| |
| + protected:
| |
| + GLint total_mem_kb;
| |
| + GLint cur_avail_mem_kb;
| |
| + simgear::PropertyObject<int> total_vram_kb,available_vram_kb, used_vram_kb;
| |
| + public:
| |
| + GPUInfo(std::string &glvendor): total_mem_kb(-1),
| |
| + cur_avail_mem_kb(-1),
| |
| + total_vram_kb("/stats/vram/total-size-kb"),
| |
| + available_vram_kb ("/stats/vram/available-kb"),
| |
| + used_vram_kb ("/stats/vram/used-kb") {
| |
| + SG_LOG(SG_GENERAL, SG_ALERT, "VRAM Tracking: Supported GPU found:"<< glvendor);
| |
| + }
| |
| + virtual ~GPUInfo() {}
| |
| + virtual void updateVRAMStats() {};
| |
| + }; //GPUInfo
| |
| +
| |
| + // Actually implement the GPUInfo class for all 3 GPU vendors:
| |
| +
| |
| +// http://www.geeks3d.com/20100531/programming-tips-how-to-know-the-graphics-memory-size-and-usage-in-opengl/
| |
| +#define GL_GPU_MEM_INFO_TOTAL_AVAILABLE_MEM_NVX 0x9048
| |
| +#define GL_GPU_MEM_INFO_CURRENT_AVAILABLE_MEM_NVX 0x9049
| |
| +
| |
| + struct NVIDIA_GPU: public GPUInfo {
| |
| + NVIDIA_GPU(std::string glvendor) : GPUInfo(glvendor) {
| |
| + // determine total memory and store it (this wont change at runtime, so only do it once)
| |
| + glGetIntegerv(GL_GPU_MEM_INFO_TOTAL_AVAILABLE_MEM_NVX, &total_mem_kb);
| |
| + SG_LOG(SG_GENERAL, SG_DEBUG, "NVIDIA GPU with total memory: " << total_mem_kb << " kbytes");
| |
| + total_vram_kb = total_mem_kb;
| |
| + }
| |
| + virtual void updateVRAMStats() {
| |
| +
| |
| + glGetIntegerv(GL_GPU_MEM_INFO_CURRENT_AVAILABLE_MEM_NVX, &cur_avail_mem_kb);
| |
| + SG_LOG(SG_GENERAL, SG_DEBUG,"NVIDIA VRAM tracking function says:"<<total_mem_kb<<" (total) available:"<<cur_avail_mem_kb);
| |
| + // update property objects
| |
| + available_vram_kb = cur_avail_mem_kb;
| |
| + used_vram_kb = total_vram_kb - available_vram_kb;
| |
| + }
| |
| + }; //NVIDIA_GPU
| |
| +
| |
| + struct ATI_GPU: public GPUInfo {
| |
| + ATI_GPU(std::string glvendor) : GPUInfo(glvendor) {
| |
| + }
| |
| + // https://github.com/OpenGLInsights/OpenGLInsightsCode/blob/master/Chapter%2038%20Monitoring%20Graphics%20Memory%20Usage/Ch38AMD/Ch38AMD/Ch38AMDDlg.cpp#L280
| |
| + virtual void updateVRAMStats() {
| |
| +// https://github.com/OpenGLInsights/OpenGLInsightsCode/blob/master/Chapter%2038%20Monitoring%20Graphics%20Memory%20Usage/Ch38AMD/Ch38AMD/Ch38AMDDlg.cpp
| |
| +#ifndef GL_VBO_FREE_MEMORY_ATI
| |
| + #define GL_VBO_FREE_MEMORY_ATI 0x87FB
| |
| + #define GL_TEXTURE_FREE_MEMORY_ATI 0x87FC
| |
| + #define GL_RENDERBUFFER_FREE_MEMORY_ATI 0x87FD
| |
| +#endif
| |
| +
| |
| + SG_LOG(SG_GENERAL, SG_ALERT,"Sorry:ATI VRAM tracking function not yet implemented (nvidia only)!");
| |
| + used_vram_kb = total_vram_kb = available_vram_kb = -1;
| |
| + }
| |
| + };//ATI_GPU
| |
| +
| |
| + struct INTEL_GPU : public GPUInfo {
| |
| + INTEL_GPU(std::string glvendor) : GPUInfo(glvendor) {
| |
| +}
| |
| + virtual void updateVRAMStat() {
| |
| + SG_LOG(SG_GENERAL, SG_ALERT,"Sorry:Intel VRAM tracking function not yet implemented (nvidia only)!");
| |
| + used_vram_kb = total_vram_kb = available_vram_kb = -1;
| |
| + }
| |
| +
| |
| + }; // INTEL_GPU
| |
| +
| |
| + struct UNKNOWN_GPU: public GPUInfo {
| |
| + UNKNOWN_GPU(std::string glvendor) : GPUInfo(glvendor) {
| |
| + }
| |
| + virtual void updateVRAMStat() {
| |
| + SG_LOG(SG_GENERAL, SG_DEBUG,"Sorry:VRAM tracking function not yet implemented for your GPU (nvidia only)!");
| |
| + used_vram_kb = total_vram_kb = available_vram_kb = -1;
| |
| + }
| |
| +
| |
| + }; // UNKNOWN_GPU
| |
| +
| |
| +// Linux: /proc/pid/smaps
| |
| +// Windows: http://msdn.microsoft.com/en-us/library/windows/desktop/ms682050(v=vs.85).aspx
| |
| +
| |
| +class MemoryInterface {
| |
| +public:
| |
| + virtual ~MemoryInterface() {
| |
| + delete _gpu;
| |
| + };
| |
| + MemoryInterface() : _total_size("/stats/ram/memory-used-kb"), _swap_size("/stats/ram/swap-used-kb") {
| |
| + // see if we have a GPU that supports vram tracking:
| |
| +
| |
| + std::string fallback = "NVIDIA"; //default value
| |
| +
| |
| + // get the actual GL vendor string from the property tree, using the fallback
| |
| + std::string glvendor = fgGetString("/sim/rendering/gl-vendor",fallback.c_str() );
| |
| +
| |
| + // make it upper case: http://stackoverflow.com/questions/735204/convert-a-string-in-c-to-upper-case
| |
| + std::transform(glvendor.begin(), glvendor.end(), glvendor.begin(), ::toupper);
| |
| +
| |
| + // look for the "NVIDIA" substring: http://www.cplusplus.com/reference/string/string/find/
| |
| + std::size_t found = glvendor.find("NVIDIA");
| |
| + if (found!=std::string::npos) {
| |
| + _gpu = new NVIDIA_GPU(glvendor);
| |
| + }
| |
| + else if (glvendor.find("INTEL") != std::string::npos) {
| |
| + _gpu = new INTEL_GPU(glvendor);
| |
| + }
| |
| + else if (glvendor.find("ATI") != std::string::npos) {
| |
| + _gpu = new ATI_GPU(glvendor);
| |
| + }
| |
| + else {
| |
| + _gpu = new UNKNOWN_GPU(glvendor);
| |
| + }
| |
| +}
| |
| + typedef map<const char*, double> RamMap;
| |
| + virtual void update() = 0;
| |
| +
| |
| + void updateVRAMStats() const {
| |
| +if (_gpu)
| |
| +_gpu->updateVRAMStats();
| |
| +}
| |
| +
| |
| +protected:
| |
| + RamMap _size;
| |
| + std::string _path;
| |
| + std::stringstream _pid;
| |
| + GPUInfo* _gpu; // wraps VRAM utilization gathering
| |
| +
| |
| +public:
| |
| + simgear::PropertyObject<int> _total_size; // memory used by process
| |
| + simgear::PropertyObject<int> _swap_size; // swap used by process
| |
| +};
| |
| +
| |
| +class MemoryUsageStats : public SGSubsystem
| |
| +{
| |
| +public:
| |
| + MemoryUsageStats();
| |
| + ~MemoryUsageStats();
| |
| + virtual void update(double);
| |
| + void snapshot() {
| |
| + update();
| |
| + simgear::PropertyObject<int> used_vram_kb ("/stats/vram/used-kb");
| |
| + SG_LOG(SG_GENERAL, SG_ALERT, "Process size: " <<_ram->_total_size << "kb" << std::endl);
| |
| + // SG_LOG(SG_GENERAL, SG_ALERT, "VRAM utilization: " << used_vram_kb << " kb" << std::endl);
| |
| + }
| |
| +
| |
| +protected:
| |
| +private:
| |
| + MemoryInterface* _ram;
| |
| + void update() {
| |
| + _ram->update(); // will update ram/swap utilization by reading /proc/pid/smaps
| |
| + _ram->updateVRAMStats(); // will update vram utilization by using NVIDIA/AMD APIs
| |
| + }
| |
| +};
| |
| +
| |
| +#endif
| |
| +
| |
| diff --git a/src/Main/ram_usage_linux.cxx b/src/Main/ram_usage_linux.cxx | |
| new file mode 100644
| |
| index 0000000..3a6498a
| |
| --- /dev/null
| |
| +++ b/src/Main/ram_usage_linux.cxx
| |
| @@ -0,0 +1,49 @@
| |
| +// https://gist.github.com/896026/c346c7c8e4a9ab18577b4e6abfca37e358de83c1
| |
| +
| |
| +#include "ram_usage_linux.hxx"
| |
| +
| |
| +#include <cstring>
| |
| +#include <string>
| |
| +
| |
| +#include "Main/globals.hxx"
| |
| +
| |
| +using std::string;
| |
| +
| |
| +LinuxMemoryInterface::LinuxMemoryInterface() {
| |
| + _pid << getpid();
| |
| + _path = "/proc/"+ _pid.str() +"/smaps";
| |
| +}
| |
| +
| |
| +void
| |
| +LinuxMemoryInterface::OpenProcFile() {
| |
| + file = fopen(_path.c_str(),"r" );
| |
| + if (!file) {
| |
| + throw("MemoryTracker:Cannot open /proc/pid/smaps");
| |
| + }
| |
| + SG_LOG(SG_GENERAL, SG_DEBUG, "Opened:"<< _path.c_str() );
| |
| +}
| |
| +
| |
| +LinuxMemoryInterface::~LinuxMemoryInterface() {
| |
| + //if (file) fclose(file);
| |
| +}
| |
| +
| |
| +void LinuxMemoryInterface::update() {
| |
| + OpenProcFile();
| |
| + if (!file) throw("MemoryTracker: ProcFile not open");
| |
| +
| |
| + // reset our counters
| |
| + _total_size = 0;
| |
| + _swap_size = 0;
| |
| +
| |
| + while (fgets(line, sizeof line, file))
| |
| + {
| |
| + char substr[32];
| |
| + int n;
| |
| + if (sscanf(line, "%31[^:]: %d", substr, &n) == 2) {
| |
| + if (strcmp(substr, "Size") == 0) { _total_size += n; }
| |
| + else if (strcmp(substr, "Swap") == 0) { _swap_size += n; }
| |
| + }
| |
| + }
| |
| + fclose(file);
| |
| +}
| |
| +
| |
| diff --git a/src/Main/ram_usage_linux.hxx b/src/Main/ram_usage_linux.hxx
| |
| new file mode 100644
| |
| index 0000000..abce41b
| |
| --- /dev/null
| |
| +++ b/src/Main/ram_usage_linux.hxx
| |
| @@ -0,0 +1,23 @@
| |
| +#ifndef __RAM_USAGE_LINUX
| |
| +#define __RAM_USAGE_LINUX
| |
| +
| |
| + #include <sys/types.h>
| |
| + #include <unistd.h>
| |
| + #include <stdio.h>
| |
| +
| |
| + #include "ram_usage.hxx"
| |
| +
| |
| +class LinuxMemoryInterface : public MemoryInterface {
| |
| +public:
| |
| + LinuxMemoryInterface();
| |
| +~LinuxMemoryInterface();
| |
| + virtual void update();
| |
| +private:
| |
| + char line[1024];
| |
| + void OpenProcFile();
| |
| + const char* filename;
| |
| + FILE* file;
| |
| +};
| |
| +
| |
| +
| |
| +#endif
| |
| | |
| </syntaxhighlight> | |
|
| |
|
| == History == | | == History == |