rewrite soak with more flexibility and ability to record/playback events to reproduce bugs #10
1 changed files with 159 additions and 31 deletions
188
tests/soak.c
188
tests/soak.c
|
@ -17,8 +17,6 @@
|
||||||
|
|
||||||
#define INITIAL_AMOUNT 1024 * 2
|
#define INITIAL_AMOUNT 1024 * 2
|
||||||
|
|
||||||
bool recording = true;
|
|
||||||
|
|
||||||
typedef size_t pgno_t;
|
typedef size_t pgno_t;
|
||||||
|
|
||||||
typedef enum { SM, ML, RB } container_impl_t;
|
typedef enum { SM, ML, RB } container_impl_t;
|
||||||
|
@ -133,6 +131,8 @@ typedef struct container {
|
||||||
|
|
||||||
} container_t;
|
} container_t;
|
||||||
|
|
||||||
|
#define digest(name) containers[type].name##_stats.td
|
||||||
|
|
||||||
char *
|
char *
|
||||||
bytes_as(double bytes, char *s, size_t size)
|
bytes_as(double bytes, char *s, size_t size)
|
||||||
{
|
{
|
||||||
|
@ -316,6 +316,8 @@ b64_decode(const char *in, unsigned char *out, size_t outlen)
|
||||||
|
|
||||||
/* recording ------------------------------------------------------------- */
|
/* recording ------------------------------------------------------------- */
|
||||||
|
|
||||||
|
bool recording = false;
|
||||||
|
|
||||||
static void
|
static void
|
||||||
record_set_mutation(FILE *out, pgno_t pg)
|
record_set_mutation(FILE *out, pgno_t pg)
|
||||||
{
|
{
|
||||||
|
@ -871,7 +873,9 @@ __roar_validate(void *handle)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* histogram ------------------------------------------------------------- */
|
/* statistics ------------------------------------------------------------ */
|
||||||
|
|
||||||
|
bool statistics = false;
|
||||||
|
|
||||||
typedef struct sw {
|
typedef struct sw {
|
||||||
struct timespec t1; /* start time */
|
struct timespec t1; /* start time */
|
||||||
|
@ -892,7 +896,7 @@ elapsed(struct timespec *s, struct timespec *e)
|
||||||
long sec, nanos;
|
long sec, nanos;
|
||||||
|
|
||||||
sec = e->tv_sec - s->tv_sec;
|
sec = e->tv_sec - s->tv_sec;
|
||||||
nanos = e->tv_nsec - e->tv_nsec;
|
nanos = e->tv_nsec - s->tv_nsec;
|
||||||
if (nanos < 0) {
|
if (nanos < 0) {
|
||||||
nanos += 1e9;
|
nanos += 1e9;
|
||||||
sec--;
|
sec--;
|
||||||
|
@ -1102,7 +1106,8 @@ container_t containers[] = {
|
||||||
|
|
||||||
void *handles[(sizeof((containers)) / sizeof((containers)[0]))];
|
void *handles[(sizeof((containers)) / sizeof((containers)[0]))];
|
||||||
void *new_handles[(sizeof((containers)) / sizeof((containers)[0]))];
|
void *new_handles[(sizeof((containers)) / sizeof((containers)[0]))];
|
||||||
FILE *fp;
|
FILE *record_fp;
|
||||||
|
FILE *stats_fp;
|
||||||
|
|
||||||
#define alloc(type, size) containers[type].alloc(size);
|
#define alloc(type, size) containers[type].alloc(size);
|
||||||
#define cast(type, fn, ...) \
|
#define cast(type, fn, ...) \
|
||||||
|
@ -1111,14 +1116,14 @@ FILE *fp;
|
||||||
|
|
||||||
#define invoke(type, fn, ...) __stats_##fn(containers[type].fn##_stats.td, containers[type].fn##_stats.fn, handles[type], __VA_ARGS__)
|
#define invoke(type, fn, ...) __stats_##fn(containers[type].fn##_stats.td, containers[type].fn##_stats.fn, handles[type], __VA_ARGS__)
|
||||||
#define mutate(type, fn, ...) \
|
#define mutate(type, fn, ...) \
|
||||||
(type == 0) ? record_##fn##_mutation(fp, __VA_ARGS__) : (void)0, \
|
(type == 0) ? record_##fn##_mutation(record_fp, __VA_ARGS__) : (void)0, \
|
||||||
__stats_##fn(containers[type].fn##_stats.td, containers[type].fn##_stats.fn, &handles[type], __VA_ARGS__)
|
__stats_##fn(containers[type].fn##_stats.td, containers[type].fn##_stats.fn, &handles[type], __VA_ARGS__)
|
||||||
#define foreach(set) for (unsigned type = 0; type < (sizeof((set)) / sizeof((set)[0])); type++)
|
#define foreach(set) for (unsigned type = 0; type < (sizeof((set)) / sizeof((set)[0])); type++)
|
||||||
#define checkpoint(set) \
|
#define checkpoint(set) \
|
||||||
for (unsigned type = 1; type < (sizeof((set)) / sizeof((set)[0])); type++) { \
|
for (unsigned type = 1; type < (sizeof((set)) / sizeof((set)[0])); type++) { \
|
||||||
verify_eq(0, handles[0], type, handles[type]); \
|
verify_eq(0, handles[0], type, handles[type]); \
|
||||||
} \
|
} \
|
||||||
record_checkpoint(fp, handles[0])
|
record_checkpoint(record_fp, handles[0])
|
||||||
|
|
||||||
bool
|
bool
|
||||||
verify_sm_eq_rb(sparsemap_t *map, roaring_bitmap_t *rbm)
|
verify_sm_eq_rb(sparsemap_t *map, roaring_bitmap_t *rbm)
|
||||||
|
@ -1186,36 +1191,39 @@ verify_eq(unsigned a, void *ad, unsigned b, void *bd)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define SHORT_OPT "r:fa:bh"
|
|
||||||
#define LONG_OPT "record:,force,amount:,buffer,help"
|
|
||||||
|
|
||||||
void
|
void
|
||||||
print_usage(const char *program_name)
|
print_usage(const char *program_name)
|
||||||
{
|
{
|
||||||
printf("Usage: %s [OPTIONS]\n", program_name);
|
printf("Usage: %s [OPTIONS]\n", program_name);
|
||||||
printf(" -r, --record <file> Path to the file for recording (optional)\n");
|
printf(" -r <file> Path to the file for recording (optional)\n");
|
||||||
printf(" -f, --force Force overwrite of existing file (optional)\n");
|
printf(" -s <file> Path to the file for statistics (optional)\n");
|
||||||
printf(" -b, --buffer Disable buffering writes to stdout/err (optional)\n");
|
printf(" -f Force overwrite of existing file (optional)\n");
|
||||||
printf(" -a, --amount <number> Specify the number of entries to record (must be positive, optional)\n");
|
printf(" -b Disable buffering writes to stdout/err (optional)\n");
|
||||||
printf(" -h, --help Print this help message\n");
|
printf(" -a <number> Specify the number of entries to record (must be positive, optional)\n");
|
||||||
|
printf(" -h Print this help message\n");
|
||||||
}
|
}
|
||||||
|
#define SHORT_OPT "r:s:fa:bh"
|
||||||
|
|
||||||
int
|
int
|
||||||
main(int argc, char *argv[])
|
main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
int opt;
|
int opt;
|
||||||
const char *record_file = NULL;
|
const char *record_file = NULL;
|
||||||
|
const char *stats_file = NULL;
|
||||||
int force_flag = 0;
|
int force_flag = 0;
|
||||||
size_t left, iteration = 0, amt = INITIAL_AMOUNT;
|
size_t left, iteration = 0, amt = INITIAL_AMOUNT;
|
||||||
bool buffer = true;
|
bool buffer = true;
|
||||||
|
|
||||||
fp = stdout;
|
while ((opt = getopt(argc, argv, SHORT_OPT)) != -1) {
|
||||||
|
|
||||||
while ((opt = getopt(argc, argv, SHORT_OPT LONG_OPT)) != -1) {
|
|
||||||
switch (opt) {
|
switch (opt) {
|
||||||
case 'r':
|
case 'r':
|
||||||
|
recording = true;
|
||||||
record_file = optarg;
|
record_file = optarg;
|
||||||
break;
|
break;
|
||||||
|
case 's':
|
||||||
|
statistics = true;
|
||||||
|
stats_file = optarg;
|
||||||
|
break;
|
||||||
case 'f':
|
case 'f':
|
||||||
force_flag = 1;
|
force_flag = 1;
|
||||||
break;
|
break;
|
||||||
|
@ -1240,10 +1248,9 @@ main(int argc, char *argv[])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if record file is specified
|
if (recording) {
|
||||||
if (record_file == NULL) {
|
record_fp = stdout;
|
||||||
recording = false;
|
|
||||||
} else {
|
|
||||||
// Check for existing file without force flag
|
// Check for existing file without force flag
|
||||||
if (access(record_file, F_OK) == 0 && !force_flag) {
|
if (access(record_file, F_OK) == 0 && !force_flag) {
|
||||||
fprintf(stderr, "Warning: File '%s' already exists. Use -f or --force to overwrite.\n", record_file);
|
fprintf(stderr, "Warning: File '%s' already exists. Use -f or --force to overwrite.\n", record_file);
|
||||||
|
@ -1251,17 +1258,38 @@ main(int argc, char *argv[])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open the file for writing (truncate if force flag is set)
|
// Open the file for writing (truncate if force flag is set)
|
||||||
fp = fopen(record_file, force_flag ? "w" : "a");
|
record_fp = fopen(record_file, force_flag ? "w" : "a");
|
||||||
if (fp == NULL) {
|
if (record_fp == NULL) {
|
||||||
perror("Error opening file");
|
perror("Error opening file");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if statistics file is specified
|
||||||
|
if (statistics) {
|
||||||
|
if (stats_file[0] == '-') {
|
||||||
|
stats_fp = stdout;
|
||||||
|
setvbuf(stdout, NULL, _IONBF, 0);
|
||||||
|
} else {
|
||||||
|
// Check for existing file without force flag
|
||||||
|
if (access(stats_file, F_OK) == 0 && !force_flag) {
|
||||||
|
fprintf(stderr, "Warning: File '%s' already exists. Use -f or --force to overwrite.\n", stats_file);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open the file for writing (truncate if force flag is set)
|
||||||
|
stats_fp = fopen(stats_file, force_flag ? "w" : "a");
|
||||||
|
if (stats_fp == NULL) {
|
||||||
|
perror("Error opening file");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// disable buffering
|
// disable buffering
|
||||||
if (!buffer) {
|
if (!buffer) {
|
||||||
setvbuf(stdout, NULL, _IONBF, 0);
|
setvbuf(record_fp, NULL, _IONBF, 0);
|
||||||
setvbuf(fp, NULL, _IONBF, 0);
|
setvbuf(stats_fp, NULL, _IONBF, 0);
|
||||||
}
|
}
|
||||||
unsigned types[] = { SM, ML, RB };
|
unsigned types[] = { SM, ML, RB };
|
||||||
unsigned num_types = (sizeof((types)) / sizeof((types)[0]));
|
unsigned num_types = (sizeof((types)) / sizeof((types)[0]));
|
||||||
|
@ -1280,9 +1308,6 @@ main(int argc, char *argv[])
|
||||||
containers[type].is_empty_stats.td = NULL;
|
containers[type].is_empty_stats.td = NULL;
|
||||||
containers[type].is_first_stats.td = NULL;
|
containers[type].is_first_stats.td = NULL;
|
||||||
containers[type].merge_stats.td = td_new(100);
|
containers[type].merge_stats.td = td_new(100);
|
||||||
containers[type].size = NULL;
|
|
||||||
containers[type].count = NULL;
|
|
||||||
containers[type].validate = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Setup: add an amt of bits to each container. */
|
/* Setup: add an amt of bits to each container. */
|
||||||
|
@ -1299,6 +1324,77 @@ main(int argc, char *argv[])
|
||||||
checkpoint(types);
|
checkpoint(types);
|
||||||
left = amt;
|
left = amt;
|
||||||
|
|
||||||
|
if (statistics) {
|
||||||
|
const char *names[] = { "sm", "ml", "rb" };
|
||||||
|
const char *dists[] = { "p50", "p75", "p90", "p99", "p999" };
|
||||||
|
fprintf(stats_fp, "timestamp,iterations,");
|
||||||
|
foreach(types)
|
||||||
|
{
|
||||||
|
fprintf(stats_fp, "%s_size,%s_bytes,", names[type], names[type]);
|
||||||
|
if (digest(alloc) != NULL) {
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
fprintf(stats_fp, "%s_alloc_%s,", names[type], dists[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (digest(free) != NULL) {
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
fprintf(stats_fp, "%s_free_%s,", names[type], dists[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (digest(set) != NULL) {
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
fprintf(stats_fp, "%s_set_%s,", names[type], dists[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (digest(is_set) != NULL) {
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
fprintf(stats_fp, "%s_is_set_%s,", names[type], dists[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (digest(clear) != NULL) {
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
fprintf(stats_fp, "%s_clear_%s,", names[type], dists[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (digest(find_span) != NULL) {
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
fprintf(stats_fp, "%s_find_span_%s,", names[type], dists[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (digest(take_span) != NULL) {
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
fprintf(stats_fp, "%s_take_span_%s,", names[type], dists[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (digest(release_span) != NULL) {
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
fprintf(stats_fp, "%s_release_span_%s,", names[type], dists[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (digest(is_span) != NULL) {
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
fprintf(stats_fp, "%s_is_span_%s,", names[type], dists[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (digest(is_empty) != NULL) {
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
fprintf(stats_fp, "%s_is_empty_%s,", names[type], dists[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (digest(is_first) != NULL) {
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
fprintf(stats_fp, "%s_is_first_%s,", names[type], dists[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (digest(merge) != NULL) {
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
fprintf(stats_fp, "%s_merge_%s,", names[type], dists[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fprintf(stats_fp, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
iteration++;
|
iteration++;
|
||||||
// the an amount [1, 16] of pages to find preferring smaller sizes
|
// the an amount [1, 16] of pages to find preferring smaller sizes
|
||||||
|
@ -1395,7 +1491,39 @@ main(int argc, char *argv[])
|
||||||
containers[type].free(new_handles[type]);
|
containers[type].free(new_handles[type]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (statistics) {
|
||||||
|
const float dists[] = { 0.5, 0.75, 0.90, 0.99, 0.999 };
|
||||||
|
fprintf(stats_fp, "%f,%zu,", nsts(), iteration);
|
||||||
|
foreach(types)
|
||||||
|
{
|
||||||
|
fprintf(stats_fp, "%zu,%zu,", containers[type].count(handles[type]), containers[type].size(handles[type]));
|
||||||
|
// clang-format off
|
||||||
|
td_histogram_t *td[] = {
|
||||||
|
digest(alloc),
|
||||||
|
digest(free),
|
||||||
|
digest(set),
|
||||||
|
digest(is_set),
|
||||||
|
digest(clear),
|
||||||
|
digest(find_span),
|
||||||
|
digest(take_span),
|
||||||
|
digest(release_span),
|
||||||
|
digest(is_span),
|
||||||
|
digest(is_empty),
|
||||||
|
digest(is_first),
|
||||||
|
digest(merge)
|
||||||
|
};
|
||||||
|
// clang-format on
|
||||||
|
for (int i = 0; i < 12; i++) {
|
||||||
|
if (td[i] != NULL) {
|
||||||
|
td_compress(td[i]);
|
||||||
|
for (int j = 0; j < 5; j++) {
|
||||||
|
fprintf(stats_fp, "%.10f,", td_quantile(td[i], dists[j]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fprintf(stats_fp, "\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue