1
0
Fork 0
mirror of https://git.rwth-aachen.de/acs/public/villas/node/ synced 2025-03-09 00:00:00 +01:00

initial commit

This commit is contained in:
Steffen Vogel 2018-08-21 00:25:44 +02:00
parent e5a518275f
commit d7dd73f5f6
26 changed files with 2669 additions and 0 deletions

56
common/.gitlab-ci.yml Normal file
View file

@ -0,0 +1,56 @@
variables:
GIT_STRATEGY: fetch
GIT_SUBMODULE_STRATEGY: recursive
PREFIX: /usr/
DOCKER_TAG_DEV: ${CI_BUILD_REF_SLUG}
DOCKER_IMAGE_DEV: villas/common-dev
# For some reason, GitLab CI prunes the contents of the submodules so we need to restore them.
before_script:
- git submodule foreach git checkout .
stages:
- prepare
- build
- test
# Stage: prepare
##############################################################################
# Build docker image which is used to build & test VILLASnode
docker-dev:
stage: prepare
script:
- docker build -t ${DOCKER_IMAGE_DEV}:${DOCKER_TAG_DEV} .
tags:
- shell
- linux
# Stage: build
##############################################################################
build:source:
stage: build
script:
- mkdir build && cd build && cmake .. && make -j8
artifacts:
expire_in: 1 week
name: ${CI_PROJECT_NAME}-${CI_BUILD_REF}
paths:
- build/
image: ${DOCKER_IMAGE_DEV}:${DOCKER_TAG_DEV}
tags:
- docker
# Stage: test
##############################################################################
#test:unit:
# stage: test
# dependencies:
# - build:source
# script:
# - build/tests/unit-tests --filter 'graph/*'
# image: ${DOCKER_IMAGE_DEV}:${DOCKER_TAG_DEV}
# tags:
# - docker

6
common/.gitmodules vendored Normal file
View file

@ -0,0 +1,6 @@
[submodule "thirdparty/spdlog"]
path = thirdparty/spdlog
url = https://github.com/gabime/spdlog.git
[submodule "thirdparty/criterion"]
path = thirdparty/criterion
url = https://github.com/Snaipe/Criterion.git

37
common/CMakeLists.txt Normal file
View file

@ -0,0 +1,37 @@
## CMakeLists.txt
#
# @author Daniel Krebs <github@daniel-krebs.net>
# @copyright 2018, RWTH Institute for Automation of Complex Power Systems (ACS)
# @license GNU General Public License (version 3)
#
# VILLASfpga
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
##############################################################################
cmake_minimum_required(VERSION 3.7)
project(villas-common CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror")
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake)
include(FindPkgConfig)
pkg_check_modules(JANSSON jansson)
add_subdirectory(lib)
add_subdirectory(tests)

70
common/Dockerfile Normal file
View file

@ -0,0 +1,70 @@
# Dockerfile for VILLASfpga development.
#
# This Dockerfile builds an image which contains all library dependencies
# and tools to build VILLASfpga.
# However, VILLASfpga itself it not part of the image.
#
# This image can be used for developing VILLASfpga
# by running:
# make docker
#
# @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
# @copyright 2017-2018, Institute for Automation of Complex Power Systems, EONERC
# @license GNU General Public License (version 3)
#
# VILLASfpga
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
###################################################################################
FROM fedora:28
LABEL \
org.label-schema.schema-version="1.0" \
org.label-schema.name="VILLAScommon" \
org.label-schema.license="GPL-3.0" \
org.label-schema.vendor="Institute for Automation of Complex Power Systems, RWTH Aachen University" \
org.label-schema.author.name="Steffen Vogel" \
org.label-schema.author.email="stvogel@eonerc.rwth-aachen.de" \
org.label-schema.description="A library for shared code across VILLAS C/C++ projects" \
org.label-schema.url="http://fein-aachen.org/projects/villas-framework/" \
org.label-schema.vcs-url="https://git.rwth-aachen.de/VILLASframework/VILLASfpga" \
org.label-schema.usage="https://villas.fein-aachen.org/doc/"
# Toolchain
RUN dnf -y install \
gcc gcc-c++ \
pkgconfig make cmake \
autoconf automake autogen libtool \
texinfo git curl tar
# Several tools only needed for developement and testing
RUN dnf -y install \
rpmdevtools rpm-build
# Some of the dependencies are only available in our own repo
ADD https://villas.fein-aachen.org/packages/villas.repo /etc/yum.repos.d/
# Dependencies
RUN dnf -y install \
jansson-devel
# Build & Install Criterion
COPY thirdparty/criterion /tmp/criterion
RUN mkdir -p /tmp/criterion/build && cd /tmp/criterion/build && cmake .. && make install && rm -rf /tmp/*
ENV LD_LIBRARY_PATH /usr/local/lib:/usr/local/lib64
WORKDIR /villas
ENTRYPOINT bash

45
common/README.md Normal file
View file

@ -0,0 +1,45 @@
# VILLAScommon
[![build status](https://git.rwth-aachen.de/VILLASframework/VILLAScommon/badges/develop/build.svg)](https://git.rwth-aachen.de/acs/VILLAScommon/commits/develop)
**TODO:** Write project description
## Documentation
User documentation is available here: <https://villas.fein-aachen.org/doc/>
## Copyright
2018, Institute for Automation of Complex Power Systems, EONERC
## License
This project is released under the terms of the [GPL version 3](COPYING.md).
```
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
```
For other licensing options please consult [Prof. Antonello Monti](mailto:amonti@eonerc.rwth-aachen.de).
## Contact
[![EONERC ACS Logo](doc/pictures/eonerc_logo.png)](http://www.acs.eonerc.rwth-aachen.de)
- Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
- Daniel Krebs <dkrebs@eonerc.rwth-aachen.de>
[Institute for Automation of Complex Power Systems (ACS)](http://www.acs.eonerc.rwth-aachen.de)
[EON Energy Research Center (EONERC)](http://www.eonerc.rwth-aachen.de)
[RWTH University Aachen, Germany](http://www.rwth-aachen.de)

View file

@ -0,0 +1,27 @@
# This file is licensed under the WTFPL version 2 -- you can see the full
# license over at http://www.wtfpl.net/txt/copying/
#
# - Try to find Criterion
#
# Once done this will define
# CRITERION_FOUND - System has Criterion
# CRITERION_INCLUDE_DIRS - The Criterion include directories
# CRITERION_LIBRARIES - The libraries needed to use Criterion
find_package(PkgConfig)
find_path(CRITERION_INCLUDE_DIR criterion/criterion.h
PATH_SUFFIXES criterion)
find_library(CRITERION_LIBRARY NAMES criterion libcriterion)
set(CRITERION_LIBRARIES ${CRITERION_LIBRARY})
set(CRITERION_INCLUDE_DIRS ${CRITERION_INCLUDE_DIR})
include(FindPackageHandleStandardArgs)
# handle the QUIET and REQUIRED arguments and set CRITERION_FOUND to TRUE
# if all listed variables are TRUE
find_package_handle_standard_args(Criterion DEFAULT_MSG
CRITERION_LIBRARY CRITERION_INCLUDE_DIR)
mark_as_advanced(CRITERION_INCLUDE_DIR CRITERION_LIBRARY)

View file

@ -0,0 +1,36 @@
/** Some common defines, enums and datastructures.
*
* @file
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
* @copyright 2017-2018, Institute for Automation of Complex Power Systems, EONERC
* @license GNU General Public License (version 3)
*
* VILLASfpga
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************************/
#pragma once
/* Common states for most objects in VILLASfpga (paths, nodes, hooks, plugins) */
enum state {
STATE_DESTROYED = 0,
STATE_INITIALIZED = 1,
STATE_PARSED = 2,
STATE_CHECKED = 3,
STATE_STARTED = 4,
STATE_LOADED = 4, /* alias for STATE_STARTED used by plugins */
STATE_STOPPED = 5,
STATE_UNLOADED = 5 /* alias for STATE_STARTED used by plugins */
};

View file

