1b8e80941Smrg/**************************************************************************
2b8e80941Smrg *
3b8e80941Smrg * Copyright (C) 2016 Steven Toth <stoth@kernellabs.com>
4b8e80941Smrg * Copyright (C) 2016 Zodiac Inflight Innovations
5b8e80941Smrg * All Rights Reserved.
6b8e80941Smrg *
7b8e80941Smrg * Permission is hereby granted, free of charge, to any person obtaining a
8b8e80941Smrg * copy of this software and associated documentation files (the
9b8e80941Smrg * "Software"), to deal in the Software without restriction, including
10b8e80941Smrg * without limitation the rights to use, copy, modify, merge, publish,
11b8e80941Smrg * distribute, sub license, and/or sell copies of the Software, and to
12b8e80941Smrg * permit persons to whom the Software is furnished to do so, subject to
13b8e80941Smrg * the following conditions:
14b8e80941Smrg *
15b8e80941Smrg * The above copyright notice and this permission notice (including the
16b8e80941Smrg * next paragraph) shall be included in all copies or substantial portions
17b8e80941Smrg * of the Software.
18b8e80941Smrg *
19b8e80941Smrg * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20b8e80941Smrg * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21b8e80941Smrg * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
22b8e80941Smrg * IN NO EVENT SHALL THE AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR
23b8e80941Smrg * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24b8e80941Smrg * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
25b8e80941Smrg * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26b8e80941Smrg *
27b8e80941Smrg **************************************************************************/
28b8e80941Smrg
29b8e80941Smrg#ifdef HAVE_GALLIUM_EXTRA_HUD
30b8e80941Smrg
31b8e80941Smrg/* Purpose: Reading /sys/block/<*>/stat MB/s read/write throughput per second,
32b8e80941Smrg * displaying on the HUD.
33b8e80941Smrg */
34b8e80941Smrg
35b8e80941Smrg#include "hud/hud_private.h"
36b8e80941Smrg#include "util/list.h"
37b8e80941Smrg#include "util/os_time.h"
38b8e80941Smrg#include "os/os_thread.h"
39b8e80941Smrg#include "util/u_memory.h"
40b8e80941Smrg#include <stdio.h>
41b8e80941Smrg#include <unistd.h>
42b8e80941Smrg#include <dirent.h>
43b8e80941Smrg#include <stdlib.h>
44b8e80941Smrg#include <unistd.h>
45b8e80941Smrg#include <inttypes.h>
46b8e80941Smrg#include <sys/types.h>
47b8e80941Smrg#include <sys/stat.h>
48b8e80941Smrg#include <unistd.h>
49b8e80941Smrg
50b8e80941Smrgstruct stat_s
51b8e80941Smrg{
52b8e80941Smrg   /* Read */
53b8e80941Smrg   uint64_t r_ios;
54b8e80941Smrg   uint64_t r_merges;
55b8e80941Smrg   uint64_t r_sectors;
56b8e80941Smrg   uint64_t r_ticks;
57b8e80941Smrg   /* Write */
58b8e80941Smrg   uint64_t w_ios;
59b8e80941Smrg   uint64_t w_merges;
60b8e80941Smrg   uint64_t w_sectors;
61b8e80941Smrg   uint64_t w_ticks;
62b8e80941Smrg   /* Misc */
63b8e80941Smrg   uint64_t in_flight;
64b8e80941Smrg   uint64_t io_ticks;
65b8e80941Smrg   uint64_t time_in_queue;
66b8e80941Smrg};
67b8e80941Smrg
68b8e80941Smrgstruct diskstat_info
69b8e80941Smrg{
70b8e80941Smrg   struct list_head list;
71b8e80941Smrg   int mode; /* DISKSTAT_RD, DISKSTAT_WR */
72b8e80941Smrg   char name[64]; /* EG. sda5 */
73b8e80941Smrg
74b8e80941Smrg   char sysfs_filename[128];
75b8e80941Smrg   uint64_t last_time;
76b8e80941Smrg   struct stat_s last_stat;
77b8e80941Smrg};
78b8e80941Smrg
79b8e80941Smrg/* TODO: We don't handle dynamic block device / partition
80b8e80941Smrg * arrival or removal.
81b8e80941Smrg * Static globals specific to this HUD category.
82b8e80941Smrg */
83b8e80941Smrgstatic int gdiskstat_count = 0;
84b8e80941Smrgstatic struct list_head gdiskstat_list;
85b8e80941Smrgstatic mtx_t gdiskstat_mutex = _MTX_INITIALIZER_NP;
86b8e80941Smrg
87b8e80941Smrgstatic struct diskstat_info *
88b8e80941Smrgfind_dsi_by_name(const char *n, int mode)
89b8e80941Smrg{
90b8e80941Smrg   list_for_each_entry(struct diskstat_info, dsi, &gdiskstat_list, list) {
91b8e80941Smrg      if (dsi->mode != mode)
92b8e80941Smrg         continue;
93b8e80941Smrg      if (strcasecmp(dsi->name, n) == 0)
94b8e80941Smrg         return dsi;
95b8e80941Smrg   }
96b8e80941Smrg   return 0;
97b8e80941Smrg}
98b8e80941Smrg
99b8e80941Smrgstatic int
100b8e80941Smrgget_file_values(const char *fn, struct stat_s *s)
101b8e80941Smrg{
102b8e80941Smrg   int ret = 0;
103b8e80941Smrg   FILE *fh = fopen(fn, "r");
104b8e80941Smrg   if (!fh)
105b8e80941Smrg      return -1;
106b8e80941Smrg
107b8e80941Smrg   ret = fscanf(fh,
108b8e80941Smrg        "%" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64
109b8e80941Smrg        " %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64 "",
110b8e80941Smrg        &s->r_ios, &s->r_merges, &s->r_sectors, &s->r_ticks, &s->w_ios,
111b8e80941Smrg        &s->w_merges, &s->w_sectors, &s->w_ticks, &s->in_flight, &s->io_ticks,
112b8e80941Smrg        &s->time_in_queue);
113b8e80941Smrg
114b8e80941Smrg   fclose(fh);
115b8e80941Smrg
116b8e80941Smrg   return ret;
117b8e80941Smrg}
118b8e80941Smrg
119b8e80941Smrgstatic void
120b8e80941Smrgquery_dsi_load(struct hud_graph *gr, struct pipe_context *pipe)
121b8e80941Smrg{
122b8e80941Smrg   /* The framework calls us periodically, compensate for the
123b8e80941Smrg    * calling interval accordingly when reporting per second.
124b8e80941Smrg    */
125b8e80941Smrg   struct diskstat_info *dsi = gr->query_data;
126b8e80941Smrg   uint64_t now = os_time_get();
127b8e80941Smrg
128b8e80941Smrg   if (dsi->last_time) {
129b8e80941Smrg      if (dsi->last_time + gr->pane->period <= now) {
130b8e80941Smrg         struct stat_s stat;
131b8e80941Smrg         if (get_file_values(dsi->sysfs_filename, &stat) < 0)
132b8e80941Smrg            return;
133b8e80941Smrg         float val = 0;
134b8e80941Smrg
135b8e80941Smrg         switch (dsi->mode) {
136b8e80941Smrg         case DISKSTAT_RD:
137b8e80941Smrg            val =
138b8e80941Smrg               ((stat.r_sectors -
139b8e80941Smrg                 dsi->last_stat.r_sectors) * 512) /
140b8e80941Smrg               (((float) gr->pane->period / 1000) / 1000);
141b8e80941Smrg            break;
142b8e80941Smrg         case DISKSTAT_WR:
143b8e80941Smrg            val =
144b8e80941Smrg               ((stat.w_sectors -
145b8e80941Smrg                 dsi->last_stat.w_sectors) * 512) /
146b8e80941Smrg               (((float) gr->pane->period / 1000) / 1000);
147b8e80941Smrg            break;
148b8e80941Smrg         }
149b8e80941Smrg
150b8e80941Smrg         hud_graph_add_value(gr, (uint64_t) val);
151b8e80941Smrg         dsi->last_stat = stat;
152b8e80941Smrg         dsi->last_time = now;
153b8e80941Smrg      }
154b8e80941Smrg   }
155b8e80941Smrg   else {
156b8e80941Smrg      /* initialize */
157b8e80941Smrg      switch (dsi->mode) {
158b8e80941Smrg      case DISKSTAT_RD:
159b8e80941Smrg      case DISKSTAT_WR:
160b8e80941Smrg         get_file_values(dsi->sysfs_filename, &dsi->last_stat);
161b8e80941Smrg         break;
162b8e80941Smrg      }
163b8e80941Smrg      dsi->last_time = now;
164b8e80941Smrg   }
165b8e80941Smrg}
166b8e80941Smrg
167b8e80941Smrg/**
168b8e80941Smrg  * Create and initialize a new object for a specific block I/O device.
169b8e80941Smrg  * \param  pane  parent context.
170b8e80941Smrg  * \param  dev_name  logical block device name, EG. sda5.
171b8e80941Smrg  * \param  mode  query read or write (DISKSTAT_RD/DISKSTAT_WR) statistics.
172b8e80941Smrg  */
173b8e80941Smrgvoid
174b8e80941Smrghud_diskstat_graph_install(struct hud_pane *pane, const char *dev_name,
175b8e80941Smrg                           unsigned int mode)
176b8e80941Smrg{
177b8e80941Smrg   struct hud_graph *gr;
178b8e80941Smrg   struct diskstat_info *dsi;
179b8e80941Smrg
180b8e80941Smrg   int num_devs = hud_get_num_disks(0);
181b8e80941Smrg   if (num_devs <= 0)
182b8e80941Smrg      return;
183b8e80941Smrg
184b8e80941Smrg   dsi = find_dsi_by_name(dev_name, mode);
185b8e80941Smrg   if (!dsi)
186b8e80941Smrg      return;
187b8e80941Smrg
188b8e80941Smrg   gr = CALLOC_STRUCT(hud_graph);
189b8e80941Smrg   if (!gr)
190b8e80941Smrg      return;
191b8e80941Smrg
192b8e80941Smrg   dsi->mode = mode;
193b8e80941Smrg   if (dsi->mode == DISKSTAT_RD) {
194b8e80941Smrg      snprintf(gr->name, sizeof(gr->name), "%s-Read-MB/s", dsi->name);
195b8e80941Smrg   }
196b8e80941Smrg   else if (dsi->mode == DISKSTAT_WR) {
197b8e80941Smrg      snprintf(gr->name, sizeof(gr->name), "%s-Write-MB/s", dsi->name);
198b8e80941Smrg   }
199b8e80941Smrg   else {
200b8e80941Smrg      free(gr);
201b8e80941Smrg      return;
202b8e80941Smrg   }
203b8e80941Smrg
204b8e80941Smrg   gr->query_data = dsi;
205b8e80941Smrg   gr->query_new_value = query_dsi_load;
206b8e80941Smrg
207b8e80941Smrg   hud_pane_add_graph(pane, gr);
208b8e80941Smrg   hud_pane_set_max_value(pane, 100);
209b8e80941Smrg}
210b8e80941Smrg
211b8e80941Smrgstatic void
212b8e80941Smrgadd_object_part(const char *basename, const char *name, int objmode)
213b8e80941Smrg{
214b8e80941Smrg   struct diskstat_info *dsi = CALLOC_STRUCT(diskstat_info);
215b8e80941Smrg
216b8e80941Smrg   snprintf(dsi->name, sizeof(dsi->name), "%s", name);
217b8e80941Smrg   snprintf(dsi->sysfs_filename, sizeof(dsi->sysfs_filename), "%s/%s/stat",
218b8e80941Smrg      basename, name);
219b8e80941Smrg   dsi->mode = objmode;
220b8e80941Smrg   list_addtail(&dsi->list, &gdiskstat_list);
221b8e80941Smrg   gdiskstat_count++;
222b8e80941Smrg}
223b8e80941Smrg
224b8e80941Smrgstatic void
225b8e80941Smrgadd_object(const char *basename, const char *name, int objmode)
226b8e80941Smrg{
227b8e80941Smrg   struct diskstat_info *dsi = CALLOC_STRUCT(diskstat_info);
228b8e80941Smrg
229b8e80941Smrg   snprintf(dsi->name, sizeof(dsi->name), "%s", name);
230b8e80941Smrg   snprintf(dsi->sysfs_filename, sizeof(dsi->sysfs_filename), "%s/stat",
231b8e80941Smrg      basename);
232b8e80941Smrg   dsi->mode = objmode;
233b8e80941Smrg   list_addtail(&dsi->list, &gdiskstat_list);
234b8e80941Smrg   gdiskstat_count++;
235b8e80941Smrg}
236b8e80941Smrg
237b8e80941Smrg/**
238b8e80941Smrg  * Initialize internal object arrays and display block I/O HUD help.
239b8e80941Smrg  * \param  displayhelp  true if the list of detected devices should be
240b8e80941Smrg                         displayed on the console.
241b8e80941Smrg  * \return  number of detected block I/O devices.
242b8e80941Smrg  */
243b8e80941Smrgint
244b8e80941Smrghud_get_num_disks(bool displayhelp)
245b8e80941Smrg{
246b8e80941Smrg   struct dirent *dp;
247b8e80941Smrg   struct stat stat_buf;
248b8e80941Smrg   char name[64];
249b8e80941Smrg
250b8e80941Smrg   /* Return the number of block devices and partitions. */
251b8e80941Smrg   mtx_lock(&gdiskstat_mutex);
252b8e80941Smrg   if (gdiskstat_count) {
253b8e80941Smrg      mtx_unlock(&gdiskstat_mutex);
254b8e80941Smrg      return gdiskstat_count;
255b8e80941Smrg   }
256b8e80941Smrg
257b8e80941Smrg   /* Scan /sys/block, for every object type we support, create and
258b8e80941Smrg    * persist an object to represent its different statistics.
259b8e80941Smrg    */
260b8e80941Smrg   list_inithead(&gdiskstat_list);
261b8e80941Smrg   DIR *dir = opendir("/sys/block/");
262b8e80941Smrg   if (!dir) {
263b8e80941Smrg      mtx_unlock(&gdiskstat_mutex);
264b8e80941Smrg      return 0;
265b8e80941Smrg   }
266b8e80941Smrg
267b8e80941Smrg   while ((dp = readdir(dir)) != NULL) {
268b8e80941Smrg
269b8e80941Smrg      /* Avoid 'lo' and '..' and '.' */
270b8e80941Smrg      if (strlen(dp->d_name) <= 2)
271b8e80941Smrg         continue;
272b8e80941Smrg
273b8e80941Smrg      char basename[256];
274b8e80941Smrg      snprintf(basename, sizeof(basename), "/sys/block/%s", dp->d_name);
275b8e80941Smrg      snprintf(name, sizeof(name), "%s/stat", basename);
276b8e80941Smrg      if (stat(name, &stat_buf) < 0)
277b8e80941Smrg         continue;
278b8e80941Smrg
279b8e80941Smrg      if (!S_ISREG(stat_buf.st_mode))
280b8e80941Smrg         continue;              /* Not a regular file */
281b8e80941Smrg
282b8e80941Smrg      /* Add a physical block device with R/W stats */
283b8e80941Smrg      add_object(basename, dp->d_name, DISKSTAT_RD);
284b8e80941Smrg      add_object(basename, dp->d_name, DISKSTAT_WR);
285b8e80941Smrg
286b8e80941Smrg      /* Add any partitions */
287b8e80941Smrg      struct dirent *dpart;
288b8e80941Smrg      DIR *pdir = opendir(basename);
289b8e80941Smrg      if (!pdir) {
290b8e80941Smrg         mtx_unlock(&gdiskstat_mutex);
291b8e80941Smrg         closedir(dir);
292b8e80941Smrg         return 0;
293b8e80941Smrg      }
294b8e80941Smrg
295b8e80941Smrg      while ((dpart = readdir(pdir)) != NULL) {
296b8e80941Smrg         /* Avoid 'lo' and '..' and '.' */
297b8e80941Smrg         if (strlen(dpart->d_name) <= 2)
298b8e80941Smrg            continue;
299b8e80941Smrg
300b8e80941Smrg         char p[64];
301b8e80941Smrg         snprintf(p, sizeof(p), "%s/%s/stat", basename, dpart->d_name);
302b8e80941Smrg         if (stat(p, &stat_buf) < 0)
303b8e80941Smrg            continue;
304b8e80941Smrg
305b8e80941Smrg         if (!S_ISREG(stat_buf.st_mode))
306b8e80941Smrg            continue;           /* Not a regular file */
307b8e80941Smrg
308b8e80941Smrg         /* Add a partition with R/W stats */
309b8e80941Smrg         add_object_part(basename, dpart->d_name, DISKSTAT_RD);
310b8e80941Smrg         add_object_part(basename, dpart->d_name, DISKSTAT_WR);
311b8e80941Smrg      }
312b8e80941Smrg   }
313b8e80941Smrg   closedir(dir);
314b8e80941Smrg
315b8e80941Smrg   if (displayhelp) {
316b8e80941Smrg      list_for_each_entry(struct diskstat_info, dsi, &gdiskstat_list, list) {
317b8e80941Smrg         char line[32];
318b8e80941Smrg         snprintf(line, sizeof(line), "    diskstat-%s-%s",
319b8e80941Smrg                 dsi->mode == DISKSTAT_RD ? "rd" :
320b8e80941Smrg                 dsi->mode == DISKSTAT_WR ? "wr" : "undefined", dsi->name);
321b8e80941Smrg
322b8e80941Smrg         puts(line);
323b8e80941Smrg      }
324b8e80941Smrg   }
325b8e80941Smrg   mtx_unlock(&gdiskstat_mutex);
326b8e80941Smrg
327b8e80941Smrg   return gdiskstat_count;
328b8e80941Smrg}
329b8e80941Smrg
330b8e80941Smrg#endif /* HAVE_GALLIUM_EXTRA_HUD */
331