Compare commits

...

5 Commits

  1. 116
      samsung/t2/main.c
  2. BIN
      samsung/t2/samsung_t2.jpg
  3. 23
      tieto-cpu-tracker/CMakeLists.txt
  4. 28
      tieto-cpu-tracker/app/CMakeLists.txt
  5. 19
      tieto-cpu-tracker/app/src/core/ProcAnalyzer.c
  6. 17
      tieto-cpu-tracker/app/src/core/ProcAnalyzer.h
  7. 10
      tieto-cpu-tracker/app/src/infrastructure/ProcPrinter.c
  8. 9
      tieto-cpu-tracker/app/src/infrastructure/ProcPrinter.h
  9. 50
      tieto-cpu-tracker/app/src/infrastructure/ProcReader.c
  10. 10
      tieto-cpu-tracker/app/src/infrastructure/ProcReader.h
  11. 165
      tieto-cpu-tracker/app/src/main/CpuTracker.c
  12. 12
      tieto-cpu-tracker/app/src/main/CpuTracker.h
  13. 29
      tieto-cpu-tracker/app/src/main/Main.c
  14. 19
      tieto-cpu-tracker/app/src/main/Version.h.in
  15. 16
      tieto-cpu-tracker/app/tests/integration/CMakeLists.txt
  16. 35
      tieto-cpu-tracker/app/tests/integration/ProcReader.test.c
  17. 24
      tieto-cpu-tracker/lib/log/CMakeLists.txt
  18. 12
      tieto-cpu-tracker/lib/log/include/log/Log.h
  19. 62
      tieto-cpu-tracker/lib/log/src/Log.c
  20. 12
      tieto-cpu-tracker/lib/log/tests/integration/CMakeLists.txt
  21. 54
      tieto-cpu-tracker/lib/log/tests/integration/Log.test.c
  22. 29
      tieto-cpu-tracker/lib/thread/CMakeLists.txt
  23. 22
      tieto-cpu-tracker/lib/thread/include/thread/Blocker.h
  24. 26
      tieto-cpu-tracker/lib/thread/include/thread/QueuedThread.h
  25. 42
      tieto-cpu-tracker/lib/thread/include/thread/TaskQueue.h
  26. 21
      tieto-cpu-tracker/lib/thread/include/thread/Thread.h
  27. 39
      tieto-cpu-tracker/lib/thread/include/thread/Watchdog.h
  28. 95
      tieto-cpu-tracker/lib/thread/src/core/TaskQueue.c
  29. 76
      tieto-cpu-tracker/lib/thread/src/infrastructure/PtBlocker.c
  30. 44
      tieto-cpu-tracker/lib/thread/src/infrastructure/QueuedThread.c
  31. 43
      tieto-cpu-tracker/lib/thread/src/infrastructure/Thread.c
  32. 158
      tieto-cpu-tracker/lib/thread/src/infrastructure/Watchdog.c
  33. 20
      tieto-cpu-tracker/lib/thread/tests/integration/CMakeLists.txt
  34. 36
      tieto-cpu-tracker/lib/thread/tests/integration/QueuedThread.test.c
  35. 47
      tieto-cpu-tracker/lib/thread/tests/integration/Thread.test.c
  36. 96
      tieto-cpu-tracker/lib/thread/tests/integration/Watchdog.test.c
  37. 17
      tieto-cpu-tracker/lib/thread/tests/unit/CMakeLists.txt
  38. 175
      tieto-cpu-tracker/lib/thread/tests/unit/TaskQueue.test.c

116
samsung/t2/main.c

