Skip to content

Commit

Permalink
Add TraceState implementation (open-telemetry#273)
Browse files Browse the repository at this point in the history
  • Loading branch information
nadiaciobanu authored Aug 14, 2020
1 parent a55843f commit 2300a53
Show file tree
Hide file tree
Showing 2 changed files with 264 additions and 19 deletions.
135 changes: 117 additions & 18 deletions api/include/opentelemetry/trace/trace_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,51 +45,150 @@ class TraceState
class Entry
{
public:
Entry() noexcept = default;

// Copy constructor.
Entry(const Entry &copy);
Entry() : key_(nullptr), value_(nullptr){};

// Copy constructor
Entry(Entry &copy)
{
key_ = CopyStringToPointer(copy.key_.get());
value_ = CopyStringToPointer(copy.value_.get());
}

// Copy assignment operator
Entry &operator=(Entry &other)
{
key_ = CopyStringToPointer(other.key_.get());
value_ = CopyStringToPointer(other.value_.get());
return *this;
}

// Move contructor and assignment operator
Entry(Entry &&other) = default;
Entry &operator=(Entry &&other) = default;

// Creates an Entry for a given key-value pair.
Entry(nostd::string_view key, nostd::string_view value) noexcept;
Entry(nostd::string_view key, nostd::string_view value) noexcept
{
key_ = CopyStringToPointer(key);
value_ = CopyStringToPointer(value);
}

// Gets the key associated with this entry.
nostd::string_view GetKey() const { return key_.get(); }

// Gets the value associated with this entry.
nostd::string_view GetValue() const { return value_.get(); }

nostd::string_view GetKey();
nostd::string_view GetValue();
// Sets the value for this entry. This overrides the previous value.
void SetValue(nostd::string_view value) { value_ = CopyStringToPointer(value); }

private:
// Store key and value as raw char pointers to avoid using std::string.
nostd::unique_ptr<const char[]> key_;
nostd::unique_ptr<const char[]> value_;

// Copies string into a buffer and returns a unique_ptr to the buffer.
// This is a workaround for the fact that strcpy doesn't accept a const char* destination.
nostd::unique_ptr<const char[]> CopyStringToPointer(nostd::string_view str)
{
nostd::unique_ptr<char[]> temp(new char[str.size() + 1]);
strcpy(temp.get(), str.data());
return nostd::unique_ptr<const char[]>(temp.release());
}
};

// An empty TraceState.
TraceState() noexcept : num_entries_(0) {}
TraceState() noexcept : entries_(new Entry[kMaxKeyValuePairs]), num_entries_(0) {}

// Returns false if no such key, otherwise returns true and populates value.
bool Get(nostd::string_view key, nostd::string_view value) const noexcept { return false; }
// Returns false if no such key, otherwise returns true and populates the value parameter with the
// associated value.
bool Get(nostd::string_view key, nostd::string_view &value) const noexcept
{
for (const auto &entry : Entries())
{
if (key == entry.GetKey())
{
value = entry.GetValue();
return true;
}
}
return false;
}

// Creates an Entry for the key-value pair and adds it to entries. Returns true if pair was added
// succesfully, false otherwise. If value is null or entries_ is full, this function is a no-op.
bool Set(nostd::string_view key, nostd::string_view value) noexcept
{
if (value.empty() || num_entries_ >= kMaxKeyValuePairs)
{
return false;
}

// Creates an Entry for the key-value pair and adds it to entries.
// If value is null, this function is a no-op.
void Set(nostd::string_view key, nostd::string_view value) const noexcept;
Entry entry(key, value);
(entries_.get())[num_entries_] = entry;
num_entries_++;
return true;
}

// Returns true if there are no keys, false otherwise.
bool Empty() const noexcept { return true; }
bool Empty() const noexcept { return num_entries_ == 0; }

// Returns a span of all the entries. The TraceState object must outlive the span.
nostd::span<Entry> Entries() const noexcept { return {}; }
nostd::span<Entry> Entries() const noexcept
{
return nostd::span<Entry>(entries_.get(), num_entries_);
}

// Returns whether key is a valid key. See https://www.w3.org/TR/trace-context/#key
static bool IsValidKey(nostd::string_view key);
static bool IsValidKey(nostd::string_view key)
{
if (key.empty() || key.size() > kKeyMaxSize || !IsLowerCaseAlphaOrDigit(key[0]))
{
return false;
}

int ats = 0;

for (const char c : key)
{
if (!IsLowerCaseAlphaOrDigit(c) && c != '_' && c != '-' && c != '@' && c != '*' && c != '/')
{
return false;
}
if ((c == '@') && (++ats > 1))
{
return false;
}
}
return true;
}

// Returns whether value is a valid value. See https://www.w3.org/TR/trace-context/#value
static bool IsValidValue(nostd::string_view value);
static bool IsValidValue(nostd::string_view value)
{
if (value.empty() || value.size() > kValueMaxSize)
{
return false;
}

for (const char c : value)
{
if (c < ' ' || c > '~' || c == ',' || c == '=')
{
return false;
}
}
return true;
}

private:
// Store entries in a C-style array to avoid using std::array or std::vector.
Entry entries_[kMaxKeyValuePairs];
nostd::unique_ptr<Entry[]> entries_;

// Maintain the number of entries in entries_. Must be in the range [0, kMaxKeyValuePairs].
int num_entries_;

static bool IsLowerCaseAlphaOrDigit(char c) { return isdigit(c) || islower(c); }
};

} // namespace trace
Expand Down
148 changes: 147 additions & 1 deletion api/test/trace/trace_state_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,157 @@ namespace

using opentelemetry::trace::TraceState;

// Random string of length 257. Used for testing strings with max length 256.
const char *kLongString =
"4aekid3he76zgytjavudqqeltyvu5zqio2lx7d92dlxlf0z4883irvxuwelsq27sx1mlrjg3r7ad3jeq09rjppyd9veorg"
"2nmihy4vilabfts8bsxruih0urusmjnglzl3iwpjinmo835dbojcrd73p56nw80v4xxrkye59ytmu5v84ysfa24d58ovv9"
"w1n54n0mhhf4z0mpv6oudywrp9vfoks6lrvxv3uihvbi2ihazf237kvt1nbsjn3kdvfdb";

// ------------------------- Entry class tests ---------------------------------

// Test constructor that takes a key-value pair
TEST(EntryTest, KeyValueConstruction)
{
opentelemetry::nostd::string_view key = "test_key";
opentelemetry::nostd::string_view val = "test_value";
TraceState::Entry e(key, val);

EXPECT_EQ(key.size(), e.GetKey().size());
EXPECT_EQ(strcmp(key.data(), e.GetKey().data()), 0);

EXPECT_EQ(val.size(), e.GetValue().size());
EXPECT_EQ(strcmp(val.data(), e.GetValue().data()), 0);
}

// Test copy constructor
TEST(EntryTest, Copy)
{
TraceState::Entry e("test_key", "test_value");
TraceState::Entry copy(e);
EXPECT_EQ(strcmp(copy.GetKey().data(), e.GetKey().data()), 0);
EXPECT_EQ(strcmp(copy.GetValue().data(), e.GetValue().data()), 0);
}

// Test assignment operator
TEST(EntryTest, Assignment)
{
TraceState::Entry e("test_key", "test_value");
TraceState::Entry empty;
empty = e;
EXPECT_EQ(strcmp(empty.GetKey().data(), e.GetKey().data()), 0);
EXPECT_EQ(strcmp(empty.GetValue().data(), e.GetValue().data()), 0);
}

TEST(EntryTest, SetValue)
{
TraceState::Entry e("test_key", "test_value");
opentelemetry::nostd::string_view new_val = "new_value";
e.SetValue(new_val);

EXPECT_EQ(new_val.size(), e.GetValue().size());
EXPECT_EQ(strcmp(new_val.data(), e.GetValue().data()), 0);
}

