Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit abd73e61 authored by James Hawkins's avatar James Hawkins
Browse files

system/core: Add initial implementation of the bootstat command.

The bootstat command enables the measurement and logging of boot time
metrics
for GMS devices.

BUG:21724738
Change-Id: I331456dd38a60fb4ef24a4d5320909dbad30db66
parent d8ecde09
Loading
Loading
Loading
Loading

bootstat/Android.mk

0 → 100644
+137 −0
Original line number Diff line number Diff line
#
# Copyright (C) 2016 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

LOCAL_PATH := $(call my-dir)

bootstat_c_includes := external/gtest/include

bootstat_lib_src_files := \
        boot_event_record_store.cpp \
        event_log_list_builder.cpp

bootstat_src_files := \
        bootstat.cpp

bootstat_test_src_files := \
        boot_event_record_store_test.cpp \
        event_log_list_builder_test.cpp \
        testrunner.cpp

bootstat_shared_libs := \
        libbase \
        liblog

bootstat_cflags := \
        -Wall \
        -Wextra \
        -Werror

bootstat_cppflags := \
        -Wno-non-virtual-dtor

bootstat_debug_cflags := \
        $(bootstat_cflags) \
        -UNDEBUG

# 524291 corresponds to sysui_histogram, from
# frameworks/base/core/java/com/android/internal/logging/EventLogTags.logtags
bootstat_cflags += -DHISTOGRAM_LOG_TAG=524291


# bootstat static library
# -----------------------------------------------------------------------------

include $(CLEAR_VARS)

LOCAL_MODULE := libbootstat
LOCAL_CFLAGS := $(bootstat_cflags)
LOCAL_CPPFLAGS := $(bootstat_cppflags)
LOCAL_C_INCLUDES := $(bootstat_c_includes)
LOCAL_SHARED_LIBRARIES := $(bootstat_shared_libs)
LOCAL_SRC_FILES := $(bootstat_lib_src_files)

include $(BUILD_STATIC_LIBRARY)

# bootstat static library, debug
# -----------------------------------------------------------------------------

include $(CLEAR_VARS)

LOCAL_MODULE := libbootstat_debug
LOCAL_CFLAGS := $(bootstat_cflags)
LOCAL_CPPFLAGS := $(bootstat_debug_cppflags)
LOCAL_C_INCLUDES := $(bootstat_c_includes)
LOCAL_SHARED_LIBRARIES := $(bootstat_shared_libs)
LOCAL_SRC_FILES := $(bootstat_lib_src_files)

include $(BUILD_STATIC_LIBRARY)

# bootstat host static library, debug
# -----------------------------------------------------------------------------

include $(CLEAR_VARS)

LOCAL_MODULE := libbootstat_host_debug
LOCAL_CFLAGS := $(bootstat_debug_cflags)
LOCAL_CPPFLAGS := $(bootstat_cppflags)
LOCAL_C_INCLUDES := $(bootstat_c_includes)
LOCAL_SHARED_LIBRARIES := $(bootstat_shared_libs)
LOCAL_SRC_FILES := $(bootstat_lib_src_files)

include $(BUILD_HOST_STATIC_LIBRARY)

# bootstat binary
# -----------------------------------------------------------------------------

include $(CLEAR_VARS)

LOCAL_MODULE := bootstat
LOCAL_CFLAGS := $(bootstat_cflags)
LOCAL_CPPFLAGS := $(bootstat_cppflags)
LOCAL_C_INCLUDES := $(bootstat_c_includes)
LOCAL_SHARED_LIBRARIES := $(bootstat_shared_libs)
LOCAL_STATIC_LIBRARIES := libbootstat
LOCAL_SRC_FILES := $(bootstat_src_files)

include $(BUILD_EXECUTABLE)

# Native tests
# -----------------------------------------------------------------------------

include $(CLEAR_VARS)

LOCAL_MODULE := bootstat_tests
LOCAL_CFLAGS := $(bootstat_tests_cflags)
LOCAL_CPPFLAGS := $(bootstat_cppflags)
LOCAL_SHARED_LIBRARIES := $(bootstat_shared_libs)
LOCAL_STATIC_LIBRARIES := libbootstat_debug libgmock
LOCAL_SRC_FILES := $(bootstat_test_src_files)

include $(BUILD_NATIVE_TEST)

# Host native tests
# -----------------------------------------------------------------------------

include $(CLEAR_VARS)

LOCAL_MODULE := bootstat_tests
LOCAL_CFLAGS := $(bootstat_tests_cflags)
LOCAL_CPPFLAGS := $(bootstat_cppflags)
LOCAL_SHARED_LIBRARIES := $(bootstat_shared_libs)
LOCAL_STATIC_LIBRARIES := libbootstat_host_debug libgmock_host
LOCAL_SRC_FILES := $(bootstat_test_src_files)

include $(BUILD_HOST_NATIVE_TEST)

bootstat/README.md

0 → 100644
+47 −0
Original line number Diff line number Diff line
# bootstat #

