13 changed files with 418 additions and 0 deletions
@ -1 +1,28 @@ |
|||||||
cmake_minimum_required(VERSION 3.5) |
cmake_minimum_required(VERSION 3.5) |
||||||
|
|
||||||
|
project(cpu-tracker-app LANGUAGES C VERSION 0.1.0) |
||||||
|
|
||||||
|
set(C_STANDARD 11) |
||||||
|
set(TARGET ${PROJECT_NAME}) |
||||||
|
set(OUTPUT_NAME cpu-tracker) |
||||||
|
|
||||||
|
set(CMAKE_INCLUDE_CURRENT_DIR ON) |
||||||
|
|
||||||
|
configure_file(src/main/Version.h.in Version.h) |
||||||
|
|
||||||
|
add_executable(${TARGET} |
||||||
|
src/main/Main.c |
||||||
|
src/main/CpuTracker.c |
||||||
|
src/core/ProcAnalyzer.c |
||||||
|
src/infrastructure/ProcReader.c |
||||||
|
src/infrastructure/ProcPrinter.c |
||||||
|
) |
||||||
|
|
||||||
|
target_include_directories(${TARGET} PRIVATE src) |
||||||
|
target_link_libraries(${TARGET} log thread) |
||||||
|
set_target_properties(${TARGET} |
||||||
|
PROPERTIES OUTPUT_NAME ${OUTPUT_NAME}) |
||||||
|
|
||||||
|
if (${BUILD_TESTS}) |
||||||
|
add_subdirectory(tests/integration) |
||||||
|
endif() |
||||||
@ -0,0 +1,19 @@ |
|||||||
|
#include "core/ProcAnalyzer.h" |
||||||
|
|
||||||
|
#include <math.h> |
||||||
|
|
||||||
|
void getCpuUsage(double results[], CpuStats stats[], CpuStats prevStats[], uint8_t numCores) |
||||||
|
{ |
||||||
|
// https://rosettacode.org/wiki/Linux_CPU_utilization
|
||||||
|
|
||||||
|
for (int i = 0; i < numCores; i++) { |
||||||
|
uint64_t prevTotal = prevStats[i].user + prevStats[i].nice + prevStats[i].system + prevStats[i].idle; |
||||||
|
uint64_t currentTotal = stats[i].user + stats[i].nice + stats[i].system + stats[i].idle; |
||||||
|
uint64_t totalDiff = currentTotal - prevTotal; |
||||||
|
uint64_t idleDiff = stats[i].idle - prevStats[i].idle; |
||||||
|
results[i] = (1.0 - (double)idleDiff / (double)totalDiff) * 100.0; |
||||||
|
if (results[i] < 0 || isnan(results[i])) { |
||||||
|
results[i] = 0; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,17 @@ |
|||||||
|
#ifndef PROCANALYZER_H |
||||||
|
#define PROCANALYZER_H |
||||||
|
|
||||||
|
#include <inttypes.h> |
||||||
|
|
||||||
|
typedef struct __attribute__((packed)) CpuStats { |
||||||
|
char cpuName[20]; |
||||||
|
uint64_t user; |
||||||
|
uint64_t nice; |
||||||
|
uint64_t system; |
||||||
|
uint64_t idle; |
||||||
|
} CpuStats; |
||||||
|
|
||||||
|
void getCpuUsage(double results[], CpuStats stats[], CpuStats prevStats[], uint8_t numCores); |
||||||
|
|
||||||
|
#endif // PROCANALYZER_H
|
||||||
|
|
||||||
@ -0,0 +1,10 @@ |
|||||||
|
#include "infrastructure/ProcPrinter.h" |
||||||
|
#include <stdio.h> |
||||||
|
|
||||||
|
void printCpuUsage(double results[], uint8_t size) |
||||||
|
{ |
||||||
|
for (uint8_t i = 0; i < size; i++) { |
||||||
|
printf("CPU %d: %f\n", i, results[i]); |
||||||
|
} |
||||||
|
printf("---\n"); |
||||||
|
} |
||||||
@ -0,0 +1,9 @@ |
|||||||
|
#ifndef PROCPRINTER_H |
||||||
|
#define PROCPRINTER_H |
||||||
|
|
||||||
|
#include "core/ProcAnalyzer.h" |
||||||
|
#include <inttypes.h> |
||||||
|
|
||||||
|
void printCpuUsage(double results[], uint8_t size); |
||||||
|
|
||||||
|
#endif // PROCPRINTER_H
|
||||||
@ -0,0 +1,50 @@ |
|||||||
|
#include "infrastructure/ProcReader.h" |
||||||
|
|
||||||
|
#include <stdio.h> |
||||||
|
#include <stdlib.h> |
||||||
|
#include <string.h> |
||||||
|
#include <inttypes.h> |
||||||
|
|
||||||
|
uint8_t countCpuCores(void) |
||||||
|
{ |
||||||
|
uint8_t count = 0; |
||||||
|
char line[256]; |
||||||
|
FILE* fp = fopen("/proc/cpuinfo", "r"); |
||||||
|
if (fp == NULL) { |
||||||
|
fprintf(stderr, "Unable to open /proc/cpuinfo\n"); |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
while (fgets(line, sizeof(line), fp)) { |
||||||
|
if (strncmp(line, "core id", 7) == 0) { |
||||||
|
++count; |
||||||
|
} |
||||||
|
} |
||||||
|
fclose(fp); |
||||||
|
return count;
|
||||||
|
} |
||||||
|
|
||||||
|
void readCpuStats(CpuStats* stats, uint8_t numCores) |
||||||
|
{ |
||||||
|
// https://www.linuxhowtos.org/System/procstat.htm
|
||||||
|
|
||||||
|
char line[256]; |
||||||
|
FILE* fp = fopen("/proc/stat", "r"); |
||||||
|
if (fp == NULL) { |
||||||
|
fprintf(stderr, "Unable to open /proc/stat\n"); |
||||||
|
exit(1); |
||||||
|
} |
||||||
|
|
||||||
|
fgets(line, sizeof(line), fp); // ignore the first line
|
||||||
|
for (int i = 0; i < numCores; i++) { |
||||||
|
fgets(line, sizeof(line), fp); |
||||||
|
sscanf(line, "%s %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64,
|
||||||
|
stats[i].cpuName, |
||||||
|
&stats[i].user, |
||||||
|
&stats[i].nice, |
||||||
|
&stats[i].system, |
||||||
|
&stats[i].idle); |
||||||
|
} |
||||||
|
|
||||||
|
fclose(fp); |
||||||
|
} |
||||||
@ -0,0 +1,10 @@ |
|||||||
|
#ifndef PROCREADER_H |
||||||
|
#define PROCREADER_H |
||||||
|
|
||||||
|
#include "core/ProcAnalyzer.h" |
||||||
|
|
||||||
|
uint8_t countCpuCores(void); |
||||||
|
|
||||||
|
void readCpuStats(CpuStats* stats, uint8_t numCores); |
||||||
|
|
||||||
|
#endif // PROCREADER_H
|
||||||
@ -0,0 +1,165 @@ |
|||||||
|
#include "CpuTracker.h" |
||||||
|
#include "core/ProcAnalyzer.h" |
||||||
|
#include "infrastructure/ProcReader.h" |
||||||
|
#include "infrastructure/ProcPrinter.h" |
||||||
|
#include "thread/Watchdog.h" |
||||||
|
#include "log/Log.h" |
||||||
|
|
||||||
|
#include <stdio.h> |
||||||
|
#include <stdlib.h> |
||||||
|
#include <string.h> |
||||||
|
#include <unistd.h> |
||||||
|
#include <stdarg.h> |
||||||
|
|
||||||
|
#define LOG_LINE_MAX 256 |
||||||
|
#define LOG_TAG_MAX 16 |
||||||
|
|
||||||
|
static const int WATCHDOG_INTERVAL_MS = 2000; |
||||||
|
static const int CPU_CHECK_INTERVAL_MS = 1000; |
||||||
|
static const char* LOG_FILE = "/tmp/cpu-monitor.log"; |
||||||
|
|
||||||
|
typedef struct AppContext |
||||||
|
{ |
||||||
|
CpuStats* stats;
|
||||||
|
CpuStats* prevStats; |
||||||
|
Watchdog* watchdog; |
||||||
|
QueuedThread* readProcThread; |
||||||
|
QueuedThread* analyzerThread; |
||||||
|
QueuedThread* printerThread; |
||||||
|
QueuedThread* loggerThread; |
||||||
|
double* results; |
||||||
|
uint8_t numCores; |
||||||
|
BlockerHandle mainBlocker; |
||||||
|
BlockerHandle readBlocker; |
||||||
|
uint8_t reserved[5]; |
||||||
|
} AppContext; |
||||||
|
|
||||||
|
typedef struct LogTuple |
||||||
|
{ |
||||||
|
char tag[LOG_TAG_MAX]; |
||||||
|
char message[LOG_LINE_MAX]; |
||||||
|
} LogTuple; |
||||||
|
|
||||||
|
static AppContext appCtx; |
||||||
|
|
||||||
|
|
||||||
|
_Noreturn static void onThreadUnresponsive(Thread* thread); |
||||||
|
static void threadedLog(const char* tag, const char* format, ...); |
||||||
|
|
||||||
|
static void readProcTask(void* arg); |
||||||
|
static void analyzerTask(void* arg); |
||||||
|
static void printerTask(void* arg); |
||||||
|
static void logTask(void* arg); |
||||||
|
|
||||||
|
|
||||||
|
void initCpuTracker(void) |
||||||
|
{ |
||||||
|
initLogger(LOG_FILE); |
||||||
|
appCtx.numCores = countCpuCores(); |
||||||
|
appCtx.prevStats = calloc(appCtx.numCores, sizeof(CpuStats)); |
||||||
|
appCtx.stats = calloc(appCtx.numCores, sizeof(CpuStats)); |
||||||
|
appCtx.results = calloc(appCtx.numCores, sizeof(double)); |
||||||
|
appCtx.mainBlocker = createBlocker(); |
||||||
|
appCtx.readBlocker = createBlocker(); |
||||||
|
appCtx.watchdog = createWatchdog(WATCHDOG_INTERVAL_MS, |
||||||
|
&onThreadUnresponsive, &threadedLog); |
||||||
|
appCtx.readProcThread = createWatchedThread(appCtx.watchdog); |
||||||
|
appCtx.analyzerThread = createWatchedThread(appCtx.watchdog); |
||||||
|
appCtx.printerThread = createWatchedThread(appCtx.watchdog); |
||||||
|
appCtx.loggerThread = createWatchedThread(appCtx.watchdog); |
||||||
|
} |
||||||
|
|
||||||
|
void runCpuTracker(void) |
||||||
|
{ |
||||||
|
// this task will post itself again until the app is closed
|
||||||
|
postQueuedTask(appCtx.readProcThread, &readProcTask, &appCtx); |
||||||
|
|
||||||
|
startWatchdog(appCtx.watchdog); |
||||||
|
lockBlocker(appCtx.mainBlocker); |
||||||
|
} |
||||||
|
|
||||||
|
void stopCpuTracker(void) |
||||||
|
{ |
||||||
|
notifyBlocker(appCtx.mainBlocker); |
||||||
|
} |
||||||
|
|
||||||
|
void destroyCpuTracker(void) |
||||||
|
{ |
||||||
|
stopWatchdog(appCtx.watchdog); |
||||||
|
notifyBlocker(appCtx.readBlocker); |
||||||
|
|
||||||
|
joinWatchedThreads(appCtx.watchdog); |
||||||
|
freeWatchedThreads(appCtx.watchdog); |
||||||
|
freeWatchdog(&(appCtx.watchdog)); |
||||||
|
freeBlocker(&(appCtx.mainBlocker)); |
||||||
|
freeBlocker(&(appCtx.readBlocker)); |
||||||
|
free(appCtx.results); |
||||||
|
free(appCtx.prevStats); |
||||||
|
free(appCtx.stats); |
||||||
|
closeLogger(); |
||||||
|
} |
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
|
_Noreturn static void onThreadUnresponsive(Thread* thread) |
||||||
|
{ |
||||||
|
printf("Thread %p is unresponsive, exiting\n", (void*)(thread)); |
||||||
|
usleep(1000 * 1000); |
||||||
|
_exit(1); |
||||||
|
} |
||||||
|
|
||||||
|
static void readProcTask(void* arg) |
||||||
|
{ |
||||||
|
(void)arg; |
||||||
|
readCpuStats(appCtx.stats, appCtx.numCores); |
||||||
|
|
||||||
|
if (appCtx.prevStats[0].user != 0) { |
||||||
|
getCpuUsage(appCtx.results, appCtx.stats, appCtx.prevStats, appCtx.numCores); |
||||||
|
// NOTE: a mutex could be used here, but since the analyzer thread is idle
|
||||||
|
// until notified and we assume t(analyzer) < t(read) (including interval
|
||||||
|
// in the latter) there will be no data race.
|
||||||
|
postQueuedTask(appCtx.analyzerThread, &analyzerTask, NULL); |
||||||
|
} |
||||||
|
|
||||||
|
memcpy(appCtx.prevStats, appCtx.stats, appCtx.numCores * sizeof(appCtx.stats)); |
||||||
|
lockBlockerTimed(appCtx.readBlocker, CPU_CHECK_INTERVAL_MS); |
||||||
|
postQueuedTask(appCtx.readProcThread, &readProcTask, NULL); |
||||||
|
} |
||||||
|
|
||||||
|
static void analyzerTask(void* arg) |
||||||
|
{ |
||||||
|
(void)arg; |
||||||
|
threadedLog("analyzer", "Processing results"); |
||||||
|
getCpuUsage(appCtx.results, appCtx.stats, appCtx.prevStats, appCtx.numCores); |
||||||
|
postQueuedTask(appCtx.printerThread, &printerTask, NULL); |
||||||
|
} |
||||||
|
|
||||||
|
static void printerTask(void* arg) |
||||||
|
{ |
||||||
|
(void)arg; |
||||||
|
threadedLog("printer", "Printing results"); |
||||||
|
printCpuUsage(appCtx.results, appCtx.numCores); |
||||||
|
} |
||||||
|
|
||||||
|
static void logTask(void* arg) |
||||||
|
{ |
||||||
|
LogTuple* tuple = (LogTuple*)arg; |
||||||
|
writeLog(tuple->tag, tuple->message); |
||||||
|
free(tuple); |
||||||
|
} |
||||||
|
|
||||||
|
__attribute__((__format__ (__printf__, 2, 0))) |
||||||
|
static void threadedLog(const char* tag, const char* format, ...) |
||||||
|
{ |
||||||
|
LogTuple* log = NULL; |
||||||
|
va_list args; |
||||||
|
if (!appCtx.watchdog->running) { // stop posting tasks if the application is exiting
|
||||||
|
return; |
||||||
|
} |
||||||
|
log = calloc(1, sizeof(LogTuple)); |
||||||
|
va_start(args, format); |
||||||
|
vsprintf((char*)log->message, format, args); |
||||||
|
va_end(args); |
||||||
|
strncpy(log->tag, tag, LOG_TAG_MAX); |
||||||
|
postQueuedTask(appCtx.loggerThread, &logTask, log); |
||||||
|
} |
||||||
@ -0,0 +1,12 @@ |
|||||||
|
#ifndef CPUTRACKER_H |
||||||
|
#define CPUTRACKER_H |
||||||
|
|
||||||
|
void initCpuTracker(void); |
||||||
|
|
||||||
|
void runCpuTracker(void); |
||||||
|
|
||||||
|
void stopCpuTracker(void); |
||||||
|
|
||||||
|
void destroyCpuTracker(void); |
||||||
|
|
||||||
|
#endif // CPUTRACKER_H
|
||||||
@ -0,0 +1,29 @@ |
|||||||
|
#include "Version.h" |
||||||
|
#include "CpuTracker.h" |
||||||
|
|
||||||
|
#include <signal.h> |
||||||
|
|
||||||
|
#define VERSION_BUFFER_SIZE (16) |
||||||
|
|
||||||
|
static void onSigInt(int sig) |
||||||
|
{ |
||||||
|
(void)sig; |
||||||
|
printf("\nSIGINT received, exiting\n"); |
||||||
|
stopCpuTracker(); |
||||||
|
} |
||||||
|
|
||||||
|
int main(void) |
||||||
|
{ |
||||||
|
{ |
||||||
|
char vBuf[VERSION_BUFFER_SIZE]; |
||||||
|
printf("CPU monitor %s\n", getVersionString(vBuf)); |
||||||
|
} |
||||||
|
|
||||||
|
signal(SIGINT, onSigInt); |
||||||
|
|
||||||
|
initCpuTracker(); |
||||||
|
runCpuTracker(); |
||||||
|
printf("Stopping...\n"); |
||||||
|
destroyCpuTracker(); |
||||||
|
printf("Done.\n"); |
||||||
|
} |
||||||
@ -0,0 +1,19 @@ |
|||||||
|
#ifndef VERSION_H_IN |
||||||
|
#define VERSION_H_IN |
||||||
|
|
||||||
|
#include <stdio.h> |
||||||
|
#include <inttypes.h> |
||||||
|
|
||||||
|
static const uint8_t VERSION_MAJOR = ${PROJECT_VERSION_MAJOR}; |
||||||
|
static const uint8_t VERSION_MINOR = ${PROJECT_VERSION_MINOR}; |
||||||
|
static const uint8_t VERSION_PATCH = ${PROJECT_VERSION_PATCH}; |
||||||
|
static const char* VERSION_SUFFIX = "${PROJECT_VERSION_SUFFIX}"; |
||||||
|
|
||||||
|
static char* getVersionString(char* buffer) { |
||||||
|
sprintf(buffer, "%d.%d.%d%s", |
||||||
|
VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, |
||||||
|
VERSION_SUFFIX); |
||||||
|
return buffer; |
||||||
|
} |
||||||
|
|
||||||
|
#endif // VERSION_H_IN
|
||||||
@ -0,0 +1,16 @@ |
|||||||
|
cmake_minimum_required(VERSION 3.5) |
||||||
|
|
||||||
|
set(C_STANDARD 11) |
||||||
|
enable_testing() |
||||||
|
|
||||||
|
set(SRC_DIR ../../src) |
||||||
|
|
||||||
|
include_directories(${SRC_DIR}) |
||||||
|
|
||||||
|
add_executable(ProcReaderTest |
||||||
|
${SRC_DIR}/core/ProcAnalyzer.c |
||||||
|
${SRC_DIR}/infrastructure/ProcReader.c |
||||||
|
${SRC_DIR}/infrastructure/ProcPrinter.c |
||||||
|
ProcReader.test.c |
||||||
|
) |
||||||
|
add_test(ProcReaderTest ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ProcReaderTest) |
||||||
@ -0,0 +1,35 @@ |
|||||||
|
#include "infrastructure/ProcReader.h" |
||||||
|
#include "infrastructure/ProcPrinter.h" |
||||||
|
|
||||||
|
#include <stdio.h> |
||||||
|
#include <stdlib.h> |
||||||
|
#include <unistd.h> |
||||||
|
#include <string.h> |
||||||
|
#include <inttypes.h> |
||||||
|
|
||||||
|
int main() { |
||||||
|
uint8_t numCores = countCpuCores(); |
||||||
|
|
||||||
|
CpuStats* prevStats = calloc(numCores, sizeof(CpuStats)); |
||||||
|
CpuStats* currentStats = calloc(numCores, sizeof(CpuStats)); |
||||||
|
|
||||||
|
double* results = calloc(numCores, sizeof(double)); |
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++) { |
||||||
|
readCpuStats(currentStats, numCores); |
||||||
|
|
||||||
|
if (prevStats[0].user != 0) { |
||||||
|
getCpuUsage(results, currentStats, prevStats, numCores); |
||||||
|
printCpuUsage(results, numCores); |
||||||
|
} |
||||||
|
|
||||||
|
memcpy(prevStats, currentStats, numCores * sizeof(currentStats)); |
||||||
|
|
||||||
|
usleep(500 * 1000); |
||||||
|
} |
||||||
|
|
||||||
|
free(results); |
||||||
|
free(prevStats); |
||||||
|
free(currentStats); |
||||||
|
return 0; |
||||||
|
} |
||||||
Loading…
Reference in new issue