1/*
2 * Copyright © 2020-2021 Collabora, Ltd.
3 * Author: Antonio Caggiano <antonio.caggiano@collabora.com>
4 *
5 * SPDX-License-Identifier: MIT
6 */
7
8#include <pps/pps_driver.h>
9
10#include <charconv>
11#include <cstdlib>
12#include <cstring>
13#include <optional>
14#include <thread>
15
16#include <docopt/docopt.h>
17
18static const char *USAGE =
19   R"(pps-config
20
21  Usage:
22	pps-config info
23	pps-config dump [--gpu=<n>] [--ids=<n>] [--sec=<n>]
24	pps-config groups [--gpu=<n>]
25	pps-config counters [--gpu=<n>]
26	pps-config (-h | --help)
27	pps-config --version
28
29  Options:
30	-h --help  Show this screen.
31	--version  Show version.
32	--gpu=<n>  GPU number to query [default: 0].
33	--ids=<n>  Comma separated list of numbers.
34	--sec=<n>  Seconds to wait before dumping performance counters [default: 1].
35)";
36
37// Tool running mode
38enum class Mode {
39   // Show help message
40   Help,
41
42   // Show system information
43   Info,
44
45   // Show list of available counters
46   Counters,
47
48   // Groups
49   Groups,
50
51   // Dump performance counters
52   Dump,
53};
54
55std::vector<std::string_view> split(const std::string &list, const std::string &separator)
56{
57   std::vector<std::string_view> ret;
58   std::string_view list_view = list;
59   while (!list_view.empty()) {
60      size_t pos = list_view.find(separator);
61      if (pos == std::string::npos) {
62         ret.push_back(list_view);
63         break;
64      }
65      ret.push_back(list_view.substr(0, pos));
66      list_view = list_view.substr(pos + separator.length(), list_view.length());
67   }
68   return ret;
69}
70
71std::optional<uint32_t> to_counter_id(const std::string_view &view)
72{
73   uint32_t counter_id = 0;
74
75   auto res = std::from_chars(view.data(), view.data() + view.size(), counter_id);
76   if (res.ec == std::errc::invalid_argument) {
77      return std::nullopt;
78   }
79
80   return counter_id;
81}
82
83int main(int argc, const char **argv)
84{
85   using namespace pps;
86
87   Mode mode = Mode::Help;
88   auto secs = std::chrono::seconds(1);
89   uint32_t gpu_num = 0;
90   std::vector<uint32_t> counter_ids;
91
92   auto args =
93      docopt::docopt(USAGE, {std::next(argv), std::next(argv, argc)}, true, "pps-config 0.3");
94
95   if (args["info"].asBool()) {
96      mode = Mode::Info;
97   }
98
99   if (args["dump"].asBool()) {
100      mode = Mode::Dump;
101   }
102
103   if (args["--gpu"]) {
104      gpu_num = static_cast<uint32_t>(args["--gpu"].asLong());
105   }
106
107   if (args["--ids"]) {
108      auto comma_separated_list = args["--ids"].asString();
109      std::vector<std::string_view> ids_list = split(comma_separated_list, ",");
110
111      for (auto &id : ids_list) {
112         if (auto counter_id = to_counter_id(id)) {
113            counter_ids.push_back(*counter_id);
114         } else {
115            fprintf(stderr, "Failed to parse counter ids: %s\n", comma_separated_list.c_str());
116            return EXIT_FAILURE;
117         }
118      }
119   }
120
121   if (args["--sec"]) {
122      secs = std::chrono::seconds(args["--sec"].asLong());
123   }
124
125   if (args["groups"].asBool()) {
126      mode = Mode::Groups;
127   }
128
129   if (args["counters"].asBool()) {
130      mode = Mode::Counters;
131   }
132
133   // Docopt shows the help message for us
134   if (mode == Mode::Help) {
135      return EXIT_SUCCESS;
136   }
137
138   switch (mode) {
139   default:
140      break;
141   case Mode::Info: {
142      // Header: device name, and whether it is supported or not
143      printf("#%4s %16s %16s\n", "num", "device", "support");
144
145      auto devices = DrmDevice::create_all();
146      for (auto &device : devices) {
147         auto gpu_num = device.gpu_num;
148         auto name = device.name;
149         auto driver = Driver::get_driver(std::move(device));
150         printf(" %4u %16s %16s\n", gpu_num, name.c_str(), driver ? "yes" : "no");
151      }
152
153      break;
154   }
155   case Mode::Dump: {
156      if (auto device = DrmDevice::create(gpu_num)) {
157         if (auto driver = Driver::get_driver(std::move(device.value()))) {
158            driver->init_perfcnt();
159
160            // Enable counters
161            if (counter_ids.empty()) {
162               driver->enable_all_counters();
163            } else {
164               for (auto id : counter_ids) {
165                  driver->enable_counter(id);
166               }
167            }
168
169            driver->enable_perfcnt(std::chrono::nanoseconds(secs).count());
170            std::this_thread::sleep_for(std::chrono::seconds(secs));
171
172            // Try dumping until it succeeds
173            while (!driver->dump_perfcnt())
174               ;
175            // Try collecting samples until it succeeds
176            while (!driver->next())
177               ;
178
179            printf("#%32s %32s\n", "counter", "value");
180            for (auto &counter : driver->enabled_counters) {
181               printf(" %32s ", counter.name.c_str());
182               auto value = counter.get_value(*driver);
183               if (auto d_val = std::get_if<double>(&value)) {
184                  printf("%32f\n", *d_val);
185               } else if (auto i_val = std::get_if<int64_t>(&value))
186                  printf("%32li\n", *i_val);
187               else {
188                  printf("%32s\n", "error");
189               }
190            }
191         }
192      }
193      break;
194   }
195   case Mode::Groups: {
196      if (auto device = DrmDevice::create(gpu_num)) {
197         if (auto driver = Driver::get_driver(std::move(device.value()))) {
198            driver->init_perfcnt();
199            printf("#%4s %32s\n", "id", "name");
200
201            for (auto &group : driver->groups) {
202               printf(" %4u %32s\n", group.id, group.name.c_str());
203            }
204         }
205      }
206
207      break;
208   }
209   case Mode::Counters: {
210      if (auto device = DrmDevice::create(gpu_num)) {
211         if (auto driver = Driver::get_driver(std::move(device.value()))) {
212            driver->init_perfcnt();
213            printf("#%4s %32s\n", "id", "name");
214
215            for (uint32_t i = 0; i < driver->counters.size(); ++i) {
216               auto &counter = driver->counters[i];
217               printf(" %4u %32s\n", counter.id, counter.name.c_str());
218            }
219         }
220      }
221
222      break;
223   }
224   } // switch
225
226   return EXIT_SUCCESS;
227}
228