blob: 115b8fa96630654b751fe2de0d4403e88fa9ef83 [file] [log] [blame]
#pragma once
#include <Thresholds.hpp>
#include <Utils.hpp>
#include <sdbusplus/asio/object_server.hpp>
#include <limits>
#include <memory>
#include <string>
#include <vector>
constexpr size_t sensorFailedPollTimeMs = 5000;
// Enable useful logging with sensor instrumentation
// This is intentionally not DEBUG, avoid clash with usage in .cpp files
constexpr bool enableInstrumentation = false;
constexpr const char* sensorValueInterface = "xyz.openbmc_project.Sensor.Value";
constexpr const char* availableInterfaceName =
"xyz.openbmc_project.State.Decorator.Availability";
constexpr const char* operationalInterfaceName =
"xyz.openbmc_project.State.Decorator.OperationalStatus";
constexpr const size_t errorThreshold = 5;
struct SensorInstrumentation
{
// These are for instrumentation for debugging
int numCollectsGood = 0;
int numCollectsMiss = 0;
int numStreakGreats = 0;
int numStreakMisses = 0;
double minCollected = 0.0;
double maxCollected = 0.0;
};
struct Sensor
{
Sensor(const std::string& name,
std::vector<thresholds::Threshold>&& thresholdData,
const std::string& configurationPath, const std::string& objectType,
const double max, const double min,
std::shared_ptr<sdbusplus::asio::connection>& conn,
PowerState readState = PowerState::always) :
name(std::regex_replace(name, std::regex("[^a-zA-Z0-9_/]+"), "_")),
configurationPath(configurationPath), objectType(objectType),
maxValue(max), minValue(min), thresholds(std::move(thresholdData)),
hysteresisTrigger((max - min) * 0.01),
hysteresisPublish((max - min) * 0.0001), dbusConnection(conn),
readState(readState), errCount(0),
instrumentation(enableInstrumentation
? std::make_unique<SensorInstrumentation>()
: nullptr)
{}
virtual ~Sensor() = default;
virtual void checkThresholds(void) = 0;
std::string name;
std::string configurationPath;
std::string objectType;
double maxValue;
double minValue;
std::vector<thresholds::Threshold> thresholds;
std::shared_ptr<sdbusplus::asio::dbus_interface> sensorInterface;
std::shared_ptr<sdbusplus::asio::dbus_interface> thresholdInterfaceWarning;
std::shared_ptr<sdbusplus::asio::dbus_interface> thresholdInterfaceCritical;
std::shared_ptr<sdbusplus::asio::dbus_interface> association;
std::shared_ptr<sdbusplus::asio::dbus_interface> availableInterface;
std::shared_ptr<sdbusplus::asio::dbus_interface> operationalInterface;
double value = std::numeric_limits<double>::quiet_NaN();
double rawValue = std::numeric_limits<double>::quiet_NaN();
bool overriddenState = false;
bool internalSet = false;
double hysteresisTrigger;
double hysteresisPublish;
std::shared_ptr<sdbusplus::asio::connection> dbusConnection;
PowerState readState;
size_t errCount;
std::unique_ptr<SensorInstrumentation> instrumentation;
void updateInstrumentation(double readValue)
{
// Do nothing if this feature is not enabled
if constexpr (!enableInstrumentation)
{
return;
}
if (!instrumentation)
{
return;
}
// Save some typing
auto& inst = *instrumentation;
// Show constants if first reading (even if unsuccessful)
if ((inst.numCollectsGood == 0) && (inst.numCollectsMiss == 0))
{
std::cerr << "Sensor " << name << ": Configuration min=" << minValue
<< ", max=" << maxValue << ", type=" << objectType
<< ", path=" << configurationPath << "\n";
}
// Sensors can use "nan" to indicate unavailable reading
if (!std::isfinite(readValue))
{
// Only show this if beginning a new streak
if (inst.numStreakMisses == 0)
{
std::cerr << "Sensor " << name
<< ": Missing reading, Reading counts good="
<< inst.numCollectsGood
<< ", miss=" << inst.numCollectsMiss
<< ", Prior good streak=" << inst.numStreakGreats
<< "\n";
}
inst.numStreakGreats = 0;
++(inst.numCollectsMiss);
++(inst.numStreakMisses);
return;
}
// Only show this if beginning a new streak and not the first time
if ((inst.numStreakGreats == 0) && (inst.numCollectsGood != 0))
{
std::cerr << "Sensor " << name
<< ": Recovered reading, Reading counts good="
<< inst.numCollectsGood
<< ", miss=" << inst.numCollectsMiss
<< ", Prior miss streak=" << inst.numStreakMisses << "\n";
}
// Initialize min/max if the first successful reading
if (inst.numCollectsGood == 0)
{
std::cerr << "Sensor " << name << ": First reading=" << readValue
<< "\n";
inst.minCollected = readValue;
inst.maxCollected = readValue;
}
inst.numStreakMisses = 0;
++(inst.numCollectsGood);
++(inst.numStreakGreats);
// Only provide subsequent output if new min/max established
if (readValue < inst.minCollected)
{
std::cerr << "Sensor " << name << ": Lowest reading=" << readValue
<< "\n";
inst.minCollected = readValue;
}
if (readValue > inst.maxCollected)
{
std::cerr << "Sensor " << name << ": Highest reading=" << readValue
<< "\n";
inst.maxCollected = readValue;
}
}
int setSensorValue(const double& newValue, double& oldValue)
{
if (!internalSet)
{
oldValue = newValue;
overriddenState = true;
// check thresholds for external set
value = newValue;
checkThresholds();
}
else if (!overriddenState)
{
oldValue = newValue;
}
return 1;
}
void
setInitialProperties(std::shared_ptr<sdbusplus::asio::connection>& conn,
const std::string& label = std::string(),
size_t thresholdSize = 0)
{
if (readState == PowerState::on || readState == PowerState::biosPost)
{
setupPowerMatch(conn);
}
createAssociation(association, configurationPath);
sensorInterface->register_property("MaxValue", maxValue);
sensorInterface->register_property("MinValue", minValue);
sensorInterface->register_property(
"Value", value, [&](const double& newValue, double& oldValue) {
return setSensorValue(newValue, oldValue);
});
for (auto& threshold : thresholds)
{
std::shared_ptr<sdbusplus::asio::dbus_interface> iface;
std::string level;
std::string alarm;
if (threshold.level == thresholds::Level::CRITICAL)
{
iface = thresholdInterfaceCritical;
if (threshold.direction == thresholds::Direction::HIGH)
{
level = "CriticalHigh";
alarm = "CriticalAlarmHigh";
}
else
{
level = "CriticalLow";
alarm = "CriticalAlarmLow";
}
}
else if (threshold.level == thresholds::Level::WARNING)
{
iface = thresholdInterfaceWarning;
if (threshold.direction == thresholds::Direction::HIGH)
{
level = "WarningHigh";
alarm = "WarningAlarmHigh";
}
else
{
level = "WarningLow";
alarm = "WarningAlarmLow";
}
}
else
{
std::cerr << "Unknown threshold level" << threshold.level
<< "\n";
continue;
}
if (!iface)
{
std::cout << "trying to set uninitialized interface\n";
continue;
}
size_t thresSize =
label.empty() ? thresholds.size() : thresholdSize;
iface->register_property(
level, threshold.value,
[&, label, thresSize](const double& request, double& oldValue) {
oldValue = request; // todo, just let the config do this?
threshold.value = request;
thresholds::persistThreshold(configurationPath, objectType,
threshold, conn, thresSize,
label);
// Invalidate previously remembered value,
// so new thresholds will be checked during next update,
// even if sensor reading remains unchanged.
value = std::numeric_limits<double>::quiet_NaN();
// Although tempting, don't call checkThresholds() from here
// directly. Let the regular sensor monitor call the same
// using updateValue(), which can check conditions like
// poweron, etc., before raising any event.
return 1;
});
iface->register_property(alarm, false);
}
if (!sensorInterface->initialize())
{
std::cerr << "error initializing value interface\n";
}
if (thresholdInterfaceWarning &&
!thresholdInterfaceWarning->initialize(true))
{
std::cerr << "error initializing warning threshold interface\n";
}
if (thresholdInterfaceCritical &&
!thresholdInterfaceCritical->initialize(true))
{
std::cerr << "error initializing critical threshold interface\n";
}
if (!availableInterface)
{
availableInterface =
std::make_shared<sdbusplus::asio::dbus_interface>(
conn, sensorInterface->get_object_path(),
availableInterfaceName);
availableInterface->register_property(
"Available", true, [this](const bool propIn, bool& old) {
if (propIn == old)
{
return 1;
}
old = propIn;
if (!propIn)
{
updateValue(std::numeric_limits<double>::quiet_NaN());
}
return 1;
});
availableInterface->initialize();
}
if (!operationalInterface)
{
operationalInterface =
std::make_shared<sdbusplus::asio::dbus_interface>(
conn, sensorInterface->get_object_path(),
operationalInterfaceName);
operationalInterface->register_property("Functional", true);
operationalInterface->initialize();
}
}
bool readingStateGood()
{
if (readState == PowerState::on && !isPowerOn())
{
return false;
}
if (readState == PowerState::biosPost &&
(!hasBiosPost() || !isPowerOn()))
{
return false;
}
return true;
}
void markFunctional(bool isFunctional)
{
if (operationalInterface)
{
operationalInterface->set_property("Functional", isFunctional);
}
if (isFunctional)
{
errCount = 0;
}
else
{
updateValue(std::numeric_limits<double>::quiet_NaN());
}
}
void markAvailable(bool isAvailable)
{
if (availableInterface)
{
availableInterface->set_property("Available", isAvailable);
errCount = 0;
}
}
void incrementError()
{
if (!readingStateGood())
{
markAvailable(false);
return;
}
if (errCount >= errorThreshold)
{
return;
}
errCount++;
if (errCount == errorThreshold)
{
std::cerr << "Sensor " << name << " reading error!\n";
markFunctional(false);
}
}
void updateValue(const double& newValue)
{
// Ignore if overriding is enabled
if (overriddenState)
{
return;
}
if (!readingStateGood())
{
markAvailable(false);
updateValueProperty(std::numeric_limits<double>::quiet_NaN());
return;
}
updateValueProperty(newValue);
updateInstrumentation(newValue);
// Always check thresholds after changing the value,
// as the test against hysteresisTrigger now takes place in
// the thresholds::checkThresholds() method,
// which is called by checkThresholds() below,
// in all current implementations of sensors that have thresholds.
checkThresholds();
if (!std::isnan(newValue))
{
markFunctional(true);
markAvailable(true);
}
}
void updateProperty(
std::shared_ptr<sdbusplus::asio::dbus_interface>& interface,
double& oldValue, const double& newValue, const char* dbusPropertyName)
{
if (requiresUpdate(oldValue, newValue))
{
oldValue = newValue;
if (interface &&
!(interface->set_property(dbusPropertyName, newValue)))
{
std::cerr << "error setting property " << dbusPropertyName
<< " to " << newValue << "\n";
}
}
}
bool requiresUpdate(const double& lVal, const double& rVal)
{
if (std::isnan(lVal) || std::isnan(rVal))
{
return true;
}
double diff = std::abs(lVal - rVal);
if (diff > hysteresisPublish)
{
return true;
}
return false;
}
private:
void updateValueProperty(const double& newValue)
{
// Indicate that it is internal set call, not an external overwrite
internalSet = true;
updateProperty(sensorInterface, value, newValue, "Value");
internalSet = false;
}
};