Collect suspend and resume info from power manager

Updated the unclean shutdown collector to check and report the
modification times of trace files left by power manager to indicate that
the system was suspended and that it was running on low battery.

These files are interpreted by the unclean shutdown collector to
determine the cause of an unclean shutdown -- either the battery ran
out during suspend or the battery ran out after resuming from a low
battery-induced suspend and it continued to run on battery.

BUG=chromium-os:1472
TEST=Let the battery run below the cutoff, suspend & resume, and then
remove the battery to simulate running dry; check crash reporter
logs.  Alternatively/additionally, let it suspend and remove the battery
while suspended.  Restart and check crash reporter logs.

Signed-off-by: Simon Que <sque@chromium.org>

Change-Id: I8e6767e8457afb7abf1e7300eac020adda1ebb48

Review URL: http://codereview.chromium.org/3644007
diff --git a/crash_reporter/unclean_shutdown_collector.cc b/crash_reporter/unclean_shutdown_collector.cc
index bb2dd7a..1e1c784 100644
--- a/crash_reporter/unclean_shutdown_collector.cc
+++ b/crash_reporter/unclean_shutdown_collector.cc
@@ -11,8 +11,18 @@
 static const char kUncleanShutdownFile[] =
     "/var/lib/crash_reporter/pending_clean_shutdown";
 
+// Files created by power manager used for crash reporting.
+static const char kPowerdTracePath[] = "/var/lib/power_manager";
+// Presence of this file indicates that the system was suspended
+static const char kPowerdSuspended[] = "powerd_suspended";
+// Presence of this file indicates that the battery was critically low.
+static const char kPowerdLowBattery[] = "powerd_low_battery";
+
 UncleanShutdownCollector::UncleanShutdownCollector()
