This commit was manufactured by cvs2svn to create tag
[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 To use laptop mode, you don't need to set any kernel configuration options
32 or anything. You simply need to run the laptop_mode control script (which
33 is included in this document) as follows:
34
35 # laptop_mode start
36
37 Then set your harddisk spindown time to a relatively low value with hdparm:
38
39 hdparm -S 4 /dev/hda
40
41 The value -S 4 means 20 seconds idle time before spindown. Your harddisk will
42 now only spin up when a disk cache miss occurs, or at least once every 10
43 minutes to write back any pending changes.
44
45 To stop laptop_mode, run "laptop_mode stop".
46
47
48 Caveats
49 -------
50
51 * The downside of laptop mode is that you have a chance of losing up
52   to 10 minutes of work. If you cannot afford this, don't use it! It's
53   wise to turn OFF laptop mode when you're almost out of battery --
54   although this will make the battery run out faster, at least you'll
55   lose less work when it actually runs out. I'm still looking for someone
56   to submit instructions on how to turn off laptop mode when battery is low,
57   e.g., using ACPI events. I don't have a laptop myself, so if you do and
58   you care to contribute such instructions, please do.
59
60 * Most desktop hard drives have a very limited lifetime measured in spindown
61   cycles, typically about 50.000 times (it's usually listed on the spec sheet).
62   Check your drive's rating, and don't wear down your drive's lifetime if you
63   don't need to.
64
65 * If you mount some of your ext3/reiserfs filesystems with the -n option, then
66   the control script will not be able to remount them correctly. You must set
67   DO_REMOUNTS=0 in the control script, otherwise it will remount them with the
68   wrong options -- or it will fail because it cannot write to /etc/mtab.
69
70 * If you have your filesystems listed as type "auto" in fstab, like I did, then
71   the control script will not recognize them as filesystems that need remounting.
72
73 * It has been reported that some versions of the mutt mail client use file access
74   times to determine whether a folder contains new mail. If you use mutt and
75   experience this, you must disable the noatime remounting in the control script
76   by setting DO_REMOUNT_NOATIME=0.
77
78
79 The details
80 -----------
81
82 Laptop-mode is controlled by the flag /proc/sys/vm/laptop_mode. This flag is
83 present for all kernels that have the laptop mode patch, regardless of any
84 configuration options. When the flag is set, any physical disk read operation
85 (that might have caused the hard disk to spin up) causes Linux to flush all dirty
86 blocks. The result of this is that after a disk has spun down, it will not be spun
87 up anymore 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/bash
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 #                                Herve Eychenne
180 #                                Dax Kelson
181 #
182 # Original Linux 2.4 version by: Jens Axboe
183
184 #############################################################################
185
186 # Age time, in seconds. should be put into a sysconfig file
187 MAX_AGE=600
188
189 # Read-ahead, in kilobytes
190 READAHEAD=4096
191
192 # Shall we remount journaled fs. with appropiate commit interval? (1=yes)
193 DO_REMOUNTS=1
194
195 # And shall we add the "noatime" option to that as well? (1=yes)
196 DO_REMOUNT_NOATIME=1
197
198 # Dirty synchronous ratio.  At this percentage of dirty pages the process which
199 # calls write() does its own writeback
200 DIRTY_RATIO=40
201
202 #
203 # Allowed dirty background ratio, in percent.  Once DIRTY_RATIO has been
204 # exceeded, the kernel will wake pdflush which will then reduce the amount
205 # of dirty memory to dirty_background_ratio.  Set this nice and low, so once
206 # some writeout has commenced, we do a lot of it.
207 #
208 DIRTY_BACKGROUND_RATIO=5
209
210 # kernel default dirty buffer age
211 DEF_AGE=30
212 DEF_UPDATE=5
213 DEF_DIRTY_BACKGROUND_RATIO=10
214 DEF_DIRTY_RATIO=40
215 DEF_XFS_AGE_BUFFER=15
216 DEF_XFS_SYNC_INTERVAL=30
217 DEF_XFS_BUFD_INTERVAL=1
218
219 # This must be adjusted manually to the value of HZ in the running kernel
220 # on 2.4, until the XFS people change their 2.4 external interfaces to work in
221 # centisecs. This can be automated, but it's a work in progress that still needs
222 # some fixes. On 2.6 kernels, XFS uses USER_HZ instead of HZ for external
223 # interfaces, and that is currently always set to 100. So you don't need to
224 # change this on 2.6.
225 XFS_HZ=100
226
227 #############################################################################
228
229 KLEVEL="$(uname -r |
230              {
231                IFS='.' read a b c
232                echo $a.$b
233              }
234 )"
235 case "$KLEVEL" in
236         "2.4"|"2.6")
237                 ;;
238         *)
239                 echo "Unhandled kernel version: $KLEVEL ('uname -r' = '$(uname -r)')" >&2
240                 exit 1
241                 ;;
242 esac
243
244 if [ ! -e /proc/sys/vm/laptop_mode ] ; then
245         echo "Kernel is not patched with laptop_mode patch." >&2
246         exit 1
247 fi
248
249 if [ ! -w /proc/sys/vm/laptop_mode ] ; then
250         echo "You do not have enough privileges to enable laptop_mode." >&2
251         exit 1
252 fi
253
254 # Remove an option (the first parameter) of the form option=<number> from
255 # a mount options string (the rest of the parameters).
256 parse_mount_opts () {
257         OPT="$1"
258         shift
259         echo ",$*," | sed               \
260          -e 's/,'"$OPT"'=[0-9]*,/,/g'   \
261          -e 's/,,*/,/g'                 \
262          -e 's/^,//'                    \
263          -e 's/,$//'
264 }
265
266 # Remove an option (the first parameter) without any arguments from
267 # a mount option string (the rest of the parameters).
268 parse_nonumber_mount_opts () {
269         OPT="$1"
270         shift
271         echo ",$*," | sed               \
272          -e 's/,'"$OPT"',/,/g'          \
273          -e 's/,,*/,/g'                 \
274          -e 's/^,//'                    \
275          -e 's/,$//'
276 }
277
278 # Find out the state of a yes/no option (e.g. "atime"/"noatime") in
279 # fstab for a given filesystem, and use this state to replace the
280 # value of the option in another mount options string. The device
281 # is the first argument, the option name the second, and the default
282 # value the third. The remainder is the mount options string.
283 #
284 # Example:
285 # parse_yesno_opts_wfstab /dev/hda1 atime atime defaults,noatime
286 #
287 # If fstab contains, say, "rw" for this filesystem, then the result
288 # will be "defaults,atime".
289 parse_yesno_opts_wfstab () {
290         L_DEV="$1"
291         OPT="$2"
292         DEF_OPT="$3"
293         shift 3
294         L_OPTS="$*"
295         PARSEDOPTS1="$(parse_nonumber_mount_opts $OPT $L_OPTS)"
296         PARSEDOPTS1="$(parse_nonumber_mount_opts no$OPT $PARSEDOPTS1)"
297         # Watch for a default atime in fstab
298         FSTAB_OPTS="$(awk '$1 == "'$L_DEV'" { print $4 }' /etc/fstab)"
299         if echo "$FSTAB_OPTS" | grep "$OPT" > /dev/null ; then
300                 # option specified in fstab: extract the value and use it
301                 if echo "$FSTAB_OPTS" | grep "no$OPT" > /dev/null ; then
302                         echo "$PARSEDOPTS1,no$OPT"
303                 else
304                         # no$OPT not found -- so we must have $OPT.
305                         echo "$PARSEDOPTS1,$OPT"
306                 fi
307         else
308                 # option not specified in fstab -- choose the default.
309                 echo "$PARSEDOPTS1,$DEF_OPT"
310         fi
311 }
312
313 # Find out the state of a numbered option (e.g. "commit=NNN") in
314 # fstab for a given filesystem, and use this state to replace the
315 # value of the option in another mount options string. The device
316 # is the first argument, and the option name the second. The
317 # remainder is the mount options string in which the replacement
318 # must be done.
319 #
320 # Example:
321 # parse_mount_opts_wfstab /dev/hda1 commit defaults,commit=7
322 #
323 # If fstab contains, say, "commit=3,rw" for this filesystem, then the
324 # result will be "rw,commit=3".
325 parse_mount_opts_wfstab () {
326         L_DEV="$1"
327         OPT="$2"
328         shift 2
329         L_OPTS="$*"
330         PARSEDOPTS1="$(parse_mount_opts $OPT $L_OPTS)"
331         # Watch for a default commit in fstab
332         FSTAB_OPTS="$(awk '$1 == "'$L_DEV'" { print $4 }' /etc/fstab)"
333         if echo "$FSTAB_OPTS" | grep "$OPT=" > /dev/null ; then
334                 # option specified in fstab: extract the value, and use it
335                 echo -n "$PARSEDOPTS1,$OPT="
336                 echo ",$FSTAB_OPTS," | sed \
337                  -e 's/.*,'"$OPT"'=//'  \
338                  -e 's/,.*//'
339         else
340                 # option not specified in fstab: set it to 0
341                 echo "$PARSEDOPTS1,$OPT=0"
342         fi
343 }
344
345
346 if [ $DO_REMOUNT_NOATIME -eq 1 ] ; then
347         NOATIME_OPT=",noatime"
348 fi
349
350 case "$1" in
351         start)
352                 AGE=$((100*$MAX_AGE))
353                 XFS_AGE=$(($XFS_HZ*$MAX_AGE))
354                 echo -n "Starting laptop_mode"
355
356                 if [ -d /proc/sys/vm/pagebuf ] ; then
357                         # (For 2.4 and early 2.6.)
358                         # This only needs to be set, not reset -- it is only used when
359                         # laptop mode is enabled.
360                         echo $XFS_AGE > /proc/sys/vm/pagebuf/lm_flush_age
361                         echo $XFS_AGE > /proc/sys/fs/xfs/lm_sync_interval
362                 elif [ -f /proc/sys/fs/xfs/lm_age_buffer ] ; then
363                         # (A couple of early 2.6 laptop mode patches had these.)
364                         # The same goes for these.
365                         echo $XFS_AGE > /proc/sys/fs/xfs/lm_age_buffer
366                         echo $XFS_AGE > /proc/sys/fs/xfs/lm_sync_interval
367                 elif [ -f /proc/sys/fs/xfs/age_buffer ] ; then
368                         # (2.6.6)
369                         # But not for these -- they are also used in normal
370                         # operation.
371                         echo $XFS_AGE > /proc/sys/fs/xfs/age_buffer
372                         echo $XFS_AGE > /proc/sys/fs/xfs/sync_interval
373                 elif [ -f /proc/sys/fs/xfs/age_buffer_centisecs ] ; then
374                         # (2.6.7 upwards)
375                         # And not for these either. These are in centisecs,
376                         # not USER_HZ, so we have to use $AGE, not $XFS_AGE.
377                         echo $AGE > /proc/sys/fs/xfs/age_buffer_centisecs
378                         echo $AGE > /proc/sys/fs/xfs/xfssyncd_centisecs
379                         echo 3000 > /proc/sys/fs/xfs/xfsbufd_centisecs
380                 fi
381
382                 case "$KLEVEL" in
383                         "2.4")
384                                 echo 1                                  > /proc/sys/vm/laptop_mode
385                                 echo "30 500 0 0 $AGE $AGE 60 20 0"     > /proc/sys/vm/bdflush
386                                 ;;
387                         "2.6")
388                                 echo 5                                  > /proc/sys/vm/laptop_mode
389                                 echo "$AGE"                             > /proc/sys/vm/dirty_writeback_centisecs
390                                 echo "$AGE"                             > /proc/sys/vm/dirty_expire_centisecs
391                                 echo "$DIRTY_RATIO"                     > /proc/sys/vm/dirty_ratio
392                                 echo "$DIRTY_BACKGROUND_RATIO"          > /proc/sys/vm/dirty_background_ratio
393                                 ;;
394                 esac
395                 if [ $DO_REMOUNTS -eq 1 ]; then
396                         cat /etc/mtab | while read DEV MP FST OPTS DUMP PASS ; do
397                                 PARSEDOPTS="$(parse_mount_opts "$OPTS")"
398                                 case "$FST" in
399                                         "ext3"|"reiserfs")
400                                                 PARSEDOPTS="$(parse_mount_opts commit "$OPTS")"
401                                                 mount $DEV -t $FST $MP -o remount,$PARSEDOPTS,commit=$MAX_AGE$NOATIME_OPT
402                                                 ;;
403                                         "xfs")
404                                                 mount $DEV -t $FST $MP -o remount,$OPTS$NOATIME_OPT
405                                                 ;;
406                                 esac
407                                 if [ -b $DEV ] ; then
408                                         blockdev --setra $(($READAHEAD * 2)) $DEV
409                                 fi
410                         done
411                 fi
412                 echo "."
413                 ;;
414         stop)
415                 U_AGE=$((100*$DEF_UPDATE))
416                 B_AGE=$((100*$DEF_AGE))
417                 echo -n "Stopping laptop_mode"
418                 echo 0 > /proc/sys/vm/laptop_mode
419                 if [ -f /proc/sys/fs/xfs/age_buffer -a ! -f /proc/sys/fs/xfs/lm_age_buffer ] ; then
420                         # These need to be restored, if there are no lm_*.
421                         echo $(($XFS_HZ*$DEF_XFS_AGE_BUFFER))           > /proc/sys/fs/xfs/age_buffer
422                         echo $(($XFS_HZ*$DEF_XFS_SYNC_INTERVAL))        > /proc/sys/fs/xfs/sync_interval
423                 elif [ -f /proc/sys/fs/xfs/age_buffer_centisecs ] ; then
424                         # These need to be restored as well.
425                         echo $((100*$DEF_XFS_AGE_BUFFER))       > /proc/sys/fs/xfs/age_buffer_centisecs
426                         echo $((100*$DEF_XFS_SYNC_INTERVAL))    > /proc/sys/fs/xfs/xfssyncd_centisecs
427                         echo $((100*$DEF_XFS_BUFD_INTERVAL))    > /proc/sys/fs/xfs/xfsbufd_centisecs
428                 fi
429                 case "$KLEVEL" in
430                         "2.4")
431                                 echo "30 500 0 0 $U_AGE $B_AGE 60 20 0" > /proc/sys/vm/bdflush
432                                 ;;
433                         "2.6")
434                                 echo "$U_AGE"                           > /proc/sys/vm/dirty_writeback_centisecs
435                                 echo "$B_AGE"                           > /proc/sys/vm/dirty_expire_centisecs
436                                 echo "$DEF_DIRTY_RATIO"                 > /proc/sys/vm/dirty_ratio
437                                 echo "$DEF_DIRTY_BACKGROUND_RATIO"      > /proc/sys/vm/dirty_background_ratio
438                                 ;;
439                 esac
440                 if [ $DO_REMOUNTS -eq 1 ] ; then
441                         cat /etc/mtab | while read DEV MP FST OPTS DUMP PASS ; do
442                                 # Reset commit and atime options to defaults.
443                                 case "$FST" in
444                                         "ext3"|"reiserfs")
445                                                 PARSEDOPTS="$(parse_mount_opts_wfstab $DEV commit $OPTS)"
446                                                 PARSEDOPTS="$(parse_yesno_opts_wfstab $DEV atime atime $PARSEDOPTS)"
447                                                 mount $DEV -t $FST $MP -o remount,$PARSEDOPTS
448                                                 ;;
449                                         "xfs")
450                                                 PARSEDOPTS="$(parse_yesno_opts_wfstab $DEV atime atime $OPTS)"
451                                                 mount $DEV -t $FST $MP -o remount,$PARSEDOPTS
452                                                 ;;
453                                 esac
454                                 if [ -b $DEV ] ; then
455                                         blockdev --setra 256 $DEV
456                                 fi
457                         done
458                 fi
459                 echo "."
460                 ;;
461         *)
462                 echo "Usage: $0 {start|stop}" 2>&1
463                 exit 1
464                 ;;
465
466 esac
467
468 exit 0
469 --------------------CONTROL SCRIPT END--------------------------------------------
470
471
472 ACPI integration
473 ----------------
474
475 Dax Kelson submitted this so that the ACPI acpid daemon will
476 kick off the laptop_mode script and run hdparm.
477
478 ---------------------------/etc/acpi/events/ac_adapter BEGIN-------------------------------------------
479 event=ac_adapter
480 action=/etc/acpi/actions/battery.sh
481 ---------------------------/etc/acpi/events/ac_adapter END-------------------------------------------
482
483 ---------------------------/etc/acpi/actions/battery.sh BEGIN-------------------------------------------
484 #!/bin/sh
485
486 # cpu throttling
487 # cat /proc/acpi/processor/CPU0/throttling for more info
488 ACAD_THR=0
489 BATT_THR=2
490
491 # spindown time for HD (man hdparm for valid values)
492 # I prefer 2 hours for acad and 20 seconds for batt
493 ACAD_HD=244
494 BATT_HD=4
495
496 # ac/battery event handler
497
498 status=`awk '/^state: / { print $2 }' /proc/acpi/ac_adapter/AC/state`
499
500 case $status in
501         "on-line")
502                 echo "Setting HD spindown for AC mode."
503                 /sbin/laptop_mode stop
504                 /sbin/hdparm -S $ACAD_HD /dev/hda > /dev/null 2>&1
505                 /sbin/hdparm -B 255 /dev/hda > /dev/null 2>&1
506                 #echo -n $ACAD_CPU:$ACAD_THR > /proc/acpi/processor/CPU0/limit
507                 exit 0
508         ;;
509         "off-line")
510                 echo "Setting HD spindown for battery mode."
511                 /sbin/laptop_mode start
512                 /sbin/hdparm -S $BATT_HD /dev/hda > /dev/null 2>&1
513                 /sbin/hdparm -B 1 /dev/hda > /dev/null 2>&1
514                 #echo -n $BATT_CPU:$BATT_THR > /proc/acpi/processor/CPU0/limit
515                 exit 0
516         ;;
517 esac
518 ---------------------------/etc/acpi/actions/battery.sh END-------------------------------------------
519
520 Monitoring tool
521 ---------------
522
523 Bartek Kania submitted this, it can be used to measure how much time your disk
524 spends spun up/down.
525
526 ---------------------------dslm.c BEGIN-------------------------------------------
527 /*
528  * Simple Disk Sleep Monitor
529  *  by Bartek Kania
530  * Licenced under the GPL
531  */
532 #include <unistd.h>
533 #include <stdlib.h>
534 #include <stdio.h>
535 #include <fcntl.h>
536 #include <errno.h>
537 #include <time.h>
538 #include <string.h>
539 #include <signal.h>
540 #include <sys/ioctl.h>
541 #include <linux/hdreg.h>
542
543 #ifdef DEBUG
544 #define D(x) x
545 #else
546 #define D(x)
547 #endif
548
549 int endit = 0;
550
551 /* Check if the disk is in powersave-mode
552  * Most of the code is stolen from hdparm.
553  * 1 = active, 0 = standby/sleep, -1 = unknown */
554 int check_powermode(int fd)
555 {
556     unsigned char args[4] = {WIN_CHECKPOWERMODE1,0,0,0};
557     int state;
558
559     if (ioctl(fd, HDIO_DRIVE_CMD, &args)
560         && (args[0] = WIN_CHECKPOWERMODE2) /* try again with 0x98 */
561         && ioctl(fd, HDIO_DRIVE_CMD, &args)) {
562         if (errno != EIO || args[0] != 0 || args[1] != 0) {
563             state = -1; /* "unknown"; */
564         } else
565             state = 0; /* "sleeping"; */
566     } else {
567         state = (args[2] == 255) ? 1 : 0;
568     }
569     D(printf(" drive state is:  %d\n", state));
570
571     return state;
572 }
573
574 char *state_name(int i)
575 {
576     if (i == -1) return "unknown";
577     if (i == 0) return "sleeping";
578     if (i == 1) return "active";
579
580     return "internal error";
581 }
582
583 char *myctime(time_t time)
584 {
585     char *ts = ctime(&time);
586     ts[strlen(ts) - 1] = 0;
587
588     return ts;
589 }
590
591 void measure(int fd)
592 {
593     time_t start_time;
594     int last_state;
595     time_t last_time;
596     int curr_state;
597     time_t curr_time = 0;
598     time_t time_diff;
599     time_t active_time = 0;
600     time_t sleep_time = 0;
601     time_t unknown_time = 0;
602     time_t total_time = 0;
603     int changes = 0;
604     float tmp;
605
606     printf("Starting measurements\n");
607
608     last_state = check_powermode(fd);
609     start_time = last_time = time(0);
610     printf("  System is in state %s\n\n", state_name(last_state));
611
612     while(!endit) {
613         sleep(1);
614         curr_state = check_powermode(fd);
615
616         if (curr_state != last_state || endit) {
617             changes++;
618             curr_time = time(0);
619             time_diff = curr_time - last_time;
620
621             if (last_state == 1) active_time += time_diff;
622             else if (last_state == 0) sleep_time += time_diff;
623             else unknown_time += time_diff;
624
625             last_state = curr_state;
626             last_time = curr_time;
627
628             printf("%s: State-change to %s\n", myctime(curr_time),
629                    state_name(curr_state));
630         }
631     }
632     changes--; /* Compensate for SIGINT */
633
634     total_time = time(0) - start_time;
635     printf("\nTotal running time:  %lus\n", curr_time - start_time);
636     printf(" State changed %d times\n", changes);
637
638     tmp = (float)sleep_time / (float)total_time * 100;
639     printf(" Time in sleep state:   %lus (%.2f%%)\n", sleep_time, tmp);
640     tmp = (float)active_time / (float)total_time * 100;
641     printf(" Time in active state:  %lus (%.2f%%)\n", active_time, tmp);
642     tmp = (float)unknown_time / (float)total_time * 100;
643     printf(" Time in unknown state: %lus (%.2f%%)\n", unknown_time, tmp);
644 }
645
646 void ender(int s)
647 {
648     endit = 1;
649 }
650
651 void usage()
652 {
653     puts("usage: dslm [-w <time>] <disk>");
654     exit(0);
655 }
656
657 int main(int ac, char **av)
658 {
659     int fd;
660     char *disk = 0;
661     int settle_time = 60;
662
663     /* Parse the simple command-line */
664     if (ac == 2)
665         disk = av[1];
666     else if (ac == 4) {
667         settle_time = atoi(av[2]);
668         disk = av[3];
669     } else
670         usage();
671
672     if (!(fd = open(disk, O_RDONLY|O_NONBLOCK))) {
673         printf("Can't open %s, because: %s\n", disk, strerror(errno));
674         exit(-1);
675     }
676
677     if (settle_time) {
678         printf("Waiting %d seconds for the system to settle down to "
679                "'normal'\n", settle_time);
680         sleep(settle_time);
681     } else
682         puts("Not waiting for system to settle down");
683
684     signal(SIGINT, ender);
685
686     measure(fd);
687
688     close(fd);
689
690     return 0;
691 }
692 ---------------------------dslm.c END---------------------------------------------