diff options
author | Ed Tanous <ed.tanous@intel.com> | 2018-01-11 20:50:25 +0300 |
---|---|---|
committer | Ed Tanous <ed.tanous@intel.com> | 2018-05-04 19:30:03 +0300 |
commit | d592681baeb54ba3e3adc2f4c7a5cf64dcaa978a (patch) | |
tree | 5d84d78cd3d72638a80a91e1d2fd5cf06ef652bd /mapperx | |
parent | da6a71d6a0d061d9ef5195b683506794a8300980 (diff) | |
download | provingground-d592681baeb54ba3e3adc2f4c7a5cf64dcaa978a.tar.xz |
Implement Mapper in sdbusplus asio
Change-Id: Ie746db4c3ad94d02e4075bcc0e42a58600f52b29
Diffstat (limited to 'mapperx')
-rw-r--r-- | mapperx/.clang-format | 84 | ||||
-rw-r--r-- | mapperx/CMakeLists.txt | 77 | ||||
-rw-r--r-- | mapperx/CMakeLists.txt.in | 64 | ||||
-rw-r--r-- | mapperx/src/main.cpp | 502 |
4 files changed, 727 insertions, 0 deletions
diff --git a/mapperx/.clang-format b/mapperx/.clang-format new file mode 100644 index 0000000..37469de --- /dev/null +++ b/mapperx/.clang-format @@ -0,0 +1,84 @@ +--- +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlinesLeft: false +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: false +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: true + AfterControlStatement: true + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: true + BeforeCatch: true + BeforeElse: true + IndentBraces: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: AfterColon +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: true +PointerAlignment: Left +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] +IndentCaseLabels: true +IndentWidth: 4 +IndentWrappedFunctionNames: true +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +ReflowComments: true +SortIncludes: false +SpaceAfterCStyleCast: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 4 +UseTab: Never +... diff --git a/mapperx/CMakeLists.txt b/mapperx/CMakeLists.txt new file mode 100644 index 0000000..c70d6cd --- /dev/null +++ b/mapperx/CMakeLists.txt @@ -0,0 +1,77 @@ +cmake_minimum_required(VERSION 2.8.10 FATAL_ERROR) +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH}) + +option(YOCTO_DEPENDENCIES "Use YOCTO depedencies system" OFF) + +project(mapperx CXX) + +# Enable link time optimization This is a temporary workaround because +# INTERPROCEDURAL_OPTIMIZATION isn't available until cmake 3.9. gcc-ar and gcc- +# ranlib are wrappers around ar and ranlib which add the lto plugin to the +# command line. +if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + if(NOT CMAKE_BUILD_TYPE MATCHES Debug) + STRING(REGEX REPLACE "ar$" "gcc-ar" CMAKE_AR ${CMAKE_AR}) + STRING(REGEX REPLACE "ranlib$" "gcc-ranlib" CMAKE_RANLIB ${CMAKE_RANLIB}) + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -flto -fno-fat-lto-objects") + + # Reduce the binary size by removing unnecessary dynamic symbol table + # entries + SET( + CMAKE_CXX_FLAGS + "${CMAKE_CXX_FLAGS} \ + -fvisibility=hidden \ + -fvisibility-inlines-hidden \ + -Wl,--exclude-libs,ALL" + ) + endif(NOT CMAKE_BUILD_TYPE MATCHES Debug) +endif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + +if(NOT ${YOCTO_DEPENDENCIES}) + file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/prefix) + file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/prefix/include) + include_directories(${CMAKE_BINARY_DIR}/openbmc-sdbusplus + ${CMAKE_BINARY_DIR}/prefix/include) + configure_file(CMakeLists.txt.in 3rdparty/CMakeLists.txt) + execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/3rdparty) + execute_process(COMMAND ${CMAKE_COMMAND} --build . + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/3rdparty) + + set(CMAKE_PREFIX_PATH ${CMAKE_BINARY_DIR}/prefix ${CMAKE_PREFIX_PATH}) + + include_directories(${CMAKE_BINARY_DIR}/sdbusplus-src + ${CMAKE_BINARY_DIR}/prefix/include) + + set(WANT_TRANSACTION 0) + + configure_file(${CMAKE_BINARY_DIR}/sdbusplus-src/sdbusplus/server.hpp.in + ${CMAKE_BINARY_DIR}/prefix/include/sdbusplus/server.hpp @ONLY) + configure_file(${CMAKE_BINARY_DIR}/sdbusplus-src/sdbusplus/bus.hpp.in + ${CMAKE_BINARY_DIR}/prefix/include/sdbusplus/bus.hpp @ONLY) + +endif() + +add_definitions(-DBOOST_ERROR_CODE_HEADER_ONLY) +add_definitions(-DBOOST_SYSTEM_NO_DEPRECATED) +add_definitions(-DBOOST_ALL_NO_LIB) +add_definitions(-DBOOST_NO_RTTI) +add_definitions(-DBOOST_NO_TYPEID) + +find_package(Boost 1.66 REQUIRED) +include_directories(${BOOST_SRC_DIR}) + +SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti") + +find_package(tinyxml2 REQUIRED) + +set(TEST_FILES tests/mapper_test.cpp) + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) + +add_executable(mapperx src/main.cpp ${SRC_FILES}) +target_link_libraries(mapperx systemd pthread) +target_link_libraries(mapperx tinyxml2) +install(TARGETS mapperx DESTINATION bin) diff --git a/mapperx/CMakeLists.txt.in b/mapperx/CMakeLists.txt.in new file mode 100644 index 0000000..54d51f1 --- /dev/null +++ b/mapperx/CMakeLists.txt.in @@ -0,0 +1,64 @@ +cmake_minimum_required(VERSION 3.5) + +include(ExternalProject) + +file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/prefix) +file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/prefix/include) + +ExternalProject_Add( + sdbusplus + GIT_REPOSITORY + "ssh://git-amr-2.devtools.intel.com:29418/openbmc-sdbusplus" + SOURCE_DIR + "${CMAKE_BINARY_DIR}/sdbusplus-src" + BINARY_DIR + "${CMAKE_BINARY_DIR}/sdbusplus-build" + CMAKE_ARGS + -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/prefix + CONFIGURE_COMMAND + "" + UPDATE_COMMAND + git fetch origin refs/changes/95/127095/1 + && + git checkout FETCH_HEAD BUILD_COMMAND + INSTALL_COMMAND + cp + -r + "${CMAKE_BINARY_DIR}/sdbusplus-src/sdbusplus" + "${CMAKE_BINARY_DIR}/prefix/include" +) + +ExternalProject_Add( + Boost + URL + https://dl.bintray.com/boostorg/release/1.66.0/source/boost_1_66_0.tar.gz + URL_MD5 + d275cd85b00022313c171f602db59fc5 + SOURCE_DIR + "${CMAKE_BINARY_DIR}/boost-src" + BINARY_DIR + "${CMAKE_BINARY_DIR}/boost-build" + CONFIGURE_COMMAND + "" + BUILD_COMMAND + "" + INSTALL_COMMAND + mkdir -p "${CMAKE_BINARY_DIR}/prefix/include/" + && + cp -R ${CMAKE_BINARY_DIR}/boost-src/boost ${CMAKE_BINARY_DIR}/prefix/include +) + +ExternalProject_Add( + tinyxml2 + GIT_REPOSITORY + "https://github.com/leethomason/tinyxml2.git" + GIT_TAG + 8c8293ba8969a46947606a93ff0cb5a083aab47a + CMAKE_ARGS + SOURCE_DIR + "${CMAKE_BINARY_DIR}/tinyxml2-src" + BINARY_DIR + "${CMAKE_BINARY_DIR}/tinyxml2-build" + CMAKE_ARGS + -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/prefix +)
\ No newline at end of file diff --git a/mapperx/src/main.cpp b/mapperx/src/main.cpp new file mode 100644 index 0000000..f2434a3 --- /dev/null +++ b/mapperx/src/main.cpp @@ -0,0 +1,502 @@ +#include <tinyxml2.h> +#include <atomic> +#include <boost/algorithm/string/predicate.hpp> +#include <boost/container/flat_map.hpp> +#include <boost/container/flat_set.hpp> +#include <chrono> +#include <iomanip> +#include <iostream> +#include <sdbusplus/asio/connection.hpp> +#include <sdbusplus/asio/object_server.hpp> + +// interface map type is the underlying datastructure the mapper uses. +// The 3 levels of map are +// object paths +// connection names +// interface names +using interface_map_type = boost::container::flat_map< + std::string, boost::container::flat_map< + std::string, boost::container::flat_set<std::string>>>; + +struct InProgressIntrospect +{ + std::string process_name; + std::chrono::time_point<std::chrono::steady_clock> process_start_time; + std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>> + global_start_time; +}; + +struct cmp_str +{ + bool operator()(const char* a, const char* b) const + { + return std::strcmp(a, b) < 0; + } +}; + +static const boost::container::flat_set<const char*, cmp_str> + ignored_interfaces{"org.freedesktop.DBus.Introspectable", + "org.freedesktop.DBus.Peer", + "org.freedesktop.DBus.Properties"}; + +inline bool should_scan_dbus_interface(const std::string& process_name) +{ + return boost::starts_with(process_name, "xyz.openbmc_project.") || + boost::starts_with(process_name, "org.openbmc.") || + boost::starts_with(process_name, "com.intel."); +} + +void do_introspect(sdbusplus::asio::connection* system_bus, + std::shared_ptr<InProgressIntrospect> transaction, + interface_map_type& interface_map, std::string path) +{ + system_bus->async_method_call( + [&, transaction, path, system_bus](const boost::system::error_code ec, + const std::string& introspect_xml) { + if (ec) + { + std::cerr << "Introspect call failed with error: " + << ec.message() + << " on process: " << transaction->process_name + << " path: " << path << "\n"; + } + else + { + tinyxml2::XMLDocument doc; + + doc.Parse(introspect_xml.c_str()); + tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node"); + if (pRoot == nullptr) + { + std::cerr << "XML document did not contain any data\n"; + } + else + { + tinyxml2::XMLElement* pElement = + pRoot->FirstChildElement("node"); + while (pElement != nullptr) + { + std::string child_path = pElement->Attribute("name"); + if (child_path.empty()) + { + continue; + } + else + { + std::string parent_path(path); + if (parent_path == "/") + { + parent_path.clear(); + } + do_introspect(system_bus, transaction, + interface_map, + parent_path + "/" + child_path); + } + pElement = pElement->NextSiblingElement("node"); + } + + pElement = pRoot->FirstChildElement("interface"); + while (pElement != nullptr) + { + std::string iface_name = pElement->Attribute("name"); + if (iface_name.empty()) + { + continue; + } + else + { + if (ignored_interfaces.find(iface_name.c_str()) == + ignored_interfaces.end()) + { + interface_map[path][transaction->process_name] + .emplace(iface_name); + } + if (iface_name == + "xyz.openbmc_project.Associations") + { + // get association + } + } + pElement = pElement->NextSiblingElement("interface"); + } + } + } + // if we're the last outstanding caller for this process + if (transaction.use_count() == 1) + { + // TODO(ed) This signal doesn't get exposed properly in the + // introspect right now. Find out how to register signals in + // sdbusplus + sdbusplus::message::message m = system_bus->new_signal( + "/xyz/openbmc_project/object_mapper", + "xyz.openbmc_project.ObjectMapper.Private", + "IntrospectionComplete"); + m.append(transaction->process_name); + system_bus->call_noreply(m); + std::chrono::duration<float> diff = + std::chrono::steady_clock::now() - + *transaction->global_start_time; + std::cout << std::setw(40) << transaction->process_name + << " scan took " << diff.count() << " seconds\n"; + + // If we're the last outstanding caller globally, calculate the + // time it took + if (transaction->global_start_time.use_count() == 1) + { + diff = std::chrono::steady_clock::now() - + *transaction->global_start_time; + std::cout << "Total scan took " << diff.count() + << " seconds to complete\n"; + } + } + }, + transaction->process_name, path, "org.freedesktop.DBus.Introspectable", + "Introspect"); +} + +void start_new_introspect( + sdbusplus::asio::connection* system_bus, interface_map_type& interface_map, + const std::string& process_name, + std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>> + global_start_time) +{ + auto transaction = + std::make_shared<InProgressIntrospect>(InProgressIntrospect{ + process_name, std::chrono::steady_clock::now(), global_start_time}); + do_introspect(system_bus, transaction, interface_map, "/"); +} + +template <class InputIt1, class InputIt2> +bool intersect(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2) +{ + while (first1 != last1 && first2 != last2) + { + if (*first1 < *first2) + { + ++first1; + continue; + } + if (*first2 < *first1) + { + ++first2; + continue; + } + return true; + } + return false; +} + +int main(int argc, char** argv) +{ + boost::asio::io_service io; + auto system_bus = std::make_shared<sdbusplus::asio::connection>(io); + system_bus->request_name("xyz.openbmc_project.ObjectMapperX"); + + interface_map_type interface_map; + + std::function<void(sdbusplus::message::message & message)> + nameChangeHandler = [&](sdbusplus::message::message& message) { + std::string name; + std::string old_owner; + std::string new_owner; + + message.read(name, old_owner, new_owner); + + if (!new_owner.empty()) + { + auto transaction = std::make_shared< + std::chrono::time_point<std::chrono::steady_clock>>( + std::chrono::steady_clock::now()); + if (should_scan_dbus_interface(new_owner)) + { + // New daemon added + start_new_introspect(system_bus.get(), interface_map, + new_owner, transaction); + } + } + if (!old_owner.empty()) + { + // Connection removed + interface_map_type::iterator path_it = interface_map.begin(); + while (path_it != interface_map.end()) + { + auto connection_it = path_it->second.find(old_owner); + if (connection_it != path_it->second.end()) + { + if (connection_it->first == old_owner) + { + path_it->second.erase(connection_it); + break; + } + } + if (path_it->second.empty()) + { + // If the last connection to the object is gone, delete + // the top level object + path_it = interface_map.erase(path_it); + continue; + } + path_it++; + } + } + else + { + std::cerr << "ERROR: both new path and old path are empty"; + } + }; + + sdbusplus::bus::match::match nameOwnerChanged( + *system_bus, sdbusplus::bus::match::rules::nameOwnerChanged(), + nameChangeHandler); + + std::function<void(sdbusplus::message::message & message)> + interfacesAddedHandler = [&](sdbusplus::message::message& message) { + sdbusplus::message::object_path obj_path; + std::vector< + std::pair<std::string, + std::vector<std::pair< + std::string, sdbusplus::message::variant<bool>>>>> + interfaces_added; + message.read(obj_path, interfaces_added); + const std::string& obj_str = + static_cast<const std::string&>(obj_path); + auto iface_list = interface_map[obj_str]; + for (const std::pair< + std::string, + std::vector<std::pair<std::string, + sdbusplus::message::variant<bool>>>>& + interface_pair : interfaces_added) + { + iface_list[interface_pair.first].emplace(message.get_sender()); + } + }; + + sdbusplus::bus::match::match interfacesAdded( + *system_bus, sdbusplus::bus::match::rules::interfacesAdded(), + interfacesAddedHandler); + + std::function<void(sdbusplus::message::message & message)> + interfacesRemovedHandler = [&](sdbusplus::message::message& message) { + sdbusplus::message::object_path obj_path; + std::vector<std::string> interfaces_removed; + message.read(obj_path, interfaces_removed); + const std::string& object_path_str = + static_cast<const std::string&>(obj_path); + auto connection_map = interface_map.find(object_path_str); + if (connection_map == interface_map.end()) + { + std::cerr << "Unable to find " << object_path_str + << " in map\n"; + return; + } + + const std::string sender = std::string(message.get_sender()); + + for (const std::string& interface : interfaces_removed) + { + auto interface_set = connection_map->second.find(sender); + if (interface_set == connection_map->second.end()) + { + std::cerr << "Unable to find " << sender << " in map for " + << interface << "\n"; + continue; + } + + interface_set->second.erase(interface); + // If this was the last interface on this connection, erase the + // connection + if (interface_set->second.empty()) + { + connection_map->second.erase(interface_set); + } + } + // If this was the last connection on this object path, erase the + // object path + if (connection_map->second.empty()) + { + interface_map.erase(connection_map); + } + }; + + sdbusplus::bus::match::match interfacesRemoved( + *system_bus, sdbusplus::bus::match::rules::interfacesRemoved(), + interfacesRemovedHandler); + + // Set up the object server, and send some objects + auto server = sdbusplus::asio::object_server(system_bus); + + std::shared_ptr<sdbusplus::asio::dbus_interface> iface = + server.add_interface("/xyz/openbmc_project/object_mapper", + "xyz.openbmc_project.ObjectMapper"); + + iface->register_method( + "GetAncestors", + [&](const std::string& req_path, std::vector<std::string>& interfaces) { + + std::sort(interfaces.begin(), interfaces.end()); + + std::vector<interface_map_type::value_type> ret; + for (auto& object_path : interface_map) + { + auto& this_path = object_path.first; + bool add = interfaces.empty(); + if (boost::starts_with(req_path, this_path)) + { + for (auto& interface_map : object_path.second) + { + add = intersect(interfaces.begin(), interfaces.end(), + interface_map.second.begin(), + interface_map.second.end()); + if (add) + { + break; + } + } + if (add) + { + // THis makes a copy. TODO(ed) make sdbusplus allow + // vectors of pointers so that this doesn't need to copy + // all strings + ret.emplace_back(object_path); + } + } + } + + return ret; + }); + + iface->register_method( + "GetObject", + [&](const std::string& path, std::vector<std::string>& interfaces) { + std::sort(interfaces.begin(), interfaces.end()); + auto path_ref = interface_map.find(path); + bool add = false; + if (path_ref != interface_map.end()) + { + add = interfaces.empty(); + for (auto& interface_map : path_ref->second) + { + if (intersect(interfaces.begin(), interfaces.end(), + interface_map.second.begin(), + interface_map.second.end())) + { + add = true; + break; + } + } + } + if (add) + { + return path_ref->second; + } + return decltype(path_ref->second){}; + }); + + iface->register_method( + "GetSubTree", [&](const std::string& req_path, int32_t depth, + std::vector<std::string>& interfaces) { + std::sort(interfaces.begin(), interfaces.end()); + std::vector<interface_map_type::value_type> ret; + + for (auto& object_path : interface_map) + { + auto& this_path = object_path.first; + if (boost::starts_with(this_path, req_path)) + { + // count the number of slashes past the search term + auto this_depth = + std::count(this_path.begin() + req_path.size(), + this_path.end(), '/'); + if (this_depth <= depth) + { + bool add = interfaces.empty(); + for (auto& interface_map : object_path.second) + { + if (intersect(interfaces.begin(), interfaces.end(), + interface_map.second.begin(), + interface_map.second.end())) + { + add = true; + break; + } + } + if (add) + { + // todo(ed) this is a copy + ret.emplace_back(object_path); + } + } + } + } + return ret; + }); + iface->register_method( + "GetSubTreePaths", [&](const std::string& req_path, int32_t depth, + const std::vector<std::string>& interfaces) { + std::vector<std::string> ret; + for (auto& object_path : interface_map) + { + auto& this_path = object_path.first; + if (boost::starts_with(this_path, req_path)) + { + // count the number of slashes past the search term + auto this_depth = + std::count(this_path.begin() + req_path.size(), + this_path.end(), '/'); + if (this_depth <= depth) + { + bool add = interfaces.empty(); + for (auto& interface_map : object_path.second) + { + if (intersect(interfaces.begin(), interfaces.end(), + interface_map.second.begin(), + interface_map.second.end())) + { + add = true; + break; + } + } + if (add) + { + // TODO(ed) this is a copy + ret.emplace_back(this_path); + } + } + } + } + + return ret; + }); + iface->initialize(); + + // This needs to be done after our io_service is in run, so that the match + // creation and name reqest happen before we start introspecting. + io.post([&]() { + system_bus->async_method_call( + [&](const boost::system::error_code ec, + const std::vector<std::string>& process_names) { + if (ec) + { + std::cerr << "error getting names: " << ec << "\n"; + } + else + { + auto global_start_time = std::make_shared< + std::chrono::time_point<std::chrono::steady_clock>>( + std::chrono::steady_clock::now()); + + for (const std::string& process_name : process_names) + { + if (should_scan_dbus_interface(process_name)) + { + start_new_introspect(system_bus.get(), + interface_map, process_name, + global_start_time); + } + } + } + }, + "org.freedesktop.DBus", "/", "org.freedesktop.DBus", "ListNames"); + }); + io.run(); +} |