fboss/platform/rackmon/Rackmon.cpp (286 lines of code) (raw):
// Copyright 2021-present Facebook. All Rights Reserved.
#include "Rackmon.h"
#include <nlohmann/json.hpp>
#include <fstream>
#include <iomanip>
#include "Log.h"
#if (defined(__llvm__) && (__clang_major__ < 9)) || \
(!defined(__llvm__) && (__GNUC__ < 8))
#include <experimental/filesystem>
namespace std {
namespace filesystem = experimental::filesystem;
}
#else
#include <filesystem>
#endif
using nlohmann::json;
using namespace std::literals;
namespace rackmon {
void Rackmon::loadInterface(const nlohmann::json& config) {
if (scanThread_ != nullptr || monitorThread_ != nullptr) {
throw std::runtime_error("Cannot load configuration when started");
}
if (interfaces_.size() > 0) {
throw std::runtime_error("Interfaces already loaded");
}
for (const auto& ifaceConf : config["interfaces"]) {
interfaces_.push_back(makeInterface());
interfaces_.back()->initialize(ifaceConf);
}
}
void Rackmon::loadRegisterMap(const nlohmann::json& config) {
if (scanThread_ != nullptr || monitorThread_ != nullptr) {
throw std::runtime_error("Cannot load configuration when started");
}
registerMapDB_.load(config);
// Precomputing this makes our scan soooo much easier.
// its 256 bytes wasted. but worth it. TODO use a
// interval list with an iterator to waste less bytes.
for (uint16_t addr = config["address_range"][0];
addr <= config["address_range"][1];
++addr) {
allPossibleDevAddrs_.push_back(uint8_t(addr));
}
nextDeviceToProbe_ = allPossibleDevAddrs_.begin();
}
void Rackmon::load(const std::string& confPath, const std::string& regmapDir) {
auto getJSON = [](const std::string& fileName) {
std::ifstream ifs(fileName);
json contents;
try {
ifs >> contents;
} catch (const nlohmann::json::parse_error& ex) {
logError << "Error loading: " << fileName << " byte: " << ex.byte
<< std::endl;
throw;
}
ifs.close();
return contents;
};
loadInterface(getJSON(confPath));
for (auto const& dir_entry : std::filesystem::directory_iterator{regmapDir}) {
loadRegisterMap(getJSON(dir_entry.path().string()));
}
}
bool Rackmon::probe(Modbus& interface, uint8_t addr) {
if (!interface.isPresent()) {
return false;
}
const RegisterMap& rmap = registerMapDB_.at(addr);
std::vector<uint16_t> v(1);
try {
ReadHoldingRegistersReq req(addr, rmap.probeRegister, v.size());
ReadHoldingRegistersResp resp(addr, v);
interface.command(req, resp, rmap.defaultBaudrate, kProbeTimeout);
std::unique_lock lock(devicesMutex_);
devices_[addr] = std::make_unique<ModbusDevice>(interface, addr, rmap);
logInfo << std::hex << std::setw(2) << std::setfill('0') << "Found "
<< int(addr) << " on " << interface.name() << std::endl;
return true;
} catch (std::exception& e) {
return false;
}
}
bool Rackmon::probe(uint8_t addr) {
// We do not support the same address
// on multiple interfaces.
return std::any_of(
interfaces_.begin(), interfaces_.end(), [this, addr](auto& iface) {
return probe(*iface, addr);
});
}
std::vector<uint8_t> Rackmon::inspectDormant() {
time_t curr = std::time(nullptr);
std::vector<uint8_t> ret{};
std::shared_lock lock(devicesMutex_);
for (const auto& it : devices_) {
if (it.second->isActive()) {
continue;
}
// If its more than 300s since last activity, start probing it.
// change to something larger if required.
if ((it.second->lastActive() + kDormantMinInactiveTime) < curr) {
const RegisterMap& rmap = registerMapDB_.at(it.first);
uint16_t probe = rmap.probeRegister;
std::vector<uint16_t> v(1);
try {
uint8_t addr = it.first;
it.second->readHoldingRegisters(probe, v);
ret.push_back(addr);
} catch (...) {
continue;
}
}
}
return ret;
}
void Rackmon::recoverDormant() {
std::vector<uint8_t> candidates = inspectDormant();
for (auto& addr : candidates) {
std::unique_lock lock(devicesMutex_);
devices_.at(addr)->setActive();
}
}
void Rackmon::monitor(void) {
std::shared_lock lock(devicesMutex_);
for (const auto& dev_it : devices_) {
if (!dev_it.second->isActive()) {
continue;
}
dev_it.second->monitor();
}
lastMonitorTime_ = std::time(nullptr);
}
bool Rackmon::isDeviceKnown(uint8_t addr) {
std::shared_lock lk(devicesMutex_);
return devices_.find(addr) != devices_.end();
}
void Rackmon::fullScan() {
logInfo << "Starting scan of all devices" << std::endl;
for (auto& addr : allPossibleDevAddrs_) {
if (isDeviceKnown(addr)) {
continue;
}
for (int i = 0; i < kScanNumRetry; i++) {
if (probe(addr)) {
break;
}
}
}
}
void Rackmon::scan() {
// Circular iterator.
if (reqForceScan_.load()) {
fullScan();
reqForceScan_ = false;
return;
}
// Probe for the address only if we already dont know it.
if (!isDeviceKnown(*nextDeviceToProbe_)) {
probe(*nextDeviceToProbe_);
lastScanTime_ = std::time(nullptr);
}
// Try and recover dormant devices
recoverDormant();
if (++nextDeviceToProbe_ == allPossibleDevAddrs_.end()) {
nextDeviceToProbe_ = allPossibleDevAddrs_.begin();
}
}
std::unique_ptr<PollThread<Rackmon>> Rackmon::makeThread(
std::function<void(Rackmon*)> func,
PollThreadTime interval) {
return std::make_unique<PollThread<Rackmon>>(func, this, interval);
}
void Rackmon::start(PollThreadTime interval) {
if (scanThread_ != nullptr || monitorThread_ != nullptr) {
throw std::runtime_error("Already running");
}
scanThread_ = makeThread(&Rackmon::scan, interval);
scanThread_->start();
monitorThread_ = makeThread(&Rackmon::monitor, interval);
monitorThread_->start();
}
void Rackmon::stop() {
// TODO We probably need a timer to ensure we
// are not waiting here forever.
if (monitorThread_ != nullptr) {
monitorThread_->stop();
monitorThread_ = nullptr;
}
if (scanThread_ != nullptr) {
scanThread_->stop();
scanThread_ = nullptr;
}
}
void Rackmon::rawCmd(Request& req, Response& resp, ModbusTime timeout) {
uint8_t addr = req.addr;
RACKMON_PROFILE_SCOPE(
raw_cmd, "rawcmd::" + std::to_string(int(req.addr)), profileStore_);
std::shared_lock lock(devicesMutex_);
if (!devices_.at(addr)->isActive()) {
throw std::exception();
}
devices_.at(addr)->command(req, resp, timeout);
// Add back the CRC removed by validate.
resp.len += 2;
}
void Rackmon::readHoldingRegisters(
uint8_t deviceAddress,
uint16_t registerOffset,
std::vector<uint16_t>& registerContents,
ModbusTime timeout) {
RACKMON_PROFILE_SCOPE(
raw_cmd,
"readRegs::" + std::to_string(int(deviceAddress)),
profileStore_);
std::shared_lock lock(devicesMutex_);
if (!devices_.at(deviceAddress)->isActive()) {
throw std::exception();
}
devices_.at(deviceAddress)
->readHoldingRegisters(registerOffset, registerContents, timeout);
}
void Rackmon::writeSingleRegister(
uint8_t deviceAddress,
uint16_t registerOffset,
uint16_t value,
ModbusTime timeout) {
RACKMON_PROFILE_SCOPE(
raw_cmd,
"writeReg::" + std::to_string(int(deviceAddress)),
profileStore_);
std::shared_lock lock(devicesMutex_);
if (!devices_.at(deviceAddress)->isActive()) {
throw std::exception();
}
devices_.at(deviceAddress)
->writeSingleRegister(registerOffset, value, timeout);
}
void Rackmon::writeMultipleRegisters(
uint8_t deviceAddress,
uint16_t registerOffset,
std::vector<uint16_t>& values,
ModbusTime timeout) {
RACKMON_PROFILE_SCOPE(
raw_cmd,
"writeRegs::" + std::to_string(int(deviceAddress)),
profileStore_);
std::shared_lock lock(devicesMutex_);
if (!devices_.at(deviceAddress)->isActive()) {
throw std::exception();
}
devices_.at(deviceAddress)
->writeMultipleRegisters(registerOffset, values, timeout);
}
void Rackmon::readFileRecord(
uint8_t deviceAddress,
std::vector<FileRecord>& records,
ModbusTime timeout) {
RACKMON_PROFILE_SCOPE(
raw_cmd,
"ReadFile::" + std::to_string(int(deviceAddress)),
profileStore_);
std::shared_lock lock(devicesMutex_);
if (!devices_.at(deviceAddress)->isActive()) {
throw std::exception();
}
devices_.at(deviceAddress)->readFileRecord(records, timeout);
}
std::vector<ModbusDeviceInfo> Rackmon::listDevices() const {
std::shared_lock lock(devicesMutex_);
std::vector<ModbusDeviceInfo> devices;
std::transform(
devices_.begin(),
devices_.end(),
std::back_inserter(devices),
[](auto& kv) { return kv.second->getInfo(); });
return devices;
}
void Rackmon::getRawData(std::vector<ModbusDeviceRawData>& data) const {
data.clear();
std::shared_lock lock(devicesMutex_);
std::transform(
devices_.begin(), devices_.end(), std::back_inserter(data), [](auto& kv) {
return kv.second->getRawData();
});
}
void Rackmon::getValueData(std::vector<ModbusDeviceValueData>& data) const {
data.clear();
std::shared_lock lock(devicesMutex_);
std::transform(
devices_.begin(), devices_.end(), std::back_inserter(data), [](auto& kv) {
return kv.second->getValueData();
});
}
std::string Rackmon::getProfileData() {
std::stringstream ss;
profileStore_.swap(ss);
return ss.str();
}
} // namespace rackmon