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