Index: trunk/tools/slayerd/slayerd.conf.example |
— | — | @@ -0,0 +1,23 @@ |
| 2 | +# Example configuration file for slayerd(8). This file is read |
| 3 | +# before the command line options. |
| 4 | + |
| 5 | +# Maximum per-user memory limit; processes will be killed if the |
| 6 | +# user's memory use exceeds this limit. Given in MB. |
| 7 | +limit 500 |
| 8 | + |
| 9 | +# Kill threshold. Processes will be killed until the user's memory |
| 10 | +# use drops below this number, also in MB. |
| 11 | +thresh 300 |
| 12 | + |
| 13 | +# Time to wait between checks, in seconds. |
| 14 | +delay 10 |
| 15 | + |
| 16 | +# Ignore certain users. root (UID 0) is always exempt. |
| 17 | +#exempt adminuser |
| 18 | +#exempt gooduser |
| 19 | + |
| 20 | +# Command to send mail. |
| 21 | +sendmail /usr/lib/sendmail -oi -bm -- |
| 22 | + |
| 23 | +# File to store PID in. |
| 24 | +#pidfile /var/run/slayerd.pid |
Index: trunk/tools/slayerd/debian/slayerd.substvars |
— | — | @@ -0,0 +1 @@ |
| 2 | +shlibs:Depends=libboost-filesystem1.33.1, libc6 (>= 2.3.5-1), libgcc1 (>= 1:4.1.1-12), libstdc++6 (>= 4.1.1-12) |
Index: trunk/tools/slayerd/debian/dirs |
— | — | @@ -0,0 +1 @@ |
| 2 | +usr/sbin |
Index: trunk/tools/slayerd/debian/files |
— | — | @@ -0,0 +1 @@ |
| 2 | +slayerd_1.0-1_amd64.deb admin extra |
Index: trunk/tools/slayerd/debian/copyright |
— | — | @@ -0,0 +1,10 @@ |
| 2 | +This package was debianized by River Tarnell <river@attenuate.org> on |
| 3 | +Thu, 19 Apr 2007 21:55:19 +0000. |
| 4 | + |
| 5 | +Copyright: 2007, River Tarnell. |
| 6 | + |
| 7 | +License: |
| 8 | + Permission is granted to anyone to use this software for any purpose, |
| 9 | + including commercial applications, and to alter it and redistribute it |
| 10 | + freely. This software is provided 'as-is', without any express or implied |
| 11 | + warranty. |
Index: trunk/tools/slayerd/debian/slayerd.postinst.debhelper |
— | — | @@ -0,0 +1,10 @@ |
| 2 | +# Automatically added by dh_installinit |
| 3 | +if [ -x "/etc/init.d/slayerd" ]; then |
| 4 | + update-rc.d slayerd defaults >/dev/null |
| 5 | + if [ -x "`which invoke-rc.d 2>/dev/null`" ]; then |
| 6 | + invoke-rc.d slayerd start || exit $? |
| 7 | + else |
| 8 | + /etc/init.d/slayerd start || exit $? |
| 9 | + fi |
| 10 | +fi |
| 11 | +# End automatically added section |
Index: trunk/tools/slayerd/debian/slayerd.postrm.debhelper |
— | — | @@ -0,0 +1,5 @@ |
| 2 | +# Automatically added by dh_installinit |
| 3 | +if [ "$1" = "purge" ] ; then |
| 4 | + update-rc.d slayerd remove >/dev/null || exit $? |
| 5 | +fi |
| 6 | +# End automatically added section |
Index: trunk/tools/slayerd/debian/slayerd.prerm.debhelper |
— | — | @@ -0,0 +1,9 @@ |
| 2 | +# Automatically added by dh_installinit |
| 3 | +if [ -x "/etc/init.d/slayerd" ]; then |
| 4 | + if [ -x "`which invoke-rc.d 2>/dev/null`" ]; then |
| 5 | + invoke-rc.d slayerd stop || exit $? |
| 6 | + else |
| 7 | + /etc/init.d/slayerd stop || exit $? |
| 8 | + fi |
| 9 | +fi |
| 10 | +# End automatically added section |
Index: trunk/tools/slayerd/debian/control |
— | — | @@ -0,0 +1,14 @@ |
| 2 | +Source: slayerd |
| 3 | +Section: admin |
| 4 | +Priority: extra |
| 5 | +Maintainer: River Tarnell <river@attenuate.org> |
| 6 | +Build-Depends: debhelper (>= 5) |
| 7 | +Standards-Version: 3.7.2 |
| 8 | + |
| 9 | +Package: slayerd |
| 10 | +Architecture: any |
| 11 | +Depends: ${shlibs:Depends}, ${misc:Depends} |
| 12 | +Description: Kill errant user processes |
| 13 | + slayerd monitors the system for users using too much memory. |
| 14 | + If any are found, their processes are trimmed to within acceptable |
| 15 | + limits. |
Index: trunk/tools/slayerd/debian/compat |
— | — | @@ -0,0 +1 @@ |
| 2 | +5 |
Index: trunk/tools/slayerd/debian/postinst |
— | — | @@ -0,0 +1,6 @@ |
| 2 | +#! /bin/sh |
| 3 | + |
| 4 | +set -e |
| 5 | + |
| 6 | +#DEBHELPER# |
| 7 | + |
Property changes on: trunk/tools/slayerd/debian/postinst |
___________________________________________________________________ |
Added: svn:executable |
1 | 8 | + * |
Index: trunk/tools/slayerd/debian/default |
— | — | @@ -0,0 +1,10 @@ |
| 2 | +# Defaults for slayerd initscript |
| 3 | +# sourced by /etc/init.d/slayerd |
| 4 | +# installed at /etc/default/slayerd by the maintainer scripts |
| 5 | + |
| 6 | +# |
| 7 | +# This is a POSIX shell fragment |
| 8 | +# |
| 9 | + |
| 10 | +# Additional options that are passed to the daemon. |
| 11 | +DAEMON_OPTS="" |
Index: trunk/tools/slayerd/debian/postrm |
— | — | @@ -0,0 +1,6 @@ |
| 2 | +#! /bin/sh |
| 3 | + |
| 4 | +set -e |
| 5 | + |
| 6 | +#DEBHELPER# |
| 7 | + |
Property changes on: trunk/tools/slayerd/debian/postrm |
___________________________________________________________________ |
Added: svn:executable |
1 | 8 | + * |
Index: trunk/tools/slayerd/debian/preinst |
— | — | @@ -0,0 +1,6 @@ |
| 2 | +#! /bin/sh |
| 3 | + |
| 4 | +set -e |
| 5 | + |
| 6 | +#DEBHELPER# |
| 7 | + |
Property changes on: trunk/tools/slayerd/debian/preinst |
___________________________________________________________________ |
Added: svn:executable |
1 | 8 | + * |
Index: trunk/tools/slayerd/debian/init.d |
— | — | @@ -0,0 +1,67 @@ |
| 2 | +#! /bin/sh |
| 3 | +# |
| 4 | +# Written by Miquel van Smoorenburg <miquels@cistron.nl>. |
| 5 | +# Modified for Debian |
| 6 | +# by Ian Murdock <imurdock@gnu.ai.mit.edu>. |
| 7 | +# |
| 8 | +# Version: @(#)skeleton 1.9 26-Feb-2001 miquels@cistron.nl |
| 9 | +# |
| 10 | + |
| 11 | +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin |
| 12 | +DAEMON=/usr/sbin/slayerd |
| 13 | +NAME=slayerd |
| 14 | +DESC="process slayed (slayerd)" |
| 15 | + |
| 16 | +test -x $DAEMON || exit 0 |
| 17 | + |
| 18 | +# Include defaults if available |
| 19 | +if [ -f /etc/default/slayerd ] ; then |
| 20 | + . /etc/default/slayerd |
| 21 | +fi |
| 22 | + |
| 23 | +set -e |
| 24 | + |
| 25 | +[ -f /etc/slayerd/slayerd.conf ] || exit 0 |
| 26 | + |
| 27 | +case "$1" in |
| 28 | + start) |
| 29 | + echo -n "Starting $DESC: " |
| 30 | + start-stop-daemon --start --quiet --pidfile /var/run/$NAME.pid \ |
| 31 | + --exec $DAEMON -- $DAEMON_OPTS |
| 32 | + echo "$NAME." |
| 33 | + ;; |
| 34 | + stop) |
| 35 | + echo -n "Stopping $DESC: " |
| 36 | + start-stop-daemon --oknodo --stop --quiet --pidfile /var/run/$NAME.pid \ |
| 37 | + --exec $DAEMON |
| 38 | + echo "$NAME." |
| 39 | + ;; |
| 40 | + force-reload) |
| 41 | + # |
| 42 | + # If the "reload" option is implemented, move the "force-reload" |
| 43 | + # option to the "reload" entry above. If not, "force-reload" is |
| 44 | + # just the same as "restart" except that it does nothing if the |
| 45 | + # daemon isn't already running. |
| 46 | + # check wether $DAEMON is running. If so, restart |
| 47 | + start-stop-daemon --stop --test --quiet --pidfile \ |
| 48 | + /var/run/$NAME.pid --exec $DAEMON \ |
| 49 | + && $0 restart \ |
| 50 | + || exit 0 |
| 51 | + ;; |
| 52 | + restart) |
| 53 | + echo -n "Restarting $DESC: " |
| 54 | + start-stop-daemon --stop --quiet --pidfile \ |
| 55 | + /var/run/$NAME.pid --exec $DAEMON |
| 56 | + sleep 1 |
| 57 | + start-stop-daemon --start --quiet --pidfile \ |
| 58 | + /var/run/$NAME.pid --exec $DAEMON -- $DAEMON_OPTS |
| 59 | + echo "$NAME." |
| 60 | + ;; |
| 61 | + *) |
| 62 | + N=/etc/init.d/$NAME |
| 63 | + echo "Usage: $N {start|stop|restart|force-reload}" >&2 |
| 64 | + exit 1 |
| 65 | + ;; |
| 66 | +esac |
| 67 | + |
| 68 | +exit 0 |
Index: trunk/tools/slayerd/debian/prerm |
— | — | @@ -0,0 +1,6 @@ |
| 2 | +#! /bin/sh |
| 3 | + |
| 4 | +set -e |
| 5 | + |
| 6 | +#DEBHELPER# |
| 7 | + |
Property changes on: trunk/tools/slayerd/debian/prerm |
___________________________________________________________________ |
Added: svn:executable |
1 | 8 | + * |
Index: trunk/tools/slayerd/debian/changelog |
— | — | @@ -0,0 +1,6 @@ |
| 2 | +slayerd (1.0-1) unstable; urgency=low |
| 3 | + |
| 4 | + * Initial release |
| 5 | + |
| 6 | + -- River Tarnell <river@attenuate.org> Thu, 19 Apr 2007 21:55:19 +0000 |
| 7 | + |
Index: trunk/tools/slayerd/debian/docs |
Index: trunk/tools/slayerd/debian/rules |
— | — | @@ -0,0 +1,60 @@ |
| 2 | +#!/usr/bin/make -f |
| 3 | + |
| 4 | +CFLAGS = -Wall -g |
| 5 | + |
| 6 | +ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS))) |
| 7 | + CFLAGS += -O0 |
| 8 | +else |
| 9 | + CFLAGS += -O2 |
| 10 | +endif |
| 11 | + |
| 12 | +configure: configure-stamp |
| 13 | +configure-stamp: |
| 14 | + dh_testdir |
| 15 | + touch configure-stamp |
| 16 | + |
| 17 | + |
| 18 | +build: build-stamp |
| 19 | + |
| 20 | +build-stamp: configure-stamp |
| 21 | + dh_testdir |
| 22 | + $(MAKE) PREFIX=/usr |
| 23 | + touch $@ |
| 24 | + |
| 25 | +clean: |
| 26 | + dh_testdir |
| 27 | + dh_testroot |
| 28 | + rm -f build-stamp configure-stamp |
| 29 | + -$(MAKE) clean |
| 30 | + dh_clean |
| 31 | + |
| 32 | +install: build |
| 33 | + dh_testdir |
| 34 | + dh_testroot |
| 35 | + dh_clean -k |
| 36 | + dh_installdirs |
| 37 | + |
| 38 | + $(MAKE) DESTDIR=$(CURDIR)/debian/slayerd PREFIX=/usr install |
| 39 | + |
| 40 | +binary-indep: build install |
| 41 | + |
| 42 | +binary-arch: build install |
| 43 | + dh_testdir |
| 44 | + dh_testroot |
| 45 | + dh_installchangelogs |
| 46 | + dh_installdocs |
| 47 | + dh_installexamples |
| 48 | + dh_installinit |
| 49 | + dh_installman |
| 50 | + dh_link |
| 51 | + dh_strip |
| 52 | + dh_compress |
| 53 | + dh_fixperms |
| 54 | + dh_installdeb |
| 55 | + dh_shlibdeps |
| 56 | + dh_gencontrol |
| 57 | + dh_md5sums |
| 58 | + dh_builddeb |
| 59 | + |
| 60 | +binary: binary-indep binary-arch |
| 61 | +.PHONY: build clean binary-indep binary-arch binary install configure |
Property changes on: trunk/tools/slayerd/debian/rules |
___________________________________________________________________ |
Added: svn:executable |
1 | 62 | + * |
Index: trunk/tools/slayerd/mailmessage |
— | — | @@ -0,0 +1,27 @@ |
| 2 | +From: slayerd <slayerd@%hostname%> |
| 3 | +To: %user% <%user%@%hostname%> |
| 4 | +Subject: Excessive memory usage from your processes. |
| 5 | +Reply-To: root <root@%hostname%> |
| 6 | + |
| 7 | +This message was automatically generated by slayerd on %hostname%. |
| 8 | + |
| 9 | +Hello, |
| 10 | + |
| 11 | +One or more of your processes on the host %hostname% |
| 12 | +were exceeding the configured memory limit, which is %limit% megabytes. |
| 13 | +I have killed enough of your processes to bring your usage back to the |
| 14 | +threshold limit, which is %thresh% megabytes. |
| 15 | + |
| 16 | +These are the processes I killed: |
| 17 | + |
| 18 | +%processes% |
| 19 | + |
| 20 | +Your total memory usage is now %current% megabyte(s). |
| 21 | + |
| 22 | +Excessive memory usage is usually a symptom of a broken program. Please |
| 23 | +investigate the cause of the problem and fix it before you restart these |
| 24 | +processes. |
| 25 | + |
| 26 | +Regards, |
| 27 | + slayerd (the process slayer) |
| 28 | + |
Index: trunk/tools/slayerd/slayerd.cc |
— | — | @@ -39,9 +39,28 @@ |
40 | 40 | |
41 | 41 | namespace { |
42 | 42 | std::string PATH_PROC = "/proc"; |
43 | | - std::string SENDMAIL = "/usr/lib/sendmail"; |
| 43 | + std::string CONFFILE = "/etc/slayerd/slayerd.conf"; |
44 | 44 | } |
45 | 45 | |
| 46 | +struct config_t { |
| 47 | + std::size_t limit; |
| 48 | + std::size_t thresh; |
| 49 | + int delay; |
| 50 | + std::string mailmessage; |
| 51 | + std::string sendmail; |
| 52 | + std::string pidfile; |
| 53 | + std::set<uid_t> exempt; |
| 54 | + |
| 55 | + config_t() |
| 56 | + : limit(0) |
| 57 | + , thresh(0) |
| 58 | + , delay(60) |
| 59 | + , mailmessage(ETCDIR "/mailmessage") |
| 60 | + , sendmail("/usr/lib/sendmail -oi -bm --") |
| 61 | + , pidfile("/var/run/slayerd.pid") |
| 62 | + {} |
| 63 | +} config; |
| 64 | + |
46 | 65 | struct process { |
47 | 66 | process(fs::path const &pth); |
48 | 67 | |
— | — | @@ -188,14 +207,13 @@ |
189 | 208 | |
190 | 209 | void |
191 | 210 | version(void) { |
192 | | - std::cerr << "slayerd $Revision$\n"; |
| 211 | + std::cerr << "slayerd 1.0\n"; |
193 | 212 | std::cerr << "Copyright (C) 2007, River Tarnell <river@attenuate.org>.\n"; |
194 | 213 | } |
195 | 214 | |
196 | 215 | void |
197 | 216 | usage(void) { |
198 | | - std::cerr << |
199 | | -"usage: slayerd [-vh] -l <limit> -t <thread> [-e <user>]\n" |
| 217 | + std::cerr << "usage: slayerd [-fvh] [-c <conf>]\n" |
200 | 218 | ; |
201 | 219 | } |
202 | 220 | |
— | — | @@ -208,10 +226,10 @@ |
209 | 227 | void |
210 | 228 | sendmail(std::string const &username, std::string const &message) |
211 | 229 | { |
212 | | - std::string cmd = str(boost::format("%s -oi -bm -- %s") % SENDMAIL % username); |
| 230 | + std::string cmd = str(boost::format("%s %s") % config.sendmail % username); |
213 | 231 | FILE *p = popen(cmd.c_str(), "w"); |
214 | 232 | if (p == 0) { |
215 | | - log(str(boost::format("cannot send mail using %s: %s") % SENDMAIL % std::strerror(errno))); |
| 233 | + log(str(boost::format("cannot send mail using %s: %s") % config.sendmail % std::strerror(errno))); |
216 | 234 | return; |
217 | 235 | } |
218 | 236 | |
— | — | @@ -219,55 +237,210 @@ |
220 | 238 | pclose(p); |
221 | 239 | } |
222 | 240 | |
| 241 | +std::string |
| 242 | +replace_file(std::string const &filename, std::map<std::string, std::string> const &vars) |
| 243 | +{ |
| 244 | + std::string file; |
| 245 | + std::string result; |
| 246 | + |
| 247 | + std::ifstream f(filename.c_str()); |
| 248 | + if (!f) { |
| 249 | + log(str(boost::format("cannot open %s: %s") % filename % std::strerror(errno))); |
| 250 | + return ""; |
| 251 | + } |
| 252 | + |
| 253 | + std::string line; |
| 254 | + while (std::getline(f, line)) { |
| 255 | + std::string::size_type i, j; |
| 256 | + while ((i = line.find('%')) != std::string::npos) { |
| 257 | + if ((j = line.find('%', i + 1)) == std::string::npos) { |
| 258 | + result += line; |
| 259 | + line = ""; |
| 260 | + break; |
| 261 | + } |
| 262 | + |
| 263 | + result += line.substr(0, i); |
| 264 | + std::string var = line.substr(i + 1, j - i - 1); |
| 265 | + std::map<std::string, std::string>::const_iterator it = vars.find(var); |
| 266 | + |
| 267 | + if (it == vars.end()) { |
| 268 | + result += line.substr(i); |
| 269 | + line = ""; |
| 270 | + break; |
| 271 | + } |
| 272 | + |
| 273 | + result += it->second; |
| 274 | + line = line.substr(j + 1); |
| 275 | + } |
| 276 | + |
| 277 | + result += line; |
| 278 | + result += '\n'; |
| 279 | + } |
| 280 | + |
| 281 | + return result; |
| 282 | +} |
| 283 | + |
| 284 | +bool |
| 285 | +configure(void) |
| 286 | +{ |
| 287 | + std::ifstream conffile(CONFFILE.c_str()); |
| 288 | + if (!conffile) { |
| 289 | + std::string err = str(boost::format("cannot open %s: %s") % CONFFILE % std::strerror(errno)); |
| 290 | + std::cerr << err << '\n'; |
| 291 | + log(err); |
| 292 | + return false; |
| 293 | + } |
| 294 | + |
| 295 | + std::string line; |
| 296 | + int lineno = 0; |
| 297 | + while (std::getline(conffile, line)) { |
| 298 | + ++lineno; |
| 299 | + |
| 300 | + if (line[0] == '#') |
| 301 | + continue; |
| 302 | + if (line.empty()) |
| 303 | + continue; |
| 304 | + |
| 305 | + std::istringstream l(line); |
| 306 | + std::string d; |
| 307 | + if (!(l >> d)) |
| 308 | + continue; |
| 309 | + |
| 310 | + if (d == "limit") { |
| 311 | + if (!(l >> config.limit)) { |
| 312 | + std::string err = str(boost::format("\"%s\", line %d: invalid limit") % CONFFILE % lineno); |
| 313 | + std::cerr << err << '\n'; |
| 314 | + log(err); |
| 315 | + return false; |
| 316 | + } |
| 317 | + config.limit *= 1024 * 1024; |
| 318 | + } else if (d == "thresh") { |
| 319 | + if (!(l >> config.thresh)) { |
| 320 | + std::string err = str(boost::format("\"%s\", line %d: invalid threshold") % CONFFILE % lineno); |
| 321 | + std::cerr << err << '\n'; |
| 322 | + log(err); |
| 323 | + return false; |
| 324 | + } |
| 325 | + config.thresh *= 1024 * 1024; |
| 326 | + } else if (d == "exempt") { |
| 327 | + std::string username; |
| 328 | + if (!(l >> username)) { |
| 329 | + std::string err = str(boost::format("\"%s\", line %d: invalid username") % CONFFILE % lineno); |
| 330 | + std::cerr << err << '\n'; |
| 331 | + log(err); |
| 332 | + return false; |
| 333 | + } |
| 334 | + |
| 335 | + uid_t id = uid(username); |
| 336 | + if (id == -1) { |
| 337 | + std::string err = str(boost::format("\"%s\", line %d: user \"%s\" does not exist") |
| 338 | + % CONFFILE % lineno % username); |
| 339 | + std::cerr << err << '\n'; |
| 340 | + log(err); |
| 341 | + return false; |
| 342 | + } |
| 343 | + config.exempt.insert(id); |
| 344 | + } else if (d == "delay") { |
| 345 | + if (!(l >> config.delay)) { |
| 346 | + std::string err = str(boost::format("\"%s\", line %d: invalid delay") % CONFFILE % lineno); |
| 347 | + std::cerr << err << '\n'; |
| 348 | + log(err); |
| 349 | + return false; |
| 350 | + } |
| 351 | + } else if (d == "mailmessage") { |
| 352 | + if (!(l >> config.mailmessage)) { |
| 353 | + std::string err = str(boost::format("\"%s\", line %d: invalid mail message") % CONFFILE % lineno); |
| 354 | + std::cerr << err << '\n'; |
| 355 | + log(err); |
| 356 | + return false; |
| 357 | + } |
| 358 | + } else if (d == "sendmail") { |
| 359 | + if (!(l >> config.sendmail)) { |
| 360 | + std::string err = str(boost::format("\"%s\", line %d: invalid sendmail command") % CONFFILE % lineno); |
| 361 | + std::cerr << err << '\n'; |
| 362 | + log(err); |
| 363 | + return false; |
| 364 | + } |
| 365 | + } else if (d == "pidfile") { |
| 366 | + if (!(l >> config.pidfile)) { |
| 367 | + std::string err = str(boost::format("\"%s\", line %d: invalid pid file") % CONFFILE % lineno); |
| 368 | + std::cerr << err << '\n'; |
| 369 | + log(err); |
| 370 | + return false; |
| 371 | + } |
| 372 | + } else { |
| 373 | + std::string err = str(boost::format("\"%s\", line %d: invalid directive") % CONFFILE % lineno); |
| 374 | + std::cerr << err << '\n'; |
| 375 | + log(err); |
| 376 | + return false; |
| 377 | + } |
| 378 | + } |
| 379 | + |
| 380 | + return true; |
| 381 | +} |
| 382 | + |
| 383 | +void |
| 384 | +rmpidfile(void) |
| 385 | +{ |
| 386 | + unlink(config.pidfile.c_str()); |
| 387 | +} |
| 388 | + |
| 389 | +void |
| 390 | +do_signal(int sig) |
| 391 | +{ |
| 392 | + rmpidfile(); |
| 393 | + _exit(0); |
| 394 | +} |
| 395 | + |
| 396 | +bool |
| 397 | +do_pidfile(void) |
| 398 | +{ |
| 399 | + { |
| 400 | + std::ifstream pf(config.pidfile.c_str()); |
| 401 | + if (pf) { |
| 402 | + std::string pid; |
| 403 | + std::getline(pf, pid); |
| 404 | + std::cerr << boost::format("slayerd already running (pid %d) or stale pidfile %s\n") |
| 405 | + % pid % config.pidfile; |
| 406 | + return false; |
| 407 | + } |
| 408 | + } |
| 409 | + |
| 410 | + std::ofstream pf(config.pidfile.c_str()); |
| 411 | + if (!pf) { |
| 412 | + std::cerr << boost::format("cannot open pidfile %s: %s\n") |
| 413 | + % config.pidfile % std::strerror(errno); |
| 414 | + return false; |
| 415 | + } |
| 416 | + |
| 417 | + pf << getpid() << '\n'; |
| 418 | + atexit(rmpidfile); |
| 419 | + signal(SIGINT, do_signal); |
| 420 | + signal(SIGTERM, do_signal); |
| 421 | + return true; |
| 422 | +} |
| 423 | + |
223 | 424 | int |
224 | 425 | main(int argc, char **argv) |
225 | 426 | { |
226 | | - int delay = 10, pagesize = sysconf(_SC_PAGE_SIZE); |
227 | | - std::size_t limit = 0, thresh = 0; |
| 427 | + int pagesize = sysconf(_SC_PAGE_SIZE), fflag = 0; |
228 | 428 | int c; |
229 | | - std::set<uid_t> exempt; |
230 | 429 | |
| 430 | + config.exempt.insert(0); /* root is always exempt */ |
| 431 | + |
231 | 432 | char nodename[255]; |
232 | 433 | gethostname(nodename, sizeof nodename); |
233 | 434 | |
234 | | - while ((c = getopt(argc, argv, "l:t:e:vh")) != -1) { |
| 435 | + while ((c = getopt(argc, argv, "fvhc:")) != -1) { |
235 | 436 | switch (c) { |
236 | | - case 'l': |
237 | | - try { |
238 | | - limit = boost::lexical_cast<std::size_t>(optarg) * 1024 * 1024; |
239 | | - } catch (boost::bad_lexical_cast &) { |
240 | | - std::cerr << boost::format("\"%s\" is not a valid number\n") % optarg; |
241 | | - return 1; |
242 | | - } |
| 437 | + case 'f': |
| 438 | + fflag++; |
243 | 439 | break; |
244 | 440 | |
245 | | - case 't': |
246 | | - try { |
247 | | - thresh = boost::lexical_cast<std::size_t>(optarg) * 1024 * 1024; |
248 | | - } catch (boost::bad_lexical_cast &) { |
249 | | - std::cerr << boost::format("\"%s\" is not a valid number\n") % optarg; |
250 | | - return 1; |
251 | | - } |
| 441 | + case 'c': |
| 442 | + CONFFILE = optarg; |
252 | 443 | break; |
253 | 444 | |
254 | | - case 'd': |
255 | | - try { |
256 | | - delay = boost::lexical_cast<std::size_t>(optarg); |
257 | | - } catch (boost::bad_lexical_cast &) { |
258 | | - std::cerr << boost::format("\"%s\" is not a valid number\n") % optarg; |
259 | | - return 1; |
260 | | - } |
261 | | - break; |
262 | | - |
263 | | - case 'e': |
264 | | - uid_t u; |
265 | | - if ((u = uid(optarg)) == -1) { |
266 | | - std::cerr << boost::format("user \"%s\" does not exist\n") % optarg; |
267 | | - return 1; |
268 | | - } |
269 | | - exempt.insert(u); |
270 | | - break; |
271 | | - |
272 | 445 | case 'v': |
273 | 446 | version(); |
274 | 447 | return 0; |
— | — | @@ -286,23 +459,29 @@ |
287 | 460 | argc -= optind; |
288 | 461 | argv += optind; |
289 | 462 | |
290 | | - if (limit == 0 || thresh == 0) { |
291 | | - usage(); |
| 463 | + openlog("slayerd", LOG_PID, LOG_DAEMON); |
| 464 | + |
| 465 | + if (!configure()) |
292 | 466 | return 1; |
| 467 | + |
| 468 | + if (config.limit == 0 || config.thresh == 0) { |
| 469 | + std::cerr << "invalid limit/threshold\n"; |
| 470 | + return 1; |
293 | 471 | } |
294 | 472 | |
295 | | - openlog("slayerd", LOG_PID, LOG_DAEMON); |
296 | | - |
297 | | - if (daemon(0, 0) == -1) { |
| 473 | + if (!fflag && daemon(0, 0) == -1) { |
298 | 474 | std::cerr << boost::format("cannot daemonise: %s\n") % std::strerror(errno); |
299 | 475 | return 1; |
300 | 476 | } |
301 | 477 | |
| 478 | + if (!do_pidfile()) |
| 479 | + return 1; |
| 480 | + |
302 | 481 | if (mlockall(MCL_CURRENT | MCL_FUTURE) == -1) |
303 | 482 | log(str(boost::format("warning: cannot lock memory: %s\n") % std::strerror(errno))); |
304 | 483 | |
305 | 484 | log(str(boost::format("delay: %d, limit: %dM, threshold: %dM\n") |
306 | | - % delay % (limit / 1024 / 1024) % (thresh / 1024 / 1024))); |
| 485 | + % config.delay % (config.limit / 1024 / 1024) % (config.thresh / 1024 / 1024))); |
307 | 486 | |
308 | 487 | for (;;) { |
309 | 488 | fs::path proc(PATH_PROC); |
— | — | @@ -345,41 +524,23 @@ |
346 | 525 | user &u = users[i]; |
347 | 526 | std::size_t bytes = u.rss * pagesize; |
348 | 527 | |
349 | | - if (exempt.find(u.uid) != exempt.end()) |
| 528 | + if (config.exempt.find(u.uid) != config.exempt.end()) |
350 | 529 | continue; |
351 | 530 | |
352 | | - if (bytes < limit) |
| 531 | + if (bytes < config.limit) |
353 | 532 | continue; |
354 | 533 | |
355 | 534 | std::string uname = username(u.uid); |
356 | | - std::string message = str(boost::format( |
357 | | -"From: slayerd <slayerd@%1%>\n" |
358 | | -"To: %2% <%2%@%1%>\n" |
359 | | -"Subject: Excessive memory usage from your processes.\n" |
360 | | -"Reply-To: Wikimedia Toolserver Administrators <ts-admins@wikimedia.org>\n" |
361 | | -"X-Mailer: slayerd $Revision$\n" |
362 | | -"\n" |
363 | | -"This message was automatically generated by slayerd on %1%.\n" |
364 | | -"\n" |
365 | | -"Hello,\n" |
366 | | -"\n" |
367 | | -"One or more of your processes on the host %1%\n" |
368 | | -"were exceeding the configured memory limit, which is %3% megabytes.\n" |
369 | | -"I have killed enough of your processes to bring your usage back to the\n" |
370 | | -"threshold limit, which is %4% megabytes.\n" |
371 | | -"\n" |
372 | | -"These are the processes I killed:\n" |
373 | | -"\n" |
374 | | - ) % nodename % uname % (limit / 1024 / 1024) % (thresh / 1024 / 1024)); |
| 535 | + std::string process_list; |
375 | 536 | |
376 | 537 | log(str(boost::format("user \"%s\" is using %dM, over configured limit %dM") |
377 | 538 | % uname |
378 | 539 | % (bytes / 1024 / 1024) |
379 | | - % (limit / 1024 / 1024))); |
| 540 | + % (config.limit / 1024 / 1024))); |
380 | 541 | |
381 | 542 | std::sort(u.processes.begin(), u.processes.end(), field_comparator<process, long, &process::_rss>); |
382 | 543 | |
383 | | - while (bytes >= thresh && !u.processes.empty()) { |
| 544 | + while (bytes >= config.thresh && !u.processes.empty()) { |
384 | 545 | process &p = u.processes[0]; |
385 | 546 | std::string comm = p._comm.substr(1); |
386 | 547 | comm.resize(comm.size() - 1); |
— | — | @@ -391,7 +552,7 @@ |
392 | 553 | % (p._rss * pagesize / 1024 / 1024) |
393 | 554 | % ((bytes - p._rss * pagesize) / 1024 / 1024))); |
394 | 555 | |
395 | | - message += str(boost::format(" %s (pid %d), using %d megabyte(s)\n") |
| 556 | + process_list += str(boost::format(" %s (pid %d), using %d megabyte(s)\n") |
396 | 557 | % comm % p._pid % (p._rss * pagesize / 1024 / 1024)); |
397 | 558 | |
398 | 559 | bytes -= p._rss * pagesize; |
— | — | @@ -401,21 +562,19 @@ |
402 | 563 | log(str(boost::format(" usage is now within acceptable limits (%dM)") |
403 | 564 | % (bytes / 1024 / 1024))); |
404 | 565 | |
405 | | - message += str(boost::format( |
406 | | -"\n" |
407 | | -"Your total memory usage is now %d megabyte(s).\n" |
408 | | -"\n" |
409 | | -"Excessive memory usage is usually a symptom of a broken program. Please\n" |
410 | | -"investigate the cause of the problem and fix it before you restart these\n" |
411 | | -"processes.\n" |
412 | | -"\n" |
413 | | -"Regards,\n" |
414 | | -" slayerd (the process slayer)\n" |
415 | | - ) % (bytes / 1024 / 1024)); |
| 566 | + std::map<std::string, std::string> msgvars; |
| 567 | + msgvars["limit"] = boost::lexical_cast<std::string>(config.limit / 1024 / 1024); |
| 568 | + msgvars["thresh"] = boost::lexical_cast<std::string>(config.thresh / 1024 / 1024); |
| 569 | + msgvars["user"] = uname; |
| 570 | + msgvars["hostname"] = nodename; |
| 571 | + msgvars["current"] = boost::lexical_cast<std::string>(bytes / 1024 / 1024); |
| 572 | + msgvars["processes"] = process_list; |
416 | 573 | |
417 | | - sendmail(uname, message); |
| 574 | + std::string message = replace_file(config.mailmessage, msgvars); |
| 575 | + if (!message.empty()) |
| 576 | + sendmail(uname, message); |
418 | 577 | } |
419 | 578 | |
420 | | - sleep(delay); |
| 579 | + sleep(config.delay); |
421 | 580 | } |
422 | 581 | } |
Property changes on: trunk/tools/slayerd/slayerd.cc |
___________________________________________________________________ |
Added: svn:keywords |
423 | 582 | + Id Revision |
Index: trunk/tools/slayerd/Makefile |
— | — | @@ -1,5 +1,11 @@ |
| 2 | +PREFIX ?= /usr/local |
| 3 | +CONFDIR ?= /etc/slayerd |
| 4 | +SBINDIR ?= $(PREFIX)/sbin |
| 5 | +MANDIR ?= $(PREFIX)/share/man/man8 |
| 6 | + |
2 | 7 | CXX = g++ |
3 | 8 | CXXFLAGS = -O2 -g3 -ggdb |
| 9 | +CPPFLAGS = -DETCDIR=\"$(CONFDIR)\" -DPREFIX=\"$(PREFIX)\" |
4 | 10 | LDFLAGS = |
5 | 11 | SRCS = slayerd.cc |
6 | 12 | OBJS = $(SRCS:.cc=.o) |
— | — | @@ -8,9 +14,19 @@ |
9 | 15 | $(CXX) $(CXXFLAGS) $(LDFLAGS) $^ -o $@ -lboost_filesystem |
10 | 16 | |
11 | 17 | install: slayerd |
12 | | - install -c -s -o root -g root -m 755 slayerd /usr/local/sbin |
| 18 | + install -d $(DESTDIR)$(SBINDIR) |
| 19 | + install -d $(DESTDIR)$(CONFDIR) |
| 20 | + install -d $(DESTDIR)$(MANDIR) |
| 21 | + install -c -s -o root -g root -m 755 slayerd $(DESTDIR)$(SBINDIR) |
| 22 | + install -c -o root -g root -m 640 slayerd.conf.example $(DESTDIR)$(CONFDIR) |
| 23 | + install -c -o root -g root -m 640 mailmessage $(DESTDIR)$(CONFDIR) |
| 24 | + install -c -o root -g root -m 644 slayerd.8 $(DESTDIR)$(MANDIR) |
13 | 25 | |
14 | 26 | .cc.o: |
15 | 27 | $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< |
16 | 28 | |
| 29 | +clean: |
| 30 | + rm -f slayerd slayerd.o |
| 31 | + |
| 32 | +.PHONY: clean install |
17 | 33 | .SUFFICES: .cc .o |
Property changes on: trunk/tools/slayerd/Makefile |
___________________________________________________________________ |
Added: svn:keywords |
18 | 34 | + Id Revision |