gloo/common/linux.cc (212 lines of code) (raw):
/**
* Copyright (c) 2017-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "gloo/common/linux.h"
#include "gloo/common/linux_devices.h"
#include <dirent.h>
#include <errno.h>
#include <ifaddrs.h>
#include <linux/ethtool.h>
#include <linux/sockios.h>
#include <linux/version.h>
#include <net/if.h>
#include <netinet/in.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
#include <algorithm>
#include <array>
#include <fstream>
#include <map>
#include <mutex>
#include "gloo/common/logging.h"
#ifndef SPEED_UNKNOWN
/* SPEED_UNKOWN is sometimes undefined, c.f.
* https://github.com/facebookincubator/gloo/pull/127
*/
#define SPEED_UNKNOWN 0
#endif
namespace gloo {
const std::set<std::string>& kernelModules() {
static std::once_flag once;
static std::set<std::string> modules;
std::call_once(once, [](){
std::ifstream ifs("/proc/modules");
std::string line;
while (std::getline(ifs, line)) {
auto sep = line.find(' ');
GLOO_ENFORCE_NE(sep, std::string::npos);
modules.insert(line.substr(0, sep));
}
});
return modules;
}
static const std::string kSysfsPath = "/sys/bus/pci/devices/";
static std::vector<std::string> listDir(const std::string& path) {
DIR* dirp;
struct dirent* dirent;
std::vector<std::string> result;
dirp = opendir(path.c_str());
if (dirp == nullptr && errno == ENOENT) {
// Ignore non-directories
return result;
}
GLOO_ENFORCE(dirp != nullptr, strerror(errno));
errno = 0;
while ((dirent = readdir(dirp)) != nullptr) {
if (dirent->d_name[0] == '.') {
continue;
}
result.push_back(dirent->d_name);
}
GLOO_ENFORCE(errno == 0, strerror(errno));
auto rv = closedir(dirp);
GLOO_ENFORCE(rv == 0, strerror(errno));
return result;
}
static unsigned int pciGetClass(const std::string& id) {
auto path = kSysfsPath + id + "/class";
std::ifstream ifs(path);
GLOO_ENFORCE(ifs.good());
unsigned int pciClass = 0;
ifs.ignore(2);
ifs >> std::hex >> pciClass;
return pciClass;
}
std::vector<std::string> pciDevices(PCIClassMatch match) {
std::vector<std::string> devices;
for (const auto& device : listDir(kSysfsPath)) {
if (match.value != (pciGetClass(device) & match.mask)) {
continue;
}
devices.push_back(device);
}
return devices;
}
static std::string pciPath(const std::string& id) {
auto path = kSysfsPath + id;
std::transform(path.begin(), path.end(), path.begin(), ::tolower);
std::array<char, 256> buf;
auto rv = readlink(path.c_str(), buf.data(), buf.size());
GLOO_ENFORCE_NE(rv, -1, strerror(errno));
GLOO_ENFORCE_LT(rv, buf.size());
return std::string(buf.data(), rv);
}
template<typename Out>
void split(const std::string& s, char delim, Out result) {
std::stringstream ss;
ss.str(s);
std::string item;
while (std::getline(ss, item, delim)) {
*(result++) = item;
}
}
int pciDistance(const std::string& a, const std::string& b) {
std::vector<std::string> partsA;
split(pciPath(a), '/', std::back_inserter(partsA));
std::vector<std::string> partsB;
split(pciPath(b), '/', std::back_inserter(partsB));
// Count length of common prefix
auto prefixLength = 0;
for (;;) {
if (prefixLength == partsA.size()) {
break;
}
if (prefixLength == partsB.size()) {
break;
}
if (partsA[prefixLength] != partsB[prefixLength]) {
break;
}
prefixLength++;
}
return (partsA.size() - prefixLength) + (partsB.size() - prefixLength);
}
const std::string& interfaceToBusID(const std::string& name) {
static std::once_flag once;
static std::map<std::string, std::string> map;
static std::string default_busid;
std::call_once(once, [](){
for (const auto& device : pciDevices(kPCIClassNetwork)) {
// Register interfaces for this devices
const auto path = kSysfsPath + device + "/net";
for (const auto& interface : listDir(path)) {
map[interface] = device;
}
}
});
auto it = map.find(name);
if (it != map.end()) {
return it->second;
}
return default_busid;
}
const std::string& infinibandToBusID(const std::string& name) {
static std::once_flag once;
static std::map<std::string, std::string> map;
std::call_once(once, [](){
for (const auto& device : pciDevices(kPCIClassNetwork)) {
// Register interfaces for this devices
const auto path = kSysfsPath + device + "/infiniband";
for (const auto& interface : listDir(path)) {
map[interface] = device;
}
}
});
return map[name];
}
static int getInterfaceSpeedGLinkSettings(int sock, struct ifreq* ifr) {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,6,0)
constexpr auto link_mode_data_nwords = 3 * 127;
struct {
struct ethtool_link_settings req;
__u32 link_mode_data[link_mode_data_nwords];
} ecmd;
int rv;
ifr->ifr_data = (__caddr_t)&ecmd;
memset(&ecmd, 0, sizeof(ecmd));
ecmd.req.cmd = ETHTOOL_GLINKSETTINGS;
rv = ioctl(sock, SIOCETHTOOL, ifr);
if (rv < 0 || ecmd.req.link_mode_masks_nwords >= 0) {
return SPEED_UNKNOWN;
}
ecmd.req.cmd = ETHTOOL_GLINKSETTINGS;
ecmd.req.link_mode_masks_nwords = -ecmd.req.link_mode_masks_nwords;
rv = ioctl(sock, SIOCETHTOOL, ifr);
if (rv < 0) {
return SPEED_UNKNOWN;
}
return ecmd.req.speed;
#else
(void)sock;
(void)ifr;
return SPEED_UNKNOWN;
#endif
}
static int getInterfaceSpeedGSet(int sock, struct ifreq* ifr) {
struct ethtool_cmd edata;
int rv;
ifr->ifr_data = (__caddr_t)&edata;
memset(&edata, 0, sizeof(edata));
edata.cmd = ETHTOOL_GSET;
rv = ioctl(sock, SIOCETHTOOL, ifr);
if (rv < 0) {
return SPEED_UNKNOWN;
}
return ethtool_cmd_speed(&edata);
}
int getInterfaceSpeedByName(const std::string& ifname) {
int sock;
struct ifreq ifr;
int rv;
size_t len;
sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
if (sock < 0) {
return SPEED_UNKNOWN;
}
memset(&ifr, 0, sizeof(ifreq));
len = ifname.length();
len = std::min(len, sizeof(ifr.ifr_name) - 1);
memcpy(ifr.ifr_name, ifname.c_str(), len);
ifr.ifr_name[len] = '\0';
rv = getInterfaceSpeedGLinkSettings(sock, &ifr);
if (rv != SPEED_UNKNOWN) {
close(sock);
return rv;
}
rv = getInterfaceSpeedGSet(sock, &ifr);
close(sock);
return rv;
}
} // namespace gloo