@ -0,0 +1,297 @@
/** A directed graph.
*
* @file
* @author Daniel Krebs <github@daniel-krebs.net>
* @copyright 2017-2018, Institute for Automation of Complex Power Systems, EONERC
* @license GNU General Public License (version 3)
*
* VILLASfpga
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************************/
#pragma once
#include <map>
#include <list>
#include <memory>
#include <sstream>
#include <string>
#include <fstream>
#include <stdexcept>
#include <algorithm>
#include <villas/log.hpp>
#include <villas/graph/vertex.hpp>
#include <villas/graph/edge.hpp>
namespace villas {
namespace graph {
template<typename VertexType = Vertex, typename EdgeType = Edge>
class DirectedGraph {
public:
using VertexIdentifier = Vertex::Identifier;
using EdgeIdentifier = Edge::Identifier;
using Path = std::list<EdgeIdentifier>;
DirectedGraph(const std::string& name = "DirectedGraph") :
lastVertexId(0), lastEdgeId(0)
{
logger = loggerGetOrCreate(name);
}
std::shared_ptr<VertexType> getVertex(VertexIdentifier vertexId) const
{
if(vertexId >= lastVertexId)
throw std::invalid_argument("vertex doesn't exist");
// cannot use [] operator, because creates non-existing elements
// at() will throw std::out_of_range if element does not exist
return vertices.at(vertexId);
}
template<class UnaryPredicate>
VertexIdentifier findVertex(UnaryPredicate p)
{
for(auto& v : vertices) {
auto& vertexId = v.first;
auto& vertex = v.second;
if(p(vertex)) {
return vertexId;
}
}
throw std::out_of_range("vertex not found");
}
std::shared_ptr<EdgeType> getEdge(EdgeIdentifier edgeId) const
{
if(edgeId >= lastEdgeId)
throw std::invalid_argument("edge doesn't exist");
// cannot use [] operator, because creates non-existing elements
// at() will throw std::out_of_range if element does not exist
return edges.at(edgeId);
}
std::size_t getEdgeCount() const
{ return edges.size(); }
std::size_t getVertexCount() const
{ return vertices.size(); }
VertexIdentifier addVertex(std::shared_ptr<VertexType> vertex)
{
vertex->id = lastVertexId++;
logger->debug("New vertex: {}", *vertex);
vertices[vertex->id] = vertex;
return vertex->id;
}
EdgeIdentifier addEdge(std::shared_ptr<EdgeType> edge,
VertexIdentifier fromVertexId,
VertexIdentifier toVertexId)
{
// allocate edge id
edge->id = lastEdgeId++;
// connect it
edge->from = fromVertexId;
edge->to = toVertexId;
logger->debug("New edge {}: {} -> {}", *edge, edge->from, edge->to);
// this is a directed graph, so only push edge to starting vertex
getVertex(edge->from)->edges.push_back(edge->id);
// add new edge to graph
edges[edge->id] = edge;
return edge->id;
}
EdgeIdentifier addDefaultEdge(VertexIdentifier fromVertexId,
VertexIdentifier toVertexId)
{
// create a new edge
std::shared_ptr<EdgeType> edge(new EdgeType);
return addEdge(edge, fromVertexId, toVertexId);
}
void removeEdge(EdgeIdentifier edgeId)
{
auto edge = getEdge(edgeId);
auto startVertex = getVertex(edge->from);
// remove edge only from starting vertex (this is a directed graph)
logger->debug("Remove edge {} from vertex {}", edgeId, edge->from);
startVertex->edges.remove(edgeId);
logger->debug("Remove edge {}", edgeId);
edges.erase(edgeId);
}
void removeVertex(VertexIdentifier vertexId)
{
// delete every edge that start or ends at this vertex
auto it = edges.begin();
while(it != edges.end()) {
auto& edgeId = it->first;
auto& edge = it->second;
bool removeEdge = false;
if(edge->to == vertexId) {
logger->debug("Remove edge {} from vertex {}'s edge list",
edgeId, edge->from);
removeEdge = true;
auto startVertex = getVertex(edge->from);
startVertex->edges.remove(edge->id);
}
if((edge->from == vertexId) or removeEdge) {
logger->debug("Remove edge {}", edgeId);
// remove edge from global edge list
it = edges.erase(it);
} else {
++it;
}
}
logger->debug("Remove vertex {}", vertexId);
vertices.erase(vertexId);
}
const std::list<EdgeIdentifier>&
vertexGetEdges(VertexIdentifier vertexId) const
{ return getVertex(vertexId)->edges; }
using check_path_fn = std::function<bool(const Path&)>;
static bool
checkPath(const Path&)
{ return true; }
bool getPath(VertexIdentifier fromVertexId,
VertexIdentifier toVertexId,
Path& path,
check_path_fn pathCheckFunc = checkPath)
{
if(fromVertexId == toVertexId) {
// arrived at the destination
return true;
} else {
auto fromVertex = getVertex(fromVertexId);
for(auto& edgeId : fromVertex->edges) {
auto edgeOfFromVertex = getEdge(edgeId);
// loop detection
bool loop = false;
for(auto& edgeIdInPath : path) {
auto edgeInPath = getEdge(edgeIdInPath);
if(edgeInPath->from == edgeOfFromVertex->to) {
loop = true;
break;
}
}
if(loop) {
logger->debug("Loop detected via edge {}", edgeId);
continue;
}
// remember the path we're investigating to detect loops
path.push_back(edgeId);
// recursive, depth-first search
if(getPath(edgeOfFromVertex->to, toVertexId, path, pathCheckFunc) and
pathCheckFunc(path)) {
// path found, we're done
return true;
} else {
// tear down path that didn't lead to the destination
path.pop_back();
}
}
}
return false;
}
void dump(const std::string& fileName = "")
{
logger->info("Vertices:");
for(auto& v : vertices) {
auto& vertex = v.second;
// format connected vertices into a list
std::stringstream ssEdges;
for(auto& edge : vertex->edges) {
ssEdges << getEdge(edge)->to << " ";
}
logger->info(" {} connected to: {}", *vertex, ssEdges.str());
}
std::fstream s(fileName, s.out | s.trunc);
if(s.is_open()) {
s << "digraph memgraph {" << std::endl;
}
logger->info("Edges:");
for(auto& e : edges) {
auto& edge = e.second;
logger->info(" {}: {} -> {}", *edge, edge->from, edge->to);
if(s.is_open()) {
auto from = getVertex(edge->from);
auto to = getVertex(edge->to);
s << std::dec;
s << " \"" << *from << "\" -> \"" << *to << "\""
<< " [label=\"" << *edge << "\"];" << std::endl;
}
}
if(s.is_open()) {
s << "}" << std::endl;
s.close();
}
}
protected:
VertexIdentifier lastVertexId;
EdgeIdentifier lastEdgeId;
std::map<VertexIdentifier, std::shared_ptr<VertexType>> vertices;
std::map<EdgeIdentifier, std::shared_ptr<EdgeType>> edges;
SpdLogger logger;
};
} // namespacae graph
} // namespace villas

View file

@ -0,0 +1,57 @@
/** A graph edge.
*
* @file
* @author Daniel Krebs <github@daniel-krebs.net>
* @copyright 2017-2018, Institute for Automation of Complex Power Systems, EONERC
* @license GNU General Public License (version 3)
*
* VILLASfpga
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************************/
#pragma once
namespace villas {
namespace graph {
class Edge {
template<typename VertexType, typename EdgeType>
friend class DirectedGraph;
public:
using Identifier = std::size_t;
friend std::ostream&
operator<< (std::ostream& stream, const Edge& edge)
{ return stream << edge.id; }
bool
operator==(const Edge& other)
{ return this->id == other.id; }
Vertex::Identifier getVertexTo() const
{ return to; }
Vertex::Identifier getVertexFrom() const
{ return from; }
private:
Identifier id;
Vertex::Identifier from;
Vertex::Identifier to;
};
} // namespacae graph
} // namespace villas

View file

@ -0,0 +1,55 @@
/** A graph vertex.
*
* @file
* @author Daniel Krebs <github@daniel-krebs.net>
* @copyright 2017-2018, Institute for Automation of Complex Power Systems, EONERC
* @license GNU General Public License (version 3)
*
* VILLASfpga
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************************/
#pragma once
namespace villas {
namespace graph {
class Vertex {
template<typename VertexType, typename EdgeType>
friend class DirectedGraph;
public:
using Identifier = std::size_t;
const Identifier&
getIdentifier() const
{ return id; }
friend std::ostream&
operator<< (std::ostream& stream, const Vertex& vertex)
{ return stream << vertex.id; }
bool
operator==(const Vertex& other)
{ return this->id == other.id; }
private:
Identifier id;
// HACK: how to resolve this circular type dependency?
std::list<std::size_t> edges;
};
} // namespacae graph
} // namespace villas

View file

