From e66d04a4eb86446c1dcfd8fa96e826d4f6d7911e Mon Sep 17 00:00:00 2001 From: Tessa Todorowski Date: Tue, 25 Jun 2024 14:13:55 +0200 Subject: [PATCH] summarize Events in PerfEvent & PerfEventInstance class --- CMakeLists.txt | 1 + .../lo2s/perf/counter/counter_collection.hpp | 10 +- .../lo2s/perf/counter/counter_provider.hpp | 8 +- include/lo2s/perf/counter/group/reader.hpp | 17 +- .../lo2s/perf/counter/userspace/reader.hpp | 3 +- include/lo2s/perf/event_description.hpp | 110 ------ include/lo2s/perf/event_provider.hpp | 51 +-- include/lo2s/perf/io_reader.hpp | 53 +-- include/lo2s/perf/multi_reader.hpp | 1 + include/lo2s/perf/reader.hpp | 284 +++++++++++++++ include/lo2s/perf/sample/reader.hpp | 141 ++------ include/lo2s/perf/syscall/reader.hpp | 116 ++---- include/lo2s/perf/time/reader.hpp | 4 +- include/lo2s/perf/tracepoint/reader.hpp | 52 +-- include/lo2s/perf/util.hpp | 8 - include/lo2s/platform.hpp | 4 +- include/lo2s/trace/reg_keys.hpp | 10 +- include/lo2s/trace/trace.hpp | 7 +- src/config.cpp | 12 +- src/perf/counter/counter_provider.cpp | 30 +- src/perf/counter/group/reader.cpp | 137 ++++--- src/perf/counter/userspace/reader.cpp | 51 ++- src/perf/event_provider.cpp | 217 +++++------ src/perf/reader.cpp | 341 ++++++++++++++++++ src/perf/time/reader.cpp | 40 +- src/perf/util.cpp | 63 +--- src/platform.cpp | 2 +- 27 files changed, 998 insertions(+), 775 deletions(-) delete mode 100644 include/lo2s/perf/event_description.hpp create mode 100644 include/lo2s/perf/reader.hpp create mode 100644 src/perf/reader.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7eb87554..608d5d7c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -164,6 +164,7 @@ set(SOURCE_FILES src/process_controller.cpp src/perf/event_provider.cpp + src/perf/reader.cpp src/perf/bio/block_device.cpp src/perf/counter/counter_provider.cpp diff --git a/include/lo2s/perf/counter/counter_collection.hpp b/include/lo2s/perf/counter/counter_collection.hpp index b8f9f248..df4bb66c 100644 --- a/include/lo2s/perf/counter/counter_collection.hpp +++ b/include/lo2s/perf/counter/counter_collection.hpp @@ -21,7 +21,7 @@ #pragma once -#include +#include #include @@ -33,18 +33,18 @@ namespace counter { struct CounterCollection { - EventDescription leader; - std::vector counters; + SysfsEvent leader; + std::vector counters; double get_scale(int index) const { if (index == 0) { - return leader.scale; + return leader.get_scale(); } else { - return counters[index - 1].scale; + return counters[index - 1].get_scale(); } } diff --git a/include/lo2s/perf/counter/counter_provider.hpp b/include/lo2s/perf/counter/counter_provider.hpp index 29290a4c..850b640f 100644 --- a/include/lo2s/perf/counter/counter_provider.hpp +++ b/include/lo2s/perf/counter/counter_provider.hpp @@ -23,7 +23,7 @@ #include #include -#include +#include #include @@ -56,9 +56,9 @@ class CounterProvider CounterCollection collection_for(MeasurementScope scope); private: - EventDescription group_leader_; - std::vector group_events_; - std::vector userspace_events_; + SysfsEvent group_leader_; + std::vector group_events_; + std::vector userspace_events_; }; } // namespace counter } // namespace perf diff --git a/include/lo2s/perf/counter/group/reader.hpp b/include/lo2s/perf/counter/group/reader.hpp index 9d32a90f..2d0ed070 100644 --- a/include/lo2s/perf/counter/group/reader.hpp +++ b/include/lo2s/perf/counter/group/reader.hpp @@ -24,6 +24,7 @@ #include #include #include +#include #include @@ -58,21 +59,9 @@ class Reader : public EventReader struct GroupReadFormat v; }; - ~Reader() - { - for (int fd : counter_fds_) - { - if (fd != -1) - { - ::close(fd); - } - } - ::close(group_leader_fd_); - } - protected: - int group_leader_fd_; - std::vector counter_fds_; + PerfEventInstance counter_leader_; + std::vector counters_; CounterCollection counter_collection_; GroupCounterBuffer counter_buffer_; }; diff --git a/include/lo2s/perf/counter/userspace/reader.hpp b/include/lo2s/perf/counter/userspace/reader.hpp index 0a83bdd9..1d92939b 100644 --- a/include/lo2s/perf/counter/userspace/reader.hpp +++ b/include/lo2s/perf/counter/userspace/reader.hpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -55,11 +56,11 @@ class Reader } protected: - std::vector counter_fds_; CounterCollection counter_collection_; UserspaceCounterBuffer counter_buffer_; int timer_fd_; + std::vector counters_; std::vector data_; }; } // namespace userspace diff --git a/include/lo2s/perf/event_description.hpp b/include/lo2s/perf/event_description.hpp deleted file mode 100644 index 6b7eaedb..00000000 --- a/include/lo2s/perf/event_description.hpp +++ /dev/null @@ -1,110 +0,0 @@ -/* - * This file is part of the lo2s software. - * Linux OTF2 sampling - * - * Copyright (c) 2017, - * Technische Universitaet Dresden, Germany - * - * lo2s is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * lo2s is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with lo2s. If not, see . - */ - -#pragma once - -#include -#include - -#include -#include - -extern "C" -{ -#include -} - -namespace lo2s -{ -namespace perf -{ -enum class Availability -{ - UNAVAILABLE, - SYSTEM_MODE, - PROCESS_MODE, - UNIVERSAL -}; - -struct EventDescription -{ - EventDescription(const std::string& name, perf_type_id type, std::uint64_t config, - std::uint64_t config1 = 0, std::set cpus = std::set(), - double scale = 1, std::string unit = "#", - Availability availability = Availability::UNAVAILABLE) - : name(name), type(type), config(config), config1(config1), scale(scale), unit(unit), - availability(availability), cpus_(cpus) - { - } - - EventDescription() - : name(""), type(static_cast(-1)), config(0), config1(0), scale(1), unit("#"), - availability(Availability::UNAVAILABLE) - { - } - - const std::set& supported_cpus() const - { - if (cpus_.empty()) - { - return Topology::instance().cpus(); - } - return cpus_; - } - - bool is_supported_in(ExecutionScope scope) const - { - // per-process should always work. the counter will just not count if the process is - // scheduled on a core that is not supprted by that counter - return scope.is_thread() || cpus_.empty() || cpus_.count(scope.as_cpu()); - } - - friend bool operator==(const EventDescription& lhs, const EventDescription& rhs) - { - return (lhs.type == rhs.type) && (lhs.config == rhs.config) && (lhs.config1 == rhs.config1); - } - - friend bool operator<(const EventDescription& lhs, const EventDescription& rhs) - { - if (lhs.type == rhs.type) - { - if (lhs.config == rhs.config) - { - return lhs.config1 < rhs.config1; - } - return lhs.config < rhs.config; - } - return lhs.type < rhs.type; - } - - std::string name; - perf_type_id type; - std::uint64_t config; - std::uint64_t config1; - double scale; - std::string unit; - Availability availability; - -private: - std::set cpus_; -}; -} // namespace perf -} // namespace lo2s diff --git a/include/lo2s/perf/event_provider.hpp b/include/lo2s/perf/event_provider.hpp index a0200f81..a9fed024 100644 --- a/include/lo2s/perf/event_provider.hpp +++ b/include/lo2s/perf/event_provider.hpp @@ -26,7 +26,7 @@ #include #include -#include +#include namespace lo2s { @@ -36,43 +36,6 @@ namespace perf class EventProvider { public: - struct DescriptionCache - { - private: - DescriptionCache() - : description(std::string(), static_cast(-1), 0, 0), valid_(false) - { - } - - public: - DescriptionCache(const EventDescription& description) - : description(description), valid_(true) - { - } - - DescriptionCache(EventDescription&& description) - : description(std::move(description)), valid_(true) - { - } - - static DescriptionCache make_invalid() - { - return DescriptionCache(); - } - - bool is_valid() const - { - return valid_; - } - - EventDescription description; - - private: - bool valid_; - }; - - using EventMap = std::unordered_map; - EventProvider(); EventProvider(const EventProvider&) = delete; void operator=(const EventProvider&) = delete; @@ -82,14 +45,14 @@ class EventProvider return instance_mutable(); } - static EventDescription get_event_by_name(const std::string& name); + static SysfsEvent get_event_by_name(const std::string& name); static bool has_event(const std::string& name); - static std::vector get_predefined_events(); - static std::vector get_pmu_events(); + static std::vector get_predefined_events(); + static std::vector get_pmu_events(); - static EventDescription fallback_metric_leader_event(); + static SysfsEvent fallback_metric_leader_event(); class InvalidEvent : public std::runtime_error { @@ -107,9 +70,9 @@ class EventProvider return e; } - EventDescription cache_event(const std::string& name); + SysfsEvent cache_event(const std::string& name); - EventMap event_map_; + std::unordered_map event_map_; }; } // namespace perf } // namespace lo2s diff --git a/include/lo2s/perf/io_reader.hpp b/include/lo2s/perf/io_reader.hpp index 5a95221c..2344e8ec 100644 --- a/include/lo2s/perf/io_reader.hpp +++ b/include/lo2s/perf/io_reader.hpp @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -92,15 +93,13 @@ class IoReader : public PullReader public: IoReader(IoReaderIdentity identity) : identity_(identity) { - struct perf_event_attr attr = common_perf_event_attrs(); - attr.type = PERF_TYPE_TRACEPOINT; + perf::TracepointEvent event(0, identity.tracepoint.id()); - attr.config = identity.tracepoint.id(); - - attr.sample_period = 1; - attr.sample_type = PERF_SAMPLE_RAW | PERF_SAMPLE_TIME; - fd_ = perf_event_open(&attr, identity.cpu.as_scope(), -1, 0); - if (fd_ < 0) + try + { + ev_instance_ = event.open(identity.cpu); + } + catch (const std::system_error& e) { Log::error() << "perf_event_open for raw tracepoint failed."; throw_errno(); @@ -110,45 +109,21 @@ class IoReader : public PullReader try { - if (fcntl(fd_, F_SETFL, O_NONBLOCK)) - { - throw_errno(); - } - - init_mmap(fd_); + init_mmap(ev_instance_.get_fd()); Log::debug() << "perf_tracepoint_reader mmap initialized"; - auto ret = ioctl(fd_, PERF_EVENT_IOC_ENABLE); - Log::debug() << "perf_tracepoint_reader ioctl(fd, PERF_EVENT_IOC_ENABLE) = " << ret; - if (ret == -1) - { - throw_errno(); - } + ev_instance_.enable(); } catch (...) { Log::error() << "Couldn't initialize block:rq_insert reading"; - close(fd_); throw; } } - ~IoReader() - { - if (fd_ != -1) - { - close(fd_); - } - } - void stop() { - auto ret = ioctl(fd_, PERF_EVENT_IOC_DISABLE); - Log::debug() << "perf_tracepoint_reader ioctl(fd, PERF_EVENT_IOC_DISABLE) = " << ret; - if (ret == -1) - { - throw_errno(); - } + ev_instance_.disable(); } TracepointSampleType* top() @@ -158,7 +133,7 @@ class IoReader : public PullReader int fd() const { - return fd_; + return ev_instance_.get_fd(); } IoReader& operator=(const IoReader&) = delete; @@ -168,19 +143,19 @@ class IoReader : public PullReader { PullReader::operator=(std::move(other)); std::swap(identity_, other.identity_); - std::swap(fd_, other.fd_); + std::swap(ev_instance_, other.ev_instance_); return *this; } IoReader(IoReader&& other) : PullReader(std::move(other)), identity_(other.identity_) { - std::swap(fd_, other.fd_); + std::swap(ev_instance_, other.ev_instance_); } private: IoReaderIdentity identity_; - int fd_ = -1; + perf::PerfEventInstance ev_instance_; }; } // namespace perf } // namespace lo2s diff --git a/include/lo2s/perf/multi_reader.hpp b/include/lo2s/perf/multi_reader.hpp index 3ddd8e51..f2d0f7a0 100644 --- a/include/lo2s/perf/multi_reader.hpp +++ b/include/lo2s/perf/multi_reader.hpp @@ -25,6 +25,7 @@ #include #include #include +#include #include diff --git a/include/lo2s/perf/reader.hpp b/include/lo2s/perf/reader.hpp new file mode 100644 index 00000000..cbd8df1c --- /dev/null +++ b/include/lo2s/perf/reader.hpp @@ -0,0 +1,284 @@ +/* + * This file is part of the lo2s software. + * Linux OTF2 sampling + * + * Copyright (c) 2017, + * Technische Universitaet Dresden, Germany + * + * lo2s is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * lo2s is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with lo2s. If not, see . + */ + +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifndef USE_HW_BREAKPOINT_COMPAT +extern "C" +{ +#include +} +#else +extern "C" +{ +#include +#include +} +#endif + +namespace lo2s +{ +namespace perf +{ + +enum class Availability +{ + UNAVAILABLE, + SYSTEM_MODE, + PROCESS_MODE, + UNIVERSAL +}; + +class PerfEventInstance; + +/** + * Base class for all Event types + * contains common attributes + */ +class PerfEvent +{ +public: + PerfEvent(bool enable_on_exec); + PerfEvent(); + + /** + * returns an opened instance of any PerfEvent object + * @param group_fd file descriptor of the leader event, omit if there is no leader + */ + PerfEventInstance open(std::variant location, int group_fd = -1, + int cgroup_fd = config().cgroup_fd); + + perf_event_attr& get_attr() + { + return attr_; + } + + double get_scale() const + { + return scale_; + } + + void set_scale(double scale) + { + scale_ = scale; + } + + std::string get_unit() const + { + return unit_; + } + + void set_unit(std::string unit) + { + unit_ = unit; + } + + friend bool operator==(const PerfEvent& lhs, const PerfEvent& rhs) + { + return !memcmp(&lhs.attr_, &rhs.attr_, sizeof(struct perf_event_attr)); + } + + friend bool operator<(const PerfEvent& lhs, const PerfEvent& rhs) + { + return memcmp(&lhs.attr_, &rhs.attr_, sizeof(struct perf_event_attr)); + } + + ~PerfEvent() + { + } + +protected: + struct perf_event_attr attr_; + double scale_ = 1; + std::string unit_ = "#"; +}; + +/** + * Contains an event parsed from sysfs + * @note call on either as_counter(), as_sample() or as_group_leader() after creation to get a valid + * event, otherwise the availability will be set to UNAVAILABLE + */ +class SysfsEvent : public PerfEvent +{ +public: + SysfsEvent(); + SysfsEvent(bool enable_on_exec, const std::string& ev_name); + SysfsEvent(std::string name, perf_type_id type, std::uint64_t config, std::uint64_t config1 = 0, + std::set cpus = std::set()); + + void as_counter(); + void as_group_leader(); + void as_sample(); + + const std::set& supported_cpus() const; + + // automatically determine availability + void set_availability(); + + const Availability& get_availability() const + { + return availability_; + }; + + bool degrade_percision(); + + bool is_valid() const; + + bool is_available_in(ExecutionScope scope) const + { + // per-process should always work. the counter will just not count if the process is + // scheduled on a core that is not supprted by that counter + return scope.is_thread() || cpus_.empty() || cpus_.count(scope.as_cpu()); + } + + std::string get_name() const + { + return name_; + } + + std::set get_cpus() const + { + return cpus_; + } + + void set_common_attrs(std::string name, perf_type_id type, std::uint64_t config, + std::uint64_t config1 = 0, std::set cpus = std::set()) + { + name_ = name; + cpus_ = cpus; + attr_.type = type; + attr_.config = config; + attr_.config1 = config1; + } + +private: + std::string name_ = ""; + std::set cpus_; + Availability availability_ = Availability::UNAVAILABLE; +}; + +class TimeEvent : public PerfEvent +{ +public: + TimeEvent(bool enable_on_exec, uint64_t addr); +}; + +/** + * Contains an event that is addressable via ID + * @note Call on as_syscall() after creation if needed + */ +class TracepointEvent : public PerfEvent +{ +public: + TracepointEvent(bool enable_on_exec, int event_id); + + void as_syscall() + { + attr_.sample_type |= PERF_SAMPLE_IDENTIFIER; + } +}; + +/** + * Contains a raw event for testing + */ +class RawEvent : public PerfEvent +{ +public: + RawEvent(bool enable_on_exec); +}; + +/** + * Contains an opened instance of PerfEvent. + * Use PerfEvent.open() to construct an object + */ +class PerfEventInstance +{ +public: + PerfEventInstance(); + PerfEventInstance(PerfEvent& ev, std::variant location, int group_fd, + int cgroup_fd); + + PerfEventInstance(PerfEventInstance&) = delete; + PerfEventInstance& operator=(const PerfEventInstance&) = delete; + + PerfEventInstance(PerfEventInstance&& other) + { + std::swap(fd_, other.fd_); + std::swap(ev_, other.ev_); + } + + PerfEventInstance& operator=(PerfEventInstance&& other) + { + std::swap(fd_, other.fd_); + std::swap(ev_, other.ev_); + return *this; + } + + void enable(); + void disable(); + + void set_output(const PerfEventInstance& other_ev); + void set_syscall_filter(); + + int get_fd() const + { + return fd_; + } + + bool is_valid() const + { + return fd_ >= 0; + }; + + template + T read() + { + T val; + if (::read(fd_, &val, sizeof(val)) != sizeof(T)) + { + throw std::system_error(errno, std::system_category()); + } + + return val; + } + + ~PerfEventInstance() + { + close(fd_); + } + +protected: + int fd_; + PerfEvent ev_; +}; + +} // namespace perf +} // namespace lo2s diff --git a/include/lo2s/perf/sample/reader.hpp b/include/lo2s/perf/sample/reader.hpp index ffec9d92..ffcb2155 100644 --- a/include/lo2s/perf/sample/reader.hpp +++ b/include/lo2s/perf/sample/reader.hpp @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -84,120 +85,61 @@ class Reader : public EventReader Log::debug() << "initializing event_reader for:" << scope.name() << ", enable_on_exec: " << enable_on_exec; - struct perf_event_attr perf_attr = common_perf_event_attrs(); + SysfsEvent event(enable_on_exec, config().sampling_event); - if (config().use_pebs) - { - perf_attr.use_clockid = 0; - } - - perf_attr.exclude_kernel = config().exclude_kernel; - perf_attr.sample_period = config().sampling_period; - - if (config().sampling) - { - EventDescription sampling_event = EventProvider::get_event_by_name( - config().sampling_event); // config parser has already - // checked for event - // availability, should not throw - - Log::debug() << "using sampling event \'" << config().sampling_event - << "\', period: " << config().sampling_period; - - perf_attr.type = sampling_event.type; - perf_attr.config = sampling_event.config; - perf_attr.config1 = sampling_event.config1; - - perf_attr.mmap = 1; - } - else - { - // Set up a dummy event for recording calling context enter/leaves only - perf_attr.type = PERF_TYPE_SOFTWARE; - perf_attr.config = PERF_COUNT_SW_DUMMY; - } - - perf_attr.sample_id_all = 1; - // Generate PERF_RECORD_COMM events to trace changes to the command - // name of a task. This is used to write a meaningful name for any - // traced thread to the archive. - perf_attr.comm = 1; - perf_attr.context_switch = 1; + event.as_sample(); - // We need this to get all mmap_events - if (enable_on_exec) - { - perf_attr.enable_on_exec = 1; - } - - // TODO see if we can remove remove tid - perf_attr.sample_type = - PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME | PERF_SAMPLE_CPU; - if (has_cct_) - { - perf_attr.sample_type |= PERF_SAMPLE_CALLCHAIN; - } - - perf_attr.precise_ip = 3; - /* precise_ip is an unsigned integer therefore we have to check if we get an underflow - * and the value of it is greater than the initial value */ do { - fd_ = perf_event_open(&perf_attr, scope, -1, 0, config().cgroup_fd); - - if (errno == EACCES && !perf_attr.exclude_kernel && perf_event_paranoid() > 1) + try { - perf_attr.exclude_kernel = 1; - - perf_warn_paranoid(); - - continue; + if (scope.is_cpu()) + { + ev_instance_ = event.open(scope.as_cpu()); + } + else + { + ev_instance_ = event.open(scope.as_thread()); + } } + catch (const std::system_error& e) + { + if (e.code().value() == EACCES && !event.get_attr().exclude_kernel && + perf_event_paranoid() > 1) + { + event.get_attr().exclude_kernel = 1; + perf_warn_paranoid(); + continue; + } - /* reduce exactness of IP can help if the kernel does not support really exact events */ - if (perf_attr.precise_ip == 0) - break; - else - perf_attr.precise_ip--; - } while (fd_ <= 0); + if (!event.degrade_percision()) + { + Log::error() << "perf_event_open for sampling failed: " << e.what(); - if (fd_ < 0) - { - Log::error() << "perf_event_open for sampling failed"; - if (perf_attr.use_clockid) - { - Log::error() << "maybe the specified clock is unavailable?"; + if (event.get_attr().use_clockid) + { + Log::error() << "maybe the specified clock is unavailable?"; + } + throw_errno(); + } } - throw_errno(); - } - Log::debug() << "Using precise_ip level: " << perf_attr.precise_ip; + } while (!ev_instance_.is_valid()); + + Log::debug() << "Using precise_ip level: " << event.get_attr().precise_ip; // Exception safe, so much wow! try { - // asynchronous delivery - // if (fcntl(fd, F_SETFL, O_ASYNC | O_NONBLOCK)) - if (fcntl(fd_, F_SETFL, O_NONBLOCK)) - { - throw_errno(); - } - - init_mmap(fd_); + init_mmap(ev_instance_.get_fd()); Log::debug() << "mmap initialized"; if (!enable_on_exec) { - auto ret = ioctl(fd_, PERF_EVENT_IOC_ENABLE); - Log::debug() << "ioctl(fd, PERF_EVENT_IOC_ENABLE) = " << ret; - if (ret == -1) - { - throw_errno(); - } + ev_instance_.enable(); } } catch (...) { - close(); throw; } } @@ -220,24 +162,13 @@ class Reader : public EventReader "extend to prevent the kernel from doing that" " or you can increase your sampling period."; } - close(); - } - -public: - void close() - { - if (fd_ != -1) - { - ::close(fd_); - fd_ = -1; - } } protected: bool has_cct_; private: - int fd_ = -1; + PerfEventInstance ev_instance_; }; } // namespace sample } // namespace perf diff --git a/include/lo2s/perf/syscall/reader.hpp b/include/lo2s/perf/syscall/reader.hpp index 40d04113..3fdf7088 100644 --- a/include/lo2s/perf/syscall/reader.hpp +++ b/include/lo2s/perf/syscall/reader.hpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -68,110 +69,51 @@ class Reader : public EventReader Reader(Cpu cpu) : cpu_(cpu) { - struct perf_event_attr attr = common_perf_event_attrs(); - attr.type = PERF_TYPE_TRACEPOINT; - attr.config = tracepoint::EventFormat("raw_syscalls:sys_enter").id(); - attr.sample_period = 1; - attr.sample_type = PERF_SAMPLE_RAW | PERF_SAMPLE_TIME | PERF_SAMPLE_IDENTIFIER; - - fd_ = perf_event_open(&attr, cpu.as_scope(), -1, 0, config().cgroup_fd); - if (fd_ < 0) + TracepointEvent enter_event(0, tracepoint::EventFormat("raw_syscalls:sys_enter").id()); + TracepointEvent exit_event(0, tracepoint::EventFormat("raw_syscalls:sys_exit").id()); + + enter_event.as_syscall(); + exit_event.as_syscall(); + + try { - Log::error() << "perf_event_open for raw tracepoint failed."; - throw_errno(); + // instance with tracepoint::EventFormat enter (default) + enter_ev_ = enter_event.open(cpu_); + exit_ev_ = exit_event.open(cpu_); } - - attr.config = tracepoint::EventFormat("raw_syscalls:sys_exit").id(); - other_fd_ = perf_event_open(&attr, cpu.as_scope(), -1, 0, config().cgroup_fd); - if (other_fd_ < 0) + catch (const std::system_error& e) { Log::error() << "perf_event_open for raw tracepoint failed."; throw_errno(); - close(fd_); } - try - { - if (fcntl(fd_, F_SETFL, O_NONBLOCK)) - { - throw_errno(); - } - - init_mmap(fd_); - Log::debug() << "perf_tracepoint_reader mmap initialized"; - if (ioctl(other_fd_, PERF_EVENT_IOC_SET_OUTPUT, fd_) == -1) - { - throw_errno(); - } - if (!config().syscall_filter.empty()) - { - std::vector names; - std::transform(config().syscall_filter.cbegin(), config().syscall_filter.end(), - std::back_inserter(names), - [](const auto& elem) { return fmt::format("id == {}", elem); }); - std::string filter = fmt::format("{}", fmt::join(names, "||")); - - if (ioctl(other_fd_, PERF_EVENT_IOC_SET_FILTER, filter.c_str()) == -1) - { - throw_errno(); - } - if (ioctl(fd_, PERF_EVENT_IOC_SET_FILTER, filter.c_str()) == -1) - { - throw_errno(); - } - } - auto ret = ioctl(fd_, PERF_EVENT_IOC_ENABLE); - Log::debug() << "perf_tracepoint_reader ioctl(fd, PERF_EVENT_IOC_ENABLE) = " << ret; - if (ret == -1) - { - throw_errno(); - } - - ret = ioctl(other_fd_, PERF_EVENT_IOC_ENABLE); - Log::debug() << "perf_tracepoint_reader ioctl(fd, PERF_EVENT_IOC_ENABLE) = " << ret; - if (ret == -1) - { - throw_errno(); - } - } - catch (...) + init_mmap(enter_ev_.get_fd()); + Log::debug() << "perf_tracepoint_reader mmap initialized"; + + exit_ev_.set_output(enter_ev_); + + if (!config().syscall_filter.empty()) { - close(other_fd_); - close(fd_); - throw; + enter_ev_.set_syscall_filter(); + exit_ev_.set_syscall_filter(); } - ioctl(fd_, PERF_EVENT_IOC_ID, &sys_enter_id); - ioctl(other_fd_, PERF_EVENT_IOC_ID, &sys_exit_id); + enter_ev_.enable(); + exit_ev_.enable(); + + ioctl(enter_ev_.get_fd(), PERF_EVENT_IOC_ID, &sys_enter_id); + ioctl(exit_ev_.get_fd(), PERF_EVENT_IOC_ID, &sys_exit_id); } Reader(Reader&& other) : EventReader(std::forward>(other)), cpu_(other.cpu_) { - std::swap(fd_, other.fd_); - } - - ~Reader() - { - if (fd_ != -1) - { - close(fd_); - } - - if (other_fd_ != -1) - { - close(other_fd_); - } + std::swap(enter_ev_, other.enter_ev_); } void stop() { - auto ret = ioctl(fd_, PERF_EVENT_IOC_DISABLE); - Log::debug() << "perf_tracepoint_reader ioctl(fd, PERF_EVENT_IOC_DISABLE) = " << ret; - if (ret == -1) - { - throw_errno(); - } + enter_ev_.disable(); this->read(); } @@ -182,8 +124,8 @@ class Reader : public EventReader private: Cpu cpu_; - int fd_ = -1; - int other_fd_ = -1; + PerfEventInstance enter_ev_; + PerfEventInstance exit_ev_; const static std::filesystem::path base_path; }; diff --git a/include/lo2s/perf/time/reader.hpp b/include/lo2s/perf/time/reader.hpp index aa4db0c5..5e0595f5 100644 --- a/include/lo2s/perf/time/reader.hpp +++ b/include/lo2s/perf/time/reader.hpp @@ -23,6 +23,7 @@ #include #include +#include #include @@ -44,7 +45,6 @@ class Reader : public EventReader { public: Reader(); - ~Reader(); public: using EventReader::handle; @@ -67,7 +67,7 @@ class Reader : public EventReader perf::Clock::time_point perf_time; private: - int fd_; + PerfEventInstance ev_instance_; }; } // namespace time } // namespace perf diff --git a/include/lo2s/perf/tracepoint/reader.hpp b/include/lo2s/perf/tracepoint/reader.hpp index b2f960ce..99e6ca13 100644 --- a/include/lo2s/perf/tracepoint/reader.hpp +++ b/include/lo2s/perf/tracepoint/reader.hpp @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -114,43 +115,29 @@ class Reader : public EventReader Reader(Cpu cpu, int event_id) : cpu_(cpu) { - struct perf_event_attr attr = common_perf_event_attrs(); - attr.type = PERF_TYPE_TRACEPOINT; - attr.config = event_id; - attr.sample_period = 1; - attr.sample_type = PERF_SAMPLE_RAW | PERF_SAMPLE_TIME; - - fd_ = perf_event_open(&attr, cpu.as_scope(), -1, 0, config().cgroup_fd); - if (fd_ < 0) + TracepointEvent event(0, event_id); + try + { + ev_instance_ = event.open(cpu_); + } + catch (const std::system_error& e) { Log::error() << "perf_event_open for raw tracepoint failed."; throw_errno(); } + Log::debug() << "Opened perf_sample_tracepoint_reader for " << cpu_ << " with id " << event_id; try { - // asynchronous delivery - // if (fcntl(fd, F_SETFL, O_ASYNC | O_NONBLOCK)) - if (fcntl(fd_, F_SETFL, O_NONBLOCK)) - { - throw_errno(); - } - - init_mmap(fd_); + init_mmap(ev_instance_.get_fd()); Log::debug() << "perf_tracepoint_reader mmap initialized"; - auto ret = ioctl(fd_, PERF_EVENT_IOC_ENABLE); - Log::debug() << "perf_tracepoint_reader ioctl(fd, PERF_EVENT_IOC_ENABLE) = " << ret; - if (ret == -1) - { - throw_errno(); - } + ev_instance_.enable(); } catch (...) { - close(fd_); throw; } } @@ -158,25 +145,12 @@ class Reader : public EventReader Reader(Reader&& other) : EventReader(std::forward>(other)), cpu_(other.cpu_) { - std::swap(fd_, other.fd_); - } - - ~Reader() - { - if (fd_ != -1) - { - close(fd_); - } + std::swap(ev_instance_, other.ev_instance_); } void stop() { - auto ret = ioctl(fd_, PERF_EVENT_IOC_DISABLE); - Log::debug() << "perf_tracepoint_reader ioctl(fd, PERF_EVENT_IOC_DISABLE) = " << ret; - if (ret == -1) - { - throw_errno(); - } + ev_instance_.disable(); this->read(); } @@ -185,7 +159,7 @@ class Reader : public EventReader private: Cpu cpu_; - int fd_ = -1; + PerfEventInstance ev_instance_; const static std::filesystem::path base_path; }; diff --git a/include/lo2s/perf/util.hpp b/include/lo2s/perf/util.hpp index a9ca34f3..18d7f607 100644 --- a/include/lo2s/perf/util.hpp +++ b/include/lo2s/perf/util.hpp @@ -1,7 +1,5 @@ #pragma once -#include - extern "C" { #include @@ -15,13 +13,7 @@ namespace perf int perf_event_paranoid(); int perf_event_open(struct perf_event_attr* perf_attr, ExecutionScope scope, int group_fd, unsigned long flags, int cgroup_fd = -1); -struct perf_event_attr common_perf_event_attrs(); void perf_warn_paranoid(); void perf_check_disabled(); - -int perf_event_description_open(ExecutionScope scope, const EventDescription& desc, int group_fd); -int perf_try_event_open(struct perf_event_attr* perf_attr, ExecutionScope scope, int group_fd, - unsigned long flags, int cgroup_fd = -1); - } // namespace perf } // namespace lo2s diff --git a/include/lo2s/platform.hpp b/include/lo2s/platform.hpp index 61cd2990..9a220259 100644 --- a/include/lo2s/platform.hpp +++ b/include/lo2s/platform.hpp @@ -32,7 +32,7 @@ #include -#include +#include /* gracefully copied from https://github.com/deater/perf_event_tests/blob/master/ */ @@ -94,6 +94,6 @@ enum class Processor ARM1176 = 204, }; -std::vector get_mem_events(); +std::vector get_mem_events(); } // namespace platform } // namespace lo2s diff --git a/include/lo2s/trace/reg_keys.hpp b/include/lo2s/trace/reg_keys.hpp index ae0ba9d2..e3669a52 100644 --- a/include/lo2s/trace/reg_keys.hpp +++ b/include/lo2s/trace/reg_keys.hpp @@ -27,7 +27,7 @@ #include #include #include -#include +#include #include #include @@ -133,11 +133,11 @@ struct ByMeasurementScopeTag using ByMeasurementScope = SimpleKeyType; -struct ByEventDescriptionTag +struct BySamplingEventName { }; -using ByEventDescription = SimpleKeyType; +using BySamplingEvent = SimpleKeyType; struct ByCounterCollectionTag { @@ -184,8 +184,8 @@ struct Holder template <> struct Holder { - using type = otf2::lookup_definition_holder; + using type = + otf2::lookup_definition_holder; }; template <> diff --git a/include/lo2s/trace/trace.hpp b/include/lo2s/trace/trace.hpp index ad8e71ce..5702b0cc 100644 --- a/include/lo2s/trace/trace.hpp +++ b/include/lo2s/trace/trace.hpp @@ -170,12 +170,13 @@ class Trace return cpuid_metric_class_; } - otf2::definition::metric_member& get_event_metric_member(perf::EventDescription event) + otf2::definition::metric_member& get_event_metric_member(perf::SysfsEvent event) { return registry_.emplace( - ByEventDescription(event), intern(event.name), intern(event.name), + BySamplingEvent(event), intern(event.get_name()), intern(event.get_name()), otf2::common::metric_type::other, otf2::common::metric_mode::accumulated_start, - otf2::common::type::Double, otf2::common::base_type::decimal, 0, intern(event.unit)); + otf2::common::type::Double, otf2::common::base_type::decimal, 0, + intern(event.get_unit())); } otf2::definition::metric_class& perf_metric_class(MeasurementScope scope) diff --git a/src/config.cpp b/src/config.cpp index 690298db..34068da1 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -69,23 +69,23 @@ static inline void list_arguments_sorted(std::ostream& os, const std::string& de } static inline void print_availability(std::ostream& os, const std::string& description, - const std::vector& events) + const std::vector& events) { std::vector event_names; for (const auto& ev : events) { - if (ev.availability == perf::Availability::UNAVAILABLE) + if (!ev.is_valid()) { continue; } std::string availability = ""; std::string cpu = ""; - if (ev.availability == perf::Availability::PROCESS_MODE) + if (ev.get_availability() == perf::Availability::PROCESS_MODE) { availability = " *"; } - else if (ev.availability == perf::Availability::SYSTEM_MODE) + else if (ev.get_availability() == perf::Availability::SYSTEM_MODE) { availability = " #"; } @@ -97,7 +97,7 @@ static inline void print_availability(std::ostream& os, const std::string& descr std::max_element(cpus.begin(), cpus.end())->as_int()); } - event_names.push_back(ev.name + availability + cpu); + event_names.push_back(ev.get_name() + availability + cpu); } list_arguments_sorted(os, description, event_names); } @@ -720,7 +720,7 @@ void parse_program_options(int argc, const char** argv) { for (const auto& mem_event : platform::get_mem_events()) { - perf_group_events.emplace_back(mem_event.name); + perf_group_events.emplace_back(mem_event.get_name()); } perf_group_events.emplace_back("instructions"); perf_group_events.emplace_back("cpu-cycles"); diff --git a/src/perf/counter/counter_provider.cpp b/src/perf/counter/counter_provider.cpp index 936f1692..e42b5b0d 100644 --- a/src/perf/counter/counter_provider.cpp +++ b/src/perf/counter/counter_provider.cpp @@ -43,6 +43,7 @@ void CounterProvider::initialize_userspace_counters(const std::vector #include -#include #include +#include #include #include @@ -53,79 +53,100 @@ Reader::Reader(ExecutionScope scope, bool enable_on_exec) CounterProvider::instance().collection_for(MeasurementScope::group_metric(scope))), counter_buffer_(counter_collection_.counters.size() + 1) { - perf_event_attr leader_attr = common_perf_event_attrs(); - - leader_attr.type = counter_collection_.leader.type; - leader_attr.config = counter_collection_.leader.config; - leader_attr.config1 = counter_collection_.leader.config1; - - leader_attr.sample_type = PERF_SAMPLE_TIME | PERF_SAMPLE_READ; - leader_attr.freq = config().metric_use_frequency; - - if (leader_attr.freq) - { - Log::debug() << "counter::Reader: sample_freq: " << config().metric_frequency; - - leader_attr.sample_freq = config().metric_frequency; - } - else + counter_collection_.leader.as_group_leader(); + do { - Log::debug() << "counter::Reader: sample_period: " << config().metric_count; - leader_attr.sample_period = config().metric_count; - } + try + { + if (scope.is_cpu()) + { + counter_leader_ = std::move(counter_collection_.leader.open(scope.as_cpu())); + } + else + { + counter_leader_ = std::move(counter_collection_.leader.open(scope.as_thread())); + } + } + catch (const std::system_error& e) + { + // perf_try_event_open was used here before + if (counter_leader_.get_fd() < 0 && errno == EACCES && + !counter_collection_.leader.get_attr().exclude_kernel && perf_event_paranoid() > 1) + { + counter_collection_.leader.get_attr().exclude_kernel = 1; + perf_warn_paranoid(); - leader_attr.exclude_kernel = config().exclude_kernel; - leader_attr.read_format = - PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING | PERF_FORMAT_GROUP; - leader_attr.enable_on_exec = enable_on_exec; + continue; + } - group_leader_fd_ = perf_try_event_open(&leader_attr, scope, -1, 0, config().cgroup_fd); - if (group_leader_fd_ < 0) - { - Log::error() << "perf_event_open for counter group leader failed"; - throw_errno(); - } + if (!counter_leader_.is_valid()) + { + Log::error() << "perf_event_open for counter group leader failed"; + throw_errno(); + } + } + } while (!counter_leader_.is_valid()); - Log::debug() << "counter::Reader: leader event: '" << counter_collection_.leader.name << "'"; + Log::debug() << "counter::Reader: leader event: '" << counter_collection_.leader.get_name() + << "'"; - counter_fds_.reserve(counter_collection_.counters.size()); - for (auto& description : counter_collection_.counters) + for (auto& counter_ev : counter_collection_.counters) { - if (description.is_supported_in(scope)) + if (counter_ev.is_available_in(scope)) { - try - { - counter_fds_.emplace_back( - perf_event_description_open(scope, description, group_leader_fd_)); - } - catch (const std::system_error& e) + PerfEventInstance counter; + do { - Log::error() << "failed to add counter '" << description.name - << "': " << e.code().message(); - - if (e.code().value() == EINVAL) + try { - Log::error() - << "opening " << counter_collection_.counters.size() - << " counters at once might exceed the hardware limit of simultaneously " - "openable counters."; + if (scope.is_cpu()) + { + counter = + std::move(counter_ev.open(scope.as_cpu(), counter_leader_.get_fd())); + } + else + { + counter = + std::move(counter_ev.open(scope.as_thread(), counter_leader_.get_fd())); + } } - throw e; - } + catch (const std::system_error& e) + { + // perf_try_event_open was used here before + if (counter.get_fd() < 0 && errno == EACCES && + !counter_ev.get_attr().exclude_kernel && perf_event_paranoid() > 1) + { + counter_ev.get_attr().exclude_kernel = 1; + perf_warn_paranoid(); + + continue; + } + + if (!counter.is_valid()) + { + Log::error() << "failed to add counter '" << counter_ev.get_name() + << "': " << e.code().message(); + + if (e.code().value() == EINVAL) + { + Log::error() << "opening " << counter_collection_.counters.size() + << " counters at once might exceed the hardware limit of " + "simultaneously " + "openable counters."; + } + throw e; + } + counters_.emplace_back(std::move(counter)); + } + } while (!counter.is_valid()); } } if (!enable_on_exec) { - auto ret = ::ioctl(group_leader_fd_, PERF_EVENT_IOC_ENABLE); - if (ret == -1) - { - Log::error() << "failed to enable perf counter group"; - ::close(group_leader_fd_); - throw_errno(); - } + counter_leader_.enable(); } - EventReader::init_mmap(group_leader_fd_); + EventReader::init_mmap(counter_leader_.get_fd()); } template class Reader; } // namespace group diff --git a/src/perf/counter/userspace/reader.cpp b/src/perf/counter/userspace/reader.cpp index 32b82f95..4a242083 100644 --- a/src/perf/counter/userspace/reader.cpp +++ b/src/perf/counter/userspace/reader.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -65,19 +66,57 @@ Reader::Reader(ExecutionScope scope) for (auto& event : counter_collection_.counters) { - counter_fds_.emplace_back(perf_event_description_open(scope, event, -1)); + PerfEventInstance counter; + + try + { + if (scope.is_cpu()) + { + counter = event.open(scope.as_cpu()); + } + else + { + counter = event.open(scope.as_thread()); + } + } + catch (const std::system_error& e) + { + // perf_try_event_open was used here before + if (counter.get_fd() < 0 && errno == EACCES && !event.get_attr().exclude_kernel && + perf_event_paranoid() > 1) + { + event.get_attr().exclude_kernel = 1; + perf_warn_paranoid(); + + if (scope.is_cpu()) + { + counter = std::move(event.open(scope.as_cpu())); + } + else + { + counter = std::move(event.open(scope.as_thread())); + } + } + + if (!counter.is_valid()) + { + Log::error() << "perf_event_open for counter failed"; + throw_errno(); + } + else + { + counters_.emplace_back(std::move(counter)); + } + } } } template void Reader::read() { - for (std::size_t i = 0; i < counter_fds_.size(); i++) + for (std::size_t i = 0; i < counters_.size(); i++) { - [[maybe_unused]] auto bytes_read = - ::read(counter_fds_[i], &(data_[i]), sizeof(UserspaceReadFormat)); - - assert(bytes_read == sizeof(UserspaceReadFormat)); + data_[i] = counters_[i].read(); } static_cast(this)->handle(data_); diff --git a/src/perf/event_provider.cpp b/src/perf/event_provider.cpp index 3dd434ac..0e37bc91 100644 --- a/src/perf/event_provider.cpp +++ b/src/perf/event_provider.cpp @@ -23,8 +23,8 @@ #include #include #include -#include #include +#include #include #include #include @@ -56,7 +56,7 @@ namespace #define PERF_EVENT_HW(name, id) PERF_EVENT(name, PERF_TYPE_HARDWARE, PERF_COUNT_HW_##id) #define PERF_EVENT_SW(name, id) PERF_EVENT(name, PERF_TYPE_SOFTWARE, PERF_COUNT_SW_##id) -static lo2s::perf::EventDescription HW_EVENT_TABLE[] = { +static lo2s::perf::SysfsEvent HW_EVENT_TABLE[] = { PERF_EVENT_HW("cpu-cycles", CPU_CYCLES), PERF_EVENT_HW("instructions", INSTRUCTIONS), PERF_EVENT_HW("cache-references", CACHE_REFERENCES), @@ -75,7 +75,7 @@ static lo2s::perf::EventDescription HW_EVENT_TABLE[] = { #endif }; -static lo2s::perf::EventDescription SW_EVENT_TABLE[] = { +static lo2s::perf::SysfsEvent SW_EVENT_TABLE[] = { PERF_EVENT_SW("cpu-clock", CPU_CLOCK), PERF_EVENT_SW("task-clock", TASK_CLOCK), PERF_EVENT_SW("page-faults", PAGE_FAULTS), @@ -154,77 +154,52 @@ namespace lo2s namespace perf { -const EventDescription sysfs_read_event(const std::string& ev_desc); +const SysfsEvent sysfs_read_event(const std::string& ev_name); -static bool event_is_openable(EventDescription& ev) +static bool event_is_openable(SysfsEvent& provider) { - struct perf_event_attr attr; - memset(&attr, 0, sizeof(attr)); - attr.size = sizeof(attr); - attr.type = ev.type; - attr.config = ev.config; - attr.config1 = ev.config1; - - int proc_fd = perf_event_open(&attr, ExecutionScope(Thread(0)), -1, 0); - int sys_fd = perf_event_open(&attr, ExecutionScope(*ev.supported_cpus().begin()), -1, 0); - if (sys_fd == -1 && proc_fd == -1) + provider.set_availability(); + + if (!provider.is_valid()) { Log::debug() << "perf event not openable, retrying with exclude_kernel=1"; + provider.get_attr().exclude_kernel = 1; + provider.set_availability(); - attr.exclude_kernel = 1; - int proc_fd = perf_event_open(&attr, ExecutionScope(Thread(0)), -1, 0); - int sys_fd = perf_event_open(&attr, ExecutionScope(*ev.supported_cpus().begin()), -1, 0); - - if (sys_fd == -1 && proc_fd == -1) + if (!provider.is_valid()) { switch (errno) { case ENOTSUP: - Log::debug() << "perf event not supported by the running kernel: " << ev.name; + Log::debug() << "perf event not supported by the running kernel: " + << provider.get_name(); break; default: - Log::debug() << "perf event " << ev.name + Log::debug() << "perf event " << provider.get_name() << " not available: " << std::string(std::strerror(errno)); break; } return false; } } - if (sys_fd == -1 && proc_fd != -1) - { - close(proc_fd); - ev.availability = Availability::PROCESS_MODE; - } - else if (proc_fd == -1 && sys_fd != -1) - { - close(sys_fd); - ev.availability = Availability::SYSTEM_MODE; - } - else - { - ev.availability = Availability::UNIVERSAL; - close(sys_fd); - close(proc_fd); - } - return true; } -static void populate_event_map(EventProvider::EventMap& map) +static void populate_event_map(std::unordered_map& map) { Log::info() << "checking available events..."; map.reserve(array_size(HW_EVENT_TABLE) + array_size(SW_EVENT_TABLE) + array_size(CACHE_NAME_TABLE) * array_size(CACHE_OPERATION_TABLE)); for (auto& ev : HW_EVENT_TABLE) { - map.emplace(ev.name, - event_is_openable(ev) ? ev : EventProvider::DescriptionCache::make_invalid()); + SysfsEvent event(ev); + map.emplace(event.get_name(), event); } for (auto& ev : SW_EVENT_TABLE) { - map.emplace(ev.name, - event_is_openable(ev) ? ev : EventProvider::DescriptionCache::make_invalid()); + SysfsEvent event(ev); + map.emplace(event.get_name(), event); } std::stringstream name_fmt; @@ -235,20 +210,16 @@ static void populate_event_map(EventProvider::EventMap& map) name_fmt.str(std::string()); name_fmt << cache.name << '-' << operation.name; - EventDescription ev( - name_fmt.str(), PERF_TYPE_HW_CACHE, - make_cache_config(cache.id, operation.id.op_id, operation.id.result_id)); - - map.emplace(ev.name, event_is_openable(ev) ? - ev : - EventProvider::DescriptionCache::make_invalid()); + map.emplace(name_fmt.str(), SysfsEvent(name_fmt.str(), PERF_TYPE_HW_CACHE, + make_cache_config(cache.id, operation.id.op_id, + operation.id.result_id))); } } } -std::vector EventProvider::get_pmu_events() +std::vector EventProvider::get_pmu_events() { - std::vector events; + std::vector events; const std::filesystem::path pmu_devices("/sys/bus/event_source/devices"); @@ -294,7 +265,7 @@ std::vector EventProvider::get_pmu_events() return events; } -EventDescription EventProvider::fallback_metric_leader_event() +SysfsEvent EventProvider::fallback_metric_leader_event() { Log::debug() << "checking for metric leader event..."; for (auto candidate : { @@ -305,7 +276,7 @@ EventDescription EventProvider::fallback_metric_leader_event() { try { - const EventDescription ev = get_event_by_name(candidate); + const SysfsEvent ev = get_event_by_name(candidate); Log::debug() << "found suitable metric leader event: " << candidate; return ev; } @@ -378,8 +349,7 @@ static constexpr std::uint64_t apply_mask(std::uint64_t value, std::uint64_t mas return res; } -static void event_description_update(EventDescription& event, std::uint64_t value, - const std::string& format) +static void event_attr_update(SysfsEvent& event, std::uint64_t value, const std::string& format) { // Parse config terms // @@ -401,48 +371,59 @@ static void event_description_update(EventDescription& event, std::uint64_t valu if (target_config == "config") { - event.config |= apply_mask(value, mask); + event.get_attr().config |= apply_mask(value, mask); } if (target_config == "config1") { - event.config1 |= apply_mask(value, mask); + event.get_attr().config1 |= apply_mask(value, mask); } } -const EventDescription raw_read_event(const std::string& ev_desc) +/** + * takes the name of an event, checks if it can be opened with each cpu and returns a SysfsEvent + * with a set of working cpus + */ +const SysfsEvent raw_read_event(const std::string& ev_name) { - uint64_t code = std::stoull(ev_desc.substr(1), nullptr, 16); - - struct perf_event_attr perf_attr; - memset(&perf_attr, 0, sizeof(perf_attr)); - perf_attr.size = sizeof(perf_attr); - perf_attr.sample_period = 0; - perf_attr.type = PERF_TYPE_RAW; - perf_attr.config = code; - perf_attr.config1 = 0; - perf_attr.exclude_kernel = 1; - // Needed when scaling multiplexed events, and recognize activation phases - perf_attr.read_format = PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING; + uint64_t code = std::stoull(ev_name.substr(1), nullptr, 16); + + RawEvent ev(0); + ev.get_attr().config = code; std::set cpus; for (const auto& cpu : Topology::instance().cpus()) { - int fd = perf_try_event_open(&perf_attr, cpu.as_scope(), -1, 0, -1); - if (fd != -1) + try { + PerfEventInstance ev_instance = ev.open(cpu, -1, -1); cpus.emplace(cpu); - close(fd); + } + catch (const std::system_error& e) + { } } - const EventDescription event(ev_desc, PERF_TYPE_RAW, code, 0, cpus); + const SysfsEvent event(ev_name, PERF_TYPE_RAW, code, 0, cpus); // Do not check whether the event_is_openable because we don't know whether we are in // system or process mode return event; } -const EventDescription sysfs_read_event(const std::string& ev_desc) +template +T read_file_or_else(std::string filename, T or_else) +{ + T val; + std::ifstream stream(filename); + stream >> val; + if (stream.fail()) + { + return or_else; + } + return val; +} + +const SysfsEvent sysfs_read_event(const std::string& ev_name) { // Parse event description // @@ -469,16 +450,16 @@ const EventDescription sysfs_read_event(const std::string& ev_desc) ED_NAME, }; - static const std::regex ev_desc_regex(R"(([a-z0-9-_]+)[\/:]([a-z0-9-_]+)\/?)"); - std::smatch ev_desc_match; + static const std::regex ev_name_regex(R"(([a-z0-9-_]+)[\/:]([a-z0-9-_]+)\/?)"); + std::smatch ev_name_match; - if (!std::regex_match(ev_desc, ev_desc_match, ev_desc_regex)) + if (!std::regex_match(ev_name, ev_name_match, ev_name_regex)) { throw EventProvider::InvalidEvent("invalid event description format"); } - const std::string& pmu_name = ev_desc_match[ED_PMU]; - const std::string& event_name = ev_desc_match[ED_NAME]; + const std::string& pmu_name = ev_name_match[ED_PMU]; + const std::string& event_name = ev_name_match[ED_NAME]; Log::debug() << "parsing event description: pmu='" << pmu_name << "', event='" << event_name << "'"; @@ -487,10 +468,8 @@ const EventDescription sysfs_read_event(const std::string& ev_desc) std::filesystem::path("/sys/bus/event_source/devices") / pmu_name; // read PMU type id - std::underlying_type::type type; - std::ifstream type_stream(pmu_path / "type"); - type_stream >> type; - if (!type_stream) + std::underlying_type::type type = read_file_or_else(pmu_path / "type", 0); + if (!type) { using namespace std::string_literals; throw EventProvider::InvalidEvent("unknown PMU '"s + pmu_name + "'"); @@ -509,16 +488,15 @@ const EventDescription sysfs_read_event(const std::string& ev_desc) std::transform(cpuids.begin(), cpuids.end(), std::inserter(cpus, cpus.end()), [](uint32_t cpuid) { return Cpu(cpuid); }); - EventDescription event(ev_desc, static_cast(type), 0, 0, cpus); + + SysfsEvent event(ev_name, static_cast(type), 0, 0, cpus); // Parse event configuration from sysfs // // read event configuration - std::string ev_cfg; std::filesystem::path event_path = pmu_path / "events" / event_name; - std::ifstream event_stream(event_path); - event_stream >> ev_cfg; - if (!event_stream) + std::string ev_cfg = read_file_or_else(event_path, std::string("0")); + if (ev_cfg == "0") { using namespace std::string_literals; throw EventProvider::InvalidEvent("unknown event '"s + event_name + "' for PMU '"s + @@ -556,10 +534,8 @@ const EventDescription sysfs_read_event(const std::string& ev_desc) const std::string& value = (kv_match[EC_VALUE].length() != 0) ? kv_match[EC_VALUE] : default_value; - std::string format; - std::ifstream format_stream(pmu_path / "format" / term); - format_stream >> format; - if (!format_stream) + std::string format = read_file_or_else(pmu_path / "format" / term, std::string("0")); + if (format == "0") { throw EventProvider::InvalidEvent("cannot read event format"); } @@ -570,28 +546,18 @@ const EventDescription sysfs_read_event(const std::string& ev_desc) std::uint64_t val = std::stol(value, nullptr, 0); Log::debug() << "parsing config assignment: " << term << " = " << std::hex << std::showbase << val << std::dec << std::noshowbase; - event_description_update(event, val, format); + event_attr_update(event, val, format); ev_cfg = kv_match.suffix(); } Log::debug() << std::hex << std::showbase << "parsed event description: " << pmu_name << "/" - << event_name << "/type=" << event.type << ",config=" << event.config - << ",config1=" << event.config1 << std::dec << std::noshowbase << "/"; + << event_name << "/type=" << event.get_attr().type + << ",config=" << event.get_attr().config << ",config1=" << event.get_attr().config1 + << std::dec << std::noshowbase << "/"; - std::ifstream scale_stream(event_path.string() + ".scale"); - scale_stream >> event.scale; - if (scale_stream.fail()) - { - event.scale = 1; - } - - std::ifstream unit_stream(event_path.string() + ".unit"); - unit_stream >> event.unit; - if (scale_stream.fail()) - { - event.unit = "#"; - } + event.set_scale(read_file_or_else(event_path.replace_extension(".scale"), 1.0)); + event.set_unit(read_file_or_else(event_path.replace_extension(".unit"), "#")); if (!event_is_openable(event)) { @@ -606,7 +572,7 @@ EventProvider::EventProvider() populate_event_map(event_map_); } -EventDescription EventProvider::cache_event(const std::string& name) +SysfsEvent EventProvider::cache_event(const std::string& name) { // Format for raw events is r followed by a hexadecimal number static const std::regex raw_regex("r[[:xdigit:]]{1,8}"); @@ -617,23 +583,27 @@ EventDescription EventProvider::cache_event(const std::string& name) { if (regex_match(name, raw_regex)) { - return event_map_.emplace(name, DescriptionCache(raw_read_event(name))) - .first->second.description; + return event_map_.emplace(name, raw_read_event(name)).first->second; } else { - return event_map_.emplace(name, DescriptionCache(sysfs_read_event(name))) - .first->second.description; + return event_map_.emplace(name, sysfs_read_event(name)).first->second; } } catch (const InvalidEvent& e) { - event_map_.emplace(name, DescriptionCache::make_invalid()); + // emplace unavailable Sampling Event + event_map_.emplace(name, SysfsEvent()); throw e; } } -EventDescription EventProvider::get_event_by_name(const std::string& name) +/** + * takes the name of an event and looks it up in an internal event list. + * @returns The corresponding SysfsEvent if it is available + * @throws InvalidEvent if the event is unavailable + */ +SysfsEvent EventProvider::get_event_by_name(const std::string& name) { auto& ev_map = instance().event_map_; auto event_it = ev_map.find(name); @@ -641,7 +611,7 @@ EventDescription EventProvider::get_event_by_name(const std::string& name) { if (event_it->second.is_valid()) { - return event_it->second.description; + return event_it->second; } else { @@ -660,7 +630,7 @@ bool EventProvider::has_event(const std::string& name) const auto event_it = ev_map.find(name); if (event_it != ev_map.end()) { - return event_it->second.is_valid(); + return (event_it->second.is_valid()); } else { @@ -676,19 +646,18 @@ bool EventProvider::has_event(const std::string& name) } } -std::vector EventProvider::get_predefined_events() +std::vector EventProvider::get_predefined_events() { - const auto& ev_map = instance().event_map_; - std::vector events; + std::vector events; events.reserve(ev_map.size()); for (const auto& event : ev_map) { if (event.second.is_valid()) { - events.push_back(event.second.description); + events.push_back(std::move(event.second)); } } diff --git a/src/perf/reader.cpp b/src/perf/reader.cpp new file mode 100644 index 00000000..2b7b6ee9 --- /dev/null +++ b/src/perf/reader.cpp @@ -0,0 +1,341 @@ +/* + * This file is part of the lo2s software. + * Linux OTF2 sampling + * + * Copyright (c) 2017, + * Technische Universitaet Dresden, Germany + * + * lo2s is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * lo2s is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with lo2s. If not, see . + */ + +#include +#include + +#include + +extern "C" +{ +#include +#include +} + +namespace lo2s +{ +namespace perf +{ + +// helper for visit function +template +struct overloaded : Ts... +{ + using Ts::operator()...; +}; +template +overloaded(Ts...) -> overloaded; + +PerfEvent::PerfEvent(bool enable_on_exec) +{ + memset(&attr_, 0, sizeof(attr_)); + attr_.size = sizeof(attr_); + attr_.type = -1; + attr_.disabled = 1; + +#ifndef USE_HW_BREAKPOINT_COMPAT + attr_.use_clockid = config().use_clockid; + attr_.clockid = config().clockid; +#endif + + // When we poll on the fd given by perf_event_open, wakeup, when our buffer is 80% full + // Default behaviour is to wakeup on every event, which is horrible performance wise + attr_.watermark = 1; + attr_.wakeup_watermark = + static_cast(0.8 * config().mmap_pages * sysconf(_SC_PAGESIZE)); + + attr_.exclude_kernel = config().exclude_kernel; + attr_.sample_period = 1; + attr_.enable_on_exec = enable_on_exec; + + // Needed when scaling multiplexed events, and recognize activation phases + attr_.read_format = PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING; + attr_.sample_type = PERF_SAMPLE_TIME; +} + +PerfEvent::PerfEvent() +{ + memset(&attr_, 0, sizeof(attr_)); + attr_.size = sizeof(attr_); + attr_.type = -1; +} + +SysfsEvent::SysfsEvent() +{ + set_common_attrs("", static_cast(-1), 0); + set_availability(); +} + +SysfsEvent::SysfsEvent(bool enable_on_exec, const std::string& ev_name) : PerfEvent(enable_on_exec) +{ + SysfsEvent other = EventProvider::instance().get_event_by_name(ev_name); + set_common_attrs(other.get_name(), static_cast(other.get_attr().type), + other.get_attr().config, other.get_attr().config1, other.get_cpus()); + set_availability(); +} + +SysfsEvent::SysfsEvent(std::string name, perf_type_id type, std::uint64_t config, + std::uint64_t config1, std::set cpus) +: PerfEvent() +{ + attr_.sample_type = PERF_SAMPLE_TIME; + + set_common_attrs(name, type, config, config1, cpus); + set_availability(); +} + +void SysfsEvent::as_counter() +{ + attr_.sample_period = 0; +} + +void SysfsEvent::as_group_leader() +{ + attr_.freq = config().metric_use_frequency; + + if (attr_.freq) + { + Log::debug() << "counter::Reader: sample_freq: " << config().metric_frequency; + attr_.sample_freq = config().metric_frequency; + } + else + { + Log::debug() << "counter::Reader: sample_period: " << config().metric_count; + attr_.sample_period = config().metric_count; + } + + attr_.read_format |= PERF_FORMAT_GROUP; + attr_.sample_type |= PERF_SAMPLE_READ; +} + +void SysfsEvent::as_sample() +{ + if (config().use_pebs) + { + attr_.use_clockid = 0; + } + + attr_.sample_period = config().sampling_period; + + if (config().sampling) + { + Log::debug() << "using sampling event \'" << config().sampling_event + << "\', period: " << config().sampling_period; + + attr_.mmap = 1; + } + else + { + // Set up a dummy event for recording calling context enter/leaves only + attr_.type = PERF_TYPE_SOFTWARE; + attr_.config = PERF_COUNT_SW_DUMMY; + } + + attr_.sample_id_all = 1; + // Generate PERF_RECORD_COMM events to trace changes to the command + // name of a task. This is used to write a meaningful name for any + // traced thread to the archive. + attr_.comm = 1; + attr_.context_switch = 1; + + // TODO see if we can remove remove tid + attr_.sample_type |= PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_CPU; + if (config().enable_cct) + { + attr_.sample_type |= PERF_SAMPLE_CALLCHAIN; + } + + attr_.precise_ip = 3; +} + +const std::set& SysfsEvent::supported_cpus() const +{ + if (cpus_.empty()) + { + return Topology::instance().cpus(); + } + return cpus_; +} + +bool SysfsEvent::degrade_percision() +{ + /* reduce exactness of IP can help if the kernel does not support really exact events */ + if (attr_.precise_ip == 0) + { + return false; + } + else + { + attr_.precise_ip--; + return true; + } +} + +bool SysfsEvent::is_valid() const +{ + return (availability_ != Availability::UNAVAILABLE); +} + +void SysfsEvent::set_availability() +{ + PerfEventInstance proc_ev; + PerfEventInstance sys_ev; + + try + { + proc_ev = open(Thread(0), -1, -1); + } + catch (const std::system_error& e) + { + } + + try + { + sys_ev = open(*supported_cpus().begin(), -1, -1); + } + catch (const std::system_error& e) + { + } + + if (proc_ev.get_fd() == -1) + { + if (sys_ev.get_fd() == -1) + availability_ = Availability::UNAVAILABLE; + else + availability_ = Availability::SYSTEM_MODE; + } + else + { + if (sys_ev.get_fd() == -1) + availability_ = Availability::PROCESS_MODE; + else + availability_ = Availability::UNIVERSAL; + } +} + +TimeEvent::TimeEvent(bool enable_on_exec, uint64_t addr) : PerfEvent(enable_on_exec) +{ + attr_.exclude_kernel = 1; + +#ifndef USE_HW_BREAKPOINT_COMPAT + attr_.type = PERF_TYPE_BREAKPOINT; + attr_.bp_type = HW_BREAKPOINT_W; + attr_.bp_len = HW_BREAKPOINT_LEN_8; + attr_.wakeup_events = 1; + attr_.bp_addr = addr; +#else + attr_.type = PERF_TYPE_HARDWARE; + attr_.config = PERF_COUNT_HW_INSTRUCTIONS; + attr_.sample_period = 100000000; + attr_.task = 1; +#endif +} + +TracepointEvent::TracepointEvent(bool enable_on_exec, int event_id) : PerfEvent(enable_on_exec) +{ + attr_.type = PERF_TYPE_TRACEPOINT; + attr_.config = event_id; + + attr_.sample_type |= PERF_SAMPLE_RAW; +} + +RawEvent::RawEvent(bool enable_on_exec) : PerfEvent(enable_on_exec) +{ + attr_.sample_period = 0; + attr_.type = PERF_TYPE_RAW; + attr_.config1 = 0; + attr_.exclude_kernel = 1; +} + +PerfEventInstance PerfEvent::open(std::variant location, int group_fd, int cgroup_fd) +{ + return PerfEventInstance(*this, location, group_fd, cgroup_fd); +}; + +PerfEventInstance::PerfEventInstance() : fd_(-1){}; + +PerfEventInstance::PerfEventInstance(PerfEvent& ev, std::variant location, + int group_fd, int cgroup_fd) +: ev_(ev) +{ + // can be deleted when scope gets replaced + ExecutionScope scope; + std::visit(overloaded{ [&](Cpu cpu) { scope = cpu.as_scope(); }, + [&](Thread thread) { scope = thread.as_scope(); } }, + location); + + fd_ = perf_event_open(&ev_.get_attr(), scope, group_fd, 0, cgroup_fd); + + // other error handling + if (fd_ < 0) + { + throw_errno(); + } + + if (fcntl(fd_, F_SETFL, O_NONBLOCK)) + { + Log::error() << errno; + throw_errno(); + } +} + +void PerfEventInstance::enable() +{ + auto ret = ioctl(fd_, PERF_EVENT_IOC_ENABLE); + if (ret == -1) + { + throw_errno(); + } +} + +void PerfEventInstance::disable() +{ + auto ret = ioctl(fd_, PERF_EVENT_IOC_DISABLE); + if (ret == -1) + { + throw_errno(); + } +} + +void PerfEventInstance::set_output(const PerfEventInstance& other_ev) +{ + if (ioctl(fd_, PERF_EVENT_IOC_SET_OUTPUT, other_ev.get_fd()) == -1) + { + throw_errno(); + } +} + +void PerfEventInstance::set_syscall_filter() +{ + std::vector names; + std::transform(config().syscall_filter.cbegin(), config().syscall_filter.end(), + std::back_inserter(names), + [](const auto& elem) { return fmt::format("id == {}", elem); }); + std::string filter = fmt::format("{}", fmt::join(names, "||")); + + if (ioctl(fd_, PERF_EVENT_IOC_SET_FILTER, filter.c_str()) == -1) + { + throw_errno(); + } +} + +} // namespace perf +} // namespace lo2s diff --git a/src/perf/time/reader.cpp b/src/perf/time/reader.cpp index 7a4f6baf..4addb837 100644 --- a/src/perf/time/reader.cpp +++ b/src/perf/time/reader.cpp @@ -19,6 +19,7 @@ * along with lo2s. If not, see . */ +#include #include #include @@ -55,39 +56,15 @@ Reader::Reader() { static_assert(sizeof(local_time) == 8, "The local time object must not be a big fat " "object, or the hardware breakpoint won't work."); - struct perf_event_attr attr = common_perf_event_attrs(); - attr.sample_type = PERF_SAMPLE_TIME; - attr.exclude_kernel = 1; - -#ifndef USE_HW_BREAKPOINT_COMPAT - attr.type = PERF_TYPE_BREAKPOINT; - attr.bp_type = HW_BREAKPOINT_W; - attr.bp_addr = (uint64_t)(&local_time); - attr.bp_len = HW_BREAKPOINT_LEN_8; - attr.wakeup_events = 1; - attr.sample_period = 1; -#else - attr.type = PERF_TYPE_HARDWARE; - attr.config = PERF_COUNT_HW_INSTRUCTIONS; - attr.sample_period = 100000000; - attr.task = 1; -#endif + TimeEvent event(0, (uint64_t)&local_time); try { - fd_ = perf_event_open(&attr, ExecutionScope(Thread(0)), -1, 0); - if (fd_ == -1) - { - throw_errno(); - } + ev_instance_ = event.open(Thread(0)); - init_mmap(fd_); - - if (ioctl(fd_, PERF_EVENT_IOC_ENABLE) == -1) - { - throw_errno(); - } + init_mmap(ev_instance_.get_fd()); + ev_instance_.enable(); } catch (...) { @@ -98,7 +75,6 @@ Reader::Reader() Log::error() << "opening the perf event for HW_BREAKPOINT_COMPAT time synchronization failed"; #endif - close(fd_); throw; } @@ -110,7 +86,6 @@ Reader::Reader() } else if (pid == -1) { - close(fd_); throw_errno(); } waitpid(pid, NULL, 0); @@ -125,11 +100,6 @@ bool Reader::handle(const RecordSyncType* sync_event) return true; } -Reader::~Reader() -{ - close(fd_); -} - } // namespace time } // namespace perf } // namespace lo2s diff --git a/src/perf/util.cpp b/src/perf/util.cpp index f52b8bda..024e2b00 100644 --- a/src/perf/util.cpp +++ b/src/perf/util.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include #include @@ -49,27 +49,6 @@ int perf_event_open(struct perf_event_attr* perf_attr, ExecutionScope scope, int return syscall(__NR_perf_event_open, perf_attr, pid, cpuid, group_fd, flags); } -// Default options we use in every perf_event_open call -struct perf_event_attr common_perf_event_attrs() -{ - struct perf_event_attr attr; - memset(&attr, 0, sizeof(struct perf_event_attr)); - - attr.size = sizeof(struct perf_event_attr); - attr.disabled = 1; - -#ifndef USE_HW_BREAKPOINT_COMPAT - attr.use_clockid = config().use_clockid; - attr.clockid = config().clockid; -#endif - // When we poll on the fd given by perf_event_open, wakeup, when our buffer is 80% full - // Default behaviour is to wakeup on every event, which is horrible performance wise - attr.watermark = 1; - attr.wakeup_watermark = static_cast(0.8 * config().mmap_pages * get_page_size()); - - return attr; -} - void perf_warn_paranoid() { static bool warning_issued = false; @@ -99,45 +78,5 @@ void perf_check_disabled() throw std::runtime_error("Perf is disabled via a paranoid setting of 3."); } } - -int perf_try_event_open(struct perf_event_attr* perf_attr, ExecutionScope scope, int group_fd, - unsigned long flags, int cgroup_fd) -{ - int fd = perf_event_open(perf_attr, scope, group_fd, flags, cgroup_fd); - if (fd < 0 && errno == EACCES && !perf_attr->exclude_kernel && perf_event_paranoid() > 1) - { - perf_attr->exclude_kernel = 1; - perf_warn_paranoid(); - fd = perf_event_open(perf_attr, scope, group_fd, flags, cgroup_fd); - } - return fd; -} - -int perf_event_description_open(ExecutionScope scope, const EventDescription& desc, int group_fd) -{ - struct perf_event_attr perf_attr; - memset(&perf_attr, 0, sizeof(perf_attr)); - perf_attr.size = sizeof(perf_attr); - perf_attr.sample_period = 0; - perf_attr.type = desc.type; - perf_attr.config = desc.config; - perf_attr.config1 = desc.config1; - perf_attr.exclude_kernel = config().exclude_kernel; - // Needed when scaling multiplexed events, and recognize activation phases - perf_attr.read_format = PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING; - -#ifndef USE_HW_BREAKPOINT_COMPAT - perf_attr.use_clockid = config().use_clockid; - perf_attr.clockid = config().clockid; -#endif - - int fd = perf_try_event_open(&perf_attr, scope, group_fd, 0, config().cgroup_fd); - if (fd < 0) - { - Log::error() << "perf_event_open for counter failed"; - throw_errno(); - } - return fd; -} } // namespace perf } // namespace lo2s diff --git a/src/platform.cpp b/src/platform.cpp index f2bc018d..e59af746 100644 --- a/src/platform.cpp +++ b/src/platform.cpp @@ -217,7 +217,7 @@ Processor detect_processor(void) return Processor::UNKNOWN; } -std::vector get_mem_events() +std::vector get_mem_events() { static auto proc = detect_processor(); switch (proc)