ftp://ftp.kernel.org/pub/linux/kernel/v2.6/linux-2.6.6.tar.bz2
[linux-2.6.git] / Documentation / laptop-mode.txt
1 How to conserve battery power using laptop-mode
2 -----------------------------------------------
3
4 Document Author: Bart Samwel (bart@samwel.tk)
5 Date created: January 2, 2004
6 Last modified: April 3, 2004
7
8 Introduction
9 ------------
10
11 Laptopmode is used to minimize the time that the hard disk needs to be spun up,
12 to conserve battery power on laptops. It has been reported to cause significant
13 power savings.
14
15 Contents
16 --------
17
18 * Introduction
19 * The short story
20 * Caveats
21 * The details
22 * Tips & Tricks
23 * Control script
24 * ACPI integration
25 * Monitoring tool
26
27
28 The short story
29 ---------------
30
31 If you just want to use it, run the laptop_mode control script (which is included
32 at the end of this document) as follows:
33
34 # laptop_mode start
35
36 Then set your harddisk spindown time to a relatively low value with hdparm:
37
38 hdparm -S 4 /dev/hda
39
40 The value -S 4 means 20 seconds idle time before spindown. Your harddisk will
41 now only spin up when a disk cache miss occurs, or at least once every 10
42 minutes to write back any pending changes.
43
44 To stop laptop_mode, run "laptop_mode stop".
45
46
47 Caveats
48 -------
49
50 * The downside of laptop mode is that you have a chance of losing up
51   to 10 minutes of work. If you cannot afford this, don't use it! It's
52   wise to turn OFF laptop mode when you're almost out of battery --
53   although this will make the battery run out faster, at least you'll
54   lose less work when it actually runs out. I'm still looking for someone
55   to submit instructions on how to turn off laptop mode when battery is low,
56   e.g., using ACPI events. I don't have a laptop myself, so if you do and
57   you care to contribute such instructions, please do.
58
59 * Most desktop hard drives have a very limited lifetime measured in spindown
60   cycles, typically about 50.000 times (it's usually listed on the spec sheet).
61   Check your drive's rating, and don't wear down your drive's lifetime if you
62   don't need to.
63
64 * If you mount some of your ext3/reiserfs filesystems with the -n option, then
65   the control script will not be able to remount them correctly. You must set
66   DO_REMOUNTS=0 in the control script, otherwise it will remount them with the
67   wrong options -- or it will fail because it cannot write to /etc/mtab.
68
69 * If you have your filesystems listed as type "auto" in fstab, like I did, then
70   the control script will not recognize them as filesystems that need remounting.
71
72 * If you have XFS, make SURE that you set the XFS_HZ value in the control script
73   correctly, to the value of HZ of your running kernel. Laptop mode will not
74   work correctly if it is set too low, and you may lose data if it is set too
75   high. The reason for this problem is that XFS does not export its sysctl
76   variables in centisecs (like most other subsystems do) but in "jiffies",
77   which is an internal kernel measure. Once this is fixed things will get better.
78
79
80 The details
81 -----------
82
83 Laptop-mode is controlled by the flag /proc/sys/vm/laptop_mode. When this
84 flag is set, any physical disk read operation (that might have caused the
85 hard disk to spin up) causes Linux to flush all dirty blocks. The result
86 of this is that after a disk has spun down, it will not be spun up anymore
87 to write dirty blocks, because those blocks had already been written
88 immediately after the most recent read operation
89
90 To increase the effectiveness of the laptop_mode strategy, the laptop_mode
91 control script increases dirty_expire_centisecs and dirty_writeback_centisecs in
92 /proc/sys/vm to about 10 minutes (by default), which means that pages that are
93 dirtied are not forced to be written to disk as often. The control script also
94 changes the dirty background ratio, so that background writeback of dirty pages
95 is not done anymore. Combined with a higher commit value (also 10 minutes) for
96 ext3 or ReiserFS filesystems (also done automatically by the control script),
97 this results in concentration of disk activity in a small time interval which
98 occurs only once every 10 minutes, or whenever the disk is forced to spin up by
99 a cache miss. The disk can then be spun down in the periods of inactivity.
100
101 If you want to find out which process caused the disk to spin up, you can
102 gather information by setting the flag /proc/sys/vm/block_dump. When this flag
103 is set, Linux reports all disk read and write operations that take place, and
104 all block dirtyings done to files. This makes it possible to debug why a disk
105 needs to spin up, and to increase battery life even more. The output of
106 block_dump is written to the kernel output, and it can be retrieved using
107 "dmesg". When you use block_dump, you may want to turn off klogd, otherwise
108 the output of block_dump will be logged, causing disk activity that is not
109 normally there.
110
111 If 10 minutes is too much or too little downtime for you, you can configure
112 this downtime as follows. In the control script, set the MAX_AGE value to the
113 maximum number of seconds of disk downtime that you would like. You should
114 then set your filesystem's commit interval to the same value. The dirty ratio
115 is also configurable from the control script.
116
117 If you don't like the idea of the control script remounting your filesystems
118 for you, you can change DO_REMOUNTS to 0 in the script.
119
120 Thanks to Kiko Piris, the control script can be used to enable laptop mode on
121 both the Linux 2.4 and 2.6 series.
122
123
124 Tips & Tricks
125 -------------
126
127 * Bartek Kania reports getting up to 50 minutes of extra battery life (on top
128   of his regular 3 to 3.5 hours) using very aggressive power management (hdparm
129   -B1) and a spindown time of 5 seconds (hdparm -S1).
130
131 * You can spin down the disk while playing MP3, by setting the disk readahead
132   to 8MB (hdparm -a 16384). Effectively, the disk will read a complete MP3 at
133   once, and will then spin down while the MP3 is playing. (Thanks to Bartek
134   Kania.)
135
136 * Drew Scott Daniels observed: "I don't know why, but when I decrease the number
137   of colours that my display uses it consumes less battery power. I've seen
138   this on powerbooks too. I hope that this is a piece of information that
139   might be useful to the Laptop Mode patch or it's users."
140
141 * One thing which will cause disks to spin up is not-present application
142   and dynamic library text pages.  The kernel will load program text off disk
143   on-demand, so each time you invoke an application feature for the first
144   time, the kernel needs to spin the disk up to go and fetch that part of the
145   application.
146
147   So it is useful to increase the disk readahead parameter greatly, so that
148   the kernel will pull all of the executable's pages into memory on the first
149   pagefault.
150
151   The supplied script does this.
152
153 * In syslog.conf, you can prefix entries with a dash ``-'' to omit syncing the
154   file after every logging. When you're using laptop-mode and your disk doesn't
155   spin down, this is a likely culprit.
156
157 * Richard Atterer observed that laptop mode does not work well with noflushd
158   (http://noflushd.sourceforge.net/), it seems that noflushd prevents laptop-mode
159   from doing its thing.
160
161
162 Control script
163 --------------
164
165 Please note that this control script works for the Linux 2.4 and 2.6 series.
166
167 --------------------CONTROL SCRIPT BEGIN------------------------------------------
168 #! /bin/sh
169
170 # start or stop laptop_mode, best run by a power management daemon when
171 # ac gets connected/disconnected from a laptop
172 #
173 # install as /sbin/laptop_mode
174 #
175 # Contributors to this script:   Kiko Piris
176 #                                Bart Samwel
177 #                                Micha Feigin
178 #                                Andrew Morton
179 #                                Dax Kelson
180 #
181 # Original Linux 2.4 version by: Jens Axboe
182
183 # Remove an option (the first parameter) of the form option=<number> from
184 # a mount options string (the rest of the parameters).
185 parse_mount_opts () {
186         OPT="$1"
187         shift
188         echo "$*"                       | \
189         sed 's/.*/,&,/'                 | \
190         sed 's/,'"$OPT"'=[0-9]*,/,/g'   | \
191         sed 's/,,*/,/g'                 | \
192         sed 's/^,//'                    | \
193         sed 's/,$//'                    | \
194         cat -
195 }
196
197 # Remove an option (the first parameter) without any arguments from
198 # a mount option string (the rest of the parameters).
199 parse_nonumber_mount_opts () {
200         OPT="$1"
201         shift
202         echo "$*"                       | \
203         sed 's/.*/,&,/'                 | \
204         sed 's/,'"$OPT"',/,/g'          | \
205         sed 's/,,*/,/g'                 | \
206         sed 's/^,//'                    | \
207         sed 's/,$//'                    | \
208         cat -
209 }
210
211 # Find out the state of a yes/no option (e.g. "atime"/"noatime") in
212 # fstab for a given filesystem, and use this state to replace the
213 # value of the option in another mount options string. The device
214 # is the first argument, the option name the second, and the default
215 # value the third. The remainder is the mount options string.
216 #
217 # Example:
218 # parse_yesno_opts_wfstab /dev/hda1 atime atime defaults,noatime
219 #
220 # If fstab contains, say, "rw" for this filesystem, then the result
221 # will be "defaults,atime".
222 parse_yesno_opts_wfstab () {
223         L_DEV=$1
224         shift
225         OPT=$1
226         shift
227         DEF_OPT=$1
228         shift
229         L_OPTS="$*"
230         PARSEDOPTS1="$(parse_nonumber_mount_opts $OPT $L_OPTS)"
231         PARSEDOPTS1="$(parse_nonumber_mount_opts no$OPT $PARSEDOPTS1)"
232         # Watch for a default atime in fstab
233         FSTAB_OPTS="$(cat /etc/fstab | sed 's/  / /g' | grep ^\ *"$L_DEV " | awk '{ print $4 }')"
234         if [ -z "$(echo "$FSTAB_OPTS" | grep "$OPT")" ] ; then
235                 # option not specified in fstab -- choose the default.
236                 echo "$PARSEDOPTS1,$DEF_OPT"
237         else
238                 # option specified in fstab: extract the value and use it
239                 if [ -z "$(echo "$FSTAB_OPTS" | grep "no$OPT")" ] ; then
240                         # no$OPT not found -- so we must have $OPT.
241                         echo "$PARSEDOPTS1,$OPT"
242                 else
243                         echo "$PARSEDOPTS1,no$OPT"
244                 fi
245         fi
246 }
247
248 # Find out the state of a numbered option (e.g. "commit=NNN") in
249 # fstab for a given filesystem, and use this state to replace the
250 # value of the option in another mount options string. The device
251 # is the first argument, and the option name the second. The
252 # remainder is the mount options string in which the replacement
253 # must be done.
254 #
255 # Example:
256 # parse_mount_opts_wfstab /dev/hda1 commit defaults,commit=7
257 #
258 # If fstab contains, say, "commit=3,rw" for this filesystem, then the
259 # result will be "rw,commit=3".
260 parse_mount_opts_wfstab () {
261         L_DEV=$1
262         shift
263         OPT=$1
264         shift
265         L_OPTS="$*"
266
267         PARSEDOPTS1="$(parse_mount_opts $OPT $L_OPTS)"
268         # Watch for a default commit in fstab
269         FSTAB_OPTS="$(cat /etc/fstab | sed 's/  / /g' | grep ^\ *"$L_DEV " | awk '{ print $4 }')"
270         if [ -z "$(echo "$FSTAB_OPTS" | grep "$OPT=")" ] ; then
271                 # option not specified in fstab: set it to 0
272                 echo "$PARSEDOPTS1,$OPT=0"
273         else
274                 # option specified in fstab: extract the value, and use it
275                 echo -n "$PARSEDOPTS1,$OPT="
276                 echo "$FSTAB_OPTS"      | \
277                 sed 's/.*/,&,/'         | \
278                 sed 's/.*,'"$OPT"'=//'  | \
279                 sed 's/,.*//'           | \
280                 cat -
281         fi
282 }
283
284 KLEVEL="$(uname -r | cut -c1-3)"
285 case "$KLEVEL" in
286         "2.4"|"2.6")
287                 true
288                 ;;
289         *)
290                 echo "Unhandled kernel version: $KLEVEL ('uname -r' = '$(uname -r)')"
291                 exit 1
292                 ;;
293 esac
294
295 # Shall we remount journaled fs. with appropiate commit interval? (1=yes)
296 DO_REMOUNTS=1
297
298 # age time, in seconds. should be put into a sysconfig file
299 MAX_AGE=600
300
301 # Dirty synchronous ratio.  At this percentage of dirty pages the process which
302 # calls write() does its own writeback
303 DIRTY_RATIO=40
304
305 #
306 # Allowed dirty background ratio, in percent.  Once DIRTY_RATIO has been
307 # exceeded, the kernel will wake pdflush which will then reduce the amount
308 # of dirty memory to dirty_background_ratio.  Set this nice and low, so once
309 # some writeout has commenced, we do a lot of it.
310 #
311 DIRTY_BACKGROUND_RATIO=5
312
313 READAHEAD=4096          # kilobytes
314
315 # kernel default dirty buffer age
316 DEF_AGE=30
317 DEF_UPDATE=5
318 DEF_DIRTY_BACKGROUND_RATIO=10
319 DEF_DIRTY_RATIO=40
320 DEF_XFS_AGE_BUFFER=15
321 DEF_XFS_SYNC_INTERVAL=30
322
323 # This must be adjusted manually to the value of HZ in the running kernel,
324 # until the XFS people change their external interfaces to work in centisecs
325 # like the rest of the external world. Unfortunately this cannot be automated. :(
326 XFS_HZ=1000
327
328 if [ ! -e /proc/sys/vm/laptop_mode ]; then
329         echo "Kernel is not patched with laptop_mode patch."
330         exit 1
331 fi
332
333 if [ ! -w /proc/sys/vm/laptop_mode ]; then
334         echo "You do not have enough privileges to enable laptop_mode."
335         exit 1
336 fi
337
338 case "$1" in
339         start)
340                 AGE=$((100*$MAX_AGE))
341                 XFS_AGE=$(($XFS_HZ*$MAX_AGE))
342                 echo -n "Starting laptop_mode"
343
344                 if [ -d /proc/sys/vm/pagebuf ] ; then
345                         # This only needs to be set, not reset -- it is only used when
346                         # laptop mode is enabled.
347                         echo $XFS_AGE > /proc/sys/vm/pagebuf/lm_flush_age
348                         echo $XFS_AGE > /proc/sys/fs/xfs/lm_sync_interval
349                 elif [ -f /proc/sys/fs/xfs/lm_age_buffer ] ; then
350                         # The same goes for these.
351                         echo $XFS_AGE > /proc/sys/fs/xfs/lm_age_buffer
352                         echo $XFS_AGE > /proc/sys/fs/xfs/lm_sync_interval
353                 elif [ -f /proc/sys/fs/xfs/age_buffer ] ; then
354                         # But not for these -- they are also used in normal
355                         # operation.
356                         echo $XFS_AGE > /proc/sys/fs/xfs/age_buffer
357                         echo $XFS_AGE > /proc/sys/fs/xfs/sync_interval
358                 fi
359
360                 case "$KLEVEL" in
361                         "2.4")
362                                 echo "1"                                > /proc/sys/vm/laptop_mode
363                                 echo "30 500 0 0 $AGE $AGE 60 20 0"     > /proc/sys/vm/bdflush
364                                 ;;
365                         "2.6")
366                                 echo "5"                                > /proc/sys/vm/laptop_mode
367                                 echo "$AGE"                             > /proc/sys/vm/dirty_writeback_centisecs
368                                 echo "$AGE"                             > /proc/sys/vm/dirty_expire_centisecs
369                                 echo "$DIRTY_RATIO"                     > /proc/sys/vm/dirty_ratio
370                                 echo "$DIRTY_BACKGROUND_RATIO"          > /proc/sys/vm/dirty_background_ratio
371                                 ;;
372                 esac
373                 if [ $DO_REMOUNTS -eq 1 ]; then
374                         cat /etc/mtab | while read DEV MP FST OPTS DUMP PASS ; do
375                                 PARSEDOPTS="$(parse_mount_opts "$OPTS")"
376                                 case "$FST" in
377                                         "ext3"|"reiserfs")
378                                                 PARSEDOPTS="$(parse_mount_opts commit "$OPTS")"
379                                                 mount $DEV -t $FST $MP -o remount,$PARSEDOPTS,commit=$MAX_AGE,noatime
380                                                 ;;
381                                         "xfs")
382                                                 mount $DEV -t $FST $MP -o remount,$OPTS,noatime
383                                                 ;;
384                                 esac
385                                 if [ -b $DEV ] ; then
386                                         blockdev --setra $(($READAHEAD * 2)) $DEV
387                                 fi
388                         done
389                 fi
390                 echo "."
391                 ;;
392         stop)
393                 U_AGE=$((100*$DEF_UPDATE))
394                 B_AGE=$((100*$DEF_AGE))
395                 echo -n "Stopping laptop_mode"
396                 echo "0" > /proc/sys/vm/laptop_mode
397                 if [ -f /proc/sys/fs/xfs/age_buffer ] && [ ! -f /proc/sys/fs/xfs/lm_age_buffer ] ; then
398                         # These need to be restored though, if there are no lm_*.
399                         echo "$(($XFS_HZ*$DEF_XFS_AGE_BUFFER))"         > /proc/sys/fs/xfs/age_buffer
400                         echo "$(($XFS_HZ*$DEF_XFS_SYNC_INTERVAL))"      > /proc/sys/fs/xfs/sync_interval
401                 fi
402                 case "$KLEVEL" in
403                         "2.4")
404                                 echo "30 500 0 0 $U_AGE $B_AGE 60 20 0" > /proc/sys/vm/bdflush
405                                 ;;
406                         "2.6")
407                                 echo "$U_AGE"                           > /proc/sys/vm/dirty_writeback_centisecs
408                                 echo "$B_AGE"                           > /proc/sys/vm/dirty_expire_centisecs
409                                 echo "$DEF_DIRTY_RATIO"                 > /proc/sys/vm/dirty_ratio
410                                 echo "$DEF_DIRTY_BACKGROUND_RATIO"      > /proc/sys/vm/dirty_background_ratio
411                                 ;;
412                 esac
413                 if [ $DO_REMOUNTS -eq 1 ]; then
414                         cat /etc/mtab | while read DEV MP FST OPTS DUMP PASS ; do
415                                 # Reset commit and atime options to defaults.
416                                 case "$FST" in
417                                         "ext3"|"reiserfs")
418                                                 PARSEDOPTS="$(parse_mount_opts_wfstab $DEV commit $OPTS)"
419                                                 PARSEDOPTS="$(parse_yesno_opts_wfstab $DEV atime atime $PARSEDOPTS)"
420                                                 mount $DEV -t $FST $MP -o remount,$PARSEDOPTS
421                                                 ;;
422                                         "xfs")
423                                                 PARSEDOPTS="$(parse_yesno_opts_wfstab $DEV atime atime $OPTS)"
424                                                 mount $DEV -t $FST $MP -o remount,$PARSEDOPTS
425                                                 ;;
426                                 esac
427                                 if [ -b $DEV ] ; then
428                                         blockdev --setra 256 $DEV
429                                 fi
430                         done
431                 fi
432                 echo "."
433                 ;;
434         *)
435                 echo "Usage: $0 {start|stop}"
436                 ;;
437
438 esac
439
440 exit 0
441
442 --------------------CONTROL SCRIPT END--------------------------------------------
443
444
445 ACPI integration
446 ----------------
447
448 Dax Kelson submitted this so that the ACPI acpid daemon will
449 kick off the laptop_mode script and run hdparm.
450
451 ---------------------------/etc/acpi/events/ac_adapter BEGIN-------------------------------------------
452 event=ac_adapter
453 action=/etc/acpi/actions/battery.sh
454 ---------------------------/etc/acpi/events/ac_adapter END-------------------------------------------
455
456 ---------------------------/etc/acpi/actions/battery.sh BEGIN-------------------------------------------
457 #!/bin/sh
458
459 # cpu throttling
460 # cat /proc/acpi/processor/CPU0/throttling for more info
461 ACAD_THR=0
462 BATT_THR=2
463
464 # spindown time for HD (man hdparm for valid values)
465 # I prefer 2 hours for acad and 20 seconds for batt
466 ACAD_HD=244
467 BATT_HD=4
468
469 # ac/battery event handler
470
471 status=`awk '/^state: / { print $2 }' /proc/acpi/ac_adapter/AC/state`
472
473 case $status in
474         "on-line")
475                 echo "Setting HD spindown to 2 hours"
476                 /sbin/laptop-mode stop
477                 /sbin/hdparm -S $ACAD_HD /dev/hda > /dev/null 2>&1
478                 /sbin/hdparm -B 255 /dev/hda > /dev/null 2>&1
479                 #echo -n $ACAD_CPU:$ACAD_THR > /proc/acpi/processor/CPU0/limit
480                 exit 0
481         ;;
482         "off-line")
483                 echo "Setting HD spindown to 20 seconds"
484                 /sbin/laptop-mode start
485                 /sbin/hdparm -S $BATT_HD /dev/hda > /dev/null 2>&1
486                 /sbin/hdparm -B 1 /dev/hda > /dev/null 2>&1
487                 #echo -n $BATT_CPU:$BATT_THR > /proc/acpi/processor/CPU0/limit
488                 exit 0
489         ;;
490 esac
491 ---------------------------/etc/acpi/actions/battery.sh END-------------------------------------------
492
493 Monitoring tool
494 ---------------
495
496 Bartek Kania submitted this, it can be used to measure how much time your disk
497 spends spun up/down.
498
499 ---------------------------dslm.c BEGIN-------------------------------------------
500 /*
501  * Simple Disk Sleep Monitor
502  *  by Bartek Kania
503  * Licenced under the GPL
504  */
505 #include <unistd.h>
506 #include <stdlib.h>
507 #include <stdio.h>
508 #include <fcntl.h>
509 #include <errno.h>
510 #include <time.h>
511 #include <string.h>
512 #include <signal.h>
513 #include <sys/ioctl.h>
514 #include <linux/hdreg.h>
515
516 #ifdef DEBUG
517 #define D(x) x
518 #else
519 #define D(x)
520 #endif
521
522 int endit = 0;
523
524 /* Check if the disk is in powersave-mode
525  * Most of the code is stolen from hdparm.
526  * 1 = active, 0 = standby/sleep, -1 = unknown */
527 int check_powermode(int fd)
528 {
529     unsigned char args[4] = {WIN_CHECKPOWERMODE1,0,0,0};
530     int state;
531
532     if (ioctl(fd, HDIO_DRIVE_CMD, &args)
533         && (args[0] = WIN_CHECKPOWERMODE2) /* try again with 0x98 */
534         && ioctl(fd, HDIO_DRIVE_CMD, &args)) {
535         if (errno != EIO || args[0] != 0 || args[1] != 0) {
536             state = -1; /* "unknown"; */
537         } else
538             state = 0; /* "sleeping"; */
539     } else {
540         state = (args[2] == 255) ? 1 : 0;
541     }
542     D(printf(" drive state is:  %d\n", state));
543
544     return state;
545 }
546
547 char *state_name(int i)
548 {
549     if (i == -1) return "unknown";
550     if (i == 0) return "sleeping";
551     if (i == 1) return "active";
552
553     return "internal error";
554 }
555
556 char *myctime(time_t time)
557 {
558     char *ts = ctime(&time);
559     ts[strlen(ts) - 1] = 0;
560
561     return ts;
562 }
563
564 void measure(int fd)
565 {
566     time_t start_time;
567     int last_state;
568     time_t last_time;
569     int curr_state;
570     time_t curr_time = 0;
571     time_t time_diff;
572     time_t active_time = 0;
573     time_t sleep_time = 0;
574     time_t unknown_time = 0;
575     time_t total_time = 0;
576     int changes = 0;
577     float tmp;
578
579     printf("Starting measurements\n");
580
581     last_state = check_powermode(fd);
582     start_time = last_time = time(0);
583     printf("  System is in state %s\n\n", state_name(last_state));
584
585     while(!endit) {
586         sleep(1);
587         curr_state = check_powermode(fd);
588
589         if (curr_state != last_state || endit) {
590             changes++;
591             curr_time = time(0);
592             time_diff = curr_time - last_time;
593
594             if (last_state == 1) active_time += time_diff;
595             else if (last_state == 0) sleep_time += time_diff;
596             else unknown_time += time_diff;
597
598             last_state = curr_state;
599             last_time = curr_time;
600
601             printf("%s: State-change to %s\n", myctime(curr_time),
602                    state_name(curr_state));
603         }
604     }
605     changes--; /* Compensate for SIGINT */
606
607     total_time = time(0) - start_time;
608     printf("\nTotal running time:  %lus\n", curr_time - start_time);
609     printf(" State changed %d times\n", changes);
610
611     tmp = (float)sleep_time / (float)total_time * 100;
612     printf(" Time in sleep state:   %lus (%.2f%%)\n", sleep_time, tmp);
613     tmp = (float)active_time / (float)total_time * 100;
614     printf(" Time in active state:  %lus (%.2f%%)\n", active_time, tmp);
615     tmp = (float)unknown_time / (float)total_time * 100;
616     printf(" Time in unknown state: %lus (%.2f%%)\n", unknown_time, tmp);
617 }
618
619 void ender(int s)
620 {
621     endit = 1;
622 }
623
624 void usage()
625 {
626     puts("usage: dslm [-w <time>] <disk>");
627     exit(0);
628 }
629
630 int main(int ac, char **av)
631 {
632     int fd;
633     char *disk = 0;
634     int settle_time = 60;
635
636     /* Parse the simple command-line */
637     if (ac == 2)
638         disk = av[1];
639     else if (ac == 4) {
640         settle_time = atoi(av[2]);
641         disk = av[3];
642     } else
643         usage();
644
645     if (!(fd = open(disk, O_RDONLY|O_NONBLOCK))) {
646         printf("Can't open %s, because: %s\n", disk, strerror(errno));
647         exit(-1);
648     }
649
650     if (settle_time) {
651         printf("Waiting %d seconds for the system to settle down to "
652                "'normal'\n", settle_time);
653         sleep(settle_time);
654     } else
655         puts("Not waiting for system to settle down");
656
657     signal(SIGINT, ender);
658
659     measure(fd);
660
661     close(fd);
662
663     return 0;
664 }
665 ---------------------------dslm.c END---------------------------------------------