@ -0,0 +1,116 @@
#include <stdio.h>
#include <math.h>
#include <string.h>
// #define BIG_SIZE (64) // that is 18 446 744 073 709 551 616 paths to check
#define BIG_SIZE (32) // that is 4 294 967 295 paths to check, takes about 30 seconds on i7 3.60GHz
// NOTE: This is probably not the best way to do this.
// A tricky case example is:
// X = 1, Y = 100,
// A = {1, 10, 1, 1, 1},
// B = {10, 1, 1, 1, 1000}
// NOTE: The idea is to start from the end (N-1) and assume that we
// have already found the best paths for previous nodes and pick the
// lower sum for current nodes. While checking previous nodes the
// function is called recursively so it again assumes that previous
// nodes are already solved. Since the first pair of nodes are just
// integers to be compared, we stop the recursion and the "unwinded"
// stack gives us the answer.
int solution_(int A[], int B[], int N, int X, int Y, char onA)
{
if (N == 0) {
return onA ? A[0] : B[0];
}
int c1, c2;
if (onA) {
c1 = 0 + A[N] + solution_(A, B, N-1, X, Y, 1);
c2 = Y + A[N] + solution_(A, B, N-1, X, Y, 0);
}
else {
c1 = 0 + B[N] + solution_(A, B, N-1, X, Y, 0);
c2 = X + B[N] + solution_(A, B, N-1, X, Y, 1);
}
return (c1 <= c2 ? c1 : c2);
}
int solution(int A[], int B[], int N, int X, int Y)
{
int sa = solution_(A, B, N-1, X, Y, 1);
int sb = solution_(A, B, N-1, X, Y, 0);
return (sa <= sb ? sa : sb);
}
void test(int actual, int expected)
{
if (actual != expected) {
printf("Test failed, expected %i, got %i\n", expected, actual);
}
else {
printf("Test passed (expected %i, got %i)\n", expected, actual);
}
}
int main()
{
{
int A[] = {1, 6};
int B[] = {3, 2};
int N = 2, X = 2, Y = 10;
test(solution(A, B, N, X, Y), 5);
}
{
int A[] = {1, 6, 2};
int B[] = {3, 2, 5};
int N = 3, X = 2, Y = 1;
test(solution(A, B, N, X, Y), 8);
}
{
int A[] = {2, 11, 4, 4};
int B[] = {9, 2, 5, 11};
int N = 4, X = 8, Y = 4;
test(solution(A, B, N, X, Y), 21);
}
{
int A[] = {1, 10, 1};
int B[] = {10, 1, 10};
int N = 3, X = 1, Y = 5;
test(solution(A, B, N, X, Y), 9);
}
{
int A[] = {8, 3, 3};
int B[] = {6, 1, 10};
int N = 3, X = 4, Y = 3;
test(solution(A, B, N, X, Y), 13);
}
{
int A[] = {1, 10, 1, 1};
int B[] = {10, 1, 1, 100};
int N = 4, X = 1, Y = 100;
test(solution(A, B, N, X, Y), 13);
}
{
int A[BIG_SIZE];
int B[BIG_SIZE];
int N = BIG_SIZE, X = 1, Y = 100;
memset(A, 0, sizeof(A));
memset(B, 0, sizeof(A));
A[0] = 1;
B[0] = 10;
B[BIG_SIZE-1] = 100;
test(solution(A, B, N, X, Y), 1);
}
printf("Done\n");
}

BIN
samsung/t2/samsung_t2.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

23
tieto-cpu-tracker/CMakeLists.txt

@ -0,0 +1,23 @@
cmake_minimum_required(VERSION 3.5)
project(cpu-tracker LANGUAGES C)
set(C_STANDARD 11)
enable_testing()
if(CMAKE_C_COMPILER_ID MATCHES "Clang")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Weverything")
elseif(CMAKE_C_COMPILER_ID STREQUAL "GNU")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra")
endif()
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
get_filename_component(EXTERN_DIR ${CMAKE_CURRENT_LIST_DIR}/extern ABSOLUTE)
option(BUILD_TESTS "Build tests" ON)
add_subdirectory(app)
add_subdirectory(lib/thread)
add_subdirectory(lib/log)

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

@ -0,0 +1,28 @@
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;
}

24
tieto-cpu-tracker/lib/log/CMakeLists.txt

@ -0,0 +1,24 @@
cmake_minimum_required(VERSION 3.5)
project(log LANGUAGES C VERSION 0.1.0)
set(C_STANDARD 11)
set(TARGET ${PROJECT_NAME})
set(CMAKE_INCLUDE_CURRENT_DIR ON)
add_library(${TARGET}
src/Log.c
)
target_include_directories(${TARGET}
PRIVATE
src
PUBLIC
include
)
if (${BUILD_TESTS})
add_subdirectory(tests/integration)
endif()

12
tieto-cpu-tracker/lib/log/include/log/Log.h

@ -0,0 +1,12 @@
#ifndef LOG_H
#define LOG_H
void initLogger(const char* logfile);
void clearLogs(void);
void writeLog(const char* tag, const char* format, ...);
void closeLogger(void);
#endif // LOG_H

62
tieto-cpu-tracker/lib/log/src/Log.c

@ -0,0 +1,62 @@
#include "log/Log.h"
#include <string.h>
#include <time.h>
#include <stdio.h>
#include <stdarg.h>
#define LOG_FILE_PATH_MAX 256
// #define LOG_LINE_MAX 256
static FILE* logFile = NULL;
static char logFilePath[LOG_FILE_PATH_MAX];
void initLogger(const char* logfile)
{
strcpy(logFilePath, logfile);
if (logFile != NULL) {
fclose(logFile);
}
logFile = fopen(logFilePath, "a");
if (logFile == NULL) {
fprintf(stderr, "Unable to open logfile\n");
}
}
void clearLogs(void)
{
if (logFile != NULL) {
closeLogger();
}
if (remove(logFilePath) != 0) {
fprintf(stderr, "Unable to remove logfile\n");
}
initLogger(logFilePath);
}
__attribute__((__format__ (__printf__, 2, 0)))
void writeLog(const char* tag, const char* format, ...)
{
time_t now = time(NULL);
struct tm* timeinfo = localtime(&now);
char timestamp[20];
va_list args;
strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", timeinfo);
va_start(args, format);
fprintf(logFile, "%s %s ", timestamp, tag);
vfprintf(logFile, format, args);
fprintf(logFile, "\n");
va_end(args);
fflush(logFile);
}
void closeLogger(void)
{
if (logFile != NULL) {
fclose(logFile);
logFile = NULL;
}
}

12
tieto-cpu-tracker/lib/log/tests/integration/CMakeLists.txt

