| // Copyright (c) 2010 The Chromium OS 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 "window_manager/chrome_watchdog.h" |
| |
| #include <csignal> |
| |
| #include <gflags/gflags.h> |
| |
| #include "base/logging.h" |
| #include "cros/chromeos_wm_ipc_enums.h" |
| #include "window_manager/callback.h" |
| #include "window_manager/event_consumer_registrar.h" |
| #include "window_manager/event_loop.h" |
| #include "window_manager/util.h" |
| #include "window_manager/window.h" |
| #include "window_manager/window_manager.h" |
| |
| DEFINE_bool(kill_chrome_if_hanging, false, |
| "Kill Chrome if it doesn't respond to pings sent via X"); |
| |
| using chromeos::WmIpcMessageType; |
| using window_manager::util::GetHostname; |
| using window_manager::util::GetMonotonicTime; |
| |
| namespace { |
| |
| const int kSignalToSend = SIGABRT; |
| |
| const char kHistogramName[] = "WindowManager.ChromePingResponseTime"; |
| |
| // What histogram sample should we report when we kill Chrome? |
| // This must be higher than the timeout passed to SendPingToChrome(). |
| const int kKillHistogramSampleMs = 60000; |
| |
| // Minimum and maximum sample values that will be recorded in the histogram. |
| const int kMinHistogramSampleMs = 0; |
| const int kMaxHistogramSampleMs = kKillHistogramSampleMs; |
| |
| // Number of histogram buckets (as recommended by metrics_library.h). |
| const int kNumHistogramBuckets = 50; |
| |
| } // namespace |
| |
| namespace window_manager { |
| |
| ChromeWatchdog::ChromeWatchdog(WindowManager* wm) |
| : wm_(wm), |
| local_hostname_(GetHostname()), |
| registrar_(new EventConsumerRegistrar(wm, this)), |
| pinged_chrome_xid_(0), |
| ping_timestamp_(0), |
| timeout_id_(EventLoop::kUnsetTimeoutId), |
| last_killed_pid_(-1) { |
| } |
| |
| ChromeWatchdog::~ChromeWatchdog() { |
| if (has_outstanding_ping()) |
| AbortTimeout(); |
| } |
| |
| void ChromeWatchdog::HandleWindowMap(Window* win) { |
| DCHECK(win); |
| if (WmIpcWindowTypeIsChrome(win->type()) && |
| win->supports_wm_ping() && |
| win->client_hostname() == local_hostname_ && |
| is_pid_valid(win->client_pid())) { |
| usable_chrome_xids_.insert(win->xid()); |
| } |
| } |
| |
| void ChromeWatchdog::HandleWindowUnmap(Window* win) { |
| DCHECK(win); |
| usable_chrome_xids_.erase(win->xid()); |
| |
| if (has_outstanding_ping() && pinged_chrome_xid_ == win->xid()) |
| AbortTimeout(); |
| } |
| |
| void ChromeWatchdog::HandleClientMessage(XWindow xid, |
| XAtom message_type, |
| const long data[5]) { |
| DCHECK_EQ(xid, wm_->root()); |
| if (message_type != wm_->GetXAtom(ATOM_WM_PROTOCOLS) || |
| static_cast<XAtom>(data[0]) != wm_->GetXAtom(ATOM_NET_WM_PING) || |
| static_cast<XTime>(data[1]) != ping_timestamp_ || |
| static_cast<XWindow>(data[2]) != pinged_chrome_xid_) { |
| return; |
| } |
| |
| const int elapsed_ms = static_cast<int>( |
| (GetMonotonicTime() - monotonic_time_from_last_ping_).InMilliseconds()); |
| wm_->ReportHistogramSample( |
| kHistogramName, |
| elapsed_ms, |
| kMinHistogramSampleMs, kMaxHistogramSampleMs, |
| kNumHistogramBuckets); |
| |
| AbortTimeout(); |
| } |
| |
| bool ChromeWatchdog::SendPingToChrome(XTime timestamp, int timeout_ms) { |
| if (has_outstanding_ping()) { |
| LOG(ERROR) << "Got request to send ping while previous ping is still " |
| << "outstanding; abandoning previous ping"; |
| AbortTimeout(); |
| } |
| |
| if (usable_chrome_xids_.empty()) |
| return false; |
| |
| XWindow xid = *(usable_chrome_xids_.begin()); |
| if (!wm_->GetWindowOrDie(xid)->SendPing(timestamp)) |
| return false; |
| |
| DCHECK_EQ(timeout_id_, EventLoop::kUnsetTimeoutId); |
| timeout_id_ = wm_->event_loop()->AddTimeout( |
| NewPermanentCallback(this, &ChromeWatchdog::HandleTimeout), |
| timeout_ms, 0); |
| |
| pinged_chrome_xid_ = xid; |
| ping_timestamp_ = timestamp; |
| monotonic_time_from_last_ping_ = GetMonotonicTime(); |
| registrar_->RegisterForWindowEvents(wm_->root()); |
| return true; |
| } |
| |
| void ChromeWatchdog::AbortTimeout() { |
| if (!has_outstanding_ping()) |
| return; |
| |
| registrar_->UnregisterForWindowEvents(wm_->root()); |
| pinged_chrome_xid_ = 0; |
| ping_timestamp_ = 0; |
| wm_->event_loop()->RemoveTimeoutIfSet(&timeout_id_); |
| } |
| |
| void ChromeWatchdog::HandleTimeout() { |
| DCHECK(has_outstanding_ping()); |
| DCHECK(pinged_chrome_xid_); |
| Window* win = wm_->GetWindowOrDie(pinged_chrome_xid_); |
| AbortTimeout(); |
| |
| wm_->ReportHistogramSample( |
| kHistogramName, |
| kKillHistogramSampleMs, |
| kMinHistogramSampleMs, kMaxHistogramSampleMs, |
| kNumHistogramBuckets); |
| |
| pid_t chrome_pid = static_cast<pid_t>(win->client_pid()); |
| LOG(INFO) << "Chrome window " << win->xid_str() << " didn't respond to ping; " |
| << (!FLAGS_kill_chrome_if_hanging ? "(not really) " : "") |
| << "sending signal " << kSignalToSend << " to PID " << chrome_pid; |
| if (FLAGS_kill_chrome_if_hanging && is_pid_valid(chrome_pid)) { |
| if (kill(chrome_pid, kSignalToSend) != 0) |
| PLOG(ERROR) << "Unable to kill Chrome PID " << chrome_pid; |
| } |
| |
| last_killed_pid_ = chrome_pid; |
| } |
| |
| } // namespace window_manager |