<commit_before>#pragma once

#include "boost_defs.hpp"

#include "gcd_utility.hpp"
#include "human_interface_device.hpp"
#include "logger.hpp"
#include "types.hpp"
#include <IOKit/hid/IOHIDManager.h>
#include <boost/signals2.hpp>
#include <unordered_map>

namespace krbn {
class hid_manager final {
public:
  boost::signals2::signal<void(human_interface_device&)> device_detected;
  boost::signals2::signal<void(human_interface_device&)> device_removed;

  hid_manager(const hid_manager&) = delete;

  hid_manager(void) : manager_(nullptr) {
  }

  ~hid_manager(void) {
    stop();
  }

  void start(const std::vector<std::pair<hid_usage_page, hid_usage>>& usage_pairs) {
    if (manager_) {
      stop();
    }

    manager_ = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
    if (!manager_) {
      logger::get_logger().error("{0}: failed to IOHIDManagerCreate", __PRETTY_FUNCTION__);
      return;
    }

    if (auto device_matching_dictionaries = iokit_utility::create_device_matching_dictionaries(usage_pairs)) {
      IOHIDManagerSetDeviceMatchingMultiple(manager_, device_matching_dictionaries);
      CFRelease(device_matching_dictionaries);
    }

    IOHIDManagerRegisterDeviceMatchingCallback(manager_, static_device_matching_callback, this);
    IOHIDManagerRegisterDeviceRemovalCallback(manager_, static_device_removal_callback, this);

    IOHIDManagerScheduleWithRunLoop(manager_, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
  }

  void stop(void) {
    // Release manager_ in main thread to avoid callback invocations after object has been destroyed.
    gcd_utility::dispatch_sync_in_main_queue(^{
      if (manager_) {
        IOHIDManagerUnscheduleFromRunLoop(manager_, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
        CFRelease(manager_);
        manager_ = nullptr;
      }
    });
  }

private:
  static void static_device_matching_callback(void* _Nullable context,
                                              IOReturn result,
                                              void* _Nullable sender,
                                              IOHIDDeviceRef _Nonnull device) {
    if (result != kIOReturnSuccess) {
      return;
    }

    auto self = static_cast<hid_manager*>(context);
    if (!self) {
      return;
    }

    self->device_matching_callback(device);
  }

  void device_matching_callback(IOHIDDeviceRef _Nonnull device) {
    if (!device) {
      return;
    }

    iokit_utility::log_matching_device(device);

    if (auto registry_entry_id = iokit_utility::find_registry_entry_id(device)) {
      // iokit_utility::find_registry_entry_id will be failed in device_removal_callback at least on macOS 10.13.
      // Thus, we have to memory device and registry_entry_id correspondence by myself.
      registry_entry_ids_[device] = *registry_entry_id;

      // Skip if same device is already matched.
      // (Multiple usage device (e.g. usage::pointer and usage::mouse) will be matched twice.)
      auto it = hids_.find(*registry_entry_id);
      if (it != std::end(hids_)) {
        logger::get_logger().info("registry_entry_id:{0} already exists.", static_cast<uint64_t>(*registry_entry_id));
        return;
      }

      auto hid = std::make_shared<human_interface_device>(device);
      hids_[*registry_entry_id] = hid;

      logger::get_logger().info("{0} is detected.", hid->get_name_for_log());

      device_detected(*hid);
    }
  }

  static void static_device_removal_callback(void* _Nullable context,
                                             IOReturn result,
                                             void* _Nullable sender,
                                             IOHIDDeviceRef _Nonnull device) {
    if (result != kIOReturnSuccess) {
      return;
    }

    auto self = static_cast<hid_manager*>(context);
    if (!self) {
      return;
    }

    self->device_removal_callback(device);
  }

  void device_removal_callback(IOHIDDeviceRef _Nonnull device) {
    if (!device) {
      return;
    }

    iokit_utility::log_removal_device(device);

    // ----------------------------------------

    if (auto registry_entry_id = find_registry_entry_id(device)) {
      auto it = hids_.find(*registry_entry_id);
      if (it != hids_.end()) {
        if (auto hid = it->second) {
          logger::get_logger().info("{0} is removed.", hid->get_name_for_log());

          hid->set_removed();
          device_removed(*hid);
          hids_.erase(it);
        }
      }

      // There might be multiple devices for one registry_entry_id.
      // (For example, keyboard and mouse combined device.)
      // And there is possibility that removal callback is not called with some device.
      //
      // For example, there are 3 devices for 1 registry_entry_id (4294974284).
      //   - device1 { device:0x7fb9d64078a0 => registry_entry_id:4294974284 }
      //   - device2 { device:0x7fb9d8301390 => registry_entry_id:4294974284 }
      //   - device3 { device:0x7fb9d830a630 => registry_entry_id:4294974284 }
      // And device_removal_callback are called only with device1 and device2.
      //
      // We should remove device1, device2 and device3 at the same time in order to deal with this case.

      for (auto it = std::begin(registry_entry_ids_); it != std::end(registry_entry_ids_);) {
        if (it->second == *registry_entry_id) {
          it = registry_entry_ids_.erase(it);
        } else
          std::advance(it, 1);
      }
    }
  }

  boost::optional<registry_entry_id> find_registry_entry_id(IOHIDDeviceRef _Nonnull device) {
    auto it = registry_entry_ids_.find(device);
    if (it != std::end(registry_entry_ids_)) {
      return it->second;
    }
    return boost::none;
  }

  IOHIDManagerRef _Nullable manager_;
  std::unordered_map<IOHIDDeviceRef, registry_entry_id> registry_entry_ids_;
  std::unordered_map<registry_entry_id, std::shared_ptr<human_interface_device>> hids_;
};
} // namespace krbn
<commit_msg>add hid_manager::device_detecting<commit_after>#pragma once

#include "boost_utility.hpp"
#include "gcd_utility.hpp"
#include "human_interface_device.hpp"
#include "logger.hpp"
#include "types.hpp"
#include <IOKit/hid/IOHIDManager.h>
#include <unordered_map>

namespace krbn {
class hid_manager final {
public:
  // Return false to ignore device.
  boost::signals2::signal<bool(IOHIDDeviceRef _Nonnull),
                          boost_utility::signal2_combiner_call_while_true>
      device_detecting;

  boost::signals2::signal<void(human_interface_device&)> device_detected;

  boost::signals2::signal<void(human_interface_device&)> device_removed;

  hid_manager(const hid_manager&) = delete;

  hid_manager(void) : manager_(nullptr) {
  }

  ~hid_manager(void) {
    stop();
  }

  const std::unordered_map<registry_entry_id, std::shared_ptr<human_interface_device>>& get_hids(void) const {
    return hids_;
  }

  void start(const std::vector<std::pair<hid_usage_page, hid_usage>>& usage_pairs) {
    if (manager_) {
      stop();
    }

    manager_ = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
    if (!manager_) {
      logger::get_logger().error("{0}: failed to IOHIDManagerCreate", __PRETTY_FUNCTION__);
      return;
    }

    if (auto device_matching_dictionaries = iokit_utility::create_device_matching_dictionaries(usage_pairs)) {
      IOHIDManagerSetDeviceMatchingMultiple(manager_, device_matching_dictionaries);
      CFRelease(device_matching_dictionaries);
    }

    IOHIDManagerRegisterDeviceMatchingCallback(manager_, static_device_matching_callback, this);
    IOHIDManagerRegisterDeviceRemovalCallback(manager_, static_device_removal_callback, this);

    IOHIDManagerScheduleWithRunLoop(manager_, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
  }

  void stop(void) {
    // Release manager_ in main thread to avoid callback invocations after object has been destroyed.
    gcd_utility::dispatch_sync_in_main_queue(^{
      if (manager_) {
        IOHIDManagerUnscheduleFromRunLoop(manager_, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
        CFRelease(manager_);
        manager_ = nullptr;
      }
    });
  }

private:
  static void static_device_matching_callback(void* _Nullable context,
                                              IOReturn result,
                                              void* _Nullable sender,
                                              IOHIDDeviceRef _Nonnull device) {
    if (result != kIOReturnSuccess) {
      return;
    }

    auto self = static_cast<hid_manager*>(context);
    if (!self) {
      return;
    }

    self->device_matching_callback(device);
  }

  void device_matching_callback(IOHIDDeviceRef _Nonnull device) {
    if (!device) {
      return;
    }

    if (!device_detecting(device)) {
      return;
    }

    iokit_utility::log_matching_device(device);

    if (auto registry_entry_id = iokit_utility::find_registry_entry_id(device)) {
      // iokit_utility::find_registry_entry_id will be failed in device_removal_callback at least on macOS 10.13.
      // Thus, we have to memory device and registry_entry_id correspondence by myself.
      registry_entry_ids_[device] = *registry_entry_id;

      // Skip if same device is already matched.
      // (Multiple usage device (e.g. usage::pointer and usage::mouse) will be matched twice.)
      auto it = hids_.find(*registry_entry_id);
      if (it != std::end(hids_)) {
        logger::get_logger().info("registry_entry_id:{0} already exists.", static_cast<uint64_t>(*registry_entry_id));
        return;
      }

      auto hid = std::make_shared<human_interface_device>(device);
      hids_[*registry_entry_id] = hid;

      logger::get_logger().info("{0} is detected.", hid->get_name_for_log());

      device_detected(*hid);
    }
  }

  static void static_device_removal_callback(void* _Nullable context,
                                             IOReturn result,
                                             void* _Nullable sender,
                                             IOHIDDeviceRef _Nonnull device) {
    if (result != kIOReturnSuccess) {
      return;
    }

    auto self = static_cast<hid_manager*>(context);
    if (!self) {
      return;
    }

    self->device_removal_callback(device);
  }

  void device_removal_callback(IOHIDDeviceRef _Nonnull device) {
    if (!device) {
      return;
    }

    iokit_utility::log_removal_device(device);

    // ----------------------------------------

    if (auto registry_entry_id = find_registry_entry_id(device)) {
      auto it = hids_.find(*registry_entry_id);
      if (it != hids_.end()) {
        if (auto hid = it->second) {
          logger::get_logger().info("{0} is removed.", hid->get_name_for_log());

          hids_.erase(it);

          hid->set_removed();
          device_removed(*hid);
        }
      }

      // There might be multiple devices for one registry_entry_id.
      // (For example, keyboard and mouse combined device.)
      // And there is possibility that removal callback is not called with some device.
      //
      // For example, there are 3 devices for 1 registry_entry_id (4294974284).
      //   - device1 { device:0x7fb9d64078a0 => registry_entry_id:4294974284 }
      //   - device2 { device:0x7fb9d8301390 => registry_entry_id:4294974284 }
      //   - device3 { device:0x7fb9d830a630 => registry_entry_id:4294974284 }
      // And device_removal_callback are called only with device1 and device2.
      //
      // We should remove device1, device2 and device3 at the same time in order to deal with this case.

      for (auto it = std::begin(registry_entry_ids_); it != std::end(registry_entry_ids_);) {
        if (it->second == *registry_entry_id) {
          it = registry_entry_ids_.erase(it);
        } else
          std::advance(it, 1);
      }
    }
  }

  boost::optional<registry_entry_id> find_registry_entry_id(IOHIDDeviceRef _Nonnull device) {
    auto it = registry_entry_ids_.find(device);
    if (it != std::end(registry_entry_ids_)) {
      return it->second;
    }
    return boost::none;
  }

  IOHIDManagerRef _Nullable manager_;
  std::unordered_map<IOHIDDeviceRef, registry_entry_id> registry_entry_ids_;
  std::unordered_map<registry_entry_id, std::shared_ptr<human_interface_device>> hids_;
};
} // namespace krbn
<|endoftext|>


<commit_before>#pragma once

#include "boost_utility.hpp"
#include "cf_utility.hpp"
#include "device_detail.hpp"
#include "human_interface_device.hpp"
#include "logger.hpp"
#include "types.hpp"
#include <IOKit/hid/IOHIDManager.h>
#include <unordered_map>

namespace krbn {
class hid_manager final {
public:
  // Return false to ignore device.
  boost::signals2::signal<bool(IOHIDDeviceRef _Nonnull),
                          boost_utility::signals2_combiner_call_while_true>
      device_detecting;

