Compare commits

...

6 Commits

Author SHA1 Message Date
Daniel Paz-Soldan 862ba15b00 Bump to 5.20.0-alpha.4 2018-10-23 10:38:32 -04:00
Daniel Paz-Soldan c62d250399 Disable mld cucumber tests 2018-10-23 10:36:41 -04:00
Daniel Paz-Soldan c3085d5dc7 Bump to 5.20.0-alpha.3 2018-10-22 14:49:58 -04:00
Daniel Patterson 3c0cec3a2c Expose new --mmap switch (use_mmap in NodeJS), and run test suite in this mode, as well as shared mem mode. 2018-10-22 14:46:43 -04:00
Daniel Patterson 2b18954cb9 mmap tarfiles directly when mmapping is enabled 2018-10-22 14:27:04 -04:00
Daniel Patterson cb5d596a5a Make packed bool vector datafiles match in-memory layout for vector_view. 2018-10-22 14:26:45 -04:00
24 changed files with 487 additions and 258 deletions
+6 -1
View File
@@ -1,7 +1,12 @@
# UNRELEASED
- Changes from 5.19.0:
- Features:
- ADDED: direct mmapping of datafiles is now supported via the `-mmap` switch. [#5242](https://github.com/Project-OSRM/osrm-backend/pull/5242)
- REMOVED: the previous `--memory_file` switch is now deprecated and will fallback to `--mmap` [#5242](https://github.com/Project-OSRM/osrm-backend/pull/5242)
# 5.19.0
- Changes from 5.18.0:
- Optimizations:
- CHANGED: Map matching is now almost twice as fast. [#5060](https://github.com/Project-OSRM/osrm-backend/pull/5060)
- CHANGED: Use Grisu2 for serializing floating point numbers. [#5188](https://github.com/Project-OSRM/osrm-backend/pull/5188)
- ADDED: Node bindings can return pre-rendered JSON buffer. [#5189](https://github.com/Project-OSRM/osrm-backend/pull/5189)
- Bugfixes:
+53 -3
View File
@@ -84,7 +84,47 @@ class OSRMDirectLoader extends OSRMBaseLoader {
throw new Error(util.format('osrm-routed %s: %s', errorReason(err), err.cmd));
}
});
callback();
this.child.readyFunc = (data) => {
if (/running and waiting for requests/.test(data)) {
this.child.stdout.removeListener('data', this.child.readyFunc);
callback();
}
};
this.child.stdout.on('data',this.child.readyFunc);
}
};
class OSRMmmapLoader extends OSRMBaseLoader {
constructor (scope) {
super(scope);
}
load (inputFile, callback) {
this.inputFile = inputFile;
this.shutdown(() => {
this.launch(callback);
});
}
osrmUp (callback) {
if (this.osrmIsRunning()) return callback(new Error("osrm-routed already running!"));
const command_arguments = util.format('%s -p %d -i %s -a %s --mmap', this.inputFile, this.scope.OSRM_PORT, this.scope.OSRM_IP, this.scope.ROUTING_ALGORITHM);
this.child = this.scope.runBin('osrm-routed', command_arguments, this.scope.environment, (err) => {
if (err && err.signal !== 'SIGINT') {
this.child = null;
throw new Error(util.format('osrm-routed %s: %s', errorReason(err), err.cmd));
}
});
this.child.readyFunc = (data) => {
if (/running and waiting for requests/.test(data)) {
this.child.stdout.removeListener('data', this.child.readyFunc);
callback();
}
};
this.child.stdout.on('data',this.child.readyFunc);
}
};
@@ -135,22 +175,32 @@ class OSRMLoader {
this.scope = scope;
this.sharedLoader = new OSRMDatastoreLoader(this.scope);
this.directLoader = new OSRMDirectLoader(this.scope);
this.mmapLoader = new OSRMmmapLoader(this.scope);
this.method = scope.DEFAULT_LOAD_METHOD;
}
load (inputFile, callback) {
if (!this.loader) {
this.loader = {shutdown: (cb) => cb() };
}
if (this.method === 'datastore') {
this.directLoader.shutdown((err) => {
this.loader.shutdown((err) => {
if (err) return callback(err);
this.loader = this.sharedLoader;
this.sharedLoader.load(inputFile, callback);
});
} else if (this.method === 'directly') {
this.sharedLoader.shutdown((err) => {
this.loader.shutdown((err) => {
if (err) return callback(err);
this.loader = this.directLoader;
this.directLoader.load(inputFile, callback);
});
} else if (this.method === 'mmap') {
this.loader.shutdown((err) => {
if (err) return callback(err);
this.loader = this.mmapLoader;
this.mmapLoader.load(inputFile, callback);
});
} else {
callback(new Error('*** Unknown load method ' + method));
}
+1 -1
View File
@@ -32,7 +32,7 @@ module.exports = function () {
this.DEFAULT_ENVIRONMENT = Object.assign({STXXLCFG: stxxl_config}, process.env);
this.DEFAULT_PROFILE = 'bicycle';
this.DEFAULT_INPUT_FORMAT = 'osm';
this.DEFAULT_LOAD_METHOD = 'datastore';
this.DEFAULT_LOAD_METHOD = process.argv[process.argv.indexOf('-m') +1].match('mmap') ? 'mmap' : 'datastore';
this.DEFAULT_ORIGIN = [1,1];
this.OSM_USER = 'osrm';
this.OSM_UID = 1;
@@ -10,6 +10,7 @@
#include <boost/iostreams/device/mapped_file.hpp>
#include <memory>
#include <string>
namespace osrm
{
@@ -24,8 +25,7 @@ namespace datafacade
class MMapMemoryAllocator : public ContiguousBlockAllocator
{
public:
explicit MMapMemoryAllocator(const storage::StorageConfig &config,
const boost::filesystem::path &memory_file);
explicit MMapMemoryAllocator(const storage::StorageConfig &config);
~MMapMemoryAllocator() override final;
// interface to give access to the datafacades
@@ -33,8 +33,8 @@ class MMapMemoryAllocator : public ContiguousBlockAllocator
private:
storage::SharedDataIndex index;
util::vector_view<char> mapped_memory;
boost::iostreams::mapped_file mapped_memory_file;
std::vector<boost::iostreams::mapped_file> mapped_memory_files;
std::string rtree_filename;
};
} // namespace datafacade
+5 -6
View File
@@ -32,9 +32,8 @@ class ExternalProvider final : public DataFacadeProvider<AlgorithmT, FacadeT>
public:
using Facade = typename DataFacadeProvider<AlgorithmT, FacadeT>::Facade;
ExternalProvider(const storage::StorageConfig &config,
const boost::filesystem::path &memory_file)
: facade_factory(std::make_shared<datafacade::MMapMemoryAllocator>(config, memory_file))
ExternalProvider(const storage::StorageConfig &config)
: facade_factory(std::make_shared<datafacade::MMapMemoryAllocator>(config))
{
}
@@ -94,7 +93,7 @@ class WatchingProvider : public DataFacadeProvider<AlgorithmT, FacadeT>
return watchdog.Get(params);
}
};
}
} // namespace detail
template <typename AlgorithmT>
using DataFacadeProvider = detail::DataFacadeProvider<AlgorithmT, DataFacade>;
@@ -104,7 +103,7 @@ template <typename AlgorithmT>
using ImmutableProvider = detail::ImmutableProvider<AlgorithmT, DataFacade>;
template <typename AlgorithmT>
using ExternalProvider = detail::ExternalProvider<AlgorithmT, DataFacade>;
}
}
} // namespace engine
} // namespace osrm
#endif
+11 -7
View File
@@ -63,12 +63,16 @@ template <typename Algorithm> class Engine final : public EngineInterface
<< "\" with algorithm " << routing_algorithms::name<Algorithm>();
facade_provider = std::make_unique<WatchingProvider<Algorithm>>(config.dataset_name);
}
else if (!config.memory_file.empty())
else if (!config.memory_file.empty() || config.use_mmap)
{
util::Log(logDEBUG) << "Using memory mapped filed at " << config.memory_file
<< " with algorithm " << routing_algorithms::name<Algorithm>();
facade_provider = std::make_unique<ExternalProvider<Algorithm>>(config.storage_config,
config.memory_file);
if (!config.memory_file.empty())
{
util::Log(logWARNING)
<< "The 'memory_file' option is DEPRECATED - using direct mmaping instead";
}
util::Log(logDEBUG) << "Using direct memory mapping with algorithm "
<< routing_algorithms::name<Algorithm>();
facade_provider = std::make_unique<ExternalProvider<Algorithm>>(config.storage_config);
}
else
{
@@ -134,7 +138,7 @@ template <typename Algorithm> class Engine final : public EngineInterface
const plugins::MatchPlugin match_plugin;
const plugins::TilePlugin tile_plugin;
};
}
}
} // namespace engine
} // namespace osrm
#endif // OSRM_IMPL_HPP
+3 -2
View File
@@ -89,11 +89,12 @@ struct EngineConfig final
int max_alternatives = 3; // set an arbitrary upper bound; can be adjusted by user
bool use_shared_memory = true;
boost::filesystem::path memory_file;
bool use_mmap = true;
Algorithm algorithm = Algorithm::CH;
std::string verbosity;
std::string dataset_name;
};
}
}
} // namespace engine
} // namespace osrm
#endif // SERVER_CONFIG_HPP
+16
View File
@@ -142,6 +142,10 @@ inline engine_config_ptr argumentsToEngineConfig(const Nan::FunctionCallbackInfo
if (shared_memory.IsEmpty())
return engine_config_ptr();
auto mmap_memory = params->Get(Nan::New("mmap_memory").ToLocalChecked());
if (mmap_memory.IsEmpty())
return engine_config_ptr();
if (!memory_file->IsUndefined())
{
if (path->IsUndefined())
@@ -190,6 +194,18 @@ inline engine_config_ptr argumentsToEngineConfig(const Nan::FunctionCallbackInfo
return engine_config_ptr();
}
}
if (!mmap_memory->IsUndefined())
{
if (mmap_memory->IsBoolean())
{
engine_config->use_mmap = Nan::To<bool>(mmap_memory).FromJust();
}
else
{
Nan::ThrowError("mmap_memory option must be a boolean");
return engine_config_ptr();
}
}
if (path->IsUndefined() && !engine_config->use_shared_memory)
{
+8 -3
View File
@@ -16,10 +16,15 @@ struct Block
{
std::uint64_t num_entries;
std::uint64_t byte_size;
std::uint64_t offset;
Block() : num_entries(0), byte_size(0) {}
Block() : num_entries(0), byte_size(0), offset(0) {}
Block(std::uint64_t num_entries, std::uint64_t byte_size, std::uint64_t offset)
: num_entries(num_entries), byte_size(byte_size), offset(offset)
{
}
Block(std::uint64_t num_entries, std::uint64_t byte_size)
: num_entries(num_entries), byte_size(byte_size)
: num_entries(num_entries), byte_size(byte_size), offset(0)
{
}
};
@@ -29,7 +34,7 @@ using NamedBlock = std::tuple<std::string, Block>;
template <typename T> Block make_block(uint64_t num_entries)
{
static_assert(sizeof(T) % alignof(T) == 0, "aligned T* can't be used as an array pointer");
return Block{num_entries, sizeof(T) * num_entries};
return Block{num_entries, sizeof(T) * num_entries, 0};
}
}
}
+24 -14
View File
@@ -30,22 +30,29 @@ namespace serialization
namespace detail
{
template <typename T, typename BlockT = unsigned char>
inline BlockT packBits(const T &data, std::size_t index, std::size_t count)
inline BlockT packBits(const T &data, std::size_t base_index, const std::size_t count)
{
static_assert(std::is_same<typename T::value_type, bool>::value, "value_type is not bool");
// Note: if this packing is changed, be sure to update vector_view<bool>
// as well, so that on-disk and in-memory layouts match.
BlockT value = 0;
for (std::size_t bit = 0; bit < count; ++bit, ++index)
value = (value << 1) | data[index];
for (std::size_t bit = 0; bit < count; ++bit)
{
value |= (data[base_index + bit] ? BlockT{1} : BlockT{0}) << bit;
}
return value;
}
template <typename T, typename BlockT = unsigned char>
inline void unpackBits(T &data, std::size_t index, std::size_t count, BlockT value)
inline void
unpackBits(T &data, const std::size_t base_index, const std::size_t count, const BlockT value)
{
static_assert(std::is_same<typename T::value_type, bool>::value, "value_type is not bool");
const BlockT mask = BlockT{1} << (count - 1);
for (std::size_t bit = 0; bit < count; value <<= 1, ++bit, ++index)
data[index] = value & mask;
for (std::size_t bit = 0; bit < count; ++bit)
{
data[base_index + bit] = value & (BlockT{1} << bit);
}
}
template <typename VectorT>
@@ -90,7 +97,7 @@ void writeBoolVector(tar::FileWriter &writer, const std::string &name, const Vec
boost::make_function_input_iterator(encode_function, boost::infinite()),
number_of_blocks);
}
}
} // namespace detail
/* All vector formats here use the same on-disk format.
* This is important because we want to be able to write from a vector
@@ -266,14 +273,17 @@ template <typename K, typename V> void write(io::BufferWriter &writer, const std
}
}
inline void read(io::BufferReader &reader, DataLayout &layout) { read(reader, layout.blocks); }
inline void write(io::BufferWriter &writer, const DataLayout &layout)
inline void read(io::BufferReader &reader, std::unique_ptr<BaseDataLayout> &layout)
{
write(writer, layout.blocks);
}
}
read(reader, layout->blocks);
}
inline void write(io::BufferWriter &writer, const std::unique_ptr<BaseDataLayout> &layout)
{
write(writer, layout->blocks);
}
} // namespace serialization
} // namespace storage
} // namespace osrm
#endif
+10 -10
View File
@@ -20,7 +20,7 @@ class SharedDataIndex
struct AllocatedRegion
{
char *memory_ptr;
DataLayout layout;
std::unique_ptr<BaseDataLayout> layout;
};
SharedDataIndex() = default;
@@ -29,10 +29,10 @@ class SharedDataIndex
// Build mapping from block name to region
for (auto index : util::irange<std::uint32_t>(0, regions.size()))
{
regions[index].layout.List("",
boost::make_function_output_iterator([&](const auto &name) {
block_to_region[name] = index;
}));
regions[index].layout->List("",
boost::make_function_output_iterator([&](const auto &name) {
block_to_region[name] = index;
}));
}
}
@@ -40,32 +40,32 @@ class SharedDataIndex
{
for (const auto &region : regions)
{
region.layout.List(name_prefix, out);
region.layout->List(name_prefix, out);
}
}
template <typename T> auto GetBlockPtr(const std::string &name) const
{
const auto &region = GetBlockRegion(name);
return region.layout.GetBlockPtr<T>(region.memory_ptr, name);
return reinterpret_cast<T *>(region.layout->GetBlockPtr(region.memory_ptr, name));
}
template <typename T> auto GetBlockPtr(const std::string &name)
{
const auto &region = GetBlockRegion(name);
return region.layout.GetBlockPtr<T>(region.memory_ptr, name);
return reinterpret_cast<T *>(region.layout->GetBlockPtr(region.memory_ptr, name));
}
std::size_t GetBlockEntries(const std::string &name) const
{
const auto &region = GetBlockRegion(name);
return region.layout.GetBlockEntries(name);
return region.layout->GetBlockEntries(name);
}
std::size_t GetBlockSize(const std::string &name) const
{
const auto &region = GetBlockRegion(name);
return region.layout.GetBlockSize(name);
return region.layout->GetBlockSize(name);
}
private:
+120 -36
View File
@@ -20,12 +20,12 @@ namespace osrm
namespace storage
{
class DataLayout;
class BaseDataLayout;
namespace serialization
{
inline void read(io::BufferReader &reader, DataLayout &layout);
inline void read(io::BufferReader &reader, std::unique_ptr<BaseDataLayout> &layout);
inline void write(io::BufferWriter &writer, const DataLayout &layout);
inline void write(io::BufferWriter &writer, const std::unique_ptr<BaseDataLayout> &layout);
} // namespace serialization
namespace detail
@@ -54,43 +54,20 @@ inline std::string trimName(const std::string &name_prefix, const std::string &n
}
} // namespace detail
class DataLayout
class BaseDataLayout
{
public:
DataLayout() : blocks{} {}
virtual ~BaseDataLayout() = default;
inline void SetBlock(const std::string &name, Block block) { blocks[name] = std::move(block); }
virtual inline void SetBlock(const std::string &name, Block block) = 0;
inline uint64_t GetBlockEntries(const std::string &name) const
{
return GetBlock(name).num_entries;
}
virtual inline uint64_t GetBlockEntries(const std::string &name) const = 0;
inline uint64_t GetBlockSize(const std::string &name) const { return GetBlock(name).byte_size; }
virtual inline uint64_t GetBlockSize(const std::string &name) const = 0;
inline bool HasBlock(const std::string &name) const
{
return blocks.find(name) != blocks.end();
}
virtual inline bool HasBlock(const std::string &name) const = 0;
inline uint64_t GetSizeOfLayout() const
{
uint64_t result = 0;
for (const auto &name_and_block : blocks)
{
result += GetBlockSize(name_and_block.first) + BLOCK_ALIGNMENT;
}
return result;
}
template <typename T> inline T *GetBlockPtr(char *shared_memory, const std::string &name) const
{
static_assert(BLOCK_ALIGNMENT % std::alignment_of<T>::value == 0,
"Datatype does not fit alignment constraints.");
char *ptr = (char *)GetAlignedBlockPtr(shared_memory, name);
return (T *)ptr;
}
virtual inline uint64_t GetSizeOfLayout() const = 0;
// Depending on the name prefix this function either lists all blocks with the same prefix
// or all entries in the sub-directory.
@@ -115,9 +92,59 @@ class DataLayout
}
}
virtual inline void *GetBlockPtr(char *shared_memory, const std::string &name) const = 0;
std::map<std::string, Block> blocks;
};
class DataLayout final : public BaseDataLayout
{
public:
inline void SetBlock(const std::string &name, Block block) override final
{
blocks[name] = std::move(block);
}
inline uint64_t GetBlockEntries(const std::string &name) const override final
{
return GetBlock(name).num_entries;
}
inline uint64_t GetBlockSize(const std::string &name) const override final
{
return GetBlock(name).byte_size;
}
inline bool HasBlock(const std::string &name) const override final
{
return blocks.find(name) != blocks.end();
}
inline uint64_t GetSizeOfLayout() const override final
{
uint64_t result = 0;
for (const auto &name_and_block : blocks)
{
result += GetBlockSize(name_and_block.first) + BLOCK_ALIGNMENT;
}
return result;
}
inline void *GetBlockPtr(char *shared_memory, const std::string &name) const override final
{
// TODO: re-enable this alignment checking somehow
// static_assert(BLOCK_ALIGNMENT % std::alignment_of<T>::value == 0,
// "Datatype does not fit alignment constraints.");
char *ptr = (char *)GetAlignedBlockPtr(shared_memory, name);
return ptr;
}
private:
friend void serialization::read(io::BufferReader &reader, DataLayout &layout);
friend void serialization::write(io::BufferWriter &writer, const DataLayout &layout);
friend void serialization::read(io::BufferReader &reader,
std::unique_ptr<BaseDataLayout> &layout);
friend void serialization::write(io::BufferWriter &writer,
const std::unique_ptr<BaseDataLayout> &layout);
const Block &GetBlock(const std::string &name) const
{
@@ -157,7 +184,64 @@ class DataLayout
}
static constexpr std::size_t BLOCK_ALIGNMENT = 64;
std::map<std::string, Block> blocks;
};
class TarDataLayout final : public BaseDataLayout
{
public:
inline void SetBlock(const std::string &name, Block block) override final
{
blocks[name] = std::move(block);
}
inline uint64_t GetBlockEntries(const std::string &name) const override final
{
return GetBlock(name).num_entries;
}
inline uint64_t GetBlockSize(const std::string &name) const override final
{
return GetBlock(name).byte_size;
}
inline bool HasBlock(const std::string &name) const override final
{
return blocks.find(name) != blocks.end();
}
inline uint64_t GetSizeOfLayout() const override final
{
uint64_t result = 0;
for (const auto &name_and_block : blocks)
{
result += GetBlockSize(name_and_block.first);
}
return result;
}
inline void *GetBlockPtr(char *memory_ptr, const std::string &name) const override final
{
auto offset = GetBlock(name).offset;
const auto offset_memory = memory_ptr + offset;
return offset_memory;
}
private:
friend void serialization::read(io::BufferReader &reader,
std::unique_ptr<BaseDataLayout> &layout);
friend void serialization::write(io::BufferWriter &writer,
const std::unique_ptr<BaseDataLayout> &layout);
const Block &GetBlock(const std::string &name) const
{
auto iter = blocks.find(name);
if (iter == blocks.end())
{
throw util::exception("Could not find block " + name);
}
return iter->second;
}
};
struct SharedRegion
+7 -3
View File
@@ -46,11 +46,15 @@ class Storage
Storage(StorageConfig config);
int Run(int max_wait, const std::string &name, bool only_metric);
void PopulateStaticLayout(DataLayout &layout);
void PopulateUpdatableLayout(DataLayout &layout);
void PopulateStaticData(const SharedDataIndex &index);
void PopulateUpdatableData(const SharedDataIndex &index);
void PopulateLayout(std::unique_ptr<storage::BaseDataLayout> &layout,
std::vector<std::pair<bool, boost::filesystem::path>> files);
std::string PopulateLayoutWithRTree(std::unique_ptr<storage::BaseDataLayout> &layout);
void readBlocks(const boost::filesystem::path &path,
std::unique_ptr<storage::BaseDataLayout> &layout);
std::vector<std::pair<bool, boost::filesystem::path>> GetUpdatableFiles();
std::vector<std::pair<bool, boost::filesystem::path>> GetStaticFiles();
private:
StorageConfig config;
+17 -16
View File
@@ -15,14 +15,14 @@ namespace util
namespace detail
{
template <typename T, typename RegionT>
util::vector_view<T> mmapFile(const boost::filesystem::path &file, RegionT &region)
template <typename T, typename MmapContainerT>
util::vector_view<T> mmapFile(const boost::filesystem::path &file, MmapContainerT &mmap_container)
{
try
{
region.open(file);
std::size_t num_objects = region.size() / sizeof(T);
auto data_ptr = region.data();
mmap_container.open(file);
std::size_t num_objects = mmap_container.size() / sizeof(T);
auto data_ptr = mmap_container.data();
BOOST_ASSERT(reinterpret_cast<uintptr_t>(data_ptr) % alignof(T) == 0);
return util::vector_view<T>(reinterpret_cast<T *>(data_ptr), num_objects);
}
@@ -34,9 +34,10 @@ util::vector_view<T> mmapFile(const boost::filesystem::path &file, RegionT &regi
}
}
template <typename T, typename RegionT>
util::vector_view<T>
mmapFile(const boost::filesystem::path &file, RegionT &region, const std::size_t size)
template <typename T, typename MmapContainerT>
util::vector_view<T> mmapFile(const boost::filesystem::path &file,
MmapContainerT &mmap_container,
const std::size_t size)
{
try
{
@@ -45,10 +46,10 @@ mmapFile(const boost::filesystem::path &file, RegionT &region, const std::size_t
params.path = file.string();
params.flags = boost::iostreams::mapped_file::readwrite;
params.new_file_size = size;
region.open(params);
mmap_container.open(params);
std::size_t num_objects = size / sizeof(T);
auto data_ptr = region.data();
auto data_ptr = mmap_container.data();
BOOST_ASSERT(reinterpret_cast<uintptr_t>(data_ptr) % alignof(T) == 0);
return util::vector_view<T>(reinterpret_cast<T *>(data_ptr), num_objects);
}
@@ -63,24 +64,24 @@ mmapFile(const boost::filesystem::path &file, RegionT &region, const std::size_t
template <typename T>
util::vector_view<const T> mmapFile(const boost::filesystem::path &file,
boost::iostreams::mapped_file_source &region)
boost::iostreams::mapped_file_source &mmap_container)
{
return detail::mmapFile<const T>(file, region);
return detail::mmapFile<const T>(file, mmap_container);
}
template <typename T>
util::vector_view<T> mmapFile(const boost::filesystem::path &file,
boost::iostreams::mapped_file &region)
boost::iostreams::mapped_file &mmap_container)
{
return detail::mmapFile<T>(file, region);
return detail::mmapFile<T>(file, mmap_container);
}
template <typename T>
util::vector_view<T> mmapFile(const boost::filesystem::path &file,
boost::iostreams::mapped_file &region,
boost::iostreams::mapped_file &mmap_container,
std::size_t size)
{
return detail::mmapFile<T>(file, region, size);
return detail::mmapFile<T>(file, mmap_container, size);
}
}
}
+13
View File
@@ -195,6 +195,8 @@ template <> class vector_view<bool>
{
BOOST_ASSERT_MSG(index < m_size, "invalid size");
const std::size_t bucket = index / WORD_BITS;
// Note: ordering of bits here should match packBits in storage/serialization.hpp
// so that directly mmap-ing data is possible
const auto offset = index % WORD_BITS;
return m_ptr[bucket] & (static_cast<Word>(1) << offset);
}
@@ -224,11 +226,22 @@ template <> class vector_view<bool>
{
BOOST_ASSERT(index < m_size);
const auto bucket = index / WORD_BITS;
// Note: ordering of bits here should match packBits in storage/serialization.hpp
// so that directly mmap-ing data is possible
const auto offset = index % WORD_BITS;
return reference{m_ptr + bucket, static_cast<Word>(1) << offset};
}
template <typename T> friend void swap(vector_view<T> &, vector_view<T> &) noexcept;
friend std::ostream &operator<<(std::ostream &os, const vector_view<bool> &rhs)
{
for (std::size_t i = 0; i < rhs.size(); ++i)
{
os << (i > 0 ? " " : "") << rhs.at(i);
}
return os;
}
};
// Both vector_view<T> and the vector_view<bool> specializations share this impl.
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "osrm",
"version": "5.20.0-alpha.2",
"version": "5.20.0-alpha.4",
"private": false,
"description": "The Open Source Routing Machine is a high performance routing engine written in C++14 designed to run on OpenStreetMap data.",
"dependencies": {
@@ -18,7 +18,7 @@
},
"scripts": {
"lint": "node ./node_modules/eslint/bin/eslint.js -c ./.eslintrc features/step_definitions/ features/support/",
"test": "npm run lint && node ./node_modules/cucumber/bin/cucumber.js features/ -p verify",
"test": "npm run lint && node ./node_modules/cucumber/bin/cucumber.js features/ -p verify && node ./node_modules/cucumber/bin/cucumber.js features/ -p verify -m mmap",
"clean": "rm -rf test/cache",
"docs": "./scripts/build_api_docs.sh",
"install": "node-pre-gyp install --fallback-to-build=false || ./scripts/node_install.sh",
+38 -32
View File
@@ -1,5 +1,6 @@
#include "engine/datafacade/mmap_memory_allocator.hpp"
#include "storage/block.hpp"
#include "storage/io.hpp"
#include "storage/serialization.hpp"
#include "storage/storage.hpp"
@@ -16,47 +17,52 @@ namespace engine
namespace datafacade
{
MMapMemoryAllocator::MMapMemoryAllocator(const storage::StorageConfig &config,
const boost::filesystem::path &memory_file)
MMapMemoryAllocator::MMapMemoryAllocator(const storage::StorageConfig &config)
{
storage::Storage storage(config);
std::vector<storage::SharedDataIndex::AllocatedRegion> allocated_regions;
if (!boost::filesystem::exists(memory_file))
{
storage::DataLayout initial_layout;
storage.PopulateStaticLayout(initial_layout);
storage.PopulateUpdatableLayout(initial_layout);
std::unique_ptr<storage::BaseDataLayout> fake_layout =
std::make_unique<storage::TarDataLayout>();
auto data_size = initial_layout.GetSizeOfLayout();
// Convert the boost::filesystem::path object into a plain string
// that's stored as a member of this allocator object
rtree_filename = storage.PopulateLayoutWithRTree(fake_layout);
storage::io::BufferWriter writer;
storage::serialization::write(writer, initial_layout);
auto encoded_layout = writer.GetBuffer();
auto total_size = data_size + encoded_layout.size();
mapped_memory = util::mmapFile<char>(memory_file, mapped_memory_file, total_size);
std::copy(encoded_layout.begin(), encoded_layout.end(), mapped_memory.data());
index = storage::SharedDataIndex(
{{mapped_memory.data() + encoded_layout.size(), std::move(initial_layout)}});
storage.PopulateStaticData(index);
storage.PopulateUpdatableData(index);
// Now, we add one more AllocatedRegion, with it's start address as the start
// of the rtree_filename string we've saved. In the fake_layout, we've
// stated that the data is at offset 0, which is where the string starts
// at it's own memory address.
// The syntax &(rtree_filename[0]) gets the memory address of the first char.
// We can't use the convenient `.data()` or `.c_str()` methods, because
// prior to C++17 (which we're not using), those return a `const char *`,
// which isn't compatible with the `char *` that AllocatedRegion expects
// for it's memory_ptr
allocated_regions.push_back({&(rtree_filename[0]), std::move(fake_layout)});
}
else
std::vector<std::pair<bool, boost::filesystem::path>> files = storage.GetStaticFiles();
std::vector<std::pair<bool, boost::filesystem::path>> updatable_files =
storage.GetUpdatableFiles();
files.insert(files.end(), updatable_files.begin(), updatable_files.end());
for (const auto &file : files)
{
mapped_memory = util::mmapFile<char>(memory_file, mapped_memory_file);
storage::DataLayout layout;
storage::io::BufferReader reader(mapped_memory.data(), mapped_memory.size());
storage::serialization::read(reader, layout);
auto layout_size = reader.GetPosition();
index = storage::SharedDataIndex({{mapped_memory.data() + layout_size, std::move(layout)}});
if (boost::filesystem::exists(file.second))
{
std::unique_ptr<storage::BaseDataLayout> layout =
std::make_unique<storage::TarDataLayout>();
boost::iostreams::mapped_file mapped_memory_file;
util::mmapFile<char>(file.second, mapped_memory_file);
mapped_memory_files.push_back(std::move(mapped_memory_file));
storage.readBlocks(file.second, layout);
allocated_regions.push_back({mapped_memory_file.data(), std::move(layout)});
}
}
}
index = storage::SharedDataIndex{std::move(allocated_regions)};
} // namespace datafacade
MMapMemoryAllocator::~MMapMemoryAllocator() {}
@@ -15,20 +15,26 @@ ProcessMemoryAllocator::ProcessMemoryAllocator(const storage::StorageConfig &con
storage::Storage storage(config);
// Calculate the layout/size of the memory block
storage::DataLayout layout;
storage.PopulateStaticLayout(layout);
storage.PopulateUpdatableLayout(layout);
std::vector<std::pair<bool, boost::filesystem::path>> static_files = storage.GetStaticFiles();
std::vector<std::pair<bool, boost::filesystem::path>> updatable_files =
storage.GetUpdatableFiles();
std::unique_ptr<storage::BaseDataLayout> layout = std::make_unique<storage::DataLayout>();
storage.PopulateLayoutWithRTree(layout);
storage.PopulateLayout(layout, static_files);
storage.PopulateLayout(layout, updatable_files);
// Allocate the memory block, then load data from files into it
internal_memory = std::make_unique<char[]>(layout.GetSizeOfLayout());
internal_memory = std::make_unique<char[]>(layout->GetSizeOfLayout());
index = storage::SharedDataIndex({{internal_memory.get(), std::move(layout)}});
std::vector<storage::SharedDataIndex::AllocatedRegion> regions;
regions.push_back({internal_memory.get(), std::move(layout)});
index = {std::move(regions)};
storage.PopulateStaticData(index);
storage.PopulateUpdatableData(index);
}
ProcessMemoryAllocator::~ProcessMemoryAllocator() {}
ProcessMemoryAllocator::~ProcessMemoryAllocator() { /* free(internal_memory) */}
const storage::SharedDataIndex &ProcessMemoryAllocator::GetIndex() { return index; }
@@ -25,7 +25,7 @@ SharedMemoryAllocator::SharedMemoryAllocator(
auto mem = storage::makeSharedMemory(shm_key);
storage::io::BufferReader reader(reinterpret_cast<char *>(mem->Ptr()), mem->Size());
storage::DataLayout layout;
std::unique_ptr<storage::BaseDataLayout> layout = std::make_unique<storage::DataLayout>();
storage::serialization::read(reader, layout);
auto layout_size = reader.GetPosition();
+5 -3
View File
@@ -23,7 +23,9 @@ bool EngineConfig::IsValid() const
unlimited_or_more_than(max_results_nearest, 0) &&
max_alternatives >= 0;
return ((use_shared_memory && all_path_are_empty) || storage_config.IsValid()) && limits_valid;
}
}
return ((use_shared_memory && all_path_are_empty) || (use_mmap && storage_config.IsValid()) ||
storage_config.IsValid()) &&
limits_valid;
}
} // namespace engine
} // namespace osrm
+90 -73
View File
@@ -44,24 +44,6 @@ namespace
{
using Monitor = SharedMonitor<SharedRegionRegister>;
void readBlocks(const boost::filesystem::path &path, DataLayout &layout)
{
tar::FileReader reader(path, tar::FileReader::VerifyFingerprint);
std::vector<tar::FileReader::FileEntry> entries;
reader.List(std::back_inserter(entries));
for (const auto &entry : entries)
{
const auto name_end = entry.name.rfind(".meta");
if (name_end == std::string::npos)
{
auto number_of_elements = reader.ReadElementCount64(entry.name);
layout.SetBlock(entry.name, Block{number_of_elements, entry.size});
}
}
}
struct RegionHandle
{
std::unique_ptr<SharedMemory> memory;
@@ -69,7 +51,8 @@ struct RegionHandle
std::uint16_t shm_key;
};
auto setupRegion(SharedRegionRegister &shared_register, const DataLayout &layout)
auto setupRegion(SharedRegionRegister &shared_register,
const std::unique_ptr<storage::BaseDataLayout> &layout)
{
// This is safe because we have an exclusive lock for all osrm-datastore processes.
auto shm_key = shared_register.ReserveKey();
@@ -90,7 +73,7 @@ auto setupRegion(SharedRegionRegister &shared_register, const DataLayout &layout
auto encoded_static_layout = writer.GetBuffer();
// Allocate shared memory block
auto regions_size = encoded_static_layout.size() + layout.GetSizeOfLayout();
auto regions_size = encoded_static_layout.size() + layout->GetSizeOfLayout();
util::Log() << "Data layout has a size of " << encoded_static_layout.size() << " bytes";
util::Log() << "Allocating shared memory of " << regions_size << " bytes";
auto memory = makeSharedMemory(shm_key, regions_size);
@@ -182,10 +165,29 @@ bool swapData(Monitor &monitor,
return true;
}
}
} // namespace
Storage::Storage(StorageConfig config_) : config(std::move(config_)) {}
void Storage::readBlocks(const boost::filesystem::path &path,
std::unique_ptr<storage::BaseDataLayout> &layout)
{
tar::FileReader reader(path, tar::FileReader::VerifyFingerprint);
std::vector<tar::FileReader::FileEntry> entries;
reader.List(std::back_inserter(entries));
for (const auto &entry : entries)
{
const auto name_end = entry.name.rfind(".meta");
if (name_end == std::string::npos)
{
auto number_of_elements = reader.ReadElementCount64(entry.name);
layout->SetBlock(entry.name, Block{number_of_elements, entry.size, entry.offset});
}
}
}
int Storage::Run(int max_wait, const std::string &dataset_name, bool only_metric)
{
BOOST_ASSERT_MSG(config.IsValid(), "Invalid storage config");
@@ -243,29 +245,35 @@ int Storage::Run(int max_wait, const std::string &dataset_name, bool only_metric
auto static_region = shared_register.GetRegion(region_id);
auto static_memory = makeSharedMemory(static_region.shm_key);
DataLayout static_layout;
std::unique_ptr<storage::BaseDataLayout> static_layout =
std::make_unique<storage::DataLayout>();
io::BufferReader reader(reinterpret_cast<char *>(static_memory->Ptr()),
static_memory->Size());
serialization::read(reader, static_layout);
auto layout_size = reader.GetPosition();
auto *data_ptr = reinterpret_cast<char *>(static_memory->Ptr()) + layout_size;
regions.push_back({data_ptr, static_layout});
regions.push_back({data_ptr, std::move(static_layout)});
readonly_handles.push_back({std::move(static_memory), data_ptr, static_region.shm_key});
}
else
{
DataLayout static_layout;
PopulateStaticLayout(static_layout);
std::unique_ptr<storage::BaseDataLayout> static_layout =
std::make_unique<storage::DataLayout>();
Storage::PopulateLayoutWithRTree(static_layout);
std::vector<std::pair<bool, boost::filesystem::path>> files = Storage::GetStaticFiles();
Storage::PopulateLayout(static_layout, files);
auto static_handle = setupRegion(shared_register, static_layout);
regions.push_back({static_handle.data_ptr, static_layout});
regions.push_back({static_handle.data_ptr, std::move(static_layout)});
handles[dataset_name + "/static"] = std::move(static_handle);
}
DataLayout updatable_layout;
PopulateUpdatableLayout(updatable_layout);
std::unique_ptr<storage::BaseDataLayout> updatable_layout =
std::make_unique<storage::DataLayout>();
std::vector<std::pair<bool, boost::filesystem::path>> files = Storage::GetUpdatableFiles();
Storage::PopulateLayout(updatable_layout, files);
auto updatable_handle = setupRegion(shared_register, updatable_layout);
regions.push_back({updatable_handle.data_ptr, updatable_layout});
regions.push_back({updatable_handle.data_ptr, std::move(updatable_layout)});
handles[dataset_name + "/updatable"] = std::move(updatable_handle);
SharedDataIndex index{std::move(regions)};
@@ -281,24 +289,12 @@ int Storage::Run(int max_wait, const std::string &dataset_name, bool only_metric
return EXIT_SUCCESS;
}
/**
* This function examines all our data files and figures out how much
* memory needs to be allocated, and the position of each data structure
* in that big block. It updates the fields in the DataLayout parameter.
*/
void Storage::PopulateStaticLayout(DataLayout &static_layout)
std::vector<std::pair<bool, boost::filesystem::path>> Storage::GetStaticFiles()
{
{
auto absolute_file_index_path =
boost::filesystem::absolute(config.GetPath(".osrm.fileIndex"));
static_layout.SetBlock("/common/rtree/file_index_path",
make_block<char>(absolute_file_index_path.string().length() + 1));
}
constexpr bool REQUIRED = true;
constexpr bool OPTIONAL = false;
std::vector<std::pair<bool, boost::filesystem::path>> tar_files = {
std::vector<std::pair<bool, boost::filesystem::path>> files = {
{OPTIONAL, config.GetPath(".osrm.cells")},
{OPTIONAL, config.GetPath(".osrm.partition")},
{REQUIRED, config.GetPath(".osrm.icd")},
@@ -310,53 +306,74 @@ void Storage::PopulateStaticLayout(DataLayout &static_layout)
{REQUIRED, config.GetPath(".osrm.maneuver_overrides")},
{REQUIRED, config.GetPath(".osrm.edges")},
{REQUIRED, config.GetPath(".osrm.names")},
{REQUIRED, config.GetPath(".osrm.ramIndex")},
};
{REQUIRED, config.GetPath(".osrm.ramIndex")}};
for (const auto &file : tar_files)
for (const auto &file : files)
{
if (boost::filesystem::exists(file.second))
if (file.first == REQUIRED && !boost::filesystem::exists(file.second))
{
readBlocks(file.second, static_layout);
}
else
{
if (file.first == REQUIRED)
{
throw util::exception("Could not find required filed: " +
std::get<1>(file).string());
}
throw util::exception("Could not find required filed: " + std::get<1>(file).string());
}
}
return files;
}
void Storage::PopulateUpdatableLayout(DataLayout &updatable_layout)
std::vector<std::pair<bool, boost::filesystem::path>> Storage::GetUpdatableFiles()
{
constexpr bool REQUIRED = true;
constexpr bool OPTIONAL = false;
std::vector<std::pair<bool, boost::filesystem::path>> tar_files = {
std::vector<std::pair<bool, boost::filesystem::path>> files = {
{OPTIONAL, config.GetPath(".osrm.mldgr")},
{OPTIONAL, config.GetPath(".osrm.cell_metrics")},
{OPTIONAL, config.GetPath(".osrm.hsgr")},
{REQUIRED, config.GetPath(".osrm.datasource_names")},
{REQUIRED, config.GetPath(".osrm.geometry")},
{REQUIRED, config.GetPath(".osrm.turn_weight_penalties")},
{REQUIRED, config.GetPath(".osrm.turn_duration_penalties")},
};
{REQUIRED, config.GetPath(".osrm.turn_duration_penalties")}};
for (const auto &file : tar_files)
for (const auto &file : files)
{
if (file.first == REQUIRED && !boost::filesystem::exists(file.second))
{
throw util::exception("Could not find required filed: " + std::get<1>(file).string());
}
}
return files;
}
std::string Storage::PopulateLayoutWithRTree(std::unique_ptr<storage::BaseDataLayout> &layout)
{
// Figure out the path to the rtree file (it's not a tar file)
auto absolute_file_index_path = boost::filesystem::absolute(config.GetPath(".osrm.fileIndex"));
// Convert the boost::filesystem::path object into a plain string
// that can then be stored as a member of an allocator object
std::string rtree_filename = absolute_file_index_path.string();
// Here, we hardcode the special file_index_path block name.
// The important bit here is that the "offset" is set to zero
layout->SetBlock("/common/rtree/file_index_path",
make_block<char>(rtree_filename.length() + 1));
return rtree_filename;
}
/**
* This function examines all our data files and figures out how much
* memory needs to be allocated, and the position of each data structure
* in that big block. It updates the fields in the std::unique_ptr<BaseDataLayout> parameter.
*/
void Storage::PopulateLayout(std::unique_ptr<storage::BaseDataLayout> &layout,
std::vector<std::pair<bool, boost::filesystem::path>> files)
{
for (const auto &file : files)
{
if (boost::filesystem::exists(file.second))
{
readBlocks(file.second, updatable_layout);
}
else
{
if (file.first == REQUIRED)
{
throw util::exception("Could not find required filed: " +
std::get<1>(file).string());
}
readBlocks(file.second, layout);
}
}
}
@@ -569,5 +586,5 @@ void Storage::PopulateUpdatableData(const SharedDataIndex &index)
}
}
}
}
}
} // namespace storage
} // namespace osrm
+4 -1
View File
@@ -119,7 +119,10 @@ inline unsigned generateServerProgramOptions(const int argc,
"Load data from shared memory") //
("memory_file",
value<boost::filesystem::path>(&config.memory_file),
"Store data in a memory mapped file rather than in process memory.") //
"DEPRECATED: Will behave the same as --mmap.")(
"mmap,m",
value<bool>(&config.use_mmap)->implicit_value(true)->default_value(false),
"Map datafiles directly, do not use any additional memory.") //
("dataset-name",
value<std::string>(&config.dataset_name),
"Name of the shared memory dataset to connect to.") //
+3 -3
View File
@@ -52,14 +52,14 @@ void listRegions(bool show_blocks)
auto memory = makeSharedMemory(region.shm_key);
io::BufferReader reader(reinterpret_cast<char *>(memory->Ptr()), memory->Size());
DataLayout layout;
std::unique_ptr<BaseDataLayout> layout = std::make_unique<DataLayout>();
serialization::read(reader, layout);
std::vector<std::string> block_names;
layout.List("", std::back_inserter(block_names));
layout->List("", std::back_inserter(block_names));
for (auto &name : block_names)
{
osrm::util::Log() << " " << name << " " << layout.GetBlockSize(name);
osrm::util::Log() << " " << name << " " << layout->GetBlockSize(name);
}
}
}
+34 -31
View File
@@ -15,86 +15,89 @@ using namespace osrm::storage;
BOOST_AUTO_TEST_CASE(layout_write_test)
{
DataLayout layout;
std::unique_ptr<BaseDataLayout> layout = std::make_unique<DataLayout>();
Block block_1{20, 8 * 20};
Block block_2{1, 4 * 1};
Block block_3{100, static_cast<std::uint64_t>(std::ceil(100 / 64.))};
layout.SetBlock("block1", block_1);
layout.SetBlock("block2", block_2);
layout.SetBlock("block3", block_3);
layout->SetBlock("block1", block_1);
layout->SetBlock("block2", block_2);
layout->SetBlock("block3", block_3);
// Canary and alignment change layout size
BOOST_CHECK_GT(layout.GetSizeOfLayout(),
BOOST_CHECK_GT(layout->GetSizeOfLayout(),
block_1.byte_size + block_2.byte_size + block_3.byte_size);
BOOST_CHECK_EQUAL(layout.GetBlockSize("block1"), block_1.byte_size);
BOOST_CHECK_EQUAL(layout.GetBlockSize("block2"), block_2.byte_size);
BOOST_CHECK_EQUAL(layout.GetBlockSize("block3"), block_3.byte_size);
BOOST_CHECK_EQUAL(layout->GetBlockSize("block1"), block_1.byte_size);
BOOST_CHECK_EQUAL(layout->GetBlockSize("block2"), block_2.byte_size);
BOOST_CHECK_EQUAL(layout->GetBlockSize("block3"), block_3.byte_size);
std::vector<char> buffer(layout.GetSizeOfLayout());
std::vector<char> buffer(layout->GetSizeOfLayout());
auto smallest_addr = buffer.data();
auto biggest_addr = buffer.data() + buffer.size();
{
auto block_1_ptr = layout.GetBlockPtr<std::uint64_t>(buffer.data(), "block1");
auto block_2_ptr = layout.GetBlockPtr<std::uint32_t>(buffer.data(), "block2");
auto block_3_ptr = layout.GetBlockPtr<std::uint64_t>(buffer.data(), "block3");
auto block_1_ptr =
reinterpret_cast<std::uint64_t *>(layout->GetBlockPtr(buffer.data(), "block1"));
auto block_2_ptr =
reinterpret_cast<std::uint32_t *>(layout->GetBlockPtr(buffer.data(), "block2"));
auto block_3_ptr =
reinterpret_cast<std::uint64_t *>(layout->GetBlockPtr(buffer.data(), "block3"));
BOOST_CHECK_LT(reinterpret_cast<std::size_t>(smallest_addr),
BOOST_CHECK_LE(reinterpret_cast<std::size_t>(smallest_addr),
reinterpret_cast<std::size_t>(block_1_ptr));
BOOST_CHECK_GT(
reinterpret_cast<std::size_t>(biggest_addr),
reinterpret_cast<std::size_t>(block_1_ptr + layout.GetBlockEntries("block1")));
reinterpret_cast<std::size_t>(block_1_ptr + layout->GetBlockEntries("block1")));
BOOST_CHECK_LT(reinterpret_cast<std::size_t>(smallest_addr),
reinterpret_cast<std::size_t>(block_2_ptr));
BOOST_CHECK_GT(
reinterpret_cast<std::size_t>(biggest_addr),
reinterpret_cast<std::size_t>(block_2_ptr + layout.GetBlockEntries("block2")));
reinterpret_cast<std::size_t>(block_2_ptr + layout->GetBlockEntries("block2")));
BOOST_CHECK_LT(reinterpret_cast<std::size_t>(smallest_addr),
reinterpret_cast<std::size_t>(block_3_ptr));
BOOST_CHECK_GT(reinterpret_cast<std::size_t>(biggest_addr),
reinterpret_cast<std::size_t>(
block_3_ptr + static_cast<std::size_t>(
std::ceil(layout.GetBlockEntries("block3") / 64))));
std::ceil(layout->GetBlockEntries("block3") / 64))));
}
}
BOOST_AUTO_TEST_CASE(layout_list_test)
{
DataLayout layout;
std::unique_ptr<BaseDataLayout> layout = std::make_unique<DataLayout>();
Block block_1{20, 8 * 20};
Block block_2{1, 4 * 1};
Block block_3{100, static_cast<std::uint64_t>(std::ceil(100 / 64.))};
layout.SetBlock("/ch/edge_filter/block1", block_1);
layout.SetBlock("/ch/edge_filter/block2", block_2);
layout.SetBlock("/ch/edge_filter/block3", block_3);
layout.SetBlock("/mld/metrics/0/durations", block_2);
layout.SetBlock("/mld/metrics/0/weights", block_3);
layout.SetBlock("/mld/metrics/1/durations", block_2);
layout.SetBlock("/mld/metrics/1/weights", block_3);
layout->SetBlock("/ch/edge_filter/block1", block_1);
layout->SetBlock("/ch/edge_filter/block2", block_2);
layout->SetBlock("/ch/edge_filter/block3", block_3);
layout->SetBlock("/mld/metrics/0/durations", block_2);
layout->SetBlock("/mld/metrics/0/weights", block_3);
layout->SetBlock("/mld/metrics/1/durations", block_2);
layout->SetBlock("/mld/metrics/1/weights", block_3);
std::vector<std::string> results_1;
std::vector<std::string> results_2;
std::vector<std::string> results_3;
layout.List("/ch/edge_filter", std::back_inserter(results_1));
layout.List("/ch/edge_filter/", std::back_inserter(results_2));
layout.List("/ch/", std::back_inserter(results_3));
layout->List("/ch/edge_filter", std::back_inserter(results_1));
layout->List("/ch/edge_filter/", std::back_inserter(results_2));
layout->List("/ch/", std::back_inserter(results_3));
std::vector<std::string> results_4;
std::vector<std::string> results_5;
std::vector<std::string> results_6;
layout.List("/mld/metrics", std::back_inserter(results_4));
layout.List("/mld/metrics/", std::back_inserter(results_5));
layout.List("/mld/", std::back_inserter(results_6));
layout->List("/mld/metrics", std::back_inserter(results_4));
layout->List("/mld/metrics/", std::back_inserter(results_5));
layout->List("/mld/", std::back_inserter(results_6));
std::vector<std::string> results_7;
layout.List("", std::back_inserter(results_7));
layout->List("", std::back_inserter(results_7));
BOOST_CHECK_EQUAL(results_7.size(), 7);
CHECK_EQUAL_RANGE(