You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
552 lines
12 KiB
552 lines
12 KiB
/*
|
|
Naughty applet - Runaway process monitor for the TDE panel
|
|
|
|
Copyright 2000 Rik Hemsley (rikkus) <rik@kde.org>
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program 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 General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
/* OpenBSD support by Jean-Yves Burlett <jean-yves@burlett.org> */
|
|
|
|
#if defined(__OpenBSD__) || defined(__NetBSD__)
|
|
#include <sys/param.h>
|
|
#include <sys/time.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/ucred.h>
|
|
#include <sys/sched.h>
|
|
#include <stdlib.h>
|
|
#endif
|
|
|
|
#ifdef __NetBSD__
|
|
#include <kvm.h>
|
|
#include <sys/sched.h>
|
|
#endif
|
|
|
|
#include <sys/types.h>
|
|
#include <signal.h>
|
|
#include <unistd.h>
|
|
|
|
#include <tqfile.h>
|
|
#include <tqstring.h>
|
|
#include <tqstringlist.h>
|
|
#include <tqtextstream.h>
|
|
#include <tqdir.h>
|
|
#include <tqtimer.h>
|
|
#include <tqmap.h>
|
|
#include <tqdatetime.h>
|
|
|
|
#include <tdelocale.h>
|
|
|
|
#include "NaughtyProcessMonitor.h"
|
|
|
|
class NaughtyProcessMonitorPrivate
|
|
{
|
|
public:
|
|
|
|
NaughtyProcessMonitorPrivate()
|
|
: interval_(0),
|
|
timer_(0),
|
|
oldLoad_(0),
|
|
triggerLevel_(0)
|
|
{
|
|
}
|
|
|
|
~NaughtyProcessMonitorPrivate()
|
|
{
|
|
// Empty.
|
|
}
|
|
|
|
uint interval_;
|
|
TQTimer * timer_;
|
|
TQMap<ulong, uint> loadMap_;
|
|
TQMap<ulong, uint> scoreMap_;
|
|
#if defined(__OpenBSD__) || defined(__NetBSD__)
|
|
TQMap<ulong, uint> cacheLoadMap_;
|
|
TQMap<ulong, uid_t> uidMap_;
|
|
#endif
|
|
#ifdef __NetBSD__
|
|
kvm_t *kd;
|
|
#endif
|
|
uint oldLoad_;
|
|
uint triggerLevel_;
|
|
|
|
private:
|
|
|
|
NaughtyProcessMonitorPrivate(const NaughtyProcessMonitorPrivate &);
|
|
|
|
NaughtyProcessMonitorPrivate & operator =
|
|
(const NaughtyProcessMonitorPrivate &);
|
|
};
|
|
|
|
NaughtyProcessMonitor::NaughtyProcessMonitor
|
|
(
|
|
uint interval,
|
|
uint triggerLevel,
|
|
TQObject * parent,
|
|
const char * name
|
|
)
|
|
: TQObject(parent, name)
|
|
{
|
|
d = new NaughtyProcessMonitorPrivate;
|
|
d->interval_ = interval * 1000;
|
|
d->triggerLevel_ = triggerLevel;
|
|
d->timer_ = new TQTimer(this, "NaughtyProcessMonitorPrivate::timer");
|
|
#ifdef __NetBSD__
|
|
d->kd = kvm_open(NULL, NULL, NULL, KVM_NO_FILES, "kvm_open");
|
|
#endif
|
|
connect(d->timer_, TQT_SIGNAL(timeout()), this, TQT_SLOT(slotTimeout()));
|
|
}
|
|
|
|
NaughtyProcessMonitor::~NaughtyProcessMonitor()
|
|
{
|
|
#ifdef __NetBSD__
|
|
kvm_close(d->kd);
|
|
#endif
|
|
delete d;
|
|
}
|
|
|
|
void
|
|
NaughtyProcessMonitor::start()
|
|
{
|
|
d->timer_->start(d->interval_, true);
|
|
}
|
|
|
|
void
|
|
NaughtyProcessMonitor::stop()
|
|
{
|
|
d->timer_->stop();
|
|
}
|
|
|
|
uint
|
|
NaughtyProcessMonitor::interval() const
|
|
{
|
|
return d->interval_ / 1000;
|
|
}
|
|
|
|
void
|
|
NaughtyProcessMonitor::setInterval(uint i)
|
|
{
|
|
stop();
|
|
d->interval_ = i * 1000;
|
|
start();
|
|
}
|
|
|
|
uint
|
|
NaughtyProcessMonitor::triggerLevel() const
|
|
{
|
|
return d->triggerLevel_;
|
|
}
|
|
|
|
void
|
|
NaughtyProcessMonitor::setTriggerLevel(uint i)
|
|
{
|
|
d->triggerLevel_ = i;
|
|
}
|
|
|
|
void
|
|
NaughtyProcessMonitor::slotTimeout()
|
|
{
|
|
uint cpu = cpuLoad();
|
|
|
|
emit(load(cpu));
|
|
|
|
if (cpu > d->triggerLevel_ * (d->interval_ / 1000))
|
|
{
|
|
uint load;
|
|
TQValueList<ulong> l(pidList());
|
|
|
|
for (TQValueList<ulong>::ConstIterator it(l.begin()); it != l.end(); ++it)
|
|
if (getLoad(*it, load))
|
|
_process(*it, load);
|
|
}
|
|
|
|
d->timer_->start(d->interval_, true);
|
|
}
|
|
|
|
void
|
|
NaughtyProcessMonitor::_process(ulong pid, uint load)
|
|
{
|
|
if (!d->loadMap_.contains(pid))
|
|
{
|
|
d->loadMap_.insert(pid, load);
|
|
return;
|
|
}
|
|
|
|
uint oldLoad = d->loadMap_[pid];
|
|
bool misbehaving = (load - oldLoad) > 40 * (d->interval_ / 1000);
|
|
bool wasMisbehaving = d->scoreMap_.contains(pid);
|
|
|
|
if (misbehaving)
|
|
if (wasMisbehaving)
|
|
{
|
|
d->scoreMap_.replace(pid, d->scoreMap_[pid] + 1);
|
|
if (canKill(pid))
|
|
emit(runawayProcess(pid, processName(pid)));
|
|
}
|
|
else
|
|
d->scoreMap_.insert(pid, 1);
|
|
else
|
|
if (wasMisbehaving)
|
|
d->scoreMap_.remove(pid);
|
|
|
|
d->loadMap_.replace(pid, load);
|
|
}
|
|
|
|
// Here begins the set of system-specific methods.
|
|
|
|
bool
|
|
NaughtyProcessMonitor::canKill(ulong pid) const
|
|
{
|
|
#ifdef __linux__
|
|
TQFile f("/proc/" + TQString::number(pid) + "/status");
|
|
|
|
if (!f.open(IO_ReadOnly))
|
|
return false;
|
|
|
|
TQTextStream t(&f);
|
|
|
|
TQString s;
|
|
|
|
while (!t.atEnd() && s.left(4) != "Uid:")
|
|
s = t.readLine();
|
|
|
|
TQStringList l(TQStringList::split('\t', s));
|
|
|
|
uint a(l[1].toUInt());
|
|
|
|
// What are these 3 fields for ? Would be nice if the Linux kernel docs
|
|
// were complete, eh ?
|
|
// uint b(l[2].toUInt());
|
|
// uint c(l[3].toUInt());
|
|
// uint d(l[4].toUInt());
|
|
|
|
return geteuid() == a;
|
|
#elif defined(__OpenBSD__) || defined(__NetBSD__)
|
|
// simply check if entry exists in the uid map and use it
|
|
if (!d->uidMap_.contains(pid))
|
|
return false ;
|
|
|
|
return geteuid () == d->uidMap_[pid] ;
|
|
#else
|
|
Q_UNUSED( pid );
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
TQString
|
|
NaughtyProcessMonitor::processName(ulong pid) const
|
|
{
|
|
#if defined(__linux__) || defined(__OpenBSD__) || defined(__NetBSD__)
|
|
#ifdef __linux__
|
|
TQFile f("/proc/" + TQString::number(pid) + "/cmdline");
|
|
|
|
if (!f.open(IO_ReadOnly))
|
|
return i18n("Unknown");
|
|
|
|
TQCString s;
|
|
|
|
while (true)
|
|
{
|
|
int c = f.getch();
|
|
|
|
// Stop at NUL
|
|
if (c == -1 || char(c) == '\0')
|
|
break;
|
|
else
|
|
s += char(c);
|
|
}
|
|
|
|
// Now strip 'tdeinit:' prefix.
|
|
TQString unicode(TQString::fromLocal8Bit(s));
|
|
|
|
#elif defined(__NetBSD__)
|
|
struct kinfo_proc2 *p;
|
|
int len;
|
|
char **argv;
|
|
|
|
p = kvm_getproc2(d->kd, KERN_PROC_PID, pid,
|
|
sizeof(struct kinfo_proc2), &len);
|
|
if (len < 1) {
|
|
return i18n("Unknown");
|
|
}
|
|
|
|
// Now strip 'tdeinit:' prefix.
|
|
TQString unicode(TQString::fromLocal8Bit(p->p_comm));
|
|
|
|
if (unicode == "tdeinit") {
|
|
argv = kvm_getargv2(d->kd, p, 100);
|
|
while (argv != NULL && (*argv == "tdeinit:")) {
|
|
argv++;
|
|
}
|
|
if (argv != NULL) {
|
|
unicode = *argv;
|
|
}
|
|
}
|
|
#elif defined(__OpenBSD__)
|
|
int mib[4] ;
|
|
size_t size ;
|
|
char **argv ;
|
|
|
|
// fetch argv for the process `pid'
|
|
|
|
mib[0] = CTL_KERN ;
|
|
mib[1] = KERN_PROC_ARGS ;
|
|
mib[2] = pid ;
|
|
mib[3] = KERN_PROC_ARGV ;
|
|
|
|
// we assume argv[0]'s size will be less than one page
|
|
|
|
size = getpagesize () ;
|
|
argv = (char **)calloc (size, sizeof (char)) ;
|
|
size-- ; // ensure argv is ended by 0
|
|
if (-1 == sysctl (mib, 4, argv, &size, NULL, 0)) {
|
|
free (argv) ;
|
|
return i18n("Unknown") ;
|
|
}
|
|
|
|
// Now strip 'tdeinit:' prefix.
|
|
TQString unicode(TQString::fromLocal8Bit(argv[0]));
|
|
|
|
free (argv) ;
|
|
#endif
|
|
|
|
TQStringList parts(TQStringList::split(' ', unicode));
|
|
|
|
TQString processName = parts[0] == "tdeinit:" ? parts[1] : parts[0];
|
|
|
|
int lastSlash = processName.findRev('/');
|
|
|
|
// Get basename, if there's a path.
|
|
if (-1 != lastSlash)
|
|
processName = processName.mid(lastSlash + 1);
|
|
|
|
return processName;
|
|
|
|
#else
|
|
Q_UNUSED( pid );
|
|
return TQString::null;
|
|
#endif
|
|
}
|
|
|
|
uint
|
|
NaughtyProcessMonitor::cpuLoad() const
|
|
{
|
|
#ifdef __linux__
|
|
TQFile f("/proc/stat");
|
|
|
|
if (!f.open(IO_ReadOnly))
|
|
return 0;
|
|
|
|
bool forgetThisOne = 0 == d->oldLoad_;
|
|
|
|
TQTextStream t(&f);
|
|
|
|
TQString s = t.readLine();
|
|
|
|
TQStringList l(TQStringList::split(' ', s));
|
|
|
|
uint user = l[1].toUInt();
|
|
uint sys = l[3].toUInt();
|
|
|
|
uint load = user + sys;
|
|
uint diff = load - d->oldLoad_;
|
|
d->oldLoad_ = load;
|
|
|
|
return (forgetThisOne ? 0 : diff);
|
|
#elif defined(__OpenBSD__) || defined(__NetBSD__)
|
|
int mib[2] ;
|
|
#ifdef __NetBSD__
|
|
u_int64_t cp_time[CPUSTATES] ;
|
|
#else
|
|
long cp_time[CPUSTATES] ;
|
|
#endif
|
|
size_t size ;
|
|
uint load, diff ;
|
|
bool forgetThisOne = 0 == d->oldLoad_;
|
|
|
|
// fetch CPU time statistics
|
|
|
|
mib[0] = CTL_KERN ;
|
|
mib[1] = KERN_CP_TIME ;
|
|
|
|
size = CPUSTATES * sizeof(cp_time[0]) ;
|
|
|
|
if (-1 == sysctl (mib, 2, cp_time, &size, NULL, 0))
|
|
return 0 ;
|
|
|
|
load = cp_time[CP_USER] + cp_time[CP_SYS] ;
|
|
diff = load - d->oldLoad_ ;
|
|
d->oldLoad_ = load ;
|
|
|
|
return (forgetThisOne ? 0 : diff);
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
TQValueList<ulong>
|
|
NaughtyProcessMonitor::pidList() const
|
|
{
|
|
#ifdef __linux__
|
|
TQStringList dl(TQDir("/proc").entryList());
|
|
|
|
TQValueList<ulong> pl;
|
|
|
|
for (TQStringList::ConstIterator it(dl.begin()); it != dl.end(); ++it)
|
|
if (((*it)[0].isDigit()))
|
|
pl << (*it).toUInt();
|
|
|
|
return pl;
|
|
#elif defined(__NetBSD__)
|
|
struct kinfo_proc2 *kp;
|
|
int nentries;
|
|
int i;
|
|
TQValueList<ulong> l;
|
|
|
|
kp = kvm_getproc2(d->kd, KERN_PROC_ALL, 0,
|
|
sizeof(struct kinfo_proc2), &nentries);
|
|
|
|
// time statictics and euid data are fetched only for proceses in
|
|
// the pidList, so, instead of doing on sysctl per process for
|
|
// getLoad and canKill calls, simply cache the data we already have.
|
|
|
|
d->cacheLoadMap_.clear();
|
|
d->uidMap_.clear();
|
|
for (i = 0; i < nentries; i++) {
|
|
i << (unsigned long) kp[i].p_pid;
|
|
d->cacheLoadMap_.insert (kp[i].p_pid,
|
|
kp[i].p_cpticks);
|
|
d->uidMap_.insert (kp[i].p_pid,
|
|
kp[i].p_uid);
|
|
}
|
|
|
|
return l;
|
|
|
|
#elif defined(__OpenBSD__)
|
|
int mib[3] ;
|
|
int nprocs = 0, nentries ;
|
|
size_t size ;
|
|
struct kinfo_proc *kp ;
|
|
int i ;
|
|
TQValueList<ulong> l;
|
|
|
|
// fetch number of processes
|
|
|
|
mib[0] = CTL_KERN ;
|
|
mib[1] = KERN_NPROCS ;
|
|
|
|
if (-1 == sysctl (mib, 2, &nprocs, &size, NULL, 0))
|
|
return l ;
|
|
|
|
// magic size evaluation ripped from ps
|
|
|
|
size = (5 * nprocs * sizeof(struct kinfo_proc)) / 4 ;
|
|
kp = (struct kinfo_proc *)calloc (size, sizeof (char)) ;
|
|
|
|
// fetch process info
|
|
|
|
mib[0] = CTL_KERN ;
|
|
mib[1] = KERN_PROC ;
|
|
mib[2] = KERN_PROC_ALL ;
|
|
|
|
if (-1 == sysctl (mib, 3, kp, &size, NULL, 0)) {
|
|
free (kp) ;
|
|
return l ;
|
|
}
|
|
|
|
nentries = size / sizeof (struct kinfo_proc) ;
|
|
|
|
// time statistics and euid data are fetched only for processes in
|
|
// the pidList, so, instead of doing one sysctl per process for
|
|
// getLoad and canKill calls, simply cache the data we already have.
|
|
|
|
d->cacheLoadMap_.clear () ;
|
|
d->uidMap_.clear () ;
|
|
for (i = 0; i < nentries; i++) {
|
|
#ifdef __OpenBSD__
|
|
l << (unsigned long) kp[i].p_pid ;
|
|
d->cacheLoadMap_.insert (kp[i].p_pid,
|
|
(kp[i].p_uticks +
|
|
kp[i].p_sticks)) ;
|
|
d->uidMap_.insert (kp[i].p_pid,
|
|
kp[i].p_uid) ;
|
|
#else
|
|
l << (unsigned long) kp[i].kp_proc.p_pid ;
|
|
d->cacheLoadMap_.insert (kp[i].kp_proc.p_pid,
|
|
(kp[i].kp_proc.p_uticks +
|
|
kp[i].kp_proc.p_sticks)) ;
|
|
d->uidMap_.insert (kp[i].kp_proc.p_pid,
|
|
kp[i].kp_eproc.e_ucred.cr_uid) ;
|
|
#endif
|
|
}
|
|
|
|
free (kp) ;
|
|
|
|
return l ;
|
|
#else
|
|
TQValueList<ulong> l;
|
|
return l;
|
|
#endif
|
|
}
|
|
|
|
bool
|
|
NaughtyProcessMonitor::getLoad(ulong pid, uint & load) const
|
|
{
|
|
#ifdef __linux__
|
|
TQFile f("/proc/" + TQString::number(pid) + "/stat");
|
|
|
|
if (!f.open(IO_ReadOnly))
|
|
return false;
|
|
|
|
TQTextStream t(&f);
|
|
|
|
TQString line(t.readLine());
|
|
|
|
TQStringList fields(TQStringList::split(' ', line));
|
|
|
|
uint userTime (fields[13].toUInt());
|
|
uint sysTime (fields[14].toUInt());
|
|
|
|
load = userTime + sysTime;
|
|
|
|
return true;
|
|
#elif defined(__OpenBSD__) || defined(__NetBSD__)
|
|
// use cache
|
|
if (!d->cacheLoadMap_.contains(pid))
|
|
return false ;
|
|
|
|
load = d->cacheLoadMap_[pid] ;
|
|
return true ;
|
|
#else
|
|
Q_UNUSED( pid );
|
|
Q_UNUSED( load );
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool
|
|
NaughtyProcessMonitor::kill(ulong pid) const
|
|
{
|
|
#if defined(__linux__) || defined(__OpenBSD__) || defined(__NetBSD__)
|
|
return 0 == ::kill(pid, SIGKILL);
|
|
#else
|
|
Q_UNUSED( pid );
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
#include "NaughtyProcessMonitor.moc"
|