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

Commit f05a8c49 authored by Hansong Zhang's avatar Hansong Zhang
Browse files

Add Timer as an alternative to osi alarm

* Add a private method MessageLoopThread.DoInThreadDelayed to post a
  delayed task in message loop, as an alternative approach to osi alarm
  clock
* Add a unit test for MessageLoopThread to check ShutDown() waits until
  current task finishes
* Add Timer using MessageLoopThread.DoInThreadDelayed
* Timer provides similar API as osi alarm, and uses same OS clock (boot
  timer) as alarm
* Add benchmark and unit tests to ensure the performance is comparable
  to the existing osi alarm

Test: Run unit test and benchmark test
      ./test/run_unit_tests.sh bluetooth_test_common
      ./test/run_benchmarks.sh bluetooth_benchmark_timer_performance
      --benchmark_repetitions=10 --benchmark_report_aggregates_only=true
Bug: 110303473
Change-Id: I6f2e7ae2f80f9889fc5fe3c8cd6b9b2670938b46
parent 76250727
Loading
Loading
Loading
Loading
+21 −0
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@ cc_library_static {
        "packages/modules/Bluetooth/system/stack/include",
    ],
    srcs: [
        "timer.cc",
        "message_loop_thread.cc",
        "metrics.cc",
        "time_util.cc",
@@ -26,6 +27,7 @@ cc_test {
        "packages/modules/Bluetooth/system/stack/include",
    ],
    srcs : [
        "timer_unittest.cc",
        "leaky_bonded_queue_unittest.cc",
        "message_loop_thread_unittest.cc",
        "metrics_unittest.cc",
@@ -77,3 +79,22 @@ cc_benchmark {
        "libbt-common"
    ],
}

cc_benchmark {
    name: "bluetooth_benchmark_timer_performance",
    defaults: ["fluoride_defaults"],
    include_dirs: ["packages/modules/Bluetooth/system"],
    srcs: [
        "benchmark/timer_performance_benchmark.cc",
    ],
    shared_libs: [
        "liblog",
        "libprotobuf-cpp-lite",
        "libcutils",
    ],
    static_libs: [
        "libosi",
        "libbt-common",
        "libbt-protos-lite",
    ],
}
+271 −0
Original line number Diff line number Diff line
/*
 * Copyright 2018 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 <base/bind.h>
#include <base/run_loop.h>
#include <base/threading/thread.h>
#include <benchmark/benchmark.h>
#include <future>

#include "common/message_loop_thread.h"
#include "common/time_util.h"
#include "common/timer.h"
#include "osi/include/alarm.h"

using ::benchmark::State;
using bluetooth::common::MessageLoopThread;
using bluetooth::common::time_get_os_boottime_us;
using bluetooth::common::Timer;

// fake get_main_message_loop implementation for alarm
base::MessageLoop* get_main_message_loop() { return nullptr; }

namespace {
std::unordered_map<int, int> g_map;
std::shared_ptr<std::promise<void>> g_promise;
uint64_t g_start_time;
int g_scheduled_tasks;
int g_task_length;
int g_task_interval;
int g_task_counter;

void TimerFire(void*) { g_promise->set_value(); }

void AlarmSleepAndCountDelayedTime(void*) {
  auto end_time_us = time_get_os_boottime_us();
  auto time_after_start_ms = (end_time_us - g_start_time) / 1000;
  g_task_counter++;
  g_map[time_after_start_ms - g_task_counter * g_task_interval]++;
  std::this_thread::sleep_for(std::chrono::milliseconds(g_task_length));
  if (g_task_counter >= g_scheduled_tasks) {
    g_promise->set_value();
  }
}

}  // namespace

class BM_OsiAlarmTimer : public ::benchmark::Fixture {
 protected:
  void SetUp(State& st) override {
    ::benchmark::Fixture::SetUp(st);
    alarm_ = alarm_new("osi_alarm_timer_test");
    g_promise = std::make_shared<std::promise<void>>();
  }

  void TearDown(State& st) override {
    g_promise = nullptr;
    alarm_free(alarm_);
    ::benchmark::Fixture::TearDown(st);
  }

  alarm_t* alarm_ = nullptr;
};

BENCHMARK_DEFINE_F(BM_OsiAlarmTimer, timer_performance_ms)(State& state) {
  auto milliseconds = static_cast<int>(state.range(0));
  for (auto _ : state) {
    auto start_time_point = time_get_os_boottime_us();
    alarm_set(alarm_, milliseconds, &TimerFire, nullptr);
    g_promise->get_future().get();
    auto end_time_point = time_get_os_boottime_us();
    auto duration = end_time_point - start_time_point;
    state.SetIterationTime(duration * 1e-6);
  }
};

BENCHMARK_REGISTER_F(BM_OsiAlarmTimer, timer_performance_ms)
    ->Arg(1)
    ->Arg(5)
    ->Arg(10)
    ->Arg(20)
    ->Arg(100)
    ->Arg(1000)
    ->Arg(2000)
    ->Iterations(1)
    ->UseManualTime();

class BM_AlarmTaskTimer : public ::benchmark::Fixture {
 protected:
  void SetUp(State& st) override {
    ::benchmark::Fixture::SetUp(st);
    message_loop_thread_ = new MessageLoopThread("timer_benchmark");
    message_loop_thread_->StartUp();
    message_loop_thread_->EnableRealTimeScheduling();
    timer_ = new Timer();
    g_promise = std::make_shared<std::promise<void>>();
  }

  void TearDown(State& st) override {
    g_promise = nullptr;
    delete timer_;
    timer_ = nullptr;
    message_loop_thread_->ShutDown();
    delete message_loop_thread_;
    message_loop_thread_ = nullptr;
    ::benchmark::Fixture::TearDown(st);
  }

  MessageLoopThread* message_loop_thread_;
  Timer* timer_;
};

BENCHMARK_DEFINE_F(BM_AlarmTaskTimer, timer_performance_ms)(State& state) {
  auto milliseconds = static_cast<int>(state.range(0));
  for (auto _ : state) {
    auto start_time_point = time_get_os_boottime_us();
    timer_->Schedule(message_loop_thread_->GetWeakPtr(), FROM_HERE,
                     base::Bind(&TimerFire, nullptr),
                     base::TimeDelta::FromMilliseconds(milliseconds));
    g_promise->get_future().get();
    timer_->Cancel();
    auto end_time_point = time_get_os_boottime_us();
    auto duration = end_time_point - start_time_point;
    state.SetIterationTime(duration * 1e-6);
  }
};

BENCHMARK_REGISTER_F(BM_AlarmTaskTimer, timer_performance_ms)
    ->Arg(1)
    ->Arg(5)
    ->Arg(10)
    ->Arg(20)
    ->Arg(100)
    ->Arg(1000)
    ->Arg(2000)
    ->Iterations(1)
    ->UseManualTime();

class BM_OsiPeriodicAlarmTimer : public ::benchmark::Fixture {
 protected:
  void SetUp(State& st) override {
    ::benchmark::Fixture::SetUp(st);
    alarm_ = alarm_new_periodic("osi_alarm_timer_test");
    g_map.clear();
    g_promise = std::make_shared<std::promise<void>>();
    g_scheduled_tasks = 0;
    g_task_length = 0;
    g_task_interval = 0;
    g_task_counter = 0;
  }

  void TearDown(State& st) override {
    g_promise = nullptr;
    alarm_free(alarm_);
    ::benchmark::Fixture::TearDown(st);
  }

  alarm_t* alarm_ = nullptr;
};

BENCHMARK_DEFINE_F(BM_OsiPeriodicAlarmTimer, periodic_accuracy)(State& state) {
  for (auto _ : state) {
    g_scheduled_tasks = state.range(0);
    g_task_length = state.range(1);
    g_task_interval = state.range(2);
    g_start_time = time_get_os_boottime_us();
    alarm_set(alarm_, g_task_interval, &AlarmSleepAndCountDelayedTime, nullptr);
    g_promise->get_future().get();
    alarm_cancel(alarm_);
  }
  for (const auto& delay : g_map) {
    state.counters[std::to_string(delay.first)] = delay.second;
  }
};

BENCHMARK_REGISTER_F(BM_OsiPeriodicAlarmTimer, periodic_accuracy)
    ->Args({2000, 1, 5})
    ->Args({2000, 3, 5})
    ->Args({2000, 1, 7})
    ->Args({2000, 3, 7})
    ->Args({2000, 1, 20})
    ->Args({2000, 5, 20})
    ->Args({2000, 10, 20})
    ->Args({2000, 15, 20})
    ->Iterations(1)
    ->UseRealTime();

class BM_AlarmTaskPeriodicTimer : public ::benchmark::Fixture {
 protected:
  void SetUp(State& st) override {
    ::benchmark::Fixture::SetUp(st);
    message_loop_thread_ = new MessageLoopThread("timer_benchmark");
    message_loop_thread_->StartUp();
    message_loop_thread_->EnableRealTimeScheduling();
    timer_ = new Timer();
    g_map.clear();
    g_promise = std::make_shared<std::promise<void>>();
    g_scheduled_tasks = 0;
    g_task_length = 0;
    g_task_interval = 0;
    g_task_counter = 0;
  }

  void TearDown(State& st) override {
    g_promise = nullptr;
    delete timer_;
    timer_ = nullptr;
    message_loop_thread_->ShutDown();
    delete message_loop_thread_;
    message_loop_thread_ = nullptr;
    ::benchmark::Fixture::TearDown(st);
  }

  MessageLoopThread* message_loop_thread_;
  Timer* timer_;
};

BENCHMARK_DEFINE_F(BM_AlarmTaskPeriodicTimer, periodic_accuracy)
(State& state) {
  for (auto _ : state) {
    g_scheduled_tasks = state.range(0);
    g_task_length = state.range(1);
    g_task_interval = state.range(2);
    g_start_time = time_get_os_boottime_us();
    timer_->SchedulePeriodic(
        message_loop_thread_->GetWeakPtr(), FROM_HERE,
        base::Bind(&AlarmSleepAndCountDelayedTime, nullptr),
        base::TimeDelta::FromMilliseconds(g_task_interval));
    g_promise->get_future().get();
    timer_->Cancel();
  }
  for (const auto& delay : g_map) {
    state.counters[std::to_string(delay.first)] = delay.second;
  }
};

BENCHMARK_REGISTER_F(BM_AlarmTaskPeriodicTimer, periodic_accuracy)
    ->Args({2000, 1, 5})
    ->Args({2000, 3, 5})
    ->Args({2000, 1, 7})
    ->Args({2000, 3, 7})
    ->Args({2000, 1, 20})
    ->Args({2000, 5, 20})
    ->Args({2000, 10, 20})
    ->Args({2000, 15, 20})
    ->Iterations(1)
    ->UseRealTime();

int main(int argc, char** argv) {
  // Disable LOG() output from libchrome
  logging::LoggingSettings log_settings;
  log_settings.logging_dest = logging::LoggingDestination::LOG_NONE;
  CHECK(logging::InitLogging(log_settings)) << "Failed to set up logging";
  ::benchmark::Initialize(&argc, argv);
  if (::benchmark::ReportUnrecognizedArguments(argc, argv)) {
    return 1;
  }
  ::benchmark::RunSpecifiedBenchmarks();
}
+15 −2
Original line number Diff line number Diff line
@@ -34,7 +34,8 @@ MessageLoopThread::MessageLoopThread(const std::string& thread_name)
      run_loop_(nullptr),
      thread_(nullptr),
      thread_id_(-1),
      linux_tid_(-1) {}
      linux_tid_(-1),
      weak_ptr_factory_(this) {}

MessageLoopThread::~MessageLoopThread() {
  std::lock_guard<std::recursive_mutex> api_lock(api_mutex_);
@@ -58,13 +59,20 @@ void MessageLoopThread::StartUp() {

bool MessageLoopThread::DoInThread(const tracked_objects::Location& from_here,
                                   base::OnceClosure task) {
  return DoInThreadDelayed(from_here, std::move(task), base::TimeDelta());
}

bool MessageLoopThread::DoInThreadDelayed(
    const tracked_objects::Location& from_here, base::OnceClosure task,
    const base::TimeDelta& delay) {
  std::lock_guard<std::recursive_mutex> api_lock(api_mutex_);
  if (message_loop_ == nullptr) {
    LOG(ERROR) << __func__ << ": message loop is null for thread " << *this
               << ", from " << from_here.ToString();
    return false;
  }
  if (!message_loop_->task_runner()->PostTask(from_here, std::move(task))) {
  if (!message_loop_->task_runner()->PostDelayedTask(from_here, std::move(task),
                                                     delay)) {
    LOG(ERROR) << __func__
               << ": failed to post task to message loop for thread " << *this
               << ", from " << from_here.ToString();
@@ -145,6 +153,11 @@ bool MessageLoopThread::EnableRealTimeScheduling() {
  return true;
}

base::WeakPtr<MessageLoopThread> MessageLoopThread::GetWeakPtr() {
  std::lock_guard<std::recursive_mutex> api_lock(api_mutex_);
  return weak_ptr_factory_.GetWeakPtr();
}

// Non API method, should NOT be protected by API mutex to avoid deadlock
void MessageLoopThread::Run(std::promise<void> start_up_promise) {
  LOG(INFO) << __func__ << ": message loop starting for thread "
+38 −1
Original line number Diff line number Diff line
@@ -122,7 +122,13 @@ class MessageLoopThread final {
  bool EnableRealTimeScheduling();

  /**
   * Return the mssage loop for this thread. Accessing raw message loop is not
   * Return the weak pointer to this object. This can be useful when posting
   * delayed tasks to this MessageLoopThread using Timer.
   */
  base::WeakPtr<MessageLoopThread> GetWeakPtr();

  /**
   * Return the message loop for this thread. Accessing raw message loop is not
   * recommended as message loop can be freed internally.
   *
   * @return message loop associated with this thread, nullptr if thread is not
@@ -143,6 +149,36 @@ class MessageLoopThread final {
  static void RunThread(MessageLoopThread* context,
                        std::promise<void> start_up_promise);

  /**
   * Post a task to run on this thread after a specified delay. If the task
   * needs to be cancelable before it's run, use base::CancelableClosure type
   * for task closure. For example:
   * <code>
   * base::CancelableClosure cancelable_task;
   * cancelable_task.Reset(base::Bind(...)); // bind the task
   * same_thread->DoInThreadDelayed(FROM_HERE,
   *                                cancelable_task.callback(), delay);
   * ...
   * // Cancel the task closure
   * same_thread->DoInThread(FROM_HERE,
   *                         base::Bind(&base::CancelableClosure::Cancel,
   *                                    base::Unretained(&cancelable_task)));
   * </code>
   *
   * Warning: base::CancelableClosure objects must be created on, posted to,
   * cancelled on, and destroyed on the same thread.
   *
   * @param from_here location where this task is originated
   * @param task task created through base::Bind()
   * @param delay delay for the task to be executed
   * @return true if task is successfully scheduled, false if task cannot be
   * scheduled
   */
  bool DoInThreadDelayed(const tracked_objects::Location& from_here,
                         base::OnceClosure task, const base::TimeDelta& delay);

  friend class Timer;  // allow Timer to use DoInThreadDelayed()

  /**
   * Actual method to run the thread, blocking until ShutDown() is called
   *
@@ -159,6 +195,7 @@ class MessageLoopThread final {
  base::PlatformThreadId thread_id_;
  // Linux specific abstractions
  pid_t linux_tid_;
  base::WeakPtrFactory<MessageLoopThread> weak_ptr_factory_;

  DISALLOW_COPY_AND_ASSIGN(MessageLoopThread);
};
+32 −0
Original line number Diff line number Diff line
@@ -61,6 +61,11 @@ class MessageLoopThreadTest : public ::testing::Test {
    execution_promise.set_value();
  }

  void SleepAndGetName(std::promise<std::string> name_promise, int sleep_ms) {
    std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms));
    GetName(std::move(name_promise));
  }

 protected:
  static bool CanSetCurrentThreadPriority() {
    struct __user_cap_header_struct linux_user_header = {
@@ -78,6 +83,16 @@ class MessageLoopThreadTest : public ::testing::Test {
  }
};

TEST_F(MessageLoopThreadTest, get_weak_ptr) {
  base::WeakPtr<MessageLoopThread> message_loop_thread_ptr;
  {
    MessageLoopThread message_loop_thread("test_thread");
    message_loop_thread_ptr = message_loop_thread.GetWeakPtr();
    ASSERT_NE(message_loop_thread_ptr, nullptr);
  }
  ASSERT_EQ(message_loop_thread_ptr, nullptr);
}

TEST_F(MessageLoopThreadTest, test_running_thread) {
  MessageLoopThread message_loop_thread("test_thread");
  message_loop_thread.StartUp();
@@ -248,3 +263,20 @@ TEST_F(MessageLoopThreadTest, test_to_string_method) {
  ASSERT_STREQ(thread_string_after_shutdown.c_str(),
               thread_string_before_start.c_str());
}

// Verify the message loop thread will shutdown after callback finishes
TEST_F(MessageLoopThreadTest, shut_down_while_in_callback) {
  std::string name = "test_thread";
  MessageLoopThread message_loop_thread(name);
  message_loop_thread.StartUp();
  std::promise<std::string> name_promise;
  std::future<std::string> name_future = name_promise.get_future();
  uint32_t delay_ms = 5;
  message_loop_thread.DoInThread(
      FROM_HERE, base::BindOnce(&MessageLoopThreadTest::SleepAndGetName,
                                base::Unretained(this), std::move(name_promise),
                                delay_ms));
  message_loop_thread.ShutDown();
  std::string my_name = name_future.get();
  ASSERT_EQ(name, my_name);
}
Loading