Index: third_party/grpc/src/core/census/context.c |
diff --git a/third_party/grpc/src/core/census/context.c b/third_party/grpc/src/core/census/context.c |
new file mode 100644 |
index 0000000000000000000000000000000000000000..89b8ee0b399dbb7a403f07075b13b9da9664c545 |
--- /dev/null |
+++ b/third_party/grpc/src/core/census/context.c |
@@ -0,0 +1,509 @@ |
+/* |
+ * |
+ * 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. |
+ * |
+ */ |
+ |
+#include <grpc/census.h> |
+#include <grpc/support/alloc.h> |
+#include <grpc/support/log.h> |
+#include <grpc/support/port_platform.h> |
+#include <grpc/support/useful.h> |
+#include <stdbool.h> |
+#include <string.h> |
+#include "src/core/support/string.h" |
+ |
+// Functions in this file support the public context API, including |
+// encoding/decoding as part of context propagation across RPC's. The overall |
+// requirements (in approximate priority order) for the |
+// context representation: |
+// 1. Efficient conversion to/from wire format |
+// 2. Minimal bytes used on-wire |
+// 3. Efficient context creation |
+// 4. Efficient lookup of tag value for a key |
+// 5. Efficient iteration over tags |
+// 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. It also means there is a hard upper |
+// limit of 255 for both CENSUS_MAX_TAG_KV_LEN and CENSUS_MAX_PROPAGATED_TAGS. |
+// * Keep all tag information (keys/values/flags) in a single memory buffer, |
+// that can be directly copied to the wire. |
+ |
+// min and max valid chars in tag keys and values. All printable ASCII is OK. |
+#define MIN_VALID_TAG_CHAR 32 // ' ' |
+#define MAX_VALID_TAG_CHAR 126 // '~' |
+ |
+// 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). This will always be == ntags, except during the process |
+ // of building a new tag set. |
+ 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, including trailing 0 (V) |
+ // 2 1 Flags |
+ // 3 K Key bytes |
+ // 3 + K V Value bytes |
+ // |
+ // We refer to the first 3 entries as the 'tag header'. If extra values are |
+ // introduced in the header, you will need to modify the TAG_HEADER_SIZE |
+ // constant, the raw_tag structure (and everything that uses it) and the |
+ // encode/decode functions appropriately. |
+}; |
+ |
+// 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 a 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 representation of a context. Composed of 2 underlying tag_set |
+// structs, one each for propagated and local (non-propagated) tags. This is |
+// to efficiently support tag encoding/decoding. |
+// TODO(aveitch): need to add tracing id's/structure. |
+struct census_context { |
+ struct tag_set tags[2]; |
+ census_context_status status; |
+}; |
+ |
+// Indices into the tags member of census_context |
+#define PROPAGATED_TAGS 0 |
+#define LOCAL_TAGS 1 |
+ |
+// Validate (check all characters are in range and size is less than limit) a |
+// key or value string. Returns 0 if the string is invalid, or the length |
+// (including terminator) if valid. |
+static size_t validate_tag(const char *kv) { |
+ size_t len = 1; |
+ char ch; |
+ while ((ch = *kv++) != 0) { |
+ if (ch < MIN_VALID_TAG_CHAR || ch > MAX_VALID_TAG_CHAR) { |
+ return 0; |
+ } |
+ len++; |
+ } |
+ if (len > CENSUS_MAX_TAG_KV_LEN) { |
+ return 0; |
+ } |
+ return len; |
+} |
+ |
+// Extract a raw tag given a pointer (raw) to the tag header. Allow for some |
+// extra bytes in the tag header (see encode/decode functions for usage: this |
+// 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, from->kvm_used); |
+} |
+ |
+// Delete a tag from a tag_set, if it exists (returns true if it did). |
+static bool tag_set_delete_tag(struct tag_set *tags, const char *key, |
+ size_t key_len) { |
+ char *kvp = tags->kvm; |
+ for (int i = 0; i < tags->ntags_alloc; i++) { |
+ uint8_t *flags = (uint8_t *)(kvp + FLAG_OFFSET); |
+ struct raw_tag tag; |
+ kvp = decode_tag(&tag, kvp, 0); |
+ if (CENSUS_TAG_IS_DELETED(tag.flags)) continue; |
+ if ((key_len == tag.key_len) && (memcmp(key, tag.key, key_len) == 0)) { |
+ *flags |= CENSUS_TAG_DELETED; |
+ tags->ntags--; |
+ return true; |
+ } |
+ } |
+ return false; |
+} |
+ |
+// Delete a tag from a context, return true if it existed. |
+static bool context_delete_tag(census_context *context, const census_tag *tag, |
+ size_t key_len) { |
+ return ( |
+ tag_set_delete_tag(&context->tags[LOCAL_TAGS], tag->key, key_len) || |
+ tag_set_delete_tag(&context->tags[PROPAGATED_TAGS], tag->key, key_len)); |
+} |
+ |
+// Add a tag to a tag_set. Return true on success, false if the tag could |
+// not be added because of constraints on tag set size. This function should |
+// not be called if the tag may already exist (in a non-deleted state) in |
+// the tag_set, as that would result in two tags with the same key. |
+static bool tag_set_add_tag(struct tag_set *tags, const census_tag *tag, |
+ size_t key_len, size_t value_len) { |
+ if (tags->ntags == CENSUS_MAX_PROPAGATED_TAGS) { |
+ return false; |
+ } |
+ const size_t tag_size = key_len + value_len + TAG_HEADER_SIZE; |
+ if (tags->kvm_used + tag_size > tags->kvm_size) { |
+ // allocate new memory if needed |
+ 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)value_len; |
+ // ensure reserved flags are not used. |
+ *kvp++ = (char)(tag->flags & (CENSUS_TAG_PROPAGATE | CENSUS_TAG_STATS)); |
+ memcpy(kvp, tag->key, key_len); |
+ kvp += key_len; |
+ memcpy(kvp, tag->value, value_len); |
+ tags->kvm_used += tag_size; |
+ tags->ntags++; |
+ tags->ntags_alloc++; |
+ return true; |
+} |
+ |
+// Add/modify/delete a tag to/in a context. Caller must validate that tag key |
+// etc. are valid. |
+static void context_modify_tag(census_context *context, const census_tag *tag, |
+ size_t key_len, size_t value_len) { |
+ // First delete the tag if it is already present. |
+ bool deleted = context_delete_tag(context, tag, key_len); |
+ bool added = false; |
+ if (CENSUS_TAG_IS_PROPAGATED(tag->flags)) { |
+ added = tag_set_add_tag(&context->tags[PROPAGATED_TAGS], tag, key_len, |
+ value_len); |
+ } else { |
+ added = |
+ tag_set_add_tag(&context->tags[LOCAL_TAGS], tag, key_len, value_len); |
+ } |
+ |
+ if (deleted) { |
+ context->status.n_modified_tags++; |
+ } else { |
+ if (added) { |
+ context->status.n_added_tags++; |
+ } else { |
+ context->status.n_ignored_tags++; |
+ } |
+ } |
+} |
+ |
+// Remove memory used for deleted tags from a tag set. Basic algorithm: |
+// 1) Walk through tag set to find first deleted tag. Record where it is. |
+// 2) Find the next not-deleted tag. Copy all of kvm from there to the end |
+// "over" the deleted tags |
+// 3) repeat #1 and #2 until we have seen all tags |
+// 4) if we are still looking for a not-deleted tag, then all the end portion |
+// of the kvm is deleted. Just reduce the used amount of memory by the |
+// appropriate amount. |
+static void tag_set_flatten(struct tag_set *tags) { |
+ if (tags->ntags == tags->ntags_alloc) return; |
+ bool found_deleted = false; // found a deleted tag. |
+ char *kvp = tags->kvm; |
+ char *dbase = NULL; // record location of deleted tag |
+ for (int i = 0; i < tags->ntags_alloc; i++) { |
+ struct raw_tag tag; |
+ char *next_kvp = decode_tag(&tag, kvp, 0); |
+ if (found_deleted) { |
+ if (!CENSUS_TAG_IS_DELETED(tag.flags)) { |
+ ptrdiff_t reduce = kvp - dbase; // #bytes in deleted tags |
+ GPR_ASSERT(reduce > 0); |
+ ptrdiff_t copy_size = tags->kvm + tags->kvm_used - kvp; |
+ GPR_ASSERT(copy_size > 0); |
+ memmove(dbase, kvp, (size_t)copy_size); |
+ tags->kvm_used -= (size_t)reduce; |
+ next_kvp -= reduce; |
+ found_deleted = false; |
+ } |
+ } else { |
+ if (CENSUS_TAG_IS_DELETED(tag.flags)) { |
+ dbase = kvp; |
+ found_deleted = true; |
+ } |
+ } |
+ kvp = next_kvp; |
+ } |
+ if (found_deleted) { |
+ GPR_ASSERT(dbase > tags->kvm); |
+ tags->kvm_used = (size_t)(dbase - tags->kvm); |
+ } |
+ tags->ntags_alloc = tags->ntags; |
+} |
+ |
+census_context *census_context_create(const census_context *base, |
+ const census_tag *tags, int ntags, |
+ census_context_status const **status) { |
+ census_context *context = gpr_malloc(sizeof(census_context)); |
+ // If we are given a base, copy it into our new tag set. Otherwise set it |
+ // to zero/NULL everything. |
+ if (base == NULL) { |
+ memset(context, 0, sizeof(census_context)); |
+ } else { |
+ tag_set_copy(&context->tags[PROPAGATED_TAGS], &base->tags[PROPAGATED_TAGS]); |
+ tag_set_copy(&context->tags[LOCAL_TAGS], &base->tags[LOCAL_TAGS]); |
+ memset(&context->status, 0, sizeof(context->status)); |
+ } |
+ // Walk over the additional tags and, for those that aren't invalid, modify |
+ // the context to add/replace/delete as required. |
+ for (int i = 0; i < ntags; i++) { |
+ const census_tag *tag = &tags[i]; |
+ size_t key_len = validate_tag(tag->key); |
+ // ignore the tag if it is invalid or too short. |
+ if (key_len <= 1) { |
+ context->status.n_invalid_tags++; |
+ } else { |
+ if (tag->value != NULL) { |
+ size_t value_len = validate_tag(tag->value); |
+ if (value_len != 0) { |
+ context_modify_tag(context, tag, key_len, value_len); |
+ } else { |
+ context->status.n_invalid_tags++; |
+ } |
+ } else { |
+ if (context_delete_tag(context, tag, key_len)) { |
+ context->status.n_deleted_tags++; |
+ } |
+ } |
+ } |
+ } |
+ // Remove any deleted tags, update status if needed, and return. |
+ tag_set_flatten(&context->tags[PROPAGATED_TAGS]); |
+ tag_set_flatten(&context->tags[LOCAL_TAGS]); |
+ context->status.n_propagated_tags = context->tags[PROPAGATED_TAGS].ntags; |
+ context->status.n_local_tags = context->tags[LOCAL_TAGS].ntags; |
+ if (status) { |
+ *status = &context->status; |
+ } |
+ return context; |
+} |
+ |
+const census_context_status *census_context_get_status( |
+ const census_context *context) { |
+ return &context->status; |
+} |
+ |
+void census_context_destroy(census_context *context) { |
+ gpr_free(context->tags[PROPAGATED_TAGS].kvm); |
+ gpr_free(context->tags[LOCAL_TAGS].kvm); |
+ gpr_free(context); |
+} |
+ |
+void census_context_initialize_iterator(const census_context *context, |
+ census_context_iterator *iterator) { |
+ iterator->context = context; |
+ iterator->index = 0; |
+ if (context->tags[PROPAGATED_TAGS].ntags != 0) { |
+ iterator->base = PROPAGATED_TAGS; |
+ iterator->kvm = context->tags[PROPAGATED_TAGS].kvm; |
+ } else if (context->tags[LOCAL_TAGS].ntags != 0) { |
+ iterator->base = LOCAL_TAGS; |
+ iterator->kvm = context->tags[LOCAL_TAGS].kvm; |
+ } else { |
+ iterator->base = -1; |
+ } |
+} |
+ |
+int census_context_next_tag(census_context_iterator *iterator, |
+ census_tag *tag) { |
+ if (iterator->base < 0) { |
+ return 0; |
+ } |
+ struct raw_tag raw; |
+ iterator->kvm = decode_tag(&raw, iterator->kvm, 0); |
+ tag->key = raw.key; |
+ tag->value = raw.value; |
+ tag->flags = raw.flags; |
+ if (++iterator->index == iterator->context->tags[iterator->base].ntags) { |
+ do { |
+ if (iterator->base == LOCAL_TAGS) { |
+ iterator->base = -1; |
+ return 1; |
+ } |
+ } while (iterator->context->tags[++iterator->base].ntags == 0); |
+ iterator->index = 0; |
+ iterator->kvm = iterator->context->tags[iterator->base].kvm; |
+ } |
+ return 1; |
+} |
+ |
+// Find a tag in a tag_set by key. Return true if found, false otherwise. |
+static bool tag_set_get_tag(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; |
+ kvp = decode_tag(&raw, kvp, 0); |
+ if (key_len == raw.key_len && memcmp(raw.key, key, key_len) == 0) { |
+ tag->key = raw.key; |
+ tag->value = raw.value; |
+ tag->flags = raw.flags; |
+ return true; |
+ } |
+ } |
+ return false; |
+} |
+ |
+int census_context_get_tag(const census_context *context, const char *key, |
+ census_tag *tag) { |
+ size_t key_len = strlen(key) + 1; |
+ if (key_len == 1) { |
+ return 0; |
+ } |
+ if (tag_set_get_tag(&context->tags[PROPAGATED_TAGS], key, key_len, tag) || |
+ tag_set_get_tag(&context->tags[LOCAL_TAGS], key, key_len, tag)) { |
+ return 1; |
+ } |
+ return 0; |
+} |
+ |
+// Context encoding and decoding functions. |
+// |
+// Wire format for tag_set's 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. |
+// |
+// This is followed by the key/value memory from struct tag_set. |
+ |
+#define ENCODED_VERSION 0 // Version number |
+#define ENCODED_HEADER_SIZE 4 // size of tag set header |
+ |
+// 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 + tags->kvm_used) { |
+ 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) { |
+ return ENCODED_HEADER_SIZE; |
+ } |
+ memcpy(buffer, tags->kvm, tags->kvm_used); |
+ return ENCODED_HEADER_SIZE + tags->kvm_used; |
+} |
+ |
+size_t census_context_encode(const census_context *context, char *buffer, |
+ size_t buf_size) { |
+ return tag_set_encode(&context->tags[PROPAGATED_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 = tags->ntags_alloc = (int)(*buffer++); |
+ if (tags->ntags == 0) { |
+ tags->ntags_alloc = 0; |
+ tags->kvm_size = 0; |
+ tags->kvm_used = 0; |
+ tags->kvm = NULL; |
+ return; |
+ } |
+ 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; 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_context *census_context_decode(const char *buffer, size_t size) { |
+ census_context *context = gpr_malloc(sizeof(census_context)); |
+ memset(&context->tags[LOCAL_TAGS], 0, sizeof(struct tag_set)); |
+ if (buffer == NULL) { |
+ memset(&context->tags[PROPAGATED_TAGS], 0, sizeof(struct tag_set)); |
+ } else { |
+ tag_set_decode(&context->tags[PROPAGATED_TAGS], buffer, size); |
+ } |
+ memset(&context->status, 0, sizeof(context->status)); |
+ context->status.n_propagated_tags = context->tags[PROPAGATED_TAGS].ntags; |
+ return context; |
+} |