| OLD | NEW |
| (Empty) | |
| 1 // Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "src/procfs_byte_counter.h" |
| 6 |
| 7 #include <stdio.h> |
| 8 |
| 9 #include <glog/logging.h> |
| 10 |
| 11 namespace cashew { |
| 12 |
| 13 static const char *kProcNetDevPath = "/proc/net/dev"; |
| 14 static const int kLineBufferSize = 256; |
| 15 |
| 16 // /proc/net/dev output format |
| 17 // must remain consistent with kernel (see net/core/dev.c) |
| 18 static const char *kProcNetDevExpectedLine1 = |
| 19 "Inter-| Receive " |
| 20 " | Transmit\n"; |
| 21 static const char *kProcNetDevExpectedLine2 = |
| 22 " face |bytes packets errs drop fifo frame " |
| 23 "compressed multicast|bytes packets errs " |
| 24 "drop fifo colls carrier compressed\n"; |
| 25 static const char *kProcNetDevLineFormat = |
| 26 "%s %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu\n"; |
| 27 static const int kProcNetDevLineFormatNumElements = 17; |
| 28 |
| 29 // ReadStats timer interval |
| 30 static const guint kReadStatsIntervalSeconds = 15; |
| 31 |
| 32 ProcfsByteCounter::ProcfsByteCounter(const std::string& interface) |
| 33 : interface_(interface), proc_net_dev_fp_(NULL), read_stats_source_id_(0), |
| 34 counter_rx_bytes_(0), counter_tx_bytes_(0), baseline_stats_(), |
| 35 baseline_initialized_(false), delegate_(NULL) { |
| 36 DCHECK(!interface_.empty()); |
| 37 // initialize our counter state |
| 38 if (!ReadStats()) { |
| 39 LOG(WARNING) << interface_ << ": ctor: ReadStats failed"; |
| 40 // fall through so that we'll try again later |
| 41 } |
| 42 // schedule periodic ReadStats() calls to update counter state in the future |
| 43 read_stats_source_id_ = g_timeout_add_seconds(kReadStatsIntervalSeconds, |
| 44 StaticReadStatsCallback, this); |
| 45 if (read_stats_source_id_ == 0) { |
| 46 LOG(ERROR) << interface_ << ": ctor: g_idle_add failed"; |
| 47 return; |
| 48 } |
| 49 } |
| 50 |
| 51 ProcfsByteCounter::~ProcfsByteCounter() { |
| 52 DCHECK(proc_net_dev_fp_ == NULL); |
| 53 if (read_stats_source_id_ != 0 && !g_source_remove(read_stats_source_id_)) { |
| 54 DLOG(WARNING) << interface_ << ": dtor: g_source_remove_failed"; |
| 55 } |
| 56 } |
| 57 |
| 58 const std::string& ProcfsByteCounter::GetInterface() const { |
| 59 return interface_; |
| 60 } |
| 61 |
| 62 uint64 ProcfsByteCounter::GetRxBytes() const { |
| 63 return counter_rx_bytes_; |
| 64 } |
| 65 |
| 66 uint64 ProcfsByteCounter::GetTxBytes() const { |
| 67 return counter_tx_bytes_; |
| 68 } |
| 69 |
| 70 void ProcfsByteCounter::SetDelegate(ByteCounterDelegate *delegate) { |
| 71 DLOG(INFO) << interface_ << ": SetDelegate"; |
| 72 delegate_ = delegate; |
| 73 } |
| 74 |
| 75 // private methods |
| 76 |
| 77 bool ProcfsByteCounter::OpenProcNetDev() { |
| 78 DCHECK(proc_net_dev_fp_ == NULL); |
| 79 if ((proc_net_dev_fp_ = fopen(kProcNetDevPath, "r")) == NULL) { |
| 80 PLOG(ERROR) << interface_ << ": OpenProcNetDev: couldn't open " |
| 81 << kProcNetDevPath; |
| 82 return false; |
| 83 } |
| 84 return true; |
| 85 } |
| 86 |
| 87 void ProcfsByteCounter::CloseProcNetDev() { |
| 88 if (proc_net_dev_fp_ != NULL) { |
| 89 if (fclose(proc_net_dev_fp_)) { |
| 90 PLOG(WARNING) << interface_ << ": CloseProcNetDev: fclose failed"; |
| 91 } |
| 92 proc_net_dev_fp_ = NULL; |
| 93 } |
| 94 DCHECK(proc_net_dev_fp_ == NULL); |
| 95 } |
| 96 |
| 97 // static |
| 98 gboolean ProcfsByteCounter::StaticReadStatsCallback(gpointer data) { |
| 99 ProcfsByteCounter *counter = reinterpret_cast<ProcfsByteCounter*>(data); |
| 100 CHECK_NOTNULL(counter); |
| 101 if (!counter->ReadStats()) { |
| 102 DLOG(WARNING) << counter->GetInterface() << ": StaticReadStatsCallback: " |
| 103 << "ReadStats failed"; |
| 104 } |
| 105 return TRUE; // we want to be called again later |
| 106 } |
| 107 |
| 108 bool ProcfsByteCounter::ReadStats() { |
| 109 // we open and close the virtual file each time through, because we get stale |
| 110 // data if we keep the file open and rewind/read repeatedly |
| 111 if (!OpenProcNetDev()) { |
| 112 LOG(ERROR) << interface_ << ": ReadStats: OpenProcDevNet failed"; |
| 113 return false; |
| 114 } |
| 115 char line_buffer[kLineBufferSize]; |
| 116 for (int line_number = 1; // use 1-based counting to make log msgs sensible |
| 117 fgets(line_buffer, sizeof(line_buffer), proc_net_dev_fp_) != NULL; |
| 118 ++line_number) { |
| 119 // the first two lines contain human-friendly column headers |
| 120 // skip them, while ensuring that they are as we expect |
| 121 // this provides some confidence that the format hasn't changed on us |
| 122 if (line_number == 1 || line_number == 2) { |
| 123 if (strncmp(line_buffer, (line_number == 1) ? kProcNetDevExpectedLine1 : |
| 124 kProcNetDevExpectedLine2, sizeof(line_buffer))) { |
| 125 LOG(ERROR) << interface_ << ": ReadStats: line " << line_number |
| 126 << " is not as we expect"; |
| 127 CloseProcNetDev(); |
| 128 return false; |
| 129 } |
| 130 continue; |
| 131 } |
| 132 // each subsequent line contains the name and stats for a network interface |
| 133 // parse it, reading stats, and see if the name matches |interface_| |
| 134 // |
| 135 // replace first colon in string (immediately after device name) with a |
| 136 // space so that sscanf greedy string parsing doesn't cause the colon and |
| 137 // possibly the rx byte count following it to all be consumed as part of the |
| 138 // device name. |
| 139 char *colon_ptr = strchr(line_buffer, ':'); |
| 140 if (colon_ptr == NULL) { |
| 141 LOG(ERROR) << interface_ << ": ReadStats: line " << line_number |
| 142 << ": strchr failed to find colon"; |
| 143 CloseProcNetDev(); |
| 144 return false; |
| 145 } |
| 146 *colon_ptr = ' '; |
| 147 char device_name[IFNAMSIZ]; |
| 148 struct net_device_stats stats; |
| 149 memset(&stats, 0, sizeof(stats)); |
| 150 // cpplint tells us that sscanf, while not prohibited, is sketchy |
| 151 // we'll risk it since this data is from the kernel proc fs and we provide |
| 152 // the format string |
| 153 // |
| 154 // note that some fields of net_device_stats are combined when producing the |
| 155 // contents of /proc/net/dev |
| 156 // we assign these combined values to the field named in the column header |
| 157 int result = sscanf(line_buffer, kProcNetDevLineFormat, // NOLINT |
| 158 device_name, &stats.rx_bytes, &stats.rx_packets, |
| 159 &stats.rx_errors, &stats.rx_dropped, |
| 160 &stats.rx_fifo_errors, &stats.rx_frame_errors, |
| 161 &stats.rx_compressed, &stats.multicast, &stats.tx_bytes, |
| 162 &stats.tx_packets, &stats.tx_errors, &stats.tx_dropped, |
| 163 &stats.tx_fifo_errors, &stats.collisions, |
| 164 &stats.tx_carrier_errors, &stats.tx_compressed); |
| 165 if (result == EOF && ferror(proc_net_dev_fp_)) { |
| 166 PLOG(ERROR) << interface_ << ": ReadStats: line " << line_number |
| 167 << ": sscanf failed"; |
| 168 CloseProcNetDev(); |
| 169 return false; |
| 170 } |
| 171 if (result != kProcNetDevLineFormatNumElements) { |
| 172 LOG(ERROR) << interface_ << ": ReadStats: line " << line_number |
| 173 << ": sscanf matched " << ((result == EOF) ? 0 : result) |
| 174 << " elements (" << kProcNetDevLineFormatNumElements << " expected)"; |
| 175 CloseProcNetDev(); |
| 176 return false; |
| 177 } |
| 178 if (device_name != interface_) { |
| 179 continue; |
| 180 } |
| 181 // we found a match |
| 182 // update our counter based on new stats |
| 183 OnStatsUpdate(stats); |
| 184 CloseProcNetDev(); |
| 185 return true; |
| 186 } |
| 187 LOG(ERROR) << interface_ << ": ReadStats: no matching device"; |
| 188 CloseProcNetDev(); |
| 189 return false; |
| 190 } |
| 191 |
| 192 void ProcfsByteCounter::OnStatsUpdate(const struct net_device_stats& stats) { |
| 193 DLOG(INFO) << interface_ << ": OnStatsUpdate: stats: rx_bytes = " |
| 194 << stats.rx_bytes << ", tx_bytes = " << stats.tx_bytes; |
| 195 if (baseline_initialized_) { |
| 196 DLOG(INFO) << interface_ << ": OnStatsUpdate: baseline: rx_bytes = " |
| 197 << baseline_stats_.rx_bytes << ", tx_bytes = " |
| 198 << baseline_stats_.tx_bytes; |
| 199 uint64 old_counter_rx_bytes = counter_rx_bytes_; |
| 200 uint64 old_counter_tx_bytes = counter_tx_bytes_; |
| 201 DLOG(INFO) << interface_ << ": OnStatsUpdate: old counter: rx_bytes = " |
| 202 << old_counter_rx_bytes << ", tx_bytes = " << old_counter_tx_bytes; |
| 203 // these seemingly naive computations handle wraparound due to the modular |
| 204 // nature of unsigned subtraction |
| 205 counter_rx_bytes_ += stats.rx_bytes - baseline_stats_.rx_bytes; |
| 206 counter_tx_bytes_ += stats.tx_bytes - baseline_stats_.tx_bytes; |
| 207 DLOG(INFO) << interface_ << ": OnStatsUpdate: new counter: rx_bytes = " |
| 208 << counter_rx_bytes_ << ", tx_bytes = " << counter_tx_bytes_; |
| 209 bool counter_changed = counter_rx_bytes_ != old_counter_rx_bytes || |
| 210 counter_tx_bytes_ != old_counter_tx_bytes; |
| 211 if (delegate_ != NULL && counter_changed) { |
| 212 delegate_->OnByteCounterUpdate(this, counter_rx_bytes_, |
| 213 counter_tx_bytes_); |
| 214 } |
| 215 } else { |
| 216 baseline_initialized_ = true; |
| 217 } |
| 218 // We update the baseline on each new sample so that we aren't confused about |
| 219 // how many times the device counter has wrapped around since we've started. |
| 220 // This works as long as the sample interval is short enough to prevent wrap |
| 221 // around between consecutive samples. |
| 222 baseline_stats_ = stats; |
| 223 } |
| 224 |
| 225 } // namespace cashew |
| OLD | NEW |