1 // Copyright (c) 2012 The Chromium 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.
5 #include "chrome/browser/chromeos/system/syslogs_provider.h"
9 #include "base/bind_helpers.h"
10 #include "base/command_line.h"
11 #include "base/compiler_specific.h"
12 #include "base/file_util.h"
13 #include "base/files/file_path.h"
14 #include "base/logging.h"
15 #include "base/memory/scoped_ptr.h"
16 #include "base/memory/singleton.h"
17 #include "base/message_loop/message_loop_proxy.h"
18 #include "base/strings/string_util.h"
19 #include "base/task_runner.h"
20 #include "base/threading/sequenced_worker_pool.h"
21 #include "chrome/browser/feedback/feedback_util.h"
22 #include "chrome/browser/memory_details.h"
23 #include "chrome/common/chrome_switches.h"
24 #include "chromeos/network/network_event_log.h"
25 #include "content/public/browser/browser_thread.h"
26 #include "dbus/dbus_statistics.h"
28 using content::BrowserThread
;
33 const size_t kFeedbackMaxLength
= 4 * 1024;
34 const size_t kFeedbackMaxLineCount
= 40;
38 const char kSysLogsScript
[] =
39 "/usr/share/userfeedback/scripts/sysinfo_script_runner";
40 const char kBzip2Command
[] =
42 const char kMultilineQuote
[] = "\"\"\"";
43 const char kNewLineChars
[] = "\r\n";
44 const char kInvalidLogEntry
[] = "<invalid characters in log entry>";
45 const char kEmptyLogEntry
[] = "<no value>";
47 const char kContextFeedback
[] = "feedback";
48 const char kContextSysInfo
[] = "sysinfo";
49 const char kContextNetwork
[] = "network";
51 // Reads a key from the input string erasing the read values + delimiters read
52 // from the initial string
53 std::string
ReadKey(std::string
* data
) {
55 size_t equal_sign
= data
->find("=");
56 if (equal_sign
== std::string::npos
)
58 key
= data
->substr(0, equal_sign
);
59 data
->erase(0, equal_sign
);
62 // erase the equal to sign also
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
70 std::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
75 // Why not use TrimWhitespace? Consider the following input:
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.
82 base::TrimString(*data
, " \t", data
);
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
93 std::string value
= data
->substr(0, next_multi
);
94 data
->erase(0, next_multi
+ 3);
98 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
);
106 // Returns a map of system log keys and values.
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
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"
121 LogDictionaryType
* GetSystemLogs(base::FilePath
* zip_file_name
,
122 const std::string
& context
) {
123 // Create the temp file, logs will go here
124 base::FilePath temp_filename
;
126 if (!base::CreateTemporaryFile(&temp_filename
))
129 std::string cmd
= std::string(kSysLogsScript
) + " " + context
+ " >> " +
130 temp_filename
.value();
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";
137 // Compress the logs file if requested.
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";
144 // Read logs from the temp file
146 bool read_success
= base::ReadFileToString(temp_filename
, &data
);
147 // 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
149 base::DeleteFile(temp_filename
, false);
154 // Parse the return data into a dictionary
155 LogDictionaryType
* logs
= new LogDictionaryType();
156 while (data
.length() > 0) {
157 std::string key
= ReadKey(&data
);
158 base::TrimWhitespaceASCII(key
, base::TRIM_ALL
, &key
);
160 std::string value
= ReadValue(&data
);
161 if (IsStringUTF8(value
)) {
162 base::TrimWhitespaceASCII(value
, base::TRIM_ALL
, &value
);
164 (*logs
)[key
] = kEmptyLogEntry
;
166 (*logs
)[key
] = value
;
168 LOG(WARNING
) << "Invalid characters in system log entry: " << key
;
169 (*logs
)[key
] = kInvalidLogEntry
;
172 // no more keys, we're done
182 class SyslogsProviderImpl
: public SyslogsProvider
{
184 // SyslogsProvider implementation:
185 virtual base::CancelableTaskTracker::TaskId
RequestSyslogs(
187 SyslogsContext context
,
188 const ReadCompleteCallback
& callback
,
189 base::CancelableTaskTracker
* tracker
) OVERRIDE
;
191 static SyslogsProviderImpl
* GetInstance();
194 friend struct DefaultSingletonTraits
<SyslogsProviderImpl
>;
196 // Reads system logs, compresses content if requested.
197 // Called from blocking pool thread.
199 const base::CancelableTaskTracker::IsCanceledCallback
& is_canceled
,
201 SyslogsContext context
,
202 const ReadCompleteCallback
& callback
);
204 // Loads compressed logs and writes into |zip_content|.
205 void LoadCompressedLogs(const base::FilePath
& zip_file
,
206 std::string
* zip_content
);
208 SyslogsProviderImpl();
210 // Gets syslogs context string from the enum value.
211 const char* GetSyslogsContextString(SyslogsContext context
);
213 // If not canceled, run callback on originating thread (the thread on which
214 // ReadSyslogs was run).
215 static void RunCallbackIfNotCanceled(
216 const base::CancelableTaskTracker::IsCanceledCallback
& is_canceled
,
217 base::TaskRunner
* origin_runner
,
218 const ReadCompleteCallback
& callback
,
219 LogDictionaryType
* logs
,
220 std::string
* zip_content
);
222 DISALLOW_COPY_AND_ASSIGN(SyslogsProviderImpl
);
225 SyslogsProviderImpl::SyslogsProviderImpl() {
228 base::CancelableTaskTracker::TaskId
SyslogsProviderImpl::RequestSyslogs(
230 SyslogsContext context
,
231 const ReadCompleteCallback
& callback
,
232 base::CancelableTaskTracker
* tracker
) {
233 base::CancelableTaskTracker::IsCanceledCallback is_canceled
;
234 base::CancelableTaskTracker::TaskId id
=
235 tracker
->NewTrackedTaskId(&is_canceled
);
237 ReadCompleteCallback callback_runner
=
238 base::Bind(&SyslogsProviderImpl::RunCallbackIfNotCanceled
,
239 is_canceled
, base::MessageLoopProxy::current(), callback
);
241 // Schedule a task which will run the callback later when complete.
242 BrowserThread::PostBlockingPoolTask(
244 base::Bind(&SyslogsProviderImpl::ReadSyslogs
, base::Unretained(this),
245 is_canceled
, compress_logs
, context
, callback_runner
));
249 // 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
254 // Tab [Title]: 20 MB
255 // Extension [Title]: 10 MB
257 class SyslogsMemoryHandler
: public MemoryDetails
{
259 typedef SyslogsProvider::ReadCompleteCallback ReadCompleteCallback
;
261 // |logs| is modified (see comment above) and passed to |request|.
262 // |zip_content| is passed to |request|.
263 SyslogsMemoryHandler(const ReadCompleteCallback
& callback
,
264 LogDictionaryType
* logs
,
265 std::string
* zip_content
);
267 virtual void OnDetailsAvailable() OVERRIDE
;
270 virtual ~SyslogsMemoryHandler();
272 ReadCompleteCallback callback_
;
274 LogDictionaryType
* logs_
;
275 std::string
* zip_content_
;
277 DISALLOW_COPY_AND_ASSIGN(SyslogsMemoryHandler
);
280 SyslogsMemoryHandler::SyslogsMemoryHandler(
281 const ReadCompleteCallback
& callback
,
282 LogDictionaryType
* logs
,
283 std::string
* zip_content
)
284 : callback_(callback
),
286 zip_content_(zip_content
) {
287 DCHECK(!callback_
.is_null());
290 void SyslogsMemoryHandler::OnDetailsAvailable() {
291 (*logs_
)["mem_usage"] = ToLogString();
292 callback_
.Run(logs_
, zip_content_
);
295 SyslogsMemoryHandler::~SyslogsMemoryHandler() {}
297 // Called from blocking pool thread.
298 void SyslogsProviderImpl::ReadSyslogs(
299 const base::CancelableTaskTracker::IsCanceledCallback
& is_canceled
,
301 SyslogsContext context
,
302 const ReadCompleteCallback
& callback
) {
303 DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
305 if (is_canceled
.Run())
309 base::FilePath zip_file
;
310 if (compress_logs
&& !base::CreateTemporaryFile(&zip_file
)) {
311 LOG(ERROR
) << "Cannot create temp file";
312 compress_logs
= false;
315 LogDictionaryType
* logs
= NULL
;
316 logs
= GetSystemLogs(
317 compress_logs
? &zip_file
: NULL
,
318 GetSyslogsContextString(context
));
320 std::string
* zip_content
= NULL
;
322 // Load compressed logs.
323 zip_content
= new std::string();
324 LoadCompressedLogs(zip_file
, zip_content
);
325 base::DeleteFile(zip_file
, false);
328 // Include dbus statistics summary
329 (*logs
)["dbus"] = dbus::statistics::GetAsString(
330 dbus::statistics::SHOW_INTERFACE
,
331 dbus::statistics::FORMAT_ALL
);
333 // Include recent network log events
334 (*logs
)["network_event_log"] = network_event_log::GetAsString(
335 network_event_log::OLDEST_FIRST
,
337 network_event_log::kDefaultLogLevel
,
338 system::kFeedbackMaxLineCount
);
340 // SyslogsMemoryHandler will clean itself up.
341 // SyslogsMemoryHandler::OnDetailsAvailable() will modify |logs| and call
342 // request->ForwardResult(logs, zip_content).
343 scoped_refptr
<SyslogsMemoryHandler
>
344 handler(new SyslogsMemoryHandler(callback
, logs
, zip_content
));
345 // TODO(jamescook): Maybe we don't need to update histograms here?
346 handler
->StartFetch(MemoryDetails::UPDATE_USER_METRICS
);
349 void SyslogsProviderImpl::LoadCompressedLogs(const base::FilePath
& zip_file
,
350 std::string
* zip_content
) {
352 if (!base::ReadFileToString(zip_file
, zip_content
)) {
353 LOG(ERROR
) << "Cannot read compressed logs file from " <<
354 zip_file
.value().c_str();
358 const char* SyslogsProviderImpl::GetSyslogsContextString(
359 SyslogsContext 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
;
376 void SyslogsProviderImpl::RunCallbackIfNotCanceled(
377 const base::CancelableTaskTracker::IsCanceledCallback
& is_canceled
,
378 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());
384 if (is_canceled
.Run()) {
390 // TODO(achuith@chromium.org): Maybe always run callback asynchronously?
391 if (origin_runner
->RunsTasksOnCurrentThread()) {
392 callback
.Run(logs
, zip_content
);
394 origin_runner
->PostTask(FROM_HERE
, base::Bind(callback
, logs
, zip_content
));
398 SyslogsProviderImpl
* SyslogsProviderImpl::GetInstance() {
399 return Singleton
<SyslogsProviderImpl
,
400 DefaultSingletonTraits
<SyslogsProviderImpl
> >::get();
403 SyslogsProvider
* SyslogsProvider::GetInstance() {
404 return SyslogsProviderImpl::GetInstance();
407 } // namespace system
408 } // namespace chromeos