| #include "managed_store.hpp" |
| |
| #include "dbus_utility.hpp" |
| |
| #include <boost/asio/io_context.hpp> |
| #include <boost/system/error_code.hpp> |
| #include <sdbusplus/asio/property.hpp> |
| #include <sdbusplus/message/native_types.hpp> |
| |
| // TODO:: move these to a separate file (future CL when we have more stores): |
| namespace managedStore |
| { |
| |
| // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) |
| ManagedObjectStore* managedObjectStore = nullptr; |
| |
| // TODO:: can ^ be a std::shared_ptr on `static ManagedObjectStore::instance()` |
| // when we implement graceful shutdowns |
| |
| void ManagedObjectStore::processDbusResponse(KeyType keyType, |
| const ValueType& managedValue, |
| bool isReadThroughRequest) |
| { |
| ValueType value(managedValue); |
| std::string keyTypeStr = keyType.toString(); |
| |
| BMCWEB_LOG_STATEFUL_DEBUG << "Updating store with fresh " |
| << " Object: " << keyTypeStr |
| << " Value: " << value.toString() |
| << " ec: " << value.errorCode.message(); |
| |
| // Initialize numTimesUsed: |
| value.numTimesUsed = 1; |
| auto currentObjectIter = managedObjects.find(keyTypeStr); |
| if (currentObjectIter != managedObjects.end()) |
| { |
| // preserve the lastUsed: |
| value.lastUsed = currentObjectIter->second.lastUsed; |
| // preserve the lastUsedAges: |
| value.lastUsedAges = currentObjectIter->second.lastUsedAges; |
| // preserve the numTimesUsed: |
| value.numTimesUsed = currentObjectIter->second.numTimesUsed; |
| // an shared / unique pointer) count refreshes: |
| value.numRefreshesDone = currentObjectIter->second.numRefreshesDone + 1; |
| // count the scheduled refreshes: |
| value.numRefreshesScheduled = |
| currentObjectIter->second.numRefreshesScheduled; |
| |
| BMCWEB_LOG_STATEFUL_DEBUG << "Updating Current Object:" |
| << " Key: " << keyTypeStr |
| << " Value: " << value.toString(); |
| } |
| |
| // Avoid queuing a refresh if a Pq entry exists for the key. |
| if (!pqEntrySet.contains(keyTypeStr)) |
| { |
| // Queue refresh operation |
| pq.push(PriorityQueueEntry(keyType, value.nextRefreshAt)); |
| // Track pq entry |
| pqEntrySet.insert(keyTypeStr); |
| } |
| |
| // upsert the key: |
| managedObjects.insert_or_assign(keyTypeStr, value); |
| |
| BMCWEB_LOG_STATEFUL_DEBUG |
| << "Updated Object:" |
| << " Object: " << keyTypeStr |
| << " New: " << managedObjects.find(keyTypeStr)->second.toString(); |
| |
| // Update pending dbus response count on receiving response. |
| this->managedStoreTracker.decrementPendingDbusResponses( |
| isReadThroughRequest); |
| |
| auto cbMapIter = requestCbMap.find(keyTypeStr); |
| if (cbMapIter != requestCbMap.end()) |
| { |
| std::queue<ManagedStoreCb>& cbQueue = cbMapIter->second; |
| while (!cbQueue.empty()) |
| { |
| auto callback = cbQueue.front(); |
| callback(value); |
| cbQueue.pop(); |
| } |
| requestCbMap.erase(cbMapIter->first); |
| } |
| |
| // Finally, call scheduler to queue up next refresh operations. |
| scheduleRefresh(); |
| } |
| |
| void ManagedObjectStore::refreshObject(const KeyType& key, |
| bool isReadThroughRequest) |
| { |
| auto managedObjectIter = this->managedObjects.find(key.toString()); |
| if (managedObjectIter != this->managedObjects.end()) |
| { |
| managedObjectIter->second.numRefreshesScheduled += 1; |
| |
| BMCWEB_LOG_STATEFUL_DEBUG |
| << "refreshObject:" |
| << " Key: " << managedObjectIter->first |
| << " value: " << managedObjectIter->second.toString(); |
| } |
| |
| switch (key.getManagedType()) |
| { |
| case ManagedType::kManagedObject: |
| { |
| getManagedObjectsFromDbusService( |
| key.serviceName, key.objectPath, |
| [this, self = shared_from_this(), keyType = key, |
| isReadThroughRequest](const ValueType& managedValue) mutable { |
| processDbusResponse(std::move(keyType), managedValue, |
| isReadThroughRequest); |
| }); |
| break; |
| } |
| case ManagedType::kManagedPropertyMap: |
| { |
| getManagedPropertiesMapFromDbusService( |
| key.serviceName, key.objectPath, key.interface, |
| [this, self = shared_from_this(), keyType = key, |
| isReadThroughRequest](const ValueType& managedValue) mutable { |
| processDbusResponse(std::move(keyType), managedValue, |
| isReadThroughRequest); |
| }); |
| break; |
| } |
| case ManagedType::kManagedProperty: |
| { |
| getManagedPropertiesFromDbusService( |
| key.serviceName, key.objectPath, key.interface, key.property, |
| [this, self = shared_from_this(), keyType = key, |
| isReadThroughRequest](const ValueType& managedValue) mutable { |
| processDbusResponse(std::move(keyType), managedValue, |
| isReadThroughRequest); |
| }); |
| break; |
| } |
| case ManagedType::kManagedSubtree: |
| { |
| getManagedSubtreeFromDbusService( |
| key.objectPath, key.treeDepth, key.interfaceList, |
| [this, self = shared_from_this(), keyType = key, |
| isReadThroughRequest](const ValueType& managedValue) mutable { |
| processDbusResponse(std::move(keyType), managedValue, |
| isReadThroughRequest); |
| }); |
| break; |
| } |
| case ManagedType::kManagedSubtreePaths: |
| { |
| getManagedSubtreePathsFromDbusService( |
| key.objectPath, key.treeDepth, key.interfaceList, |
| [this, self = shared_from_this(), keyType = key, |
| isReadThroughRequest](const ValueType& managedValue) mutable { |
| processDbusResponse(std::move(keyType), managedValue, |
| isReadThroughRequest); |
| }); |
| break; |
| } |
| case ManagedType::kManagedAssociatedSubtree: |
| { |
| getManagedAssociatedSubtreeFromDbusService( |
| key.associatedPath, key.objectPath, key.treeDepth, |
| key.interfaceList, |
| [this, self = shared_from_this(), keyType = key, |
| isReadThroughRequest](const ValueType& managedValue) mutable { |
| processDbusResponse(std::move(keyType), managedValue, |
| isReadThroughRequest); |
| }); |
| break; |
| } |
| case ManagedType::kManagedAssociatedSubtreePaths: |
| { |
| getManagedAssociatedSubtreePathsFromDbusService( |
| key.associatedPath, key.objectPath, key.treeDepth, |
| key.interfaceList, |
| [this, self = shared_from_this(), keyType = key, |
| isReadThroughRequest](const ValueType& managedValue) mutable { |
| processDbusResponse(std::move(keyType), managedValue, |
| isReadThroughRequest); |
| }); |
| break; |
| } |
| } |
| |
| // Record a pending refresh operation. |
| this->managedStoreTracker.incrementPendingDbusResponses( |
| isReadThroughRequest); |
| } |
| |
| bool ManagedObjectStore::canScheduleRefresh() |
| { |
| // The timer callback will schedule the next refresh if the scheduler is |
| // active. |
| if (this->isSchedulerActive) |
| { |
| return false; |
| } |
| |
| // Cannot schedule refresh if pending dbus responses reach threshold. |
| if (this->managedStoreTracker.countPendingDbusResponses >= |
| this->config.pendingDbusResponsesMax) |
| { |
| BMCWEB_LOG_STATEFUL_DEBUG << "scheduleRefresh: Pending io " |
| << "threshold reached."; |
| return false; |
| } |
| |
| // Scan the priority queue to pop the entries that are no longer used |
| // or can be refreshed instantly to get to a schedulable entry. |
| std::chrono::steady_clock::time_point timeNow = clockNow(); |
| bool canSchedule = false; |
| while (!canSchedule && !this->pq.empty()) |
| { |
| const PriorityQueueEntry& pqEntry = pq.top(); |
| |
| // Get managedStore key to queue refresh of corresponding object in |
| // the managedStore. |
| const KeyType& managedStoreKey = pqEntry.managedStoreKey; |
| const std::string managedStoreKeyStr = managedStoreKey.toString(); |
| auto managedObjectIter = managedObjects.find(managedStoreKeyStr); |
| |
| // the object is no longer tracked: |
| if (managedObjectIter == managedObjects.end()) |
| { |
| pq.pop(); |
| pqEntrySet.erase(managedStoreKeyStr); |
| continue; |
| } |
| |
| ValueType& keyValue = managedObjectIter->second; |
| const auto durationSinceUsed = clockSince(keyValue.lastUsed, timeNow); |
| bool isRecentlyUsed = durationSinceUsed < this->config.tLRUThreshold; |
| |
| BMCWEB_LOG_STATEFUL_DEBUG |
| << "SinceUsed:" |
| << " Key: " << managedStoreKeyStr << " durationSinceUsed: " |
| << clockSinceMilliseconds(keyValue.lastUsed, timeNow) |
| << " isRecentlyUsed: " << isRecentlyUsed; |
| // LRU Eviction: |
| if (!isRecentlyUsed) |
| { |
| BMCWEB_LOG_STATEFUL_DEBUG << "Since Used: Evict:" |
| << " Key: " << managedStoreKeyStr; |
| managedObjects.erase(managedObjectIter->first); |
| pq.pop(); |
| pqEntrySet.erase(managedStoreKeyStr); |
| continue; |
| } |
| |
| // If the object corresponding to pqEntry was recently refreshed, |
| // re-queue the entry to schedule refresh for later point in time. |
| if (keyValue.nextRefreshAt > pqEntry.nextRefreshAt) |
| { |
| BMCWEB_LOG_STATEFUL_DEBUG << "PQ entry recently refreshed: " |
| << "Re-queue Key: " << managedStoreKeyStr; |
| // Remember associated managedStore key before we pop the pq entry. |
| const KeyType key = managedStoreKey; |
| pq.pop(); |
| pq.push(PriorityQueueEntry(key, keyValue.nextRefreshAt)); |
| continue; |
| } |
| canSchedule = true; |
| } |
| return canSchedule; |
| } |
| |
| void ManagedObjectStore::scheduleRefresh() |
| { |
| if (!this->canScheduleRefresh()) |
| { |
| return; |
| } |
| |
| BMCWEB_LOG_STATEFUL_DEBUG |
| << "scheduleRefresh: " << pq.size() << " Pending dbus responses: " |
| << this->managedStoreTracker.countPendingDbusResponses; |
| |
| PriorityQueueEntry pqEntry = pq.top(); |
| pq.pop(); |
| |
| std::chrono::steady_clock::time_point timeNow = clockNow(); |
| |
| // Get service name and object path to schedule refresh of the corresponding |
| // object in store. |
| const KeyType& managedStoreKey = pqEntry.managedStoreKey; |
| std::string managedStoreKeyStr = managedStoreKey.toString(); |
| |
| // Stop tracking the pq entry once it is pop'd from the queue. |
| pqEntrySet.erase(managedStoreKeyStr); |
| |
| // 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. " |
| << "ServiceName: " << managedStoreKey.serviceName |
| << " ObjectPath: " << managedStoreKey.objectPath.str |
| << " Interface: " << managedStoreKey.interface << " Property: " |
| << managedStoreKey.property; |
| |
| timer.async_wait([this, self = shared_from_this(), |
| managedStoreKey](boost::system::error_code ec) { |
| isSchedulerActive = false; |
| if (ec == boost::asio::error::operation_aborted) |
| { |
| BMCWEB_LOG_STATEFUL_ERROR << "Timer operation aborted" << ec; |
| return; |
| } |
| |
| if (ec) |
| { |
| BMCWEB_LOG_STATEFUL_ERROR << "Async_wait failed" << ec; |
| return; |
| } |
| BMCWEB_LOG_STATEFUL_DEBUG |
| << "Refreshing managed object. " |
| << "ServiceName: " << managedStoreKey.serviceName |
| << " ObjectPath: " << managedStoreKey.objectPath.str |
| << " Interface: " << managedStoreKey.interface << " Property: " |
| << managedStoreKey.property; |
| refreshObject(managedStoreKey); |
| scheduleRefresh(); |
| }); |
| } |
| |
| void ManagedObjectStore::getProperty( |
| const KeyType& keyType, const ManagedObjectStoreContext& requestContext, |
| GetManagedPropertyCb&& callback) |
| { |
| // When Object Store is disabled, the requests read through. |
| if (!this->isEnabled()) |
| { |
| dbus::utility::getProperty(keyType.serviceName, keyType.objectPath, |
| keyType.interface, keyType.property, |
| std::move(callback)); |
| return; |
| } |
| |
| getManagedObjectsInStore( |
| keyType, requestContext, |
| [callback{std::move(callback)}](const ValueType& valueType) { |
| if (!valueType.managedProperty.has_value()) |
| { |
| callback(valueType.errorCode, {}); |
| return; |
| } |
| callback(valueType.errorCode, valueType.managedProperty.value()); |
| }); |
| } |
| |
| 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()) |
| { |
| sdbusplus::asio::getAllProperties(*crow::connections::systemBus, |
| service, objectPath, interface, |
| std::move(callback)); |
| return; |
| } |
| |
| KeyType keyType(ManagedType::kManagedPropertyMap, service, objectPath, |
| interface); |
| getManagedObjectsInStore( |
| keyType, requestContext, |
| [callback{std::move(callback)}](const ValueType& valueType) { |
| if (!valueType.managedPropertyMap.has_value()) |
| { |
| callback(valueType.errorCode, {}); |
| return; |
| } |
| callback(valueType.errorCode, valueType.managedPropertyMap.value()); |
| }); |
| } |
| |
| 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()) |
| { |
| crow::connections::systemBus->async_method_call( |
| [callback{std::move(callback)}]( |
| const boost::system::error_code& ec, |
| const dbus::utility::MapperGetSubTreeResponse& subtree) { |
| callback(ec, subtree); |
| }, |
| "xyz.openbmc_project.ObjectMapper", |
| "/xyz/openbmc_project/object_mapper", |
| "xyz.openbmc_project.ObjectMapper", "GetSubTree", path, depth, |
| interfaces); |
| return; |
| } |
| |
| KeyType keyType(ManagedType::kManagedSubtree, path, depth, interfaces); |
| getManagedObjectsInStore( |
| keyType, requestContext, |
| [callback{std::move(callback)}](const ValueType& valueType) { |
| if (!valueType.managedSubtree.has_value()) |
| { |
| callback(valueType.errorCode, {}); |
| return; |
| } |
| 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 (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 (auto& s : interfaces) |
| { |
| interfaces2.emplace_back(s); |
| } |
| |
| // When Object Store is disabled, the requests read through. |
| if (!this->isEnabled()) |
| { |
| crow::connections::systemBus->async_method_call( |
| [callback{std::move(callback)}]( |
| const boost::system::error_code& ec, |
| const dbus::utility::MapperGetSubTreePathsResponse& |
| subtreePaths) { callback(ec, subtreePaths); }, |
| "xyz.openbmc_project.ObjectMapper", |
| "/xyz/openbmc_project/object_mapper", |
| "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", path, depth, |
| interfaces2); |
| return; |
| } |
| |
| KeyType keyType(ManagedType::kManagedSubtreePaths, path, depth, |
| interfaces2); |
| getManagedObjectsInStore( |
| keyType, requestContext, |
| [callback{std::move(callback)}](const ValueType& valueType) { |
| if (!valueType.managedSubtreePaths.has_value()) |
| { |
| callback(valueType.errorCode, {}); |
| return; |
| } |
| 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 (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; |
| for (auto& s : interfaces) |
| { |
| interfaces2.emplace_back(s); |
| } |
| |
| // When Object Store is disabled, the requests read through. |
| if (!this->isEnabled()) |
| { |
| crow::connections::systemBus->async_method_call( |
| [callback{std::move(callback)}]( |
| const boost::system::error_code& ec, |
| const dbus::utility::MapperGetSubTreeResponse& subtree) { |
| callback(ec, subtree); |
| }, |
| "xyz.openbmc_project.ObjectMapper", |
| "/xyz/openbmc_project/object_mapper", |
| "xyz.openbmc_project.ObjectMapper", "GetAssociatedSubTree", |
| associatedPath, path, depth, interfaces2); |
| return; |
| } |
| |
| KeyType keyType(ManagedType::kManagedAssociatedSubtree, associatedPath.str, |
| path.str, depth, interfaces2); |
| getManagedObjectsInStore( |
| keyType, requestContext, |
| [callback{std::move(callback)}](const ValueType& valueType) { |
| if (!valueType.managedSubtree.has_value()) |
| { |
| callback(valueType.errorCode, {}); |
| return; |
| } |
| 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 (auto& s : interfaces) |
| { |
| interfaces2.emplace_back(s); |
| } |
| |
| // When Object Store is disabled, the requests read through. |
| if (!this->isEnabled()) |
| { |
| crow::connections::systemBus->async_method_call( |
| [callback{std::move(callback)}]( |
| const boost::system::error_code& ec, |
| const dbus::utility::MapperGetSubTreePathsResponse& |
| subtreePaths) { callback(ec, subtreePaths); }, |
| "xyz.openbmc_project.ObjectMapper", |
| "/xyz/openbmc_project/object_mapper", |
| "xyz.openbmc_project.ObjectMapper", "GetAssociatedSubTreePaths", |
| associatedPath, path, depth, interfaces); |
| return; |
| } |
| |
| KeyType keyType(ManagedType::kManagedAssociatedSubtreePaths, |
| associatedPath.str, path.str, depth, interfaces2); |
| getManagedObjectsInStore( |
| keyType, requestContext, |
| [callback{std::move(callback)}](const ValueType& valueType) { |
| if (!valueType.managedSubtreePaths.has_value()) |
| { |
| callback(valueType.errorCode, {}); |
| return; |
| } |
| callback(valueType.errorCode, valueType.managedSubtreePaths.value()); |
| }); |
| } |
| |
| void ManagedObjectStore::getManagedObjects( |
| const std::string& service, const sdbusplus::message::object_path& path, |
| GetManagedObjectsCb&& callback) |
| { |
| BMCWEB_LOG_STATEFUL_DEBUG << "getManagedObjects:" |
| << " service: " << service |
| << " path: " << path.str; |
| // std::shared_ptr<bmcweb::AsyncResp> resp; |
| ManagedObjectStoreContext context(nullptr); |
| getManagedObjectsWithContext(service, path, context, std::move(callback)); |
| } |
| |
| void ManagedObjectStore::getManagedObjectsWithContext( |
| const std::string& service, const sdbusplus::message::object_path& path, |
| const ManagedObjectStoreContext& requestContext, |
| GetManagedObjectsCb&& callback) |
| { |
| // total counter of calls: |
| this->managedStoreTracker.countGetManagedObjects += 1; |
| |
| BMCWEB_LOG_STATEFUL_DEBUG |
| << "getManagedObjectsWithContext:" |
| << " service: " << service << " path: " << path.str |
| << " requestContext: " << requestContext.toString() |
| << " count: " << this->managedStoreTracker.countGetManagedObjects; |
| |
| // 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? |
| this->managedStoreTracker.countGetManagedObjectsCacheMiss += 1; |
| dbus::utility::getManagedObjects(service, path, std::move(callback)); |
| return; |
| } |
| |
| KeyType keyType(ManagedType::kManagedObject, service, path.str); |
| getManagedObjectsInStore( |
| keyType, requestContext, |
| [callback{std::move(callback)}](const ValueType& valueType) { |
| if (valueType.managedObject.has_value()) |
| { |
| callback(valueType.errorCode, valueType.managedObject.value()); |
| return; |
| } |
| callback(valueType.errorCode, {}); |
| }); |
| } |
| |
| void ManagedObjectStore::getManagedObjectsInStore( |
| const KeyType& keyType, const ManagedObjectStoreContext& requestContext, |
| ManagedStoreCb&& callback) |
| { |
| BMCWEB_LOG_STATEFUL_DEBUG |
| << "getManagedObjectsInStore:" |
| << " service: " << keyType.serviceName |
| << " path: " << keyType.objectPath.str << " interface: " |
| << keyType.interface << " property: " << keyType.property |
| << " requestContext: " << requestContext.toString(); |
| |
| // Execute callback immidiately if object is cached in store. |
| std::string keyTypeStr = keyType.toString(); |
| auto managedObjectIter = managedObjects.find(keyTypeStr); |
| if (managedObjectIter != managedObjects.end()) |
| { |
| ValueType& mappedValueInStore = managedObjectIter->second; |
| const std::chrono::steady_clock::time_point timeNow = clockNow(); |
| const auto currentAge = |
| clockSince(mappedValueInStore.lastRefreshAt, timeNow); |
| |
| // update lastUsed: |
| mappedValueInStore.lastUsed = timeNow; |
| // count number of times used: |
| mappedValueInStore.numTimesUsed += 1; |
| // update lastUsedAges: |
| if (!mappedValueInStore.lastUsedAges) |
| { |
| mappedValueInStore.lastUsedAges = std::make_shared< |
| std::list<std::chrono::steady_clock::duration>>(); |
| } |
| mappedValueInStore.lastUsedAges->push_back(currentAge); |
| while (mappedValueInStore.lastUsedAges->size() >= |
| mappedValueInStore.kLastUsedAgesMaxSize) |
| { |
| mappedValueInStore.lastUsedAges->pop_front(); |
| } |
| |
| BMCWEB_LOG_STATEFUL_DEBUG |
| << "ManagedObject found in store. " |
| << " service: " << keyType.serviceName |
| << " path: " << keyType.objectPath.str << " interface: " |
| << keyType.interface << " property: " << keyType.property |
| << " CurrentAge: " << clockDurationMilliseconds(currentAge) |
| << " Context: " << requestContext.toString() |
| << " Value: " << mappedValueInStore.toString(); |
| |
| // check if the current object is stale: |
| const bool isObjectStale = currentAge >= (this->config.tfixedThreshold + |
| this->config.tgraceThreshold); |
| |
| // can we use it as is: |
| bool isUseCurrentObject = !isObjectStale; |
| |
| if (requestContext.hintMaxAge > std::chrono::seconds(0)) |
| { |
| // we have to refresh it because of hintMaxAge |
| isUseCurrentObject = (currentAge <= requestContext.hintMaxAge); |
| this->managedStoreTracker.countGetManagedObjectsCacheMissMaxAge += |
| 1; |
| BMCWEB_LOG_STATEFUL_DEBUG |
| << "apply:" |
| << " hintMaxAgeMS: " |
| << clockDurationMilliseconds(requestContext.hintMaxAge); |
| } |
| |
| // Return if the object is still fresh. |
| if (isUseCurrentObject) |
| { |
| this->managedStoreTracker.countGetManagedObjectsCacheHit += 1; |
| callback(mappedValueInStore); |
| return; |
| } |
| |
| // Evict the object from store as it has became stale. |
| if (isObjectStale) |
| { |
| BMCWEB_LOG_ALWAYS |
| << "Object has become stale! " |
| << " service: " << keyType.serviceName |
| << " path: " << keyType.objectPath.str << " interface: " |
| << keyType.interface << " property: " << keyType.property |
| << " CurrentAge: " << clockDurationMilliseconds(currentAge) |
| << " Context: " << requestContext.toString() |
| << " Value: " << mappedValueInStore.toString(); |
| managedObjects.erase(managedObjectIter->first); |
| } |
| } |
| |
| BMCWEB_LOG_STATEFUL_DEBUG |
| << "Fetching fresh object over dbus. " |
| << " Key: " << keyType.toString() |
| << " requestContext: " << requestContext.toString(); |
| |
| // Handle concurrent requests for the same object by storing callback |
| // for the request. |
| auto cbMapIter = requestCbMap.find(keyTypeStr); |
| if (cbMapIter != requestCbMap.end()) |
| { |
| // Since a request has already read through, any subsequent request |
| // will not read through. The callback for these duplicate requests |
| // will be stored and invoked when the first request gets a response |
| // from dbus. |
| cbMapIter->second.push(std::move(callback)); |
| return; |
| } |
| requestCbMap[keyTypeStr].push(std::move(callback)); |
| |
| this->managedStoreTracker.countGetManagedObjectsCacheMiss += 1; |
| |
| // 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. |
| refreshObject(keyType, true); |
| } |
| |
| void ManagedObjectStore::clearAllObjects() |
| { |
| this->managedObjects.clear(); |
| // looks like it's the only way to do this: |
| this->pq = ManagedStorePriorityQueue(); |
| } |
| |
| void ManagedObjectStore::getManagedObjectsFromDbusService( |
| const std::string& service, const sdbusplus::message::object_path& path, |
| ManagedStoreCb&& callback) |
| { |
| dbus::utility::getManagedObjects( |
| service, path, |
| [this, self = shared_from_this(), callback{std::move(callback)}]( |
| const boost::system::error_code& ec, |
| const dbus::utility::ManagedObjectType& objects) { |
| std::chrono::steady_clock::time_point timeNow = clockNow(); |
| ValueType value(objects, ec, timeNow, |
| timeNow + this->config.tfixedThreshold); |
| callback(value); |
| }); |
| } |
| |
| void ManagedObjectStore::getManagedPropertiesFromDbusService( |
| const std::string& service, const sdbusplus::message::object_path& path, |
| const std::string& interface, const std::string& property, |
| ManagedStoreCb&& callback) |
| { |
| dbus::utility::getProperty( |
| service, path, interface, property, |
| [this, self = shared_from_this(), callback{std::move(callback)}]( |
| const boost::system::error_code& ec, |
| const dbus::utility::DbusVariantType& propertyValue) { |
| std::chrono::steady_clock::time_point timeNow = clockNow(); |
| ValueType value(propertyValue, ec, timeNow, |
| timeNow + this->config.tfixedThreshold); |
| callback(value); |
| }); |
| } |
| |
| void ManagedObjectStore::getManagedPropertiesMapFromDbusService( |
| const std::string& service, const sdbusplus::message::object_path& path, |
| const std::string& interface, ManagedStoreCb&& callback) |
| { |
| sdbusplus::asio::getAllProperties( |
| *crow::connections::systemBus, service, path.str, interface, |
| [this, self = shared_from_this(), callback{std::move(callback)}]( |
| const boost::system::error_code& ec, |
| const dbus::utility::DBusPropertiesMap& propertyMap) { |
| std::chrono::steady_clock::time_point timeNow = clockNow(); |
| ValueType value(propertyMap, ec, timeNow, |
| timeNow + this->config.tfixedThreshold); |
| callback(value); |
| }); |
| } |
| |
| // Calls into dbus service to get subtree. |
| void ManagedObjectStore::getManagedSubtreeFromDbusService( |
| const sdbusplus::message::object_path& path, int32_t depth, |
| const std::vector<std::string>& interfaces, ManagedStoreCb&& callback) |
| { |
| crow::connections::systemBus->async_method_call( |
| [this, self = shared_from_this(), callback{std::move(callback)}]( |
| const boost::system::error_code& ec, |
| const dbus::utility::MapperGetSubTreeResponse& subtree) { |
| std::chrono::steady_clock::time_point timeNow = clockNow(); |
| ValueType value(subtree, ec, timeNow, |
| timeNow + this->config.tfixedThreshold); |
| 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 sdbusplus::message::object_path& path, int32_t depth, |
| const std::vector<std::string>& interfaces, ManagedStoreCb&& callback) |
| { |
| crow::connections::systemBus->async_method_call( |
| [this, self = shared_from_this(), callback{std::move(callback)}]( |
| const boost::system::error_code& ec, |
| const dbus::utility::MapperGetSubTreePathsResponse& subtreePaths) { |
| std::chrono::steady_clock::time_point timeNow = clockNow(); |
| ValueType value(subtreePaths, ec, timeNow, |
| timeNow + this->config.tfixedThreshold); |
| 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 sdbusplus::message::object_path& associatedPath, |
| const sdbusplus::message::object_path& path, int32_t depth, |
| const std::vector<std::string>& interfaces, ManagedStoreCb&& callback) |
| { |
| crow::connections::systemBus->async_method_call( |
| [this, self = shared_from_this(), callback{std::move(callback)}]( |
| const boost::system::error_code& ec, |
| const dbus::utility::MapperGetSubTreeResponse& subtree) { |
| std::chrono::steady_clock::time_point timeNow = clockNow(); |
| ValueType value(subtree, ec, timeNow, |
| timeNow + this->config.tfixedThreshold); |
| 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 sdbusplus::message::object_path& associatedPath, |
| const sdbusplus::message::object_path& path, int32_t depth, |
| const std::vector<std::string>& interfaces, ManagedStoreCb&& callback) |
| { |
| crow::connections::systemBus->async_method_call( |
| [this, self = shared_from_this(), callback{std::move(callback)}]( |
| const boost::system::error_code& ec, |
| const dbus::utility::MapperGetSubTreePathsResponse& subtreePaths) { |
| std::chrono::steady_clock::time_point timeNow = clockNow(); |
| ValueType value(subtreePaths, ec, timeNow, |
| timeNow + this->config.tfixedThreshold); |
| 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(); |
| } |
| |
| 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); |
| return obj; |
| } |
| |
| } // namespace managedStore |