| // Copyright (c) 2012 The Chromium 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 "chrome/browser/chromeos/system/syslogs_provider.h" |
| |
| #include "ash/shell.h" |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/command_line.h" |
| #include "base/compiler_specific.h" |
| #include "base/file_util.h" |
| #include "base/files/file_path.h" |
| #include "base/logging.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/memory/singleton.h" |
| #include "base/message_loop/message_loop_proxy.h" |
| #include "base/strings/string_util.h" |
| #include "base/task_runner.h" |
| #include "base/threading/sequenced_worker_pool.h" |
| #include "chrome/browser/feedback/feedback_util.h" |
| #include "chrome/browser/memory_details.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chromeos/network/network_event_log.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "dbus/dbus_statistics.h" |
| |
| using content::BrowserThread; |
| |
| namespace chromeos { |
| namespace system { |
| |
| const size_t kFeedbackMaxLength = 4 * 1024; |
| const size_t kFeedbackMaxLineCount = 40; |
| |
| namespace { |
| |
| const char kSysLogsScript[] = |
| "/usr/share/userfeedback/scripts/sysinfo_script_runner"; |
| const char kBzip2Command[] = |
| "/bin/bzip2"; |
| const char kMultilineQuote[] = "\"\"\""; |
| const char kNewLineChars[] = "\r\n"; |
| const char kInvalidLogEntry[] = "<invalid characters in log entry>"; |
| const char kEmptyLogEntry[] = "<no value>"; |
| |
| const char kContextFeedback[] = "feedback"; |
| const char kContextSysInfo[] = "sysinfo"; |
| const char kContextNetwork[] = "network"; |
| |
| // Reads a key from the input string erasing the read values + delimiters read |
| // from the initial string |
| std::string ReadKey(std::string* data) { |
| size_t equal_sign = data->find("="); |
| if (equal_sign == std::string::npos) |
| return std::string(""); |
| std::string key = data->substr(0, equal_sign); |
| data->erase(0, equal_sign); |
| if (data->size() > 0) { |
| // erase the equal to sign also |
| data->erase(0,1); |
| return key; |
| } |
| return std::string(); |
| } |
| |
| // Reads a value from the input string; erasing the read values from |
| // the initial string; detects if the value is multiline and reads |
| // accordingly |
| std::string ReadValue(std::string* data) { |
| // Trim the leading spaces and tabs. In order to use a multi-line |
| // value, you have to place the multi-line quote on the same line as |
| // the equal sign. |
| // |
| // Why not use TrimWhitespace? Consider the following input: |
| // |
| // KEY1= |
| // KEY2=VALUE |
| // |
| // If we use TrimWhitespace, we will incorrectly trim the new line |
| // and assume that KEY1's value is "KEY2=VALUE" rather than empty. |
| base::TrimString(*data, " \t", data); |
| |
| // If multiline value |
| if (StartsWithASCII(*data, std::string(kMultilineQuote), false)) { |
| data->erase(0, strlen(kMultilineQuote)); |
| size_t next_multi = data->find(kMultilineQuote); |
| if (next_multi == std::string::npos) { |
| // Error condition, clear data to stop further processing |
| data->erase(); |
| return std::string(); |
| } |
| std::string value = data->substr(0, next_multi); |
| data->erase(0, next_multi + 3); |
| return value; |
| } else { // single line value |
| size_t endl_pos = data->find_first_of(kNewLineChars); |
| // if we don't find a new line, we just return the rest of the data |
| std::string value = data->substr(0, endl_pos); |
| data->erase(0, endl_pos); |
| return value; |
| } |
| } |
| |
| // Returns a map of system log keys and values. |
| // |
| // Parameters: |
| // temp_filename: This is an out parameter that holds the name of a file in |
| // Reads a value from the input string; erasing the read values from |
| // the initial string; detects if the value is multiline and reads |
| // accordingly |
| // /tmp that contains the system logs in a KEY=VALUE format. |
| // If this parameter is NULL, system logs are not retained on |
| // the filesystem after this call completes. |
| // context: This is an in parameter specifying what context should be |
| // passed to the syslog collection script; currently valid |
| // values are "sysinfo" or "feedback"; in case of an invalid |
| // value, the script will currently default to "sysinfo" |
| |
| LogDictionaryType* GetSystemLogs(base::FilePath* zip_file_name, |
| const std::string& context) { |
| // Create the temp file, logs will go here |
| base::FilePath temp_filename; |
| |
| if (!base::CreateTemporaryFile(&temp_filename)) |
| return NULL; |
| |
| std::string cmd = std::string(kSysLogsScript) + " " + context + " >> " + |
| temp_filename.value(); |
| |
| // Ignore the return value - if the script execution didn't work |
| // stderr won't go into the output file anyway. |
| if (::system(cmd.c_str()) == -1) |
| LOG(WARNING) << "Command " << cmd << " failed to run"; |
| |
| // Compress the logs file if requested. |
| if (zip_file_name) { |
| cmd = std::string(kBzip2Command) + " -c " + temp_filename.value() + " > " + |
| zip_file_name->value(); |
| if (::system(cmd.c_str()) == -1) |
| LOG(WARNING) << "Command " << cmd << " failed to run"; |
| } |
| // Read logs from the temp file |
| std::string data; |
| bool read_success = base::ReadFileToString(temp_filename, &data); |
| // if we were using an internal temp file, the user does not need the |
| // logs to stay past the ReadFile call - delete the file |
| base::DeleteFile(temp_filename, false); |
| |
| if (!read_success) |
| return NULL; |
| |
| // Parse the return data into a dictionary |
| LogDictionaryType* logs = new LogDictionaryType(); |
| while (data.length() > 0) { |
| std::string key = ReadKey(&data); |
| TrimWhitespaceASCII(key, TRIM_ALL, &key); |
| if (!key.empty()) { |
| std::string value = ReadValue(&data); |
| if (IsStringUTF8(value)) { |
| TrimWhitespaceASCII(value, TRIM_ALL, &value); |
| if (value.empty()) |
| (*logs)[key] = kEmptyLogEntry; |
| else |
| (*logs)[key] = value; |
| } else { |
| LOG(WARNING) << "Invalid characters in system log entry: " << key; |
| (*logs)[key] = kInvalidLogEntry; |
| } |
| } else { |
| // no more keys, we're done |
| break; |
| } |
| } |
| |
| return logs; |
| } |
| |
| } // namespace |
| |
| class SyslogsProviderImpl : public SyslogsProvider { |
| public: |
| // SyslogsProvider implementation: |
| virtual CancelableTaskTracker::TaskId RequestSyslogs( |
| bool compress_logs, |
| SyslogsContext context, |
| const ReadCompleteCallback& callback, |
| CancelableTaskTracker* tracker) OVERRIDE; |
| |
| static SyslogsProviderImpl* GetInstance(); |
| |
| private: |
| friend struct DefaultSingletonTraits<SyslogsProviderImpl>; |
| |
| // Reads system logs, compresses content if requested. |
| // Called from blocking pool thread. |
| void ReadSyslogs( |
| const CancelableTaskTracker::IsCanceledCallback& is_canceled, |
| bool compress_logs, |
| SyslogsContext context, |
| const ReadCompleteCallback& callback); |
| |
| // Loads compressed logs and writes into |zip_content|. |
| void LoadCompressedLogs(const base::FilePath& zip_file, |
| std::string* zip_content); |
| |
| SyslogsProviderImpl(); |
| |
| // Gets syslogs context string from the enum value. |
| const char* GetSyslogsContextString(SyslogsContext context); |
| |
| // If not canceled, run callback on originating thread (the thread on which |
| // ReadSyslogs was run). |
| static void RunCallbackIfNotCanceled( |
| const CancelableTaskTracker::IsCanceledCallback& is_canceled, |
| base::TaskRunner* origin_runner, |
| const ReadCompleteCallback& callback, |
| LogDictionaryType* logs, |
| std::string* zip_content); |
| |
| DISALLOW_COPY_AND_ASSIGN(SyslogsProviderImpl); |
| }; |
| |
| SyslogsProviderImpl::SyslogsProviderImpl() { |
| } |
| |
| CancelableTaskTracker::TaskId SyslogsProviderImpl::RequestSyslogs( |
| bool compress_logs, |
| SyslogsContext context, |
| const ReadCompleteCallback& callback, |
| CancelableTaskTracker* tracker) { |
| CancelableTaskTracker::IsCanceledCallback is_canceled; |
| CancelableTaskTracker::TaskId id = tracker->NewTrackedTaskId(&is_canceled); |
| |
| ReadCompleteCallback callback_runner = |
| base::Bind(&SyslogsProviderImpl::RunCallbackIfNotCanceled, |
| is_canceled, base::MessageLoopProxy::current(), callback); |
| |
| // Schedule a task which will run the callback later when complete. |
| BrowserThread::PostBlockingPoolTask( |
| FROM_HERE, |
| base::Bind(&SyslogsProviderImpl::ReadSyslogs, base::Unretained(this), |
| is_canceled, compress_logs, context, callback_runner)); |
| return id; |
| } |
| |
| // Derived class from memoryDetails converts the results into a single string |
| // and adds a "mem_usage" entry to the logs, then forwards the result. |
| // Format of entry is (one process per line, reverse-sorted by size): |
| // Tab [Title1|Title2]: 50 MB |
| // Browser: 30 MB |
| // Tab [Title]: 20 MB |
| // Extension [Title]: 10 MB |
| // ... |
| class SyslogsMemoryHandler : public MemoryDetails { |
| public: |
| typedef SyslogsProvider::ReadCompleteCallback ReadCompleteCallback; |
| |
| // |logs| is modified (see comment above) and passed to |request|. |
| // |zip_content| is passed to |request|. |
| SyslogsMemoryHandler(const ReadCompleteCallback& callback, |
| LogDictionaryType* logs, |
| std::string* zip_content); |
| |
| virtual void OnDetailsAvailable() OVERRIDE; |
| |
| private: |
| virtual ~SyslogsMemoryHandler(); |
| |
| ReadCompleteCallback callback_; |
| |
| LogDictionaryType* logs_; |
| std::string* zip_content_; |
| |
| DISALLOW_COPY_AND_ASSIGN(SyslogsMemoryHandler); |
| }; |
| |
| SyslogsMemoryHandler::SyslogsMemoryHandler( |
| const ReadCompleteCallback& callback, |
| LogDictionaryType* logs, |
| std::string* zip_content) |
| : callback_(callback), |
| logs_(logs), |
| zip_content_(zip_content) { |
| DCHECK(!callback_.is_null()); |
| } |
| |
| void SyslogsMemoryHandler::OnDetailsAvailable() { |
| (*logs_)["mem_usage"] = ToLogString(); |
| callback_.Run(logs_, zip_content_); |
| } |
| |
| SyslogsMemoryHandler::~SyslogsMemoryHandler() {} |
| |
| // Called from blocking pool thread. |
| void SyslogsProviderImpl::ReadSyslogs( |
| const CancelableTaskTracker::IsCanceledCallback& is_canceled, |
| bool compress_logs, |
| SyslogsContext context, |
| const ReadCompleteCallback& callback) { |
| DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); |
| |
| if (is_canceled.Run()) |
| return; |
| |
| // Create temp file. |
| base::FilePath zip_file; |
| if (compress_logs && !base::CreateTemporaryFile(&zip_file)) { |
| LOG(ERROR) << "Cannot create temp file"; |
| compress_logs = false; |
| } |
| |
| LogDictionaryType* logs = NULL; |
| logs = GetSystemLogs( |
| compress_logs ? &zip_file : NULL, |
| GetSyslogsContextString(context)); |
| |
| std::string* zip_content = NULL; |
| if (compress_logs) { |
| // Load compressed logs. |
| zip_content = new std::string(); |
| LoadCompressedLogs(zip_file, zip_content); |
| base::DeleteFile(zip_file, false); |
| } |
| |
| // Include dbus statistics summary |
| (*logs)["dbus"] = dbus::statistics::GetAsString( |
| dbus::statistics::SHOW_INTERFACE, |
| dbus::statistics::FORMAT_ALL); |
| |
| // Include recent network log events |
| (*logs)["network_event_log"] = network_event_log::GetAsString( |
| network_event_log::OLDEST_FIRST, |
| "time,file,desc", |
| network_event_log::kDefaultLogLevel, |
| system::kFeedbackMaxLineCount); |
| |
| // SyslogsMemoryHandler will clean itself up. |
| // SyslogsMemoryHandler::OnDetailsAvailable() will modify |logs| and call |
| // request->ForwardResult(logs, zip_content). |
| scoped_refptr<SyslogsMemoryHandler> |
| handler(new SyslogsMemoryHandler(callback, logs, zip_content)); |
| // TODO(jamescook): Maybe we don't need to update histograms here? |
| handler->StartFetch(MemoryDetails::UPDATE_USER_METRICS); |
| } |
| |
| void SyslogsProviderImpl::LoadCompressedLogs(const base::FilePath& zip_file, |
| std::string* zip_content) { |
| DCHECK(zip_content); |
| if (!base::ReadFileToString(zip_file, zip_content)) { |
| LOG(ERROR) << "Cannot read compressed logs file from " << |
| zip_file.value().c_str(); |
| } |
| } |
| |
| const char* SyslogsProviderImpl::GetSyslogsContextString( |
| SyslogsContext context) { |
| switch (context) { |
| case(SYSLOGS_FEEDBACK): |
| return kContextFeedback; |
| case(SYSLOGS_SYSINFO): |
| return kContextSysInfo; |
| case(SYSLOGS_NETWORK): |
| return kContextNetwork; |
| case(SYSLOGS_DEFAULT): |
| return kContextSysInfo; |
| default: |
| NOTREACHED(); |
| return ""; |
| } |
| } |
| |
| // static |
| void SyslogsProviderImpl::RunCallbackIfNotCanceled( |
| const CancelableTaskTracker::IsCanceledCallback& is_canceled, |
| base::TaskRunner* origin_runner, |
| const ReadCompleteCallback& callback, |
| LogDictionaryType* logs, |
| std::string* zip_content) { |
| DCHECK(!is_canceled.is_null() && !callback.is_null()); |
| |
| if (is_canceled.Run()) { |
| delete logs; |
| delete zip_content; |
| return; |
| } |
| |
| // TODO(achuith@chromium.org): Maybe always run callback asynchronously? |
| if (origin_runner->RunsTasksOnCurrentThread()) { |
| callback.Run(logs, zip_content); |
| } else { |
| origin_runner->PostTask(FROM_HERE, base::Bind(callback, logs, zip_content)); |
| } |
| } |
| |
| SyslogsProviderImpl* SyslogsProviderImpl::GetInstance() { |
| return Singleton<SyslogsProviderImpl, |
| DefaultSingletonTraits<SyslogsProviderImpl> >::get(); |
| } |
| |
| SyslogsProvider* SyslogsProvider::GetInstance() { |
| return SyslogsProviderImpl::GetInstance(); |
| } |
| |
| } // namespace system |
| } // namespace chromeos |