Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
46 changes: 35 additions & 11 deletions libs/meter/meter_id/meter_id.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,16 @@

namespace spectator {

// Define the static member
const std::regex INVALID_CHARS("[^-._A-Za-z0-9~^]");
namespace {

// The characters spectatord accepts in names and tags: [-._A-Za-z0-9~^]
bool IsValidChar(const char c)
{
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '.' ||
c == '_' || c == '~' || c == '^';
}

} // namespace

std::unordered_map<std::string, std::string> ValidateTags(const std::unordered_map<std::string, std::string>& tags)
{
Expand All @@ -22,20 +30,36 @@ std::unordered_map<std::string, std::string> ValidateTags(const std::unordered_m
return validTags;
}

std::string ReplaceInvalidChars(const std::string& s) { return std::regex_replace(s, INVALID_CHARS, "_"); }
// Appends s with each invalid byte replaced by '_'. A byte loop rather than std::regex_replace, which was the dominant
// cost of creating a MeterId.
void AppendValidChars(std::string& out, const std::string& s)
{
for (const char c : s)
{
out += IsValidChar(c) ? c : '_';
}
}

std::string ToSpectatorId(const std::string& name, const std::unordered_map<std::string, std::string>& tags)
{
std::ostringstream ss;
ss << ReplaceInvalidChars(name);
if (!tags.empty())
// Each byte is kept or replaced by one byte, so the size is known up front and the id is allocated once.
std::size_t size = name.size();
for (const auto& [fst, snd] : tags)
{
for (const auto& [fst, snd] : tags)
{
ss << "," << ReplaceInvalidChars(fst) << "=" << ReplaceInvalidChars(snd);
}
size += 1 + fst.size() + 1 + snd.size();
}
return ss.str();

std::string id;
id.reserve(size);
AppendValidChars(id, name);
for (const auto& [fst, snd] : tags)
{
id += ',';
AppendValidChars(id, fst);
id += '=';
AppendValidChars(id, snd);
}
return id;
}

MeterId::MeterId(const std::string& name, const std::unordered_map<std::string, std::string>& tags)
Expand Down
18 changes: 18 additions & 0 deletions libs/meter/meter_id/test_meter_id.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,24 @@ TEST(MeterIdTest, IllegalCharsAreReplaced)
EXPECT_EQ("test______^____-_~______________.___foo", id.GetSpectatordId());
}

TEST(MeterIdTest, IllegalCharsInTagsAreReplaced)
{
const MeterId id("foo", {{"a b", "c/d"}});
EXPECT_EQ("foo,a_b=c_d", id.GetSpectatordId());
}

TEST(MeterIdTest, ValidCharsAreKept)
{
const MeterId id("azAZ09-._~^");
EXPECT_EQ("azAZ09-._~^", id.GetSpectatordId());
}

TEST(MeterIdTest, EachNonAsciiByteIsReplaced)
{
const MeterId id("caf\xc3\xa9");
EXPECT_EQ("caf__", id.GetSpectatordId());
}

TEST(MeterIdTest, LookupTags)
{
const MeterId id1("foo", {{"a", "1"}, {"b", "2"}, {"c", "3"}});
Expand Down
Loading