| #include "tlbmc/store/store_hft_adapter.h" |
| |
| #include <cmath> |
| #include <cstddef> |
| #include <cstdint> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/log/log.h" |
| #include "absl/memory/memory.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/str_join.h" |
| #include "absl/strings/str_split.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/strings/substitute.h" |
| #include "absl/time/time.h" |
| #include "g3/macros.h" |
| #include "tlbmc/adapter/data_source.h" |
| #include "fru_identifier.pb.h" |
| #include "fru_payload.pb.h" |
| #include "identifier.pb.h" |
| #include "payload.pb.h" |
| #include "sensor_identifier.pb.h" |
| #include "sensor_payload.pb.h" |
| #include "subscription_params.pb.h" |
| #include "tlbmc/collector/monitoring_change_base.h" |
| #include "topology_config.pb.h" |
| #include "fru.pb.h" |
| #include "memory.pb.h" |
| #include "processor.pb.h" |
| #include "resource.pb.h" |
| #include "sensor.pb.h" |
| #include "tlbmc/sensors/sensor.h" |
| #include "tlbmc/store/store.h" |
| #include "tlbmc/time/time.h" |
| |
| namespace milotic_tlbmc { |
| |
| namespace { |
| |
| constexpr absl::string_view kAllFrusIdentifier = "all_frus"; |
| |
| bool IsGetAllFrus(const milotic_hft::Identifier& identifier) { |
| return identifier.fru_identifier().barepath() == kAllFrusIdentifier; |
| } |
| |
| std::vector<milotic_hft::Identifier> GetSensorIdentifiers(const Store* store) { |
| std::vector<milotic_hft::Identifier> identifiers; |
| for (const std::shared_ptr<const Sensor>& sensor : store->GetAllSensors()) { |
| milotic_hft::Identifier identifier; |
| identifier.mutable_sensor_identifier()->set_name(sensor->GetKey()); |
| identifiers.push_back(identifier); |
| } |
| return identifiers; |
| } |
| |
| absl::StatusOr<milotic_hft::Payload> CollectSensorData( |
| const milotic_hft::SensorIdentifier& sensor_identifier, Store* store, |
| absl::Time start_time) { |
| milotic_hft::Payload payload; |
| milotic_hft::HighFrequencySensorsReadingsBatch* batch = |
| payload.mutable_high_frequency_sensors_readings_batch(); |
| std::shared_ptr<const Sensor> sensor = |
| store->GetSensorBySensorKey(sensor_identifier.name()); |
| if (sensor == nullptr) { |
| return absl::NotFoundError( |
| absl::Substitute("Sensor $0 not found", sensor_identifier.name())); |
| } |
| const SensorAttributesDynamic dynamic_attributes = |
| sensor->GetSensorAttributesDynamic(); |
| batch->set_configured_sampling_interval_ms( |
| dynamic_attributes.refresh_interval().nanos() / 1000000); |
| milotic_hft::HighFrequencySensorsReadings* sensors = |
| batch->add_high_frequency_sensors(); |
| milotic_hft::SensorIdentifier* sensor_identifier_from_readings = |
| sensors->mutable_sensor_identifier(); |
| sensors->mutable_state()->set_status( |
| ConvertToHftStatus(dynamic_attributes.state().status())); |
| if (dynamic_attributes.state().has_status_message()) { |
| sensors->mutable_state()->set_status_message( |
| dynamic_attributes.state().status_message()); |
| } |
| const SensorAttributesStatic& static_attributes = |
| sensor->GetSensorAttributesStatic(); |
| |
| // Replace the local root identifier with /phys when exporting the devpath. |
| ECCLESIA_ASSIGN_OR_RETURN(const std::string& devpath, |
| store->GetDevpathFromSensor(sensor)); |
| if (!absl::StartsWith(devpath, "/phys")) { |
| std::vector<std::string> devpath_parts = absl::StrSplit(devpath, '/'); |
| if (devpath_parts.size() < 2) { |
| return absl::InternalError( |
| absl::Substitute("Invalid devpath: $0", devpath)); |
| } |
| size_t device_index = devpath_parts[1].find_first_of(':'); |
| if (device_index != std::string::npos) { |
| devpath_parts[1] = |
| absl::StrCat("phys", devpath_parts[1].substr(device_index)); |
| } else { |
| devpath_parts[1] = "phys"; |
| } |
| sensor_identifier_from_readings->set_devpath( |
| absl::StrJoin(devpath_parts, "/")); |
| } else { |
| sensor_identifier_from_readings->set_devpath(devpath); |
| } |
| std::string sensor_name = GetTrimmedSensorName(sensor->GetKey()); |
| sensor_identifier_from_readings->set_key(sensor->GetKey()); |
| sensor_identifier_from_readings->set_name(sensor_name); |
| sensor_identifier_from_readings->set_source(milotic_hft::SENSOR_SOURCE_BMC); |
| sensor_identifier_from_readings->set_units( |
| ConvertToHftUnit(static_attributes.unit())); |
| sensor_identifier_from_readings->set_type( |
| ConvertToHftType(static_attributes.unit())); |
| for (const auto& sensor_data : |
| sensor->GetSensorDataHistorySince(start_time)) { |
| // Drop non-finite readings (NaN/Inf, e.g. from a virtual-sensor expression |
| // or a malformed parse) so they never enter the HFT stream. A genuinely |
| // unavailable read carries no reading and is surfaced via sensor status |
| // instead. This also keeps the UNIT_COUNT cast below well-defined. |
| if (sensor_data->has_reading() && !std::isfinite(sensor_data->reading())) { |
| continue; |
| } |
| milotic_hft::HighFrequencySensorReading* reading = |
| sensors->add_timestamped_readings(); |
| if (sensor_data->has_reading()) { |
| if (static_attributes.unit() == UNIT_COUNT) { |
| reading->set_count(static_cast<uint64_t>(sensor_data->reading())); |
| } else { |
| reading->set_float_reading(static_cast<float>(sensor_data->reading())); |
| } |
| } else if (sensor_data->has_count()) { |
| reading->set_count(sensor_data->count()); |
| } |
| reading->set_timestamp_ns( |
| absl::ToUnixNanos(DecodeGoogleApiProto(sensor_data->timestamp()))); |
| if (sensor_data->has_source_timestamp()) { |
| reading->set_source_timestamp_ns(absl::ToUnixNanos( |
| DecodeGoogleApiProto(sensor_data->source_timestamp()))); |
| } else { |
| reading->set_source_timestamp_ns(reading->timestamp_ns()); |
| } |
| } |
| return payload; |
| } |
| |
| void PopulateFruDetails(const milotic_tlbmc::Fru& fru, |
| milotic_hft::FruDetails* details) { |
| details->mutable_fru_identifier()->set_barepath( |
| fru.attributes().expected_hardware_info().expected_barepath()); |
| details->set_presence_status(fru.attributes().presence_status()); |
| if (fru.data().has_fru_info()) { |
| *details->mutable_i2c_info() = fru.data().fru_info(); |
| } else if (fru.data().has_memory_info()) { |
| *details->mutable_memory() = fru.data().memory_info(); |
| } else if (fru.data().has_processor_info()) { |
| *details->mutable_processor() = fru.data().processor_info(); |
| } |
| } |
| |
| absl::StatusOr<milotic_hft::Payload> CollectFruData(Store* store) { |
| if (store->GetFruCollector() == nullptr) { |
| return absl::InternalError("FruCollector is null"); |
| } |
| |
| milotic_hft::Payload payload; |
| milotic_hft::FruBatch* batch = payload.mutable_fru_batch(); |
| |
| // FRUs from Deterministic Scans |
| milotic_tlbmc::FruTable determ_frus = |
| store->GetFruCollector()->GetCopyOfDeterministicFruTable(); |
| for (const auto& [key, fru] : determ_frus.key_to_fru()) { |
| if (!fru.attributes().expected_hardware_info().has_expected_barepath()) { |
| // See comment in |
| // https://source.corp.google.com/piper///depot/google3/third_party/milotic/external/cc/fast_sanity/service/fru_service.cc;l=385 |
| LOG(WARNING) << "FRU key: " << key << " does not have expected barepath."; |
| continue; |
| } |
| PopulateFruDetails(fru, batch->add_frus()); |
| } |
| |
| // SMBIOS FRUs |
| milotic_tlbmc::FruTable smbios_frus = |
| store->GetFruCollector()->GetCopyOfSmbiosFruTable(); |
| for (const auto& [key, fru] : smbios_frus.key_to_fru()) { |
| PopulateFruDetails(fru, batch->add_frus()); |
| } |
| return payload; |
| } |
| |
| // An implementation of the SensorMutationBatch which uses the |
| // MonitoringChangeBase interface to apply changes to the store. |
| // This is used only for Identifier::kSensorIdentifier. |
| class StoreSensorMutationBatch : public milotic_hft::SensorMutationBatch { |
| public: |
| StoreSensorMutationBatch( |
| milotic_hft::DataSource* data_source, |
| const std::vector<milotic_hft::Identifier>& identifiers, |
| std::unique_ptr<MonitoringChangeBase> monitoring_change) |
| : milotic_hft::SensorMutationBatch(data_source, identifiers), |
| monitoring_change_(std::move(monitoring_change)) {} |
| |
| absl::Status SetSamplingInterval(const milotic_hft::Identifier& identifier, |
| int sampling_interval_ms) & |
| override { |
| ECCLESIA_RETURN_IF_ERROR(CheckIdentifier(identifier)); |
| if (identifier.identifier_case() != |
| milotic_hft::Identifier::kSensorIdentifier) { |
| return absl::InvalidArgumentError("Not a sensor identifier"); |
| } |
| return AddSensor(identifier.sensor_identifier().name(), |
| {sampling_interval_ms, std::nullopt}); |
| } |
| |
| absl::Status SetBatchSize(const milotic_hft::Identifier& identifier, |
| int max_batch_size) & |
| override { |
| ECCLESIA_RETURN_IF_ERROR(CheckIdentifier(identifier)); |
| if (identifier.identifier_case() != |
| milotic_hft::Identifier::kSensorIdentifier) { |
| return absl::InvalidArgumentError("Not a sensor identifier"); |
| } |
| return AddSensor(identifier.sensor_identifier().name(), |
| {std::nullopt, max_batch_size}); |
| } |
| |
| absl::Status ResetSensor(const milotic_hft::Identifier& identifier) & |
| override { |
| ECCLESIA_RETURN_IF_ERROR(CheckIdentifier(identifier)); |
| if (identifier.identifier_case() != |
| milotic_hft::Identifier::kSensorIdentifier) { |
| return absl::InvalidArgumentError("Not a sensor identifier"); |
| } |
| return AddSensor(identifier.sensor_identifier().name(), |
| {kResetMonitoring, 1}); |
| } |
| |
| absl::Status Apply() && override { |
| std::move(*monitoring_change_).Apply(); |
| return absl::OkStatus(); |
| } |
| |
| private: |
| absl::Status AddSensor(std::string_view key, |
| ::milotic_tlbmc::Mutation&& mutation) { |
| return monitoring_change_->AddSensor(key, std::move(mutation)) |
| ? absl::OkStatus() |
| : absl::InvalidArgumentError(absl::Substitute( |
| "Failed to add sensor $0 to monitoring change", key)); |
| } |
| |
| absl::Status CheckIdentifier(const milotic_hft::Identifier& identifier) { |
| if (identifier.identifier_case() != |
| milotic_hft::Identifier::kSensorIdentifier) { |
| return absl::InvalidArgumentError("Not a sensor identifier"); |
| } |
| return milotic_hft::SensorMutationBatch::CheckIdentifier(identifier); |
| } |
| |
| std::unique_ptr<MonitoringChangeBase> monitoring_change_; |
| }; |
| } // namespace |
| |
| milotic_hft::SensorUnits ConvertToHftUnit(SensorUnit unit) { |
| switch (unit) { |
| case UNIT_DEGREE_CELSIUS: |
| return milotic_hft::SENSOR_UNIT_DEGREES; |
| case UNIT_WATT: |
| return milotic_hft::SENSOR_UNIT_WATTS; |
| case UNIT_AMPERE: |
| return milotic_hft::SENSOR_UNIT_AMPS; |
| case UNIT_VOLT: |
| return milotic_hft::SENSOR_UNIT_VOLTS; |
| case UNIT_REVOLUTION_PER_MINUTE: |
| return milotic_hft::SENSOR_UNIT_RPM; |
| case UNIT_PERCENT: |
| return milotic_hft::SENSOR_UNIT_PERCENT; |
| case UNIT_COUNT: |
| return milotic_hft::SENSOR_UNIT_COUNT; |
| case UNIT_JOULES: |
| return milotic_hft::SENSOR_UNIT_JOULES; |
| case UNIT_PASCAL: |
| return milotic_hft::SENSOR_UNIT_PASCAL; |
| default: |
| return milotic_hft::SENSOR_UNIT_UNSPECIFIED; |
| } |
| } |
| |
| milotic_hft::SensorType ConvertToHftType(SensorUnit unit) { |
| switch (unit) { |
| case UNIT_DEGREE_CELSIUS: |
| return milotic_hft::SENSOR_TYPE_THERMAL; |
| case UNIT_WATT: |
| return milotic_hft::SENSOR_TYPE_POWER; |
| case UNIT_AMPERE: |
| return milotic_hft::SENSOR_TYPE_CURRENT; |
| case UNIT_VOLT: |
| return milotic_hft::SENSOR_TYPE_VOLTAGE; |
| case UNIT_REVOLUTION_PER_MINUTE: |
| return milotic_hft::SENSOR_TYPE_FANTACH; |
| case UNIT_PERCENT: |
| return milotic_hft::SENSOR_TYPE_DUTYCYCLE; |
| case UNIT_COUNT: |
| return milotic_hft::SENSOR_TYPE_COUNTER; |
| case UNIT_JOULES: |
| return milotic_hft::SENSOR_TYPE_ENERGY; |
| case UNIT_PASCAL: |
| return milotic_hft::SENSOR_TYPE_ALTITUDE; |
| default: |
| return milotic_hft::SENSOR_TYPE_UNSPECIFIED; |
| } |
| } |
| |
| milotic_hft::Status ConvertToHftStatus(milotic_tlbmc::Status status) { |
| // Translate the tlBMC status to the corresponding HFT status. |
| // This should be updated to handle new HFT statuses as needed. |
| switch (status) { |
| case milotic_tlbmc::STATUS_READY: |
| return milotic_hft::STATUS_OK; |
| case milotic_tlbmc::STATUS_STALE: |
| return milotic_hft::STATUS_STALE; |
| case milotic_tlbmc::STATUS_CREATION_FAILED: |
| case milotic_tlbmc::STATUS_CREATION_PENDING: |
| return milotic_hft::STATUS_MISSING; |
| default: |
| return milotic_hft::STATUS_UNKNOWN; |
| } |
| } |
| |
| absl::StatusOr<std::unique_ptr<StoreHftAdapter>> StoreHftAdapter::Create( |
| Store* store) { |
| return absl::WrapUnique(new StoreHftAdapter(store)); |
| } |
| |
| // Note: sensor mutation is handled by StoreSensorMutationBatch. |
| absl::Status StoreHftAdapter::ConfigureSamplingInterval( |
| const milotic_hft::Identifier& identifier, int sampling_interval_ms) { |
| LOG(INFO) << "Configure called for identifier: " << identifier; |
| switch (identifier.identifier_case()) { |
| case milotic_hft::Identifier::kFruIdentifier: |
| if (!IsGetAllFrus(identifier)) { |
| return absl::InvalidArgumentError(absl::Substitute( |
| "Identifier $0 is not supported. Only " |
| "CONFIGURATION_TYPE_CONFIGURE_ALL_RESOURCES is supported for FRUs", |
| identifier.identifier_case())); |
| } |
| |
| ECCLESIA_RETURN_IF_ERROR(store_->ConfigureDeterministicCollection( |
| {.sampling_interval_ms = sampling_interval_ms})); |
| |
| break; |
| // Add support for other telemetry types. |
| default: |
| return absl::InvalidArgumentError(absl::Substitute( |
| "Identifier $0 is not supported", identifier.identifier_case())); |
| } |
| return absl::OkStatus(); |
| } |
| |
| absl::Status StoreHftAdapter::ResetSamplingIntervalToDefault( |
| const milotic_hft::Identifier& identifier) { |
| switch (identifier.identifier_case()) { |
| case milotic_hft::Identifier::kFruIdentifier: |
| if (!IsGetAllFrus(identifier)) { |
| return absl::InvalidArgumentError(absl::Substitute( |
| "Identifier $0 is not supported. Only " |
| "CONFIGURATION_TYPE_CONFIGURE_ALL_RESOURCES is supported for FRUs", |
| identifier.identifier_case())); |
| } |
| return store_->ConfigureDeterministicCollection( |
| {.sampling_interval_ms = 0}); |
| // Add support for other telemetry types. |
| default: |
| return absl::InvalidArgumentError(absl::Substitute( |
| "Identifier $0 is not supported", identifier.identifier_case())); |
| } |
| } |
| |
| absl::StatusOr<milotic_hft::Payload> StoreHftAdapter::Collect( |
| const milotic_hft::Identifier& identifier, absl::Time start_time) const { |
| switch (identifier.identifier_case()) { |
| case milotic_hft::Identifier::kSensorIdentifier: |
| return CollectSensorData(identifier.sensor_identifier(), store_, |
| start_time); |
| case milotic_hft::Identifier::kFruIdentifier: |
| if (!IsGetAllFrus(identifier)) { |
| return absl::InvalidArgumentError(absl::Substitute( |
| "Identifier $0 is not supported. Only " |
| "CONFIGURATION_TYPE_CONFIGURE_ALL_RESOURCES is supported for FRUs", |
| identifier.identifier_case())); |
| } |
| return CollectFruData(store_); |
| // Add support for other telemetry types. |
| default: |
| return absl::InvalidArgumentError("Identifier is not supported"); |
| } |
| } |
| |
| std::unique_ptr<milotic_hft::SensorMutationBatch> |
| StoreHftAdapter::CreateSensorMutationBatch( |
| milotic_hft::SubscriptionPolicy::ResourceType resource_type, |
| std::vector<milotic_hft::Identifier> identifiers) { |
| if (resource_type == milotic_hft::SubscriptionPolicy::RESOURCE_TYPE_SENSOR) { |
| return std::make_unique<StoreSensorMutationBatch>( |
| this, identifiers, store_->CreateMonitoringChange()); |
| } |
| return milotic_hft::DataSource::CreateSensorMutationBatch( |
| resource_type, std::move(identifiers)); |
| } |
| |
| absl::StatusOr<std::vector<milotic_hft::Identifier>> |
| StoreHftAdapter::GetIdentifiersForResourceType( |
| const milotic_hft::SubscriptionPolicy::ResourceType& resource_type) const { |
| switch (resource_type) { |
| case milotic_hft::SubscriptionPolicy::RESOURCE_TYPE_SENSOR: |
| return GetSensorIdentifiers(store_); |
| case milotic_hft::SubscriptionPolicy::RESOURCE_TYPE_FRU: { |
| std::vector<milotic_hft::Identifier> identifiers; |
| milotic_hft::Identifier identifier; |
| identifier.mutable_fru_identifier()->set_barepath(kAllFrusIdentifier); |
| identifiers.push_back(identifier); |
| return identifiers; |
| } |
| default: |
| return absl::InvalidArgumentError("Resource type is not supported"); |
| } |
| } |
| |
| absl::Status StoreHftAdapter::ConfigureBatchSize( |
| const milotic_hft::Identifier& identifier, int max_batch_size) { |
| switch (identifier.identifier_case()) { |
| case milotic_hft::Identifier::kFruIdentifier: |
| return absl::OkStatus(); |
| default: |
| return absl::InvalidArgumentError(absl::Substitute( |
| "Identifier $0 is not supported", identifier.identifier_case())); |
| } |
| } |
| |
| absl::Status StoreHftAdapter::ResetBatchSizeToDefault( |
| const milotic_hft::Identifier& identifier) { |
| switch (identifier.identifier_case()) { |
| case milotic_hft::Identifier::kFruIdentifier: |
| return absl::OkStatus(); |
| default: |
| return absl::InvalidArgumentError(absl::Substitute( |
| "Identifier $0 is not supported", identifier.identifier_case())); |
| } |
| } |
| |
| } // namespace milotic_tlbmc |