@ -0,0 +1,12 @@
cmake_minimum_required(VERSION 3.5)
set(C_STANDARD 11)
enable_testing()
set(SRC_DIR ../../src)
include_directories(${SRC_DIR})
add_executable(LogTest Log.test.c)
target_link_libraries(LogTest log)
add_test(LogTest ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/LogTest)

54
tieto-cpu-tracker/lib/log/tests/integration/Log.test.c

@ -0,0 +1,54 @@
#include "log/Log.h"
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#define TAG "TEST"
static const char* LOG_FILE = "/tmp/log-test.log";
static int countLines(const char* filePath) {
int count = 0;
int ch;
FILE* file = fopen(filePath, "r");
if (file == NULL) {
return -1;
}
while ((ch = fgetc(file)) != EOF) {
if (ch == '\n') {
count++;
}
}
fclose(file);
return count;
}
int main(void)
{
int lineCount = 0;
initLogger(LOG_FILE);
clearLogs();
lineCount = countLines(LOG_FILE);
assert(lineCount == 0);
writeLog(TAG, "First log message");
writeLog(TAG, "Second log message");
writeLog(TAG, "Third log message");
lineCount = countLines(LOG_FILE);
assert(lineCount == 3);
writeLog(TAG, "This is %ith line that is %s", 4, "formatted");
lineCount = countLines(LOG_FILE);
assert(lineCount == 4);
closeLogger();
printf("Log file %s contains %d lines\n", LOG_FILE, lineCount);
return 0;
}

29
tieto-cpu-tracker/lib/thread/CMakeLists.txt

@ -0,0 +1,29 @@
cmake_minimum_required(VERSION 3.5)
project(thread LANGUAGES C VERSION 0.1.0)
set(C_STANDARD 11)
set(TARGET ${PROJECT_NAME})
set(CMAKE_INCLUDE_CURRENT_DIR ON)
enable_testing()
add_library(${TARGET}
src/core/TaskQueue.c
src/infrastructure/Thread.c
src/infrastructure/QueuedThread.c
src/infrastructure/Watchdog.c
src/infrastructure/PtBlocker.c
)
target_include_directories(${TARGET}
PRIVATE
src
PUBLIC
include
)
if (${BUILD_TESTS})
add_subdirectory(tests/unit)
add_subdirectory(tests/integration)
endif()

22
tieto-cpu-tracker/lib/thread/include/thread/Blocker.h

@ -0,0 +1,22 @@
#ifndef BLOCKER_H
#define BLOCKER_H
#include <inttypes.h>
typedef int8_t BlockerHandle;
typedef void(*LockBlockerFn)(BlockerHandle);
typedef void(*NotifyBlockerFn)(BlockerHandle);
enum PTB_TIMEOUT_STATUS {PTB_TIMEOUT, PTB_NO_TIMEOUT};
BlockerHandle createBlocker(void);
void lockBlocker(BlockerHandle id);
enum PTB_TIMEOUT_STATUS lockBlockerTimed(BlockerHandle id, uint32_t timeoutMs);
void notifyBlocker(BlockerHandle id);
void freeBlocker(BlockerHandle* id);
#endif // BLOCKER_H

26
tieto-cpu-tracker/lib/thread/include/thread/QueuedThread.h

@ -0,0 +1,26 @@
#ifndef QUEUEDTHREAD_H
#define QUEUEDTHREAD_H
#include "thread/Blocker.h"
#include "thread/TaskQueue.h"
#include "thread/Thread.h"
typedef struct QueuedThread
{
TaskQueue* taskQueue;
BlockerHandle blocker;
uint8_t reserved[sizeof(void*)-sizeof(BlockerHandle)];
Thread* thread;
} QueuedThread;
QueuedThread* createQueuedThread(void);
void postQueuedTask(QueuedThread* qthread, Task task, void* arg);
void joinQueuedThread(QueuedThread* qthread);
void freeQueuedThread(QueuedThread** qthread);
#endif // QUEUEDTHREAD_H

42
tieto-cpu-tracker/lib/thread/include/thread/TaskQueue.h

@ -0,0 +1,42 @@
#ifndef TASKQUEUE_H
#define TASKQUEUE_H
#include "Blocker.h"
#include <inttypes.h>
#include <stdbool.h>
typedef void(*Task)(void*);
typedef struct TaskEntry {
Task task;
void* data;
struct TaskEntry* next;
} TaskEntry;
typedef struct TaskQueue {
TaskEntry* front;
TaskEntry* back;
NotifyBlockerFn notify;
LockBlockerFn lock;
BlockerHandle blocker;
bool running;
uint8_t reserved[6];
} TaskQueue;
typedef struct TaskQueue* TaskQueuePtr;
TaskQueuePtr createTaskQueue(BlockerHandle blocker, LockBlockerFn lock, NotifyBlockerFn notify);
void enqueueTask(TaskQueuePtr queue, Task task, void* data);
TaskEntry* dequeueTask(TaskQueuePtr queue);
void runQueue(TaskQueuePtr queue);
void stopQueue(TaskQueuePtr queue);
void freeTask(TaskEntry** entry);
void freeTaskQueue(TaskQueuePtr* queue);
#endif // TASKQUEUE_H

