blob: f9f0ca25a9ecc12098fe405d9d9b13888ddb4fc7 [file]
#include "tlbmc/hal/shared_mem/static_server_impl.h"
#include <cerrno>
#include <cstdint>
#include <filesystem> // NOLINT
#include <fstream>
#include <limits>
#include <memory>
#include <new>
#include <string>
#include <string_view>
#include <system_error> // NOLINT
#include <utility>
#include "absl/base/no_destructor.h"
#include "absl/container/flat_hash_map.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/time/time.h"
#include "absl/types/span.h"
#include "boost/interprocess/exceptions.hpp" // NOLINT
#include "boost/interprocess/managed_shared_memory.hpp" //NOLINT
#include "g3/macros.h"
#include "tlbmc/hal/shared_mem/metrics.h"
#include "tlbmc/hal/shared_mem/sensors.h"
#include "tlbmc/hal/shared_mem/server_interface.h"
#include "tlbmc/hal/shared_mem/static_shm_common.h"
namespace milotic_tlbmc {
namespace {
bool PathExists(const std::filesystem::path& path) {
std::error_code ec;
bool exists = std::filesystem::exists(path, ec);
if (ec) {
LOG(ERROR) << "Filesystem error while checking for " << path << ": "
<< ec.message();
return false;
}
return exists;
}
} // namespace
static std::unique_ptr<TlbmcSharedMemoryType> FindExistingSegment(
const std::string& initialized_file_path, const std::string& shm_name) {
if (!PathExists(initialized_file_path)) {
return nullptr;
}
try {
return std::make_unique<TlbmcSharedMemoryType>(
boost::interprocess::open_only, shm_name.c_str());
} catch (const boost::interprocess::interprocess_exception& e) {
LOG(ERROR) << "Failed to open shared memory: " << e.what();
return nullptr;
}
return nullptr;
}
static void LoadDataFromSegment(
TlbmcSharedMemoryType& segment,
absl::flat_hash_map<std::string, IpcSensor*>& sensors_idx) {
for (auto it = segment.named_begin(); it != segment.named_end(); ++it) {
const std::string_view name(it->name(), it->name_length());
if (name.empty()) {
continue;
}
if (name.starts_with("__")) {
continue;
}
auto val = segment.find<IpcSensor>(it->name());
if (val.first != nullptr) {
sensors_idx[name] = val.first;
}
}
}
static std::unique_ptr<StaticSharedMemoryServer> LoadServer(
const std::string& initialized_file_path, const std::string& shm_name) {
auto shared_memory = FindExistingSegment(initialized_file_path, shm_name);
if (shared_memory == nullptr) {
return nullptr;
}
absl::flat_hash_map<std::string, IpcSensor*> sensors_idx;
LoadDataFromSegment(*shared_memory, sensors_idx);
return std::make_unique<StaticSharedMemoryServer>(
std::move(shared_memory), std::move(sensors_idx),
StaticSharedMemoryMetrics::GetInProcessMetrics());
}
/* These values are approximate, with some headroom. Precise calculation would
* require factoring in memory alignment, size of internal data structures, and
* length of sensor names
*/
constexpr uint64_t kBytesPerSensor = 128;
constexpr uint64_t kMinObjectOverhead = 64;
constexpr uint64_t kConstantOverhead = 256;
static std::unique_ptr<TlbmcSharedMemoryType> CreateNewSegment(
const std::string& shm_name, uint64_t sensor_count) {
try {
// Creates the segment.
if (boost::interprocess::shared_memory_object::remove(shm_name.c_str())) {
LOG(WARNING) << "Shared memory already existed without a initialization "
"file. Cleared previous segment ";
}
uint64_t size_to_reserve =
(sensor_count * (sizeof(IpcSensor) + kBytesPerSensor)) +
kConstantOverhead;
return std::make_unique<TlbmcSharedMemoryType>(
boost::interprocess::create_only, shm_name.c_str(), size_to_reserve);
} catch (...) {
return nullptr;
}
return nullptr;
}
class FallbackServer : public IpcServer {
public:
absl::StatusOr<std::pair<float, uint64_t>> ReadSensorValue(
const std::string& /*sensor_name*/) override {
return absl::UnavailableError(
"Using fallback server due to error initializing shared memory");
}
// copybara:strip_begin(g3-shared-libs)
void UpdateMetricsRequestCount(bool is_tlbmc_request) override {
StaticSharedMemoryMetrics::GetInProcessMetrics()->UpdateMetricsRequestCount(
is_tlbmc_request);
}
void UpdateMetricsResponse(absl::Duration response_time, int status_code,
std::string_view resource_url) override {
StaticSharedMemoryMetrics::GetInProcessMetrics()->UpdateMetricsResponse(
response_time, status_code, resource_url);
}
void UpdateMetricsRps(absl::Duration time_delta) override {
StaticSharedMemoryMetrics::GetInProcessMetrics()->UpdateMetricsRps(
time_delta);
}
// copybara:strip_end
};
class StaticSharedMemoryInitializer {
static absl::Status CreateInitFile(const std::string& initialized_file_path) {
std::filesystem::path const directory =
std::filesystem::path(initialized_file_path).parent_path();
if (!PathExists(directory)) {
std::error_code ec;
std::filesystem::create_directories(directory, ec);
if (ec) {
LOG(ERROR) << "Failed to create directory: " << directory << ": "
<< ec.message();
return absl::InternalError("Failed to create directory");
}
}
errno = 0;
std::ofstream file(std::string{initialized_file_path});
if (!file.is_open()) {
int const error_num = errno;
auto error_message =
absl::StrCat("Failed to open file at: ", initialized_file_path);
if (error_num != 0) {
return absl::ErrnoToStatus(error_num, error_message);
}
return absl::UnavailableError(error_message);
}
// An error can happen during write/flush but we don't rely on the contents
// of the file so we can ignore the error here
file << 1;
file.flush();
return absl::OkStatus();
}
static absl::Status Allocate(TlbmcSharedMemoryType& segment,
absl::Span<const std::string> shm_sensors) {
segment.reserve_named_objects(shm_sensors.size());
for (const auto& sensor_name : shm_sensors) {
if (segment.construct<IpcSensor>(sensor_name.c_str(), std::nothrow)() ==
nullptr) {
return absl::UnknownError("Cannot allocate sensor");
}
}
return absl::OkStatus();
}
public:
static absl::Status Initialize(
const std::string& initialized_file_path, const std::string& shm_name,
const absl::Span<const std::string> shm_sensors) {
if (FindExistingSegment(initialized_file_path, shm_name) != nullptr) {
return absl::OkStatus();
}
auto new_segment = CreateNewSegment(shm_name, shm_sensors.size());
if (new_segment == nullptr) {
return absl::InternalError("Failed to create new shared memory");
}
ECCLESIA_RETURN_IF_ERROR(Allocate(*new_segment, shm_sensors));
auto status = CreateInitFile(initialized_file_path);
if (!status.ok()) {
LOG(ERROR) << "Failed to create init file" << status;
return status;
}
return absl::OkStatus();
}
static void TearDownForTesting(const std::string& initialized_file_path) {
std::error_code ec;
std::filesystem::remove(initialized_file_path, ec);
if (ec) {
LOG(ERROR) << "Error calling remove for path '" << initialized_file_path
<< "': " << ec.message();
}
}
};
absl::Status StaticSharedMemoryServer::InitializeSharedMemoryForUnitTest(
absl::Span<const std::string> shm_sensors) {
StaticSharedMemoryInitializer::TearDownForTesting(
"/tmp/tlbmc/static_shm_initialized");
return InitializeSharedMemory("/tmp/tlbmc/static_shm_initialized",
kStaticShmName, shm_sensors);
}
void StaticSharedMemoryServer::SetupInstanceForUnitTest() {
GetInstance("/tmp/tlbmc/static_shm_initialized", kStaticShmName, true);
}
absl::Status StaticSharedMemoryServer::InitializeSharedMemory(
absl::Span<const std::string> shm_sensors) {
return InitializeSharedMemory(kStaticShmInitializedFile, kStaticShmName,
shm_sensors);
}
absl::Status StaticSharedMemoryServer::InitializeSharedMemory(
const std::string& initialized_file_path, const std::string& shm_name,
absl::Span<const std::string> shm_sensors) {
return StaticSharedMemoryInitializer::Initialize(initialized_file_path,
shm_name, shm_sensors);
}
IpcServer& StaticSharedMemoryServer::GetInstance() {
return GetInstance(kStaticShmInitializedFile, kStaticShmName);
}
std::unique_ptr<IpcServer> StaticSharedMemoryServer::PerformServerInit(
const std::string& initialized_file_path, const std::string& shm_name) {
std::unique_ptr<StaticSharedMemoryServer> server =
LoadServer(initialized_file_path, shm_name);
if (server == nullptr) {
LOG(ERROR) << "Failed to initialize shared memory server , falling "
"back to dummy ";
return std::make_unique<FallbackServer>();
}
return server;
}
IpcServer& StaticSharedMemoryServer::GetInstance(
const std::string& initialized_file_path, const std::string& shm_name,
bool force_reset_for_testing) {
static absl::NoDestructor instance(
(PerformServerInit(initialized_file_path, shm_name)));
if (force_reset_for_testing) {
*instance = PerformServerInit(initialized_file_path, shm_name);
}
return **instance;
}
absl::StatusOr<std::pair<float, uint64_t>>
StaticSharedMemoryServer::ReadSensorValue(const std::string& sensor_name) {
auto sensor_entry = name_to_sensor_.find(sensor_name);
if (sensor_entry == name_to_sensor_.end()) {
return absl::UnavailableError("Requested sensor does not exist");
}
std::pair<float, uint64_t> value = sensor_entry->second->GetValue();
// If the sensor value is not updated by the client, the value will be set
// to infinity and the timestamp will be 0.
if (value.first == std::numeric_limits<float>::infinity() ||
value.second == 0) {
return absl::UnavailableError(
"Sensor value is still invalid due to no update from client.");
}
return value;
}
// copybara:strip_begin(g3-shared-libs)
void StaticSharedMemoryServer::UpdateMetricsRequestCount(
bool is_tlbmc_request) {
if (metrics_ == nullptr) {
return;
}
metrics_->UpdateMetricsRequestCount(is_tlbmc_request);
}
void StaticSharedMemoryServer::UpdateMetricsResponse(
absl::Duration response_time, int status_code,
std::string_view resource_url) {
if (metrics_ == nullptr) {
return;
}
metrics_->UpdateMetricsResponse(response_time, status_code, resource_url);
}
void StaticSharedMemoryServer::UpdateMetricsRps(absl::Duration time_delta) {
if (metrics_ == nullptr) {
return;
}
metrics_->UpdateMetricsRps(time_delta);
}
// copybara:strip_end
StaticSharedMemoryServer::StaticSharedMemoryServer(
std::unique_ptr<TlbmcSharedMemoryType> memory,
absl::flat_hash_map<std::string, IpcSensor*> name_to_sensor,
TlbmcMetrics* metrics)
: memory_(std::move(memory)),
name_to_sensor_(std::move(name_to_sensor)),
metrics_(metrics) {}
} // namespace milotic_tlbmc