From cd89c3452349750676a0ee9d9de95f821b922bed Mon Sep 17 00:00:00 2001 From: Andy Dalton Date: Fri, 21 Dec 2018 11:46:48 -0500 Subject: [PATCH 01/14] Add prototype for async_metric_source This is a prototype for an abstract asynchronous metric source. The idea is that for each metric source (e.g., docker), we'll have a concrete subclass. The concrete subclasses will implement the run_impl() method, which will get invoked in the context of a separate thread. Metrics can be returned to the client code in one of three ways: (1) If we can fetch the metrics within a specified time window, we'll return them synchronously. (2) If we don't fetch the metrics fast enough, and if the client code provided a callback function, then we'll invoke the callback as soon as the metrics are available. (3) If we don't fetch the metrics fast enough, and if the client code did not provide a callback function, then we'll save the metrics and the next time the client code tries to fetch metrics for an entity with the same key, we'll return the previously fetched metric. Option (3) leaves some room for leaking entries -- if we collect metrics for some entity, store them, but the client code never asks for them again. If we can eliminate option (3) and support only options (1) and (2), then this problem goes away. Otherwise, I'll need to build in some way to trim "old" metrics. --- userspace/async/async_metric_source.h | 198 +++++++++++++++++++++ userspace/async/async_metric_source.tpp | 220 ++++++++++++++++++++++++ 2 files changed, 418 insertions(+) create mode 100644 userspace/async/async_metric_source.h create mode 100644 userspace/async/async_metric_source.tpp diff --git a/userspace/async/async_metric_source.h b/userspace/async/async_metric_source.h new file mode 100644 index 0000000000..bd4ef14346 --- /dev/null +++ b/userspace/async/async_metric_source.h @@ -0,0 +1,198 @@ +/* +Copyright (C) 2018 Sysdig, Inc. + +This file is part of sysdig. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace sysdig +{ + +/** + * Base class for classes that need to collect metrics asynchronously from some + * metric source. Subclasses will override the the run_impl() method. In + * that method, subclasses will use use dequeue_next_key() method to get the + * key that it will use to collect the metrics, collect the appropriate metrics, + * and call the store_metrics() method to save the metrics. The run_impl() + * method should continue to dequeue and process metrics while the queue_size() + * method returns non-zero. + * + * The constructor for this class accepts a maximum wait time; this specifies + * how long client code is willing to wait for a synchronous response (i.e., + * how long the lookup() method will block waiting for the requested metrics). + * If the async_metric_source is able to collect the requested metrics within + * that time period, then the lookup() method will return them. + * + * If the lookup() method is unable to collect the requested metrics within + * the requested time period, then one of two things will happen. (1) If + * the client supplied a handler in the call to lookup(), then that handler + * will be invoked by the async_metric_source once the metric has been + * collected. Note that the callback handler will be invoked in the context + * of the asynchronous thread associated with the async_metric_source. (2) If + * the client did not supply a handler, then the metric will be stored, and the + * next call to the lookup() method with the same key will return the previously + * collected metrics. + * + * @tparam key_type The type of the keys for which concrete subclasses will + * query. + * @tparam metric_type The type of metric that concrete subclasses will receive + * from a query. + */ +template +class async_metric_source +{ +public: + typedef std::function callback_handler; + + /** + * Initialize this new async_metric_source, which will block + * synchronously for the given max_wait_ms for metric collection. + * + * @param[in] max_wait_ms The maximum amount of time that client code + * is willing to wait for lookup() to collect + * metrics before falling back to an async + * return. + */ + async_metric_source(uint64_t max_wait_ms); + + async_metric_source(const async_metric_source&) = delete; + async_metric_source(async_metric_source&&) = delete; + async_metric_source& operator=(const async_metric_source&) = delete; + + virtual ~async_metric_source(); + + /** + * Lookup metrics based on the given key. This method will block + * the caller for up the max_wait_ms time specified at construction + * for the desired metrics to be available. + * + * @param[in] key The key to the metric for which the client wishes + * to query. + * @param[out] metric If this method is able to fetch the desired + * metrics within the max_wait_ms specified at + * construction time, then this output parameter will + * contain the collected metrics. The value of this + * parameter is defined only if this method returns + * true. + * @param[in] handler If this method is unable to collect the requested + * metrics before the timeout, and if this parameter + * is a valid, non-empty, function, then this class + * will invoke the given handler from the async + * thread immediately after the collected metrics + * are available. If this handler is empty, then + * this async_metric_source will store the metrics + * and return them on the next call to lookup(). + * + * @returns true if this method was able to lookup and return the + * metric synchronously; false otherwise. + */ + bool lookup(const key_type& key, + metric_type& metric, + const callback_handler& handler = callback_handler()); + + /** + * @returns true if the async thread assocaited with this + * async_metric_source is running, false otherwise. + */ + bool is_running() const; + +protected: + /** + * Stops the thread assocaited with this async_metric_source, if + * it is running. + */ + void stop(); + + /** + * Returns the number of elements in the requeust queue. Concrete + * subclasses will call this methods from their run_impl() methods to + * determine if there is more asynchronous work from them to perform. + * + * @returns the size of the request queue. + */ + std::size_t queue_size() const; + + /** + * Dequeues an entry from the request queue and returns it. Concrete + * subclasses will call this method to get the next key for which + * to collect metrics. + * + * Precondition: queue_size() must be non-zero. + * + * @returns the next key to look up. + */ + key_type dequeue_next_key(); + + /** + * Stores a collected set of metrics for the given key. Concrete + * subclasses will call this method from their run_impl() method to + * save (or otherwise notifiy the client about) a collected metric. + * + * @param[in] key The key for which the client asked for metrics. + * @param[in] metrics The collected metrics. + */ + void store_metric(const key_type& key, const metric_type& metric); + + /** + * Concrete subclasses must override this method to perform the + * asynchronous metric lookup. + */ + virtual void run_impl() = 0; + +private: + struct lookup_request + { + lookup_request(): + m_available(false), + m_metric(), + m_available_condition(), + m_callback() + {} + + bool m_available; + metric_type m_metric; + std::condition_variable m_available_condition; + callback_handler m_callback; + }; + + typedef std::map metric_map; + + void run(); + + uint64_t m_max_wait_ms; + std::thread m_thread; + bool m_running; + bool m_terminate; + mutable std::mutex m_mutex; + std::condition_variable m_start_condition; + std::condition_variable m_queue_not_empty_condition; + std::list m_request_queue; + metric_map m_metric_map; +}; + + +} // end namespace sysdig + +#include "async_metric_source.tpp" diff --git a/userspace/async/async_metric_source.tpp b/userspace/async/async_metric_source.tpp new file mode 100644 index 0000000000..f51745277a --- /dev/null +++ b/userspace/async/async_metric_source.tpp @@ -0,0 +1,220 @@ +/* +Copyright (C) 2018 Sysdig, Inc. + +This file is part of sysdig. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ +#include +#include +#include +#include +#include + +namespace sysdig +{ + +template +async_metric_source::async_metric_source( + const uint64_t max_wait_ms): + m_max_wait_ms(max_wait_ms), + m_thread(), + m_running(false), + m_terminate(false), + m_mutex(), + m_queue_not_empty_condition(), + m_metric_map() +{ } + +template +async_metric_source::~async_metric_source() +{ + try + { + stop(); + } + catch(...) + { + // TODO: Ignore? Log? + } +} + +template +void async_metric_source::stop() +{ + bool join_needed = false; + + { + std::unique_lock guard(m_mutex); + + if(m_running) + { + m_terminate = true; + join_needed = true; + + // The async thread might be waiting for new events + // so wake it up + m_queue_not_empty_condition.notify_one(); + } + } // Drop the mutex before join() + + if (join_needed) + { + m_thread.join(); + + // Remove any pointers from the thread to this object + // (just to be safe) + m_thread = std::thread(); + } +} + +template +bool async_metric_source::is_running() const +{ + std::lock_guard guard(m_mutex); + + return m_running; +} + +template +void async_metric_source::run() +{ + m_running = true; + + while(!m_terminate) + { + { + std::unique_lock guard(m_mutex); + + while(!m_terminate && m_request_queue.empty()) + { + // Wait for something to show up on the queue + m_queue_not_empty_condition.wait(guard); + } + } + + if(!m_terminate) + { + run_impl(); + } + } + + m_running = false; +} + +template +bool async_metric_source::lookup( + const key_type& key, + metric_type& metric, + const callback_handler& callback) +{ + std::unique_lock guard(m_mutex); + + if(!m_running) + { + m_thread = std::thread(&async_metric_source::run, this); + } + + typename metric_map::const_iterator itr = m_metric_map.find(key); + bool found = (itr != m_metric_map.end()) && itr->second.m_available; + + if(!found) + { + if (itr == m_metric_map.end()) + { + m_metric_map[key].m_available = false; + } + + // Make request to API and let the async thread know about it + if (std::find(m_request_queue.begin(), + m_request_queue.end(), + key) == m_request_queue.end()) + { + m_request_queue.push_back(key); + m_queue_not_empty_condition.notify_one(); + } + + // + // If the client code is willing to wait a short amount of time + // to satisfy the request, then wait for the async thread to + // pick up the newly-added request and execute it. If + // processing that request takes too much time, then we'll + // not be able to return the metric information on this call, + // and the async thread will continue handling the request so + // that it'll be available on the next call. + // + if (m_max_wait_ms > 0) + { + m_metric_map[key].m_available_condition.wait_for( + guard, + std::chrono::milliseconds(m_max_wait_ms)); + + itr = m_metric_map.find(key); + found = (itr != m_metric_map.end()) && itr->second.m_available; + + if(!found) + { + m_metric_map[key].m_callback = callback; + } + } + } + + if(found) + { + metric = itr->second.m_metric; + m_metric_map.erase(key); + } + + return found; +} + +template +std::size_t async_metric_source::queue_size() const +{ + std::lock_guard guard(m_mutex); + return m_request_queue.size(); +} + +template +key_type async_metric_source::dequeue_next_key() +{ + std::lock_guard guard(m_mutex); + key_type key = m_request_queue.front(); + + m_request_queue.pop_front(); + + return key; +} + +template +void async_metric_source::store_metric( + const key_type& key, + const metric_type& metric) +{ + std::lock_guard guard(m_mutex); + + if (m_metric_map[key].m_callback) + { + m_metric_map[key].m_callback(key, metric); + m_metric_map.erase(key); + } + else + { + m_metric_map[key].m_metric = metric; + m_metric_map[key].m_available = true; + m_metric_map[key].m_available_condition.notify_one(); + } +} + +} // end namespace sysdig From 7d02663451e0b2cc2d034c7a7b1a71ff46ed9e3f Mon Sep 17 00:00:00 2001 From: Andy Dalton Date: Fri, 21 Dec 2018 18:05:56 -0500 Subject: [PATCH 02/14] Start unit tests for async_metric_source This change starts adding basic unit tests for async_metric_source that demonstrate how a client might use the component. --- .../async/async_metric_source.ut.cpp | 210 ++++++++++++++++++ userspace/async/async_metric_source.h | 4 +- userspace/async/async_metric_source.tpp | 38 ++-- 3 files changed, 235 insertions(+), 17 deletions(-) create mode 100644 test/userspace/async/async_metric_source.ut.cpp diff --git a/test/userspace/async/async_metric_source.ut.cpp b/test/userspace/async/async_metric_source.ut.cpp new file mode 100644 index 0000000000..be43cb2d97 --- /dev/null +++ b/test/userspace/async/async_metric_source.ut.cpp @@ -0,0 +1,210 @@ +/** + * @file + * + * Fill in a short overview of the file's content + * + * @copyright Copyright (c) 2018 Draios Inc. dba Sysdig, All Rights Reserved + */ +#include "async_metric_source.h" + +#include +#include + +using namespace sysdig; + +namespace +{ + +/** + * Intermediate realization of async_metric_source that can return pre-canned + * results. + */ +class precanned_metric_source : public async_metric_source +{ +public: + precanned_metric_source(const uint64_t max_wait_ms) + : async_metric_source(max_wait_ms), + m_responses() + { } + + void set_response(const std::string& key, const std::string& response) + { + m_responses[key] = response; + } + + std::string get_response(const std::string& key) + { + return m_responses[key]; + } + +private: + std::map m_responses; +}; + +/** + * Realization of async_metric_source that returns results without delay. + */ +class immediate_metric_source : public precanned_metric_source +{ +public: + const static uint64_t MAX_WAIT_TIME_MS; + + immediate_metric_source(): + precanned_metric_source(MAX_WAIT_TIME_MS) + { } + +protected: + virtual void run_impl() override + { + while(queue_size() > 0) + { + const std::string key = dequeue_next_key(); + store_metric(key, get_response(key)); + } + } +}; +const uint64_t immediate_metric_source::MAX_WAIT_TIME_MS = 5000; + +/** + * Realization of async_metric_source that returns results with some + * specified delay. + */ +class delayed_metric_source : public precanned_metric_source +{ +public: + const static uint64_t MAX_WAIT_TIME_MS; + + delayed_metric_source(const uint64_t delay_ms): + precanned_metric_source(MAX_WAIT_TIME_MS), + m_delay_ms(delay_ms) + { } + +protected: + virtual void run_impl() override + { + while(queue_size() > 0) + { + const std::string key = dequeue_next_key(); + + std::this_thread::sleep_for(std::chrono::milliseconds(m_delay_ms)); + + store_metric(key, get_response(key)); + } + } + +private: + uint64_t m_delay_ms; +}; +const uint64_t delayed_metric_source::MAX_WAIT_TIME_MS = 0; + +} + +/** + * Ensure that a concrete async_metric_source is in the expected initial state + * after construction. + */ +TEST(async_metric_source, construction) +{ + immediate_metric_source source; + + ASSERT_EQ(immediate_metric_source::MAX_WAIT_TIME_MS, source.get_max_wait()); + ASSERT_FALSE(source.is_running()); +} + +/** + * Ensure that if a concrete async_metric_source returns the metrics before + * the timeout, that the lookup() method returns true, and that it returns + * the metrics in the output parameter. + */ +TEST(async_metric_source, lookup_key_immediate_return) +{ + const std::string key = "foo"; + const std::string metric = "bar"; + std::string response = "response-not-set"; + bool response_found; + + immediate_metric_source source; + + // Seed the precanned response + source.set_response(key, metric); + + response_found = source.lookup(key, response); + + ASSERT_TRUE(response_found); + ASSERT_EQ(metric, response); + ASSERT_TRUE(source.is_running()); +} + +/** + * Ensure that if a concrete async_metric_source cannot return the result + * before the timeout, and if the client did not provide a callback, that + * calling lookup() after the result it available returns the value. + */ +TEST(async_metric_source, lookup_key_delayed_return_second_call) +{ + const uint64_t DELAY_MS = 50; + const std::string key = "mykey"; + const std::string metric = "myvalue"; + + delayed_metric_source source(DELAY_MS); + + std::string response = "response-not-set"; + bool response_found; + + // Seed the precanned response + source.set_response(key, metric); + + response_found = source.lookup(key, response); + + ASSERT_FALSE(response_found); + + // Since we didn't supply a callback, a subsequent call to lookup + // after the metric collection is complete will return the previously + // collected metric. We know that the delayed_metric_source is + // waiting for DELAY_MS, so wait longer than that. + std::this_thread::sleep_for(std::chrono::milliseconds(2 * DELAY_MS)); + + // Response should now be available + response_found = source.lookup(key, response); + + ASSERT_TRUE(response_found); + ASSERT_EQ(metric, response); +} + +/** + * Ensure that if a concrete async_metric_source cannot return the result + * before the timeout, and if the client did provide a callback, that the + * callback is invoked with the metrics once they're avaialble. + */ +TEST(async_metric_source, look_key_delayed_async_callback) +{ + const uint64_t DELAY_MS = 50; + const std::string key = "mykey"; + const std::string metric = "myvalue"; + + delayed_metric_source source(DELAY_MS); + + std::string sync_response = "sync-response-not-set"; + std::string async_response = "async-response-not-set"; + bool response_found; + + // Seed the precanned response + source.set_response(key, metric); + + response_found = source.lookup(key, + sync_response, + [&async_response](const std::string& key, + const std::string& value) + { + async_response = value; + }); + + ASSERT_FALSE(response_found); + + // Since we supplied a callback, the delayed_metric_source should + // complete after DELAY_MS, and it should immediately call our + // callback. Wait long enough for that to happen. + std::this_thread::sleep_for(std::chrono::milliseconds(5 * DELAY_MS)); + + ASSERT_EQ(metric, async_response); +} diff --git a/userspace/async/async_metric_source.h b/userspace/async/async_metric_source.h index bd4ef14346..bfb949a18a 100644 --- a/userspace/async/async_metric_source.h +++ b/userspace/async/async_metric_source.h @@ -83,6 +83,8 @@ class async_metric_source virtual ~async_metric_source(); + uint64_t get_max_wait() const; + /** * Lookup metrics based on the given key. This method will block * the caller for up the max_wait_ms time specified at construction @@ -174,7 +176,7 @@ class async_metric_source bool m_available; metric_type m_metric; std::condition_variable m_available_condition; - callback_handler m_callback; + callback_handler m_callback; // TODO: This may need to be a list }; typedef std::map metric_map; diff --git a/userspace/async/async_metric_source.tpp b/userspace/async/async_metric_source.tpp index f51745277a..6a6ea50c7c 100644 --- a/userspace/async/async_metric_source.tpp +++ b/userspace/async/async_metric_source.tpp @@ -16,6 +16,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +#include #include #include #include @@ -50,6 +51,12 @@ async_metric_source::~async_metric_source() } } +template +uint64_t async_metric_source::get_max_wait() const +{ + return m_max_wait_ms; +} + template void async_metric_source::stop() { @@ -162,11 +169,6 @@ bool async_metric_source::lookup( itr = m_metric_map.find(key); found = (itr != m_metric_map.end()) && itr->second.m_available; - - if(!found) - { - m_metric_map[key].m_callback = callback; - } } } @@ -175,6 +177,10 @@ bool async_metric_source::lookup( metric = itr->second.m_metric; m_metric_map.erase(key); } + else + { + m_metric_map[key].m_callback = callback; + } return found; } @@ -204,17 +210,17 @@ void async_metric_source::store_metric( { std::lock_guard guard(m_mutex); - if (m_metric_map[key].m_callback) - { - m_metric_map[key].m_callback(key, metric); - m_metric_map.erase(key); - } - else - { - m_metric_map[key].m_metric = metric; - m_metric_map[key].m_available = true; - m_metric_map[key].m_available_condition.notify_one(); - } + if (m_metric_map[key].m_callback) + { + m_metric_map[key].m_callback(key, metric); + m_metric_map.erase(key); + } + else + { + m_metric_map[key].m_metric = metric; + m_metric_map[key].m_available = true; + m_metric_map[key].m_available_condition.notify_one(); + } } } // end namespace sysdig From 0eaa79b0baf12f0ed3c4418e56272ebe9088d48f Mon Sep 17 00:00:00 2001 From: Andy Dalton Date: Thu, 3 Jan 2019 17:10:29 -0500 Subject: [PATCH 03/14] Add async Docker metadata collection This change extends the original framework with the addition of async Docker metadata collection. Part of this change is in the same sprit as a change from Greg -- I've trying to split out the Linux/Windows/Noop implementations rather than having macro guards throughout the implementation. My approach differs a little in the sense that I introduced a class hierarchy to deal with it. Note that this is just a prototype; there's a good bit of work that isn't here. That includes: * Need to implement the Windows version of the collector * Need to implement unit tests. That will include the need to implement something that will "act like" Docker. --- .../async/async_metric_source.ut.cpp | 25 +- userspace/async/async_metric_source.h | 8 + userspace/async/async_metric_source.tpp | 44 +- userspace/libsinsp/CMakeLists.txt | 3 + .../libsinsp/async_docker_metrics_source.cpp | 361 +++++++++++++ .../libsinsp/async_docker_metrics_source.h | 140 +++++ .../async_linux_docker_metrics_source.cpp | 206 ++++++++ .../async_linux_docker_metrics_source.h | 57 ++ userspace/libsinsp/container.cpp | 485 ++++-------------- userspace/libsinsp/container.h | 22 +- userspace/libsinsp/event.h | 1 + userspace/libsinsp/ifinfo.h | 2 +- userspace/libsinsp/logger.h | 10 +- userspace/libsinsp/sinsp_public.h | 26 + 14 files changed, 989 insertions(+), 401 deletions(-) create mode 100644 userspace/libsinsp/async_docker_metrics_source.cpp create mode 100644 userspace/libsinsp/async_docker_metrics_source.h create mode 100644 userspace/libsinsp/async_linux_docker_metrics_source.cpp create mode 100644 userspace/libsinsp/async_linux_docker_metrics_source.h create mode 100644 userspace/libsinsp/sinsp_public.h diff --git a/test/userspace/async/async_metric_source.ut.cpp b/test/userspace/async/async_metric_source.ut.cpp index be43cb2d97..a78b598666 100644 --- a/test/userspace/async/async_metric_source.ut.cpp +++ b/test/userspace/async/async_metric_source.ut.cpp @@ -1,10 +1,21 @@ -/** - * @file - * - * Fill in a short overview of the file's content - * - * @copyright Copyright (c) 2018 Draios Inc. dba Sysdig, All Rights Reserved - */ +/* +Copyright (C) 2018 Sysdig, Inc. + +This file is part of sysdig. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ #include "async_metric_source.h" #include diff --git a/userspace/async/async_metric_source.h b/userspace/async/async_metric_source.h index bfb949a18a..a1dbaeff4c 100644 --- a/userspace/async/async_metric_source.h +++ b/userspace/async/async_metric_source.h @@ -63,6 +63,12 @@ template class async_metric_source { public: + /** + * If provided to the constructor as max_wait_ms, then lookup will + * not wait for a response. + */ + const static uint64_t NO_LOOKUP_WAIT = 0; + typedef std::function callback_handler; @@ -147,6 +153,8 @@ class async_metric_source */ key_type dequeue_next_key(); + metric_type get_metrics(const key_type& key); + /** * Stores a collected set of metrics for the given key. Concrete * subclasses will call this method from their run_impl() method to diff --git a/userspace/async/async_metric_source.tpp b/userspace/async/async_metric_source.tpp index 6a6ea50c7c..04a19108b9 100644 --- a/userspace/async/async_metric_source.tpp +++ b/userspace/async/async_metric_source.tpp @@ -16,6 +16,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +#include "logger.h" + #include #include #include @@ -41,13 +43,14 @@ async_metric_source::async_metric_source( template async_metric_source::~async_metric_source() { + g_logger.log("async_metric_source destructor"); try { stop(); } catch(...) { - // TODO: Ignore? Log? + g_logger.log(std::string(__FUNCTION__) + ": Exception in destructor", sinsp_logger::SEV_ERROR); } } @@ -60,6 +63,7 @@ uint64_t async_metric_source::get_max_wait() const template void async_metric_source::stop() { + g_logger.log("ENTRY: sync_metric_source::stop"); bool join_needed = false; { @@ -84,6 +88,7 @@ void async_metric_source::stop() // (just to be safe) m_thread = std::thread(); } + g_logger.log("EXIT: sync_metric_source::stop"); } template @@ -97,6 +102,7 @@ bool async_metric_source::is_running() const template void async_metric_source::run() { + g_logger.log("ENTRY: sync_metric_source::run"); m_running = true; while(!m_terminate) @@ -106,6 +112,7 @@ void async_metric_source::run() while(!m_terminate && m_request_queue.empty()) { + g_logger.log("sync_metric_source::run: Waiting for queue item"); // Wait for something to show up on the queue m_queue_not_empty_condition.wait(guard); } @@ -113,11 +120,13 @@ void async_metric_source::run() if(!m_terminate) { + g_logger.log("sync_metric_source::run: Invoking run_impl"); run_impl(); } } m_running = false; + g_logger.log("EXIT: sync_metric_source::run"); } template @@ -126,21 +135,27 @@ bool async_metric_source::lookup( metric_type& metric, const callback_handler& callback) { + g_logger.log("ENTRY: sync_metric_source::lookup: key:" + key); std::unique_lock guard(m_mutex); if(!m_running) { + g_logger.log("sync_metric_source::lookup: starting thread"); m_thread = std::thread(&async_metric_source::run, this); } typename metric_map::const_iterator itr = m_metric_map.find(key); - bool found = (itr != m_metric_map.end()) && itr->second.m_available; + bool request_complete = (itr != m_metric_map.end()) && itr->second.m_available; - if(!found) + if(!request_complete) { + g_logger.log("sync_metric_source::lookup: metrics for key not yet available"); + // Haven't made the request yet if (itr == m_metric_map.end()) { + g_logger.log("sync_metric_source::lookup: first request for metrics"); m_metric_map[key].m_available = false; + m_metric_map[key].m_metric = metric; } // Make request to API and let the async thread know about it @@ -148,6 +163,7 @@ bool async_metric_source::lookup( m_request_queue.end(), key) == m_request_queue.end()) { + g_logger.log("sync_metric_source::lookup: adding work to queue"); m_request_queue.push_back(key); m_queue_not_empty_condition.notify_one(); } @@ -168,21 +184,23 @@ bool async_metric_source::lookup( std::chrono::milliseconds(m_max_wait_ms)); itr = m_metric_map.find(key); - found = (itr != m_metric_map.end()) && itr->second.m_available; + request_complete = (itr != m_metric_map.end()) && itr->second.m_available; } } - if(found) + g_logger.log("sync_metric_source::lookup: request_complete: " + std::to_string(request_complete)); + if(request_complete) { metric = itr->second.m_metric; m_metric_map.erase(key); } else { + g_logger.log("sync_metric_source::lookup: saving callback"); m_metric_map[key].m_callback = callback; } - return found; + return request_complete; } template @@ -195,32 +213,46 @@ std::size_t async_metric_source::queue_size() const template key_type async_metric_source::dequeue_next_key() { + g_logger.log("ENTRY: sync_metric_source::dequeue_next_key"); std::lock_guard guard(m_mutex); key_type key = m_request_queue.front(); m_request_queue.pop_front(); + g_logger.log("EXIT: sync_metric_source::dequeue_next_key"); return key; } +template +metric_type async_metric_source::get_metrics(const key_type& key) +{ + std::lock_guard guard(m_mutex); + + return m_metric_map[key].m_metric; +} + template void async_metric_source::store_metric( const key_type& key, const metric_type& metric) { + g_logger.log("ENTRY: sync_metric_source::store_metric"); std::lock_guard guard(m_mutex); if (m_metric_map[key].m_callback) { + g_logger.log("sync_metric_source::store_metric: Invoking callback"); m_metric_map[key].m_callback(key, metric); m_metric_map.erase(key); } else { + g_logger.log("sync_metric_source::store_metric: Saving metrics for later"); m_metric_map[key].m_metric = metric; m_metric_map[key].m_available = true; m_metric_map[key].m_available_condition.notify_one(); } + g_logger.log("EXIT: sync_metric_source::store_metric"); } } // end namespace sysdig diff --git a/userspace/libsinsp/CMakeLists.txt b/userspace/libsinsp/CMakeLists.txt index fa47321eb1..e3081a57d5 100644 --- a/userspace/libsinsp/CMakeLists.txt +++ b/userspace/libsinsp/CMakeLists.txt @@ -18,6 +18,7 @@ include_directories(./) include_directories(../../common) include_directories(../libscap) +include_directories(../async) include_directories("${JSONCPP_INCLUDE}") include_directories("${LUAJIT_INCLUDE}") @@ -31,6 +32,8 @@ if(NOT WIN32 AND NOT APPLE) endif() add_library(sinsp STATIC + async_docker_metrics_source.cpp + async_linux_docker_metrics_source.cpp chisel.cpp chisel_api.cpp container.cpp diff --git a/userspace/libsinsp/async_docker_metrics_source.cpp b/userspace/libsinsp/async_docker_metrics_source.cpp new file mode 100644 index 0000000000..6e6e8a18c5 --- /dev/null +++ b/userspace/libsinsp/async_docker_metrics_source.cpp @@ -0,0 +1,361 @@ +/* +Copyright (C) 2018 Sysdig, Inc. + +This file is part of sysdig. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ +#include "async_docker_metrics_source.h" +#include "async_linux_docker_metrics_source.h" +#include "sinsp_int.h" +#include "logger.h" + +using namespace sysdig; + +const uint16_t async_docker_metrics_source::DEFAULT_PORT = 80; + +async_docker_metrics_source::async_docker_metrics_source(const std::string& api_version, + const uint16_t port): + async_metric_source(NO_LOOKUP_WAIT), + m_query_image_info(true), + m_api_version(api_version), + m_port(port) +{ +} + +const std::string& async_docker_metrics_source::get_api_version() const +{ + return m_api_version; +} + +uint16_t async_docker_metrics_source::get_port() const +{ + return m_port; +} + +bool async_docker_metrics_source::query_image_info() const +{ + return m_query_image_info; +} + +void async_docker_metrics_source::set_query_image_info(const bool query_info) +{ + m_query_image_info = query_info; +} + +void async_docker_metrics_source::run_impl() +{ + while(queue_size() > 0) + { + const std::string container_id = dequeue_next_key(); + docker_metrics metrics = get_metrics(container_id); + + if(metrics.m_manager != nullptr) + { + if(parse_docker(metrics.m_manager, + metrics.m_container_info.get())) + { + store_metric(container_id, metrics); + } + } + else + { + g_logger.log("Unexpected null manager", + sinsp_logger::SEV_ERROR); + ASSERT(metrics.m_manager != nullptr); + } + } +} + +bool async_docker_metrics_source::parse_docker(sinsp_container_manager* const manager, + sinsp_container_info* const container) +{ + std::string json; + sinsp_docker_response resp = + get_docker(manager, + build_request("/containers/" + container->m_id + "/json"), + json); + + switch(resp) + { + case sinsp_docker_response::RESP_OK: + break; + + case sinsp_docker_response::RESP_BAD_REQUEST: + m_api_version = ""; + json = ""; + + resp = get_docker(manager, + build_request("/containers/" + container->m_id + "/json"), + json); + if(resp == sinsp_docker_response::RESP_OK) + { + break; + } + /* FALLTHRU */ + + case sinsp_docker_response::RESP_ERROR: + ASSERT(false); + return false; + } + + Json::Value root; + Json::Reader reader; + const bool parsingSuccessful = reader.parse(json, root); + if(!parsingSuccessful) + { + g_logger.log("Parsing unsuccessful", sinsp_logger::SEV_ERROR); + ASSERT(false); + return false; + } + + const Json::Value& config_obj = root["Config"]; + + container->m_image = config_obj["Image"].asString(); + + std::string imgstr = root["Image"].asString(); + size_t cpos = imgstr.find(":"); + if(cpos != std::string::npos) + { + container->m_imageid = imgstr.substr(cpos + 1); + } + + // containers can be spawned using just the imageID as image name, + // with or without the hash prefix (e.g. sha256:) + bool no_name = !container->m_imageid.empty() && + strncmp(container->m_image.c_str(), container->m_imageid.c_str(), + MIN(container->m_image.length(), container->m_imageid.length())) == 0; + no_name |= !imgstr.empty() && + strncmp(container->m_image.c_str(), imgstr.c_str(), + MIN(container->m_image.length(), imgstr.length())) == 0; + + if(!no_name || !m_query_image_info) + { + std::string hostname; + std::string port; + + sinsp_utils::split_container_image(container->m_image, + hostname, + port, + container->m_imagerepo, + container->m_imagetag, + container->m_imagedigest, + false); + } + + if(m_query_image_info && !container->m_imageid.empty() && + (no_name || container->m_imagedigest.empty() || + (!container->m_imagedigest.empty() && container->m_imagetag.empty()))) + { + std::string img_json; + + resp = get_docker(manager, + build_request("/images/" + container->m_imageid + "/json?digests=1"), + json); + if(resp == sinsp_docker_response::RESP_OK) + { + Json::Value img_root; + if(reader.parse(img_json, img_root)) + { + for(const auto& rdig : img_root["RepoDigests"]) + { + if(rdig.isString()) + { + std::string repodigest = rdig.asString(); + if(container->m_imagerepo.empty()) + { + container->m_imagerepo = repodigest.substr(0, repodigest.find("@")); + } + if(repodigest.find(container->m_imagerepo) != std::string::npos) + { + container->m_imagedigest = repodigest.substr(repodigest.find("@")+1); + break; + } + } + } + for(const auto& rtag : img_root["RepoTags"]) + { + if(rtag.isString()) + { + std::string repotag = rtag.asString(); + if(container->m_imagerepo.empty()) + { + container->m_imagerepo = repotag.substr(0, repotag.rfind(":")); + } + if(repotag.find(container->m_imagerepo) != std::string::npos) + { + container->m_imagetag = repotag.substr(repotag.rfind(":")+1); + break; + } + } + } + } + } + } + if(container->m_imagetag.empty()) + { + container->m_imagetag = "latest"; + } + + container->m_name = root["Name"].asString(); + + if(!container->m_name.empty() && container->m_name[0] == '/') + { + container->m_name = container->m_name.substr(1); + } + + const Json::Value& net_obj = root["NetworkSettings"]; + + std::string ip = net_obj["IPAddress"].asString(); + if(ip.empty()) + { + const Json::Value& hconfig_obj = root["HostConfig"]; + std::string net_mode = hconfig_obj["NetworkMode"].asString(); + if(strncmp(net_mode.c_str(), "container:", strlen("container:")) == 0) + { + std::string container_id = net_mode.substr(net_mode.find(":") + 1); + uint32_t container_ip; + const sinsp_container_info* const container_info = manager->get_container(container_id); + if(container_info) + { + container_ip = container_info->m_container_ip; + } + else + { + sinsp_container_info pcnt; + pcnt.m_id = container_id; + parse_docker(manager, &pcnt); + container_ip = pcnt.m_container_ip; + } + container->m_container_ip = container_ip; + } + } + else + { + if(inet_pton(AF_INET, ip.c_str(), &container->m_container_ip) == -1) + { + ASSERT(false); + } + container->m_container_ip = ntohl(container->m_container_ip); + } + + std::vector ports = net_obj["Ports"].getMemberNames(); + for(std::vector::const_iterator it = ports.begin(); it != ports.end(); ++it) + { + size_t tcp_pos = it->find("/tcp"); + if(tcp_pos == std::string::npos) + { + continue; + } + + uint16_t container_port = atoi(it->c_str()); + + const Json::Value& v = net_obj["Ports"][*it]; + if(v.isArray()) + { + for(uint32_t j = 0; j < v.size(); ++j) + { + sinsp_container_info::container_port_mapping port_mapping; + + ip = v[j]["HostIp"].asString(); + std::string port = v[j]["HostPort"].asString(); + + if(inet_pton(AF_INET, ip.c_str(), &port_mapping.m_host_ip) == -1) + { + ASSERT(false); + continue; + } + port_mapping.m_host_ip = ntohl(port_mapping.m_host_ip); + + port_mapping.m_container_port = container_port; + port_mapping.m_host_port = atoi(port.c_str()); + container->m_port_mappings.push_back(port_mapping); + } + } + } + + std::vector labels = config_obj["Labels"].getMemberNames(); + for(std::vector::const_iterator it = labels.begin(); it != labels.end(); ++it) + { + std::string val = config_obj["Labels"][*it].asString(); + container->m_labels[*it] = val; + } + + const Json::Value& env_vars = config_obj["Env"]; + + for(const auto& env_var : env_vars) + { + if(env_var.isString()) + { + container->m_env.emplace_back(env_var.asString()); + } + } + +// TODO: Need to factor this out and get rid of the CYGWING_AGENT check +#ifndef CYGWING_AGENT + // FIXME: Should we move this outside somewhere? + //if (sinsp_container_engine_mesos::set_mesos_task_id(container, tinfo)) + //{ + // g_logger.log("Mesos Docker container: [" + root["Id"].asString() + "], Mesos task ID: [" + container->m_mesos_task_id + ']', sinsp_logger::SEV_DEBUG); + //} +#endif + + const auto& host_config_obj = root["HostConfig"]; + container->m_memory_limit = host_config_obj["Memory"].asInt64(); + container->m_swap_limit = host_config_obj["MemorySwap"].asInt64(); + const auto cpu_shares = host_config_obj["CpuShares"].asInt64(); + if(cpu_shares > 0) + { + container->m_cpu_shares = cpu_shares; + } + container->m_cpu_quota = host_config_obj["CpuQuota"].asInt64(); + const auto cpu_period = host_config_obj["CpuPeriod"].asInt64(); + if(cpu_period > 0) + { + container->m_cpu_period = cpu_period; + } + const Json::Value &privileged = host_config_obj["Privileged"]; + if(!privileged.isNull() && privileged.isBool()) + { + container->m_privileged = privileged.asBool(); + } + + sinsp_container_info::parse_json_mounts(root["Mounts"], container->m_mounts); + +#ifdef HAS_ANALYZER + sinsp_utils::find_env(container->m_sysdig_agent_conf, container->get_env(), "SYSDIG_AGENT_CONF"); + // container->m_sysdig_agent_conf = get_docker_env(env_vars, "SYSDIG_AGENT_CONF"); +#endif + g_logger.log("EXIT: parse_docker"); + return true; +} + +async_docker_metrics_source* async_docker_metrics_source::new_async_docker_metrics_source() +{ + async_docker_metrics_source* docker_metrics = nullptr; + +#if defined(CYGWING_AGENT) + // TODO: Need to implement async_windows_docker_metrics + // docker_metrics = new async_windows_docker_metrics(); +#else // CYGWING_AGENT +# if defined(HAS_ANALYZER) + docker_metrics = new async_linux_docker_metrics_source(); +# else // HAS_ANALYZER + // TODO: Need to implement async_null_docker_metrics_source + // docker_metrics = new async_null_docker_metrics_source(); +# endif //HAS_ANALYZER +#endif // CYGWING_AGENT + + return docker_metrics; +} diff --git a/userspace/libsinsp/async_docker_metrics_source.h b/userspace/libsinsp/async_docker_metrics_source.h new file mode 100644 index 0000000000..ebb0f76c30 --- /dev/null +++ b/userspace/libsinsp/async_docker_metrics_source.h @@ -0,0 +1,140 @@ +/* +Copyright (C) 2018 Sysdig, Inc. + +This file is part of sysdig. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ +#pragma once + +#include "async_metric_source.h" +#include "sinsp.h" +#include "container.h" + +namespace sysdig +{ + +// TODO: Can this be inside the class below? +struct docker_metrics +{ + docker_metrics(): + m_manager(nullptr), + m_container_info() + { + } + + docker_metrics(sinsp_container_manager* const manager, + std::shared_ptr& container_info): + m_manager(manager), + m_container_info(container_info) + { + } + + + sinsp_container_manager* m_manager; + std::shared_ptr m_container_info; +}; + +/** + * Interface to async_docker_metrics_source -- an abstract async_metric_source + * for fetching docker metrics and metadata. + */ +class async_docker_metrics_source : public async_metric_source +{ +public: + /** The default port on which Docker listens for REST requests. */ + static const uint16_t DEFAULT_PORT; + + /** + * Returns the API version that this async_metric_source will use to + * fetch information from Docker. + */ + const std::string& get_api_version() const; + + + /** + * Returns the port that this async_docker_metrics_source will use + * to connect to Docker. + */ + uint16_t get_port() const; + + /** + * Returns true if this async_docker_metrics_source should query for + * image info, false otherwise. + */ + bool query_image_info() const; + + /** + * Update the query_image_info state for this async_docker_metrics_source. + */ + void set_query_image_info(bool query_info); + + /** + * Creates a new async_docker_metric_source that is appropriate + * for the build environment (Linux/Windows/no-analyzer) + * + * Note that the caller is responsible for deleting the returned object. + */ + static async_docker_metrics_source* new_async_docker_metrics_source(); + +protected: + async_docker_metrics_source(const std::string& api_version, + uint16_t port = DEFAULT_PORT); + + /** + * Builds and returns a URL for querying Docker on the local host. + * This differs between Linux and Windows, so the concrete implementation + * is left to subclasses. + * + * @param[in] url The base path of the URL + */ + virtual std::string build_request(const std::string& url) = 0; + + /** + * Fetches the JSON from Docker using the given url. + * + * @param[in] manager Used to query container information + * @param[in] url The URL to query + * @param[out] json The fetched JSON + */ + virtual sinsp_docker_response get_docker(sinsp_container_manager* manager, + const std::string& url, + std::string &json) = 0; + + /** + * Parses the JSON returned from Dcoker and populates the given + * container with the information within. + * + * @param[in] manager Used to query container information + * @param[in,out] container The container information to populate + * + * @returns true on success, false otherwise. + */ + bool parse_docker(sinsp_container_manager* manager, + sinsp_container_info *container); + + /** + * Drives the asynchronous fetching of the information from docker. + * This method runs in the context of the thread associated with + * this async_docker_metrics_source. + */ + void run_impl() override; + +private: + bool m_query_image_info; + std::string m_api_version; + uint16_t m_port; +}; + +} // end namespace sysdig diff --git a/userspace/libsinsp/async_linux_docker_metrics_source.cpp b/userspace/libsinsp/async_linux_docker_metrics_source.cpp new file mode 100644 index 0000000000..dfba063d2d --- /dev/null +++ b/userspace/libsinsp/async_linux_docker_metrics_source.cpp @@ -0,0 +1,206 @@ +/* +Copyright (C) 2018 Sysdig, Inc. + +This file is part of sysdig. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ +#include "async_linux_docker_metrics_source.h" +#include "sinsp_int.h" +#include "logger.h" + +using namespace sysdig; + +namespace +{ + +const std::string s_docker_socket_path = "/var/run/docker.sock"; + +#if defined(HAS_CAPTURE) +/** + * Handles callbacks from libcurl to build a string representation of the + * document that its fetching. + */ +size_t docker_curl_write_callback(const char* const ptr, + const size_t size, + const size_t nmemb, + std::string* const json) +{ + const std::size_t total = size * nmemb; + + json->append(ptr, total); + + return total; +} +#endif + +// TODO: Move to windows implementation file +// sinsp_docker_response get_docker(sinsp_container_manager* const manager, +// const std::string& url, +// std::string &json) +// { +// const char* response = NULL; +// bool qdres = wh_query_docker(manager->get_inspector()->get_wmi_handle(), +// (char*)url.c_str(), +// &response); +// if(qdres == false) +// { +// ASSERT(false); +// return sinsp_docker_response::RESP_ERROR; +// } +// +// json = response; +// if(strncmp(json.c_str(), "HTTP/1.0 200 OK", sizeof("HTTP/1.0 200 OK") -1)) +// { +// return sinsp_docker_response::RESP_BAD_REQUEST; +// } +// +// size_t pos = json.find("{"); +// if(pos == std::string::npos) +// { +// ASSERT(false); +// return sinsp_docker_response::RESP_ERROR; +// } +// json = json.substr(pos); +// +// return sinsp_docker_response::RESP_OK; +// } + +} // end namespace + +const std::string async_linux_docker_metrics_source::DEFAULT_API_VERSION = "/v1.24"; + +async_linux_docker_metrics_source::async_linux_docker_metrics_source( + const std::string& api_version, + const uint16_t port): + async_docker_metrics_source(api_version, port) + , m_unix_socket_path(scap_get_host_root() + s_docker_socket_path) +#if defined(HAS_CAPTURE) + , m_curl(curl_easy_init()) + , m_curlm(curl_multi_init()) +#endif +{ +#if defined(HAS_CAPTURE) + if(m_curlm != nullptr) + { + curl_multi_setopt(m_curlm, + CURLMOPT_PIPELINING, + CURLPIPE_HTTP1 | CURLPIPE_MULTIPLEX); + } + + if(m_curl != nullptr) + { + curl_easy_setopt(m_curl, CURLOPT_UNIX_SOCKET_PATH, m_unix_socket_path.c_str()); + curl_easy_setopt(m_curl, CURLOPT_HTTPGET, 1); + curl_easy_setopt(m_curl, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, docker_curl_write_callback); + } +#endif +} + +async_linux_docker_metrics_source::~async_linux_docker_metrics_source() +{ +#if defined(HAS_CAPTURE) + curl_easy_cleanup(m_curl); + curl_multi_cleanup(m_curlm); +#endif +} + +std::string async_linux_docker_metrics_source::build_request(const std::string& url) +{ + return "http://localhost" + get_api_version() + url; +} + +sinsp_docker_response async_linux_docker_metrics_source::get_docker( + sinsp_container_manager* const, + const std::string& url, + std::string &json) +{ +#if defined(HAS_CAPTURE) + + if(curl_easy_setopt(m_curl, CURLOPT_URL, url.c_str()) != CURLE_OK) + { + ASSERT(false); + return sinsp_docker_response::RESP_ERROR; + } + + if(curl_easy_setopt(m_curl, CURLOPT_PORT, get_port()) != CURLE_OK) + { + ASSERT(false); + return sinsp_docker_response::RESP_ERROR; + } + + if(curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &json) != CURLE_OK) + { + ASSERT(false); + return sinsp_docker_response::RESP_ERROR; + } + + if(curl_multi_add_handle(m_curlm, m_curl) != CURLM_OK) + { + ASSERT(false); + return sinsp_docker_response::RESP_ERROR; + } + + while(true) + { + int still_running = 42; + CURLMcode res = curl_multi_perform(m_curlm, &still_running); + + if(res != CURLM_OK) + { + ASSERT(false); + return sinsp_docker_response::RESP_ERROR; + } + + if(still_running == 0) + { + break; + } + + int numfds = 0; + res = curl_multi_wait(m_curlm, NULL, 0, -1, &numfds); + if(res != CURLM_OK) + { + ASSERT(false); + return sinsp_docker_response::RESP_ERROR; + } + } + + if(curl_multi_remove_handle(m_curlm, m_curl) != CURLM_OK) + { + ASSERT(false); + return sinsp_docker_response::RESP_ERROR; + } + + long http_code = 0; + if(curl_easy_getinfo(m_curl, CURLINFO_RESPONSE_CODE, &http_code) != CURLE_OK) + { + ASSERT(false); + return sinsp_docker_response::RESP_ERROR; + } + + if(http_code != 200) + { + g_logger.log("http_code: " + std::to_string(http_code), + sinsp_logger::SEV_WARNING); + return sinsp_docker_response::RESP_BAD_REQUEST; + } + + return sinsp_docker_response::RESP_OK; +#else /* HAS_CAPTURE */ + return sinsp_docker_response::RESP_ERROR; +#endif /* HAS_CAPTURE */ +} + diff --git a/userspace/libsinsp/async_linux_docker_metrics_source.h b/userspace/libsinsp/async_linux_docker_metrics_source.h new file mode 100644 index 0000000000..04a8afce3b --- /dev/null +++ b/userspace/libsinsp/async_linux_docker_metrics_source.h @@ -0,0 +1,57 @@ +/* +Copyright (C) 2018 Sysdig, Inc. + +This file is part of sysdig. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ +#pragma once + +#include "async_docker_metrics_source.h" +#include + +namespace sysdig +{ + +/** + * Interface to async_linux_docker_metrics_source -- a concrete + * async_docker_metrics_source for fetching docker metrics and metadata + * on Linux. + */ +class async_linux_docker_metrics_source : public async_docker_metrics_source +{ +public: + const static std::string DEFAULT_API_VERSION; + + async_linux_docker_metrics_source( + const std::string& api_version = DEFAULT_API_VERSION, + uint16_t port = DEFAULT_PORT); + ~async_linux_docker_metrics_source(); + +protected: + std::string build_request(const std::string& url) override; + sinsp_docker_response get_docker(sinsp_container_manager* manager, + const std::string& url, + std::string &json) override; + +private: + std::string m_unix_socket_path; + +#if defined(HAS_CAPTURE) + CURL* const m_curl; + CURLM* const m_curlm; +#endif +}; + +} diff --git a/userspace/libsinsp/container.cpp b/userspace/libsinsp/container.cpp index 8d1a129a1a..8f3443681a 100644 --- a/userspace/libsinsp/container.cpp +++ b/userspace/libsinsp/container.cpp @@ -23,6 +23,7 @@ limitations under the License. #include #endif +#include "async_docker_metrics_source.h" #include "sinsp.h" #include "sinsp_int.h" #include "container.h" @@ -31,6 +32,10 @@ limitations under the License. #include "dragent_win_hal_public.h" #endif +#include + +using namespace sysdig; + void sinsp_container_info::parse_json_mounts(const Json::Value &mnt_obj, vector &mounts) { if(!mnt_obj.isNull() && mnt_obj.isArray()) @@ -83,316 +88,125 @@ const sinsp_container_info::container_mount_info *sinsp_container_info::mount_by return NULL; } -#if !defined(CYGWING_AGENT) && defined(HAS_CAPTURE) -CURLM *sinsp_container_engine_docker::m_curlm = NULL; -CURL *sinsp_container_engine_docker::m_curl = NULL; -#endif - -bool sinsp_container_engine_docker::m_query_image_info = true; - -sinsp_container_engine_docker::sinsp_container_engine_docker() : - m_unix_socket_path(string(scap_get_host_root()) + "/var/run/docker.sock"), - m_api_version("/v1.24") +namespace { -#if !defined(CYGWING_AGENT) && defined(HAS_CAPTURE) - if(!m_curlm) - { - m_curl = curl_easy_init(); - m_curlm = curl_multi_init(); - - if(m_curlm) - { - curl_multi_setopt(m_curlm, CURLMOPT_PIPELINING, CURLPIPE_HTTP1|CURLPIPE_MULTIPLEX); - } - - if(m_curl) - { - curl_easy_setopt(m_curl, CURLOPT_UNIX_SOCKET_PATH, m_unix_socket_path.c_str()); - curl_easy_setopt(m_curl, CURLOPT_HTTPGET, 1); - curl_easy_setopt(m_curl, CURLOPT_FOLLOWLOCATION, 1); - curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, curl_write_callback); - } - } -#endif -} -void sinsp_container_engine_docker::cleanup() +std::ostream& operator<<(std::ostream& out, const sinsp_container_info::container_mount_info& info) { -#if !defined(CYGWING_AGENT) && defined(HAS_CAPTURE) - curl_easy_cleanup(m_curl); - m_curl = NULL; - curl_multi_cleanup(m_curlm); - m_curlm = NULL; -#endif + out << info.to_string(); + return out; } -void sinsp_container_engine_docker::set_query_image_info(bool query_image_info) +std::ostream& operator<<(std::ostream& out, const sinsp_container_info::container_port_mapping& info) { - m_query_image_info = query_image_info; + out << info.to_string(); + return out; } -#if !defined(CYGWING_AGENT) && defined(HAS_CAPTURE) -size_t sinsp_container_engine_docker::curl_write_callback(const char* ptr, size_t size, size_t nmemb, string* json) +template +std::string iterable_to_string(const Iterable& itr) { - const std::size_t total = size * nmemb; - json->append(ptr, total); - return total; -} -#endif + std::stringstream out; + auto i = itr.begin(); -bool sinsp_container_engine_docker::parse_docker(sinsp_container_manager* manager, sinsp_container_info *container, sinsp_threadinfo* tinfo) -{ - string json; -#ifndef CYGWING_AGENT - sinsp_docker_response resp = get_docker(manager, "http://localhost" + m_api_version + "/containers/" + container->m_id + "/json", json); -#else - sinsp_docker_response resp = get_docker(manager, "GET /v1.30/containers/" + container->m_id + "/json HTTP/1.1\r\nHost: docker\r\n\r\n", json); -#endif - switch(resp) { - case sinsp_docker_response::RESP_BAD_REQUEST: - m_api_version = ""; - json = ""; -#ifndef CYGWING_AGENT - resp = get_docker(manager, "http://localhost/containers/" + container->m_id + "/json", json); -#else - resp = get_docker(manager, "GET /containers/" + container->m_id + "/json HTTP/1.1\r\nHost: docker\r\n\r\n", json); -#endif - if (resp == sinsp_docker_response::RESP_OK) - { - break; - } - /* FALLTHRU */ - case sinsp_docker_response::RESP_ERROR: - ASSERT(false); - return false; - - case sinsp_docker_response::RESP_OK: - break; - } + out << "["; - Json::Value root; - Json::Reader reader; - bool parsingSuccessful = reader.parse(json, root); - if(!parsingSuccessful) + if (i != itr.end()) { - ASSERT(false); - return false; - } + out << *i; - const Json::Value& config_obj = root["Config"]; - - container->m_image = config_obj["Image"].asString(); - - string imgstr = root["Image"].asString(); - size_t cpos = imgstr.find(":"); - if(cpos != string::npos) - { - container->m_imageid = imgstr.substr(cpos + 1); - } - - // containers can be spawned using just the imageID as image name, - // with or without the hash prefix (e.g. sha256:) - bool no_name = !container->m_imageid.empty() && - strncmp(container->m_image.c_str(), container->m_imageid.c_str(), - MIN(container->m_image.length(), container->m_imageid.length())) == 0; - no_name |= !imgstr.empty() && - strncmp(container->m_image.c_str(), imgstr.c_str(), - MIN(container->m_image.length(), imgstr.length())) == 0; - - if(!no_name || !m_query_image_info) - { - string hostname, port; - sinsp_utils::split_container_image(container->m_image, - hostname, - port, - container->m_imagerepo, - container->m_imagetag, - container->m_imagedigest, - false); - } - - if(m_query_image_info && !container->m_imageid.empty() && - (no_name || container->m_imagedigest.empty() || (!container->m_imagedigest.empty() && container->m_imagetag.empty()))) - { - string img_json; -#ifndef CYGWING_AGENT - if(get_docker(manager, "http://localhost" + m_api_version + "/images/" + container->m_imageid + "/json?digests=1", img_json) == sinsp_docker_response::RESP_OK) -#else - if(get_docker(manager, "GET /v1.30/images/" + container->m_imageid + "/json?digests=1 HTTP/1.1\r\nHost: docker \r\n\r\n", img_json) == sinsp_docker_response::RESP_OK) -#endif + for(++i; i != itr.end(); ++i) { - Json::Value img_root; - if(reader.parse(img_json, img_root)) - { - for(const auto& rdig : img_root["RepoDigests"]) - { - if(rdig.isString()) - { - string repodigest = rdig.asString(); - if(container->m_imagerepo.empty()) - { - container->m_imagerepo = repodigest.substr(0, repodigest.find("@")); - } - if(repodigest.find(container->m_imagerepo) != string::npos) - { - container->m_imagedigest = repodigest.substr(repodigest.find("@")+1); - break; - } - } - } - for(const auto& rtag : img_root["RepoTags"]) - { - if(rtag.isString()) - { - string repotag = rtag.asString(); - if(container->m_imagerepo.empty()) - { - container->m_imagerepo = repotag.substr(0, repotag.rfind(":")); - } - if(repotag.find(container->m_imagerepo) != string::npos) - { - container->m_imagetag = repotag.substr(repotag.rfind(":")+1); - break; - } - } - } - } + out << ", " << *i; } } - if(container->m_imagetag.empty()) - { - container->m_imagetag = "latest"; - } - container->m_name = root["Name"].asString(); + out << "]"; - if(!container->m_name.empty() && container->m_name[0] == '/') - { - container->m_name = container->m_name.substr(1); - } + return out.str(); +} - const Json::Value& net_obj = root["NetworkSettings"]; +template +std::string map_to_string(const std::map& target) +{ + std::stringstream out; + auto i = target.begin(); - string ip = net_obj["IPAddress"].asString(); - if(ip.empty()) - { - const Json::Value& hconfig_obj = root["HostConfig"]; - string net_mode = hconfig_obj["NetworkMode"].asString(); - if(strncmp(net_mode.c_str(), "container:", strlen("container:")) == 0) - { - std::string container_id = net_mode.substr(net_mode.find(":") + 1); - uint32_t container_ip; - const sinsp_container_info *container_info = manager->get_container(container_id); - if(container_info) - { - container_ip = container_info->m_container_ip; - } - else - { - sinsp_container_info pcnt; - pcnt.m_id = container_id; - parse_docker(manager, &pcnt, tinfo); - container_ip = pcnt.m_container_ip; - } - container->m_container_ip = container_ip; - } - } - else - { - if(inet_pton(AF_INET, ip.c_str(), &container->m_container_ip) == -1) - { - ASSERT(false); - } - container->m_container_ip = ntohl(container->m_container_ip); - } + out << "{"; - vector ports = net_obj["Ports"].getMemberNames(); - for(vector::const_iterator it = ports.begin(); it != ports.end(); ++it) + if (i != target.end()) { - size_t tcp_pos = it->find("/tcp"); - if(tcp_pos == string::npos) + out << i->first << ":" << i->second; + + for(++i; i != target.end(); ++i) { - continue; + out << ", " << i->first << ":" << i->second; } + } - uint16_t container_port = atoi(it->c_str()); + out << "}"; - const Json::Value& v = net_obj["Ports"][*it]; - if(v.isArray()) - { - for(uint32_t j = 0; j < v.size(); ++j) - { - sinsp_container_info::container_port_mapping port_mapping; + return out.str(); +} - ip = v[j]["HostIp"].asString(); - string port = v[j]["HostPort"].asString(); +} // end namespace - if(inet_pton(AF_INET, ip.c_str(), &port_mapping.m_host_ip) == -1) - { - ASSERT(false); - continue; - } - port_mapping.m_host_ip = ntohl(port_mapping.m_host_ip); - - port_mapping.m_container_port = container_port; - port_mapping.m_host_port = atoi(port.c_str()); - container->m_port_mappings.push_back(port_mapping); - } - } - } +std::string sinsp_container_info::to_string() const +{ + std::stringstream out; + + out << "container_info:" << std::endl; + + out << "m_id: " << m_id << std::endl; + out << "m_type: " << static_cast(m_type) << std::endl; + out << "m_name: " << m_name << std::endl; + out << "m_image: " << m_image << std::endl; + out << "m_imageid: " << m_imageid << std::endl; + out << "m_imagerepo: " << m_imagerepo << std::endl; + out << "m_imagetag: " << m_imagetag << std::endl; + out << "m_imagedigest: " << m_imagedigest << std::endl; + out << "m_container_ip: " << m_container_ip << std::endl; + out << "m_privileged: " << m_privileged << std::endl; + out << "m_mounts: " << ::iterable_to_string(m_mounts) << std::endl; + out << "m_port_mappings: " << ::iterable_to_string(m_port_mappings) << std::endl; + out << "m_labels: " << ::map_to_string(m_labels) << std::endl; + out << "m_env: " << ::iterable_to_string(m_env) << std::endl; + out << "m_mesos_task_id: " << m_mesos_task_id << std::endl; + out << "m_memory_limit: " << m_memory_limit << std::endl; + out << "m_swap_limit: " << m_swap_limit << std::endl; + out << "m_cpu_shares: " << m_cpu_shares << std::endl; + out << "m_cpu_quota: " << m_cpu_quota << std::endl; + out << "m_cpu_period: " << m_cpu_period << std::endl; +#ifdef HAS_ANALYZER + out << "m_sysdig_agent_conf: " << m_sysdig_agent_conf << std::endl; + out << "m_metadata_deadline: " << m_metadata_deadline << std::endl; +#endif + return out.str(); +} - vector labels = config_obj["Labels"].getMemberNames(); - for(vector::const_iterator it = labels.begin(); it != labels.end(); ++it) - { - string val = config_obj["Labels"][*it].asString(); - container->m_labels[*it] = val; - } +std::unique_ptr s_docker_metrics; - const Json::Value& env_vars = config_obj["Env"]; - for(const auto& env_var : env_vars) - { - if(env_var.isString()) - { - container->m_env.emplace_back(env_var.asString()); - } - } -#ifndef CYGWING_AGENT - if (sinsp_container_engine_mesos::set_mesos_task_id(container, tinfo)) - { - g_logger.log("Mesos Docker container: [" + root["Id"].asString() + "], Mesos task ID: [" + container->m_mesos_task_id + ']', sinsp_logger::SEV_DEBUG); - } -#endif +bool sinsp_container_engine_docker::m_query_image_info = true; - const auto& host_config_obj = root["HostConfig"]; - container->m_memory_limit = host_config_obj["Memory"].asInt64(); - container->m_swap_limit = host_config_obj["MemorySwap"].asInt64(); - const auto cpu_shares = host_config_obj["CpuShares"].asInt64(); - if(cpu_shares > 0) - { - container->m_cpu_shares = cpu_shares; - } - container->m_cpu_quota = host_config_obj["CpuQuota"].asInt64(); - const auto cpu_period = host_config_obj["CpuPeriod"].asInt64(); - if(cpu_period > 0) - { - container->m_cpu_period = cpu_period; - } - const Json::Value &privileged = host_config_obj["Privileged"]; - if(!privileged.isNull() && privileged.isBool()) +sinsp_container_engine_docker::sinsp_container_engine_docker() +{ + if(!s_docker_metrics) { - container->m_privileged = privileged.asBool(); + s_docker_metrics.reset(async_docker_metrics_source::new_async_docker_metrics_source()); } - sinsp_container_info::parse_json_mounts(root["Mounts"], container->m_mounts); +} -#ifdef HAS_ANALYZER - sinsp_utils::find_env(container->m_sysdig_agent_conf, container->get_env(), "SYSDIG_AGENT_CONF"); - // container->m_sysdig_agent_conf = get_docker_env(env_vars, "SYSDIG_AGENT_CONF"); -#endif - return true; +void sinsp_container_engine_docker::cleanup() +{ + s_docker_metrics.reset(); } +void sinsp_container_engine_docker::set_query_image_info(bool query_image_info) +{ + m_query_image_info = query_image_info; +} #ifdef CYGWING_AGENT bool sinsp_container_engine_docker::resolve(sinsp_container_manager* manager, sinsp_threadinfo* tinfo, bool query_os_for_missing_info) @@ -421,39 +235,11 @@ bool sinsp_container_engine_docker::resolve(sinsp_container_manager* manager, si return true; } -sinsp_docker_response sinsp_container_engine_docker::get_docker(sinsp_container_manager* manager, const string& url, string &json) -{ - const char* response = NULL; - bool qdres = wh_query_docker(manager->get_inspector()->get_wmi_handle(), - (char*)url.c_str(), - &response); - if(qdres == false) - { - ASSERT(false); - return sinsp_docker_response::RESP_ERROR; - } - - json = response; - if(strncmp(json.c_str(), "HTTP/1.0 200 OK", sizeof("HTTP/1.0 200 OK") -1)) - { - return sinsp_docker_response::RESP_BAD_REQUEST; - } - - size_t pos = json.find("{"); - if(pos == string::npos) - { - ASSERT(false); - return sinsp_docker_response::RESP_ERROR; - } - json = json.substr(pos); - - return sinsp_docker_response::RESP_OK; -} #else bool sinsp_container_engine_docker::resolve(sinsp_container_manager* manager, sinsp_threadinfo* tinfo, bool query_os_for_missing_info) { - sinsp_container_info container_info; + std::shared_ptr container_info(new sinsp_container_info()); bool matches = false; for(auto it = tinfo->m_cgroups.begin(); it != tinfo->m_cgroups.end(); ++it) @@ -470,8 +256,8 @@ bool sinsp_container_engine_docker::resolve(sinsp_container_manager* manager, si if(cgroup.length() - pos - 1 == 64 && cgroup.find_first_not_of("0123456789abcdefABCDEF", pos + 1) == string::npos) { - container_info.m_type = CT_DOCKER; - container_info.m_id = cgroup.substr(pos + 1, 12); + container_info->m_type = CT_DOCKER; + container_info->m_id = cgroup.substr(pos + 1, 12); matches = true; break; } @@ -487,8 +273,8 @@ bool sinsp_container_engine_docker::resolve(sinsp_container_manager* manager, si if(pos2 != string::npos && pos2 - pos - sizeof("docker-") + 1 == 64) { - container_info.m_type = CT_DOCKER; - container_info.m_id = cgroup.substr(pos + sizeof("docker-") - 1, 12); + container_info->m_type = CT_DOCKER; + container_info->m_id = cgroup.substr(pos + sizeof("docker-") - 1, 12); matches = true; break; } @@ -498,88 +284,43 @@ bool sinsp_container_engine_docker::resolve(sinsp_container_manager* manager, si if (!matches) return false; - tinfo->m_container_id = container_info.m_id; - if (!manager->container_exists(container_info.m_id)) + tinfo->m_container_id = container_info->m_id; + if (!manager->container_exists(container_info->m_id)) { + g_logger.log("resolve: container with id " + container_info->m_id + " does not exist"); #ifndef _WIN32 + g_logger.log("resolve: query_os_for_missing_info: " + std::to_string(query_os_for_missing_info)); if (query_os_for_missing_info) { - parse_docker(manager, &container_info, tinfo); - } -#endif - manager->add_container(container_info, tinfo); - manager->notify_new_container(container_info); - } - return true; -} + docker_metrics metrics(manager, container_info); -sinsp_docker_response sinsp_container_engine_docker::get_docker(sinsp_container_manager* manager, const string& url, string &json) -{ -#ifdef HAS_CAPTURE - if(curl_easy_setopt(m_curl, CURLOPT_URL, url.c_str()) != CURLE_OK) - { - ASSERT(false); - return sinsp_docker_response::RESP_ERROR; - } - if(curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &json) != CURLE_OK) - { - ASSERT(false); - return sinsp_docker_response::RESP_ERROR; - } + // TODO: This will need to eventually change when we + // want to report partial information to the + // backend. + if(s_docker_metrics->lookup(tinfo->m_container_id, metrics)) + { + g_logger.log("resolve: metric lookup successful, metrics: " + metrics.m_container_info->to_string()); - if(curl_multi_add_handle(m_curlm, m_curl) != CURLM_OK) - { - ASSERT(false); - return sinsp_docker_response::RESP_ERROR; - } + manager->add_container(*metrics.m_container_info, tinfo); + manager->notify_new_container(*metrics.m_container_info); - while(true) - { - int still_running; - CURLMcode res = curl_multi_perform(m_curlm, &still_running); - if(res != CURLM_OK) - { - ASSERT(false); - return sinsp_docker_response::RESP_ERROR; - } + return true; + } - if(still_running == 0) - { - break; + return false; } +#endif - int numfds; - res = curl_multi_wait(m_curlm, NULL, 0, -1, &numfds); - if(res != CURLM_OK) + if (!query_os_for_missing_info) { - ASSERT(false); - return sinsp_docker_response::RESP_ERROR; + manager->add_container(*container_info, tinfo); + manager->notify_new_container(*container_info); } } - - if(curl_multi_remove_handle(m_curlm, m_curl) != CURLM_OK) - { - ASSERT(false); - return sinsp_docker_response::RESP_ERROR; - } - - long http_code = 0; - if(curl_easy_getinfo(m_curl, CURLINFO_RESPONSE_CODE, &http_code) != CURLE_OK) - { - ASSERT(false); - return sinsp_docker_response::RESP_ERROR; - } - if(http_code != 200) - { - return sinsp_docker_response::RESP_BAD_REQUEST; - } - - return sinsp_docker_response::RESP_OK; -#else - return sinsp_docker_response::RESP_ERROR; -#endif + return true; } + bool sinsp_container_engine_lxc::resolve(sinsp_container_manager* manager, sinsp_threadinfo* tinfo, bool query_os_for_missing_info) { sinsp_container_info container_info; diff --git a/userspace/libsinsp/container.h b/userspace/libsinsp/container.h index a7130d1880..0126cf81af 100644 --- a/userspace/libsinsp/container.h +++ b/userspace/libsinsp/container.h @@ -60,6 +60,13 @@ class sinsp_container_info uint32_t m_host_ip; uint16_t m_host_port; uint16_t m_container_port; + + std::string to_string() const + { + return std::to_string(m_host_ip) + ":" + + std::to_string(m_host_port) + "->" + + std::to_string(m_container_port); + } }; class container_mount_info @@ -135,6 +142,8 @@ class sinsp_container_info const container_mount_info *mount_by_source(std::string &source) const; const container_mount_info *mount_by_dest(std::string &dest) const; + std::string to_string() const; + string m_id; sinsp_container_type m_type; string m_name; @@ -173,20 +182,9 @@ class sinsp_container_engine_docker bool resolve(sinsp_container_manager* manager, sinsp_threadinfo* tinfo, bool query_os_for_missing_info); static void cleanup(); static void set_query_image_info(bool query_image_info); -protected: -#if !defined(CYGWING_AGENT) && defined(HAS_CAPTURE) - static size_t curl_write_callback(const char* ptr, size_t size, size_t nmemb, string* json); -#endif - sinsp_docker_response get_docker(sinsp_container_manager* manager, const string& url, string &json); - bool parse_docker(sinsp_container_manager* manager, sinsp_container_info *container, sinsp_threadinfo* tinfo); - string m_unix_socket_path; - string m_api_version; +protected: static bool m_query_image_info; -#if !defined(CYGWING_AGENT) && defined(HAS_CAPTURE) - static CURLM *m_curlm; - static CURL *m_curl; -#endif }; #ifndef CYGWING_AGENT diff --git a/userspace/libsinsp/event.h b/userspace/libsinsp/event.h index 2916b8c326..1ec8ed49c6 100644 --- a/userspace/libsinsp/event.h +++ b/userspace/libsinsp/event.h @@ -18,6 +18,7 @@ limitations under the License. */ #pragma once +#include "sinsp_public.h" #include #ifndef VISIBILITY_PRIVATE diff --git a/userspace/libsinsp/ifinfo.h b/userspace/libsinsp/ifinfo.h index 1f77e5ae9d..355c5bf3fe 100644 --- a/userspace/libsinsp/ifinfo.h +++ b/userspace/libsinsp/ifinfo.h @@ -93,4 +93,4 @@ void sinsp_network_interfaces::clear() { m_ipv4_interfaces.clear(); m_ipv6_interfaces.clear(); -} \ No newline at end of file +} diff --git a/userspace/libsinsp/logger.h b/userspace/libsinsp/logger.h index e444e53d80..2e86733b3e 100644 --- a/userspace/libsinsp/logger.h +++ b/userspace/libsinsp/logger.h @@ -19,6 +19,8 @@ limitations under the License. #pragma once +#include "sinsp_public.h" + /////////////////////////////////////////////////////////////////////////////// // The logger class /////////////////////////////////////////////////////////////////////////////// @@ -78,7 +80,7 @@ class SINSP_PUBLIC sinsp_logger void set_log_output_type(sinsp_logger::output_type log_output_type); void add_stdout_log(); void add_stderr_log(); - void add_file_log(string filename); + void add_file_log(std::string filename); void add_file_log(FILE* f); void add_callback_log(sinsp_logger_callback callback); void remove_callback_log(); @@ -86,8 +88,8 @@ class SINSP_PUBLIC sinsp_logger void set_severity(severity sev); severity get_severity() const; - void log(string msg, severity sev=SEV_INFO); - void log(string msg, event_severity sev); + void log(std::string msg, severity sev=SEV_INFO); + void log(std::string msg, event_severity sev); // Log functions that accept printf syntax and return the formatted buffer. char* format(severity sev, const char* fmt, ...); @@ -104,6 +106,8 @@ class SINSP_PUBLIC sinsp_logger char m_tbuf[32768]; }; +extern sinsp_logger g_logger; + inline bool sinsp_logger::is_callback() const { return (m_flags & sinsp_logger::OT_CALLBACK) != 0; diff --git a/userspace/libsinsp/sinsp_public.h b/userspace/libsinsp/sinsp_public.h new file mode 100644 index 0000000000..dca73e1dda --- /dev/null +++ b/userspace/libsinsp/sinsp_public.h @@ -0,0 +1,26 @@ +/* +Copyright (C) 2018 Sysdig, Inc. + +This file is part of sysdig. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ + +#pragma once + +#if defined(_WIN32) +# define SINSP_PUBLIC __declspec(dllexport) +#else +# define SINSP_PUBLIC +#endif From 92cf8ff51184e8420753c0361db79e9f1d7ea99b Mon Sep 17 00:00:00 2001 From: Andy Dalton Date: Fri, 4 Jan 2019 13:28:24 -0500 Subject: [PATCH 04/14] Add Windows version of the metadata collector This change adds a concrete implementation of the Docker metadata collector for Windows (as yet untested). I also renamed the new files since I realized that this is collecting metadata and not metrics --- ...ce.ut.cpp => async_metadata_source.ut.cpp} | 88 +++--- ...etric_source.h => async_metadata_source.h} | 102 +++---- userspace/async/async_metadata_source.tpp | 241 ++++++++++++++++ userspace/async/async_metric_source.tpp | 258 ------------------ userspace/libsinsp/CMakeLists.txt | 4 +- ...e.cpp => async_docker_metadata_source.cpp} | 63 +++-- ...ource.h => async_docker_metadata_source.h} | 34 +-- ...=> async_linux_docker_metadata_source.cpp} | 48 +--- ...h => async_linux_docker_metadata_source.h} | 14 +- .../async_windows_docker_metadata_source.cpp | 71 +++++ .../async_windows_docker_metadata_source.h | 47 ++++ userspace/libsinsp/container.cpp | 21 +- userspace/libsinsp/sinsp_int.h | 2 - 13 files changed, 532 insertions(+), 461 deletions(-) rename test/userspace/async/{async_metric_source.ut.cpp => async_metadata_source.ut.cpp} (59%) rename userspace/async/{async_metric_source.h => async_metadata_source.h} (61%) create mode 100644 userspace/async/async_metadata_source.tpp delete mode 100644 userspace/async/async_metric_source.tpp rename userspace/libsinsp/{async_docker_metrics_source.cpp => async_docker_metadata_source.cpp} (84%) rename userspace/libsinsp/{async_docker_metrics_source.h => async_docker_metadata_source.h} (74%) rename userspace/libsinsp/{async_linux_docker_metrics_source.cpp => async_linux_docker_metadata_source.cpp} (72%) rename userspace/libsinsp/{async_linux_docker_metrics_source.h => async_linux_docker_metadata_source.h} (74%) create mode 100644 userspace/libsinsp/async_windows_docker_metadata_source.cpp create mode 100644 userspace/libsinsp/async_windows_docker_metadata_source.h diff --git a/test/userspace/async/async_metric_source.ut.cpp b/test/userspace/async/async_metadata_source.ut.cpp similarity index 59% rename from test/userspace/async/async_metric_source.ut.cpp rename to test/userspace/async/async_metadata_source.ut.cpp index a78b598666..ba13c35fc8 100644 --- a/test/userspace/async/async_metric_source.ut.cpp +++ b/test/userspace/async/async_metadata_source.ut.cpp @@ -16,7 +16,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -#include "async_metric_source.h" +#include "async_metadata_source.h" #include #include @@ -27,14 +27,14 @@ namespace { /** - * Intermediate realization of async_metric_source that can return pre-canned + * Intermediate realization of async_metadata_source that can return pre-canned * results. */ -class precanned_metric_source : public async_metric_source +class precanned_metadata_source : public async_metadata_source { public: - precanned_metric_source(const uint64_t max_wait_ms) - : async_metric_source(max_wait_ms), + precanned_metadata_source(const uint64_t max_wait_ms) + : async_metadata_source(max_wait_ms), m_responses() { } @@ -53,15 +53,15 @@ class precanned_metric_source : public async_metric_source 0) { const std::string key = dequeue_next_key(); - store_metric(key, get_response(key)); + store_metadata(key, get_response(key)); } } }; -const uint64_t immediate_metric_source::MAX_WAIT_TIME_MS = 5000; +const uint64_t immediate_metadata_source::MAX_WAIT_TIME_MS = 5000; /** - * Realization of async_metric_source that returns results with some + * Realization of async_metadata_source that returns results with some * specified delay. */ -class delayed_metric_source : public precanned_metric_source +class delayed_metadata_source : public precanned_metadata_source { public: const static uint64_t MAX_WAIT_TIME_MS; - delayed_metric_source(const uint64_t delay_ms): - precanned_metric_source(MAX_WAIT_TIME_MS), + delayed_metadata_source(const uint64_t delay_ms): + precanned_metadata_source(MAX_WAIT_TIME_MS), m_delay_ms(delay_ms) { } @@ -99,79 +99,79 @@ class delayed_metric_source : public precanned_metric_source std::this_thread::sleep_for(std::chrono::milliseconds(m_delay_ms)); - store_metric(key, get_response(key)); + store_metadata(key, get_response(key)); } } private: uint64_t m_delay_ms; }; -const uint64_t delayed_metric_source::MAX_WAIT_TIME_MS = 0; +const uint64_t delayed_metadata_source::MAX_WAIT_TIME_MS = 0; } /** - * Ensure that a concrete async_metric_source is in the expected initial state + * Ensure that a concrete async_metadata_source is in the expected initial state * after construction. */ -TEST(async_metric_source, construction) +TEST(async_metadata_source, construction) { - immediate_metric_source source; + immediate_metadata_source source; - ASSERT_EQ(immediate_metric_source::MAX_WAIT_TIME_MS, source.get_max_wait()); + ASSERT_EQ(immediate_metadata_source::MAX_WAIT_TIME_MS, source.get_max_wait()); ASSERT_FALSE(source.is_running()); } /** - * Ensure that if a concrete async_metric_source returns the metrics before + * Ensure that if a concrete async_metadata_source returns the metadata before * the timeout, that the lookup() method returns true, and that it returns - * the metrics in the output parameter. + * the metadata in the output parameter. */ -TEST(async_metric_source, lookup_key_immediate_return) +TEST(async_metadata_source, lookup_key_immediate_return) { const std::string key = "foo"; - const std::string metric = "bar"; + const std::string metadata = "bar"; std::string response = "response-not-set"; bool response_found; - immediate_metric_source source; + immediate_metadata_source source; // Seed the precanned response - source.set_response(key, metric); + source.set_response(key, metadata); response_found = source.lookup(key, response); ASSERT_TRUE(response_found); - ASSERT_EQ(metric, response); + ASSERT_EQ(metadata, response); ASSERT_TRUE(source.is_running()); } /** - * Ensure that if a concrete async_metric_source cannot return the result + * Ensure that if a concrete async_metadata_source cannot return the result * before the timeout, and if the client did not provide a callback, that * calling lookup() after the result it available returns the value. */ -TEST(async_metric_source, lookup_key_delayed_return_second_call) +TEST(async_metadata_source, lookup_key_delayed_return_second_call) { const uint64_t DELAY_MS = 50; const std::string key = "mykey"; - const std::string metric = "myvalue"; + const std::string metadata = "myvalue"; - delayed_metric_source source(DELAY_MS); + delayed_metadata_source source(DELAY_MS); std::string response = "response-not-set"; bool response_found; // Seed the precanned response - source.set_response(key, metric); + source.set_response(key, metadata); response_found = source.lookup(key, response); ASSERT_FALSE(response_found); // Since we didn't supply a callback, a subsequent call to lookup - // after the metric collection is complete will return the previously - // collected metric. We know that the delayed_metric_source is + // after the metadata collection is complete will return the previously + // collected metadata. We know that the delayed_metadata_source is // waiting for DELAY_MS, so wait longer than that. std::this_thread::sleep_for(std::chrono::milliseconds(2 * DELAY_MS)); @@ -179,28 +179,28 @@ TEST(async_metric_source, lookup_key_delayed_return_second_call) response_found = source.lookup(key, response); ASSERT_TRUE(response_found); - ASSERT_EQ(metric, response); + ASSERT_EQ(metadata, response); } /** - * Ensure that if a concrete async_metric_source cannot return the result + * Ensure that if a concrete async_metadata_source cannot return the result * before the timeout, and if the client did provide a callback, that the - * callback is invoked with the metrics once they're avaialble. + * callback is invoked with the metadata once they're avaialble. */ -TEST(async_metric_source, look_key_delayed_async_callback) +TEST(async_metadata_source, look_key_delayed_async_callback) { const uint64_t DELAY_MS = 50; const std::string key = "mykey"; - const std::string metric = "myvalue"; + const std::string metadata = "myvalue"; - delayed_metric_source source(DELAY_MS); + delayed_metadata_source source(DELAY_MS); std::string sync_response = "sync-response-not-set"; std::string async_response = "async-response-not-set"; bool response_found; // Seed the precanned response - source.set_response(key, metric); + source.set_response(key, metadata); response_found = source.lookup(key, sync_response, @@ -212,10 +212,10 @@ TEST(async_metric_source, look_key_delayed_async_callback) ASSERT_FALSE(response_found); - // Since we supplied a callback, the delayed_metric_source should + // Since we supplied a callback, the delayed_metadata_source should // complete after DELAY_MS, and it should immediately call our // callback. Wait long enough for that to happen. std::this_thread::sleep_for(std::chrono::milliseconds(5 * DELAY_MS)); - ASSERT_EQ(metric, async_response); + ASSERT_EQ(metadata, async_response); } diff --git a/userspace/async/async_metric_source.h b/userspace/async/async_metadata_source.h similarity index 61% rename from userspace/async/async_metric_source.h rename to userspace/async/async_metadata_source.h index a1dbaeff4c..83ed55c74b 100644 --- a/userspace/async/async_metric_source.h +++ b/userspace/async/async_metadata_source.h @@ -30,37 +30,37 @@ namespace sysdig { /** - * Base class for classes that need to collect metrics asynchronously from some - * metric source. Subclasses will override the the run_impl() method. In + * Base class for classes that need to collect metadata asynchronously from some + * metadata source. Subclasses will override the the run_impl() method. In * that method, subclasses will use use dequeue_next_key() method to get the - * key that it will use to collect the metrics, collect the appropriate metrics, - * and call the store_metrics() method to save the metrics. The run_impl() - * method should continue to dequeue and process metrics while the queue_size() - * method returns non-zero. + * key that it will use to collect the metadata, collect the appropriate + * metadata, and call the store_metadata() method to save the metadata. The + * run_impl() method should continue to dequeue and process metadata while the + * queue_size() method returns non-zero. * * The constructor for this class accepts a maximum wait time; this specifies * how long client code is willing to wait for a synchronous response (i.e., - * how long the lookup() method will block waiting for the requested metrics). - * If the async_metric_source is able to collect the requested metrics within + * how long the lookup() method will block waiting for the requested metadata). + * If the async_metadata_source is able to collect the requested metadata within * that time period, then the lookup() method will return them. * - * If the lookup() method is unable to collect the requested metrics within + * If the lookup() method is unable to collect the requested metadata within * the requested time period, then one of two things will happen. (1) If * the client supplied a handler in the call to lookup(), then that handler - * will be invoked by the async_metric_source once the metric has been + * will be invoked by the async_metadata_source once the metadata has been * collected. Note that the callback handler will be invoked in the context - * of the asynchronous thread associated with the async_metric_source. (2) If - * the client did not supply a handler, then the metric will be stored, and the + * of the asynchronous thread associated with the async_metadata_source. (2) If + * the client did not supply a handler, then the metadata will be stored, and the * next call to the lookup() method with the same key will return the previously - * collected metrics. + * collected metadata. * * @tparam key_type The type of the keys for which concrete subclasses will * query. - * @tparam metric_type The type of metric that concrete subclasses will receive + * @tparam metadata_type The type of metadata that concrete subclasses will receive * from a query. */ -template -class async_metric_source +template +class async_metadata_source { public: /** @@ -70,65 +70,65 @@ class async_metric_source const static uint64_t NO_LOOKUP_WAIT = 0; typedef std::function callback_handler; + const metadata_type& metadata)> callback_handler; /** - * Initialize this new async_metric_source, which will block - * synchronously for the given max_wait_ms for metric collection. + * Initialize this new async_metadata_source, which will block + * synchronously for the given max_wait_ms for metadata collection. * * @param[in] max_wait_ms The maximum amount of time that client code * is willing to wait for lookup() to collect - * metrics before falling back to an async + * metadata before falling back to an async * return. */ - async_metric_source(uint64_t max_wait_ms); + async_metadata_source(uint64_t max_wait_ms); - async_metric_source(const async_metric_source&) = delete; - async_metric_source(async_metric_source&&) = delete; - async_metric_source& operator=(const async_metric_source&) = delete; + async_metadata_source(const async_metadata_source&) = delete; + async_metadata_source(async_metadata_source&&) = delete; + async_metadata_source& operator=(const async_metadata_source&) = delete; - virtual ~async_metric_source(); + virtual ~async_metadata_source(); uint64_t get_max_wait() const; /** - * Lookup metrics based on the given key. This method will block + * Lookup metadata based on the given key. This method will block * the caller for up the max_wait_ms time specified at construction - * for the desired metrics to be available. + * for the desired metadata to be available. * - * @param[in] key The key to the metric for which the client wishes + * @param[in] key The key to the metadata for which the client wishes * to query. - * @param[out] metric If this method is able to fetch the desired - * metrics within the max_wait_ms specified at + * @param[out] metadata If this method is able to fetch the desired + * metadata within the max_wait_ms specified at * construction time, then this output parameter will - * contain the collected metrics. The value of this + * contain the collected metadata. The value of this * parameter is defined only if this method returns * true. * @param[in] handler If this method is unable to collect the requested - * metrics before the timeout, and if this parameter + * metadata before the timeout, and if this parameter * is a valid, non-empty, function, then this class * will invoke the given handler from the async - * thread immediately after the collected metrics + * thread immediately after the collected metadata * are available. If this handler is empty, then - * this async_metric_source will store the metrics + * this async_metadata_source will store the metadata * and return them on the next call to lookup(). * * @returns true if this method was able to lookup and return the - * metric synchronously; false otherwise. + * metadata synchronously; false otherwise. */ bool lookup(const key_type& key, - metric_type& metric, + metadata_type& metadata, const callback_handler& handler = callback_handler()); /** * @returns true if the async thread assocaited with this - * async_metric_source is running, false otherwise. + * async_metadata_source is running, false otherwise. */ bool is_running() const; protected: /** - * Stops the thread assocaited with this async_metric_source, if + * Stops the thread assocaited with this async_metadata_source, if * it is running. */ void stop(); @@ -145,7 +145,7 @@ class async_metric_source /** * Dequeues an entry from the request queue and returns it. Concrete * subclasses will call this method to get the next key for which - * to collect metrics. + * to collect metadata. * * Precondition: queue_size() must be non-zero. * @@ -153,21 +153,21 @@ class async_metric_source */ key_type dequeue_next_key(); - metric_type get_metrics(const key_type& key); + metadata_type get_metadata(const key_type& key); /** - * Stores a collected set of metrics for the given key. Concrete + * Stores a collected set of metadata for the given key. Concrete * subclasses will call this method from their run_impl() method to - * save (or otherwise notifiy the client about) a collected metric. + * save (or otherwise notifiy the client about) a collected metadata. * - * @param[in] key The key for which the client asked for metrics. - * @param[in] metrics The collected metrics. + * @param[in] key The key for which the client asked for metadata. + * @param[in] metadata The collected metadata. */ - void store_metric(const key_type& key, const metric_type& metric); + void store_metadata(const key_type& key, const metadata_type& metadata); /** * Concrete subclasses must override this method to perform the - * asynchronous metric lookup. + * asynchronous metadata lookup. */ virtual void run_impl() = 0; @@ -176,18 +176,18 @@ class async_metric_source { lookup_request(): m_available(false), - m_metric(), + m_metadata(), m_available_condition(), m_callback() {} bool m_available; - metric_type m_metric; + metadata_type m_metadata; std::condition_variable m_available_condition; callback_handler m_callback; // TODO: This may need to be a list }; - typedef std::map metric_map; + typedef std::map metadata_map; void run(); @@ -199,10 +199,10 @@ class async_metric_source std::condition_variable m_start_condition; std::condition_variable m_queue_not_empty_condition; std::list m_request_queue; - metric_map m_metric_map; + metadata_map m_metadata_map; }; } // end namespace sysdig -#include "async_metric_source.tpp" +#include "async_metadata_source.tpp" diff --git a/userspace/async/async_metadata_source.tpp b/userspace/async/async_metadata_source.tpp new file mode 100644 index 0000000000..2d4bf04800 --- /dev/null +++ b/userspace/async/async_metadata_source.tpp @@ -0,0 +1,241 @@ +/* +Copyright (C) 2018 Sysdig, Inc. + +This file is part of sysdig. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ +#include "logger.h" + +#include +#include +#include +#include +#include +#include + +namespace sysdig +{ + +template +async_metadata_source::async_metadata_source( + const uint64_t max_wait_ms): + m_max_wait_ms(max_wait_ms), + m_thread(), + m_running(false), + m_terminate(false), + m_mutex(), + m_queue_not_empty_condition(), + m_metadata_map() +{ } + +template +async_metadata_source::~async_metadata_source() +{ + try + { + stop(); + } + catch(...) + { + g_logger.log(std::string(__FUNCTION__) + + ": Exception in destructor", + sinsp_logger::SEV_ERROR); + } +} + +template +uint64_t async_metadata_source::get_max_wait() const +{ + return m_max_wait_ms; +} + +template +void async_metadata_source::stop() +{ + bool join_needed = false; + + { + std::unique_lock guard(m_mutex); + + if(m_running) + { + m_terminate = true; + join_needed = true; + + // The async thread might be waiting for new events + // so wake it up + m_queue_not_empty_condition.notify_one(); + } + } // Drop the mutex before join() + + if (join_needed) + { + m_thread.join(); + + // Remove any pointers from the thread to this object + // (just to be safe) + m_thread = std::thread(); + } +} + +template +bool async_metadata_source::is_running() const +{ + std::lock_guard guard(m_mutex); + + return m_running; +} + +template +void async_metadata_source::run() +{ + m_running = true; + + while(!m_terminate) + { + { + std::unique_lock guard(m_mutex); + + while(!m_terminate && m_request_queue.empty()) + { + // Wait for something to show up on the queue + m_queue_not_empty_condition.wait(guard); + } + } + + if(!m_terminate) + { + run_impl(); + } + } + + m_running = false; +} + +template +bool async_metadata_source::lookup( + const key_type& key, + metadata_type& metadata, + const callback_handler& callback) +{ + std::unique_lock guard(m_mutex); + + if(!m_running) + { + m_thread = std::thread(&async_metadata_source::run, this); + } + + typename metadata_map::const_iterator itr = m_metadata_map.find(key); + bool request_complete = (itr != m_metadata_map.end()) && itr->second.m_available; + + if(!request_complete) + { + // Haven't made the request yet + if (itr == m_metadata_map.end()) + { + m_metadata_map[key].m_available = false; + m_metadata_map[key].m_metadata = metadata; + } + + // Make request to API and let the async thread know about it + if (std::find(m_request_queue.begin(), + m_request_queue.end(), + key) == m_request_queue.end()) + { + m_request_queue.push_back(key); + m_queue_not_empty_condition.notify_one(); + } + + // + // If the client code is willing to wait a short amount of time + // to satisfy the request, then wait for the async thread to + // pick up the newly-added request and execute it. If + // processing that request takes too much time, then we'll + // not be able to return the metadata information on this call, + // and the async thread will continue handling the request so + // that it'll be available on the next call. + // + if (m_max_wait_ms > 0) + { + m_metadata_map[key].m_available_condition.wait_for( + guard, + std::chrono::milliseconds(m_max_wait_ms)); + + itr = m_metadata_map.find(key); + request_complete = (itr != m_metadata_map.end()) && itr->second.m_available; + } + } + + if(request_complete) + { + metadata = itr->second.m_metadata; + m_metadata_map.erase(key); + } + else + { + m_metadata_map[key].m_callback = callback; + } + + return request_complete; +} + +template +std::size_t async_metadata_source::queue_size() const +{ + std::lock_guard guard(m_mutex); + return m_request_queue.size(); +} + +template +key_type async_metadata_source::dequeue_next_key() +{ + std::lock_guard guard(m_mutex); + key_type key = m_request_queue.front(); + + m_request_queue.pop_front(); + + return key; +} + +template +metadata_type async_metadata_source::get_metadata( + const key_type& key) +{ + std::lock_guard guard(m_mutex); + + return m_metadata_map[key].m_metadata; +} + +template +void async_metadata_source::store_metadata( + const key_type& key, + const metadata_type& metadata) +{ + std::lock_guard guard(m_mutex); + + if (m_metadata_map[key].m_callback) + { + m_metadata_map[key].m_callback(key, metadata); + m_metadata_map.erase(key); + } + else + { + m_metadata_map[key].m_metadata = metadata; + m_metadata_map[key].m_available = true; + m_metadata_map[key].m_available_condition.notify_one(); + } +} + +} // end namespace sysdig diff --git a/userspace/async/async_metric_source.tpp b/userspace/async/async_metric_source.tpp deleted file mode 100644 index 04a19108b9..0000000000 --- a/userspace/async/async_metric_source.tpp +++ /dev/null @@ -1,258 +0,0 @@ -/* -Copyright (C) 2018 Sysdig, Inc. - -This file is part of sysdig. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - -*/ -#include "logger.h" - -#include -#include -#include -#include -#include -#include - -namespace sysdig -{ - -template -async_metric_source::async_metric_source( - const uint64_t max_wait_ms): - m_max_wait_ms(max_wait_ms), - m_thread(), - m_running(false), - m_terminate(false), - m_mutex(), - m_queue_not_empty_condition(), - m_metric_map() -{ } - -template -async_metric_source::~async_metric_source() -{ - g_logger.log("async_metric_source destructor"); - try - { - stop(); - } - catch(...) - { - g_logger.log(std::string(__FUNCTION__) + ": Exception in destructor", sinsp_logger::SEV_ERROR); - } -} - -template -uint64_t async_metric_source::get_max_wait() const -{ - return m_max_wait_ms; -} - -template -void async_metric_source::stop() -{ - g_logger.log("ENTRY: sync_metric_source::stop"); - bool join_needed = false; - - { - std::unique_lock guard(m_mutex); - - if(m_running) - { - m_terminate = true; - join_needed = true; - - // The async thread might be waiting for new events - // so wake it up - m_queue_not_empty_condition.notify_one(); - } - } // Drop the mutex before join() - - if (join_needed) - { - m_thread.join(); - - // Remove any pointers from the thread to this object - // (just to be safe) - m_thread = std::thread(); - } - g_logger.log("EXIT: sync_metric_source::stop"); -} - -template -bool async_metric_source::is_running() const -{ - std::lock_guard guard(m_mutex); - - return m_running; -} - -template -void async_metric_source::run() -{ - g_logger.log("ENTRY: sync_metric_source::run"); - m_running = true; - - while(!m_terminate) - { - { - std::unique_lock guard(m_mutex); - - while(!m_terminate && m_request_queue.empty()) - { - g_logger.log("sync_metric_source::run: Waiting for queue item"); - // Wait for something to show up on the queue - m_queue_not_empty_condition.wait(guard); - } - } - - if(!m_terminate) - { - g_logger.log("sync_metric_source::run: Invoking run_impl"); - run_impl(); - } - } - - m_running = false; - g_logger.log("EXIT: sync_metric_source::run"); -} - -template -bool async_metric_source::lookup( - const key_type& key, - metric_type& metric, - const callback_handler& callback) -{ - g_logger.log("ENTRY: sync_metric_source::lookup: key:" + key); - std::unique_lock guard(m_mutex); - - if(!m_running) - { - g_logger.log("sync_metric_source::lookup: starting thread"); - m_thread = std::thread(&async_metric_source::run, this); - } - - typename metric_map::const_iterator itr = m_metric_map.find(key); - bool request_complete = (itr != m_metric_map.end()) && itr->second.m_available; - - if(!request_complete) - { - g_logger.log("sync_metric_source::lookup: metrics for key not yet available"); - // Haven't made the request yet - if (itr == m_metric_map.end()) - { - g_logger.log("sync_metric_source::lookup: first request for metrics"); - m_metric_map[key].m_available = false; - m_metric_map[key].m_metric = metric; - } - - // Make request to API and let the async thread know about it - if (std::find(m_request_queue.begin(), - m_request_queue.end(), - key) == m_request_queue.end()) - { - g_logger.log("sync_metric_source::lookup: adding work to queue"); - m_request_queue.push_back(key); - m_queue_not_empty_condition.notify_one(); - } - - // - // If the client code is willing to wait a short amount of time - // to satisfy the request, then wait for the async thread to - // pick up the newly-added request and execute it. If - // processing that request takes too much time, then we'll - // not be able to return the metric information on this call, - // and the async thread will continue handling the request so - // that it'll be available on the next call. - // - if (m_max_wait_ms > 0) - { - m_metric_map[key].m_available_condition.wait_for( - guard, - std::chrono::milliseconds(m_max_wait_ms)); - - itr = m_metric_map.find(key); - request_complete = (itr != m_metric_map.end()) && itr->second.m_available; - } - } - - g_logger.log("sync_metric_source::lookup: request_complete: " + std::to_string(request_complete)); - if(request_complete) - { - metric = itr->second.m_metric; - m_metric_map.erase(key); - } - else - { - g_logger.log("sync_metric_source::lookup: saving callback"); - m_metric_map[key].m_callback = callback; - } - - return request_complete; -} - -template -std::size_t async_metric_source::queue_size() const -{ - std::lock_guard guard(m_mutex); - return m_request_queue.size(); -} - -template -key_type async_metric_source::dequeue_next_key() -{ - g_logger.log("ENTRY: sync_metric_source::dequeue_next_key"); - std::lock_guard guard(m_mutex); - key_type key = m_request_queue.front(); - - m_request_queue.pop_front(); - - g_logger.log("EXIT: sync_metric_source::dequeue_next_key"); - return key; -} - -template -metric_type async_metric_source::get_metrics(const key_type& key) -{ - std::lock_guard guard(m_mutex); - - return m_metric_map[key].m_metric; -} - -template -void async_metric_source::store_metric( - const key_type& key, - const metric_type& metric) -{ - g_logger.log("ENTRY: sync_metric_source::store_metric"); - std::lock_guard guard(m_mutex); - - if (m_metric_map[key].m_callback) - { - g_logger.log("sync_metric_source::store_metric: Invoking callback"); - m_metric_map[key].m_callback(key, metric); - m_metric_map.erase(key); - } - else - { - g_logger.log("sync_metric_source::store_metric: Saving metrics for later"); - m_metric_map[key].m_metric = metric; - m_metric_map[key].m_available = true; - m_metric_map[key].m_available_condition.notify_one(); - } - g_logger.log("EXIT: sync_metric_source::store_metric"); -} - -} // end namespace sysdig diff --git a/userspace/libsinsp/CMakeLists.txt b/userspace/libsinsp/CMakeLists.txt index e3081a57d5..3ad33a8925 100644 --- a/userspace/libsinsp/CMakeLists.txt +++ b/userspace/libsinsp/CMakeLists.txt @@ -32,8 +32,8 @@ if(NOT WIN32 AND NOT APPLE) endif() add_library(sinsp STATIC - async_docker_metrics_source.cpp - async_linux_docker_metrics_source.cpp + async_docker_metadata_source.cpp + async_linux_docker_metadata_source.cpp chisel.cpp chisel_api.cpp container.cpp diff --git a/userspace/libsinsp/async_docker_metrics_source.cpp b/userspace/libsinsp/async_docker_metadata_source.cpp similarity index 84% rename from userspace/libsinsp/async_docker_metrics_source.cpp rename to userspace/libsinsp/async_docker_metadata_source.cpp index 6e6e8a18c5..1fd70ea656 100644 --- a/userspace/libsinsp/async_docker_metrics_source.cpp +++ b/userspace/libsinsp/async_docker_metadata_source.cpp @@ -16,69 +16,75 @@ See the License for the specific language governing permissions and limitations under the License. */ -#include "async_docker_metrics_source.h" -#include "async_linux_docker_metrics_source.h" +#include "async_docker_metadata_source.h" + +#if defined(CYGWING_AGENT) +# include "async_windows_docker_metadata_source.h" +#else +# include "async_linux_docker_metadata_source.h" +#endif + #include "sinsp_int.h" #include "logger.h" using namespace sysdig; -const uint16_t async_docker_metrics_source::DEFAULT_PORT = 80; +const uint16_t async_docker_metadata_source::DEFAULT_PORT = 80; -async_docker_metrics_source::async_docker_metrics_source(const std::string& api_version, +async_docker_metadata_source::async_docker_metadata_source(const std::string& api_version, const uint16_t port): - async_metric_source(NO_LOOKUP_WAIT), + async_metadata_source(NO_LOOKUP_WAIT), m_query_image_info(true), m_api_version(api_version), m_port(port) { } -const std::string& async_docker_metrics_source::get_api_version() const +const std::string& async_docker_metadata_source::get_api_version() const { return m_api_version; } -uint16_t async_docker_metrics_source::get_port() const +uint16_t async_docker_metadata_source::get_port() const { return m_port; } -bool async_docker_metrics_source::query_image_info() const +bool async_docker_metadata_source::query_image_info() const { return m_query_image_info; } -void async_docker_metrics_source::set_query_image_info(const bool query_info) +void async_docker_metadata_source::set_query_image_info(const bool query_info) { m_query_image_info = query_info; } -void async_docker_metrics_source::run_impl() +void async_docker_metadata_source::run_impl() { while(queue_size() > 0) { const std::string container_id = dequeue_next_key(); - docker_metrics metrics = get_metrics(container_id); + docker_metadata metadata = get_metadata(container_id); - if(metrics.m_manager != nullptr) + if(metadata.m_manager != nullptr) { - if(parse_docker(metrics.m_manager, - metrics.m_container_info.get())) + if(parse_docker(metadata.m_manager, + metadata.m_container_info.get())) { - store_metric(container_id, metrics); + store_metadata(container_id, metadata); } } else { g_logger.log("Unexpected null manager", sinsp_logger::SEV_ERROR); - ASSERT(metrics.m_manager != nullptr); + ASSERT(metadata.m_manager != nullptr); } } } -bool async_docker_metrics_source::parse_docker(sinsp_container_manager* const manager, +bool async_docker_metadata_source::parse_docker(sinsp_container_manager* const manager, sinsp_container_info* const container) { std::string json; @@ -341,21 +347,20 @@ bool async_docker_metrics_source::parse_docker(sinsp_container_manager* const ma return true; } -async_docker_metrics_source* async_docker_metrics_source::new_async_docker_metrics_source() +async_docker_metadata_source* async_docker_metadata_source::new_async_docker_metadata_source() { - async_docker_metrics_source* docker_metrics = nullptr; + async_docker_metadata_source* docker_metadata = nullptr; #if defined(CYGWING_AGENT) - // TODO: Need to implement async_windows_docker_metrics - // docker_metrics = new async_windows_docker_metrics(); -#else // CYGWING_AGENT -# if defined(HAS_ANALYZER) - docker_metrics = new async_linux_docker_metrics_source(); -# else // HAS_ANALYZER - // TODO: Need to implement async_null_docker_metrics_source - // docker_metrics = new async_null_docker_metrics_source(); -# endif //HAS_ANALYZER + docker_metadata = new async_windows_docker_metadata_source(); +#else // !CYGWING_AGENT +# if defined(HAS_CAPTURE) + docker_metadata = new async_linux_docker_metadata_source(); +# else // !HAS_CAPTURE + // TODO: Need to implement async_null_docker_metadata_source + // docker_metadata = new async_null_docker_metadata_source(); +# endif //HAS_CAPTURE #endif // CYGWING_AGENT - return docker_metrics; + return docker_metadata; } diff --git a/userspace/libsinsp/async_docker_metrics_source.h b/userspace/libsinsp/async_docker_metadata_source.h similarity index 74% rename from userspace/libsinsp/async_docker_metrics_source.h rename to userspace/libsinsp/async_docker_metadata_source.h index ebb0f76c30..d6752d45be 100644 --- a/userspace/libsinsp/async_docker_metrics_source.h +++ b/userspace/libsinsp/async_docker_metadata_source.h @@ -18,7 +18,7 @@ limitations under the License. */ #pragma once -#include "async_metric_source.h" +#include "async_metadata_source.h" #include "sinsp.h" #include "container.h" @@ -26,15 +26,15 @@ namespace sysdig { // TODO: Can this be inside the class below? -struct docker_metrics +struct docker_metadata { - docker_metrics(): + docker_metadata(): m_manager(nullptr), m_container_info() { } - docker_metrics(sinsp_container_manager* const manager, + docker_metadata(sinsp_container_manager* const manager, std::shared_ptr& container_info): m_manager(manager), m_container_info(container_info) @@ -47,49 +47,49 @@ struct docker_metrics }; /** - * Interface to async_docker_metrics_source -- an abstract async_metric_source - * for fetching docker metrics and metadata. + * Interface to async_docker_metadata_source -- an abstract async_metadata_source + * for fetching docker metadata and metadata. */ -class async_docker_metrics_source : public async_metric_source +class async_docker_metadata_source : public async_metadata_source { public: /** The default port on which Docker listens for REST requests. */ static const uint16_t DEFAULT_PORT; /** - * Returns the API version that this async_metric_source will use to + * Returns the API version that this async_metadata_source will use to * fetch information from Docker. */ const std::string& get_api_version() const; /** - * Returns the port that this async_docker_metrics_source will use + * Returns the port that this async_docker_metadata_source will use * to connect to Docker. */ uint16_t get_port() const; /** - * Returns true if this async_docker_metrics_source should query for + * Returns true if this async_docker_metadata_source should query for * image info, false otherwise. */ bool query_image_info() const; /** - * Update the query_image_info state for this async_docker_metrics_source. + * Update the query_image_info state for this async_docker_metadata_source. */ void set_query_image_info(bool query_info); /** - * Creates a new async_docker_metric_source that is appropriate + * Creates a new async_docker_metadata_source that is appropriate * for the build environment (Linux/Windows/no-analyzer) * * Note that the caller is responsible for deleting the returned object. */ - static async_docker_metrics_source* new_async_docker_metrics_source(); + static async_docker_metadata_source* new_async_docker_metadata_source(); protected: - async_docker_metrics_source(const std::string& api_version, + async_docker_metadata_source(const std::string& api_version, uint16_t port = DEFAULT_PORT); /** @@ -97,9 +97,9 @@ class async_docker_metrics_source : public async_metric_sourceget_inspector()->get_wmi_handle(), -// (char*)url.c_str(), -// &response); -// if(qdres == false) -// { -// ASSERT(false); -// return sinsp_docker_response::RESP_ERROR; -// } -// -// json = response; -// if(strncmp(json.c_str(), "HTTP/1.0 200 OK", sizeof("HTTP/1.0 200 OK") -1)) -// { -// return sinsp_docker_response::RESP_BAD_REQUEST; -// } -// -// size_t pos = json.find("{"); -// if(pos == std::string::npos) -// { -// ASSERT(false); -// return sinsp_docker_response::RESP_ERROR; -// } -// json = json.substr(pos); -// -// return sinsp_docker_response::RESP_OK; -// } - } // end namespace -const std::string async_linux_docker_metrics_source::DEFAULT_API_VERSION = "/v1.24"; +const std::string async_linux_docker_metadata_source::DEFAULT_API_VERSION = "/v1.24"; -async_linux_docker_metrics_source::async_linux_docker_metrics_source( +async_linux_docker_metadata_source::async_linux_docker_metadata_source( const std::string& api_version, const uint16_t port): - async_docker_metrics_source(api_version, port) + async_docker_metadata_source(api_version, port) , m_unix_socket_path(scap_get_host_root() + s_docker_socket_path) #if defined(HAS_CAPTURE) , m_curl(curl_easy_init()) @@ -109,7 +77,7 @@ async_linux_docker_metrics_source::async_linux_docker_metrics_source( #endif } -async_linux_docker_metrics_source::~async_linux_docker_metrics_source() +async_linux_docker_metadata_source::~async_linux_docker_metadata_source() { #if defined(HAS_CAPTURE) curl_easy_cleanup(m_curl); @@ -117,12 +85,12 @@ async_linux_docker_metrics_source::~async_linux_docker_metrics_source() #endif } -std::string async_linux_docker_metrics_source::build_request(const std::string& url) +std::string async_linux_docker_metadata_source::build_request(const std::string& path) { - return "http://localhost" + get_api_version() + url; + return "http://localhost" + get_api_version() + path; } -sinsp_docker_response async_linux_docker_metrics_source::get_docker( +sinsp_docker_response async_linux_docker_metadata_source::get_docker( sinsp_container_manager* const, const std::string& url, std::string &json) diff --git a/userspace/libsinsp/async_linux_docker_metrics_source.h b/userspace/libsinsp/async_linux_docker_metadata_source.h similarity index 74% rename from userspace/libsinsp/async_linux_docker_metrics_source.h rename to userspace/libsinsp/async_linux_docker_metadata_source.h index 04a8afce3b..cdec92f8be 100644 --- a/userspace/libsinsp/async_linux_docker_metrics_source.h +++ b/userspace/libsinsp/async_linux_docker_metadata_source.h @@ -18,29 +18,29 @@ limitations under the License. */ #pragma once -#include "async_docker_metrics_source.h" +#include "async_docker_metadata_source.h" #include namespace sysdig { /** - * Interface to async_linux_docker_metrics_source -- a concrete - * async_docker_metrics_source for fetching docker metrics and metadata + * Interface to async_linux_docker_metadata_source -- a concrete + * async_docker_metadata_source for fetching docker metadata and metadata * on Linux. */ -class async_linux_docker_metrics_source : public async_docker_metrics_source +class async_linux_docker_metadata_source : public async_docker_metadata_source { public: const static std::string DEFAULT_API_VERSION; - async_linux_docker_metrics_source( + async_linux_docker_metadata_source( const std::string& api_version = DEFAULT_API_VERSION, uint16_t port = DEFAULT_PORT); - ~async_linux_docker_metrics_source(); + ~async_linux_docker_metadata_source(); protected: - std::string build_request(const std::string& url) override; + std::string build_request(const std::string& path) override; sinsp_docker_response get_docker(sinsp_container_manager* manager, const std::string& url, std::string &json) override; diff --git a/userspace/libsinsp/async_windows_docker_metadata_source.cpp b/userspace/libsinsp/async_windows_docker_metadata_source.cpp new file mode 100644 index 0000000000..bc107bb457 --- /dev/null +++ b/userspace/libsinsp/async_windows_docker_metadata_source.cpp @@ -0,0 +1,71 @@ +/* +Copyright (C) 2018 Sysdig, Inc. + +This file is part of sysdig. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ +#include "async_windows_docker_metadata_source.h" +#include "sinsp_int.h" +#include "logger.h" + +using namespace sysdig; + +const std::string async_windows_docker_metadata_source::DEFAULT_API_VERSION = "/v1.30"; + +async_windows_docker_metadata_source::async_windows_docker_metadata_source( + const std::string& api_version, + const uint16_t port): + async_docker_metadata_source(api_version, port) +{ +} + +std::string async_windows_docker_metadata_source::build_request(const std::string& path) +{ + return "GET " + get_api_version() + path + " HTTP/1.1\r\nHost: docker\r\n\r\n"; +} + +sinsp_docker_response async_windows_docker_metadata_source::get_docker( + sinsp_container_manager* const manager, + const std::string& url, + std::string &json) +{ + const char* response = nullptr; + + const bool qdres = wh_query_docker(manager->get_inspector()->get_wmi_handle(), + const_cast(url.c_str()), + &response); + + if(!qdres) + { + ASSERT(false); + return sinsp_docker_response::RESP_ERROR; + } + + json = response; + if(strncmp(json.c_str(), "HTTP/1.0 200 OK", sizeof("HTTP/1.0 200 OK") - 1)) + { + return sinsp_docker_response::RESP_BAD_REQUEST; + } + + size_t pos = json.find("{"); + if(pos == std::string::npos) + { + ASSERT(false); + return sinsp_docker_response::RESP_ERROR; + } + json = json.substr(pos); + + return sinsp_docker_response::RESP_OK; +} diff --git a/userspace/libsinsp/async_windows_docker_metadata_source.h b/userspace/libsinsp/async_windows_docker_metadata_source.h new file mode 100644 index 0000000000..688a2d829d --- /dev/null +++ b/userspace/libsinsp/async_windows_docker_metadata_source.h @@ -0,0 +1,47 @@ +/* +Copyright (C) 2018 Sysdig, Inc. + +This file is part of sysdig. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ +#pragma once + +#include "async_docker_metadata_source.h" + +namespace sysdig +{ + +/** + * Interface to async_windows_docker_metadata_source -- a concrete + * async_docker_metadata_source for fetching docker metadata and metadata + * on Windows. + */ +class async_windows_docker_metadata_source : public async_docker_metadata_source +{ +public: + const static std::string DEFAULT_API_VERSION; + + async_windows_docker_metadata_source( + const std::string& api_version = DEFAULT_API_VERSION, + uint16_t port = DEFAULT_PORT); + +protected: + std::string build_request(const std::string& path) override; + sinsp_docker_response get_docker(sinsp_container_manager* manager, + const std::string& url, + std::string &json) override; +}; + +} diff --git a/userspace/libsinsp/container.cpp b/userspace/libsinsp/container.cpp index 8f3443681a..3475936d58 100644 --- a/userspace/libsinsp/container.cpp +++ b/userspace/libsinsp/container.cpp @@ -23,7 +23,7 @@ limitations under the License. #include #endif -#include "async_docker_metrics_source.h" +#include "async_docker_metadata_source.h" #include "sinsp.h" #include "sinsp_int.h" #include "container.h" @@ -184,23 +184,24 @@ std::string sinsp_container_info::to_string() const return out.str(); } -std::unique_ptr s_docker_metrics; +std::unique_ptr s_docker_metadata; bool sinsp_container_engine_docker::m_query_image_info = true; sinsp_container_engine_docker::sinsp_container_engine_docker() { - if(!s_docker_metrics) + if(!s_docker_metadata) { - s_docker_metrics.reset(async_docker_metrics_source::new_async_docker_metrics_source()); + s_docker_metadata.reset( + async_docker_metadata_source::new_async_docker_metadata_source()); } } void sinsp_container_engine_docker::cleanup() { - s_docker_metrics.reset(); + s_docker_metadata.reset(); } void sinsp_container_engine_docker::set_query_image_info(bool query_image_info) @@ -292,17 +293,15 @@ bool sinsp_container_engine_docker::resolve(sinsp_container_manager* manager, si g_logger.log("resolve: query_os_for_missing_info: " + std::to_string(query_os_for_missing_info)); if (query_os_for_missing_info) { - docker_metrics metrics(manager, container_info); + docker_metadata metadata(manager, container_info); // TODO: This will need to eventually change when we // want to report partial information to the // backend. - if(s_docker_metrics->lookup(tinfo->m_container_id, metrics)) + if(s_docker_metadata->lookup(tinfo->m_container_id, metadata)) { - g_logger.log("resolve: metric lookup successful, metrics: " + metrics.m_container_info->to_string()); - - manager->add_container(*metrics.m_container_info, tinfo); - manager->notify_new_container(*metrics.m_container_info); + manager->add_container(*metadata.m_container_info, tinfo); + manager->notify_new_container(*metadata.m_container_info); return true; } diff --git a/userspace/libsinsp/sinsp_int.h b/userspace/libsinsp/sinsp_int.h index e0e2d18f6a..bfb1a6d761 100644 --- a/userspace/libsinsp/sinsp_int.h +++ b/userspace/libsinsp/sinsp_int.h @@ -80,10 +80,8 @@ using namespace std; // Public export macro // #ifdef _WIN32 -#define SINSP_PUBLIC __declspec(dllexport) #define BRK(X) {if(evt != NULL && evt->get_num() == X)__debugbreak();} #else -#define SINSP_PUBLIC #define BRK(X) #endif From 8c27d4ed3839f044dcda016db3244a73a0ca609f Mon Sep 17 00:00:00 2001 From: Andy Dalton Date: Wed, 9 Jan 2019 15:09:33 -0500 Subject: [PATCH 05/14] Tweaks to make core more UT-able In the earlier versions of the Linux implementation, I overlooked the fact that we were using UNIX domain sockets and not TCP. I updated it to parameterize the path to the socket, and I removed the bits related to the TCP port. This change also adds UT for that. --- .../async/async_metadata_source.ut.cpp | 13 +- .../async_linux_docker_metadata_source.ut.cpp | 268 ++++++++++++++++++ .../libsinsp/async_docker_metadata_source.cpp | 42 ++- .../libsinsp/async_docker_metadata_source.h | 19 +- .../async_linux_docker_metadata_source.cpp | 37 ++- .../async_linux_docker_metadata_source.h | 7 +- .../async_windows_docker_metadata_source.cpp | 8 +- .../async_windows_docker_metadata_source.h | 3 +- 8 files changed, 327 insertions(+), 70 deletions(-) create mode 100644 test/userspace/libsinsp/async_linux_docker_metadata_source.ut.cpp diff --git a/test/userspace/async/async_metadata_source.ut.cpp b/test/userspace/async/async_metadata_source.ut.cpp index ba13c35fc8..2e6ec97a1b 100644 --- a/test/userspace/async/async_metadata_source.ut.cpp +++ b/test/userspace/async/async_metadata_source.ut.cpp @@ -114,7 +114,7 @@ const uint64_t delayed_metadata_source::MAX_WAIT_TIME_MS = 0; * Ensure that a concrete async_metadata_source is in the expected initial state * after construction. */ -TEST(async_metadata_source, construction) +TEST(async_metadata_source_test, construction) { immediate_metadata_source source; @@ -127,21 +127,18 @@ TEST(async_metadata_source, construction) * the timeout, that the lookup() method returns true, and that it returns * the metadata in the output parameter. */ -TEST(async_metadata_source, lookup_key_immediate_return) +TEST(async_metadata_source_test, lookup_key_immediate_return) { const std::string key = "foo"; const std::string metadata = "bar"; std::string response = "response-not-set"; - bool response_found; immediate_metadata_source source; // Seed the precanned response source.set_response(key, metadata); - response_found = source.lookup(key, response); - - ASSERT_TRUE(response_found); + ASSERT_TRUE(source.lookup(key, response)); ASSERT_EQ(metadata, response); ASSERT_TRUE(source.is_running()); } @@ -151,7 +148,7 @@ TEST(async_metadata_source, lookup_key_immediate_return) * before the timeout, and if the client did not provide a callback, that * calling lookup() after the result it available returns the value. */ -TEST(async_metadata_source, lookup_key_delayed_return_second_call) +TEST(async_metadata_source_test, lookup_key_delayed_return_second_call) { const uint64_t DELAY_MS = 50; const std::string key = "mykey"; @@ -187,7 +184,7 @@ TEST(async_metadata_source, lookup_key_delayed_return_second_call) * before the timeout, and if the client did provide a callback, that the * callback is invoked with the metadata once they're avaialble. */ -TEST(async_metadata_source, look_key_delayed_async_callback) +TEST(async_metadata_source_test, look_key_delayed_async_callback) { const uint64_t DELAY_MS = 50; const std::string key = "mykey"; diff --git a/test/userspace/libsinsp/async_linux_docker_metadata_source.ut.cpp b/test/userspace/libsinsp/async_linux_docker_metadata_source.ut.cpp new file mode 100644 index 0000000000..49302118ee --- /dev/null +++ b/test/userspace/libsinsp/async_linux_docker_metadata_source.ut.cpp @@ -0,0 +1,268 @@ +/** + * @file + * + * Fill in a short overview of the file's content + * + * @copyright Copyright (c) 2019 Sysdig Inc., All Rights Reserved + */ +#include "async_linux_docker_metadata_source.h" +#include "test_helpers/web_server_helper.h" + +#include +#include +#include +#include +#include + +#include +#include + +#if defined(LOCAL_DEBUG) +# include +# define LOG(fmt, ...) fprintf(stderr, "[%s]:%d: " fmt "\n", __FUNCTION__, __LINE__, ##__VA_ARGS__) +#else +# define LOG(fmt, ...) do { } while(false) +#endif + +using namespace test; +using namespace sysdig; + +class sinsp_container_manager; + +namespace +{ + +const std::string s_container_id = "b646c6d7cad09218238eb0ccf72d78024bc4e742a11b778c1637575121abcdd5"; +const std::string s_image_id = "fe52b035c0bdc374d688ec285efc80a349b34e188031408a037c171cadf3e47b"; + +/** + * Read the content of the file with the given filename into the given + * content. + * + * @param[in] filename The name of the file to read + * @param[out] content On success, returns the output of the file + * + * @returns true if the file can be opened for reading, false otherwise. + */ +bool read_file(const std::string& filename, std::string& content) +{ + std::ifstream in(filename); + + if(!in) + { + return false; + } + + std::ostringstream out; + + Poco::StreamCopier::copyStream(in, out); + + content = std::move(out.str()); + + return true; +} + +/** + * Base class for all tests; used to set up a suite-wide fixture. + */ +class async_linux_docker_metadata_source_test : public testing::Test +{ +public: + /** + * Allocate the web_server_helper before any test run. + */ + static void SetUpTestCase() + { + std::string content; + + ASSERT_EQ(s_server_helper, nullptr); + s_server_helper = new web_server_helper(); + + ASSERT_TRUE(read_file("./resources/docker_container_" + s_container_id + ".json", + content)); + s_server_helper->set_content("/v1.24/containers/" + s_container_id + "/json", + content); + + ASSERT_TRUE(read_file("./resources/docker_image_" + s_image_id + ".json", + content)); + + s_server_helper->set_content("/v1.24/images/" + s_image_id + "/json?digests=1", + content); + } + + /** + * Deallocate the web_server_helper after all tests have finished. + */ + static void TearDownTestCase() + { + ASSERT_NE(s_server_helper, nullptr); + delete s_server_helper; + } + +protected: + /** + * Enable the tests to get the server without being able to muck + * with the pointer. + */ + web_server_helper& get_docker() + { + return *s_server_helper; + } + + /** + * Returns true if the given collection contains the given element + * + * @tparam collection The type of collection to search + * @tparam element_type The type of element in the collection + * + * @param[in] collection The collection to search + * @param[in] element The element for which to search + * + * @returns true if the element is found in the collection, + * false otherwise. + */ + template + bool contains(const collection_type& collection, const element_type& element) + { + return (std::find(collection.begin(), collection.end(), element) != collection.end()); + } + +private: + static web_server_helper* s_server_helper; +}; + +web_server_helper* async_linux_docker_metadata_source_test::s_server_helper; + +} // end namespace + + +/** + * Ensure that the constructor puts the metadata source in the expected + * initial state. + */ +TEST_F(async_linux_docker_metadata_source_test, constructor) +{ + async_linux_docker_metadata_source source; + + ASSERT_EQ(async_linux_docker_metadata_source::DEFAULT_API_VERSION, + source.get_api_version()); + ASSERT_EQ(async_linux_docker_metadata_source::DEFAULT_DOCKER_SOCKET_PATH, + source.get_socket_path()); +} + +/** + * Ensure that if the client specifies custom values for the api version and + * the socket path, that those values are recorded. + */ +TEST_F(async_linux_docker_metadata_source_test, constructor_custom_values) +{ + const std::string api_version = "v10"; + const std::string socket_path = "/some/path.sock"; + + async_linux_docker_metadata_source source(socket_path, api_version);; + + ASSERT_EQ(api_version, source.get_api_version()); + ASSERT_EQ(socket_path, source.get_socket_path()); +} + +/** + * Ensure that lookup_metrics() exhibits the expected behavior. Specifically, + * we expect the first call to lookup_metrics() to fail, and to kick off the + * background thread. We expect that thread, within a reasonable amount of + * time, to fetch the desired content, and to parse it. We expect a subsequent + * call to lookup_metrics() to return the parsed metrics. + */ +TEST_F(async_linux_docker_metadata_source_test, lookup_metrics) +{ + std::shared_ptr container_info(new sinsp_container_info()); + sinsp_container_manager* manager = nullptr; + + container_info->m_id = s_container_id; + container_info->m_type = CT_DOCKER; + + docker_metadata metadata(manager, container_info); + async_linux_docker_metadata_source source(get_docker().get_socket_path()); + + // The first call to lookup() will kick off the async lookup. The + // Docker metadata fetcher will not block waiting for a response, so + // the first call for a given id should always fail. + ASSERT_FALSE(source.lookup(container_info->m_id, metadata)); + + // We don't know exactly how long it will take for the async fetcher to + // contact the docker server helper, for the server helper to return + // the precanned response, and for the async fetcher to parse the + // results. We should be able to poll for the response. We'll poll + // for up to a max of 10s -- if it takes more than 10s, we'll assume + // something has gone horribly wrong. + const int MAX_WAIT_TIME_SECS = 10; + const int FRACTION_OF_SECOND = 10; + bool eventually_successful = false; + + for (int i = 0; !eventually_successful && i < (MAX_WAIT_TIME_SECS * FRACTION_OF_SECOND); ++i) + { + const int ONE_SEC_MS = 1000; + std::this_thread::sleep_for(std::chrono::milliseconds(ONE_SEC_MS / FRACTION_OF_SECOND)); + eventually_successful = source.lookup(container_info->m_id, metadata); + } + + ASSERT_TRUE(eventually_successful); + container_info = metadata.m_container_info; + + // Make sure that we correctly parsed the interesting information + ASSERT_EQ(container_info->m_id, s_container_id); + ASSERT_EQ(container_info->m_type, CT_DOCKER); + ASSERT_EQ(container_info->m_name, "opengrok"); + ASSERT_EQ(container_info->m_image, "opengrok/docker:latest"); + ASSERT_EQ(container_info->m_imageid, s_image_id); + ASSERT_EQ(container_info->m_imagerepo, "opengrok/docker"); + ASSERT_EQ(container_info->m_imagetag, "latest"); + ASSERT_EQ(container_info->m_imagedigest, ""); + ASSERT_EQ(container_info->m_container_ip, 2886795267); + ASSERT_FALSE(container_info->m_privileged); + + ASSERT_NE(container_info->m_mounts.begin(), container_info->m_mounts.end()); + { + auto itr = container_info->m_mounts.begin(); + + ASSERT_EQ(itr->m_source, "/home/user/.opengrok"); + ASSERT_EQ(itr->m_dest, "/src"); + ASSERT_EQ(itr->m_mode, ""); + ASSERT_TRUE(itr->m_rdwr); + ASSERT_EQ(itr->m_propagation, "rprivate"); + } + + ASSERT_NE(container_info->m_port_mappings.begin(), container_info->m_port_mappings.end()); + { + auto itr = container_info->m_port_mappings.begin(); + + ASSERT_EQ(itr->m_host_ip, 0); + ASSERT_EQ(itr->m_host_port, 8080); + ASSERT_EQ(itr->m_container_port, 8080); + } + + ASSERT_TRUE(container_info->m_labels.empty()); + + ASSERT_NE(container_info->m_env.begin(), container_info->m_env.end()); + { + ASSERT_TRUE(contains(container_info->m_env, "REINDEX=0")); + ASSERT_TRUE(contains(container_info->m_env, "LANG=C.UTF-8")); + ASSERT_TRUE(contains(container_info->m_env, "JAVA_HOME=/docker-java-home/jre")); + ASSERT_TRUE(contains(container_info->m_env, "JAVA_VERSION=8u181")); + ASSERT_TRUE(contains(container_info->m_env, "JAVA_DEBIAN_VERSION=8u181-b13-2~deb9u1")); + ASSERT_TRUE(contains(container_info->m_env, "CATALINA_HOME=/usr/local/tomcat")); + ASSERT_TRUE(contains(container_info->m_env, "TOMCAT_NATIVE_LIBDIR=/usr/local/tomcat/native-jni-lib")); + ASSERT_TRUE(contains(container_info->m_env, "LD_LIBRARY_PATH=/usr/local/tomcat/native-jni-lib")); + ASSERT_TRUE(contains(container_info->m_env, "OPENSSL_VERSION=1.1.0j-1~deb9u1")); + ASSERT_TRUE(contains(container_info->m_env, "TOMCAT_MAJOR=9")); + ASSERT_TRUE(contains(container_info->m_env, "TOMCAT_VERSION=9.0.14")); + } + + ASSERT_EQ(container_info->m_mesos_task_id, std::string()); + ASSERT_EQ(container_info->m_memory_limit, 0); + ASSERT_EQ(container_info->m_swap_limit, 0); + ASSERT_EQ(container_info->m_cpu_shares, 1024); + ASSERT_EQ(container_info->m_cpu_quota, 0); + ASSERT_EQ(container_info->m_cpu_period, 100000); + ASSERT_EQ(container_info->m_sysdig_agent_conf, std::string()); + ASSERT_EQ(container_info->m_metadata_deadline, 0); +} diff --git a/userspace/libsinsp/async_docker_metadata_source.cpp b/userspace/libsinsp/async_docker_metadata_source.cpp index 1fd70ea656..fe45ea0949 100644 --- a/userspace/libsinsp/async_docker_metadata_source.cpp +++ b/userspace/libsinsp/async_docker_metadata_source.cpp @@ -27,29 +27,26 @@ limitations under the License. #include "sinsp_int.h" #include "logger.h" -using namespace sysdig; +#if defined(LOCAL_DEBUG) +# include +# define LOG(fmt, ...) fprintf(stderr, "[%s]:%d: " fmt "\n", __FUNCTION__, __LINE__, ##__VA_ARGS__) +#else +# define LOG(fmt, ...) do { } while(false) +#endif -const uint16_t async_docker_metadata_source::DEFAULT_PORT = 80; +using namespace sysdig; -async_docker_metadata_source::async_docker_metadata_source(const std::string& api_version, - const uint16_t port): +async_docker_metadata_source::async_docker_metadata_source(const std::string& api_version): async_metadata_source(NO_LOOKUP_WAIT), m_query_image_info(true), - m_api_version(api_version), - m_port(port) -{ -} + m_api_version(api_version) +{ } const std::string& async_docker_metadata_source::get_api_version() const { return m_api_version; } -uint16_t async_docker_metadata_source::get_port() const -{ - return m_port; -} - bool async_docker_metadata_source::query_image_info() const { return m_query_image_info; @@ -67,19 +64,10 @@ void async_docker_metadata_source::run_impl() const std::string container_id = dequeue_next_key(); docker_metadata metadata = get_metadata(container_id); - if(metadata.m_manager != nullptr) + if(parse_docker(metadata.m_manager, + metadata.m_container_info.get())) { - if(parse_docker(metadata.m_manager, - metadata.m_container_info.get())) - { - store_metadata(container_id, metadata); - } - } - else - { - g_logger.log("Unexpected null manager", - sinsp_logger::SEV_ERROR); - ASSERT(metadata.m_manager != nullptr); + store_metadata(container_id, metadata); } } } @@ -232,7 +220,9 @@ bool async_docker_metadata_source::parse_docker(sinsp_container_manager* const m { std::string container_id = net_mode.substr(net_mode.find(":") + 1); uint32_t container_ip; - const sinsp_container_info* const container_info = manager->get_container(container_id); + const sinsp_container_info* const container_info = + manager ? manager->get_container(container_id) : nullptr; + if(container_info) { container_ip = container_info->m_container_ip; diff --git a/userspace/libsinsp/async_docker_metadata_source.h b/userspace/libsinsp/async_docker_metadata_source.h index d6752d45be..6e962f33e3 100644 --- a/userspace/libsinsp/async_docker_metadata_source.h +++ b/userspace/libsinsp/async_docker_metadata_source.h @@ -53,22 +53,12 @@ struct docker_metadata class async_docker_metadata_source : public async_metadata_source { public: - /** The default port on which Docker listens for REST requests. */ - static const uint16_t DEFAULT_PORT; - /** * Returns the API version that this async_metadata_source will use to * fetch information from Docker. */ const std::string& get_api_version() const; - - /** - * Returns the port that this async_docker_metadata_source will use - * to connect to Docker. - */ - uint16_t get_port() const; - /** * Returns true if this async_docker_metadata_source should query for * image info, false otherwise. @@ -89,8 +79,12 @@ class async_docker_metadata_source : public async_metadata_source +# define LOG(fmt, ...) fprintf(stderr, "[%s]:%d: " fmt "\n", __FUNCTION__, __LINE__, ##__VA_ARGS__) +#else +# define LOG(fmt, ...) do { } while(false) +#endif + using namespace sysdig; namespace { -const std::string s_docker_socket_path = "/var/run/docker.sock"; - #if defined(HAS_CAPTURE) /** * Handles callbacks from libcurl to build a string representation of the @@ -47,13 +52,14 @@ size_t docker_curl_write_callback(const char* const ptr, } // end namespace +const std::string async_linux_docker_metadata_source::DEFAULT_DOCKER_SOCKET_PATH = "/var/run/docker.sock"; const std::string async_linux_docker_metadata_source::DEFAULT_API_VERSION = "/v1.24"; async_linux_docker_metadata_source::async_linux_docker_metadata_source( - const std::string& api_version, - const uint16_t port): - async_docker_metadata_source(api_version, port) - , m_unix_socket_path(scap_get_host_root() + s_docker_socket_path) + const std::string& socket_path, + const std::string& api_version): + async_docker_metadata_source(api_version) + , m_unix_socket_path(scap_get_host_root() + socket_path) #if defined(HAS_CAPTURE) , m_curl(curl_easy_init()) , m_curlm(curl_multi_init()) @@ -79,12 +85,19 @@ async_linux_docker_metadata_source::async_linux_docker_metadata_source( async_linux_docker_metadata_source::~async_linux_docker_metadata_source() { + stop(); + #if defined(HAS_CAPTURE) curl_easy_cleanup(m_curl); curl_multi_cleanup(m_curlm); #endif } +const std::string& async_linux_docker_metadata_source::get_socket_path() const +{ + return m_unix_socket_path; +} + std::string async_linux_docker_metadata_source::build_request(const std::string& path) { return "http://localhost" + get_api_version() + path; @@ -95,28 +108,24 @@ sinsp_docker_response async_linux_docker_metadata_source::get_docker( const std::string& url, std::string &json) { + LOG("url: %s", url.c_str()); + #if defined(HAS_CAPTURE) + LOG("url: %s", url.c_str()); if(curl_easy_setopt(m_curl, CURLOPT_URL, url.c_str()) != CURLE_OK) { ASSERT(false); return sinsp_docker_response::RESP_ERROR; } - if(curl_easy_setopt(m_curl, CURLOPT_PORT, get_port()) != CURLE_OK) - { - ASSERT(false); - return sinsp_docker_response::RESP_ERROR; - } - if(curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &json) != CURLE_OK) { ASSERT(false); return sinsp_docker_response::RESP_ERROR; } - if(curl_multi_add_handle(m_curlm, m_curl) != CURLM_OK) - { + if(curl_multi_add_handle(m_curlm, m_curl) != CURLM_OK) { ASSERT(false); return sinsp_docker_response::RESP_ERROR; } diff --git a/userspace/libsinsp/async_linux_docker_metadata_source.h b/userspace/libsinsp/async_linux_docker_metadata_source.h index cdec92f8be..fb35bb38c6 100644 --- a/userspace/libsinsp/async_linux_docker_metadata_source.h +++ b/userspace/libsinsp/async_linux_docker_metadata_source.h @@ -32,13 +32,16 @@ namespace sysdig class async_linux_docker_metadata_source : public async_docker_metadata_source { public: + const static std::string DEFAULT_DOCKER_SOCKET_PATH; const static std::string DEFAULT_API_VERSION; async_linux_docker_metadata_source( - const std::string& api_version = DEFAULT_API_VERSION, - uint16_t port = DEFAULT_PORT); + const std::string& socket_path = DEFAULT_DOCKER_SOCKET_PATH, + const std::string& api_version = DEFAULT_API_VERSION); ~async_linux_docker_metadata_source(); + const std::string& get_socket_path() const; + protected: std::string build_request(const std::string& path) override; sinsp_docker_response get_docker(sinsp_container_manager* manager, diff --git a/userspace/libsinsp/async_windows_docker_metadata_source.cpp b/userspace/libsinsp/async_windows_docker_metadata_source.cpp index bc107bb457..144eb6302c 100644 --- a/userspace/libsinsp/async_windows_docker_metadata_source.cpp +++ b/userspace/libsinsp/async_windows_docker_metadata_source.cpp @@ -25,11 +25,9 @@ using namespace sysdig; const std::string async_windows_docker_metadata_source::DEFAULT_API_VERSION = "/v1.30"; async_windows_docker_metadata_source::async_windows_docker_metadata_source( - const std::string& api_version, - const uint16_t port): - async_docker_metadata_source(api_version, port) -{ -} + const std::string& api_version): + async_docker_metadata_source(api_version) +{ } std::string async_windows_docker_metadata_source::build_request(const std::string& path) { diff --git a/userspace/libsinsp/async_windows_docker_metadata_source.h b/userspace/libsinsp/async_windows_docker_metadata_source.h index 688a2d829d..c98a84c75f 100644 --- a/userspace/libsinsp/async_windows_docker_metadata_source.h +++ b/userspace/libsinsp/async_windows_docker_metadata_source.h @@ -34,8 +34,7 @@ class async_windows_docker_metadata_source : public async_docker_metadata_source const static std::string DEFAULT_API_VERSION; async_windows_docker_metadata_source( - const std::string& api_version = DEFAULT_API_VERSION, - uint16_t port = DEFAULT_PORT); + const std::string& api_version = DEFAULT_API_VERSION); protected: std::string build_request(const std::string& path) override; From 7f3e3e5164fa8c2d6ae54da6606a53a170a111dc Mon Sep 17 00:00:00 2001 From: Andy Dalton Date: Thu, 10 Jan 2019 13:45:16 -0500 Subject: [PATCH 06/14] Introduce url_fetcher This change pulls the URL fetching logic out of the implementation of async_linux_docker_metadata_source and into a separate class. The rationale for this change is: * The details of URL fetching is a separate concern * The URL fetching logic might be reusable by other components The URL fetcher is separated into an interface and a concrete realization of that interface, with factory methods for creating concretions. The rationale for this design is: * It avoids class-to-class coupling between async_linux_docker_metadata_source and a concrete url fetcher * It will enable us to tweak the factory methods in a unit test environemnt to return UT-specific realizations of the interface that don't actually have to talk to web servers. --- .../libsinsp/curl_url_fetcher.ut.cpp | 160 +++++++++++ userspace/libsinsp/CMakeLists.txt | 2 + .../async_linux_docker_metadata_source.cpp | 123 ++------- .../async_linux_docker_metadata_source.h | 8 +- userspace/libsinsp/curl_url_fetcher.cpp | 257 ++++++++++++++++++ userspace/libsinsp/curl_url_fetcher.h | 74 +++++ userspace/libsinsp/url_fetcher.cpp | 35 +++ userspace/libsinsp/url_fetcher.h | 69 +++++ 8 files changed, 619 insertions(+), 109 deletions(-) create mode 100644 test/userspace/libsinsp/curl_url_fetcher.ut.cpp create mode 100644 userspace/libsinsp/curl_url_fetcher.cpp create mode 100644 userspace/libsinsp/curl_url_fetcher.h create mode 100644 userspace/libsinsp/url_fetcher.cpp create mode 100644 userspace/libsinsp/url_fetcher.h diff --git a/test/userspace/libsinsp/curl_url_fetcher.ut.cpp b/test/userspace/libsinsp/curl_url_fetcher.ut.cpp new file mode 100644 index 0000000000..d18fcb38f7 --- /dev/null +++ b/test/userspace/libsinsp/curl_url_fetcher.ut.cpp @@ -0,0 +1,160 @@ +/* +Copyright (C) 2018 Sysdig, Inc. + +This file is part of sysdig. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ +#include "curl_url_fetcher.h" +#include "test_helpers/web_server_helper.h" +#include + +using namespace sysdig; +using namespace test; + +namespace +{ + +const int HTTP_OK = 200; + +} // end namespace; + +/* + * Base class for all tests; used to set up a suite-wide fixture. + */ +class curl_url_fetcher_test : public testing::Test +{ +public: + /** + * Allocate the web_server_helper before any test run. + */ + static void SetUpTestCase() + { + ASSERT_EQ(s_unix_server_helper, nullptr); + s_unix_server_helper = new web_server_helper(); + + ASSERT_EQ(s_tcp_server_helper, nullptr); + s_tcp_server_helper = new web_server_helper( + web_server_helper::SELECT_EPHEMERAL_PORT); + } + + /** + * Deallocate the web_server_helper after all tests have finished. + */ + static void TearDownTestCase() + { + ASSERT_NE(s_unix_server_helper, nullptr); + delete s_unix_server_helper; + s_unix_server_helper = nullptr; + + ASSERT_NE(s_tcp_server_helper, nullptr); + delete s_tcp_server_helper; + s_tcp_server_helper = nullptr; + } + + /** + * Clear any entries from the helpers. + */ + void TearDown() + { + s_unix_server_helper->reset(); + s_tcp_server_helper->reset(); + } + +protected: + /** + * Enable the tests to get the UNIX domain socket server helper without + * being able to muck with the pointer. + */ + web_server_helper& get_unix_server() + { + return *s_unix_server_helper; + } + + /** + * Enable the tests to get the TCP server helper without + * being able to muck with the pointer. + */ + web_server_helper& get_tcp_server() + { + return *s_tcp_server_helper; + } + + +private: + static web_server_helper* s_unix_server_helper; + static web_server_helper* s_tcp_server_helper; +}; + +web_server_helper* curl_url_fetcher_test::s_unix_server_helper; +web_server_helper* curl_url_fetcher_test::s_tcp_server_helper; + +/** + * Ensure that the default constructor creates a curl_url_fetcher for TCP. + */ +TEST_F(curl_url_fetcher_test, tcp_constructor) +{ + curl_url_fetcher fetcher; + + ASSERT_TRUE(fetcher.is_tcp()); + ASSERT_EQ(fetcher.get_socket_path(), std::string()); +} + +/** + * Ensure that the parameterized constructor creates a curl_url_fetcher for + * UNIX domain sockets. + */ +TEST_F(curl_url_fetcher_test, unix_domain_constructor) +{ + curl_url_fetcher fetcher(get_unix_server().get_socket_path()); + + ASSERT_FALSE(fetcher.is_tcp()); + ASSERT_EQ(fetcher.get_socket_path(), get_unix_server().get_socket_path()); +} + +/** + * Ensure that a curl_url_fetcher can fetch a document via TCP. + */ +TEST_F(curl_url_fetcher_test, fetch_tcp) +{ + const std::string path = "/foo"; + const std::string expected_content = "bar"; + const std::string url = "http://localhost:" + + std::to_string(get_tcp_server().get_port()) + + path; + std::string actual_content; + curl_url_fetcher fetcher; + + get_tcp_server().set_content(path, expected_content); + + ASSERT_EQ(fetcher.fetch(url, actual_content), HTTP_OK); + ASSERT_EQ(expected_content, actual_content); +} + +/** + * Ensure that a curl_url_fetcher can fetch a document via a UNIX domain socket. + */ +TEST_F(curl_url_fetcher_test, fetch_unix) +{ + const std::string path = "/bar"; + const std::string expected_content = "foo"; + const std::string url = "http://localhost:" + path; + std::string actual_content; + curl_url_fetcher fetcher(get_unix_server().get_socket_path()); + + get_unix_server().set_content(path, expected_content); + + ASSERT_EQ(fetcher.fetch(url, actual_content), HTTP_OK); + ASSERT_EQ(expected_content, actual_content); +} diff --git a/userspace/libsinsp/CMakeLists.txt b/userspace/libsinsp/CMakeLists.txt index 3ad33a8925..87dcb46f73 100644 --- a/userspace/libsinsp/CMakeLists.txt +++ b/userspace/libsinsp/CMakeLists.txt @@ -38,6 +38,7 @@ add_library(sinsp STATIC chisel_api.cpp container.cpp ctext.cpp + curl_url_fetcher.cpp cyclewriter.cpp cursescomponents.cpp cursestable.cpp @@ -103,6 +104,7 @@ add_library(sinsp STATIC sinsp_auth.cpp sinsp_curl.cpp stopwatch.cpp + url_fetcher.cpp uri_parser.c uri.cpp utils.cpp diff --git a/userspace/libsinsp/async_linux_docker_metadata_source.cpp b/userspace/libsinsp/async_linux_docker_metadata_source.cpp index 3589ac3155..6ec7ef9b2d 100644 --- a/userspace/libsinsp/async_linux_docker_metadata_source.cpp +++ b/userspace/libsinsp/async_linux_docker_metadata_source.cpp @@ -29,29 +29,6 @@ limitations under the License. using namespace sysdig; -namespace -{ - -#if defined(HAS_CAPTURE) -/** - * Handles callbacks from libcurl to build a string representation of the - * document that its fetching. - */ -size_t docker_curl_write_callback(const char* const ptr, - const size_t size, - const size_t nmemb, - std::string* const json) -{ - const std::size_t total = size * nmemb; - - json->append(ptr, total); - - return total; -} -#endif - -} // end namespace - const std::string async_linux_docker_metadata_source::DEFAULT_DOCKER_SOCKET_PATH = "/var/run/docker.sock"; const std::string async_linux_docker_metadata_source::DEFAULT_API_VERSION = "/v1.24"; @@ -61,36 +38,13 @@ async_linux_docker_metadata_source::async_linux_docker_metadata_source( async_docker_metadata_source(api_version) , m_unix_socket_path(scap_get_host_root() + socket_path) #if defined(HAS_CAPTURE) - , m_curl(curl_easy_init()) - , m_curlm(curl_multi_init()) + , m_url_fetcher(url_fetcher::new_fetcher(m_unix_socket_path)) #endif -{ -#if defined(HAS_CAPTURE) - if(m_curlm != nullptr) - { - curl_multi_setopt(m_curlm, - CURLMOPT_PIPELINING, - CURLPIPE_HTTP1 | CURLPIPE_MULTIPLEX); - } - - if(m_curl != nullptr) - { - curl_easy_setopt(m_curl, CURLOPT_UNIX_SOCKET_PATH, m_unix_socket_path.c_str()); - curl_easy_setopt(m_curl, CURLOPT_HTTPGET, 1); - curl_easy_setopt(m_curl, CURLOPT_FOLLOWLOCATION, 1); - curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, docker_curl_write_callback); - } -#endif -} +{ } async_linux_docker_metadata_source::~async_linux_docker_metadata_source() { stop(); - -#if defined(HAS_CAPTURE) - curl_easy_cleanup(m_curl); - curl_multi_cleanup(m_curlm); -#endif } const std::string& async_linux_docker_metadata_source::get_socket_path() const @@ -108,76 +62,33 @@ sinsp_docker_response async_linux_docker_metadata_source::get_docker( const std::string& url, std::string &json) { - LOG("url: %s", url.c_str()); + sinsp_docker_response response = sinsp_docker_response::RESP_ERROR; #if defined(HAS_CAPTURE) - - LOG("url: %s", url.c_str()); - if(curl_easy_setopt(m_curl, CURLOPT_URL, url.c_str()) != CURLE_OK) + try { - ASSERT(false); - return sinsp_docker_response::RESP_ERROR; - } - - if(curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &json) != CURLE_OK) - { - ASSERT(false); - return sinsp_docker_response::RESP_ERROR; - } + LOG("url: %s", url.c_str()); - if(curl_multi_add_handle(m_curlm, m_curl) != CURLM_OK) { - ASSERT(false); - return sinsp_docker_response::RESP_ERROR; - } - - while(true) - { - int still_running = 42; - CURLMcode res = curl_multi_perform(m_curlm, &still_running); + const int http_code = m_url_fetcher->fetch(url, json); - if(res != CURLM_OK) + if(http_code == 200) { - ASSERT(false); - return sinsp_docker_response::RESP_ERROR; + response = sinsp_docker_response::RESP_OK; } - - if(still_running == 0) + else { - break; - } - - int numfds = 0; - res = curl_multi_wait(m_curlm, NULL, 0, -1, &numfds); - if(res != CURLM_OK) - { - ASSERT(false); - return sinsp_docker_response::RESP_ERROR; + g_logger.log("http_code: " + std::to_string(http_code), + sinsp_logger::SEV_WARNING); + response = sinsp_docker_response::RESP_BAD_REQUEST; } } - - if(curl_multi_remove_handle(m_curlm, m_curl) != CURLM_OK) + catch(const std::exception& ex) { + g_logger.log(std::string("Failed to fetch URL: ") + ex.what()); ASSERT(false); - return sinsp_docker_response::RESP_ERROR; + response = sinsp_docker_response::RESP_ERROR; } +#endif // HAS_CAPTURE - long http_code = 0; - if(curl_easy_getinfo(m_curl, CURLINFO_RESPONSE_CODE, &http_code) != CURLE_OK) - { - ASSERT(false); - return sinsp_docker_response::RESP_ERROR; - } - - if(http_code != 200) - { - g_logger.log("http_code: " + std::to_string(http_code), - sinsp_logger::SEV_WARNING); - return sinsp_docker_response::RESP_BAD_REQUEST; - } - - return sinsp_docker_response::RESP_OK; -#else /* HAS_CAPTURE */ - return sinsp_docker_response::RESP_ERROR; -#endif /* HAS_CAPTURE */ + return response; } - diff --git a/userspace/libsinsp/async_linux_docker_metadata_source.h b/userspace/libsinsp/async_linux_docker_metadata_source.h index fb35bb38c6..59f19b88a1 100644 --- a/userspace/libsinsp/async_linux_docker_metadata_source.h +++ b/userspace/libsinsp/async_linux_docker_metadata_source.h @@ -19,7 +19,10 @@ limitations under the License. #pragma once #include "async_docker_metadata_source.h" -#include + +#if defined(HAS_CAPTURE) +# include "url_fetcher.h" +#endif namespace sysdig { @@ -52,8 +55,7 @@ class async_linux_docker_metadata_source : public async_docker_metadata_source std::string m_unix_socket_path; #if defined(HAS_CAPTURE) - CURL* const m_curl; - CURLM* const m_curlm; + url_fetcher::ptr m_url_fetcher; #endif }; diff --git a/userspace/libsinsp/curl_url_fetcher.cpp b/userspace/libsinsp/curl_url_fetcher.cpp new file mode 100644 index 0000000000..ed099a2e64 --- /dev/null +++ b/userspace/libsinsp/curl_url_fetcher.cpp @@ -0,0 +1,257 @@ +/* +Copyright (C) 2018 Sysdig, Inc. + +This file is part of sysdig. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ +#include "curl_url_fetcher.h" +#include +#include + +using namespace sysdig; + +namespace +{ + +/** + * An exception that curl_url_fetcher can throw to indicate errors. + */ +class curl_url_fetcher_exception : public std::exception +{ +public: + curl_url_fetcher_exception(const std::string& message): + m_message("curl_url_fetcher_exception: " + message) + { } + + const char* what() const noexcept override + { + return m_message.c_str(); + } + +private: + std::string m_message; +}; + +/** + * Handles potentially partial buffer writes from libcurl. This function + * will append each chunk to the given body. + */ +size_t write_callback(const char* const ptr, + const size_t size, + const size_t nmemb, + std::stringstream* const body) +{ + const std::size_t total = size * nmemb; + + body->write(ptr, total); + + return total; +} + +/** + * Wrapper over curl_easy_strerror() that returns a std::string + */ +std::string easy_strerror(const CURLcode ecode) +{ + return curl_easy_strerror(ecode); +} + +/** + * Wrapper over curl_multi_strerror() that returns a std::string + */ +std::string multi_strerror(const CURLMcode mcode) +{ + return curl_multi_strerror(mcode); +} + +/** + * Wrapper over curl_easy_setopt() that throws a curl_url_fetcher_exception + * if the operation fails. + */ +template +void easy_setopt(CURL* const handle, const CURLoption option, param_type param) +{ + const CURLcode code = curl_easy_setopt(handle, option, param); + + if(code != CURLE_OK) + { + throw curl_url_fetcher_exception("Failed to set option: " + + easy_strerror(code)); + } +} + +/** + * A RAII component to handles adding/removing curl multi handles. + * This will ensure that curl_multi_remove_handle() is called before objects + * of this type go out of scope. + */ +class scoped_curl_multi_handle +{ +public: + scoped_curl_multi_handle(CURLM* const curlm, CURL* const curl): + m_curl(curl), + m_curlm(curlm), + m_added(false) + { + const CURLMcode code = curl_multi_add_handle(m_curlm, m_curl); + + if(code != CURLM_OK) + { + throw curl_url_fetcher_exception( + "Failed to add multi handler: " + + multi_strerror(code)); + } + m_added = true; + } + + ~scoped_curl_multi_handle() + { + try + { + remove(); + } + catch(...) + { + } + } + + void remove() + { + if(m_added) + { + const CURLMcode code = curl_multi_remove_handle(m_curlm, + m_curl); + + if(code != CURLM_OK) + { + throw curl_url_fetcher_exception( + "Failed curl_multi_remove_handle: " + + multi_strerror(code)); + } + + m_added = false; + } + } + +private: + CURL* const m_curl; + CURLM* const m_curlm; + bool m_added; +}; + +} // end namespace + +struct curl_url_fetcher::impl +{ + impl(CURL* const curl, + CURLM* const curlm, + const std::string socket_path = ""): + m_curl(curl), + m_curlm(curlm), + m_socket_path(socket_path) + { } + + CURL* const m_curl; + CURLM* const m_curlm; + const std::string m_socket_path; +}; + +curl_url_fetcher::curl_url_fetcher(): + curl_url_fetcher("") +{ } + +curl_url_fetcher::curl_url_fetcher(const std::string& socket_filename): + m_impl(new impl(curl_easy_init(), curl_multi_init(), socket_filename)) +{ + if(!m_impl->m_socket_path.empty()) + { + easy_setopt(m_impl->m_curl, + CURLOPT_UNIX_SOCKET_PATH, + m_impl->m_socket_path.c_str()); + } + easy_setopt(m_impl->m_curl, CURLOPT_HTTPGET, 1); + easy_setopt(m_impl->m_curl, CURLOPT_FOLLOWLOCATION, 1); + easy_setopt(m_impl->m_curl, CURLOPT_WRITEFUNCTION, write_callback); +} + +curl_url_fetcher::~curl_url_fetcher() +{ + curl_multi_cleanup(m_impl->m_curlm); + curl_easy_cleanup(m_impl->m_curl); +} + +int curl_url_fetcher::fetch(const std::string& url, std::string& body) +{ + std::stringstream out; + CURLcode ecode = CURLE_OK; + CURLMcode mcode = CURLM_OK; + + easy_setopt(m_impl->m_curl, CURLOPT_URL, url.c_str()); + easy_setopt(m_impl->m_curl, CURLOPT_WRITEDATA, &out); + + scoped_curl_multi_handle multi_handle(m_impl->m_curlm, m_impl->m_curl); + + for(;;) + { + int still_running = 42; + + mcode = curl_multi_perform(m_impl->m_curlm, &still_running); + if(mcode != CURLM_OK) + { + throw curl_url_fetcher_exception( + "Failed curl_multi_perform: " + + multi_strerror(mcode)); + } + + if(still_running == 0) + { + break; + } + + int numfds = 0; + mcode = curl_multi_wait(m_impl->m_curlm, NULL, 0, -1, &numfds); + if(mcode != CURLM_OK) + { + throw curl_url_fetcher_exception( + "Failed curl_multi_wait: " + + multi_strerror(mcode)); + } + } + + multi_handle.remove(); + + long http_code = 0; + ecode = curl_easy_getinfo(m_impl->m_curl, CURLINFO_RESPONSE_CODE, &http_code); + if(ecode != CURLE_OK) + { + throw curl_url_fetcher_exception( + "Failed to get response code: " + + easy_strerror(ecode)); + } + + body = out.str(); + + return http_code; +} + +bool curl_url_fetcher::is_tcp() const +{ + return m_impl->m_socket_path.empty(); +} + +const std::string& curl_url_fetcher::get_socket_path() const +{ + return m_impl->m_socket_path; +} diff --git a/userspace/libsinsp/curl_url_fetcher.h b/userspace/libsinsp/curl_url_fetcher.h new file mode 100644 index 0000000000..9716e5d5d1 --- /dev/null +++ b/userspace/libsinsp/curl_url_fetcher.h @@ -0,0 +1,74 @@ +/* +Copyright (C) 2018 Sysdig, Inc. + +This file is part of sysdig. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ +#pragma once + +#include "url_fetcher.h" +#include + +namespace sysdig +{ + +/** + * A concrete url_fetcher implemented using libcurl. + */ +class curl_url_fetcher : public url_fetcher +{ +public: + /** + * Initialize this curl_url_fetcher for fetching URLs via TCP. + */ + curl_url_fetcher(); + + /** + * Initialize this curl_url_fetcher for fetching URLs via UNIX + * domain sockets. + */ + curl_url_fetcher(const std::string& socket_filename); + + virtual ~curl_url_fetcher(); + + /** + * Fetch the given url and return its body in the given body. + * + * @param[in] url The URL to fetch + * @param[out] body On success, the body of the requested URL. + * + * @returns the HTTP status code returned by the far-end + */ + int fetch(const std::string& url, std::string& body) override; + + /** + * Returns true if this curl_url_fetcher will fetch URLs via TCP, + * false otherwise. + */ + bool is_tcp() const; + + /** + * Returns the UNIX domain socket that this curl_url_fetcher will + * use for requests. This method is meaningful only if is_tcp() + * returns false. + */ + const std::string& get_socket_path() const; + +private: + struct impl; + std::unique_ptr m_impl; +}; + +} // end namespace sysdig diff --git a/userspace/libsinsp/url_fetcher.cpp b/userspace/libsinsp/url_fetcher.cpp new file mode 100644 index 0000000000..f59afd488d --- /dev/null +++ b/userspace/libsinsp/url_fetcher.cpp @@ -0,0 +1,35 @@ +/* +Copyright (C) 2018 Sysdig, Inc. + +This file is part of sysdig. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ +#include "url_fetcher.h" +#include "curl_url_fetcher.h" + +using namespace sysdig; + +url_fetcher::~url_fetcher() +{ } + +url_fetcher::ptr url_fetcher::new_fetcher() +{ + return ptr(new curl_url_fetcher()); +} + +url_fetcher::ptr url_fetcher::new_fetcher(const std::string& socket_filename) +{ + return ptr(new curl_url_fetcher(socket_filename)); +} diff --git a/userspace/libsinsp/url_fetcher.h b/userspace/libsinsp/url_fetcher.h new file mode 100644 index 0000000000..360b4ed11a --- /dev/null +++ b/userspace/libsinsp/url_fetcher.h @@ -0,0 +1,69 @@ +/* +Copyright (C) 2018 Sysdig, Inc. + +This file is part of sysdig. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ +#pragma once + +#include +#include + +namespace sysdig +{ + +/** + * Interface to an abstract url_fetcher -- an object that clients can use + * to fetch URLs. We'll eventually be able to unit test-specific concrete + * implementaions of this that can serve pre-canned responses without actually + * spinning up HTTP servers. + */ +class url_fetcher +{ +public: + typedef std::unique_ptr ptr; + + virtual ~url_fetcher(); + + /** + * Fetches the given url and stores the fetched document in the + * given body. + * + * @param[in] url The URL to fetch + * @param[out] body The body of the fetched URL. + * + * @returns the HTTP response code + */ + virtual int fetch(const std::string& url, std::string& body) = 0; + + /** + * Factory method for creating url_fetcher%s that can TCP. + * + * @returns a pointer to a concrete url_fetcher. + */ + static ptr new_fetcher(); + + /** + * Factory method for creating url_fetcher%s that use UNIX domain + * sockets. + * + * @param[in] socket_filename The filename of the UNIX domain socket. + * + * @returns a pointer to a concrete url_fetcher. + */ + static ptr new_fetcher(const std::string& socket_filename); +}; + +} // end namespace sysdig From 791057e9de36e331661aae8e94fd8c9eccff6e0c Mon Sep 17 00:00:00 2001 From: Andy Dalton Date: Thu, 10 Jan 2019 17:53:49 -0500 Subject: [PATCH 07/14] Prune metadata cache The async_docker_metadata_source will cache metadata lookups if the lookup takes longer than the client is willing to wait. A subsequent call to lookup with the same key will returned the cached result. The risk is, if nothing ever makes that subsequent call then that cached result will sit in the cache forever. Over time, the cache will continue to leak such metadata. This change introduces a time to live for cache entries. When a cache entry is created, a timestamp is recorded. The async thread examines the entries in the cache and prunes ones that are too old, as specified by the client code. --- .../async/async_metadata_source.ut.cpp | 54 +++++++++++++++++-- userspace/async/async_metadata_source.h | 32 ++++++++--- userspace/async/async_metadata_source.tpp | 47 +++++++++++++++- .../libsinsp/async_docker_metadata_source.cpp | 2 +- .../libsinsp/async_docker_metadata_source.h | 7 +++ 5 files changed, 129 insertions(+), 13 deletions(-) diff --git a/test/userspace/async/async_metadata_source.ut.cpp b/test/userspace/async/async_metadata_source.ut.cpp index 2e6ec97a1b..8c1f681363 100644 --- a/test/userspace/async/async_metadata_source.ut.cpp +++ b/test/userspace/async/async_metadata_source.ut.cpp @@ -33,8 +33,10 @@ namespace class precanned_metadata_source : public async_metadata_source { public: - precanned_metadata_source(const uint64_t max_wait_ms) - : async_metadata_source(max_wait_ms), + const static uint64_t FOREVER_MS; + + precanned_metadata_source(const uint64_t max_wait_ms, const uint64_t ttl_ms = FOREVER_MS) + : async_metadata_source(max_wait_ms, ttl_ms), m_responses() { } @@ -51,6 +53,7 @@ class precanned_metadata_source : public async_metadata_source m_responses; }; +const uint64_t precanned_metadata_source::FOREVER_MS = static_cast(~0L); /** * Realization of async_metadata_source that returns results without delay. @@ -85,8 +88,9 @@ class delayed_metadata_source : public precanned_metadata_source public: const static uint64_t MAX_WAIT_TIME_MS; - delayed_metadata_source(const uint64_t delay_ms): - precanned_metadata_source(MAX_WAIT_TIME_MS), + delayed_metadata_source(const uint64_t delay_ms, + const uint64_t ttl_ms = FOREVER_MS): + precanned_metadata_source(MAX_WAIT_TIME_MS, ttl_ms), m_delay_ms(delay_ms) { } @@ -119,6 +123,7 @@ TEST(async_metadata_source_test, construction) immediate_metadata_source source; ASSERT_EQ(immediate_metadata_source::MAX_WAIT_TIME_MS, source.get_max_wait()); + ASSERT_EQ(precanned_metadata_source::FOREVER_MS, source.get_ttl()); ASSERT_FALSE(source.is_running()); } @@ -216,3 +221,44 @@ TEST(async_metadata_source_test, look_key_delayed_async_callback) ASSERT_EQ(metadata, async_response); } + +/** + * Ensure that "old" results are pruned + */ +TEST(async_metadata_source_test, prune_old_metadata) +{ + const uint64_t DELAY_MS = 0; + const uint64_t TTL_MS = 20; + + const std::string key1 = "mykey1"; + const std::string metadata1 = "myvalue1"; + + const std::string key2 = "mykey2"; + const std::string metadata2 = "myvalue2"; + + delayed_metadata_source source(DELAY_MS, TTL_MS); + std::string response = "response-not-set"; + + // Seed the precanned response + source.set_response(key1, metadata1); + source.set_response(key2, metadata2); + + // Since DELAY_MS is 0, then lookup should return false immediately, + // and should almost immediately add the result to the cache + ASSERT_FALSE(source.lookup(key1, response)); + + // Wait long enough for the old entry to require pruning + std::this_thread::sleep_for(std::chrono::milliseconds(2 * TTL_MS)); + + // Request the other key. This should wake up the thread and actually + // preform the pruning. + ASSERT_FALSE(source.lookup(key2, response)); + + // Wait long enough for the async thread to get woken up and to + // prune the old entry + std::this_thread::sleep_for(std::chrono::milliseconds(TTL_MS)); + + // Since the first key should have been pruned, a second call to + // fetch the first key should also return false. + ASSERT_FALSE(source.lookup(key1, response)); +} diff --git a/userspace/async/async_metadata_source.h b/userspace/async/async_metadata_source.h index 83ed55c74b..60869ec2dd 100644 --- a/userspace/async/async_metadata_source.h +++ b/userspace/async/async_metadata_source.h @@ -18,6 +18,7 @@ limitations under the License. */ #pragma once +#include #include #include #include @@ -54,10 +55,10 @@ namespace sysdig * next call to the lookup() method with the same key will return the previously * collected metadata. * - * @tparam key_type The type of the keys for which concrete subclasses will - * query. - * @tparam metadata_type The type of metadata that concrete subclasses will receive - * from a query. + * @tparam key_type The type of the keys for which concrete subclasses will + * query. + * @tparam metadata_type The type of metadata that concrete subclasses will + * receive from a query. */ template class async_metadata_source @@ -80,8 +81,11 @@ class async_metadata_source * is willing to wait for lookup() to collect * metadata before falling back to an async * return. + * @param[in] ttl_ms The time, in milliseconds, that a cached + * result will live before being considered + * "too old" and being pruned. */ - async_metadata_source(uint64_t max_wait_ms); + async_metadata_source(uint64_t max_wait_ms, uint64_t ttl_ms); async_metadata_source(const async_metadata_source&) = delete; async_metadata_source(async_metadata_source&&) = delete; @@ -89,8 +93,18 @@ class async_metadata_source virtual ~async_metadata_source(); + /** + * Returns the maximum amount of time, in milliseconds, that a call to + * lookup() will block synchronously before returning. + */ uint64_t get_max_wait() const; + /** + * Returns the maximum amount of time, in milliseconds, that a cached + * metadata result will live before being pruned. + */ + uint64_t get_ttl() const; + /** * Lookup metadata based on the given key. This method will block * the caller for up the max_wait_ms time specified at construction @@ -178,20 +192,24 @@ class async_metadata_source m_available(false), m_metadata(), m_available_condition(), - m_callback() - {} + m_callback(), + m_start_time(std::chrono::steady_clock::now()) + { } bool m_available; metadata_type m_metadata; std::condition_variable m_available_condition; callback_handler m_callback; // TODO: This may need to be a list + std::chrono::time_point m_start_time; }; typedef std::map metadata_map; void run(); + void prune_stale_requests(); uint64_t m_max_wait_ms; + uint64_t m_ttl_ms; std::thread m_thread; bool m_running; bool m_terminate; diff --git a/userspace/async/async_metadata_source.tpp b/userspace/async/async_metadata_source.tpp index 2d4bf04800..6aff8e012a 100644 --- a/userspace/async/async_metadata_source.tpp +++ b/userspace/async/async_metadata_source.tpp @@ -30,8 +30,10 @@ namespace sysdig template async_metadata_source::async_metadata_source( - const uint64_t max_wait_ms): + const uint64_t max_wait_ms, + const uint64_t ttl_ms): m_max_wait_ms(max_wait_ms), + m_ttl_ms(ttl_ms), m_thread(), m_running(false), m_terminate(false), @@ -61,6 +63,12 @@ uint64_t async_metadata_source::get_max_wait() const return m_max_wait_ms; } +template +uint64_t async_metadata_source::get_ttl() const +{ + return m_ttl_ms; +} + template void async_metadata_source::stop() { @@ -113,6 +121,8 @@ void async_metadata_source::run() // Wait for something to show up on the queue m_queue_not_empty_condition.wait(guard); } + + prune_stale_requests(); } if(!m_terminate) @@ -238,4 +248,39 @@ void async_metadata_source::store_metadata( } } +/** + * Prune any "old" outstanding requests. This method expect that the caller + * is holding m_mutex. + */ +template +void async_metadata_source::prune_stale_requests() +{ + // Avoid both iterating over and modifying the map by saving a list + // of keys to prune. + std::vector keys_to_prune; + + for(auto i = m_metadata_map.begin(); + !m_terminate && (i != m_metadata_map.end()); + ++i) + { + const auto now = std::chrono::steady_clock::now(); + + const auto age_ms = + std::chrono::duration_cast( + now - i->second.m_start_time).count(); + + if(age_ms > m_ttl_ms) + { + keys_to_prune.push_back(i->first); + } + } + + for(auto i = keys_to_prune.begin(); + !m_terminate && (i != keys_to_prune.end()); + ++i) + { + m_metadata_map.erase(*i); + } +} + } // end namespace sysdig diff --git a/userspace/libsinsp/async_docker_metadata_source.cpp b/userspace/libsinsp/async_docker_metadata_source.cpp index fe45ea0949..255a312479 100644 --- a/userspace/libsinsp/async_docker_metadata_source.cpp +++ b/userspace/libsinsp/async_docker_metadata_source.cpp @@ -37,7 +37,7 @@ limitations under the License. using namespace sysdig; async_docker_metadata_source::async_docker_metadata_source(const std::string& api_version): - async_metadata_source(NO_LOOKUP_WAIT), + async_metadata_source(NO_LOOKUP_WAIT, MAX_TTL_MS), m_query_image_info(true), m_api_version(api_version) { } diff --git a/userspace/libsinsp/async_docker_metadata_source.h b/userspace/libsinsp/async_docker_metadata_source.h index 6e962f33e3..270ac4ae31 100644 --- a/userspace/libsinsp/async_docker_metadata_source.h +++ b/userspace/libsinsp/async_docker_metadata_source.h @@ -53,6 +53,13 @@ struct docker_metadata class async_docker_metadata_source : public async_metadata_source { public: + /** + * The maximum amount of time that a collected Docker metric will + * remain cached waiting for a subsequent call to lookup() before + * being discarded. + */ + const uint64_t MAX_TTL_MS = (30 * 1000); + /** * Returns the API version that this async_metadata_source will use to * fetch information from Docker. From 58edc7c2def22b93b208b4b76c017059568a5485 Mon Sep 17 00:00:00 2001 From: Andy Dalton Date: Fri, 11 Jan 2019 10:09:49 -0500 Subject: [PATCH 08/14] Handle query_image_info This change just fills in passing the query_image_info bool into the async lookup objects, and keeps that in sync with the corresponding value in the sinsp_container_engine_docker. --- .../async_linux_docker_metadata_source.ut.cpp | 26 +++++++++++++++++-- .../libsinsp/async_docker_metadata_source.cpp | 16 +++++++----- .../libsinsp/async_docker_metadata_source.h | 13 +++++++--- .../async_linux_docker_metadata_source.cpp | 3 ++- .../async_linux_docker_metadata_source.h | 2 ++ .../async_windows_docker_metadata_source.cpp | 5 ++-- .../async_windows_docker_metadata_source.h | 3 ++- userspace/libsinsp/container.cpp | 9 +++++-- 8 files changed, 60 insertions(+), 17 deletions(-) diff --git a/test/userspace/libsinsp/async_linux_docker_metadata_source.ut.cpp b/test/userspace/libsinsp/async_linux_docker_metadata_source.ut.cpp index 49302118ee..31c79aa1b2 100644 --- a/test/userspace/libsinsp/async_linux_docker_metadata_source.ut.cpp +++ b/test/userspace/libsinsp/async_linux_docker_metadata_source.ut.cpp @@ -148,6 +148,7 @@ TEST_F(async_linux_docker_metadata_source_test, constructor) source.get_api_version()); ASSERT_EQ(async_linux_docker_metadata_source::DEFAULT_DOCKER_SOCKET_PATH, source.get_socket_path()); + ASSERT_TRUE(source.query_image_info()); } /** @@ -156,13 +157,31 @@ TEST_F(async_linux_docker_metadata_source_test, constructor) */ TEST_F(async_linux_docker_metadata_source_test, constructor_custom_values) { + const bool query_image_info = true; const std::string api_version = "v10"; const std::string socket_path = "/some/path.sock"; - async_linux_docker_metadata_source source(socket_path, api_version);; + async_linux_docker_metadata_source source(query_image_info, + socket_path, + api_version); ASSERT_EQ(api_version, source.get_api_version()); ASSERT_EQ(socket_path, source.get_socket_path()); + ASSERT_EQ(query_image_info, source.query_image_info()); +} + +/** + * Ensure that set_query_image_info() updates the image info query state. + */ +TEST_F(async_linux_docker_metadata_source_test, query_image_info) +{ + async_linux_docker_metadata_source source; + + source.set_query_image_info(false); + ASSERT_FALSE(source.query_image_info()); + + source.set_query_image_info(true); + ASSERT_TRUE(source.query_image_info()); } /** @@ -174,6 +193,7 @@ TEST_F(async_linux_docker_metadata_source_test, constructor_custom_values) */ TEST_F(async_linux_docker_metadata_source_test, lookup_metrics) { + const bool query_image_info = true; std::shared_ptr container_info(new sinsp_container_info()); sinsp_container_manager* manager = nullptr; @@ -181,7 +201,8 @@ TEST_F(async_linux_docker_metadata_source_test, lookup_metrics) container_info->m_type = CT_DOCKER; docker_metadata metadata(manager, container_info); - async_linux_docker_metadata_source source(get_docker().get_socket_path()); + async_linux_docker_metadata_source source(query_image_info, + get_docker().get_socket_path()); // The first call to lookup() will kick off the async lookup. The // Docker metadata fetcher will not block waiting for a response, so @@ -266,3 +287,4 @@ TEST_F(async_linux_docker_metadata_source_test, lookup_metrics) ASSERT_EQ(container_info->m_sysdig_agent_conf, std::string()); ASSERT_EQ(container_info->m_metadata_deadline, 0); } + diff --git a/userspace/libsinsp/async_docker_metadata_source.cpp b/userspace/libsinsp/async_docker_metadata_source.cpp index 255a312479..8a34608f8c 100644 --- a/userspace/libsinsp/async_docker_metadata_source.cpp +++ b/userspace/libsinsp/async_docker_metadata_source.cpp @@ -36,9 +36,12 @@ limitations under the License. using namespace sysdig; -async_docker_metadata_source::async_docker_metadata_source(const std::string& api_version): - async_metadata_source(NO_LOOKUP_WAIT, MAX_TTL_MS), - m_query_image_info(true), +async_docker_metadata_source::async_docker_metadata_source( + const std::string& api_version, + const bool query_image_info): + async_metadata_source(NO_LOOKUP_WAIT, + MAX_TTL_MS), + m_query_image_info(query_image_info), m_api_version(api_version) { } @@ -337,15 +340,16 @@ bool async_docker_metadata_source::parse_docker(sinsp_container_manager* const m return true; } -async_docker_metadata_source* async_docker_metadata_source::new_async_docker_metadata_source() +async_docker_metadata_source* +async_docker_metadata_source::new_async_docker_metadata_source(const bool query_image_info) { async_docker_metadata_source* docker_metadata = nullptr; #if defined(CYGWING_AGENT) - docker_metadata = new async_windows_docker_metadata_source(); + docker_metadata = new async_windows_docker_metadata_source(query_image_info); #else // !CYGWING_AGENT # if defined(HAS_CAPTURE) - docker_metadata = new async_linux_docker_metadata_source(); + docker_metadata = new async_linux_docker_metadata_source(query_image_info); # else // !HAS_CAPTURE // TODO: Need to implement async_null_docker_metadata_source // docker_metadata = new async_null_docker_metadata_source(); diff --git a/userspace/libsinsp/async_docker_metadata_source.h b/userspace/libsinsp/async_docker_metadata_source.h index 270ac4ae31..cc878742d6 100644 --- a/userspace/libsinsp/async_docker_metadata_source.h +++ b/userspace/libsinsp/async_docker_metadata_source.h @@ -81,17 +81,24 @@ class async_docker_metadata_source : public async_metadata_sourceset_query_image_info(m_query_image_info); + } } #ifdef CYGWING_AGENT From 370875fc7f5db61c9fbb98c6012198a729247ceb Mon Sep 17 00:00:00 2001 From: Andy Dalton Date: Mon, 14 Jan 2019 08:49:22 -0500 Subject: [PATCH 09/14] Handle Meso metadata lookup --- userspace/async/async_metadata_source.h | 39 ++++++++++++------- .../libsinsp/async_docker_metadata_source.cpp | 11 ------ .../async_linux_docker_metadata_source.cpp | 3 +- userspace/libsinsp/container.cpp | 16 +++++++- 4 files changed, 42 insertions(+), 27 deletions(-) diff --git a/userspace/async/async_metadata_source.h b/userspace/async/async_metadata_source.h index 60869ec2dd..5088f9aef9 100644 --- a/userspace/async/async_metadata_source.h +++ b/userspace/async/async_metadata_source.h @@ -32,12 +32,13 @@ namespace sysdig /** * Base class for classes that need to collect metadata asynchronously from some - * metadata source. Subclasses will override the the run_impl() method. In - * that method, subclasses will use use dequeue_next_key() method to get the - * key that it will use to collect the metadata, collect the appropriate - * metadata, and call the store_metadata() method to save the metadata. The - * run_impl() method should continue to dequeue and process metadata while the - * queue_size() method returns non-zero. + * metadata source. Subclasses will override the the run_impl() method and + * implement the concrete metadata lookup behavior. In that method, subclasses + * will use use dequeue_next_key() method to get the key that it will use to + * collect the metadata, collect the appropriate metadata, and call the + * store_metadata() method to save the metadata. The run_impl() method should + * continue to dequeue and process metadata while the queue_size() method + * returns non-zero. * * The constructor for this class accepts a maximum wait time; this specifies * how long client code is willing to wait for a synchronous response (i.e., @@ -50,15 +51,17 @@ namespace sysdig * the client supplied a handler in the call to lookup(), then that handler * will be invoked by the async_metadata_source once the metadata has been * collected. Note that the callback handler will be invoked in the context - * of the asynchronous thread associated with the async_metadata_source. (2) If - * the client did not supply a handler, then the metadata will be stored, and the - * next call to the lookup() method with the same key will return the previously - * collected metadata. + * of the asynchronous thread associated with the async_metadata_source. + * (2) If the client did not supply a handler, then the metadata will be stored, + * and the next call to the lookup() method with the same key will return the + * previously collected metadata. If lookup() is not called with the specified + * ttl time, then this compoment will prune the stored metadata. * * @tparam key_type The type of the keys for which concrete subclasses will - * query. + * query. This type must have a valid operator==(). * @tparam metadata_type The type of metadata that concrete subclasses will - * receive from a query. + * receive from a query. This type must have a valid + * operator=(). */ template class async_metadata_source @@ -135,8 +138,16 @@ class async_metadata_source const callback_handler& handler = callback_handler()); /** - * @returns true if the async thread assocaited with this - * async_metadata_source is running, false otherwise. + * Determines if the async thread assocaited with this + * async_metadata_source is running. + * + * Note: This API is for information only. Clients should + * not use this to implement any sort of complex behavior. Such + * use will lead to race conditions. For example, is_running() and + * lookup() could potentially race, causing is_running() to return + * false after lookup() has started the thread. + * + * @returns true if the async thread is running, false otherwise. */ bool is_running() const; diff --git a/userspace/libsinsp/async_docker_metadata_source.cpp b/userspace/libsinsp/async_docker_metadata_source.cpp index 8a34608f8c..d0443127c9 100644 --- a/userspace/libsinsp/async_docker_metadata_source.cpp +++ b/userspace/libsinsp/async_docker_metadata_source.cpp @@ -112,7 +112,6 @@ bool async_docker_metadata_source::parse_docker(sinsp_container_manager* const m const bool parsingSuccessful = reader.parse(json, root); if(!parsingSuccessful) { - g_logger.log("Parsing unsuccessful", sinsp_logger::SEV_ERROR); ASSERT(false); return false; } @@ -301,15 +300,6 @@ bool async_docker_metadata_source::parse_docker(sinsp_container_manager* const m } } -// TODO: Need to factor this out and get rid of the CYGWING_AGENT check -#ifndef CYGWING_AGENT - // FIXME: Should we move this outside somewhere? - //if (sinsp_container_engine_mesos::set_mesos_task_id(container, tinfo)) - //{ - // g_logger.log("Mesos Docker container: [" + root["Id"].asString() + "], Mesos task ID: [" + container->m_mesos_task_id + ']', sinsp_logger::SEV_DEBUG); - //} -#endif - const auto& host_config_obj = root["HostConfig"]; container->m_memory_limit = host_config_obj["Memory"].asInt64(); container->m_swap_limit = host_config_obj["MemorySwap"].asInt64(); @@ -336,7 +326,6 @@ bool async_docker_metadata_source::parse_docker(sinsp_container_manager* const m sinsp_utils::find_env(container->m_sysdig_agent_conf, container->get_env(), "SYSDIG_AGENT_CONF"); // container->m_sysdig_agent_conf = get_docker_env(env_vars, "SYSDIG_AGENT_CONF"); #endif - g_logger.log("EXIT: parse_docker"); return true; } diff --git a/userspace/libsinsp/async_linux_docker_metadata_source.cpp b/userspace/libsinsp/async_linux_docker_metadata_source.cpp index 0228314522..c8a2758de7 100644 --- a/userspace/libsinsp/async_linux_docker_metadata_source.cpp +++ b/userspace/libsinsp/async_linux_docker_metadata_source.cpp @@ -85,7 +85,8 @@ sinsp_docker_response async_linux_docker_metadata_source::get_docker( } catch(const std::exception& ex) { - g_logger.log(std::string("Failed to fetch URL: ") + ex.what()); + g_logger.log(std::string("Failed to fetch URL: ") + ex.what(), + sinsp_logger::SEV_WARNING); ASSERT(false); response = sinsp_docker_response::RESP_ERROR; } diff --git a/userspace/libsinsp/container.cpp b/userspace/libsinsp/container.cpp index a9ec3c9952..f83951154d 100644 --- a/userspace/libsinsp/container.cpp +++ b/userspace/libsinsp/container.cpp @@ -305,7 +305,21 @@ bool sinsp_container_engine_docker::resolve(sinsp_container_manager* manager, si // backend. if(s_docker_metadata->lookup(tinfo->m_container_id, metadata)) { - manager->add_container(*metadata.m_container_info, tinfo); +#ifndef CYGWING_AGENT + if(sinsp_container_engine_mesos::set_mesos_task_id( + metadata.m_container_info.get(), + tinfo)) + { + g_logger.log("Mesos Docker container: [" + + metadata.m_container_info->m_id + + "], Mesos task ID: [" + + metadata.m_container_info->m_mesos_task_id + + ']', sinsp_logger::SEV_DEBUG); + } +#endif + + manager->add_container(*metadata.m_container_info, + tinfo); manager->notify_new_container(*metadata.m_container_info); return true; From 6f32690f52fc2a7959db6984e5336b9ff7f8a244 Mon Sep 17 00:00:00 2001 From: Andy Dalton Date: Mon, 14 Jan 2019 14:02:42 -0500 Subject: [PATCH 10/14] Address Zipper's comments --- ...e.ut.cpp => async_key_value_source.ut.cpp} | 35 ++--- ...data_source.h => async_key_value_source.h} | 140 +++++++++++++----- ..._source.tpp => async_key_value_source.tpp} | 30 ++-- .../libsinsp/async_docker_metadata_source.cpp | 4 +- .../libsinsp/async_docker_metadata_source.h | 8 +- 5 files changed, 139 insertions(+), 78 deletions(-) rename test/userspace/async/{async_metadata_source.ut.cpp => async_key_value_source.ut.cpp} (84%) rename userspace/async/{async_metadata_source.h => async_key_value_source.h} (58%) rename userspace/async/{async_metadata_source.tpp => async_key_value_source.tpp} (85%) diff --git a/test/userspace/async/async_metadata_source.ut.cpp b/test/userspace/async/async_key_value_source.ut.cpp similarity index 84% rename from test/userspace/async/async_metadata_source.ut.cpp rename to test/userspace/async/async_key_value_source.ut.cpp index 8c1f681363..a6d64fe9eb 100644 --- a/test/userspace/async/async_metadata_source.ut.cpp +++ b/test/userspace/async/async_key_value_source.ut.cpp @@ -16,7 +16,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -#include "async_metadata_source.h" +#include "async_key_value_source.h" #include #include @@ -27,16 +27,17 @@ namespace { /** - * Intermediate realization of async_metadata_source that can return pre-canned + * Intermediate realization of async_key_value_source that can return pre-canned * results. */ -class precanned_metadata_source : public async_metadata_source +class precanned_metadata_source : public async_key_value_source { public: const static uint64_t FOREVER_MS; - precanned_metadata_source(const uint64_t max_wait_ms, const uint64_t ttl_ms = FOREVER_MS) - : async_metadata_source(max_wait_ms, ttl_ms), + precanned_metadata_source(const uint64_t max_wait_ms, + const uint64_t ttl_ms = FOREVER_MS) + : async_key_value_source(max_wait_ms, ttl_ms), m_responses() { } @@ -56,7 +57,7 @@ class precanned_metadata_source : public async_metadata_source(~0L); /** - * Realization of async_metadata_source that returns results without delay. + * Realization of async_key_value_source that returns results without delay. */ class immediate_metadata_source : public precanned_metadata_source { @@ -80,7 +81,7 @@ class immediate_metadata_source : public precanned_metadata_source const uint64_t immediate_metadata_source::MAX_WAIT_TIME_MS = 5000; /** - * Realization of async_metadata_source that returns results with some + * Realization of async_key_value_source that returns results with some * specified delay. */ class delayed_metadata_source : public precanned_metadata_source @@ -115,10 +116,10 @@ const uint64_t delayed_metadata_source::MAX_WAIT_TIME_MS = 0; } /** - * Ensure that a concrete async_metadata_source is in the expected initial state - * after construction. + * Ensure that a concrete async_key_value_source is in the expected initial + * state after construction. */ -TEST(async_metadata_source_test, construction) +TEST(async_key_value_source_test, construction) { immediate_metadata_source source; @@ -128,11 +129,11 @@ TEST(async_metadata_source_test, construction) } /** - * Ensure that if a concrete async_metadata_source returns the metadata before + * Ensure that if a concrete async_key_value_source returns the metadata before * the timeout, that the lookup() method returns true, and that it returns * the metadata in the output parameter. */ -TEST(async_metadata_source_test, lookup_key_immediate_return) +TEST(async_key_value_source_test, lookup_key_immediate_return) { const std::string key = "foo"; const std::string metadata = "bar"; @@ -149,11 +150,11 @@ TEST(async_metadata_source_test, lookup_key_immediate_return) } /** - * Ensure that if a concrete async_metadata_source cannot return the result + * Ensure that if a concrete async_key_value_source cannot return the result * before the timeout, and if the client did not provide a callback, that * calling lookup() after the result it available returns the value. */ -TEST(async_metadata_source_test, lookup_key_delayed_return_second_call) +TEST(async_key_value_source_test, lookup_key_delayed_return_second_call) { const uint64_t DELAY_MS = 50; const std::string key = "mykey"; @@ -185,11 +186,11 @@ TEST(async_metadata_source_test, lookup_key_delayed_return_second_call) } /** - * Ensure that if a concrete async_metadata_source cannot return the result + * Ensure that if a concrete async_key_value_source cannot return the result * before the timeout, and if the client did provide a callback, that the * callback is invoked with the metadata once they're avaialble. */ -TEST(async_metadata_source_test, look_key_delayed_async_callback) +TEST(async_key_value_source_test, look_key_delayed_async_callback) { const uint64_t DELAY_MS = 50; const std::string key = "mykey"; @@ -225,7 +226,7 @@ TEST(async_metadata_source_test, look_key_delayed_async_callback) /** * Ensure that "old" results are pruned */ -TEST(async_metadata_source_test, prune_old_metadata) +TEST(async_key_value_source_test, prune_old_metadata) { const uint64_t DELAY_MS = 0; const uint64_t TTL_MS = 20; diff --git a/userspace/async/async_metadata_source.h b/userspace/async/async_key_value_source.h similarity index 58% rename from userspace/async/async_metadata_source.h rename to userspace/async/async_key_value_source.h index 5088f9aef9..9f9a28648a 100644 --- a/userspace/async/async_metadata_source.h +++ b/userspace/async/async_key_value_source.h @@ -43,19 +43,24 @@ namespace sysdig * The constructor for this class accepts a maximum wait time; this specifies * how long client code is willing to wait for a synchronous response (i.e., * how long the lookup() method will block waiting for the requested metadata). - * If the async_metadata_source is able to collect the requested metadata within - * that time period, then the lookup() method will return them. + * If the async_key_value_source is able to collect the requested metadata + * within that time period, then the lookup() method will return them. * * If the lookup() method is unable to collect the requested metadata within - * the requested time period, then one of two things will happen. (1) If - * the client supplied a handler in the call to lookup(), then that handler - * will be invoked by the async_metadata_source once the metadata has been - * collected. Note that the callback handler will be invoked in the context - * of the asynchronous thread associated with the async_metadata_source. - * (2) If the client did not supply a handler, then the metadata will be stored, - * and the next call to the lookup() method with the same key will return the - * previously collected metadata. If lookup() is not called with the specified - * ttl time, then this compoment will prune the stored metadata. + * the requested time period, then one of two things will happen. + * + *
    + *
  1. If the client supplied a callback handler in the call to lookup(), then + * that callback handler will be invoked by the async_key_value_source once + * the metadata has been collected. Note that the callback handler will be + * invoked in the context of the asynchronous thread associated with the + * async_key_value_source.
  2. + *
  3. If the client did not supply a handler, then the metadata will be stored, + * and the next call to the lookup() method with the same key will return + * the previously collected metadata. If lookup() is not called with the + * specified ttl time, then this compoment will prune the stored + * metadata.
  4. + *