The bootstat command records boot events (e.g., `firmware_loaded`,
`boot_complete`) and the relative time at which these events occurred. The
command also aggregates boot event metrics locally and logs the metrics for
analysis.

    Usage: bootstat [options]
    options include:
      -d              Dump the boot event records to the console.
      -h              Show this help.
      -l              Log all metrics to logstorage.
      -r              Record the relative time of a named boot event.

## Relative time ##

The timestamp recorded by bootstat is the uptime of the system, i.e., the
number of seconds since the system booted.

## Recording boot events ##

To record the relative time of an event during the boot phase, call `bootstat`
with the `-r` option and the name of the boot event.

    $ bootstat -r boot_complete

The relative time at which the command runs is recorded along with the name of
the boot event to be persisted.

## Logging boot events ##

To log the persisted boot events, call `bootstat` with the `-l` option.

    $ bootstat -l

bootstat logs all boot events recorded using the `-r` option to the EventLog
using the Tron histogram. On GMS devices these logs are uploaded via Clearcut
for aggregation and analysis.

## Printing boot events ##

To print the set of persisted boot events, call `bootstat` with the `-p` option.

    $ bootstat -p
    Boot events:
    ------------
    boot_complete   71
 No newline at end of file
+116 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "boot_event_record_store.h"

#include <dirent.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <utime.h>
#include <cstdlib>
#include <base/file.h>
#include <base/logging.h>

namespace {

const char BOOTSTAT_DATA_DIR[] = "/data/misc/bootstat/";

// Given a boot even record file at |path|, extracts the event's relative time
// from the record into |uptime|.
bool ParseRecordEventTime(const std::string& path, int32_t* uptime) {
  DCHECK_NE(static_cast<int32_t*>(nullptr), uptime);

  struct stat file_stat;
  if (stat(path.c_str(), &file_stat) == -1) {
    PLOG(ERROR) << "Failed to read " << path;
    return false;
  }

  *uptime = file_stat.st_mtime;
  return true;
}

}  // namespace

BootEventRecordStore::BootEventRecordStore() {
  SetStorePath(BOOTSTAT_DATA_DIR);
}

void BootEventRecordStore::AddBootEvent(const std::string& name) {
  std::string uptime_str;
  if (!android::base::ReadFileToString("/proc/uptime", &uptime_str)) {
    LOG(ERROR) << "Failed to read /proc/uptime";
  }

  std::string record_path = GetBootEventPath(name);
  if (creat(record_path.c_str(), S_IRUSR | S_IWUSR) == -1) {
    PLOG(ERROR) << "Failed to create " << record_path;
  }

  struct stat file_stat;
  if (stat(record_path.c_str(), &file_stat) == -1) {
    PLOG(ERROR) << "Failed to read " << record_path;
  }

  // Cast intentionally rounds down.
  time_t uptime = static_cast<time_t>(strtod(uptime_str.c_str(), NULL));
  struct utimbuf times = {file_stat.st_atime, uptime};
  if (utime(record_path.c_str(), &times) == -1) {
    PLOG(ERROR) << "Failed to set mtime for " << record_path;
  }
}

std::vector<BootEventRecordStore::BootEventRecord> BootEventRecordStore::
    GetAllBootEvents() const {
  std::vector<BootEventRecord> events;

  std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(store_path_.c_str()), closedir);

  // This case could happen due to external manipulation of the filesystem,
  // so crash out if the record store doesn't exist.
  CHECK_NE(static_cast<DIR*>(nullptr), dir.get());

  struct dirent* entry;
  while ((entry = readdir(dir.get())) != NULL) {
    // Only parse regular files.
    if (entry->d_type != DT_REG) {
      continue;
    }

    const std::string event = entry->d_name;
    const std::string record_path = GetBootEventPath(event);
    int32_t uptime;
    if (!ParseRecordEventTime(record_path, &uptime)) {
      LOG(ERROR) << "Failed to parse boot time record: " << record_path;
      continue;
    }

    events.push_back(std::make_pair(event, uptime));
  }

  return events;
}

void BootEventRecordStore::SetStorePath(const std::string& path) {
  DCHECK_EQ('/', path.back());
  store_path_ = path;
}

std::string BootEventRecordStore::GetBootEventPath(
    const std::string& event) const {
  DCHECK_EQ('/', store_path_.back());
  return store_path_ + event;
}
+61 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef BOOT_EVENT_RECORD_STORE_H_
#define BOOT_EVENT_RECORD_STORE_H_

#include <cstdint>
#include <string>
#include <utility>
#include <vector>
#include <base/macros.h>
#include <gtest/gtest_prod.h>

// BootEventRecordStore manages the persistence of boot events to the record
// store and the retrieval of all boot event records from the store.
class BootEventRecordStore {
 public:
  // A BootEventRecord consists of the event name and the timestamp the event
  // occurred.
  typedef std::pair<std::string, int32_t> BootEventRecord;

  BootEventRecordStore();

  // Persists the boot event named |name| in the record store.
  void AddBootEvent(const std::string& name);

  // Returns a list of all of the boot events persisted in the record store.
  std::vector<BootEventRecord> GetAllBootEvents() const;

