This commit is contained in:
lxsang 2021-02-03 12:40:43 +01:00
parent 6b3240f7ea
commit dc4f5e252d
8 changed files with 1204 additions and 0 deletions

14
Makefile.am Normal file
View File

@ -0,0 +1,14 @@
AUTOMAKE_OPTIONS = foreign
AM_CPPFLAGS = -W -Wall -g -std=c99 -DPREFIX="\"$(prefix)\""
# bin
bin_PROGRAMS = sysmond
# source files
sysmond_SOURCES = ini.c sysmon.c
sysconf_DATA = sysmond.conf
install-data-local:
- [ -d /etc/systemd/system/ ] && cp sysmond.service /etc/systemd/system/
EXTRA_DIST = ini.h sysmond.conf sysmond.service

52
configure.ac Normal file
View File

@ -0,0 +1,52 @@
# initialise autoconf and set up some basic information about the program were packaging
AC_INIT([sysmon], [0.1.0], [xsang.le@gmail.com])
# Were going to use automake for this project
# [subdir-objects] if needed
AM_INIT_AUTOMAKE([subdir-objects])
# dependencies
# C compiler
AC_PROG_CC
# libtool for linking
AC_PROG_LIBTOOL
AC_DEFINE([_GNU_SOURCE], [1],[Use GNU source])
# AC_CANONICAL_HOST is needed to access the 'host_os' variable
AC_CHECK_LIB([m],[pow],[],[
AC_MSG_ERROR([The math library is required])
])
AC_CANONICAL_HOST
build_linux=no
build_windows=no
build_mac=no
# Detect the target system
case "${host_os}" in
linux*)
AC_DEFINE([LINUX], [1],[Linux system])
build_linux=yes
;;
darwin*)
build_mac=yes
AC_DEFINE([MACOS], [1],[MacOS system])
;;
*)
AC_MSG_ERROR(["OS $host_os is not supported"])
;;
esac
AM_CONDITIONAL([LINUX], [test "$build_linux" = "yes"])
AM_CONDITIONAL([WINDOWS], [test "$build_windows" = "yes"])
AM_CONDITIONAL([OSX], [test "$build_mac" = "yes"])
# find a file called Makefile.in, substitute placeholders
# like @PACKAGE_VERSION@ with values like 0.1.0,
# and write the results to Makefile.
AC_CONFIG_FILES([
Makefile
])
# output the script:
AC_OUTPUT

185
ini.c Normal file
View File

