This is an automated email from the ASF dual-hosted git repository. bneradt pushed a commit to branch dev-1-1-2 in repository https://gitbox.apache.org/repos/asf/trafficserver-libswoc.git
commit 82b5d7f47316abc748bd052a614debec2eb69140 Author: Alan M. Carroll <a...@apache.org> AuthorDate: Fri Mar 27 14:49:01 2020 -0500 Fix bugs in IPSPace blending where ranges were not coalesced properly. --- CMakeLists.txt | 1 + example/CMakeLists.txt | 11 ++ example/ex_netdb.cc | 295 ++++++++++++++++++++++++++++++++++++ swoc++/include/swoc/DiscreteRange.h | 62 +++++--- unit_tests/test_ip.cc | 204 +++++++------------------ 5 files changed, 402 insertions(+), 171 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7c6e0c4..518ba09 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,7 @@ set(INSTALL_DIR ${CMAKE_HOME_DIRECTORY}) # Fortunately this has no external dependencies so the set up can be simple. add_subdirectory(swoc++) add_subdirectory(unit_tests) +add_subdirectory(example) add_subdirectory(doc EXCLUDE_FROM_ALL) # Find all of the directories subject to clang formatting and make a target to do the format. diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 0000000..07394ab --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.12) +project(libswoc_examples CXX) +set(CMAKE_CXX_STANDARD 17) + +add_executable(ex_netdb + ex_netdb.cc + ) + +target_link_libraries(ex_netdb PUBLIC swoc++) +set_target_properties(ex_netdb PROPERTIES CLANG_FORMAT_DIRS ${CMAKE_CURRENT_SOURCE_DIR}) +target_compile_options(ex_netdb PRIVATE -Wall -Wextra -Werror -Wno-unused-parameter -Wno-format-truncation -Wno-stringop-overflow -Wno-invalid-offsetof) diff --git a/example/ex_netdb.cc b/example/ex_netdb.cc new file mode 100644 index 0000000..fc50158 --- /dev/null +++ b/example/ex_netdb.cc @@ -0,0 +1,295 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2014 Network Geographics + +/** @file + + Example tool to process network DB files. + + This generates an executable that processes a list of network files in a specific format. + Tokens are (variable) space separated. Example lines look like + + 4441:34F8:1E40:1EF:0:0:A0:0/108 partner:ngeo raz ?_ prod,dmz "shared space" + 4441:34F8:1E40:1EF:0:0:B0:0/108 yahoo raz ?_ prod,dmz "local routing" + + There is + - an IP address network + - type/owner field. If "yahoo", it's type "yahoo" and owner "yahoo". Otherwise it's a + partner network, with "partner:" followed by the partner name. + - pod / colo code + - Useless column marker + - Network property flags + - Comment in double quotes + + The goal is to transform this in a standard CSV file, dropping the useless column, and + tweaking the flags to use ';' instead of ',' as a separator (to avoid confusing the CSV). + Adjacent networks with identical properties should also be merged to reduce the data set size. + The comments may or not be kept - currently the implement discards them to get better + network coalescence. One of the goals of the over all work is to make it easy to build + variants of this code to output CSV files tuned to specific uses. +*/ + +#include <unordered_set> +#include <fstream> + +#include <swoc/TextView.h> +#include <swoc/swoc_ip.h> +#include <swoc/bwf_ip.h> +#include <swoc/bwf_std.h> +#include <swoc/bwf_ex.h> +#include <swoc/swoc_file.h> +#include <swoc/Lexicon.h> + +using namespace std::literals; +using namespace swoc::literals; + +using swoc::TextView; +using swoc::IPEndpoint; + +using swoc::IP4Addr; +using swoc::IP4Range; + +using swoc::IP6Addr; +using swoc::IP6Range; + +using swoc::IPAddr; +using swoc::IPRange; + +using swoc::IPMask; + +/// Type for temporary buffer writer output. +using W = swoc::LocalBufferWriter<512>; + +/// Network properties. +enum class Flag { + INVALID = -1, ///< Value for initialization, invalid. + INTERNAL = 0, ///< Internal network. + PROD, ///< Production network. + DMZ, ///< DMZ + SECURE, ///< Secure network + NONE ///< No flags - useful because the input uses "-" sometimes to mark no properties. +}; + +namespace std { +/// Make the enum size look like a tuple size. +template <> struct tuple_size<Flag> : public std::integral_constant<size_t, static_cast<size_t>(Flag::NONE)> {}; +} // namespace std + +/// Bit set for network property flags. +using FlagSet = std::bitset<std::tuple_size<Flag>::value>; + +/// Pod type. +enum class PodType { + INVALID, ///< Initialization value. + YAHOO, ///< Yahoo! pod. + PARTNER ///< Partner pod. +}; + +/// Mapping of names and property flags. +swoc::Lexicon<Flag> FlagNames {{ + {Flag::NONE, {"-", "NONE"}} + , {Flag::INTERNAL, { "internal" }} + , {Flag::PROD, {"prod"}} + , {Flag::DMZ, {"dmz"}} + , {Flag::SECURE, {"secure"}} + }}; + +/// Mapping of names and pod types. +swoc::Lexicon<PodType> PodTypeNames {{ + {PodType::YAHOO, "yahoo"} + , {PodType::PARTNER, "partner"} + }}; + +// Create BW formatters for the types so they can be used for output. +namespace swoc { + +BufferWriter& bwformat(BufferWriter& w, bwf::Spec const& spec, PodType pt) { + return w.write(PodTypeNames[pt]); +} + +BufferWriter& bwformat(BufferWriter& w, bwf::Spec const& spec, FlagSet const& flags) { + bool first_p = true; // Track to get separators correct. + // Loop through the indices and write the flag name if it's set. + for ( unsigned idx = 0 ; idx < std::tuple_size<Flag>::value ; ++idx) { + if (flags[idx]) { + if (!first_p) { + w.write(';'); + } + w.write(FlagNames[static_cast<Flag>(idx)]); + first_p = false; + } + } + return w; +} + +} // namespace swoc + +// These are used to keep pointers for the same string identical so the payloads +// can be directly compared. +std::unordered_set<TextView, std::hash<std::string_view>> PodNames; +std::unordered_set<TextView, std::hash<std::string_view>> OwnerNames; +std::unordered_set<TextView, std::hash<std::string_view>> Descriptions; + +/// The "color" for the IPSpace. +struct Payload { + PodType _type = PodType::INVALID; ///< Type of ownership. + TextView _owner; ///< Corporate owner. + TextView _pod; ///< Pod / colocation. + TextView _descr; ///< Description / comment + FlagSet _flags; ///< Flags. + + /// @return @c true if @a this is equal to @a that. + bool operator == (Payload const& that) { + return _type == that._type && + _owner == that._owner && + _pod == that._pod && + _flags == that._flags && + _descr == that._descr; + } + + /// @return @c true if @a this is not equal to @a that. + bool operator != (Payload const& that) { + return ! (*this == that); + } +}; + + +/// IPSpace for mapping address to @c Payload +using Space = swoc::IPSpace<Payload>; + +/// Place to store strings parsed from the input files. +swoc::MemArena Storage; + +/// Convert a parsed string into a stored string to make the pointer persistent. +TextView store(TextView const& text) { + auto span = Storage.alloc(text.size()).rebind<char>(); + memcpy(span, text); + return span.view(); +} + +/// Process the @a content of a file in to @a space. +void process(Space& space, TextView content) { + int line_no = 0; /// Track for error reporting. + + // For each line in @a content + for (TextView line ; ! (line = content.take_prefix_at('\n')).empty() ; ) { + ++line_no; + line.trim_if(&isspace); + // Allow empty lines and '#' comments without error. + if (line.empty() || '#' == *line) { + continue; + } + + // Get the range, make sure it's a valid range. + auto range_token = line.take_prefix_if(&isspace); + IPRange range{range_token}; + if (range.empty()) { + std::cerr << W().print("Invalid range '{}' on line {}\n", range_token, line_no); + continue; + } + + // Get the owner / type. + auto owner_token = line.ltrim_if(&isspace).take_prefix_if(&isspace); + PodType pod_type = PodType::YAHOO; // default to this if no owner specifier found. + if (auto type_token = owner_token.split_prefix_at(':') ; ! type_token.empty()) { + pod_type = PodTypeNames[type_token]; + if (PodType::INVALID == pod_type) { + std::cerr << W().print("Invalid type '{}' on line {}\n", type_token, line_no); + continue; + } + } + + // normalize owner + if ( auto spot = OwnerNames.find(owner_token) ; spot == OwnerNames.end()) { + owner_token = store(owner_token); + OwnerNames.insert(owner_token); + } else { + owner_token = *spot; + } + + // Get the pod code. + auto pod_token = line.ltrim_if(&isspace).take_prefix_if(&isspace); + + // normalize + if ( auto spot = PodNames.find(pod_token) ; spot == PodNames.end()) { + pod_token = store(pod_token); + PodNames.insert(pod_token); + } else { + pod_token = *spot; + } + + line.ltrim_if(&isspace).take_prefix_if(&isspace); // skip bogus column. + + // Work on the flags. + auto flag_token = line.ltrim_if(&isspace).take_prefix_if(&isspace); + FlagSet flags; + // Loop over the token, picking out comma separated keys. + for ( TextView key ; ! (key = flag_token.take_prefix_at(',')).empty() ; ) { + auto idx = FlagNames[key]; // look up the key. + if (Flag::INVALID == idx) { + std::cerr << W().print("Invalid flag '{}' on line {}\n", key, line_no); + continue; + } + if (idx != Flag::NONE) { // "NONE" means the input was marked explicitly as no flags. + flags[int(idx)] = true; + } + } + + #if 0 + // The description is what's left, trim the spaces and then the quotes. + auto descr_token = line.trim_if(&isspace).trim('"'); + // normalize + if ( auto spot = Descriptions.find(descr_token) ; spot == Descriptions.end()) { + descr_token = store(descr_token); + Descriptions.insert(descr_token); + } else { + descr_token = *spot; + } + #endif + + // Everything went OK, create the payload and put it in the space. + Payload payload{pod_type, owner_token, pod_token, {}, flags}; + space.blend(range, payload, [&](Payload & lhs, Payload const& rhs) { + // It's an error if there's overlap that's not consistent. + if (lhs._type != PodType::INVALID && lhs != rhs) { + std::cerr << W().print("Range collision while blending {} on line {}\n", range, line_no); + } + lhs = rhs; + return true; + }); + } +} + +int main(int argc, char *argv[]) { + Space space; + + // Set the defaults so bogus input doesn't throw. + PodTypeNames.set_default(PodType::INVALID); + FlagNames.set_default(Flag::INVALID); + + // Open the output file. + std::ofstream output; + output.open("vz_netdb.csv", std::ofstream::out | std::ofstream::trunc); + if (! output.is_open()) { + std::cerr << W().print("Unable to open output file: {}", swoc::bwf::Errno{errno}); + } + + // Process the files in the command line. + for ( int idx = 1 ; idx < argc ; ++idx ) { + swoc::file::path path(argv[idx]); + std::error_code ec; + std::string content = swoc::file::load(path, ec); + if (! ec) { + std::cout << W().print("Processing {}, {} bytes\n", path, content.size()); + process(space, content); + } + } + + // Dump the resulting space. + std::cout << W().print("{} ranges\n", space.count()); + for ( auto && [ r, p] : space) { + // Note - if description is to be used, it needs to be added here. + output << W().print("{},{},{},{},{}\n", r, p._type, p._owner, p._pod, p._flags); + } + + return 0; +} diff --git a/swoc++/include/swoc/DiscreteRange.h b/swoc++/include/swoc/DiscreteRange.h index 967460b..d6f933e 100644 --- a/swoc++/include/swoc/DiscreteRange.h +++ b/swoc++/include/swoc/DiscreteRange.h @@ -242,9 +242,7 @@ public: */ EdgeRelation left_edge_relationship(self_type const& that) const { if (_max < that._max) { - auto tmp{_max}; - ++tmp; - return tmp < that._max ? EdgeRelation::GAP : EdgeRelation::ADJ; + return ++metric_type(_max) < that._max ? EdgeRelation::GAP : EdgeRelation::ADJ; } return _min >= that._min ? EdgeRelation::NONE : EdgeRelation::OVLP; } @@ -1215,7 +1213,7 @@ DiscreteSpace<METRIC, PAYLOAD>::blend(DiscreteSpace::range_type const&range, U c , F &&blender) -> self_type & { // Do a base check for the color to use on unmapped values. If self blending on @a color // is @c false, then do not color currently unmapped values. - auto plain_color = PAYLOAD(); + auto plain_color = PAYLOAD{}; bool plain_color_p = blender(plain_color, color); auto node_cleaner = [&] (Node * ptr) -> void { _fa.destroy(ptr); }; @@ -1238,28 +1236,52 @@ DiscreteSpace<METRIC, PAYLOAD>::blend(DiscreteSpace::range_type const&range, U c // Process @a n, covering the values from the previous range to @a n.max while (n) { - // Always look back at prev, so if there's no overlap at all, skip it. + // If there's no overlap, skip because this will be checked next loop, or it's the last node + // and will be checked post loop. Loop logic is simpler if n->max() >= remaining.min() if (n->max() < remaining.min()) { n = next(n); continue; } - // Invariant - n->max() >= remaining.min(); + Node* pred = prev(n); // Check for left extension. If found, clip that node to be adjacent and put in a // temporary that covers the overlap with the original payload. if (n->min() < remaining.min()) { - auto stub = _fa.make(remaining.min(), n->max(), n->payload()); - auto x { remaining.min() }; - --x; - n->assign_max(x); - this->insert_after(n, stub); - pred = n; - n = stub; + // @a fill is inserted iff n->max() < remaining.max(), in which case the max is correct. + // This is needed in other cases only for the color blending result. + unique_node fill { _fa.make(remaining.min(), n->max(), n->payload()), node_cleaner }; + bool fill_p = blender(fill->payload(), color); // fill or clear? + + if (fill_p) { + bool same_color_p = fill->payload() == n->payload(); + if (same_color_p && n->max() >= remaining.max()) { + return *this; // incoming range is completely covered by @a n in the same color, done. + } + remaining.assign_min(++METRIC(n->max())); // going to fill up n->max(), clip target. + if (! same_color_p) { + n->assign_max(--METRIC(remaining.min())); // clip @a n down. + this->insert_after(n, fill.get()); // add intersection node in different color. + n = fill.release(); + } + } else { // clear, don't fill. + auto max = n->max(); // cache this before reassigning. + if (max > remaining.max()) { // overhang on the right, must split. + fill.release(); + this->insert_before(n, _fa.make(n->min(), --METRIC(remaining.min()), n->payload())); + n->assign_min(++METRIC(remaining.max())); + return *this; + } + n->assign_max(--METRIC(remaining.min())); // clip @a n down. + if (max == remaining.max()) { + return *this; + } + remaining.assign_min(++METRIC(max)); + } + continue; } - auto pred_edge = pred ? remaining.left_edge_relationship(pred->range()) : DiscreteRangeEdgeRelation::NONE; // invariant - pred->max() < remaining.min() // Calculate and cache key relationships between @a n and @a remaining. @@ -1269,12 +1291,14 @@ DiscreteSpace<METRIC, PAYLOAD>::blend(DiscreteSpace::range_type const&range, U c // @a n strictly right overlaps with @a remaining. bool right_overlap_p = remaining.contains(n->min()); // @a n is adjacent on the right to @a remaining. - bool right_adj_p = remaining.is_left_adjacent_to(n->range()); + bool right_adj_p = !right_overlap_p && remaining.is_left_adjacent_to(n->range()); // @a n has the same color as would be used for unmapped values. bool n_plain_colored_p = plain_color_p && (n->payload() == plain_color); - // @a rped has the same color as would be used for unmapped values. + // @a pred has the same color as would be used for unmapped values. bool pred_plain_colored_p = - (DiscreteRangeEdgeRelation::NONE != pred_edge && DiscreteRangeEdgeRelation::GAP != pred_edge) && + pred && + pred->max() < remaining.min() && + ++metric_type(pred->max()) == remaining.min() && pred->payload() == plain_color; // Check for no right overlap - that means @a n is past the target range. @@ -1336,8 +1360,8 @@ DiscreteSpace<METRIC, PAYLOAD>::blend(DiscreteSpace::range_type const&range, U c } else { n->assign_min(range_max_plus_1); this->insert_before(n, fill.release()); - return *this; } + return *this; // @a n extends past @a remaining, -> all done. } else { // Collapse in to previous range if it's adjacent and the color matches. if (nullptr != (pred = prev(n)) && pred->range().is_left_adjacent_to(fill->range()) && pred->payload() == fill->payload()) { @@ -1361,7 +1385,7 @@ DiscreteSpace<METRIC, PAYLOAD>::blend(DiscreteSpace::range_type const&range, U c // Arriving here means there are no more ranges past @a range (those cases return from the loop). // Therefore the final fill node is always last in the tree. - if (plain_color_p && remaining.min() <= range.max()) { + if (plain_color_p && ! remaining.empty()) { // Check if the last node can be extended to cover because it's left adjacent. // Can decrement @a range_min because if there's a range to the left, @a range_min is not minimal. n = _list.tail(); diff --git a/unit_tests/test_ip.cc b/unit_tests/test_ip.cc index 4639c16..f1c8d05 100644 --- a/unit_tests/test_ip.cc +++ b/unit_tests/test_ip.cc @@ -631,6 +631,58 @@ TEST_CASE("IP Space Int", "[libswoc][ip][ipspace]") { CHECK(space.end() != space.find(IP4Addr{"100.0.4.16"})); CHECK(space.end() != space.find(IPAddr{"100.0.4.16"})); CHECK(space.end() != space.find(IPAddr{IPEndpoint{"100.0.4.16:80"}})); + + auto b2 = [] (unsigned &lhs, unsigned const& rhs) { lhs = rhs; return true; }; + + std::array<std::tuple<TextView, unsigned>, 31> r2 = { + { + {"2001:4998:58:400::1/128", 1} // 1 + , {"2001:4998:58:400::2/128", 1} + , {"2001:4998:58:400::3/128", 1} + , {"2001:4998:58:400::4/128", 1} + , {"2001:4998:58:400::5/128", 1} + , {"2001:4998:58:400::6/128", 1} + , {"2001:4998:58:400::7/128", 1} + , {"2001:4998:58:400::8/128", 1} + , {"2001:4998:58:400::9/128", 1} + , {"2001:4998:58:400::A/127", 1} + , {"2001:4998:58:400::10/127", 1} // 2 + , {"2001:4998:58:400::12/127", 1} + , {"2001:4998:58:400::14/127", 1} + , {"2001:4998:58:400::16/127", 1} + , {"2001:4998:58:400::18/127", 1} + , {"2001:4998:58:400::1a/127", 1} + , {"2001:4998:58:400::1c/127", 1} + , {"2001:4998:58:400::1e/127", 1} + , {"2001:4998:58:400::20/127", 1} + , {"2001:4998:58:400::22/127", 1} + , {"2001:4998:58:400::24/127", 1} + , {"2001:4998:58:400::26/127", 1} + , {"2001:4998:58:400::2a/127", 1} // 3 + , {"2001:4998:58:400::2c/127", 1} + , {"2001:4998:58:400::2e/127", 1} + , {"2001:4998:58:400::30/127", 1} + , {"2001:4998:58:400::140/127", 1} // 4 + , {"2001:4998:58:400::142/127", 1} + , {"2001:4998:58:400::146/127", 1} // 5 + , {"2001:4998:58:400::148/127", 1} + , {"2001:4998:58:400::150/127", 1} // 6 + }}; + + space.clear(); + for (auto &&[text, value] : r2) { + IPRange range{text}; + space.blend(IPRange{text}, value, b2); + } + CHECK(6 == space.count()); + for (auto &&[text, value] : r2) { + IPRange range{text}; + space.blend(IPRange{text}, value, b2); + } + CHECK(6 == space.count()); + // Drop a non-intersecting range between existing ranges 1 and 2, make sure both sides coalesce. + space.blend(IPRange{"2001:4998:58:400::C/126"_tv}, 1, b2); + CHECK(5 == space.count()); } TEST_CASE("IPSpace bitset", "[libswoc][ipspace][bitset]") { @@ -736,155 +788,3 @@ TEST_CASE("IPSpace docJJ", "[libswoc][ipspace][docJJ]") { ++idx; } } - -#if 0 -TEST_CASE("IP Space YNETDB", "[libswoc][ipspace][ynetdb]") { - std::set<std::string_view> Locations; - std::set<std::string_view> Owners; - std::set<std::string_view> Descriptions; - swoc::MemArena arena; - auto Localize = [&](TextView const&view) -> TextView { - auto span = arena.alloc(view.size() + 1).rebind<char>(); - memcpy(span, view); - span[view.size()] = '\0'; - return span.view(); - }; - - struct Payload { - std::string_view _colo; - std::string_view _owner; - std::string_view _descr; - unsigned int _internal_p : 1; - unsigned int _prod_p : 1; - unsigned int _corp_p : 1; - unsigned int _flakey_p : 1; - unsigned int _secure_p : 1; - - Payload() { - _internal_p = _prod_p = _corp_p = _flakey_p = _secure_p = false; - } - - bool operator==(Payload const&that) { - return - this->_colo == that._colo && - this->_owner == that._owner && - this->_descr == that._descr && - this->_corp_p == that._corp_p && - this->_internal_p == that._internal_p && - this->_prod_p == that._prod_p && - this->_flakey_p == that._flakey_p && - this->_secure_p == that._secure_p; - } - }; - - using Space = swoc::IPSpace<Payload>; - - Space space; - - std::error_code ec; - swoc::file::path csv{"/tmp/ynetdb.csv"}; - - // Force these so I can easily change the set of tests. - auto start = std::chrono::high_resolution_clock::now(); - auto delta = std::chrono::high_resolution_clock::now() - start; - - auto file_content{swoc::file::load(csv, ec)}; - TextView content{file_content}; - - delta = std::chrono::high_resolution_clock::now() - start; - std::cout << "File load " << delta.count() << "ns or " << std::chrono::duration_cast<std::chrono::milliseconds>(delta).count() - << "ms" << std::endl; - - start = std::chrono::high_resolution_clock::now(); - while (content) { - Payload payload; - - auto line = content.take_prefix_at('\n'); - if (line.trim_if(&isspace).empty() || *line == '#') { - continue; - } - - auto range_token{line.take_prefix_at(',')}; - auto internal_token{line.take_prefix_at(',')}; - auto owner_token{line.take_prefix_at(',')}; - auto colo_token{line.take_prefix_at(',')}; - auto flags_token{line.take_prefix_at(',')}; - auto descr_token{line.take_prefix_at(',')}; - - if (auto spot{Owners.find(owner_token)}; spot != Owners.end()) { - payload._owner = *spot; - } else { - payload._owner = Localize(owner_token); - Owners.insert(payload._owner); - } - - if (auto spot{Descriptions.find(owner_token)}; spot != Descriptions.end()) { - payload._descr = *spot; - } else { - payload._descr = Localize(owner_token); - Descriptions.insert(payload._descr); - } - - if (auto spot{Locations.find(owner_token)}; spot != Locations.end()) { - payload._colo = *spot; - } else { - payload._colo = Localize(owner_token); - Locations.insert(payload._colo); - } - - static constexpr TextView INTERNAL_TAG{"yahoo"}; - static constexpr TextView FLAKEY_TAG{"flakey"}; - static constexpr TextView PROD_TAG{"prod"}; - static constexpr TextView CORP_TAG{"corp"}; - static constexpr TextView SECURE_TAG { "secure" }; - - if (0 == strcasecmp(internal_token, INTERNAL_TAG)) { - payload._internal_p = true; - } - - if (flags_token != "-"_sv) { - while (flags_token) { - auto key = flags_token.take_prefix_at(';').trim_if(&isspace); - if (0 == strcasecmp(key, FLAKEY_TAG)) { - payload._flakey_p = true; - } else if (0 == strcasecmp(key, PROD_TAG)) { - payload._prod_p = true; - } else if (0 == strcasecmp(key, CORP_TAG)) { - payload._corp_p = true; - } else if (0 == strcasecmp(key, SECURE_TAG)) { - payload._secure_p = true; - } else { - throw std::domain_error("Bad flag tag"); - } - } - } - - auto BF = [](Payload&lhs, Payload const&rhs) -> bool { - if (! lhs._colo.empty()) { - std::cout << "Overlap " << lhs._descr << " with " << rhs._descr << std::endl; - } - return true; - }; - swoc::LocalBufferWriter<1024> bw; - swoc::IPRange range; - if (range.load(range_token)) { - space.blend(range, payload, BF); - } else { - std::cout << "Failed to parse range " << range_token << std::endl; - } - } - delta = std::chrono::high_resolution_clock::now() - start; - std::cout << "Space load " << delta.count() << "ns or " << std::chrono::duration_cast<std::chrono::milliseconds>(delta).count() - << "ms" << std::endl; - - static constexpr size_t N_LOOPS = 1000000; - start = std::chrono::high_resolution_clock::now(); - for ( size_t idx = 0 ; idx < N_LOOPS ; ++idx ) { - space.find(IP4Addr(static_cast<in_addr_t>(idx * 2500))); - } - delta = std::chrono::high_resolution_clock::now() - start; - std::cout << "Space IP4 lookup " << delta.count() << "ns or " << std::chrono::duration_cast<std::chrono::milliseconds>(delta).count() - << "ms" << std::endl; - -} -#endif