* * @tparam key_type The type of the keys for which concrete subclasses will * query. This type must have a valid operator==(). @@ -64,20 +69,20 @@ namespace sysdig * operator=(). */ template -class async_metadata_source +class async_key_value_source { public: /** * If provided to the constructor as max_wait_ms, then lookup will * not wait for a response. */ - const static uint64_t NO_LOOKUP_WAIT = 0; + const static uint64_t NO_WAIT_LOOKUP = 0; typedef std::function callback_handler; /** - * Initialize this new async_metadata_source, which will block + * Initialize this new async_key_value_source, which will block * synchronously for the given max_wait_ms for metadata collection. * * @param[in] max_wait_ms The maximum amount of time that client code @@ -88,13 +93,13 @@ class async_metadata_source * result will live before being considered * "too old" and being pruned. */ - async_metadata_source(uint64_t max_wait_ms, uint64_t ttl_ms); + async_key_value_source(uint64_t max_wait_ms, uint64_t ttl_ms); - async_metadata_source(const async_metadata_source&) = delete; - async_metadata_source(async_metadata_source&&) = delete; - async_metadata_source& operator=(const async_metadata_source&) = delete; + async_key_value_source(const async_key_value_source&) = delete; + async_key_value_source(async_key_value_source&&) = delete; + async_key_value_source& operator=(const async_key_value_source&) = delete; - virtual ~async_metadata_source(); + virtual ~async_key_value_source(); /** * Returns the maximum amount of time, in milliseconds, that a call to @@ -113,22 +118,25 @@ class async_metadata_source * the caller for up the max_wait_ms time specified at construction * for the desired metadata to be available. * - * @param[in] key The key to the metadata for which the client wishes - * to query. + * @param[in] key The key to the metadata for which the client + * wishs to query. * @param[out] metadata If this method is able to fetch the desired - * metadata within the max_wait_ms specified at - * construction time, then this output parameter will - * contain the collected metadata. The value of this - * parameter is defined only if this method returns - * true. - * @param[in] handler If this method is unable to collect the requested - * metadata before the timeout, and if this parameter - * is a valid, non-empty, function, then this class - * will invoke the given handler from the async - * thread immediately after the collected metadata - * are available. If this handler is empty, then - * this async_metadata_source will store the metadata - * and return them on the next call to lookup(). + * metadata within the max_wait_ms specified at + * construction time, then this output parameter + * will contain the collected metadata. The value + * of this parameter is defined only if this method + * returns true. + * @param[in] handler If this method is unable to collect the requested + * metadata before the timeout, and if this parameter + * is a valid, non-empty, function, then this class + * will invoke the given handler from the async + * thread immediately after the collected metadata + * are available. If this handler is empty, then + * this async_key_value_source will store the + * metadata until either the next call to lookup() + * or until its ttl expires, whichever comes first. + * The handler is responsible for any thread-safety + * guarantees. * * @returns true if this method was able to lookup and return the * metadata synchronously; false otherwise. @@ -139,7 +147,7 @@ class async_metadata_source /** * Determines if the async thread assocaited with this - * async_metadata_source is running. + * async_key_value_source is running. * * Note: This API is for information only. Clients should * not use this to implement any sort of complex behavior. Such @@ -153,8 +161,10 @@ class async_metadata_source protected: /** - * Stops the thread assocaited with this async_metadata_source, if - * it is running. + * Stops the thread assocaited with this async_key_value_source, if + * it is running; otherwise, does nothing. The only use for this is + * in a destructor to ensure that the async thread stops when the + * object is destroyed. */ void stop(); @@ -163,6 +173,16 @@ class async_metadata_source * subclasses will call this methods from their run_impl() methods to * determine if there is more asynchronous work from them to perform. * + * The only client of this API is the run_impl() method of concrete + * subclasses running within the context of the async thread. That + * thread is the only thread that can decrease the value returned by + * this queue_size(). + * + * It is possible for there to be a race between the async thread + * calling this and some other thread calling lookup(). In that case, + * the key stored in the queue will be processed by the next call to + * lookup(). + * * @returns the size of the request queue. */ std::size_t queue_size() const; @@ -178,6 +198,13 @@ class async_metadata_source */ key_type dequeue_next_key(); + /** + * Get the metadata for the given key. + * + * @param[in] key The key whose metadata is needed. + * + * @returns the metadata associated with the given key. + */ metadata_type get_metadata(const key_type& key); /** @@ -192,11 +219,24 @@ class async_metadata_source /** * Concrete subclasses must override this method to perform the - * asynchronous metadata lookup. + * asynchronous metadata lookup. The implementation should: + * + *
    + *
  • Loop while queue_size() is non-zero.
  • + *
  • Dequeue the next key to process using dequeue_next_key()
  • + *
  • Get any existing metadata for thaty key using get_metadata()
  • + *
  • Do whatever work is necessary to lookup the metadata associated + * with that key.
  • + *
  • Call store_metadata to store the updated metadata, and to + * notify any client code waiting on that data.
  • + *
*/ virtual void run_impl() = 0; private: + /** + * Holds information associated with a single lookup() request. + */ struct lookup_request { lookup_request(): @@ -207,16 +247,36 @@ class async_metadata_source m_start_time(std::chrono::steady_clock::now()) { } + /** Is the metadata here available? */ bool m_available; + + /** The metadata for a key. */ metadata_type m_metadata; + + /** Block in lookup() waiting for a sync response. */ std::condition_variable m_available_condition; - callback_handler m_callback; // TODO: This may need to be a list + + /** + * A optional client-specified callback handler for async + * response notification. + */ + callback_handler m_callback; + + /** The time at which this request was made. */ std::chrono::time_point m_start_time; }; typedef std::map metadata_map; + /** + * The entry point of the async thread, which blocks waiting for work + * and dispatches work to run_impl(). + */ void run(); + + /** + * Remove any entries that are older than the time-to-live. + */ void prune_stale_requests(); uint64_t m_max_wait_ms; @@ -234,4 +294,4 @@ class async_metadata_source } // end namespace sysdig -#include "async_metadata_source.tpp" +#include "async_key_value_source.tpp" diff --git a/userspace/async/async_metadata_source.tpp b/userspace/async/async_key_value_source.tpp similarity index 85% rename from userspace/async/async_metadata_source.tpp rename to userspace/async/async_key_value_source.tpp index 6aff8e012a..29b02ebd77 100644 --- a/userspace/async/async_metadata_source.tpp +++ b/userspace/async/async_key_value_source.tpp @@ -29,7 +29,7 @@ namespace sysdig { template -async_metadata_source::async_metadata_source( +async_key_value_source::async_key_value_source( const uint64_t max_wait_ms, const uint64_t ttl_ms): m_max_wait_ms(max_wait_ms), @@ -43,7 +43,7 @@ async_metadata_source::async_metadata_source( { } template -async_metadata_source::~async_metadata_source() +async_key_value_source::~async_key_value_source() { try { @@ -58,19 +58,19 @@ async_metadata_source::~async_metadata_source() } template -uint64_t async_metadata_source::get_max_wait() const +uint64_t async_key_value_source::get_max_wait() const { return m_max_wait_ms; } template -uint64_t async_metadata_source::get_ttl() const +uint64_t async_key_value_source::get_ttl() const { return m_ttl_ms; } template -void async_metadata_source::stop() +void async_key_value_source::stop() { bool join_needed = false; @@ -99,7 +99,7 @@ void async_metadata_source::stop() } template -bool async_metadata_source::is_running() const +bool async_key_value_source::is_running() const { std::lock_guard guard(m_mutex); @@ -107,7 +107,7 @@ bool async_metadata_source::is_running() const } template -void async_metadata_source::run() +void async_key_value_source::run() { m_running = true; @@ -135,16 +135,16 @@ void async_metadata_source::run() } template -bool async_metadata_source::lookup( +bool async_key_value_source::lookup( const key_type& key, metadata_type& metadata, const callback_handler& callback) { std::unique_lock guard(m_mutex); - if(!m_running) + if(!m_running && !m_thread.joinable()) { - m_thread = std::thread(&async_metadata_source::run, this); + m_thread = std::thread(&async_key_value_source::run, this); } typename metadata_map::const_iterator itr = m_metadata_map.find(key); @@ -202,14 +202,14 @@ bool async_metadata_source::lookup( } template -std::size_t async_metadata_source::queue_size() const +std::size_t async_key_value_source::queue_size() const { std::lock_guard guard(m_mutex); return m_request_queue.size(); } template -key_type async_metadata_source::dequeue_next_key() +key_type async_key_value_source::dequeue_next_key() { std::lock_guard guard(m_mutex); key_type key = m_request_queue.front(); @@ -220,7 +220,7 @@ key_type async_metadata_source::dequeue_next_key() } template -metadata_type async_metadata_source::get_metadata( +metadata_type async_key_value_source::get_metadata( const key_type& key) { std::lock_guard guard(m_mutex); @@ -229,7 +229,7 @@ metadata_type async_metadata_source::get_metadata( } template -void async_metadata_source::store_metadata( +void async_key_value_source::store_metadata( const key_type& key, const metadata_type& metadata) { @@ -253,7 +253,7 @@ void async_metadata_source::store_metadata( * is holding m_mutex. */ template -void async_metadata_source::prune_stale_requests() +void async_key_value_source::prune_stale_requests() { // Avoid both iterating over and modifying the map by saving a list // of keys to prune. diff --git a/userspace/libsinsp/async_docker_metadata_source.cpp b/userspace/libsinsp/async_docker_metadata_source.cpp index d0443127c9..277aff1d74 100644 --- a/userspace/libsinsp/async_docker_metadata_source.cpp +++ b/userspace/libsinsp/async_docker_metadata_source.cpp @@ -39,8 +39,8 @@ using namespace sysdig; async_docker_metadata_source::async_docker_metadata_source( const std::string& api_version, const bool query_image_info): - async_metadata_source(NO_LOOKUP_WAIT, - MAX_TTL_MS), + async_key_value_source(NO_WAIT_LOOKUP, + MAX_TTL_MS), m_query_image_info(query_image_info), m_api_version(api_version) { } diff --git a/userspace/libsinsp/async_docker_metadata_source.h b/userspace/libsinsp/async_docker_metadata_source.h index cc878742d6..8625bb0845 100644 --- a/userspace/libsinsp/async_docker_metadata_source.h +++ b/userspace/libsinsp/async_docker_metadata_source.h @@ -18,7 +18,7 @@ limitations under the License. */ #pragma once -#include "async_metadata_source.h" +#include "async_key_value_source.h" #include "sinsp.h" #include "container.h" @@ -47,10 +47,10 @@ struct docker_metadata }; /** - * Interface to async_docker_metadata_source -- an abstract async_metadata_source + * Interface to async_docker_metadata_source -- an abstract async_key_value_source * for fetching docker metadata and metadata. */ -class async_docker_metadata_source : public async_metadata_source +class async_docker_metadata_source : public async_key_value_source { public: /** @@ -61,7 +61,7 @@ class async_docker_metadata_source : public async_metadata_source Date: Mon, 14 Jan 2019 16:21:20 -0500 Subject: [PATCH 11/14] Address some more of Zipper's comments --- .../async/async_key_value_source.ut.cpp | 4 +- userspace/async/async_key_value_source.h | 113 +++++++++--------- userspace/async/async_key_value_source.tpp | 106 ++++++++-------- .../libsinsp/async_docker_metadata_source.cpp | 4 +- 4 files changed, 113 insertions(+), 114 deletions(-) diff --git a/test/userspace/async/async_key_value_source.ut.cpp b/test/userspace/async/async_key_value_source.ut.cpp index a6d64fe9eb..0e7639333b 100644 --- a/test/userspace/async/async_key_value_source.ut.cpp +++ b/test/userspace/async/async_key_value_source.ut.cpp @@ -74,7 +74,7 @@ class immediate_metadata_source : public precanned_metadata_source while(queue_size() > 0) { const std::string key = dequeue_next_key(); - store_metadata(key, get_response(key)); + store_value(key, get_response(key)); } } }; @@ -104,7 +104,7 @@ class delayed_metadata_source : public precanned_metadata_source std::this_thread::sleep_for(std::chrono::milliseconds(m_delay_ms)); - store_metadata(key, get_response(key)); + store_value(key, get_response(key)); } } diff --git a/userspace/async/async_key_value_source.h b/userspace/async/async_key_value_source.h index 9f9a28648a..55ec1c9be8 100644 --- a/userspace/async/async_key_value_source.h +++ b/userspace/async/async_key_value_source.h @@ -31,44 +31,43 @@ namespace sysdig { /** - * Base class for classes that need to collect metadata asynchronously from some - * metadata source. Subclasses will override the the run_impl() method and - * implement the concrete metadata lookup behavior. In that method, subclasses + * Base class for classes that need to collect values asynchronously from some + * value source. Subclasses will override the the run_impl() method and + * implement the concrete value lookup behavior. In that method, subclasses * will use use dequeue_next_key() method to get the key that it will use to - * collect the metadata, collect the appropriate metadata, and call the - * store_metadata() method to save the metadata. The run_impl() method should - * continue to dequeue and process metadata while the queue_size() method + * collect the value(s), collect the appropriate value(s), and call the + * store_value() method to save the value. The run_impl() method should + * continue to dequeue and process values while the queue_size() method * returns non-zero. * * The constructor for this class accepts a maximum wait time; this specifies * how long client code is willing to wait for a synchronous response (i.e., - * how long the lookup() method will block waiting for the requested metadata). - * If the async_key_value_source is able to collect the requested metadata + * how long the lookup() method will block waiting for the requested value). + * If the async_key_value_source is able to collect the requested value * within that time period, then the lookup() method will return them. * - * If the lookup() method is unable to collect the requested metadata within + * If the lookup() method is unable to collect the requested value within * the requested time period, then one of two things will happen. * *
    *
  1. If the client supplied a callback handler in the call to lookup(), then * that callback handler will be invoked by the async_key_value_source once - * the metadata has been collected. Note that the callback handler will be + * the value has been collected. Note that the callback handler will be * invoked in the context of the asynchronous thread associated with the * async_key_value_source.
  2. - *
  3. If the client did not supply a handler, then the metadata will be stored, + *
  4. If the client did not supply a handler, then the value will be stored, * and the next call to the lookup() method with the same key will return - * the previously collected metadata. If lookup() is not called with the - * specified ttl time, then this compoment will prune the stored - * metadata.
  5. + * the previously collected value. If lookup() is not called with the + * specified ttl time, then this compoment will prune the stored value. *
* - * @tparam key_type The type of the keys for which concrete subclasses will - * query. This type must have a valid operator==(). - * @tparam metadata_type The type of metadata that concrete subclasses will - * receive from a query. This type must have a valid - * operator=(). + * @tparam key_type The type of the keys for which concrete subclasses will + * query. This type must have a valid operator==(). + * @tparam value_type The type of value that concrete subclasses will + * receive from a query. This type must have a valid + * operator=(). */ -template +template class async_key_value_source { public: @@ -79,18 +78,18 @@ class async_key_value_source const static uint64_t NO_WAIT_LOOKUP = 0; typedef std::function callback_handler; + const value_type& value)> callback_handler; /** * Initialize this new async_key_value_source, which will block - * synchronously for the given max_wait_ms for metadata collection. + * synchronously for the given max_wait_ms for value collection. * * @param[in] max_wait_ms The maximum amount of time that client code * is willing to wait for lookup() to collect - * metadata before falling back to an async + * a value before falling back to an async * return. * @param[in] ttl_ms The time, in milliseconds, that a cached - * result will live before being considered + * value will live before being considered * "too old" and being pruned. */ async_key_value_source(uint64_t max_wait_ms, uint64_t ttl_ms); @@ -109,40 +108,40 @@ class async_key_value_source /** * Returns the maximum amount of time, in milliseconds, that a cached - * metadata result will live before being pruned. + * value will live before being pruned. */ uint64_t get_ttl() const; /** - * Lookup metadata based on the given key. This method will block + * Lookup value(s) based on the given key. This method will block * the caller for up the max_wait_ms time specified at construction - * for the desired metadata to be available. + * for the desired value(s) to be available. * - * @param[in] key The key to the metadata for which the client + * @param[in] key The key to the value for which the client * wishs to query. - * @param[out] metadata If this method is able to fetch the desired - * metadata within the max_wait_ms specified at + * @param[out] value If this method is able to fetch the desired + * value within the max_wait_ms specified at * construction time, then this output parameter - * will contain the collected metadata. The value + * will contain the collected value. The value * of this parameter is defined only if this method * returns true. * @param[in] handler If this method is unable to collect the requested - * metadata before the timeout, and if this parameter + * value(s) before the timeout, and if this parameter * is a valid, non-empty, function, then this class * will invoke the given handler from the async - * thread immediately after the collected metadata + * thread immediately after the collected values * are available. If this handler is empty, then * this async_key_value_source will store the - * metadata until either the next call to lookup() + * values until either the next call to lookup() * or until its ttl expires, whichever comes first. * The handler is responsible for any thread-safety * guarantees. * * @returns true if this method was able to lookup and return the - * metadata synchronously; false otherwise. + * value synchronously; false otherwise. */ bool lookup(const key_type& key, - metadata_type& metadata, + value_type& value, const callback_handler& handler = callback_handler()); /** @@ -190,7 +189,7 @@ class async_key_value_source /** * Dequeues an entry from the request queue and returns it. Concrete * subclasses will call this method to get the next key for which - * to collect metadata. + * to collect values. * * Precondition: queue_size() must be non-zero. * @@ -199,35 +198,35 @@ class async_key_value_source key_type dequeue_next_key(); /** - * Get the metadata for the given key. + * Get the (potentially partial) value for the given key. * - * @param[in] key The key whose metadata is needed. + * @param[in] key The key whose value is needed. * - * @returns the metadata associated with the given key. + * @returns the value associated with the given key. */ - metadata_type get_metadata(const key_type& key); + value_type get_value(const key_type& key); /** - * Stores a collected set of metadata for the given key. Concrete - * subclasses will call this method from their run_impl() method to - * save (or otherwise notifiy the client about) a collected metadata. + * Stores a value for the given key. Concrete subclasses will call + * this method from their run_impl() method to save (or otherwise + * notifiy the client about) an available value. * - * @param[in] key The key for which the client asked for metadata. - * @param[in] metadata The collected metadata. + * @param[in] key The key for which the client asked for the value. + * @param[in] value The collected value. */ - void store_metadata(const key_type& key, const metadata_type& metadata); + void store_value(const key_type& key, const value_type& value); /** * Concrete subclasses must override this method to perform the - * asynchronous metadata lookup. The implementation should: + * asynchronous value lookup. The implementation should: * *
    *
  • Loop while queue_size() is non-zero.
  • *
  • Dequeue the next key to process using dequeue_next_key()
  • - *
  • Get any existing metadata for thaty key using get_metadata()
  • - *
  • Do whatever work is necessary to lookup the metadata associated + *
  • Get any existing value for that key using get_value()
  • + *
  • Do whatever work is necessary to lookup the value associated * with that key.
  • - *
  • Call store_metadata to store the updated metadata, and to + *
  • Call store_value to store the updated value, and to * notify any client code waiting on that data.
  • *
*/ @@ -241,17 +240,17 @@ class async_key_value_source { lookup_request(): m_available(false), - m_metadata(), + m_value(), m_available_condition(), m_callback(), m_start_time(std::chrono::steady_clock::now()) { } - /** Is the metadata here available? */ + /** Is the value here available? */ bool m_available; - /** The metadata for a key. */ - metadata_type m_metadata; + /** The value for a key. */ + value_type m_value; /** Block in lookup() waiting for a sync response. */ std::condition_variable m_available_condition; @@ -266,7 +265,7 @@ class async_key_value_source std::chrono::time_point m_start_time; }; - typedef std::map metadata_map; + typedef std::map value_map; /** * The entry point of the async thread, which blocks waiting for work @@ -288,7 +287,7 @@ class async_key_value_source std::condition_variable m_start_condition; std::condition_variable m_queue_not_empty_condition; std::list m_request_queue; - metadata_map m_metadata_map; + value_map m_value_map; }; diff --git a/userspace/async/async_key_value_source.tpp b/userspace/async/async_key_value_source.tpp index 29b02ebd77..d4053fa281 100644 --- a/userspace/async/async_key_value_source.tpp +++ b/userspace/async/async_key_value_source.tpp @@ -28,8 +28,8 @@ limitations under the License. namespace sysdig { -template -async_key_value_source::async_key_value_source( +template +async_key_value_source::async_key_value_source( const uint64_t max_wait_ms, const uint64_t ttl_ms): m_max_wait_ms(max_wait_ms), @@ -39,11 +39,11 @@ async_key_value_source::async_key_value_source( m_terminate(false), m_mutex(), m_queue_not_empty_condition(), - m_metadata_map() + m_value_map() { } -template -async_key_value_source::~async_key_value_source() +template +async_key_value_source::~async_key_value_source() { try { @@ -57,20 +57,20 @@ async_key_value_source::~async_key_value_source() } } -template -uint64_t async_key_value_source::get_max_wait() const +template +uint64_t async_key_value_source::get_max_wait() const { return m_max_wait_ms; } -template -uint64_t async_key_value_source::get_ttl() const +template +uint64_t async_key_value_source::get_ttl() const { return m_ttl_ms; } -template -void async_key_value_source::stop() +template +void async_key_value_source::stop() { bool join_needed = false; @@ -98,16 +98,17 @@ void async_key_value_source::stop() } } -template -bool async_key_value_source::is_running() const +template +bool async_key_value_source::is_running() const { - std::lock_guard guard(m_mutex); + // Since this is for information only and it's ok to race, we + // expliclty do not lock here. return m_running; } -template -void async_key_value_source::run() +template +void async_key_value_source::run() { m_running = true; @@ -134,10 +135,10 @@ void async_key_value_source::run() m_running = false; } -template -bool async_key_value_source::lookup( +template +bool async_key_value_source::lookup( const key_type& key, - metadata_type& metadata, + value_type& value, const callback_handler& callback) { std::unique_lock guard(m_mutex); @@ -147,16 +148,16 @@ bool async_key_value_source::lookup( m_thread = std::thread(&async_key_value_source::run, this); } - typename metadata_map::const_iterator itr = m_metadata_map.find(key); - bool request_complete = (itr != m_metadata_map.end()) && itr->second.m_available; + typename value_map::const_iterator itr = m_value_map.find(key); + bool request_complete = (itr != m_value_map.end()) && itr->second.m_available; if(!request_complete) { // Haven't made the request yet - if (itr == m_metadata_map.end()) + if (itr == m_value_map.end()) { - m_metadata_map[key].m_available = false; - m_metadata_map[key].m_metadata = metadata; + m_value_map[key].m_available = false; + m_value_map[key].m_value = value; } // Make request to API and let the async thread know about it @@ -173,43 +174,42 @@ bool async_key_value_source::lookup( // to satisfy the request, then wait for the async thread to // pick up the newly-added request and execute it. If // processing that request takes too much time, then we'll - // not be able to return the metadata information on this call, + // not be able to return the value information on this call, // and the async thread will continue handling the request so // that it'll be available on the next call. // if (m_max_wait_ms > 0) { - m_metadata_map[key].m_available_condition.wait_for( + m_value_map[key].m_available_condition.wait_for( guard, std::chrono::milliseconds(m_max_wait_ms)); - itr = m_metadata_map.find(key); - request_complete = (itr != m_metadata_map.end()) && itr->second.m_available; + itr = m_value_map.find(key); + request_complete = (itr != m_value_map.end()) && itr->second.m_available; } } if(request_complete) { - metadata = itr->second.m_metadata; - m_metadata_map.erase(key); + value = itr->second.m_value; + m_value_map.erase(key); } else { - m_metadata_map[key].m_callback = callback; + m_value_map[key].m_callback = callback; } return request_complete; } -template -std::size_t async_key_value_source::queue_size() const +template +std::size_t async_key_value_source::queue_size() const { - std::lock_guard guard(m_mutex); return m_request_queue.size(); } -template -key_type async_key_value_source::dequeue_next_key() +template +key_type async_key_value_source::dequeue_next_key() { std::lock_guard guard(m_mutex); key_type key = m_request_queue.front(); @@ -219,32 +219,32 @@ key_type async_key_value_source::dequeue_next_key() return key; } -template -metadata_type async_key_value_source::get_metadata( +template +value_type async_key_value_source::get_value( const key_type& key) { std::lock_guard guard(m_mutex); - return m_metadata_map[key].m_metadata; + return m_value_map[key].m_value; } -template -void async_key_value_source::store_metadata( +template +void async_key_value_source::store_value( const key_type& key, - const metadata_type& metadata) + const value_type& value) { std::lock_guard guard(m_mutex); - if (m_metadata_map[key].m_callback) + if (m_value_map[key].m_callback) { - m_metadata_map[key].m_callback(key, metadata); - m_metadata_map.erase(key); + m_value_map[key].m_callback(key, value); + m_value_map.erase(key); } else { - m_metadata_map[key].m_metadata = metadata; - m_metadata_map[key].m_available = true; - m_metadata_map[key].m_available_condition.notify_one(); + m_value_map[key].m_value = value; + m_value_map[key].m_available = true; + m_value_map[key].m_available_condition.notify_one(); } } @@ -252,15 +252,15 @@ void async_key_value_source::store_metadata( * Prune any "old" outstanding requests. This method expect that the caller * is holding m_mutex. */ -template -void async_key_value_source::prune_stale_requests() +template +void async_key_value_source::prune_stale_requests() { // Avoid both iterating over and modifying the map by saving a list // of keys to prune. std::vector keys_to_prune; - for(auto i = m_metadata_map.begin(); - !m_terminate && (i != m_metadata_map.end()); + for(auto i = m_value_map.begin(); + !m_terminate && (i != m_value_map.end()); ++i) { const auto now = std::chrono::steady_clock::now(); @@ -279,7 +279,7 @@ void async_key_value_source::prune_stale_requests() !m_terminate && (i != keys_to_prune.end()); ++i) { - m_metadata_map.erase(*i); + m_value_map.erase(*i); } } diff --git a/userspace/libsinsp/async_docker_metadata_source.cpp b/userspace/libsinsp/async_docker_metadata_source.cpp index 277aff1d74..d707b2fc3a 100644 --- a/userspace/libsinsp/async_docker_metadata_source.cpp +++ b/userspace/libsinsp/async_docker_metadata_source.cpp @@ -65,12 +65,12 @@ void async_docker_metadata_source::run_impl() while(queue_size() > 0) { const std::string container_id = dequeue_next_key(); - docker_metadata metadata = get_metadata(container_id); + docker_metadata metadata = get_value(container_id); if(parse_docker(metadata.m_manager, metadata.m_container_info.get())) { - store_metadata(container_id, metadata); + store_value(container_id, metadata); } } } From 3bd7c14a3dc069c66d04dc7c30f1b2476a4ea199 Mon Sep 17 00:00:00 2001 From: Andy Dalton Date: Tue, 15 Jan 2019 12:38:29 -0500 Subject: [PATCH 12/14] Address Zipper's comments Eliminated the queue_size(). --- .../async/async_key_value_source.ut.cpp | 12 +++--- userspace/async/async_key_value_source.h | 38 ++++--------------- userspace/async/async_key_value_source.tpp | 20 +++++----- .../libsinsp/async_docker_metadata_source.cpp | 5 ++- 4 files changed, 27 insertions(+), 48 deletions(-) diff --git a/test/userspace/async/async_key_value_source.ut.cpp b/test/userspace/async/async_key_value_source.ut.cpp index 0e7639333b..f57816c3c4 100644 --- a/test/userspace/async/async_key_value_source.ut.cpp +++ b/test/userspace/async/async_key_value_source.ut.cpp @@ -71,9 +71,10 @@ class immediate_metadata_source : public precanned_metadata_source protected: virtual void run_impl() override { - while(queue_size() > 0) + std::string key; + + while(dequeue_next_key(key)) { - const std::string key = dequeue_next_key(); store_value(key, get_response(key)); } } @@ -98,12 +99,11 @@ class delayed_metadata_source : public precanned_metadata_source protected: virtual void run_impl() override { - while(queue_size() > 0) - { - const std::string key = dequeue_next_key(); + std::string key; + while(dequeue_next_key(key)) + { std::this_thread::sleep_for(std::chrono::milliseconds(m_delay_ms)); - store_value(key, get_response(key)); } } diff --git a/userspace/async/async_key_value_source.h b/userspace/async/async_key_value_source.h index 55ec1c9be8..60c4a80585 100644 --- a/userspace/async/async_key_value_source.h +++ b/userspace/async/async_key_value_source.h @@ -37,8 +37,8 @@ namespace sysdig * will use use dequeue_next_key() method to get the key that it will use to * collect the value(s), collect the appropriate value(s), and call the * store_value() method to save the value. The run_impl() method should - * continue to dequeue and process values while the queue_size() method - * returns non-zero. + * continue to dequeue and process values while the dequeue_next_key() method + * returns true. * * The constructor for this class accepts a maximum wait time; this specifies * how long client code is willing to wait for a synchronous response (i.e., @@ -168,34 +168,13 @@ class async_key_value_source void stop(); /** - * Returns the number of elements in the requeust queue. Concrete - * subclasses will call this methods from their run_impl() methods to - * determine if there is more asynchronous work from them to perform. + * Dequeues an entry from the request queue and returns it in the given + * key. Concrete subclasses will call this method to get the next key + * for which to collect values. * - * The only client of this API is the run_impl() method of concrete - * subclasses running within the context of the async thread. That - * thread is the only thread that can decrease the value returned by - * this queue_size(). - * - * It is possible for there to be a race between the async thread - * calling this and some other thread calling lookup(). In that case, - * the key stored in the queue will be processed by the next call to - * lookup(). - * - * @returns the size of the request queue. - */ - std::size_t queue_size() const; - - /** - * Dequeues an entry from the request queue and returns it. Concrete - * subclasses will call this method to get the next key for which - * to collect values. - * - * Precondition: queue_size() must be non-zero. - * - * @returns the next key to look up. + * @returns true if there was a key to dequeue, false otherwise. */ - key_type dequeue_next_key(); + bool dequeue_next_key(key_type& key); /** * Get the (potentially partial) value for the given key. @@ -221,8 +200,7 @@ class async_key_value_source * asynchronous value lookup. The implementation should: * *
    - *
  • Loop while queue_size() is non-zero.
  • - *
  • Dequeue the next key to process using dequeue_next_key()
  • + *
  • Loop while dequeue_next_key() is true.
  • *
  • Get any existing value for that key using get_value()
  • *
  • Do whatever work is necessary to lookup the value associated * with that key.
  • diff --git a/userspace/async/async_key_value_source.tpp b/userspace/async/async_key_value_source.tpp index d4053fa281..30e2d304e4 100644 --- a/userspace/async/async_key_value_source.tpp +++ b/userspace/async/async_key_value_source.tpp @@ -203,20 +203,20 @@ bool async_key_value_source::lookup( } template -std::size_t async_key_value_source::queue_size() const -{ - return m_request_queue.size(); -} - -template -key_type async_key_value_source::dequeue_next_key() +bool async_key_value_source::dequeue_next_key(key_type& key) { std::lock_guard guard(m_mutex); - key_type key = m_request_queue.front(); + bool key_found = false; - m_request_queue.pop_front(); + if(m_request_queue.size() > 0) + { + key_found = true; + + key = m_request_queue.front(); + m_request_queue.pop_front(); + } - return key; + return key_found; } template diff --git a/userspace/libsinsp/async_docker_metadata_source.cpp b/userspace/libsinsp/async_docker_metadata_source.cpp index d707b2fc3a..442cf8b8ce 100644 --- a/userspace/libsinsp/async_docker_metadata_source.cpp +++ b/userspace/libsinsp/async_docker_metadata_source.cpp @@ -62,9 +62,10 @@ void async_docker_metadata_source::set_query_image_info(const bool query_info) void async_docker_metadata_source::run_impl() { - while(queue_size() > 0) + std::string container_id; + + while(dequeue_next_key(container_id)) { - const std::string container_id = dequeue_next_key(); docker_metadata metadata = get_value(container_id); if(parse_docker(metadata.m_manager, From 21c55e7d48c314a926c697c4a552b3bcd15b0d73 Mon Sep 17 00:00:00 2001 From: Andy Dalton Date: Wed, 16 Jan 2019 14:42:31 -0500 Subject: [PATCH 13/14] Add set for more efficient searching --- userspace/async/async_key_value_source.h | 2 ++ userspace/async/async_key_value_source.tpp | 8 +++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/userspace/async/async_key_value_source.h b/userspace/async/async_key_value_source.h index 60c4a80585..8f20324002 100644 --- a/userspace/async/async_key_value_source.h +++ b/userspace/async/async_key_value_source.h @@ -24,6 +24,7 @@ limitations under the License. #include #include #include +#include #include #include @@ -265,6 +266,7 @@ class async_key_value_source std::condition_variable m_start_condition; std::condition_variable m_queue_not_empty_condition; std::list m_request_queue; + std::set m_request_set; value_map m_value_map; }; diff --git a/userspace/async/async_key_value_source.tpp b/userspace/async/async_key_value_source.tpp index 30e2d304e4..ed972e1d95 100644 --- a/userspace/async/async_key_value_source.tpp +++ b/userspace/async/async_key_value_source.tpp @@ -161,11 +161,12 @@ bool async_key_value_source::lookup( } // Make request to API and let the async thread know about it - if (std::find(m_request_queue.begin(), - m_request_queue.end(), - key) == m_request_queue.end()) + if (std::find(m_request_set.begin(), + m_request_set.end(), + key) == m_request_set.end()) { m_request_queue.push_back(key); + m_request_set.insert(key); m_queue_not_empty_condition.notify_one(); } @@ -214,6 +215,7 @@ bool async_key_value_source::dequeue_next_key(key_type& ke key = m_request_queue.front(); m_request_queue.pop_front(); + m_request_set.erase(key); } return key_found; From f850d3b426d13eddaacb7d08023bb5127d745e0f Mon Sep 17 00:00:00 2001 From: Andy Dalton Date: Wed, 16 Jan 2019 14:49:39 -0500 Subject: [PATCH 14/14] Remove TODO comment I wanted to put a struct type that is used as a template parameter inside the class in which it was used, but the compiler wasn't happy when I tried that. --- userspace/libsinsp/async_docker_metadata_source.h | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/userspace/libsinsp/async_docker_metadata_source.h b/userspace/libsinsp/async_docker_metadata_source.h index 8625bb0845..cc1fe0cf4c 100644 --- a/userspace/libsinsp/async_docker_metadata_source.h +++ b/userspace/libsinsp/async_docker_metadata_source.h @@ -25,22 +25,22 @@ limitations under the License. namespace sysdig { -// TODO: Can this be inside the class below? +/** + * The value_type that an async_docker_metadata_source's lookup() method + * will produce. + */ struct docker_metadata { docker_metadata(): m_manager(nullptr), m_container_info() - { - } + { } docker_metadata(sinsp_container_manager* const manager, std::shared_ptr& container_info): m_manager(manager), m_container_info(container_info) - { - } - + { } sinsp_container_manager* m_manager; std::shared_ptr m_container_info; @@ -50,7 +50,8 @@ struct docker_metadata * Interface to async_docker_metadata_source -- an abstract async_key_value_source * for fetching docker metadata and metadata. */ -class async_docker_metadata_source : public async_key_value_source +class async_docker_metadata_source : public async_key_value_source { public: /**