From bb30d2591fc52c5bacc309c107077d92d5afc70a Mon Sep 17 00:00:00 2001 From: Alistair Veitch Date: Tue, 12 Jan 2016 17:36:05 -0800 Subject: [PATCH] initial commit --- BUILD | 6 + Makefile | 37 ++ binding.gyp | 1 + build.yaml | 12 + gRPC.podspec | 5 +- include/grpc/census.h | 102 ++-- src/core/census/tag_set.c | 527 ++++++++++++++++++ src/core/census/tag_set.h | 57 ++ test/core/census/tag_set_test.c | 404 ++++++++++++++ tools/doxygen/Doxyfile.core.internal | 2 + tools/run_tests/sources_and_headers.json | 20 + tools/run_tests/tests.json | 19 + vsprojects/buildtests_c.sln | 27 + vsprojects/vcxproj/grpc/grpc.vcxproj | 3 + vsprojects/vcxproj/grpc/grpc.vcxproj.filters | 6 + .../grpc_unsecure/grpc_unsecure.vcxproj | 3 + .../grpc_unsecure.vcxproj.filters | 6 + .../test/tag_set_test/tag_set_test.vcxproj | 197 +++++++ .../tag_set_test/tag_set_test.vcxproj.filters | 21 + 19 files changed, 1408 insertions(+), 47 deletions(-) create mode 100644 src/core/census/tag_set.c create mode 100644 src/core/census/tag_set.h create mode 100644 test/core/census/tag_set_test.c create mode 100644 vsprojects/vcxproj/test/tag_set_test/tag_set_test.vcxproj create mode 100644 vsprojects/vcxproj/test/tag_set_test/tag_set_test.vcxproj.filters diff --git a/BUILD b/BUILD index 2b386fb814c..ae1a69073fc 100644 --- a/BUILD +++ b/BUILD @@ -268,6 +268,7 @@ cc_library( "src/core/census/aggregation.h", "src/core/census/context.h", "src/core/census/rpc_metric_id.h", + "src/core/census/tag_set.h", "src/core/httpcli/httpcli_security_connector.c", "src/core/security/base64.c", "src/core/security/client_auth_filter.c", @@ -417,6 +418,7 @@ cc_library( "src/core/census/context.c", "src/core/census/initialize.c", "src/core/census/operation.c", + "src/core/census/tag_set.c", "src/core/census/tracing.c", ], hdrs = [ @@ -559,6 +561,7 @@ cc_library( "src/core/census/aggregation.h", "src/core/census/context.h", "src/core/census/rpc_metric_id.h", + "src/core/census/tag_set.h", "src/core/surface/init_unsecure.c", "src/core/census/grpc_context.c", "src/core/census/grpc_filter.c", @@ -688,6 +691,7 @@ cc_library( "src/core/census/context.c", "src/core/census/initialize.c", "src/core/census/operation.c", + "src/core/census/tag_set.c", "src/core/census/tracing.c", ], hdrs = [ @@ -1222,6 +1226,7 @@ objc_library( "src/core/census/context.c", "src/core/census/initialize.c", "src/core/census/operation.c", + "src/core/census/tag_set.c", "src/core/census/tracing.c", ], hdrs = [ @@ -1362,6 +1367,7 @@ objc_library( "src/core/census/aggregation.h", "src/core/census/context.h", "src/core/census/rpc_metric_id.h", + "src/core/census/tag_set.h", ], includes = [ "include", diff --git a/Makefile b/Makefile index 449077b65c7..3d607c2174c 100644 --- a/Makefile +++ b/Makefile @@ -875,6 +875,7 @@ set_initial_connect_string_test: $(BINDIR)/$(CONFIG)/set_initial_connect_string_ sockaddr_resolver_test: $(BINDIR)/$(CONFIG)/sockaddr_resolver_test sockaddr_utils_test: $(BINDIR)/$(CONFIG)/sockaddr_utils_test socket_utils_test: $(BINDIR)/$(CONFIG)/socket_utils_test +tag_set_test: $(BINDIR)/$(CONFIG)/tag_set_test tcp_client_posix_test: $(BINDIR)/$(CONFIG)/tcp_client_posix_test tcp_posix_test: $(BINDIR)/$(CONFIG)/tcp_posix_test tcp_server_posix_test: $(BINDIR)/$(CONFIG)/tcp_server_posix_test @@ -1180,6 +1181,7 @@ buildtests_c: privatelibs_c \ $(BINDIR)/$(CONFIG)/sockaddr_resolver_test \ $(BINDIR)/$(CONFIG)/sockaddr_utils_test \ $(BINDIR)/$(CONFIG)/socket_utils_test \ + $(BINDIR)/$(CONFIG)/tag_set_test \ $(BINDIR)/$(CONFIG)/tcp_client_posix_test \ $(BINDIR)/$(CONFIG)/tcp_posix_test \ $(BINDIR)/$(CONFIG)/tcp_server_posix_test \ @@ -1471,6 +1473,8 @@ test_c: buildtests_c $(Q) $(BINDIR)/$(CONFIG)/sockaddr_utils_test || ( echo test sockaddr_utils_test failed ; exit 1 ) $(E) "[RUN] Testing socket_utils_test" $(Q) $(BINDIR)/$(CONFIG)/socket_utils_test || ( echo test socket_utils_test failed ; exit 1 ) + $(E) "[RUN] Testing tag_set_test" + $(Q) $(BINDIR)/$(CONFIG)/tag_set_test || ( echo test tag_set_test failed ; exit 1 ) $(E) "[RUN] Testing tcp_client_posix_test" $(Q) $(BINDIR)/$(CONFIG)/tcp_client_posix_test || ( echo test tcp_client_posix_test failed ; exit 1 ) $(E) "[RUN] Testing tcp_posix_test" @@ -2446,6 +2450,7 @@ LIBGRPC_SRC = \ src/core/census/context.c \ src/core/census/initialize.c \ src/core/census/operation.c \ + src/core/census/tag_set.c \ src/core/census/tracing.c \ PUBLIC_HEADERS_C += \ @@ -2748,6 +2753,7 @@ LIBGRPC_UNSECURE_SRC = \ src/core/census/context.c \ src/core/census/initialize.c \ src/core/census/operation.c \ + src/core/census/tag_set.c \ src/core/census/tracing.c \ PUBLIC_HEADERS_C += \ @@ -8190,6 +8196,37 @@ endif endif +TAG_SET_TEST_SRC = \ + test/core/census/tag_set_test.c \ + +TAG_SET_TEST_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(TAG_SET_TEST_SRC)))) +ifeq ($(NO_SECURE),true) + +# You can't build secure targets if you don't have OpenSSL. + +$(BINDIR)/$(CONFIG)/tag_set_test: openssl_dep_error + +else + + + +$(BINDIR)/$(CONFIG)/tag_set_test: $(TAG_SET_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a + $(E) "[LD] Linking $@" + $(Q) mkdir -p `dirname $@` + $(Q) $(LD) $(LDFLAGS) $(TAG_SET_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LDLIBS) $(LDLIBS_SECURE) -o $(BINDIR)/$(CONFIG)/tag_set_test + +endif + +$(OBJDIR)/$(CONFIG)/test/core/census/tag_set_test.o: $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a +deps_tag_set_test: $(TAG_SET_TEST_OBJS:.o=.dep) + +ifneq ($(NO_SECURE),true) +ifneq ($(NO_DEPS),true) +-include $(TAG_SET_TEST_OBJS:.o=.dep) +endif +endif + + TCP_CLIENT_POSIX_TEST_SRC = \ test/core/iomgr/tcp_client_posix_test.c \ diff --git a/binding.gyp b/binding.gyp index 75e2f3c8de5..cd4265f0c34 100644 --- a/binding.gyp +++ b/binding.gyp @@ -301,6 +301,7 @@ 'src/core/census/context.c', 'src/core/census/initialize.c', 'src/core/census/operation.c', + 'src/core/census/tag_set.c', 'src/core/census/tracing.c', ], "conditions": [ diff --git a/build.yaml b/build.yaml index 6227b18b7d6..367ef98401f 100644 --- a/build.yaml +++ b/build.yaml @@ -16,10 +16,12 @@ filegroups: - src/core/census/aggregation.h - src/core/census/context.h - src/core/census/rpc_metric_id.h + - src/core/census/tag_set.h src: - src/core/census/context.c - src/core/census/initialize.c - src/core/census/operation.c + - src/core/census/tag_set.c - src/core/census/tracing.c - name: grpc++_base public_headers: @@ -1617,6 +1619,16 @@ targets: - mac - linux - posix +- name: tag_set_test + build: test + language: c + src: + - test/core/census/tag_set_test.c + deps: + - grpc_test_util + - grpc + - gpr_test_util + - gpr - name: tcp_client_posix_test build: test language: c diff --git a/gRPC.podspec b/gRPC.podspec index 80f26817d09..4acf2d7e1ba 100644 --- a/gRPC.podspec +++ b/gRPC.podspec @@ -272,6 +272,7 @@ Pod::Spec.new do |s| 'src/core/census/aggregation.h', 'src/core/census/context.h', 'src/core/census/rpc_metric_id.h', + 'src/core/census/tag_set.h', 'include/grpc/grpc_security.h', 'include/grpc/byte_buffer.h', 'include/grpc/byte_buffer_reader.h', @@ -428,6 +429,7 @@ Pod::Spec.new do |s| 'src/core/census/context.c', 'src/core/census/initialize.c', 'src/core/census/operation.c', + 'src/core/census/tag_set.c', 'src/core/census/tracing.c' ss.private_header_files = 'src/core/profiling/timers.h', @@ -569,7 +571,8 @@ Pod::Spec.new do |s| 'src/core/transport/transport_impl.h', 'src/core/census/aggregation.h', 'src/core/census/context.h', - 'src/core/census/rpc_metric_id.h' + 'src/core/census/rpc_metric_id.h', + 'src/core/census/tag_set.h' ss.header_mappings_dir = '.' # This isn't officially supported in Cocoapods. We've asked for an alternative: diff --git a/include/grpc/census.h b/include/grpc/census.h index d0bc90420c0..b1276696944 100644 --- a/include/grpc/census.h +++ b/include/grpc/census.h @@ -324,60 +324,70 @@ int census_get_trace_record(census_trace_record *trace_record); /** End a scan previously started by census_trace_scan_start() */ void census_trace_scan_end(); -/* Max number of characters in tag key */ -#define CENSUS_MAX_TAG_KEY_LENGTH 20 -/* Max number of tag value characters */ -#define CENSUS_MAX_TAG_VALUE_LENGTH 50 - /* A Census tag set is a collection of key:value string pairs; these form the basis against which Census metrics will be recorded. Keys are unique within a tag set. All contexts have an associated tag set. */ typedef struct census_tag_set census_tag_set; -/* Returns a pointer to a newly created, empty tag set. If size_hint > 0, - indicates that the tag set is intended to hold approximately that number - of tags. */ -census_tag_set *census_tag_set_create(size_t size_hint); - -/* Add a new tag key/value to an existing tag set; if the tag key already exists - in the tag set, then its value is overwritten with the new one. Can also be - used to delete a tag, by specifying a NULL value. If key is NULL, returns - the number of tags in the tag set. - Return values: - -1: invalid length key or value - non-negative value: the number of tags in the tag set. */ -int census_tag_set_add(census_tag_set *tags, const char *key, - const char *value); - -/* Destroys a tag set. This function must be called to prevent memory leaks. - Once called, the tag set cannot be used again. */ -void census_tag_set_destroy(census_tag_set *tags); - -/* Get a contexts tag set. */ -census_tag_set *census_context_tag_set(census_context *context); - -/* A read-only representation of a tag for use by census clients. */ +/* A tag is a key:value pair. The key is a printable, nil-terminate + string. The value is a binary string, that may be printable. There are no + limits on the sizes of either keys or values, but code authors should + remember that systems may have inbuilt limits (e.g. for propagated tags, + the bytes on the wire) and that larger tags means more memory consumed and + time in processing. */ typedef struct { - size_t key_len; /* Number of bytes in tag key. */ - const char *key; /* A pointer to the tag key. May not be null-terminated. */ - size_t value_len; /* Number of bytes in tag value. */ - const char *value; /* Pointer to the tag value. May not be null-terminated. */ -} census_tag_const; + const char *key; + const char *value; + size_t value_len; + gpr_uint8 flags; +} census_tag; + +/* Tag flags. */ +#define CENSUS_TAG_PROPAGATE 1 /* Tag should be propagated over RPC */ +#define CENSUS_TAG_STATS 2 /* Tag will be used for statistics aggregation */ +#define CENSUS_TAG_BINARY 4 /* Tag value is not printable */ +#define CENSUS_TAG_RESERVED 8 /* Reserved for internal use. */ +/* Flag values 8,16,32,64,128 are reserved for future/internal use. Clients + should not use or rely on their values. */ + +#define CENSUS_TAG_IS_PROPAGATED(flags) (flags & CENSUS_TAG_PROPAGATE) +#define CENSUS_TAG_IS_STATS(flags) (flags & CENSUS_TAG_STATS) +#define CENSUS_TAG_IS_BINARY(flags) (flags & CENSUS_TAG_BINARY) + +#define CENSUS_MAX_TAG_KV_LEN 255 /* Maximum length of key/value in a tag. */ +#define CENSUS_MAX_TAGS 255 /* Maximum number of tags in a tag set. */ + +/* Create a new tag set, adding and removing tags from an existing tag set. + @param base Base tag set to build upon. Can be NULL. + @param tags A set of tags to be added/changed/deleted. Tags with keys that + are in 'tags', but not 'base', are added to the tag set. Keys that are in + both 'tags' and 'base' will have their value replaced. Tags with keys in + both, but with NULL or zero-length values, will be deleted from the + tag set. + @param ntags number of tags in 'tags' +*/ +census_tag_set *census_tag_set_create(const census_tag_set *base, + const census_tag *tags, int ntags); + +/* Destroy a tag set created by census_tag_set_create(). Once this function + has been called, the tag set cannot be reused. */ +void census_tag_set_destroy(census_tag_set *tags); -/* Used to iterate through a tag sets contents. */ -typedef struct census_tag_set_iterator census_tag_set_iterator; +/* Get the number of tags in the tag set. */ +int census_tag_set_ntags(const census_tag_set *tags); -/* Open a tag set for iteration. The tag set must not be modified while - iteration is ongoing. Returns an iterator for use in following functions. */ -census_tag_set_iterator *census_tag_set_open(census_tag_set *tags); +/* Get a tag by it's index in the tag set. Returns 0 if the index is invalid + (<0 or >= census_tag_set_ntags). There is no guarantee on tag ordering. */ +int census_tag_set_get_tag_by_index(const census_tag_set *tags, int index, + census_tag *tag); -/* Get the next tag in the tag set, by writing into the 'tag' argument. Returns - 1 if there is a "next" tag, 0 if there are no more tags. */ -int census_tag_set_next(census_tag_set_iterator *it, census_tag_const *tag); +/* Get a tag by its key. Returns 0 if the key is not present in the tag + set. */ +int census_tag_set_get_tag_by_key(const census_tag_set *tags, const char *key, + census_tag *tag); -/* Close an iterator opened by census_tag_set_open(). The iterator will be - invalidated, and should not be used once close is called. */ -void census_tag_set_close(census_tag_set_iterator *it); +/* Get a contexts tag set. */ +census_tag_set *census_context_tag_set(census_context *context); /* Core stats collection API's. The following concepts are used: * Aggregation: A collection of values. Census supports the following @@ -424,8 +434,8 @@ extern census_aggregation_ops census_agg_window; construction via census_define_view(). */ typedef struct { const census_aggregation_ops *ops; - const void * - create_arg; /* Argument to be used for aggregation initialization. */ + const void + *create_arg; /* Argument to be used for aggregation initialization. */ } census_aggregation; /** A census view type. Opaque. */ diff --git a/src/core/census/tag_set.c b/src/core/census/tag_set.c new file mode 100644 index 00000000000..80c209031f4 --- /dev/null +++ b/src/core/census/tag_set.c @@ -0,0 +1,527 @@ +/* + * + * Copyright 2015, 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 "tag_set.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include "src/core/support/murmur_hash.h" +#include "src/core/support/string.h" + +// Functions in this file support the public tag_set API, as well as +// encoding/decoding tag_sets as part of context transmission across +// RPC's. The overall requirements (in approximate priority order) for the +// tag_set representations: +// 1. Efficient conversion to/from wire format +// 2. Minimal bytes used on-wire +// 3. Efficient tag set creation +// 4. Efficient lookup of value for a key +// 5. Efficient lookup of value for an index (to support iteration) +// 6. Minimal memory footprint +// +// Notes on tradeoffs/decisions: +// * tag includes 1 byte length of key, as well as nil-terminating byte. These +// are to aid in efficient parsing and the ability to directly return key +// strings. This is more important than saving a single byte/tag on the wire. +// * The wire encoding uses only single byte values. This eliminates the need +// to handle endian-ness conversions. +// * Keep all tag information (keys/values/flags) in a single memory buffer, +// that can be directly copied to the wire. This makes iteration by index +// somewhat less efficient. +// * Binary tags are encoded seperately from non-binary tags. There are a +// primarily because non-binary tags are far more likely to be repeated +// across multiple RPC calls, so are more efficiently cached and +// compressed in any metadata schemes. +// * deleted/modified tags are kept in memory, just marked with a deleted +// flag. This enables faster processing TODO: benchmark this +// * all lengths etc. are restricted to one byte. This eliminates endian +// issues. + +// Structure representing a set of tags. Essentially a count of number of tags +// present, and pointer to a chunk of memory that contains the per-tag details. +struct tag_set { + int ntags; // number of tags. + int ntags_alloc; // ntags + number of deleted tags (total number of tags + // in all of kvm). + size_t kvm_size; // number of bytes allocated for key/value storage. + size_t kvm_used; // number of bytes of used key/value memory + char *kvm; // key/value memory. Consists of repeated entries of: + // Offset Size Description + // 0 1 Key length, including trailing 0. (K) + // 1 1 Value length. (V) + // 2 1 Flags + // 3 K Key bytes + // 3 + K V Value bytes + // + // We refer to the first 3 entries as the 'tag header'. +}; + +// Number of bytes in tag header. +#define TAG_HEADER_SIZE 3 // key length (1) + value length (1) + flags (1) +// Offsets to tag header entries. +#define KEY_LEN_OFFSET 0 +#define VALUE_LEN_OFFSET 1 +#define FLAG_OFFSET 2 + +// raw_tag represents the raw-storage form of a tag in the kvm of a tag_set. +struct raw_tag { + uint8_t key_len; + uint8_t value_len; + uint8_t flags; + char *key; + char *value; +}; + +// use reserved flag bit for indication of deleted tag. +#define CENSUS_TAG_DELETED CENSUS_TAG_RESERVED +#define CENSUS_TAG_IS_DELETED(flags) (flags & CENSUS_TAG_DELETED) + +// Primary (external) representation of a tag set. Composed of 3 underlying +// tag_set structs, one for each of the binary/printable propagated tags, and +// one for everything else. +struct census_tag_set { + struct tag_set propagated_tags; + struct tag_set propagated_binary_tags; + struct tag_set local_tags; +}; + +// Extract a raw tag given a pointer (raw) to the tag header. Allow for some +// extra bytes in the tag header (see encode/decode for usage: allows for +// future expansion of the tag header). +static char *decode_tag(struct raw_tag *tag, char *header, int offset) { + tag->key_len = (uint8_t)(*header++); + tag->value_len = (uint8_t)(*header++); + tag->flags = (uint8_t)(*header++); + header += offset; + tag->key = header; + header += tag->key_len; + tag->value = header; + return header + tag->value_len; +} + +// Make a copy (in 'to') of an existing tag_set. +static void tag_set_copy(struct tag_set *to, const struct tag_set *from) { + memcpy(to, from, sizeof(struct tag_set)); + to->kvm = gpr_malloc(to->kvm_size); + memcpy(to->kvm, from->kvm, to->kvm_used); +} + +// We may want to keep information about a deleted tag for a short time, +// in case we can reuse the space (same tag is reinserted). This structure +// is used for that purpose. +struct deleted_tag_info { + struct raw_tag raw; // raw tag information. + uint8_t *raw_flags_p; // pointer to original flags + struct tag_set *tags; // the tag set from which tag was deleted. +}; + +// Delete a tag from a tag set, if it exists. Returns true if the tag was +// originally present (and is now deleted), false if it wasn't. +static bool tag_set_delete_tag(struct tag_set *tags, + struct deleted_tag_info *dtag, const char *key, + size_t key_len) { + char *kvp = tags->kvm; + for (int i = 0; i < tags->ntags_alloc; i++) { + dtag->raw_flags_p = (uint8_t *)(kvp + FLAG_OFFSET); + kvp = decode_tag(&dtag->raw, kvp, 0); + if (CENSUS_TAG_IS_DELETED(dtag->raw.flags)) continue; + if ((key_len == dtag->raw.key_len) && + (memcmp(key, dtag->raw.key, key_len) == 0)) { + *(dtag->raw_flags_p) |= CENSUS_TAG_DELETED; + tags->ntags--; + return true; + } + } + return false; +} + +// Delete a tag from a tag set. If the tag is found in any of the underlying +// tag sets, *and* that tag set corresponds to the one in which the tag (if +// later inserted) would be placed, then fills in dtag, and returns true. +// Returns false otherwise. This information is later used to optimize the +// placement of the tag if the value space can be reused, effectively +// "undeleting" the tag. +static bool cts_delete_tag(census_tag_set *tags, const census_tag *tag, + size_t key_len, struct deleted_tag_info *dtag) { + // use the to-be-deleted tag flags as a hint to determine the order in which + // we delete from the underlying tag sets. + if (CENSUS_TAG_IS_PROPAGATED(tag->flags)) { + if (CENSUS_TAG_IS_BINARY(tag->flags)) { + if (tag_set_delete_tag(&tags->propagated_binary_tags, dtag, tag->key, + key_len)) { + dtag->tags = &tags->propagated_binary_tags; + return true; + } + if (tag_set_delete_tag(&tags->propagated_tags, dtag, tag->key, key_len)) + return false; + tag_set_delete_tag(&tags->local_tags, dtag, tag->key, key_len); + } else { + if (tag_set_delete_tag(&tags->propagated_tags, dtag, tag->key, key_len)) { + dtag->tags = &tags->propagated_tags; + return true; + } + if (tag_set_delete_tag(&tags->propagated_binary_tags, dtag, tag->key, + key_len)) + return false; + tag_set_delete_tag(&tags->local_tags, dtag, tag->key, key_len); + } + } else { + if (tag_set_delete_tag(&tags->local_tags, dtag, tag->key, key_len)) { + dtag->tags = &tags->local_tags; + return true; + } + if (tag_set_delete_tag(&tags->propagated_tags, dtag, tag->key, key_len)) + return false; + tag_set_delete_tag(&tags->propagated_binary_tags, dtag, tag->key, key_len); + } + return false; +} + +// Add a tag to a tag set. +static void tag_set_add_tag(struct tag_set *tags, const census_tag *tag, + size_t key_len) { + const size_t tag_size = key_len + tag->value_len + TAG_HEADER_SIZE; + // drop tag if too many. + if (tags->ntags == CENSUS_MAX_TAGS) { + return; + } + if (tags->kvm_used + tag_size > tags->kvm_size) { + tags->kvm_size += 2 * CENSUS_MAX_TAG_KV_LEN + TAG_HEADER_SIZE; + char *new_kvm = gpr_malloc(tags->kvm_size); + memcpy(new_kvm, tags->kvm, tags->kvm_used); + gpr_free(tags->kvm); + tags->kvm = new_kvm; + } + char *kvp = tags->kvm + tags->kvm_used; + *kvp++ = (char)key_len; + *kvp++ = (char)tag->value_len; + // ensure reserved flags are not used. + *kvp++ = (char)(tag->flags & (CENSUS_TAG_PROPAGATE | CENSUS_TAG_STATS | + CENSUS_TAG_BINARY)); + memcpy(kvp, tag->key, key_len); + kvp += key_len; + memcpy(kvp, tag->value, tag->value_len); + tags->kvm_used += tag_size; + tags->ntags++; + tags->ntags_alloc++; +} + +// Add a tag to a census_tag_set +static void cts_add_tag(census_tag_set *tags, const census_tag *tag, + size_t key_len) { + // first delete the tag if it is already present + struct deleted_tag_info dtag; + bool deleted_match = cts_delete_tag(tags, tag, key_len, &dtag); + if (tag->value != NULL && tag->value_len != 0) { + if (deleted_match && tag->value_len == dtag.raw.value_len) { + // if we have a close match for tag being added to one just deleted, + // only need to modify value and flags. + memcpy(dtag.raw.value, tag->value, tag->value_len); + *dtag.raw_flags_p = (tag->flags & (CENSUS_TAG_PROPAGATE | + CENSUS_TAG_STATS | CENSUS_TAG_BINARY)); + dtag.tags->ntags++; + } else { + if (CENSUS_TAG_IS_PROPAGATED(tag->flags)) { + if (CENSUS_TAG_IS_BINARY(tag->flags)) { + tag_set_add_tag(&tags->propagated_binary_tags, tag, key_len); + } else { + tag_set_add_tag(&tags->propagated_tags, tag, key_len); + } + } else { + tag_set_add_tag(&tags->local_tags, tag, key_len); + } + } + } +} + +census_tag_set *census_tag_set_create(const census_tag_set *base, + const census_tag *tags, int ntags) { + census_tag_set *new_ts = gpr_malloc(sizeof(census_tag_set)); + if (base == NULL) { + memset(new_ts, 0, sizeof(census_tag_set)); + } else { + tag_set_copy(&new_ts->propagated_tags, &base->propagated_tags); + tag_set_copy(&new_ts->propagated_binary_tags, + &base->propagated_binary_tags); + tag_set_copy(&new_ts->local_tags, &base->local_tags); + } + for (int i = 0; i < ntags; i++) { + const census_tag *tag = &tags[i]; + size_t key_len = strlen(tag->key) + 1; + // ignore the tag if it is too long/short. + if (key_len != 1 && key_len <= CENSUS_MAX_TAG_KV_LEN && + tag->value_len <= CENSUS_MAX_TAG_KV_LEN) { + cts_add_tag(new_ts, tag, key_len); + } + } + return new_ts; +} + +void census_tag_set_destroy(census_tag_set *tags) { + gpr_free(tags->propagated_tags.kvm); + gpr_free(tags->propagated_binary_tags.kvm); + gpr_free(tags->local_tags.kvm); + gpr_free(tags); +} + +int census_tag_set_ntags(const census_tag_set *tags) { + return tags->propagated_tags.ntags + tags->propagated_binary_tags.ntags + + tags->local_tags.ntags; +} + +// Get the nth tag in a tag set. The caller must validate that index is +// in range. +static void tag_set_get_tag_by_index(const struct tag_set *tags, int index, + census_tag *tag) { + GPR_ASSERT(index < tags->ntags); + char *kvp = tags->kvm; + for (;;) { + struct raw_tag raw; + kvp = decode_tag(&raw, kvp, 0); + if (CENSUS_TAG_IS_DELETED(raw.flags)) { + continue; + } else if (index == 0) { + tag->key = raw.key; + tag->value = raw.value; + tag->value_len = raw.value_len; + tag->flags = raw.flags; + return; + } + index--; + } + // NOT REACHED +} + +int census_tag_set_get_tag_by_index(const census_tag_set *tags, int index, + census_tag *tag) { + if (index < 0) return 0; + if (index < tags->propagated_tags.ntags) { + tag_set_get_tag_by_index(&tags->propagated_tags, index, tag); + return 1; + } + index -= tags->propagated_tags.ntags; + if (index < tags->propagated_binary_tags.ntags) { + tag_set_get_tag_by_index(&tags->propagated_binary_tags, index, tag); + return 1; + } + index -= tags->propagated_binary_tags.ntags; + if (index < tags->local_tags.ntags) { + tag_set_get_tag_by_index(&tags->local_tags, index, tag); + return 1; + } + return 0; +} + +// Find a tag in a tag_set by key. Return true if found, false otherwise. +static bool tag_set_get_tag_by_key(const struct tag_set *tags, const char *key, + size_t key_len, census_tag *tag) { + char *kvp = tags->kvm; + for (int i = 0; i < tags->ntags; i++) { + struct raw_tag raw; + do { + kvp = decode_tag(&raw, kvp, 0); + } while (CENSUS_TAG_IS_DELETED(raw.flags)); + if (key_len == raw.key_len && memcmp(raw.key, key, key_len) == 0) { + tag->key = raw.key; + tag->value = raw.value; + tag->value_len = raw.value_len; + tag->flags = raw.flags; + return true; + } + } + return false; +} + +int census_tag_set_get_tag_by_key(const census_tag_set *tags, const char *key, + census_tag *tag) { + size_t key_len = strlen(key) + 1; + if (key_len == 1) { + return 0; + } + if (tag_set_get_tag_by_key(&tags->propagated_tags, key, key_len, tag) || + tag_set_get_tag_by_key(&tags->propagated_binary_tags, key, key_len, + tag) || + tag_set_get_tag_by_key(&tags->local_tags, key, key_len, tag)) { + return 1; + } + return 0; +} + +// tag_set encoding and decoding functions. +// +// Wire format for tag sets on the wire: +// +// First, a tag set header: +// +// offset bytes description +// 0 1 version number +// 1 1 number of bytes in this header. This allows for future +// expansion. +// 2 1 number of bytes in each tag header. +// 3 1 ntags value from tag set. +// 4 1 ntags_alloc value from tag set. +// +// This is followed by the key/value memory from struct tag_set. + +#define ENCODED_VERSION 0 // Version number +#define ENCODED_HEADER_SIZE 5 // size of tag set header + +// Pack a tag set into as few bytes as possible (eliding deleted tags). Assumes +// header is already generated. +static size_t tag_set_encode_packed(const struct tag_set *tags, char *buffer, + size_t buf_size) { + size_t encoded_size = 0; + char *kvp = tags->kvm; + for (int i = 0; i < tags->ntags_alloc; i++) { + struct raw_tag raw; + char *base = kvp; + kvp = decode_tag(&raw, kvp, 0); + size_t tag_size = + TAG_HEADER_SIZE + (size_t)raw.key_len + (size_t)raw.value_len; + if (!(CENSUS_TAG_IS_DELETED(raw.flags))) { + if (tag_size > buf_size) { + return 0; + } + memcpy(buffer, base, tag_size); + buffer += tag_size; + encoded_size += tag_size; + buf_size -= tag_size; + } + } + return encoded_size; +} + +// Encode a tag set. Returns 0 if buffer is too small. +static size_t tag_set_encode(const struct tag_set *tags, char *buffer, + size_t buf_size) { + if (buf_size < ENCODED_HEADER_SIZE) { + return 0; + } + buf_size -= ENCODED_HEADER_SIZE; + *buffer++ = (char)ENCODED_VERSION; + *buffer++ = (char)ENCODED_HEADER_SIZE; + *buffer++ = (char)TAG_HEADER_SIZE; + *buffer++ = (char)tags->ntags; + if (tags->ntags == 0) { + *buffer = (char)tags->ntags; + return ENCODED_HEADER_SIZE; + } + if (buf_size < tags->kvm_used || tags->ntags_alloc > CENSUS_MAX_TAGS) { + *buffer++ = (char)tags->ntags; + size_t enc_size = tag_set_encode_packed(tags, buffer, buf_size); + if (enc_size == 0) { + return 0; + } + return ENCODED_HEADER_SIZE + enc_size; + } + *buffer++ = (char)tags->ntags_alloc; + memcpy(buffer, tags->kvm, tags->kvm_used); + return ENCODED_HEADER_SIZE + tags->kvm_used; +} + +size_t census_tag_set_encode_propagated(const census_tag_set *tags, + char *buffer, size_t buf_size) { + return tag_set_encode(&tags->propagated_tags, buffer, buf_size); +} + +size_t census_tag_set_encode_propagated_binary(const census_tag_set *tags, + char *buffer, size_t buf_size) { + return tag_set_encode(&tags->propagated_binary_tags, buffer, buf_size); +} + +// Decode a tag set. +static void tag_set_decode(struct tag_set *tags, const char *buffer, + size_t size) { + uint8_t version = (uint8_t)(*buffer++); + uint8_t header_size = (uint8_t)(*buffer++); + uint8_t tag_header_size = (uint8_t)(*buffer++); + tags->ntags = (int)(*buffer++); + if (tags->ntags == 0) { + tags->ntags_alloc = 0; + tags->kvm_size = 0; + tags->kvm_used = 0; + tags->kvm = NULL; + return; + } + tags->ntags_alloc = (uint8_t)(*buffer++); + if (header_size != ENCODED_HEADER_SIZE) { + GPR_ASSERT(version != ENCODED_VERSION); + GPR_ASSERT(ENCODED_HEADER_SIZE < header_size); + buffer += (header_size - ENCODED_HEADER_SIZE); + } + tags->kvm_used = size - header_size; + tags->kvm_size = tags->kvm_used + CENSUS_MAX_TAG_KV_LEN; + tags->kvm = gpr_malloc(tags->kvm_size); + if (tag_header_size != TAG_HEADER_SIZE) { + // something new in the tag information. I don't understand it, so + // don't copy it over. + GPR_ASSERT(version != ENCODED_VERSION); + GPR_ASSERT(tag_header_size > TAG_HEADER_SIZE); + char *kvp = tags->kvm; + for (int i = 0; i < tags->ntags_alloc; i++) { + memcpy(kvp, buffer, TAG_HEADER_SIZE); + kvp += header_size; + struct raw_tag raw; + buffer = + decode_tag(&raw, (char *)buffer, tag_header_size - TAG_HEADER_SIZE); + memcpy(kvp, raw.key, (size_t)raw.key_len + raw.value_len); + kvp += raw.key_len + raw.value_len; + } + } else { + memcpy(tags->kvm, buffer, tags->kvm_used); + } +} + +census_tag_set *census_tag_set_decode(const char *buffer, size_t size, + const char *bin_buffer, size_t bin_size) { + census_tag_set *new_ts = gpr_malloc(sizeof(census_tag_set)); + memset(&new_ts->local_tags, 0, sizeof(struct tag_set)); + if (buffer == NULL) { + memset(&new_ts->propagated_tags, 0, sizeof(struct tag_set)); + } else { + tag_set_decode(&new_ts->propagated_tags, buffer, size); + } + if (bin_buffer == NULL) { + memset(&new_ts->propagated_binary_tags, 0, sizeof(struct tag_set)); + } else { + tag_set_decode(&new_ts->propagated_binary_tags, bin_buffer, bin_size); + } + // TODO(aveitch): check that BINARY flag is correct for each type. + return new_ts; +} diff --git a/src/core/census/tag_set.h b/src/core/census/tag_set.h new file mode 100644 index 00000000000..9eec0ad4384 --- /dev/null +++ b/src/core/census/tag_set.h @@ -0,0 +1,57 @@ +/* + * + * Copyright 2015, 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_INTERNAL_CORE_CENSUS_TAG_SET_H +#define GRPC_INTERNAL_CORE_CENSUS_TAG_SET_H + +#include +#include +#include + +/* Encode to-be-propagated tags from a tag set into a memory buffer. The total + number of bytes used in the buffer is returned. If the buffer is too small + to contain the encoded tag set, then 0 is returned. */ +size_t census_tag_set_encode_propagated(const census_tag_set *tags, + char *buffer, size_t buf_size); + +/* Encode to-be-propagated binary tags from a tag set into a memory + buffer. The total number of bytes used in the buffer is returned. If the + buffer is too small to contain the encoded tag set, then 0 is returned. */ +size_t census_tag_set_encode_propagated_binary(const census_tag_set *tags, + char *buffer, size_t buf_size); + +/* Decode tag set buffers encoded with census_tag_set_encode_*(). */ +census_tag_set *census_tag_set_decode(const char *buffer, size_t size, + const char *bin_buffer, size_t bin_size); + +#endif /* GRPC_INTERNAL_CORE_CENSUS_TAG_SET_H */ diff --git a/test/core/census/tag_set_test.c b/test/core/census/tag_set_test.c new file mode 100644 index 00000000000..d6a7e453346 --- /dev/null +++ b/test/core/census/tag_set_test.c @@ -0,0 +1,404 @@ +/* + * + * Copyright 2015-2016, 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. + * + */ + +// Test census_tag_set functions, including encoding/decoding + +#include "src/core/census/tag_set.h" +#include +#include +#include +#include +#include +#include +#include "test/core/util/test_config.h" + +static uint8_t one_byte_val = 7; +static uint32_t four_byte_val = 0x12345678; +static uint64_t eight_byte_val = 0x1234567890abcdef; + +// A set of tags Used to create a basic tag_set for testing. Each tag has a +// unique set of flags. Note that replace_add_delete_test() relies on specific +// offsets into this array - if you add or delete entries, you will also need +// to change the test. +#define BASIC_TAG_COUNT 8 +static census_tag basic_tags[BASIC_TAG_COUNT] = { + /* 0 */ {"key0", "printable", 10, 0}, + /* 1 */ {"k1", "a", 2, CENSUS_TAG_PROPAGATE}, + /* 2 */ {"k2", "longer printable string", 24, CENSUS_TAG_STATS}, + /* 3 */ {"key_three", (char *)&one_byte_val, 1, CENSUS_TAG_BINARY}, + /* 4 */ {"really_long_key_4", "random", 7, + CENSUS_TAG_PROPAGATE | CENSUS_TAG_STATS}, + /* 5 */ {"k5", (char *)&four_byte_val, 4, + CENSUS_TAG_PROPAGATE | CENSUS_TAG_BINARY}, + /* 6 */ {"k6", (char *)&eight_byte_val, 8, + CENSUS_TAG_STATS | CENSUS_TAG_BINARY}, + /* 7 */ {"k7", (char *)&four_byte_val, 4, + CENSUS_TAG_PROPAGATE | CENSUS_TAG_STATS | CENSUS_TAG_BINARY}}; + +// Set of tags used to modify the basic tag_set. Note that +// replace_add_delete_test() relies on specific offsets into this array - if +// you add or delete entries, you will also need to change the test. Other +// tests that rely on specific instances have XXX_XXX_OFFSET definitions (also +// change the defines below if you add/delete entires). +#define MODIFY_TAG_COUNT 10 +static census_tag modify_tags[MODIFY_TAG_COUNT] = { +#define REPLACE_VALUE_OFFSET 0 + /* 0 */ {"key0", "replace printable", 18, 0}, // replaces tag value only +#define ADD_TAG_OFFSET 1 + /* 1 */ {"new_key", "xyzzy", 6, CENSUS_TAG_STATS}, // new tag +#define DELETE_TAG_OFFSET 2 + /* 2 */ {"k5", NULL, 5, + 0}, // should delete tag, despite bogus value length + /* 3 */ {"k6", "foo", 0, 0}, // should delete tag, despite bogus value + /* 4 */ {"k6", "foo", 0, 0}, // try deleting already-deleted tag + /* 5 */ {"non-existent", NULL, 0, 0}, // another non-existent tag +#define REPLACE_FLAG_OFFSET 6 + /* 6 */ {"k1", "a", 2, 0}, // change flags only + /* 7 */ {"k7", "bar", 4, CENSUS_TAG_STATS}, // change flags and value + /* 8 */ {"k2", (char *)&eight_byte_val, 8, + CENSUS_TAG_BINARY | CENSUS_TAG_PROPAGATE}, // more flags change + // non-binary -> binary + /* 9 */ {"k6", "bar", 4, + 0} // add back tag, with different value, but same length +}; + +// Utility function to compare tags. Returns true if all fields match. +static bool compare_tag(const census_tag *t1, const census_tag *t2) { + return (strcmp(t1->key, t2->key) == 0 && t1->value_len == t2->value_len && + memcmp(t1->value, t2->value, t1->value_len) == 0 && + t1->flags == t2->flags); +} + +// Utility function to validate a tag exists in tag set. +static bool validate_tag(const census_tag_set *cts, const census_tag *tag) { + census_tag tag2; + if (census_tag_set_get_tag_by_key(cts, tag->key, &tag2) != 1) return false; + return compare_tag(tag, &tag2); +} + +// Create an empty tag_set. +static void empty_test(void) { + struct census_tag_set *cts = census_tag_set_create(NULL, NULL, 0); + GPR_ASSERT(census_tag_set_ntags(cts) == 0); + census_tag_set_destroy(cts); +} + +// Create basic tag set, and test that retreiving tag by index works. +static void basic_test(void) { + struct census_tag_set *cts = + census_tag_set_create(NULL, basic_tags, BASIC_TAG_COUNT); + GPR_ASSERT(census_tag_set_ntags(cts) == BASIC_TAG_COUNT); + for (int i = 0; i < census_tag_set_ntags(cts); i++) { + census_tag tag; + GPR_ASSERT(census_tag_set_get_tag_by_index(cts, i, &tag) == 1); + // can't rely on tag return order: make sure it matches exactly one. + int matches = 0; + for (int j = 0; j < BASIC_TAG_COUNT; j++) { + if (compare_tag(&tag, &basic_tags[j])) matches++; + } + GPR_ASSERT(matches == 1); + } + census_tag_set_destroy(cts); +} + +// Try census_tag_set_get_tag_by_index() with bad indices. +static void bad_index_test(void) { + struct census_tag_set *cts = + census_tag_set_create(NULL, basic_tags, BASIC_TAG_COUNT); + GPR_ASSERT(census_tag_set_ntags(cts) == BASIC_TAG_COUNT); + census_tag tag; + GPR_ASSERT(census_tag_set_get_tag_by_index(cts, -1, &tag) == 0); + GPR_ASSERT(census_tag_set_get_tag_by_index(cts, BASIC_TAG_COUNT, &tag) == 0); + GPR_ASSERT(census_tag_set_get_tag_by_index(cts, BASIC_TAG_COUNT + 1, &tag) == + 0); + census_tag_set_destroy(cts); +} + +// Test that census_tag_set_get_tag_by_key(). +static void lookup_by_key_test(void) { + struct census_tag_set *cts = + census_tag_set_create(NULL, basic_tags, BASIC_TAG_COUNT); + GPR_ASSERT(census_tag_set_ntags(cts) == BASIC_TAG_COUNT); + census_tag tag; + for (int i = 0; i < census_tag_set_ntags(cts); i++) { + GPR_ASSERT(census_tag_set_get_tag_by_key(cts, basic_tags[i].key, &tag) == + 1); + GPR_ASSERT(compare_tag(&tag, &basic_tags[i])); + } + // non-existent keys + GPR_ASSERT(census_tag_set_get_tag_by_key(cts, "key", &tag) == 0); + GPR_ASSERT(census_tag_set_get_tag_by_key(cts, "key01", &tag) == 0); + GPR_ASSERT(census_tag_set_get_tag_by_key(cts, "k9", &tag) == 0); + GPR_ASSERT(census_tag_set_get_tag_by_key(cts, "random", &tag) == 0); + GPR_ASSERT(census_tag_set_get_tag_by_key(cts, "", &tag) == 0); + census_tag_set_destroy(cts); +} + +// Try creating tag set with invalid entries. +static void invalid_test(void) { + char key[300]; + memset(key, 'k', 299); + key[299] = 0; + char value[300]; + memset(value, 'v', 300); + census_tag tag = {key, value, 3, CENSUS_TAG_BINARY}; + // long keys, short value. Key lengths (including terminator) should be + // <= 255 (CENSUS_MAX_TAG_KV_LEN) + GPR_ASSERT(strlen(key) == 299); + struct census_tag_set *cts = census_tag_set_create(NULL, &tag, 1); + GPR_ASSERT(census_tag_set_ntags(cts) == 0); + census_tag_set_destroy(cts); + key[CENSUS_MAX_TAG_KV_LEN] = 0; + GPR_ASSERT(strlen(key) == CENSUS_MAX_TAG_KV_LEN); + cts = census_tag_set_create(NULL, &tag, 1); + GPR_ASSERT(census_tag_set_ntags(cts) == 0); + census_tag_set_destroy(cts); + key[CENSUS_MAX_TAG_KV_LEN - 1] = 0; + GPR_ASSERT(strlen(key) == CENSUS_MAX_TAG_KV_LEN - 1); + cts = census_tag_set_create(NULL, &tag, 1); + GPR_ASSERT(census_tag_set_ntags(cts) == 1); + census_tag_set_destroy(cts); + // now try with long values + tag.value_len = 300; + cts = census_tag_set_create(NULL, &tag, 1); + GPR_ASSERT(census_tag_set_ntags(cts) == 0); + census_tag_set_destroy(cts); + tag.value_len = CENSUS_MAX_TAG_KV_LEN + 1; + cts = census_tag_set_create(NULL, &tag, 1); + GPR_ASSERT(census_tag_set_ntags(cts) == 0); + census_tag_set_destroy(cts); + tag.value_len = CENSUS_MAX_TAG_KV_LEN; + cts = census_tag_set_create(NULL, &tag, 1); + GPR_ASSERT(census_tag_set_ntags(cts) == 1); + census_tag_set_destroy(cts); + // 0 length key. + key[0] = 0; + cts = census_tag_set_create(NULL, &tag, 1); + GPR_ASSERT(census_tag_set_ntags(cts) == 0); + census_tag_set_destroy(cts); +} + +// Make a copy of a tag set +static void copy_test(void) { + struct census_tag_set *cts = + census_tag_set_create(NULL, basic_tags, BASIC_TAG_COUNT); + GPR_ASSERT(census_tag_set_ntags(cts) == BASIC_TAG_COUNT); + struct census_tag_set *cts2 = census_tag_set_create(cts, NULL, 0); + GPR_ASSERT(census_tag_set_ntags(cts2) == BASIC_TAG_COUNT); + for (int i = 0; i < census_tag_set_ntags(cts2); i++) { + census_tag tag; + GPR_ASSERT(census_tag_set_get_tag_by_key(cts2, basic_tags[i].key, &tag) == + 1); + GPR_ASSERT(compare_tag(&tag, &basic_tags[i])); + } + census_tag_set_destroy(cts); + census_tag_set_destroy(cts2); +} + +// replace a single tag value +static void replace_value_test(void) { + struct census_tag_set *cts = + census_tag_set_create(NULL, basic_tags, BASIC_TAG_COUNT); + GPR_ASSERT(census_tag_set_ntags(cts) == BASIC_TAG_COUNT); + struct census_tag_set *cts2 = + census_tag_set_create(cts, modify_tags + REPLACE_VALUE_OFFSET, 1); + GPR_ASSERT(census_tag_set_ntags(cts2) == BASIC_TAG_COUNT); + census_tag tag; + GPR_ASSERT(census_tag_set_get_tag_by_key( + cts2, modify_tags[REPLACE_VALUE_OFFSET].key, &tag) == 1); + GPR_ASSERT(compare_tag(&tag, &modify_tags[REPLACE_VALUE_OFFSET])); + census_tag_set_destroy(cts); + census_tag_set_destroy(cts2); +} + +// replace a single tags flags +static void replace_flags_test(void) { + struct census_tag_set *cts = + census_tag_set_create(NULL, basic_tags, BASIC_TAG_COUNT); + GPR_ASSERT(census_tag_set_ntags(cts) == BASIC_TAG_COUNT); + struct census_tag_set *cts2 = + census_tag_set_create(cts, modify_tags + REPLACE_FLAG_OFFSET, 1); + GPR_ASSERT(census_tag_set_ntags(cts2) == BASIC_TAG_COUNT); + census_tag tag; + GPR_ASSERT(census_tag_set_get_tag_by_key( + cts2, modify_tags[REPLACE_FLAG_OFFSET].key, &tag) == 1); + GPR_ASSERT(compare_tag(&tag, &modify_tags[REPLACE_FLAG_OFFSET])); + census_tag_set_destroy(cts); + census_tag_set_destroy(cts2); +} + +// delete a single tag. +static void delete_tag_test(void) { + struct census_tag_set *cts = + census_tag_set_create(NULL, basic_tags, BASIC_TAG_COUNT); + GPR_ASSERT(census_tag_set_ntags(cts) == BASIC_TAG_COUNT); + struct census_tag_set *cts2 = + census_tag_set_create(cts, modify_tags + DELETE_TAG_OFFSET, 1); + GPR_ASSERT(census_tag_set_ntags(cts2) == BASIC_TAG_COUNT - 1); + census_tag tag; + GPR_ASSERT(census_tag_set_get_tag_by_key( + cts2, modify_tags[DELETE_TAG_OFFSET].key, &tag) == 0); + census_tag_set_destroy(cts); + census_tag_set_destroy(cts2); +} + +// add a single new tag. +static void add_tag_test(void) { + struct census_tag_set *cts = + census_tag_set_create(NULL, basic_tags, BASIC_TAG_COUNT); + GPR_ASSERT(census_tag_set_ntags(cts) == BASIC_TAG_COUNT); + struct census_tag_set *cts2 = + census_tag_set_create(cts, modify_tags + ADD_TAG_OFFSET, 1); + GPR_ASSERT(census_tag_set_ntags(cts2) == BASIC_TAG_COUNT + 1); + census_tag tag; + GPR_ASSERT(census_tag_set_get_tag_by_key( + cts2, modify_tags[ADD_TAG_OFFSET].key, &tag) == 1); + GPR_ASSERT(compare_tag(&tag, &modify_tags[ADD_TAG_OFFSET])); + census_tag_set_destroy(cts); + census_tag_set_destroy(cts2); +} + +// test many changes at once. +static void replace_add_delete_test(void) { + struct census_tag_set *cts = + census_tag_set_create(NULL, basic_tags, BASIC_TAG_COUNT); + GPR_ASSERT(census_tag_set_ntags(cts) == BASIC_TAG_COUNT); + struct census_tag_set *cts2 = + census_tag_set_create(cts, modify_tags, MODIFY_TAG_COUNT); + GPR_ASSERT(census_tag_set_ntags(cts2) == 8); + // validate tag set contents. Use specific indices into the two arrays + // holding tag values. + GPR_ASSERT(validate_tag(cts2, &basic_tags[3])); + GPR_ASSERT(validate_tag(cts2, &basic_tags[4])); + GPR_ASSERT(validate_tag(cts2, &modify_tags[0])); + GPR_ASSERT(validate_tag(cts2, &modify_tags[1])); + GPR_ASSERT(validate_tag(cts2, &modify_tags[6])); + GPR_ASSERT(validate_tag(cts2, &modify_tags[7])); + GPR_ASSERT(validate_tag(cts2, &modify_tags[8])); + GPR_ASSERT(validate_tag(cts2, &modify_tags[9])); + GPR_ASSERT(!validate_tag(cts2, &basic_tags[0])); + GPR_ASSERT(!validate_tag(cts2, &basic_tags[1])); + GPR_ASSERT(!validate_tag(cts2, &basic_tags[2])); + GPR_ASSERT(!validate_tag(cts2, &basic_tags[5])); + GPR_ASSERT(!validate_tag(cts2, &basic_tags[6])); + GPR_ASSERT(!validate_tag(cts2, &basic_tags[7])); + census_tag_set_destroy(cts); + census_tag_set_destroy(cts2); +} + +// Use the basic tag set to test encode/decode. +static void simple_encode_decode_test(void) { + char buf1[1000]; + char buf2[1000]; + struct census_tag_set *cts = + census_tag_set_create(NULL, basic_tags, BASIC_TAG_COUNT); + GPR_ASSERT(census_tag_set_ntags(cts) == BASIC_TAG_COUNT); + GPR_ASSERT(census_tag_set_encode_propagated(cts, buf1, 1) == 0); + size_t b1 = census_tag_set_encode_propagated(cts, buf1, 1000); + GPR_ASSERT(b1 != 0); + GPR_ASSERT(census_tag_set_encode_propagated_binary(cts, buf2, 1) == 0); + size_t b2 = census_tag_set_encode_propagated_binary(cts, buf2, 1000); + GPR_ASSERT(b2 != 0); + census_tag_set *cts2 = census_tag_set_decode(buf1, b1, buf2, b2); + GPR_ASSERT(cts2 != NULL); + GPR_ASSERT(census_tag_set_ntags(cts2) == 4); + for (int i = 0; i < census_tag_set_ntags(cts); i++) { + census_tag tag; + if (CENSUS_TAG_IS_PROPAGATED(basic_tags[i].flags)) { + GPR_ASSERT(census_tag_set_get_tag_by_key(cts2, basic_tags[i].key, &tag) == + 1); + GPR_ASSERT(compare_tag(&tag, &basic_tags[i])); + } else { + GPR_ASSERT(census_tag_set_get_tag_by_key(cts2, basic_tags[i].key, &tag) == + 0); + } + } + census_tag_set_destroy(cts2); + census_tag_set_destroy(cts); +} + +// Use more complex/modified tag set to test encode/decode. +static void complex_encode_decode_test(void) { + char buf1[500]; + char buf2[500]; + struct census_tag_set *cts = + census_tag_set_create(NULL, basic_tags, BASIC_TAG_COUNT); + GPR_ASSERT(census_tag_set_ntags(cts) == BASIC_TAG_COUNT); + struct census_tag_set *cts2 = + census_tag_set_create(cts, modify_tags, MODIFY_TAG_COUNT); + GPR_ASSERT(census_tag_set_ntags(cts2) == 8); + + size_t b1 = census_tag_set_encode_propagated(cts2, buf1, 500); + GPR_ASSERT(b1 != 0); + size_t b2 = census_tag_set_encode_propagated_binary(cts2, buf2, 500); + GPR_ASSERT(b2 != 0); + census_tag_set *cts3 = census_tag_set_decode(buf1, b1, buf2, b2); + GPR_ASSERT(cts3 != NULL); + GPR_ASSERT(census_tag_set_ntags(cts3) == 2); + GPR_ASSERT(validate_tag(cts3, &basic_tags[4])); + GPR_ASSERT(validate_tag(cts3, &modify_tags[8])); + // Now force tag set to be in smaller space + census_tag_set_destroy(cts3); + size_t nb1 = census_tag_set_encode_propagated(cts2, buf1, b1 - 1); + GPR_ASSERT(nb1 != 0); + GPR_ASSERT(nb1 < b1); + size_t nb2 = census_tag_set_encode_propagated_binary(cts2, buf2, b2 - 1); + GPR_ASSERT(nb2 != 0); + GPR_ASSERT(nb2 < b2); + cts3 = census_tag_set_decode(buf1, nb1, buf2, nb2); + GPR_ASSERT(cts3 != NULL); + GPR_ASSERT(census_tag_set_ntags(cts3) == 2); + GPR_ASSERT(validate_tag(cts3, &basic_tags[4])); + GPR_ASSERT(validate_tag(cts3, &modify_tags[8])); + census_tag_set_destroy(cts3); + census_tag_set_destroy(cts2); + census_tag_set_destroy(cts); +} + +int main(int argc, char *argv[]) { + grpc_test_init(argc, argv); + empty_test(); + basic_test(); + bad_index_test(); + lookup_by_key_test(); + invalid_test(); + copy_test(); + replace_value_test(); + replace_flags_test(); + delete_tag_test(); + add_tag_test(); + replace_add_delete_test(); + simple_encode_decode_test(); + complex_encode_decode_test(); + return 0; +} diff --git a/tools/doxygen/Doxyfile.core.internal b/tools/doxygen/Doxyfile.core.internal index aef5bec86b0..ac57dd03b7e 100644 --- a/tools/doxygen/Doxyfile.core.internal +++ b/tools/doxygen/Doxyfile.core.internal @@ -897,6 +897,7 @@ src/core/transport/transport_impl.h \ src/core/census/aggregation.h \ src/core/census/context.h \ src/core/census/rpc_metric_id.h \ +src/core/census/tag_set.h \ src/core/httpcli/httpcli_security_connector.c \ src/core/security/base64.c \ src/core/security/client_auth_filter.c \ @@ -1046,6 +1047,7 @@ src/core/transport/transport_op_string.c \ src/core/census/context.c \ src/core/census/initialize.c \ src/core/census/operation.c \ +src/core/census/tag_set.c \ src/core/census/tracing.c \ include/grpc/support/alloc.h \ include/grpc/support/atm.h \ diff --git a/tools/run_tests/sources_and_headers.json b/tools/run_tests/sources_and_headers.json index a1beafd41e1..529a4173ead 100644 --- a/tools/run_tests/sources_and_headers.json +++ b/tools/run_tests/sources_and_headers.json @@ -1089,6 +1089,20 @@ "test/core/iomgr/socket_utils_test.c" ] }, + { + "deps": [ + "gpr", + "gpr_test_util", + "grpc", + "grpc_test_util" + ], + "headers": [], + "language": "c", + "name": "tag_set_test", + "src": [ + "test/core/census/tag_set_test.c" + ] + }, { "deps": [ "gpr", @@ -2845,6 +2859,7 @@ "src/core/census/context.h", "src/core/census/grpc_filter.h", "src/core/census/rpc_metric_id.h", + "src/core/census/tag_set.h", "src/core/channel/channel_args.h", "src/core/channel/channel_stack.h", "src/core/channel/client_channel.h", @@ -2991,6 +3006,8 @@ "src/core/census/initialize.c", "src/core/census/operation.c", "src/core/census/rpc_metric_id.h", + "src/core/census/tag_set.c", + "src/core/census/tag_set.h", "src/core/census/tracing.c", "src/core/channel/channel_args.c", "src/core/channel/channel_args.h", @@ -3357,6 +3374,7 @@ "src/core/census/context.h", "src/core/census/grpc_filter.h", "src/core/census/rpc_metric_id.h", + "src/core/census/tag_set.h", "src/core/channel/channel_args.h", "src/core/channel/channel_stack.h", "src/core/channel/client_channel.h", @@ -3488,6 +3506,8 @@ "src/core/census/initialize.c", "src/core/census/operation.c", "src/core/census/rpc_metric_id.h", + "src/core/census/tag_set.c", + "src/core/census/tag_set.h", "src/core/census/tracing.c", "src/core/channel/channel_args.c", "src/core/channel/channel_args.h", diff --git a/tools/run_tests/tests.json b/tools/run_tests/tests.json index 0b5f847af18..a8398eb8606 100644 --- a/tools/run_tests/tests.json +++ b/tools/run_tests/tests.json @@ -1328,6 +1328,25 @@ "posix" ] }, + { + "args": [], + "ci_platforms": [ + "linux", + "mac", + "posix", + "windows" + ], + "exclude_configs": [], + "flaky": false, + "language": "c", + "name": "tag_set_test", + "platforms": [ + "linux", + "mac", + "posix", + "windows" + ] + }, { "args": [], "ci_platforms": [ diff --git a/vsprojects/buildtests_c.sln b/vsprojects/buildtests_c.sln index 06e882ee95b..592b3984b6a 100644 --- a/vsprojects/buildtests_c.sln +++ b/vsprojects/buildtests_c.sln @@ -848,6 +848,17 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sockaddr_utils_test", "vcxp {B23D3D1A-9438-4EDA-BEB6-9A0A03D17792} = {B23D3D1A-9438-4EDA-BEB6-9A0A03D17792} EndProjectSection EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tag_set_test", "vcxproj\test\tag_set_test\tag_set_test.vcxproj", "{430F8F07-6AAD-0150-B35B-DB9E2E21941A}" + ProjectSection(myProperties) = preProject + lib = "False" + EndProjectSection + ProjectSection(ProjectDependencies) = postProject + {17BCAFC0-5FDC-4C94-AEB9-95F3E220614B} = {17BCAFC0-5FDC-4C94-AEB9-95F3E220614B} + {29D16885-7228-4C31-81ED-5F9187C7F2A9} = {29D16885-7228-4C31-81ED-5F9187C7F2A9} + {EAB0A629-17A9-44DB-B5FF-E91A721FE037} = {EAB0A629-17A9-44DB-B5FF-E91A721FE037} + {B23D3D1A-9438-4EDA-BEB6-9A0A03D17792} = {B23D3D1A-9438-4EDA-BEB6-9A0A03D17792} + EndProjectSection +EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "time_averaged_stats_test", "vcxproj\test\time_averaged_stats_test\time_averaged_stats_test.vcxproj", "{D1EB2A9B-8508-62D7-8FC4-11A11B1CBFD3}" ProjectSection(myProperties) = preProject lib = "False" @@ -2624,6 +2635,22 @@ Global {529771F0-10B0-9B1A-1E7E-8A8E01870348}.Release-DLL|Win32.Build.0 = Release|Win32 {529771F0-10B0-9B1A-1E7E-8A8E01870348}.Release-DLL|x64.ActiveCfg = Release|x64 {529771F0-10B0-9B1A-1E7E-8A8E01870348}.Release-DLL|x64.Build.0 = Release|x64 + {430F8F07-6AAD-0150-B35B-DB9E2E21941A}.Debug|Win32.ActiveCfg = Debug|Win32 + {430F8F07-6AAD-0150-B35B-DB9E2E21941A}.Debug|x64.ActiveCfg = Debug|x64 + {430F8F07-6AAD-0150-B35B-DB9E2E21941A}.Release|Win32.ActiveCfg = Release|Win32 + {430F8F07-6AAD-0150-B35B-DB9E2E21941A}.Release|x64.ActiveCfg = Release|x64 + {430F8F07-6AAD-0150-B35B-DB9E2E21941A}.Debug|Win32.Build.0 = Debug|Win32 + {430F8F07-6AAD-0150-B35B-DB9E2E21941A}.Debug|x64.Build.0 = Debug|x64 + {430F8F07-6AAD-0150-B35B-DB9E2E21941A}.Release|Win32.Build.0 = Release|Win32 + {430F8F07-6AAD-0150-B35B-DB9E2E21941A}.Release|x64.Build.0 = Release|x64 + {430F8F07-6AAD-0150-B35B-DB9E2E21941A}.Debug-DLL|Win32.ActiveCfg = Debug|Win32 + {430F8F07-6AAD-0150-B35B-DB9E2E21941A}.Debug-DLL|Win32.Build.0 = Debug|Win32 + {430F8F07-6AAD-0150-B35B-DB9E2E21941A}.Debug-DLL|x64.ActiveCfg = Debug|x64 + {430F8F07-6AAD-0150-B35B-DB9E2E21941A}.Debug-DLL|x64.Build.0 = Debug|x64 + {430F8F07-6AAD-0150-B35B-DB9E2E21941A}.Release-DLL|Win32.ActiveCfg = Release|Win32 + {430F8F07-6AAD-0150-B35B-DB9E2E21941A}.Release-DLL|Win32.Build.0 = Release|Win32 + {430F8F07-6AAD-0150-B35B-DB9E2E21941A}.Release-DLL|x64.ActiveCfg = Release|x64 + {430F8F07-6AAD-0150-B35B-DB9E2E21941A}.Release-DLL|x64.Build.0 = Release|x64 {D1EB2A9B-8508-62D7-8FC4-11A11B1CBFD3}.Debug|Win32.ActiveCfg = Debug|Win32 {D1EB2A9B-8508-62D7-8FC4-11A11B1CBFD3}.Debug|x64.ActiveCfg = Debug|x64 {D1EB2A9B-8508-62D7-8FC4-11A11B1CBFD3}.Release|Win32.ActiveCfg = Release|Win32 diff --git a/vsprojects/vcxproj/grpc/grpc.vcxproj b/vsprojects/vcxproj/grpc/grpc.vcxproj index 9d646153e28..037b8ff2224 100644 --- a/vsprojects/vcxproj/grpc/grpc.vcxproj +++ b/vsprojects/vcxproj/grpc/grpc.vcxproj @@ -404,6 +404,7 @@ + @@ -704,6 +705,8 @@ + + diff --git a/vsprojects/vcxproj/grpc/grpc.vcxproj.filters b/vsprojects/vcxproj/grpc/grpc.vcxproj.filters index 055256a7e81..0ddbd5e46d5 100644 --- a/vsprojects/vcxproj/grpc/grpc.vcxproj.filters +++ b/vsprojects/vcxproj/grpc/grpc.vcxproj.filters @@ -448,6 +448,9 @@ src\core\census + + src\core\census + src\core\census @@ -866,6 +869,9 @@ src\core\census + + src\core\census + diff --git a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj index b2965212bb4..a3532e5d05e 100644 --- a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj +++ b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj @@ -382,6 +382,7 @@ + @@ -642,6 +643,8 @@ + + diff --git a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters index e30ca5f685d..f63e3012e75 100644 --- a/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters +++ b/vsprojects/vcxproj/grpc_unsecure/grpc_unsecure.vcxproj.filters @@ -388,6 +388,9 @@ src\core\census + + src\core\census + src\core\census @@ -761,6 +764,9 @@ src\core\census + + src\core\census + diff --git a/vsprojects/vcxproj/test/tag_set_test/tag_set_test.vcxproj b/vsprojects/vcxproj/test/tag_set_test/tag_set_test.vcxproj new file mode 100644 index 00000000000..38949026726 --- /dev/null +++ b/vsprojects/vcxproj/test/tag_set_test/tag_set_test.vcxproj @@ -0,0 +1,197 @@ + + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {430F8F07-6AAD-0150-B35B-DB9E2E21941A} + true + $(SolutionDir)IntDir\$(MSBuildProjectName)\ + + + + v100 + + + v110 + + + v120 + + + v140 + + + Application + true + Unicode + + + Application + false + true + Unicode + + + + + + + + + + + + + + tag_set_test + static + Debug + Debug + + + tag_set_test + static + Release + Release + + + + NotUsing + Level3 + Disabled + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + MultiThreadedDebug + true + None + false + + + Console + true + false + + + + + + NotUsing + Level3 + Disabled + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + MultiThreadedDebug + true + None + false + + + Console + true + false + + + + + + NotUsing + Level3 + MaxSpeed + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + true + true + MultiThreaded + true + None + false + + + Console + true + false + true + true + + + + + + NotUsing + Level3 + MaxSpeed + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + true + true + MultiThreaded + true + None + false + + + Console + true + false + true + true + + + + + + + + + + {17BCAFC0-5FDC-4C94-AEB9-95F3E220614B} + + + {29D16885-7228-4C31-81ED-5F9187C7F2A9} + + + {EAB0A629-17A9-44DB-B5FF-E91A721FE037} + + + {B23D3D1A-9438-4EDA-BEB6-9A0A03D17792} + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + + + diff --git a/vsprojects/vcxproj/test/tag_set_test/tag_set_test.vcxproj.filters b/vsprojects/vcxproj/test/tag_set_test/tag_set_test.vcxproj.filters new file mode 100644 index 00000000000..6b315322519 --- /dev/null +++ b/vsprojects/vcxproj/test/tag_set_test/tag_set_test.vcxproj.filters @@ -0,0 +1,21 @@ + + + + + test\core\census + + + + + + {500aa440-5924-8047-996a-4c5096d1ef96} + + + {a3bf80f0-5b13-f623-277b-05f0231dd933} + + + {b6ed1b86-7795-4da9-a169-9eccf836852c} + + + +