Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding baggage implementation #676

Merged
merged 9 commits into from
Apr 21, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
304 changes: 304 additions & 0 deletions api/include/opentelemetry/baggage/baggage.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
// Copyright 2021, OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#pragma once

#include "opentelemetry/common/kv_properties.h"
#include "opentelemetry/nostd/shared_ptr.h"
#include "opentelemetry/nostd/string_view.h"
#include "opentelemetry/version.h"

OPENTELEMETRY_BEGIN_NAMESPACE

namespace baggage
{

class Baggage
{
public:
static constexpr size_t kMaxKeyValuePairs = 180;
static constexpr size_t kMaxKeyValueSize = 4096;
static constexpr size_t kMaxSize = 8192;
static constexpr char kKeyValueSeparator = '=';
static constexpr char kMembersSeparator = ',';
static constexpr char kMetadataSeparator = ';';

Baggage() : kv_properties_(new opentelemetry::common::KeyValueProperties()) {}
Baggage(size_t size) : kv_properties_(new opentelemetry::common::KeyValueProperties(size)){};

template <class T>
Baggage(const T &keys_and_values)
: kv_properties_(new opentelemetry::common::KeyValueProperties(keys_and_values))
{}

static nostd::shared_ptr<Baggage> GetDefault()
{
static nostd::shared_ptr<Baggage> baggage{new Baggage()};
return baggage;
}

/* Get value for key in the baggage
@returns true if key is found, false otherwise
*/
bool GetValue(const nostd::string_view &key, std::string &value) const
lalitb marked this conversation as resolved.
Show resolved Hide resolved
{
return kv_properties_->GetValue(key, value);
}

/* Returns shared_ptr of new baggage object which contains new key-value pair. If key or value is
invalid, copy of current baggage is returned
*/
nostd::shared_ptr<Baggage> Set(const nostd::string_view &key, const nostd::string_view &value)
{

nostd::shared_ptr<Baggage> baggage(new Baggage(kv_properties_->Size() + 1));
const bool valid_kv = IsValidKey(key) && IsValidValue(value);

if (valid_kv)
{
baggage->kv_properties_->AddEntry(key, value);
}

// add rest of the fields.
kv_properties_->GetAllEntries(
[&baggage, &key, &valid_kv](nostd::string_view e_key, nostd::string_view e_value) {
nikhil1511 marked this conversation as resolved.
Show resolved Hide resolved
// if key or value was not valid, add all the entries. Add only remaining entries
// otherwise.
if (!valid_kv || key != e_key)
{
baggage->kv_properties_->AddEntry(e_key, e_value);
}

return true;
});

return baggage;
}

// @return all key-values entries by repeatedly invoking the function reference passed as argument
// for each entry
bool GetAllEntries(
nostd::function_ref<bool(nostd::string_view, nostd::string_view)> callback) const noexcept
nikhil1511 marked this conversation as resolved.
Show resolved Hide resolved
{
return kv_properties_->GetAllEntries(callback);
}

// delete key from the baggage if it exists. Returns shared_ptr of new baggage object.
lalitb marked this conversation as resolved.
Show resolved Hide resolved
// if key does not exist, copy of current baggage is returned.
// Validity of key is not checked as invalid keys should never be populated in baggage in the
// first place.
nostd::shared_ptr<Baggage> Delete(const nostd::string_view &key)
{
// keeping size of baggage same as key might not be found in it
nostd::shared_ptr<Baggage> baggage(new Baggage(kv_properties_->Size()));
nikhil1511 marked this conversation as resolved.
Show resolved Hide resolved
kv_properties_->GetAllEntries(
[&baggage, &key](nostd::string_view e_key, nostd::string_view e_value) {
if (key != e_key)
baggage->kv_properties_->AddEntry(e_key, e_value);
return true;
});
return baggage;
}

// Returns shared_ptr of baggage after extracting key-value pairs from header
static nostd::shared_ptr<Baggage> FromHeader(nostd::string_view header)
nikhil1511 marked this conversation as resolved.
Show resolved Hide resolved
{
if (header.size() > kMaxSize)
{
// header size exceeds maximum threshold, return empty baggage
return GetDefault();
}

common::KeyValueStringTokenizer kv_str_tokenizer(header);
size_t cnt = kv_str_tokenizer.NumTokens(); // upper bound on number of kv pairs
if (cnt > kMaxKeyValuePairs)
{
cnt = kMaxKeyValuePairs;
}

nostd::shared_ptr<Baggage> baggage(new Baggage(cnt));
bool kv_valid;
nostd::string_view key, value;

while (kv_str_tokenizer.next(kv_valid, key, value) && baggage->kv_properties_->Size() < cnt)
{
if (!kv_valid || (key.size() + value.size() > kMaxKeyValueSize))
{
// if kv pair is not valid, skip it
continue;
}

// NOTE : metadata is kept as part of value only as it does not have any semantic meaning.
// but, we need to extract it (else Decode on value will return error)
nostd::string_view metadata;
auto metadata_separator = value.find(kMetadataSeparator);
if (metadata_separator != std::string::npos)
{
metadata = value.substr(metadata_separator);
value = value.substr(0, metadata_separator);
}

bool err = 0;
auto key_str = UrlDecode(common::StringUtil::Trim(key), err);
auto value_str = UrlDecode(common::StringUtil::Trim(value), err);

if (err == false && IsValidKey(key_str) && IsValidValue(value_str))
{
if (!metadata.empty())
{
value_str.append(metadata.data(), metadata.size());
}
baggage->kv_properties_->AddEntry(key_str, value_str);
}
}

return baggage;
}

// Creates string from baggage object.
std::string ToHeader() const
{
std::string header_s;
bool first = true;
kv_properties_->GetAllEntries([&](nostd::string_view key, nostd::string_view value) {
if (!first)
{
header_s.push_back(kMembersSeparator);
}
else
{
first = false;
}
header_s.append(UrlEncode(key));
header_s.push_back(kKeyValueSeparator);

// extracting metadata from value. We do not encode metadata
auto metadata_separator = value.find(kMetadataSeparator);
if (metadata_separator != std::string::npos)
{
header_s.append(UrlEncode(value.substr(0, metadata_separator)));
auto metadata = value.substr(metadata_separator);
header_s.append(std::string(metadata.data(), metadata.size()));
}
else
{
header_s.append(UrlEncode(value));
}
return true;
});
return header_s;
}

private:
static bool IsPrintableString(nostd::string_view str)
{
for (const auto &ch : str)
lalitb marked this conversation as resolved.
Show resolved Hide resolved
{
if (ch < ' ' || ch > '~')
{
return false;
}
}

return true;
}

static bool IsValidKey(nostd::string_view key) { return key.size() && IsPrintableString(key); }

static bool IsValidValue(nostd::string_view value) { return IsPrintableString(value); }

// Uri encode key value pairs before injecting into header
// Implementation inspired from : https://golang.org/src/net/url/url.go?s=7851:7884#L264
static std::string UrlEncode(nostd::string_view str)
{
auto to_hex = [](char c) -> char {
static const char *hex = "0123456789ABCDEF";
return hex[c & 15];
};

std::string ret;

for (auto c : str)
{
if (std::isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~')
{
ret.push_back(c);
}
else if (c == ' ')
{
ret.push_back('+');
}
else
{
ret.push_back('%');
ret.push_back(to_hex(c >> 4));
ret.push_back(to_hex(c & 15));
}
}

return ret;
}

// Uri decode key value pairs after extracting from header
static std::string UrlDecode(nostd::string_view str, bool &err)
{
auto IsHex = [](char c) {
return std::isdigit(c) || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
};

auto from_hex = [](char c) -> char {
return std::isdigit(c) ? c - '0' : std::toupper(c) - 'A' + 10;
};

std::string ret;

for (size_t i = 0; i < str.size(); i++)
{
if (str[i] == '%')
{
if (i + 2 >= str.size() || !IsHex(str[i + 1]) || !IsHex(str[i + 2]))
{
err = 1;
return "";
}
ret.push_back(from_hex(str[i + 1]) << 4 | from_hex(str[i + 2]));
i += 2;
}
else if (str[i] == '+')
{
ret.push_back(' ');
}
else if (std::isalnum(str[i]) || str[i] == '-' || str[i] == '_' || str[i] == '.' ||
str[i] == '~')
{
ret.push_back(str[i]);
}
else
{
err = 1;
return "";
}
}

return ret;
}

private:
// Store entries in a C-style array to avoid using std::array or std::vector.
nostd::unique_ptr<opentelemetry::common::KeyValueProperties> kv_properties_;
};

} // namespace baggage

OPENTELEMETRY_END_NAMESPACE
21 changes: 16 additions & 5 deletions api/include/opentelemetry/common/kv_properties.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ class KeyValueStringTokenizer
: str_(str), opts_(opts), index_(0)
{}

static nostd::string_view GetDefaultKeyOrValue()
{
static std::string default_str = "";
return default_str;
}

// Returns next key value in the string header
// @param valid_kv : if the found kv pair is valid or not
// @param key : key in kv pair
Expand All @@ -57,29 +63,34 @@ class KeyValueStringTokenizer
valid_kv = true;
while (index_ < str_.size())
{
size_t end = str_.find(opts_.member_separator, index_);
bool is_empty_pair = false;
size_t end = str_.find(opts_.member_separator, index_);
if (end == std::string::npos)
{
end = str_.size() - 1;
}
else if (end == index_) // empty pair. do not update end
{
is_empty_pair = true;
}
else
{
end--;
}

auto list_member = StringUtil::Trim(str_, index_, end);
if (list_member.size() == 0)
if (list_member.size() == 0 || is_empty_pair)
{
// empty list member
index_ = end + 2;
index_ = end + 2 - is_empty_pair;
if (opts_.ignore_empty_members)
{
continue;
}

valid_kv = true;
key = "";
value = "";
key = GetDefaultKeyOrValue();
value = GetDefaultKeyOrValue();
return true;
}

Expand Down
10 changes: 10 additions & 0 deletions api/include/opentelemetry/common/string_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ class StringUtil
}
return str.substr(left, 1 + right - left);
}

static nostd::string_view Trim(nostd::string_view str)
{
if (str.empty())
{
return str;
}

return Trim(str, 0, str.size() - 1);
}
};

} // namespace common
Expand Down
1 change: 1 addition & 0 deletions api/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ add_subdirectory(trace)
add_subdirectory(metrics)
add_subdirectory(logs)
add_subdirectory(common)
add_subdirectory(baggage)
12 changes: 12 additions & 0 deletions api/test/baggage/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
load("//bazel:otel_cc_benchmark.bzl", "otel_cc_benchmark")

cc_test(
name = "baggage_test",
srcs = [
"baggage_test.cc",
],
deps = [
"//api",
"@com_google_googletest//:gtest_main",
],
)
Loading