  boost::signals2::signal<void(std::shared_ptr<human_interface_device>)> device_detected;

  boost::signals2::signal<void(std::shared_ptr<human_interface_device>)>
      device_removed;

  hid_manager(const hid_manager&) = delete;

  hid_manager(const std::vector<std::pair<hid_usage_page, hid_usage>>& usage_pairs) : usage_pairs_(usage_pairs),
                                                                                manager_(nullptr) {
    run_loop_thread_ = std::make_shared<cf_utility::run_loop_thread>();
  }

  ~hid_manager(void) {
    stop();

    run_loop_thread_ = nullptr;
  }

  void start(void) {
    run_loop_thread_->enqueue(^{
      if (manager_) {
        return;
      }

      manager_ = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
      if (!manager_) {
        logger::get_logger().error("{0}: failed to IOHIDManagerCreate", __PRETTY_FUNCTION__);
        return;
      }

      if (auto device_matching_dictionaries = iokit_utility::create_device_matching_dictionaries(usage_pairs_)) {
        IOHIDManagerSetDeviceMatchingMultiple(manager_, device_matching_dictionaries);
        CFRelease(device_matching_dictionaries);
      }

      IOHIDManagerRegisterDeviceMatchingCallback(manager_, static_device_matching_callback, this);
      IOHIDManagerRegisterDeviceRemovalCallback(manager_, static_device_removal_callback, this);

      IOHIDManagerScheduleWithRunLoop(manager_,
                                      run_loop_thread_->get_run_loop(),
                                      kCFRunLoopDefaultMode);

      refresh_timer_ = std::make_unique<thread_utility::timer>(
          std::chrono::milliseconds(5000),
          true,
          [this] {
            run_loop_thread_->enqueue(^{
              refresh_if_needed();
            });
          });

      logger::get_logger().info("hid_manager is started.");
    });
  }

  void stop(void) {
    run_loop_thread_->enqueue(^{
      if (!manager_) {
        return;
      }

      // refresh_timer_

      refresh_timer_ = nullptr;

      // manager_

      if (manager_) {
        IOHIDManagerUnscheduleFromRunLoop(manager_,
                                          run_loop_thread_->get_run_loop(),
                                          kCFRunLoopDefaultMode);
        CFRelease(manager_);
        manager_ = nullptr;
      }

      // Other variables

      registry_entry_ids_.clear();

      {
        std::lock_guard<std::mutex> lock(hids_mutex_);

        hids_.clear();
      }

      logger::get_logger().info("hid_manager is stopped.");
    });
  }

  std::vector<std::shared_ptr<human_interface_device>> copy_hids(void) const {
    std::lock_guard<std::mutex> lock(hids_mutex_);

    return hids_;
  }

private:
  static void static_device_matching_callback(void* _Nullable context,
                                              IOReturn result,
                                              void* _Nullable sender,
                                              IOHIDDeviceRef _Nonnull device) {
    if (result != kIOReturnSuccess) {
      return;
    }

    auto self = static_cast<hid_manager*>(context);
    if (!self) {
      return;
    }

    self->device_matching_callback(device);
  }

  void device_matching_callback(IOHIDDeviceRef _Nonnull device) {
    if (!device) {
      return;
    }

    if (!device_detecting(device)) {
      return;
    }

    if (auto registry_entry_id = iokit_utility::find_registry_entry_id(device)) {
      // iokit_utility::find_registry_entry_id will be failed in device_removal_callback at least on macOS 10.13.
      // Thus, we have to memory device and registry_entry_id correspondence by myself.
      registry_entry_ids_[device] = *registry_entry_id;

      // Skip if same device is already matched.
      // (Multiple usage device (e.g. usage::pointer and usage::mouse) will be matched twice.)

      std::shared_ptr<human_interface_device> hid;

      {
        std::lock_guard<std::mutex> lock(hids_mutex_);

        if (std::any_of(std::begin(hids_),
                        std::end(hids_),
                        [&](auto&& h) {
                          return *registry_entry_id == h->get_registry_entry_id();
                        })) {
          logger::get_logger().info("registry_entry_id:{0} already exists", static_cast<uint64_t>(*registry_entry_id));
          return;
        }

        hid = std::make_shared<human_interface_device>(device, *registry_entry_id);
        hids_.push_back(hid);
      }

      device_detected(hid);
    }
  }

  static void static_device_removal_callback(void* _Nullable context,
                                             IOReturn result,
                                             void* _Nullable sender,
                                             IOHIDDeviceRef _Nonnull device) {
    if (result != kIOReturnSuccess) {
      return;
    }

    auto self = static_cast<hid_manager*>(context);
    if (!self) {
      return;
    }

    self->device_removal_callback(device);
  }

  void device_removal_callback(IOHIDDeviceRef _Nonnull device) {
    if (!device) {
      return;
    }

    // ----------------------------------------

    if (auto registry_entry_id = find_registry_entry_id(device)) {
      std::shared_ptr<human_interface_device> hid;

      {
        std::lock_guard<std::mutex> lock(hids_mutex_);

        auto it = std::find_if(std::begin(hids_),
                               std::end(hids_),
                               [&](auto&& h) {
                                 return *registry_entry_id == h->get_registry_entry_id();
                               });
        if (it != hids_.end()) {
          hid = *it;
          hids_.erase(it);
        }
      }

      if (hid) {
        hid->set_removed();
        device_removed(hid);
      }

      // There might be multiple devices for one registry_entry_id.
      // (For example, keyboard and mouse combined device.)
      // And there is possibility that removal callback is not called with some device.
      //
      // For example, there are 3 devices for 1 registry_entry_id (4294974284).
      //   - device1 { device:0x7fb9d64078a0 => registry_entry_id:4294974284 }
      //   - device2 { device:0x7fb9d8301390 => registry_entry_id:4294974284 }
      //   - device3 { device:0x7fb9d830a630 => registry_entry_id:4294974284 }
      // And device_removal_callback are called only with device1 and device2.
      //
      // We should remove device1, device2 and device3 at the same time in order to deal with this case.

      for (auto it = std::begin(registry_entry_ids_); it != std::end(registry_entry_ids_);) {
        if (it->second == *registry_entry_id) {
          it = registry_entry_ids_.erase(it);
        } else
          std::advance(it, 1);
      }
    }
  }

  void refresh_if_needed(void) {
    // `device_removal_callback` is sometimes missed
    // and invalid human_interface_devices are remained into hids_.
    // (The problem is likely occur just after wake up from sleep.)
    //
    // We validate the human_interface_device,
    // and then reload devices if there is an invalid human_interface_device.

    bool needs_refresh = false;

    {
      std::lock_guard<std::mutex> lock(hids_mutex_);

      for (const auto& hid : hids_) {
        if (!hid->validate()) {
          logger::get_logger().warn("Refreshing hid_manager since a dangling human_interface_device is found. ({0})",
                                    hid->get_name_for_log());
          needs_refresh = true;
          break;
        }
      }
    }

    if (needs_refresh) {
      stop();
      start();
    }
  }

  boost::optional<registry_entry_id> find_registry_entry_id(IOHIDDeviceRef _Nonnull device) {
    auto it = registry_entry_ids_.find(device);
    if (it != std::end(registry_entry_ids_)) {
      return it->second;
    }
    return boost::none;
  }

  std::shared_ptr<cf_utility::run_loop_thread> run_loop_thread_;

  std::vector<std::pair<hid_usage_page, hid_usage>> usage_pairs_;

  IOHIDManagerRef _Nullable manager_;
  std::unique_ptr<thread_utility::timer> refresh_timer_;

  std::unordered_map<IOHIDDeviceRef, registry_entry_id> registry_entry_ids_;
  // We do not need to use registry_entry_ids_mutex_ since it is modified only in run_loop_thread_.
  std::vector<std::shared_ptr<human_interface_device>> hids_;
  mutable std::mutex hids_mutex_;
};
} // namespace krbn
<commit_msg>remove a log message<commit_after>#pragma once

#include "boost_utility.hpp"
#include "cf_utility.hpp"
#include "device_detail.hpp"
#include "human_interface_device.hpp"
#include "logger.hpp"
#include "types.hpp"
#include <IOKit/hid/IOHIDManager.h>
#include <unordered_map>

namespace krbn {
class hid_manager final {
public:
  // Return false to ignore device.
  boost::signals2::signal<bool(IOHIDDeviceRef _Nonnull),
                          boost_utility::signals2_combiner_call_while_true>
      device_detecting;

  boost::signals2::signal<void(std::shared_ptr<human_interface_device>)> device_detected;

  boost::signals2::signal<void(std::shared_ptr<human_interface_device>)>
      device_removed;

  hid_manager(const hid_manager&) = delete;

  hid_manager(const std::vector<std::pair<hid_usage_page, hid_usage>>& usage_pairs) : usage_pairs_(usage_pairs),
                                                                                manager_(nullptr) {
    run_loop_thread_ = std::make_shared<cf_utility::run_loop_thread>();
  }

  ~hid_manager(void) {
    stop();

    run_loop_thread_ = nullptr;
  }

  void start(void) {
    run_loop_thread_->enqueue(^{
      if (manager_) {
        return;
      }

      manager_ = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
      if (!manager_) {
        logger::get_logger().error("{0}: failed to IOHIDManagerCreate", __PRETTY_FUNCTION__);
        return;
      }

      if (auto device_matching_dictionaries = iokit_utility::create_device_matching_dictionaries(usage_pairs_)) {
        IOHIDManagerSetDeviceMatchingMultiple(manager_, device_matching_dictionaries);
        CFRelease(device_matching_dictionaries);
      }

      IOHIDManagerRegisterDeviceMatchingCallback(manager_, static_device_matching_callback, this);
      IOHIDManagerRegisterDeviceRemovalCallback(manager_, static_device_removal_callback, this);

      IOHIDManagerScheduleWithRunLoop(manager_,
                                      run_loop_thread_->get_run_loop(),
                                      kCFRunLoopDefaultMode);

      refresh_timer_ = std::make_unique<thread_utility::timer>(
          std::chrono::milliseconds(5000),
          true,
          [this] {
            run_loop_thread_->enqueue(^{
              refresh_if_needed();
            });
          });

      logger::get_logger().info("hid_manager is started.");
    });
  }

  void stop(void) {
    run_loop_thread_->enqueue(^{
      if (!manager_) {
        return;
      }

      // refresh_timer_

      refresh_timer_ = nullptr;

      // manager_

      if (manager_) {
        IOHIDManagerUnscheduleFromRunLoop(manager_,
                                          run_loop_thread_->get_run_loop(),
                                          kCFRunLoopDefaultMode);
        CFRelease(manager_);
        manager_ = nullptr;
      }

      // Other variables

      registry_entry_ids_.clear();

      {
        std::lock_guard<std::mutex> lock(hids_mutex_);

        hids_.clear();
      }

      logger::get_logger().info("hid_manager is stopped.");
    });
  }

  std::vector<std::shared_ptr<human_interface_device>> copy_hids(void) const {
    std::lock_guard<std::mutex> lock(hids_mutex_);

    return hids_;
  }

private:
  static void static_device_matching_callback(void* _Nullable context,
                                              IOReturn result,
                                              void* _Nullable sender,
                                              IOHIDDeviceRef _Nonnull device) {
    if (result != kIOReturnSuccess) {
      return;
    }

    auto self = static_cast<hid_manager*>(context);
    if (!self) {
      return;
    }

    self->device_matching_callback(device);
  }

