blob: 9fb02c260322d81c333a6058fcfe20ec0b7b966e [file]
#include "tlbmc/collector/sensor_collector.h"
#include <array>
#include <cstddef>
#include <cstring>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "absl/functional/any_invocable.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "absl/strings/substitute.h"
#include "absl/synchronization/mutex.h"
#include "absl/time/time.h"
#include "absl/types/span.h"
#include "boost/asio.hpp" //NOLINT: boost::asio is commonly used in BMC
#include "g3/macros.h"
#include "thread/thread.h"
#include "gbmc_sel_defs.h"
#include "gbmc_sel_pub.h"
#include <nlohmann/json.hpp>
#include "tlbmc/central_config/config.h"
#include "tlbmc/collector/monitoring_change_base.h"
#include "tlbmc/collector/peci_scanner.h"
#include "tlbmc/collector/power_control_collector.h"
#include "adc_sensor_config.pb.h"
#include "entity_common_config.pb.h"
#include "fan_controller_config.pb.h"
#include "fan_pwm_config.pb.h"
#include "fan_tach_config.pb.h"
#include "gpio_sensor_config.pb.h"
#include "hwmon_temp_sensor_config.pb.h"
#include "intel_cpu_sensor_config.pb.h"
#include "psu_sensor_config.pb.h"
#include "redfish_aggregated_batch_config.pb.h"
#include "redfish_aggregated_sensor_config.pb.h"
#include "shared_mem_sensor_config.pb.h"
#include "threshold_config.pb.h"
#include "virtual_sensor_config.pb.h"
#include "tlbmc/expression/expression.h"
#include "tlbmc/hal/nic_veeprom/interface.h"
#include "veeprom.pb.h"
#include "tlbmc/hal/sysfs/hwmon.h"
#include "tlbmc/host_state/power_control.h"
#include "resource.pb.h"
#include "sensor.pb.h"
#include "tlbmc/sensors/adc_sensor.h"
#include "tlbmc/sensors/amd_cpu_sensor.h"
#include "tlbmc/sensors/fan_controller.h"
#include "tlbmc/sensors/fan_pwm.h"
#include "tlbmc/sensors/fan_tach.h"
#include "tlbmc/sensors/gpio_sensor.h"
#include "tlbmc/sensors/hwmon_temp_sensor.h"
#include "tlbmc/sensors/intel_cpu_sensor.h"
#include "tlbmc/sensors/nic_sensor.h"
#include "tlbmc/sensors/psu_sensor.h"
#include "tlbmc/sensors/redfish_aggregated_batch.h"
#include "tlbmc/sensors/redfish_aggregated_sensor.h"
#include "tlbmc/sensors/sensor.h"
#include "tlbmc/sensors/shared_mem_based_sensor.h"
#include "tlbmc/sensors/virtual_sensor.h"
#include "tlbmc/time/time.h"
constexpr absl::Duration kDefaultSensorSamplingInterval =
absl::Milliseconds(1000);
namespace milotic_tlbmc {
// Returns true if the status means the sensor never came up: creation failed
// outright, or is still pending because the sensor's FRU was not detected.
bool IsCreationIncomplete(Status status) {
return status == STATUS_CREATION_FAILED || status == STATUS_CREATION_PENDING;
}
void ScheduleIndividualSensorRead(
const std::shared_ptr<Sensor>& sensor,
const SensorNotification* refresh_notification,
std::optional<int> override_sensor_sampling_interval_ms,
ThreadManager& thread_manager) {
absl::Duration interval = kDefaultSensorSamplingInterval;
if (override_sensor_sampling_interval_ms.has_value()) {
LOG(INFO) << "Overriding sensor sampling interval to "
<< *override_sensor_sampling_interval_ms;
interval = absl::Milliseconds(*override_sensor_sampling_interval_ms);
} else {
absl::Duration static_refresh_interval = DecodeGoogleApiProto(
sensor->GetSensorAttributesStatic().static_refresh_interval());
if (static_refresh_interval > absl::ZeroDuration()) {
interval = static_refresh_interval;
}
}
const Status status = sensor->GetSensorAttributesDynamic().state().status();
if (IsCreationIncomplete(status)) {
LOG(INFO) << "Skipping schedule sensor read for " << sensor->GetKey()
<< " because sensor initialization failed. Status: " << status;
return;
}
LOG(INFO) << "Scheduling Sensor Read! Sensor key is " << sensor->GetKey()
<< " and interval is " << interval;
{
absl::MutexLock lock(thread_manager.key_to_task_id_mutex);
auto it = thread_manager.sensor_key_to_task_id.find(sensor->GetKey());
if (it != thread_manager.sensor_key_to_task_id.end()) {
thread_manager.task_scheduler->Cancel(it->second);
}
int task_id = thread_manager.task_scheduler->RunAndScheduleAsync(
[sensor = std::weak_ptr<Sensor>(sensor),
refresh_notification =
refresh_notification](absl::AnyInvocable<void()> on_done) {
std::shared_ptr<Sensor> sensor_locked = sensor.lock();
if (!sensor_locked) {
return;
}
sensor_locked->RefreshOnceAsync(
[refresh_notification = refresh_notification,
on_done =
std::move(on_done)](const std::shared_ptr<const SensorValue>&
sensor_data) mutable {
if (refresh_notification != nullptr) {
refresh_notification->NotifyWithData(sensor_data);
}
on_done();
});
},
interval);
thread_manager.sensor_key_to_task_id[sensor->GetKey()] = task_id;
}
}
void ScheduleAllSensorReads(
const std::vector<std::shared_ptr<Sensor>>& sensors,
const SensorNotification* refresh_notification,
std::optional<int> override_sensor_sampling_interval_ms,
ThreadManager& thread_manager) {
LOG(INFO) << "Scheduling Sensor Read! Total sensor count is "
<< sensors.size();
absl::flat_hash_map<absl::Duration, std::vector<std::weak_ptr<Sensor>>>
sensors_by_interval;
for (const auto& sensor : sensors) {
ScheduleIndividualSensorRead(sensor, refresh_notification,
override_sensor_sampling_interval_ms,
thread_manager);
}
}
void ScheduleIndividualBatchRead(
const std::shared_ptr<RedfishAggregatedBatch>& batch,
const SensorNotification* refresh_notification,
std::optional<int> override_sensor_sampling_interval_ms,
ThreadManager& thread_manager) {
absl::Duration interval = kDefaultSensorSamplingInterval;
if (override_sensor_sampling_interval_ms.has_value()) {
interval = absl::Milliseconds(*override_sensor_sampling_interval_ms);
}
LOG(INFO) << "Scheduling Redfish Batch Read! Batch name is "
<< batch->GetBatchName() << " and interval is " << interval;
{
absl::MutexLock lock(thread_manager.key_to_task_id_mutex);
auto it = thread_manager.sensor_key_to_task_id.find(batch->GetBatchName());
if (it != thread_manager.sensor_key_to_task_id.end()) {
thread_manager.task_scheduler->Cancel(it->second);
}
int task_id = thread_manager.task_scheduler->RunAndScheduleAsync(
[batch = std::weak_ptr<RedfishAggregatedBatch>(batch),
refresh_notification =
refresh_notification](absl::AnyInvocable<void()> on_done) {
std::shared_ptr<RedfishAggregatedBatch> batch_locked = batch.lock();
if (!batch_locked) {
return;
}
batch_locked->RefreshOnceAsync(
[refresh_notification, on_done = std::move(on_done)]() mutable {
if (refresh_notification != nullptr) {
refresh_notification->NotifyWithData(nullptr);
}
on_done();
});
},
interval);
thread_manager.sensor_key_to_task_id[batch->GetBatchName()] = task_id;
}
}
void ScheduleAllBatchSensorReads(
const std::vector<std::shared_ptr<RedfishAggregatedBatch>>&
redfish_aggregated_batches,
const SensorNotification* refresh_notification,
std::optional<int> override_sensor_sampling_interval_ms,
ThreadManager& thread_manager) {
LOG(INFO)
<< "Scheduling Sensor Read! Total redfish aggregated batch count is "
<< redfish_aggregated_batches.size();
for (const auto& batch : redfish_aggregated_batches) {
ScheduleIndividualBatchRead(batch, refresh_notification,
override_sensor_sampling_interval_ms,
thread_manager);
}
}
// This class is used to apply sampling interval and batch size changes to the
// SensorCollector in a transaction-like manner.
class SensorCollectorMonitoringChange : public MonitoringChangeBase {
public:
explicit SensorCollectorMonitoringChange(SensorCollector* sensor_collector)
: sensor_collector_(sensor_collector) {}
bool AddSensor(std::string_view sensor_key, Mutation mutation) & override {
if (sensor_collector_->GetSensorBySensorKey(std::string(sensor_key)) !=
nullptr) {
auto& current = mutations_[sensor_key];
current.MergeWith(mutation);
return true;
}
return false;
}
void Apply() && override {
for (const auto& [sensor_key, mutation] : mutations_) {
sensor_collector_->ConfigureCollection(sensor_key, mutation)
.IgnoreError();
}
}
private:
SensorCollector* sensor_collector_;
absl::flat_hash_map<std::string, Mutation> mutations_;
};
namespace {
constexpr absl::string_view kDefaultHwmonContext = "DEFAULT_HWMON_CONTEXT";
constexpr absl::string_view kDefaultPsuContext = "DEFAULT_PSU_CONTEXT";
constexpr absl::string_view kDefaultFanContext = "DEFAULT_FAN_CONTEXT";
constexpr absl::string_view kDefaultVirtualSensorContext =
"DEFAULT_VIRTUAL_SENSOR_CONTEXT";
constexpr absl::string_view kDefaultIntelCpuContext =
"DEFAULT_INTEL_CPU_CONTEXT";
constexpr absl::string_view kDefaultRedfishAggregatedSensorContext =
"DEFAULT_REDFISH_AGGREGATED_SENSOR_CONTEXT";
constexpr absl::string_view kDefaultRedfishAggregatedBatchSensorContext =
"DEFAULT_REDFISH_AGGREGATED_BATCH_SENSOR_CONTEXT";
constexpr absl::string_view kDefaultNicTelemetryContext =
"DEFAULT_NIC_TELEMETRY_CONTEXT";
constexpr absl::string_view kDefaultNicTelemetryRefreshContext =
"DEFAULT_NIC_TELEMETRY_REFRESH_CONTEXT";
constexpr absl::string_view kDefaultGpioContext = "DEFAULT_GPIO_CONTEXT";
constexpr absl::string_view kDefaultAdcSensorContext =
"DEFAULT_ADC_SENSOR_CONTEXT";
std::optional<gbmc_sel_framework::EventSourceComponent> GetEventSourceComponent(
SensorUnit unit) {
switch (unit) {
case UNIT_DEGREE_CELSIUS:
return gbmc_sel_framework::EventSourceComponent::
EVENT_SOURCE_COMPONENT_TemperatureSensor;
case UNIT_WATT:
return gbmc_sel_framework::EventSourceComponent::
EVENT_SOURCE_COMPONENT_PowerSensor;
case UNIT_AMPERE:
return gbmc_sel_framework::EventSourceComponent::
EVENT_SOURCE_COMPONENT_CurrentSensor;
case UNIT_VOLT:
return gbmc_sel_framework::EventSourceComponent::
EVENT_SOURCE_COMPONENT_VoltageSensor;
case UNIT_REVOLUTION_PER_MINUTE:
return gbmc_sel_framework::EventSourceComponent::
EVENT_SOURCE_COMPONENT_FAN;
default:
return std::nullopt;
}
}
gbmc_sel_framework::EventSeverity GetEventSeverity(
ThresholdType threshold_type) {
if (threshold_type == THRESHOLD_TYPE_UPPER_CRITICAL ||
threshold_type == THRESHOLD_TYPE_LOWER_CRITICAL) {
return gbmc_sel_framework::EventSeverity::EVENT_SEVERITY_ERROR;
}
return gbmc_sel_framework::EventSeverity::EVENT_SEVERITY_WARNING;
}
std::optional<absl::string_view> GetThresholdEventStr(
ThresholdType threshold_type) {
switch (threshold_type) {
case THRESHOLD_TYPE_UPPER_CRITICAL:
return "OverCriticalThreshold";
case THRESHOLD_TYPE_UPPER_NON_CRITICAL:
return "OverNonCriticalThreshold";
case THRESHOLD_TYPE_LOWER_CRITICAL:
return "LowerCriticalThreshold";
case THRESHOLD_TYPE_LOWER_NON_CRITICAL:
return "LowerNonCriticalThreshold";
default:
return std::nullopt;
}
}
gbmc_sel_framework::EventSourceType GetEventSourceType(
absl::string_view devpath) {
for (const auto& [type, name] : gbmc_sel_framework::kEventSourceNames) {
if (absl::StrContains(devpath, name)) {
return type;
}
}
return gbmc_sel_framework::EventSourceType::EVENT_SOURCE_UNSPECIFIED;
}
std::vector<std::string> BuildAdditionalData(
absl::string_view sensor_name, double reading,
absl::string_view threshold_event_str,
std::optional<absl::string_view> devpath) {
std::vector<std::string> additional_data = {
"GBMC_SYSTEM_EVENT_TYPE=SensorEvent",
absl::StrCat("GBMC_SENSOR_NAME=", sensor_name),
absl::StrCat("GBMC_SENSOR_VALUE=", reading),
absl::StrCat("GBMC_SENSOR_THRESHOLD_EVENT=", threshold_event_str)};
if (devpath.has_value()) {
additional_data.push_back(absl::StrCat("GBMC_SENSOR_DEVPATH=", *devpath));
}
return additional_data;
}
void ResizeBufferAndMetrics(const std::shared_ptr<Sensor>& sensor,
size_t buffer_size_from_config) {
size_t default_buffer_size =
sensor->GetSensorAttributesStatic().entity_common_config().queue_size();
if (buffer_size_from_config > default_buffer_size) {
sensor->ResizeBuffer(buffer_size_from_config);
} else {
// If the buffer size from the config is less than the default buffer size,
// we need to resize the buffer to the default buffer size.
// This covers both the cases where we need to reset the buffer size to the
// default value and when a lower than default size is specified in the
// config.
sensor->ResizeBuffer(default_buffer_size);
}
sensor->ResetMetrics();
}
// Returns the io_context for the given sensor group. If the io_context does not
// exist, it will be created. SensorGroup should be a unique identifier for
// sensors that share the same i2c device or are otherwise related to optimize
// reading speed for HFT and minimize contention over the bus mutex. See
// b/434024387 for details.
std::shared_ptr<boost::asio::io_context> GetOrCreateIoContextForSensorGroup(
absl::flat_hash_map<std::string, std::shared_ptr<boost::asio::io_context>>&
sensor_group_to_io_context,
absl::string_view sensor_group) {
auto& group_io_context = sensor_group_to_io_context[sensor_group];
if (group_io_context == nullptr) {
group_io_context = std::make_shared<boost::asio::io_context>();
}
return group_io_context;
}
absl::Status CreateHwmonSensors(
const SensorCollector::Params& params,
std::vector<std::shared_ptr<Sensor>>& sensors,
absl::flat_hash_map<std::string, std::shared_ptr<boost::asio::io_context>>&
sensor_group_to_io_context) {
std::vector<std::shared_ptr<Sensor>> hwmon_temp_sensors;
size_t count_sensors = 0;
for (const auto& config : params.sensor_configs.hwmon_temp_sensor_configs) {
absl::string_view sensor_group =
config.entity_common_config().has_sensor_group()
? config.entity_common_config().sensor_group()
: kDefaultHwmonContext;
ECCLESIA_ASSIGN_OR_RETURN(
hwmon_temp_sensors,
HwmonTempSensor::Create(config,
GetOrCreateIoContextForSensorGroup(
sensor_group_to_io_context, sensor_group),
params.i2c_sysfs));
// We want to use syslog to track device creation status
LOG(INFO) << absl::Substitute("Created $0 HWmon sensors at $1",
hwmon_temp_sensors.size(),
config.hal_common_config());
count_sensors += hwmon_temp_sensors.size();
sensors.insert(sensors.end(), hwmon_temp_sensors.begin(),
hwmon_temp_sensors.end());
}
return absl::OkStatus();
}
absl::Status CreatePsuSensors(
const SensorCollector::Params& params,
std::vector<std::shared_ptr<Sensor>>& sensors,
absl::flat_hash_map<std::string, std::shared_ptr<boost::asio::io_context>>&
sensor_group_to_io_context) {
size_t count_sensors = 0;
for (const auto& config : params.sensor_configs.psu_sensor_configs) {
absl::string_view sensor_group =
config.entity_common_config().has_sensor_group()
? config.entity_common_config().sensor_group()
: kDefaultPsuContext;
// Default to use i2c
HwmonSysfs* hwmon_sysfs = &params.i2c_sysfs;
std::vector<std::shared_ptr<Sensor>> psu_sensors;
if (AmdCpuSensor::IsAmdCpuSensor(config.type())) {
ECCLESIA_ASSIGN_OR_RETURN(
psu_sensors,
AmdCpuSensor::Create(config,
GetOrCreateIoContextForSensorGroup(
sensor_group_to_io_context, sensor_group),
params.i3c_sysfs));
} else {
// There's no I3C sensor other than AMD CPU sensors, use i2c by default.
ECCLESIA_ASSIGN_OR_RETURN(
psu_sensors,
PsuSensor::Create(config,
GetOrCreateIoContextForSensorGroup(
sensor_group_to_io_context, sensor_group),
*hwmon_sysfs));
}
// We want to use syslog to track device creation status
LOG(INFO) << absl::Substitute("Created $0 PSU sensors at $1",
psu_sensors.size(),
config.hal_common_config());
count_sensors += psu_sensors.size();
sensors.insert(sensors.end(), psu_sensors.begin(), psu_sensors.end());
}
return absl::OkStatus();
}
absl::Status CreateFanSensors(
const SensorCollector::Params& params,
absl::Span<const std::shared_ptr<FanController>> fan_controllers,
std::vector<std::shared_ptr<Sensor>>& sensors,
absl::flat_hash_map<std::string, std::shared_ptr<boost::asio::io_context>>&
sensor_group_to_io_context) {
int count = 0;
for (const auto& config : params.sensor_configs.fan_pwm_configs) {
for (const auto& fan_controller : fan_controllers) {
if (!fan_controller->ControllerHasSensor(config.hal_common_config())) {
continue;
}
absl::string_view sensor_group =
config.entity_common_config().has_sensor_group()
? config.entity_common_config().sensor_group()
: kDefaultFanContext;
ECCLESIA_ASSIGN_OR_RETURN(
std::shared_ptr<Sensor> fan_pwm,
FanPwm::Create(config, *fan_controller,
GetOrCreateIoContextForSensorGroup(
sensor_group_to_io_context, sensor_group),
params.i2c_sysfs));
sensors.push_back(std::move(fan_pwm));
count++;
break;
}
}
for (const auto& config : params.sensor_configs.fan_tach_configs) {
for (const auto& fan_controller : fan_controllers) {
if (!fan_controller->ControllerHasSensor(config.hal_common_config())) {
continue;
}
absl::string_view sensor_group =
config.entity_common_config().has_sensor_group()
? config.entity_common_config().sensor_group()
: kDefaultFanContext;
ECCLESIA_ASSIGN_OR_RETURN(
std::shared_ptr<Sensor> fan_tach,
FanTachometer::Create(config, *fan_controller,
GetOrCreateIoContextForSensorGroup(
sensor_group_to_io_context, sensor_group),
params.i2c_sysfs));
sensors.push_back(std::move(fan_tach));
count++;
break;
}
}
// We want to use syslog to track device creation status
LOG(INFO) << absl::Substitute("Created $0 Fan PWM/Tach sensors", count);
return absl::OkStatus();
}
absl::Status CreateGpioSensors(
const SensorCollector::Params& params,
std::vector<std::shared_ptr<Sensor>>& sensors,
absl::flat_hash_map<std::string, std::shared_ptr<boost::asio::io_context>>&
sensor_group_to_io_context) {
if (params.gpio_collector == nullptr) {
return absl::OkStatus();
}
for (const auto& config : params.sensor_configs.gpio_sensor_configs) {
ECCLESIA_ASSIGN_OR_RETURN(
std::shared_ptr<Sensor> gpio_sensor,
GpioSensor::Create(config,
GetOrCreateIoContextForSensorGroup(
sensor_group_to_io_context, kDefaultGpioContext),
params.gpio_collector));
// We want to use syslog to track device creation status
LOG(INFO) << absl::Substitute("Created Gpio sensor: $0",
config.instance_properties().name());
sensors.push_back(std::move(gpio_sensor));
}
return absl::OkStatus();
}
absl::Status CreateSharedMemSensors(
const SensorCollector::Params& params,
std::vector<std::shared_ptr<Sensor>>& sensors,
ThreadManager& thread_manager) {
auto io_context = std::make_shared<boost::asio::io_context>();
boost::asio::executor_work_guard<boost::asio::io_context::executor_type>
work_guard(boost::asio::make_work_guard(*io_context));
int count = 0;
for (const auto& config : params.sensor_configs.shared_mem_sensor_configs) {
ECCLESIA_ASSIGN_OR_RETURN(std::shared_ptr<Sensor> shared_mem_sensor,
SharedMemBasedSensor::Create(config, io_context));
// We want to use syslog to track device creation status
LOG(INFO) << absl::Substitute("Created SharedMem sensor: $0",
config.instance_properties().name());
sensors.push_back(std::move(shared_mem_sensor));
count++;
}
if (count > 0) {
thread_manager.work_guards.push_back(std::move(work_guard));
thread_manager.io_contexts.push_back(std::move(io_context));
}
return absl::OkStatus();
}
absl::Status CreateVirtualSensors(
const SensorCollector::Params& params,
std::vector<std::shared_ptr<Sensor>>& sensors,
absl::flat_hash_map<std::string, std::shared_ptr<boost::asio::io_context>>&
sensor_group_to_io_context) {
absl::flat_hash_map<std::string, std::shared_ptr<Sensor>> sensor_map;
for (const std::shared_ptr<Sensor>& sensor : sensors) {
sensor_map[sensor->GetKey()] = sensor;
}
// All virtual sensors are created in the same io_context.
std::shared_ptr<boost::asio::io_context> io_context =
GetOrCreateIoContextForSensorGroup(sensor_group_to_io_context,
kDefaultVirtualSensorContext);
for (const auto& config : params.sensor_configs.virtual_sensor_configs) {
absl::flat_hash_map<std::string, std::shared_ptr<Sensor>>
reference_sensors_map;
ECCLESIA_ASSIGN_OR_RETURN(std::unique_ptr<Expression> expression,
Parse(config.expression()));
absl::flat_hash_set<std::string> required_sensors;
expression->GetRequiredVariables(required_sensors);
for (const auto& sensor_key : required_sensors) {
auto it = sensor_map.find(sensor_key);
if (it == sensor_map.end()) {
if (!GetTlbmcConfig()
.sensor_collector_module()
.allow_sensor_creation_failure()) {
return absl::InvalidArgumentError(
absl::StrCat("Reference sensor not found: ", sensor_key));
}
reference_sensors_map[sensor_key] = nullptr;
} else {
reference_sensors_map[sensor_key] = it->second;
}
}
ECCLESIA_ASSIGN_OR_RETURN(
std::shared_ptr<Sensor> virtual_sensor,
VirtualSensor::Create(config, io_context, reference_sensors_map,
std::move(expression)));
LOG(INFO) << absl::StrCat("Created Virtual sensor: ",
config.instance_properties().name());
sensors.push_back(std::move(virtual_sensor));
}
return absl::OkStatus();
}
// Creates NIC sensors and adds them to the sensors vector.
// Also creates the accessors for the NICs per bus.
absl::Status CreateNicSensors(
const SensorCollector::Params& params,
std::vector<std::shared_ptr<Sensor>>& sensors,
absl::flat_hash_map<std::string, std::shared_ptr<boost::asio::io_context>>&
sensor_group_to_io_context,
absl::flat_hash_map<int, std::unique_ptr<nic_veeprom::Accessor>>&
bus_to_v1_accessor,
absl::flat_hash_map<int, std::unique_ptr<nic_veeprom::Accessor>>&
bus_to_v2_accessor,
absl::flat_hash_map<int, std::unique_ptr<nic_veeprom::Accessor>>&
bus_to_v4_accessor,
absl::flat_hash_map<int, std::unique_ptr<nic_veeprom::Accessor>>&
bus_to_v5_accessor) {
std::string i2c_sysfs_path = params.i2c_sysfs.GetI2cSysfsPath();
std::shared_ptr<boost::asio::io_context> io_context =
GetOrCreateIoContextForSensorGroup(sensor_group_to_io_context,
kDefaultNicTelemetryContext);
for (const auto& config : params.sensor_configs.nic_telemetry_configs) {
int bus = static_cast<int>(config.hal_common_config().bus());
// Existing assumption on
// https://gbmc-internal.git.corp.google.com/diorite-hss/+/8c81357e247e55df1ee2e3799f32550d43124c46/find_diorite.cpp#223
constexpr int kNicVirtualEepromAddress = 75;
nic_veeprom::Accessor* accessor = nullptr;
if (config.version() == nic_veeprom::NIC_TELEMETRY_VERSION_V1) {
auto& v1_accessor = bus_to_v1_accessor[bus];
if (v1_accessor == nullptr) {
v1_accessor = params.nic_accessor_factory(config.version(), bus,
kNicVirtualEepromAddress);
}
accessor = v1_accessor.get();
}
if (config.version() == nic_veeprom::NIC_TELEMETRY_VERSION_V2) {
auto& v2_accessor = bus_to_v2_accessor[bus];
if (v2_accessor == nullptr) {
v2_accessor = params.nic_accessor_factory(config.version(), bus,
kNicVirtualEepromAddress);
}
accessor = v2_accessor.get();
}
if (config.version() == nic_veeprom::NIC_TELEMETRY_VERSION_V4) {
auto& v4_accessor = bus_to_v4_accessor[bus];
if (v4_accessor == nullptr) {
v4_accessor = params.nic_accessor_factory(config.version(), bus,
kNicVirtualEepromAddress);
}
accessor = v4_accessor.get();
}
if (config.version() == nic_veeprom::NIC_TELEMETRY_VERSION_V5) {
auto& v5_accessor = bus_to_v5_accessor[bus];
if (v5_accessor == nullptr) {
v5_accessor = params.nic_accessor_factory(config.version(), bus,
kNicVirtualEepromAddress);
}
accessor = v5_accessor.get();
}
if (accessor == nullptr) {
return absl::InternalError(
"Failed to create accessor for NIC telemetry config: no accessor");
}
for (const auto& telemetry_instance : config.telemetry_instances()) {
// For now, unit won't be set. Sensor unit can be derived from the sensor
// name.
ECCLESIA_ASSIGN_OR_RETURN(
std::shared_ptr<Sensor> nic_sensor,
NicSensor::Create(telemetry_instance, config.entity_common_config(),
io_context, *accessor));
sensors.push_back(std::move(nic_sensor));
}
}
return absl::OkStatus();
}
// Creates ADC sensors and adds them to the sensors vector.
absl::Status CreateAdcSensors(
const SensorCollector::Params& params,
std::vector<std::shared_ptr<Sensor>>& sensors,
absl::flat_hash_map<std::string, std::shared_ptr<boost::asio::io_context>>&
sensor_group_to_io_context) {
std::shared_ptr<boost::asio::io_context> io_context =
GetOrCreateIoContextForSensorGroup(sensor_group_to_io_context,
kDefaultAdcSensorContext);
for (const auto& config : params.sensor_configs.adc_sensor_configs) {
for (const auto& channel_config : config.channels()) {
ECCLESIA_ASSIGN_OR_RETURN(
std::shared_ptr<Sensor> adc_sensor,
AdcSensor::Create(
{.device_name = config.device_name(),
.channel_config = channel_config,
.hal_common_config = config.hal_common_config(),
.entity_common_config = config.entity_common_config(),
.io_context = io_context,
.type = config.type()},
params.iio_sysfs, params.i2c_sysfs));
sensors.push_back(std::move(adc_sensor));
}
}
return absl::OkStatus();
}
absl::StatusOr<std::vector<std::shared_ptr<FanController>>>
CreateFanControllers(const SensorCollector::Params& params) {
std::vector<std::shared_ptr<FanController>> fan_controllers;
for (const auto& config : params.sensor_configs.fan_controller_configs) {
ECCLESIA_ASSIGN_OR_RETURN(std::shared_ptr<FanController> fan_controller,
FanController::Create(config, params.i2c_sysfs));
// We want to use syslog to track device creation status
LOG(INFO) << absl::Substitute("Created 1 fan controller at $0",
config.hal_common_config());
fan_controllers.push_back(std::move(fan_controller));
}
return fan_controllers;
}
absl::Status CreateIntelCpuSensors(
const SensorCollector::Params& params,
std::vector<std::shared_ptr<Sensor>>& sensors,
absl::flat_hash_map<std::string, std::shared_ptr<boost::asio::io_context>>&
sensor_group_to_io_context) {
for (const auto& config : params.sensor_configs.intel_cpu_sensor_configs) {
absl::string_view sensor_group =
config.entity_common_config().has_sensor_group()
? config.entity_common_config().sensor_group()
: kDefaultIntelCpuContext;
ECCLESIA_ASSIGN_OR_RETURN(
std::vector<std::shared_ptr<Sensor>> intel_cpu_sensors,
IntelCpuSensor::CreateInitialSensors(
config,
GetOrCreateIoContextForSensorGroup(sensor_group_to_io_context,
sensor_group),
params.peci_sysfs));
// We want to use syslog to track device creation status
LOG(INFO) << absl::Substitute("Created $0 Intel CPU sensors at $1",
intel_cpu_sensors.size(),
config.hal_common_config());
sensors.insert(sensors.end(), intel_cpu_sensors.begin(),
intel_cpu_sensors.end());
}
return absl::OkStatus();
}
absl::Status CreateRedfishAggregatedSensors(
const SensorCollector::Params& params,
std::vector<std::shared_ptr<Sensor>>& sensors,
absl::flat_hash_map<std::string, std::shared_ptr<boost::asio::io_context>>&
sensor_group_to_io_context) {
absl::string_view sensor_group = kDefaultRedfishAggregatedSensorContext;
std::shared_ptr<boost::asio::io_context> io_context =
GetOrCreateIoContextForSensorGroup(sensor_group_to_io_context,
sensor_group);
for (const auto& config :
params.sensor_configs.redfish_aggregated_sensor_configs) {
ECCLESIA_ASSIGN_OR_RETURN(
std::shared_ptr<Sensor> redfish_aggregated_sensor,
RedfishAggregatedSensor::Create(
config, io_context, params.http_client_factory(*io_context)));
sensors.push_back(std::move(redfish_aggregated_sensor));
}
return absl::OkStatus();
}
absl::Status CreateRedfishAggregatedBatchSensors(
const SensorCollector::Params& params,
std::vector<std::shared_ptr<Sensor>>& sensors,
std::vector<std::shared_ptr<RedfishAggregatedBatch>>&
redfish_aggregated_batches,
absl::flat_hash_map<std::string, std::shared_ptr<boost::asio::io_context>>&
sensor_group_to_io_context) {
absl::string_view sensor_group = kDefaultRedfishAggregatedBatchSensorContext;
std::shared_ptr<boost::asio::io_context> io_context =
GetOrCreateIoContextForSensorGroup(sensor_group_to_io_context,
sensor_group);
for (const auto& config :
params.sensor_configs.redfish_aggregated_batch_configs) {
std::vector<std::shared_ptr<RedfishAggregatedSensor>>
redfish_aggregated_sensors;
for (const auto& telemetry_config : config.telemetry_configs()) {
ECCLESIA_ASSIGN_OR_RETURN(
std::shared_ptr<RedfishAggregatedSensor> redfish_aggregated_sensor,
RedfishAggregatedSensor::Create(
telemetry_config, io_context,
params.http_client_factory(*io_context)));
redfish_aggregated_sensors.push_back(redfish_aggregated_sensor);
sensors.push_back(std::move(redfish_aggregated_sensor));
}
ECCLESIA_ASSIGN_OR_RETURN(
std::shared_ptr<RedfishAggregatedBatch> redfish_aggregated_batch,
RedfishAggregatedBatch::Create(config, io_context,
params.http_client_factory(*io_context),
std::move(redfish_aggregated_sensors)));
redfish_aggregated_batches.push_back(std::move(redfish_aggregated_batch));
}
return absl::OkStatus();
}
// Drops sensors that failed creation when a successfully created sensor with
// the same sensor key exists on another board. Supports platforms where one
// physical device is configured under every board it could belong to because
// the board cannot be identified at config time: only the config matching the
// real hardware initializes, and without this filter the winner of key-based
// lookups (GetSensorBySensorKey and everything built on it) is decided by
// hash-map iteration order. Sensors with unique keys are kept regardless of
// status, so ordinary failed sensors remain visible for diagnosis.
void DropFailedDuplicateSensors(std::vector<std::shared_ptr<Sensor>>& sensors) {
absl::flat_hash_set<absl::string_view> healthy_keys;
for (const std::shared_ptr<Sensor>& sensor : sensors) {
if (!IsCreationIncomplete(
sensor->GetSensorAttributesDynamic().state().status())) {
healthy_keys.insert(sensor->GetKey());
}
}
std::erase_if(sensors, [&healthy_keys](
const std::shared_ptr<Sensor>& sensor) {
const Status status = sensor->GetSensorAttributesDynamic().state().status();
const bool is_shadowed_duplicate =
IsCreationIncomplete(status) && healthy_keys.contains(sensor->GetKey());
if (!is_shadowed_duplicate) {
return false;
}
LOG(INFO) << "Dropping sensor " << sensor->GetKey() << " on board "
<< sensor->GetBoardConfigKey()
<< " (status: " << Status_Name(status)
<< "): a healthy sensor with the same key exists "
"(drop_failed_duplicate_sensors).";
return true;
});
}
absl::flat_hash_map<std::string,
absl::flat_hash_map<std::string, std::shared_ptr<Sensor>>>
CreateSensorsTable(std::vector<std::shared_ptr<Sensor>>&& sensors) {
absl::flat_hash_map<std::string,
absl::flat_hash_map<std::string, std::shared_ptr<Sensor>>>
sensor_table;
for (auto& sensor : sensors) {
sensor_table[sensor->GetBoardConfigKey()][sensor->GetKey()] =
std::move(sensor);
}
return sensor_table;
}
// NIC sensors won't be refreshed at the sensor level. Instead, we will have a
// dedicated task to refresh all NIC sensors together per accessor.
void ScheduleNicSensorAccessorRefresh(
const absl::flat_hash_map<int, std::unique_ptr<nic_veeprom::Accessor>>&
bus_to_v1_accessor,
const absl::flat_hash_map<int, std::unique_ptr<nic_veeprom::Accessor>>&
bus_to_v2_accessor,
const absl::flat_hash_map<int, std::unique_ptr<nic_veeprom::Accessor>>&
bus_to_v4_accessor,
const absl::flat_hash_map<int, std::unique_ptr<nic_veeprom::Accessor>>&
bus_to_v5_accessor,
absl::flat_hash_map<std::string, std::shared_ptr<boost::asio::io_context>>&
sensor_group_to_io_context,
ThreadManager& thread_manager) {
if (!bus_to_v1_accessor.empty() || !bus_to_v2_accessor.empty() ||
!bus_to_v4_accessor.empty() || !bus_to_v5_accessor.empty()) {
std::vector<nic_veeprom::Accessor*> accessors;
auto& io_context =
sensor_group_to_io_context[kDefaultNicTelemetryRefreshContext];
io_context = std::make_shared<boost::asio::io_context>();
for (const auto& [bus, accessor] : bus_to_v1_accessor) {
accessors.push_back(accessor.get());
}
for (const auto& [bus, accessor] : bus_to_v2_accessor) {
accessors.push_back(accessor.get());
}
for (const auto& [bus, accessor] : bus_to_v4_accessor) {
accessors.push_back(accessor.get());
}
for (const auto& [bus, accessor] : bus_to_v5_accessor) {
accessors.push_back(accessor.get());
}
// Now have a dedicated task to refresh the NIC sensors.
thread_manager.task_scheduler->RunAndScheduleAsync(
[accessors = std::move(accessors),
io_context = io_context](absl::AnyInvocable<void()> on_done) mutable {
for (auto* accessor : accessors) {
boost::asio::post(*io_context,
[accessor]() { accessor->DoRefresh(); });
}
on_done();
},
absl::Seconds(1), absl::Seconds(10));
}
}
} // namespace
nlohmann::json SensorCollector::ToJson() const {
nlohmann::json::object_t response;
for (const auto& [board_config_key, key_to_sensor] : sensor_table_) {
nlohmann::json::object_t sensors;
for (const auto& [key, sensor] : key_to_sensor) {
if (SharedMemBasedSensor::IsSharedMemSensorKey(key)) {
continue;
}
absl::StatusOr<nlohmann::json> sensor_json = sensor->ToJson();
if (!sensor_json.ok()) {
LOG(ERROR) << "Failed to get sensor data for " << key << ": "
<< sensor_json.status();
continue;
}
sensors[key] = *std::move(sensor_json);
}
response[board_config_key] = sensors;
}
return response;
}
SensorCollector::SensorCollector(
std::vector<std::shared_ptr<Sensor>>&& sensors,
absl::flat_hash_map<int, std::unique_ptr<nic_veeprom::Accessor>>&&
bus_to_v1_accessor,
absl::flat_hash_map<int, std::unique_ptr<nic_veeprom::Accessor>>&&
bus_to_v2_accessor,
absl::flat_hash_map<int, std::unique_ptr<nic_veeprom::Accessor>>&&
bus_to_v4_accessor,
absl::flat_hash_map<int, std::unique_ptr<nic_veeprom::Accessor>>&&
bus_to_v5_accessor,
std::unique_ptr<ThreadManager> thread_manager,
const SensorNotification* refresh_notification,
std::optional<int> override_sensor_sampling_interval_ms,
ecclesia::ThreadFactoryInterface* thread_factory,
std::unique_ptr<PeciScanner> peci_scanner,
std::vector<std::shared_ptr<RedfishAggregatedBatch>>&&
redfish_aggregated_batches,
bool enable_threshold_monitoring)
: bus_to_v1_accessor_(std::move(bus_to_v1_accessor)),
bus_to_v2_accessor_(std::move(bus_to_v2_accessor)),
bus_to_v4_accessor_(std::move(bus_to_v4_accessor)),
bus_to_v5_accessor_(std::move(bus_to_v5_accessor)),
sensor_table_(CreateSensorsTable(std::move(sensors))),
redfish_aggregated_batches_(std::move(redfish_aggregated_batches)),
thread_manager_(std::move(thread_manager)),
peci_scanner_(std::move(peci_scanner)),
refresh_notification_(refresh_notification),
override_sensor_sampling_interval_ms_(
override_sensor_sampling_interval_ms),
thread_factory_(thread_factory),
enable_threshold_monitoring_(enable_threshold_monitoring) {
// We only populate sensors_with_thresholds_ if threshold monitoring is
// enabled to avoid unnecessary overhead.
// We do not expect thresholds to be added dynamically at runtime.
if (enable_threshold_monitoring_) {
for (const auto& [_, key_to_sensor] : sensor_table_) {
for (const auto& [_, sensor] : key_to_sensor) {
if (!sensor->GetSensorAttributesDynamic()
.thresholds()
.threshold_configs()
.empty()) {
sensors_with_thresholds_.push_back(sensor);
}
}
}
}
}
SensorCollector::~SensorCollector() {
// In the case of EmptySensorCollector, avoid dereferencing nullptr.
if (thread_manager_ == nullptr) {
return;
}
// Stop the scheduler first.
thread_manager_->task_scheduler->Stop();
// Stop io_contexts.
for (const std::shared_ptr<boost::asio::io_context>& io_context :
thread_manager_->io_contexts) {
io_context->stop();
}
// Finally join all threads.
for (const std::unique_ptr<ecclesia::ThreadInterface>& thread :
thread_manager_->threads) {
thread->Join();
}
peci_scanner_.reset();
thread_manager_.reset();
}
void SensorCollector::GetAllSensorKeysByConfigKey(
const std::string& board_config_key,
std::vector<std::string>& sensor_keys) const {
if (auto it = sensor_table_.find(board_config_key);
it != sensor_table_.end()) {
for (const auto& [key, _] : it->second) {
sensor_keys.push_back(key);
}
}
}
void SensorCollector::ReinitializeAndScheduleAllSensorsForConfigKey(
const std::string& board_config_key) {
auto it = sensor_table_.find(board_config_key);
if (it == sensor_table_.end()) {
LOG(WARNING) << "No sensors found for config key: " << board_config_key
<< ". Skipping reinitialization.";
return;
}
for (const auto& [key, sensor] : it->second) {
sensor->Reinitialize([this, sensor, key](absl::Status status) {
if (!status.ok()) {
LOG(WARNING) << "Failed to reinitialize sensor: " << key
<< " with status: " << status;
return;
}
ScheduleIndividualSensorRead(sensor, refresh_notification_,
override_sensor_sampling_interval_ms_,
*thread_manager_);
});
}
}
void SensorCollector::StartCollection() {
for (const auto& io_context : thread_manager_->io_contexts) {
thread_manager_->threads.push_back(
thread_factory_->New([io_context]() { io_context->run(); }));
}
if (enable_threshold_monitoring_) {
ScheduleThresholdMonitoring();
}
}
std::shared_ptr<const Sensor> SensorCollector::GetSensorByConfigKeyAndSensorKey(
const std::string& board_config_key, const std::string& sensor_key) const {
auto board_it = sensor_table_.find(board_config_key);
if (board_it == sensor_table_.end()) {
return nullptr;
}
auto sensor_it = board_it->second.find(sensor_key);
if (sensor_it == board_it->second.end()) {
return nullptr;
}
return sensor_it->second;
}
void SensorCollector::GetAllSensors(
std::vector<std::shared_ptr<const Sensor>>& all_sensors) const {
for (const auto& [_, key_to_sensor] : sensor_table_) {
for (const auto& [_, sensor] : key_to_sensor) {
all_sensors.push_back(sensor);
}
}
}
std::shared_ptr<const Sensor> SensorCollector::GetSensorBySensorKey(
const std::string& sensor_key) const {
for (const auto& [_, key_to_sensor] : sensor_table_) {
auto it = key_to_sensor.find(sensor_key);
if (it != key_to_sensor.end()) {
return it->second;
}
}
return nullptr;
}
absl::Status SensorCollector::WriteToSensor(const std::string& sensor_key,
const SensorValue& value) {
for (const auto& [_, key_to_sensor] : sensor_table_) {
auto it = key_to_sensor.find(sensor_key);
if (it != key_to_sensor.end()) {
return it->second->WriteReading(value);
}
}
return absl::NotFoundError(absl::StrCat("Sensor not found: ", sensor_key));
}
absl::Status SensorCollector::SetDevpathForSensor(absl::string_view sensor_key,
absl::string_view devpath) {
for (const auto& [_, key_to_sensor] : sensor_table_) {
auto it = key_to_sensor.find(sensor_key);
if (it != key_to_sensor.end()) {
return it->second->SetDevpath(devpath);
}
}
return absl::NotFoundError(absl::StrCat("Sensor not found: ", sensor_key));
}
absl::Status SensorCollector::ConfigureCollection(std::string_view sensor_key,
Mutation mutation) const {
for (auto& [_, key_to_sensor] : sensor_table_) {
auto it = key_to_sensor.find(sensor_key);
if (it != key_to_sensor.end()) {
const std::shared_ptr<Sensor>& sensor = it->second;
const Status status =
sensor->GetSensorAttributesDynamic().state().status();
if (IsCreationIncomplete(status)) {
LOG(WARNING) << "Skipping configuration for sensor " << sensor_key
<< " because sensor status is: " << Status_Name(status);
return absl::OkStatus();
}
if (mutation.interval.has_value()) {
int sampling_interval_ms = mutation.interval.value();
absl::Duration interval;
if (sampling_interval_ms > 0) {
interval = absl::Milliseconds(sampling_interval_ms);
} else {
// If the sampling interval is not set in the config, then we use the
// static refresh interval of the sensor if it is set. Otherwise, we
// use the default sensor sampling interval.
absl::Duration static_refresh_interval = DecodeGoogleApiProto(
sensor->GetSensorAttributesStatic().static_refresh_interval());
if (static_refresh_interval > absl::ZeroDuration()) {
LOG(INFO) << "Using static refresh interval: "
<< static_refresh_interval
<< " for sensor: " << sensor->GetKey();
interval = static_refresh_interval;
} else {
LOG(INFO) << "Using default sensor sampling interval: "
<< kDefaultSensorSamplingInterval
<< " for sensor: " << sensor->GetKey();
interval = kDefaultSensorSamplingInterval;
}
}
{
absl::MutexLock lock(thread_manager_->key_to_task_id_mutex);
thread_manager_->task_scheduler->UpdateTaskPeriod(
thread_manager_->sensor_key_to_task_id.at(sensor_key), interval);
}
sensor->UpdateSensorSamplingInterval(interval);
}
if (mutation.batch_size.has_value()) {
int batch_size = mutation.interval == kResetMonitoring
? 0
: mutation.batch_size.value();
ResizeBufferAndMetrics(sensor, batch_size);
}
return absl::OkStatus();
}
}
return absl::NotFoundError("No sensor found for configuration");
}
std::unique_ptr<MonitoringChangeBase>
SensorCollector::CreateMonitoringChange() {
return std::make_unique<SensorCollectorMonitoringChange>(this);
}
void SensorCollector::ReinitializeSensorByConfigKeyAndSensorKey(
const std::string& board_config_key, const std::string& sensor_key) const {
auto board_it = sensor_table_.find(board_config_key);
if (board_it == sensor_table_.end()) {
LOG(WARNING) << "No sensors found for config key: " << board_config_key
<< ". Skipping reinitialization.";
return;
}
auto sensor_it = board_it->second.find(sensor_key);
if (sensor_it == board_it->second.end()) {
LOG(WARNING) << "Failed to find sensor " << sensor_key << " in config "
<< board_config_key;
return;
}
sensor_it->second->Reinitialize(
[this, sensor_it, sensor_key](absl::Status status) {
if (!status.ok()) {
LOG(WARNING) << "Failed to reinitialize sensor: " << sensor_key
<< " with status: " << status;
return;
}
ScheduleIndividualSensorRead(sensor_it->second, refresh_notification_,
override_sensor_sampling_interval_ms_,
*thread_manager_);
});
}
absl::StatusOr<std::unique_ptr<SensorCollector>> SensorCollector::Create(
const Params& params) {
std::vector<std::shared_ptr<Sensor>> sensors;
std::vector<std::shared_ptr<RedfishAggregatedBatch>>
redfish_aggregated_batches;
auto thread_manager = std::make_unique<ThreadManager>(params.clock);
absl::flat_hash_map<std::string, std::shared_ptr<boost::asio::io_context>>
sensor_group_to_io_context;
ECCLESIA_RETURN_IF_ERROR(
CreateHwmonSensors(params, sensors, sensor_group_to_io_context));
ECCLESIA_RETURN_IF_ERROR(
CreatePsuSensors(params, sensors, sensor_group_to_io_context));
ECCLESIA_ASSIGN_OR_RETURN(
std::vector<std::shared_ptr<FanController>> fan_controllers,
CreateFanControllers(params));
ECCLESIA_RETURN_IF_ERROR(CreateFanSensors(params, fan_controllers, sensors,
sensor_group_to_io_context));
ECCLESIA_RETURN_IF_ERROR(
CreateSharedMemSensors(params, sensors, *thread_manager));
ECCLESIA_RETURN_IF_ERROR(
CreateIntelCpuSensors(params, sensors, sensor_group_to_io_context));
ECCLESIA_RETURN_IF_ERROR(
CreateAdcSensors(params, sensors, sensor_group_to_io_context));
ECCLESIA_RETURN_IF_ERROR(CreateRedfishAggregatedBatchSensors(
params, sensors, redfish_aggregated_batches, sensor_group_to_io_context));
ECCLESIA_RETURN_IF_ERROR(CreateRedfishAggregatedSensors(
params, sensors, sensor_group_to_io_context));
absl::flat_hash_map<int, std::unique_ptr<nic_veeprom::Accessor>>
bus_to_v1_accessor;
absl::flat_hash_map<int, std::unique_ptr<nic_veeprom::Accessor>>
bus_to_v2_accessor;
absl::flat_hash_map<int, std::unique_ptr<nic_veeprom::Accessor>>
bus_to_v4_accessor;
absl::flat_hash_map<int, std::unique_ptr<nic_veeprom::Accessor>>
bus_to_v5_accessor;
ECCLESIA_RETURN_IF_ERROR(CreateNicSensors(
params, sensors, sensor_group_to_io_context, bus_to_v1_accessor,
bus_to_v2_accessor, bus_to_v4_accessor, bus_to_v5_accessor));
ECCLESIA_RETURN_IF_ERROR(
CreateGpioSensors(params, sensors, sensor_group_to_io_context));
// Create virtual sensors last to make sure all physical sensors can be used
// in the formula for virtual sensors.
ECCLESIA_RETURN_IF_ERROR(
CreateVirtualSensors(params, sensors, sensor_group_to_io_context));
// Must run after all sensors are created and before anything consumes the
// sensor list (power callbacks, scheduling, the sensor table), so a dropped
// sensor leaves no trace.
if (GetTlbmcConfig()
.sensor_collector_module()
.drop_failed_duplicate_sensors()) {
DropFailedDuplicateSensors(sensors);
}
ScheduleNicSensorAccessorRefresh(bus_to_v1_accessor, bus_to_v2_accessor,
bus_to_v4_accessor, bus_to_v5_accessor,
sensor_group_to_io_context, *thread_manager);
for (const auto& [sensor_group, io_context] : sensor_group_to_io_context) {
LOG(INFO) << "Starting io_context thread for sensor group: "
<< sensor_group;
boost::asio::executor_work_guard<boost::asio::io_context::executor_type>
work_guard(boost::asio::make_work_guard(*io_context));
thread_manager->work_guards.push_back(std::move(work_guard));
thread_manager->io_contexts.push_back(io_context);
}
for (const auto& sensor : sensors) {
const EntityCommonConfig& entity_common_config =
sensor->GetSensorAttributesStatic().entity_common_config();
if (entity_common_config.power_state() ==
SensorPowerRequirement::SENSOR_POWER_REQUIREMENT_ALWAYS) {
continue;
}
if (params.power_control_collector == nullptr) {
continue;
}
PowerControl* power_control =
params.power_control_collector->GetPowerControl(
entity_common_config.host_id());
if (power_control == nullptr) {
LOG(WARNING) << "No power control found for host: "
<< entity_common_config.host_id();
continue;
}
if (entity_common_config.power_state() ==
SensorPowerRequirement::SENSOR_POWER_REQUIREMENT_CHASSIS_ON) {
power_control->RegisterHostPowerOffToOnCallback(
[sensor]([[maybe_unused]] bool setup_run) {
sensor->PowerOffToOnCallback(setup_run);
});
power_control->RegisterHostPowerOnToOffCallback(
[sensor]([[maybe_unused]] bool setup_run) {
sensor->PowerOnToOffCallback(setup_run);
});
} else if (entity_common_config.power_state() ==
SensorPowerRequirement::SENSOR_POWER_REQUIREMENT_BIOS_POST) {
power_control->RegisterHostOSInactiveToStandbyCallback(
[sensor]([[maybe_unused]] bool setup_run) {
sensor->OSInactiveToStandbyCallback(setup_run);
});
power_control->RegisterHostOSStandbyToInactiveCallback(
[sensor]([[maybe_unused]] bool setup_run) {
sensor->OSStandbyToInactiveCallback(setup_run);
});
}
}
ScheduleAllSensorReads(sensors, params.refresh_notification,
params.override_sensor_sampling_interval_ms,
*thread_manager);
ScheduleAllBatchSensorReads(
redfish_aggregated_batches, params.refresh_notification,
params.override_sensor_sampling_interval_ms, *thread_manager);
// Create the SensorCollector instance first.
std::unique_ptr<SensorCollector> collector(new SensorCollector(
std::move(sensors), std::move(bus_to_v1_accessor),
std::move(bus_to_v2_accessor), std::move(bus_to_v4_accessor),
std::move(bus_to_v5_accessor), std::move(thread_manager),
params.refresh_notification, params.override_sensor_sampling_interval_ms,
params.thread_factory,
/*peci_scanner=*/nullptr, std::move(redfish_aggregated_batches),
params.enable_threshold_monitoring));
if (!params.sensor_configs.intel_cpu_sensor_configs.empty()) {
// Initialize the PeciScanner after the SensorCollector is created,
// so the callback can capture 'this'.
collector->InitializePeciScanner(params);
collector->peci_scanner_->InitializeScanContexts();
}
return collector;
}
void SensorCollector::InitializePeciScanner(const Params& params) {
peci_scanner_ = PeciScanner::Create(
params.sensor_configs.intel_cpu_sensor_configs, params.peci_sysfs,
params.peci_access, thread_manager_->task_scheduler.get(),
[this](const std::string& board_config_key,
const std::string& sensor_key) {
this->ReinitializeSensorByConfigKeyAndSensorKey(board_config_key,
sensor_key);
});
}
void SensorCollector::TriggerPeciScan() {
if (peci_scanner_ != nullptr) {
peci_scanner_->TriggerScan();
}
}
void SensorCollector::ScheduleThresholdMonitoring() {
thread_manager_->task_scheduler->RunAndScheduleAsync(
[this](absl::AnyInvocable<void()> on_done) {
for (const auto& sensor : sensors_with_thresholds_) {
if (!sensor->IsReadyForRead() ||
sensor->GetSensorAttributesDynamic().state().status() ==
STATUS_STALE) {
continue;
}
std::vector<Sensor::ThresholdEvent> events =
sensor->CheckSensorThresholds();
if (events.empty()) {
continue;
}
std::shared_ptr<const SensorValue> sensor_data =
sensor->GetSensorData();
if (sensor_data == nullptr) {
continue;
}
for (const Sensor::ThresholdEvent& event : events) {
PublishSensorThresholdEvent(sensor, sensor_data, event);
}
}
on_done();
},
absl::Seconds(1));
}
void SensorCollector::PublishSensorThresholdEvent(
const std::shared_ptr<const Sensor>& sensor,
const std::shared_ptr<const SensorValue>& sensor_data,
const Sensor::ThresholdEvent& threshold_event) {
const SensorAttributesStatic& static_attributes =
sensor->GetSensorAttributesStatic();
std::optional<gbmc_sel_framework::EventSourceComponent>
event_source_component =
GetEventSourceComponent(static_attributes.unit());
if (!event_source_component.has_value()) {
// Not supported sensor unit. silent return.
return;
}
std::optional<absl::string_view> threshold_event_str =
GetThresholdEventStr(threshold_event.threshold_type);
std::string sensor_name =
GetTrimmedSensorName(static_attributes.attributes().key());
if (!threshold_event_str.has_value()) {
LOG(ERROR) << "Failed to find threshold event for threshold type "
<< threshold_event.threshold_type << " for sensor "
<< sensor_name;
return;
}
gbmc_sel_framework::EventSeverity event_severity =
GetEventSeverity(threshold_event.threshold_type);
gbmc_sel_framework::EventAction event_action =
threshold_event.is_assert
? gbmc_sel_framework::EventAction::EVENT_ACTION_ASSERT
: gbmc_sel_framework::EventAction::EVENT_ACTION_DEASSERT;
std::string event_name = absl::StrCat(
"Sensor ", sensor_name, " reading ", sensor_data->reading(), " ",
gbmc_sel_framework::ToString(event_action), " ", *threshold_event_str);
absl::StatusOr<const std::string&> devpath = sensor->GetDevpath();
absl::string_view devpath_str;
if (devpath.ok()) {
devpath_str = *devpath;
}
gbmc_sel_framework::EventSourceType event_source_type =
GetEventSourceType(devpath_str);
std::vector<std::string> additional_data = BuildAdditionalData(
sensor_name, sensor_data->reading(), *threshold_event_str,
devpath.ok() ? std::make_optional(*devpath) : std::nullopt);
std::vector<absl::string_view> additional_data_view;
additional_data_view.reserve(additional_data.size());
for (const std::string& data : additional_data) {
additional_data_view.push_back(data);
}
absl::Status status;
if (journal_sender_ != nullptr) {
status = gbmc_sel_framework::PublishGBMCSystemEvent(
event_source_type, *event_source_component, event_severity,
event_action, event_name, additional_data_view,
[this](const struct iovec* iov, int iovcnt) {
return journal_sender_(iov, iovcnt);
});
} else {
status = gbmc_sel_framework::PublishGBMCSystemEvent(
event_source_type, *event_source_component, event_severity,
event_action, event_name, additional_data_view);
}
if (!status.ok()) {
// use info log level to avoid flooding the log with threshold events.
LOG(INFO) << "Failed to publish sensor threshold event: " << status;
}
}
std::unique_ptr<EmptySensorCollector> EmptySensorCollector::Create() {
return std::make_unique<EmptySensorCollector>();
}
// Returns the list of sensor names contained by the given config key.
void EmptySensorCollector::GetAllSensorKeysByConfigKey(
const std::string& board_config_key,
std::vector<std::string>& sensor_keys) const {
LOG(WARNING) << "EmptySensorCollector::GetAllSensorKeysByConfigKey is "
"called. This will return an empty list.";
}
// Returns all the sensors.
void EmptySensorCollector::GetAllSensors(
std::vector<std::shared_ptr<const Sensor>>& sensors) const {
LOG(WARNING) << "EmptySensorCollector::GetAllSensorKeysByConfigKey is "
"called. This will return an empty list.";
}
// Returns the sensor for the given sensor key.
std::shared_ptr<const Sensor> EmptySensorCollector::GetSensorBySensorKey(
const std::string& sensor_key) const {
LOG(WARNING) << "EmptySensorCollector::GetSensorBySensorKey is "
"called. This will return a nullptr.";
return nullptr;
}
std::unique_ptr<MonitoringChangeBase>
EmptySensorCollector::CreateMonitoringChange() {
return std::make_unique<FailingMonitoringChange>();
}
absl::Status EmptySensorCollector::ConfigureCollection(
std::string_view sensor_key, Mutation mutation) const {
return absl::UnimplementedError(
"EmptySensorCollector::ConfigureCollection is called. This is not "
"implemented.");
}
std::shared_ptr<const Sensor>
EmptySensorCollector::GetSensorByConfigKeyAndSensorKey(
const std::string& board_config_key, const std::string& sensor_key) const {
LOG(WARNING) << "EmptySensorCollector::GetSensorByConfigKeyAndSensorKey is "
"called. This will return a nullptr.";
return nullptr;
}
absl::Status EmptySensorCollector::WriteToSensor(const std::string& sensor_key,
const SensorValue& value) {
LOG(WARNING) << "EmptySensorCollector::WriteToSensor is "
"called. This will return an error.";
return absl::UnimplementedError(
"EmptySensorCollector::WriteToSensor is called. This is not "
"implemented.");
}
absl::Status EmptySensorCollector::SetDevpathForSensor(
absl::string_view sensor_key, absl::string_view devpath) {
LOG(WARNING) << "EmptySensorCollector::SetDevpathForSensor is "
"called. This will return an error.";
return absl::UnimplementedError(
"EmptySensorCollector::SetDevpathForSensor is called. This is not "
"implemented.");
}
nlohmann::json EmptySensorCollector::GetSchedulerStats() const {
return nlohmann::json::parse("{\"Warning\": \"EmptySensorCollector used.\"}");
}
nlohmann::json EmptySensorCollector::ToJson() const {
return nlohmann::json::parse("{\"Warning\": \"EmptySensorCollector used.\"}");
}
void EmptySensorCollector::StartCollection() {
LOG(WARNING) << "EmptySensorCollector::StartCollection is "
"called. This does nothing.";
}
} // namespace milotic_tlbmc