diff --git a/lib/BCMK b/lib/BCMK index a409f819..c518d489 100644 --- a/lib/BCMK +++ b/lib/BCMK @@ -12,7 +12,7 @@ CFLAGS += -fPIC -DETCDIR="\"$(etc_dir)\"" SOLIB = libbluecherry.so SOLIBVER = $(SOLIB).0 OBJS = bc-core.o bc-utils.o bc-db-core.o bc-db-mysql.o \ - bc-key.o bc-media.o lavf_device.o bc-ptz.o \ + bc-key.o bc-media.o lavf_device.o bc-ptz.o bc-stats.o \ input_device.o v4l2_device_solo6x10.o stream_elements.o \ logging.o bc-syslog.o sliding_seq_window.o \ v4l2_device_tw5864.o v4l2_device_solo6010-dkms.o diff --git a/lib/bc-stats.cpp b/lib/bc-stats.cpp new file mode 100644 index 00000000..2a501707 --- /dev/null +++ b/lib/bc-stats.cpp @@ -0,0 +1,460 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "bc-stats.h" +#include "logc.h" + +#define BC_DIR_NETWORK "/sys/class/net/" +#define BC_FILE_SELFSTATUS "/proc/self/status" +#define BC_FILE_SELFSTAT "/proc/self/stat" +#define BC_FILE_MEMINFO "/proc/meminfo" +#define BC_FILE_LOADAVG "/proc/loadavg" +#define BC_FILE_STAT "/proc/stat" +#define BC_FILE_MAX 8192 + +static std::string get_iface_ip_addr(const char *iface) +{ + int fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) std::string(""); + + struct ifreq ifr; + ifr.ifr_addr.sa_family = AF_INET; + + strncpy(ifr.ifr_name, iface, IFNAMSIZ-1); + ioctl(fd, SIOCGIFADDR, &ifr); + close(fd); + + char *ip_addr = inet_ntoa(((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr); + return std::string(ip_addr); +} + +uint32_t bc_stats::bc_float_to_u32(float value) +{ + uint16_t integral = (uint16_t)floor(value); + float balance = value - (float)integral; + uint16_t decimal = (uint16_t)(balance * 100); + + uint32_t ret_val; + ret_val = (uint32_t)integral; + ret_val <<= 16; + ret_val += (uint32_t)decimal; + return ret_val; +} + +float bc_stats::bc_u32_to_float(uint32_t value) +{ + uint16_t integral = (uint16_t)(value >> 16); + uint16_t decimal = (uint16_t)(value & 0xFF); + float balance = (float)decimal / (float)100; + return (float)((float)integral + balance); +} + +int bc_stats::load_file(const char *path, char *buffer, size_t size) +{ + /* Open target file for reading only */ + int fd = open(path, O_RDONLY, S_IRUSR | S_IRGRP | S_IROTH); + if (fd < 0) return 0; + + /* Read whole file buffer from descriptor */ + int bytes = read(fd, buffer, size); + if (bytes <= 0) + { + bc_log(Error, "Can not read file: %s (%s): %u", path, strerror(errno)); + close(fd); + return 0; + } + + /* Null terminate buffer */ + buffer[bytes] = '\0'; + close(fd); + + return bytes; +} + +uint64_t bc_stats::parse_info(char *buffer, size_t size, const char *field) +{ + const char *end = buffer + size; + char *offset = strstr(buffer, field); + if (offset == NULL) return 0; + offset += strlen(field) + 1; + if (offset >= end) return 0; + return atoll(offset); +} + +void bc_stats::get_mem_info(bc_stats::memory *mem) +{ + mem->resident = __sync_add_and_fetch(&_memory.resident, 0); + mem->virt = __sync_add_and_fetch(&_memory.virt, 0); + mem->avail = __sync_add_and_fetch(&_memory.avail, 0); + mem->total = __sync_add_and_fetch(&_memory.total, 0); + mem->free = __sync_add_and_fetch(&_memory.free, 0); + mem->swap = __sync_add_and_fetch(&_memory.swap, 0); + mem->buff = __sync_add_and_fetch(&_memory.buff, 0); +} + +bool bc_stats::update_mem_info() +{ + char buffer[BC_FILE_MAX]; /* Load /proc/meminfo file */ + int length = load_file(BC_FILE_MEMINFO, buffer, sizeof(buffer)); + if (length <= 0) return false; + + /* Parse memory statistics */ + __sync_lock_test_and_set(&_memory.total, parse_info(buffer, length, "MemTotal")); + __sync_lock_test_and_set(&_memory.free, parse_info(buffer, length, "MemFree")); + __sync_lock_test_and_set(&_memory.avail, parse_info(buffer, length, "MemAvailable")); + __sync_lock_test_and_set(&_memory.buff, parse_info(buffer, length, "Buffers")); + __sync_lock_test_and_set(&_memory.swap, parse_info(buffer, length, "SwapCached")); + + /* Load /proc/self/status file */ + length = load_file(BC_FILE_SELFSTATUS, buffer, sizeof(buffer)); + if (length <= 0) return false; + + /* Parse memory statistics for current process */ + __sync_lock_test_and_set(&_memory.resident, parse_info(buffer, length, "VmRSS")); + __sync_lock_test_and_set(&_memory.virt, parse_info(buffer, length, "VmSize")); + + return true; +} + +void bc_stats::get_proc_usage(bc_stats::cpu::process *proc) +{ + proc->user_childs = __sync_add_and_fetch(&_cpu.proc.user_childs, 0); + proc->kernel_childs = __sync_add_and_fetch(&_cpu.proc.kernel_childs, 0); + proc->user_space = __sync_add_and_fetch(&_cpu.proc.user_space, 0); + proc->kernel_space = __sync_add_and_fetch(&_cpu.proc.kernel_space, 0); + proc->total_time = __sync_add_and_fetch(&_cpu.proc.total_time, 0); + proc->user_space_usg = __sync_add_and_fetch(&_cpu.proc.user_space_usg, 0); + proc->kernel_space_usg = __sync_add_and_fetch(&_cpu.proc.kernel_space_usg, 0); +} + +void bc_stats::copy_cpu_info(bc_stats::cpu_info *dst, bc_stats::cpu_info *src) +{ + dst->soft_ints_raw = __sync_add_and_fetch(&src->soft_ints_raw, 0); + dst->hard_ints_raw = __sync_add_and_fetch(&src->hard_ints_raw, 0); + dst->kernel_space_raw = __sync_add_and_fetch(&src->kernel_space_raw, 0); + dst->user_niced_raw = __sync_add_and_fetch(&src->user_niced_raw, 0); + dst->user_space_raw = __sync_add_and_fetch(&src->user_space_raw, 0); + dst->idle_time_raw = __sync_add_and_fetch(&src->idle_time_raw, 0); + dst->io_wait_raw = __sync_add_and_fetch(&src->io_wait_raw, 0); + dst->total_raw = __sync_add_and_fetch(&src->total_raw, 0); + dst->soft_ints = __sync_add_and_fetch(&src->soft_ints, 0); + dst->hard_ints = __sync_add_and_fetch(&src->hard_ints, 0); + dst->kernel_space = __sync_add_and_fetch(&src->kernel_space, 0); + dst->user_niced = __sync_add_and_fetch(&src->user_niced, 0); + dst->user_space = __sync_add_and_fetch(&src->user_space, 0); + dst->idle_time = __sync_add_and_fetch(&src->idle_time, 0); + dst->io_wait = __sync_add_and_fetch(&src->io_wait, 0); + dst->cpu_id = __sync_add_and_fetch(&src->cpu_id, 0); +} + +bool bc_stats::update_cpu_info() +{ + char buffer[BC_FILE_MAX]; /* Load /proc/stat file */ + if (load_file(BC_FILE_STAT, buffer, sizeof(buffer)) <= 0) return false; + + /* Get last CPU usage by process */ + bc_stats::cpu::process last_usage; + get_proc_usage(&last_usage); + + int core_count = __sync_add_and_fetch(&_cpu.core_count, 0); + char *save_ptr = NULL; + int cpu_id = -1; + + char *ptr = strtok_r(buffer, "\n", &save_ptr); + while (ptr != NULL && !strncmp(ptr, "cpu", 3)) + { + bc_stats::cpu_info info; + memset(&info, 0, sizeof(bc_stats::cpu_info)); + + sscanf(ptr, "%*s %u %u %u %u %u %u %u", &info.user_space_raw, + &info.user_niced_raw, &info.kernel_space_raw, &info.idle_time_raw, + &info.io_wait_raw, &info.hard_ints_raw, &info.soft_ints_raw); + + info.total_raw = info.kernel_space_raw + info.user_space_raw + info.user_niced_raw; + info.total_raw += info.hard_ints_raw + info.soft_ints_raw; + info.total_raw += info.idle_time_raw + info.io_wait_raw; + info.cpu_id = cpu_id++; + + if (!core_count && info.cpu_id >= 0) + { + _cpu.cores.push_back(info); + } + else + { + bc_stats::cpu_info last_info, *curr_info; + + if (info.cpu_id < 0) curr_info = &_cpu.sum; + else curr_info = &_cpu.cores[info.cpu_id]; + + copy_cpu_info(&last_info, curr_info); + uint32_t total_diff = info.total_raw - last_info.total_raw; + + float fHardInterrupts = ((info.hard_ints_raw - last_info.hard_ints_raw) / (float)total_diff) * 100; + float fSoftInterrupts = ((info.soft_ints_raw - last_info.soft_ints_raw) / (float)total_diff) * 100; + float fKernelSpace = ((info.kernel_space_raw - last_info.kernel_space_raw) / (float)total_diff) * 100; + float fUserSpace = ((info.user_space_raw - last_info.user_space_raw) / (float)total_diff) * 100; + float fUserNiced = ((info.user_niced_raw - last_info.user_niced_raw) / (float)total_diff) * 100; + float fIdleTime = ((info.idle_time_raw - last_info.idle_time_raw) / (float)total_diff) * 100; + float fIOWait = ((info.io_wait_raw - last_info.io_wait_raw) / (float)total_diff) * 100; + + __sync_lock_test_and_set(&curr_info->hard_ints, bc_float_to_u32(fHardInterrupts)); + __sync_lock_test_and_set(&curr_info->soft_ints, bc_float_to_u32(fSoftInterrupts)); + __sync_lock_test_and_set(&curr_info->kernel_space, bc_float_to_u32(fKernelSpace)); + __sync_lock_test_and_set(&curr_info->user_space, bc_float_to_u32(fUserSpace)); + __sync_lock_test_and_set(&curr_info->user_niced, bc_float_to_u32(fUserNiced)); + __sync_lock_test_and_set(&curr_info->idle_time, bc_float_to_u32(fIdleTime)); + __sync_lock_test_and_set(&curr_info->io_wait, bc_float_to_u32(fIOWait)); + + /* Raw information about CPU usage for later percentage calculations */ + __sync_lock_test_and_set(&curr_info->hard_ints_raw, info.hard_ints_raw); + __sync_lock_test_and_set(&curr_info->soft_ints_raw, info.soft_ints_raw); + __sync_lock_test_and_set(&curr_info->kernel_space_raw, info.kernel_space_raw); + __sync_lock_test_and_set(&curr_info->user_space_raw, info.user_space_raw); + __sync_lock_test_and_set(&curr_info->user_niced_raw, info.user_niced_raw); + __sync_lock_test_and_set(&curr_info->idle_time_raw, info.idle_time_raw); + __sync_lock_test_and_set(&curr_info->io_wait_raw, info.io_wait_raw); + __sync_lock_test_and_set(&curr_info->total_raw, info.total_raw); + __sync_lock_test_and_set(&curr_info->cpu_id, info.cpu_id); + } + + ptr = strtok_r(NULL, "\n", &save_ptr); + } + + __sync_lock_test_and_set(&_cpu.core_count, _cpu.cores.size()); + if (load_file(BC_FILE_SELFSTAT, buffer, sizeof(buffer)) <= 0) return false; + + bc_stats::cpu::process curr_usage; + sscanf(buffer, "%*u %*s %*c %*u %*u %*u %*u %*u %*u %*u %*u %*u %*u %lu %lu %ld %ld", + &curr_usage.user_space, &curr_usage.kernel_space, + &curr_usage.user_childs, &curr_usage.kernel_childs); + + curr_usage.total_time = __sync_add_and_fetch(&_cpu.sum.total_raw, 0); + uint64_t total_diff = curr_usage.total_time - last_usage.total_time; + + float nUserCPU = 100 * (((curr_usage.user_space + curr_usage.user_childs) - + (last_usage.user_space + last_usage.user_childs)) / (float)total_diff); + + float nSystemCPU = 100 * (((curr_usage.kernel_space + curr_usage.kernel_childs) - + (last_usage.kernel_space + last_usage.kernel_childs)) / (float)total_diff); + + __sync_lock_test_and_set(&_cpu.proc.user_childs, curr_usage.user_childs); + __sync_lock_test_and_set(&_cpu.proc.kernel_childs, curr_usage.user_childs); + __sync_lock_test_and_set(&_cpu.proc.user_space, curr_usage.user_space); + __sync_lock_test_and_set(&_cpu.proc.kernel_space, curr_usage.kernel_space); + __sync_lock_test_and_set(&_cpu.proc.total_time, curr_usage.total_time); + __sync_lock_test_and_set(&_cpu.proc.user_space_usg, bc_float_to_u32(nUserCPU)); + __sync_lock_test_and_set(&_cpu.proc.kernel_space_usg, bc_float_to_u32(nSystemCPU)); + + float one_min_int, five_min_int, ten_min_int; + if (load_file(BC_FILE_LOADAVG, buffer, sizeof(buffer)) <= 0) return false; + sscanf(buffer, "%f %f %f", &one_min_int, &five_min_int, &ten_min_int); + + __sync_lock_test_and_set(&_cpu.load_avg[0], bc_float_to_u32(one_min_int)); + __sync_lock_test_and_set(&_cpu.load_avg[1], bc_float_to_u32(five_min_int)); + __sync_lock_test_and_set(&_cpu.load_avg[2], bc_float_to_u32(ten_min_int)); + + return true; +} + +bool bc_stats::get_cpu_info(bc_stats::cpu *cpu) +{ + cpu->load_avg[0] = cpu->load_avg[1] = cpu->load_avg[2] = 0; + int i, core_count = __sync_add_and_fetch(&_cpu.core_count, 0); + if (core_count <= 0) return false; + + get_proc_usage(&cpu->proc); + copy_cpu_info(&cpu->sum, &_cpu.sum); + + for (i = 0; i < core_count; i++) + { + bc_stats::cpu_info dst_info; + copy_cpu_info(&dst_info, &_cpu.cores[i]); + cpu->cores.push_back(dst_info); + } + + cpu->core_count = cpu->cores.size(); + cpu->load_avg[0] = __sync_add_and_fetch(&_cpu.load_avg[0], 0); + cpu->load_avg[1] = __sync_add_and_fetch(&_cpu.load_avg[1], 0); + cpu->load_avg[2] = __sync_add_and_fetch(&_cpu.load_avg[2], 0); + return cpu->core_count ? true : false; +} + +void bc_stats::get_net_info(bc_stats::network *net) +{ + pthread_mutex_lock(&_mutex); + *net = _network; + pthread_mutex_unlock(&_mutex); +} + +bool bc_stats::update_net_info() +{ + pthread_mutex_lock(&_mutex); + + DIR *net_dir = opendir(BC_DIR_NETWORK); + if (net_dir == NULL) + { + pthread_mutex_unlock(&_mutex); + return false; + } + + struct dirent *dir_entry = readdir(net_dir); + while(dir_entry != NULL) + { + /* Found an entry, but ignore . and .. */ + if (!strcmp(".", dir_entry->d_name) || + !strcmp("..", dir_entry->d_name)) + { + dir_entry = readdir(net_dir); + continue; + } + + char buffer[BC_FILE_MAX]; + char iface_path[PATH_MAX]; + + net_iface iface; + iface.name = std::string(dir_entry->d_name); + + snprintf(iface_path, sizeof(iface_path), "%s%s/address", BC_DIR_NETWORK, iface.name.c_str()); + if (load_file(iface_path, buffer, sizeof(buffer)) > 0) + { + char *saveptr = NULL; + strtok_r(buffer, "\n", &saveptr); + if (buffer[0] == '\n') buffer[0] = 0; + iface.hwaddr = std::string(buffer); + } + + snprintf(iface_path, sizeof(iface_path), "%s%s/type", BC_DIR_NETWORK, iface.name.c_str()); + if (load_file(iface_path, buffer, sizeof(buffer)) > 0) iface.type = atol(buffer); + + snprintf(iface_path, sizeof(iface_path), "%s%s/statistics/rx_bytes", BC_DIR_NETWORK, iface.name.c_str()); + if (load_file(iface_path, buffer, sizeof(buffer)) > 0) iface.bytes_recv = atol(buffer); + + snprintf(iface_path, sizeof(iface_path), "%s%s/statistics/tx_bytes", BC_DIR_NETWORK, iface.name.c_str()); + if (load_file(iface_path, buffer, sizeof(buffer)) > 0) iface.bytes_sent = atol(buffer); + + snprintf(iface_path, sizeof(iface_path), "%s%s/statistics/rx_packets", BC_DIR_NETWORK, iface.name.c_str()); + if (load_file(iface_path, buffer, sizeof(buffer)) > 0) iface.pkts_recv = atol(buffer); + + snprintf(iface_path, sizeof(iface_path), "%s%s/statistics/tx_packets", BC_DIR_NETWORK, iface.name.c_str()); + if (load_file(iface_path, buffer, sizeof(buffer)) > 0) iface.pkts_sent = atol(buffer); + + network_it it = _network.find(iface.name.c_str()); + if (it != _network.end()) + { + if (iface.bytes_recv > it->second.bytes_recv && it->second.bytes_recv > 0) + iface.bytes_recv_per_sec = (iface.bytes_recv - it->second.bytes_recv) / _monitoring_interval; + + if (iface.pkts_recv > it->second.pkts_recv && it->second.pkts_recv > 0) + iface.pkts_recv_per_sec = (iface.pkts_recv - it->second.pkts_recv) / _monitoring_interval; + + if (iface.bytes_sent > it->second.bytes_sent && it->second.bytes_sent > 0) + iface.bytes_sent_per_sec = (iface.bytes_sent - it->second.bytes_sent) / _monitoring_interval; + + if (iface.pkts_sent > it->second.pkts_sent && it->second.pkts_sent > 0) + iface.pkts_sent_per_sec = (iface.pkts_sent - it->second.pkts_sent) / _monitoring_interval; + + iface.ipaddr = get_iface_ip_addr(iface.name.c_str()); + it->second = iface; + } + else + { + /* Insert new value into unordered map */ + iface.ipaddr = get_iface_ip_addr(iface.name.c_str()); + _network.insert(std::pair(iface.name.c_str(), iface)); + } + + dir_entry = readdir(net_dir); + } + + closedir(net_dir); + pthread_mutex_unlock(&_mutex); + + return true; +} + +void bc_stats::display() +{ + bc_stats::memory mem; + get_mem_info(&mem); + + bc_log(Debug, "memory: avail(%lu), total(%lu), free(%lu), swap(%lu), buff(%lu)", + mem.avail, mem.total, mem.free, mem.swap, mem.buff); + + bc_stats::cpu cpu; + get_cpu_info(&cpu); + + bc_log(Debug, "process: mem-res(%lu), mem-virt(%lu), cpu-us(%.2f), cpu-ks(%.2f)", + mem.resident, mem.virt, + bc_u32_to_float(cpu.proc.user_space_usg), + bc_u32_to_float(cpu.proc.kernel_space_usg)); + + bc_log(Debug, "loadavg: 5m(%.2f), 10m(%.2f), 15m(%.2f),\n", bc_u32_to_float(cpu.load_avg[0]), + bc_u32_to_float(cpu.load_avg[1]), bc_u32_to_float(cpu.load_avg[2])); + + bc_log(Debug, "core(s): us(%.2f), un(%.2f), ks(%.2f), idl(%.2f), si(%.2f), hi(%.2f), io(%.2f)", + bc_u32_to_float(cpu.sum.user_space), bc_u32_to_float(cpu.sum.user_niced), + bc_u32_to_float(cpu.sum.kernel_space), bc_u32_to_float(cpu.sum.idle_time), + bc_u32_to_float(cpu.sum.hard_ints), bc_u32_to_float(cpu.sum.soft_ints), + bc_u32_to_float(cpu.sum.io_wait)); + + for (uint i = 0; i < cpu.cores.size(); i++) + { + bc_stats::cpu_info *core = &cpu.cores[i]; + bc_log(Debug, "core(%d): us(%.2f), un(%.2f), ks(%.2f), idl(%.2f), si(%.2f), hi(%.2f), io(%.2f)", + core->cpu_id, bc_u32_to_float(core->user_space), bc_u32_to_float(core->user_niced), + bc_u32_to_float(core->kernel_space), bc_u32_to_float(core->idle_time), + bc_u32_to_float(core->hard_ints), bc_u32_to_float(core->soft_ints), + bc_u32_to_float(core->io_wait)); + } +} + +void bc_stats::monithoring_thread() +{ + while (!__sync_add_and_fetch(&_cancel, 0)) + { + update_mem_info(); + update_cpu_info(); + update_net_info(); + + //display(); + sleep(1); + } + + __sync_lock_test_and_set(&_active, 0); +} + +void bc_stats::start_monithoring() +{ + pthread_mutex_init(&_mutex, NULL); + __sync_lock_test_and_set(&_active, 1); + + std::thread thread_id(&bc_stats::monithoring_thread, this); + thread_id.detach(); +} + +void bc_stats::stop_monithoring() +{ + /* Notify thread about finish processing */ + __sync_lock_test_and_set(&_cancel, 1); + + while (__sync_add_and_fetch(&_active, 0)) + usleep(10000); // Wait for thread termination + + /* Destroy mutex */ + pthread_mutex_destroy(&_mutex); +} diff --git a/lib/bc-stats.h b/lib/bc-stats.h new file mode 100644 index 00000000..73a41eba --- /dev/null +++ b/lib/bc-stats.h @@ -0,0 +1,120 @@ +#ifndef __BC_STATS__ +#define __BC_STATS__ + +#include +#include +#include + +#include +#include + +#define _BC_ALIGNED_ __attribute__((aligned)) + +class bc_stats +{ +public: + struct memory { + uint64_t _BC_ALIGNED_ resident = 0; + uint64_t _BC_ALIGNED_ virt = 0; + uint64_t _BC_ALIGNED_ avail = 0; + uint64_t _BC_ALIGNED_ total = 0; + uint64_t _BC_ALIGNED_ free = 0; + uint64_t _BC_ALIGNED_ swap = 0; + uint64_t _BC_ALIGNED_ buff = 0; + }; + + struct cpu_info { + int cpu_id = 0; // -1 for sum + + /* Calculated percents */ + uint32_t _BC_ALIGNED_ soft_ints = 0; + uint32_t _BC_ALIGNED_ hard_ints = 0; + uint32_t _BC_ALIGNED_ user_niced = 0; + uint32_t _BC_ALIGNED_ kernel_space = 0; + uint32_t _BC_ALIGNED_ user_space = 0; + uint32_t _BC_ALIGNED_ idle_time = 0; + uint32_t _BC_ALIGNED_ io_wait = 0; + + /* Raw information */ + uint32_t _BC_ALIGNED_ soft_ints_raw = 0; + uint32_t _BC_ALIGNED_ hard_ints_raw = 0; + uint32_t _BC_ALIGNED_ user_niced_raw = 0; + uint32_t _BC_ALIGNED_ kernel_space_raw = 0; + uint32_t _BC_ALIGNED_ user_space_raw = 0; + uint32_t _BC_ALIGNED_ idle_time_raw = 0; + uint32_t _BC_ALIGNED_ io_wait_raw = 0; + uint64_t _BC_ALIGNED_ total_raw = 0; + }; + + struct net_iface { + std::string hwaddr = std::string(""); + std::string ipaddr = std::string(""); + std::string name = std::string(""); + + uint64_t bytes_recv_per_sec = 0; + uint64_t bytes_sent_per_sec = 0; + uint64_t pkts_recv_per_sec = 0; + uint64_t pkts_sent_per_sec = 0; + + int64_t bytes_recv = 0; + int64_t bytes_sent = 0; + int64_t pkts_recv = 0; + int64_t pkts_sent = 0; + int32_t type = 0; + }; + + static void copy_cpu_info(bc_stats::cpu_info *dst, bc_stats::cpu_info *src); + static uint64_t parse_info(char *buffer, size_t size, const char *field); + static int load_file(const char *path, char *buffer, size_t size); + + static uint32_t bc_float_to_u32(float value); + static float bc_u32_to_float(uint32_t value); + + typedef std::unordered_map::iterator network_it; + typedef std::unordered_map network; + typedef std::vector cpu_infos; + + struct cpu { + struct process { + uint32_t _BC_ALIGNED_ kernel_space_usg = 0; + uint32_t _BC_ALIGNED_ user_space_usg = 0; + uint64_t _BC_ALIGNED_ kernel_space = 0; + uint64_t _BC_ALIGNED_ user_space = 0; + uint64_t _BC_ALIGNED_ total_time = 0; + int64_t _BC_ALIGNED_ kernel_childs = 0; + int64_t _BC_ALIGNED_ user_childs = 0; + } proc; + + uint16_t _BC_ALIGNED_ core_count = 0; + uint32_t _BC_ALIGNED_ load_avg[3]; + + cpu_infos cores; + cpu_info sum; + }; + + void start_monithoring(); + void stop_monithoring(); + void display(); + + void get_proc_usage(bc_stats::cpu::process *proc); + void get_net_info(bc_stats::network *net); + void get_mem_info(bc_stats::memory *mem); + bool get_cpu_info(bc_stats::cpu *cpu); + +private: + void monithoring_thread(); + bool update_net_info(); + bool update_mem_info(); + bool update_cpu_info(); + + uint8_t _BC_ALIGNED_ _active = 0; + uint8_t _BC_ALIGNED_ _cancel = 0; + uint8_t _monitoring_interval = 1; + pthread_mutex_t _mutex; + + bc_stats::network _network; + bc_stats::memory _memory; + bc_stats::cpu _cpu; +}; + +#endif /* __BC_STATS__ */ \ No newline at end of file diff --git a/nginx-configs/bluecherry.conf b/nginx-configs/bluecherry.conf index caa3c60a..1a6c4138 100644 --- a/nginx-configs/bluecherry.conf +++ b/nginx-configs/bluecherry.conf @@ -24,4 +24,9 @@ server { location /hls { proxy_pass http://127.0.0.1:7003; } + + # API proxy + location /api { + proxy_pass http://127.0.0.1:7005; + } } diff --git a/server/BCMK b/server/BCMK index 96b139e4..0eb32c2c 100644 --- a/server/BCMK +++ b/server/BCMK @@ -21,7 +21,7 @@ SERVER_OBJS = bc-server.o bc-thread.o media_writer.o g723-dec.o \ decoder.o encoder.o reencoder.o vaapi.o scaler.o \ recorder.o motion_handler.o ffmpeg-init.o bt.o status_server.o \ trigger_server.o substream-thread.o onvif_events.o \ - v3license_server.o v3license_processor.o hls.o + v3license_server.o v3license_processor.o hls.o bc-api.o all: $(TARGETS) FORCE diff --git a/server/bc-api.cpp b/server/bc-api.cpp new file mode 100644 index 00000000..28ce33ea --- /dev/null +++ b/server/bc-api.cpp @@ -0,0 +1,764 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bc-api.h" +#include "bc-stats.h" +#include "libbluecherry.h" + +extern "C" { +#include +#include +#include +} + +static int api_sock_shutdown(int fd) +{ + if (fd >= 0) + { + shutdown(fd, SHUT_RDWR); + close(fd); + } + + return -1; +} + +static void std_string_append(std::string &source, const char *data, ...) +{ + char buffer[4048]; + size_t length = 0; + va_list args; + + va_start(args, data); + length += vsnprintf(buffer, sizeof(buffer), data, args); + va_end(args); + + source.append(buffer, length); +} + +////////////////////////////////////////////////// +// BC EVENT HANDLER +////////////////////////////////////////////////// + +bc_events::~bc_events() +{ + if (event_fd >= 0) + { + close(event_fd); + event_fd = -1; + } + + if (event_array) + { + free(event_array); + event_array = NULL; + } + + if (event_callback) + event_callback(this, NULL, BC_EVENT_DESTROY); +} + +bool bc_events::create(size_t max, void *userptr, event_callback_t callback) +{ + events_max = max > 0 ? max : BC_EVENTS_MAX; + event_callback = callback; + user_data = userptr; + + /* Allocate memory for event array */ + event_array = (struct epoll_event*)calloc(events_max, sizeof(struct epoll_event)); + if (event_array == NULL) + { + bc_log(Error, "Can not allocate memory for event array: %s", strerror(errno)); + return false; + } + + /* Create event epoll instance */ + event_fd = epoll_create1(0); + if (event_fd < 0) + { + bc_log(Error, "Can not crerate epoll: %s", strerror(errno)); + free(event_array); + event_array = NULL; + return false; + } + + return true; +} + +void bc_events::service_callback(bc_events::event_data *data) +{ + /* Check error condition */ + if (data->events & EPOLLRDHUP) { event_callback(this, data, BC_EVENT_CLOSED); return; } + if (data->events & EPOLLHUP) { event_callback(this, data, BC_EVENT_HUNGED); return; } + if (data->events & EPOLLERR) { event_callback(this, data, BC_EVENT_ERROR); return; } + if (data->events & EPOLLPRI) { event_callback(this, data, BC_EVENT_EXCEPTION); return; } + + /* Callback on writeable */ + if (data->events & EPOLLOUT && event_callback(this, data, BC_EVENT_WRITE) < 0) + { event_callback(this, data, BC_EVENT_USER); return; } // User requested callback + + /* Callback on readable */ + if (data->events & EPOLLIN && event_callback(this, data, BC_EVENT_READ) < 0) + { event_callback(this, data, BC_EVENT_USER); return; } // User requested callback +} + +void bc_events::clear_callback(bc_events::event_data *data) +{ + if (data != NULL) + { + event_callback(this, data, BC_EVENT_CLEAR); + free(data); + } +} + +bc_events::event_data* bc_events::register_event(void *ctx, int fd, int events, bc_events::type type) +{ + /* Allocate memory for event data */ + bc_events::event_data* data = (bc_events::event_data*)malloc(sizeof(bc_events::event_data)); + if (data == NULL) + { + bc_log(Error, "Can not allocate memory for event: %s", strerror(errno)); + return NULL; + } + + /* Initialize event */ + data->events = 0; + data->type = type; + data->ptr = ctx; + data->fd = fd; + + /* Add event to the instance */ + if (!this->add(data, events)) + { + free(data); + return NULL; + } + + return data; +} + +bool bc_events::add(bc_events::event_data* data, int events) +{ + struct epoll_event event; + event.data.ptr = data; + event.events = events; + + if (epoll_ctl(event_fd, EPOLL_CTL_ADD, data->fd, &event) < 0) + { + bc_log(Error, "epoll_ctl() add failed: %s", strerror(errno)); + return false; + } + + return true; +} + +bool bc_events::modify(bc_events::event_data *data, int events) +{ + struct epoll_event event; + event.data.ptr = data; + event.events = events; + + if (epoll_ctl(event_fd, EPOLL_CTL_MOD, data->fd, &event) < 0) + { + bc_log(Error, "epoll_ctl() mod failed: %s", strerror(errno)); + return false; + } + + return true; +} + +bool bc_events::remove(bc_events::event_data *data) +{ + bool status = true; + if (data->fd >= 0 && epoll_ctl(event_fd, EPOLL_CTL_DEL, data->fd, NULL) < 0) status = false; + clear_callback(data); + return status; +} + +bool bc_events::service(int timeout_ms) +{ + int count; /* Wait for ready events */ + do count = epoll_wait(event_fd, event_array, events_max, timeout_ms); + while (errno == EINTR); + + for (int i = 0; i < count; i++) + { + /* Call callback for each ready event */ + bc_events::event_data *data = (bc_events::event_data*)event_array[i].data.ptr; + data->events = event_array[i].events; + service_callback(data); + } + + return (count < 0) ? false : true; +} + +////////////////////////////////////////////////// +// BC API SESSION +////////////////////////////////////////////////// + +std::string api_session::get_request() +{ + std::string request; + request.clear(); // for dumb compilers + + size_t posit = _rx_buffer.find("\r\n\r\n"); + if (posit != std::string::npos) + { + request = _rx_buffer.substr(0, posit + 4); + rx_buffer_advance(request.length()); + } + + return request; +} + +api_session::~api_session() +{ + _fd = api_sock_shutdown(_fd); +} + +int api_session::writeable() +{ + int events = EPOLLRDHUP | EPOLLOUT; + return _events->modify(_ev_data, events) ? 1 : -1; +} + +void api_session::set_addr(const struct in_addr addr) +{ + char address[64]; + + /* Get IP string from network byte order */ + int length = snprintf(address, + sizeof(address), "%d.%d.%d.%d", + (int)((addr.s_addr & 0x000000FF)), + (int)((addr.s_addr & 0x0000FF00)>>8), + (int)((addr.s_addr & 0x00FF0000)>>16), + (int)((addr.s_addr & 0xFF000000)>>24)); + + _address = std::string(address, length); +} + +void api_session::tx_buffer_append(const char *data, size_t size) +{ + _tx_buffer.append(data, size); +} + +ssize_t api_session::tx_buffer_flush() +{ + if (!_tx_buffer.size()) return 0; + + ssize_t sent = send(_fd, _tx_buffer.c_str(), _tx_buffer.size(), MSG_NOSIGNAL); + if (sent < 0) + { + bc_log(Error, "Can not send data to HLS client: %s (%s)", get_addr(), strerror(errno)); + return sent; + } + + _tx_buffer.erase(0, sent); + return _tx_buffer.size(); +} + +bool api_session::authenticate(const std::string &request) +{ + size_t posit = request.find("Authorization"); + if (posit == std::string::npos) + { + /* Try lowercase (lazy case sensitivity) */ + posit = request.find("authorization"); + if (posit == std::string::npos) return false; + } + + posit = request.find(":", posit + 13) + 1; + if (posit == std::string::npos) return false; + + size_t end_posit = request.find("\r\n", posit); + if (end_posit == std::string::npos) return false; + + std::string auth = request.substr(posit, end_posit - posit); + while (auth.length() && auth.at(0) == ' ') auth.erase(0, 1); + if (!auth.length()) return false; + + if (auth.size() > 6 && auth.substr(0, 6) == "Basic ") + { + auth = auth.substr(6); + char *buf = new char[auth.size()]; + int ret = av_base64_decode((uint8_t*)buf, auth.c_str(), auth.size()); + + if (ret > 0) + { + buf[ret] = 0; + char *password = buf; + char *username = strsep(&password, ":"); + + if (username && password && + bc_user_auth(username, password, ACCESS_REMOTE, -1) == 1) + { + delete[] buf; + return true; + } + } + + delete[] buf; + } + + return false; +} + +bool api_session::handle_request(const std::string &request) +{ + /* Authenticate request */ + if (!authenticate(request)) + { + bc_log(Debug, "Authorization failure for API request"); + + _tx_buffer = std::string("HTTP/1.1 401 Unauthorized\r\n"); + std_string_append(_tx_buffer, "Access-Control-Allow-Origin: %s\r\n", "*"); + std_string_append(_tx_buffer, "WWW-Authenticate: Basic realm=\"HLS Server\"\r\n"); + std_string_append(_tx_buffer, "User-Agent: bluechery/%s\r\n", __VERSION__); + std_string_append(_tx_buffer, "Content-Length: 0\r\n\r\n"); + return true; + } + + /* Get start position of URL */ + size_t start_posit = request.find("/api"); + if (start_posit == std::string::npos) + { + start_posit = request.find("/"); + if (start_posit == std::string::npos) return false; + } + else start_posit += 4; // skip /api + + /* Get end position of URL */ + size_t end_posit = request.find(" ", start_posit); + if (end_posit == std::string::npos) return false; + + /* Calculate URL length */ + size_t url_length = end_posit - start_posit; + std::string url = request.substr(start_posit, url_length); + + if (!url.compare(0, 10, "/stats/cpu")) + { + std::string body; + _api->get_cpu_stats(body); + _api->create_http_response(_tx_buffer, body); + return true; + } + else if (!url.compare(0, 13, "/stats/memory")) + { + std::string body; + _api->get_memory_stats(body); + _api->create_http_response(_tx_buffer, body); + return true; + } + else if (!url.compare(0, 14, "/stats/network")) + { + std::string body; + _api->get_network_stats(body); + _api->create_http_response(_tx_buffer, body); + return true; + } + else if (!url.compare(0, 14, "/stats/overall")) + { + std::string body; + _api->get_overall_stats(body); + _api->create_http_response(_tx_buffer, body); + return true; + } + + bc_log(Warning, "Invalid API request: %s", url.c_str()); + _tx_buffer = std::string("HTTP/1.1 501 Not Implemented\r\n"); + std_string_append(_tx_buffer, "User-Agent: bluechery/%s\r\n", __VERSION__); + std_string_append(_tx_buffer, "Content-Length: 0\r\n\r\n"); + return true; +} + +////////////////////////////////////////////////// +// BC API CALLBACKS +////////////////////////////////////////////////// + +void api_clear_event(bc_events::event_data *ev_data) +{ + if (ev_data->ptr != NULL) + { + if (ev_data->type == bc_events::type::session) + { + api_session *session = (api_session*)ev_data->ptr; + if (session != NULL) + { + delete session; + /* + This socket will be closed in the session destructor and we must + set -1 on it just because we don't need to close it twice below. + */ + ev_data->fd = -1; + } + } + + ev_data->ptr = NULL; + } + + /* Shutdown and close file descriptor */ + ev_data->fd = api_sock_shutdown(ev_data->fd); +} + +int api_read_event(bc_events *events, bc_events::event_data *ev_data) +{ + if (ev_data->type == bc_events::type::listener) + { + bc_api *listener = (bc_api*)ev_data->ptr; + socklen_t len = sizeof(struct sockaddr); + struct sockaddr_in addr; + + /* Accept to the new connection request */ + int client_fd = accept(ev_data->fd, (struct sockaddr*)&addr, &len); + if (client_fd < 0) + { + bc_log(Error, "Can not accept to the socket: %s", strerror(errno)); + return 0; + } + + /* Get active flags on the socket */ + int fl = fcntl(client_fd, F_GETFL); + if (fl < 0) + { + bc_log(Error, "Failed fcntl(): %s", strerror(errno)); + client_fd = api_sock_shutdown(client_fd); + return 0; + } + + /* Set non-block flag */ + fl = fcntl(client_fd, F_SETFL, fl | O_NONBLOCK); + if (fl < 0) + { + bc_log(Error, "Failed to non-block socket: %s", strerror(errno)); + client_fd = api_sock_shutdown(client_fd); + return 0; + } + + /* Initialize new session */ + api_session *session = new api_session; + session->set_listener(listener); + session->set_addr(addr.sin_addr); + session->set_fd(client_fd); + + /* Register client into the event instance */ + bc_events::event_data *sess_data = events->register_event(session, client_fd, EPOLLIN, bc_events::type::session); + if (sess_data == NULL) + { + delete session; + return 0; + } + + /* Give session access to the event engine */ + session->set_api_event_data(sess_data); + session->set_event_handler(events); + + bc_log(Debug, "Connected API client: %s (%d)", session->get_addr(), client_fd); + return 1; + } + else if (ev_data->type == bc_events::type::session) + { + api_session *session = (api_session*)ev_data->ptr; + int client_fd = ev_data->fd; + char rx_buffer[BC_REQUEST_MAX]; + + /* Read incomming message from the client */ + int bytes = read(client_fd, rx_buffer, sizeof(rx_buffer)); + if (bytes <= 0) + { + if (!bytes) bc_log(Debug, "Disconnected API client: %s[%d]", session->get_addr(), client_fd); + else bc_log(Error, "Can not read data from client: %s (%s)", session->get_addr(), strerror(errno)); + return -1; + } + + /* Null terminate receive buffer */ + rx_buffer[bytes] = '\0'; + session->rx_buffer_append(rx_buffer); + + /* Get completed request */ + std::string request = session->get_request(); + if (!request.length()) return 0; + + /* Parse and handle the request */ + if (!session->handle_request(request)) bc_log(Warning, "Rejecting API request: (%s) %s", session->get_addr(), request.c_str()); + else bc_log(Debug, "Received API request from: client(%s)[%d]: %s", session->get_addr(), client_fd, request.c_str()); + + /* Callback on writeable */ + return session->writeable(); + } + + return -1; +} + +void bc_api::create_http_response(std::string &outout, const std::string &body) +{ + outout = std::string("HTTP/1.1 200 OK\r\n"); + std_string_append(outout, "Access-Control-Allow-Origin: %s\r\n", "*"); + std_string_append(outout, "User-Agent: bluechery/%s\r\n", __VERSION__); + std_string_append(outout, "Content-Type: %s\r\n", "application/json"); + std_string_append(outout, "Cache-Control: %s\r\n", "no-cache"); + std_string_append(outout, "Content-Length: %zu\r\n\r\n", body.length()); + outout.append(body); +} + +void bc_api::get_cpu_stats(std::string &outout) +{ + bc_stats::cpu cpu; + _stats->get_cpu_info(&cpu); + + char response[BC_REQUEST_MAX]; + int length = snprintf(response, sizeof(response), + "{\"load_average\":[" + "{\"interval\":\"1m\",\"value\":%.2f}," + "{\"interval\":\"5m\",\"value\":%.2f}," + "{\"interval\":\"10m\",\"value\":%.2f}" + "]," + "\"usage\":{" + "\"bluecherry\":{" + "\"user_space\":%.2f," + "\"kernel_space\":%.2f" + "}," + "\"idle\":%.2f," + "\"user_space\":%.2f," + "\"kernel_space\":%.2f," + "\"user_niced\":%.2f," + "\"soft_ints\":%.2f," + "\"hard_ints\":%.2f," + "\"io_wait\":%.2f" + "}}", + bc_stats::bc_u32_to_float(cpu.load_avg[0]), + bc_stats::bc_u32_to_float(cpu.load_avg[1]), + bc_stats::bc_u32_to_float(cpu.load_avg[2]), + bc_stats::bc_u32_to_float(cpu.proc.user_space_usg), + bc_stats::bc_u32_to_float(cpu.proc.kernel_space_usg), + bc_stats::bc_u32_to_float(cpu.sum.idle_time), + bc_stats::bc_u32_to_float(cpu.sum.user_space), + bc_stats::bc_u32_to_float(cpu.sum.kernel_space), + bc_stats::bc_u32_to_float(cpu.sum.user_niced), + bc_stats::bc_u32_to_float(cpu.sum.soft_ints), + bc_stats::bc_u32_to_float(cpu.sum.hard_ints), + bc_stats::bc_u32_to_float(cpu.sum.io_wait)); + + outout.append(response, length); +} + +void bc_api::get_memory_stats(std::string &outout) +{ + bc_stats::memory meminfo; + _stats->get_mem_info(&meminfo); + + char response[BC_REQUEST_MAX]; + int length = snprintf(response, sizeof(response), + "{" + "\"bluecherry\":{" + "\"resident\":%lu," + "\"virtual\":%lu" + "}," + "\"system\":{" + "\"available\":%lu," + "\"buffers\":%lu," + "\"total\":%lu," + "\"swap\":%lu," + "\"free\":%lu" + "}" + "}", + meminfo.resident, + meminfo.virt, + meminfo.avail, + meminfo.buff, + meminfo.total, + meminfo.swap, + meminfo.free + ); + + outout.append(response, length); +} + +void bc_api::get_network_stats(std::string &outout) +{ + bc_stats::network netstat; + _stats->get_net_info(&netstat); + outout = std::string("["); + + for (bc_stats::network_it it = netstat.begin(); it != netstat.end(); it++) + { + bc_stats::net_iface iface = it->second; + char buffer[BC_REQUEST_MAX]; + + int length = snprintf(buffer, sizeof(buffer), + "{" + "\"name\":\"%s\"," + "\"type\":%d," + "\"hw_addr\":\"%s\"," + "\"ip_addr\":\"%s\"," + "\"bytes_received\":%ld," + "\"bytes_sent\":%ld," + "\"packets_received\":%ld," + "\"packets_sent\":%ld," + "\"bytes_received_per_sec\":%lu," + "\"bytes_sent_per_sec\":%lu," + "\"packets_received_per_sec\":%lu," + "\"packets_sent_per_sec\":%lu" + "}", + iface.name.c_str(), + iface.type, + iface.hwaddr.c_str(), + iface.ipaddr.c_str(), + iface.bytes_recv, + iface.bytes_sent, + iface.pkts_recv, + iface.pkts_sent, + iface.bytes_recv_per_sec, + iface.bytes_sent_per_sec, + iface.pkts_recv_per_sec, + iface.pkts_sent_per_sec); + + outout.append(buffer, length); + if (std::next(it) != netstat.end()) outout.append(","); + } + + outout.append("]"); +} + +void bc_api::get_overall_stats(std::string &outout) +{ + outout = std::string("{\"cpu\":"); + + std::string stats; + get_cpu_stats(stats); + outout.append(stats); + stats.clear(); + + outout.append(",\"memory\":"); + get_memory_stats(stats); + outout.append(stats); + stats.clear(); + + outout.append(",\"network\":"); + get_network_stats(stats); + outout.append(stats); + outout.append("}"); +} + +int api_write_event(bc_events *events, bc_events::event_data *ev_data) +{ + api_session *session = (api_session*)ev_data->ptr; + ssize_t data_left = session->tx_buffer_flush(); + return (data_left <= 0) ? -1 : 1; +} + +int bc_api_event_callback(void *events, void* data, int reason) +{ + bc_events::event_data *ev_data = (bc_events::event_data*)data; + bc_events *pevents = (bc_events*)events; + int server_fd = (*(int*)pevents->get_user_data()); + + switch (reason) + { + case BC_EVENT_READ: + return api_read_event(pevents, ev_data); + case BC_EVENT_WRITE: + return api_write_event(pevents, ev_data); + case BC_EVENT_CLEAR: + api_clear_event(ev_data); + break; + case BC_EVENT_HUNGED: + bc_log(Warning, "API Connection hunged: fd(%d)", ev_data->fd); + pevents->remove(ev_data); + break; + case BC_EVENT_CLOSED: + bc_log(Debug, "API Connection closed: fd(%d)", ev_data->fd); + pevents->remove(ev_data); + break; + case BC_EVENT_USER: + bc_log(Debug, "Finishing API session: fd(%d)", ev_data->fd); + pevents->remove(ev_data); + break; + case BC_EVENT_DESTROY: + bc_log(Info, "API Service destroyed"); + close(server_fd); + break; + default: + break; + } + + return 0; +} + +////////////////////////////////////////////////// +// BC API LISTENER +////////////////////////////////////////////////// + +bc_api::~bc_api() +{ + _fd = api_sock_shutdown(_fd); +} + +bool bc_api::create_socket(uint16_t port) +{ + _port = port; + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(_port); + addr.sin_addr.s_addr = htonl(INADDR_ANY);; + + _fd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_TCP); + if (_fd < 0) + { + bc_log(Error, "Failed to create API listener socket: (%s)", strerror(errno)); + return false; + } + + const int opt = 1; + if (setsockopt(_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) + { + bc_log(Error, "Failed to set SO_REUSEADDR on the API socket: (%s)", strerror(errno)); + _fd = api_sock_shutdown(_fd); + return false; + } + + /* Bind the socket on port */ + if (bind(_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) + { + bc_log(Error, "Failed to bind socket on port: %u (%s)", _port, strerror(errno)); + _fd = api_sock_shutdown(_fd); + return false; + } + + /* Listen to the socket */ + if (listen(_fd, BC_EVENTS_MAX) < 0) + { + bc_log(Error, "Failed to listen port: %u (%s)", _port, strerror(errno)); + _fd = api_sock_shutdown(_fd); + return false; + } + + bc_log(Info, "API server started listen to port: %d", _port); + return true; +} + +bool bc_api::start_listener(uint16_t port) +{ + /* Create socket and start listen */ + if (!this->create_socket(port)) return false; + + /* Create event instance and add listener socket to the instance */ + if (!_events.create(0, &_fd, bc_api_event_callback) || + !_events.register_event(this, _fd, EPOLLIN, bc_events::type::listener)) return false; + + return true; +} + +void bc_api::run() +{ + bool status = true; + while (status) status = _events.service(100); + + /* Thats all */ + close(_fd); + _fd = -1; +} \ No newline at end of file diff --git a/server/bc-api.h b/server/bc-api.h new file mode 100644 index 00000000..4aaf3078 --- /dev/null +++ b/server/bc-api.h @@ -0,0 +1,134 @@ +#ifndef __BC_API__ +#define __BC_API__ + +#include +#include + +#include "bc-stats.h" + +#define BC_REQUEST_MAX 4096 +#define BC_EVENTS_MAX 120000 + +#define BC_EVENT_ERROR 1 +#define BC_EVENT_USER 2 +#define BC_EVENT_READ 3 +#define BC_EVENT_WRITE 4 +#define BC_EVENT_HUNGED 5 +#define BC_EVENT_CLOSED 6 +#define BC_EVENT_CLEAR 7 +#define BC_EVENT_DESTROY 8 +#define BC_EVENT_EXCEPTION 9 + +typedef int(*event_callback_t)(void *events, void* data, int reason); + +class bc_events +{ +public: + + typedef enum { + listener = (int)0, + session, + fstream + } type; + + typedef struct { + bc_events::type type; // Event type + void *ptr; // Data pointer + int events; // Ready events + int fd; // Socket descriptor + } event_data; + + ~bc_events(); + + bool create(size_t max, void *userptr, event_callback_t callBack); + bc_events::event_data* register_event(void *ctx, int fd, int events, bc_events::type type); + + bool add(bc_events::event_data* data, int events); + bool modify(bc_events::event_data *data, int events); + bool remove(bc_events::event_data *data); + bool service(int timeout_ms); + + void service_callback(bc_events::event_data *data); + void clear_callback(bc_events::event_data *data); + void *get_user_data() { return user_data; } + +private: + event_callback_t event_callback = NULL; /* Service callback */ + struct epoll_event* event_array = NULL; /* EPOLL event array */ + void* user_data = NULL; /* User data pointer */ + uint32_t events_max = 0; /* Max allowed file descriptors */ + int event_fd = -1; /* EPOLL File decriptor */ +}; + +class bc_api; + +class api_session +{ +public: + ~api_session(); + + int writeable(); + + std::string get_request(); + bool authenticate(const std::string &request); + bool handle_request(const std::string &request); + + std::string& tx_buffer_get() { return _tx_buffer; } + void tx_buffer_append(const char *data, size_t size); + size_t tx_buffer_advance(size_t size); + ssize_t tx_buffer_flush(); + + void rx_buffer_append(const char *data) { _rx_buffer.append(data); } + void rx_buffer_advance(size_t size) { _rx_buffer.erase(0, size); } + + void set_api_event_data(bc_events::event_data *data) { _ev_data = data; } + void set_event_handler(bc_events *events) { _events = events; } + bc_events* get_event_handler() { return _events; } + + void set_listener(bc_api *listener) { _api = listener; } + bc_api* get_listener() { return _api; } + + const char *get_addr() { return _address.c_str(); } + void set_addr(const struct in_addr addr); + void set_fd(int fd) { _fd = fd; } + int get_fd() { return _fd; } + +private: + /* Objects */ + bc_events::event_data* _ev_data = NULL; + bc_events* _events = NULL; + bc_api* _api = NULL; + + /* rx/tx */ + std::string _api_token; + std::string _tx_buffer; + std::string _rx_buffer; + std::string _address; + int _fd = -1; +}; + +class bc_api { +public: + ~bc_api(); + + void run(); + + bool create_socket(uint16_t port); + bool start_listener(uint16_t port); + void set_stats(bc_stats* stats) { _stats = stats; } + + void get_cpu_stats(std::string &outout); + void get_memory_stats(std::string &outout); + void get_network_stats(std::string &outout); + void get_overall_stats(std::string &outout); + + void create_http_response(std::string &outout, const std::string &body); + +private: + bc_stats* _stats; + bc_events _events; + uint16_t _port = 0; + int _fd = -1; +}; + +#endif /* __BC_API__ */ \ No newline at end of file diff --git a/server/bc-server.cpp b/server/bc-server.cpp index 0c8184d4..560e33c9 100644 --- a/server/bc-server.cpp +++ b/server/bc-server.cpp @@ -35,6 +35,8 @@ extern "C" { #include "bc-server.h" #include "rtsp.h" #include "bc-syslog.h" +#include "bc-stats.h" +#include "bc-api.h" #include "version.h" #include "status_server.h" #include "trigger_server.h" @@ -95,6 +97,7 @@ typedef std::vector bc_string_array; static rtsp_server *rtsp = NULL; static hls_listener *hls = NULL; +static bc_api *api = NULL; #ifdef V3_LICENSING static v3license_server *v3license = NULL; #endif /* V3_LICENSING */ @@ -1573,6 +1576,22 @@ int main(int argc, char **argv) /* Mutex */ bc_initialize_mutexes(); + /* Start monitoring */ + bc_stats stats; + stats.start_monithoring(); + + api = new bc_api; + api->set_stats(&stats); + + if (!api->start_listener(7005)) { + bc_log(Error, "Failed to setup API listener"); + delete api; + return 1; + } else { + std::thread api_th(&bc_api::run, api); + api_th.detach(); + } + rtsp = new rtsp_server; if (rtsp->setup(7002)) { delete rtsp;