@ -0,0 +1,56 @@
/** Logging.
*
* @file
* @author Daniel Krebs <github@daniel-krebs.net>
* @copyright 2017-2018, Institute for Automation of Complex Power Systems, EONERC
* @license GNU General Public License (version 3)
*
* VILLASfpga
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************************/
#pragma once
#include <string>
#define SPDLOG_LEVEL_NAMES { "trace", "debug", "info ", "warn ", "error", "crit ", "off " }
#define SPDLOG_NAME_WIDTH 17
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/fmt/ostr.h>
#define _ESCAPE "\x1b"
#define TXT_RESET_ALL _ESCAPE "[0m"
#define TXT_RESET_BOLD _ESCAPE "[21m"
#define TXT_BOLD(s) _ESCAPE "[1m" + std::string(s) + TXT_RESET_BOLD
#define TXT_RESET_COLOR _ESCAPE "[39m"
#define TXT_RED(s) _ESCAPE "[31m" + std::string(s) + TXT_RESET_COLOR
#define TXT_GREEN(s) _ESCAPE "[32m" + std::string(s) + TXT_RESET_COLOR
#define TXT_YELLOW(s) _ESCAPE "[33m" + std::string(s) + TXT_RESET_COLOR
#define TXT_BLUE(s) _ESCAPE "[34m" + std::string(s) + TXT_RESET_COLOR
using SpdLogger = std::shared_ptr<spdlog::logger>;
inline SpdLogger loggerGetOrCreate(const std::string& logger_name)
{
auto logger = spdlog::get(logger_name);
if(not logger) {
logger = spdlog::stdout_color_mt(logger_name);
}
return logger;
}

View file

@ -0,0 +1,309 @@
/** Memory management.
*
* @file
* @author Daniel Krebs <github@daniel-krebs.net>
* @copyright 2017-2018, Institute for Automation of Complex Power Systems, EONERC
* @license GNU General Public License (version 3)
*
* VILLASfpga
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************************/
#pragma once
#include <string>
#include <unistd.h>
#include <villas/log.hpp>
#include <villas/memory_manager.hpp>
namespace villas {
/**
* @brief Basic memory block backed by an address space in the memory graph
*
* This is a generic representation of a chunk of memory in the system. It can
* reside anywhere and represent different types of memory.
*/
class MemoryBlock {
public:
using deallocator_fn = std::function<void(MemoryBlock*)>;
MemoryBlock(size_t offset, size_t size, MemoryManager::AddressSpaceId addrSpaceId) :
offset(offset), size(size), addrSpaceId(addrSpaceId) {}
MemoryManager::AddressSpaceId getAddrSpaceId() const
{ return addrSpaceId; }
size_t getSize() const
{ return size; }
size_t getOffset() const
{ return offset; }
protected:
size_t offset; ///< Offset (or address) inside address space
size_t size; ///< Size in bytes of this block
MemoryManager::AddressSpaceId addrSpaceId; ///< Identifier in memory graph
};
/**
* @brief Wrapper for a MemoryBlock to access the underlying memory directly
*
* The underlying memory block has to be accessible for the current process,
* that means it has to be mapped accordingly and registered to the global
* memory graph.
* Furthermore, this wrapper can be owning the memory block when initialized
* with a moved unique pointer. Otherwise, it just stores a reference to the
* memory block and it's the users responsibility to take care that the memory
* block is valid.
*/
template<typename T>
class MemoryAccessor {
public:
using Type = T;
// take ownership of the MemoryBlock
MemoryAccessor(std::unique_ptr<MemoryBlock, MemoryBlock::deallocator_fn> mem) :
translation(MemoryManager::get().getTranslationFromProcess(mem->getAddrSpaceId())),
memoryBlock(std::move(mem)) {}
// just act as an accessor, do not take ownership of MemoryBlock
MemoryAccessor(const MemoryBlock& mem) :
translation(MemoryManager::get().getTranslationFromProcess(mem.getAddrSpaceId())) {}
T& operator*() const {
return *reinterpret_cast<T*>(translation.getLocalAddr(0));
}
T& operator[](size_t idx) const {
const size_t offset = sizeof(T) * idx;
return *reinterpret_cast<T*>(translation.getLocalAddr(offset));
}
T* operator&() const {
return reinterpret_cast<T*>(translation.getLocalAddr(0));
}
T* operator->() const {
return reinterpret_cast<T*>(translation.getLocalAddr(0));
}
const MemoryBlock&
getMemoryBlock() const
{ if(not memoryBlock) throw std::bad_alloc(); else return *memoryBlock; }
private:
/// cached memory translation for fast access
MemoryTranslation translation;
/// take the unique pointer in case user wants this class to have ownership
std::unique_ptr<MemoryBlock, MemoryBlock::deallocator_fn> memoryBlock;
};
/**
* @brief Base memory allocator
*
* Note the usage of CRTP idiom here to access methods of derived allocators.
* The concept is explained here at [1].
*
* [1] https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern
*/
template<typename DerivedAllocator>
class BaseAllocator {
public:
/// memoryAddrSpaceId: memory that is managed by this allocator
BaseAllocator(MemoryManager::AddressSpaceId memoryAddrSpaceId) :
memoryAddrSpaceId(memoryAddrSpaceId)
{
// CRTP
derivedAlloc = static_cast<DerivedAllocator*>(this);
logger = loggerGetOrCreate(derivedAlloc->getName());
// default deallocation callback
free = [&](MemoryBlock* mem) {
logger->warn("no free callback defined for addr space {}, not freeing",
mem->getAddrSpaceId());
removeMemoryBlock(*mem);
};
}
virtual std::unique_ptr<MemoryBlock, MemoryBlock::deallocator_fn>
allocateBlock(size_t size) = 0;
template<typename T>
MemoryAccessor<T>
allocate(size_t num)
{
const size_t size = num * sizeof(T);
auto mem = allocateBlock(size);
// Check if the allocated memory is really accessible by writing to the
// allocated memory and reading back. Exponentially increase offset to
// speed up testing.
MemoryAccessor<volatile uint8_t> byteAccessor(*mem);
size_t idx = 0;
for(int i = 0; idx < mem->getSize(); i++, idx = (1 << i)) {
auto val = static_cast<uint8_t>(i);
byteAccessor[idx] = val;
if(byteAccessor[idx] != val) {
logger->error("Cannot access allocated memory");
throw std::bad_alloc();
}
}
return MemoryAccessor<T>(std::move(mem));
}
protected:
void insertMemoryBlock(const MemoryBlock& mem)
{
auto& mm = MemoryManager::get();
mm.createMapping(mem.getOffset(), 0, mem.getSize(),
derivedAlloc->getName(),
memoryAddrSpaceId,
mem.getAddrSpaceId());
}
void removeMemoryBlock(const MemoryBlock& mem)
{
// this will also remove any mapping to and from the memory block
auto& mm = MemoryManager::get();
mm.removeAddressSpace(mem.getAddrSpaceId());
}
MemoryManager::AddressSpaceId getAddrSpaceId() const
{ return memoryAddrSpaceId; }
protected:
MemoryBlock::deallocator_fn free;
SpdLogger logger;
private:
MemoryManager::AddressSpaceId memoryAddrSpaceId;
DerivedAllocator* derivedAlloc;
};
/**
* @brief Linear memory allocator
*
* This is the simplest kind of allocator. The idea is to keep a pointer at the
* first memory address of your memory chunk and move it every time an
* allocation is done. Due to its simplicity, this allocator doesn't allow
* specific positions of memory to be freed. Usually, all memory is freed
* together.
*/
class LinearAllocator : public BaseAllocator<LinearAllocator> {
public:
LinearAllocator(MemoryManager::AddressSpaceId memoryAddrSpaceId,
size_t memorySize,
size_t internalOffset = 0);
size_t getAvailableMemory() const
{ return memorySize - nextFreeAddress; }
size_t getSize() const
{ return memorySize; }
std::string getName() const;
std::unique_ptr<MemoryBlock, MemoryBlock::deallocator_fn>
allocateBlock(size_t size);
private:
static constexpr size_t alignBytes = sizeof(uintptr_t);
static constexpr size_t alignMask = alignBytes - 1;
size_t getAlignmentPadding(uintptr_t addr) const
{ return (alignBytes - (addr & alignMask)) & alignMask; }
private:
size_t nextFreeAddress; ///< next chunk will be allocated here
size_t memorySize; ///< total size of managed memory
size_t internalOffset; ///< offset in address space (usually 0)
size_t allocationCount; ///< Number of individual allocations present
};
/**
* @brief Wrapper around mmap() to create villas memory blocks
*
* This class simply wraps around mmap() and munmap() to allocate memory in the
* host memory via the OS.
*/
class HostRam {
public:
class HostRamAllocator : public BaseAllocator<HostRamAllocator> {
public:
HostRamAllocator();
std::string getName() const
{ return "HostRamAlloc"; }
std::unique_ptr<MemoryBlock, MemoryBlock::deallocator_fn>
allocateBlock(size_t size);
};
static HostRamAllocator&
getAllocator()
{ return allocator; }
private:
static HostRamAllocator allocator;
};
class HostDmaRam {
private:
static std::string
getUdmaBufName(int num);
static std::string
getUdmaBufBasePath(int num);
static size_t
getUdmaBufBufSize(int num);
static uintptr_t
getUdmaBufPhysAddr(int num);
public:
class HostDmaRamAllocator : public LinearAllocator {
public:
HostDmaRamAllocator(int num);
virtual ~HostDmaRamAllocator();
std::string getName() const
{ return getUdmaBufName(num); }
private:
int num;
};
static HostDmaRamAllocator&
getAllocator(int num = 0);
private:
static std::map<int, std::unique_ptr<HostDmaRamAllocator>> allocators;
};
} // namespace villas

