diff --git a/.gitignore b/.gitignore
index 5761abcfdf0c26a75374c945dfe366eaeee04285..83a8dc00367e5de634dfdb98da09de87e5de9c48 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,15 @@
-*.o
+src/counters_option.h
+src/captors.h
+tests/run
+doc/test_main_ex
+doc/info_reader_ex
+doc/mojitos.1
+tests/run
+src/counters_option.h
+src/sensors.h
+sensors.mk
+bin
+obj
+*.swp
+*.swo
+.vscode
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2052da5573562a34c1d611d4b391a780622fbf56
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,42 @@
+stages:
+  - build-option
+  - build
+
+job-build-counters:
+  stage: build-option
+  script:
+    - ./configure.sh -u counters
+    - make
+
+job-build-load:
+  stage: build-option
+  script:
+    - ./configure.sh -u load
+    - make
+
+job-build-network:
+  stage: build-option
+  script:
+    - ./configure.sh -u network
+    - make
+
+job-build-rapl:
+  stage: build-option
+  script:
+    - ./configure.sh -u rapl
+    - make
+
+job-build:
+  stage: build
+  script:
+    - ./configure.sh
+    - make
+
+job-build-test:
+  stage: build
+  script: 
+    - ./configure.sh
+    - make tests
+
+workflow:
+  name: 'Pipeline for branch: $CI_COMMIT_BRANCH'
diff --git a/AUTHORS b/AUTHORS
index 0bfc663bd4cba8c78a3de80d903028b587a3f338..13ddd11d874c2c46bb4f6792525e0d3a02371bb4 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1 +1,4 @@
-Georges Da Costa georges.da-costa@irit.fr
\ No newline at end of file
+Georges Da Costa georges.da-costa@irit.fr
+Floréal Risso floreal.risso@univ-tlse3.fr
+Alexis Paronnaud alexis.paronnaud@univ-tlse3.fr
+Téo Tinarrage teo.tinarrage@univ-tlse3.fr
diff --git a/README.md b/README.md
index 2cf04f18a0611162f0c19e04fde35b7a26e727ca..8c6d591851cbb80dcaf0bfa4c302f869baecc7cc 100644
--- a/README.md
+++ b/README.md
@@ -1,49 +1,64 @@
 # MOJITO/S
 
 An Open Source System, Energy and Network Monitoring Tools at the O/S level
-
-## Documentation
-
 MojitO/S runs on GNU/Linux
-Usage
 
-```python
-Usage : ./mojitos [-t time] [-f freq] [-r] [-p perf_list] [-l]  \
-                  [-u] [-d network_device] [-o logfile] [-e command arguments...]
-```
+## Usage
 
-Timing
-- If time is 0 then mojitos loops infinitively
-- If -e is present, time and freq are not used
+```bash
+Usage : ./bin/mojitos [OPTIONS] [SENSOR ...] [-e <cmd> ...]
 
-Configuration
-- -r activates RAPL (deprecated)
-- -R activates RAPL
-- -p activates performance counters. perf_list is coma separated list of performance counters without space.
-- -l lists the possible performance counters and quits
-- -d activates network monitoring
-- -u activates system-level load monitoring
-- -s activates statistics of overhead in nanoseconds
+OPTIONS:
+-f|--freq <freq>
+	set amount of measurements per second.
+-t|--time <time>
+	set duration value (seconds). If 0, then loops infinitely.
+-e|--exec <cmd> ...
+	Execute a command with optional arguments.
+	If this option is used, any usage of -t or -f is ignored.
+-o|--logfile <file>
+	specify a log file.
+-s|--overhead-stats
+	enable overhead statistics (nanoseconds).
 
-MojitO/S is published under the GPL3 license and is part of the [Energumen Project](https://www.irit.fr/energumen/)
+```
 
-<img src="https://www.irit.fr/energumen/images/energumen.png" width="100">
+The following is an exhaustive list of all the sensors (it is very likely
+that one will not have all the sensors activated in his build):
+```bash
+SENSORS:
+-a|--amd-rapl
+	AMD RAPL
+-p|--perf-list <perf_list>
+	performance counters
+	perf_list is a coma separated list of performance counters.
+	Ex: instructions,cache_misses
+-l|--list
+	list the available performance counters and quit
+-i|--monitor-infiniband <infiniband_path>
+	infiniband monitoring (if infiniband_path is X, tries to detect it automatically)
+-u|--sysload
+	system load
+-d|--net-dev <net_dev>
+	network monitoring (if network_device is X, tries to detect it automatically)
+-r|--intel-rapl
+	INTEL RAPL
+```
 
 ## Installation Instructions
 
-Dependencies
-```bash
-sudo apt install libpowercap0 libpowercap-dev powercap-utils python3
-```
 Download the source code
 ```bash
 git clone https://gitlab.irit.fr/sepia-pub/mojitos.git
 ```
-Compile the code
+The quickest way to compile the code is:
 ```bash
 cd mojitos
+./configure.sh
 make
 ```
+You may want to run `./configure.sh --help` to see configuration options.
+
 To execute mojitos without being root to monitor performance counters
 ```bash
 sudo sh -c 'echo 0 >/proc/sys/kernel/perf_event_paranoid'
@@ -58,43 +73,49 @@ sudo chmod a+w /sys/class/powercap/intel-rapl/*/*/*
 
 RAPL values during 2 seconds with a frequency of 2 Hz
 ```bash
-$ ./mojitos -t 2 -f 2 -r
-#timestamp package-00 core0 dram0 
-1036389.135659868 10986 2869 1526 
-1036389.500183551 1291440 255736 515562 
-1036390.000754048 1333553 228393 689513 
-1036390.500113978 1581967 267944 701536 
+$ ./bin/mojitos -t 2 -f 2 -r
+#timestamp package-00 core0 dram0
+1036389.135659868 10986 2869 1526
+1036389.500183551 1291440 255736 515562
+1036390.000754048 1333553 228393 689513
+1036390.500113978 1581967 267944 701536
 ```
 Performance counters (cpu_cycle, cache_ll_r_a and page_faults) during 4 seconds with a frequency of 1Hz. For cache performance counters, _r and _w are respectively read and write, and _a, _m and _p are respectively access, miss, pending.
 
 ```bash
-$ ./mojitos -t 4 -f 1 -p cpu_cycles,cache_ll_r_a,page_faults
-#timestamp cpu_cycles cache_ll page_faults 
-1036846.351749455 571199 1232 0 
-1036847.001098880 348173344 2451387 872 
-1036848.000166158 388112961 2509305 791 
-1036849.000191883 402255979 2625283 799 
+$ ./bin/mojitos -t 4 -f 1 -p cpu_cycles,cache_ll_r_a,page_faults
+#timestamp cpu_cycles cache_ll page_faults
+1036846.351749455 571199 1232 0
+1036847.001098880 348173344 2451387 872
+1036848.000166158 388112961 2509305 791
+1036849.000191883 402255979 2625283 799
 ```
 
 Network values with no time limit with a frequency of 1Hz. rxp and txp are the number of received and sent packets, while rxb and txp are the number of received and sent bytes.
 ```bash
-$ ./mojitos -t 0 -f 1 -d enp0s25
-#timestamp rxp rxb txp txb 
-1036559.277376027 0 0 0 0 
-1036560.000161101 4 581 2 179 
-1036561.000083968 178 268675 55 4954 
-1036562.000076162 11 1010 5 510 
-1036563.000069724 17 1643 12 3602 
-1036564.000113394 990 1493008 369 27299 
+$ ./bin/mojitos -t 0 -f 1 -d enp0s25
+#timestamp rxp rxb txp txb
+1036559.277376027 0 0 0 0
+1036560.000161101 4 581 2 179
+1036561.000083968 178 268675 55 4954
+1036562.000076162 11 1010 5 510
+1036563.000069724 17 1643 12 3602
+1036564.000113394 990 1493008 369 27299
 ```
 
 Overhead of the monitoring for RAPL and cpu_cycle
 ```bash
