blob: 85f4dcf22f7d8d673dae6eab5a326f01ee98b5c8 [file] [log] [blame]
#include "managed_store.hpp"
#include <sys/types.h>
#include <algorithm>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <exception>
#include <fstream>
#include <functional>
#include <ios>
#include <list>
#include <memory>
#include <optional>
#include <queue>
#include <span>
#include <string>
#include <string_view>
#include <thread>
#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/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 "nlohmann/json.hpp"
#include "dbus_utility.hpp" // NOLINT
#include "async_resp.hpp" // NOLINT
#include "http_request.hpp" // NOLINT
#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"
#ifdef UNIT_TEST_BUILD
#include "test/g3/mock_managed_store.hpp" // NOLINT
#endif
// TODO:: move these to a separate file (future CL when we have more stores):
namespace managedStore {
namespace {
using ::ecclesia::EventSourceId;
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...);
}
} // namespace
// 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) {
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 ManagedStore instance(
*cfg, *io_main_thread, std::this_thread::get_id(),
io_context_worker_threads, subscription_service, systemBus);
return &instance;
}
ManagedStore* GetManagedObjectStore() {
return InitializeManagedStore(nullptr, nullptr, nullptr, nullptr, nullptr);
}
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::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 {
std::function<void()> callback_wrapper = internal::CallAtMostOnce(
[callback(std::move(callback)), value]() mutable {
std::move(callback)(value);
});
strand->post(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([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