commit a2d5d03629642d7240bbd59e77b8295bad56ca60 Author: Bjango Date: Sun Oct 23 14:47:30 2016 +1100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..47b3f12 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +._* +istatserver +config.h +config.log +config.status +config.guess +config.sub +configure +config.h.in +Makefile +Makefile.in +autom4te.cache +aclocal.m4 +depcomp +missing +install-sh +stamp-h1 +stamp-h2 +.deps +.libs +*.o +*.lo +*.exe +*.so +*.la +*.a +.DS_Store +.DS_Store? +.Spotlight-V100 +.Trashes diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..98fd8ce --- /dev/null +++ b/COPYING @@ -0,0 +1,27 @@ +/* + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..da1fcaf --- /dev/null +++ b/Makefile.am @@ -0,0 +1,4 @@ +SUBDIRS = . src conf resource + +EXTRA_DIST = README COPYING autogen + diff --git a/README b/README new file mode 100644 index 0000000..4032ac7 --- /dev/null +++ b/README @@ -0,0 +1,32 @@ +About istatserver + +istatserer is a system monitoring daemon that is used in conjunction with iStat for iOS (https://bjango.com/ios/istat/) and iStat for macOS (https://bjango.com/mac/istat/) to remotely monitor computers. + + +Supported OSs +- Linux +- FreeBSD, DragonFly BSD, OpenBSD, NetBSD and other BSD based OSs +- AIX +- Solaris +- HP-UX (Still in development and not tested) + + +Requirements +- C and C++ compilers such as gcc and g++. +- Auto tools (autoconf and automake). +- OpenSSL/libssl + development libraries. +- Sqlite3 + development libraries. +- libxml2 + development libraries. + +We have a package guide available to help you install all the required packages for your OS - https://bjango.com/help/istat3/linuxpackages/ + + +Build procedure +- cd /path/to/istatserver +- ./autogen +- ./configure +- make +- sudo make install +- sudo /usr/local/bin/istatserver -d (daemon mode) + +A default passcode is generated on install. It can be found in the preference file, which is generally located at /usr/local/etc/istatserver/istatserver.conf. iStat for iOS and iStat for macOS will ask for this passcode the first time you connect to your computer. diff --git a/autogen b/autogen new file mode 100755 index 0000000..95908d4 --- /dev/null +++ b/autogen @@ -0,0 +1,68 @@ +#! /bin/sh + +# Jazzio Labs Autotools support (modified for istatd) + +# Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Mo McRoberts. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The names of the author(s) of this software may not be used to endorse +# or promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +# AUTHORS OF THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +AUTOHEADER26=${AUTOHEADER26-autoheader} +ACLOCAL110=${ACLOCAL110-aclocal} +AUTOMAKE110=${AUTOMAKE110-automake} +AUTOCONF26=${AUTOCONF26-autoconf} + +appname="$0" +srcdir="`pwd`" + +fail() { + echo "$appname: $*" >&2 + exit 1 +} + +oprogress() { + echo ">>> $*" +} +progress() { + echo " +> $*" +} + +test -r "$srcdir/configure.ac" || fail "Cannot find configure.ac in $srcdir" + +oprogress "Generating files in $srcdir" +progress "Generating aclocal.m4" +${ACLOCAL110} || exit + +if egrep '^AC_CONFIG_HEADER' "$srcdir/configure.ac" >/dev/null ; then + progress "Generating config.h.in" + ${AUTOHEADER26} || exit +fi +if egrep "^AM_INIT_AUTOMAKE" "$srcdir/configure.ac" >/dev/null ; then + progress "Generating Makefile.in from Makefile.am" + ${AUTOMAKE110} --add-missing --copy || exit +fi + +progress "Generating configure script" +${AUTOCONF26} || exit + +rm -rf autom4te.cache diff --git a/compile b/compile new file mode 100755 index 0000000..531136b --- /dev/null +++ b/compile @@ -0,0 +1,347 @@ +#! /bin/sh +# Wrapper for compilers which do not understand '-c -o'. + +scriptversion=2012-10-14.11; # UTC + +# Copyright (C) 1999-2013 Free Software Foundation, Inc. +# Written by Tom Tromey . +# +# 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, 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, see . + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# This file is maintained in Automake, please report +# bugs to or send patches to +# . + +nl=' +' + +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent tools from complaining about whitespace usage. +IFS=" "" $nl" + +file_conv= + +# func_file_conv build_file lazy +# Convert a $build file to $host form and store it in $file +# Currently only supports Windows hosts. If the determined conversion +# type is listed in (the comma separated) LAZY, no conversion will +# take place. +func_file_conv () +{ + file=$1 + case $file in + / | /[!/]*) # absolute file, and not a UNC file + if test -z "$file_conv"; then + # lazily determine how to convert abs files + case `uname -s` in + MINGW*) + file_conv=mingw + ;; + CYGWIN*) + file_conv=cygwin + ;; + *) + file_conv=wine + ;; + esac + fi + case $file_conv/,$2, in + *,$file_conv,*) + ;; + mingw/*) + file=`cmd //C echo "$file " | sed -e 's/"\(.*\) " *$/\1/'` + ;; + cygwin/*) + file=`cygpath -m "$file" || echo "$file"` + ;; + wine/*) + file=`winepath -w "$file" || echo "$file"` + ;; + esac + ;; + esac +} + +# func_cl_dashL linkdir +# Make cl look for libraries in LINKDIR +func_cl_dashL () +{ + func_file_conv "$1" + if test -z "$lib_path"; then + lib_path=$file + else + lib_path="$lib_path;$file" + fi + linker_opts="$linker_opts -LIBPATH:$file" +} + +# func_cl_dashl library +# Do a library search-path lookup for cl +func_cl_dashl () +{ + lib=$1 + found=no + save_IFS=$IFS + IFS=';' + for dir in $lib_path $LIB + do + IFS=$save_IFS + if $shared && test -f "$dir/$lib.dll.lib"; then + found=yes + lib=$dir/$lib.dll.lib + break + fi + if test -f "$dir/$lib.lib"; then + found=yes + lib=$dir/$lib.lib + break + fi + if test -f "$dir/lib$lib.a"; then + found=yes + lib=$dir/lib$lib.a + break + fi + done + IFS=$save_IFS + + if test "$found" != yes; then + lib=$lib.lib + fi +} + +# func_cl_wrapper cl arg... +# Adjust compile command to suit cl +func_cl_wrapper () +{ + # Assume a capable shell + lib_path= + shared=: + linker_opts= + for arg + do + if test -n "$eat"; then + eat= + else + case $1 in + -o) + # configure might choose to run compile as 'compile cc -o foo foo.c'. + eat=1 + case $2 in + *.o | *.[oO][bB][jJ]) + func_file_conv "$2" + set x "$@" -Fo"$file" + shift + ;; + *) + func_file_conv "$2" + set x "$@" -Fe"$file" + shift + ;; + esac + ;; + -I) + eat=1 + func_file_conv "$2" mingw + set x "$@" -I"$file" + shift + ;; + -I*) + func_file_conv "${1#-I}" mingw + set x "$@" -I"$file" + shift + ;; + -l) + eat=1 + func_cl_dashl "$2" + set x "$@" "$lib" + shift + ;; + -l*) + func_cl_dashl "${1#-l}" + set x "$@" "$lib" + shift + ;; + -L) + eat=1 + func_cl_dashL "$2" + ;; + -L*) + func_cl_dashL "${1#-L}" + ;; + -static) + shared=false + ;; + -Wl,*) + arg=${1#-Wl,} + save_ifs="$IFS"; IFS=',' + for flag in $arg; do + IFS="$save_ifs" + linker_opts="$linker_opts $flag" + done + IFS="$save_ifs" + ;; + -Xlinker) + eat=1 + linker_opts="$linker_opts $2" + ;; + -*) + set x "$@" "$1" + shift + ;; + *.cc | *.CC | *.cxx | *.CXX | *.[cC]++) + func_file_conv "$1" + set x "$@" -Tp"$file" + shift + ;; + *.c | *.cpp | *.CPP | *.lib | *.LIB | *.Lib | *.OBJ | *.obj | *.[oO]) + func_file_conv "$1" mingw + set x "$@" "$file" + shift + ;; + *) + set x "$@" "$1" + shift + ;; + esac + fi + shift + done + if test -n "$linker_opts"; then + linker_opts="-link$linker_opts" + fi + exec "$@" $linker_opts + exit 1 +} + +eat= + +case $1 in + '') + echo "$0: No command. Try '$0 --help' for more information." 1>&2 + exit 1; + ;; + -h | --h*) + cat <<\EOF +Usage: compile [--help] [--version] PROGRAM [ARGS] + +Wrapper for compilers which do not understand '-c -o'. +Remove '-o dest.o' from ARGS, run PROGRAM with the remaining +arguments, and rename the output as expected. + +If you are trying to build a whole package this is not the +right script to run: please start by reading the file 'INSTALL'. + +Report bugs to . +EOF + exit $? + ;; + -v | --v*) + echo "compile $scriptversion" + exit $? + ;; + cl | *[/\\]cl | cl.exe | *[/\\]cl.exe ) + func_cl_wrapper "$@" # Doesn't return... + ;; +esac + +ofile= +cfile= + +for arg +do + if test -n "$eat"; then + eat= + else + case $1 in + -o) + # configure might choose to run compile as 'compile cc -o foo foo.c'. + # So we strip '-o arg' only if arg is an object. + eat=1 + case $2 in + *.o | *.obj) + ofile=$2 + ;; + *) + set x "$@" -o "$2" + shift + ;; + esac + ;; + *.c) + cfile=$1 + set x "$@" "$1" + shift + ;; + *) + set x "$@" "$1" + shift + ;; + esac + fi + shift +done + +if test -z "$ofile" || test -z "$cfile"; then + # If no '-o' option was seen then we might have been invoked from a + # pattern rule where we don't need one. That is ok -- this is a + # normal compilation that the losing compiler can handle. If no + # '.c' file was seen then we are probably linking. That is also + # ok. + exec "$@" +fi + +# Name of file we expect compiler to create. +cofile=`echo "$cfile" | sed 's|^.*[\\/]||; s|^[a-zA-Z]:||; s/\.c$/.o/'` + +# Create the lock directory. +# Note: use '[/\\:.-]' here to ensure that we don't use the same name +# that we are using for the .o file. Also, base the name on the expected +# object file name, since that is what matters with a parallel build. +lockdir=`echo "$cofile" | sed -e 's|[/\\:.-]|_|g'`.d +while true; do + if mkdir "$lockdir" >/dev/null 2>&1; then + break + fi + sleep 1 +done +# FIXME: race condition here if user kills between mkdir and trap. +trap "rmdir '$lockdir'; exit 1" 1 2 15 + +# Run the compile. +"$@" +ret=$? + +if test -f "$cofile"; then + test "$cofile" = "$ofile" || mv "$cofile" "$ofile" +elif test -f "${cofile}bj"; then + test "${cofile}bj" = "$ofile" || mv "${cofile}bj" "$ofile" +fi + +rmdir "$lockdir" +exit $ret + +# Local Variables: +# mode: shell-script +# sh-indentation: 2 +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC" +# time-stamp-end: "; # UTC" +# End: diff --git a/conf/Makefile.am b/conf/Makefile.am new file mode 100644 index 0000000..fa86640 --- /dev/null +++ b/conf/Makefile.am @@ -0,0 +1,2 @@ +noinst_PROGRAMS = istatserverconf +istatserverconf_SOURCES = main.cpp diff --git a/conf/main.cpp b/conf/main.cpp new file mode 100644 index 0000000..21e9b61 --- /dev/null +++ b/conf/main.cpp @@ -0,0 +1,165 @@ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include + +#ifdef HAVE_ERRNO_H +# include "errno.h" +#endif + +#include +#include +#include +#include +#include +#include + +using namespace std; + +int +main(int argc, char **argv) +{ + static FILE * fp = NULL; + + char buf[1024]; + + vector lines; + vector lines_old; + + bool generatePasscode = false; + bool added_disk_labels = false; + + string argument; + int c; + for (c = 1; c < argc; c++) + { + argument = string(argv[c]); + + if (argument == "-p") + { + generatePasscode = true; + } + } + + string path_old = string(CONFIG_PATH) + "istatserver.conf.old"; + fp = fopen(path_old.c_str(), "r"); + if(fp) + { + while (fgets(buf, sizeof(buf), fp)){ + string line = string(buf); + lines_old.push_back(line); + } + fclose(fp); + fp = NULL; + } + + string path = string(CONFIG_PATH) + "istatserver.conf"; + if (!(fp = fopen(path.c_str(), "r"))) return 0; + + while (fgets(buf, sizeof(buf), fp)) + { + string line = string(buf); + + bool found_old_line = false; + if(lines_old.size() > 0) + { + if(line.find("disk_rename_label") != std::string::npos) + { + string key = line.substr(0, line.find(" ")); + + if(!added_disk_labels) + { + for (vector::iterator cur = lines_old.begin(); cur < lines_old.end(); ++cur) + { + if((*cur).find("disk_rename_label") != std::string::npos) + { + lines.push_back((*cur)); + } + } + } + found_old_line = true; + added_disk_labels = true; + } + else if(line.length() > 0 && line.substr(0, 1) != "#") + { + string::size_type pos = line.find_first_of(" \t"); + if(pos != std::string::npos) + { + string key = line.substr(0, pos); + + if(key == "server_code" && generatePasscode == true) + { + srand(time(NULL)); + int c = (rand()%88888) + 10000; + + stringstream code; + code << c; + + cout << "generating passcode" << endl; + + line = "server_code " + code.str() + "\n"; + } + else + { + for (vector::iterator cur = lines_old.begin(); cur < lines_old.end(); ++cur) + { + if((*cur).find(key) == 0) + { + lines.push_back((*cur)); + found_old_line = true; + } + } + } + } + } + } + + if(!found_old_line) + { + if(line.length() > 0 && line.substr(0, 1) != "#") + { + string::size_type pos = line.find_first_of(" \t"); + if(pos != std::string::npos) + { + string key = line.substr(0, pos); + + if(key == "server_code" && generatePasscode == true) + { + srand(time(NULL)); + int c = (rand()%88888) + 10000; + + stringstream code; + code << c; + + cout << "generating passcode" << endl; + + line = "server_code " + code.str() + "\n"; + } + } + } + + if(!found_old_line) + lines.push_back(line); + } + } + + if(lines.size() > 0) + { + string new_path = string(CONFIG_PATH) + "istatserver.conf"; + ofstream out(new_path.c_str()); + + for (vector::iterator cur = lines.begin(); cur < lines.end(); ++cur) + { + //cout << (*cur); + out << (*cur); + } + + out.close(); + } + + return 0; +} diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..a61c4dd --- /dev/null +++ b/configure.ac @@ -0,0 +1,629 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_PREREQ([2.59]) +AC_INIT([istatserver], [3.0], [http://github.com/bjango/istatserverlinux/issues]) +AM_INIT_AUTOMAKE([foreign subdir-objects]) +AM_MAINTAINER_MODE +AC_CANONICAL_HOST +AC_PROG_RANLIB + +AC_CONFIG_HEADERS([config.h]) + +# Checks for programs +AC_PROG_CXX([c++ g++ clang++ CC cxx cc++]) +AC_PROG_CC([cc gcc clang]) +#AC_PROG_CXX +#AC_PROG_CC +AC_PROG_INSTALL +AC_CHECK_TOOL(AR, ar, false) + +LIBS="$LIBS -lpthread" + +m4_ifndef([AC_LANG_DEFINES_PROVIDED], [m4_define([AC_LANG_DEFINES_PROVIDED])]) + +AC_MSG_CHECKING([if the compiler supports -Wall]) +oldcppflags="$CPPFLAGS" +CPPFLAGS="$CPPFLAGS -Wall" +AC_COMPILE_IFELSE([AC_LANG_DEFINES_PROVIDED + int main(void) { return 0; }],[ + AC_MSG_RESULT([yes]) +],[ + AC_MSG_RESULT([no]) + CPPFLAGS="$oldcppflags" +]) + +AC_MSG_CHECKING([if the linker supports -rdynamic]) +oldldflags="$LDFLAGS" +LDFLAGS="$LDFLAGS -rdynamic" +AC_LINK_IFELSE([AC_LANG_DEFINES_PROVIDED + int main(void) { return 0; }],[ + AC_MSG_RESULT([yes]) +],[ + AC_MSG_RESULT([no]) + LDFLAGS="$oldldflags" +]) + +# Add non-standard directories to the include path +AC_ARG_WITH(libraries, + [ + --with-libraries= additional place to look for libraries], + [LDFLAGS="$LDFLAGS -L $withval"], + , +) +# Add non-standard includes to the include path +AC_ARG_WITH(includes, + [ + --with-includes= additional place to look for header files], + [CPPFLAGS="$CPPFLAGS -I $withval"], + , +) +# Checks for libraries. +# AC_CHECK_LIB([dl], [dlopen],,[ +# AC_CHECK_FUNC([dlopen],,[ +# AC_MSG_ERROR([cannot find a suitable dlopen() implementation]) +# ]) +# ]) + +CPPFLAGS="$CPPFLAGS -I /usr/local/include" +LDFLAGS="$LDFLAGS -L /usr/local/lib" + +have_procfs=none + +case "${host_os}" in + linux|linux-*) + have_procfs=procfs + ;; +esac + + +dnl Fall back to procfs, if available + +use_cpu=$have_procfs +use_net=$have_procfs +use_uptime=$have_procfs +use_mem=$have_procfs +use_load=$have_procfs +use_activity=$have_procfs +use_battery=$have_procfs +use_processes=$have_procfs +use_disk_uuid=$have_procfs +use_disk=none +use_tls=none +use_bonjour=none +use_sqlite=none + +AC_CHECK_LIB([nsl],[inet_ntoa]) +AC_CHECK_LIB([socket],[socket]) + +AC_CHECK_PROG([XML2_CONFIG],[xml2-config],[xml2-config]) +if test x"$XML2_CONFIG" = x"" ; then + AC_MSG_ERROR([cannot find xml2-config: depending on your operating system you may need to install a '-dev' package for libxml2]) +fi +LIBS="$LIBS `$XML2_CONFIG --libs`" +CPPFLAGS="$CPPFLAGS `$XML2_CONFIG --cflags`" +AC_CHECK_HEADER([libxml/parser.h],,[ + AC_MSG_ERROR([xml2-config was located, but the header files could not be found]) +]) +AC_CHECK_FUNC([xmlFileOpen],[ + AC_DEFINE_UNQUOTED([HAVE_LIBXML2],[1],[Define to 1 if you have the 'xml2' library (-lxml2).]) +],[ + AC_MSG_ERROR([xml2-config was located, but a test program linking against -lxml2 could not be built]) +]) + +AC_CHECK_HEADERS([openssl/x509.h openssl/pem.h openssl/ssl.h openssl/evp.h openssl/err.h],[],[]) +AC_CHECK_LIB([ssl],[SSL_library_init], [ + AC_CHECK_LIB([ssl],[TLSv1_server_method], [ + AC_CHECK_LIB([crypto],[X509_new], [ + AC_DEFINE_UNQUOTED([HAVE_OPENSSL],1,[Define is openssl is available]) + LIBS="$LIBS -lssl -lcrypto" + use_tls=yes + ], []) + ], []) +], []) + +if test x"$use_tls" = x"none" ; then + AC_MSG_ERROR([openssl/crypto not found or does not support tlsv1. you may need to update openssl or install openssl-dev/libssl-dev or a similar package]) +fi + +AC_ARG_WITH(no-sqlite, + [ --with-no-sqlite disable sqlite support], + use_sqlite=no, + [ + AC_CHECK_HEADER([sqlite3.h], + AC_CHECK_LIB([sqlite3],[sqlite3_open], [ + LIBS="$LIBS -lsqlite3" + use_sqlite=yes + AC_DEFINE_UNQUOTED([USE_SQLITE],1,[Use sqlite for storing history]) + ], []) + , []) + + if test x"$use_sqlite" = x"none" ; then + AC_MSG_ERROR([sqlite not found. please install libsqlite3-dev/sqlite-devel or a similar package. Configure with "--with-no-sqlite" to disable sqlite support - not recommended as this will disable all history data support]) + fi + ]) + +AC_ARG_WITH(no-avahi, + [ --with-no-avahi disable avahi support], + use_bonjour=no, + [ + AC_CHECK_HEADERS([avahi-common/thread-watch.h], [ + AC_CHECK_LIB([avahi-client], [avahi_client_new], + AC_CHECK_LIB([avahi-common],[avahi_threaded_poll_new], [ + AC_DEFINE_UNQUOTED([HAVE_LIBAVAHI_CLIENT], 1, [Define if avahi is available]) + LIBS="$LIBS -lavahi-client -lavahi-common" + use_bonjour=yes + ]) + ) + ], []) +]) + +AC_CHECK_HEADERS([zlib.h], [ + AC_DEFINE_UNQUOTED([HAVE_LIBZLIB], 1, [Define if zlib is available]) + LIBS="$LIBS -lz" +], []) + +dnl Specific tests for probe types + +AC_CHECK_LIB([kstat],[kstat_open],[ +AC_DEFINE_UNQUOTED([HAVE_LIBKSTAT],1,[Define if libkstat is available]) +have_kstat=yes +LIBS="$LIBS -lkstat" +use_cpu=kstat +use_net=kstat +use_uptime=kstat +use_mem=kstat +use_processes=psinfo +use_activity=kstat +],[have_kstat=no]) + +if test x"$have_kstat" = x"no" ; then + AC_CHECK_LIB([kvm],[kvm_open],[ + AC_DEFINE_UNQUOTED([HAVE_LIBKVM],1,[Define if libkvm is available]) + LIBS="$LIBS -lkvm" + use_mem=kvm + use_processes=kvm + ]) +fi + +case "${host_os}" in + *hpux11*) + use_cpu=hpux + use_mem=hpux + use_net=hpux + use_load=hpux + use_processes=hpux + use_activity=hpux + use_uptime=hpux + CPPFLAGS="$CPPFLAGS -D_PSTAT64" + ;; + *openbsd*) + use_cpu=sysctl + use_mem=sysctl + use_net=getifaddrs + use_processes=kvm + use_activity=hw_diskstats_open + AC_DEFINE_UNQUOTED([PROCESSES_KVM_OPENBSD],1,[Define for openbsd]) + ;; + *netbsd*) + use_cpu=sysctlbyname + use_mem=sysctl + use_net=getifaddrs + use_processes=kvm + use_activity=hw_diskstats_net + AC_DEFINE_UNQUOTED([PROCESSES_KVM_NETBSD],1,[Define for netbsd]) + AC_DEFINE_UNQUOTED([GETMNTINFO_STATVFS],1,[Define if getmntinfo uses statvfs]) + ;; + *dragonfly*) + use_cpu=sysctlbyname + use_mem=sysctlbyname + use_net=getifaddrs + use_processes=kvm + AC_DEFINE_UNQUOTED([PROCESSES_KVM_DRAGONFLY],1,[Define for dragonfly]) + ;; + *solaris*) + AC_DEFINE_UNQUOTED([IPS_IOCTL],1,[Define to use ioctl for ip addresses on solaris]) + ;; + *aix*) + use_processes=aix + ;; +esac + +AC_CHECK_LIB([sensors],[sensors_init],[ + AC_DEFINE_UNQUOTED([HAVE_LIBSENSORS],1,[Define if libsensors is available]) + LIBS="$LIBS -lsensors" +]) + +AC_CHECK_LIB([devstat], [devstat_getdevs],[ +AC_DEFINE_UNQUOTED([HAVE_DEVSTAT],1,[Define if devstat is available]) +use_activity=devstat +LIBS="$LIBS -ldevstat" +],) + +AC_CHECK_LIB([devstat], [getdevs],[ +AC_DEFINE_UNQUOTED([HAVE_DEVSTAT_ALT],1,[Define if devstat is available]) +use_activity=devstat +LIBS="$LIBS -ldevstat" +],) + +AC_CHECK_LIB([perfstat],[perfstat_cpu_total],[ +AC_DEFINE_UNQUOTED([HAVE_LIBPERFSTAT],1,[Define if libperfstat is available]) +have_perfstat=yes +LIBS="$LIBS -lperfstat" +use_cpu=perfstat +use_net=perfstat +use_uptime=perfstat +use_mem=perfstat +use_load=perfstat +use_activity=perfstat +],[have_perfstat=no]) + +AC_CHECK_FUNC([getloadavg],[ +use_load=getloadavg +]) + +AC_HEADER_TIME +AC_CHECK_FUNC([clock_gettime],[ +AC_MSG_CHECKING([if CLOCK_UPTIME is available]) +AC_COMPILE_IFELSE([AC_LANG_DEFINES_PROVIDED +#ifdef TIME_WITH_SYS_TIME +# include +# include +#else +# ifdef HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +static clockid_t testuptime = CLOCK_UPTIME; +],[have_clock_uptime=yes ; use_uptime=gettime],[have_clock_uptime=no]) +AC_MSG_RESULT([$have_clock_uptime]) +]) + +dnl Use sysctl for cpu and net if procfs isn't available +AC_CHECK_FUNC([sysctl],[ +AC_DEFINE_UNQUOTED([HAVE_SYSCTL],1,[Define if sysctl is available]) +test x"$use_cpu" = x"none" && use_cpu=sysctlbyname +test x"$use_net" = x"none" && use_net=sysctl +have_sysctl=yes +],[have_sysctl=no]) + +AC_CHECK_FUNC([sysctlbyname],[ +AC_DEFINE_UNQUOTED([HAVE_SYSCTLBYNAME],1,[Define if sysctlbyname is available]) + +if test x"$use_battery" = x"none" ; then + case "${host_os}" in + freebsd*) + use_battery=sysctl + ;; + esac +fi +],) + +dnl Try to use sysctl on freebsd/netbsd if there's no CLOCK_UPTIME +if test x"$use_uptime" = x"none" ; then + case "$host_os" in + freebsd*|*netbsd*) + if test x"$have_sysctl" = x"yes" ; then + use_uptime=sysctl + fi + ;; + esac +fi + +AC_CHECK_FUNC([statvfs],[ +use_disk=statvfs +],[ + AC_CHECK_FUNC([statfs],[ + use_disk=statfs + ]) +]) + +# Checks for header files. +AC_CHECK_HEADERS([arpa/inet.h fcntl.h mntent.h netdb.h stdlib.h string.h paths.h sys/socket.h sys/statfs.h sys/statvfs.h sys/mnttab.h sys/loadavg.h kstat.h errno.h sys/sysinfo.h sys/processor.h sys/swap.h kvm.h alloca.h sys/resource.h netinet/in.h sys/sysctl.h sys/vmmeter.h sys/param.h sys/user.h sys/sched.h sys/dkstat.h sys/ioctl.h sensors/sensors.h libperfstat.h devstat.h ifaddrs.h dirent.h inet/common.h sys/sockio.h dev/acpica/acpiio.h sys/stat.h procfs.h sys/disk.h uvm/uvm_extern.h sys/time.h sys/procfs.h procinfo.h]) + +# hp-ux headers +AC_CHECK_HEADERS([sys/pstat.h sys/dk.h sys/dlpi.h sys/dlpi_ext.h sys/mib.h sys/stropts.h]) +AC_CHECK_MEMBER(struct pst_diskinfo.psd_dkbytewrite, + [AC_DEFINE(HAVE_HPUX_DISK_RW, , [pst_diskinfo.psd_dkbytewrite])], + [], + [#include ]) + +AC_CHECK_HEADERS([machine/apmvar.h], [ + use_battery=apm +], [], []) + +AC_CHECK_HEADERS([dev/acpica/acpiio.h], [ + use_battery=acpi +], [], []) + +# Mute warning in freebsd 6.0 +AC_CHECK_HEADERS([sys/mount.h], [], [], [#include ]) + +# Mute warning in openbsd 7.0 +AC_CHECK_HEADERS([sys/proc.h], [], [], [#include ]) + +# check net/if.h seperately, as it requires other headers on atleast FreeBSD +AC_CHECK_HEADERS([net/if.h net/if_mib.h net/if_var.h net/if_types.h],[],[], +[[ +#include +#ifdef HAVE_SYS_SOCKET_H +# include +#endif +#ifdef HAVE_NET_IF_H +# include +#endif +]]) + +# Checks for getifaddrs. +AC_CHECK_FUNCS([getifaddrs],[ + AC_DEFINE_UNQUOTED([HAVE_GETIFADDRS],[1],[Define if getifaddrs() is available.])] +,[]) + +# Checks for typedefs, structures, and compiler characteristics. +AC_HEADER_STDBOOL + +# Checks for library functions. +AC_FUNC_FORK +AC_FUNC_GETMNTENT +AC_CHECK_FUNCS([gethostbyname getmntent getmntinfo inet_ntoa memset mkdir select socket strerror statvfs setmntent]) + +# Try to figure out which type of mntent structure we're dealing with +AC_CHECK_MEMBER([struct mnttab.mnt_special],[AC_DEFINE_UNQUOTED([USE_STRUCT_MNTTAB],[1],[define to use 'struct mnttab'])],,[ +#include +#include +]) +AC_CHECK_MEMBER([struct mntent.mnt_fsname],[AC_DEFINE_UNQUOTED([USE_STRUCT_MNTENT],[1],[define to use 'struct mntent'])],,[ +#include +#include +]) +AC_CHECK_MEMBER([struct statvfs.f_frsize],[AC_DEFINE_UNQUOTED([HAVE_STATVFS_FRSIZE],[1],[define if statvfs has the f_frsize member])],,[ +#include +#include +]) + +AC_MSG_CHECKING([for default location of config directory]) +test x"$prefix" = x"NONE" && prefix="$ac_default_prefix" +configpath=`eval echo "$sysconfdir"/istatserver/` +AC_MSG_RESULT([$configpath]) +AC_DEFINE_UNQUOTED([CONFIG_PATH],["${configpath}"],[default location of the certificate files]) + +AC_MSG_CHECKING([for default location of configuration file]) +test x"$prefix" = x"NONE" && prefix="$ac_default_prefix" +configpath=`eval echo "$sysconfdir"/istatserver/istatserver.conf` +AC_MSG_RESULT([$configpath]) + +AC_MSG_CHECKING([CPU usage probe type]) +case $use_cpu in + hpux) + AC_DEFINE_UNQUOTED([USE_CPU_HPUX],1,[Use pstat_getdynamic to probe CPU usage]) + ;; + procfs) + AC_DEFINE_UNQUOTED([USE_CPU_PROCFS],1,[Use procfs to probe CPU usage]) + ;; + kstat) + AC_DEFINE_UNQUOTED([USE_CPU_KSTAT],1,[Use kstat(3) to probe CPU usage]) + ;; + perfstat) + AC_DEFINE_UNQUOTED([USE_CPU_PERFSTAT],1,[Use AIX perfstat to probe CPU usage]) + ;; + sysctlbyname) + AC_DEFINE_UNQUOTED([USE_CPU_SYSCTL_BYNAME],1,[Use sysctlbyname to probe CPU usage]) + ;; + sysctl) + AC_DEFINE_UNQUOTED([USE_CPU_SYSCTL],1,[Use sysctl to probe CPU usage]) + ;; + none) + AC_DEFINE_UNQUOTED([USE_CPU_NONE],1,[No CPU usage]) + ;; +esac +AC_MSG_RESULT($use_cpu) + +AC_MSG_CHECKING([load average probe type]) +case $use_load in + hpux) + AC_DEFINE_UNQUOTED([USE_LOAD_HPUX],1,[Use pstat_getdynamic to probe load average]) + ;; + procfs) + AC_DEFINE_UNQUOTED([USE_LOAD_PROCFS],1,[Use procfs to probe load average]) + ;; + getloadavg) + AC_DEFINE_UNQUOTED([USE_LOAD_GETLOADAVG],1,[Use getloadavg(3) to probe load average]) + ;; + perfstat) + AC_DEFINE_UNQUOTED([USE_LOAD_PERFSTAT],1,[Use perfstat (AIX) to probe load average]) + ;; + none) + AC_DEFINE_UNQUOTED([USE_LOAD_NONE],1,[No load average]) + ;; +esac +AC_MSG_RESULT($use_load) + +AC_MSG_CHECKING([memory usage probe type]) +case $use_mem in + hpux) + AC_DEFINE_UNQUOTED([USE_MEM_HPUX],1,[Use pstat_getdynamic to probe memory usage]) + ;; + procfs) + AC_DEFINE_UNQUOTED([USE_MEM_PROCFS],1,[Use procfs to probe memory usage]) + ;; + kstat) + AC_DEFINE_UNQUOTED([USE_MEM_KSTAT],1,[Use kstat(3) to probe memory usage]) + ;; + perfstat) + AC_DEFINE_UNQUOTED([USE_MEM_PERFSTAT],1,[Use perfstat (AIX) to probe memory usage]) + ;; + kvm) + AC_DEFINE_UNQUOTED([USE_MEM_KVM],1,[Use kvm(3) to probe memory usage]) + ;; + sysctl) + AC_DEFINE_UNQUOTED([USE_MEM_SYSCTL],1,[Use sysctl to probe memory usage]) + ;; + sysctlbyname) + AC_DEFINE_UNQUOTED([USE_MEM_SYSCTLBYNAME],1,[Use sysctl to probe memory usage]) + ;; + none) + AC_DEFINE_UNQUOTED([USE_MEM_NONE],1,[No memory usage]) + ;; +esac +AC_MSG_RESULT($use_mem) + +AC_MSG_CHECKING([uptime probe type]) +case $use_uptime in + hpux) + AC_DEFINE_UNQUOTED([USE_UPTIME_HPUX],1,[Use pstat_getdynamic to probe uptime]) + ;; + procfs) + AC_DEFINE_UNQUOTED([USE_UPTIME_PROCFS],1,[Use procfs to probe uptime]) + ;; + kstat) + AC_DEFINE_UNQUOTED([USE_UPTIME_KSTAT],1,[Use kstat(3) to probe uptime]) + ;; + perfstat) + AC_DEFINE_UNQUOTED([USE_UPTIME_PERFSTAT],1,[Use perfstat(3) to probe uptime]) + ;; + gettime) + AC_DEFINE_UNQUOTED([USE_UPTIME_GETTIME],1,[Use clock_gettime(2) to probe load uptime]) + ;; + sysctl) + AC_DEFINE_UNQUOTED([USE_UPTIME_SYSCTL],1,[Use sysctl(2) to probe uptime]) + ;; + sysctl) + AC_DEFINE_UNQUOTED([USE_UPTIME_NONE],1,[No uptime]) + ;; +esac +AC_MSG_RESULT($use_uptime) + +AC_MSG_CHECKING([network activity probe type]) +case $use_net in + hpux) + AC_DEFINE_UNQUOTED([USE_NET_HPUX],1,[Use pstat_getdynamic to probe network activity]) + ;; + procfs) + AC_DEFINE_UNQUOTED([USE_NET_PROCFS],1,[Use procfs to probe network activity]) + ;; + kstat) + AC_DEFINE_UNQUOTED([USE_NET_KSTAT],1,[Use kstat(3) to probe network activity]) + ;; + perfstat) + AC_DEFINE_UNQUOTED([USE_NET_PERFSTAT],1,[Use perfstat(3) to probe network activity]) + ;; + sysctl) + AC_DEFINE_UNQUOTED([USE_NET_SYSCTL],1,[Use sysctl to probe network activity]) + ;; + getifaddrs) + AC_DEFINE_UNQUOTED([USE_NET_GETIFADDRS],1,[Use getifaddrs to probe network activity]) + ;; + none) + AC_DEFINE_UNQUOTED([USE_NET_NONE],1,[No network activity]) + ;; +esac +AC_MSG_RESULT($use_net) + +AC_MSG_CHECKING([disk space probe type]) +case $use_disk in + statfs|statvfs) + AC_DEFINE_UNQUOTED([USE_DISK_STATFS],1,[Use statfs/statvfs(2) to probe disk space]) + ;; + none) + AC_DEFINE_UNQUOTED([USE_DISK_NONE],1,[No disk space]) + ;; +esac +AC_MSG_RESULT($use_disk) + +AC_MSG_CHECKING([disk activity probe type]) +case $use_activity in + hpux) + AC_DEFINE_UNQUOTED([USE_ACTIVITY_HPUX],1,[Use pstat_getdynamic to probe disk activity]) + ;; + procfs) + AC_DEFINE_UNQUOTED([USE_ACTIVITY_PROCFS],1,[Use procfs to probe disk activity]) + ;; + devstat) + AC_DEFINE_UNQUOTED([USE_ACTIVITY_DEVSTAT],1,[Use devstat to probe disk activity]) + ;; + kstat) + AC_DEFINE_UNQUOTED([USE_ACTIVITY_KSTAT],1,[Use kstat to probe disk activity]) + ;; + hw_diskstats_open) + AC_DEFINE_UNQUOTED([USE_ACTIVITY_HWDS_OPEN],1,[Use hw.diskstats to probe disk activity]) + ;; + hw_diskstats_net) + AC_DEFINE_UNQUOTED([USE_ACTIVITY_HWDS_NET],1,[Use hw.diskstats to probe disk activity]) + ;; + perfstat) + AC_DEFINE_UNQUOTED([USE_ACTIVITY_PERFSTAT],1,[Use AIX perfstat to probe disk activity]) + ;; + none) + AC_DEFINE_UNQUOTED([USE_ACTIVITY_NONE],1,[No disk activity]) + ;; +esac +AC_MSG_RESULT($use_activity) + +AC_MSG_CHECKING([battery probe type]) +case $use_battery in + procfs) + AC_DEFINE_UNQUOTED([USE_BATTERY_PROCFS],1,[Use procfs to probe battery]) + ;; + sysctl) + AC_DEFINE_UNQUOTED([USE_BATTERY_SYSCTL],1,[Use sysctl to probe battery]) + ;; + acpi) + AC_DEFINE_UNQUOTED([USE_BATTERY_ACPI],1,[Use acpi to probe battery]) + ;; + apm) + AC_DEFINE_UNQUOTED([USE_BATTERY_APM],1,[Use apm to probe battery]) + ;; + none) + AC_DEFINE_UNQUOTED([USE_BATTERY_NONE],1,[No battery]) + ;; +esac +AC_MSG_RESULT($use_battery) + +AC_MSG_CHECKING([processes probe type]) +case $use_processes in + hpux) + AC_DEFINE_UNQUOTED([USE_PROCESSES_HPUX],1,[Use pstat_getdynamic to probe processes]) + ;; + procfs) + AC_DEFINE_UNQUOTED([USE_PROCESSES_PROCFS],1,[Use procfs to probe processes]) + ;; + kvm) + AC_DEFINE_UNQUOTED([USE_PROCESSES_KVM],1,[Use kvm to probe processes]) + ;; + psinfo) + AC_DEFINE_UNQUOTED([USE_PROCESSES_PSINFO],1,[Use proc/psinfo to probe processes]) + ;; + aix) + AC_DEFINE_UNQUOTED([USE_PROCESSES_AIX],1,[Use getprocs64/psinfo to probe processes]) + ;; + none) + AC_DEFINE_UNQUOTED([USE_PROCESSES_NONE],1,[No processes]) + ;; +esac +AC_MSG_RESULT($use_processes) + +case $use_disk_uuid in + procfs) + AC_DEFINE_UNQUOTED([USE_DISKUUID_PROCFS],1,[Use procfs to probe disk uuids]) + ;; +esac + +AC_MSG_CHECKING([checking bonjour/zeroconf support]) +AC_MSG_RESULT($use_bonjour) + +AC_MSG_CHECKING([checking tls support]) +AC_MSG_RESULT($use_tls) + +AC_MSG_CHECKING([checking sqlite support]) +AC_MSG_RESULT($use_sqlite) + +AC_CONFIG_FILES([ +Makefile +src/Makefile +resource/Makefile +conf/Makefile +]) +AC_OUTPUT diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..8b163cb --- /dev/null +++ b/readme.md @@ -0,0 +1,41 @@ +# istatserver + +A system monitoring daemon that is used in conjunction with [iStat for iOS](https://bjango.com/ios/istat/) and [iStat for macOS](https://bjango.com/mac/istat/) to remotely monitor computers. + +----- + +### Supported OSs +- Linux +- FreeBSD, DragonFly BSD, OpenBSD, NetBSD and other BSD based OSs +- AIX +- Solaris +- HP-UX (Still in development and not tested) + +----- + +### Requirements +- C and C++ compilers such as gcc and g++. +- Auto tools (autoconf and automake). +- OpenSSL/libssl + development libraries. +- Sqlite3 + development libraries. +- libxml2 + development libraries. + +We have a package guide [available](https://bjango.com/help/istat3/linuxpackages/) to help you install all the required packages for your OS. + +----- + +### Build procedure +- [Download](https://download.bjango.com/istatserverlinux.tar.gz) and decompress source +- cd /path/to/istatserver +- ./autogen +- ./configure +- make +- sudo make install +- sudo /usr/local/bin/istatserver -d (daemon mode) + + +A default passcode is generated on install. It can be found in the preference file, which is generally located at **/usr/local/etc/istatserver/istatserver.conf**. iStat for iOS and iStat for macOS will ask for this passcode the first time you connect to your computer. + +----- + +istatserver is based on [istatd](https://github.com/tiwilliam/istatd) by William Tisäter diff --git a/resource/Makefile.am b/resource/Makefile.am new file mode 100644 index 0000000..0d4fc8e --- /dev/null +++ b/resource/Makefile.am @@ -0,0 +1,97 @@ + +dist_man_MANS = istatserver.1 istatserver.conf.5 +EXTRA_DIST = istatserver.conf istatserver_generated.conf + +#dist_sysconf_DATA = istatserver.conf + +MKDIR=/bin/mkdir +MV=/bin/mv +ADDUSER=/usr/sbin/useradd +ADDGROUP=/usr/sbin/groupadd +ADDUSERPW=/usr/sbin/pw +MKGROUP=/usr/bin/mkgroup +OLD_CONFIG_FILE = $(sysconfdir)/istat.conf +DEFAULT_CONFIG_FILE = $(sysconfdir)/istatserver/istatserver.conf +DEFAULT_CONFIG_FILE_MV = $(sysconfdir)/istatserver/istatserver.conf.old +DEFAULT_CONFIG_GENERATED_FILE = $(sysconfdir)/istatserver/istatserver_generated.conf +CERT_FILE = $(sysconfdir)/istatserver/cert.pem +KEY_FILE = $(sysconfdir)/istatserver/key.pem +CACHE_FILE = $(sysconfdir)/istatserver/clients.dat + +install-exec-hook: + @if test -f "$(ADDUSERPW)" ; then \ + $(ADDUSERPW) groupadd istat || true ; \ + $(ADDUSERPW) useradd istat -g istat || true ; \ + fi + + @if test -f "$(ADDGROUP)" ; then \ + $(ADDGROUP) istat || true ; \ + fi + + @if test -f "$(MKGROUP)" ; then \ + $(MKGROUP) istat || true ; \ + fi + + @if test -f "$(ADDUSER)" ; then \ + $(ADDUSER) --system -g istat istat || true ; \ + $(ADDUSER) -g istat istat || true ; \ + fi + + $(MKDIR) -p "$(DESTDIR)$(sysconfdir)"; + $(MKDIR) -p "$(DESTDIR)$(sysconfdir)/istatserver/" + $(MKDIR) -p "/var/run/istatserver" || true ; + chown istat:istat "/var/run/istatserver" || true ; + chown istat:istat "$(DESTDIR)$(sysconfdir)/istatserver/" || true ; + chmod 755 "$(DESTDIR)$(sysconfdir)/istatserver/" || true ; + + @if test -f "$(DESTDIR)$(OLD_CONFIG_FILE)" ; then \ + $(MV) "$(DESTDIR)$(OLD_CONFIG_FILE)" "$(DESTDIR)$(DEFAULT_CONFIG_FILE)" ; \ + fi + + @if test -f "/var/cache/istat/clients.dat" ; then \ + $(MV) "/var/cache/istat/clients.dat" "$(DESTDIR)$(sysconfdir)/istatserver/clients.dat" ; \ + chown istat:istat "$(DESTDIR)$(sysconfdir)/istatserver/clients.dat" || true ; \ + fi + + @if test -f "$(DESTDIR)$(DEFAULT_CONFIG_FILE)" ; then \ + $(MV) "$(DESTDIR)$(DEFAULT_CONFIG_FILE)" "$(DESTDIR)$(DEFAULT_CONFIG_FILE_MV)" ; \ + $(INSTALL_DATA) "$(srcdir)/istatserver.conf" "$(DESTDIR)$(DEFAULT_CONFIG_FILE)"; \ + echo "$@ running conf updater"; \ + ../conf/istatserverconf || true ; \ + rm "$(DESTDIR)$(DEFAULT_CONFIG_FILE_MV)" || true ; \ + chown istat:istat "$(DESTDIR)$(DEFAULT_CONFIG_FILE)" || true ; \ + else \ + echo "$(INSTALL_DATA) istatserver.conf $(DESTDIR)$(DEFAULT_CONFIG_FILE)"; \ + $(INSTALL_DATA) "$(srcdir)/istatserver.conf" "$(DESTDIR)$(DEFAULT_CONFIG_FILE)"; \ + echo "$@ running conf updater and generating passcode"; \ + ../conf/istatserverconf -p || true ; \ + chown istat:istat "$(DESTDIR)$(DEFAULT_CONFIG_FILE)" || true ; \ + fi + + @if test -f "$(DESTDIR)$(DEFAULT_CONFIG_GENERATED_FILE)" ; then \ + echo "$@ will not overwrite existing $(DESTDIR)$(DEFAULT_CONFIG_GENERATED_FILE)" ; \ + else \ + echo "$(INSTALL_DATA) istatserver_generated.conf $(DESTDIR)$(DEFAULT_CONFIG_GENERATED_FILE)"; \ + $(INSTALL_DATA) "$(srcdir)/istatserver_generated.conf" "$(DESTDIR)$(DEFAULT_CONFIG_GENERATED_FILE)"; \ + fi + +uninstall-hook: + @if test -f "$(DESTDIR)$(DEFAULT_CONFIG_GENERATED_FILE)" ; then \ + rm "$(DESTDIR)$(DEFAULT_CONFIG_GENERATED_FILE)"; \ + fi + + @if test -f "$(DESTDIR)$(DEFAULT_CONFIG_FILE)" ; then \ + rm "$(DESTDIR)$(DEFAULT_CONFIG_FILE)"; \ + fi + + @if test -f "$(DESTDIR)$(CERT_FILE)" ; then \ + rm "$(DESTDIR)$(CERT_FILE)"; \ + fi + + @if test -f "$(DESTDIR)$(KEY_FILE)" ; then \ + rm "$(DESTDIR)$(KEY_FILE)"; \ + fi + + @if test -f "$(DESTDIR)$(CACHE_FILE)" ; then \ + rm "$(DESTDIR)$(CACHE_FILE)"; \ + fi diff --git a/resource/istatserver.1 b/resource/istatserver.1 new file mode 100644 index 0000000..469afd8 --- /dev/null +++ b/resource/istatserver.1 @@ -0,0 +1,50 @@ +.Dd 2009-05-17 +.Dt istatd 1 +.Os +.Sh NAME +.Nm istatd +.Nd serving statistics to your istat iphone application +.Sh SYNOPSIS +.Nm +.Op Fl c Ar config +.Op Fl a Ar address +.Op Fl p Ar port +.Op Fl d + +.Sh DESCRIPTION +.Nm +is a server daemon for serving statistics to your istat iphone application. +.Nm +collects data such as cpu, mem, network and disk usage and keeps the history. +once connecting from the iphone and entering the lock code this data will be +sent to the iphone and shown in fancy graphs. + +.Sh OPTIONS +.Bl -tag -width -indent-three +.It Fl v +Print version number +.It Fl d +Run in the process in background +.It Fl c Ar config +Use a custom config file search path +.It Fl a Ar address +Listen for network connections on this address +.It Fl p Ar port +Listen for network connections on this port +.It Fl u Ar user +User to run daemon as +.It Fl g Ar group +Group to run daemon as +.It Fl -pid Ar path +Custom pid file location +.It Fl -clear-sessions +Clear all saved authorized sessions. +.El +.Pp +.Sh FILES +/etc/istat.conf +.Pp +Configuration for network settings, lock code, running user, devices to monitor and more. +.El +.Sh SEE ALSO +.Xr istat.conf 5 diff --git a/resource/istatserver.conf b/resource/istatserver.conf new file mode 100644 index 0000000..7d84f99 --- /dev/null +++ b/resource/istatserver.conf @@ -0,0 +1,28 @@ +# +# istatserver.conf: Configuration for istatserver +# + +# server_code is a 5 digit number by default but it can be anything you like including text +server_code 12345 + +# network_addr 127.0.0.1 +# network_port 5109 +# server_user istat +# server_group istat +# server_socket /tmp/istatserver.sock +# server_pid /var/run/istatserver/istatserver.pid + +# Set to 1 if you want to disable sqlite history storage. +disable_history_storage 0 + +# Set to 1 if you want to disable disk filtering based on mount path. +disk_disable_filtering 0 + +# Set to 1 if you want to use mount path as label instead of the device name. +disk_mount_path_label 1 + +# Set custom disk label. Will override all other labels. +# disk_rename_label /dev/sda1 "root" +# disk_rename_label /home "home" + +# End of file diff --git a/resource/istatserver.conf.5 b/resource/istatserver.conf.5 new file mode 100644 index 0000000..cb4862f --- /dev/null +++ b/resource/istatserver.conf.5 @@ -0,0 +1,43 @@ +.Dd 2009-05-17 +.Dt istat.conf 5 +.Os +.Sh NAME +.Nm istat.conf +.Nd configuration file for istatd + +.Sh OPTIONS +.Bl -tag -width -indent-three +.It network_addr +Address to bind (default: 0.0.0.0) +.It network_port +Port to bind (default: 5109) +.It server_code +Lock code needed when connecting to the server for the first time. +.It server_socket +Location of the unix socket. (default: /tmp/istatd.sock) +.It server_pid +Location of the pid. (default: /var/run/istat/istatd.pid) +.It server_user +User to switch to when entering daemon mode. It's not recommended to use high privilaged users like root due to security reasons. Defaults to root if the user doesn't exist. (default: istat) +.It server_group +Group to switch to when entering daemon mode. Defaults to root if the group doesn't exist. (default: istat) +.It monitor_disk +Can contain search path to device or mount point. When specifying more than one disk, use paranteses around the list: + +monitor_disk /dev/sda1 + +monitor_disk ( /dev/sda1 /dev/sda2 ) + +.It disk_mount_path_label +Set to 1 if you want to use mount path as label instead of the device name. +.It disk_filesystem_label +Set to 1 if you want to probe the filesystem for disk label, will override the mount path and device name. +.It disk_rename_label +Set custom disk label. Will override all other labels. You can use either the device name or mount path: + +disk_rename_label /dev/sda1 "root" + +disk_rename_label /home "home" +.El +.Sh SEE ALSO +.Xr istatd 1 diff --git a/resource/istatserver_generated.conf b/resource/istatserver_generated.conf new file mode 100644 index 0000000..33fe72d --- /dev/null +++ b/resource/istatserver_generated.conf @@ -0,0 +1,5 @@ +# +# istatserver_generated.conf: Configuration for istatserver +# this file is generated by istatserver +# do not modify + diff --git a/src/Argument.cpp b/src/Argument.cpp new file mode 100644 index 0000000..2539ff3 --- /dev/null +++ b/src/Argument.cpp @@ -0,0 +1,108 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include + +#include "Argument.h" + +using namespace std; + +ArgumentSet::ArgumentSet(int argc, char ** argv) +{ + int c; + Argument temp; + string argument, value; + + for (c = 1; c < argc; c++) + { + argument = string(argv[c]); + + if (argument.substr(0, 1) == "-") + { + // Handle long options + if (argument.substr(1, 1) == "-") + { + // Is this a toggle flag or a flag with value? + if (argument.find_first_of("=") < string::npos) + { + temp.value = argument.substr(argument.find_first_of("=") + 1); + temp.argument = argument.substr(2, argument.find_first_of("=") - 2); + } + else + { + temp.value = "1"; + temp.argument = argument.substr(2); + } + } + else + { + if ((c + 1) < argc) + value = string(argv[c + 1]); + else + value = "1"; + + if (value.substr(0, 1) == "-") + value = "1"; + + argument = argument.substr(1); + + temp.value = value; + temp.argument = argument; + } + + this->args.push_back(temp); + } + } +} + +bool ArgumentSet::is_set(const string & arg) +{ + for (vector::iterator i = args.begin(); i != args.end(); i++) + { + if (i->argument == arg) + if (i->value != "") + return 1; + } + + return 0; +} + +string ArgumentSet::get(const string & arg, const std::string & val) +{ + for (vector::iterator i = args.begin(); i != args.end(); i++) + { + if (i->argument == arg) + return i->value; + } + + return val; +} diff --git a/src/Argument.h b/src/Argument.h new file mode 100644 index 0000000..e2a7f19 --- /dev/null +++ b/src/Argument.h @@ -0,0 +1,58 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef _ARGUMENT_H +#define _ARGUMENT_H + +#include +#include +#include + +class Argument +{ + public: + std::string value; + std::string argument; +}; + +class ArgumentSet +{ + public: + ArgumentSet(int argc, char ** argv); + + bool is_set(const std::string & arg); + std::string get(const std::string & arg, const std::string & val = ""); + + private: + std::vector args; +}; + +#endif diff --git a/src/Avahi.cpp b/src/Avahi.cpp new file mode 100644 index 0000000..0e80f69 --- /dev/null +++ b/src/Avahi.cpp @@ -0,0 +1,210 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifdef HAVE_LIBAVAHI_CLIENT + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Avahi.h" +#include + +using namespace std; + +static AvahiEntryGroup *avahiGroup = NULL; +static AvahiClient* avahiClient = NULL; +static AvahiThreadedPoll* avahiThread = NULL; + +void AvahiGroupCallback(AvahiEntryGroup *_avahiGroup, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata) +{ + assert(_avahiGroup == avahiGroup || avahiGroup == NULL); + avahiGroup = _avahiGroup; + switch (state) + { + case AVAHI_ENTRY_GROUP_ESTABLISHED: + { + cout << "avahi successfully established" << endl; + } + break; + case AVAHI_ENTRY_GROUP_COLLISION: + case AVAHI_ENTRY_GROUP_FAILURE: + { + cout << "avahi entry group failure: " << avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(avahiGroup))) << endl; + } + break; + case AVAHI_ENTRY_GROUP_UNCOMMITED: + case AVAHI_ENTRY_GROUP_REGISTERING: + { + } + break; + } + return; +} + +void AvahiCreateService(AvahiClient *avahiClient, AvahiPublisher *publisher) +{ + assert(avahiClient); + if (!avahiGroup) + { + avahiGroup = avahi_entry_group_new(avahiClient, AvahiGroupCallback, NULL); + if (!avahiGroup) + { + cout << "avahi_entry_group_new() failed: " << avahi_strerror(avahi_client_errno(avahiClient)) << endl; + return; + } + } + if (avahi_entry_group_is_empty(avahiGroup)) + { + AvahiStringList *txt = NULL; + txt = avahi_string_list_add_pair(txt, "name", publisher->name.c_str()); + txt = avahi_string_list_add_pair(txt, "model", ""); + + char buffer [8]; + int n = sprintf(buffer, "%d", publisher->protocol); + if(n > 0){ + txt = avahi_string_list_add_pair(txt, "protocol", buffer); + } + + char platformBuffer [8]; + n = sprintf(platformBuffer, "%d", publisher->platform); + if(n > 0){ + txt = avahi_string_list_add_pair(txt, "platform", platformBuffer); + } + + int ret = avahi_entry_group_add_service_strlst( + avahiGroup, + AVAHI_IF_UNSPEC, + AVAHI_PROTO_UNSPEC, + (AvahiPublishFlags)0, + publisher->uuid.c_str(), + "_istatserver._tcp", + "local.", + NULL, + publisher->port, + txt + ); + if (ret < 0) + { + cout << "avahi_entry_group_add_service() failed: " << avahi_strerror(ret) << endl; + return; + } + ret = avahi_entry_group_commit(avahiGroup); + if (ret < 0) + { + cout << "avahi_entry_group_commit() failed: " << avahi_strerror(ret) << endl; + return; + } + } + return; +} + +void AvahiCallback(AvahiClient *avahiClient, AvahiClientState state, void *data) +{ + assert(avahiClient); + + AvahiPublisher *publisher = static_cast(data); + + switch (state) + { + case AVAHI_CLIENT_S_RUNNING: + { + AvahiCreateService(avahiClient, publisher); + } + break; + case AVAHI_CLIENT_FAILURE: + { + cout << "avahi client failure: " << avahi_strerror(avahi_client_errno(avahiClient)) << endl; + } + break; + case AVAHI_CLIENT_S_COLLISION: + case AVAHI_CLIENT_S_REGISTERING: + { + if (avahiGroup) + avahi_entry_group_reset(avahiGroup); + } + break; + case AVAHI_CLIENT_CONNECTING: + { + } + break; + } +} + +void AvahiPublisher::stop() +{ + if(avahiClient == NULL) + return; + + cout << "Closing avahi" << endl; + avahi_client_free(avahiClient); +} + +int AvahiPublisher::publish_service() +{ + avahiThread = avahi_threaded_poll_new(); /* lost to never be found.. */ + if (!avahiThread) + { + cout << "avahi_threaded_poll_new failed" << endl; + return -1; + } + int error; + avahiClient = avahi_client_new( + avahi_threaded_poll_get(avahiThread), + (AvahiClientFlags)0, + AvahiCallback, + this, + &error + ); + if (!avahiClient) + { + cout << "avahi_client_new failed: " << avahi_strerror(error) << endl; + return -1; + } + if (avahi_threaded_poll_start(avahiThread) < 0) + { + cout << "avahi_threaded_poll_start failed" << endl; + return -1; + } + return 0; +} + +#endif + diff --git a/src/Avahi.h b/src/Avahi.h new file mode 100644 index 0000000..6e1a8bf --- /dev/null +++ b/src/Avahi.h @@ -0,0 +1,52 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "config.h" + +#ifdef HAVE_LIBAVAHI_CLIENT + +#ifndef _AVAHI_H +#define _AVAHI_H + +class AvahiPublisher +{ + public: + int port; + int platform; + std::string name; + std::string uuid; + int protocol; + int publish_service(); + void stop(); +}; +#endif +#endif + diff --git a/src/Certificate.cpp b/src/Certificate.cpp new file mode 100644 index 0000000..d0ef250 --- /dev/null +++ b/src/Certificate.cpp @@ -0,0 +1,181 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "Certificate.h" +#include +#include + +using namespace std; + +/* Generates a 2048-bit RSA key. */ +EVP_PKEY * generate_key() +{ + /* Allocate memory for the EVP_PKEY structure. */ + EVP_PKEY * pkey = EVP_PKEY_new(); + if(!pkey) + { + std::cerr << "Unable to create EVP_PKEY structure." << std::endl; + return NULL; + } + + /* Generate the RSA key and assign it to pkey. */ + RSA * rsa = RSA_generate_key(2048, RSA_F4, NULL, NULL); + if(!EVP_PKEY_assign_RSA(pkey, rsa)) + { + std::cerr << "Unable to generate 2048-bit RSA key." << std::endl; + EVP_PKEY_free(pkey); + return NULL; + } + + /* The key has been generated, return it. */ + return pkey; +} + +/* Generates a self-signed x509 certificate. */ +X509 * generate_x509(EVP_PKEY * pkey) +{ + /* Allocate memory for the X509 structure. */ + X509 * x509 = X509_new(); + if(!x509) + { + std::cerr << "Unable to create X509 structure." << std::endl; + return NULL; + } + + /* Set the serial number. */ + ASN1_INTEGER_set(X509_get_serialNumber(x509), 1); + + /* This certificate is valid from now until exactly one year from now. */ + X509_gmtime_adj(X509_get_notBefore(x509), 0); + X509_gmtime_adj(X509_get_notAfter(x509), 31536000L); + + /* Set the public key for our certificate. */ + X509_set_pubkey(x509, pkey); + + /* We want to copy the subject name to the issuer name. */ + X509_NAME * name = X509_get_subject_name(x509); + + /* Set the country code and common name. */ + X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char *)"CA", -1, -1, 0); + X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char *)"istatd", -1, -1, 0); + X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char *)"localhost", -1, -1, 0); + + /* Now set the issuer name. */ + X509_set_issuer_name(x509, name); + + /* Actually sign the certificate with our key. */ + if(!X509_sign(x509, pkey, EVP_sha1())) + { + std::cerr << "Error signing certificate." << std::endl; + X509_free(x509); + return NULL; + } + + return x509; +} + +bool write_to_disk(EVP_PKEY * pkey, X509 * x509) +{ + /* Open the PEM file for writing the key to disk. */ + string privateKeyPath = string(CONFIG_PATH) + "key.pem"; + FILE * pkey_file = fopen(privateKeyPath.c_str(), "wb"); + if(!pkey_file) + { + std::cerr << "Unable to open \"key.pem\" for writing." << std::endl; + return false; + } + + /* Write the key to disk. */ + bool ret = PEM_write_PrivateKey(pkey_file, pkey, NULL, NULL, 0, NULL, NULL); + fclose(pkey_file); + + if(!ret) + { + std::cerr << "Unable to write private key to disk." << std::endl; + return false; + } + + /* Open the PEM file for writing the certificate to disk. */ + string certPath = string(CONFIG_PATH) + "cert.pem"; + FILE * x509_file = fopen(certPath.c_str(), "wb"); + if(!x509_file) + { + std::cerr << "Unable to open \"cert.pem\" for writing." << std::endl; + return false; + } + + /* Write the certificate to disk. */ + ret = PEM_write_X509(x509_file, x509); + fclose(x509_file); + + if(!ret) + { + std::cerr << "Unable to write certificate to disk." << std::endl; + return false; + } + + return true; +} + +int createSSLCertificate() +{ + /* Generate the key. */ + std::cout << "Generating RSA key..." << std::endl; + + EVP_PKEY * pkey = generate_key(); + if(!pkey) + return 1; + + /* Generate the certificate. */ + std::cout << "Generating x509 certificate..." << std::endl; + + X509 * x509 = generate_x509(pkey); + if(!x509) + { + EVP_PKEY_free(pkey); + return 1; + } + + /* Write the private key and certificate out to disk. */ + std::cout << "Writing key and certificate to disk..." << std::endl; + + bool ret = write_to_disk(pkey, x509); + EVP_PKEY_free(pkey); + X509_free(x509); + + if(ret) + { + std::cout << "Success!" << std::endl; + return 0; + } + + return 1; +} diff --git a/src/Certificate.h b/src/Certificate.h new file mode 100644 index 0000000..c821ff2 --- /dev/null +++ b/src/Certificate.h @@ -0,0 +1,40 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "config.h" +#include +#include +#include +#include +#include +#include + +int createSSLCertificate(); diff --git a/src/Clientset.cpp b/src/Clientset.cpp new file mode 100644 index 0000000..2a46377 --- /dev/null +++ b/src/Clientset.cpp @@ -0,0 +1,153 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Utility.h" +#include "Clientset.h" +#include + +using namespace std; + +void ClientSet::authenticate(string uuid) +{ + authenticatedClients.push_back(uuid); + save_cache(); +} + +int ClientSet::is_authenticated(string _duuid) +{ + for (vector::const_iterator search = authenticatedClients.begin(); search != authenticatedClients.end(); ++search) + { + if (*search == _duuid) + { + return 1; + } + } + + return 0; +} + +void ClientSet::clear_cache(void) +{ + if (this->cache_dir.length()) + { + stringstream path; + string cache_file = "clients.dat"; + path << this->cache_dir << cache_file; + + if (check_file_exist(path.str())) + { + if (unlink(path.str().c_str()) == 0) + { + cout << "Successfully cleared all sessions." << endl; + } + else + { + cout << "Could not clear sessions in '" << path.str() << "': " << strerror(errno) << endl; + } + } + } +} + +void ClientSet::save_cache(void) +{ + if (this->cache_dir.length()) + { + stringstream path; + string cache_file = "clients.dat"; + path << this->cache_dir << cache_file; + + ofstream out(path.str().c_str()); + chmod(path.str().c_str(), 0600); + + if (!out) + { + cout << "Could not create file '" << path.str() << "': " << strerror(errno) << endl; + return; + } + for (vector::const_iterator search = authenticatedClients.begin(); search != authenticatedClients.end(); ++search) + { + string uuid = *search; + out << uuid << endl; + } + + out.close(); + } +} + +void ClientSet::read_cache(const std::string & _cache_dir) +{ + string line; + stringstream path; + vector array; + string cache_file = "clients.dat"; + + this->cache_dir = _cache_dir; + path << this->cache_dir << cache_file; + + ifstream cache(path.str().c_str()); + + if (cache.good()) + { + while (getline(cache, line)) + { + if (line.length()) + { + array = explode(line, ":"); + + if(array.size() > 1){ + if (array.size() < 3) continue; + + if(to_int(array[2]) == 1) + authenticatedClients.push_back(array[1]); + } else { + authenticatedClients.push_back(line); + } + } + } + } + else + { + // Ignore no such file errors, we will create the file upon save + if (errno != ENOENT) + cout << "Could not read cache file '" << path.str() << "': " << strerror(errno) << endl; + } + + cache.close(); +} diff --git a/src/Clientset.h b/src/Clientset.h new file mode 100644 index 0000000..35ea5aa --- /dev/null +++ b/src/Clientset.h @@ -0,0 +1,59 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef _CLIENTSET_H +#define _CLIENTSET_H + +#include +#include + +//#include "socketset.h" + +class ClientSet +{ + public: +// void operator += (Client & _client); + void authenticate(std::string _duuid); +// Client *get_client(int _socket); + int is_authenticated(std::string _duuid); +// void init_session(int _socket, int protocol); + int length(void); + void clear_cache(void); + void save_cache(void); + void read_cache(const std::string & cachedir); + + private: + std::string cache_dir; + std::vector authenticatedClients; +// std::vector clients; +}; + +#endif diff --git a/src/Conf.cpp b/src/Conf.cpp new file mode 100644 index 0000000..836c563 --- /dev/null +++ b/src/Conf.cpp @@ -0,0 +1,192 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Conf.h" +#include "Utility.h" + +using namespace std; + +unsigned int Property::get_array_size() +{ + return array.size(); +} + +std::string Property::get_array(unsigned int _index) +{ + if (_index < array.size()) + return array[_index]; + + return ""; +} + +void Config::parse() +{ + Property property; + unsigned int num = 1; + vector array; + string::size_type pos; + string var, val, line; + + ifstream config(filename.c_str()); + + if (config.good()) + { + while (getline(config, line)) + { + // Remove whitespace and comments + remove_junk(line); + + // Okey we got something to analyze... + if (line.length()) + { + pos = line.find_first_of(" \t"); + var = line.substr(0, pos); + val = trim(line.substr(pos)); + + property.var = var; + property.val = val; + + // Parse arrays + if (val[0] == ARRAY_CHAR_BEG && val[val.length() - 1] == ARRAY_CHAR_END) + { + val = trim(val.substr(1, val.length() - 2)); + array = explode(val, " \t"); + + property.val = ""; + property.array = array; + } + + properties.push_back(property); + } + + // Count lines for syntax error + num++; + } + + config.close(); + } + else + { + cout << "Could not read configuration from " << filename << ": " << strerror(errno) << endl; + config.close(); + exit(1); + } +} + +void Config::validate() +{ + if (properties.size()) + { + /* + if (this->get("server_addr") != "") cout << "Validating server_addr: " << this->get("server_addr") << endl; + if (this->get("server_port") != "") cout << "Validating server_port: " << this->get("server_port") << endl; + if (this->get("server_code") != "") cout << "Validating server_code: " << this->get("server_code") << endl; + if (this->get("server_pid") != "") cout << "Validating server_pid: " << this->get("server_pid") << endl; + if (this->get("server_socket") != "") cout << "Validating server_socket: " << this->get("server_socket") << endl; + */ + } +} + +void Config::remove_junk(string & _line) +{ + string::size_type pos; + + // Remove whitespace + _line = trim(_line); + + // Remove comments + if ((pos = _line.find_first_of(COMMENT_CHAR)) != string::npos) + { + _line = _line.substr(0, pos); + } +} + +bool Config::is_set(const string & _var) +{ + for (vector::iterator i = properties.begin(); i != properties.end(); i++) + { + if (i->var == _var) + if (i->val != "") + return 1; + } + + return 0; +} + +string Config::get(const string & _var, const std::string & _default) +{ + for (vector::iterator i = properties.begin(); i != properties.end(); i++) + { + if (i->var == _var) + return i->val; + } + + return _default; +} + +vector Config::get_array(const string & _var) +{ + vector temp; + + for (vector::iterator i = properties.begin(); i != properties.end(); i++) + { + if (i->var == _var) + temp.push_back(i->val); + } + + return temp; +} + +Property Config::get_property(const string & _var) +{ + Property null; + + null.val = ""; + null.var = ""; + + for (vector::iterator i = properties.begin(); i != properties.end(); i++) + { + if (i->var == _var) + return (*i); + } + + return null; +} diff --git a/src/Conf.h b/src/Conf.h new file mode 100644 index 0000000..912729e --- /dev/null +++ b/src/Conf.h @@ -0,0 +1,73 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef _CONF_H +#define _CONF_H + +#include +#include +#include + +#define COMMENT_CHAR '#' +#define ARRAY_CHAR_BEG '(' +#define ARRAY_CHAR_END ')' + +class Property +{ + public: + std::string var; + std::string val; + std::vector array; + + unsigned int get_array_size(); + std::string get_array(unsigned int _index); +}; + +class Config +{ + public: + Config(const std::string & _filename) : filename(_filename) {} + + void parse(); + void validate(); + bool is_set(const std::string & _var); + std::string get(const std::string & _var, const std::string & _default = ""); + std::vector get_array(const std::string & _var); + Property get_property(const std::string & _var); + + private: + void remove_junk(std::string & _line); + + std::string filename; + std::vector properties; +}; + +#endif diff --git a/src/Daemon.cpp b/src/Daemon.cpp new file mode 100644 index 0000000..8c8b37a --- /dev/null +++ b/src/Daemon.cpp @@ -0,0 +1,258 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "main.h" +#include "Daemon.h" +#include "Utility.h" + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +using namespace std; + +#ifdef HAVE_LIBAVAHI_CLIENT +static AvahiPublisher avahiPublisher; +#endif + +void Daemon::create(bool _back, const string &_user, const string &_group) +{ + uid_t uid; + gid_t gid; + ofstream out; + string piddir; + unsigned int pid; + string::size_type pos; + + // Obtain new process group + setsid(); + + // Translate user from configuration to uid + if ((uid = get_uid_from_str(_user)) == (uid_t) -1) + { + cout << "Warning! Cannot get uid for username " << _user << ", will run as current user." << endl; + uid = get_current_uid(); + } + + // Translate group from configuration to gid + if ((gid = get_gid_from_str(_group)) == (gid_t) -1) + { + cout << "Warning! Cannot get gid for group " << _group << ", will run as current group." << endl; + gid = get_current_gid(); + } + +#ifdef USE_MEM_KVM + gid = get_gid_from_str("kmem"); +#endif + + // Create pid directory if it does not exist + pos = pidfile.find_last_of("/"); + if (pos != string::npos) + { + piddir = pidfile.substr(0, pidfile.find_last_of("/")); + + if (check_dir_exist(piddir) == 0 && pidfile.length()) + { + create_directory(piddir, 0755); + if(chown(piddir.c_str(), uid, 0)){} + } + } + + // Only change owner if we want to change the pidfile + if (pidfile.length()) + { + out.open(pidfile.c_str()); + + if (!out) + { + cout << "Could not create pid file " << pidfile << ": " << strerror(errno) << endl; + exit(1); + } + + chmod(pidfile.c_str(), 0644); + if(chown(pidfile.c_str(), uid, gid)){} + } + + // Drop root privileges now, if we were run that way + if (geteuid() == 0) + { + if (uid || gid) + { + if (setgid(gid) != 0) + cout << "Could not switch to group " << _group << ": " << + strerror(errno) << endl; + if (setuid(uid) != 0) + cout << "Could not switch to user " << _user << ": " << + strerror(errno) << endl; + } + else + { + cout << "WARNING: istatd set to run as root in istat.conf! " + "Not recommended." << endl; + } + } + else { + cout << "Ignoring server_{user,group} settings, wasn't run as root." << endl; + } + + if (_back) + { + if ((pid = fork()) > 0) + { + // cout << "Entering standalone mode with pid " << pid << endl; + exit(0); + } + + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + + sleep(1); + } + + if (pidfile.length()) + { + out << getpid() << '\n'; + out.close(); + } + + // Create UNIX socket + int unix_socket; + socklen_t length; + struct sockaddr_un local; + + if ((unix_socket = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) + { + cout << "Could not create UNIX socket file " << sockfile << ": " << strerror(errno) << endl; + exit(1); + } + + local.sun_family = AF_UNIX; + + strcpy(local.sun_path, sockfile.c_str()); + unlink(local.sun_path); + + length = offsetof(struct sockaddr_un, sun_path) + strlen(sockfile.c_str()); + + if (bind(unix_socket, (struct sockaddr *) &local, length) == -1) + { + cout << "Could not bind UNIX socket: " << strerror(errno) << endl; + exit(1); + } + + if (listen(unix_socket, 5) == -1) + { + cout << "Could not listen to UNIX socket: " << strerror(errno) << endl; + exit(1); + } +} + +void Daemon::publish(int port, int protocol, string name, string uuid, int platform) +{ +#ifdef HAVE_LIBAVAHI_CLIENT + + if(name.length() == 0 || uuid.length() == 0) + return; + + avahiPublisher.port = port; + avahiPublisher.name = name; + avahiPublisher.uuid = uuid; + avahiPublisher.platform = platform; + avahiPublisher.protocol = protocol; + avahiPublisher.publish_service(); +#endif +} + +void Daemon::destroy() +{ +#ifdef HAVE_LIBAVAHI_CLIENT + avahiPublisher.stop(); +#endif + + int ret; + ofstream out; + + ret = unlink(pidfile.c_str()); + + if (ret == -1) + { + // Empty pid file if we can't remove it + out.open(pidfile.c_str()); + + if (out.good()) + { + out << ""; + out.close(); + } + } + + unlink(sockfile.c_str()); + + exit(0); +} + +void SignalResponder::destroy() +{ + //cout << endl << "Shutting down and saving clients." << endl; + + this->stats->close(); + this->sockets->close(); + this->listener->close(); + this->unixdaemon->destroy(); +} + +void SignalResponder::on_sigint() +{ + this->destroy(); +} + +void SignalResponder::on_sigterm() +{ + this->stats->finalize(); + this->destroy(); +} + +void SignalResponder::on_sighup() +{ + cout << "Placeholder for reloading config." << endl; +} diff --git a/src/Daemon.h b/src/Daemon.h new file mode 100644 index 0000000..45c4c9e --- /dev/null +++ b/src/Daemon.h @@ -0,0 +1,74 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef _DAEMON_H +#define _DAEMON_H + +#include +#include + +#include "Socket.h" +#include "Socketset.h" +#include "Clientset.h" +#include "Stats.h" +#include "Avahi.h" + +class Daemon +{ + public: + Daemon(const std::string &_pidfile, const std::string &_sockfile) : pidfile(_pidfile), sockfile(_sockfile) {} + + void create(bool _back, const std::string &_user, const std::string &_group); + void destroy(); + void publish(int port, int protocol, std::string name, std::string uuid, int platform); + private: + std::string pidfile; + std::string sockfile; +}; + +class SignalResponder +{ + public: + SignalResponder(SocketSet *_sockets, Socket *_listener, Daemon *_unixdaemon, Stats *_stats) : listener(_listener), sockets(_sockets), unixdaemon(_unixdaemon), stats(_stats) {} + + void destroy(); + void on_sigint(); + void on_sigterm(); + void on_sighup(); + + private: + Socket * listener; + SocketSet * sockets; + Daemon * unixdaemon; + Stats * stats; +}; + +#endif diff --git a/src/Database.cpp b/src/Database.cpp new file mode 100644 index 0000000..59473ff --- /dev/null +++ b/src/Database.cpp @@ -0,0 +1,246 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "Database.h" +#include + +using namespace std; + +#ifdef USE_SQLITE + +bool Database::verify() +{ + sqlite3_stmt *statement = 0; + bool valid = true; + int rc = sqlite3_prepare_v2(_db, "PRAGMA integrity_check", -1, &statement, 0); + if(SQLITE_OK == rc) + { + int success = sqlite3_step(statement); + string value; + switch (success) { + case SQLITE_ERROR: + case SQLITE_MISUSE: + case SQLITE_DONE: + case SQLITE_BUSY: + cout << "Error verifying database" << endl; + break; + case SQLITE_CORRUPT: + cout << "Error verifying database - Corrupt" << endl; + valid = false; + break; + case SQLITE_ROW: + value = string((char *)sqlite3_column_text(statement, 0)); + + if(value == "ok") + { + cout << "Database valid" << endl; + } + else + { + cout << "Database invalid - " << value << endl; + valid = false; + } + break; + default: + break; + } + + sqlite3_finalize(statement); + } + else + { + cout << "Unable to verify database" << endl; + } + return valid; +} + +void DatabaseItem::prepare(string sql, sqlite3 *db) +{ + _statement = 0; + int rc = sqlite3_prepare_v2(db, sql.c_str(), -1, &_statement, 0); + if(SQLITE_OK != rc) + { + _statement = NULL; + return; + } +} + +void DatabaseItem::finalize() +{ + sqlite3_finalize(_statement); +} + +int DatabaseItem::query() +{ + int rc = sqlite3_step(_statement); + finalize(); + return rc; +} + +int DatabaseItem::next() +{ + int rc = sqlite3_step(_statement); + if (rc != SQLITE_ROW) + { + finalize(); + return 0; + } + + if(columnNames.empty()) + { + int columnCount = sqlite3_column_count(_statement); + for (int i = 0; i < columnCount; ++i) + { + const char* pName = sqlite3_column_name(_statement, i); + columnNames[pName] = i; + } + } + + return 1; +} + +double DatabaseItem::doubleForColumn(string column) +{ + const DatabaseColumnNames::const_iterator iIndex = columnNames.find(column); + if (iIndex == columnNames.end()) + { + return 0; + } + double value = sqlite3_column_double(_statement, (*iIndex).second); + if(isnan(value)) + value = 0; + + return value; +} + +string DatabaseItem::stringForColumn(string column) +{ + const DatabaseColumnNames::const_iterator iIndex = columnNames.find(column); + if (iIndex == columnNames.end()) + { + return 0; + } + + string value = string((char *)sqlite3_column_text(_statement, (*iIndex).second)); + return value; +} + +int DatabaseItem::executeUpdate() +{ + if(_statement == NULL) + return 0; + + int rc = sqlite3_step(_statement); + finalize(); + + return (rc == SQLITE_DONE || rc == SQLITE_OK); +} + +void Database::init() +{ + string path = string(CONFIG_PATH) + "istatserver.db"; + int rc = sqlite3_open(path.c_str(), &_db); + if(rc != SQLITE_OK) + { + cout << "Unable to open database" << endl; + enabled = 0; + } + else + { + cout << "Opened database" << endl; + enabled = 1; + } +} + +void Database::close() +{ + sqlite3_close(_db); + cout << "Closed database" << endl; +} + +DatabaseItem Database::databaseItem(string sql) +{ + DatabaseItem item; + item.prepare(sql, _db); + return item; +} + +int Database::beginTransaction() +{ + DatabaseItem query; + query.prepare("begin exclusive transaction", _db); + query.query(); + return 0; +} + +int Database::commit() +{ + DatabaseItem query; + query.prepare("commit transaction", _db); + query.query(); + return 0; +} + +int Database::tableExists(string name) +{ + stringstream sql; + sql << "select name from sqlite_master where type='table' and lower(name) = ?"; + + DatabaseItem query; + query.prepare(sql.str(), _db); + + sqlite3_bind_text(query._statement, 1, name.c_str(), -1, SQLITE_STATIC); + + if(query.next()) + { + query.finalize(); + return 1; + } + return 0; +} + +bool Database::columnExists(string column, string table) +{ + stringstream sql; + sql << "pragma table_info('" << table << "')"; + + DatabaseItem query; + query.prepare(sql.str(), _db); + + while(query.next()) + { + if(query.stringForColumn("name") == column) + return true; + } + return false; +} + +#endif diff --git a/src/Database.h b/src/Database.h new file mode 100644 index 0000000..3f28745 --- /dev/null +++ b/src/Database.h @@ -0,0 +1,79 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "config.h" +#include +#include +#include +#include +#include +#include + +#ifdef USE_SQLITE + +#include + +#ifndef _DATABASE_H +#define _DATABASE_H +class DatabaseItem +{ + private: + typedef std::map DatabaseColumnNames; + + public: + int executeUpdate(); + void prepare(std::string sql, sqlite3 *db); + int next(); + int query(); + void finalize(); + double doubleForColumn(std::string column); + std::string stringForColumn(std::string column); + sqlite3_stmt* _statement; + DatabaseColumnNames columnNames; + int result; +}; +class Database +{ + public: + sqlite3 *_db; + int enabled; + void init(); + bool verify(); + void close(); + int beginTransaction(); + int commit(); + DatabaseItem databaseItem(std::string sql); + int tableExists(std::string name); + bool columnExists(std::string column, std::string table); +}; + +#endif +#endif diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..fe89af0 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,30 @@ +SUBDIRS = + +bin_PROGRAMS = istatserver + +istatserver_SOURCES = \ + ./main.h ./main.cpp \ + ./Conf.h ./Conf.cpp \ + ./Avahi.h ./Avahi.cpp \ + ./Argument.h ./Argument.cpp \ + ./Socket.h ./Socket.cpp \ + ./Clientset.h ./Clientset.cpp \ + ./Responses.h ./Responses.cpp \ + ./Daemon.h ./Daemon.cpp \ + ./Stats.h ./Stats.cpp \ + ./Socketset.h ./Socketset.cpp \ + ./Utility.h ./Utility.cpp \ + ./Certificate.h ./Certificate.cpp \ + ./Database.h ./Database.cpp \ + ./stats/StatBase.h ./stats/StatBase.cpp\ + ./stats/StatsCPU.h ./stats/StatsCPU.cpp\ + ./stats/StatsMemory.h ./stats/StatsMemory.cpp\ + ./stats/StatsSensors.h ./stats/StatsSensors.cpp\ + ./stats/StatsLoad.h ./stats/StatsLoad.cpp\ + ./stats/StatsNetwork.h ./stats/StatsNetwork.cpp\ + ./stats/StatsDisks.h ./stats/StatsDisks.cpp\ + ./stats/StatsUptime.h ./stats/StatsUptime.cpp\ + ./stats/StatsActivity.h ./stats/StatsActivity.cpp\ + ./stats/StatsBattery.h ./stats/StatsBattery.cpp\ + ./stats/StatsProcesses.h ./stats/StatsProcesses.cpp\ + System.h diff --git a/src/Responses.cpp b/src/Responses.cpp new file mode 100644 index 0000000..90492f9 --- /dev/null +++ b/src/Responses.cpp @@ -0,0 +1,633 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include +#include +#include +#include +#include +#include "System.h" + +#include "Responses.h" +#include "Stats.h" +#include "Utility.h" + +using namespace std; + +string isr_create_header() +{ + return ""; +} + +string isr_accept_code() +{ + stringstream temp; + temp << isr_create_header() << ""; + return temp.str(); +} + +string isr_reject_code() +{ + stringstream temp; + temp << isr_create_header() << ""; + return temp.str(); +} + +string isr_serverinfo(int session, int auth, string uuid, bool historyEnabled) +{ + int history = 0; + #ifdef USE_SQLITE + if(historyEnabled == true) + history = 1; + #endif + + stringstream temp; + temp << isr_create_header() << ""; + return temp.str(); +} + +string isr_accept_connection() +{ + stringstream temp; + temp << isr_create_header() << ""; + return temp.str(); +} + +string isr_cpu_data(xmlNodePtr node, Stats *stats) +{ + #ifdef USE_CPU_NONE + return ""; + #endif + + stringstream output; + + char *identifiers = (char *)xmlGetProp(node, (const xmlChar *)"samples"); + vector identifierItems = explode(string(identifiers), "|"); + for(uint x = 0;x < identifierItems.size(); x++) + { + double sampleID = to_double(identifierItems[x].c_str()); + + deque samples; + for(size_t i = 0;i < stats->cpuStats.samples[x].size(); i++) + { + cpu_data sample = stats->cpuStats.samples[x][i]; + if (sample.sampleID > sampleID) + { + samples.push_front(sample); + } + else + break; + } + + output << "cpuStats.session << "\" id=\"" << stats->cpuStats.sampleIndex[x].sampleID << "\" threads=\"" << stats->processStats.threadCount << "\" tasks=\"" << stats->processStats.processCount << "\" samples=\"" << samples.size() << "\">"; + for(size_t i = 0;i < samples.size(); i++) + { + struct cpu_data sample = samples[i]; + output << "cpuStats.hasLpar) + output << " ent=\"" << sample.ent << "\" phys=\"" << sample.phys << "\""; + #endif + + output << ">"; + } + output << ""; + } + free(identifiers); + + return output.str(); +} + +string isr_memory_data(xmlNodePtr node, Stats *stats) +{ + #ifdef USE_MEM_NONE + return ""; + #endif + + stringstream output; + + char *identifiers = (char *)xmlGetProp(node, (const xmlChar *)"samples"); + vector identifierItems = explode(string(identifiers), "|"); + for(uint x = 0;x < identifierItems.size(); x++) + { + double sampleID = to_double(identifierItems[x].c_str()); + + deque samples; + for(size_t i = 0;i < stats->memoryStats.samples[x].size(); i++) + { + mem_data sample = stats->memoryStats.samples[x][i]; + if (sample.sampleID > sampleID) + { + samples.push_front(sample); + } + else + break; + } + + output << "memoryStats.session << "\" id=\"" << stats->memoryStats.sampleIndex[x].sampleID << "\" samples=\"" << samples.size() << "\">"; + for(size_t i = 0;i < samples.size(); i++) + { + struct mem_data mem = samples[i]; + output << "= 0) + output << " file=\"" << mem.values[memory_value_file] << "\""; + if(mem.values[memory_value_ex] >= 0) + output << " ex=\"" << mem.values[memory_value_ex] << "\""; + if(mem.values[memory_value_buffer] >= 0) + output << " buf=\"" << mem.values[memory_value_buffer] << "\""; + if(mem.values[memory_value_used] >= 0) + output << " u=\"" << mem.values[memory_value_used] << "\""; + if(mem.values[memory_value_wired] >= 0) + output << " w=\"" << mem.values[memory_value_wired] << "\""; + if(mem.values[memory_value_cached] >= 0) + output << " ca=\"" << mem.values[memory_value_cached] << "\""; + if(mem.values[memory_value_active] >= 0) + output << " a=\"" << mem.values[memory_value_active] << "\""; + if(mem.values[memory_value_inactive] >= 0) + output << " i=\"" << mem.values[memory_value_inactive] << "\""; + if(mem.values[memory_value_free] >= 0) + output << " f=\"" << mem.values[memory_value_free] << "\""; + if(mem.values[memory_value_total] >= 0) + output << " t=\"" << mem.values[memory_value_total] << "\""; + if(mem.values[memory_value_swapused] >= 0) + output << " su=\"" << mem.values[memory_value_swapused] << "\""; + if(mem.values[memory_value_swaptotal] >= 0) + output << " st=\"" << mem.values[memory_value_swaptotal] << "\""; + if(mem.values[memory_value_swapin] >= 0) + output << " pi=\"" << mem.values[memory_value_swapin] << "\""; + if(mem.values[memory_value_swapin] >= 0) + output << " po=\"" << mem.values[memory_value_swapout] << "\""; + if(mem.values[memory_value_virtualtotal] >= 0) + output << " vt=\"" << mem.values[memory_value_virtualtotal] << "\""; + if(mem.values[memory_value_virtualactive] >= 0) + output << " va=\"" << mem.values[memory_value_virtualactive] << "\""; + + output << ">"; + } + output << ""; + } + free(identifiers); + + return output.str(); +} + +string keyForIndex(string uuid, int index) +{ + stringstream skey; + skey << index << ":" << uuid; + string key = skey.str(); + return key; +} + +string isr_network_data(int index, long sampleID, StatsNetwork stats, vector keys, vector *added) +{ + stringstream output; + output << ""; + + for(size_t itemindex = 0;itemindex < stats._items.size(); itemindex++) + { + network_info item = stats._items[itemindex]; + if(!item.active) + continue; + + if(!shouldAddKey(index, item.device, keys, added)) + continue; + + deque samples; + for(size_t i = 0;i < item.samples[index].size(); i++) + { + net_data sample = item.samples[index][i]; + if (sample.sampleID > sampleID) + { + samples.push_front(sample); + } + else + break; + } + + output << " 0) + addresses += ","; + addresses += item.addresses[i]; + } + output << " name=\"" << item.device << "\" ip=\"" << addresses << "\" d=\"" << item.last_down << "\" u=\"" << item.last_up << "\""; + } + + output << ">"; + + for(size_t i = 0;i < samples.size(); i++) + { + struct net_data sample = samples[i]; + output << ""; + } + output << ""; + } + output << ""; + + return output.str(); +} + +bool shouldAddKey(int index, string key, vector keys, vector *added) +{ + bool add = true; + string k = keyForIndex(key, index); + + for(size_t i = 0;i < added->size(); i++) + { + if(added->at(i) == k) + { + add = false; + break; + } + } + if(add == true && keys.size() > 0) + { + add = false; + for(size_t i = 0;i < keys.size(); i++) + { + if(keys[i] == key) + { + add = true; + break; + } + } + } + if(add == true) + { + added->push_back(k); + } + + return add; +} + +string isr_multiple_data(xmlNodePtr node, Stats *stats) +{ + stringstream output; + vector addedKeys; + + char *type = (char *)xmlGetProp(node, (const xmlChar *)"type"); + xmlNodePtr child = node->children; + while (child){ + char *identifiers = (char *)xmlGetProp(child, (const xmlChar *)"samples"); + char *keys = (char *)xmlGetProp(child, (const xmlChar *)"keys"); + + vector identifierItems = explode(string(identifiers), "|"); + vector keyItems; + if(keys != NULL) + keyItems = explode(string(keys), "|"); + + for(uint x = 0;x < identifierItems.size(); x++) + { + double sampleID = to_double(identifierItems[x].c_str()); + + if(strcmp(type, "network") == 0) + output << isr_network_data(x, sampleID, stats->networkStats, keyItems, &addedKeys); + else if(strcmp(type, "diskactivity") == 0) + output << isr_activity_data(x, sampleID, stats->activityStats, keyItems, &addedKeys); + else if(strcmp(type, "sensors") == 0) + output << isr_sensor_data(x, sampleID, stats->sensorStats, keyItems, &addedKeys); + else if(strcmp(type, "disks") == 0) + output << isr_disk_data(x, sampleID, stats->diskStats, keyItems, &addedKeys); + else if(strcmp(type, "processes") == 0) + output << isr_process_data(x, sampleID, stats->processStats, keyItems, &addedKeys); + else if(strcmp(type, "battery") == 0) + output << isr_battery_data(x, sampleID, stats->batteryStats, keyItems, &addedKeys); + } + + if(keys != NULL) + free(keys); + + free(identifiers); + child = child->next; + } + free(type); + return output.str(); +} + +string isr_activity_data(int index, long sampleID, StatsActivity stats, vector keys, vector *added) +{ + stringstream output; + output << ""; + + for(size_t itemindex = 0;itemindex < stats._items.size(); itemindex++) + { + activity_info item = stats._items[itemindex]; + if(!item.active) + continue; + + if(!shouldAddKey(index, item.device, keys, added)) + continue; + + deque samples; + for(size_t i = 0;i < item.samples[index].size(); i++) + { + activity_data sample = item.samples[index][i]; + if (sample.sampleID > sampleID) + { + samples.push_front(sample); + } + else + break; + } + + output << " 0) + { + output << " mounts=\""; + + unsigned int x; + for(x=0;x 0) + output << ","; + output << item.mounts[x]; + + } + output << "\""; + } + } + + output << ">"; + + for(size_t i = 0;i < samples.size(); i++) + { + struct activity_data sample = samples[i]; + output << ""; + } + output << ""; + } + output << ""; + + return output.str(); +} + +string isr_disk_data(int index, long sampleID, StatsDisks stats, vector keys, vector *added) +{ + stringstream output; + output << ""; + + for(size_t itemindex = 0;itemindex < stats._items.size(); itemindex++) + { + disk_info item = stats._items[itemindex]; + if(!item.active) + continue; + + if(!shouldAddKey(index, item.key, keys, added)) + continue; + + deque samples; + for(size_t i = 0;i < item.samples[index].size(); i++) + { + disk_data sample = item.samples[index][i]; + if (sample.sampleID > sampleID) + { + samples.push_front(sample); + } + else + break; + } + + output << ""; + for(size_t i = 0;i < samples.size(); i++) + { + struct disk_data sample = samples[i]; + output << ""; + } + output << ""; + } + output << ""; + + return output.str(); +} + +string isr_uptime_data(long uptime) +{ + #ifdef USE_UPTIME_NONE + return ""; + #endif + + stringstream temp; + temp << ""; + return temp.str(); +} + +string isr_loadavg_data(xmlNodePtr node, Stats *stats) +{ + #ifdef USE_LOAD_NONE + return ""; + #endif + + stringstream output; + + char *identifiers = (char *)xmlGetProp(node, (const xmlChar *)"samples"); + vector identifierItems = explode(string(identifiers), "|"); + for(uint x = 0;x < identifierItems.size(); x++) + { + double sampleID = to_double(identifierItems[x].c_str()); + + deque samples; + for(size_t i = 0;i < stats->loadStats.samples[x].size(); i++) + { + load_data sample = stats->loadStats.samples[x][i]; + if (sample.sampleID > sampleID) + { + samples.push_front(sample); + } + else + break; + } + + output << "loadStats.session << "\" id=\"" << stats->loadStats.sampleIndex[x].sampleID << "\" samples=\"" << samples.size() << "\">"; + for(size_t i = 0;i < samples.size(); i++) + { + load_data sample = samples[i]; + output << ""; + } + output << ""; + } + free(identifiers); + + return output.str(); +} + +string isr_sensor_data(int index, long sampleID, StatsSensors stats, vector keys, vector *added) +{ + stringstream output; + output << ""; + + for(size_t itemindex = 0;itemindex < stats._items.size(); itemindex++) + { + sensor_info item = stats._items[itemindex]; + if(!shouldAddKey(index, item.key, keys, added)) + continue; + + deque samples; + for(size_t i = 0;i < item.samples[index].size(); i++) + { + sensor_data sample = item.samples[index][i]; + if (sample.sampleID > sampleID) + { + samples.push_front(sample); + } + else + break; + } + + output << ""; + for(size_t i = 0;i < samples.size(); i++) + { + struct sensor_data sample = samples[i]; + output << ""; + } + output << ""; + } + output << ""; + + return output.str(); +} + +string isr_battery_data(int index, long sampleID, StatsBattery stats, vector keys, vector *added) +{ + stringstream output; + output << ""; + + for(size_t itemindex = 0;itemindex < stats._items.size(); itemindex++) + { + battery_info item = stats._items[itemindex]; + if(!shouldAddKey(index, item.key, keys, added)) + continue; + + output << ""; + output << ""; + } + output << ""; + + return output.str(); +} + +bool sortProcessesCPU (process_info i,process_info j) { return (j.cpu keys, vector *added) +{ + vector _history; + + if(stats._items.size() > 0) + { + for (vector::iterator cur = stats._items.begin(); cur != stats._items.end(); ++cur) + { + process_info temp = *cur; + _history.push_back(temp); + } + } + + stringstream output; + + if(_history.size() > 0) + { + output << ""; + + if(shouldAddKey(0, "cpu", keys, added)) + { + std::sort (_history.begin(), _history.end(), sortProcessesCPU); + int count = 0; + + for (vector::iterator cur = _history.begin(); cur != _history.end(); ++cur) + { + output << "pid << "\" c=\"" << cur->cpu << "\" name=\"" << cur->name << "\">"; + count++; + if(count == 20) + break; + } + + } + + if(shouldAddKey(0, "memory", keys, added)) + { + int count = 0; + std::sort (_history.begin(), _history.end(), sortProcessesMemory); + + for (vector::iterator cur = _history.begin(); cur != _history.end(); ++cur) + { + output << "pid << "\" m=\"" << cur->memory << "\" name=\"" << cur->name << "\">"; + count++; + if(count == 20) + break; + } + } + + /* + if(keys.find("disks") != std::string::npos) + { + int count = 0; + std::sort (_history->begin(), _history->end(), sortProcessesIORead); + + for (vector::iterator cur = _history->begin(); cur != _history->end(); ++cur) + { + temp << "pid << "\" r=\"" << cur->io_read << "\" w=\"" << cur->io_write << "\" name=\"" << cur->name << "\">"; + count++; + if(count == 10) + break; + } + + count = 0; + std::sort (_history->begin(), _history->end(), sortProcessesIOWrite); + + for (vector::iterator cur = _history->begin(); cur != _history->end(); ++cur) + { + temp << "pid << "\" r=\"" << cur->io_read << "\" w=\"" << cur->io_write << "\" name=\"" << cur->name << "\">"; + count++; + if(count == 10) + break; + } + }*/ + + output << ""; + } else { + output << ""; + } + + + return output.str(); +} diff --git a/src/Responses.h b/src/Responses.h new file mode 100644 index 0000000..1c6ce03 --- /dev/null +++ b/src/Responses.h @@ -0,0 +1,68 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef _RESPONSES_H +#define _RESPONSES_H + +#include "config.h" +#include +#include +#include +#include +#include + +#include "Stats.h" +#include "System.h" + +std::string isr_create_header(); +std::string isr_accept_code(); +std::string isr_reject_code(); +std::string isr_accept_connection(); +std::string isr_serverinfo(int session, int auth, std::string uuid, bool historyEnabled); + +bool shouldAddKey(int index, std::string key, std::vector keys, std::vector *added); +std::string keyForIndex(std::string uuid, int index); + +std::string isr_multiple_data(xmlNodePtr node, Stats *stats); +std::string isr_cpu_data(xmlNodePtr node, Stats *stats); +std::string isr_network_data(int index, long sampleID, StatsNetwork stats, std::vector keys, std::vector *added); +std::string isr_disk_data(int index, long sampleID, StatsDisks stats, std::vector keys, std::vector *added); +std::string isr_uptime_data(long uptime); +std::string isr_loadavg_data(xmlNodePtr node, Stats *stats); +std::string isr_memory_data(xmlNodePtr node, Stats *stats); +std::string isr_fan_data(std::vector *_data, long _init); +std::string isr_temp_data(std::vector *_data, long _init); +std::string isr_sensor_data(int index, long sampleID, StatsSensors stats, std::vector keys, std::vector *added); +std::string isr_activity_data(int index, long sampleID, StatsActivity stats, std::vector keys, std::vector *added); +std::string isr_battery_data(int index, long sampleID, StatsBattery stats, std::vector keys, std::vector *added); +std::string isr_process_data(int index, long sampleID, StatsProcesses stats, std::vector keys, std::vector *added); + +#endif diff --git a/src/Socket.cpp b/src/Socket.cpp new file mode 100644 index 0000000..1317fec --- /dev/null +++ b/src/Socket.cpp @@ -0,0 +1,557 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef HAVE_NETINET_IN_H +#include +#endif + +#ifdef HAVE_ALLOCA_H +# include +#endif + +#include +#include + +#ifdef HAVE_LIBZLIB +#include +#endif + +#include "Utility.h" +#include "Socket.h" + +using namespace std; + + +#ifdef HAVE_LIBZLIB +std::string compress_string(const std::string& str, int compressionlevel = Z_BEST_COMPRESSION) +{ + z_stream zs; // z_stream is zlib's control structure + memset(&zs, 0, sizeof(zs)); + + if (deflateInit(&zs, compressionlevel) != Z_OK) + { + return ""; + } + + zs.next_in = (Bytef*)str.data(); + zs.avail_in = str.size(); // set the z_stream's input + + int ret; + char outbuffer[32768]; + std::string outstring; + + // retrieve the compressed bytes blockwise + do { + zs.next_out = reinterpret_cast(outbuffer); + zs.avail_out = sizeof(outbuffer); + + ret = deflate(&zs, Z_FINISH); + + if (outstring.size() < zs.total_out) { + // append the block to the output string + outstring.append(outbuffer, + zs.total_out - outstring.size()); + } + } while (ret == Z_OK); + + deflateEnd(&zs); + + if (ret != Z_STREAM_END) { // an error occurred that was not EOF + return ""; + } + + return outstring; +} +#endif + +int Socket::listen() +{ + int yes = 1; + hostent * host = NULL; + + if ((socket = ::socket(AF_INET, SOCK_STREAM, 0)) == -1) + { + cout << "Could not create socket: " << strerror(errno) << endl; + return 0; + } + + setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, (const char *) &yes, sizeof(yes)); + + struct timeval timeout; + timeout.tv_sec = 10; + timeout.tv_usec = 0; + + if (setsockopt (socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout)) < 0) + cout << "setsockopt failed" << endl; + + if (setsockopt (socket, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof(timeout)) < 0) + cout << "setsockopt failed" << endl; + + sockaddr_in myAddress; + + memset(&myAddress, 0, sizeof(sockaddr_in)); + + if (!(host = gethostbyname(address.c_str()))) + myAddress.sin_addr.s_addr = inet_addr(address.c_str()); + else + myAddress.sin_addr.s_addr = ((in_addr *) host->h_addr)->s_addr; + + myAddress.sin_family = AF_INET; + myAddress.sin_port = htons(port); + + if (::bind(socket, (sockaddr *) &myAddress, sizeof(myAddress)) == -1) + { + cout << "Could not bind socket: " << strerror(errno) << endl; + return 0; + } + + // cout << "(" << address << ":" << port << ") Socket binded." << endl; + + if (::listen(socket, 5) == -1) + { + cout << "Could not listen on socket: " << strerror(errno) << endl; + return 0; + } + + // cout << "(" << address << ":" << port << ") Socket initialized." << endl; + return 1; +} + + +Socket Socket::accept() +{ + int theirSocket; + sockaddr_in theirAddress; + socklen_t size = sizeof(sockaddr_in); + + if ((theirSocket = ::accept(socket, (sockaddr *) &theirAddress, &size)) == -1) + { + cout << "Could not accept connection: " << strerror(errno) << endl; + } + + Socket socket(theirSocket, inet_ntoa(theirAddress.sin_addr), ntohs(theirAddress.sin_port)); + socket.lastRequest = get_current_time(); + socket.debugLogging = false; + socket.readbuf = ""; + socket._session = _session; + socket._serverUUID = _serverUUID; + socket._sslEnabled = _sslEnabled; + socket.sslContext = sslContext; + + cout << socket.get_description() << " New connection accepted. " << endl; + + fcntl(socket.get_id(), F_SETFL, fcntl(socket.get_id(), F_GETFL, 0) | O_NONBLOCK); + +/* struct timeval timeout; + timeout.tv_sec = 10; + timeout.tv_usec = 0; + + if (setsockopt (theirSocket, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout)) < 0) + cout << "setsockopt failed" << endl; + + if (setsockopt (theirSocket, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof(timeout)) < 0) + cout << "setsockopt failed" << endl; + */ + + return socket; +} + +string Socket::get_description() +{ + stringstream desc; + desc << get_current_time_string() << " - (" << get_address() << ":" << get_port(); + if(_name.length() > 0) + desc << " - " << _name; + desc << ")"; + return desc.str(); +} + +int Socket::send(string data) +{ +// cout << "Writing- " << data << endl; + int r = 0; + + if(secure == true) + { + while(data.size() > 0) + { + ERR_clear_error(); + + unsigned int limit = 8192; + + if(data.size() > limit) + { + if(debugLogging) + cout << get_description() << " Writing " << limit << " bytes" << endl; + r = SSL_write(ssl, data.substr(0, limit).c_str(), limit); + } + else + { + if(debugLogging) + cout << get_description() << " Writing " << data.size() << " bytes" << endl; + r = SSL_write(ssl, data.c_str(), data.size()); + } + + if(debugLogging) + cout << get_description() << " Write result " << r << ", ssl error " << SSL_get_error (ssl,r) << ", errorno " << errno << ", err_get_error " << ERR_get_error() << endl; + + switch ( SSL_get_error (ssl,r) ){ + case SSL_ERROR_NONE: + data = data.substr(r, readbuf.size() - r); + break; + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + break; + case SSL_ERROR_ZERO_RETURN: + cout << get_description() << " SSL connection closed by peer" << endl; + return 0; + break; + default: + // cout << get_description() << " SSL error - " << ERR_error_string( SSL_get_error(ssl, r), NULL ) << endl; + cout << get_description() << " SSL write error - " << ERR_error_string( ERR_get_error(), NULL ) << endl; + return 0; + break; + } + } + } + else + { + if ((r = ::send(socket, data.c_str(), data.size(), 0)) == -1) + { + cout << get_description() << " Could not send data: " << strerror(errno) << endl; + return 0; + } + } + + // cout << "(" << address << ":" << port << ") Data sent: " << _data << endl; + + return r; +} + + +int Socket::receive(ClientSet * _clients, Config * _config, Stats * _stats) +{ + char buf[1024]; + int len = 0; + + if(secure == true) + { + do { + memset(buf, 0, 1024); + ERR_clear_error(); + int r = SSL_read (ssl, buf, 1024); + + if(debugLogging) + cout << get_description() << " Read result " << r << ", ssl error " << SSL_get_error (ssl,r) << ", errorno " << errno << ", err_get_error " << ERR_get_error() << endl; + + switch ( SSL_get_error (ssl,r) ){ + case SSL_ERROR_NONE: + len += r; + readbuf += buf; +// if(debugLogging) +// cout << "Read bytes " << buf << endl; + + break; + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + break; + case SSL_ERROR_ZERO_RETURN: + cout << get_description() << " SSL connection closed by peer" << endl; + return 0; + break; + default: +// cout << get_description() << " SSL read error - " << ERR_error_string( SSL_get_error(ssl, r), NULL ) << endl; + cout << get_description() << " SSL read error - " << ERR_error_string( ERR_get_error(), NULL ) << endl; + return 0; + break; + } + } + while(SSL_pending (ssl) > 0); + } + else + { + while(1) + { + memset(buf, 0, 1024); + #ifdef MSG_DONTWAIT + int ret = recv(socket, buf, 1024, MSG_DONTWAIT); + #else + int ret = recv(socket, buf, 1024, MSG_NONBLOCK); + #endif + if(ret < 0 && (errno == EWOULDBLOCK || errno == EAGAIN)) + { + break; + } + else if(ret <= 0) + { + return 0; + } + else + { + len += ret; + readbuf += buf; + } + } + } + + size_t position = readbuf.find(""); + if(position != std::string::npos) + { + lastRequest = get_current_time(); + string xml = readbuf.substr(0, position); +// if(debugLogging) +// cout << get_description() << " Read xml data " << xml << endl; + parse(readbuf, _clients, _config, _stats); + position += 6; + readbuf = readbuf.substr(position, readbuf.size() - position); + } + + return len; +} + +void Socket::close() +{ +} + +int Socket::startSSL() +{ + ssl = SSL_new(sslContext); + SSL_set_fd(ssl, socket); + SSL_set_accept_state(ssl); + int code = SSL_accept(ssl); + + if ( code != 1){ + int error = SSL_get_error(ssl, code); + while (code != 1 && (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE)) + { + code = SSL_accept(ssl); + error = SSL_get_error(ssl, code); + } + if(code != 1) + { + cout << get_description() << " Error starting SSL: " << ERR_error_string( SSL_get_error(ssl, code), NULL ) <get("server_code", "00000"); + string cf_server_reject_delay = _config->get("server_reject_delay", "3"); + + + xmlDocPtr doc; + xmlNodePtr cur; + xmlParserCtxtPtr ctxt = xmlNewParserCtxt(); + + doc = xmlCtxtReadMemory(ctxt, _data.c_str(), _data.length(), NULL, NULL, 0); + + if ((cur = xmlDocGetRootElement(doc)) != NULL) + { + if (xmlStrEqual(cur->name, BAD_CAST "isr")) + { + char *type = (char *)xmlGetProp(cur, (const xmlChar *)"type"); + int code = atoi(type); + free(type); + + if(code == 100){ + char *protocol = (char *)xmlGetProp(cur, (const xmlChar *)"protocol"); + _protocol = atoi(protocol); + + send(isr_accept_connection()); + free(protocol); + } + if(code == 101){ + char *uuid = (char *)xmlGetProp(cur, (const xmlChar *)"uuid"); + char *name = (char *)xmlGetProp(cur, (const xmlChar *)"name"); + char *version = (char *)xmlGetProp(cur, (const xmlChar *)"version"); + + _uuid = string(uuid); + _name = string(name); + + int auth = 1; + if (_clients->is_authenticated(_uuid)) + auth = 0; + else { + int code = atoi(cf_server_code.c_str()); + std::ostringstream stm; + stm << code; + string stringcode = stm.str(); + + // check if code is a 5 digit number + if(cf_server_code != stringcode || cf_server_code.length() != 5){ + auth = 2; // text based passcode + } + } + + send(isr_serverinfo(_session, auth, _serverUUID, _stats->historyEnabled)); + + free(uuid); + free(name); + free(version); + } + + if(code == 102){ + char *code = (char *)xmlGetProp(cur, (const xmlChar *)"code"); + if (code == cf_server_code) + { + _clients->authenticate(_uuid); + send(isr_accept_code()); + } + else { + send(isr_reject_code()); + } + free(code); + } + + if(code == 103){ + pthread_mutex_lock(&_stats->lock); + temp << isr_create_header() << ""; + if (_clients->is_authenticated(_uuid)) + { + xmlNodePtr child = cur->children; + while (child){ + char *type = (char *)xmlGetProp(child, (const xmlChar *)"type"); + if(type == NULL) + { + child = child->next; + continue; + } + if(strcmp(type, "cpu") == 0) + { + temp << isr_cpu_data(child, _stats); + } + if(strcmp(type, "memory") == 0) + { + temp << isr_memory_data(child, _stats); + } + if(strcmp(type, "load") == 0) + { + temp << isr_loadavg_data(child, _stats); + } + if(strcmp(type, "network") == 0) + { + #ifndef USE_NET_NONE + temp << isr_multiple_data(child, _stats); + #endif + } + if(strcmp(type, "diskactivity") == 0) + { + #ifndef USE_ACTIVITY_NONE + temp << isr_multiple_data(child, _stats); + #endif + } + if(strcmp(type, "processes") == 0) + { + #ifndef USE_PROCESSES_NONE + temp << isr_multiple_data(child, _stats); + #endif + } + if(strcmp(type, "disks") == 0) + { + #ifndef USE_DISK_NONE + temp << isr_multiple_data(child, _stats); + #endif + } + if(strcmp(type, "sensors") == 0) + { + temp << isr_multiple_data(child, _stats); + } + if(strcmp(type, "uptime") == 0) + { + temp << isr_uptime_data(_stats->uptime()); + } + if(strcmp(type, "battery") == 0) + { + #ifndef USE_BATTERY_NONE + temp << isr_multiple_data(child, _stats); + #endif + } + + free(type); + child = child->next; + } + } + pthread_mutex_unlock(&_stats->lock); + + temp << ""; + + string data = temp.str(); + string compressedTag = ""; + + #ifdef HAVE_LIBZLIB + string compressedData = compress_string(data, Z_BEST_COMPRESSION); + if(compressedData.size() > 0) + { + compressedTag = " c=\"1\""; + data = compressedData; + } + #endif + + stringstream temp2; + temp2 << ""; + send(temp2.str()); + send(data); + } + } // Unknown element recived after header + } // Failed to read header + + xmlFreeDoc(doc); + xmlFreeParserCtxt(ctxt); +} diff --git a/src/Socket.h b/src/Socket.h new file mode 100644 index 0000000..9e2fe51 --- /dev/null +++ b/src/Socket.h @@ -0,0 +1,93 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef _SOCKET_H +#define _SOCKET_H + +#include "config.h" + +#include +#include +#include +#include + +#include "Clientset.h" +#include "Conf.h" +#include "Stats.h" +#include "Responses.h" + +#include "Certificate.h" + +class Socket +{ + public: + Socket(const std::string & _address, unsigned int _port) : listener(true), port(_port), address(_address) {} + Socket(int _socket, std::string _address, unsigned int _port) : secure(false), socket(_socket), listener(false), port(_port), address(_address) {} + + int get_id() { return socket; } + bool get_listener() { return listener; } + unsigned int get_port() { return port; } + std::string get_address() { return address; } + std::string get_description(); + int send(std::string data); + int receive(ClientSet * _clients, Config * _config, Stats * _stats); + Socket accept(); + int listen(); + + void initClient(int s); + void startReading(); + void close(); + + int startSSL(); + SSL *ssl; + SSL_CTX *sslContext; + + std::string _serverUUID; + std::string _uuid; + std::string _name; + long _session; + int _protocol; + int _sslEnabled; + bool isServer; + double lastRequest; + std::string readbuf; + bool secure; + bool debugLogging; + + private: + int socket; + bool listener; + unsigned int port; + std::string address; + void parse(std::string _data, ClientSet * _clients, Config * _config, Stats * _stats); +}; + +#endif diff --git a/src/Socketset.cpp b/src/Socketset.cpp new file mode 100644 index 0000000..a4c29d4 --- /dev/null +++ b/src/Socketset.cpp @@ -0,0 +1,157 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include "Socketset.h" + +using namespace std; + +SocketSet::SocketSet() +{ + highest = 0; + + FD_ZERO(&socketset); +} + +void SocketSet::operator += (Socket & _socket) +{ + if (_socket.get_id() > highest) + highest = _socket.get_id(); + + connections.push_back(_socket); + FD_SET(_socket.get_id(), &socketset); +} + +void SocketSet::operator -= (Socket & _socket) +{ + cout << _socket.get_description() << " Disconnected." << endl; + + FD_CLR(_socket.get_id(), &socketset); + + if(fcntl(_socket.get_id(), F_GETFD) != -1) + ::close(_socket.get_id()); + + for (std::vector::iterator eraser = connections.begin(); eraser != connections.end(); ++eraser) + { + if ((*eraser).get_id() == _socket.get_id()) + { + connections.erase(eraser); + + break; + } + } + + if (_socket.get_id() == highest) + { + highest = 0; + + for (std::vector::iterator higher = connections.begin(); higher != connections.end(); ++higher) + { + if (highest < (*higher).get_id()) + { + highest = (*higher).get_id(); + } + } + } +} + +bool SocketSet::operator == (Socket & _socket) +{ + return FD_ISSET(_socket.get_id(), &readyset); +} + +Socket & SocketSet::get_ready() +{ + for (vector::iterator ready = connections.begin(); ready != connections.end(); ++ready) + { + if (FD_ISSET((*ready).get_id(), &readyset)) + { + return *ready; + } + } + + return connections.front(); +} + +Socket & SocketSet::get_socket(int _socket) +{ + for (vector::iterator ready = connections.begin(); ready != connections.end(); ++ready) + { + if ((*ready).get_id() == _socket) + { + return *ready; + } + } + + return connections.front(); +} + +int SocketSet::get_status(int _timeout) +{ + int result; + timeval timeout; + + timeout.tv_sec = _timeout; + timeout.tv_usec = 0; + + readyset = socketset; + + if (_timeout > 0) + result = select(highest + 1, &readyset, NULL, NULL, &timeout); + else + result = select(highest + 1, &readyset, NULL, NULL, NULL); + + return result; +} + +void SocketSet::send(const string & _data) +{ + for (vector::iterator socket = connections.begin(); socket != connections.end(); ++socket) + { + if (!(*socket).get_listener()) (*socket).send(_data); + } +} + +void SocketSet::close() +{ + for (vector::iterator socket = connections.begin(); socket != connections.end(); ++socket) + { + ::close((*socket).get_id()); + } +} diff --git a/src/Socketset.h b/src/Socketset.h new file mode 100644 index 0000000..e2058bb --- /dev/null +++ b/src/Socketset.h @@ -0,0 +1,64 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef _SOCKETSET_H +#define _SOCKETSET_H + +#include +#include +#include +#include + +#include "Socket.h" + +class SocketSet +{ + public: + SocketSet(); + + void operator += (Socket & _socket); + void operator -= (Socket & _socket); + bool operator == (Socket & _socket); + + Socket & get_ready(); + Socket & get_socket(int _socket); + int get_status(int _timeout = 0); + void send(const std::string & _data); + void close(); + std::vector connections; + + private: + int highest; + fd_set readyset; + fd_set socketset; +}; + +#endif diff --git a/src/Stats.cpp b/src/Stats.cpp new file mode 100644 index 0000000..895b70b --- /dev/null +++ b/src/Stats.cpp @@ -0,0 +1,435 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include +#include +#include +#include +#include +#include "Utility.h" + +#include "Stats.h" +#include "System.h" +#include +#include + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +using namespace std; + +void* start_stats_thread(void*); +void* start_stats_thread(void*a) +{ + Stats *s = static_cast(a); + s->startStats(); + return 0; +} + +void Stats::finalize() +{ + #ifdef USE_SQLITE + if(historyEnabled == true) + { + _database.beginTransaction(); + insertDatabaseItems(&cpuStats); + insertDatabaseItems(&loadStats); + insertDatabaseItems(&memoryStats); + insertDatabaseItems(&activityStats); + insertDatabaseItems(&sensorStats); + insertDatabaseItems(&networkStats); + insertDatabaseItems(&diskStats); + _database.commit(); + } + #endif +} + +void Stats::close() +{ + #ifdef USE_SQLITE + if(historyEnabled == true) + { + sqlite3_interrupt(_database._db); + } + #endif +} + +void Stats::startStats() +{ +#ifdef HAVE_LIBKSTAT + if(NULL == (ksh = kstat_open())) + { + cout << "unable to open kstat " << strerror(errno) << endl; + return; + } else { + cpuStats.ksh = ksh; + memoryStats.ksh = ksh; + loadStats.ksh = ksh; + networkStats.ksh = ksh; + uptimeStats.ksh = ksh; + activityStats.ksh = ksh; + } +#endif + + #ifdef USE_SQLITE + if(historyEnabled == true) + { + _database.init(); + _database.verify(); + cpuStats._database = _database; + loadStats._database = _database; + memoryStats._database = _database; + activityStats._database = _database; + sensorStats._database = _database; + networkStats._database = _database; + diskStats._database = _database; + batteryStats._database = _database; + } + cpuStats.historyEnabled = historyEnabled; + loadStats.historyEnabled = historyEnabled; + memoryStats.historyEnabled = historyEnabled; + activityStats.historyEnabled = historyEnabled; + sensorStats.historyEnabled = historyEnabled; + networkStats.historyEnabled = historyEnabled; + diskStats.historyEnabled = historyEnabled; + batteryStats.historyEnabled = historyEnabled; + #endif + + cpuStats.debugLogging = debugLogging; + loadStats.debugLogging = debugLogging; + memoryStats.debugLogging = debugLogging; + activityStats.debugLogging = debugLogging; + sensorStats.debugLogging = debugLogging; + networkStats.debugLogging = debugLogging; + diskStats.debugLogging = debugLogging; + batteryStats.debugLogging = debugLogging; + + if(debugLogging) + cout << "Initiating cpu" << endl; + cpuStats.init(); + + if(debugLogging) + cout << "Initiating load" << endl; + loadStats.init(); + + if(debugLogging) + cout << "Initiating memory" << endl; + memoryStats.init(); + + if(debugLogging) + cout << "Initiating activity" << endl; + activityStats.init(); + + if(debugLogging) + cout << "Initiating sensors" << endl; + sensorStats.init(); + + if(debugLogging) + cout << "Initiating network" << endl; + networkStats.init(); + + if(debugLogging) + cout << "Initiating disks" << endl; + diskStats.init(); + + if(debugLogging) + cout << "Initiating battery" << endl; + batteryStats.init(); + + if(debugLogging) + cout << "Initiating processes" << endl; + processStats.init(); + + if(debugLogging) + cout << "Init complete" << endl; + + processStats.aixEntitlement = cpuStats.aixEntitlement; + + if(debugLogging) + cout << "Updating cpu" << endl; + cpuStats.update(sampleID); + + if(debugLogging) + cout << "Updating memory" << endl; + memoryStats.update(sampleID); + + if(debugLogging) + cout << "Updating load" << endl; + loadStats.update(sampleID); + + if(debugLogging) + cout << "Updating sensors" << endl; + sensorStats.update(sampleID); + + if(debugLogging) + cout << "Updating network" << endl; + networkStats.update(sampleID); + + if(debugLogging) + cout << "Updating disks" << endl; + diskStats.update(sampleID); + + if(debugLogging) + cout << "Updating activity" << endl; + activityStats.update(sampleID); + + if(debugLogging) + cout << "Updating battery" << endl; + batteryStats.update(sampleID); + + if(debugLogging) + cout << "Updating processes" << endl; + processStats.update(sampleID, 0); + + activityStats.ready = 1; + sensorStats.ready = 1; + networkStats.ready = 1; + diskStats.ready = 1; + batteryStats.ready = 1; + + if(debugLogging) + cout << "Updating network addresses" << endl; + networkStats.updateAddresses(); + + if(debugLogging) + cout << "Initital loading complete" << endl; + + + updateTime = get_current_time(); + double next = ceil(updateTime) - updateTime; + updateTime = ceil(updateTime); + nextIPAddressTime = 0;//updateTime + 600; + double nextQueueTime = updateTime + 60; + + updateNextTimes(updateTime); + usleep(next * 1000000); + + while(1){ + pthread_mutex_lock(&lock); + update_system_stats(); + if(get_current_time() >= nextIPAddressTime) + { + nextIPAddressTime = updateTime + 600; + networkStats.updateAddresses(); + } + pthread_mutex_unlock(&lock); + + double now = get_current_time(); + double next = updateTime + 1; + double interval = next - now; + if(interval < 0) + { + interval = ceil(now) < now; + next = ceil(now); + } + else if(interval > 2) + { + double n = next; + while(n < now) + { + cpuStats.tickSample(); + loadStats.tickSample(); + memoryStats.tickSample(); + activityStats.tickSample(); + sensorStats.tickSample(); + networkStats.tickSample(); + diskStats.tickSample(); + batteryStats.tickSample(); + n += 1; + } + interval = ceil(now) < now; + next = ceil(now); + } + + updateTime = next; + + if(get_current_time() >= nextQueueTime) + { + #ifdef USE_SQLITE + if(debugLogging) + cout << "Running database queue" << endl; + cpuStats.removeOldSamples(); + loadStats.removeOldSamples(); + memoryStats.removeOldSamples(); + activityStats.removeOldSamples(); + sensorStats.removeOldSamples(); + networkStats.removeOldSamples(); + diskStats.removeOldSamples(); + + _database.beginTransaction(); + insertDatabaseItems(&cpuStats); + insertDatabaseItems(&loadStats); + insertDatabaseItems(&memoryStats); + insertDatabaseItems(&activityStats); + insertDatabaseItems(&sensorStats); + insertDatabaseItems(&networkStats); + insertDatabaseItems(&diskStats); + _database.commit(); + if(debugLogging) + cout << "Database queue complete" << endl; + #endif +// nextQueueTime = now + 60; + nextQueueTime = now + 300; + } + + updateNextTimes(updateTime); + + usleep(interval * 1000000); + } +} + +void Stats::start() +{ + updateTime = 0; + pthread_mutex_init(&lock, NULL); + pthread_create(&_thread, NULL, start_stats_thread, (void*)this); +} + +void Stats::update_system_stats() +{ +#ifdef HAVE_LIBKSTAT + kstat_chain_update(ksh); +#endif + + sampleID++; + + cpuStats.prepareUpdate(); + loadStats.prepareUpdate(); + memoryStats.prepareUpdate(); + activityStats.prepareUpdate(); + sensorStats.prepareUpdate(); + networkStats.prepareUpdate(); + + // not updated + processStats.prepareUpdate(); + + if(debugLogging) + cout << "Updating cpu" << endl; + cpuStats.update(sampleID); + + if(debugLogging) + cout << "Updating load" << endl; + loadStats.update(sampleID); + + if(debugLogging) + cout << "Updating memory" << endl; + memoryStats.update(sampleID); + + if(debugLogging) + cout << "Updating network" << endl; + networkStats.update(sampleID); + + if(debugLogging) + cout << "Updating activity" << endl; + activityStats.update(sampleID); + + if(debugLogging) + cout << "Updating processes" << endl; + processStats.update(sampleID, cpuStats.ticks); + + processStats.finishUpdate(); + + if((sampleID % 3) == 0) + { + batteryStats.prepareUpdate(); + diskStats.prepareUpdate(); + sensorStats.prepareUpdate(); + + if(debugLogging) + cout << "Updating battery" << endl; + batteryStats.update(sampleID); + + if(debugLogging) + cout << "Updating disks" << endl; + diskStats.update(sampleID); + + if(debugLogging) + cout << "Updating sensors" << endl; + sensorStats.update(sampleID); + } + else + { + sensorStats.tick(); + diskStats.tick(); + batteryStats.tick(); + } + + #ifdef USE_SQLITE + if(historyEnabled == true) + { + if(debugLogging) + cout << "Updating history" << endl; + + cpuStats.updateHistory(); + loadStats.updateHistory(); + memoryStats.updateHistory(); + activityStats.updateHistory(); + sensorStats.updateHistory(); + networkStats.updateHistory(); + diskStats.updateHistory(); + } + #endif + if(debugLogging) + cout << "Updating complete" << endl; +} + +#ifdef USE_SQLITE +void Stats::insertDatabaseItems(StatsBase *collector) +{ + for (vector::iterator cur = collector->databaseQueue.begin(); cur != collector->databaseQueue.end(); ++cur) + { + (*cur).executeUpdate(); + } + collector->databaseQueue.clear(); +} +#endif + +void Stats::updateNextTimes(double t) +{ + cpuStats.sampleIndex[0].nextTime = t; + loadStats.sampleIndex[0].nextTime = t; + memoryStats.sampleIndex[0].nextTime = t; + activityStats.sampleIndex[0].nextTime = t; + sensorStats.sampleIndex[0].nextTime = t; + networkStats.sampleIndex[0].nextTime = t; + diskStats.sampleIndex[0].nextTime = t; + batteryStats.sampleIndex[0].nextTime = t; + /* + processStats.sampleIndex[0].nextTime = t; + */ +} + +long Stats::uptime() +{ + return uptimeStats.getUptime(); +} diff --git a/src/Stats.h b/src/Stats.h new file mode 100644 index 0000000..cc302a6 --- /dev/null +++ b/src/Stats.h @@ -0,0 +1,105 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef _STATS_H +#define _STATS_H + +#include "config.h" +#include +#include + +#include +#include + +#include "System.h" +#include "stats/StatsCPU.h" +#include "stats/StatsSensors.h" +#include "stats/StatsMemory.h" +#include "stats/StatsLoad.h" +#include "stats/StatsNetwork.h" +#include "stats/StatsDisks.h" +#include "stats/StatsUptime.h" +#include "stats/StatsActivity.h" +#include "stats/StatsBattery.h" +#include "stats/StatsProcesses.h" + +#ifdef HAVE_KSTAT_H +# include +#endif + +#ifdef USE_SQLITE +#include "Database.h" +#endif + +class Stats +{ + public: + void start(); + void startStats(); + void update_system_stats(); + void updateNextTimes(double t); + void close(); + void finalize(); + + std::vector get_battery_history(long _pos); + long uptime(); + long long sampleID; + pthread_mutex_t lock; + + bool historyEnabled; + bool debugLogging; + + #ifdef USE_SQLITE + Database _database; + void insertDatabaseItems(StatsBase *collector); + #endif + + StatsCPU cpuStats; + StatsSensors sensorStats; + StatsMemory memoryStats; + StatsLoad loadStats; + StatsNetwork networkStats; + StatsDisks diskStats; + StatsUptime uptimeStats; + StatsActivity activityStats; + StatsBattery batteryStats; + StatsProcesses processStats; + + private: +#ifdef HAVE_LIBKSTAT + kstat_ctl_t *ksh; +#endif + pthread_t _thread; + double updateTime; + double nextIPAddressTime; +}; + +#endif diff --git a/src/System.h b/src/System.h new file mode 100644 index 0000000..b6965c5 --- /dev/null +++ b/src/System.h @@ -0,0 +1,126 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef _SYSTEM_H +#define _SYSTEM_H + +#include +#include + +#define SERVER_VERSION 3.00 +#define SERVER_BUILD 102 +#define PROTOCOL_VERSION 3 +#define HISTORY_SIZE 600 + +struct load_data +{ + float one, two, three; + long long sampleID; + double time; + bool empty; +}; + +struct cpu_data +{ + double u, n, s, i, io, ent, phys; + long long sampleID; + double time; + bool empty; +}; + +#define memory_value_total 0 +#define memory_value_free 1 +#define memory_value_used 2 +#define memory_value_active 3 +#define memory_value_inactive 4 +#define memory_value_cached 5 +#define memory_value_swapused 6 +#define memory_value_swaptotal 7 +#define memory_value_swapout 8 +#define memory_value_swapin 9 +#define memory_value_buffer 10 +#define memory_value_wired 11 +#define memory_value_ex 12 +#define memory_value_file 13 +#define memory_value_virtualtotal 14 +#define memory_value_virtualactive 15 + +#define memory_values_count 16 + +struct sample_data +{ + long long sampleID; + double time; +}; + +struct mem_data +{ + double values[memory_values_count]; + long long sampleID; + double time; + bool empty; +}; + +struct activity_data +{ + long long sampleID; + double r, w; + double rIOPS, wIOPS; + double time; + bool empty; +}; + +struct net_data +{ + long long sampleID; + double u, d; + double time; + bool empty; +}; + +struct disk_data +{ + long long sampleID; + float p; + double t, u, f; + double time; + bool empty; +}; + +struct sensor_data +{ + long long sampleID; + double value; + double time; + bool empty; +}; + +#endif diff --git a/src/Utility.cpp b/src/Utility.cpp new file mode 100644 index 0000000..e0ca6c0 --- /dev/null +++ b/src/Utility.cpp @@ -0,0 +1,294 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "Utility.h" + +using namespace std; + +#ifdef HAVE_KSTAT_H + +unsigned long long ksgetull(kstat_named_t *kn) +{ + switch(kn->data_type) + { +# ifdef KSTAT_DATA_INT32 + case KSTAT_DATA_INT32: return kn->value.i32; + case KSTAT_DATA_UINT32: return kn->value.ui32; + case KSTAT_DATA_INT64: return kn->value.i64; + case KSTAT_DATA_UINT64: return kn->value.ui64; +# else + case KSTAT_DATA_LONG: return kn->value.l; + case KSTAT_DATA_ULONG: return kn->value.ul; + case KSTAT_DATA_LONGLONG: return kn->value.ll; + case KSTAT_DATA_ULONGLONG: return kn->value.ull; +# endif + default: + return (unsigned long long) -1; + } +} +#endif + +int serverPlatform() +{ + int platform = 2; + +#if defined(__sun) && defined(__SVR4) + platform = 3; +#endif + +#if defined(__FreeBSD__) || defined(__FreeBSD) + platform = 4; +#endif + +#if defined(_AIX) || defined(__AIX) || defined(__AIX__) || defined(__aix) || defined(__aix__) + platform = 6; +#endif + +#if defined(__NetBSD__) || defined(__NetBSD) + platform = 8; +#endif + +#if defined(__OpenBSD__) || defined(__OPENBSD) + platform = 9; +#endif + +#if defined(__DragonFly__) + platform = 10; +#endif + + return platform; +} + +double get_current_time() +{ + struct timeval tp; + gettimeofday(&tp, NULL); + return (double)tp.tv_sec + ((double)tp.tv_usec / 1000000.f); +} + +string get_current_time_string() +{ + struct timeval tp; + gettimeofday(&tp, NULL); + + double t = (double)tp.tv_sec + ((double)tp.tv_usec / 1000000.f); + char buffer[32]; + snprintf(buffer, sizeof(buffer), "%.4f", t); + + return string(buffer); +} + +uid_t get_current_uid() +{ + return geteuid(); +} + +gid_t get_current_gid() +{ + return getegid(); +} + +uid_t get_uid_from_str(const string &_user) +{ + struct passwd * ent; + + if(!(ent = getpwnam(_user.c_str()))) + { + return -1; + } + + return(ent->pw_uid); +} + +uid_t get_gid_from_str(const string &_group) +{ + struct group * ent; + + if(!(ent = getgrnam(_group.c_str()))) + { + return -1; + } + + return(ent->gr_gid); +} + +int get_file_owner(const string &_file) +{ + struct stat stats; + + stat(_file.c_str(), &stats); + + return stats.st_uid; +} + +int pid_dead(int _pid) +{ + // Return 1 if process is dead + if (waitpid(_pid, NULL, WNOHANG) != 0) + return 1; + + return 0; +} + +int check_dir_exist(const string &_dir) +{ + struct stat stats; + + // Return 1 if dir exists + if (stat(_dir.c_str(), &stats) == 0 && S_ISDIR(stats.st_mode) == 1) + return 1; + + return 0; +} + +int check_file_exist(const string &_file) +{ + struct stat stats; + + // Return 1 if file exists + if (stat(_file.c_str(), &stats) == 0 && S_ISREG(stats.st_mode) == 1) + return 1; + + return 0; +} + +string trim(const string & source, const char *_delim) +{ + string result(source); + string::size_type index = result.find_last_not_of(_delim); + + if (index != string::npos) + result.erase(++index); + + index = result.find_first_not_of(_delim); + + if (index != string::npos) + result.erase(0, index); + else + result.erase(); + + return result; +} + +vector split(const string &_str, const string _delim) +{ + vector v; + string str_elem(""); + std::string::size_type ui_cur_pos, ui_last_pos = 0; + + // Check for empty string + if (_str.empty()) return v; + + ui_cur_pos = _str.find_first_of(_delim.c_str(), ui_last_pos); + + while(ui_cur_pos != _str.npos) + { + str_elem = _str.substr(ui_last_pos, ui_cur_pos-ui_last_pos); + v.push_back(str_elem); + + ui_last_pos = ui_cur_pos + 1; + ui_cur_pos = _str.find_first_of(_delim.c_str(), ui_last_pos); + } + + // Handle last substring - if any + if(_str.length() != ui_last_pos) + { + str_elem = _str.substr(ui_last_pos, _str.length()-ui_last_pos); + v.push_back(str_elem); + } + + return v; +} + +vector explode(string _source, const string &_delim) +{ + vector ret; + string splitted_part; + unsigned int i, no_match = 0; + string::size_type pos, split_pos; + + // Loop array string until we can't find more delimiters + while (no_match < _delim.length()) + { + no_match = 0; + split_pos = string::npos; + + // Find first occuring splitter + for (i = 0; i < _delim.length(); i++) + { + pos = _source.find(_delim[i], 0); + + if (pos == string::npos) no_match++; + if (pos < split_pos) split_pos = pos; + } + + // Be nice to things wrapped with quotes + if (_source[0] == '"' && _source.substr(1).find_first_of("\"") != string::npos) + { + split_pos = _source.substr(1).find_first_of("\"") + 2; + } + + // One value from the array + splitted_part = _source.substr(0, split_pos); + + // Save the value if it's not empty + if (splitted_part != "") + { + ret.push_back(splitted_part); + } + + // Remove value from string + _source.erase(0, split_pos + 1); + } + + return ret; +} + +int create_directory(const string &_dir, mode_t _mask) +{ + if (mkdir(_dir.c_str(), _mask) < 0) + return -1; + + return 0; +} diff --git a/src/Utility.h b/src/Utility.h new file mode 100644 index 0000000..5d8c50c --- /dev/null +++ b/src/Utility.h @@ -0,0 +1,114 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef _UTILITY_H +#define _UTILITY_H + +#include "config.h" + +#include +#include +#include + +#ifdef HAVE_SYS_PARAM_H +# include +#endif + +#ifdef HAVE_SYS_STAT_H +# include +#endif + +#ifdef HAVE_SYS_TYPES_H +# include +#endif + +#ifdef HAVE_SYS_TIME_H +# include +#endif + +#ifdef HAVE_KSTAT_H +# include +#endif + +#ifdef HAVE_KSTAT_H +unsigned long long ksgetull(kstat_named_t *kn); +#endif + +uid_t get_current_uid(); +gid_t get_current_gid(); +uid_t get_uid_from_str(const std::string & _user); +gid_t get_gid_from_str(const std::string & _group); +int get_file_owner(const std::string & _file); +int pid_dead(int _pid); +int check_dir_exist(const std::string & _dir); +int check_file_exist(const std::string & _file); +int create_directory(const std::string &_dir, mode_t _mask); +double get_current_time(); +int serverPlatform(); +std::string get_current_time_string(); + +std::string trim(const std::string & _source, const char * _delims = " \t\r\n"); +std::vector split(const std::string &_str, const std::string _delim); +std::vector explode(std::string _str, const std::string &_delim = " "); + +template double to_double(const T &_val) +{ + double n; + std::stringstream buffer; + buffer << _val; + buffer >> n; + return n; +} + +template int to_int(const T &_val) +{ + int n; + std::stringstream buffer; + buffer << _val; + buffer >> n; + return n; +} + +template std::string to_ascii(const T &_val) +{ + std::ostringstream buffer; + for (std::string::const_iterator i = _val.begin(); i != _val.end(); i++) buffer << static_cast(*i); + return buffer.str(); +} + +template std::string to_string(const T &_val) +{ + std::ostringstream buffer; + buffer << _val; + return buffer.str(); +} + +#endif diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..3f16366 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,447 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "config.h" +#include +#include + +#include "main.h" +#include "Conf.h" +#include "Stats.h" +#include "System.h" +#include "Daemon.h" +#include "Socket.h" +#include "Utility.h" +#include "Argument.h" +#include "Clientset.h" +#include "Avahi.h" +#include "Socketset.h" +#include + +#include +#include +#include + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifdef USE_SQLITE +#include "Database.h" +#endif + +using namespace std; + +SignalResponder * pn_signalresponder = NULL; + +int main(int argc, char ** argv) +{ + Stats stats; + SocketSet sockets; + ClientSet clients; + ArgumentSet arguments(argc, argv); + + if (arguments.is_set("version") || arguments.is_set("v")) + { + char buffer[32]; + snprintf(buffer, sizeof(buffer), "%.2f", SERVER_VERSION); + cout << PACKAGE_NAME << " version " << buffer << ", build " << SERVER_BUILD << endl; + return 0; + } + + if (arguments.is_set("verify")) + { + #ifdef USE_SQLITE + Database database; + database.init(); + database.verify(); + database.close(); + #endif + return 0; + } + + if (arguments.is_set("help") || arguments.is_set("h")) + { + cout << endl; + cout << "usage: istatserver [-a HOST] [-p PORT]" << endl; + cout << endl; + cout << " -d run in background" << endl; + cout << " -h print this help text" << endl; + cout << " -v print version number" << endl; + cout << " -verify verify sqlite database" << endl; + cout << endl; + //cout << " -c DIRECTORY custom config directory location" << endl; + cout << " -a HOST listen on this address" << endl; + cout << " -p PORT listen on this port" << endl; + cout << " -u USER change running user" << endl; + cout << " -g GROUP change running group" << endl; + cout << endl; + cout << " --pid=FILE custom pid file location" << endl; + cout << " --socket=FILE custom socket file location" << endl; + cout << " --code=CODE custom lock code" << endl; + cout << endl; + return 0; + } + +// string config_directory = arguments.get("c", string(CONFIG_PATH)); + string config_directory = string(CONFIG_PATH); + + // Load and parse configuration + Config config(config_directory + "istatserver.conf"); + config.parse(); + config.validate(); + + // Load configuration properties from command line and config file + bool arg_d = arguments.is_set("d"); + string cf_network_addr = arguments.get("a", config.get("network_addr", "0.0.0.0")); + string cf_network_port = arguments.get("p", config.get("network_port", "5109")); + string cf_server_user = arguments.get("u", config.get("server_user", "istat")); + string cf_server_group = arguments.get("g", config.get("server_group", "istat")); + string cf_server_pid = arguments.get("pid", config.get("server_pid", "/var/run/istatserver.pid")); + + string cf_server_socket = arguments.get("socket", config.get("server_socket", "/tmp/istatserver.sock")); + + // Load server generated config file + string generated_path = config_directory + "istatserver_generated.conf"; + Config configGenerated(arguments.get("c", generated_path.c_str())); + configGenerated.parse(); + configGenerated.validate(); + + string serverUUID = configGenerated.get("uuid", ""); + + if(serverUUID.length() == 0) + { + char *uuid = (char *)malloc(128); + GenerateGuid(uuid); + + std::ofstream outfile; + outfile.open(generated_path.c_str(), std::ios_base::app); + outfile << "uuid " << uuid << "\n"; + outfile.close(); + + serverUUID = std::string(uuid); + + free(uuid); + } + + Socket listener(cf_network_addr, to_int(cf_network_port)); + Daemon unixdaemon(cf_server_pid, cf_server_socket); + SignalResponder signalresponder(&sockets, &listener, &unixdaemon, &stats); + + ::pn_signalresponder = &signalresponder; + + // Create socket, pid file and put in background if desired + unixdaemon.create(arg_d, cf_server_user, cf_server_group); + +#ifndef HOST_NAME_MAX + #define HOST_NAME_MAX 1024 +#endif + + string hostname = "Unknown"; + char namebuf[HOST_NAME_MAX]; + int z = gethostname(namebuf, HOST_NAME_MAX); + if(z == 0) + hostname = std::string(namebuf); + + if(hostname.length() == 0) + hostname = "Unknown"; + + unixdaemon.publish(to_int(cf_network_port), PROTOCOL_VERSION, hostname, serverUUID, serverPlatform()); + + // Get old sessions from disk cache + clients.read_cache(string(CONFIG_PATH)); + + // Clear cache of saved sessions + if (arguments.is_set("clear-sessions")) + { + clients.clear_cache(); + return 0; + } + + signal(SIGHUP, handler); + signal(SIGUSR1, handler); + signal(SIGINT, handler); + signal(SIGTERM, handler); + signal(SIGPIPE, handler); + + stats.debugLogging = false; + stats.sampleID = 0; + + bool debugSocket = false; + bool debugStats = false; + + if (arguments.is_set("debug")) + debugStats = true; + + if (arguments.is_set("debugsocket")) + debugSocket = true; + + stats.debugLogging = debugStats; + + listener._session = (long)get_current_time(); + listener._serverUUID = serverUUID; + listener._sslEnabled = 1; + listener.isServer = true; + listener.debugLogging = debugSocket; + + + string privateKeyPath = string(CONFIG_PATH) + "key.pem"; + string certPath = string(CONFIG_PATH) + "cert.pem"; + + listener.sslContext = InitServerCTX(); + if(listener.sslContext != NULL) + { + if(access(certPath.c_str(), F_OK) == -1) + { + createSSLCertificate(); + } + + LoadCertificates(listener.sslContext, (char *)certPath.c_str(), (char *)privateKeyPath.c_str()); + listener._sslEnabled = 1; + } + + if (!listener.listen()) return 1; + + sockets += listener; + + stats.historyEnabled = true; + if(to_int(config.get("disable_history_storage", "0")) == 1) + stats.historyEnabled = false; + + stats.diskStats.useMountPaths = to_int(config.get("disk_mount_path_label", "0")); + stats.diskStats.customNames = config.get_array("disk_rename_label"); + stats.diskStats.disableFiltering = to_int(config.get("disk_disable_filtering", "0")); + stats.start(); + + while (1) + { + if (sockets.get_status(1) > 0) + { + if (sockets == listener) + { + Socket new_socket = listener.accept(); + new_socket.debugLogging = debugSocket; + new_socket.sslContext = InitServerCTX(); + LoadCertificates(new_socket.sslContext, (char *)certPath.c_str(), (char *)privateKeyPath.c_str()); + new_socket.startSSL(); + sockets += new_socket; + } + else + { + Socket &active_socket = sockets.get_ready(); + + if (active_socket.receive(&clients, &config, &stats)) + { + } + else + { + sockets -= active_socket; + } + } + } + + bool finished = false; + while(finished == false) + { + finished = true; + + vector::iterator it = sockets.connections.begin(); + while(it != sockets.connections.end()) + { + if((*it).isServer == true) + { + ++it; + continue; + } + if((*it).lastRequest < (get_current_time() - 120)){ + cout << (*it).get_description() << " Removing connection due to timeout: " << (long long)(get_current_time() - (*it).lastRequest) << endl; + sockets -= (*it); + finished = false; + break; + } + ++it; + } + } + } + + ::pn_signalresponder = NULL; + + return 0; +} + +SSL_CTX* InitServerCTX(void) +{ + SSL_library_init(); + + OpenSSL_add_all_algorithms(); + SSL_load_error_strings(); + SSL_CTX *ctx = SSL_CTX_new(TLSv1_server_method()); + if ( ctx == NULL ) + { + ERR_print_errors_fp(stdout); + fflush(stdout); + return NULL; + } + + SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF); + + EC_KEY *ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + if (ecdh == NULL) { + cout << "Failed to get EC curve" << endl; + SSL_CTX_free(ctx); + return NULL; + } + SSL_CTX_set_tmp_ecdh(ctx, ecdh); + EC_KEY_free(ecdh); + + DH *dh = get_dh2236(); + if (dh == NULL) { + cout << "Failed to get DH params" << endl; + SSL_CTX_free(ctx); + return NULL; + } + SSL_CTX_set_tmp_dh(ctx, dh); + DH_free(dh); + + return ctx; +} + +DH *get_dh2236() + { + static unsigned char dh2236_p[]={ + 0x0A,0x08,0x7D,0xA9,0x64,0x7E,0xC4,0xD5,0xFB,0x56,0xD2,0xFA, + 0x17,0x36,0x21,0xFE,0xE5,0x37,0x9B,0x1C,0x56,0x2B,0x52,0x5A, + 0x81,0xA5,0x76,0x54,0xF0,0x35,0xEB,0xCA,0xC4,0x65,0xA4,0x62, + 0x3E,0x54,0xBC,0x05,0xEC,0x8F,0xBC,0xCB,0x95,0xA4,0x4B,0xE4, + 0x45,0xD2,0xFB,0x4D,0x89,0x39,0x97,0x99,0x3F,0x13,0xCA,0xF4, + 0x42,0xC6,0x19,0xB3,0xCD,0xE6,0xEA,0x50,0xFB,0x9E,0x9E,0xEB, + 0xB6,0x31,0x31,0x58,0xB5,0xA4,0x48,0x9C,0x1E,0x3F,0xFF,0x6F, + 0xE6,0x59,0x00,0xFB,0x54,0x7E,0x79,0x86,0x99,0x55,0xE8,0x8A, + 0x58,0x7E,0xAA,0x35,0x8F,0x44,0x28,0x32,0xB4,0x95,0x73,0xB1, + 0xE4,0x63,0x68,0xD0,0xA8,0x95,0x55,0xE7,0x7B,0x35,0xF8,0x92, + 0x9B,0x15,0x31,0x0C,0x1D,0xDA,0xA3,0x11,0x83,0x63,0x5E,0x47, + 0x01,0x63,0x88,0xDE,0xB2,0x72,0x1B,0x6E,0x07,0xD1,0x68,0xC2, + 0x01,0x70,0x29,0xCC,0x07,0x97,0xF6,0x75,0x7C,0x88,0x2F,0xAD, + 0xBE,0x5B,0x99,0x4D,0x93,0xE6,0xF9,0xA8,0xDF,0x1B,0x95,0xA1, + 0x84,0xCF,0x3C,0xC6,0x9A,0x42,0x41,0xEB,0x21,0x27,0x57,0x95, + 0xBE,0x2C,0xD5,0x0B,0x8E,0xCD,0xCF,0x56,0xB6,0x65,0x71,0x06, + 0x6E,0xCA,0xF8,0x4E,0xB7,0xD0,0x94,0x10,0xC0,0x45,0xE1,0x86, + 0xC6,0x40,0xFF,0xE4,0x71,0xFE,0x3C,0x96,0x4F,0xE4,0xB3,0x97, + 0xEA,0x64,0xEB,0xAF,0x13,0x0A,0xCC,0xA3,0xCD,0xDE,0x41,0x83, + 0x4A,0x67,0x9F,0x0D,0x60,0x81,0x18,0xFD,0x91,0x90,0x47,0x99, + 0x5D,0x49,0x4A,0xE3,0xBD,0x8B,0xEE,0x54,0xCC,0x64,0xB9,0x5D, + 0xE0,0x19,0x5A,0x58,0x2D,0xB4,0xB9,0x55,0xC3,0x4E,0x83,0xEB, + 0xCB,0x15,0x22,0x83,0xD0,0x3C,0x6F,0x90,0x95,0x44,0xE2,0x0D, + 0xDA,0xD2,0x6C,0x53, + }; + static unsigned char dh2236_g[]={ + 0x02, + }; + DH *dh; + + if ((dh=DH_new()) == NULL) return(NULL); + dh->p=BN_bin2bn(dh2236_p,sizeof(dh2236_p),NULL); + dh->g=BN_bin2bn(dh2236_g,sizeof(dh2236_g),NULL); + if ((dh->p == NULL) || (dh->g == NULL)) + { DH_free(dh); return(NULL); } + return(dh); +} + +void LoadCertificates(SSL_CTX* ctx, char* CertFile, char* KeyFile) +{ + if ( SSL_CTX_use_certificate_file(ctx, CertFile, SSL_FILETYPE_PEM) <= 0 ) + { + ERR_print_errors_fp(stdout); + fflush(stdout); + return; + } + if ( SSL_CTX_use_PrivateKey_file(ctx, KeyFile, SSL_FILETYPE_PEM) <= 0 ) + { + ERR_print_errors_fp(stdout); + fflush(stdout); + return; + } + if ( !SSL_CTX_check_private_key(ctx) ) + { + cout << "Private key does not match the public certificate" << endl; + return; + } +} + +void handler(int _signal) +{ + if (pn_signalresponder) + { + switch (_signal) + { + case SIGINT: + pn_signalresponder->on_sigint(); + return; + + case SIGTERM: + pn_signalresponder->on_sigterm(); + return; + + case SIGHUP: + pn_signalresponder->on_sighup(); + return; + } + } +} + +void GenerateGuid(char *guidStr) +{ + char *pGuidStr = guidStr; + int i; + + srand(static_cast (time(NULL))); /*Randomize based on time.*/ + + /*Data1 - 8 characters.*/ + for(i = 0; i < 8; i++, pGuidStr++) + ((*pGuidStr = (rand() % 16)) < 10) ? *pGuidStr += 48 : *pGuidStr += 55; + + /*Data2 - 4 characters.*/ + *pGuidStr++ = '-'; + for(i = 0; i < 4; i++, pGuidStr++) + ((*pGuidStr = (rand() % 16)) < 10) ? *pGuidStr += 48 : *pGuidStr += 55; + + /*Data3 - 4 characters.*/ + *pGuidStr++ = '-'; + for(i = 0; i < 4; i++, pGuidStr++) + ((*pGuidStr = (rand() % 16)) < 10) ? *pGuidStr += 48 : *pGuidStr += 55; + + /*Data4 - 4 characters.*/ + *pGuidStr++ = '-'; + for(i = 0; i < 4; i++, pGuidStr++) + ((*pGuidStr = (rand() % 16)) < 10) ? *pGuidStr += 48 : *pGuidStr += 55; + + *pGuidStr = '\0'; +} diff --git a/src/main.h b/src/main.h new file mode 100644 index 0000000..3136bb2 --- /dev/null +++ b/src/main.h @@ -0,0 +1,44 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef _MAIN_H +#define _MAIN_H + +#include +#include "Certificate.h" + +void handler(int _signal); +void GenerateGuid(char *guidStr); +SSL_CTX* InitServerCTX(void); +void LoadCertificates(SSL_CTX* ctx, char* CertFile, char* KeyFile); +DH *get_dh2236(); + +#endif diff --git a/src/stats/StatBase.cpp b/src/stats/StatBase.cpp new file mode 100644 index 0000000..57c2d24 --- /dev/null +++ b/src/stats/StatBase.cpp @@ -0,0 +1,160 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "StatBase.h" + +using namespace std; + +void StatsBase::tickSample() +{ + sampleIndex[0].sampleID = sampleIndex[0].sampleID + 1; +} + +void StatsBase::tick() +{ + sampleIndex[0].time = sampleIndex[0].nextTime; +} + +void StatsBase::prepareUpdate() +{ + sampleIndex[0].time = sampleIndex[0].nextTime; + sampleIndex[0].sampleID = sampleIndex[0].sampleID + 1; +} + +void StatsBase::initShared() +{ + int x; + int storageIndexes[9] = {0, 0, 1, 1, 2, 2, 2, 2, 0}; + int intervals[9] = {1, 6, 144, 1008, 4320, 12960, 25920, 52560, 300}; + for(x=0;x<8;x++){ + sampleIndex[x].time = 0; + sampleIndex[x].nextTime = 0; + sampleIndex[x].sampleID = 1; + sampleIndex[x].interval = intervals[x]; + sampleIndex[x].historyIndex = storageIndexes[x]; + } + ready = 1; + session = 0; +} + +#ifdef USE_SQLITE +string StatsBase::tableAtIndex(int index) +{ + string tables[8] = {"", "hour", "day", "week", "month", "threemonth", "sixmonth", "year" }; + return tables[index]; +} + +double StatsBase::sampleIdForTable(string table) +{ + string sql = "select sample from " + table + " order by sample desc limit 1"; + DatabaseItem query = _database.databaseItem(sql); + + if(query.next()) + { + double value = sqlite3_column_double(query._statement, 0); + query.finalize(); + return value; + } + + return 0; +} + +void StatsBase::fillGaps() +{ + fillGapsAtIndex(1); + fillGapsAtIndex(2); + fillGapsAtIndex(3); + fillGapsAtIndex(4); + fillGapsAtIndex(5); + fillGapsAtIndex(6); + fillGapsAtIndex(7); +} + +void StatsBase::fillGapsAtIndex(int index) +{ + double now = get_current_time(); + + if (sampleIndex[index].nextTime == 0) { + sampleIndex[index].nextTime = ceil(now) + sampleIndex[index].interval; + sampleIndex[index].time = sampleIndex[index].nextTime - sampleIndex[index].interval; + + InsertInitialSample(index, sampleIndex[index].time, sampleIndex[index].sampleID); + return; + } + + if (sampleIndex[index].nextTime < now) + { + while (sampleIndex[index].nextTime < now) + { + sampleIndex[index].nextTime = sampleIndex[index].nextTime + sampleIndex[index].interval; + sampleIndex[index].sampleID = sampleIndex[index].sampleID + 1; + } + } +} + +int StatsBase::InsertInitialSample(int index, double t, long long sampleID) +{ + string table = databasePrefix + tableAtIndex(index); + if (databaseType == 1) + table += "_id"; + + string sql = "insert into " + table + " (sample, time, empty) values(?, ?, 1)"; + + DatabaseItem dbItem = _database.databaseItem(sql); + sqlite3_bind_double(dbItem._statement, 1, (double)sampleID); + sqlite3_bind_double(dbItem._statement, 2, t); + dbItem.executeUpdate(); + + return 0; +} + +void StatsBase::removeOldSamples() +{ + int x; + for(x=1;x<8;x++) + { + if (databaseType == 1) + { + string table = databasePrefix + tableAtIndex(x) + "_id"; + string sql = "delete from " + table + " WHERE sample < ?"; + DatabaseItem dbItem = _database.databaseItem(sql); + sqlite3_bind_double(dbItem._statement, 1, sampleIndex[x].sampleID - 600); + databaseQueue.push_back(dbItem); + } + string table = databasePrefix + tableAtIndex(x); + string sql = "delete from " + table + " WHERE sample < ?"; + DatabaseItem dbItem = _database.databaseItem(sql); + sqlite3_bind_double(dbItem._statement, 1, sampleIndex[x].sampleID - 600); + databaseQueue.push_back(dbItem); + } +} + +#endif diff --git a/src/stats/StatBase.h b/src/stats/StatBase.h new file mode 100644 index 0000000..bf9e258 --- /dev/null +++ b/src/stats/StatBase.h @@ -0,0 +1,303 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef _STATBASE_H +#define _STATBASE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "System.h" +#include "Utility.h" + +// needed for solaris +#define _STRUCTURED_PROC 1 + +#ifdef HAVE_SENSORS_SENSORS_H +# include +#endif + +#ifdef HAVE_SYS_PROCFS_H +#include +#endif + +#ifdef HAVE_DIRENT_H +#include +#endif + +#ifdef HAVE_PROCFS_H +#include +#endif + +#ifdef HAVE_PROCINFO_H +#include +#endif + +#ifdef HAVE_KVM_H +# include +#endif + +#ifdef HAVE_SYS_USER_H +#include +#endif + +#ifdef HAVE_SYS_SOCKET_H +# include +#endif + +#ifdef HAVE_NET_IF_H +# include +#endif + +#ifdef HAVE_NET_IF_MIB_H +# include +#endif + +#ifdef HAVE_NETINET_IN_H +#include +#endif + +#ifdef HAVE_GETIFADDRS + #include + #include + + #ifndef AF_LINK + #define AF_LINK AF_PACKET + #endif +#endif + +#ifdef HAVE_INET_COMMON_H +#include +#endif + +#ifdef HAVE_SYS_SOCKIO_H +#include +#endif + +#ifdef HAVE_SYS_SWAP_H +# include +#endif + +#ifdef HAVE_UVM_UVM_EXTERN_H +# include +#endif + +#ifdef TIME_WITH_SYS_TIME +# include +# include +#elif defined(HAVE_SYS_TIME_H) +# include +#else +# include +#endif + +#ifdef HAVE_SYS_VMMETER_H +# include +#endif + +#ifdef HAVE_SYS_MNTTAB_H +# include +#endif + +#ifdef HAVE_SYS_STATVFS_H +# include +#elif defined(HAVE_SYS_STATFS_H) +# include +#endif + +#ifdef HAVE_SYS_MOUNT_H +# include +#endif + +#ifdef HAVE_MNTENT_H +# include +#endif + +#ifdef HAVE_PATHS_H +# include +#endif + +#ifdef HAVE_SYS_RESOURCE_H +# include +#endif + +#ifdef HAVE_SYS_SCHED_H +# include +#endif + +#ifdef HAVE_SYS_PROCESSOR_H +# include +#endif + +#ifdef HAVE_SYS_SYSINFO_H +# include +#endif + +#ifdef HAVE_DIRENT_H +#include +#endif + +#ifdef HAVE_SYS_IOCTL_H +# include +#endif + +#ifdef HAVE_MACHINE_APMVAR_H +#include +#endif + +#ifdef HAVE_DEV_ACPICA_ACPIIO_H +#include +#endif + +#ifdef HAVE_SYS_SYSCTL_H +# include +#endif + +#if defined(HAVE_DEVSTAT) || defined(HAVE_DEVSTAT_ALT) +#include +#endif + +#ifdef HAVE_KSTAT_H +# include +#endif + +#ifdef HAVE_PATHS_H +# include +#endif + +#ifdef HAVE_ERRNO_H +# include +#endif + +#ifdef HAVE_SYS_LOADAVG_H +# include +#endif + +#ifdef HAVE_SYS_TYPES_H +# include +#endif + +#ifdef HAVE_SYS_PARAM_H +# include +#endif + +#ifdef HAVE_SYS_PROC_H +# include +#endif + +#ifdef HAVE_LIBPERFSTAT_H +# include +#endif + +#ifdef HAVE_SYS_PSTAT_H +# include +#endif + +#ifdef HAVE_SYS_DK_H +# include +#endif + +#ifdef HAVE_SYS_DLPI_H +# include +#endif + +#ifdef HAVE_SYS_DLPI_EXT_H +# include +#endif + +#ifdef HAVE_SYS_MIB_H +# include +#endif + +#ifdef HAVE_SYS_STROPTS_H +# include +#endif + +#ifdef HAVE_SYS_DISK_H +# include +#endif + +#ifdef HAVE_SYS_DKSTAT_H +# include +#endif + +#ifdef USE_SQLITE +#include "Database.h" +#endif + +class StatsBase +{ + typedef struct sampleindexconfig { + long long sampleID; + double time; + double nextTime; + double interval; + int historyIndex; + } sampleindexconfig_t; + + public: + void prepareUpdate(); + void tick(); + void tickSample(); + struct sampleindexconfig sampleIndex[8]; + void initShared(); + int ready; + long session; + bool historyEnabled; + bool debugLogging; + + #ifdef USE_SQLITE + std::vector databaseQueue; + Database _database; + int databaseType; + std::string databasePrefix; + void fillGaps(); + void fillGapsAtIndex(int index); + std::string tableAtIndex(int index); + int InsertInitialSample(int index, double time, long long sampleID); + double sampleIdForTable(std::string table); + void loadPreviousSamples(); + void removeOldSamples(); + #endif +}; +#endif diff --git a/src/stats/StatsActivity.cpp b/src/stats/StatsActivity.cpp new file mode 100644 index 0000000..2d78392 --- /dev/null +++ b/src/stats/StatsActivity.cpp @@ -0,0 +1,672 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "StatsActivity.h" + +using namespace std; + +#if defined(USE_ACTIVITY_HPUX) + +void StatsActivity::init() +{ + _init(); +} + +void StatsActivity::update(long long sampleID) +{ + struct pst_diskinfo pstat_disk; + int i = 0; + + while (pstat_getdisk(pstat_diskinfo, sizeof pstat_disk, 1, i) != -1) { + i++; + if(pstat_disk.psd_status == 0) + { + continue; + } + + processDisk(string(pstat_disk.psd_hw_path.psh_name), sampleID, pstat_disk.psd_dkbyteread, pstat_disk.psd_dkbytewrite, pstat_disk.psd_dkread, pstat_disk.psd_dkwrite); + } +} + +#elif defined(USE_ACTIVITY_HWDS_NET) + +#ifdef HW_IOSTATS +#define HW_DISKSTATS HW_IOSTATS +#define disk_sysctl io_sysctl +#define dk_rbytes rbytes +#define dk_wbytes wbytes +#define dk_name name +#define dk_rxfer rxfer +#define dk_wxfer wxfer +#endif + +void StatsActivity::init() +{ + _init(); +} + +void StatsActivity::update(long long sampleID) +{ + int mib[3]; + int x; + int diskn; + size_t len; + struct disk_sysctl *ds; + + mib[0] = CTL_HW; + mib[1] = HW_DISKSTATS; + mib[2] = sizeof(struct disk_sysctl); + if (sysctl(mib, 3, NULL, &len, NULL, 0) == -1) + return; + + diskn = len / sizeof(struct disk_sysctl); + ds = (struct disk_sysctl *)malloc(len); + if (sysctl(mib, 3, ds, &len, NULL, 0) == -1) + return; + + for (x = 0; x < diskn; x++){ + if(!strncmp(ds[x].dk_name, "cd", 2)) + continue; + if(!strncmp(ds[x].dk_name, "fd", 2)) + continue; + processDisk(string(ds[x].dk_name), sampleID, ds[x].dk_rbytes, ds[x].dk_wbytes, ds[x].dk_rxfer, ds[x].dk_wxfer); + } + + free(ds); +} + +#elif defined(USE_ACTIVITY_HWDS_OPEN) + +void StatsActivity::init() +{ + _init(); +} + +void StatsActivity::update(long long sampleID) +{ + int mib[3]; + int x; + int diskn; + size_t len; + struct diskstats *ds; + + mib[0] = CTL_HW; + mib[1] = HW_DISKCOUNT; + len = sizeof(diskn); + + if (sysctl(mib, 2, &diskn, &len, NULL, 0) < 0) + return; + + mib[0] = CTL_HW; + mib[1] = HW_DISKSTATS; + len = diskn * sizeof(struct diskstats); + + ds = (struct diskstats *)malloc(len); + if (ds == NULL) + return; + + if (sysctl(mib, 2, ds, &len, NULL, 0) < 0) { + free(ds); + return; + } + + for (x = 0; x < diskn; x++){ + if(!strncmp(ds[x].ds_name, "cd", 2)) + continue; + if(!strncmp(ds[x].ds_name, "fd", 2)) + continue; + processDisk(string(ds[x].ds_name), sampleID, ds[x].ds_rbytes, ds[x].ds_wbytes, ds[x].ds_rxfer, ds[x].ds_wxfer); + } + + free(ds); +} + +#elif defined(USE_ACTIVITY_KSTAT) + +void StatsActivity::init() +{ + _init(); +} + +void StatsActivity::update(long long sampleID) +{ + kstat_t *ksp; + kstat_io_t kios; + + for (ksp = ksh->kc_chain; ksp; ksp = ksp->ks_next) { + if (ksp->ks_type != KSTAT_TYPE_IO) + continue; + if (strncmp(ksp->ks_class, "disk", 4) || !strcmp(ksp->ks_module, "fd")) + continue; + + memset((void *)&kios, 0, sizeof(kstat_io_t)); + kstat_read(ksh, ksp, &kios); + + processDisk(string(ksp->ks_name), sampleID, kios.nread, kios.nwritten, kios.reads, kios.writes); + } +} + +#elif (defined(HAVE_DEVSTAT) || defined(HAVE_DEVSTAT_ALT)) && defined(USE_ACTIVITY_DEVSTAT) + +void StatsActivity::init() +{ + _init(); +} + +void StatsActivity::update(long long sampleID) +{ + int devs_count, num_selected, num_selections, dn; + struct device_selection *dev_select = NULL; + long select_generation; + static struct statinfo statinfo_cur; + + memset(&statinfo_cur, 0, sizeof(statinfo_cur)); + statinfo_cur.dinfo = (struct devinfo *)calloc(1, sizeof(struct devinfo)); + +#ifdef HAVE_DEVSTAT_ALT + if (getdevs(&statinfo_cur) < 0) { +#else + if (devstat_getdevs(NULL, &statinfo_cur) < 0) { +#endif + free(statinfo_cur.dinfo); + return; + } + + devs_count = statinfo_cur.dinfo->numdevs; +#ifdef HAVE_DEVSTAT_ALT + if (selectdevs(&dev_select, &num_selected, &num_selections, +#else + if (devstat_selectdevs(&dev_select, &num_selected, &num_selections, +#endif + &select_generation, statinfo_cur.dinfo->generation, + statinfo_cur.dinfo->devices, devs_count, NULL, 0, NULL, 0, + DS_SELECT_ONLY, 16, 1) >= 0) { + for (dn = 0; dn < devs_count; dn++) { + int di; + struct devstat *dev; + + di = dev_select[dn].position; + dev = &statinfo_cur.dinfo->devices[di]; + + if (((dev->device_type & DEVSTAT_TYPE_MASK) == DEVSTAT_TYPE_DIRECT) && ((dev->device_type & DEVSTAT_TYPE_PASS) == 0) && ((dev->device_name[0] != '\0'))) + { + stringstream n; + n << "/dev/" << dev->device_name << dev->unit_number; + + #ifdef HAVE_DEVSTAT_ALT + processDisk(n.str(), sampleID, dev->bytes_read, dev->bytes_written, dev->num_reads, dev->num_writes); + #else + processDisk(n.str(), sampleID, dev->bytes[DEVSTAT_READ], dev->bytes[DEVSTAT_WRITE], dev->operations[DEVSTAT_READ], dev->operations[DEVSTAT_WRITE]); + #endif + } + } + + free(dev_select); + } + + free(statinfo_cur.dinfo); +} + +#elif defined(USE_ACTIVITY_PROCFS) + +void StatsActivity::init() +{ + _init(); +} + +void StatsActivity::update(long long sampleID) +{ + FILE * fp = NULL; + + if (!(fp = fopen("/proc/diskstats", "r"))) + { + return; + } + + while (!feof(fp)) + { + unsigned long long read; + unsigned long long write; + unsigned long long reads; + unsigned long long writes; + unsigned int major; + unsigned int minor; + char dev[32]; + + if(fscanf(fp, "%u %u %s %llu %*u %llu %*u %llu %*u %llu %*[^\n]", &major, &minor, dev, &reads, &read, &writes, &write) > 0) + { + if(major == 1 || major == 58 || major == 43 || major == 7 || major == 253 || major == 11) + continue; + + processDisk(string(dev), sampleID, read * 512, write * 512, reads, writes); + } + else + { + if(fscanf(fp, "%*[^\n]")){} + } + } + + fclose(fp); +} + +#elif defined(HAVE_LIBPERFSTAT) && defined(USE_ACTIVITY_PERFSTAT) + +void StatsActivity::init() +{ + _init(); +} + +void StatsActivity::update(long long sampleID) +{ + int disks = perfstat_disk(NULL, NULL, sizeof(perfstat_disk_t), 0); + if(disks < 0) + return; + + perfstat_disk_t *storage = (perfstat_disk_t *)calloc(disks, sizeof(perfstat_disk_t)); + + perfstat_id_t firstpath; + disks = perfstat_disk(&firstpath, storage, sizeof(perfstat_disk_t), disks); + + int i; + for (i = 0; i < disks; i++) + { + processDisk(string(storage[i].name), sampleID, storage[i].rblks * storage[i].bsize, storage[i].wblks * storage[i].bsize, storage[i].__rxfers, storage[i].xfers - storage[i].__rxfers); + for (vector::iterator curdisk = _items.begin(); curdisk != _items.end(); ++curdisk) + { + if(string(storage[i].name) == (*curdisk).device) + { + if((*curdisk).is_new == true) + { + (*curdisk).is_new = false; + + char path[2048]; + + stringstream cmd; + cmd << "/usr/sbin/lspv -l "; + cmd << storage[i].name; + + FILE *fp = popen(cmd.str().c_str(), "r"); + if (fp != NULL) { + stringstream output; + while (fgets(path, sizeof(path)-1, fp) != NULL) { + output << string(path) << endl; + } + vector lines = explode(output.str(), "\n"); + + unsigned int x; + for(x=0;x segments = explode(line, " "); + if(segments.size() == 5){ + string mount = string(segments[4]); + if(mount.compare("N/A") != 0) + { + (*curdisk).mounts.push_back(mount); + } + } + } + } + + pclose(fp); + } + } + } + } + free(storage); +} + +#else + +void StatsActivity::init() +{ + _init(); +} + +void StatsActivity::update(long long sampleID) +{ +} + +#endif + +void StatsActivity::createDisk(string key) +{ + if(_items.size() > 0) + { + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + activity_info interface = *cur; + if (interface.device == key) + { + return; + } + } + } + + activity_info item; + item.last_r = 0; + item.last_w = 0; + item.last_rIOPS = 0; + item.last_wIOPS = 0; + item.active = false; + item.is_new = true; + + item.device = key; + +#ifdef USE_SQLITE + if(historyEnabled == true) + { + int x; + for(x=1;x<8;x++) + { + string table = databasePrefix + tableAtIndex(x); + double sampleID = 0; + if(samples[x].size() > 0) + sampleID = samples[x][0].sampleID; + + + string sql = "select * from " + table + " where sample >= @sample AND uuid = ? order by sample asc limit 602"; + DatabaseItem query = _database.databaseItem(sql); + sqlite3_bind_double(query._statement, 1, sampleID - 602); + sqlite3_bind_text(query._statement, 2, key.c_str(), -1, SQLITE_STATIC); + + while(query.next()) + { + activity_data sample; + sample.r = query.doubleForColumn("read"); + sample.w = query.doubleForColumn("write"); + sample.rIOPS = query.doubleForColumn("readiops"); + sample.wIOPS = query.doubleForColumn("writeiops"); + sample.sampleID = query.doubleForColumn("sample"); + sample.time = query.doubleForColumn("time"); + item.samples[x].push_front(sample); + } + } + } +#endif + + session++; + _items.insert(_items.begin(), item); +} + +void StatsActivity::prepareUpdate() +{ + if(ready == 0) + return; + + StatsBase::prepareUpdate(); + + if(_items.size() > 0) + { + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + (*cur).active = false; + } + } +} + +void StatsActivity::processDisk(string key, long long sampleID, unsigned long long read, unsigned long long write, unsigned long long reads, unsigned long long writes) +{ + if(key == "pass" || key == "cd") + return; + + createDisk(key); + + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + if (key == (*cur).device) + { + (*cur).active = true; + + if(ready == 0) + continue; + + activity_data data; + + if((*cur).last_r == 0) + (*cur).last_r = read; + if((*cur).last_w == 0) + (*cur).last_w = write; + if((*cur).last_wIOPS == 0) + (*cur).last_wIOPS = writes; + if((*cur).last_rIOPS == 0) + (*cur).last_rIOPS = reads; + + double w = (double)(write - (*cur).last_w); + double r = (double)(read - (*cur).last_r); + double wIOPS = (double)(writes - (*cur).last_wIOPS); + double rIOPS = (double)(reads - (*cur).last_rIOPS); + + (*cur).last_w = write; + (*cur).last_r = read; + (*cur).last_wIOPS = writes; + (*cur).last_rIOPS = reads; + + data.r = r; + data.w = w; + data.rIOPS = rIOPS; + data.wIOPS = wIOPS; + data.sampleID = sampleIndex[0].sampleID; + data.time = sampleIndex[0].time; + + (*cur).samples[0].push_front(data); + if ((*cur).samples[0].size() > HISTORY_SIZE) + (*cur).samples[0].pop_back(); + + break; + } + } +} + +void StatsActivity::_init() +{ + initShared(); + ready = 0; + + #ifdef USE_SQLITE + if(historyEnabled == true) + { + databaseType = 1; + databasePrefix = "activity_"; + + int x; + for(x=1;x<8;x++) + { + string table = databasePrefix + tableAtIndex(x) + "_id"; + if(!_database.tableExists(table)) + { + string sql = "create table " + table + " (sample double PRIMARY KEY NOT NULL, time double NOT NULL DEFAULT 0, empty integer NOT NULL DEFAULT 0)"; + DatabaseItem dbItem = _database.databaseItem(sql); + dbItem.executeUpdate(); + } + + table = databasePrefix + tableAtIndex(x); + if(!_database.tableExists(table)) + { + string sql = "create table " + table + " (sample double NOT NULL, time double NOT NULL DEFAULT 0, uuid varchar(255) NOT NULL, read double NOT NULL DEFAULT 0, write double NOT NULL DEFAULT 0, readiops double NOT NULL DEFAULT 0, writeiops double NOT NULL DEFAULT 0)"; + DatabaseItem dbItem = _database.databaseItem(sql); + dbItem.executeUpdate(); + } + } + loadPreviousSamples(); + fillGaps(); + } + #endif +} + +#ifdef USE_SQLITE +void StatsActivity::loadPreviousSamples() +{ + loadPreviousSamplesAtIndex(1); + loadPreviousSamplesAtIndex(2); + loadPreviousSamplesAtIndex(3); + loadPreviousSamplesAtIndex(4); + loadPreviousSamplesAtIndex(5); + loadPreviousSamplesAtIndex(6); + loadPreviousSamplesAtIndex(7); +} + +void StatsActivity::loadPreviousSamplesAtIndex(int index) +{ + + string table = databasePrefix + tableAtIndex(index) + "_id"; + double sampleID = sampleIdForTable(table); + + string sql = "select * from " + table + " where sample >= @sample order by sample asc limit 602"; + DatabaseItem query = _database.databaseItem(sql); + sqlite3_bind_double(query._statement, 1, sampleID - 602); + + while(query.next()) + { + sample_data sample; + sample.sampleID = (long long)query.doubleForColumn("sample"); + sample.time = query.doubleForColumn("time"); + samples[index].insert(samples[index].begin(), sample); + } + if(samples[index].size() > 0) + { + sampleIndex[index].sampleID = samples[index][0].sampleID; + sampleIndex[index].time = samples[index][0].time; + sampleIndex[index].nextTime = sampleIndex[index].time + sampleIndex[index].interval; + } +} + +void StatsActivity::updateHistory() +{ + int x; + for (x = 1; x < 8; x++) + { + if(sampleIndex[0].time >= sampleIndex[x].nextTime) + { + double now = get_current_time(); + double earlistTime = now - (HISTORY_SIZE * sampleIndex[x].interval); + while(sampleIndex[x].nextTime < now) + { + sampleIndex[x].sampleID = sampleIndex[x].sampleID + 1; + sampleIndex[x].time = sampleIndex[x].nextTime; + sampleIndex[x].nextTime = sampleIndex[x].nextTime + sampleIndex[x].interval; + + if(sampleIndex[x].time < earlistTime) + continue; + + if(_items.size() > 0) + { + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + activity_data sample = historyItemAtIndex(x, (*cur)); + + (*cur).samples[x].push_front(sample); + if ((*cur).samples[x].size() > HISTORY_SIZE) (*cur).samples[x].pop_back(); + + if(sample.empty) + continue; + + string table = databasePrefix + tableAtIndex(x); + string sql = "insert into " + table + " (sample, time, read, write, readiops, writeiops, uuid) values(?, ?, ?, ?, ?, ?, ?)"; + + DatabaseItem dbItem = _database.databaseItem(sql); + sqlite3_bind_double(dbItem._statement, 1, (double)sample.sampleID); + sqlite3_bind_double(dbItem._statement, 2, sample.time); + sqlite3_bind_double(dbItem._statement, 3, sample.r); + sqlite3_bind_double(dbItem._statement, 4, sample.w); + sqlite3_bind_double(dbItem._statement, 5, sample.rIOPS); + sqlite3_bind_double(dbItem._statement, 6, sample.wIOPS); + sqlite3_bind_text(dbItem._statement, 7, (*cur).device.c_str(), -1, SQLITE_STATIC); + databaseQueue.push_back(dbItem); + } + } + + string table = databasePrefix + tableAtIndex(x) + "_id"; + string sql = "insert into " + table + " (empty, sample, time) values(?, ?, ?)"; + + DatabaseItem dbItem = _database.databaseItem(sql); + sqlite3_bind_int(dbItem._statement, 1, 0); + sqlite3_bind_double(dbItem._statement, 2, (double)sampleIndex[x].sampleID); + sqlite3_bind_double(dbItem._statement, 3, sampleIndex[x].time); + databaseQueue.push_back(dbItem); + } + } + } +} + +activity_data StatsActivity::historyItemAtIndex(int index, activity_info item) +{ + activity_data sample; + double r = 0; + double w = 0; + double rIOPS = 0; + double wIOPS = 0; + + std::deque from = item.samples[sampleIndex[index].historyIndex]; + double minimumTime = sampleIndex[index].time - sampleIndex[index].interval; + double maximumTime = sampleIndex[index].time; + if(sampleIndex[index].historyIndex == 0) + maximumTime += 0.99; + + int count = 0; + if(from.size() > 0) + { + for (deque::iterator cursample = from.begin(); cursample != from.end(); ++cursample) + { + if ((*cursample).time > maximumTime) + continue; + + if ((*cursample).time < minimumTime) + break; + + r += (*cursample).r; + w += (*cursample).w; + rIOPS += (*cursample).rIOPS; + wIOPS += (*cursample).wIOPS; + count++; + } + if (count > 0) + { + r /= count; + w /= count; + rIOPS /= count; + wIOPS /= count; + } + } + + sample.r = r; + sample.w = w; + sample.rIOPS = rIOPS; + sample.wIOPS = wIOPS; + + sample.sampleID = sampleIndex[index].sampleID; + sample.time = sampleIndex[index].time; + sample.empty = false; + if(count == 0) + sample.empty = true; + + return sample; +} +#endif diff --git a/src/stats/StatsActivity.h b/src/stats/StatsActivity.h new file mode 100644 index 0000000..ec2a0ea --- /dev/null +++ b/src/stats/StatsActivity.h @@ -0,0 +1,79 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +# include "config.h" + +#include "StatBase.h" + +#ifndef _STATSACTIVITY_H +#define _STATSACTIVITY_H + +class activity_info +{ + public: + bool active; + bool is_new; + unsigned int id; + unsigned long long last_r; + unsigned long long last_w; + unsigned long long last_rIOPS; + unsigned long long last_wIOPS; + + std::string device; + std::vector mounts; + std::deque samples[8]; +}; + +class StatsActivity : public StatsBase +{ + public: + void update(long long sampleID); + void init(); + void prepareUpdate(); + void createDisk(std::string key); + void processDisk(std::string key, long long sampleID, unsigned long long read, unsigned long long write, unsigned long long reads, unsigned long long writes); + + void _init(); + #ifdef USE_SQLITE + void updateHistory(); + activity_data historyItemAtIndex(int index, activity_info item); + void loadPreviousSamples(); + void loadPreviousSamplesAtIndex(int index); + #endif + + std::vector _items; + std::deque samples[8]; + + #ifdef HAVE_LIBKSTAT + kstat_ctl_t *ksh; + #endif + }; +#endif diff --git a/src/stats/StatsBattery.cpp b/src/stats/StatsBattery.cpp new file mode 100644 index 0000000..552b75c --- /dev/null +++ b/src/stats/StatsBattery.cpp @@ -0,0 +1,510 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "StatsBattery.h" + +using namespace std; + +#if defined(USE_BATTERY_APM) + +#ifndef APM_BATT_ABSENT +#define APM_BATT_ABSENT APM_BATTERY_ABSENT +#endif + +void StatsBattery::init() +{ + _init(); + + int fd = open("/dev/apm", O_RDONLY); + if(fd == -1) + return; + createBattery("apm", "", ""); +} + +void StatsBattery::update(long long sampleID) +{ + if(_items.size() == 0) + return; + + int fd = open("/dev/apm", O_RDONLY); + if(fd == -1) + return; + + int state = BatteryStateUnknown; + int source = BatterySourceAC; + int present = 1; + int percentage = -1; + float t = 0; + + struct apm_power_info info; + if (ioctl(fd, APM_IOC_GETPOWER, &info) == 0) + { + if(info.battery_life == 255) + percentage = 100; + else + percentage = info.battery_life; + + if(info.battery_state == APM_BATT_CHARGING) + { + if(percentage == 100) + state = BatteryStateCharged; + else + state = BatteryStateCharging; + } + else if(info.battery_state == APM_BATT_UNKNOWN || info.battery_state == APM_BATT_ABSENT) + { + + } + else + { + state = BatteryStateDraining; + } + + if(info.ac_state == APM_AC_ON) + { + if(percentage == 100) + state = BatteryStateCharged; + else + state = BatteryStateCharging; + source = BatterySourceAC; + } + else + source = BatterySourceBattery; + + if(info.minutes_left > 0 && info.minutes_left < 0xFFFF) + t = info.minutes_left; + + int timeRemaining = t; + + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + (*cur).state = state; + (*cur).source = source; + (*cur).present = present; + (*cur).percentage = percentage; + (*cur).timeRemaining = timeRemaining; + } + } + close(fd); +} + +#elif defined(USE_BATTERY_ACPI) && defined(HAVE_SYSCTLBYNAME) + +#ifndef UNKNOWN_CAP +#define UNKNOWN_CAP 0xffffffff +#endif + +#ifndef UNKNOWN_VOLTAGE +#define UNKNOWN_VOLTAGE 0xffffffff +#endif + +void StatsBattery::init() +{ + _init(); + + int batteries; + size_t len = sizeof(batteries); + + if (sysctlbyname("hw.acpi.battery.units", &batteries, &len, NULL, 0) >= 0) + { + int x; + for(x=0;x::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + union acpi_battery_ioctl_arg battio; + battio.unit = atoi((*cur).key.c_str()); + if (ioctl(acpifd, ACPIIO_BATT_GET_BIF, &battio) == -1) + { + (*cur).active = false; + continue; + } + + int state = BatteryStateUnknown; + int source = BatterySourceAC; + int present = 1; + int cycles = 0; + int percentage = -1; + int capacityDesign = 0; + int capacityFull = 0; + int capacityNow = 0; + int capacityRate = 0; + int voltage = 0; + float health = 0; + float t = 0; + + if (battio.bif.dcap != UNKNOWN_CAP) + capacityDesign = battio.bif.dcap; + + if (battio.bif.lfcap != UNKNOWN_CAP) + capacityFull = battio.bif.lfcap; + + + battio.unit = atoi((*cur).key.c_str()); + if (ioctl(acpifd, ACPIIO_BATT_GET_BATTINFO, &battio) == -1) + { + (*cur).active = false; + continue; + } + + (*cur).active = true; + + int acline = 0; + ioctl(acpifd, ACPIIO_ACAD_GET_STATUS, &acline); + + if (battio.battinfo.state != ACPI_BATT_STAT_NOT_PRESENT) + { + if (battio.battinfo.cap != -1) + percentage = battio.battinfo.cap; + + if(acline == 1) + { + source = BatterySourceAC; + + if (battio.battinfo.state & ACPI_BATT_STAT_CHARGING) + state = BatteryStateCharging; + else + { + if(percentage == 100) + state = BatteryStateCharged; + else + state = BatteryStateCharging; + } + } + else + { + source = BatterySourceBattery; + state = BatteryStateDraining; + } + + if (battio.battinfo.min != -1) + t = battio.battinfo.min; + } + + battio.unit = atoi((*cur).key.c_str()); + if (ioctl(acpifd, ACPIIO_BATT_GET_BST, &battio) != -1) + { + if (battio.bst.state != ACPI_BATT_STAT_NOT_PRESENT) { + if (battio.bst.volt != UNKNOWN_VOLTAGE) + voltage = battio.bst.volt; + } + } + +/* + if(percentage == -1) + { + if(capacityFull > 0 && capacityNow > 0) + percentage = (capacityNow / capacityFull) * 100; + else + percentage = 0; + }*/ + + if(capacityDesign > 0 && capacityFull > 0) + health = ((float)capacityFull / (float)capacityDesign) * 100; + + int timeRemaining = t; + + (*cur).state = state; + (*cur).source = source; + (*cur).present = present; + (*cur).cycles = cycles; + (*cur).percentage = percentage; + (*cur).capacityDesign = capacityDesign; + (*cur).capacityFull = capacityFull; + (*cur).capacityNow = capacityNow; + (*cur).capacityRate = capacityRate; + (*cur).voltage = voltage; + (*cur).timeRemaining = timeRemaining; + (*cur).health = health; + } + + close(acpifd); +} + +#elif defined(USE_BATTERY_PROCFS) + +bool StatsBattery::fileExists(string path) +{ + FILE * fp = NULL; + + if (!(fp = fopen(path.c_str(), "r"))) + { + return false; + } + + fclose(fp); + return true; +} + +void StatsBattery::init() +{ + _init(); + + DIR *dpdf; + struct dirent *epdf; + + vector adapters; + vector batteries; + string directory = string("/sys/class/power_supply/"); + + dpdf = opendir(directory.c_str()); + if (dpdf != NULL){ + while ((epdf = readdir(dpdf))){ + string file = string(epdf->d_name); + if(file.find("A") == 0) + { + string path = directory; + adapters.push_back(path.append(string(epdf->d_name))); + } + if(file.find("BAT") == 0) + { + string path = directory; + batteries.push_back(path.append(string(epdf->d_name))); + } + } + } + + if(batteries.size() > 0) + { + for (vector::iterator curbat = batteries.begin(); curbat != batteries.end(); ++curbat) + { + string uevent = (*curbat); + uevent = uevent.append("/uevent"); + if(!fileExists(uevent)) + continue; + + string batterysuffix = (*curbat).substr(3, (*curbat).length() - 3); + + string adapterpath = string(""); + if(batterysuffix.length() > 0 && adapters.size() > 0) + { + for (vector::iterator curadapter = adapters.begin(); curadapter != adapters.end(); ++curadapter) + { + string adaptersuffix = (*curadapter).substr((*curadapter).length() - batterysuffix.length(), batterysuffix.length()); + if(batterysuffix == adaptersuffix) + { + adapterpath = (*curadapter); + break; + } + } + } + + createBattery((*curbat), uevent, adapterpath); + } + } + closedir(dpdf); +} + +void StatsBattery::update(long long sampleID) +{ + if(_items.size() == 0) + return; + + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + FILE * fp = NULL; + char buf[512]; + + if (!(fp = fopen((*cur).path.c_str(), "r"))) + { + (*cur).present = 0; + continue; + } + + (*cur).active = true; + + int state = BatteryStateUnknown; + int source = BatterySourceAC; + int present = 1; + int cycles = 0; + int percentage = -1; + int capacityDesign = 0; + int capacityFull = 0; + int capacityNow = 0; + int capacityRate = 0; + int voltage = 0; + float health = 0; + + char charbuf[128]; + + while (fgets(buf, sizeof(buf), fp)) + { + sscanf(buf, "POWER_SUPPLY_PRESENT=%d", &present); + sscanf(buf, "POWER_SUPPLY_CYCLE_COUNT=%d", &cycles); + sscanf(buf, "POWER_SUPPLY_CAPACITY=%d", &percentage); + sscanf(buf, "POWER_SUPPLY_ENERGY_FULL_DESIGN=%d", &capacityDesign); + sscanf(buf, "POWER_SUPPLY_ENERGY_FULL=%d", &capacityFull); + sscanf(buf, "POWER_SUPPLY_ENERGY_NOW=%d", &capacityNow); + sscanf(buf, "POWER_SUPPLY_POWER_NOW=%d", &capacityRate); + sscanf(buf, "POWER_SUPPLY_VOLTAGE_NOW=%d", &voltage); + + if(sscanf(buf, "POWER_SUPPLY_STATUS=%s", charbuf) == 1) + { + string status = string(charbuf); + if(status.find("Charging") == 0) + { + source = BatterySourceAC; + state = BatteryStateCharging; + } + else if(status.find("Discharging") == 0) + { + source = BatterySourceBattery; + state = BatteryStateDraining; + } + else if(status.find("Charged") == 0) + { + source = BatterySourceAC; + state = BatteryStateCharged; + } + else + { + source = BatterySourceAC; + state = BatteryStateCharged; + } + } + } + + + if(percentage == -1) + { + if(capacityFull > 0 && capacityNow > 0) + percentage = (capacityNow / capacityFull) * 100; + else + percentage = 0; + } + + if(percentage > 100) + percentage = 100; + + float t = 0; + if(state == BatteryStateCharging) + { + int remainingUntilFull = capacityFull - capacityNow; + t = 0; + if(remainingUntilFull > 0 && capacityRate > 0) + t = ((float)remainingUntilFull / capacityRate); + } + else if(state == BatteryStateDraining) + { + t = 0; + if(capacityNow > 0 && capacityRate > 0) + t = ((float)capacityNow / capacityRate); + } + + int timeRemaining = t * 60; + + if(capacityDesign > 0 && capacityFull > 0) + health = ((float)capacityFull / (float)capacityDesign) * 100; + + (*cur).state = state; + (*cur).source = source; + (*cur).present = present; + (*cur).cycles = cycles; + (*cur).percentage = percentage; + (*cur).capacityDesign = capacityDesign; + (*cur).capacityFull = capacityFull; + (*cur).capacityNow = capacityNow; + (*cur).capacityRate = capacityRate; + (*cur).voltage = voltage; + (*cur).timeRemaining = timeRemaining; + (*cur).health = health; + + fclose(fp); + } +} + +#else + +void StatsBattery::init() +{ + _init(); +} + +void StatsBattery::update(long long sampleID) +{ +} + +#endif + +void StatsBattery::_init() +{ + initShared(); + ready = 1; +} + +void StatsBattery::prepareUpdate() +{ + StatsBase::prepareUpdate(); + + if(_items.size() > 0) + { + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + (*cur).active = false; + } + } +} + +void StatsBattery::createBattery(string key, string batterypath, string adapterpath) +{ + if(_items.size() > 0) + { + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + battery_info interface = *cur; + if (interface.key == key) + { + return; + } + } + } + + battery_info battery; + battery.key = key; + battery.path = batterypath; + battery.adapterpath = adapterpath; + + _items.insert(_items.begin(), battery); +} diff --git a/src/stats/StatsBattery.h b/src/stats/StatsBattery.h new file mode 100644 index 0000000..ab2e16f --- /dev/null +++ b/src/stats/StatsBattery.h @@ -0,0 +1,82 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "StatBase.h" + +#ifndef _STATSBATTERY_H +#define _STATSBATTERY_H + +#define BatteryStateUnknown -1 +#define BatteryStateCharged 0 +#define BatteryStateCharging 1 +#define BatteryStateDraining 2 +#define BatteryStateNotCharging 3 +#define BatteryStateNA 4 + +#define BatterySourceBattery 0 +#define BatterySourceAC 1 + +class battery_info +{ + public: + bool active; + long long sampleID; + std::string key; + std::string path; + std::string adapterpath; + + int state; + int source; + int present; + int cycles; + int percentage; + int capacityDesign; + int capacityFull; + int capacityNow; + int capacityRate; + int voltage; + int timeRemaining; + float health; +// std::vector history; +}; + +class StatsBattery : public StatsBase +{ + public: + void update(long long sampleID); + void init(); + void _init(); + bool fileExists(std::string path); + std::vector _items; + void createBattery(std::string key, std::string batterypath, std::string adapterpath); + void prepareUpdate(); + }; +#endif diff --git a/src/stats/StatsCPU.cpp b/src/stats/StatsCPU.cpp new file mode 100644 index 0000000..d745066 --- /dev/null +++ b/src/stats/StatsCPU.cpp @@ -0,0 +1,625 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "StatsCPU.h" + +using namespace std; + +#if defined(USE_CPU_HPUX) + +void StatsCPU::init() +{ + _init(); + + struct pst_dynamic pstat_dynamic; + if (pstat_getdynamic(&pstat_dynamic, sizeof(pstat_dynamic), 1, 0) == -1) { + return; + } + + int i; + for (i = 0; i < PST_MAX_CPUSTATES; i++) { + last_ticks[i] = (unsigned long long)pstat_dynamic.psd_cpu_time[i]; + } +} + +void StatsCPU::update(long long sampleID) +{ + struct pst_dynamic pstat_dynamic; + if (pstat_getdynamic(&pstat_dynamic, sizeof(pstat_dynamic), 1, 0) == -1) { + return; + } + + unsigned long long user = (unsigned long long)pstat_dynamic.psd_cpu_time[CP_USER]; + unsigned long long sys = (unsigned long long)pstat_dynamic.psd_cpu_time[CP_SSYS] + (unsigned long long)pstat_dynamic.psd_cpu_time[CP_SYS]; + unsigned long long nice = (unsigned long long)pstat_dynamic.psd_cpu_time[CP_NICE]; + unsigned long long idle = (unsigned long long)pstat_dynamic.psd_cpu_time[CP_IDLE]; + unsigned long long wait = (unsigned long long)pstat_dynamic.psd_cpu_time[CP_WAIT]; + + unsigned long long total = 0; + int i; + for (i = 0; i < PST_MAX_CPUSTATES; i++) { + total += (unsigned long long)pstat_dynamic.psd_cpu_time[i] - last_ticks[i]; + } + + processSample(sampleID, user, nice, sys, idle, wait, total); + + for (i = 0; i < PST_MAX_CPUSTATES; i++) { + last_ticks[i] = (unsigned long long)pstat_dynamic.psd_cpu_time[i]; + } +} + +#elif defined(USE_CPU_SYSCTL) + +void StatsCPU::init() +{ + _init(); + + int mib[2] = {CTL_HW, HW_NCPU}; + size_t size = sizeof(cpu_count); + if(sysctl(mib, 2, &cpu_count, &size, NULL, 0) < 0) + cpu_count = 1; +} + +void StatsCPU::update(long long sampleID) +{ + #if defined(__OpenBSD__) || defined(__OPENBSD) + unsigned long long cp_time[CPUSTATES]; + #else + long cp_time[CPUSTATES]; + #endif + + int x; + for(x=0;x HISTORY_SIZE) samples[0].pop_back(); +}/*USE_CPU_PERFSTAT*/ + +#else + +void StatsCPU::init() +{ + _init(); +} + +void StatsCPU::update(long long sampleID) +{ +} + +#endif + +void StatsCPU::_init() +{ + initShared(); + + last_ticks[0] = 0; last_ticks[1] = 0; last_ticks[2] = 0; last_ticks[3] = 0; last_ticks[4] = 0; + + #ifdef USE_SQLITE + if(historyEnabled == true) + { + databaseType = 0; + databasePrefix = "cpu_"; + + int x; + for(x=1;x<8;x++) + { + string table = databasePrefix + tableAtIndex(x); + if(!_database.tableExists(table)) + { + string sql = "create table " + table + " (sample double PRIMARY KEY NOT NULL, time double NOT NULL DEFAULT 0, empty integer NOT NULL DEFAULT 0, user double NOT NULL DEFAULT 0, system double NOT NULL DEFAULT 0, nice double NOT NULL DEFAULT 0, wait double NOT NULL DEFAULT 0)"; + DatabaseItem dbItem = _database.databaseItem(sql); + dbItem.executeUpdate(); + } + } + loadPreviousSamples(); + fillGaps(); + } + #endif +} + +#ifdef USE_SQLITE +void StatsCPU::loadPreviousSamples() +{ + loadPreviousSamplesAtIndex(1); + loadPreviousSamplesAtIndex(2); + loadPreviousSamplesAtIndex(3); + loadPreviousSamplesAtIndex(4); + loadPreviousSamplesAtIndex(5); + loadPreviousSamplesAtIndex(6); + loadPreviousSamplesAtIndex(7); +} + +void StatsCPU::loadPreviousSamplesAtIndex(int index) +{ + string table = databasePrefix + tableAtIndex(index); + double sampleID = sampleIdForTable(table); + + string sql = "select * from " + table + " where sample >= @sample order by sample asc limit 602"; + DatabaseItem query = _database.databaseItem(sql); + sqlite3_bind_double(query._statement, 1, sampleID - 602); + + while(query.next()) + { + cpu_data sample; + sample.u = query.doubleForColumn("user"); + sample.s = query.doubleForColumn("system"); + sample.n = query.doubleForColumn("nice"); + sample.io = query.doubleForColumn("wait"); + sample.sampleID = (long long)query.doubleForColumn("sample"); + sample.time = query.doubleForColumn("time"); + samples[index].insert(samples[index].begin(), sample); + } + if(samples[index].size() > 0) + { + sampleIndex[index].sampleID = samples[index][0].sampleID; + sampleIndex[index].time = samples[index][0].time; + sampleIndex[index].nextTime = sampleIndex[index].time + sampleIndex[index].interval; + } +} +#endif + +void StatsCPU::processSample(long long sampleID, unsigned long long user, unsigned long long nice, unsigned long long kernel, unsigned long long idle, unsigned long long wait, unsigned long long total) +{ + if(total > 0) + ticks = total; + else + { + if(last_ticks[0] == 0) + { + last_ticks[0] = user; + last_ticks[1] = nice; + last_ticks[2] = kernel; + last_ticks[3] = idle; + last_ticks[4] = wait; + } + ticks = (double)(user + kernel + nice + idle) - (last_ticks[0] + last_ticks[1] + last_ticks[2] + last_ticks[3]); + } + + cpu_data _cpu; + + if(ticks > 0) + { + _cpu.u = ((double)(user - last_ticks[0]) / ticks) * 100; + _cpu.n = ((double)(nice - last_ticks[1]) / ticks) * 100; + _cpu.s = ((double)(kernel - last_ticks[2]) / ticks) * 100; + _cpu.i = ((double)(idle - last_ticks[3]) / ticks) * 100; + _cpu.io = ((double)(wait - last_ticks[4]) / ticks) * 100; + } + else + { + _cpu.u = 0; + _cpu.n = 0; + _cpu.s = 0; + _cpu.i = 0; + _cpu.io = 0; + } + + _cpu.ent = -1; + _cpu.phys = -1; + + _cpu.sampleID = sampleIndex[0].sampleID; + _cpu.time = sampleIndex[0].time; + + if(total == 0) + { + last_ticks[0] = user; + last_ticks[1] = nice; + last_ticks[2] = kernel; + last_ticks[3] = idle; + last_ticks[4] = wait; + } + + samples[0].push_front(_cpu); + if (samples[0].size() > HISTORY_SIZE) samples[0].pop_back(); +} + +#ifdef USE_SQLITE +void StatsCPU::updateHistory() +{ + int x; + for (x = 1; x < 8; x++) + { + if(sampleIndex[0].time >= sampleIndex[x].nextTime) + { + double now = get_current_time(); + double earlistTime = now - (HISTORY_SIZE * sampleIndex[x].interval); + while(sampleIndex[x].nextTime < now) + { + sampleIndex[x].sampleID = sampleIndex[x].sampleID + 1; + sampleIndex[x].time = sampleIndex[x].nextTime; + sampleIndex[x].nextTime = sampleIndex[x].nextTime + sampleIndex[x].interval; + + if(sampleIndex[x].time < earlistTime) + continue; + + cpu_data sample = historyItemAtIndex(x); + samples[x].push_front(sample); + if (samples[x].size() > HISTORY_SIZE) samples[x].pop_back(); + + if(sample.empty) + continue; + + string table = databasePrefix + tableAtIndex(x); + string sql = "insert into " + table + " (empty, sample, time, user, system, nice, wait) values(?, ?, ?, ?, ?, ?, ?)"; + + DatabaseItem dbItem = _database.databaseItem(sql); + sqlite3_bind_int(dbItem._statement, 1, 0); + sqlite3_bind_double(dbItem._statement, 2, (double)sample.sampleID); + sqlite3_bind_double(dbItem._statement, 3, sample.time); + sqlite3_bind_double(dbItem._statement, 4, sample.u); + sqlite3_bind_double(dbItem._statement, 5, sample.s); + sqlite3_bind_double(dbItem._statement, 6, sample.n); + sqlite3_bind_double(dbItem._statement, 7, sample.io); + databaseQueue.push_back(dbItem); + } + } + } +} + +cpu_data StatsCPU::historyItemAtIndex(int index) +{ + std::deque from = samples[sampleIndex[index].historyIndex]; + double minimumTime = sampleIndex[index].time - sampleIndex[index].interval; + double maximumTime = sampleIndex[index].time; + if(sampleIndex[index].historyIndex == 0) + maximumTime += 0.99; + + double cpuUser = 0; + double cpuSystem = 0; + double cpuNice = 0; + double cpuIO = 0; + cpu_data sample; + + int count = 0; + if(from.size() > 0) + { + for (deque::iterator cur = from.begin(); cur != from.end(); ++cur) + { + if ((*cur).time > maximumTime) + continue; + + if ((*cur).time < minimumTime) + break; + + cpuUser += (*cur).u; + cpuSystem += (*cur).s; + cpuNice += (*cur).n; + cpuIO += (*cur).io; + count++; + } + if (count > 0) + { + cpuUser /= count; + cpuSystem /= count; + cpuNice /= count; + cpuIO /= count; + } + } + + sample.u = cpuUser; + sample.s = cpuSystem; + sample.n = cpuNice; + sample.io = cpuIO; + + sample.sampleID = sampleIndex[index].sampleID; + sample.time = sampleIndex[index].time; + sample.empty = false; + if(count == 0) + sample.empty = true; + + return sample; +} +#endif diff --git a/src/stats/StatsCPU.h b/src/stats/StatsCPU.h new file mode 100644 index 0000000..7c035ed --- /dev/null +++ b/src/stats/StatsCPU.h @@ -0,0 +1,75 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "StatBase.h" + +#ifndef _STATSCPU_H +#define _STATSCPU_H + +class StatsCPU : public StatsBase +{ + public: + void _init(); + void init(); + void update(long long sampleID); + void processSample(long long sampleID, unsigned long long user, unsigned long long nice, unsigned long long kernel, unsigned long long idle, unsigned long long wait, unsigned long long total); + #ifdef USE_SQLITE + void updateHistory(); + cpu_data historyItemAtIndex(int index); + void loadPreviousSamples(); + void loadPreviousSamplesAtIndex(int index); + #endif + + std::deque samples[8]; + + #ifdef PST_MAX_CPUSTATES + unsigned long long last_ticks[PST_MAX_CPUSTATES]; + #elif defined(CPUSTATES) + unsigned long long last_ticks[CPUSTATES]; + #else + unsigned long long last_ticks[5]; + #endif + unsigned long long last_ticks_lpar[5]; + unsigned long long last_time; + double ticks; + int cpu_count; + bool hasLpar; + double aixEntitlement; + + #ifdef HAVE_LIBKSTAT + kstat_ctl_t *ksh; + #endif + + #ifdef USE_CPU_PROCFS + bool hasIOWait; + #endif + }; +#endif diff --git a/src/stats/StatsDisks.cpp b/src/stats/StatsDisks.cpp new file mode 100644 index 0000000..dd56bd5 --- /dev/null +++ b/src/stats/StatsDisks.cpp @@ -0,0 +1,606 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "StatsDisks.h" + +#ifndef _PATH_MOUNTED +#ifdef MNTTAB +#define _PATH_MOUNTED MNTTAB +#else +#define _PATH_MOUNTED "/etc/mtab" +#endif +#endif + +using namespace std; + +#ifdef USE_DISK_STATFS +int StatsDisks::get_sizes(const char *dev, struct disk_data *data) +{ +#ifdef HAVE_STATVFS + struct statvfs space; + if (statvfs(dev, &space) == 0) +#else + struct statfs space; + if (statfs(dev, &space) == 0) +#endif + + { + unsigned long long bsize; +#ifdef HAVE_STATVFS_FRSIZE + bsize = space.f_frsize; +#else + bsize = space.f_bsize; +#endif + + data->t = (double)(space.f_blocks * bsize); + data->u = (double)((space.f_blocks - space.f_bavail) * bsize); + data->f = data->t - data->u; + if(data->t > 0) + data->p = ((float) data->u / data->t) * 100; + else + data->p = 0; + + return 0; + } + + return -1; +} + +int StatsDisks::should_ignore_mount(char *mount) +{ + if(disableFiltering == 1) + return 0; + + if(!strncmp(mount, "/export", 7)) + return 1; +// if(!strncmp(mount, "/var", 4)) +// return 1; + if(!strncmp(mount, "/boot", 5)) + return 1; + if(!strncmp(mount, "/tmp", 4)) + return 1; + if(!strncmp(mount, "/system", 7)) + return 1; + if(!strncmp(mount, "/usr/obj", 8)) + return 1; + if(!strncmp(mount, "/usr/src", 8)) + return 1; + if(!strncmp(mount, "/usr/local", 10)) + return 1; + if(!strncmp(mount, "/usr/X11", 8)) + return 1; + if(!strcmp(mount, "/usr")) + return 1; + if(!strcmp(mount, "/kern")) + return 1; + if(!strcmp(mount, "/dev/pts")) + return 1; + if(!strcmp(mount, "/compat/linux/proc")) + return 1; + if(!strncmp(mount, "/usr/jails/.", 12)) + return 1; + if(!strncmp(mount, "/usr/home", 9)) + return 1; + if(!strcmp(mount, "/usr/jails")) + return 1; + if(!strcmp(mount, "/usr/ports")) + return 1; + return 0; +} + +int StatsDisks::should_ignore_type(char *type) +{ + if(!strcmp(type, "rpc_pipefs")) + return 1; + if(!strcmp(type, "rootfs")) + return 1; + if(!strcmp(type, "configfs")) + return 1; + if(!strcmp(type, "hugetlbfs")) + return 1; + if(!strcmp(type, "nfsd")) + return 1; + if(!strcmp(type, "mqueue")) + return 1; + if(!strcmp(type, "selinuxfs")) + return 1; + if(!strcmp(type, "linprocfs")) + return 1; + if(!strcmp(type, "binfmt_misc")) + return 1; + if(!strcmp(type, "tmpfs")) + return 1; + if(!strcmp(type, "devpts")) + return 1; + if(!strcmp(type, "devtmpfs")) + return 1; + if(!strcmp(type, "pstore")) + return 1; + if(!strcmp(type, "prl_fs")) + return 1; + if(!strcmp(type, "cgroup")) + return 1; + if(!strcmp(type, "iso9660")) + return 1; + if(!strcmp(type, "securityfs")) + return 1; + if(!strcmp(type, "fusectl")) + return 1; + if(!strcmp(type, "proc")) + return 1; + if(!strcmp(type, "procfs")) + return 1; + if(!strcmp(type, "debugfs")) + return 1; + if(!strcmp(type, "fuse.gvfsd-fuse")) + return 1; + if(!strcmp(type, "sysfs")) + return 1; + if(!strcmp(type, "devfs")) + return 1; + if(!strcmp(type, "autofs")) + return 1; + if(!strcmp(type, "fd")) + return 1; + if(!strcmp(type, "lofs")) + return 1; + if(!strcmp(type, "sharefs")) + return 1; + if(!strcmp(type, "objfs")) + return 1; + if(!strcmp(type, "mntfs")) + return 1; + if(!strcmp(type, "dev")) + return 1; + return 0; +} + +void StatsDisks::update(long long sampleID) +{ + #ifdef USE_STRUCT_MNTENT + struct mntent *entry; + FILE *table; + if (!(table = setmntent(_PATH_MOUNTED, "r"))) return; + while ((entry = getmntent(table)) != 0) + { + processDisk(entry->mnt_fsname, entry->mnt_dir, entry->mnt_type); + } + endmntent(table); + + #elif defined(USE_STRUCT_MNTTAB) + struct mnttab *entry, ebuf; + FILE *table; + if (!(table = fopen(_PATH_MOUNTED, "r"))) return; + resetmnttab(table); + + entry = &ebuf; + while (!getmntent(table, entry)) + { + processDisk(entry->mnt_special, entry->mnt_mountp, entry->mnt_fstype); + } + + fclose(table); + #else + #ifdef GETMNTINFO_STATVFS + struct statvfs *entry; + #else + struct statfs *entry; + #endif + + int mnts; + if (!(mnts = getmntinfo(&entry, MNT_NOWAIT))) + return; + + int x; + for(x=0;x 0) + { + for (vector::iterator curdisk = _items.begin(); curdisk != _items.end(); ++curdisk) + { + if(string(name) == (*curdisk).key) + { + exists = true; + break; + } + } + } + + if(!exists && (should_ignore_type(type) || should_ignore_mount(mount))) + return; + + createDisk(name); + + for (vector::iterator curdisk = _items.begin(); curdisk != _items.end(); ++curdisk) + { + if(string(name) == (*curdisk).key) + { + if((*curdisk).is_new == true) + { + (*curdisk).is_new = false; + + if((*curdisk).uuid.size() == 0) + { + #ifdef USE_DISKUUID_PROCFS + DIR *dpdf; + struct dirent *epdf; + dpdf = opendir("/dev/disk/by-uuid"); + if (dpdf != NULL){ + while ((epdf = readdir(dpdf))){ + string file = string(epdf->d_name); + + char buff[PATH_MAX]; + string fullpath = "/dev/disk/by-uuid/" + file; + ssize_t len = ::readlink(fullpath.c_str(), buff, sizeof(buff)-1); + if (len != -1) { + buff[len] = '\0'; + vector components = explode(string(buff), "/"); + if(components.size() > 0) + { + string p = "/dev/" + components.back(); + if(p == (*curdisk).key) + (*curdisk).uuid = file; + } + } + } + closedir(dpdf); + } + #endif + if((*curdisk).uuid.size() == 0) + (*curdisk).uuid = (*curdisk).key; + } + + (*curdisk).name = string(mount); + + string disk_label, disk_label_custom; + disk_label = ""; + disk_label_custom = ""; + + // Read custom disk label from config + vector conf_label; + for (vector::iterator cur_label = customNames.begin(); cur_label != customNames.end(); ++cur_label) + { + conf_label = explode(*cur_label); + + if (conf_label[1].length()) + { + if (conf_label[0] == (*curdisk).key || conf_label[0] == (*curdisk).name) + { + disk_label_custom = trim(conf_label[1], "\""); + break; + } + } + } + + if (useMountPaths) + disk_label = (*curdisk).name; + else + disk_label = (*curdisk).key; + + // Set custom disk label if configured. Will override everything. + if (disk_label_custom.length()) + disk_label = disk_label_custom; + + (*curdisk).displayName = disk_label; + + loadHistoryForDisk(&(*curdisk)); + } + + if(ready == 0) + { + (*curdisk).active = true; + break; + } + + disk_data data; + if (get_sizes(mount, &data) == 0) + { + (*curdisk).active = true; + + data.sampleID = sampleIndex[0].sampleID; + data.time = sampleIndex[0].time; + + (*curdisk).samples[0].push_front(data); + if ((*curdisk).samples[0].size() > HISTORY_SIZE) + (*curdisk).samples[0].pop_back(); + } + } + } +} + +#else /*USE_DISK_STATFS*/ + +void StatsDisks::update(long long sampleID) +{ + +} + +#endif + +void StatsDisks::prepareUpdate() +{ + if(ready == 0) + return; + + StatsBase::prepareUpdate(); + + if(_items.size() > 0) + { + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + (*cur).active = false; + } + } +} + +void StatsDisks::createDisk(string key) +{ + if(_items.size() > 0) + { + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + disk_info interface = *cur; + if (interface.key == key) + { + return; + } + } + } + + disk_info item; + item.is_new = true; + item.key = key; + session++; + + _items.insert(_items.begin(), item); +} + +void StatsDisks::loadHistoryForDisk(disk_info *disk) +{ +#ifdef USE_SQLITE + if(historyEnabled == true) + { + int x; + for(x=1;x<8;x++) + { + string table = databasePrefix + tableAtIndex(x); + double sampleID = 0; + if(samples[x].size() > 0) + sampleID = samples[x][0].sampleID; + + + string sql = "select * from " + table + " where sample >= @sample AND uuid = ? order by sample asc limit 602"; + DatabaseItem query = _database.databaseItem(sql); + sqlite3_bind_double(query._statement, 1, sampleID - 602); + sqlite3_bind_text(query._statement, 2, disk->uuid.c_str(), -1, SQLITE_STATIC); + + while(query.next()) + { + disk_data sample; + sample.t = query.doubleForColumn("total"); + sample.u = query.doubleForColumn("used"); + sample.f = query.doubleForColumn("free"); + sample.sampleID = (long long)query.doubleForColumn("sample"); + sample.time = query.doubleForColumn("time"); + disk->samples[x].push_front(sample); + } + } + } +#endif +} + +void StatsDisks::init() +{ + initShared(); + ready = 0; + + #ifdef USE_SQLITE + if(historyEnabled == true) + { + databaseType = 1; + databasePrefix = "disk_"; + + int x; + for(x=1;x<8;x++) + { + string table = databasePrefix + tableAtIndex(x) + "_id"; + if(!_database.tableExists(table)) + { + string sql = "create table " + table + " (sample double PRIMARY KEY NOT NULL, time double NOT NULL DEFAULT 0, empty integer NOT NULL DEFAULT 0)"; + DatabaseItem dbItem = _database.databaseItem(sql); + dbItem.executeUpdate(); + } + + table = databasePrefix + tableAtIndex(x); + if(!_database.tableExists(table)) + { + string sql = "create table " + table + " (sample double NOT NULL, time double NOT NULL DEFAULT 0, uuid varchar(255) NOT NULL, total double NOT NULL DEFAULT 0, used double NOT NULL DEFAULT 0, free double NOT NULL DEFAULT 0)"; + DatabaseItem dbItem = _database.databaseItem(sql); + dbItem.executeUpdate(); + } + } + loadPreviousSamples(); + fillGaps(); + } + #endif +} + +#ifdef USE_SQLITE +void StatsDisks::loadPreviousSamples() +{ + loadPreviousSamplesAtIndex(1); + loadPreviousSamplesAtIndex(2); + loadPreviousSamplesAtIndex(3); + loadPreviousSamplesAtIndex(4); + loadPreviousSamplesAtIndex(5); + loadPreviousSamplesAtIndex(6); + loadPreviousSamplesAtIndex(7); +} + +void StatsDisks::loadPreviousSamplesAtIndex(int index) +{ + + string table = databasePrefix + tableAtIndex(index) + "_id"; + double sampleID = sampleIdForTable(table); + + string sql = "select * from " + table + " where sample >= @sample order by sample asc limit 602"; + DatabaseItem query = _database.databaseItem(sql); + sqlite3_bind_double(query._statement, 1, sampleID - 602); + + while(query.next()) + { + sample_data sample; + sample.sampleID = (long long)query.doubleForColumn("sample"); + sample.time = query.doubleForColumn("time"); + samples[index].insert(samples[index].begin(), sample); + } + if(samples[index].size() > 0) + { + sampleIndex[index].sampleID = samples[index][0].sampleID; + sampleIndex[index].time = samples[index][0].time; + sampleIndex[index].nextTime = sampleIndex[index].time + sampleIndex[index].interval; + } +} + +void StatsDisks::updateHistory() +{ + int x; + for (x = 1; x < 8; x++) + { + if(sampleIndex[0].time >= sampleIndex[x].nextTime) + { + double now = get_current_time(); + double earlistTime = now - (HISTORY_SIZE * sampleIndex[x].interval); + while(sampleIndex[x].nextTime < now) + { + sampleIndex[x].sampleID = sampleIndex[x].sampleID + 1; + sampleIndex[x].time = sampleIndex[x].nextTime; + sampleIndex[x].nextTime = sampleIndex[x].nextTime + sampleIndex[x].interval; + + if(sampleIndex[x].time < earlistTime) + continue; + + if(_items.size() > 0) + { + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + disk_data sample = historyItemAtIndex(x, (*cur)); + + (*cur).samples[x].push_front(sample); + if ((*cur).samples[x].size() > HISTORY_SIZE) (*cur).samples[x].pop_back(); + + if(sample.empty) + continue; + + string table = databasePrefix + tableAtIndex(x); + string sql = "insert into " + table + " (sample, time, total, used, free, uuid) values(?, ?, ?, ?, ?, ?)"; + + DatabaseItem dbItem = _database.databaseItem(sql); + sqlite3_bind_double(dbItem._statement, 1, (double)sample.sampleID); + sqlite3_bind_double(dbItem._statement, 2, sample.time); + sqlite3_bind_double(dbItem._statement, 3, sample.t); + sqlite3_bind_double(dbItem._statement, 4, sample.u); + sqlite3_bind_double(dbItem._statement, 5, sample.f); + sqlite3_bind_text(dbItem._statement, 6, (*cur).uuid.c_str(), -1, SQLITE_STATIC); + databaseQueue.push_back(dbItem); + } + } + + string table = databasePrefix + tableAtIndex(x) + "_id"; + string sql = "insert into " + table + " (empty, sample, time) values(?, ?, ?)"; + + DatabaseItem dbItem = _database.databaseItem(sql); + sqlite3_bind_int(dbItem._statement, 1, 0); + sqlite3_bind_double(dbItem._statement, 2, (double)sampleIndex[x].sampleID); + sqlite3_bind_double(dbItem._statement, 3, sampleIndex[x].time); + databaseQueue.push_back(dbItem); + } + } + } +} + +disk_data StatsDisks::historyItemAtIndex(int index, disk_info item) +{ + disk_data sample; + double u = 0; + double f = 0; + double t = 0; + + std::deque from = item.samples[sampleIndex[index].historyIndex]; + double minimumTime = sampleIndex[index].time - sampleIndex[index].interval; + double maximumTime = sampleIndex[index].time; + if(sampleIndex[index].historyIndex == 0) + maximumTime += 0.99; + + int count = 0; + if(from.size() > 0) + { + for (deque::iterator cursample = from.begin(); cursample != from.end(); ++cursample) + { + if ((*cursample).time > maximumTime) + continue; + + if ((*cursample).time < minimumTime) + break; + + u += (*cursample).u; + f += (*cursample).f; + t = (*cursample).t; + count++; + } + if (count > 0) + { + u /= count; + f /= count; + } + } + + sample.u = u; + sample.f = f; + sample.t = t; + + sample.sampleID = sampleIndex[index].sampleID; + sample.time = sampleIndex[index].time; + sample.empty = false; + if(count == 0) + sample.empty = true; + + return sample; +} +#endif diff --git a/src/stats/StatsDisks.h b/src/stats/StatsDisks.h new file mode 100644 index 0000000..930242b --- /dev/null +++ b/src/stats/StatsDisks.h @@ -0,0 +1,78 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "StatBase.h" + +#ifndef _STATSDISKS_H +#define _STATSDISKS_H + +class disk_info +{ + public: + bool active; + bool is_new; + unsigned int id; + std::string uuid; + std::string label; + std::string name; + std::string key; + std::string displayName; + double last_update; + std::deque samples[8]; +}; +class StatsDisks : public StatsBase +{ + public: + int useMountPaths; + int disableFiltering; + void update(long long sampleID); + int sensorType(int type); + void prepareUpdate(); + void init(); + std::vector _items; + void createDisk(std::string key); + void processDisk(char *name, char *mount, char *type); + int get_sizes(const char *dev, struct disk_data *data); + int should_ignore_type(char *type); + int should_ignore_mount(char *mount); + std::vector customNames; + + void loadHistoryForDisk(disk_info *disk); + #ifdef USE_SQLITE + void updateHistory(); + disk_data historyItemAtIndex(int index, disk_info item); + void loadPreviousSamples(); + void loadPreviousSamplesAtIndex(int index); + #endif + + std::deque samples[8]; + }; +#endif diff --git a/src/stats/StatsLoad.cpp b/src/stats/StatsLoad.cpp new file mode 100644 index 0000000..8f01a37 --- /dev/null +++ b/src/stats/StatsLoad.cpp @@ -0,0 +1,268 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "StatsLoad.h" + +using namespace std; + +#ifdef USE_LOAD_HPUX + +void StatsLoad::update(long long sampleID) +{ + struct pst_dynamic pstat_dynamic; + if (pstat_getdynamic(&pstat_dynamic, sizeof(pstat_dynamic), 1, 0) == -1) { + return; + } + + struct load_data load; + load.one = pstat_dynamic.psd_avg_1_min; + load.two = pstat_dynamic.psd_avg_5_min; + load.three = pstat_dynamic.psd_avg_15_min; + + addSample(load, sampleID); +} /*USE_LOAD_HPUX*/ + +#elif defined(USE_LOAD_PERFSTAT) + +void StatsLoad::update(long long sampleID) +{ + struct load_data load; + perfstat_cpu_total_t cpustats; + perfstat_cpu_total(NULL, &cpustats, sizeof cpustats, 1); + load.one = cpustats.loadavg[0]/(float)(1< HISTORY_SIZE) samples[0].pop_back(); +} + +#ifdef USE_SQLITE +void StatsLoad::loadPreviousSamples() +{ + loadPreviousSamplesAtIndex(1); + loadPreviousSamplesAtIndex(2); + loadPreviousSamplesAtIndex(3); + loadPreviousSamplesAtIndex(4); + loadPreviousSamplesAtIndex(5); + loadPreviousSamplesAtIndex(6); + loadPreviousSamplesAtIndex(7); +} + +void StatsLoad::loadPreviousSamplesAtIndex(int index) +{ + string table = databasePrefix + tableAtIndex(index); + double sampleID = sampleIdForTable(table); + + string sql = "select * from " + table + " where sample >= @sample order by sample asc limit 602"; + DatabaseItem query = _database.databaseItem(sql); + sqlite3_bind_double(query._statement, 1, sampleID - 602); + + while(query.next()) + { + load_data sample; + sample.one = query.doubleForColumn("one"); + sample.two = query.doubleForColumn("five"); + sample.three = query.doubleForColumn("fifteen"); + sample.sampleID = (long long)query.doubleForColumn("sample"); + sample.time = query.doubleForColumn("time"); + samples[index].insert(samples[index].begin(), sample); + } + if(samples[index].size() > 0) + { + sampleIndex[index].sampleID = samples[index][0].sampleID; + sampleIndex[index].time = samples[index][0].time; + sampleIndex[index].nextTime = sampleIndex[index].time + sampleIndex[index].interval; + } +} + +void StatsLoad::updateHistory() +{ + int x; + for (x = 1; x < 8; x++) + { + if(sampleIndex[0].time >= sampleIndex[x].nextTime) + { + double now = get_current_time(); + double earlistTime = now - (HISTORY_SIZE * sampleIndex[x].interval); + while(sampleIndex[x].nextTime < now) + { + sampleIndex[x].sampleID = sampleIndex[x].sampleID + 1; + sampleIndex[x].time = sampleIndex[x].nextTime; + sampleIndex[x].nextTime = sampleIndex[x].nextTime + sampleIndex[x].interval; + + if(sampleIndex[x].time < earlistTime) + continue; + + load_data sample = historyItemAtIndex(x); + + samples[x].push_front(sample); + if (samples[x].size() > HISTORY_SIZE) samples[x].pop_back(); + + if(sample.empty) + continue; + + string table = databasePrefix + tableAtIndex(x); + string sql = "insert into " + table + " (empty, sample, time, one, five, fifteen) values(?, ?, ?, ?, ?, ?)"; + + DatabaseItem dbItem = _database.databaseItem(sql); + sqlite3_bind_int(dbItem._statement, 1, 0); + sqlite3_bind_double(dbItem._statement, 2, (double)sample.sampleID); + sqlite3_bind_double(dbItem._statement, 3, sample.time); + sqlite3_bind_double(dbItem._statement, 4, sample.one); + sqlite3_bind_double(dbItem._statement, 5, sample.two); + sqlite3_bind_double(dbItem._statement, 6, sample.three); + databaseQueue.push_back(dbItem); + } + } + } +} + +load_data StatsLoad::historyItemAtIndex(int index) +{ + std::deque from = samples[sampleIndex[index].historyIndex]; + double minimumTime = sampleIndex[index].time - sampleIndex[index].interval; + double maximumTime = sampleIndex[index].time; + if(sampleIndex[index].historyIndex == 0) + maximumTime += 0.99; + + double load1 = 0; + double load5 = 0; + double load15 = 0; + load_data sample; + + int count = 0; + if(from.size() > 0) + { + for (deque::iterator cur = from.begin(); cur != from.end(); ++cur) + { + if ((*cur).time > maximumTime) + continue; + + if ((*cur).time < minimumTime) + break; + + load1 += (*cur).one; + load5 += (*cur).two; + load15 += (*cur).three; + count++; + } + if (count > 0) + { + load1 /= count; + load15 /= count; + load15 /= count; + } + } + + sample.one = load1; + sample.two = load15; + sample.three = load15; + + sample.sampleID = sampleIndex[index].sampleID; + sample.time = sampleIndex[index].time; + sample.empty = false; + if(count == 0) + sample.empty = true; + + return sample; +} +#endif diff --git a/src/stats/StatsLoad.h b/src/stats/StatsLoad.h new file mode 100644 index 0000000..9025472 --- /dev/null +++ b/src/stats/StatsLoad.h @@ -0,0 +1,56 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "StatBase.h" + +#ifndef _STATSLOAD_H +#define _STATSLOAD_H + +class StatsLoad : public StatsBase +{ + public: + void update(long long sampleID); + void addSample(load_data data, long long sampleID); + std::deque samples[8]; + + void init(); + #ifdef USE_SQLITE + void updateHistory(); + load_data historyItemAtIndex(int index); + void loadPreviousSamples(); + void loadPreviousSamplesAtIndex(int index); + #endif + + #ifdef HAVE_LIBKSTAT + kstat_ctl_t *ksh; + #endif + }; +#endif diff --git a/src/stats/StatsMemory.cpp b/src/stats/StatsMemory.cpp new file mode 100644 index 0000000..7d116c8 --- /dev/null +++ b/src/stats/StatsMemory.cpp @@ -0,0 +1,957 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "StatsMemory.h" + +using namespace std; + +#if defined(USE_MEM_HPUX) + +void StatsMemory::init() +{ + databaseKeys.push_back("total"); + databaseKeys.push_back("free"); + databaseKeys.push_back("used"); + databaseKeys.push_back("swaptotal"); + databaseKeys.push_back("swapused"); + + databaseMap.push_back(memory_value_total); + databaseMap.push_back(memory_value_free); + databaseMap.push_back(memory_value_used); + databaseMap.push_back(memory_value_swaptotal); + databaseMap.push_back(memory_value_swapused); + + _init(); +} + +void StatsMemory::update(long long sampleID) +{ + mem_data _mem; + prepareSample(&_mem); + + struct pst_static pstat_static; + struct pst_dynamic pstat_dynamic; + struct pst_vminfo vminfo; + + if( pstat_getstatic(&pstat_static, sizeof(pstat_static), 1, 0) == -1 ) { + return; + } + + if (pstat_getdynamic(&pstat_dynamic, sizeof(pstat_dynamic), 1, 0) == -1) { + return; + } + + if(pstat_getvminfo( &vminfo, sizeof(vminfo), 1, 0 ) == -1 ) { + return; + } + + _mem.values[memory_value_total] = (double)(pstat_static.physical_memory * pstat_static.page_size); + _mem.values[memory_value_free] = (double)(pstat_dynamic.psd_free * pstat_static.page_size); + _mem.values[memory_value_used] = (double)(_mem.values[memory_value_total] - _mem.values[memory_value_free]); + + + struct pst_swapinfo swap; + int i = 0; + + while (pstat_getswap(&pss, sizeof(pss), (size_t) 1, i) != -1) { + i++; + + if ((swap.pss_flags & SW_ENABLED) != SW_ENABLED) + continue; + + if ((swap.pss_flags & SW_BLOCK) == SW_BLOCK) { + _mem.values[memory_value_swaptotal] += (double)(swap.pss_nblksavail * 1024); + _mem.values[memory_value_swapused] += (double)(swap.pss_nfpgs * 1024); + } + + if ((swap.pss_flags & SW_FS) == SW_FS) { + _mem.values[memory_value_swaptotal] += (double)(swap.pss_limit * 1024); + _mem.values[memory_value_swapused] += (double)(swap.pss_allocated * 1024); + } + } + + //vminfo.psv_spgin; + //vminfo.psv_spgout; + + + addSample(_mem, sampleID); +} + +#elif defined(USE_MEM_SYSCTLBYNAME) && defined(HAVE_SYSCTLBYNAME) + +void StatsMemory::init() +{ + databaseKeys.push_back("total"); + databaseKeys.push_back("free"); + databaseKeys.push_back("active"); + databaseKeys.push_back("inactive"); + databaseKeys.push_back("wired"); + databaseKeys.push_back("cached"); + databaseKeys.push_back("used"); + databaseKeys.push_back("buffer"); + databaseKeys.push_back("swaptotal"); + databaseKeys.push_back("swapused"); + + databaseMap.push_back(memory_value_total); + databaseMap.push_back(memory_value_free); + databaseMap.push_back(memory_value_active); + databaseMap.push_back(memory_value_inactive); + databaseMap.push_back(memory_value_wired); + databaseMap.push_back(memory_value_cached); + databaseMap.push_back(memory_value_used); + databaseMap.push_back(memory_value_buffer); + databaseMap.push_back(memory_value_swaptotal); + databaseMap.push_back(memory_value_swapused); + + _init(); +} + +void StatsMemory::update(long long sampleID) +{ + mem_data _mem; + prepareSample(&_mem); + + u_int _total, _inactive, _free, _active, _cached, _wired, _swaptotal, _swapused; + long _buf; + + int pagesize = getpagesize(); + + size_t len = sizeof(_total); + + if (sysctlbyname("vm.stats.vm.v_page_count", &_total, &len, NULL, 0) < 0) + _total = 0; + + if (sysctlbyname("vm.stats.vm.v_free_count", &_free, &len, NULL, 0) < 0) + _free = 0; + + if (sysctlbyname("vm.stats.vm.v_inactive_count", &_inactive, &len, NULL, 0) < 0) + _inactive = 0; + + if (sysctlbyname("vm.stats.vm.v_active_count", &_active, &len, NULL, 0) < 0) + _active = 0; + + if (sysctlbyname("vm.stats.vm.v_cache_count", &_cached, &len, NULL, 0) < 0) + _cached = 0; + + if (sysctlbyname("vm.stats.vm.v_wire_count", &_wired, &len, NULL, 0) < 0) + _wired = 0; + + if (sysctlbyname("vm.swap_size", &_swaptotal, &len, NULL, 0) < 0) + _swaptotal = 0; + + if (sysctlbyname("vm.swap_anon_use", &_swapused, &len, NULL, 0) < 0) + _swapused = 0; + + len = sizeof(_buf); + if (sysctlbyname("vfs.bufspace", &_buf, &len, NULL, 0) < 0) + _buf = 0; + + _mem.values[memory_value_total] = (double)(_total * pagesize); + _mem.values[memory_value_free] = (double)(_free * pagesize); + _mem.values[memory_value_active] = (double)(_active * pagesize); + _mem.values[memory_value_inactive] = (double)(_inactive * pagesize); + _mem.values[memory_value_wired] = (double)(_wired * pagesize); + _mem.values[memory_value_cached] = (double)(_cached * pagesize); + _mem.values[memory_value_used] = (double)(_mem.values[memory_value_total] - (_mem.values[memory_value_free] + _mem.values[memory_value_inactive])); + _mem.values[memory_value_buffer] = (double)(_buf); + _mem.values[memory_value_swapused] = (double)(_swapused * pagesize); + _mem.values[memory_value_swaptotal] = (double)(_swaptotal * pagesize); + + addSample(_mem, sampleID); +} + +#elif defined(USE_MEM_SYSCTL) + +void StatsMemory::init() +{ + + databaseKeys.push_back("total"); + databaseKeys.push_back("free"); + databaseKeys.push_back("used"); + databaseKeys.push_back("active"); + databaseKeys.push_back("inactive"); + databaseKeys.push_back("wired"); + databaseKeys.push_back("swaptotal"); + databaseKeys.push_back("swapused"); + +#ifdef VFS_BCACHESTAT + databaseKeys.push_back("buffer"); +#endif + +#ifdef VM_UVMEXP2 + databaseKeys.push_back("file"); + databaseKeys.push_back("exec"); +#endif + + databaseMap.push_back(memory_value_total); + databaseMap.push_back(memory_value_free); + databaseMap.push_back(memory_value_used); + databaseMap.push_back(memory_value_active); + databaseMap.push_back(memory_value_inactive); + databaseMap.push_back(memory_value_wired); + databaseMap.push_back(memory_value_swaptotal); + databaseMap.push_back(memory_value_swapused); + +#ifdef VFS_BCACHESTAT + databaseMap.push_back(memory_value_buffer); +#endif + +#ifdef VM_UVMEXP2 + databaseMap.push_back(memory_value_file); + databaseMap.push_back(memory_value_ex); +#endif + + _init(); +} + +void StatsMemory::updateSwap(mem_data* _mem) +{ + struct swapent *swdev; + int nswap, rnswap, i; + + nswap = swapctl(SWAP_NSWAP, 0, 0); + if (nswap == 0) + return; + + swdev = (struct swapent *)calloc(nswap, sizeof(*swdev)); + if (swdev == NULL) + return; + + rnswap = swapctl(SWAP_STATS, swdev, nswap); + if (rnswap == -1) { + free(swdev); + return; + } + + /* if rnswap != nswap, then what? */ + /* Total things up */ + double total = 0; + double used = 0; + for (i = 0; i < nswap; i++) { + if (swdev[i].se_flags & SWF_ENABLE) { + used += (double)(swdev[i].se_inuse / (1024 / DEV_BSIZE)); + total += (double)(swdev[i].se_nblks / (1024 / DEV_BSIZE)); + } + } + + _mem->values[memory_value_swapused] = used; + _mem->values[memory_value_swaptotal] = total; + + free(swdev); +} + +void StatsMemory::update(long long sampleID) +{ + mem_data _mem; + prepareSample(&_mem); + +#ifdef VM_UVMEXP2 + static int mib_vm[2] = { CTL_VM, VM_UVMEXP2 }; + struct uvmexp_sysctl uvmexp; +#else + static int mib_vm[2] = { CTL_VM, VM_UVMEXP }; + struct uvmexp uvmexp; +#endif + + size_t size; + int pagesize = getpagesize(); + + size = sizeof(uvmexp); + if (sysctl(mib_vm, 2, &uvmexp, &size, NULL, 0) < 0) { + return; + } + +#ifdef VFS_BCACHESTAT + static int mib_buf[3] = { CTL_VFS, VFS_GENERIC, VFS_BCACHESTAT }; + struct bcachestats bcstats; + + size = sizeof(bcstats); + if (sysctl(mib_buf, 3, &bcstats, &size, NULL, 0) >= 0) { + _mem.values[memory_value_buffer] = (double)(bcstats.numbufpages * pagesize); + } +#endif + + _mem.values[memory_value_total] = (double)(uvmexp.npages * pagesize); + _mem.values[memory_value_free] = (double)(uvmexp.free * pagesize); + _mem.values[memory_value_used] = (double)(_mem.values[memory_value_total] - _mem.values[memory_value_free]); + _mem.values[memory_value_active] = (double)(uvmexp.active * pagesize); + _mem.values[memory_value_inactive] = (double)(uvmexp.inactive * pagesize); + _mem.values[memory_value_wired] = (double)(uvmexp.wired * pagesize); + +#ifdef VM_UVMEXP2 + _mem.values[memory_value_file] = (double)(uvmexp.filepages * pagesize); + _mem.values[memory_value_ex] = (double)(uvmexp.execpages * pagesize); +#endif + + updateSwap(&_mem); + + addSample(_mem, sampleID); +} + +#elif defined(HAVE_LIBKVM) && defined(USE_MEM_KVM) + +void StatsMemory::init() +{ + databaseKeys.push_back("total"); + databaseKeys.push_back("free"); + databaseKeys.push_back("active"); + databaseKeys.push_back("inactive"); + databaseKeys.push_back("wired"); + databaseKeys.push_back("cached"); + databaseKeys.push_back("used"); + databaseKeys.push_back("buffer"); + databaseKeys.push_back("swaptotal"); + databaseKeys.push_back("swapused"); + databaseKeys.push_back("swapin"); + databaseKeys.push_back("swapout"); + + databaseMap.push_back(memory_value_total); + databaseMap.push_back(memory_value_free); + databaseMap.push_back(memory_value_active); + databaseMap.push_back(memory_value_inactive); + databaseMap.push_back(memory_value_wired); + databaseMap.push_back(memory_value_cached); + databaseMap.push_back(memory_value_used); + databaseMap.push_back(memory_value_buffer); + databaseMap.push_back(memory_value_swaptotal); + databaseMap.push_back(memory_value_swapused); + databaseMap.push_back(memory_value_swapin); + databaseMap.push_back(memory_value_swapout); + + _init(); +} + +void StatsMemory::update(long long sampleID) +{ + mem_data _mem; + prepareSample(&_mem); + + kvm_t *kd; + size_t len; + double kbpp; + struct vmmeter sum; + struct kvm_swap swap[1]; + + struct nlist nl[] = { + { "_cnt" }, + { NULL } + }; + + if ((kd = kvm_open(NULL, NULL, NULL, O_RDONLY, NULL)) == NULL) + { + fprintf(stderr, "kvm_open(): %s\n", strerror(errno)); + return; + } + + /* get virtual memory data */ + if (kvm_nlist(kd, nl) == -1) + { + fprintf(stderr, "kvm_nlist(): %s\n", strerror(errno)); + kvm_close(kd); + return; + } + + len = sizeof(sum); + + if (kvm_read(kd, nl[0].n_value, &sum, len) == -1) + { + fprintf(stderr, "kvm_read(): %s\n", strerror(errno)); + kvm_close(kd); + return; + } + + /* kilo bytes per page */ + kbpp = sum.v_page_size;// / 1024; + + _mem.values[memory_value_total] = (double)(sum.v_page_count * kbpp); + _mem.values[memory_value_free] = (double)(sum.v_free_count * kbpp); + _mem.values[memory_value_active] = (double)(sum.v_active_count * kbpp); + _mem.values[memory_value_inactive] = (double)(sum.v_inactive_count * kbpp); + _mem.values[memory_value_cached] = (double)(sum.v_cache_count * kbpp); + _mem.values[memory_value_wired] = (double)(sum.v_wire_count * kbpp); + _mem.values[memory_value_used] = (double)(_mem.values[memory_value_active] + _mem.values[memory_value_wired]); + + _mem.values[memory_value_swapin] = (double)(sum.v_swappgsin); + _mem.values[memory_value_swapout] = (double)(sum.v_swappgsout); + +#ifdef HAVE_SYSCTLBYNAME + size_t bufsizelen; + long bufsize; + bufsizelen = sizeof(bufsize); + + if (sysctlbyname("kern.nbuf", &bufsize, &bufsizelen, NULL, 0) >= 0) + { + _mem.values[memory_value_buffer] = (double)((bufsize * 16) * 1024); + } +#endif + + if (kvm_getswapinfo(kd, swap, 1, 0) == -1) + { + fprintf(stderr, "kvm_getswapinfo(): %s\n", strerror(errno)); + kvm_close(kd); + return; + } + + _mem.values[memory_value_swaptotal] = (double)(swap[0].ksw_total * kbpp); + _mem.values[memory_value_swapused] = (double)(swap[0].ksw_used * kbpp); + + kvm_close(kd); + + addSample(_mem, sampleID); + +} /*USE_MEM_KVM*/ + +# elif defined(HAVE_LIBPERFSTAT) && defined(USE_MEM_PERFSTAT) + +void StatsMemory::init() +{ + databaseKeys.push_back("total"); + databaseKeys.push_back("free"); + databaseKeys.push_back("used"); + databaseKeys.push_back("swaptotal"); + databaseKeys.push_back("swapused"); + databaseKeys.push_back("swapin"); + databaseKeys.push_back("swapout"); + databaseKeys.push_back("virtualtotal"); + databaseKeys.push_back("virtualactive"); + + databaseMap.push_back(memory_value_total); + databaseMap.push_back(memory_value_free); + databaseMap.push_back(memory_value_used); + databaseMap.push_back(memory_value_swaptotal); + databaseMap.push_back(memory_value_swapused); + databaseMap.push_back(memory_value_swapin); + databaseMap.push_back(memory_value_swapout); + databaseMap.push_back(memory_value_virtualtotal); + databaseMap.push_back(memory_value_virtualactive); + + _init(); +} + +void StatsMemory::update(long long sampleID) +{ + perfstat_memory_total_t meminfo; + int ps; + mem_data _mem; + prepareSample(&_mem); + + ps=getpagesize(); + perfstat_memory_total(NULL, &meminfo, sizeof meminfo, 1); + _mem.values[memory_value_total] = (double)(ps * meminfo.real_total); + _mem.values[memory_value_free] = (double)(ps * meminfo.real_free); + _mem.values[memory_value_swapin] = (double)(ps * meminfo.pgspins); + _mem.values[memory_value_swapout] = (double)(ps * meminfo.pgspouts); + _mem.values[memory_value_swaptotal] = (double)(ps * meminfo.pgsp_total); + _mem.values[memory_value_swapused] = (double)(ps * (meminfo.pgsp_total- meminfo.pgsp_free)); + _mem.values[memory_value_used] = (double)(_mem.values[memory_value_total] - _mem.values[memory_value_free]); + _mem.values[memory_value_virtualtotal] = (double)(ps * meminfo.virt_total); + _mem.values[memory_value_virtualactive] = (double)(ps * meminfo.virt_active); + + addSample(_mem, sampleID); + +} /*USE_MEM_PERFSTAT*/ +#elif defined(USE_MEM_PROCFS) + +void StatsMemory::init() +{ + databaseKeys.push_back("total"); + databaseKeys.push_back("free"); + databaseKeys.push_back("active"); + databaseKeys.push_back("inactive"); + databaseKeys.push_back("cached"); + databaseKeys.push_back("used"); + databaseKeys.push_back("buffer"); + databaseKeys.push_back("swaptotal"); + databaseKeys.push_back("swapused"); + databaseKeys.push_back("swapin"); + databaseKeys.push_back("swapout"); + + databaseMap.push_back(memory_value_total); + databaseMap.push_back(memory_value_free); + databaseMap.push_back(memory_value_active); + databaseMap.push_back(memory_value_inactive); + databaseMap.push_back(memory_value_cached); + databaseMap.push_back(memory_value_used); + databaseMap.push_back(memory_value_buffer); + databaseMap.push_back(memory_value_swaptotal); + databaseMap.push_back(memory_value_swapused); + databaseMap.push_back(memory_value_swapin); + databaseMap.push_back(memory_value_swapout); + + _init(); +} + +void StatsMemory::update(long long sampleID) +{ + char buf[320]; + FILE * fp = NULL; + mem_data _mem; + prepareSample(&_mem); + + unsigned long long t = 0; + unsigned long long f = 0; + unsigned long long a = 0; + unsigned long long i = 0; + unsigned long long swt = 0; + unsigned long long swf = 0; + unsigned long long swi = 0; + unsigned long long swo = 0; + unsigned long long c = 0; + unsigned long long b = 0; + + if (!(fp = fopen("/proc/meminfo", "r"))) return; + + while (fgets(buf, sizeof(buf), fp)) + { + sscanf(buf, "MemTotal: %llu kB", &t); + sscanf(buf, "MemFree: %llu kB", &f); + sscanf(buf, "Active: %llu kB", &a); + sscanf(buf, "Inactive: %llu kB", &i); + sscanf(buf, "SwapTotal: %llu kB", &swt); + sscanf(buf, "SwapFree: %llu kB", &swf); + sscanf(buf, "Cached: %llu kB", &c); + sscanf(buf, "Buffers: %llu kB", &b); + } + + swf = swf * 1024; + + _mem.values[memory_value_total] = (double)(t * 1024); + _mem.values[memory_value_free] = (double)(f * 1024); + _mem.values[memory_value_active] = (double)(a * 1024); + _mem.values[memory_value_inactive] = (double)(i * 1024); + _mem.values[memory_value_swaptotal] = (double)(swt * 1024); + _mem.values[memory_value_cached] = (double)(c * 1024); + _mem.values[memory_value_buffer] = (double)(b * 1024); + _mem.values[memory_value_swapused] = (double)(_mem.values[memory_value_swaptotal] - swf); + + if (0 == _mem.values[memory_value_active] && 0 == _mem.values[memory_value_inactive] && 0 == _mem.values[memory_value_cached]) + { + _mem.values[memory_value_active] = (double)(_mem.values[memory_value_total] - _mem.values[memory_value_free]); + } + + fclose(fp); + + if (!(fp = fopen("/proc/vmstat", "r"))) return; + + while (fgets(buf, sizeof(buf), fp)) + { + sscanf(buf, "pswpin %llu", &swi); + sscanf(buf, "pswpout %llu", &swo); + } + + _mem.values[memory_value_swapin] = (double)swi; + _mem.values[memory_value_swapout] = (double)swo; + + fclose(fp); + + _mem.values[memory_value_used] = (double)(_mem.values[memory_value_total] - (_mem.values[memory_value_free] + _mem.values[memory_value_buffer] + _mem.values[memory_value_cached])); + + addSample(_mem, sampleID); + +} /* USE_MEM_PROCFS */ +#elif defined(USE_MEM_KSTAT) + +void StatsMemory::init() +{ + databaseKeys.push_back("total"); + databaseKeys.push_back("free"); + databaseKeys.push_back("active"); + databaseKeys.push_back("inactive"); + databaseKeys.push_back("cached"); + databaseKeys.push_back("used"); + databaseKeys.push_back("swaptotal"); + databaseKeys.push_back("swapused"); + databaseKeys.push_back("swapin"); + databaseKeys.push_back("swapout"); + + databaseMap.push_back(memory_value_total); + databaseMap.push_back(memory_value_free); + databaseMap.push_back(memory_value_active); + databaseMap.push_back(memory_value_inactive); + databaseMap.push_back(memory_value_cached); + databaseMap.push_back(memory_value_used); + databaseMap.push_back(memory_value_swaptotal); + databaseMap.push_back(memory_value_swapused); + databaseMap.push_back(memory_value_swapin); + databaseMap.push_back(memory_value_swapout); + + _init(); +} + +void StatsMemory::update(long long sampleID) +{ + kstat_t *ksp; + kstat_named_t *kn; + unsigned long long lv, ps; + int cc, ncpu; + cpu_stat_t cs; + mem_data _mem; + prepareSample(&_mem); + + if(NULL == (ksp = kstat_lookup(ksh, (char*)"unix", -1, (char*)"system_pages"))) return; + if(-1 == kstat_read(ksh, ksp, NULL)) return; + + ps = getpagesize(); + + if(NULL != (kn = (kstat_named_t *) kstat_data_lookup(ksp, (char*)"pagestotal"))) + { + lv = ksgetull(kn); + _mem.values[memory_value_total] = (double)(lv * ps);// / 1024; + } + + if(NULL != (kn = (kstat_named_t *) kstat_data_lookup(ksp, (char*)"pageslocked"))) + { + lv = ksgetull(kn); + _mem.values[memory_value_cached] = (double)(lv * ps);// / 1024; + } + + if(NULL != (kn = (kstat_named_t *) kstat_data_lookup(ksp, (char*)"pagesfree"))) + { + lv = ksgetull(kn); + _mem.values[memory_value_free] = (double)(lv * ps);// / 1024; + } + + if(NULL != (kn = (kstat_named_t *) kstat_data_lookup(ksp, (char*)"pp_kernel"))) + { + lv = ksgetull(kn); + _mem.values[memory_value_wired] = (double)(lv * ps);// / 1024; + } + + _mem.values[memory_value_active] = (double)(_mem.values[memory_value_total] - _mem.values[memory_value_free]); + _mem.values[memory_value_used] = (double)(_mem.values[memory_value_active]); + + ncpu = sysconf(_SC_NPROCESSORS_CONF); + + for(cc = 0; cc < ncpu; cc++) + { + if(p_online(cc, P_STATUS) != P_ONLINE) + { + continue; + } + if(NULL != (ksp = kstat_lookup(ksh, (char*)"cpu_stat", cc, NULL))) + { + if(-1 != kstat_read(ksh, ksp, &cs)) + { + _mem.values[memory_value_swapin] += (double)(cs.cpu_vminfo.pgin); + _mem.values[memory_value_swapout] += (double)(cs.cpu_vminfo.pgout); + } + } + } + get_swp_data(&_mem); + + addSample(_mem, sampleID); +} + +void StatsMemory::get_swp_data(struct mem_data * _mem) +{ + swaptbl_t *s; + char strtab[255]; + int swap_num; + int status; + int i; + int bpp = getpagesize() >> DEV_BSHIFT; + + unsigned long long avail = 0; + unsigned long long total = 0; + + swap_num = swapctl (SC_GETNSWP, NULL); + if (swap_num < 0) + { + return; + } + else if (swap_num == 0) + return; + + s = (swaptbl_t *) malloc (swap_num * sizeof (swapent_t) + sizeof (struct swaptable)); + if (s == NULL) + { + return; + } + + for (i = 0; i < (swap_num + 1); i++) { + s->swt_ent[i].ste_path = strtab; + } + s->swt_n = swap_num + 1; + status = swapctl (SC_LIST, s); + if (status != swap_num) + { + free (s); + return; + } + + for (i = 0; i < swap_num; i++) + { + if ((s->swt_ent[i].ste_flags & ST_INDEL) != 0) + continue; + + avail += ((unsigned long long) s->swt_ent[i].ste_free) * bpp * DEV_BSIZE; + total += ((unsigned long long) s->swt_ent[i].ste_pages) * bpp * DEV_BSIZE; + } + + if (total < avail) + return; + + _mem->values[memory_value_swaptotal] = (double)(total); + _mem->values[memory_value_swapused] = (double)(total - avail); +} + +#else + +void StatsMemory::init() +{ + _init(); +} + +void StatsMemory::update(long long sampleID) {} + +#endif + +void StatsMemory::prepareSample(mem_data* data) +{ + int x; + for(x=0;xvalues[x] = -1; +} + +void StatsMemory::addSample(mem_data data, long long sampleID) +{ + data.sampleID = sampleIndex[0].sampleID; + data.time = sampleIndex[0].time; + + samples[0].push_front(data); + if (samples[0].size() > HISTORY_SIZE) samples[0].pop_back(); +} + +void StatsMemory::_init() +{ + initShared(); + + #ifdef USE_SQLITE + if(historyEnabled == true) + { + databaseType = 0; + databasePrefix = "memory_"; + + int x; + for(x=1;x<8;x++) + { + string table = databasePrefix + tableAtIndex(x); + if(!_database.tableExists(table)) + { + string sql = "create table " + table + " (sample double PRIMARY KEY NOT NULL, time double NOT NULL DEFAULT 0, empty integer NOT NULL DEFAULT 0)"; + DatabaseItem dbItem = _database.databaseItem(sql); + dbItem.executeUpdate(); + } + + size_t y; + for(y=0;y= @sample order by sample asc limit 602"; + DatabaseItem query = _database.databaseItem(sql); + sqlite3_bind_double(query._statement, 1, sampleID - 602); + + while(query.next()) + { + mem_data sample; + prepareSample(&sample); + + size_t y; + for(y=0;y 0) + { + sampleIndex[index].sampleID = samples[index][0].sampleID; + sampleIndex[index].time = samples[index][0].time; + sampleIndex[index].nextTime = sampleIndex[index].time + sampleIndex[index].interval; + } +} + +void StatsMemory::updateHistory() +{ + int x; + for (x = 1; x < 8; x++) + { + if(sampleIndex[0].time >= sampleIndex[x].nextTime) + { + double now = get_current_time(); + double earlistTime = now - (HISTORY_SIZE * sampleIndex[x].interval); + while(sampleIndex[x].nextTime < now) + { + sampleIndex[x].sampleID = sampleIndex[x].sampleID + 1; + sampleIndex[x].time = sampleIndex[x].nextTime; + sampleIndex[x].nextTime = sampleIndex[x].nextTime + sampleIndex[x].interval; + + if(sampleIndex[x].time < earlistTime) + continue; + + mem_data sample = historyItemAtIndex(x); + + samples[x].push_front(sample); + if (samples[x].size() > HISTORY_SIZE) samples[x].pop_back(); + + if(sample.empty) + continue; + + string table = databasePrefix + tableAtIndex(x); + stringstream sql; + sql << "insert into " + table + " (empty, sample, time"; + + size_t y; + for(y=0;y from = samples[sampleIndex[index].historyIndex]; + double minimumTime = sampleIndex[index].time - sampleIndex[index].interval; + double maximumTime = sampleIndex[index].time; + if(sampleIndex[index].historyIndex == 0) + maximumTime += 0.99; + + mem_data sample; + + double values[memory_values_count]; + + int x; + for(x=0;x 0) + { + for (deque::iterator cur = from.begin(); cur != from.end(); ++cur) + { + if ((*cur).time > maximumTime) + continue; + + if ((*cur).time < minimumTime) + break; + + for(x=0;x 0) + { + for(x=0;x 0) + { + if(samples[0][0].values[x] == -1) + values[x] = -1; + } + } + + + for(x=0;x samples[8]; + + std::deque databaseKeys; + std::deque databaseMap; + + void _init(); + void init(); + #ifdef USE_SQLITE + void updateHistory(); + mem_data historyItemAtIndex(int index); + void loadPreviousSamples(); + void loadPreviousSamplesAtIndex(int index); + #endif + + #if defined(USE_MEM_SYSCTL) + void updateSwap(mem_data* data); + #endif + + #ifdef HAVE_LIBKSTAT + kstat_ctl_t *ksh; + void get_swp_data(struct mem_data * _mem); + #endif + }; +#endif diff --git a/src/stats/StatsNetwork.cpp b/src/stats/StatsNetwork.cpp new file mode 100644 index 0000000..0922544 --- /dev/null +++ b/src/stats/StatsNetwork.cpp @@ -0,0 +1,816 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "StatsNetwork.h" + +using namespace std; + +#if defined(USE_NET_GETIFADDRS) && defined(HAVE_GETIFADDRS) + +void StatsNetwork::init() +{ + _init(); +} + +void StatsNetwork::update(long long sampleID) +{ + struct ifaddrs *ifap, *ifa; + + if (getifaddrs(&ifap) < 0) + { + return; + } + + vector keys; + + for (ifa = ifap; ifa; ifa = ifa->ifa_next) + { + if (ifa->ifa_flags & IFF_UP) + { + struct ifaddrs *iftmp; + + if (ifa->ifa_addr == NULL) { + continue; + } + + if (ifa->ifa_addr->sa_family != AF_LINK) { + continue; + } + + for (iftmp = ifa->ifa_next; iftmp != NULL; iftmp = iftmp->ifa_next) + { + if(strcmp(ifa->ifa_name, iftmp->ifa_name) == 0) + { + if(keys.size() > 0) + { + if (std::find(keys.begin(), keys.end(), string(ifa->ifa_name)) != keys.end()) + continue; + } + + struct if_data *ifd = (struct if_data *) ifa->ifa_data; + processInterface(string(ifa->ifa_name), sampleID, ifd->ifi_obytes, ifd->ifi_ibytes); + keys.push_back(string(ifa->ifa_name)); + } + } + } + } + + freeifaddrs(ifap); +} + +#elif defined(HAVE_LIBPERFSTAT) && defined(USE_NET_PERFSTAT) + +void StatsNetwork::init() +{ + _init(); +} + +void StatsNetwork::update(long long sampleID) +{ + perfstat_netinterface_total_t ninfo; + perfstat_netinterface_total(NULL, &ninfo, sizeof ninfo, 1); + processInterface(string("net"), sampleID, ninfo.obytes, ninfo.ibytes); +} /*NET_USE_PERFSTAT*/ + +#elif defined(USE_NET_PROCFS) + +void StatsNetwork::init() +{ + _init(); +} + +void StatsNetwork::update(long long sampleID) +{ + char dev[255]; + FILE * fp = NULL; + + if (!(fp = fopen("/proc/net/dev", "r"))) + { + cout << "fopen: /proc/net/dev" << endl; + return; + } + +#ifdef HAVE_GETIFADDRS + struct ifaddrs *ifap, *ifa; + vector active_infs; + + if (getifaddrs(&ifap) >= 0) { + for (ifa = ifap; ifa; ifa = ifa->ifa_next) { + if (ifa->ifa_flags & IFF_UP && ifa->ifa_name) + { + active_infs.push_back(string(ifa->ifa_name)); + } + } + freeifaddrs(ifap); + } +#endif + + if(fscanf(fp, "%*[^\n] %*[^\n] ")) + { + } + + while (!feof(fp)) + { + unsigned long long upload; + unsigned long long download; + + if(fscanf(fp, "%7[^:]:%llu %*u %*u %*u %*u %*u %*u %*u %llu %*[^\n] ", dev, &download, &upload) > 0) + { +#ifdef HAVE_GETIFADDRS + if(active_infs.size() > 0) + { + if (std::find(active_infs.begin(), active_infs.end(), string(dev)) == active_infs.end()) + continue; + } +#endif + processInterface(string(dev), sampleID, upload, download); + } + else + { + if(fscanf(fp, "%*[^\n]")){} + } + } + + fclose(fp); +} + +#elif defined(USE_NET_SYSCTL) + +void StatsNetwork::init() +{ + _init(); +} + +int StatsNetwork::get_ifcount() +{ + size_t len; + int name[5], count; + + name[0] = CTL_NET; + name[1] = PF_LINK; + name[2] = NETLINK_GENERIC; + name[3] = IFMIB_SYSTEM; + name[4] = IFMIB_IFCOUNT; + + len = sizeof(int); + + sysctl(name, 5, &count, &len, NULL, 0); + + return count; +} + +void StatsNetwork::update(long long sampleID) +{ + int i; + int name[6]; + size_t len; + struct ifmibdata ifmd; + int ifcount = get_ifcount(); + + len = sizeof(ifmd); + + name[0] = CTL_NET; + name[1] = PF_LINK; + name[2] = NETLINK_GENERIC; + name[3] = IFMIB_IFDATA; + name[5] = IFDATA_GENERAL; + + for (i = 1; i <= ifcount; i++) + { + name[4] = i; + + sysctl(name, 6, &ifmd, &len, (void *) 0, 0); + + unsigned int flags = ifmd.ifmd_flags; + + if ((flags & IFF_UP) && (flags & IFF_RUNNING)) + { + unsigned long long upload = ifmd.ifmd_data.ifi_obytes; + unsigned long long download = ifmd.ifmd_data.ifi_ibytes; + + processInterface(string(ifmd.ifmd_name), sampleID, upload, download); + } + } +} /*NET_USE_SYSCTL*/ + +#elif defined(HAVE_LIBKSTAT) && defined(USE_NET_KSTAT) + +void StatsNetwork::init() +{ + _init(); + + vector infs = interfaces(); + if(infs.size() > 0) + { + for (vector::iterator cur = infs.begin(); cur != infs.end(); ++cur) + { + createInterface((*cur)); + } + } +} + +vector StatsNetwork::interfaces() +{ + + vector interfaces; + + int n; + int s; + char *buf; + struct lifnum lifn; + struct lifconf lifc; + struct lifreq *lifrp; + int numifs; + size_t bufsize; + + s = socket(AF_INET6, SOCK_DGRAM, 0); + if (s < 0) { + s = socket(AF_INET, SOCK_DGRAM, 0); + if (s < 0) + return interfaces; + } + + lifn.lifn_family = AF_UNSPEC; + lifn.lifn_flags = LIFC_NOXMIT | LIFC_TEMPORARY | LIFC_ALLZONES; + if (ioctl(s, SIOCGLIFNUM, (char *)&lifn) < 0) + return interfaces; + numifs = lifn.lifn_count; + + bufsize = numifs * sizeof (struct lifreq); + buf = (char*)malloc(bufsize); + if (buf == NULL) { + (void) close(s); + return interfaces; + } + lifc.lifc_family = AF_UNSPEC; + lifc.lifc_flags = LIFC_NOXMIT | LIFC_TEMPORARY | LIFC_ALLZONES; + lifc.lifc_len = bufsize; + lifc.lifc_buf = buf; + if (ioctl(s, SIOCGLIFCONF, (char *)&lifc) < 0) { + (void) close(s); + free(buf); + return interfaces; + } + + lifrp = lifc.lifc_req; + (void) close(s); + + for (n = numifs; n > 0; n--, lifrp++) { + bool found = false; + + string infname = string(lifrp->lifr_name); + + if (infname.find(":") != std::string::npos) + { + infname = infname.substr(0, infname.find(":")); + } + + if(interfaces.size() > 0) + { + for (vector::iterator cur = interfaces.begin(); cur != interfaces.end(); ++cur) + { + if((*cur) == infname) + { + found = true; + } + } + } + + + if (found == false) { + interfaces.push_back(infname); + } + } + + free(buf); + + return interfaces; +} + +void StatsNetwork::update(long long sampleID) +{ + if(_items.size() > 0) + { + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + kstat_t *ksp; + kstat_named_t *kn; + char name[32]; + char module[32]; + char *p; + + char *_dev = (char*)((*cur).device.c_str()); + + unsigned long long upload; + unsigned long long download; + + strcpy(module, (char*)"link"); + if((p = strchr(_dev, ':')) == NULL) + { + strncpy(name, _dev, sizeof(name) - 1); + name[sizeof(name) - 1] = 0; + } + else + { + strncpy(name, p + 1, sizeof(name) - 1); + name[sizeof(name) - 1]=0; + strncpy(module, _dev, sizeof(module) - 1); + module[p - _dev] = 0; + } + if(NULL == (ksp = kstat_lookup(ksh, module, -1, name))) + { + (*cur).active = false; + continue; + } + + if(-1 == kstat_read(ksh, ksp, NULL)) + { + (*cur).active = false; + continue; + } + + kn = (kstat_named_t *) ksp->ks_data; + if(NULL == (kn = (kstat_named_t *) kstat_data_lookup(ksp, (char*)"obytes64"))) + { + (*cur).active = false; + continue; + } + + upload = ksgetull(kn); + + if(NULL == (kn = (kstat_named_t *) kstat_data_lookup(ksp, (char*)"rbytes64"))) + { + (*cur).active = false; + continue; + } + + download = ksgetull(kn); + + processInterface((*cur).device, sampleID, upload, download); + } + } +} + +# else + +void StatsNetwork::init() +{ + _init(); +} + +void StatsNetwork::update(long long sampleID) +{ + +} + +#endif + +#define INT_TO_ADDR(_addr) \ +(_addr & 0xFF), \ +(_addr >> 8 & 0xFF), \ +(_addr >> 16 & 0xFF), \ +(_addr >> 24 & 0xFF) + +// IPS_IOCTL - We dont use getifaddrs on solaris even if its available. Doesn't seem to work correctly +#if defined(HAVE_GETIFADDRS) && !defined(IPS_IOCTL) +void StatsNetwork::updateAddresses() +{ + if(_items.size() == 0) + return; + + struct ifaddrs *ifap, *ifa; + + if (getifaddrs(&ifap) < 0) { + return; + } + + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + (*cur).addresses.clear(); + } + + for (ifa = ifap; ifa; ifa = ifa->ifa_next) { + if (ifa->ifa_flags & IFF_UP) + { + struct ifaddrs *iftmp; + + if (ifa->ifa_addr == NULL) { + continue; + } + + if (ifa->ifa_addr->sa_family != AF_LINK) { + continue; + } + + for (iftmp = ifa->ifa_next; iftmp != NULL; iftmp = iftmp->ifa_next) + { + if(strcmp(ifa->ifa_name, iftmp->ifa_name) == 0) + { + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + network_info interface = *cur; + if (interface.device == string(ifa->ifa_name)) + { + if (iftmp->ifa_addr->sa_family == AF_INET) { + (*cur).addresses.push_back(string(inet_ntoa(((struct sockaddr_in *)iftmp->ifa_addr)->sin_addr))); + } + + #ifdef AF_INET6 + if (iftmp->ifa_addr->sa_family == AF_INET6) { + char ip[INET6_ADDRSTRLEN]; + const char *conversion = inet_ntop(AF_INET6, &((struct sockaddr_in6 *)iftmp->ifa_addr)->sin6_addr, ip, sizeof(ip)); + (*cur).addresses.push_back(string(conversion)); + } + #endif + } + } + } + } + } + } + + freeifaddrs(ifap); +} + +#else + +void StatsNetwork::updateAddresses() +{ + if(_items.size() == 0) + return; + + struct ifconf ifc; + struct ifreq ifr[10]; + int i, sd, ifc_num, addr; + + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + (*cur).addresses.clear(); + } + + sd = socket(PF_INET, SOCK_DGRAM, 0); + if (sd > 0) + { + ifc.ifc_len = sizeof(ifr); + ifc.ifc_ifcu.ifcu_buf = (caddr_t)ifr; + + if (ioctl(sd, SIOCGIFCONF, &ifc) == 0) + { + ifc_num = ifc.ifc_len / sizeof(struct ifreq); + + for (i = 0; i < ifc_num; ++i) + { + if (ifr[i].ifr_addr.sa_family != AF_INET) + { + continue; + } + + string ip = string(""); + + if (ioctl(sd, SIOCGIFADDR, &ifr[i]) == 0) + { + addr = ((struct sockaddr_in *)(&ifr[i].ifr_addr))->sin_addr.s_addr; + + char str[32]; + sprintf(str, "%d.%d.%d.%d", INT_TO_ADDR(addr)); + ip = string(str); + } + if(_items.size() > 0) + { + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + network_info interface = *cur; + if (interface.device == string(ifr[i].ifr_name)) + { + (*cur).addresses.push_back(ip); + } + } + } + } + } + + close(sd); + } +} + +#endif + +void StatsNetwork::createInterface(string key) +{ + if(key == "lo" || key == "lo0") + return; + + if(_items.size() > 0) + { + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + network_info interface = *cur; + if (interface.device == key) + { + return; + } + } + } + + network_info item; + item.last_down = 0; + item.last_up = 0; + item.device = key; + session++; + +#ifdef USE_SQLITE + if(historyEnabled == true) + { + int x; + for(x=1;x<8;x++) + { + string table = databasePrefix + tableAtIndex(x); + double sampleID = 0; + if(samples[x].size() > 0) + sampleID = samples[x][0].sampleID; + + + string sql = "select * from " + table + " where sample >= @sample AND uuid = ? order by sample asc limit 602"; + DatabaseItem query = _database.databaseItem(sql); + sqlite3_bind_double(query._statement, 1, sampleID - 602); + sqlite3_bind_text(query._statement, 2, key.c_str(), -1, SQLITE_STATIC); + + while(query.next()) + { + net_data sample; + sample.d = query.doubleForColumn("download"); + sample.u = query.doubleForColumn("upload"); + sample.sampleID = (long long)query.doubleForColumn("sample"); + sample.time = query.doubleForColumn("time"); + item.samples[x].push_front(sample); + } + } + } +#endif + _items.insert(_items.begin(), item); + + updateAddresses(); +} + +void StatsNetwork::processInterface(string key, long long sampleID, unsigned long long upload, unsigned long long download) +{ + if(key == "lo" || key == "lo0" || key == "nic" || key.find("virbr") == 0 || key.find("ath0") == 0) + return; + + createInterface(key); + + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + if (key == (*cur).device) + { + (*cur).active = true; + + if(ready == 0) + return; + + net_data data; + + if((*cur).last_up == 0) + (*cur).last_up = upload; + if((*cur).last_down == 0) + (*cur).last_down = download; + + double u = upload - (*cur).last_up; + double d = download - (*cur).last_down; + + (*cur).last_up = upload; + (*cur).last_down = download; + + data.u = u; + data.d = d; + data.sampleID = sampleIndex[0].sampleID; + data.time = sampleIndex[0].time; + + (*cur).samples[0].push_front(data); + if ((*cur).samples[0].size() > HISTORY_SIZE) + (*cur).samples[0].pop_back(); + + break; + } + } +} + +void StatsNetwork::prepareUpdate() +{ + if(ready == 0) + return; + + StatsBase::prepareUpdate(); + + if(_items.size() > 0) + { + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + (*cur).active = false; + } + } +} + +void StatsNetwork::_init() +{ + initShared(); + ready = 0; + + #ifdef USE_SQLITE + if(historyEnabled == true) + { + databaseType = 1; + databasePrefix = "network_"; + + int x; + for(x=1;x<8;x++) + { + string table = databasePrefix + tableAtIndex(x) + "_id"; + if(!_database.tableExists(table)) + { + string sql = "create table " + table + " (sample double PRIMARY KEY NOT NULL, time double NOT NULL DEFAULT 0, empty integer NOT NULL DEFAULT 0)"; + DatabaseItem dbItem = _database.databaseItem(sql); + dbItem.executeUpdate(); + } + + table = databasePrefix + tableAtIndex(x); + if(!_database.tableExists(table)) + { + string sql = "create table " + table + " (sample double NOT NULL, time double NOT NULL DEFAULT 0, uuid varchar(255) NOT NULL, download double NOT NULL DEFAULT 0, upload double NOT NULL DEFAULT 0)"; + DatabaseItem dbItem = _database.databaseItem(sql); + dbItem.executeUpdate(); + } + } + loadPreviousSamples(); + fillGaps(); + } + #endif +} + +#ifdef USE_SQLITE +void StatsNetwork::loadPreviousSamples() +{ + loadPreviousSamplesAtIndex(1); + loadPreviousSamplesAtIndex(2); + loadPreviousSamplesAtIndex(3); + loadPreviousSamplesAtIndex(4); + loadPreviousSamplesAtIndex(5); + loadPreviousSamplesAtIndex(6); + loadPreviousSamplesAtIndex(7); +} + +void StatsNetwork::loadPreviousSamplesAtIndex(int index) +{ + + string table = databasePrefix + tableAtIndex(index) + "_id"; + double sampleID = sampleIdForTable(table); + + string sql = "select * from " + table + " where sample >= @sample order by sample asc limit 602"; + DatabaseItem query = _database.databaseItem(sql); + sqlite3_bind_double(query._statement, 1, sampleID - 602); + + while(query.next()) + { + sample_data sample; + sample.sampleID = (long long)query.doubleForColumn("sample"); + sample.time = query.doubleForColumn("time"); + samples[index].insert(samples[index].begin(), sample); + } + if(samples[index].size() > 0) + { + sampleIndex[index].sampleID = samples[index][0].sampleID; + sampleIndex[index].time = samples[index][0].time; + sampleIndex[index].nextTime = sampleIndex[index].time + sampleIndex[index].interval; + } +} + +void StatsNetwork::updateHistory() +{ + int x; + for (x = 1; x < 8; x++) + { + if(sampleIndex[0].time >= sampleIndex[x].nextTime) + { + double now = get_current_time(); + double earlistTime = now - (HISTORY_SIZE * sampleIndex[x].interval); + while(sampleIndex[x].nextTime < now) + { + sampleIndex[x].sampleID = sampleIndex[x].sampleID + 1; + sampleIndex[x].time = sampleIndex[x].nextTime; + sampleIndex[x].nextTime = sampleIndex[x].nextTime + sampleIndex[x].interval; + + if(sampleIndex[x].time < earlistTime) + continue; + + if(_items.size() > 0) + { + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + net_data sample = historyItemAtIndex(x, (*cur)); + + (*cur).samples[x].push_front(sample); + if ((*cur).samples[x].size() > HISTORY_SIZE) (*cur).samples[x].pop_back(); + + if(sample.empty) + continue; + + string table = databasePrefix + tableAtIndex(x); + string sql = "insert into " + table + " (sample, time, upload, download, uuid) values(?, ?, ?, ?, ?)"; + + DatabaseItem dbItem = _database.databaseItem(sql); + sqlite3_bind_double(dbItem._statement, 1, (double)sample.sampleID); + sqlite3_bind_double(dbItem._statement, 2, sample.time); + sqlite3_bind_double(dbItem._statement, 3, sample.u); + sqlite3_bind_double(dbItem._statement, 4, sample.d); + sqlite3_bind_text(dbItem._statement, 5, (*cur).device.c_str(), -1, SQLITE_STATIC); + databaseQueue.push_back(dbItem); + } + } + + string table = databasePrefix + tableAtIndex(x) + "_id"; + string sql = "insert into " + table + " (empty, sample, time) values(?, ?, ?)"; + + DatabaseItem dbItem = _database.databaseItem(sql); + sqlite3_bind_int(dbItem._statement, 1, 0); + sqlite3_bind_double(dbItem._statement, 2, (double)sampleIndex[x].sampleID); + sqlite3_bind_double(dbItem._statement, 3, sampleIndex[x].time); + databaseQueue.push_back(dbItem); + } + } + } +} + +net_data StatsNetwork::historyItemAtIndex(int index, network_info item) +{ + net_data sample; + double u = 0; + double d = 0; + + std::deque from = item.samples[sampleIndex[index].historyIndex]; + double minimumTime = sampleIndex[index].time - sampleIndex[index].interval; + double maximumTime = sampleIndex[index].time; + if(sampleIndex[index].historyIndex == 0) + maximumTime += 0.99; + + int count = 0; + if(from.size() > 0) + { + for (deque::iterator cursample = from.begin(); cursample != from.end(); ++cursample) + { + if ((*cursample).time > maximumTime) + continue; + + if ((*cursample).time < minimumTime) + break; + + u += (*cursample).u; + d += (*cursample).d; + count++; + } + if (count > 0) + { + u /= count; + d /= count; + } + } + + sample.u = u; + sample.d = d; + + sample.sampleID = sampleIndex[index].sampleID; + sample.time = sampleIndex[index].time; + sample.empty = false; + if(count == 0) + sample.empty = true; + + return sample; +} +#endif diff --git a/src/stats/StatsNetwork.h b/src/stats/StatsNetwork.h new file mode 100644 index 0000000..fbcc3e9 --- /dev/null +++ b/src/stats/StatsNetwork.h @@ -0,0 +1,81 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "StatBase.h" + +#ifndef _STATSNETWORK_H +#define _STATSNETWORK_H + +class network_info +{ + public: + bool active; + unsigned int id; + unsigned long long last_up; + unsigned long long last_down; + + std::vector addresses; + std::string device; + double last_update; + + std::deque samples[8]; +}; + +class StatsNetwork : public StatsBase +{ + public: + void update(long long sampleID); + void init(); + std::vector _items; + void createInterface(std::string key); + void processInterface(std::string key, long long sampleID, unsigned long long upload, unsigned long long download); + void updateAddresses(); + void prepareUpdate(); + #ifdef HAVE_LIBKSTAT + kstat_ctl_t *ksh; + std::vector interfaces(); + #endif + + void _init(); + #ifdef USE_SQLITE + void updateHistory(); + net_data historyItemAtIndex(int index, network_info item); + void loadPreviousSamples(); + void loadPreviousSamplesAtIndex(int index); + #endif + + std::deque samples[8]; + + #ifdef USE_NET_SYSCTL + int get_ifcount(); + #endif + }; +#endif diff --git a/src/stats/StatsProcesses.cpp b/src/stats/StatsProcesses.cpp new file mode 100644 index 0000000..0aff83f --- /dev/null +++ b/src/stats/StatsProcesses.cpp @@ -0,0 +1,575 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "StatsProcesses.h" + +using namespace std; + + +#ifdef USE_PROCESSES_AIX + +void StatsProcesses::init() +{ + //aixEntitlement = 0.12; +} + +void StatsProcesses::update(long long sampleID, double totalTicks) +{ + int treesize; + pid_t firstproc = 0; + if ((treesize = getprocs64(NULL, 0, NULL, 0, &firstproc, PID_MAX)) < 0) { + return; + } + struct procentry64 *procs = (struct procentry64 *)malloc(treesize * sizeof (struct procentry64));; + + firstproc = 0; + if ((treesize = getprocs64(procs, sizeof(struct procentry64), NULL, 0, &firstproc, treesize)) < 0) { + free(procs); + return; + } + + threadCount = 0; + + double currentTime = get_current_time(); + + for (int i = 0; i < treesize; i++) + { + pid_t pid = procs[i].pi_pid; + if(pid == 0) + continue; + + processProcess(pid, sampleID); + + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + if((*cur).pid == pid) + { + struct psinfo psinfo; + char buffer [BUFSIZ]; + + sprintf (buffer, "/proc/%d/psinfo", (int) pid); + + int fd = open(buffer, O_RDONLY); + + if (fd < 0) { + continue; + } + + ssize_t len = pread(fd, &psinfo, sizeof (struct psinfo), 0); + if (len != sizeof (struct psinfo)) + { + close(fd); + continue; + } + + (*cur).exists = true; + if((*cur).is_new == true) + { + sprintf((*cur).name, psinfo.pr_fname); + (*cur).is_new = false; + } + + double t = (double)(procs[i].pi_ru.ru_utime.tv_sec + procs[i].pi_ru.ru_stime.tv_sec); + t += (double)(procs[i].pi_ru.ru_utime.tv_usec + procs[i].pi_ru.ru_stime.tv_usec) / NS_PER_SEC; + + if((*cur).cpuTime == 0) + { + (*cur).cpuTime = t; + (*cur).lastClockTime = currentTime; + } + + double timediff = currentTime - (*cur).lastClockTime; + + (*cur).cpu = ((t - (*cur).cpuTime) / timediff * 10000) / (aixEntitlement * 100); + (*cur).threads = procs[i].pi_thcount; + (*cur).memory = (uint64_t)(procs[i].pi_drss + procs[i].pi_trss) * (uint64_t)getpagesize(); + threadCount += procs[i].pi_thcount; + + (*cur).cpuTime = t; + (*cur).lastClockTime = currentTime; + + close(fd); + } + } + } + + free(procs); +} + +#elif defined(USE_PROCESSES_PSINFO) + +void StatsProcesses::init() +{ +} + +void StatsProcesses::update(long long sampleID, double totalTicks) +{ + DIR *dir; + struct dirent *entry; + + if (!(dir = opendir("/proc"))) + { + return; + } + + while ((entry = readdir(dir))) + { + int pid = 0; + + if (!entry) + { + closedir(dir); + return; + } + + if (sscanf(entry->d_name, "%d", &pid) > 0) + { + processProcess(pid, sampleID); + + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + if((*cur).pid == pid) + { + struct psinfo psinfo; + char buffer [BUFSIZ]; + + sprintf (buffer, "/proc/%d/psinfo", (int) pid); + + int fd = open(buffer, O_RDONLY); + + if (fd < 0) { + continue; + } + + ssize_t len = pread(fd, &psinfo, sizeof (struct psinfo), 0); + if (len != sizeof (struct psinfo)) + { + close(fd); + continue; + } + + (*cur).exists = true; + if((*cur).is_new == true) + { + sprintf((*cur).name, psinfo.pr_fname); + (*cur).is_new = false; + } + + (*cur).cpu = (double)(psinfo.pr_pctcpu * 100.0f) / 0x8000; + (*cur).memory = psinfo.pr_rssize * 1024; + + close(fd); + } + } + } + } + + closedir(dir); +} + +#elif defined(USE_PROCESSES_KVM) + +void StatsProcesses::init() +{ + +} +void StatsProcesses::update(long long sampleID, double totalTicks) +{ + #if defined(PROCESSES_KVM_NETBSD) + struct kinfo_proc2 *p; + #else + struct kinfo_proc *p; + #endif + int n_processes; + int i; + kvm_t *kd; + + if ((kd = kvm_open(NULL, NULL, NULL, O_RDONLY, NULL)) == NULL) + { + return; + } + + #if defined(PROCESSES_KVM_NETBSD) + p = kvm_getproc2(kd, KERN_PROC_ALL, 0, sizeof(kinfo_proc2), &n_processes); + #elif defined(PROCESSES_KVM_OPENBSD) + p = kvm_getprocs(kd, KERN_PROC_ALL, 0, sizeof(kinfo_proc), &n_processes); + #elif defined(PROCESSES_KVM_DRAGONFLY) + p = kvm_getprocs(kd, KERN_PROC_ALL, sizeof(kinfo_proc), &n_processes); + #else + p = kvm_getprocs(kd, KERN_PROC_ALL, 0, &n_processes); + #endif + + for (i = 0; i < n_processes; i++) { + #if defined(PROCESSES_KVM_DRAGONFLY) + if (!((p[i].kp_flags & P_SYSTEM)) && p[i].kp_comm != NULL) { + #elif defined(PROCESSES_KVM_OPENBSD) || defined(PROCESSES_KVM_NETBSD) + if (!((p[i].p_flag & P_SYSTEM)) && p[i].p_comm != NULL) { + #else + if (!((p[i].ki_flag & P_SYSTEM)) && p[i].ki_comm != NULL) { + #endif + + #if defined(PROCESSES_KVM_DRAGONFLY) + int pid = p[i].kp_pid; + struct kinfo_lwp lwp = p[i].kp_lwp; + #elif defined(PROCESSES_KVM_OPENBSD) || defined(PROCESSES_KVM_NETBSD) + int pid = p[i].p_pid; + #else + int pid = p[i].ki_pid; + #endif + processProcess(pid, sampleID); + + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + if((*cur).pid == pid) + { + (*cur).exists = true; + if((*cur).is_new == true) + { + #if defined(PROCESSES_KVM_DRAGONFLY) + sprintf((*cur).name, "%s", p[i].kp_comm); + #elif defined(PROCESSES_KVM_OPENBSD) || defined(PROCESSES_KVM_NETBSD) + sprintf((*cur).name, "%s", p[i].p_comm); + #else + sprintf((*cur).name, "%s", p[i].ki_comm); + #endif + (*cur).is_new = false; + } + + #if defined(PROCESSES_KVM_DRAGONFLY) + (*cur).memory = (p[i].kp_vm_rssize * getpagesize()); + (*cur).cpu = (double)(100.0 * lwp.kl_pctcpu / FSCALE); + #elif defined(PROCESSES_KVM_OPENBSD) || defined(PROCESSES_KVM_NETBSD) + (*cur).memory = (p[i].p_vm_rssize * getpagesize()); + (*cur).cpu = (double)(100.0 * p[i].p_pctcpu / FSCALE); + #else + (*cur).memory = (p[i].ki_rssize * getpagesize()); + (*cur).cpu = (double)(100.0 * p[i].ki_pctcpu / FSCALE); + #endif + } + } + } + } + + kvm_close(kd); +} + +#elif defined(USE_PROCESSES_PROCFS) + +void StatsProcesses::init() +{ + +} + +vector StatsProcesses::componentsFromString(string input, char seperator) +{ + vector components; + stringstream ss(input); + string tok; + + while(getline(ss, tok, seperator)) + { + components.push_back(tok); + } + return components; +} + +string StatsProcesses::nameFromCmd(int pid, string name) +{ + stringstream tmp; + tmp << "/proc/" << pid << "/cmdline"; + + int fd = open(tmp.str().c_str(), O_RDONLY); + if (fd > 0) { + char buffer[1024]; + ssize_t len; + if ((len = read(fd, buffer, sizeof(buffer) - 1)) > 0) { + buffer[len] = '\0'; + + int i; + for (i = 0; i < len; i++) { + if (buffer[i] == '\0') + buffer[i] = ' '; + } + } + close(fd); + + if(len == 0) + return name; + + string cmd = string(buffer); + + vector components = componentsFromString(cmd, ' '); + if(components.size() > 0) + { + for (vector::iterator cur = components.begin(); cur != components.end(); ++cur) + { + if((*cur).find(name) != std::string::npos) + { + vector componentsn = componentsFromString((*cur), '/'); + if(componentsn.size() > 0) + return componentsn.back(); + return (*cur); + } + } + } + } + return name; +} + +string StatsProcesses::nameFromStatus(int pid) +{ + + stringstream tmp; + tmp << "/proc/" << pid << "/status"; + + FILE * fp = NULL; + + if ((fp = fopen(tmp.str().c_str(), "r"))) + { + char buf[32]; + while (fgets(buf, sizeof(buf), fp) != NULL) + { + if(sscanf(buf, "Name: %s", buf) > 0) + { + fclose(fp); + return string(buf); + } + } + fclose(fp); + } + return ""; +} + +void StatsProcesses::update(long long sampleID, double totalTicks) +{ + DIR *dir; + struct dirent *entry; + + if (!(dir = opendir("/proc"))) + { + return; + } + + while ((entry = readdir(dir))) + { + int pid = 0; + + if (!entry) + { + break; + } + + if (sscanf(entry->d_name, "%d", &pid) > 0) + { + processProcess(pid, sampleID); + + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + if((*cur).pid == pid) + { + (*cur).exists = true; + + if((*cur).is_new == true) + { + string name = nameFromStatus(pid); + if(name.length() == 15) + { + name = nameFromCmd(pid, name); + } + sprintf((*cur).name, "%s", name.c_str()); + (*cur).is_new = false; + } + + { + stringstream tmp; + tmp << "/proc/" << pid << "/stat"; + + FILE * fp = NULL; + + if ((fp = fopen(tmp.str().c_str(), "r"))) + { + unsigned long userTime = 0; + unsigned long systemTime = 0; + unsigned long rss = 0; + long threads = 0; + char name[255]; + + if(fscanf(fp, "%*d %s %*c %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %lu %lu %*s %*s %*s %*s %ld %*s %*s %*s %lu", name, &userTime, &systemTime, &threads, &rss) > 0) + { + if((*cur).cpuTime == 0) + { + (*cur).cpuTime = (double)(userTime + systemTime); + (*cur).lastClockTime = get_current_time(); + } + + double cpuTime = (double)(userTime + (double)systemTime) - (*cur).cpuTime; + double clockTimeDifference = get_current_time() - (*cur).lastClockTime; + + if(clockTimeDifference > 0) + { + double procs = (double)sysconf(_SC_NPROCESSORS_ONLN); + if(procs < 1) + procs = 1; + (*cur).cpu = (((cpuTime / (double)sysconf(_SC_CLK_TCK)) / clockTimeDifference) * 100) / procs; + } + else + { + (*cur).cpu = 0; + } + + threadCount += threads; + (*cur).threads = threads; + (*cur).memory = rss * getpagesize(); + (*cur).cpuTime = (double)(userTime + systemTime); + (*cur).lastClockTime = get_current_time(); + } + fclose(fp); + } + } + + // /proc/pid/io requires root access which we usually dont run with + /* + { + stringstream tmp; + tmp << "/proc/" << pid << "/io"; + + FILE * fp = NULL; + + if ((fp = fopen(tmp.str().c_str(), "r"))) + { + char buf[1024]; + unsigned long long totalRead = 0; + unsigned long long totalWrite = 0; + while (fgets(buf, sizeof(buf), fp)) + { + sscanf(buf, "read_bytes: %llu", &totalRead); + sscanf(buf, "write_bytes: %llu", &totalWrite); + } + + if((*cur).io_read_total == 0) + { + (*cur).io_read_total = totalRead; + } + + if((*cur).io_write_total == 0) + { + (*cur).io_write_total = totalWrite; + } + + (*cur).io_read = totalRead - (*cur).io_read_total; + (*cur).io_write = totalWrite - (*cur).io_write_total; + + (*cur).io_read_total = totalRead; + (*cur).io_write_total = totalWrite; + + fclose(fp); + } + }*/ + } + } + } + } + + closedir(dir); +} + +#else + +void StatsProcesses::init() +{ + +} + +void StatsProcesses::update(long long sampleID, double totalTicks) +{ + +} + +#endif + +void StatsProcesses::createProcess(int pid) +{ + if(_items.size() > 0) + { + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + process_info process = *cur; + if(process.pid == pid){ + return; + } + } + } + + process_info process; + process.cpu = 0; + process.memory = 0; + process.is_new = true; + process.pid = pid; + process.cpuTime = 0; + process.io_read = 0; + process.io_write = 0; + process.exists = true; + _items.insert(_items.begin(), process); +} + +void StatsProcesses::processProcess(int pid, long long sampleID) +{ + processCount++; + createProcess(pid); +} + +void StatsProcesses::prepareUpdate() +{ + threadCount = 0; + processCount = 0; + + if(_items.size() > 0) + { + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + (*cur).exists = false; + } + } +} + +void StatsProcesses::finishUpdate() +{ + if(_items.size() > 0) + { + for (vector::iterator i = _items.begin(); i != _items.end(); ) { + if (i->exists == false) { + i = _items.erase(i); + } else { + ++i; + } + } + } +} diff --git a/src/stats/StatsProcesses.h b/src/stats/StatsProcesses.h new file mode 100644 index 0000000..ab95be3 --- /dev/null +++ b/src/stats/StatsProcesses.h @@ -0,0 +1,81 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "StatBase.h" + +#ifndef _AIXVERSION_610 +int getprocs64 (struct procentry64 *procsinfo, int sizproc, struct fdsinfo64 *fdsinfo, int sizfd, pid_t *index, int count); +#endif + +#ifndef _STATSPROCESSES_H +#define _STATSPROCESSES_H + +class process_info +{ + public: + bool exists; + bool is_new; + double cpu; + unsigned long long io_read; + unsigned long long io_write; + unsigned long long io_read_total; + unsigned long long io_write_total; + unsigned long memory; + double cpuTime; + double lastClockTime; + long threads; + int pid; + long long sampleID; + char name[128]; +}; +class StatsProcesses +{ + public: + void update(long long sampleID, double ticks); + void prepareUpdate(); + void init(); + std::vector _items; + void createProcess(int pid); + void processProcess(int pid, long long sampleID); + + long threadCount; + long processCount; + double aixEntitlement; + + #ifdef USE_PROCESSES_PROCFS + std::string nameFromCmd(int pid, std::string name); + std::string nameFromStatus(int pid); + std::vector componentsFromString(std::string input, char seperator); + #endif + + void finishUpdate(); + }; +#endif diff --git a/src/stats/StatsSensors.cpp b/src/stats/StatsSensors.cpp new file mode 100644 index 0000000..f2a3f9f --- /dev/null +++ b/src/stats/StatsSensors.cpp @@ -0,0 +1,748 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "StatsSensors.h" + +using namespace std; + +#ifdef HAVE_LIBSENSORS +#if SENSORS_API_VERSION >= 0x0400 /* libsensor 4 */ + +void StatsSensors::update_libsensors(long long sampleID) +{ + int a, b, c, num; + const sensors_chip_name * chip; + const sensors_feature * features; + const sensors_subfeature * subfeatures; + + a = num = 0; + + while ((chip = sensors_get_detected_chips(NULL, &a))) + { + b = 0; + while ((features = sensors_get_features(chip, &b))) + { + c = 0; + while ((subfeatures = sensors_get_all_subfeatures(chip, features, &c))) + { + if (subfeatures->type == SENSORS_SUBFEATURE_FAN_INPUT || + subfeatures->type == SENSORS_SUBFEATURE_CURR_INPUT || + subfeatures->type == SENSORS_SUBFEATURE_POWER_INPUT || + subfeatures->type == SENSORS_SUBFEATURE_IN_INPUT || + subfeatures->type == SENSORS_SUBFEATURE_VID || + subfeatures->type == SENSORS_SUBFEATURE_TEMP_INPUT) + { + + //if (chip->bus == SENSORS_CHIP_NAME_BUS_ISA) + // printf ("%s-isa-%04x", chip->prefix, chip->addr); + //else + // printf ("%s\n", chip->prefix); + //fflush(stdout); + + + char *label = sensors_get_label(chip, features); + stringstream key; + key << label << "_" << sensorType(subfeatures->type); + + if(createSensor(key.str()) == 1) + { + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + if((*cur).key == key.str()) + { + (*cur).method = 1; + (*cur).chip = chip->addr; + (*cur).sensor = features->number; + (*cur).label = string(label); + (*cur).kind = sensorType(subfeatures->type); + } + } + } + + double value; + sensors_get_value(chip, subfeatures->number, &value); + + processSensor(key.str(), sampleID, value); + + free(label); + + num++; + } + } + } + } +} + +int StatsSensors::sensorType(int type) +{ + switch(type) + { + case SENSORS_SUBFEATURE_FAN_INPUT: + return 2; + break; + case SENSORS_SUBFEATURE_CURR_INPUT: + return 4; + break; + case SENSORS_SUBFEATURE_POWER_INPUT: + return 5; + break; + case SENSORS_SUBFEATURE_IN_INPUT: + case SENSORS_SUBFEATURE_VID: + return 3; + break; + default: + return 0; + break; + } + return 0; +} +#elif SENSORS_API_VERSION < 0x0400 /* libsensor 3 and earlier */ + +void StatsSensors::update_libsensors(long long sampleID) +{ + int a, b, c, num; + const sensors_chip_name * chip; + const sensors_feature_data * features; + + a = num = 0; + + while ((chip = sensors_get_detected_chips(&a))) + { + b = c = 0; + + while ((features = sensors_get_all_features(*chip, &b, &c))) + { + if ((!memcmp(features->name, "fan", 3) && features->name[4]=='\0') || (!memcmp(features->name, "temp", 3) && features->name[5]=='\0')){ + stringstream key; + key << num; + + if(createSensor(key.str()) == 1) + { + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + if((*cur).key == key.str()) + { + (*cur).method = 1; + (*cur).chip = chip->addr; + (*cur).sensor = features->number; + if(!memcmp(features->name, "fan", 3) && features->name[4]=='\0') + (*cur).kind = 2; + else + (*cur).kind = 0; + + char *label; + sensors_get_label(*chip, (*cur).sensor, &label); + (*cur).label = string(label); + } + } + } + + double value; + sensors_get_feature(*chip, features->number, &value); + + processSensor(key.str(), sampleID, value); + + num++; + } + } + } +} +#endif +#endif + +#ifdef HAVE_LIBSENSORS +void StatsSensors::init_libsensors() +{ +#if SENSORS_API_VERSION >= 0x0400 /* libsensor 4 */ + sensors_init(NULL); + libsensors_ready = true; +#else + FILE *fp; + + if ((fp = fopen("/etc/sensors.conf", "r")) == NULL) + return; + + if (sensors_init(fp) != 0) { + libsensors_ready = false; + fclose(fp); + } + libsensors_ready = true; +#endif +} +#endif + +void StatsSensors::init_dev_cpu() +{ +#if defined(HAVE_SYSCTLBYNAME) + int x; + for(x=0;x<32;x++) + { + stringstream key; + key << "dev.cpu." << x << ".temperature"; + + size_t len; + long buf; + len = sizeof(buf); + + if (sysctlbyname(key.str().c_str(), &buf, &len, NULL, 0) >= 0) + { + createSensor(key.str()); + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + if((*cur).key == key.str()) + { + stringstream label; + label << "CPU " << x; + (*cur).label = label.str(); + (*cur).method = 2; + } + } + } + } +#endif +} + +void StatsSensors::init_acpi_thermal() +{ +#if defined(HAVE_SYSCTLBYNAME) + int x; + for(x=0;x<32;x++) + { + stringstream key; + key << "hw.acpi.thermal.tz" << x << ".temperature"; + + size_t len; + long buf; + len = sizeof(buf); + + if (sysctlbyname(key.str().c_str(), &buf, &len, NULL, 0) >= 0) + { + createSensor(key.str()); + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + if((*cur).key == key.str()) + { + stringstream label; + label << "Thermal Zone " << x; + (*cur).label = label.str(); + (*cur).method = 4; + } + } + } + } +#endif +} + +void StatsSensors::init_acpi_freq() +{ +#if defined(HAVE_SYSCTLBYNAME) + int x; + for(x=0;x<32;x++) + { + stringstream key; + key << "dev.cpu." << x << ".freq"; + + size_t len; + long buf; + len = sizeof(buf); + + if (sysctlbyname(key.str().c_str(), &buf, &len, NULL, 0) >= 0) + { + createSensor(key.str()); + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + if((*cur).key == key.str()) + { + stringstream label; + label << "CPU " << x << " Frequency"; + (*cur).label = label.str(); + (*cur).method = 5; + (*cur).kind = 8; + } + } + } + } +#endif +} + +void StatsSensors::init_qnap() +{ + int temp; + + char *systempfile=(char*)"/proc/tsinfo/systemp"; + FILE *systempfp; + if ((systempfp=fopen(systempfile, "r"))==NULL) { + return; + } + if (fscanf(systempfp, "%d", &temp)!=1) { + fclose(systempfp); + return; + } + fclose(systempfp); + + createSensor("qnap"); + + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + (*cur).label = "Temperature"; + (*cur).method = 3; + } +} + +void StatsSensors::update_qnap(long long sampleID) +{ + char *systempfile=(char*)"/proc/tsinfo/systemp"; + FILE *systempfp; + int systemp; + if ((systempfp=fopen(systempfile, "r"))==NULL) + return; + else { + fseek(systempfp, 0l, 0); + if (fscanf(systempfp, "%d", &systemp) == 1){ + processSensor("qnap", sampleID, systemp); + } + fclose(systempfp); + } +} + +void StatsSensors::update_dev_cpu(long long sampleID) +{ +#if defined(HAVE_SYSCTLBYNAME) + if(_items.size() == 0) + return; + + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + if((*cur).method != 2) + continue; + + size_t len; + int buf; + len = sizeof(buf); + + if (sysctlbyname((*cur).key.c_str(), &buf, &len, NULL, 0) >= 0) + { + double value = (buf - 2732) / 10.0f; + processSensor((*cur).key, sampleID, value); + } + } +#endif +} + +void StatsSensors::update_acpi_thermal(long long sampleID) +{ +#if defined(HAVE_SYSCTLBYNAME) + if(_items.size() == 0) + return; + + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + if((*cur).method != 4) + continue; + + size_t len; + int buf; + len = sizeof(buf); + + if (sysctlbyname((*cur).key.c_str(), &buf, &len, NULL, 0) >= 0) + { + double value = (buf - 2732) / 10.0f; + processSensor((*cur).key, sampleID, value); + } + } +#endif +} + +void StatsSensors::update_acpi_freq(long long sampleID) +{ +#if defined(HAVE_SYSCTLBYNAME) + if(_items.size() == 0) + return; + + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + if((*cur).method != 5) + continue; + + size_t len; + int buf; + len = sizeof(buf); + + if (sysctlbyname((*cur).key.c_str(), &buf, &len, NULL, 0) >= 0) + { + processSensor((*cur).key, sampleID, (double)buf); + } + } +#endif +} +/* +Methods + +1 = libsensors +2 = dev.cpu.(x).temperature +3 = qnap +4 = hw.acpi.thermal.tz(x).temperature" +5 = dev.cpu.(x).freq + +*/ + +void StatsSensors::init() +{ + _init(); + +#ifdef HAVE_LIBSENSORS + init_libsensors(); +#endif + + init_dev_cpu(); + init_qnap(); + init_acpi_thermal(); + init_acpi_freq(); +} + +void StatsSensors::update(long long sampleID) +{ + #ifdef HAVE_LIBSENSORS + if(libsensors_ready == true) + update_libsensors(sampleID); + #endif + + update_qnap(sampleID); + update_dev_cpu(sampleID); + update_acpi_thermal(sampleID); + update_acpi_freq(sampleID); + + #ifdef USE_SQLITE + if(historyEnabled == true) + { + if(_items.size() > 0) + { + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + if((*cur).recordedValueChanged) + { + string sql = "UPDATE sensor_limits SET low = ?, high = ? where uuid = ?"; + + DatabaseItem dbItem = _database.databaseItem(sql); + sqlite3_bind_double(dbItem._statement, 1, (*cur).lowestValue); + sqlite3_bind_double(dbItem._statement, 2, (*cur).highestValue); + sqlite3_bind_text(dbItem._statement, 3, (*cur).key.c_str(), -1, SQLITE_STATIC); + databaseQueue.push_back(dbItem); + } + } + } + } + #endif +} + +int StatsSensors::createSensor(string key) +{ + if(_items.size() > 0) + { + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + sensor_info sensor = *cur; + if(sensor.key == key){ + return 0; + } + } + } + + sensor_info item; + item.key = key; + item.lowestValue = -1; + item.highestValue = 0; + + #ifdef USE_SQLITE + if(historyEnabled == true) + { + string sql = "select * from sensor_limits where uuid = ?"; + DatabaseItem query = _database.databaseItem(sql); + sqlite3_bind_text(query._statement, 1, key.c_str(), -1, SQLITE_STATIC); + + bool hasRow = false; + while(query.next()) + { + hasRow = true; + item.lowestValue = query.doubleForColumn("low"); + item.highestValue = query.doubleForColumn("high"); + } + + if(!hasRow){ + string sql = "insert into sensor_limits (uuid) values(?)"; + DatabaseItem dbItem = _database.databaseItem(sql); + sqlite3_bind_text(dbItem._statement, 1, key.c_str(), -1, SQLITE_STATIC); + dbItem.executeUpdate(); + } + + int x; + for(x=1;x<8;x++) + { + string table = databasePrefix + tableAtIndex(x); + double sampleID = 0; + if(samples[x].size() > 0) + sampleID = samples[x][0].sampleID; + + string sql = "select * from " + table + " where sample >= @sample AND uuid = ? order by sample asc limit 602"; + DatabaseItem query = _database.databaseItem(sql); + sqlite3_bind_double(query._statement, 1, sampleID - 602); + sqlite3_bind_text(query._statement, 2, key.c_str(), -1, SQLITE_STATIC); + + while(query.next()) + { + sensor_data sample; + sample.value = query.doubleForColumn("value"); + sample.sampleID = (long long)query.doubleForColumn("sample"); + sample.time = query.doubleForColumn("time"); + item.samples[x].push_front(sample); + } + } + } +#endif + + _items.insert(_items.begin(), item); + return 1; +} + +void StatsSensors::processSensor(string key, long long sampleID, double value) +{ + if(ready == 0) + return; + + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + if((*cur).key == key){ + sensor_data data; + data.value = value; + data.sampleID = sampleIndex[0].sampleID; + data.time = sampleIndex[0].time; + + (*cur).samples[0].push_front(data); + if ((*cur).samples[0].size() > HISTORY_SIZE) + (*cur).samples[0].pop_back(); + + bool changed = false; + (*cur).recordedValueChanged = false; + + if(value > (*cur).highestValue){ + (*cur).highestValue = value; + changed = true; + } + + if(value < (*cur).lowestValue || (*cur).lowestValue == -1){ + (*cur).lowestValue = value; + changed = true; + } + + (*cur).recordedValueChanged = changed; + } + } +} + +void StatsSensors::_init() +{ + initShared(); + ready = 0; + + #ifdef USE_SQLITE + if(historyEnabled == true) + { + databaseType = 1; + databasePrefix = "sensors_"; + + int x; + for(x=1;x<8;x++) + { + string table = databasePrefix + tableAtIndex(x) + "_id"; + if(!_database.tableExists(table)) + { + string sql = "create table " + table + " (sample double PRIMARY KEY NOT NULL, time double NOT NULL DEFAULT 0, empty integer NOT NULL DEFAULT 0)"; + DatabaseItem dbItem = _database.databaseItem(sql); + dbItem.executeUpdate(); + } + + table = databasePrefix + tableAtIndex(x); + if(!_database.tableExists(table)) + { + string sql = "create table " + table + " (sample double NOT NULL, time double NOT NULL DEFAULT 0, uuid varchar(255) NOT NULL, value double NOT NULL DEFAULT 0)"; + DatabaseItem dbItem = _database.databaseItem(sql); + dbItem.executeUpdate(); + } + } + + string table = "sensor_limits"; + if(!_database.tableExists(table)) + { + string sql = "create table sensor_limits (uuid varchar(255) NOT NULL, low double NOT NULL DEFAULT 0, high double NOT NULL DEFAULT 0)"; + DatabaseItem dbItem = _database.databaseItem(sql); + dbItem.executeUpdate(); + } + loadPreviousSamples(); + fillGaps(); + } + #endif +} + +#ifdef USE_SQLITE +void StatsSensors::loadPreviousSamples() +{ + loadPreviousSamplesAtIndex(1); + loadPreviousSamplesAtIndex(2); + loadPreviousSamplesAtIndex(3); + loadPreviousSamplesAtIndex(4); + loadPreviousSamplesAtIndex(5); + loadPreviousSamplesAtIndex(6); + loadPreviousSamplesAtIndex(7); +} + +void StatsSensors::loadPreviousSamplesAtIndex(int index) +{ + + string table = databasePrefix + tableAtIndex(index) + "_id"; + double sampleID = sampleIdForTable(table); + + string sql = "select * from " + table + " where sample >= @sample order by sample asc limit 602"; + DatabaseItem query = _database.databaseItem(sql); + sqlite3_bind_double(query._statement, 1, sampleID - 602); + + while(query.next()) + { + sample_data sample; + sample.sampleID = (long long)query.doubleForColumn("sample"); + sample.time = query.doubleForColumn("time"); + samples[index].insert(samples[index].begin(), sample); + } + if(samples[index].size() > 0) + { + sampleIndex[index].sampleID = samples[index][0].sampleID; + sampleIndex[index].time = samples[index][0].time; + sampleIndex[index].nextTime = sampleIndex[index].time + sampleIndex[index].interval; + } +} + +void StatsSensors::updateHistory() +{ + int x; + for (x = 1; x < 8; x++) + { + if(sampleIndex[0].time >= sampleIndex[x].nextTime) + { + double now = get_current_time(); + double earlistTime = now - (HISTORY_SIZE * sampleIndex[x].interval); + while(sampleIndex[x].nextTime < now) + { + sampleIndex[x].sampleID = sampleIndex[x].sampleID + 1; + sampleIndex[x].time = sampleIndex[x].nextTime; + sampleIndex[x].nextTime = sampleIndex[x].nextTime + sampleIndex[x].interval; + + if(sampleIndex[x].time < earlistTime) + continue; + + if(_items.size() > 0) + { + for (vector::iterator cur = _items.begin(); cur != _items.end(); ++cur) + { + sensor_data sample = historyItemAtIndex(x, (*cur)); + + (*cur).samples[x].push_front(sample); + if ((*cur).samples[x].size() > HISTORY_SIZE) (*cur).samples[x].pop_back(); + + if(sample.empty) + continue; + + string table = databasePrefix + tableAtIndex(x); + string sql = "insert into " + table + " (sample, time, value, uuid) values(?, ?, ?, ?)"; + + DatabaseItem dbItem = _database.databaseItem(sql); + sqlite3_bind_double(dbItem._statement, 1, (double)sample.sampleID); + sqlite3_bind_double(dbItem._statement, 2, sample.time); + sqlite3_bind_double(dbItem._statement, 3, sample.value); + sqlite3_bind_text(dbItem._statement, 4, (*cur).key.c_str(), -1, SQLITE_STATIC); + databaseQueue.push_back(dbItem); + //dbItem.executeUpdate(); + } + } + + string table = databasePrefix + tableAtIndex(x) + "_id"; + string sql = "insert into " + table + " (empty, sample, time) values(?, ?, ?)"; + + DatabaseItem dbItem = _database.databaseItem(sql); + sqlite3_bind_int(dbItem._statement, 1, 0); + sqlite3_bind_double(dbItem._statement, 2, (double)sampleIndex[x].sampleID); + sqlite3_bind_double(dbItem._statement, 3, sampleIndex[x].time); + databaseQueue.push_back(dbItem); + } + } + } +} + +sensor_data StatsSensors::historyItemAtIndex(int index, sensor_info item) +{ + sensor_data sample; + double value = 0; + + std::deque from = item.samples[sampleIndex[index].historyIndex]; + double minimumTime = sampleIndex[index].time - sampleIndex[index].interval; + double maximumTime = sampleIndex[index].time; + if(sampleIndex[index].historyIndex == 0) + maximumTime += 0.99; + + int count = 0; + if(from.size() > 0) + { + for (deque::iterator cursample = from.begin(); cursample != from.end(); ++cursample) + { + if ((*cursample).time > maximumTime){ + continue; + } + + if ((*cursample).time < minimumTime) + break; + + value += (*cursample).value; + count++; + } + if (count > 0 && value > 0) + { + value /= count; + } + } + + sample.value = value; + sample.sampleID = sampleIndex[index].sampleID; + sample.time = sampleIndex[index].time; + sample.empty = false; + if(count == 0) + sample.empty = true; + + return sample; +} +#endif diff --git a/src/stats/StatsSensors.h b/src/stats/StatsSensors.h new file mode 100644 index 0000000..34baf2a --- /dev/null +++ b/src/stats/StatsSensors.h @@ -0,0 +1,99 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "StatBase.h" + +#ifndef _STATSSENSORS_H +#define _STATSSENSORS_H + +class sensor_info +{ + public: + int id; + std::string key; + double last_update; + double lowestValue; + double highestValue; + bool recordedValueChanged; + + int kind; + std::string label; + unsigned int chip; + unsigned int sensor; + int method; + + std::deque samples[8]; +}; + +class StatsSensors : public StatsBase +{ + public: + void init(); + void _init(); + void init_dev_cpu(); + void init_qnap(); + void init_acpi_thermal(); + void init_acpi_freq(); + #ifdef HAVE_LIBSENSORS + void init_libsensors(); + #endif + + void update(long long sampleID); + void update_qnap(long long sampleID); + void update_dev_cpu(long long sampleID); + void update_acpi_thermal(long long sampleID); + void update_acpi_freq(long long sampleID); + #ifdef HAVE_LIBSENSORS + void update_libsensors(long long sampleID); + #endif + + std::vector _items; + int createSensor(std::string key); + void processSensor(std::string key, long long sampleID, double value); + + + #ifdef HAVE_LIBSENSORS + bool libsensors_ready; + #if SENSORS_API_VERSION >= 0x0400 /* libsensor 4 */ + int sensorType(int type); + #endif + #endif + + #ifdef USE_SQLITE + void updateHistory(); + sensor_data historyItemAtIndex(int index, sensor_info item); + void loadPreviousSamples(); + void loadPreviousSamplesAtIndex(int index); + #endif + + std::deque samples[8]; + }; +#endif diff --git a/src/stats/StatsUptime.cpp b/src/stats/StatsUptime.cpp new file mode 100644 index 0000000..350b46e --- /dev/null +++ b/src/stats/StatsUptime.cpp @@ -0,0 +1,141 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "StatsUptime.h" + +using namespace std; + +#if defined(USE_UPTIME_HPUX) + +long StatsUptime::getUptime() +{ + struct pst_static pstat_static; + if (pstat_getstatic(&pstat_static, sizeof(pstat_static), 1, 0) == -1) { + return; + } + return time(NULL) - pstat_static.boot_time; +} + +#elif defined(HAVE_LIBKSTAT) && defined(USE_UPTIME_KSTAT) + +long StatsUptime::getUptime() +{ + kstat_t *ksp; + kstat_named_t *kn; + static time_t boottime; + + if(0 == boottime) + { + kstat_chain_update(ksh); + if(NULL == (ksp = kstat_lookup(ksh, (char*)"unix", -1, (char*)"system_misc"))) return -1; + if(-1 == kstat_read(ksh, ksp, NULL)) return -1; + if(NULL == (kn = (kstat_named_t *) kstat_data_lookup(ksp, (char*)"boot_time"))) return -1; + boottime = (time_t) ksgetull(kn); + } + return time(NULL) - boottime; +} + +/*USE_UPTIME_KSTAT*/ +# elif defined(USE_UPTIME_PROCFS) + +long StatsUptime::getUptime() +{ + long uptime; + FILE * fp = NULL; + + if (!(fp = fopen("/proc/uptime", "r"))) return -1; + + if(1 != fscanf(fp, "%ld", &uptime)) + { + return -1; + } + + fclose(fp); + + return uptime; +} + +#elif defined(USE_UPTIME_PERFSTAT) + +long StatsUptime::getUptime() +{ + perfstat_cpu_total_t cpustats; + if (perfstat_cpu_total(NULL, &cpustats, sizeof cpustats, 1)!=1) + return 0; + + int hertz = sysconf(_SC_CLK_TCK); + if (hertz <= 0) + hertz = HZ; + + return cpustats.lbolt / hertz; +}/*USE_UPTIME_PERFSTAT*/ + +#elif defined(USE_UPTIME_GETTIME) + +long StatsUptime::getUptime() +{ + struct timespec tp; + + tp.tv_sec = 0; + tp.tv_nsec = 0; + + clock_gettime(CLOCK_UPTIME, &tp); + + return (long)(tp.tv_sec); +}/*USE_UPTIME_GETTIME*/ + +#elif defined(USE_UPTIME_SYSCTL) + +long StatsUptime::getUptime() +{ + struct timeval tm; + time_t now; + int mib[2]; + size_t size; + + mib[0] = CTL_KERN; + mib[1] = KERN_BOOTTIME; + size = sizeof(tm); + now = time(NULL); + if(-1 != sysctl(mib, 2, &tm, &size, NULL, 0) && 0 != tm.tv_sec) + { + return (long)(now - tm.tv_sec); + } + return 0; +} +#else + +long StatsUptime::getUptime() +{ + return 0; +} + +#endif diff --git a/src/stats/StatsUptime.h b/src/stats/StatsUptime.h new file mode 100644 index 0000000..82a971a --- /dev/null +++ b/src/stats/StatsUptime.h @@ -0,0 +1,46 @@ +/* + * Copyright 2016 Bjango Pty Ltd. All rights reserved. + * Copyright 2010 William Tisäter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the copyright holder may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL WILLIAM TISÄTER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "StatBase.h" + +#ifndef _STATSUPTIME_H +#define _STATSUPTIME_H + +class StatsUptime +{ + public: + long getUptime(); + + #ifdef HAVE_LIBKSTAT + kstat_ctl_t *ksh; + #endif + }; +#endif