MetroCollect  2.3.4
SourceEthtool.cc
Go to the documentation of this file.
1 //
2 // SourceEthtool.cc
3 //
4 // Created on August 8th 2018
5 //
6 // Copyright 2018 CFM (www.cfm.fr)
7 //
8 // Licensed under the Apache License, Version 2.0 (the "License");
9 // you may not use this file except in compliance with the License.
10 // You may obtain a copy of the License at
11 //
12 // http://www.apache.org/licenses/LICENSE-2.0
13 //
14 // Unless required by applicable law or agreed to in writing, software
15 // distributed under the License is distributed on an "AS IS" BASIS,
16 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 // See the License for the specific language governing permissions and
18 // limitations under the License.
19 //
20 
21 #include <linux/ethtool.h>
22 #include <linux/sockios.h>
23 #include <sys/ioctl.h>
24 #include <sys/socket.h>
25 #include <unistd.h>
26 
27 #include <algorithm>
28 #include <cstring>
29 #include <numeric>
30 
31 #include "SourceEthtool.h"
32 
33 
36  name(aName),
37  fieldCount(0)
38  {
39  strncpy(this->ifr.ifr_name, aName, IF_NAMESIZE);
40  }
41 
42 
44  errno = 0;
45  this->fd = socket(AF_INET, SOCK_DGRAM, 0);
46  if (errno) {
47  perror("Ethtool: Error opening socket");
48  errno = 0;
49  }
50  }
51 
53  int result = 0;
54  errno = 0;
55  do {
56  result = close(this->fd);
57  } while (result == -1 && errno == EINTR);
58  if (errno) {
59  perror("Ethtool: Error closing socket");
60  errno = 0;
61  }
62  }
63 
64 
66  gatherIfData();
67  }
68 
69 
71  struct ifreq ifr;
72 
73  this->ifInfo_.clear();
74 
75  while (true) {
76  ifr.ifr_ifindex = this->ifInfo_.size() + 1;
77  int val = ioctl(this->socketfd_.fd, SIOCGIFNAME, &ifr);
78  if (val != 0)
79  break;
80  ifr.ifr_name[IF_NAMESIZE - 1] = '\0';
81  this->ifInfo_.emplace_back(ifr.ifr_name);
82  }
83  errno = 0;
84 
85  auto driverInfo = EthtoolPointer<struct ethtool_drvinfo>(ETHTOOL_GDRVINFO);
86  auto ssetInfo = EthtoolPointer<struct ethtool_sset_info, uint32_t>(ETHTOOL_GSSET_INFO, 1);
87  for (size_t i = 0; i < this->ifInfo_.size(); i++) {
88  this->ifInfo_[i].ifr.ifr_data = driverInfo.raw();
89  if (ioctl(this->socketfd_.fd, SIOCETHTOOL, &this->ifInfo_[i].ifr) == 0)
90  this->ifInfo_[i].driver = driverInfo->driver;
91  else {
92  this->ifInfo_[i].driver = "unknown";
93  errno = 0;
94  }
95  ssetInfo->sset_mask = 1 << ETH_SS_STATS;
96  this->ifInfo_[i].ifr.ifr_data = ssetInfo.raw();
97  if (ioctl(this->socketfd_.fd, SIOCETHTOOL, &this->ifInfo_[i].ifr) == 0) {
98  size_t count = ssetInfo->sset_mask ? ssetInfo->data[0] : 0;
99  this->ifInfo_[i].fieldCount = count;
100  this->ifInfo_[i].statsValues = EthtoolPointer<struct ethtool_stats, uint64_t>(ETHTOOL_GSTATS, count);
101  if (count == 0)
102  continue;
103 
104  auto gstrings = EthtoolPointer<struct ethtool_gstrings, char[ETH_GSTRING_LEN]>(ETHTOOL_GSTRINGS, count);
105  gstrings->string_set = ETH_SS_STATS;
106  gstrings->len = count;
107  this->ifInfo_[i].ifr.ifr_data = gstrings.raw();
108  if (ioctl(this->socketfd_.fd, SIOCETHTOOL, &this->ifInfo_[i].ifr) == 0) {
109  for (size_t j = 0; j < gstrings->len; j++) {
110  gstrings->data[(j + 1) * ETH_GSTRING_LEN - 1] = '\0';
111  this->ifInfo_[i].fieldNames.emplace_back(this->parseEthtoolString(reinterpret_cast<char*>(&gstrings->data[j * ETH_GSTRING_LEN])));
112  }
113  this->ifInfo_[i].fieldIndexes.resize(this->ifInfo_[i].fieldCount);
114  std::iota(this->ifInfo_[i].fieldIndexes.begin(), this->ifInfo_[i].fieldIndexes.end(), 0);
115  if (this->ifInfo_[i].driver == "ixgbe")
116  this->ixgbeIfFilter(this->ifInfo_[i]);
117  } else {
118  perror("Ethtool: cannot read device stats strings");
119  errno = 0;
120  }
121  } else {
122  perror("Ethtool: cannot read device stats count");
123  errno = 0;
124  }
125  this->ifInfo_[i].ifr.ifr_data = nullptr;
126  }
127  }
128 
130  std::string indexString = "";
131  size_t index = ethtoolString.find_first_of("0123456789");
132  if (index != std::string::npos) {
133  size_t count = 0;
134  while (std::isdigit(ethtoolString[index + count])) {
135  indexString.append(ethtoolString, index + count, 1);
136  count++;
137  }
138  if (index + count >= ethtoolString.size())
139  indexString.clear();
140  else
141  ethtoolString.replace(index, count, "i");
142  }
143 
144  bool wasCapitalized = false;
145  for (size_t i = 0; i < ethtoolString.size(); i++) {
146  if (!std::isalnum(ethtoolString[i]) && ethtoolString[i] != '_') {
147  if (i > 0 && ethtoolString[i - 1] != '_') {
148  ethtoolString[i] = '_';
149  } else {
150  ethtoolString.erase(i, 1);
151  i--;
152  }
153  }
154  else if (std::isupper(ethtoolString[i])) {
155  if (i > 0 && ethtoolString[i - 1] != '_' && std::islower(ethtoolString[i - 1]) && !wasCapitalized) {
156  ethtoolString.insert(i, "_");
157  i++;
158  }
159  ethtoolString[i] = std::tolower(ethtoolString[i]);
160  wasCapitalized = true;
161  } else
162  wasCapitalized = false;
163  }
164  while (ethtoolString.back() == '_')
165  ethtoolString.erase(ethtoolString.size() - 1, 1);
166  while (ethtoolString.front() == '_')
167  ethtoolString.erase(0, 1);
168 
170  ret.name = std::move(ethtoolString);
171  if (!indexString.empty())
172  ret.index = std::move(indexString);
173  return ret;
174  }
175 
177  auto channels = EthtoolPointer<struct ethtool_channels>(ETHTOOL_GCHANNELS);
178  ifInfo.ifr.ifr_data = channels.raw();
179  if (ioctl(this->socketfd_.fd, SIOCETHTOOL, &ifInfo.ifr) == 0) {
180  ifInfo.fieldCount = ifInfo.fieldNames.size();
181  ifInfo.fieldIndexes.clear();
182  for (size_t i = 0; i < ifInfo.fieldNames.size(); i++) {
183  const char* name = ifInfo.fieldNames[i].name.c_str();
184  if ((name[0] == 'r' || name[0] == 't') && std::strncmp(name + 1, "x_queue_i_", 10) == 0 && ifInfo.fieldNames[i].index.has_value() && std::stoull(ifInfo.fieldNames[i].index.value()) >= channels->combined_count)
185  ifInfo.fieldCount--;
186  else
187  ifInfo.fieldIndexes.push_back(i);
188  }
189  } else {
190  perror("Ethtool: cannot read device channels");
191  errno = 0;
192  }
193  ifInfo.ifr.ifr_data = nullptr;
194  }
195 
196 
197  size_t SourceEthtool::fieldCount() const noexcept {
198  size_t count = 0;
199  for (const auto& info : this->ifInfo_)
200  count += info.fieldCount;
201  return count;
202  }
203 
204  const std::vector<size_t> SourceEthtool::indexesOfFieldName(const FieldName& fieldName, Interests* interests) const noexcept {
205  if (fieldName[0] != SourceEthtool::sourcePrefix)
206  return {};
207 
208  size_t baseIndex = 0;
209  std::vector<size_t> indexes;
210  for (size_t i = 0; i < this->ifInfo_.size(); i++) {
211  if (fieldName[2] == this->ifInfo_[i].name || (fieldName[1] == this->ifInfo_[i].driver && fieldName[2] == SourceEthtool::fieldNameAll)) {
212  auto itr = std::find_if(this->ifInfo_[i].fieldIndexes.begin(), this->ifInfo_[i].fieldIndexes.end(), [&](const auto& a) {
213  return this->ifInfo_[i].fieldNames[a].name == fieldName[3];
214  });
215  while (itr != this->ifInfo_[i].fieldIndexes.end()) {
216  indexes.push_back(std::distance(this->ifInfo_[i].fieldIndexes.begin(), itr) + baseIndex);
217  if (interests)
218  interests->set(i);
219  itr++;
220  itr = std::find_if(itr, this->ifInfo_[i].fieldIndexes.end(), [&](const auto& a) {
221  return this->ifInfo_[i].fieldNames[a].name == fieldName[3];
222  });
223  }
224  }
225  baseIndex += this->ifInfo_[i].fieldCount;
226  }
227 
228  return indexes;
229  }
230 
231  const std::string SourceEthtool::fieldNameSourcePrefix() const noexcept {
232  return std::string(SourceEthtool::sourcePrefix);
233  }
234 
235  const FieldInfo SourceEthtool::fieldInfoAtIndex(size_t index) const noexcept {
236  for (size_t i = 0; i < this->ifInfo_.size(); i++) {
237  if (index < this->ifInfo_[i].fieldCount) {
238  auto& fieldName = this->ifInfo_[i].fieldNames[this->ifInfo_[i].fieldIndexes[index]];
239  FieldName name = {std::string(SourceEthtool::sourcePrefix), this->ifInfo_[i].driver, this->ifInfo_[i].name, fieldName.name};
240  if (fieldName.index)
241  name.push_back(fieldName.index.value());
242  std::string unit = findUnit(fieldName.name, SourceEthtool::fieldUnitsAssociation, SourceEthtool::defaultUnit);
243  FieldInfo info = {name, "Interface " + this->ifInfo_[i].name + " metric: " + fieldName.name, unit, 2, std::string(SourceEthtool::fieldNameInterfaceDescription)};
244  if (fieldName.index)
245  info.dynamicIndexes.emplace_back(4, std::string(SourceEthtool::fieldNameIndexDescription));
246  return info;
247  } else
248  index -= this->ifInfo_[i].fieldCount;
249  }
250  return {};
251  }
252 
253  const std::vector<FieldInfo> SourceEthtool::allFieldsInfo() const noexcept {
254  std::vector<FieldInfo> info;
255  size_t count = this->fieldCount();
256  for (size_t i = 0; i < count; i++)
257  info.push_back(this->fieldInfoAtIndex(i));
258 
259  std::sort(info.begin(), info.end(), [&](const auto& a, const auto& b) {
260  auto cmp = alphanumCompare(a.name[1].c_str(), b.name[1].c_str());
261  if (cmp < 0)
262  return true;
263  if (cmp == 0) {
264  cmp = alphanumCompare(a.name[3].c_str(), b.name[3].c_str());
265  if (cmp < 0)
266  return true;
267  if (cmp == 0 && a.name.size() > 4 && b.name.size() > 4) {
268  cmp = alphanumCompare(a.name[4].c_str(), b.name[4].c_str());
269  if (cmp < 0)
270  return true;
271  }
272  }
273  return false;
274  });
275  auto last = std::unique(info.begin(), info.end(), [&](const auto& a, const auto& b) {
276  return (a.name[1] == b.name[1] && a.name[3] == b.name[3]);
277  });
278  info.erase(last, info.end());
279 
280  for (auto& field : info) {
281  for (const auto& dynamicIndex: field.dynamicIndexes)
282  field.name[dynamicIndex.index] = std::string(SourceEthtool::fieldNameAll);
283  }
284 
285  return info;
286  }
287 
288 
289  void SourceEthtool::fetchData(const Interests& interests, DataArray::Iterator current) {
290  for (size_t i = 0; i < this->ifInfo_.size(); i++) {
291  if (this->ifInfo_[i].fieldCount == 0)
292  continue;
293  else if (!interests.isSet(i)) {
294  std::fill_n(current, this->ifInfo_[i].fieldCount, 0);
295  current += this->ifInfo_[i].fieldCount;
296  continue;
297  }
298 
299  this->ifInfo_[i].ifr.ifr_data = this->ifInfo_[i].statsValues.raw();
300  if (ioctl(this->socketfd_.fd, SIOCETHTOOL, &this->ifInfo_[i].ifr) == 0) {
301  for (size_t j: this->ifInfo_[i].fieldIndexes) {
302  *current = static_cast<DataValueType>(this->ifInfo_[i].statsValues->data[j]);
303  current++;
304  }
305  } else {
306  perror("Ethtool: cannot read device stats values");
307  errno = 0;
308  std::fill_n(current, this->ifInfo_[i].fieldCount, 0);
309  }
310  this->ifInfo_[i].ifr.ifr_data = nullptr;
311  }
312  }
313 
314 
315  void SourceEthtool::computeDiff(const Interests& interests, DiffArray::Iterator diff, DataArray::ConstIterator current, DataArray::ConstIterator previous, double factor) noexcept {
316  for (size_t i = 0; i < this->ifInfo_.size(); i++) {
317  if (!interests.isSet(i))
318  continue;
319  for (size_t j = 0; j < this->ifInfo_[i].fieldCount; j++) {
320  *diff = static_cast<DiffValueType>((*current - *previous) * factor);
321  diff++;
322  current++;
323  previous++;
324  }
325  }
326  }
327 }
Object used to describe a field (or metric)
Definition: SourceField.h:37
Socketfd socketfd_
Ethtool socket manager.
std::vector< std::string > FieldName
Type used for field names (an array of strings)
Definition: SourceField.h:31
Namespace for sources of metrics objects and operations.
std::vector< InterfaceInfo > ifInfo_
Array holding details of each network interface.
std::optional< std::string > index
index of the metric if the metric name has an index
Class managing a pointer to a dynamically sized type.
Definition: SourceEthtool.h:57
static constexpr std::string_view fieldNameAll
Metrics name wildcard.
Definition: SourceEthtool.h:43
std::vector< DataValueType >::const_iterator ConstIterator
Const iterator type of MetricsDataArray.
Definition: MetricTypes.h:37
void fetchData(const Interests &interests, DataArray::Iterator current) override final
Fetch the latest metrics.
const FieldInfo fieldInfoAtIndex(size_t index) const noexcept override final
Get details about a specific field.
SourceEthtool()
Private default constructor.
const std::vector< size_t > indexesOfFieldName(const FieldName &fieldName, Interests *interests=nullptr) const noexcept override final
Search for fields associated to a specific name.
static constexpr std::array fieldUnitsAssociation
Metric units associations.
Definition: SourceEthtool.h:46
const std::vector< FieldInfo > allFieldsInfo() const noexcept override final
Get all fields details, field which share a common name should appear only once.
static constexpr std::string_view sourcePrefix
Metrics name source prefix.
Definition: SourceEthtool.h:42
void gatherIfData()
Gather details on interfaces.
static constexpr std::string_view fieldNameIndexDescription
Metrics name category description.
Definition: SourceEthtool.h:45
struct ifreq ifr
Ethtool data structure of this interface.
static constexpr std::string_view fieldNameInterfaceDescription
Metrics name category description.
Definition: SourceEthtool.h:44
int64_t DataValueType
Type of fetched raw metrics.
Definition: MetricTypes.h:28
int alphanumCompare(const char *l, const char *r)
Alphanumeric string comparison.
Definition: SourceTools.cc:70
Boolean array to keep track of which fields a source has to fetch metrics for.
InterfaceInfo::NameAndIndex parseEthtoolString(std::string ethtoolString)
Parse raw Ethtool metric name.
std::vector< size_t > fieldIndexes
Indexes of relevant metrics (some may be skipped)
std::vector< DiffValueType >::iterator Iterator
Iterator type of MetricsDiffArray.
Definition: MetricTypes.h:44
Socketfd()
Construct a new Socketfd object.
static constexpr std::string_view defaultUnit
Metric unit.
Definition: SourceEthtool.h:47
std::vector< NameAndIndex > fieldNames
Names of the metrics.
void ixgbeIfFilter(InterfaceInfo &ifInfo)
Filter out relevant metrics for ixgbe driver.
void computeDiff(const Interests &interests, DiffArray::Iterator diff, DataArray::ConstIterator current, DataArray::ConstIterator previous, double factor=1) noexcept override final
Compute the variation (in appropriate unit) of metrics.
caddr_t raw() const noexcept
Returns the raw managed pointer with Ethtool-specific type.
size_t fieldCount
Number of metrics of the interface.
size_t fieldCount() const noexcept override final
Get the number of field the source has.
const std::string fieldNameSourcePrefix() const noexcept override final
Get the prefix by which all field names should begin with.
InterfaceInfo()=delete
Deleted default constructor.
Class to store the details and metrics names of a network interface.
std::string findUnit(const std::string &name, const std::array< KeyUnit, N > &keyUnitAssociation, const std::string_view &defaultUnit)
Tries to associate a field name with a unit, falling back on the default unit if none matches...
Definition: SourceTools.h:96
std::vector< IndexAndDescription > dynamicIndexes
Indexes and descriptions of the dynamic parts of the field name.
Definition: SourceField.h:48
std::vector< DataValueType >::iterator Iterator
Iterator type of MetricsDataArray.
Definition: MetricTypes.h:36
bool isSet(T index) const
Checks if a specific bit is true.
double DiffValueType
Type of metric variation.
Definition: MetricTypes.h:29