  void device_matching_callback(IOHIDDeviceRef _Nonnull device) {
    if (!device) {
      return;
    }

    if (!device_detecting(device)) {
      return;
    }

    if (auto registry_entry_id = iokit_utility::find_registry_entry_id(device)) {
      // iokit_utility::find_registry_entry_id will be failed in device_removal_callback at least on macOS 10.13.
      // Thus, we have to memory device and registry_entry_id correspondence by myself.
      registry_entry_ids_[device] = *registry_entry_id;

      // Skip if same device is already matched.
      // (Multiple usage device (e.g. usage::pointer and usage::mouse) will be matched twice.)

      std::shared_ptr<human_interface_device> hid;

      {
        std::lock_guard<std::mutex> lock(hids_mutex_);

        if (std::any_of(std::begin(hids_),
                        std::end(hids_),
                        [&](auto&& h) {
                          return *registry_entry_id == h->get_registry_entry_id();
                        })) {
          // logger::get_logger().info("registry_entry_id:{0} already exists", static_cast<uint64_t>(*registry_entry_id));
          return;
        }

        hid = std::make_shared<human_interface_device>(device, *registry_entry_id);
        hids_.push_back(hid);
      }

      device_detected(hid);
    }
  }

  static void static_device_removal_callback(void* _Nullable context,
                                             IOReturn result,
                                             void* _Nullable sender,
                                             IOHIDDeviceRef _Nonnull device) {
    if (result != kIOReturnSuccess) {
      return;
    }

    auto self = static_cast<hid_manager*>(context);
    if (!self) {
      return;
    }

    self->device_removal_callback(device);
  }

  void device_removal_callback(IOHIDDeviceRef _Nonnull device) {
    if (!device) {
      return;
    }

    // ----------------------------------------

    if (auto registry_entry_id = find_registry_entry_id(device)) {
      std::shared_ptr<human_interface_device> hid;

      {
        std::lock_guard<std::mutex> lock(hids_mutex_);

        auto it = std::find_if(std::begin(hids_),
                               std::end(hids_),
                               [&](auto&& h) {
                                 return *registry_entry_id == h->get_registry_entry_id();
                               });
        if (it != hids_.end()) {
          hid = *it;
          hids_.erase(it);
        }
      }

      if (hid) {
        hid->set_removed();
        device_removed(hid);
      }

      // There might be multiple devices for one registry_entry_id.
      // (For example, keyboard and mouse combined device.)
      // And there is possibility that removal callback is not called with some device.
      //
      // For example, there are 3 devices for 1 registry_entry_id (4294974284).
      //   - device1 { device:0x7fb9d64078a0 => registry_entry_id:4294974284 }
      //   - device2 { device:0x7fb9d8301390 => registry_entry_id:4294974284 }
      //   - device3 { device:0x7fb9d830a630 => registry_entry_id:4294974284 }
      // And device_removal_callback are called only with device1 and device2.
      //
      // We should remove device1, device2 and device3 at the same time in order to deal with this case.

      for (auto it = std::begin(registry_entry_ids_); it != std::end(registry_entry_ids_);) {
        if (it->second == *registry_entry_id) {
          it = registry_entry_ids_.erase(it);
        } else
          std::advance(it, 1);
      }
    }
  }

  void refresh_if_needed(void) {
    // `device_removal_callback` is sometimes missed
    // and invalid human_interface_devices are remained into hids_.
    // (The problem is likely occur just after wake up from sleep.)
    //
    // We validate the human_interface_device,
    // and then reload devices if there is an invalid human_interface_device.

    bool needs_refresh = false;

    {
      std::lock_guard<std::mutex> lock(hids_mutex_);

      for (const auto& hid : hids_) {
        if (!hid->validate()) {
          logger::get_logger().warn("Refreshing hid_manager since a dangling human_interface_device is found. ({0})",
                                    hid->get_name_for_log());
          needs_refresh = true;
          break;
        }
      }
    }

    if (needs_refresh) {
      stop();
      start();
    }
  }

  boost::optional<registry_entry_id> find_registry_entry_id(IOHIDDeviceRef _Nonnull device) {
    auto it = registry_entry_ids_.find(device);
    if (it != std::end(registry_entry_ids_)) {
      return it->second;
    }
    return boost::none;
  }

  std::shared_ptr<cf_utility::run_loop_thread> run_loop_thread_;

  std::vector<std::pair<hid_usage_page, hid_usage>> usage_pairs_;

  IOHIDManagerRef _Nullable manager_;
  std::unique_ptr<thread_utility::timer> refresh_timer_;

  std::unordered_map<IOHIDDeviceRef, registry_entry_id> registry_entry_ids_;
  // We do not need to use registry_entry_ids_mutex_ since it is modified only in run_loop_thread_.
  std::vector<std::shared_ptr<human_interface_device>> hids_;
  mutable std::mutex hids_mutex_;
};
} // namespace krbn
<|endoftext|>


<commit_before>#pragma once

// `krbn::hid_manager` can be used safely in a multi-threaded environment.

#include "boost_utility.hpp"
#include "cf_utility.hpp"
#include "device_detail.hpp"
#include "dispatcher.hpp"
#include "human_interface_device.hpp"
#include "logger.hpp"
#include "thread_utility.hpp"
#include "types.hpp"
#include <IOKit/hid/IOHIDManager.h>
#include <unordered_map>

namespace krbn {
class hid_manager final : public pqrs::dispatcher::extra::dispatcher_client {
public:
  // Signals

  // Return false to ignore device.
  boost::signals2::signal<bool(IOHIDDeviceRef _Nonnull), boost_utility::signals2_combiner_call_while_true> device_detecting;
  boost::signals2::signal<void(std::weak_ptr<human_interface_device>)> device_detected;
  boost::signals2::signal<void(std::weak_ptr<human_interface_device>)> device_removed;

  // Methods

  hid_manager(const hid_manager&) = delete;

  hid_manager(std::weak_ptr<pqrs::dispatcher::dispatcher> weak_dispatcher,
              const std::vector<std::pair<hid_usage_page, hid_usage>>& usage_pairs) : dispatcher_client(weak_dispatcher),
                                                                                usage_pairs_(usage_pairs),
                                                                                manager_(nullptr),
                                                                                refresh_timer_(*this) {
    run_loop_thread_ = std::make_shared<cf_utility::run_loop_thread>();
  }

  ~hid_manager(void) {
    async_stop();

    run_loop_thread_->terminate();
    run_loop_thread_ = nullptr;
  }

  void async_start(void) {
    run_loop_thread_->enqueue(^{
      if (manager_) {
        logger::get_logger().warn("hid_manager is already started.");
        return;
      }

      manager_ = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
      if (!manager_) {
        logger::get_logger().error("{0}: failed to IOHIDManagerCreate", __PRETTY_FUNCTION__);
        return;
      }

      if (auto device_matching_dictionaries = iokit_utility::create_device_matching_dictionaries(usage_pairs_)) {
        IOHIDManagerSetDeviceMatchingMultiple(manager_, device_matching_dictionaries);
        CFRelease(device_matching_dictionaries);
      }

      IOHIDManagerRegisterDeviceMatchingCallback(manager_, static_device_matching_callback, this);
      IOHIDManagerRegisterDeviceRemovalCallback(manager_, static_device_removal_callback, this);

      IOHIDManagerScheduleWithRunLoop(manager_,
                                      run_loop_thread_->get_run_loop(),
                                      kCFRunLoopDefaultMode);

      refresh_timer_.start(
          [this] {
            run_loop_thread_->enqueue(^{
              refresh_if_needed();
            });
          },
          std::chrono::milliseconds(5000));

      logger::get_logger().info("hid_manager is started.");
    });
  }

  void async_stop(void) {
    run_loop_thread_->enqueue(^{
      if (!manager_) {
        return;
      }

      // refresh_timer_

      refresh_timer_.stop();

      // manager_

      if (manager_) {
        IOHIDManagerUnscheduleFromRunLoop(manager_,
                                          run_loop_thread_->get_run_loop(),
                                          kCFRunLoopDefaultMode);
        CFRelease(manager_);
        manager_ = nullptr;
      }

      // Other variables

      registry_entry_ids_.clear();

      {
        std::lock_guard<std::mutex> lock(hids_mutex_);

        hids_.clear();
      }

      logger::get_logger().info("hid_manager is stopped.");
    });
  }

  std::vector<std::shared_ptr<human_interface_device>> copy_hids(void) const {
    std::lock_guard<std::mutex> lock(hids_mutex_);

    return hids_;
  }

private:
  static void static_device_matching_callback(void* _Nullable context,
                                              IOReturn result,
                                              void* _Nullable sender,
                                              IOHIDDeviceRef _Nonnull device) {
    if (result != kIOReturnSuccess) {
      return;
    }

    auto self = static_cast<hid_manager*>(context);
    if (!self) {
      return;
    }

    self->device_matching_callback(device);
  }

  void device_matching_callback(IOHIDDeviceRef _Nonnull device) {
    // `static_device_matching_callback` is called by multiple threads.
    // (It is not called from `run_loop_thread_`.)
    // Thus, we have to synchronize by using `run_loop_thread_`.

    run_loop_thread_->enqueue(^{
      if (!device) {
        return;
      }

      if (!device_detecting(device)) {
        return;
      }

      if (auto registry_entry_id = iokit_utility::find_registry_entry_id(device)) {
        // iokit_utility::find_registry_entry_id will be failed in device_removal_callback at least on macOS 10.13.
        // Thus, we have to memory device and registry_entry_id correspondence by myself.
        registry_entry_ids_[device] = *registry_entry_id;

        // Skip if same device is already matched.
        // (Multiple usage device (e.g. usage::pointer and usage::mouse) will be matched twice.)

        std::shared_ptr<human_interface_device> hid;

        {
          std::lock_guard<std::mutex> lock(hids_mutex_);

          if (std::any_of(std::begin(hids_),
                          std::end(hids_),
                          [&](auto&& h) {
                            return *registry_entry_id == h->get_registry_entry_id();
                          })) {
            // logger::get_logger().info("registry_entry_id:{0} already exists", static_cast<uint64_t>(*registry_entry_id));
            return;
          }

          hid = std::make_shared<human_interface_device>(device, *registry_entry_id);
          hids_.push_back(hid);
        }

        device_detected(hid);
      }
    });
  }

  static void static_device_removal_callback(void* _Nullable context,
                                             IOReturn result,
                                             void* _Nullable sender,
                                             IOHIDDeviceRef _Nonnull device) {
    if (result != kIOReturnSuccess) {
      return;
    }

    auto self = static_cast<hid_manager*>(context);
    if (!self) {
      return;
    }

    self->device_removal_callback(device);
  }

  void device_removal_callback(IOHIDDeviceRef _Nonnull device) {
    // `static_device_removal_callback` is called by multiple threads.
    // (It is not called from `run_loop_thread_`.)
    // Thus, we have to synchronize by using `run_loop_thread_`.

    run_loop_thread_->enqueue(^{
      if (!device) {
        return;
      }

      // ----------------------------------------

      if (auto registry_entry_id = find_registry_entry_id(device)) {
        std::shared_ptr<human_interface_device> hid;

        {
          std::lock_guard<std::mutex> lock(hids_mutex_);

          auto it = std::find_if(std::begin(hids_),
                                 std::end(hids_),
                                 [&](auto&& h) {
                                   return *registry_entry_id == h->get_registry_entry_id();
                                 });
          if (it != hids_.end()) {
            hid = *it;
            hids_.erase(it);
          }
        }

        if (hid) {
          hid->set_removed();
          device_removed(hid);
        }

        // There might be multiple devices for one registry_entry_id.
        // (For example, keyboard and mouse combined device.)
        // And there is possibility that removal callback is not called with some device.
        //
        // For example, there are 3 devices for 1 registry_entry_id (4294974284).
        //   - device1 { device:0x7fb9d64078a0 => registry_entry_id:4294974284 }
        //   - device2 { device:0x7fb9d8301390 => registry_entry_id:4294974284 }
        //   - device3 { device:0x7fb9d830a630 => registry_entry_id:4294974284 }
        // And device_removal_callback are called only with device1 and device2.
        //
        // We should remove device1, device2 and device3 at the same time in order to deal with this case.

        for (auto it = std::begin(registry_entry_ids_); it != std::end(registry_entry_ids_);) {
          if (it->second == *registry_entry_id) {
            it = registry_entry_ids_.erase(it);
          } else {
            std::advance(it, 1);
          }
        }
      }
    });
  }

