diff --git a/meson.build b/meson.build index 08c1b84d25d..ebe070aa54a 100644 --- a/meson.build +++ b/meson.build @@ -2047,6 +2047,8 @@ endif gcc_lto_quirk = (cc.get_id() == 'gcc') ? ['-fno-lto'] : [] with_perfetto = get_option('perfetto') +with_datasources = get_option('datasources') +with_any_datasource = with_datasources.length() != 0 if with_perfetto dep_perfetto = dependency('perfetto', fallback: ['perfetto', 'dep_perfetto']) endif @@ -2176,7 +2178,12 @@ lines += 'HUD lmsensors: ' + (dep_lmsensors.found() ? 'yes' : 'no') lines += '' lines += 'Shared-glapi: ' + (with_shared_glapi ? 'yes' : 'no') + +lines += '' lines += 'Perfetto: ' + (with_perfetto ? 'yes' : 'no') +if with_any_datasource + lines += 'Perfetto ds: ' + ' '.join(with_datasources) +endif indent = ' ' diff --git a/meson_options.txt b/meson_options.txt index 772ea057903..8ab9309806a 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -470,4 +470,11 @@ option( type : 'boolean', value : false, description : 'Enable performance analysis with Perfetto. Default: false' -) \ No newline at end of file +) +option( + 'datasources', + type : 'array', + value : ['auto'], + choices : ['auto', 'panfrost', 'intel'], + description: 'List of Perfetto datasources to build. If this is set to `auto`, datasources that can not be build are skipped. Default: [`auto`]' +) diff --git a/src/meson.build b/src/meson.build index 6725bbac931..a36db2fe18d 100644 --- a/src/meson.build +++ b/src/meson.build @@ -25,6 +25,8 @@ inc_gallium = include_directories('gallium/include') inc_gallium_aux = include_directories('gallium/auxiliary') inc_amd_common = include_directories('amd/common') inc_amd_common_llvm = include_directories('amd/llvm') +inc_tool = include_directories('tool') +pps_datasources = [] libglsl_util = static_library( 'glsl_util', @@ -139,3 +141,5 @@ if with_glx != 'disabled' and not with_glvnd variables : ['glx_tls=@0@'.format(use_elf_tls ? 'yes' : 'no')], ) endif + +subdir('tool') diff --git a/src/tool/meson.build b/src/tool/meson.build new file mode 100644 index 00000000000..1844a9be249 --- /dev/null +++ b/src/tool/meson.build @@ -0,0 +1,8 @@ +# Copyright © 2021 Collabora, Ltd. +# Author: Antonio Caggiano +# +# SPDX-License-Identifier: MIT + +if with_perfetto + subdir('pps') +endif diff --git a/src/tool/pps/.clang-format b/src/tool/pps/.clang-format new file mode 100644 index 00000000000..41203078b2b --- /dev/null +++ b/src/tool/pps/.clang-format @@ -0,0 +1,21 @@ +BasedOnStyle: WebKit +AlignTrailingComments: 'true' +AllowAllParametersOfDeclarationOnNextLine: 'false' +AllowShortFunctionsOnASingleLine: None +AlwaysBreakBeforeMultilineStrings: 'true' +BinPackArguments: 'false' +BinPackParameters: 'false' +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Linux +ColumnLimit: '100' +Cpp11BracedListStyle: 'true' +KeepEmptyLinesAtTheStartOfBlocks: 'false' +NamespaceIndentation: None +PointerAlignment: Right +SortIncludes: 'true' +SpaceAfterTemplateKeyword: 'false' +Standard: Cpp11 +TabWidth: '3' +IndentWidth: '3' +ConstructorInitializerIndentWidth: '3' +ContinuationIndentWidth: '3' diff --git a/src/tool/pps/meson.build b/src/tool/pps/meson.build new file mode 100644 index 00000000000..c379a74c847 --- /dev/null +++ b/src/tool/pps/meson.build @@ -0,0 +1,44 @@ +# Copyright © 2020-2021 Collabora, Ltd. +# Author: Antonio Caggiano +# +# SPDX-License-Identifier: MIT + +pps_sources = [ + 'pps.cc', + 'pps_device.cc', + 'pps_driver.cc', + 'pps_counter.cc', +] + +include_pps = include_directories('../') + +dep_drm = dependency('libdrm') +pps_deps = [dep_drm, dep_perfetto] +pps_deps += pps_datasources + +lib_pps = static_library( + 'pps', + sources: pps_sources, + include_directories: [include_pps, inc_src], + dependencies: pps_deps, + cpp_args: '-std=c++17' +) + +dep_pps = declare_dependency( + link_with: lib_pps, + include_directories: [include_pps, inc_src] +) + +producer_sources = [ + 'pps_datasource.cc', + 'pps_producer.cc' +] + +executable( + 'pps-producer', + sources: producer_sources, + include_directories: [include_pps, inc_src], + dependencies: [dep_pps, dep_perfetto], + cpp_args: '-std=c++17', + install: true +) diff --git a/src/tool/pps/pps.cc b/src/tool/pps/pps.cc new file mode 100644 index 00000000000..0aa2e930f8c --- /dev/null +++ b/src/tool/pps/pps.cc @@ -0,0 +1,26 @@ +/* + * Copyright © 2020 Collabora, Ltd. + * Author: Antonio Caggiano + * + * SPDX-License-Identifier: MIT + */ + +#include "pps.h" + +#include +#include + +namespace pps +{ +bool check(int res, const char *msg) +{ + if (res < 0) { + char *err_msg = std::strerror(errno); + PERFETTO_ELOG("%s: %s", msg, err_msg); + return false; + } + + return true; +} + +} // namespace pps diff --git a/src/tool/pps/pps.h b/src/tool/pps/pps.h new file mode 100644 index 00000000000..639cccbb5f6 --- /dev/null +++ b/src/tool/pps/pps.h @@ -0,0 +1,38 @@ +/* + * Copyright © 2020 Collabora, Ltd. + * Author: Antonio Caggiano + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include + +#define PPS_LOG PERFETTO_LOG +#define PPS_LOG_IMPORTANT PERFETTO_ILOG +#define PPS_LOG_ERROR PERFETTO_ELOG +#define PPS_LOG_FATAL PERFETTO_FATAL + +namespace pps +{ +enum class State { + Stop, // initial state, or stopped by the tracing service + Start, // running, sampling data +}; + +/// @brief Checks whether a return value is valid +/// @param res Result from a syscall +/// @param msg Message to prepend to strerror +/// @return True if ok, false otherwise +bool check(int res, const char *msg); + +/// @param num Numerator +/// @param den Denominator +/// @return A ratio between two floating point numbers, or 0 if the denominator is 0 +constexpr double ratio(double num, double den) +{ + return den > 0.0 ? num / den : 0.0; +} + +} // namespace pps diff --git a/src/tool/pps/pps_algorithm.h b/src/tool/pps/pps_algorithm.h new file mode 100644 index 00000000000..5f96b0a4bb8 --- /dev/null +++ b/src/tool/pps/pps_algorithm.h @@ -0,0 +1,16 @@ +/* + * Copyright © 2020 Collabora, Ltd. + * Author: Antonio Caggiano + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include + +#define FIND_IF(c, lambda) (std::find_if(std::begin(c), std::end(c), lambda)) +#define FIND(c, e) (std::find(std::begin(c), std::end(c), e)) +#define CONTAINS(c, e) (FIND(c, e) != std::end(c)) +#define CONTAINS_IT(c, it) (it != std::end(c)) +#define APPEND(a, b) (a.insert(std::end(a), std::begin(b), std::end(b))) diff --git a/src/tool/pps/pps_counter.cc b/src/tool/pps/pps_counter.cc new file mode 100644 index 00000000000..46c55dd4749 --- /dev/null +++ b/src/tool/pps/pps_counter.cc @@ -0,0 +1,33 @@ +/* + * Copyright © 2019-2020 Collabora, Ltd. + * Author: Antonio Caggiano + * Author: Rohan Garg + * Author: Robert Beckett + * + * SPDX-License-Identifier: MIT + */ + +#include "pps_counter.h" + +#include +#include + +#include "pps_algorithm.h" + +namespace pps +{ +Counter::Counter(int32_t id, const std::string &name, int32_t group) + : id {id} + , name {name} + , group {group} +{ + assert(id >= 0 && "Invalid counter ID"); + assert(group >= 0 && "Invalid group ID"); +} + +bool Counter::operator==(const Counter &other) const +{ + return id == other.id; +} + +} // namespace pps diff --git a/src/tool/pps/pps_counter.h b/src/tool/pps/pps_counter.h new file mode 100644 index 00000000000..13cf67b547b --- /dev/null +++ b/src/tool/pps/pps_counter.h @@ -0,0 +1,110 @@ +/* + * Copyright © 2020 Collabora, Ltd. + * Author: Antonio Caggiano + * Author: Rohan Garg + * Author: Robert Beckett + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include +#include +#include + +namespace pps +{ +struct CounterGroup { + std::string name; + + uint32_t id; + + /// List of counters ID belonging to this group + std::vector counters; + + std::vector subgroups; +}; + +class Driver; + +class Counter +{ + public: + /// @brief A counter value can be of different types depending on what it represents: + /// cycles, cycles-per-instruction, percentages, bytes, and so on. + enum class Units { + Percent, + Byte, + Hertz, + None, + }; + + using Value = std::variant; + + /// @param c Counter which we want to retrieve a value + /// @param d Driver used to sample performance counters + /// @return The value of the counter + using Getter = Value(const Counter &c, const Driver &d); + + Counter() = default; + virtual ~Counter() = default; + + /// @param id ID of the counter + /// @param name Name of the counter + /// @param group Group ID this counter belongs to + Counter(int32_t id, const std::string &name, int32_t group); + + bool operator==(const Counter &c) const; + + /// @param get New getter function for this counter + void set_getter(const std::function &get); + + /// @brief d Driver used to sample performance counters + /// @return Last sampled value for this counter + Value get_value(const Driver &d) const; + + /// Id of the counter + int32_t id = -1; + + /// Name of the counter + std::string name = ""; + + /// ID of the group this counter belongs to + int32_t group = -1; + + /// Offset of this counter within GPU counter list + /// For derived counters it is negative and remains unused + int32_t offset = -1; + + /// Whether it is a derived counter or not + bool derived = false; + + /// Returns the value of this counter + std::function getter; + + /// The unit of the counter + Units units; +}; + +/// @param get New getter function for this counter +inline void Counter::set_getter(const std::function &get) +{ + getter = get; +} + +/// @brief d Driver used to sample performance counters +/// @return Last sampled value for this counter +inline Counter::Value Counter::get_value(const Driver &d) const +{ + return getter(*this, d); +} + +/// @return The underlying u32 value +template constexpr uint32_t to_u32(T &&elem) +{ + return static_cast(elem); +} + +} // namespace pps diff --git a/src/tool/pps/pps_datasource.cc b/src/tool/pps/pps_datasource.cc new file mode 100644 index 00000000000..bcaa5e0a557 --- /dev/null +++ b/src/tool/pps/pps_datasource.cc @@ -0,0 +1,323 @@ +/* + * Copyright © 2019-2021 Collabora, Ltd. + * Author: Antonio Caggiano + * Author: Rohan Garg + * Author: Robert Beckett + * Author: Corentin Noël + * + * SPDX-License-Identifier: MIT + */ + +#include "pps_datasource.h" +#include "pps_driver.h" + +#include +#include +#include + +// Minimum supported sampling period in nanoseconds +#define MIN_SAMPLING_PERIOD_NS 500000 + +namespace pps +{ +static std::string driver_name; + +/// Synchronize access to started_cv and started +static std::mutex started_m; +static std::condition_variable started_cv; +static bool started = false; + +float ms(const std::chrono::nanoseconds &t) +{ + return t.count() / 1000000.0f; +} + +void GpuDataSource::OnSetup(const SetupArgs &args) +{ + // Create drivers for all supported devices + auto drm_devices = DrmDevice::create_all(); + for (auto &drm_device : drm_devices) { + if (drm_device.name != driver_name) + continue; + + if (auto driver = Driver::get_driver(std::move(drm_device))) { + if (!driver->init_perfcnt()) { + // Skip failing driver + PPS_LOG_ERROR("Failed to initialize %s driver", driver->drm_device.name.c_str()); + continue; + } + + this->driver = driver; + } + } + if (driver == nullptr) { + PPS_LOG_FATAL("No DRM devices supported"); + } + + // Parse perfetto config + const std::string &config_raw = args.config->gpu_counter_config_raw(); + perfetto::protos::pbzero::GpuCounterConfig::Decoder config(config_raw); + + if (config.has_counter_ids()) { + // Get enabled counters + PPS_LOG_IMPORTANT("Selecting counters"); + for (auto it = config.counter_ids(); it; ++it) { + uint32_t counter_id = it->as_uint32(); + driver->enable_counter(counter_id); + } + } else { + // Enable all counters + driver->enable_all_counters(); + } + + // Get sampling period + auto min_sampling_period = std::chrono::nanoseconds(MIN_SAMPLING_PERIOD_NS); + + auto dev_supported = std::chrono::nanoseconds(driver->get_min_sampling_period_ns()); + if (dev_supported > min_sampling_period) { + min_sampling_period = dev_supported; + } + + time_to_sleep = std::max(time_to_sleep, min_sampling_period); + + if (config.has_counter_period_ns()) { + auto requested_sampling_period = std::chrono::nanoseconds(config.counter_period_ns()); + if (requested_sampling_period < min_sampling_period) { + PPS_LOG_ERROR("Sampling period should be greater than %" PRIu64 " ns (%.2f ms)", + uint64_t(min_sampling_period.count()), + ms(min_sampling_period)); + } else { + time_to_sleep = requested_sampling_period; + } + } + PPS_LOG("Sampling period set to %" PRIu64 " ns", uint64_t(time_to_sleep.count())); +} + +void GpuDataSource::OnStart(const StartArgs &args) +{ + driver->enable_perfcnt(time_to_sleep.count()); + + state = State::Start; + + { + std::lock_guard lock(started_m); + started = true; + } + started_cv.notify_all(); +} + +void close_callback(GpuDataSource::TraceContext ctx) +{ + auto packet = ctx.NewTracePacket(); + packet->Finalize(); + ctx.Flush(); + PPS_LOG("Context flushed"); +} + +void GpuDataSource::OnStop(const StopArgs &args) +{ + state = State::Stop; + auto stop_closure = args.HandleStopAsynchronously(); + Trace(close_callback); + stop_closure(); + + driver->disable_perfcnt(); + driver = nullptr; + + std::lock_guard lock(started_m); + started = false; +} + +void GpuDataSource::wait_started() +{ + std::unique_lock lock(started_m); + if (!started) { + PPS_LOG("Waiting for start"); + started_cv.wait(lock, [] { return started; }); + } +} + +void GpuDataSource::register_data_source(const std::string &_driver_name) +{ + driver_name = _driver_name; + static perfetto::DataSourceDescriptor dsd; + dsd.set_name("gpu.counters." + driver_name); + Register(dsd); +} + +void add_group(perfetto::protos::pbzero::GpuCounterDescriptor *desc, + const CounterGroup &group, + const std::string &prefix, + int32_t gpu_num) +{ + if (!group.counters.empty()) { + // Define a block for each group containing counters + auto block_desc = desc->add_blocks(); + block_desc->set_name(prefix + "." + group.name); + block_desc->set_block_id(group.id); + + // Associate counters to blocks + for (auto id : group.counters) { + block_desc->add_counter_ids(id); + } + } + + for (auto const &sub : group.subgroups) { + // Perfetto doesnt currently support nested groups. + // Flatten group hierarchy, using dot separator + add_group(desc, sub, prefix + "." + group.name, gpu_num); + } +} + +void add_descriptors(perfetto::protos::pbzero::GpuCounterEvent *event, + std::vector const &groups, + std::vector const &counters, + Driver &driver) +{ + // Start a counter descriptor + auto desc = event->set_counter_descriptor(); + + // Add the groups + for (auto const &group : groups) { + add_group(desc, group, driver.drm_device.name, driver.drm_device.gpu_num); + } + + // Add the counters + for (auto const &counter : counters) { + auto spec = desc->add_specs(); + spec->set_counter_id(counter.id); + spec->set_name(counter.name); + + auto units = perfetto::protos::pbzero::GpuCounterDescriptor::NONE; + switch (counter.units) { + case Counter::Units::Percent: + units = perfetto::protos::pbzero::GpuCounterDescriptor::PERCENT; + break; + case Counter::Units::Byte: + units = perfetto::protos::pbzero::GpuCounterDescriptor::BYTE; + break; + case Counter::Units::Hertz: + units = perfetto::protos::pbzero::GpuCounterDescriptor::HERTZ; + break; + case Counter::Units::None: + units = perfetto::protos::pbzero::GpuCounterDescriptor::NONE; + break; + default: + assert(false && "Missing counter units type!"); + break; + } + spec->add_numerator_units(units); + } +} + +void add_samples(perfetto::protos::pbzero::GpuCounterEvent &event, const Driver &driver) +{ + if (driver.enabled_counters.size() == 0) { + PPS_LOG_FATAL("There are no counters enabled"); + } + + for (const auto &counter : driver.enabled_counters) { + auto counter_event = event.add_counters(); + + counter_event->set_counter_id(counter.id); + + auto value = counter.get_value(driver); + if (auto d_value = std::get_if(&value)) { + counter_event->set_double_value(*d_value); + } else if (auto i_value = std::get_if(&value)) { + counter_event->set_int_value(*i_value); + } else { + PPS_LOG_ERROR("Failed to get value for counter %s", counter.name.c_str()); + } + } +} + +void GpuDataSource::trace(TraceContext &ctx) +{ + using namespace perfetto::protos::pbzero; + + if (auto state = ctx.GetIncrementalState(); state->was_cleared) { + // Mark any incremental state before this point invalid + { + auto packet = ctx.NewTracePacket(); + packet->set_timestamp(perfetto::base::GetBootTimeNs().count()); + packet->set_sequence_flags(TracePacket::SEQ_INCREMENTAL_STATE_CLEARED); + } + + auto packet = ctx.NewTracePacket(); + descriptor_timestamp = perfetto::base::GetBootTimeNs().count(); + packet->set_timestamp(descriptor_timestamp); + + auto event = packet->set_gpu_counter_event(); + event->set_gpu_id(driver->drm_device.gpu_num); + + auto &groups = driver->groups; + auto &counters = driver->enabled_counters; + PPS_LOG("Sending counter descriptors"); + add_descriptors(event, groups, counters, *driver); + + state->was_cleared = false; + } + + // Save current scheduler for restoring later + int prev_sched_policy = sched_getscheduler(0); + sched_param prev_priority_param; + sched_getparam(0, &prev_priority_param); + + // Use FIFO policy to avoid preemption while collecting counters + int sched_policy = SCHED_FIFO; + // Do not use max priority to avoid starving migration and watchdog threads + int priority_value = sched_get_priority_max(sched_policy) - 1; + sched_param priority_param { priority_value }; + sched_setscheduler(0, sched_policy, &priority_param); + + if (driver->dump_perfcnt()) { + while (auto timestamp = driver->next()) { + if (timestamp <= descriptor_timestamp) { + // Do not send counter values before counter descriptors + PPS_LOG_ERROR("Skipping counter values coming before descriptors"); + continue; + } + + auto packet = ctx.NewTracePacket(); + packet->set_timestamp(timestamp); + + auto event = packet->set_gpu_counter_event(); + event->set_gpu_id(driver->drm_device.gpu_num); + + add_samples(*event, *driver); + } + } + + // Reset normal scheduler + sched_setscheduler(0, prev_sched_policy, &prev_priority_param); +} + +void GpuDataSource::trace_callback(TraceContext ctx) +{ + using namespace std::chrono; + + nanoseconds sleep_time = nanoseconds(0); + + if (auto data_source = ctx.GetDataSourceLocked()) { + if (data_source->time_to_sleep > data_source->time_to_trace) { + sleep_time = data_source->time_to_sleep - data_source->time_to_trace; + } + } + + // Wait sampling period before tracing + std::this_thread::sleep_for(sleep_time); + + auto time_zero = perfetto::base::GetBootTimeNs(); + if (auto data_source = ctx.GetDataSourceLocked()) { + // Check data source is still running + if (data_source->state == pps::State::Start) { + data_source->trace(ctx); + data_source->time_to_trace = perfetto::base::GetBootTimeNs() - time_zero; + } + } else { + PPS_LOG("Tracing finished"); + } +} + +} // namespace pps diff --git a/src/tool/pps/pps_datasource.h b/src/tool/pps/pps_datasource.h new file mode 100644 index 00000000000..96a83b5ae97 --- /dev/null +++ b/src/tool/pps/pps_datasource.h @@ -0,0 +1,64 @@ +/* + * Copyright © 2019-2021 Collabora, Ltd. + * Author: Antonio Caggiano + * Author: Robert Beckett + * Author: Corentin Noël + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include "pps.h" +#include "pps_driver.h" + +namespace pps +{ +struct GpuIncrementalState { + bool was_cleared = true; +}; + +struct GpuDataSourceTraits : public perfetto::DefaultDataSourceTraits { + using IncrementalStateType = GpuIncrementalState; +}; + +class Driver; + +/// @brief This datasource samples performance counters at a specified rate. +/// Once the data is available, it sends a protobuf packet to the perfetto service. +/// At the very beginning, it sends a description of the counters. +/// After that, it sends counter values using the IDs set in the description. +class GpuDataSource : public perfetto::DataSource +{ + public: + void OnSetup(const SetupArgs &args) override; + void OnStart(const StartArgs &args) override; + void OnStop(const StopArgs &args) override; + + /// Blocks until the data source starts + static void wait_started(); + + /// @brief Perfetto trace callback + static void trace_callback(TraceContext ctx); + static void register_data_source(const std::string &driver_name); + + void trace(TraceContext &ctx); + + private: + State state = State::Stop; + + /// Time between trace callbacks + std::chrono::nanoseconds time_to_sleep = std::chrono::nanoseconds(1000000); + + /// Used to check whether the datasource is quick enough + std::chrono::nanoseconds time_to_trace; + + /// A data source supports one driver at a time, but if you need more + /// than one gpu datasource you can just run another producer + Driver *driver = nullptr; + + /// Timestamp of packet sent with counter descriptors + uint64_t descriptor_timestamp = 0; +}; + +} // namespace pps diff --git a/src/tool/pps/pps_device.cc b/src/tool/pps/pps_device.cc new file mode 100644 index 00000000000..c06ae1e336c --- /dev/null +++ b/src/tool/pps/pps_device.cc @@ -0,0 +1,141 @@ +/* + * Copyright © 2020 Collabora, Ltd. + * Author: Antonio Caggiano + * Author: Rohan Garg + * Author: Robert Beckett + * + * SPDX-License-Identifier: MIT + */ + +#include "pps_device.h" + +#include +#include +#include +#include +#include + +namespace pps +{ +#define MAX_DRM_DEVICES 64 + +uint32_t DrmDevice::device_count() +{ + drmDevicePtr devices[MAX_DRM_DEVICES] = {}; + int num_devices = drmGetDevices2(0, devices, MAX_DRM_DEVICES); + drmFreeDevices(devices, num_devices); + return static_cast(num_devices); +} + +/// @return The name of a DRM device, empty string in case of error +std::string query_drm_name(const int fd) +{ + assert(fd && "Failed to query DrmDevice: invalid fd"); + + std::string name = ""; + + if (drmVersionPtr version = drmGetVersion(fd)) { + name = std::string(version->name, version->name_len); + drmFreeVersion(version); + } + + return name; +} + +/// @return A DRM device, nullopt in case of error +std::optional create_drm_device(int fd, int32_t gpu_num) +{ + if (fd < 0 || gpu_num < 0) { + return std::nullopt; + } + + // Try getting the name + std::string name = query_drm_name(fd); + if (name.empty()) { + return std::nullopt; + } + + auto ret = DrmDevice(); + ret.fd = fd; + ret.gpu_num = gpu_num; + ret.name = name; + return ret; +} + +std::vector DrmDevice::create_all() +{ + std::vector ret = {}; + + drmDevicePtr devices[MAX_DRM_DEVICES] = {}; + int num_devices = drmGetDevices2(0, devices, MAX_DRM_DEVICES); + if (num_devices <= 0) { + return ret; + } + + for (int32_t gpu_num = 0; gpu_num < num_devices; gpu_num++) { + drmDevicePtr device = devices[gpu_num]; + if ((device->available_nodes & (1 << DRM_NODE_RENDER))) { + int fd = open(device->nodes[DRM_NODE_RENDER], O_RDWR); + + // If it can create a device, push it into the vector + if (auto drm_device = create_drm_device(fd, gpu_num)) { + ret.emplace_back(std::move(drm_device.value())); + } + } + } + + drmFreeDevices(devices, num_devices); + return ret; +} + +std::optional DrmDevice::create(int32_t gpu_num) +{ + std::optional ret = std::nullopt; + + if (gpu_num < 0) { + return ret; + } + + drmDevicePtr devices[MAX_DRM_DEVICES] = {}; + int num_devices = drmGetDevices2(0, devices, MAX_DRM_DEVICES); + + if (num_devices > 0 && gpu_num < num_devices) { + drmDevicePtr device = devices[gpu_num]; + int fd = open(device->nodes[DRM_NODE_RENDER], O_RDONLY); + ret = create_drm_device(fd, gpu_num); + } + + drmFreeDevices(devices, num_devices); + return ret; +} + +DrmDevice::DrmDevice(DrmDevice &&other) + : fd {other.fd} + , gpu_num {other.gpu_num} + , name {std::move(other.name)} +{ + other.fd = -1; + other.gpu_num = -1; +} + +DrmDevice &DrmDevice::operator=(DrmDevice &&other) +{ + std::swap(fd, other.fd); + std::swap(gpu_num, other.gpu_num); + std::swap(name, other.name); + return *this; +} + +DrmDevice::~DrmDevice() +{ + if (fd >= 0) { + close(fd); + } +} + +DrmDevice::operator bool() const +{ + return !name.empty(); +} + +} // namespace pps diff --git a/src/tool/pps/pps_device.h b/src/tool/pps/pps_device.h new file mode 100644 index 00000000000..6623059b9d2 --- /dev/null +++ b/src/tool/pps/pps_device.h @@ -0,0 +1,53 @@ +/* + * Copyright © 2020 Collabora, Ltd. + * Author: Antonio Caggiano + * Author: Rohan Garg + * Author: Robert Beckett + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include +#include + +namespace pps +{ +/// @brief Helper class for a DRM device +class DrmDevice +{ + public: + /// @return The number of DRM devices available in the system + static uint32_t device_count(); + + /// @return All DRM devices available in the system + static std::vector create_all(); + + /// @return A DRM device selected by its number in the system, nullopt otherwise + static std::optional create(int32_t gpu_num); + + /// @brief Prefer calling create instead of default constructor + DrmDevice() = default; + + // Allow move + DrmDevice(DrmDevice &&); + DrmDevice &operator=(DrmDevice &&); + + // Forbid copy + DrmDevice(const DrmDevice &) = delete; + DrmDevice &operator=(const DrmDevice &) = delete; + + ~DrmDevice(); + + /// @return Whether a device has a valid name + operator bool() const; + + /// File descriptor of the device opened in read/write mode + int fd = -1; + int32_t gpu_num = -1; + std::string name = ""; +}; + +} // namespace pps diff --git a/src/tool/pps/pps_driver.cc b/src/tool/pps/pps_driver.cc new file mode 100644 index 00000000000..6c7c340a941 --- /dev/null +++ b/src/tool/pps/pps_driver.cc @@ -0,0 +1,93 @@ +/* + * Copyright © 2019-2020 Collabora, Ltd. + * Author: Antonio Caggiano + * Author: Rohan Garg + * Author: Robert Beckett + * Author: Corentin Noël + * + * SPDX-License-Identifier: MIT + */ + +#include "pps_driver.h" + +#include +#include + +#include "pps.h" +#include "pps_algorithm.h" + +namespace pps +{ +std::unordered_map> create_supported_drivers() +{ + std::unordered_map> map; + return map; +} + +const std::unordered_map> &Driver::get_supported_drivers() +{ + static auto map = create_supported_drivers(); + return map; +} + +const std::vector Driver::supported_device_names() +{ + std::vector supported_device_names; + + for (auto &entry : get_supported_drivers()) { + supported_device_names.emplace_back(entry.first); + } + + return supported_device_names; +} + +Driver *Driver::get_driver(DrmDevice &&drm_device) +{ + auto &supported_drivers = get_supported_drivers(); + auto it = supported_drivers.find(drm_device.name); + if (it == std::end(supported_drivers)) { + PERFETTO_FATAL("Failed to find a driver for DRM device %s", drm_device.name.c_str()); + } + + Driver *driver = it->second.get(); + driver->drm_device = std::move(drm_device); + return driver; +} + +std::string Driver::default_driver_name() +{ + auto supported_devices = Driver::supported_device_names(); + auto devices = DrmDevice::create_all(); + for (auto &device : devices) { + if (CONTAINS(supported_devices, device.name)) { + PPS_LOG_IMPORTANT("Driver selected: %s", device.name.c_str()); + return device.name; + } + } + PPS_LOG_FATAL("Failed to find any driver"); +} + +std::string Driver::find_driver_name(const char *requested) +{ + auto supported_devices = Driver::supported_device_names(); + auto devices = DrmDevice::create_all(); + for (auto &device : devices) { + if (device.name == requested) { + PPS_LOG_IMPORTANT("Driver selected: %s", device.name.c_str()); + return device.name; + } + } + + std::ostringstream drivers_os; + std::copy(supported_devices.begin(), + supported_devices.end() - 1, + std::ostream_iterator(drivers_os, ", ")); + drivers_os << supported_devices.back(); + + PPS_LOG_ERROR( + "Device '%s' not found (supported drivers: %s)", requested, drivers_os.str().c_str()); + + return default_driver_name(); +} + +} // namespace pps diff --git a/src/tool/pps/pps_driver.h b/src/tool/pps/pps_driver.h new file mode 100644 index 00000000000..55849d07ada --- /dev/null +++ b/src/tool/pps/pps_driver.h @@ -0,0 +1,95 @@ +/* + * Copyright © 2020 Collabora, Ltd. + * Author: Antonio Caggiano + * Author: Robert Beckett + * Author: Corentin Noël + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include +#include +#include + +#include "pps_counter.h" +#include "pps_device.h" + +namespace pps +{ +/// @brief Abstract Driver class +class Driver +{ + public: + /// @return A map of supported DRM device names and their relative pps driver + static const std::unordered_map> &get_supported_drivers(); + + /// @return A list of supported DRM device names + static const std::vector supported_device_names(); + + /// @return A driver supporting a specific DRM device + static Driver *get_driver(DrmDevice &&drm_device); + + /// @return The name of a default selected PPS driver + static std::string default_driver_name(); + + /// @return The name of a driver based on the request, otherwise the default driver name + static std::string find_driver_name(const char *requested_name); + + Driver() = default; + virtual ~Driver() = default; + + // Forbid copy + Driver(const Driver &) = delete; + Driver &operator=(const Driver &) = delete; + + /// @return The minimum sampling period for the current device + virtual uint64_t get_min_sampling_period_ns() = 0; + + /// @brief Enable a counter by its ID + virtual void enable_counter(uint32_t counter_id) = 0; + + virtual void enable_all_counters() = 0; + + /// @brief Initialize performance counters data such as groups and counters + /// @return Whether it was successful or not + virtual bool init_perfcnt() = 0; + + /// @brief Enables performance counters, meaning that from now on they can be sampled + virtual void enable_perfcnt(uint64_t sampling_period_ns) = 0; + + /// @brief Disables performance counters on the device + virtual void disable_perfcnt() = 0; + + /// @brief Asking the GPU to dump performance counters could have different meanings + /// depending on the concrete driver. Some could just ask the GPU to dump counters to a + /// user space buffer, while some others will need to read data from a stream which was + /// written asynchronously. + /// @return Whether it was able to dump, false otherwise + virtual bool dump_perfcnt() = 0; + + /// @brief After dumping performance counters, with this function you can iterate + /// through the samples collected. + /// @return The CPU timestamp associated to current sample, or 0 if there are no more samples + virtual uint64_t next() = 0; + + DrmDevice drm_device; + + /// List of counter groups + std::vector groups; + + /// List of counters exposed by the GPU + std::vector counters; + + /// List of counters that are actually enabled + std::vector enabled_counters; + + protected: + // Prevent object slicing by allowing move only from subclasses + Driver(Driver &&) = default; + Driver &operator=(Driver &&) = default; +}; + +} // namespace pps diff --git a/src/tool/pps/pps_producer.cc b/src/tool/pps/pps_producer.cc new file mode 100644 index 00000000000..69ff7193b4f --- /dev/null +++ b/src/tool/pps/pps_producer.cc @@ -0,0 +1,33 @@ +/* + * Copyright © 2019-2020 Collabora, Ltd. + * Author: Antonio Caggiano + * Author: Robert Beckett + * Author: Corentin Noël + * + * SPDX-License-Identifier: MIT + */ + +#include + +#include "pps_datasource.h" + +int main(int argc, const char **argv) +{ + using namespace pps; + + // Connects to the system tracing service + perfetto::TracingInitArgs args; + args.backends = perfetto::kSystemBackend; + perfetto::Tracing::Initialize(args); + + std::string driver_name = + (argc > 1) ? Driver::find_driver_name(argv[1]) : Driver::default_driver_name(); + GpuDataSource::register_data_source(driver_name); + + while (true) { + GpuDataSource::wait_started(); + GpuDataSource::Trace(GpuDataSource::trace_callback); + } + + return EXIT_SUCCESS; +}