OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 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. |
| 4 |
| 5 #include "chrome/browser/memory/memory_kills_monitor.h" |
| 6 |
| 7 #include <errno.h> |
| 8 #include <fcntl.h> |
| 9 #include <inttypes.h> |
| 10 #include <stdio.h> |
| 11 |
| 12 #include <string> |
| 13 #include <vector> |
| 14 |
| 15 #include "base/bind.h" |
| 16 #include "base/command_line.h" |
| 17 #include "base/debug/leak_annotations.h" |
| 18 #include "base/files/file_util.h" |
| 19 #include "base/files/scoped_file.h" |
| 20 #include "base/lazy_instance.h" |
| 21 #include "base/location.h" |
| 22 #include "base/logging.h" |
| 23 #include "base/memory/ptr_util.h" |
| 24 #include "base/metrics/histogram_macros.h" |
| 25 #include "base/posix/safe_strerror.h" |
| 26 #include "base/sequenced_task_runner.h" |
| 27 #include "base/strings/string_number_conversions.h" |
| 28 #include "base/strings/string_split.h" |
| 29 #include "base/synchronization/atomic_flag.h" |
| 30 #include "base/time/time.h" |
| 31 #include "chrome/browser/memory/memory_kills_histogram.h" |
| 32 #include "third_party/re2/src/re2/re2.h" |
| 33 |
| 34 namespace memory { |
| 35 |
| 36 using base::SequencedWorkerPool; |
| 37 using base::TimeDelta; |
| 38 |
| 39 namespace { |
| 40 |
| 41 int64_t GetTimestamp(const std::string& line) { |
| 42 std::vector<std::string> fields = base::SplitString( |
| 43 line, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); |
| 44 |
| 45 int64_t timestamp = -1; |
| 46 // Timestamp is the third field in a line of /dev/kmsg. |
| 47 if (fields.size() < 3 || !base::StringToInt64(fields[2], ×tamp)) |
| 48 return -1; |
| 49 return timestamp; |
| 50 } |
| 51 |
| 52 void LogEvent(const base::Time& time_stamp, const std::string& event) { |
| 53 VLOG(1) << time_stamp.ToJavaTime() << ", " << event; |
| 54 } |
| 55 |
| 56 void LogOOMKill(int64_t time_stamp, int oom_badness) { |
| 57 static int64_t last_kill_time = -1; |
| 58 static int oom_kills = 0; |
| 59 |
| 60 // Ideally the timestamp should be parsed from /dev/kmsg, but the timestamp |
| 61 // there is the elapsed time since system boot. So the timestamp |now| used |
| 62 // here is a bit delayed. |
| 63 base::Time now = base::Time::Now(); |
| 64 LogEvent(now, "OOM_KILL"); |
| 65 |
| 66 ++oom_kills; |
| 67 // Report the cumulative count of killed process in one login session. |
| 68 // For example if there are 3 processes killed, it would report 1 for the |
| 69 // first kill, 2 for the second kill, then 3 for the final kill. |
| 70 // It doesn't report a final count at the end of a user session because |
| 71 // the code runs in a dedicated thread and never ends until browser shutdown |
| 72 // (or logout on Chrome OS). And on browser shutdown the thread may be |
| 73 // terminated brutally so there's no chance to execute a "final" block. |
| 74 // More specifically, code outside the main loop of MemoryKillsMonitor::Run() |
| 75 // are not guaranteed to be executed. |
| 76 UMA_HISTOGRAM_CUSTOM_COUNTS("Arc.OOMKills.Count", oom_kills, 1, 1000, 1001); |
| 77 |
| 78 // In practice most process has oom_badness < 1000, but |
| 79 // strictly speaking the number could be [1, 2000]. What it really |
| 80 // means is the baseline, proportion of memory used (normalized to |
| 81 // [0, 1000]), plus an adjustment score oom_score_adj [-1000, 1000], |
| 82 // truncated to 1 if negative (0 means never kill). |
| 83 // Ref: https://lwn.net/Articles/396552/ |
| 84 UMA_HISTOGRAM_CUSTOM_COUNTS("Arc.OOMKills.Score", oom_badness, 1, 2000, 2001); |
| 85 |
| 86 if (time_stamp > 0) { |
| 87 // Sets to |kMaxMemoryKillTimeDelta| for the first kill event. |
| 88 const TimeDelta time_delta = |
| 89 last_kill_time < 0 ? kMaxMemoryKillTimeDelta: |
| 90 TimeDelta::FromMicroseconds(time_stamp - last_kill_time); |
| 91 |
| 92 last_kill_time = time_stamp; |
| 93 |
| 94 UMA_HISTOGRAM_MEMORY_KILL_TIME_INTERVAL( |
| 95 "Arc.OOMKills.TimeDelta", time_delta); |
| 96 } |
| 97 } |
| 98 |
| 99 } // namespace |
| 100 |
| 101 MemoryKillsMonitor::Handle::Handle(MemoryKillsMonitor* outer) : outer_(outer) { |
| 102 DCHECK(outer_); |
| 103 } |
| 104 |
| 105 MemoryKillsMonitor::Handle::Handle(MemoryKillsMonitor::Handle&& other) |
| 106 : outer_(nullptr) { |
| 107 outer_ = other.outer_; |
| 108 other.outer_ = nullptr; |
| 109 } |
| 110 |
| 111 MemoryKillsMonitor::Handle::~Handle() { |
| 112 if (outer_) { |
| 113 VLOG(2) << "Chrome is shutting down" << outer_; |
| 114 outer_->is_shutting_down_.Set(); |
| 115 } |
| 116 } |
| 117 |
| 118 MemoryKillsMonitor::MemoryKillsMonitor() { |
| 119 base::SimpleThread::Options non_joinable_options; |
| 120 non_joinable_options.joinable = false; |
| 121 non_joinable_worker_thread_ = base::MakeUnique<base::DelegateSimpleThread>( |
| 122 this, "memory_kills_monitor", non_joinable_options); |
| 123 non_joinable_worker_thread_->Start(); |
| 124 } |
| 125 |
| 126 MemoryKillsMonitor::~MemoryKillsMonitor() { |
| 127 // The instance has to be leaked on shutdown as it is referred to by a |
| 128 // non-joinable thread but ~MemoryKillsMonitor() can't be explicitly deleted |
| 129 // as it overrides ~SimpleThread(), it should nevertheless never be invoked. |
| 130 NOTREACHED(); |
| 131 } |
| 132 |
| 133 // static |
| 134 MemoryKillsMonitor::Handle MemoryKillsMonitor::StartMonitoring() { |
| 135 #if DCHECK_IS_ON() |
| 136 static volatile bool monitoring_active = false; |
| 137 DCHECK(!monitoring_active); |
| 138 monitoring_active = true; |
| 139 #endif |
| 140 |
| 141 // Instantiate the MemoryKillsMonitor and its underlying thread. The |
| 142 // MemoryKillsMonitor itself has to be leaked on shutdown per having a |
| 143 // non-joinable thread associated to its state. The MemoryKillsMonitor::Handle |
| 144 // will notify the MemoryKillsMonitor when it is destroyed so that the |
| 145 // underlying thread can at a minimum not do extra work during shutdown. |
| 146 MemoryKillsMonitor* instance = new MemoryKillsMonitor(); |
| 147 ANNOTATE_LEAKING_OBJECT_PTR(instance); |
| 148 return Handle(instance); |
| 149 } |
| 150 |
| 151 // static |
| 152 void MemoryKillsMonitor::LogLowMemoryKill( |
| 153 const std::string& type, int estimated_freed_kb) { |
| 154 static base::Time last_kill_time; |
| 155 static int low_memory_kills = 0; |
| 156 |
| 157 base::Time now = base::Time::Now(); |
| 158 LogEvent(now, "LOW_MEMORY_KILL_" + type); |
| 159 |
| 160 const TimeDelta time_delta = |
| 161 last_kill_time.is_null() ? |
| 162 kMaxMemoryKillTimeDelta : |
| 163 (now - last_kill_time); |
| 164 UMA_HISTOGRAM_MEMORY_KILL_TIME_INTERVAL( |
| 165 "Arc.LowMemoryKiller.TimeDelta", time_delta); |
| 166 last_kill_time = now; |
| 167 |
| 168 ++low_memory_kills; |
| 169 UMA_HISTOGRAM_CUSTOM_COUNTS( |
| 170 "Arc.LowMemoryKiller.Count", low_memory_kills, 1, 1000, 1001); |
| 171 |
| 172 UMA_HISTOGRAM_MEMORY_KB("Arc.LowMemoryKiller.FreedSize", |
| 173 estimated_freed_kb); |
| 174 } |
| 175 |
| 176 // static |
| 177 void MemoryKillsMonitor::TryMatchOomKillLine(const std::string& line) { |
| 178 // Sample OOM log line: |
| 179 // 3,1362,97646497541,-;Out of memory: Kill process 29582 (android.vending) |
| 180 // score 961 or sacrifice child. |
| 181 int oom_badness; |
| 182 TimeDelta time_delta; |
| 183 if (RE2::PartialMatch(line, |
| 184 "Out of memory: Kill process .* score (\\d+)", |
| 185 &oom_badness)) { |
| 186 int64_t time_stamp = GetTimestamp(line); |
| 187 LogOOMKill(time_stamp, oom_badness); |
| 188 } |
| 189 } |
| 190 |
| 191 void MemoryKillsMonitor::Run() { |
| 192 VLOG(1) << "MemoryKillsMonitor started"; |
| 193 base::ScopedFILE kmsg_handle( |
| 194 base::OpenFile(base::FilePath("/dev/kmsg"), "r")); |
| 195 if (!kmsg_handle) { |
| 196 LOG(WARNING) << "Open /dev/kmsg failed: " << base::safe_strerror(errno); |
| 197 return; |
| 198 } |
| 199 // Skip kernel messages prior to the instantiation of this object to avoid |
| 200 // double reporting. |
| 201 fseek(kmsg_handle.get(), 0, SEEK_END); |
| 202 |
| 203 static constexpr int kMaxBufSize = 512; |
| 204 char buf[kMaxBufSize]; |
| 205 |
| 206 while (fgets(buf, kMaxBufSize, kmsg_handle.get())) { |
| 207 if (is_shutting_down_.IsSet()) { |
| 208 // Not guaranteed to execute when the process is shutting down, |
| 209 // because the thread might be blocked in fgets(). |
| 210 VLOG(1) << "Chrome is shutting down, MemoryKillsMonitor exits."; |
| 211 break; |
| 212 } |
| 213 TryMatchOomKillLine(buf); |
| 214 } |
| 215 } |
| 216 |
| 217 |
| 218 } // namespace memory |
OLD | NEW |