  void refresh_if_needed(void) {
    // `device_removal_callback` is sometimes missed
    // and invalid human_interface_devices are remained into hids_.
    // (The problem is likely occur just after wake up from sleep.)
    //
    // We validate the human_interface_device,
    // and then reload devices if there is an invalid human_interface_device.

    bool needs_refresh = false;

    {
      std::lock_guard<std::mutex> lock(hids_mutex_);

      for (const auto& hid : hids_) {
        if (!hid->validate()) {
          logger::get_logger().warn("Refreshing hid_manager since a dangling human_interface_device is found. ({0})",
                                    hid->get_name_for_log());
          needs_refresh = true;
          break;
        }
      }
    }

    if (needs_refresh) {
      async_stop();
      async_start();
    }
  }

  boost::optional<registry_entry_id> find_registry_entry_id(IOHIDDeviceRef _Nonnull device) {
    auto it = registry_entry_ids_.find(device);
    if (it != std::end(registry_entry_ids_)) {
      return it->second;
    }
    return boost::none;
  }

  std::shared_ptr<cf_utility::run_loop_thread> run_loop_thread_;

  std::vector<std::pair<hid_usage_page, hid_usage>> usage_pairs_;

  IOHIDManagerRef _Nullable manager_;
  pqrs::dispatcher::extra::timer refresh_timer_;

  std::unordered_map<IOHIDDeviceRef, registry_entry_id> registry_entry_ids_;
  // We do not need to use registry_entry_ids_mutex_ since it is modified only in run_loop_thread_.
  std::vector<std::shared_ptr<human_interface_device>> hids_;
  mutable std::mutex hids_mutex_;
};
} // namespace krbn
<commit_msg>update hid_manager<commit_after>#pragma once

// `krbn::hid_manager` can be used safely in a multi-threaded environment.

#include "boost_utility.hpp"
#include "cf_utility.hpp"
#include "device_detail.hpp"
#include "dispatcher.hpp"
#include "human_interface_device.hpp"
#include "logger.hpp"
#include "thread_utility.hpp"
#include "types.hpp"
#include <IOKit/hid/IOHIDManager.h>
#include <unordered_map>

namespace krbn {
class hid_manager final : public pqrs::dispatcher::extra::dispatcher_client {
public:
  // Signals

  // Return false to ignore device.
  boost::signals2::signal<bool(IOHIDDeviceRef _Nonnull), boost_utility::signals2_combiner_call_while_true> device_detecting;
  boost::signals2::signal<void(std::weak_ptr<human_interface_device>)> device_detected;
  boost::signals2::signal<void(std::weak_ptr<human_interface_device>)> device_removed;

  // Methods

  hid_manager(const hid_manager&) = delete;

  hid_manager(std::weak_ptr<pqrs::dispatcher::dispatcher> weak_dispatcher,
              const std::vector<std::pair<hid_usage_page, hid_usage>>& usage_pairs) : dispatcher_client(weak_dispatcher),
                                                                                usage_pairs_(usage_pairs),
                                                                                manager_(nullptr),
                                                                                refresh_timer_(*this) {
    run_loop_thread_ = std::make_shared<cf_utility::run_loop_thread>();
  }

  virtual ~hid_manager(void) {
    async_stop();

    run_loop_thread_->terminate();
    run_loop_thread_ = nullptr;

    detach_from_dispatcher([] {
    });
  }

  void async_start(void) {
    enqueue_to_dispatcher([this] {
      if (manager_) {
        logger::get_logger().warn("hid_manager is already started.");
        return;
      }

      manager_ = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
      if (!manager_) {
        logger::get_logger().error("{0}: failed to IOHIDManagerCreate", __PRETTY_FUNCTION__);
        return;
      }

      if (auto device_matching_dictionaries = iokit_utility::create_device_matching_dictionaries(usage_pairs_)) {
        IOHIDManagerSetDeviceMatchingMultiple(manager_, device_matching_dictionaries);
        CFRelease(device_matching_dictionaries);
      }

      IOHIDManagerRegisterDeviceMatchingCallback(manager_, static_device_matching_callback, this);
      IOHIDManagerRegisterDeviceRemovalCallback(manager_, static_device_removal_callback, this);

      IOHIDManagerScheduleWithRunLoop(manager_,
                                      run_loop_thread_->get_run_loop(),
                                      kCFRunLoopDefaultMode);

      refresh_timer_.start(
          [this] {
            refresh_if_needed();
          },
          std::chrono::milliseconds(5000));

      logger::get_logger().info("hid_manager is started.");
    });
  }

  void async_stop(void) {
    enqueue_to_dispatcher([this] {
      if (!manager_) {
        return;
      }

      // refresh_timer_

      refresh_timer_.stop();

      // manager_

      if (manager_) {
        IOHIDManagerUnscheduleFromRunLoop(manager_,
                                          run_loop_thread_->get_run_loop(),
                                          kCFRunLoopDefaultMode);
        CFRelease(manager_);
        manager_ = nullptr;
      }

      // Other variables

      registry_entry_ids_.clear();

      {
        std::lock_guard<std::mutex> lock(hids_mutex_);

        hids_.clear();
      }

      logger::get_logger().info("hid_manager is stopped.");
    });
  }

  std::vector<std::shared_ptr<human_interface_device>> copy_hids(void) const {
    std::lock_guard<std::mutex> lock(hids_mutex_);

    return hids_;
  }

private:
  static void static_device_matching_callback(void* _Nullable context,
                                              IOReturn result,
                                              void* _Nullable sender,
                                              IOHIDDeviceRef _Nonnull device) {
    if (result != kIOReturnSuccess) {
      return;
    }

    auto self = static_cast<hid_manager*>(context);
    if (!self) {
      return;
    }

    self->device_matching_callback(device);
  }

  void device_matching_callback(IOHIDDeviceRef _Nonnull device) {
    // `static_device_matching_callback` is called by multiple threads.
    // (It is not called from `run_loop_thread_`.)
    // Thus, we have to synchronize by using `dispatcher`.

    enqueue_to_dispatcher([this, device] {
      if (!device) {
        return;
      }

      if (!device_detecting(device)) {
        return;
      }

      if (auto registry_entry_id = iokit_utility::find_registry_entry_id(device)) {
        // iokit_utility::find_registry_entry_id will be failed in device_removal_callback at least on macOS 10.13.
        // Thus, we have to memory device and registry_entry_id correspondence by myself.
        registry_entry_ids_[device] = *registry_entry_id;

        // Skip if same device is already matched.
        // (Multiple usage device (e.g. usage::pointer and usage::mouse) will be matched twice.)

        std::shared_ptr<human_interface_device> hid;

        {
          std::lock_guard<std::mutex> lock(hids_mutex_);

          if (std::any_of(std::begin(hids_),
                          std::end(hids_),
                          [&](auto&& h) {
                            return *registry_entry_id == h->get_registry_entry_id();
                          })) {
            // logger::get_logger().info("registry_entry_id:{0} already exists", static_cast<uint64_t>(*registry_entry_id));
            return;
          }

          hid = std::make_shared<human_interface_device>(device, *registry_entry_id);
          hids_.push_back(hid);
        }

        device_detected(hid);
      }
    });
  }

  static void static_device_removal_callback(void* _Nullable context,
                                             IOReturn result,
                                             void* _Nullable sender,
                                             IOHIDDeviceRef _Nonnull device) {
    if (result != kIOReturnSuccess) {
      return;
    }

    auto self = static_cast<hid_manager*>(context);
    if (!self) {
      return;
    }

    self->device_removal_callback(device);
  }

  void device_removal_callback(IOHIDDeviceRef _Nonnull device) {
    // `static_device_removal_callback` is called by multiple threads.
    // (It is not called from `run_loop_thread_`.)
    // Thus, we have to synchronize by using `dispatcher`.

    enqueue_to_dispatcher([this, device] {
      if (!device) {
        return;
      }

      // ----------------------------------------

      if (auto registry_entry_id = find_registry_entry_id(device)) {
        std::shared_ptr<human_interface_device> hid;

        {
          std::lock_guard<std::mutex> lock(hids_mutex_);

          auto it = std::find_if(std::begin(hids_),
                                 std::end(hids_),
                                 [&](auto&& h) {
                                   return *registry_entry_id == h->get_registry_entry_id();
                                 });
          if (it != hids_.end()) {
            hid = *it;
            hids_.erase(it);
          }
        }

        if (hid) {
          hid->set_removed();
          device_removed(hid);
        }

        // There might be multiple devices for one registry_entry_id.
        // (For example, keyboard and mouse combined device.)
        // And there is possibility that removal callback is not called with some device.
        //
        // For example, there are 3 devices for 1 registry_entry_id (4294974284).
        //   - device1 { device:0x7fb9d64078a0 => registry_entry_id:4294974284 }
        //   - device2 { device:0x7fb9d8301390 => registry_entry_id:4294974284 }
        //   - device3 { device:0x7fb9d830a630 => registry_entry_id:4294974284 }
        // And device_removal_callback are called only with device1 and device2.
        //
        // We should remove device1, device2 and device3 at the same time in order to deal with this case.

        for (auto it = std::begin(registry_entry_ids_); it != std::end(registry_entry_ids_);) {
          if (it->second == *registry_entry_id) {
            it = registry_entry_ids_.erase(it);
          } else {
            std::advance(it, 1);
          }
        }
      }
    });
  }

  void refresh_if_needed(void) {
    // `device_removal_callback` is sometimes missed
    // and invalid human_interface_devices are remained into hids_.
    // (The problem is likely occur just after wake up from sleep.)
    //
    // We validate the human_interface_device,
    // and then reload devices if there is an invalid human_interface_device.

    bool needs_refresh = false;

    {
      std::lock_guard<std::mutex> lock(hids_mutex_);

      for (const auto& hid : hids_) {
        if (!hid->validate()) {
          logger::get_logger().warn("Refreshing hid_manager since a dangling human_interface_device is found. ({0})",
                                    hid->get_name_for_log());
          needs_refresh = true;
          break;
        }
      }
    }

    if (needs_refresh) {
      async_stop();
      async_start();
    }
  }

  boost::optional<registry_entry_id> find_registry_entry_id(IOHIDDeviceRef _Nonnull device) {
    auto it = registry_entry_ids_.find(device);
    if (it != std::end(registry_entry_ids_)) {
      return it->second;
    }
    return boost::none;
  }

  std::shared_ptr<cf_utility::run_loop_thread> run_loop_thread_;

  std::vector<std::pair<hid_usage_page, hid_usage>> usage_pairs_;

  IOHIDManagerRef _Nullable manager_;
  pqrs::dispatcher::extra::timer refresh_timer_;

  std::unordered_map<IOHIDDeviceRef, registry_entry_id> registry_entry_ids_;