@ -0,0 +1,185 @@
/* inih -- simple .INI file parser
inih is released under the New BSD license (see LICENSE.txt). Go to the project
home page for more info:
https://github.com/benhoyt/inih
*/
#ifdef _MSC_VER
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include "ini.h"
#if !INI_USE_STACK
#include <stdlib.h>
#endif
#define MAX_SECTION 50
#define MAX_NAME 50
/* Strip whitespace chars off end of given string, in place. Return s. */
static char* rstrip(char* s)
{
char* p = s + strlen(s);
while (p > s && isspace((unsigned char)(*--p)))
*p = '\0';
return s;
}
/* Return pointer to first non-whitespace char in given string. */
static char* lskip(const char* s)
{
while (*s && isspace((unsigned char)(*s)))
s++;
return (char*)s;
}
/* Return pointer to first char c or ';' comment in given string, or pointer to
null at end of string if neither found. ';' must be prefixed by a whitespace
character to register as a comment. */
static char* find_char_or_comment(const char* s, char c)
{
int was_whitespace = 0;
while (*s && *s != c && !(was_whitespace && *s == ';')) {
was_whitespace = isspace((unsigned char)(*s));
s++;
}
return (char*)s;
}
/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
static char* strncpy0(char* dest, const char* src, size_t size)
{
strncpy(dest, src, size);
dest[size - 1] = '\0';
return dest;
}
/* See documentation in header file. */
int ini_parse_file(FILE* file,
int (*handler)(void*, const char*, const char*,
const char*),
void* user)
{
/* Uses a fair bit of stack (use heap instead if you need to) */
#if INI_USE_STACK
char line[INI_MAX_LINE];
#else
char* line;
#endif
char section[MAX_SECTION] = "";
char prev_name[MAX_NAME] = "";
char* start;
char* end;
char* name;
char* value;
int lineno = 0;
int error = 0;
#if !INI_USE_STACK
line = (char*)malloc(INI_MAX_LINE);
if (!line) {
return -2;
}
#endif
/* Scan through file line by line */
while (fgets(line, INI_MAX_LINE, file) != NULL) {
lineno++;
start = line;
#if INI_ALLOW_BOM
if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
(unsigned char)start[1] == 0xBB &&
(unsigned char)start[2] == 0xBF) {
start += 3;
}
#endif
start = lskip(rstrip(start));
if (*start == ';' || *start == '#') {
/* Per Python ConfigParser, allow '#' comments at start of line */
}
#if INI_ALLOW_MULTILINE
else if (*prev_name && *start && start > line) {
/* Non-black line with leading whitespace, treat as continuation
of previous name's value (as per Python ConfigParser). */
if (!handler(user, section, prev_name, start) && !error)
error = lineno;
}
#endif
else if (*start == '[') {
/* A "[section]" line */
end = find_char_or_comment(start + 1, ']');
if (*end == ']') {
*end = '\0';
strncpy0(section, start + 1, sizeof(section));
*prev_name = '\0';
}
else if (!error) {
/* No ']' found on section line */
error = lineno;
}
}
else if (*start && *start != ';') {
/* Not a comment, must be a name[=:]value pair */
end = find_char_or_comment(start, '=');
if (*end != '=') {
end = find_char_or_comment(start, ':');
}
if (*end == '=' || *end == ':') {
*end = '\0';
name = rstrip(start);
value = lskip(end + 1);
end = find_char_or_comment(value, '\0');
if (*end == ';')
*end = '\0';
rstrip(value);
/* Valid name[=:]value pair found, call handler */
strncpy0(prev_name, name, sizeof(prev_name));
if (!handler(user, section, name, value) && !error)
error = lineno;
}
else if (!error) {
/* No '=' or ':' found on name[=:]value line */
error = lineno;
}
}
#if INI_STOP_ON_FIRST_ERROR
if (error)
break;
#endif
}
#if !INI_USE_STACK
free(line);
#endif
return error;
}
/* See documentation in header file. */
int ini_parse(const char* filename,
int (*handler)(void*, const char*, const char*, const char*),
void* user)
{
FILE* file;
int error;
file = fopen(filename, "r");
if (!file)
return -1;
error = ini_parse_file(file, handler, user);
fclose(file);
return error;
}

77
ini.h Normal file
View File

@ -0,0 +1,77 @@
/* inih -- simple .INI file parser
inih is released under the New BSD license (see LICENSE.txt). Go to the project
home page for more info:
https://github.com/benhoyt/inih
*/
#ifndef __INI_H__
#define __INI_H__
/* Make this header file easier to include in C++ code */
#ifdef __cplusplus
extern "C" {
#endif
#include <stdio.h>
/* Parse given INI-style file. May have [section]s, name=value pairs
(whitespace stripped), and comments starting with ';' (semicolon). Section
is "" if name=value pair parsed before any section heading. name:value
pairs are also supported as a concession to Python's ConfigParser.
For each name=value pair parsed, call handler function with given user
pointer as well as section, name, and value (data only valid for duration
of handler call). Handler should return nonzero on success, zero on error.
Returns 0 on success, line number of first error on parse error (doesn't
stop on first error), -1 on file open error, or -2 on memory allocation
error (only when INI_USE_STACK is zero).
*/
int ini_parse(const char* filename,
int (*handler)(void* user, const char* section,
const char* name, const char* value),
void* user);
/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't
close the file when it's finished -- the caller must do that. */
int ini_parse_file(FILE* file,
int (*handler)(void* user, const char* section,
const char* name, const char* value),
void* user);
/* Nonzero to allow multi-line value parsing, in the style of Python's
ConfigParser. If allowed, ini_parse() will call the handler with the same
name for each subsequent line parsed. */
#ifndef INI_ALLOW_MULTILINE
#define INI_ALLOW_MULTILINE 1
#endif
/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
the file. See http://code.google.com/p/inih/issues/detail?id=21 */
#ifndef INI_ALLOW_BOM
#define INI_ALLOW_BOM 1
#endif
/* Nonzero to use stack, zero to use heap (malloc/free). */
#ifndef INI_USE_STACK
#define INI_USE_STACK 1
#endif
/* Stop parsing on first error (default is to keep parsing). */
#ifndef INI_STOP_ON_FIRST_ERROR
#define INI_STOP_ON_FIRST_ERROR 0
#endif
/* Maximum line length for any line in INI file. */
#ifndef INI_MAX_LINE
#define INI_MAX_LINE 512
#endif
#ifdef __cplusplus
}
#endif
#endif /* __INI_H__ */

832
sysmon.c Normal file
View File

