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