View file

@ -0,0 +1,273 @@
/** Memory manager.
*
* @file
* @author Daniel Krebs <github@daniel-krebs.net>
* @copyright 2017-2018, Institute for Automation of Complex Power Systems, EONERC
* @license GNU General Public License (version 3)
*
* VILLASfpga
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************************/
#pragma once
#include <cstdint>
#include <string>
#include <map>
#include <stdexcept>
#include <unistd.h>
#include <villas/log.hpp>
#include <villas/graph/directed.hpp>
namespace villas {
/**
* @brief Translation between a local (master) to a foreign (slave) address space
*
* Memory translations can be chained together using the `+=` operator which is
* used internally by the MemoryManager to compute a translation through
* multiple hops (memory mappings).
*/
class MemoryTranslation {
public:
/**
* @brief MemoryTranslation
* @param src Base address of local address space
* @param dst Base address of foreign address space
* @param size Size of "memory window"
*/
MemoryTranslation(uintptr_t src, uintptr_t dst, size_t size) :
src(src), dst(dst), size(size) {}
uintptr_t
getLocalAddr(uintptr_t addrInForeignAddrSpace) const;
uintptr_t
getForeignAddr(uintptr_t addrInLocalAddrSpace) const;
size_t
getSize() const
{ return size; }
friend std::ostream&
operator<< (std::ostream& stream, const MemoryTranslation& translation)
{
return stream << std::hex
<< "(src=0x" << translation.src
<< ", dst=0x" << translation.dst
<< ", size=0x" << translation.size
<< ")";
}
/// Merge two MemoryTranslations together
MemoryTranslation& operator+=(const MemoryTranslation& other);
private:
uintptr_t src; ///< Base address of local address space
uintptr_t dst; ///< Base address of foreign address space
size_t size; ///< Size of "memory window"
};
/**
* @brief Global memory manager to resolve addresses across address spaces
*
* Every entity in the system has to register its (master) address space and
* create mappings to other (slave) address spaces that it can access. A
* directed graph is then constructed which allows to traverse addresses spaces
* through multiple mappings and resolve addresses through this "tunnel" of
* memory mappings.
*/
class MemoryManager {
private:
// This is a singleton, so private constructor ...
MemoryManager() :
memoryGraph("MemoryGraph"),
logger(loggerGetOrCreate("MemoryManager"))
{
pathCheckFunc = [&](const MemoryGraph::Path& path) {
return this->pathCheck(path);
};
}
// ... and no copying or assigning
MemoryManager(const MemoryManager&) = delete;
MemoryManager& operator=(const MemoryManager&) = delete;
/**
* @brief Custom edge in memory graph representing a memory mapping
*
* A memory mapping maps from one address space into another and can only be
* traversed in the forward direction which reflects the nature of real
* memory mappings.
*
* Implementation Notes:
* The member #src is the address in the "from" address space, where the
* destination address space is mapped. The member #dest is the address in
* the destination address space, where the mapping points to. Often, #dest
* will be zero for mappings to hardware, but consider the example when
* mapping FPGA to application memory:
* The application allocates a block 1kB at address 0x843001000 in its
* address space. The mapping would then have a #dest address of 0x843001000
* and a #size of 1024.
*/
class Mapping : public graph::Edge {
public:
std::string name; ///< Human-readable name
uintptr_t src; ///< Base address in "from" address space
uintptr_t dest; ///< Base address in "to" address space
size_t size; ///< Size of the mapping
friend std::ostream&
operator<< (std::ostream& stream, const Mapping& mapping)
{
return stream << static_cast<const Edge&>(mapping) << " = "
<< mapping.name
<< std::hex
<< " (src=0x" << mapping.src
<< ", dest=0x" << mapping.dest
<< ", size=0x" << mapping.size
<< ")";
}
};
/**
* @brief Custom vertex in memory graph representing an address space
*
* Since most information in the memory graph is stored in the edges (memory
* mappings), this is just a small extension to the default vertex. It only
* associates an additional string #name for human-readability.
*/
class AddressSpace : public graph::Vertex {
public:
std::string name; ///< Human-readable name
friend std::ostream&
operator<< (std::ostream& stream, const AddressSpace& addrSpace)
{
return stream << static_cast<const Vertex&>(addrSpace) << " = "
<< addrSpace.name;
}
};
/// Memory graph with custom edges and vertices for address resolution
using MemoryGraph = graph::DirectedGraph<AddressSpace, Mapping>;
public:
using AddressSpaceId = MemoryGraph::VertexIdentifier;
using MappingId = MemoryGraph::EdgeIdentifier;
struct InvalidTranslation : public std::exception {};
/// Get singleton instance
static MemoryManager&
get();
AddressSpaceId
getProcessAddressSpace()
{ return getOrCreateAddressSpace("villas-fpga"); }
AddressSpaceId
getPciAddressSpace()
{ return getOrCreateAddressSpace("PCIe"); }
AddressSpaceId
getProcessAddressSpaceMemoryBlock(const std::string& memoryBlock)
{ return getOrCreateAddressSpace(getSlaveAddrSpaceName("villas-fpga", memoryBlock)); }
AddressSpaceId
getOrCreateAddressSpace(std::string name);
void
removeAddressSpace(AddressSpaceId addrSpaceId)
{ memoryGraph.removeVertex(addrSpaceId); }
/// Create a default mapping
MappingId
createMapping(uintptr_t src, uintptr_t dest, size_t size,
const std::string& name,
AddressSpaceId fromAddrSpace,
AddressSpaceId toAddrSpace);
/// Add a mapping
///
/// Can be used to derive from Mapping in order to implement custom
/// constructor/destructor.
MappingId
addMapping(std::shared_ptr<Mapping> mapping,
AddressSpaceId fromAddrSpace,
AddressSpaceId toAddrSpace);
AddressSpaceId
findAddressSpace(const std::string& name);
std::list<AddressSpaceId>
findPath(AddressSpaceId fromAddrSpaceId, AddressSpaceId toAddrSpaceId);
MemoryTranslation
getTranslation(AddressSpaceId fromAddrSpaceId, AddressSpaceId toAddrSpaceId);
MemoryTranslation
getTranslationFromProcess(AddressSpaceId foreignAddrSpaceId)
{ return getTranslation(getProcessAddressSpace(), foreignAddrSpaceId); }
static std::string
getSlaveAddrSpaceName(const std::string& ipInstance, const std::string& memoryBlock)
{ return ipInstance + "/" + memoryBlock; }
static std::string
getMasterAddrSpaceName(const std::string& ipInstance, const std::string& busInterface)
{ return ipInstance + ":" + busInterface; }
void
dump()
{ memoryGraph.dump(); }
void
dumpToFile(const std::string& fileName)
{ memoryGraph.dump(fileName); }
private:
/// Convert a Mapping to MemoryTranslation for calculations
static MemoryTranslation
getTranslationFromMapping(const Mapping& mapping)
{ return MemoryTranslation(mapping.src, mapping.dest, mapping.size); }
bool
pathCheck(const MemoryGraph::Path& path);
private:
/// Directed graph that stores address spaces and memory mappings
MemoryGraph memoryGraph;
/// Cache mapping of names to address space ids for fast lookup
std::map<std::string, AddressSpaceId> addrSpaceLookup;
/// Logger for universal access in this class
SpdLogger logger;
MemoryGraph::check_path_fn pathCheckFunc;
/// Static pointer to global instance, because this is a singleton
static MemoryManager* instance;
};
} // namespace villas