@ -0,0 +1,832 @@
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdint.h>
#include <getopt.h>
#include <stdlib.h>
#include <syslog.h>
#include <signal.h>
#include <sys/timerfd.h>
#include <time.h>
#include <sys/time.h>
#include <sys/statvfs.h>
#include <math.h>
#include "ini.h"
#define DEFAULT_CONF_FILE (PREFIX "/etc/sysmond.conf")
#define MODULE_NAME "sysmon"
// #define DEFAULT_INPUT "/sys/class/hwmon/hwmon2/device/in3_input"
#define NET_INF_STAT_PT "/sys/class/net/%s/statistics/%s"
#define LOG_INIT(m) do { \
setlogmask (LOG_UPTO (LOG_NOTICE)); \
openlog ((m), LOG_CONS | LOG_PID | LOG_NDELAY, LOG_USER); \
} while(0)
#define M_LOG(m, a,...) syslog ((LOG_NOTICE),m "_log@[%s: %d]: " a "\n", __FILE__, \
__LINE__, ##__VA_ARGS__)
#define M_ERROR(m, a,...) syslog ((LOG_ERR),m "_error@[%s: %d]: " a "\n", __FILE__, \
__LINE__, ##__VA_ARGS__)
#define JSON_FMT "{" \
"\"stamp_sec\": %lu," \
"\"stamp_usec\": %lu," \
"\"battery\": %.3f," \
"\"battery_percent\": %.3f," \
"\"battery_max_voltage\": %d," \
"\"battery_min_voltage\": %d," \
"\"cpu_temp\": %d," \
"\"gpu_temp\": %d," \
"\"cpu_usages\":[%s]," \
"\"mem_total\": %lu," \
"\"mem_free\": %lu," \
"\"mem_used\": %lu," \
"\"mem_buff_cache\": %lu," \
"\"mem_available\": %lu," \
"\"mem_swap_total\": %lu," \
"\"mem_swap_free\": %lu," \
"\"disk_total\": %lu," \
"\"disk_free\": %lu," \
"\"net\":[%s]" \
"}"
#define JSON_NET_FMT "{" \
"\"name\":\"%s\"," \
"\"rx\": %lu," \
"\"tx\": %lu," \
"\"rx_rate\": %.3f," \
"\"tx_rate\": %.3f" \
"},"
#define MAX_BUF 256
#define EQU(a,b) (strncmp(a,b,MAX_BUF) == 0)
#define MAX_NETWORK_INF 8
typedef struct
{
char bat_in[MAX_BUF];
uint16_t max_voltage;
uint16_t min_voltage;
uint16_t cutoff_voltage;
float ratio;
uint16_t read_voltage;
float percent;
} sys_bat_t;
typedef struct
{
char cpu_temp_file[MAX_BUF];
char gpu_temp_file[MAX_BUF];
uint16_t cpu;
uint16_t gpu;
} sys_temp_t;
typedef struct {
char name[32];
unsigned long tx;
unsigned long rx;
float rx_rate;
float tx_rate;
} sys_net_inf_t;
typedef struct {
uint8_t n_intf;
/*Monitor up to 8 interfaces*/
sys_net_inf_t interfaces[MAX_NETWORK_INF];
} sys_net_t;
typedef struct
{
unsigned long last_idle;
unsigned long last_sum;
float percent;
} sys_cpu_t;
typedef struct {
char mount_path[MAX_BUF];
unsigned long d_total;
unsigned long d_free;
} sys_disk_t;
typedef struct
{
unsigned long m_total;
unsigned long m_free;
unsigned long m_available;
unsigned long m_cache;
unsigned long m_buffer;
unsigned long m_swap_total;
unsigned long m_swap_free;
} sys_mem_t;
typedef struct {
char conf_file[MAX_BUF];
char data_file_out[MAX_BUF];
sys_bat_t bat_stat;
sys_cpu_t* cpus;
sys_mem_t mem;
sys_temp_t temp;
sys_net_t net;
sys_disk_t disk;
int n_cpus;
struct itimerspec sample_period;
int pwoff_cd;
uint8_t power_off_percent;
} app_data_t;
static volatile int running = 1;
static char buf[MAX_BUF];
static void int_handler(int dummy)
{
(void)dummy;
running = 0;
}
static void help(const char *app)
{
fprintf(stderr,
"Usage: %s options.\n"
"Options:\n"
"\t -f <value>: config file\n"
"\t -h <value>: this help message\n",
app);
}
static void map(app_data_t* opt)
{
float volt = opt->bat_stat.read_voltage*opt->bat_stat.ratio;
if(volt < opt->bat_stat.min_voltage)
{
opt->bat_stat.percent = 0.0;
return;
}
float result = 101 - (101 / pow(1 + pow(1.33 * (volt - opt->bat_stat.min_voltage) /
(opt->bat_stat.max_voltage - opt->bat_stat.min_voltage), 4.5), 3));
if(result > 100.0)
result = 100.0;
opt->bat_stat.percent = result;
}
static int guard_write(int fd, void* buffer, size_t size)
{
int n = 0;
int write_len;
int st;
while(n != (int)size)
{
write_len = (int)size - n;
st = write(fd,buffer + n,write_len);
if(st == -1)
{
M_ERROR(MODULE_NAME,"Unable to write to #%d: %s", fd, strerror(errno));
return -1;
}
if(st == 0)
{
M_ERROR(MODULE_NAME,"Endpoint %d is closed", fd);
return -1;
}
n += st;
}
return n;
}
static int read_line(int fd, char *buf, int size)
{
int i = 0;
char c = '\0';
int n;
while ((i < size - 1) && (c != '\n'))
{
n = read(fd, &c, 1);
if (n > 0)
{
buf[i] = c;
i++;
}
else
c = '\n';
}
buf[i] = '\0';
return i;
}
static int read_voltage(app_data_t* opts)
{
int fd, ret;
if(opts->bat_stat.bat_in[0] == '\0')
{
return 0;
}
fd = open(opts->bat_stat.bat_in, O_RDONLY);
if(fd < 0)
{
M_ERROR(MODULE_NAME, "Unable to open input: %s", opts->bat_stat.bat_in);
return -1;
}
(void)memset(buf, '\0', sizeof(buf));
ret = read(fd, buf, sizeof(buf));
if(ret > 0)
{
opts->bat_stat.read_voltage = atoi(buf);
map(opts);
}
(void)close(fd);
return 0;
}
static int read_cpu_info(app_data_t* opts)
{
int fd, ret, j, i = 0;
const char d[2] = " ";
char* token;
unsigned long sum = 0, idle = 0;
fd = open("/proc/stat", O_RDONLY);
if(fd < 0)
{
M_ERROR(MODULE_NAME, "Unable to open stat: %s", strerror(errno));
return -1;
}
for (i = 0; i < opts->n_cpus; i++)
{
ret = read_line(fd, buf, MAX_BUF);
if(ret > 0 && buf[0] == 'c' && buf[1] == 'p' && buf[2] == 'u')
{
token = strtok(buf,d);
sum = 0;
j = 0;
while(token!=NULL)
{
token = strtok(NULL,d);
if(token!=NULL){
sum += strtoul(token, NULL, 10);
if(j==3)
idle = strtoul(token, NULL, 10);
j++;
}
}
opts->cpus[i].percent = 100 - (idle-opts->cpus[i].last_idle)*100.0/(sum-opts->cpus[i].last_sum);
opts->cpus[i].last_idle = idle;
opts->cpus[i].last_sum = sum;
}
else
{
M_ERROR(MODULE_NAME, "Unable to read CPU infos at: %d", i);
break;
}
}
(void) close(fd);
if(i==0)
{
M_ERROR(MODULE_NAME, "No CPU info found");
return -1;
}
return i;
}
static int read_mem_info(app_data_t* opts)
{
int fd, ret;
const char d[2] = " ";
unsigned long data[7];
char* token;
fd = open("/proc/meminfo", O_RDONLY);
if(fd < 0)
{
M_ERROR(MODULE_NAME, "Unable to open meminfo: %s", strerror(errno));
return -1;
}
for (int i = 0; i < 7; i++) {
ret = read_line(fd,buf,MAX_BUF);
token = strtok(buf,d);
token = strtok(NULL,d);
if(token != NULL)
{
data[i] = (unsigned long)strtoul(token, NULL, 10);
}
else
{
data[i] = 0;
}
if(i == 4)
{
for (int j = 0; j < 9; j++) {
ret = read_line(fd,buf,MAX_BUF);
// skip 10 line
}
}
}
opts->mem.m_total = data[0];
opts->mem.m_free = data[1];
opts->mem.m_available = data[2];
opts->mem.m_buffer = data[3];
opts->mem.m_cache = data[4];
opts->mem.m_swap_total = data[5];
opts->mem.m_swap_free = data[6];
(void)ret;
(void)close(fd);
/*printf("total: %d used: %d, free: %d buffer/cache: %d, available: %d \n",
opts->mem.m_total / 1024,
(opts->mem.m_total - opts->mem.m_free - opts->mem.m_buffer-opts->mem.m_cache)/1024,
opts->mem.m_free/1024,
(opts->mem.m_buffer+opts->mem.m_cache)/1024,
opts->mem.m_available/1024);*/
return 0;
}
static int read_temp_file(const char* file, uint16_t* output)
{
int fd, ret;
if(file[0] != '\0')
{
fd = open(file, O_RDONLY);
if(fd < 0)
{
M_ERROR(MODULE_NAME, "Unable to open temp file %s : %s", file, strerror(errno));
return -1;
}
(void)memset(buf, '\0', sizeof(buf));
ret = read(fd, buf, MAX_BUF);
if(ret < 0)
{
M_ERROR(MODULE_NAME, "Unable to read temperature: %s", strerror(errno));
(void) close(fd);
return -1;
}
*output = (uint16_t)atoi(buf);
(void) close(fd);
}
return 0;
}
static int read_cpu_temp(app_data_t* opts)
{
if(read_temp_file(opts->temp.cpu_temp_file, &opts->temp.cpu) == -1)
{
return -1;
}
return read_temp_file(opts->temp.gpu_temp_file, &opts->temp.gpu);
}
static int read_net_statistic(app_data_t* opts)
{
int fd, ret;
float period;
long unsigned int bytes;
period = ((float)opts->sample_period.it_value.tv_nsec) / 1.0e9;
for (int i = 0; i < opts->net.n_intf; i++)
{
// rx
(void)snprintf(buf, MAX_BUF-1, NET_INF_STAT_PT, opts->net.interfaces[i].name, "rx_bytes");
fd = open(buf, O_RDONLY);
if(fd < 0)
{
M_ERROR(MODULE_NAME, "Unable to open %s: %s", buf, strerror(errno));
return -1;
}
// read data to buff
(void)memset(buf,'\0', MAX_BUF);
ret = read(fd, buf, MAX_BUF);
(void)close(fd);
if(ret <= 0)
{
M_ERROR(MODULE_NAME, "Unable to read RX data of %s: %s", opts->net.interfaces[i].name, strerror(errno));
return -1;
}
bytes = (unsigned long) strtoul(buf, NULL, 10);
opts->net.interfaces[i].rx_rate = ((float)(bytes - opts->net.interfaces[i].rx) / period);
opts->net.interfaces[i].rx = bytes;
(void)snprintf(buf, MAX_BUF-1, NET_INF_STAT_PT, opts->net.interfaces[i].name, "tx_bytes");
fd = open(buf, O_RDONLY);
if(fd < 0)
{
M_ERROR(MODULE_NAME, "Unable to open %s: %s", buf, strerror(errno));
return -1;
}
// read data to buff
(void)memset(buf,'\0', MAX_BUF);
ret = read(fd, buf, MAX_BUF);
(void)close(fd);
if(ret <= 0)
{
M_ERROR(MODULE_NAME, "Unable to read TX data of %s: %s", opts->net.interfaces[i].name, strerror(errno));
return -1;
}
bytes = (unsigned long) strtoul(buf, NULL, 10);
opts->net.interfaces[i].tx_rate = ((float)(bytes - opts->net.interfaces[i].tx) / period);
opts->net.interfaces[i].tx = bytes ;
}
return 0;
}
static int read_disk_usage(app_data_t* opts)
{
struct statvfs stat;
int ret = statvfs(opts->disk.mount_path, &stat);
if(ret < 0)
{
M_ERROR(MODULE_NAME, "Unable to query disk usage of %s: %s", opts->disk.mount_path, strerror(errno));
return -1;
}
opts->disk.d_total = stat.f_blocks * stat.f_frsize;
opts->disk.d_free = stat.f_bfree * stat.f_frsize;
return 0;
}
static int log_to_file(app_data_t* opts)
{
int ret,fd;
char out_buf[1024];
char net_buf[MAX_BUF];
if(opts->data_file_out[0] == '\0')
{
return 0;
}
fd = open(opts->data_file_out, O_CREAT|O_WRONLY|O_APPEND | O_NONBLOCK, 0644);
if(fd < 0)
{
M_ERROR(MODULE_NAME, "Unable to open output file: %s", strerror(errno));
return -1;
}
(void)memset(buf,'\0',MAX_BUF);
char* ptr = buf;
// CPU
size_t len = 0;
for (int i = 0; i < opts->n_cpus; i++) {
if(MAX_BUF - len -1 <= 0)
{
break;
}
snprintf(ptr, MAX_BUF - len -1, "%.3f,", opts->cpus[i].percent);
len = strlen(buf);
ptr = buf+len;
}
buf[len - 1] = '\0';
// NET
len = 0;
ptr = net_buf;
for (int i = 0; i < opts->net.n_intf; i++) {
if(MAX_BUF - len -1 < strlen(JSON_NET_FMT))
{
break;
}
snprintf(ptr, MAX_BUF - len -1, JSON_NET_FMT,
opts->net.interfaces[i].name,
opts->net.interfaces[i].rx,
opts->net.interfaces[i].tx,
opts->net.interfaces[i].rx_rate,
opts->net.interfaces[i].tx_rate
);
len = strlen(net_buf);
ptr = net_buf+len;
}
net_buf[len - 1] = '\0';
struct timeval now;
gettimeofday(&now, NULL);
snprintf(out_buf, sizeof(out_buf), JSON_FMT,
now.tv_sec,
now.tv_usec,
opts->bat_stat.read_voltage* opts->bat_stat.ratio,
opts->bat_stat.percent,
opts->bat_stat.max_voltage,
opts->bat_stat.min_voltage,
opts->temp.cpu,
opts->temp.gpu,
buf,
opts->mem.m_total,
opts->mem.m_free,
(opts->mem.m_total - opts->mem.m_free - opts->mem.m_buffer-opts->mem.m_cache),
opts->mem.m_buffer+opts->mem.m_cache,
opts->mem.m_available,
opts->mem.m_swap_total,
opts->mem.m_swap_free,
opts->disk.d_total,
opts->disk.d_free,
net_buf
);
ret = guard_write(fd,out_buf,strlen(out_buf));
if(ret <= 0)
{
M_ERROR(MODULE_NAME, "Unable to write data to output file");
ret = -1;
}
if(ret != (int)strlen(out_buf))
{
M_ERROR(MODULE_NAME, "Unable to write all battery info to output file");
ret = -1;
}
else
{
// M_LOG(MODULE_NAME, "written %d bytes to file: %s", strlen(out_buf), opts->data_file_out);
ret = 0;
}
(void) close(fd);
return ret;
}
static int ini_handle(void *user_data, const char *section, const char *name, const char *value)
{
(void)section;
unsigned long period = 0;
const char d[2] = ",";
char* token;
app_data_t* opts = (app_data_t*) user_data;
if(EQU(name, "battery_max_voltage"))
{
opts->bat_stat.max_voltage = atoi(value);
}
else if(EQU(name, "battery_min_voltage"))
{
opts->bat_stat.min_voltage = atoi(value);
}
else if(EQU(name, "battery_cutoff_votalge"))
{
opts->bat_stat.cutoff_voltage = atoi(value);
}
else if(EQU(name, "battery_divide_ratio"))
{
opts->bat_stat.ratio = atof(value);
}
else if(EQU(name, "battery_input"))
{
strncpy(opts->bat_stat.bat_in, value, MAX_BUF - 1);
}
else if(EQU(name, "sample_period"))
{
period = strtoul(value, NULL, 10)*1e6;
opts->sample_period.it_interval.tv_nsec = period;
opts->sample_period.it_value.tv_nsec = period;
}
else if(EQU(name, "cpu_core_number"))
{
opts->n_cpus = atoi(value) + 1;
}
else if(EQU(name, "power_off_count_down"))
{
opts->pwoff_cd = atoi(value);
}
else if(EQU(name, "power_off_percent"))
{
opts->power_off_percent = (uint8_t)atoi(value);
}
else if(EQU(name, "data_file_out"))
{
(void)strncpy(opts->data_file_out, value, MAX_BUF-1);
}
else if(EQU(name, "cpu_temperature_input"))
{
(void)strncpy(opts->temp.cpu_temp_file, value, MAX_BUF-1);
}
else if(EQU(name, "gpu_temperature_input"))
{
(void)strncpy(opts->temp.gpu_temp_file, value, MAX_BUF-1);
}
else if(EQU(name, "disk_mount_point"))
{
(void)strncpy(opts->disk.mount_path, value, MAX_BUF-1);
}
else if(EQU(name, "network_interfaces"))
{
// parsing the network interfaces
token = strtok((char*)value,d);
opts->net.n_intf = 0;
while(token != NULL)
{
(void) strncpy(opts->net.interfaces[opts->net.n_intf].name, token, sizeof(opts->net.interfaces[opts->net.n_intf].name) - 1);
opts->net.n_intf++;
if(opts->net.n_intf >= MAX_NETWORK_INF)
break;
token = strtok(NULL,d);
}
}
else
{
M_ERROR(MODULE_NAME, "Ignore unknown configuration %s = %s", name, value);
return 0;
}
return 1;
}
static int load_config(app_data_t* opts)
{
// global
(void)memset(opts->data_file_out, '\0', MAX_BUF);
(void)memset(opts->temp.cpu_temp_file, '\0', MAX_BUF);
(void)memset(opts->temp.gpu_temp_file, '\0', MAX_BUF);
opts->pwoff_cd = 5;
opts->sample_period.it_interval.tv_sec = 0;
opts->sample_period.it_interval.tv_nsec = 3e+8;
opts->sample_period.it_value.tv_sec = 0;
opts->sample_period.it_value.tv_nsec = 3e+8;
opts->cpus = NULL;
opts->n_cpus = 2;
//battery
(void)memset(opts->bat_stat.bat_in, '\0', MAX_BUF);
opts->bat_stat.max_voltage = 4200;
opts->bat_stat.min_voltage = 3300;
opts->bat_stat.cutoff_voltage = 3000;
opts->bat_stat.ratio = 1.0;
opts->bat_stat.read_voltage = 0.0;
opts->bat_stat.percent = 0.0;
opts->power_off_percent = 1;
(void)memset(&opts->mem, '\0', sizeof(opts->mem));
(void)memset(&opts->temp, '\0', sizeof(opts->temp));
(void)memset(&opts->net, '\0', sizeof(opts->net));
(void)memset(&opts->disk, '\0', sizeof(opts->disk));
opts->disk.mount_path[0] = '/';
M_LOG(MODULE_NAME, "Use configuration: %s", opts->conf_file);
if (ini_parse(opts->conf_file, ini_handle, opts) < 0)
{
M_ERROR(MODULE_NAME, "Can't load '%s'", opts->conf_file);
return -1;
}
// check battery configuration
if((opts->bat_stat.max_voltage < opts->bat_stat.min_voltage) ||
(opts->bat_stat.max_voltage < opts->bat_stat.cutoff_voltage) ||
(opts->bat_stat.min_voltage < opts->bat_stat.cutoff_voltage))
{
M_ERROR(MODULE_NAME, "Battery configuration is invalid: max: %d, min: %d, cut off: %d",
opts->bat_stat.max_voltage,
opts->bat_stat.min_voltage,
opts->bat_stat.cutoff_voltage);
return -1;
}
return 0;
}
int main(int argc, char *const *argv)
{
int ret, tfd, count_down;
float volt;
uint64_t expirations_count;
app_data_t opts;
LOG_INIT(MODULE_NAME);
signal(SIGPIPE, SIG_IGN);
signal(SIGABRT, SIG_IGN);
signal(SIGINT, int_handler);
(void)strncpy(opts.conf_file, DEFAULT_CONF_FILE, MAX_BUF - 1);
while ((ret = getopt(argc, argv, "hf:")) != -1)
{
switch (ret)
{
case 'f':
(void)strncpy(opts.conf_file, optarg, MAX_BUF-1);
break;
default:
help(argv[0]);
return -1;
}
}
if(optind > argc)
{
help(argv[0]);
return -1;
}
if(load_config(&opts) != 0)
{
fprintf(stderr,"Unable to read config file\n");
return -1;
}
M_LOG(MODULE_NAME, "Data Output: %s", opts.data_file_out);
M_LOG(MODULE_NAME, "Battery input: %s", opts.bat_stat.bat_in);
M_LOG(MODULE_NAME, "Battery Max voltage: %d", opts.bat_stat.max_voltage);
M_LOG(MODULE_NAME, "Battery Min voltage: %d", opts.bat_stat.min_voltage);
M_LOG(MODULE_NAME, "Battery Cut off voltage: %d", opts.bat_stat.cutoff_voltage);
M_LOG(MODULE_NAME, "Battery Divide ratio: %.3f", opts.bat_stat.ratio);
M_LOG(MODULE_NAME, "Sample period: %d", (int)(opts.sample_period.it_value.tv_nsec / 1e6));
M_LOG(MODULE_NAME, "CPU cores: %d", opts.n_cpus);
M_LOG(MODULE_NAME, "Power off count down: %d", opts.pwoff_cd);
M_LOG(MODULE_NAME,"CPU temp. input: %s",opts.temp.cpu_temp_file);
M_LOG(MODULE_NAME,"GPU temp. input: %s",opts.temp.gpu_temp_file);
M_LOG(MODULE_NAME, "Poweroff percent: %d", opts.power_off_percent);
// init timerfd
tfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
if (tfd == -1)
{
M_ERROR(MODULE_NAME, "Unable to create timerfd: %s", strerror(errno));
fprintf(stderr,"Unable to create timer fd: %s\n", strerror(errno));
return -1;
}
if (timerfd_settime(tfd, 0 /* no flags */, &opts.sample_period, NULL) == -1)
{
M_ERROR(MODULE_NAME, "Unable to set framerate period: %s", strerror(errno));
(void)close(tfd);
return -1;
}
//init CPU monitors
opts.cpus = (sys_cpu_t*) malloc(opts.n_cpus*sizeof(sys_cpu_t));
for(int i=0; i < opts.n_cpus; i++)
{
opts.cpus[i].last_sum = 0;
opts.cpus[i].last_idle = 0;
opts.cpus[i].percent = 0.0;
}
// loop
count_down = opts.pwoff_cd;
while(running)
{
if(opts.bat_stat.bat_in[0] != '\0')
{
// open the file
if(read_voltage(&opts) == -1)
{
M_ERROR(MODULE_NAME, "Unable to read system voltage");
}
volt = opts.bat_stat.read_voltage*opts.bat_stat.ratio;
if(volt < opts.bat_stat.cutoff_voltage)
{
M_LOG(MODULE_NAME, "Invalid voltage read: %.3f", volt);
}
else
{
if(opts.bat_stat.percent <= (float)opts.power_off_percent)
{
count_down--;
M_LOG(MODULE_NAME, "Out of battery. Will shutdown after %d count down", count_down);
}
else
{
// reset the count_down
count_down = opts.pwoff_cd;
}
// check if we should shutdown
if(count_down <= 0)
{
M_LOG(MODULE_NAME, "Shutting down system");
ret = system("poweroff");
(void) ret;
// this should never happend
return 0;
}
}
}
// read cpu info
if(read_cpu_info(&opts) == -1)
{
M_ERROR(MODULE_NAME, "Unable to read CPU infos");
}
// read memory usage
if(read_mem_info(&opts) == -1)
{
M_ERROR(MODULE_NAME, "Unable to read memory usage");
}
// read CPU temperature
if(read_cpu_temp(&opts) == -1)
{
M_ERROR(MODULE_NAME, "Unable to read CPU temperature");
}
if(read_net_statistic(&opts) == -1)
{
M_ERROR(MODULE_NAME, "Unable to query network statistic");
}
if(read_disk_usage(&opts) == -1)
{
M_ERROR(MODULE_NAME, "Unable to query disk usage");
}
// log to file
if(log_to_file(&opts) == -1)
{
M_ERROR(MODULE_NAME, "Unable to write sysinfo to output");
}
// check timeout
if(read(tfd, &expirations_count, sizeof(expirations_count)) != (int)sizeof(expirations_count))
{
M_ERROR(MODULE_NAME, "Unable to read timer: %s", strerror(errno));
}
else if (expirations_count > 1u)
{
M_ERROR(MODULE_NAME, "LOOP OVERFLOW COUNT: %lu", (long unsigned int)expirations_count);
}
}
if(opts.cpus)
free(opts.cpus);
if(tfd > 0)
{
(void)close(tfd);
}
return 0;
}