-$ ./mojitos -t 5 -f 1 -p cpu_cycles -r -s
-#timestamp cpu_cycles package-00 core0 dram0 overhead 
-1036988.197227391 162214 19898 4944 1586 149612 
-1036989.000151326 332613664 2513116 379577 1115171 739573 
-1036990.000116433 482150700 3321341 587218 1380673 315719 
-1036991.000182835 525984292 3592582 691221 1385982 272182 
-1036992.000165117 397678789 2770561 444030 1375729 510379 
+$ ./bin/mojitos -t 5 -f 1 -p cpu_cycles -r -s
+#timestamp cpu_cycles package-00 core0 dram0 overhead
+1036988.197227391 162214 19898 4944 1586 149612
+1036989.000151326 332613664 2513116 379577 1115171 739573
+1036990.000116433 482150700 3321341 587218 1380673 315719
+1036991.000182835 525984292 3592582 691221 1385982 272182
+1036992.000165117 397678789 2770561 444030 1375729 510379
 ```
+
+## License
+
+MojitO/S is published under the GPL3 license and is part of the [Energumen Project](https://www.irit.fr/energumen/)
+
+<img src="https://www.irit.fr/energumen/images/energumen.png" width="100">
diff --git a/configure.sh b/configure.sh
new file mode 100755
index 0000000000000000000000000000000000000000..21c94eff4ef28cab03761595e6473b29c80e8b2b
--- /dev/null
+++ b/configure.sh
@@ -0,0 +1,188 @@
+#!/bin/sh
+
+# SPDX-License-Identifier: GPL-3.0-or-later
+# Copyright (C) 2023-2023 Georges Da Costa <georges.da-costa@irit.fr>
+
+try() { "$@" || die "cannot $*"; }
+die() { yell "$*"; exit 111; }
+yell() { echo "$0: $*" >&2; }
+echo() { printf '%s\n' "$*"; }
+isnum() {
+	case "${1#[+-]}" in
+	*[!0-9]*|'') return 1 ;;
+	*) return 0 ;;
+	esac
+}
+dprint() {
+	for v in "$@"; do
+		decho "$v : $(eval "echo \$$v")"
+	done
+}
+decho() {
+	[ "$debug" = '1' ] && echo "$@"
+}
+
+debug=0
+target_hdr=src/sensors.h
+target_mk=sensors.mk
+
+nonsensor='counters_option|sensors|util'
+
+hdr_blacklist=$nonsensor
+hdr_whitelist=''
+
+usage() {
+	printf -- 'Usage: %s [-la] [-e <sensor>] [-i <sensor>] [-u <sensor>]\n' "$(basename "$0")" >&2
+	printf -- '-e | --exclude      :   exclude sensor, can be called multiple times\n' >&2
+	printf -- '-i | --include      :   include sensor, can be called multiple times\n' >&2
+	printf -- '-l | --list-sensors :   list all sensors and exit\n' >&2
+	printf -- '-u | --unique       :   only include the specified sensor\n' >&2
+	printf -- '                        if this option is used, any usage of `-e` or `-i` will be ignored\n' >&2
+	printf -- '-a | --all          :   include all sensors, meant to be used only by the makefile\n' >&2
+	exit 1
+}
+
+ls_sensors() {
+	try cd src
+
+	[ -z "$hdr_whitelist" ] && hdr_whitelist='.*'
+	dprint hdr_blacklist >&2
+	dprint hdr_whitelist >&2
+
+	ls -1 *.h |
+		grep -xEv "($hdr_blacklist)\.h" |
+		grep -xE  "($hdr_whitelist)\.h" |
+		sed 's/\.h$//'
+}
+
+# gen_sensors_h(sensor, nb_sensors)
+gen_sensors_h() {
+	sensors=$1
+	nb_sensors=$2
+	nb_sensor_opts=$(
+		for sensor in $sensors; do
+			sed -n 's/.*'"${sensor}"'_opt\[\([0-9]\+\)\].*/\1/p' "src/${sensor}.h"
+		done |
+			paste -s -d '+'
+	)
+	nb_sensor_opts=$(eval "echo \$(($nb_sensor_opts))")
+
+	dprint sensors >&2
+	dprint nb_sensor_opts >&2
+	isnum "$nb_sensor_opts" || die "could not get total number of sensors's command-line options"
+
+	# gen includes
+	for sensor in $sensors; do
+		printf '#include "%s.h"\n' "$sensor"
+	done
+	printf '\n'
+
+	printf '#define NB_SENSOR %d\n' "$nb_sensors"
+	printf '#define NB_SENSOR_OPT %d\n' "$nb_sensor_opts"
+	printf '\n'
+
+	# gen `init_sensors()`
+	printf 'void init_sensors(Optparse *opts, Sensor *sensors, size_t len, size_t offset, int *nb_defined)\n{\n'
+	printf '    int opt_idx = offset;\n'
+	for sensor in $sensors; do
+		cat <<-!
+		    for (int i = 0; i < ${sensor}.nb_opt; i++) {
+		        opts[opt_idx++] = ${sensor}_opt[i];
+		    }
+		    sensors[(*nb_defined)++] = ${sensor};
+		!
+	done
+	printf '    assert((offset + *nb_defined) <= len);\n'
+	printf '}\n'
+}
+
+gen_sensors_mk() {
+	sensors=$1
+	printf 'CAPTOR_OBJ = '
+	for sensor in $sensors; do
+		printf '$(OBJ_DIR)/%s.o ' "$sensor"
+	done
+	printf '\n'
+}
+
+detect_caps() {
+	[ -r /usr/include/linux/perf_event.h ] && hdr_whitelist=counters
+	[ -d /sys/class/infiniband ] && hdr_whitelist="${hdr_whitelist}|infiniband"
+	[ -r /proc/stat ] && hdr_whitelist="${hdr_whitelist}|load"
+
+	if [ -r /proc/net/route ]; then
+		dev=$(awk 'NR == 2 { print $1 }' /proc/net/route)
+		[ -e "/sys/class/net/$dev" ] && hdr_whitelist="${hdr_whitelist}|network"
+	fi
+
+	vendor=$(awk '/vendor_id/ {print $3; exit}' /proc/cpuinfo)
+	vendor_lc=$(echo "$vendor" | tr 'A-Z' 'a-z')
+	case $vendor_lc in
+	*intel*)
+		hdr_whitelist="${hdr_whitelist}|rapl"
+		;;
+	*amd*)
+		family=$(awk '/cpu[ \t]*family/ {print $4; exit}' /proc/cpuinfo)
+		if isnum "$family"; then
+			[ $family -ge 17 ] && hdr_whitelist="${hdr_whitelist}|amd_rapl"
+		fi
+		;;
+	*)
+		yell "unsupported processor vendor id: $vendor"
+		;;
+	esac
+
+	[ $(ls -1 /sys/class/hwmon | wc -l) -gt 0 ] && hdr_whitelist="${hdr_whitelist}|temperature"
+}
+
+case $1 in
+--all|-a)
+	all=1
+	;;
+esac
+
+[ "$all" ] || detect_caps
+
+[ "$all" ] ||
+while [ "$1" ]; do
+	case $1 in
+	--include|-i)
+		shift; [ "$1" ] || usage
+		hdr_whitelist="${hdr_whitelist}|${1}"
+		;;
+	--exclude|-e)
+		shift; [ "$1" ] || usage
+		hdr_blacklist="${hdr_blacklist}|${1}"
+		;;
+	--list-sensors|-l)
+		ls_sensors
+		exit 0
+		;;
+	--unique|-u)
+		shift; [ "$1" ] || usage
+		hdr_whitelist=$1
+		;;
+	--help|-h)
+		usage
+		;;
+	esac
+	shift
+done
+
+sensors=$(ls_sensors)
+nb_sensors=$(echo "$sensors" | sed '/^$/d' | wc -l)
+
+if [ "$nb_sensors" -eq 0 ]; then
+	printf -- '0 sensors are selected. cannot build.\n' >&2
+	exit 1
+fi
+
+try gen_sensors_h "$sensors" "$nb_sensors" > "$target_hdr"
+try gen_sensors_mk "$sensors" > "$target_mk"
+
+printf -- 'Run `make` to build `bin/mojitos`.\n' >&2
+printf -- 'The resulting binary will have the %d following sensors:\n' "$nb_sensors" >&2
+echo "$sensors" >&2
+
+make clean >/dev/null
+
diff --git a/counters_group.c b/counters_group.c
deleted file mode 100644
index bc9b448f818df565565416077a9d5788cfbadf62..0000000000000000000000000000000000000000
--- a/counters_group.c
+++ /dev/null
@@ -1,119 +0,0 @@
-/*******************************************************
- Copyright (C) 2018-2021 Georges Da Costa <georges.da-costa@irit.fr>
-
-    This file is part of Mojitos.
-
-    Mojitos is free software: you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation, either version 3 of the License, or
-    (at your option) any later version.
-
-    Mojitos is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with MojitO/S.  If not, see <https://www.gnu.org/licenses/>.
-
- *******************************************************/
-
-#include <linux/perf_event.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <string.h>
-#include <sys/ioctl.h>
-#include <stdio.h>
-#include <stdint.h>
-#include <asm/unistd.h>
-
-#include "counters.h"
-
-struct _counter_t {
-  int nbcores;
-  int nbperf;
-  int *counters;
-};
- 
-static long
-perf_event_open(struct perf_event_attr *hw_event, pid_t pid,
-		int cpu, int group_fd, unsigned long flags) {
-  long res = syscall(__NR_perf_event_open, hw_event, pid, cpu, group_fd, flags);
-  if (res == -1) {
-    perror("perf_event_open");
-    fprintf(stderr, "Error opening leader %llx\n", hw_event->config);
-    exit(EXIT_FAILURE);
-  }
-  return res;
-}
-
-counter_t init_counters(const int nb_perf, const __u32 *types, const __u64 *names) {
-  struct perf_event_attr pe;
-  struct _counter_t *counters = malloc(sizeof(struct _counter_t));
-  
-  unsigned int nbcores = sysconf(_SC_NPROCESSORS_ONLN);
-  counters->nbcores=nbcores;
-  counters->nbperf=nb_perf;
-  memset(&pe, 0, sizeof(struct perf_event_attr));
-  pe.size = sizeof(struct perf_event_attr);
-  pe.disabled = 1;
-
-  pe.read_format = PERF_FORMAT_GROUP;
-  counters->counters = malloc((nbcores+1)*sizeof(int));
-
-  for (int core=0; core<nbcores; core++) {
-    counters->counters[core]=-1;
-    for(int idperf=0; idperf<nb_perf; idperf ++){
-      pe.type = types[idperf];
-      pe.config = names[idperf];
-      int res=perf_event_open(&pe, -1, core, counters->counters[core], PERF_FLAG_FD_CLOEXEC);
-      if(counters->counters[core]==-1)
-	counters->counters[core]=res;
-    }
-  }
-  return counters;
-}
-
-void clean_counters(counter_t counters) {
-  for(int core=0; core<counters->nbcores; core++)
-    close(counters->counters[core]);
-  free(counters->counters);
-  free(counters);
-}
-
-void start_counters(counter_t counters) {
-  for(int core=0; core<counters->nbcores; core++)
-    ioctl(counters->counters[core], PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP);
-}
-void reset_counters(counter_t counters) {
-  for(int core=0; core<counters->nbcores; core++)
-    ioctl(counters->counters[core], PERF_EVENT_IOC_RESET, PERF_IOC_FLAG_GROUP);
-}
-
-struct read_format {
-  uint64_t nr;
-  struct {
-    uint64_t value;
-  } values[];
-};
-
-void get_counters(counter_t counters, long long *values) {
-  int nb_perf = counters->nbperf;
-  size_t buffer_size = sizeof(uint64_t)*(1+nb_perf);
-  struct read_format* buffer = NULL;
-  if(buffer==NULL)
-    buffer = malloc(buffer_size);
-  
-  memset(values, 0, nb_perf*sizeof(long long));
-
-  for (int core=0; core<counters->nbcores; core++) {
-    if (-1 == read(counters->counters[core], buffer, buffer_size)) {
-      perror("PB Lecture resultat");
-      exit(EXIT_FAILURE);
-    } 
-    for(int idperf=0; idperf<=nb_perf; idperf++) {
-      values[idperf] += buffer->values[idperf].value;
-    }
-  }
-  reset_counters(counters);
-}
diff --git a/counters_individual.c b/counters_individual.c
deleted file mode 100644
index 1437aa38442230ae0196dbb14e63c13a81c7b6d9..0000000000000000000000000000000000000000
--- a/counters_individual.c
+++ /dev/null
@@ -1,200 +0,0 @@
-/*******************************************************
- Copyright (C) 2018-2021 Georges Da Costa <georges.da-costa@irit.fr>
-
-    This file is part of Mojitos.
-
-    Mojitos is free software: you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation, either version 3 of the License, or
-    (at your option) any later version.
-
-    Mojitos is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with MojitO/S.  If not, see <https://www.gnu.org/licenses/>.
-
- *******************************************************/
-
-#include <linux/perf_event.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <string.h>
-#include <sys/ioctl.h>
-#include <stdio.h>
-#include <stdint.h>
-#include <asm/unistd.h>
-#include <stdint.h>
-
-struct _counter_t {
-  int nbcores;
-  int nbperf;
-  int **counters;
-  uint64_t *counters_values;
-  uint64_t *tmp_counters_values;
-
-  int* perf_indexes;
-  
-};
-typedef struct _counter_t* counter_t;
-
-#include "counters_option.h"
-
-void show_all_counters() {
-  for(int i=0; i<nb_counter_option;i++)
-    printf("%s\n", perf_static_info[i].name);
-}
-
-void perf_type_key(__u32 **perf_type, __u64 **perf_key, int *indexes, int nb){
-  *perf_type = malloc(nb*sizeof(__u32));
-  *perf_key  = malloc(nb*sizeof(__u64));
-  for(int i=0; i<nb; i++) {
-    (*perf_key)[i]  = perf_static_info[indexes[i]].perf_key;
-    (*perf_type)[i] = perf_static_info[indexes[i]].perf_type;
-  }
-}
-void perf_event_list(char *perf_string, int *nb_perf, int **perf_indexes) {
-  char *token;
-  *nb_perf=0;
-  *perf_indexes=NULL;
-  while((token=strtok(perf_string, ",")) != NULL) {
-    perf_string = NULL;
-    int i;
-    for(i=0; i<nb_counter_option; i++) {
-      if(strcmp(perf_static_info[i].name, token) == 0) {
-	(*nb_perf)++;
-	(*perf_indexes) = realloc(*perf_indexes, sizeof(int)*(*nb_perf));
-	(*perf_indexes)[*nb_perf-1]=i;
-	break;
-      }
-    }
-    if(i == nb_counter_option) {
-      fprintf(stderr, "Unknown performance counter: %s\n", token);
-      exit(EXIT_FAILURE);
-    }
-  }
-}
-
-static long perf_event_open(struct perf_event_attr *hw_event, pid_t pid,
-			    int cpu, int group_fd, unsigned long flags) {
-  long res = syscall(__NR_perf_event_open, hw_event, pid, cpu, group_fd, flags);
-  if (res == -1) {
-    perror("perf_event_open");
-    fprintf(stderr, "Error opening leader %llx\n", hw_event->config);
-    exit(EXIT_FAILURE);
-  }
-  return res;
-}
-
-counter_t _init_counters(const int nb_perf, const __u32 *types, const __u64 *names) {
-  struct perf_event_attr pe;
-  unsigned int nbcores = sysconf(_SC_NPROCESSORS_ONLN);
-  memset(&pe, 0, sizeof(struct perf_event_attr));
-  pe.size = sizeof(struct perf_event_attr);
-  pe.disabled = 1;
-
-  counter_t counters = malloc(sizeof(struct _counter_t));
-  counters->nbperf = nb_perf;
-  counters->nbcores=nbcores;
-  counters->counters=malloc(nb_perf*sizeof(int*));
-  for (int i=0; i<nb_perf; i++) {
-    pe.type = types[i];
-    pe.config = names[i];
-    counters->counters[i] = malloc(nbcores*sizeof(int));
-
-    for (int core=0; core<nbcores; core++) {
-      counters->counters[i][core] = perf_event_open(&pe, -1, core, -1, PERF_FLAG_FD_CLOEXEC);
-    }
-  }
-  return counters;
-}
-
-void clean_counters(void *ptr) {
-  counter_t counters = (counter_t) ptr;
-  for(int counter=0; counter<counters->nbperf; counter++) {
-    for(int core=0; core<counters->nbcores; core++)
-      close(counters->counters[counter][core]);
-    free(counters->counters[counter]);
-  }
-  free(counters->counters);
-  free(counters->counters_values);
-  free(counters->tmp_counters_values);
-  free(counters->perf_indexes);
-
-  free(counters);
-}
-
-void start_counters(counter_t counters) {
-  for(int counter=0; counter<counters->nbperf; counter++)
-    for(int core=0; core<counters->nbcores; core++)
-      ioctl(counters->counters[counter][core], PERF_EVENT_IOC_ENABLE, 0);
-}
-
-void reset_counters(counter_t counters) {
-  for(int counter=0; counter<counters->nbperf; counter++)
-    for(int core=0; core<counters->nbcores; core++)
-      ioctl(counters->counters[counter][core], PERF_EVENT_IOC_RESET, 0);
-}
-
-void _get_counters(counter_t counters, uint64_t *values) {
-  for(int i=0; i<counters->nbperf; i++) {
-    uint64_t accu=0;
-    uint64_t count=0;
-    for (int core=0; core<counters->nbcores; core++) {
-      if (-1 == read(counters->counters[i][core], &count, sizeof(uint64_t))) {
-	fprintf(stderr, "Cannot read result");
-	exit(EXIT_FAILURE);
-      }
-      accu += count;
-    }
-    values[i] = accu;
-  }
-}
-
-
-
-
-
-
-unsigned int init_counters(char* args, void **state) {
-  int nb_perf;
-  int* perf_indexes=NULL;
-
-  perf_event_list(args, &nb_perf, &perf_indexes);
-
-  __u32* perf_type;
-  __u64* perf_key;
-  perf_type_key(&perf_type, &perf_key, perf_indexes, nb_perf);
-  counter_t fd = _init_counters(nb_perf, perf_type, perf_key);
-  free(perf_type);
-  free(perf_key);
-
-  fd->perf_indexes = perf_indexes;
-  fd->counters_values = malloc(nb_perf*sizeof(uint64_t));
-  fd->tmp_counters_values = malloc(nb_perf*sizeof(uint64_t));
-  start_counters(fd);
-  _get_counters(fd, fd->counters_values);
-  *state = (void*) fd;
-
-  return nb_perf;
-}
-
-unsigned int get_counters(uint64_t* results, void*ptr) {
-  counter_t state = (counter_t) ptr;
-
-_get_counters(state, state->tmp_counters_values);
-  for(int i=0; i<state->nbperf; i++)
-    results[i] = state->tmp_counters_values[i] - state->counters_values[i];
-
-  memcpy(state->counters_values, state->tmp_counters_values, state->nbperf * sizeof(uint64_t));
-  return state->nbperf;
-}
-
-void label_counters(char **labels, void*ptr) {
-  counter_t state = (counter_t) ptr;
-  for(int i=0; i<state->nbperf;i++)
-    labels[i] = perf_static_info[state->perf_indexes[i]].name;
-
-}
diff --git a/counters_option.py b/counters_option.py
deleted file mode 100755
index 877a33afbd61814acf3ddbb4c5311bd043c4ef37..0000000000000000000000000000000000000000
--- a/counters_option.py
+++ /dev/null
@@ -1,62 +0,0 @@
-#! /usr/bin/python3
-
-# SPDX-License-Identifier: GPL-3.0-or-later
-# Copyright (C) 2018-2021 Georges Da Costa <georges.da-costa@irit.fr>
-
-linux_include = '/usr/include/linux/perf_event.h'
-
-string = """#include <linux/perf_event.h>
-
-typedef struct counter_option {
-  char *name;
-  __u32 perf_type;
-  __u64 perf_key;
-} counter_option;
-
-static counter_option perf_static_info[] = {"""
-print(string)
-
-nb = 0
-
-black_list = ['stalled_cycles_frontend','stalled_cycles_backend',
-              'cache_l1i', 'cache_op_write', 'cache_result_miss']
-
-with open(linux_include, 'r') as infile:
-    mode = ''
-    for line in infile:
-        if 'perf_hw_id' in line:
-            mode = 'PERF_TYPE_HARDWARE'
-        elif 'perf_hw_cache_' in line:
-            mode = 'PERF_TYPE_HW_CACHE'
-        elif 'perf_sw_id' in line:
-            mode = 'PERF_TYPE_SOFTWARE'
-        elif 'PERF_COUNT_' in line and '=' in line:
-            perf_name = line.split()[0]
-            short_perf = perf_name[14:].lower()
-            if short_perf in black_list:
-                continue
-            if mode == 'PERF_TYPE_HW_CACHE':
-                for op_id, op_id_str in enumerate(['r', 'w', 'p']):
-                    op_id_names = ['PERF_COUNT_HW_CACHE_OP_READ', 'PERF_COUNT_HW_CACHE_OP_WRITE', 'PERF_COUNT_HW_CACHE_OP_PREFETCH']
-                    for result_id, result_id_str in enumerate(['a', 'm']):
-                        result_id_names = ['PERF_COUNT_HW_CACHE_RESULT_ACCESS', 'PERF_COUNT_HW_CACHE_RESULT_MISS']
-
-                        res = '{ .name = "%s_%s_%s", .perf_type = %s, .perf_key = %s | (%s >> 8) | (%s >> 16) },' % (
-                            short_perf, op_id_str, result_id_str,
-                            mode,
-                            perf_name,
-                            op_id_names[op_id],
-                            result_id_names[result_id])
-                                                                                     
-                        print(res)
-                        nb += 1
-
-            else:
-                res = '{ .name = "'+short_perf+'", .perf_type = '+mode+', .perf_key = '+perf_name+'},'
-                print(res)
-                nb += 1
-
-
-print('};')
-
-print('static unsigned int nb_counter_option =',nb,';')
diff --git a/doc/info_reader_ex.c b/doc/info_reader_ex.c
new file mode 100644
index 0000000000000000000000000000000000000000..f1e6d96118f9bad951c516c5cb8392c5470141fb
--- /dev/null
+++ b/doc/info_reader_ex.c
@@ -0,0 +1,140 @@
+/*******************************************************
+ Copyright (C) 2023-2023 Georges Da Costa <georges.da-costa@irit.fr>
+
+    This file is part of Mojitos.
+
+    Mojitos is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Mojitos is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with MojitO/S.  If not, see <https://www.gnu.org/licenses/>.
+
+*******************************************************/
+
+
+// ~/mojitos/doc/$ gcc -Wall -Wextra -Wpedantic -O3 -o info_reader_ex info_reader_ex.c ./../src/util.c && ./info_reader_ex
+#include "./../lib/info_reader.h"
+
+#define MAX_PROCS 64
+
+// -----------------------------The storage
+typedef struct {
+    unsigned int processor;
+    char *vendor_id;
+    unsigned int family;
+    unsigned int core_id;
+    unsigned int physical_id;
+    char *model_name;
+} Cpu;
+
+// --------------IMPLEMENTING THE INTERFACE
+
+// -- Define the behaviour if the attempted value is an int
+GenericPointer int_allocator(char *s)
+{
+    unsigned int value = atoi(s);
+    return (GenericPointer) value;
+}
+
+// -- Define the behaviour if the attempted value is a string
+GenericPointer string_allocator(char *s)
+{
+    char *value = malloc(strlen(s) + 1);
+    strcpy(value, s);
+    return (GenericPointer) value;
+}
+
+// -- Define the processor setter
+void set_processor(GenericPointer storage, GenericPointer data)
+{
+    Cpu *cpu = (Cpu *) storage;
+    cpu->processor = data;
+}
+
+// -- Define the vendor_id setter
+void set_vendor_id(GenericPointer storage, GenericPointer data)
+{
+    Cpu *cpu = (Cpu *) storage;
+    cpu->vendor_id = (char *) data;
+}
+
+// -- Define the family setter
+void set_family(GenericPointer storage, GenericPointer data)
+{
+    Cpu *cpu = (Cpu *) storage;
+    cpu->family = data;
+}
+
+// -- Define the core_id setter
+void set_core_id(GenericPointer storage, GenericPointer data)
+{
+    Cpu *cpu = (Cpu *) storage;
+    cpu->core_id = data;
+}
+
+// -- Define the physical_id setter
+void set_physical_id(GenericPointer storage, GenericPointer data)
+{
+    Cpu *cpu = (Cpu *) storage;
+    cpu->physical_id = data;
+}
+
+// -- Define the model_name setter
+void set_model_name(GenericPointer storage, GenericPointer data)
+{
+    Cpu *cpu = (Cpu *) storage;
+    cpu->model_name = (char *) data;
+}
+
+int main()
+{
+    Cpu cpus[MAX_PROCS];
+
+    // -- Define the setter, the allocator for each key / separator.
+    KeyFinder keys[] = {
+        {.key = "processor", .delimiter = ": ", .copy = (CopyAllocator *) int_allocator, .set = (Setter *)set_processor},
+        {.key = "vendor_id", .delimiter = ": ", .copy = (CopyAllocator *) string_allocator, .set = (Setter *)set_vendor_id},
+        {.key = "cpu family", .delimiter = ": ", .copy = (CopyAllocator *) int_allocator, .set = (Setter *)set_family},
+        {.key = "core id", .delimiter = ": ", .copy = (CopyAllocator *) int_allocator, .set = (Setter *)set_core_id},
+        {.key = "physical id", .delimiter = ": ", .copy = (CopyAllocator *) int_allocator, .set = (Setter *)set_physical_id},
+        {.key = "model name", .delimiter = ": ", .copy = (CopyAllocator *) string_allocator, .set = (Setter *)set_model_name}
+    };
+
+    size_t nb_keys = sizeof(keys)/sizeof(KeyFinder);
+
+    // -- Init the parser
+    Parser parser = {
+        .storage = (GenericPointer) cpus,
+        .capacity = MAX_PROCS,
+        .storage_struct_size = sizeof(Cpu),
+        .keys = keys,
+        .nb_keys = nb_keys,
+        .file = fopen("/proc/cpuinfo", "r")
+    };
+
+    // -- Parse the file
+    parse(&parser);
+
+    // Print and free the results
+    for (unsigned int i = 0; i < parser.nb_stored; ++i) {
+        printf("========== PROC[%d] ==========\n", i);
+        printf("Processor: %u\n", cpus[i].processor);
+        printf("Vendor ID: %s\n", cpus[i].vendor_id);
+        printf("Family: %u\n", cpus[i].family);
+        printf("Core ID: %u\n", cpus[i].core_id);
+        printf("Physical ID: %u\n", cpus[i].physical_id);
+        printf("Model Name: %s\n", cpus[i].model_name);
+        free(cpus[i].vendor_id);
+        free(cpus[i].model_name);
+    }
+    printf("==============================\n");
+    return 0;
+}
+
diff --git a/doc/mojitos.pre.1 b/doc/mojitos.pre.1
new file mode 100644
index 0000000000000000000000000000000000000000..af9cb9bc5681422a8af870eead9cea78e88b0504
--- /dev/null
+++ b/doc/mojitos.pre.1
@@ -0,0 +1,81 @@
+.Dd January 9, 2023
+.Dt MOJITOS 1
+.Os
+.Sh NAME
+.Nm mojitos
+.Nd An open source system monitoring tool.
+.Sh SYNOPSIS
+.Nm mojitos
+.Op Ar OPTIONS
+.Op Ar SENSOR ...
+.Op Fl e Ar cmd ...
+.Sh DESCRIPTION
+.Nm
+is a monitoring tool with a multitude of sensors that does measurements at the OS level.
+.Nm
+runs on GNU/Linux.
+USAGE
+.Sh EXIT STATUS
+.Ex
+.Sh EXAMPLES
+RAPL values during 2 seconds with a frequency of 2Hz:
+.Bd -literal -offset indent
+$ mojitos -t 2 -f 2 -r
+#timestamp package-00 core0 dram0
+1036389.135659868 10986 2869 1526
+1036389.500183551 1291440 255736 515562
+1036390.000754048 1333553 228393 689513
+1036390.500113978 1581967 267944 701536
+.Ed
+.Pp
+Performance counters (cpu_cycle, cache_ll_r_a and page_faults) during 4 seconds
+with a frequency of 1Hz.
+In the context of cache performance counters (as with "cache_ll_r_a"),
+ _r and _w suffixes mean respectively read and write,
+while the _a, _m and _p ones mean respectively access, miss and pending.
+.Pp
+.Bd -literal -offset indent
+$ ./mojitos -t 4 -f 1 -p cpu_cycles,cache_ll_r_a,page_faults
+#timestamp cpu_cycles cache_ll page_faults
+1036846.351749455 571199 1232 0
+1036847.001098880 348173344 2451387 872
+1036848.000166158 388112961 2509305 791
+1036849.000191883 402255979 2625283 799
+.Ed
+.Pp
+Network values with no time limit with a frequency of 1Hz.
+rxp and txp are the number of received and sent packets, while rxb and txp are
+the number of received and sent bytes.
+.Pp
+.Bd -literal -offset indent
+$ ./mojitos -t 0 -f 1 -d enp0s25
+#timestamp rxp rxb txp txb
+1036559.277376027 0 0 0 0
+1036560.000161101 4 581 2 179
+1036561.000083968 178 268675 55 4954
+1036562.000076162 11 1010 5 510
+1036563.000069724 17 1643 12 3602
+1036564.000113394 990 1493008 369 27299
+.Ed
+.Pp
+overhead of the monitoring when using RAPL and the cpu_cycle performance counter:
+.Pp
+.Bd -literal -offset indent
+$ ./mojitos -t 5 -f 1 -p cpu_cycles -r -s
+#timestamp cpu_cycles package-00 core0 dram0 overhead
+1036988.197227391 162214 19898 4944 1586 149612
+1036989.000151326 332613664 2513116 379577 1115171 739573
+1036990.000116433 482150700 3321341 587218 1380673 315719
+1036991.000182835 525984292 3592582 691221 1385982 272182
+1036992.000165117 397678789 2770561 444030 1375729 510379
+.Ed
+.Sh SEE ALSO
+.Xr perf 1 ,
+.Xr lscpu 1 ,
+.Xr sysfs 5 ,
+.Xr proc 5
+.Sh LICENSE
+MojitO/S is published under the GPL3 license and is part of the
+.Lk https://www.irit.fr/energumen/ [Energumen Project]
+.Sh BUGS
+Yes.
diff --git a/doc/sensor_ex.c b/doc/sensor_ex.c
new file mode 100644
index 0000000000000000000000000000000000000000..5fc8eeb559a618e3a30b0354dd94860e2a2b0c21
--- /dev/null
+++ b/doc/sensor_ex.c
@@ -0,0 +1,124 @@
+/*******************************************************
+ Copyright (C) 2018-2023 Georges Da Costa <georges.da-costa@irit.fr>
+
+    This file is part of Mojitos.
+
+    Mojitos is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Mojitos is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with MojitO/S.  If not, see <https://www.gnu.org/licenses/>.
+
+ *******************************************************/
+
+/*
+ * compilation:
+ *
+
+# normally, this part is done with `configure.sh`, but here we need to
+# do this manually
+cat <<EOF > src/sensors.h
+#include "sensor_ex.h"
+
+#define NB_SENSOR 1
+#define NB_SENSOR_OPT 1
+
+void init_sensors(Optparse *opts, Sensor *sensors, size_t len, size_t offset, int *nb_defined)
+{
+    int opt_idx = offset;
+    for (int i = 0; i < sensor_ex.nb_opt; i++) {
+        opts[opt_idx++] = sensor_ex_opt[i];
+    }
+    sensors[(*nb_defined)++] = sensor_ex;
+    assert((offset + *nb_defined) <= len);
+}
+EOF
+
+# idem
+echo 'CAPTOR_OBJ = doc/sensor_ex.o' > sensors.mk
+
+# actual compilation here
+gcc -std=gnu99 -Wall -Wpedantic -I./lib -I./doc -I./src -g -Og -c doc/sensor_ex.c -o obj/sensor_ex.o
+gcc -std=gnu99 -Wall -Wpedantic -I./lib -I./doc -I./src -g -Og -c doc/util.c -o obj/util.o
+gcc -std=gnu99 -Wall -Wpedantic -I./lib -I./doc -I./src -g -Og -c doc/mojitos.c -o obj/mojitos.o
+gcc -std=gnu99 -Wall -Wpedantic -I./lib -I./doc -I./src -g -Og obj/util.o obj/mojitos.o obj/sensor_ex.o -o bin/mojitos
+
+ *
+**/
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "util.h"
+
+#define NB_SENSOR 3
+
+struct Accumulator {
+    int v[NB_SENSOR];
+};
+typedef struct Accumulator Accumulator;
+
+void _get_acc(int v[NB_SENSOR])
+{
+    for (int i = 0; i < NB_SENSOR; i++) {
+        v[i]++;
+    }
+}
+
+unsigned int init_acc(char *none, void **ptr)
+{
+    /* "none" refers to an optionnal command-line argument */
+    /* there is none in this case, so this parameter is not used */
+    UNUSED(none);
+
+    Accumulator *state = malloc(sizeof(Accumulator));
+
+    for (int i = 0; i < NB_SENSOR; i++) {
+        state->v[i] = -1;
+    }
+
+    *ptr = (void *)state;
+    _get_acc(state->v);
+
+    return NB_SENSOR;
+}
+
+unsigned int get_acc(uint64_t *results, void *ptr)
+{
+    Accumulator *state = (Accumulator *)ptr;
+
+    _get_acc(state->v);
+
+    for (int i = 0; i < NB_SENSOR; i++) {
+        results[i] = state->v[i];
+    }
+
+    return NB_SENSOR;
+}
+
+char *_labels_accumulator[NB_SENSOR] = {"acc1", "acc2", "acc3"};
+void label_acc(char **labels, void *none)
+{
+    UNUSED(none);
+    for (int i = 0; i < NB_SENSOR; i++) {
+        labels[i] = _labels_accumulator[i];
+    }
+}
+
+void clean_acc(void *ptr)
+{
+    Accumulator *state = (Accumulator *)ptr;
+
+    if (state == NULL) {
+        return;
+    }
+
+    free(state);
+}
diff --git a/doc/sensor_ex.h b/doc/sensor_ex.h
new file mode 100644
index 0000000000000000000000000000000000000000..9fd95d3e0f32b0013ca8115edb36f82a9203da69
--- /dev/null
+++ b/doc/sensor_ex.h
@@ -0,0 +1,46 @@
+/*******************************************************
+ Copyright (C) 2018-2023 Georges Da Costa <georges.da-costa@irit.fr>
+
+    This file is part of Mojitos.
+
+    Mojitos is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Mojitos is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with MojitO/S.  If not, see <https://www.gnu.org/licenses/>.
+
+ *******************************************************/
+
+/*
+ * Example of a basic sensor: an accumulator
+**/
+
+unsigned int init_acc(char *, void **);
+unsigned int get_acc(uint64_t *results, void *);
+void clean_acc(void *);
+void label_acc(char **labels, void *);
+
+Sensor sensor_ex = {
+    .init = init_acc,
+    .get = get_acc,
+    .clean = clean_acc,
+    .label = label_acc,
+    .nb_opt = 1,
+};
+
+Optparse sensor_ex_opt[1] = {
+    {
+        .longname = "accumulator",
+        .shortname = 'a',
+        .argtype = OPTPARSE_NONE,		/* OPTPARSE_NONE / OPTPARSE_OPTIONAL / OPTPARSE_REQUIRED */
+        .usage_arg = NULL,
+        .usage_msg = "dumb accumulator",
+    },
+};
diff --git a/doc/test_file_ex.c b/doc/test_file_ex.c
new file mode 100644
index 0000000000000000000000000000000000000000..0595e9a8c41cbfa349d139c0dcb96df2c4f2716a
--- /dev/null
+++ b/doc/test_file_ex.c
@@ -0,0 +1,158 @@
+/*******************************************************
+ Copyright (C) 2023-2023 Georges Da Costa <georges.da-costa@irit.fr>
+
+    This file is part of Mojitos.
+
+    Mojitos is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Mojitos is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with MojitO/S.  If not, see <https://www.gnu.org/licenses/>.
+
+*******************************************************/
+
+// Include of the test library
+#include "./../tests/small_test.h"
+// Include the file that contains the function you want to test
+// #include "a_file.c"
+
+// This example is divided into three parts:
+// 1. SOME SIMPLE EXAMPLES
+// 2. A MORE COMPLEX EXAMPLE
+// 3. FILE ENTRY POINT
+
+//  -----------------1. SOME SIMPLE EXAMPLES
+
+// A simple function that should pass
+// test_should_pass is the name of the function
+// {} is the function code
+// Use one of the define TEST_XXX defined in small_test.h
+TFUNCTION(test_should_pass, {
+    int result = 42;
+    int expected = 42;
+    TEST_INT(&result, &expected);
+})
+
+// A simple function that should pass
+// test_should_fail is the name of the function
+// {} is the function code
+// Use one of the define TEST_XXX defined in small_test.h
+TFUNCTION(test_should_fail, {
+    char *result = "a fail";
+    char *expected = "a nice fail";
+    TEST_STR(result, expected);
+})
+
+// A simple test on an array
+// test_array is the name of the function
+// {} is the function code
+// Use one of the define TEST_XXX defined in small_test.h
+TFUNCTION(test_array, {
+    static unsigned int size = 10;
+    int results[10] = {0};
+    int expecteds[10] = {0};
+    for (unsigned int i = 0; i < size; i++)
+    {
+        TEST_INT(&results[i], &expecteds[i]);
+    }
+})
+
+// See "TFILE_ENTRY_POINT" for how to call these functions.
+
+// ------------2. THE MORE COMPLEX EXAMPLE
+// This example shows how to add a new type to the framework
+
+// The type that you want to add :
+typedef struct {
+    int simple_int;
+    char simple_str[20];
+} UserType;
+
+
+// The test framework needs two functions :
+
+// A function that compares two values.
+int usertype_compare(void *ptr1, void *ptr2)
+{
+    return memcmp(ptr1, ptr2, sizeof(UserType)) == 0;
+}
+
+// A function to format a value.
+char *usertype_format(char buffer[FMT_BUFFER_SIZE], void *ptr)
+{
+    UserType *x = (UserType *) ptr;
+    snprintf(buffer, FMT_BUFFER_SIZE, "UserType {simple_int: %d, simple_str: %s}", x->simple_int, x->simple_str);
+    return buffer;
+}
+
+// Store the functions in the interface type.
+static const TestInterface usertype_interface = {
+    .compare = usertype_compare,
+    .format = usertype_format
+};
+
+// Create a macro that implements the interface by passing the interface created to the TEST_INTERFACE macro.
+#define TEST_USER_TYPE(__result, __expected) \
+    TEST_INTERFACE(__result, __expected, &usertype_interface)
+
+
+// Creating a constructor is recommended (C can have problems parsing macros where "," is used).
+#define DUMMY_USER_TYPE(__user, __simple_int, __simple_str) \
+    do {                                                    \
+      __user = (UserType) {                                 \
+          .simple_int = __simple_int,                       \
+          .simple_str = __simple_str                        \
+    };                                                      \
+} while (0);
+
+// Now you can write test for your new type
+// all you have to do is call the macro that implements "TEST_INTERFACE".
+// here it's "TEST_USERTYPE"
+TFUNCTION(test_user_type, {
+    UserType result;
+    UserType expected;
+
+    DUMMY_USER_TYPE(result, 1, "John Doe");
+    DUMMY_USER_TYPE(expected, 1, "John Doe");
+
+    TEST_USER_TYPE(&result, &expected);
+})
+
+// Now you can write test for your new type
+// all you have to do is call the macro that implements "TEST_INTERFACE".
+// here it's "TEST_USERTYPE"
+TFUNCTION(test_array_user_type, {
+    UserType results[1];
+    UserType expecteds[1];
+
+    DUMMY_USER_TYPE(results[0], 1, "John Doe");
+    DUMMY_USER_TYPE(expecteds[0], 1, "John Doe");
+
+    for (unsigned int i = 0; i < 1; i++)
+    {
+        TEST_USER_TYPE(&results[i], &expecteds[i]);
+    }
+})
+
+// ---------------------3. FILE ENTRY POINT
+
+// Define the entry point of a test file
+// Use the macro "CALL_TFUNCTION" where the name of a function is defined with the macro "TFUNCTION".
+TFILE_ENTRY_POINT(test_file_ex, {
+    // Call the simple examples
+    CALL_TFUNCTION(test_should_pass);
+    CALL_TFUNCTION(test_should_fail);
+    CALL_TFUNCTION(test_array);
+
+    // Call the more complex example
+    CALL_TFUNCTION(test_user_type);
+    CALL_TFUNCTION(test_array_user_type);
+})
+
diff --git a/doc/test_main_ex.c b/doc/test_main_ex.c
new file mode 100644
index 0000000000000000000000000000000000000000..a5e7841eed202c9a4dd7a7dcad500619fcdaeb30
--- /dev/null
+++ b/doc/test_main_ex.c
@@ -0,0 +1,36 @@
+/*******************************************************
+ Copyright (C) 2023-2023 Georges Da Costa <georges.da-costa@irit.fr>
+
+    This file is part of Mojitos.
+
+    Mojitos is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Mojitos is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with MojitO/S.  If not, see <https://www.gnu.org/licenses/>.
+
+*******************************************************/
+
+// ~/mojitos/doc$ gcc -Wall -Wextra -Wpedantic -o test_main_ex test_main_ex.c ./../src/util.c
+// Include of the test library
+#include "./../tests/small_test.h"
+
+// Include the *.c files containing the tests
+#include "./test_file_ex.c"
+// #include "./test_another_test_file.c"
+
+// Define the entry point of the programme
+TMAIN({
+    // Must contain the call to the entry point functions of each file
+    // USE CALL_TFUNCTION(my_function) instead of my_function()
+    CALL_TFUNCTION(test_file_ex);
+    // CALL_TFUNCTION(another_file_entry_point_function);
+})
+
diff --git a/frapl.c b/frapl.c
deleted file mode 100644
index 433a28c13ac5ecd847c95935fb3fa0e8f7131935..0000000000000000000000000000000000000000
--- a/frapl.c
+++ /dev/null
@@ -1,161 +0,0 @@
-/*******************************************************
- Copyright (C) 2022-2023 Georges Da Costa <georges.da-costa@irit.fr>
-
-    This file is part of Mojitos.
-
-    Mojitos is free software: you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation, either version 3 of the License, or
-    (at your option) any later version.
-
-    Mojitos is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with MojitO/S.  If not, see <https://www.gnu.org/licenses/>.
-
- *******************************************************/
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <inttypes.h>
-
-
-#define MAX_HEADER 128
-
-char *get_frapl_string(const char *filename) {
-  int fd = open(filename, O_RDONLY);
-  if( fd == -1)
-    return NULL;
-  char *result = malloc(MAX_HEADER);
-  int nb = read(fd, result, MAX_HEADER);
-  close(fd);
-  result[nb-1] = 0;
-  return (result);
-}  
-
-void test_append(char* name, int i) {
-  //char last = name[strlen(name)-1];
-  //if (last>='0' && last <= '9')
-  //  return;
-  sprintf(name+strlen(name), "%d", i);
-}
-
-
-struct _frapl_t {
-  unsigned int nb;
-  char **names;
-  int *fids;
-  uint64_t* values;
-  uint64_t* tmp_values;
-
-};
-typedef struct _frapl_t _frapl_t;
-
-
-void add_frapl_source(_frapl_t* rapl, char*name, char*energy_uj) {
-  rapl->nb += 1;
-  rapl->names = realloc(rapl->names, sizeof(char**)*rapl->nb);
-  rapl->fids = realloc(rapl->fids, sizeof(int*)*rapl->nb);
-  
-  rapl->names[rapl->nb-1] = malloc(strlen(name)+1);
-  strcpy(rapl->names[rapl->nb-1], name);
-  //printf("%s\n", energy_uj);
-
-  rapl->fids[rapl->nb-1] = open(energy_uj, O_RDONLY);
-}
-
-
-void _get_frapl(uint64_t *values, _frapl_t* rapl) {
-  static char buffer[512];
-
-  for (int i = 0; i < rapl->nb; i++) {
-
-    pread(rapl->fids[i], buffer, 100, 0);
-    values[i] = strtoull(buffer, NULL, 10);
-  }
-}
-
-
-unsigned int init_frapl(char* none, void **ptr) {
-  _frapl_t *rapl = malloc(sizeof(_frapl_t));
-  rapl->nb = 0;
-  rapl->names = NULL;
-  rapl->fids = NULL;
-
-  char buffer[1024];
-  char *name_base = "/sys/devices/virtual/powercap/intel-rapl/intel-rapl:%d/%s";
-  char *name_sub = "/sys/devices/virtual/powercap/intel-rapl/intel-rapl:%d/intel-rapl:%d:%d/%s";
-
-  for (int i=0;; i++) {
-    sprintf(buffer, name_base, i, "name");
-    char *tmp = get_frapl_string(buffer);
-    if (tmp == NULL) break;
-    //printf("%s\n", tmp);
-    test_append(tmp, i);
-    //printf("%s -> %s\n", buffer, tmp);
-
-    sprintf(buffer, name_base, i, "energy_uj");
-    add_frapl_source(rapl, tmp, buffer);
-    free(tmp);
-
-    for (int j=0;; j++) {
-      sprintf(buffer, name_sub, i, i, j, "name");
-      char *tmp_sub = get_frapl_string(buffer);
-      if (tmp_sub == NULL) break;
-      //printf("%s\n", tmp_sub);
-      test_append(tmp_sub, i);
-      //printf("%s -> %s\n", buffer, tmp_sub);
-
-
-      sprintf(buffer, name_sub, i, i, j, "energy_uj");
-      add_frapl_source(rapl, tmp_sub, buffer);
-      
-      free(tmp_sub);
-    }
-  }
-
-  rapl->values = calloc(sizeof(uint64_t), rapl->nb);
-  rapl->tmp_values = calloc(sizeof(uint64_t), rapl->nb);
-
-  _get_frapl(rapl->values, rapl);
-
-  *ptr = (void*)rapl;
-  return rapl->nb;
-}
-
-
-unsigned int get_frapl(uint64_t* results, void* ptr) {
-  _frapl_t* state = (_frapl_t*) ptr;
-  _get_frapl(state->tmp_values, state);
-  for(int i=0; i<state->nb; i++)
-    results[i] = state->tmp_values[i] - state->values[i];
-
-  memcpy(state->values, state->tmp_values, sizeof(uint64_t)*state->nb);
-  return state->nb;
-}
-
-void clean_frapl(void *ptr) {
-  _frapl_t* rapl = (_frapl_t*) ptr;
-  for(int i=0; i<rapl->nb; i++) {
-    free(rapl->names[i]);
-    close(rapl->fids[i]);
-  }
-  free(rapl->names);
-  free(rapl->fids);
-  free(rapl->values);
-  free(rapl->tmp_values);
-  free(rapl);
-}
-
-
-void label_frapl(char **labels, void *ptr) {
-  _frapl_t* rapl = (_frapl_t*) ptr;
-  for(int i=0; i<rapl->nb; i++)
-    labels[i] = rapl->names[i];
-}
diff --git a/infiniband.c b/infiniband.c
deleted file mode 100644
index 49dca077f6f8552b5707a75c4ad5bc2ffa2da462..0000000000000000000000000000000000000000
--- a/infiniband.c
+++ /dev/null
@@ -1,74 +0,0 @@
-/*******************************************************
- Copyright (C) 2018-2021 Georges Da Costa <georges.da-costa@irit.fr>
-
-    This file is part of Mojitos.
-
-    Mojitos is free software: you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation, either version 3 of the License, or
-    (at your option) any later version.
-
-    Mojitos is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with MojitO/S.  If not, see <https://www.gnu.org/licenses/>.
-
- *******************************************************/
-#include <stdlib.h>
-#include <fcntl.h>
-#include <stdio.h>
-#include <string.h>
-
-#include <glob.h>
-
-#include <stdint.h>
-
-struct network_t {
-  uint64_t values[4];
-  uint64_t tmp_values[4];
-  int sources[4];
-};
-unsigned int _get_network(uint64_t* results, int* sources);
-
-
-unsigned int init_infiniband(char* infi_path, void**ptr) {
-  if(infi_path==NULL)
-    return 0;
-
-  if(strcmp(infi_path,"X")==0) {
-
-    glob_t res;
-  
-    glob("/sys/class/infiniband/*/ports/*/counters/", 0, NULL, &res);
-    if(res.gl_pathc == 0)
-      return 0;
-    infi_path = res.gl_pathv[0];
-  }
-  
-  char *filenames[] = {"%s/port_rcv_packets",
-		       "%s/port_rcv_data",
-		       "%s/port_xmit_packets",
-		       "%s/port_xmit_data"};
-
-  struct network_t *state = malloc(sizeof(struct network_t));
-
-  char buffer[1024];
-  for(int i=0; i<4; i++) {
-    sprintf(buffer, filenames[i], infi_path);
-    state->sources[i] = open(buffer, O_RDONLY);
-  }
-  
-  *ptr = (void*) state;
-  _get_network(state->values, state->sources);
-
-  return 4;
-}
-
-char *_labels_infiniband[4] = {"irxp", "irxb", "itxp", "itxb"};
-void label_infiniband(char **labels, void*none) {
-  for(int i=0; i<4; i++)
-    labels[i] = _labels_infiniband[i];
-}
diff --git a/infiniband.h b/infiniband.h
deleted file mode 100644
index af8b2b6d0993a138dee43e4aa7aa8cd71fad661d..0000000000000000000000000000000000000000
--- a/infiniband.h
+++ /dev/null
@@ -1,22 +0,0 @@
-/*******************************************************
- Copyright (C) 2018-2019 Georges Da Costa <georges.da-costa@irit.fr>
-
-    This file is part of Mojitos.
-
-    Mojitos is free software: you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation, either version 3 of the License, or
-    (at your option) any later version.
-
-    Mojitos is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with MojitO/S.  If not, see <https://www.gnu.org/licenses/>.
-
- *******************************************************/
-
-unsigned int init_infiniband(char* infi_path, void**ptr);
-void label_infiniband(char **labels, void*);
diff --git a/lib/info_reader.h b/lib/info_reader.h
new file mode 100644
index 0000000000000000000000000000000000000000..ec28db62867db4c1b9f64d3e61008dc418fd1bc1
--- /dev/null
+++ b/lib/info_reader.h
@@ -0,0 +1,243 @@
+/*******************************************************
+ Copyright (C) 2023-2023 Georges Da Costa <georges.da-costa@irit.fr>
+
+    This file is part of Mojitos.
+
+    Mojitos is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Mojitos is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with MojitO/S.  If not, see <https://www.gnu.org/licenses/>.
+
+*******************************************************/
+
+#ifndef _INFO_READER_H
+#define _INFO_READER_H
+
+#include <string.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+/**
+ * @struct Parser
+ * @brief The parser struct
+ * The struct containing all the necessary informations and functions to parse a file
+ *
+ * @var storage : GenericPointer : pointer to the storage where the parsed data will be stored
+ * @var nb_stored : unsigned int : the number of struct stored
+ * @var capacity : unsigned int : the maximum number of struct that can be stored
+ * @var storage_struct_size : size_t : the size of the struct stored in the storage
+ * @var keys : KeyFinder* : pointer to an array of KeyFinder containing the possible keys
+ * @var nb_keys : unsigned int : number of key finders
+ * @var file : FILE* : pointer to the file that will be parsed
+*/
+typedef struct Parser Parser;
+
+/**
+ * @struct KeyFinder
+ * @brief The key finder struct
+ * The struct containing all the necessary informations and functions to find a key in a line of text
+ *
+ * @var key : char* : the key to be found
+ * @var delimiter : char* : the delimiter between the key and the value
+ * @var copy : CopyAllocator*: the function to use to make a copy of the value
+ * @var set : Setter*: the function to use to store the value in the storage
+*/
+typedef struct KeyFinder KeyFinder;
+
+/**
+ * @brief Split a string into a key and value based on a specified delimiter.
+ *
+ * The function takes a string and splits it into two parts: a key and a value.
+ * The parts are determined by a specified delimiter.
+ * The key and value are returned through output parameters.
+ * If the delimiter is not found in the input string, the key and value pointers
+ * will be set to NULL.
+ *
+ * @param[in] string The input string to be split, the string is modified.
+ * @param[in] delimiter The delimiter string.
+ * @param[out] key A pointer to a char pointer where the key will be stored.
+ * @param[out] value A pointer to a char pointer where the value will be stored.
+ * @return None.
+ */
+static void split_on_delimiter(char *string, const char *delimiter, char **key, char **value);
+
+/**
+ * @brief Replace the first occurrence of a character in a string with another character.
+ *
+ * The function takes a string and two characters as input, and replaces the first
+ * occurrence of the first character in the string with the second character.
+ *
+ * @param[in,out] string The input string where the replacement should take place.
+ * @param[in] from The character to be replaced.
+ * @param[in] to The character to replace with.
+ * @return None.
+ */
+static void replace_first(char *string, char from, char to);
+
+/**
+ * @brief Check if a string starts with a prefix.
+ *
+ * @param[in] prefix The prefix to check.
+ * @param[in] string The string to check.
+ * @return true The string starts with the prefix.
+ * @return false The string does not start with the prefix or one of the input pointers is NULL.
+ */
+static bool start_with(const char *prefix, const char *string);
+
+/**
+ * @brief Matches a line of text to a key in the parser's list of keys.
+ *
+ * @param[in] parser Pointer to the Parser struct.
+ * @param[in] line Line of text to match.
+ * @param[out] key_finder Pointer to a KeyFinder pointer where the matched key will be stored.
+ * @param[out] raw_value Pointer to a char pointer where the value associated with the matched key will be stored.
+ *
+ * @return Returns 1 if a key is matched, 0 otherwise.
+ */
+static unsigned int match(Parser *parser, char *line, KeyFinder **key_finder, char **raw_value);
+
+typedef size_t GenericPointer;
+typedef GenericPointer (CopyAllocator) (char *string);
+typedef void (Setter) (GenericPointer storage, GenericPointer value);
+
+struct KeyFinder {
+    char *key;
+    char *delimiter;
+
+    CopyAllocator *copy;
+    Setter *set;
+};
+
+struct Parser {
+    GenericPointer storage;
+    unsigned int nb_stored;
+    unsigned int capacity;
+    size_t storage_struct_size;
+
+    KeyFinder *keys;
+    unsigned int nb_keys;
+
+    FILE *file;
+};
+
+static void set_value(Parser *parser, KeyFinder *key_finder, char *raw_value)
+{
+    GenericPointer address = parser->storage + (parser->storage_struct_size * parser->nb_stored);
+    GenericPointer value = key_finder->copy(raw_value);
+    key_finder->set(address, value);
+}
+
+static unsigned int match(Parser *parser, char *line, KeyFinder **key_finder, char **raw_value)
+{
+    for (unsigned int i = 0; i < parser->nb_keys; i++) {
+        KeyFinder *finder = &parser->keys[i];
+
+        if (start_with(finder->key, line)) {
+            char *value = NULL;
+            char *key = NULL;
+
+            split_on_delimiter(line, finder->delimiter, &key, &value);
+            if ( key == NULL || value == NULL) {
+                return 0;
+            }
+            *key_finder = finder;
+            *raw_value = value;
+            return 1;
+        }
+    }
+    return 0;
+}
+
+static unsigned int move_to_next(Parser *parser)
+{
+    parser->nb_stored += 1;
+    if (parser->nb_stored >= parser->capacity) {
+        return 0;
+    }
+    return 1;
+}
+
+static unsigned int parse(Parser *parser)
+{
+    char *line = NULL;
+    size_t len = 0;
+    ssize_t read;
+    unsigned int key_assigned = 0;
+
+    while ((read = getline(&line, &len, parser->file)) != -1) {
+        if (key_assigned == parser->nb_keys && read > 1) {
+            continue;
+        } else if (read == 1) {
+            if (!move_to_next(parser)) {
+                return 0;
+            }
+            key_assigned = 0;
+        } else {
+            KeyFinder *key_finder = NULL;
+            char *raw_value = NULL;
+            replace_first(line, '\n', '\0');
+            if (match(parser, line, &key_finder, &raw_value)) {
+                set_value(parser, key_finder, raw_value);
+                ++key_assigned;
+            }
+        }
+    }
+    if (key_assigned > 0) {
+        parser->nb_stored++;
+    }
+    free(line);
+    return 1;
+}
+
+
+
+static void replace_first(char *string, char from, char to)
+{
+    for (int i = 0; string[i] != '\0'; i++) {
+        if (string[i] == from) {
+            string[i] = to;
+            break;
+        }
+    }
+}
+
+static void split_on_delimiter(char *string, const char *delimiter, char **key, char **value)
+{
+    *key = NULL;
+    *value = NULL;
+    size_t delimiter_len = strlen(delimiter);
+    char *start_delimiter = strstr(string, delimiter);
+    if (start_delimiter != NULL) {
+        *start_delimiter = '\0';
+        *key = string;
+        *value = start_delimiter + delimiter_len;
+    }
+}
+
+static bool start_with(const char *prefix, const char *string)
+{
+    if (prefix == NULL || string == NULL) {
+        return false;
+    }
+
+    size_t prefix_len = strlen(prefix);
+    size_t string_len = strlen(string);
+
+    if (string_len < prefix_len) {
+        return false;
+    } else {
+        return  memcmp(prefix, string, prefix_len) == 0;
+    }
+}
+
+#endif
+
diff --git a/lib/optparse.h b/lib/optparse.h
new file mode 100644
index 0000000000000000000000000000000000000000..ee7dcb6c967fc158df36b4a844a13d5f7b6746ac
--- /dev/null
+++ b/lib/optparse.h
@@ -0,0 +1,392 @@
+/* Optparse --- portable, reentrant, embeddable, getopt-like option parser
+ *
+ * This is free and unencumbered software released into the public domain.
+ *
+ * To get the implementation, define OPTPARSE_IMPLEMENTATION.
+ * Optionally define OPTPARSE_API to control the API's visibility
+ * and/or linkage (static, __attribute__, __declspec).
+ *
+ * The POSIX getopt() option parser has three fatal flaws. These flaws
+ * are solved by Optparse.
+ *
+ * 1) Parser state is stored entirely in global variables, some of
+ * which are static and inaccessible. This means only one thread can
+ * use getopt(). It also means it's not possible to recursively parse
+ * nested sub-arguments while in the middle of argument parsing.
+ * Optparse fixes this by storing all state on a local struct.
+ *
+ * 2) The POSIX standard provides no way to properly reset the parser.
+ * This means for portable code that getopt() is only good for one
+ * run, over one argv with one option string. It also means subcommand
+ * options cannot be processed with getopt(). Most implementations
+ * provide a method to reset the parser, but it's not portable.
+ * Optparse provides an optparse_arg() function for stepping over
+ * subcommands and continuing parsing of options with another option
+ * string. The Optparse struct itself can be passed around to
+ * subcommand handlers for additional subcommand option parsing. A
+ * full reset can be achieved by with an additional optparse_init().
+ *
+ * 3) Error messages are printed to stderr. This can be disabled with
+ * opterr, but the messages themselves are still inaccessible.
+ * Optparse solves this by writing an error message in its errmsg
+ * field. The downside to Optparse is that this error message will
+ * always be in English rather than the current locale.
+ *
+ * Optparse should be familiar with anyone accustomed to getopt(), and
+ * it could be a nearly drop-in replacement. The option string is the
+ * same and the fields have the same names as the getopt() global
+ * variables (optarg, optind, optopt).
+ *
+ * Optparse also supports GNU-style long options with optparse_long().
+ * The interface is slightly different and simpler than getopt_long().
+ *
+ * By default, argv is permuted as it is parsed, moving non-option
+ * arguments to the end. This can be disabled by setting the `permute`
+ * field to 0 after initialization.
+ */
+#ifndef OPTPARSE_H
+#define OPTPARSE_H
+
+#ifndef OPTPARSE_API
+#  define OPTPARSE_API
+#endif
+
+struct optparse {
+    char **argv;
+    int permute;
+    int optind;
+    int optopt;
+    char *optarg;
+    char errmsg[64];
+    int subopt;
+};
+
+enum optparse_argtype {
+    OPTPARSE_NONE,
+    OPTPARSE_REQUIRED,
+    OPTPARSE_OPTIONAL
+};
+
+struct optparse_long {
+    const char *longname;
+    int shortname;
+    enum optparse_argtype argtype;
+    char *usage_arg;
+    char *usage_msg;
+    void *(*fn)(void *, size_t);	/* SUBJECT TO CHANGE */
+};
+
+/**
+ * Initializes the parser state.
+ */
+OPTPARSE_API
+void optparse_init(struct optparse *options, char **argv);
+
+/**
+ * Read the next option in the argv array.
+ * @param optstring a getopt()-formatted option string.
+ * @return the next option character, -1 for done, or '?' for error
+ *
+ * Just like getopt(), a character followed by no colons means no
+ * argument. One colon means the option has a required argument. Two
+ * colons means the option takes an optional argument.
+ */
+OPTPARSE_API
+int optparse(struct optparse *options, const char *optstring);
+
+/**
+ * Handles GNU-style long options in addition to getopt() options.
+ * This works a lot like GNU's getopt_long(). The last option in
+ * longopts must be all zeros, marking the end of the array. The
+ * longindex argument may be NULL.
+ */
+OPTPARSE_API
+int optparse_long(struct optparse *options,
+                  const struct optparse_long *longopts,
+                  int *longindex);
+
+/**
+ * Used for stepping over non-option arguments.
+ * @return the next non-option argument, or NULL for no more arguments
+ *
+ * Argument parsing can continue with optparse() after using this
+ * function. That would be used to parse the options for the
+ * subcommand returned by optparse_arg(). This function allows you to
+ * ignore the value of optind.
+ */
+OPTPARSE_API
+char *optparse_arg(struct optparse *options);
+
+/* Implementation */
+#ifdef OPTPARSE_IMPLEMENTATION
+
+#define OPTPARSE_MSG_INVALID "invalid option"
+#define OPTPARSE_MSG_MISSING "option requires an argument"
+#define OPTPARSE_MSG_TOOMANY "option takes no arguments"
+
+static int optparse_error(struct optparse *options, const char *msg, const char *data)
+{
+    unsigned p = 0;
+    const char *sep = " -- '";
+    while (*msg)
+        options->errmsg[p++] = *msg++;
+    while (*sep)
+        options->errmsg[p++] = *sep++;
+    while (p < sizeof(options->errmsg) - 2 && *data)
+        options->errmsg[p++] = *data++;
+    options->errmsg[p++] = '\'';
+    options->errmsg[p++] = '\0';
+    return '?';
+}
+
+OPTPARSE_API
+void optparse_init(struct optparse *options, char **argv)
+{
+    options->argv = argv;
+    options->permute = 1;
+    options->optind = argv[0] != 0;
+    options->subopt = 0;
+    options->optarg = 0;
+    options->errmsg[0] = '\0';
+}
+
+static int optparse_is_dashdash(const char *arg)
+{
+    return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] == '\0';
+}
+
+static int optparse_is_shortopt(const char *arg)
+{
+    return arg != 0 && arg[0] == '-' && arg[1] != '-' && arg[1] != '\0';
+}
+
+static int optparse_is_longopt(const char *arg)
+{
+    return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] != '\0';
+}
+
+static void optparse_permute(struct optparse *options, int index)
+{
+    char *nonoption = options->argv[index];
+    int i;
+    for (i = index; i < options->optind - 1; i++)
+        options->argv[i] = options->argv[i + 1];
+    options->argv[options->optind - 1] = nonoption;
+}
+
+static int optparse_argtype(const char *optstring, char c)
+{
+    int count = OPTPARSE_NONE;
+    if (c == ':')
+        return -1;
+    for (; *optstring && c != *optstring; optstring++);
+    if (!*optstring)
+        return -1;
+    if (optstring[1] == ':')
+        count += optstring[2] == ':' ? 2 : 1;
+    return count;
+}
+
+OPTPARSE_API
+int optparse(struct optparse *options, const char *optstring)
+{
+    int type;
+    char *next;
+    char *option = options->argv[options->optind];
+    options->errmsg[0] = '\0';
+    options->optopt = 0;
+    options->optarg = 0;
+    if (option == 0) {
+        return -1;
+    } else if (optparse_is_dashdash(option)) {
+        options->optind++; /* consume "--" */
+        return -1;
+    } else if (!optparse_is_shortopt(option)) {
+        if (options->permute) {
+            int index = options->optind++;
+            int r = optparse(options, optstring);
+            optparse_permute(options, index);
+            options->optind--;
+            return r;
+        } else {
+            return -1;
+        }
+    }
+    option += options->subopt + 1;
+    options->optopt = option[0];
+    type = optparse_argtype(optstring, option[0]);
+    next = options->argv[options->optind + 1];
+    switch (type) {
+    case -1: {
+        char str[2] = {0, 0};
+        str[0] = option[0];
+        options->optind++;
+        return optparse_error(options, OPTPARSE_MSG_INVALID, str);
+    }
+    case OPTPARSE_NONE:
+        if (option[1]) {
+            options->subopt++;
+        } else {
+            options->subopt = 0;
+            options->optind++;
+        }
+        return option[0];
+    case OPTPARSE_REQUIRED:
+        options->subopt = 0;
+        options->optind++;
+        if (option[1]) {
+            options->optarg = option + 1;
+        } else if (next != 0) {
+            options->optarg = next;
+            options->optind++;
+        } else {
+            char str[2] = {0, 0};
+            str[0] = option[0];
+            options->optarg = 0;
+            return optparse_error(options, OPTPARSE_MSG_MISSING, str);
+        }
+        return option[0];
+    case OPTPARSE_OPTIONAL:
+        options->subopt = 0;
+        options->optind++;
+        if (option[1])
+            options->optarg = option + 1;
+        else
+            options->optarg = 0;
+        return option[0];
+    }
+    return 0;
+}
+
+OPTPARSE_API
+char *optparse_arg(struct optparse *options)
+{
+    char *option = options->argv[options->optind];
+    options->subopt = 0;
+    if (option != 0)
+        options->optind++;
+    return option;
+}
+
+static int optparse_longopts_end(const struct optparse_long *longopts, int i)
+{
+    return !longopts[i].longname && !longopts[i].shortname;
+}
+
+static void optparse_from_long(const struct optparse_long *longopts, char *optstring)
+{
+    char *p = optstring;
+    int i;
+    for (i = 0; !optparse_longopts_end(longopts, i); i++) {
+        if (longopts[i].shortname && longopts[i].shortname < 127) {
+            int a;
+            *p++ = longopts[i].shortname;
+            for (a = 0; a < (int)longopts[i].argtype; a++)
+                *p++ = ':';
+        }
+    }
+    *p = '\0';
+}
+
+/* Unlike strcmp(), handles options containing "=". */
+static int optparse_longopts_match(const char *longname, const char *option)
+{
+    const char *a = option, *n = longname;
+    if (longname == 0)
+        return 0;
+    for (; *a && *n && *a != '='; a++, n++)
+        if (*a != *n)
+            return 0;
+    return *n == '\0' && (*a == '\0' || *a == '=');
+}
+
+/* Return the part after "=", or NULL. */
+static char *optparse_longopts_arg(char *option)
+{
+    for (; *option && *option != '='; option++);
+    if (*option == '=')
+        return option + 1;
+    else
+        return 0;
+}
+
+static int optparse_long_fallback(struct optparse *options,
+                                  const struct optparse_long *longopts,
+                                  int *longindex)
+{
+    int result;
+    char optstring[96 * 3 + 1]; /* 96 ASCII printable characters */
+    optparse_from_long(longopts, optstring);
+    result = optparse(options, optstring);
+    if (longindex != 0) {
+        *longindex = -1;
+        if (result != -1) {
+            int i;
+            for (i = 0; !optparse_longopts_end(longopts, i); i++)
+                if (longopts[i].shortname == options->optopt)
+                    *longindex = i;
+        }
+    }
+    return result;
+}
+
+OPTPARSE_API
+int optparse_long(struct optparse *options,
+                  const struct optparse_long *longopts,
+                  int *longindex)
+{
+    int i;
+    char *option = options->argv[options->optind];
+    if (option == 0) {
+        return -1;
+    } else if (optparse_is_dashdash(option)) {
+        options->optind++; /* consume "--" */
+        return -1;
+    } else if (optparse_is_shortopt(option)) {
+        return optparse_long_fallback(options, longopts, longindex);
+    } else if (!optparse_is_longopt(option)) {
+        if (options->permute) {
+            int index = options->optind++;
+            int r = optparse_long(options, longopts, longindex);
+            optparse_permute(options, index);
+            options->optind--;
+            return r;
+        } else {
+            return -1;
+        }
+    }
+
+    /* Parse as long option. */
+    options->errmsg[0] = '\0';
+    options->optopt = 0;
+    options->optarg = 0;
+    option += 2; /* skip "--" */
+    options->optind++;
+    for (i = 0; !optparse_longopts_end(longopts, i); i++) {
+        const char *name = longopts[i].longname;
+        if (optparse_longopts_match(name, option)) {
+            char *arg;
+            if (longindex)
+                *longindex = i;
+            options->optopt = longopts[i].shortname;
+            arg = optparse_longopts_arg(option);
+            if (longopts[i].argtype == OPTPARSE_NONE && arg != 0) {
+                return optparse_error(options, OPTPARSE_MSG_TOOMANY, name);
+            }
+            if (arg != 0) {
+                options->optarg = arg;
+            } else if (longopts[i].argtype == OPTPARSE_REQUIRED) {
+                options->optarg = options->argv[options->optind];
+                if (options->optarg == 0)
+                    return optparse_error(options, OPTPARSE_MSG_MISSING, name);
+                else
+                    options->optind++;
+            }
+            return options->optopt;
+        }
+    }
+    return optparse_error(options, OPTPARSE_MSG_INVALID, option);
+}
+
+#endif /* OPTPARSE_IMPLEMENTATION */
+#endif /* OPTPARSE_H */
diff --git a/load.c b/load.c
deleted file mode 100644
index b3d154d794c35f96baeb64ae61d447400633cbc0..0000000000000000000000000000000000000000
--- a/load.c
+++ /dev/null
@@ -1,70 +0,0 @@
-/*******************************************************
- Copyright (C) 2019-2021 Georges Da Costa <georges.da-costa@irit.fr>
-
-    This file is part of Mojitos.
-
-    Mojitos is free software: you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation, either version 3 of the License, or
-    (at your option) any later version.
-
-    Mojitos is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with MojitO/S.  If not, see <https://www.gnu.org/licenses/>.
-
- *******************************************************/
-#include <unistd.h>
-#include <fcntl.h>
-#include <stdlib.h>
-#include <stdint.h>
-#include <string.h>
-
-#define LOAD_BUFFER_SIZE 1024
-char buffer[LOAD_BUFFER_SIZE];
-
-static int load_fid=-1;
-static uint64_t load_values[10]={0,0,0,0,0,0,0,0,0,0};
-static uint64_t tmp_load_values[10]={0,0,0,0,0,0,0,0,0,0};
-
-void _get_load(uint64_t* results) {
-  pread(load_fid, buffer, LOAD_BUFFER_SIZE-1, 0);
-  int pos=0;
-  while(buffer[pos] > '9' || buffer[pos] < '0') pos++;
-  for(int i=0; i<10; i++) {
-    results[i] = strtoull(buffer+pos, NULL, 10);
-    while(buffer[pos] <= '9' && buffer[pos] >= '0') pos++;
-    pos++;
-  }
-}
-
-// Public interface
-
-unsigned int init_load(char* argument, void **state) {
-  load_fid = open("/proc/stat", O_RDONLY);
-  _get_load(load_values);
-  return 10;
-}
-
-unsigned int get_load(uint64_t* results, void* state) {
-  _get_load(tmp_load_values);
-  for(int i=0; i<10; i++)
-    results[i] = tmp_load_values[i] - load_values[i];
-
-  memcpy(load_values, tmp_load_values, sizeof(load_values));
-  return 10;
-}
-
-void clean_load(void *state) {
-  close(load_fid);
-}
-
-char *_labels[10] = {"user","nice","system","idle","iowait","irq",
-  "softirq","steal","guest","guest_nice"};
-void label_load(char **labels, void*none) {
-  for(int i=0; i<10; i++)
-    labels[i] = _labels[i];
-}
diff --git a/makefile b/makefile
index 433b76f63d171d59b4965f8981f9cfd262d80e11..a197961099a277e4e12d6d442236b2fd5327d14c 100644
--- a/makefile
+++ b/makefile
@@ -1,30 +1,77 @@
-all: mojitos
+.POSIX:
 
-OBJECTS = mojitos.o counters_individual.o rapl.o frapl.o network.o load.o infiniband.o temperature.o
+SRC_DIR = src
+DOC_DIR = doc
+OBJ_DIR = obj
+BIN_DIR = bin
+TESTS_DIR = tests
 
-mojitos:$(OBJECTS)
-	gcc $(DEBUG) -O3 -Wall -o mojitos $(OBJECTS) -lpowercap
+BIN = mojitos
 
-OBJECTS_GRP = $(subst _individual,_group, $(OBJECTS))
-mojitos_group: $(OBJECTS_GRP) counters_option.h
-	gcc $(DEBUG) -O3 -Wall -o mojitos_group $(OBJECTS_GRP) -lpowercap
+CAPTOR_OBJ =
 
-counters_%.o: counters_%.c counters.h counters_option.h
-	gcc $(DEBUG) -c -O3 -Wall $< -o $@
+include ./sensors.mk
 
-counters_option.h: counters_option.py
-	./counters_option.py > counters_option.h
+OBJ =  \
+	$(CAPTOR_OBJ) \
+	$(OBJ_DIR)/util.o
 
+CC = gcc
+CPPFLAGS = -std=gnu99 -Wall -Wextra -Wpedantic -Wno-unused-function -I./lib
+CFLAGS = $(CPPFLAGS) -O3 -Werror
+LDFLAGS =
 
-mojitos.o: mojitos.c counters_option.h
-	gcc $(DEBUG) -c -O3 -Wall $< -o $@
+ASTYLE = astyle --style=kr -xf -s4 -k3 -n -Z -Q
 
-debug: DEBUG = -DDEBUG -g
 
-debug: all
+all: $(BIN) man
 
-%.o : %.c %.h
-	gcc $(DEBUG) -c -O3 -Wall $< -o $@
+$(BIN): $(BIN_DIR) $(OBJ) $(OBJ_DIR)/$(BIN).o
+	$(CC) $(LDFLAGS) -o $(BIN_DIR)/$(BIN) $(OBJ) $(OBJ_DIR)/$(BIN).o
+
+$(OBJ): $(OBJ_DIR)
+$(OBJ_DIR)/counters.o: $(SRC_DIR)/counters_option.h
+
+$(OBJ_DIR)/$(BIN).o: $(SRC_DIR)/$(BIN).c $(SRC_DIR)/counters_option.h
+	$(CC) $(CFLAGS) -c $< -o $@
+
+$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c $(SRC_DIR)/%.h
+	$(CC) $(CFLAGS) -c $< -o $@
+
+$(SRC_DIR)/counters_option.h: $(SRC_DIR)/counters_option.sh
+	sh ./$(SRC_DIR)/counters_option.sh > $(SRC_DIR)/counters_option.h
+
+$(OBJ_DIR):
+	mkdir -p $(OBJ_DIR)
+
+$(BIN_DIR):
+	mkdir -p $(BIN_DIR)
+
+debug: CFLAGS = $(CPPFLAGS) -DDEBUG -g -Og
+debug: $(BIN)
+
+tests:
+	$(CC) $(CPPFLAGS) $(TESTS_DIR)/main.c $(SRC_DIR)/util.c -o $(TESTS_DIR)/run
+	$(TESTS_DIR)/run
+
+format:
+	$(ASTYLE) $(SRC_DIR)/*.[ch] \
+		$(DOC_DIR)/*.[ch] \
+		$(TESTS_DIR)/*.[ch]
 
 clean:
-	\rm -f *~ *.o mojitos_group mojitos counters_option.h
+	\rm -f $(OBJ_DIR)/* $(BIN_DIR)/* \
+		$(SRC_DIR)/counters_option.h \
+		$(TESTS_DIR)/run \
+		$(DOC_DIR)/test_main_ex \
+		$(DOC_DIR)/info_reader_ex
+
+readme: $(BIN)
+	sh ./tools/update-readme-usage.sh
+
+man: $(BIN)
+	awk -v "usage=$$($(BIN_DIR)/$(BIN) --dump-opts)" \
+		'/^USAGE/ { $$0=usage } 1' \
+		doc/$(BIN).pre.1 > doc/$(BIN).1 2>/dev/null
+
+.PHONY: all clean mojitos debug format tests readme man
diff --git a/mojitos b/mojitos
deleted file mode 100755
index ab3f7b4824740935e193e4d9efee35d0f20849fa..0000000000000000000000000000000000000000
Binary files a/mojitos and /dev/null differ
diff --git a/mojitos.c b/mojitos.c
deleted file mode 100644
index e80e692f5a4498e02e60761cee1a2d568860c314..0000000000000000000000000000000000000000
--- a/mojitos.c
+++ /dev/null
@@ -1,259 +0,0 @@
-/*******************************************************
- Copyright (C) 2018-2019 Georges Da Costa <georges.da-costa@irit.fr>
-
-    This file is part of Mojitos.
-
-    Mojitos is free software: you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation, either version 3 of the License, or
-    (at your option) any later version.
-
-    Mojitos is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with MojitO/S.  If not, see <https://www.gnu.org/licenses/>.
-
- *******************************************************/
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <time.h>
-#include <signal.h>
-#include <inttypes.h>
-#include <unistd.h>
-
-#include "counters.h"
-#include "rapl.h"
-#include "frapl.h"
-#include "network.h"
-#include "infiniband.h"
-#include "load.h"
-#include "temperature.h"
-
-
-
-void usage(char** argv) {
-  printf("Usage : %s [-t time] [-f freq] [-r] [-p perf_list] [-l] [-u] [-c] [-d network_device] [-i infiniband_path] [-o logfile] [-e command arguments...]\n", argv[0]);
-  printf("if time==0 then loops infinitively\n");
-  printf("if -e is present, time and freq are not used\n");
-  printf("-r activates RAPL\n");
-  printf("-R activates the file version of RAPL\n");
-  printf("-p activates performance counters\n");
-  printf("   perf_list is coma separated list of performance counters without space. Ex: instructions,cache_misses\n");
-  printf("-l lists the possible performance counters and quits\n");
-  printf("-d activates network monitoring (if network_device is X, tries to detect it automatically)\n");
-  printf("-i activates infiniband monitoring (if infiniband_path is X, tries to detect it automatically)\n");
-  printf("-s activates statistics of overhead in nanoseconds\n");
-  printf("-u activates report of system load\n");
-  printf("-c activates report of processor temperature\n");
-  exit(EXIT_SUCCESS);
-}
-
-void sighandler(int none) {
-}
-
-void flush(int none) {
-  exit(0);
-}
-
-FILE *output;
-void flushexit() {
-  fflush(output);
-  fclose(output);
-}
-
-typedef unsigned int (initializer_t)(char*, void **);
-typedef void (labeler_t)(char **, void*);
-typedef unsigned int (*getter_t)(uint64_t*, void*);
-typedef void (*cleaner_t)(void*);
-
-unsigned int nb_sources=0;
-void **states=NULL;
-getter_t *getter=NULL;
-cleaner_t *cleaner=NULL;
-
-unsigned int nb_sensors=0;
-char **labels=NULL;
-uint64_t *values=NULL;
-
-void add_source(initializer_t init, char* arg, labeler_t labeler,
-		getter_t get, cleaner_t clean) {
-    nb_sources++;
-    states = realloc(states, nb_sources*sizeof(void*));
-    int nb = init(arg, &states[nb_sources-1]);
-    if (nb == 0) {
-      nb_sources--;
-      states = realloc(states, nb_sources*sizeof(void*));
-      return;
-    }
-
-    getter = realloc(getter, nb_sources*sizeof(void*));
-    getter[nb_sources-1] = get;
-    cleaner = realloc(cleaner, nb_sources*sizeof(void*));
-    cleaner[nb_sources-1] = clean;
-
-    labels = realloc(labels, (nb_sensors+nb)*sizeof(char*));
-    labeler(labels+nb_sensors, states[nb_sources-1]);
-    
-    values = realloc(values, (nb_sensors+nb)*sizeof(uint64_t));
-    nb_sensors += nb;
-}
-
-int main(int argc, char **argv) {
-  int total_time=1;
-  int delta=0;
-  int frequency=1;
-  char **application = NULL;
-
-  int stat_mode = -1;
-
-  if(argc==1)
-    usage(argv);
-  
-  output=stdout;
-  
-  atexit(flushexit);
-  signal(15, flush);
-  
-  int c;
-  while ((c = getopt (argc, argv, "ilhcftdeoprRsu")) != -1 && application==NULL)
-    switch (c) {
-    case 'f':
-      frequency=atoi(argv[optind]);
-      break;
-    case 't':
-      total_time=atoi(argv[optind]);
-      delta=1;
-      if(total_time==0) {
-	total_time=1;
-	delta=0;
-      }
-      break;
-    case 'd':
-      add_source(init_network, argv[optind], label_network, get_network, clean_network);
-      break;
-    case 'i':
-      add_source(init_infiniband, argv[optind], label_infiniband, get_network, clean_network);
-      break;
-    case 'o':
-      output = fopen(argv[optind],"wb");
-      break;
-    case 'e':
-      application=&argv[optind];
-      signal(17,sighandler);
-      break;
-    case 'p':
-      add_source(init_counters, argv[optind], label_counters, get_counters, clean_counters);
-      break;
-    case 'r':
-      add_source(init_rapl, NULL, label_rapl, get_rapl, clean_rapl);
-      break;
-    case 'R':
-      add_source(init_frapl, NULL, label_frapl, get_frapl, clean_frapl);
-      break;
-    case 'u':
-      add_source(init_load, NULL, label_load, get_load, clean_load);
-      break;
-    case 'c':
-      add_source(init_temperature, NULL, label_temperature, get_temperature, clean_temperature);
-      break;
-    case 's':
-      stat_mode=0;
-      break;
-    case 'l':
-      show_all_counters();
-      exit(EXIT_SUCCESS);
-    default:
-      usage(argv);
-    }
-
-
-  setvbuf(output, NULL, _IONBF, BUFSIZ);
-  struct timespec ts;
-  struct timespec ts_ref;
-
-  fprintf(output, "#timestamp ");
-
-  for(int i=0; i<nb_sensors; i++)
-    fprintf(output, "%s ", labels[i]);
-
-  if(stat_mode==0)
-    fprintf(output, "overhead ");
-
-  fprintf(output, "\n");
-
-  unsigned long int stat_data=0;
-
-  for (int temps = 0; temps <total_time*frequency; temps+=delta) {
-    clock_gettime(CLOCK_MONOTONIC, &ts_ref);
-
-    // Get Data
-    unsigned int current = 0;
-    for(int i=0; i<nb_sources; i++)
-      current += getter[i](&values[current], states[i]);
-    
-    if(application != NULL) {
-
-      if(fork()==0){
-	execvp(application[0], application);
-	exit(0);
-      }
-      pause();
-      clock_gettime(CLOCK_MONOTONIC, &ts);
-      if(ts.tv_nsec >= ts_ref.tv_nsec)
-	fprintf(output, "%ld.%09ld ", (ts.tv_sec-ts_ref.tv_sec), ts.tv_nsec-ts_ref.tv_nsec);
-      else
-	fprintf(output, "%ld.%09ld ", (ts.tv_sec-ts_ref.tv_sec)-1,1000000000+ts.tv_nsec-ts_ref.tv_nsec);
-    }
-    else {
-#ifdef DEBUG
-      clock_gettime(CLOCK_MONOTONIC, &ts);
-      fprintf(stderr, "%ld\n", (ts.tv_nsec-ts_ref.tv_nsec)/1000);
-      //Indiv: mean: 148 std: 31 % med: 141 std: 28 %
-      //Group: mean: 309 std: 41 % med: 297 std: 39 %
-#endif
-      if(stat_mode==0) {
-	clock_gettime(CLOCK_MONOTONIC, &ts);
-      if(ts.tv_nsec >= ts_ref.tv_nsec)
-	stat_data = ts.tv_nsec-ts_ref.tv_nsec;
-      else
-	stat_data = 1000000000+ts.tv_nsec-ts_ref.tv_nsec;
-    }
-    
-      // Treat Data
-      fprintf(output, "%ld.%09ld ", ts_ref.tv_sec, ts_ref.tv_nsec);
-    }
-    
-    for(int i=0; i<nb_sensors; i++)
-      fprintf(output, "%" PRIu64 " ", values[i]);
-
-    if(stat_mode==0)
-      fprintf(output, "%ld ", stat_data);
-
-    fprintf(output, "\n");
-
-    if(application != NULL)
-      break;
-
-    clock_gettime(CLOCK_MONOTONIC, &ts);
-    usleep(1000*1000/frequency-(ts.tv_nsec/1000)%(1000*1000/frequency));
-  }
-
-  for(int i=0; i<nb_sources;i++)
-    cleaner[i](states[i]);
-
-  if(nb_sources > 0) {
-    free(getter);
-    free(cleaner);
-    free(labels);
-    free(values);
-    free(states);
-  }
-}
-
-
-
-
diff --git a/network.c b/network.c
deleted file mode 100644
index 6e3f494bd49b86c68e452efd202873821b9c6832..0000000000000000000000000000000000000000
--- a/network.c
+++ /dev/null
@@ -1,103 +0,0 @@
-/*******************************************************
- Copyright (C) 2018-2019 Georges Da Costa <georges.da-costa@irit.fr>
-
-    This file is part of Mojitos.
-
-    Mojitos is free software: you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation, either version 3 of the License, or
-    (at your option) any later version.
-
-    Mojitos is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with MojitO/S.  If not, see <https://www.gnu.org/licenses/>.
-
- *******************************************************/
-#include <unistd.h>
-#include <fcntl.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <stdint.h>
-
-struct network_t {
-  uint64_t values[4];
-  uint64_t tmp_values[4];
-  int sources[4];
-};
-
-unsigned int _get_network(uint64_t* results, int* sources) {
-  if(sources==NULL)
-    return 0;
-  char buffer[128];
-  for(int i=0; i<4; i++){
-    pread(sources[i], buffer, 127, 0);
-
-    results[i] = strtoull(buffer, NULL, 10);
-  }
-  return 4;
-}
-
-
-
-unsigned int init_network(char* dev, void**ptr) {
-  if(dev==NULL)
-    return 0;
-
-  if(strcmp(dev,"X")==0) {
-    int f = open("/proc/net/route", O_RDONLY);
-    char buffer[1000];
-    read(f, buffer, 999);
-    char *start_of_dev = index(buffer, '\n')+1;
-    char *end_of_dev = index(start_of_dev, '\t');
-    *end_of_dev='\0';
-    dev = start_of_dev;
-    close(f);
-  }
-  
-  char *filenames[] = {"/sys/class/net/%s/statistics/rx_packets",
-		       "/sys/class/net/%s/statistics/rx_bytes",
-		       "/sys/class/net/%s/statistics/tx_packets",
-		       "/sys/class/net/%s/statistics/tx_bytes"};
-
-  struct network_t *state = malloc(sizeof(struct network_t));
-
-  char buffer2[256];
-  for(int i=0; i<4; i++) {
-    sprintf(buffer2, filenames[i], dev);
-    state->sources[i] = open(buffer2, O_RDONLY);
-  }
-  *ptr = (void*) state;
-  _get_network(state->values, state->sources);
-
-  return 4;
-}
-
-unsigned int get_network(uint64_t* results, void* ptr) {
-  struct network_t *state = (struct network_t *) ptr;
-  _get_network(state->tmp_values, state->sources);
-  for(int i=0; i<4; i++)
-    results[i] = state->tmp_values[i] - state->values[i];
-
-  memcpy(state->values, state->tmp_values, 4*sizeof(uint64_t));
-  return 4;
-}
-
-void clean_network(void *ptr) {
-  struct network_t *state = (struct network_t *) ptr;
-  if(state==NULL)
-    return;
-  for(int i=0;i<4;i++)
-    close(state->sources[i]);
-  free(state);
-}    
-
-char *_labels_network[4] = {"rxp", "rxb", "txp", "txb"};
-void label_network(char **labels, void*none) {
-  for(int i=0; i<4; i++)
-    labels[i] = _labels_network[i];
-}
diff --git a/rapl.c b/rapl.c
deleted file mode 100644
index 14b4a58ce302d1251680540155fb372d98acec46..0000000000000000000000000000000000000000
--- a/rapl.c
+++ /dev/null
@@ -1,166 +0,0 @@
-/*******************************************************
- Copyright (C) 2018-2019 Georges Da Costa <georges.da-costa@irit.fr>
-
-    This file is part of Mojitos.
-
-    Mojitos is free software: you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation, either version 3 of the License, or
-    (at your option) any later version.
-
-    Mojitos is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with MojitO/S.  If not, see <https://www.gnu.org/licenses/>.
-
- *******************************************************/
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <powercap/powercap-rapl.h>
-
-struct _rapl_t {
-  powercap_rapl_pkg* pkgs;
-  uint32_t nb_pkgs;
-  uint32_t nb;
-  char **names;
-  uint32_t* zones;
-  uint32_t* packages;
-  uint64_t* values;
-  uint64_t* tmp_values;
-};
-
-typedef struct _rapl_t _rapl_t;
-
-
-
-
-
-
-const int nb_zones = 3;
-const int rapl_zones[3] = { POWERCAP_RAPL_ZONE_PACKAGE,   POWERCAP_RAPL_ZONE_CORE,   POWERCAP_RAPL_ZONE_DRAM};
-
-
-#define MAX_LEN_NAME 100
-
-// values [zone + package *nbzones] microjoules
-void _get_rapl(uint64_t *values, _rapl_t* rapl) {
-  for (int i = 0; i < rapl->nb; i++) {
-#ifdef DEBUG
-    int ret =
-#endif
-      powercap_rapl_get_energy_uj(&rapl->pkgs[rapl->packages[i]],
-				rapl->zones[i],
-				&values[i]);
-#ifdef DEBUG
-    printf("GETRAPL: package %d, zone %d, name %s, ret: %d\n", rapl->packages[i], rapl->zones[i], rapl->names[i], ret);
-#endif
-  }
-}
-
-unsigned int init_rapl(char* none, void **ptr) {
-  // get number of processor sockets
-  _rapl_t* rapl= malloc(sizeof(struct _rapl_t));
-  rapl->nb = 0;
-  rapl->packages = NULL;
-  rapl->zones = NULL;
-
-  rapl->nb_pkgs = powercap_rapl_get_num_instances();
-  //rapl->nb_pkgs = powercap_rapl_get_num_packages();
-  
-  if (rapl->nb_pkgs == 0) {
-    perror("no packages found (maybe the kernel module isn't loaded?)");
-    exit(-1);
-  }
-  rapl->pkgs = malloc(rapl->nb_pkgs * sizeof(powercap_rapl_pkg));  
-  for (int package = 0; package < rapl->nb_pkgs; package++)
-    if (powercap_rapl_init(package, &rapl->pkgs[package], 0)) {
-      perror("powercap_rapl_init, check access (root needed ?)");
-      exit(-1);
-    }
-
-  rapl->names = NULL;
-    
-  char _name[MAX_LEN_NAME+1];
-  char _name2[MAX_LEN_NAME+11];
-
-  for (unsigned int package = 0; package < rapl->nb_pkgs; package++) {
-    for(unsigned int zone=0; zone < nb_zones; zone++) {
-      int length=powercap_rapl_get_name(&rapl->pkgs[package], rapl_zones[zone],
-			     _name, MAX_LEN_NAME);
-      if (length>0) {
-
-	sprintf(_name2, "%s%u", _name, package);
-
-	rapl->nb++;
-	rapl->names = realloc(rapl->names, sizeof(char*)*rapl->nb);
-	rapl->names[rapl->nb-1] = malloc(sizeof(char) * (strlen(_name2)+1));
-	rapl->zones = realloc(rapl->zones, sizeof(uint32_t)*rapl->nb);
-	rapl->packages = realloc(rapl->packages, sizeof(uint32_t)*rapl->nb);
-	
-	strcpy(rapl->names[rapl->nb-1], _name2);
-	rapl->zones[rapl->nb-1] = rapl_zones[zone];
-	rapl->packages[rapl->nb-1] = package;
-      }
-#ifdef DEBUG
-      printf("%d %d %d %d %s\n\n", length, package, zone, rapl_zones[zone], _name2);
-#endif
-    }
-  }
-#ifdef DEBUG
-  printf("Result of init\n");
-  for(int i=0; i<rapl->nb; i++)
-    printf("package %d, zone %d, name %s\n", rapl->packages[i], rapl->zones[i], rapl->names[i]);
-#endif
-
-  rapl->values = calloc(sizeof(uint64_t), rapl->nb);
-  rapl->tmp_values = calloc(sizeof(uint64_t), rapl->nb);
-
-  _get_rapl(rapl->values, rapl);
-
-  *ptr = (void*)rapl;
-  return rapl->nb;
-}
-
-
-
-unsigned int get_rapl(uint64_t* results, void* ptr) {
-  _rapl_t* state = (_rapl_t*) ptr;
-  _get_rapl(state->tmp_values, state);
-  for(int i=0; i<state->nb; i++)
-    results[i] = state->tmp_values[i] - state->values[i];
-
-  memcpy(state->values, state->tmp_values, sizeof(uint64_t)*state->nb);
-  return state->nb;
-}
-
-
-
-
-void clean_rapl(void* ptr) {
-  _rapl_t* rapl = (_rapl_t*) ptr;
-  for (int package = 0; package < rapl->nb_pkgs; package++)
-    if (powercap_rapl_destroy(&rapl->pkgs[package]))
-      perror("powercap_rapl_destroy");
-  for (int elem=0; elem<rapl->nb; elem++) 
-      free(rapl->names[elem]);
-  
-  free(rapl->names);
-  free(rapl->pkgs);
-  free(rapl->zones);
-  free(rapl->packages);
-  free(rapl->values);
-  free(rapl->tmp_values);
-  free(rapl);
-}
-
-void label_rapl(char **labels, void *ptr) {
-  _rapl_t* rapl = (_rapl_t*) ptr;
-  for(int i=0; i<rapl->nb; i++)
-    labels[i] = rapl->names[i];
-}
diff --git a/src/amd_rapl.c b/src/amd_rapl.c
new file mode 100644
index 0000000000000000000000000000000000000000..1e08517d7d2f66c5b5404e973f13aed5f366d390
--- /dev/null
+++ b/src/amd_rapl.c
@@ -0,0 +1,361 @@
+/*******************************************************
+ Copyright (C) 2023-2023 Georges Da Costa <georges.da-costa@irit.fr>
+
+    This file is part of Mojitos.
+
+    Mojitos is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Mojitos is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with MojitO/S.  If not, see <https://www.gnu.org/licenses/>.
+
+*******************************************************/
+
+#include <stdio.h>
+#include <stdint.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#include "info_reader.h"
+#include "util.h"
+
+#define BUFFER_SIZE 64
+#define __READ_CPUINFO__
+
+// ---------------------------MSR_REGISTERS
+static const uint64_t amd_energy_mask = 0xFFFFFFFF;
+static const uint64_t amd_energy_unit_mask = 0x01F00;
+static const uint64_t msr_rapl_power_unit = 0xC0010299;
+static const uint64_t energy_core_msr = 0xC001029A;
+
+// ------------------------------FILE_PATHS
+static const char *base_str = "/dev/cpu/%d/msr";
+
+struct CpuSensor {
+    unsigned int cpu_id;
+    unsigned int package_id;
+    unsigned int core_id;
+
+    char *name;
+    int fd;
+
+    unsigned int energy_units;
+    uint64_t core_energy;
+};
+typedef struct CpuSensor CpuSensor;
+
+struct AmdRapl {
+    CpuSensor *sensors;
+    unsigned int sensor_count;
+};
+typedef struct AmdRapl AmdRapl;
+
+// -----------------------------INFO_READER
+
+#ifdef __READ_CPUINFO__
+#define NB_KEYS 3
+
+static char *cpuinfo = "/proc/cpuinfo";
+
+static GenericPointer uint_allocator(char *s)
+{
+    unsigned int value = atoi(s);
+    return (GenericPointer) value;
+}
+
+static void _set_cpu_id(GenericPointer storage, GenericPointer data)
+{
+    CpuSensor *cpu = (CpuSensor *) storage;
+    cpu->cpu_id = (unsigned int) data;
+}
+
+static void _set_package_id(GenericPointer storage, GenericPointer data)
+{
+    CpuSensor *cpu = (CpuSensor *) storage;
+    cpu->package_id = (unsigned int) data;
+}
+
+static void _set_core_id(GenericPointer storage, GenericPointer data)
+{
+    CpuSensor *cpu = (CpuSensor *) storage;
+    cpu->core_id = (unsigned int) data;
+}
+
+static KeyFinder keys[NB_KEYS] = {
+    {"processor", ": ", uint_allocator, _set_cpu_id},
+    {"physical id", ": ", uint_allocator, _set_package_id},
+    {"core id", ": ", uint_allocator, _set_core_id}
+};
+
+
+static unsigned int parse_cpuinfo(CpuSensor *storage, unsigned int capacity)
+{
+    Parser parser = {
+        .storage = (GenericPointer) storage,
+        .nb_stored = 0,
+        .capacity = capacity,
+        .storage_struct_size = sizeof(CpuSensor),
+        .keys = keys,
+        .nb_keys = NB_KEYS,
+        .file = fopen(cpuinfo, "r")
+    };
+    return parse(&parser);
+}
+
+#endif
+
+// --------------------------------READ_MSR
+
+uint64_t read_msr(int fd, uint64_t msr)
+{
+    uint64_t data;
+    if (pread(fd, &data, sizeof data, msr) != sizeof data) {
+        fprintf(stderr, "read_msr(%ld):", msr);
+        perror("pread");
+        exit(127);
+    }
+    return data;
+}
+
+unsigned int read_unit(int fd)
+{
+    uint64_t unit = read_msr(fd, msr_rapl_power_unit);
+    return ((unit & amd_energy_unit_mask) >> 8);
+}
+
+uint64_t read_raw_core_energy(int fd)
+{
+    uint64_t energy = read_msr(fd, energy_core_msr);
+    return energy & amd_energy_mask;
+}
+
+// -------------------------READ_PKG_ENERGY
+
+#ifdef __READ_PKG_ENERGY__
+// TODO: Verify if these functions are still useful (the package energy can be calculed)
+
+static const uint64_t energy_pkg_msr = 0xC001029B;
+uint64_t read_raw_pkg_energy(int fd)
+{
+    uint64_t energy = read_msr(fd, energy_pkg_msr);
+    return energy & amd_energy_mask;
+}
+#endif
+
+// ----------------------------------ENERGY
+
+uint64_t raw_to_microjoule(uint64_t raw, unsigned int unit)
+{
+    static const double to_microjoule = 1000000.0;
+    // raw * (1 / (unit^2)) -> Joule
+    // Joule * 1000000 -> uJoule
+    return (uint64_t) (((double) raw * to_microjoule) / (double)(1U << unit));
+}
+
+uint64_t raw_to_joule(uint64_t raw, uint64_t unit)
+{
+    // raw * (1 / (unit^2)) -> Joule
+    int64_t joule = raw / (1UL << unit);
+    return joule;
+}
+
+// -----------------------------------DEBUG
+
+#ifdef DEBUG
+void debug_print_sensor(CpuSensor *sensor)
+{
+    //CASSERT(sizeof(CpuSensor) == 56, amd_rapl_c);
+    printf("cpu_id : %d, package_id : %d, core_id : %d, name : %s, fd: %d,  energy_units : %d, core_energy: %ld\n",
+           sensor->cpu_id,
+           sensor->package_id,
+           sensor->core_id,
+           sensor->name,
+           sensor->fd,
+           sensor->energy_units,
+           sensor->core_energy
+          );
+}
+
+void debug_print_amd_rapl(AmdRapl *rapl)
+{
+    for (unsigned int i = 0; i < rapl->sensor_count; i++) {
+        debug_print_sensor(&rapl->sensors[i]);
+    }
+}
+
+#endif
+
+// ---------------------------AMD_RAPL_UTIL
+
+unsigned int get_nb_cpu()
+{
+    char filename[BUFFER_SIZE];
+
+    unsigned int n_cpu = 0;
+    for (;; n_cpu++) {
+        snprintf(filename, BUFFER_SIZE, base_str, n_cpu);
+
+        int fd = open(filename, O_RDONLY);
+        if (fd < 0) {
+            break;
+        }
+        close(fd);
+    }
+    return n_cpu;
+}
+
+void get_arch(unsigned int *ret_nb_package, unsigned int *ret_nb_core, CpuSensor *sensors, unsigned int nb_sensor)
+{
+    unsigned int nb_package = 0;
+    unsigned int nb_core = 0;
+    for (unsigned int i = 0; i < nb_sensor; i++) {
+        if (sensors[i].package_id > nb_package) {
+            nb_package = sensors[i].package_id;
+        }
+        if (sensors[i].core_id > nb_core) {
+            nb_core = sensors[i].core_id;
+        }
+    }
+    *ret_nb_package = nb_package + 1;
+    *ret_nb_core = nb_core + 1;
+}
+
+char *get_name(unsigned int cpu_id)
+{
+    static const char *base_name = "core%d";
+    static const size_t max_lenght = 20;
+    char *name = (char *)calloc(max_lenght, sizeof(char));
+    snprintf(name, max_lenght, base_name, cpu_id);
+    return name;
+}
+
+void update_cpu_sensor(CpuSensor *sensor, uint64_t *energy_consumed)
+{
+    sensor->energy_units = read_unit(sensor->fd);
+    uint64_t raw_core_energy = read_raw_core_energy(sensor->fd);
+    uint64_t core_energy = raw_to_microjoule(raw_core_energy, sensor->energy_units);
+
+    *energy_consumed = modulo_substraction(core_energy, sensor->core_energy);
+    sensor->core_energy = core_energy;
+}
+
+unsigned int is_duplicate(CpuSensor *sensor,unsigned int nb_core, unsigned int nb_package, unsigned char map[nb_core][nb_package])
+{
+    if (map[sensor->core_id][sensor->package_id] == 1) {
+        return 0;
+    }
+    map[sensor->core_id][sensor->package_id] += 1;
+    return 1;
+}
+
+void init_cpu_sensor(CpuSensor *sensor, CpuSensor *cpu_info)
+{
+    static char filename[BUFFER_SIZE];
+    snprintf(filename,BUFFER_SIZE, base_str, cpu_info->cpu_id);
+
+    int fd = open(filename, O_RDONLY);
+    if (fd < 0) {
+        fprintf(stderr, base_str, cpu_info->cpu_id);
+        perror(":open()");
+        exit(127);
+    }
+
+    memcpy(sensor, cpu_info, sizeof(CpuSensor));
+    sensor->name = get_name(sensor->cpu_id);
+    sensor->fd = fd;
+}
+
+void clean_cpu_sensor(CpuSensor *sensor)
+{
+    close(sensor->fd);
+    free(sensor->name);
+}
+
+void free_amd_rapl(AmdRapl *rapl)
+{
+    free(rapl->sensors);
+    free(rapl);
+}
+
+// ----------------------AMD_RAPL_INTERFACE
+
+unsigned int init_amd_rapl(char *none, void **ptr)
+{
+    UNUSED(none);
+
+    unsigned int max_cpus = get_nb_cpu();
+    if (max_cpus == 0) {
+        fprintf(stderr, base_str, 0);
+        perror(":open()");
+        exit(127);
+    }
+
+    CpuSensor *cpu_information = (CpuSensor *) calloc(max_cpus, sizeof(CpuSensor));
+    if (parse_cpuinfo(cpu_information, max_cpus)) {
+        free(cpu_information);
+        PANIC(1, "cpuinfo");
+    }
+
+    unsigned int nb_package;
+    unsigned int nb_core;
+    get_arch(&nb_package, &nb_core, cpu_information, max_cpus);
+
+    unsigned char cpu_map[nb_core][nb_package];
+    memset(cpu_map, 0, sizeof(cpu_map));
+    CpuSensor *sensors = (CpuSensor *) calloc(max_cpus, sizeof(CpuSensor));
+
+    unsigned int sensor_count = 0;
+    for (unsigned int i = 0; i < max_cpus; i++) {
+        if (is_duplicate(&cpu_information[i], nb_core, nb_package, cpu_map) == 1) {
+            init_cpu_sensor(&sensors[sensor_count],&cpu_information[i]);
+            sensor_count += 1;
+        }
+    }
+    free(cpu_information);
+
+    AmdRapl *rapl = (AmdRapl *) calloc(1, sizeof(AmdRapl));
+    rapl->sensors = sensors;
+    rapl->sensor_count = sensor_count;
+    *ptr = (void *) rapl;
+    return rapl->sensor_count;
+}
+
+
+unsigned int get_amd_rapl(uint64_t *results, void *ptr)
+{
+    AmdRapl *rapl = (AmdRapl *) ptr;
+    for (unsigned int i = 0; i < rapl->sensor_count; i++) {
+        update_cpu_sensor(&rapl->sensors[i], &results[i]);
+    }
+    return rapl->sensor_count;
+}
+
+void label_amd_rapl(char **labels, void *ptr)
+{
+    AmdRapl *rapl = (AmdRapl *) ptr;
+    for (unsigned int i = 0; i < rapl->sensor_count; i++) {
+        labels[i] = rapl->sensors[i].name;
+    }
+}
+
+void clean_amd_rapl(void *ptr)
+{
+    AmdRapl *rapl = (AmdRapl *) ptr;
+
+    for (unsigned int i = 0; i < rapl->sensor_count; ++i) {
+        clean_cpu_sensor(&rapl->sensors[i]);
+    }
+    free(rapl->sensors);
+    free(rapl);
+}
+
diff --git a/counters.h b/src/amd_rapl.h
similarity index 57%
rename from counters.h
rename to src/amd_rapl.h
index 751557ab5b308c0eda13c61f077d5bdff6519cd1..e58cc16498f4b7553f9a7ebf6b6f40e8cd4ee288 100644
--- a/counters.h
+++ b/src/amd_rapl.h
@@ -1,5 +1,5 @@
 /*******************************************************
- Copyright (C) 2018-2019 Georges Da Costa <georges.da-costa@irit.fr>
+ Copyright (C) 2023-2023 Georges Da Costa <georges.da-costa@irit.fr>
 
     This file is part of Mojitos.
 
@@ -18,9 +18,25 @@
 
  *******************************************************/
 
-unsigned int init_counters(char*, void **);
-unsigned int get_counters(uint64_t* results, void*);
-void clean_counters(void *);
-void label_counters(char **labels, void*);
+unsigned int init_amd_rapl(char *, void **);
+unsigned int get_amd_rapl(uint64_t *results, void *);
+void clean_amd_rapl(void *);
+void label_amd_rapl(char **labels, void *);
 
-void show_all_counters();
+Sensor amd_rapl = {
+    .init = init_amd_rapl,
+    .get = get_amd_rapl,
+    .clean = clean_amd_rapl,
+    .label = label_amd_rapl,
+    .nb_opt = 1,
+};
+
+Optparse amd_rapl_opt[1] = {
+    {
+        .longname = "amd-rapl",
+        .shortname = 'r',
+        .argtype = OPTPARSE_NONE,
+        .usage_arg = NULL,
+        .usage_msg = "AMD RAPL",
+    },
+};
diff --git a/src/counters.c b/src/counters.c
new file mode 100644
index 0000000000000000000000000000000000000000..8013ff863757b2f614094cd96dc09e12776467c8
--- /dev/null
+++ b/src/counters.c
@@ -0,0 +1,240 @@
+/*******************************************************
+ Copyright (C) 2018-2023 Georges Da Costa <georges.da-costa@irit.fr>
+
+    This file is part of Mojitos.
+
+    Mojitos is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Mojitos is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with MojitO/S.  If not, see <https://www.gnu.org/licenses/>.
+
+ *******************************************************/
+
+#include <linux/perf_event.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <asm/unistd.h>
+#include <stdint.h>
+#include "util.h"
+
+
+struct Counter {
+    int nbcores;
+    int nbperf;
+    int **counters;
+    uint64_t *counters_values;
+    uint64_t *tmp_counters_values;
+
+    int *perf_indexes;
+
+};
+typedef struct Counter Counter;
+
+#include "counters_option.h"
+
+void *show_all_counters(void *none1, size_t none2)
+{
+    for (unsigned int i = 0; i < nb_counter_option; i++) {
+        printf("%s\n", perf_static_info[i].name);
+    }
+    UNUSED(none1);
+    UNUSED(none2);
+    exit(EXIT_SUCCESS);
+    return NULL; /* not reached */
+}
+
+void perf_type_key(__u32 **perf_type, __u64 **perf_key, int *indexes, int nb)
+{
+    *perf_type = malloc(nb * sizeof(__u32));
+    *perf_key  = malloc(nb * sizeof(__u64));
+
+    for (int i = 0; i < nb; i++) {
+        (*perf_key)[i]  = perf_static_info[indexes[i]].perf_key;
+        (*perf_type)[i] = perf_static_info[indexes[i]].perf_type;
+    }
+}
+void perf_event_list(char *perf_string, int *nb_perf, int **perf_indexes)
+{
+    char *token;
+    *nb_perf = 0;
+    *perf_indexes = NULL;
+
+    while ((token = strtok(perf_string, ",")) != NULL) {
+        perf_string = NULL;
+        unsigned int i;
+
+        for (i = 0; i < nb_counter_option; i++) {
+            if (strcmp(perf_static_info[i].name, token) == 0) {
+                (*nb_perf)++;
+                (*perf_indexes) = realloc(*perf_indexes, sizeof(int) * (*nb_perf));
+                (*perf_indexes)[*nb_perf - 1] = i;
+                break;
+            }
+        }
+
+        if (i == nb_counter_option) {
+            fprintf(stderr, "Unknown performance counter: %s\n", token);
+            exit(EXIT_FAILURE);
+        }
+    }
+}
+
+static long perf_event_open(struct perf_event_attr *hw_event, pid_t pid,
+                            int cpu, int group_fd, unsigned long flags)
+{
+    long res = syscall(__NR_perf_event_open, hw_event, pid, cpu, group_fd, flags);
+
+    if (res == -1) {
+        perror("perf_event_open");
+        fprintf(stderr, "Error opening leader %llx\n", hw_event->config);
+        exit(EXIT_FAILURE);
+    }
+
+    return res;
+}
+
+Counter *_init_counters(const int nb_perf, const __u32 *types, const __u64 *names)
+{
+    struct perf_event_attr pe;
+    unsigned int nbcores = sysconf(_SC_NPROCESSORS_ONLN);
+    memset(&pe, 0, sizeof(struct perf_event_attr));
+    pe.size = sizeof(struct perf_event_attr);
+    pe.disabled = 1;
+
+    Counter *counters = malloc(sizeof(struct Counter));
+    counters->nbperf = nb_perf;
+    counters->nbcores = nbcores;
+    counters->counters = malloc(nb_perf * sizeof(int *));
+
+    for (int i = 0; i < nb_perf; i++) {
+        pe.type = types[i];
+        pe.config = names[i];
+        counters->counters[i] = malloc(nbcores * sizeof(int));
+
+        for (unsigned int core = 0; core < nbcores; core++) {
+            counters->counters[i][core] = perf_event_open(&pe, -1, core, -1, PERF_FLAG_FD_CLOEXEC);
+        }
+    }
+
+    return counters;
+}
+
+void clean_counters(void *ptr)
+{
+    Counter *counters = (Counter *) ptr;
+
+    for (int counter = 0; counter < counters->nbperf; counter++) {
+        for (int core = 0; core < counters->nbcores; core++) {
+            close(counters->counters[counter][core]);
+        }
+
+        free(counters->counters[counter]);
+    }
+
+    free(counters->counters);
+    free(counters->counters_values);
+    free(counters->tmp_counters_values);
+    free(counters->perf_indexes);
+
+    free(counters);
+}
+
+void start_counters(Counter *counters)
+{
+    for (int counter = 0; counter < counters->nbperf; counter++) {
+        for (int core = 0; core < counters->nbcores; core++) {
+            ioctl(counters->counters[counter][core], PERF_EVENT_IOC_ENABLE, 0);
+        }
+    }
+}
+
+void reset_counters(Counter *counters)
+{
+    for (int counter = 0; counter < counters->nbperf; counter++) {
+        for (int core = 0; core < counters->nbcores; core++) {
+            ioctl(counters->counters[counter][core], PERF_EVENT_IOC_RESET, 0);
+        }
+    }
+}
+
+void _get_counters(Counter *counters, uint64_t *values)
+{
+    for (int i = 0; i < counters->nbperf; i++) {
+        uint64_t accu = 0;
+        uint64_t count = 0;
+
+        for (int core = 0; core < counters->nbcores; core++) {
+            if (-1 == read(counters->counters[i][core], &count, sizeof(uint64_t))) {
+                fprintf(stderr, "Cannot read result");
+                exit(EXIT_FAILURE);
+            }
+            accu += count;
+        }
+
+        values[i] = accu;
+    }
+}
+
+
+
+
+
+
+unsigned int init_counters(char *args, void **state)
+{
+    int nb_perf;
+    int *perf_indexes = NULL;
+
+    perf_event_list(args, &nb_perf, &perf_indexes);
+
+    __u32 *perf_type;
+    __u64 *perf_key;
+    perf_type_key(&perf_type, &perf_key, perf_indexes, nb_perf);
+    Counter *fd = _init_counters(nb_perf, perf_type, perf_key);
+    free(perf_type);
+    free(perf_key);
+
+    fd->perf_indexes = perf_indexes;
+    fd->counters_values = malloc(nb_perf * sizeof(uint64_t));
+    fd->tmp_counters_values = malloc(nb_perf * sizeof(uint64_t));
+    start_counters(fd);
+    _get_counters(fd, fd->counters_values);
+    *state = (void *) fd;
+
+    return nb_perf;
+}
+
+unsigned int get_counters(uint64_t *results, void *ptr)
+{
+    Counter *state = (Counter *) ptr;
+
+    _get_counters(state, state->tmp_counters_values);
+
+    for (int i = 0; i < state->nbperf; i++) {
+        results[i] = modulo_substraction(state->tmp_counters_values[i], state->counters_values[i]);
+    }
+
+    memcpy(state->counters_values, state->tmp_counters_values, state->nbperf * sizeof(uint64_t));
+    return state->nbperf;
+}
+
+void label_counters(char **labels, void *ptr)
+{
+    Counter *state = (Counter *) ptr;
+
+    for (int i = 0; i < state->nbperf; i++) {
+        labels[i] = perf_static_info[state->perf_indexes[i]].name;
+    }
+}
diff --git a/src/counters.h b/src/counters.h
new file mode 100644
index 0000000000000000000000000000000000000000..4374cd1f6ea0fb4e59d1226b03753d38a2fa3b2b
--- /dev/null
+++ b/src/counters.h
@@ -0,0 +1,54 @@
+/*******************************************************
+ Copyright (C) 2018-2023 Georges Da Costa <georges.da-costa@irit.fr>
+
+    This file is part of Mojitos.
+
+    Mojitos is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Mojitos is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with MojitO/S.  If not, see <https://www.gnu.org/licenses/>.
+
+ *******************************************************/
+
+unsigned int init_counters(char *, void **);
+unsigned int get_counters(uint64_t *results, void *);
+void clean_counters(void *);
+void label_counters(char **labels, void *);
+void *show_all_counters(void *, size_t);
+
+Sensor counters = {
+    .init = init_counters,
+    .get = get_counters,
+    .clean = clean_counters,
+    .label = label_counters,
+    .nb_opt = 2,
+};
+
+Optparse counters_opt[2] = {
+    {
+        .longname = "perf-list",
+        .shortname = 'p',
+        .argtype = OPTPARSE_REQUIRED,
+        .usage_arg = "<perf_list>",
+        .usage_msg = "performance counters\n"
+        "\tperf_list is a coma separated list of performance counters.\n"
+        "\tEx: instructions,cache_misses",
+    },
+    {
+        .longname = "list",
+        .shortname = 'l',
+        .argtype = OPTPARSE_NONE,
+        .usage_arg = NULL,
+        .usage_msg = "list the available performance counters and quit",
+        .fn = show_all_counters,
+    },
+};
+
diff --git a/src/counters_option.sh b/src/counters_option.sh
new file mode 100644
index 0000000000000000000000000000000000000000..76f92757f1e09207c1113421c76cd6a67d22f3df
--- /dev/null
+++ b/src/counters_option.sh
@@ -0,0 +1,88 @@
+#!/bin/sh
+
+# SPDX-License-Identifier: GPL-3.0-or-later
+# Copyright (C) 2018-2023 Georges Da Costa <georges.da-costa@irit.fr>
+
+linux_include=/usr/include/linux/perf_event.h
+
+echo '
+typedef struct CounterOption{
+    char *name;
+    __u32 perf_type;
+    __u64 perf_key;
+} CounterOption;
+
+static CounterOption perf_static_info[] = {'
+
+nb=0
+
+while IFS= read line; do
+	case $line in
+	*perf_hw_id*)
+        mode=PERF_TYPE_HARDWARE
+		;;
+    *perf_hw_cache_*)
+        mode=PERF_TYPE_HW_CACHE
+		;;
+    *perf_sw_id*)
+        mode=PERF_TYPE_SOFTWARE
+		;;
+    *PERF_COUNT_*=*)
+		perf_name=$(echo "$line" | awk '{print $1}')
+		short_perf=$(echo "$perf_name" | sed 's/PERF_COUNT_[HS]W_//' | tr 'A-Z' 'a-z')
+		case $short_perf in
+		# blacklist
+		stalled_cycles_frontend|stalled_cycles_backend|cache_l1i|cache_op_write|cache_result_miss)
+			continue
+			;;
+		esac
+
+		if [ "$mode" != 'PERF_TYPE_HW_CACHE' ]; then
+			printf '    { .name = "%s", .perf_type = %s, .perf_key = %s},\n' \
+					"$short_perf" \
+					"$mode" \
+					"$perf_name"
+
+            : $((nb += 1))
+			continue
+		fi
+
+		# $mode == PERF_TYPE_HW_CACHE
+        for op_id in \
+        	'r PERF_COUNT_HW_CACHE_OP_READ' \
+        	'w PERF_COUNT_HW_CACHE_OP_WRITE' \
+        	'p PERF_COUNT_HW_CACHE_OP_PREFETCH'
+        do
+            op_id_str=${op_id% *}
+            op_id_name=${op_id#* }
+
+            for result_id in \
+            	'a PERF_COUNT_HW_CACHE_RESULT_ACCESS' \
+            	'm PERF_COUNT_HW_CACHE_RESULT_MISS'
+            do
+                result_id_str=${result_id% *}
+                result_id_name=${result_id#* }
+
+            	printf '    {'
+            	printf ' .name = "%s_%s_%s",' \
+            		"$short_perf" \
+            		"$op_id_str" \
+            		"$result_id_str"
+            	printf ' .perf_type = %s,' \
+            		"$mode"
+            	printf ' .perf_key = %s | (%s >> 8) | (%s >> 16)' \
+            		"$perf_name" \
+            		"$op_id_name" \
+            		"$result_id_name"
+            	printf ' },\n'
+                : $((nb += 1))
+            done
+        done
+		;;
+	esac
+done < "$linux_include"
+
+echo '};'
+
+printf '\nstatic unsigned int nb_counter_option = %d;\n' "$nb"
+
diff --git a/src/infiniband.c b/src/infiniband.c
new file mode 100644
index 0000000000000000000000000000000000000000..6a5a6b4e2422d819269d1a470783286be8a5c162
--- /dev/null
+++ b/src/infiniband.c
@@ -0,0 +1,135 @@
+/*******************************************************
+ Copyright (C) 2018-2023 Georges Da Costa <georges.da-costa@irit.fr>
+
+    This file is part of Mojitos.
+
+    Mojitos is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Mojitos is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with MojitO/S.  If not, see <https://www.gnu.org/licenses/>.
+
+ *******************************************************/
+#include <fcntl.h>
+#include <glob.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "util.h"
+
+#define NB_SENSOR 4
+
+struct Infiniband {
+    uint64_t values[NB_SENSOR];
+    uint64_t tmp_values[NB_SENSOR];
+    int sources[NB_SENSOR];
+};
+typedef struct Infiniband Infiniband;
+
+unsigned int _get_infiniband(uint64_t *results, int *sources)
+{
+    if (sources == NULL) {
+        return 0;
+    }
+
+    char buffer[128];
+
+    for (int i = 0; i < NB_SENSOR; i++) {
+        if (pread(sources[i], buffer, 127, 0) < 0) {
+            perror("pread");
+            exit(1);
+        }
+
+        results[i] = strtoull(buffer, NULL, 10);
+    }
+
+    return NB_SENSOR;
+}
+
+unsigned int init_infiniband(char *infi_path, void **ptr)
+{
+    if (infi_path == NULL) {
+        return 0;
+    }
+
+    if (strcmp(infi_path, "X") == 0) {
+
+        glob_t res;
+
+        glob("/sys/class/infiniband/*/ports/*/counters/", 0, NULL, &res);
+
+        if (res.gl_pathc == 0) {
+            fprintf(stderr, "No infiniband found.\n");
+            return 0;
+        }
+
+        infi_path = res.gl_pathv[0];
+    }
+
+    char *filenames[] = {"%s/port_rcv_packets",
+                         "%s/port_rcv_data",
+                         "%s/port_xmit_packets",
+                         "%s/port_xmit_data"
+                        };
+
+    Infiniband *state = malloc(sizeof(Infiniband));
+
+    char buffer[1024];
+    for (int i = 0; i < NB_SENSOR; i++) {
+        snprintf(buffer, 1024, filenames[i], infi_path);
+        state->sources[i] = open(buffer, O_RDONLY);
+    }
+
+    *ptr = (void *) state;
+    _get_infiniband(state->values, state->sources);
+
+    return NB_SENSOR;
+}
+
+unsigned int get_infiniband(uint64_t *results, void *ptr)
+{
+    Infiniband *state = (Infiniband *) ptr;
+    _get_infiniband(state->tmp_values, state->sources);
+
+    for (int i = 0; i < NB_SENSOR; i++) {
+        results[i] = modulo_substraction(state->tmp_values[i], state->values[i]);
+    }
+
+    memcpy(state->values, state->tmp_values, NB_SENSOR * sizeof(uint64_t));
+    return NB_SENSOR;
+}
+
+void clean_infiniband(void *ptr)
+{
+    Infiniband *state = (Infiniband *) ptr;
+
+    if (state == NULL) {
+        return;
+    }
+
+    for (int i = 0; i < NB_SENSOR; i++) {
+        close(state->sources[i]);
+    }
+
+    free(state);
+}
+
+char *_labels_infiniband[NB_SENSOR] = {"irxp", "irxb", "itxp", "itxb"};
+void label_infiniband(char **labels, void *none)
+{
+    UNUSED(none);
+
+    for (int i = 0; i < NB_SENSOR; i++) {
+        labels[i] = _labels_infiniband[i];
+    }
+}
diff --git a/src/infiniband.h b/src/infiniband.h
new file mode 100644
index 0000000000000000000000000000000000000000..8098cf5c68773326e2bda38e3748e81dfb52ee0c
--- /dev/null
+++ b/src/infiniband.h
@@ -0,0 +1,43 @@
+/*******************************************************
+ Copyright (C) 2018-2023 Georges Da Costa <georges.da-costa@irit.fr>
+
+    This file is part of Mojitos.
+
+    Mojitos is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Mojitos is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with MojitO/S.  If not, see <https://www.gnu.org/licenses/>.
+
+ *******************************************************/
+
+unsigned int init_infiniband(char *infi_path, void **ptr);
+unsigned int get_infiniband(uint64_t *results, void *ptr);
+void clean_infiniband(void *ptr);
+void label_infiniband(char **labels, void *);
+
+Sensor infiniband = {
+    .init = init_infiniband,
+    .get = get_infiniband,
+    .clean = clean_infiniband,
+    .label = label_infiniband,
+    .nb_opt = 1,
+};
+
+Optparse infiniband_opt[1] = {
+    {
+        .longname = "monitor-infiniband",
+        .shortname = 'i',
+        .argtype = OPTPARSE_REQUIRED,
+        .usage_arg = "<infiniband_path>",
+        .usage_msg = "infiniband monitoring (if infiniband_path is X, tries to detect it automatically)",
+    },
+};
+
diff --git a/src/load.c b/src/load.c
new file mode 100644
index 0000000000000000000000000000000000000000..64502e587c1472e603d96d36f53a3068d67053e9
--- /dev/null
+++ b/src/load.c
@@ -0,0 +1,108 @@
+/*******************************************************
+ Copyright (C) 2019-2023 Georges Da Costa <georges.da-costa@irit.fr>
+
+    This file is part of Mojitos.
+
+    Mojitos is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Mojitos is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with MojitO/S.  If not, see <https://www.gnu.org/licenses/>.
+
+ *******************************************************/
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdio.h>
+#include "util.h"
+
+#define LOAD_BUFFER_SIZE 1024
+#define LOAD_VALUES_SIZE 10
+char buffer[LOAD_BUFFER_SIZE];
+
+static int load_fid = -1;
+static uint64_t load_values[LOAD_VALUES_SIZE] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+static uint64_t tmp_load_values[LOAD_VALUES_SIZE] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+static char *stat = "/proc/stat";
+
+void _get_load(uint64_t *results)
+{
+    if (pread(load_fid, buffer, LOAD_BUFFER_SIZE - 1, 0) < 0) {
+        perror("pread");
+        exit(1);
+    }
+
+    int pos = 0;
+
+    while (buffer[pos] > '9' || buffer[pos] < '0') {
+        pos++;
+    }
+
+    for (int i = 0; i < LOAD_VALUES_SIZE; i++) {
+        results[i] = strtoull(buffer + pos, NULL, LOAD_VALUES_SIZE);
+
+        while (buffer[pos] <= '9' && buffer[pos] >= '0') {
+            pos++;
+        }
+
+        pos++;
+    }
+}
+
+// Public interface
+
+unsigned int init_load(char *argument, void **state)
+{
+    UNUSED(argument);
+    UNUSED(state);
+    load_fid = open(stat, O_RDONLY);
+    if (load_fid < 0) {
+        fprintf(stderr, "%s ", stat);
+        perror("open");
+        exit(1);
+    }
+
+    _get_load(load_values);
+    return LOAD_VALUES_SIZE;
+}
+
+unsigned int get_load(uint64_t *results, void *state)
+{
+    UNUSED(state);
+    _get_load(tmp_load_values);
+
+    for (int i = 0; i < LOAD_VALUES_SIZE; i++) {
+        results[i] = tmp_load_values[i] - load_values[i];
+    }
+
+    memcpy(load_values, tmp_load_values, sizeof(load_values));
+    return LOAD_VALUES_SIZE;
+}
+
+void clean_load(void *state)
+{
+    UNUSED(state);
+    close(load_fid);
+}
+
+char *_labels[LOAD_VALUES_SIZE] = {
+    "user", "nice", "system", "idle", "iowait", "irq",
+    "softirq", "steal", "guest", "guest_nice"
+};
+void label_load(char **labels, void *none)
+{
+    UNUSED(none);
+
+    for (int i = 0; i < LOAD_VALUES_SIZE; i++) {
+        labels[i] = _labels[i];
+    }
+}
diff --git a/load.h b/src/load.h
similarity index 61%
rename from load.h
rename to src/load.h
index 42ce968062dcb68a13ebf67713eabc865abe1d7a..6d8d29804f93c6be0805b6670486210f74452fdf 100644
--- a/load.h
+++ b/src/load.h
@@ -1,5 +1,5 @@
 /*******************************************************
- Copyright (C) 2018-2019 Georges Da Costa <georges.da-costa@irit.fr>
+ Copyright (C) 2018-2023 Georges Da Costa <georges.da-costa@irit.fr>
 
     This file is part of Mojitos.
 
@@ -18,7 +18,25 @@
 
  *******************************************************/
 
-unsigned int init_load(char*, void **);
-unsigned int get_load(uint64_t* results, void*);
+unsigned int init_load(char *, void **);
+unsigned int get_load(uint64_t *results, void *);
 void clean_load(void *);
-void label_load(char **labels, void*);
+void label_load(char **labels, void *);
+
+Sensor load = {
+    .init = init_load,
+    .get = get_load,
+    .clean = clean_load,
+    .label = label_load,
+    .nb_opt = 1,
+};
+
+Optparse load_opt[1] = {
+    {
+        .longname = "sysload",
+        .shortname = 'u',
+        .argtype = OPTPARSE_NONE,
+        .usage_arg = NULL,
+        .usage_msg = "system load",
+    },
+};
diff --git a/src/mojitos.c b/src/mojitos.c
new file mode 100644
index 0000000000000000000000000000000000000000..e8cb6fa2a2a8100de93cfa40dee93ec20f3c0a93
--- /dev/null
+++ b/src/mojitos.c
@@ -0,0 +1,384 @@
+/*******************************************************
+ Copyright (C) 2018-2023 Georges Da Costa <georges.da-costa@irit.fr>
+
+    This file is part of Mojitos.
+
+    Mojitos is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Mojitos is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with MojitO/S.  If not, see <https://www.gnu.org/licenses/>.
+
+ *******************************************************/
+
+#include <assert.h>
+#include <inttypes.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include "util.h"
+
+#define OPTPARSE_IMPLEMENTATION
+#define OPTPARSE_API static
+#include "optparse.h"
+
+typedef unsigned int (*initializer_t)(char *, void **);
+typedef void (*labeler_t)(char **, void *);
+typedef unsigned int (*getter_t)(uint64_t *, void *);
+typedef void (*cleaner_t)(void *);
+
+typedef struct Opt Opt;
+typedef struct Sensor Sensor;
+/* optparse typedef */
+typedef struct optparse_long Optparse;
+
+struct Sensor {
+    initializer_t init;
+    getter_t get;
+    cleaner_t clean;
+    labeler_t label;
+    int nb_opt;
+};
+
+int nb_defined_sensors = 0;
+
+#include "sensors.h"
+
+Sensor sensors[NB_SENSOR];
+
+#define NB_OPT 5
+Optparse opts[NB_OPT + NB_SENSOR_OPT + 1] = {
+    {
+        .longname = "freq", .shortname = 'f', .argtype = OPTPARSE_REQUIRED,
+        .usage_arg = "<freq>",
+        .usage_msg = "set amount of measurements per second.",
+    },
+    {
+        .longname = "time", .shortname = 't', .argtype = OPTPARSE_REQUIRED,
+        .usage_arg = "<time>",
+        .usage_msg = "set duration value (seconds). If 0, then loops infinitely.",
+    },
+    {
+        .longname = "exec", .shortname = 'e', .argtype = OPTPARSE_REQUIRED,
+        .usage_arg = "<cmd> ...",
+        .usage_msg = "Execute a command with optional arguments.\n"
+        "\tIf this option is used, any usage of -t or -f is ignored.",
+    },
+    {
+        .longname = "logfile", .shortname = 'o', .argtype = OPTPARSE_REQUIRED,
+        .usage_arg = "<file>",
+        .usage_msg = "specify a log file.",
+    },
+    {
+        .longname = "overhead-stats", .shortname = 's', .argtype = OPTPARSE_NONE,
+        .usage_arg = NULL,
+        .usage_msg = "enable overhead statistics (nanoseconds).",
+    },
+};
+
+void dumpopt(Optparse *opt)
+{
+    printf(".It Fl %c | Fl \\-%s", opt->shortname, opt->longname);
+    if (opt->usage_arg != NULL) {
+        printf(" Ar %s", opt->usage_arg);
+    }
+    printf("\n");
+    printf("%s\n", opt->usage_msg);
+}
+
+void dumpopts(Optparse *opts, size_t nb_opt, size_t nb_sensor_opt)
+{
+    size_t i;
+
+    /* options */
+    printf(".Pp\nOPTIONS:\n.Bl -tag -width Ds\n");
+    for (i = 0; i < nb_opt; i++) {
+        dumpopt(&opts[i]);
+    }
+    printf(".El\n");
+
+    /* sensors */
+    printf(".Pp\nSENSORS:\n.Bl -tag -width Ds\n");
+    for (i++; i < nb_opt + nb_sensor_opt; i++) {
+        dumpopt(&opts[i]);
+    }
+    printf(".El\n");
+}
+
+void printopt(Optparse *opt)
+{
+    printf("-%c", opt->shortname);
+    printf("|--%s", opt->longname);
+    if (opt->usage_arg != NULL) {
+        printf(" %s", opt->usage_arg);
+    }
+    printf("\n\t%s\n", opt->usage_msg);
+}
+
+void usage(char **argv)
+{
+    printf("Usage : %s [OPTIONS] [SENSOR ...] [-e <cmd> ...]\n", argv[0]);
+
+    printf("\nOPTIONS:\n");
+    for (int i = 0; i < NB_OPT; i++) {
+        printopt(&opts[i]);
+    }
+
+    if (nb_defined_sensors == 0) {
+        // no captor to show
+        exit(EXIT_FAILURE);
+    }
+
+    printf("\nSENSORS:\n");
+    for (int i = 0; i < NB_SENSOR_OPT; i++) {
+        printopt(&opts[NB_OPT + i]);
+    }
+
+    exit(EXIT_FAILURE);
+}
+
+void sighandler(int none)
+{
+    UNUSED(none);
+}
+
+void flush(int none)
+{
+    UNUSED(none);
+    exit(0);
+}
+
+FILE *output;
+void flushexit()
+{
+    if (output != NULL) {
+        fflush(output);
+        fclose(output);
+    }
+}
+
+unsigned int nb_sources = 0;
+void **states = NULL;
+getter_t *getter = NULL;
+cleaner_t *cleaner = NULL;
+
+unsigned int nb_sensors = 0;
+char **labels = NULL;
+uint64_t *values = NULL;
+
+void add_source(Sensor *cpt, char *arg)
+{
+    nb_sources++;
+    initializer_t init = cpt->init;
+    labeler_t labeler = cpt->label;
+    getter_t get = cpt->get;
+    cleaner_t clean = cpt->clean;
+
+    states = realloc(states, nb_sources * sizeof(void *));
+    int nb = init(arg, &states[nb_sources - 1]);
+
+    if (nb == 0) {
+        nb_sources--;
+        states = realloc(states, nb_sources * sizeof(void *));
+        return;
+    }
+
+    getter = realloc(getter, nb_sources * sizeof(void *));
+    getter[nb_sources - 1] = get;
+    cleaner = realloc(cleaner, nb_sources * sizeof(void *));
+    cleaner[nb_sources - 1] = clean;
+
+    labels = realloc(labels, (nb_sensors + nb) * sizeof(char *));
+    labeler(labels + nb_sensors, states[nb_sources - 1]);
+
+    values = realloc(values, (nb_sensors + nb) * sizeof(uint64_t));
+    nb_sensors += nb;
+}
+
+int main(int argc, char **argv)
+{
+    int total_time = 1;
+    int delta = 0;
+    int frequency = 1;
+    char **application = NULL;
+    int stat_mode = -1;
+
+    init_sensors(opts, sensors, NB_OPT + NB_SENSOR_OPT, NB_OPT, &nb_defined_sensors);
+
+    if (argc == 1) {
+        usage(argv);
+    }
+
+    if (argc == 2 && strcmp(argv[1], "--dump-opts") == 0) {
+        dumpopts(opts, NB_OPT, NB_SENSOR_OPT);
+        exit(EXIT_SUCCESS);
+    }
+
+    output = stdout;
+
+    atexit(flushexit);
+    signal(15, flush);
+
+    int opt;
+    struct optparse options;
+    options.permute = 0;
+
+    optparse_init(&options, argv);
+    while ((opt = optparse_long(&options, opts, NULL)) != -1 && application == NULL) {
+        switch (opt) {
+        case 'f':
+            frequency = atoi(options.optarg);
+            break;
+        case 't':
+            total_time = atoi(options.optarg);
+            delta = 1;
+            if (total_time == 0) {
+                total_time = 1;
+                delta = 0;
+            }
+            break;
+        case 's':
+            stat_mode = 0;
+            break;
+        case 'o':
+            if ((output = fopen(options.optarg, "wb")) == NULL) {
+                perror("fopen");
+                PANIC(1, "-o %s", options.optarg);
+            }
+            break;
+        case 'e':
+            application = options.argv;
+            signal(17, sighandler);
+            break;
+        default: {
+            int ismatch = 0;
+            int opt_idx = NB_OPT;
+            for (int i = 0; i < nb_defined_sensors && !ismatch; i++) {
+                for (int j = 0; j < sensors[i].nb_opt; j++) {
+                    if (opt == opts[opt_idx].shortname) {
+                        ismatch = 1;
+                        if (opts[opt_idx].fn != NULL) {
+                            (void) opts[opt_idx].fn(NULL, 0);
+                        } else {
+                            add_source(&sensors[i], options.optarg);
+                        }
+                        break;
+                    }
+                    opt_idx++;
+                }
+            }
+            if (!ismatch) {
+                fprintf(stderr, "%s: %s\n", argv[0], options.errmsg);
+                usage(argv);
+            }
+        }
+        }
+    }
+
+    setvbuf(output, NULL, _IONBF, BUFSIZ);
+    struct timespec ts;
+    struct timespec ts_ref;
+
+    fprintf(output, "#timestamp ");
+
+    for (unsigned int i = 0; i < nb_sensors; i++) {
+        fprintf(output, "%s ", labels[i]);
+    }
+
+    if (stat_mode == 0) {
+        fprintf(output, "overhead ");
+    }
+
+    fprintf(output, "\n");
+
+    unsigned long int stat_data = 0;
+
+    for (int temps = 0; temps < total_time * frequency; temps += delta) {
+        clock_gettime(CLOCK_MONOTONIC, &ts_ref);
+
+        // Get Data
+        unsigned int current = 0;
+
+        for (unsigned int i = 0; i < nb_sources; i++) {
+            current += getter[i](&values[current], states[i]);
+        }
+
+        if (application != NULL) {
+
+            if (fork() == 0) {
+                execvp(application[0], application);
+                exit(0);
+            }
+
+            pause();
+            clock_gettime(CLOCK_MONOTONIC, &ts);
+
+            if (ts.tv_nsec >= ts_ref.tv_nsec) {
+                fprintf(output, "%ld.%09ld ", (ts.tv_sec - ts_ref.tv_sec), ts.tv_nsec - ts_ref.tv_nsec);
+            } else {
+                fprintf(output, "%ld.%09ld ", (ts.tv_sec - ts_ref.tv_sec) - 1, 1000 * 1000 * 1000 + ts.tv_nsec - ts_ref.tv_nsec);
+            }
+        } else {
+#ifdef DEBUG
+            clock_gettime(CLOCK_MONOTONIC, &ts);
+            fprintf(stderr, "%ld\n", (ts.tv_nsec - ts_ref.tv_nsec) / 1000);
+            //Indiv: mean: 148 std: 31 % med: 141 std: 28 %
+            //Group: mean: 309 std: 41 % med: 297 std: 39 %
+#endif
+
+            if (stat_mode == 0) {
+                clock_gettime(CLOCK_MONOTONIC, &ts);
+
+                if (ts.tv_nsec >= ts_ref.tv_nsec) {
+                    stat_data = ts.tv_nsec - ts_ref.tv_nsec;
+                } else {
+                    stat_data = 1000 * 1000 * 1000 + ts.tv_nsec - ts_ref.tv_nsec;
+                }
+            }
+
+            // Treat Data
+            fprintf(output, "%ld.%09ld ", ts_ref.tv_sec, ts_ref.tv_nsec);
+        }
+
+        for (unsigned int i = 0; i < nb_sensors; i++) {
+            /* "PRIu64" is a format specifier to print uint64_t values */
+            fprintf(output, "%" PRIu64 " ", values[i]);
+        }
+
+        if (stat_mode == 0) {
+            fprintf(output, "%ld ", stat_data);
+        }
+
+        fprintf(output, "\n");
+
+        if (application != NULL) {
+            break;
+        }
+
+        clock_gettime(CLOCK_MONOTONIC, &ts);
+        usleep(1000 * 1000 / frequency - (ts.tv_nsec / 1000) % (1000 * 1000 / frequency));
+    }
+
+    for (unsigned int i = 0; i < nb_sources; i++) {
+        cleaner[i](states[i]);
+    }
+
+    if (nb_sources > 0) {
+        free(getter);
+        free(cleaner);
+        free(labels);
+        free(values);
+        free(states);
+    }
+}
+
diff --git a/src/network.c b/src/network.c
new file mode 100644
index 0000000000000000000000000000000000000000..37d237376be4c893e2c29de8d160b61ba3190628
--- /dev/null
+++ b/src/network.c
@@ -0,0 +1,146 @@
+/*******************************************************
+ Copyright (C) 2018-2023 Georges Da Costa <georges.da-costa@irit.fr>
+
+    This file is part of Mojitos.
+
+    Mojitos is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Mojitos is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with MojitO/S.  If not, see <https://www.gnu.org/licenses/>.
+
+ *******************************************************/
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include "util.h"
+
+#define NB_SENSOR 4
+
+static char *route = "/proc/net/route";
+struct Network {
+    uint64_t values[NB_SENSOR];
+    uint64_t tmp_values[NB_SENSOR];
+    int sources[NB_SENSOR];
+};
+typedef struct Network Network;
+
+unsigned int _get_network(uint64_t *results, int *sources)
+{
+    if (sources == NULL) {
+        return 0;
+    }
+
+    char buffer[128];
+
+    for (int i = 0; i < NB_SENSOR; i++) {
+        if (pread(sources[i], buffer, 127, 0) < 0) {
+            perror("pread");
+            exit(1);
+        }
+
+        results[i] = strtoull(buffer, NULL, 10);
+    }
+
+    return NB_SENSOR;
+}
+
+
+
+unsigned int init_network(char *dev, void **ptr)
+{
+    if (dev == NULL) {
+        return 0;
+    }
+
+    if (strcmp(dev, "X") == 0) {
+        int fd = open(route, O_RDONLY);
+
+        if (fd < 0) {
+            fprintf(stderr, "%s ", route);
+            perror("open");
+            exit(1);
+        }
+
+        char buffer[1000];
+
+        if (read(fd, buffer, 999) < 0 ) {
+            perror("read");
+            close(fd);
+            exit(1);
+        }
+
+        char *start_of_dev = index(buffer, '\n') + 1;
+        char *end_of_dev = index(start_of_dev, '\t');
+        *end_of_dev = '\0';
+        dev = start_of_dev;
+        close(fd);
+    }
+
+    char *filenames[] = {"/sys/class/net/%s/statistics/rx_packets",
+                         "/sys/class/net/%s/statistics/rx_bytes",
+                         "/sys/class/net/%s/statistics/tx_packets",
+                         "/sys/class/net/%s/statistics/tx_bytes"
+                        };
+
+    Network *state = malloc(sizeof(Network));
+
+    char buffer2[256];
+    for (int i = 0; i < NB_SENSOR; i++) {
+        snprintf(buffer2, 256, filenames[i], dev);
+        state->sources[i] = open(buffer2, O_RDONLY);
+    }
+
+    *ptr = (void *) state;
+    _get_network(state->values, state->sources);
+
+    return NB_SENSOR;
+}
+
+unsigned int get_network(uint64_t *results, void *ptr)
+{
+    Network *state = (Network *) ptr;
+    _get_network(state->tmp_values, state->sources);
+
+    for (int i = 0; i < NB_SENSOR; i++) {
+        results[i] = modulo_substraction(state->tmp_values[i], state->values[i]);
+    }
+
+    memcpy(state->values, state->tmp_values, NB_SENSOR * sizeof(uint64_t));
+    return NB_SENSOR;
+}
+
+void clean_network(void *ptr)
+{
+    Network *state = (Network *) ptr;
+
+    if (state == NULL) {
+        return;
+    }
+
+    for (int i = 0; i < NB_SENSOR; i++) {
+        close(state->sources[i]);
+    }
+
+    free(state);
+}
+
+char *_labels_network[NB_SENSOR] = {"rxp", "rxb", "txp", "txb"};
+void label_network(char **labels, void *none)
+{
+    UNUSED(none);
+
+    for (int i = 0; i < NB_SENSOR; i++) {
+        labels[i] = _labels_network[i];
+    }
+}
diff --git a/src/network.h b/src/network.h
new file mode 100644
index 0000000000000000000000000000000000000000..a158dbbce25333edf64c5f5dd0cde42856e08c70
--- /dev/null
+++ b/src/network.h
@@ -0,0 +1,42 @@
+/*******************************************************
+ Copyright (C) 2018-2023 Georges Da Costa <georges.da-costa@irit.fr>
+
+    This file is part of Mojitos.
+
+    Mojitos is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Mojitos is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with MojitO/S.  If not, see <https://www.gnu.org/licenses/>.
+
+ *******************************************************/
+
+unsigned int init_network(char *, void **);
+unsigned int get_network(uint64_t *results, void *);
+void clean_network(void *);
+void label_network(char **labels, void *);
+
+Sensor network = {
+    .init = init_network,
+    .get = get_network,
+    .clean = clean_network,
+    .label = label_network,
+    .nb_opt = 1,
+};
+
+Optparse network_opt[1] = {
+    {
+        .longname = "net-dev",
+        .shortname = 'd',
+        .argtype = OPTPARSE_REQUIRED,
+        .usage_arg = "<net_dev>",
+        .usage_msg = "network monitoring (if network_device is X, tries to detect it automatically)",
+    },
+};
diff --git a/src/rapl.c b/src/rapl.c
new file mode 100644
index 0000000000000000000000000000000000000000..3d13367a47fb009b7f0a0f1567a9169daf9a08a4
--- /dev/null
+++ b/src/rapl.c
@@ -0,0 +1,194 @@
+/*******************************************************
+ Copyright (C) 2022-2023 Georges Da Costa <georges.da-costa@irit.fr>
+
+    This file is part of Mojitos.
+
+    Mojitos is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Mojitos is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with MojitO/S.  If not, see <https://www.gnu.org/licenses/>.
+
+ *******************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <errno.h>
+#include "util.h"
+
+#define MAX_HEADER 128
+#define BUFFER_SIZE 1024
+
+char *get_rapl_string(const char *filename)
+{
+    int fd = open(filename, O_RDONLY);
+    if (fd == -1) {
+        return NULL;
+    }
+
+    char *result = malloc(MAX_HEADER);
+    int nb = read(fd, result, MAX_HEADER);
+    close(fd);
+    result[nb - 1] = 0;
+    return result;
+}
+
+void append(char *name, int i, size_t buffer_size)
+{
+    size_t name_len = strlen(name);
+    char *ptr = name + name_len;
+
+    size_t remaining_space = buffer_size - name_len;
+    snprintf(ptr, remaining_space, "%d", i);
+}
+
+
+struct IntelRapl {
+    unsigned int nb;
+    char **names;
+    int *fids;
+    uint64_t *values;
+    uint64_t *tmp_values;
+
+};
+typedef struct IntelRapl IntelRapl;
+
+
+void add_rapl_source(IntelRapl *rapl, char *name, char *energy_uj)
+{
+    rapl->nb += 1;
+    rapl->names = realloc(rapl->names, sizeof(char **)*rapl->nb);
+    rapl->fids = realloc(rapl->fids, sizeof(int *)*rapl->nb);
+
+    rapl->names[rapl->nb - 1] = malloc(strlen(name) + 1);
+    strcpy(rapl->names[rapl->nb - 1], name);
+    //printf("%s\n", energy_uj);
+
+    int fd = open(energy_uj, O_RDONLY);
+
+    if (fd < 0) {
+        fprintf(stderr, "%s ", energy_uj);
+        perror("open");
+        exit(1);
+    }
+
+    rapl->fids[rapl->nb - 1] = fd;
+}
+
+
+void _get_rapl(uint64_t *values, IntelRapl *rapl)
+{
+    static char buffer[512];
+
+    for (unsigned int i = 0; i < rapl->nb; i++) {
+
+        if (pread(rapl->fids[i], buffer, 100, 0) < 0) {
+            perror("pread");
+            exit(1);
+        }
+
+        values[i] = strtoull(buffer, NULL, 10);
+    }
+}
+
+
+unsigned int init_rapl(char *none, void **ptr)
+{
+    UNUSED(none);
+    IntelRapl *rapl = malloc(sizeof(IntelRapl));
+    rapl->nb = 0;
+    rapl->names = NULL;
+    rapl->fids = NULL;
+
+    char buffer[BUFFER_SIZE];
+    char *name_base = "/sys/devices/virtual/powercap/intel-rapl/intel-rapl:%d/%s";
+    char *name_sub = "/sys/devices/virtual/powercap/intel-rapl/intel-rapl:%d/intel-rapl:%d:%d/%s";
+
+    for (unsigned int i = 0;; i++) {
+        snprintf(buffer, BUFFER_SIZE, name_base, i, "name");
+        char *tmp = get_rapl_string(buffer);
+
+        if (tmp == NULL) {
+            break;
+        }
+
+        append(tmp, i, MAX_HEADER);
+        snprintf(buffer, BUFFER_SIZE, name_base, i, "energy_uj");
+        add_rapl_source(rapl, tmp, buffer);
+        free(tmp);
+
+        for (unsigned int j = 0;; j++) {
+            snprintf(buffer, BUFFER_SIZE, name_sub, i, i, j, "name");
+            char *tmp_sub = get_rapl_string(buffer);
+
+            if (tmp_sub == NULL) {
+                break;
+            }
+
+            append(tmp_sub, i, MAX_HEADER);
+            snprintf(buffer, BUFFER_SIZE, name_sub, i, i, j, "energy_uj");
+            add_rapl_source(rapl, tmp_sub, buffer);
+
+            free(tmp_sub);
+        }
+    }
+
+    rapl->values = calloc(sizeof(uint64_t), rapl->nb);
+    rapl->tmp_values = calloc(sizeof(uint64_t), rapl->nb);
+
+    _get_rapl(rapl->values, rapl);
+
+    *ptr = (void *)rapl;
+    return rapl->nb;
+}
+
+
+unsigned int get_rapl(uint64_t *results, void *ptr)
+{
+    IntelRapl *state = (IntelRapl *) ptr;
+    _get_rapl(state->tmp_values, state);
+
+    for (unsigned int i = 0; i < state->nb; i++) {
+        results[i] = modulo_substraction(state->tmp_values[i], state->values[i]);
+    }
+
+    memcpy(state->values, state->tmp_values, sizeof(uint64_t)*state->nb);
+    return state->nb;
+}
+
+void clean_rapl(void *ptr)
+{
+    IntelRapl *rapl = (IntelRapl *) ptr;
+
+    for (unsigned int i = 0; i < rapl->nb; i++) {
+        free(rapl->names[i]);
+        close(rapl->fids[i]);
+    }
+
+    free(rapl->names);
+    free(rapl->fids);
+    free(rapl->values);
+    free(rapl->tmp_values);
+    free(rapl);
+}
+
+
+void label_rapl(char **labels, void *ptr)
+{
+    IntelRapl *rapl = (IntelRapl *) ptr;
+
+    for (unsigned int i = 0; i < rapl->nb; i++) {
+        labels[i] = rapl->names[i];
+    }
+}
diff --git a/rapl.h b/src/rapl.h
similarity index 61%
rename from rapl.h
rename to src/rapl.h
index 9ec5f3afdc88d5f1bcdd4dcb5fdb4208261e2db0..7720f071a4be6b98dad5cb72d537bd69a430799d 100644
--- a/rapl.h
+++ b/src/rapl.h
@@ -1,5 +1,5 @@
 /*******************************************************
- Copyright (C) 2018-2019 Georges Da Costa <georges.da-costa@irit.fr>
+ Copyright (C) 2022-2023 Georges Da Costa <georges.da-costa@irit.fr>
 
     This file is part of Mojitos.
 
@@ -18,8 +18,26 @@
 
  *******************************************************/
 
-unsigned int init_rapl(char*, void **);
-unsigned int get_rapl(uint64_t* results, void*);
+unsigned int init_rapl(char *, void **);
+unsigned int get_rapl(uint64_t *results, void *);
 void clean_rapl(void *);
-void label_rapl(char **labels, void*);
+void label_rapl(char **labels, void *);
+
+Sensor rapl = {
+    .init = init_rapl,
+    .get = get_rapl,
+    .clean = clean_rapl,
+    .label = label_rapl,
+    .nb_opt = 1,
+};
+
+Optparse rapl_opt[1] = {
+    {
+        .longname = "intel-rapl",
+        .shortname = 'r',
+        .argtype = OPTPARSE_NONE,
+        .usage_arg = NULL,
+        .usage_msg = "INTEL RAPL",
+    },
+};
 
diff --git a/src/temperature.c b/src/temperature.c
new file mode 100644
index 0000000000000000000000000000000000000000..1dc1099d15e73128ec9d5124b521b958ae9efe46
--- /dev/null
+++ b/src/temperature.c
@@ -0,0 +1,177 @@
+/*******************************************************
+ Copyright (C) 2018-2023 Georges Da Costa <georges.da-costa@irit.fr>
+
+    This file is part of Mojitos.
+
+    Mojitos is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Mojitos is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with MojitO/S.  If not, see <https://www.gnu.org/licenses/>.
+
+ *******************************************************/
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdint.h>
+#include "util.h"
+
+#define BUFFER_SIZE 512
+
+struct Temperature {
+    char **label_list;
+    int *fid_list;
+    int nb_elem;
+};
+typedef struct Temperature Temperature;
+
+int get_string(char *filename, char *buffer, int max_size)
+{
+    int fid = open(filename, O_RDONLY);
+
+    //printf("Tries to open : %s : %d\n", filename, fid);
+    if (fid == -1) {
+        return -1;
+    }
+
+    int nb = read(fid, buffer, max_size);
+    if (nb == -1) {
+        close(fid);
+        return -1;
+    }
+
+    buffer[nb] = 0;
+    close(fid);
+    return 0;
+}
+
+void add_to_list(char ***list_name, char *source, int nb_elem)
+{
+    //printf("Adds: %s\n", source);
+    *list_name = realloc(*list_name, (nb_elem + 1) * sizeof(char *));
+    (*list_name)[nb_elem] = malloc(strlen(source) + 1);
+    strcpy((*list_name)[nb_elem], source);
+
+}
+
+void add_temperature_sensor(int id_rep, Temperature *state)
+{
+    static int key = 0;
+    static char buffer_filename[BUFFER_SIZE];
+    static char buffer_label[BUFFER_SIZE];
+
+    int delta = snprintf(buffer_label, BUFFER_SIZE, "Temp_%d_", key);
+
+    for (int i = 1;; i++) {
+        snprintf(buffer_filename, BUFFER_SIZE, "/sys/class/hwmon/hwmon%d/temp%d_label", id_rep, i);
+
+        if (get_string(buffer_filename, buffer_label + delta, 100) == -1) {
+            break;
+        }
+
+        for (unsigned int pos = 0; pos < strlen(buffer_label); pos++) {
+            if (buffer_label[pos] == ' ') {
+                buffer_label[pos] = '_';
+            }
+
+            if (buffer_label[pos] == '\n') {
+                buffer_label[pos] = '\0';
+            }
+        }
+
+        add_to_list(&state->label_list, buffer_label, state->nb_elem);
+
+        snprintf(buffer_filename, BUFFER_SIZE, "/sys/class/hwmon/hwmon%d/temp%d_input", id_rep, i);
+        state->fid_list = realloc(state->fid_list, (state->nb_elem + 1) * sizeof(int));
+        int fd = open(buffer_filename, O_RDONLY);
+
+        if (fd < 0) {
+            fprintf(stderr, "%s ", buffer_filename);
+            perror("open");
+            exit(1);
+        }
+
+        state->fid_list[state->nb_elem] = fd;
+        state->nb_elem++;
+        // printf("%s : %s\n", buffer_label, buffer_filename);
+    }
+
+    key++;
+}
+
+unsigned int init_temperature(char *args, void **ptr)
+{
+    UNUSED(args);
+    Temperature *state = malloc(sizeof(Temperature));
+    state->nb_elem = 0;
+    state->label_list = NULL;
+    state->fid_list = NULL;
+
+    char base_name[] = "/sys/class/hwmon/hwmon%d/name";
+    static char name[BUFFER_SIZE];
+    static char buffer[BUFFER_SIZE];
+
+    int i = 0;
+    snprintf(name, BUFFER_SIZE, base_name, i);
+
+    while (get_string(name, buffer, 8) != -1) {
+        if (strcmp(buffer, "coretemp") == 0) {
+            add_temperature_sensor(i, state);
+        }
+
+        i++;
+        snprintf(name, BUFFER_SIZE, base_name, i);
+    }
+
+    *ptr = (void *) state;
+    return state->nb_elem;
+}
+
+unsigned int get_temperature(uint64_t *results, void *ptr)
+{
+    Temperature *state = (Temperature *)ptr;
+    static char buffer[BUFFER_SIZE];
+
+    for (int i = 0; i < state->nb_elem; i++) {
+        if (pread(state->fid_list[i], buffer, 100, 0) < 0) {
+            perror("pread");
+            exit(1);
+        }
+        results[i] = strtoull(buffer, NULL, 10);
+    }
+
+    return state->nb_elem;
+}
+
+void clean_temperature(void *ptr)
+{
+    Temperature *state = (Temperature *)ptr;
+
+    for (int i = 0; i < state->nb_elem; i++) {
+        free(state->label_list[i]);
+        close(state->fid_list[i]);
+    }
+
+    free(state->label_list);
+    free(state->fid_list);
+    free(state);
+}
+
+void label_temperature(char **labels, void *ptr)
+{
+    Temperature *state = (Temperature *)ptr;
+
+    for (int i = 0; i < state->nb_elem; i++) {
+        labels[i] = state->label_list[i];
+    }
+}
diff --git a/temperature.h b/src/temperature.h
similarity index 58%
rename from temperature.h
rename to src/temperature.h
index c6c34df6c612e709764e2b7d9dfd37ec234ccbf5..e70554d17ab7878b788bbee3afa96da72620cfe2 100644
--- a/temperature.h
+++ b/src/temperature.h
@@ -1,5 +1,5 @@
 /*******************************************************
- Copyright (C) 2018-2021 Georges Da Costa <georges.da-costa@irit.fr>
+ Copyright (C) 2018-2023 Georges Da Costa <georges.da-costa@irit.fr>
 
     This file is part of Mojitos.
 
@@ -18,7 +18,25 @@
 
  *******************************************************/
 
-unsigned int init_temperature(char*, void **);
-unsigned int get_temperature(uint64_t* results, void*);
+unsigned int init_temperature(char *, void **);
+unsigned int get_temperature(uint64_t *results, void *);
 void clean_temperature(void *);
-void label_temperature(char **labels, void*);
+void label_temperature(char **labels, void *);
+
+Sensor temperature = {
+    .init = init_temperature,
+    .get = get_temperature,
+    .clean = clean_temperature,
+    .label = label_temperature,
+    .nb_opt = 1,
+};
+
+Optparse temperature_opt[1] = {
+    {
+        .longname = "cpu-temp",
+        .shortname = 'c',
+        .argtype = OPTPARSE_NONE,
+        .usage_arg = NULL,
+        .usage_msg = "processor temperature"
+    },
+};
diff --git a/frapl.h b/src/util.c
similarity index 77%
rename from frapl.h
rename to src/util.c
index 6b2d15424723ba2f670718525e294aa75eaba90e..5e835a7f21583cdca51707b97ecf122aa3e4910f 100644
--- a/frapl.h
+++ b/src/util.c
@@ -16,10 +16,13 @@
     You should have received a copy of the GNU General Public License
     along with MojitO/S.  If not, see <https://www.gnu.org/licenses/>.
 
- *******************************************************/
+*******************************************************/
 
-unsigned int init_frapl(char*, void **);
-unsigned int get_frapl(uint64_t* results, void*);
-void clean_frapl(void *);
-void label_frapl(char **labels, void*);
+#include "util.h"
 
+
+uint64_t modulo_substraction(const uint64_t lhs, const uint64_t rhs)
+{
+    return lhs >= rhs ? (lhs - rhs)
+           : (UINT64_MAX - rhs + 1) + lhs;
+}
diff --git a/src/util.h b/src/util.h
new file mode 100644
index 0000000000000000000000000000000000000000..e422d070693cd28af9613e77a5efdb917ce6b2f6
--- /dev/null
+++ b/src/util.h
@@ -0,0 +1,51 @@
+/*******************************************************
+ Copyright (C) 2023-2023 Georges Da Costa <georges.da-costa@irit.fr>
+
+    This file is part of Mojitos.
+
+    Mojitos is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Mojitos is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with MojitO/S.  If not, see <https://www.gnu.org/licenses/>.
+
+*******************************************************/
+
+#ifndef _UTIL_H
+#define _UTIL_H
+
+#include <stdint.h>
+
+#define CASSERT(predicate, file) _impl_CASSERT_LINE(predicate,__LINE__,file)
+
+#define _impl_PASTE(a,b) a##b
+#define _impl_CASSERT_LINE(predicate, line, file) \
+    typedef char _impl_PASTE(assertion_failed_##file##_,line)[2*!!(predicate)-1];
+
+#define UNUSED(expr) do { (void)(expr); } while (0)
+#define PANIC(code, fmt, ...)                \
+    do {                                     \
+        fprintf(stderr, "Exit on error: ");  \
+        fprintf(stderr, fmt, ##__VA_ARGS__); \
+        fprintf(stderr, "\n");               \
+        exit(code);                          \
+    } while (0)
+
+
+/**
+ * @brief Substracts lhs by rhs, assuming that lhs is a cyclic increment from rhs,
+ * meaning that if rhs is greater, lhs's value overflowed.
+ * @param lhs
+ * @param rhs
+ * @return uint64_t
+ */
+uint64_t modulo_substraction(const uint64_t lhs, const uint64_t rhs);
+
+#endif
diff --git a/temperature.c b/temperature.c
deleted file mode 100644
index 142e86ae61604d920c92563196cffef9ad043386..0000000000000000000000000000000000000000
--- a/temperature.c
+++ /dev/null
@@ -1,137 +0,0 @@
-/*******************************************************
- Copyright (C) 2018-2021 Georges Da Costa <georges.da-costa@irit.fr>
-
-    This file is part of Mojitos.
-
-    Mojitos is free software: you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation, either version 3 of the License, or
-    (at your option) any later version.
-
-    Mojitos is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with MojitO/S.  If not, see <https://www.gnu.org/licenses/>.
-
- *******************************************************/
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <string.h>
-#include <stdio.h>
-#include <stdint.h>
-
-struct temperature_t {
-  char **label_list;
-  int *fid_list;
-  int nb_elem;
-};
-  
-int get_string(char *filename, char *buffer, int max_size) {
-  int fid = open(filename, O_RDONLY);
-  //printf("Tries to open : %s : %d\n", filename, fid);
-  if(fid == -1)
-    return -1;
-      
-  int nb = read(fid, buffer, max_size);
-  if(nb == -1) {
-    close(fid);
-    return -1;
-  }
-
-  buffer[nb]=0;
-  close(fid);
-  return 0;
-}
-
-void add_to_list(char ***list_name, char *source, int nb_elem) {
-  //printf("Adds: %s\n", source);
-  *list_name = realloc(*list_name, (nb_elem+1)*sizeof(char*));
-  (*list_name)[nb_elem] = malloc(strlen(source)+1);
-  strcpy((*list_name)[nb_elem], source);
-
-}
-
-void add_temperature_sensor(int id_rep, struct temperature_t *state) {
-  static int key=0;
-  static char buffer_filename[512];
-  static char buffer_label[512];
-  
-  int delta = sprintf(buffer_label, "Temp_%d_", key);
-  
-  for(int i=1;;i++) {
-    sprintf(buffer_filename, "/sys/class/hwmon/hwmon%d/temp%d_label", id_rep, i);
-    if(get_string(buffer_filename, buffer_label+delta, 100) == -1)
-      break;
-
-    for(int pos = 0; pos < strlen(buffer_label); pos++) {
-      if (buffer_label[pos] == ' ')  buffer_label[pos] = '_';
-      if (buffer_label[pos] == '\n') buffer_label[pos] = '\0';
-    }
-    add_to_list(&state->label_list, buffer_label, state->nb_elem);
-
-    sprintf(buffer_filename, "/sys/class/hwmon/hwmon%d/temp%d_input", id_rep, i);
-    state->fid_list = realloc(state->fid_list, (state->nb_elem+1)*sizeof(int));
-    state->fid_list[state->nb_elem] = open(buffer_filename, O_RDONLY);
-
-    state->nb_elem++;
-    // printf("%s : %s\n", buffer_label, buffer_filename);
-  }
-  
-  key++;
-}
-
-unsigned int init_temperature(char *args, void** ptr) {
-  struct temperature_t *state = malloc(sizeof(struct temperature_t));
-  state->nb_elem = 0;
-  state->label_list = NULL;
-  state->fid_list = NULL;
-  
-  char base_name[] = "/sys/class/hwmon/hwmon%d/name";
-  static char name[512];
-  static char buffer[512];
-
-  int i = 0;
-  sprintf(name, base_name, i);
-  while(get_string(name, buffer, 8) != -1) {
-    if (strcmp(buffer, "coretemp")==0)
-      add_temperature_sensor(i, state);
-
-    i++;
-    sprintf(name, base_name, i);
-  }
-  *ptr = (void*) state;
-  return state->nb_elem;
-}
-
-unsigned int get_temperature(uint64_t* results, void* ptr) {
-  struct temperature_t *state = (struct temperature_t*)ptr;
-  static char buffer[512];
-  for(int i=0; i<state->nb_elem; i++) {
-    pread(state->fid_list[i], buffer, 100, 0);
-    results[i] = strtoull(buffer, NULL, 10);
-  }
-  return state->nb_elem;
-}
-
-void clean_temperature(void* ptr) {
-  struct temperature_t *state = (struct temperature_t*)ptr;
-  
-  for(int i=0; i<state->nb_elem; i++) {
-    free(state->label_list[i]);
-    close(state->fid_list[i]);
-  }
-  free(state->label_list);
-  free(state->fid_list);
-  free(state);
-}
-
-void label_temperature(char**labels, void* ptr) {
-  struct temperature_t *state = (struct temperature_t*)ptr;
-  for(int i=0; i<state->nb_elem; i++)
-    labels[i] = state->label_list[i];
-}
diff --git a/tests/amd_rapl.c b/tests/amd_rapl.c
new file mode 100644
index 0000000000000000000000000000000000000000..7c05ab5826954a7f4d7127c9fa3b18896af31e86
--- /dev/null
+++ b/tests/amd_rapl.c
@@ -0,0 +1,349 @@
+/*******************************************************
+ Copyright (C) 2023-2023 Georges Da Costa <georges.da-costa@irit.fr>
+
+    This file is part of Mojitos.
+
+    Mojitos is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Mojitos is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with MojitO/S.  If not, see <https://www.gnu.org/licenses/>.
+
+*******************************************************/
+
+#include "small_test.h"
+#include "../src/amd_rapl.c"
+
+TFUNCTION(test_raw_to_microjoule, {
+    uint64_t raw = 0;
+    uint64_t unit = 0;
+    uint64_t result = 0;
+    uint64_t expected = 0;
+
+    // Test 1:
+    // -- Setup
+    raw = 100;
+    unit = 1000;
+    expected = 390625;
+    // -- Run
+    result = raw_to_microjoule(raw, unit);
+    // -- Verification
+    TEST_UINT64_T(&result, &expected);
+
+    // TEST 2:
+    // -- Setup
+    raw = 200;
+    unit = 1;
+    expected = 100000000;
+    // -- Run
+    result = raw_to_microjoule(raw, unit);
+    // -- Verification
+    TEST_UINT64_T(&result, &expected);
+
+    // TEST 3:
+    // -- Setup
+    raw = 500;
+    unit = 2;
+    expected = 125000000;
+    // -- Run
+    result = raw_to_microjoule(raw, unit);
+    // -- Verification
+    TEST_UINT64_T(&result, &expected);
+
+    // TEST 4:
+    // -- Setup
+    raw = 1000;
+    unit = 3;
+    expected = 125000000;
+    // -- Run
+    result = raw_to_microjoule(raw, unit);
+    // -- Verification
+    TEST_UINT64_T(&result, &expected);
+
+    // TEST 5:
+    // -- Setup
+    raw = 10000;
+    unit = 4;
+    expected = 625000000;
+    // -- Run
+    result = raw_to_microjoule(raw, unit);
+    // -- Verification
+    TEST_UINT64_T(&result, &expected);
+})
+
+TFUNCTION(test_get_name, {
+    size_t cpu_id = 0;
+    char *result = NULL;
+    char expected[100];
+
+    // TEST 1:
+    // -- Setup
+    cpu_id = 0;
+    strcpy(expected, "core0");
+    // -- Run
+    result = get_name(cpu_id);
+    // -- Verification
+    TEST_STR(result, expected);
+    free(result);
+
+    // TEST 2:
+    // -- Setup
+    cpu_id = 10000;
+    strcpy(expected, "core10000");
+    // -- Run
+    result = get_name(cpu_id);
+    // -- Verification
+    TEST_STR(result, expected);
+    free(result);
+})
+
+#define NONE 0
+#define DUMMY_SENSOR(__sensor, __cpu_id, __name) \
+do {                                             \
+  __sensor = (CpuSensor) {                    \
+    .cpu_id = __cpu_id,                          \
+    .package_id = NONE,                          \
+    .core_id = NONE,                             \
+    .name = __name,                              \
+    .fd = NONE,                                  \
+    .energy_units = NONE,                        \
+    .core_energy = NONE,                         \
+  };                                             \
+} while(0);
+
+#define DUMMY_RAPL(__rapl, __sensors, __sensors_count) \
+do {                                                   \
+    __rapl = (AmdRapl) {                           \
+        .sensors = __sensors,                          \
+        .sensor_count = __sensors_count                \
+    };                                                 \
+} while(0);
+
+TFUNCTION(test_label_amd_rapl, {
+    CpuSensor sensors[100];
+    AmdRapl rapl;
+    char *results[100];
+    char expecteds[10][100];
+    uint64_t nb = 0;
+
+    // Test 1:
+    // -- Setup
+    nb = 1;
+    DUMMY_SENSOR(sensors[0], 0, "core0");
+    DUMMY_RAPL(rapl, sensors, nb);
+    strcpy(expecteds[0], "core0");
+    // -- Run
+    label_amd_rapl(results, (void *) &rapl);
+    // -- Verification
+    for(unsigned int i = 0; i < nb; i++)
+    {
+        TEST_STR(results[i], expecteds[i]);
+    }
+
+    // Test 2:
+    // -- Setup
+    nb = 4;
+    DUMMY_SENSOR(sensors[0], 0, "core0");
+    DUMMY_SENSOR(sensors[1], 1, "core1");
+    DUMMY_SENSOR(sensors[2], 2, "core2");
+    DUMMY_SENSOR(sensors[3], 3, "core3");
+    DUMMY_RAPL(rapl, sensors, nb);
+    strcpy(expecteds[0], "core0");
+    strcpy(expecteds[1], "core1");
+    strcpy(expecteds[2], "core2");
+    strcpy(expecteds[3], "core3");
+    // -- Run
+    label_amd_rapl(results, (void *) &rapl);
+    // -- Verification
+    for(unsigned int i = 0; i < nb; i++)
+    {
+        TEST_STR(results[i], expecteds[i]);
+    }
+    // Test 3:
+    // -- Setup
+    nb = 4;
+    DUMMY_SENSOR(sensors[0], 0, "core0");
+    DUMMY_SENSOR(sensors[1], 3, "core3");
+    DUMMY_SENSOR(sensors[2], 1, "core1");
+    DUMMY_SENSOR(sensors[3], 2, "core2");
+    DUMMY_RAPL(rapl, sensors, nb);
+    strcpy(expecteds[0], "core0");
+    strcpy(expecteds[1], "core3");
+    strcpy(expecteds[2], "core1");
+    strcpy(expecteds[3], "core2");
+    // -- Run
+    label_amd_rapl(results, (void *) &rapl);
+    // -- Verification
+    for(unsigned int i = 0; i < nb; i++)
+    {
+        TEST_STR(results[i], expecteds[i]);
+    }
+})
+
+
+#define DUMMY_CPUINFO(__sensor, __cpu_id, __package_id, __core_id) \
+do {                                                               \
+    __sensor = (CpuSensor) {                                    \
+        .cpu_id = __cpu_id,                                        \
+        .package_id = __package_id,                                \
+        .core_id = __core_id,                                      \
+        .name = NULL,                                              \
+        .fd = NONE,                                                \
+        .energy_units = NONE,                                      \
+        .core_energy = NONE                                        \
+    };                                                             \
+} while (0);
+
+TFUNCTION(test_is_duplicate, {
+    static const unsigned int nb_core = 4;
+    static const unsigned int nb_package = 2;
+    static const unsigned int max_cpu = 10;
+
+    unsigned char map[nb_core][nb_package];
+    CpuSensor cpu_information[max_cpu];
+    unsigned int results[max_cpu];
+    unsigned int expecteds[max_cpu];
+
+    // -- Setup
+    memset(map, NONE, sizeof(map));
+    memset(cpu_information,NONE, sizeof(CpuSensor) * max_cpu);
+    DUMMY_CPUINFO(cpu_information[0], 0, 1, 1);
+    expecteds[0] = 1;
+    expecteds[1] = 0;
+    // -- Run
+    results[0] = is_duplicate(&cpu_information[0], nb_core, nb_package,map);
+    results[1] = is_duplicate(&cpu_information[0], nb_core, nb_package,map);
+    // -- Verification
+    TEST_BOOL(&results[0], &expecteds[0]);
+    TEST_BOOL(&results[1], &expecteds[1]);
+
+    // -- Setup
+    memset(map, NONE, sizeof(map));
+    memset(cpu_information,NONE, sizeof(CpuSensor) * max_cpu);
+    DUMMY_CPUINFO(cpu_information[0], 0, 1, 1);
+    DUMMY_CPUINFO(cpu_information[1], 0, 1, 1);
+    expecteds[0] = 1;
+    expecteds[1] = 0;
+    // -- Run
+    results[0] = is_duplicate(&cpu_information[0], nb_core, nb_package, map);
+    results[1] = is_duplicate(&cpu_information[1], nb_core, nb_package, map);
+    // -- Verification
+    TEST_BOOL(&results[0], &expecteds[0]);
+    TEST_BOOL(&results[1], &expecteds[1]);
+
+    // -- Setup
+    memset(map, NONE, sizeof(map));
+    memset(cpu_information,NONE, sizeof(CpuSensor) * max_cpu);
+    DUMMY_CPUINFO(cpu_information[0], 0, 1, 1);
+    DUMMY_CPUINFO(cpu_information[1], 0, 0, 0);
+    expecteds[0] = 1;
+    expecteds[1] = 1;
+    // -- Run
+    results[0] = is_duplicate(&cpu_information[0], nb_core, nb_package, map);
+    results[1] = is_duplicate(&cpu_information[1], nb_core, nb_package, map);
+    // -- Verification
+    TEST_BOOL(&results[0], &expecteds[0]);
+    TEST_BOOL(&results[1], &expecteds[1]);
+
+    // -- Setup
+    memset(map, NONE, sizeof(map));
+    memset(cpu_information,NONE, sizeof(CpuSensor) * max_cpu);
+    DUMMY_CPUINFO(cpu_information[0], 0, 1, 1);
+    DUMMY_CPUINFO(cpu_information[1], 0, 1, 0);
+    expecteds[0] = 1;
+    expecteds[1] = 1;
+    // -- Run
+    results[0] = is_duplicate(&cpu_information[0], nb_core, nb_package, map);
+    results[1] = is_duplicate(&cpu_information[1], nb_core, nb_package, map);
+    // -- Verification
+    TEST_BOOL(&results[0], &expecteds[0]);
+    TEST_BOOL(&results[1], &expecteds[1]);
+
+    // -- Setup
+    memset(map, NONE, sizeof(map));
+    memset(cpu_information,NONE, sizeof(CpuSensor) * max_cpu);
+    DUMMY_CPUINFO(cpu_information[0], 0, 1, 1);
+    DUMMY_CPUINFO(cpu_information[1], 0, 0, 1);
+    expecteds[0] = 1;
+    expecteds[1] = 1;
+    // -- Run
+    results[0] = is_duplicate(&cpu_information[0], nb_core, nb_package, map);
+    results[1] = is_duplicate(&cpu_information[1], nb_core, nb_package, map);
+    // -- Verification
+    TEST_BOOL(&results[0], &expecteds[0]);
+    TEST_BOOL(&results[1], &expecteds[1]);
+
+    // -- Setup
+    memset(map, NONE, sizeof(map));
+    memset(cpu_information,NONE, sizeof(CpuSensor) * max_cpu);
+    DUMMY_CPUINFO(cpu_information[0], 0, 0, 0);
+    DUMMY_CPUINFO(cpu_information[1], 0, 0, 1);
+    DUMMY_CPUINFO(cpu_information[2], 0, 1, 0);
+    DUMMY_CPUINFO(cpu_information[3], 0, 1, 1);
+    DUMMY_CPUINFO(cpu_information[4], 0, 0, 0);
+    DUMMY_CPUINFO(cpu_information[5], 0, 0, 1);
+    DUMMY_CPUINFO(cpu_information[6], 0, 1, 0);
+    DUMMY_CPUINFO(cpu_information[7], 0, 1, 1);
+    memset(expecteds, 1, sizeof(unsigned int) * 4);
+    memset(&expecteds[4], 0, sizeof(unsigned int) * 4);
+    // -- Run
+    for (unsigned int i = 0; i < 8; i++)
+    {
+        results[i] = is_duplicate(&cpu_information[i], nb_core, nb_package, map );
+    }
+    // -- Verification
+    for(unsigned int i = 0; i < 8; i++)
+    {
+        TEST_BOOL(&results[i], &expecteds[i]);
+    }
+})
+
+
+TFILE_ENTRY_POINT(test_amd_rapl, {
+    CALL_TFUNCTION(test_raw_to_microjoule);
+    CALL_TFUNCTION(test_get_name);
+    CALL_TFUNCTION(test_label_amd_rapl);
+    CALL_TFUNCTION(test_is_duplicate);
+})
+
+#ifdef __TESTING__AMD__
+int main()
+{
+    static const unsigned int time = 10;
+    AmdRapl *rapl = NULL;
+    unsigned int count_cpu = init_amd_rapl(NULL, (void **) &rapl);
+    uint64_t results[count_cpu];
+    char *labels[count_cpu];
+
+    label_amd_rapl(labels, (void *) rapl);
+
+    for (unsigned int i = 0; i < rapl->sensor_count; ++i) {
+        printf("%s ", labels[i]);
+    }
+    printf("\n");
+
+    // -- Run
+
+    for (unsigned int i = 0; i < time; ++i) {
+        sleep(1);
+        get_amd_rapl(results, (void *)rapl);
+
+        for (unsigned int j = 0; j < rapl->sensor_count; ++j) {
+            printf("%ld ", results[j]);
+        }
+        printf("\n");
+    }
+
+    clean_amd_rapl(rapl);
+    return 0;
+}
+
+#endif
+
diff --git a/tests/info_reader.c b/tests/info_reader.c
new file mode 100644
index 0000000000000000000000000000000000000000..5000f485e7218676974af828bc75d33da474ddd4
--- /dev/null
+++ b/tests/info_reader.c
@@ -0,0 +1,284 @@
+/*******************************************************
+ Copyright (C) 2023-2023 Georges Da Costa <georges.da-costa@irit.fr>
+
+    This file is part of Mojitos.
+
+    Mojitos is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Mojitos is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with MojitO/S.  If not, see <https://www.gnu.org/licenses/>.
+
+*******************************************************/
+
+#include "small_test.h"
+#include "./../lib/info_reader.h"
+
+TFUNCTION(test_replace_first, {
+    // useful variables :
+    char result[100];
+    char expected[100];
+
+    // -- Setup
+    strcpy(result, "This is my string");
+    strcpy(expected, "ThIs is my string");
+    // -- Run
+    replace_first(result, 'i', 'I');
+    // -- Verification
+    TEST_STR(result, expected);
+
+    // -- Setup
+    strcpy(result, "This is my string");
+    strcpy(expected, "This is my string");
+    // -- Run
+    replace_first(result, 'x', 'X');
+    // -- Verification
+    TEST_STR(result, expected);
+
+    // -- Setup
+    strcpy(result, "This is my string");
+    strcpy(expected, "This_is my string");
+    // -- Run
+    replace_first(result, ' ', '_');
+    // -- Verification
+    TEST_STR(result, expected);
+
+    // -- Setup
+    strcpy(result, "This is my string");
+    strcpy(expected, "This_is my string");
+    // -- Run
+    replace_first(result, ' ', '_');
+    // -- Verification
+    TEST_STR(result, expected);
+
+    // -- Setup
+    strcpy(result, "This is my string");
+    strcpy(expected, "This is my string");
+    // -- Run
+    replace_first(result, 'T', 'T');
+    // -- Verification
+    TEST_STR(result, expected);
+})
+
+TFUNCTION(test_split_on_delimiter, {
+    // Useful variables
+    char string[100];
+    char *result_key;
+    char *result_value;
+    char expected_key[100];
+    char expected_value[100];
+
+    // Setup
+    strcpy(string, "key:value");
+    strcpy(expected_key, "key");
+    strcpy(expected_value, "value");
+    // Run
+    split_on_delimiter(string, ":", &result_key, &result_value);
+    // Verification
+    TEST_STR(result_key, expected_key);
+    TEST_STR(result_value, expected_value);
+
+    // Setup
+    strcpy(string, "key: value");
+    strcpy(expected_key, "key");
+    strcpy(expected_value, " value");
+    // Run
+    split_on_delimiter(string, ":", &result_key, &result_value);
+    // Verification
+    TEST_STR(result_key, expected_key);
+    TEST_STR(result_value, expected_value);
+
+    // Setup
+    strcpy(string, "key:value");
+    strcpy(expected_key, "key");
+    strcpy(expected_value, "value");
+    replace_first(string, ':', ' ');
+    // Run
+    split_on_delimiter(string, " ", &result_key, &result_value);
+    // Verification
+    TEST_STR(result_key, expected_key);
+    TEST_STR(result_value, expected_value);
+
+    // Setup
+    strcpy(string, "");
+    // Run
+    split_on_delimiter(string, ":", &result_key, &result_value);
+    // Verification
+    TEST_STR(result_key, NULL);
+    TEST_STR(result_value, NULL);
+
+    // Setup
+    strcpy(string, "key:value:extra");
+    strcpy(expected_key, "key");
+    strcpy(expected_value, "value:extra");
+    // Run
+    split_on_delimiter(string, ":", &result_key, &result_value);
+    // Verification
+    TEST_STR(result_key, expected_key);
+    TEST_STR(result_value, expected_value);
+
+    // Setup
+    strcpy(string, "key: value :extra");
+    strcpy(expected_key, "key");
+    strcpy(expected_value, " value :extra");
+    // Run
+    split_on_delimiter(string, ":", &result_key, &result_value);
+    // Verification
+    TEST_STR(result_key, expected_key);
+    TEST_STR(result_value, expected_value);
+})
+
+TFUNCTION(test_start_with, {
+    char *prefix = NULL;
+    char *string = NULL;
+    bool result = false;
+    bool _true = true;
+    bool _false = false;
+
+    prefix = "Hello";
+    string = "Hello World";
+    result = start_with(prefix, string);
+    TEST_BOOL(&result, &_true);
+
+    prefix = "Goodbye";
+    string = "Hello World";
+    result = start_with(prefix, string);
+    TEST_BOOL(&result, &_false);
+
+    prefix = "Hello World";
+    string = "Hello";
+    result = start_with(prefix, string);
+    TEST_BOOL(&result, &_false);
+
+    prefix = "Hello";
+    string = "Hello";
+    result = start_with(prefix, string);
+    TEST_BOOL(&result, &_true);
+
+    prefix = NULL;
+    string = "Hello World";
+    result = start_with(prefix, string);
+    TEST_BOOL(&result, &_false);
+})
+
+#define NONE 0
+#define DUMMY_KEYFINDER(__key_finder, __key, __delimiter) \
+  do {                                                    \
+    __key_finder = (KeyFinder) {                          \
+      .key = __key,                                       \
+      .delimiter = __delimiter,                           \
+      .copy = NONE,                                       \
+      .set = NONE                                         \
+    };                                                    \
+  } while (0);
+
+#define DUMMY_PARSER(__parser, __keys, __nb_keys) \
+  do {                                            \
+    __parser = (Parser) {                         \
+      .storage = NONE,                            \
+      .nb_stored = NONE,                          \
+      .capacity = NONE,                           \
+      .storage_struct_size = NONE,                \
+      .keys = __keys,                             \
+      .nb_keys = __nb_keys,                       \
+      .file = NONE                                \
+    };                                            \
+  } while (0);
+
+
+TFUNCTION(test_match, {
+    // useful variables :
+    bool _true = true;
+    bool _false = false;
+    KeyFinder keys[10];
+    char line[100];
+    Parser parser;
+    bool result;
+    KeyFinder *found_key_finder = NULL;
+    char *raw_value = NULL;
+
+    // Test 1:
+    // -- Setup
+    DUMMY_KEYFINDER(keys[0], "key", ": ");
+    DUMMY_PARSER(parser, keys, 1);
+    strcpy(line, "key: value");
+    found_key_finder = NULL;
+    raw_value = NULL;
+    // -- Run
+    result = match(&parser, line, &found_key_finder, &raw_value);
+    // -- Verification
+    TEST_BOOL(&result, &_true);
+    TEST_PTR(found_key_finder, &keys[0]);
+    TEST_STR(raw_value, "value");
+
+    // Test 2:
+    // -- Setup
+    DUMMY_KEYFINDER(keys[0], "key", ": ");
+    DUMMY_PARSER(parser, keys, 1)
+    strcpy(line, "not a key: value");
+    found_key_finder = NULL;
+    raw_value = NULL;
+    // -- Run
+    result = match(&parser, line, &found_key_finder, &raw_value);
+    // -- Verification
+    TEST_BOOL(&result, &_false);
+    TEST_PTR(found_key_finder, NULL);
+    TEST_STR(raw_value, NULL);
+
+    // Test 3:
+    // -- Setup
+    DUMMY_KEYFINDER(keys[0],"key", ": ");
+    DUMMY_PARSER(parser, keys, 1);
+    strcpy(line, "key:value");
+    found_key_finder = NULL;
+    raw_value = NULL;
+    // -- Run
+    result = match(&parser, line, &found_key_finder, &raw_value);
+    // -- Verification
+    TEST_BOOL(&result, &_false);
+    TEST_PTR(found_key_finder, NULL);
+    TEST_STR(raw_value, NULL);
+
+    // Test 4:
+    // -- Setup
+    DUMMY_KEYFINDER(keys[0], "key", ": ");
+    DUMMY_KEYFINDER(keys[1], "second_key", ": ");
+    DUMMY_PARSER(parser, keys, 2);
+    strcpy(line, "second_key: value");
+    found_key_finder = NULL;
+    raw_value = NULL;
+    // -- Run
+    result = match(&parser, line, &found_key_finder, &raw_value);
+    // -- Verification
+    TEST_BOOL(&result, &_true);
+    TEST_PTR(found_key_finder, &keys[1]);
+    TEST_STR(raw_value, "value");
+
+    // Test 5:
+    // -- Setup
+    DUMMY_KEYFINDER(keys[0], "key", ": ");
+    DUMMY_PARSER(parser, keys, 1);
+    strcpy(line, "");
+    found_key_finder = NULL;
+    raw_value = NULL;
+    // -- Run
+    result = match(&parser, line, &found_key_finder, &raw_value);
+    TEST_BOOL(&result, &_false);
+    TEST_PTR(found_key_finder, NULL);
+    TEST_STR(raw_value, NULL);
+})
+
+TFILE_ENTRY_POINT(test_info_reader, {
+    CALL_TFUNCTION(test_replace_first);
+    CALL_TFUNCTION(test_split_on_delimiter);
+    CALL_TFUNCTION(test_start_with);
+    CALL_TFUNCTION(test_match);
+})
+
diff --git a/network.h b/tests/main.c
similarity index 71%
rename from network.h
rename to tests/main.c
index 44759dcf883ac66c90aafdf6447d00d6b02e9a39..3246f0429a7ff7ad065070ac195428af7e099872 100644
--- a/network.h
+++ b/tests/main.c
@@ -1,5 +1,5 @@
 /*******************************************************
- Copyright (C) 2018-2019 Georges Da Costa <georges.da-costa@irit.fr>
+ Copyright (C) 2023-2023 Georges Da Costa <georges.da-costa@irit.fr>
 
     This file is part of Mojitos.
 
@@ -16,10 +16,14 @@
     You should have received a copy of the GNU General Public License
     along with MojitO/S.  If not, see <https://www.gnu.org/licenses/>.
 
- *******************************************************/
+*******************************************************/
 
-unsigned int init_network(char*, void **);
-unsigned int get_network(uint64_t* results, void*);
-void clean_network(void *);
-void label_network(char **labels, void*);
+#include "util.c"
+#include "amd_rapl.c"
+#include "info_reader.c"
 
+TMAIN({
+    CALL_TFUNCTION(test_util);
+    CALL_TFUNCTION(test_amd_rapl);
+    CALL_TFUNCTION(test_info_reader);
+})
diff --git a/tests/small_test.h b/tests/small_test.h
new file mode 100644
index 0000000000000000000000000000000000000000..66c4de9119ddd3f67b0b9db85e299487074fd6e1
--- /dev/null
+++ b/tests/small_test.h
@@ -0,0 +1,393 @@
+/*******************************************************
+ Copyright (C) 2023-2023 Georges Da Costa <georges.da-costa@irit.fr>
+
+    This file is part of Mojitos.
+
+    Mojitos is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Mojitos is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with MojitO/S.  If not, see <https://www.gnu.org/licenses/>.
+
+*******************************************************/
+
+#ifndef __SMALL_TEST_H
+#define __SMALL_TEST_H
+
+#include <stdbool.h>
+#include <string.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "../src/util.h"
+
+#define FMT_BUFFER_SIZE 1000
+
+// ---------------------------API_INTERFACE
+/**
+ * @def TMAIN(__code)
+ *
+ * @brief Define the entry point for the tests.
+ * It initialises any useful variables and acts as the main one.
+ *
+ * @param __code The code that contains the calls to the test functions.
+ */
+
+#define TMAIN(__code)                            \
+  int main()                                     \
+{                                                \
+  unsigned int __indentation_level = 0;          \
+  INDENTED_PRINT("%s:%s\n", __FILE__, __func__); \
+  unsigned int __error_counter__ = 0;            \
+  do __code while (0);                           \
+  DEFERRED_ERROR(__error_counter__);             \
+  return __error_counter__;                      \
+}
+
+/**
+ * @def TFILE_ENTRY_POINT(__filename, __code)
+ *
+ * @brief Define the entry point of a test file.
+ * This macro is used to define the entry point of a test file.
+ * It defines a function with the specified __filename that contains the test code specified in code.
+ *
+ * When the function is called, it initialises the test file using the INIT_TEST_FILE macro,
+ * declares an integer variable __error_counter__ to keep track of any errors encountered during the tests,
+ * executes the test code in a do-while loop, and then checks for any deferred errors using the DEFERRED_ERROR macro.
+ * The function returns the value of __error_counter__,
+ * which indicates the number of errors encountered during the tests.
+ *
+ * @param __filename The name of the function to be used as an entry point for the test file.
+ * @param __code The test code to be executed in the function.
+ */
+#define TFILE_ENTRY_POINT(__filename, __code)       \
+  int __filename (unsigned int __indentation_level) \
+{                                                   \
+  INIT_TEST_FILE();                                 \
+  int __error_counter__ = 0;                        \
+  do __code while(0);                               \
+  DEFERRED_ERROR(__error_counter__);                \
+  return __error_counter__;                         \
+}
+
+/**
+ * @def TFUNCTION(__function_name, __code)
+ *
+ * @brief Define a test function within a test file.
+ * This macro is used to define a test function within a test file.
+ * It defines a function with the given __function_name containing the test code specified in __code.
+ *
+ * When the function is called, it initialises the test function using the INIT_TEST_FUNCTION macro,
+ * declares an integer variable __error_counter__ to keep track of any errors encountered during the tests,
+ * executes the test code in a do-while loop, and then checks for any deferred errors using the DEFERRED_ERROR macro.
+ * The function returns the value of __error_counter__, which indicates the number of errors encountered during the tests.
+ *
+ * @param __function_name The name of the test function.
+ * @param __code The test code to be executed in the function.
+ */
+#define TFUNCTION(__function_name, __code)              \
+  int __function_name(unsigned int __indentation_level) \
+{                                                       \
+  INIT_TEST_FUNCTION();                                 \
+  int __error_counter__ = 0;                            \
+  do __code while(0);                                   \
+  DEFERRED_ERROR(__error_counter__);                    \
+  return __error_counter__;                             \
+}
+
+/**
+ * @def CALL_TFUNCTION(__function_name)
+ *
+ * @brief Call a test function within a test file.
+ * This macro is used to call a test function within a test file.
+ * It calls the function specified by __function_name and adds the return value to the __error_counter__ variable.
+ * This allows multiple test functions to be executed and their error count to be accumulated.
+ *
+ * @param __function_name The name of the test function to be called.
+ */
+#define CALL_TFUNCTION(__function_name) \
+  do {__error_counter__ += __function_name(__indentation_level + 1);} while(0)
+
+/**
+ * @def TEST_STR(__result, __expected)
+ *
+ * @brief Test strings
+ * This macro is used to test strings. It takes two arguments: `__result`, which is the string to test, and `__expected`,
+ * which is the expected value of the string.
+ *
+ * @param __result the string to test.
+ * @param __expected the expected value of the string.
+ *
+ * @code
+ * TEST_STR("Hello", "Hello");
+ * @endcode
+ */
+#define TEST_STR(__result, __expected) \
+    do {__error_counter__ += test(__FILE__, __LINE__, __indentation_level, __result, __expected, &str_interface);} while(0)
+
+/**
+ * @def TEST_BOOL(__result, __expected)
+ *
+ * @brief Test bools
+ * This macro is used to test bools. It takes two arguments: `__result`, which is the bool to test, and `__expected`,
+ * which is the expected value of the bool.
+ *
+ * @param __result the pointer to bool to test.
+ * @param __expected the pointer to the expected value of the bool.
+ *
+ * @code
+ * bool x = true;
+ * bool y = true;
+ * TEST_BOOL(&x, &y);
+ * @endcode
+ */
+#define TEST_BOOL(__result, __expected) \
+    do {__error_counter__ += test(__FILE__, __LINE__, __indentation_level, __result, __expected, &bool_interface);} while (0)
+
+/**
+ * @def TEST_INT(__result, __expected)
+ *
+ * @brief Test ints
+ * This macro is used to test ints. It takes two arguments: `__result`, which is the int to be test, and `__expected`,
+ * which is the expected value of the int.
+ *
+ * @param __result the pointer to int to test.
+ * @param __expected the pointer to expected value of the int.
+ *
+ * @code
+ * int x = 1;
+ * int y = 1;
+ * TEST_INT(&x, &y)
+ * @endcode
+ */
+#define TEST_INT(__result, __expected) \
+    do {__error_counter__ += test(__FILE__, __LINE__, __indentation_level, __result, __expected, &int_interface);} while (0)
+
+/**
+ * @def TEST_PTR(__result, __expected)
+ *
+ * @brief Test pointers
+ * This macro is used to test pointers. It takes two arguments: `__result`, which is the pointer to test, and `__expected`,
+ * which is the expected value of the pointer.
+ *
+ * @param __result the pointer to test.
+ * @param __expected the expected value of the pointer.
+ *
+ * @code
+ * int x = 5;
+ * int *ptr = &x;
+ * TEST_PTR(ptr, &x);
+ * @endcode
+ */
+#define TEST_PTR(__result, __expected) \
+	do {__error_counter__ += test(__FILE__, __LINE__, __indentation_level, __result, __expected, &ptr_interface);} while(0)
+
+
+/**
+ * @def TEST_UINT64_T(__result, __expected)
+ *
+ * @brief Test 64-bit unsigned integers
+ * This macro is used to test 64-bit unsigned integers. It takes two arguments: `__result`,
+ * which is the integer to test, and `__expected`, which is the expected value of the integer.
+ *
+ * @param __result the pointer to integer to test.
+ * @param __expected the pointer to expected value of the integer.
+ *
+ * @code
+ * uint64_t x = 5;
+ * uint64_t y = 5;
+ * TEST_UINT64_T(&x, &y);
+ * @endcode
+ */
+#define TEST_UINT64_T(__result, __expected) \
+    do {__error_counter__ += test(__FILE__, __LINE__, __indentation_level, __result, __expected, &u64_interface);} while(0)
+
+/**
+ * @def TEST_INTERFACE(__result, __expected, __interface)
+ *
+ * @brief Define a macro on a usertype with the given __interface.
+ * This macro is used by the user to define a new test macro on a usertype.
+ *
+ * @param __result
+ * @param __expected
+ * @param __interface the interface of the usertype.
+ *
+ * @code
+ * TestInterface usertype_interface = {.compare = ..., .format = ...};
+ * #define TEST_USERTYPE(__result, __expected) \
+ * TEST_INTERFACE(__result, __expected, &usertype_interface)
+ * @endcode
+ */
+#define TEST_INTERFACE(__result, __expected, __interface) \
+    do {__error_counter__ += test(__FILE__, __LINE__, __indentation_level, __result, __expected, __interface);} while(0)
+
+
+
+
+// ------------------------------------CODE
+// These functions should not be in use, only the previous macros should be in use.
+
+#define INDENTED_PRINT(__fmt, ...)                          \
+  do {                                                      \
+    for(unsigned int i = 0; i < __indentation_level; i++) { \
+      printf("|    ");                                      \
+    }                                                       \
+    printf(__fmt, ##__VA_ARGS__);                           \
+  } while(0)
+
+
+#define INIT_TEST_FILE() \
+  INDENTED_PRINT("%s:%s\n", __FILE__, __func__)
+
+#define INIT_TEST_FUNCTION() \
+  INDENTED_PRINT("%s()\n", __func__);
+
+#define DEFERRED_ERROR(nb_error) \
+    INDENTED_PRINT("|_Deferred Error : %d\n",nb_error);
+
+typedef int (Comparator) (void *, void *);
+typedef char *(Formatter) (char[FMT_BUFFER_SIZE], void *);
+
+typedef struct {
+    Comparator *compare;
+    Formatter *format;
+} TestInterface;
+
+// ---------------------------TEST FUNCTION
+
+int test(char *file, int line, unsigned int __indentation_level, void *result, void *expected, const TestInterface *interface)
+{
+    __indentation_level += 1;
+    static char buffer_result[FMT_BUFFER_SIZE];
+    static char buffer_expected[FMT_BUFFER_SIZE];
+    int is_equal = interface->compare(result, expected);
+
+    char *fmt_result = interface->format(buffer_expected, expected);
+    char *fmt_expected = interface->format(buffer_result, result);
+    if  (!is_equal) {
+        INDENTED_PRINT("%s:%d: failed, expected <%s>, got <%s>\n", file, line, fmt_expected, fmt_result);
+    }
+    return !is_equal;
+}
+
+// ------------------------------INTERFACES
+
+// -- str_interface
+int str_compare(void *ptr1, void *ptr2)
+{
+    char *str1 = (char *) ptr1;
+    char *str2 = (char *) ptr2;
+
+    if (str1 == NULL && str2 == NULL) {
+        return 1;
+    } else if (str1 == NULL || str2 == NULL) {
+        return 0;
+    } else {
+        return (strcmp(str1, str2) == 0);
+    }
+}
+
+char *str_format(char buffer[FMT_BUFFER_SIZE], void *ptr)
+{
+    UNUSED(buffer);
+    static char *str_null = "NULL";
+    char *str = (char *) ptr;
+    return str ? str : str_null;
+}
+
+static const TestInterface str_interface = {
+    .compare = str_compare,
+    .format = str_format
+};
+
+// -- bool_interface
+
+int bool_compare(void *ptr1, void *ptr2)
+{
+    bool *bool1 = (bool *) ptr1;
+    bool *bool2 = (bool *) ptr2;
+    return *bool1 == *bool2;
+}
+
+char *bool_format(char buffer[FMT_BUFFER_SIZE], void *ptr)
+{
+    UNUSED(buffer);
+    bool *_bool = (bool *) ptr;
+    return *_bool ? "True" : "False";
+}
+
+static const TestInterface bool_interface = {
+    .compare = bool_compare,
+    .format = bool_format
+};
+
+// -- int_interface
+
+int int_compare(void *ptr1, void *ptr2)
+{
+    int *int1 = (int *) ptr1;
+    int *int2 = (int *) ptr2;
+    return *int1 == *int2;
+}
+
+char *int_format(char buffer[FMT_BUFFER_SIZE], void *ptr)
+{
+    int *_int = (int *) ptr;
+    snprintf(buffer, FMT_BUFFER_SIZE, "%d", *_int);
+    return buffer;
+}
+
+static const TestInterface int_interface = {
+    .compare = int_compare,
+    .format = int_format
+};
+
+// -- ptr_interface
+
+int ptr_compare(void *ptr1, void *ptr2)
+{
+    return ptr1 == ptr2;
+}
+
+char *ptr_format(char buffer[FMT_BUFFER_SIZE], void *ptr)
+{
+    snprintf(buffer, FMT_BUFFER_SIZE, "%p", ptr);
+    return buffer;
+}
+
+static const TestInterface ptr_interface = {
+    .compare = ptr_compare,
+    .format = ptr_format
+};
+
+// -- u64_interface
+
+int u64_compare(void *ptr1, void *ptr2)
+{
+    uint64_t *v1 = (uint64_t *) ptr1;
+    uint64_t *v2 = (uint64_t *) ptr2;
+    return *v1 == *v2;
+}
+
+char *u64_format(char buffer[FMT_BUFFER_SIZE], void *ptr)
+{
+    uint64_t *v = (uint64_t *) ptr;
+    snprintf(buffer, FMT_BUFFER_SIZE, "%"PRIu64"", *v);
+    return buffer;
+}
+
+static const TestInterface u64_interface = {
+    .compare = u64_compare,
+    .format = u64_format
+};
+
+#endif
+
diff --git a/tests/util.c b/tests/util.c
new file mode 100644
index 0000000000000000000000000000000000000000..69941bfac22fac87d56cd282989721c0c3dc0d76
--- /dev/null
+++ b/tests/util.c
@@ -0,0 +1,82 @@
+/*******************************************************
+ Copyright (C) 2023-2023 Georges Da Costa <georges.da-costa@irit.fr>
+
+    This file is part of Mojitos.
+
+    Mojitos is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Mojitos is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with MojitO/S.  If not, see <https://www.gnu.org/licenses/>.
+
+*******************************************************/
+
+#include "../src/util.h"
+#include "small_test.h"
+
+
+TFUNCTION(test_modulo_substraction, {
+    uint64_t lhs = 0;
+    uint64_t rhs = 0;
+    uint64_t result = 0;
+    uint64_t expected = 0;
+
+    // Test 1:
+    // -- Setup
+    lhs = 10;
+    rhs = 10;
+    expected = 0;
+    // -- Run
+    result = modulo_substraction(lhs, rhs);
+    // -- Verification
+    TEST_UINT64_T(&result, &expected);
+
+    // Test 2:
+    // -- Setup
+    lhs = UINT64_MAX;
+    rhs = 0;
+    expected = UINT64_MAX;
+    // -- Run
+    result = modulo_substraction(lhs, rhs);
+    // -- Verification
+    TEST_UINT64_T(&result, &expected);
+
+    // Test 3:
+    // -- Setup
+    lhs = 0;
+    rhs = UINT64_MAX;
+    expected = 1;
+    // -- Run
+    result = modulo_substraction(lhs, rhs);
+    // -- Verification
+    TEST_UINT64_T(&result, &expected);
+
+    // Test 4:
+    // -- Setup
+    lhs = 10;
+    rhs = 20;
+    expected = UINT64_MAX - 9;
+    // -- Run
+    result = modulo_substraction(lhs, rhs);
+    // -- Verification
+    TEST_UINT64_T(&result, &expected);
+
+    // Test 5:
+    // -- Setup
+    lhs = 1000;
+    rhs = 1000;
+    expected = 0;
+    result = modulo_substraction(lhs, rhs);
+    TEST_UINT64_T(&result, &expected);
+})
+
+TFILE_ENTRY_POINT(test_util, {
+    CALL_TFUNCTION(test_modulo_substraction);
+})
diff --git a/tools/update-readme-usage.sh b/tools/update-readme-usage.sh
new file mode 100755
index 0000000000000000000000000000000000000000..2c1950ec6ad9f5e2681e968b251d80f8c335969d
--- /dev/null
+++ b/tools/update-readme-usage.sh
@@ -0,0 +1,50 @@
+#!/bin/sh
+
+# SPDX-License-Identifier: GPL-3.0-or-later
+# Copyright (C) 2023-2023 Georges Da Costa <georges.da-costa@irit.fr>
+
+die() { yell "$*"; exit 111; }
+try() { "$@" || die "cannot $*"; }
+yell() { echo "$0: $*" >&2; }
+echo() { printf '%s\n' "$*"; }
+
+try ./configure.sh --all
+try make mojitos
+usage=$(
+	./bin/mojitos |
+		awk '
+			/^SENSORS/ {
+				$0 = ""
+				printf "```\n"
+				printf "\n"
+				printf "The following is an exhaustive list of all the sensors (it is very likely\n"
+				printf "that one will not have all the sensors activated in his build):\n"
+				printf "```bash\n"
+				printf "SENSORS:"
+			}
+			{ print }
+		'
+)
+[ -n "$usage" ] || die 'empty usage. cannot continue.'
+
+try awk -v "usage=$usage" '
+	/^Usage/ {
+		print usage
+		del = 1
+	}
+	{
+		if (del == 1 || del == 2) {
+			if (match($0, "^```")) {
+				del++
+			}
+		} else if (del == 3) {
+			if (match($0, "^```")) {
+				del = 0
+				print $0
+			}
+		} else {
+			print $0
+		}
+	}
+' README.md > README.tmp
+try mv README.tmp README.md