View file

@ -0,0 +1,113 @@
/** Loadable / plugin support.
*
* @file
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
* @author Daniel Krebs <github@daniel-krebs.net>
* @copyright 2017-2018, Institute for Automation of Complex Power Systems, EONERC
* @license GNU General Public License (version 3)
*
* VILLASfpga
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************************/
#pragma once
#include <list>
#include <string>
#include <jansson.h>
#include <villas/log.hpp>
#include <villas/common.h>
namespace villas {
class Plugin {
public:
enum class Type {
Unknown,
FpgaIp,
FpgaCard,
Gpu
};
Plugin(Type type, const std::string& name);
virtual ~Plugin();
// copying a plugin doesn't make sense, so explicitly deny it
Plugin(Plugin const&) = delete;
void operator=(Plugin const&) = delete;
int load();
int unload();
virtual int parse(json_t *cfg);
virtual void dump();
static void
dumpList();
/// Find plugin by type and (optional if empty) by name. If more match, it
/// is not defined which one will be returned.
static Plugin *
lookup(Type type, std::string name);
/// Get all plugins of a given type.
static std::list<Plugin*>
lookup(Type type);
// TODO: check if this makes sense! (no intermediate plugins)
bool
operator==(const Plugin& other) const;
Type pluginType;
std::string name;
std::string description;
std::string path;
void *handle;
enum state state;
protected:
static SpdLogger
getStaticLogger()
{ return loggerGetOrCreate("plugin"); }
private:
/* Just using a standard std::list<> to hold plugins is problematic, because
we want to push Plugins to the list from within each Plugin's constructor
that is executed during static initialization. Since the order of static
initialization is undefined in C++, it may happen that a Plugin
constructor is executed before the list could be initialized. Therefore,
we use the Nifty Counter Idiom [1] to initialize the list ourself before
the first usage.
In short:
- allocate a buffer for the list
- initialize list before first usage
- (complicatedly) declaring a buffer is neccessary in order to avoid
that the constructor of the static list is executed again
[1] https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Nifty_Counter
*/
using PluginList = std::list<Plugin *>;
using PluginListBuffer = typename std::aligned_storage<sizeof (Plugin::PluginList), alignof (Plugin::PluginList)>::type;
static PluginListBuffer pluginListBuffer; ///< buffer to hold a PluginList
static PluginList& pluginList; ///< reference to pluginListBuffer
static int pluginListNiftyCounter; ///< track if pluginList has been initialized
};
} // namespace villas

View file

@ -0,0 +1,46 @@
/** Utilities.
*
* @file
* @author Daniel Krebs <github@daniel-krebs.net>
* @copyright 2017-2018, Institute for Automation of Complex Power Systems, EONERC
* @license GNU General Public License (version 3)
*
* VILLASfpga
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************************/
#pragma once
#include <string>
#include <vector>
namespace villas {
namespace utils {
std::vector<std::string>
tokenize(std::string s, std::string delimiter);
template<typename T>
void
assertExcept(bool condition, const T& exception)
{
if(not condition)
throw exception;
}
} // namespace utils
} // namespace villas

39
common/lib/CMakeLists.txt Normal file
View file

@ -0,0 +1,39 @@
## CMakeLists.txt
#
# @author Daniel Krebs <github@daniel-krebs.net>
# @copyright 2018, RWTH Institute for Automation of Complex Power Systems (ACS)
# @license GNU General Public License (version 3)
#
# VILLASfpga
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
##############################################################################
add_library(villas-common SHARED
plugin.cpp
utils.cpp
memory.cpp
memory_manager.cpp
)
target_include_directories(villas-common PUBLIC
${PROJECT_SOURCE_DIR}/include
${PROJECT_SOURCE_DIR}/thirdparty/spdlog/include
${JANSSON_INCLUDE_DIRS}
)
target_link_libraries(villas-common PUBLIC
${JANSSON_LIBRARIES}
-ldl
)

300
common/lib/memory.cpp Normal file
View file

@ -0,0 +1,300 @@
/** Memory managment.
*
* @author Daniel Krebs <github@daniel-krebs.net>
* @copyright 2017-2018, Institute for Automation of Complex Power Systems, EONERC
* @license GNU General Public License (version 3)
*
* VILLASfpga
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************************/
#include <sys/mman.h>
#include <unistd.h>
#include <sstream>
#include <fstream>
#include <villas/memory.hpp>
namespace villas {
std::unique_ptr<MemoryBlock, MemoryBlock::deallocator_fn>
HostRam::HostRamAllocator::allocateBlock(size_t size)
{
/* Align to next bigger page size chunk */
if (size & size_t(0xFFF)) {
size += size_t(0x1000);
size &= size_t(~0xFFF);
}
const int mmap_flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT;
const int mmap_protection = PROT_READ | PROT_WRITE;
const void* addr = mmap(nullptr, size, mmap_protection, mmap_flags, 0, 0);
if(addr == nullptr) {
throw std::bad_alloc();
}
auto& mm = MemoryManager::get();
// assemble name for this block
std::stringstream name;
name << std::showbase << std::hex << reinterpret_cast<uintptr_t>(addr);
auto blockAddrSpaceId = mm.getProcessAddressSpaceMemoryBlock(name.str());
const auto localAddr = reinterpret_cast<uintptr_t>(addr);
std::unique_ptr<MemoryBlock, MemoryBlock::deallocator_fn>
mem(new MemoryBlock(localAddr, size, blockAddrSpaceId), this->free);
insertMemoryBlock(*mem);
return mem;
}
LinearAllocator::LinearAllocator(MemoryManager::AddressSpaceId memoryAddrSpaceId,
size_t memorySize,
size_t internalOffset) :
BaseAllocator(memoryAddrSpaceId),
nextFreeAddress(0),
memorySize(memorySize),
internalOffset(internalOffset),
allocationCount(0)
{
// make sure to start at aligned offset, reduce size in case we need padding
if(const size_t paddingBytes = getAlignmentPadding(internalOffset)) {
assert(paddingBytes < memorySize);
internalOffset += paddingBytes;
memorySize -= paddingBytes;
}
// deallocation callback
free = [&](MemoryBlock* mem) {
logger->debug("Deallocating memory block at local addr {:#x} (addr space {})",
mem->getOffset(), mem->getAddrSpaceId());
removeMemoryBlock(*mem);
allocationCount--;
if(allocationCount == 0) {
logger->debug("All allocations are deallocated now, freeing memory");
// all allocations have been deallocated, free all memory
nextFreeAddress = 0;
}
logger->debug("Available memory: {:#x} bytes", getAvailableMemory());
};
}
std::string
LinearAllocator::getName() const
{
std::stringstream name;
name << "LinearAlloc" << getAddrSpaceId();
if(internalOffset != 0) {
name << "@0x" << std::hex << internalOffset;
}
return name.str();
}
std::unique_ptr<MemoryBlock, MemoryBlock::deallocator_fn>
LinearAllocator::allocateBlock(size_t size)
{
if(size > getAvailableMemory()) {
throw std::bad_alloc();
}
// assign address
const uintptr_t localAddr = nextFreeAddress + internalOffset;
// reserve memory
nextFreeAddress += size;
// make sure it is aligned
if(const size_t paddingBytes = getAlignmentPadding(nextFreeAddress)) {
nextFreeAddress += paddingBytes;
// if next free address is outside this block due to padding, cap it
nextFreeAddress = std::min(nextFreeAddress, memorySize);
}
auto& mm = MemoryManager::get();
// assemble name for this block
std::stringstream blockName;
blockName << std::showbase << std::hex << localAddr;
// create address space
auto addrSpaceName = mm.getSlaveAddrSpaceName(getName(), blockName.str());
auto addrSpaceId = mm.getOrCreateAddressSpace(addrSpaceName);
logger->debug("Allocated {:#x} bytes for {}, {:#x} bytes remaining",
size, addrSpaceId, getAvailableMemory());
std::unique_ptr<MemoryBlock, MemoryBlock::deallocator_fn>
mem(new MemoryBlock(localAddr, size, addrSpaceId), this->free);
// mount block into the memory graph
insertMemoryBlock(*mem);
// increase the allocation count
allocationCount++;
return mem;
}
HostRam::HostRamAllocator
HostRam::allocator;
HostRam::HostRamAllocator::HostRamAllocator() :
BaseAllocator(MemoryManager::get().getProcessAddressSpace())
{
free = [&](MemoryBlock* mem) {
if(::munmap(reinterpret_cast<void*>(mem->getOffset()), mem->getSize()) != 0) {
logger->warn("munmap() failed for {:#x} of size {:#x}",
mem->getOffset(), mem->getSize());
}
removeMemoryBlock(*mem);
};
}
std::map<int, std::unique_ptr<HostDmaRam::HostDmaRamAllocator>>
HostDmaRam::allocators;
HostDmaRam::HostDmaRamAllocator::HostDmaRamAllocator(int num) :
LinearAllocator(MemoryManager::get().getOrCreateAddressSpace(getUdmaBufName(num)), getUdmaBufBufSize(num)),
num(num)
{
auto& mm = MemoryManager::get();
logger = loggerGetOrCreate(getName());
if(getSize() == 0) {
logger->error("Zero-sized DMA buffer not supported, is the kernel module loaded?");
throw std::bad_alloc();
}
const uintptr_t base = getUdmaBufPhysAddr(num);
mm.createMapping(base, 0, getSize(), getName() + "-PCI",
mm.getPciAddressSpace(), getAddrSpaceId());
const auto bufPath = std::string("/dev/") + getUdmaBufName(num);
const int bufFd = open(bufPath.c_str(), O_RDWR | O_SYNC);
if(bufFd != -1) {
void* buf = mmap(nullptr, getSize(), PROT_READ|PROT_WRITE, MAP_SHARED, bufFd, 0);
close(bufFd);
if(buf != MAP_FAILED) {
mm.createMapping(reinterpret_cast<uintptr_t>(buf), 0, getSize(),
getName() + "-VA",
mm.getProcessAddressSpace(), getAddrSpaceId());
} else {
logger->warn("Cannot map {}", bufPath);
}
} else {
logger->warn("Cannot open {}", bufPath);
}
logger->info("Mapped {} of size {} bytes", bufPath, getSize());
}
HostDmaRam::HostDmaRamAllocator::~HostDmaRamAllocator()
{
auto& mm = MemoryManager::get();
void* baseVirt;
try {
auto translation = mm.getTranslationFromProcess(getAddrSpaceId());
baseVirt = reinterpret_cast<void*>(translation.getLocalAddr(0));
} catch(const std::out_of_range&) {
// not mapped, nothing to do
return;
}
logger->debug("Unmapping {}", getName());
// try to unmap it
if(::munmap(baseVirt, getSize()) != 0) {
logger->warn("munmap() failed for {:p} of size {:#x}",
baseVirt, getSize());
}
}
std::string
HostDmaRam::getUdmaBufName(int num)
{
std::stringstream name;
name << "udmabuf" << num;
return name.str();
}
std::string
HostDmaRam::getUdmaBufBasePath(int num)
{
std::stringstream path;
path << "/sys/class/udmabuf/udmabuf" << num << "/";
return path.str();
}
size_t
HostDmaRam::getUdmaBufBufSize(int num)
{
std::fstream s(getUdmaBufBasePath(num) + "size", s.in);
if(s.is_open()) {
std::string line;
if(std::getline(s, line)) {
return std::strtoul(line.c_str(), nullptr, 10);
}
}
return 0;
}
uintptr_t
HostDmaRam::getUdmaBufPhysAddr(int num)
{
std::fstream s(getUdmaBufBasePath(num) + "phys_addr", s.in);
if(s.is_open()) {
std::string line;
if(std::getline(s, line)) {
return std::strtoul(line.c_str(), nullptr, 16);
}
}
return UINTPTR_MAX;
}
HostDmaRam::HostDmaRamAllocator&HostDmaRam::getAllocator(int num)
{
auto& allocator = allocators[num];
if(not allocator) {
allocator = std::make_unique<HostDmaRamAllocator>(num);
}
return *allocator;
}
} // namespace villas

