Mesa (main): pps: Gfx-pps v0.3.0

GitLab Mirror gitlab-mirror at kemper.freedesktop.org
Fri May 7 14:04:32 UTC 2021


Module: Mesa
Branch: main
Commit: 1cc72b2aef82373247466c2e7b81970c867ad0fa
URL:    http://cgit.freedesktop.org/mesa/mesa/commit/?id=1cc72b2aef82373247466c2e7b81970c867ad0fa

Author: Antonio Caggiano <antonio.caggiano at collabora.com>
Date:   Thu Mar 18 18:30:45 2021 +0100

pps: Gfx-pps v0.3.0

Add the gfx-pps backbone in `src/pps`.

v2: Simplify supported drivers creation.
v3: No default getter is provided for counters.
v4: Open DRM device in read/write mode.
v5: Wait for datasource to be started.
v6: Set FIFO scheduler while sampling counters.

Signed-off-by: Antonio Caggiano <antonio.caggiano at collabora.com>
Acked-by: Emma Anholt <emma at anholt.net>
Reviewed-by: Rob Clark <robdclark at chromium.org>
Reviewed-by: John Bates <jbates at chromium.org>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/9652>

---

 meson.build                    |   7 +
 meson_options.txt              |   9 +-
 src/meson.build                |   4 +
 src/tool/meson.build           |   8 +
 src/tool/pps/.clang-format     |  21 +++
 src/tool/pps/meson.build       |  44 ++++++
 src/tool/pps/pps.cc            |  26 ++++
 src/tool/pps/pps.h             |  38 +++++
 src/tool/pps/pps_algorithm.h   |  16 ++
 src/tool/pps/pps_counter.cc    |  33 +++++
 src/tool/pps/pps_counter.h     | 110 ++++++++++++++
 src/tool/pps/pps_datasource.cc | 323 +++++++++++++++++++++++++++++++++++++++++
 src/tool/pps/pps_datasource.h  |  64 ++++++++
 src/tool/pps/pps_device.cc     | 141 ++++++++++++++++++
 src/tool/pps/pps_device.h      |  53 +++++++
 src/tool/pps/pps_driver.cc     |  93 ++++++++++++
 src/tool/pps/pps_driver.h      |  95 ++++++++++++
 src/tool/pps/pps_producer.cc   |  33 +++++
 18 files changed, 1117 insertions(+), 1 deletion(-)

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 <antonio.caggiano at collabora.com>
+#
+# 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 <antonio.caggiano at collabora.com>
+#
+# 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 <antonio.caggiano at collabora.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include "pps.h"
+
+#include <cerrno>
+#include <cstring>
+
+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 <antonio.caggiano at collabora.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#pragma once
+
+#include <perfetto.h>
+
+#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 <antonio.caggiano at collabora.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#pragma once
+
+#include <algorithm>
+
+#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 <antonio.caggiano at collabora.com>
+ * Author: Rohan Garg <rohan.garg at collabora.com>
+ * Author: Robert Beckett <bob.beckett at collabora.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include "pps_counter.h"
+
+#include <cassert>
+#include <cstring>
+
+#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 <antonio.caggiano at collabora.com>
+ * Author: Rohan Garg <rohan.garg at collabora.com>
+ * Author: Robert Beckett <bob.beckett at collabora.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#pragma once
+
+#include <functional>
+#include <string>
+#include <variant>
+#include <vector>
+
+namespace pps
+{
+struct CounterGroup {
+   std::string name;
+
+   uint32_t id;
+
+   /// List of counters ID belonging to this group
+   std::vector<int32_t> counters;
+
+   std::vector<CounterGroup> 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<int64_t, double>;
+
+   /// @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<Getter> &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> getter;
+
+   /// The unit of the counter
+   Units units;
+};
+
+/// @param get New getter function for this counter
+inline void Counter::set_getter(const std::function<Getter> &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<typename T> constexpr uint32_t to_u32(T &&elem)
+{
+   return static_cast<uint32_t>(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 <antonio.caggiano at collabora.com>
+ * Author: Rohan Garg <rohan.garg at collabora.com>
+ * Author: Robert Beckett <bob.beckett at collabora.com>
+ * Author: Corentin Noël <corentin.noel at collabora.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include "pps_datasource.h"
+#include "pps_driver.h"
+
+#include <condition_variable>
+#include <thread>
+#include <variant>
+
+// 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<std::mutex> 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<std::mutex> lock(started_m);
+   started = false;
+}
+
+void GpuDataSource::wait_started()
+{
+   std::unique_lock<std::mutex> 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<CounterGroup> const &groups,
+   std::vector<Counter> 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<double>(&value)) {
+         counter_event->set_double_value(*d_value);
+      } else if (auto i_value = std::get_if<int64_t>(&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 <antonio.caggiano at collabora.com>
+ * Author: Robert Beckett <bob.beckett at collabora.com>
+ * Author: Corentin Noël <corentin.noel at collabora.com>
+ *
+ * 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<GpuDataSource, GpuDataSourceTraits>
+{
+   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 <antonio.caggiano at collabora.com>
+ * Author: Rohan Garg <rohan.garg at collabora.com>
+ * Author: Robert Beckett <bob.beckett at collabora.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include "pps_device.h"
+
+#include <cassert>
+#include <fcntl.h>
+#include <memory>
+#include <unistd.h>
+#include <xf86drm.h>
+
+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<uint32_t>(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<DrmDevice> 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> DrmDevice::create_all()
+{
+   std::vector<DrmDevice> 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> DrmDevice::create(int32_t gpu_num)
+{
+   std::optional<DrmDevice> 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 <antonio.caggiano at collabora.com>
+ * Author: Rohan Garg <rohan.garg at collabora.com>
+ * Author: Robert Beckett <bob.beckett at collabora.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#pragma once
+
+#include <optional>
+#include <string>
+#include <vector>
+
+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<DrmDevice> create_all();
+
+   /// @return A DRM device selected by its number in the system, nullopt otherwise
+   static std::optional<DrmDevice> 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 <antonio.caggiano at collabora.com>
+ * Author: Rohan Garg <rohan.garg at collabora.com>
+ * Author: Robert Beckett <bob.beckett at collabora.com>
+ * Author: Corentin Noël <corentin.noel at collabora.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include "pps_driver.h"
+
+#include <iterator>
+#include <sstream>
+
+#include "pps.h"
+#include "pps_algorithm.h"
+
+namespace pps
+{
+std::unordered_map<std::string, std::unique_ptr<Driver>> create_supported_drivers()
+{
+   std::unordered_map<std::string, std::unique_ptr<Driver>> map;
+   return map;
+}
+
+const std::unordered_map<std::string, std::unique_ptr<Driver>> &Driver::get_supported_drivers()
+{
+   static auto map = create_supported_drivers();
+   return map;
+}
+
+const std::vector<std::string> Driver::supported_device_names()
+{
+   std::vector<std::string> 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<std::string>(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 <antonio.caggiano at collabora.com>
+ * Author: Robert Beckett <bob.beckett at collabora.com>
+ * Author: Corentin Noël <corentin.noel at collabora.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#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<std::string, std::unique_ptr<Driver>> &get_supported_drivers();
+
+   /// @return A list of supported DRM device names
+   static const std::vector<std::string> 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<CounterGroup> groups;
+
+   /// List of counters exposed by the GPU
+   std::vector<Counter> counters;
+
+   /// List of counters that are actually enabled
+   std::vector<Counter> 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 <antonio.caggiano at collabora.com>
+ * Author: Robert Beckett <bob.beckett at collabora.com>
+ * Author: Corentin Noël <corentin.noel at collabora.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include <cstdlib>
+
+#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;
+}



More information about the mesa-commit mailing list