1/*
2 KSysGuard, the KDE System Guard
3
4 Copyright (c) 2006 Greg Martyn <greg.martyn@gmail.com>
5
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of version 2 or later of the GNU General Public
8 License as published by the Free Software Foundation.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
19*/
20
21/* This file will read from /proc/diskstats.
22 /proc/diskstats support should exist in kernel versions 2.4.20, 2.5.45, 2.6 and up
23*/
24
25#include <sys/time.h> /* for gettimeofday */
26#include <string.h> /* for strcmp */
27#include <stdlib.h> /* for malloc */
28
29#include <sys/types.h>
30#include <sys/stat.h>
31#include <fcntl.h>
32#include <unistd.h>
33#include <ctype.h>
34
35#include "Command.h"
36#include "ksysguardd.h"
37
38#include "diskstats.h"
39
40#define DISKSTATSBUFSIZE (32 * 1024)
41#define DISKDEVNAMELEN 20
42
43#define STRINGIFY(x) #x
44#define TOSTRING(x) STRINGIFY(x)
45
46typedef struct
47{
48 unsigned long delta;
49 unsigned long old;
50} DiskLoadSample;
51
52typedef struct
53{
54 /* 5 types of samples are taken:
55 total, rio, wio, rBlk, wBlk */
56 DiskLoadSample s[ 5 ];
57} DiskLoadInfo;
58
59typedef struct DiskIOInfo
60{
61 int major;
62 int minor;
63 char* devname;
64
65 int alive;
66 DiskLoadSample total; /* Total accesses - Fields 1+5 */
67 DiskLoadSample rio; /* Read Accesses - Field 1 - # of reads issued */
68 DiskLoadSample wio; /* Write Accesses - Field 5 - # of writes completed */
69 DiskLoadSample rblk; /* Read Data - Field 3 - # of sectors read */
70 DiskLoadSample wblk; /* Written Data - Field 7 - # of sectors written */
71 DiskLoadSample rtim; /* - Field 4 - # of milliseconds spent reading */
72 DiskLoadSample wtim; /* - Field 8 - # of milliseconds spent writing */
73 unsigned int ioqueue; /* - Field 9 - # of I/Os currently in progress */
74 struct DiskIOInfo* next;
75} DiskIOInfo;
76
77/* We have observed deviations of up to 5% in the accuracy of the timer
78* interrupts. So we try to measure the interrupt interval and use this
79* value to calculate timing dependant values. */
80static float timeInterval = 0;
81static struct timeval lastSampling;
82static struct timeval currSampling;
83static struct SensorModul* StatSM;
84
85static DiskLoadInfo* DiskLoad = 0;
86static DiskIOInfo* DiskIO = 0;
87
88static int Dirty = 0;
89
90static void cleanup26DiskList( void );
91static int process26DiskIO( const char* buf );
92
93void initDiskstats( struct SensorModul* sm ) {
94 StatSM = sm;
95 processDiskstats(); /* This causes the disks monitors to be added */
96}
97
98void exitDiskstats( void ) {
99 free( DiskLoad );
100 DiskLoad = 0;
101}
102
103int updateDiskstats( void ) {
104 Dirty = 1;
105 return 0;
106}
107void processDiskstats( void ) {
108
109 char buf[1024];
110 FILE *file = NULL;
111
112 gettimeofday( &currSampling, 0 );
113 /* Process values from /proc/diskstats (Linux >= 2.6.x) */
114 if ( ( file = fopen( "/proc/diskstats", "r" ) ) == NULL )
115 return; /* unable to open file. disable this module. */
116
117
118 /* Process values from /proc/diskstats (Linux >= 2.6.x) */
119 while (fgets(buf, sizeof(buf) - 1, file) != NULL) {
120 process26DiskIO(buf);
121 }
122 fclose( file );
123
124 /* save exact time interval between this and the last read of /proc/stat */
125 timeInterval = currSampling.tv_sec - lastSampling.tv_sec +
126 ( currSampling.tv_usec - lastSampling.tv_usec ) / 1000000.0;
127 lastSampling = currSampling;
128 cleanup26DiskList();
129 Dirty = 0;
130}
131
132static int process26DiskIO( const char* buf ) {
133 /* Process values from /proc/diskstats (Linux >= 2.6.x) */
134
135 /* For each disk /proc/diskstats includes lines as follows:
136 * 3 0 hda 1314558 74053 26451438 14776742 1971172 4607401 52658448 202855090 0 9597019 217637839
137 * 3 1 hda1 178 360 0 0
138 * 3 2 hda2 354 360 0 0
139 * 3 3 hda3 354 360 0 0
140 * 3 4 hda4 0 0 0 0
141 * 3 5 hda5 529506 9616000 4745856 37966848
142 *
143 * - See Documentation/iostats.txt for details on the changes
144 */
145 int major, minor;
146 char devname[DISKDEVNAMELEN];
147 unsigned long total,
148 rio, rmrg, rblk, rtim,
149 wio, wmrg, wblk, wtim,
150 ioqueue, iotim, iotimw;
151 DiskIOInfo *ptr = DiskIO;
152 DiskIOInfo *last = 0;
153 char sensorName[128];
154
155 /*
156 From kernel 2.6.22.1's Documentation/iostats.txt:
157
158 First 3 fields of line are major, minor, devname
159 Then:
160 Field 1 -- # of reads issued
161 This is the total number of reads completed successfully.
162 Field 2 -- # of reads merged, field 6 -- # of writes merged
163 Reads and writes which are adjacent to each other may be merged for
164 efficiency. Thus two 4K reads may become one 8K read before it is
165 ultimately handed to the disk, and so it will be counted (and queued)
166 as only one I/O. This field lets you know how often this was done.
167 Field 3 -- # of sectors read
168 This is the total number of sectors read successfully.
169 Field 4 -- # of milliseconds spent reading
170 This is the total number of milliseconds spent by all reads (as
171 measured from __make_request() to end_that_request_last()).
172 Field 5 -- # of writes completed
173 This is the total number of writes completed successfully.
174 Field 7 -- # of sectors written
175 This is the total number of sectors written successfully.
176 Field 8 -- # of milliseconds spent writing
177 This is the total number of milliseconds spent by all writes (as
178 measured from __make_request() to end_that_request_last()).
179 Field 9 -- # of I/Os currently in progress
180 The only field that should go to zero. Incremented as requests are
181 given to appropriate request_queue_t and decremented as they finish.
182 Field 10 -- # of milliseconds spent doing I/Os
183 This field is increases so long as field 9 is nonzero.
184 Field 11 -- weighted # of milliseconds spent doing I/Os
185 This field is incremented at each I/O start, I/O completion, I/O
186 merge, or read of these stats by the number of I/Os in progress
187 (field 9) times the number of milliseconds spent doing I/O since the
188 last update of this field. This can provide an easy measure of both
189 I/O completion time and the backlog that may be accumulating.
190 */
191
192 switch (sscanf(buf, "%d %d %" TOSTRING(DISKDEVNAMELEN) "s %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu",
193 &major, &minor, devname,
194 &rio, &rmrg, &rblk, &rtim,
195 &wio, &wmrg, &wblk, &wtim,
196 &ioqueue, &iotim, &iotimw))
197 {
198 case 7:
199 /* Partition stats entry */
200 /* Adjust read fields rio rmrg rblk rtim -> rio rblk wio wblk */
201 wblk = rtim;
202 wio = rblk;
203 rblk = rmrg;
204
205 total = rio + wio;
206
207 break;
208 case 14:
209 /* Disk stats entry */
210 total = rio + wio;
211
212 break;
213 default:
214 /* Something unexpected */
215 return -1;
216 }
217 devname[DISKDEVNAMELEN-1] = 0;
218
219 last = 0;
220 ptr = DiskIO;
221 while (ptr) {
222 if (ptr->major == major && ptr->minor == minor)
223 {
224 /* The IO device has already been registered. */
225 ptr->total.delta = total - ptr->total.old;
226 ptr->total.old = total;
227 ptr->rio.delta = rio - ptr->rio.old;
228 ptr->rio.old = rio;
229 ptr->wio.delta = wio - ptr->wio.old;
230 ptr->wio.old = wio;
231 ptr->rblk.delta = rblk - ptr->rblk.old;
232 ptr->rblk.old = rblk;
233 ptr->wblk.delta = wblk - ptr->wblk.old;
234 ptr->wblk.old = wblk;
235 ptr->rtim.delta = rtim - ptr->rtim.old;
236 ptr->rtim.old = rtim;
237 ptr->wtim.delta = wtim - ptr->wtim.old;
238 ptr->wtim.old = wtim;
239 /* fyi: ipqueue doesn't have a delta */
240 ptr->ioqueue = ioqueue;
241
242 ptr->alive = 1;
243 break;
244 }
245
246 last = ptr;
247 ptr = ptr->next;
248 }
249
250 if (!ptr) {
251 /* The IO device has not been registered yet. We need to add it. */
252 ptr = (DiskIOInfo*)malloc( sizeof( DiskIOInfo ) );
253 ptr->major = major;
254 ptr->minor = minor;
255 ptr->devname = devname;
256 ptr->total.delta = 0;
257 ptr->total.old = total;
258 ptr->rio.delta = 0;
259 ptr->rio.old = rio;
260 ptr->wio.delta = 0;
261 ptr->wio.old = wio;
262 ptr->rblk.delta = 0;
263 ptr->rblk.old = rblk;
264 ptr->wblk.delta = 0;
265 ptr->wblk.old = wblk;
266 ptr->rtim.delta = 0;
267 ptr->rtim.old = rtim;
268 ptr->wtim.delta = 0;
269 ptr->wtim.old = wtim;
270 /* fyi: ipqueue doesn't have a delta */
271 ptr->ioqueue = ioqueue;
272
273 ptr->alive = 1;
274 ptr->next = 0;
275 if (last) {
276 /* Append new entry at end of list. */
277 last->next = ptr;
278 }
279 else {
280 /* List is empty, so we insert the fist element into the list. */
281 DiskIO = ptr;
282 }
283
284 sprintf(sensorName, "disk/%s_(%d:%d)/Rate/totalio", devname, major, minor);
285 registerMonitor(sensorName, "float", print26DiskIO, print26DiskIOInfo,
286 StatSM);
287 sprintf(sensorName, "disk/%s_(%d:%d)/Rate/rio", devname, major, minor);
288 registerMonitor(sensorName, "float", print26DiskIO, print26DiskIOInfo,
289 StatSM);
290 sprintf(sensorName, "disk/%s_(%d:%d)/Rate/wio", devname, major, minor);
291 registerMonitor(sensorName, "float", print26DiskIO, print26DiskIOInfo,
292 StatSM);
293 sprintf(sensorName, "disk/%s_(%d:%d)/Rate/rblk", devname, major, minor);
294 registerMonitor(sensorName, "float", print26DiskIO, print26DiskIOInfo,
295 StatSM);
296 sprintf(sensorName, "disk/%s_(%d:%d)/Rate/wblk", devname, major, minor);
297 registerMonitor(sensorName, "float", print26DiskIO, print26DiskIOInfo,
298 StatSM);
299
300 sprintf(sensorName, "disk/%s_(%d:%d)/Delta/totalio", devname, major, minor);
301 registerMonitor(sensorName, "integer", print26DiskIO, print26DiskIOInfo,
302 StatSM);
303 sprintf(sensorName, "disk/%s_(%d:%d)/Delta/rio", devname, major, minor);
304 registerMonitor(sensorName, "integer", print26DiskIO, print26DiskIOInfo,
305 StatSM);
306 sprintf(sensorName, "disk/%s_(%d:%d)/Delta/wio", devname, major, minor);
307 registerMonitor(sensorName, "integer", print26DiskIO, print26DiskIOInfo,
308 StatSM);
309 sprintf(sensorName, "disk/%s_(%d:%d)/Delta/rblk", devname, major, minor);
310 registerMonitor(sensorName, "integer", print26DiskIO, print26DiskIOInfo,
311 StatSM);
312 sprintf(sensorName, "disk/%s_(%d:%d)/Delta/wblk", devname, major, minor);
313 registerMonitor(sensorName, "integer", print26DiskIO, print26DiskIOInfo,
314 StatSM);
315 sprintf(sensorName, "disk/%s_(%d:%d)/Delta/rtim", devname, major, minor);
316 registerMonitor(sensorName, "integer", print26DiskIO, print26DiskIOInfo,
317 StatSM);
318 sprintf(sensorName, "disk/%s_(%d:%d)/Delta/wtim", devname, major, minor);
319 registerMonitor(sensorName, "integer", print26DiskIO, print26DiskIOInfo,
320 StatSM);
321
322 sprintf(sensorName, "disk/%s_(%d:%d)/ioqueue", devname, major, minor);
323 registerMonitor(sensorName, "integer", print26DiskIO, print26DiskIOInfo,
324 StatSM);
325 }
326
327 return 0;
328}
329
330static void cleanup26DiskList( void ) {
331 DiskIOInfo* ptr = DiskIO;
332 DiskIOInfo* last = 0;
333
334 while ( ptr ) {
335 if ( ptr->alive == 0 ) {
336 DiskIOInfo* newPtr;
337 char sensorName[ 128 ];
338
339 /* Disk device has disappeared. We have to remove it from
340 * the list and unregister the monitors. */
341 sprintf( sensorName, "disk/%s_(%d:%d)/Rate/totalio", ptr->devname, ptr->major, ptr->minor );
342 removeMonitor( sensorName );
343 sprintf( sensorName, "disk/%s_(%d:%d)/Rate/rio", ptr->devname, ptr->major, ptr->minor );
344 removeMonitor( sensorName );
345 sprintf( sensorName, "disk/%s_(%d:%d)/Rate/wio", ptr->devname, ptr->major, ptr->minor );
346 removeMonitor( sensorName );
347 sprintf( sensorName, "disk/%s_(%d:%d)/Rate/rblk", ptr->devname, ptr->major, ptr->minor );
348 removeMonitor( sensorName );
349 sprintf( sensorName, "disk/%s_(%d:%d)/Rate/wblk", ptr->devname, ptr->major, ptr->minor );
350 removeMonitor( sensorName );
351
352 sprintf( sensorName, "disk/%s_(%d:%d)/Delta/totalio", ptr->devname, ptr->major, ptr->minor );
353 removeMonitor( sensorName );
354 sprintf( sensorName, "disk/%s_(%d:%d)/Delta/rio", ptr->devname, ptr->major, ptr->minor );
355 removeMonitor( sensorName );
356 sprintf( sensorName, "disk/%s_(%d:%d)/Delta/wio", ptr->devname, ptr->major, ptr->minor );
357 removeMonitor( sensorName );
358 sprintf( sensorName, "disk/%s_(%d:%d)/Delta/rblk", ptr->devname, ptr->major, ptr->minor );
359 removeMonitor( sensorName );
360 sprintf( sensorName, "disk/%s_(%d:%d)/Delta/wblk", ptr->devname, ptr->major, ptr->minor );
361 removeMonitor( sensorName );
362 sprintf( sensorName, "disk/%s_(%d:%d)/Delta/rtim", ptr->devname, ptr->major, ptr->minor );
363 removeMonitor( sensorName );
364 sprintf( sensorName, "disk/%s_(%d:%d)/Delta/wtim", ptr->devname, ptr->major, ptr->minor );
365 removeMonitor( sensorName );
366
367 sprintf( sensorName, "disk/%s_(%d:%d)/ioqueue", ptr->devname, ptr->major, ptr->minor );
368 removeMonitor( sensorName );
369
370 if ( last ) {
371 last->next = ptr->next;
372 newPtr = ptr->next;
373 }
374 else {
375 DiskIO = ptr->next;
376 newPtr = DiskIO;
377 last = 0;
378 }
379
380 free ( ptr );
381 ptr = newPtr;
382 }
383 else {
384 ptr->alive = 0;
385 last = ptr;
386 ptr = ptr->next;
387 }
388 }
389}
390
391void print26DiskIO( const char* cmd ) {
392 int major, minor;
393 char devname[DISKDEVNAMELEN];
394 char name[ 17 ];
395 DiskIOInfo* ptr;
396
397 if ( Dirty )
398 processDiskstats();
399
400 if(sscanf( cmd, "disk/%[^_]_(%d:%d)/Rate/%16s", devname, &major, &minor, name ) == 4) {
401 /* Show rate of change in sensor values in this interval */
402
403 ptr = DiskIO;
404 while ( ptr && ( ptr->major != major || ptr->minor != minor ) )
405 ptr = ptr->next;
406
407 if ( !ptr ) {
408 print_error( "RECONFIGURE" );
409 output( "0\n" );
410
411 log_error( "Disk device disappeared" );
412 return;
413 }
414
415 if ( strcmp( name, "totalio" ) == 0 )
416 output( "%f\n", (float)( ptr->total.delta / timeInterval ) );
417 else if ( strcmp( name, "rio" ) == 0 )
418 output( "%f\n", (float)( ptr->rio.delta / timeInterval ) );
419 else if ( strcmp( name, "wio" ) == 0 )
420 output( "%f\n", (float)( ptr->wio.delta / timeInterval ) );
421 else if ( strcmp( name, "rblk" ) == 0 )
422 output( "%f\n", (float)( ptr->rblk.delta / ( timeInterval * 2 ) ) );
423 else if ( strcmp( name, "wblk" ) == 0 )
424 output( "%f\n", (float)( ptr->wblk.delta / ( timeInterval * 2 ) ) );
425 else {
426 output( "0\n" );
427 log_error( "Unknown disk device property \'%s\'", name );
428 }
429 }
430 else if(sscanf( cmd, "disk/%[^_]_(%d:%d)/Delta/%16s", devname, &major, &minor, name ) == 4) {
431 /* Show change in sensor values per this interval */
432
433 ptr = DiskIO;
434 while ( ptr && ( ptr->major != major || ptr->minor != minor ) )
435 ptr = ptr->next;
436
437 if ( !ptr ) {
438 print_error( "RECONFIGURE" );
439 output( "0\n" );
440
441 log_error( "Disk device disappeared" );
442 return;
443 }
444
445 if ( strcmp( name, "totalio" ) == 0 )
446 output( "%lu\n", ptr->total.delta );
447 else if ( strcmp( name, "rio" ) == 0 )
448 output( "%lu\n", ptr->rio.delta );
449 else if ( strcmp( name, "wio" ) == 0 )
450 output( "%lu\n", ptr->wio.delta );
451 else if ( strcmp( name, "rblk" ) == 0 )
452 output( "%lu\n", ptr->rblk.delta );
453 else if ( strcmp( name, "wblk" ) == 0 )
454 output( "%lu\n", ptr->wblk.delta );
455 else if ( strcmp( name, "rtim" ) == 0 )
456 output( "%lu\n", ptr->rtim.delta );
457 else if ( strcmp( name, "wtim" ) == 0 )
458 output( "%lu\n", ptr->wtim.delta );
459 else {
460 output( "0\n" );
461 log_error( "Unknown disk device property \'%s\'", name );
462 }
463 }
464 else if(sscanf( cmd, "disk/%[^_]_(%d:%d)/%16s", devname, &major, &minor, name ) == 4) {
465 /* Show raw sensor values */
466
467 ptr = DiskIO;
468 while ( ptr && ( ptr->major != major || ptr->minor != minor ) )
469 ptr = ptr->next;
470
471 if ( !ptr ) {
472 print_error( "RECONFIGURE" );
473 output( "0\n" );
474
475 log_error( "Disk device disappeared" );
476 return;
477 }
478
479 if ( strcmp( name, "ioqueue" ) == 0 )
480 output( "%u\n", ptr->ioqueue );
481 else {
482 output( "0\n" );
483 log_error( "Unknown disk device property \'%s\'", name );
484 }
485 }
486}
487
488void print26DiskIOInfo( const char* cmd ) {
489 int major, minor;
490 char devname[DISKDEVNAMELEN];
491 char name[ 17 ];
492 DiskIOInfo* ptr = DiskIO;
493
494 /* For now we print the same info regardless of whether it was a rate, delta, or raw monitor */
495 if(sscanf( cmd, "disk/%[^_]_(%d:%d)/Rate/%16s", devname, &major, &minor, name ) == 4) {
496 }
497 else if(sscanf( cmd, "disk/%[^_]_(%d:%d)/Delta/%16s", devname, &major, &minor, name ) == 4) {
498 }
499 else if(sscanf( cmd, "disk/%[^_]_(%d:%d)/%16s", devname, &major, &minor, name ) == 4) {
500 }
501 else {
502 output( "Dummy\t0\t0\t\n" );
503 log_error( "Request for unknown device property \'%s\'", cmd );
504 }
505
506 while ( ptr && ( ptr->major != major || ptr->minor != minor ) )
507 ptr = ptr->next;
508
509 if ( !ptr ) {
510 /* Disk device has disappeared. Print a dummy answer. */
511 output( "Dummy\t0\t0\t\n" );
512 return;
513 }
514
515 /* remove trailing '?' */
516 name[ strlen( name ) - 1 ] = '\0';
517
518 if ( strcmp( name, "totalio" ) == 0 )
519 output( "Total accesses device %s (%d:%d)\t0\t0\t1/s\n",
520 devname, major, minor );
521 else if ( strcmp( name, "rio" ) == 0 )
522 output( "Read data device %s (%d:%d)\t0\t0\t1/s\n",
523 devname, major, minor );
524 else if ( strcmp( name, "wio" ) == 0 )
525 output( "Write data device %s (%d:%d)\t0\t0\t1/s\n",
526 devname, major, minor );
527 else if ( strcmp( name, "rblk" ) == 0 )
528 output( "Read accesses device %s (%d:%d)\t0\t0\tKB/s\n",
529 devname, major, minor );
530 else if ( strcmp( name, "wblk" ) == 0 )
531 output( "Write accesses device %s (%d:%d)\t0\t0\tKB/s\n",
532 devname, major, minor );
533 else if ( strcmp( name, "rtim" ) == 0 )
534 output( "# of milliseconds spent reading device %s (%d:%d)\t0\t0\ts\n",
535 devname, major, minor );
536 else if ( strcmp( name, "wtim" ) == 0 )
537 output( "# of milliseconds spent writing device %s (%d:%d)\t0\t0\ts\n",
538 devname, major, minor );
539 else if ( strcmp( name, "ioqueue" ) == 0 )
540 output( "# of I/Os currently in progress on device %s (%d:%d)\t0\t0\t\n",
541 devname, major, minor );
542 else {
543 output( "Dummy\t0\t0\t\n" );
544 log_error( "Request for unknown device property \'%s\'", name );
545 }
546}
547