 private:
  // The tests call SetStorePath to override the default store location with a
  // more test-friendly path.
  FRIEND_TEST(BootEventRecordStoreTest, AddSingleBootEvent);
  FRIEND_TEST(BootEventRecordStoreTest, AddMultipleBootEvents);

  // Sets the filesystem path of the record store.
  void SetStorePath(const std::string& path);

  // Constructs the full path of the given boot |event|.
  std::string GetBootEventPath(const std::string& event) const;

  // The filesystem path of the record store.
  std::string store_path_;

  DISALLOW_COPY_AND_ASSIGN(BootEventRecordStore);
};

#endif  // BOOT_EVENT_RECORD_STORE_H_
 No newline at end of file
+156 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "boot_event_record_store.h"

#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include <cstdint>
#include <cstdlib>
#include <base/file.h>
#include <base/test_utils.h>
#include <gtest/gtest.h>
#include <gmock/gmock.h>

using testing::UnorderedElementsAreArray;

namespace {

// Returns true if the time difference between |a| and |b| is no larger
// than 10 seconds.  This allow for a relatively large fuzz when comparing
// two timestamps taken back-to-back.
bool FuzzUptimeEquals(int32_t a, int32_t b) {
  const int32_t FUZZ_SECONDS = 10;
  return (abs(a - b) <= FUZZ_SECONDS);
}

// Returns the uptime as read from /proc/uptime, rounded down to an integer.
int32_t ReadUptime() {
  std::string uptime_str;
  if (!android::base::ReadFileToString("/proc/uptime", &uptime_str)) {
    return -1;
  }

  // Cast to int to round down.
  return static_cast<int32_t>(strtod(uptime_str.c_str(), NULL));
}

// Recursively deletes the directory at |path|.
void DeleteDirectory(const std::string& path) {
  typedef std::unique_ptr<DIR, decltype(&closedir)> ScopedDIR;
  ScopedDIR dir(opendir(path.c_str()), closedir);
  ASSERT_NE(nullptr, dir.get());

  struct dirent* entry;
  while ((entry = readdir(dir.get())) != NULL) {
    const std::string entry_name(entry->d_name);
    if (entry_name == "." || entry_name == "..") {
      continue;
    }

    const std::string entry_path = path + "/" + entry_name;
    if (entry->d_type == DT_DIR) {
      DeleteDirectory(entry_path);
    }  else {
      unlink(entry_path.c_str());
    }
  }

  rmdir(path.c_str());
}

class BootEventRecordStoreTest : public ::testing::Test {
 public:
  BootEventRecordStoreTest() {
    store_path_ = std::string(store_dir_.path) + "/";
  }

  const std::string& GetStorePathForTesting() const {
    return store_path_;
  }

 private:
  void TearDown() {
    // This removes the record store temporary directory even though
    // TemporaryDir should already take care of it, but this method cleans up
    // the test files added to the directory which prevent TemporaryDir from
    // being able to remove the directory.
    DeleteDirectory(store_path_);
  }

  // A scoped temporary directory. Using this abstraction provides creation of
  // the directory and the path to the directory, which is stored in
  // |store_path_|.
  TemporaryDir store_dir_;

  // The path to the temporary directory used by the BootEventRecordStore to
  // persist records.  The directory is created and destroyed for each test.
  std::string store_path_;

  DISALLOW_COPY_AND_ASSIGN(BootEventRecordStoreTest);
};

}  // namespace

TEST_F(BootEventRecordStoreTest, AddSingleBootEvent) {
  BootEventRecordStore store;
  store.SetStorePath(GetStorePathForTesting());

  int32_t uptime = ReadUptime();
  ASSERT_NE(-1, uptime);

  store.AddBootEvent("cenozoic");

  auto events = store.GetAllBootEvents();
  ASSERT_EQ(1U, events.size());
  EXPECT_EQ("cenozoic", events[0].first);
  EXPECT_TRUE(FuzzUptimeEquals(uptime, events[0].second));
}

TEST_F(BootEventRecordStoreTest, AddMultipleBootEvents) {
  BootEventRecordStore store;
  store.SetStorePath(GetStorePathForTesting());

  int32_t uptime = ReadUptime();
  ASSERT_NE(-1, uptime);

  store.AddBootEvent("cretaceous");
  store.AddBootEvent("jurassic");
  store.AddBootEvent("triassic");

  const std::string EXPECTED_NAMES[] = {
    "cretaceous",
    "jurassic",
    "triassic",
  };

  auto events = store.GetAllBootEvents();
  ASSERT_EQ(3U, events.size());

  std::vector<std::string> names;
  std::vector<int32_t> timestamps;
  for (auto i = events.begin(); i != events.end(); ++i) {
    names.push_back(i->first);
    timestamps.push_back(i->second);
  }

  EXPECT_THAT(names, UnorderedElementsAreArray(EXPECTED_NAMES));

  for (auto i = timestamps.cbegin(); i != timestamps.cend(); ++i) {
    EXPECT_TRUE(FuzzUptimeEquals(uptime, *i));
  }
}
Loading