21
tieto-cpu-tracker/lib/thread/include/thread/Thread.h

@ -0,0 +1,21 @@
#ifndef THREAD_H
#define THREAD_H
typedef void*(*ThreadFn)(void*);
typedef struct Thread
{
void* impl;
} Thread;
Thread* createThread(ThreadFn threadFn, void* arg);
void joinThread(Thread* thread);
void killThread(Thread* thread);
void freeThread(Thread** thread);
#endif // THREAD_H

39
tieto-cpu-tracker/lib/thread/include/thread/Watchdog.h

@ -0,0 +1,39 @@
#ifndef WATCHDOG_H
#define WATCHDOG_H
#include "thread/QueuedThread.h"
#include <inttypes.h>
typedef void(*WatchdogEventHandler)(Thread*);
typedef void(*LogFn)(const char* tag, const char* format, ...);
typedef struct Watchdog
{
Thread* watchingThread;
void* threads;
WatchdogEventHandler eventHandler;
LogFn log;
uint32_t intervalMs;
bool running;
BlockerHandle blocker;
uint8_t reserved[2];
} Watchdog;
Watchdog* createWatchdog(uint32_t intervalMs, WatchdogEventHandler eventHandler, LogFn logFn);
QueuedThread* createWatchedThread(Watchdog* watchdog);
void startWatchdog(Watchdog* watchdog);
void startWatchdogSync(Watchdog* watchdog);
void stopWatchdog(Watchdog* watchdog);
void joinWatchedThreads(Watchdog* watchdog);
void freeWatchedThreads(Watchdog* watchdog);
void freeWatchdog(Watchdog** watchdog);
#endif // WATCHDOG_H

95
tieto-cpu-tracker/lib/thread/src/core/TaskQueue.c

@ -0,0 +1,95 @@
#include "thread/TaskQueue.h"
#include <stdlib.h>
inline static void runUntilEmpty(TaskQueuePtr queue) {
TaskEntry* current = NULL;
for (current = dequeueTask(queue);
current != NULL;
current = dequeueTask(queue)) {
if (queue->running) { // any task could've changed it
current->task(current->data);
}
freeTask(&current);
}
}
TaskQueuePtr createTaskQueue(BlockerHandle blocker, LockBlockerFn lock, NotifyBlockerFn notify)
{
TaskQueuePtr queue = calloc(1, sizeof(TaskQueue));
queue->front = NULL;
queue->back = NULL;
queue->blocker = blocker;
queue->lock = lock;
queue->notify = notify;
queue->running = false;
return queue;
}
void enqueueTask(TaskQueuePtr queue, Task task, void* data)
{
TaskEntry* newEntry = (TaskEntry*)calloc(1, sizeof(TaskEntry));
newEntry->task = task;
newEntry->data = data;
newEntry->next = NULL;
if (queue->front == NULL) {
queue->front = newEntry;
queue->back = newEntry;
} else {
queue->back->next = newEntry;
queue->back = newEntry;
}
queue->notify(queue->blocker);
}
TaskEntry* dequeueTask(TaskQueuePtr queue)
{
TaskEntry* current = NULL;
if (queue->front == NULL) {
return current;
}
current = queue->front;
queue->front = current->next;
if (queue->front == NULL) {
queue->back = NULL;
}
return current;
}
void runQueue(TaskQueuePtr queue)
{
queue->running = true;
while (queue->running) {
runUntilEmpty(queue);
if (queue->running) {
queue->lock(queue->blocker);
}
}
// execute remaining tasks
runUntilEmpty(queue);
}
void stopQueue(TaskQueuePtr queue)
{
queue->running = false;
queue->notify(queue->blocker);
}
void freeTask(TaskEntry** entry)
{
free(*entry);
*entry = NULL;
}
void freeTaskQueue(TaskQueuePtr* queue)
{
TaskEntry* task = dequeueTask(*queue);
while (task != NULL) {
freeTask(&task);
task = dequeueTask(*queue);
}
free(*queue);
*queue = NULL;
}

76
tieto-cpu-tracker/lib/thread/src/infrastructure/PtBlocker.c