View file

@ -0,0 +1,256 @@
/** Memory managment.
*
* @author Daniel Krebs <github@daniel-krebs.net>
* @copyright 2017-2018, Institute for Automation of Complex Power Systems, EONERC
* @license GNU General Public License (version 3)
*
* VILLASfpga
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************************/
#include <memory>
#include <limits>
#include <cstdint>
#include <villas/utils.hpp>
#include <villas/memory_manager.hpp>
using namespace villas::utils;
namespace villas {
MemoryManager*
MemoryManager::instance = nullptr;
MemoryManager&
MemoryManager::get()
{
if(instance == nullptr) {
instance = new MemoryManager;
}
return *instance;
}
MemoryManager::AddressSpaceId
MemoryManager::getOrCreateAddressSpace(std::string name)
{
try {
// try fast lookup
return addrSpaceLookup.at(name);
} catch (const std::out_of_range&) {
// does not yet exist, create
std::shared_ptr<AddressSpace> addrSpace(new AddressSpace);
addrSpace->name = name;
// cache it for the next access
addrSpaceLookup[name] = memoryGraph.addVertex(addrSpace);
return addrSpaceLookup[name];
}
}
MemoryManager::MappingId
MemoryManager::createMapping(uintptr_t src, uintptr_t dest, size_t size,
const std::string& name,
MemoryManager::AddressSpaceId fromAddrSpace,
MemoryManager::AddressSpaceId toAddrSpace)
{
std::shared_ptr<Mapping> mapping(new Mapping);
mapping->name = name;
mapping->src = src;
mapping->dest = dest;
mapping->size = size;
return addMapping(mapping, fromAddrSpace, toAddrSpace);
}
MemoryManager::MappingId
MemoryManager::addMapping(std::shared_ptr<Mapping> mapping,
MemoryManager::AddressSpaceId fromAddrSpace,
MemoryManager::AddressSpaceId toAddrSpace)
{
return memoryGraph.addEdge(mapping, fromAddrSpace, toAddrSpace);
}
MemoryManager::AddressSpaceId
MemoryManager::findAddressSpace(const std::string& name)
{
return memoryGraph.findVertex(
[&](const std::shared_ptr<AddressSpace>& v) {
return v->name == name;
});
}
std::list<MemoryManager::AddressSpaceId>
MemoryManager::findPath(MemoryManager::AddressSpaceId fromAddrSpaceId,
MemoryManager::AddressSpaceId toAddrSpaceId)
{
std::list<AddressSpaceId> path;
auto fromAddrSpace = memoryGraph.getVertex(fromAddrSpaceId);
auto toAddrSpace = memoryGraph.getVertex(toAddrSpaceId);
// find a path through the memory graph
MemoryGraph::Path pathGraph;
if(not memoryGraph.getPath(fromAddrSpaceId, toAddrSpaceId, pathGraph, pathCheckFunc)) {
logger->debug("No translation found from ({}) to ({})",
*fromAddrSpace, *toAddrSpace);
throw std::out_of_range("no translation found");
}
for(auto& mappingId : pathGraph) {
auto mapping = memoryGraph.getEdge(mappingId);
path.push_back(mapping->getVertexTo());
}
return path;
}
MemoryTranslation
MemoryManager::getTranslation(MemoryManager::AddressSpaceId fromAddrSpaceId,
MemoryManager::AddressSpaceId toAddrSpaceId)
{
// find a path through the memory graph
MemoryGraph::Path path;
if(not memoryGraph.getPath(fromAddrSpaceId, toAddrSpaceId, path, pathCheckFunc)) {
auto fromAddrSpace = memoryGraph.getVertex(fromAddrSpaceId);
auto toAddrSpace = memoryGraph.getVertex(toAddrSpaceId);
logger->debug("No translation found from ({}) to ({})",
*fromAddrSpace, *toAddrSpace);
throw std::out_of_range("no translation found");
}
// start with an identity mapping
MemoryTranslation translation(0, 0, SIZE_MAX);
// iterate through path and merge all mappings into a single translation
for(auto& mappingId : path) {
auto mapping = memoryGraph.getEdge(mappingId);
translation += getTranslationFromMapping(*mapping);
}
return translation;
}
bool
MemoryManager::pathCheck(const MemoryGraph::Path& path)
{
// start with an identity mapping
MemoryTranslation translation(0, 0, SIZE_MAX);
// Try to add all mappings together to a common translation. If this fails
// there is a non-overlapping window
for(auto& mappingId : path) {
auto mapping = memoryGraph.getEdge(mappingId);
try {
translation += getTranslationFromMapping(*mapping);
} catch(const InvalidTranslation&) {
return false;
}
}
return true;
}
uintptr_t
MemoryTranslation::getLocalAddr(uintptr_t addrInForeignAddrSpace) const
{
assert(addrInForeignAddrSpace >= dst);
assert(addrInForeignAddrSpace < (dst + size));
return src + addrInForeignAddrSpace - dst;
}
uintptr_t
MemoryTranslation::getForeignAddr(uintptr_t addrInLocalAddrSpace) const
{
assert(addrInLocalAddrSpace >= src);
assert(addrInLocalAddrSpace < (src + size));
return dst + addrInLocalAddrSpace - src;
}
MemoryTranslation&
MemoryTranslation::operator+=(const MemoryTranslation& other)
{
auto logger = loggerGetOrCreate("MemoryTranslation");
// set level to debug to enable debug output
logger->set_level(spdlog::level::info);
const uintptr_t this_dst_high = this->dst + this->size;
const uintptr_t other_src_high = other.src + other.size;
logger->debug("this->src: {:#x}", this->src);
logger->debug("this->dst: {:#x}", this->dst);
logger->debug("this->size: {:#x}", this->size);
logger->debug("other.src: {:#x}", other.src);
logger->debug("other.dst: {:#x}", other.dst);
logger->debug("other.size: {:#x}", other.size);
logger->debug("this_dst_high: {:#x}", this_dst_high);
logger->debug("other_src_high: {:#x}", other_src_high);
// make sure there is a common memory area
assertExcept(other.src < this_dst_high, MemoryManager::InvalidTranslation());
assertExcept(this->dst < other_src_high, MemoryManager::InvalidTranslation());
const uintptr_t hi = std::max(this_dst_high, other_src_high);
const uintptr_t lo = std::min(this->dst, other.src);
const uintptr_t diff_hi = (this_dst_high > other_src_high)
? (this_dst_high - other_src_high)
: (other_src_high - this_dst_high);
const bool otherSrcIsSmaller = this->dst > other.src;
const uintptr_t diff_lo = (otherSrcIsSmaller)
? (this->dst - other.src)
: (other.src - this->dst);
logger->debug("hi: {:#x}", hi);
logger->debug("lo: {:#x}", lo);
logger->debug("diff_hi: {:#x}", diff_hi);
logger->debug("diff_lo: {:#x}", diff_lo);
// new size of aperture, can only stay or shrink
this->size = (hi - lo) - diff_hi - diff_lo;
// new translation will come out other's destination (by default)
this->dst = other.dst;
// the source stays the same and can only increase with merged translations
this->src = this->src;
if(otherSrcIsSmaller) {
// other mapping starts at lower addresses, so we actually arrive at
// higher addresses
this->dst += diff_lo;
} else {
// other mapping starts at higher addresses than this, so we have to
// increase the start
// NOTE: for addresses equality, this just adds 0
this->src += diff_lo;
}
logger->debug("result src: {:#x}", this->src);
logger->debug("result dst: {:#x}", this->dst);
logger->debug("result size: {:#x}", this->size);
return *this;
}
} // namespace villas