  std::vector<std::shared_ptr<human_interface_device>> hids_;
  mutable std::mutex hids_mutex_;
};
} // namespace krbn
<|endoftext|>


<commit_before>#include <atomic>
#include <memory>
#include <string>
#include <thread>

#include <boost/program_options/cmdline.hpp>
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/parsers.hpp>
#include <boost/program_options/variables_map.hpp>

#include "jml/arch/exception.h"
#include "soa/types/date.h"
#include "soa/types/value_description.h"
#include "soa/service/http_client.h"
#include "soa/service/http_endpoint.h"
#include "soa/service/named_endpoint.h"
#include "soa/service/message_loop.h"
#include "soa/service/rest_proxy.h"
#include "soa/service/rest_service_endpoint.h"
#include "soa/service/runner.h"

#include "test_http_services.h"

using namespace std;
using namespace Datacratic;

/* bench methods */

void
AsyncModelBench(const string & baseUrl, int maxReqs, int concurrency,
                Date & start, Date & end)
{
    int numReqs, numResponses(0), numMissed(0);
    MessageLoop loop(1, 0, -1);

    loop.start();

    auto client = make_shared<HttpClient>(baseUrl, concurrency);
    loop.addSource("httpClient", client);

    auto onResponse = [&] (const HttpRequest & rq, HttpClientError errorCode_,
                           int status, string && headers, string && body) {
        numResponses++;
        // if (numResponses % 1000) {
            // cerr << "resps: "  + to_string(numResponses) + "\n";
        // }
        if (numResponses == maxReqs) {
            // cerr << "received all responses\n";
            ML::futex_wake(numResponses);
        }
    };
    auto cbs = make_shared<HttpClientSimpleCallbacks>(onResponse);

    auto & clientRef = *client.get();
    string url("/");
    start = Date::now();
    for (numReqs = 0; numReqs < maxReqs;) {
        if (clientRef.get(url, cbs)) {
            numReqs++;
            // if (numReqs % 1000) {
            //     cerr << "reqs: "  + to_string(numReqs) + "\n";
            // }
        }
        else {
            numMissed++;
        }
    }

    while (numResponses < maxReqs) {
        // cerr << (" num Responses: " + to_string(numResponses)
        //          + "; max reqs: " + to_string(maxReqs)
        //          + "\n");
        int old(numResponses);
        ML::futex_wait(numResponses, old);
    }
    end = Date::now();

    loop.removeSource(client.get());
    client->waitConnectionState(AsyncEventSource::DISCONNECTED);

    cerr << "num misses: "  + to_string(numMissed) + "\n";
}

void
ThreadedModelBench(const string & baseUrl, int maxReqs, int concurrency,
                   Date & start, Date & end)
{
    vector<thread> threads;

    auto threadFn = [&] (int num, int nReqs) {
        int i;
        HttpRestProxy client(baseUrl);
        for (i = 0; i < nReqs; i++) {
            auto response = client.get("/");
        }
    };

    start = Date::now();
    int slice(maxReqs / concurrency);
    for (int i = 0; i < concurrency; i++) {
        // cerr << "doing slice: "  + to_string(slice) + "\n";
        threads.emplace_back(threadFn, i, slice);
    }
    for (int i = 0; i < concurrency; i++) {
        threads[i].join();
    }
    end = Date::now();
}

int main(int argc, char *argv[])
{
    using namespace boost::program_options;

    size_t concurrency(0);
    int model(0);
    size_t maxReqs(0);
    size_t payloadSize(0);

    string serveriface("127.0.0.1");
    string clientiface(serveriface);

    options_description all_opt;
    all_opt.add_options()
        ("client-iface,C", value(&clientiface),
         "address:port to connect to (\"none\" for no client)")
        ("concurrency,c", value(&concurrency),
         "Number of concurrent requests")
        ("model,m", value(&model),
         "Type of concurrency model (1 for async, 2 for threaded))")
        ("requests,r", value(&maxReqs),
         "total of number of requests to perform")
        ("payload-size,s", value(&payloadSize),
         "size of the response body")
        ("server-iface,S", value(&serveriface),
         "server address (\"none\" for no server)")
        ("help,H", "show help");

    if (argc == 1) {
        return 0;
    }

    variables_map vm;
    store(command_line_parser(argc, argv)
          .options(all_opt)
          .run(),
          vm);
    notify(vm);

    if (vm.count("help")) {
        cerr << all_opt << endl;
        return 1;
    }

    /* service setup */
    auto proxies = make_shared<ServiceProxies>();

    HttpGetService service(proxies);

    if (concurrency == 0) {
        throw ML::Exception("'concurrency' must be specified");
    }

    if (payloadSize == 0) {
        throw ML::Exception("'payload-size' must be specified");
    }

    string payload;
    while (payload.size() < payloadSize) {
        payload += "aaaaaaaa";
    }

    if (serveriface != "none") {
        cerr << "launching server\n";
        service.portToUse = 20000;

        service.addResponse("GET", "/", 200, payload);
        service.start(serveriface, concurrency);
    }

    if (clientiface != "none") {
        cerr << "launching client\n";
        if (maxReqs == 0) {
            throw ML::Exception("'max-reqs' must be specified");
        }

        string baseUrl;
        if (serveriface != "none") {
            baseUrl = ("http://" + serveriface
                       + ":" + to_string(service.port()));
        }
        else {
            baseUrl = "http://" + clientiface;
        }

        ::printf("model\tconc.\treqs\tsize\ttime_secs\tqps\n");

        Date start, end;
        if (model == 1) {
            AsyncModelBench(baseUrl, maxReqs, concurrency, start, end);
        }
        else if (model == 2) {
            ThreadedModelBench(baseUrl, maxReqs, concurrency, start, end);
        }
        else {
            throw ML::Exception("invalid 'model'");
        }
        double delta = end - start;
        double qps = maxReqs / delta;
        ::printf("%d\t%lu\t%lu\t%lu\t%f\t%f\n",
                 model, concurrency, maxReqs, payloadSize, delta, qps);
    }
    else {
        while (1) {
            sleep(100);
        }
    }

    struct rusage usage;
    getrusage(RUSAGE_SELF, &usage);
    Json::Value jsonUsage = jsonEncode(usage);
    cerr << "rusage:\n" << jsonUsage.toStyledString() << endl;

    return 0;
}
<commit_msg>http_client_bench: added the ability to specify the http method to benchmark (TRIVIAL: enhancement, affects only the http_client_bench tool)<commit_after>#include <atomic>
#include <memory>
#include <string>
#include <thread>

#include <boost/program_options/cmdline.hpp>
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/parsers.hpp>
#include <boost/program_options/variables_map.hpp>

#include "jml/arch/exception.h"
#include "soa/types/date.h"
#include "soa/types/value_description.h"
#include "soa/utils/print_utils.h"
#include "soa/service/http_client.h"
#include "soa/service/http_endpoint.h"
#include "soa/service/named_endpoint.h"
#include "soa/service/message_loop.h"
#include "soa/service/rest_proxy.h"
#include "soa/service/rest_service_endpoint.h"
#include "soa/service/runner.h"

#include "test_http_services.h"

using namespace std;
using namespace Datacratic;

enum HttpMethod {
    GET,
    POST,
    PUT
};

/* bench methods */

double
AsyncModelBench(HttpMethod method,
                const string & baseUrl, const string & payload,
                int maxReqs, int concurrency)
{
    int numReqs, numResponses(0), numMissed(0);
    MessageLoop loop(1, 0, -1);

    loop.start();

    auto client = make_shared<HttpClient>(baseUrl, concurrency);
    loop.addSource("httpClient", client);

    auto onResponse = [&] (const HttpRequest & rq, HttpClientError errorCode_,
                           int status, string && headers, string && body) {
        numResponses++;
        // if (numResponses % 1000) {
            // cerr << "resps: "  + to_string(numResponses) + "\n";
        // }
        if (numResponses == maxReqs) {
            // cerr << "received all responses\n";
            ML::futex_wake(numResponses);
        }
    };
    auto cbs = make_shared<HttpClientSimpleCallbacks>(onResponse);
    HttpRequest::Content content(payload, "application/binary");

    auto & clientRef = *client.get();
    string url("/");
    Date start = Date::now();
    for (numReqs = 0; numReqs < maxReqs;) {
        bool result;
        if (method == GET) {
            result = clientRef.get(url, cbs);
        }
        else if (method == POST) {
            result = clientRef.post(url, cbs, content);
        }
        else if (method == PUT) {
            result = clientRef.put(url, cbs, content);
        }
        else {
            result = true;
        }
        if (result) {
            numReqs++;
            // if (numReqs % 1000) {
            //     cerr << "reqs: "  + to_string(numReqs) + "\n";
            // }
        }
        else {
            numMissed++;
        }
    }

    while (numResponses < maxReqs) {
        // cerr << (" num Responses: " + to_string(numResponses)
        //          + "; max reqs: " + to_string(maxReqs)
        //          + "\n");
        int old(numResponses);
        ML::futex_wait(numResponses, old);
    }
    Date end = Date::now();

    loop.removeSource(client.get());
    client->waitConnectionState(AsyncEventSource::DISCONNECTED);

    cerr << "num misses: "  + to_string(numMissed) + "\n";

    return end - start;
}

double
ThreadedModelBench(HttpMethod method,
                   const string & baseUrl, const string & payload,
                   int maxReqs, int concurrency)
{
    vector<thread> threads;

    HttpRestProxy::Content content(payload, "application/binary");

    auto threadFn = [&] (int num, int nReqs) {
        int i;
        HttpRestProxy client(baseUrl);
        for (i = 0; i < nReqs; i++) {
            if (method == GET) {
                auto response = client.get("/");
            }
            else if (method == POST) {
                auto response = client.post("/", content);
            }
            else if (method == PUT) {
                auto response = client.put("/", content);
            }
        }
    };

    Date start = Date::now();
    int slice(maxReqs / concurrency);
    for (int i = 0; i < concurrency; i++) {
        // cerr << "doing slice: "  + to_string(slice) + "\n";
        threads.emplace_back(threadFn, i, slice);
    }
    for (int i = 0; i < concurrency; i++) {
        threads[i].join();
    }

    return Date::now() - start;
}

int main(int argc, char *argv[])
{
    using namespace boost::program_options;

    size_t concurrency(0);
    int model(0);
    size_t maxReqs(0);
    string method("GET");
    size_t payloadSize(0);

    string serveriface("127.0.0.1");
    string clientiface(serveriface);

    options_description all_opt;
    all_opt.add_options()
        ("client-iface,C", value(&clientiface),
         "address:port to connect to (\"none\" for no client)")
        ("concurrency,c", value(&concurrency),
         "Number of concurrent requests")
        ("method,M", value(&method),
         "Method to use (\"GET\"*, \"PUT\", \"POST\")")
        ("model,m", value(&model),
         "Type of concurrency model (1 for async, 2 for threaded))")
        ("requests,r", value(&maxReqs),
         "total of number of requests to perform")
        ("payload-size,s", value(&payloadSize),
         "size of the response body")
        ("server-iface,S", value(&serveriface),
         "server address (\"none\" for no server)")
        ("help,H", "show help");

    if (argc == 1) {
        return 0;
    }

    variables_map vm;
    store(command_line_parser(argc, argv)
          .options(all_opt)
          .run(),
          vm);
    notify(vm);

    if (vm.count("help")) {
        cerr << all_opt << endl;
        return 1;
    }

    /* service setup */
    auto proxies = make_shared<ServiceProxies>();

    HttpGetService service(proxies);

    if (concurrency == 0) {
        throw ML::Exception("'concurrency' must be specified");
    }

    if (payloadSize == 0) {
        throw ML::Exception("'payload-size' must be specified");
    }

    string payload;
    while (payload.size() < payloadSize) {
        payload += randomString(128);
    }

    if (serveriface != "none") {
        cerr << "launching server\n";
        service.portToUse = 20000;

        service.addResponse("GET", "/", 200, payload);
        service.addResponse("PUT", "/", 200, "");
        service.addResponse("POST", "/", 200, "");
        service.start(serveriface, concurrency);
    }

    if (clientiface != "none") {
        cerr << "launching client\n";
        if (maxReqs == 0) {
            throw ML::Exception("'max-reqs' must be specified");
        }

        if (!(method == "GET" || method == "POST" || method == "PUT")) {
            throw ML::Exception("invalid method:" + method);
        }

        string baseUrl;
        if (serveriface != "none") {
            baseUrl = ("http://" + serveriface
                       + ":" + to_string(service.port()));
        }
        else {
            baseUrl = "http://" + clientiface;
        }

        ::printf("model\tconc.\treqs\tsize\ttime_secs\tqps\n");

        HttpMethod httpMethod;
        if (method == "GET") {
            httpMethod = GET;
        }
        else if (method == "POST") {
            httpMethod = POST;
        }
        else if (method == "PUT") {
            httpMethod = PUT;
        }
        else {
            throw ML::Exception("unknown method: "  + method);
        }

        double delta;
        if (model == 1) {
            delta = AsyncModelBench(httpMethod, baseUrl, payload, maxReqs, concurrency);
        }
        else if (model == 2) {
            delta = ThreadedModelBench(httpMethod, baseUrl, payload, maxReqs, concurrency);
        }
        else {
            throw ML::Exception("invalid 'model'");
        }
        double qps = maxReqs / delta;
        ::printf("%d\t%lu\t%lu\t%lu\t%f\t%f\n",
                 model, concurrency, maxReqs, payloadSize, delta, qps);
    }
    else {
        while (1) {
            sleep(100);
        }
    }

    struct rusage usage;
    getrusage(RUSAGE_SELF, &usage);
    Json::Value jsonUsage = jsonEncode(usage);
    cerr << "rusage:\n" << jsonUsage.toStyledString() << endl;

    return 0;
}
<|endoftext|>


<commit_before>#include "gmock/gmock.h"

#include "tcframe/experimental/runner.hpp"

using ::testing::A;
using ::testing::AllOf;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::Property;
using ::testing::SizeIs;
using ::testing::Test;

namespace tcframe {

class BaseTestSpecTests : public Test {
protected:
    class ProblemSpec : public BaseProblemSpec {
    public: // intentionaly public so that input values can be inspected
        int A, B;

