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