| #include "managed_store.hpp" | |
| #include <sys/types.h> | |
| #include <algorithm> | |
| #include <chrono> | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <exception> | |
| #include <fstream> | |
| #include <ios> | |
| #include <list> | |
| #include <memory> | |
| #include <optional> | |
| #include <queue> | |
| #include <span> | |
| #include <string> | |
| #include <string_view> | |
| #include <thread> | |
| #include <tuple> | |
| #include <utility> | |
| #include <vector> | |
| #include "absl/functional/any_invocable.h" | |
| #include "absl/log/log.h" | |
| #include "absl/status/status.h" | |
| #include "absl/strings/string_view.h" | |
| #include "absl/strings/substitute.h" | |
| #include "absl/synchronization/mutex.h" | |
| #include "boost/asio/io_context.hpp" | |
| #include "boost/asio/post.hpp" // NOLINT | |
| #include "boost/asio/steady_timer.hpp" | |
| #include "boost/callable_traits/return_type.hpp" | |
| #include "boost/system/detail/errc.hpp" | |
| #include "boost/system/detail/error_code.hpp" | |
| #include "boost/system/error_code.hpp" | |
| #include "subscription.h" | |
| #include "logging.hpp" | |
| #include "dbus_utility.hpp" | |
| #include <nlohmann/json.hpp> | |
| #include "tlbmc/hal/system_registry.h" | |
| #include "managed_store_clock.hpp" | |
| #include "managed_store_types.hpp" | |
| #include "sdbusplus/asio/connection.hpp" | |
| #include "sdbusplus/bus/match.hpp" | |
| #include "sdbusplus/message.hpp" | |
| #include "sdbusplus/message/native_types.hpp" | |
| // TODO:: move these to a separate file (future CL when we have more stores): | |
| namespace managedStore { | |
| namespace { | |
| void AppendWithSep(std::string& out, const char sep, const std::string& arg) { | |
| if (!out.empty()) out += sep; | |
| out += arg; | |
| } | |
| // join 'args' strings with a separator without creating any intermediate | |
| // objects, append to 'out' | |
| template <typename... Args> | |
| void AppendWithSep(std::string& out, const char sep, const std::string& arg, | |
| const Args&... args) { | |
| AppendWithSep(out, sep, arg); | |
| AppendWithSep(out, sep, args...); | |
| } | |
| ManagedObjectStore* managedObjectStoreInstance = nullptr; | |
| } // namespace | |
| void SetManagedStoreInstanceForTesting(ManagedObjectStore* instance) { | |
| managedObjectStoreInstance = instance; | |
| } | |
| // TODO:: can ^ be a std::shared_ptr on `static ManagedObjectStore::instance()` | |
| // when we implement graceful shutdowns | |
| ManagedStore* InitializeManagedStore( | |
| const ManagedObjectStoreConfig* cfg, | |
| boost::asio::io_context* io_main_thread, | |
| const std::shared_ptr<boost::asio::io_context>& io_context_worker_threads, | |
| ecclesia::SubscriptionService* subscription_service, | |
| sdbusplus::asio::connection* systemBus, | |
| std::shared_ptr<milotic_tlbmc::SystemRegistry> system_registry) { | |
| if (managedObjectStoreInstance != nullptr) { | |
| return managedObjectStoreInstance; | |
| } | |
| LOG(INFO) << "InitializeManagedStore with systembus"; | |
| static bool initialize = false; | |
| // First initialization should only happen if cfg, io, and systemBus are not | |
| // null | |
| if (cfg != nullptr && io_main_thread != nullptr && systemBus != nullptr) { | |
| // Once this is set to true, every future call will always return the | |
| // managed store instance | |
| initialize = true; | |
| } | |
| if (!initialize) { | |
| return nullptr; | |
| } | |
| // NOLINTNEXTLINE(google3-runtime-global-variables) | |
| static ManagedObjectStore instance( | |
| *cfg, *io_main_thread, std::this_thread::get_id(), | |
| io_context_worker_threads, subscription_service, systemBus, | |
| system_registry); | |
| managedObjectStoreInstance = &instance; | |
| return managedObjectStoreInstance; | |
| } | |
| ManagedStore* InitializeManagedStore( | |
| const ManagedObjectStoreConfig* cfg, | |
| boost::asio::io_context* io_main_thread, | |
| const std::shared_ptr<boost::asio::io_context>& io_context_worker_threads, | |
| ecclesia::SubscriptionService* subscription_service, | |
| sdbusplus::asio::connection* systemBus) { | |
| return InitializeManagedStore(cfg, io_main_thread, io_context_worker_threads, | |
| subscription_service, systemBus, nullptr); | |
| } | |
| ManagedStore* GetManagedObjectStore() { return managedObjectStoreInstance; } | |
| void ManagedObjectStore::UpdateOrInsertManagedObjects( | |
| const std::string& key_id, const std::shared_ptr<ValueType>& value) { | |
| absl::MutexLock lock(managed_objects_mutex_); | |
| // Initialize numTimesUsed: | |
| value->numTimesUsed = 1; | |
| auto current_object_iter = managedObjects.find(key_id); | |
| // Update managed object if being tracked already. | |
| if (current_object_iter != managedObjects.end()) { | |
| // preserve the lastUsed: | |
| value->lastUsed = current_object_iter->second->lastUsed; | |
| // preserve the lastUsedAges: | |
| value->lastUsedAges = current_object_iter->second->lastUsedAges; | |
| // preserve the numTimesUsed: | |
| value->numTimesUsed = current_object_iter->second->numTimesUsed; | |
| // an shared / unique pointer count refreshes: | |
| value->numRefreshesDone = current_object_iter->second->numRefreshesDone + 1; | |
| // count the scheduled refreshes: | |
| value->numRefreshesScheduled = | |
| current_object_iter->second->numRefreshesScheduled; | |
| BMCWEB_LOG_STATEFUL_DEBUG << "Update existing entry:" << " Key: " << key_id | |
| << " Value: " << value->toString(); | |
| } | |
| // upsert the key: | |
| managedObjects.insert_or_assign(key_id, value); | |
| BMCWEB_LOG_STATEFUL_DEBUG | |
| << "Updated Object! Key: " << key_id | |
| << " New: " << managedObjects.find(key_id)->second->toString(); | |
| } | |
| void ManagedObjectStore::processDbusResponse( | |
| const KeyType& key, const std::shared_ptr<ValueType>& value, | |
| bool isReadThroughRequest, std::optional<uint64_t> refresh_interval) { | |
| std::string key_id = key.GetId(); | |
| BMCWEB_LOG_STATEFUL_DEBUG << "Updating store with fresh dbus response " | |
| << " Key: " << key_id | |
| << " Value: " << value->toString() | |
| << " ec: " << value->errorCode.message(); | |
| UpdateOrInsertManagedObjects(key_id, value); | |
| // Callbacks to invoke after processing dbus response. | |
| std::queue<ManagedStoreCb> cb_queue = GetCallbacksFromRequestCbMap(key_id); | |
| // Update pending dbus response count on receiving response. | |
| { | |
| absl::MutexLock lock(managed_store_tracker_mutex_); | |
| this->managedStoreTracker.decrementPendingDbusResponses( | |
| isReadThroughRequest); | |
| } | |
| while (!cb_queue.empty()) { | |
| auto& callback = cb_queue.front(); | |
| std::move(callback)(value); | |
| cb_queue.pop(); | |
| } | |
| bool has_refresh_interval = refresh_interval.has_value(); | |
| // A refresh operation is queued into the priority queue if the object has | |
| // no previous queued refresh operation or if there is a | |
| // custom refresh interval that needs to be prioritized over previously queued | |
| // refresh operation. | |
| size_t& pq_entry_count = key_to_pq_entry_[key_id]; | |
| if (pq_entry_count == 0 || has_refresh_interval) { | |
| pq_entry_count++; | |
| // Queue refresh operation. | |
| pq.push( | |
| PriorityQueueEntry(key, value->nextRefreshAt, value->lastRefreshAt)); | |
| // Cancel current scheduled refresh operation if refresh interval of the key | |
| // is less than the timer wait duration. | |
| auto timer_wait_duration = timer.expiry() - clockNow(); | |
| if (isSchedulerActive && has_refresh_interval && | |
| timer_wait_duration > std::chrono::milliseconds(*refresh_interval)) { | |
| timer.cancel(); | |
| return; | |
| } | |
| } | |
| // Finally, call scheduler to queue up next refresh operations. | |
| scheduleRefresh(); | |
| } | |
| bool ManagedObjectStore::DetectChange(const ValueType& valueA, | |
| const ValueType& valueB) { | |
| return (valueA != valueB); | |
| } | |
| void ManagedObjectStore::UpdateNumRefreshesScheduled(std::string& key_id) { | |
| absl::MutexLock lock(managed_objects_mutex_); | |
| auto managed_object_iter = this->managedObjects.find(key_id); | |
| if (managed_object_iter != this->managedObjects.end()) { | |
| managed_object_iter->second->numRefreshesScheduled += 1; | |
| BMCWEB_LOG_STATEFUL_DEBUG | |
| << "refreshObject: Key: " << managed_object_iter->first | |
| << " Value before refresh: " << managed_object_iter->second->toString(); | |
| } | |
| } | |
| void ManagedObjectStore::refreshObject( | |
| const KeyType& key, bool isReadThroughRequest, | |
| const std::shared_ptr<boost::asio::io_context::strand>& strand) { | |
| std::string key_id = key.GetId(); | |
| UpdateNumRefreshesScheduled(key_id); | |
| std::optional<uint64_t> refresh_interval = GetRefreshInterval(key_id); | |
| switch (key.getManagedType()) { | |
| case ManagedType::kManagedObject: { | |
| getManagedObjectsFromDbusService( | |
| strand, key.serviceName, key.objectPath, | |
| [this, keyType = key, isReadThroughRequest, refresh_interval]( | |
| const std::shared_ptr<ValueType>& managedValue) mutable { | |
| processDbusResponse(keyType, managedValue, isReadThroughRequest, | |
| refresh_interval); | |
| }, | |
| refresh_interval); | |
| break; | |
| } | |
| case ManagedType::kManagedPropertyMap: { | |
| getManagedPropertiesMapFromDbusService( | |
| strand, key.serviceName, key.objectPath, key.interface, | |
| [this, keyType = key, isReadThroughRequest, refresh_interval]( | |
| const std::shared_ptr<ValueType>& managedValue) mutable { | |
| processDbusResponse(keyType, managedValue, isReadThroughRequest, | |
| refresh_interval); | |
| }, | |
| refresh_interval); | |
| break; | |
| } | |
| case ManagedType::kManagedProperty: { | |
| getManagedPropertiesFromDbusService( | |
| strand, key.serviceName, key.objectPath, key.interface, key.property, | |
| [this, keyType = key, isReadThroughRequest, refresh_interval]( | |
| const std::shared_ptr<ValueType>& managedValue) mutable { | |
| processDbusResponse(keyType, managedValue, isReadThroughRequest, | |
| refresh_interval); | |
| }, | |
| refresh_interval); | |
| break; | |
| } | |
| case ManagedType::kManagedSubtree: { | |
| getManagedSubtreeFromDbusService( | |
| strand, key.objectPath, key.treeDepth, key.interfaceList, | |
| [this, keyType = key, isReadThroughRequest, refresh_interval]( | |
| const std::shared_ptr<ValueType>& managedValue) mutable { | |
| processDbusResponse(keyType, managedValue, isReadThroughRequest, | |
| refresh_interval); | |
| }, | |
| refresh_interval); | |
| break; | |
| } | |
| case ManagedType::kManagedSubtreePaths: { | |
| getManagedSubtreePathsFromDbusService( | |
| strand, key.objectPath, key.treeDepth, key.interfaceList, | |
| [this, keyType = key, isReadThroughRequest, refresh_interval]( | |
| const std::shared_ptr<ValueType>& managedValue) mutable { | |
| processDbusResponse(keyType, managedValue, isReadThroughRequest, | |
| refresh_interval); | |
| }, | |
| refresh_interval); | |
| break; | |
| } | |
| case ManagedType::kManagedAssociatedSubtree: { | |
| getManagedAssociatedSubtreeFromDbusService( | |
| strand, key.associatedPath, key.objectPath, key.treeDepth, | |
| key.interfaceList, | |
| [this, keyType = key, isReadThroughRequest, refresh_interval]( | |
| const std::shared_ptr<ValueType>& managedValue) mutable { | |
| processDbusResponse(keyType, managedValue, isReadThroughRequest, | |
| refresh_interval); | |
| }, | |
| refresh_interval); | |
| break; | |
| } | |
| case ManagedType::kManagedAssociatedSubtreePaths: { | |
| getManagedAssociatedSubtreePathsFromDbusService( | |
| strand, key.associatedPath, key.objectPath, key.treeDepth, | |
| key.interfaceList, | |
| [this, keyType = key, isReadThroughRequest, refresh_interval]( | |
| const std::shared_ptr<ValueType>& managedValue) mutable { | |
| processDbusResponse(keyType, managedValue, isReadThroughRequest, | |
| refresh_interval); | |
| }, | |
| refresh_interval); | |
| break; | |
| } | |
| case ManagedType::kManagedMapperObject: { | |
| getManagedMapperObjectFromDbusService( | |
| strand, key.objectPath, key.interfaceList, | |
| [this, keyType = key, isReadThroughRequest, refresh_interval]( | |
| const std::shared_ptr<ValueType>& managedValue) mutable { | |
| processDbusResponse(keyType, managedValue, isReadThroughRequest, | |
| refresh_interval); | |
| }, | |
| refresh_interval); | |
| break; | |
| } | |
| case ManagedType::kManagedSystemdListUnits: { | |
| getSystemdUnitsFromDbusService( | |
| strand, | |
| [this, keyType = key, isReadThroughRequest, refresh_interval]( | |
| const std::shared_ptr<ValueType>& managedValue) mutable { | |
| processDbusResponse(keyType, managedValue, isReadThroughRequest, | |
| refresh_interval); | |
| }, | |
| refresh_interval); | |
| break; | |
| } | |
| } | |
| // Record a pending refresh operation. | |
| absl::MutexLock lock(managed_store_tracker_mutex_); | |
| managedStoreTracker.incrementPendingDbusResponses(isReadThroughRequest); | |
| } | |
| void ManagedObjectStore::scheduleRefresh() { | |
| if (this->isSchedulerActive || this->config.snap_shot_mode) { | |
| // The timer callback will schedule the next refresh if the scheduler is | |
| // active. | |
| return; | |
| } | |
| // Cannot schedule refresh if pending dbus responses reach threshold. | |
| uint64_t count_pending_dbus_responses = 0; | |
| { | |
| absl::MutexLock lock(managed_store_tracker_mutex_); | |
| count_pending_dbus_responses = | |
| managedStoreTracker.countPendingDbusResponses; | |
| if (count_pending_dbus_responses >= this->config.pendingDbusResponsesMax) { | |
| BMCWEB_LOG_STATEFUL_DEBUG << "scheduleRefresh: Pending io " | |
| << "threshold reached."; | |
| return; | |
| } | |
| } | |
| // Scan the priority queue to remove the entries that are no longer used and | |
| // get to a schedulable entry. | |
| std::chrono::steady_clock::time_point timeNow = clockNow(); | |
| bool can_schedule = false; | |
| while (!can_schedule && !this->pq.empty()) { | |
| const PriorityQueueEntry& pqEntry = pq.top(); | |
| // Get managedStore key to queue refresh of corresponding object in | |
| // the managedStore. | |
| const KeyType& key = pqEntry.managedStoreKey; | |
| const std::string key_id = key.GetId(); | |
| // The object is no longer tracked: | |
| if (!IsManagedObjectTracked(key_id)) { | |
| pq.pop(); | |
| key_to_pq_entry_.erase(key_id); | |
| continue; | |
| } | |
| std::chrono::steady_clock::time_point managed_store_entry_last_used; | |
| std::chrono::steady_clock::time_point managed_store_entry_next_refresh_at; | |
| std::chrono::steady_clock::time_point managed_store_entry_last_refresh_at; | |
| { | |
| absl::MutexLock lock(managed_objects_mutex_); | |
| auto managed_object_iter = managedObjects.find(key_id); | |
| if (managed_object_iter == managedObjects.end()) continue; | |
| // Copy managed_store_entry timepoints so that we don't need locks later. | |
| // Note, time here might be invalid after the following line | |
| // because of an immediate object insertion or usage. This is ok because | |
| // even if the priority queue entry is removed, when we find the object | |
| // becomes stale, it will enqueued again. | |
| // Invalid `last_refresh_at` or `next_refresh_at` will not cause fatal | |
| // issues, as well, because freshness is strictly checked. | |
| managed_store_entry_last_used = managed_object_iter->second->lastUsed; | |
| managed_store_entry_next_refresh_at = | |
| managed_object_iter->second->nextRefreshAt; | |
| managed_store_entry_last_refresh_at = | |
| managed_object_iter->second->lastRefreshAt; | |
| } | |
| const auto duration_since_used = | |
| clockSince(managed_store_entry_last_used, timeNow); | |
| bool is_recently_used = duration_since_used < this->config.tLRUThreshold; | |
| BMCWEB_LOG_STATEFUL_DEBUG | |
| << "SinceUsed:" << " Key: " << key.toString() | |
| << " duration_since_used: " | |
| << clockSinceMilliseconds(managed_store_entry_last_used, timeNow) | |
| << " is_recently_used: " << is_recently_used; | |
| // LRU Eviction: | |
| if (!is_recently_used) { | |
| BMCWEB_LOG_STATEFUL_DEBUG << "Since Used: Evict:" << " Key: " << key_id; | |
| EvictManagedObject(key_id); | |
| pq.pop(); | |
| key_to_pq_entry_.erase(key_id); | |
| continue; | |
| } | |
| // Handle recently refreshed object. | |
| if (managed_store_entry_last_refresh_at > pqEntry.lastRefreshAt) { | |
| BMCWEB_LOG_STATEFUL_DEBUG << "PQ entry recently refreshed: " << key_id; | |
| // Get current refresh interval from the managedStore entry. | |
| auto current_refresh_interval = managed_store_entry_next_refresh_at - | |
| managed_store_entry_last_refresh_at; | |
| // Get refresh interval from the priority queue entry. | |
| auto pq_entry_refresh_interval = | |
| pqEntry.nextRefreshAt - pqEntry.lastRefreshAt; | |
| size_t* pq_entry_count = nullptr; | |
| if (auto find_key_count = key_to_pq_entry_.find(key_id); | |
| find_key_count != key_to_pq_entry_.end()) { | |
| pq_entry_count = &find_key_count->second; | |
| } | |
| // The pqEntry is redundant and needs to be removed if corresponding | |
| // managed store entry is refreshed recently and refresh interval | |
| // configured in managedStore entry is less than the refresh interval | |
| // configured in pqEntry. | |
| if (pq_entry_count != nullptr && | |
| (current_refresh_interval < pq_entry_refresh_interval || | |
| (current_refresh_interval == pq_entry_refresh_interval && | |
| *pq_entry_count > 1))) { | |
| BMCWEB_LOG_STATEFUL_DEBUG << "Removing redundant pqEntry: " | |
| << pqEntry.managedStoreKey.toString(); | |
| pq.pop(); | |
| if (--(*pq_entry_count) == 0) { | |
| key_to_pq_entry_.erase(key_id); | |
| } | |
| continue; | |
| } | |
| // Re-queue the entry because the last object was refreshed due to a | |
| // forced cache read through. | |
| BMCWEB_LOG_STATEFUL_DEBUG << "Re-queue pqEntry: " | |
| << pqEntry.managedStoreKey.toString(); | |
| const KeyType key_local = key; | |
| pq.pop(); | |
| pq.push(PriorityQueueEntry(key_local, managed_store_entry_next_refresh_at, | |
| managed_store_entry_last_refresh_at)); | |
| continue; | |
| } | |
| can_schedule = true; | |
| } | |
| if (!can_schedule) { | |
| return; | |
| } | |
| BMCWEB_LOG_STATEFUL_DEBUG | |
| << "scheduleRefresh: " << pq.size() | |
| << " Pending dbus responses: " << count_pending_dbus_responses; | |
| PriorityQueueEntry pqEntry = pq.top(); | |
| pq.pop(); | |
| // Get service name and object path to schedule refresh of the corresponding | |
| // object in store. | |
| const KeyType& managedStoreKey = pqEntry.managedStoreKey; | |
| std::string key_id = managedStoreKey.GetId(); | |
| // Stop tracking the pq entry once it is pop'd from the queue. | |
| if (auto iter = key_to_pq_entry_.find(key_id); | |
| iter != key_to_pq_entry_.end()) { | |
| if (--iter->second == 0) { | |
| key_to_pq_entry_.erase(iter); | |
| } | |
| } | |
| // Set scheduler status to active before scheduling the refresh op to ensure | |
| // an immediate callback keeps the system in the correct state. | |
| isSchedulerActive = true; | |
| // If the refresh needs to be scheduled later in time, we set the timeout | |
| // based on the time difference between the expected refresh time and | |
| // current time. | |
| // NOTE: the expected refresh time is set based on t_ttl. | |
| const auto timerTimeout = | |
| std::chrono::duration_cast<std::chrono::milliseconds>( | |
| pqEntry.nextRefreshAt - timeNow); | |
| timer.expires_after(timerTimeout); | |
| BMCWEB_LOG_STATEFUL_DEBUG << "Scheduling Refresh after " | |
| << timerTimeout.count() << " milliseconds. " | |
| << "Key: " << managedStoreKey.GetId(); | |
| timer.async_wait( | |
| [this, managedStoreKey, pqEntry](boost::system::error_code ec) { | |
| isSchedulerActive = false; | |
| if (ec == boost::asio::error::operation_aborted) { | |
| BMCWEB_LOG_STATEFUL_INFO << "Timer operation aborted for key: " | |
| << managedStoreKey.GetId(); | |
| // There is an object needs to be refreshed earlier than currently | |
| // scheduled object. Push the key for current object back into the PQ | |
| // to be scheduled later point in time. | |
| pq.push(PriorityQueueEntry(managedStoreKey, pqEntry.nextRefreshAt, | |
| pqEntry.lastRefreshAt)); | |
| scheduleRefresh(); | |
| return; | |
| } | |
| if (ec) { | |
| BMCWEB_LOG_STATEFUL_ERROR << "Async_wait failed" << ec; | |
| return; | |
| } | |
| BMCWEB_LOG_STATEFUL_DEBUG << "Refreshing managed store entry. " | |
| << "Key: " << managedStoreKey.GetId(); | |
| refreshObject(managedStoreKey); | |
| scheduleRefresh(); | |
| }); | |
| } | |
| // TODO - testing | |
| void ManagedObjectStore::listUnits( | |
| const ManagedObjectStoreContext& requestContext, | |
| GetManagedSystemdListUnitsCb callback) { | |
| // When Object Store is disabled, the requests read through. | |
| if (!this->isEnabled()) { | |
| PostDbusCallToIoContextThreadSafe( | |
| requestContext.GetStrand(), | |
| [callback{std::move(callback)}]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::SystemdListUnits& listUnits) mutable { | |
| std::move(callback)(ec, listUnits); | |
| }, | |
| "org.freedesktop.systemd1", "/org/freedesktop/systemd1", | |
| "org.freedesktop.systemd1.Manager", "ListUnits"); | |
| return; | |
| } | |
| KeyType key_type(ManagedType::kManagedSystemdListUnits); | |
| getStoredOrReadThrough( | |
| key_type, requestContext, | |
| [callback{std::move(callback)}]( | |
| const std::shared_ptr<ValueType>& valueType) mutable { | |
| if (!valueType->SystemdListUnits.has_value()) { | |
| std::move(callback)(valueType->errorCode, {}); | |
| return; | |
| } | |
| std::move(callback)(valueType->errorCode, | |
| valueType->SystemdListUnits.value()); | |
| }); | |
| } | |
| void ManagedObjectStore::getProperty( | |
| const KeyType& keyType, const ManagedObjectStoreContext& requestContext, | |
| GetManagedPropertyCb callback) { | |
| // When Object Store is disabled, the requests read through. | |
| if (!this->isEnabled()) { | |
| PostDbusCallToIoContextThreadSafe( | |
| requestContext.GetStrand(), | |
| [callback{std::move(callback)}]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::DbusVariantType& value) mutable { | |
| std::move(callback)(ec, value, 0); | |
| }, | |
| keyType.serviceName, keyType.objectPath.str, | |
| "org.freedesktop.DBus.Properties", "Get", keyType.interface, | |
| keyType.property); | |
| return; | |
| } | |
| getStoredOrReadThrough( | |
| keyType, requestContext, | |
| [callback{std::move(callback)}]( | |
| const std::shared_ptr<ValueType>& valueType) mutable { | |
| if (!valueType->managedProperty.has_value()) { | |
| callback(valueType->errorCode, {}, 0); | |
| return; | |
| } | |
| uint64_t age_in_ms = static_cast<uint64_t>( | |
| std::chrono::duration_cast<std::chrono::milliseconds>( | |
| clockNow() - valueType->lastRefreshAt) | |
| .count()); | |
| LOG(INFO) << "MAX AGE AT THIS POINT: " << age_in_ms; | |
| std::move(callback)(valueType->errorCode, | |
| valueType->managedProperty.value(), age_in_ms); | |
| }); | |
| } | |
| void ManagedObjectStore::getAllProperties( | |
| const std::string& service, const std::string& objectPath, | |
| const std::string& interface, | |
| const ManagedObjectStoreContext& requestContext, | |
| GetManagedPropertyMapCb callback) { | |
| // When Object Store is disabled, the requests read through. | |
| if (!this->isEnabled()) { | |
| PostDbusCallToIoContextThreadSafe( | |
| requestContext.GetStrand(), | |
| [callback{std::move(callback)}]( | |
| const boost::system::error_code& ec, | |
| const ::dbus::utility::DBusPropertiesMap& properties) mutable { | |
| std::move(callback)(ec, properties); | |
| }, | |
| service, objectPath, "org.freedesktop.DBus.Properties", "GetAll", | |
| interface); | |
| return; | |
| } | |
| KeyType key_type(ManagedType::kManagedPropertyMap, service, objectPath, | |
| interface); | |
| getStoredOrReadThrough( | |
| key_type, requestContext, | |
| [callback{std::move(callback)}]( | |
| const std::shared_ptr<ValueType>& valueType) mutable { | |
| if (!valueType->managedPropertyMap.has_value()) { | |
| std::move(callback)(valueType->errorCode, {}); | |
| return; | |
| } | |
| std::move(callback)(valueType->errorCode, | |
| valueType->managedPropertyMap.value()); | |
| }); | |
| } | |
| void ManagedObjectStore::subscribe( | |
| const ManagedObjectStoreContext& requestContext, | |
| const std::string& match_expression, | |
| ecclesia::EventSourceId event_source_id) { | |
| if (match_expression.empty()) { | |
| LOG(WARNING) | |
| << "D-Bus match expression is empty. Invalid call to subscribe!"; | |
| return; | |
| } | |
| if (requestContext.eventSourceIds == nullptr || | |
| requestContext.request_type == | |
| ManagedObjectStoreContext::RequestType::kQuery) { | |
| LOG(WARNING) << "eventSourceIds is null. Invalid call to subscribe!"; | |
| return; | |
| } | |
| // Check if monitor already exists. | |
| { | |
| absl::MutexLock lock(signal_monitor_mutex_); | |
| if (auto find_monitor = signal_monitors_.find(event_source_id); | |
| find_monitor != signal_monitors_.end()) { | |
| if (requestContext.request_type == | |
| ManagedObjectStoreContext::RequestType::kCancelSubscription) { | |
| // Erase the monitor | |
| signal_monitors_.erase(find_monitor); | |
| LOG(WARNING) << "Erased D-Bus monitor for id: " | |
| << event_source_id.ToString(); | |
| return; | |
| } | |
| LOG(WARNING) << "D-Bus monitor already created for id: " | |
| << event_source_id.ToString(); | |
| // Record monitor id regardless of an existing subscription since | |
| // multiple Redfish resources can have dependency on a dbus signal. | |
| requestContext.eventSourceIds->push_back(std::move(event_source_id)); | |
| return; | |
| } | |
| } | |
| boost::asio::post(io_context_main_thread_, [this, event_source_id, | |
| match_expression] { | |
| absl::MutexLock lock(signal_monitor_mutex_); | |
| signal_monitors_[event_source_id] = std::make_unique< | |
| sdbusplus::bus::match_t>( | |
| *system_bus_, match_expression, | |
| [event_source_id, this](sdbusplus::message_t&) { | |
| LOG(INFO) | |
| << "Received dbus signal. Notifying Subscription service now!"; | |
| auto status = | |
| subscription_service_->Notify(event_source_id, absl::OkStatus()); | |
| if (status.ok()) { | |
| LOG(INFO) << "Notified successfully!"; | |
| return; | |
| } | |
| LOG(WARNING) << "Error while sending notification: " << status; | |
| if (status.code() == absl::StatusCode::kNotFound) { | |
| absl::MutexLock lock(signal_monitor_mutex_); | |
| signal_monitors_.erase(event_source_id); | |
| LOG(INFO) << "Erased D-Bus monitor for id: " | |
| << event_source_id.ToString(); | |
| } | |
| }); | |
| LOG(INFO) << "D-Bus monitor created. Event Source id: " | |
| << event_source_id.ToString(); | |
| }); | |
| // Record event source ids. | |
| requestContext.eventSourceIds->push_back(std::move(event_source_id)); | |
| } | |
| void ManagedObjectStore::subscribeToInterfacesAdded( | |
| const std::string& object_path, | |
| const ManagedObjectStoreContext& request_context) { | |
| std::string match_expression = absl::Substitute( | |
| "type='signal',interface='org.freedesktop.DBus.ObjectManager',path_" | |
| "namespace='$0'," | |
| "member='InterfacesAdded'", | |
| object_path); | |
| std::string monitor_id; | |
| AppendWithSep(monitor_id, '|', object_path, "InterfacesAdded", "EVENT"); | |
| ecclesia::EventSourceId event_source_id( | |
| monitor_id, ecclesia::EventSourceId::Type::kDbusObjects); | |
| subscribe(request_context, match_expression, std::move(event_source_id)); | |
| } | |
| void ManagedObjectStore::subscribeToHealthMonitor( | |
| const std::string& object_path, HealthChangeCallback callback) { | |
| std::string match_expression = absl::Substitute( | |
| "type='signal'" | |
| ",interface='xyz.openbmc_project.HealthMonitor.GetStatus',path_" | |
| "namespace='$0'," | |
| "member='healthyPropertyChanged'", | |
| object_path); | |
| std::string monitor_id; | |
| AppendWithSep(monitor_id, '|', object_path, "healthyPropertyChanged", | |
| "EVENT"); | |
| boost::asio::post( | |
| io_context_main_thread_, [this, match_expression, object_path, | |
| callback = std::move(callback)]() mutable { | |
| if (health_monitors_.contains(object_path)) { | |
| return; | |
| } | |
| health_monitors_[object_path] = | |
| std::make_unique<sdbusplus::bus::match_t>( | |
| *system_bus_, match_expression, | |
| [callback = std::make_shared<HealthChangeCallback>( | |
| std::move(callback))](sdbusplus::message_t& msg) { | |
| std::string dbusPath; | |
| try { | |
| msg.read(dbusPath); | |
| } catch (const std::exception& e) { | |
| LOG(WARNING) << "Failed to read dbus path: " << e.what(); | |
| return; | |
| } | |
| (*callback)(dbusPath); | |
| }); | |
| }); | |
| } | |
| void ManagedObjectStore::subscribeToEntityManagerChange( | |
| EntityManagerChangeCallback callback) { | |
| std::string match_expression = | |
| "type='signal',member='PropertiesChanged'," | |
| "path_namespace='/xyz/openbmc_project/inventory'," | |
| "arg0namespace='xyz.openbmc_project.Configuration.HealthMonitor'"; | |
| std::string monitor_id; | |
| AppendWithSep(monitor_id, '|', "/xyz/openbmc_project/inventory", | |
| "PropertiesChanged", "EVENT"); | |
| boost::asio::post( | |
| io_context_main_thread_, | |
| [this, match_expression, callback = std::move(callback)]() mutable { | |
| entity_manager_monitor_ = std::make_unique<sdbusplus::bus::match_t>( | |
| *system_bus_, match_expression, | |
| [callback = std::make_shared<EntityManagerChangeCallback>( | |
| std::move(callback))](sdbusplus::message_t& message) { | |
| // TODO: b/433598433 - | |
| // Add unit test for this callback. | |
| (*callback)(message.get_path()); | |
| }); | |
| }); | |
| } | |
| void ManagedObjectStore::getSubTree( | |
| const std::string& path, int32_t depth, | |
| const std::vector<std::string>& interfaces, | |
| const ManagedObjectStoreContext& requestContext, | |
| GetManagedSubtreeCb callback) { | |
| // When Object Store is disabled, the requests read through. | |
| if (!this->isEnabled()) { | |
| PostDbusCallToIoContextThreadSafe( | |
| requestContext.GetStrand(), | |
| [callback{std::move(callback)}]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::MapperGetSubTreeResponse& subtree) mutable { | |
| std::move(callback)(ec, subtree); | |
| }, | |
| "xyz.openbmc_project.ObjectMapper", | |
| "/xyz/openbmc_project/object_mapper", | |
| "xyz.openbmc_project.ObjectMapper", "GetSubTree", path, depth, | |
| interfaces); | |
| return; | |
| } | |
| KeyType key_type(ManagedType::kManagedSubtree, path, depth, interfaces); | |
| getStoredOrReadThrough( | |
| key_type, requestContext, | |
| [callback{std::move(callback)}]( | |
| const std::shared_ptr<ValueType>& valueType) mutable { | |
| if (!valueType->managedSubtree.has_value()) { | |
| std::move(callback)(valueType->errorCode, {}); | |
| return; | |
| } | |
| std::move(callback)(valueType->errorCode, | |
| valueType->managedSubtree.value()); | |
| }); | |
| } | |
| void ManagedObjectStore::getSubTree( | |
| const std::string& path, int32_t depth, | |
| std::span<const std::string_view> interfaces, | |
| const ManagedObjectStoreContext& requestContext, | |
| GetManagedSubtreeCb callback) { | |
| std::vector<std::string> interfaces2; | |
| for (const auto& s : interfaces) { | |
| interfaces2.emplace_back(s); | |
| } | |
| this->getSubTree(path, depth, interfaces2, requestContext, | |
| std::move(callback)); | |
| } | |
| void ManagedObjectStore::getSubTreePaths( | |
| const std::string& path, int32_t depth, | |
| std::span<const std::string_view> interfaces, | |
| const ManagedObjectStoreContext& requestContext, | |
| GetManagedSubtreePathsCb callback) { | |
| std::vector<std::string> interfaces2; | |
| for (const auto& s : interfaces) { | |
| interfaces2.emplace_back(s); | |
| } | |
| // When Object Store is disabled, the requests read through. | |
| if (!this->isEnabled()) { | |
| PostDbusCallToIoContextThreadSafe( | |
| requestContext.GetStrand(), | |
| [callback{std::move(callback)}]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::MapperGetSubTreePathsResponse& | |
| subtreePaths) mutable { | |
| std::move(callback)(ec, subtreePaths); | |
| }, | |
| "xyz.openbmc_project.ObjectMapper", | |
| "/xyz/openbmc_project/object_mapper", | |
| "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", path, depth, | |
| interfaces2); | |
| return; | |
| } | |
| KeyType key_type(ManagedType::kManagedSubtreePaths, path, depth, interfaces2); | |
| getStoredOrReadThrough( | |
| key_type, requestContext, | |
| [callback{std::move(callback)}]( | |
| const std::shared_ptr<ValueType>& valueType) mutable { | |
| if (!valueType->managedSubtreePaths.has_value()) { | |
| std::move(callback)(valueType->errorCode, {}); | |
| return; | |
| } | |
| std::move(callback)(valueType->errorCode, | |
| valueType->managedSubtreePaths.value()); | |
| }); | |
| } | |
| void ManagedObjectStore::getAssociatedSubTree( | |
| const sdbusplus::message::object_path& associatedPath, | |
| const sdbusplus::message::object_path& path, int32_t depth, | |
| std::span<const std::string_view> interfaces, | |
| const ManagedObjectStoreContext& requestContext, | |
| GetManagedSubtreeCb callback) { | |
| std::vector<std::string> interfaces2; | |
| for (const auto& s : interfaces) { | |
| interfaces2.emplace_back(s); | |
| } | |
| this->getAssociatedSubTree(associatedPath, path, depth, interfaces2, | |
| requestContext, std::move(callback)); | |
| } | |
| void ManagedObjectStore::getAssociatedSubTree( | |
| const sdbusplus::message::object_path& associatedPath, | |
| const sdbusplus::message::object_path& path, int32_t depth, | |
| const std::vector<std::string>& interfaces, | |
| const ManagedObjectStoreContext& requestContext, | |
| GetManagedSubtreeCb callback) { | |
| std::vector<std::string> interfaces2; | |
| interfaces2.reserve(interfaces.size()); | |
| for (const auto& s : interfaces) { | |
| interfaces2.emplace_back(s); | |
| } | |
| // When Object Store is disabled, the requests read through. | |
| if (!this->isEnabled()) { | |
| PostDbusCallToIoContextThreadSafe( | |
| requestContext.GetStrand(), | |
| [callback{std::move(callback)}]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::MapperGetSubTreeResponse& subtree) mutable { | |
| std::move(callback)(ec, subtree); | |
| }, | |
| "xyz.openbmc_project.ObjectMapper", | |
| "/xyz/openbmc_project/object_mapper", | |
| "xyz.openbmc_project.ObjectMapper", "GetAssociatedSubTree", | |
| associatedPath, path, depth, interfaces2); | |
| return; | |
| } | |
| KeyType key_type(ManagedType::kManagedAssociatedSubtree, associatedPath.str, | |
| path.str, depth, interfaces2); | |
| getStoredOrReadThrough( | |
| key_type, requestContext, | |
| [callback{std::move(callback)}]( | |
| const std::shared_ptr<ValueType>& valueType) mutable { | |
| if (!valueType->managedSubtree.has_value()) { | |
| std::move(callback)(valueType->errorCode, {}); | |
| return; | |
| } | |
| std::move(callback)(valueType->errorCode, | |
| valueType->managedSubtree.value()); | |
| }); | |
| } | |
| void ManagedObjectStore::getAssociatedSubTreePaths( | |
| const sdbusplus::message::object_path& associatedPath, | |
| const sdbusplus::message::object_path& path, int32_t depth, | |
| std::span<const std::string_view> interfaces, | |
| const ManagedObjectStoreContext& requestContext, | |
| GetManagedSubtreePathsCb callback) { | |
| std::vector<std::string> interfaces2; | |
| for (const auto& s : interfaces) { | |
| interfaces2.emplace_back(s); | |
| } | |
| // When Object Store is disabled, the requests read through. | |
| if (!this->isEnabled()) { | |
| PostDbusCallToIoContextThreadSafe( | |
| requestContext.GetStrand(), | |
| [callback{std::move(callback)}]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::MapperGetSubTreePathsResponse& | |
| subtreePaths) mutable { | |
| std::move(callback)(ec, subtreePaths); | |
| }, | |
| "xyz.openbmc_project.ObjectMapper", | |
| "/xyz/openbmc_project/object_mapper", | |
| "xyz.openbmc_project.ObjectMapper", "GetAssociatedSubTreePaths", | |
| associatedPath, path, depth, interfaces2); | |
| return; | |
| } | |
| KeyType key_type(ManagedType::kManagedAssociatedSubtreePaths, | |
| associatedPath.str, path.str, depth, interfaces2); | |
| getStoredOrReadThrough( | |
| key_type, requestContext, | |
| [callback{std::move(callback)}]( | |
| const std::shared_ptr<ValueType>& valueType) mutable { | |
| if (!valueType->managedSubtreePaths.has_value()) { | |
| std::move(callback)(valueType->errorCode, {}); | |
| return; | |
| } | |
| std::move(callback)(valueType->errorCode, | |
| valueType->managedSubtreePaths.value()); | |
| }); | |
| } | |
| void ManagedObjectStore::getDbusObject( | |
| const std::string& path, std::span<const std::string_view> interfaces, | |
| const ManagedObjectStoreContext& requestContext, | |
| GetManagedMapperObjectCb callback) { | |
| std::vector<std::string> interfaces2; | |
| for (const auto& s : interfaces) { | |
| interfaces2.emplace_back(s); | |
| } | |
| // When Object Store is disabled, the requests read through. | |
| if (!this->isEnabled()) { | |
| PostDbusCallToIoContextThreadSafe( | |
| requestContext.GetStrand(), | |
| [callback{std::move(callback)}]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::MapperGetObject& object) mutable { | |
| std::move(callback)(ec, object); | |
| }, | |
| "xyz.openbmc_project.ObjectMapper", | |
| "/xyz/openbmc_project/object_mapper", | |
| "xyz.openbmc_project.ObjectMapper", "GetObject", path, interfaces2); | |
| return; | |
| } | |
| KeyType key_type(ManagedType::kManagedMapperObject, path, interfaces2); | |
| getStoredOrReadThrough( | |
| key_type, requestContext, | |
| [callback{std::move(callback)}]( | |
| const std::shared_ptr<ValueType>& valueType) mutable { | |
| if (valueType->mapperGetObject.has_value()) { | |
| std::move(callback)(valueType->errorCode, | |
| valueType->mapperGetObject.value()); | |
| return; | |
| } | |
| std::move(callback)(valueType->errorCode, {}); | |
| }); | |
| } | |
| void ManagedObjectStore::getManagedObjectsWithContext( | |
| const std::string& service, const sdbusplus::message::object_path& path, | |
| const ManagedObjectStoreContext& requestContext, | |
| GetManagedObjectsCb callback) { | |
| { | |
| uint64_t count_get_managed_objects = 0; | |
| // total counter of calls: | |
| absl::MutexLock lock(managed_store_tracker_mutex_); | |
| count_get_managed_objects = | |
| ++this->managedStoreTracker.countGetManagedObjects; | |
| BMCWEB_LOG_STATEFUL_DEBUG | |
| << "getManagedObjectsWithContext:" << " service: " << service | |
| << " path: " << path.str | |
| << " requestContext: " << requestContext.toString() | |
| << " count: " << count_get_managed_objects; | |
| } | |
| // When Object Store is disabled, the requests read through. | |
| if (!this->isEnabled()) { | |
| // TODO:: do we want to count that (if the cache is actually disabled) | |
| // i think we should since we want to use that as validation for the | |
| // store being enabled? | |
| { | |
| absl::MutexLock lock(managed_store_tracker_mutex_); | |
| this->managedStoreTracker.countGetManagedObjectsCacheMiss += 1; | |
| } | |
| PostDbusCallToIoContextThreadSafe( | |
| requestContext.GetStrand(), | |
| [callback{std::move(callback)}]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::ManagedObjectType& objects) mutable { | |
| std::move(callback)(ec, objects); | |
| }, | |
| service, path, "org.freedesktop.DBus.ObjectManager", | |
| "GetManagedObjects"); | |
| return; | |
| } | |
| KeyType key_type(ManagedType::kManagedObject, service, path.str); | |
| getStoredOrReadThrough( | |
| key_type, requestContext, | |
| [callback = std::move(callback)]( | |
| const std::shared_ptr<ValueType>& valueType) mutable { | |
| if (valueType->managedObject.has_value()) { | |
| std::move(callback)(valueType->errorCode, | |
| valueType->managedObject.value()); | |
| return; | |
| } | |
| std::move(callback)(valueType->errorCode, {}); | |
| }); | |
| } | |
| std::shared_ptr<ValueType> ManagedObjectStore::IsManagedObjectCurrentAndTracked( | |
| const std::string& key_id, const KeyType& keyType, | |
| const ManagedObjectStoreContext& requestContext) { | |
| absl::MutexLock lock(managed_objects_mutex_); | |
| std::shared_ptr<ValueType> cached_object = nullptr; | |
| bool is_object_current = false; | |
| auto managed_object_iter = managedObjects.find(key_id); | |
| // Execute callback immediately if object is cached in store. | |
| if (managed_object_iter != managedObjects.end()) { | |
| cached_object = managed_object_iter->second; | |
| const std::chrono::steady_clock::time_point timeNow = clockNow(); | |
| std::chrono::steady_clock::duration current_age = | |
| clockSince(cached_object->lastRefreshAt, timeNow); | |
| // update lastUsed: | |
| // NOMUTANTS -- TODO later | |
| cached_object->lastUsed = timeNow; | |
| // count number of times used: | |
| cached_object->numTimesUsed += 1; | |
| // update lastUsedAges: | |
| if (!cached_object->lastUsedAges) { | |
| cached_object->lastUsedAges = | |
| std::make_shared<std::list<std::chrono::steady_clock::duration>>(); | |
| } | |
| cached_object->lastUsedAges->push_back(current_age); | |
| while (cached_object->lastUsedAges->size() >= | |
| managedStore::ValueType::kLastUsedAgesMaxSize) { | |
| cached_object->lastUsedAges->pop_front(); | |
| } | |
| LOG(INFO) << "Object found in store. " << " Key: " << key_id | |
| << " CurrentAge: " << clockDurationMilliseconds(current_age) | |
| << " Context: " << requestContext.toString() | |
| << " Value: " << cached_object->toString(); | |
| // check if the current object is stale: | |
| bool is_object_stale = current_age >= (this->config.tfixedThreshold + | |
| this->config.tgraceThreshold); | |
| // Always use the cache if snapshot mode is enabled. | |
| if (config.snap_shot_mode) { | |
| is_object_stale = false; | |
| } | |
| // Evict the object from store as it has become stale. | |
| if (is_object_stale) { | |
| LOG(INFO) << "Object has become stale! " | |
| << " service: " << keyType.serviceName | |
| << " path: " << keyType.objectPath.str << " interface: " | |
| << keyType.interface << " property: " << keyType.property | |
| << " current_age: " << clockDurationMilliseconds(current_age) | |
| << " Context: " << requestContext.toString() | |
| << " Value: " << cached_object->toString(); | |
| managedObjects.erase(managed_object_iter->first); | |
| } | |
| // can we use it as is: | |
| is_object_current = !is_object_stale; | |
| if (requestContext.hintMaxAge > std::chrono::seconds(0)) { | |
| // we have to refresh it because of hintMaxAge | |
| is_object_current = (current_age <= requestContext.hintMaxAge); | |
| { | |
| absl::MutexLock lock(managed_store_tracker_mutex_); | |
| this->managedStoreTracker.countGetManagedObjectsCacheMissMaxAge += 1; | |
| } | |
| BMCWEB_LOG_STATEFUL_DEBUG | |
| << "apply:" << " hintMaxAgeMS: " | |
| << clockDurationMilliseconds(requestContext.hintMaxAge); | |
| } | |
| // Force a refresh if the request is associated with subscription. | |
| if (requestContext.request_type == | |
| ManagedObjectStoreContext::RequestType::kOnEvent) { | |
| // TODO(rahulkpr): Need to log how many read throughs were due to | |
| // events. | |
| is_object_current = false; | |
| } | |
| } | |
| if (requestContext.HasCustomRefreshInterval()) { | |
| std::optional<uint64_t> current_refresh_interval = | |
| GetRefreshInterval(key_id); | |
| // NO_CDC: HasCustomRefreshInterval checks for optional. | |
| const uint64_t& refresh_interval_ms = *requestContext.refresh_interval_ms; | |
| if (std::chrono::milliseconds(refresh_interval_ms) >= | |
| this->config.tfixedThreshold) { | |
| // Requested interval is greater than global refresh interval. This | |
| // is a no-op. Just read from cache without changing intervals. | |
| BMCWEB_LOG_STATEFUL_DEBUG | |
| << "Ignoring custom refresh interval.\nRequested " | |
| "refresh_interval_ms: " | |
| << refresh_interval_ms << " is greater than global refresh interval: " | |
| << config.tfixedThreshold << " for key: " << key_id; | |
| } else if (!current_refresh_interval.has_value() || | |
| (current_refresh_interval.has_value() && | |
| refresh_interval_ms < *current_refresh_interval)) { | |
| // Requested interval is less than global refresh interval and there | |
| // isn't any custom interval set. | |
| // We need to do a cache read through and update refresh interval. | |
| SetRefreshInterval(key_id, refresh_interval_ms); | |
| // Read through is required to set custom interval. | |
| is_object_current = false; | |
| } | |
| } | |
| if (!is_object_current) { | |
| return nullptr; | |
| } | |
| return cached_object; | |
| } | |
| void ManagedObjectStore::getStoredOrReadThrough( | |
| const KeyType& keyType, const ManagedObjectStoreContext& requestContext, | |
| ManagedStoreCb callback) { | |
| std::string key_id = keyType.GetId(); | |
| BMCWEB_LOG_DEBUG << __func__ << " Key: " << key_id | |
| << " requestContext: " << requestContext.toString(); | |
| // Object is served from cache except in following scenarios: | |
| // 1. Cache is cold. | |
| // 2. Cached object is stale. | |
| // 3. Custom refresh interval needs to be configured. | |
| // 4. Request is associated with redfish event which requires a read | |
| // through to create origin of condition. | |
| std::shared_ptr<ValueType> cached_object = | |
| IsManagedObjectCurrentAndTracked(key_id, keyType, requestContext); | |
| bool is_object_current = cached_object != nullptr; | |
| // Evict the object from store as it has become stale. | |
| if (is_object_current) { | |
| // Return if the object is still fresh | |
| { | |
| absl::MutexLock lock(managed_store_tracker_mutex_); | |
| this->managedStoreTracker.countGetManagedObjectsCacheHit += 1; | |
| } | |
| BMCWEB_LOG_STATEFUL_DEBUG << "Cache hit: calling the user callback for " | |
| << key_id; | |
| // If we are in snapshot mode, we want to simulate asynchronous callback | |
| // behavior to catch dangling references into the callback. | |
| if (config.snap_shot_mode) { | |
| boost::asio::post(io_context_main_thread_, | |
| [callback{std::move(callback)}, | |
| cached_object{std::move(cached_object)}]() mutable { | |
| // already in the strand | |
| std::move(callback)(cached_object); | |
| }); | |
| return; | |
| } | |
| callback(cached_object); | |
| return; | |
| } | |
| { | |
| absl::MutexLock lock(managed_store_tracker_mutex_); | |
| this->managedStoreTracker.countGetManagedObjectsCacheMiss += 1; | |
| } | |
| // If snapshot mode is enabled, we will fail the request and send error code | |
| if (this->config.snap_shot_mode) { | |
| LOG(WARNING) << key_id << " not found in cache."; | |
| LOG(WARNING) << "Cache miss in snapshot mode; no way to update the cache."; | |
| callback(std::make_shared<ValueType>(boost::system::errc::make_error_code( | |
| boost::system::errc::no_such_file_or_directory))); | |
| return; | |
| } | |
| bool need_refresh = true; | |
| if (requestContext.GetStrand()) { | |
| // from now on we need to make callback only happen in the strand | |
| absl::AnyInvocable<void(const std::shared_ptr<ValueType>&)> | |
| callback_in_strand = | |
| [callback = std::move(callback), | |
| strand = requestContext.GetStrand()]( | |
| const std::shared_ptr<ValueType>& value) mutable { | |
| auto callback_wrapper = internal::CallAtMostOnce( | |
| [callback(std::move(callback)), value]() mutable { | |
| std::move(callback)(value); | |
| }); | |
| boost::asio::post(*strand, std::move(callback_wrapper)); | |
| }; | |
| // Handle concurrent requests for the same object by storing callback | |
| // for the request. | |
| // There might be cases where a callback is enqueued but dequeued shortly | |
| // after, which makes the `need_refresh` invalid, however, this is totally | |
| // fine as the follow `refreshObject` will just do a refresh earlier. | |
| need_refresh = InsertCallbackIntoRequestCbMapAndReturnTrueIfNew( | |
| key_id, std::move(callback_in_strand)); | |
| } else { | |
| need_refresh = InsertCallbackIntoRequestCbMapAndReturnTrueIfNew( | |
| key_id, std::move(callback)); | |
| } | |
| if (!need_refresh) { | |
| return; | |
| } | |
| BMCWEB_LOG_DEBUG << "Fetching fresh object over dbus. " << " Key: " << key_id | |
| << " requestContext: " << requestContext.toString(); | |
| // Note: This refresh operation is not rate limited i.e all incoming | |
| // requests with cache-miss will continue to read through without a cap on | |
| // pending i/o since we don't want to block on scheduler or send a failure | |
| // response. | |
| BMCWEB_LOG_DEBUG << "strand is " | |
| << (requestContext.GetStrand() ? "set" : "null"); | |
| refreshObject(keyType, true, requestContext.GetStrand()); | |
| } | |
| void ManagedObjectStore::clearAllObjects() { | |
| { | |
| absl::MutexLock lock(managed_objects_mutex_); | |
| this->managedObjects.clear(); | |
| } | |
| clearTimeTrace(); | |
| // looks like it's the only way to do this: | |
| boost::asio::post(io_context_main_thread_, | |
| [this]() { pq = ManagedStorePriorityQueue(); }); | |
| } | |
| void ManagedObjectStore::getManagedObjectsFromDbusService( | |
| const std::shared_ptr<boost::asio::io_context::strand>& strand, | |
| const std::string& service, const sdbusplus::message::object_path& path, | |
| ManagedStoreCb callback, std::optional<uint64_t> refresh_interval) { | |
| PostDbusCallToIoContextThreadSafe( | |
| strand, | |
| [this, callback{std::move(callback)}, refresh_interval]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::ManagedObjectType& objects) mutable { | |
| std::chrono::steady_clock::time_point timeNow = clockNow(); | |
| std::chrono::steady_clock::duration interval = | |
| refresh_interval.has_value() | |
| ? std::chrono::milliseconds(*refresh_interval) | |
| : this->config.tfixedThreshold; | |
| auto value = std::make_shared<ValueType>(objects, ec, timeNow, | |
| timeNow + interval); | |
| std::move(callback)(value); | |
| }, | |
| service, path.str, "org.freedesktop.DBus.ObjectManager", | |
| "GetManagedObjects"); | |
| } | |
| void ManagedObjectStore::getManagedMapperObjectFromDbusService( | |
| const std::shared_ptr<boost::asio::io_context::strand>& strand, | |
| const sdbusplus::message::object_path& path, | |
| const std::vector<std::string>& interfaces, ManagedStoreCb callback, | |
| std::optional<uint64_t> refresh_interval) { | |
| PostDbusCallToIoContextThreadSafe( | |
| strand, | |
| [this, callback{std::move(callback)}, refresh_interval]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::MapperGetObject& object) mutable { | |
| std::chrono::steady_clock::time_point timeNow = clockNow(); | |
| std::chrono::steady_clock::duration interval = | |
| refresh_interval.has_value() | |
| ? std::chrono::milliseconds(*refresh_interval) | |
| : this->config.tfixedThreshold; | |
| auto value = std::make_shared<ValueType>(object, ec, timeNow, | |
| timeNow + interval); | |
| std::move(callback)(value); | |
| }, | |
| "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper", | |
| "xyz.openbmc_project.ObjectMapper", "GetObject", path.str, interfaces); | |
| } | |
| void ManagedObjectStore::getSystemdUnitsFromDbusService( | |
| const std::shared_ptr<boost::asio::io_context::strand>& strand, | |
| ManagedStoreCb callback, std::optional<uint64_t> refresh_interval) { | |
| PostDbusCallToIoContextThreadSafe( | |
| strand, | |
| [this, callback{std::move(callback)}, refresh_interval]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::SystemdListUnits& listUnits) mutable { | |
| std::chrono::steady_clock::time_point timeNow = clockNow(); | |
| std::chrono::steady_clock::duration interval = | |
| refresh_interval.has_value() | |
| ? std::chrono::milliseconds(*refresh_interval) | |
| : this->config.tfixedThreshold; | |
| auto value = std::make_shared<ValueType>(listUnits, ec, timeNow, | |
| timeNow + interval); | |
| std::move(callback)(value); | |
| }, | |
| "org.freedesktop.systemd1", "/org/freedesktop/systemd1", | |
| "org.freedesktop.systemd1.Manager", "ListUnits"); | |
| } | |
| void ManagedObjectStore::getManagedPropertiesFromDbusService( | |
| const std::shared_ptr<boost::asio::io_context::strand>& strand, | |
| const std::string& service, const sdbusplus::message::object_path& path, | |
| const std::string& interface, const std::string& property, | |
| ManagedStoreCb callback, std::optional<uint64_t> refresh_interval) { | |
| PostDbusCallToIoContextThreadSafe( | |
| strand, | |
| [this, callback{std::move(callback)}, refresh_interval]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::DbusVariantType& propertyValue) mutable { | |
| std::chrono::steady_clock::time_point timeNow = clockNow(); | |
| std::chrono::steady_clock::duration interval = | |
| refresh_interval.has_value() | |
| ? std::chrono::milliseconds(*refresh_interval) | |
| : this->config.tfixedThreshold; | |
| auto value = std::make_shared<ValueType>(propertyValue, ec, timeNow, | |
| timeNow + interval); | |
| std::move(callback)(value); | |
| }, | |
| service, path.str, "org.freedesktop.DBus.Properties", "Get", interface, | |
| property); | |
| } | |
| void ManagedObjectStore::getManagedPropertiesMapFromDbusService( | |
| const std::shared_ptr<boost::asio::io_context::strand>& strand, | |
| const std::string& service, const sdbusplus::message::object_path& path, | |
| const std::string& interface, ManagedStoreCb callback, | |
| std::optional<uint64_t> refresh_interval) { | |
| PostDbusCallToIoContextThreadSafe( | |
| strand, | |
| [this, callback{std::move(callback)}, refresh_interval]( | |
| const boost::system::error_code ec, | |
| const ::dbus::utility::DBusPropertiesMap& properties) mutable { | |
| std::chrono::steady_clock::time_point timeNow = clockNow(); | |
| std::chrono::steady_clock::duration interval = | |
| refresh_interval.has_value() | |
| ? std::chrono::milliseconds(*refresh_interval) | |
| : this->config.tfixedThreshold; | |
| auto value = std::make_shared<ValueType>(properties, ec, timeNow, | |
| timeNow + interval); | |
| std::move(callback)(value); | |
| }, | |
| service, path, "org.freedesktop.DBus.Properties", "GetAll", interface); | |
| } | |
| // Calls into dbus service to get subtree. | |
| void ManagedObjectStore::getManagedSubtreeFromDbusService( | |
| const std::shared_ptr<boost::asio::io_context::strand>& strand, | |
| const sdbusplus::message::object_path& path, int32_t depth, | |
| const std::vector<std::string>& interfaces, ManagedStoreCb callback, | |
| std::optional<uint64_t> refresh_interval) { | |
| PostDbusCallToIoContextThreadSafe( | |
| strand, | |
| [this, callback(std::move(callback)), refresh_interval]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::MapperGetSubTreeResponse& subtree) mutable { | |
| std::chrono::steady_clock::time_point timeNow = clockNow(); | |
| std::chrono::steady_clock::duration interval = | |
| refresh_interval.has_value() | |
| ? std::chrono::milliseconds(*refresh_interval) | |
| : this->config.tfixedThreshold; | |
| auto value = std::make_shared<ValueType>(subtree, ec, timeNow, | |
| timeNow + interval); | |
| std::move(callback)(value); | |
| }, | |
| "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper", | |
| "xyz.openbmc_project.ObjectMapper", "GetSubTree", path.str, depth, | |
| interfaces); | |
| } | |
| // Calls into dbus service to get subtree paths. | |
| void ManagedObjectStore::getManagedSubtreePathsFromDbusService( | |
| const std::shared_ptr<boost::asio::io_context::strand>& strand, | |
| const sdbusplus::message::object_path& path, int32_t depth, | |
| const std::vector<std::string>& interfaces, ManagedStoreCb callback, | |
| std::optional<uint64_t> refresh_interval) { | |
| PostDbusCallToIoContextThreadSafe( | |
| strand, | |
| [this, callback(std::move(callback)), refresh_interval]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::MapperGetSubTreePathsResponse& | |
| subtreePaths) mutable { | |
| std::chrono::steady_clock::time_point timeNow = clockNow(); | |
| std::chrono::steady_clock::duration interval = | |
| refresh_interval.has_value() | |
| ? std::chrono::milliseconds(*refresh_interval) | |
| : this->config.tfixedThreshold; | |
| auto value = std::make_shared<ValueType>(subtreePaths, ec, timeNow, | |
| timeNow + interval); | |
| std::move(callback)(value); | |
| }, | |
| "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper", | |
| "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", path.str, depth, | |
| interfaces); | |
| } | |
| // Calls into dbus service to get subtree. | |
| void ManagedObjectStore::getManagedAssociatedSubtreeFromDbusService( | |
| const std::shared_ptr<boost::asio::io_context::strand>& strand, | |
| const sdbusplus::message::object_path& associatedPath, | |
| const sdbusplus::message::object_path& path, int32_t depth, | |
| const std::vector<std::string>& interfaces, ManagedStoreCb callback, | |
| std::optional<uint64_t> refresh_interval) { | |
| PostDbusCallToIoContextThreadSafe( | |
| strand, | |
| [this, callback(std::move(callback)), refresh_interval]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::MapperGetSubTreeResponse& subtree) mutable { | |
| std::chrono::steady_clock::time_point timeNow = clockNow(); | |
| std::chrono::steady_clock::duration interval = | |
| refresh_interval.has_value() | |
| ? std::chrono::milliseconds(*refresh_interval) | |
| : this->config.tfixedThreshold; | |
| auto value = std::make_shared<ValueType>(subtree, ec, timeNow, | |
| timeNow + interval); | |
| std::move(callback)(value); | |
| }, | |
| "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper", | |
| "xyz.openbmc_project.ObjectMapper", "GetAssociatedSubTree", | |
| associatedPath, path, depth, interfaces); | |
| } | |
| // Calls into dbus service to get subtree paths. | |
| void ManagedObjectStore::getManagedAssociatedSubtreePathsFromDbusService( | |
| const std::shared_ptr<boost::asio::io_context::strand>& strand, | |
| const sdbusplus::message::object_path& associatedPath, | |
| const sdbusplus::message::object_path& path, int32_t depth, | |
| const std::vector<std::string>& interfaces, ManagedStoreCb callback, | |
| std::optional<uint64_t> refresh_interval) { | |
| PostDbusCallToIoContextThreadSafe( | |
| strand, | |
| [this, callback(std::move(callback)), refresh_interval]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::MapperGetSubTreePathsResponse& | |
| subtreePaths) mutable { | |
| std::chrono::steady_clock::time_point timeNow = clockNow(); | |
| std::chrono::steady_clock::duration interval = | |
| refresh_interval.has_value() | |
| ? std::chrono::milliseconds(*refresh_interval) | |
| : this->config.tfixedThreshold; | |
| auto value = std::make_shared<ValueType>(subtreePaths, ec, timeNow, | |
| timeNow + interval); | |
| std::move(callback)(value); | |
| }, | |
| "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper", | |
| "xyz.openbmc_project.ObjectMapper", "GetAssociatedSubTreePaths", | |
| associatedPath, path, depth, interfaces); | |
| } | |
| std::string ManagedObjectStoreConfig::toString() const { | |
| return this->toJson().dump(); | |
| } | |
| void ManagedObjectStore::storeTimeTrace(const nlohmann::json& tTrace) { | |
| absl::MutexLock lock(time_trace_array_mutex_); | |
| if (time_trace_array_.size() >= this->config.timetrace_size_max) { | |
| time_trace_array_.erase(0); | |
| } | |
| time_trace_array_.push_back(tTrace); | |
| } | |
| void ManagedObjectStore::clearTimeTrace() { | |
| absl::MutexLock lock(time_trace_array_mutex_); | |
| time_trace_array_.clear(); | |
| } | |
| bool ManagedObjectStore::serialize(std::string_view filePath) { | |
| BMCWEB_LOG_STATEFUL_DEBUG << "starting Serialized"; | |
| nlohmann::json obj; | |
| if (!toJson(obj)) { | |
| BMCWEB_LOG_STATEFUL_DEBUG << "unable to serialize data ManagedObjectStore"; | |
| return false; | |
| } | |
| try { | |
| std::ofstream serializeFile(filePath.data(), std::ios::out); | |
| serializeFile << obj.dump(); | |
| } catch (const std::exception& e) { | |
| BMCWEB_LOG_STATEFUL_DEBUG << "unable write serialzed data to file"; | |
| BMCWEB_LOG_STATEFUL_DEBUG << "error message: " << e.what(); | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool ManagedObjectStore::deserialize(std::string_view filePath) { | |
| BMCWEB_LOG_STATEFUL_DEBUG << "starting Deserialized"; | |
| nlohmann::json obj; | |
| std::optional<std::string> file_string = | |
| serialize::ReadBinaryFileToString(filePath); | |
| if (!file_string.has_value()) { | |
| BMCWEB_LOG_STATEFUL_DEBUG << "unable read the deserialzed file"; | |
| return false; | |
| } | |
| obj = nlohmann::json::parse(file_string.value()); | |
| BMCWEB_LOG_STATEFUL_DEBUG << "json file parsed"; | |
| if (!fromJson(obj)) { | |
| BMCWEB_LOG_STATEFUL_DEBUG | |
| << "unable to deserialize ManagedObjectStore from json"; | |
| return false; | |
| } | |
| BMCWEB_LOG_STATEFUL_DEBUG << "ManagedObjectStore deserialized from json"; | |
| return true; | |
| } | |
| bool ManagedObjectStore::toJson(nlohmann::json& obj) { | |
| // turn data from ManagedObjectsMap to json | |
| nlohmann::json jsonManagedObjects = nlohmann::json::array(); | |
| absl::MutexLock lock(managed_objects_mutex_); | |
| for (ManagedObjectsMap::iterator itor = managedObjects.begin(); | |
| itor != managedObjects.end(); itor++) { | |
| nlohmann::json managedObject; | |
| managedObject["key"] = itor->first; | |
| managedObject["value"] = itor->second->serialize(); | |
| jsonManagedObjects.push_back(managedObject); | |
| } | |
| obj["managedObject"] = jsonManagedObjects; | |
| return true; | |
| } | |
| bool ManagedObjectStore::fromJson(const nlohmann::json& obj) { | |
| nlohmann::json jsonManagedObjects = obj["managedObject"]; | |
| for (nlohmann::json::iterator jmanagedObject = jsonManagedObjects.begin(); | |
| jmanagedObject != jsonManagedObjects.end(); jmanagedObject++) { | |
| try { | |
| nlohmann::json::reference ref = jmanagedObject->at("value"); | |
| std::shared_ptr<ValueType> val = std::make_shared<ValueType>(ref); | |
| std::string key = jmanagedObject->at("key"); | |
| absl::MutexLock lock(managed_objects_mutex_); | |
| managedObjects.insert({key, val}); | |
| } catch (const std::exception& e) { | |
| BMCWEB_LOG_STATEFUL_DEBUG | |
| << "unable deserialize, error building managedObjects"; | |
| BMCWEB_LOG_STATEFUL_DEBUG << "error message: " << e.what(); | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| nlohmann::json ManagedObjectStore::GetSubscriptionsToJSON() { | |
| nlohmann::json json; | |
| if (subscription_service_ == nullptr) { | |
| json["Error"] = "SubscriptionService is null"; | |
| return json; | |
| } | |
| return subscription_service_->GetSubscriptionsToJSON(); | |
| } | |
| nlohmann::json ManagedObjectStore::GetEventsToJSON() { | |
| nlohmann::json json; | |
| if (subscription_service_ == nullptr) { | |
| json["Error"] = "SubscriptionService is null"; | |
| return json; | |
| } | |
| return subscription_service_->GetEventsToJSON(); | |
| } | |
| nlohmann::json ManagedObjectStore::GetDBusMonitorsToJSON() { | |
| nlohmann::json json; | |
| nlohmann::json& monitors_json = json["DBusMonitors"]; | |
| monitors_json = nlohmann::json::array(); | |
| absl::MutexLock lock(signal_monitor_mutex_); | |
| for (const auto& [event_source_id, match] : signal_monitors_) { | |
| nlohmann::json m_json; | |
| m_json["EventSourceId"] = event_source_id.ToJSON(); | |
| monitors_json.push_back(m_json); | |
| } | |
| return json; | |
| } | |
| nlohmann::json ManagedObjectStore::GetEventsBySubscriptionIdToJSON( | |
| size_t subscription_id) { | |
| nlohmann::json json; | |
| if (subscription_service_ == nullptr) { | |
| json["Error"] = "SubscriptionService is null"; | |
| return json; | |
| } | |
| return subscription_service_->GetEventsBySubscriptionIdToJSON( | |
| subscription_id); | |
| } | |
| nlohmann::json ManagedObjectStore::ClearEventStore() { | |
| nlohmann::json json; | |
| if (subscription_service_ == nullptr) { | |
| json["Error"] = "SubscriptionService is null"; | |
| return json; | |
| } | |
| subscription_service_->ClearEventStore(); | |
| return subscription_service_->GetEventsToJSON(); | |
| } | |
| nlohmann::json ManagedObjectStoreConfig::toJson() const { | |
| nlohmann::json obj; | |
| obj["is_enabled"] = this->isEnabled; | |
| obj["pendingDbusResponsesMax"] = this->pendingDbusResponsesMax; | |
| obj["tfixedThresholdMilliseconds"] = | |
| clockDurationMilliseconds(this->tfixedThreshold); | |
| obj["tgraceThresholdMilliseconds"] = | |
| clockDurationMilliseconds(this->tgraceThreshold); | |
| obj["tLRUThresholdMilliseconds"] = | |
| clockDurationMilliseconds(this->tLRUThreshold); | |
| obj["is_timetrace_enabled"] = this->timetrace; | |
| obj["timetrace_size_max"] = this->timetrace_size_max; | |
| return obj; | |
| } | |
| std::string KeyType::GetId() const { | |
| // Convert interface list to string | |
| std::string interface_list_to_str; | |
| for (const std::string& interface_str : interfaceList) { | |
| AppendWithSep(interface_list_to_str, ',', interface_str); | |
| } | |
| std::string ret; | |
| switch (managedElementType) { | |
| case ManagedType::kManagedObject: { | |
| AppendWithSep(ret, kSep, "kManagedObject", serviceName, objectPath.str); | |
| break; | |
| } | |
| case ManagedType::kManagedPropertyMap: { | |
| AppendWithSep(ret, kSep, "kManagedPropertyMap", serviceName, | |
| objectPath.str, interface); | |
| break; | |
| } | |
| case ManagedType::kManagedProperty: { | |
| AppendWithSep(ret, kSep, "kManagedProperty", serviceName, objectPath.str, | |
| interface, property); | |
| break; | |
| } | |
| case ManagedType::kManagedMapperObject: { | |
| AppendWithSep(ret, kSep, "kManagedMapperObject", objectPath.str, | |
| interface_list_to_str); | |
| break; | |
| } | |
| case ManagedType::kManagedSystemdListUnits: { | |
| AppendWithSep(ret, kSep, "kManagedSystemdListUnits"); | |
| break; | |
| } | |
| case ManagedType::kManagedSubtree: { | |
| AppendWithSep(ret, kSep, "kManagedSubtree", objectPath.str, | |
| std::to_string(treeDepth), interface_list_to_str); | |
| break; | |
| } | |
| case ManagedType::kManagedSubtreePaths: { | |
| AppendWithSep(ret, kSep, "kManagedSubtreePaths", objectPath.str, | |
| std::to_string(treeDepth), interface_list_to_str); | |
| break; | |
| } | |
| case ManagedType::kManagedAssociatedSubtree: { | |
| AppendWithSep(ret, kSep, "kManagedAssociatedSubtree", objectPath.str, | |
| std::to_string(treeDepth), interface_list_to_str, | |
| associatedPath.str); | |
| break; | |
| } | |
| case ManagedType::kManagedAssociatedSubtreePaths: { | |
| AppendWithSep(ret, kSep, "kManagedAssociatedSubtreePaths", objectPath.str, | |
| std::to_string(treeDepth), interface_list_to_str, | |
| associatedPath.str); | |
| break; | |
| } | |
| } | |
| return ret; | |
| } | |
| inline std::size_t GetEstimatedJsonSize(const nlohmann::json& root) { | |
| if (root.is_null()) { | |
| return 0; | |
| } | |
| if (root.is_number()) { | |
| return 8; | |
| } | |
| if (root.is_boolean()) { | |
| return 1; | |
| } | |
| if (root.is_string()) { | |
| return root.get<std::string>().size(); | |
| } | |
| if (root.is_binary()) { | |
| return root.get_binary().size(); | |
| } | |
| const nlohmann::json::array_t* arr = | |
| root.get_ptr<const nlohmann::json::array_t*>(); | |
| if (arr != nullptr) { | |
| std::size_t sum = 0; | |
| for (const auto& element : *arr) { | |
| sum += GetEstimatedJsonSize(element); | |
| } | |
| return sum; | |
| } | |
| const nlohmann::json::object_t* object = | |
| root.get_ptr<const nlohmann::json::object_t*>(); | |
| if (object != nullptr) { | |
| std::size_t sum = 0; | |
| for (const auto& [k, v] : *object) { | |
| sum += k.size() + GetEstimatedJsonSize(v); | |
| } | |
| return sum; | |
| } | |
| return 0; | |
| } | |
| std::size_t ManagedObjectStore::GetSnapshotSizeInBytes() const { | |
| std::size_t bytes = 0; | |
| absl::MutexLock lock(managed_objects_mutex_); | |
| for (const auto& iter : managedObjects) { | |
| const std::string& key = iter.first; | |
| bytes += key.size(); // Not using capacity here for simplicity | |
| const ValueType& value = *iter.second; | |
| bytes += GetEstimatedJsonSize( | |
| value.serialize()); // This might be over-estimate but it is the | |
| // simpliest way to give us a quick number without | |
| // developing complicated code that visit all types | |
| } | |
| return bytes; | |
| } | |
| // Returns a json object with metrics about the managed objects. | |
| nlohmann::json ManagedObjectStore::GetManagedObjectsMetrics( | |
| std::chrono::steady_clock::time_point now) const { | |
| constexpr int64_t kNumBuckets = 20; | |
| constexpr int64_t kBucketSize = 1000; | |
| nlohmann::json managed_objects_age_histograms = nlohmann::json::array(); | |
| for (size_t i = 0; i < kNumBuckets; i++) { | |
| nlohmann::json bucket; | |
| bucket["Start"] = i * kBucketSize; | |
| bucket["Count"] = 0; | |
| managed_objects_age_histograms.push_back(bucket); | |
| } | |
| nlohmann::json managed_objects = nlohmann::json::array(); | |
| int64_t max_object_age_ms = 0; | |
| int64_t max_last_served_age_ms = 0; | |
| int64_t count_last_served = 0; | |
| uint64_t sum_sotal_object_refreshes = 0; | |
| uint64_t sum_total_object_refresh_scheduled = 0; | |
| uint64_t sum_total_object_used = 0; | |
| int key_index = 0; | |
| { | |
| absl::MutexLock lock(managed_objects_mutex_); | |
| for (const auto& iter : managedObjects) { | |
| const std::string& key = iter.first; | |
| const ValueType& value = *iter.second; | |
| nlohmann::json obj; | |
| obj["@odata.id"] = | |
| "/redfish/v1/Managers/bmc/ManagerDiagnosticData/Oem/Google/" | |
| "GoogleManagedObjectStoreMetrics#/ManagedObjects/" + | |
| std::to_string(key_index); | |
| obj["@odata.type"] = | |
| "#GoogleManagedObjectMetrics.v1_0_0.GoogleManagedObjectMetrics"; | |
| // Split the key: | |
| std::string service_name; | |
| std::string object_path; | |
| std::string interface; | |
| std::string property; | |
| KeyType::fromKey(key, service_name, object_path, interface, property); | |
| obj["Id"] = key_index; | |
| obj["ServiceName"] = service_name; | |
| obj["ObjectPath"] = object_path; | |
| obj["Interface"] = interface; | |
| obj["Property"] = property; | |
| obj["Age"] = clockSinceMilliseconds(value.lastRefreshAt, now); | |
| obj["RefreshCountDone"] = value.numRefreshesDone; | |
| obj["RefreshCountScheduled"] = value.numRefreshesScheduled; | |
| obj["NumTimesUsed"] = value.numTimesUsed; | |
| key_index++; | |
| // sum up the counts: | |
| sum_sotal_object_refreshes += value.numRefreshesDone; | |
| sum_total_object_refresh_scheduled += value.numRefreshesScheduled; | |
| sum_total_object_used += value.numTimesUsed; | |
| nlohmann::json last_used_ages_obj = nlohmann::json::array(); | |
| if (value.lastUsedAges) { | |
| std::for_each( | |
| value.lastUsedAges->begin(), value.lastUsedAges->end(), | |
| [&last_used_ages_obj, &count_last_served, &max_last_served_age_ms]( | |
| const std::chrono::steady_clock::duration& dur) { | |
| const int64_t lastUsedDurationMs = clockDurationMilliseconds(dur); | |
| // report the last served ages: | |
| last_used_ages_obj.push_back(lastUsedDurationMs); | |
| // total count: | |
| count_last_served += 1; | |
| // track the max: | |
| max_last_served_age_ms = | |
| std::max(max_last_served_age_ms, lastUsedDurationMs); | |
| }); | |
| } | |
| obj["AgeLastServed"] = last_used_ages_obj; | |
| // object age histogram: | |
| const int64_t object_age_ms = | |
| clockSinceMilliseconds(value.lastRefreshAt, now); | |
| if (object_age_ms >= 0) { | |
| int64_t bucket_index = | |
| std::min(object_age_ms / kBucketSize, kNumBuckets); | |
| if (bucket_index >= 0) { | |
| // last buckets catches all: | |
| if (bucket_index >= kNumBuckets) { | |
| bucket_index = (kNumBuckets - 1); | |
| } | |
| const auto i = static_cast<size_t>(bucket_index); | |
| auto count = managed_objects_age_histograms[i]["Count"] | |
| .get_ref<nlohmann::json::number_integer_t&>(); | |
| managed_objects_age_histograms[i]["Count"] = count + 1; | |
| } | |
| // max age: | |
| max_object_age_ms = std::max(max_object_age_ms, object_age_ms); | |
| } | |
| managed_objects.push_back(obj); | |
| } | |
| } | |
| nlohmann::json google; | |
| google["@odata.id"] = | |
| "/redfish/v1/Managers/bmc/ManagerDiagnosticData/Oem/Google/" | |
| "GoogleManagedObjectStoreMetrics"; | |
| google["@odata.type"] = | |
| "#GoogleManagedObjectStoreMetrics.v1_0_0." | |
| "GoogleManagedObjectStoreMetrics"; | |
| // Age Buckets: | |
| google["ManagedObjectsAgeHistograms"]["AgeBuckets"] = | |
| managed_objects_age_histograms; | |
| // Age Max: | |
| google["ManagedObjectsAgeHistograms"]["AgeMax"] = max_object_age_ms; | |
| // Last Served Count: | |
| google["ManagedObjectsAgeHistograms"]["LastServedCount"] = count_last_served; | |
| google["ManagedObjectsAgeHistograms"]["LastServedMax"] = | |
| max_last_served_age_ms; | |
| // Total num of refreshes: | |
| google["NumRefreshesDone"] = sum_sotal_object_refreshes; | |
| google["NumRefreshesScheduled"] = sum_total_object_refresh_scheduled; | |
| // Total num used: | |
| google["NumTimesUsed"] = sum_total_object_used; | |
| // ManagedStore counters from managedStoreTracker | |
| const ManagedStoreTracker store_tracker = getStoreTracker(); | |
| google["NumGetManagedObjects"] = store_tracker.countGetManagedObjects; | |
| google["NumGetManagedObjectsCacheMiss"] = | |
| store_tracker.countGetManagedObjectsCacheMiss; | |
| google["NumGetManagedObjectsCacheHit"] = | |
| store_tracker.countGetManagedObjectsCacheHit; | |
| google["NumGetManagedObjectsCacheMissMaxAge"] = | |
| store_tracker.countGetManagedObjectsCacheMissMaxAge; | |
| // Export max pending and the threshold: | |
| google["PendingResponsesMax"] = store_tracker.countMaxPendingDbusResponses; | |
| google["PendingResponsesThreshold"] = | |
| store_tracker.countThresholdPendingResponses; | |
| // objects: (will probably remove from none debug builds) | |
| google["ManagedObjects"] = managed_objects; | |
| google["ManagedObjectsSize"] = managed_objects.size(); | |
| // final object: | |
| return google; | |
| } | |
| } // namespace managedStore |