        void InputFormat() {}
    };

    class TestSpec : public BaseTestSpec<ProblemSpec> {
    protected:
        void Config() {
            setSolutionCommand("python Sol.py");
            setTestCasesDir("dir");
        }

        void InputFinalizer() {
            A *= 2;
        }
    };

    class TestSpecWithTestCases : public TestSpec {
    protected:
        void SampleTestCases() {
            addSampleTestCase({"10", "20"});
            addSampleTestCase({"30", "40"});
        }

        void TestCases() {
            addOfficialTestCase(OfficialTestCase([=] {A = 1, B = 2;}, "A = 1, B = 2"));
            addOfficialTestCase(OfficialTestCase([=] {A = 3, B = 4;}, "A = 3, B = 4"));
        }
    };

    class TestSpecWithTestGroups : public TestSpec {
    protected:
        void SampleTestCases() {
            addSampleTestCase({"10", "20"}, {1, 2});
            addSampleTestCase({"30", "40"}, {2});
        }

        void TestGroup1() {
            assignToSubtasks({1, 2});

            addOfficialTestCase(OfficialTestCase([=] {A = 1, B = 2;}, "A = 1, B = 2"));
            addOfficialTestCase(OfficialTestCase([=] {A = 3, B = 4;}, "A = 3, B = 4"));
            addOfficialTestCase(OfficialTestCase([=] {A = 5, B = 6;}, "A = 5, B = 6"));
        }

        void TestGroup2() {
            assignToSubtasks({2});

            addOfficialTestCase(OfficialTestCase([=] {A = 101, B = 201;}, "A = 101, B = 201"));
            addOfficialTestCase(OfficialTestCase([=] {A = 301, B = 401;}, "A = 301, B = 401"));
        }
    };

    class TestSpecWithRandom : public TestSpec {
    protected:
        void TestCases() {
            addOfficialTestCase(OfficialTestCase([=] {A = rnd.nextInt(1, 100);}, "A = rnd.nextInt(1, 100)"));
        }
    };

    TestSpec spec;
    TestSpecWithTestCases specWithTestCases;
    TestSpecWithTestGroups specWithTestGroups;
};

TEST_F(BaseTestSpecTests, Config) {
    TestConfig config = spec.buildTestConfig();
    EXPECT_THAT(config.solutionCommand(), Eq("python Sol.py"));
    EXPECT_THAT(config.testCasesDir(), Eq("dir"));
}

TEST_F(BaseTestSpecTests, InputFinalizer) {
    TestSuite testSuite = spec.buildTestSuite();
    function<void()> inputFinalizer = testSuite.inputFinalizer();

    spec.A = 3;
    inputFinalizer();

    EXPECT_THAT(spec.A, Eq(3 * 2));
}

TEST_F(BaseTestSpecTests, TestSuite) {
    TestSuite testSuite = specWithTestCases.buildTestSuite();
    EXPECT_THAT(testSuite.sampleTests(), SizeIs(2));
    EXPECT_THAT(testSuite.officialTests(), ElementsAre(
            AllOf(Property(&TestGroup::id, -1), Property(&TestGroup::officialTestCases, SizeIs(2)))));
}

TEST_F(BaseTestSpecTests, TestSuite_WithGroups) {
    TestSuite testSuite = specWithTestGroups.buildTestSuite();
    EXPECT_THAT(testSuite.sampleTests(), SizeIs(2));
    EXPECT_THAT(testSuite.officialTests(), ElementsAre(
            AllOf(Property(&TestGroup::id, 1), Property(&TestGroup::officialTestCases, SizeIs(3))),
            AllOf(Property(&TestGroup::id, 2), Property(&TestGroup::officialTestCases, SizeIs(2)))));
}

TEST_F(BaseTestSpecTests, Random_Compiles) {
    SUCCEED();
}

}
<commit_msg>Remove silly test<commit_after>#include "gmock/gmock.h"

#include "tcframe/experimental/runner.hpp"

using ::testing::A;
using ::testing::AllOf;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::Property;
using ::testing::SizeIs;
using ::testing::Test;

namespace tcframe {

class BaseTestSpecTests : public Test {
protected:
    class ProblemSpec : public BaseProblemSpec {
    public: // intentionaly public so that input values can be inspected
        int A, B;

        void InputFormat() {}
    };

    class TestSpec : public BaseTestSpec<ProblemSpec> {
    protected:
        void Config() {
            setSolutionCommand("python Sol.py");
            setTestCasesDir("dir");
        }

        void InputFinalizer() {
            A *= 2;
        }
    };

    class TestSpecWithTestCases : public TestSpec {
    protected:
        void SampleTestCases() {
            addSampleTestCase({"10", "20"});
            addSampleTestCase({"30", "40"});
        }

        void TestCases() {
            addOfficialTestCase(OfficialTestCase([=] {A = 1, B = 2;}, "A = 1, B = 2"));
            addOfficialTestCase(OfficialTestCase([=] {A = 3, B = 4;}, "A = 3, B = 4"));
        }
    };

    class TestSpecWithTestGroups : public TestSpec {
    protected:
        void SampleTestCases() {
            addSampleTestCase({"10", "20"}, {1, 2});
            addSampleTestCase({"30", "40"}, {2});
        }

        void TestGroup1() {
            assignToSubtasks({1, 2});

            addOfficialTestCase(OfficialTestCase([=] {A = 1, B = 2;}, "A = 1, B = 2"));
            addOfficialTestCase(OfficialTestCase([=] {A = 3, B = 4;}, "A = 3, B = 4"));
            addOfficialTestCase(OfficialTestCase([=] {A = 5, B = 6;}, "A = 5, B = 6"));
        }

        void TestGroup2() {
            assignToSubtasks({2});

            addOfficialTestCase(OfficialTestCase([=] {A = 101, B = 201;}, "A = 101, B = 201"));
            addOfficialTestCase(OfficialTestCase([=] {A = 301, B = 401;}, "A = 301, B = 401"));
        }
    };

    // Testing that rnd is available in TestSpec
    class TestSpecWithRandom : public TestSpec {
    protected:
        void TestCases() {
            addOfficialTestCase(OfficialTestCase([=] {A = rnd.nextInt(1, 100);}, "A = rnd.nextInt(1, 100)"));
        }
    };

