Merge branch 'vendor/OPENSSL'
[dragonfly.git] / usr.sbin / powerd / powerd.c
1 /*
2  * Copyright (c) 2010 The DragonFly Project.  All rights reserved.
3  *
4  * This code is derived from software contributed to The DragonFly Project
5  * by Matthew Dillon <dillon@backplane.com>
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in
15  *    the documentation and/or other materials provided with the
16  *    distribution.
17  * 3. Neither the name of The DragonFly Project nor the names of its
18  *    contributors may be used to endorse or promote products derived
19  *    from this software without specific, prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
25  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34
35 /*
36  * The powerd daemon monitors the cpu load and adjusts cpu frequencies
37  * via hw.acpi.cpu.px_dom*.
38  */
39
40 #include <sys/types.h>
41 #include <sys/sysctl.h>
42 #include <sys/kinfo.h>
43 #include <sys/file.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <unistd.h>
47 #include <string.h>
48 #include <syslog.h>
49
50 static void usage(void);
51 static double getcputime(void);
52 static void acpi_setcpufreq(int nstate);
53 static void setupdominfo(void);
54
55 int DebugOpt;
56 int CpuLimit;           /* # of cpus at max frequency */
57 int DomLimit;           /* # of domains at max frequency */
58 int PowerFd;
59 int DomBeg;
60 int DomEnd;
61 int NCpus;
62 int CpuCount[256];      /* # of cpus in any given domain */
63 int CpuToDom[256];      /* domain a particular cpu belongs to */
64 double Trigger = 0.25;  /* load per cpu to force max freq */
65
66 int
67 main(int ac, char **av)
68 {
69         double qavg;
70         double savg;
71         int ch;
72         int nstate;
73         char buf[64];
74
75         while ((ch = getopt(ac, av, "d")) != -1) {
76                 switch(ch) {
77                 case 'd':
78                         DebugOpt = 1;
79                         break;
80                 default:
81                         usage();
82                         /* NOT REACHED */
83                 }
84         }
85         ac -= optind;
86         av += optind;
87
88         /*
89          * Prime delta cputime calculation, make sure at least dom0 exists,
90          * and make sure powerd is not already running.
91          */
92         getcputime();
93         savg = 0.0;
94
95         setupdominfo();
96         if (DomBeg >= DomEnd) {
97                 fprintf(stderr, "hw.acpi.cpu.px_dom* sysctl not available\n");
98                 exit(1);
99         }
100         DomLimit = DomEnd;
101         CpuLimit = NCpus;
102
103         PowerFd = open("/var/run/powerd.pid", O_CREAT|O_RDWR, 0644);
104         if (PowerFd < 0) {
105                 fprintf(stderr,
106                         "Cannot create /var/run/powerd.pid, "
107                         "continuing anyway\n");
108         } else {
109                 if (flock(PowerFd, LOCK_EX|LOCK_NB) < 0) {
110                         fprintf(stderr, "powerd is already running\n");
111                         exit(1);
112                 }
113         }
114
115         /*
116          * Demonize and set pid
117          */
118         if (DebugOpt == 0) {
119                 daemon(0, 0);
120                 openlog("powerd", LOG_CONS | LOG_PID, LOG_DAEMON);
121         }
122
123         if (PowerFd >= 0) {
124                 ftruncate(PowerFd, 0);
125                 snprintf(buf, sizeof(buf), "%d\n", (int)getpid());
126                 write(PowerFd, buf, strlen(buf));
127         }
128
129         /*
130          * Monitoring loop
131          *
132          * Calculate nstate, the number of cpus we wish to run at max
133          * frequency.  All remaining cpus will be set to their lowest
134          * frequency and mapped out of the user process scheduler.
135          */
136         for (;;) {
137                 qavg = getcputime();
138                 savg = (savg * 7.0 + qavg) / 8.0;
139
140                 nstate = savg / Trigger;
141                 if (nstate > NCpus)
142                         nstate = NCpus;
143                 if (DebugOpt) {
144                         printf("\rqavg=%5.2f savg=%5.2f %2d/%2d ncpus=%d\r",
145                                 qavg, savg, CpuLimit, DomLimit, nstate);
146                         fflush(stdout);
147                 }
148                 if (nstate != CpuLimit)
149                         acpi_setcpufreq(nstate);
150                 sleep(1);
151         }
152 }
153
154 /*
155  * Figure out the domains and calculate the CpuCount[] and CpuToDom[]
156  * arrays.
157  */
158 static
159 void
160 setupdominfo(void)
161 {
162         char buf[64];
163         char members[1024];
164         char *str;
165         size_t msize;
166         int i;
167         int n;
168
169         for (i = 0; i < 256; ++i) {
170                 snprintf(buf, sizeof(buf),
171                          "hw.acpi.cpu.px_dom%d.available", i);
172                 if (sysctlbyname(buf, NULL, NULL, NULL, 0) >= 0)
173                         break;
174         }
175         DomBeg = i;
176
177         for (i = 255; i >= DomBeg; --i) {
178                 snprintf(buf, sizeof(buf),
179                          "hw.acpi.cpu.px_dom%d.available", i);
180                 if (sysctlbyname(buf, NULL, NULL, NULL, 0) >= 0) {
181                         ++i;
182                         break;
183                 }
184         }
185         DomEnd = i;
186
187         for (i = DomBeg; i < DomEnd; ++i) {
188                 snprintf(buf, sizeof(buf),
189                          "hw.acpi.cpu.px_dom%d.members", i);
190                 msize = sizeof(members);
191                 if (sysctlbyname(buf, members, &msize, NULL, 0) == 0) {
192                         members[msize] = 0;
193                         for (str = strtok(members, " "); str;
194                              str = strtok(NULL, " ")) {
195                                 n = -1;
196                                 sscanf(str, "cpu%d", &n);
197                                 if (n >= 0) {
198                                         ++NCpus;
199                                         ++CpuCount[i];
200                                         CpuToDom[n]= i;
201                                 }
202                         }
203                 }
204         }
205 }
206
207 /*
208  * Return the one-second cpu load.  One cpu at 100% will return a value
209  * of 1.0.  On a SMP system N cpus running at 100% will return a value of N.
210  */
211 static
212 double
213 getcputime(void)
214 {
215         static struct kinfo_cputime ocpu_time[64];
216         static struct kinfo_cputime ncpu_time[64];
217         size_t slen;
218         int ncpu;
219         int cpu;
220         uint64_t delta;
221
222         bcopy(ncpu_time, ocpu_time, sizeof(ncpu_time));
223         slen = sizeof(ncpu_time);
224         if (sysctlbyname("kern.cputime", &ncpu_time, &slen, NULL, 0) < 0) {
225                 fprintf(stderr, "kern.cputime sysctl not available\n");
226                 exit(1);
227         }
228         ncpu = slen / sizeof(ncpu_time[0]);
229         delta = 0;
230
231         for (cpu = 0; cpu < ncpu; ++cpu) {
232                 delta += (ncpu_time[cpu].cp_user + ncpu_time[cpu].cp_sys +
233                           ncpu_time[cpu].cp_intr) -
234                          (ocpu_time[cpu].cp_user + ocpu_time[cpu].cp_sys +
235                           ocpu_time[cpu].cp_intr);
236         }
237         return((double)delta / 1000000.0);
238 }
239
240 /*
241  * nstate is the requested number of cpus that we wish to run at full
242  * frequency.  We calculate how many domains we have to adjust to reach
243  * this goal.
244  *
245  * This function also sets the user scheduler global cpu mask.
246  */
247 static
248 void
249 acpi_setcpufreq(int nstate)
250 {
251         int ncpus = 0;
252         int increasing = (nstate > CpuLimit);
253         int dom;
254         int domBeg;
255         int domEnd;
256         int lowest;
257         int highest;
258         int desired;
259         int v;
260         char *sysid;
261         char *ptr;
262         char buf[256];
263         size_t buflen;
264         cpumask_t global_cpumask;
265
266         /*
267          * Calculate the ending domain if the number of operating cpus
268          * has increased.
269          *
270          * Calculate the starting domain if the number of operating cpus
271          * has decreased.
272          */
273         for (dom = DomBeg; dom < DomEnd; ++dom) {
274                 if (ncpus >= nstate)
275                         break;
276                 ncpus += CpuCount[dom];
277         }
278
279         syslog(LOG_INFO, "using %d cpus", nstate);
280
281         /*
282          * Set the mask of cpus the userland scheduler is allowed to use.
283          */
284         global_cpumask = (1L << nstate) - 1;
285         sysctlbyname("kern.usched_global_cpumask", NULL, 0,
286                      &global_cpumask, sizeof(global_cpumask));
287
288         if (increasing) {
289                 domBeg = DomLimit;
290                 domEnd = dom;
291         } else {
292                 domBeg = dom;
293                 domEnd = DomLimit;
294         }
295         DomLimit = dom;
296         CpuLimit = nstate;
297
298         /*
299          * Adjust the cpu frequency
300          */
301         if (DebugOpt)
302                 printf("\n");
303         for (dom = domBeg; dom < domEnd; ++dom) {
304                 /*
305                  * Retrieve availability list
306                  */
307                 asprintf(&sysid, "hw.acpi.cpu.px_dom%d.available", dom);
308                 buflen = sizeof(buf) - 1;
309                 v = sysctlbyname(sysid, buf, &buflen, NULL, 0);
310                 free(sysid);
311                 if (v < 0)
312                         continue;
313                 buf[buflen] = 0;
314
315                 /*
316                  * Parse out the highest and lowest cpu frequencies
317                  */
318                 ptr = buf;
319                 highest = lowest = 0;
320                 while (ptr && (v = strtol(ptr, &ptr, 10)) > 0) {
321                         if (lowest == 0 || lowest > v)
322                                 lowest = v;
323                         if (highest == 0 || highest < v)
324                                 highest = v;
325                 }
326
327                 /*
328                  * Calculate the desired cpu frequency, test, and set.
329                  */
330                 desired = increasing ? highest : lowest;
331
332                 asprintf(&sysid, "hw.acpi.cpu.px_dom%d.select", dom);
333                 buflen = sizeof(v);
334                 v = 0;
335                 sysctlbyname(sysid, &v, &buflen, NULL, 0);
336                 {
337                         if (DebugOpt) {
338                                 printf("dom%d set frequency %d\n",
339                                        dom, desired);
340                         }
341                         sysctlbyname(sysid, NULL, NULL,
342                                      &desired, sizeof(desired));
343                 }
344                 free(sysid);
345         }
346 }
347
348 static
349 void
350 usage(void)
351 {
352         fprintf(stderr, "usage: powerd [-d]\n");
353         exit(1);
354 }