-    : unclean_shutdown_file_(kUncleanShutdownFile) {
+    : unclean_shutdown_file_(kUncleanShutdownFile),
+      powerd_trace_path_(kPowerdTracePath),
+      powerd_suspended_file_(powerd_trace_path_.Append(kPowerdSuspended)),
+      powerd_low_battery_file_(powerd_trace_path_.Append(kPowerdLowBattery)) {
 }
 
 UncleanShutdownCollector::~UncleanShutdownCollector() {
@@ -28,12 +38,15 @@
   return true;
 }
 
-bool UncleanShutdownCollector::DeleteUncleanShutdownFile() {
+bool UncleanShutdownCollector::DeleteUncleanShutdownFiles() {
   if (!file_util::Delete(FilePath(unclean_shutdown_file_), false)) {
     logger_->LogError("Failed to delete unclean shutdown file %s",
                       unclean_shutdown_file_);
     return false;
   }
+  // Delete power manager trace files if they exist.
+  file_util::Delete(powerd_suspended_file_, false);
+  file_util::Delete(powerd_low_battery_file_, false);
   return true;
 }
 
@@ -43,7 +56,11 @@
     return false;
   }
   logger_->LogWarning("Last shutdown was not clean");
-  DeleteUncleanShutdownFile();
+  if (DeadBatteryCausedUncleanShutdown()) {
+    DeleteUncleanShutdownFiles();
+    return false;
+  }
+  DeleteUncleanShutdownFiles();
 
   if (is_feedback_allowed_function_()) {
     count_crash_function_();
@@ -53,5 +70,24 @@
 
 bool UncleanShutdownCollector::Disable() {
   logger_->LogInfo("Clean shutdown signalled");
-  return DeleteUncleanShutdownFile();
+  return DeleteUncleanShutdownFiles();
+}
+
+bool UncleanShutdownCollector::DeadBatteryCausedUncleanShutdown()
+{
+  // Check for case of battery running out while suspended.
+  if (file_util::PathExists(powerd_suspended_file_)) {
+    logger_->LogInfo("Unclean shutdown occurred while suspended. Not counting "
+                     "toward unclean shutdown statistic.");
+    return true;
+  }
+  // Check for case of battery running out after resuming from a low-battery
+  // suspend.
+  if (file_util::PathExists(powerd_low_battery_file_)) {
+    logger_->LogInfo("Unclean shutdown occurred while running with battery "
+                     "critically low.  Not counting toward unclean shutdown "
+                     "statistic.");
+    return true;
+  }
+  return false;
 }
diff --git a/crash_reporter/unclean_shutdown_collector.h b/crash_reporter/unclean_shutdown_collector.h
index e7d8cef..7e10e7f 100644
--- a/crash_reporter/unclean_shutdown_collector.h
+++ b/crash_reporter/unclean_shutdown_collector.h
@@ -30,10 +30,19 @@
  private:
   friend class UncleanShutdownCollectorTest;
   FRIEND_TEST(UncleanShutdownCollectorTest, EnableCannotWrite);
+  FRIEND_TEST(UncleanShutdownCollectorTest, CollectDeadBatteryRunningLow);
+  FRIEND_TEST(UncleanShutdownCollectorTest, CollectDeadBatterySuspended);
 
-  bool DeleteUncleanShutdownFile();
+  bool DeleteUncleanShutdownFiles();
+
+  // Check for unclean shutdown due to battery running out by analyzing powerd
+  // trace files.
+  bool DeadBatteryCausedUncleanShutdown();
 
   const char *unclean_shutdown_file_;
+  FilePath powerd_trace_path_;
+  FilePath powerd_suspended_file_;
+  FilePath powerd_low_battery_file_;
 };
 
 #endif  // _CRASH_REPORTER_UNCLEAN_SHUTDOWN_COLLECTOR_H_
diff --git a/crash_reporter/unclean_shutdown_collector_test.cc b/crash_reporter/unclean_shutdown_collector_test.cc
index 7be52af..2cf9fb5 100644
--- a/crash_reporter/unclean_shutdown_collector_test.cc
+++ b/crash_reporter/unclean_shutdown_collector_test.cc
@@ -12,8 +12,10 @@
 #include "gtest/gtest.h"
 
 static int s_crashes = 0;
-static bool s_metrics = false;
+static bool s_metrics = true;
 
+static const char kTestLowBattery[] = "test/low_battery";
+static const char kTestSuspended[] = "test/suspended";
 static const char kTestUnclean[] = "test/unclean";
 
 void CountCrash() {
@@ -34,6 +36,9 @@
     test_unclean_ = FilePath(kTestUnclean);
     collector_.unclean_shutdown_file_ = kTestUnclean;
     file_util::Delete(test_unclean_, true);
+    // Set up alternate power manager tracing files as well
+    collector_.powerd_suspended_file_ = FilePath(kTestSuspended);
+    collector_.powerd_low_battery_file_ = FilePath(kTestLowBattery);
   }
  protected:
   void WriteStringToFile(const FilePath &file_path,
@@ -70,12 +75,39 @@
   ASSERT_TRUE(file_util::PathExists(test_unclean_));
   ASSERT_TRUE(collector_.Collect());
   ASSERT_FALSE(file_util::PathExists(test_unclean_));
+  ASSERT_EQ(1, s_crashes);
   ASSERT_NE(std::string::npos,
             logging_.log().find("Last shutdown was not clean"));
 }
 
 TEST_F(UncleanShutdownCollectorTest, CollectFalse) {
   ASSERT_FALSE(collector_.Collect());
+  ASSERT_EQ(0, s_crashes);
+}
+
+TEST_F(UncleanShutdownCollectorTest, CollectDeadBatteryRunningLow) {
+  ASSERT_TRUE(collector_.Enable());
+  ASSERT_TRUE(file_util::PathExists(test_unclean_));
+  file_util::WriteFile(collector_.powerd_low_battery_file_, "", 0);
+  ASSERT_FALSE(collector_.Collect());
+  ASSERT_FALSE(file_util::PathExists(test_unclean_));
+  ASSERT_FALSE(file_util::PathExists(collector_.powerd_low_battery_file_));
+  ASSERT_EQ(0, s_crashes);
+  ASSERT_NE(std::string::npos,
+            logging_.log().find("Unclean shutdown occurred while running with "
+                                "battery critically low."));
+}
+
+TEST_F(UncleanShutdownCollectorTest, CollectDeadBatterySuspended) {
+  ASSERT_TRUE(collector_.Enable());
+  ASSERT_TRUE(file_util::PathExists(test_unclean_));
+  file_util::WriteFile(collector_.powerd_suspended_file_, "", 0);
+  ASSERT_FALSE(collector_.Collect());
+  ASSERT_FALSE(file_util::PathExists(test_unclean_));
+  ASSERT_FALSE(file_util::PathExists(collector_.powerd_suspended_file_));
+  ASSERT_EQ(0, s_crashes);
+  ASSERT_NE(std::string::npos,
+            logging_.log().find("Unclean shutdown occurred while suspended."));
 }
 
 TEST_F(UncleanShutdownCollectorTest, Disable) {