    TestSpec spec;
    TestSpecWithTestCases specWithTestCases;
    TestSpecWithTestGroups specWithTestGroups;
};

TEST_F(BaseTestSpecTests, Config) {
    TestConfig config = spec.buildTestConfig();
    EXPECT_THAT(config.solutionCommand(), Eq("python Sol.py"));
    EXPECT_THAT(config.testCasesDir(), Eq("dir"));
}

TEST_F(BaseTestSpecTests, InputFinalizer) {
    TestSuite testSuite = spec.buildTestSuite();
    function<void()> inputFinalizer = testSuite.inputFinalizer();

    spec.A = 3;
    inputFinalizer();

    EXPECT_THAT(spec.A, Eq(3 * 2));
}

TEST_F(BaseTestSpecTests, TestSuite) {
    TestSuite testSuite = specWithTestCases.buildTestSuite();
    EXPECT_THAT(testSuite.sampleTests(), SizeIs(2));
    EXPECT_THAT(testSuite.officialTests(), ElementsAre(
            AllOf(Property(&TestGroup::id, -1), Property(&TestGroup::officialTestCases, SizeIs(2)))));
}

TEST_F(BaseTestSpecTests, TestSuite_WithGroups) {
    TestSuite testSuite = specWithTestGroups.buildTestSuite();
    EXPECT_THAT(testSuite.sampleTests(), SizeIs(2));
    EXPECT_THAT(testSuite.officialTests(), ElementsAre(
            AllOf(Property(&TestGroup::id, 1), Property(&TestGroup::officialTestCases, SizeIs(3))),
            AllOf(Property(&TestGroup::id, 2), Property(&TestGroup::officialTestCases, SizeIs(2)))));
}

}
<|endoftext|>


<commit_before>#include "utest_helper.hpp"

static void compiler_movforphi_undef(void)
{
  const size_t w = 16;
  const size_t h = 16;
  cl_sampler sampler;
  cl_image_format format;
  cl_image_desc desc;

  // Setup kernel and images
  OCL_CREATE_KERNEL("test_movforphi_undef");
  buf_data[0] = (uint32_t*) malloc(sizeof(uint32_t) * w * h);
  for (uint32_t j = 0; j < h; ++j)
    for (uint32_t i = 0; i < w; i++)
      ((uint32_t*)buf_data[0])[j * w + i] = j * w + i;

  format.image_channel_order = CL_RGBA;
  format.image_channel_data_type = CL_UNSIGNED_INT8;
  desc.image_type = CL_MEM_OBJECT_IMAGE2D;
  desc.image_width = w;
  desc.image_height = h;
  desc.image_row_pitch = w * sizeof(uint32_t);
  OCL_CREATE_IMAGE(buf[0], CL_MEM_COPY_HOST_PTR, &format, &desc, buf_data[0]);

  desc.image_row_pitch = 0;
  OCL_CREATE_IMAGE(buf[1], 0, &format, &desc, NULL);
  OCL_CREATE_SAMPLER(sampler, CL_ADDRESS_REPEAT, CL_FILTER_NEAREST);
  free(buf_data[0]);
  buf_data[0] = NULL;

  // Run the kernel
  OCL_SET_ARG(0, sizeof(cl_mem), &buf[0]);
  OCL_SET_ARG(1, sizeof(cl_mem), &buf[1]);
  OCL_SET_ARG(2, sizeof(sampler), &sampler);
  globals[0] = w;
  globals[1] = h;
  locals[0] = 16;
  locals[1] = 16;
  OCL_NDRANGE(2);

  // Check result
  OCL_MAP_BUFFER(0);
  OCL_MAP_BUFFER(1);
  // Just compare the initial 2 data is enough for this case, as the initial 2 data must in the first
  // tile box and we can just get the correct coords.
  for (uint32_t j = 0; j < 1; ++j)
    for (uint32_t i = 0; i < 3; i++)
    {
      if (i < w - 1)
       OCL_ASSERT(((uint32_t*)buf_data[0])[j * w + i + 1] == ((uint32_t*)buf_data[1])[j * w + i]);
    }
  OCL_UNMAP_BUFFER(0);
  OCL_UNMAP_BUFFER(1);
}

MAKE_UTEST_FROM_FUNCTION(compiler_movforphi_undef);
<commit_msg>utests: Fix a bug in movforphi test case.<commit_after>#include "utest_helper.hpp"

static void compiler_movforphi_undef(void)
{
  const size_t w = 16;
  const size_t h = 16;
  cl_sampler sampler;
  cl_image_format format;
  cl_image_desc desc;

  // Setup kernel and images
  OCL_CREATE_KERNEL("test_movforphi_undef");
  buf_data[0] = (uint32_t*) malloc(sizeof(uint32_t) * w * h);
  for (uint32_t j = 0; j < h; ++j)
    for (uint32_t i = 0; i < w; i++)
      ((uint32_t*)buf_data[0])[j * w + i] = j * w + i;

  format.image_channel_order = CL_RGBA;
  format.image_channel_data_type = CL_UNSIGNED_INT8;
  desc.image_type = CL_MEM_OBJECT_IMAGE2D;
  desc.image_width = w;
  desc.image_height = h;
  desc.image_row_pitch = w * sizeof(uint32_t);
  OCL_CREATE_IMAGE(buf[0], CL_MEM_COPY_HOST_PTR, &format, &desc, buf_data[0]);

  desc.image_row_pitch = 0;
  OCL_CREATE_IMAGE(buf[1], 0, &format, &desc, NULL);
  OCL_CREATE_SAMPLER(sampler, CL_ADDRESS_REPEAT, CL_FILTER_NEAREST);
  free(buf_data[0]);
  buf_data[0] = NULL;

  // Run the kernel
  OCL_SET_ARG(0, sizeof(cl_mem), &buf[0]);
  OCL_SET_ARG(1, sizeof(cl_mem), &buf[1]);
  OCL_SET_ARG(2, sizeof(sampler), &sampler);
  globals[0] = w;
  globals[1] = h;
  locals[0] = 16;
  locals[1] = 16;
  OCL_NDRANGE(2);

  // Check result
  OCL_MAP_BUFFER(0);
  OCL_MAP_BUFFER(1);
  // Just compare the initial 2 data is enough for this case, as the initial 2 data must in the first
  // tile box and we can just get the correct coords.
  for (uint32_t j = 0; j < 1; ++j)
    for (uint32_t i = 0; i < 3; i++)
    {
      if (i == 0)
        OCL_ASSERT(((uint32_t*)buf_data[0])[j * w + i + 1] == ((uint32_t*)buf_data[1])[j * w + i]);
    }
  OCL_UNMAP_BUFFER(0);
  OCL_UNMAP_BUFFER(1);
}

MAKE_UTEST_FROM_FUNCTION(compiler_movforphi_undef);
<|endoftext|>


<commit_before>//===- SubtargetEmitter.cpp - Generate subtarget enumerations -------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file was developed by James M. Laskey and is distributed under
// the University of Illinois Open Source License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This tablegen backend emits subtarget enumerations.
//
//===----------------------------------------------------------------------===//

#include "SubtargetEmitter.h"
#include "CodeGenTarget.h"
#include "Record.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/Debug.h"
#include <algorithm>
#include <set>
using namespace llvm;

//
// Convenience types.
//
typedef std::vector<Record*> RecordList;
typedef std::vector<Record*>::iterator RecordListIter;

//
// Record sort by name function.
//
struct LessRecord {
  bool operator()(const Record *Rec1, const Record *Rec2) const {
    return Rec1->getName() < Rec2->getName();
  }
};

//
// Record sort by field "Name" function.
//
struct LessRecordFieldName {
  bool operator()(const Record *Rec1, const Record *Rec2) const {
    return Rec1->getValueAsString("Name") < Rec2->getValueAsString("Name");
  }
};

//
// Enumeration - Emit the specified class as an enumeration.
//
void SubtargetEmitter::Enumeration(std::ostream &OS,
                                   const char *ClassName,
                                   bool isBits) {
  RecordList Defs = Records.getAllDerivedDefinitions(ClassName);
  sort(Defs.begin(), Defs.end(), LessRecord());

  int i = 0;

  OS << "enum {\n";

  for (RecordListIter RI = Defs.begin(), E = Defs.end(); RI != E;) {
    Record *R = *RI++;
    std::string Instance = R->getName();
    OS << "  "
       << Instance;
    if (isBits) {
      OS << " = "
         << " 1 << " << i++;
    }
    OS << ((RI != E) ? ",\n" : "\n");
  }

  OS << "};\n";
}

//
// FeatureKeyValues - Emit data of all the subtarget features.  Used by command
// line.
//
void SubtargetEmitter::FeatureKeyValues(std::ostream &OS) {
  RecordList Features = Records.getAllDerivedDefinitions("SubtargetFeature");
  sort(Features.begin(), Features.end(), LessRecord());

  OS << "// Sorted (by key) array of values for CPU features.\n"
     << "static llvm::SubtargetFeatureKV FeatureKV[] = {\n";
  for (RecordListIter RI = Features.begin(), E = Features.end(); RI != E;) {
    Record *R = *RI++;
    std::string Instance = R->getName();
    std::string Name = R->getValueAsString("Name");
    std::string Desc = R->getValueAsString("Desc");
    OS << "  { "
       << "\"" << Name << "\", "
       << "\"" << Desc << "\", "
       << Instance
       << ((RI != E) ? " },\n" : " }\n");
  }
  OS << "};\n";

  OS<<"\nenum {\n";
  OS<<"  FeatureKVSize = sizeof(FeatureKV)/sizeof(llvm::SubtargetFeatureKV)\n";
  OS<<"};\n";
}

//
// CPUKeyValues - Emit data of all the subtarget processors.  Used by command
// line.
//
void SubtargetEmitter::CPUKeyValues(std::ostream &OS) {
  RecordList Processors = Records.getAllDerivedDefinitions("Processor");
  sort(Processors.begin(), Processors.end(), LessRecordFieldName());

  OS << "// Sorted (by key) array of values for CPU subtype.\n"
     << "static const llvm::SubtargetFeatureKV SubTypeKV[] = {\n";
  for (RecordListIter RI = Processors.begin(), E = Processors.end();
       RI != E;) {
    Record *R = *RI++;
    std::string Name = R->getValueAsString("Name");
    Record *ProcItin = R->getValueAsDef("ProcItin");
    ListInit *Features = R->getValueAsListInit("Features");
    unsigned N = Features->getSize();
    OS << "  { "
       << "\"" << Name << "\", "
       << "\"Select the " << Name << " processor\", ";

    if (N == 0) {
      OS << "0";
    } else {
      for (unsigned i = 0; i < N; ) {
        if (DefInit *DI = dynamic_cast<DefInit*>(Features->getElement(i++))) {
          Record *Feature = DI->getDef();
          std::string Name = Feature->getName();
          OS << Name;
          if (i != N) OS << " | ";
        } else {
          throw "Feature: " + Name +
                " expected feature in processor feature list!";
        }
      }
    }

    OS << ((RI != E) ? " },\n" : " }\n");
  }
  OS << "};\n";

  OS<<"\nenum {\n";
  OS<<"  SubTypeKVSize = sizeof(SubTypeKV)/sizeof(llvm::SubtargetFeatureKV)\n";
  OS<<"};\n";
}

//
// ParseFeaturesFunction - Produces a subtarget specific function for parsing
// the subtarget features string.
//
void SubtargetEmitter::ParseFeaturesFunction(std::ostream &OS) {
  RecordList Features = Records.getAllDerivedDefinitions("SubtargetFeature");
  sort(Features.begin(), Features.end(), LessRecord());

  OS << "// ParseSubtargetFeatures - Parses features string setting specified\n"
        "// subtarget options.\n"
        "void llvm::";
  OS << Target;
  OS << "Subtarget::ParseSubtargetFeatures(const std::string &FS,\n"
        "                                  const std::string &CPU) {\n"
        "  SubtargetFeatures Features(FS);\n"
        "  Features.setCPUIfNone(CPU);\n"
        "  uint32_t Bits =  Features.getBits(SubTypeKV, SubTypeKVSize,\n"
        "                                    FeatureKV, FeatureKVSize);\n";

  for (RecordListIter RI = Features.begin(), E = Features.end(); RI != E;) {
    Record *R = *RI++;
    std::string Instance = R->getName();
    std::string Name = R->getValueAsString("Name");
    std::string Type = R->getValueAsString("Type");
    std::string Attribute = R->getValueAsString("Attribute");

    OS << "  " << Attribute << " = (Bits & " << Instance << ") != 0;\n";
  }
  OS << "}\n";
}

//
// SubtargetEmitter::run - Main subtarget enumeration emitter.
//
void SubtargetEmitter::run(std::ostream &OS) {
  std::vector<Record*> Targets = Records.getAllDerivedDefinitions("Target");
  if (Targets.size() == 0)
    throw std::string("ERROR: No 'Target' subclasses defined!");
  if (Targets.size() != 1)
    throw std::string("ERROR: Multiple subclasses of Target defined!");
  Target = Targets[0]->getName();

  EmitSourceFileHeader("Subtarget Enumeration Source Fragment", OS);

  OS << "#include \"llvm/Target/SubtargetFeature.h\"\n\n";

  Enumeration(OS, "FuncUnit", true);
  OS<<"\n";
  Enumeration(OS, "InstrItinClass", false);
  OS<<"\n";
  Enumeration(OS, "SubtargetFeature", true);
  OS<<"\n";
  FeatureKeyValues(OS);
  OS<<"\n";
  CPUKeyValues(OS);
  OS<<"\n";
  ParseFeaturesFunction(OS);
}
<commit_msg>Simplify.<commit_after>//===- SubtargetEmitter.cpp - Generate subtarget enumerations -------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file was developed by James M. Laskey and is distributed under
// the University of Illinois Open Source License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This tablegen backend emits subtarget enumerations.
//
//===----------------------------------------------------------------------===//

#include "SubtargetEmitter.h"
#include "CodeGenTarget.h"
#include "Record.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/Debug.h"
#include <algorithm>
#include <set>
using namespace llvm;

//
// Convenience types.
//
typedef std::vector<Record*> RecordList;
typedef std::vector<Record*>::iterator RecordListIter;

//
// Record sort by name function.
//
struct LessRecord {
  bool operator()(const Record *Rec1, const Record *Rec2) const {
    return Rec1->getName() < Rec2->getName();
  }
};

//
// Record sort by field "Name" function.
//
struct LessRecordFieldName {
  bool operator()(const Record *Rec1, const Record *Rec2) const {
    return Rec1->getValueAsString("Name") < Rec2->getValueAsString("Name");
  }
};

//
// Enumeration - Emit the specified class as an enumeration.
//
void SubtargetEmitter::Enumeration(std::ostream &OS,
                                   const char *ClassName,
                                   bool isBits) {
  RecordList Defs = Records.getAllDerivedDefinitions(ClassName);
  sort(Defs.begin(), Defs.end(), LessRecord());

  int i = 0;

  OS << "enum {\n";

  for (RecordListIter RI = Defs.begin(), E = Defs.end(); RI != E;) {
    Record *R = *RI++;
    std::string Instance = R->getName();
    OS << "  "
       << Instance;
    if (isBits) {
      OS << " = "
         << " 1 << " << i++;
    }
    OS << ((RI != E) ? ",\n" : "\n");
  }

  OS << "};\n";
}

//
// FeatureKeyValues - Emit data of all the subtarget features.  Used by command
// line.
//
void SubtargetEmitter::FeatureKeyValues(std::ostream &OS) {
  RecordList Features = Records.getAllDerivedDefinitions("SubtargetFeature");
  sort(Features.begin(), Features.end(), LessRecord());

  OS << "// Sorted (by key) array of values for CPU features.\n"
     << "static llvm::SubtargetFeatureKV FeatureKV[] = {\n";
  for (RecordListIter RI = Features.begin(), E = Features.end(); RI != E;) {
    Record *R = *RI++;
    std::string Instance = R->getName();
    std::string Name = R->getValueAsString("Name");
    std::string Desc = R->getValueAsString("Desc");
    OS << "  { "
       << "\"" << Name << "\", "
       << "\"" << Desc << "\", "
       << Instance
       << ((RI != E) ? " },\n" : " }\n");
  }
  OS << "};\n";

  OS<<"\nenum {\n";
  OS<<"  FeatureKVSize = sizeof(FeatureKV)/sizeof(llvm::SubtargetFeatureKV)\n";
  OS<<"};\n";
}

//
// CPUKeyValues - Emit data of all the subtarget processors.  Used by command
// line.
//
void SubtargetEmitter::CPUKeyValues(std::ostream &OS) {
  RecordList Processors = Records.getAllDerivedDefinitions("Processor");
  sort(Processors.begin(), Processors.end(), LessRecordFieldName());

  OS << "// Sorted (by key) array of values for CPU subtype.\n"
     << "static const llvm::SubtargetFeatureKV SubTypeKV[] = {\n";
  for (RecordListIter RI = Processors.begin(), E = Processors.end();
       RI != E;) {
    Record *R = *RI++;
    std::string Name = R->getValueAsString("Name");
    Record *ProcItin = R->getValueAsDef("ProcItin");
    ListInit *Features = R->getValueAsListInit("Features");
    unsigned N = Features->getSize();
    OS << "  { "
       << "\"" << Name << "\", "
       << "\"Select the " << Name << " processor\", ";

    if (N == 0) {
      OS << "0";
    } else {
      for (unsigned i = 0; i < N; ) {
        if (DefInit *DI = dynamic_cast<DefInit*>(Features->getElement(i++))) {
          Record *Feature = DI->getDef();
          std::string Name = Feature->getName();
          OS << Name;
          if (i != N) OS << " | ";
        } else {
          throw "Feature: " + Name +
                " expected feature in processor feature list!";
        }
      }
    }

    OS << ((RI != E) ? " },\n" : " }\n");
  }
  OS << "};\n";

  OS<<"\nenum {\n";
  OS<<"  SubTypeKVSize = sizeof(SubTypeKV)/sizeof(llvm::SubtargetFeatureKV)\n";
  OS<<"};\n";
}

//
// ParseFeaturesFunction - Produces a subtarget specific function for parsing
// the subtarget features string.
//
void SubtargetEmitter::ParseFeaturesFunction(std::ostream &OS) {
  RecordList Features = Records.getAllDerivedDefinitions("SubtargetFeature");
  sort(Features.begin(), Features.end(), LessRecord());

  OS << "// ParseSubtargetFeatures - Parses features string setting specified\n"
        "// subtarget options.\n"
        "void llvm::";
  OS << Target;
  OS << "Subtarget::ParseSubtargetFeatures(const std::string &FS,\n"
        "                                  const std::string &CPU) {\n"
        "  SubtargetFeatures Features(FS);\n"
        "  Features.setCPUIfNone(CPU);\n"
        "  uint32_t Bits =  Features.getBits(SubTypeKV, SubTypeKVSize,\n"
        "                                    FeatureKV, FeatureKVSize);\n";

  for (RecordListIter RI = Features.begin(), E = Features.end(); RI != E;) {
    Record *R = *RI++;
    std::string Instance = R->getName();
    std::string Name = R->getValueAsString("Name");
    std::string Type = R->getValueAsString("Type");
    std::string Attribute = R->getValueAsString("Attribute");

    OS << "  " << Attribute << " = (Bits & " << Instance << ") != 0;\n";
  }
  OS << "}\n";
}

//
// SubtargetEmitter::run - Main subtarget enumeration emitter.
//
void SubtargetEmitter::run(std::ostream &OS) {
  Target = CodeGenTarget().getName();

  EmitSourceFileHeader("Subtarget Enumeration Source Fragment", OS);

  OS << "#include \"llvm/Target/SubtargetFeature.h\"\n\n";

  Enumeration(OS, "FuncUnit", true);
  OS<<"\n";
  Enumeration(OS, "InstrItinClass", false);
  OS<<"\n";
  Enumeration(OS, "SubtargetFeature", true);
  OS<<"\n";
  FeatureKeyValues(OS);
  OS<<"\n";
  CPUKeyValues(OS);
  OS<<"\n";
  ParseFeaturesFunction(OS);
}
<|endoftext|>


<commit_before>// Copyright (c) 2010 The Chromium 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 "base/command_line.h"
#include "chrome/browser/browser.h"
#include "chrome/browser/pref_service.h"
#include "chrome/browser/profile.h"
#include "chrome/browser/sync/profile_sync_service.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/live_sync/profile_sync_service_test_harness.h"
#include "chrome/test/live_sync/live_sync_test.h"

class TwoClientLivePreferencesSyncTest : public LiveSyncTest {
 protected:
  TwoClientLivePreferencesSyncTest() {
    // This makes sure browser is visible and active while running test.
    InProcessBrowserTest::set_show_window(true);
    // Set the initial timeout value to 5 min.
    InProcessBrowserTest::SetInitialTimeoutInMS(300000);
  }
  ~TwoClientLivePreferencesSyncTest() {}

