Browse Source

Added cpu-tracker main application tests and implementation

master
chodak166 2 years ago
parent
commit
6bff8e440d
  1. 27
      tieto-cpu-tracker/app/CMakeLists.txt
  2. 19
      tieto-cpu-tracker/app/src/core/ProcAnalyzer.c
  3. 17
      tieto-cpu-tracker/app/src/core/ProcAnalyzer.h
  4. 10
      tieto-cpu-tracker/app/src/infrastructure/ProcPrinter.c
  5. 9
      tieto-cpu-tracker/app/src/infrastructure/ProcPrinter.h
  6. 50
      tieto-cpu-tracker/app/src/infrastructure/ProcReader.c
  7. 10
      tieto-cpu-tracker/app/src/infrastructure/ProcReader.h
  8. 165
      tieto-cpu-tracker/app/src/main/CpuTracker.c
  9. 12
      tieto-cpu-tracker/app/src/main/CpuTracker.h
  10. 29
      tieto-cpu-tracker/app/src/main/Main.c
  11. 19
      tieto-cpu-tracker/app/src/main/Version.h.in
  12. 16
      tieto-cpu-tracker/app/tests/integration/CMakeLists.txt
  13. 35
      tieto-cpu-tracker/app/tests/integration/ProcReader.test.c

27
tieto-cpu-tracker/app/CMakeLists.txt

@ -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()

19
tieto-cpu-tracker/app/src/core/ProcAnalyzer.c

@ -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;
}
}
}

17
tieto-cpu-tracker/app/src/core/ProcAnalyzer.h

@ -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

10
tieto-cpu-tracker/app/src/infrastructure/ProcPrinter.c

@ -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");
}

9
tieto-cpu-tracker/app/src/infrastructure/ProcPrinter.h

@ -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

50
tieto-cpu-tracker/app/src/infrastructure/ProcReader.c

@ -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);
}

10
tieto-cpu-tracker/app/src/infrastructure/ProcReader.h

@ -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

165
tieto-cpu-tracker/app/src/main/CpuTracker.c

@ -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);
}

12
tieto-cpu-tracker/app/src/main/CpuTracker.h

@ -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

29
tieto-cpu-tracker/app/src/main/Main.c

@ -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");
}

19
tieto-cpu-tracker/app/src/main/Version.h.in

@ -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

16
tieto-cpu-tracker/app/tests/integration/CMakeLists.txt

@ -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)

35
tieto-cpu-tracker/app/tests/integration/ProcReader.test.c

@ -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…
Cancel
Save