diff --git a/deps/ccommon/docs/modules/cc_log.rst b/deps/ccommon/docs/modules/cc_log.rst index 122dfe9c1..167fc4ff6 100644 --- a/deps/ccommon/docs/modules/cc_log.rst +++ b/deps/ccommon/docs/modules/cc_log.rst @@ -53,7 +53,7 @@ Synopsis void log_flush(struct logger *logger); - rstatus_i log_reopen(struct logger *logger); + rstatus_i log_reopen(struct logger *logger, char *target); Description ----------- @@ -95,18 +95,18 @@ Flush to file ^^^^^^^^^^^^^ .. code-block:: C - void log_flush(struct logger *logger); + size_t log_flush(struct logger *logger); -``log_flush`` writes as much data to the log file as possible, and updates the (read) marker in the ring buffer. Data that cannot be written to the file will be kept until next call. If the ring buffer or the file was never setup, no action is taken. +``log_flush`` writes as much data to the log file as possible, and updates the (read) marker in the ring buffer. Data that cannot be written to the file will be kept until next call. If the ring buffer or the file was never setup, no action is taken. Return the number of bytes flushed. Log reopen ^^^^^^^^^^ .. code-block:: C - rstatus_i log_reopen(struct logger *logger); + rstatus_i log_reopen(struct logger *logger, char *target); -``log_reopen`` reopens the log file according to ``name``, and does nothing if standard outputs are used. It returns ``CC_OK`` for success or ``CC_ERROR`` if reopen failed (at which point ``logger`` will no longer have a valid ``fd``). +``log_reopen`` reopens the log file according to ``name``, and does nothing if standard outputs are used. It returns ``CC_OK`` for success or ``CC_ERROR`` if reopen failed (at which point ``logger`` will no longer have a valid ``fd``). If ``target`` is specified function will rename original log file to the provided target filename and reopen the log file. This function can be used to reopen the log file when an exception has happened, or another party such as ``logrotate`` instructs the application to do so. Log rotation in a ``nocopytruncate`` manner- i.e. the content in the file is not copied, but the file is simply renamed- is more efficient in high-load systems. But doing so requires signaling the application to reopen the log file after renaming. This function makes it possible to achieve that when used with proper signal handling. diff --git a/deps/ccommon/docs/modules/cc_metric.rst b/deps/ccommon/docs/modules/cc_metric.rst index bfc6adad0..d220fd9db 100644 --- a/deps/ccommon/docs/modules/cc_metric.rst +++ b/deps/ccommon/docs/modules/cc_metric.rst @@ -111,10 +111,10 @@ Helper functions .. code-block:: C void metric_reset(struct metric sarr[], unsigned int nmetric); - size_t metric_print(char *buf, size_t nbuf, struct metric *m); + size_t metric_print(char *buf, size_t nbuf, char *fmt, struct metric *m); ``metric_reset`` resets the values of an array of metrics. -``metric_print`` prints the name and value of a metric, in human readable format, to buffer ``buf``, with a single space separating the two fields. This simple style is compatible with how Memcached currently reports metrics ([Memcached]_). Helper functions for other formats (e.g. Redis [Redis]_, StatsD [StatsD]_) may be introduced in the future. +``metric_print`` prints the name and value of a metric, in human readable format specified by ``fmt``, to buffer ``buf``. Update diff --git a/deps/ccommon/docs/modules/cc_option.rst b/deps/ccommon/docs/modules/cc_option.rst index ecde3dbe6..20ab0c0f0 100644 --- a/deps/ccommon/docs/modules/cc_option.rst +++ b/deps/ccommon/docs/modules/cc_option.rst @@ -40,6 +40,7 @@ Data Structure typedef enum option_type { OPTION_TYPE_BOOL, OPTION_TYPE_UINT, + OPTION_TYPE_FPN, OPTION_TYPE_STR, OPTION_TYPE_SENTINEL } option_type_e; @@ -47,6 +48,7 @@ Data Structure typedef union option_val { bool vbool; uintmax_t vuint; + double vfpn; char *vstr; } option_val_u; @@ -59,7 +61,7 @@ Data Structure char *description; }; -The core data structure ``struct option`` has six members. ``name`` and ``description`` help identify and explain the purpose of the option. ``type`` decides how input should be interpreted, which currently can be boolean, unsigned integer or C string. Both the default value and current value are kept around, with values matching the type. Keeping the default separately will make it easy to reset the option to original. Finally, boolean ``set`` tells if an option has been set, and thus usable. +The core data structure ``struct option`` has six members. ``name`` and ``description`` help identify and explain the purpose of the option. ``type`` decides how input should be interpreted, which currently can be boolean, unsigned integer, double or C string. Both the default value and current value are kept around, with values matching the type. Keeping the default separately will make it easy to reset the option to original. Finally, boolean ``set`` tells if an option has been set, and thus usable. Synopsis -------- @@ -71,8 +73,8 @@ Synopsis rstatus_i option_load_file(FILE *fp, struct option options[], unsigned int nopt); void option_print(struct option *opt); - void option_printall(struct option options[], unsigned int nopt); - void option_printall_default(struct option options[], unsigned int nopt); + void option_print_all(struct option options[], unsigned int nopt); + void option_describe_all(struct option options[], unsigned int nopt); void option_free(struct option options[], unsigned int nopt); @@ -118,8 +120,8 @@ Print option info .. code-block:: C void option_print(struct option *opt); - void option_printall(struct option options[], unsigned int nopt); - void option_printall_default(struct option options[], unsigned int nopt); + void option_print_all(struct option options[], unsigned int nopt); + void option_describe_all(struct option options[], unsigned int nopt); Examples diff --git a/deps/ccommon/include/cc_bstring.h b/deps/ccommon/include/cc_bstring.h index 18cfda558..86c1aadb7 100644 --- a/deps/ccommon/include/cc_bstring.h +++ b/deps/ccommon/include/cc_bstring.h @@ -170,6 +170,7 @@ void bstring_free(struct bstring **bstring); /* bstring to uint conversion */ rstatus_i bstring_atou64(uint64_t *u64, struct bstring *str); +rstatus_i bstring_atoi64(int64_t *i64, struct bstring *str); #ifdef __cplusplus } diff --git a/deps/ccommon/src/cc_array.c b/deps/ccommon/src/cc_array.c index 5768ddde6..0832647a1 100644 --- a/deps/ccommon/src/cc_array.c +++ b/deps/ccommon/src/cc_array.c @@ -74,7 +74,7 @@ array_create(struct array **arr, uint32_t nalloc, size_t size) ASSERT(nalloc != 0 && size != 0); *arr = (struct array *)cc_alloc(sizeof(**arr)); - if (arr == NULL) { + if (*arr == NULL) { log_info("array creation failed due to OOM"); return CC_ENOMEM; diff --git a/deps/ccommon/src/cc_bstring.c b/deps/ccommon/src/cc_bstring.c index efa51949f..68374423c 100644 --- a/deps/ccommon/src/cc_bstring.c +++ b/deps/ccommon/src/cc_bstring.c @@ -113,6 +113,46 @@ bstring_compare(const struct bstring *s1, const struct bstring *s2) return cc_bcmp(s1->data, s2->data, s1->len); } +rstatus_i +bstring_atoi64(int64_t *i64, struct bstring *str) +{ + uint32_t offset = 0; + uint8_t c; + int64_t sign = 1; + + if (str->len == 0 || str->len >= CC_INT64_MAXLEN) { + return CC_ERROR; + } + + if (*str->data == '-') { + offset = 1; + sign = -1; + } + + for (*i64 = 0LL; offset < str->len; offset++) { + c = *(str->data + offset); + if (c < '0' || c > '9') { + return CC_ERROR; + } + + // overflow check + if (offset == CC_INT64_MAXLEN - 2) { + if (sign < 0 && *i64 == INT64_MIN / 10 && + c - '0' > (uint64_t)(-INT64_MIN) % 10) { + return CC_ERROR; + } + if (sign > 0 && *i64 == INT64_MAX / 10 && + c - '0' > INT64_MAX % 10) { + return CC_ERROR; + } + } + + *i64 = *i64 * 10LL + sign * (int64_t)(c - '0'); + } + + return CC_OK; +} + rstatus_i bstring_atou64(uint64_t *u64, struct bstring *str) { diff --git a/deps/ccommon/src/cc_option.c b/deps/ccommon/src/cc_option.c index 2a18fbf4c..330463760 100644 --- a/deps/ccommon/src/cc_option.c +++ b/deps/ccommon/src/cc_option.c @@ -441,8 +441,7 @@ static inline bool _allowed_in_name(char c) { /* the criteria is C's rules on variable names since we use it as such */ - if ((c >= 'a' && c <= 'z') || c == '_' || (c >= 'A' && c <= 'Z') || - (c >= '0' && c <= '9')) { + if (isalnum(c) || c == '_') { return true; } else { return false; diff --git a/deps/ccommon/src/cc_ring_array.c b/deps/ccommon/src/cc_ring_array.c index 0d423d931..4b3dc80db 100644 --- a/deps/ccommon/src/cc_ring_array.c +++ b/deps/ccommon/src/cc_ring_array.c @@ -172,13 +172,13 @@ ring_array_create(size_t elem_size, uint32_t cap) void ring_array_destroy(struct ring_array **arr) { - log_verb("destroying ring array %p and freeing memory", *arr); - if ((arr == NULL) || (*arr == NULL)) { log_warn("destroying NULL ring_array pointer"); return; } + log_verb("destroying ring array %p and freeing memory", *arr); + cc_free(*arr); *arr = NULL; } diff --git a/deps/ccommon/test/bstring/check_bstring.c b/deps/ccommon/test/bstring/check_bstring.c index 313e7196b..e35fa61aa 100644 --- a/deps/ccommon/test/bstring/check_bstring.c +++ b/deps/ccommon/test/bstring/check_bstring.c @@ -119,6 +119,37 @@ START_TEST(test_strcmp) END_TEST +START_TEST(test_atoi64) +{ + int64_t val; + struct bstring bstr; + char int64[CC_INT64_MAXLEN]; + + test_reset(); + + ck_assert_int_eq(bstring_atoi64(&val, &str2bstr("foo")), CC_ERROR); + + ck_assert_int_eq(bstring_atoi64(&val, &str2bstr("123")), CC_OK); + ck_assert_uint_eq(val, 123); + ck_assert_int_eq(bstring_atoi64(&val, &str2bstr("-123")), CC_OK); + ck_assert_uint_eq(val, -123); + + sprintf(int64, "%"PRIi64, INT64_MAX); + bstring_init(&bstr); + ck_assert_int_eq(bstring_copy(&bstr, int64, strlen(int64)), CC_OK); + ck_assert_int_eq(bstring_atoi64(&val, &bstr), CC_OK); + ck_assert_int_eq(val, INT64_MAX); + bstring_deinit(&bstr); + + sprintf(int64, "%"PRIi64, INT64_MIN); + bstring_init(&bstr); + ck_assert_int_eq(bstring_copy(&bstr, int64, strlen(int64)), CC_OK); + ck_assert_int_eq(bstring_atoi64(&val, &bstr), CC_OK); + ck_assert_int_eq(val, INT64_MIN); + bstring_deinit(&bstr); +} +END_TEST + START_TEST(test_atou64) { uint64_t val; @@ -189,6 +220,7 @@ bstring_suite(void) tcase_add_test(tc_bstring, test_copy); tcase_add_test(tc_bstring, test_compare); tcase_add_test(tc_bstring, test_strcmp); + tcase_add_test(tc_bstring, test_atoi64); tcase_add_test(tc_bstring, test_atou64); tcase_add_test(tc_bstring, test_bstring_alloc_and_free); diff --git a/src/data_structure/CMakeLists.txt b/src/data_structure/CMakeLists.txt index ab90a5bae..e679cc3d0 100644 --- a/src/data_structure/CMakeLists.txt +++ b/src/data_structure/CMakeLists.txt @@ -1,3 +1,3 @@ add_subdirectory(bitmap) -add_subdirectory(intarray) +add_subdirectory(sarray) add_subdirectory(ziplist) diff --git a/src/data_structure/intarray/CMakeLists.txt b/src/data_structure/intarray/CMakeLists.txt deleted file mode 100644 index 0cacbc03c..000000000 --- a/src/data_structure/intarray/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -add_library(ds_intarray intarray.c) diff --git a/src/data_structure/intarray/intarray.h b/src/data_structure/intarray/intarray.h deleted file mode 100644 index afdc7357f..000000000 --- a/src/data_structure/intarray/intarray.h +++ /dev/null @@ -1,103 +0,0 @@ -#pragma once - -/* The intarray is designed for sorted array of integers of uniform but - * configurable sizes, including 1-, 2-, 4-, 8-byte unsigned integers. - * The array can be of ASC or DESC order. Once an array is created, these - * configurable attributes cannot be changed. - * - * Because of the limitation on data type, intarray is both more memory- - * efficient and faster for value lookups compared to a more generic data - * structure such as ziplist. It is particularly useful if users intend to - * keep a sorted list of numbers without duplication, such as an index of - * numeric IDs. - * - * NOTE: start with ASC order, allow DESC later. - * - * ---------------------------------------------------------------------------- - * - * INTARRAY OVERALL LAYOUT - * ======================= - * - * The general layout of the intarray is as follows: - * - * ... - * ╰------------╯ ╰-------------------------╯ - * header body - * - * Overhead: 8 bytes - * - * is the number of entries. - * - * is the size of each entry (of value 1, 2, 4, 8 for now) - * - * - * INTARRAY ENTRIES - * ================ - * - * Every entry in the intarray is a simple integer of size specified in the - * header. - * - * RUNTIME - * ======= - * - * Entry lookup takes O(log N) where N is the number of entries in the list. If - * the entry size are below a threshold (64-bytes for now), then a linear scan - * is performed instead of binary lookup. - * - * Insertion and removal of entries involve index-based lookup, as well as - * shifting data. So in additional to the considerations above, the amount of - * data being moved for updates will affect performance. Updates near the "fixed - * end" of the ziplist (currently the beginning) require moving more data and - * therefore will be slower. Overall, it is cheapest to perform updates at the - * end of the array due to zero data movement. - * - */ - -#include - -#define INTARRAY_HEADER_SIZE 8 - -typedef uint8_t * intarray_p; - -typedef enum { - INTARRAY_OK, - INTARRAY_ENOTFOUND, /* value not found error */ - INTARRAY_EOOB, /* out-of-bound error */ - INTARRAY_EINVALID, /* invalid data error */ - INTARRAY_EDUP, /* duplicate value found */ - INTARRAY_ERROR, - INTARRAY_SENTINEL -} intarray_rstatus_e; - -#define IA_NENTRY(_ia) (*((uint32_t *)(_ia))) -#define IA_ESIZE(_ia) (*((uint32_t *)(_ia) + 1)) - -static inline uint32_t -intarray_nentry(const intarray_p ia) -{ - return IA_NENTRY(ia); -} - -static inline uint32_t -intarray_esize(const intarray_p ia) -{ - return IA_ESIZE(ia); -} - -/* initialize an intarray of element size 1/2/4/8 bytes */ -intarray_rstatus_e intarray_init(intarray_p ia, uint32_t esize); - -/* intarray APIs: seek */ -intarray_rstatus_e intarray_value(uint64_t *val, const intarray_p ia, uint32_t idx); -intarray_rstatus_e intarray_index(uint32_t *idx, const intarray_p ia, uint64_t val); - -/* ziplist APIs: modify */ -intarray_rstatus_e intarray_insert(intarray_p ia, uint64_t val); -intarray_rstatus_e intarray_remove(intarray_p ia, uint64_t val); - -/* - * if count is positive, remove count entries starting at the beginning - * if count is negative, remove -count entries starting at the end - */ -intarray_rstatus_e intarray_truncate(intarray_p ia, int64_t count); - diff --git a/src/data_structure/sarray/CMakeLists.txt b/src/data_structure/sarray/CMakeLists.txt new file mode 100644 index 000000000..7c3141bf9 --- /dev/null +++ b/src/data_structure/sarray/CMakeLists.txt @@ -0,0 +1 @@ +add_library(ds_sarray sarray.c) diff --git a/src/data_structure/intarray/intarray.c b/src/data_structure/sarray/sarray.c similarity index 67% rename from src/data_structure/intarray/intarray.c rename to src/data_structure/sarray/sarray.c index 06dc292c4..b79847f7e 100644 --- a/src/data_structure/intarray/intarray.c +++ b/src/data_structure/sarray/sarray.c @@ -1,9 +1,9 @@ -#include "intarray.h" +#include "sarray.h" #include -#define IA_BODY(_ia) ((uint8_t *)(_ia) + INTARRAY_HEADER_SIZE) +#define SA_BODY(_sa) ((uint8_t *)(_sa) + SARRAY_HEADER_SIZE) #define SCAN_THRESHOLD 64 static inline uint8_t * @@ -93,12 +93,16 @@ _linear_search(uint32_t *idx, uint8_t *body, uint32_t nentry, uint32_t esize, ui for (i = 0; i < nentry; ++i, ++*idx) { - if (val == _get_value(_position(body, esize, i), esize)) { - return true; + if (val <= _get_value(_position(body, esize, i), esize)) { + break; } } - return false; + if (val == _get_value(_position(body, esize, *idx), esize)) { + return true; + } else { + return false; + } } static inline bool @@ -116,7 +120,9 @@ _binary_search(uint32_t *idx, uint8_t *body, uint32_t nentry, uint32_t esize, ui if (val == _get_value(_position(body, esize, 0), esize)) { return true; } - + if (val < _get_value(_position(body, esize, 0), esize)) { + return false; + } if (val > _get_value(_position(body, esize, nentry - 1), esize)) { *idx = nentry; return false; @@ -159,159 +165,159 @@ _locate(uint32_t *idx, uint8_t *body, uint32_t nentry, uint32_t esize, uint64_t } -intarray_rstatus_e -intarray_init(intarray_p ia, uint32_t esize) +sarray_rstatus_e +sarray_init(sarray_p sa, uint32_t esize) { - if (ia == NULL) { - return INTARRAY_ERROR; + if (sa == NULL) { + return SARRAY_ERROR; } if (esize != 1 && esize != 2 && esize != 4 && esize != 8) { - return INTARRAY_EINVALID; + return SARRAY_EINVALID; } - IA_NENTRY(ia) = 0; - IA_ESIZE(ia) = esize; + SA_NENTRY(sa) = 0; + SA_ESIZE(sa) = esize; - return INTARRAY_OK; + return SARRAY_OK; } -intarray_rstatus_e -intarray_value(uint64_t *val, const intarray_p ia, uint32_t idx) +sarray_rstatus_e +sarray_value(uint64_t *val, const sarray_p sa, uint32_t idx) { uint32_t esize, nentry; - if (val == NULL || ia == NULL) { - return INTARRAY_ERROR; + if (val == NULL || sa == NULL) { + return SARRAY_ERROR; } - nentry = intarray_nentry(ia); + nentry = sarray_nentry(sa); idx += (idx < 0) * nentry; if (idx < 0 || idx >= nentry) { - return INTARRAY_EOOB; + return SARRAY_EOOB; } - esize = intarray_esize(ia); - *val = _get_value(_position(IA_BODY(ia), esize, idx), esize); + esize = sarray_esize(sa); + *val = _get_value(_position(SA_BODY(sa), esize, idx), esize); - return INTARRAY_OK; + return SARRAY_OK; } /* caller should discard values in idx if function returns ENOTFOUND */ -intarray_rstatus_e -intarray_index(uint32_t *idx, const intarray_p ia, uint64_t val) +sarray_rstatus_e +sarray_index(uint32_t *idx, const sarray_p sa, uint64_t val) { uint32_t esize; bool found; - if (ia == NULL || idx == NULL) { - return INTARRAY_ERROR; + if (sa == NULL || idx == NULL) { + return SARRAY_ERROR; } - esize = intarray_esize(ia); + esize = sarray_esize(sa); if (!_validate_range(esize, val)) { - return INTARRAY_EINVALID; + return SARRAY_EINVALID; } - found = _locate(idx, IA_BODY(ia), intarray_nentry(ia), esize, val); + found = _locate(idx, SA_BODY(sa), sarray_nentry(sa), esize, val); if (found) { - return INTARRAY_OK; + return SARRAY_OK; } else { - return INTARRAY_ENOTFOUND; + return SARRAY_ENOTFOUND; } } -intarray_rstatus_e -intarray_insert(intarray_p ia, uint64_t val) +sarray_rstatus_e +sarray_insert(sarray_p sa, uint64_t val) { bool found; uint8_t *body, *p; uint32_t idx, esize, nentry; - if (ia == NULL) { - return INTARRAY_ERROR; + if (sa == NULL) { + return SARRAY_ERROR; } - esize = intarray_esize(ia); + esize = sarray_esize(sa); if (!_validate_range(esize, val)) { - return INTARRAY_EINVALID; + return SARRAY_EINVALID; } - body = IA_BODY(ia); - nentry = intarray_nentry(ia); + body = SA_BODY(sa); + nentry = sarray_nentry(sa); found = _locate(&idx, body, nentry, esize, val); if (found) { - return INTARRAY_EDUP; + return SARRAY_EDUP; } p = _position(body, esize, idx); cc_memmove(p + esize, p, esize * (nentry - idx)); _set_value(p, esize, val); - IA_NENTRY(ia)++; + SA_NENTRY(sa)++; - return INTARRAY_OK; + return SARRAY_OK; } -intarray_rstatus_e -intarray_remove(intarray_p ia, uint64_t val) +sarray_rstatus_e +sarray_remove(sarray_p sa, uint64_t val) { bool found; uint8_t *body, *p; uint32_t idx, esize, nentry; - if (ia == NULL) { - return INTARRAY_ERROR; + if (sa == NULL) { + return SARRAY_ERROR; } - esize = intarray_esize(ia); + esize = sarray_esize(sa); if (!_validate_range(esize, val)) { - return INTARRAY_EINVALID; + return SARRAY_EINVALID; } - body = IA_BODY(ia); - nentry = intarray_nentry(ia); + body = SA_BODY(sa); + nentry = sarray_nentry(sa); found = _locate(&idx, body, nentry, esize, val); if (found) { p = _position(body, esize, idx); cc_memmove(p, p + esize, esize * (nentry - idx - 1)); - IA_NENTRY(ia)--; + SA_NENTRY(sa)--; - return INTARRAY_OK; + return SARRAY_OK; } - return INTARRAY_ENOTFOUND; + return SARRAY_ENOTFOUND; } -intarray_rstatus_e -intarray_truncate(intarray_p ia, int64_t count) +sarray_rstatus_e +sarray_truncate(sarray_p sa, int64_t count) { uint8_t *body; uint32_t esize, nentry; - if (ia == NULL) { - return INTARRAY_ERROR; + if (sa == NULL) { + return SARRAY_ERROR; } if (count == 0) { - return INTARRAY_OK; + return SARRAY_OK; } - body = IA_BODY(ia); - esize = intarray_esize(ia); - nentry = intarray_nentry(ia); + body = SA_BODY(sa); + esize = sarray_esize(sa); + nentry = sarray_nentry(sa); /* if abs(count) >= num entries in the array, remove all */ if (count >= nentry || -count >= nentry) { - return intarray_init(ia, intarray_esize(ia)); + return sarray_init(sa, sarray_esize(sa)); } if (count > 0) { /* only need to move data if truncating from left */ cc_memmove(body, body + esize * count, esize * (nentry - count)); - IA_NENTRY(ia) -= count; + SA_NENTRY(sa) -= count; } else { - IA_NENTRY(ia) += count; + SA_NENTRY(sa) += count; } - return INTARRAY_OK; + return SARRAY_OK; } diff --git a/src/data_structure/sarray/sarray.h b/src/data_structure/sarray/sarray.h new file mode 100644 index 000000000..d2527093d --- /dev/null +++ b/src/data_structure/sarray/sarray.h @@ -0,0 +1,108 @@ +#pragma once + +/* The sarray (sorted array) is designed for sorted array of integers of uniform + * but configurable sizes. Currently we only implemented this for unsigned + * integer entries of width 1-, 2-, 4-, 8-byte. But it can be extended to byte + * strings as well. The array is stored in ASC order without duplicates. Once an + * array is created, these configurable attributes cannot be changed. + * + * Because of the limitation on data type, sarray is both more memory- + * efficient and faster for value lookups compared to a more generic data + * structure such as ziplist. It is particularly useful if users intend to + * keep a sorted list of numbers without duplication, such as an index of + * numeric IDs. + * + * ---------------------------------------------------------------------------- + * + * SARRAY OVERALL LAYOUT + * ===================== + * + * The general layout of the sarray is as follows: + * + * ... + * ╰------------╯ ╰-------------------------╯ + * header body + * + * Overhead: 8 bytes + * + * is the number of entries. + * + * is the size of each entry (of value 1, 2, 4, 8 for now) + * + * + * SARRAY ENTRIES + * ============== + * + * Every entry in the sarray is a simple integer of size specified in the + * header. + * + * RUNTIME + * ======= + * + * Entry lookup takes O(log N) where N is the number of entries in the list. If + * the entry size are below a threshold (64-bytes for now), then a linear scan + * is performed instead of binary lookup. + * + * Insertion and removal of entries involve index-based lookup, as well as + * shifting data. So in additional to the considerations above, the amount of + * data being moved for updates will affect performance. Updates near the "fixed + * end" of the ziplist (currently the beginning) require moving more data and + * therefore will be slower. Overall, it is cheapest to perform updates at the + * end of the array due to zero data movement. + * + */ + +#include + +#define SARRAY_HEADER_SIZE 8 + +typedef uint8_t * sarray_p; + +typedef enum { + SARRAY_OK, + SARRAY_ENOTFOUND, /* value not found error */ + SARRAY_EOOB, /* out-of-bound error */ + SARRAY_EINVALID, /* invalid data error */ + SARRAY_EDUP, /* duplicate value found */ + SARRAY_ERROR, + SARRAY_SENTINEL +} sarray_rstatus_e; + +#define SA_NENTRY(_sa) (*((uint32_t *)(_sa))) +#define SA_ESIZE(_sa) (*((uint32_t *)(_sa) + 1)) + +static inline uint32_t +sarray_nentry(const sarray_p sa) +{ + return SA_NENTRY(sa); +} + +static inline uint32_t +sarray_esize(const sarray_p sa) +{ + return SA_ESIZE(sa); +} + +static inline uint32_t +sarray_size(const sarray_p sa) +{ + return SARRAY_HEADER_SIZE + SA_ESIZE(sa) * SA_NENTRY(sa); +} + +/* initialize an sarray of element size 1/2/4/8 bytes */ +sarray_rstatus_e sarray_init(sarray_p sa, uint32_t esize); + +/* sarray APIs: seek */ +sarray_rstatus_e sarray_value(uint64_t *val, const sarray_p sa, uint32_t idx); +sarray_rstatus_e sarray_index(uint32_t *idx, const sarray_p sa, uint64_t val); + +/* ziplist APIs: modify */ +sarray_rstatus_e sarray_insert(sarray_p sa, uint64_t val); +sarray_rstatus_e sarray_remove(sarray_p sa, uint64_t val); + +/* + * if count is positive, remove count entries starting at the beginning + * if count is negative, remove -count entries starting at the end + */ +sarray_rstatus_e sarray_truncate(sarray_p sa, int64_t count); + diff --git a/src/protocol/data/resp/cmd_list.h b/src/protocol/data/resp/cmd_list.h index 3c6847b38..eb496f9ae 100644 --- a/src/protocol/data/resp/cmd_list.h +++ b/src/protocol/data/resp/cmd_list.h @@ -31,12 +31,12 @@ #define REQ_LIST(ACTION) \ ACTION( REQ_LIST_CREATE, "List.create", 2, 0 )\ ACTION( REQ_LIST_DELETE, "List.delete", 2, 2 )\ - ACTION( REQ_LIST_TRIM, "List.trim", 4, 0 )\ ACTION( REQ_LIST_LEN, "List.len", 2, 0 )\ ACTION( REQ_LIST_FIND, "List.find", 3, 0 )\ ACTION( REQ_LIST_GET, "List.get", 2, 2 )\ ACTION( REQ_LIST_INSERT, "List.insert", 4, 0 )\ - ACTION( REQ_LIST_PUSH, "List.push", 3, -1 ) + ACTION( REQ_LIST_PUSH, "List.push", 3, -1 )\ + ACTION( REQ_LIST_TRIM, "List.trim", 4, 0 ) typedef enum list_elem { LIST_VERB = 0, diff --git a/src/protocol/data/resp/cmd_sarray.h b/src/protocol/data/resp/cmd_sarray.h new file mode 100644 index 000000000..80453d8a2 --- /dev/null +++ b/src/protocol/data/resp/cmd_sarray.h @@ -0,0 +1,50 @@ +#pragma once + +/** + * create: create an empty array or integer width ESIZE + * SArray.create KEY ESIZE + * + * delete: delete an array + * SArray.delete KEY + * + * len: return number of entries in array + * SArray.len KEY + * + * find: find (rank of an value) in array + * SArray.find KEY VALUE + * + * get: get entry/entries at an index + * SArray.get KEY [INDEX [COUNT]] + * + * insert: insert value + * SArray.insert KEY VALUE [VALUE ...] + * + * remove: remove a particular value from array + * SArray.remove KEY VALUE + * + * truncate: truncate a array + * SArray.truncate KEY COUNT + * + */ + + +/* type string #arg #opt */ +#define REQ_SARRAY(ACTION) \ + ACTION( REQ_SARRAY_CREATE, "SArray.create", 3, 0 )\ + ACTION( REQ_SARRAY_DELETE, "SArray.delete", 2, 0 )\ + ACTION( REQ_SARRAY_LEN, "SArray.len", 2, 0 )\ + ACTION( REQ_SARRAY_FIND, "SArray.find", 3, 0 )\ + ACTION( REQ_SARRAY_GET, "SArray.get", 2, 2 )\ + ACTION( REQ_SARRAY_INSERT, "SArray.insert", 3, -1 )\ + ACTION( REQ_SARRAY_REMOVE, "SArray.remove", 3, 0 )\ + ACTION( REQ_SARRAY_TRUNCATE, "SArray.truncate", 3, 0 ) + +typedef enum sarray_elem { + SARRAY_VERB = 0, + SARRAY_KEY = 1, + SARRAY_ESIZE = 2, + SARRAY_VAL = 2, + SARRAY_IDX = 2, + SARRAY_CNT = 2, + SARRAY_ICNT = 3, /* when an index is also present */ +} sarray_elem_e; diff --git a/src/protocol/data/resp/request.c b/src/protocol/data/resp/request.c index b0ab87312..a5707ab37 100644 --- a/src/protocol/data/resp/request.c +++ b/src/protocol/data/resp/request.c @@ -21,6 +21,7 @@ struct command command_table[REQ_SENTINEL] = { REQ_BITMAP(CMD_INIT) REQ_HASH(CMD_INIT) REQ_LIST(CMD_INIT) + REQ_SARRAY(CMD_INIT) REQ_ZSET(CMD_INIT) REQ_MISC(CMD_INIT) }; diff --git a/src/protocol/data/resp/request.h b/src/protocol/data/resp/request.h index fce9bc817..a32854851 100644 --- a/src/protocol/data/resp/request.h +++ b/src/protocol/data/resp/request.h @@ -4,6 +4,7 @@ #include "cmd_hash.h" #include "cmd_list.h" #include "cmd_misc.h" +#include "cmd_sarray.h" #include "cmd_zset.h" #include "token.h" @@ -47,6 +48,7 @@ typedef enum cmd_type { REQ_BITMAP(GET_TYPE) REQ_HASH(GET_TYPE) REQ_LIST(GET_TYPE) + REQ_SARRAY(GET_TYPE) REQ_ZSET(GET_TYPE) REQ_MISC(GET_TYPE) REQ_SENTINEL diff --git a/src/protocol/data/resp/response.h b/src/protocol/data/resp/response.h index 89cfd3710..6b9ebd241 100644 --- a/src/protocol/data/resp/response.h +++ b/src/protocol/data/resp/response.h @@ -44,10 +44,12 @@ typedef struct { #define RSP_NOTFOUND "NOT_FOUND" #define RSP_PONG "PONG" #define RSP_EXIST "EXIST" /* key already exists and op is non-overwriting */ +#define RSP_NOOP "NOOP" /* key unmodified */ #define RSP_ERR_ARG "Err invalid argument" #define RSP_ERR_NOSUPPORT "Err command not supported" #define RSP_ERR_OUTOFRANGE "Err index out of range" +#define RSP_ERR_SERVER "Err unspecified server failure" #define RSP_ERR_STORAGE "Err storage failure" #define RSP_ERR_TYPE "Err type mismatch" diff --git a/src/server/ds/CMakeLists.txt b/src/server/ds/CMakeLists.txt index 9b031c6c7..8ae123622 100644 --- a/src/server/ds/CMakeLists.txt +++ b/src/server/ds/CMakeLists.txt @@ -10,6 +10,7 @@ set(SOURCE set(MODULES core ds_ziplist + ds_sarray protocol_admin protocol_resp slab diff --git a/src/server/ds/data/CMakeLists.txt b/src/server/ds/data/CMakeLists.txt index 94a887408..e3e68cd45 100644 --- a/src/server/ds/data/CMakeLists.txt +++ b/src/server/ds/data/CMakeLists.txt @@ -3,4 +3,5 @@ set(SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/process.c ${CMAKE_CURRENT_SOURCE_DIR}/cmd_misc.c ${CMAKE_CURRENT_SOURCE_DIR}/cmd_list.c + ${CMAKE_CURRENT_SOURCE_DIR}/cmd_sarray.c PARENT_SCOPE) diff --git a/src/server/ds/data/cmd_list.c b/src/server/ds/data/cmd_list.c index 8e52c4024..47f6d0527 100644 --- a/src/server/ds/data/cmd_list.c +++ b/src/server/ds/data/cmd_list.c @@ -26,7 +26,7 @@ _elem2blob(struct blob *blob, const struct element *elem) } static inline struct bstring * -_get_key(struct request *req) +_get_key(const struct request *req) { ASSERT(array_nelem(req->token) > LIST_KEY); struct element *key = (struct element *)array_get(req->token, LIST_KEY); @@ -34,14 +34,14 @@ _get_key(struct request *req) } static inline struct element * -_get_val(struct request *req) +_get_val(const struct request *req) { ASSERT(array_nelem(req->token) > LIST_VAL); return (struct element *)array_get(req->token, LIST_VAL); } static inline bool -_get_idx(int64_t *idx, struct request *req) +_get_idx(int64_t *idx, const struct request *req) { ASSERT(array_nelem(req->token) > LIST_IDX); ASSERT(idx != NULL); @@ -54,7 +54,7 @@ _get_idx(int64_t *idx, struct request *req) } static inline bool -_get_vidx(int64_t *vidx, struct request *req) +_get_vidx(int64_t *vidx, const struct request *req) { ASSERT(array_nelem(req->token) > LIST_VIDX); ASSERT(vidx != NULL); @@ -67,7 +67,7 @@ _get_vidx(int64_t *vidx, struct request *req) } static inline bool -_get_cnt(int64_t *cnt, struct request *req) +_get_cnt(int64_t *cnt, const struct request *req) { ASSERT(array_nelem(req->token) > LIST_CNT); ASSERT(cnt != NULL); @@ -80,7 +80,7 @@ _get_cnt(int64_t *cnt, struct request *req) } static inline struct item * -_add_key(struct response *rsp, struct bstring *key) +_add_key(struct response *rsp, const struct bstring *key) { struct element *reply = (struct element *)array_get(rsp->token, 0); struct item *it; @@ -226,7 +226,8 @@ _rsp_storage_err(struct response *rsp, struct element *reply, void -cmd_list_create(struct response *rsp, struct request *req, struct command *cmd) +cmd_list_create(struct response *rsp, const struct request *req, const struct + command *cmd) { struct item *it; struct bstring *key = _get_key(req); @@ -312,7 +313,8 @@ _delete_list_vals(struct element *reply, struct response *rsp, const struct bstr } void -cmd_list_delete(struct response *rsp, struct request *req, struct command *cmd) +cmd_list_delete(struct response *rsp, const struct request *req, const struct + command *cmd) { struct bstring *key = _get_key(req); struct element *reply = (struct element *)array_push(rsp->token); @@ -348,7 +350,8 @@ cmd_list_delete(struct response *rsp, struct request *req, struct command *cmd) } void -cmd_list_trim(struct response *rsp, struct request *req, struct command *cmd) +cmd_list_trim(struct response *rsp, const struct request *req, const struct + command *cmd) { struct bstring *key = _get_key(req); struct element *reply = (struct element *)array_push(rsp->token); @@ -397,7 +400,8 @@ cmd_list_trim(struct response *rsp, struct request *req, struct command *cmd) } void -cmd_list_len(struct response *rsp, struct request *req, struct command *cmd) +cmd_list_len(struct response *rsp, const struct request *req, const struct + command *cmd) { struct bstring *key = _get_key(req); struct element *reply = (struct element *)array_push(rsp->token); @@ -426,7 +430,8 @@ cmd_list_len(struct response *rsp, struct request *req, struct command *cmd) } void -cmd_list_find(struct response *rsp, struct request *req, struct command *cmd) +cmd_list_find(struct response *rsp, const struct request *req, const struct + command *cmd) { /* TODO: this command doesn't seem to have a counterpart in redis. let's re-evaluate whether or not we want to support this functionality @@ -438,7 +443,8 @@ cmd_list_find(struct response *rsp, struct request *req, struct command *cmd) } void -cmd_list_get(struct response *rsp, struct request *req, struct command *cmd) +cmd_list_get(struct response *rsp, const struct request *req, const struct + command *cmd) { struct bstring *key = _get_key(req); struct element *reply = (struct element *)array_push(rsp->token); @@ -500,7 +506,8 @@ cmd_list_get(struct response *rsp, struct request *req, struct command *cmd) } void -cmd_list_insert(struct response *rsp, struct request *req, struct command *cmd) +cmd_list_insert(struct response *rsp, const struct request *req, const struct + command *cmd) { struct bstring *key = _get_key(req); struct element *reply = (struct element *)array_push(rsp->token); @@ -560,7 +567,8 @@ cmd_list_insert(struct response *rsp, struct request *req, struct command *cmd) } void -cmd_list_push(struct response *rsp, struct request *req, struct command *cmd) +cmd_list_push(struct response *rsp, const struct request *req, const struct + command *cmd) { struct bstring *key = _get_key(req); struct element *reply = (struct element *)array_push(rsp->token); diff --git a/src/server/ds/data/cmd_list.h b/src/server/ds/data/cmd_list.h index 82b11aecc..661f34bc8 100644 --- a/src/server/ds/data/cmd_list.h +++ b/src/server/ds/data/cmd_list.h @@ -31,11 +31,11 @@ struct response; struct command; /* cmd_* functions must be command_fn (process.c) compatible */ -void cmd_list_create(struct response *rsp, struct request *req, struct command *cmd); -void cmd_list_delete(struct response *rsp, struct request *req, struct command *cmd); -void cmd_list_trim(struct response *rsp, struct request *req, struct command *cmd); -void cmd_list_len(struct response *rsp, struct request *req, struct command *cmd); -void cmd_list_find(struct response *rsp, struct request *req, struct command *cmd); -void cmd_list_get(struct response *rsp, struct request *req, struct command *cmd); -void cmd_list_insert(struct response *rsp, struct request *req, struct command *cmd); -void cmd_list_push(struct response *rsp, struct request *req, struct command *cmd); +void cmd_list_create(struct response *rsp, const struct request *req, const struct command *cmd); +void cmd_list_delete(struct response *rsp, const struct request *req, const struct command *cmd); +void cmd_list_trim(struct response *rsp, const struct request *req, const struct command *cmd); +void cmd_list_len(struct response *rsp, const struct request *req, const struct command *cmd); +void cmd_list_find(struct response *rsp, const struct request *req, const struct command *cmd); +void cmd_list_get(struct response *rsp, const struct request *req, const struct command *cmd); +void cmd_list_insert(struct response *rsp, const struct request *req, const struct command *cmd); +void cmd_list_push(struct response *rsp, const struct request *req, const struct command *cmd); diff --git a/src/server/ds/data/cmd_misc.c b/src/server/ds/data/cmd_misc.c index edec66795..c01b5b658 100644 --- a/src/server/ds/data/cmd_misc.c +++ b/src/server/ds/data/cmd_misc.c @@ -8,7 +8,8 @@ bool allow_flush = ALLOW_FLUSH; void -cmd_ping(struct response *rsp, struct request *req, struct command *cmd) +cmd_ping(struct response *rsp, const struct request *req, const struct command + *cmd) { struct element *el = NULL; diff --git a/src/server/ds/data/cmd_misc.h b/src/server/ds/data/cmd_misc.h index ca2410717..8524533a6 100644 --- a/src/server/ds/data/cmd_misc.h +++ b/src/server/ds/data/cmd_misc.h @@ -10,4 +10,4 @@ struct response; struct command; /* cmd_* functions must be command_fn (process.c) compatible */ -void cmd_ping(struct response *rsp, struct request *req, struct command *cmd); +void cmd_ping(struct response *rsp, const struct request *req, const struct command *cmd); diff --git a/src/server/ds/data/cmd_sarray.c b/src/server/ds/data/cmd_sarray.c new file mode 100644 index 000000000..e12d04dd3 --- /dev/null +++ b/src/server/ds/data/cmd_sarray.c @@ -0,0 +1,481 @@ +#include "process.h" +#include "shared.h" + +#include "data_structure/sarray/sarray.h" +#include "storage/slab/item.h" +#include "storage/slab/slab.h" + +#include +#include +#include +#include + + +static inline struct item * +_add_key(struct response *rsp, struct bstring *key) +{ + struct element *reply = (struct element *)array_get(rsp->token, 0); + struct item *it; + item_rstatus_e istatus; + + it = item_get(key); + if (it != NULL) { + rsp->type = reply->type = ELEM_ERR; + reply->bstr = str2bstr(RSP_EXIST); + INCR(process_metrics, sarray_create_exist); + + return NULL; + } else { + /* TODO: figure out a TTL story here */ + istatus = item_reserve(&it, key, NULL, SARRAY_HEADER_SIZE, 0, INT32_MAX); + if (istatus != ITEM_OK) { + rsp->type = reply->type = ELEM_ERR; + reply->bstr = str2bstr(RSP_ERR_STORAGE); + INCR(process_metrics, sarray_create_ex); + INCR(process_metrics, process_ex); + } else { + INCR(process_metrics, sarray_create_ok); + } + + return it; + } +} + +/** + * Attempt to extend an item by delta bytes. This is accomplished by first + * checking if adding delta bytes to payload of it would require a larger item + * to fit. + * - If no, then returns OK status without altering item. + * - If yes, then attempts to reserve an item that would be large enough. If + * this succeeds, then `it' is updated to the new item, and the payload of + * the old item is copied to the new one. If allocation fails, then a failure + * status is returned, and `it' remain unchanged. + */ +static inline item_rstatus_e +_realloc_key(struct item **it, const struct bstring *key, uint32_t delta) +{ + ASSERT(it != NULL && *it != NULL); + + if (!item_will_fit(*it, delta)) { + /* must alloc new item, cannot fit in place */ + struct item *nit; + item_rstatus_e istatus; + + /* carry over all applilcable item metadata */ + istatus = item_reserve(&nit, key, NULL, item_nval(*it) + delta, + (*it)->olen, (*it)->expire_at); + if (istatus != ITEM_OK) { + return istatus; + } + /*copy item payload */ + cc_memcpy(nit->end, (*it)->end, item_npayload(*it)); + + *it = nit; + item_insert(nit, key); + } + + ASSERT(item_will_fit(*it, delta)); + return ITEM_OK; +} + +void +cmd_sarray_create(struct response *rsp, const struct request *req, + const struct command *cmd) +{ + struct element *reply = (struct element *)array_push(rsp->token); + struct bstring *key; + struct item *it; + int64_t esize; + + ASSERT(array_nelem(req->token) == cmd->narg); + + INCR(process_metrics, sarray_create); + + if (!req_get_bstr(&key, req, SARRAY_KEY)) { + compose_rsp_client_err(rsp, reply, cmd, key); + INCR(process_metrics, sarray_create_ex); + + return; + } + log_verb("before esize"); + if (!req_get_int(&esize, req, SARRAY_ESIZE)) { + compose_rsp_client_err(rsp, reply, cmd, key); + INCR(process_metrics, sarray_find_ex); + + return; + } + log_verb("post parse"); + + it = _add_key(rsp, key); + if (it == NULL) { + compose_rsp_storage_err(rsp, reply, cmd, key); + INCR(process_metrics, sarray_create_ex); + + return; + } + + /* initialize data structure */ + sarray_init((sarray_p)item_data(it), (uint32_t)esize); + it->vlen = SARRAY_HEADER_SIZE; + + item_insert(it, key); + + compose_rsp_ok(rsp, reply, cmd, key); + INCR(process_metrics, sarray_create_ok); +} + +void +cmd_sarray_delete(struct response *rsp, const struct request *req, + const struct command *cmd) +{ + struct element *reply = (struct element *)array_push(rsp->token); + struct bstring *key; + + ASSERT(array_nelem(req->token) == cmd->narg); + + INCR(process_metrics, sarray_delete); + + if (!req_get_bstr(&key, req, SARRAY_KEY)) { + compose_rsp_client_err(rsp, reply, cmd, key); + INCR(process_metrics, sarray_delete_ex); + + return; + } + + if (item_delete(key)) { + compose_rsp_ok(rsp, reply, cmd, key); + INCR(process_metrics, sarray_delete_ok); + } else { + compose_rsp_notfound(rsp, reply, cmd, key); + INCR(process_metrics, sarray_delete_notfound); + } +} + +void +cmd_sarray_len(struct response *rsp, const struct request *req, + const struct command *cmd) +{ + struct element *reply = (struct element *)array_push(rsp->token); + struct bstring *key; + struct item *it; + uint32_t nentry; + + ASSERT(array_nelem(req->token) == cmd->narg); + + INCR(process_metrics, sarray_len); + + if (!req_get_bstr(&key, req, SARRAY_KEY)) { + compose_rsp_client_err(rsp, reply, cmd, key); + INCR(process_metrics, sarray_len_ex); + + return; + } + + it = item_get(key); + if (it == NULL) { + compose_rsp_notfound(rsp, reply, cmd, key); + INCR(process_metrics, sarray_len_notfound); + + return; + } + + nentry = sarray_nentry((sarray_p)item_data(it)); + + rsp->type = reply->type = ELEM_INT; + reply->num = (int64_t)nentry; + log_verb("command '%.*s' '%.*s' succeeded, sarray length %u", + cmd->bstr.len, cmd->bstr.data, key->len, key->data, nentry); +} + +void +cmd_sarray_find(struct response *rsp, const struct request *req, const struct + command *cmd) +{ + struct element *reply = (struct element *)array_push(rsp->token); + struct bstring *key; + struct item *it; + uint32_t idx; + int64_t val; + sarray_rstatus_e status; + + ASSERT(array_nelem(req->token) == cmd->narg); + + INCR(process_metrics, sarray_find); + + if (!req_get_bstr(&key, req, SARRAY_KEY)) { + compose_rsp_client_err(rsp, reply, cmd, key); + INCR(process_metrics, sarray_find_ex); + + return; + } + if (!req_get_int(&val, req, SARRAY_VAL)) { + compose_rsp_client_err(rsp, reply, cmd, key); + INCR(process_metrics, sarray_find_ex); + + return; + } + + it = item_get(key); + if (it == NULL) { + compose_rsp_notfound(rsp, reply, cmd, key); + INCR(process_metrics, sarray_find_notfound); + + return; + } + + status = sarray_index(&idx, (sarray_p)item_data(it), (uint32_t)val); + switch (status) { + case SARRAY_OK: + rsp->type = reply->type = ELEM_INT; + reply->num = (int64_t)idx; + log_verb("command '%.*s' '%.*s' succeeded, value %"PRIu64" at index " + PRIu32, cmd->bstr.len, cmd->bstr.data, key->len, key->data, val, + idx); + INCR(process_metrics, sarray_find_ok); + + break; + case SARRAY_ENOTFOUND: + compose_rsp_nil(rsp, reply, cmd, key); + INCR(process_metrics, sarray_find_notamember); + + break; + case SARRAY_EINVALID: + compose_rsp_client_err(rsp, reply, cmd, key); + INCR(process_metrics, sarray_find_ex); + + break; + default: + compose_rsp_server_err(rsp, reply, cmd, key); + NOT_REACHED(); + } +} + +void +cmd_sarray_get(struct response *rsp, const struct request *req, const struct + command *cmd) +{ + struct element *reply = (struct element *)array_push(rsp->token); + struct bstring *key; + struct item *it; + int64_t idx; + uint64_t val; + sarray_rstatus_e status; + + ASSERT(array_nelem(req->token) == cmd->narg); + + INCR(process_metrics, sarray_get); + + if (!req_get_bstr(&key, req, SARRAY_KEY)) { + compose_rsp_client_err(rsp, reply, cmd, key); + INCR(process_metrics, sarray_get_ex); + + return; + } + if (!req_get_int(&idx, req, SARRAY_IDX)) { + compose_rsp_client_err(rsp, reply, cmd, key); + INCR(process_metrics, sarray_get_ex); + + return; + } + + it = item_get(key); + if (it == NULL) { + compose_rsp_notfound(rsp, reply, cmd, key); + INCR(process_metrics, sarray_get_notfound); + + return; + } + + status = sarray_value(&val, (sarray_p)item_data(it), (uint32_t)idx); + switch (status) { + case SARRAY_OK: + rsp->type = reply->type = ELEM_INT; + reply->num = (int64_t)val; + log_verb("command '%.*s' '%.*s' succeeded, index %"PRIu32" has value " + PRIu64, cmd->bstr.len, cmd->bstr.data, key->len, key->data, idx, + val); + INCR(process_metrics, sarray_get_ok); + + break; + case SARRAY_EOOB: + compose_rsp_oob(rsp, reply, cmd, key, idx); + INCR(process_metrics, sarray_get_oob); + + break; + default: + compose_rsp_server_err(rsp, reply, cmd, key); + NOT_REACHED(); + } +} + +void +cmd_sarray_insert(struct response *rsp, const struct request *req, const struct + command *cmd) +{ + struct element *reply = (struct element *)array_push(rsp->token); + struct bstring *key; + struct item *it; + uint32_t delta = 0; + int64_t val; + sarray_p sa; + sarray_rstatus_e status; + + ASSERT(array_nelem(req->token) == cmd->narg); + + INCR(process_metrics, sarray_insert); + + if (!req_get_bstr(&key, req, SARRAY_KEY)) { + compose_rsp_client_err(rsp, reply, cmd, key); + INCR(process_metrics, sarray_insert_ex); + + return; + } + if (!req_get_int(&val, req, SARRAY_VAL)) { + compose_rsp_client_err(rsp, reply, cmd, key); + INCR(process_metrics, sarray_insert_ex); + + return; + } + + it = item_get(key); + if (it == NULL) { + compose_rsp_notfound(rsp, reply, cmd, key); + INCR(process_metrics, sarray_insert_notfound); + + return; + } + + delta = sarray_esize((sarray_p)item_data(it)); + if (_realloc_key(&it, key, delta) != ITEM_OK) { + compose_rsp_storage_err(rsp, reply, cmd, key); + INCR(process_metrics, sarray_insert_ex); + + return; + } + + sa = (sarray_p)item_data(it); + status = sarray_insert(sa, (uint64_t)val); + + if (status == SARRAY_EINVALID) { + compose_rsp_client_err(rsp, reply, cmd, key); + INCR(process_metrics, sarray_insert_ex); + + return; + } + if (status == SARRAY_EDUP) { + compose_rsp_noop(rsp, reply, cmd, key); + INCR(process_metrics, sarray_insert_noop); + + return; + } + + compose_rsp_ok(rsp, reply, cmd, key); + INCR(process_metrics, sarray_insert_ok); +} + +void +cmd_sarray_remove(struct response *rsp, const struct request *req, const struct + command *cmd) +{ + struct element *reply = (struct element *)array_push(rsp->token); + struct bstring *key; + struct item *it; + int64_t val; + sarray_p sa; + sarray_rstatus_e status; + + ASSERT(array_nelem(req->token) == cmd->narg); + + INCR(process_metrics, sarray_insert); + + if (!req_get_bstr(&key, req, SARRAY_KEY)) { + compose_rsp_client_err(rsp, reply, cmd, key); + INCR(process_metrics, sarray_remove_ex); + + return; + } + if (!req_get_int(&val, req, SARRAY_VAL)) { + compose_rsp_client_err(rsp, reply, cmd, key); + INCR(process_metrics, sarray_remove_ex); + + return; + } + + it = item_get(key); + if (it == NULL) { + compose_rsp_notfound(rsp, reply, cmd, key); + INCR(process_metrics, sarray_remove_notfound); + + return; + } + + sa = (sarray_p)item_data(it); + status = sarray_remove(sa, val); + + switch (status) { + case SARRAY_OK: + /* TODO: should we try to "fit" to a smaller item here? */ + compose_rsp_ok(rsp, reply, cmd, key); + INCR(process_metrics, sarray_remove_ok); + + break; + case SARRAY_ENOTFOUND: + compose_rsp_noop(rsp, reply, cmd, key); + INCR(process_metrics, sarray_remove_noop); + + break; + case SARRAY_EINVALID: + /* client error, bad argument */ + compose_rsp_client_err(rsp, reply, cmd, key); + INCR(process_metrics, sarray_remove_ex); + + break; + default: + compose_rsp_server_err(rsp, reply, cmd, key); + NOT_REACHED(); + } +} + +void +cmd_sarray_truncate(struct response *rsp, const struct request *req, const + struct command *cmd) +{ + struct element *reply = (struct element *)array_push(rsp->token); + struct bstring *key; + struct item *it; + int64_t cnt; + sarray_rstatus_e status; + + ASSERT(array_nelem(req->token) == cmd->narg); + + INCR(process_metrics, sarray_truncate); + + if (!req_get_bstr(&key, req, SARRAY_KEY)) { + compose_rsp_client_err(rsp, reply, cmd, key); + INCR(process_metrics, sarray_truncate_ex); + + return; + } + if (!req_get_int(&cnt, req, SARRAY_IDX)) { + compose_rsp_client_err(rsp, reply, cmd, key); + INCR(process_metrics, sarray_truncate_ex); + + return; + } + + it = item_get(key); + if (it == NULL) { + compose_rsp_notfound(rsp, reply, cmd, key); + INCR(process_metrics, sarray_truncate_notfound); + + return; + } + + status = sarray_truncate((sarray_p)item_data(it), cnt); + if (status != SARRAY_OK) { + compose_rsp_server_err(rsp, reply, cmd, key); + } + + compose_rsp_ok(rsp, reply, cmd, key); + INCR(process_metrics, sarray_truncate_ok); +} diff --git a/src/server/ds/data/cmd_sarray.h b/src/server/ds/data/cmd_sarray.h new file mode 100644 index 000000000..0d8725b17 --- /dev/null +++ b/src/server/ds/data/cmd_sarray.h @@ -0,0 +1,54 @@ +#pragma once + +/* name type description */ +#define PROCESS_SARRAY_METRIC(ACTION) \ + ACTION( sarray_create, METRIC_COUNTER, "# sarray create requests" )\ + ACTION( sarray_create_exist, METRIC_COUNTER, "# sarray already exist" )\ + ACTION( sarray_create_ok, METRIC_COUNTER, "# sarray stored" )\ + ACTION( sarray_create_ex, METRIC_COUNTER, "# sarray create exceptions" )\ + ACTION( sarray_delete, METRIC_COUNTER, "# sarray delete requests" )\ + ACTION( sarray_delete_ok, METRIC_COUNTER, "# sarray delete success" )\ + ACTION( sarray_delete_notfound, METRIC_COUNTER, "# sarray delete miss" )\ + ACTION( sarray_delete_ex, METRIC_COUNTER, "# sarray delete exceptions" )\ + ACTION( sarray_len, METRIC_COUNTER, "# sarray length requests" )\ + ACTION( sarray_len_ok, METRIC_COUNTER, "# sarray length success" )\ + ACTION( sarray_len_notfound, METRIC_COUNTER, "# sarray length miss" )\ + ACTION( sarray_len_ex, METRIC_COUNTER, "# sarray len exceptions" )\ + ACTION( sarray_find, METRIC_COUNTER, "# sarray find requests" )\ + ACTION( sarray_find_ok, METRIC_COUNTER, "# sarray find success" )\ + ACTION( sarray_find_notfound, METRIC_COUNTER, "# sarray find miss" )\ + ACTION( sarray_find_notamember, METRIC_COUNTER, "# sarray find not present" )\ + ACTION( sarray_find_ex, METRIC_COUNTER, "# sarray find exceptions" )\ + ACTION( sarray_get, METRIC_COUNTER, "# sarray get requests" )\ + ACTION( sarray_get_ok, METRIC_COUNTER, "# sarray get success" )\ + ACTION( sarray_get_notfound, METRIC_COUNTER, "# sarray get miss" )\ + ACTION( sarray_get_oob, METRIC_COUNTER, "# sarray get out of bound" )\ + ACTION( sarray_get_ex, METRIC_COUNTER, "# sarray get exceptions" )\ + ACTION( sarray_insert, METRIC_COUNTER, "# sarray insert requests" )\ + ACTION( sarray_insert_ok, METRIC_COUNTER, "# sarray insert success" )\ + ACTION( sarray_insert_notfound, METRIC_COUNTER, "# sarray insert miss" )\ + ACTION( sarray_insert_noop, METRIC_COUNTER, "# sarray insert no action" )\ + ACTION( sarray_insert_ex, METRIC_COUNTER, "# sarray insert exceptions" )\ + ACTION( sarray_remove, METRIC_COUNTER, "# sarray remove requests" )\ + ACTION( sarray_remove_ok, METRIC_COUNTER, "# sarray remove success" )\ + ACTION( sarray_remove_notfound, METRIC_COUNTER, "# sarray remove miss" )\ + ACTION( sarray_remove_noop, METRIC_COUNTER, "# sarray remove no-op" )\ + ACTION( sarray_remove_ex, METRIC_COUNTER, "# sarray remove exceptions" )\ + ACTION( sarray_truncate, METRIC_COUNTER, "# sarray truncate requests" )\ + ACTION( sarray_truncate_ok, METRIC_COUNTER, "# sarray truncate success" )\ + ACTION( sarray_truncate_notfound, METRIC_COUNTER, "# sarray truncate miss" )\ + ACTION( sarray_truncate_ex, METRIC_COUNTER, "# sarray truncate exceptions" ) + +struct request; +struct response; +struct command; + +/* cmd_* functions must be command_fn (process.c) compatible */ +void cmd_sarray_create(struct response *rsp, const struct request *req, const struct command *cmd); +void cmd_sarray_delete(struct response *rsp, const struct request *req, const struct command *cmd); +void cmd_sarray_truncate(struct response *rsp, const struct request *req, const struct command *cmd); +void cmd_sarray_len(struct response *rsp, const struct request *req, const struct command *cmd); +void cmd_sarray_find(struct response *rsp, const struct request *req, const struct command *cmd); +void cmd_sarray_get(struct response *rsp, const struct request *req, const struct command *cmd); +void cmd_sarray_insert(struct response *rsp, const struct request *req, const struct command *cmd); +void cmd_sarray_remove(struct response *rsp, const struct request *req, const struct command *cmd); diff --git a/src/server/ds/data/process.c b/src/server/ds/data/process.c index dfcce9fbb..14ccdc330 100644 --- a/src/server/ds/data/process.c +++ b/src/server/ds/data/process.c @@ -14,7 +14,8 @@ #define OTHER_ERR_MSG "unknown server error" -typedef void (* command_fn)(struct response *, struct request *, struct command *cmd); +typedef void (* command_fn)(struct response *, const struct request *, const + struct command *cmd); static command_fn command_registry[REQ_SENTINEL]; static bool process_init = false; @@ -36,7 +37,6 @@ process_setup(process_options_st *options, process_metrics_st *metrics) allow_flush = option_bool(&options->allow_flush); } - command_registry[REQ_PING] = cmd_ping; command_registry[REQ_LIST_CREATE] = cmd_list_create; command_registry[REQ_LIST_DELETE] = cmd_list_delete; command_registry[REQ_LIST_TRIM] = cmd_list_trim; @@ -46,6 +46,17 @@ process_setup(process_options_st *options, process_metrics_st *metrics) command_registry[REQ_LIST_INSERT] = cmd_list_insert; command_registry[REQ_LIST_PUSH] = cmd_list_push; + command_registry[REQ_SARRAY_CREATE] = cmd_sarray_create; + command_registry[REQ_SARRAY_DELETE] = cmd_sarray_delete; + command_registry[REQ_SARRAY_TRUNCATE] = cmd_sarray_truncate; + command_registry[REQ_SARRAY_LEN] = cmd_sarray_len; + command_registry[REQ_SARRAY_FIND] = cmd_sarray_find; + command_registry[REQ_SARRAY_GET] = cmd_sarray_get; + command_registry[REQ_SARRAY_INSERT] = cmd_sarray_insert; + command_registry[REQ_SARRAY_REMOVE] = cmd_sarray_remove; + + command_registry[REQ_PING] = cmd_ping; + process_init = true; } diff --git a/src/server/ds/data/process.h b/src/server/ds/data/process.h index 8c9952ab1..1f484418d 100644 --- a/src/server/ds/data/process.h +++ b/src/server/ds/data/process.h @@ -2,6 +2,7 @@ #include "cmd_misc.h" #include "cmd_list.h" +#include "cmd_sarray.h" #include #include @@ -27,6 +28,7 @@ typedef struct { typedef struct { PROCESS_METRIC(METRIC_DECLARE) PROCESS_LIST_METRIC(METRIC_DECLARE) + PROCESS_SARRAY_METRIC(METRIC_DECLARE) PROCESS_MISC_METRIC(METRIC_DECLARE) } process_metrics_st; diff --git a/src/server/ds/data/shared.h b/src/server/ds/data/shared.h new file mode 100644 index 000000000..8f8095edb --- /dev/null +++ b/src/server/ds/data/shared.h @@ -0,0 +1,119 @@ +#include "protocol/data/resp_include.h" + +/*TODO(yao): moving this to protocol/data/resp ? */ + +/* help functions to compose common responses */ +static inline void +compose_rsp_ok(struct response *rsp, struct element *reply, + const struct command *cmd, const struct bstring *key) +{ + rsp->type = reply->type = ELEM_STR; + reply->bstr = str2bstr(RSP_OK); + log_verb("command '%.*s' '%.*s' succeeded", cmd->bstr.len, cmd->bstr.data, + key->len, key->data); +} + +static inline void +compose_rsp_oob(struct response *rsp, struct element *reply, + const struct command *cmd, const struct bstring *key, int64_t idx) +{ + rsp->type = reply->type = ELEM_ERR; + reply->bstr = str2bstr(RSP_ERR_OUTOFRANGE); + log_verb("command '%.*s' '%.*s' has idx %lld out of bounds", + cmd->bstr.len, cmd->bstr.data, key->len, key->data, idx); +} + +static inline void +compose_rsp_notfound(struct response *rsp, struct element *reply, + const struct command *cmd, const struct bstring *key) +{ + rsp->type = reply->type = ELEM_STR; + reply->bstr = str2bstr(RSP_NOTFOUND); + log_verb("command '%.*s' '%.*s' miss, key not found", + cmd->bstr.len, cmd->bstr.data, key->len, key->data); +} + +static inline void +compose_rsp_nil(struct response *rsp, struct element *reply, + const struct command *cmd, const struct bstring *key) +{ + rsp->type = reply->type = ELEM_NIL; + log_verb("command '%.*s' '%.*s' returns nil", + cmd->bstr.len, cmd->bstr.data, key->len, key->data); +} + +static inline void +compose_rsp_noop(struct response *rsp, struct element *reply, + const struct command *cmd, const struct bstring *key) +{ + rsp->type = reply->type = ELEM_ERR; + reply->bstr = str2bstr(RSP_NOOP); + log_verb("command '%.*s' '%.*s' is a noop", + cmd->bstr.len, cmd->bstr.data, key->len, key->data); +} + +static inline void +compose_rsp_client_err(struct response *rsp, struct element *reply, + const struct command *cmd, const struct bstring *key) +{ + rsp->type = reply->type = ELEM_ERR; + reply->bstr = str2bstr(RSP_ERR_ARG); + INCR(process_metrics, process_client_ex); + log_debug("command '%.*s' '%.*s' has invalid arg(s)", + cmd->bstr.len, cmd->bstr.data, key->len, key->data); +} + +static inline void +compose_rsp_storage_err(struct response *rsp, struct element *reply, + const struct command *cmd, const struct bstring *key) +{ + rsp->type = reply->type = ELEM_ERR; + reply->bstr = str2bstr(RSP_ERR_STORAGE); + INCR(process_metrics, process_server_ex); + log_warn("command '%.*s' '%.*s' failed, unable to allocate storage", + cmd->bstr.len, cmd->bstr.data, key->len, key->data); +} + +static inline void +compose_rsp_server_err(struct response *rsp, struct element *reply, + const struct command *cmd, const struct bstring *key) +{ + rsp->type = reply->type = ELEM_ERR; + reply->bstr = str2bstr(RSP_ERR_SERVER); + INCR(process_metrics, process_server_ex); + log_warn("command '%.*s' '%.*s' failed, unspecified server error", + cmd->bstr.len, cmd->bstr.data, key->len, key->data); +} + +/* helper functions for getting elements from the request */ +static inline bool +req_get_bstr(struct bstring **bstr, const struct request *req, uint32_t offset) +{ + ASSERT(array_nelem(req->token) > offset); + ASSERT(bstr != NULL); + + struct element *e = (struct element *)array_get(req->token, offset); + + if (e->type != ELEM_BULK) { + return false; + } + + *bstr = &e->bstr; + return true; +} + +static inline bool +req_get_int(int64_t *i, const struct request *req, uint32_t offset) +{ + struct bstring *bstr; + + if (!req_get_bstr(&bstr, req, offset)) { + return false; + } + + if (bstring_atoi64(i, bstr) != CC_OK) { + return false; + } + + return true; +} diff --git a/src/storage/slab/item.h b/src/storage/slab/item.h index 1f411c99d..54d69e7ef 100644 --- a/src/storage/slab/item.h +++ b/src/storage/slab/item.h @@ -43,6 +43,7 @@ * * item->end is followed by: * - 8-byte cas, if ITEM_CAS flag is set + * - other optional metadata * - key * - data */ @@ -138,6 +139,12 @@ item_nval(const struct item *it) return it->vlen; } +static inline size_t +item_npayload(struct item *it) +{ + return item_cas_size() + it->olen + it->klen + it->vlen; +} + static inline size_t item_ntotal(uint8_t klen, uint32_t vlen, uint8_t olen) { diff --git a/test/data_structure/CMakeLists.txt b/test/data_structure/CMakeLists.txt index ab90a5bae..e679cc3d0 100644 --- a/test/data_structure/CMakeLists.txt +++ b/test/data_structure/CMakeLists.txt @@ -1,3 +1,3 @@ add_subdirectory(bitmap) -add_subdirectory(intarray) +add_subdirectory(sarray) add_subdirectory(ziplist) diff --git a/test/data_structure/intarray/check_intarray.c b/test/data_structure/intarray/check_intarray.c deleted file mode 100644 index 3229114c0..000000000 --- a/test/data_structure/intarray/check_intarray.c +++ /dev/null @@ -1,156 +0,0 @@ -#include - -#include -#include -#include - -#include - -/* define for each suite, local scope due to macro visibility rule */ -#define SUITE_NAME "intarray" -#define DEBUG_LOG SUITE_NAME ".log" - -#define BUF_SIZE 8200 -#define NENTRY 1024 - -static unsigned char buf[BUF_SIZE]; - - -/* - * intarray tests - */ - -START_TEST(test_intarray_create) -{ - ck_assert_int_eq(intarray_init(buf, 1), INTARRAY_OK); - ck_assert_int_eq(intarray_init(buf, 2), INTARRAY_OK); - ck_assert_int_eq(intarray_nentry(buf), 0); - ck_assert_int_eq(intarray_init(buf, 4), INTARRAY_OK); - ck_assert_int_eq(intarray_nentry(buf), 0); - ck_assert_int_eq(intarray_init(buf, 8), INTARRAY_OK); - ck_assert_int_eq(intarray_init(buf, 16), INTARRAY_EINVALID); - ck_assert_int_eq(intarray_init(buf, 3), INTARRAY_EINVALID); -} -END_TEST - - -START_TEST(test_intarray_insert_seek) -{ - uint32_t idx; - uint64_t val; - - ck_assert_int_eq(intarray_init(buf, 1), INTARRAY_OK); - ck_assert_int_eq(intarray_insert(buf, 1), INTARRAY_OK); - ck_assert_int_eq(intarray_insert(buf, 3), INTARRAY_OK); - ck_assert_int_eq(intarray_insert(buf, 5), INTARRAY_OK); - ck_assert_int_eq(intarray_insert(buf, 12345), INTARRAY_EINVALID); - ck_assert_int_eq(intarray_nentry(buf), 3); - ck_assert_int_eq(intarray_value(&val, buf, 1), INTARRAY_OK); - ck_assert_int_eq(val, 3); - ck_assert_int_eq(intarray_index(&idx, buf, 3), INTARRAY_OK); - ck_assert_int_eq(idx, 1); - ck_assert_int_eq(intarray_index(&idx, buf, 2), INTARRAY_ENOTFOUND); - - ck_assert_int_eq(intarray_init(buf, 8), INTARRAY_OK); - for (int i = 0; i < 50; ++i) { /* 0, 2, 4, ..., 98 */ - ck_assert_int_eq(intarray_insert(buf, 1000 + i * 2), INTARRAY_OK); - } - ck_assert_int_eq(intarray_nentry(buf), 50); - ck_assert_int_eq(intarray_value(&val, buf, 10), INTARRAY_OK); - ck_assert_int_eq(val, 1020); - ck_assert_int_eq(intarray_index(&idx, buf, 1020), INTARRAY_OK); - ck_assert_int_eq(idx, 10); - ck_assert_int_eq(intarray_index(&idx, buf, 1000), INTARRAY_OK); - ck_assert_int_eq(idx, 0); - ck_assert_int_eq(intarray_index(&idx, buf, 1098), INTARRAY_OK); - ck_assert_int_eq(idx, 49); - ck_assert_int_eq(intarray_index(&idx, buf, 1), INTARRAY_ENOTFOUND); - ck_assert_int_eq(intarray_index(&idx, buf, 2000), INTARRAY_ENOTFOUND); -} -END_TEST - -START_TEST(test_intarray_remove) -{ - uint32_t idx; - - intarray_init(buf, 1); - intarray_insert(buf, 1); - intarray_insert(buf, 3); - intarray_insert(buf, 5); - ck_assert_int_eq(intarray_remove(buf, 12345), INTARRAY_EINVALID); - - intarray_init(buf, 8); - for (int i = 0; i < 50; ++i) { /* 0, 2, 4, ..., 98 */ - intarray_insert(buf, 1000 + i * 2); - } - ck_assert_int_eq(intarray_nentry(buf), 50); - ck_assert_int_eq(intarray_remove(buf, 1020), INTARRAY_OK); - ck_assert_int_eq(intarray_nentry(buf), 49); - ck_assert_int_eq(intarray_index(&idx, buf, 1020), INTARRAY_ENOTFOUND); - ck_assert_int_eq(intarray_index(&idx, buf, 1022), INTARRAY_OK); - ck_assert_int_eq(idx, 10); - ck_assert_int_eq(intarray_remove(buf, 1000), INTARRAY_OK); - ck_assert_int_eq(intarray_nentry(buf), 48); - ck_assert_int_eq(intarray_index(&idx, buf, 1000), INTARRAY_ENOTFOUND); - ck_assert_int_eq(intarray_remove(buf, 1098), INTARRAY_OK); - ck_assert_int_eq(intarray_nentry(buf), 47); - ck_assert_int_eq(intarray_index(&idx, buf, 1098), INTARRAY_ENOTFOUND); -} -END_TEST - -START_TEST(test_intarray_truncate) -{ - uint32_t idx; - - intarray_init(buf, 8); - for (int i = 0; i < 50; ++i) { /* 0, 2, 4, ..., 98 */ - intarray_insert(buf, 1000 + i * 2); - } - ck_assert_int_eq(intarray_nentry(buf), 50); - ck_assert_int_eq(intarray_truncate(buf, -10), INTARRAY_OK); - ck_assert_int_eq(intarray_nentry(buf), 40); - ck_assert_int_eq(intarray_index(&idx, buf, 1080), INTARRAY_ENOTFOUND); - ck_assert_int_eq(intarray_index(&idx, buf, 1078), INTARRAY_OK); - ck_assert_int_eq(intarray_truncate(buf, 10), INTARRAY_OK); - ck_assert_int_eq(intarray_nentry(buf), 30); - ck_assert_int_eq(intarray_index(&idx, buf, 1018), INTARRAY_ENOTFOUND); - ck_assert_int_eq(intarray_index(&idx, buf, 1020), INTARRAY_OK); - ck_assert_int_eq(intarray_truncate(buf, 31), INTARRAY_OK); - ck_assert_int_eq(intarray_nentry(buf), 0); -} -END_TEST - - -/* - * test suite - */ -static Suite * -intarray_suite(void) -{ - Suite *s = suite_create(SUITE_NAME); - - TCase *tc_intarray = tcase_create("intarray"); - suite_add_tcase(s, tc_intarray); - - tcase_add_test(tc_intarray, test_intarray_create); - tcase_add_test(tc_intarray, test_intarray_insert_seek); - tcase_add_test(tc_intarray, test_intarray_remove); - tcase_add_test(tc_intarray, test_intarray_truncate); - - return s; -} - -int -main(void) -{ - int nfail; - - Suite *suite = intarray_suite(); - SRunner *srunner = srunner_create(suite); - srunner_set_log(srunner, DEBUG_LOG); - srunner_run_all(srunner, CK_ENV); /* set CK_VEBOSITY in ENV to customize */ - nfail = srunner_ntests_failed(srunner); - srunner_free(srunner); - - return (nfail == 0) ? EXIT_SUCCESS : EXIT_FAILURE; -} diff --git a/test/data_structure/intarray/CMakeLists.txt b/test/data_structure/sarray/CMakeLists.txt similarity index 92% rename from test/data_structure/intarray/CMakeLists.txt rename to test/data_structure/sarray/CMakeLists.txt index f67cc8045..a8bca6f51 100644 --- a/test/data_structure/intarray/CMakeLists.txt +++ b/test/data_structure/sarray/CMakeLists.txt @@ -1,4 +1,4 @@ -set(suite intarray) +set(suite sarray) set(test_name check_${suite}) set(source check_${suite}.c) diff --git a/test/data_structure/sarray/check_sarray.c b/test/data_structure/sarray/check_sarray.c new file mode 100644 index 000000000..06dd0b5e9 --- /dev/null +++ b/test/data_structure/sarray/check_sarray.c @@ -0,0 +1,158 @@ +#include + +#include +#include +#include + +#include + +/* define for each suite, local scope due to macro visibility rule */ +#define SUITE_NAME "intarray" +#define DEBUG_LOG SUITE_NAME ".log" + +#define BUF_SIZE 8200 +#define NENTRY 1024 + +static unsigned char buf[BUF_SIZE]; + + +/* + * intarray tests + */ + +START_TEST(test_sarray_create) +{ + ck_assert_int_eq(sarray_init(buf, 1), SARRAY_OK); + ck_assert_int_eq(sarray_init(buf, 2), SARRAY_OK); + ck_assert_int_eq(sarray_nentry(buf), 0); + ck_assert_int_eq(sarray_init(buf, 4), SARRAY_OK); + ck_assert_int_eq(sarray_nentry(buf), 0); + ck_assert_int_eq(sarray_init(buf, 8), SARRAY_OK); + ck_assert_int_eq(sarray_init(buf, 16), SARRAY_EINVALID); + ck_assert_int_eq(sarray_init(buf, 3), SARRAY_EINVALID); +} +END_TEST + + +START_TEST(test_sarray_insert_seek) +{ + uint32_t idx; + uint64_t val; + + ck_assert_int_eq(sarray_init(buf, 1), SARRAY_OK); + ck_assert_int_eq(sarray_insert(buf, 3), SARRAY_OK); /* [3] */ + ck_assert_int_eq(sarray_insert(buf, 1), SARRAY_OK); /* [1, 3] */ + ck_assert_int_eq(sarray_insert(buf, 5), SARRAY_OK); /* [1, 3, 5] */ + ck_assert_int_eq(sarray_insert(buf, 12345), SARRAY_EINVALID); + ck_assert_int_eq(sarray_nentry(buf), 3); + ck_assert_int_eq(sarray_value(&val, buf, 1), SARRAY_OK); + ck_assert_int_eq(val, 3); + ck_assert_int_eq(sarray_index(&idx, buf, 3), SARRAY_OK); + ck_assert_int_eq(idx, 1); + ck_assert_int_eq(sarray_index(&idx, buf, 2), SARRAY_ENOTFOUND); + + ck_assert_int_eq(sarray_init(buf, 8), SARRAY_OK); + for (int i = 49; i >= 0; --i) { + ck_assert_int_eq(sarray_insert(buf, 1000 + i * 2), SARRAY_OK); + } + ck_assert_int_eq(sarray_nentry(buf), 50); + ck_assert_int_eq(sarray_value(&val, buf, 0), SARRAY_OK); + ck_assert_int_eq(val, 1000); + ck_assert_int_eq(sarray_value(&val, buf, 10), SARRAY_OK); + ck_assert_int_eq(val, 1020); + ck_assert_int_eq(sarray_index(&idx, buf, 1020), SARRAY_OK); + ck_assert_int_eq(idx, 10); + ck_assert_int_eq(sarray_index(&idx, buf, 1000), SARRAY_OK); + ck_assert_int_eq(idx, 0); + ck_assert_int_eq(sarray_index(&idx, buf, 1098), SARRAY_OK); + ck_assert_int_eq(idx, 49); + ck_assert_int_eq(sarray_index(&idx, buf, 1), SARRAY_ENOTFOUND); + ck_assert_int_eq(sarray_index(&idx, buf, 2000), SARRAY_ENOTFOUND); +} +END_TEST + +START_TEST(test_sarray_remove) +{ + uint32_t idx; + + sarray_init(buf, 1); + sarray_insert(buf, 1); + sarray_insert(buf, 3); + sarray_insert(buf, 5); + ck_assert_int_eq(sarray_remove(buf, 12345), SARRAY_EINVALID); + + sarray_init(buf, 8); + for (int i = 0; i < 50; ++i) { /* 0, 2, 4, ..., 98 */ + sarray_insert(buf, 1000 + i * 2); + } + ck_assert_int_eq(sarray_nentry(buf), 50); + ck_assert_int_eq(sarray_remove(buf, 1020), SARRAY_OK); + ck_assert_int_eq(sarray_nentry(buf), 49); + ck_assert_int_eq(sarray_index(&idx, buf, 1020), SARRAY_ENOTFOUND); + ck_assert_int_eq(sarray_index(&idx, buf, 1022), SARRAY_OK); + ck_assert_int_eq(idx, 10); + ck_assert_int_eq(sarray_remove(buf, 1000), SARRAY_OK); + ck_assert_int_eq(sarray_nentry(buf), 48); + ck_assert_int_eq(sarray_index(&idx, buf, 1000), SARRAY_ENOTFOUND); + ck_assert_int_eq(sarray_remove(buf, 1098), SARRAY_OK); + ck_assert_int_eq(sarray_nentry(buf), 47); + ck_assert_int_eq(sarray_index(&idx, buf, 1098), SARRAY_ENOTFOUND); +} +END_TEST + +START_TEST(test_sarray_truncate) +{ + uint32_t idx; + + sarray_init(buf, 8); + for (int i = 0; i < 50; ++i) { /* 0, 2, 4, ..., 98 */ + sarray_insert(buf, 1000 + i * 2); + } + ck_assert_int_eq(sarray_nentry(buf), 50); + ck_assert_int_eq(sarray_truncate(buf, -10), SARRAY_OK); + ck_assert_int_eq(sarray_nentry(buf), 40); + ck_assert_int_eq(sarray_index(&idx, buf, 1080), SARRAY_ENOTFOUND); + ck_assert_int_eq(sarray_index(&idx, buf, 1078), SARRAY_OK); + ck_assert_int_eq(sarray_truncate(buf, 10), SARRAY_OK); + ck_assert_int_eq(sarray_nentry(buf), 30); + ck_assert_int_eq(sarray_index(&idx, buf, 1018), SARRAY_ENOTFOUND); + ck_assert_int_eq(sarray_index(&idx, buf, 1020), SARRAY_OK); + ck_assert_int_eq(sarray_truncate(buf, 31), SARRAY_OK); + ck_assert_int_eq(sarray_nentry(buf), 0); +} +END_TEST + + +/* + * test suite + */ +static Suite * +sarray_suite(void) +{ + Suite *s = suite_create(SUITE_NAME); + + TCase *tc_intarray = tcase_create("intarray"); + suite_add_tcase(s, tc_intarray); + + tcase_add_test(tc_intarray, test_sarray_create); + tcase_add_test(tc_intarray, test_sarray_insert_seek); + tcase_add_test(tc_intarray, test_sarray_remove); + tcase_add_test(tc_intarray, test_sarray_truncate); + + return s; +} + +int +main(void) +{ + int nfail; + + Suite *suite = sarray_suite(); + SRunner *srunner = srunner_create(suite); + srunner_set_log(srunner, DEBUG_LOG); + srunner_run_all(srunner, CK_ENV); /* set CK_VEBOSITY in ENV to customize */ + nfail = srunner_ntests_failed(srunner); + srunner_free(srunner); + + return (nfail == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +}