diff --git a/libs/meter/meter_id/meter_id.cpp b/libs/meter/meter_id/meter_id.cpp index 32c1a73..3eee024 100644 --- a/libs/meter/meter_id/meter_id.cpp +++ b/libs/meter/meter_id/meter_id.cpp @@ -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 ValidateTags(const std::unordered_map& tags) { @@ -22,20 +30,36 @@ std::unordered_map 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& 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& tags) diff --git a/libs/meter/meter_id/test_meter_id.cpp b/libs/meter/meter_id/test_meter_id.cpp index d672af2..8bc9110 100644 --- a/libs/meter/meter_id/test_meter_id.cpp +++ b/libs/meter/meter_id/test_meter_id.cpp @@ -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"}});