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