BIN
sysmond Executable file

Binary file not shown.

33
sysmond.conf Normal file
View File

@ -0,0 +1,33 @@
# Battery configuration
battery_max_voltage = 12600
battery_min_voltage = 10000
battery_cutoff_votalge = 9000
battery_divide_ratio = 3.36
battery_input = /sys/class/hwmon/hwmon2/device/in3_input
# daemon configuration
# time period between loop step in ms
sample_period = 500
#number of cpus to monitor
cpu_core_number = 4
# network interfaces to monitor
network_interfaces = wlan0
# e.g. wlan0,eth0
# disk mount point to monitor
disk_mount_point = /
# when battery is low
# the system will be shutdown after n count down
power_off_count_down = 10
# the system will bet shutdown if the battery voltage percent is below this value
power_off_percent = 3
cpu_temperature_input=/sys/devices/virtual/thermal/thermal_zone1/temp
gpu_temperature_input=/sys/devices/virtual/thermal/thermal_zone2/temp
# output system info to file
data_file_out = /var/jarvis:fbf070ddea3ea90d07f456540b405d302554ec82

11
sysmond.service Normal file
View File

@ -0,0 +1,11 @@
[Unit]
Description=System monitor
[Service]
Type=simple
User=root
ExecStart=/usr/bin/sysmond
Restart=always
[Install]
WantedBy=multi-user.target