mirror of
https://github.com/Zygo/bees.git
synced 2025-07-02 00:32:27 +02:00
seeker: add a runtime debug stream
This allows detailed but selective debugging when using the library, particularly when something goes wrong. Signed-off-by: Zygo Blaxell <bees@furryterror.org>
This commit is contained in:
@ -6,23 +6,23 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
|
||||||
#include <cstdint>
|
// Debug stream
|
||||||
|
#include <memory>
|
||||||
#if 0
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#define DINIT(__x) __x
|
|
||||||
#define DLOG(__x) do { logs << __x << std::endl; } while (false)
|
#include <cstdint>
|
||||||
#define DOUT(__err) do { __err << logs.str(); } while (false)
|
|
||||||
#else
|
|
||||||
#define DINIT(__x) do {} while (false)
|
|
||||||
#define DLOG(__x) do {} while (false)
|
|
||||||
#define DOUT(__x) do {} while (false)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace crucible {
|
namespace crucible {
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
extern thread_local shared_ptr<ostream> tl_seeker_debug_str;
|
||||||
|
#define SEEKER_DEBUG_LOG(__x) do { \
|
||||||
|
if (tl_seeker_debug_str) { \
|
||||||
|
(*tl_seeker_debug_str) << __x << "\n"; \
|
||||||
|
} \
|
||||||
|
} while (false)
|
||||||
|
|
||||||
// Requirements for Container<Pos> Fetch(Pos lower, Pos upper):
|
// Requirements for Container<Pos> Fetch(Pos lower, Pos upper):
|
||||||
// - fetches objects in Pos order, starting from lower (must be >= lower)
|
// - fetches objects in Pos order, starting from lower (must be >= lower)
|
||||||
// - must return upper if present, may or may not return objects after that
|
// - must return upper if present, may or may not return objects after that
|
||||||
@ -49,113 +49,107 @@ namespace crucible {
|
|||||||
Pos
|
Pos
|
||||||
seek_backward(Pos const target_pos, Fetch fetch, Pos min_step = 1, size_t max_loops = numeric_limits<size_t>::max())
|
seek_backward(Pos const target_pos, Fetch fetch, Pos min_step = 1, size_t max_loops = numeric_limits<size_t>::max())
|
||||||
{
|
{
|
||||||
DINIT(ostringstream logs);
|
static const Pos end_pos = numeric_limits<Pos>::max();
|
||||||
try {
|
// TBH this probably won't work if begin_pos != 0, i.e. any signed type
|
||||||
static const Pos end_pos = numeric_limits<Pos>::max();
|
static const Pos begin_pos = numeric_limits<Pos>::min();
|
||||||
// TBH this probably won't work if begin_pos != 0, i.e. any signed type
|
// Run a binary search looking for the highest key below target_pos.
|
||||||
static const Pos begin_pos = numeric_limits<Pos>::min();
|
// Initial upper bound of the search is target_pos.
|
||||||
// Run a binary search looking for the highest key below target_pos.
|
// Find initial lower bound by doubling the size of the range until a key below target_pos
|
||||||
// Initial upper bound of the search is target_pos.
|
// is found, or the lower bound reaches the beginning of the search space.
|
||||||
// Find initial lower bound by doubling the size of the range until a key below target_pos
|
// If the lower bound search reaches the beginning of the search space without finding a key,
|
||||||
// is found, or the lower bound reaches the beginning of the search space.
|
// return the beginning of the search space; otherwise, perform a binary search between
|
||||||
// If the lower bound search reaches the beginning of the search space without finding a key,
|
// the bounds now established.
|
||||||
// return the beginning of the search space; otherwise, perform a binary search between
|
Pos lower_bound = 0;
|
||||||
// the bounds now established.
|
Pos upper_bound = target_pos;
|
||||||
Pos lower_bound = 0;
|
bool found_low = false;
|
||||||
Pos upper_bound = target_pos;
|
Pos probe_pos = target_pos;
|
||||||
bool found_low = false;
|
// We need one loop for each bit of the search space to find the lower bound,
|
||||||
Pos probe_pos = target_pos;
|
// one loop for each bit of the search space to find the upper bound,
|
||||||
// We need one loop for each bit of the search space to find the lower bound,
|
// and one extra loop to confirm the boundary is correct.
|
||||||
// one loop for each bit of the search space to find the upper bound,
|
for (size_t loop_count = min(numeric_limits<Pos>::digits * size_t(2) + 1, max_loops); loop_count; --loop_count) {
|
||||||
// and one extra loop to confirm the boundary is correct.
|
SEEKER_DEBUG_LOG("fetch(probe_pos = " << probe_pos << ", target_pos = " << target_pos << ")");
|
||||||
for (size_t loop_count = min(numeric_limits<Pos>::digits * size_t(2) + 1, max_loops); loop_count; --loop_count) {
|
auto result = fetch(probe_pos, target_pos);
|
||||||
DLOG("fetch(probe_pos = " << probe_pos << ", target_pos = " << target_pos << ")");
|
const Pos low_pos = result.empty() ? end_pos : *result.begin();
|
||||||
auto result = fetch(probe_pos, target_pos);
|
const Pos high_pos = result.empty() ? end_pos : *result.rbegin();
|
||||||
const Pos low_pos = result.empty() ? end_pos : *result.begin();
|
SEEKER_DEBUG_LOG(" = " << low_pos << ".." << high_pos);
|
||||||
const Pos high_pos = result.empty() ? end_pos : *result.rbegin();
|
// check for correct behavior of the fetch function
|
||||||
DLOG(" = " << low_pos << ".." << high_pos);
|
THROW_CHECK2(out_of_range, high_pos, probe_pos, probe_pos <= high_pos);
|
||||||
// check for correct behavior of the fetch function
|
THROW_CHECK2(out_of_range, low_pos, probe_pos, probe_pos <= low_pos);
|
||||||
THROW_CHECK2(out_of_range, high_pos, probe_pos, probe_pos <= high_pos);
|
THROW_CHECK2(out_of_range, low_pos, high_pos, low_pos <= high_pos);
|
||||||
THROW_CHECK2(out_of_range, low_pos, probe_pos, probe_pos <= low_pos);
|
if (!found_low) {
|
||||||
THROW_CHECK2(out_of_range, low_pos, high_pos, low_pos <= high_pos);
|
// if target_pos == end_pos then we will find it in every empty result set,
|
||||||
if (!found_low) {
|
// so in that case we force the lower bound to be lower than end_pos
|
||||||
// if target_pos == end_pos then we will find it in every empty result set,
|
if ((target_pos == end_pos) ? (low_pos < target_pos) : (low_pos <= target_pos)) {
|
||||||
// so in that case we force the lower bound to be lower than end_pos
|
// found a lower bound, set the low bound there and switch to binary search
|
||||||
if ((target_pos == end_pos) ? (low_pos < target_pos) : (low_pos <= target_pos)) {
|
found_low = true;
|
||||||
// found a lower bound, set the low bound there and switch to binary search
|
lower_bound = low_pos;
|
||||||
found_low = true;
|
SEEKER_DEBUG_LOG("found_low = true, lower_bound = " << lower_bound);
|
||||||
lower_bound = low_pos;
|
} else {
|
||||||
DLOG("found_low = true, lower_bound = " << lower_bound);
|
// still looking for lower bound
|
||||||
} else {
|
// if probe_pos was begin_pos then we can stop with no result
|
||||||
// still looking for lower bound
|
if (probe_pos == begin_pos) {
|
||||||
// if probe_pos was begin_pos then we can stop with no result
|
SEEKER_DEBUG_LOG("return: probe_pos == begin_pos " << begin_pos);
|
||||||
if (probe_pos == begin_pos) {
|
return begin_pos;
|
||||||
DLOG("return: probe_pos == begin_pos " << begin_pos);
|
|
||||||
return begin_pos;
|
|
||||||
}
|
|
||||||
// double the range size, or use the distance between objects found so far
|
|
||||||
THROW_CHECK2(out_of_range, upper_bound, probe_pos, probe_pos <= upper_bound);
|
|
||||||
// already checked low_pos <= high_pos above
|
|
||||||
const Pos want_delta = max(upper_bound - probe_pos, min_step);
|
|
||||||
// avoid underflowing the beginning of the search space
|
|
||||||
const Pos have_delta = min(want_delta, probe_pos - begin_pos);
|
|
||||||
THROW_CHECK2(out_of_range, want_delta, have_delta, have_delta <= want_delta);
|
|
||||||
// move probe and try again
|
|
||||||
probe_pos = probe_pos - have_delta;
|
|
||||||
DLOG("probe_pos " << probe_pos << " = probe_pos - have_delta " << have_delta << " (want_delta " << want_delta << ")");
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
// double the range size, or use the distance between objects found so far
|
||||||
|
THROW_CHECK2(out_of_range, upper_bound, probe_pos, probe_pos <= upper_bound);
|
||||||
|
// already checked low_pos <= high_pos above
|
||||||
|
const Pos want_delta = max(upper_bound - probe_pos, min_step);
|
||||||
|
// avoid underflowing the beginning of the search space
|
||||||
|
const Pos have_delta = min(want_delta, probe_pos - begin_pos);
|
||||||
|
THROW_CHECK2(out_of_range, want_delta, have_delta, have_delta <= want_delta);
|
||||||
|
// move probe and try again
|
||||||
|
probe_pos = probe_pos - have_delta;
|
||||||
|
SEEKER_DEBUG_LOG("probe_pos " << probe_pos << " = probe_pos - have_delta " << have_delta << " (want_delta " << want_delta << ")");
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
if (low_pos <= target_pos && target_pos <= high_pos) {
|
|
||||||
// have keys on either side of target_pos in result
|
|
||||||
// search from the high end until we find the highest key below target
|
|
||||||
for (auto i = result.rbegin(); i != result.rend(); ++i) {
|
|
||||||
// more correctness checking for fetch
|
|
||||||
THROW_CHECK2(out_of_range, *i, probe_pos, probe_pos <= *i);
|
|
||||||
if (*i <= target_pos) {
|
|
||||||
DLOG("return: *i " << *i << " <= target_pos " << target_pos);
|
|
||||||
return *i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// if the list is empty then low_pos = high_pos = end_pos
|
|
||||||
// if target_pos = end_pos also, then we will execute the loop
|
|
||||||
// above but not find any matching entries.
|
|
||||||
THROW_CHECK0(runtime_error, result.empty());
|
|
||||||
}
|
|
||||||
if (target_pos <= low_pos) {
|
|
||||||
// results are all too high, so probe_pos..low_pos is too high
|
|
||||||
// lower the high bound to the probe pos
|
|
||||||
upper_bound = probe_pos;
|
|
||||||
DLOG("upper_bound = probe_pos " << probe_pos);
|
|
||||||
}
|
|
||||||
if (high_pos < target_pos) {
|
|
||||||
// results are all too low, so probe_pos..high_pos is too low
|
|
||||||
// raise the low bound to the high_pos
|
|
||||||
DLOG("lower_bound = high_pos " << high_pos);
|
|
||||||
lower_bound = high_pos;
|
|
||||||
}
|
|
||||||
// compute a new probe pos at the middle of the range and try again
|
|
||||||
// we can't have a zero-size range here because we would not have set found_low yet
|
|
||||||
THROW_CHECK2(out_of_range, lower_bound, upper_bound, lower_bound <= upper_bound);
|
|
||||||
const Pos delta = (upper_bound - lower_bound) / 2;
|
|
||||||
probe_pos = lower_bound + delta;
|
|
||||||
if (delta < 1) {
|
|
||||||
// nothing can exist in the range (lower_bound, upper_bound)
|
|
||||||
// and an object is known to exist at lower_bound
|
|
||||||
DLOG("return: probe_pos == lower_bound " << lower_bound);
|
|
||||||
return lower_bound;
|
|
||||||
}
|
|
||||||
THROW_CHECK2(out_of_range, lower_bound, probe_pos, lower_bound <= probe_pos);
|
|
||||||
THROW_CHECK2(out_of_range, upper_bound, probe_pos, probe_pos <= upper_bound);
|
|
||||||
DLOG("loop: lower_bound " << lower_bound << ", probe_pos " << probe_pos << ", upper_bound " << upper_bound);
|
|
||||||
}
|
}
|
||||||
THROW_ERROR(runtime_error, "FIXME: should not reach this line: "
|
if (low_pos <= target_pos && target_pos <= high_pos) {
|
||||||
"lower_bound..upper_bound " << lower_bound << ".." << upper_bound << ", "
|
// have keys on either side of target_pos in result
|
||||||
"found_low " << found_low);
|
// search from the high end until we find the highest key below target
|
||||||
} catch (...) {
|
for (auto i = result.rbegin(); i != result.rend(); ++i) {
|
||||||
DOUT(cerr);
|
// more correctness checking for fetch
|
||||||
throw;
|
THROW_CHECK2(out_of_range, *i, probe_pos, probe_pos <= *i);
|
||||||
|
if (*i <= target_pos) {
|
||||||
|
SEEKER_DEBUG_LOG("return: *i " << *i << " <= target_pos " << target_pos);
|
||||||
|
return *i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if the list is empty then low_pos = high_pos = end_pos
|
||||||
|
// if target_pos = end_pos also, then we will execute the loop
|
||||||
|
// above but not find any matching entries.
|
||||||
|
THROW_CHECK0(runtime_error, result.empty());
|
||||||
|
}
|
||||||
|
if (target_pos <= low_pos) {
|
||||||
|
// results are all too high, so probe_pos..low_pos is too high
|
||||||
|
// lower the high bound to the probe pos, low_pos cannot be lower
|
||||||
|
SEEKER_DEBUG_LOG("upper_bound = probe_pos " << probe_pos);
|
||||||
|
upper_bound = probe_pos;
|
||||||
|
}
|
||||||
|
if (high_pos < target_pos) {
|
||||||
|
// results are all too low, so probe_pos..high_pos is too low
|
||||||
|
// raise the low bound to high_pos since it's above probe_pos
|
||||||
|
SEEKER_DEBUG_LOG("lower_bound = high_pos " << high_pos);
|
||||||
|
lower_bound = high_pos;
|
||||||
|
}
|
||||||
|
// compute a new probe pos at the middle of the range and try again
|
||||||
|
// we can't have a zero-size range here because we would not have set found_low yet
|
||||||
|
THROW_CHECK2(out_of_range, lower_bound, upper_bound, lower_bound <= upper_bound);
|
||||||
|
const Pos delta = (upper_bound - lower_bound) / 2;
|
||||||
|
probe_pos = lower_bound + delta;
|
||||||
|
if (delta < 1) {
|
||||||
|
// nothing can exist in the range (lower_bound, upper_bound)
|
||||||
|
// and an object is known to exist at lower_bound
|
||||||
|
SEEKER_DEBUG_LOG("return: probe_pos == lower_bound " << lower_bound);
|
||||||
|
return lower_bound;
|
||||||
|
}
|
||||||
|
THROW_CHECK2(out_of_range, lower_bound, probe_pos, lower_bound <= probe_pos);
|
||||||
|
THROW_CHECK2(out_of_range, upper_bound, probe_pos, probe_pos <= upper_bound);
|
||||||
|
SEEKER_DEBUG_LOG("loop bottom: lower_bound " << lower_bound << ", probe_pos " << probe_pos << ", upper_bound " << upper_bound);
|
||||||
}
|
}
|
||||||
|
THROW_ERROR(runtime_error, "FIXME: should not reach this line: "
|
||||||
|
"lower_bound..upper_bound " << lower_bound << ".." << upper_bound << ", "
|
||||||
|
"found_low " << found_low);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ CRUCIBLE_OBJS = \
|
|||||||
openat2.o \
|
openat2.o \
|
||||||
path.o \
|
path.o \
|
||||||
process.o \
|
process.o \
|
||||||
|
seeker.o \
|
||||||
string.o \
|
string.o \
|
||||||
table.o \
|
table.o \
|
||||||
task.o \
|
task.o \
|
||||||
|
7
lib/seeker.cc
Normal file
7
lib/seeker.cc
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#include "crucible/seeker.h"
|
||||||
|
|
||||||
|
namespace crucible {
|
||||||
|
|
||||||
|
thread_local shared_ptr<ostream> tl_seeker_debug_str;
|
||||||
|
|
||||||
|
};
|
@ -36,6 +36,8 @@ seeker_test(const vector<uint64_t> &vec, uint64_t const target)
|
|||||||
}
|
}
|
||||||
cerr << " } = ";
|
cerr << " } = ";
|
||||||
size_t loops = 0;
|
size_t loops = 0;
|
||||||
|
tl_seeker_debug_str = make_shared<ostringstream>();
|
||||||
|
bool local_test_fails = false;
|
||||||
bool excepted = catch_all([&]() {
|
bool excepted = catch_all([&]() {
|
||||||
auto found = seek_backward(target, [&](uint64_t lower, uint64_t upper) {
|
auto found = seek_backward(target, [&](uint64_t lower, uint64_t upper) {
|
||||||
++loops;
|
++loops;
|
||||||
@ -52,13 +54,15 @@ seeker_test(const vector<uint64_t> &vec, uint64_t const target)
|
|||||||
cerr << " (correct)";
|
cerr << " (correct)";
|
||||||
} else {
|
} else {
|
||||||
cerr << " (INCORRECT - right answer is " << my_found << ")";
|
cerr << " (INCORRECT - right answer is " << my_found << ")";
|
||||||
test_fails = true;
|
local_test_fails = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
cerr << " (" << loops << " loops)" << endl;
|
cerr << " (" << loops << " loops)" << endl;
|
||||||
if (excepted) {
|
if (excepted || local_test_fails) {
|
||||||
|
cerr << dynamic_pointer_cast<ostringstream>(tl_seeker_debug_str)->str();
|
||||||
test_fails = true;
|
test_fails = true;
|
||||||
}
|
}
|
||||||
|
tl_seeker_debug_str.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static
|
||||||
|
Reference in New Issue
Block a user