| // 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 |