diff options
Diffstat (limited to 'test/core/statistics')
-rw-r--r-- | test/core/statistics/census_stub_test.c | 75 | ||||
-rw-r--r-- | test/core/statistics/hash_table_test.c | 288 | ||||
-rw-r--r-- | test/core/statistics/log_tests.c | 568 | ||||
-rw-r--r-- | test/core/statistics/log_tests.h | 50 | ||||
-rw-r--r-- | test/core/statistics/multiple_writers_circular_buffer_test.c | 46 | ||||
-rw-r--r-- | test/core/statistics/multiple_writers_test.c | 46 | ||||
-rw-r--r-- | test/core/statistics/performance_test.c | 46 | ||||
-rw-r--r-- | test/core/statistics/quick_test.c | 54 | ||||
-rw-r--r-- | test/core/statistics/window_stats_test.c | 317 |
9 files changed, 1490 insertions, 0 deletions
diff --git a/test/core/statistics/census_stub_test.c b/test/core/statistics/census_stub_test.c new file mode 100644 index 0000000000..7d85550f70 --- /dev/null +++ b/test/core/statistics/census_stub_test.c @@ -0,0 +1,75 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <stdio.h> +#include <stdlib.h> + +#include "src/core/statistics/census_interface.h" +#include "src/core/statistics/census_rpc_stats.h" +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include "test/core/util/test_config.h" + +/* Tests census noop stubs in a simulated rpc flow */ +void test_census_stubs() { + census_op_id op_id; + census_rpc_stats* stats = census_rpc_stats_create_empty(); + census_aggregated_rpc_stats data_map; + /* Initializes census library at server start up time. */ + census_init(); + /* Starts tracing at the beginning of a rpc. */ + op_id = census_tracing_start_op(); + /* Appends custom annotations on a trace object. */ + census_tracing_print(op_id, "annotation foo"); + census_tracing_print(op_id, "annotation bar"); + /* Appends method tag on the trace object. */ + census_add_method_tag(op_id, "service_foo/method.bar"); + /* Either record client side stats or server side stats associated with the + op_id. Here for testing purpose, we record both. */ + census_record_rpc_client_stats(op_id, stats); + census_record_rpc_server_stats(op_id, stats); + /* Ends a tracing. */ + census_tracing_end_op(op_id); + /* In process stats queries. */ + census_get_server_stats(&data_map); + census_get_client_stats(&data_map); + census_aggregated_rpc_stats_destroy(&data_map); + gpr_free(stats); + census_shutdown(); +} + +int main(int argc, char** argv) { + grpc_test_init(argc, argv); + test_census_stubs(); + return 0; +} diff --git a/test/core/statistics/hash_table_test.c b/test/core/statistics/hash_table_test.c new file mode 100644 index 0000000000..fb75de520e --- /dev/null +++ b/test/core/statistics/hash_table_test.c @@ -0,0 +1,288 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "src/core/statistics/hash_table.h" + +#include "src/core/support/murmur_hash.h" +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/time.h> +#include "test/core/util/test_config.h" + +static gpr_uint64 hash64(const void* k) { + size_t len = strlen(k); + gpr_uint64 higher = gpr_murmur_hash3((const char*)k, len / 2, 0); + return higher << 32 | + gpr_murmur_hash3((const char*)(k) + len / 2, len - len / 2, 0); +} + +static int cmp_str_keys(const void* k1, const void* k2) { + return strcmp((const char*)k1, (const char*)k2); +} + +static gpr_uint64 force_collision(const void* k) { + return (1997 + hash64(k) % 3); +} + +static void free_data(void* data) { gpr_free(data); } + +/* Basic tests that empty hash table can be created and destroyed. */ +static void test_create_table() { + /* Create table with uint64 key type */ + census_ht* ht = NULL; + census_ht_option ht_options = {CENSUS_HT_UINT64, 1999, NULL, NULL, NULL, + NULL}; + ht = census_ht_create(&ht_options); + GPR_ASSERT(ht != NULL); + GPR_ASSERT(census_ht_get_size(ht) == 0); + census_ht_destroy(ht); + /* Create table with pointer key type */ + ht = NULL; + ht_options.key_type = CENSUS_HT_POINTER; + ht_options.hash = &hash64; + ht_options.compare_keys = &cmp_str_keys; + ht = census_ht_create(&ht_options); + GPR_ASSERT(ht != NULL); + GPR_ASSERT(census_ht_get_size(ht) == 0); + census_ht_destroy(ht); +} + +static void test_table_with_int_key() { + census_ht_option opt = {CENSUS_HT_UINT64, 7, NULL, NULL, NULL, NULL}; + census_ht* ht = census_ht_create(&opt); + gpr_uint64 i = 0; + gpr_uint64 sum_of_keys = 0; + size_t num_elements; + census_ht_kv* elements = NULL; + GPR_ASSERT(ht != NULL); + GPR_ASSERT(census_ht_get_size(ht) == 0); + elements = census_ht_get_all_elements(ht, &num_elements); + GPR_ASSERT(num_elements == 0); + GPR_ASSERT(elements == NULL); + for (i = 0; i < 20; ++i) { + census_ht_key key; + key.val = i; + census_ht_insert(ht, key, (void*)i); + GPR_ASSERT(census_ht_get_size(ht) == i + 1); + } + for (i = 0; i < 20; i++) { + gpr_uint64* val = NULL; + census_ht_key key; + key.val = i; + val = census_ht_find(ht, key); + GPR_ASSERT(val == (void*)i); + } + elements = census_ht_get_all_elements(ht, &num_elements); + GPR_ASSERT(elements != NULL); + GPR_ASSERT(num_elements == 20); + for (i = 0; i < num_elements; i++) { + sum_of_keys += elements[i].k.val; + } + GPR_ASSERT(sum_of_keys == 190); + gpr_free(elements); + census_ht_destroy(ht); +} + +/* Test that there is no memory leak when keys and values are owned by table. */ +static void test_value_and_key_deleter() { + census_ht_option opt = {CENSUS_HT_POINTER, 7, &hash64, &cmp_str_keys, + &free_data, &free_data}; + census_ht* ht = census_ht_create(&opt); + census_ht_key key; + char* val; + key.ptr = gpr_malloc(100); + val = gpr_malloc(10); + strcpy(val, "value"); + strcpy(key.ptr, "some string as a key"); + GPR_ASSERT(ht != NULL); + GPR_ASSERT(census_ht_get_size(ht) == 0); + census_ht_insert(ht, key, val); + GPR_ASSERT(census_ht_get_size(ht) == 1); + census_ht_destroy(ht); +} + +/* Test simple insert and erase operations. */ +static void test_simple_add_and_erase() { + census_ht_option opt = {CENSUS_HT_UINT64, 7, NULL, NULL, NULL, NULL}; + census_ht* ht = census_ht_create(&opt); + GPR_ASSERT(ht != NULL); + GPR_ASSERT(census_ht_get_size(ht) == 0); + { + census_ht_key key; + int val = 3; + key.val = 2; + census_ht_insert(ht, key, (void*)&val); + GPR_ASSERT(census_ht_get_size(ht) == 1); + census_ht_erase(ht, key); + GPR_ASSERT(census_ht_get_size(ht) == 0); + /* Erasing a key from an empty table should be noop. */ + census_ht_erase(ht, key); + GPR_ASSERT(census_ht_get_size(ht) == 0); + /* Erasing a non-existant key from a table should be noop. */ + census_ht_insert(ht, key, (void*)&val); + key.val = 3; + census_ht_insert(ht, key, (void*)&val); + key.val = 9; + census_ht_insert(ht, key, (void*)&val); + GPR_ASSERT(census_ht_get_size(ht) == 3); + key.val = 1; + census_ht_erase(ht, key); + /* size unchanged after deleting non-existant key. */ + GPR_ASSERT(census_ht_get_size(ht) == 3); + /* size decrease by 1 after deleting an existant key. */ + key.val = 2; + census_ht_erase(ht, key); + GPR_ASSERT(census_ht_get_size(ht) == 2); + } + census_ht_destroy(ht); +} + +static void test_insertion_and_deletion_with_high_collision_rate() { + census_ht_option opt = {CENSUS_HT_POINTER, 13, &force_collision, + &cmp_str_keys, NULL, NULL}; + census_ht* ht = census_ht_create(&opt); + char key_str[1000][10]; + gpr_uint64 val = 0; + int i = 0; + for (i = 0; i < 1000; i++) { + census_ht_key key; + key.ptr = key_str[i]; + sprintf(key_str[i], "%d", i); + census_ht_insert(ht, key, (void*)(&val)); + printf("%d\n", i); + GPR_ASSERT(census_ht_get_size(ht) == (i + 1)); + } + for (i = 0; i < 1000; i++) { + census_ht_key key; + key.ptr = key_str[i]; + census_ht_erase(ht, key); + GPR_ASSERT(census_ht_get_size(ht) == (999 - i)); + } + census_ht_destroy(ht); +} + +static void test_table_with_string_key() { + census_ht_option opt = {CENSUS_HT_POINTER, 7, &hash64, &cmp_str_keys, NULL, + NULL}; + census_ht* ht = census_ht_create(&opt); + const char* keys[] = {"k1", "a", "000", "apple", + "banana_a_long_long_long_banana", "%$", "111", "foo", + "b"}; + const int vals[] = {0, 1, 2, 3, 4, 5, 6, 7, 8}; + int i = 0; + GPR_ASSERT(ht != NULL); + GPR_ASSERT(census_ht_get_size(ht) == 0); + for (i = 0; i < 9; i++) { + census_ht_key key; + key.ptr = (void*)(keys[i]); + census_ht_insert(ht, key, (void*)(vals + i)); + } + GPR_ASSERT(census_ht_get_size(ht) == 9); + for (i = 0; i < 9; i++) { + census_ht_key key; + int* val_ptr; + key.ptr = (void*)(keys[i]); + val_ptr = census_ht_find(ht, key); + GPR_ASSERT(*val_ptr == vals[i]); + } + { + /* inserts duplicate keys */ + census_ht_key key; + int* val_ptr = NULL; + key.ptr = (void*)(keys[2]); + census_ht_insert(ht, key, (void*)(vals + 8)); + /* expect value to be over written by new insertion */ + GPR_ASSERT(census_ht_get_size(ht) == 9); + val_ptr = census_ht_find(ht, key); + GPR_ASSERT(*val_ptr == vals[8]); + } + for (i = 0; i < 9; i++) { + census_ht_key key; + int* val_ptr; + gpr_uint32 expected_tbl_sz = 9 - i; + GPR_ASSERT(census_ht_get_size(ht) == expected_tbl_sz); + key.ptr = (void*)(keys[i]); + val_ptr = census_ht_find(ht, key); + GPR_ASSERT(val_ptr != NULL); + census_ht_erase(ht, key); + GPR_ASSERT(census_ht_get_size(ht) == expected_tbl_sz - 1); + val_ptr = census_ht_find(ht, key); + GPR_ASSERT(val_ptr == NULL); + } + census_ht_destroy(ht); +} + +static void test_insertion_with_same_key() { + census_ht_option opt = {CENSUS_HT_UINT64, 11, NULL, NULL, NULL, NULL}; + census_ht* ht = census_ht_create(&opt); + census_ht_key key; + const char vals[] = {'a', 'b', 'c'}; + char* val_ptr; + key.val = 3; + census_ht_insert(ht, key, (void*)&(vals[0])); + GPR_ASSERT(census_ht_get_size(ht) == 1); + val_ptr = (char*)census_ht_find(ht, key); + GPR_ASSERT(val_ptr != NULL); + GPR_ASSERT(*val_ptr == 'a'); + key.val = 4; + val_ptr = (char*)census_ht_find(ht, key); + GPR_ASSERT(val_ptr == NULL); + key.val = 3; + census_ht_insert(ht, key, (void*)&(vals[1])); + GPR_ASSERT(census_ht_get_size(ht) == 1); + val_ptr = (char*)census_ht_find(ht, key); + GPR_ASSERT(val_ptr != NULL); + GPR_ASSERT(*val_ptr == 'b'); + census_ht_insert(ht, key, (void*)&(vals[2])); + GPR_ASSERT(census_ht_get_size(ht) == 1); + val_ptr = (char*)census_ht_find(ht, key); + GPR_ASSERT(val_ptr != NULL); + GPR_ASSERT(*val_ptr == 'c'); + census_ht_destroy(ht); +} + +int main(int argc, char** argv) { + grpc_test_init(argc, argv); + test_create_table(); + test_simple_add_and_erase(); + test_table_with_int_key(); + test_table_with_string_key(); + test_value_and_key_deleter(); + test_insertion_with_same_key(); + test_insertion_and_deletion_with_high_collision_rate(); + return 0; +} diff --git a/test/core/statistics/log_tests.c b/test/core/statistics/log_tests.c new file mode 100644 index 0000000000..f0cbdbdf70 --- /dev/null +++ b/test/core/statistics/log_tests.c @@ -0,0 +1,568 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/statistics/log.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "src/core/support/cpu.h" +#include <grpc/support/log.h> +#include <grpc/support/port_platform.h> +#include <grpc/support/sync.h> +#include <grpc/support/thd.h> +#include <grpc/support/time.h> + +/* Fills in 'record' of size 'size'. Each byte in record is filled in with the + same value. The value is extracted from 'record' pointer. */ +static void write_record(char* record, size_t size) { + char data = (gpr_uintptr)record % 255; + memset(record, data, size); +} + +/* Reads fixed size records. Returns the number of records read in + 'num_records'. */ +static void read_records(size_t record_size, const char* buffer, + size_t buffer_size, gpr_int32* num_records) { + gpr_int32 ix; + GPR_ASSERT(buffer_size >= record_size); + GPR_ASSERT(buffer_size % record_size == 0); + *num_records = buffer_size / record_size; + for (ix = 0; ix < *num_records; ++ix) { + gpr_int32 jx; + const char* record = buffer + (record_size * ix); + char data = (gpr_uintptr)record % 255; + for (jx = 0; jx < record_size; ++jx) { + GPR_ASSERT(data == record[jx]); + } + } +} + +/* Tries to write the specified number of records. Stops when the log gets + full. Returns the number of records written. Spins for random + number of times, up to 'max_spin_count', between writes. */ +static size_t write_records_to_log(int writer_id, gpr_int32 record_size, + gpr_int32 num_records, + gpr_int32 max_spin_count) { + gpr_int32 ix; + int counter = 0; + for (ix = 0; ix < num_records; ++ix) { + gpr_int32 jx; + gpr_int32 spin_count = max_spin_count ? rand() % max_spin_count : 0; + char* record; + if (counter++ == num_records / 10) { + printf(" Writer %d: %d out of %d written\n", writer_id, ix, + num_records); + counter = 0; + } + record = (char*)(census_log_start_write(record_size)); + if (record == NULL) { + return ix; + } + write_record(record, record_size); + census_log_end_write(record, record_size); + for (jx = 0; jx < spin_count; ++jx) { + GPR_ASSERT(jx >= 0); + } + } + return num_records; +} + +/* Performs a single read iteration. Returns the number of records read. */ +static size_t perform_read_iteration(size_t record_size) { + const void* read_buffer = NULL; + size_t bytes_available; + size_t records_read = 0; + census_log_init_reader(); + while ((read_buffer = census_log_read_next(&bytes_available))) { + gpr_int32 num_records = 0; + read_records(record_size, (const char*)read_buffer, bytes_available, + &num_records); + records_read += num_records; + } + return records_read; +} + +/* Asserts that the log is empty. */ +static void assert_log_empty() { + size_t bytes_available; + census_log_init_reader(); + GPR_ASSERT(census_log_read_next(&bytes_available) == NULL); +} + +/* Given log size and record size, computes the minimum usable space. */ +static size_t min_usable_space(size_t log_size, size_t record_size) { + gpr_int32 num_blocks = log_size / CENSUS_LOG_MAX_RECORD_SIZE; + gpr_int32 waste_per_block = CENSUS_LOG_MAX_RECORD_SIZE % record_size; + /* In the worst case, all except one core-local block is empty. */ + return (log_size - ((gpr_cpu_num_cores() - 1) * CENSUS_LOG_MAX_RECORD_SIZE) - + ((num_blocks - gpr_cpu_num_cores() - 1) * waste_per_block)); +} + +/* Fills the log and verifies data. If 'no fragmentation' is true, records + are sized such that CENSUS_LOG_2_MAX_RECORD_SIZE is a multiple of record + size. If not a circular log, verifies that the number of records written + match the number of records read. */ +static void fill_log(size_t log_size, int no_fragmentation, int circular_log) { + int size; + gpr_int32 records_written; + gpr_int32 usable_space; + gpr_int32 records_read; + if (no_fragmentation) { + int log2size = rand() % (CENSUS_LOG_2_MAX_RECORD_SIZE + 1); + size = (1 << log2size); + } else { + while (1) { + size = 1 + (rand() % CENSUS_LOG_MAX_RECORD_SIZE); + if (CENSUS_LOG_MAX_RECORD_SIZE % size) { + break; + } + } + } + printf(" Fill record size: %d\n", size); + records_written = write_records_to_log( + 0 /* writer id */, size, (log_size / size) * 2, 0 /* spin count */); + usable_space = min_usable_space(log_size, size); + GPR_ASSERT(usable_space > 0); + GPR_ASSERT(records_written * size >= usable_space); + records_read = perform_read_iteration(size); + if (!circular_log) { + GPR_ASSERT(records_written == records_read); + } + assert_log_empty(); +} + +/* Structure to pass args to writer_thread */ +typedef struct writer_thread_args { + /* Index of this thread in the writers vector. */ + int index; + /* Record size. */ + size_t record_size; + /* Number of records to write. */ + gpr_int32 num_records; + /* Used to signal when writer is complete */ + gpr_cv* done; + gpr_mu* mu; + int* count; +} writer_thread_args; + +/* Writes the given number of records of random size (up to kMaxRecordSize) and + random data to the specified log. */ +static void writer_thread(void* arg) { + writer_thread_args* args = (writer_thread_args*)arg; + /* Maximum number of times to spin between writes. */ + static const gpr_int32 MAX_SPIN_COUNT = 50; + int records_written = 0; + printf(" Writer: %d\n", args->index); + while (records_written < args->num_records) { + records_written += write_records_to_log(args->index, args->record_size, + args->num_records - records_written, + MAX_SPIN_COUNT); + if (records_written < args->num_records) { + /* Ran out of log space. Sleep for a bit and let the reader catch up. + This should never happen for circular logs. */ + printf(" Writer stalled due to out-of-space: %d out of %d written\n", + records_written, args->num_records); + gpr_sleep_until(gpr_time_add(gpr_now(), gpr_time_from_micros(10000))); + } + } + /* Done. Decrement count and signal. */ + gpr_mu_lock(args->mu); + (*args->count)--; + gpr_cv_broadcast(args->done); + printf(" Writer done: %d\n", args->index); + gpr_mu_unlock(args->mu); +} + +/* struct to pass args to reader_thread */ +typedef struct reader_thread_args { + /* Record size. */ + size_t record_size; + /* Interval between read iterations. */ + gpr_int32 read_iteration_interval_in_msec; + /* Total number of records. */ + gpr_int32 total_records; + /* Signalled when reader should stop. */ + gpr_cv stop; + int stop_flag; + /* Used to signal when reader has finished */ + gpr_cv* done; + gpr_mu* mu; + int running; +} reader_thread_args; + +/* Reads and verifies the specified number of records. Reader can also be + stopped via gpr_cv_signal(&args->stop). Sleeps for 'read_interval_in_msec' + between read iterations. */ +static void reader_thread(void* arg) { + gpr_int32 records_read = 0; + reader_thread_args* args = (reader_thread_args*)arg; + gpr_int32 num_iterations = 0; + gpr_timespec interval; + int counter = 0; + printf(" Reader starting\n"); + interval = gpr_time_from_micros(args->read_iteration_interval_in_msec * 1000); + gpr_mu_lock(args->mu); + while (!args->stop_flag && records_read < args->total_records) { + gpr_cv_wait(&args->stop, args->mu, interval); + if (!args->stop_flag) { + records_read += perform_read_iteration(args->record_size); + GPR_ASSERT(records_read <= args->total_records); + if (counter++ == 100000) { + printf(" Reader: %d out of %d read\n", records_read, + args->total_records); + counter = 0; + } + ++num_iterations; + } + } + /* Done */ + args->running = 0; + gpr_cv_broadcast(args->done); + printf(" Reader: records: %d, iterations: %d\n", records_read, + num_iterations); + gpr_mu_unlock(args->mu); +} + +/* Creates NUM_WRITERS writers where each writer writes NUM_RECORDS_PER_WRITER + records. Also, starts a reader that iterates over and reads blocks every + READ_ITERATION_INTERVAL_IN_MSEC. */ +/* Number of writers. */ +#define NUM_WRITERS 5 +static void multiple_writers_single_reader(int circular_log) { + /* Sleep interval between read iterations. */ + static const gpr_int32 READ_ITERATION_INTERVAL_IN_MSEC = 10; + /* Number of records written by each writer. */ + static const gpr_int32 NUM_RECORDS_PER_WRITER = 10 * 1024 * 1024; + /* Maximum record size. */ + static const size_t MAX_RECORD_SIZE = 10; + int ix; + gpr_thd_id id; + gpr_cv writers_done; + int writers_count = NUM_WRITERS; + gpr_mu writers_mu; /* protects writers_done and writers_count */ + writer_thread_args writers[NUM_WRITERS]; + gpr_cv reader_done; + gpr_mu reader_mu; /* protects reader_done and reader.running */ + reader_thread_args reader; + gpr_int32 record_size = 1 + rand() % MAX_RECORD_SIZE; + printf(" Record size: %d\n", record_size); + /* Create and start writers. */ + gpr_cv_init(&writers_done); + gpr_mu_init(&writers_mu); + for (ix = 0; ix < NUM_WRITERS; ++ix) { + writers[ix].index = ix; + writers[ix].record_size = record_size; + writers[ix].num_records = NUM_RECORDS_PER_WRITER; + writers[ix].done = &writers_done; + writers[ix].count = &writers_count; + writers[ix].mu = &writers_mu; + gpr_thd_new(&id, &writer_thread, &writers[ix], NULL); + } + /* Start reader. */ + reader.record_size = record_size; + reader.read_iteration_interval_in_msec = READ_ITERATION_INTERVAL_IN_MSEC; + reader.total_records = NUM_WRITERS * NUM_RECORDS_PER_WRITER; + reader.stop_flag = 0; + gpr_cv_init(&reader.stop); + gpr_cv_init(&reader_done); + reader.done = &reader_done; + gpr_mu_init(&reader_mu); + reader.mu = &reader_mu; + reader.running = 1; + gpr_thd_new(&id, &reader_thread, &reader, NULL); + /* Wait for writers to finish. */ + gpr_mu_lock(&writers_mu); + while (writers_count != 0) { + gpr_cv_wait(&writers_done, &writers_mu, gpr_inf_future); + } + gpr_mu_unlock(&writers_mu); + gpr_mu_destroy(&writers_mu); + gpr_cv_destroy(&writers_done); + gpr_mu_lock(&reader_mu); + if (circular_log) { + /* Stop reader. */ + reader.stop_flag = 1; + gpr_cv_signal(&reader.stop); + } + /* wait for reader to finish */ + while (reader.running) { + gpr_cv_wait(&reader_done, &reader_mu, gpr_inf_future); + } + if (circular_log) { + /* Assert that there were no out-of-space errors. */ + GPR_ASSERT(0 == census_log_out_of_space_count()); + } + gpr_mu_unlock(&reader_mu); + gpr_mu_destroy(&reader_mu); + gpr_cv_destroy(&reader_done); + printf(" Reader: finished\n"); +} + +/* Log sizes to use for all tests. */ +#define LOG_SIZE_IN_MB 1 +#define LOG_SIZE_IN_BYTES (LOG_SIZE_IN_MB << 20) + +static void setup_test(int circular_log) { + census_log_initialize(LOG_SIZE_IN_MB, circular_log); + GPR_ASSERT(census_log_remaining_space() == LOG_SIZE_IN_BYTES); +} + +/* Attempts to create a record of invalid size (size > + CENSUS_LOG_MAX_RECORD_SIZE). */ +void test_invalid_record_size() { + static const size_t INVALID_SIZE = CENSUS_LOG_MAX_RECORD_SIZE + 1; + static const size_t VALID_SIZE = 1; + void* record; + printf("Starting test: invalid record size\n"); + setup_test(0); + record = census_log_start_write(INVALID_SIZE); + GPR_ASSERT(record == NULL); + /* Now try writing a valid record. */ + record = census_log_start_write(VALID_SIZE); + GPR_ASSERT(record != NULL); + census_log_end_write(record, VALID_SIZE); + /* Verifies that available space went down by one block. In theory, this + check can fail if the thread is context switched to a new CPU during the + start_write execution (multiple blocks get allocated), but this has not + been observed in practice. */ + GPR_ASSERT(LOG_SIZE_IN_BYTES - CENSUS_LOG_MAX_RECORD_SIZE == + census_log_remaining_space()); + census_log_shutdown(); +} + +/* Tests end_write() with a different size than what was specified in + start_write(). */ +void test_end_write_with_different_size() { + static const size_t START_WRITE_SIZE = 10; + static const size_t END_WRITE_SIZE = 7; + void* record_written; + const void* record_read; + size_t bytes_available; + printf("Starting test: end write with different size\n"); + setup_test(0); + record_written = census_log_start_write(START_WRITE_SIZE); + GPR_ASSERT(record_written != NULL); + census_log_end_write(record_written, END_WRITE_SIZE); + census_log_init_reader(); + record_read = census_log_read_next(&bytes_available); + GPR_ASSERT(record_written == record_read); + GPR_ASSERT(END_WRITE_SIZE == bytes_available); + assert_log_empty(); + census_log_shutdown(); +} + +/* Verifies that pending records are not available via read_next(). */ +void test_read_pending_record() { + static const size_t PR_RECORD_SIZE = 1024; + size_t bytes_available; + const void* record_read; + void* record_written; + printf("Starting test: read pending record\n"); + setup_test(0); + /* Start a write. */ + record_written = census_log_start_write(PR_RECORD_SIZE); + GPR_ASSERT(record_written != NULL); + /* As write is pending, read should fail. */ + census_log_init_reader(); + record_read = census_log_read_next(&bytes_available); + GPR_ASSERT(record_read == NULL); + /* A read followed by end_write() should succeed. */ + census_log_end_write(record_written, PR_RECORD_SIZE); + census_log_init_reader(); + record_read = census_log_read_next(&bytes_available); + GPR_ASSERT(record_written == record_read); + GPR_ASSERT(PR_RECORD_SIZE == bytes_available); + assert_log_empty(); + census_log_shutdown(); +} + +/* Tries reading beyond pending write. */ +void test_read_beyond_pending_record() { + /* Start a write. */ + gpr_int32 incomplete_record_size = 10; + gpr_int32 complete_record_size = 20; + size_t bytes_available; + void* complete_record; + const void* record_read; + void* incomplete_record; + printf("Starting test: read beyond pending record\n"); + setup_test(0); + incomplete_record = census_log_start_write(incomplete_record_size); + GPR_ASSERT(incomplete_record != NULL); + complete_record = census_log_start_write(complete_record_size); + GPR_ASSERT(complete_record != NULL); + GPR_ASSERT(complete_record != incomplete_record); + census_log_end_write(complete_record, complete_record_size); + /* Now iterate over blocks to read completed records. */ + census_log_init_reader(); + record_read = census_log_read_next(&bytes_available); + GPR_ASSERT(complete_record == record_read); + GPR_ASSERT(complete_record_size == bytes_available); + /* Complete first record. */ + census_log_end_write(incomplete_record, incomplete_record_size); + /* Have read past the incomplete record, so read_next() should return NULL. */ + /* NB: this test also assumes our thread did not get switched to a different + CPU between the two start_write calls */ + record_read = census_log_read_next(&bytes_available); + GPR_ASSERT(record_read == NULL); + /* Reset reader to get the newly completed record. */ + census_log_init_reader(); + record_read = census_log_read_next(&bytes_available); + GPR_ASSERT(incomplete_record == record_read); + GPR_ASSERT(incomplete_record_size == bytes_available); + assert_log_empty(); + census_log_shutdown(); +} + +/* Tests scenario where block being read is detached from a core and put on the + dirty list. */ +void test_detached_while_reading() { + static const size_t DWR_RECORD_SIZE = 10; + size_t bytes_available; + const void* record_read; + void* record_written; + gpr_int32 block_read = 0; + printf("Starting test: detached while reading\n"); + setup_test(0); + /* Start a write. */ + record_written = census_log_start_write(DWR_RECORD_SIZE); + GPR_ASSERT(record_written != NULL); + census_log_end_write(record_written, DWR_RECORD_SIZE); + /* Read this record. */ + census_log_init_reader(); + record_read = census_log_read_next(&bytes_available); + GPR_ASSERT(record_read != NULL); + GPR_ASSERT(DWR_RECORD_SIZE == bytes_available); + /* Now fill the log. This will move the block being read from core-local + array to the dirty list. */ + while ((record_written = census_log_start_write(DWR_RECORD_SIZE))) { + census_log_end_write(record_written, DWR_RECORD_SIZE); + } + + /* In this iteration, read_next() should only traverse blocks in the + core-local array. Therefore, we expect at most gpr_cpu_num_cores() more + blocks. As log is full, if read_next() is traversing the dirty list, we + will get more than gpr_cpu_num_cores() blocks. */ + while ((record_read = census_log_read_next(&bytes_available))) { + ++block_read; + GPR_ASSERT(block_read <= gpr_cpu_num_cores()); + } + census_log_shutdown(); +} + +/* Fills non-circular log with records sized such that size is a multiple of + CENSUS_LOG_MAX_RECORD_SIZE (no per-block fragmentation). */ +void test_fill_log_no_fragmentation() { + const int circular = 0; + printf("Starting test: fill log no fragmentation\n"); + setup_test(circular); + fill_log(LOG_SIZE_IN_BYTES, 1 /* no fragmentation */, circular); + census_log_shutdown(); +} + +/* Fills circular log with records sized such that size is a multiple of + CENSUS_LOG_MAX_RECORD_SIZE (no per-block fragmentation). */ +void test_fill_circular_log_no_fragmentation() { + const int circular = 1; + printf("Starting test: fill circular log no fragmentation\n"); + setup_test(circular); + fill_log(LOG_SIZE_IN_BYTES, 1 /* no fragmentation */, circular); + census_log_shutdown(); +} + +/* Fills non-circular log with records that may straddle end of a block. */ +void test_fill_log_with_straddling_records() { + const int circular = 0; + printf("Starting test: fill log with straddling records\n"); + setup_test(circular); + fill_log(LOG_SIZE_IN_BYTES, 0 /* block straddling records */, circular); + census_log_shutdown(); +} + +/* Fills circular log with records that may straddle end of a block. */ +void test_fill_circular_log_with_straddling_records() { + const int circular = 1; + printf("Starting test: fill circular log with straddling records\n"); + setup_test(circular); + fill_log(LOG_SIZE_IN_BYTES, 0 /* block straddling records */, circular); + census_log_shutdown(); +} + +/* Tests scenario where multiple writers and a single reader are using a log + that is configured to discard old records. */ +void test_multiple_writers_circular_log() { + const int circular = 1; + printf("Starting test: multiple writers circular log\n"); + setup_test(circular); + multiple_writers_single_reader(circular); + census_log_shutdown(); +} + +/* Tests scenario where multiple writers and a single reader are using a log + that is configured to discard old records. */ +void test_multiple_writers() { + const int circular = 0; + printf("Starting test: multiple writers\n"); + setup_test(circular); + multiple_writers_single_reader(circular); + census_log_shutdown(); +} + +void test_performance() { + int write_size = 1; + for (; write_size < CENSUS_LOG_MAX_RECORD_SIZE; write_size *= 2) { + gpr_timespec write_time; + gpr_timespec start_time; + double write_time_micro = 0.0; + int nrecords = 0; + setup_test(0); + start_time = gpr_now(); + while (1) { + void* record = census_log_start_write(write_size); + if (record == NULL) { + break; + } + census_log_end_write(record, write_size); + nrecords++; + } + write_time = gpr_time_sub(gpr_now(), start_time); + write_time_micro = write_time.tv_sec * 1000000 + write_time.tv_nsec / 1000; + census_log_shutdown(); + printf( + "Wrote %d %d byte records in %.3g microseconds: %g records/us " + "(%g ns/record), %g gigabytes/s\n", + nrecords, write_size, write_time_micro, nrecords / write_time_micro, + 1000 * write_time_micro / nrecords, + (write_size * nrecords) / write_time_micro / 1000); + } +} diff --git a/test/core/statistics/log_tests.h b/test/core/statistics/log_tests.h new file mode 100644 index 0000000000..10993ac76f --- /dev/null +++ b/test/core/statistics/log_tests.h @@ -0,0 +1,50 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef __GRPC_TEST_STATISTICS_LOG_TESTS_H__ +#define __GRPC_TEST_STATISTICS_LOG_TESTS_H__ + +void test_invalid_record_size(); +void test_end_write_with_different_size(); +void test_read_pending_record(); +void test_read_beyond_pending_record(); +void test_detached_while_reading(); +void test_fill_log_no_fragmentation(); +void test_fill_circular_log_no_fragmentation(); +void test_fill_log_with_straddling_records(); +void test_fill_circular_log_with_straddling_records(); +void test_multiple_writers_circular_log(); +void test_multiple_writers(); +void test_performance(); + +#endif /* __GRPC_TEST_STATISTICS_LOG_TESTS_H__ */ diff --git a/test/core/statistics/multiple_writers_circular_buffer_test.c b/test/core/statistics/multiple_writers_circular_buffer_test.c new file mode 100644 index 0000000000..0cd0d78df2 --- /dev/null +++ b/test/core/statistics/multiple_writers_circular_buffer_test.c @@ -0,0 +1,46 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "test/core/statistics/log_tests.h" + +#include <stdlib.h> + +#include <grpc/support/time.h> +#include "test/core/util/test_config.h" + +int main(int argc, char **argv) { + grpc_test_init(argc, argv); + srand(gpr_now().tv_nsec); + test_multiple_writers_circular_log(); + return 0; +} diff --git a/test/core/statistics/multiple_writers_test.c b/test/core/statistics/multiple_writers_test.c new file mode 100644 index 0000000000..b1f3be4eba --- /dev/null +++ b/test/core/statistics/multiple_writers_test.c @@ -0,0 +1,46 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "test/core/statistics/log_tests.h" + +#include <stdlib.h> + +#include <grpc/support/time.h> +#include "test/core/util/test_config.h" + +int main(int argc, char **argv) { + grpc_test_init(argc, argv); + srand(gpr_now().tv_nsec); + test_multiple_writers(); + return 0; +} diff --git a/test/core/statistics/performance_test.c b/test/core/statistics/performance_test.c new file mode 100644 index 0000000000..9197dd5c73 --- /dev/null +++ b/test/core/statistics/performance_test.c @@ -0,0 +1,46 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "log_tests.h" + +#include <stdlib.h> + +#include <grpc/support/time.h> +#include "test/core/util/test_config.h" + +int main(int argc, char **argv) { + grpc_test_init(argc, argv); + srand(gpr_now().tv_nsec); + test_performance(); + return 0; +} diff --git a/test/core/statistics/quick_test.c b/test/core/statistics/quick_test.c new file mode 100644 index 0000000000..fe2b89a9a4 --- /dev/null +++ b/test/core/statistics/quick_test.c @@ -0,0 +1,54 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "log_tests.h" + +#include <stdlib.h> + +#include <grpc/support/time.h> +#include "test/core/util/test_config.h" + +int main(int argc, char **argv) { + grpc_test_init(argc, argv); + srand(gpr_now().tv_nsec); + test_invalid_record_size(); + test_end_write_with_different_size(); + test_read_pending_record(); + test_read_beyond_pending_record(); + test_detached_while_reading(); + test_fill_log_no_fragmentation(); + test_fill_circular_log_no_fragmentation(); + test_fill_log_with_straddling_records(); + test_fill_circular_log_with_straddling_records(); + return 0; +} diff --git a/test/core/statistics/window_stats_test.c b/test/core/statistics/window_stats_test.c new file mode 100644 index 0000000000..2bf93d8c87 --- /dev/null +++ b/test/core/statistics/window_stats_test.c @@ -0,0 +1,317 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/statistics/window_stats.h" +#include <stdlib.h> +#include <grpc/support/log.h> +#include <grpc/support/time.h> +#include "test/core/util/test_config.h" + +typedef struct test_stat { + double value1; + int value2; +} test_stat; + +void add_test_stat(void* base, const void* addme) { + test_stat* b = (test_stat*)base; + const test_stat* a = (const test_stat*)addme; + b->value1 += a->value1; + b->value2 += a->value2; +} + +void add_proportion_test_stat(double p, void* base, const void* addme) { + test_stat* b = (test_stat*)base; + const test_stat* a = (const test_stat*)addme; + b->value1 += p * a->value1; + b->value2 += p * a->value2 + 0.5; /* +0.5 is poor mans (no c99) round() */ +} + +const struct census_window_stats_stat_info kMyStatInfo = { + sizeof(test_stat), NULL, add_test_stat, add_proportion_test_stat}; + +const gpr_timespec kMilliSecInterval = {0, 1000000}; +const gpr_timespec kSecInterval = {1, 0}; +const gpr_timespec kMinInterval = {60, 0}; +const gpr_timespec kHourInterval = {3600, 0}; +const gpr_timespec kPrimeInterval = {0, 101}; + +static int compare_double(double a, double b, double epsilon) { + if (a >= b) { + return (a > b + epsilon) ? 1 : 0; + } else { + return (b > a + epsilon) ? -1 : 0; + } +} + +void empty_test() { + census_window_stats_sums result; + const gpr_timespec zero = {0, 0}; + test_stat sum; + struct census_window_stats* stats = + census_window_stats_create(1, &kMinInterval, 5, &kMyStatInfo); + GPR_ASSERT(stats != NULL); + result.statistic = ∑ + census_window_stats_get_sums(stats, zero, &result); + GPR_ASSERT(result.count == 0 && sum.value1 == 0 && sum.value2 == 0); + census_window_stats_get_sums(stats, gpr_now(), &result); + GPR_ASSERT(result.count == 0 && sum.value1 == 0 && sum.value2 == 0); + census_window_stats_destroy(stats); +} + +void one_interval_test() { + const test_stat value = {0.1, 4}; + const double epsilon = 1e10 - 11; + gpr_timespec when = {0, 0}; + census_window_stats_sums result; + test_stat sum; + /* granularity == 5 so width of internal windows should be 12s */ + struct census_window_stats* stats = + census_window_stats_create(1, &kMinInterval, 5, &kMyStatInfo); + GPR_ASSERT(stats != NULL); + /* phase 1: insert a single value at t=0s, and check that various measurement + times result in expected output values */ + census_window_stats_add(stats, when, &value); + result.statistic = ∑ + /* when = 0s, values extracted should be everything */ + census_window_stats_get_sums(stats, when, &result); + GPR_ASSERT(compare_double(result.count, 1, epsilon) == 0 && + compare_double(sum.value1, value.value1, epsilon) == 0 && + sum.value2 == value.value2); + /* when = 6,30,60s, should be all of the data */ + when.tv_sec = 6; + census_window_stats_get_sums(stats, when, &result); + GPR_ASSERT(compare_double(result.count, 1.0, epsilon) == 0 && + compare_double(sum.value1, value.value1, epsilon) == 0 && + sum.value2 == value.value2); + /* when == 30s,60s, should be all of the data */ + when.tv_sec = 30; + census_window_stats_get_sums(stats, when, &result); + GPR_ASSERT(compare_double(result.count, 1.0, epsilon) == 0 && + compare_double(sum.value1, value.value1, epsilon) == 0 && + sum.value2 == value.value2); + when.tv_sec = 60; + census_window_stats_get_sums(stats, when, &result); + GPR_ASSERT(compare_double(result.count, 1.0, epsilon) == 0 && + compare_double(sum.value1, value.value1, epsilon) == 0 && + sum.value2 == value.value2); + /* when = 66s, should be half (only take half of bottom bucket) */ + when.tv_sec = 66; + census_window_stats_get_sums(stats, when, &result); + GPR_ASSERT(compare_double(result.count, 0.5, epsilon) == 0 && + compare_double(sum.value1, value.value1 / 2, epsilon) == 0 && + sum.value2 == value.value2 / 2); + /* when = 72s, should be completely out of window */ + when.tv_sec = 72; + census_window_stats_get_sums(stats, when, &result); + GPR_ASSERT(compare_double(result.count, 0, epsilon) == 0 && + compare_double(sum.value1, 0, epsilon) == 0 && sum.value2 == 0); + + /* phase 2: tear down and do as before, but inserting two values */ + census_window_stats_destroy(stats); + stats = census_window_stats_create(1, &kMinInterval, 5, &kMyStatInfo); + GPR_ASSERT(stats != NULL); + when.tv_sec = 0; + when.tv_nsec = 17; + census_window_stats_add(stats, when, &value); + when.tv_sec = 1; + census_window_stats_add(stats, when, &value); + when.tv_sec = 0; + census_window_stats_get_sums(stats, when, &result); + GPR_ASSERT(compare_double(result.count, 0, epsilon) == 0 && + compare_double(sum.value1, 0, epsilon) == 0 && sum.value2 == 0); + /* time = 3s, 30s, should get all data */ + when.tv_sec = 3; + census_window_stats_get_sums(stats, when, &result); + GPR_ASSERT(compare_double(result.count, 2, epsilon) == 0 && + compare_double(sum.value1, 2 * value.value1, epsilon) == 0 && + sum.value2 == 2 * value.value2); + when.tv_sec = 30; + census_window_stats_get_sums(stats, when, &result); + GPR_ASSERT(compare_double(result.count, 2, epsilon) == 0 && + compare_double(sum.value1, 2 * value.value1, epsilon) == 0 && + sum.value2 == 2 * value.value2); + + /* phase 3: insert into "middle" bucket, and force a shift, pushing out + the two values in bottom bucket */ + when.tv_sec = 30; + census_window_stats_add(stats, when, &value); + when.tv_sec = 76; + census_window_stats_add(stats, when, &value); + when.tv_sec = 0; + census_window_stats_get_sums(stats, when, &result); + GPR_ASSERT(result.count == 0 && sum.value1 == 0 && sum.value2 == 0); + when.tv_sec = 30; + census_window_stats_get_sums(stats, when, &result); + /* half of the single value in the 30 second bucket */ + GPR_ASSERT(compare_double(result.count, 0.5, epsilon) == 0 && + compare_double(sum.value1, value.value1 / 2, epsilon) == 0 && + sum.value2 == value.value2 / 2); + when.tv_sec = 74; + census_window_stats_get_sums(stats, when, &result); + /* half of the 76 second bucket, all of the 30 second bucket */ + GPR_ASSERT(compare_double(result.count, 1.5, epsilon) == 0 && + compare_double(sum.value1, value.value1 * 1.5, epsilon) == 0 && + sum.value2 == value.value2 / 2 * 3); + when.tv_sec = 76; + census_window_stats_get_sums(stats, when, &result); + /* >=76s, get all of the 76 second bucket, all of the 30 second bucket */ + GPR_ASSERT(compare_double(result.count, 2, epsilon) == 0 && + compare_double(sum.value1, value.value1 * 2, epsilon) == 0 && + sum.value2 == value.value2 * 2); + when.tv_sec = 78; + census_window_stats_get_sums(stats, when, &result); + /* half of the 76 second bucket, all of the 30 second bucket */ + GPR_ASSERT(compare_double(result.count, 2, epsilon) == 0 && + compare_double(sum.value1, value.value1 * 2, epsilon) == 0 && + sum.value2 == value.value2 * 2); + census_window_stats_destroy(stats); +} + +void many_interval_test() { + gpr_timespec intervals[4]; + const test_stat value = {123.45, 8}; + const double epsilon = 1e10 - 11; + gpr_timespec when = {3600, 0}; /* one hour */ + census_window_stats_sums result[4]; + test_stat sums[4]; + int i; + struct census_window_stats* stats; + intervals[0] = kMilliSecInterval; + intervals[1] = kSecInterval; + intervals[2] = kMinInterval; + intervals[3] = kHourInterval; + for (i = 0; i < 4; i++) { + result[i].statistic = &sums[i]; + } + stats = census_window_stats_create(4, intervals, 100, &kMyStatInfo); + GPR_ASSERT(stats != NULL); + /* add 10 stats within half of each time range */ + for (i = 0; i < 10; i++) { + when.tv_sec += 180; /* covers 30 min of one hour range */ + census_window_stats_add(stats, when, &value); + } + when.tv_sec += 120; + for (i = 0; i < 10; i++) { + when.tv_sec += 3; /* covers 30 sec of one minute range */ + census_window_stats_add(stats, when, &value); + } + when.tv_sec += 2; + for (i = 0; i < 10; i++) { + when.tv_nsec += 50000000; /* covers 0.5s of 1s range */ + census_window_stats_add(stats, when, &value); + } + when.tv_nsec += 2000000; + for (i = 0; i < 10; i++) { + when.tv_nsec += 50000; /* covers 0.5 ms of 1 ms range */ + census_window_stats_add(stats, when, &value); + } + when.tv_nsec += 20000; + census_window_stats_get_sums(stats, when, result); + GPR_ASSERT(compare_double(result[0].count, 10, epsilon) == 0 && + compare_double(sums[0].value1, value.value1 * 10, epsilon) == 0 && + sums[0].value2 == value.value2 * 10); + when.tv_nsec += 20000000; + census_window_stats_get_sums(stats, when, result); + GPR_ASSERT(compare_double(result[1].count, 20, epsilon) == 0 && + compare_double(sums[1].value1, value.value1 * 20, epsilon) == 0 && + sums[1].value2 == value.value2 * 20); + when.tv_sec += 2; + census_window_stats_get_sums(stats, when, result); + GPR_ASSERT(compare_double(result[2].count, 30, epsilon) == 0 && + compare_double(sums[2].value1, value.value1 * 30, epsilon) == 0 && + sums[2].value2 == value.value2 * 30); + when.tv_sec += 72; + census_window_stats_get_sums(stats, when, result); + GPR_ASSERT(compare_double(result[3].count, 40, epsilon) == 0 && + compare_double(sums[3].value1, value.value1 * 40, epsilon) == 0 && + sums[3].value2 == value.value2 * 40); + census_window_stats_destroy(stats); +} + +void rolling_time_test() { + const test_stat value = {0.1, 4}; + gpr_timespec when = {0, 0}; + census_window_stats_sums result; + test_stat sum; + int i; + gpr_timespec increment = {0, 0}; + struct census_window_stats* stats = + census_window_stats_create(1, &kMinInterval, 7, &kMyStatInfo); + GPR_ASSERT(stats != NULL); + srand(gpr_now().tv_nsec); + for (i = 0; i < 100000; i++) { + increment.tv_nsec = rand() % 100000000; /* up to 1/10th second */ + when = gpr_time_add(when, increment); + census_window_stats_add(stats, when, &value); + } + result.statistic = ∑ + census_window_stats_get_sums(stats, when, &result); + /* With 1/20th second average between samples, we expect 20*60 = 1200 + samples on average. Make sure we are within 100 of that. */ + GPR_ASSERT(compare_double(result.count, 1200, 100) == 0); + census_window_stats_destroy(stats); +} +#include <stdio.h> +void infinite_interval_test() { + const test_stat value = {0.1, 4}; + gpr_timespec when = {0, 0}; + census_window_stats_sums result; + test_stat sum; + int i; + const int count = 100000; + gpr_timespec increment = {0, 0}; + struct census_window_stats* stats = + census_window_stats_create(1, &gpr_inf_future, 10, &kMyStatInfo); + srand(gpr_now().tv_nsec); + for (i = 0; i < count; i++) { + increment.tv_sec = rand() % 21600; /* 6 hours */ + when = gpr_time_add(when, increment); + census_window_stats_add(stats, when, &value); + } + result.statistic = ∑ + census_window_stats_get_sums(stats, when, &result); + /* The only thing it makes sense to compare for "infinite" periods is the + total counts */ + GPR_ASSERT(result.count == count); + census_window_stats_destroy(stats); +} + +int main(int argc, char* argv[]) { + grpc_test_init(argc, argv); + empty_test(); + one_interval_test(); + many_interval_test(); + rolling_time_test(); + infinite_interval_test(); + return 0; +} |