1 /**************************************************************************
2 *
3 * Copyright (C) 2016 Steven Toth <[email protected]>
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 "util/simple_mtx.h"
39 #include "util/u_thread.h"
40 #include "util/u_memory.h"
41 #include "util/u_string.h"
42 #include <stdio.h>
43 #include <unistd.h>
44 #include <dirent.h>
45 #include <stdlib.h>
46 #include <unistd.h>
47 #include <inttypes.h>
48 #include <sys/types.h>
49 #include <sys/stat.h>
50 #include <unistd.h>
51
52 struct stat_s
53 {
54 /* Read */
55 uint64_t r_ios;
56 uint64_t r_merges;
57 uint64_t r_sectors;
58 uint64_t r_ticks;
59 /* Write */
60 uint64_t w_ios;
61 uint64_t w_merges;
62 uint64_t w_sectors;
63 uint64_t w_ticks;
64 /* Misc */
65 uint64_t in_flight;
66 uint64_t io_ticks;
67 uint64_t time_in_queue;
68 };
69
70 struct diskstat_info
71 {
72 struct list_head list;
73 int mode; /* DISKSTAT_RD, DISKSTAT_WR */
74 char name[64]; /* EG. sda5 */
75
76 char sysfs_filename[128];
77 uint64_t last_time;
78 struct stat_s last_stat;
79 };
80
81 /* TODO: We don't handle dynamic block device / partition
82 * arrival or removal.
83 * Static globals specific to this HUD category.
84 */
85 static int gdiskstat_count = 0;
86 static struct list_head gdiskstat_list;
87 static simple_mtx_t gdiskstat_mutex = SIMPLE_MTX_INITIALIZER;
88
89 static struct diskstat_info *
find_dsi_by_name(const char * n,int mode)90 find_dsi_by_name(const char *n, int mode)
91 {
92 list_for_each_entry(struct diskstat_info, dsi, &gdiskstat_list, list) {
93 if (dsi->mode != mode)
94 continue;
95 if (strcasecmp(dsi->name, n) == 0)
96 return dsi;
97 }
98 return 0;
99 }
100
101 static int
get_file_values(const char * fn,struct stat_s * s)102 get_file_values(const char *fn, struct stat_s *s)
103 {
104 int ret = 0;
105 FILE *fh = fopen(fn, "r");
106 if (!fh)
107 return -1;
108
109 ret = fscanf(fh,
110 "%" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64
111 " %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64 "",
112 &s->r_ios, &s->r_merges, &s->r_sectors, &s->r_ticks, &s->w_ios,
113 &s->w_merges, &s->w_sectors, &s->w_ticks, &s->in_flight, &s->io_ticks,
114 &s->time_in_queue);
115
116 fclose(fh);
117
118 return ret;
119 }
120
121 static void
query_dsi_load(struct hud_graph * gr,struct pipe_context * pipe)122 query_dsi_load(struct hud_graph *gr, struct pipe_context *pipe)
123 {
124 /* The framework calls us periodically, compensate for the
125 * calling interval accordingly when reporting per second.
126 */
127 struct diskstat_info *dsi = gr->query_data;
128 uint64_t now = os_time_get();
129
130 if (dsi->last_time) {
131 if (dsi->last_time + gr->pane->period <= now) {
132 struct stat_s stat;
133 if (get_file_values(dsi->sysfs_filename, &stat) < 0)
134 return;
135 float val = 0;
136
137 switch (dsi->mode) {
138 case DISKSTAT_RD:
139 val =
140 ((stat.r_sectors -
141 dsi->last_stat.r_sectors) * 512) /
142 (((float) gr->pane->period / 1000) / 1000);
143 break;
144 case DISKSTAT_WR:
145 val =
146 ((stat.w_sectors -
147 dsi->last_stat.w_sectors) * 512) /
148 (((float) gr->pane->period / 1000) / 1000);
149 break;
150 }
151
152 hud_graph_add_value(gr, (uint64_t) val);
153 dsi->last_stat = stat;
154 dsi->last_time = now;
155 }
156 }
157 else {
158 /* initialize */
159 switch (dsi->mode) {
160 case DISKSTAT_RD:
161 case DISKSTAT_WR:
162 get_file_values(dsi->sysfs_filename, &dsi->last_stat);
163 break;
164 }
165 dsi->last_time = now;
166 }
167 }
168
169 /**
170 * Create and initialize a new object for a specific block I/O device.
171 * \param pane parent context.
172 * \param dev_name logical block device name, EG. sda5.
173 * \param mode query read or write (DISKSTAT_RD/DISKSTAT_WR) statistics.
174 */
175 void
hud_diskstat_graph_install(struct hud_pane * pane,const char * dev_name,unsigned int mode)176 hud_diskstat_graph_install(struct hud_pane *pane, const char *dev_name,
177 unsigned int mode)
178 {
179 struct hud_graph *gr;
180 struct diskstat_info *dsi;
181
182 int num_devs = hud_get_num_disks(0);
183 if (num_devs <= 0)
184 return;
185
186 dsi = find_dsi_by_name(dev_name, mode);
187 if (!dsi)
188 return;
189
190 gr = CALLOC_STRUCT(hud_graph);
191 if (!gr)
192 return;
193
194 dsi->mode = mode;
195 if (dsi->mode == DISKSTAT_RD) {
196 snprintf(gr->name, sizeof(gr->name), "%s-Read-MB/s", dsi->name);
197 }
198 else if (dsi->mode == DISKSTAT_WR) {
199 snprintf(gr->name, sizeof(gr->name), "%s-Write-MB/s", dsi->name);
200 }
201 else {
202 free(gr);
203 return;
204 }
205
206 gr->query_data = dsi;
207 gr->query_new_value = query_dsi_load;
208
209 hud_pane_add_graph(pane, gr);
210 hud_pane_set_max_value(pane, 100);
211 }
212
213 static void
add_object_part(const char * basename,const char * name,int objmode)214 add_object_part(const char *basename, const char *name, int objmode)
215 {
216 struct diskstat_info *dsi = CALLOC_STRUCT(diskstat_info);
217
218 snprintf(dsi->name, sizeof(dsi->name), "%s", name);
219 snprintf(dsi->sysfs_filename, sizeof(dsi->sysfs_filename), "%s/%s/stat",
220 basename, name);
221 dsi->mode = objmode;
222 list_addtail(&dsi->list, &gdiskstat_list);
223 gdiskstat_count++;
224 }
225
226 static void
add_object(const char * basename,const char * name,int objmode)227 add_object(const char *basename, const char *name, int objmode)
228 {
229 struct diskstat_info *dsi = CALLOC_STRUCT(diskstat_info);
230
231 snprintf(dsi->name, sizeof(dsi->name), "%s", name);
232 snprintf(dsi->sysfs_filename, sizeof(dsi->sysfs_filename), "%s/stat",
233 basename);
234 dsi->mode = objmode;
235 list_addtail(&dsi->list, &gdiskstat_list);
236 gdiskstat_count++;
237 }
238
239 /**
240 * Initialize internal object arrays and display block I/O HUD help.
241 * \param displayhelp true if the list of detected devices should be
242 displayed on the console.
243 * \return number of detected block I/O devices.
244 */
245 int
hud_get_num_disks(bool displayhelp)246 hud_get_num_disks(bool displayhelp)
247 {
248 struct dirent *dp;
249 struct stat stat_buf;
250 char name[64];
251
252 /* Return the number of block devices and partitions. */
253 simple_mtx_lock(&gdiskstat_mutex);
254 if (gdiskstat_count) {
255 simple_mtx_unlock(&gdiskstat_mutex);
256 return gdiskstat_count;
257 }
258
259 /* Scan /sys/block, for every object type we support, create and
260 * persist an object to represent its different statistics.
261 */
262 list_inithead(&gdiskstat_list);
263 DIR *dir = opendir("/sys/block/");
264 if (!dir) {
265 simple_mtx_unlock(&gdiskstat_mutex);
266 return 0;
267 }
268
269 while ((dp = readdir(dir)) != NULL) {
270
271 /* Avoid 'lo' and '..' and '.' */
272 if (strlen(dp->d_name) <= 2)
273 continue;
274
275 char basename[256];
276 snprintf(basename, sizeof(basename), "/sys/block/%s", dp->d_name);
277 snprintf(name, sizeof(name), "%s/stat", basename);
278 if (stat(name, &stat_buf) < 0)
279 continue;
280
281 if (!S_ISREG(stat_buf.st_mode))
282 continue; /* Not a regular file */
283
284 /* Add a physical block device with R/W stats */
285 add_object(basename, dp->d_name, DISKSTAT_RD);
286 add_object(basename, dp->d_name, DISKSTAT_WR);
287
288 /* Add any partitions */
289 struct dirent *dpart;
290 DIR *pdir = opendir(basename);
291 if (!pdir) {
292 simple_mtx_unlock(&gdiskstat_mutex);
293 closedir(dir);
294 return 0;
295 }
296
297 while ((dpart = readdir(pdir)) != NULL) {
298 /* Avoid 'lo' and '..' and '.' */
299 if (strlen(dpart->d_name) <= 2)
300 continue;
301
302 char p[64];
303 snprintf(p, sizeof(p), "%s/%s/stat", basename, dpart->d_name);
304 if (stat(p, &stat_buf) < 0)
305 continue;
306
307 if (!S_ISREG(stat_buf.st_mode))
308 continue; /* Not a regular file */
309
310 /* Add a partition with R/W stats */
311 add_object_part(basename, dpart->d_name, DISKSTAT_RD);
312 add_object_part(basename, dpart->d_name, DISKSTAT_WR);
313 }
314 }
315 closedir(dir);
316
317 if (displayhelp) {
318 list_for_each_entry(struct diskstat_info, dsi, &gdiskstat_list, list) {
319 char line[32];
320 snprintf(line, sizeof(line), " diskstat-%s-%s",
321 dsi->mode == DISKSTAT_RD ? "rd" :
322 dsi->mode == DISKSTAT_WR ? "wr" : "undefined", dsi->name);
323
324 puts(line);
325 }
326 }
327 simple_mtx_unlock(&gdiskstat_mutex);
328
329 return gdiskstat_count;
330 }
331
332 #endif /* HAVE_GALLIUM_EXTRA_HUD */
333