@ -0,0 +1,76 @@
#include "thread/Blocker.h"
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <time.h>
#ifndef BLOCKER_COUNT_MAX
#define BLOCKER_COUNT_MAX 8
#endif
typedef struct PtBlocker
{
pthread_mutex_t mutex;
pthread_cond_t cond;
} PtBlocker;
// TODO: dynamic list?
static PtBlocker* blockers[BLOCKER_COUNT_MAX] = {NULL};
BlockerHandle createBlocker(void)
{
BlockerHandle id = 0;
while (blockers[id] != NULL && id < BLOCKER_COUNT_MAX) {
++id;
}
if (id == BLOCKER_COUNT_MAX) {
return -1;
}
blockers[id] = calloc(1, sizeof(PtBlocker));
return id;
}
void lockBlocker(BlockerHandle id)
{
pthread_mutex_lock(&blockers[id]->mutex);
pthread_cond_wait(&blockers[id]->cond, &blockers[id]->mutex);
pthread_mutex_unlock(&blockers[id]->mutex);
}
enum PTB_TIMEOUT_STATUS lockBlockerTimed(BlockerHandle id, uint32_t timeoutMs)
{
enum PTB_TIMEOUT_STATUS status = PTB_TIMEOUT;
int waitResult = 0;
struct timespec timeout;
clock_gettime(CLOCK_REALTIME, &timeout);
timeout.tv_sec += timeoutMs / 1000;
timeout.tv_nsec += (timeoutMs % 1000) * 1000000;
pthread_mutex_lock(&blockers[id]->mutex);
waitResult = pthread_cond_timedwait(&blockers[id]->cond, &blockers[id]->mutex, &timeout);
if (waitResult == 0) {
status = PTB_NO_TIMEOUT;
}
pthread_mutex_unlock(&blockers[id]->mutex);
return status;
}
void notifyBlocker(BlockerHandle id)
{
pthread_mutex_lock(&blockers[id]->mutex);
pthread_cond_signal(&blockers[id]->cond);
pthread_mutex_unlock(&blockers[id]->mutex);
}
void freeBlocker(BlockerHandle* id)
{
if (*id == -1) {
return;
}
pthread_mutex_destroy(&blockers[*id]->mutex);
pthread_cond_destroy(&blockers[*id]->cond);
free(blockers[*id]);
blockers[*id] = NULL;
*id = -1;
}

44
tieto-cpu-tracker/lib/thread/src/infrastructure/QueuedThread.c

@ -0,0 +1,44 @@
#include "thread/QueuedThread.h"
#include "thread/Blocker.h"
#include <stdlib.h>
#include <stdio.h>
static void* queueThread(void* arg)
{
QueuedThread* qthread = (QueuedThread*)arg;
runQueue(qthread->taskQueue);
return NULL;
}
QueuedThread* createQueuedThread(void)
{
QueuedThread* qthread = calloc(1, sizeof(QueuedThread));
qthread->blocker = createBlocker();
qthread->taskQueue = createTaskQueue(qthread->blocker, &lockBlocker, &notifyBlocker);
qthread->thread = createThread(&queueThread, qthread);
return qthread;
}
void postQueuedTask(QueuedThread* qthread, Task task, void* arg)
{
enqueueTask(qthread->taskQueue, task, arg);
}
void joinQueuedThread(QueuedThread* qthread)
{
stopQueue(qthread->taskQueue);
joinThread(qthread->thread);
}
void freeQueuedThread(QueuedThread** qthread)
{
QueuedThread* t = *qthread;
freeTaskQueue(&(t->taskQueue));
freeBlocker(&(t->blocker));
freeThread(&(t->thread));
free(t);
*qthread = NULL;
}

43
tieto-cpu-tracker/lib/thread/src/infrastructure/Thread.c

@ -0,0 +1,43 @@
#include "thread/Thread.h"
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
typedef struct ThreadPthreadImpl
{
pthread_t thread;
} ThreadPthreadImpl;
Thread* createThread(ThreadFn threadFn, void* arg)
{
ThreadPthreadImpl* pthreadImpl = NULL;
Thread* thread = calloc(1, sizeof(Thread));
thread->impl = calloc(1, sizeof(ThreadPthreadImpl));
pthreadImpl = (ThreadPthreadImpl*)thread->impl;
if (pthread_create(&(pthreadImpl->thread), NULL, threadFn, arg) != 0) {
printf("Thread: Failed to create thread.\n");
}
return thread;
}
void joinThread(Thread* thread)
{
ThreadPthreadImpl* pthreadImpl = (ThreadPthreadImpl*)thread->impl;
pthread_join(pthreadImpl->thread, NULL);
}
void killThread(Thread* thread)
{
ThreadPthreadImpl* pthreadImpl = (ThreadPthreadImpl*)thread->impl;
pthread_kill(pthreadImpl->thread, SIGUSR1); // thread needs to handle or SIG_IGN this
}
void freeThread(Thread** thread)
{
free((ThreadPthreadImpl*)((*thread)->impl));
free(*thread);
*thread = NULL;
}

158
tieto-cpu-tracker/lib/thread/src/infrastructure/Watchdog.c