// -------------------------- TraceState class tests ---------------------------

TEST(TraceStateTest, DefaultConstruction)
{
TraceState s;
EXPECT_FALSE(s.Get("missing_key", "value"));
opentelemetry::nostd::string_view return_val = "";
EXPECT_FALSE(s.Get("missing_key", return_val));
EXPECT_EQ(return_val.data(), "");
EXPECT_TRUE(s.Empty());
EXPECT_EQ(0, s.Entries().size());
}

TEST(TraceStateTest, Set)
{
TraceState s;
opentelemetry::nostd::string_view key = "test_key";
opentelemetry::nostd::string_view val = "test_value";
s.Set(key, val);

opentelemetry::nostd::string_view bad_key = "bad_key";
opentelemetry::nostd::string_view null_val;
// Since string_view data is null by default, this should be a no-op
s.Set(bad_key, null_val);

opentelemetry::nostd::span<TraceState::Entry> entries = s.Entries();
EXPECT_EQ(entries.size(), 1);
EXPECT_EQ(entries[0].GetKey().data(), key);
EXPECT_EQ(entries[0].GetValue().data(), val);
}

TEST(TraceStateTest, Get)
{
TraceState s;
const int kNumPairs = 3;
opentelemetry::nostd::string_view keys[kNumPairs] = {"test_key_1", "test_key_2", "test_key_3"};
opentelemetry::nostd::string_view values[kNumPairs] = {"test_val_1", "test_val_2", "test_val_3"};

for (int i = 0; i < kNumPairs; i++)
{
s.Set(keys[i], values[i]);
}

opentelemetry::nostd::string_view return_val = "";

for (int i = 0; i < kNumPairs; i++)
{
EXPECT_TRUE(s.Get(keys[i], return_val));
EXPECT_EQ(return_val.data(), values[i]);
return_val = "";
}

EXPECT_FALSE(s.Get("fake_key", return_val));
EXPECT_EQ(return_val.data(), "");
}

TEST(TraceStateTest, Empty)
{
TraceState s;
EXPECT_TRUE(s.Empty());

s.Set("test_key", "test_value");
EXPECT_FALSE(s.Empty());
}

TEST(TraceStateTest, Entries)
{
TraceState s;
const int kNumPairs = 3;
opentelemetry::nostd::string_view keys[kNumPairs] = {"test_key_1", "test_key_2", "test_key_3"};
opentelemetry::nostd::string_view values[kNumPairs] = {"test_val_1", "test_val_2", "test_val_3"};

for (int i = 0; i < kNumPairs; i++)
{
s.Set(keys[i], values[i]);
}

opentelemetry::nostd::span<TraceState::Entry> entries = s.Entries();
for (int i = 0; i < kNumPairs; i++)
{
EXPECT_EQ(entries[i].GetKey().data(), keys[i]);
EXPECT_EQ(entries[i].GetValue().data(), values[i]);
}
}

TEST(TraceStateTest, IsValidKey)
{
EXPECT_TRUE(TraceState::IsValidKey("valid-key23/*"));
EXPECT_FALSE(TraceState::IsValidKey("Invalid_key"));
EXPECT_FALSE(TraceState::IsValidKey("invalid$Key&"));
EXPECT_FALSE(TraceState::IsValidKey(""));
EXPECT_FALSE(TraceState::IsValidKey(kLongString));
}

TEST(TraceStateTest, IsValidValue)
{
EXPECT_TRUE(TraceState::IsValidValue("valid-val$%&~"));
EXPECT_FALSE(TraceState::IsValidValue("\tinvalid"));
EXPECT_FALSE(TraceState::IsValidValue("invalid="));
EXPECT_FALSE(TraceState::IsValidValue("invalid,val"));
EXPECT_FALSE(TraceState::IsValidValue(""));
EXPECT_FALSE(TraceState::IsValidValue(kLongString));
}
} // namespace

0 comments on commit 2300a53

Please sign in to comment.