1/*
2 *  Copyright 2008 The WebRTC Project Authors. All rights reserved.
3 *
4 *  Use of this source code is governed by a BSD-style license
5 *  that can be found in the LICENSE file in the root of the source
6 *  tree. An additional intellectual property rights grant can be found
7 *  in the file PATENTS.  All contributing project authors may
8 *  be found in the AUTHORS file in the root of the source tree.
9 */
10
11#if defined(WEBRTC_LINUX)
12#include "webrtc/base/linux.h"
13
14#include <ctype.h>
15
16#include <errno.h>
17#include <sys/utsname.h>
18#include <sys/wait.h>
19
20#include <cstdio>
21#include <set>
22
23#include "webrtc/base/stringencode.h"
24
25namespace rtc {
26
27static const char kCpuInfoFile[] = "/proc/cpuinfo";
28static const char kCpuMaxFreqFile[] =
29    "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq";
30
31ProcCpuInfo::ProcCpuInfo() {
32}
33
34ProcCpuInfo::~ProcCpuInfo() {
35}
36
37bool ProcCpuInfo::LoadFromSystem() {
38  ConfigParser procfs;
39  if (!procfs.Open(kCpuInfoFile)) {
40    return false;
41  }
42  return procfs.Parse(&sections_);
43};
44
45bool ProcCpuInfo::GetSectionCount(size_t* count) {
46  if (sections_.empty()) {
47    return false;
48  }
49  if (count) {
50    *count = sections_.size();
51  }
52  return true;
53}
54
55bool ProcCpuInfo::GetNumCpus(int* num) {
56  if (sections_.empty()) {
57    return false;
58  }
59  int total_cpus = 0;
60#if defined(__arm__)
61  // Count the number of blocks that have a "processor" key defined. On ARM,
62  // there may be extra blocks of information that aren't per-processor.
63  size_t section_count = sections_.size();
64  for (size_t i = 0; i < section_count; ++i) {
65    int processor_id;
66    if (GetSectionIntValue(i, "processor", &processor_id)) {
67      ++total_cpus;
68    }
69  }
70  // Single core ARM systems don't include "processor" keys at all, so return
71  // that we have a single core if we didn't find any explicitly above.
72  if (total_cpus == 0) {
73    total_cpus = 1;
74  }
75#else
76  // On X86, there is exactly one info section per processor.
77  total_cpus = static_cast<int>(sections_.size());
78#endif
79  if (num) {
80    *num = total_cpus;
81  }
82  return true;
83}
84
85bool ProcCpuInfo::GetNumPhysicalCpus(int* num) {
86  if (sections_.empty()) {
87    return false;
88  }
89  // TODO: /proc/cpuinfo only reports cores that are currently
90  // _online_, so this may underreport the number of physical cores.
91#if defined(__arm__)
92  // ARM (currently) has no hyperthreading, so just return the same value
93  // as GetNumCpus.
94  return GetNumCpus(num);
95#else
96  int total_cores = 0;
97  std::set<int> physical_ids;
98  size_t section_count = sections_.size();
99  for (size_t i = 0; i < section_count; ++i) {
100    int physical_id;
101    int cores;
102    // Count the cores for the physical id only if we have not counted the id.
103    if (GetSectionIntValue(i, "physical id", &physical_id) &&
104        GetSectionIntValue(i, "cpu cores", &cores) &&
105        physical_ids.find(physical_id) == physical_ids.end()) {
106      physical_ids.insert(physical_id);
107      total_cores += cores;
108    }
109  }
110
111  if (num) {
112    *num = total_cores;
113  }
114  return true;
115#endif
116}
117
118bool ProcCpuInfo::GetCpuFamily(int* id) {
119  int cpu_family = 0;
120
121#if defined(__arm__)
122  // On some ARM platforms, there is no 'cpu family' in '/proc/cpuinfo'. But
123  // there is 'CPU Architecture' which can be used as 'cpu family'.
124  // See http://en.wikipedia.org/wiki/ARM_architecture for a good list of
125  // ARM cpu families, architectures, and their mappings.
126  // There may be multiple sessions that aren't per-processor. We need to scan
127  // through each session until we find the first 'CPU architecture'.
128  size_t section_count = sections_.size();
129  for (size_t i = 0; i < section_count; ++i) {
130    if (GetSectionIntValue(i, "CPU architecture", &cpu_family)) {
131      // We returns the first one (if there are multiple entries).
132      break;
133    };
134  }
135#else
136  GetSectionIntValue(0, "cpu family", &cpu_family);
137#endif
138  if (id) {
139    *id = cpu_family;
140  }
141  return true;
142}
143
144bool ProcCpuInfo::GetSectionStringValue(size_t section_num,
145                                        const std::string& key,
146                                        std::string* result) {
147  if (section_num >= sections_.size()) {
148    return false;
149  }
150  ConfigParser::SimpleMap::iterator iter = sections_[section_num].find(key);
151  if (iter == sections_[section_num].end()) {
152    return false;
153  }
154  *result = iter->second;
155  return true;
156}
157
158bool ProcCpuInfo::GetSectionIntValue(size_t section_num,
159                                     const std::string& key,
160                                     int* result) {
161  if (section_num >= sections_.size()) {
162    return false;
163  }
164  ConfigParser::SimpleMap::iterator iter = sections_[section_num].find(key);
165  if (iter == sections_[section_num].end()) {
166    return false;
167  }
168  return FromString(iter->second, result);
169}
170
171ConfigParser::ConfigParser() {}
172
173ConfigParser::~ConfigParser() {}
174
175bool ConfigParser::Open(const std::string& filename) {
176  FileStream* fs = new FileStream();
177  if (!fs->Open(filename, "r", NULL)) {
178    return false;
179  }
180  instream_.reset(fs);
181  return true;
182}
183
184void ConfigParser::Attach(StreamInterface* stream) {
185  instream_.reset(stream);
186}
187
188bool ConfigParser::Parse(MapVector* key_val_pairs) {
189  // Parses the file and places the found key-value pairs into key_val_pairs.
190  SimpleMap section;
191  while (ParseSection(&section)) {
192    key_val_pairs->push_back(section);
193    section.clear();
194  }
195  return (!key_val_pairs->empty());
196}
197
198bool ConfigParser::ParseSection(SimpleMap* key_val_pair) {
199  // Parses the next section in the filestream and places the found key-value
200  // pairs into key_val_pair.
201  std::string key, value;
202  while (ParseLine(&key, &value)) {
203    (*key_val_pair)[key] = value;
204  }
205  return (!key_val_pair->empty());
206}
207
208bool ConfigParser::ParseLine(std::string* key, std::string* value) {
209  // Parses the next line in the filestream and places the found key-value
210  // pair into key and val.
211  std::string line;
212  if ((instream_->ReadLine(&line)) == SR_EOS) {
213    return false;
214  }
215  std::vector<std::string> tokens;
216  if (2 != split(line, ':', &tokens)) {
217    return false;
218  }
219  // Removes whitespace at the end of Key name
220  size_t pos = tokens[0].length() - 1;
221  while ((pos > 0) && isspace(tokens[0][pos])) {
222    pos--;
223  }
224  tokens[0].erase(pos + 1);
225  // Removes whitespace at the start of value
226  pos = 0;
227  while (pos < tokens[1].length() && isspace(tokens[1][pos])) {
228    pos++;
229  }
230  tokens[1].erase(0, pos);
231  *key = tokens[0];
232  *value = tokens[1];
233  return true;
234}
235
236#if !defined(WEBRTC_CHROMIUM_BUILD)
237static bool ExpectLineFromStream(FileStream* stream,
238                                 std::string* out) {
239  StreamResult res = stream->ReadLine(out);
240  if (res != SR_SUCCESS) {
241    if (res != SR_EOS) {
242      LOG(LS_ERROR) << "Error when reading from stream";
243    } else {
244      LOG(LS_ERROR) << "Incorrect number of lines in stream";
245    }
246    return false;
247  }
248  return true;
249}
250
251static void ExpectEofFromStream(FileStream* stream) {
252  std::string unused;
253  StreamResult res = stream->ReadLine(&unused);
254  if (res == SR_SUCCESS) {
255    LOG(LS_WARNING) << "Ignoring unexpected extra lines from stream";
256  } else if (res != SR_EOS) {
257    LOG(LS_WARNING) << "Error when checking for extra lines from stream";
258  }
259}
260
261// For caching the lsb_release output (reading it invokes a sub-process and
262// hence is somewhat expensive).
263static std::string lsb_release_string;
264static CriticalSection lsb_release_string_critsec;
265
266std::string ReadLinuxLsbRelease() {
267  CritScope cs(&lsb_release_string_critsec);
268  if (!lsb_release_string.empty()) {
269    // Have cached result from previous call.
270    return lsb_release_string;
271  }
272  // No cached result. Run lsb_release and parse output.
273  POpenStream lsb_release_output;
274  if (!lsb_release_output.Open("lsb_release -idrcs", "r", NULL)) {
275    LOG_ERR(LS_ERROR) << "Can't run lsb_release";
276    return lsb_release_string;  // empty
277  }
278  // Read in the command's output and build the string.
279  std::ostringstream sstr;
280  std::string line;
281  int wait_status;
282
283  if (!ExpectLineFromStream(&lsb_release_output, &line)) {
284    return lsb_release_string;  // empty
285  }
286  sstr << "DISTRIB_ID=" << line;
287
288  if (!ExpectLineFromStream(&lsb_release_output, &line)) {
289    return lsb_release_string;  // empty
290  }
291  sstr << " DISTRIB_DESCRIPTION=\"" << line << '"';
292
293  if (!ExpectLineFromStream(&lsb_release_output, &line)) {
294    return lsb_release_string;  // empty
295  }
296  sstr << " DISTRIB_RELEASE=" << line;
297
298  if (!ExpectLineFromStream(&lsb_release_output, &line)) {
299    return lsb_release_string;  // empty
300  }
301  sstr << " DISTRIB_CODENAME=" << line;
302
303  // Should not be anything left.
304  ExpectEofFromStream(&lsb_release_output);
305
306  lsb_release_output.Close();
307  wait_status = lsb_release_output.GetWaitStatus();
308  if (wait_status == -1 ||
309      !WIFEXITED(wait_status) ||
310      WEXITSTATUS(wait_status) != 0) {
311    LOG(LS_WARNING) << "Unexpected exit status from lsb_release";
312  }
313
314  lsb_release_string = sstr.str();
315
316  return lsb_release_string;
317}
318#endif
319
320std::string ReadLinuxUname() {
321  struct utsname buf;
322  if (uname(&buf) < 0) {
323    LOG_ERR(LS_ERROR) << "Can't call uname()";
324    return std::string();
325  }
326  std::ostringstream sstr;
327  sstr << buf.sysname << " "
328       << buf.release << " "
329       << buf.version << " "
330       << buf.machine;
331  return sstr.str();
332}
333
334int ReadCpuMaxFreq() {
335  FileStream fs;
336  std::string str;
337  int freq = -1;
338  if (!fs.Open(kCpuMaxFreqFile, "r", NULL) ||
339      SR_SUCCESS != fs.ReadLine(&str) ||
340      !FromString(str, &freq)) {
341    return -1;
342  }
343  return freq;
344}
345
346}  // namespace rtc
347
348#endif  // defined(WEBRTC_LINUX)
349