@ -0,0 +1,158 @@
#include "thread/Watchdog.h"
#include "thread/Blocker.h"
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <pthread.h>
#define TAG "Watchdog"
typedef struct ThreadNode
{
QueuedThread* thread;
struct ThreadNode* next;
uint64_t lastCheck;
} ThreadNode;
static uint64_t getCurrentTimestampMs() {
uint64_t nowMs;
struct timespec currentTime;
clock_gettime(CLOCK_REALTIME, &currentTime);
nowMs = (uint64_t)currentTime.tv_sec * 1000UL
+ (uint64_t)currentTime.tv_nsec / 1000000UL;
return nowMs;
}
static void check(void* arg)
{
ThreadNode* node = (ThreadNode*)arg;
node->lastCheck = getCurrentTimestampMs();
}
static void* watchThread(void* arg)
{
Watchdog* watchdog = (Watchdog*)arg;
uint64_t sendMs = 0;
uint64_t responseMs = 0;
ThreadNode* node = NULL;
while (watchdog->running) {
// post check tasks
sendMs = getCurrentTimestampMs();
node = watchdog->threads;
while (node != NULL) {
watchdog->log(TAG, "Posting check task for thread %p", node->thread->thread);
postQueuedTask(node->thread, &check, node);
node = node->next;
}
lockBlockerTimed(watchdog->blocker, watchdog->intervalMs);
node = watchdog->threads;
while (node != NULL) {
responseMs = node->lastCheck - sendMs;
watchdog->log(TAG, "Response time for thread %p = %dms", node->thread->thread, responseMs);
if (responseMs > watchdog->intervalMs) {
if (watchdog->eventHandler != NULL && watchdog->running) {
watchdog->log(TAG, "Thread %p is unresponsive, notifying handler", node->thread->thread);
watchdog->eventHandler(node->thread->thread);
}
}
node = node->next;
}
}
pthread_exit(NULL);
return NULL;
}
Watchdog* createWatchdog(uint32_t intervalMs, WatchdogEventHandler eventHandler, LogFn logFn)
{
Watchdog* watchdog = calloc(1, sizeof(Watchdog));
watchdog->intervalMs = intervalMs;
watchdog->threads = NULL;
watchdog->watchingThread = NULL;
watchdog->blocker = createBlocker();
watchdog->eventHandler = eventHandler;
watchdog->log = logFn;
return watchdog;
}
void startWatchdog(Watchdog* watchdog)
{
if (watchdog->running || watchdog->watchingThread != NULL) {
printf("Watchdog: Watching thread is already running\n");
return;
}
watchdog->running = true;
watchdog->watchingThread = createThread(watchThread, watchdog);
}
void startWatchdogSync(Watchdog* watchdog)
{
if (watchdog->running) {
printf("Watchdog: Watching thread is already running\n");
return;
}
watchdog->running = true;
watchThread(watchdog);
}
void stopWatchdog(Watchdog* watchdog)
{
watchdog->running = false;
notifyBlocker(watchdog->blocker);
if (watchdog->watchingThread == NULL) {
return;
}
joinThread(watchdog->watchingThread);
freeThread(&(watchdog->watchingThread));
watchdog->watchingThread = NULL;
}
QueuedThread* createWatchedThread(Watchdog* watchdog)
{
QueuedThread* qthread = createQueuedThread();
ThreadNode* node = calloc(1, sizeof(ThreadNode));
node->thread = qthread;
node->next = (watchdog->threads == NULL) ? NULL : watchdog->threads;
node->lastCheck = getCurrentTimestampMs();
watchdog->threads = node;
return qthread;
}
void joinWatchedThreads(Watchdog* watchdog)
{
ThreadNode* node = watchdog->threads;
while (node != NULL) {
joinQueuedThread(node->thread);
node = node->next;
}
}
void freeWatchedThreads(Watchdog* watchdog)
{
ThreadNode* node = watchdog->threads;
while (node != NULL) {
freeQueuedThread(&(node->thread));
node = node->next;
}
}
void freeWatchdog(Watchdog** watchdog)
{
Watchdog* w = *watchdog;
ThreadNode* node = w->threads;
ThreadNode* tmpNode = NULL;
while (node != NULL) {
tmpNode = node;
node = node->next;
free(tmpNode);
}
freeBlocker(&(w->blocker));
free(w);
*watchdog = NULL;
}

20
tieto-cpu-tracker/lib/thread/tests/integration/CMakeLists.txt

@ -0,0 +1,20 @@
cmake_minimum_required(VERSION 3.5)
set(C_STANDARD 11)
enable_testing()
set(SRC_DIR ../../src)
include_directories(${SRC_DIR})
add_executable(ThreadTest Thread.test.c)
target_link_libraries(ThreadTest thread)
add_test(ThreadTest ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ThreadTest)
add_executable(QueuedThreadTest QueuedThread.test.c)
target_link_libraries(QueuedThreadTest thread)
add_test(QueuedThreadTest ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/QueuedThreadTest)
add_executable(WatchdogTest Watchdog.test.c)
target_link_libraries(WatchdogTest thread)
add_test(WatchdogTest ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/WatchdogTest)

36
tieto-cpu-tracker/lib/thread/tests/integration/QueuedThread.test.c

@ -0,0 +1,36 @@
#include "thread/QueuedThread.h"
#include "thread/Blocker.h"
#include <stdio.h>
#include <unistd.h>
static const int COUNT = 3;
static const int SLEEP_US = 200*1000;
void task(void* arg);
int main() {
QueuedThread* thread = createQueuedThread();
postQueuedTask(thread, &task, NULL);
postQueuedTask(thread, &task, NULL);
for (int i = 0; i < COUNT*2+1; i++) {
printf("Main: %d\n", i);
usleep(SLEEP_US);
}
joinQueuedThread(thread);
freeQueuedThread(&thread);
return 0;
}
// ---
void task(void* arg) {
(void)arg;
for (int i = 0; i < COUNT; i++) {
printf("Thread: %d\n", i);
usleep(SLEEP_US);
}
}

47
tieto-cpu-tracker/lib/thread/tests/integration/Thread.test.c

