summaryrefslogtreecommitdiffstats
path: root/lib/clplumbing/cpulimits.c
blob: 4c03f236389f78859bd67101bed443dd9ea8913b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
/*
 * Functions to put dynamic limits on CPU consumption.
 *
 * Copyright (C) 2003 IBM Corporation
 *
 * Author:	<alanr@unix.sh>
 *
 * This software licensed under the GNU LGPL.
 *
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of version 2.1 of the GNU Lesser General Public
 * License as published by the Free Software Foundation.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 **************************************************************************
 *
 * This allows us to better catch runaway realtime processes that
 * might otherwise hang the whole system (if they're POSIX realtime
 * processes).
 *
 * We do this by getting a "lease" on CPU time, and then extending
 * the lease every so often as real time elapses.  Since we only
 * extend the lease by a bounded amount computed on the basis of an
 * upper bound of how much CPU the code is "expected" to consume during
 * the lease interval, this means that if we go into an infinite
 * loop, it is highly probable that this will be detected and our
 * process will be terminated by the operating system with a SIGXCPU.
 *
 * If you want to handle this signal, then fine... Do so...
 *
 * If not, the default is to terminate the process and produce a core
 * dump.  This is a great default for debugging...
 *
 *
 * The process is basically this:
 *  - Set the CPU percentage limit with cl_cpu_limit_setpercent()
 *	according to what you expect the CPU percentage to top out at
 *	measured over an interval at >= 60 seconds
 *  - Call cl_cpu_limit_ms_interval() to figure out how often to update
 *	the CPU limit (it returns milliseconds)
 *  - At least as often as indicated above, call cl_cpu_limit_update()
 *	to update our current CPU limit.
 *
 * These limits are approximate, so be a little conservative.
 * If you've gone into an infinite loop, it'll likely get caught ;-)
 *
 * As of this writing, this code will never set the soft CPU limit less
 * than four seconds, or greater than 60 seconds.
 *
 */
#include <lha_internal.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <clplumbing/longclock.h>
#include <unistd.h>
#include <clplumbing/cpulimits.h>
#include <clplumbing/cl_log.h>

static longclock_t	nexttimetoupdate;

/* How long between checking out CPU usage? */
static int		cpuinterval_ms = 0;

/* How much cpu (in seconds) allowed at each check interval? */
static int		cpusecs;

#define	ROUND(foo)	((int)((foo)+0.5))


/*
 * Update our current CPU limit (via setrlimit) according to our
 * current resource consumption, and our current cpu % limit
 *
 * We only set the soft CPU limit, and do not change the maximum
 * (hard) CPU limit, but we respect it if it's already set.
 *
 * As a result, this code can be used by privileged and non-privileged
 * processes.
 */

static int
update_cpu_interval(void)
{
	struct rusage	ru;
	struct rlimit	rlim;
	unsigned long	timesecs;
	unsigned long	microsec;

	/* Compute how much CPU we've used so far... */

	getrusage(RUSAGE_SELF, &ru);
	timesecs  = ru.ru_utime.tv_sec  + ru.ru_stime.tv_sec;
	microsec  = ru.ru_utime.tv_usec + ru.ru_stime.tv_usec;

	/* Round up to the next higher second */
	if (microsec > 1000000) {
		timesecs += 2;
	}else{
		timesecs += 1;
	}

	/* Compute our next CPU limit */
	timesecs += cpusecs;

	/* Figure out when we next need to update our CPU limit */
	nexttimetoupdate = add_longclock(time_longclock()
	,	msto_longclock(cpuinterval_ms));

	getrlimit(RLIMIT_CPU, &rlim);

	/* Make sure we don't exceed the hard CPU limit (if set) */
	if (rlim.rlim_max != RLIM_INFINITY && timesecs > rlim.rlim_max) {
		timesecs = rlim.rlim_max;
	}
#if 0
	cl_log(LOG_DEBUG
	,	"Setting max CPU limit to %ld seconds", timesecs);
#endif

	/* Update the OS-level soft CPU limit */
	rlim.rlim_cur = timesecs;
	return setrlimit(RLIMIT_CPU, &rlim);
}

#define	MININTERVAL	60 /* seconds */

int
cl_cpu_limit_setpercent(int ipercent)
{
	float	percent;
	int	interval;

	if (ipercent > 99) {
		ipercent = 99;
	}
	if (ipercent < 1) {
		ipercent = 1;
	}
	percent = ipercent;
	percent /= (float)100;

	interval= MININTERVAL;

	/*
	 * Compute how much CPU we will allow to be used
	 * for each check interval.
	 *
	 * Rules:
	 *  - we won't require checking more often than
	 *    every 60 seconds
	 *  - we won't limit ourselves to less than
	 *	4 seconds of CPU per checking interval
	 */
	for (;;) {
		cpusecs = ROUND((float)interval*percent);
		if (cpusecs >= 4) {
			break;
		}
		interval *= 2;
	}

	/*
	 * Now compute how long to go between updates to our CPU limit
	 * from the perspective of the OS (via setrlimit(2)).
	 *
	 * We do the computation this way because the CPU limit
	 * can only be set to the nearest second, but timers can
	 * generally be set more accurately.
	 */
	cpuinterval_ms = (int)(((float)cpusecs / percent)*1000.0);

	cl_log(LOG_DEBUG
	,	"Limiting CPU: %d CPU seconds every %d milliseconds"
	,	cpusecs, cpuinterval_ms);

	return update_cpu_interval();
}

int
cl_cpu_limit_ms_interval(void)
{
	return	cpuinterval_ms;
}

int
cl_cpu_limit_update(void)
{
	longclock_t	now = time_longclock();
	long		msleft;

	if (cpuinterval_ms <= 0) {
		return 0;
	}
	if (cmp_longclock(now, nexttimetoupdate) > 0) {
		return update_cpu_interval();
	}
	msleft = longclockto_ms(sub_longclock(nexttimetoupdate, now));
	if (msleft < 500) {
		return update_cpu_interval();
	}
	return 0;
}
int
cl_cpu_limit_disable(void)
{
	struct rlimit	rlim;
	getrlimit(RLIMIT_CPU, &rlim);
	rlim.rlim_cur = rlim.rlim_max;
	return setrlimit(RLIMIT_CPU, &rlim);
}