Skip to content

Commit a0f4b2b

Browse files
authored
Improved Error Handling (#161)
* added aborting mechanism and enhanced the output in example 01 * defined NOGDI for windows to remove silly clashes from a polluted global namespae -- thanks ms * added calls to tirex::abort() * added error reporting to python * remove outdated comment from measure cmd * oops :D Signed-off-by: Sheldon <[email protected]> * apply patch for cjson * fixed minor issues * Fixed an issue with the error handling test --------- Signed-off-by: Sheldon <[email protected]>
1 parent 19f75a1 commit a0f4b2b

File tree

23 files changed

+767
-409
lines changed

23 files changed

+767
-409
lines changed
Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
add_executable(01_tracking
22
main.c
3+
plotting.cpp
34
)
45

5-
target_link_libraries(01_tracking tirex::tracker)
6+
target_link_libraries(01_tracking tirex::tracker)
7+
8+
9+
##########################################################################################
10+
# Libraries
11+
##########################################################################################
12+
# asciichart for simple plotting
13+
CPMAddPackage(URI "gh:Civitasv/asciichart#7464831085adcc51f7389fd6a825cc648a99e82f" EXCLUDE_FROM_ALL YES)
14+
target_include_directories(01_tracking PRIVATE ${asciichart_SOURCE_DIR}/include)
15+
16+
# cJSON for lightweight parsing of the results
17+
CPMAddPackage(URI "gh:DaveGamble/[email protected]" EXCLUDE_FROM_ALL YES PATCHES "${CMAKE_CURRENT_LIST_DIR}/cjson.patch")
18+
target_link_libraries(01_tracking cjson)
19+
target_include_directories(01_tracking PRIVATE ${cJSON_SOURCE_DIR}) # cJSON does not seem to set this properly so we have to

c/examples/01_tracking/cjson.patch

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
--- a/CMakeLists.txt
2+
+++ b/CMakeLists.txt
3+
@@ -1,5 +1,5 @@
4+
set(CMAKE_LEGACY_CYGWIN_WIN32 0)
5+
-cmake_minimum_required(VERSION 3.0)
6+
+cmake_minimum_required(VERSION 3.20)
7+
8+
project(cJSON
9+
VERSION 1.7.19

c/examples/01_tracking/main.c

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ int fib(int n) {
110110
return fib(n - 1) + fib(n - 2);
111111
}
112112

113+
extern void plot(tirexResult* result);
114+
113115
int main(int argc, char* argv[]) {
114116
tirexMeasureHandle* measure;
115117
tirexResult* result;
@@ -177,17 +179,35 @@ int main(int argc, char* argv[]) {
177179
if (tirexStartTracking(providers, 100, &measure) != TIREX_SUCCESS)
178180
abort();
179181
{
182+
// 1) Sleep
180183
thrd_sleep(&(struct timespec){.tv_sec = 1}, NULL);
181-
char* data = calloc(24 * 1000 * 1000, 1); // allocate 24 MB
182-
for (size_t i = 0; i < 24 * 1000 * 1000; ++i) // Access the data so it is not optimized away
183-
data[i] = 1;
184-
thrd_sleep(&(struct timespec){.tv_sec = 1}, NULL);
185-
free(data);
184+
185+
// 2) Allocate Memory then sleep
186+
{
187+
char* data = calloc(24 * 1000 * 1000, 1); // allocate 24 MB
188+
for (size_t i = 0; i < 24 * 1000 * 1000; ++i) // Access the data so it is not optimized away
189+
data[i] = 1;
190+
thrd_sleep(&(struct timespec){.tv_sec = 1}, NULL);
191+
free(data);
192+
}
193+
194+
// 3) Calculate Fibonacci
186195
fib(45);
196+
197+
// 4) Allocate some more memory then sleep
198+
{
199+
char* data = calloc(48 * 1000 * 1000, 1); // allocate 24 MB
200+
for (size_t i = 0; i < 48 * 1000 * 1000; ++i) // Access the data so it is not optimized away
201+
data[i] = 1;
202+
thrd_sleep(&(struct timespec){.tv_sec = 1}, NULL);
203+
free(data);
204+
}
187205
}
188206
if (tirexStopTracking(measure, &result) != TIREX_SUCCESS)
189207
abort();
190208
printResult(result, NULL);
209+
plot(result);
191210
tirexResultFree(result);
211+
192212
return 0;
193213
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#include <tirex_tracker.h>
2+
3+
#include <ascii/ascii.h>
4+
#include <cJSON.h>
5+
6+
extern "C" {
7+
void plot(tirexResult* result);
8+
}
9+
10+
bool tirexResultEntryByMeasure(tirexResult* result, tirexMeasure measure, tirexResultEntry& entry) {
11+
size_t num;
12+
if (tirexResultEntryNum(result, &num) != tirexError::TIREX_SUCCESS)
13+
std::abort();
14+
for (size_t i = 0; i < num; ++i) {
15+
if (tirexResultEntryGetByIndex(result, i, &entry) == tirexError::TIREX_SUCCESS && entry.source == measure)
16+
return true;
17+
}
18+
return false;
19+
}
20+
21+
std::vector<double> parseTimeseries(const char* json) {
22+
cJSON* parsed = cJSON_Parse(json);
23+
auto timeseries = cJSON_GetObjectItem(parsed, "timeseries");
24+
// We ignore the timestamps here. This will result in a somewhat skewed graph since the timesteps may not be equidistant
25+
auto values = cJSON_GetObjectItem(timeseries, "values");
26+
size_t size = cJSON_GetArraySize(values);
27+
std::vector<double> data;
28+
data.reserve(size);
29+
for (size_t i = 0; i < size; ++i) {
30+
auto item = cJSON_GetArrayItem(values, i);
31+
data.push_back(cJSON_GetNumberValue(item));
32+
}
33+
cJSON_Delete(parsed);
34+
return data;
35+
}
36+
37+
std::vector<double> readTimeseriesMeasure(tirexResult* result, tirexMeasure measure) {
38+
tirexResultEntry entry;
39+
if (!tirexResultEntryByMeasure(result, measure, entry))
40+
std::abort();
41+
42+
return parseTimeseries(static_cast<const char*>(entry.value));
43+
}
44+
45+
void plot(tirexResult* result) {
46+
// Note: these two timeseries may not necessarily align in their time-axis
47+
{
48+
auto cpu = readTimeseriesMeasure(result, tirexMeasure::TIREX_CPU_USED_PROCESS_PERCENT);
49+
std::cout << "CPU usage over time:" << std::endl;
50+
ascii::Asciichart asciichart(std::vector<std::vector<double>>{cpu});
51+
std::cout << '\n' << asciichart.height(10).Plot() << '\n';
52+
}
53+
{
54+
auto ram = readTimeseriesMeasure(result, tirexMeasure::TIREX_RAM_USED_PROCESS_KB);
55+
std::cout << "RAM usage over time:" << std::endl;
56+
ascii::Asciichart asciichart(std::vector<std::vector<double>>{ram});
57+
std::cout << '\n' << asciichart.height(10).Plot() << '\n';
58+
}
59+
}

c/examples/03_measure_command/main.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,11 @@ static void runMeasureCmd(const MeasureCmdArgs& args) {
9595
auto logger = tirex::getLogger("measure");
9696
logger->info("Measuring command: {}", args.command);
9797

98+
if (args.pedantic) {
99+
logger->info("Switching to pedantic mode");
100+
tirexSetAbortLevel(tirexLogLevel::WARN);
101+
}
102+
98103
// Start measuring
99104
std::vector<tirexMeasureConf> measures;
100105
for (const auto& provider : args.statproviders) {
@@ -149,7 +154,7 @@ int main(int argc, char* argv[]) {
149154
)
150155
->default_val(100);
151156
app.add_flag("--pedantic", measureArgs.pedantic, "If set, measure will stop execution on errors")
152-
->default_val(false); /** \todo support pedantic **/
157+
->default_val(false);
153158
app.add_option("-o", measureArgs.outfile)->description("Sets the file to write the result measurements into.");
154159

155160
app.callback([&measureArgs]() { runMeasureCmd(measureArgs); });

c/include/tirex_tracker.h

Lines changed: 41 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,35 @@
1717
extern "C" {
1818
#endif
1919

20+
/**
21+
* @defgroup logging Logging
22+
* @details Definitions and functions relevant for telling measure where to log information to.
23+
* @{
24+
*/
25+
/**
26+
* @brief Represents different levels of verbosity.
27+
* @details The enum values are sorted by verbosity. That is, for example, if logs should only be logged \c level
28+
* \c INFO or higher, then `level >= INFO` can be used to check what should be logged.
29+
*/
30+
typedef enum tirexLogLevel_enum { TRACE, DEBUG, INFO, WARN, ERROR, CRITICAL } tirexLogLevel;
31+
32+
/**
33+
* @brief Points to a function that takes in a log level, component, and message which should be logged.
34+
* @details The log callback can handle the call as it likes (including ignoring it for example because logging is set
35+
* to a lower verbosity or turned of). The \p component may be used to identify, where the log is comming from.
36+
*/
37+
typedef void (*tirexLogCallback)(tirexLogLevel level, const char* component, const char* message);
38+
39+
/**
40+
* @brief Set a callback that should be used for all future logs.
41+
* @details Per default, nothing is logged and passing NULL for \p callback disables logging.
42+
*
43+
* @param callback Points to a function that takes in a log level, component, and message which should be logged. Pass
44+
* NULL to disable logging.
45+
*/
46+
TIREX_TRACKER_EXPORT void tirexSetLogCallback(tirexLogCallback callback);
47+
/** @} */ // end of logging
48+
2049
/**
2150
* @defgroup error Error handling
2251
* @details
@@ -26,6 +55,18 @@ extern "C" {
2655
* @brief Categorizes different classes of error.
2756
*/
2857
typedef enum tirexError_enum { TIREX_SUCCESS = 0, TIREX_INVALID_ARGUMENT = 1 } tirexError;
58+
59+
typedef void (*tirexAbortCallback)(const char* message);
60+
61+
TIREX_TRACKER_EXPORT void tirexSetAbortLevel(tirexLogLevel level);
62+
63+
/**
64+
* @brief Set a callback that should be used in case the program needs to abort.
65+
* @details Per default, [`abort()`](https://en.cppreference.com/w/c/program/abort.html) is used.
66+
*
67+
* @param callback
68+
*/
69+
TIREX_TRACKER_EXPORT void tirexSetAbortCallback(tirexAbortCallback callback);
2970
/** @} */ // end of error
3071

3172
/**
@@ -281,35 +322,6 @@ tirexStartTracking(const tirexMeasureConf* measures, size_t pollIntervalMs, tire
281322
TIREX_TRACKER_EXPORT tirexError tirexStopTracking(tirexMeasureHandle* handle, tirexResult** result);
282323
/** @} */ // end of measure
283324

284-
/**
285-
* @defgroup logging Logging
286-
* @details Definitions and functions relevant for telling measure where to log information to.
287-
* @{
288-
*/
289-
/**
290-
* @brief Represents different levels of verbosity.
291-
* @details The enum values are sorted by verbosity. That is, for example, if logs should only be logged \c level
292-
* \c INFO or higher, then `level >= INFO` can be used to check what should be logged.
293-
*/
294-
typedef enum tirexLogLevel_enum { TRACE, DEBUG, INFO, WARN, ERROR, CRITICAL } tirexLogLevel;
295-
296-
/**
297-
* @brief Points to a function that takes in a log level, component, and message which should be logged.
298-
* @details The log callback can handle the call as it likes (including ignoring it for example because logging is set
299-
* to a lower verbosity or turned of). The \p component may be used to identify, where the log is comming from.
300-
*/
301-
typedef void (*tirexLogCallback)(tirexLogLevel level, const char* component, const char* message);
302-
303-
/**
304-
* @brief Set a callback that should be used for all future logs.
305-
* @details Per default, nothing is logged and passing NULL for \p callback disables logging.
306-
*
307-
* @param callback Points to a function that takes in a log level, component, and message which should be logged. Pass
308-
* NULL to disable logging.
309-
*/
310-
TIREX_TRACKER_EXPORT void tirexSetLogCallback(tirexLogCallback callback);
311-
/** @} */ // end of logging
312-
313325
/**
314326
* @defgroup dataprovider Data Providers
315327
* @details Methods and definitions necessary to fetch information about the underlying data sources.

c/src/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@ target_sources(tirex_tracker
2222
${CMAKE_CURRENT_BINARY_DIR}/tirex_tracker_export.h
2323
${TIREX_EXT_INCLUDE_FILES}
2424
PRIVATE
25+
abort.cpp
26+
logging.cpp
2527
measureapi.cpp
2628
measureinfo.cpp
2729
measureresult.cpp
28-
logging.cpp
2930
measure/stats/provider.cpp
3031

3132
measure/stats/energystats.cpp

c/src/abort.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#include "abort.hpp"
2+
#include "logging.hpp"
3+
4+
#include <cstdlib>
5+
6+
static void defaultabort(const char* msg) {
7+
tirex::log::critical("tracker", "Aborting with Error: {}", msg);
8+
std::abort();
9+
}
10+
11+
static tirexLogLevel abortlevel = tirexLogLevel::CRITICAL;
12+
static tirexAbortCallback abortfn = defaultabort;
13+
14+
void tirexSetAbortCallback(tirexAbortCallback callback) { abortfn = (callback == nullptr) ? defaultabort : callback; }
15+
void tirexSetAbortLevel(tirexLogLevel level) { abortlevel = level; }
16+
17+
void tirex::abort(tirexLogLevel level, const char* message) {
18+
if (level >= abortlevel)
19+
abortfn(message);
20+
}

c/src/abort.hpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#ifndef ABORT_HPP
2+
#define ABORT_HPP
3+
4+
#include <tirex_tracker.h>
5+
6+
namespace tirex {
7+
void abort(tirexLogLevel level, const char* message);
8+
} // namespace tirex
9+
10+
#endif

c/src/measure/stats/gitstats.cpp

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "gitstats.hpp"
22

3+
#include "../../abort.hpp"
34
#include "../../logging.hpp"
45
#include "../utils/rangeutils.hpp"
56

@@ -164,9 +165,8 @@ static std::string hashAllFiles(git_repository* repo) {
164165
"gitstats", "I will not include it in the hash. Please add it to the .gitignore if is not part of "
165166
"your codebase or check it into the repository if it should be."
166167
);
168+
tirex::abort(tirexLogLevel::WARN, "Folders that are not checked into the repository are ignored.");
167169
continue;
168-
/** \todo if pedantic abort here **/
169-
// abort();
170170
}
171171
std::ifstream is(root / entry->index_to_workdir->new_file.path, std::ios::binary);
172172
if (!is) {
@@ -218,12 +218,17 @@ repoToArchive(git_repository* repo, const std::filesystem::path& archive) noexce
218218
}
219219
auto source = zip_source_file(handle, path.string().c_str(), 0, 0);
220220
if (source == nullptr) {
221-
tirex::log::error("gitstats", "Error reading file: {}", entry->index_to_workdir->new_file.path);
222-
continue; /** \todo handle pedantic? **/
221+
tirex::log::error(
222+
"gitstats", "Error reading file: {}; I will not add it to the archive",
223+
entry->index_to_workdir->new_file.path
224+
);
225+
tirex::abort(tirexLogLevel::ERROR, "Failed to read file to add it to the archive");
226+
continue;
223227
}
224228
if (zip_file_add(handle, entry->index_to_workdir->new_file.path, source, ZIP_FL_OVERWRITE) < 0) {
225229
tirex::log::error("gitstats", "Error adding file to archive: {}", entry->index_to_workdir->new_file.path);
226-
continue; /** \todo handle pedantic? **/
230+
tirex::abort(tirexLogLevel::ERROR, "Failed to add file to the archive");
231+
continue;
227232
}
228233
}
229234
git_status_list_free(list);

0 commit comments

Comments
 (0)