160
common/lib/plugin.cpp Normal file
View file

@ -0,0 +1,160 @@
/** Loadable / plugin support.
*
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
* @copyright 2017-2018, Institute for Automation of Complex Power Systems, EONERC
* @license GNU General Public License (version 3)
*
* VILLASfpga
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************************/
#include <iostream>
#include <string>
#include <new>
#include <type_traits>
#include <dlfcn.h>
#include <villas/plugin.hpp>
namespace villas {
// list of all registered plugins
Plugin::PluginList&
Plugin::pluginList = reinterpret_cast<Plugin::PluginList&>(Plugin::pluginListBuffer);
Plugin::PluginListBuffer
Plugin::pluginListBuffer;
// relies on zero initialization
int Plugin::pluginListNiftyCounter;
Plugin::Plugin(Type type, const std::string& name) :
pluginType(type),
name(name),
description(""),
path(""),
state(STATE_INITIALIZED)
{
// see comment in plugin.hpp on why we need to do this (Nifty Counter Idiom)
if(pluginListNiftyCounter++ == 0)
new (&pluginList) PluginList;
// push to global plugin list
pluginList.push_back(this);
}
Plugin::~Plugin()
{
// clean from global plugin list
pluginList.remove(this);
}
int
Plugin::parse(json_t *cfg)
{
const char *path;
path = json_string_value(cfg);
if (!path)
return -1;
this->path = std::string(path);
this->state = STATE_PARSED;
return 0;
}
int
Plugin::load()
{
assert(this->state == STATE_PARSED);
assert(not this->path.empty());
this->handle = dlopen(this->path.c_str(), RTLD_NOW);
if (this->handle == nullptr)
return -1;
this->state = STATE_LOADED;
return 0;
}
int
Plugin::unload()
{
int ret;
assert(this->state == STATE_LOADED);
ret = dlclose(this->handle);
if (ret != 0)
return -1;
this->state = STATE_UNLOADED;
return 0;
}
void
Plugin::dump()
{
auto logger = getStaticLogger();
logger->info("Name: '{}' Description: '{}'", name, description);
}
void
Plugin::dumpList()
{
auto logger = getStaticLogger();
logger->info("Registered plugins:");
for(auto& p : pluginList) {
logger->info(" - {}", p->name);
}
}
Plugin*
Plugin::lookup(Plugin::Type type, std::string name)
{
for(auto& p : pluginList) {
if(p->pluginType == type and (name.empty() or p->name == name))
return p;
}
return nullptr;
}
std::list<Plugin*>
Plugin::lookup(Plugin::Type type)
{
std::list<Plugin*> list;
for(auto& p : pluginList) {
if(p->pluginType == type)
list.push_back(p);
}
return list;
}
bool
Plugin::operator==(const Plugin &other) const
{
return (this->pluginType == other.pluginType) and (this->name == other.name);
}
} // namespace villas

57
common/lib/utils.cpp Normal file
View file

@ -0,0 +1,57 @@
/** Utilities.
*
* @author Daniel Krebs <github@daniel-krebs.net>
* @copyright 2017-2018, Institute for Automation of Complex Power Systems, EONERC
* @license GNU General Public License (version 3)
*
* VILLASfpga
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************************/
#include <vector>
#include <string>
#include <villas/utils.hpp>
namespace villas {
namespace utils {
std::vector<std::string>
tokenize(std::string s, std::string delimiter)
{
std::vector<std::string> tokens;
size_t lastPos = 0;
size_t curentPos;
while((curentPos = s.find(delimiter, lastPos)) != std::string::npos) {
const size_t tokenLength = curentPos - lastPos;
tokens.push_back(s.substr(lastPos, tokenLength));
// advance in string
lastPos = curentPos + delimiter.length();
}
// check if there's a last token behind the last delimiter
if(lastPos != s.length()) {
const size_t lastTokenLength = s.length() - lastPos;
tokens.push_back(s.substr(lastPos, lastTokenLength));
}
return tokens;
}
} // namespace utils
} // namespace villas

View file

@ -0,0 +1,41 @@
## CMakeLists.txt
#
# @author Daniel Krebs <github@daniel-krebs.net>
# @copyright 2018, RWTH Institute for Automation of Complex Power Systems (ACS)
# @license GNU General Public License (version 3)
#
# VILLASfpga
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
##############################################################################
set(SOURCES
main.cpp
logging.cpp
graph.cpp
)
add_executable(unit-tests ${SOURCES})
find_package(Criterion REQUIRED)
target_include_directories(unit-tests PUBLIC
${PROJECT_SOURCE_DIR}/include
${CRITERION_INCLUDE_DIRECTORIES}
)
target_link_libraries(unit-tests PUBLIC
villas-common
${CRITERION_LIBRARIES}
)

159
common/tests/graph.cpp Normal file
View file

