blob: 8ad26be4640f0cac28f6f36f10ff5d74b3ddd2ae [file] [log] [blame]
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 Verizon Media
/** @file
Example of a variant of IPSpace optimized for fast loading.
This will build the flat files if given the --build option.
This will look up addresses from the flat files given the --find option.
Build flat files from "data.csv"
--build data.csv
Lookup some addresses.
--find 172.17.18.19 2001:BADF::0E0E
Build and lookup
--build data.csv --find 172.17.18.19 2001:BADF::0E0E
*/
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <iostream>
#include "swoc/TextView.h"
#include "swoc/swoc_ip.h"
#include "swoc/bwf_ip.h"
#include "swoc/bwf_ex.h"
#include "swoc/bwf_std.h"
#include "swoc/swoc_file.h"
#include "swoc/Errata.h"
using namespace std::literals;
using namespace swoc::literals;
using swoc::TextView;
using swoc::MemSpan;
using swoc::Errata;
using swoc::IPRange;
using swoc::IPAddr;
using swoc::IP4Addr;
using swoc::IP6Addr;
using swoc::IPSpace;
// Temp for error messages.
std::string err_text;
std::error_code errno_code() { return std::error_code(errno, std::system_category()); }
/** Allocate a span of type @a T.
*
* @tparam T Element type.
* @param n Number of elements.
* @return A @c MemSpan<T> with @a n elements.
*/
template < typename T > MemSpan<T> span_alloc(size_t n) {
size_t bytes = sizeof(T) * n;
return { static_cast<T*>(malloc(bytes)), n };
}
// Array type for flat files.
// Each flat file is an instance of this, which is a wrapper over an array of its nodes.
template < typename METRIC, typename PAYLOAD > class IPArray {
public:
struct Node {
METRIC _min;
METRIC _max;
PAYLOAD _payload;
};
/** Load from array already in memory.
*
* @param span Memory containing the array.
*
* The presumption is @a span has been brough in to memory from a file via @c mmap.
*/
IPArray(MemSpan<Node> span) : _nodes(span) {}
/** Load from a @a space.
*
* @param space Populated IPSpace.
*
* This will allocate memory to hold an array that mirrors the data in @a space.
*/
IPArray(IPSpace<PAYLOAD> const& space);
/** Find @a addr.
*
* @param addr Search value.
* @return The node that contains @a addr, or @c nullptr if not found.
*/
Node* find(METRIC const& addr);
Errata store(swoc::file::path const& path);
size_t size() const { return _nodes.size(); }
void* data() const { return _nodes.data(); }
protected:
/// Array memory.
MemSpan<Node> _nodes;
};
// Standard binary search on a sorted array.
template < typename METRIC, typename PAYLOAD > auto IPArray<METRIC, PAYLOAD>::find(METRIC const& addr) -> Node* {
ssize_t lidx = 0;
ssize_t ridx = _nodes.count() - 1;
while (lidx <= ridx) { // still some array left to search.
auto idx = (lidx + ridx) / 2; // Look at the middle element.
auto n = _nodes.data() + idx;
if (addr < n->_min) { // target is to the left
ridx = idx - 1;
} else if (addr > n->_max) { // target is to the right
lidx = idx + 1;
} else { // target is right here, done.
return n;
}
}
return nullptr; // dropped out of the loop -> not found.
}
template < typename METRIC, typename PAYLOAD > IPArray<METRIC, PAYLOAD>::IPArray(IPSpace<PAYLOAD> const& space) {
auto n = space.count(METRIC::AF_value);
_nodes = { span_alloc<Node>(n) }; // In memory array.
auto node = _nodes.data();
// Update the array with all IPv4 ranges.
for ( auto spot = space.begin(METRIC::AF_value), limit = space.end(METRIC::AF_value) ; spot != limit ; ++spot, ++node ) {
auto && [ range, payload ] { *spot };
node->_min = static_cast<METRIC>(range.min());
node->_max = static_cast<METRIC>(range.max());
node->_payload = payload;
}
}
template <typename METRIC, typename PAYLOAD> Errata IPArray<METRIC, PAYLOAD>::store(swoc::file::path const &path) {
auto fd = ::open(path.c_str(), O_CREAT | O_TRUNC | O_WRONLY, 0644);
if (fd >= 0) {
auto written = ::write(fd, _nodes.data(), _nodes.size());
if (written != ssize_t(_nodes.size())) {
return Errata(errno_code(), "Failed to write IP4 output - {} of {} bytes written to '{}'\n", written, _nodes.size(), path);
}
close(fd);
} else {
return Errata(errno_code(), "Failed to open IP4 output '{}'\n", path);
}
return {};
}
using A4 = IPArray<IP4Addr, unsigned>;
using A6 = IPArray<IP6Addr, unsigned>;
// Load the CSV file @a src into @a space.
void build(IPSpace<unsigned> & space, swoc::file::path src) {
std::error_code ec;
auto content = swoc::file::load(src, ec);
TextView text { content };
while (text) {
auto line = text.take_prefix_at('\n');
if ('#' == *line) {
continue;
}
auto addr_token = line.take_prefix_at(',');
IPRange range{addr_token};
space.mark(range, swoc::svtou(line));
}
}
int main(int argc, char const *argv[]) {
swoc::file::path path_4{"/opt/ip4.mem"};
swoc::file::path path_6{"/tmp/ip6.mem"};
swoc::file::path src;
MemSpan<char const*> args{argv, size_t(argc)};
args.remove_prefix(1); // drop executable name.
if (args.empty()) {
exit(0); // nothing to do.
}
// Check if the array files need to be built.
if (0 == strcasecmp("--build"_tv, args.front())) {
IPSpace<unsigned int> space;
args.remove_prefix(1);
while (!args.empty() && ! TextView(std::string_view(args[0])).starts_with("-")) {
build(space, swoc::file::path(args[0]));
args.remove_prefix(1);
}
if ( auto errata = A4(space).store(path_4) ; !errata.is_ok() ) {
std::cerr << errata << std::endl;
exit(1);
}
if (auto errata = A6(space).store(path_6) ; !errata.is_ok()) {
std::cerr << errata << std::endl;
exit(1);
}
}
if (args.empty()) {
exit(0);
}
if (0 == strcasecmp("--find"_tv, args.front())) {
args.remove_prefix(1);
} else {
std::cerr << swoc::bwprint(err_text, "Unrecognized argument '{}'\n", args.front());
exit(1);
}
std::error_code ec;
auto t0 = std::chrono::system_clock::now();
// Make sure the flat files are there.
auto stat_4 = swoc::file::status(path_4, ec);
if (ec) {
std::cerr << swoc::bwprint(err_text, "Flat file for IPv4 '{}' not found. {}\n", path_4, ec);
exit(1);
}
auto stat_6 = swoc::file::status(path_6, ec);
if (ec) {
std::cerr << swoc::bwprint(err_text, "Flat file for IPv6 '{}' not found. {}\n", path_6, ec);
exit(1);
}
// map the flat files in to memory.
auto fsize = swoc::file::file_size(stat_4);
auto fd_4 = ::open(path_4.c_str(), O_RDONLY);
MemSpan<void> mem4 { ::mmap(nullptr, fsize, PROT_READ, MAP_PRIVATE, fd_4, 0), size_t(fsize) };
A4 a_4 { mem4.rebind<A4::Node>() };
fsize = swoc::file::file_size(stat_6);
auto fd_6 = ::open(path_6.c_str(), O_RDONLY);
MemSpan<void> mem6 { ::mmap(nullptr, fsize, PROT_READ, MAP_PRIVATE, fd_6, 0), size_t(fsize) };
A6 a_6 { mem6.rebind<A6::Node>() };
std::cout << swoc::bwprint(err_text, "Mapped files in {} us\n", std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - t0).count());
#if 0
// performance testing.
t0 = std::chrono::system_clock::now();
auto step = ~0U / 10000000;
IP4Addr addr {in_addr_t(1)};
for ( unsigned idx = 0 ; idx < 10000000 ; ++idx ) {
[[maybe_unused]] auto n = a_4.find(addr);
addr = addr.host_order() + step;
}
auto delta = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now() - t0);
std::cout << swoc::bwprint(err_text, "Searched files in {} ns - {} ns / lookup\n", delta.count(), delta.count() / 10000000);
#endif
// Now the in memory flat files can be searched.
while (! args.empty()) {
IPAddr addr;
if (addr.load(args.front())) {
if (addr.is_ip4()) {
auto n = a_4.find(addr.ip4());
if (n) {
std::cout << swoc::bwprint(err_text, "{} -> {}\n", addr, n->_payload);
} else {
std::cout << swoc::bwprint(err_text, "{} not found\n", addr);
}
} else if (addr.is_ip6()) {
auto n = a_6.find(addr.ip6());
if (n) {
std::cout << swoc::bwprint(err_text, "{} -> {}\n", addr, n->_payload);
} else {
std::cout << swoc::bwprint(err_text, "{} not found\n", addr);
}
}
} else {
std::cerr << swoc::bwprint(err_text, "Unrecognized address '{}'\n", args.front());
}
args.remove_prefix(1);
}
return 0;
}