  void SetupSync() {
    client1_.reset(new ProfileSyncServiceTestHarness(
        browser()->profile(), username_, password_));
    profile2_.reset(MakeProfile(FILE_PATH_LITERAL("client2")));
    client2_.reset(new ProfileSyncServiceTestHarness(
        profile2_.get(), username_, password_));
    EXPECT_TRUE(client1_->SetupSync());
    EXPECT_TRUE(client1_->AwaitSyncCycleCompletion("Initial setup 1"));
    EXPECT_TRUE(client2_->SetupSync());
    EXPECT_TRUE(client2_->AwaitSyncCycleCompletion("Initial setup 2"));
  }

  void Cleanup() {
    client2_.reset();
    profile2_.reset();
  }

  ProfileSyncServiceTestHarness* client1() { return client1_.get(); }
  ProfileSyncServiceTestHarness* client2() { return client2_.get(); }

  PrefService* prefs1() { return browser()->profile()->GetPrefs(); }
  PrefService* prefs2() { return profile2_->GetPrefs(); }

 private:
  scoped_ptr<ProfileSyncServiceTestHarness> client1_;
  scoped_ptr<ProfileSyncServiceTestHarness> client2_;
  scoped_ptr<Profile> profile2_;

  DISALLOW_COPY_AND_ASSIGN(TwoClientLivePreferencesSyncTest);
};

IN_PROC_BROWSER_TEST_F(TwoClientLivePreferencesSyncTest, Sanity) {
  SetupSync();

  EXPECT_EQ(false, prefs1()->GetBoolean(prefs::kHomePageIsNewTabPage));
  EXPECT_EQ(false, prefs2()->GetBoolean(prefs::kHomePageIsNewTabPage));

  PrefService* expected = LiveSyncTest::MakeProfile(
      FILE_PATH_LITERAL("verifier"))->GetPrefs();
  expected->SetBoolean(prefs::kHomePageIsNewTabPage, true);

  prefs1()->SetBoolean(prefs::kHomePageIsNewTabPage, true);
  EXPECT_TRUE(client2()->AwaitMutualSyncCycleCompletion(client1()));

  EXPECT_EQ(expected->GetBoolean(prefs::kHomePageIsNewTabPage),
            prefs1()->GetBoolean(prefs::kHomePageIsNewTabPage));
  EXPECT_EQ(expected->GetBoolean(prefs::kHomePageIsNewTabPage),
            prefs2()->GetBoolean(prefs::kHomePageIsNewTabPage));

  Cleanup();
}

IN_PROC_BROWSER_TEST_F(TwoClientLivePreferencesSyncTest, Race) {
  SetupSync();

  prefs1()->SetString(prefs::kHomePage, L"http://www.google.com/1");
  prefs2()->SetString(prefs::kHomePage, L"http://www.google.com/2");

  EXPECT_TRUE(client2()->AwaitMutualSyncCycleCompletion(client1()));

  EXPECT_EQ(prefs1()->GetString(prefs::kHomePage),
            prefs2()->GetString(prefs::kHomePage));

  Cleanup();
}
<commit_msg>In the sync integration tests for preferences, don't assume a specific initial value for kHomePageIsNewTabPage.<commit_after>// Copyright (c) 2010 The Chromium 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 "base/command_line.h"
#include "chrome/browser/browser.h"
#include "chrome/browser/pref_service.h"
#include "chrome/browser/profile.h"
#include "chrome/browser/sync/profile_sync_service.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/live_sync/profile_sync_service_test_harness.h"
#include "chrome/test/live_sync/live_sync_test.h"

class TwoClientLivePreferencesSyncTest : public LiveSyncTest {
 protected:
  TwoClientLivePreferencesSyncTest() {
    // This makes sure browser is visible and active while running test.
    InProcessBrowserTest::set_show_window(true);
    // Set the initial timeout value to 5 min.
    InProcessBrowserTest::SetInitialTimeoutInMS(300000);
  }
  ~TwoClientLivePreferencesSyncTest() {}

  void SetupSync() {
    client1_.reset(new ProfileSyncServiceTestHarness(
        browser()->profile(), username_, password_));
    profile2_.reset(MakeProfile(FILE_PATH_LITERAL("client2")));
    client2_.reset(new ProfileSyncServiceTestHarness(
        profile2_.get(), username_, password_));
    EXPECT_TRUE(client1_->SetupSync());
    EXPECT_TRUE(client1_->AwaitSyncCycleCompletion("Initial setup 1"));
    EXPECT_TRUE(client2_->SetupSync());
    EXPECT_TRUE(client2_->AwaitSyncCycleCompletion("Initial setup 2"));
  }

  void Cleanup() {
    client2_.reset();
    profile2_.reset();
  }

  ProfileSyncServiceTestHarness* client1() { return client1_.get(); }
  ProfileSyncServiceTestHarness* client2() { return client2_.get(); }

  PrefService* prefs1() { return browser()->profile()->GetPrefs(); }
  PrefService* prefs2() { return profile2_->GetPrefs(); }

 private:
  scoped_ptr<ProfileSyncServiceTestHarness> client1_;
  scoped_ptr<ProfileSyncServiceTestHarness> client2_;
  scoped_ptr<Profile> profile2_;

  DISALLOW_COPY_AND_ASSIGN(TwoClientLivePreferencesSyncTest);
};

IN_PROC_BROWSER_TEST_F(TwoClientLivePreferencesSyncTest, Sanity) {
  SetupSync();

  EXPECT_EQ(prefs1()->GetBoolean(prefs::kHomePageIsNewTabPage),
            prefs2()->GetBoolean(prefs::kHomePageIsNewTabPage));

  PrefService* expected = LiveSyncTest::MakeProfile(
      FILE_PATH_LITERAL("verifier"))->GetPrefs();
  bool value = !expected->GetBoolean(prefs::kHomePageIsNewTabPage);
  expected->SetBoolean(prefs::kHomePageIsNewTabPage, value);

  prefs1()->SetBoolean(prefs::kHomePageIsNewTabPage, value);
  EXPECT_TRUE(client2()->AwaitMutualSyncCycleCompletion(client1()));

  EXPECT_EQ(expected->GetBoolean(prefs::kHomePageIsNewTabPage),
            prefs1()->GetBoolean(prefs::kHomePageIsNewTabPage));
  EXPECT_EQ(expected->GetBoolean(prefs::kHomePageIsNewTabPage),
            prefs2()->GetBoolean(prefs::kHomePageIsNewTabPage));

  Cleanup();
}

IN_PROC_BROWSER_TEST_F(TwoClientLivePreferencesSyncTest, Race) {
  SetupSync();

  prefs1()->SetString(prefs::kHomePage, L"http://www.google.com/1");
  prefs2()->SetString(prefs::kHomePage, L"http://www.google.com/2");

  EXPECT_TRUE(client2()->AwaitMutualSyncCycleCompletion(client1()));

  EXPECT_EQ(prefs1()->GetString(prefs::kHomePage),
            prefs2()->GetString(prefs::kHomePage));

  Cleanup();
}
<|endoftext|>