blob: b30212b62c356b9a0abfc75011f66b139a763285 [file] [log] [blame]
[email protected]4306df762012-04-20 18:58:571// Copyright (c) 2012 The Chromium Authors. All rights reserved.
[email protected]d43970a72011-07-10 06:24:522// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/chromeos/system/syslogs_provider.h"
6
[email protected]40c21bd2013-02-02 02:07:457#include "ash/shell.h"
[email protected]51d02152011-12-24 05:55:228#include "base/bind.h"
9#include "base/bind_helpers.h"
[email protected]d43970a72011-07-10 06:24:5210#include "base/command_line.h"
[email protected]0defb7b2012-12-05 23:27:0711#include "base/compiler_specific.h"
[email protected]d43970a72011-07-10 06:24:5212#include "base/file_util.h"
[email protected]57999812013-02-24 05:40:5213#include "base/files/file_path.h"
[email protected]d43970a72011-07-10 06:24:5214#include "base/logging.h"
15#include "base/memory/scoped_ptr.h"
16#include "base/memory/singleton.h"
[email protected]76ae8a62013-05-10 05:34:2217#include "base/message_loop/message_loop_proxy.h"
[email protected]5c073322013-06-11 08:03:3018#include "base/strings/string_util.h"
[email protected]0defb7b2012-12-05 23:27:0719#include "base/task_runner.h"
20#include "base/threading/sequenced_worker_pool.h"
[email protected]40c21bd2013-02-02 02:07:4521#include "chrome/browser/feedback/feedback_util.h"
[email protected]9bb480ee2011-08-03 21:41:1622#include "chrome/browser/memory_details.h"
[email protected]d43970a72011-07-10 06:24:5223#include "chrome/common/chrome_switches.h"
[email protected]669b00102012-11-21 01:38:0124#include "chromeos/network/network_event_log.h"
[email protected]c38831a12011-10-28 12:44:4925#include "content/public/browser/browser_thread.h"
[email protected]669b00102012-11-21 01:38:0126#include "dbus/dbus_statistics.h"
[email protected]d43970a72011-07-10 06:24:5227
[email protected]631bb742011-11-02 11:29:3928using content::BrowserThread;
29
[email protected]d43970a72011-07-10 06:24:5230namespace chromeos {
31namespace system {
[email protected]52c6db32012-11-22 03:19:2532
33const size_t kFeedbackMaxLength = 4 * 1024;
34const size_t kFeedbackMaxLineCount = 40;
35
[email protected]d43970a72011-07-10 06:24:5236namespace {
37
38const char kSysLogsScript[] =
39 "/usr/share/userfeedback/scripts/sysinfo_script_runner";
40const char kBzip2Command[] =
41 "/bin/bzip2";
42const char kMultilineQuote[] = "\"\"\"";
43const char kNewLineChars[] = "\r\n";
44const char kInvalidLogEntry[] = "<invalid characters in log entry>";
45const char kEmptyLogEntry[] = "<no value>";
46
47const char kContextFeedback[] = "feedback";
48const char kContextSysInfo[] = "sysinfo";
49const char kContextNetwork[] = "network";
50
51// Reads a key from the input string erasing the read values + delimiters read
52// from the initial string
53std::string ReadKey(std::string* data) {
[email protected]30066392014-01-30 05:38:5354 std::string key;
[email protected]d43970a72011-07-10 06:24:5255 size_t equal_sign = data->find("=");
56 if (equal_sign == std::string::npos)
[email protected]d43970a72011-07-10 06:24:5257 return key;
[email protected]30066392014-01-30 05:38:5358 key = data->substr(0, equal_sign);
59 data->erase(0, equal_sign);
60 if (data->empty())
61 return key;
62 // erase the equal to sign also
63 data->erase(0, 1);
64 return key;
[email protected]d43970a72011-07-10 06:24:5265}
66
67// Reads a value from the input string; erasing the read values from
68// the initial string; detects if the value is multiline and reads
69// accordingly
70std::string ReadValue(std::string* data) {
71 // Trim the leading spaces and tabs. In order to use a multi-line
72 // value, you have to place the multi-line quote on the same line as
73 // the equal sign.
74 //
75 // Why not use TrimWhitespace? Consider the following input:
76 //
77 // KEY1=
78 // KEY2=VALUE
79 //
80 // If we use TrimWhitespace, we will incorrectly trim the new line
81 // and assume that KEY1's value is "KEY2=VALUE" rather than empty.
[email protected]466c9862013-12-03 22:05:2882 base::TrimString(*data, " \t", data);
[email protected]d43970a72011-07-10 06:24:5283
84 // If multiline value
85 if (StartsWithASCII(*data, std::string(kMultilineQuote), false)) {
86 data->erase(0, strlen(kMultilineQuote));
87 size_t next_multi = data->find(kMultilineQuote);
88 if (next_multi == std::string::npos) {
89 // Error condition, clear data to stop further processing
90 data->erase();
91 return std::string();
92 }
93 std::string value = data->substr(0, next_multi);
94 data->erase(0, next_multi + 3);
95 return value;
[email protected]30066392014-01-30 05:38:5396 } else {
97 // single line value
[email protected]d43970a72011-07-10 06:24:5298 size_t endl_pos = data->find_first_of(kNewLineChars);
99 // if we don't find a new line, we just return the rest of the data
100 std::string value = data->substr(0, endl_pos);
101 data->erase(0, endl_pos);
102 return value;
103 }
104}
105
106// Returns a map of system log keys and values.
107//
108// Parameters:
109// temp_filename: This is an out parameter that holds the name of a file in
110// Reads a value from the input string; erasing the read values from
111// the initial string; detects if the value is multiline and reads
112// accordingly
113// /tmp that contains the system logs in a KEY=VALUE format.
114// If this parameter is NULL, system logs are not retained on
115// the filesystem after this call completes.
116// context: This is an in parameter specifying what context should be
117// passed to the syslog collection script; currently valid
118// values are "sysinfo" or "feedback"; in case of an invalid
119// value, the script will currently default to "sysinfo"
120
[email protected]650b2d52013-02-10 03:41:45121LogDictionaryType* GetSystemLogs(base::FilePath* zip_file_name,
[email protected]d43970a72011-07-10 06:24:52122 const std::string& context) {
123 // Create the temp file, logs will go here
[email protected]650b2d52013-02-10 03:41:45124 base::FilePath temp_filename;
[email protected]d43970a72011-07-10 06:24:52125
[email protected]03d9afc02013-12-03 17:55:52126 if (!base::CreateTemporaryFile(&temp_filename))
[email protected]d43970a72011-07-10 06:24:52127 return NULL;
128
129 std::string cmd = std::string(kSysLogsScript) + " " + context + " >> " +
130 temp_filename.value();
131
132 // Ignore the return value - if the script execution didn't work
133 // stderr won't go into the output file anyway.
134 if (::system(cmd.c_str()) == -1)
135 LOG(WARNING) << "Command " << cmd << " failed to run";
136
137 // Compress the logs file if requested.
138 if (zip_file_name) {
139 cmd = std::string(kBzip2Command) + " -c " + temp_filename.value() + " > " +
140 zip_file_name->value();
141 if (::system(cmd.c_str()) == -1)
142 LOG(WARNING) << "Command " << cmd << " failed to run";
143 }
144 // Read logs from the temp file
145 std::string data;
[email protected]82f84b92013-08-30 18:23:50146 bool read_success = base::ReadFileToString(temp_filename, &data);
[email protected]d43970a72011-07-10 06:24:52147 // if we were using an internal temp file, the user does not need the
148 // logs to stay past the ReadFile call - delete the file
[email protected]dd3aa792013-07-16 19:10:23149 base::DeleteFile(temp_filename, false);
[email protected]d43970a72011-07-10 06:24:52150
151 if (!read_success)
152 return NULL;
153
154 // Parse the return data into a dictionary
155 LogDictionaryType* logs = new LogDictionaryType();
156 while (data.length() > 0) {
157 std::string key = ReadKey(&data);
[email protected]8af69c6c2014-03-03 19:05:31158 base::TrimWhitespaceASCII(key, base::TRIM_ALL, &key);
[email protected]d43970a72011-07-10 06:24:52159 if (!key.empty()) {
160 std::string value = ReadValue(&data);
[email protected]bd6fc2f2014-03-17 23:55:43161 if (IsStringUTF8(value)) {
[email protected]8af69c6c2014-03-03 19:05:31162 base::TrimWhitespaceASCII(value, base::TRIM_ALL, &value);
[email protected]d43970a72011-07-10 06:24:52163 if (value.empty())
164 (*logs)[key] = kEmptyLogEntry;
165 else
166 (*logs)[key] = value;
167 } else {
168 LOG(WARNING) << "Invalid characters in system log entry: " << key;
169 (*logs)[key] = kInvalidLogEntry;
170 }
171 } else {
172 // no more keys, we're done
173 break;
174 }
175 }
176
177 return logs;
178}
179
180} // namespace
181
182class SyslogsProviderImpl : public SyslogsProvider {
183 public:
184 // SyslogsProvider implementation:
[email protected]e95b717f2014-02-06 13:47:13185 virtual base::CancelableTaskTracker::TaskId RequestSyslogs(
[email protected]d43970a72011-07-10 06:24:52186 bool compress_logs,
187 SyslogsContext context,
[email protected]0defb7b2012-12-05 23:27:07188 const ReadCompleteCallback& callback,
[email protected]e95b717f2014-02-06 13:47:13189 base::CancelableTaskTracker* tracker) OVERRIDE;
[email protected]d43970a72011-07-10 06:24:52190
191 static SyslogsProviderImpl* GetInstance();
192
193 private:
194 friend struct DefaultSingletonTraits<SyslogsProviderImpl>;
195
[email protected]0defb7b2012-12-05 23:27:07196 // Reads system logs, compresses content if requested.
197 // Called from blocking pool thread.
198 void ReadSyslogs(
[email protected]e95b717f2014-02-06 13:47:13199 const base::CancelableTaskTracker::IsCanceledCallback& is_canceled,
[email protected]0defb7b2012-12-05 23:27:07200 bool compress_logs,
201 SyslogsContext context,
202 const ReadCompleteCallback& callback);
203
204 // Loads compressed logs and writes into |zip_content|.
[email protected]650b2d52013-02-10 03:41:45205 void LoadCompressedLogs(const base::FilePath& zip_file,
[email protected]0defb7b2012-12-05 23:27:07206 std::string* zip_content);
207
[email protected]d43970a72011-07-10 06:24:52208 SyslogsProviderImpl();
209
210 // Gets syslogs context string from the enum value.
211 const char* GetSyslogsContextString(SyslogsContext context);
212
[email protected]0defb7b2012-12-05 23:27:07213 // If not canceled, run callback on originating thread (the thread on which
214 // ReadSyslogs was run).
215 static void RunCallbackIfNotCanceled(
[email protected]e95b717f2014-02-06 13:47:13216 const base::CancelableTaskTracker::IsCanceledCallback& is_canceled,
[email protected]0defb7b2012-12-05 23:27:07217 base::TaskRunner* origin_runner,
218 const ReadCompleteCallback& callback,
219 LogDictionaryType* logs,
220 std::string* zip_content);
221
[email protected]d43970a72011-07-10 06:24:52222 DISALLOW_COPY_AND_ASSIGN(SyslogsProviderImpl);
223};
224
225SyslogsProviderImpl::SyslogsProviderImpl() {
226}
227
[email protected]e95b717f2014-02-06 13:47:13228base::CancelableTaskTracker::TaskId SyslogsProviderImpl::RequestSyslogs(
[email protected]d43970a72011-07-10 06:24:52229 bool compress_logs,
230 SyslogsContext context,
[email protected]0defb7b2012-12-05 23:27:07231 const ReadCompleteCallback& callback,
[email protected]e95b717f2014-02-06 13:47:13232 base::CancelableTaskTracker* tracker) {
233 base::CancelableTaskTracker::IsCanceledCallback is_canceled;
234 base::CancelableTaskTracker::TaskId id =
235 tracker->NewTrackedTaskId(&is_canceled);
[email protected]d43970a72011-07-10 06:24:52236
[email protected]0defb7b2012-12-05 23:27:07237 ReadCompleteCallback callback_runner =
238 base::Bind(&SyslogsProviderImpl::RunCallbackIfNotCanceled,
239 is_canceled, base::MessageLoopProxy::current(), callback);
240
241 // Schedule a task which will run the callback later when complete.
242 BrowserThread::PostBlockingPoolTask(
243 FROM_HERE,
[email protected]51d02152011-12-24 05:55:22244 base::Bind(&SyslogsProviderImpl::ReadSyslogs, base::Unretained(this),
[email protected]0defb7b2012-12-05 23:27:07245 is_canceled, compress_logs, context, callback_runner));
246 return id;
[email protected]d43970a72011-07-10 06:24:52247}
248
[email protected]9bb480ee2011-08-03 21:41:16249// Derived class from memoryDetails converts the results into a single string
250// and adds a "mem_usage" entry to the logs, then forwards the result.
251// Format of entry is (one process per line, reverse-sorted by size):
252// Tab [Title1|Title2]: 50 MB
253// Browser: 30 MB
254// Tab [Title]: 20 MB
255// Extension [Title]: 10 MB
256// ...
257class SyslogsMemoryHandler : public MemoryDetails {
258 public:
259 typedef SyslogsProvider::ReadCompleteCallback ReadCompleteCallback;
260
261 // |logs| is modified (see comment above) and passed to |request|.
262 // |zip_content| is passed to |request|.
[email protected]0defb7b2012-12-05 23:27:07263 SyslogsMemoryHandler(const ReadCompleteCallback& callback,
264 LogDictionaryType* logs,
265 std::string* zip_content);
[email protected]9bb480ee2011-08-03 21:41:16266
[email protected]0defb7b2012-12-05 23:27:07267 virtual void OnDetailsAvailable() OVERRIDE;
[email protected]9bb480ee2011-08-03 21:41:16268
269 private:
[email protected]0defb7b2012-12-05 23:27:07270 virtual ~SyslogsMemoryHandler();
[email protected]9bb480ee2011-08-03 21:41:16271
[email protected]0defb7b2012-12-05 23:27:07272 ReadCompleteCallback callback_;
273
[email protected]9bb480ee2011-08-03 21:41:16274 LogDictionaryType* logs_;
275 std::string* zip_content_;
[email protected]0defb7b2012-12-05 23:27:07276
[email protected]9bb480ee2011-08-03 21:41:16277 DISALLOW_COPY_AND_ASSIGN(SyslogsMemoryHandler);
278};
279
[email protected]0defb7b2012-12-05 23:27:07280SyslogsMemoryHandler::SyslogsMemoryHandler(
281 const ReadCompleteCallback& callback,
282 LogDictionaryType* logs,
283 std::string* zip_content)
284 : callback_(callback),
285 logs_(logs),
286 zip_content_(zip_content) {
287 DCHECK(!callback_.is_null());
288}
[email protected]d43970a72011-07-10 06:24:52289
[email protected]0defb7b2012-12-05 23:27:07290void SyslogsMemoryHandler::OnDetailsAvailable() {
291 (*logs_)["mem_usage"] = ToLogString();
292 callback_.Run(logs_, zip_content_);
293 }
294
295SyslogsMemoryHandler::~SyslogsMemoryHandler() {}
296
297// Called from blocking pool thread.
298void SyslogsProviderImpl::ReadSyslogs(
[email protected]e95b717f2014-02-06 13:47:13299 const base::CancelableTaskTracker::IsCanceledCallback& is_canceled,
[email protected]0defb7b2012-12-05 23:27:07300 bool compress_logs,
301 SyslogsContext context,
302 const ReadCompleteCallback& callback) {
303 DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
304
305 if (is_canceled.Run())
[email protected]d43970a72011-07-10 06:24:52306 return;
307
[email protected]d43970a72011-07-10 06:24:52308 // Create temp file.
[email protected]650b2d52013-02-10 03:41:45309 base::FilePath zip_file;
[email protected]03d9afc02013-12-03 17:55:52310 if (compress_logs && !base::CreateTemporaryFile(&zip_file)) {
[email protected]d43970a72011-07-10 06:24:52311 LOG(ERROR) << "Cannot create temp file";
312 compress_logs = false;
313 }
314
315 LogDictionaryType* logs = NULL;
316 logs = GetSystemLogs(
317 compress_logs ? &zip_file : NULL,
318 GetSyslogsContextString(context));
319
320 std::string* zip_content = NULL;
321 if (compress_logs) {
322 // Load compressed logs.
323 zip_content = new std::string();
324 LoadCompressedLogs(zip_file, zip_content);
[email protected]dd3aa792013-07-16 19:10:23325 base::DeleteFile(zip_file, false);
[email protected]d43970a72011-07-10 06:24:52326 }
327
[email protected]52c6db32012-11-22 03:19:25328 // Include dbus statistics summary
329 (*logs)["dbus"] = dbus::statistics::GetAsString(
330 dbus::statistics::SHOW_INTERFACE,
331 dbus::statistics::FORMAT_ALL);
332
[email protected]669b00102012-11-21 01:38:01333 // Include recent network log events
[email protected]5092ea872013-05-16 21:56:08334 (*logs)["network_event_log"] = network_event_log::GetAsString(
335 network_event_log::OLDEST_FIRST,
336 "time,file,desc",
337 network_event_log::kDefaultLogLevel,
338 system::kFeedbackMaxLineCount);
[email protected]669b00102012-11-21 01:38:01339
[email protected]9bb480ee2011-08-03 21:41:16340 // SyslogsMemoryHandler will clean itself up.
341 // SyslogsMemoryHandler::OnDetailsAvailable() will modify |logs| and call
342 // request->ForwardResult(logs, zip_content).
343 scoped_refptr<SyslogsMemoryHandler>
[email protected]0defb7b2012-12-05 23:27:07344 handler(new SyslogsMemoryHandler(callback, logs, zip_content));
[email protected]4306df762012-04-20 18:58:57345 // TODO(jamescook): Maybe we don't need to update histograms here?
346 handler->StartFetch(MemoryDetails::UPDATE_USER_METRICS);
[email protected]d43970a72011-07-10 06:24:52347}
348
[email protected]650b2d52013-02-10 03:41:45349void SyslogsProviderImpl::LoadCompressedLogs(const base::FilePath& zip_file,
[email protected]d43970a72011-07-10 06:24:52350 std::string* zip_content) {
351 DCHECK(zip_content);
[email protected]82f84b92013-08-30 18:23:50352 if (!base::ReadFileToString(zip_file, zip_content)) {
[email protected]d43970a72011-07-10 06:24:52353 LOG(ERROR) << "Cannot read compressed logs file from " <<
354 zip_file.value().c_str();
355 }
356}
357
358const char* SyslogsProviderImpl::GetSyslogsContextString(
359 SyslogsContext context) {
360 switch (context) {
361 case(SYSLOGS_FEEDBACK):
362 return kContextFeedback;
363 case(SYSLOGS_SYSINFO):
364 return kContextSysInfo;
365 case(SYSLOGS_NETWORK):
366 return kContextNetwork;
367 case(SYSLOGS_DEFAULT):
368 return kContextSysInfo;
369 default:
370 NOTREACHED();
371 return "";
372 }
373}
374
[email protected]0defb7b2012-12-05 23:27:07375// static
376void SyslogsProviderImpl::RunCallbackIfNotCanceled(
[email protected]e95b717f2014-02-06 13:47:13377 const base::CancelableTaskTracker::IsCanceledCallback& is_canceled,
[email protected]0defb7b2012-12-05 23:27:07378 base::TaskRunner* origin_runner,
379 const ReadCompleteCallback& callback,
380 LogDictionaryType* logs,
381 std::string* zip_content) {
382 DCHECK(!is_canceled.is_null() && !callback.is_null());
383
384 if (is_canceled.Run()) {
385 delete logs;
386 delete zip_content;
387 return;
388 }
389
390 // TODO([email protected]): Maybe always run callback asynchronously?
391 if (origin_runner->RunsTasksOnCurrentThread()) {
392 callback.Run(logs, zip_content);
393 } else {
394 origin_runner->PostTask(FROM_HERE, base::Bind(callback, logs, zip_content));
395 }
396}
397
[email protected]d43970a72011-07-10 06:24:52398SyslogsProviderImpl* SyslogsProviderImpl::GetInstance() {
399 return Singleton<SyslogsProviderImpl,
400 DefaultSingletonTraits<SyslogsProviderImpl> >::get();
401}
402
403SyslogsProvider* SyslogsProvider::GetInstance() {
404 return SyslogsProviderImpl::GetInstance();
405}
406
407} // namespace system
408} // namespace chromeos