blob: bffb5a673ee11f79e6a586d5bf6d794b5615c09e [file] [edit]
// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/procfs_byte_counter.h"
#include <stdio.h>
#include <glog/logging.h>
namespace cashew {
static const char *kProcNetDevPath = "/proc/net/dev";
static const int kLineBufferSize = 256;
// /proc/net/dev output format
// must remain consistent with kernel (see net/core/dev.c)
static const char *kProcNetDevExpectedLine1 =
"Inter-| Receive "
" | Transmit\n";
static const char *kProcNetDevExpectedLine2 =
" face |bytes packets errs drop fifo frame "
"compressed multicast|bytes packets errs "
"drop fifo colls carrier compressed\n";
static const char *kProcNetDevLineFormat =
"%s %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu\n";
static const int kProcNetDevLineFormatNumElements = 17;
// UpdateDelegateWithStats timer interval
// TODO(vlaviano): can reduce this once we implement a smarter ShouldEmitSignal
// policy than 'return true'
static const guint kUpdateDelegateIntervalSeconds = 60;
ProcfsByteCounter::ProcfsByteCounter(const std::string& interface)
: interface_(interface), proc_net_dev_fp_(NULL), read_stats_source_id_(0),
counter_rx_bytes_(0), counter_tx_bytes_(0), baseline_stats_(),
baseline_initialized_(false), counter_changed_since_last_update_(false),
delegate_(NULL) {
DCHECK(!interface_.empty());
// initialize our counter state
if (!DoReadStats()) {
LOG(WARNING) << interface_ << ": ctor: DoReadStats failed";
// fall through so that we'll try again later
}
// schedule periodic UpdateDelegateWithStats calls to
// update counter state in the future
read_stats_source_id_ = g_timeout_add_seconds(
kUpdateDelegateIntervalSeconds,
StaticUpdateDelegateWithStatsCallback, this);
if (read_stats_source_id_ == 0) {
LOG(ERROR) << interface_ << ": ctor: g_idle_add failed";
return;
}
}
ProcfsByteCounter::~ProcfsByteCounter() {
DCHECK(proc_net_dev_fp_ == NULL);
if (read_stats_source_id_ != 0 && !g_source_remove(read_stats_source_id_)) {
LOG(WARNING) << interface_ << ": dtor: g_source_remove failed";
}
// read and update delegate with most recent stats
if (!UpdateDelegateWithStats()) {
LOG(WARNING) << interface_ << ": dtor: UpdateDelegateWithStats failed";
}
}
const std::string& ProcfsByteCounter::GetInterface() const {
return interface_;
}
uint64 ProcfsByteCounter::GetRxBytes() const {
return counter_rx_bytes_;
}
uint64 ProcfsByteCounter::GetTxBytes() const {
return counter_tx_bytes_;
}
bool ProcfsByteCounter::ReadStats() {
return DoReadStats();
}
void ProcfsByteCounter::SetDelegate(ByteCounterDelegate *delegate) {
LOG(INFO) << interface_ << ": SetDelegate";
delegate_ = delegate;
}
// private methods
bool ProcfsByteCounter::OpenProcNetDev() {
DCHECK(proc_net_dev_fp_ == NULL);
if ((proc_net_dev_fp_ = fopen(kProcNetDevPath, "r")) == NULL) {
PLOG(ERROR) << interface_ << ": OpenProcNetDev: couldn't open "
<< kProcNetDevPath;
return false;
}
return true;
}
void ProcfsByteCounter::CloseProcNetDev() {
if (proc_net_dev_fp_ != NULL) {
if (fclose(proc_net_dev_fp_)) {
PLOG(WARNING) << interface_ << ": CloseProcNetDev: fclose failed";
}
proc_net_dev_fp_ = NULL;
}
DCHECK(proc_net_dev_fp_ == NULL);
}
// static
gboolean ProcfsByteCounter::StaticUpdateDelegateWithStatsCallback(
gpointer data) {
ProcfsByteCounter *counter = reinterpret_cast<ProcfsByteCounter*>(data);
CHECK_NOTNULL(counter);
if (!counter->UpdateDelegateWithStats()) {
LOG(WARNING) << counter->GetInterface()
<< ": StaticUpdateDelegateWithStatsCallback"
<< ": UpdateDelegateWithStats failed";
}
return TRUE; // we want to be called again later
}
bool ProcfsByteCounter::UpdateDelegateWithStats() {
// read stats
if (!DoReadStats()) {
LOG(WARNING) << interface_
<< ": UpdateDelegateWithStats: DoReadStats failed";
return false;
}
// update the delegate with changes
UpdateDelegate();
return true;
}
void ProcfsByteCounter::UpdateDelegate() {
if (delegate_ != NULL && counter_changed_since_last_update_) {
// reset counter changed flag since we are about to update the delegate
counter_changed_since_last_update_ = false;
// update delegate
delegate_->OnByteCounterUpdate(this, counter_rx_bytes_,
counter_tx_bytes_);
// NOTE: OnByteCounterUpdate can cause this counter to be deleted, so
// we're limited in what we can do beyond this point. See this FAQ:
// http://www.parashift.com/c++-faq-lite/freestore-mgmt.html#faq-16.15
}
}
bool ProcfsByteCounter::DoReadStats() {
// we open and close the virtual file each time through, because we get stale
// data if we keep the file open and rewind/read repeatedly
if (!OpenProcNetDev()) {
LOG(ERROR) << interface_ << ": DoReadStats: OpenProcDevNet failed";
return false;
}
char line_buffer[kLineBufferSize];
for (int line_number = 1; // use 1-based counting to make log msgs sensible
fgets(line_buffer, sizeof(line_buffer), proc_net_dev_fp_) != NULL;
++line_number) {
// the first two lines contain human-friendly column headers
// skip them, while ensuring that they are as we expect
// this provides some confidence that the format hasn't changed on us
if (line_number == 1 || line_number == 2) {
if (strncmp(line_buffer, (line_number == 1) ? kProcNetDevExpectedLine1 :
kProcNetDevExpectedLine2, sizeof(line_buffer))) {
LOG(ERROR) << interface_ << ": DoReadStats: line " << line_number
<< " is not as we expect";
CloseProcNetDev();
return false;
}
continue;
}
// each subsequent line contains the name and stats for a network interface
// parse it, reading stats, and see if the name matches |interface_|
//
// replace first colon in string (immediately after device name) with a
// space so that sscanf greedy string parsing doesn't cause the colon and
// possibly the rx byte count following it to all be consumed as part of the
// device name.
char *colon_ptr = strchr(line_buffer, ':');
if (colon_ptr == NULL) {
LOG(ERROR) << interface_ << ": DoReadStats: line " << line_number
<< ": strchr failed to find colon";
CloseProcNetDev();
return false;
}
*colon_ptr = ' ';
char device_name[IFNAMSIZ];
struct netdev_stats stats;
memset(&stats, 0, sizeof(stats));
// cpplint tells us that sscanf, while not prohibited, is sketchy
// we'll risk it since this data is from the kernel proc fs and we provide
// the format string
//
// note that some fields of netdev_stats are combined when producing the
// contents of /proc/net/dev
// we assign these combined values to the field named in the column header
int result = sscanf(line_buffer, kProcNetDevLineFormat, // NOLINT
device_name, &stats.rx_bytes, &stats.rx_packets,
&stats.rx_errors, &stats.rx_dropped,
&stats.rx_fifo_errors, &stats.rx_frame_errors,
&stats.rx_compressed, &stats.multicast, &stats.tx_bytes,
&stats.tx_packets, &stats.tx_errors, &stats.tx_dropped,
&stats.tx_fifo_errors, &stats.collisions,
&stats.tx_carrier_errors, &stats.tx_compressed);
if (result == EOF && ferror(proc_net_dev_fp_)) {
PLOG(ERROR) << interface_ << ": DoReadStats: line " << line_number
<< ": sscanf failed";
CloseProcNetDev();
return false;
}
if (result != kProcNetDevLineFormatNumElements) {
LOG(ERROR) << interface_ << ": DoReadStats: line " << line_number
<< ": sscanf matched " << ((result == EOF) ? 0 : result)
<< " elements (" << kProcNetDevLineFormatNumElements << " expected)";
CloseProcNetDev();
return false;
}
if (device_name != interface_) {
continue;
}
// we found a match
// update our counter based on new stats
CloseProcNetDev();
OnStatsUpdate(stats);
return true;
}
LOG(ERROR) << interface_ << ": DoReadStats: no matching device";
CloseProcNetDev();
return false;
}
void ProcfsByteCounter::OnStatsUpdate(const struct netdev_stats& stats) {
LOG(INFO) << interface_ << ": OnStatsUpdate: stats: rx_bytes = "
<< stats.rx_bytes << ", tx_bytes = " << stats.tx_bytes;
if (baseline_initialized_) {
LOG(INFO) << interface_ << ": OnStatsUpdate: baseline: rx_bytes = "
<< baseline_stats_.rx_bytes << ", tx_bytes = "
<< baseline_stats_.tx_bytes;
uint64 old_counter_rx_bytes = counter_rx_bytes_;
uint64 old_counter_tx_bytes = counter_tx_bytes_;
LOG(INFO) << interface_ << ": OnStatsUpdate: old counter: rx_bytes = "
<< old_counter_rx_bytes << ", tx_bytes = " << old_counter_tx_bytes;
// these seemingly naive computations handle wraparound due to the modular
// nature of unsigned subtraction
counter_rx_bytes_ += stats.rx_bytes - baseline_stats_.rx_bytes;
counter_tx_bytes_ += stats.tx_bytes - baseline_stats_.tx_bytes;
baseline_stats_ = stats;
LOG(INFO) << interface_ << ": OnStatsUpdate: new counter: rx_bytes = "
<< counter_rx_bytes_ << ", tx_bytes = " << counter_tx_bytes_;
// check if we have changed counters since last update
if (!counter_changed_since_last_update_) {
counter_changed_since_last_update_ =
counter_rx_bytes_ != old_counter_rx_bytes ||
counter_tx_bytes_ != old_counter_tx_bytes;
}
} else {
// We initialize the baseline here and update it on on each new sample so
// that we aren't confused about how many times the device counter has
// wrapped around since we've started. This works as long as the sample
// interval is short enough to prevent wrap around between consecutive
// samples.
baseline_stats_ = stats;
baseline_initialized_ = true;
}
}
} // namespace cashew