blob: c9418eadf9aebdfafb699b732b6a9ca6d332f75e [file]
/*****************************************************************************\
* Copyright (C) SchedMD LLC.
*
* This file is part of Slurm, a resource management program.
* For details, see <https://slurm.schedmd.com/>.
* Please also read the included file: DISCLAIMER.
*
* Slurm 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 2 of the License, or (at your option)
* any later version.
*
* In addition, as a special exception, the copyright holders give permission
* to link the code of portions of this program with the OpenSSL library under
* certain conditions as described in each individual source file, and
* distribute linked combinations including the two. You must obey the GNU
* General Public License in all respects for all of the code used other than
* OpenSSL. If you modify file(s) with this exception, you may extend this
* exception to your version of the file(s), but you are not obligated to do
* so. If you do not wish to do so, delete this exception statement from your
* version. If you delete this exception statement from all source files in
* the program, then also delete it here.
*
* Slurm 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 Slurm; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
\*****************************************************************************/
/*
* This is a port of the old slurm_unit serializer-test.c. It runs under the
* test_100_1 pytest meta-runner (compiled and run by atf.run_check_test() as a
* single translation unit, so the large JSON data blobs are #include'd rather
* than linked).
*
* It tests the serializer/json plugin for round-trip correctness.
*/
#define _GNU_SOURCE
#include <limits.h>
#if defined(__GLIBC__) && !defined(__UCLIBC__) && !defined(__MUSL__)
#include <features.h>
#if defined(__GLIBC_PREREQ)
#if __GLIBC_PREREQ(2, 33) && defined(__SIZEOF_INT128__)
#define HAVE_MALLINFO2
#include <malloc.h>
#endif
#endif
#endif
#include <check.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "slurm/slurm_errno.h"
#include "src/common/data.h"
#include "src/common/log.h"
#include "src/common/macros.h"
#include "src/common/read_config.h"
#include "src/common/slurm_protocol_defs.h"
#include "src/common/slurm_time.h"
#include "src/common/timers.h"
#include "src/common/xmalloc.h"
#include "src/common/xstring.h"
#include "src/interfaces/serializer.h"
#include "public_datasets/twitter.json.c"
#include "public_datasets/noaa_global_temps.json.c"
#define BYTES_IN_MiB (1024 * 1024)
#ifdef HAVE_MALLINFO2
typedef unsigned __int128 uint128_t;
typedef struct {
uint128_t arena; /* non-mmapped space allocated from system */
uint128_t ordblks; /* number of free chunks */
uint128_t smblks; /* number of fastbin blocks */
uint128_t hblks; /* number of mmapped regions */
uint128_t hblkhd; /* space in mmapped regions */
uint128_t usmblks; /* always 0, preserved for backwards compatibility */
uint128_t fsmblks; /* space available in freed fastbin blocks */
uint128_t uordblks; /* total allocated space */
uint128_t fordblks; /* total free space */
uint128_t keepcost; /* top-most, releasable (via malloc_trim) space */
} mallinfo2_128_t;
typedef struct {
struct mallinfo2 peak;
mallinfo2_128_t total;
int count;
} mem_track_t;
#endif /* HAVE_MALLINFO2 */
static const char *mime_types[] = {
MIME_TYPE_YAML,
MIME_TYPE_JSON,
};
static const serializer_flags_t flag_combinations[] = {
SER_FLAGS_COMPACT,
SER_FLAGS_PRETTY,
};
static const struct {
const char *source;
const char *tag;
const int run_count; /* diff count to avoid test running too long */
} test_json[] = { { test_json1, "twitter-dataset", 25 },
{ test_json2, "NOAA-ocean-temps", 50 } };
static void setup(void)
{
log_options_t log_opts = LOG_OPTS_INITIALIZER;
const char *debug_env = getenv("SLURM_DEBUG");
const char *debug_flags_env = getenv("SLURM_DEBUG_FLAGS");
if (debug_env)
log_opts.stderr_level = log_string2num(debug_env);
if (debug_flags_env)
debug_str2flags(debug_flags_env, &slurm_conf.debug_flags);
log_init("serializer-test", log_opts, 0, NULL);
ck_assert(!slurm_conf_init(NULL));
/*
* Load the serializer plugins on demand. The bound serializer for
* application/json is whichever plugin in PluginDir registers it; the
* test environment provides serializer/json (asserted by test_mimetype).
*/
serializer_required(MIME_TYPE_JSON);
serializer_required(MIME_TYPE_YAML);
}
static void teardown(void)
{
serializer_g_fini();
slurm_conf_destroy();
log_fini();
}
/* serialize src to a string, re-parse it, and verify the round-trip matches */
static void _test_run(const char *tag, const data_t *src, const char *mime_type,
const serializer_flags_t flags)
{
char *output = NULL;
size_t output_len = -1;
data_t *verify_src = NULL;
int rc;
rc = serialize_g_data_to_string(&output, &output_len, src, mime_type,
flags);
ck_assert_int_eq(rc, 0);
debug("dumped %s with %s:\n%s\n\n\n\n", tag, mime_type, output);
rc = serialize_g_string_to_data(&verify_src, output, output_len,
mime_type);
ck_assert_int_eq(rc, 0);
ck_assert_msg(data_check_match(src, verify_src, false),
"match verification failed");
xfree(output);
FREE_NULL_DATA(verify_src);
}
START_TEST(test_mimetype)
{
const char *ptr = NULL;
ck_assert(resolve_mime_type(MIME_TYPE_JSON, &ptr) != NULL);
ck_assert(ptr != NULL);
ck_assert(!xstrcmp(ptr, MIME_TYPE_JSON_PLUGIN));
ptr = NULL;
ck_assert(resolve_mime_type("application/jsonrequest", &ptr) != NULL);
ck_assert(ptr != NULL);
ck_assert(!xstrcmp(ptr, MIME_TYPE_JSON_PLUGIN));
}
END_TEST
START_TEST(test_parse_invalid)
{
/* malformed JSON that the parser must reject */
static const char
*sf[] = {
"\"taco",
"taco\"",
"[",
"]",
"{",
"}",
"[{",
"{[",
"{[}",
"[{}",
"[\"taco",
"{\"taco",
"{\"taco:",
"{taco:",
"{\"taco\":",
"[taco:",
"[\"taco\":",
"[\"taco\",:",
",,,,]",
",:,,]",
"\\,",
":",
",:,",
"\"\\\"",
"[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[",
"{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:{test:test}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}",
"{\"taco\"::taco}",
"{::taco}",
"\xFE",
"\xFF",
"\xFE\xFF",
"\xFF\xFE",
"\xFE\xFF\x00\x00",
"\xFEtaco",
"\xFFtaco",
"\xFE\xFFtaco",
"\xFF\xFEtaco",
"\xFE\xFF\x00\x00taco",
"\x01",
"\x02",
"\x03",
"\x04",
"\x05",
"\x06",
"\x07",
"\x08",
"\\u10FFFF",
"\\u10FFFFFFFFFFFFFFFFFFFFFFF",
"\\u0",
"\\uTACOS",
"\\u000TACOS",
"tacos\"tacos\"taco\"\"",
"*\"tacos\"taco\"\"",
"*,0",
};
for (int i = 0; i < ARRAY_SIZE(sf); i++) {
int rc;
data_t *d = NULL;
rc = serialize_g_string_to_data(&d, sf[i], strlen(sf[i]),
MIME_TYPE_JSON);
debug("expected fail source %d=%d -> %pD\n%s\n\n\n\n", i, rc, d,
sf[i]);
ck_assert_ptr_null(d);
FREE_NULL_DATA(d);
}
}
END_TEST
START_TEST(test_parse_valid)
{
/* valid JSON that must parse, match, and round-trip */
static const char *s[] = {
"\"taco\"",
"\"\\\"taco\\\"\"",
"[ 100 ]",
"[ 100.389 ]",
"[ -100.389 ]",
"[ 1.1238e10 ]",
"[ -1.1238e10 ]",
"{ \"taco\": \"tacos\" }",
"[ \"taco1\", \"taco2\", ]",
"[ \"taco1\", \"taco2\", \"taco3\" ]",
"[ true, false ]",
"{\t\t\t\n}",
"{ }",
"[ \"\\u0024\", \"\\u00a3\", \"\\u00c0\", \"\\u0418\", \"\\u0939\", \"\\u20ac\", \"\\ud55c\" ]",
"[]",
"[]",
"{}",
"[]",
"[[] \t]",
"[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]",
"[{\"test\":\"test\"}]",
"{\"test\":[]}",
"{\"test\":{\"test\":{\"test\":{\"test\":{\"test\":{\"test\":{\"test\":{\"test\":{\"test\":{\"test\":{\"test\":{\"test\":{\"test\":{\"test\":{\"test\":{\"test\":{\"test\":{\"test\":{\"test\":{\"test\":{\"test\":{\"test\":{\"test\":{\"test\":{\"test\":{\"test\":\"test\"}}}}}}}}}}}}}}}}}}}}}}}}}}",
};
data_t *c[] = {
data_set_string(data_new(), "taco"),
data_set_string(data_new(), "\"taco\""),
data_set_list(data_new()),
data_set_list(data_new()),
data_set_list(data_new()),
data_set_list(data_new()),
data_set_list(data_new()),
data_set_dict(data_new()),
data_set_list(data_new()),
data_set_list(data_new()),
data_set_list(data_new()),
data_set_dict(data_new()),
data_set_dict(data_new()),
data_set_list(data_new()),
data_set_list(data_new()),
data_set_list(data_new()),
data_set_dict(data_new()),
data_set_list(data_new()),
data_set_list(data_new()),
data_set_list(data_new()),
data_set_list(data_new()),
data_set_dict(data_new()),
data_set_dict(data_new()),
};
data_set_int(data_list_append(c[2]), 100);
data_set_float(data_list_append(c[3]), 100.389);
data_set_float(data_list_append(c[4]), -100.389);
data_set_float(data_list_append(c[5]), 1.1238e10);
data_set_float(data_list_append(c[6]), -1.1238e10);
data_set_string(data_key_set(c[7], "taco"), "tacos");
data_set_string(data_list_append(c[8]), "taco1");
data_set_string(data_list_append(c[8]), "taco2");
data_set_string(data_list_append(c[9]), "taco1");
data_set_string(data_list_append(c[9]), "taco2");
data_set_string(data_list_append(c[9]), "taco3");
data_set_bool(data_list_append(c[10]), true);
data_set_bool(data_list_append(c[10]), false);
data_set_string(data_list_append(c[13]), "\U00000024");
data_set_string(data_list_append(c[13]), "\U000000a3");
data_set_string(data_list_append(c[13]), "\U000000c0");
data_set_string(data_list_append(c[13]), "\U00000418");
data_set_string(data_list_append(c[13]), "\U00000939");
data_set_string(data_list_append(c[13]), "\U000020ac");
data_set_string(data_list_append(c[13]), "\U0000d55c");
data_set_list(data_list_append(c[18]));
{
data_t *t = c[19];
for (int i = 0; i < 19; i++)
t = data_set_list(data_list_append(t));
}
data_set_string(data_key_set(data_set_dict(data_list_append(c[20])),
"test"),
"test");
data_set_list(data_key_set(c[21], "test"));
{
data_t *t = c[22];
for (int i = 0; i < 26; i++)
t = data_set_dict(data_key_set(t, "test"));
data_set_string(t, "test");
}
for (int i = 0; i < ARRAY_SIZE(s); i++) {
int rc;
data_t *d = NULL;
rc = serialize_g_string_to_data(&d, s[i], strlen(s[i]),
MIME_TYPE_JSON);
debug("expected pass source %d=%d -> %pD\n%s\n\n\n\n", i, rc, d,
s[i]);
ck_assert_int_eq(rc, 0);
data_convert_tree(d, DATA_TYPE_NONE);
ck_assert_msg(data_check_match(c[i], d, false),
"verify failed: %s", s[i]);
for (int f = 0; f < ARRAY_SIZE(flag_combinations); f++) {
for (int m = 0; m < ARRAY_SIZE(mime_types); m++) {
const char *mptr = NULL;
const char *mime_type =
resolve_mime_type(mime_types[m], &mptr);
if (mime_type)
_test_run(s[i], d, mime_type,
flag_combinations[f]);
else
debug("skipping test with %s",
mime_types[m]);
}
}
FREE_NULL_DATA(d);
}
for (int i = 0; i < ARRAY_SIZE(c); i++)
FREE_NULL_DATA(c[i]);
}
END_TEST
START_TEST(test_compliance_large)
{
/*
* Verify that the same JSON file can be parsed and dumped with same
* contents.
*/
for (int i = 0; i < ARRAY_SIZE(test_json); i++) {
int rc;
data_t *data = NULL;
const int len = strlen(test_json[i].source);
debug("source %s:\n%s\n\n\n\n", test_json[i].tag,
test_json[i].source);
rc = serialize_g_string_to_data(&data, test_json[i].source, len,
MIME_TYPE_JSON);
ck_assert_int_eq(rc, 0);
for (int f = 0; f < ARRAY_SIZE(flag_combinations); f++) {
for (int m = 0; m < ARRAY_SIZE(mime_types); m++) {
const char *mptr = NULL;
const char *mime_type =
resolve_mime_type(mime_types[m], &mptr);
if (mime_type)
_test_run(test_json[i].tag, data,
mime_type,
flag_combinations[f]);
else
debug("skipping test with %s",
mime_types[m]);
}
}
FREE_NULL_DATA(data);
}
}
END_TEST
#ifdef HAVE_MALLINFO2
static void _track_mem(mem_track_t *track)
{
struct mallinfo2 mi = mallinfo2();
track->peak.arena = MAX(mi.arena, track->peak.arena);
track->peak.ordblks = MAX(mi.ordblks, track->peak.ordblks);
track->peak.hblks = MAX(mi.hblks, track->peak.hblks);
track->peak.hblkhd = MAX(mi.hblkhd, track->peak.hblkhd);
track->peak.usmblks = MAX(mi.usmblks, track->peak.usmblks);
track->peak.fsmblks = MAX(mi.fsmblks, track->peak.fsmblks);
track->peak.uordblks = MAX(mi.uordblks, track->peak.uordblks);
track->peak.fordblks = MAX(mi.fordblks, track->peak.fordblks);
track->peak.keepcost = MAX(mi.keepcost, track->peak.keepcost);
track->total.arena += mi.arena;
track->total.ordblks += mi.ordblks;
track->total.hblks += mi.hblks;
track->total.hblkhd += mi.hblkhd;
track->total.usmblks += mi.usmblks;
track->total.fsmblks += mi.fsmblks;
track->total.uordblks += mi.uordblks;
track->total.fordblks += mi.fordblks;
track->total.keepcost += mi.keepcost;
track->count++;
}
static void _print_tracked_mem(mem_track_t *track, const char *type)
{
struct mallinfo2 avg = {
.arena = track->total.arena / track->count,
.ordblks = track->total.ordblks / track->count,
.hblks = track->total.hblks / track->count,
.hblkhd = track->total.hblkhd / track->count,
.usmblks = track->total.usmblks / track->count,
.fsmblks = track->total.fsmblks / track->count,
.uordblks = track->total.uordblks / track->count,
.fordblks = track->total.fordblks / track->count,
.keepcost = track->total.keepcost / track->count,
};
printf("\t%s Total non-mmapped bytes (arena): %zu/%zuB %zu/%zuMiB\n",
type, avg.arena, track->peak.arena, (avg.arena / 1048576),
(track->peak.arena / 1048576));
printf("\t%s # of free chunks (ordblks): %zu/%zu\n", type,
avg.ordblks, track->peak.ordblks);
printf("\t%s # of free fastbin blocks (smblks): %zu/%zu\n", type,
avg.smblks, track->peak.smblks);
printf("\t%s # of mapped regions (hblks): %zu/%zu\n", type,
avg.hblks, track->peak.hblks);
printf("\t%s Bytes in mapped regions (hblkhd): %zu/%zu\n", type,
avg.hblkhd, track->peak.hblkhd);
printf("\t%s Max. total allocated space (usmblks): %zu/%zu\n", type,
avg.usmblks, track->peak.usmblks);
printf("\t%s Free bytes held in fastbins (fsmblks): %zu/%zu\n", type,
avg.fsmblks, track->peak.fsmblks);
printf("\t%s Total allocated space (uordblks): %zu/%zu\n", type,
avg.uordblks, track->peak.uordblks);
printf("\t%s Total free space (fordblks): %zu/%zu\n", type,
avg.fordblks, track->peak.fordblks);
printf("\t%s Topmost releasable block (keepcost): %zu/%zu\n\n", type,
avg.keepcost, track->peak.keepcost);
}
#else /* !HAVE_MALLINFO2 */
/* avoid compile errors for undefined/unused variables */
typedef struct {
int a;
int b;
} mem_track_t;
#define _track_mem(track) ((void) (track))
#define _print_tracked_mem(track, type) ((void) (track))
#endif /* !HAVE_MALLINFO2 */
static void _test_bandwidth_str(const char *tag, const char *source,
const int run_count)
{
int rc;
data_t *data = NULL;
const int test_json_len = strlen(source);
char *output = NULL;
size_t output_len = 0;
timespec_t read_times = { 0, 0 }, write_times = { 0, 0 };
uint64_t total_written = 0, total_read = 0;
timespec_t fastest_write = { UINT_MAX, 0 };
timespec_t fastest_read = { UINT_MAX, 0 };
double read_avg, write_avg;
double read_diff, write_diff, read_rate, write_rate;
double read_rate_bytes, write_rate_bytes;
double fastest_read_rate_bytes, fastest_write_rate_bytes;
double fastest_read_rate, fastest_write_rate;
mem_track_t read_mem = { 0 }, write_mem = { 0 };
for (int i = 0; i < run_count; i++) {
DEF_TIMERS;
timespec_t duration = { 0, 0 };
FREE_NULL_DATA(data);
_track_mem(&read_mem);
START_TIMER;
rc = serialize_g_string_to_data(&data, source, test_json_len,
MIME_TYPE_JSON);
END_TIMER3(__func__, INFINITE);
duration = timespec_diff_ns(TIMER_END_TS, TIMER_START_TS).diff;
_track_mem(&read_mem);
total_read += test_json_len;
read_times = timespec_add(read_times, duration);
if (timespec_is_after(fastest_read, duration))
fastest_read = duration;
ck_assert_int_eq(rc, 0);
}
for (int i = 0; i < run_count; i++) {
DEF_TIMERS;
timespec_t duration = { 0, 0 };
_track_mem(&write_mem);
START_TIMER;
rc = serialize_g_data_to_string(&output, &output_len, data,
MIME_TYPE_JSON,
SER_FLAGS_PRETTY);
END_TIMER3(__func__, INFINITE);
duration = timespec_diff_ns(TIMER_END_TS, TIMER_START_TS).diff;
_track_mem(&write_mem);
total_written += output_len;
write_times = timespec_add(write_times, duration);
if (timespec_is_after(fastest_write, duration))
fastest_write = duration;
xfree(output);
output_len = 0;
ck_assert_int_eq(rc, 0);
}
FREE_NULL_DATA(data);
read_diff = timespec_to_secs(read_times) / run_count;
write_diff = timespec_to_secs(write_times) / run_count;
read_avg = total_read / run_count;
write_avg = total_written / run_count;
/* (bytes / sec) * (1 MiB / 1024*1024 bytes) */
read_rate_bytes = (read_avg / read_diff);
write_rate_bytes = (write_avg / write_diff);
fastest_read_rate_bytes = read_avg / timespec_to_secs(fastest_read);
fastest_write_rate_bytes = write_avg / timespec_to_secs(fastest_write);
read_rate = read_rate_bytes / BYTES_IN_MiB;
write_rate = write_rate_bytes / BYTES_IN_MiB;
fastest_read_rate = fastest_read_rate_bytes / BYTES_IN_MiB;
fastest_write_rate = fastest_write_rate_bytes / BYTES_IN_MiB;
printf("%s: %u runs:\n", tag, run_count);
printf("\tfastest read=%lf sec\n\tfastest write=%lf sec\n\n",
timespec_to_secs(fastest_read), timespec_to_secs(fastest_write));
printf("\tfastest read=%f MiB/sec \n\tfastest write=%f MiB/sec\n\n",
fastest_read_rate, fastest_write_rate);
printf("\tavg read=%lf sec\n\tavg write=%lf sec\n\n", read_diff,
write_diff);
printf("\tavg read=%f MiB/sec \n\tavg write=%f MiB/sec\n\n", read_rate,
write_rate);
_print_tracked_mem(&read_mem, "read");
_print_tracked_mem(&write_mem, "write");
}
START_TEST(test_bandwidth)
{
for (int i = 0; i < ARRAY_SIZE(test_json); i++)
_test_bandwidth_str(test_json[i].tag, test_json[i].source,
test_json[i].run_count);
}
END_TEST
extern int main(int argc, char **argv)
{
int failures;
TCase *tcase = tcase_create("serializer");
Suite *suite = suite_create("serializer");
SRunner *sr = NULL;
tcase_add_unchecked_fixture(tcase, setup, teardown);
/* generous timeout: the bandwidth and large-dataset cases run a while */
tcase_set_timeout(tcase, 3000);
tcase_add_test(tcase, test_mimetype);
tcase_add_test(tcase, test_parse_invalid);
tcase_add_test(tcase, test_parse_valid);
tcase_add_test(tcase, test_compliance_large);
tcase_add_test(tcase, test_bandwidth);
suite_add_tcase(suite, tcase);
sr = srunner_create(suite);
srunner_run_all(sr, CK_VERBOSE);
failures = srunner_ntests_failed(sr);
srunner_free(sr);
return failures;
}