@ -0,0 +1,47 @@
#include "thread/Thread.h"
#include "thread/Blocker.h"
#include <stdio.h>
#include <unistd.h>
static const int COUNT = 3;
static const int SLEEP_US = 200*1000;
void* run(void* arg);
int main() {
Thread* thread = NULL;
BlockerHandle blocker = createBlocker();
if (blocker == -1) {
printf("Main: Failed to create blocker.\n");
return 1;
}
thread = createThread(&run, &blocker);
for (int i = 0; i < COUNT+1; i++) {
printf("Main: %d\n", i);
usleep(SLEEP_US);
}
notifyBlocker(blocker);
joinThread(thread);
freeThread(&thread);
freeBlocker(&blocker);
return 0;
}
// ---
void* run(void* arg) {
BlockerHandle* blocker = arg;
for (int i = 0; i < COUNT; i++) {
printf("Thread: %d\n", i);
usleep(SLEEP_US);
}
printf("Blocker ID: %d\n", *blocker);
lockBlocker(*blocker);
printf("Thread: Blocker notified, continuing execution.\n");
return NULL;
}

96
tieto-cpu-tracker/lib/thread/tests/integration/Watchdog.test.c

@ -0,0 +1,96 @@
#include "thread/Watchdog.h"
#include <stdio.h>
#include <stdarg.h>
#include <unistd.h>
#include <assert.h>
static const int COUNT = 3;
static const int SLEEP_US = 500*1000;
static const int WATCHDOG_INTERVAL_MS = 2000;
static void run1(void* arg);
static void run2(void* arg);
static void run3(void* arg);
static QueuedThread* t1 = NULL;
static QueuedThread* t2 = NULL;
static QueuedThread* t3 = NULL;
__attribute__((__format__ (__printf__, 2, 0)))
static void stdLog(const char* tag, const char* format, ...)
{
va_list args;
printf("%s ", tag);
va_start(args, format);
vprintf(format, args);
va_end(args);
}
_Noreturn static void onThreadUnresponsive(Thread* thread)
{
printf("Thread %p is unresponsive, exiting\n", (void*)(thread));
assert(thread == t3->thread);
_exit(0); // this actually means passing the test
}
int main()
{
Watchdog* watchdog = createWatchdog(WATCHDOG_INTERVAL_MS, &onThreadUnresponsive, &stdLog);
t1 = createWatchedThread(watchdog);
t2 = createWatchedThread(watchdog);
t3 = createWatchedThread(watchdog);
printf("T1: %p\nT2: %p\nT3: %p\n",
(void*)t1->thread, (void*)t2->thread, (void*)t3->thread);
startWatchdog(watchdog);
postQueuedTask(t1, &run1, NULL);
postQueuedTask(t2, &run2, NULL);
postQueuedTask(t3, &run3, NULL);
for (int i = 0; i < COUNT+1; i++) {
printf("Main: %d\n", i);
usleep(SLEEP_US);
}
// give watchdog a chance to spot the hanged thread
usleep((WATCHDOG_INTERVAL_MS+500) * 1000);
stopWatchdog(watchdog);
joinWatchedThreads(watchdog);
freeWatchedThreads(watchdog);
freeWatchdog(&watchdog);
printf("Done.\n");
return 1; // this actually means failing the test
}
// ---
void run1(void* arg) {
(void)arg;
for (int i = 0; i < COUNT; i++) {
printf("Thread 1: %d\n", i);
usleep(SLEEP_US);
}
}
void run2(void* arg) {
(void)arg;
for (int i = 0; i < COUNT; i++) {
printf("Thread 2: %d\n", i);
usleep(SLEEP_US);
}
}
_Noreturn void run3(void* arg) {
(void)arg;
for (int i = 0; i < COUNT; i++) {
printf("Thread 3: %d\n", i);
usleep(SLEEP_US);
}
while (1) {} // loop forever
}

17
tieto-cpu-tracker/lib/thread/tests/unit/CMakeLists.txt

@ -0,0 +1,17 @@
cmake_minimum_required(VERSION 3.5)
set(C_STANDARD 11)
enable_testing()
set(SRC_DIR ../../src)
set(INC_DIR ../../include)
include_directories(${SRC_DIR} ${INC_DIR})
include_directories(SYSTEM ${EXTERN_DIR}/greatest)
add_executable(TaskQueueTest
${SRC_DIR}/core/TaskQueue.c
TaskQueue.test.c
)
add_test(TaskQueueTest ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/TaskQueueTest)

175
tieto-cpu-tracker/lib/thread/tests/unit/TaskQueue.test.c

@ -0,0 +1,175 @@
#include "thread/TaskQueue.h"
#include <stdlib.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
// Testing macros:
#define RUN_TEST(test_function) \
do {test_setup();test_function();test_teardown();} while (0)
#define CHECK(type, value, operator, expected, message) \
do {if (!(value operator expected)) { \
printf("-- Failed: %s\n---- Failed expression: %" #type " %s %" #type "\n", \
message, value, #operator, expected); ++failCounter;\
} else {printf("-- PASSED (%s %s %s)\n", \
#value, #operator, #expected);}} while (0)
static int failCounter = 0;
// usage:
// CHECK(type, actual, operator, expected, message);
// RUN_TEST(test_function);
enum {
IDX_RESULT_TASK_1 = 0,
IDX_RESULT_TASK_2,
IDX_RESULT_TASK_ORDER,
IDX_RESULT_TASK_STOP,
IDX_RESULT_BLOCK,
IDX_RESULT_NOTIFY,
IDX_RESULT_COUNT
};
static int taskResults[IDX_RESULT_COUNT] = {0};
static BlockerHandle blockerHnd = 99;
static TaskQueuePtr globalQueue = NULL;
void testTask1(void*);
void testTask2(void*);
void testTaskStop(void*);
void testTaskPostItself(void*);
void blockMock(BlockerHandle);
void notifyMock(BlockerHandle);
static void test_setup(void){}
static void test_teardown(void){}
static void test_createTaskQueue(void)
{
TaskQueuePtr queue = createTaskQueue(blockerHnd, &blockMock, &notifyMock);
CHECK(p, (void*)queue, !=, NULL, "createTaskQueue should return a valid task queue");
freeTaskQueue(&queue);
}
static void test_runQueue(void)
{
const char* msg = "runQueue should execute enqueued tasks in order with no loop";
TaskQueuePtr queue = createTaskQueue(blockerHnd, &blockMock, &notifyMock);
enqueueTask(queue, &testTask1, NULL);
enqueueTask(queue, &testTask2, NULL);
enqueueTask(queue, &testTaskStop, queue);
runQueue(queue);
CHECK(i, taskResults[IDX_RESULT_TASK_1] , ==, 1, msg);
CHECK(i, taskResults[IDX_RESULT_TASK_2] , ==, 2, msg);
CHECK(i, taskResults[IDX_RESULT_TASK_ORDER], ==, 3, msg);
CHECK(i, taskResults[IDX_RESULT_TASK_STOP] , ==, 1, msg);
freeTaskQueue(&queue);
}
static void test_enqueueNotify(void)
{
const char* msg = "Enqueueing new task should notify the blocker";
TaskQueuePtr queue = createTaskQueue(blockerHnd, &blockMock, &notifyMock);
memset(taskResults, 0, sizeof(taskResults));
enqueueTask(queue, &testTask1, NULL);
enqueueTask(queue, &testTask1, NULL);
CHECK(i, taskResults[IDX_RESULT_NOTIFY], ==, 2, msg);
freeTaskQueue(&queue);
}
static void test_stopNotify(void)
{
const char* msg = "Stopping the queue should notify the blocker";
TaskQueuePtr queue = createTaskQueue(blockerHnd, &blockMock, &notifyMock);
memset(taskResults, 0, sizeof(taskResults));
enqueueTask(queue, &testTaskStop, queue);
runQueue(queue);
CHECK(i, taskResults[IDX_RESULT_NOTIFY], ==, 2, msg); // enqueue + stop
freeTaskQueue(&queue);
}
static void test_blockOnEmpty(void)
{
const char* msg = "Empty queue should lock the blocker";
TaskQueuePtr queue = createTaskQueue(blockerHnd, &blockMock, &notifyMock);
memset(taskResults, 0, sizeof(taskResults));
globalQueue = queue;
enqueueTask(queue, &testTask1, NULL);
runQueue(queue);
CHECK(i, taskResults[IDX_RESULT_BLOCK], ==, 1, msg);
freeTaskQueue(&queue);
}
static void test_leak(void)
{
// This is a leak test for valgrind.
// The scenario is that a task enqueueing itself again
// shouldn't cause infinite loop nor memory leak.
TaskQueuePtr queue = createTaskQueue(blockerHnd, &blockMock, &notifyMock);
enqueueTask(queue, &testTaskPostItself, queue);
enqueueTask(queue, &testTaskStop, queue);
runQueue(queue);
freeTaskQueue(&queue);
}
int main(void) {
RUN_TEST(test_createTaskQueue);
RUN_TEST(test_runQueue);
RUN_TEST(test_enqueueNotify);
RUN_TEST(test_stopNotify);
RUN_TEST(test_blockOnEmpty);
RUN_TEST(test_leak);
return failCounter;
}
// ---
void blockMock(BlockerHandle blocker)
{
(void)blocker;
taskResults[IDX_RESULT_BLOCK] += 1;
if (globalQueue) {
stopQueue(globalQueue);
}
}
void notifyMock(BlockerHandle blocker)
{
(void)blocker;
taskResults[IDX_RESULT_NOTIFY] += 1;
}
void testTask1(void* data)
{
(void)data;
taskResults[IDX_RESULT_TASK_1] += 1;
taskResults[IDX_RESULT_TASK_ORDER] += 6;
}
void testTask2(void* data)
{
(void)data;
taskResults[IDX_RESULT_TASK_2] += 2;
taskResults[IDX_RESULT_TASK_ORDER] /= 2;
}
void testTaskStop(void* data)
{
taskResults[IDX_RESULT_TASK_STOP] += 1;
stopQueue((TaskQueuePtr)data);
}
void testTaskPostItself(void* data)
{
enqueueTask((TaskQueuePtr)data, testTaskPostItself, data);
}
Loading…
Cancel
Save