@ -0,0 +1,159 @@
/** Graph unit test.
*
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
* @copyright 2017-2018, Steffen Vogel
* @license GNU General Public License (version 3)
*
* VILLASfpga
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************************/
#include <memory>
#include <criterion/criterion.h>
#include <villas/graph/directed.hpp>
#include <villas/log.hpp>
#include <villas/memory_manager.hpp>
static void init_graph()
{
spdlog::set_pattern("[%T] [%l] [%n] %v");
spdlog::set_level(spdlog::level::debug);
}
TestSuite(graph,
.description = "Graph library",
.init = init_graph
);
Test(graph, basic, .description = "DirectedGraph")
{
auto logger = loggerGetOrCreate("test:graph:basic");
villas::graph::DirectedGraph<> g("test:graph:basic");
logger->info("Testing basic graph construction and modification");
std::shared_ptr<villas::graph::Vertex> v1(new villas::graph::Vertex);
std::shared_ptr<villas::graph::Vertex> v2(new villas::graph::Vertex);
std::shared_ptr<villas::graph::Vertex> v3(new villas::graph::Vertex);
auto v1id = g.addVertex(v1);
auto v2id = g.addVertex(v2);
auto v3id = g.addVertex(v3);
cr_assert(g.getVertexCount() == 3);
g.addDefaultEdge(v1id, v2id);
g.addDefaultEdge(v3id, v2id);
g.addDefaultEdge(v1id, v3id);
g.addDefaultEdge(v2id, v1id);
cr_assert(g.getEdgeCount() == 4);
cr_assert(g.vertexGetEdges(v1id).size() == 2);
cr_assert(g.vertexGetEdges(v2id).size() == 1);
cr_assert(g.vertexGetEdges(v3id).size() == 1);
g.removeVertex(v1id);
g.dump();
cr_assert(g.getVertexCount() == 2);
cr_assert(g.vertexGetEdges(v2id).size() == 0);
logger->info(TXT_GREEN("Passed"));
}
Test(graph, path, .description = "Find path")
{
auto logger = loggerGetOrCreate("test:graph:path");
logger->info("Testing path finding algorithm");
using Graph = villas::graph::DirectedGraph<>;
Graph g("test:graph:path");
std::shared_ptr<villas::graph::Vertex> v1(new villas::graph::Vertex);
std::shared_ptr<villas::graph::Vertex> v2(new villas::graph::Vertex);
std::shared_ptr<villas::graph::Vertex> v3(new villas::graph::Vertex);
std::shared_ptr<villas::graph::Vertex> v4(new villas::graph::Vertex);
std::shared_ptr<villas::graph::Vertex> v5(new villas::graph::Vertex);
std::shared_ptr<villas::graph::Vertex> v6(new villas::graph::Vertex);
auto v1id = g.addVertex(v1);
auto v2id = g.addVertex(v2);
auto v3id = g.addVertex(v3);
auto v4id = g.addVertex(v4);
auto v5id = g.addVertex(v5);
auto v6id = g.addVertex(v6);
g.addDefaultEdge(v1id, v2id);
g.addDefaultEdge(v2id, v3id);
// create circular subgraph
g.addDefaultEdge(v4id, v5id);
g.addDefaultEdge(v5id, v4id);
g.addDefaultEdge(v5id, v6id);
g.dump();
logger->info("Find simple path via two edges");
std::list<Graph::EdgeIdentifier> path1;
cr_assert(g.getPath(v1id, v3id, path1));
logger->info(" Path from {} to {} via:", v1id, v3id);
for(auto& edge : path1) {
logger->info(" -> edge {}", edge);
}
logger->info("Find path between two unconnected sub-graphs");
std::list<Graph::EdgeIdentifier> path2;
cr_assert(not g.getPath(v1id, v4id, path2));
logger->info(" no path found -> ok");
logger->info("Find non-existing path in circular sub-graph");
std::list<Graph::EdgeIdentifier> path3;
cr_assert(not g.getPath(v4id, v2id, path3));
logger->info(" no path found -> ok");
logger->info("Find path in circular graph");
std::list<Graph::EdgeIdentifier> path4;
cr_assert(g.getPath(v4id, v6id, path4));
logger->info(" Path from {} to {} via:", v4id, v6id);
for(auto& edge : path4) {
logger->info(" -> edge {}", edge);
}
logger->info(TXT_GREEN("Passed"));
}
Test(graph, memory_manager, .description = "Global Memory Manager")
{
auto logger = loggerGetOrCreate("test:graph:mm");
auto& mm = villas::MemoryManager::get();
logger->info("Create address spaces");
auto dmaRegs = mm.getOrCreateAddressSpace("DMA Registers");
auto pcieBridge = mm.getOrCreateAddressSpace("PCIe Bridge");
logger->info("Create a mapping");
mm.createMapping(0x1000, 0, 0x1000, "Testmapping", dmaRegs, pcieBridge);
logger->info("Find address space by name");
auto vertex = mm.findAddressSpace("PCIe Bridge");
logger->info(" found: {}", vertex);
mm.dump();
logger->info(TXT_GREEN("Passed"));
}

125
common/tests/logging.cpp Normal file
View file

@ -0,0 +1,125 @@
/** Logging utilities for unit test.
*
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
* @copyright 2017-2018, Steffen Vogel
* @license GNU General Public License (version 3)
*
* VILLASfpga
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************************/
#include <cstdarg>
#include <memory>
#include <criterion/logging.h>
#include <criterion/options.h>
#include <villas/log.hpp>
#include <spdlog/spdlog.h>
extern "C" {
/* We override criterions function here */
void criterion_log_noformat(enum criterion_severity severity, const char *msg);
void criterion_plog(enum criterion_logging_level level, const struct criterion_prefix_data *prefix, const char *msg, ...);
void criterion_vlog(enum criterion_logging_level level, const char *msg, va_list args);
}
struct criterion_prefix_data {
const char *prefix;
const char *color;
};
static int format_msg(char *buf, size_t buflen, const char *msg, va_list args)
{
int len = vsnprintf(buf, buflen, msg, args);
/* Strip new line */
char *nl = strchr(buf, '\n');
if (nl)
*nl = 0;
return len;
}
void criterion_log_noformat(enum criterion_severity severity, const char *msg)
{
auto logger = loggerGetOrCreate("criterion");
switch (severity) {
case CR_LOG_INFO:
logger->info(msg);
break;
case CR_LOG_WARNING:
logger->warn(msg);
break;
case CR_LOG_ERROR:
logger->error(msg);
break;
}
}
void criterion_vlog(enum criterion_logging_level level, const char *msg, va_list args)
{
char formatted_msg[1024];
if (level < criterion_options.logging_threshold)
return;
format_msg(formatted_msg, sizeof(formatted_msg), msg, args);
auto logger = loggerGetOrCreate("criterion");
logger->info(formatted_msg);
}
void criterion_plog(enum criterion_logging_level level, const struct criterion_prefix_data *prefix, const char *msg, ...)
{
char formatted_msg[1024];
va_list args;
if (level < criterion_options.logging_threshold)
return;
va_start(args, msg);
format_msg(formatted_msg, sizeof(formatted_msg), msg, args);
va_end(args);
auto logger = loggerGetOrCreate("criterion");
if (strstr(formatted_msg, "Warning"))
logger->warn(formatted_msg);
else if (strstr(formatted_msg, "Failed"))
logger->error(formatted_msg);
else if(!strcmp(prefix->prefix, "----") && !strcmp(prefix->color, "\33[0;34m"))
logger->info(formatted_msg);
else if (!strcmp(prefix->prefix, "----") && !strcmp(prefix->color, "\33[1;30m"))
logger->debug(formatted_msg);
else if (!strcmp(prefix->prefix, "===="))
logger->info(formatted_msg);
else if (!strcmp(prefix->prefix, "RUN "))
logger->info("Run: {}", formatted_msg);
else if (!strcmp(prefix->prefix, "SKIP"))
logger->info("Skip: {}", formatted_msg);
else if (!strcmp(prefix->prefix, "PASS"))
logger->info("Pass: {}", formatted_msg);
else if (!strcmp(prefix->prefix, "FAIL"))
logger->error("Fail: {}", formatted_msg);
else if (!strcmp(prefix->prefix, "WARN"))
logger->warn(formatted_msg);
else if (!strcmp(prefix->prefix, "ERR "))
logger->error(formatted_msg);
}

47
common/tests/main.cpp Normal file
View file

@ -0,0 +1,47 @@
/** Main Unit Test entry point.
*
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
* @copyright 2017-2018, Steffen Vogel
* @license GNU General Public License (version 3)
*
* VILLASfpga
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************************/
#include <criterion/criterion.h>
#include <criterion/options.h>
#include <villas/log.hpp>
#include <spdlog/spdlog.h>
int main(int argc, char *argv[])
{
int ret;
spdlog::set_level(spdlog::level::debug);
spdlog::set_pattern("[%T] [%l] [%n] %v");
/* Run criterion tests */
auto tests = criterion_initialize();
ret = criterion_handle_args(argc, argv, true);
if (ret)
ret = !criterion_run_all_tests(tests);
criterion_finalize(tests);
return ret;
}

1
common/thirdparty/criterion vendored Submodule

@ -0,0 +1 @@
Subproject commit 9b70365825aced7333d7867bb5c64c63919ce510

1
common/thirdparty/spdlog vendored Submodule

@ -0,0 +1 @@
Subproject commit d3c1ad29a064d001da2435db56f159784ef54964